鸿蒙学习实战之路-PDF页眉页脚添加与删除最佳实践

最近好多朋友问我:“西兰花啊,我想给PDF文档加页眉页脚,咋搞?有时候加错了想删除,又不知道咋弄?” 害,这问题我太熟了!今天我就手把手带你搞定PDF页眉页脚的添加和删除,这可是PDF文档格式化的重要操作,学会了这个,你的文档瞬间变得专业起来~

一、页眉页脚,核心接口有哪些?

PDF Kit提供了两个核心接口来处理页眉页脚:

接口名 功能描述
addHeaderFooter 插入PDF文档页眉页脚
removeHeaderFooter 删除PDF文档页眉页脚

这两个接口就是咱们今天要摆弄的"装饰工具",用好了,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;

  build() {
    Column() {
      // 添加页眉页脚
      Button('添加页眉页脚')
        .onClick(async () => {
          // 确保沙箱目录有input.pdf文档
          let filePath = this.context.filesDir + '/input.pdf';
          let loadResult = this.pdfDocument.loadDocument(filePath);
          
          if (loadResult === pdfService.ParseResult.PARSE_SUCCESS) {
            // 创建页眉页脚信息对象
            let headerFooterInfo: pdfService.HeaderFooterInfo = new pdfService.HeaderFooterInfo();
            
            // 设置字体信息
            headerFooterInfo.fontInfo = new pdfService.FontInfo();
            let font: Font = new Font();
            headerFooterInfo.fontInfo.fontPath = font.getFontByName('HarmonyOS Sans')?.path;
            headerFooterInfo.fontInfo.fontName = ''; // 如果不知道字体名称,设为空
            
            // 设置字体大小和颜色
            headerFooterInfo.textSize = 10;
            headerFooterInfo.charset = pdfService.CharsetType.PDF_FONT_DEFAULT_CHARSET;
            headerFooterInfo.underline = false;
            headerFooterInfo.textColor = 0x00000000; // 黑色
            
            // 设置边距
            headerFooterInfo.leftMargin = 1.0;
            headerFooterInfo.topMargin = 40.0;
            headerFooterInfo.rightMargin = 1.0;
            headerFooterInfo.bottomMargin = 40.0;
            
            // 设置页眉页脚内容
            // <<dd.mm.yyyy>> 是日期占位符,<<1/n>> 是页码占位符
            headerFooterInfo.headerLeftText = '左侧页眉 <<dd.mm.yyyy>> <<1/n>>';
            headerFooterInfo.headerCenterText = '居中页眉 <<m/d/yyyy>> <<1/n>>';
            headerFooterInfo.headerRightText = '右侧页眉 <<m/d>><<1>>';
            headerFooterInfo.footerLeftText = '左侧页脚 <<m/d>><<1>>';
            headerFooterInfo.footerCenterText = '居中页脚 <<m/d>><<1>>';
            headerFooterInfo.footerRightText = '右侧页脚 <<dd.mm.yyyy>><<1>>';
            
            // 添加页眉页脚到第1-5页,包括奇数页和偶数页
            this.pdfDocument.addHeaderFooter(headerFooterInfo, 1, 5, true, true);
            
            // 保存文档
            let outPdfPath = this.context.filesDir + '/testAddHeaderFooter.pdf';
            let saveResult = this.pdfDocument.saveDocument(outPdfPath);
            
            console.log('添加页眉页脚结果:', saveResult ? '成功' : '失败');
          }
          
          // 释放资源
          this.pdfDocument.releaseDocument();
        })
    }
  }
}

2. 实际应用场景

在实际开发中,我们通常会根据不同的需求设置不同的页眉页脚:

// 根据文档类型设置不同的页眉页脚
function setupHeaderFooter(pdfDoc: pdfService.PdfDocument, docType: string) {
  let headerFooterInfo: pdfService.HeaderFooterInfo = new pdfService.HeaderFooterInfo();
  
  // 基础设置
  headerFooterInfo.fontInfo = new pdfService.FontInfo();
  let font: Font = new Font();
  headerFooterInfo.fontInfo.fontPath = font.getFontByName('HarmonyOS Sans')?.path;
  headerFooterInfo.textSize = 10;
  headerFooterInfo.textColor = 0x00000000;
  
  // 根据文档类型设置不同内容
  switch (docType) {
    case 'report':
      headerFooterInfo.headerCenterText = '工作报告';
      headerFooterInfo.footerRightText = '<<dd.mm.yyyy>> 第 <<1>> 页';
      break;
    case 'contract':
      headerFooterInfo.headerCenterText = '合同文档';
      headerFooterInfo.footerRightText = '机密文档 <<1/n>>';
      break;
    case 'manual':
      headerFooterInfo.headerCenterText = '操作手册';
      headerFooterInfo.footerRightText = '版本 1.0 <<1/n>>';
      break;
  }
  
  // 添加到所有页面
  let pageCount = pdfDoc.getPageCount();
  pdfDoc.addHeaderFooter(headerFooterInfo, 1, pageCount, true, true);
}

