拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

iOS PDF 工具怎么实现?从 PDFKit、标注、签名到扫描生成 PDF

iOS PDF 工具怎么实现?从 PDFKit、标注、签名到扫描生成 PDF iOS PDF 工具怎么实现从 PDFKit、标注、签名到扫描生成 PDF前面的系列我们已经从文件浏览一路扩展到了压缩、图片、视频和音频处理01 iOS 文件浏览器架构 02 GB 级大文件处理 03 Wi-Fi 文件传输 04 SMB / NAS 05 WebDAV 06 FTP 07 ZIP / RAR / 7z 08 图片转换与压缩 09 视频转换与压缩 10 音频格式转换这一篇开始进入另外一个非常高频的文件类型PDF对于普通用户来说PDF 需求往往非常明确收到一份 PDF怎么在 iPhone 上签字或者PDF 里怎么高亮、画线、写字又或者没有扫描仪能不能直接拍照生成 PDF从产品角度看这些都是一个 PDF 工具应该完成的事情。但从技术实现上看PDF 模块实际上包含多个不同能力PDF 浏览页面渲染缩略图文本选择HighlightUnderlineStrikeoutFreehand InkSignature页面旋转页面增删图片转 PDF相机扫描文件保存大型 PDF 内存控制增量保存与文件替换所以PDFService 不应该只是一个 PDFView 页面而应该是一套完整的 Document Processing 模块。1. PDF 为什么和图片不一样很多人第一反应是PDF 不就是很多张图片放在一起吗不完全是。一个 PDF 页面里可能包含Text Vector Graphics Images Annotations Forms Links Metadata例如Page 1 ├── Text ├── Logo ├── Vector Line ├── Embedded Image └── Annotation所以 PDF 更像一种完整的页面描述文档格式。这也是为什么PDF可以在不同屏幕尺寸上缩放而文字依然保持清晰。2. iOS PDF 开发最核心的框架PDFKit在 Apple 平台上PDF 处理经常会使用PDFKit几个最核心的类型包括PDFView PDFDocument PDFPage PDFAnnotation可以简单理解为PDFDocument │ ├── PDFPage ├── PDFPage └── PDFPage然后PDFView ↓ Display PDFDocument这是整个 PDF 模块最基础的一层。3. PDFDocument 可以看作 PDF 文件模型例如importPDFKitletdocumentPDFDocument(url:fileURL)拿到以后letcountdocument?.pageCount获取页面letpagedocument?.page(at:0)整体FileItem ↓ PDFService ↓ PDFDocument ↓ PDFPage因此不要让UIViewController直接承担 PDF 文件逻辑。4. PDFView 负责什么PDFView 更接近PDF 阅读和交互 UI。例如letpdfViewPDFView()pdfView.documentdocument可以负责页面显示缩放滚动页面切换文本选择所以架构最好是PDF Reader UI ↓ PDFView ↓ PDFDocument而保存、标注、签名等业务逻辑继续放在PDFService里。5. PDF 模块应该先有统一 PDFInfo类似前面的ImageInfo VideoInfo AudioInfoPDF 也应该先 Inspect。例如structPDFInfo{letpageCount:IntletfileSize:Int64lettitle:String?letauthor:String?letisEncrypted:BoolletallowsCopying:BoolletallowsPrinting:Bool}UI 可以先展示27 Pages 4.8 MB Encrypted: No以后也方便进行PDF Validation6. PDFAnnotation 是标注功能的核心PDF 中很多常见编辑行为实际上不是修改原正文。而是给页面增加 Annotation。例如Highlight Underline Strikeout Ink FreeText Link Stamp都可以抽象为PDFAnnotation因此用户看到高亮一段文字底层可能只是PDFPage ↓ Add Annotation而不是重新生成整页 PDF。7. Highlight 怎么实现用户选择文字This is important text然后点击Highlight底层需要得到Selection ↓ Bounds ↓ PDFAnnotation概念上letannotationPDFAnnotation(bounds:bounds,forType:.highlight,withProperties:nil)page.addAnnotation(annotation)于是Page Content Highlight Annotation共同组成最终页面。8. Underline 和 Strikeout 也是类似思路例如Highlight Underline StrikeoutUI 看起来是三种功能。底层PDFAnnotation Type不同。因此 PDF 编辑工具不应该写highlightText() underlineText() strikeoutText()然后各自独立一整套架构。更合理TextMarkupService ↓ Annotation Type9. 手写画笔怎么实现自由手写Pen / Ink通常可以抽象为Touch Points ↓ Bezier Path ↓ PDF Ink Annotation用户手指 / Apple Pencil在页面上画底层记录Point 1 Point 2 Point 3 ...然后变成Path最终加入 PDF。10. 不要每移动一个点都立即重写 PDF如果用户正在连续书写Touch Move Touch Move Touch Move如果每次Save PDF性能会非常差。更合理Drawing Session ↓ Collect Points ↓ Render Overlay ↓ Gesture End ↓ Create Annotation ↓ Mark Document Dirty最后再统一保存。所以UI 实时反馈和 PDF 文件持久化应该分离。11. Apple Pencil 怎么处理如果产品支持 Pencil可以进一步区分Finger Apple Pencil并利用PressureTiltPrecise Points改善笔迹。但 PDFService 本身不应该知道UITouch细节。可以设计DrawingInput ↓ InkStroke ↓ PDF Annotation这样输入层和文件层继续分离。12. PDF 签名本质上是什么普通用户看到Sign PDF以为 PDF 有一个特殊“签名按钮”。但很多普通电子签名功能本质上可能是用户手写签名 ↓ Signature Drawing/Image ↓ Placed on PDF Page ↓ Annotation例如John Smith用户先保存一个签名然后拖到Signature: ___________位置。这和数字证书签名不是一回事。13. 手写签名 ≠ 数字签名这个区别非常重要。Handwritten Signature本质更接近Image / Vector Drawing放到 PDF 页面上。主要解决我要在合同上签字。Digital Signature则涉及Certificate Private Key Cryptographic Signature Document Integrity用于验证谁签的文档有没有被篡改证书是否可信所以PDF 工具如果只是支持手写签字不应该宣传成数字证书签名系统。14. Signature 应该独立建模例如structSavedSignature{letid:UUIDletcreatedAt:DateletvectorData:Data?letimageData:Data?}UISignature Library │ ├── Signature A └── Signature B用户选择Signature A然后Drag Resize Place最终转成PDF Annotation15. 签名保存在哪里这涉及用户隐私。签名属于比较敏感的数据。因此不建议随便放到公开 Documents更适合Application Support配合File Protection或者加密存储。如果产品支持Face ID App Lock签名库也可以被整体应用锁保护。16. PDF 签名最好支持矢量数据如果只是PNG Signature放大后可能变模糊。更好的方式是Stroke Points ↓ Vector Path保存签名。渲染时Vector ↓ Scale ↓ Still Sharp当然实现成本更高。第一版使用透明 PNG 也完全可以满足大部分需求。17. Lasso 是什么如果用户画了多个Ink Annotation希望框选 移动 删除就需要Selection Tool / Lasso架构上Touch Path ↓ Selection Bounds ↓ Intersect Annotations ↓ Selected Annotation Set然后可以Move Delete Resize所以 PDF 编辑器一旦支持Ink后面很自然就会进入Annotation Editing体系。18. Undo / Redo 很重要用户在 PDF 上写错 删错 移动错如果没有 Undo体验会非常差。因此可以设计PDFEditCommand例如AddAnnotationCommand RemoveAnnotationCommand MoveAnnotationCommand ResizeAnnotationCommand然后Undo Stack Redo Stack这比到处手写undoLastThing()更容易维护。19. Command Pattern 很适合 PDF 编辑比如protocolPDFEditCommand{funcexecute()funcundo()}新增高亮Add Annotation执行page.addAnnotation(...)撤销page.removeAnnotation(...)于是Highlight Ink Signature Delete Move都可以进入统一编辑历史。20. PDF 保存不能太随意用户Add Highlight之后需要Save最危险的做法是直接破坏式覆盖原文件更稳妥Original PDF ↓ Write Temp ↓ Validate ↓ Replace Original这和我们前几篇所有文件任务的原则完全一致。21. 为什么还是要临时文件假设document.pdf保存到78%突然App 被终止磁盘满了写文件失败如果直接写原文件就可能Original PDF ↓ Corrupted所以.document.pdf.tmp ↓ Write ↓ Complete ↓ Validate ↓ Replace更安全。22. Auto Save 怎么设计PDF 标注很适合Auto Save但不要每画一个点就写磁盘可以Document Dirty ↓ Debounce ↓ Auto Save例如用户停止编辑一段时间再保存。同时保留Explicit Save以增强用户确定感。23. 大型 PDF 最大的问题之一是渲染例如1000 Pages或者每页都是超高清扫描图如果一次Render All Pages会造成Memory Pressure所以 PDF 阅读器应该始终围绕Visible Pages工作。即Current Page Nearby Pages优先渲染。24. PDF Thumbnail 不能一次全生成假设2000 Pages缩略图栏如果一打开Generate 2000 Thumbnails非常浪费。应该Visible Thumbnail ↓ Generate ↓ Cache滚动到新的页面Generate On Demand这和前面的Image Thumbnail Video Thumbnail NAS Thumbnail完全一样。25. PDF Thumbnail 也应该有 Cache例如 KeyPDF File ID Modified Date Page Index Target Size然后Memory Cache Disk Cache文件修改后Modified Date变化缓存失效。26. Page Rendering 和 Thumbnail Rendering 应该区分正文阅读Full Page Rendering要求清晰 缩放缩略图Small Preview要求速度 低内存所以不要Render Full Resolution ↓ Resize to Thumbnail应该Render Directly at Target Size尽量减少无意义资源消耗。27. PDF Search 是另一个非常有价值的能力用户打开300 Page Manual想找authentication需要Text Search如果 PDF 包含真正的 Text Layer可以通过PDFDocument ↓ Find String找到匹配位置。UI23 Results然后跳转页面。28. 扫描 PDF 可能没有文字层例如Paper ↓ Camera ↓ PDF如果只是图片生成 PDF页面里面实际上是Image而不是Text于是Search Copy Text Select Text都无法工作。这就是OCR能力出现的原因。29. OCR 和 PDF 是两个不同模块PDFService 负责PDF Structure Pages Annotations SaveOCRService 负责Image ↓ Text Recognition不要把所有能力塞进PDFManager更合理PDFService OCRService ScanService各自负责不同问题。30. 扫描生成 PDF 的基本流程是什么用户操作打开相机 ↓ 拍摄纸张 ↓ 自动识别边缘 ↓ 透视矫正 ↓ 增强 ↓ 生成页面 ↓ PDF整个 PipelineCamera Image ↓ Document Detection ↓ Perspective Correction ↓ Image Enhancement ↓ Page Image ↓ PDF Generator这并不是简单Camera Photo ↓ PDF31. VisionKit 很适合文档扫描场景在 iOS 里系统提供了和文档扫描相关的能力。产品可以利用系统扫描体验完成Document Capture包括页面边缘检测透视调整多页扫描然后拿到扫描结果Page Images再进入自己的PDFService生成 PDF。32. ScanService 应该独立例如protocolScanService{funcscanDocuments()asyncthrows-[ScannedPage]}其中structScannedPage{letimage:UIImage}然后ScanService ↓ ScannedPage[] ↓ PDFService ↓ PDFDocument这样Camera Scanning和PDF Generation职责清晰。33. 图片转 PDF 其实也是同一条路径例如用户已经有Photo 1 Photo 2 Photo 3想Create PDF流程Images ↓ PDF Page Generator ↓ PDFDocument所以Camera Scan只是图片来源不同。统一后Image Source ↓ PDF Generator可以同时支持Camera Photo Library Files Scanner34. 页面尺寸怎么选图片生成 PDF 时要决定Page Size例如A4 Letter Original Image Ratio如果用户扫描合同通常A4比较自然。如果只是照片合集 PDF则Fit Image可能更合适。因此 PDF Create Options 可以包含Page Size Margins Orientation Image Fit35. 扫描 PDF 为什么很容易变得巨大假设一页扫描图4032 × 302410 页10 high-resolution images如果全部用高质量 PNG 写进 PDF最终可能100MB所以扫描生成 PDF 时必须考虑Image Compression比如Downsample JPEG Compression36. PDF Scanner 应该考虑目标用途例如Document合同 文字 表格可以采用高对比度 适中分辨率Photo需要颜色 更高质量所以可以提供Document Color Photo不同模式。不必暴露JPEG Quality 0.72这种参数给普通用户。37. 黑白扫描可以显著减少体积很多纯文字文件黑字白纸没必要保存完整彩色信息。可以Color ↓ Grayscale甚至Black White大幅降低数据量。同时提高Text Contrast这也是扫描工具常见的模式。38. OCR 可以在扫描后异步进行例如Scan ↓ Create PDF ↓ User can immediately view后台再OCR而不是强制等待 OCR 全部完成才能生成文件。架构Scan ↓ PDF Ready ↓ OCR Operation这能明显提升用户感知速度。39. OCR 结果应该如何保存可以有几种思路。方案 A只用于Search Index不写回 PDF。方案 B生成Invisible Text Layer让扫描 PDF 可以搜索复制文字方案 C单独保存OCR Text作为辅助数据。不同实现复杂度差异很大。40. PDF 页面管理也是重要功能除了标注用户还经常需要Delete Page Rotate Page Reorder Page Insert Page这些能力最好统一成PDFPageOperation而不是每个按钮自己直接操作 Document。例如Move Page 8 → 2可以成为ReorderPageCommand同样进入Undo / Redo体系。41. Merge PDF 怎么实现例如A.pdf B.pdf C.pdf合并Merged.pdf逻辑New PDFDocument ↓ Insert Pages from A ↓ Insert Pages from B ↓ Insert Pages from C但仍然需要考虑页面顺序Metadata加密 PDF大文件失败恢复所以 Merge 本质也是FileOperation42. Split PDF 呢例如100-page.pdf用户选择Pages 1–10导出part1.pdf或者每页一个文件这就是Split Operation所以未来 PDFService 可以拥有merge() split() extractPages()43. PDF 密码怎么办PDF 可能是Encrypted PDF打开时需要密码。流程Open PDF ↓ Encrypted? ↓ Yes Request Password ↓ Unlock错误应该明确Wrong Password而不是Failed to open PDF继续延续整个系列的错误业务化原则。44. PDF 权限也可能有限制某些 PDF 会设置Allow Copy Allow Print等权限信息。所以 PDFInfo 可以记录allowsCopying allowsPrinting应用在实现Copy Text Print时应尊重对应文档状态和系统能力。45. PDF Password Store 也可以统一到安全存储如果用户明确选择Remember Password可以PDF Credential ↓ Keychain和之前SMB WebDAV FTP Archive Password统一进CredentialStore这样安全层继续复用。46. 大 PDF 也应该进入 FileOperationManager 吗不是所有浏览行为都需要。例如Open PDF Scroll PDF属于交互。但Save Edited PDF Merge Split Scan to PDF Export OCR这些都可能是长任务。所以可以定义OperationType.pdfSave OperationType.pdfMerge OperationType.pdfSplit OperationType.pdfScan OperationType.ocr统一进入任务系统。47. PDF 保存进度可能比复制更难例如Rewriting 500-page PDF底层不一定总能直接给你一个非常精确的0...100%所以进度可以分成Preparing Processing Writing Finalizing而不是假装给用户一个特别精确但实际不可靠的百分比。有时候Stage Progress比Fake 73%更诚实。48. PDF 错误应该统一业务化例如enumPDFProcessingError:Error{caseinvalidDocumentcaseencryptedcasewrongPasswordcasepermissionDeniedcaseunsupportedOperationcaseinsufficientStoragecasesaveFailedcasecancelledcaseunknown(Error)}UI 显示PDF 密码错误而不是Error DomainPDFKit...49. 远程 PDF 怎么办例如WebDAV ↓ contract.pdf用户只是阅读可以Download / Cache ↓ PDFView用户要签名则更合理Remote PDF ↓ Local Working Copy ↓ Edit ↓ Save ↓ Upload Back因为编辑通常需要可靠的本地工作文件。50. 为什么远程 PDF 编辑最好先做 Working Copy如果直接Remote File ↓ Edit In Place网络中断时非常危险。更稳妥Remote ↓ Download ↓ Local Working Copy ↓ Edit ↓ Save ↓ Upload Temp ↓ Replace Remote这个流程其实就是Transactional Remote Edit51. 远程更新还要考虑冲突假设10:00 你下载 contract.pdf你编辑了 10 分钟。与此同时服务器上的contract.pdf已经被别人修改。如果你直接上传覆盖Remote Changes Lost所以成熟实现可以比较ETag Modified Date File ID判断Remote Changed?如果变化Conflict让用户决定Replace Save Copy Cancel这和前面的 Provider 架构完全一致。52. PDFService 可以怎么抽象例如protocolPDFService{funcinspect(_item:FileItem)asyncthrows-PDFInfofuncaddAnnotation(_annotation:PDFAnnotationModel,to item:FileItem)asyncthrowsfuncmerge(_items:[FileItem],destination:FileLocation)asyncthrows-FileItemfuncsplit(_item:FileItem,pages:IndexSet,destination:FileLocation)asyncthrows-FileItemfunccreate(from images:[FileItem],options:PDFCreateOptions)asyncthrows-FileItem}这样PDFKit只是底层实现。53. PDFAnnotationModel 最好独立于 PDFKit如果业务层直接持有PDFAnnotation以后很难做 Undo做持久化做同步做其他 PDF Engine更好的方式structPDFAnnotationModel{letid:UUIDletpageIndex:Intlettype:AnnotationTypeletbounds:CGRect}然后PDFAnnotationModel ↓ PDFKit Adapter ↓ PDFAnnotation保持业务模型和框架解耦。54. 一个完整 PDF 架构最终可以整理成┌─────────────────────────────┐ │ UI │ │ │ │ PDF Reader │ │ Annotation Toolbar │ │ Signature │ │ Scan │ │ Page Manager │ └──────────────┬──────────────┘ │ ▼ ┌─────────────────────────────┐ │ PDFService │ │ │ │ Inspect │ │ Annotation │ │ Signature │ │ Merge / Split │ │ Create PDF │ │ Save │ └──────────────┬──────────────┘ │ ┌──────┼───────────┐ ▼ ▼ ▼ PDFKit ScanService OCRService │ ▼ PDFDocument旁边继续复用FileOperationManager TemporaryFileManager ThumbnailService CredentialStore ConflictResolver StorageChecker55. TS File Explorer 的 PDF 能力真正解决什么从开发者角度PDFDocument PDFPage PDFAnnotation VisionKit OCR是技术实现。但用户真正的问题可能是合同突然发来我现在就要签字。或者我想把纸质文件直接扫描成 PDF。或者PDF 里这几段文字需要高亮。所以真正的使用路径是contract.pdf ↓ TS File Explorer ↓ Sign ↓ Save ↓ Share或者Paper Document ↓ Scan ↓ PDF ↓ Share用户真正需要的不是一个 PDFKit Demo。而是文件来了以后直接在手机上把事情做完。56. 文件处理平台已经进入 Document 层到现在FileProvider │ ├── Local ├── SMB ├── WebDAV └── FTP上层Services │ ├── ArchiveService ├── ImageService ├── VideoService ├── AudioService ├── PDFService ├── ScanService ├── OCRService ├── ThumbnailService └── WaveformService再由FileOperationManager统一处理Copy Transfer Compress Convert Scan Merge Split Save整个系统已经明显从File Browser变成File Processing Platform57. 开发 PDF 模块前建议先回答这 20 个问题1. PDF 浏览使用什么架构 2. PDFDocument 生命周期如何管理 3. Annotation 如何建模 4. Highlight / Underline 如何实现 5. Ink 如何保存 6. 是否支持 Apple Pencil 7. Signature 如何保存 8. 手写签名与数字签名是否明确区分 9. Undo / Redo 如何设计 10. PDF 保存是否使用临时文件 11. 大 PDF 如何控制内存 12. Thumbnail 如何缓存 13. 是否支持 Search 14. Scan 与 PDFService 是否分层 15. 是否支持 OCR 16. OCR 结果如何保存 17. 是否支持 Merge / Split 18. PDF 密码如何处理 19. Remote PDF 编辑如何做冲突检测 20. PDF 长任务如何接入 FileOperationManager如果这些问题没有提前设计PDF 模块很容易从一个 PDFView逐渐变成各种手势 各种标注状态 保存 Bug 页面 Bug 远程文件 Bug最后难以维护。写在最后PDF 功能表面上看只是Read Highlight Sign Scan但真正的工程架构其实涉及PDF Rendering Annotation Drawing Document Editing Scanning OCR File Persistence Remote File Conflict最重要的一点仍然是不要把 PDF 模块写成 PDFView 上不断叠加按钮而应该把它设计成独立的 Document Processing Service。整个系列现在已经来到01 File Browser ↓ 02 Large File IO ↓ 03 Wi-Fi Transfer ↓ 04 SMB ↓ 05 WebDAV ↓ 06 FTP ↓ 07 Archive ↓ 08 Image ↓ 09 Video ↓ 10 Audio ↓ 11 PDF下一篇我建议继续进入《iOS EPUB / MOBI 阅读器怎么实现从电子书解析、目录、分页到 TTS 阅读架构》可以继续拆解EPUB MOBI HTML CSS Table of Contents Chapter Pagination Theme Font Size Reading Progress Bookmark TTS这样就把 TS File Explorer 的Reader能力也完整接入这套架构。
返回列表