鸿蒙学习实战之路-PDF页面文本图片与批注最佳实践

最近好多朋友问我:“西兰花啊,我用PDF Kit搞文档编辑,咋添加个文本图片就报错?批注也搞不明白?这可咋整?” 害,这问题我太熟了!今天我就手把手带你搞定PDF页面的文本添加、图片插入和批注功能,这些可是PDF编辑的核心操作,学会了这个,你就能随便丰富文档内容啦~

一、核心接口速览

PDF页面内容编辑,主要就这些核心接口:

接口名 功能描述
addTextObject 添加文本内容(只可按行添加)
addImageObject 在PDF文档页面中添加图片
deleteGraphicsObject 删除指定的图形对象
addAnnotation 在当前页添加批注

这几个接口就是咱们今天要摆弄的"画笔和颜料",用好了,PDF页面任你画~

二、添加文本,轻松搞定

1. 准备工作

import { pdfService } from '@kit.PDFKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { Font } from '@kit.ArkUI';

@Entry
@Component
struct PdfPage {
  private pdfDocument: pdfService.PdfDocument = new pdfService.PdfDocument();
  private context = this.getUIContext().getHostContext() as Context;

  aboutToAppear(): void {
    // 确保沙箱目录有input.pdf文档
    let filePath = this.context.filesDir + '/input.pdf';
    this.pdfDocument.loadDocument(filePath);
  }

2. 添加文本内容

Button('添加文本')
  .onClick(async () => {
    // 获取要编辑的页面
    let page: pdfService.PdfPage = this.pdfDocument.getPage(0);
    
    // 准备要添加的文本
    let textContent = '这是用鸿蒙PDF Kit添加的文本!';
    
    // 设置字体信息
    let fontInfo = new pdfService.FontInfo();
    let font: Font = new Font();
    fontInfo.fontPath = font.getFontByName('HarmonyOS Sans')?.path;
    fontInfo.fontName = '';
    
    // 设置文本样式
    let style: pdfService.TextStyle = {
      textColor: 0x000000,  // 黑色文本
      textSize: 30,         // 30号字体
      fontInfo: fontInfo    // 使用的字体
    };
    
    // 在指定位置添加文本(x:10, y:10)
    page.addTextObject(textContent, 10, 10, style);
    
    // 保存文档
    let outPdfPath = this.context.filesDir + '/testAddText.pdf';
    let result = this.pdfDocument.saveDocument(outPdfPath);
    
    hilog.info(0x0000, 'PdfPage', '添加文本 %{public}s!', result ? '成功' : '失败');
  })

3. 删除文本内容

Button('删除文本')
  .onClick(async () => {
    // 获取要编辑的页面
    let page: pdfService.PdfPage = this.pdfDocument.getPage(0);
    
    // 获取页面上所有的图形对象
    let graphicsObjects = page.getGraphicsObjects();
    
    // 找到第一个文本对象
    let index = graphicsObjects.findIndex(
      item => item.type === pdfService.GraphicsObjectType.OBJECT_TEXT
    );
    
    // 如果找到文本对象,就删除它
    if (index > -1) {
      page.deleteGraphicsObject(graphicsObjects[index]);
    }
    
    // 保存文档
    let outPdfPath = this.context.filesDir + '/testDelText.pdf';
    let result = this.pdfDocument.saveDocument(outPdfPath);
    
    hilog.info(0x0000, 'PdfPage', '删除文本 %{public}s!', result ? '成功' : '失败');
  })

三、添加图片,so easy!

Button('添加图片')
  .onClick(async () => {
    // 获取要编辑的页面
    let page: pdfService.PdfPage = this.pdfDocument.getPage(0);
    
    // 设置图片路径(确保沙箱目录有img.jpg图片)
    let imagePath = this.context.filesDir + '/img.jpg';
    
    // 在指定位置添加图片(x:100, y:100),宽100,高120
    page.addImageObject(imagePath, 100, 100, 100, 120);
    
    // 保存文档
    let outPdfPath = this.context.filesDir + '/testAddImage.pdf';
    let result = this.pdfDocument.saveDocument(outPdfPath);
    
    hilog.info(0x0000, 'PdfPage', '添加图片 %{public}s!', result ? '成功' : '失败');
  })

四、添加批注,让文档更生动

PDF Kit支持13种批注类型,包括文本批注、下划线批注、高亮批注、删除线批注等。咱们先从最常用的文本批注开始~

1. 添加文本批注

Button('添加文本批注')
  .onClick(async () => {
    // 确保沙箱目录有input.pdf文档
    let filePath = this.context.filesDir + '/input.pdf';
    this.pdfDocument.loadDocument(filePath);
    
    // 获取要编辑的页面
    let page: pdfService.PdfPage = this.pdfDocument.getPage(0);
    
    // 创建文本批注对象
    let annotationInfo = new pdfService.TextAnnotationInfo();
    annotationInfo.iconName = 'cument Format';
    annotationInfo.content = '这是批注内容,用来标注重要信息!';
    annotationInfo.subject = '批注';
    annotationInfo.title = '这是批注标题';
    annotationInfo.state = pdfService.TextAnnotationState.MARKED;
    annotationInfo.x = 200;  // 批注位置x坐标
    annotationInfo.y = 200;  // 批注位置y坐标
    annotationInfo.color = 0xf9b1b1;  // 批注颜色(浅红色)
    annotationInfo.flag = pdfService.AnnotationFlag.PRINTED;  // 打印时包含批注
    
    // 添加批注到页面
    let annotation: pdfService.PdfAnnotation = page.addAnnotation(annotationInfo);
    
    // 保存文档
    let outPdfPath = this.context.filesDir + '/testAddTextAnnotation.pdf';
    let result = this.pdfDocument.saveDocument(outPdfPath);
    
    this.pdfDocument.releaseDocument();
    hilog.info(0x0000, 'PdfPage', '添加文本批注 %{public}s!', result ? '成功' : '失败');
  })

2. 修改文本批注

Button('修改文本批注')
  .onClick(async () => {
    // 打开包含批注的PDF文档
    let filePath = this.context.filesDir + '/testAddTextAnnotation.pdf';
    let result = this.pdfDocument.loadDocument(filePath);
    
    if (result === pdfService.ParseResult.PARSE_SUCCESS) {
      // 获取要编辑的页面
      let page: pdfService.PdfPage = this.pdfDocument.getPage(0);
      
      // 获取页面上所有批注
      let annotations = page.getAnnotations();
      
      // 找到第一个文本批注
      if (annotations.length > 0 && annotations[0].type === pdfService.AnnotationType.TEXT) {
        // 先删除旧批注
        page.removeAnnotation(annotations[0]);
        
        // 创建新的批注信息
        let newAnnotationInfo = new pdfService.TextAnnotationInfo();
        newAnnotationInfo.title = '修改后的标题';
        newAnnotationInfo.content = '修改后的批注内容!';
        newAnnotationInfo.state = pdfService.TextAnnotationState.MARKED;
        newAnnotationInfo.x = 100;  // 新位置x坐标
        newAnnotationInfo.y = 100;  // 新位置y坐标
        
        // 添加新批注
        page.addAnnotation(newAnnotationInfo);
        
        // 保存文档
        let outPdfPath = this.context.filesDir + '/testSetAnnotation.pdf';
        let saveResult = this.pdfDocument.saveDocument(outPdfPath);
        
        this.pdfDocument.releaseDocument();
        hilog.info(0x0000, 'PdfPage', '修改文本批注 %{public}s!', saveResult ? '成功' : '失败');
      }
    }
  })

3. 删除文本批注

Button('删除文本批注')
  .onClick(async () => {
    // 打开包含批注的PDF文档
    let filePath = this.context.filesDir + '/testAddTextAnnotation.pdf';
    let result = this.pdfDocument.loadDocument(filePath);
    
    if (result === pdfService.ParseResult.PARSE_SUCCESS) {
      // 获取要编辑的页面
      let page: pdfService.PdfPage = this.pdfDocument.getPage(0);
      
      // 获取页面上所有批注
      let annotations = page.getAnnotations();
      
      // 找到第一个文本批注并删除
      if (annotations.length > 0 && annotations[0].type === pdfService.AnnotationType.TEXT) {
        page.removeAnnotation(annotations[0]);
        
        // 保存文档
        let outPdfPath = this.context.filesDir + '/testRemoveAnnotation.pdf';
        let saveResult = this.pdfDocument.saveDocument(outPdfPath);
        
        this.pdfDocument.releaseDocument();
        hilog.info(0x0000, 'PdfPage', '删除文本批注 %{public}s!', saveResult ? '成功' : '失败');
      }
    }
  })

🥦 西兰花警告