三、删除页眉页脚,一键搞定

1. 基本用法

// 删除页眉页脚
Button('删除页眉页脚')
  .onClick(async () => {
    let filePath = this.context.filesDir + '/testAddHeaderFooter.pdf';
    let loadResult = this.pdfDocument.loadDocument(filePath);
    
    if (loadResult === pdfService.ParseResult.PARSE_SUCCESS) {
      // 检查是否有页眉页脚
      if (this.pdfDocument.hasHeaderFooter()) {
        // 删除页眉页脚
        let removeResult = this.pdfDocument.removeHeaderFooter();
        
        if (removeResult) {
          // 保存文档
          let outPdfPath = this.context.filesDir + '/removeHeaderFooter.pdf';
          let saveResult = this.pdfDocument.saveDocument(outPdfPath);
          console.log('删除页眉页脚结果:', saveResult ? '成功' : '失败');
        } else {
          console.log('删除页眉页脚失败');
        }
      } else {
        console.log('文档没有页眉页脚');
      }
    }
    
    // 释放资源
    this.pdfDocument.releaseDocument();
  })

🥦 西兰花警告

  • 性能问题:addHeaderFooter方法属于耗时操作,需要遍历每一页添加页眉页脚,页面较多时一定要放到线程里处理,不然会阻塞UI线程!
  • 字体路径:字体路径一定要正确,不然会导致页眉页脚显示异常或者添加失败!
  • 边距设置:边距设置要合理,太小会导致页眉页脚与正文重叠,太大会浪费页面空间!
  • 资源释放:操作完文档一定要记得调用releaseDocument(),不然会内存泄漏!
  • 占位符格式:日期和页码占位符要使用正确的格式,比如<<dd.mm.yyyy>>表示日期,<<1/n>>表示页码!

🥦 西兰花小贴士

  • 批量处理:添加页眉页脚时,可以指定起始页和结束页,实现部分页面添加
  • 奇偶页不同:通过设置oddPages和evenPages参数,可以为奇数页和偶数页设置不同的页眉页脚
  • 线程处理:处理大量页面时,建议使用Worker线程,避免阻塞UI
  • 预览效果:添加页眉页脚前,最好先预览一下效果,避免反复修改
  • 样式统一:保持页眉页脚的样式与文档整体风格一致,增强专业性

四、常见问题与解决方案

1. 添加页眉页脚时应用卡死

问题:调用addHeaderFooter后,应用卡死不动

解决方案:这是因为添加页眉页脚是耗时操作,需要放到线程里处理:

// 正确写法
import { worker } from '@kit.WorkerKit';

// 创建Worker来处理耗时操作
async function addHeaderFooterInWorker(filePath: string) {
  return new Promise((resolve, reject) => {
    const headerFooterWorker = worker.createWorker('headerFooterWorker.ts');
    
    headerFooterWorker.onmessage = (message) => {
      resolve(message);
      headerFooterWorker.terminate();
    };
    
    headerFooterWorker.onerror = (error) => {
      reject(error);
      headerFooterWorker.terminate();
    };
    
    headerFooterWorker.postMessage({ filePath });
  });
}

2. 页眉页脚与正文重叠

问题:添加的页眉页脚与文档正文重叠了

解决方案:增大边距设置:

// 正确写法
headerFooterInfo.topMargin = 50.0;  // 增大页眉边距
headerFooterInfo.bottomMargin = 50.0;  // 增大页脚边距

3. 页码占位符不显示

问题:设置了<<1/n>>占位符,但生成的PDF中显示的还是<<1/n>>,不是实际页码

解决方案:确保占位符格式正确,不要有多余的空格:

// 正确写法
headerFooterInfo.footerRightText = '第 <<1>> 页,共 <<n>> 页';
// 或者
headerFooterInfo.footerRightText = '<<1/n>>';

