查看: 121|回复: 3

HarmonyOS 6.1 PDF Kit实战:setMaxZoom缩放

[复制链接]
发表于 3 小时前 | 显示全部楼层 |阅读模式
在移动办公与学习场景中,PDF文档的查看和文本提取是核心需求。但传统PDF组件在高倍缩放时容易出现文字模糊、排版崩溃,或提取文本时换行符错位、水印干扰等问题。HarmonyOS NEXT 6.1(API 23)对PDF Kit(办公服务套件)进行了重要升级,新增了`setMaxZoom`和`getTextContent`两个API,分别用于控制页面视图的最大缩放比例和提取包含原始转义字符的文本。本文将基于ArkTS工程,讲解这两个API的实战用法。

## 一、能力概览与API说明

### 1. 缩放上限控制:setMaxZoom

`PdfController`新增方法:
<br/>
  1. setMaxZoom(maxZoom: number): boolean;
复制代码

- 系统能力:`SystemCapability.OfficeService.PDFService.Core`
- 参数范围:`[0.1 ~ 10.0]`,支持小数点后两位(例如`8.55`)。
- 返回值:设置成功返回`true`,若传入值小于当前最小缩放、超出范围或非法则返回`false`。
- 适用场景:电子合同签署、精密图纸阅读时防止误操作导致视图错位。

### 2. 原始文本提取:getTextContent

`PdfPage`新增方法:
<br/>
  1. getTextContent(): string;
复制代码

- 提取规格:仅提取文本型页面内容,自动过滤图片、图形、水印等非文本元素。
- 返回值:包含完整排版格式和转义字符(如`\r\n`)的原始字符串。
- 适用场景:端侧文档检索、智能摘要、数据归档等。

## 二、工程结构与路由配置

创建一个名为`PDFKitDemo`的Standard工程,物理结构如下:
<br/>
  1. PDFKitDemo
  2. ├── AppScope
  3. │   └── app.json5
  4. ├── entry
  5. │   ├── oh-package.json5
  6. │   ├── src
  7. │   │   └── main
  8. │   │       ├── ets
  9. │   │       │   ├── entryability
  10. │   │       │   │   └── EntryAbility.ets
  11. │   │       │   └── pages
  12. │   │       │       ├── Index.ets
  13. │   │       │       └── PdfDemo.ets
  14. │   │       └── resources
  15. │   │           └── base
  16. │   │               └── profile
  17. │   │                   └── main_pages.json
  18. │   └── build-profile.json5
  19. ├── oh-package.json5
  20. └── build-profile.json5
复制代码

在`entry/src/main/resources/base/profile/main_pages.json`中注册路由:
<br/>
  1. {
  2.   "src": [
  3.     "pages/Index",
  4.     "pages/PdfDemo"
  5.   ]
  6. }
复制代码

## 三、实战代码:智能控制舱

### 1. 入口页 Index.ets

此页展示API 23 PDF Kit的核心特性,并提供一个跳转按钮进入演示界面。
<br/>
  1. import { router } from '@kit.ArkUI';
  2. @Entry
  3. @Component
  4. struct Index {
  5.   build() {
  6.     Column() {
  7.       Column() {
  8.         Image($r('app.media.startIcon'))
  9.           .width(80).height(80).margin({ bottom: 16 })
  10.           .shadow({ radius: 20, color: '#38BDF8', offsetY: 4 })
  11.         Text('HarmonyOS NEXT PDF Kit')
  12.           .fontSize(24).fontWeight(FontWeight.Bold).fontColor(Color.White).margin({ bottom: 8 })
  13.         Text('办公服务套件高级中控台 · API 23 深度演练')
  14.           .fontSize(14).fontColor('#94A3B8').textAlign(TextAlign.Center)
  15.       }
  16.       .width('100%').padding({ top: 70, bottom: 50, left: 24, right: 24 })
  17.       .backgroundColor('#090D16').border({ width: { bottom: 1 }, color: '#1E293B' })
  18.       Column({ space: 24 }) {
  19.         Column({ space: 14 }) {
  20.           Text('🚀 API 23 PDF 核心特性进化')
  21.             .fontSize(16).fontWeight(FontWeight.Bold).fontColor('#38BDF8').alignSelf(ItemAlign.Start)
  22.           Text('1. 【自定义缩放上限】`setMaxZoom(maxZoom: number)`,范围0.1~10,支持小数点后2位,精细调控阅读视界。')
  23.             .fontSize(13).fontColor('#94A3B8').lineHeight(20).alignSelf(ItemAlign.Start)
  24.           Text('2. 【原始文本智能提取】`getTextContent()`,从文本型PDF页面提取包含转义符的原始字符串,构筑高保真检索基础。')
  25.             .fontSize(13).fontColor('#94A3B8').lineHeight(20).alignSelf(ItemAlign.Start)
  26.         }
  27.         .padding(24).backgroundColor('#0F172A').borderRadius(16).border({ width: 1, color: '#1E293B' })
  28.         Blank()
  29.         Button('🚀 开启 PDF Kit 智能控制舱', { type: ButtonType.Normal })
  30.           .width('100%').height(52).fontSize(16).fontWeight(FontWeight.Bold).fontColor(Color.White)
  31.           .backgroundColor('#38BDF8').borderRadius(12).shadow({ radius: 15, color: '#38BDF8', offsetY: 6 })
  32.           .onClick(() => { router.pushUrl({ url: 'pages/PdfDemo' }); })
  33.       }
  34.       .layoutWeight(1).padding(24).justifyContent(FlexAlign.SpaceBetween)
  35.     }
  36.     .width('100%').height('100%').backgroundColor('#090D16')
  37.   }
  38. }
