# PDF 插件性能审查 本文依据源码分析实现复杂度;未运行性能基准或 UI 测试,不提供未经测量的加速比例。 ## 已实现 符号:P 为页数,A 为相关页面标注数,D 为一次批量删除数,H 为有效历史操作数,Δ 为检查点后的操作数,B 为 PDF 字节数,W 为 OCR 词数,T 为原生文字框数,K 为查询命中的索引候选数。 | 路径 | 原实现 | 当前实现 | 空间边界 | | --- | --- | --- | --- | | 滚动定位当前页 | 每次 scroll 查询所有页,并依次读取 DOM rect,O(P) | 同帧事件合并,一次对有序页 wrapper 二分,O(log P) | O(1) 附加空间;使用现有 DOM,不复制页偏移表 | | 标注命中、链接悬停 | 每次鼠标事件扫描所有对象,重复转坐标、解析 URL/quad | 近页构建有界网格,事件只检查局部及大框候选,O(K);最坏重叠情况仍为 O(A) | 每项最多 16 格引用;大框单列;远页释放索引;保持后绘制优先 | | OCR 与原生文字去重 | O(W×T),内循环重复把词框转换为 viewport 坐标 | 词框转换一次,再查相交网格候选,通常 O(W+ΣK);密集重叠最坏仍 O(W×T) | 索引 O(T);宽区域查询超过 64 格回退线性扫描,避免异常几何扩大内存 | | OCR 字形量宽 | 每个词读取 scrollWidth 后立刻写 transform,反复布局 | 同一批先读完宽度,再写完样式;词组件和 viewport 复用 | O(可见词数) 短暂记录,无额外整页画布 | | 文字选区 | 遍历整层文本;每个节点创建 Range;收集所有 rect 后合并 | 从选区边界/公共子树开始,到结束节点停止;复用一个 Range,在线合并范围 | 几何输出与选区大小相关;不保留全部临时 rect | | 批量删除原生标注 | 每个删除扫描并 splice 页 Annots,O(D×A) | 按页收集待删对象,原位压缩一次,O(A+D) | O(D) 待删集合;保留未知对象、关联 Popup 和稳定 ID | | 重做、恢复的历史回放 | 未命中精确缓存时,从原文重放整个前缀 O(H),重新解码原文 | 选取有效较早检查点,只重放 Δ;即使大 PDF 未入 LRU,重做可复用当前副本 | 总渲染缓存仍 32 MiB,每文档最多 3 个状态 | | 签名扫描 | 额外复制全对象引用列表及 PDFArray 内容 | 根和容器索引迭代,去掉额外列表/数组复制 | 不取消 PDF 引擎自身枚举和必要 visited 集合 | | 工具栏、颜色及批注输入 | Reader 重建全部 Page 元素,回调变动导致页反复更新 | 稳定页面回调、记忆页面树、memo 页组件;选区/标注状态只传给对应页 | 复用已有页面和模型,不增加第二份编辑对象图 | | 保存/状态响应的未变化内容 | 即使字节相同,仍传全 PDF base64 和元数据,Client 再校验整套模型 | 请求带已持有摘要;相同摘要返回状态信息,复用客户端 bytes/document | 避免 O(B) wire/base64 及 O(P+A) 元数据复制;并发请求保留所需快照引用 | 检查点只允许同一原始字节/摘要、相同历史前缀且 cursor 不晚于目标,丢弃全部修改不能误用未来状态。PDF.js worker 会转移传入的 ArrayBuffer,因此保留必要的 reader 输入复制;不能直接转移 Host 编辑状态使用的同一 Uint8Array。 ## 阅读布局与缩放 Ctrl+滚轮同帧合并比例更新,鼠标落在页面上时直接定位,落在页间空白时二分定位,分别为 O(1) 和 O(log P)。侧栏调宽或折叠时同样二分捕获可见页,只保留一个页内位置,尺寸提交后恢复,不扫描全页或等待画布重绘;锚点处理额外空间为 O(1)。两个路径共享浏览器滚动锚定锁,避免默认滚动补偿与插件补偿重复执行。 这些复杂度只描述事件定位和锚点处理。比例改变后仍需更新所有页面的布局占位,当前页面树更新为 O(P),近页画布重绘另有像素成本;没有把整个缩放过程降为 O(log P)。右栏切换复用已挂载视图,翻译请求和结果保持;标注/便笺列表缓存复用现有标注模型,没有复制 PDF 字节。 ## 仍存在的主要成本 一次普通标注编辑仍执行: 1. pdf-lib 解析当前 PDF。 2. 修改标注、生成必要 appearance,序列化整份 PDF。 3. 写入可恢复草稿,传输新字节。 4. PDF.js 打开新字节,并重新绘制近页。 因此,这次改动减少重复扫描、历史回放、布局和未变化内容传输,但没有把单标注编辑降为 O(1)。实际保存仍需完整文件写入、源版本检查和提交验证。保存中的磁盘成本不能由客户端摘要复用消除。 ## 后续进一步降低编辑复杂度的方案 建议独立重构为:Host 保留已保存 baseline + 操作历史 + 标注模型,普通编辑仅持久化操作和返回标注差量;在保存、导出或需要完整 PDF 的操作中才物化整份 PDF。这样普通编辑不必反复做 B 级工作,且不必长期常驻完整 pdf-lib 对象图。 完成该重构前必须同时解决: - 客户端如何隐藏被删或改色的原生 annotation appearance,并显示差量;仅叠加 SVG 无法擦掉旧高亮,直接禁用全部原生标注会丢失未知类型和自定义 appearance。 - Host 标注身份与 PDF.js 原生 annotation ID 的准确映射,不能猜 ID。 - 草稿提交失败、取消、撤销/重做及共享工作副本的原子更新。 - 截图和 OCR 在物化之前也要反映正确的工作副本。 - 保存成功后的新 baseline、文档重开、历史重置及外部版本冲突。 当前实现尚未引入这套未完成的渲染/事务架构,也没有增加常驻多份 PDF 编辑图。