  • 文本添加限制:addTextObject只能按行添加文本,不能自动换行,长文本需要自己处理换行逻辑!
  • 字体路径问题:字体路径一定要正确,不然会导致文本显示异常或者添加失败!
  • 图片格式支持:目前支持的图片格式有限,建议使用JPG格式,避免使用PNG透明图片!
  • 资源释放:操作完文档一定要记得调用releaseDocument(),不然会内存泄漏!
  • 坐标系统:PDF的坐标系统原点在左下角,和咱们平时用的屏幕坐标不太一样,别搞反了!

🥦 西兰花小贴士

  • 文本样式:可以设置字体大小、颜色、字体类型等,让文本更美观
  • 图片位置:添加图片时,要注意坐标和尺寸,避免图片超出页面范围
  • 批注类型:PDF Kit支持13种批注类型,可以根据需要选择合适的批注样式
  • 批量操作:如果要添加大量文本或图片,建议先缓存样式,减少重复创建
  • 性能优化:处理大量内容时,尽量减少saveDocument的调用次数

五、常见问题与解决方案

1. 添加文本时提示"字体不存在"

问题:设置字体路径后,提示"Font not found"

解决方案:使用系统默认字体,或者确保字体文件存在:

// 正确写法
let font: Font = new Font();
fontInfo.fontPath = font.getFontByName('HarmonyOS Sans')?.path;
// 如果获取不到,使用系统默认字体
if (!fontInfo.fontPath) {
  fontInfo.fontName = 'Helvetica';  // 使用默认字体
}

2. 添加图片时提示"文件不存在"

问题:图片路径写的是"/sdcard/img.jpg",结果报错

解决方案:鸿蒙应用有沙箱限制,改用应用沙箱路径:

// 正确写法
let imagePath = this.context.filesDir + '/img.jpg';

3. 批注显示位置不对

问题:设置的坐标看起来是对的,但批注显示位置却不对

解决方案:记住PDF的坐标系统原点在左下角,不是左上角:

// 调整坐标到页面上半部分
let pageHeight = page.getHeight();
annotationInfo.y = pageHeight - 200;  // 距离顶部200像素

六、批注类型一览

PDF Kit支持13种批注类型,除了文本批注,还有这些常用的:

  • LineAnnotationInfo:下划线批注
  • HighlightAnnotationInfo:高亮批注
  • StrikethroughAnnotationInfo:删除线批注

每种批注都有自己的特性和使用场景,可以根据需要选择合适的批注类型~

七、完整代码示例

import { pdfService } from '@kit.PDFKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { Font } from '@kit.ArkUI';

@Entry
@Component
struct PdfPage {
  private pdfDocument: pdfService.PdfDocument = new pdfService.PdfDocument();
  private context = this.getUIContext().getHostContext() as Context;

  aboutToAppear(): void {
    // 确保沙箱目录有input.pdf文档
    let filePath = this.context.filesDir + '/input.pdf';
    this.pdfDocument.loadDocument(filePath);
  }

  build() {
    Column() {
      // 添加文本
      Button('添加文本')
        .margin(10)
        .onClick(async () => {
          try {
            let page: pdfService.PdfPage = this.pdfDocument.getPage(0);
            let textContent = '这是用鸿蒙PDF Kit添加的文本!';
            
            let fontInfo = new pdfService.FontInfo();
            let font: Font = new Font();
            fontInfo.fontPath = font.getFontByName('HarmonyOS Sans')?.path;
            fontInfo.fontName = '';
            
            let style: pdfService.TextStyle = {
              textColor: 0x000000,
              textSize: 30,
              fontInfo: fontInfo
            };
            
            page.addTextObject(textContent, 10, 10, style);
            
            let outPdfPath = this.context.filesDir + '/testAddText.pdf';
            let result = this.pdfDocument.saveDocument(outPdfPath);
            
            hilog.info(0x0000, 'PdfPage', '添加文本 %{public}s!', result ? '成功' : '失败');
          } catch (e) {
            hilog.error(0x0000, 'PdfPage', '添加文本失败: %{public}s', JSON.stringify(e));
          }
        })

      // 删除文本
      Button('删除文本')
        .margin(10)
        .onClick(async () => {
          try {
            let page: pdfService.PdfPage = this.pdfDocument.getPage(0);
            let graphicsObjects = page.getGraphicsObjects();
            
            let index = graphicsObjects.findIndex(
              item => item.type === pdfService.GraphicsObjectType.OBJECT_TEXT
            );
            
            if (index > -1) {
              page.deleteGraphicsObject(graphicsObjects[index]);
            }
            
            let outPdfPath = this.context.filesDir + '/testDelText.pdf';
            let result = this.pdfDocument.saveDocument(outPdfPath);
            
            hilog.info(0x0000, 'PdfPage', '删除文本 %{public}s!', result ? '成功' : '失败');
          } catch (e) {
            hilog.error(0x0000, 'PdfPage', '删除文本失败: %{public}s', JSON.stringify(e));
          }
        })

      // 添加图片
      Button('添加图片')
        .margin(10)
        .onClick(async () => {
          try {
            let page: pdfService.PdfPage = this.pdfDocument.getPage(0);
            let imagePath = this.context.filesDir + '/img.jpg';
            
            page.addImageObject(imagePath, 100, 100, 100, 120);
            
            let outPdfPath = this.context.filesDir + '/testAddImage.pdf';
            let result = this.pdfDocument.saveDocument(outPdfPath);
            
            hilog.info(0x0000, 'PdfPage', '添加图片 %{public}s!', result ? '成功' : '失败');
          } catch (e) {
            hilog.error(0x0000, 'PdfPage', '添加图片失败: %{public}s', JSON.stringify(e));
          }
        })

      // 添加文本批注
      Button('添加文本批注')
        .margin(10)
        .onClick(async () => {
          try {
            let filePath = this.context.filesDir + '/input.pdf';
            this.pdfDocument.loadDocument(filePath);
            
            let page: pdfService.PdfPage = this.pdfDocument.getPage(0);
            let annotationInfo = new pdfService.TextAnnotationInfo();
            annotationInfo.iconName = 'cument Format';
            annotationInfo.content = '这是批注内容,用来标注重要信息!';
            annotationInfo.subject = '批注';
            annotationInfo.title = '这是批注标题';
            annotationInfo.state = pdfService.TextAnnotationState.MARKED;
            annotationInfo.x = 200;
            annotationInfo.y = 200;
            annotationInfo.color = 0xf9b1b1;
            annotationInfo.flag = pdfService.AnnotationFlag.PRINTED;
            
            let annotation: pdfService.PdfAnnotation = page.addAnnotation(annotationInfo);
            
            let outPdfPath = this.context.filesDir + '/testAddTextAnnotation.pdf';
            let result = this.pdfDocument.saveDocument(outPdfPath);
            
            this.pdfDocument.releaseDocument();
            hilog.info(0x0000, 'PdfPage', '添加文本批注 %{public}s!', result ? '成功' : '失败');
          } catch (e) {
            hilog.error(0x0000, 'PdfPage', '添加文本批注失败: %{public}s', JSON.stringify(e));
          }
        })
    }
    .padding(20)
  }
}

八、实用资源推荐

📚 推荐资料


我是盐焗西兰花,
不教理论,只给你能跑的代码和避坑指南。
下期见!🥦

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