复制代码

### 2. 核心控制页 PdfDemo.ets

此页面分为左右两栏:左侧控制PDF加载与缩放上限,右侧控制文本提取并展示结果。注意代码中包含了设备无PDF文件时的降级模拟逻辑,便于快速验证。
<br/>
  1. import { pdfViewManager, pdfService } from '@kit.PDFKit';
  2. import { common } from '@kit.AbilityKit';
  3. import { promptAction, router } from '@kit.ArkUI';
  4. interface LogItem {
  5.   timestamp: string;
  6.   message: string;
  7.   type: 'info' | 'success' | 'warning' | 'error';
  8. }
  9. @Entry
  10. @Component
  11. struct PdfDemo {
  12.   private pdfController: pdfViewManager.PdfController = new pdfViewManager.PdfController();
  13.   @State maxZoomValue: number = 5.0;
  14.   @State zoomSettingSuccess: boolean = false;
  15.   @State extractedText: string = '';
  16.   @State targetPageIndex: number = 0;
  17.   @State currentFilePath: string = '';
  18.   @State consoleLogs: LogItem[] = [];
  19.   aboutToAppear() {
  20.     let context = this.getUIContext().getHostContext() as common.UIAbilityContext;
  21.     if (context) {
  22.       this.currentFilePath = context.filesDir + '/input.pdf';
  23.     }
  24.     this.pushLog('🗂️ PDF Kit 智能处理控制舱已就绪', 'info');
  25.   }
  26.   pushLog(msg: string, type: 'info' | 'success' | 'warning' | 'error' = 'info') {
  27.     const time = new Date().toLocaleTimeString();
  28.     this.consoleLogs.unshift({ timestamp: time, message: msg, type: type });
  29.   }
  30.   // 加载文档并设置最大缩放
  31.   async handleLoadAndSetZoom() {
  32.     this.pushLog(`⏳ 正在尝试从路径加载 PDF 文档...`, 'info');
  33.     this.pushLog(`📂 物理路径: ${this.currentFilePath}`, 'info');
  34.     try {
  35.       let loadResult: pdfService.ParseResult = await this.pdfController.loadDocument(this.currentFilePath);
  36.       if (loadResult !== undefined && pdfService.ParseResult.PARSE_SUCCESS === loadResult) {
  37.         this.pushLog('💚 PDF 文档加载成功!接下来设置自定义缩放极限...', 'success');
  38.         const targetZoom = Math.round(this.maxZoomValue * 100) / 100;
  39.         let success = this.pdfController.setMaxZoom(targetZoom);
  40.         this.zoomSettingSuccess = success;
  41.         if (success) {
  42.           this.pushLog(`🎉 成功将最大缩放比例锁定为: ${targetZoom}x`, 'success');
  43.         } else {
  44.           this.pushLog(`⚠️ 缩放比例设置失败:传入值可能超出 [0.1 ~ 10] 范围`, 'warning');
  45.         }
  46.       } else {
  47.         // 降级模拟
  48.         this.pushLog('⚠️ 未能在沙箱检测到 input.pdf。启动本地虚拟解析防线...', 'warning');
  49.         const targetZoom = Math.round(this.maxZoomValue * 100) / 100;
  50.         if (targetZoom >= 0.1 && targetZoom <= 10.0) {
  51.           this.zoomSettingSuccess = true;
  52.           this.pushLog(`🎉 [虚拟底座] 虚拟文档加载并设置最大缩放比例: ${targetZoom}x 成功!`, 'success');
  53.         } else {
  54.           this.zoomSettingSuccess = false;
  55.           this.pushLog(`⚠️ [虚拟底座] 缩放参数 ${targetZoom} 超出 [0.1 ~ 10] 安全范围,返回 false`, 'error');
  56.         }
  57.       }
  58.     } catch (err) {
  59.       this.pushLog(`❌ PDF 加载崩溃: ${JSON.stringify(err)}`, 'error');
  60.     }
  61.   }
  62.   // 提取页面文本
  63.   handleExtractText() {
  64.     this.pushLog(`⏳ 正在对页面索引为 ${this.targetPageIndex} 的文本层执行高精度解析...`, 'info');
  65.     try {
  66.       let pdfDocument = new pdfService.PdfDocument();
  67.       let tempFilePath = '/data/storage/el2/base/temp/test.pdf';
  68.       let loadResult = pdfDocument.loadDocument(tempFilePath, '');
  69.       if (loadResult === pdfService.ParseResult.PARSE_SUCCESS) {
  70.         let pdfPage = pdfDocument.getPage(this.targetPageIndex);
  71.         if (pdfPage) {
  72.           let text = pdfPage.getTextContent();
  73.           this.extractedText = text;
  74.           this.pushLog(`🎉 提取页面文本成功!共获取 ${text.length} 个原始字符`, 'success');
  75.         } else {
  76.           this.pushLog('⚠️ 目标页面索引不存在或超出文档页数范围', 'warning');
  77.         }
  78.       } else {
  79.         // 降级模拟
  80.         this.pushLog('⚠️ 沙箱路径 test.pdf 未就绪。进入高保真内置文本层检索...', 'warning');
  81.         const mockPdfText = `HarmonyOS NEXT Office Service Kit\r\n` +
  82.           `---------------------------------\r\n` +
  83.           `Document Title: PDF Kit API 23 Feature Report\r\n` +
  84.           `Page Number: ${this.targetPageIndex + 1}\r\n` +
  85.           `Text Layer Version: 6.1.0(23)\r\n` +
  86.           `Extracted Result: Success\r\n` +
  87.           `Detected characters: [setMaxZoom, getTextContent]\r\n` +
  88.           `Remarks: This text contains raw escape sequences \\r\\n parsed dynamically.`;
  89.         this.extractedText = mockPdfText;
  90.         this.pushLog(`🎉 [虚拟底座] 成功模拟页面 ${this.targetPageIndex} 原始文本提取!`, 'success');
  91.       }
  92.     } catch (err) {
  93.       this.pushLog(`❌ 文本提取发生故障: ${JSON.stringify(err)}`, 'error');
  94.     }
  95.   }
  96.   build() {
  97.     Row() {
  98.       // 左侧面板:缩放控制
  99.       Column() {
  100.         Row() {
  101.           Text('PDF KIT 视界控制中控台')
  102.             .fontSize(16.5).fontWeight(FontWeight.Bold).fontColor('#F8FAFC')
  103.           Row({ space: 8 }) {
  104.             Circle().width(8).height(8).fill(this.zoomSettingSuccess ? '#10B981' : '#64748B')
  105.             Text(this.zoomSettingSuccess ? '缩放已同步' : '未同步')
  106.               .fontSize(12).fontColor('#94A3B8')
  107.           }
  108.         }
  109.         .width('100%').height(56).padding({ left: 24, right: 24 })
  110.         .backgroundColor('#0F172A').border({ width: { bottom: 1 }, color: '#334155' })
  111.         List({ space: 20 }) {
  112.           ListItem() {
  113.             Column({ space: 12 }) {
  114.               Text('📂 PDF 文档解析源')
  115.                 .fontSize(14).fontWeight(FontWeight.Bold).fontColor('#F8FAFC').alignSelf(ItemAlign.Start)
  116.               Text('请指定应用沙箱内的 PDF 文件加载绝对路径:')
  117.                 .fontSize(12).fontColor('#94A3B8').alignSelf(ItemAlign.Start)
  118.               TextInput({ text: this.currentFilePath })
  119.                 .fontColor('#FFFFFF').fontSize(12).backgroundColor('#0F172A')
  120.                 .border({ width: 1, color: '#334155' }).borderRadius(8).height(40).width('100%')
  121.                 .onChange((val: string) => { this.currentFilePath = val; })
  122.             }
  123.             .padding(16).backgroundColor('#1E293B').borderRadius(12)
  124.           }
  125.           ListItem() {
  126.             Column({ space: 14 }) {
  127.               Text('📏 自定义页面缩放上限 (setMaxZoom)')
  128.                 .fontSize(14).fontWeight(FontWeight.Bold).fontColor('#F8FAFC').alignSelf(ItemAlign.Start)
  129.               Text('设置视图的最大缩放比例。取值范围:[0.1 ~ 10.0],高精度支持到小数点后第2位。')
  130.                 .fontSize(12).fontColor('#94A3B8').alignSelf(ItemAlign.Start).lineHeight(16)
  131.               Row() {
  132.                 Text('设定最大缩放比:')
  133.                   .fontSize(13).fontColor('#94A3B8')
  134.                 Text(this.maxZoomValue.toFixed(2) + 'x')
  135.                   .fontSize(15).fontWeight(FontWeight.Bold).fontColor('#38BDF8')
  136.               }
  137.               .width('100%').justifyContent(FlexAlign.SpaceBetween)
  138.               Slider({
  139.                 value: this.maxZoomValue,
  140.                 min: 0.05,
  141.                 max: 12.0,
  142.                 step: 0.05,
  143.                 style: SliderStyle.OutSet
  144.               })
  145.                 .blockColor('#38BDF8').trackColor('#334155').selectedColor('#38BDF8')
  146.                 .onChange((value: number) => { this.maxZoomValue = value; })
  147.               Button('⚡ 加载文档并配置最大缩放比', { type: ButtonType.Normal })
  148.                 .width('100%').height(40).backgroundColor('#10B981').borderRadius(8)
  149.                 .fontColor('#000000').fontWeight(FontWeight.Bold)
  150.                 .onClick(() => { this.handleLoadAndSetZoom(); })
  151.             }
  152.             .padding(16).backgroundColor('#1E293B').borderRadius(12)
  153.           }
  154.           ListItem() {
  155.             Column({ space: 12 }) {
  156.               Text('🖥️ 操作日志实时中控台')
  157.                 .fontSize(14).fontWeight(FontWeight.Bold).fontColor('#F8FAFC').alignSelf(ItemAlign.Start)
  158.               List({ space: 8 }) {
  159.                 ForEach(this.consoleLogs, (item: LogItem) => {
  160.                   ListItem() {
  161.                     Row() {
  162.                       Text(`[${item.timestamp}] `)
  163.                         .fontSize(11).fontColor('#64748B').fontFamily('monospace')
  164.                       Text(item.message)
  165.                         .fontSize(11)
  166.                         .fontColor(
  167.                           item.type === 'success' ? '#10B981' :
  168.                           item.type === 'error' ? '#EF4444' :
  169.                           item.type === 'warning' ? '#F59E0B' : '#FFFFFF'
  170.                         )
  171.                         .layoutWeight(1)
  172.                     }
  173.                     .width('100%').alignItems(VerticalAlign.Top)
  174.                   }
  175.                 })
  176.               }
  177.               .width('100%').height(130).padding(8).backgroundColor('#090D16')
  178.               .borderRadius(8).border({ width: 1, color: '#334155' })
  179.             }
  180.             .padding(16).backgroundColor('#1E293B').borderRadius(12)
  181.           }
  182.         }
  183.         .width('100%').layoutWeight(1).padding(24)
  184.       }
  185.       .width('50%').height('100%').backgroundColor('#0F172A')
  186.       // 右侧面板:文本提取
  187.       Column() {
  188.         Column() {
  189.           Text('PDF 页面文本提取层 (getTextContent)')
  190.             .fontSize(15).fontWeight(FontWeight.Bold).fontColor('#FFFFFF').margin({ bottom: 4 })
  191.           Text('智能无纸化文本检索组件驱动')
  192.             .fontSize(12).fontColor('#94A3B8')
  193.         }
  194.         .width('100%').alignItems(HorizontalAlign.Start)
  195.         .padding({ top: 32, bottom: 24, left: 24, right: 24 })
  196.         .border({ width: { bottom: 1 }, color: '#1E293B' })
  197.         Column({ space: 16 }) {
  198.           Text('页面原始文本数据提取 (包含换行与转义字符):')
  199.             .fontSize(13).fontWeight(FontWeight.Medium).fontColor('#38BDF8').alignSelf(ItemAlign.Start)
  200.           Row({ space: 12 }) {
  201.             Text('目标页码')
  202.               .fontSize(13).fontColor('#94A3B8')
  203.             Counter({ count: this.targetPageIndex, min: 0, max: 100 })
  204.               .onChange((val: number) => { this.targetPageIndex = val; })
  205.           }
  206.           Button('🔍 提取页面原始文本', { type: ButtonType.Normal })
  207.             .width('100%').height(40).backgroundColor('#38BDF8').borderRadius(8)
  208.             .fontColor('#000000').fontWeight(FontWeight.Bold)
  209.             .onClick(() => { this.handleExtractText(); })
  210.           Scroll() {
  211.             Text(this.extractedText)
  212.               .width('100%').padding(16)
  213.               .fontSize(13).fontFamily('monospace').fontColor('#E2E8F0')
  214.               .backgroundColor('#090D16')
  215.               .borderRadius(8).border({ width: 1, color: '#334155' })
  216.           }
  217.           .width('100%').height('100%').layoutWeight(1)
  218.         }
  219.         .width('100%').layoutWeight(1).padding(24)
  220.       }
  221.       .width('50%').height('100%').backgroundColor('#0F172A')
  222.     }
  223.     .width('100%').height('100%').backgroundColor('#090D16')
  224.   }
  225. }
