在移动办公与学习场景中,PDF文档的查看和文本提取是核心需求。但传统PDF组件在高倍缩放时容易出现文字模糊、排版崩溃,或提取文本时换行符错位、水印干扰等问题。HarmonyOS NEXT 6.1(API 23)对PDF Kit(办公服务套件)进行了重要升级,新增了`setMaxZoom`和`getTextContent`两个API,分别用于控制页面视图的最大缩放比例和提取包含原始转义字符的文本。本文将基于ArkTS工程,讲解这两个API的实战用法。
## 一、能力概览与API说明
### 1. 缩放上限控制:setMaxZoom
`PdfController`新增方法:
<br/>- setMaxZoom(maxZoom: number): boolean;
复制代码
- 系统能力:`SystemCapability.OfficeService.PDFService.Core`
- 参数范围:`[0.1 ~ 10.0]`,支持小数点后两位(例如`8.55`)。
- 返回值:设置成功返回`true`,若传入值小于当前最小缩放、超出范围或非法则返回`false`。
- 适用场景:电子合同签署、精密图纸阅读时防止误操作导致视图错位。
### 2. 原始文本提取:getTextContent
`PdfPage`新增方法:
<br/>- getTextContent(): string;
复制代码
- 提取规格:仅提取文本型页面内容,自动过滤图片、图形、水印等非文本元素。
- 返回值:包含完整排版格式和转义字符(如`\r\n`)的原始字符串。
- 适用场景:端侧文档检索、智能摘要、数据归档等。
## 二、工程结构与路由配置
创建一个名为`PDFKitDemo`的Standard工程,物理结构如下:
<br/>- PDFKitDemo
- ├── AppScope
- │ └── app.json5
- ├── entry
- │ ├── oh-package.json5
- │ ├── src
- │ │ └── main
- │ │ ├── ets
- │ │ │ ├── entryability
- │ │ │ │ └── EntryAbility.ets
- │ │ │ └── pages
- │ │ │ ├── Index.ets
- │ │ │ └── PdfDemo.ets
- │ │ └── resources
- │ │ └── base
- │ │ └── profile
- │ │ └── main_pages.json
- │ └── build-profile.json5
- ├── oh-package.json5
- └── build-profile.json5
复制代码
在`entry/src/main/resources/base/profile/main_pages.json`中注册路由:
<br/>- {
- "src": [
- "pages/Index",
- "pages/PdfDemo"
- ]
- }
复制代码
## 三、实战代码:智能控制舱
### 1. 入口页 Index.ets
此页展示API 23 PDF Kit的核心特性,并提供一个跳转按钮进入演示界面。
<br/>- import { router } from '@kit.ArkUI';
- @Entry
- @Component
- struct Index {
- build() {
- Column() {
- Column() {
- Image($r('app.media.startIcon'))
- .width(80).height(80).margin({ bottom: 16 })
- .shadow({ radius: 20, color: '#38BDF8', offsetY: 4 })
- Text('HarmonyOS NEXT PDF Kit')
- .fontSize(24).fontWeight(FontWeight.Bold).fontColor(Color.White).margin({ bottom: 8 })
- Text('办公服务套件高级中控台 · API 23 深度演练')
- .fontSize(14).fontColor('#94A3B8').textAlign(TextAlign.Center)
- }
- .width('100%').padding({ top: 70, bottom: 50, left: 24, right: 24 })
- .backgroundColor('#090D16').border({ width: { bottom: 1 }, color: '#1E293B' })
- Column({ space: 24 }) {
- Column({ space: 14 }) {
- Text('🚀 API 23 PDF 核心特性进化')
- .fontSize(16).fontWeight(FontWeight.Bold).fontColor('#38BDF8').alignSelf(ItemAlign.Start)
- Text('1. 【自定义缩放上限】`setMaxZoom(maxZoom: number)`,范围0.1~10,支持小数点后2位,精细调控阅读视界。')
- .fontSize(13).fontColor('#94A3B8').lineHeight(20).alignSelf(ItemAlign.Start)
- Text('2. 【原始文本智能提取】`getTextContent()`,从文本型PDF页面提取包含转义符的原始字符串,构筑高保真检索基础。')
- .fontSize(13).fontColor('#94A3B8').lineHeight(20).alignSelf(ItemAlign.Start)
- }
- .padding(24).backgroundColor('#0F172A').borderRadius(16).border({ width: 1, color: '#1E293B' })
- Blank()
- Button('🚀 开启 PDF Kit 智能控制舱', { type: ButtonType.Normal })
- .width('100%').height(52).fontSize(16).fontWeight(FontWeight.Bold).fontColor(Color.White)
- .backgroundColor('#38BDF8').borderRadius(12).shadow({ radius: 15, color: '#38BDF8', offsetY: 6 })
- .onClick(() => { router.pushUrl({ url: 'pages/PdfDemo' }); })
- }
- .layoutWeight(1).padding(24).justifyContent(FlexAlign.SpaceBetween)
- }
- .width('100%').height('100%').backgroundColor('#090D16')
- }
- }
复制代码
### 2. 核心控制页 PdfDemo.ets
此页面分为左右两栏:左侧控制PDF加载与缩放上限,右侧控制文本提取并展示结果。注意代码中包含了设备无PDF文件时的降级模拟逻辑,便于快速验证。
<br/>- import { pdfViewManager, pdfService } from '@kit.PDFKit';
- import { common } from '@kit.AbilityKit';
- import { promptAction, router } from '@kit.ArkUI';
- interface LogItem {
- timestamp: string;
- message: string;
- type: 'info' | 'success' | 'warning' | 'error';
- }
- @Entry
- @Component
- struct PdfDemo {
- private pdfController: pdfViewManager.PdfController = new pdfViewManager.PdfController();
- @State maxZoomValue: number = 5.0;
- @State zoomSettingSuccess: boolean = false;
- @State extractedText: string = '';
- @State targetPageIndex: number = 0;
- @State currentFilePath: string = '';
- @State consoleLogs: LogItem[] = [];
- aboutToAppear() {
- let context = this.getUIContext().getHostContext() as common.UIAbilityContext;
- if (context) {
- this.currentFilePath = context.filesDir + '/input.pdf';
- }
- this.pushLog('🗂️ PDF Kit 智能处理控制舱已就绪', 'info');
- }
- pushLog(msg: string, type: 'info' | 'success' | 'warning' | 'error' = 'info') {
- const time = new Date().toLocaleTimeString();
- this.consoleLogs.unshift({ timestamp: time, message: msg, type: type });
- }
- // 加载文档并设置最大缩放
- async handleLoadAndSetZoom() {
- this.pushLog(`⏳ 正在尝试从路径加载 PDF 文档...`, 'info');
- this.pushLog(`📂 物理路径: ${this.currentFilePath}`, 'info');
- try {
- let loadResult: pdfService.ParseResult = await this.pdfController.loadDocument(this.currentFilePath);
- if (loadResult !== undefined && pdfService.ParseResult.PARSE_SUCCESS === loadResult) {
- this.pushLog('💚 PDF 文档加载成功!接下来设置自定义缩放极限...', 'success');
- const targetZoom = Math.round(this.maxZoomValue * 100) / 100;
- let success = this.pdfController.setMaxZoom(targetZoom);
- this.zoomSettingSuccess = success;
- if (success) {
- this.pushLog(`🎉 成功将最大缩放比例锁定为: ${targetZoom}x`, 'success');
- } else {
- this.pushLog(`⚠️ 缩放比例设置失败:传入值可能超出 [0.1 ~ 10] 范围`, 'warning');
- }
- } else {
- // 降级模拟
- this.pushLog('⚠️ 未能在沙箱检测到 input.pdf。启动本地虚拟解析防线...', 'warning');
- const targetZoom = Math.round(this.maxZoomValue * 100) / 100;
- if (targetZoom >= 0.1 && targetZoom <= 10.0) {
- this.zoomSettingSuccess = true;
- this.pushLog(`🎉 [虚拟底座] 虚拟文档加载并设置最大缩放比例: ${targetZoom}x 成功!`, 'success');
- } else {
- this.zoomSettingSuccess = false;
- this.pushLog(`⚠️ [虚拟底座] 缩放参数 ${targetZoom} 超出 [0.1 ~ 10] 安全范围,返回 false`, 'error');
- }
- }
- } catch (err) {
- this.pushLog(`❌ PDF 加载崩溃: ${JSON.stringify(err)}`, 'error');
- }
- }
- // 提取页面文本
- handleExtractText() {
- this.pushLog(`⏳ 正在对页面索引为 ${this.targetPageIndex} 的文本层执行高精度解析...`, 'info');
- try {
- let pdfDocument = new pdfService.PdfDocument();
- let tempFilePath = '/data/storage/el2/base/temp/test.pdf';
- let loadResult = pdfDocument.loadDocument(tempFilePath, '');
- if (loadResult === pdfService.ParseResult.PARSE_SUCCESS) {
- let pdfPage = pdfDocument.getPage(this.targetPageIndex);
- if (pdfPage) {
- let text = pdfPage.getTextContent();
- this.extractedText = text;
- this.pushLog(`🎉 提取页面文本成功!共获取 ${text.length} 个原始字符`, 'success');
- } else {
- this.pushLog('⚠️ 目标页面索引不存在或超出文档页数范围', 'warning');
- }
- } else {
- // 降级模拟
- this.pushLog('⚠️ 沙箱路径 test.pdf 未就绪。进入高保真内置文本层检索...', 'warning');
- const mockPdfText = `HarmonyOS NEXT Office Service Kit\r\n` +
- `---------------------------------\r\n` +
- `Document Title: PDF Kit API 23 Feature Report\r\n` +
- `Page Number: ${this.targetPageIndex + 1}\r\n` +
- `Text Layer Version: 6.1.0(23)\r\n` +
- `Extracted Result: Success\r\n` +
- `Detected characters: [setMaxZoom, getTextContent]\r\n` +
- `Remarks: This text contains raw escape sequences \\r\\n parsed dynamically.`;
- this.extractedText = mockPdfText;
- this.pushLog(`🎉 [虚拟底座] 成功模拟页面 ${this.targetPageIndex} 原始文本提取!`, 'success');
- }
- } catch (err) {
- this.pushLog(`❌ 文本提取发生故障: ${JSON.stringify(err)}`, 'error');
- }
- }
- build() {
- Row() {
- // 左侧面板:缩放控制
- Column() {
- Row() {
- Text('PDF KIT 视界控制中控台')
- .fontSize(16.5).fontWeight(FontWeight.Bold).fontColor('#F8FAFC')
- Row({ space: 8 }) {
- Circle().width(8).height(8).fill(this.zoomSettingSuccess ? '#10B981' : '#64748B')
- Text(this.zoomSettingSuccess ? '缩放已同步' : '未同步')
- .fontSize(12).fontColor('#94A3B8')
- }
- }
- .width('100%').height(56).padding({ left: 24, right: 24 })
- .backgroundColor('#0F172A').border({ width: { bottom: 1 }, color: '#334155' })
- List({ space: 20 }) {
- ListItem() {
- Column({ space: 12 }) {
- Text('📂 PDF 文档解析源')
- .fontSize(14).fontWeight(FontWeight.Bold).fontColor('#F8FAFC').alignSelf(ItemAlign.Start)
- Text('请指定应用沙箱内的 PDF 文件加载绝对路径:')
- .fontSize(12).fontColor('#94A3B8').alignSelf(ItemAlign.Start)
- TextInput({ text: this.currentFilePath })
- .fontColor('#FFFFFF').fontSize(12).backgroundColor('#0F172A')
- .border({ width: 1, color: '#334155' }).borderRadius(8).height(40).width('100%')
- .onChange((val: string) => { this.currentFilePath = val; })
- }
- .padding(16).backgroundColor('#1E293B').borderRadius(12)
- }
- ListItem() {
- Column({ space: 14 }) {
- Text('📏 自定义页面缩放上限 (setMaxZoom)')
- .fontSize(14).fontWeight(FontWeight.Bold).fontColor('#F8FAFC').alignSelf(ItemAlign.Start)
- Text('设置视图的最大缩放比例。取值范围:[0.1 ~ 10.0],高精度支持到小数点后第2位。')
- .fontSize(12).fontColor('#94A3B8').alignSelf(ItemAlign.Start).lineHeight(16)
- Row() {
- Text('设定最大缩放比:')
- .fontSize(13).fontColor('#94A3B8')
- Text(this.maxZoomValue.toFixed(2) + 'x')
- .fontSize(15).fontWeight(FontWeight.Bold).fontColor('#38BDF8')
- }
- .width('100%').justifyContent(FlexAlign.SpaceBetween)
- Slider({
- value: this.maxZoomValue,
- min: 0.05,
- max: 12.0,
- step: 0.05,
- style: SliderStyle.OutSet
- })
- .blockColor('#38BDF8').trackColor('#334155').selectedColor('#38BDF8')
- .onChange((value: number) => { this.maxZoomValue = value; })
- Button('⚡ 加载文档并配置最大缩放比', { type: ButtonType.Normal })
- .width('100%').height(40).backgroundColor('#10B981').borderRadius(8)
- .fontColor('#000000').fontWeight(FontWeight.Bold)
- .onClick(() => { this.handleLoadAndSetZoom(); })
- }
- .padding(16).backgroundColor('#1E293B').borderRadius(12)
- }
- ListItem() {
- Column({ space: 12 }) {
- Text('🖥️ 操作日志实时中控台')
- .fontSize(14).fontWeight(FontWeight.Bold).fontColor('#F8FAFC').alignSelf(ItemAlign.Start)
- List({ space: 8 }) {
- ForEach(this.consoleLogs, (item: LogItem) => {
- ListItem() {
- Row() {
- Text(`[${item.timestamp}] `)
- .fontSize(11).fontColor('#64748B').fontFamily('monospace')
- Text(item.message)
- .fontSize(11)
- .fontColor(
- item.type === 'success' ? '#10B981' :
- item.type === 'error' ? '#EF4444' :
- item.type === 'warning' ? '#F59E0B' : '#FFFFFF'
- )
- .layoutWeight(1)
- }
- .width('100%').alignItems(VerticalAlign.Top)
- }
- })
- }
- .width('100%').height(130).padding(8).backgroundColor('#090D16')
- .borderRadius(8).border({ width: 1, color: '#334155' })
- }
- .padding(16).backgroundColor('#1E293B').borderRadius(12)
- }
- }
- .width('100%').layoutWeight(1).padding(24)
- }
- .width('50%').height('100%').backgroundColor('#0F172A')
- // 右侧面板:文本提取
- Column() {
- Column() {
- Text('PDF 页面文本提取层 (getTextContent)')
- .fontSize(15).fontWeight(FontWeight.Bold).fontColor('#FFFFFF').margin({ bottom: 4 })
- Text('智能无纸化文本检索组件驱动')
- .fontSize(12).fontColor('#94A3B8')
- }
- .width('100%').alignItems(HorizontalAlign.Start)
- .padding({ top: 32, bottom: 24, left: 24, right: 24 })
- .border({ width: { bottom: 1 }, color: '#1E293B' })
- Column({ space: 16 }) {
- Text('页面原始文本数据提取 (包含换行与转义字符):')
- .fontSize(13).fontWeight(FontWeight.Medium).fontColor('#38BDF8').alignSelf(ItemAlign.Start)
- Row({ space: 12 }) {
- Text('目标页码')
- .fontSize(13).fontColor('#94A3B8')
- Counter({ count: this.targetPageIndex, min: 0, max: 100 })
- .onChange((val: number) => { this.targetPageIndex = val; })
- }
- Button('🔍 提取页面原始文本', { type: ButtonType.Normal })
- .width('100%').height(40).backgroundColor('#38BDF8').borderRadius(8)
- .fontColor('#000000').fontWeight(FontWeight.Bold)
- .onClick(() => { this.handleExtractText(); })
- Scroll() {
- Text(this.extractedText)
- .width('100%').padding(16)
- .fontSize(13).fontFamily('monospace').fontColor('#E2E8F0')
- .backgroundColor('#090D16')
- .borderRadius(8).border({ width: 1, color: '#334155' })
- }
- .width('100%').height('100%').layoutWeight(1)
- }
- .width('100%').layoutWeight(1).padding(24)
- }
- .width('50%').height('100%').backgroundColor('#0F172A')
- }
- .width('100%').height('100%').backgroundColor('#090D16')
- }
- }
复制代码
## 四、关键要点与注意事项
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在高精度缩放控制和原始文本提取方面的空白,特别适合电子签名、合同校对、文档摘要等场景。开发者可基于上述代码快速集成到自己的鸿蒙应用中。 |