目录

如何合并多个PDF以及给PDF添加页码

问题现象

HarmonyOS Next有没有提供将多个pdf合并成一个pdf以及给pdf添加页码的API。

背景知识

insertPageFromDocument接口提供能力,将其他Document的Page添加到当前Document,Page中的批注不支持插入到当前Document。

参数说明:

参数名

类型

必填

说明

document

PdfDocument

是

PdfDocument对象。

fromIndex

number

是

从其他文档第几页开始添加,大于等于0,0为起始页。

pageCount

number

是

添加页数量,大于0,小于等于总页数。

index

number

是

从当前文档第几页开始添加,大于等于0,小于总页数,0为起始页。

addHeaderFooter接口提供能力,插入pdf文档页眉页脚。该方法属于耗时业务,需要遍历每一页去添加页眉页脚,添加页面较多时建议放到线程里去处理。

参数说明:

参数名

类型

必填

说明

info

HeaderFooterInfo

是

页眉页脚的信息。

startIndex

number

是

起始页,必须大于等于0,0为起始页。

endIndex

number

是

结束页,小于总页数。

oddPages

boolean

是

奇数页是否添加,true表示是,false表示否。

evenPages

boolean

是

偶数页是否添加,true表示是,false表示否。

解决方案

  1. aboutToAppear回调中,确保rawfile目录下有pdf文件,拷贝到沙箱内。
  2. 使用insertPageFromDocument接口将input_add.pdf文档页插入到input_src.pdf末尾的位置,并另存文档。
  3. 给生成的testInsertPageFromDocument.pdf文档添加页码。

完整示例代码如下:

import { pdfService } from '@kit.PDFKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { Font } from '@kit.ArkUI';
import { common } from '@kit.AbilityKit';
import { fileIo as fs } from '@kit.CoreFileKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { promptAction } from '@kit.ArkUI';

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

  async aboutToAppear(): Promise<void> {
    try {
      //确保rawfile目录下有pdf文件
      await this.copyRawFileToSdcard(this.context, 'input_src.pdf');
      await this.copyRawFileToSdcard(this.context, 'input_add.pdf');
      promptAction.openToast({ message: '全部拷贝完成' });
    } catch (error) {
      promptAction.openToast({ message: '文件拷贝失败' });
    }
  }

  build() {
    Column() {
      // 将input_add.pdf文档页插入到input_src.pdf末尾的位置,并另存文档
      Button('insertPageFromDocument').onClick(async () => {
        let filePath = this.context.filesDir + '/input_src.pdf';
        let loadResult: pdfService.ParseResult = this.pdfDocument.loadDocument(filePath);
        if (pdfService.ParseResult.PARSE_SUCCESS === loadResult) {
          this.lastPage = this.pdfDocument.getPageCount();
        } else {
          promptAction.openToast({ message: '加载失败' });
        }
        let pdfDoc: pdfService.PdfDocument = new pdfService.PdfDocument();
        // 确保该沙箱目录下有input_add.pdf文档
        let res = pdfDoc.loadDocument(this.context.filesDir + '/input_add.pdf');
        if (res === pdfService.ParseResult.PARSE_SUCCESS) {
          this.pdfDocument.insertPageFromDocument(pdfDoc, 0, pdfDoc.getPageCount(), this.lastPage);
          let outPdfPath = this.context.filesDir + '/testInsertPageFromDocument.pdf';
          let result = this.pdfDocument.saveDocument(outPdfPath);
          this.lastPage = this.pdfDocument.getPageCount();
          hilog.info(0x0000, 'PdfPage', 'insertPageFromDocument %{public}s!', result ? 'success' : 'fail');
        } else {
          promptAction.openToast({ message: '加载失败' });
        }
        pdfDoc.releaseDocument();
      })

      Button('addHeaderFooter').onClick(async () => {
        // 确保沙箱目录有testInsertPageFromDocument.pdf文档
        let filePath = this.context.filesDir + '/testInsertPageFromDocument.pdf';
        let res = this.pdfDocument.loadDocument(filePath);
        if (res === pdfService.ParseResult.PARSE_SUCCESS) {
          let hfInfo: pdfService.HeaderFooterInfo = new pdfService.HeaderFooterInfo();
          hfInfo.fontInfo = new pdfService.FontInfo();
          // 确保字体路径存在
          let font: Font = new Font();
          hfInfo.fontInfo.fontPath = font.getFontByName('HarmonyOS Sans')?.path;
          // 如果不知道字体的具体名称,可以为空字符串
          hfInfo.fontInfo.fontName = '';
          hfInfo.textSize = 10;
          hfInfo.charset = pdfService.CharsetType.PDF_FONT_DEFAULT_CHARSET;
          hfInfo.underline = false;
          hfInfo.textColor = 0x00000000;
          hfInfo.leftMargin = 1.0;
          hfInfo.topMargin = 40.0;
          hfInfo.rightMargin = 1.0;
          hfInfo.bottomMargin = 40.0;
          let pdfPageCount = this.pdfDocument.getPageCount();
          for (let index = 0; index < pdfPageCount; index++) {
            hfInfo.footerCenterText = `${index + 1}`;
            this.pdfDocument.addHeaderFooter(hfInfo, index, index, true, true);
          }
          let outPdfPath = this.context.filesDir + '/testAddHeaderFooter.pdf';
          let result = this.pdfDocument.saveDocument(outPdfPath);
          hilog.info(0x0000, 'PdfPage', 'addHeaderFooter %{public}s!', result ? 'success' : 'fail');
        } else {
          promptAction.openToast({ message: '加载失败' });
        }
        this.pdfDocument.releaseDocument();
      })
    }
  }

  // 拷贝pdf文件到应用沙箱目录
  private copyRawFileToSdcard(context: common.Context, pdfName: string): Promise<void> {
    return new Promise((resolve) => {
      let destRoot = context.filesDir;
      // rawfile下的文件名
      let srcFileName = pdfName;
      let destFilePath = `${destRoot}/${srcFileName}`;
      context.resourceManager.getRawFileContent(srcFileName, (error: BusinessError, data: Uint8Array) => {
        if (error) {
          promptAction.openToast({ message: '拷贝失败' });
          hilog.error(0x0000, 'PdfCopy', `Copy failed: ${error.code}`);
          return;
        }
        const fileStream = fs.createStreamSync(destFilePath, 'w+');
        fileStream.writeSync(data.buffer);
        fileStream.close();
        promptAction.openToast({ message: '拷贝成功' });
        resolve();
      });
    });
  }
}
  • 示例代码中input_src、input_add文件需要提前放在项目rawfile目录下,路径:项目/entry/src/main/resources/rawfile。
  • 先点击insertPageFromDocument按钮将pdf文件合并。
  • 再点击addHeaderFooter按钮给合并后的pdf文件添加页脚页码。
  • IDE打开Device File Browser,查看保存的pdf文件,路径如下:应用包名/data/storage/el2/base/haps/entry/files,具体参考访问设备文件。