复制代码

## 四、关键要点与注意事项

1. **沙箱路径**:真机测试时需将PDF文件放入应用沙箱对应目录,代码中使用了`context.filesDir + '/input.pdf'`和`/data/storage/el2/base/temp/test.pdf`,请根据实际沙箱路径调整。
2. **降级模拟**:代码中提供了设备无PDF文件时的虚拟数据展示,便于在预览器或模拟器中验证UI。正式发布时应移除降级逻辑或替换为真实文件检测。
3. **返回值检查**:`setMaxZoom`返回`boolean`,建议根据返回值更新UI状态;`getTextContent`在非文本型页面返回空串,需做空值判断。
4. **性能影响**:`setMaxZoom`仅设置上限,实际缩放表现依赖视图引擎的渲染能力;`getTextContent`提取大型文档时建议放在异步任务中。

这两个API填补了HarmonyOS PDF Kit在高精度缩放控制和原始文本提取方面的空白,特别适合电子签名、合同校对、文档摘要等场景。开发者可基于上述代码快速集成到自己的鸿蒙应用中。
回复

使用道具 举报

发表于 3 小时前 | 显示全部楼层

Re: HarmonyOS 6.1 PDF Kit实战:setMaxZoom缩放

感谢楼主的分享,写得非常详细!setMaxZoom 对精密图纸阅读场景很实用,能防止误触导致视图崩掉;getTextContent 自动过滤水印和图片,提取原始换行符也很贴心。想追问一下,提取中文文本时 \r\n 转义符会不会有乱码问题?或者有没有 demo 页面的完整代码示例?方便的话能否补充一下?
回复 支持 反对

使用道具 举报

发表于 3 小时前 | 显示全部楼层

Re: HarmonyOS 6.1 PDF Kit实战:setMaxZoom缩放

感谢分享!setMaxZoom 的范围支持到小数点后两位确实很灵活,之前用其他组件调整缩放时经常遇到边界模糊的问题,这个 API 看来能精确控制最大缩放比了。getTextContent 自动过滤水印和图片这个特性也很实用,做文档检索时能省去不少后处理工作。回头在 API 23 工程里试一下。
回复 支持 反对

使用道具 举报

发表于 3 小时前 | 显示全部楼层

Re: HarmonyOS 6.1 PDF Kit实战:setMaxZoom缩放

赞一个,鸿蒙PDF Kit这次更新确实很实用。setMaxZoom 对于看图纸和合同特别重要,之前缩放大了容易崩,现在能把上限锁定在合理范围,体验提升明显。不过想请教一下 getTextContent 提取中文内容时转义字符(比如换行)对齐是否准确?有没有遇到特殊字体或竖排文本丢失格式的情况?
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-7-14 15:21 , Processed in 0.028824 second(s), 18 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部