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

0 阅读16分钟

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 浏览
  • 页面渲染
  • 缩略图
  • 文本选择
  • Highlight
  • Underline
  • Strikeout
  • Freehand Ink
  • Signature
  • 页面旋转
  • 页面增删
  • 图片转 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 文件模型

例如:

import PDFKit

let document = PDFDocument(url: fileURL)

拿到以后:

let count = document?.pageCount

获取页面:

let page = document?.page(at: 0)

整体:

FileItem
  ↓
PDFService
  ↓
PDFDocument
  ↓
PDFPage

因此不要让:

UIViewController

直接承担 PDF 文件逻辑。


4. PDFView 负责什么?

PDFView 更接近:

PDF 阅读和交互 UI。

例如:

let pdfView = PDFView()
pdfView.document = document

可以负责:

  • 页面显示
  • 缩放
  • 滚动
  • 页面切换
  • 文本选择

所以架构最好是:

PDF Reader UI
      ↓
PDFView
      ↓
PDFDocument

而保存、标注、签名等业务逻辑继续放在:

PDFService

里。


5. PDF 模块应该先有统一 PDFInfo

类似前面的:

ImageInfo
VideoInfo
AudioInfo

PDF 也应该先 Inspect。

例如:

struct PDFInfo {
    let pageCount: Int
    let fileSize: Int64
    let title: String?
    let author: String?
    let isEncrypted: Bool
    let allowsCopying: Bool
    let allowsPrinting: Bool
}

UI 可以先展示:

27 Pages
4.8 MB
Encrypted: No

以后也方便进行:

PDF Validation

6. 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

概念上:

let annotation = PDFAnnotation(
    bounds: bounds,
    forType: .highlight,
    withProperties: nil
)

page.addAnnotation(annotation)

于是:

Page Content
+
Highlight Annotation

共同组成最终页面。


8. Underline 和 Strikeout 也是类似思路

例如:

Highlight
Underline
Strikeout

UI 看起来是三种功能。

底层:

PDFAnnotation Type

不同。

因此 PDF 编辑工具不应该写:

highlightText()
underlineText()
strikeoutText()

然后各自独立一整套架构。

更合理:

TextMarkupService
      ↓
Annotation Type

9. 手写画笔怎么实现?

自由手写:

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

并利用:

  • Pressure
  • Tilt
  • Precise 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 应该独立建模

例如:

struct SavedSignature {
    let id: UUID
    let createdAt: Date
    let vectorData: Data?
    let imageData: Data?
}

UI:

Signature Library
│
├── Signature A
└── Signature B

用户选择:

Signature A

然后:

Drag
Resize
Place

最终转成:

PDF Annotation

15. 签名保存在哪里?

这涉及用户隐私。

签名属于比较敏感的数据。

因此不建议:

随便放到公开 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 编辑

比如:

protocol PDFEditCommand {
    func execute()
    func undo()
}

新增高亮:

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

例如 Key:

PDF 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

找到匹配位置。

UI:

23 Results

然后跳转页面。


28. 扫描 PDF 可能没有文字层

例如:

Paper
 ↓
Camera
 ↓
PDF

如果只是图片生成 PDF,

页面里面实际上是:

Image

而不是:

Text

于是:

Search
Copy Text
Select Text

都无法工作。

这就是:

OCR

能力出现的原因。


29. OCR 和 PDF 是两个不同模块

PDFService 负责:

PDF Structure
Pages
Annotations
Save

OCRService 负责:

Image
 ↓
Text Recognition

不要把所有能力塞进:

PDFManager

更合理:

PDFService
OCRService
ScanService

各自负责不同问题。


30. 扫描生成 PDF 的基本流程是什么?

用户操作:

打开相机
 ↓
拍摄纸张
 ↓
自动识别边缘
 ↓
透视矫正
 ↓
增强
 ↓
生成页面
 ↓
PDF

整个 Pipeline:

Camera Image
    ↓
Document Detection
    ↓
Perspective Correction
    ↓
Image Enhancement
    ↓
Page Image
    ↓
PDF Generator

这并不是简单:

Camera Photo
 ↓
PDF

31. VisionKit 很适合文档扫描场景

在 iOS 里,系统提供了和文档扫描相关的能力。

产品可以利用系统扫描体验完成:

Document Capture

包括:

  • 页面边缘检测
  • 透视调整
  • 多页扫描

然后拿到扫描结果:

Page Images

再进入自己的:

PDFService

生成 PDF。


32. ScanService 应该独立

例如:

protocol ScanService {
    func scanDocuments() async throws -> [ScannedPage]
}

其中:

struct ScannedPage {
    let image: 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
Scanner

34. 页面尺寸怎么选?

图片生成 PDF 时要决定:

Page Size

例如:

A4
Letter
Original Image Ratio

如果用户扫描合同,

通常:

A4

比较自然。

如果只是:

照片合集 PDF

则:

Fit Image

可能更合适。

因此 PDF Create Options 可以包含:

Page Size
Margins
Orientation
Image Fit

35. 扫描 PDF 为什么很容易变得巨大?

假设一页扫描图:

4032 × 3024

10 页:

10 high-resolution images

如果全部用高质量 PNG 写进 PDF,

最终可能:

100MB+

所以扫描生成 PDF 时必须考虑:

Image Compression

比如:

Downsample
+
JPEG Compression

36. 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 本质也是:

FileOperation

42. 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 错误应该统一业务化

例如:

enum PDFProcessingError: Error {
    case invalidDocument
    case encrypted
    case wrongPassword
    case permissionDenied
    case unsupportedOperation
    case insufficientStorage
    case saveFailed
    case cancelled
    case unknown(Error)
}

UI 显示:

PDF 密码错误

而不是:

Error Domain=PDFKit...

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 Edit

51. 远程更新还要考虑冲突

假设:

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 可以怎么抽象?

例如:

protocol PDFService {

    func inspect(
        _ item: FileItem
    ) async throws -> PDFInfo

    func addAnnotation(
        _ annotation: PDFAnnotationModel,
        to item: FileItem
    ) async throws

    func merge(
        _ items: [FileItem],
        destination: FileLocation
    ) async throws -> FileItem

    func split(
        _ item: FileItem,
        pages: IndexSet,
        destination: FileLocation
    ) async throws -> FileItem

    func create(
        from images: [FileItem],
        options: PDFCreateOptions
    ) async throws -> FileItem
}

这样:

PDFKit

只是底层实现。


53. PDFAnnotationModel 最好独立于 PDFKit

如果业务层直接持有:

PDFAnnotation

以后很难:

  • 做 Undo
  • 做持久化
  • 做同步
  • 做其他 PDF Engine

更好的方式:

struct PDFAnnotationModel {
    let id: UUID
    let pageIndex: Int
    let type: AnnotationType
    let bounds: 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
StorageChecker

55. 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 Platform

57. 开发 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 能力也完整接入这套架构。