资讯详情

HarmonyOS 7 PDF Kit:旋转表单页坐标反算与字段写入

📅 2026/10/8 22:04:19 | 华诺云谱 👁 阅读
HarmonyOS 7 PDF Kit:旋转表单页坐标反算与字段写入
一、签名没有丢只是写到了旋转前的坐标系FormSeal 是一个设备巡检签批 Demo。用户从文件选择器打开inspection_A17.pdf在第 4 页勾选 14 个字段并写入两处签名最后交给 PDF Kit 预览。验收时出现了很尴尬的结果前三页正常第 4 页的签名跑到右上角文本框整体偏移日志却显示 14/14 字段写入成功保存也没有报错。我没有先调偏移量而是把 PDF 页盒、旋转角和屏幕触点全部打印出来。第 4 页原始尺寸是 595×842 pt/Rotate为 90°手机预览区域是 360×509 vp屏幕坐标原点在左上PDF 绘制坐标原点在左下。我们把预览触点直接乘比例写进 PDF相当于同时忽略了 Y 轴翻转、页面旋转和 fit-center 留白。这次修复最终没有落在某个“神奇偏移值”上而是形成了一个明确流水线触点先还原到未旋转页面空间字段写入内存副本临时文件完整落盘并验证后再原子替换任一步失败只删除本次任务的临时副本。任务编号固定为PDF-2142这样页面、HiLog 和导出文件都能指向同一次操作。二、把预览空间、旋转空间和 PDF 空间分开说清楚工程目录刻意没有让页面直接操作文件。FormCoordinatePage负责采集字段与手写轨迹PageSpaceMapper只做坐标变换PdfFormWriter只改内存字节AtomicPdfStore管临时文件和替换PDF Kit 负责最终预览。entry/src/main/ets/ ├── pages/FormCoordinatePage.ets ├── pdf/PageSpaceMapper.ets ├── pdf/PdfFormWriter.ets ├── storage/AtomicPdfStore.ets └── model/FormTask.ets第一段代码解决坐标反算。输入是去掉 fit-center 留白后的局部触点以及预览内容的实际宽高输出始终是未旋转 PDF 页的坐标。这里只接受 0、90、180、270 四种标准旋转发现异常角度直接终止任务避免生成看似成功却不可用的文件。interfacePoint{x:number;y:number}interfacePageSize{width:number;height:number}exportfunctionnormalizePageSpace(local:Point,preview:PageSize,page:PageSize,rotation:number):Point{constdisplayXlocal.x*(rotation%1800?page.width:page.height)/preview.widthconstdisplayYToplocal.y*(rotation%1800?page.height:page.width)/preview.heightswitch(rotation){case0:return{x:displayX,y:page.height-displayYTop}case90:return{x:displayYTop,y:displayX}case180:return{x:page.width-displayX,y:displayYTop}case270:return{x:page.width-displayYTop,y:page.height-displayX}default:thrownewError(UNSUPPORTED_ROTATION_${rotation})}}写法的重点是先移除预览留白再调用这个函数。若预览控件 360 vp 宽、内容实际只有 340 vp却仍按 360 计算旋转公式再正确也会整体漂移。签名轨迹的每个点都要经过同一变换不能只换算包围框左上角。页面缩放或窗口旋转时旧轨迹应保留在 PDF 空间重新渲染时再投影到新的预览空间这样不会反复换算产生误差。三、pdf-lib 只处理内存副本页面状态不提前宣布成功pdf-lib 能加载既有 PDF、取得页面并序列化为Uint8Array。FormSeal 不在写字段时碰原文件先加载原始字节按字段模型绘制再保存到内存。第二段代码展示第 4 页的核心写入逻辑真实 Demo 的 14 个字段和两条签名都从同一任务模型进入。import{PDFDocument,StandardFonts,rgb}frompdf-libinterfaceFieldValue{label:string;value:string;point:Point}exportasyncfunctionapplyFields(source:Uint8Array,fields:FieldValue[],signaturePng:Uint8Array):PromiseUint8Array{constpdfawaitPDFDocument.load(source)constpagepdf.getPages()[3]constfontawaitpdf.embedFont(StandardFonts.Helvetica)constsignatureawaitpdf.embedPng(signaturePng)fields.forEach((field:FieldValue){page.drawText(${field.label}:${field.value},{x:field.point.x,y:field.point.y,size:9,font,color:rgb(0.12,0.16,0.22)})})page.drawImage(signature,{x:186,y:122,width:128,height:42})page.drawImage(signature,{x:356,y:122,width:128,height:42})pdf.setProducer(FormSeal 1.0.0)returnawaitpdf.save({useObjectStreams:false})}这里的两个签名坐标已经是normalizePageSpace()的输出不再关心预览页旋转了多少度。useObjectStreams: false是这个 Demo 为兼容下游旧检查器做的取舍会牺牲一点体积不能当作所有项目的默认值。标准字体也不覆盖中文字段生产环境要注册并嵌入授权字体否则“写入成功”可能只是英文正常、中文变方框。页面状态在这一步仍是WRITING不能变成SEALED。因为内存序列化成功不等于文件完整落盘更不等于 PDF Kit 能重新打开。连续点击“生成签批件”时按钮立即禁用并以任务 ID 去重如果允许并发写同一目标文件后完成的旧任务会覆盖新结果。四、临时文件不是备份它只属于一次提交事务之前的实现直接 truncate 目标文件后写入。写到一半遇到空间不足原文件和新文件一起损坏。现在每个任务使用明确的临时名inspection_A17.PDF-2142.tmp写完、同步并验证后才替换目标inspection_A17_sealed.pdf。第三段代码省略平台文件句柄细节但保留事务顺序。exportclassAtomicPdfStore{asyncsaveDraftAtomically(taskId:string,bytes:Uint8Array):Promisestring{constfinalPath${this.cacheDir}/inspection_A17_sealed.pdfconsttempPath${this.cacheDir}/inspection_A17.${taskId}.tmptry{awaitthis.writeAll(tempPath,bytes)awaitthis.flush(tempPath)awaitthis.verifyPdf(tempPath,7)awaitthis.replace(tempPath,finalPath)returnfinalPath}catch(error){awaitthis.removeIfExists(tempPath)throwerror}}privateasyncverifyPdf(path:string,expectedPages:number):Promisevoid{constbytesawaitthis.readAll(path)constpdfawaitPDFDocument.load(bytes)if(pdf.getPageCount()!expectedPages){thrownewError(VERIFY_PAGE_COUNT_MISMATCH)}}}清理范围必须精确到本任务临时文件不能在 catch 里清空整个 cache 目录。PDF Kit 预览可能仍持有上一次成功文件粗暴清目录会把可回退版本也删掉。replace()应由同一文件系统内的原子重命名或等价能力实现临时目录若在另一分区“重命名”可能退化成复制。页面离开时可以取消未开始的写入但对已经进入 flush 的任务只标记 UI 不再接收回调不能强杀到半个文件。五、验证不是重新打开一次而是检查关键事实verifyPdf()先确认能解析且仍是 7 页随后生产版还会检查第 4 页旋转角为 90°、14 个字段的绘制记录存在、输出字节大于最小阈值。最终文件大小 1.84 MiB字段映射 14/14两处签名均在预期区域越界字段为 0。本轮没有触发回滚所以页面显示“回收临时副本 0”这不是说没有清理机制而是本次成功路径没有残留。手机页展示的是提交后的最终事实任务PDF-2142输入inspection_A17.pdf第 4/7 页旋转 90°字段 14/14签名 2/2输出 1.84 MiB耗时 226 ms状态SEALED。这些数字与 HiLog 中的mapped14 signatures2 overflow0 stateSEALED完全一致。预览按钮只有在目标文件替换完成后才可用避免 PDF Kit 打开临时路径。六、一次失败演练暴露了真正需要保护的东西为了确认异常路径我们把可用空间限制到小于输出体积。写入在 flush 前失败页面状态从WRITING进入FAILED错误为NO_SPACE_LEFT临时文件被删除上一版inspection_A17_sealed.pdf仍能预览。恢复空间后再次提交沿用业务任务PDF-2142的同时增加内部 attempt避免外部审核记录出现两个任务只有成功 attempt 才写入审计结果。坐标边界也做了单独测试。四种旋转各取页面四角与中心共 20 个基准点反算误差控制在 0.5 pt 内。触点超出内容区域时先裁剪或拒绝不能让负坐标进入绘制页面 CropBox 与 MediaBox 不一致时以实际预览采用的页盒为准。若 PDF 带密码、动态 XFA 表单或不受 pdf-lib 支持的结构Demo 直接提示“不支持编辑”而不是尝试保存后再赌预览结果。七、留下的工程判断PDF 签批最容易被误认为“画几个字再保存”。实际可靠性来自三层契约。第一层是几何契约屏幕左上原点、fit-center 留白、PDF 左下原点和/Rotate必须在同一公式里闭环。第二层是状态契约LOADING → EDITING → WRITING → VERIFYING → SEALED单向推进只有最新 attempt 能改 UI。第三层是文件契约原文件只读、临时副本独占、验证成功后原子替换、失败只回收本任务残留。PDF Kit 在这里承担系统预览与最终验收pdf-lib 负责内存级修改两者不是互相替代。把坐标转换、字节修改和文件提交拆开后任何问题都能落到具体层是触点错了、绘制错了还是提交错了。对需要审核追踪的巡检、合同和设备交付场景这种可解释性比“多数文件能打开”更重要也让下一步加入多页骑缝章、版本签名和导出审计时不必重写整条链路。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