五、完整代码示例

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

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

  // 添加页眉页脚
  async addHeaderFooter() {
    try {
      let filePath = this.context.filesDir + '/input.pdf';
      let loadResult = this.pdfDocument.loadDocument(filePath);
      
      if (loadResult === pdfService.ParseResult.PARSE_SUCCESS) {
        // 创建页眉页脚信息
        let headerFooterInfo: pdfService.HeaderFooterInfo = new pdfService.HeaderFooterInfo();
        
        // 设置字体
        headerFooterInfo.fontInfo = new pdfService.FontInfo();
        let font: Font = new Font();
        headerFooterInfo.fontInfo.fontPath = font.getFontByName('HarmonyOS Sans')?.path;
        headerFooterInfo.fontInfo.fontName = '';
        
        // 设置样式
        headerFooterInfo.textSize = 10;
        headerFooterInfo.charset = pdfService.CharsetType.PDF_FONT_DEFAULT_CHARSET;
        headerFooterInfo.underline = false;
        headerFooterInfo.textColor = 0x00000000;
        
        // 设置边距
        headerFooterInfo.leftMargin = 1.0;
        headerFooterInfo.topMargin = 40.0;
        headerFooterInfo.rightMargin = 1.0;
        headerFooterInfo.bottomMargin = 40.0;
        
        // 设置内容
        headerFooterInfo.headerLeftText = '公司名称';
        headerFooterInfo.headerCenterText = '文档标题';
        headerFooterInfo.headerRightText = '<<dd.mm.yyyy>>';
        headerFooterInfo.footerLeftText = '版权所有';
        headerFooterInfo.footerCenterText = 'CONFIDENTIAL';
        headerFooterInfo.footerRightText = '第 <<1>> 页,共 <<n>> 页';
        
        // 获取页面总数
        let pageCount = this.pdfDocument.getPageCount();
        
        // 添加页眉页脚到所有页面
        this.pdfDocument.addHeaderFooter(headerFooterInfo, 1, pageCount, true, true);
        
        // 保存文档
        let outPdfPath = this.context.filesDir + '/withHeaderFooter.pdf';
        let saveResult = this.pdfDocument.saveDocument(outPdfPath);
        
        console.log('添加页眉页脚结果:', saveResult ? '成功' : '失败');
        return saveResult;
      } else {
        console.log('加载PDF失败');
        return false;
      }
    } catch (error) {
      console.error('添加页眉页脚时出错:', error);
      return false;
    } finally {
      this.pdfDocument.releaseDocument();
    }
  }

  // 删除页眉页脚
  async removeHeaderFooter() {
    try {
      let filePath = this.context.filesDir + '/withHeaderFooter.pdf';
      let loadResult = this.pdfDocument.loadDocument(filePath);
      
      if (loadResult === pdfService.ParseResult.PARSE_SUCCESS) {
        // 检查是否有页眉页脚
        if (this.pdfDocument.hasHeaderFooter()) {
          // 删除页眉页脚
          let removeResult = this.pdfDocument.removeHeaderFooter();
          
          if (removeResult) {
            // 保存文档
            let outPdfPath = this.context.filesDir + '/withoutHeaderFooter.pdf';
            let saveResult = this.pdfDocument.saveDocument(outPdfPath);
            
            console.log('删除页眉页脚结果:', saveResult ? '成功' : '失败');
            return saveResult;
          } else {
            console.log('删除页眉页脚失败');
            return false;
          }
        } else {
          console.log('文档没有页眉页脚');
          return true;
        }
      } else {
        console.log('加载PDF失败');
        return false;
      }
    } catch (error) {
      console.error('删除页眉页脚时出错:', error);
      return false;
    } finally {
      this.pdfDocument.releaseDocument();
    }
  }

  build() {
    Column() {
      Button('添加页眉页脚')
        .margin(10)
        .onClick(async () => {
          let result = await this.addHeaderFooter();
          if (result) {
            console.log('操作成功');
          } else {
            console.log('操作失败');
          }
        })

      Button('删除页眉页脚')
        .margin(10)
        .onClick(async () => {
          let result = await this.removeHeaderFooter();
          if (result) {
            console.log('操作成功');
          } else {
            console.log('操作失败');
          }
        })
    }
    .padding(20)
  }
}

五、实际应用案例

案例1:批量文档处理工具

在批量文档处理工具中,我们可以为多个PDF文档添加统一的页眉页脚:

import { pdfService } from '@kit.PDFKit';
import { worker } from '@kit.WorkerKit';

@Entry
@Component
struct BatchPdfProcessor {
  private context = this.getUIContext().getHostContext() as Context;

  // 批量添加页眉页脚
  async batchAddHeaderFooter(filePaths: string[]) {
    for (let filePath of filePaths) {
      console.log(`正在处理: ${filePath}`);
      
      // 使用Worker处理耗时操作
      await this.addHeaderFooterInWorker(filePath);
    }
    
    console.log('批量处理完成');
  }

  // 在Worker中处理
  async addHeaderFooterInWorker(filePath: string) {
    // 实现Worker处理逻辑
    // ...
  }

  build() {
    Column() {
      Button('批量添加页眉页脚')
        .onClick(async () => {
          // 假设这里获取了多个PDF文件路径
          let filePaths = [
            this.context.filesDir + '/doc1.pdf',
            this.context.filesDir + '/doc2.pdf',
            this.context.filesDir + '/doc3.pdf'
          ];
          
          await this.batchAddHeaderFooter(filePaths);
        })
    }
  }
}

案例2:文档模板生成器

在文档模板生成器中,我们可以根据用户选择的模板添加不同的页眉页脚:

import { pdfService } from '@kit.PDFKit';

@Entry
@Component
struct DocumentTemplateGenerator {
  private pdfDocument: pdfService.PdfDocument = new pdfService.PdfDocument();
  private context = this.getUIContext().getHostContext() as Context;
  @State selectedTemplate: string = 'report';

  // 根据模板设置页眉页脚
  async generateDocumentWithTemplate(template: string) {
    try {
      let filePath = this.context.filesDir + '/template.pdf';
      let loadResult = this.pdfDocument.loadDocument(filePath);
      
      if (loadResult === pdfService.ParseResult.PARSE_SUCCESS) {
        let headerFooterInfo: pdfService.HeaderFooterInfo = new pdfService.HeaderFooterInfo();
        
        // 设置基础样式
        headerFooterInfo.textSize = 10;
        headerFooterInfo.textColor = 0x00000000;
        
        // 根据模板设置内容
        switch (template) {
          case 'report':
            headerFooterInfo.headerCenterText = '工作报告';
            headerFooterInfo.footerRightText = '<<dd.mm.yyyy>> <<1/n>>';
            break;
          case 'contract':
            headerFooterInfo.headerCenterText = '合同文档';
            headerFooterInfo.footerRightText = '机密 <<1/n>>';
            break;
          case 'invoice':
            headerFooterInfo.headerCenterText = '发票';
            headerFooterInfo.footerRightText = '编号: INV-<<1>>';
            break;
        }
        
        // 添加到所有页面
        let pageCount = this.pdfDocument.getPageCount();
        this.pdfDocument.addHeaderFooter(headerFooterInfo, 1, pageCount, true, true);
        
        // 保存文档
        let outPdfPath = this.context.filesDir + `/generated_${template}.pdf`;
        let saveResult = this.pdfDocument.saveDocument(outPdfPath);
        
        console.log('生成文档结果:', saveResult ? '成功' : '失败');
      }
    } catch (error) {
      console.error('生成文档时出错:', error);
    } finally {
      this.pdfDocument.releaseDocument();
    }
  }

  build() {
    Column() {
      Text('选择文档模板')
        .fontSize(16)
        .margin(10)
      
      RadioGroup({
        value: this.selectedTemplate
      }) {
        Radio({ value: 'report' })
          .group('template')
          .onChange((isChecked: boolean) => {
            if (isChecked) {
              this.selectedTemplate = 'report';
            }
          })
        Text('工作报告')
          .marginLeft(10)
        
        Radio({ value: 'contract' })
          .group('template')
          .onChange((isChecked: boolean) => {
            if (isChecked) {
              this.selectedTemplate = 'contract';
            }
          })
        Text('合同文档')
          .marginLeft(10)
        
        Radio({ value: 'invoice' })
          .group('template')
          .onChange((isChecked: boolean) => {
            if (isChecked) {
              this.selectedTemplate = 'invoice';
            }
          })
        Text('发票')
          .marginLeft(10)
      }
      .margin(10)
      
      Button('生成文档')
        .margin(10)
        .onClick(async () => {
          await this.generateDocumentWithTemplate(this.selectedTemplate);
        })
    }
    .padding(20)
  }
}

六、实用资源推荐

📚 推荐资料


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

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