diff --git a/Doc/大书优化方案_内存与主线程.md b/Doc/大书优化方案_内存与主线程.md deleted file mode 100644 index ed5971f..0000000 --- a/Doc/大书优化方案_内存与主线程.md +++ /dev/null @@ -1,2957 +0,0 @@ -# 大书优化方案:章节级缓存运行时与二次打开加速 - -> 适用对象:`textReflowable` 路径下的超大 EPUB,例如《凡人修仙传》精校版全本。 -> 文档目标:把当前”整书分页元数据磁盘缓存 + 打开时重建整本模型”的实现,迁移为**以 WXRead 为基线**的章节级运行时;同时明确哪些部分是 SDK 为了工程化和二次打开体验做的增强。 -> 更新时间:2026-06-02 - -### 作用域边界 - -**本方案所有改造(含 DataSource 适配、窗口快照、章节缓存运行时)仅适用于 `textReflowable` 路径。** - -具体边界: - -- `RDEPUBReaderController+DataSource` 的窗口快照直连改造,仅在 `textReflowable` 类型书籍的控制器实例上启用 -- `RDReaderView` 消费的数据源切换为窗口快照,仅限 textReflowable 路径 -- 固定版式(`fixed-layout`)、PDF 等其他路径维持原有 DataSource 实现不变 -- `RDEPUBReaderController` 需在初始化时判断书籍类型,选择对应的 DataSource 策略 - -实现要求: - -- 不允许将窗口快照 DataSource 无条件覆盖到所有 `RDReaderView` 消费者 -- 书籍类型判断必须在控制器层完成,不能下沉到 `RDReaderView` 内部 -- 非 textReflowable 路径继续使用整书 `pages` 数组供页,不受本方案影响 - ---- - -## 1. 范围与结论 - -这份方案的核心方向与 WXRead 一致: - -- 正文运行时不再以全书 `RDEPUBTextBook` 为真值 -- 正文缓存不再以整书分页文件为主路径 -- 运行时只围绕“当前章 + 相邻章”工作 -- 章节加载串行化 -- 内存回收与阅读窗口绑定 - -但这份方案**不是逐字逐句复刻 WXRead 的类拆分**,而是: - -- `WXRead` 的核心缓存与生命周期策略:保持一致 -- `ReadViewSDK` 的工程化拆分类、轻量磁盘摘要层:作为 SDK 增强 - -一句话总结: - -**主缓存与运行时语义复刻 WXRead;工程结构与二次打开加速允许做 SDK 增强。** - ---- - -## 2. 当前问题 - -当前实现的主要问题不是“完全没有缓存”,而是缓存重心与运行时真值都放错了位置: - -- `RDEPUBTextBookCache` 缓存的是整书分页元数据 -- 打开书时仍然会重新读取 HTML、重新生成富文本、重新组装整书模型 -- `context.textBook`、全书 `pages`、全书索引表仍然参与正文主链路 -- 大书场景下,CPU、内存、主线程压力都容易被整书路径放大 - -这和 WXRead 的关键差异是: - -- WXRead 主缓存是章节运行时缓存 -- WXRead 主路径不依赖整书分页文件 -- WXRead 主运行时不以“后台补齐整书再切换”为中心 - ---- - -## 3. 与 WXRead 对齐的部分 - -以下部分要求与 WXRead 保持同方向: - -### 3.0 本次复刻依据 - -本方案以 `Doc/WXRead` 中的本地逆向资料为依据,尤其是: - -- `Doc/WXRead/内存优化方案_WXRead策略.md` -- `Doc/WXRead/decompiled/WRReaderViewController.m` -- `Doc/WXRead/decompiled/WRReaderViewController.h` -- `Doc/WXRead/decompiled/WRChapterPageCount.m` -- `Doc/WXRead/analysis/03_数据结构与API协议定义.md` - -WXRead 中需要被复刻的运行时语义如下: - -- `WRReaderViewController` 持有 `chapterDataCache`、`pageCountCache`、`currentChapterData`、`currentChapterPageCount`、`chapterLoadQueue` -- `chapterLoadQueue = dispatch_queue_create("com.weread.chapterload", DISPATCH_QUEUE_SERIAL)` -- `gotoChapterIdx:position:positionOfFile:` 是章节跳转主入口: - - 先更新 `readingProgress.chapterIndex/pageIndex/charIndex` - - 命中 `chapterDataCache[@(chapterIdx)]` 时直接显示 - - 未命中时异步 `_loadChapterAtIndex` - - `positionOfFile > 0` 时用 `WRChapterPageCount.pageIndexForCharacterIndex:` 恢复章内页 -- `didFlipPage` 只更新当前章内页码与 `charIndex`,再保存进度并触发相邻章预取 -- `_handleMemoryWarning:` 保留当前章,清空 `chapterDataCache` 后放回当前章,并清空全部 `pageCountCache` -- 设置变化时清空 `pageCountCache`;全局排版变化时清空 `chapterDataCache + pageCountCache`,再重载当前章 -- `_prefetchAdjacentChaptersForIndex:` 只围绕当前章的上一章/下一章工作,不做正文运行时全书预加载 - -因此 ReadViewSDK 的实现必须复刻这些语义,而不是继续沿用“快速首章进入 + 后台补齐整书 `RDEPUBTextBook` + staged/full apply”的路线。 - -### 3.1 章节级主缓存 - -- 章节缓存粒度为单章 -- 当前章与相邻章构成运行时窗口 -- 窗口外章节必须可确定性淘汰 - -### 3.2 串行章节加载 - -- 所有章节构建都通过单一串行队列 -- 同一时刻只允许一章实际构建 - -### 3.3 内存警告策略 - -- 当前章必须保住 -- 非当前窗口章节优先释放 - -### 3.4 正文真值退化为章节级 - -- 不再以全书 `TextBook` 为正文主真值 -- 不再把整书分页完成视为阅读器唯一稳定态 - ---- - -## 4. SDK 增强项 - -下面这些设计是**相对 WXRead 的 SDK 增强**,不应表述为“原样复刻”: - -### 4.1 轻量章节摘要磁盘缓存 - -`chapterSummaryDiskCache` 是 SDK 增强,不是 WXRead 原生能力。 - -定位: - -- 只用于二次打开加速 -- 只缓存轻量摘要 -- 不是正文主缓存 - -### 4.2 协调器拆分 - -以下类拆分是 SDK 工程化改进: - -- `RDEPUBChapterLoader` -- `RDEPUBChapterWindowCoordinator` -- `RDEPUBChapterLocationCoordinator` - -WXRead 把类似职责大量内联在 `WRReaderViewController` 内;ReadViewSDK 不必复制这种类膨胀结构。 - -### 4.3 旧链路处理边界 - -本稿按“章节运行时路径为唯一主路径”组织,不再保留旧整书主路径的并行设计。 - -要求: - -- 文档中的数据源、分页、位置持久化均只描述章节运行时方案 -- 旧整书 `TextBook` 路径只作为历史背景,不再作为本方案的一部分 -- 实施时如需过渡脚手架,可单独记录在迁移任务中,但不写入主设计文档 - ---- - -## 5. 设计原则 - -### 5.1 正文主缓存坚持 WXRead - -- 正文主缓存以章节级内存缓存为主 -- 章节淘汰必须由阅读窗口和内存策略显式决定 -- 不能把整书磁盘分页缓存重新扶正成正文主路径 - -### 5.2 图片与轻量摘要层可参考 SDWebImage - -可以借鉴 SDWebImage 的部分: - -- `memory -> disk -> rebuild` 分层思路 -- cache key 设计 -- 版本化与失效策略 -- 图片 `NSCache` - -但以下对象不能按 SDWebImage 思路长期磁盘化: - -- `RDEPUBRuntimeChapter` -- `NSAttributedString` -- `RDEPUBTextLayouter` -- 完整 `pages` - -### 5.3 章节生命周期优先于历史命中率 - -缓存目标不是“尽量记住所有历史章节”,而是: - -- 当前章立即可读 -- 相邻章尽量无感 -- 窗口外尽快释放 - -### 5.4 位置真值改为章节语义 - -主位置语义改成: - -- `spineIndex` -- `chapterOffset` -- `fragmentID` - -全书页码不再作为稳定真值,只能是派生展示值。 - ---- - -## 6. 运行时架构 - -### 6.1 核心分层 - -建议分成 4 层: - -1. `RDEPUBChapterRuntimeStore` -2. `RDEPUBChapterLoader` -3. `RDEPUBChapterWindowCoordinator` -4. `RDEPUBChapterLocationCoordinator` - -### 6.2 类型安全缓存封装 - -虽然缓存策略等价于 WXRead 的 `NSMutableDictionary + 手动淘汰`,但在 Swift 代码库里不建议直接把 `NSMutableDictionary` 暴露为主接口。 - -建议使用类型安全封装: - -```swift -final class RDEPUBChapterDataCache { - private var storage: [Int: RDEPUBRuntimeChapter] = [:] - private let lock = NSLock() -} - -final class RDEPUBPageCountCache { - private var storage: [RDEPUBChapterCacheKey: RDEPUBRuntimePageCount] = [:] - private let lock = NSLock() -} -``` - -要求: - -- 对外暴露 typed API -- 内部保持确定性淘汰能力 -- 不把 Objective-C 容器直接扩散到全链路 - -### 6.3 线程安全规则 - -缓存不是天然线程安全的,文档必须明确: - -- 章节构建统一在 `chapterLoadQueue` 发生 -- 缓存写入统一通过 store 的同步接口完成 -- 缓存读取也必须通过 store 的同步接口 -- 不允许主线程裸读缓存、后台线程裸写缓存 - -推荐实现: - -- `chapterLoadQueue` 负责串行构建 -- cache wrapper 内部用 `NSLock` 或专用串行队列保护读写 - ---- - -## 7. 缓存设计 - -### 7.1 一级缓存:章节运行时缓存 - -```swift -final class RDEPUBRuntimeChapter { - let spineIndex: Int - let href: String - let title: String - - let sourceAttributedString: NSAttributedString? - let typesetAttributedString: NSAttributedString - let layouter: RDEPUBTextLayouter - let pages: [RDEPUBTextPage] - let pageRanges: [NSRange] - - let chapterOffsetMap: RDEPUBChapterOffsetMap -} -``` - -#### 关于 `sourceAttributedString` - -这里不再强制“缓存中永远同时保留 source 和 typeset 两份”。 - -建议策略: - -- 首次构建后可同时保留两份 -- 当章节进入稳定缓存态后,可按策略释放 `sourceAttributedString` -- 需要重新排版时,再从 HTML 或轻量中间态重建 source - -原因: - -- `source + typeset` 双持会显著放大内存 -- WXRead 这么做不代表 ReadViewSDK 必须照搬内存代价 - -文档结论: - -- `sourceAttributedString` 在类型上允许存在 -- 但不是必须长期常驻的字段 - -### 7.2 二级缓存:页数与页范围缓存 - -`pageCountCache` 的定位要收紧: - -- 它不是独立于 `chapterDataCache` 的第二真值 -- 它是“轻量分页结构缓存”的内存映像 -- 它主要服务于: - - 二次打开加速 - - 章节未命中时的快速分页结构恢复 - -一致性原则: - -- `chapterDataCache` 命中时,以章节对象内部 `pageRanges` 为准 -- `pageCountCache` 不能覆盖章节对象真值 -- 常规章节淘汰时,可以同步清理与该章关联的 `pageCountCache` -- 内存警告与全局排版设置变化时,按 WXRead 语义直接清空全部 `pageCountCache` - -注意: - -- WXRead 的 `pageCountCache` key 由 `WRChapterPageCount.currentCacheKeyWithBookId:` 生成,编码书籍 ID 与当前排版设置 -- ReadViewSDK 可以把 `spineIndex/chapterContentHash` 纳入 key,增强失效精度 -- 但运行时语义必须保持一致:`pageCountCache` 是可重建的分页结构缓存,不是稳定正文真值 - -### 7.3 三级缓存:轻量章节摘要磁盘缓存 - -`chapterSummaryDiskCache` 是 SDK 增强层。 - -只允许缓存轻量字段: - -- `pageRanges` -- `pageCount` -- `fragmentOffsets` 摘要 -- `renderSignature` -- `schemaVersion` -- `chapterContentHash` -- `pageMetadataList`(每页的 breakReason / attachmentKinds / blockKinds / semanticHints / attachmentPlacements / trailingFragmentID 摘要) - -不允许缓存: - -- 完整 `RDEPUBRuntimeChapter` -- `NSAttributedString` -- `RDEPUBTextLayouter` -- 完整 `pages` - -### 7.4 四级缓存:解压缓存 - -EPUB 解压目录仍然保留在 `Caches`,但它只负责避免重复解压,不参与正文真值。 - ---- - -## 8. 关键缺失定义补齐 - -### 8.1 `RDEPUBChapterOffsetMap` - -需要明确定义: - -```swift -struct RDEPUBChapterOffsetMap { - let fragmentOffsets: [String: Int] - let pageStartOffsets: [Int] - let pageEndOffsets: [Int] -} -``` - -职责: - -- `fragmentID -> chapterOffset` -- `chapterOffset -> pageIndex` -- 章内命中测试与位置恢复 - -### 8.2 `renderSignature` - -`renderSignature` 必须包含: - -- `fontName` -- `fontSize` -- `lineHeightMultiple` -- `lineSpacing` -- `layoutConfigSignature` -- `schemaVersion` - -它是分页结构与轻量摘要层的核心失效依据。 - -补充约束: - -- `contentInsets`、`pageSize`、`frameWidth/frameHeight` 不在外层重复编码,由 `layoutConfig.cacheSignature` 统一覆盖 -- `renderSignature` 的职责是组合“字体/行距/布局签名/版本号”,避免和 `layoutConfig.cacheSignature` 双重编码导致无意义失效 - -### 8.2.1 `layoutConfig.cacheSignature` 字段组成 - -`cacheSignature` 是 `RDEPUBTextLayoutConfig` 的签名摘要,必须覆盖所有影响分页结果的排版参数。任何参数变化都必须导致签名不同,从而使旧缓存自然失效。 - -WXRead 的 `WRChapterPageCount.currentCacheKeyWithBookId:` 至少编码: - -- `bookId` -- 字号 -- 行距 -- 页面宽高 - -ReadViewSDK 需要在这个基础上增加当前工程已有的分页参数。必须以当前 `RDEPUBTextLayoutConfig` 的真实字段为准,不允许照抄不存在的伪字段。 - -当前工程必须包含的字段: - -```swift -extension RDEPUBTextLayoutConfig { - /// 缓存签名:覆盖所有影响分页结果的排版参数 - /// 任何字段变化都必须导致签名变化,否则会出现缓存命中但分页结果不一致的 bug - var cacheSignature: String { - let fields: [String] = [ - "frameWidth:\(frameWidth)", - "frameHeight:\(frameHeight)", - "edgeInsets:\(edgeInsets.top),\(edgeInsets.left),\(edgeInsets.bottom),\(edgeInsets.right)", - "numberOfColumns:\(numberOfColumns)", - "columnGap:\(columnGap)", - "avoidOrphans:\(avoidOrphans)", - "avoidWidows:\(avoidWidows)", - "avoidPageBreakInsideEnabled:\(avoidPageBreakInsideEnabled)", - "hyphenation:\(hyphenation)", - "imageMaxHeightRatio:\(imageMaxHeightRatio)", - "fallbackViewportSize:\(fallbackViewportSize.width),\(fallbackViewportSize.height)", - ] - return fields.joined(separator: "|") - } -} -``` - -设计原则: - -- **只包含影响分页结果的字段**:纯展示参数(如高亮颜色、选中样式)不进签名 -- **`renderSignature` 负责组合**:`fontName/fontSize/lineHeightMultiple/lineSpacing/layoutConfig.cacheSignature/schemaVersion` 必须在最终 key 中同时出现 -- **字段顺序固定**:签名拼接顺序必须稳定,避免因字段顺序变化导致无意义的缓存失效 -- **新增字段时必须追加到签名末尾**:并在 `schemaVersion` 中递增,确保旧缓存不被错误命中 - -不包含的字段: - -- `highlightColor`:不影响分页结果 -- `selectionColor`:不影响分页结果 -- `debugShowPageBounds`:调试开关,不影响分页结果 -- 文档或历史方案中提到但当前模型不存在的字段,例如 `paragraphSpacing`、`letterSpacing`、`attachmentPlacement`,不能进入实现 - -### 8.3 `RDEPUBChapterCacheKey` - -建议定义: - -```swift -struct RDEPUBChapterCacheKey: Hashable { - let bookID: String - let spineIndex: Int - let renderSignature: String - let chapterContentHash: String -} -``` - -### 8.4 `RDEPUBRuntimePageCount` - -`pageCountCache` 依赖的轻量分页结构类型需要明确: - -```swift -struct RDEPUBRuntimePageCount { - let cacheKey: RDEPUBChapterCacheKey - let spineIndex: Int - let pageRanges: [NSRange] - let pageCount: Int - let renderSignature: String -} -``` - -### 8.5 排版参数变化时的缓存失效 - -排版参数变化后必须同时处理: - -- 清理 `chapterDataCache` -- 清理 `pageCountCache` -- 使 `chapterSummaryDiskCache` 对应 key 自然失效 - -不要做部分失效,否则最容易出现页范围与正文不一致。 - ---- - -## 9. 二次打开加速方案 - -### 9.1 目标 - -第二次打开要更快,但不允许把完整章节运行时对象长期磁盘化。 - -### 9.2 命中链路 - -第二次打开推荐链路: - -1. 命中 EPUB 解压缓存 -2. 定位目标 `spineIndex` -3. 先查 `chapterDataCache` -4. 未命中时查 `pageCountCache` -5. 仍未命中时查 `chapterSummaryDiskCache` -6. 命中轻量分页结构后跳过最重的分页计算 -7. 重建当前章必需的富文本和页面对象 -8. 当前章先进入阅读器 -9. 再按 `±1` 规则预取相邻章 - -### 9.3 能省掉什么 - -能明显减少: - -- 重新分页计算 -- 页范围生成 -- 部分 fragment offset 计算 - -仍然需要: - -- 读取目标章 HTML -- 生成必要的富文本 -- 构建当前章页面对象 - -### 9.4 结果预期 - -这不是“零重建秒进”,而是“跳过最重步骤后的明显加速”。 - ---- - -## 10. 位置模型与跨章能力 - -### 10.1 新位置结构 - -```swift -public struct RDEPUBChapterLocation: Codable { - public var spineIndex: Int - public var chapterOffset: Int - public var fragmentID: String? - public var progressionInChapter: Double? -} -``` - -### 10.2 存量数据迁移 - -必须考虑旧数据兼容: - -- 旧版页码位置 -- 旧版全局偏移位置 -- 旧版书签 -- 旧版高亮 - -建议: - -1. 位置持久化增加版本字段 -2. 先实现 `legacy -> chapterLocation` 转换器 -3. 新写入统一用 `RDEPUBChapterLocation` -4. 旧数据转换成功后覆盖为新格式 - -### 10.3 跨章功能替代方案 - -停止依赖全书 `RDEPUBTextIndexTable` 后,需要明确替代设计: - -- 全书搜索结果 - - 搜索索引层仍可维护轻量章节级倒排或章节命中列表 - - 展示位置以“章节标题 + 章内片段”替代全书绝对页码真值 -- 书签 / 高亮 - - 统一持久化为 `spineIndex + chapterOffset + rangeLength` - - 当章节不在窗口内时,按章节级锚点懒加载恢复 -- 目录跳转 - - 直接定位 `spineIndex` / `fragmentID` - -文档结论: - -- 可以放弃全书绝对页码真值 -- 不能放弃跨章功能 - ---- - -## 11. RDReaderView 适配策略 - -当前 `RDReaderView` 更习惯消费连续页数组;迁移到章节窗口后,需要加一层适配。 - -### 11.1 建议方案 - -引入窗口快照: - -```swift -struct RDEPUBChapterWindowSnapshot { - let chapters: [RDEPUBRuntimeChapter] - let flattenedPages: [RDEPUBTextPage] - let anchorChapterIndex: Int -} -``` - -说明: - -- 运行时真值仍然是章节窗口 -- `RDReaderView` 只消费当前窗口展开后的局部连续页数组 -- 不再消费整书连续页数组 - -### 11.2 切窗策略 - -当跨章时: - -1. 先构造新的窗口快照 -2. 再切换 `RDReaderView` 数据源 -3. 保持当前阅读锚点不抖动 - ---- - -## 12. 快速翻章体验 - -`±1` 窗口 + 串行队列意味着快速翻章一定存在等待风险,方案必须定义 UI 行为。 - -建议: - -- 下一章未就绪时,展示章节级 loading 状态 -- loading 必须是页内轻提示,不要整屏阻塞 -- 若用户连续快速翻章,只保留最后一次目标章节请求 -- 非当前目标章节的排队请求可取消或降级 -- 已经开始执行中的章节构建默认不强行中断 -- 当前任务结束后只允许最后一次目标章节请求进入显示链路 - -目标: - -- 不追求无限预读 -- 追求在内存可控前提下的稳定体验 - ---- - -## 13. 内存警告与淘汰策略 - -### 13.1 章节缓存策略 - -收到 `UIApplication.didReceiveMemoryWarningNotification` 时: - -1. 保存当前章 -2. 清空非当前章缓存 -3. 恢复当前章 -4. 清理图片缓存 - -### 13.2 `pageCountCache` 策略 - -这里按 WXRead 的 `_handleMemoryWarning:` 复刻: - -- `pageCountCache` 不是独立真值 -- 当前章的页范围已经包含在 `RDEPUBRuntimeChapter` -- 内存警告时直接清空全部 `pageCountCache` - -ReadViewSDK 不再采用“保留当前章 pageCountCache”的保守方案。原因: - -- WXRead 的内存警告策略就是清空全部分页缓存 -- 当前章显示依赖 `RDEPUBRuntimeChapter.pageRanges/pages`,不依赖 `pageCountCache` -- `pageCountCache` 可由当前章对象或磁盘摘要重新回填 - -普通窗口淘汰时可以移除对应章节的 `pageCountCache` 条目;内存警告、全局排版变化、schemaVersion 变化时必须清空全部。 - ---- - -## 14. 具体开发方案 - -### 14.1 目标分层 - -后续代码建议拆成: - -1. `RDEPUBChapterRuntimeStore` -2. `RDEPUBChapterLoader` -3. `RDEPUBChapterWindowCoordinator` -4. `RDEPUBChapterLocationCoordinator` -5. `RDEPUBChapterSummaryDiskCache` - -### 14.2 与现有模块的替换关系 - -- `RDEPUBReaderPaginationCoordinator` - - 从整书分页协调器改成章节窗口分页入口 -- `RDEPUBTextBookBuilder` - - 保留单章构建能力 - - 整书构建不再作为大书正文主路径 -- `RDEPUBReaderContext` - - 降低 `textBook` 真值地位 - - 挂入 `chapterRuntimeStore` -- `RDEPUBReaderController+DataSource` - - 改为基于窗口快照供页 -- `RDEPUBReaderRuntime` - - `go(toPageNumber:)` 改成章内语义 - -### 14.3 标准章节加载链路 - -1. 输入 `spineIndex` -2. 查 `chapterDataCache` -3. miss 后查 `pageCountCache` -4. 仍未命中时查 `chapterSummaryDiskCache` -5. 在 `chapterLoadQueue` 中读取 HTML -6. 构建 source/typeset -7. 构建 layouter -8. 若命中 `pageCountCache` 或 `chapterSummaryDiskCache`,优先复用 `pageRanges` -9. 若未命中轻量分页结构,则执行完整分页 -10. 生成 `pages` -11. 生成 `chapterOffsetMap` -12. 回填章节缓存 -13. 回填 `pageCountCache` -14. 回填轻量摘要层 -15. 更新窗口快照 -16. 回主线程驱动显示 - -### 14.4 串行队列中的取消语义 - -串行队列下的取消分为两类: - -1. 尚未开始执行的排队请求 -2. 已经进入章节构建中的请求 - -文档结论: - -- 对于尚未开始执行的请求:允许取消,只保留最后一次目标章节请求 -- 对于已经开始执行的请求:默认不强行中断 CoreText / 分页过程 - -原因: - -- 当前工程没有安全的“可中断分页事务”机制 -- 强中断会放大半完成状态、缓存污染和 UI 状态错乱的风险 - -推荐实现: - -- 采用“可取消排队,不中断执行中任务”的策略 -- 当前任务完成后,立刻检查最后一次目标章节是否变化 -- 若目标已变化,则丢弃不再需要的结果,不更新窗口 -- 只把最后一次目标章节推进到显示链路 - -快速跳章体验要求: - -- 如果用户从目录直接跳到较远章节,例如第 50 章: - - 旧的排队请求可以取消 - - 当前正在构建的章节允许自然完成 - - 完成后立即转向最新目标章节请求 -- UI 必须提供轻量 loading 提示,明确当前正在打开目标章节 - -延迟约束: - -- 单章构建耗时必须可观测 -- 如果快速跳章的等待不可接受,优先优化单章构建耗时 -- 不应先引入危险的强中断机制 - ---- - -## 15. 迁移策略 - -### 15.1 单一路径切换 - -本方案不再维护“新旧两套正文链路并存”。 - -要求: - -- `RDEPUBChapterRuntimeStore + RDEPUBChapterLoader + RDEPUBChapterWindowCoordinator` 组成唯一正文主路径 -- `RDReaderView` 的数据源直接消费窗口快照 -- 位置持久化、翻章、搜索结果定位统一落到章节语义 -- P0 可以保留旧代码文件作为未调用的回退脚手架,但 textReflowable 启动入口不得再进入 `paginateTextPublication -> buildQuickTextBook -> staged/full apply` -- P4 的职责是删除未调用旧代码,而不是才切换主入口 - -### 15.2 P0 风险控制 - -P0 改成: - -- `P0-1` 建立章节 store 与 loader -- `P0-2` 建立窗口数据源适配 -- `P0-3` 打通打开书、翻章、持久化三条核心链路 -- `P0-4` 将 textReflowable 的 `paginatePublication` 委托到 `ChapterWindowCoordinator.openBook(at:)` -- `P0-5` 验证通过后,P4 再删除旧整书主路径相关未调用代码 - -这样可以避免“主设计仍在描述双路径”,让实现和文档保持一致。 - ---- - -## 16. 可直接开发的实施清单 - -### P0:建立章节真值主路径 - -1. 新建 `RDEPUBChapterRuntimeStore` -2. 新建 `RDEPUBChapterLoader` -3. 新建 `RDEPUBChapterWindowSnapshot` -4. 让 `RDEPUBReaderController+DataSource` 直接消费窗口快照 -5. 打通打开书、翻章、位置持久化 -6. textReflowable 路径停止调用 staged/full `RDEPUBTextBook` apply - -### P1:建立完整章节缓存运行时 - -1. 实现 `chapterDataCache` -2. 实现 `pageCountCache` -3. 实现 `chapterOffsetMap` -4. 建立 `±1` 预取窗口 -5. 建立内存警告清理 - -### P2:接入二次打开加速层 - -1. 新建 `RDEPUBChapterSummaryDiskCache` -2. 定义 `renderSignature` -3. 定义 `RDEPUBChapterCacheKey` -4. 当前章优先命中轻量摘要层 - -### P3:完成位置与跨章能力迁移 - -1. 新建 `RDEPUBChapterLocation` -2. 实现 legacy 位置转换器 -3. 搜索/书签/高亮改成章节级锚点 -4. `go(toPageNumber:)` 改为章内语义 - -### P4:移除旧整书主路径依赖 - -1. 移除大书场景下的整书 `TextBook` 主路径依赖 -2. 降级 `RDEPUBTextBookCache` -3. 清理 staged/full apply 相关状态 - ---- - -## 17. 验收标准 - -- 大书正文主路径不再依赖整书 `RDEPUBTextBook` -- 当前章与相邻章构成运行时真值 -- 所有章节构建始终串行 -- 缓存读写线程安全规则明确且已落地 -- 参数变化后缓存整体一致失效,不出现页范围错配 -- 内存警告后当前阅读不中断 -- 二次打开能明显减少分页计算时间 -- 旧书签、高亮、阅读位置能够迁移到章节级位置模型 -- 搜索、目录、书签、高亮等跨章能力在无全书索引真值下仍可正常工作 -- **位置迁移降级验收**:`schemaVersion == 1` 的粗估降级结果偏差 ≤ ±15%,且下次打开同一章时必须被精确值覆盖(详见 §19.4.1 粗估降级验收标准) - ---- - -## 18. 结论 - -这份方案的最终边界是: - -- **WXRead 对齐部分** - - 章节级主缓存 - - 串行章节加载 - - `±1` 窗口 - - 当前章优先的内存回收 - - 章节级位置真值 - -- **SDK 增强部分** - - 协调器拆分 - - 类型安全缓存包装 - - 轻量章节摘要磁盘缓存 - - 单一路径分阶段切换 - -只要实现时始终坚持”正文主缓存是章节内存缓存、磁盘层只是辅助加速层”,这条路线就是正确的。 - ---- - -## 19. 伪代码级别落实方案 - -以下按 P0→P4 阶段给出每个核心类型的伪代码,精确到属性、方法签名和关键逻辑分支,可作为开发参照;实现时仍需按当前工程真实 API 名称适配,不能逐字复制尚未存在的 helper。 - -### 19.1 P0:切换 textReflowable 到章节真值主路径 - -P0 的目标不是继续保留双主链路,而是让 textReflowable 的正文显示从第一屏开始就由章节窗口驱动。旧整书分页代码可以暂时留在文件中,但不再作为 textReflowable 的运行时入口。 - -#### 19.1.1 RDEPUBChapterRuntimeStore - -```swift -// ============ 新增文件:RDEPUBChapterRuntimeStore.swift ============ - -final class RDEPUBChapterRuntimeStore { - - // MARK: - 子缓存 - - /// 章节运行时主缓存(等价 WXRead chapterDataCache) - private let chapterDataCache = RDEPUBChapterDataCache() - - /// 轻量分页结构缓存(等价 WXRead pageCountCache) - private let pageCountCache = RDEPUBPageCountCache() - - /// 图片缓存(独立 NSCache,等价 WXRead imageCache) - let imageCache = NSCache() - - /// 串行加载队列(等价 WXRead com.weread.chapterload) - let chapterLoadQueue = DispatchQueue(label: "com.rdreader.chapterload", qos: .utility) - private let chapterLoadQueueKey = DispatchSpecificKey() - - // MARK: - 窗口状态 - - /// 当前章 spineIndex - private(set) var currentSpineIndex: Int? - - /// 当前窗口内的 spineIndex 集合(当前 + prev + next) - private(set) var windowSpineIndices: [Int] = [] - - // MARK: - 请求通道(前台导航 vs 后台预取,语义独立,互不抢占) - - /// 前台导航目标(用户主动跳章:目录/书签/搜索/翻章) - /// 仅保留最后一次目标,旧的排队请求可被取消 - private var pendingNavigationTarget: Int? - private let navigationLock = NSLock() - - /// 后台预取目标集合(±1 相邻章预取) - /// 预取不抢占前台导航通道,预取完成后仅刷新快照,不触发跳章 - private var pendingPrefetchTargets: Set = [] - private let prefetchLock = NSLock() - - /// 是否有章节正在构建中 - private(set) var isBuilding: Bool = false - private let buildingLock = NSLock() - - // MARK: - 初始化 - - init() { - imageCache.countLimit = 50 - chapterLoadQueue.setSpecific(key: chapterLoadQueueKey, value: ()) - } - - func assertNotOnChapterLoadQueue() { - dispatchPrecondition(condition: .notOnQueue(chapterLoadQueue)) - } - - // MARK: - 缓存查询(线程安全,通过 cache wrapper 的 lock 保护) - - func chapterData(for spineIndex: Int) -> RDEPUBRuntimeChapter? { - return chapterDataCache[spineIndex] - } - - func pageCount(for key: RDEPUBChapterCacheKey) -> RDEPUBRuntimePageCount? { - return pageCountCache[key] - } - - // MARK: - 缓存插入 - - func insertChapter(_ chapter: RDEPUBRuntimeChapter) { - chapterDataCache[spineIndex: chapter.spineIndex] = chapter - } - - func insertPageCount(_ pc: RDEPUBRuntimePageCount, for key: RDEPUBChapterCacheKey) { - pageCountCache[key] = pc - } - - // MARK: - 窗口管理 - - /// 设定当前章,自动计算 ±1 窗口 - func setCurrentChapter(spineIndex: Int, totalSpineCount: Int) { - currentSpineIndex = spineIndex - var window = [spineIndex] - if spineIndex > 0 { window.append(spineIndex - 1) } - if spineIndex < totalSpineCount - 1 { window.append(spineIndex + 1) } - windowSpineIndices = window - } - - /// 返回窗口外、应该淘汰的 spineIndex - func evictableSpineIndices() -> [Int] { - let windowSet = Set(windowSpineIndices) - return chapterDataCache.storedSpineIndices.filter { !windowSet.contains($0) } - } - - // MARK: - 淘汰 - - func evict(spineIndex: Int) { - chapterDataCache.remove(spineIndex: spineIndex) - // 同步淘汰对应的 pageCountCache - pageCountCache.remove(forSpineIndex: spineIndex) - } - - func evictAllExceptCurrent() { - guard let current = currentSpineIndex else { - chapterDataCache.removeAll() - pageCountCache.removeAll() - return - } - let currentChapter = chapterDataCache[current] - chapterDataCache.removeAll() - if let ch = currentChapter { - chapterDataCache[spineIndex: current] = ch - } - // WXRead 语义:内存警告时 pageCountCache 全量清空 - pageCountCache.removeAll() - } - - // MARK: - 内存警告 - - func handleMemoryWarning() { - evictAllExceptCurrent() - imageCache.removeAllObjects() - } - - // MARK: - 前台导航请求管理(§14.4 取消语义) - - /// 注册前台导航目标(用户主动跳章时调用) - /// 仅保留最后一次目标,旧的排队请求可被取消 - func setNavigationTarget(spineIndex: Int) { - navigationLock.lock() - pendingNavigationTarget = spineIndex - navigationLock.unlock() - } - - /// 消费前台导航目标(章节构建完成后调用,检查是否有更新的目标) - func consumeNavigationTarget() -> Int? { - navigationLock.lock() - let target = pendingNavigationTarget - pendingNavigationTarget = nil - navigationLock.unlock() - return target - } - - // MARK: - 后台预取请求管理 - - /// 注册后台预取目标(±1 相邻章预取时调用) - /// 预取不抢占前台导航通道 - func addPrefetchTarget(_ spineIndex: Int) { - prefetchLock.lock() - pendingPrefetchTargets.insert(spineIndex) - prefetchLock.unlock() - } - - /// 标记预取目标已完成 - func removePrefetchTarget(_ spineIndex: Int) { - prefetchLock.lock() - pendingPrefetchTargets.remove(spineIndex) - prefetchLock.unlock() - } - - /// 清空所有预取目标(切章时调用,旧预取结果不再需要) - func clearPrefetchTargets() { - prefetchLock.lock() - pendingPrefetchTargets.removeAll() - prefetchLock.unlock() - } - - /// 检查是否有待处理的预取目标 - func hasPrefetchTarget(_ spineIndex: Int) -> Bool { - prefetchLock.lock() - let has = pendingPrefetchTargets.contains(spineIndex) - prefetchLock.unlock() - return has - } - - func markBuilding(_ building: Bool) { - buildingLock.lock() - isBuilding = building - buildingLock.unlock() - } -} -``` - -#### 19.1.2 RDEPUBChapterDataCache / RDEPUBPageCountCache - -```swift -// ============ 新增文件:RDEPUBChapterDataCache.swift ============ - -final class RDEPUBChapterDataCache { - private var storage: [Int: RDEPUBRuntimeChapter] = [:] - private let lock = NSLock() - - subscript(spineIndex: Int) -> RDEPUBRuntimeChapter? { - get { - lock.lock() - defer { lock.unlock() } - return storage[spineIndex] - } - set { - lock.lock() - defer { lock.unlock() } - storage[spineIndex] = newValue - } - } - - var storedSpineIndices: [Int] { - lock.lock() - defer { lock.unlock() } - return Array(storage.keys) - } - - func remove(spineIndex: Int) { - lock.lock() - defer { lock.unlock() } - storage.removeValue(forKey: spineIndex) - } - - func removeAll() { - lock.lock() - defer { lock.unlock() } - storage.removeAll() - } -} - -// ============ 新增文件:RDEPUBPageCountCache.swift ============ - -final class RDEPUBPageCountCache { - private var storage: [RDEPUBChapterCacheKey: RDEPUBRuntimePageCount] = [:] - private let lock = NSLock() - - subscript(key: RDEPUBChapterCacheKey) -> RDEPUBRuntimePageCount? { - get { - lock.lock() - defer { lock.unlock() } - return storage[key] - } - set { - lock.lock() - defer { lock.unlock() } - storage[key] = newValue - } - } - - func entriesForSpineIndex(_ spineIndex: Int) -> [(RDEPUBChapterCacheKey, RDEPUBRuntimePageCount)] { - lock.lock() - defer { lock.unlock() } - return storage.filter { $0.value.spineIndex == spineIndex }.map { ($0.key, $0.value) } - } - - func remove(forSpineIndex spineIndex: Int) { - lock.lock() - defer { lock.unlock() } - storage = storage.filter { $0.value.spineIndex != spineIndex } - } - - func removeAll() { - lock.lock() - defer { lock.unlock() } - storage.removeAll() - } -} -``` - -#### 19.1.3 RDEPUBRuntimeChapter - -```swift -// ============ 新增文件:RDEPUBRuntimeChapter.swift ============ - -final class RDEPUBRuntimeChapter { - let spineIndex: Int - let href: String - let title: String - - /// 原始富文本(可按策略释放,不强制常驻) - var sourceAttributedString: NSAttributedString? - - /// 排版后富文本 - let typesetAttributedString: NSAttributedString - - /// 排版器 - let layouter: RDEPUBTextLayouter - - /// 页范围 - let pageRanges: [NSRange] - - /// 页面数组 - let pages: [RDEPUBTextPage] - - /// 章节偏移映射 - let chapterOffsetMap: RDEPUBChapterOffsetMap - - init( - spineIndex: Int, - href: String, - title: String, - sourceAttributedString: NSAttributedString?, - typesetAttributedString: NSAttributedString, - layouter: RDEPUBTextLayouter, - pageRanges: [NSRange], - pages: [RDEPUBTextPage], - chapterOffsetMap: RDEPUBChapterOffsetMap - ) { - self.spineIndex = spineIndex - self.href = href - self.title = title - self.sourceAttributedString = sourceAttributedString - self.typesetAttributedString = typesetAttributedString - self.layouter = layouter - self.pageRanges = pageRanges - self.pages = pages - self.chapterOffsetMap = chapterOffsetMap - } - - /// 释放 sourceAttributedString 以降低内存 - func releaseSourceText() { - sourceAttributedString = nil - } -} -``` - -#### 19.1.4 RDEPUBChapterOffsetMap / RDEPUBRuntimePageCount / RDEPUBChapterCacheKey / RDEPUBChapterLocation - -```swift -// ============ 新增文件:RDEPUBChapterOffsetMap.swift ============ - -struct RDEPUBChapterOffsetMap { - let fragmentOffsets: [String: Int] - let pageStartOffsets: [Int] - let pageEndOffsets: [Int] - - /// fragmentID -> 章内字符偏移 - func chapterOffset(forFragmentID fragmentID: String) -> Int? { - return fragmentOffsets[fragmentID] - } - - /// 章内字符偏移 -> 章内页码(从 0 开始) - func pageIndex(forChapterOffset offset: Int) -> Int? { - for i in 0..= pageStartOffsets[i] && offset <= pageEndOffsets[i] { - return i - } - } - return nil - } -} - -// ============ 新增文件:RDEPUBRuntimePageCount.swift ============ - -struct RDEPUBRuntimePageCount { - let cacheKey: RDEPUBChapterCacheKey - let spineIndex: Int - let pageRanges: [NSRange] - let pageCount: Int - let renderSignature: String -} - -// ============ 新增文件:RDEPUBChapterCacheKey.swift ============ - -struct RDEPUBChapterCacheKey: Hashable { - let bookID: String - let spineIndex: Int - let renderSignature: String - let chapterContentHash: String -} - -// ============ 新增文件:RDEPUBChapterLocation.swift ============ - -public struct RDEPUBChapterLocation: Codable { - public var spineIndex: Int - public var chapterOffset: Int - public var fragmentID: String? - public var progressionInChapter: Double? - public var schemaVersion: Int = 2 // v1 = 旧全局模型, v2 = 章节模型 -} -``` - -#### 19.1.5 RDEPUBChapterLoader - -```swift -// ============ 新增文件:RDEPUBChapterLoader.swift ============ - -final class RDEPUBChapterLoader { - private unowned let context: RDEPUBReaderContext - private var summaryDiskCache: RDEPUBChapterSummaryDiskCache? - - init(context: RDEPUBReaderContext) {} - - func setSummaryDiskCache(_ cache: RDEPUBChapterSummaryDiskCache) { - summaryDiskCache = cache - } - - // MARK: - 主入口:加载单个章节 - - /// 请求优先级 - enum LoadPriority { - case navigation // 前台导航:用户主动跳章,完成后检查导航目标队列 - case prefetch // 后台预取:±1 相邻章,完成后仅回填缓存 + 刷新快照 - } - - /// 在 chapterLoadQueue 上构建单章,完成后回调到主线程 - func loadChapter( - spineIndex: Int, - store: RDEPUBChapterRuntimeStore, - priority: LoadPriority = .navigation, - completion: @escaping (Result) -> Void - ) { - // 1. 查内存缓存(统一回主线程,保证 completion 线程语义一致) - if let cached = store.chapterData(for: spineIndex) { - DispatchQueue.main.async { - completion(.success(cached)) - } - return - } - - // 2. 构建缓存键 - let cacheKey = makeCacheKey(spineIndex: spineIndex) - - // 3. 查内存级 pageCountCache(轻量,无磁盘 I/O) - let precomputedPageRanges = store.pageCount(for: cacheKey)?.pageRanges - - // 4. 全部后续操作(含磁盘 I/O)统一放到串行队列 - // 避免磁盘读取阻塞主线程 - store.markBuilding(true) - store.chapterLoadQueue.async { - // 4a. 仅当内存级 pageCountCache 未命中时才查磁盘摘要 - // pageCountCache 命中 → 已有 pageRanges,不需要磁盘 I/O - // pageCountCache 未命中 → 查磁盘拿 pageRanges + metadata - let diskSummary: RDEPUBChapterSummary? - if precomputedPageRanges == nil { - diskSummary = self.summaryDiskCache?.read(for: cacheKey) - } else { - diskSummary = nil - } - let diskPageRanges = diskSummary?.pageRanges.map { $0.nsRange } - let availablePageRanges = precomputedPageRanges ?? diskPageRanges - - do { - let chapter = try self.buildChapter( - spineIndex: spineIndex, - availablePageRanges: availablePageRanges, - diskSummary: diskSummary - ) - - // 5. 回填缓存 - store.insertChapter(chapter) - let pc = RDEPUBRuntimePageCount( - cacheKey: cacheKey, - spineIndex: spineIndex, - pageRanges: chapter.pageRanges, - pageCount: chapter.pages.count, - renderSignature: cacheKey.renderSignature - ) - store.insertPageCount(pc, for: cacheKey) - - // 6. 按优先级处理完成逻辑 - switch priority { - case .navigation: - // 前台导航:检查是否有更新的导航目标(§14.4 取消语义) - let nextTarget = store.consumeNavigationTarget() - if let target = nextTarget, target != spineIndex { - // 当前结果不再是用户目标,丢弃,转而加载新目标 - store.markBuilding(false) - self.loadChapter(spineIndex: target, store: store, priority: .navigation, completion: completion) - return - } - store.markBuilding(false) - DispatchQueue.main.async { - completion(.success(chapter)) - } - - case .prefetch: - // 后台预取:仅回填缓存,标记预取目标完成 - // 不触发跳章,不检查导航目标队列 - store.removePrefetchTarget(spineIndex) - store.markBuilding(false) - DispatchQueue.main.async { - completion(.success(chapter)) - } - } - } catch { - store.markBuilding(false) - DispatchQueue.main.async { - completion(.failure(error)) - } - } - } - } - - /// 仅供 legacy 位置迁移使用的同步加载入口。 - /// - /// 约束: - /// - 必须复用同一个 `chapterLoadQueue`,保持 WXRead 的单章串行语义 - /// - 不允许恢复整书 `RDEPUBTextBook` - /// - 只允许从主线程或明确的非 `chapterLoadQueue` 上下文调用 - /// - 调用前必须执行 `store.assertNotOnChapterLoadQueue()`,避免 semaphore 等待自身队列造成死锁 - /// - 只在首次迁移且目标章未命中缓存时使用 - func loadChapterSynchronouslyForMigration( - spineIndex: Int, - store: RDEPUBChapterRuntimeStore? - ) throws -> RDEPUBRuntimeChapter { - if let cached = store?.chapterData(for: spineIndex) { - return cached - } - - guard let store else { - throw RDEPUBChapterLoadError.missingParser - } - - store.assertNotOnChapterLoadQueue() - - var result: Result? - let semaphore = DispatchSemaphore(value: 0) - store.chapterLoadQueue.async { - do { - let chapter = try self.buildChapter( - spineIndex: spineIndex, - availablePageRanges: nil, - diskSummary: nil - ) - store.insertChapter(chapter) - result = .success(chapter) - } catch { - result = .failure(error) - } - semaphore.signal() - } - semaphore.wait() - return try result!.get() - } - - // MARK: - 单章构建(支持轻量缓存命中后跳过分页) - - private func buildChapter( - spineIndex: Int, - availablePageRanges: [NSRange]?, - diskSummary: RDEPUBChapterSummary? = nil - ) throws -> RDEPUBRuntimeChapter { - guard let parser = context.parser, - let publication = context.publication else { - throw RDEPUBChapterLoadError.missingParser - } - - let pageSize = context.currentTextPageSize() - let style = context.currentTextRenderStyle() - let layoutConfig = context.currentTextLayoutConfig(pageSize: pageSize) - - if let pageRanges = availablePageRanges { - // ---- 轻量路径:pageCountCache 或 chapterSummaryDiskCache 命中 ---- - // 只需渲染 HTML → NSAttributedString,跳过完整分页计算 - return try buildChapterFromCachedPageRanges( - spineIndex: spineIndex, - pageRanges: pageRanges, - parser: parser, - publication: publication, - pageSize: pageSize, - style: style, - layoutConfig: layoutConfig, - diskSummary: diskSummary - ) - } - - // ---- 完整路径:无缓存,走全量渲染 + 分页 ---- - let builder = context.makeTextBookBuilder(layoutConfig: layoutConfig) - guard let result = try builder.buildChapter( - parser: parser, - publication: publication, - spineIndex: spineIndex, - pageSize: pageSize, - style: style - ) else { - throw RDEPUBChapterLoadError.emptyChapter(spineIndex: spineIndex) - } - - return try assembleRuntimeChapter( - from: result.chapter, - spineIndex: spineIndex, - pageSize: pageSize, - layoutConfig: layoutConfig - ) - } - - // MARK: - 轻量路径:复用已有 pageRanges,跳过完整分页 - - private func buildChapterFromCachedPageRanges( - spineIndex: Int, - pageRanges: [NSRange], - parser: RDEPUBParser, - publication: RDEPUBPublication, - pageSize: CGSize, - style: RDEPUBTextRenderStyle, - layoutConfig: RDEPUBTextLayoutConfig, - diskSummary: RDEPUBChapterSummary? = nil - ) throws -> RDEPUBRuntimeChapter { - let spineItem = publication.spine[spineIndex] - let href = spineItem.href - let title = spineItem.title ?? "" - let baseURL = parser.fileURL(forRelativePath: href)?.deletingLastPathComponent() - - // 1. 只做 HTML → NSAttributedString 渲染,不做分页 - let request = RDEPUBTextRendererSupport.makeChapterRenderRequest( - href: href, - title: title, - rawHTML: try requireHTMLString(parser, href: href), - baseURL: baseURL, - style: style, - resourceResolver: publication.resourceResolver, - pageSize: pageSize, - layoutConfig: layoutConfig - ) - let renderer = context.resolvedTextRenderer() - let rendered = try renderer.renderChapter(request: request) - - let typesetString = NSMutableAttributedString(attributedString: rendered.attributedString) - RDEPUBTextRendererSupport.normalizeReadingAttributes( - in: typesetString, style: style, layoutConfig: layoutConfig - ) - - // 2. metadata 来源策略: - // - diskSummary 非空(磁盘路径命中):从摘要恢复完整 metadata - // - diskSummary 为空(pageCountCache 命中但没走磁盘):从 attributedString 属性推断 - // 不再在轻量路径内做额外磁盘 I/O - let metadataSource = diskSummary?.pageMetadataList - - // 3. 直接用缓存的 pageRanges 构建 pages(跳过 CoreText 分页) - let pages = buildPagesFromRanges( - pageRanges: pageRanges, - typesetString: typesetString, - spineIndex: spineIndex, - href: href, - title: title, - metadataSource: metadataSource - ) - - // 3. 构建 layouter(用于后续可能的重新分页场景) - let layouter = RDEPUBTextLayouter( - attributedString: typesetString, - pageSize: pageSize, - config: layoutConfig - ) - - // 4. 构建 chapterOffsetMap - let offsetMap = RDEPUBChapterOffsetMap( - fragmentOffsets: rendered.fragmentOffsets, - pageStartOffsets: pages.map { $0.pageStartOffset }, - pageEndOffsets: pages.map { $0.pageEndOffset } - ) - - return RDEPUBRuntimeChapter( - spineIndex: spineIndex, - href: href, - title: title, - // 轻量路径特殊语义:sourceAttributedString 此时不是"HTML 原始文本", - // 而是"经过 normalizeReadingAttributes 归一化后的当前排版文本", - // 与 typesetAttributedString 相同。如果后续需要重新排版(如字号变化), - // 需要从 HTML 重新渲染,不能依赖此字段作为 source。 - sourceAttributedString: nil, // 轻量路径不保留原始 source,降低内存 - typesetAttributedString: typesetString, - layouter: layouter, - pageRanges: pageRanges, - pages: pages, - chapterOffsetMap: offsetMap - ) - } - - /// 从缓存的 pageRanges 直接构建 RDEPUBTextPage 数组 - /// metadataSource: 轻量摘要中的页元数据;为 nil 时从 attributedString 属性推断 - private func buildPagesFromRanges( - pageRanges: [NSRange], - typesetString: NSAttributedString, - spineIndex: Int, - href: String, - title: String, - metadataSource: [RDEPUBChapterSummary.PageMetadataSummary]? = nil - ) -> [RDEPUBTextPage] { - let totalPageCount = pageRanges.count - return pageRanges.enumerated().map { (pageIndex, range) in - let pageContent = typesetString.attributedSubstring(from: range) - let metadata: RDEPUBTextPageMetadata - if let metaList = metadataSource, pageIndex < metaList.count { - // 从摘要缓存恢复完整 metadata - metadata = metaList[pageIndex].toPageMetadata() - } else { - // 无缓存 metadata,从 attributedString 属性推断 - metadata = inferPageMetadata( - from: typesetString, - range: range, - isLastPage: pageIndex == totalPageCount - 1 - ) - } - return RDEPUBTextPage( - absolutePageIndex: -1, // 全书绝对页码:章节模式下不赋值,保持 -1;由上层按需回填 - chapterIndex: 0, // 所属章节在窗口 chapters 中的索引:在快照构建时重设 - spineIndex: spineIndex, - href: href, - chapterTitle: title, - pageIndexInChapter: pageIndex, - totalPagesInChapter: totalPageCount, - chapterContent: typesetString, - content: pageContent, - contentRange: range, - pageStartOffset: range.location, - pageEndOffset: range.location + range.length - 1, - metadata: metadata - ) - } - } - - /// 从 attributedString 的自定义属性推断页 metadata - /// 复用分页阶段注入的 rdPageBlockKind / rdPageAttachmentKind 等属性 - private func inferPageMetadata( - from string: NSAttributedString, - range: NSRange, - isLastPage: Bool - ) -> RDEPUBTextPageMetadata { - // 从自定义属性中提取分页语义信息 - var attachmentRanges: [NSRange] = [] - var attachmentKinds: [RDEPUBTextAttachmentKind] = [] - var blockKinds: [RDEPUBTextBlockKind] = [] - var semanticHints: [RDEPUBTextSemanticHint] = [] - var attachmentPlacements: [RDEPUBTextAttachmentPlacement] = [] - var trailingFragmentID: String? = nil - - string.enumerateAttribute(.rdPageAttachmentKind, in: range, options: []) { value, attrRange, _ in - if let rawValue = value as? String, - let kind = RDEPUBTextAttachmentKind(rawValue: rawValue) { - attachmentRanges.append(attrRange) - attachmentKinds.append(kind) - } - } - string.enumerateAttribute(.rdPageBlockKind, in: range, options: []) { value, _, _ in - if let rawValue = value as? String, - let kind = RDEPUBTextBlockKind(rawValue: rawValue), - !blockKinds.contains(kind) { - blockKinds.append(kind) - } - } - string.enumerateAttribute(.rdPageSemanticHints, in: range, options: []) { value, _, _ in - if let rawValue = value as? String { - let hints = rawValue - .split(separator: ",") - .compactMap { RDEPUBTextSemanticHint(rawValue: String($0)) } - for hint in hints where !semanticHints.contains(hint) { - semanticHints.append(hint) - } - } - } - string.enumerateAttribute(.rdPageAttachmentPlacement, in: range, options: []) { value, _, _ in - if let rawValue = value as? String, - let placement = RDEPUBTextAttachmentPlacement(rawValue: rawValue), - !attachmentPlacements.contains(placement) { - attachmentPlacements.append(placement) - } - } - string.enumerateAttribute(.rdPageFragmentID, in: range, options: [.reverse]) { value, _, stop in - if let fid = value as? String { - trailingFragmentID = fid - stop.pointee = true - } - } - - return RDEPUBTextPageMetadata( - breakReason: isLastPage ? .chapterEnd : .frameLimit, - blockRange: nil, - attachmentRanges: attachmentRanges, - attachmentKinds: attachmentKinds, - blockKinds: blockKinds, - semanticHints: semanticHints, - attachmentPlacements: attachmentPlacements, - trailingFragmentID: trailingFragmentID, - diagnostics: [] - ) - } - - /// 从完整构建结果组装 RDEPUBRuntimeChapter - private func assembleRuntimeChapter( - from chapter: RDEPUBTextChapter, - spineIndex: Int, - pageSize: CGSize, - layoutConfig: RDEPUBTextLayoutConfig - ) throws -> RDEPUBRuntimeChapter { - let layouter = RDEPUBTextLayouter( - attributedString: chapter.attributedContent, - pageSize: pageSize, - config: layoutConfig - ) - - let offsetMap = RDEPUBChapterOffsetMap( - fragmentOffsets: chapter.fragmentOffsets, - pageStartOffsets: chapter.pages.map { $0.pageStartOffset }, - pageEndOffsets: chapter.pages.map { $0.pageEndOffset } - ) - - let pageRanges = chapter.pages.map { $0.contentRange } - - // 回填磁盘摘要(P2 阶段生效) - let cacheKey = makeCacheKey(spineIndex: spineIndex) - let summary = RDEPUBChapterSummary( - pageRanges: pageRanges.map { .init(location: $0.location, length: $0.length) }, - pageCount: chapter.pages.count, - fragmentOffsets: chapter.fragmentOffsets, - renderSignature: cacheKey.renderSignature, - schemaVersion: 6, - chapterContentHash: cacheKey.chapterContentHash, - pageMetadataList: chapter.pages.map { .from($0.metadata) } - ) - summaryDiskCache?.write(summary: summary, for: cacheKey) - - return RDEPUBRuntimeChapter( - spineIndex: spineIndex, - href: chapter.href, - title: chapter.title, - sourceAttributedString: chapter.attributedContent, - typesetAttributedString: chapter.attributedContent, - layouter: layouter, - pageRanges: pageRanges, - pages: chapter.pages, - chapterOffsetMap: offsetMap - ) - } - - // MARK: - 缓存键 - - private func makeCacheKey(spineIndex: Int) -> RDEPUBChapterCacheKey { - let style = context.currentTextRenderStyle() - let layoutConfig = context.currentTextLayoutConfig(pageSize: context.currentTextPageSize()) - - // renderSignature 必须覆盖 §8.2 定义的全部参数 - // 任何排版参数变化都必须导致 key 不同,从而自然失效旧缓存 - // 注意:当前 RDEPUBTextRenderStyle 只有 lineSpacing,lineHeightMultiple 来自 reader configuration - // 不能写成 style.lineHeightMultiple,否则实现会引用不存在的字段 - let lineHeightMultiple = context.configuration.lineHeightMultiple - - let renderSignature = [ - style.font.fontName, // fontName - "\(style.font.pointSize)", // fontSize - "\(lineHeightMultiple)", // lineHeightMultiple - "\(style.lineSpacing)", // lineSpacing: 实际 CoreText 段落样式使用值 - layoutConfig.cacheSignature, // layoutConfigSignature - "\(6)" // schemaVersion - ].joined(separator: "|") - - let contentHash = contentHashForSpineIndex(spineIndex) - - return RDEPUBChapterCacheKey( - bookID: context.currentBookIdentifier ?? "", - spineIndex: spineIndex, - renderSignature: renderSignature, - chapterContentHash: contentHash - ) - } - - private func contentHashForSpineIndex(_ spineIndex: Int) -> String { - guard let parser = context.parser, - let publication = context.publication else { return "" } - let href = publication.spine[spineIndex].href - guard let html = parser.htmlString(forRelativePath: href) else { return "" } - return stableContentHash(html) - } - - private func requireHTMLString(_ parser: RDEPUBParser, href: String) throws -> String { - guard let html = parser.htmlString(forRelativePath: href) else { - throw RDEPUBChapterLoadError.emptyChapterHref(href) - } - return html - } - - /// 示例 helper:实现时可用 CryptoKit 或现有项目内哈希工具生成稳定字符串摘要。 - private func stableContentHash(_ html: String) -> String { - html - } -} - -enum RDEPUBChapterLoadError: Error { - case missingParser - case emptyChapter(spineIndex: Int) - case emptyChapterHref(String) -} -``` - -#### 19.1.6 RDEPUBChapterWindowSnapshot - -```swift -// ============ 新增文件:RDEPUBChapterWindowSnapshot.swift ============ - -struct RDEPUBChapterWindowSnapshot { - /// 窗口中的章节(有序:prev, current, next) - let chapters: [RDEPUBRuntimeChapter] - - /// 展平后的连续页数组(供 RDReaderView 消费) - /// 窗口内页码不写回 `RDEPUBTextPage` 模型; - /// `flattenedPages` 的数组下标就是窗口内连续页码(从 0 开始)。 - /// 这和 `RDEPUBTextPage.absolutePageIndex`(全书绝对页码)严格区分。 - let flattenedPages: [RDEPUBTextPage] - - /// 当前章在 chapters 数组中的索引 - let anchorChapterIndex: Int - - /// 当前章在 flattenedPages 中的起始页码(从 0 开始,窗口内编号) - let anchorPageOffset: Int - - /// 当前窗口首章的 spineIndex,用于调试日志和跨窗口映射 - let windowStartSpineIndex: Int - - // MARK: - 构建 - - /// 从章节窗口构建快照 - static func from( - currentChapter: RDEPUBRuntimeChapter, - previousChapter: RDEPUBRuntimeChapter?, - nextChapter: RDEPUBRuntimeChapter? - ) -> RDEPUBChapterWindowSnapshot { - var chapters: [RDEPUBRuntimeChapter] = [] - var anchorIndex = 0 - var pageOffset = 0 - - if let prev = previousChapter { - chapters.append(prev) - anchorIndex = 1 - pageOffset = prev.pages.count - } - - chapters.append(currentChapter) - - if let next = nextChapter { - chapters.append(next) - } - - // 展平页数组,编号规则: - // flattenedPages 下标 — 窗口内连续编号(从 0 开始),供 RDReaderView 消费 - // chapterIndex — 当前页所属章节在 chapters 数组中的索引 - // absolutePageIndex — 保持原值不动(全书绝对页码,章节模式下通常为 -1 或由上层按需赋值) - // 其中“窗口内页码”不新增到 RDEPUBTextPage 模型,避免污染现有文本页结构。 - var allPages: [RDEPUBTextPage] = [] - for (chIdx, ch) in chapters.enumerated() { - for var page in ch.pages { - page.chapterIndex = chIdx - // absolutePageIndex 不在这里赋值,保持章节构建时的原始值 - allPages.append(page) - } - } - - let windowStartSpineIndex = chapters.first?.spineIndex ?? currentChapter.spineIndex - - return RDEPUBChapterWindowSnapshot( - chapters: chapters, - flattenedPages: allPages, - anchorChapterIndex: anchorIndex, - anchorPageOffset: pageOffset, - windowStartSpineIndex: windowStartSpineIndex - ) - } - - /// 兼容边界: - /// - `flattenedPages` 下标 = 章节窗口路径中的唯一页码真值 - /// - `RDEPUBTextPage.absolutePageIndex` 在章节窗口路径中不参与定位 - /// - 旧的 `RDEPUBChapterData` / `RDEPUBTextIndexTable` / 搜索定位逻辑若仍依赖 absolutePageIndex, - /// 必须在 P3 迁移期间通过适配层改成消费 `flattenedPages` 下标或 `RDEPUBChapterLocation` - - // MARK: - 查询 - - /// 窗口内页码(即 flattenedPages 下标)-> 所属章节 - func chapterForPage(flattenedPageIndex: Int) -> RDEPUBRuntimeChapter? { - var offset = 0 - for ch in chapters { - if flattenedPageIndex < offset + ch.pages.count { - return ch - } - offset += ch.pages.count - } - return nil - } - - /// 窗口内页码(即 flattenedPages 下标)-> 所属章节的 spineIndex - func spineIndexForPage(flattenedPageIndex: Int) -> Int? { - return chapterForPage(flattenedPageIndex: flattenedPageIndex)?.spineIndex - } - - /// 总页数(窗口内) - var pageCount: Int { flattenedPages.count } -} -``` - -#### 19.1.7 RDEPUBChapterWindowCoordinator - -```swift -// ============ 新增文件:RDEPUBChapterWindowCoordinator.swift ============ - -final class RDEPUBChapterWindowCoordinator { - private unowned let context: RDEPUBReaderContext - private let store: RDEPUBChapterRuntimeStore - private let loader: RDEPUBChapterLoader - - /// 当前窗口快照 - private(set) var currentSnapshot: RDEPUBChapterWindowSnapshot? - - /// 窗口切换回调 - var onSnapshotChanged: ((RDEPUBChapterWindowSnapshot) -> Void)? - - init(context: RDEPUBReaderContext, store: RDEPUBChapterRuntimeStore, loader: RDEPUBChapterLoader) { - self.context = context - self.store = store - self.loader = loader - } - - // MARK: - 打开书籍 - - func openBook(at targetSpineIndex: Int) { - let totalSpineCount = context.publication?.spine.count ?? 0 - store.setCurrentChapter(spineIndex: targetSpineIndex, totalSpineCount: totalSpineCount) - - // 标记切章进行中(与 flipToChapter 一致,阻止预取回调在构建期间刷新快照) - isSwitchingChapter = true - - // 注册前台导航目标 - store.setNavigationTarget(spineIndex: targetSpineIndex) - // 清空旧预取目标(打开新书时旧预取不再需要) - store.clearPrefetchTargets() - - // 加载目标章(前台导航优先级) - loader.loadChapter(spineIndex: targetSpineIndex, store: store, priority: .navigation) { [weak self] result in - guard let self = self else { return } - self.isSwitchingChapter = false - switch result { - case .success(let chapter): - self.buildSnapshotAroundCurrent(chapter: chapter) - case .failure(let error): - self.context.handle(error: error) - } - } - } - - // MARK: - 构建窗口快照 - - private func buildSnapshotAroundCurrent(chapter: RDEPUBRuntimeChapter) { - guard let current = store.currentSpineIndex else { return } - let prev = current > 0 ? store.chapterData(for: current - 1) : nil - let next = store.chapterData(for: current + 1) - - let snapshot = RDEPUBChapterWindowSnapshot.from( - currentChapter: chapter, - previousChapter: prev, - nextChapter: next - ) - currentSnapshot = snapshot - isApplyingSnapshot = true - onSnapshotChanged?(snapshot) - isApplyingSnapshot = false - - // 预取 ±1 - prefetchAdjacent(current: current) - } - - // MARK: - 预取(后台优先级,不抢占前台导航通道) - - private func prefetchAdjacent(current: Int) { - let totalSpineCount = context.publication?.spine.count ?? 0 - - // 预取 prev(后台优先级) - if current > 0 && store.chapterData(for: current - 1) == nil { - let prevIndex = current - 1 - store.addPrefetchTarget(prevIndex) - loader.loadChapter(spineIndex: prevIndex, store: store, priority: .prefetch) { [weak self] result in - guard let self = self, case .success = result else { return } - // 预取成功后刷新窗口快照,使相邻章纳入 RDReaderView 消费范围 - // 仅在阅读器空闲时刷新,不触发跳章 - self.refreshSnapshot() - } - } - - // 预取 next(后台优先级) - if current < totalSpineCount - 1 && store.chapterData(for: current + 1) == nil { - let nextIndex = current + 1 - store.addPrefetchTarget(nextIndex) - loader.loadChapter(spineIndex: nextIndex, store: store, priority: .prefetch) { [weak self] result in - guard let self = self, case .success = result else { return } - self.refreshSnapshot() - } - } - } - - // MARK: - 翻章 - - /// 到达章末,翻到下一章 - func flipToNextChapter(completion: @escaping (Result) -> Void) { - guard let current = store.currentSpineIndex else { return } - let next = current + 1 - let totalSpineCount = context.publication?.spine.count ?? 0 - guard next < totalSpineCount else { return } - - flipToChapter(spineIndex: next, completion: completion) - } - - /// 到达章首,翻到上一章 - func flipToPreviousChapter(completion: @escaping (Result) -> Void) { - guard let current = store.currentSpineIndex, current > 0 else { return } - flipToChapter(spineIndex: current - 1, completion: completion) - } - - /// 跳转到指定章节(目录/书签/搜索) - func flipToChapter( - spineIndex: Int, - completion: @escaping (Result) -> Void - ) { - let totalSpineCount = context.publication?.spine.count ?? 0 - - // 注册前台导航目标(§14.4 取消语义:排队请求只保留最后一次) - store.setNavigationTarget(spineIndex: spineIndex) - // 清空后台预取目标(切章时旧预取结果不再需要) - store.clearPrefetchTargets() - // 标记切章进行中(阻止预取回调在切章期间刷新快照) - isSwitchingChapter = true - - // 先淘汰旧窗口外章节 - store.setCurrentChapter(spineIndex: spineIndex, totalSpineCount: totalSpineCount) - let evictable = store.evictableSpineIndices() - for idx in evictable { - store.evict(spineIndex: idx) - } - - // 如果目标章已在缓存中,直接构建快照 - if let cached = store.chapterData(for: spineIndex) { - buildSnapshotAroundCurrent(chapter: cached) - isSwitchingChapter = false - if let snap = currentSnapshot { - completion(.success(snap)) - } - return - } - - // 未命中缓存,走加载链路(前台导航优先级) - loader.loadChapter(spineIndex: spineIndex, store: store, priority: .navigation) { [weak self] result in - guard let self = self else { return } - self.isSwitchingChapter = false - switch result { - case .success(let chapter): - self.buildSnapshotAroundCurrent(chapter: chapter) - if let snap = self.currentSnapshot { - completion(.success(snap)) - } - case .failure(let error): - completion(.failure(error)) - } - } - } - - // MARK: - 刷新快照(预取完成后调用,把新的相邻章纳入快照) - - /// 预取成功后刷新窗口快照 - /// 硬性约束:仅在阅读器完全空闲时才允许刷新快照 - /// 空闲定义 = 非构建中 + 非切章中 + 协调器内部未处于“正在应用新快照”状态 - /// 不满足条件时延后重试,绝不打断当前阅读状态 - func refreshSnapshot() { - guard let current = store.currentSpineIndex, - let currentChapter = store.chapterData(for: current) else { return } - - // 空闲门槛检查 - guard isReaderIdle() else { - // 推迟到空闲后再刷新 - DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { [weak self] in - self?.refreshSnapshot() - } - return - } - - let prev = current > 0 ? store.chapterData(for: current - 1) : nil - let next = store.chapterData(for: current + 1) - - // 只在快照内容确实变化时才更新和通知 - let newSnapshot = RDEPUBChapterWindowSnapshot.from( - currentChapter: currentChapter, - previousChapter: prev, - nextChapter: next - ) - - if snapshotContentChanged(old: currentSnapshot, new: newSnapshot) { - currentSnapshot = newSnapshot - isApplyingSnapshot = true - onSnapshotChanged?(newSnapshot) - isApplyingSnapshot = false - } - } - - /// 判断新快照是否与旧快照内容不同(避免无变化时触发不必要的 UI 刷新) - /// 判定条件(满足任一即认为变化): - /// 1. 章节数量不同 - /// 2. 章节 spineIndex 序列不同(相邻章换了) - /// 3. 总页数不同 - /// 4. 任一章的页数不同(排版参数变化导致页重排) - /// 5. 锚点章在窗口中的位置不同 - /// 仅比较"章节数量 + 总页数"不够:相邻章换了但页数巧合相同时会漏检, - /// 导致窗口边界内容陈旧。 - private func snapshotContentChanged( - old: RDEPUBChapterWindowSnapshot?, - new: RDEPUBChapterWindowSnapshot - ) -> Bool { - guard let old = old else { return true } - - // 1. 章节数量不同 - if old.chapters.count != new.chapters.count { return true } - - // 2. spineIndex 序列不同(相邻章换了) - let oldSpines = old.chapters.map { $0.spineIndex } - let newSpines = new.chapters.map { $0.spineIndex } - if oldSpines != newSpines { return true } - - // 3. 总页数不同 - if old.pageCount != new.pageCount { return true } - - // 4. 任一章的页数不同 - for (oldCh, newCh) in zip(old.chapters, new.chapters) { - if oldCh.pages.count != newCh.pages.count { return true } - } - - // 5. 锚点位置不同 - if old.anchorChapterIndex != new.anchorChapterIndex - || old.anchorPageOffset != new.anchorPageOffset { return true } - - return false - } - - /// 空闲判断:必须全部满足才允许刷新快照 - private func isReaderIdle() -> Bool { - // 1. 没有章节正在后台构建 - guard !store.isBuilding else { return false } - // 2. 没有切章操作正在进行 - guard !isSwitchingChapter else { return false } - // 3. 当前没有正在应用新快照/跳转目标页 - guard !isApplyingSnapshot else { return false } - return true - } - - /// 切章进行中标记(flipToChapter 开始设 true,completion 回调后设 false) - private var isSwitchingChapter: Bool = false - /// 应用快照进行中标记(避免预取回调和主动切章同时刷新 UI) - private var isApplyingSnapshot: Bool = false -} -``` - -#### 19.1.8 PageProvider 适配(窗口快照直连) - -> **作用域**:以下分页提供者改造仅适用于 `textReflowable` 路径。`RDEPUBReaderController` 需在初始化时根据书籍类型选择分页策略:textReflowable 走窗口快照路径并设置 `readerView.pageProvider`,其他类型(fixed-layout、PDF 等)维持原有整书 `pages` 数组/DataSource 路径。 - -```swift -// ============ 修改文件:RDEPUBReaderController+DataSource.swift ============ - -// 重要:此扩展仅在 textReflowable 路径启用,并通过 RDReaderPageProvider 接入。 -// RDEPUBReaderController 初始化时需判断书籍类型: -// if publication.metadata.layout == .reflowable { -// readerView.pageProvider = self // 走本扩展 -// } else { -// readerView.pageProvider = nil -// readerView.dataSource = self // 走原有整书 pages 路径 -// } - -extension RDEPUBReaderController: RDReaderPageProvider, RDReaderDelegate { - - public func numberOfPages(in readerView: RDReaderView) -> Int { - guard let snapshot = windowCoordinator?.currentSnapshot else { return 0 } - return snapshot.pageCount - } - - public func readerView(_ readerView: RDReaderView, viewForPageAt pageNum: Int, reusableView: UIView?) -> UIView { - guard let snapshot = windowCoordinator?.currentSnapshot else { - return reusableView ?? RDEPUBTextContentView() - } - return textContentViewFromSnapshot(snapshot, pageNum: pageNum, reusableView: reusableView) - } - - public func pageIdentifier(in readerView: RDReaderView, index: Int) -> String? { - NSStringFromClass(RDEPUBTextContentView.self) - } - - public func readerViewTopChrome(_ readerView: RDReaderView) -> UIView? { - topToolView - } - - public func readerViewBottomChrome(_ readerView: RDReaderView) -> UIView? { - bottomToolView - } - - // MARK: - 页面内容构建 / 视图复用 - - private func textContentViewFromSnapshot( - _ snapshot: RDEPUBChapterWindowSnapshot, - pageNum: Int, - reusableView: UIView? - ) -> UIView { - guard pageNum >= 0, pageNum < snapshot.flattenedPages.count else { - return reusableView ?? RDEPUBTextContentView() - } - let page = snapshot.flattenedPages[pageNum] - let contentView = (reusableView as? RDEPUBTextContentView) ?? RDEPUBTextContentView() - contentView.delegate = self - contentView.configure( - page: page, - pageNumber: pageNum + 1, - totalPages: snapshot.pageCount, - configuration: configuration, - highlights: highlightsForPage(page), - searchState: searchState - ) - return contentView - } - - // MARK: - 翻章检测 - - public func pageNum(readerView: RDReaderView, pageNum: Int) { - handleChapterAwarePageChange(pageNum: pageNum) - } - - /// 检测是否到达章节边界,触发翻章 - private func handleChapterAwarePageChange(pageNum: Int) { - guard let snapshot = windowCoordinator?.currentSnapshot else { return } - - // 到达窗口末尾 → 翻到下一章 - if pageNum >= snapshot.pageCount - 1 { - windowCoordinator?.flipToNextChapter { [weak self] result in - guard let self = self else { return } - switch result { - case .success(let newSnapshot): - self.applyNewSnapshot(newSnapshot, landing: .chapterStart) - case .failure: - break // 停在当前页 - } - } - return - } - - // 到达窗口开头 → 翻到上一章 - if pageNum <= 0 { - windowCoordinator?.flipToPreviousChapter { [weak self] result in - guard let self = self else { return } - switch result { - case .success(let newSnapshot): - self.applyNewSnapshot(newSnapshot, landing: .chapterEnd) - case .failure: - break - } - } - return - } - - // 普通页移动:当前位置已稳定,可直接持久化 - persistCurrentChapterLocation(pageNum: pageNum) - } - - /// 应用新窗口快照 - private enum ChapterLanding { - case chapterStart - case chapterEnd - } - - private func applyNewSnapshot(_ snapshot: RDEPUBChapterWindowSnapshot, landing: ChapterLanding) { - // 通知 RDReaderView 刷新数据源 - readerView?.reloadData() - - // 跳到新章的正确位置 - let targetPage: Int - switch landing { - case .chapterStart: - targetPage = snapshot.anchorPageOffset - case .chapterEnd: - let currentChapter = snapshot.chapters[snapshot.anchorChapterIndex] - targetPage = snapshot.anchorPageOffset + currentChapter.pages.count - 1 - } - - readerView?.transitionToPage(pageNum: targetPage, animated: false) - - // 位置持久化必须发生在新快照和新页码确定之后,避免把旧边界页写回去 - persistCurrentChapterLocation(pageNum: targetPage) - } -} -``` - -#### 19.1.9 Context 和 Runtime 挂入 - -```swift -// ============ 修改文件:RDEPUBReaderContext.swift ============ - -final class RDEPUBReaderContext { - // ... 保留所有现有属性 ... - - // MARK: - 新增章节运行时 - - /// 章节运行时缓存 - var chapterRuntimeStore: RDEPUBChapterRuntimeStore? - - /// 章节加载器 - var chapterLoader: RDEPUBChapterLoader? - - /// 窗口协调器 - var chapterWindowCoordinator: RDEPUBChapterWindowCoordinator? -} - -// ============ 修改文件:RDEPUBReaderRuntime.swift ============ - -final class RDEPUBReaderRuntime { - // ... 保留所有现有子协调器 ... - - // 新增:章节运行时初始化 - func setupChapterRuntimeIfNeeded() { - guard context.chapterRuntimeStore == nil else { return } - - let store = RDEPUBChapterRuntimeStore() - let loader = RDEPUBChapterLoader(context: context) - let coordinator = RDEPUBChapterWindowCoordinator( - context: context, - store: store, - loader: loader - ) - - context.chapterRuntimeStore = store - context.chapterLoader = loader - context.chapterWindowCoordinator = coordinator - - // 监听内存警告 - NotificationCenter.default.addObserver( - forName: UIApplication.didReceiveMemoryWarningNotification, - object: nil, queue: .main - ) { [weak store] _ in - store?.handleMemoryWarning() - } - } - - // 初始化章节运行时基础设施,然后委托给 PaginationCoordinator - // PaginationCoordinator.paginatePublication() 是唯一启动入口 - func loadPublication() { - setupChapterRuntimeIfNeeded() - // 启动入口统一由 paginationCoordinator 承担 - paginationCoordinator.paginatePublication(restoreLocation: /* 从持久化取 */) - } -} -``` - -说明: - -- **唯一启动点**是 `RDEPUBReaderPaginationCoordinator.paginatePublication()` -- `loadPublication()` 只负责初始化基础设施,然后委托给 `paginationCoordinator` -- `paginatePublication()` 内部调用 `context.chapterWindowCoordinator?.openBook(at:)`,是章节路径的唯一入口 -- P4 阶段对 PaginationCoordinator 的改造只是删掉旧整书路径代码,不改变启动入口(委托结构改造已在 P0 完成,见文件修改清单) - ---- - -### 19.2 P1:建立完整章节缓存运行时 - -#### 19.2.1 ±1 预取窗口完善 - -```swift -// ============ 扩展:RDEPUBChapterWindowCoordinator.swift ============ - -extension RDEPUBChapterWindowCoordinator { - - /// 翻章后维护窗口:当前章常驻,预取新的相邻章 - private func maintainWindow(afterMovingTo spineIndex: Int) { - let totalSpineCount = context.publication?.spine.count ?? 0 - - // 淘汰窗口外章节 - store.setCurrentChapter(spineIndex: spineIndex, totalSpineCount: totalSpineCount) - for idx in store.evictableSpineIndices() { - store.evict(spineIndex: idx) - } - - // 预取 prev(后台优先级,不抢占前台导航通道) - if spineIndex > 0 && store.chapterData(for: spineIndex - 1) == nil { - let prevIndex = spineIndex - 1 - store.addPrefetchTarget(prevIndex) - loader.loadChapter(spineIndex: prevIndex, store: store, priority: .prefetch) { [weak self] result in - guard let self = self, case .success = result else { return } - self.refreshSnapshot() - } - } - - // 预取 next(后台优先级,不抢占前台导航通道) - if spineIndex < totalSpineCount - 1 && store.chapterData(for: spineIndex + 1) == nil { - let nextIndex = spineIndex + 1 - store.addPrefetchTarget(nextIndex) - loader.loadChapter(spineIndex: nextIndex, store: store, priority: .prefetch) { [weak self] result in - guard let self = self, case .success = result else { return } - self.refreshSnapshot() - } - } - } -} -``` - -#### 19.2.2 排版参数变化后整体失效 - -```swift -// ============ 扩展:RDEPUBChapterRuntimeStore.swift ============ - -extension RDEPUBChapterRuntimeStore { - - /// 排版参数变化后整体失效(§8.5) - func invalidateAllForSettingsChange() { - chapterDataCache.removeAll() - pageCountCache.removeAll() - imageCache.removeAllObjects() - } -} -``` - -#### 19.2.3 CoreText / CF 释放审计 - -需要在以下类型中确认 CF 对象的成对释放: - -- `RDEPUBTextLayouter` → 检查内部 `RDEPUBChapterPageCounter` 是否持有 `CTTypesetter` -- `RDEPUBCoreTextPageFrameFactory` → 检查是否持有 `CTFramesetter` / `CTFrame` / `CGPath` -- `RDEPUBDTCoreTextRenderer` → 检查 `DTCoreTextLayoutFrame` 的生命周期 - -规则: - -- 谁创建,谁释放 -- `deinit` / `dealloc` 中成对处理 -- 章节淘汰时 `RDEPUBRuntimeChapter` 释放会级联释放 `layouter`,需确认其 `deinit` 链完整 - ---- - -### 19.3 P2:接入二次打开加速层 - -#### 19.3.1 RDEPUBChapterSummaryDiskCache - -```swift -// ============ 新增文件:RDEPUBChapterSummaryDiskCache.swift ============ - -final class RDEPUBChapterSummaryDiskCache { - private let cacheDirectory: URL - private let fileManager = FileManager.default - private let queue = DispatchQueue(label: "com.rdreader.summarydiskcache", qos: .utility) - - init(cacheDirectory: URL) { - self.cacheDirectory = cacheDirectory - try? fileManager.createDirectory(at: cacheDirectory, withIntermediateDirectories: true) - } - - // MARK: - 写入(异步) - - func write(summary: RDEPUBChapterSummary, for key: RDEPUBChapterCacheKey) { - queue.async { - let fileURL = self.fileURL(for: key) - let data = try? JSONEncoder().encode(summary) - try? data?.write(to: fileURL) - } - } - - // MARK: - 读取(同步,因为 loadChapter 已在串行队列上) - - func read(for key: RDEPUBChapterCacheKey) -> RDEPUBChapterSummary? { - let fileURL = self.fileURL(for: key) - guard let data = try? Data(contentsOf: fileURL) else { return nil } - return try? JSONDecoder().decode(RDEPUBChapterSummary.self, from: data) - } - - // MARK: - key -> 文件路径 - - /// 使用确定性字符串拼接生成文件名,不依赖 Hashable.hashValue - /// hashValue 跨进程不稳定,会导致二次打开缓存失效 - private func fileURL(for key: RDEPUBChapterCacheKey) -> URL { - // 用 SHA256 对完整 key 内容取摘要,保证跨进程稳定且无文件名冲突 - let rawKey = "\(key.bookID)_\(key.spineIndex)_\(key.renderSignature)_\(key.chapterContentHash)" - let digest = rawKey.sha256Hex - return cacheDirectory.appendingPathComponent("\(digest).json") - } -} - -struct RDEPUBChapterSummary: Codable { - let pageRanges: [RangeData] // NSRange 不 Codable,需包装 - let pageCount: Int - let fragmentOffsets: [String: Int] - let renderSignature: String - let schemaVersion: Int - let chapterContentHash: String - /// 每页的元数据摘要,轻量路径恢复时使用 - let pageMetadataList: [PageMetadataSummary] - - struct RangeData: Codable { - let location: Int - let length: Int - var nsRange: NSRange { NSRange(location: location, length: length) } - } - - /// 轻量化的每页 metadata(只保留跨章功能依赖的字段) - struct PageMetadataSummary: Codable { - let breakReason: String // RDEPUBTextPageBreakReason.rawValue - let attachmentRanges: [RangeData] - let attachmentKinds: [String] // RDEPUBTextAttachmentKind.rawValue - let blockKinds: [String] // RDEPUBTextBlockKind.rawValue - let semanticHints: [String] // RDEPUBTextSemanticHint.rawValue - let attachmentPlacements: [String] // RDEPUBTextAttachmentPlacement.rawValue - let trailingFragmentID: String? - - func toPageMetadata() -> RDEPUBTextPageMetadata { - RDEPUBTextPageMetadata( - breakReason: RDEPUBTextPageBreakReason(rawValue: breakReason) ?? .frameLimit, - blockRange: nil, - attachmentRanges: attachmentRanges.map { $0.nsRange }, - attachmentKinds: attachmentKinds.compactMap { RDEPUBTextAttachmentKind(rawValue: $0) }, - blockKinds: blockKinds.compactMap { RDEPUBTextBlockKind(rawValue: $0) }, - semanticHints: semanticHints.compactMap { RDEPUBTextSemanticHint(rawValue: $0) }, - attachmentPlacements: attachmentPlacements.compactMap { RDEPUBTextAttachmentPlacement(rawValue: $0) }, - trailingFragmentID: trailingFragmentID, - diagnostics: [] - ) - } - - static func from(_ metadata: RDEPUBTextPageMetadata) -> PageMetadataSummary { - PageMetadataSummary( - breakReason: metadata.breakReason.rawValue, - attachmentRanges: metadata.attachmentRanges.map { .init(location: $0.location, length: $0.length) }, - attachmentKinds: metadata.attachmentKinds.map { $0.rawValue }, - blockKinds: metadata.blockKinds.map { $0.rawValue }, - semanticHints: metadata.semanticHints.map { $0.rawValue }, - attachmentPlacements: metadata.attachmentPlacements.map { $0.rawValue }, - trailingFragmentID: metadata.trailingFragmentID - ) - } - } -} -``` - -#### 19.3.2 集成到 RDEPUBChapterLoader - -已在 §19.1.5 的 `buildChapter` 中预留了 `summaryDiskCache?.write(summary:for:)` 调用和 `chapterSummaryDiskCachePageRanges(for:)` 查询。 - -P2 阶段只需: - -1. 在 `RDEPUBReaderRuntime.setupChapterRuntimeIfNeeded()` 中创建 `RDEPUBChapterSummaryDiskCache` 并注入 loader -2. 确认 `buildChapter` 中已回填磁盘摘要 - ---- - -### 19.4 P3:位置与跨章能力迁移 - -#### 19.4.1 Legacy 位置转换器 - -```swift -// ============ 新增文件:RDEPUBLocationConverter.swift ============ - -struct RDEPUBLocationConverter { - - // MARK: - 主路径:先构建目标章,拿到真实长度后再精确转换 - - /// 旧版 RDEPUBLocation -> 新版 RDEPUBChapterLocation - /// 主迁移路径:要求先构建目标章,用真实 chapterLength 做精确转换 - /// 仅在无法获取章节长度时才降级到粗估 fallback - static func convert( - legacy location: RDEPUBLocation, - parser: RDEPUBParser, - publication: RDEPUBPublication, - chapterLengthProvider: ((Int) -> Int?)? = nil - ) -> RDEPUBChapterLocation? { - // 1. 从 href 找到 spineIndex - guard let spineItem = publication.spine.first(where: { - $0.href == location.href || $0.href.contains(location.href) - }) else { return nil } - - let spineIndex = publication.spine.firstIndex(of: spineItem) ?? 0 - - // 2. 优先用 fragmentID 定位(最精确,不受 progression 精度影响) - if let fragmentID = location.fragment { - return RDEPUBChapterLocation( - spineIndex: spineIndex, - chapterOffset: 0, // fragmentID 由 chapterOffsetMap 精确解析 - fragmentID: fragmentID, - progressionInChapter: location.progression - ) - } - - // 3. 有 chapterLength 时做精确转换 - if let provider = chapterLengthProvider, - let chapterLength = provider(spineIndex), chapterLength > 0 { - return convert( - legacy: location, - spineIndex: spineIndex, - chapterLength: chapterLength - ) - } - - // 4. Fallback:无法获取章节长度时的粗估(仅作临时降级,不应作为主路径) - // 粗估对大章/短章/带 fragment 场景误差明显 - // 正式迁移应在上层先构建目标章再调用精确版本 - let estimatedOffset = Int(location.progression * 10000) - return RDEPUBChapterLocation( - spineIndex: spineIndex, - chapterOffset: estimatedOffset, - fragmentID: nil, - progressionInChapter: location.progression, - schemaVersion: 1 // 标记为降级结果,后续可被精确值覆盖 - ) - } - - // MARK: - 粗估降级验收标准 - - /// schemaVersion == 1 的降级结果必须满足以下验收标准: - /// - /// 1. **允许误差范围** - /// - 粗估 chapterOffset 与真实 chapterOffset 的偏差不超过章节总长度的 ±15% - /// - 偏差超过 15% 时,用户可见的位置偏移会被感知(例如跳到错误段落) - /// - 验收测试:对比 schemaVersion == 1 与 schemaVersion == 2 的 chapterOffset, - /// 偏差 = |estimated - exact| / chapterLength,必须 < 0.15 - /// - /// 2. **必须回填精确值的场景** - /// - 用户下次打开同一章节时,必须走主路径(先构建目标章)获取精确值 - /// - 精确值获取成功后,立即覆盖 schemaVersion == 1 的降级结果 - /// - 不允许降级结果永久驻留在持久化层 - /// - /// 3. **禁止降级的场景** - /// - 带 fragmentID 的位置:fragmentID 是精确锚点,粗估会完全丢失语义 - /// (已在 step 2 优先处理 fragmentID,此处不再重复) - /// - 首次打开时的"当前阅读位置":这是用户最关心的位置,必须精确 - /// (调用方必须保证主路径先构建目标章,见 §19.4.2 调用示例) - /// - /// 4. **监控与告警** - /// - 生产环境应统计 schemaVersion == 1 的出现频率 - /// - 如果降级率 > 5%,说明主路径保证未落地,需要排查调用链路 - /// - 降级结果应在日志中标记,便于问题定位 - /// - /// 5. **验收测试用例** - /// ```swift - /// // 测试:粗估偏差必须在 ±15% 以内 - /// func testFallbackOffsetAccuracy() { - /// let legacy = RDEPUBLocation(href: "chapter10.xhtml", progression: 0.5, fragment: nil) - /// let fallback = RDEPUBLocationConverter.convert(legacy: legacy, spineIndex: 10, chapterLength: nil) - /// let exact = RDEPUBLocationConverter.convert(legacy: legacy, spineIndex: 10, chapterLength: 50000) - /// - /// let deviation = abs(fallback.chapterOffset - exact.chapterOffset) / Double(exact.chapterLength) - /// XCTAssertLessThan(deviation, 0.15, "粗估偏差超过 15%,用户可感知位置偏移") - /// } - /// - /// // 测试:schemaVersion == 1 必须被精确值覆盖 - /// func testFallbackOverwrittenByExact() { - /// let fallback = RDEPUBChapterLocation(spineIndex: 10, chapterOffset: 5000, schemaVersion: 1) - /// persistence.saveChapterLocation(fallback, for: "book123") - /// - /// // 模拟下次打开时走主路径 - /// let exact = RDEPUBChapterLocation(spineIndex: 10, chapterOffset: 25000, schemaVersion: 2) - /// persistence.saveChapterLocation(exact, for: "book123") - /// - /// let loaded = persistence.loadChapterLocation(for: "book123") - /// XCTAssertEqual(loaded.schemaVersion, 2, "降级结果未被精确值覆盖") - /// XCTAssertEqual(loaded.chapterOffset, 25000) - /// } - /// ``` - - /// 精确转换:已知章节实际长度 - static func convert( - legacy location: RDEPUBLocation, - spineIndex: Int, - chapterLength: Int - ) -> RDEPUBChapterLocation? { - let offset = Int(location.progression * Double(chapterLength)) - return RDEPUBChapterLocation( - spineIndex: spineIndex, - chapterOffset: offset, - fragmentID: location.fragment, - progressionInChapter: location.progression, - schemaVersion: 2 // 精确结果 - ) - } - - /// 从已构建的 RDEPUBRuntimeChapter 做精确转换(推荐迁移路径) - static func convert( - legacy location: RDEPUBLocation, - chapter: RDEPUBRuntimeChapter - ) -> RDEPUBChapterLocation? { - // 优先用 fragmentID - if let fragmentID = location.fragment, - let fragmentOffset = chapter.chapterOffsetMap.chapterOffset(forFragmentID: fragmentID) { - return RDEPUBChapterLocation( - spineIndex: chapter.spineIndex, - chapterOffset: fragmentOffset, - fragmentID: fragmentID, - progressionInChapter: nil, - schemaVersion: 2 - ) - } - - // 用 progression + 真实长度 - let chapterLength = chapter.typesetAttributedString.length - return convert( - legacy: location, - spineIndex: chapter.spineIndex, - chapterLength: chapterLength - ) - } - - /// 新版 -> 旧版(兼容外部接口) - static func toLegacy( - chapterLocation: RDEPUBChapterLocation, - href: String, - chapterLength: Int - ) -> RDEPUBLocation { - let progression = chapterLength > 0 - ? Double(chapterLocation.chapterOffset) / Double(chapterLength) - : 0 - return RDEPUBLocation( - href: href, - progression: min(max(progression, 0), 1), - fragment: chapterLocation.fragmentID - ) - } -} -``` - -#### 19.4.2 持久化迁移 - -```swift -// ============ 修改:RDEPUBUserDefaultsPersistence ============ - -extension RDEPUBUserDefaultsPersistence { - - /// 加载章节级位置,同时完成历史格式的一次性迁移 - func loadChapterLocation( - for bookIdentifier: String, - legacyMigrator: ((RDEPUBLocation) -> RDEPUBChapterLocation?)? = nil - ) -> RDEPUBChapterLocation? { - let key = locationPrefix + bookIdentifier - - // 1. 尝试直接读取新格式 - if let data = defaults.data(forKey: key), - let chapterLoc = try? JSONDecoder().decode(RDEPUBChapterLocation.self, from: data) { - // 新格式已存在,直接返回 - return chapterLoc - } - - // 2. 新格式不存在,尝试读取旧格式并迁移 - guard let migrator = legacyMigrator else { return nil } - - if let legacyData = defaults.data(forKey: key), - let legacyLoc = try? JSONDecoder().decode(RDEPUBLocation.self, from: legacyData), - let migrated = migrator(legacyLoc) { - // 迁移成功,立即覆盖为新格式 - saveChapterLocation(migrated, for: bookIdentifier) - return migrated - } - - return nil - } - - func saveChapterLocation(_ location: RDEPUBChapterLocation, for bookIdentifier: String) { - let key = locationPrefix + bookIdentifier - if let data = try? JSONEncoder().encode(location) { - defaults.set(data, forKey: key) - } - } -} -``` - -调用方在打开书时传入 migrator 闭包(主路径保证:先构建目标章,再用真实长度精确转换): - -```swift -// 典型调用点:RDEPUBReaderLocationCoordinator 或 RDEPUBReaderRuntime -// -// 主路径保证: -// migrator 闭包内先尝试从缓存取真实章节长度;如果目标章尚未加载(首次打开), -// 则同步调用 chapterLoader 构建目标章再取长度。 -// 只有构建失败时才降级到 fallback(progression * 10000 粗估)。 -let chapterLoc = persistence.loadChapterLocation(for: bookID) { legacyLoc in - // 1. 从 href 解析 spineIndex(与 converter 内部逻辑一致) - guard let spineItem = publication.spine.first(where: { - $0.href == legacyLoc.href || $0.href.contains(legacyLoc.href) - }), let spineIndex = publication.spine.firstIndex(of: spineItem) else { - return RDEPUBLocationConverter.convert( - legacy: legacyLoc, parser: parser, publication: publication - ) - } - - // 2. 优先从已加载的章节缓存取真实长度(二次打开,章节已在缓存) - let chapterLength: Int? - if let cached = context.chapterRuntimeStore?.chapterData(for: spineIndex) { - chapterLength = cached.typesetAttributedString.length - } else { - // 3. 缓存未命中(首次打开)→ 主路径:先构建目标章,再用真实长度 - // 这是保证"主路径一定先拿到真实章节长度"的关键步骤 - // 具体实现必须通过章节 loader 的串行构建接口完成,不允许直接恢复整书 TextBook - let runtimeChapter = try? context.chapterLoader?.loadChapterSynchronouslyForMigration( - spineIndex: spineIndex, - store: context.chapterRuntimeStore - ) - // 构建成功后放入缓存,后续章节加载可复用 - if let chapter = runtimeChapter { - context.chapterRuntimeStore?.insertChapter(chapter) - } - chapterLength = runtimeChapter?.typesetAttributedString.length - } - - // 4. 用精确路径或降级 fallback - return RDEPUBLocationConverter.convert( - legacy: legacyLoc, - parser: parser, - publication: publication, - chapterLengthProvider: { _ in chapterLength } - ) -} -``` - -说明: - -- 主持久化格式只保留 `RDEPUBChapterLocation` -- 历史格式在首次加载时自动迁移并覆盖,不需要单独的迁移脚本 -- 本方案不维护"运行期双格式并存"或"新旧链路双写" -- **主路径保证**:`migrator` 闭包内显式处理了"缓存未命中时先构建目标章"的逻辑,确保 `chapterLengthProvider` 在首次打开时也能返回真实章节长度,而不是直接掉到 `progression * 10000` 粗估 fallback -- 构建目标章的开销在首次迁移时只发生一次(迁移后立即覆盖为新格式,后续打开走新格式直接读取) - -#### 19.4.3 跨章功能适配 - -搜索适配: - -```swift -// ============ 修改:RDEPUBReaderSearchCoordinator.swift ============ - -extension RDEPUBReaderSearchCoordinator { - - /// 章节模式下搜索:逐章搜索,结果携带章节语义定位 - func searchInChapterMode(keyword: String) { - guard let publication = context.publication else { return } - - var allMatches: [RDEPUBSearchMatch] = [] - - for (spineIndex, item) in publication.spine.enumerated() { - guard - let parser = context.parser, - let html = try? parser.htmlString(forRelativePath: item.href) - else { continue } - - // 搜索必须在渲染后纯文本空间进行,不能直接在原始 HTML 上匹配。 - // 原因:正文链路的 chapterOffset 基于 typesetAttributedString(经 HTML → 渲染 → 去标签), - // chapterOffsetMap 的偏移也在同一空间。如果直接在原始 HTML 上搜索, - // matchRange.location 是含标签/实体的 HTML 偏移,与 chapterOffset 语义不一致, - // 会导致搜索结果点击后恢复位置、高亮范围、预览命中全部偏移。 - let plainText = stripHTMLTags(html) - - // 在渲染后纯文本中查找所有匹配位置 - let matches = findKeywordRanges(in: plainText, keyword: keyword) - for (localIndex, matchRange) in matches.enumerated() { - // matchRange.location 现在是纯文本偏移,与 chapterOffsetMap 语义一致 - let chapterOffset = matchRange.location - let rangeAnchor = RDEPUBTextRangeAnchor( - start: RDEPUBTextAnchor( - fileIndex: spineIndex, - row: 0, - column: 0, - chapterOffset: chapterOffset, - fragmentID: nil - ), - end: RDEPUBTextAnchor( - fileIndex: spineIndex, - row: 0, - column: 0, - chapterOffset: chapterOffset + matchRange.length, - fragmentID: nil - ) - ) - - let chapterLength = plainText.count - let progression = chapterLength > 0 - ? Double(chapterOffset) / Double(chapterLength) : 0 - - // 截取预览文本(基于纯文本偏移,与正文一致) - let previewStart = max(0, chapterOffset - 20) - let previewEnd = min(plainText.count, chapterOffset + keyword.count + 20) - let previewText = String(plainText[plainText.index(plainText.startIndex, offsetBy: previewStart).. String { - // 去除 ", - with: "", options: .regularExpression) - .replacingOccurrences(of: "]*>[\\s\\S]*?", - with: "", options: .regularExpression) - // 去除所有标签 - let noTags = strippedBlocks - .replacingOccurrences(of: "<[^>]+>", with: "", options: .regularExpression) - // 解码常见 HTML 实体 - return noTags - .replacingOccurrences(of: "&", with: "&") - .replacingOccurrences(of: "<", with: "<") - .replacingOccurrences(of: ">", with: ">") - .replacingOccurrences(of: " ", with: " ") - .replacingOccurrences(of: """, with: "\"") - } - - /// 在纯文本(非原始 HTML)中查找关键词的所有 NSRange。 - /// 调用方必须先对 HTML 做 stripHTMLTags 再传入,确保返回的偏移与 - /// chapterOffsetMap / typesetAttributedString 的纯文本偏移语义一致。 - private func findKeywordRanges(in text: String, keyword: String) -> [NSRange] { - var ranges: [NSRange] = [] - let nsText = text as NSString - var searchRange = NSRange(location: 0, length: nsText.length) - while searchRange.location + searchRange.length <= nsText.length { - let found = nsText.range(of: keyword, options: [.caseInsensitive], range: searchRange) - if found.location == NSNotFound { break } - ranges.append(found) - searchRange.location = found.location + found.length - searchRange.length = nsText.length - searchRange.location - } - return ranges - } -} -``` - -搜索结果定位到具体章节的方式: - -- 点击搜索结果 → 取出 `rangeAnchor.start.spineIndex` → 调用 `flipToChapter(spineIndex:)` → 章节加载完成后用 `chapterOffsetMap` 或 `rangeAnchor.start.chapterOffset` 恢复精确位置 -- 不再依赖全书绝对页码 -- **轻量去标签 vs 完整渲染对齐**:`stripHTMLTags` 是轻量级标签剥离,不走完整 HTML → `NSAttributedString` 渲染管线,与 `typesetAttributedString` 之间可能存在白空格归一化等微小差异。搜索场景可以接受此精度(搜索结果是入口锚点,不是精确排版锚点);如果需要严格对齐,可以改为在搜索结果点击后、章节加载完成时,用 `chapterOffsetMap` 对 `chapterOffset` 做一次精化映射 - -书签/高亮适配要点: - -- 高亮和书签的 `location` 字段已经是 `RDEPUBLocation`,包含 `href + progression + fragment + rangeAnchor` -- `rangeAnchor` 已经是 `spineIndex + chapterOffset` 语义(`RDEPUBTextAnchor`) -- 核心适配点: - 1. 写入时确保 `rangeAnchor` 被正确填充 - 2. 读取时从 `rangeAnchor` 恢复,而不是从全局页码 - ---- - -### 19.5 P4:清理旧整书主路径 - -#### 19.5.1 RDEPUBReaderPaginationCoordinator 改造 - -`RDEPUBReaderPaginationCoordinator` 是章节路径的**唯一启动入口**。 - -> **阶段说明**:委托结构改造(`paginatePublication()` 委托 `ChapterWindowCoordinator.openBook(at:)`)在 **P0** 阶段完成,与 `loadPublication()` 的委托调用同步落地。P4 阶段在此基础上**仅删除旧整书分页路径代码**(`paginateTextPublication`、`buildQuickTextBook`、`IncrementalChapterStore`、`StagedBookRequest`、`stageBookRequest`、`scheduleStagedIncrementalTextBookApplication` 等),不改变启动入口。 - -```swift -// ============ 修改:RDEPUBReaderPaginationCoordinator.swift ============ - -final class RDEPUBReaderPaginationCoordinator { - private unowned let context: RDEPUBReaderContext - - init(context: RDEPUBReaderContext) - - /// 唯一启动入口:解析恢复位置,委托给 ChapterWindowCoordinator - func paginatePublication(restoreLocation: RDEPUBLocation?) { - let targetSpineIndex = resolveTargetSpineIndex(from: restoreLocation) - context.chapterWindowCoordinator?.openBook(at: targetSpineIndex) - } - - private func resolveTargetSpineIndex(from location: RDEPUBLocation?) -> Int { - guard let location = location, - let publication = context.publication else { return 0 } - - let href = location.href - if let idx = publication.spine.firstIndex(where: { $0.href == href }) { - return idx - } - return 0 - } -} -``` - -#### 19.5.2 RDEPUBTextBookCache 降级 - -```swift -// ============ 修改:RDEPUBReaderContext.swift ============ - -extension RDEPUBReaderContext { - - func makeTextBookBuilder(layoutConfig: RDEPUBTextLayoutConfig) -> RDEPUBTextBookBuilder { - return RDEPUBTextBookBuilder( - renderer: resolvedTextRenderer(), - cache: nil, - layoutConfig: layoutConfig - ) - } -} -``` - ---- - -### 19.6 文件新增与修改清单 - -#### 新增文件 - -| 文件 | 阶段 | 职责 | -|------|------|------| -| `RDEPUBChapterRuntimeStore.swift` | P0 | 章节缓存中心 | -| `RDEPUBChapterDataCache.swift` | P0 | 类型安全章节缓存 wrapper | -| `RDEPUBPageCountCache.swift` | P0 | 类型安全页数缓存 wrapper | -| `RDEPUBRuntimeChapter.swift` | P0 | 章节运行时对象 | -| `RDEPUBChapterOffsetMap.swift` | P0 | 章内偏移映射 | -| `RDEPUBRuntimePageCount.swift` | P0 | 轻量分页结构 | -| `RDEPUBChapterCacheKey.swift` | P0 | 缓存键 | -| `RDEPUBChapterLocation.swift` | P0 | 章节级位置模型 | -| `RDEPUBChapterLoader.swift` | P0 | 单章构建器 | -| `RDEPUBChapterWindowSnapshot.swift` | P0 | 窗口快照 | -| `RDEPUBChapterWindowCoordinator.swift` | P0 | 窗口协调器 | -| `RDEPUBChapterSummaryDiskCache.swift` | P2 | 轻量摘要磁盘缓存 | -| `RDEPUBLocationConverter.swift` | P3 | 新旧位置格式转换 | - -#### 修改文件 - -| 文件 | 阶段 | 改动 | -|------|------|------| -| `RDEPUBReaderContext.swift` | P0 | 挂入 store / loader / coordinator | -| `RDEPUBReaderRuntime.swift` | P0 | setupChapterRuntime + 单一路径接入 | -| `RDEPUBReaderController+DataSource.swift` | P0 | 窗口快照数据源 | -| `RDEPUBReaderPaginationCoordinator.swift` | P0 | 委托结构改造:`paginatePublication()` 改为委托 `ChapterWindowCoordinator.openBook(at:)`,成为唯一启动入口 | -| `RDEPUBReaderPaginationCoordinator.swift` | P4 | 清理旧整书分页代码(P0 已完成委托结构,P4 只删除不再使用的旧路径) | diff --git a/Doc/大书内存优化方案_元数据缓存与按需加载.md b/Doc/大书内存优化方案_元数据缓存与按需加载.md new file mode 100644 index 0000000..7608077 --- /dev/null +++ b/Doc/大书内存优化方案_元数据缓存与按需加载.md @@ -0,0 +1,1258 @@ +# EPUB 大书内存优化方案:首开分页落盘,二开恢复分页,运行时仅保留少量章节 + +> 适用场景:`textReflowable` 路径打开超大正文 EPUB,典型样本为《凡人修仙传》精校版全本(~1000 章节)。 +> 目标:在保留全书完整页码(总页数、绝对页号、进度条、目录跳页)的前提下,将内存占用从“全书内容常驻”降至“仅当前少量章节内容常驻”。 +> 主线:**首开:分页并落盘;二开:直接恢复分页结果,不再重新分页;运行时:只加载当前少量章节内容。** + +--- + +## 1. 结论先行 + +当前大书内存问题的根因,不是“必须维护完整页码”,而是当前实现把两件事绑死了: + +1. `RDEPUBTextBook.pages.count` 负责全书总页数 +2. `RDEPUBTextBook.pages` 同时又挂着整本书的内容页对象 + +最终效果是: +- 打开大书时先构建整本 `RDEPUBTextBook` +- 快速进入后后台继续补齐整本书 +- 完整 `NSAttributedString` 和 `RDEPUBTextPage` 链长期常驻 + +本方案的核心调整是把“页码索引”和“内容缓存”拆开: + +- **全书页码索引常驻** + - 保存总页数 + - 保存 `absolutePageIndex <-> spineIndex/localPageIndex` + - 保存每章 `pageRanges`、`fragmentOffsets` + +- **章节内容按需加载** + - 只保留当前章 ±1 章 + - 其余章节只保留轻量分页结果 + - 内存警告时仅保留当前章 + +这样可以同时满足: +- 完整页码不丢 +- 二次打开不再重新分页 +- 运行时内存不随全书章节数线性增长 + +--- + +## 2. 当前问题 + +### 2.1 当前打开路径 + +当前 `textReflowable` 路径大致是: + +```text +RDEPUBReaderPaginationCoordinator.paginateTextPublication() + -> RDEPUBTextBookBuilder.build() + -> 遍历全部 spine + -> 每章 render + paginate + -> 汇总成完整 RDEPUBTextBook + -> context.textBook = textBook +``` + +即便已有“快速进入 + 后台补齐”,后台仍然会继续累积整本书。 + +### 2.2 当前主要内存来源 + +以当前模型看,主要内存压力来自: + +| 模型 | 重字段 | 问题 | +|------|--------|------| +| `RDEPUBTextChapter` | `attributedContent` | 每章完整富文本常驻 | +| `RDEPUBTextPage` | `chapterContent` | 每页再次强持有整章内容 | +| `RDEPUBTextPage` | `content` | 每页片段富文本也会累计 | +| `RDEPUBTextBook` | `pages` / `chapters` | 一旦全书完成,整本内容无法下降到“阅读位置附近” | + +### 2.3 当前已有可复用基础 + +当前项目并不是从零开始,已经有三类非常关键的基础设施: + +1. **分页缓存** + - `RDEPUBTextBookCache` + - `RDEPUBPaginationCacheCoordinator` + - 已可缓存每章 `pageRanges` + +2. **章节摘要磁盘缓存** + - `RDEPUBChapterSummaryDiskCache` + - 已可保存 `pageRanges`、`pageCount`、`fragmentOffsets`、`pageMetadataList` + +3. **章节运行时加载基础设施** + - `RDEPUBChapterRuntimeStore` + - `RDEPUBChapterLoader` + - `RDEPUBChapterWindowCoordinator` + +所以本方案不是“推倒重来”,而是把这些能力串成一条主路径。 + +--- + +## 3. 目标方案总览 + +### 3.1 一句话流程 + +```text +首开: + 当前章先分页、显示、并把分页结果落盘 + 后台继续为其余章节生成分页结果并落盘 + +二开: + 直接恢复分页结果,不再重新分页 + 只重建当前目标章节的内容对象 + +运行时: + RDReaderView 仍使用全书总页数 + 页面内容按绝对页号映射到当前章节内容 + 内存中只保留当前章 ±1 章 +``` + +### 3.2 三层缓存结构 + +本方案明确区分三层缓存。 + +#### A. 磁盘分页缓存 + +职责: +- 保存每章分页结果 +- 支撑二次打开“跳过重新分页” + +内容: +- `pageRanges` +- `pageCount` +- `fragmentOffsets` +- 轻量 `pageMetadata` +- `renderSignature` +- `schemaVersion` +- `chapterContentHash` + +对应当前类: +- `RDEPUBTextBookCache` +- `RDEPUBChapterSummaryDiskCache` + +#### B. 内存轻量索引 + +职责: +- 提供完整页码能力 +- 不持有整章文本 + +内容: +- `totalPages` +- `absolutePageIndex -> (spineIndex, localPageIndex)` +- `(spineIndex, localPageIndex) -> absolutePageIndex` +- 每章 `pageRanges` +- 每章 `fragmentOffsets` + +新增类: +- `RDEPUBBookPageMap` + +#### C. 内存内容缓存 + +职责: +- 支持当前阅读页渲染 +- 支持选区、高亮、搜索跳转后的实际页面显示 + +内容: +- `RDEPUBRuntimeChapter` +- `typesetAttributedString` +- `RDEPUBRuntimePage` +- `layouter` + +对应当前类: +- `RDEPUBChapterRuntimeStore` +- `RDEPUBChapterLoader` + +--- + +## 4. 首开 / 二开 / 运行时详细流程 + +## 4.1 首开流程 + +### 目标 + +- 尽快看到正文 +- 当前章节分页结果立即落盘 +- 后台异步补齐全书分页结果 +- 不再构建完整 `RDEPUBTextBook` + +### 详细步骤 + +#### Step 1:恢复阅读位置 + +入口: +- `RDEPUBReaderLoadCoordinator.applyParsedPublication()` +- `RDEPUBReaderLocationCoordinator.persistenceLocation()` + +恢复结果至少确定: +- `targetSpineIndex` +- `targetChapterOffset` 或 `targetRangeAnchor` + +如果本地已有 `bookPageMap`,还可以进一步计算: +- `targetAbsolutePageIndex` + +#### Step 2:快速构建目标章节 + +入口: +- `RDEPUBReaderPaginationCoordinator.paginateTextPublication()` + +行为: +- 不调用 `builder.build()` 全书构建 +- 只调用 `builder.buildChapter(...)` 或 `chapterLoader.loadChapter(...)` +- 构建目标章节 +- 如有需要,顺手构建前后相邻章 + +输出: +- `RDEPUBRuntimeChapter` +- 当前章 `pageRanges` +- 当前章 `fragmentOffsets` + +#### Step 3:当前章节分页结果立刻落盘 + +当前项目已经有两个方向的缓存设施,建议统一如下: + +1. `RDEPUBTextBookCache` + - 继续作为每章 `pageRanges` 的主缓存 + - 负责高效读写分页范围 + +2. `RDEPUBChapterSummaryDiskCache` + - 保存更完整的章节摘要 + - 包含: + - `pageRanges` + - `pageCount` + - `fragmentOffsets` + - `pageMetadataList` + - `chapterContentHash` + - `renderSignature` + +首开时,当前章节一旦分页完成,必须立刻写入这两层缓存中的统一主来源。 + +#### Step 4:立刻显示目标页 + +当前页展示不依赖全书完成,只依赖: +- 当前章节 `RDEPUBRuntimeChapter` +- 当前章节分页结果 + +页面展示路径改为: + +```text +RDEPUBReaderController+DataSource.pageContentView(pageNum:) + -> pageResolver.resolvePage(absolutePageIndex:) + -> 命中当前运行时章节 + -> 交给 RDEPUBTextContentView +``` + +#### Step 5:后台补齐全书分页结果 + +后台任务: +- 顺序遍历剩余可渲染章节 +- 对每章执行: + 1. 构建单章分页 + 2. 提取 `pageRanges/pageCount/fragmentOffsets/pageMetadata` + 3. 写入磁盘缓存 + 4. 立即释放章节内容 + +重要约束: +- 后台任务优先级低于用户交互 +- 用户跳章/搜索跳转时,后台任务应暂停或让路 +- 后台不要累积 `RDEPUBTextChapter` +- 后台不要 merge 成完整 `RDEPUBTextBook` + +### 首开最终状态 + +首开完成后,系统应处于: +- 当前章内容在内存 +- 相邻章可选预取在内存 +- 已完成章节的分页结果在磁盘 +- 全书 `BookPageMap` 可逐步构建或在后台最终生成 + +--- + +## 4.2 二开流程 + +### 目标 + +- 不再重新分页 +- 直接恢复全书页码索引 +- 只重建当前阅读点附近的章节内容 + +### 详细步骤 + +#### Step 1:恢复分页索引 + +入口: +- `RDEPUBReaderLoadCoordinator.applyParsedPublication()` + +行为: +- 判断磁盘分页缓存是否完整 +- 若完整: + - 直接从 `RDEPUBChapterSummaryDiskCache.readAll(...)` + - 构建 `RDEPUBBookPageMap` + - **不再重新分页** + +“完整”的定义建议为: +- 当前书的所有可渲染 spine 均存在缓存摘要 +- `schemaVersion` 匹配 +- `renderSignature` 匹配 +- `chapterContentHash` 匹配 + +#### Step 2:恢复目标绝对页 + +恢复位置后: +- 从 `RDEPUBLocation` 算出目标章节和页内偏移 +- 或从 `BookPageMap` 直接恢复绝对页号 + +#### Step 3:只加载目标章节内容 + +二开时即使已恢复了全书分页结果,也不需要整本内容。 + +只做: +- `chapterLoader.loadChapter(targetSpineIndex, ...)` +- 若缓存命中分页结果: + - 重新渲染当前章 HTML + - 按已缓存的 `pageRanges` 直接切页 + - 跳过完整分页流程 + +#### Step 4:显示目标页 + +此时: +- `numberOfPages()` 直接返回 `bookPageMap.totalPages` +- `pageContentView(pageNum:)` 通过 `pageResolver` 映射到目标章节页 + +### 二开最终状态 + +二开完成后: +- 全书页码立即可用 +- 当前章节内容已加载 +- 不需要整本重新分页 + +--- + +## 4.3 运行时流程 + +### 目标 + +- 全书页码不变 +- 内容只围绕当前阅读位置保留 +- 翻页时尽量无感 + +### 运行时规则 + +#### 规则 1:RDReaderView 始终使用全书总页数 + +```text +pageCountOfReaderView = bookPageMap.totalPages +``` + +不采用“窗口页数模式”。 + +#### 规则 2:页面内容按绝对页号解析 + +新增: +- `RDEPUBPageResolver` + +职责: +- 输入:`absolutePageIndex` +- 输出:目标页的 `RDEPUBResolvedPage` + +解析步骤: +1. `bookPageMap.pageRef(forAbsolutePage:)` +2. 得到 `spineIndex + localPageIndex` +3. 查看 `chapterRuntimeStore` 是否已有该章 +4. 无则加载该章 +5. 返回运行时页和渲染上下文 + +#### 规则 3:运行时只保留当前章 ±1 章 + +继续利用: +- `RDEPUBChapterRuntimeStore` +- `RDEPUBChapterWindowCoordinator` + +策略: +- 当前章常驻 +- 前后章预取 +- 窗口外章节淘汰 + +#### 规则 4:内存警告时只保留当前章 + +行为: +- 保留当前章 +- 清空其余章节缓存 +- 清空图片缓存 +- 视情况清空内存级分页缓存 +- 保留 `BookPageMap` + +--- + +## 5. 数据模型设计 + +## 5.1 全书轻量索引:`RDEPUBBookPageMap` + +建议新增文件: +- `Sources/RDReaderView/EPUBUI/ReaderController/ChapterRuntime/RDEPUBBookPageMap.swift` + +```swift +struct RDEPUBAbsolutePageRef { + let absolutePageIndex: Int + let spineIndex: Int + let localPageIndex: Int + let href: String +} + +struct RDEPUBLightPageMetadata { + let breakReason: RDEPUBTextPageBreakReason + let trailingFragmentID: String? + let attachmentKinds: [RDEPUBTextAttachmentKind] + let attachmentPlacements: [RDEPUBTextAttachmentPlacement] +} + +struct RDEPUBBookPageMapEntry { + let spineIndex: Int + let href: String + let title: String + let absolutePageStart: Int + let pageCount: Int + let fragmentOffsets: [String: Int] + let pageRanges: [NSRange] + let pageMetadata: [RDEPUBLightPageMetadata] +} + +struct RDEPUBBookPageMap { + let entries: [RDEPUBBookPageMapEntry] + let totalPages: Int + + func absolutePageIndex(spineIndex: Int, localPageIndex: Int) -> Int? + func pageRef(forAbsolutePage absolutePage: Int) -> RDEPUBAbsolutePageRef? + func chapter(forAbsolutePage absolutePage: Int) -> RDEPUBBookPageMapEntry? +} +``` + +职责: +- 完整页码 +- 绝对页到章节页的映射 +- 目录跳页 +- 搜索跳页 +- 位置恢复 + +## 5.2 运行时内容:`RDEPUBRuntimeChapter` + +当前已有: +- `RDEPUBRuntimeChapter` + +建议保留章节级重对象,不再让 page 自己强持有整章文本多份。 + +```swift +final class RDEPUBRuntimeChapter { + let spineIndex: Int + let href: String + let title: String + + let typesetAttributedString: NSAttributedString + let layouter: RDEPUBTextLayouter + let pages: [RDEPUBRuntimePage] + let chapterOffsetMap: RDEPUBChapterOffsetMap +} +``` + +## 5.3 运行时页:`RDEPUBRuntimePage` + +建议让运行时页只保存渲染必需数据: + +```swift +struct RDEPUBRuntimePage { + let spineIndex: Int + let href: String + let localPageIndex: Int + let contentRange: NSRange + let pageStartOffset: Int + let content: NSAttributedString + let metadata: RDEPUBTextPageMetadata +} +``` + +不建议继续常驻在 page 上的字段: +- `chapterContent` +- `pageEndOffset` +- `absolutePageIndex` +- `chapterIndex` +- `chapterTitle` +- `totalPagesInChapter` + +这些都应外置到渲染上下文或章节对象。 + +## 5.4 渲染上下文:`RDEPUBResolvedPage` + +新增: +- `RDEPUBResolvedPage` +- `RDEPUBPageRenderContext` + +```swift +struct RDEPUBPageRenderContext { + let absolutePageIndex: Int + let totalPages: Int + let chapterTitle: String + let totalPagesInChapter: Int + let chapterContentProvider: () -> NSAttributedString +} + +struct RDEPUBResolvedPage { + let page: RDEPUBRuntimePage + let context: RDEPUBPageRenderContext +} +``` + +这样: +- 页模型变轻 +- UI 仍可显示完整页码 +- 选区/高亮/续段判断仍能拿到整章文本 + +--- + +## 6. 与当前项目类的映射 + +## 6.1 `RDEPUBReaderContext.swift` + +新增属性: + +```swift +var bookPageMap: RDEPUBBookPageMap? +var chapterRuntimeStore: RDEPUBChapterRuntimeStore? +var chapterLoader: RDEPUBChapterLoader? +var summaryDiskCache: RDEPUBChapterSummaryDiskCache? +var pageResolver: RDEPUBPageResolver? +``` + +作用: +- 让 `textBook` 不再是 EPUB 大书主路径的单一事实来源 + +## 6.2 `RDEPUBReaderRuntime.swift` + +新增懒属性: +- `chapterRuntimeStore` +- `chapterLoader` +- `summaryDiskCache` +- `pageResolver` + +新增方法: +- `switchToOnDemandPageMode()` + +职责: +- 把运行时组件串起来 +- 在加载完成后切换到“绝对页号 + 按章内容”模式 + +## 6.3 `RDEPUBReaderPaginationCoordinator.swift` + +这是主改造点。 + +### 当前问题 +- 快速进入后后台继续补齐整本 `RDEPUBTextBook` + +### 目标改造 + +重写 `paginateTextPublication()`: + +#### 路径 A:分页缓存完整(二开) +- 读取 `BookPageMap` +- 加载目标章节 +- 切到按需内容模式 + +#### 路径 B:分页缓存不完整(首开) +- 快速构建目标章节 +- 立刻显示 +- 后台执行 `paginateMetadataOnly()` +- 逐章落盘,不再 merge 整本 `RDEPUBTextBook` + +新增方法建议: +- `paginateMetadataOnly(...)` +- `restoreBookPageMapIfPossible(...)` +- `buildInitialRuntimeChapters(...)` + +## 6.4 `RDEPUBChapterSummaryDiskCache.swift` + +当前已具备单章摘要缓存。 + +需要增强: +- `readAll(bookID:renderSignature:)` +- `containsCompleteSet(...)` +- `removeAll(bookID:renderSignature:)` + +新增建议: +- 为全书维护一个 manifest,记录: + - 缓存完成的 spineIndex 列表 + - `renderSignature` + - `schemaVersion` + - `bookID` + +这样二开时才能可靠判断“是否可以完全跳过重新分页”。 + +## 6.5 `RDEPUBTextBookCache.swift` + +当前已经能缓存章节分页范围。 + +建议职责收敛为: +- 高效保存 / 加载章节 `pageRanges` +- 作为 `ChapterSummaryDiskCache` 的底层快路径或兼容层 + +需要明确: +- 它缓存的是分页结果 +- 不是整章富文本缓存 + +## 6.6 `RDEPUBChapterLoader.swift` + +当前已经有关键能力: +- 缓存命中时跳过完整分页 +- 轻量路径使用已有 `pageRanges` + +这正是二开所需主路径。 + +建议增强: +- 统一从 `summaryDiskCache` 获取章节摘要 +- 加载章节时优先走: + 1. 读摘要 + 2. 渲染 HTML + 3. 按缓存 `pageRanges` 构建 `RDEPUBRuntimePage` +- 仅缓存未命中时才真正重新分页 + +## 6.7 `RDEPUBReaderController+DataSource.swift` + +这是 UI 接口层的主改造点。 + +### 改造前 + +```swift +pageCountOfReaderView -> textBook?.pages.count +pageContentView -> textBook.page(at:) +``` + +### 改造后 + +```swift +pageCountOfReaderView -> context.bookPageMap?.totalPages ?? 0 +pageContentView -> context.pageResolver?.resolvePage(absolutePageIndex: pageNum) +``` + +要求: +- RDReaderView 继续使用全书总页数 +- `pageNum` 始终是绝对页号 +- 页面内容按需解析 + +## 6.8 `RDEPUBTextContentView.swift` + +第一阶段不要推翻整套渲染链,只需改输入模型。 + +建议: +- 当前仍可兼容旧 `RDEPUBTextPage` +- 逐步切为接收 `RDEPUBResolvedPage` + +需要调整的点: +- 页码标签使用 `absolutePageIndex / totalPages` +- 选区、高亮、续段判断改为使用 `chapterContentProvider` +- 不再依赖 `page.chapterContent` 常驻 + +--- + +## 7. 按文件的具体改动清单 + +这一节不再讲“方向”,而是按当前项目的真实文件结构,给出**先删什么、加什么、保留什么**。 + +## 7.1 `RDEPUBReaderPaginationCoordinator.swift` + +这是第一优先级改造文件。 + +### 先删什么 + +- 删除或停用以下“后台补齐整本书”的结构: + - `QuickBuildState` + - `StagedBookRequest` + - `IncrementalChapterStore` + - `stagedIncrementalApplyWorkItem` + - `stageBookRequest(...)` + - `consumeStagedBookRequest()` + - `scheduleStagedIncrementalTextBookApplication()` + - `applyStagedIncrementalTextBookIfPossible()` + - `textBook(from:orderedBy:)` +- 删除“每 20 章 merge 成整本 `RDEPUBTextBook`”的后台路径: + - `incrementalMergeChapterThreshold` + - `pendingIncrementalCount` 相关逻辑 +- 删除“快速进入后仍然 apply 整本 TextBook”的后续逻辑 + +### 加什么 + +- 新增 `restoreBookPageMapIfPossible(...)` + - 二开时优先恢复 `BookPageMap` + - 命中则直接走缓存分页路径 +- 新增 `paginateMetadataOnly(...)` + - 首开后台只生成章节摘要并落盘 + - 不创建整本 `RDEPUBTextBook` +- 新增 `buildInitialRuntimeChapters(...)` + - 只构建目标章节和前后章节 +- 新增“前台优先”调度逻辑: + - 用户跳页 / 搜索跳转时,后台摘要任务暂停或让路 + +### 保留什么 + +- `paginatePublication(restoreLocation:)` + - 仍负责按阅读类型分发 +- `applyPaginationSnapshot(...)` + - Fixed Layout / Web 内容仍可继续使用 +- `finishPagination(...)` + - 作为 reloadData + restore 的统一收尾逻辑继续保留 +- `rebuildExternalTextBook()` + - 外部 TXT 路径不动 +- `waitForReadingInteractionToSettle()` + - 可继续作为后台任务让路机制 + +### 改造后的职责 + +改造后它只负责三件事: + +1. 首开时快速构建目标章节 +2. 后台补齐分页摘要并落盘 +3. 二开时恢复分页索引并切换到按需内容模式 + +## 7.2 `RDEPUBReaderContext.swift` + +### 先删什么 + +- 不删现有字段,但要降低 `textBook` 的中心地位 +- 不再把 `textBook` 当成 EPUB 大书路径的唯一事实来源 + +### 加什么 + +- 新增状态: + - `var bookPageMap: RDEPUBBookPageMap?` + - `var chapterRuntimeStore: RDEPUBChapterRuntimeStore?` + - `var chapterLoader: RDEPUBChapterLoader?` + - `var summaryDiskCache: RDEPUBChapterSummaryDiskCache?` + - `var pageResolver: RDEPUBPageResolver?` +- 新增工厂方法(可选): + - `makeChapterSummaryDiskCache()` + - `makePageResolver()` + +### 保留什么 + +- `textBookCache` + - 仍作为分页缓存入口保留 +- `makeTextBookBuilder(...)` + - 仍用于单章构建 +- `currentTextPageSize()` + - `chapterLoader` 仍需依赖 +- `currentTextRenderStyle()` + - `chapterLoader` 仍需依赖 +- `currentTextLayoutConfig(pageSize:)` + - `chapterLoader` 仍需依赖 + +### 改造后的职责 + +- 上下文中同时持有: + - “全书轻量索引” + - “当前少量章节内容缓存” +- `textBook` 仅作为: + - 外部文本书籍路径 + - 或迁移期间兼容桥接 + +## 7.3 `RDEPUBReaderRuntime.swift` + +### 先删什么 + +- 不需要立即删除现有 coordinator +- 但 `go(toPageNumber:)` 中对 `textBook` 的强依赖要逐步降级 + +### 加什么 + +- 新增懒属性: + - `chapterRuntimeStore` + - `chapterLoader` + - `summaryDiskCache` + - `pageResolver` +- 新增 `switchToOnDemandPageMode()` + - 将 context 中的 runtime 组件连起来 +- 新增 `clearOnDemandPageModeState()` + - `reloadBook()` 时清理 `bookPageMap` / runtime chapter 缓存 + +### 保留什么 + +- `loadCoordinator` +- `locationCoordinator` +- `searchCoordinator` +- `annotationCoordinator` +- `viewportMonitor` + +### 改造后的职责 + +- Runtime 继续做 façade +- 但主数据入口从 `textBook` 切到: + - `bookPageMap` + - `pageResolver` + - `chapterRuntimeStore` + +## 7.4 `RDEPUBReaderController+DataSource.swift` + +### 先删什么 + +- 删除对 EPUB 大书主路径下 `textBook.page(at:)` 的直接依赖 +- 删除“页数由 `textBook.pages.count` 决定”的唯一入口 + +### 加什么 + +- `pageCountOfReaderView` + - 优先返回 `readerContext.bookPageMap?.totalPages` +- `pageContentView` + - 优先走 `readerContext.pageResolver?.resolvePage(absolutePageIndex:)` + - 命中后组装 `RDEPUBTextContentView` +- `pageIdentifier` + - 允许增加基于 `resolvedPage` 的文本类型分支 +- `pageNum` + - 保持绝对页号语义 + - 在靠近章节边界时触发预取 + +### 保留什么 + +- Web/Fixed Layout 回退分支 +- `topToolView` / `bottomToolView` +- `readerViewOrientationWillChange` + +### 特别注意 + +当前 `pageNum(...)` 中有: +- “到达末页通知” +- `resolvedTextLocation(forPageNumber:)` +- `reconcileTextPaginationSizeIfNeeded` + +这些都默认“当前 pageNum 可直接映射到 textBook page”,改造时必须把它们切到: +- `absolutePageIndex` +- `resolvedPage.context` +- `bookPageMap` + +## 7.5 `RDEPUBChapterLoader.swift` + +### 先删什么 + +- 不删主结构 +- 不删当前的轻量路径 +- 但要避免继续把 loader 理解成“只服务章节窗口” + +### 加什么 + +- 新增“优先恢复摘要”的统一路径: + - 先查 `pageCountCache` + - 再查 `summaryDiskCache` + - 最后才完整分页 +- 新增“构建后立即落盘摘要”的回调或委托接口 +- 新增“输出轻量运行时页”的路径 + - 减少 `RDEPUBTextPage` 的冗余字段复制 + +### 保留什么 + +- `loadChapter(...)` +- `loadChapterSynchronouslyForMigration(...)` +- `buildChapterFromCachedPageRanges(...)` +- `buildChapter(...)` + +### 特别注意 + +当前轻量路径已经具备二开主能力: +- 重新渲染 HTML +- 复用缓存 `pageRanges` +- 跳过完整分页 + +这一点不要推翻,应该直接作为二开的标准实现路径。 + +## 7.6 `RDEPUBChapterRuntimeStore.swift` + +### 先删什么 + +- 不删章节窗口模型 +- 不删 `chapterLoadQueue` + +### 加什么 + +- 增加“当前绝对页附近章节”的窗口维护辅助 +- 增加“只保留当前章”的强制裁剪方法(虽然已有 `evictAllExceptCurrent()`,但要作为主策略写进流程) +- 如有需要,增加“摘要已命中但内容未加载”的状态标记 + +### 保留什么 + +- `chapterDataCache` +- `pageCountCache` +- `imageCache` +- `chapterLoadQueue` +- `setCurrentChapter(...)` +- `evictableSpineIndices()` +- `handleMemoryWarning()` + +### 改造后的职责 + +- 继续做内存中的“少量章节内容缓存” +- 不承担全书页码职责 + +## 7.7 `RDEPUBChapterSummaryDiskCache.swift` + +### 先删什么 + +- 不删现有单章读写接口 + +### 加什么 + +- `readAll(bookID:renderSignature:)` +- `writeManifest(...)` +- `readManifest(...)` +- `containsCompleteSet(...)` +- `removeAll(bookID:renderSignature:)` + +### 保留什么 + +- `write(summary:for:)` +- `read(for:)` + +### 改造后的职责 + +- 从“单章摘要缓存”升级为“二开恢复全书分页索引的主数据源” + +## 7.8 `RDEPUBTextBookCache.swift` + +### 先删什么 + +- 不删当前归档结构 +- 不急着删 NSKeyedArchiver 路径 + +### 加什么 + +- 明确每章级读取能力(若当前只有整包读写,可补章节级辅助接口) +- 明确与 `summaryDiskCache` 的职责边界: + - 它负责快速页范围缓存 + - `summaryDiskCache` 负责更完整摘要和全书恢复 + +### 保留什么 + +- `cacheKey(...)` +- `load(key:)` +- `save(_:key:)` +- `invalidateAll()` + +### 改造后的职责 + +- 继续作为分页结果缓存 +- 不扩展成富文本缓存 + +## 7.9 `RDEPUBTextBookModels.swift` + +### 先删什么 + +- 分阶段删减 `RDEPUBTextPage` 中的重字段: + - 第一阶段:`pageEndOffset` + - 第二阶段:`chapterContent` + - 第三阶段:`absolutePageIndex`、`chapterIndex`、`chapterTitle`、`totalPagesInChapter` 外置 + +### 加什么 + +- 新增轻量运行时页模型(如果不想立刻重命名,可先新增 parallel model) +- 新增用于 `resolvedPage` 的渲染上下文 + +### 保留什么 + +- `RDEPUBTextBook` + - 仍保留给外部文本书籍或兼容路径 +- `RDEPUBTextChapter` + - 仍保留给 builder 输出 + +### 改造后的职责 + +- 从“全书常驻模型”退化为: + - builder 产物 + - 兼容层 + - 非大书路径使用 + +## 7.10 `RDEPUBTextContentView.swift` + +### 先删什么 + +- 不立刻删当前渲染链 +- 不立刻删对 `RDEPUBTextPage` 的支持 + +### 加什么 + +- 新增接收 `RDEPUBResolvedPage` 的 `configure(...)` 重载 +- 页码显示读取 `renderContext.absolutePageIndex` +- `chapterContentProvider` 用于: + - 续段判断 + - 选区提取 + - 高亮定位 + +### 保留什么 + +- CoreText 渲染 +- 封面判断 +- 高亮叠加 +- 搜索高亮 + +### 改造后的职责 + +- UI 层不再假设“page 自己携带全部上下文” +- 改为“page + renderContext”联合渲染 + +## 7.11 `RDEPUBReaderLocationCoordinator.swift` + +### 先删什么 + +- 删除“位置恢复完全依赖 `controller.pageNumber(for:)` 查 `textBook`”的假设 + +### 加什么 + +- 新增: + - `location -> absolutePageIndex` + - `absolutePageIndex -> resolver` +- 当前可见位置改为从: + - 当前绝对页号 + - 当前 runtime chapter 的 offset map + - `rangeAnchor` + 共同恢复 + +### 保留什么 + +- `persist(location:)` +- `persistenceLocation()` + +## 7.12 `RDEPUBReaderSearchCoordinator.swift` + +### 先删什么 + +- 不删 HTML-based 搜索分支 + +### 加什么 + +- 搜索结果导航优先映射到绝对页号 +- 如果目标章节未加载,则通过 resolver / loader 先加载再跳 + +### 保留什么 + +- `RDEPUBHTMLSearchEngine` +- 搜索结果状态机 + +## 7.13 `RDEPUBReaderViewportMonitor.swift` + +### 先删什么 + +- 不删当前视口变化检测 + +### 加什么 + +- 视口变化时: + - 清空运行时章节缓存 + - 失效 BookPageMap + - 重新跑当前书分页摘要恢复 / 重建 + +### 保留什么 + +- `capturePendingPresentationRestoreLocation()` +- `processPendingChangeAfterPagination()` + +--- + +## 8. 详细实施步骤 + +## Phase 1:让当前项目先具备“首开落盘” + +目标: +- 打开当前章后,确保分页结果立刻落盘 + +具体工作: +1. 统一 `RDEPUBTextBookCache` 与 `RDEPUBChapterSummaryDiskCache` 的写入时机 +2. 每次 `buildChapter()` 完成后立即写缓存 +3. 摘要里必须包含: + - `pageRanges` + - `pageCount` + - `fragmentOffsets` + - `pageMetadataList` + - `chapterContentHash` + - `renderSignature` + +完成标志: +- 首开后退出,再打开同一本书,当前章能命中分页缓存 + +## Phase 2:让项目具备“二开不再重新分页” + +目标: +- 直接恢复 BookPageMap +- 只重建目标章节内容 + +具体工作: +1. 增加 `readAll(...)` +2. 增加 BookPageMap 构建器 +3. 在 `applyParsedPublication()` 时优先尝试恢复 BookPageMap +4. `chapterLoader` 优先走“缓存 pageRanges + 重新渲染当前章”的轻量路径 + +完成标志: +- 二开时不再执行全书 `builder.build()` +- 日志可明确区分 `paginate skipped (cache restored)` + +## Phase 3:切断后台整本累积 + +目标: +- 后台只补齐摘要,不再补齐完整 `RDEPUBTextBook` + +具体工作: +1. 移除 `IncrementalChapterStore` 的“整本累积职责” +2. 后台任务改成: + - 构建章节 + - 提取摘要 + - 落盘 + - 释放 +3. 不再创建完整 `textBook(from:orderedBy:)` + +完成标志: +- 后台补齐完成后,内存不会再继续线性上升 + +## Phase 4:接通按绝对页号解析 + +目标: +- RDReaderView 继续显示全书总页数 +- 页面内容按需加载 + +具体工作: +1. 新增 `RDEPUBBookPageMap` +2. 新增 `RDEPUBPageResolver` +3. `DataSource` 改走 resolver +4. `TextContentView` 改为接受轻页 + render context + +完成标志: +- `numberOfPages()` 始终是全书总页数 +- 打开超大书时内存仍仅与当前少量章节相关 + +--- + +## 9. 详细实施步骤 + +### 风险 1:缓存完整性判断不可靠 + +问题: +- 部分章节有缓存、部分没有时,二开可能误判 + +对策: +- 增加 manifest +- 二开只在“全量完整”时才完全跳过重新分页 + +### 风险 2:轻量页模型改造影响选区和高亮 + +问题: +- `RDEPUBTextContentView` 目前对 `chapterContent` 有依赖 + +对策: +- 通过 `chapterContentProvider` 过渡 +- 第一阶段保留旧字段,第二阶段再彻底去掉 + +### 风险 3:后台摘要任务和前台跳页抢资源 + +问题: +- 可能导致首屏后翻页卡顿 + +对策: +- 串行章节加载队列 +- 前台优先,后台可暂停 +- 严禁多章并发重分页 + +### 风险 4:分页缓存命中但内容渲染仍偏慢 + +问题: +- 二开虽然不分页,但仍需重新渲染当前章 HTML + +对策: +- 接受这是一版可行折中 +- 第一阶段先解决“重复分页” +- 后续再评估是否缓存更轻的中间渲染结果 + +--- + +## 10. 风险与对策 + +### 9.1 首开 + +要求: +- 首次打开《凡人修仙传》时,1~2 秒内可见正文 +- 当前章分页结果成功落盘 +- 后台开始补齐其他章节摘要 + +### 9.2 二开 + +要求: +- 不再触发全书重新分页 +- 当前章直接走缓存分页路径 +- 全书总页数立刻可用 + +### 9.3 运行时 + +要求: +- 翻页时总页数不变 +- 页码显示为完整绝对页号 +- 运行时内存仅和当前章 ±1 章相关 + +### 9.4 内存警告 + +要求: +- 仅保留当前章 +- 书不退回、不重开、不丢页码 +- BookPageMap 保留 + +--- + +## 11. 验证标准 + +参考 WXRead 的地方: +- 永远不持有全书内容模型 +- 只缓存当前章 ±1 章 +- 章节加载串行化 +- 分页结果可缓存 +- 内存警告时只保留当前章 + +不直接照搬的地方: +- WXRead 主要依赖章节级页码 / 字符偏移恢复 +- 本项目需要保留**完整全书页码** +- 所以必须在 WXRead 思路上额外增加 `BookPageMap` + +一句话概括: + +**WXRead 提供的是“章节级低内存阅读器”思路;本方案是在它之上补出“完整全书页码”的产品层。** + +--- + +## 12. 与 WXRead 的关系 + +参考 WXRead 的地方: +- 永远不持有全书内容模型 +- 只缓存当前章 ±1 章 +- 章节加载串行化 +- 分页结果可缓存 +- 内存警告时只保留当前章 + +不直接照搬的地方: +- WXRead 主要依赖章节级页码 / 字符偏移恢复 +- 本项目需要保留**完整全书页码** +- 所以必须在 WXRead 思路上额外增加 `BookPageMap` + +一句话概括: + +**WXRead 提供的是“章节级低内存阅读器”思路;本方案是在它之上补出“完整全书页码”的产品层。** + +--- + +## 13. 最终落地建议 + +如果只按收益 / 风险排序,建议实际开发顺序为: + +1. **先打通首开落盘** + - 当前章分页后立即缓存 + +2. **再打通二开跳过重新分页** + - 恢复 BookPageMap + - chapterLoader 走缓存 pageRanges 路径 + +3. **最后切到按绝对页号按需加载** + - 用 resolver 替代 `textBook.page(at:)` + +不要反过来。 + +原因很简单: +- “先改数据源,再补缓存”风险高 +- “先把分页结果缓存用起来”收益最大、回归最小 + +--- + +> 本文档基于 2026-06-03 的代码状态编写,已调整为“首开分页落盘、二开恢复分页、运行时仅保留少量章节”的详细实施方案。 diff --git a/ReadViewDemo/Podfile.lock b/ReadViewDemo/Podfile.lock index 7ee89af..b90ca91 100644 --- a/ReadViewDemo/Podfile.lock +++ b/ReadViewDemo/Podfile.lock @@ -43,7 +43,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: DTCoreText: 11b7fe2104f476f82e75a4e3dbdde74d7186cecb DTFoundation: 76b624967cf5bcaae6bb057d622c536c36ef36d0 - RDReaderView: 54205c82c62ea6c674b43c833642d2643c98c573 + RDReaderView: 617ec758a5db3c10024acf83b110465ea8343b5b SnapKit: d612e99e678a2d3b95bf60b0705ed0a35c03484a SSAlertSwift: aad8dc0c20b36fcffe700b81d7be89d60c7ba4f1 ZIPFoundation: dfd3d681c4053ff7e2f7350bc4e53b5dba3f5351 diff --git a/ReadViewDemo/Pods/Local Podspecs/RDReaderView.podspec.json b/ReadViewDemo/Pods/Local Podspecs/RDReaderView.podspec.json index 76eb8d6..a095b92 100644 --- a/ReadViewDemo/Pods/Local Podspecs/RDReaderView.podspec.json +++ b/ReadViewDemo/Pods/Local Podspecs/RDReaderView.podspec.json @@ -27,9 +27,15 @@ "ZIPFoundation": [ "~> 0.9" ], - "DTCoreText": [], - "SnapKit": [], - "SSAlertSwift": [] + "DTCoreText": [ + + ], + "SnapKit": [ + + ], + "SSAlertSwift": [ + + ] }, "requires_arc": true, "swift_version": "5.10" diff --git a/ReadViewDemo/Pods/Manifest.lock b/ReadViewDemo/Pods/Manifest.lock index 7ee89af..b90ca91 100644 --- a/ReadViewDemo/Pods/Manifest.lock +++ b/ReadViewDemo/Pods/Manifest.lock @@ -43,7 +43,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: DTCoreText: 11b7fe2104f476f82e75a4e3dbdde74d7186cecb DTFoundation: 76b624967cf5bcaae6bb057d622c536c36ef36d0 - RDReaderView: 54205c82c62ea6c674b43c833642d2643c98c573 + RDReaderView: 617ec758a5db3c10024acf83b110465ea8343b5b SnapKit: d612e99e678a2d3b95bf60b0705ed0a35c03484a SSAlertSwift: aad8dc0c20b36fcffe700b81d7be89d60c7ba4f1 ZIPFoundation: dfd3d681c4053ff7e2f7350bc4e53b5dba3f5351 diff --git a/ReadViewDemo/Pods/Pods.xcodeproj/project.pbxproj b/ReadViewDemo/Pods/Pods.xcodeproj/project.pbxproj index bfe94f1..3df20d8 100644 --- a/ReadViewDemo/Pods/Pods.xcodeproj/project.pbxproj +++ b/ReadViewDemo/Pods/Pods.xcodeproj/project.pbxproj @@ -8,541 +8,561 @@ /* Begin PBXBuildFile section */ 002685407B9C4D548571DA24AFD6E8F1 /* NSString+Paragraphs.m in Sources */ = {isa = PBXBuildFile; fileRef = F53CCB60DE5D06A43E4A52EDB274E7D2 /* NSString+Paragraphs.m */; }; - 012337AC75CC2DE92285DA91F07B1CC5 /* RDEPUBNavigatorLayoutContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 521E0E2A04F1C8B2E1AB5D30420A010B /* RDEPUBNavigatorLayoutContext.swift */; }; + 00773D237A15DD1D9DEC72F217AA5A03 /* RDEPUBReaderDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B07F9611B0A5BBD519B68A10C362BE50 /* RDEPUBReaderDelegate.swift */; }; 014449AE560FFF3C628FAE639D043004 /* DTAnchorHTMLElement.m in Sources */ = {isa = PBXBuildFile; fileRef = 7D63AE1801C7AB149219317BE7529B26 /* DTAnchorHTMLElement.m */; }; - 0220B94FD97D36708E40E33DB89C76C8 /* RDEPUBParser+Resources.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE4740D05411538E3715704ED63C6E76 /* RDEPUBParser+Resources.swift */; }; - 023CD43B36E7FC2AEEB3E546121AD89F /* RDEPUBWebViewDebug.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D01284125120D95F9E41743D2CE9F81 /* RDEPUBWebViewDebug.swift */; }; - 03AA3739E62E025785FFB6EB6828B7C0 /* ConstraintMakerEditable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 392A75EAF280876545D14AA00517BA4C /* ConstraintMakerEditable.swift */; }; + 0202E5FFF7290AAFC27E45980753ED2D /* RDEPUBReaderPersistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C8A4EDCD9556F499F840510E94730CD /* RDEPUBReaderPersistence.swift */; }; + 022C814021FFCE8343CF94BEB39F5EB3 /* DTAnimatedGIF.h in Headers */ = {isa = PBXBuildFile; fileRef = A9FBC69C7DA29A7283920FDA9EBBCE8A /* DTAnimatedGIF.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 0306F17B668BC15F5ABFE8EBA33CC65F /* RDEPUBTextRendererSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 303F36CC1C0432C0F498EAED0D82A567 /* RDEPUBTextRendererSupport.swift */; }; 03D90C831397E8636A33CE557024A136 /* NSMutableAttributedString+HTML.h in Headers */ = {isa = PBXBuildFile; fileRef = A2C299CFB47AA6ECF86F7348E1EB751F /* NSMutableAttributedString+HTML.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 04138A4A20D11E47461B6CE8605C8080 /* ConstraintOffsetTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F950CEF31AC0065C0BEAB26BA2CEFA1 /* ConstraintOffsetTarget.swift */; }; 0561DF6793C8516F469D9813F5CE3211 /* DTCoreTextLayouter.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D6DA580F40DF113709E15CF6542A35C /* DTCoreTextLayouter.m */; }; - 077A7464B5BCC77F710DC721FF09B515 /* RDEPUBWebView+JavaScriptBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9FC3B2914C05EFDDFF68ABE5C7A83F0 /* RDEPUBWebView+JavaScriptBridge.swift */; }; - 07C1A54BF3FAE709C8CD683DC9199B17 /* RDEPUBReaderSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = C55B9A91FBFB85980E2E199DC0011868 /* RDEPUBReaderSettings.swift */; }; - 096932721786FDD80CE0F45A097E7162 /* DTCustomColoredAccessory.m in Sources */ = {isa = PBXBuildFile; fileRef = 0C6BE3B2DA9A8C4CD3760CDAD6F305A5 /* DTCustomColoredAccessory.m */; }; - 0A37B6C1B26290A715DB47A6D750293D /* RDEPUBStyleSheetBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 164CFAED45E63A7592580A342D44A43E /* RDEPUBStyleSheetBuilder.swift */; }; + 06C84D472718F0DE04F28EBAA2D6D18E /* URL+ZIP.swift in Sources */ = {isa = PBXBuildFile; fileRef = 537BB06317FA188DF7A8A960152520E8 /* URL+ZIP.swift */; }; + 076732EF4FCEDA7971A857A441AB51EF /* ZIPFoundation-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = D927F128EBF26AD2C61FD0E6B6F6F5ED /* ZIPFoundation-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 07EF1A9EE04C58D0972B0DF502AA49E2 /* RDEPUBWebView+FixedLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71171943FCD1D8271266E2F6347D503B /* RDEPUBWebView+FixedLayout.swift */; }; + 08408D13F523D4077F57B66DEB9F3DA6 /* RDEPUBChapterPageCounter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 694DC251BF921B37330A35A6ACD9DF62 /* RDEPUBChapterPageCounter.swift */; }; + 09A487BF1C24C76B842AF0AF2443B3B5 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B50104DE2E35D820ECA22CDB3E6F96EA /* Foundation.framework */; }; + 09D054B60B0BA4CE30A8D9EB123466B5 /* RDEPUBReadingSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7429300C02F7E8453A429E08C9BD10EA /* RDEPUBReadingSession.swift */; }; 0AAA5BB1DBF584102B15CF8738F1815E /* NSScanner+HTML.m in Sources */ = {isa = PBXBuildFile; fileRef = CA538A48DD3691701EC6B5021D7B7381 /* NSScanner+HTML.m */; }; 0ABC4D62588EABAA636441FD9A82C940 /* CTLineUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = DD58A1908F3C532210EA65ACE410A44D /* CTLineUtils.h */; settings = {ATTRIBUTES = (Public, ); }; }; 0B6FA1F22150D7F8B1865F41FF70C922 /* DTImage+HTML.h in Headers */ = {isa = PBXBuildFile; fileRef = 7AB9016408894675D11069CDAACEDE94 /* DTImage+HTML.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 0B834BEE7479E0F78E3A7D8235A2EC95 /* RDEPUBReaderController+RuntimeBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D4FDB3A066F397D1FF9727C43A0777 /* RDEPUBReaderController+RuntimeBridge.swift */; }; - 0CD6D54912E8E2787802A5EEE4773070 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B50104DE2E35D820ECA22CDB3E6F96EA /* Foundation.framework */; }; - 0DC397CC2F255374FD7DD584D742622E /* RDReaderPreloadController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B57C7D26C0F345A62E14DAB51521E69 /* RDReaderPreloadController.swift */; }; - 0F79C2851ACB90CAE1412D895C7010D7 /* DTExtendedFileAttributes.m in Sources */ = {isa = PBXBuildFile; fileRef = F5389D4E96A4AFE175691B4D3D4524E7 /* DTExtendedFileAttributes.m */; }; - 0F7F1426A2C194B170E82DE115A66DB1 /* NSURL+DTComparing.h in Headers */ = {isa = PBXBuildFile; fileRef = 731C0C61E3AD227D691FBA1C2C17BF7B /* NSURL+DTComparing.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 0F9CC95163443852A6C43FB306CBFCCA /* ConstraintPriorityTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 882FA707ED3513D263A1E0656AAD2D33 /* ConstraintPriorityTarget.swift */; }; + 0BF855C140122F5D1D410BA83E44DD50 /* DTTiledLayerWithoutFade.m in Sources */ = {isa = PBXBuildFile; fileRef = 70F5D03B9B3B7F182C8F5C1F5D4FA7E7 /* DTTiledLayerWithoutFade.m */; }; + 0C798C4A69D1C2F7DD26AD589C05F260 /* DTVersion.m in Sources */ = {isa = PBXBuildFile; fileRef = 4AF6156128A1A0CA24D9D06E346B9445 /* DTVersion.m */; }; + 0C8D728CB0CC5778E6C145E3513EE9FC /* RDPlainTextBookBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5341C765E36D0B172F2F8D4812829521 /* RDPlainTextBookBuilder.swift */; }; + 0E5F3DFC2F07EEF586848939895F5449 /* RDReaderView+ContentAccess.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8E7E1D7707960ECBBBA26D5E04CE1A8 /* RDReaderView+ContentAccess.swift */; }; + 0EBABF6BEEA7615626818E066C16AF18 /* DTAnimatedGIF.m in Sources */ = {isa = PBXBuildFile; fileRef = BC332335B380BC51C4F05C5CCDFB9189 /* DTAnimatedGIF.m */; }; + 0F5153F35326C04F32AC77F5FEB668A5 /* UIView+DTFoundation.m in Sources */ = {isa = PBXBuildFile; fileRef = 4219E7C32019899890CAB66E5FBDF524 /* UIView+DTFoundation.m */; }; + 0F6DEA0F734016D8C05D71E8FCE135E9 /* wxread-replace-latin.css in Resources */ = {isa = PBXBuildFile; fileRef = F159D5AE51CF38BA79429767DAA95194 /* wxread-replace-latin.css */; }; + 0FC46543C988F78ACAB922544907E8CE /* ZIPFoundation-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A06A36ADBE678DB44158D32E751FAFF /* ZIPFoundation-dummy.m */; }; 0FCBC8C64D8862F2A0E8794EDADDECBD /* UIFont+DTCoreText.h in Headers */ = {isa = PBXBuildFile; fileRef = 647FB0C5AAF84C71A7784DD277E48910 /* UIFont+DTCoreText.h */; settings = {ATTRIBUTES = (Public, ); }; }; 100BFFBD24F234C095EDB001B8088DB1 /* DTCoreTextLayoutFrameAccessibilityElementGenerator.m in Sources */ = {isa = PBXBuildFile; fileRef = D1599698B5A62671C488F13E07272B09 /* DTCoreTextLayoutFrameAccessibilityElementGenerator.m */; }; - 106FD56F563F6B4531E0C35C1680358E /* Data+CompressionDeprecated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D641809A99849F80FDCB02F8CE8D6A0 /* Data+CompressionDeprecated.swift */; }; + 1041631AF6C57D4393D6E1D12C937365 /* RDEPUBTextPageRenderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56781BA03B3EE632D516FA3278F78ED2 /* RDEPUBTextPageRenderView.swift */; }; 109458F9D48FD4F381F0E40E2DB3D91C /* DTLazyImageView.h in Headers */ = {isa = PBXBuildFile; fileRef = E5F69A1E1260BAD7737BEB533F643A15 /* DTLazyImageView.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 113D2805F027DF665F9FEE905F6F9D0F /* Typealiases.swift in Sources */ = {isa = PBXBuildFile; fileRef = 945D4549E0B9168D7DDB910DE874B874 /* Typealiases.swift */; }; - 1163FCAE7A2F60099BF3A9675E47E8E2 /* wxread-default.css in Resources */ = {isa = PBXBuildFile; fileRef = 5D8DA3839256D5A245E96F2C67A5E232 /* wxread-default.css */; }; 11AB1902A68385851FB81E870991F9A0 /* DTImageTextAttachment.m in Sources */ = {isa = PBXBuildFile; fileRef = FEEB15F87BA6575BBFE1C5FD19B17CD7 /* DTImageTextAttachment.m */; }; + 129EFD71F4AF6630E1FBCB0B4F9C335A /* RDEPUBReaderChapterListController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7956987E212CE94F57F9CF48F563CE8F /* RDEPUBReaderChapterListController.swift */; }; 12B4A35B8E40C112E18A7C5511F023E9 /* CoreText.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4472755F83C121DE5E1E505D145F6DBE /* CoreText.framework */; }; - 12E1D9F1AA8C4E17DC54886C3CE410C2 /* RDEPUBViewportTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42B4304B138A25135B0946C6A7A3F4A8 /* RDEPUBViewportTypes.swift */; }; 130C3A85D9F9F445222C2E31EC245B0A /* NSScanner+HTML.h in Headers */ = {isa = PBXBuildFile; fileRef = A0927A875CA1F11DBCE60DFC6F0CA0B6 /* NSScanner+HTML.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 13E76CEF04DCA3C012D6BE385BD843F9 /* DTTiledLayerWithoutFade.h in Headers */ = {isa = PBXBuildFile; fileRef = DF5EDCA3B21964D0462BE0982E2F0BF8 /* DTTiledLayerWithoutFade.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 140B64EF45322064CE974C5969E5C7A4 /* RDEPUBTextBookModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49F7945AF94DCC77CB3E7F3F711BD4AB /* RDEPUBTextBookModels.swift */; }; + 146DC5748062792BD49F4DF4197D0F5D /* ConstraintDirectionalInsets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91144FC099DD458E61625CD939577DAA /* ConstraintDirectionalInsets.swift */; }; 14BDD9FB7A7AC5C8ABFF8D24C381F3DA /* NSAttributedString+DTCoreText.m in Sources */ = {isa = PBXBuildFile; fileRef = DBC9280BA00935565E9658775DD7404D /* NSAttributedString+DTCoreText.m */; }; - 154E19088C6CC946DD47CB238C4C9BCC /* NSFileWrapper+DTCopying.m in Sources */ = {isa = PBXBuildFile; fileRef = 1756C6FFCA695290BA62DA4E7B6DD20B /* NSFileWrapper+DTCopying.m */; }; + 15818F4AD782A6DF50E174EB20DBF54E /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 1E89FE5F2996336E862E82C1E14E0EE7 /* PrivacyInfo.xcprivacy */; }; 15A7ED15DA03DD3B9F26A017D270D1C2 /* DTCSSListStyle.h in Headers */ = {isa = PBXBuildFile; fileRef = FDD333B409D528E5006FA48F4D70A6F5 /* DTCSSListStyle.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 1693DD61EFC198586623CFC4CFCAD1DB /* Archive+ZIP64.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED5693BD171B879406EA14C97F4AD8D8 /* Archive+ZIP64.swift */; }; - 16F7D312910FD9A6F8CCE8C7128390EB /* Constraint.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0EBAC7714A5F73B75AAB47D24B08B71 /* Constraint.swift */; }; - 18EE808287AF9003259B828F3C4A6B3F /* RDEPUBPaginationCacheCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20AA41D781113C9A30CD0E9EDD6835D5 /* RDEPUBPaginationCacheCoordinator.swift */; }; + 17F66CCE9BECB4B97210B5C40A6CA706 /* ConstraintMakerExtendable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5245010774F8F1C4857763CC7B96E7B4 /* ConstraintMakerExtendable.swift */; }; + 191764CA461C7A48E8653B476A202743 /* RDEPUBPageInteractionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840B0D63F6448DEAD22C81BD1837E5CF /* RDEPUBPageInteractionController.swift */; }; 191824B2B1E1B0A7DD3B6298100B4B8A /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DE4821B98DDE6C71411EC139EC6D2762 /* CoreGraphics.framework */; }; + 191F106462110C3B72DC586D721BEB57 /* NSDictionary+DTError.m in Sources */ = {isa = PBXBuildFile; fileRef = C18480913E002315E3F5EC6945371697 /* NSDictionary+DTError.m */; }; + 192120ACD3AC7EA38A9F9C113026A6DC /* RDEPUBChapterWindowSnapshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C547D687599B32842C68D8F5C1E67C0 /* RDEPUBChapterWindowSnapshot.swift */; }; 19649ED312D92A6F5D2D6AF4E540CE75 /* DTHorizontalRuleHTMLElement.h in Headers */ = {isa = PBXBuildFile; fileRef = E28CA95D89DBAB2FE56BC8513D5838D7 /* DTHorizontalRuleHTMLElement.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 197770F407C11D14237B98158E119C55 /* RDEPUBReaderController+TableOfContents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 173FEADB0A73EA7EB5B3B7CADB74C3A4 /* RDEPUBReaderController+TableOfContents.swift */; }; - 19BA94EB41DF632F761221B74DAD526A /* DTBlockFunctions.h in Headers */ = {isa = PBXBuildFile; fileRef = A0BD48DAF5AE9DB087F2552E99069709 /* DTBlockFunctions.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 19F07715D355FB1869128B129D015C90 /* WeReadApi.js in Resources */ = {isa = PBXBuildFile; fileRef = ECDEDEE1D78BA473378CB3D26C486E5B /* WeReadApi.js */; }; - 1A35329A88FF592894E7396831A4C88B /* RDEPUBReaderBottomToolView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F274F6DE3095EAF4042CC6BF0F3E566 /* RDEPUBReaderBottomToolView.swift */; }; - 1AF5422FD56B541040966046B479CE53 /* ZIPFoundation-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = D927F128EBF26AD2C61FD0E6B6F6F5ED /* ZIPFoundation-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 1CB67E5F08E9C847E38ED61A79A1F656 /* Archive+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A2659DB0685E16ABAE0382F726DA8C8 /* Archive+Helpers.swift */; }; + 19EBB30AF3127B670CB28D12A3499B43 /* Entry+ZIP64.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7345CE45F696634CEA692A86D16F684C /* Entry+ZIP64.swift */; }; 1D460603DCA4A4E5ECFE8FF1F1CACC10 /* DTColorFunctions.m in Sources */ = {isa = PBXBuildFile; fileRef = AFC4C9F69BF2D0CAEBAA83896C139D2C /* DTColorFunctions.m */; }; - 20BA812F7892018A2905DFB5E9195B33 /* UIImage+DTFoundation.m in Sources */ = {isa = PBXBuildFile; fileRef = D85A85D891A320165843EF9F489178F7 /* UIImage+DTFoundation.m */; }; - 20CBAD9874EBC0BECD8003634E76CC12 /* ConstraintView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A02EA0374C693F881AB36202FB276DCD /* ConstraintView.swift */; }; - 21A7DDFC2A430E9105419DF3DC2D4AA5 /* RDEPUBTextBuildPipelineInterfaces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2055F08A4A307EA51828460481FE8276 /* RDEPUBTextBuildPipelineInterfaces.swift */; }; + 1DB5166E2A9E995A38395A9E8A478E78 /* ConstraintMaker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60657D08CCDB38BFED30BD40F4720CE4 /* ConstraintMaker.swift */; }; + 20BE095312DB8DB101C02F8983C5A6E1 /* DTWeakSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = C422BCADBB48E28B549495663705A0FB /* DTWeakSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 217C3941623536867BD86671D091E6A8 /* RDEPUBChapterLocation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B47D4559682F8352B1AD4598CEDBB28 /* RDEPUBChapterLocation.swift */; }; 2203356EEA94A9172BC7902923E43CBE /* DTCoreTextFontCollection.h in Headers */ = {isa = PBXBuildFile; fileRef = 0B7771FDE9AB1688166584C0AA03F12A /* DTCoreTextFontCollection.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 22AEC3422EFA234293F90AB3738CE694 /* RDEPUBReaderTableOfContentsItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51F09F20128209EC507BBD2E73457DCD /* RDEPUBReaderTableOfContentsItem.swift */; }; + 224A7654E294D21374C015DB3050A57B /* DTTiledLayerWithoutFade.h in Headers */ = {isa = PBXBuildFile; fileRef = DF5EDCA3B21964D0462BE0982E2F0BF8 /* DTTiledLayerWithoutFade.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 22FC9F565564B285178F3420504813FF /* RDReaderSpreadResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6422B9C1E04A7B5F008E4D4ED496CE6 /* RDReaderSpreadResolver.swift */; }; + 232E7F6C9858C024D263A2FB2045CADE /* UIColor+RDEPUBHex.swift in Sources */ = {isa = PBXBuildFile; fileRef = E432EFA64B2640653A9B32C165EDA933 /* UIColor+RDEPUBHex.swift */; }; 24395BEB3C360D9DA5180BFB955B04A0 /* DTAttributedLabel.m in Sources */ = {isa = PBXBuildFile; fileRef = 21BAA93E67D8CBC5297302D798FB4F7A /* DTAttributedLabel.m */; }; 2445709667CB3820E51CD4AA915E6943 /* DTHTMLParserTextNode.h in Headers */ = {isa = PBXBuildFile; fileRef = C30D143134EEC5D4BA99459104B76A2E /* DTHTMLParserTextNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; 24C5B52FD2AA9C49731DC6859ABA7CDE /* NSDictionary+DTCoreText.m in Sources */ = {isa = PBXBuildFile; fileRef = 2C50CAB4241C141EF6EC9ED22AB3A7E8 /* NSDictionary+DTCoreText.m */; }; - 2617C9F41307C3F9C95A8D3A226B4F9F /* DTWeakSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = C422BCADBB48E28B549495663705A0FB /* DTWeakSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 26CF8FEBC5AD3297A149AE731007F41D /* Constraint.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0EBAC7714A5F73B75AAB47D24B08B71 /* Constraint.swift */; }; + 26DC45E81AC3D85CCE9E25F716701BD0 /* RDEPUBSemanticMarkerInjector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E2F4C746E0B7479BE677A20358CB93B /* RDEPUBSemanticMarkerInjector.swift */; }; 270C5C10028F0BC5A62548326CCB379B /* DTColor+Compatibility.m in Sources */ = {isa = PBXBuildFile; fileRef = E196F56E68578181D5BDE787DE5C0F3A /* DTColor+Compatibility.m */; }; - 27B96826B55ACC5EBD0442CE693D0B49 /* DTFolderMonitor.h in Headers */ = {isa = PBXBuildFile; fileRef = 2101691D5743105B2E3E40376584B91C /* DTFolderMonitor.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 27C72FB405DFBC4E9922AD382A2C35EB /* NSDictionary+DTError.h in Headers */ = {isa = PBXBuildFile; fileRef = 0FC368BD2A8D266EB771E5FB9A5A5B3C /* NSDictionary+DTError.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 28BF24C2F4CD401C4C24E1DE0516746E /* DTActivityTitleView.h in Headers */ = {isa = PBXBuildFile; fileRef = 8272D0B59ABD6652D0CAE3DFCC2C5EE1 /* DTActivityTitleView.h */; settings = {ATTRIBUTES = (Public, ); }; }; 29D9B32DFFC59A157EDEF005DB92A5AC /* DTCoreTextLayoutLine.m in Sources */ = {isa = PBXBuildFile; fileRef = C71D83D6AEF198FA680B8A80BF2D9605 /* DTCoreTextLayoutLine.m */; }; - 29DA9E401CE134D89FFFD1B7C2641DDA /* Entry+ZIP64.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7345CE45F696634CEA692A86D16F684C /* Entry+ZIP64.swift */; }; - 2A8AC1184CB8CD9ECA5115B5DB902490 /* RDEPUBReaderConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BBE8424D2FE92DCAA8D2B1CA7D4D029 /* RDEPUBReaderConfiguration.swift */; }; + 2A926090C1E12A6A8CD28E8D06305AF1 /* DTVersion.h in Headers */ = {isa = PBXBuildFile; fileRef = 8BD9F2023FF826F67E69E3407A70A8A6 /* DTVersion.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2A9F4B54A10516793F9EC5CD788DC865 /* RDEPUBPaginationModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24B33931EE71CDEAAB6870A8DD00F441 /* RDEPUBPaginationModels.swift */; }; 2AAF9361720FC2D8FFCA93B5FAE42019 /* NSMutableString+HTML.m in Sources */ = {isa = PBXBuildFile; fileRef = 31F3F91D5624C451E5D44D85B1DDE7C5 /* NSMutableString+HTML.m */; }; - 2ACD02E15A8ABD8CD8D51AE3A6C29683 /* LayoutConstraintItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E8C0D83EB6EDE71B264B6B0DD4644F5 /* LayoutConstraintItem.swift */; }; - 2B81BEF86D6B38359592B4576EB1851A /* ConstraintDSL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02CF362F88AB69253F1898E53D7A286A /* ConstraintDSL.swift */; }; - 2C4050D8A87BF13B3029E82DF0D712C5 /* SSAlertDefaultAnmation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81B8F42440B802E6EC2B1FF7E3022F3A /* SSAlertDefaultAnmation.swift */; }; - 2D03767663B1C3251B80EDAB4F33D3CE /* Archive+Reading.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0619F98DA80922E78A07321D0DC54F62 /* Archive+Reading.swift */; }; - 2DDC5E420C2953CB9DB36BCEB59C1F29 /* NSMutableArray+DTMoving.m in Sources */ = {isa = PBXBuildFile; fileRef = 53499F40E1790A6CBE8AFEB3AC5BEFC8 /* NSMutableArray+DTMoving.m */; }; - 2E26468A7766E7F228328A1B13FC92A8 /* DTBase64Coding.h in Headers */ = {isa = PBXBuildFile; fileRef = B8E40E302B85493BDE0FE91F829EC01E /* DTBase64Coding.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2C1A6DEDACF1E77DDC925F4A0D4ED937 /* RDEPUBReaderSearchCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 130D574A6F9B63B39EE77858FD259D12 /* RDEPUBReaderSearchCoordinator.swift */; }; + 2DA445FA308D3FB0368D295D704952A9 /* ConstraintLayoutGuideDSL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4684B406B325B35F39322D75B59B6479 /* ConstraintLayoutGuideDSL.swift */; }; + 2DF17F0C4C892B7746ADC6F015BA1A98 /* RDEPUBDTCoreTextRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3A5FB560E84A3FE29C291466EAE9E22 /* RDEPUBDTCoreTextRenderer.swift */; }; + 2E079684EDB552970365BD6915457E1D /* RDEPUBViewportTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B5B065EE6ECDEF29D79B1C769C0FF0F /* RDEPUBViewportTypes.swift */; }; + 2E33845786E9118FFCEBED5ABA2467E3 /* RDReaderView+ToolView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9392F7CFCA8C63DD163AAB74B900D729 /* RDReaderView+ToolView.swift */; }; + 2EA47826067DE455847F33A1E3D25BCF /* RDEPUBReaderController+RuntimeBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B936ABDE1067495025723B34C301C87 /* RDEPUBReaderController+RuntimeBridge.swift */; }; + 2ECDB12C26AA7F67E8C0EF4B13ACF674 /* RDEPUBReaderPaginationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B912A462ED162FF141A1E16AFA1FD229 /* RDEPUBReaderPaginationCoordinator.swift */; }; + 2EF79551C72ADF37546B05316E76D12A /* RDEPUBReaderController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01D626274DCF71D8C3FA537C9A7367D8 /* RDEPUBReaderController.swift */; }; + 2F0A7ADF79A55D07DD5805DF70045413 /* Archive+Reading.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0619F98DA80922E78A07321D0DC54F62 /* Archive+Reading.swift */; }; 2FD7360250C2255A80583D4CF518FBFA /* DTTextAttachment.m in Sources */ = {isa = PBXBuildFile; fileRef = F35039A8D0AA1F7BAFC4E5E016057E15 /* DTTextAttachment.m */; }; - 30566C35A1D4F14A6A128B570E061108 /* RDEPUBReaderTopToolView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE9D5BE3BC1B9E7C17553D6377DAA5D3 /* RDEPUBReaderTopToolView.swift */; }; - 30618822C1BD51F652D2F1A6ABA7E871 /* ConstraintLayoutSupportDSL.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5B34EE2160C27947D1EEFD42184CF5D /* ConstraintLayoutSupportDSL.swift */; }; - 308F8ADD0B8367CBD99AA8781FF1B580 /* RDEPUBTextRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01073FE593A6AE17D4376AF909753A8F /* RDEPUBTextRenderer.swift */; }; + 300C1B103D970ABBC25EABD5259376DE /* wxread-replace.css in Resources */ = {isa = PBXBuildFile; fileRef = 7F04C6EEE7B8C63B04236F35AE34B289 /* wxread-replace.css */; }; + 304087B3DB374A21BAC8B9E6DF19BE32 /* RDEPUBNavigatorLayoutContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 521E0E2A04F1C8B2E1AB5D30420A010B /* RDEPUBNavigatorLayoutContext.swift */; }; 30C01FE491C26314F776AD8B2AF136FA /* NSAttributedStringRunDelegates.m in Sources */ = {isa = PBXBuildFile; fileRef = E6F2E79CC33E0823C4E719ECDDFBBE08 /* NSAttributedStringRunDelegates.m */; }; - 30FFF4A91F83509371C5B0CC18D5B919 /* NSData+DTCrypto.m in Sources */ = {isa = PBXBuildFile; fileRef = 17F750F38183302BF345F40F5A4CDD43 /* NSData+DTCrypto.m */; }; 31B805C50E0E187E6242B0C878C4D30D /* DTCoreTextMacros.h in Headers */ = {isa = PBXBuildFile; fileRef = 0F5D34047BAA894BEF1DFA23E6A9DAB1 /* DTCoreTextMacros.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 31EFA2212FA286AB73DE8ADE65F32224 /* RDReaderView+ContentAccess.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8E7E1D7707960ECBBBA26D5E04CE1A8 /* RDReaderView+ContentAccess.swift */; }; - 3238A1DC9C7C19F477F62081CEB1CDF6 /* RDEPUBWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 268AF3AC14AC70E5C08F5EF60DBFC4F8 /* RDEPUBWebView.swift */; }; - 32B51B6FF5F874A18A0C55F9D785CB24 /* ConstraintRelatableTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 885653999BD237A574B67DB279DE7E87 /* ConstraintRelatableTarget.swift */; }; - 3349AC669F17F9DE48E0CBEF213C71F7 /* RDReaderSpreadResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6422B9C1E04A7B5F008E4D4ED496CE6 /* RDReaderSpreadResolver.swift */; }; + 31F2CB38A6379FA3544FAC5B7DF5E2B1 /* SnapKit-SnapKit_Privacy in Resources */ = {isa = PBXBuildFile; fileRef = B9DCB5EC0B1CDADD221717CADDF62359 /* SnapKit-SnapKit_Privacy */; }; + 32844568EDF13978B21D3A12652DD1E9 /* NSString+DTFormatNumbers.m in Sources */ = {isa = PBXBuildFile; fileRef = 1ACA5E519F100582F0D772FCD76125C0 /* NSString+DTFormatNumbers.m */; }; + 33C7E8291AAEAFB29321B12A38E9981D /* RDEPUBReadingLocationModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0311E22DAAE8E4ECEE12DFDB8A092A9 /* RDEPUBReadingLocationModels.swift */; }; 343E794F88AA503F5139C2244DC1A2B1 /* DTTextAttachmentHTMLElement.h in Headers */ = {isa = PBXBuildFile; fileRef = C298199F5DC5390E8B6666F638B34C10 /* DTTextAttachmentHTMLElement.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 359B41BE1F797CD1708D6E6473542222 /* Data+Compression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5964AF1336E2F0DA0E2C32991E76C78C /* Data+Compression.swift */; }; - 35A8A9292DCF25CF8E47462B12D246EA /* RDEPUBRenderRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8BD060A6BD280BDB1107988B230DB1A /* RDEPUBRenderRequest.swift */; }; + 357D4722A56E550F0314AA5F5DAEFB06 /* RDEPUBPageLayoutSnapshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2787B86458FE50067B72A704805C4688 /* RDEPUBPageLayoutSnapshot.swift */; }; 35C5BE59CA712CCDF93D6E7010D93883 /* DTAttributedTextContentView.h in Headers */ = {isa = PBXBuildFile; fileRef = C123C127CC539ACCB510F665DB774899 /* DTAttributedTextContentView.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 36B1D51E18199A4B80157CED6D97C104 /* RDEPUBWebContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B69E0662B2604A49919B5DDA504A39C4 /* RDEPUBWebContentView.swift */; }; + 36B99498C3A4842A71BAC9E5C466A439 /* RDEPUBWebView+JavaScriptBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9FC3B2914C05EFDDFF68ABE5C7A83F0 /* RDEPUBWebView+JavaScriptBridge.swift */; }; + 36C22841B8927E609AA29CD05D5132B5 /* DTBase64Coding.h in Headers */ = {isa = PBXBuildFile; fileRef = B8E40E302B85493BDE0FE91F829EC01E /* DTBase64Coding.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 36D5F57AA24043EDE534856755F63DD7 /* RDEPUBReaderViewportMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5EBB29FB787860E6DAF09A9B32BD92D /* RDEPUBReaderViewportMonitor.swift */; }; 372E907DF08263D0E1AF904F1FCD66EB /* DTTextAttachment.h in Headers */ = {isa = PBXBuildFile; fileRef = D36ED8209C907E3E9B074DDB399F54FF /* DTTextAttachment.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 374B0659957B12D9ED373A217DA8C67B /* RDEPUBTextLayouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6A32D83CDE7A6494252691BE7E99638 /* RDEPUBTextLayouter.swift */; }; 389F7C8400F9223C019F2D7825CD2369 /* NSCharacterSet+HTML.m in Sources */ = {isa = PBXBuildFile; fileRef = 713C9FD0354D16AFCCFA630A97F2DA68 /* NSCharacterSet+HTML.m */; }; - 38A65ED943B0C81B4EA40C50BBC63E50 /* UIApplication+DTNetworkActivity.m in Sources */ = {isa = PBXBuildFile; fileRef = 77213203802F44012346181FED2856F9 /* UIApplication+DTNetworkActivity.m */; }; - 38D8FC875F9F62E1F50B427EE02B2DA8 /* NSData+DTCrypto.h in Headers */ = {isa = PBXBuildFile; fileRef = 02EDFE0EEA47398F3E1A361A73AF6A6C /* NSData+DTCrypto.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 38F8914C748138F46D406222CA4E85AC /* epub-fixed-layout.html in Resources */ = {isa = PBXBuildFile; fileRef = 6CB839342BEE5213E1F9D59970EEC467 /* epub-fixed-layout.html */; }; - 3A6BC96AC902E04E85517D39B48F1FCD /* ConstraintDirectionalInsetTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = A651CEE8062C84A597DBC44ECA3DBCC1 /* ConstraintDirectionalInsetTarget.swift */; }; - 3AD8BF0401CF9D849805A1E3A57C5399 /* RDEPUBTextContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B789EEAB8791D76E537C6186EA1AC1D /* RDEPUBTextContentView.swift */; }; - 3AF7CE34BE9312F7027DC4763B33C4EB /* RDEPUBChapterTailNormalizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B832537E4E508747319F7DF89BE256A /* RDEPUBChapterTailNormalizer.swift */; }; - 3C5EDCAB9E3A36C8F43A339B24ADCAB3 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0834E84632886CDD9911CAB76183F1CE /* QuartzCore.framework */; }; - 3CCC8FC7FF95E7A988B0F0B0468C12BF /* cssInjector.js in Resources */ = {isa = PBXBuildFile; fileRef = 4D9B8DA4629A86EE848CD980111120C3 /* cssInjector.js */; }; + 3939A2FEF1CBE351626D1858A5189223 /* RDEPUBLocationConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A34CA64CF765506AA4947EE31BD5B53 /* RDEPUBLocationConverter.swift */; }; + 39EA92DA1B85B808BA7C6B96E9550BFE /* UIImage+DTFoundation.h in Headers */ = {isa = PBXBuildFile; fileRef = 15941683D394FB8D7EAE91CD2CF03FF1 /* UIImage+DTFoundation.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 3A14E77E5539416CB3616992AE1908EE /* UIScreen+DTFoundation.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F051D813D50D20D53205D5FDCAA877C /* UIScreen+DTFoundation.m */; }; + 3A5317D34FDA7B91C27C6CA5BEFFB7B3 /* UIApplication+DTNetworkActivity.m in Sources */ = {isa = PBXBuildFile; fileRef = 77213203802F44012346181FED2856F9 /* UIApplication+DTNetworkActivity.m */; }; + 3B31B5AB8A3C15F27630E73514FD47D0 /* RDEPUBTextLayoutFrame.swift in Sources */ = {isa = PBXBuildFile; fileRef = 141325B9AB28E266F40552E4F82F952D /* RDEPUBTextLayoutFrame.swift */; }; + 3D964664C06383CA1A438402714E9D10 /* RDEPUBTextPositionConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 476A874AD38352C8F072379DDDEBE8CD /* RDEPUBTextPositionConverter.swift */; }; + 3DB87FD33402BAFFDE9003F9022B1FF6 /* RDEPUBTextBuildPipelineInterfaces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2055F08A4A307EA51828460481FE8276 /* RDEPUBTextBuildPipelineInterfaces.swift */; }; 3DC8F9474FC6A0BB7E81A703CE958966 /* NSAttributedString+DTDebug.h in Headers */ = {isa = PBXBuildFile; fileRef = 71E2D85A099C99CD82FD1842EE7C52D2 /* NSAttributedString+DTDebug.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 3F9097BE7A6CA36DA12224F99D0D37BF /* RDEPUBTextRendererSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 303F36CC1C0432C0F498EAED0D82A567 /* RDEPUBTextRendererSupport.swift */; }; - 415F0FDFD64679F7847A8E1E92277D68 /* ConstraintRelation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3593ADF561D422F73C8EABAD4A4F440 /* ConstraintRelation.swift */; }; - 416D5AD659DCF761E28F76F7E23B9F7F /* RDEPUBHTMLNormalizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5612A9760A2FEC2682E417DF75F4C869 /* RDEPUBHTMLNormalizer.swift */; }; - 4195E8AE080906ABE5E4F962B53AF658 /* Archive.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C8F0DCEAA5A3DF38C503CA30DC94C1C /* Archive.swift */; }; - 431305719D350F47AB85C2E09B3FB6D4 /* NSString+DTFormatNumbers.h in Headers */ = {isa = PBXBuildFile; fileRef = 6D86C7063D1128EAEB3A30EBEF214B3F /* NSString+DTFormatNumbers.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 43C745CDBA3B1C734F9F93E63D48FF51 /* Archive+Deprecated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA251BAC8B430B3A4D4C4321046B990 /* Archive+Deprecated.swift */; }; - 43E3E9B7575ADB1B90A26C2879C6BD75 /* DTAnimatedGIF.m in Sources */ = {isa = PBXBuildFile; fileRef = BC332335B380BC51C4F05C5CCDFB9189 /* DTAnimatedGIF.m */; }; - 441269EE3B2065102DE8D9F0D7EB461B /* RDEPUBResourceResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 615F9666D462FC95654FDC2732F700A6 /* RDEPUBResourceResolver.swift */; }; - 4432641D03459CFA21386E96E69D470A /* RDEPUBReaderAnnotationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C7333A1BFCB9D7512A152B2B0EC6350 /* RDEPUBReaderAnnotationCoordinator.swift */; }; - 44688B5127920D2151DD116BB8709AE0 /* RDReaderView-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = B92A6483AF878B266B8FE776A9A6F4B3 /* RDReaderView-dummy.m */; }; + 3E9C037BE986CCEA7775F9E941D87E07 /* FileManager+ZIPDeprecated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04F9B298989305B9569B71E448E8C1D4 /* FileManager+ZIPDeprecated.swift */; }; + 3EEAC1BF3947FE7BDA0AF19A662252BB /* RDEPUBTextAnnotationOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = C702F88F0474C4EB2C57ACE7E18A4B18 /* RDEPUBTextAnnotationOverlay.swift */; }; + 40FD6F748DF81499C888E3922FBA3D54 /* RDEPUBBackgroundTrace.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CD82AF8E46DDEC2A04CAD90F008B98C /* RDEPUBBackgroundTrace.swift */; }; + 4116789760AF2C8CD5181F3FCEFFED46 /* RDEPUBPageCountCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC570F5F2BA388E5FCEA6C6A68382779 /* RDEPUBPageCountCache.swift */; }; + 4147CD8AB72DD67E941FE2293D334FB3 /* RDEPUBHTMLNormalizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5612A9760A2FEC2682E417DF75F4C869 /* RDEPUBHTMLNormalizer.swift */; }; + 4373F2A4F3253DD14440D8B9FA7A0AD8 /* NSURL+DTComparing.h in Headers */ = {isa = PBXBuildFile; fileRef = 731C0C61E3AD227D691FBA1C2C17BF7B /* NSURL+DTComparing.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 438409E070011BCB561C09F6CEED848C /* RDEPUBReaderController+RenderSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E3749137D028A340460A72BA52D076 /* RDEPUBReaderController+RenderSupport.swift */; }; + 43B5EB9D7FC7ACF025F7D6C88869A9C9 /* RDEPUBReaderAssemblyCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2823BBB9462E120DC6A762A1AD5224C /* RDEPUBReaderAssemblyCoordinator.swift */; }; + 445CC2DF30F3DD5E01A7A0B42BE67ECA /* RDReaderView+CollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7D36A8CADACEF369077B844EA911D0 /* RDReaderView+CollectionView.swift */; }; 44B1FBF893C06F314F8C9BD1405897D8 /* DTCoreTextGlyphRun.m in Sources */ = {isa = PBXBuildFile; fileRef = 36FCB869246FE45FD1C9609A34EB4E74 /* DTCoreTextGlyphRun.m */; }; - 44B918236776349CDE1D85C70EDD7328 /* RDEPUBSelectionOverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 185CD1A4E30E72715AF3129924F063E6 /* RDEPUBSelectionOverlayView.swift */; }; 45133E47CC5FBCF78BFCD38981BA027F /* DTDictationPlaceholderView.h in Headers */ = {isa = PBXBuildFile; fileRef = 982391EC8620A617B8788C8A6101C96A /* DTDictationPlaceholderView.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 453AA8CE0133392DB836780F707A41C1 /* Archive+MemoryFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 174FC0CA05D20BB995E211DE5A0857DB /* Archive+MemoryFile.swift */; }; + 458D53C484D2470508819D1D41376EE7 /* DTFoundationConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BD568D38C209BA1CC0EBFA809A35204 /* DTFoundationConstants.m */; }; 45F90B98546FDBA03AB112DFE859A185 /* DTCSSStylesheet.m in Sources */ = {isa = PBXBuildFile; fileRef = E0D90EB06FC37D534934542F9CAB1D74 /* DTCSSStylesheet.m */; }; - 4626D775FF8050FC1B85AED719C6CCD3 /* RDEPUBReaderSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49F47726654DBD3FAFF21FFA121D6EE7 /* RDEPUBReaderSettingsViewController.swift */; }; - 466AB83B99254DDBDF7ED5730D665FEC /* RDEPUBReaderController+PublicAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BD33493F588BFD65E47EC9B8773C74C /* RDEPUBReaderController+PublicAPI.swift */; }; + 4651629ECB6C6EA549A9E880D86B7648 /* UIView+DTFoundation.h in Headers */ = {isa = PBXBuildFile; fileRef = 79A77584AB3B92B2508AE0BCFCB8FD87 /* UIView+DTFoundation.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 46AEC0EB14BA522889FD7812A1139CB7 /* NSDictionary+DTError.h in Headers */ = {isa = PBXBuildFile; fileRef = 0FC368BD2A8D266EB771E5FB9A5A5B3C /* NSDictionary+DTError.h */; settings = {ATTRIBUTES = (Public, ); }; }; 46EF32D586FF590CE2C60C66364B9357 /* ImageIO.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FEB14A8F8170777DB7CCC3F27C8EA57A /* ImageIO.framework */; }; - 488A6649575C6F4DB55809630C3218B1 /* RDURLReaderController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ECB9576AFBC841E4282C8091B9D1244 /* RDURLReaderController.swift */; }; - 48A82D1D9C23DC22512E4005270631D8 /* RDEPUBTextPageRenderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E030AC563D5D21F6792CA5056667F3F /* RDEPUBTextPageRenderView.swift */; }; - 48B2080D6CD3A7C757A1F69635D1446E /* RDReaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A294C65CC6C154EF2122380953FEFE3 /* RDReaderView.swift */; }; - 48BE5A69C2B044534A947BC810B1DED4 /* RDReaderView-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 59444DC57CE3171F4EF0CD6481FACF30 /* RDReaderView-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 48C129F6559E80BA658FE6824B8BA94F /* ConstraintConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7176B677A350927FC670368313DC2CA1 /* ConstraintConfig.swift */; }; + 4701EA34789A6B21772EC0B428C8CEF5 /* DTFolderMonitor.m in Sources */ = {isa = PBXBuildFile; fileRef = B245F7E143F3091652C64F6593E33F7A /* DTFolderMonitor.m */; }; + 48622BBC0156DD7A2122F0E274337175 /* RDEPUBWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 268AF3AC14AC70E5C08F5EF60DBFC4F8 /* RDEPUBWebView.swift */; }; + 48B4BA94B5A76AEF0CEF341F5332E57E /* ConstraintMakerEditable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 392A75EAF280876545D14AA00517BA4C /* ConstraintMakerEditable.swift */; }; + 496997716B1BEACEFA587B3B9D71C2AA /* RDEPUBRuntimePageCount.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DF4AADE4D7A63EA46F2265946368FA8 /* RDEPUBRuntimePageCount.swift */; }; + 49ACCF887B0F58474D5EC6B7FD6D40AF /* Archive+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A2659DB0685E16ABAE0382F726DA8C8 /* Archive+Helpers.swift */; }; 4A13B4E5EA8616A8F09D90BC6EAEEA64 /* CTLineUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = D24FE8634A7AD2562DB8ADB9D4BBB36A /* CTLineUtils.m */; }; - 4B184BEB24237C483AE54DD52C6C107A /* NSString+DTPaths.m in Sources */ = {isa = PBXBuildFile; fileRef = A3565FEE19F19CB43A43409AFFF2BD2C /* NSString+DTPaths.m */; }; - 4B520B6C488D585307F8B6A48DC788B6 /* RDEPUBTextPageDecorationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DB5F45FEE54BD8D3F49BD345EDAB5D9 /* RDEPUBTextPageDecorationView.swift */; }; - 4D00F4DD1B8D720FFA715756C7D70D40 /* RDEPUBSearchEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16C3C684271C3DE0964041A2D8701E58 /* RDEPUBSearchEngine.swift */; }; - 4D1B1254D155186FEE0796901E729627 /* RDEPUBWebView+FixedLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71171943FCD1D8271266E2F6347D503B /* RDEPUBWebView+FixedLayout.swift */; }; + 4B58554A575684981F3BB1A6F90E4124 /* DTHTMLParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 32FB2B1B0498DCBE7B79BDF49E963E3D /* DTHTMLParser.m */; }; + 4CA1C4073140A73BB87C51B4A6C8EED0 /* RDEPUBSearchEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16C3C684271C3DE0964041A2D8701E58 /* RDEPUBSearchEngine.swift */; }; + 4CE9742F307F7E0BE251502700E47AAD /* ConstraintItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28D62D3200AC3D4E42C81CDFF7E884E8 /* ConstraintItem.swift */; }; + 4D7BC6D6F616907780E4F3842381158B /* SnapKit-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 9CE078B465FDEF40EC0469626A2C2469 /* SnapKit-dummy.m */; }; 4DA6EC46F8968265B96C6D7B2260C4E5 /* DTTextBlock.m in Sources */ = {isa = PBXBuildFile; fileRef = ADC5AC8FD4EC45E9BA5654A38715BB93 /* DTTextBlock.m */; }; 4E4939F6592B06D2CA764DCACFB47871 /* NSString+Paragraphs.h in Headers */ = {isa = PBXBuildFile; fileRef = 83E37DC74BB53F90A5AD54842858A62F /* NSString+Paragraphs.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 4E90223029F18880D771B8A854B1D710 /* NSString+DTUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = BE547E464E5D34F9FFB24BAC81D1E4CC /* NSString+DTUtilities.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 4F048676EAF2ED5D377AEB768A4B7FB5 /* ConstraintOffsetTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F950CEF31AC0065C0BEAB26BA2CEFA1 /* ConstraintOffsetTarget.swift */; }; 4F50C703427747B88405080B221349A1 /* DTLinkButton.h in Headers */ = {isa = PBXBuildFile; fileRef = D7B5E61166782ED8D585D4FCEE2074DB /* DTLinkButton.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 4FFAECEE5100543DF0EE4113D9973AEC /* wxread-replace.css in Resources */ = {isa = PBXBuildFile; fileRef = 7F04C6EEE7B8C63B04236F35AE34B289 /* wxread-replace.css */; }; + 4F52FD03B23A749F8BAAA8D096941D29 /* RDReaderView-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = B92A6483AF878B266B8FE776A9A6F4B3 /* RDReaderView-dummy.m */; }; 5065D99E1588E8CC60677E74916A5C03 /* DTAttributedTextCell.m in Sources */ = {isa = PBXBuildFile; fileRef = E1F63A854BBF28E93AC1D3F71CD48CBE /* DTAttributedTextCell.m */; }; + 50DDDD48489D8E41F64FEDABAA9A21F3 /* ConstraintRelatableTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 885653999BD237A574B67DB279DE7E87 /* ConstraintRelatableTarget.swift */; }; + 518E7331BD31A0AC933280839470DED8 /* ConstraintLayoutSupportDSL.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5B34EE2160C27947D1EEFD42184CF5D /* ConstraintLayoutSupportDSL.swift */; }; 51A1B5800D23C355B1712E8D1103780C /* DTTextBlock.h in Headers */ = {isa = PBXBuildFile; fileRef = D497C3CA364E37DAAFF2C3B90CC7DBE8 /* DTTextBlock.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 527D76D4D4C694DADE242202CB1E69C0 /* ConstraintLayoutGuide.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E6BA0ECCB0ACF8580C3B5192CA9F11B /* ConstraintLayoutGuide.swift */; }; + 52824BFFE7E7D032E3C075D472158C3F /* Archive+Writing.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF988440049800DB7824A94B81D20254 /* Archive+Writing.swift */; }; 528DBAC7FCEFC7D6575C1F6CB4399AEA /* DTStylesheetHTMLElement.h in Headers */ = {isa = PBXBuildFile; fileRef = 96FA99AA7D4AD0D4246C8DAC1CC0C24E /* DTStylesheetHTMLElement.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 53240F8A0E98A48B59C6B2FA88270310 /* RDEPUBSearchModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8830746779660E908FADB2DEAD879AA2 /* RDEPUBSearchModels.swift */; }; - 53D7A11DAB7412522214EBA6CCB2ABE3 /* NSFileWrapper+DTCopying.h in Headers */ = {isa = PBXBuildFile; fileRef = 09C957F3199C596781EFA1DEA1DDCEC8 /* NSFileWrapper+DTCopying.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 53D8E4E9570BCD825E00CDD8A881054E /* RDEPUBModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A2067EA81E19AA5F8E66DE4300DAB4A /* RDEPUBModels.swift */; }; - 53E8737EF21A600E0BFCD6F5DE467C5F /* RDEPUBReaderAssemblyCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72E8D63E9D57DA89DEE2774A89A61152 /* RDEPUBReaderAssemblyCoordinator.swift */; }; - 54634310A0DDCA6986A95D3A66CEECE0 /* Archive+WritingDeprecated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98C7359A7C3FC7675FED220200A43A17 /* Archive+WritingDeprecated.swift */; }; - 5498E8AC301713ED5BEBDDA779B37AC9 /* ConstraintDirectionalInsets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91144FC099DD458E61625CD939577DAA /* ConstraintDirectionalInsets.swift */; }; - 54B9DDFA2E91F83BB5C9E2F5722884EE /* RDEPUBReaderChromeCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2BCD9B2E62051692362E404278683A18 /* RDEPUBReaderChromeCoordinator.swift */; }; - 552B25799A8AAD599BFAB1C6C3B4D454 /* RDReaderTapRegionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C637C7A440920FA0074A7ECF39D9C6F8 /* RDReaderTapRegionHandler.swift */; }; + 5291125A832E62416FFA6D0E1D0BD3EE /* RDEPUBParser+Resources.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE4740D05411538E3715704ED63C6E76 /* RDEPUBParser+Resources.swift */; }; + 53BF7B971EC9643C6A30EB9E61DD4D39 /* ConstraintMakerRelatable+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECFE19508E12BF8B0B52E8CB4F724217 /* ConstraintMakerRelatable+Extensions.swift */; }; + 54FC0EC0F7CE25F7D1E846C8CFFEA363 /* DTCustomColoredAccessory.h in Headers */ = {isa = PBXBuildFile; fileRef = 33DB57DF8C5AE334B1BBE9F989352F1D /* DTCustomColoredAccessory.h */; settings = {ATTRIBUTES = (Public, ); }; }; 5582735073EBB9F5078E6E8AEEFB646A /* DTCoreTextLayoutLine.h in Headers */ = {isa = PBXBuildFile; fileRef = 64AB89EAB10148C39B726B26BE04F4F8 /* DTCoreTextLayoutLine.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 562A2DC2B45B453B2FD5C3F5F6EBE405 /* Archive+BackingConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61E36C4FE448DDDA03450847FCB785B6 /* Archive+BackingConfiguration.swift */; }; - 571B611A9DE148C57AF2C7EA53857C76 /* SSAlertAnimationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97995C73297871C353364E685939DED9 /* SSAlertAnimationController.swift */; }; - 5720E398FFB4ECEDDE02A66A43FB901D /* DTSmartPagingScrollView.m in Sources */ = {isa = PBXBuildFile; fileRef = 96EEB6B022E22A4ABC86D87469D4815B /* DTSmartPagingScrollView.m */; }; + 55BD6901F7A4D0189E0FA0FC0177D453 /* RDEPUBTextSearchEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 591B0B382F10A52D84F90D5C34893CB8 /* RDEPUBTextSearchEngine.swift */; }; + 565E82C6F71955C9D0E75896531597D7 /* ConstraintConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7176B677A350927FC670368313DC2CA1 /* ConstraintConfig.swift */; }; 57D5228C8A85628772D4D78E7FBEAF3C /* DTDictationPlaceholderTextAttachment.m in Sources */ = {isa = PBXBuildFile; fileRef = 477A0FC2A1F0B7F7CCF72D201ECEBAB0 /* DTDictationPlaceholderTextAttachment.m */; }; - 584FC22D5B5F0120FCF9ACE0E570D137 /* UIView+DTFoundation.h in Headers */ = {isa = PBXBuildFile; fileRef = 79A77584AB3B92B2508AE0BCFCB8FD87 /* UIView+DTFoundation.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 5894AF8B6F6022A84E3FB6F22494A364 /* RDEPUBCoreTextPageFrameFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = C996E23A573AA37FB9B35A09725C1D1D /* RDEPUBCoreTextPageFrameFactory.swift */; }; - 58A61B581F5E418C86458C824089E6DC /* DTCompatibility.h in Headers */ = {isa = PBXBuildFile; fileRef = B5FCE04EBB553439D1C2BA1001936293 /* DTCompatibility.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 58B093EF9A7EA0373AD770DC0939D925 /* RDEPUBPaginationModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24B33931EE71CDEAAB6870A8DD00F441 /* RDEPUBPaginationModels.swift */; }; - 58EB34A71BF81209B8649B5D44C5458F /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B50104DE2E35D820ECA22CDB3E6F96EA /* Foundation.framework */; }; - 58F5FFB8103C4733FF2F207057E60CE5 /* DTCoreGraphicsUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = B6425B2235BA00FE865AF58EF44F1144 /* DTCoreGraphicsUtils.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 59272D5476F27119EF257930FE3D4722 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 53D473C6F07982D3D25781A343EC5A99 /* PrivacyInfo.xcprivacy */; }; - 597A2991F0147098A1A5F3555E7A4433 /* RDEPUBTextBookBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ED26E7B899A0B29A202F0CB011D5F09 /* RDEPUBTextBookBuilder.swift */; }; + 584AF2C9447F50855A6E7BE3E59F0F2C /* RDEPUBParser+Package.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B142DE3191F790898FB028F3E6C91BA /* RDEPUBParser+Package.swift */; }; + 588B8D01B170676730D6BEEA0AE1B756 /* Archive+Progress.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD987C2FA7C8FBC96FD024E421DA0E68 /* Archive+Progress.swift */; }; + 58ACB3DC0AC4EA63B6DC65585DC75509 /* DTBase64Coding.m in Sources */ = {isa = PBXBuildFile; fileRef = 061BD3911E4E530C792E65CED7868C33 /* DTBase64Coding.m */; }; + 595DAF34748691D7C7CBB356FBAB389F /* RDEPUBTextAnchor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69F083BF98854C4CB74C7C21DA34399A /* RDEPUBTextAnchor.swift */; }; + 5966F7524564265D493279F72B6BEF10 /* DTLog.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B007AEEACE9EA271DD73AFD80A74D83 /* DTLog.m */; }; 597DE4CDF5C674E269F03F6AA0AFFCC1 /* NSAttributedString+HTML.m in Sources */ = {isa = PBXBuildFile; fileRef = 92460D196C5300656C9649784EBD3C58 /* NSAttributedString+HTML.m */; }; - 59FB4EE13B3168CADAAF65DC27802EFA /* RDEPUBTextPerformanceSampler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8607E8F20399521FFA80047941709E8C /* RDEPUBTextPerformanceSampler.swift */; }; - 5B36BCAC2EE5770917EE46A25443744F /* NSURL+DTUnshorten.h in Headers */ = {isa = PBXBuildFile; fileRef = 054674EB90C625BB4BEA9368ED92592B /* NSURL+DTUnshorten.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 5BD388950720A80033201DCCC5B396B1 /* FileManager+ZIP.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27B7D3C6F0C845E3D42BBA613FFB0465 /* FileManager+ZIP.swift */; }; - 5C08CE24A3901A3E0F8B090F71A5871B /* RDReaderView+ToolView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9392F7CFCA8C63DD163AAB74B900D729 /* RDReaderView+ToolView.swift */; }; + 598583ABA1E1066FDC5367F9808C0836 /* RDEPUBBuildDiagnosticsReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9D07EA99A4878E9F8A8839F19056654 /* RDEPUBBuildDiagnosticsReporter.swift */; }; + 59FB1B17033D0375CB66E1D98F56316A /* RDReaderTapRegionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C637C7A440920FA0074A7ECF39D9C6F8 /* RDReaderTapRegionHandler.swift */; }; + 5B4794DDD8BC9E1B37F638ABBE69D794 /* RDEPUBJavaScriptBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04C13EA061DD9866E60F683AD49EE945 /* RDEPUBJavaScriptBridge.swift */; }; + 5BE96578F3A236ED2544FB20A4996165 /* NSString+DTPaths.h in Headers */ = {isa = PBXBuildFile; fileRef = FC5EC7C3D1808C9CF53354D7A3D69EE7 /* NSString+DTPaths.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 5C30F01AF8B18ECF5D3521C0FDAAB2D3 /* RDEPUBReaderSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3D719AE19AA83101150C2A3287D7E31 /* RDEPUBReaderSettings.swift */; }; 5D03A5B9DC067875CFEC9D4EA7B059E8 /* DTCoreTextLayoutFrameAccessibilityElementGenerator.h in Headers */ = {isa = PBXBuildFile; fileRef = D863AE4F659A0F71B22DC9FF334DDF4E /* DTCoreTextLayoutFrameAccessibilityElementGenerator.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 5D8A0C9FDE40C0ECD438718CAE0080B4 /* DTTiledLayerWithoutFade.m in Sources */ = {isa = PBXBuildFile; fileRef = 70F5D03B9B3B7F182C8F5C1F5D4FA7E7 /* DTTiledLayerWithoutFade.m */; }; + 5D507DDC4C72CB2DC8D142D760607E42 /* RDReaderView-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 59444DC57CE3171F4EF0CD6481FACF30 /* RDReaderView-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 5D6041EC2E04883F9A27D93DEBC975B7 /* Archive.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C8F0DCEAA5A3DF38C503CA30DC94C1C /* Archive.swift */; }; 5D8D65121E87E419D8D2F23F8AF43673 /* DTColor+Compatibility.h in Headers */ = {isa = PBXBuildFile; fileRef = 80B3D15ED0D19D020663EF055386291A /* DTColor+Compatibility.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 5DCA3B311B4E46F3E96BF3D73119EB86 /* RDEPUBTextBookModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49F7945AF94DCC77CB3E7F3F711BD4AB /* RDEPUBTextBookModels.swift */; }; + 5E1D82CF63AD7EB0D4FB8343AAAF0B54 /* RDEPUBParser+ReadingProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 667442978548AAC9B24B14B6F2597536 /* RDEPUBParser+ReadingProfile.swift */; }; 5E90EBD876ED4E50AB890FB56C4BFA57 /* DTHTMLParserNode.h in Headers */ = {isa = PBXBuildFile; fileRef = C8343CDCF82729A8EBCCA37A6650E34F /* DTHTMLParserNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 600D8BCA0F833306A821AE9963A2705F /* RDReaderGestureController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45989DEA616D69D14306213B39E79FD6 /* RDReaderGestureController.swift */; }; 60532A2E06CCFF814933D2FAE6793AE9 /* DTCoreTextLayouter.h in Headers */ = {isa = PBXBuildFile; fileRef = C26DBE1150BC9A30B69839783E000358 /* DTCoreTextLayouter.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 60974BD9B17F54CEB69CA6B80BB25730 /* NSURL+DTComparing.m in Sources */ = {isa = PBXBuildFile; fileRef = 9BFCAC7C2AEA867E67C63A852576774F /* NSURL+DTComparing.m */; }; + 6132A7C5DE9DEB521CA73B2D8081EBBD /* SnapKit-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 0B10D3EC1701BBA4D69926915F942558 /* SnapKit-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 616E5DFABBD43DBBAA152497064256A1 /* RDEPUBParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC9B5052FF6F4941CE226FF90ED457DE /* RDEPUBParser.swift */; }; 61BE3607EABD2657BC71AF8ACB1C25D4 /* DTHTMLAttributedStringBuilder.h in Headers */ = {isa = PBXBuildFile; fileRef = 8B59C9A1B265393E4BA8383B308A5917 /* DTHTMLAttributedStringBuilder.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 636106852946C6686CE248B0722AE689 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 1E89FE5F2996336E862E82C1E14E0EE7 /* PrivacyInfo.xcprivacy */; }; - 63A8D8CEE40C56246D84EB59FCC78AF6 /* RDEPUBReaderDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EFD8EDA8C70B31844B0A06CBCD0B403 /* RDEPUBReaderDelegate.swift */; }; - 64122D8C357DCDCBF0BD1BA6080A46FE /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B50104DE2E35D820ECA22CDB3E6F96EA /* Foundation.framework */; }; + 622964509F7567B406220D2C25065A01 /* epub-fixed-layout.html in Resources */ = {isa = PBXBuildFile; fileRef = 6CB839342BEE5213E1F9D59970EEC467 /* epub-fixed-layout.html */; }; + 634D25CD2ECC2E3530A42747FEB1ABF0 /* RDEPUBChapterLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = B58B13E167D03567209323F2033289A1 /* RDEPUBChapterLoader.swift */; }; + 64E2961F391116D43AA0252BF0A08C68 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B50104DE2E35D820ECA22CDB3E6F96EA /* Foundation.framework */; }; + 655E95DF5671890070BF91C022E42427 /* epub-bridge.js in Resources */ = {isa = PBXBuildFile; fileRef = 75838C8BB89B6B0B142EC3F6922D925B /* epub-bridge.js */; }; + 65957BA854E8610EA216E8323B136C5B /* NSURL+DTUnshorten.m in Sources */ = {isa = PBXBuildFile; fileRef = C80065DBA5634DAA71129404975D7349 /* NSURL+DTUnshorten.m */; }; 6634D568E49F5610242E1454680E5885 /* NSAttributedString+SmallCaps.h in Headers */ = {isa = PBXBuildFile; fileRef = DD189DA6054AAE2242F6644A2623B50E /* NSAttributedString+SmallCaps.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 6754F409DCFA7345D497978F8C8FEB33 /* RDEPUBReaderTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40AE4D16785DB42B707640DA3DCC86E3 /* RDEPUBReaderTheme.swift */; }; 67AE2AC8D9B961D45871077774E11FD3 /* DTObjectTextAttachment.h in Headers */ = {isa = PBXBuildFile; fileRef = 073A7F9DF1A554BD1B324EA9E0090E91 /* DTObjectTextAttachment.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 67FC2E8BF2E51B5AEBE73C81BE0375C6 /* RDEPUBTextIndexTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEB1B8F778B5A8442BF121C13E5E460A /* RDEPUBTextIndexTable.swift */; }; - 68B185C109A9E448C95B2153715DBF41 /* SSAlertAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D2EC0B845131A88247902AEA26F40AE /* SSAlertAnimation.swift */; }; 69674FEA70899F07BE6098AAEC849978 /* DTAccessibilityViewProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = 43CFADE0F46014990052AD67F17C86FF /* DTAccessibilityViewProxy.m */; }; - 699FFBEC77DD1CC3A54701AFC65FB2D0 /* RDEPUBReaderLocationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C26BE3D7337938B1DB1B7610001CD169 /* RDEPUBReaderLocationCoordinator.swift */; }; - 69A2C721B126BE26E4BBAC1DF63858BB /* RDEPUBWebView+Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B55540809AED42C4B61333680CEC4CB /* RDEPUBWebView+Configuration.swift */; }; - 6AAD95B33C144808A58590F8EEC1812B /* DTFoundation-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = FCC718115E5C31C88A680522AE36B089 /* DTFoundation-dummy.m */; }; - 6AB30A0403FF95366915988914FB7D3C /* DTHTMLParser.h in Headers */ = {isa = PBXBuildFile; fileRef = 0D75FEDE65722CF41F7F32A164376926 /* DTHTMLParser.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 6D96D665BAD918861C54F9682459BE3E /* ImageIO.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FEB14A8F8170777DB7CCC3F27C8EA57A /* ImageIO.framework */; }; + 6A24B7902200A8AD2C6BA6298676F56B /* RDEPUBPublication.swift in Sources */ = {isa = PBXBuildFile; fileRef = F62A82E787C2CF458A244942F96F35A3 /* RDEPUBPublication.swift */; }; + 6B4E7177F034E79B8C3F932183E84011 /* NSData+DTCrypto.h in Headers */ = {isa = PBXBuildFile; fileRef = 02EDFE0EEA47398F3E1A361A73AF6A6C /* NSData+DTCrypto.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 6BDB4D52EC440E5AFF74F823638E88FD /* RDEPUBReaderController+DataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = F45AEF1D13277C0A27B1AB4915BFE37B /* RDEPUBReaderController+DataSource.swift */; }; + 6CDF59BFF3F212BD65C009C7080542BE /* DTPieProgressIndicator.h in Headers */ = {isa = PBXBuildFile; fileRef = 1B443D035BA5652B03A41BE905C5AC17 /* DTPieProgressIndicator.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 6DA8B5EB829C2AFB0891E6EF583C6C0E /* NSMutableArray+DTMoving.h in Headers */ = {isa = PBXBuildFile; fileRef = B0AC307F12521444C973EA0DE38B4608 /* NSMutableArray+DTMoving.h */; settings = {ATTRIBUTES = (Public, ); }; }; 6E3D0FDD78ECC20F4B3697ED21D64224 /* MediaPlayer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4C2B218B5EAA5831A036BBF7E1EFC1B9 /* MediaPlayer.framework */; }; - 6F6EE18D7F839AB238A8B47527737580 /* SSAlertPresentAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = E481007A8174943868EBC81CF3B04B9F /* SSAlertPresentAnimation.swift */; }; 6FA0125F16DDEDAD341DB75DEB084F4B /* DTHTMLWriter.m in Sources */ = {isa = PBXBuildFile; fileRef = E16D20FA492E085BD0871B107B3077B7 /* DTHTMLWriter.m */; }; - 6FF9A524B248BAACCF8E3D4A00BF4CF1 /* NSArray+DTError.h in Headers */ = {isa = PBXBuildFile; fileRef = CEA93EDA6ED54D374265AA5D6B9EBBA9 /* NSArray+DTError.h */; settings = {ATTRIBUTES = (Public, ); }; }; 7027EF8D77D4082C1BB9B80FF61FCADC /* DTCoreTextLayoutFrame+Cursor.h in Headers */ = {isa = PBXBuildFile; fileRef = 28043173CA3B3787DC401ACEB5398108 /* DTCoreTextLayoutFrame+Cursor.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 708B7BF70311E17A3B27C8AF36937ED9 /* NSString+DTPaths.h in Headers */ = {isa = PBXBuildFile; fileRef = FC5EC7C3D1808C9CF53354D7A3D69EE7 /* NSString+DTPaths.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 7104D4CFBCF8831BEFD9EC0DD9AB678F /* DTVersion.h in Headers */ = {isa = PBXBuildFile; fileRef = 8BD9F2023FF826F67E69E3407A70A8A6 /* DTVersion.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 714C19C6756608FBFDD1DDBDBCBC294D /* ConstraintLayoutGuide.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E6BA0ECCB0ACF8580C3B5192CA9F11B /* ConstraintLayoutGuide.swift */; }; - 71624302D6B9222103A160FCC949D7B3 /* Archive+Writing.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF988440049800DB7824A94B81D20254 /* Archive+Writing.swift */; }; - 7224AF6EDE2F24361DFAC2B54ADA9FE4 /* UIColor+RDEPUBHex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 933F9E11CFE9DD8A63C31D119B381969 /* UIColor+RDEPUBHex.swift */; }; + 70F696A554B44EF07110E6DAC0305F70 /* RDReaderView+PageCurl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A525BB5B1708520D409C1A30CF1CD2D /* RDReaderView+PageCurl.swift */; }; + 718326388B25165631B5288610134599 /* RDEPUBReaderLoadCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E67C6A6267B473D5F1B33C17A6DAFED4 /* RDEPUBReaderLoadCoordinator.swift */; }; + 72107C30F1F174C445EA98997BBFEA41 /* RDEPUBChapterTailNormalizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B832537E4E508747319F7DF89BE256A /* RDEPUBChapterTailNormalizer.swift */; }; + 72BDFD8E71E58E07880C0944EF9696A7 /* RDEPUBRenderRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8BD060A6BD280BDB1107988B230DB1A /* RDEPUBRenderRequest.swift */; }; 730ACB99580C5982DC08D50FC5084D41 /* DTAttributedTextView.h in Headers */ = {isa = PBXBuildFile; fileRef = A276DE925253460239BFAEC7663F60D3 /* DTAttributedTextView.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 73C9F968338A5A64F77CC8E763D05AB5 /* RDEPUBPublication.swift in Sources */ = {isa = PBXBuildFile; fileRef = F62A82E787C2CF458A244942F96F35A3 /* RDEPUBPublication.swift */; }; + 7359A2AE58B3B4A7FA377C53189D8D04 /* rangy-serializer.js in Resources */ = {isa = PBXBuildFile; fileRef = FD2D305B938F31ABC9EC9369E230A62B /* rangy-serializer.js */; }; + 7398A6AEB47EA308F2E93FE03B1A3D06 /* DTFoundation-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 5FAB3C7F19A511C2B1B7DFBA3CA308C4 /* DTFoundation-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 73BB353CA378834928C3946524DF2D12 /* DTLog.h in Headers */ = {isa = PBXBuildFile; fileRef = 6A1EBB9497F4846C84CA0BF5E4F84FC7 /* DTLog.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 741F9778F7F3965764042EDDE33420E1 /* RDEPUBTextRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01073FE593A6AE17D4376AF909753A8F /* RDEPUBTextRenderer.swift */; }; 7459A1656EDC29D685073B83561B783E /* NSNumber+RomanNumerals.h in Headers */ = {isa = PBXBuildFile; fileRef = 8552BE1A1F6A6D2879763DE3B2B8A2DD /* NSNumber+RomanNumerals.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 755E82D4E33E3D24EBA3F426A58961D9 /* RDReaderViewProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31407369BDB36B40D253244CCF1F954A /* RDReaderViewProtocols.swift */; }; - 767F49D5DE536CA0332B88DAD52D3A5F /* RDEPUBReaderController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D479BB941D318A247DDD0A5C55B859C4 /* RDEPUBReaderController.swift */; }; - 768BBF03C64346E98F23D30DABEE3CAB /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B50104DE2E35D820ECA22CDB3E6F96EA /* Foundation.framework */; }; + 74794205BE245B73D21939B91C6191C6 /* NSString+DTUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = DCECDBD73595CA88A2208000B4B68352 /* NSString+DTUtilities.m */; }; + 74D9BA3031E055ABB5AE2DDA78314197 /* RDEPUBPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B1FF16A8E3839BF599427F23328492C /* RDEPUBPreferences.swift */; }; + 76FA97B8B0555BBB9168276D59521D5E /* RDEPUBWebView+Search.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1DD2563775FC83493319BFC4E953F00 /* RDEPUBWebView+Search.swift */; }; + 7710039BB36FFBABDCCB093589600A0A /* RDEPUBReaderLocationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = A969E1A1BC7F8699EB7BF9147AD900A9 /* RDEPUBReaderLocationCoordinator.swift */; }; + 779664F3B0F751FBBAE1BC4DA55BA82F /* RDEPUBStyleSheetComposer.swift in Sources */ = {isa = PBXBuildFile; fileRef = A16842E6F1C3E8526D28C7DABA1D6898 /* RDEPUBStyleSheetComposer.swift */; }; 7821105EF320619993B3BF7FB90C7ADA /* DTCSSStylesheet.h in Headers */ = {isa = PBXBuildFile; fileRef = 3B610E558C4917C4257DC198EAFEE2F6 /* DTCSSStylesheet.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 7877D94D10C10C9B7AC335B0A5864575 /* RDEPUBReaderPersistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B8132548C8745F3AE7F3CBC78E1DDA9 /* RDEPUBReaderPersistence.swift */; }; 78D24D82D98D77E744FF73C9040618A8 /* DTHTMLParserNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 11B604FC3B0D5C91FEA763DCC0BE4DC5 /* DTHTMLParserNode.m */; }; - 7A31B9816036902B3A32A5C00CF641AC /* ConstraintMultiplierTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09BE22EE711B7A38A4443E1C1C12BD79 /* ConstraintMultiplierTarget.swift */; }; + 792ACF8BA7BA68E9534E4B8D095690D6 /* NSData+DTCrypto.m in Sources */ = {isa = PBXBuildFile; fileRef = 17F750F38183302BF345F40F5A4CDD43 /* NSData+DTCrypto.m */; }; + 795035283B9F00CF74DF195F73746032 /* Data+Compression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5964AF1336E2F0DA0E2C32991E76C78C /* Data+Compression.swift */; }; + 797ACCBCC1F8E7E01BA73F5298CF335A /* RDEPUBAssetRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98BFF67943C8CABB6171A6EDC7639F82 /* RDEPUBAssetRepository.swift */; }; + 7A3B9F9A741756370C23FCE449D10FBF /* DTBlockFunctions.h in Headers */ = {isa = PBXBuildFile; fileRef = A0BD48DAF5AE9DB087F2552E99069709 /* DTBlockFunctions.h */; settings = {ATTRIBUTES = (Public, ); }; }; 7A48CDFB40202EFD09A2A9AB0EE0770D /* DTCoreTextFontDescriptor.h in Headers */ = {isa = PBXBuildFile; fileRef = 56489D9BA834497E48438A4B4E1CCCFF /* DTCoreTextFontDescriptor.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 7A7F0DE40D8B77129FC1E7FB54D79F7C /* RDEPUBParser+Package.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B142DE3191F790898FB028F3E6C91BA /* RDEPUBParser+Package.swift */; }; - 7AF352A0D8851E7032D631349459BADB /* ConstraintMakerRelatable+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECFE19508E12BF8B0B52E8CB4F724217 /* ConstraintMakerRelatable+Extensions.swift */; }; - 7B03A8945E4D03692C21B116D97DC74C /* SSAlertSwift-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 937CEFA81DA4227008DBFB114783A08B /* SSAlertSwift-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 7B06E0624E131F4B4AE9CBA7C182DDD3 /* DTFolderMonitor.m in Sources */ = {isa = PBXBuildFile; fileRef = B245F7E143F3091652C64F6593E33F7A /* DTFolderMonitor.m */; }; - 7C026FA594F06339EAAEB774A776A030 /* RDEPUBResourceURLSchemeHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64644C3A8F17BE731AE00482BDEE9D86 /* RDEPUBResourceURLSchemeHandler.swift */; }; + 7AA9CA762DDBD33B58C9396CD6F539BC /* RDEPUBTextPageDecorationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBF8CA6102062C9482CC8D4170DC3454 /* RDEPUBTextPageDecorationView.swift */; }; 7C05F56B62B15EFF344C5CDE4A52B8FB /* DTHTMLElement.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D4C9B7ED5E04C216AB28E3A4065C1C5 /* DTHTMLElement.m */; }; - 7CB2AB8578F4A18DAEC3D738EC46F13A /* RDEPUBPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B1FF16A8E3839BF599427F23328492C /* RDEPUBPreferences.swift */; }; 7D29C0A53AB1B4C1B257D46EBBDC9DA3 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B50104DE2E35D820ECA22CDB3E6F96EA /* Foundation.framework */; }; - 7E2ACD5F2A6DB7B183F56A4B3F356AD0 /* ConstraintConstantTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F090A0289338B14050FAF16C22605B0 /* ConstraintConstantTarget.swift */; }; - 7E68A2D73CBE9B63C25ED43B6D33D494 /* ConstraintViewDSL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E5637603691B9B35C2A886B686D3CE7 /* ConstraintViewDSL.swift */; }; + 7D6EFD0580A8BCD609B2076F8A6EB4C8 /* ImageIO.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FEB14A8F8170777DB7CCC3F27C8EA57A /* ImageIO.framework */; }; + 7DB7306892F76345FF578849C1ABCC48 /* RDEPUBChapterCacheKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B7B984084289FA85BE14684929535F9 /* RDEPUBChapterCacheKey.swift */; }; + 7E751AEAEE50E4B85C68A76D9440BE2D /* SSAlertSwift-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 937CEFA81DA4227008DBFB114783A08B /* SSAlertSwift-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 7F0A4F7D502DA58E65A856AD418B7442 /* RDEPUBChapterWindowCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = A191B6108F3F50C4F1C76BE74D9612A9 /* RDEPUBChapterWindowCoordinator.swift */; }; + 7F9B8AAB4623E3CC58C37D8EBFF514B4 /* DTExtendedFileAttributes.m in Sources */ = {isa = PBXBuildFile; fileRef = F5389D4E96A4AFE175691B4D3D4524E7 /* DTExtendedFileAttributes.m */; }; 7F9CC306B17D409DB903DF850FEDA64C /* DTListItemHTMLElement.m in Sources */ = {isa = PBXBuildFile; fileRef = F7B0087DE7C394A677F5883541AB4FB7 /* DTListItemHTMLElement.m */; }; + 7FC6815502492DF1D663D87A2D9AB207 /* DTFoundationConstants.h in Headers */ = {isa = PBXBuildFile; fileRef = FD03567681A79FFB6E81B3105503065D /* DTFoundationConstants.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 7FDB342E7990D04F20D66B54DD3288F3 /* DTActivityTitleView.h in Headers */ = {isa = PBXBuildFile; fileRef = 8272D0B59ABD6652D0CAE3DFCC2C5EE1 /* DTActivityTitleView.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8099F1E613A15275EF5FC59ED3C73579 /* NSAttributedString+HTML.h in Headers */ = {isa = PBXBuildFile; fileRef = 2D3D7C8444F58D9B3FCB4F3006D2CFBA /* NSAttributedString+HTML.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 809E658A89FF98AF699C66D2429B29FD /* UIScreen+DTFoundation.h in Headers */ = {isa = PBXBuildFile; fileRef = A3765B289CB7E6C5DF6160D8EBC83C3C /* UIScreen+DTFoundation.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 8115CAF95FA9A1ED63213498A1CA421E /* DTSmartPagingScrollView.h in Headers */ = {isa = PBXBuildFile; fileRef = D2BA6D5DBD4C6A9285C4592721790A26 /* DTSmartPagingScrollView.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 810E6A4FBB7900C15F099E5989F8CEE8 /* RDEPUBChapterOffsetMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 712AC2E76CBFB2863C3DA9B4EBA22C38 /* RDEPUBChapterOffsetMap.swift */; }; 811D7CBCD710AE45FF80A4B05BFE444C /* UIFont+DTCoreText.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E1548FC42118F1A4164739DE6BE4E9 /* UIFont+DTCoreText.m */; }; 814D0D63E5FD4AB7231E63064FE9EB5C /* NSAttributedString+DTDebug.m in Sources */ = {isa = PBXBuildFile; fileRef = BF4F3EC137168B92A5DA85AF50CD239F /* NSAttributedString+DTDebug.m */; }; + 8179744DB2686642A42608EE551E2670 /* cssInjector.js in Resources */ = {isa = PBXBuildFile; fileRef = 4D9B8DA4629A86EE848CD980111120C3 /* cssInjector.js */; }; 81905A7717925A17E6B98E934A75B8BA /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0834E84632886CDD9911CAB76183F1CE /* QuartzCore.framework */; }; + 820761BB2CDA45C00C99DC18A4BA08DC /* SSAlertPresentAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = E481007A8174943868EBC81CF3B04B9F /* SSAlertPresentAnimation.swift */; }; + 8207FBC4C2ADC9BB801A355415D4E075 /* ConstraintDirectionalInsetTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = A651CEE8062C84A597DBC44ECA3DBCC1 /* ConstraintDirectionalInsetTarget.swift */; }; 82119A0DC4981CC2AF389C7C4CC08186 /* DTListItemHTMLElement.h in Headers */ = {isa = PBXBuildFile; fileRef = 98658510AA525496EBF5792E5D9884ED /* DTListItemHTMLElement.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 8250FB5F79016BDCDD60AD731F0BD350 /* wxread-replace-latin.css in Resources */ = {isa = PBXBuildFile; fileRef = F159D5AE51CF38BA79429767DAA95194 /* wxread-replace-latin.css */; }; + 82616A4F36B18EF488C317018F2CFAD1 /* RDEPUBSelectionOverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8639CA8074A14D276D10928D93D15842 /* RDEPUBSelectionOverlayView.swift */; }; + 831D4789477DF97F18B6E25C132D10DA /* RDEPUBNavigatorState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AE2A1B83DCC99894642D65CE5EADFD4 /* RDEPUBNavigatorState.swift */; }; + 8329F87853CB379C84A455A9C72F0E97 /* NSFileWrapper+DTCopying.m in Sources */ = {isa = PBXBuildFile; fileRef = 1756C6FFCA695290BA62DA4E7B6DD20B /* NSFileWrapper+DTCopying.m */; }; + 834EF1600F4487827FBFB72512E21EC7 /* RDEPUBCoreTextPageFrameFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = C996E23A573AA37FB9B35A09725C1D1D /* RDEPUBCoreTextPageFrameFactory.swift */; }; + 83A146B504F211455EA722D0E17FDB3E /* ConstraintViewDSL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E5637603691B9B35C2A886B686D3CE7 /* ConstraintViewDSL.swift */; }; 83A4BC9FE4765C9B848DD4925D99F362 /* DTAccessibilityElement.h in Headers */ = {isa = PBXBuildFile; fileRef = 99299A9FD6EF739D18352AC5B9325CB0 /* DTAccessibilityElement.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 8483273E622F869454330F71D3824098 /* RDEPUBTextBookCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE13C3BA99810308E47EC53C68F6169A /* RDEPUBTextBookCache.swift */; }; - 855020CBFB65BB877255A61BAFFF7245 /* RDReaderPageChildViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6369702FFDE8929ED1931FC77B6B02A1 /* RDReaderPageChildViewController.swift */; }; - 88A854FEBA21BB5C77868E663A7C4346 /* RDEPUBReaderChapterListController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6703BD7A2507028A11319843D0D7945F /* RDEPUBReaderChapterListController.swift */; }; - 89068EC008D095EA391E422D25604753 /* ConstraintLayoutSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50297AAB8278F2BB6FCFE82B20C7A049 /* ConstraintLayoutSupport.swift */; }; - 89A4857FB58483273A0954DA4935A558 /* DTPieProgressIndicator.m in Sources */ = {isa = PBXBuildFile; fileRef = 5DA1E8494B86FFD79C639DCB8C21E1BB /* DTPieProgressIndicator.m */; }; + 84BEC66020F30A0AE6F0B5F38C0BBF5B /* RDEPUBChapterData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CEAB7724C9E71FFC8A368EECE6AADF9 /* RDEPUBChapterData.swift */; }; + 851D7CADE59C83C2E79791EFBD2F1B9E /* DTFolderMonitor.h in Headers */ = {isa = PBXBuildFile; fileRef = 2101691D5743105B2E3E40376584B91C /* DTFolderMonitor.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 852C505B9BC6E6CE207559DB6F65F526 /* NSString+DTFormatNumbers.h in Headers */ = {isa = PBXBuildFile; fileRef = 6D86C7063D1128EAEB3A30EBEF214B3F /* NSString+DTFormatNumbers.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 854C70ECC65BAE4D46A186D4263472B2 /* Data+CompressionDeprecated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D641809A99849F80FDCB02F8CE8D6A0 /* Data+CompressionDeprecated.swift */; }; + 859B45A8A4A1B9FBEB3002EE8BEE46E1 /* NSArray+DTError.m in Sources */ = {isa = PBXBuildFile; fileRef = 73940D215D644B6815A09DA802CFBDDF /* NSArray+DTError.m */; }; + 872FE38ED0AD604DA2F5CE213D90789C /* LayoutConstraint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 932FD688DE493323BA6B691BC2CB5094 /* LayoutConstraint.swift */; }; + 88C98BA758B76A26DF050D7B8813DE9E /* RDEPUBWebContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F40B8225D553E55937EB0FAB4B649DA /* RDEPUBWebContentView.swift */; }; + 892B8712F2112DDFA6619A0913DE30B3 /* UIImage+DTFoundation.m in Sources */ = {isa = PBXBuildFile; fileRef = D85A85D891A320165843EF9F489178F7 /* UIImage+DTFoundation.m */; }; + 8972016E0797F04604D9FC71F4A96A1A /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B50104DE2E35D820ECA22CDB3E6F96EA /* Foundation.framework */; }; + 89F359611ECEC3137EF4B3029380FB58 /* ConstraintLayoutSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50297AAB8278F2BB6FCFE82B20C7A049 /* ConstraintLayoutSupport.swift */; }; 8A0A0FB23DB0021ADC9AD0D90631883A /* NSMutableString+HTML.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C6293E97E128E98559FB74D9A342EAD /* NSMutableString+HTML.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8A35CC67F0AA6B26397C10D756EA34E8 /* DTTextHTMLElement.h in Headers */ = {isa = PBXBuildFile; fileRef = FB2ABB1D297442D91AA42F1407DB4A58 /* DTTextHTMLElement.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 8A5A271780D55E365E441781D2143538 /* UIScreen+DTFoundation.h in Headers */ = {isa = PBXBuildFile; fileRef = A3765B289CB7E6C5DF6160D8EBC83C3C /* UIScreen+DTFoundation.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 8ABB70BC85808639BE2820ED576705EF /* NSString+DTPaths.m in Sources */ = {isa = PBXBuildFile; fileRef = A3565FEE19F19CB43A43409AFFF2BD2C /* NSString+DTPaths.m */; }; 8AC90941E71199090BCED76DE89EFB60 /* DTAttributedTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = 77A613841EB90827AF321DD50B21DB5D /* DTAttributedTextView.m */; }; + 8ADADD66618F7A5E44ACF3D34630E5B7 /* Archive+WritingDeprecated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98C7359A7C3FC7675FED220200A43A17 /* Archive+WritingDeprecated.swift */; }; 8B405CE13E7D718A9340A82B52766AB2 /* NSCharacterSet+HTML.h in Headers */ = {isa = PBXBuildFile; fileRef = 75FA8E843D9225BD65EB9D17825EC0F4 /* NSCharacterSet+HTML.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 8E1EFA605CED65BF0D98CD48AD0DB08B /* RDEPUBChapterData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CEAB7724C9E71FFC8A368EECE6AADF9 /* RDEPUBChapterData.swift */; }; + 8BC48C9A595913DC27EE31013ACE7EC8 /* ConstraintPriority.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45C7129757B54DFD9E445D0A17D73A83 /* ConstraintPriority.swift */; }; + 8C68B08F6E4E170047C5E35EA1BFD3DC /* RDEPUBReaderDependencies.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02C071FDE0C607A22BD7B0375DE72458 /* RDEPUBReaderDependencies.swift */; }; + 8E1DC7355706F3565B8A0B38A59AAED9 /* DTBlockFunctions.m in Sources */ = {isa = PBXBuildFile; fileRef = 375179DD3A4576CEF947E1760BBDC69B /* DTBlockFunctions.m */; }; 8E8726381CBCFB1EDB350183456D4EF7 /* DTHTMLParserTextNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 13AB6A32A293E3DC21A7047C6DB958CA /* DTHTMLParserTextNode.m */; }; - 8F8C9BEEF983EFFB9D38B5A7C8BA5518 /* RDReaderPagingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5F17B78960BAA0E4F63CBDDEAD953FE /* RDReaderPagingController.swift */; }; - 90BC61DF679C4367DAD09A5D02E0AB02 /* DTCustomColoredAccessory.h in Headers */ = {isa = PBXBuildFile; fileRef = 33DB57DF8C5AE334B1BBE9F989352F1D /* DTCustomColoredAccessory.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 90C189E1159D4DE5725F33E1E8ABF5D3 /* NSString+DTURLEncoding.m in Sources */ = {isa = PBXBuildFile; fileRef = CB7F3535507F56824BA21509302CF8B3 /* NSString+DTURLEncoding.m */; }; - 90C2D02582043D4CF91BDAD5FB0BC344 /* RDEPUBReaderContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CA5A4277907D54250DB4BF7B95CE1A9 /* RDEPUBReaderContext.swift */; }; - 912F0C755DC31C3010FB5700F94193DD /* RDEPUBTextSearchEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 591B0B382F10A52D84F90D5C34893CB8 /* RDEPUBTextSearchEngine.swift */; }; + 8F060D8C99425376DFF3A67818DE1B44 /* rangy-core.js in Resources */ = {isa = PBXBuildFile; fileRef = A2BD2727B338CE5C2C3BC6E8B8A23DE9 /* rangy-core.js */; }; + 91EA145D5B76F9BDCD03FB801F3ACB3D /* DTSmartPagingScrollView.h in Headers */ = {isa = PBXBuildFile; fileRef = D2BA6D5DBD4C6A9285C4592721790A26 /* DTSmartPagingScrollView.h */; settings = {ATTRIBUTES = (Public, ); }; }; 91FDA47810CE5CE2C48AED9205AE89B5 /* DTStylesheetHTMLElement.m in Sources */ = {isa = PBXBuildFile; fileRef = DA25968B7C9A40031DA45BB328EA8A2E /* DTStylesheetHTMLElement.m */; }; - 92594DF73D4E65DE3A257494085A7FA4 /* RDEPUBTextAnnotationOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F910EB56A7325328DCD94F082E99648 /* RDEPUBTextAnnotationOverlay.swift */; }; + 929774A0C8800A3DE8378B9A3ED3BC80 /* RDReaderViewProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31407369BDB36B40D253244CCF1F954A /* RDReaderViewProtocols.swift */; }; 92C59F96D443525E8833676479FA60C5 /* DTVideoTextAttachment.h in Headers */ = {isa = PBXBuildFile; fileRef = 73A99927E52B24583A6A2CF7E6F23848 /* DTVideoTextAttachment.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 93CDFBD7BE5E4EB5ED06820EB9A89794 /* NSURL+DTAppLinks.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D36F7CDCD7D5788CDCB3A1421DB242B /* NSURL+DTAppLinks.m */; }; - 9476BAD56597A02E230B9E4F8F6D306E /* RDEPUBReadingSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7429300C02F7E8453A429E08C9BD10EA /* RDEPUBReadingSession.swift */; }; + 92E45AB1E4920FB4AC1E7C683B915705 /* ConstraintDSL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02CF362F88AB69253F1898E53D7A286A /* ConstraintDSL.swift */; }; + 9304920D7E3F5289F47FB0F78FD33CB5 /* default.css in Resources */ = {isa = PBXBuildFile; fileRef = 05D57D16346FAF830F1F82067307EA9F /* default.css */; }; 94F5846979DBC90E2B646E975F5ECBA8 /* DTHTMLWriter.h in Headers */ = {isa = PBXBuildFile; fileRef = 6B96B0998B49D8E149C995474DA5FE5F /* DTHTMLWriter.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 9504C9FFDCBBF6F619C349CB657093A1 /* default.css in Resources */ = {isa = PBXBuildFile; fileRef = 05D57D16346FAF830F1F82067307EA9F /* default.css */; }; + 95B0E663F5F3811B4241E6EE55CFC080 /* RDEPUBPageResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD75C2171FA5F398BB9876FBECBE3002 /* RDEPUBPageResolver.swift */; }; + 95BD0D6223ECC3F10719042A77CFEFA7 /* RDReaderGestureController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45989DEA616D69D14306213B39E79FD6 /* RDReaderGestureController.swift */; }; 96507B8910C8C9DE0B221CFF0E8518D0 /* Pods-ReadViewDemo-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = EBA0272559B0F7FAD8AC670AC7431188 /* Pods-ReadViewDemo-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 96AB626B9C20C3A6512835EC71A4F9FD /* RDEPUBPageInteractionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA31C1F2D2AF87B527C261FF3EF62C58 /* RDEPUBPageInteractionController.swift */; }; - 973D5F77AA6D4F77CEF20353736DA403 /* RDEPUBFontNormalizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AEE00544B5F5C69A1636474C8E76690 /* RDEPUBFontNormalizer.swift */; }; + 975BB1BADB85F508E55EA30545F66E5A /* RDEPUBTextPaginationSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = D366C7AF049977D42FB1B20524EB0B15 /* RDEPUBTextPaginationSupport.swift */; }; 9854DC3F1763B6272C2EA3FC6EC40B95 /* NSAttributedStringRunDelegates.h in Headers */ = {isa = PBXBuildFile; fileRef = E20272295EC253826AEE1F83A15D47C5 /* NSAttributedStringRunDelegates.h */; settings = {ATTRIBUTES = (Public, ); }; }; 987538B07E11741899878EA8BC947F51 /* NSCoder+DTCompatibility.h in Headers */ = {isa = PBXBuildFile; fileRef = 354C5DD0ACC742762200A640ADA859C6 /* NSCoder+DTCompatibility.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9914ED7075FAF9F04BB3C4154952C9B9 /* DTFoundation-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = FCC718115E5C31C88A680522AE36B089 /* DTFoundation-dummy.m */; }; 9917E9B804CD33AF3BE2A992A3510C97 /* NSNumber+RomanNumerals.m in Sources */ = {isa = PBXBuildFile; fileRef = B7672A26922DBD934FA1AC60D49EB322 /* NSNumber+RomanNumerals.m */; }; 992B7C8C824B5EA190EF1DB096064DB7 /* DTDictationPlaceholderTextAttachment.h in Headers */ = {isa = PBXBuildFile; fileRef = F9A2565B302334B662AB51DCF8E65EEE /* DTDictationPlaceholderTextAttachment.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 99BEFA2791CD385F40B63F36FDE54020 /* ConstraintAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9593E7B37569219F8D9A504B47707DC /* ConstraintAttributes.swift */; }; + 99A97C3C5382067163801B6C0E293543 /* LayoutConstraintItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E8C0D83EB6EDE71B264B6B0DD4644F5 /* LayoutConstraintItem.swift */; }; + 9AF05BF0F948A54F07327DD58FC203F8 /* RDEPUBTextPerformanceSampler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8607E8F20399521FFA80047941709E8C /* RDEPUBTextPerformanceSampler.swift */; }; 9B199D71D2F48B2055A7FDF9BD7051CC /* NSString+CSS.m in Sources */ = {isa = PBXBuildFile; fileRef = E27C503C8128BC3CE109FFE077AEF325 /* NSString+CSS.m */; }; - 9B739D53E63B8810FC6F307C7997A363 /* ConstraintItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28D62D3200AC3D4E42C81CDFF7E884E8 /* ConstraintItem.swift */; }; - 9BC6C98935EEE83035986E164F76BC47 /* DTLog.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B007AEEACE9EA271DD73AFD80A74D83 /* DTLog.m */; }; + 9B77D7B645764D55F450E0C7B1B423B6 /* RDEPUBFixedLayoutTemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8E247404E492652921952ECE5BA420E /* RDEPUBFixedLayoutTemplate.swift */; }; 9BD146F72B29C703798001F138B069C2 /* NSCoder+DTCompatibility.m in Sources */ = {isa = PBXBuildFile; fileRef = 9129D37C7F89AD8FD530663BB4503FC0 /* NSCoder+DTCompatibility.m */; }; + 9C34AD53A9D273BBB569856B930F68DB /* RDEPUBAnnotationModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = C274719939F1335BCB8FA97636A9D2DD /* RDEPUBAnnotationModels.swift */; }; + 9D2C97270A65BF97952730D690577C42 /* RDEPUBTypesettingPipeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3EADCA56DD969174A20085845D93C409 /* RDEPUBTypesettingPipeline.swift */; }; 9DE04C83A68C7512C0A860FFF5CEC113 /* DTCoreText.h in Headers */ = {isa = PBXBuildFile; fileRef = 53F98F0F060B066CAFBCD3C85C26B2B4 /* DTCoreText.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9E4AB71DE8B3E7218B53B2C8EF6B843A /* DTAttributedTextContentView.m in Sources */ = {isa = PBXBuildFile; fileRef = F0D72C5680EBE6CA716EE77281AA4436 /* DTAttributedTextContentView.m */; }; - A0A8F3BD0C99B4713511CC6D1691976E /* DTBase64Coding.m in Sources */ = {isa = PBXBuildFile; fileRef = 061BD3911E4E530C792E65CED7868C33 /* DTBase64Coding.m */; }; - A0C7E2ACBEA4D3103EB06870D860E2BE /* RDEPUBReaderLoadCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C94F70B03585FF87A0DEF959FD7E5A6 /* RDEPUBReaderLoadCoordinator.swift */; }; - A115BC82073566E4B9E4901AD77705B4 /* RDEPUBReaderHighlightsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD245FC216DD480CF148530003BAA4BC /* RDEPUBReaderHighlightsViewController.swift */; }; - A118C8C3DDFA7F01BD8CD0BD57C74098 /* wxread-dark.css in Resources */ = {isa = PBXBuildFile; fileRef = 2FBA8F4FD78EAB6A6E4011D92577EA42 /* wxread-dark.css */; }; - A12A29C8B64DAB94E6013711400452B1 /* Data+Serialization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DC78D0B0F37BF9AD1058CACA9AAF9BE /* Data+Serialization.swift */; }; + 9E85B0E4906F782371E049B67FB5C0C7 /* RDReaderPreloadController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B57C7D26C0F345A62E14DAB51521E69 /* RDReaderPreloadController.swift */; }; + 9E8B7A8438F378CD94C4A766E79B8D48 /* RDEPUBAttachmentNormalizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F132DA04F32D0155954A84A4467C95CC /* RDEPUBAttachmentNormalizer.swift */; }; + 9EA64874ED208ECC988584D8D6346033 /* NSFileWrapper+DTCopying.h in Headers */ = {isa = PBXBuildFile; fileRef = 09C957F3199C596781EFA1DEA1DDCEC8 /* NSFileWrapper+DTCopying.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9F705127826BE270B99429C75CC2CA09 /* RDReaderPageChildViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6369702FFDE8929ED1931FC77B6B02A1 /* RDReaderPageChildViewController.swift */; }; + 9FD5A79D7AD9A4B886C9DED1C8A1C265 /* RDEPUBTextContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42B66EA2B45B08094B0C357085CB0846 /* RDEPUBTextContentView.swift */; }; A132A0C9EB13B65FB886B11AA2E6304C /* NSDictionary+DTCoreText.h in Headers */ = {isa = PBXBuildFile; fileRef = 27205D159B39090908CA0E9C3997B784 /* NSDictionary+DTCoreText.h */; settings = {ATTRIBUTES = (Public, ); }; }; - A15106734ACDCD5AA02C4F67C8F0F2B2 /* ConstraintMakerFinalizable.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0B32EA4B7A492EEF26CE260E9AEFA0B /* ConstraintMakerFinalizable.swift */; }; + A21BA505BD695436CEC13BAB790CD31C /* RDReaderFlowLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3346B9CF2882D4D658CE30CDCCC93CA /* RDReaderFlowLayout.swift */; }; + A2C79624120C79D72EAAB81E586355E8 /* RDEPUBBookPageMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DEAA0384B504309C6256D3F9FF428AE /* RDEPUBBookPageMap.swift */; }; A2CA9C27EB2B77637A12723F8CFE7AAB /* NSString+HTML.m in Sources */ = {isa = PBXBuildFile; fileRef = 4428633C8EC3F6289CCF376F588CE0C4 /* NSString+HTML.m */; }; - A2F0139EB87218A9361BB1F85800192A /* Debugging.swift in Sources */ = {isa = PBXBuildFile; fileRef = D495E2AB1223BD4D4909F8FF1FF47DAF /* Debugging.swift */; }; - A3045667F74EFACBD3F29F46BFB4EBC0 /* RDEPUBTextPositionConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 476A874AD38352C8F072379DDDEBE8CD /* RDEPUBTextPositionConverter.swift */; }; A30D5C1E413A3D319A83D3410A8A549F /* DTCoreTextParagraphStyle.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F760D62F6C6F083374702146337F2CB /* DTCoreTextParagraphStyle.m */; }; - A37500E37BE9E26DA5EE55916224DD7E /* FileManager+ZIPDeprecated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04F9B298989305B9569B71E448E8C1D4 /* FileManager+ZIPDeprecated.swift */; }; - A43A87703C16488B17C5871E7398371B /* RDEPUBTextPaginationInterfaces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2412CF0CE63E83F2F0EC492D3256F9 /* RDEPUBTextPaginationInterfaces.swift */; }; - A6A66002BC974BBD05385DC49E9A4CD6 /* NSURL+DTAppLinks.h in Headers */ = {isa = PBXBuildFile; fileRef = 557DA029E15EC99631B647AE8922007F /* NSURL+DTAppLinks.h */; settings = {ATTRIBUTES = (Public, ); }; }; - A6C184213CB62062887604E95FCEC014 /* RDEPUBRenderDiagnosticsCollector.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB0484A107F418C2B5E2944F8BA3C1E6 /* RDEPUBRenderDiagnosticsCollector.swift */; }; + A37720F711BDB1D915B6DAC0BCD30D75 /* SSAlertView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F410ED25E4E84C3D5166261CDF4B58F2 /* SSAlertView.swift */; }; + A43DAC6C9656DF7A74DCC11AB44433B5 /* NSMutableArray+DTMoving.m in Sources */ = {isa = PBXBuildFile; fileRef = 53499F40E1790A6CBE8AFEB3AC5BEFC8 /* NSMutableArray+DTMoving.m */; }; + A44F075844A381058ABD89158C77063E /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B50104DE2E35D820ECA22CDB3E6F96EA /* Foundation.framework */; }; + A633C89374604FFFE5119DA09B8C5088 /* NSURL+DTComparing.m in Sources */ = {isa = PBXBuildFile; fileRef = 9BFCAC7C2AEA867E67C63A852576774F /* NSURL+DTComparing.m */; }; + A6BAD47DA59DF0BAB9319402D2B5F966 /* RDEPUBWebDecorationOverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A64FCFADABD1D922738FCDF1220C149 /* RDEPUBWebDecorationOverlayView.swift */; }; + A6BD6C9B1C25EE75972456195FC19DCE /* RDEPUBTextIndexTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEB1B8F778B5A8442BF121C13E5E460A /* RDEPUBTextIndexTable.swift */; }; A72E9F983E5BBFA7485EFB482B80E89E /* DTWeakSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 5155CCDD61E030184249A020FE5D74B0 /* DTWeakSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; - A7AFFAFDE55A53049223F920254CAF86 /* ConstraintInsetTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32A5ABB8F3656A110B955B0306E9A8C3 /* ConstraintInsetTarget.swift */; }; + A7B1B8292DED55A66CBAD34CA75D78F4 /* RDReaderView-RDReaderViewAssets in Resources */ = {isa = PBXBuildFile; fileRef = 83410CC9CF2ABE63B90A92F2F988BF65 /* RDReaderView-RDReaderViewAssets */; }; A7D665BBDADBE3D64023FF52E218FCFA /* DTCoreTextConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = B2B3D0CB47B0A8E9EC5AF48B7F2DD6B0 /* DTCoreTextConstants.m */; }; - A7F239422F3768F1B4B3A696DD485D3B /* SSAlertSwift-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 2DE4FA5133891593CB45AF36B8EBF60C /* SSAlertSwift-dummy.m */; }; - A8BDFF208A28D6E52EDD9138290A3708 /* RDEPUBNavigatorState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AE2A1B83DCC99894642D65CE5EADFD4 /* RDEPUBNavigatorState.swift */; }; - A8F6AFC87576314D31AC08581890E4CD /* RDEPUBReaderController+DataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B8F6026114BC156F5EAC3906C3DAD24 /* RDEPUBReaderController+DataSource.swift */; }; - A9BDCA1E1104F38F9823FF2F9D06FE7E /* RDEPUBTextPaginationSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = D366C7AF049977D42FB1B20524EB0B15 /* RDEPUBTextPaginationSupport.swift */; }; - AA2ADBCF2640D8FF5BD8D071271284E2 /* SnapKit-SnapKit_Privacy in Resources */ = {isa = PBXBuildFile; fileRef = B9DCB5EC0B1CDADD221717CADDF62359 /* SnapKit-SnapKit_Privacy */; }; + A8CF70CDE97B17D75D7F07C6065748A8 /* RDEPUBPaginator.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA479EAA368CC8B528282E49B6C96BC8 /* RDEPUBPaginator.swift */; }; + A92A1BD4244164E33EFC993B7E980CE9 /* RDEPUBReaderChromeCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F6EEB30082AC5F62EC78FB49D9055B5 /* RDEPUBReaderChromeCoordinator.swift */; }; + AA2535B6492261202B0A992C6C9D9134 /* SSAlertDefaultAnmation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81B8F42440B802E6EC2B1FF7E3022F3A /* SSAlertDefaultAnmation.swift */; }; AA3722CC8B4992EDA8A9F5E9116835B7 /* DTCoreText-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 015F3EE69253C66E11B931E3360BA5FE /* DTCoreText-dummy.m */; }; - AB9713F8457EB1C14E53C76F0E442D82 /* DTExtendedFileAttributes.h in Headers */ = {isa = PBXBuildFile; fileRef = 538572A5C098252D629F2185F1B1C9E8 /* DTExtendedFileAttributes.h */; settings = {ATTRIBUTES = (Public, ); }; }; - ABB8DE00DCF34B2ED79C27585E97D5FB /* RDEPUBParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC9B5052FF6F4941CE226FF90ED457DE /* RDEPUBParser.swift */; }; + AA74956FA73553CC4DF712E3C11012B3 /* RDEPUBPaginationCacheCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20AA41D781113C9A30CD0E9EDD6835D5 /* RDEPUBPaginationCacheCoordinator.swift */; }; + AB093C9480101EB0EF702C7347F408AA /* Archive+BackingConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61E36C4FE448DDDA03450847FCB785B6 /* Archive+BackingConfiguration.swift */; }; + ABBDB23194AD9C4CABC7B670DF17BB1F /* RDEPUBSearchModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8830746779660E908FADB2DEAD879AA2 /* RDEPUBSearchModels.swift */; }; AC7000465258822B466855B12F1F97A3 /* DTCoreTextGlyphRun.h in Headers */ = {isa = PBXBuildFile; fileRef = 49A21AE1A0AB5383F895B34EA7D6973D /* DTCoreTextGlyphRun.h */; settings = {ATTRIBUTES = (Public, ); }; }; - AC787FA868EAD10F9929927B2AEBD247 /* RDEPUBJavaScriptBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04C13EA061DD9866E60F683AD49EE945 /* RDEPUBJavaScriptBridge.swift */; }; - AD103B6A562A487B79DDB6617889D429 /* DTFoundation-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 5FAB3C7F19A511C2B1B7DFBA3CA308C4 /* DTFoundation-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; - AD3F24E834B5215C632C984EFD959D10 /* RDReaderView+CollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7D36A8CADACEF369077B844EA911D0 /* RDReaderView+CollectionView.swift */; }; - AD4B914F1D725723EF1927B09FD6CC64 /* DTPieProgressIndicator.h in Headers */ = {isa = PBXBuildFile; fileRef = 1B443D035BA5652B03A41BE905C5AC17 /* DTPieProgressIndicator.h */; settings = {ATTRIBUTES = (Public, ); }; }; - AE2EA185BE47AA7636E4F927C1B220C7 /* RDEPUBReaderPaginationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D246461688CA3E0592B5536C95A624A /* RDEPUBReaderPaginationCoordinator.swift */; }; - AF261667F646799AC6B2D780044128B8 /* ConstraintMakerExtendable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5245010774F8F1C4857763CC7B96E7B4 /* ConstraintMakerExtendable.swift */; }; + ADF2A134862417950DB9864F86255CFC /* RDEPUBReaderConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 138A4AA21D93424C026C8557FF6F387E /* RDEPUBReaderConfiguration.swift */; }; + AE4E7B950DA8CB471D88477C5FCEC6F9 /* ConstraintDescription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6578DFAB51436E8DF266FD087700E26B /* ConstraintDescription.swift */; }; + AEA089F602E66B4E0CB655D1BF625880 /* DTActivityTitleView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4EEFAE80193E1F0DA53400C9AD641410 /* DTActivityTitleView.m */; }; + AF1CFF8A037F62C86652D4A6A7FA7152 /* SSAlertAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D2EC0B845131A88247902AEA26F40AE /* SSAlertAnimation.swift */; }; + AF5E584EB1F14EC57A5DC51D5CD93FF2 /* NSString+DTURLEncoding.m in Sources */ = {isa = PBXBuildFile; fileRef = CB7F3535507F56824BA21509302CF8B3 /* NSString+DTURLEncoding.m */; }; + AF6C446DF273E65DD84158080219FAEE /* RDEPUBChapterRuntimeStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = F61AF0B2B0B1702EDC7034AC01F593B5 /* RDEPUBChapterRuntimeStore.swift */; }; AF7A1DC953992A149CB0815C90769529 /* DTIframeTextAttachment.h in Headers */ = {isa = PBXBuildFile; fileRef = 3B931AB189BDDE2CCE8578B50BD60AF0 /* DTIframeTextAttachment.h */; settings = {ATTRIBUTES = (Public, ); }; }; - AFCBCBAE8B0C6A6EF6A6832A7213DC0D /* SSAlertViewExtention.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2280F2302F54E90122747821FE681543 /* SSAlertViewExtention.swift */; }; - B00DB012B30976FE2972CC22585AE9DE /* ConstraintView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6DB34ACB132E465CDF8191F7DB71EF6 /* ConstraintView+Extensions.swift */; }; - B16CC6DF8233C4E37B30A83420E10597 /* SnapKit-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 9CE078B465FDEF40EC0469626A2C2469 /* SnapKit-dummy.m */; }; - B18755975A9D04D3DB2732447D98CCB4 /* UIViewFrameExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8C351514678D41C58869BD057D5483F /* UIViewFrameExtension.swift */; }; - B2065C3E8DA14CE92675506BBC1A71F2 /* RDEPUBReaderTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 862E061063601D25DB09C808CA390C18 /* RDEPUBReaderTheme.swift */; }; + AFE83331052EC5073AFAE58AA60D94A2 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 53D473C6F07982D3D25781A343EC5A99 /* PrivacyInfo.xcprivacy */; }; + B0984EA45A82B8CF021C4BA7CE6B2473 /* DTCoreGraphicsUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = A8DFDCAB69B471EB5667C964865C26CF /* DTCoreGraphicsUtils.m */; }; + B0D846AEE6C2E73505BD1C3067FEDD0F /* Entry+Serialization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F991240340FA026C71B68EB25B9F9C8 /* Entry+Serialization.swift */; }; + B1AC7E9FAE650F2D3CB2F0372A70A2EC /* SSAlertCommonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE4EE750340347ED584C5A2C1164331A /* SSAlertCommonView.swift */; }; + B21AC2FBB25F091A5AE9A0602A9F4937 /* NSURL+DTAppLinks.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D36F7CDCD7D5788CDCB3A1421DB242B /* NSURL+DTAppLinks.m */; }; B24FAD34A7915FC4B90FE9038E814FF2 /* DTLazyImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = A4E43DCDE0C8751557E7E65FEEB1C865 /* DTLazyImageView.m */; }; - B4F34BAC839565D3D79B44FD9930560F /* RDEPUBAttachmentNormalizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F132DA04F32D0155954A84A4467C95CC /* RDEPUBAttachmentNormalizer.swift */; }; - B678CD12611B4C83E3AF88FF80B8D092 /* RDReaderView+PageCurl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A525BB5B1708520D409C1A30CF1CD2D /* RDReaderView+PageCurl.swift */; }; - B686B485ACD5C39FEA878C32F4741AD9 /* RDEPUBReaderToolView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE03724A9B7893A98793AE13319755A1 /* RDEPUBReaderToolView.swift */; }; - B7A73A26B8B88977C3DDC07D559EAAEC /* UIScreen+DTFoundation.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F051D813D50D20D53205D5FDCAA877C /* UIScreen+DTFoundation.m */; }; + B3908764711A87E5232E4F972AA70AB0 /* ConstraintMakerPrioritizable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E138EC76D0DF24544D29D1A4D7429E3 /* ConstraintMakerPrioritizable.swift */; }; + B3D973551DB2F6E6CE010126F4F4A4A3 /* RDEPUBTextPaginationInterfaces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2412CF0CE63E83F2F0EC492D3256F9 /* RDEPUBTextPaginationInterfaces.swift */; }; + B5196E661263405C836741490F0535ED /* RDEPUBTextBookCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE13C3BA99810308E47EC53C68F6169A /* RDEPUBTextBookCache.swift */; }; + B7A8736945B52D9330D687AFB4665FBE /* SSAlertAnimationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97995C73297871C353364E685939DED9 /* SSAlertAnimationController.swift */; }; + B7B20B657F020B36BD9AA46F0E7B72BF /* RDEPUBParser+TOC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 137B4BC4EAE7AE0D9567E53EBBBA687E /* RDEPUBParser+TOC.swift */; }; B7BDD79FC06A1E4E0E17592860844108 /* NSAttributedString+DTCoreText.h in Headers */ = {isa = PBXBuildFile; fileRef = FF3F7BA20A1213804048557D946CEEA9 /* NSAttributedString+DTCoreText.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B7F802838B7854ECF8270F92D6A7DA3D /* SSAlertSwift-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 2DE4FA5133891593CB45AF36B8EBF60C /* SSAlertSwift-dummy.m */; }; B8332F36582F7A001FE85A8D54B27C9E /* DTCoreTextParagraphStyle.h in Headers */ = {isa = PBXBuildFile; fileRef = 5D62C5531667F364F14DB67D33FFA554 /* DTCoreTextParagraphStyle.h */; settings = {ATTRIBUTES = (Public, ); }; }; - B85FE04FC7958BAB89406AD289D1D018 /* RDPlainTextBookBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5341C765E36D0B172F2F8D4812829521 /* RDPlainTextBookBuilder.swift */; }; B86493B996A888D1DA7814ECB6028E22 /* DTHorizontalRuleHTMLElement.m in Sources */ = {isa = PBXBuildFile; fileRef = A68D72764140BC3DF67A2B9E52CEC7A4 /* DTHorizontalRuleHTMLElement.m */; }; - B883A5F1131F3F9776604F3F9452AC65 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B50104DE2E35D820ECA22CDB3E6F96EA /* Foundation.framework */; }; B94CD41727DFCA8791EED8FB2CA397DD /* DTCompatibility.h in Headers */ = {isa = PBXBuildFile; fileRef = 2702E05AA7A266B7D8D0F881FD4E09DA /* DTCompatibility.h */; settings = {ATTRIBUTES = (Public, ); }; }; - B96925D97DBA16A527D4FB87673AFF4E /* RDReaderView-RDReaderViewAssets in Resources */ = {isa = PBXBuildFile; fileRef = 83410CC9CF2ABE63B90A92F2F988BF65 /* RDReaderView-RDReaderViewAssets */; }; - BB782BBE8587EDB13C294A81E7F36225 /* SnapKit-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 0B10D3EC1701BBA4D69926915F942558 /* SnapKit-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; - BBF0C5F3F558E5C82C28F9F2885428B2 /* NSMutableArray+DTMoving.h in Headers */ = {isa = PBXBuildFile; fileRef = B0AC307F12521444C973EA0DE38B4608 /* NSMutableArray+DTMoving.h */; settings = {ATTRIBUTES = (Public, ); }; }; - BC7BDDC881DED4124CD0C95A9B170A30 /* RDEPUBBuildDiagnosticsReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9D07EA99A4878E9F8A8839F19056654 /* RDEPUBBuildDiagnosticsReporter.swift */; }; - BCB700C605AF5689D526F24D02B97BFA /* RDEPUBReaderSearchCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3156D6DC256E20EF49691134C4342473 /* RDEPUBReaderSearchCoordinator.swift */; }; + BA5481B3D54EE86C9ED64289A050AB98 /* RDEPUBPageBreakPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 117036F7DB9A22578522D0B397C28F26 /* RDEPUBPageBreakPolicy.swift */; }; + BBB18335BC72FBF4584F8B5F4132CCBE /* NSString+DTURLEncoding.h in Headers */ = {isa = PBXBuildFile; fileRef = 31BD7533867336DEA9B835F471767178 /* NSString+DTURLEncoding.h */; settings = {ATTRIBUTES = (Public, ); }; }; + BBC18499E4D786E92B516E008ACEAD0F /* SSAlertViewExtention.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2280F2302F54E90122747821FE681543 /* SSAlertViewExtention.swift */; }; + BCC7E85D6E6F8DC53F922E94186C034F /* RDEPUBStyleSheetBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 164CFAED45E63A7592580A342D44A43E /* RDEPUBStyleSheetBuilder.swift */; }; + BDB9C5E0D1DEB73AC0C596080A4706C8 /* WeReadApi.js in Resources */ = {isa = PBXBuildFile; fileRef = ECDEDEE1D78BA473378CB3D26C486E5B /* WeReadApi.js */; }; BE12CD112F82AD9050D7FB29395F27F9 /* DTVideoTextAttachment.m in Sources */ = {isa = PBXBuildFile; fileRef = 63A6EDF10DA5A051C6CDCBE5D97AF11E /* DTVideoTextAttachment.m */; }; - BE67CF1248E014FD0397C32C8EC6D868 /* RDEPUBTextAnchor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69F083BF98854C4CB74C7C21DA34399A /* RDEPUBTextAnchor.swift */; }; - C078EB1EF2AD0AB675C64A3B920CDE8D /* RDEPUBChapterPageCounter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 694DC251BF921B37330A35A6ACD9DF62 /* RDEPUBChapterPageCounter.swift */; }; - C0ADCA6F77E50FEDD72E2EF8FFA5B9A1 /* DTFoundationConstants.h in Headers */ = {isa = PBXBuildFile; fileRef = FD03567681A79FFB6E81B3105503065D /* DTFoundationConstants.h */; settings = {ATTRIBUTES = (Public, ); }; }; - C100C86A98BE2D71D4DCC0C14BC9C933 /* DTBlockFunctions.m in Sources */ = {isa = PBXBuildFile; fileRef = 375179DD3A4576CEF947E1760BBDC69B /* DTBlockFunctions.m */; }; - C107081396894ED1035195055E1D03D4 /* RDEPUBReaderController+RenderSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74F9C4D562F4650166F650997ADA132B /* RDEPUBReaderController+RenderSupport.swift */; }; - C10B9F0291FFE41EBE7AF0CE975B3EB8 /* RDEPUBTextLayoutFrame.swift in Sources */ = {isa = PBXBuildFile; fileRef = 141325B9AB28E266F40552E4F82F952D /* RDEPUBTextLayoutFrame.swift */; }; - C1730B2336E26354E273E454C18E48A5 /* ConstraintMaker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60657D08CCDB38BFED30BD40F4720CE4 /* ConstraintMaker.swift */; }; - C252D4D3D440861EA6122DF66EB33500 /* DTHTMLParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 32FB2B1B0498DCBE7B79BDF49E963E3D /* DTHTMLParser.m */; }; - C37E74CF4A213BB3E7AE31B676812BD9 /* RDEPUBPageBreakPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 117036F7DB9A22578522D0B397C28F26 /* RDEPUBPageBreakPolicy.swift */; }; - C48E39E0973F3D272FF1B3A23A43A4A0 /* Entry.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDD081272B47A3B73CE693D21DC26827 /* Entry.swift */; }; + BE5E8F13438BA43EC5C46A50A2050F94 /* ConstraintView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A02EA0374C693F881AB36202FB276DCD /* ConstraintView.swift */; }; + C0106570EB1C66C2173ACECEAE177C05 /* RDEPUBParser+Archive.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9087CF398AFBBC8FFB5C60E6389EAB1 /* RDEPUBParser+Archive.swift */; }; + C065855BFB7C44A88BC0E41EDB67F138 /* DTCoreGraphicsUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = B6425B2235BA00FE865AF58EF44F1144 /* DTCoreGraphicsUtils.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C14C1AD3FD28ED7A8D1B8E9DA8BAA819 /* ConstraintRelation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3593ADF561D422F73C8EABAD4A4F440 /* ConstraintRelation.swift */; }; + C1E9EF7FE2EAA2A3929CB6D1A4D9CF01 /* RDEPUBReaderContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 006F2BEF2BA2A518F2C533B76FC7D3BC /* RDEPUBReaderContext.swift */; }; + C205D66400B80CD0D0680CEF8A1E998F /* FileManager+ZIP.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27B7D3C6F0C845E3D42BBA613FFB0465 /* FileManager+ZIP.swift */; }; + C2ECF1EDB8CEB6B610857A85E076358A /* Data+Serialization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DC78D0B0F37BF9AD1058CACA9AAF9BE /* Data+Serialization.swift */; }; + C48FCC94B7C4CD5EE702D91ACAF01DE1 /* RDEPUBReaderBottomToolView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D0E429D0360BC3A87BBA2697DA47F3F /* RDEPUBReaderBottomToolView.swift */; }; + C4D34C7A8F84569250722CBC6334DAD8 /* Archive+ZIP64.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED5693BD171B879406EA14C97F4AD8D8 /* Archive+ZIP64.swift */; }; + C4FE61E1B0CFE8B352FDD7114CCBD7C1 /* RDEPUBReaderTopToolView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 105633F335811C52B7411E5AA1A6A0BC /* RDEPUBReaderTopToolView.swift */; }; C5EE8E7373FBD24E66C678356393D871 /* DTCoreTextFontCollection.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E791886E59DC0FAB02C6BBAB5BE5B6B /* DTCoreTextFontCollection.m */; }; + C6773380BF42C95B3016CDF91D5F58DC /* ConstraintMultiplierTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09BE22EE711B7A38A4443E1C1C12BD79 /* ConstraintMultiplierTarget.swift */; }; C6B65C0EA759DF426FBBBB113BA3C3E8 /* DTBreakHTMLElement.m in Sources */ = {isa = PBXBuildFile; fileRef = F734214E067BDBDB581911E1A5CDAADB /* DTBreakHTMLElement.m */; }; + C6DEF90A63E54E5B9C32A944669BC079 /* Archive+ReadingDeprecated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 934F9BB2B3A313294F121484C3A97A14 /* Archive+ReadingDeprecated.swift */; }; C72CE4B974DFAE2C3037D425EB559314 /* DTCoreTextLayoutFrame.h in Headers */ = {isa = PBXBuildFile; fileRef = CE6907AC04870132CCE3177E894607DD /* DTCoreTextLayoutFrame.h */; settings = {ATTRIBUTES = (Public, ); }; }; - C75B12810764BCA70B81F9A3C4A562C1 /* RDEPUBAssetRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98BFF67943C8CABB6171A6EDC7639F82 /* RDEPUBAssetRepository.swift */; }; - C86AF629D40E46FD947662786FAEA40C /* DTVersion.m in Sources */ = {isa = PBXBuildFile; fileRef = 4AF6156128A1A0CA24D9D06E346B9445 /* DTVersion.m */; }; + C72E734A93614CEE60A20608F6C93968 /* RDEPUBReaderRuntime.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9511E297D368C83173C273682467DC17 /* RDEPUBReaderRuntime.swift */; }; + C7DBD197D7587C18D4572B6BC29AC583 /* wxread-dark.css in Resources */ = {isa = PBXBuildFile; fileRef = 2FBA8F4FD78EAB6A6E4011D92577EA42 /* wxread-dark.css */; }; + C8E9353276833F3362D26F17A2FF2939 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B50104DE2E35D820ECA22CDB3E6F96EA /* Foundation.framework */; }; C8F753B56A6C4D2A938B6EA68CF522F8 /* DTColorFunctions.h in Headers */ = {isa = PBXBuildFile; fileRef = 86A0361390AA0C29BC1A5487083A12A7 /* DTColorFunctions.h */; settings = {ATTRIBUTES = (Public, ); }; }; C9DD05EF2D27FFEBFA7BF6F437168501 /* DTCoreTextConstants.h in Headers */ = {isa = PBXBuildFile; fileRef = C8C20D9A71E6605DEE80342E7122B683 /* DTCoreTextConstants.h */; settings = {ATTRIBUTES = (Public, ); }; }; - CA0C55A7CB67DBA313171CF5AF49A349 /* RDEPUBDTCoreTextRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3A5FB560E84A3FE29C291466EAE9E22 /* RDEPUBDTCoreTextRenderer.swift */; }; - CC52D4A2D391BFE5D9F6F39DB6913757 /* DTCoreGraphicsUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = A8DFDCAB69B471EB5667C964865C26CF /* DTCoreGraphicsUtils.m */; }; + CA06F7E3629174D7D2B0E35E98644153 /* Date+ZIP.swift in Sources */ = {isa = PBXBuildFile; fileRef = F76A97683FB5E8E8B86EA4AD71F6F4B4 /* Date+ZIP.swift */; }; + CABB55F416696E222EDD9355BE162423 /* DTCompatibility.h in Headers */ = {isa = PBXBuildFile; fileRef = B5FCE04EBB553439D1C2BA1001936293 /* DTCompatibility.h */; settings = {ATTRIBUTES = (Public, ); }; }; + CB2659A3D443956DA142AA4EE5491E9E /* RDEPUBTextBookBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ED26E7B899A0B29A202F0CB011D5F09 /* RDEPUBTextBookBuilder.swift */; }; + CB2BDC02AA1677865896F9107B74F6BA /* RDEPUBReaderTableOfContentsItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB41DCB393D82A80671953233E936F5F /* RDEPUBReaderTableOfContentsItem.swift */; }; CC8B8F3D758E98B51F69EDFAA69663A4 /* NSString+HTML.h in Headers */ = {isa = PBXBuildFile; fileRef = 8ABDEFEA94DC6408D92AC66DA3625DE5 /* NSString+HTML.h */; settings = {ATTRIBUTES = (Public, ); }; }; - CCF3600DBACB7361D2130AAFD7A6A83D /* UIImage+DTFoundation.h in Headers */ = {isa = PBXBuildFile; fileRef = 15941683D394FB8D7EAE91CD2CF03FF1 /* UIImage+DTFoundation.h */; settings = {ATTRIBUTES = (Public, ); }; }; - CE5C346EF640EA27FE463DBC8D1E1C88 /* RDReaderFlowLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3346B9CF2882D4D658CE30CDCCC93CA /* RDReaderFlowLayout.swift */; }; + CD582808F975840BD50B8E2CE6F0F3AC /* ConstraintConstantTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F090A0289338B14050FAF16C22605B0 /* ConstraintConstantTarget.swift */; }; + CE3626DDC69337BFB7B0285FFBA5D9F4 /* RDEPUBChapterDataCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECED12E92EAFEB345573782B83100DD3 /* RDEPUBChapterDataCache.swift */; }; + CE5D85573015B5EF9AA8A9F512239835 /* NSURL+DTUnshorten.h in Headers */ = {isa = PBXBuildFile; fileRef = 054674EB90C625BB4BEA9368ED92592B /* NSURL+DTUnshorten.h */; settings = {ATTRIBUTES = (Public, ); }; }; CFC49467FE75BFABC22A1CE2FFA26570 /* DTImage+HTML.m in Sources */ = {isa = PBXBuildFile; fileRef = 12A5EA26EE7539EDDA90649D22FB675D /* DTImage+HTML.m */; }; - CFCACF1D9A6846A81C7F50C8E6A23F70 /* RDEPUBTextSelectionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A42EA2D403B179C805701DD3638696B0 /* RDEPUBTextSelectionController.swift */; }; - D02DF9F9FB5549E3D0A75B88BF9A30A8 /* DTLog.h in Headers */ = {isa = PBXBuildFile; fileRef = 6A1EBB9497F4846C84CA0BF5E4F84FC7 /* DTLog.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D031693A1CC970AC82EF0835E9DAD65D /* RDReaderContentCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 291992715B24D4645FF6E45D0881635F /* RDReaderContentCell.swift */; }; - D0F53E98858850D4E7537E7BD40E8796 /* LayoutConstraint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 932FD688DE493323BA6B691BC2CB5094 /* LayoutConstraint.swift */; }; + D014F52A4F15AB1D66EA29D55049DB3C /* ConstraintAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9593E7B37569219F8D9A504B47707DC /* ConstraintAttributes.swift */; }; + D14F24CEC26C7EDAFE40D9BAEED5BC48 /* RDEPUBReaderController+ContentDelegates.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6E22461422D4C78C300054E36E701BE /* RDEPUBReaderController+ContentDelegates.swift */; }; D1A93B2D7F1B312755C48790338D7712 /* DTIframeTextAttachment.m in Sources */ = {isa = PBXBuildFile; fileRef = AA3CFD777AD76F97EBA05B3920F5786A /* DTIframeTextAttachment.m */; }; - D1B22560078A0D3A65D0427B3A0A38DF /* RDEPUBPaginator.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA479EAA368CC8B528282E49B6C96BC8 /* RDEPUBPaginator.swift */; }; - D26C59ED1011B3D0A364E09AD764683C /* RDEPUBParser+TOC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 137B4BC4EAE7AE0D9567E53EBBBA687E /* RDEPUBParser+TOC.swift */; }; - D2EC76E48BBF742006A8DE10F31F1A25 /* NSDictionary+DTError.m in Sources */ = {isa = PBXBuildFile; fileRef = C18480913E002315E3F5EC6945371697 /* NSDictionary+DTError.m */; }; - D39C3766E7B664581FAD2688E295BAB3 /* RDEPUBReaderViewportMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FD217FE3F87580A8BA3BD7D8992D4EB /* RDEPUBReaderViewportMonitor.swift */; }; + D1E238CAEA8D1BE33C0D7EC543A66665 /* DTHTMLParser.h in Headers */ = {isa = PBXBuildFile; fileRef = 0D75FEDE65722CF41F7F32A164376926 /* DTHTMLParser.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D2232DDB9C06A5F370FBE1CF4F022992 /* NSArray+DTError.h in Headers */ = {isa = PBXBuildFile; fileRef = CEA93EDA6ED54D374265AA5D6B9EBBA9 /* NSArray+DTError.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D271607BFC21EB5330941673A2AA3EC8 /* ConstraintMakerFinalizable.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0B32EA4B7A492EEF26CE260E9AEFA0B /* ConstraintMakerFinalizable.swift */; }; + D4B6656FC2A1BE1C290454E8F5F073EB /* RDEPUBFontNormalizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AEE00544B5F5C69A1636474C8E76690 /* RDEPUBFontNormalizer.swift */; }; D526E03C098DA6CF8F01C1BC126C9BBA /* DTCoreText-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = CE7768E097F8BDE9FBE3752B98CD0101 /* DTCoreText-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D578954B277D170B2054CBA6081BEC4D /* DTAnimatedGIF.h in Headers */ = {isa = PBXBuildFile; fileRef = A9FBC69C7DA29A7283920FDA9EBBCE8A /* DTAnimatedGIF.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D5F33E813692FC371616921D7C4AD1FF /* RDEPUBFragmentMarkerInjector.swift in Sources */ = {isa = PBXBuildFile; fileRef = B58EB90E2A4610839F356FE9B2A97486 /* RDEPUBFragmentMarkerInjector.swift */; }; - D5F9B30BDC204B4148E3BA2D6D69B90B /* UILayoutSupport+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C647A2EB2720BCBF88B160D124B57BF /* UILayoutSupport+Extensions.swift */; }; - D6104499A1F639473F78E573D24672F1 /* ConstraintMakerPrioritizable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E138EC76D0DF24544D29D1A4D7429E3 /* ConstraintMakerPrioritizable.swift */; }; - D61D512B689DBF87FCF457C01594871A /* RDEPUBWebView+Reflowable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8067F377315BFA008F9F0795CCB68D5F /* RDEPUBWebView+Reflowable.swift */; }; - D77ADB50A40D2C35FAA905E24838929B /* RDEPUBWebDecorationOverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F616DAE29591FE9EC5FF94F4A922B115 /* RDEPUBWebDecorationOverlayView.swift */; }; + D551A1BDF73D7A1A2ABF4EB10FB8E3B6 /* ConstraintView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6DB34ACB132E465CDF8191F7DB71EF6 /* ConstraintView+Extensions.swift */; }; + D5ECE92F691B7A13E36D275A9BDA4B9C /* Archive+MemoryFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 174FC0CA05D20BB995E211DE5A0857DB /* Archive+MemoryFile.swift */; }; + D7CDAE3323835E7AEE99756733853939 /* Entry.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDD081272B47A3B73CE693D21DC26827 /* Entry.swift */; }; D82DDA4C1E5C4D5378A9E5EA3F8E9DFE /* DTBreakHTMLElement.h in Headers */ = {isa = PBXBuildFile; fileRef = 533CFFF04BE7BC4BDEAE11DF675A84CA /* DTBreakHTMLElement.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D886581273ADE0CC6CA73210AA2A8740 /* NSString+DTUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = DCECDBD73595CA88A2208000B4B68352 /* NSString+DTUtilities.m */; }; - D9B2103AEF9770DFFED080731A236489 /* DTActivityTitleView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4EEFAE80193E1F0DA53400C9AD641410 /* DTActivityTitleView.m */; }; - DA009A1B7AF4A6BB397EF6A0D54199EB /* ConstraintMakerRelatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD768C27E392C934C06DFB4AC401037C /* ConstraintMakerRelatable.swift */; }; + D84A04E28FE29A9276BD086C1B0973C1 /* RDEPUBReaderController+TableOfContents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C19BCB3C801B3E6D0A09384A7ACE1FD /* RDEPUBReaderController+TableOfContents.swift */; }; + D9F35C0FC6B7F766BC53C652AF9BDAFF /* RDEPUBTextSelectionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A341139B59F0CD6D051CABB66C8DC49C /* RDEPUBTextSelectionController.swift */; }; DA5B51E5D80C161D2902B6FC60DA5EC8 /* DTImageTextAttachment.h in Headers */ = {isa = PBXBuildFile; fileRef = 426D442C01742E4361F5BC34D43152A6 /* DTImageTextAttachment.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DABA6DF4CDB6C3098DDB1972D9B8D47F /* DTExtendedFileAttributes.h in Headers */ = {isa = PBXBuildFile; fileRef = 538572A5C098252D629F2185F1B1C9E8 /* DTExtendedFileAttributes.h */; settings = {ATTRIBUTES = (Public, ); }; }; DAD16D3E6EBB32FF96C63BFE6037065F /* DTLinkButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A1B75050E71BDD96FC19EC6BA129E6A /* DTLinkButton.m */; }; DBCA42F7E1697F4EC82E0E7C9657851D /* DTAttributedTextCell.h in Headers */ = {isa = PBXBuildFile; fileRef = D4093D30B372960168C1349100DF7DE0 /* DTAttributedTextCell.h */; settings = {ATTRIBUTES = (Public, ); }; }; - DBF7631687BF37FCD33C20E23D6C291C /* UIApplication+DTNetworkActivity.h in Headers */ = {isa = PBXBuildFile; fileRef = 957DB4675D5CCBD3CB1762F08619F3F7 /* UIApplication+DTNetworkActivity.h */; settings = {ATTRIBUTES = (Public, ); }; }; DC3E2932A82AD5D88D8B1EFBF004BEC8 /* DTCSSListStyle.m in Sources */ = {isa = PBXBuildFile; fileRef = B509CD4B498635D4536245E43D802ED9 /* DTCSSListStyle.m */; }; - DC5C1FECCEB131BAD26561FEBE22745C /* RDEPUBStyleSheetComposer.swift in Sources */ = {isa = PBXBuildFile; fileRef = A16842E6F1C3E8526D28C7DABA1D6898 /* RDEPUBStyleSheetComposer.swift */; }; - DD098AE376D237EBCD0602DC493FD49E /* RDEPUBParser+ReadingProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 667442978548AAC9B24B14B6F2597536 /* RDEPUBParser+ReadingProfile.swift */; }; - DD5647950937A07ACA022C75387CF527 /* ZIPFoundation-ZIPFoundation_Privacy in Resources */ = {isa = PBXBuildFile; fileRef = CA4A114775B98CEA4A566C18123B145F /* ZIPFoundation-ZIPFoundation_Privacy */; }; + DCC9AC752063FE2FB19B1C0FF3EDDB5A /* RDEPUBReaderHighlightsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F92C8ECA3907CF09E97F20AFD1F2469 /* RDEPUBReaderHighlightsViewController.swift */; }; + DD535986C7E1597F34779DF60C56A20E /* RDEPUBChapterSummaryDiskCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B39813FB9B6B3B278FF0DE60E8F41A2 /* RDEPUBChapterSummaryDiskCache.swift */; }; DD73CD0E5148961D1ABD80E050122314 /* DTCoreTextFunctions.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B031E2B903C2F177195C1C3D1D1EDB /* DTCoreTextFunctions.h */; settings = {ATTRIBUTES = (Public, ); }; }; - DDFCE8D603027FA6C70FCED85A181CA8 /* ConstraintPriority.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45C7129757B54DFD9E445D0A17D73A83 /* ConstraintPriority.swift */; }; DE68AD2F426FD462BC70F968E5AF5553 /* DTTextAttachmentHTMLElement.m in Sources */ = {isa = PBXBuildFile; fileRef = 1ABC2F552388CD08EC1EFDEF606AE222 /* DTTextAttachmentHTMLElement.m */; }; - DF39E502984126B7FFA216FC647FDB02 /* RDEPUBPageLayoutSnapshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19F9801AD3675102B14F64AFAFB99D89 /* RDEPUBPageLayoutSnapshot.swift */; }; - DF7B0A62AD5A1F39463B55A890F32E50 /* UIView+DTFoundation.m in Sources */ = {isa = PBXBuildFile; fileRef = 4219E7C32019899890CAB66E5FBDF524 /* UIView+DTFoundation.m */; }; DFB5A1C64C7908FD05BEFF7C7B571353 /* DTAttributedLabel.h in Headers */ = {isa = PBXBuildFile; fileRef = 5679D281C282896E6FC352C48E0AF439 /* DTAttributedLabel.h */; settings = {ATTRIBUTES = (Public, ); }; }; - E005DCCFE4D00062C0593F16CB0FB7C9 /* RDEPUBReadingLocationModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0311E22DAAE8E4ECEE12DFDB8A092A9 /* RDEPUBReadingLocationModels.swift */; }; - E0A64565947C7016CD15A87A29C06FE1 /* RDEPUBTypesettingPipeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3EADCA56DD969174A20085845D93C409 /* RDEPUBTypesettingPipeline.swift */; }; + DFBBFE1900812499E4548C004BF5B9F7 /* RDEPUBFragmentMarkerInjector.swift in Sources */ = {isa = PBXBuildFile; fileRef = B58EB90E2A4610839F356FE9B2A97486 /* RDEPUBFragmentMarkerInjector.swift */; }; + DFC8567EED9764161A51DB808255D041 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0834E84632886CDD9911CAB76183F1CE /* QuartzCore.framework */; }; + E199C7489AE814A75F8F8D54D8B3BFF4 /* RDEPUBReaderToolView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7EB287A47D1547ED7E637E14A5AEA64C /* RDEPUBReaderToolView.swift */; }; E1DA58105A72D6C0B77A8A9F7822BD68 /* DTCoreTextLayoutFrame.m in Sources */ = {isa = PBXBuildFile; fileRef = 029130886CE8E422D16ADF97468CB1B0 /* DTCoreTextLayoutFrame.m */; }; - E2EE910F549C5D233EB236BB480CC09D /* NSString+DTURLEncoding.h in Headers */ = {isa = PBXBuildFile; fileRef = 31BD7533867336DEA9B835F471767178 /* NSString+DTURLEncoding.h */; settings = {ATTRIBUTES = (Public, ); }; }; + E23B676D0121FF72D52379287DF8BFED /* Debugging.swift in Sources */ = {isa = PBXBuildFile; fileRef = D495E2AB1223BD4D4909F8FF1FF47DAF /* Debugging.swift */; }; + E2CF8E70EDB0332E6AFE720D8BA6254D /* ConstraintMakerRelatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD768C27E392C934C06DFB4AC401037C /* ConstraintMakerRelatable.swift */; }; + E2D1185AE98EA06E5496471130215B5F /* Archive+Deprecated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA251BAC8B430B3A4D4C4321046B990 /* Archive+Deprecated.swift */; }; + E364CAF459E15B2F4618459EF62B30E1 /* UIViewFrameExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8C351514678D41C58869BD057D5483F /* UIViewFrameExtension.swift */; }; E38FC7829B5BDD1588FA487E0FBAC208 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B50104DE2E35D820ECA22CDB3E6F96EA /* Foundation.framework */; }; - E3B9A9854AB1AB2154E1CA2C6B9E61E2 /* URL+ZIP.swift in Sources */ = {isa = PBXBuildFile; fileRef = 537BB06317FA188DF7A8A960152520E8 /* URL+ZIP.swift */; }; - E454A390799FA2F7E988A23592713FCE /* DTFoundationConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BD568D38C209BA1CC0EBFA809A35204 /* DTFoundationConstants.m */; }; - E55D6001C2F52C1C47E541F84B4C5A8A /* ConstraintDescription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6578DFAB51436E8DF266FD087700E26B /* ConstraintDescription.swift */; }; - E5AB0AA1CD3062159407E53FD7365145 /* NSString+DTUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = BE547E464E5D34F9FFB24BAC81D1E4CC /* NSString+DTUtilities.h */; settings = {ATTRIBUTES = (Public, ); }; }; - E6C30E9127E9DCDE49BDD8BBB7F81507 /* Archive+Progress.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD987C2FA7C8FBC96FD024E421DA0E68 /* Archive+Progress.swift */; }; + E41167E9986AB223798CE5F9150720F4 /* ConstraintLayoutGuide+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A9A82CB094636AFB53D6D130D6219BA /* ConstraintLayoutGuide+Extensions.swift */; }; + E52DBF03E160824F5C76EE696FED8FA1 /* RDURLReaderController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFD72846E6D1DCE0C8CF2D35F2179BB6 /* RDURLReaderController.swift */; }; + E5B7FC56FA951AD4542B3EC0EBB7C6A6 /* DTSmartPagingScrollView.m in Sources */ = {isa = PBXBuildFile; fileRef = 96EEB6B022E22A4ABC86D87469D4815B /* DTSmartPagingScrollView.m */; }; + E6D48DF9ACD5A94E7E5F9A0FAC5603B5 /* ConstraintInsets.swift in Sources */ = {isa = PBXBuildFile; fileRef = C241AD9CEC9D6732CFBA6A55B49E64C9 /* ConstraintInsets.swift */; }; + E7025D76D39D2415119AF52CC8C4717A /* RDEPUBReaderSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36855CF828CDEAE98B4B8F58CFC25485 /* RDEPUBReaderSettingsViewController.swift */; }; E8AB5CE26A5224EF09E9E363D70C7775 /* DTDictationPlaceholderView.m in Sources */ = {isa = PBXBuildFile; fileRef = AFE0B56638AFB77F4455784A7ED59876 /* DTDictationPlaceholderView.m */; }; - E8C0D5929CEA330C30F63AA5502723DD /* RDEPUBReaderController+ContentDelegates.swift in Sources */ = {isa = PBXBuildFile; fileRef = 127FB4B3B74B022CBE4054BA33CB24A0 /* RDEPUBReaderController+ContentDelegates.swift */; }; + E95E1EB90DE3754D78F341EABB8CAB4E /* RDEPUBReaderController+PublicAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C554BE53564FC720F9EBBB4B856BA87D /* RDEPUBReaderController+PublicAPI.swift */; }; EA0040823CCF8E97363DB5891F5DA6D0 /* DTCoreTextFunctions.m in Sources */ = {isa = PBXBuildFile; fileRef = 8F2D44B82815CA3397BE2C9F64819B88 /* DTCoreTextFunctions.m */; }; - EA3DDFFEE504AA55AD089305ACA3D763 /* RDEPUBTextLayouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6A32D83CDE7A6494252691BE7E99638 /* RDEPUBTextLayouter.swift */; }; - EA42097748117159D15207C9323ABDED /* RDEPUBParser+Archive.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9087CF398AFBBC8FFB5C60E6389EAB1 /* RDEPUBParser+Archive.swift */; }; - EA53228A41C6502A614D69F15A3B408A /* RDEPUBReaderDependencies.swift in Sources */ = {isa = PBXBuildFile; fileRef = 851EF2369FB225D981E94F4BB2E9F65F /* RDEPUBReaderDependencies.swift */; }; + EACD47B345480FC9DE37F1BBEBC3E6E8 /* DTCustomColoredAccessory.m in Sources */ = {isa = PBXBuildFile; fileRef = 0C6BE3B2DA9A8C4CD3760CDAD6F305A5 /* DTCustomColoredAccessory.m */; }; + EB335701169A2933C42C61AD812205A8 /* RDEPUBResourceURLSchemeHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64644C3A8F17BE731AE00482BDEE9D86 /* RDEPUBResourceURLSchemeHandler.swift */; }; EBB7E4D4DB253B5F1267CF9B4DDB4E93 /* DTCoreTextFontDescriptor.m in Sources */ = {isa = PBXBuildFile; fileRef = BCCA590DB72AA93275262A414F16578A /* DTCoreTextFontDescriptor.m */; }; - EBDF8A9891119113AA5CA45851DBD1B7 /* RDEPUBReaderRuntime.swift in Sources */ = {isa = PBXBuildFile; fileRef = 413B561A626E7BC1C78EFEFA131B92B0 /* RDEPUBReaderRuntime.swift */; }; + EC2221DF39BF4ABE42B4D74B98EE26BD /* DTPieProgressIndicator.m in Sources */ = {isa = PBXBuildFile; fileRef = 5DA1E8494B86FFD79C639DCB8C21E1BB /* DTPieProgressIndicator.m */; }; EDA269B595D70EC2B9D81DC890157D63 /* DTAccessibilityViewProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = F37563A56C180CE18EEFADE56FA3C17F /* DTAccessibilityViewProxy.h */; settings = {ATTRIBUTES = (Public, ); }; }; - EDAD4A9457EBB9E65A2381991AB738F7 /* NSArray+DTError.m in Sources */ = {isa = PBXBuildFile; fileRef = 73940D215D644B6815A09DA802CFBDDF /* NSArray+DTError.m */; }; - EE18BE5AFEFAA1893D26EFBA4AFA7193 /* Date+ZIP.swift in Sources */ = {isa = PBXBuildFile; fileRef = F76A97683FB5E8E8B86EA4AD71F6F4B4 /* Date+ZIP.swift */; }; + EDD3BEDFA206C0F5AA78A86362C7C28E /* RDEPUBReaderAnnotationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2CC94883B1A94347DB0BFAFF179364D /* RDEPUBReaderAnnotationCoordinator.swift */; }; EE4F0992A0F597320FF7BDE4691D0FD4 /* NSString+CSS.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D6C48939A8498A09DCFC7F41ECB8A25 /* NSString+CSS.h */; settings = {ATTRIBUTES = (Public, ); }; }; - EF09C17C1A9226B1D3377A4186237715 /* RDEPUBWebView+Search.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1DD2563775FC83493319BFC4E953F00 /* RDEPUBWebView+Search.swift */; }; F006F73F17F2E3CC0E7852E9357BA946 /* DTAnchorHTMLElement.h in Headers */ = {isa = PBXBuildFile; fileRef = 424F12BEB0EDAAF1C719B5103D30C653 /* DTAnchorHTMLElement.h */; settings = {ATTRIBUTES = (Public, ); }; }; F0F631056BADA32253528247DF493272 /* DTHTMLElement.h in Headers */ = {isa = PBXBuildFile; fileRef = EE9D4A455723797A27F04A15C4E47823 /* DTHTMLElement.h */; settings = {ATTRIBUTES = (Public, ); }; }; + F1402ADADDC01A1A59ED22B971A50D62 /* Typealiases.swift in Sources */ = {isa = PBXBuildFile; fileRef = 945D4549E0B9168D7DDB910DE874B874 /* Typealiases.swift */; }; + F154A6A709307ADECDA063693CEDCE13 /* UILayoutSupport+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C647A2EB2720BCBF88B160D124B57BF /* UILayoutSupport+Extensions.swift */; }; + F1A4D990BE84E010BC9AB8CAE7D40830 /* wxread-default.css in Resources */ = {isa = PBXBuildFile; fileRef = 5D8DA3839256D5A245E96F2C67A5E232 /* wxread-default.css */; }; F1E9B6D0728711AE4D7154616DBC17EC /* Pods-ReadViewDemo-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = AA0811310099A4BCB3D52683B70DB3D1 /* Pods-ReadViewDemo-dummy.m */; }; - F2BAD19B5E842273FA3B4FB464F24EBA /* ZIPFoundation-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A06A36ADBE678DB44158D32E751FAFF /* ZIPFoundation-dummy.m */; }; - F367BD50B50B680A2DA23005EAC2A7BD /* SSAlertCommonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE4EE750340347ED584C5A2C1164331A /* SSAlertCommonView.swift */; }; - F4A25EE18B07DECDA92E9D539D70A76B /* ConstraintLayoutGuideDSL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4684B406B325B35F39322D75B59B6479 /* ConstraintLayoutGuideDSL.swift */; }; + F2CC7EB7A119026069917121B90372AF /* RDEPUBResourceResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 615F9666D462FC95654FDC2732F700A6 /* RDEPUBResourceResolver.swift */; }; + F35B2E482D6BB757A8EF69A7CD98AE44 /* RDEPUBRuntimeChapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14A1816DFD380D6BFB3047B433DFFBD4 /* RDEPUBRuntimeChapter.swift */; }; + F476C2A659D5C2602D71D06B6E6E787D /* RDEPUBWebViewDebug.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D01284125120D95F9E41743D2CE9F81 /* RDEPUBWebViewDebug.swift */; }; F4A2E3C4237B8931F61B72FF0BDD6D69 /* DTObjectTextAttachment.m in Sources */ = {isa = PBXBuildFile; fileRef = F038FB491B9A4A53447EF0DC333301AE /* DTObjectTextAttachment.m */; }; - F5142E93B76DB1917D507EECB36E5DED /* rangy-serializer.js in Resources */ = {isa = PBXBuildFile; fileRef = FD2D305B938F31ABC9EC9369E230A62B /* rangy-serializer.js */; }; F58ABA106C6200175DF574DF05A75BA8 /* DTAccessibilityElement.m in Sources */ = {isa = PBXBuildFile; fileRef = 4F0486D3B46DC92AB254D6EE9CC233BF /* DTAccessibilityElement.m */; }; F5A424E67F41C4F7B830CE2F7E71570D /* DTCoreText-Resources in Resources */ = {isa = PBXBuildFile; fileRef = F4E2B1AC73977F10254628C0B7EBE38F /* DTCoreText-Resources */; }; F6036BBDFE7484F5A453F6876C97C098 /* NSAttributedString+SmallCaps.m in Sources */ = {isa = PBXBuildFile; fileRef = 76F99C8130EE83A22CD223C7F5E6E66E /* NSAttributedString+SmallCaps.m */; }; F61102C9BF3D6296CB6C6B754ABF98C5 /* DTTextHTMLElement.m in Sources */ = {isa = PBXBuildFile; fileRef = 8CFEE298DD52246B46E6FA62B16B38DF /* DTTextHTMLElement.m */; }; - F638D941A6B83EE3D0FA3116BFE9CE52 /* RDEPUBAnnotationModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = C274719939F1335BCB8FA97636A9D2DD /* RDEPUBAnnotationModels.swift */; }; - F677C12701262AFF0F6FCCE4C2150CF9 /* rangy-core.js in Resources */ = {isa = PBXBuildFile; fileRef = A2BD2727B338CE5C2C3BC6E8B8A23DE9 /* rangy-core.js */; }; - F68D834D874BA6D351EA0BAE720DB1A6 /* NSURL+DTUnshorten.m in Sources */ = {isa = PBXBuildFile; fileRef = C80065DBA5634DAA71129404975D7349 /* NSURL+DTUnshorten.m */; }; - F6E6CAC7A3C273A7C4227BD6F0E36EE1 /* SSAlertView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F410ED25E4E84C3D5166261CDF4B58F2 /* SSAlertView.swift */; }; - F77B604D4BCCE6CBE8520E588343F0A1 /* RDEPUBSemanticMarkerInjector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E2F4C746E0B7479BE677A20358CB93B /* RDEPUBSemanticMarkerInjector.swift */; }; - F8D652BDA6DF2BCEECB48009D34C6834 /* RDEPUBFixedLayoutTemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8E247404E492652921952ECE5BA420E /* RDEPUBFixedLayoutTemplate.swift */; }; - F981518F2DA0E5B53C2560759D14986B /* Archive+ReadingDeprecated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 934F9BB2B3A313294F121484C3A97A14 /* Archive+ReadingDeprecated.swift */; }; - FB2BFDE8FC140377D92952992080A8DB /* ConstraintLayoutGuide+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A9A82CB094636AFB53D6D130D6219BA /* ConstraintLayoutGuide+Extensions.swift */; }; - FC24AD03BC7583FF043A6723AC086232 /* NSString+DTFormatNumbers.m in Sources */ = {isa = PBXBuildFile; fileRef = 1ACA5E519F100582F0D772FCD76125C0 /* NSString+DTFormatNumbers.m */; }; + F75C507004C4203113DF421C59509D5A /* ZIPFoundation-ZIPFoundation_Privacy in Resources */ = {isa = PBXBuildFile; fileRef = CA4A114775B98CEA4A566C18123B145F /* ZIPFoundation-ZIPFoundation_Privacy */; }; + F797D512693374F5DAF5134FA2137D0A /* ConstraintPriorityTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 882FA707ED3513D263A1E0656AAD2D33 /* ConstraintPriorityTarget.swift */; }; + F842359CAAE73EC96B02C6874B28CD60 /* RDReaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A294C65CC6C154EF2122380953FEFE3 /* RDReaderView.swift */; }; + F927F1196A4802A1BA4041A4D51C7672 /* ConstraintInsetTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32A5ABB8F3656A110B955B0306E9A8C3 /* ConstraintInsetTarget.swift */; }; + F97B3F528A2B7417D2843479548CDF40 /* UIApplication+DTNetworkActivity.h in Headers */ = {isa = PBXBuildFile; fileRef = 957DB4675D5CCBD3CB1762F08619F3F7 /* UIApplication+DTNetworkActivity.h */; settings = {ATTRIBUTES = (Public, ); }; }; + F9ADC220D598CC27EF1B9F8277966E01 /* NSURL+DTAppLinks.h in Headers */ = {isa = PBXBuildFile; fileRef = 557DA029E15EC99631B647AE8922007F /* NSURL+DTAppLinks.h */; settings = {ATTRIBUTES = (Public, ); }; }; + FB4EB5314A8180C0EE755D0D5AA5CD99 /* RDEPUBWebView+Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B55540809AED42C4B61333680CEC4CB /* RDEPUBWebView+Configuration.swift */; }; + FB80A8C5A8855F5C7024834C815F52E6 /* RDReaderPagingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5F17B78960BAA0E4F63CBDDEAD953FE /* RDReaderPagingController.swift */; }; + FB957A6F0F564266C6BB7B461ED2D983 /* RDEPUBModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A2067EA81E19AA5F8E66DE4300DAB4A /* RDEPUBModels.swift */; }; + FC2254599C78A7A07553A9642BE78A7A /* RDReaderContentCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 291992715B24D4645FF6E45D0881635F /* RDReaderContentCell.swift */; }; FC5F52919451A15639442B46B38E4C67 /* DTCoreTextLayoutFrame+Cursor.m in Sources */ = {isa = PBXBuildFile; fileRef = E62C5E593037EF8A45E7FAA847D0277C /* DTCoreTextLayoutFrame+Cursor.m */; }; - FD4B6F54758FB3894484A092B0A1B2AC /* Entry+Serialization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F991240340FA026C71B68EB25B9F9C8 /* Entry+Serialization.swift */; }; + FD2D0288F99B3E6405F4A33B7585103D /* String+SHA256.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8B8050E89617564241284BAA5AE661C /* String+SHA256.swift */; }; FD5D68887297CFA94F543C49DC68A156 /* DTHTMLAttributedStringBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = 7C496AB6A73C2D0BD0FF340A6A2960D5 /* DTHTMLAttributedStringBuilder.m */; }; - FD6F8A543C3241B08E21FBBD47911F6D /* epub-bridge.js in Resources */ = {isa = PBXBuildFile; fileRef = 75838C8BB89B6B0B142EC3F6922D925B /* epub-bridge.js */; }; - FDAAF8F1F0F13A657B57B92F75B5F2A0 /* ConstraintInsets.swift in Sources */ = {isa = PBXBuildFile; fileRef = C241AD9CEC9D6732CFBA6A55B49E64C9 /* ConstraintInsets.swift */; }; + FDAFFD33014A537108F9074F6EF7EAD9 /* RDEPUBWebView+Reflowable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8067F377315BFA008F9F0795CCB68D5F /* RDEPUBWebView+Reflowable.swift */; }; + FE4C703DD0829444A9A441741F3630EB /* RDEPUBRenderDiagnosticsCollector.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB0484A107F418C2B5E2944F8BA3C1E6 /* RDEPUBRenderDiagnosticsCollector.swift */; }; FEB00C614DCB6A8A7BDF8DEE11260984 /* NSMutableAttributedString+HTML.m in Sources */ = {isa = PBXBuildFile; fileRef = B0500CA69405E23B09291359EB28331E /* NSMutableAttributedString+HTML.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ - 24BA72D0D912AE5A45EAC25AC27B6B7F /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 19622742EBA51E823D6DAE3F8CDBFAD4; - remoteInfo = SnapKit; - }; - 2B7231DD02DE6D67ED0FF347C2D2831A /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 0C24CB0E87A728A11AA1124CB360D6A1; - remoteInfo = "DTCoreText-Resources"; - }; - 41C358828795BDBE26E60DEDDB6F5A31 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; - proxyType = 1; - remoteGlobalIDString = C7A8D82E407CD3FDC3BA55CEE519B252; - remoteInfo = "ZIPFoundation-ZIPFoundation_Privacy"; - }; - 51F9BB05FB5362314AABDBF12FD0F619 /* PBXContainerItemProxy */ = { + 04CB85A41687F3E51BC4003ABA093E54 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; proxyType = 1; remoteGlobalIDString = AE7F393FB7805DE2664AB4111873F907; remoteInfo = "RDReaderView-RDReaderViewAssets"; }; - 5BA4345CC5FD80F403AD69F002484017 /* PBXContainerItemProxy */ = { + 291EF29F5CECA009529D76F9FE26B4B9 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; proxyType = 1; - remoteGlobalIDString = B88F4EA0695B6B3165C64594850D72C7; - remoteInfo = DTCoreText; + remoteGlobalIDString = 8F6E5A5BF72D62CDFD25F91A7CFA3309; + remoteInfo = DTFoundation; }; - 7C28D11939EC5ABB1D98898772B52AED /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 8619D5ADECF2B26CFD9A9826D61D289A; - remoteInfo = SSAlertSwift; - }; - 8EC7B58761479718591F7FDFB9BC34E2 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; - proxyType = 1; - remoteGlobalIDString = AA15C8469D67684160CC2A7098EB841C; - remoteInfo = ZIPFoundation; - }; - 962A98D094FBC182A4454FA956B2E681 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; - proxyType = 1; - remoteGlobalIDString = AA15C8469D67684160CC2A7098EB841C; - remoteInfo = ZIPFoundation; - }; - A0BFC7B494DC59D52F43761C722DAB2D /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 8619D5ADECF2B26CFD9A9826D61D289A; - remoteInfo = SSAlertSwift; - }; - A0CA6E77551A041C81844E5901F24EFC /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; - proxyType = 1; - remoteGlobalIDString = B88F4EA0695B6B3165C64594850D72C7; - remoteInfo = DTCoreText; - }; - A7A366C3CFD62C32F5B8E5EB9A8310BB /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 19622742EBA51E823D6DAE3F8CDBFAD4; - remoteInfo = SnapKit; - }; - B1D7453B0F93836CC086060595CB753A /* PBXContainerItemProxy */ = { + 296863D40BD5EF3E4022BD63E7A8B0C7 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; proxyType = 1; remoteGlobalIDString = 8A8DB685241263AFDF5E6B20FE67B93A; remoteInfo = "SnapKit-SnapKit_Privacy"; }; - CE5F9B32E7622F85FC1F741CE5013575 /* PBXContainerItemProxy */ = { + 2AA2622E7B72A2DFD0367159EB235920 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = AA15C8469D67684160CC2A7098EB841C; + remoteInfo = ZIPFoundation; + }; + 7AD1BAAD6E745A14A9201807DB7B6F66 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 0C24CB0E87A728A11AA1124CB360D6A1; + remoteInfo = "DTCoreText-Resources"; + }; + 7BAA191D501A47FE7939B55CA03FE66E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 19622742EBA51E823D6DAE3F8CDBFAD4; + remoteInfo = SnapKit; + }; + 7E29E8795F2AF85B1EE50F510591F961 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = B88F4EA0695B6B3165C64594850D72C7; + remoteInfo = DTCoreText; + }; + 854186CDD7ACBD2359F0E9F298BF270D /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 8619D5ADECF2B26CFD9A9826D61D289A; + remoteInfo = SSAlertSwift; + }; + 8FC75B4885448A5DE210832192113C7B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = AA15C8469D67684160CC2A7098EB841C; + remoteInfo = ZIPFoundation; + }; + 95CED971E55EB31D8A064B92A3B4D9B2 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 8619D5ADECF2B26CFD9A9826D61D289A; + remoteInfo = SSAlertSwift; + }; + B1D5725EC0757F29DD19F470E271A8DB /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 19622742EBA51E823D6DAE3F8CDBFAD4; + remoteInfo = SnapKit; + }; + DC1726A240735222D1E9C105AF7AB304 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = C7A8D82E407CD3FDC3BA55CEE519B252; + remoteInfo = "ZIPFoundation-ZIPFoundation_Privacy"; + }; + FCA8F8EFC9D0E6769A5625F6E48F3C1B /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; proxyType = 1; remoteGlobalIDString = 8F6E5A5BF72D62CDFD25F91A7CFA3309; remoteInfo = DTFoundation; }; - EA5C5FB405F5479FFA8A607CD33F3ECF /* PBXContainerItemProxy */ = { + FE4C489BFE4741DF6A4850DD59D2B47C /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = B88F4EA0695B6B3165C64594850D72C7; + remoteInfo = DTCoreText; + }; + FEFD55D4A212E925212A5E4683FFA101 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; proxyType = 1; remoteGlobalIDString = AA2E57587AA8EECA63C4BE08EA3CB6D2; remoteInfo = RDReaderView; }; - F24E133D0B4B0D43780A5C9975FA4EF9 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 8F6E5A5BF72D62CDFD25F91A7CFA3309; - remoteInfo = DTFoundation; - }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 006F2BEF2BA2A518F2C533B76FC7D3BC /* RDEPUBReaderContext.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBReaderContext.swift; sourceTree = ""; }; 01073FE593A6AE17D4376AF909753A8F /* RDEPUBTextRenderer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBTextRenderer.swift; sourceTree = ""; }; 015F3EE69253C66E11B931E3360BA5FE /* DTCoreText-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "DTCoreText-dummy.m"; sourceTree = ""; }; + 01D626274DCF71D8C3FA537C9A7367D8 /* RDEPUBReaderController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBReaderController.swift; sourceTree = ""; }; 029130886CE8E422D16ADF97468CB1B0 /* DTCoreTextLayoutFrame.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = DTCoreTextLayoutFrame.m; path = Core/Source/DTCoreTextLayoutFrame.m; sourceTree = ""; }; + 02C071FDE0C607A22BD7B0375DE72458 /* RDEPUBReaderDependencies.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBReaderDependencies.swift; sourceTree = ""; }; 02CF362F88AB69253F1898E53D7A286A /* ConstraintDSL.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintDSL.swift; path = Sources/ConstraintDSL.swift; sourceTree = ""; }; 02EDFE0EEA47398F3E1A361A73AF6A6C /* NSData+DTCrypto.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSData+DTCrypto.h"; path = "Core/Source/NSData+DTCrypto.h"; sourceTree = ""; }; 045ADBF2087ADD914A9CA0B62EBF2A9F /* Pods-ReadViewDemo.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = "Pods-ReadViewDemo.modulemap"; sourceTree = ""; }; @@ -559,48 +579,48 @@ 09C957F3199C596781EFA1DEA1DDCEC8 /* NSFileWrapper+DTCopying.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSFileWrapper+DTCopying.h"; path = "Core/Source/NSFileWrapper+DTCopying.h"; sourceTree = ""; }; 0A294C65CC6C154EF2122380953FEFE3 /* RDReaderView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDReaderView.swift; sourceTree = ""; }; 0B10D3EC1701BBA4D69926915F942558 /* SnapKit-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "SnapKit-umbrella.h"; sourceTree = ""; }; + 0B47D4559682F8352B1AD4598CEDBB28 /* RDEPUBChapterLocation.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBChapterLocation.swift; sourceTree = ""; }; 0B7771FDE9AB1688166584C0AA03F12A /* DTCoreTextFontCollection.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = DTCoreTextFontCollection.h; path = Core/Source/DTCoreTextFontCollection.h; sourceTree = ""; }; 0B832537E4E508747319F7DF89BE256A /* RDEPUBChapterTailNormalizer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBChapterTailNormalizer.swift; sourceTree = ""; }; 0C6BE3B2DA9A8C4CD3760CDAD6F305A5 /* DTCustomColoredAccessory.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = DTCustomColoredAccessory.m; path = Core/Source/iOS/DTCustomColoredAccessory.m; sourceTree = ""; }; - 0C7333A1BFCB9D7512A152B2B0EC6350 /* RDEPUBReaderAnnotationCoordinator.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBReaderAnnotationCoordinator.swift; sourceTree = ""; }; 0D75FEDE65722CF41F7F32A164376926 /* DTHTMLParser.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = DTHTMLParser.h; path = Core/Source/DTHTMLParser/DTHTMLParser.h; sourceTree = ""; }; + 0DEAA0384B504309C6256D3F9FF428AE /* RDEPUBBookPageMap.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBBookPageMap.swift; sourceTree = ""; }; 0E138EC76D0DF24544D29D1A4D7429E3 /* ConstraintMakerPrioritizable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintMakerPrioritizable.swift; path = Sources/ConstraintMakerPrioritizable.swift; sourceTree = ""; }; 0E8C0D83EB6EDE71B264B6B0DD4644F5 /* LayoutConstraintItem.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = LayoutConstraintItem.swift; path = Sources/LayoutConstraintItem.swift; sourceTree = ""; }; - 0ECB9576AFBC841E4282C8091B9D1244 /* RDURLReaderController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDURLReaderController.swift; sourceTree = ""; }; 0F5D34047BAA894BEF1DFA23E6A9DAB1 /* DTCoreTextMacros.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = DTCoreTextMacros.h; path = Core/Source/DTCoreTextMacros.h; sourceTree = ""; }; 0FC368BD2A8D266EB771E5FB9A5A5B3C /* NSDictionary+DTError.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSDictionary+DTError.h"; path = "Core/Source/NSDictionary+DTError.h"; sourceTree = ""; }; + 105633F335811C52B7411E5AA1A6A0BC /* RDEPUBReaderTopToolView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBReaderTopToolView.swift; sourceTree = ""; }; 117036F7DB9A22578522D0B397C28F26 /* RDEPUBPageBreakPolicy.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBPageBreakPolicy.swift; sourceTree = ""; }; 11B604FC3B0D5C91FEA763DCC0BE4DC5 /* DTHTMLParserNode.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = DTHTMLParserNode.m; path = Core/Source/DTHTMLParserNode.m; sourceTree = ""; }; - 127FB4B3B74B022CBE4054BA33CB24A0 /* RDEPUBReaderController+ContentDelegates.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "RDEPUBReaderController+ContentDelegates.swift"; sourceTree = ""; }; 12A5EA26EE7539EDDA90649D22FB675D /* DTImage+HTML.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "DTImage+HTML.m"; path = "Core/Source/DTImage+HTML.m"; sourceTree = ""; }; + 130D574A6F9B63B39EE77858FD259D12 /* RDEPUBReaderSearchCoordinator.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBReaderSearchCoordinator.swift; sourceTree = ""; }; 136BC2B3FC110EA496A8803FF547F72D /* RDReaderView-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "RDReaderView-Info.plist"; sourceTree = ""; }; 137B4BC4EAE7AE0D9567E53EBBBA687E /* RDEPUBParser+TOC.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "RDEPUBParser+TOC.swift"; sourceTree = ""; }; + 138A4AA21D93424C026C8557FF6F387E /* RDEPUBReaderConfiguration.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBReaderConfiguration.swift; sourceTree = ""; }; 13AB6A32A293E3DC21A7047C6DB958CA /* DTHTMLParserTextNode.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = DTHTMLParserTextNode.m; path = Core/Source/DTHTMLParserTextNode.m; sourceTree = ""; }; 13E1548FC42118F1A4164739DE6BE4E9 /* UIFont+DTCoreText.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UIFont+DTCoreText.m"; path = "Core/Source/UIFont+DTCoreText.m"; sourceTree = ""; }; 141325B9AB28E266F40552E4F82F952D /* RDEPUBTextLayoutFrame.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBTextLayoutFrame.swift; sourceTree = ""; }; + 14A1816DFD380D6BFB3047B433DFFBD4 /* RDEPUBRuntimeChapter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBRuntimeChapter.swift; sourceTree = ""; }; 15941683D394FB8D7EAE91CD2CF03FF1 /* UIImage+DTFoundation.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UIImage+DTFoundation.h"; path = "Core/Source/iOS/UIImage+DTFoundation.h"; sourceTree = ""; }; 164CFAED45E63A7592580A342D44A43E /* RDEPUBStyleSheetBuilder.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBStyleSheetBuilder.swift; sourceTree = ""; }; 16C3C684271C3DE0964041A2D8701E58 /* RDEPUBSearchEngine.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBSearchEngine.swift; sourceTree = ""; }; - 173FEADB0A73EA7EB5B3B7CADB74C3A4 /* RDEPUBReaderController+TableOfContents.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "RDEPUBReaderController+TableOfContents.swift"; sourceTree = ""; }; 174FC0CA05D20BB995E211DE5A0857DB /* Archive+MemoryFile.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "Archive+MemoryFile.swift"; path = "Sources/ZIPFoundation/Archive+MemoryFile.swift"; sourceTree = ""; }; 1756C6FFCA695290BA62DA4E7B6DD20B /* NSFileWrapper+DTCopying.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSFileWrapper+DTCopying.m"; path = "Core/Source/NSFileWrapper+DTCopying.m"; sourceTree = ""; }; 17F750F38183302BF345F40F5A4CDD43 /* NSData+DTCrypto.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSData+DTCrypto.m"; path = "Core/Source/NSData+DTCrypto.m"; sourceTree = ""; }; 18387159515E0523AFB4204B1AF940ED /* SSAlertSwift.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = SSAlertSwift.debug.xcconfig; sourceTree = ""; }; - 185CD1A4E30E72715AF3129924F063E6 /* RDEPUBSelectionOverlayView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBSelectionOverlayView.swift; sourceTree = ""; }; - 19F9801AD3675102B14F64AFAFB99D89 /* RDEPUBPageLayoutSnapshot.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBPageLayoutSnapshot.swift; sourceTree = ""; }; 1ABC2F552388CD08EC1EFDEF606AE222 /* DTTextAttachmentHTMLElement.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = DTTextAttachmentHTMLElement.m; path = Core/Source/DTTextAttachmentHTMLElement.m; sourceTree = ""; }; 1ACA5E519F100582F0D772FCD76125C0 /* NSString+DTFormatNumbers.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSString+DTFormatNumbers.m"; path = "Core/Source/NSString+DTFormatNumbers.m"; sourceTree = ""; }; 1AD502A1FA6EB1E38200B5283F979504 /* Pods-ReadViewDemo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-ReadViewDemo.release.xcconfig"; sourceTree = ""; }; 1B443D035BA5652B03A41BE905C5AC17 /* DTPieProgressIndicator.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = DTPieProgressIndicator.h; path = Core/Source/iOS/DTPieProgressIndicator.h; sourceTree = ""; }; - 1B8132548C8745F3AE7F3CBC78E1DDA9 /* RDEPUBReaderPersistence.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBReaderPersistence.swift; sourceTree = ""; }; - 1BD33493F588BFD65E47EC9B8773C74C /* RDEPUBReaderController+PublicAPI.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "RDEPUBReaderController+PublicAPI.swift"; sourceTree = ""; }; + 1B936ABDE1067495025723B34C301C87 /* RDEPUBReaderController+RuntimeBridge.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "RDEPUBReaderController+RuntimeBridge.swift"; sourceTree = ""; }; + 1C547D687599B32842C68D8F5C1E67C0 /* RDEPUBChapterWindowSnapshot.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBChapterWindowSnapshot.swift; sourceTree = ""; }; 1C8F0DCEAA5A3DF38C503CA30DC94C1C /* Archive.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Archive.swift; path = Sources/ZIPFoundation/Archive.swift; sourceTree = ""; }; - 1CA5A4277907D54250DB4BF7B95CE1A9 /* RDEPUBReaderContext.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBReaderContext.swift; sourceTree = ""; }; 1D01284125120D95F9E41743D2CE9F81 /* RDEPUBWebViewDebug.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBWebViewDebug.swift; sourceTree = ""; }; 1D4C9B7ED5E04C216AB28E3A4065C1C5 /* DTHTMLElement.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = DTHTMLElement.m; path = Core/Source/DTHTMLElement.m; sourceTree = ""; }; 1D6DA580F40DF113709E15CF6542A35C /* DTCoreTextLayouter.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = DTCoreTextLayouter.m; path = Core/Source/DTCoreTextLayouter.m; sourceTree = ""; }; 1E89FE5F2996336E862E82C1E14E0EE7 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; name = PrivacyInfo.xcprivacy; path = Sources/ZIPFoundation/Resources/PrivacyInfo.xcprivacy; sourceTree = ""; }; 1F090A0289338B14050FAF16C22605B0 /* ConstraintConstantTarget.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintConstantTarget.swift; path = Sources/ConstraintConstantTarget.swift; sourceTree = ""; }; + 1F92C8ECA3907CF09E97F20AFD1F2469 /* RDEPUBReaderHighlightsViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBReaderHighlightsViewController.swift; sourceTree = ""; }; 2047430DF5C84E0A8FC8874A2B915FC3 /* Pods-ReadViewDemo-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-ReadViewDemo-acknowledgements.markdown"; sourceTree = ""; }; 2055F08A4A307EA51828460481FE8276 /* RDEPUBTextBuildPipelineInterfaces.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBTextBuildPipelineInterfaces.swift; sourceTree = ""; }; 20AA41D781113C9A30CD0E9EDD6835D5 /* RDEPUBPaginationCacheCoordinator.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBPaginationCacheCoordinator.swift; sourceTree = ""; }; @@ -615,48 +635,47 @@ 268AF3AC14AC70E5C08F5EF60DBFC4F8 /* RDEPUBWebView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBWebView.swift; sourceTree = ""; }; 2702E05AA7A266B7D8D0F881FD4E09DA /* DTCompatibility.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = DTCompatibility.h; path = Core/Source/DTCompatibility.h; sourceTree = ""; }; 27205D159B39090908CA0E9C3997B784 /* NSDictionary+DTCoreText.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSDictionary+DTCoreText.h"; path = "Core/Source/NSDictionary+DTCoreText.h"; sourceTree = ""; }; + 2787B86458FE50067B72A704805C4688 /* RDEPUBPageLayoutSnapshot.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBPageLayoutSnapshot.swift; sourceTree = ""; }; 27B7D3C6F0C845E3D42BBA613FFB0465 /* FileManager+ZIP.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "FileManager+ZIP.swift"; path = "Sources/ZIPFoundation/FileManager+ZIP.swift"; sourceTree = ""; }; 28043173CA3B3787DC401ACEB5398108 /* DTCoreTextLayoutFrame+Cursor.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "DTCoreTextLayoutFrame+Cursor.h"; path = "Core/Source/DTCoreTextLayoutFrame+Cursor.h"; sourceTree = ""; }; 28D62D3200AC3D4E42C81CDFF7E884E8 /* ConstraintItem.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintItem.swift; path = Sources/ConstraintItem.swift; sourceTree = ""; }; 291992715B24D4645FF6E45D0881635F /* RDReaderContentCell.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDReaderContentCell.swift; sourceTree = ""; }; 2AEE00544B5F5C69A1636474C8E76690 /* RDEPUBFontNormalizer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBFontNormalizer.swift; sourceTree = ""; }; + 2B7B984084289FA85BE14684929535F9 /* RDEPUBChapterCacheKey.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBChapterCacheKey.swift; sourceTree = ""; }; 2BA19BD582C1BD54AF673014C04848B6 /* DTFoundation-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "DTFoundation-Info.plist"; sourceTree = ""; }; - 2BCD9B2E62051692362E404278683A18 /* RDEPUBReaderChromeCoordinator.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBReaderChromeCoordinator.swift; sourceTree = ""; }; 2C50CAB4241C141EF6EC9ED22AB3A7E8 /* NSDictionary+DTCoreText.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSDictionary+DTCoreText.m"; path = "Core/Source/NSDictionary+DTCoreText.m"; sourceTree = ""; }; + 2C8A4EDCD9556F499F840510E94730CD /* RDEPUBReaderPersistence.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBReaderPersistence.swift; sourceTree = ""; }; 2CD63BBDA1CB02B15A54D2CDD8A3ACD7 /* SnapKit-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "SnapKit-prefix.pch"; sourceTree = ""; }; 2D36F7CDCD7D5788CDCB3A1421DB242B /* NSURL+DTAppLinks.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSURL+DTAppLinks.m"; path = "Core/Source/iOS/NSURL+DTAppLinks.m"; sourceTree = ""; }; 2D3D7C8444F58D9B3FCB4F3006D2CFBA /* NSAttributedString+HTML.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSAttributedString+HTML.h"; path = "Core/Source/NSAttributedString+HTML.h"; sourceTree = ""; }; 2DE4FA5133891593CB45AF36B8EBF60C /* SSAlertSwift-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "SSAlertSwift-dummy.m"; sourceTree = ""; }; - 2F910EB56A7325328DCD94F082E99648 /* RDEPUBTextAnnotationOverlay.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBTextAnnotationOverlay.swift; sourceTree = ""; }; 2FBA8F4FD78EAB6A6E4011D92577EA42 /* wxread-dark.css */ = {isa = PBXFileReference; includeInIndex = 1; name = "wxread-dark.css"; path = "Sources/RDReaderView/EPUBCore/Resources/wxread-dark.css"; sourceTree = ""; }; 303F36CC1C0432C0F498EAED0D82A567 /* RDEPUBTextRendererSupport.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBTextRendererSupport.swift; sourceTree = ""; }; 306477706BBB1E784F55BCF8EB1AD737 /* Pods-ReadViewDemo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-ReadViewDemo.debug.xcconfig"; sourceTree = ""; }; 31407369BDB36B40D253244CCF1F954A /* RDReaderViewProtocols.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDReaderViewProtocols.swift; sourceTree = ""; }; - 3156D6DC256E20EF49691134C4342473 /* RDEPUBReaderSearchCoordinator.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBReaderSearchCoordinator.swift; sourceTree = ""; }; 31BD7533867336DEA9B835F471767178 /* NSString+DTURLEncoding.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSString+DTURLEncoding.h"; path = "Core/Source/NSString+DTURLEncoding.h"; sourceTree = ""; }; 31F3F91D5624C451E5D44D85B1DDE7C5 /* NSMutableString+HTML.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSMutableString+HTML.m"; path = "Core/Source/NSMutableString+HTML.m"; sourceTree = ""; }; 32A5ABB8F3656A110B955B0306E9A8C3 /* ConstraintInsetTarget.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintInsetTarget.swift; path = Sources/ConstraintInsetTarget.swift; sourceTree = ""; }; 32A8BED62113FF47E3925378065FA3BE /* SSAlertSwift-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "SSAlertSwift-prefix.pch"; sourceTree = ""; }; 32FB2B1B0498DCBE7B79BDF49E963E3D /* DTHTMLParser.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = DTHTMLParser.m; path = Core/Source/DTHTMLParser/DTHTMLParser.m; sourceTree = ""; }; 33DB57DF8C5AE334B1BBE9F989352F1D /* DTCustomColoredAccessory.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = DTCustomColoredAccessory.h; path = Core/Source/iOS/DTCustomColoredAccessory.h; sourceTree = ""; }; - 34D4FDB3A066F397D1FF9727C43A0777 /* RDEPUBReaderController+RuntimeBridge.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "RDEPUBReaderController+RuntimeBridge.swift"; sourceTree = ""; }; 354C5DD0ACC742762200A640ADA859C6 /* NSCoder+DTCompatibility.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSCoder+DTCompatibility.h"; path = "Core/Source/NSCoder+DTCompatibility.h"; sourceTree = ""; }; 365FB4C3E0FF364C076EE4E9E47C32EA /* DTCoreText.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = DTCoreText.modulemap; sourceTree = ""; }; + 36855CF828CDEAE98B4B8F58CFC25485 /* RDEPUBReaderSettingsViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBReaderSettingsViewController.swift; sourceTree = ""; }; 36FCB869246FE45FD1C9609A34EB4E74 /* DTCoreTextGlyphRun.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = DTCoreTextGlyphRun.m; path = Core/Source/DTCoreTextGlyphRun.m; sourceTree = ""; }; 375179DD3A4576CEF947E1760BBDC69B /* DTBlockFunctions.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = DTBlockFunctions.m; path = Core/Source/DTBlockFunctions.m; sourceTree = ""; }; 392A75EAF280876545D14AA00517BA4C /* ConstraintMakerEditable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintMakerEditable.swift; path = Sources/ConstraintMakerEditable.swift; sourceTree = ""; }; 3A525BB5B1708520D409C1A30CF1CD2D /* RDReaderView+PageCurl.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "RDReaderView+PageCurl.swift"; sourceTree = ""; }; 3B610E558C4917C4257DC198EAFEE2F6 /* DTCSSStylesheet.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = DTCSSStylesheet.h; path = Core/Source/DTCSSStylesheet.h; sourceTree = ""; }; 3B931AB189BDDE2CCE8578B50BD60AF0 /* DTIframeTextAttachment.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = DTIframeTextAttachment.h; path = Core/Source/DTIframeTextAttachment.h; sourceTree = ""; }; - 3D246461688CA3E0592B5536C95A624A /* RDEPUBReaderPaginationCoordinator.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBReaderPaginationCoordinator.swift; sourceTree = ""; }; 3DC78D0B0F37BF9AD1058CACA9AAF9BE /* Data+Serialization.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "Data+Serialization.swift"; path = "Sources/ZIPFoundation/Data+Serialization.swift"; sourceTree = ""; }; 3EADCA56DD969174A20085845D93C409 /* RDEPUBTypesettingPipeline.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBTypesettingPipeline.swift; sourceTree = ""; }; - 3F274F6DE3095EAF4042CC6BF0F3E566 /* RDEPUBReaderBottomToolView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBReaderBottomToolView.swift; sourceTree = ""; }; - 413B561A626E7BC1C78EFEFA131B92B0 /* RDEPUBReaderRuntime.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBReaderRuntime.swift; sourceTree = ""; }; + 3F6EEB30082AC5F62EC78FB49D9055B5 /* RDEPUBReaderChromeCoordinator.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBReaderChromeCoordinator.swift; sourceTree = ""; }; + 40AE4D16785DB42B707640DA3DCC86E3 /* RDEPUBReaderTheme.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBReaderTheme.swift; sourceTree = ""; }; 4219E7C32019899890CAB66E5FBDF524 /* UIView+DTFoundation.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UIView+DTFoundation.m"; path = "Core/Source/iOS/UIView+DTFoundation.m"; sourceTree = ""; }; 424F12BEB0EDAAF1C719B5103D30C653 /* DTAnchorHTMLElement.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = DTAnchorHTMLElement.h; path = Core/Source/DTAnchorHTMLElement.h; sourceTree = ""; }; 426D442C01742E4361F5BC34D43152A6 /* DTImageTextAttachment.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = DTImageTextAttachment.h; path = Core/Source/DTImageTextAttachment.h; sourceTree = ""; }; - 42B4304B138A25135B0946C6A7A3F4A8 /* RDEPUBViewportTypes.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBViewportTypes.swift; sourceTree = ""; }; + 42B66EA2B45B08094B0C357085CB0846 /* RDEPUBTextContentView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBTextContentView.swift; sourceTree = ""; }; 43CFADE0F46014990052AD67F17C86FF /* DTAccessibilityViewProxy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = DTAccessibilityViewProxy.m; path = Core/Source/DTAccessibilityViewProxy.m; sourceTree = ""; }; 4428633C8EC3F6289CCF376F588CE0C4 /* NSString+HTML.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSString+HTML.m"; path = "Core/Source/NSString+HTML.m"; sourceTree = ""; }; 4472755F83C121DE5E1E505D145F6DBE /* CoreText.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreText.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS18.0.sdk/System/Library/Frameworks/CoreText.framework; sourceTree = DEVELOPER_DIR; }; @@ -668,24 +687,22 @@ 477A0FC2A1F0B7F7CCF72D201ECEBAB0 /* DTDictationPlaceholderTextAttachment.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = DTDictationPlaceholderTextAttachment.m; path = Core/Source/DTDictationPlaceholderTextAttachment.m; sourceTree = ""; }; 48A4A6EDC08C8AC933D0DCD507E9D173 /* ZIPFoundation */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = ZIPFoundation; path = ZIPFoundation.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 49A21AE1A0AB5383F895B34EA7D6973D /* DTCoreTextGlyphRun.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = DTCoreTextGlyphRun.h; path = Core/Source/DTCoreTextGlyphRun.h; sourceTree = ""; }; - 49F47726654DBD3FAFF21FFA121D6EE7 /* RDEPUBReaderSettingsViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBReaderSettingsViewController.swift; sourceTree = ""; }; 49F7945AF94DCC77CB3E7F3F711BD4AB /* RDEPUBTextBookModels.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBTextBookModels.swift; sourceTree = ""; }; 4A06A36ADBE678DB44158D32E751FAFF /* ZIPFoundation-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "ZIPFoundation-dummy.m"; sourceTree = ""; }; 4A2659DB0685E16ABAE0382F726DA8C8 /* Archive+Helpers.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "Archive+Helpers.swift"; path = "Sources/ZIPFoundation/Archive+Helpers.swift"; sourceTree = ""; }; 4AF6156128A1A0CA24D9D06E346B9445 /* DTVersion.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = DTVersion.m; path = Core/Source/DTVersion.m; sourceTree = ""; }; 4B142DE3191F790898FB028F3E6C91BA /* RDEPUBParser+Package.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "RDEPUBParser+Package.swift"; sourceTree = ""; }; - 4B789EEAB8791D76E537C6186EA1AC1D /* RDEPUBTextContentView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBTextContentView.swift; sourceTree = ""; }; 4BEC60CB3EDA1C189D3B062E29217440 /* ZIPFoundation.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = ZIPFoundation.debug.xcconfig; sourceTree = ""; }; 4C2412CF0CE63E83F2F0EC492D3256F9 /* RDEPUBTextPaginationInterfaces.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBTextPaginationInterfaces.swift; sourceTree = ""; }; 4C2B218B5EAA5831A036BBF7E1EFC1B9 /* MediaPlayer.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MediaPlayer.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS18.0.sdk/System/Library/Frameworks/MediaPlayer.framework; sourceTree = DEVELOPER_DIR; }; - 4C94F70B03585FF87A0DEF959FD7E5A6 /* RDEPUBReaderLoadCoordinator.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBReaderLoadCoordinator.swift; sourceTree = ""; }; + 4D0E429D0360BC3A87BBA2697DA47F3F /* RDEPUBReaderBottomToolView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBReaderBottomToolView.swift; sourceTree = ""; }; 4D641809A99849F80FDCB02F8CE8D6A0 /* Data+CompressionDeprecated.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "Data+CompressionDeprecated.swift"; path = "Sources/ZIPFoundation/Data+CompressionDeprecated.swift"; sourceTree = ""; }; 4D9B8DA4629A86EE848CD980111120C3 /* cssInjector.js */ = {isa = PBXFileReference; includeInIndex = 1; name = cssInjector.js; path = Sources/RDReaderView/EPUBCore/Resources/cssInjector.js; sourceTree = ""; }; 4EEFAE80193E1F0DA53400C9AD641410 /* DTActivityTitleView.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = DTActivityTitleView.m; path = Core/Source/iOS/DTActivityTitleView.m; sourceTree = ""; }; 4F0486D3B46DC92AB254D6EE9CC233BF /* DTAccessibilityElement.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = DTAccessibilityElement.m; path = Core/Source/DTAccessibilityElement.m; sourceTree = ""; }; + 4F40B8225D553E55937EB0FAB4B649DA /* RDEPUBWebContentView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBWebContentView.swift; sourceTree = ""; }; 50297AAB8278F2BB6FCFE82B20C7A049 /* ConstraintLayoutSupport.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintLayoutSupport.swift; path = Sources/ConstraintLayoutSupport.swift; sourceTree = ""; }; 5155CCDD61E030184249A020FE5D74B0 /* DTWeakSupport.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = DTWeakSupport.h; path = Core/Source/DTWeakSupport.h; sourceTree = ""; }; - 51F09F20128209EC507BBD2E73457DCD /* RDEPUBReaderTableOfContentsItem.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBReaderTableOfContentsItem.swift; sourceTree = ""; }; 521E0E2A04F1C8B2E1AB5D30420A010B /* RDEPUBNavigatorLayoutContext.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBNavigatorLayoutContext.swift; sourceTree = ""; }; 5245010774F8F1C4857763CC7B96E7B4 /* ConstraintMakerExtendable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintMakerExtendable.swift; path = Sources/ConstraintMakerExtendable.swift; sourceTree = ""; }; 533CFFF04BE7BC4BDEAE11DF675A84CA /* DTBreakHTMLElement.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = DTBreakHTMLElement.h; path = Core/Source/DTBreakHTMLElement.h; sourceTree = ""; }; @@ -699,6 +716,7 @@ 557DA029E15EC99631B647AE8922007F /* NSURL+DTAppLinks.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSURL+DTAppLinks.h"; path = "Core/Source/iOS/NSURL+DTAppLinks.h"; sourceTree = ""; }; 5612A9760A2FEC2682E417DF75F4C869 /* RDEPUBHTMLNormalizer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBHTMLNormalizer.swift; sourceTree = ""; }; 56489D9BA834497E48438A4B4E1CCCFF /* DTCoreTextFontDescriptor.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = DTCoreTextFontDescriptor.h; path = Core/Source/DTCoreTextFontDescriptor.h; sourceTree = ""; }; + 56781BA03B3EE632D516FA3278F78ED2 /* RDEPUBTextPageRenderView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBTextPageRenderView.swift; sourceTree = ""; }; 5679D281C282896E6FC352C48E0AF439 /* DTAttributedLabel.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = DTAttributedLabel.h; path = Core/Source/DTAttributedLabel.h; sourceTree = ""; }; 591B0B382F10A52D84F90D5C34893CB8 /* RDEPUBTextSearchEngine.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBTextSearchEngine.swift; sourceTree = ""; }; 59444DC57CE3171F4EF0CD6481FACF30 /* RDReaderView-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "RDReaderView-umbrella.h"; sourceTree = ""; }; @@ -708,12 +726,11 @@ 5B007AEEACE9EA271DD73AFD80A74D83 /* DTLog.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = DTLog.m; path = Core/Source/DTLog.m; sourceTree = ""; }; 5B55540809AED42C4B61333680CEC4CB /* RDEPUBWebView+Configuration.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "RDEPUBWebView+Configuration.swift"; sourceTree = ""; }; 5BA251BAC8B430B3A4D4C4321046B990 /* Archive+Deprecated.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "Archive+Deprecated.swift"; path = "Sources/ZIPFoundation/Archive+Deprecated.swift"; sourceTree = ""; }; + 5C19BCB3C801B3E6D0A09384A7ACE1FD /* RDEPUBReaderController+TableOfContents.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "RDEPUBReaderController+TableOfContents.swift"; sourceTree = ""; }; 5D62C5531667F364F14DB67D33FFA554 /* DTCoreTextParagraphStyle.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = DTCoreTextParagraphStyle.h; path = Core/Source/DTCoreTextParagraphStyle.h; sourceTree = ""; }; 5D8DA3839256D5A245E96F2C67A5E232 /* wxread-default.css */ = {isa = PBXFileReference; includeInIndex = 1; name = "wxread-default.css"; path = "Sources/RDReaderView/EPUBCore/Resources/wxread-default.css"; sourceTree = ""; }; 5DA1E8494B86FFD79C639DCB8C21E1BB /* DTPieProgressIndicator.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = DTPieProgressIndicator.m; path = Core/Source/iOS/DTPieProgressIndicator.m; sourceTree = ""; }; - 5E030AC563D5D21F6792CA5056667F3F /* RDEPUBTextPageRenderView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBTextPageRenderView.swift; sourceTree = ""; }; 5E791886E59DC0FAB02C6BBAB5BE5B6B /* DTCoreTextFontCollection.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = DTCoreTextFontCollection.m; path = Core/Source/DTCoreTextFontCollection.m; sourceTree = ""; }; - 5EFD8EDA8C70B31844B0A06CBCD0B403 /* RDEPUBReaderDelegate.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBReaderDelegate.swift; sourceTree = ""; }; 5F760D62F6C6F083374702146337F2CB /* DTCoreTextParagraphStyle.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = DTCoreTextParagraphStyle.m; path = Core/Source/DTCoreTextParagraphStyle.m; sourceTree = ""; }; 5F991240340FA026C71B68EB25B9F9C8 /* Entry+Serialization.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "Entry+Serialization.swift"; path = "Sources/ZIPFoundation/Entry+Serialization.swift"; sourceTree = ""; }; 5FAB3C7F19A511C2B1B7DFBA3CA308C4 /* DTFoundation-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "DTFoundation-umbrella.h"; sourceTree = ""; }; @@ -729,28 +746,30 @@ 64AB89EAB10148C39B726B26BE04F4F8 /* DTCoreTextLayoutLine.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = DTCoreTextLayoutLine.h; path = Core/Source/DTCoreTextLayoutLine.h; sourceTree = ""; }; 6578DFAB51436E8DF266FD087700E26B /* ConstraintDescription.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintDescription.swift; path = Sources/ConstraintDescription.swift; sourceTree = ""; }; 667442978548AAC9B24B14B6F2597536 /* RDEPUBParser+ReadingProfile.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "RDEPUBParser+ReadingProfile.swift"; sourceTree = ""; }; - 6703BD7A2507028A11319843D0D7945F /* RDEPUBReaderChapterListController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBReaderChapterListController.swift; sourceTree = ""; }; 694DC251BF921B37330A35A6ACD9DF62 /* RDEPUBChapterPageCounter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBChapterPageCounter.swift; sourceTree = ""; }; 69F083BF98854C4CB74C7C21DA34399A /* RDEPUBTextAnchor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBTextAnchor.swift; sourceTree = ""; }; 6A1EBB9497F4846C84CA0BF5E4F84FC7 /* DTLog.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = DTLog.h; path = Core/Source/DTLog.h; sourceTree = ""; }; 6A2067EA81E19AA5F8E66DE4300DAB4A /* RDEPUBModels.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBModels.swift; sourceTree = ""; }; + 6A34CA64CF765506AA4947EE31BD5B53 /* RDEPUBLocationConverter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBLocationConverter.swift; sourceTree = ""; }; + 6A64FCFADABD1D922738FCDF1220C149 /* RDEPUBWebDecorationOverlayView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBWebDecorationOverlayView.swift; sourceTree = ""; }; + 6B39813FB9B6B3B278FF0DE60E8F41A2 /* RDEPUBChapterSummaryDiskCache.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBChapterSummaryDiskCache.swift; sourceTree = ""; }; 6B96B0998B49D8E149C995474DA5FE5F /* DTHTMLWriter.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = DTHTMLWriter.h; path = Core/Source/DTHTMLWriter.h; sourceTree = ""; }; 6C647A2EB2720BCBF88B160D124B57BF /* UILayoutSupport+Extensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "UILayoutSupport+Extensions.swift"; path = "Sources/UILayoutSupport+Extensions.swift"; sourceTree = ""; }; 6CB839342BEE5213E1F9D59970EEC467 /* epub-fixed-layout.html */ = {isa = PBXFileReference; includeInIndex = 1; name = "epub-fixed-layout.html"; path = "Sources/RDReaderView/EPUBCore/Resources/epub-fixed-layout.html"; sourceTree = ""; }; + 6CD82AF8E46DDEC2A04CAD90F008B98C /* RDEPUBBackgroundTrace.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBBackgroundTrace.swift; sourceTree = ""; }; 6D2EC0B845131A88247902AEA26F40AE /* SSAlertAnimation.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SSAlertAnimation.swift; path = Sources/SSAlertSwift/SSAlertAnimation.swift; sourceTree = ""; }; 6D86C7063D1128EAEB3A30EBEF214B3F /* NSString+DTFormatNumbers.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSString+DTFormatNumbers.h"; path = "Core/Source/NSString+DTFormatNumbers.h"; sourceTree = ""; }; 70F5D03B9B3B7F182C8F5C1F5D4FA7E7 /* DTTiledLayerWithoutFade.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = DTTiledLayerWithoutFade.m; path = Core/Source/iOS/DTTiledLayerWithoutFade.m; sourceTree = ""; }; 71171943FCD1D8271266E2F6347D503B /* RDEPUBWebView+FixedLayout.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "RDEPUBWebView+FixedLayout.swift"; sourceTree = ""; }; + 712AC2E76CBFB2863C3DA9B4EBA22C38 /* RDEPUBChapterOffsetMap.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBChapterOffsetMap.swift; sourceTree = ""; }; 713C9FD0354D16AFCCFA630A97F2DA68 /* NSCharacterSet+HTML.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSCharacterSet+HTML.m"; path = "Core/Source/NSCharacterSet+HTML.m"; sourceTree = ""; }; 7176B677A350927FC670368313DC2CA1 /* ConstraintConfig.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintConfig.swift; path = Sources/ConstraintConfig.swift; sourceTree = ""; }; 71E2D85A099C99CD82FD1842EE7C52D2 /* NSAttributedString+DTDebug.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSAttributedString+DTDebug.h"; path = "Core/Source/NSAttributedString+DTDebug.h"; sourceTree = ""; }; - 72E8D63E9D57DA89DEE2774A89A61152 /* RDEPUBReaderAssemblyCoordinator.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBReaderAssemblyCoordinator.swift; sourceTree = ""; }; 731C0C61E3AD227D691FBA1C2C17BF7B /* NSURL+DTComparing.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSURL+DTComparing.h"; path = "Core/Source/NSURL+DTComparing.h"; sourceTree = ""; }; 7345CE45F696634CEA692A86D16F684C /* Entry+ZIP64.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "Entry+ZIP64.swift"; path = "Sources/ZIPFoundation/Entry+ZIP64.swift"; sourceTree = ""; }; 73940D215D644B6815A09DA802CFBDDF /* NSArray+DTError.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSArray+DTError.m"; path = "Core/Source/NSArray+DTError.m"; sourceTree = ""; }; 73A99927E52B24583A6A2CF7E6F23848 /* DTVideoTextAttachment.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = DTVideoTextAttachment.h; path = Core/Source/DTVideoTextAttachment.h; sourceTree = ""; }; 7429300C02F7E8453A429E08C9BD10EA /* RDEPUBReadingSession.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBReadingSession.swift; sourceTree = ""; }; - 74F9C4D562F4650166F650997ADA132B /* RDEPUBReaderController+RenderSupport.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "RDEPUBReaderController+RenderSupport.swift"; sourceTree = ""; }; 75838C8BB89B6B0B142EC3F6922D925B /* epub-bridge.js */ = {isa = PBXFileReference; includeInIndex = 1; name = "epub-bridge.js"; path = "Sources/RDReaderView/EPUBCore/Resources/epub-bridge.js"; sourceTree = ""; }; 75C5986EF2D63801A8A30B9D77A7FDD0 /* ResourceBundle-ZIPFoundation_Privacy-ZIPFoundation-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "ResourceBundle-ZIPFoundation_Privacy-ZIPFoundation-Info.plist"; sourceTree = ""; }; 75FA8E843D9225BD65EB9D17825EC0F4 /* NSCharacterSet+HTML.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSCharacterSet+HTML.h"; path = "Core/Source/NSCharacterSet+HTML.h"; sourceTree = ""; }; @@ -759,11 +778,13 @@ 77A613841EB90827AF321DD50B21DB5D /* DTAttributedTextView.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = DTAttributedTextView.m; path = Core/Source/DTAttributedTextView.m; sourceTree = ""; }; 78873FAFC0AD9E930D6050A7F976714B /* DTCoreText.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = DTCoreText.debug.xcconfig; sourceTree = ""; }; 78EA0DF405F8465A86BFD3DDFD2EC295 /* Pods-ReadViewDemo */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = "Pods-ReadViewDemo"; path = Pods_ReadViewDemo.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 7956987E212CE94F57F9CF48F563CE8F /* RDEPUBReaderChapterListController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBReaderChapterListController.swift; sourceTree = ""; }; 79A77584AB3B92B2508AE0BCFCB8FD87 /* UIView+DTFoundation.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UIView+DTFoundation.h"; path = "Core/Source/iOS/UIView+DTFoundation.h"; sourceTree = ""; }; 7AB9016408894675D11069CDAACEDE94 /* DTImage+HTML.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "DTImage+HTML.h"; path = "Core/Source/DTImage+HTML.h"; sourceTree = ""; }; 7C496AB6A73C2D0BD0FF340A6A2960D5 /* DTHTMLAttributedStringBuilder.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = DTHTMLAttributedStringBuilder.m; path = Core/Source/DTHTMLAttributedStringBuilder.m; sourceTree = ""; }; 7D63AE1801C7AB149219317BE7529B26 /* DTAnchorHTMLElement.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = DTAnchorHTMLElement.m; path = Core/Source/DTAnchorHTMLElement.m; sourceTree = ""; }; 7E2F4C746E0B7479BE677A20358CB93B /* RDEPUBSemanticMarkerInjector.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBSemanticMarkerInjector.swift; sourceTree = ""; }; + 7EB287A47D1547ED7E637E14A5AEA64C /* RDEPUBReaderToolView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBReaderToolView.swift; sourceTree = ""; }; 7ED26E7B899A0B29A202F0CB011D5F09 /* RDEPUBTextBookBuilder.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBTextBookBuilder.swift; sourceTree = ""; }; 7F04C6EEE7B8C63B04236F35AE34B289 /* wxread-replace.css */ = {isa = PBXFileReference; includeInIndex = 1; name = "wxread-replace.css"; path = "Sources/RDReaderView/EPUBCore/Resources/wxread-replace.css"; sourceTree = ""; }; 7F051D813D50D20D53205D5FDCAA877C /* UIScreen+DTFoundation.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UIScreen+DTFoundation.m"; path = "Core/Source/iOS/UIScreen+DTFoundation.m"; sourceTree = ""; }; @@ -773,10 +794,10 @@ 8272D0B59ABD6652D0CAE3DFCC2C5EE1 /* DTActivityTitleView.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = DTActivityTitleView.h; path = Core/Source/iOS/DTActivityTitleView.h; sourceTree = ""; }; 83410CC9CF2ABE63B90A92F2F988BF65 /* RDReaderView-RDReaderViewAssets */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; name = "RDReaderView-RDReaderViewAssets"; path = RDReaderViewAssets.bundle; sourceTree = BUILT_PRODUCTS_DIR; }; 83E37DC74BB53F90A5AD54842858A62F /* NSString+Paragraphs.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSString+Paragraphs.h"; path = "Core/Source/NSString+Paragraphs.h"; sourceTree = ""; }; - 851EF2369FB225D981E94F4BB2E9F65F /* RDEPUBReaderDependencies.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBReaderDependencies.swift; sourceTree = ""; }; + 840B0D63F6448DEAD22C81BD1837E5CF /* RDEPUBPageInteractionController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBPageInteractionController.swift; sourceTree = ""; }; 8552BE1A1F6A6D2879763DE3B2B8A2DD /* NSNumber+RomanNumerals.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSNumber+RomanNumerals.h"; path = "Core/Source/NSNumber+RomanNumerals.h"; sourceTree = ""; }; 8607E8F20399521FFA80047941709E8C /* RDEPUBTextPerformanceSampler.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBTextPerformanceSampler.swift; sourceTree = ""; }; - 862E061063601D25DB09C808CA390C18 /* RDEPUBReaderTheme.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBReaderTheme.swift; sourceTree = ""; }; + 8639CA8074A14D276D10928D93D15842 /* RDEPUBSelectionOverlayView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBSelectionOverlayView.swift; sourceTree = ""; }; 86A0361390AA0C29BC1A5487083A12A7 /* DTColorFunctions.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = DTColorFunctions.h; path = Core/Source/DTColorFunctions.h; sourceTree = ""; }; 882FA707ED3513D263A1E0656AAD2D33 /* ConstraintPriorityTarget.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintPriorityTarget.swift; path = Sources/ConstraintPriorityTarget.swift; sourceTree = ""; }; 8830746779660E908FADB2DEAD879AA2 /* RDEPUBSearchModels.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBSearchModels.swift; sourceTree = ""; }; @@ -785,25 +806,24 @@ 8B35D0622F339A5B8A864FAA0C9EAE47 /* Pods-ReadViewDemo-acknowledgements.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-ReadViewDemo-acknowledgements.plist"; sourceTree = ""; }; 8B57C7D26C0F345A62E14DAB51521E69 /* RDReaderPreloadController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDReaderPreloadController.swift; sourceTree = ""; }; 8B59C9A1B265393E4BA8383B308A5917 /* DTHTMLAttributedStringBuilder.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = DTHTMLAttributedStringBuilder.h; path = Core/Source/DTHTMLAttributedStringBuilder.h; sourceTree = ""; }; - 8B8F6026114BC156F5EAC3906C3DAD24 /* RDEPUBReaderController+DataSource.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "RDEPUBReaderController+DataSource.swift"; sourceTree = ""; }; 8BD568D38C209BA1CC0EBFA809A35204 /* DTFoundationConstants.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = DTFoundationConstants.m; path = Core/Source/DTFoundationConstants.m; sourceTree = ""; }; 8BD9F2023FF826F67E69E3407A70A8A6 /* DTVersion.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = DTVersion.h; path = Core/Source/DTVersion.h; sourceTree = ""; }; 8CEAB7724C9E71FFC8A368EECE6AADF9 /* RDEPUBChapterData.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBChapterData.swift; sourceTree = ""; }; 8CFEE298DD52246B46E6FA62B16B38DF /* DTTextHTMLElement.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = DTTextHTMLElement.m; path = Core/Source/DTTextHTMLElement.m; sourceTree = ""; }; + 8DF4AADE4D7A63EA46F2265946368FA8 /* RDEPUBRuntimePageCount.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBRuntimePageCount.swift; sourceTree = ""; }; 8E3E834CCC952717089550F50A2DE59C /* SSAlertSwift-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "SSAlertSwift-Info.plist"; sourceTree = ""; }; 8E5637603691B9B35C2A886B686D3CE7 /* ConstraintViewDSL.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintViewDSL.swift; path = Sources/ConstraintViewDSL.swift; sourceTree = ""; }; 8F2D44B82815CA3397BE2C9F64819B88 /* DTCoreTextFunctions.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = DTCoreTextFunctions.m; path = Core/Source/DTCoreTextFunctions.m; sourceTree = ""; }; 8F950CEF31AC0065C0BEAB26BA2CEFA1 /* ConstraintOffsetTarget.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintOffsetTarget.swift; path = Sources/ConstraintOffsetTarget.swift; sourceTree = ""; }; - 8FD217FE3F87580A8BA3BD7D8992D4EB /* RDEPUBReaderViewportMonitor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBReaderViewportMonitor.swift; sourceTree = ""; }; 91144FC099DD458E61625CD939577DAA /* ConstraintDirectionalInsets.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintDirectionalInsets.swift; path = Sources/ConstraintDirectionalInsets.swift; sourceTree = ""; }; 9129D37C7F89AD8FD530663BB4503FC0 /* NSCoder+DTCompatibility.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSCoder+DTCompatibility.m"; path = "Core/Source/NSCoder+DTCompatibility.m"; sourceTree = ""; }; 92460D196C5300656C9649784EBD3C58 /* NSAttributedString+HTML.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSAttributedString+HTML.m"; path = "Core/Source/NSAttributedString+HTML.m"; sourceTree = ""; }; 932FD688DE493323BA6B691BC2CB5094 /* LayoutConstraint.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = LayoutConstraint.swift; path = Sources/LayoutConstraint.swift; sourceTree = ""; }; - 933F9E11CFE9DD8A63C31D119B381969 /* UIColor+RDEPUBHex.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "UIColor+RDEPUBHex.swift"; sourceTree = ""; }; 934F9BB2B3A313294F121484C3A97A14 /* Archive+ReadingDeprecated.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "Archive+ReadingDeprecated.swift"; path = "Sources/ZIPFoundation/Archive+ReadingDeprecated.swift"; sourceTree = ""; }; 937CEFA81DA4227008DBFB114783A08B /* SSAlertSwift-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "SSAlertSwift-umbrella.h"; sourceTree = ""; }; 9392F7CFCA8C63DD163AAB74B900D729 /* RDReaderView+ToolView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "RDReaderView+ToolView.swift"; sourceTree = ""; }; 945D4549E0B9168D7DDB910DE874B874 /* Typealiases.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Typealiases.swift; path = Sources/Typealiases.swift; sourceTree = ""; }; + 9511E297D368C83173C273682467DC17 /* RDEPUBReaderRuntime.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBReaderRuntime.swift; sourceTree = ""; }; 957DB4675D5CCBD3CB1762F08619F3F7 /* UIApplication+DTNetworkActivity.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UIApplication+DTNetworkActivity.h"; path = "Core/Source/iOS/UIApplication+DTNetworkActivity.h"; sourceTree = ""; }; 96EEB6B022E22A4ABC86D87469D4815B /* DTSmartPagingScrollView.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = DTSmartPagingScrollView.m; path = Core/Source/iOS/DTSmartPagingScrollView.m; sourceTree = ""; }; 96FA99AA7D4AD0D4246C8DAC1CC0C24E /* DTStylesheetHTMLElement.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = DTStylesheetHTMLElement.h; path = Core/Source/DTStylesheetHTMLElement.h; sourceTree = ""; }; @@ -817,13 +837,12 @@ 9A1B75050E71BDD96FC19EC6BA129E6A /* DTLinkButton.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = DTLinkButton.m; path = Core/Source/DTLinkButton.m; sourceTree = ""; }; 9AE2A1B83DCC99894642D65CE5EADFD4 /* RDEPUBNavigatorState.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBNavigatorState.swift; sourceTree = ""; }; 9B1FF16A8E3839BF599427F23328492C /* RDEPUBPreferences.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBPreferences.swift; sourceTree = ""; }; - 9BBE8424D2FE92DCAA8D2B1CA7D4D029 /* RDEPUBReaderConfiguration.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBReaderConfiguration.swift; sourceTree = ""; }; + 9B5B065EE6ECDEF29D79B1C769C0FF0F /* RDEPUBViewportTypes.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBViewportTypes.swift; sourceTree = ""; }; 9BFCAC7C2AEA867E67C63A852576774F /* NSURL+DTComparing.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSURL+DTComparing.m"; path = "Core/Source/NSURL+DTComparing.m"; sourceTree = ""; }; 9C6293E97E128E98559FB74D9A342EAD /* NSMutableString+HTML.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSMutableString+HTML.h"; path = "Core/Source/NSMutableString+HTML.h"; sourceTree = ""; }; 9CE078B465FDEF40EC0469626A2C2469 /* SnapKit-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "SnapKit-dummy.m"; sourceTree = ""; }; 9D6C48939A8498A09DCFC7F41ECB8A25 /* NSString+CSS.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSString+CSS.h"; path = "Core/Source/NSString+CSS.h"; sourceTree = ""; }; 9D940727FF8FB9C785EB98E56350EF41 /* Podfile */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; indentWidth = 2; lastKnownFileType = text; name = Podfile; path = ../Podfile; sourceTree = SOURCE_ROOT; tabWidth = 2; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; - 9DB5F45FEE54BD8D3F49BD345EDAB5D9 /* RDEPUBTextPageDecorationView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBTextPageDecorationView.swift; sourceTree = ""; }; 9E6BA0ECCB0ACF8580C3B5192CA9F11B /* ConstraintLayoutGuide.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintLayoutGuide.swift; path = Sources/ConstraintLayoutGuide.swift; sourceTree = ""; }; 9F8F40493DF0BBB3EA5FC1F759A883C4 /* ResourceBundle-Resources-DTCoreText-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "ResourceBundle-Resources-DTCoreText-Info.plist"; sourceTree = ""; }; A02EA0374C693F881AB36202FB276DCD /* ConstraintView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintView.swift; path = Sources/ConstraintView.swift; sourceTree = ""; }; @@ -831,82 +850,93 @@ A0BD48DAF5AE9DB087F2552E99069709 /* DTBlockFunctions.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = DTBlockFunctions.h; path = Core/Source/DTBlockFunctions.h; sourceTree = ""; }; A0EBAC7714A5F73B75AAB47D24B08B71 /* Constraint.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Constraint.swift; path = Sources/Constraint.swift; sourceTree = ""; }; A16842E6F1C3E8526D28C7DABA1D6898 /* RDEPUBStyleSheetComposer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBStyleSheetComposer.swift; sourceTree = ""; }; + A191B6108F3F50C4F1C76BE74D9612A9 /* RDEPUBChapterWindowCoordinator.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBChapterWindowCoordinator.swift; sourceTree = ""; }; A1B031E2B903C2F177195C1C3D1D1EDB /* DTCoreTextFunctions.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = DTCoreTextFunctions.h; path = Core/Source/DTCoreTextFunctions.h; sourceTree = ""; }; A276DE925253460239BFAEC7663F60D3 /* DTAttributedTextView.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = DTAttributedTextView.h; path = Core/Source/DTAttributedTextView.h; sourceTree = ""; }; A2BD2727B338CE5C2C3BC6E8B8A23DE9 /* rangy-core.js */ = {isa = PBXFileReference; includeInIndex = 1; name = "rangy-core.js"; path = "Sources/RDReaderView/EPUBCore/Resources/rangy-core.js"; sourceTree = ""; }; A2C299CFB47AA6ECF86F7348E1EB751F /* NSMutableAttributedString+HTML.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSMutableAttributedString+HTML.h"; path = "Core/Source/NSMutableAttributedString+HTML.h"; sourceTree = ""; }; + A341139B59F0CD6D051CABB66C8DC49C /* RDEPUBTextSelectionController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBTextSelectionController.swift; sourceTree = ""; }; A3565FEE19F19CB43A43409AFFF2BD2C /* NSString+DTPaths.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSString+DTPaths.m"; path = "Core/Source/NSString+DTPaths.m"; sourceTree = ""; }; A3765B289CB7E6C5DF6160D8EBC83C3C /* UIScreen+DTFoundation.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UIScreen+DTFoundation.h"; path = "Core/Source/iOS/UIScreen+DTFoundation.h"; sourceTree = ""; }; A3A5FB560E84A3FE29C291466EAE9E22 /* RDEPUBDTCoreTextRenderer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBDTCoreTextRenderer.swift; sourceTree = ""; }; - A42EA2D403B179C805701DD3638696B0 /* RDEPUBTextSelectionController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBTextSelectionController.swift; sourceTree = ""; }; + A3D719AE19AA83101150C2A3287D7E31 /* RDEPUBReaderSettings.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBReaderSettings.swift; sourceTree = ""; }; A4E43DCDE0C8751557E7E65FEEB1C865 /* DTLazyImageView.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = DTLazyImageView.m; path = Core/Source/DTLazyImageView.m; sourceTree = ""; }; + A5EBB29FB787860E6DAF09A9B32BD92D /* RDEPUBReaderViewportMonitor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBReaderViewportMonitor.swift; sourceTree = ""; }; A651CEE8062C84A597DBC44ECA3DBCC1 /* ConstraintDirectionalInsetTarget.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintDirectionalInsetTarget.swift; path = Sources/ConstraintDirectionalInsetTarget.swift; sourceTree = ""; }; A68D72764140BC3DF67A2B9E52CEC7A4 /* DTHorizontalRuleHTMLElement.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = DTHorizontalRuleHTMLElement.m; path = Core/Source/DTHorizontalRuleHTMLElement.m; sourceTree = ""; }; A6B5316985734E51970FA0FC168498A6 /* RDReaderView.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = RDReaderView.release.xcconfig; sourceTree = ""; }; A8DFDCAB69B471EB5667C964865C26CF /* DTCoreGraphicsUtils.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = DTCoreGraphicsUtils.m; path = Core/Source/DTCoreGraphicsUtils.m; sourceTree = ""; }; + A969E1A1BC7F8699EB7BF9147AD900A9 /* RDEPUBReaderLocationCoordinator.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBReaderLocationCoordinator.swift; sourceTree = ""; }; A9FBC69C7DA29A7283920FDA9EBBCE8A /* DTAnimatedGIF.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = DTAnimatedGIF.h; path = Core/Source/iOS/DTAnimatedGIF/DTAnimatedGIF.h; sourceTree = ""; }; AA0811310099A4BCB3D52683B70DB3D1 /* Pods-ReadViewDemo-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-ReadViewDemo-dummy.m"; sourceTree = ""; }; AA3CFD777AD76F97EBA05B3920F5786A /* DTIframeTextAttachment.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = DTIframeTextAttachment.m; path = Core/Source/DTIframeTextAttachment.m; sourceTree = ""; }; AA479EAA368CC8B528282E49B6C96BC8 /* RDEPUBPaginator.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBPaginator.swift; sourceTree = ""; }; + AC570F5F2BA388E5FCEA6C6A68382779 /* RDEPUBPageCountCache.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBPageCountCache.swift; sourceTree = ""; }; + AD75C2171FA5F398BB9876FBECBE3002 /* RDEPUBPageResolver.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBPageResolver.swift; sourceTree = ""; }; AD987C2FA7C8FBC96FD024E421DA0E68 /* Archive+Progress.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "Archive+Progress.swift"; path = "Sources/ZIPFoundation/Archive+Progress.swift"; sourceTree = ""; }; ADC5AC8FD4EC45E9BA5654A38715BB93 /* DTTextBlock.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = DTTextBlock.m; path = Core/Source/DTTextBlock.m; sourceTree = ""; }; AE4740D05411538E3715704ED63C6E76 /* RDEPUBParser+Resources.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "RDEPUBParser+Resources.swift"; sourceTree = ""; }; AFC4C9F69BF2D0CAEBAA83896C139D2C /* DTColorFunctions.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = DTColorFunctions.m; path = Core/Source/DTColorFunctions.m; sourceTree = ""; }; + AFD72846E6D1DCE0C8CF2D35F2179BB6 /* RDURLReaderController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDURLReaderController.swift; sourceTree = ""; }; AFE0B56638AFB77F4455784A7ED59876 /* DTDictationPlaceholderView.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = DTDictationPlaceholderView.m; path = Core/Source/DTDictationPlaceholderView.m; sourceTree = ""; }; B0500CA69405E23B09291359EB28331E /* NSMutableAttributedString+HTML.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSMutableAttributedString+HTML.m"; path = "Core/Source/NSMutableAttributedString+HTML.m"; sourceTree = ""; }; + B07F9611B0A5BBD519B68A10C362BE50 /* RDEPUBReaderDelegate.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBReaderDelegate.swift; sourceTree = ""; }; B0AC307F12521444C973EA0DE38B4608 /* NSMutableArray+DTMoving.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSMutableArray+DTMoving.h"; path = "Core/Source/NSMutableArray+DTMoving.h"; sourceTree = ""; }; B0B32EA4B7A492EEF26CE260E9AEFA0B /* ConstraintMakerFinalizable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintMakerFinalizable.swift; path = Sources/ConstraintMakerFinalizable.swift; sourceTree = ""; }; B1DD2563775FC83493319BFC4E953F00 /* RDEPUBWebView+Search.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "RDEPUBWebView+Search.swift"; sourceTree = ""; }; B245F7E143F3091652C64F6593E33F7A /* DTFolderMonitor.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = DTFolderMonitor.m; path = Core/Source/DTFolderMonitor.m; sourceTree = ""; }; + B2823BBB9462E120DC6A762A1AD5224C /* RDEPUBReaderAssemblyCoordinator.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBReaderAssemblyCoordinator.swift; sourceTree = ""; }; B2B3D0CB47B0A8E9EC5AF48B7F2DD6B0 /* DTCoreTextConstants.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = DTCoreTextConstants.m; path = Core/Source/DTCoreTextConstants.m; sourceTree = ""; }; B3346B9CF2882D4D658CE30CDCCC93CA /* RDReaderFlowLayout.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDReaderFlowLayout.swift; sourceTree = ""; }; B50104DE2E35D820ECA22CDB3E6F96EA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS18.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; B509CD4B498635D4536245E43D802ED9 /* DTCSSListStyle.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = DTCSSListStyle.m; path = Core/Source/DTCSSListStyle.m; sourceTree = ""; }; + B58B13E167D03567209323F2033289A1 /* RDEPUBChapterLoader.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBChapterLoader.swift; sourceTree = ""; }; B58EB90E2A4610839F356FE9B2A97486 /* RDEPUBFragmentMarkerInjector.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBFragmentMarkerInjector.swift; sourceTree = ""; }; B5FCE04EBB553439D1C2BA1001936293 /* DTCompatibility.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = DTCompatibility.h; path = Core/Source/DTCompatibility.h; sourceTree = ""; }; B6425B2235BA00FE865AF58EF44F1144 /* DTCoreGraphicsUtils.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = DTCoreGraphicsUtils.h; path = Core/Source/DTCoreGraphicsUtils.h; sourceTree = ""; }; - B69E0662B2604A49919B5DDA504A39C4 /* RDEPUBWebContentView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBWebContentView.swift; sourceTree = ""; }; B7672A26922DBD934FA1AC60D49EB322 /* NSNumber+RomanNumerals.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSNumber+RomanNumerals.m"; path = "Core/Source/NSNumber+RomanNumerals.m"; sourceTree = ""; }; B8E40E302B85493BDE0FE91F829EC01E /* DTBase64Coding.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = DTBase64Coding.h; path = Core/Source/DTBase64Coding.h; sourceTree = ""; }; + B912A462ED162FF141A1E16AFA1FD229 /* RDEPUBReaderPaginationCoordinator.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBReaderPaginationCoordinator.swift; sourceTree = ""; }; B92A6483AF878B266B8FE776A9A6F4B3 /* RDReaderView-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "RDReaderView-dummy.m"; sourceTree = ""; }; B9DCB5EC0B1CDADD221717CADDF62359 /* SnapKit-SnapKit_Privacy */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; name = "SnapKit-SnapKit_Privacy"; path = SnapKit_Privacy.bundle; sourceTree = BUILT_PRODUCTS_DIR; }; B9E40164920C9AC1B1413C51835CD370 /* DTCoreText-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "DTCoreText-Info.plist"; sourceTree = ""; }; + BB41DCB393D82A80671953233E936F5F /* RDEPUBReaderTableOfContentsItem.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBReaderTableOfContentsItem.swift; sourceTree = ""; }; BC332335B380BC51C4F05C5CCDFB9189 /* DTAnimatedGIF.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = DTAnimatedGIF.m; path = Core/Source/iOS/DTAnimatedGIF/DTAnimatedGIF.m; sourceTree = ""; }; BCCA590DB72AA93275262A414F16578A /* DTCoreTextFontDescriptor.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = DTCoreTextFontDescriptor.m; path = Core/Source/DTCoreTextFontDescriptor.m; sourceTree = ""; }; - BD245FC216DD480CF148530003BAA4BC /* RDEPUBReaderHighlightsViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBReaderHighlightsViewController.swift; sourceTree = ""; }; BD768C27E392C934C06DFB4AC401037C /* ConstraintMakerRelatable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintMakerRelatable.swift; path = Sources/ConstraintMakerRelatable.swift; sourceTree = ""; }; BDD081272B47A3B73CE693D21DC26827 /* Entry.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Entry.swift; path = Sources/ZIPFoundation/Entry.swift; sourceTree = ""; }; BE13C3BA99810308E47EC53C68F6169A /* RDEPUBTextBookCache.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBTextBookCache.swift; sourceTree = ""; }; BE547E464E5D34F9FFB24BAC81D1E4CC /* NSString+DTUtilities.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSString+DTUtilities.h"; path = "Core/Source/NSString+DTUtilities.h"; sourceTree = ""; }; - BE9D5BE3BC1B9E7C17553D6377DAA5D3 /* RDEPUBReaderTopToolView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBReaderTopToolView.swift; sourceTree = ""; }; BF4F3EC137168B92A5DA85AF50CD239F /* NSAttributedString+DTDebug.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSAttributedString+DTDebug.m"; path = "Core/Source/NSAttributedString+DTDebug.m"; sourceTree = ""; }; C123C127CC539ACCB510F665DB774899 /* DTAttributedTextContentView.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = DTAttributedTextContentView.h; path = Core/Source/DTAttributedTextContentView.h; sourceTree = ""; }; C18480913E002315E3F5EC6945371697 /* NSDictionary+DTError.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSDictionary+DTError.m"; path = "Core/Source/NSDictionary+DTError.m"; sourceTree = ""; }; C241AD9CEC9D6732CFBA6A55B49E64C9 /* ConstraintInsets.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintInsets.swift; path = Sources/ConstraintInsets.swift; sourceTree = ""; }; - C26BE3D7337938B1DB1B7610001CD169 /* RDEPUBReaderLocationCoordinator.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBReaderLocationCoordinator.swift; sourceTree = ""; }; C26DBE1150BC9A30B69839783E000358 /* DTCoreTextLayouter.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = DTCoreTextLayouter.h; path = Core/Source/DTCoreTextLayouter.h; sourceTree = ""; }; C274719939F1335BCB8FA97636A9D2DD /* RDEPUBAnnotationModels.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBAnnotationModels.swift; sourceTree = ""; }; C2840BE6D91479CCBC2E908FBD9DA183 /* DTCoreText.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = DTCoreText.release.xcconfig; sourceTree = ""; }; C298199F5DC5390E8B6666F638B34C10 /* DTTextAttachmentHTMLElement.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = DTTextAttachmentHTMLElement.h; path = Core/Source/DTTextAttachmentHTMLElement.h; sourceTree = ""; }; + C2CC94883B1A94347DB0BFAFF179364D /* RDEPUBReaderAnnotationCoordinator.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBReaderAnnotationCoordinator.swift; sourceTree = ""; }; C30D143134EEC5D4BA99459104B76A2E /* DTHTMLParserTextNode.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = DTHTMLParserTextNode.h; path = Core/Source/DTHTMLParserTextNode.h; sourceTree = ""; }; C422BCADBB48E28B549495663705A0FB /* DTWeakSupport.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = DTWeakSupport.h; path = Core/Source/DTWeakSupport.h; sourceTree = ""; }; - C55B9A91FBFB85980E2E199DC0011868 /* RDEPUBReaderSettings.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBReaderSettings.swift; sourceTree = ""; }; + C554BE53564FC720F9EBBB4B856BA87D /* RDEPUBReaderController+PublicAPI.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "RDEPUBReaderController+PublicAPI.swift"; sourceTree = ""; }; C637C7A440920FA0074A7ECF39D9C6F8 /* RDReaderTapRegionHandler.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDReaderTapRegionHandler.swift; sourceTree = ""; }; C6A32D83CDE7A6494252691BE7E99638 /* RDEPUBTextLayouter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBTextLayouter.swift; sourceTree = ""; }; C6DB34ACB132E465CDF8191F7DB71EF6 /* ConstraintView+Extensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "ConstraintView+Extensions.swift"; path = "Sources/ConstraintView+Extensions.swift"; sourceTree = ""; }; + C702F88F0474C4EB2C57ACE7E18A4B18 /* RDEPUBTextAnnotationOverlay.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBTextAnnotationOverlay.swift; sourceTree = ""; }; C71D83D6AEF198FA680B8A80BF2D9605 /* DTCoreTextLayoutLine.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = DTCoreTextLayoutLine.m; path = Core/Source/DTCoreTextLayoutLine.m; sourceTree = ""; }; C80065DBA5634DAA71129404975D7349 /* NSURL+DTUnshorten.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSURL+DTUnshorten.m"; path = "Core/Source/NSURL+DTUnshorten.m"; sourceTree = ""; }; C8343CDCF82729A8EBCCA37A6650E34F /* DTHTMLParserNode.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = DTHTMLParserNode.h; path = Core/Source/DTHTMLParserNode.h; sourceTree = ""; }; + C8B8050E89617564241284BAA5AE661C /* String+SHA256.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "String+SHA256.swift"; sourceTree = ""; }; C8C20D9A71E6605DEE80342E7122B683 /* DTCoreTextConstants.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = DTCoreTextConstants.h; path = Core/Source/DTCoreTextConstants.h; sourceTree = ""; }; C8E2FC7BE14F90878CE3FB3E317996ED /* ResourceBundle-SnapKit_Privacy-SnapKit-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "ResourceBundle-SnapKit_Privacy-SnapKit-Info.plist"; sourceTree = ""; }; C9087CF398AFBBC8FFB5C60E6389EAB1 /* RDEPUBParser+Archive.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "RDEPUBParser+Archive.swift"; sourceTree = ""; }; C9593E7B37569219F8D9A504B47707DC /* ConstraintAttributes.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintAttributes.swift; path = Sources/ConstraintAttributes.swift; sourceTree = ""; }; C960B7AA0B9B8CC473FACFEC64F9906D /* SnapKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = SnapKit.debug.xcconfig; sourceTree = ""; }; C996E23A573AA37FB9B35A09725C1D1D /* RDEPUBCoreTextPageFrameFactory.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBCoreTextPageFrameFactory.swift; sourceTree = ""; }; - CA31C1F2D2AF87B527C261FF3EF62C58 /* RDEPUBPageInteractionController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBPageInteractionController.swift; sourceTree = ""; }; CA4A114775B98CEA4A566C18123B145F /* ZIPFoundation-ZIPFoundation_Privacy */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; name = "ZIPFoundation-ZIPFoundation_Privacy"; path = ZIPFoundation_Privacy.bundle; sourceTree = BUILT_PRODUCTS_DIR; }; CA538A48DD3691701EC6B5021D7B7381 /* NSScanner+HTML.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSScanner+HTML.m"; path = "Core/Source/NSScanner+HTML.m"; sourceTree = ""; }; CB7F3535507F56824BA21509302CF8B3 /* NSString+DTURLEncoding.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSString+DTURLEncoding.m"; path = "Core/Source/NSString+DTURLEncoding.m"; sourceTree = ""; }; + CBF8CA6102062C9482CC8D4170DC3454 /* RDEPUBTextPageDecorationView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBTextPageDecorationView.swift; sourceTree = ""; }; CE6907AC04870132CCE3177E894607DD /* DTCoreTextLayoutFrame.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = DTCoreTextLayoutFrame.h; path = Core/Source/DTCoreTextLayoutFrame.h; sourceTree = ""; }; CE7768E097F8BDE9FBE3752B98CD0101 /* DTCoreText-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "DTCoreText-umbrella.h"; sourceTree = ""; }; CEA93EDA6ED54D374265AA5D6B9EBBA9 /* NSArray+DTError.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSArray+DTError.h"; path = "Core/Source/NSArray+DTError.h"; sourceTree = ""; }; @@ -919,11 +949,11 @@ D366C7AF049977D42FB1B20524EB0B15 /* RDEPUBTextPaginationSupport.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBTextPaginationSupport.swift; sourceTree = ""; }; D36ED8209C907E3E9B074DDB399F54FF /* DTTextAttachment.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = DTTextAttachment.h; path = Core/Source/DTTextAttachment.h; sourceTree = ""; }; D4093D30B372960168C1349100DF7DE0 /* DTAttributedTextCell.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = DTAttributedTextCell.h; path = Core/Source/DTAttributedTextCell.h; sourceTree = ""; }; - D479BB941D318A247DDD0A5C55B859C4 /* RDEPUBReaderController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBReaderController.swift; sourceTree = ""; }; D495E2AB1223BD4D4909F8FF1FF47DAF /* Debugging.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Debugging.swift; path = Sources/Debugging.swift; sourceTree = ""; }; D497C3CA364E37DAAFF2C3B90CC7DBE8 /* DTTextBlock.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = DTTextBlock.h; path = Core/Source/DTTextBlock.h; sourceTree = ""; }; D49AD091BED2546F36EC238A5E811D8D /* RDReaderView.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = RDReaderView.modulemap; sourceTree = ""; }; D6422B9C1E04A7B5F008E4D4ED496CE6 /* RDReaderSpreadResolver.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDReaderSpreadResolver.swift; sourceTree = ""; }; + D6E3749137D028A340460A72BA52D076 /* RDEPUBReaderController+RenderSupport.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "RDEPUBReaderController+RenderSupport.swift"; sourceTree = ""; }; D7B5E61166782ED8D585D4FCEE2074DB /* DTLinkButton.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = DTLinkButton.h; path = Core/Source/DTLinkButton.h; sourceTree = ""; }; D80CD20CB452B31CCCB14AF8567524D3 /* DTCoreText-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "DTCoreText-prefix.pch"; sourceTree = ""; }; D85A85D891A320165843EF9F489178F7 /* UIImage+DTFoundation.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UIImage+DTFoundation.m"; path = "Core/Source/iOS/UIImage+DTFoundation.m"; sourceTree = ""; }; @@ -951,19 +981,22 @@ E20272295EC253826AEE1F83A15D47C5 /* NSAttributedStringRunDelegates.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = NSAttributedStringRunDelegates.h; path = Core/Source/NSAttributedStringRunDelegates.h; sourceTree = ""; }; E27C503C8128BC3CE109FFE077AEF325 /* NSString+CSS.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSString+CSS.m"; path = "Core/Source/NSString+CSS.m"; sourceTree = ""; }; E28CA95D89DBAB2FE56BC8513D5838D7 /* DTHorizontalRuleHTMLElement.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = DTHorizontalRuleHTMLElement.h; path = Core/Source/DTHorizontalRuleHTMLElement.h; sourceTree = ""; }; + E432EFA64B2640653A9B32C165EDA933 /* UIColor+RDEPUBHex.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "UIColor+RDEPUBHex.swift"; sourceTree = ""; }; E481007A8174943868EBC81CF3B04B9F /* SSAlertPresentAnimation.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SSAlertPresentAnimation.swift; path = Sources/SSAlertSwift/SSAlertPresentAnimation.swift; sourceTree = ""; }; E5F69A1E1260BAD7737BEB533F643A15 /* DTLazyImageView.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = DTLazyImageView.h; path = Core/Source/DTLazyImageView.h; sourceTree = ""; }; E62C5E593037EF8A45E7FAA847D0277C /* DTCoreTextLayoutFrame+Cursor.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "DTCoreTextLayoutFrame+Cursor.m"; path = "Core/Source/DTCoreTextLayoutFrame+Cursor.m"; sourceTree = ""; }; + E67C6A6267B473D5F1B33C17A6DAFED4 /* RDEPUBReaderLoadCoordinator.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBReaderLoadCoordinator.swift; sourceTree = ""; }; + E6E22461422D4C78C300054E36E701BE /* RDEPUBReaderController+ContentDelegates.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "RDEPUBReaderController+ContentDelegates.swift"; sourceTree = ""; }; E6F2E79CC33E0823C4E719ECDDFBBE08 /* NSAttributedStringRunDelegates.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = NSAttributedStringRunDelegates.m; path = Core/Source/NSAttributedStringRunDelegates.m; sourceTree = ""; }; E8E7E1D7707960ECBBBA26D5E04CE1A8 /* RDReaderView+ContentAccess.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "RDReaderView+ContentAccess.swift"; sourceTree = ""; }; EBA0272559B0F7FAD8AC670AC7431188 /* Pods-ReadViewDemo-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Pods-ReadViewDemo-umbrella.h"; sourceTree = ""; }; EC9B5052FF6F4941CE226FF90ED457DE /* RDEPUBParser.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBParser.swift; sourceTree = ""; }; ECBA4C3DE9806BC9E5E66E0EE07FA019 /* RDReaderView.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = RDReaderView.debug.xcconfig; sourceTree = ""; }; ECDEDEE1D78BA473378CB3D26C486E5B /* WeReadApi.js */ = {isa = PBXFileReference; includeInIndex = 1; name = WeReadApi.js; path = Sources/RDReaderView/EPUBCore/Resources/WeReadApi.js; sourceTree = ""; }; + ECED12E92EAFEB345573782B83100DD3 /* RDEPUBChapterDataCache.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBChapterDataCache.swift; sourceTree = ""; }; ECFE19508E12BF8B0B52E8CB4F724217 /* ConstraintMakerRelatable+Extensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "ConstraintMakerRelatable+Extensions.swift"; path = "Sources/ConstraintMakerRelatable+Extensions.swift"; sourceTree = ""; }; ED5693BD171B879406EA14C97F4AD8D8 /* Archive+ZIP64.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "Archive+ZIP64.swift"; path = "Sources/ZIPFoundation/Archive+ZIP64.swift"; sourceTree = ""; }; EDA6D5D7D0A04878EE78FAAE94F65068 /* DTCoreText */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = DTCoreText; path = DTCoreText.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - EE03724A9B7893A98793AE13319755A1 /* RDEPUBReaderToolView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBReaderToolView.swift; sourceTree = ""; }; EE4EE750340347ED584C5A2C1164331A /* SSAlertCommonView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SSAlertCommonView.swift; path = Sources/SSAlertSwift/SSAlertCommonView.swift; sourceTree = ""; }; EE9D4A455723797A27F04A15C4E47823 /* DTHTMLElement.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = DTHTMLElement.h; path = Core/Source/DTHTMLElement.h; sourceTree = ""; }; EEB1B8F778B5A8442BF121C13E5E460A /* RDEPUBTextIndexTable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBTextIndexTable.swift; sourceTree = ""; }; @@ -976,12 +1009,13 @@ F35039A8D0AA1F7BAFC4E5E016057E15 /* DTTextAttachment.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = DTTextAttachment.m; path = Core/Source/DTTextAttachment.m; sourceTree = ""; }; F37563A56C180CE18EEFADE56FA3C17F /* DTAccessibilityViewProxy.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = DTAccessibilityViewProxy.h; path = Core/Source/DTAccessibilityViewProxy.h; sourceTree = ""; }; F410ED25E4E84C3D5166261CDF4B58F2 /* SSAlertView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SSAlertView.swift; path = Sources/SSAlertSwift/SSAlertView.swift; sourceTree = ""; }; + F45AEF1D13277C0A27B1AB4915BFE37B /* RDEPUBReaderController+DataSource.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "RDEPUBReaderController+DataSource.swift"; sourceTree = ""; }; F4E2B1AC73977F10254628C0B7EBE38F /* DTCoreText-Resources */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; name = "DTCoreText-Resources"; path = Resources.bundle; sourceTree = BUILT_PRODUCTS_DIR; }; F5389D4E96A4AFE175691B4D3D4524E7 /* DTExtendedFileAttributes.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = DTExtendedFileAttributes.m; path = Core/Source/DTExtendedFileAttributes.m; sourceTree = ""; }; F53CCB60DE5D06A43E4A52EDB274E7D2 /* NSString+Paragraphs.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSString+Paragraphs.m"; path = "Core/Source/NSString+Paragraphs.m"; sourceTree = ""; }; F5B34EE2160C27947D1EEFD42184CF5D /* ConstraintLayoutSupportDSL.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintLayoutSupportDSL.swift; path = Sources/ConstraintLayoutSupportDSL.swift; sourceTree = ""; }; F5F17B78960BAA0E4F63CBDDEAD953FE /* RDReaderPagingController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDReaderPagingController.swift; sourceTree = ""; }; - F616DAE29591FE9EC5FF94F4A922B115 /* RDEPUBWebDecorationOverlayView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBWebDecorationOverlayView.swift; sourceTree = ""; }; + F61AF0B2B0B1702EDC7034AC01F593B5 /* RDEPUBChapterRuntimeStore.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBChapterRuntimeStore.swift; sourceTree = ""; }; F62A82E787C2CF458A244942F96F35A3 /* RDEPUBPublication.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RDEPUBPublication.swift; sourceTree = ""; }; F6910514D1BAFDF69B99E120E4872FF9 /* ResourceBundle-RDReaderViewAssets-RDReaderView-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "ResourceBundle-RDReaderViewAssets-RDReaderView-Info.plist"; sourceTree = ""; }; F699881ABDBEB419AEBA661AD5A21F35 /* ZIPFoundation.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = ZIPFoundation.release.xcconfig; sourceTree = ""; }; @@ -1007,17 +1041,11 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ - 414777EE6814256DD02CD768EC83A8F9 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 504AA8F5440FC78EE91E2B72C8A465E3 /* Frameworks */ = { + 0D473F866FF856072D60676D4A5C9C67 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 64E2961F391116D43AA0252BF0A08C68 /* Foundation.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1029,16 +1057,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 6ACDB35CBA9857418F5EA7E98BD92808 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - B883A5F1131F3F9776604F3F9452AC65 /* Foundation.framework in Frameworks */, - 6D96D665BAD918861C54F9682459BE3E /* ImageIO.framework in Frameworks */, - 3C5EDCAB9E3A36C8F43A339B24ADCAB3 /* QuartzCore.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; 6F1DC7DF5E89C820D04E068D56B1D0EF /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -1052,49 +1070,65 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 85B8A5489B0E61482745C19DC7F2037E /* Frameworks */ = { + 8147927AB8186DD92B757CE952E49D76 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 64122D8C357DCDCBF0BD1BA6080A46FE /* Foundation.framework in Frameworks */, + 8972016E0797F04604D9FC71F4A96A1A /* Foundation.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; - 9B1811D8A0826DD2CCA0B0A05A3E4486 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 768BBF03C64346E98F23D30DABEE3CAB /* Foundation.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - B1079564B4F05764E0390AB29473621E /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 0CD6D54912E8E2787802A5EEE4773070 /* Foundation.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - B5F6669E35F9EDE5C335ECB6992286D4 /* Frameworks */ = { + 9C7FB1E75B0E0EDE110943518177F571 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; - BD50AEAFDACDDF335896EACA81D8ADEB /* Frameworks */ = { + AAFF6297ECB0F5238719655741D4CC47 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; - D45D6255F3CEDAD6DEA0624E59C43096 /* Frameworks */ = { + B06B3F51F1F519FC0EA37E6D9002F51A /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + A44F075844A381058ABD89158C77063E /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + B9105E1C122B0739DD8A57DC33E7C80F /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 09A487BF1C24C76B842AF0AF2443B3B5 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D094297E3FF019E0C05C5244B095CA0A /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + C8E9353276833F3362D26F17A2FF2939 /* Foundation.framework in Frameworks */, + 7D6EFD0580A8BCD609B2076F8A6EB4C8 /* ImageIO.framework in Frameworks */, + DFC8567EED9764161A51DB808255D041 /* QuartzCore.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D51C554479B266E13C4EFC56DF7AF8F2 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + FF5BF97A5C1247E6FD1657E53811F6E5 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 58EB34A71BF81209B8649B5D44C5458F /* Foundation.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1204,37 +1238,6 @@ path = "ReadViewDemo/Pods/Target Support Files/RDReaderView"; sourceTree = ""; }; - 26C8D2BAAE04A16BA4FFF4C03445849A /* EPUBUI */ = { - isa = PBXGroup; - children = ( - 3F274F6DE3095EAF4042CC6BF0F3E566 /* RDEPUBReaderBottomToolView.swift */, - 6703BD7A2507028A11319843D0D7945F /* RDEPUBReaderChapterListController.swift */, - D479BB941D318A247DDD0A5C55B859C4 /* RDEPUBReaderController.swift */, - 127FB4B3B74B022CBE4054BA33CB24A0 /* RDEPUBReaderController+ContentDelegates.swift */, - 8B8F6026114BC156F5EAC3906C3DAD24 /* RDEPUBReaderController+DataSource.swift */, - 1BD33493F588BFD65E47EC9B8773C74C /* RDEPUBReaderController+PublicAPI.swift */, - 74F9C4D562F4650166F650997ADA132B /* RDEPUBReaderController+RenderSupport.swift */, - 34D4FDB3A066F397D1FF9727C43A0777 /* RDEPUBReaderController+RuntimeBridge.swift */, - 173FEADB0A73EA7EB5B3B7CADB74C3A4 /* RDEPUBReaderController+TableOfContents.swift */, - 5EFD8EDA8C70B31844B0A06CBCD0B403 /* RDEPUBReaderDelegate.swift */, - BD245FC216DD480CF148530003BAA4BC /* RDEPUBReaderHighlightsViewController.swift */, - 1B8132548C8745F3AE7F3CBC78E1DDA9 /* RDEPUBReaderPersistence.swift */, - 51F09F20128209EC507BBD2E73457DCD /* RDEPUBReaderTableOfContentsItem.swift */, - EE03724A9B7893A98793AE13319755A1 /* RDEPUBReaderToolView.swift */, - BE9D5BE3BC1B9E7C17553D6377DAA5D3 /* RDEPUBReaderTopToolView.swift */, - 42B4304B138A25135B0946C6A7A3F4A8 /* RDEPUBViewportTypes.swift */, - B69E0662B2604A49919B5DDA504A39C4 /* RDEPUBWebContentView.swift */, - F616DAE29591FE9EC5FF94F4A922B115 /* RDEPUBWebDecorationOverlayView.swift */, - 0ECB9576AFBC841E4282C8091B9D1244 /* RDURLReaderController.swift */, - 933F9E11CFE9DD8A63C31D119B381969 /* UIColor+RDEPUBHex.swift */, - E921D3C17874C2A9BF3C204AC3A7C43E /* ReaderController */, - 93807EF10CD76F202828F0798B353B35 /* Settings */, - 87F9D48B334C3113CB9F2C32C7153527 /* TextPage */, - ); - name = EPUBUI; - path = Sources/RDReaderView/EPUBUI; - sourceTree = ""; - }; 28720FBBB275C39E9BBB8E26AEECE661 /* Pod */ = { isa = PBXGroup; children = ( @@ -1453,34 +1456,6 @@ name = Core; sourceTree = ""; }; - 87F9D48B334C3113CB9F2C32C7153527 /* TextPage */ = { - isa = PBXGroup; - children = ( - CA31C1F2D2AF87B527C261FF3EF62C58 /* RDEPUBPageInteractionController.swift */, - 19F9801AD3675102B14F64AFAFB99D89 /* RDEPUBPageLayoutSnapshot.swift */, - 185CD1A4E30E72715AF3129924F063E6 /* RDEPUBSelectionOverlayView.swift */, - 2F910EB56A7325328DCD94F082E99648 /* RDEPUBTextAnnotationOverlay.swift */, - 4B789EEAB8791D76E537C6186EA1AC1D /* RDEPUBTextContentView.swift */, - 9DB5F45FEE54BD8D3F49BD345EDAB5D9 /* RDEPUBTextPageDecorationView.swift */, - 5E030AC563D5D21F6792CA5056667F3F /* RDEPUBTextPageRenderView.swift */, - A42EA2D403B179C805701DD3638696B0 /* RDEPUBTextSelectionController.swift */, - ); - name = TextPage; - path = TextPage; - sourceTree = ""; - }; - 93807EF10CD76F202828F0798B353B35 /* Settings */ = { - isa = PBXGroup; - children = ( - 9BBE8424D2FE92DCAA8D2B1CA7D4D029 /* RDEPUBReaderConfiguration.swift */, - C55B9A91FBFB85980E2E199DC0011868 /* RDEPUBReaderSettings.swift */, - 49F47726654DBD3FAFF21FFA121D6EE7 /* RDEPUBReaderSettingsViewController.swift */, - 862E061063601D25DB09C808CA390C18 /* RDEPUBReaderTheme.swift */, - ); - name = Settings; - path = Settings; - sourceTree = ""; - }; 994C39A644C0722C7A9E97AB46DB8965 /* Targets Support Files */ = { isa = PBXGroup; children = ( @@ -1704,6 +1679,31 @@ name = "Development Pods"; sourceTree = ""; }; + C10D749CD5ED3AAD09F8456A2E2BAA37 /* ChapterRuntime */ = { + isa = PBXGroup; + children = ( + 6CD82AF8E46DDEC2A04CAD90F008B98C /* RDEPUBBackgroundTrace.swift */, + 0DEAA0384B504309C6256D3F9FF428AE /* RDEPUBBookPageMap.swift */, + 2B7B984084289FA85BE14684929535F9 /* RDEPUBChapterCacheKey.swift */, + ECED12E92EAFEB345573782B83100DD3 /* RDEPUBChapterDataCache.swift */, + B58B13E167D03567209323F2033289A1 /* RDEPUBChapterLoader.swift */, + 0B47D4559682F8352B1AD4598CEDBB28 /* RDEPUBChapterLocation.swift */, + 712AC2E76CBFB2863C3DA9B4EBA22C38 /* RDEPUBChapterOffsetMap.swift */, + F61AF0B2B0B1702EDC7034AC01F593B5 /* RDEPUBChapterRuntimeStore.swift */, + 6B39813FB9B6B3B278FF0DE60E8F41A2 /* RDEPUBChapterSummaryDiskCache.swift */, + A191B6108F3F50C4F1C76BE74D9612A9 /* RDEPUBChapterWindowCoordinator.swift */, + 1C547D687599B32842C68D8F5C1E67C0 /* RDEPUBChapterWindowSnapshot.swift */, + 6A34CA64CF765506AA4947EE31BD5B53 /* RDEPUBLocationConverter.swift */, + AC570F5F2BA388E5FCEA6C6A68382779 /* RDEPUBPageCountCache.swift */, + AD75C2171FA5F398BB9876FBECBE3002 /* RDEPUBPageResolver.swift */, + 14A1816DFD380D6BFB3047B433DFFBD4 /* RDEPUBRuntimeChapter.swift */, + 8DF4AADE4D7A63EA46F2265946368FA8 /* RDEPUBRuntimePageCount.swift */, + C8B8050E89617564241284BAA5AE661C /* String+SHA256.swift */, + ); + name = ChapterRuntime; + path = ChapterRuntime; + sourceTree = ""; + }; C2775EE99F51E41166DFC2EFD6739CA0 /* Resources */ = { isa = PBXGroup; children = ( @@ -1774,6 +1774,26 @@ name = Frameworks; sourceTree = ""; }; + D88AF7DDB78F700FC11AF244FEE6EE9D /* ReaderController */ = { + isa = PBXGroup; + children = ( + C2CC94883B1A94347DB0BFAFF179364D /* RDEPUBReaderAnnotationCoordinator.swift */, + B2823BBB9462E120DC6A762A1AD5224C /* RDEPUBReaderAssemblyCoordinator.swift */, + 3F6EEB30082AC5F62EC78FB49D9055B5 /* RDEPUBReaderChromeCoordinator.swift */, + 006F2BEF2BA2A518F2C533B76FC7D3BC /* RDEPUBReaderContext.swift */, + 02C071FDE0C607A22BD7B0375DE72458 /* RDEPUBReaderDependencies.swift */, + E67C6A6267B473D5F1B33C17A6DAFED4 /* RDEPUBReaderLoadCoordinator.swift */, + A969E1A1BC7F8699EB7BF9147AD900A9 /* RDEPUBReaderLocationCoordinator.swift */, + B912A462ED162FF141A1E16AFA1FD229 /* RDEPUBReaderPaginationCoordinator.swift */, + 9511E297D368C83173C273682467DC17 /* RDEPUBReaderRuntime.swift */, + 130D574A6F9B63B39EE77858FD259D12 /* RDEPUBReaderSearchCoordinator.swift */, + A5EBB29FB787860E6DAF09A9B32BD92D /* RDEPUBReaderViewportMonitor.swift */, + C10D749CD5ED3AAD09F8456A2E2BAA37 /* ChapterRuntime */, + ); + name = ReaderController; + path = ReaderController; + sourceTree = ""; + }; DD6FCBC8887B9E6DA3045CADFEB02EB4 /* Resources */ = { isa = PBXGroup; children = ( @@ -1797,7 +1817,7 @@ F159D5AE51CF38BA79429767DAA95194 /* wxread-replace-latin.css */, A8C5EB6578A4DB2C5736D37C10EC378E /* EPUBCore */, 7975B11608EFC0BE3054608D14E34E8E /* EPUBTextRendering */, - 26C8D2BAAE04A16BA4FFF4C03445849A /* EPUBUI */, + E49E9FE9D9BB1B4DEAA9B9A9878F1055 /* EPUBUI */, 28720FBBB275C39E9BBB8E26AEECE661 /* Pod */, 9BAE9E3C4CCFBCB6D66C7E0B219AD7D6 /* ReaderView */, 1C426635171E4D71C172024364A0CCF7 /* Support Files */, @@ -1869,6 +1889,37 @@ name = Core; sourceTree = ""; }; + E49E9FE9D9BB1B4DEAA9B9A9878F1055 /* EPUBUI */ = { + isa = PBXGroup; + children = ( + 4D0E429D0360BC3A87BBA2697DA47F3F /* RDEPUBReaderBottomToolView.swift */, + 7956987E212CE94F57F9CF48F563CE8F /* RDEPUBReaderChapterListController.swift */, + 01D626274DCF71D8C3FA537C9A7367D8 /* RDEPUBReaderController.swift */, + E6E22461422D4C78C300054E36E701BE /* RDEPUBReaderController+ContentDelegates.swift */, + F45AEF1D13277C0A27B1AB4915BFE37B /* RDEPUBReaderController+DataSource.swift */, + C554BE53564FC720F9EBBB4B856BA87D /* RDEPUBReaderController+PublicAPI.swift */, + D6E3749137D028A340460A72BA52D076 /* RDEPUBReaderController+RenderSupport.swift */, + 1B936ABDE1067495025723B34C301C87 /* RDEPUBReaderController+RuntimeBridge.swift */, + 5C19BCB3C801B3E6D0A09384A7ACE1FD /* RDEPUBReaderController+TableOfContents.swift */, + B07F9611B0A5BBD519B68A10C362BE50 /* RDEPUBReaderDelegate.swift */, + 1F92C8ECA3907CF09E97F20AFD1F2469 /* RDEPUBReaderHighlightsViewController.swift */, + 2C8A4EDCD9556F499F840510E94730CD /* RDEPUBReaderPersistence.swift */, + BB41DCB393D82A80671953233E936F5F /* RDEPUBReaderTableOfContentsItem.swift */, + 7EB287A47D1547ED7E637E14A5AEA64C /* RDEPUBReaderToolView.swift */, + 105633F335811C52B7411E5AA1A6A0BC /* RDEPUBReaderTopToolView.swift */, + 9B5B065EE6ECDEF29D79B1C769C0FF0F /* RDEPUBViewportTypes.swift */, + 4F40B8225D553E55937EB0FAB4B649DA /* RDEPUBWebContentView.swift */, + 6A64FCFADABD1D922738FCDF1220C149 /* RDEPUBWebDecorationOverlayView.swift */, + AFD72846E6D1DCE0C8CF2D35F2179BB6 /* RDURLReaderController.swift */, + E432EFA64B2640653A9B32C165EDA933 /* UIColor+RDEPUBHex.swift */, + D88AF7DDB78F700FC11AF244FEE6EE9D /* ReaderController */, + EA4F5524BEC2EA243CFB0F392E42E75C /* Settings */, + F9C9A453E977CC6DDDE365DF8FC97C73 /* TextPage */, + ); + name = EPUBUI; + path = Sources/RDReaderView/EPUBUI; + sourceTree = ""; + }; E861498A78913BA984BA96358FC61449 /* Pods */ = { isa = PBXGroup; children = ( @@ -1881,23 +1932,16 @@ name = Pods; sourceTree = ""; }; - E921D3C17874C2A9BF3C204AC3A7C43E /* ReaderController */ = { + EA4F5524BEC2EA243CFB0F392E42E75C /* Settings */ = { isa = PBXGroup; children = ( - 0C7333A1BFCB9D7512A152B2B0EC6350 /* RDEPUBReaderAnnotationCoordinator.swift */, - 72E8D63E9D57DA89DEE2774A89A61152 /* RDEPUBReaderAssemblyCoordinator.swift */, - 2BCD9B2E62051692362E404278683A18 /* RDEPUBReaderChromeCoordinator.swift */, - 1CA5A4277907D54250DB4BF7B95CE1A9 /* RDEPUBReaderContext.swift */, - 851EF2369FB225D981E94F4BB2E9F65F /* RDEPUBReaderDependencies.swift */, - 4C94F70B03585FF87A0DEF959FD7E5A6 /* RDEPUBReaderLoadCoordinator.swift */, - C26BE3D7337938B1DB1B7610001CD169 /* RDEPUBReaderLocationCoordinator.swift */, - 3D246461688CA3E0592B5536C95A624A /* RDEPUBReaderPaginationCoordinator.swift */, - 413B561A626E7BC1C78EFEFA131B92B0 /* RDEPUBReaderRuntime.swift */, - 3156D6DC256E20EF49691134C4342473 /* RDEPUBReaderSearchCoordinator.swift */, - 8FD217FE3F87580A8BA3BD7D8992D4EB /* RDEPUBReaderViewportMonitor.swift */, + 138A4AA21D93424C026C8557FF6F387E /* RDEPUBReaderConfiguration.swift */, + A3D719AE19AA83101150C2A3287D7E31 /* RDEPUBReaderSettings.swift */, + 36855CF828CDEAE98B4B8F58CFC25485 /* RDEPUBReaderSettingsViewController.swift */, + 40AE4D16785DB42B707640DA3DCC86E3 /* RDEPUBReaderTheme.swift */, ); - name = ReaderController; - path = ReaderController; + name = Settings; + path = Settings; sourceTree = ""; }; F3D99F452BF74190AE8A6D307FDF149B /* Support Files */ = { @@ -1916,6 +1960,22 @@ path = "../Target Support Files/ZIPFoundation"; sourceTree = ""; }; + F9C9A453E977CC6DDDE365DF8FC97C73 /* TextPage */ = { + isa = PBXGroup; + children = ( + 840B0D63F6448DEAD22C81BD1837E5CF /* RDEPUBPageInteractionController.swift */, + 2787B86458FE50067B72A704805C4688 /* RDEPUBPageLayoutSnapshot.swift */, + 8639CA8074A14D276D10928D93D15842 /* RDEPUBSelectionOverlayView.swift */, + C702F88F0474C4EB2C57ACE7E18A4B18 /* RDEPUBTextAnnotationOverlay.swift */, + 42B66EA2B45B08094B0C357085CB0846 /* RDEPUBTextContentView.swift */, + CBF8CA6102062C9482CC8D4170DC3454 /* RDEPUBTextPageDecorationView.swift */, + 56781BA03B3EE632D516FA3278F78ED2 /* RDEPUBTextPageRenderView.swift */, + A341139B59F0CD6D051CABB66C8DC49C /* RDEPUBTextSelectionController.swift */, + ); + name = TextPage; + path = TextPage; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -1992,68 +2052,76 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 34A6CF459BA72007E08489B574B5F8E6 /* Headers */ = { + 474C88FF0002F80C1A74A5D4CD7A09C5 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( - 7B03A8945E4D03692C21B116D97DC74C /* SSAlertSwift-umbrella.h in Headers */, + 7E751AEAEE50E4B85C68A76D9440BE2D /* SSAlertSwift-umbrella.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; - 4171195ED1E9280FD3E4BA5F78CC2311 /* Headers */ = { + 56396C6B581888EDCDC7B42B7CCF1A4C /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( - 28BF24C2F4CD401C4C24E1DE0516746E /* DTActivityTitleView.h in Headers */, - D578954B277D170B2054CBA6081BEC4D /* DTAnimatedGIF.h in Headers */, - 2E26468A7766E7F228328A1B13FC92A8 /* DTBase64Coding.h in Headers */, - 19BA94EB41DF632F761221B74DAD526A /* DTBlockFunctions.h in Headers */, - 58A61B581F5E418C86458C824089E6DC /* DTCompatibility.h in Headers */, - 58F5FFB8103C4733FF2F207057E60CE5 /* DTCoreGraphicsUtils.h in Headers */, - 90BC61DF679C4367DAD09A5D02E0AB02 /* DTCustomColoredAccessory.h in Headers */, - AB9713F8457EB1C14E53C76F0E442D82 /* DTExtendedFileAttributes.h in Headers */, - 27B96826B55ACC5EBD0442CE693D0B49 /* DTFolderMonitor.h in Headers */, - AD103B6A562A487B79DDB6617889D429 /* DTFoundation-umbrella.h in Headers */, - C0ADCA6F77E50FEDD72E2EF8FFA5B9A1 /* DTFoundationConstants.h in Headers */, - 6AB30A0403FF95366915988914FB7D3C /* DTHTMLParser.h in Headers */, - D02DF9F9FB5549E3D0A75B88BF9A30A8 /* DTLog.h in Headers */, - AD4B914F1D725723EF1927B09FD6CC64 /* DTPieProgressIndicator.h in Headers */, - 8115CAF95FA9A1ED63213498A1CA421E /* DTSmartPagingScrollView.h in Headers */, - 13E76CEF04DCA3C012D6BE385BD843F9 /* DTTiledLayerWithoutFade.h in Headers */, - 7104D4CFBCF8831BEFD9EC0DD9AB678F /* DTVersion.h in Headers */, - 2617C9F41307C3F9C95A8D3A226B4F9F /* DTWeakSupport.h in Headers */, - 6FF9A524B248BAACCF8E3D4A00BF4CF1 /* NSArray+DTError.h in Headers */, - 38D8FC875F9F62E1F50B427EE02B2DA8 /* NSData+DTCrypto.h in Headers */, - 27C72FB405DFBC4E9922AD382A2C35EB /* NSDictionary+DTError.h in Headers */, - 53D7A11DAB7412522214EBA6CCB2ABE3 /* NSFileWrapper+DTCopying.h in Headers */, - BBF0C5F3F558E5C82C28F9F2885428B2 /* NSMutableArray+DTMoving.h in Headers */, - 431305719D350F47AB85C2E09B3FB6D4 /* NSString+DTFormatNumbers.h in Headers */, - 708B7BF70311E17A3B27C8AF36937ED9 /* NSString+DTPaths.h in Headers */, - E2EE910F549C5D233EB236BB480CC09D /* NSString+DTURLEncoding.h in Headers */, - E5AB0AA1CD3062159407E53FD7365145 /* NSString+DTUtilities.h in Headers */, - A6A66002BC974BBD05385DC49E9A4CD6 /* NSURL+DTAppLinks.h in Headers */, - 0F7F1426A2C194B170E82DE115A66DB1 /* NSURL+DTComparing.h in Headers */, - 5B36BCAC2EE5770917EE46A25443744F /* NSURL+DTUnshorten.h in Headers */, - DBF7631687BF37FCD33C20E23D6C291C /* UIApplication+DTNetworkActivity.h in Headers */, - CCF3600DBACB7361D2130AAFD7A6A83D /* UIImage+DTFoundation.h in Headers */, - 809E658A89FF98AF699C66D2429B29FD /* UIScreen+DTFoundation.h in Headers */, - 584FC22D5B5F0120FCF9ACE0E570D137 /* UIView+DTFoundation.h in Headers */, + 7FDB342E7990D04F20D66B54DD3288F3 /* DTActivityTitleView.h in Headers */, + 022C814021FFCE8343CF94BEB39F5EB3 /* DTAnimatedGIF.h in Headers */, + 36C22841B8927E609AA29CD05D5132B5 /* DTBase64Coding.h in Headers */, + 7A3B9F9A741756370C23FCE449D10FBF /* DTBlockFunctions.h in Headers */, + CABB55F416696E222EDD9355BE162423 /* DTCompatibility.h in Headers */, + C065855BFB7C44A88BC0E41EDB67F138 /* DTCoreGraphicsUtils.h in Headers */, + 54FC0EC0F7CE25F7D1E846C8CFFEA363 /* DTCustomColoredAccessory.h in Headers */, + DABA6DF4CDB6C3098DDB1972D9B8D47F /* DTExtendedFileAttributes.h in Headers */, + 851D7CADE59C83C2E79791EFBD2F1B9E /* DTFolderMonitor.h in Headers */, + 7398A6AEB47EA308F2E93FE03B1A3D06 /* DTFoundation-umbrella.h in Headers */, + 7FC6815502492DF1D663D87A2D9AB207 /* DTFoundationConstants.h in Headers */, + D1E238CAEA8D1BE33C0D7EC543A66665 /* DTHTMLParser.h in Headers */, + 73BB353CA378834928C3946524DF2D12 /* DTLog.h in Headers */, + 6CDF59BFF3F212BD65C009C7080542BE /* DTPieProgressIndicator.h in Headers */, + 91EA145D5B76F9BDCD03FB801F3ACB3D /* DTSmartPagingScrollView.h in Headers */, + 224A7654E294D21374C015DB3050A57B /* DTTiledLayerWithoutFade.h in Headers */, + 2A926090C1E12A6A8CD28E8D06305AF1 /* DTVersion.h in Headers */, + 20BE095312DB8DB101C02F8983C5A6E1 /* DTWeakSupport.h in Headers */, + D2232DDB9C06A5F370FBE1CF4F022992 /* NSArray+DTError.h in Headers */, + 6B4E7177F034E79B8C3F932183E84011 /* NSData+DTCrypto.h in Headers */, + 46AEC0EB14BA522889FD7812A1139CB7 /* NSDictionary+DTError.h in Headers */, + 9EA64874ED208ECC988584D8D6346033 /* NSFileWrapper+DTCopying.h in Headers */, + 6DA8B5EB829C2AFB0891E6EF583C6C0E /* NSMutableArray+DTMoving.h in Headers */, + 852C505B9BC6E6CE207559DB6F65F526 /* NSString+DTFormatNumbers.h in Headers */, + 5BE96578F3A236ED2544FB20A4996165 /* NSString+DTPaths.h in Headers */, + BBB18335BC72FBF4584F8B5F4132CCBE /* NSString+DTURLEncoding.h in Headers */, + 4E90223029F18880D771B8A854B1D710 /* NSString+DTUtilities.h in Headers */, + F9ADC220D598CC27EF1B9F8277966E01 /* NSURL+DTAppLinks.h in Headers */, + 4373F2A4F3253DD14440D8B9FA7A0AD8 /* NSURL+DTComparing.h in Headers */, + CE5D85573015B5EF9AA8A9F512239835 /* NSURL+DTUnshorten.h in Headers */, + F97B3F528A2B7417D2843479548CDF40 /* UIApplication+DTNetworkActivity.h in Headers */, + 39EA92DA1B85B808BA7C6B96E9550BFE /* UIImage+DTFoundation.h in Headers */, + 8A5A271780D55E365E441781D2143538 /* UIScreen+DTFoundation.h in Headers */, + 4651629ECB6C6EA549A9E880D86B7648 /* UIView+DTFoundation.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; - 5D0FD1429CBDECE5A0750FDD9C003FFD /* Headers */ = { + 57A26A29131C0BF8AC8054BD851A51E2 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( - 1AF5422FD56B541040966046B479CE53 /* ZIPFoundation-umbrella.h in Headers */, + 076732EF4FCEDA7971A857A441AB51EF /* ZIPFoundation-umbrella.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; - 84AB3C42692480DA8A73D2EDF29029AA /* Headers */ = { + 6A142917C221AC3E1E9CDAE37173F79A /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( - 48BE5A69C2B044534A947BC810B1DED4 /* RDReaderView-umbrella.h in Headers */, + 6132A7C5DE9DEB521CA73B2D8081EBBD /* SnapKit-umbrella.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + B782603052C5E01CD204140A605F12CF /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 5D507DDC4C72CB2DC8D142D760607E42 /* RDReaderView-umbrella.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2065,24 +2133,16 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - CD9C4BFCB8CA7DF04B716BF3CF57DDCA /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - BB782BBE8587EDB13C294A81E7F36225 /* SnapKit-umbrella.h in Headers */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ 0C24CB0E87A728A11AA1124CB360D6A1 /* DTCoreText-Resources */ = { isa = PBXNativeTarget; - buildConfigurationList = F45A5207FBF041DEA1FF688C5CC78E8B /* Build configuration list for PBXNativeTarget "DTCoreText-Resources" */; + buildConfigurationList = 93A78DDD7C59F62735C4CF878C3F3EF3 /* Build configuration list for PBXNativeTarget "DTCoreText-Resources" */; buildPhases = ( - 0C2FD0281726C9DFE0D55D11BA576B8C /* Sources */, - 414777EE6814256DD02CD768EC83A8F9 /* Frameworks */, - 9AC7661DA4E67875FBDF24E284ABD99E /* Resources */, + 39526274254D11590C40944EF5209546 /* Sources */, + AAFF6297ECB0F5238719655741D4CC47 /* Frameworks */, + 180D0BDA876E115ADD5594B01FA45AA6 /* Resources */, ); buildRules = ( ); @@ -2095,17 +2155,17 @@ }; 19622742EBA51E823D6DAE3F8CDBFAD4 /* SnapKit */ = { isa = PBXNativeTarget; - buildConfigurationList = 9E4196CFA8812083973C756D405439CE /* Build configuration list for PBXNativeTarget "SnapKit" */; + buildConfigurationList = D4770C93C14F02E45B3B5072996B2DF9 /* Build configuration list for PBXNativeTarget "SnapKit" */; buildPhases = ( - CD9C4BFCB8CA7DF04B716BF3CF57DDCA /* Headers */, - C3B8C9C3E670F432088AFC01765CDA60 /* Sources */, - B1079564B4F05764E0390AB29473621E /* Frameworks */, - 54C1A5A54C4B5CCE4F5B8565BB1FBBAE /* Resources */, + 6A142917C221AC3E1E9CDAE37173F79A /* Headers */, + C0F01399DD3DF212283BF7C773F828CD /* Sources */, + B06B3F51F1F519FC0EA37E6D9002F51A /* Frameworks */, + 6D75FCC37F59F2EA9A63622FC1C3C86C /* Resources */, ); buildRules = ( ); dependencies = ( - 79C767A041874113D95C87515FD8FFBB /* PBXTargetDependency */, + 6FCFA9E56CF303BBCF5B5F5ECF0F1582 /* PBXTargetDependency */, ); name = SnapKit; productName = SnapKit; @@ -2114,12 +2174,12 @@ }; 8619D5ADECF2B26CFD9A9826D61D289A /* SSAlertSwift */ = { isa = PBXNativeTarget; - buildConfigurationList = D216B4C9C1EB4572666F55DEAC1E9CA1 /* Build configuration list for PBXNativeTarget "SSAlertSwift" */; + buildConfigurationList = B4819549570DE20B5ED63BD74F5A1AA1 /* Build configuration list for PBXNativeTarget "SSAlertSwift" */; buildPhases = ( - 34A6CF459BA72007E08489B574B5F8E6 /* Headers */, - 136703804E83202294E6BC7CCC007F8C /* Sources */, - D45D6255F3CEDAD6DEA0624E59C43096 /* Frameworks */, - 92D5CBA64CED2E01EC09E6047F2BAE24 /* Resources */, + 474C88FF0002F80C1A74A5D4CD7A09C5 /* Headers */, + D809AB9DAE6E5DAE31F4D39808AE721B /* Sources */, + 8147927AB8186DD92B757CE952E49D76 /* Frameworks */, + 09F6CA56A730176D1586FFB6F00CB888 /* Resources */, ); buildRules = ( ); @@ -2142,12 +2202,12 @@ buildRules = ( ); dependencies = ( - 72E48F15033CCF95398C27EBDD634478 /* PBXTargetDependency */, - E2AA5E04C0D28740D6B40D3F0CFD237B /* PBXTargetDependency */, - 1027BF5D9BE9ECB1B97EDFFEBDDF4B46 /* PBXTargetDependency */, - 5308D4674A93AF4C1261B8B667E85038 /* PBXTargetDependency */, - C3E83C94AC96087258AD38AFA3A7F7D6 /* PBXTargetDependency */, - B49B8B5B0940A82855A49049BD54424B /* PBXTargetDependency */, + 38010528A947DA219346F858DA074410 /* PBXTargetDependency */, + F93D7A676BEA733A5E0954FB9B5432FB /* PBXTargetDependency */, + 784BDE9B9AF828365D293AE2E195B42D /* PBXTargetDependency */, + C012A32E450C01FA72E44227CB008605 /* PBXTargetDependency */, + 523664B453CC2C70A82386A366470305 /* PBXTargetDependency */, + B2C24A9B733A2D23824DFE0C7FEFDE69 /* PBXTargetDependency */, ); name = "Pods-ReadViewDemo"; productName = Pods_ReadViewDemo; @@ -2156,11 +2216,11 @@ }; 8A8DB685241263AFDF5E6B20FE67B93A /* SnapKit-SnapKit_Privacy */ = { isa = PBXNativeTarget; - buildConfigurationList = 91423BFD48D44DAFE716B4DA47D303CA /* Build configuration list for PBXNativeTarget "SnapKit-SnapKit_Privacy" */; + buildConfigurationList = 31D05D1A391D978801D2C6E4C3F44F8D /* Build configuration list for PBXNativeTarget "SnapKit-SnapKit_Privacy" */; buildPhases = ( - 9518653016D8243678367F2922A236EC /* Sources */, - B5F6669E35F9EDE5C335ECB6992286D4 /* Frameworks */, - B261D0E343DA2B67FFE08CD0DC80A8A4 /* Resources */, + 11C6702A12DB7BC06155A7FDBEC0FC68 /* Sources */, + FF5BF97A5C1247E6FD1657E53811F6E5 /* Frameworks */, + F67F468518086F79FA968B8217E4D0B5 /* Resources */, ); buildRules = ( ); @@ -2173,12 +2233,12 @@ }; 8F6E5A5BF72D62CDFD25F91A7CFA3309 /* DTFoundation */ = { isa = PBXNativeTarget; - buildConfigurationList = 693BA49EA4876BA40D36F688DAD8D7BF /* Build configuration list for PBXNativeTarget "DTFoundation" */; + buildConfigurationList = 797A74DE557B2312CDE4DDE7BA924EF3 /* Build configuration list for PBXNativeTarget "DTFoundation" */; buildPhases = ( - 4171195ED1E9280FD3E4BA5F78CC2311 /* Headers */, - A522E405BDB282804B667EBF296C09E7 /* Sources */, - 6ACDB35CBA9857418F5EA7E98BD92808 /* Frameworks */, - F07CE27A09EC4341775D7D7AA24D00B3 /* Resources */, + 56396C6B581888EDCDC7B42B7CCF1A4C /* Headers */, + CBE49EE63AF0F9347CE685FF36B1D2E6 /* Sources */, + D094297E3FF019E0C05C5244B095CA0A /* Frameworks */, + E3C666A10576A7A8B8F85FB58DDF00AC /* Resources */, ); buildRules = ( ); @@ -2191,17 +2251,17 @@ }; AA15C8469D67684160CC2A7098EB841C /* ZIPFoundation */ = { isa = PBXNativeTarget; - buildConfigurationList = 5DA67BEF5F46ECF4C32ACCFE78FCEF84 /* Build configuration list for PBXNativeTarget "ZIPFoundation" */; + buildConfigurationList = 6BA5558505B7824FEAAE792E5D7D3702 /* Build configuration list for PBXNativeTarget "ZIPFoundation" */; buildPhases = ( - 5D0FD1429CBDECE5A0750FDD9C003FFD /* Headers */, - FE207FF2A3BE8F6502C76C63584668D5 /* Sources */, - 9B1811D8A0826DD2CCA0B0A05A3E4486 /* Frameworks */, - C5436E06F170164ADB98DD88938C2651 /* Resources */, + 57A26A29131C0BF8AC8054BD851A51E2 /* Headers */, + 91C1F6329C50A548F50791C81D823BAA /* Sources */, + 0D473F866FF856072D60676D4A5C9C67 /* Frameworks */, + F7DE08BA370C811043165F0FBB15E9F5 /* Resources */, ); buildRules = ( ); dependencies = ( - 5ABBCDAACF3D91809C05806C33121BBC /* PBXTargetDependency */, + ED57AD9569753E7911D812E329294918 /* PBXTargetDependency */, ); name = ZIPFoundation; productName = ZIPFoundation; @@ -2210,21 +2270,21 @@ }; AA2E57587AA8EECA63C4BE08EA3CB6D2 /* RDReaderView */ = { isa = PBXNativeTarget; - buildConfigurationList = 73B0667287C7357BD7E64D54FC10E7CA /* Build configuration list for PBXNativeTarget "RDReaderView" */; + buildConfigurationList = 7CEB01B66666A5A4E42C5E76A0DCE0B9 /* Build configuration list for PBXNativeTarget "RDReaderView" */; buildPhases = ( - 84AB3C42692480DA8A73D2EDF29029AA /* Headers */, - 5A715F992DB5A4CBF5EA14986468652B /* Sources */, - 85B8A5489B0E61482745C19DC7F2037E /* Frameworks */, - 1F7B3183D3A473148A98C90378860F91 /* Resources */, + B782603052C5E01CD204140A605F12CF /* Headers */, + C4474226F6270AAB160A357AF90E046A /* Sources */, + B9105E1C122B0739DD8A57DC33E7C80F /* Frameworks */, + 636E9F9A94D062FDB2D62A62DF7FA318 /* Resources */, ); buildRules = ( ); dependencies = ( - 9561148C38876D85CE97DA5473FE5CD1 /* PBXTargetDependency */, - BDB2E3ACF2EDD090E479F5F13928DEFD /* PBXTargetDependency */, - 18B19C677B889F6727DBDA0F865BF1AB /* PBXTargetDependency */, - 17938577AC1DECCDD4044365CD98BEC2 /* PBXTargetDependency */, - 6087769AB1CAFBD7015BB5E5FB531C1C /* PBXTargetDependency */, + 52F1324C9B757070324EF0998E9DB369 /* PBXTargetDependency */, + 3D4C2D88EE1F510051E6AF02F9542B7D /* PBXTargetDependency */, + 7FC7D99E28F71505A70765A20279B182 /* PBXTargetDependency */, + 66924DF60BD8C99A51BFF98D087E464C /* PBXTargetDependency */, + 020D39B2C63A014534BDC11B71D0241B /* PBXTargetDependency */, ); name = RDReaderView; productName = RDReaderView; @@ -2233,11 +2293,11 @@ }; AE7F393FB7805DE2664AB4111873F907 /* RDReaderView-RDReaderViewAssets */ = { isa = PBXNativeTarget; - buildConfigurationList = 84B6961CC1F169E2D6D5FC7861A8582E /* Build configuration list for PBXNativeTarget "RDReaderView-RDReaderViewAssets" */; + buildConfigurationList = 5DE3BEAC81CBDABB0222A889DBDCEA1A /* Build configuration list for PBXNativeTarget "RDReaderView-RDReaderViewAssets" */; buildPhases = ( - 89A30FE57CA1ED2B9D8B021F0F206ED4 /* Sources */, - BD50AEAFDACDDF335896EACA81D8ADEB /* Frameworks */, - 5A5FE7130469EBCA4D546CBC42EAA4BB /* Resources */, + 7FAC15E6DBCD8CB18FFD310D69C5B0D1 /* Sources */, + 9C7FB1E75B0E0EDE110943518177F571 /* Frameworks */, + 6D7875933FC3A0F6F2F0AD2449A4E391 /* Resources */, ); buildRules = ( ); @@ -2260,8 +2320,8 @@ buildRules = ( ); dependencies = ( - 463CDA137195F14051A84236470C93F2 /* PBXTargetDependency */, - FD3219BCD8F392FD503D4E3C397FFF1B /* PBXTargetDependency */, + DEA55EBE6C71C231E96349A3450C0332 /* PBXTargetDependency */, + 4D6AACC2BB50A205D880B6E1DE55515F /* PBXTargetDependency */, ); name = DTCoreText; productName = DTCoreText; @@ -2270,11 +2330,11 @@ }; C7A8D82E407CD3FDC3BA55CEE519B252 /* ZIPFoundation-ZIPFoundation_Privacy */ = { isa = PBXNativeTarget; - buildConfigurationList = D280590029F64DDED598A210724CF31E /* Build configuration list for PBXNativeTarget "ZIPFoundation-ZIPFoundation_Privacy" */; + buildConfigurationList = F065AB76006D6C22073E864A37DCED8E /* Build configuration list for PBXNativeTarget "ZIPFoundation-ZIPFoundation_Privacy" */; buildPhases = ( - FAFBEB5C156B9C7A1B4A93303133A682 /* Sources */, - 504AA8F5440FC78EE91E2B72C8A465E3 /* Frameworks */, - 0DF1717C86492B32F0404A3DB682D4DB /* Resources */, + ECAFA2044D12B026BC3810C100C9B993 /* Sources */, + D51C554479B266E13C4EFC56DF7AF8F2 /* Frameworks */, + 6FBC0BEBDBCE64E76319EA03F600C208 /* Resources */, ); buildRules = ( ); @@ -2325,19 +2385,18 @@ /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ - 0DF1717C86492B32F0404A3DB682D4DB /* Resources */ = { + 09F6CA56A730176D1586FFB6F00CB888 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 636106852946C6686CE248B0722AE689 /* PrivacyInfo.xcprivacy in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; - 1F7B3183D3A473148A98C90378860F91 /* Resources */ = { + 180D0BDA876E115ADD5594B01FA45AA6 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - B96925D97DBA16A527D4FB87673AFF4E /* RDReaderView-RDReaderViewAssets in Resources */, + 9304920D7E3F5289F47FB0F78FD33CB5 /* default.css in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2349,28 +2408,44 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 54C1A5A54C4B5CCE4F5B8565BB1FBBAE /* Resources */ = { + 636E9F9A94D062FDB2D62A62DF7FA318 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - AA2ADBCF2640D8FF5BD8D071271284E2 /* SnapKit-SnapKit_Privacy in Resources */, + A7B1B8292DED55A66CBAD34CA75D78F4 /* RDReaderView-RDReaderViewAssets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; - 5A5FE7130469EBCA4D546CBC42EAA4BB /* Resources */ = { + 6D75FCC37F59F2EA9A63622FC1C3C86C /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 3CCC8FC7FF95E7A988B0F0B0468C12BF /* cssInjector.js in Resources */, - FD6F8A543C3241B08E21FBBD47911F6D /* epub-bridge.js in Resources */, - 38F8914C748138F46D406222CA4E85AC /* epub-fixed-layout.html in Resources */, - F677C12701262AFF0F6FCCE4C2150CF9 /* rangy-core.js in Resources */, - F5142E93B76DB1917D507EECB36E5DED /* rangy-serializer.js in Resources */, - 19F07715D355FB1869128B129D015C90 /* WeReadApi.js in Resources */, - A118C8C3DDFA7F01BD8CD0BD57C74098 /* wxread-dark.css in Resources */, - 1163FCAE7A2F60099BF3A9675E47E8E2 /* wxread-default.css in Resources */, - 4FFAECEE5100543DF0EE4113D9973AEC /* wxread-replace.css in Resources */, - 8250FB5F79016BDCDD60AD731F0BD350 /* wxread-replace-latin.css in Resources */, + 31F2CB38A6379FA3544FAC5B7DF5E2B1 /* SnapKit-SnapKit_Privacy in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 6D7875933FC3A0F6F2F0AD2449A4E391 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8179744DB2686642A42608EE551E2670 /* cssInjector.js in Resources */, + 655E95DF5671890070BF91C022E42427 /* epub-bridge.js in Resources */, + 622964509F7567B406220D2C25065A01 /* epub-fixed-layout.html in Resources */, + 8F060D8C99425376DFF3A67818DE1B44 /* rangy-core.js in Resources */, + 7359A2AE58B3B4A7FA377C53189D8D04 /* rangy-serializer.js in Resources */, + BDB9C5E0D1DEB73AC0C596080A4706C8 /* WeReadApi.js in Resources */, + C7DBD197D7587C18D4572B6BC29AC583 /* wxread-dark.css in Resources */, + F1A4D990BE84E010BC9AB8CAE7D40830 /* wxread-default.css in Resources */, + 300C1B103D970ABBC25EABD5259376DE /* wxread-replace.css in Resources */, + 0F6DEA0F734016D8C05D71E8FCE135E9 /* wxread-replace-latin.css in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 6FBC0BEBDBCE64E76319EA03F600C208 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 15818F4AD782A6DF50E174EB20DBF54E /* PrivacyInfo.xcprivacy in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2381,196 +2456,43 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 92D5CBA64CED2E01EC09E6047F2BAE24 /* Resources */ = { + E3C666A10576A7A8B8F85FB58DDF00AC /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; - 9AC7661DA4E67875FBDF24E284ABD99E /* Resources */ = { + F67F468518086F79FA968B8217E4D0B5 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 9504C9FFDCBBF6F619C349CB657093A1 /* default.css in Resources */, + AFE83331052EC5073AFAE58AA60D94A2 /* PrivacyInfo.xcprivacy in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; - B261D0E343DA2B67FFE08CD0DC80A8A4 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 59272D5476F27119EF257930FE3D4722 /* PrivacyInfo.xcprivacy in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - C5436E06F170164ADB98DD88938C2651 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - DD5647950937A07ACA022C75387CF527 /* ZIPFoundation-ZIPFoundation_Privacy in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - F07CE27A09EC4341775D7D7AA24D00B3 /* Resources */ = { + F7DE08BA370C811043165F0FBB15E9F5 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + F75C507004C4203113DF421C59509D5A /* ZIPFoundation-ZIPFoundation_Privacy in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ - 0C2FD0281726C9DFE0D55D11BA576B8C /* Sources */ = { + 11C6702A12DB7BC06155A7FDBEC0FC68 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; - 136703804E83202294E6BC7CCC007F8C /* Sources */ = { + 39526274254D11590C40944EF5209546 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 68B185C109A9E448C95B2153715DBF41 /* SSAlertAnimation.swift in Sources */, - 571B611A9DE148C57AF2C7EA53857C76 /* SSAlertAnimationController.swift in Sources */, - F367BD50B50B680A2DA23005EAC2A7BD /* SSAlertCommonView.swift in Sources */, - 2C4050D8A87BF13B3029E82DF0D712C5 /* SSAlertDefaultAnmation.swift in Sources */, - 6F6EE18D7F839AB238A8B47527737580 /* SSAlertPresentAnimation.swift in Sources */, - A7F239422F3768F1B4B3A696DD485D3B /* SSAlertSwift-dummy.m in Sources */, - F6E6CAC7A3C273A7C4227BD6F0E36EE1 /* SSAlertView.swift in Sources */, - AFCBCBAE8B0C6A6EF6A6832A7213DC0D /* SSAlertViewExtention.swift in Sources */, - B18755975A9D04D3DB2732447D98CCB4 /* UIViewFrameExtension.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 5A715F992DB5A4CBF5EA14986468652B /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - F638D941A6B83EE3D0FA3116BFE9CE52 /* RDEPUBAnnotationModels.swift in Sources */, - C75B12810764BCA70B81F9A3C4A562C1 /* RDEPUBAssetRepository.swift in Sources */, - B4F34BAC839565D3D79B44FD9930560F /* RDEPUBAttachmentNormalizer.swift in Sources */, - BC7BDDC881DED4124CD0C95A9B170A30 /* RDEPUBBuildDiagnosticsReporter.swift in Sources */, - 8E1EFA605CED65BF0D98CD48AD0DB08B /* RDEPUBChapterData.swift in Sources */, - C078EB1EF2AD0AB675C64A3B920CDE8D /* RDEPUBChapterPageCounter.swift in Sources */, - 3AF7CE34BE9312F7027DC4763B33C4EB /* RDEPUBChapterTailNormalizer.swift in Sources */, - 5894AF8B6F6022A84E3FB6F22494A364 /* RDEPUBCoreTextPageFrameFactory.swift in Sources */, - CA0C55A7CB67DBA313171CF5AF49A349 /* RDEPUBDTCoreTextRenderer.swift in Sources */, - F8D652BDA6DF2BCEECB48009D34C6834 /* RDEPUBFixedLayoutTemplate.swift in Sources */, - 973D5F77AA6D4F77CEF20353736DA403 /* RDEPUBFontNormalizer.swift in Sources */, - D5F33E813692FC371616921D7C4AD1FF /* RDEPUBFragmentMarkerInjector.swift in Sources */, - 416D5AD659DCF761E28F76F7E23B9F7F /* RDEPUBHTMLNormalizer.swift in Sources */, - AC787FA868EAD10F9929927B2AEBD247 /* RDEPUBJavaScriptBridge.swift in Sources */, - 53D8E4E9570BCD825E00CDD8A881054E /* RDEPUBModels.swift in Sources */, - 012337AC75CC2DE92285DA91F07B1CC5 /* RDEPUBNavigatorLayoutContext.swift in Sources */, - A8BDFF208A28D6E52EDD9138290A3708 /* RDEPUBNavigatorState.swift in Sources */, - C37E74CF4A213BB3E7AE31B676812BD9 /* RDEPUBPageBreakPolicy.swift in Sources */, - 96AB626B9C20C3A6512835EC71A4F9FD /* RDEPUBPageInteractionController.swift in Sources */, - DF39E502984126B7FFA216FC647FDB02 /* RDEPUBPageLayoutSnapshot.swift in Sources */, - 18EE808287AF9003259B828F3C4A6B3F /* RDEPUBPaginationCacheCoordinator.swift in Sources */, - 58B093EF9A7EA0373AD770DC0939D925 /* RDEPUBPaginationModels.swift in Sources */, - D1B22560078A0D3A65D0427B3A0A38DF /* RDEPUBPaginator.swift in Sources */, - ABB8DE00DCF34B2ED79C27585E97D5FB /* RDEPUBParser.swift in Sources */, - EA42097748117159D15207C9323ABDED /* RDEPUBParser+Archive.swift in Sources */, - 7A7F0DE40D8B77129FC1E7FB54D79F7C /* RDEPUBParser+Package.swift in Sources */, - DD098AE376D237EBCD0602DC493FD49E /* RDEPUBParser+ReadingProfile.swift in Sources */, - 0220B94FD97D36708E40E33DB89C76C8 /* RDEPUBParser+Resources.swift in Sources */, - D26C59ED1011B3D0A364E09AD764683C /* RDEPUBParser+TOC.swift in Sources */, - 7CB2AB8578F4A18DAEC3D738EC46F13A /* RDEPUBPreferences.swift in Sources */, - 73C9F968338A5A64F77CC8E763D05AB5 /* RDEPUBPublication.swift in Sources */, - 4432641D03459CFA21386E96E69D470A /* RDEPUBReaderAnnotationCoordinator.swift in Sources */, - 53E8737EF21A600E0BFCD6F5DE467C5F /* RDEPUBReaderAssemblyCoordinator.swift in Sources */, - 1A35329A88FF592894E7396831A4C88B /* RDEPUBReaderBottomToolView.swift in Sources */, - 88A854FEBA21BB5C77868E663A7C4346 /* RDEPUBReaderChapterListController.swift in Sources */, - 54B9DDFA2E91F83BB5C9E2F5722884EE /* RDEPUBReaderChromeCoordinator.swift in Sources */, - 2A8AC1184CB8CD9ECA5115B5DB902490 /* RDEPUBReaderConfiguration.swift in Sources */, - 90C2D02582043D4CF91BDAD5FB0BC344 /* RDEPUBReaderContext.swift in Sources */, - 767F49D5DE536CA0332B88DAD52D3A5F /* RDEPUBReaderController.swift in Sources */, - E8C0D5929CEA330C30F63AA5502723DD /* RDEPUBReaderController+ContentDelegates.swift in Sources */, - A8F6AFC87576314D31AC08581890E4CD /* RDEPUBReaderController+DataSource.swift in Sources */, - 466AB83B99254DDBDF7ED5730D665FEC /* RDEPUBReaderController+PublicAPI.swift in Sources */, - C107081396894ED1035195055E1D03D4 /* RDEPUBReaderController+RenderSupport.swift in Sources */, - 0B834BEE7479E0F78E3A7D8235A2EC95 /* RDEPUBReaderController+RuntimeBridge.swift in Sources */, - 197770F407C11D14237B98158E119C55 /* RDEPUBReaderController+TableOfContents.swift in Sources */, - 63A8D8CEE40C56246D84EB59FCC78AF6 /* RDEPUBReaderDelegate.swift in Sources */, - EA53228A41C6502A614D69F15A3B408A /* RDEPUBReaderDependencies.swift in Sources */, - A115BC82073566E4B9E4901AD77705B4 /* RDEPUBReaderHighlightsViewController.swift in Sources */, - A0C7E2ACBEA4D3103EB06870D860E2BE /* RDEPUBReaderLoadCoordinator.swift in Sources */, - 699FFBEC77DD1CC3A54701AFC65FB2D0 /* RDEPUBReaderLocationCoordinator.swift in Sources */, - AE2EA185BE47AA7636E4F927C1B220C7 /* RDEPUBReaderPaginationCoordinator.swift in Sources */, - 7877D94D10C10C9B7AC335B0A5864575 /* RDEPUBReaderPersistence.swift in Sources */, - EBDF8A9891119113AA5CA45851DBD1B7 /* RDEPUBReaderRuntime.swift in Sources */, - BCB700C605AF5689D526F24D02B97BFA /* RDEPUBReaderSearchCoordinator.swift in Sources */, - 07C1A54BF3FAE709C8CD683DC9199B17 /* RDEPUBReaderSettings.swift in Sources */, - 4626D775FF8050FC1B85AED719C6CCD3 /* RDEPUBReaderSettingsViewController.swift in Sources */, - 22AEC3422EFA234293F90AB3738CE694 /* RDEPUBReaderTableOfContentsItem.swift in Sources */, - B2065C3E8DA14CE92675506BBC1A71F2 /* RDEPUBReaderTheme.swift in Sources */, - B686B485ACD5C39FEA878C32F4741AD9 /* RDEPUBReaderToolView.swift in Sources */, - 30566C35A1D4F14A6A128B570E061108 /* RDEPUBReaderTopToolView.swift in Sources */, - D39C3766E7B664581FAD2688E295BAB3 /* RDEPUBReaderViewportMonitor.swift in Sources */, - E005DCCFE4D00062C0593F16CB0FB7C9 /* RDEPUBReadingLocationModels.swift in Sources */, - 9476BAD56597A02E230B9E4F8F6D306E /* RDEPUBReadingSession.swift in Sources */, - A6C184213CB62062887604E95FCEC014 /* RDEPUBRenderDiagnosticsCollector.swift in Sources */, - 35A8A9292DCF25CF8E47462B12D246EA /* RDEPUBRenderRequest.swift in Sources */, - 441269EE3B2065102DE8D9F0D7EB461B /* RDEPUBResourceResolver.swift in Sources */, - 7C026FA594F06339EAAEB774A776A030 /* RDEPUBResourceURLSchemeHandler.swift in Sources */, - 4D00F4DD1B8D720FFA715756C7D70D40 /* RDEPUBSearchEngine.swift in Sources */, - 53240F8A0E98A48B59C6B2FA88270310 /* RDEPUBSearchModels.swift in Sources */, - 44B918236776349CDE1D85C70EDD7328 /* RDEPUBSelectionOverlayView.swift in Sources */, - F77B604D4BCCE6CBE8520E588343F0A1 /* RDEPUBSemanticMarkerInjector.swift in Sources */, - 0A37B6C1B26290A715DB47A6D750293D /* RDEPUBStyleSheetBuilder.swift in Sources */, - DC5C1FECCEB131BAD26561FEBE22745C /* RDEPUBStyleSheetComposer.swift in Sources */, - BE67CF1248E014FD0397C32C8EC6D868 /* RDEPUBTextAnchor.swift in Sources */, - 92594DF73D4E65DE3A257494085A7FA4 /* RDEPUBTextAnnotationOverlay.swift in Sources */, - 597A2991F0147098A1A5F3555E7A4433 /* RDEPUBTextBookBuilder.swift in Sources */, - 8483273E622F869454330F71D3824098 /* RDEPUBTextBookCache.swift in Sources */, - 5DCA3B311B4E46F3E96BF3D73119EB86 /* RDEPUBTextBookModels.swift in Sources */, - 21A7DDFC2A430E9105419DF3DC2D4AA5 /* RDEPUBTextBuildPipelineInterfaces.swift in Sources */, - 3AD8BF0401CF9D849805A1E3A57C5399 /* RDEPUBTextContentView.swift in Sources */, - 67FC2E8BF2E51B5AEBE73C81BE0375C6 /* RDEPUBTextIndexTable.swift in Sources */, - EA3DDFFEE504AA55AD089305ACA3D763 /* RDEPUBTextLayouter.swift in Sources */, - C10B9F0291FFE41EBE7AF0CE975B3EB8 /* RDEPUBTextLayoutFrame.swift in Sources */, - 4B520B6C488D585307F8B6A48DC788B6 /* RDEPUBTextPageDecorationView.swift in Sources */, - 48A82D1D9C23DC22512E4005270631D8 /* RDEPUBTextPageRenderView.swift in Sources */, - A43A87703C16488B17C5871E7398371B /* RDEPUBTextPaginationInterfaces.swift in Sources */, - A9BDCA1E1104F38F9823FF2F9D06FE7E /* RDEPUBTextPaginationSupport.swift in Sources */, - 59FB4EE13B3168CADAAF65DC27802EFA /* RDEPUBTextPerformanceSampler.swift in Sources */, - A3045667F74EFACBD3F29F46BFB4EBC0 /* RDEPUBTextPositionConverter.swift in Sources */, - 308F8ADD0B8367CBD99AA8781FF1B580 /* RDEPUBTextRenderer.swift in Sources */, - 3F9097BE7A6CA36DA12224F99D0D37BF /* RDEPUBTextRendererSupport.swift in Sources */, - 912F0C755DC31C3010FB5700F94193DD /* RDEPUBTextSearchEngine.swift in Sources */, - CFCACF1D9A6846A81C7F50C8E6A23F70 /* RDEPUBTextSelectionController.swift in Sources */, - E0A64565947C7016CD15A87A29C06FE1 /* RDEPUBTypesettingPipeline.swift in Sources */, - 12E1D9F1AA8C4E17DC54886C3CE410C2 /* RDEPUBViewportTypes.swift in Sources */, - 36B1D51E18199A4B80157CED6D97C104 /* RDEPUBWebContentView.swift in Sources */, - D77ADB50A40D2C35FAA905E24838929B /* RDEPUBWebDecorationOverlayView.swift in Sources */, - 3238A1DC9C7C19F477F62081CEB1CDF6 /* RDEPUBWebView.swift in Sources */, - 69A2C721B126BE26E4BBAC1DF63858BB /* RDEPUBWebView+Configuration.swift in Sources */, - 4D1B1254D155186FEE0796901E729627 /* RDEPUBWebView+FixedLayout.swift in Sources */, - 077A7464B5BCC77F710DC721FF09B515 /* RDEPUBWebView+JavaScriptBridge.swift in Sources */, - D61D512B689DBF87FCF457C01594871A /* RDEPUBWebView+Reflowable.swift in Sources */, - EF09C17C1A9226B1D3377A4186237715 /* RDEPUBWebView+Search.swift in Sources */, - 023CD43B36E7FC2AEEB3E546121AD89F /* RDEPUBWebViewDebug.swift in Sources */, - B85FE04FC7958BAB89406AD289D1D018 /* RDPlainTextBookBuilder.swift in Sources */, - D031693A1CC970AC82EF0835E9DAD65D /* RDReaderContentCell.swift in Sources */, - CE5C346EF640EA27FE463DBC8D1E1C88 /* RDReaderFlowLayout.swift in Sources */, - 600D8BCA0F833306A821AE9963A2705F /* RDReaderGestureController.swift in Sources */, - 855020CBFB65BB877255A61BAFFF7245 /* RDReaderPageChildViewController.swift in Sources */, - 8F8C9BEEF983EFFB9D38B5A7C8BA5518 /* RDReaderPagingController.swift in Sources */, - 0DC397CC2F255374FD7DD584D742622E /* RDReaderPreloadController.swift in Sources */, - 3349AC669F17F9DE48E0CBEF213C71F7 /* RDReaderSpreadResolver.swift in Sources */, - 552B25799A8AAD599BFAB1C6C3B4D454 /* RDReaderTapRegionHandler.swift in Sources */, - 48B2080D6CD3A7C757A1F69635D1446E /* RDReaderView.swift in Sources */, - AD3F24E834B5215C632C984EFD959D10 /* RDReaderView+CollectionView.swift in Sources */, - 31EFA2212FA286AB73DE8ADE65F32224 /* RDReaderView+ContentAccess.swift in Sources */, - B678CD12611B4C83E3AF88FF80B8D092 /* RDReaderView+PageCurl.swift in Sources */, - 5C08CE24A3901A3E0F8B090F71A5871B /* RDReaderView+ToolView.swift in Sources */, - 44688B5127920D2151DD116BB8709AE0 /* RDReaderView-dummy.m in Sources */, - 755E82D4E33E3D24EBA3F426A58961D9 /* RDReaderViewProtocols.swift in Sources */, - 488A6649575C6F4DB55809630C3218B1 /* RDURLReaderController.swift in Sources */, - 7224AF6EDE2F24361DFAC2B54ADA9FE4 /* UIColor+RDEPUBHex.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2582,101 +2504,292 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 89A30FE57CA1ED2B9D8B021F0F206ED4 /* Sources */ = { + 7FAC15E6DBCD8CB18FFD310D69C5B0D1 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; - 9518653016D8243678367F2922A236EC /* Sources */ = { + 91C1F6329C50A548F50791C81D823BAA /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 5D6041EC2E04883F9A27D93DEBC975B7 /* Archive.swift in Sources */, + AB093C9480101EB0EF702C7347F408AA /* Archive+BackingConfiguration.swift in Sources */, + E2D1185AE98EA06E5496471130215B5F /* Archive+Deprecated.swift in Sources */, + 49ACCF887B0F58474D5EC6B7FD6D40AF /* Archive+Helpers.swift in Sources */, + D5ECE92F691B7A13E36D275A9BDA4B9C /* Archive+MemoryFile.swift in Sources */, + 588B8D01B170676730D6BEEA0AE1B756 /* Archive+Progress.swift in Sources */, + 2F0A7ADF79A55D07DD5805DF70045413 /* Archive+Reading.swift in Sources */, + C6DEF90A63E54E5B9C32A944669BC079 /* Archive+ReadingDeprecated.swift in Sources */, + 52824BFFE7E7D032E3C075D472158C3F /* Archive+Writing.swift in Sources */, + 8ADADD66618F7A5E44ACF3D34630E5B7 /* Archive+WritingDeprecated.swift in Sources */, + C4D34C7A8F84569250722CBC6334DAD8 /* Archive+ZIP64.swift in Sources */, + 795035283B9F00CF74DF195F73746032 /* Data+Compression.swift in Sources */, + 854C70ECC65BAE4D46A186D4263472B2 /* Data+CompressionDeprecated.swift in Sources */, + C2ECF1EDB8CEB6B610857A85E076358A /* Data+Serialization.swift in Sources */, + CA06F7E3629174D7D2B0E35E98644153 /* Date+ZIP.swift in Sources */, + D7CDAE3323835E7AEE99756733853939 /* Entry.swift in Sources */, + B0D846AEE6C2E73505BD1C3067FEDD0F /* Entry+Serialization.swift in Sources */, + 19EBB30AF3127B670CB28D12A3499B43 /* Entry+ZIP64.swift in Sources */, + C205D66400B80CD0D0680CEF8A1E998F /* FileManager+ZIP.swift in Sources */, + 3E9C037BE986CCEA7775F9E941D87E07 /* FileManager+ZIPDeprecated.swift in Sources */, + 06C84D472718F0DE04F28EBAA2D6D18E /* URL+ZIP.swift in Sources */, + 0FC46543C988F78ACAB922544907E8CE /* ZIPFoundation-dummy.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; - A522E405BDB282804B667EBF296C09E7 /* Sources */ = { + C0F01399DD3DF212283BF7C773F828CD /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - D9B2103AEF9770DFFED080731A236489 /* DTActivityTitleView.m in Sources */, - 43E3E9B7575ADB1B90A26C2879C6BD75 /* DTAnimatedGIF.m in Sources */, - A0A8F3BD0C99B4713511CC6D1691976E /* DTBase64Coding.m in Sources */, - C100C86A98BE2D71D4DCC0C14BC9C933 /* DTBlockFunctions.m in Sources */, - CC52D4A2D391BFE5D9F6F39DB6913757 /* DTCoreGraphicsUtils.m in Sources */, - 096932721786FDD80CE0F45A097E7162 /* DTCustomColoredAccessory.m in Sources */, - 0F79C2851ACB90CAE1412D895C7010D7 /* DTExtendedFileAttributes.m in Sources */, - 7B06E0624E131F4B4AE9CBA7C182DDD3 /* DTFolderMonitor.m in Sources */, - 6AAD95B33C144808A58590F8EEC1812B /* DTFoundation-dummy.m in Sources */, - E454A390799FA2F7E988A23592713FCE /* DTFoundationConstants.m in Sources */, - C252D4D3D440861EA6122DF66EB33500 /* DTHTMLParser.m in Sources */, - 9BC6C98935EEE83035986E164F76BC47 /* DTLog.m in Sources */, - 89A4857FB58483273A0954DA4935A558 /* DTPieProgressIndicator.m in Sources */, - 5720E398FFB4ECEDDE02A66A43FB901D /* DTSmartPagingScrollView.m in Sources */, - 5D8A0C9FDE40C0ECD438718CAE0080B4 /* DTTiledLayerWithoutFade.m in Sources */, - C86AF629D40E46FD947662786FAEA40C /* DTVersion.m in Sources */, - EDAD4A9457EBB9E65A2381991AB738F7 /* NSArray+DTError.m in Sources */, - 30FFF4A91F83509371C5B0CC18D5B919 /* NSData+DTCrypto.m in Sources */, - D2EC76E48BBF742006A8DE10F31F1A25 /* NSDictionary+DTError.m in Sources */, - 154E19088C6CC946DD47CB238C4C9BCC /* NSFileWrapper+DTCopying.m in Sources */, - 2DDC5E420C2953CB9DB36BCEB59C1F29 /* NSMutableArray+DTMoving.m in Sources */, - FC24AD03BC7583FF043A6723AC086232 /* NSString+DTFormatNumbers.m in Sources */, - 4B184BEB24237C483AE54DD52C6C107A /* NSString+DTPaths.m in Sources */, - 90C189E1159D4DE5725F33E1E8ABF5D3 /* NSString+DTURLEncoding.m in Sources */, - D886581273ADE0CC6CA73210AA2A8740 /* NSString+DTUtilities.m in Sources */, - 93CDFBD7BE5E4EB5ED06820EB9A89794 /* NSURL+DTAppLinks.m in Sources */, - 60974BD9B17F54CEB69CA6B80BB25730 /* NSURL+DTComparing.m in Sources */, - F68D834D874BA6D351EA0BAE720DB1A6 /* NSURL+DTUnshorten.m in Sources */, - 38A65ED943B0C81B4EA40C50BBC63E50 /* UIApplication+DTNetworkActivity.m in Sources */, - 20BA812F7892018A2905DFB5E9195B33 /* UIImage+DTFoundation.m in Sources */, - B7A73A26B8B88977C3DDC07D559EAAEC /* UIScreen+DTFoundation.m in Sources */, - DF7B0A62AD5A1F39463B55A890F32E50 /* UIView+DTFoundation.m in Sources */, + 26CF8FEBC5AD3297A149AE731007F41D /* Constraint.swift in Sources */, + D014F52A4F15AB1D66EA29D55049DB3C /* ConstraintAttributes.swift in Sources */, + 565E82C6F71955C9D0E75896531597D7 /* ConstraintConfig.swift in Sources */, + CD582808F975840BD50B8E2CE6F0F3AC /* ConstraintConstantTarget.swift in Sources */, + AE4E7B950DA8CB471D88477C5FCEC6F9 /* ConstraintDescription.swift in Sources */, + 146DC5748062792BD49F4DF4197D0F5D /* ConstraintDirectionalInsets.swift in Sources */, + 8207FBC4C2ADC9BB801A355415D4E075 /* ConstraintDirectionalInsetTarget.swift in Sources */, + 92E45AB1E4920FB4AC1E7C683B915705 /* ConstraintDSL.swift in Sources */, + E6D48DF9ACD5A94E7E5F9A0FAC5603B5 /* ConstraintInsets.swift in Sources */, + F927F1196A4802A1BA4041A4D51C7672 /* ConstraintInsetTarget.swift in Sources */, + 4CE9742F307F7E0BE251502700E47AAD /* ConstraintItem.swift in Sources */, + 527D76D4D4C694DADE242202CB1E69C0 /* ConstraintLayoutGuide.swift in Sources */, + E41167E9986AB223798CE5F9150720F4 /* ConstraintLayoutGuide+Extensions.swift in Sources */, + 2DA445FA308D3FB0368D295D704952A9 /* ConstraintLayoutGuideDSL.swift in Sources */, + 89F359611ECEC3137EF4B3029380FB58 /* ConstraintLayoutSupport.swift in Sources */, + 518E7331BD31A0AC933280839470DED8 /* ConstraintLayoutSupportDSL.swift in Sources */, + 1DB5166E2A9E995A38395A9E8A478E78 /* ConstraintMaker.swift in Sources */, + 48B4BA94B5A76AEF0CEF341F5332E57E /* ConstraintMakerEditable.swift in Sources */, + 17F66CCE9BECB4B97210B5C40A6CA706 /* ConstraintMakerExtendable.swift in Sources */, + D271607BFC21EB5330941673A2AA3EC8 /* ConstraintMakerFinalizable.swift in Sources */, + B3908764711A87E5232E4F972AA70AB0 /* ConstraintMakerPrioritizable.swift in Sources */, + E2CF8E70EDB0332E6AFE720D8BA6254D /* ConstraintMakerRelatable.swift in Sources */, + 53BF7B971EC9643C6A30EB9E61DD4D39 /* ConstraintMakerRelatable+Extensions.swift in Sources */, + C6773380BF42C95B3016CDF91D5F58DC /* ConstraintMultiplierTarget.swift in Sources */, + 4F048676EAF2ED5D377AEB768A4B7FB5 /* ConstraintOffsetTarget.swift in Sources */, + 8BC48C9A595913DC27EE31013ACE7EC8 /* ConstraintPriority.swift in Sources */, + F797D512693374F5DAF5134FA2137D0A /* ConstraintPriorityTarget.swift in Sources */, + 50DDDD48489D8E41F64FEDABAA9A21F3 /* ConstraintRelatableTarget.swift in Sources */, + C14C1AD3FD28ED7A8D1B8E9DA8BAA819 /* ConstraintRelation.swift in Sources */, + BE5E8F13438BA43EC5C46A50A2050F94 /* ConstraintView.swift in Sources */, + D551A1BDF73D7A1A2ABF4EB10FB8E3B6 /* ConstraintView+Extensions.swift in Sources */, + 83A146B504F211455EA722D0E17FDB3E /* ConstraintViewDSL.swift in Sources */, + E23B676D0121FF72D52379287DF8BFED /* Debugging.swift in Sources */, + 872FE38ED0AD604DA2F5CE213D90789C /* LayoutConstraint.swift in Sources */, + 99A97C3C5382067163801B6C0E293543 /* LayoutConstraintItem.swift in Sources */, + 4D7BC6D6F616907780E4F3842381158B /* SnapKit-dummy.m in Sources */, + F1402ADADDC01A1A59ED22B971A50D62 /* Typealiases.swift in Sources */, + F154A6A709307ADECDA063693CEDCE13 /* UILayoutSupport+Extensions.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; - C3B8C9C3E670F432088AFC01765CDA60 /* Sources */ = { + C4474226F6270AAB160A357AF90E046A /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 9C34AD53A9D273BBB569856B930F68DB /* RDEPUBAnnotationModels.swift in Sources */, + 797ACCBCC1F8E7E01BA73F5298CF335A /* RDEPUBAssetRepository.swift in Sources */, + 9E8B7A8438F378CD94C4A766E79B8D48 /* RDEPUBAttachmentNormalizer.swift in Sources */, + 40FD6F748DF81499C888E3922FBA3D54 /* RDEPUBBackgroundTrace.swift in Sources */, + A2C79624120C79D72EAAB81E586355E8 /* RDEPUBBookPageMap.swift in Sources */, + 598583ABA1E1066FDC5367F9808C0836 /* RDEPUBBuildDiagnosticsReporter.swift in Sources */, + 7DB7306892F76345FF578849C1ABCC48 /* RDEPUBChapterCacheKey.swift in Sources */, + 84BEC66020F30A0AE6F0B5F38C0BBF5B /* RDEPUBChapterData.swift in Sources */, + CE3626DDC69337BFB7B0285FFBA5D9F4 /* RDEPUBChapterDataCache.swift in Sources */, + 634D25CD2ECC2E3530A42747FEB1ABF0 /* RDEPUBChapterLoader.swift in Sources */, + 217C3941623536867BD86671D091E6A8 /* RDEPUBChapterLocation.swift in Sources */, + 810E6A4FBB7900C15F099E5989F8CEE8 /* RDEPUBChapterOffsetMap.swift in Sources */, + 08408D13F523D4077F57B66DEB9F3DA6 /* RDEPUBChapterPageCounter.swift in Sources */, + AF6C446DF273E65DD84158080219FAEE /* RDEPUBChapterRuntimeStore.swift in Sources */, + DD535986C7E1597F34779DF60C56A20E /* RDEPUBChapterSummaryDiskCache.swift in Sources */, + 72107C30F1F174C445EA98997BBFEA41 /* RDEPUBChapterTailNormalizer.swift in Sources */, + 7F0A4F7D502DA58E65A856AD418B7442 /* RDEPUBChapterWindowCoordinator.swift in Sources */, + 192120ACD3AC7EA38A9F9C113026A6DC /* RDEPUBChapterWindowSnapshot.swift in Sources */, + 834EF1600F4487827FBFB72512E21EC7 /* RDEPUBCoreTextPageFrameFactory.swift in Sources */, + 2DF17F0C4C892B7746ADC6F015BA1A98 /* RDEPUBDTCoreTextRenderer.swift in Sources */, + 9B77D7B645764D55F450E0C7B1B423B6 /* RDEPUBFixedLayoutTemplate.swift in Sources */, + D4B6656FC2A1BE1C290454E8F5F073EB /* RDEPUBFontNormalizer.swift in Sources */, + DFBBFE1900812499E4548C004BF5B9F7 /* RDEPUBFragmentMarkerInjector.swift in Sources */, + 4147CD8AB72DD67E941FE2293D334FB3 /* RDEPUBHTMLNormalizer.swift in Sources */, + 5B4794DDD8BC9E1B37F638ABBE69D794 /* RDEPUBJavaScriptBridge.swift in Sources */, + 3939A2FEF1CBE351626D1858A5189223 /* RDEPUBLocationConverter.swift in Sources */, + FB957A6F0F564266C6BB7B461ED2D983 /* RDEPUBModels.swift in Sources */, + 304087B3DB374A21BAC8B9E6DF19BE32 /* RDEPUBNavigatorLayoutContext.swift in Sources */, + 831D4789477DF97F18B6E25C132D10DA /* RDEPUBNavigatorState.swift in Sources */, + BA5481B3D54EE86C9ED64289A050AB98 /* RDEPUBPageBreakPolicy.swift in Sources */, + 4116789760AF2C8CD5181F3FCEFFED46 /* RDEPUBPageCountCache.swift in Sources */, + 191764CA461C7A48E8653B476A202743 /* RDEPUBPageInteractionController.swift in Sources */, + 357D4722A56E550F0314AA5F5DAEFB06 /* RDEPUBPageLayoutSnapshot.swift in Sources */, + 95B0E663F5F3811B4241E6EE55CFC080 /* RDEPUBPageResolver.swift in Sources */, + AA74956FA73553CC4DF712E3C11012B3 /* RDEPUBPaginationCacheCoordinator.swift in Sources */, + 2A9F4B54A10516793F9EC5CD788DC865 /* RDEPUBPaginationModels.swift in Sources */, + A8CF70CDE97B17D75D7F07C6065748A8 /* RDEPUBPaginator.swift in Sources */, + 616E5DFABBD43DBBAA152497064256A1 /* RDEPUBParser.swift in Sources */, + C0106570EB1C66C2173ACECEAE177C05 /* RDEPUBParser+Archive.swift in Sources */, + 584AF2C9447F50855A6E7BE3E59F0F2C /* RDEPUBParser+Package.swift in Sources */, + 5E1D82CF63AD7EB0D4FB8343AAAF0B54 /* RDEPUBParser+ReadingProfile.swift in Sources */, + 5291125A832E62416FFA6D0E1D0BD3EE /* RDEPUBParser+Resources.swift in Sources */, + B7B20B657F020B36BD9AA46F0E7B72BF /* RDEPUBParser+TOC.swift in Sources */, + 74D9BA3031E055ABB5AE2DDA78314197 /* RDEPUBPreferences.swift in Sources */, + 6A24B7902200A8AD2C6BA6298676F56B /* RDEPUBPublication.swift in Sources */, + EDD3BEDFA206C0F5AA78A86362C7C28E /* RDEPUBReaderAnnotationCoordinator.swift in Sources */, + 43B5EB9D7FC7ACF025F7D6C88869A9C9 /* RDEPUBReaderAssemblyCoordinator.swift in Sources */, + C48FCC94B7C4CD5EE702D91ACAF01DE1 /* RDEPUBReaderBottomToolView.swift in Sources */, + 129EFD71F4AF6630E1FBCB0B4F9C335A /* RDEPUBReaderChapterListController.swift in Sources */, + A92A1BD4244164E33EFC993B7E980CE9 /* RDEPUBReaderChromeCoordinator.swift in Sources */, + ADF2A134862417950DB9864F86255CFC /* RDEPUBReaderConfiguration.swift in Sources */, + C1E9EF7FE2EAA2A3929CB6D1A4D9CF01 /* RDEPUBReaderContext.swift in Sources */, + 2EF79551C72ADF37546B05316E76D12A /* RDEPUBReaderController.swift in Sources */, + D14F24CEC26C7EDAFE40D9BAEED5BC48 /* RDEPUBReaderController+ContentDelegates.swift in Sources */, + 6BDB4D52EC440E5AFF74F823638E88FD /* RDEPUBReaderController+DataSource.swift in Sources */, + E95E1EB90DE3754D78F341EABB8CAB4E /* RDEPUBReaderController+PublicAPI.swift in Sources */, + 438409E070011BCB561C09F6CEED848C /* RDEPUBReaderController+RenderSupport.swift in Sources */, + 2EA47826067DE455847F33A1E3D25BCF /* RDEPUBReaderController+RuntimeBridge.swift in Sources */, + D84A04E28FE29A9276BD086C1B0973C1 /* RDEPUBReaderController+TableOfContents.swift in Sources */, + 00773D237A15DD1D9DEC72F217AA5A03 /* RDEPUBReaderDelegate.swift in Sources */, + 8C68B08F6E4E170047C5E35EA1BFD3DC /* RDEPUBReaderDependencies.swift in Sources */, + DCC9AC752063FE2FB19B1C0FF3EDDB5A /* RDEPUBReaderHighlightsViewController.swift in Sources */, + 718326388B25165631B5288610134599 /* RDEPUBReaderLoadCoordinator.swift in Sources */, + 7710039BB36FFBABDCCB093589600A0A /* RDEPUBReaderLocationCoordinator.swift in Sources */, + 2ECDB12C26AA7F67E8C0EF4B13ACF674 /* RDEPUBReaderPaginationCoordinator.swift in Sources */, + 0202E5FFF7290AAFC27E45980753ED2D /* RDEPUBReaderPersistence.swift in Sources */, + C72E734A93614CEE60A20608F6C93968 /* RDEPUBReaderRuntime.swift in Sources */, + 2C1A6DEDACF1E77DDC925F4A0D4ED937 /* RDEPUBReaderSearchCoordinator.swift in Sources */, + 5C30F01AF8B18ECF5D3521C0FDAAB2D3 /* RDEPUBReaderSettings.swift in Sources */, + E7025D76D39D2415119AF52CC8C4717A /* RDEPUBReaderSettingsViewController.swift in Sources */, + CB2BDC02AA1677865896F9107B74F6BA /* RDEPUBReaderTableOfContentsItem.swift in Sources */, + 6754F409DCFA7345D497978F8C8FEB33 /* RDEPUBReaderTheme.swift in Sources */, + E199C7489AE814A75F8F8D54D8B3BFF4 /* RDEPUBReaderToolView.swift in Sources */, + C4FE61E1B0CFE8B352FDD7114CCBD7C1 /* RDEPUBReaderTopToolView.swift in Sources */, + 36D5F57AA24043EDE534856755F63DD7 /* RDEPUBReaderViewportMonitor.swift in Sources */, + 33C7E8291AAEAFB29321B12A38E9981D /* RDEPUBReadingLocationModels.swift in Sources */, + 09D054B60B0BA4CE30A8D9EB123466B5 /* RDEPUBReadingSession.swift in Sources */, + FE4C703DD0829444A9A441741F3630EB /* RDEPUBRenderDiagnosticsCollector.swift in Sources */, + 72BDFD8E71E58E07880C0944EF9696A7 /* RDEPUBRenderRequest.swift in Sources */, + F2CC7EB7A119026069917121B90372AF /* RDEPUBResourceResolver.swift in Sources */, + EB335701169A2933C42C61AD812205A8 /* RDEPUBResourceURLSchemeHandler.swift in Sources */, + F35B2E482D6BB757A8EF69A7CD98AE44 /* RDEPUBRuntimeChapter.swift in Sources */, + 496997716B1BEACEFA587B3B9D71C2AA /* RDEPUBRuntimePageCount.swift in Sources */, + 4CA1C4073140A73BB87C51B4A6C8EED0 /* RDEPUBSearchEngine.swift in Sources */, + ABBDB23194AD9C4CABC7B670DF17BB1F /* RDEPUBSearchModels.swift in Sources */, + 82616A4F36B18EF488C317018F2CFAD1 /* RDEPUBSelectionOverlayView.swift in Sources */, + 26DC45E81AC3D85CCE9E25F716701BD0 /* RDEPUBSemanticMarkerInjector.swift in Sources */, + BCC7E85D6E6F8DC53F922E94186C034F /* RDEPUBStyleSheetBuilder.swift in Sources */, + 779664F3B0F751FBBAE1BC4DA55BA82F /* RDEPUBStyleSheetComposer.swift in Sources */, + 595DAF34748691D7C7CBB356FBAB389F /* RDEPUBTextAnchor.swift in Sources */, + 3EEAC1BF3947FE7BDA0AF19A662252BB /* RDEPUBTextAnnotationOverlay.swift in Sources */, + CB2659A3D443956DA142AA4EE5491E9E /* RDEPUBTextBookBuilder.swift in Sources */, + B5196E661263405C836741490F0535ED /* RDEPUBTextBookCache.swift in Sources */, + 140B64EF45322064CE974C5969E5C7A4 /* RDEPUBTextBookModels.swift in Sources */, + 3DB87FD33402BAFFDE9003F9022B1FF6 /* RDEPUBTextBuildPipelineInterfaces.swift in Sources */, + 9FD5A79D7AD9A4B886C9DED1C8A1C265 /* RDEPUBTextContentView.swift in Sources */, + A6BD6C9B1C25EE75972456195FC19DCE /* RDEPUBTextIndexTable.swift in Sources */, + 374B0659957B12D9ED373A217DA8C67B /* RDEPUBTextLayouter.swift in Sources */, + 3B31B5AB8A3C15F27630E73514FD47D0 /* RDEPUBTextLayoutFrame.swift in Sources */, + 7AA9CA762DDBD33B58C9396CD6F539BC /* RDEPUBTextPageDecorationView.swift in Sources */, + 1041631AF6C57D4393D6E1D12C937365 /* RDEPUBTextPageRenderView.swift in Sources */, + B3D973551DB2F6E6CE010126F4F4A4A3 /* RDEPUBTextPaginationInterfaces.swift in Sources */, + 975BB1BADB85F508E55EA30545F66E5A /* RDEPUBTextPaginationSupport.swift in Sources */, + 9AF05BF0F948A54F07327DD58FC203F8 /* RDEPUBTextPerformanceSampler.swift in Sources */, + 3D964664C06383CA1A438402714E9D10 /* RDEPUBTextPositionConverter.swift in Sources */, + 741F9778F7F3965764042EDDE33420E1 /* RDEPUBTextRenderer.swift in Sources */, + 0306F17B668BC15F5ABFE8EBA33CC65F /* RDEPUBTextRendererSupport.swift in Sources */, + 55BD6901F7A4D0189E0FA0FC0177D453 /* RDEPUBTextSearchEngine.swift in Sources */, + D9F35C0FC6B7F766BC53C652AF9BDAFF /* RDEPUBTextSelectionController.swift in Sources */, + 9D2C97270A65BF97952730D690577C42 /* RDEPUBTypesettingPipeline.swift in Sources */, + 2E079684EDB552970365BD6915457E1D /* RDEPUBViewportTypes.swift in Sources */, + 88C98BA758B76A26DF050D7B8813DE9E /* RDEPUBWebContentView.swift in Sources */, + A6BAD47DA59DF0BAB9319402D2B5F966 /* RDEPUBWebDecorationOverlayView.swift in Sources */, + 48622BBC0156DD7A2122F0E274337175 /* RDEPUBWebView.swift in Sources */, + FB4EB5314A8180C0EE755D0D5AA5CD99 /* RDEPUBWebView+Configuration.swift in Sources */, + 07EF1A9EE04C58D0972B0DF502AA49E2 /* RDEPUBWebView+FixedLayout.swift in Sources */, + 36B99498C3A4842A71BAC9E5C466A439 /* RDEPUBWebView+JavaScriptBridge.swift in Sources */, + FDAFFD33014A537108F9074F6EF7EAD9 /* RDEPUBWebView+Reflowable.swift in Sources */, + 76FA97B8B0555BBB9168276D59521D5E /* RDEPUBWebView+Search.swift in Sources */, + F476C2A659D5C2602D71D06B6E6E787D /* RDEPUBWebViewDebug.swift in Sources */, + 0C8D728CB0CC5778E6C145E3513EE9FC /* RDPlainTextBookBuilder.swift in Sources */, + FC2254599C78A7A07553A9642BE78A7A /* RDReaderContentCell.swift in Sources */, + A21BA505BD695436CEC13BAB790CD31C /* RDReaderFlowLayout.swift in Sources */, + 95BD0D6223ECC3F10719042A77CFEFA7 /* RDReaderGestureController.swift in Sources */, + 9F705127826BE270B99429C75CC2CA09 /* RDReaderPageChildViewController.swift in Sources */, + FB80A8C5A8855F5C7024834C815F52E6 /* RDReaderPagingController.swift in Sources */, + 9E85B0E4906F782371E049B67FB5C0C7 /* RDReaderPreloadController.swift in Sources */, + 22FC9F565564B285178F3420504813FF /* RDReaderSpreadResolver.swift in Sources */, + 59FB1B17033D0375CB66E1D98F56316A /* RDReaderTapRegionHandler.swift in Sources */, + F842359CAAE73EC96B02C6874B28CD60 /* RDReaderView.swift in Sources */, + 445CC2DF30F3DD5E01A7A0B42BE67ECA /* RDReaderView+CollectionView.swift in Sources */, + 0E5F3DFC2F07EEF586848939895F5449 /* RDReaderView+ContentAccess.swift in Sources */, + 70F696A554B44EF07110E6DAC0305F70 /* RDReaderView+PageCurl.swift in Sources */, + 2E33845786E9118FFCEBED5ABA2467E3 /* RDReaderView+ToolView.swift in Sources */, + 4F52FD03B23A749F8BAAA8D096941D29 /* RDReaderView-dummy.m in Sources */, + 929774A0C8800A3DE8378B9A3ED3BC80 /* RDReaderViewProtocols.swift in Sources */, + E52DBF03E160824F5C76EE696FED8FA1 /* RDURLReaderController.swift in Sources */, + FD2D0288F99B3E6405F4A33B7585103D /* String+SHA256.swift in Sources */, + 232E7F6C9858C024D263A2FB2045CADE /* UIColor+RDEPUBHex.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + CBE49EE63AF0F9347CE685FF36B1D2E6 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + AEA089F602E66B4E0CB655D1BF625880 /* DTActivityTitleView.m in Sources */, + 0EBABF6BEEA7615626818E066C16AF18 /* DTAnimatedGIF.m in Sources */, + 58ACB3DC0AC4EA63B6DC65585DC75509 /* DTBase64Coding.m in Sources */, + 8E1DC7355706F3565B8A0B38A59AAED9 /* DTBlockFunctions.m in Sources */, + B0984EA45A82B8CF021C4BA7CE6B2473 /* DTCoreGraphicsUtils.m in Sources */, + EACD47B345480FC9DE37F1BBEBC3E6E8 /* DTCustomColoredAccessory.m in Sources */, + 7F9B8AAB4623E3CC58C37D8EBFF514B4 /* DTExtendedFileAttributes.m in Sources */, + 4701EA34789A6B21772EC0B428C8CEF5 /* DTFolderMonitor.m in Sources */, + 9914ED7075FAF9F04BB3C4154952C9B9 /* DTFoundation-dummy.m in Sources */, + 458D53C484D2470508819D1D41376EE7 /* DTFoundationConstants.m in Sources */, + 4B58554A575684981F3BB1A6F90E4124 /* DTHTMLParser.m in Sources */, + 5966F7524564265D493279F72B6BEF10 /* DTLog.m in Sources */, + EC2221DF39BF4ABE42B4D74B98EE26BD /* DTPieProgressIndicator.m in Sources */, + E5B7FC56FA951AD4542B3EC0EBB7C6A6 /* DTSmartPagingScrollView.m in Sources */, + 0BF855C140122F5D1D410BA83E44DD50 /* DTTiledLayerWithoutFade.m in Sources */, + 0C798C4A69D1C2F7DD26AD589C05F260 /* DTVersion.m in Sources */, + 859B45A8A4A1B9FBEB3002EE8BEE46E1 /* NSArray+DTError.m in Sources */, + 792ACF8BA7BA68E9534E4B8D095690D6 /* NSData+DTCrypto.m in Sources */, + 191F106462110C3B72DC586D721BEB57 /* NSDictionary+DTError.m in Sources */, + 8329F87853CB379C84A455A9C72F0E97 /* NSFileWrapper+DTCopying.m in Sources */, + A43DAC6C9656DF7A74DCC11AB44433B5 /* NSMutableArray+DTMoving.m in Sources */, + 32844568EDF13978B21D3A12652DD1E9 /* NSString+DTFormatNumbers.m in Sources */, + 8ABB70BC85808639BE2820ED576705EF /* NSString+DTPaths.m in Sources */, + AF5E584EB1F14EC57A5DC51D5CD93FF2 /* NSString+DTURLEncoding.m in Sources */, + 74794205BE245B73D21939B91C6191C6 /* NSString+DTUtilities.m in Sources */, + B21AC2FBB25F091A5AE9A0602A9F4937 /* NSURL+DTAppLinks.m in Sources */, + A633C89374604FFFE5119DA09B8C5088 /* NSURL+DTComparing.m in Sources */, + 65957BA854E8610EA216E8323B136C5B /* NSURL+DTUnshorten.m in Sources */, + 3A5317D34FDA7B91C27C6CA5BEFFB7B3 /* UIApplication+DTNetworkActivity.m in Sources */, + 892B8712F2112DDFA6619A0913DE30B3 /* UIImage+DTFoundation.m in Sources */, + 3A14E77E5539416CB3616992AE1908EE /* UIScreen+DTFoundation.m in Sources */, + 0F5153F35326C04F32AC77F5FEB668A5 /* UIView+DTFoundation.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D809AB9DAE6E5DAE31F4D39808AE721B /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + AF1CFF8A037F62C86652D4A6A7FA7152 /* SSAlertAnimation.swift in Sources */, + B7A8736945B52D9330D687AFB4665FBE /* SSAlertAnimationController.swift in Sources */, + B1AC7E9FAE650F2D3CB2F0372A70A2EC /* SSAlertCommonView.swift in Sources */, + AA2535B6492261202B0A992C6C9D9134 /* SSAlertDefaultAnmation.swift in Sources */, + 820761BB2CDA45C00C99DC18A4BA08DC /* SSAlertPresentAnimation.swift in Sources */, + B7F802838B7854ECF8270F92D6A7DA3D /* SSAlertSwift-dummy.m in Sources */, + A37720F711BDB1D915B6DAC0BCD30D75 /* SSAlertView.swift in Sources */, + BBC18499E4D786E92B516E008ACEAD0F /* SSAlertViewExtention.swift in Sources */, + E364CAF459E15B2F4618459EF62B30E1 /* UIViewFrameExtension.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + ECAFA2044D12B026BC3810C100C9B993 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 16F7D312910FD9A6F8CCE8C7128390EB /* Constraint.swift in Sources */, - 99BEFA2791CD385F40B63F36FDE54020 /* ConstraintAttributes.swift in Sources */, - 48C129F6559E80BA658FE6824B8BA94F /* ConstraintConfig.swift in Sources */, - 7E2ACD5F2A6DB7B183F56A4B3F356AD0 /* ConstraintConstantTarget.swift in Sources */, - E55D6001C2F52C1C47E541F84B4C5A8A /* ConstraintDescription.swift in Sources */, - 5498E8AC301713ED5BEBDDA779B37AC9 /* ConstraintDirectionalInsets.swift in Sources */, - 3A6BC96AC902E04E85517D39B48F1FCD /* ConstraintDirectionalInsetTarget.swift in Sources */, - 2B81BEF86D6B38359592B4576EB1851A /* ConstraintDSL.swift in Sources */, - FDAAF8F1F0F13A657B57B92F75B5F2A0 /* ConstraintInsets.swift in Sources */, - A7AFFAFDE55A53049223F920254CAF86 /* ConstraintInsetTarget.swift in Sources */, - 9B739D53E63B8810FC6F307C7997A363 /* ConstraintItem.swift in Sources */, - 714C19C6756608FBFDD1DDBDBCBC294D /* ConstraintLayoutGuide.swift in Sources */, - FB2BFDE8FC140377D92952992080A8DB /* ConstraintLayoutGuide+Extensions.swift in Sources */, - F4A25EE18B07DECDA92E9D539D70A76B /* ConstraintLayoutGuideDSL.swift in Sources */, - 89068EC008D095EA391E422D25604753 /* ConstraintLayoutSupport.swift in Sources */, - 30618822C1BD51F652D2F1A6ABA7E871 /* ConstraintLayoutSupportDSL.swift in Sources */, - C1730B2336E26354E273E454C18E48A5 /* ConstraintMaker.swift in Sources */, - 03AA3739E62E025785FFB6EB6828B7C0 /* ConstraintMakerEditable.swift in Sources */, - AF261667F646799AC6B2D780044128B8 /* ConstraintMakerExtendable.swift in Sources */, - A15106734ACDCD5AA02C4F67C8F0F2B2 /* ConstraintMakerFinalizable.swift in Sources */, - D6104499A1F639473F78E573D24672F1 /* ConstraintMakerPrioritizable.swift in Sources */, - DA009A1B7AF4A6BB397EF6A0D54199EB /* ConstraintMakerRelatable.swift in Sources */, - 7AF352A0D8851E7032D631349459BADB /* ConstraintMakerRelatable+Extensions.swift in Sources */, - 7A31B9816036902B3A32A5C00CF641AC /* ConstraintMultiplierTarget.swift in Sources */, - 04138A4A20D11E47461B6CE8605C8080 /* ConstraintOffsetTarget.swift in Sources */, - DDFCE8D603027FA6C70FCED85A181CA8 /* ConstraintPriority.swift in Sources */, - 0F9CC95163443852A6C43FB306CBFCCA /* ConstraintPriorityTarget.swift in Sources */, - 32B51B6FF5F874A18A0C55F9D785CB24 /* ConstraintRelatableTarget.swift in Sources */, - 415F0FDFD64679F7847A8E1E92277D68 /* ConstraintRelation.swift in Sources */, - 20CBAD9874EBC0BECD8003634E76CC12 /* ConstraintView.swift in Sources */, - B00DB012B30976FE2972CC22585AE9DE /* ConstraintView+Extensions.swift in Sources */, - 7E68A2D73CBE9B63C25ED43B6D33D494 /* ConstraintViewDSL.swift in Sources */, - A2F0139EB87218A9361BB1F85800192A /* Debugging.swift in Sources */, - D0F53E98858850D4E7537E7BD40E8796 /* LayoutConstraint.swift in Sources */, - 2ACD02E15A8ABD8CD8D51AE3A6C29683 /* LayoutConstraintItem.swift in Sources */, - B16CC6DF8233C4E37B30A83420E10597 /* SnapKit-dummy.m in Sources */, - 113D2805F027DF665F9FEE905F6F9D0F /* Typealiases.swift in Sources */, - D5F9B30BDC204B4148E3BA2D6D69B90B /* UILayoutSupport+Extensions.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2749,141 +2862,105 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - FAFBEB5C156B9C7A1B4A93303133A682 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - FE207FF2A3BE8F6502C76C63584668D5 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 4195E8AE080906ABE5E4F962B53AF658 /* Archive.swift in Sources */, - 562A2DC2B45B453B2FD5C3F5F6EBE405 /* Archive+BackingConfiguration.swift in Sources */, - 43C745CDBA3B1C734F9F93E63D48FF51 /* Archive+Deprecated.swift in Sources */, - 1CB67E5F08E9C847E38ED61A79A1F656 /* Archive+Helpers.swift in Sources */, - 453AA8CE0133392DB836780F707A41C1 /* Archive+MemoryFile.swift in Sources */, - E6C30E9127E9DCDE49BDD8BBB7F81507 /* Archive+Progress.swift in Sources */, - 2D03767663B1C3251B80EDAB4F33D3CE /* Archive+Reading.swift in Sources */, - F981518F2DA0E5B53C2560759D14986B /* Archive+ReadingDeprecated.swift in Sources */, - 71624302D6B9222103A160FCC949D7B3 /* Archive+Writing.swift in Sources */, - 54634310A0DDCA6986A95D3A66CEECE0 /* Archive+WritingDeprecated.swift in Sources */, - 1693DD61EFC198586623CFC4CFCAD1DB /* Archive+ZIP64.swift in Sources */, - 359B41BE1F797CD1708D6E6473542222 /* Data+Compression.swift in Sources */, - 106FD56F563F6B4531E0C35C1680358E /* Data+CompressionDeprecated.swift in Sources */, - A12A29C8B64DAB94E6013711400452B1 /* Data+Serialization.swift in Sources */, - EE18BE5AFEFAA1893D26EFBA4AFA7193 /* Date+ZIP.swift in Sources */, - C48E39E0973F3D272FF1B3A23A43A4A0 /* Entry.swift in Sources */, - FD4B6F54758FB3894484A092B0A1B2AC /* Entry+Serialization.swift in Sources */, - 29DA9E401CE134D89FFFD1B7C2641DDA /* Entry+ZIP64.swift in Sources */, - 5BD388950720A80033201DCCC5B396B1 /* FileManager+ZIP.swift in Sources */, - A37500E37BE9E26DA5EE55916224DD7E /* FileManager+ZIPDeprecated.swift in Sources */, - E3B9A9854AB1AB2154E1CA2C6B9E61E2 /* URL+ZIP.swift in Sources */, - F2BAD19B5E842273FA3B4FB464F24EBA /* ZIPFoundation-dummy.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ - 1027BF5D9BE9ECB1B97EDFFEBDDF4B46 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - name = RDReaderView; - target = AA2E57587AA8EECA63C4BE08EA3CB6D2 /* RDReaderView */; - targetProxy = EA5C5FB405F5479FFA8A607CD33F3ECF /* PBXContainerItemProxy */; - }; - 17938577AC1DECCDD4044365CD98BEC2 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - name = SnapKit; - target = 19622742EBA51E823D6DAE3F8CDBFAD4 /* SnapKit */; - targetProxy = A7A366C3CFD62C32F5B8E5EB9A8310BB /* PBXContainerItemProxy */; - }; - 18B19C677B889F6727DBDA0F865BF1AB /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - name = SSAlertSwift; - target = 8619D5ADECF2B26CFD9A9826D61D289A /* SSAlertSwift */; - targetProxy = A0BFC7B494DC59D52F43761C722DAB2D /* PBXContainerItemProxy */; - }; - 463CDA137195F14051A84236470C93F2 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - name = "DTCoreText-Resources"; - target = 0C24CB0E87A728A11AA1124CB360D6A1 /* DTCoreText-Resources */; - targetProxy = 2B7231DD02DE6D67ED0FF347C2D2831A /* PBXContainerItemProxy */; - }; - 5308D4674A93AF4C1261B8B667E85038 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - name = SSAlertSwift; - target = 8619D5ADECF2B26CFD9A9826D61D289A /* SSAlertSwift */; - targetProxy = 7C28D11939EC5ABB1D98898772B52AED /* PBXContainerItemProxy */; - }; - 5ABBCDAACF3D91809C05806C33121BBC /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - name = "ZIPFoundation-ZIPFoundation_Privacy"; - target = C7A8D82E407CD3FDC3BA55CEE519B252 /* ZIPFoundation-ZIPFoundation_Privacy */; - targetProxy = 41C358828795BDBE26E60DEDDB6F5A31 /* PBXContainerItemProxy */; - }; - 6087769AB1CAFBD7015BB5E5FB531C1C /* PBXTargetDependency */ = { + 020D39B2C63A014534BDC11B71D0241B /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = ZIPFoundation; target = AA15C8469D67684160CC2A7098EB841C /* ZIPFoundation */; - targetProxy = 962A98D094FBC182A4454FA956B2E681 /* PBXContainerItemProxy */; + targetProxy = 2AA2622E7B72A2DFD0367159EB235920 /* PBXContainerItemProxy */; }; - 72E48F15033CCF95398C27EBDD634478 /* PBXTargetDependency */ = { + 38010528A947DA219346F858DA074410 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = DTCoreText; target = B88F4EA0695B6B3165C64594850D72C7 /* DTCoreText */; - targetProxy = A0CA6E77551A041C81844E5901F24EFC /* PBXContainerItemProxy */; + targetProxy = FE4C489BFE4741DF6A4850DD59D2B47C /* PBXContainerItemProxy */; }; - 79C767A041874113D95C87515FD8FFBB /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - name = "SnapKit-SnapKit_Privacy"; - target = 8A8DB685241263AFDF5E6B20FE67B93A /* SnapKit-SnapKit_Privacy */; - targetProxy = B1D7453B0F93836CC086060595CB753A /* PBXContainerItemProxy */; - }; - 9561148C38876D85CE97DA5473FE5CD1 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - name = DTCoreText; - target = B88F4EA0695B6B3165C64594850D72C7 /* DTCoreText */; - targetProxy = 5BA4345CC5FD80F403AD69F002484017 /* PBXContainerItemProxy */; - }; - B49B8B5B0940A82855A49049BD54424B /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - name = ZIPFoundation; - target = AA15C8469D67684160CC2A7098EB841C /* ZIPFoundation */; - targetProxy = 8EC7B58761479718591F7FDFB9BC34E2 /* PBXContainerItemProxy */; - }; - BDB2E3ACF2EDD090E479F5F13928DEFD /* PBXTargetDependency */ = { + 3D4C2D88EE1F510051E6AF02F9542B7D /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = "RDReaderView-RDReaderViewAssets"; target = AE7F393FB7805DE2664AB4111873F907 /* RDReaderView-RDReaderViewAssets */; - targetProxy = 51F9BB05FB5362314AABDBF12FD0F619 /* PBXContainerItemProxy */; + targetProxy = 04CB85A41687F3E51BC4003ABA093E54 /* PBXContainerItemProxy */; }; - C3E83C94AC96087258AD38AFA3A7F7D6 /* PBXTargetDependency */ = { + 4D6AACC2BB50A205D880B6E1DE55515F /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = DTFoundation; + target = 8F6E5A5BF72D62CDFD25F91A7CFA3309 /* DTFoundation */; + targetProxy = 291EF29F5CECA009529D76F9FE26B4B9 /* PBXContainerItemProxy */; + }; + 523664B453CC2C70A82386A366470305 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = SnapKit; target = 19622742EBA51E823D6DAE3F8CDBFAD4 /* SnapKit */; - targetProxy = 24BA72D0D912AE5A45EAC25AC27B6B7F /* PBXContainerItemProxy */; + targetProxy = B1D5725EC0757F29DD19F470E271A8DB /* PBXContainerItemProxy */; }; - E2AA5E04C0D28740D6B40D3F0CFD237B /* PBXTargetDependency */ = { + 52F1324C9B757070324EF0998E9DB369 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = DTCoreText; + target = B88F4EA0695B6B3165C64594850D72C7 /* DTCoreText */; + targetProxy = 7E29E8795F2AF85B1EE50F510591F961 /* PBXContainerItemProxy */; + }; + 66924DF60BD8C99A51BFF98D087E464C /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = SnapKit; + target = 19622742EBA51E823D6DAE3F8CDBFAD4 /* SnapKit */; + targetProxy = 7BAA191D501A47FE7939B55CA03FE66E /* PBXContainerItemProxy */; + }; + 6FCFA9E56CF303BBCF5B5F5ECF0F1582 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = "SnapKit-SnapKit_Privacy"; + target = 8A8DB685241263AFDF5E6B20FE67B93A /* SnapKit-SnapKit_Privacy */; + targetProxy = 296863D40BD5EF3E4022BD63E7A8B0C7 /* PBXContainerItemProxy */; + }; + 784BDE9B9AF828365D293AE2E195B42D /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = RDReaderView; + target = AA2E57587AA8EECA63C4BE08EA3CB6D2 /* RDReaderView */; + targetProxy = FEFD55D4A212E925212A5E4683FFA101 /* PBXContainerItemProxy */; + }; + 7FC7D99E28F71505A70765A20279B182 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = SSAlertSwift; + target = 8619D5ADECF2B26CFD9A9826D61D289A /* SSAlertSwift */; + targetProxy = 95CED971E55EB31D8A064B92A3B4D9B2 /* PBXContainerItemProxy */; + }; + B2C24A9B733A2D23824DFE0C7FEFDE69 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = ZIPFoundation; + target = AA15C8469D67684160CC2A7098EB841C /* ZIPFoundation */; + targetProxy = 8FC75B4885448A5DE210832192113C7B /* PBXContainerItemProxy */; + }; + C012A32E450C01FA72E44227CB008605 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = SSAlertSwift; + target = 8619D5ADECF2B26CFD9A9826D61D289A /* SSAlertSwift */; + targetProxy = 854186CDD7ACBD2359F0E9F298BF270D /* PBXContainerItemProxy */; + }; + DEA55EBE6C71C231E96349A3450C0332 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = "DTCoreText-Resources"; + target = 0C24CB0E87A728A11AA1124CB360D6A1 /* DTCoreText-Resources */; + targetProxy = 7AD1BAAD6E745A14A9201807DB7B6F66 /* PBXContainerItemProxy */; + }; + ED57AD9569753E7911D812E329294918 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = "ZIPFoundation-ZIPFoundation_Privacy"; + target = C7A8D82E407CD3FDC3BA55CEE519B252 /* ZIPFoundation-ZIPFoundation_Privacy */; + targetProxy = DC1726A240735222D1E9C105AF7AB304 /* PBXContainerItemProxy */; + }; + F93D7A676BEA733A5E0954FB9B5432FB /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = DTFoundation; target = 8F6E5A5BF72D62CDFD25F91A7CFA3309 /* DTFoundation */; - targetProxy = F24E133D0B4B0D43780A5C9975FA4EF9 /* PBXContainerItemProxy */; - }; - FD3219BCD8F392FD503D4E3C397FFF1B /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - name = DTFoundation; - target = 8F6E5A5BF72D62CDFD25F91A7CFA3309 /* DTFoundation */; - targetProxy = CE5F9B32E7622F85FC1F741CE5013575 /* PBXContainerItemProxy */; + targetProxy = FCA8F8EFC9D0E6769A5625F6E48F3C1B /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ - 0509BF697B2586AC7FD23EAB586720C2 /* Debug */ = { + 02B34FA52040002D4460D86AA5DF0EC8 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = ECBA4C3DE9806BC9E5E66E0EE07FA019 /* RDReaderView.debug.xcconfig */; + baseConfigurationReference = 6014DA7213E4687224F2BF921E30002C /* DTFoundation.debug.xcconfig */; buildSettings = { CLANG_ENABLE_OBJC_WEAK = NO; "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; @@ -2896,9 +2973,9 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_MODULE_VERIFIER = NO; ENABLE_USER_SCRIPT_SANDBOXING = NO; - GCC_PREFIX_HEADER = "Target Support Files/RDReaderView/RDReaderView-prefix.pch"; + GCC_PREFIX_HEADER = "Target Support Files/DTFoundation/DTFoundation-prefix.pch"; GENERATE_INFOPLIST_FILE = NO; - INFOPLIST_FILE = "Target Support Files/RDReaderView/RDReaderView-Info.plist"; + INFOPLIST_FILE = "Target Support Files/DTFoundation/DTFoundation-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 15.6; LD_RUNPATH_SEARCH_PATHS = ( @@ -2906,14 +2983,14 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MODULEMAP_FILE = "Target Support Files/RDReaderView/RDReaderView.modulemap"; - PRODUCT_MODULE_NAME = RDReaderView; - PRODUCT_NAME = RDReaderView; + MODULEMAP_FILE = "Target Support Files/DTFoundation/DTFoundation.modulemap"; + PRODUCT_MODULE_NAME = DTFoundation; + PRODUCT_NAME = DTFoundation; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_INSTALL_OBJC_HEADER = YES; - SWIFT_VERSION = 5.10; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -2960,57 +3037,17 @@ }; name = Release; }; - 14E902315F96C3E06466914BDEF2E4DE /* Release */ = { + 2C71F2C660C40EA12978829B61992376 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = F699881ABDBEB419AEBA661AD5A21F35 /* ZIPFoundation.release.xcconfig */; - buildSettings = { - CLANG_ENABLE_OBJC_WEAK = NO; - "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; - "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; - CURRENT_PROJECT_VERSION = 1; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - ENABLE_MODULE_VERIFIER = NO; - ENABLE_USER_SCRIPT_SANDBOXING = NO; - GCC_PREFIX_HEADER = "Target Support Files/ZIPFoundation/ZIPFoundation-prefix.pch"; - GENERATE_INFOPLIST_FILE = NO; - INFOPLIST_FILE = "Target Support Files/ZIPFoundation/ZIPFoundation-Info.plist"; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 15.6; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - MODULEMAP_FILE = "Target Support Files/ZIPFoundation/ZIPFoundation.modulemap"; - PRODUCT_MODULE_NAME = ZIPFoundation; - PRODUCT_NAME = ZIPFoundation; - SDKROOT = iphoneos; - SKIP_INSTALL = YES; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; - SWIFT_INSTALL_OBJC_HEADER = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = Release; - }; - 1E23F8CF35DBDFA6A4493BEBC89A2DD9 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = F699881ABDBEB419AEBA661AD5A21F35 /* ZIPFoundation.release.xcconfig */; + baseConfigurationReference = C2840BE6D91479CCBC2E908FBD9DA183 /* DTCoreText.release.xcconfig */; buildSettings = { CODE_SIGNING_ALLOWED = NO; - CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/ZIPFoundation"; + CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/DTCoreText"; ENABLE_USER_SCRIPT_SANDBOXING = NO; - IBSC_MODULE = ZIPFoundation; - INFOPLIST_FILE = "Target Support Files/ZIPFoundation/ResourceBundle-ZIPFoundation_Privacy-ZIPFoundation-Info.plist"; + IBSC_MODULE = DTCoreText; + INFOPLIST_FILE = "Target Support Files/DTCoreText/ResourceBundle-Resources-DTCoreText-Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 15.6; - PRODUCT_NAME = ZIPFoundation_Privacy; + PRODUCT_NAME = Resources; SDKROOT = iphoneos; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = "1,2"; @@ -3018,63 +3055,6 @@ }; name = Release; }; - 2A0DB7A5A82BC920C14BAAD0FEC0A0B0 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 4BEC60CB3EDA1C189D3B062E29217440 /* ZIPFoundation.debug.xcconfig */; - buildSettings = { - CLANG_ENABLE_OBJC_WEAK = NO; - "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; - "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; - CURRENT_PROJECT_VERSION = 1; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - ENABLE_MODULE_VERIFIER = NO; - ENABLE_USER_SCRIPT_SANDBOXING = NO; - GCC_PREFIX_HEADER = "Target Support Files/ZIPFoundation/ZIPFoundation-prefix.pch"; - GENERATE_INFOPLIST_FILE = NO; - INFOPLIST_FILE = "Target Support Files/ZIPFoundation/ZIPFoundation-Info.plist"; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 15.6; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - MODULEMAP_FILE = "Target Support Files/ZIPFoundation/ZIPFoundation.modulemap"; - PRODUCT_MODULE_NAME = ZIPFoundation; - PRODUCT_NAME = ZIPFoundation; - SDKROOT = iphoneos; - SKIP_INSTALL = YES; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; - SWIFT_INSTALL_OBJC_HEADER = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = Debug; - }; - 2D10C67881789378F732CC76CCAE6837 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = C960B7AA0B9B8CC473FACFEC64F9906D /* SnapKit.debug.xcconfig */; - buildSettings = { - CODE_SIGNING_ALLOWED = NO; - CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/SnapKit"; - ENABLE_USER_SCRIPT_SANDBOXING = NO; - IBSC_MODULE = SnapKit; - INFOPLIST_FILE = "Target Support Files/SnapKit/ResourceBundle-SnapKit_Privacy-SnapKit-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 15.6; - PRODUCT_NAME = SnapKit_Privacy; - SDKROOT = iphoneos; - SKIP_INSTALL = YES; - TARGETED_DEVICE_FAMILY = "1,2"; - WRAPPER_EXTENSION = bundle; - }; - name = Debug; - }; 44A4B2FB6211B90C1949A44A381F6D1F /* Release */ = { isa = XCBuildConfiguration; buildSettings = { @@ -3138,47 +3118,9 @@ }; name = Release; }; - 5494BEAD922982C0DE945CEFF977EEEF /* Debug */ = { + 5A943FA699624AA65CD92CD61CA309C7 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 18387159515E0523AFB4204B1AF940ED /* SSAlertSwift.debug.xcconfig */; - buildSettings = { - "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; - "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; - CURRENT_PROJECT_VERSION = 1; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - ENABLE_MODULE_VERIFIER = NO; - ENABLE_USER_SCRIPT_SANDBOXING = NO; - GCC_PREFIX_HEADER = "Target Support Files/SSAlertSwift/SSAlertSwift-prefix.pch"; - GENERATE_INFOPLIST_FILE = NO; - INFOPLIST_FILE = "Target Support Files/SSAlertSwift/SSAlertSwift-Info.plist"; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 15.6; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - MODULEMAP_FILE = "Target Support Files/SSAlertSwift/SSAlertSwift.modulemap"; - PRODUCT_MODULE_NAME = SSAlertSwift; - PRODUCT_NAME = SSAlertSwift; - SDKROOT = iphoneos; - SKIP_INSTALL = YES; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; - SWIFT_INSTALL_OBJC_HEADER = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = Debug; - }; - 580453B8C583E8C0040C721E84F93EC9 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = DF670EC438F05ACC1F1C7B725DBDAA18 /* DTFoundation.release.xcconfig */; + baseConfigurationReference = C960B7AA0B9B8CC473FACFEC64F9906D /* SnapKit.debug.xcconfig */; buildSettings = { CLANG_ENABLE_OBJC_WEAK = NO; "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; @@ -3191,9 +3133,9 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_MODULE_VERIFIER = NO; ENABLE_USER_SCRIPT_SANDBOXING = NO; - GCC_PREFIX_HEADER = "Target Support Files/DTFoundation/DTFoundation-prefix.pch"; + GCC_PREFIX_HEADER = "Target Support Files/SnapKit/SnapKit-prefix.pch"; GENERATE_INFOPLIST_FILE = NO; - INFOPLIST_FILE = "Target Support Files/DTFoundation/DTFoundation-Info.plist"; + INFOPLIST_FILE = "Target Support Files/SnapKit/SnapKit-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 15.6; LD_RUNPATH_SEARCH_PATHS = ( @@ -3201,22 +3143,39 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MODULEMAP_FILE = "Target Support Files/DTFoundation/DTFoundation.modulemap"; - PRODUCT_MODULE_NAME = DTFoundation; - PRODUCT_NAME = DTFoundation; + MODULEMAP_FILE = "Target Support Files/SnapKit/SnapKit.modulemap"; + PRODUCT_MODULE_NAME = SnapKit; + PRODUCT_NAME = SnapKit; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_INSTALL_OBJC_HEADER = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; - name = Release; + name = Debug; }; - 69CF396177D4FBBD3D4F9FC3FABCEB0F /* Release */ = { + 685BC0E07ADC4E4085C7C07EF8363E80 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = ECBA4C3DE9806BC9E5E66E0EE07FA019 /* RDReaderView.debug.xcconfig */; + buildSettings = { + CODE_SIGNING_ALLOWED = NO; + CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/RDReaderView"; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + IBSC_MODULE = RDReaderView; + INFOPLIST_FILE = "Target Support Files/RDReaderView/ResourceBundle-RDReaderViewAssets-RDReaderView-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 15.6; + PRODUCT_NAME = RDReaderViewAssets; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + WRAPPER_EXTENSION = bundle; + }; + name = Debug; + }; + 6D4BE2C05D48A177AD7DA4B4C0D519B2 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = FE2991699D0391172DEADF2F4613E320 /* SSAlertSwift.release.xcconfig */; buildSettings = { @@ -3255,24 +3214,6 @@ }; name = Release; }; - 73B63AD8CE8CC4CDED4CF1429F2905C4 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = C2840BE6D91479CCBC2E908FBD9DA183 /* DTCoreText.release.xcconfig */; - buildSettings = { - CODE_SIGNING_ALLOWED = NO; - CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/DTCoreText"; - ENABLE_USER_SCRIPT_SANDBOXING = NO; - IBSC_MODULE = DTCoreText; - INFOPLIST_FILE = "Target Support Files/DTCoreText/ResourceBundle-Resources-DTCoreText-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 15.6; - PRODUCT_NAME = Resources; - SDKROOT = iphoneos; - SKIP_INSTALL = YES; - TARGETED_DEVICE_FAMILY = "1,2"; - WRAPPER_EXTENSION = bundle; - }; - name = Release; - }; 73BBB08AD96AC1A6A095E15ADC5FCEE1 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 306477706BBB1E784F55BCF8EB1AD737 /* Pods-ReadViewDemo.debug.xcconfig */; @@ -3312,11 +3253,10 @@ }; name = Debug; }; - 7E52461F2289583CD17270976F26947F /* Debug */ = { + 82A8748EEA39796EEFCEDF8A758FC9B3 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 6014DA7213E4687224F2BF921E30002C /* DTFoundation.debug.xcconfig */; + baseConfigurationReference = 18387159515E0523AFB4204B1AF940ED /* SSAlertSwift.debug.xcconfig */; buildSettings = { - CLANG_ENABLE_OBJC_WEAK = NO; "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; @@ -3327,9 +3267,9 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_MODULE_VERIFIER = NO; ENABLE_USER_SCRIPT_SANDBOXING = NO; - GCC_PREFIX_HEADER = "Target Support Files/DTFoundation/DTFoundation-prefix.pch"; + GCC_PREFIX_HEADER = "Target Support Files/SSAlertSwift/SSAlertSwift-prefix.pch"; GENERATE_INFOPLIST_FILE = NO; - INFOPLIST_FILE = "Target Support Files/DTFoundation/DTFoundation-Info.plist"; + INFOPLIST_FILE = "Target Support Files/SSAlertSwift/SSAlertSwift-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 15.6; LD_RUNPATH_SEARCH_PATHS = ( @@ -3337,9 +3277,9 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MODULEMAP_FILE = "Target Support Files/DTFoundation/DTFoundation.modulemap"; - PRODUCT_MODULE_NAME = DTFoundation; - PRODUCT_NAME = DTFoundation; + MODULEMAP_FILE = "Target Support Files/SSAlertSwift/SSAlertSwift.modulemap"; + PRODUCT_MODULE_NAME = SSAlertSwift; + PRODUCT_NAME = SSAlertSwift; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; @@ -3351,24 +3291,6 @@ }; name = Debug; }; - 9AECF78B85E25BE28C3B863489170F98 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = ECBA4C3DE9806BC9E5E66E0EE07FA019 /* RDReaderView.debug.xcconfig */; - buildSettings = { - CODE_SIGNING_ALLOWED = NO; - CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/RDReaderView"; - ENABLE_USER_SCRIPT_SANDBOXING = NO; - IBSC_MODULE = RDReaderView; - INFOPLIST_FILE = "Target Support Files/RDReaderView/ResourceBundle-RDReaderViewAssets-RDReaderView-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 15.6; - PRODUCT_NAME = RDReaderViewAssets; - SDKROOT = iphoneos; - SKIP_INSTALL = YES; - TARGETED_DEVICE_FAMILY = "1,2"; - WRAPPER_EXTENSION = bundle; - }; - name = Debug; - }; A0DC1C2C814704734F83EAB35BB20B21 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -3436,24 +3358,6 @@ }; name = Debug; }; - A13E928C9D069741C4FD709D8B157643 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = A6B5316985734E51970FA0FC168498A6 /* RDReaderView.release.xcconfig */; - buildSettings = { - CODE_SIGNING_ALLOWED = NO; - CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/RDReaderView"; - ENABLE_USER_SCRIPT_SANDBOXING = NO; - IBSC_MODULE = RDReaderView; - INFOPLIST_FILE = "Target Support Files/RDReaderView/ResourceBundle-RDReaderViewAssets-RDReaderView-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 15.6; - PRODUCT_NAME = RDReaderViewAssets; - SDKROOT = iphoneos; - SKIP_INSTALL = YES; - TARGETED_DEVICE_FAMILY = "1,2"; - WRAPPER_EXTENSION = bundle; - }; - name = Release; - }; A1F5B44E8E7C59A962189D14989BCAFF /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = C2840BE6D91479CCBC2E908FBD9DA183 /* DTCoreText.release.xcconfig */; @@ -3533,9 +3437,9 @@ }; name = Debug; }; - C2D179142F0FE1E8B60F6EC6DFDA73E9 /* Debug */ = { + B42AF4755CAC5CDFC9AE224F6A1CCFE7 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 4BEC60CB3EDA1C189D3B062E29217440 /* ZIPFoundation.debug.xcconfig */; + baseConfigurationReference = F699881ABDBEB419AEBA661AD5A21F35 /* ZIPFoundation.release.xcconfig */; buildSettings = { CODE_SIGNING_ALLOWED = NO; CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/ZIPFoundation"; @@ -3549,51 +3453,11 @@ TARGETED_DEVICE_FAMILY = "1,2"; WRAPPER_EXTENSION = bundle; }; - name = Debug; - }; - D709D1D91FCABD64C95632AEF46E83A2 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 53D2AB2348A9B9C582FDC2CDB2E434FC /* SnapKit.release.xcconfig */; - buildSettings = { - CLANG_ENABLE_OBJC_WEAK = NO; - "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; - "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; - CURRENT_PROJECT_VERSION = 1; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - ENABLE_MODULE_VERIFIER = NO; - ENABLE_USER_SCRIPT_SANDBOXING = NO; - GCC_PREFIX_HEADER = "Target Support Files/SnapKit/SnapKit-prefix.pch"; - GENERATE_INFOPLIST_FILE = NO; - INFOPLIST_FILE = "Target Support Files/SnapKit/SnapKit-Info.plist"; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 15.6; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - MODULEMAP_FILE = "Target Support Files/SnapKit/SnapKit.modulemap"; - PRODUCT_MODULE_NAME = SnapKit; - PRODUCT_NAME = SnapKit; - SDKROOT = iphoneos; - SKIP_INSTALL = YES; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; - SWIFT_INSTALL_OBJC_HEADER = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; name = Release; }; - DCF7FA9FEC7849EB35AD1E621B454F4E /* Debug */ = { + B4E5B0A6A06FAC07945E566E6C517D69 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = C960B7AA0B9B8CC473FACFEC64F9906D /* SnapKit.debug.xcconfig */; + baseConfigurationReference = ECBA4C3DE9806BC9E5E66E0EE07FA019 /* RDReaderView.debug.xcconfig */; buildSettings = { CLANG_ENABLE_OBJC_WEAK = NO; "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; @@ -3606,9 +3470,9 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_MODULE_VERIFIER = NO; ENABLE_USER_SCRIPT_SANDBOXING = NO; - GCC_PREFIX_HEADER = "Target Support Files/SnapKit/SnapKit-prefix.pch"; + GCC_PREFIX_HEADER = "Target Support Files/RDReaderView/RDReaderView-prefix.pch"; GENERATE_INFOPLIST_FILE = NO; - INFOPLIST_FILE = "Target Support Files/SnapKit/SnapKit-Info.plist"; + INFOPLIST_FILE = "Target Support Files/RDReaderView/RDReaderView-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 15.6; LD_RUNPATH_SEARCH_PATHS = ( @@ -3616,21 +3480,21 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MODULEMAP_FILE = "Target Support Files/SnapKit/SnapKit.modulemap"; - PRODUCT_MODULE_NAME = SnapKit; - PRODUCT_NAME = SnapKit; + MODULEMAP_FILE = "Target Support Files/RDReaderView/RDReaderView.modulemap"; + PRODUCT_MODULE_NAME = RDReaderView; + PRODUCT_NAME = RDReaderView; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_INSTALL_OBJC_HEADER = YES; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 5.10; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Debug; }; - EEBB94326521D5AE916353F8BB9D139A /* Debug */ = { + BA59167D644A3C4695666149AEAA5B74 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 78873FAFC0AD9E930D6050A7F976714B /* DTCoreText.debug.xcconfig */; buildSettings = { @@ -3648,7 +3512,158 @@ }; name = Debug; }; - F281040EA5CFC27B696ACB295764A9B6 /* Release */ = { + BC071929212605FE7F2953B1AAD20DB8 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 4BEC60CB3EDA1C189D3B062E29217440 /* ZIPFoundation.debug.xcconfig */; + buildSettings = { + CLANG_ENABLE_OBJC_WEAK = NO; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = NO; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_PREFIX_HEADER = "Target Support Files/ZIPFoundation/ZIPFoundation-prefix.pch"; + GENERATE_INFOPLIST_FILE = NO; + INFOPLIST_FILE = "Target Support Files/ZIPFoundation/ZIPFoundation-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 15.6; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MODULEMAP_FILE = "Target Support Files/ZIPFoundation/ZIPFoundation.modulemap"; + PRODUCT_MODULE_NAME = ZIPFoundation; + PRODUCT_NAME = ZIPFoundation; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_INSTALL_OBJC_HEADER = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + C30B1246B72AFB8228DA8A932855A727 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F699881ABDBEB419AEBA661AD5A21F35 /* ZIPFoundation.release.xcconfig */; + buildSettings = { + CLANG_ENABLE_OBJC_WEAK = NO; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = NO; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_PREFIX_HEADER = "Target Support Files/ZIPFoundation/ZIPFoundation-prefix.pch"; + GENERATE_INFOPLIST_FILE = NO; + INFOPLIST_FILE = "Target Support Files/ZIPFoundation/ZIPFoundation-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 15.6; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MODULEMAP_FILE = "Target Support Files/ZIPFoundation/ZIPFoundation.modulemap"; + PRODUCT_MODULE_NAME = ZIPFoundation; + PRODUCT_NAME = ZIPFoundation; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_INSTALL_OBJC_HEADER = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + CADEECD9FCE4C079DB0041B11ECEF824 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 53D2AB2348A9B9C582FDC2CDB2E434FC /* SnapKit.release.xcconfig */; + buildSettings = { + CODE_SIGNING_ALLOWED = NO; + CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/SnapKit"; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + IBSC_MODULE = SnapKit; + INFOPLIST_FILE = "Target Support Files/SnapKit/ResourceBundle-SnapKit_Privacy-SnapKit-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 15.6; + PRODUCT_NAME = SnapKit_Privacy; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + WRAPPER_EXTENSION = bundle; + }; + name = Release; + }; + D6043FDD2E62A3DCB481359416EC3F68 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A6B5316985734E51970FA0FC168498A6 /* RDReaderView.release.xcconfig */; + buildSettings = { + CODE_SIGNING_ALLOWED = NO; + CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/RDReaderView"; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + IBSC_MODULE = RDReaderView; + INFOPLIST_FILE = "Target Support Files/RDReaderView/ResourceBundle-RDReaderViewAssets-RDReaderView-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 15.6; + PRODUCT_NAME = RDReaderViewAssets; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + WRAPPER_EXTENSION = bundle; + }; + name = Release; + }; + DF32DF8C2DB9882F5FBF92D4FD35026B /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 4BEC60CB3EDA1C189D3B062E29217440 /* ZIPFoundation.debug.xcconfig */; + buildSettings = { + CODE_SIGNING_ALLOWED = NO; + CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/ZIPFoundation"; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + IBSC_MODULE = ZIPFoundation; + INFOPLIST_FILE = "Target Support Files/ZIPFoundation/ResourceBundle-ZIPFoundation_Privacy-ZIPFoundation-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 15.6; + PRODUCT_NAME = ZIPFoundation_Privacy; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + WRAPPER_EXTENSION = bundle; + }; + name = Debug; + }; + F2DD1A83D1BF480F0E63DF5AC960AFD3 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = C960B7AA0B9B8CC473FACFEC64F9906D /* SnapKit.debug.xcconfig */; + buildSettings = { + CODE_SIGNING_ALLOWED = NO; + CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/SnapKit"; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + IBSC_MODULE = SnapKit; + INFOPLIST_FILE = "Target Support Files/SnapKit/ResourceBundle-SnapKit_Privacy-SnapKit-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 15.6; + PRODUCT_NAME = SnapKit_Privacy; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + WRAPPER_EXTENSION = bundle; + }; + name = Debug; + }; + F55AD4EE54A598A3FAF86F90D4FC341E /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = A6B5316985734E51970FA0FC168498A6 /* RDReaderView.release.xcconfig */; buildSettings = { @@ -3688,21 +3703,83 @@ }; name = Release; }; - F4D4FF0CE7108A328E6383C37F51C2BC /* Release */ = { + FB25821897C732341DE4B0E9AFFD51A2 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 53D2AB2348A9B9C582FDC2CDB2E434FC /* SnapKit.release.xcconfig */; buildSettings = { - CODE_SIGNING_ALLOWED = NO; - CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/SnapKit"; + CLANG_ENABLE_OBJC_WEAK = NO; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = NO; ENABLE_USER_SCRIPT_SANDBOXING = NO; - IBSC_MODULE = SnapKit; - INFOPLIST_FILE = "Target Support Files/SnapKit/ResourceBundle-SnapKit_Privacy-SnapKit-Info.plist"; + GCC_PREFIX_HEADER = "Target Support Files/SnapKit/SnapKit-prefix.pch"; + GENERATE_INFOPLIST_FILE = NO; + INFOPLIST_FILE = "Target Support Files/SnapKit/SnapKit-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 15.6; - PRODUCT_NAME = SnapKit_Privacy; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MODULEMAP_FILE = "Target Support Files/SnapKit/SnapKit.modulemap"; + PRODUCT_MODULE_NAME = SnapKit; + PRODUCT_NAME = SnapKit; SDKROOT = iphoneos; SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_INSTALL_OBJC_HEADER = YES; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; - WRAPPER_EXTENSION = bundle; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + FC464F0C10CBA7214FA572E1F2B7B712 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = DF670EC438F05ACC1F1C7B725DBDAA18 /* DTFoundation.release.xcconfig */; + buildSettings = { + CLANG_ENABLE_OBJC_WEAK = NO; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = NO; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_PREFIX_HEADER = "Target Support Files/DTFoundation/DTFoundation-prefix.pch"; + GENERATE_INFOPLIST_FILE = NO; + INFOPLIST_FILE = "Target Support Files/DTFoundation/DTFoundation-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 15.6; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MODULEMAP_FILE = "Target Support Files/DTFoundation/DTFoundation.modulemap"; + PRODUCT_MODULE_NAME = DTFoundation; + PRODUCT_NAME = DTFoundation; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_INSTALL_OBJC_HEADER = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; }; name = Release; }; @@ -3718,6 +3795,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 31D05D1A391D978801D2C6E4C3F44F8D /* Build configuration list for PBXNativeTarget "SnapKit-SnapKit_Privacy" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F2DD1A83D1BF480F0E63DF5AC960AFD3 /* Debug */, + CADEECD9FCE4C079DB0041B11ECEF824 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 4821239608C13582E20E6DA73FD5F1F9 /* Build configuration list for PBXProject "Pods" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -3727,56 +3813,56 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 5DA67BEF5F46ECF4C32ACCFE78FCEF84 /* Build configuration list for PBXNativeTarget "ZIPFoundation" */ = { + 5DE3BEAC81CBDABB0222A889DBDCEA1A /* Build configuration list for PBXNativeTarget "RDReaderView-RDReaderViewAssets" */ = { isa = XCConfigurationList; buildConfigurations = ( - 2A0DB7A5A82BC920C14BAAD0FEC0A0B0 /* Debug */, - 14E902315F96C3E06466914BDEF2E4DE /* Release */, + 685BC0E07ADC4E4085C7C07EF8363E80 /* Debug */, + D6043FDD2E62A3DCB481359416EC3F68 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 693BA49EA4876BA40D36F688DAD8D7BF /* Build configuration list for PBXNativeTarget "DTFoundation" */ = { + 6BA5558505B7824FEAAE792E5D7D3702 /* Build configuration list for PBXNativeTarget "ZIPFoundation" */ = { isa = XCConfigurationList; buildConfigurations = ( - 7E52461F2289583CD17270976F26947F /* Debug */, - 580453B8C583E8C0040C721E84F93EC9 /* Release */, + BC071929212605FE7F2953B1AAD20DB8 /* Debug */, + C30B1246B72AFB8228DA8A932855A727 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 73B0667287C7357BD7E64D54FC10E7CA /* Build configuration list for PBXNativeTarget "RDReaderView" */ = { + 797A74DE557B2312CDE4DDE7BA924EF3 /* Build configuration list for PBXNativeTarget "DTFoundation" */ = { isa = XCConfigurationList; buildConfigurations = ( - 0509BF697B2586AC7FD23EAB586720C2 /* Debug */, - F281040EA5CFC27B696ACB295764A9B6 /* Release */, + 02B34FA52040002D4460D86AA5DF0EC8 /* Debug */, + FC464F0C10CBA7214FA572E1F2B7B712 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 84B6961CC1F169E2D6D5FC7861A8582E /* Build configuration list for PBXNativeTarget "RDReaderView-RDReaderViewAssets" */ = { + 7CEB01B66666A5A4E42C5E76A0DCE0B9 /* Build configuration list for PBXNativeTarget "RDReaderView" */ = { isa = XCConfigurationList; buildConfigurations = ( - 9AECF78B85E25BE28C3B863489170F98 /* Debug */, - A13E928C9D069741C4FD709D8B157643 /* Release */, + B4E5B0A6A06FAC07945E566E6C517D69 /* Debug */, + F55AD4EE54A598A3FAF86F90D4FC341E /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 91423BFD48D44DAFE716B4DA47D303CA /* Build configuration list for PBXNativeTarget "SnapKit-SnapKit_Privacy" */ = { + 93A78DDD7C59F62735C4CF878C3F3EF3 /* Build configuration list for PBXNativeTarget "DTCoreText-Resources" */ = { isa = XCConfigurationList; buildConfigurations = ( - 2D10C67881789378F732CC76CCAE6837 /* Debug */, - F4D4FF0CE7108A328E6383C37F51C2BC /* Release */, + BA59167D644A3C4695666149AEAA5B74 /* Debug */, + 2C71F2C660C40EA12978829B61992376 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 9E4196CFA8812083973C756D405439CE /* Build configuration list for PBXNativeTarget "SnapKit" */ = { + B4819549570DE20B5ED63BD74F5A1AA1 /* Build configuration list for PBXNativeTarget "SSAlertSwift" */ = { isa = XCConfigurationList; buildConfigurations = ( - DCF7FA9FEC7849EB35AD1E621B454F4E /* Debug */, - D709D1D91FCABD64C95632AEF46E83A2 /* Release */, + 82A8748EEA39796EEFCEDF8A758FC9B3 /* Debug */, + 6D4BE2C05D48A177AD7DA4B4C0D519B2 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; @@ -3790,29 +3876,20 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - D216B4C9C1EB4572666F55DEAC1E9CA1 /* Build configuration list for PBXNativeTarget "SSAlertSwift" */ = { + D4770C93C14F02E45B3B5072996B2DF9 /* Build configuration list for PBXNativeTarget "SnapKit" */ = { isa = XCConfigurationList; buildConfigurations = ( - 5494BEAD922982C0DE945CEFF977EEEF /* Debug */, - 69CF396177D4FBBD3D4F9FC3FABCEB0F /* Release */, + 5A943FA699624AA65CD92CD61CA309C7 /* Debug */, + FB25821897C732341DE4B0E9AFFD51A2 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - D280590029F64DDED598A210724CF31E /* Build configuration list for PBXNativeTarget "ZIPFoundation-ZIPFoundation_Privacy" */ = { + F065AB76006D6C22073E864A37DCED8E /* Build configuration list for PBXNativeTarget "ZIPFoundation-ZIPFoundation_Privacy" */ = { isa = XCConfigurationList; buildConfigurations = ( - C2D179142F0FE1E8B60F6EC6DFDA73E9 /* Debug */, - 1E23F8CF35DBDFA6A4493BEBC89A2DD9 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - F45A5207FBF041DEA1FF688C5CC78E8B /* Build configuration list for PBXNativeTarget "DTCoreText-Resources" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - EEBB94326521D5AE916353F8BB9D139A /* Debug */, - 73B63AD8CE8CC4CDED4CF1429F2905C4 /* Release */, + DF32DF8C2DB9882F5FBF92D4FD35026B /* Debug */, + B42AF4755CAC5CDFC9AE224F6A1CCFE7 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; diff --git a/ReadViewDemo/ReadViewDemo.xcodeproj/project.pbxproj b/ReadViewDemo/ReadViewDemo.xcodeproj/project.pbxproj index 329a98e..ad3743f 100644 --- a/ReadViewDemo/ReadViewDemo.xcodeproj/project.pbxproj +++ b/ReadViewDemo/ReadViewDemo.xcodeproj/project.pbxproj @@ -7,6 +7,11 @@ objects = { /* Begin PBXBuildFile section */ + 1A2B3C4D00000001AABBCC01 /* BookmarkTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A2B3C4D00000002AABBCC01 /* BookmarkTests.swift */; }; + 1A2B3C4D00000003AABBCC01 /* TableOfContentsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A2B3C4D00000004AABBCC01 /* TableOfContentsTests.swift */; }; + 1A2B3C4D00000005AABBCC01 /* SettingsExtendedTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A2B3C4D00000006AABBCC01 /* SettingsExtendedTests.swift */; }; + 1A2B3C4D00000007AABBCC01 /* PageNavigationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A2B3C4D00000008AABBCC01 /* PageNavigationTests.swift */; }; + 1A2B3C4D00000009AABBCC01 /* ReaderAnnotationExtendedTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A2B3C4D0000000AAABBCC01 /* ReaderAnnotationExtendedTests.swift */; }; 23BB1155EA379786DAA10A89 /* DisplayTypeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB49AFCCBC2BE04C82B8F286 /* DisplayTypeTests.swift */; }; 3BC5C96D7A0ACF35F2192CC7 /* XCUIApplication+Launch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74B4C44287820D68ED6570F8 /* XCUIApplication+Launch.swift */; }; 4509ED928F228F43888E063D /* ReaderToolbarTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BADF0A18AD034B74A482A4C1 /* ReaderToolbarTests.swift */; }; @@ -16,11 +21,6 @@ C08FF8D030048DC5147729E9 /* ReaderOpenCloseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EDF066BA12974E6CFBE519F /* ReaderOpenCloseTests.swift */; }; DE1437A969DA1C5F0CBB047D /* Pods_ReadViewDemo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 792DF85CE4A3DD80D67843C7 /* Pods_ReadViewDemo.framework */; }; FEDB5937CEB858CB06E38E2D /* SettingsPanelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 201C2B482287866487EFAE66 /* SettingsPanelTests.swift */; }; - 1A2B3C4D00000001AABBCC01 /* BookmarkTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A2B3C4D00000002AABBCC01 /* BookmarkTests.swift */; }; - 1A2B3C4D00000003AABBCC01 /* TableOfContentsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A2B3C4D00000004AABBCC01 /* TableOfContentsTests.swift */; }; - 1A2B3C4D00000005AABBCC01 /* SettingsExtendedTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A2B3C4D00000006AABBCC01 /* SettingsExtendedTests.swift */; }; - 1A2B3C4D00000007AABBCC01 /* PageNavigationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A2B3C4D00000008AABBCC01 /* PageNavigationTests.swift */; }; - 1A2B3C4D00000009AABBCC01 /* ReaderAnnotationExtendedTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A2B3C4D0000000AAABBCC01 /* ReaderAnnotationExtendedTests.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -35,6 +35,11 @@ /* Begin PBXFileReference section */ 00F495E17B90CCF1C7FE8C27 /* AccessibilityIdentifiers.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AccessibilityIdentifiers.swift; sourceTree = ""; }; + 1A2B3C4D00000002AABBCC01 /* BookmarkTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = BookmarkTests.swift; sourceTree = ""; }; + 1A2B3C4D00000004AABBCC01 /* TableOfContentsTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TableOfContentsTests.swift; sourceTree = ""; }; + 1A2B3C4D00000006AABBCC01 /* SettingsExtendedTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SettingsExtendedTests.swift; sourceTree = ""; }; + 1A2B3C4D00000008AABBCC01 /* PageNavigationTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PageNavigationTests.swift; sourceTree = ""; }; + 1A2B3C4D0000000AAABBCC01 /* ReaderAnnotationExtendedTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ReaderAnnotationExtendedTests.swift; sourceTree = ""; }; 201C2B482287866487EFAE66 /* SettingsPanelTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SettingsPanelTests.swift; sourceTree = ""; }; 20BD15E5D8F04E7E9A239E14 /* ReaderAnnotationTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ReaderAnnotationTests.swift; sourceTree = ""; }; 3A43AED288BFCA3ADBA97DD7 /* Pods-ReadViewDemo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReadViewDemo.release.xcconfig"; path = "Target Support Files/Pods-ReadViewDemo/Pods-ReadViewDemo.release.xcconfig"; sourceTree = ""; }; @@ -47,11 +52,6 @@ BADF0A18AD034B74A482A4C1 /* ReaderToolbarTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ReaderToolbarTests.swift; sourceTree = ""; }; DE070C1D2FBF0CC900ED065F /* ReadViewDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ReadViewDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; FB49AFCCBC2BE04C82B8F286 /* DisplayTypeTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DisplayTypeTests.swift; sourceTree = ""; }; - 1A2B3C4D00000002AABBCC01 /* BookmarkTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = BookmarkTests.swift; sourceTree = ""; }; - 1A2B3C4D00000004AABBCC01 /* TableOfContentsTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TableOfContentsTests.swift; sourceTree = ""; }; - 1A2B3C4D00000006AABBCC01 /* SettingsExtendedTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SettingsExtendedTests.swift; sourceTree = ""; }; - 1A2B3C4D00000008AABBCC01 /* PageNavigationTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PageNavigationTests.swift; sourceTree = ""; }; - 1A2B3C4D0000000AAABBCC01 /* ReaderAnnotationExtendedTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ReaderAnnotationExtendedTests.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ @@ -364,6 +364,7 @@ CODE_SIGN_STYLE = Manual; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = MG4Z7FU83W; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.6; PRODUCT_BUNDLE_IDENTIFIER = cn.shen.ReadViewDemoUITests; @@ -388,6 +389,7 @@ CODE_SIGN_STYLE = Manual; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = MG4Z7FU83W; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.6; PRODUCT_BUNDLE_IDENTIFIER = cn.shen.ReadViewDemoUITests; diff --git a/ReadViewDemo/ReadViewDemo/ViewController.swift b/ReadViewDemo/ReadViewDemo/ViewController.swift index da3a4a2..ec1ae47 100644 --- a/ReadViewDemo/ReadViewDemo/ViewController.swift +++ b/ReadViewDemo/ReadViewDemo/ViewController.swift @@ -62,76 +62,6 @@ final class ViewController: UIViewController { } } - private enum ValidationCategory: String, CaseIterable { - case textNovel = "TXT/小说" - case textRich = "复杂图文" - case webFixed = "Fixed/互动" - case plainText = "TXT" - - var sortOrder: Int { - switch self { - case .textNovel: return 0 - case .textRich: return 1 - case .webFixed: return 2 - case .plainText: return 3 - } - } - } - - private struct BookValidationReport { - let title: String - let category: ValidationCategory - let profile: String - let passed: Bool - let notes: [String] - } - - private struct ResourceValidationSummary { - let checkedCount: Int - let passedCount: Int - let failedBooks: [String] - let matrixLines: [String] - let diagnosticLines: [String] - let rerunHint: String - - var statusText: String { - var lines: [String] = [] - if checkedCount == 0 { - lines.append("样本验证:未发现可验证样本") - } else if failedBooks.isEmpty { - lines.append("样本验证:\(passedCount)/\(checkedCount) 通过") - } else { - let titles = failedBooks.prefix(2).joined(separator: "、") - lines.append("样本验证:\(passedCount)/\(checkedCount) 通过,失败样本:\(titles)") - } - lines.append(contentsOf: matrixLines.prefix(3)) - lines.append(contentsOf: prioritizedDiagnosticLines()) - if !rerunHint.isEmpty { - lines.append(rerunHint) - } - return lines.joined(separator: "\n") - } - - private func prioritizedDiagnosticLines() -> [String] { - var selected: [String] = [] - if let semanticLine = diagnosticLines.first(where: { $0.contains("属性闭环诊断") }) { - selected.append(semanticLine) - } - if let paginationLine = diagnosticLines.first(where: { $0.contains("分页诊断") && !selected.contains($0) }) { - selected.append(paginationLine) - } - if selected.count < 2 { - for line in diagnosticLines where !selected.contains(line) { - selected.append(line) - if selected.count == 2 { - break - } - } - } - return selected - } - } - private let statusLabel: UILabel = { let label = UILabel() label.numberOfLines = 0 @@ -163,15 +93,9 @@ final class ViewController: UIViewController { }() private var books: [DemoBook] = [] - private var validationSummary: ResourceValidationSummary? - private var validationTask: Task? private let launchAutomationPlan = LaunchAutomationPlan.parse(arguments: ProcessInfo.processInfo.arguments) private var didRunLaunchAutomation = false - deinit { - validationTask?.cancel() - } - override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .systemBackground @@ -212,7 +136,6 @@ final class ViewController: UIViewController { books = discoverBooks() tableView.reloadData() updateChrome() - validateSampleBooks() } private func updateChrome() { @@ -220,14 +143,7 @@ final class ViewController: UIViewController { statusLabel.text = "ReadViewSDK Demo\nbook 目录下未找到 txt 或 epub 文件" tableView.backgroundView = emptyStateLabel } else { - var lines = [ - "ReadViewSDK Demo", - "已发现 \(books.count) 本本地图书,点击即可进入阅读器" - ] - if let validationSummary { - lines.append(validationSummary.statusText) - } - statusLabel.text = lines.joined(separator: "\n") + statusLabel.text = "ReadViewSDK Demo\n已发现 \(books.count) 本本地图书,点击即可进入阅读器" tableView.backgroundView = nil } } @@ -271,379 +187,6 @@ final class ViewController: UIViewController { } } - private func validateSampleBooks() { - validationTask?.cancel() - guard !books.isEmpty else { - validationSummary = nil - updateChrome() - return - } - - let pageSize = currentReaderPageSize() - let style = currentReaderTextStyle() - validationTask = Task.detached(priority: .utility) { [books] in - let summary = Self.validateBooks(books, pageSize: pageSize, style: style) - await MainActor.run { - print("[ReadViewDemo] \(summary.statusText)") - self.validationSummary = summary - self.updateChrome() - } - } - } - - nonisolated private static func validateBooks( - _ books: [DemoBook], - pageSize: CGSize, - style: RDEPUBTextRenderStyle - ) -> ResourceValidationSummary { - var checkedCount = 0 - var passedCount = 0 - var failedBooks: [String] = [] - var reports: [BookValidationReport] = [] - var diagnosticLines: [String] = [] - - for book in books { - let result = validateBook(book, pageSize: pageSize, style: style) - checkedCount += 1 - if result.report.passed { - passedCount += 1 - } else { - failedBooks.append(book.title) - } - reports.append(result.report) - diagnosticLines.append(contentsOf: result.diagnostics) - } - - return ResourceValidationSummary( - checkedCount: checkedCount, - passedCount: passedCount, - failedBooks: failedBooks, - matrixLines: makeMatrixLines(from: reports), - diagnosticLines: diagnosticLines, - rerunHint: "复现入口:启动 Demo 查看摘要,打开样本书后再验证搜索 / 高亮 / 主题字号切换" - ) - } - - nonisolated private static func validateBook( - _ book: DemoBook, - pageSize: CGSize, - style: RDEPUBTextRenderStyle - ) -> (report: BookValidationReport, diagnostics: [String]) { - let fileExtension = book.fileURL.pathExtension.lowercased() - if fileExtension == "txt" { - let builder = RDPlainTextBookBuilder() - do { - let textBook = try builder.build(textFileURL: book.fileURL, pageSize: pageSize, style: style) - let passed = !textBook.pages.isEmpty && !textBook.chapters.isEmpty - let report = BookValidationReport( - title: book.title, - category: .plainText, - profile: "txt", - passed: passed, - notes: [ - "章节 \(textBook.chapters.count)", - "页数 \(textBook.pages.count)" - ] - ) - let diagnostics = [ - "TXT 验证:\(book.title) · chapters \(textBook.chapters.count) · pages \(textBook.pages.count)" - ] - return (report, diagnostics) - } catch { - let report = BookValidationReport( - title: book.title, - category: .plainText, - profile: "txt", - passed: false, - notes: [error.localizedDescription] - ) - return (report, ["TXT 验证失败:\(book.title) · \(error.localizedDescription)"]) - } - } - - let parser = RDEPUBParser() - do { - try parser.parse(epubURL: book.fileURL) - let publication = parser.makePublication() - switch publication.readingProfile { - case .textReflowable: - return validateReflowableBook( - book, - parser: parser, - publication: publication, - pageSize: pageSize, - style: style - ) - case .webFixedLayout, .webInteractive: - return validateWebBook( - book, - publication: publication, - viewportSize: pageSize - ) - } - } catch { - let report = BookValidationReport( - title: book.title, - category: .textRich, - profile: "parse-failed", - passed: false, - notes: [error.localizedDescription] - ) - return (report, ["EPUB 验证失败:\(book.title) · \(error.localizedDescription)"]) - } - } - - nonisolated private static func validateReflowableBook( - _ book: DemoBook, - parser: RDEPUBParser, - publication: RDEPUBPublication, - pageSize: CGSize, - style: RDEPUBTextRenderStyle - ) -> (report: BookValidationReport, diagnostics: [String]) { - let builder = RDEPUBTextBookBuilder() - do { - let textBook = try builder.build( - parser: parser, - publication: publication, - pageSize: pageSize, - style: style - ) - let missingResources = builder.lastBuildResourceDiagnostics.filter { !$0.existsOnDisk } - let diagnostics = builder.lastBuildPaginationDiagnostics - let attachmentPages = diagnostics.reduce(0) { $0 + $1.attachmentPageCount } - let category: ValidationCategory = attachmentPages > 0 ? .textRich : .textNovel - let passed = !textBook.pages.isEmpty && missingResources.isEmpty - - var reportNotes = [ - "章节 \(textBook.chapters.count)", - "页数 \(textBook.pages.count)" - ] - if attachmentPages > 0 { - reportNotes.append("attachment 页 \(attachmentPages)") - } - - var summaryLines: [String] = [] - if let paginationSummary = makePaginationSummary(diagnostics, title: book.title) { - summaryLines.append("分页诊断:\(paginationSummary)") - } - if let semanticSummary = builder.phase7SemanticSummary(title: book.title) { - summaryLines.append("属性闭环诊断:\(semanticSummary)") - } - if let restoreSummary = makeRestoreSummary( - parser: parser, - publication: publication, - textBook: textBook, - pageSize: pageSize, - style: style, - title: book.title - ) { - summaryLines.append("恢复诊断:\(restoreSummary)") - } - - let report = BookValidationReport( - title: book.title, - category: category, - profile: publication.readingProfile.rawValue, - passed: passed, - notes: reportNotes - ) - return (report, summaryLines) - } catch { - let category: ValidationCategory = book.title.contains("凡人") ? .textNovel : .textRich - let report = BookValidationReport( - title: book.title, - category: category, - profile: publication.readingProfile.rawValue, - passed: false, - notes: [error.localizedDescription] - ) - return (report, ["EPUB 验证失败:\(book.title) · \(error.localizedDescription)"]) - } - } - - nonisolated private static func validateWebBook( - _ book: DemoBook, - publication: RDEPUBPublication, - viewportSize: CGSize - ) -> (report: BookValidationReport, diagnostics: [String]) { - let linearItems = publication.spine.filter(\.linear) - let missingFiles = linearItems.filter { - guard let normalizedHref = publication.resourceResolver.normalizedHref($0.href) else { - return true - } - return publication.resourceResolver.fileURL(forRelativePath: normalizedHref) == nil - } - let defaultConfiguration = RDEPUBReaderConfiguration.default - let preferences = RDEPUBPreferences( - fontSize: defaultConfiguration.fontSize, - lineHeightMultiple: defaultConfiguration.lineHeightMultiple, - reflowableContentInsets: defaultConfiguration.reflowableContentInsets, - fixedContentInset: defaultConfiguration.fixedContentInset, - fixedLayoutFit: defaultConfiguration.fixedLayoutFit, - fixedLayoutSpreadMode: defaultConfiguration.fixedLayoutSpreadMode - ) - let spreadCount = publication.layout == .fixed - ? publication.makeFixedSpreads( - preferences: preferences, - viewportSize: viewportSize - ).count - : 0 - let passed = !linearItems.isEmpty && missingFiles.isEmpty && (publication.layout != .fixed || spreadCount > 0) - let category: ValidationCategory = .webFixed - let report = BookValidationReport( - title: book.title, - category: category, - profile: publication.readingProfile.rawValue, - passed: passed, - notes: [ - "spine \(linearItems.count)", - publication.layout == .fixed ? "spread \(spreadCount)" : "interactive" - ] - ) - let diagnostic = "Web 路径验证:\(book.title) · profile \(publication.readingProfile.rawValue) · spine \(linearItems.count) · missing \(missingFiles.count)" - return (report, [diagnostic]) - } - - nonisolated private static func makeMatrixLines(from reports: [BookValidationReport]) -> [String] { - let grouped = Dictionary(grouping: reports, by: \.category) - return ValidationCategory.allCases.compactMap { category in - guard let items = grouped[category], !items.isEmpty else { return nil } - let passed = items.filter(\.passed).count - let notes = items.prefix(2).map { "\($0.title)(\($0.profile))" }.joined(separator: "、") - return "矩阵[\(category.rawValue)] \(passed)/\(items.count) · \(notes)" - } - } - - nonisolated private static func makePaginationSummary( - _ diagnostics: [RDEPUBTextChapterPaginationDiagnostic], - title: String - ) -> String? { - guard !diagnostics.isEmpty else { return nil } - let attachmentPages = diagnostics.reduce(0) { $0 + $1.attachmentPageCount } - let semanticBreakPages = diagnostics.reduce(0) { $0 + $1.blockAdjustedPageCount } - let blockKinds = uniqueValues(diagnostics.flatMap(\.blockKinds)) - let semanticHints = uniqueValues(diagnostics.flatMap(\.semanticHints)) - let attachmentPlacements = uniqueValues(diagnostics.flatMap(\.attachmentPlacements)) - let breakReasonCounts = diagnostics - .flatMap(\.breakReasons) - .reduce(into: [RDEPUBTextPageBreakReason: Int]()) { counts, reason in - counts[reason, default: 0] += 1 - } - let orderedReasons = breakReasonCounts - .sorted { lhs, rhs in - if lhs.value == rhs.value { - return lhs.key.rawValue < rhs.key.rawValue - } - return lhs.value > rhs.value - } - .map { "\($0.key.rawValue):\($0.value)" } - .joined(separator: ", ") - let note = diagnostics - .flatMap(\.sampleNotes) - .first(where: { $0.contains("attachment") || $0.contains("block") || $0.contains("page break") }) - var parts = [ - "\(title)", - "章节 \(diagnostics.count)", - "attachment 页 \(attachmentPages)", - "semantic break 页 \(semanticBreakPages)", - blockKinds.isEmpty ? nil : "block kinds [\(blockKinds.map(\.rawValue).joined(separator: ","))]", - semanticHints.isEmpty ? nil : "hints [\(semanticHints.map(\.rawValue).joined(separator: ","))]", - attachmentPlacements.isEmpty ? nil : "placements [\(attachmentPlacements.map(\.rawValue).joined(separator: ","))]", - orderedReasons.isEmpty ? nil : "reasons [\(orderedReasons)]" - ].compactMap { $0 } - if let note { - parts.append(note) - } - return parts.joined(separator: " · ") - } - - nonisolated private static func makeRestoreSummary( - parser: RDEPUBParser, - publication: RDEPUBPublication, - textBook: RDEPUBTextBook, - pageSize: CGSize, - style: RDEPUBTextRenderStyle, - title: String - ) -> String? { - guard textBook.pages.count > 1 else { return nil } - let targetPageNumber = min(max(textBook.pages.count / 2, 1), textBook.pages.count) - guard let restoreLocation = textBook.location(forPageNumber: targetPageNumber, bookIdentifier: publication.metadata.identifier), - let baseResolvedPage = textBook.pageNumber( - for: restoreLocation, - resolver: publication.resourceResolver, - bookIdentifier: publication.metadata.identifier - ) else { - return nil - } - - let alternateFont = UIFont.systemFont(ofSize: style.font.pointSize + 2) - let alternateLineSpacing = style.lineSpacing + max(alternateFont.lineHeight * 0.15, 2) - let alternateStyle = RDEPUBTextRenderStyle( - font: alternateFont, - lineSpacing: alternateLineSpacing, - textColor: style.textColor, - backgroundColor: style.backgroundColor - ) - let themeStyle = RDEPUBTextRenderStyle( - font: style.font, - lineSpacing: style.lineSpacing, - textColor: .white, - backgroundColor: .black - ) - - let builder = RDEPUBTextBookBuilder() - guard let alternateBook = try? builder.build( - parser: parser, - publication: publication, - pageSize: pageSize, - style: alternateStyle - ), let alternatePageNumber = alternateBook.pageNumber( - for: restoreLocation, - resolver: publication.resourceResolver, - bookIdentifier: publication.metadata.identifier - ), let alternateResolvedLocation = alternateBook.location( - forPageNumber: alternatePageNumber, - bookIdentifier: publication.metadata.identifier - ) else { - return nil - } - - let themeBuilder = RDEPUBTextBookBuilder() - let themeBook = try? themeBuilder.build( - parser: parser, - publication: publication, - pageSize: pageSize, - style: themeStyle - ) - let themePageNumber = themeBook?.pageNumber( - for: restoreLocation, - resolver: publication.resourceResolver, - bookIdentifier: publication.metadata.identifier - ) - - let hrefStable = (publication.resourceResolver.normalizedHref(alternateResolvedLocation.href) ?? alternateResolvedLocation.href) == - (publication.resourceResolver.normalizedHref(restoreLocation.href) ?? restoreLocation.href) - let progressionDelta = abs(alternateResolvedLocation.navigationProgression - restoreLocation.navigationProgression) - let themeStable = themePageNumber == baseResolvedPage - - return [ - title, - "base \(baseResolvedPage)", - "font-shift \(alternatePageNumber)", - "theme-stable \(themeStable ? "yes" : "no")", - "href-stable \(hrefStable ? "yes" : "no")", - String(format: "progression-delta %.3f", progressionDelta) - ].joined(separator: " · ") - } - - nonisolated private static func uniqueValues(_ values: [T]) -> [T] { - values.reduce(into: [T]()) { result, value in - if !result.contains(value) { - result.append(value) - } - } - } - @discardableResult private func openBook( _ book: DemoBook, @@ -683,27 +226,6 @@ final class ViewController: UIViewController { return controller } - private func currentReaderPageSize() -> CGSize { - let viewportSize = UIScreen.main.bounds.size - let insets = RDEPUBReaderConfiguration.default.reflowableContentInsets - return CGSize( - width: max(viewportSize.width - insets.left - insets.right, 1), - height: max(viewportSize.height - insets.top - insets.bottom, 1) - ) - } - - private func currentReaderTextStyle() -> RDEPUBTextRenderStyle { - let configuration = RDEPUBReaderConfiguration.default - let font = configuration.fontChoice.font(ofSize: configuration.fontSize) - let lineSpacing = max(font.lineHeight * (configuration.lineHeightMultiple - 1), 4) - return RDEPUBTextRenderStyle( - font: font, - lineSpacing: lineSpacing, - textColor: configuration.theme.contentTextColor, - backgroundColor: configuration.theme.contentBackgroundColor - ) - } - private func runLaunchAutomationIfNeeded() { guard !didRunLaunchAutomation, let launchAutomationPlan, diff --git a/ReadViewDemo/ReadViewDemoUITests/Helpers/DemoReaderState.swift b/ReadViewDemo/ReadViewDemoUITests/Helpers/DemoReaderState.swift new file mode 100644 index 0000000..fdc00fb --- /dev/null +++ b/ReadViewDemo/ReadViewDemoUITests/Helpers/DemoReaderState.swift @@ -0,0 +1,71 @@ +import XCTest + +struct DemoReaderState { + let rawValue: String + let fields: [String: String] + + init(rawValue: String) { + self.rawValue = rawValue + var parsed: [String: String] = [:] + let separators = CharacterSet.whitespacesAndNewlines.union(CharacterSet(charactersIn: ";")) + for token in rawValue.components(separatedBy: separators) where !token.isEmpty { + guard let delimiterIndex = token.firstIndex(of: "=") else { continue } + let key = String(token[.. String? { + fields[key] + } + + var isOpened: Bool { fields["reader"] == "opened" } + var page: Int? { fields["page"].flatMap(Int.init) } + var display: String? { fields["display"] } + var toolbar: String? { fields["toolbar"] } + var highlights: Int? { fields["highlights"].flatMap(Int.init) } + var selection: Int? { fields["selection"].flatMap(Int.init) } + var href: String? { fields["href"] } + var progression: Double? { fields["progression"].flatMap(Double.init) } + var mode: String? { fields["mode"] } + var pagination: String? { fields["pagination"] } + var knownPages: Int? { fields["knownPages"].flatMap(Int.init) } + var knownChapters: Int? { fields["knownChapters"].flatMap(Int.init) } + var buildableChapters: Int? { fields["buildableChapters"].flatMap(Int.init) } + var avoidWidows: Int? { fields["avoidWidows"].flatMap(Int.init) } + var avoidOrphans: Int? { fields["avoidOrphans"].flatMap(Int.init) } +} + +extension XCUIApplication { + func currentDemoReaderState() -> DemoReaderState? { + let state = staticTexts[IDs.demoReaderState] + guard state.exists else { return nil } + return DemoReaderState(rawValue: state.label) + } + + @discardableResult + func waitForDemoReaderState( + timeout: TimeInterval = 8, + description: String, + where predicate: (DemoReaderState) -> Bool + ) -> DemoReaderState { + let stateElement = staticTexts[IDs.demoReaderState] + let deadline = Date().addingTimeInterval(timeout) + var lastState = DemoReaderState(rawValue: "") + + while Date() < deadline { + if stateElement.exists { + lastState = DemoReaderState(rawValue: stateElement.label) + if predicate(lastState) { + return lastState + } + } + RunLoop.current.run(until: Date().addingTimeInterval(0.1)) + } + + XCTFail("阅读器状态未满足条件: \(description),当前:\(lastState.rawValue)") + return lastState + } +} diff --git a/ReadViewDemo/ReadViewDemoUITests/Helpers/XCUIApplication+Launch.swift b/ReadViewDemo/ReadViewDemoUITests/Helpers/XCUIApplication+Launch.swift index 09492bc..7db8052 100644 --- a/ReadViewDemo/ReadViewDemoUITests/Helpers/XCUIApplication+Launch.swift +++ b/ReadViewDemo/ReadViewDemoUITests/Helpers/XCUIApplication+Launch.swift @@ -25,12 +25,15 @@ extension XCUIApplication { func waitForReader(timeout: TimeInterval = 12) -> XCUIElement { let state = staticTexts[IDs.demoReaderState] XCTAssertTrue(state.waitForExistence(timeout: timeout), "阅读器状态标签未出现") - XCTAssertTrue(state.label.contains("reader=opened"), "阅读器未进入 opened 状态:\(state.label)") + let parsed = DemoReaderState(rawValue: state.label) + XCTAssertTrue(parsed.isOpened, "阅读器未进入 opened 状态:\(state.label)") return state } func waitForReaderPage(_ pageNumber: Int, timeout: TimeInterval = 12) { - waitForReaderState(containing: "page=\(pageNumber)", timeout: timeout) + _ = waitForDemoReaderState(timeout: timeout, description: "page=\(pageNumber)") { state in + state.page == pageNumber + } } func showReaderChromeIfNeeded() { diff --git a/ReadViewDemo/ReadViewDemoUITests/ReaderUITests/BookmarkManagementTests.swift b/ReadViewDemo/ReadViewDemoUITests/ReaderUITests/BookmarkManagementTests.swift new file mode 100644 index 0000000..74db5d7 --- /dev/null +++ b/ReadViewDemo/ReadViewDemoUITests/ReaderUITests/BookmarkManagementTests.swift @@ -0,0 +1,95 @@ +import XCTest + +final class BookmarkManagementTests: XCTestCase { + private let app = XCUIApplication() + + override func setUpWithError() throws { + continueAfterFailure = false + } + + func testBookmarksPanelShowsBookmarkAfterAdding() throws { + app.launchAndOpenSampleBook() + app.waitForReader(timeout: 12) + + app.showReaderChromeIfNeeded() + let bookmarkButton = app.buttons[IDs.readerBookmark] + XCTAssertTrue(bookmarkButton.waitForExistence(timeout: 3), "书签按钮不存在") + bookmarkButton.tap() + app.waitForReaderState(containing: "bookmarks=1", timeout: 5) + + app.showReaderChromeIfNeeded() + let bookmarksButton = app.buttons[IDs.readerBookmarks] + XCTAssertTrue(bookmarksButton.waitForExistence(timeout: 3), "书签列表按钮不存在") + bookmarksButton.tap() + + let bookmarksTable = app.tables[IDs.readerBookmarksTable] + XCTAssertTrue(bookmarksTable.waitForExistence(timeout: 5), "书签表格未出现") + XCTAssertTrue(waitForCellCount(in: bookmarksTable, minimum: 1, timeout: 5), "添加书签后面板应有至少 1 行数据") + } + + func testBookmarksPanelEmptyState() throws { + app.launchAndOpenSampleBook(resetsReaderState: true) + app.waitForReader(timeout: 12) + + app.showReaderChromeIfNeeded() + let bookmarksButton = app.buttons[IDs.readerBookmarks] + XCTAssertTrue(bookmarksButton.waitForExistence(timeout: 3), "书签列表按钮不存在") + bookmarksButton.tap() + + XCTAssertTrue(app.tables[IDs.readerBookmarksTable].waitForExistence(timeout: 5), "书签面板未出现") + XCTAssertTrue(app.staticTexts[IDs.readerBookmarksEmptyLabel].waitForExistence(timeout: 5), "空状态 label 应存在") + XCTAssertTrue(app.staticTexts[IDs.readerBookmarksEmptyLabel].label.contains("暂无"), "空状态应显示暂无书签") + } + + func testDeleteBookmarkFromPanel() throws { + app.launchAndOpenSampleBook() + app.waitForReader(timeout: 12) + + app.showReaderChromeIfNeeded() + app.buttons[IDs.readerBookmark].tap() + app.waitForReaderState(containing: "bookmarks=1", timeout: 5) + app.showReaderChromeIfNeeded() + app.buttons[IDs.readerBookmarks].tap() + + let bookmarksTable = app.tables[IDs.readerBookmarksTable] + XCTAssertTrue(bookmarksTable.waitForExistence(timeout: 5)) + XCTAssertTrue(waitForCellCount(in: bookmarksTable, minimum: 1, timeout: 5)) + + bookmarksTable.cells.element(boundBy: 0).tap() + + let deleteButton = app.buttons["删除书签"] + if deleteButton.waitForExistence(timeout: 3) { deleteButton.tap() } + + XCTAssertTrue(app.staticTexts[IDs.readerBookmarksEmptyLabel].waitForExistence(timeout: 5), "删除后应显示空状态") + } + + func testBookmarkIconStateToggle() throws { + app.launchAndOpenSampleBook() + app.waitForReader(timeout: 12) + + app.showReaderChromeIfNeeded() + let bookmarkButton = app.buttons[IDs.readerBookmark] + XCTAssertTrue(bookmarkButton.waitForExistence(timeout: 3), "书签按钮不存在") + + bookmarkButton.tap() + XCTAssertTrue(bookmarkButton.waitForExistence(timeout: 3), "添加书签后按钮应仍然存在") + + bookmarkButton.tap() + XCTAssertTrue(bookmarkButton.waitForExistence(timeout: 3), "移除书签后按钮应仍然存在") + } + + private func waitForCellCount( + in table: XCUIElement, + minimum: Int, + timeout: TimeInterval + ) -> Bool { + let deadline = Date().addingTimeInterval(timeout) + while Date() < deadline { + if table.cells.count >= minimum { + return true + } + RunLoop.current.run(until: Date().addingTimeInterval(0.1)) + } + return table.cells.count >= minimum + } +} diff --git a/ReadViewDemo/ReadViewDemoUITests/ReaderUITests/ErrorAndEdgeCaseTests.swift b/ReadViewDemo/ReadViewDemoUITests/ReaderUITests/ErrorAndEdgeCaseTests.swift new file mode 100644 index 0000000..268863a --- /dev/null +++ b/ReadViewDemo/ReadViewDemoUITests/ReaderUITests/ErrorAndEdgeCaseTests.swift @@ -0,0 +1,69 @@ +import XCTest + +final class ErrorAndEdgeCaseTests: XCTestCase { + private let app = XCUIApplication() + + override func setUpWithError() throws { + continueAfterFailure = false + } + + func testVerticalScrollSwipeNavigation() throws { + app.launchAndOpenSampleBook(displayType: "verticalscroll") + app.waitForReader(timeout: 12) + + let paging = app.collectionViews[IDs.readerPaging] + if paging.waitForExistence(timeout: 5) { paging.swipeUp() } + + app.waitForReaderState(containing: "reader=opened", timeout: 5) + } + + func testOpenAndCloseMultipleTimes() throws { + for iteration in 1...3 { + app.launchAndOpenSampleBook() + app.waitForReader(timeout: 12) + + app.showReaderChromeIfNeeded() + let backButton = app.buttons[IDs.readerBack] + XCTAssertTrue(backButton.waitForExistence(timeout: 3), "返回按钮不存在") + backButton.tap() + + let booksTable = app.tables[IDs.demoBooksTable] + XCTAssertTrue(booksTable.waitForExistence(timeout: 5), "书列表未出现") + } + } + + func testRapidPageNavigation() throws { + app.launchAndOpenSampleBook() + app.waitForReader(timeout: 12) + app.waitForReaderPage(1, timeout: 5) + + let paging = app.collectionViews[IDs.readerPaging] + guard paging.waitForExistence(timeout: 5) else { throw XCTSkip("分页视图不存在") } + + for _ in 1...5 { paging.swipeLeft() } + + app.waitForReaderState(containing: "reader=opened", timeout: 8) + } + + func testSettingsPanelScrollReachesAllControls() throws { + app.launchAndOpenSampleBook() + app.waitForReader(timeout: 12) + + app.showReaderChromeIfNeeded() + app.buttons[IDs.readerSettings].tap() + + let settingsScroll = app.scrollViews[IDs.settingsScroll] + XCTAssertTrue(settingsScroll.waitForExistence(timeout: 5), "设置面板未出现") + + XCTAssertTrue(app.buttons[IDs.settingsFontIncrease].waitForExistence(timeout: 3)) + XCTAssertTrue(app.buttons[IDs.settingsBrightness].waitForExistence(timeout: 3)) + + settingsScroll.swipeUp() + XCTAssertTrue(app.segmentedControls[IDs.settingsDisplayType].waitForExistence(timeout: 5)) + + settingsScroll.swipeUp() + XCTAssertTrue(app.buttons[IDs.settingsTheme(5)].waitForExistence(timeout: 5)) + + app.buttons[IDs.settingsDone].tap() + } +} \ No newline at end of file diff --git a/ReadViewDemo/ReadViewDemoUITests/ReaderUITests/HighlightsManagementTests.swift b/ReadViewDemo/ReadViewDemoUITests/ReaderUITests/HighlightsManagementTests.swift new file mode 100644 index 0000000..3480e6e --- /dev/null +++ b/ReadViewDemo/ReadViewDemoUITests/ReaderUITests/HighlightsManagementTests.swift @@ -0,0 +1,142 @@ +import XCTest + +final class HighlightsManagementTests: XCTestCase { + private let app = XCUIApplication() + + override func setUpWithError() throws { + continueAfterFailure = false + } + + func testHighlightsPanelOpens() throws { + app.launchAndOpenSampleBook() + app.waitForReader(timeout: 12) + + let selectionText = app.otherElements[IDs.readerSelectionText].firstMatch + guard selectionText.waitForExistence(timeout: 5) else { + throw XCTSkip("文本选区元素不存在") + } + let start = selectionText.coordinate(withNormalizedOffset: CGVector(dx: 0.3, dy: 0.5)) + let end = selectionText.coordinate(withNormalizedOffset: CGVector(dx: 0.6, dy: 0.5)) + start.press(forDuration: 0.8, thenDragTo: end) + + let highlightMenuItem = app.menuItems["高亮"] + if highlightMenuItem.waitForExistence(timeout: 3) { highlightMenuItem.tap() } + else if app.buttons["高亮"].waitForExistence(timeout: 2) { app.buttons["高亮"].tap() } + + app.waitForReaderState(containing: "highlights=", timeout: 5) + + app.showReaderChromeIfNeeded() + XCTAssertTrue(app.buttons[IDs.readerHighlights].waitForExistence(timeout: 3), "高亮列表按钮不存在") + app.buttons[IDs.readerHighlights].tap() + + XCTAssertTrue(app.tables[IDs.readerHighlightsTable].waitForExistence(timeout: 5), "高亮管理面板未出现") + } + + func testHighlightsPanelShowsCreatedHighlight() throws { + app.launchAndOpenSampleBook() + app.waitForReader(timeout: 12) + + let selectionText = app.otherElements[IDs.readerSelectionText].firstMatch + guard selectionText.waitForExistence(timeout: 5) else { throw XCTSkip("文本选区元素不存在") } + let start = selectionText.coordinate(withNormalizedOffset: CGVector(dx: 0.3, dy: 0.5)) + let end = selectionText.coordinate(withNormalizedOffset: CGVector(dx: 0.6, dy: 0.5)) + start.press(forDuration: 0.8, thenDragTo: end) + + let highlightMenuItem = app.menuItems["高亮"] + if highlightMenuItem.waitForExistence(timeout: 3) { highlightMenuItem.tap() } + + app.showReaderChromeIfNeeded() + app.buttons[IDs.readerHighlights].tap() + + let highlightsTable = app.tables[IDs.readerHighlightsTable] + XCTAssertTrue(highlightsTable.waitForExistence(timeout: 5), "高亮表格未出现") + XCTAssertTrue(waitForCellCount(in: highlightsTable, minimum: 1, timeout: 5), "创建高亮后面板应至少有 1 行数据") + } + + func testHighlightsPanelFilterAll() throws { + app.launchAndOpenSampleBook() + app.waitForReader(timeout: 12) + + let selectionText = app.otherElements[IDs.readerSelectionText].firstMatch + guard selectionText.waitForExistence(timeout: 5) else { throw XCTSkip("文本选区元素不存在") } + let start = selectionText.coordinate(withNormalizedOffset: CGVector(dx: 0.3, dy: 0.5)) + let end = selectionText.coordinate(withNormalizedOffset: CGVector(dx: 0.6, dy: 0.5)) + start.press(forDuration: 0.8, thenDragTo: end) + + let highlightMenuItem = app.menuItems["高亮"] + if highlightMenuItem.waitForExistence(timeout: 3) { highlightMenuItem.tap() } + + app.showReaderChromeIfNeeded() + app.buttons[IDs.readerHighlights].tap() + + let filterControl = app.segmentedControls[IDs.readerHighlightsFilter] + XCTAssertTrue(filterControl.waitForExistence(timeout: 5), "筛选控件未出现") + filterControl.buttons.element(boundBy: 0).tap() + + let highlightsTable = app.tables[IDs.readerHighlightsTable] + XCTAssertTrue(highlightsTable.waitForExistence(timeout: 3)) + XCTAssertTrue(waitForCellCount(in: highlightsTable, minimum: 1, timeout: 5), "筛选全部后应有高亮数据") + } + + func testHighlightsPanelEmptyState() throws { + app.launchAndOpenSampleBook(resetsReaderState: true) + app.waitForReader(timeout: 12) + + app.showReaderChromeIfNeeded() + let highlightsButton = app.buttons[IDs.readerHighlights] + XCTAssertTrue(highlightsButton.waitForExistence(timeout: 3), "高亮列表按钮不存在") + highlightsButton.tap() + + XCTAssertTrue(app.tables[IDs.readerHighlightsTable].waitForExistence(timeout: 5), "高亮管理面板未出现") + XCTAssertTrue(app.staticTexts[IDs.readerHighlightsEmptyLabel].waitForExistence(timeout: 5), "空状态 label 应存在") + XCTAssertTrue(app.staticTexts[IDs.readerHighlightsEmptyLabel].label.contains("暂无"), "空状态应显示暂无标注") + } + + func testDeleteHighlightFromPanel() throws { + app.launchAndOpenSampleBook() + app.waitForReader(timeout: 12) + + let selectionText = app.otherElements[IDs.readerSelectionText].firstMatch + guard selectionText.waitForExistence(timeout: 5) else { throw XCTSkip("文本选区元素不存在") } + let start = selectionText.coordinate(withNormalizedOffset: CGVector(dx: 0.3, dy: 0.5)) + let end = selectionText.coordinate(withNormalizedOffset: CGVector(dx: 0.6, dy: 0.5)) + start.press(forDuration: 0.8, thenDragTo: end) + + let highlightMenuItem = app.menuItems["高亮"] + if highlightMenuItem.waitForExistence(timeout: 3) { highlightMenuItem.tap() } + + app.showReaderChromeIfNeeded() + app.buttons[IDs.readerHighlights].tap() + + let highlightsTable = app.tables[IDs.readerHighlightsTable] + XCTAssertTrue(highlightsTable.waitForExistence(timeout: 5)) + XCTAssertTrue(waitForCellCount(in: highlightsTable, minimum: 1, timeout: 5)) + + highlightsTable.cells.element(boundBy: 0).tap() + + let deleteButton = app.buttons["删除标注"].firstMatch + let deleteHighlightButton = app.buttons["删除高亮"].firstMatch + if deleteButton.waitForExistence(timeout: 3) { + deleteButton.tap() + } else if deleteHighlightButton.waitForExistence(timeout: 1) { + deleteHighlightButton.tap() + } + + XCTAssertTrue(app.staticTexts[IDs.readerHighlightsEmptyLabel].waitForExistence(timeout: 5), "删除后应显示空状态") + } + + private func waitForCellCount( + in table: XCUIElement, + minimum: Int, + timeout: TimeInterval + ) -> Bool { + let deadline = Date().addingTimeInterval(timeout) + while Date() < deadline { + if table.cells.count >= minimum { + return true + } + RunLoop.current.run(until: Date().addingTimeInterval(0.1)) + } + return table.cells.count >= minimum + } +} diff --git a/ReadViewDemo/ReadViewDemoUITests/ReaderUITests/LargeBookOnDemandTests.swift b/ReadViewDemo/ReadViewDemoUITests/ReaderUITests/LargeBookOnDemandTests.swift new file mode 100644 index 0000000..1984c26 --- /dev/null +++ b/ReadViewDemo/ReadViewDemoUITests/ReaderUITests/LargeBookOnDemandTests.swift @@ -0,0 +1,104 @@ +import XCTest + +final class LargeBookOnDemandTests: XCTestCase { + private let app = XCUIApplication() + private let largeBookQuery = "凡人修仙传" + + override func setUpWithError() throws { + continueAfterFailure = false + } + + func testLargeBookInitialOpenIsReadableWhilePaginationIsPartial() throws { + app.launchAndOpenSampleBook(bookTitleQuery: largeBookQuery, resetsReaderState: true) + app.waitForReader(timeout: 20) + + let partialState = app.waitForDemoReaderState(timeout: 15, description: "大书首开进入局部分页") { state in + state.mode == "bookPageMap" && state.pagination == "partial" && (state.page ?? 0) >= 1 + } + + XCTAssertEqual(partialState.mode, "bookPageMap") + XCTAssertEqual(partialState.pagination, "partial") + XCTAssertTrue(app.otherElements[IDs.readerContent].waitForExistence(timeout: 5), "首开局部分页阶段应已可阅读") + } + + func testLargeBookBackgroundPaginationProgresses() throws { + app.launchAndOpenSampleBook(bookTitleQuery: largeBookQuery, resetsReaderState: true) + app.waitForReader(timeout: 20) + + let initialState = app.waitForDemoReaderState(timeout: 15, description: "获取初始分页进度") { state in + state.mode == "bookPageMap" && state.knownChapters != nil && state.buildableChapters != nil + } + let initialKnownChapters = initialState.knownChapters ?? 0 + let initialKnownPages = initialState.knownPages ?? 0 + + let progressedState = app.waitForDemoReaderState(timeout: 45, description: "后台分页推进") { state in + guard state.mode == "bookPageMap" else { return false } + if state.pagination == "full" { + return true + } + return (state.knownChapters ?? 0) > initialKnownChapters || (state.knownPages ?? 0) > initialKnownPages + } + + XCTAssertEqual(progressedState.mode, "bookPageMap") + XCTAssertTrue( + (progressedState.knownChapters ?? 0) >= initialKnownChapters || + (progressedState.knownPages ?? 0) >= initialKnownPages + ) + } + + func testLargeBookSecondOpenRestoresFromCache() throws { + app.launchAndOpenSampleBook(bookTitleQuery: largeBookQuery, resetsReaderState: true) + app.waitForReader(timeout: 20) + + _ = app.waitForDemoReaderState(timeout: 60, description: "首次打开完成全书缓存") { state in + state.mode == "bookPageMap" && state.pagination == "full" && (state.buildableChapters ?? 0) > 0 + } + + app.showReaderChromeIfNeeded() + XCTAssertTrue(app.buttons[IDs.readerBack].waitForExistence(timeout: 5), "返回按钮不存在") + app.buttons[IDs.readerBack].tap() + XCTAssertTrue(app.tables[IDs.demoBooksTable].waitForExistence(timeout: 5), "书架列表未出现") + + app.launchAndOpenSampleBook(bookTitleQuery: largeBookQuery, resetsReaderState: false) + app.waitForReader(timeout: 15) + + let reopenedState = app.waitForDemoReaderState(timeout: 10, description: "二开直接恢复完整缓存") { state in + state.mode == "bookPageMap" && state.pagination == "full" && (state.knownChapters ?? 0) == (state.buildableChapters ?? -1) + } + + XCTAssertEqual(reopenedState.pagination, "full") + XCTAssertEqual(reopenedState.knownChapters, reopenedState.buildableChapters) + } + + func testLargeBookContinuousPagingExtendsKnownPageMap() throws { + app.launchAndOpenSampleBook(bookTitleQuery: largeBookQuery, displayType: "scroll", resetsReaderState: true) + app.waitForReader(timeout: 20) + app.waitForReaderState(containing: "display=horizontalScroll", timeout: 8) + + let initialState = app.waitForDemoReaderState(timeout: 15, description: "初始局部分页") { state in + state.mode == "bookPageMap" && state.knownPages != nil && state.page != nil + } + let initialKnownPages = initialState.knownPages ?? 0 + let initialPage = initialState.page ?? 0 + + let paging = app.collectionViews[IDs.readerPaging] + XCTAssertTrue(paging.waitForExistence(timeout: 5), "分页视图不存在") + + for _ in 0..<4 { + paging.swipeLeft() + } + + let extendedState = app.waitForDemoReaderState(timeout: 20, description: "连续翻页触发扩窗") { state in + guard state.mode == "bookPageMap" else { return false } + let pageAdvanced = (state.page ?? 0) > initialPage + let pageMapExtended = (state.knownPages ?? 0) > initialKnownPages || state.pagination == "full" + return pageAdvanced && pageMapExtended + } + + XCTAssertTrue((extendedState.page ?? 0) > initialPage, "连续翻页后当前页应向后推进") + XCTAssertTrue( + (extendedState.knownPages ?? 0) > initialKnownPages || extendedState.pagination == "full", + "连续翻页后已知页图应扩窗或直接完成全量分页" + ) + } +} diff --git a/ReadViewDemo/ReadViewDemoUITests/ReaderUITests/LocationPersistenceTests.swift b/ReadViewDemo/ReadViewDemoUITests/ReaderUITests/LocationPersistenceTests.swift new file mode 100644 index 0000000..b1c9bb8 --- /dev/null +++ b/ReadViewDemo/ReadViewDemoUITests/ReaderUITests/LocationPersistenceTests.swift @@ -0,0 +1,75 @@ +import XCTest + +final class LocationPersistenceTests: XCTestCase { + private let app = XCUIApplication() + + override func setUpWithError() throws { + continueAfterFailure = false + } + + func testReadingPositionRestoredOnReopen() throws { + app.launchAndOpenSampleBook() + app.waitForReader(timeout: 12) + + _ = app.waitForDemoReaderState(timeout: 5, description: "存在页码") { $0.page != nil } + + let paging = app.collectionViews[IDs.readerPaging] + if paging.waitForExistence(timeout: 5) { + paging.swipeLeft() + } + + let stateAfterSwipe = app.waitForDemoReaderState(timeout: 8, description: "翻页后页码变化") { state in + (state.page ?? 0) > 1 + } + let pageAfterSwipe = stateAfterSwipe.page ?? 0 + + guard pageAfterSwipe > 1 else { + throw XCTSkip("当前翻页模式下未能翻到第 2 页") + } + + let backButton = app.buttons[IDs.readerBack] + if backButton.waitForExistence(timeout: 3) { backButton.tap() } + + app.launchAndOpenSampleBook() + app.waitForReader(timeout: 12) + + let restoredState = app.waitForDemoReaderState(timeout: 8, description: "恢复阅读位置") { state in + state.page != nil + } + let restoredPage = restoredState.page ?? 0 + XCTAssertEqual(restoredPage, pageAfterSwipe, "重新打开后应恢复到上次阅读页码") + } + + func testResetStateLaunchesAtFirstPage() throws { + app.launchAndOpenSampleBook(resetsReaderState: true) + app.waitForReader(timeout: 12) + + let state = app.waitForDemoReaderState(timeout: 5, description: "首开第 1 页") { state in + state.page != nil + } + let page = state.page ?? 0 + XCTAssertEqual(page, 1, "重置状态后应从第 1 页开始") + } + + func testPositionSurvivesFontChange() throws { + app.launchAndOpenSampleBook() + app.waitForReader(timeout: 12) + + let paging = app.collectionViews[IDs.readerPaging] + if paging.waitForExistence(timeout: 5) { paging.swipeLeft() } + app.waitForReaderState(containing: "page=", timeout: 5) + + app.showReaderChromeIfNeeded() + if app.buttons[IDs.readerSettings].waitForExistence(timeout: 3) { app.buttons[IDs.readerSettings].tap() } + if app.buttons[IDs.settingsFontIncrease].waitForExistence(timeout: 5) { app.buttons[IDs.settingsFontIncrease].tap() } + if app.buttons[IDs.settingsDone].waitForExistence(timeout: 3) { app.buttons[IDs.settingsDone].tap() } + + let backButton = app.buttons[IDs.readerBack] + if backButton.waitForExistence(timeout: 3) { backButton.tap() } + + app.launchAndOpenSampleBook() + app.waitForReader(timeout: 12) + + app.waitForReaderState(containing: "reader=opened", timeout: 8) + } +} diff --git a/ReadViewDemo/ReadViewDemoUITests/ReaderUITests/PageNavigationTests.swift b/ReadViewDemo/ReadViewDemoUITests/ReaderUITests/PageNavigationTests.swift index 18e0cf8..e17f0ed 100644 --- a/ReadViewDemo/ReadViewDemoUITests/ReaderUITests/PageNavigationTests.swift +++ b/ReadViewDemo/ReadViewDemoUITests/ReaderUITests/PageNavigationTests.swift @@ -41,15 +41,14 @@ final class PageNavigationTests: XCTestCase { let paging = app.collectionViews[IDs.readerPaging] XCTAssertTrue(paging.waitForExistence(timeout: 5)) - let state = app.staticTexts[IDs.demoReaderState] - let beforeLabel = state.label + let beforeState = app.waitForDemoReaderState(timeout: 5, description: "滑动前页码") { $0.page != nil } + let beforePage = beforeState.page ?? 0 paging.swipeLeft() - let deadline = Date().addingTimeInterval(5) - while Date() < deadline { - if state.exists && state.label != beforeLabel { break } - RunLoop.current.run(until: Date().addingTimeInterval(0.1)) + let afterState = app.waitForDemoReaderState(timeout: 8, description: "水平滑动后页码变化") { state in + guard let page = state.page else { return false } + return page != beforePage } - XCTAssertNotEqual(beforeLabel, state.label, "水平滑动后页码未变化") + XCTAssertNotEqual(beforePage, afterState.page, "水平滑动后页码未变化") } } diff --git a/ReadViewDemo/ReadViewDemoUITests/ReaderUITests/SelectionMenuTests.swift b/ReadViewDemo/ReadViewDemoUITests/ReaderUITests/SelectionMenuTests.swift new file mode 100644 index 0000000..9b64817 --- /dev/null +++ b/ReadViewDemo/ReadViewDemoUITests/ReaderUITests/SelectionMenuTests.swift @@ -0,0 +1,76 @@ +import XCTest + +final class SelectionMenuTests: XCTestCase { + private let app = XCUIApplication() + + override func setUpWithError() throws { + continueAfterFailure = false + } + + func testSelectionMenuShowsCopyOption() throws { + app.launchAndOpenSampleBook() + app.waitForReader(timeout: 12) + + let selectionText = app.otherElements[IDs.readerSelectionText].firstMatch + guard selectionText.waitForExistence(timeout: 5) else { throw XCTSkip("文本选区元素不存在") } + + let start = selectionText.coordinate(withNormalizedOffset: CGVector(dx: 0.3, dy: 0.5)) + let end = selectionText.coordinate(withNormalizedOffset: CGVector(dx: 0.6, dy: 0.5)) + start.press(forDuration: 0.8, thenDragTo: end) + + XCTAssertTrue(app.menuItems["拷贝"].waitForExistence(timeout: 3), "选区菜单应包含拷贝选项") + } + + func testSelectionMenuShowsAnnotateOption() throws { + app.launchAndOpenSampleBook() + app.waitForReader(timeout: 12) + + let selectionText = app.otherElements[IDs.readerSelectionText].firstMatch + guard selectionText.waitForExistence(timeout: 5) else { throw XCTSkip("文本选区元素不存在") } + + let start = selectionText.coordinate(withNormalizedOffset: CGVector(dx: 0.3, dy: 0.5)) + let end = selectionText.coordinate(withNormalizedOffset: CGVector(dx: 0.6, dy: 0.5)) + start.press(forDuration: 0.8, thenDragTo: end) + + XCTAssertTrue(app.menuItems["批注"].waitForExistence(timeout: 3), "选区菜单应包含批注选项") + } + + func testTapBlankAreaClearsSelection() throws { + app.launchAndOpenSampleBook() + app.waitForReader(timeout: 12) + + let selectionText = app.otherElements[IDs.readerSelectionText].firstMatch + guard selectionText.waitForExistence(timeout: 5) else { throw XCTSkip("文本选区元素不存在") } + + let start = selectionText.coordinate(withNormalizedOffset: CGVector(dx: 0.3, dy: 0.5)) + let end = selectionText.coordinate(withNormalizedOffset: CGVector(dx: 0.6, dy: 0.5)) + start.press(forDuration: 0.8, thenDragTo: end) + + XCTAssertTrue(app.menuItems["拷贝"].waitForExistence(timeout: 3), "选区菜单应出现") + + let contentArea = app.otherElements[IDs.readerContentView].firstMatch + if contentArea.waitForExistence(timeout: 3) { + contentArea.coordinate(withNormalizedOffset: CGVector(dx: 0.1, dy: 0.1)).tap() + } + + let menuGone = !app.menuItems["拷贝"].waitForExistence(timeout: 2) + XCTAssertTrue(menuGone, "点击空白区域后选区菜单应消失") + } + + func testCopySelectedText() throws { + app.launchAndOpenSampleBook() + app.waitForReader(timeout: 12) + + let selectionText = app.otherElements[IDs.readerSelectionText].firstMatch + guard selectionText.waitForExistence(timeout: 5) else { throw XCTSkip("文本选区元素不存在") } + + let start = selectionText.coordinate(withNormalizedOffset: CGVector(dx: 0.3, dy: 0.5)) + let end = selectionText.coordinate(withNormalizedOffset: CGVector(dx: 0.6, dy: 0.5)) + start.press(forDuration: 0.8, thenDragTo: end) + + let copyMenuItem = app.menuItems["拷贝"] + if copyMenuItem.waitForExistence(timeout: 3) { copyMenuItem.tap() } + + app.waitForReaderState(containing: "reader=opened", timeout: 5) + } +} \ No newline at end of file diff --git a/ReadViewDemo/ReadViewDemoUITests/ReaderUITests/SettingsEffectTests.swift b/ReadViewDemo/ReadViewDemoUITests/ReaderUITests/SettingsEffectTests.swift new file mode 100644 index 0000000..2322750 --- /dev/null +++ b/ReadViewDemo/ReadViewDemoUITests/ReaderUITests/SettingsEffectTests.swift @@ -0,0 +1,144 @@ +import XCTest + +final class SettingsEffectTests: XCTestCase { + private let app = XCUIApplication() + + override func setUpWithError() throws { + continueAfterFailure = false + } + + func testFontSizeChangeUpdatesContent() throws { + app.launchAndOpenSampleBook() + app.waitForReader(timeout: 12) + app.waitForReaderState(containing: "page=", timeout: 5) + + app.showReaderChromeIfNeeded() + XCTAssertTrue(app.buttons[IDs.readerSettings].waitForExistence(timeout: 3), "设置按钮不存在") + app.buttons[IDs.readerSettings].tap() + + XCTAssertTrue(app.scrollViews[IDs.settingsScroll].waitForExistence(timeout: 5), "设置面板未出现") + XCTAssertTrue(app.buttons[IDs.settingsFontIncrease].waitForExistence(timeout: 3), "字号增大按钮不存在") + app.buttons[IDs.settingsFontIncrease].tap() + + XCTAssertTrue(app.buttons[IDs.settingsDone].waitForExistence(timeout: 3), "完成按钮不存在") + app.buttons[IDs.settingsDone].tap() + + app.waitForReaderState(containing: "reader=opened", timeout: 8) + } + + func testThemeSwitchChangesBackground() throws { + app.launchAndOpenSampleBook() + app.waitForReader(timeout: 12) + + app.showReaderChromeIfNeeded() + app.buttons[IDs.readerSettings].tap() + XCTAssertTrue(app.scrollViews[IDs.settingsScroll].waitForExistence(timeout: 5), "设置面板未出现") + app.scrollViews[IDs.settingsScroll].swipeUp() + + let darkThemeButton = app.buttons[IDs.settingsTheme(5)] + if darkThemeButton.waitForExistence(timeout: 3) { darkThemeButton.tap() } + + app.buttons[IDs.settingsDone].tap() + + let contentView = app.otherElements[IDs.readerContentView] + XCTAssertTrue(contentView.waitForExistence(timeout: 5), "主题切换后内容区应存在") + } + + func testColumnCountChangeUpdatesLayout() throws { + app.launchAndOpenSampleBook() + app.waitForReader(timeout: 12) + + app.showReaderChromeIfNeeded() + app.buttons[IDs.readerSettings].tap() + XCTAssertTrue(app.scrollViews[IDs.settingsScroll].waitForExistence(timeout: 5)) + app.scrollViews[IDs.settingsScroll].swipeUp() + + let columnsControl = app.segmentedControls[IDs.settingsColumns] + if columnsControl.waitForExistence(timeout: 3) && columnsControl.buttons.count > 1 { + columnsControl.buttons.element(boundBy: 1).tap() + } + + app.buttons[IDs.settingsDone].tap() + app.waitForReaderState(containing: "reader=opened", timeout: 8) + } + + func testLineHeightChangeUpdatesContent() throws { + app.launchAndOpenSampleBook() + app.waitForReader(timeout: 12) + + app.showReaderChromeIfNeeded() + app.buttons[IDs.readerSettings].tap() + XCTAssertTrue(app.scrollViews[IDs.settingsScroll].waitForExistence(timeout: 5)) + + let lineHeightControl = app.segmentedControls[IDs.settingsLineHeight] + if lineHeightControl.waitForExistence(timeout: 3) && lineHeightControl.buttons.count > 1 { + lineHeightControl.buttons.element(boundBy: 1).tap() + } + + app.buttons[IDs.settingsDone].tap() + app.waitForReaderState(containing: "reader=opened", timeout: 8) + } + + func testDisplayTypeChangeRePaginates() throws { + app.launchAndOpenSampleBook(displayType: "horizontalscroll") + app.waitForReader(timeout: 12) + app.waitForReaderState(containing: "display=horizontalScroll", timeout: 5) + + app.showReaderChromeIfNeeded() + app.buttons[IDs.readerSettings].tap() + XCTAssertTrue(app.scrollViews[IDs.settingsScroll].waitForExistence(timeout: 5)) + + let displayTypeControl = app.segmentedControls[IDs.settingsDisplayType] + if displayTypeControl.waitForExistence(timeout: 3) && displayTypeControl.buttons.count > 0 { + displayTypeControl.buttons.element(boundBy: 0).tap() + } + + app.buttons[IDs.settingsDone].tap() + app.waitForReaderState(containing: "display=", timeout: 8) + } + + func testDefaultBodyPaginationDoesNotEnableWidowOrphanCompaction() throws { + app.launchAndOpenSampleBook() + app.waitForReader(timeout: 12) + + let state = app.waitForDemoReaderState(timeout: 8, description: "默认正文分页策略") { state in + state.avoidWidows != nil && state.avoidOrphans != nil + } + + XCTAssertEqual(state.avoidWidows, 0, "默认正文分页不应启用 widow compaction,避免页尾明显留白") + XCTAssertEqual(state.avoidOrphans, 0, "默认正文分页不应启用 orphan compaction,避免提前换页") + } + + func testSettingsPersistAfterReopen() throws { + app.launchAndOpenSampleBook() + app.waitForReader(timeout: 12) + + app.showReaderChromeIfNeeded() + app.buttons[IDs.readerSettings].tap() + + XCTAssertTrue(app.buttons[IDs.settingsFontIncrease].waitForExistence(timeout: 5)) + let fontValueLabel = app.staticTexts[IDs.settingsFontValue] + XCTAssertTrue(fontValueLabel.waitForExistence(timeout: 3)) + let originalFontValue = fontValueLabel.label + + app.buttons[IDs.settingsFontIncrease].tap() + let newFontValue = fontValueLabel.label + XCTAssertNotEqual(newFontValue, originalFontValue, "点击增大后字号 label 应变化") + + app.buttons[IDs.settingsDone].tap() + app.buttons[IDs.readerBack].tap() + + app.launchAndOpenSampleBook() + app.waitForReader(timeout: 12) + + app.showReaderChromeIfNeeded() + app.buttons[IDs.readerSettings].tap() + + let persistedFontValue = app.staticTexts[IDs.settingsFontValue] + if persistedFontValue.waitForExistence(timeout: 5) { + XCTAssertEqual(persistedFontValue.label, newFontValue, "重新打开后字号应持久化为之前的值") + } + + app.buttons[IDs.settingsDone].tap() + } +} diff --git a/ReadViewDemo/ReadViewDemoUITests/ReaderUITests/TOCInteractionTests.swift b/ReadViewDemo/ReadViewDemoUITests/ReaderUITests/TOCInteractionTests.swift new file mode 100644 index 0000000..420ce04 --- /dev/null +++ b/ReadViewDemo/ReadViewDemoUITests/ReaderUITests/TOCInteractionTests.swift @@ -0,0 +1,63 @@ +import XCTest + +final class TOCInteractionTests: XCTestCase { + private let app = XCUIApplication() + + override func setUpWithError() throws { + continueAfterFailure = false + } + + func testTOCPanelShowsChapterList() throws { + app.launchAndOpenSampleBook() + app.waitForReader(timeout: 12) + + app.showReaderChromeIfNeeded() + let tocButton = app.buttons[IDs.readerToc] + XCTAssertTrue(tocButton.waitForExistence(timeout: 3), "目录按钮不存在") + tocButton.tap() + + let tocTable = app.tables[IDs.readerTocTable] + XCTAssertTrue(tocTable.waitForExistence(timeout: 5), "目录表格未出现") + XCTAssertTrue(tocTable.cells.count > 0, "目录应至少有 1 个章节") + } + + func testTOCNavigatesToChapter() throws { + app.launchAndOpenSampleBook() + app.waitForReader(timeout: 12) + + let initialState = app.waitForDemoReaderState(timeout: 5, description: "初始页码") { state in + state.page != nil + } + let initialPage = initialState.page ?? 0 + + app.showReaderChromeIfNeeded() + app.buttons[IDs.readerToc].tap() + + let tocTable = app.tables[IDs.readerTocTable] + XCTAssertTrue(tocTable.waitForExistence(timeout: 5), "目录表格未出现") + + if tocTable.cells.count > 1 { + tocTable.cells.element(boundBy: 1).tap() + + let updatedState = app.waitForDemoReaderState(timeout: 8, description: "目录跳转后页码变化") { state in + guard let page = state.page else { return false } + return page != initialPage + } + let updatedPage = updatedState.page ?? 0 + XCTAssertNotEqual(updatedPage, initialPage, "目录跳转后页码应变化") + } else { + tocTable.cells.element(boundBy: 0).tap() + app.waitForReaderState(containing: "reader=opened", timeout: 8) + } + } + + func testTOCEmptyHandling() throws { + app.launchAndOpenSampleBook() + app.waitForReader(timeout: 12) + + app.showReaderChromeIfNeeded() + app.buttons[IDs.readerToc].tap() + + XCTAssertTrue(app.otherElements[IDs.readerTocPanel].waitForExistence(timeout: 5), "目录面板应可打开") + } +} diff --git a/ReadViewDemo/ReadViewDemoUITests/ReaderUITests/ToolbarStateTests.swift b/ReadViewDemo/ReadViewDemoUITests/ReaderUITests/ToolbarStateTests.swift new file mode 100644 index 0000000..127c79e --- /dev/null +++ b/ReadViewDemo/ReadViewDemoUITests/ReaderUITests/ToolbarStateTests.swift @@ -0,0 +1,71 @@ +import XCTest + +final class ToolbarStateTests: XCTestCase { + private let app = XCUIApplication() + + override func setUpWithError() throws { + continueAfterFailure = false + } + + func testTapContentHidesToolbar() throws { + app.launchAndOpenSampleBook() + app.waitForReader(timeout: 12) + + app.showReaderChromeIfNeeded() + + XCTAssertTrue(app.otherElements[IDs.readerTopToolbar].waitForExistence(timeout: 3), "顶部工具栏应可见") + XCTAssertTrue(app.otherElements[IDs.readerBottomToolbar].waitForExistence(timeout: 3), "底部工具栏应可见") + + app.hideReaderChromeIfNeeded() + + let backButton = app.buttons[IDs.readerBack] + let hidden = !backButton.waitForExistence(timeout: 2) + XCTAssertTrue(hidden, "点击内容区后工具栏应隐藏") + } + + func testToolbarShowHideToggle() throws { + app.launchAndOpenSampleBook() + app.waitForReader(timeout: 12) + + app.showReaderChromeIfNeeded() + XCTAssertTrue(app.buttons[IDs.readerBack].waitForExistence(timeout: 3)) + + app.hideReaderChromeIfNeeded() + + app.showReaderChromeIfNeeded() + XCTAssertTrue(app.buttons[IDs.readerBack].waitForExistence(timeout: 3)) + + app.waitForReaderState(containing: "reader=opened", timeout: 5) + } + + func testAddHighlightButtonDisabledWithoutSelection() throws { + app.launchAndOpenSampleBook() + app.waitForReader(timeout: 12) + + app.showReaderChromeIfNeeded() + + let addHighlightButton = app.buttons[IDs.readerAddHighlight] + XCTAssertTrue(addHighlightButton.waitForExistence(timeout: 3), "标注按钮应存在") + + let beforeState = app.waitForDemoReaderState(timeout: 3, description: "初始高亮数") { $0.highlights != nil } + let highlightsBefore = beforeState.highlights ?? 0 + + addHighlightButton.tap() + + let afterState = app.waitForDemoReaderState(timeout: 3, description: "点击后的高亮数") { $0.highlights != nil } + let highlightsAfter = afterState.highlights ?? 0 + XCTAssertEqual(highlightsAfter, highlightsBefore, "无选区时点击标注按钮不应创建标注") + } + + func testPageCurlSwipeNavigation() throws { + app.launchAndOpenSampleBook(displayType: "pagecurl") + app.waitForReader(timeout: 12) + + app.waitForReaderPage(1, timeout: 5) + + let paging = app.collectionViews[IDs.readerPaging] + if paging.waitForExistence(timeout: 5) { paging.swipeLeft() } + + app.waitForReaderState(containing: "reader=opened", timeout: 5) + } +} diff --git a/Sources/RDReaderView/EPUBTextRendering/Pagination/RDEPUBChapterPageCounter.swift b/Sources/RDReaderView/EPUBTextRendering/Pagination/RDEPUBChapterPageCounter.swift index d320d4b..f1bafe3 100644 --- a/Sources/RDReaderView/EPUBTextRendering/Pagination/RDEPUBChapterPageCounter.swift +++ b/Sources/RDReaderView/EPUBTextRendering/Pagination/RDEPUBChapterPageCounter.swift @@ -146,6 +146,10 @@ struct RDEPUBChapterPageCounter { var frames: [RDEPUBTextLayoutFrame] = [] var location = 0 let pageRect = dtLayoutRect + let isDebug = ProcessInfo.processInfo.arguments.contains("--demo-pagination-debug") + if isDebug { + print("[PAGINATION-DEBUG] pageRect=\(pageRect) totalLength=\(attributedString.length)") + } while location < attributedString.length { guard let layoutFrame = layouter.layoutFrame(with: pageRect, range: NSRange(location: location, length: 0)) else { @@ -165,6 +169,16 @@ struct RDEPUBChapterPageCounter { proposed: lineAdjusted, lineRanges: lineRanges ) + + if isDebug { + let pageCount = frames.count + 1 + let previewText = (attributedString.string as NSString).substring(with: NSRange(location: location, length: min(20, attributedString.length - location))) + let avoidRemoved = proposedRange.length - avoidAdjusted.length + let kwNextRemoved = avoidAdjusted.length - lineAdjusted.length + let widowRemoved = lineAdjusted.length - widowOrphanAdjusted.length + let totalRemoved = proposedRange.length - widowOrphanAdjusted.length + print("[PAGINATION-DEBUG] page#\(pageCount) loc=\(location) proposed=\(proposedRange.length) avoid(-\(avoidRemoved)) kwNext(-\(kwNextRemoved)) widowOrphan(-\(widowRemoved)) total(-\(totalRemoved)) lastLine=\"\(previewText)\"") + } let effectiveLineRanges = lineRangesWithinRange(lineRanges, range: widowOrphanAdjusted) let adjusted = pageBreakPolicy.adjustedRange( from: widowOrphanAdjusted, diff --git a/Sources/RDReaderView/EPUBTextRendering/Pagination/RDEPUBCoreTextPageFrameFactory.swift b/Sources/RDReaderView/EPUBTextRendering/Pagination/RDEPUBCoreTextPageFrameFactory.swift index e1cfa3f..d605b0f 100644 --- a/Sources/RDReaderView/EPUBTextRendering/Pagination/RDEPUBCoreTextPageFrameFactory.swift +++ b/Sources/RDReaderView/EPUBTextRendering/Pagination/RDEPUBCoreTextPageFrameFactory.swift @@ -146,6 +146,12 @@ struct RDEPUBCoreTextPageFrameFactory: RDEPUBPageFrameBuilding { let endLocation = lastValidLine.location + lastValidLine.length let adjustedLength = endLocation - proposed.location guard adjustedLength > 0 else { return proposed } + + if ProcessInfo.processInfo.arguments.contains("--demo-pagination-debug") { + let removedText = (attributedString.string as NSString).substring(with: NSRange(location: endLocation, length: min(proposed.length - adjustedLength, 40))) + print("[PAGINATION-DEBUG] avoidPageBreakInside removed \(linesToRemove) lines: \"\(removedText)\"") + } + return NSRange(location: proposed.location, length: adjustedLength) } @@ -180,6 +186,12 @@ struct RDEPUBCoreTextPageFrameFactory: RDEPUBPageFrameBuilding { let endLocation = lastValidLine.location + lastValidLine.length let adjustedLength = endLocation - proposed.location guard adjustedLength > 0 else { return proposed } + + if ProcessInfo.processInfo.arguments.contains("--demo-pagination-debug") { + let removedText = (attributedString.string as NSString).substring(with: NSRange(location: endLocation, length: min(proposed.length - adjustedLength, 40))) + print("[PAGINATION-DEBUG] keepWithNext removed \(linesToRemove) lines: \"\(removedText)\"") + } + return NSRange(location: proposed.location, length: adjustedLength) } @@ -317,6 +329,12 @@ struct RDEPUBCoreTextPageFrameFactory: RDEPUBPageFrameBuilding { let keptLine = lineRanges[lineRanges.count - 2] let adjustedLength = NSMaxRange(keptLine) - proposed.location guard adjustedLength > 0 else { return nil } + + if ProcessInfo.processInfo.arguments.contains("--demo-pagination-debug") { + let removedText = (attributedString.string as NSString).substring(with: NSRange(location: NSMaxRange(keptLine), length: min(proposed.length - adjustedLength, 40))) + print("[PAGINATION-DEBUG] widow control removed 1 line: \"\(removedText)\" paragraphRange=\(NSStringFromRange(paragraphRange))") + } + return NSRange(location: proposed.location, length: adjustedLength) } diff --git a/Sources/RDReaderView/EPUBUI/RDEPUBReaderController+ContentDelegates.swift b/Sources/RDReaderView/EPUBUI/RDEPUBReaderController+ContentDelegates.swift index 2eb1c96..581aa79 100644 --- a/Sources/RDReaderView/EPUBUI/RDEPUBReaderController+ContentDelegates.swift +++ b/Sources/RDReaderView/EPUBUI/RDEPUBReaderController+ContentDelegates.swift @@ -107,6 +107,31 @@ extension RDEPUBReaderController: RDEPUBTextContentViewDelegate { /// 根据 EPUB 位置计算对应的页码 func pageNumber(for location: RDEPUBLocation) -> Int? { + if let publication, + let bookPageMap = readerContext.bookPageMap, + let spineIndex = readerContext.normalizedSpineIndex(for: location), + let entry = bookPageMap.entry(forSpineIndex: spineIndex) { + let normalizedLocation = publication.resourceResolver.normalizedLocation( + location, + relativeToSpineIndex: nil, + bookIdentifier: currentBookIdentifier + ) ?? location + let localPageIndex: Int + if let summary = readerContext.chapterSummary(forSpineIndex: spineIndex) { + let offset = chapterOffset(for: normalizedLocation, fallbackEntry: entry) + localPageIndex = summary.pageRanges.firstIndex { + let range = $0.nsRange + return offset >= range.location && offset <= max(range.location + range.length - 1, range.location) + } ?? fallbackLocalPageIndex(for: normalizedLocation, pageCount: entry.pageCount) + } else { + localPageIndex = fallbackLocalPageIndex(for: normalizedLocation, pageCount: entry.pageCount) + } + return bookPageMap.absolutePageIndex( + spineIndex: spineIndex, + localPageIndex: min(max(localPageIndex, 0), max(entry.pageCount - 1, 0)) + ).map { $0 + 1 } + } + if let textBook, let publication { if let anchor = location.rangeAnchor?.start { if let page = textBook.indexTable.pageNumber(for: anchor, in: textBook) { @@ -135,6 +160,47 @@ extension RDEPUBReaderController: RDEPUBTextContentViewDelegate { /// 根据页码解析对应的文本位置 func resolvedTextLocation(forPageNumber pageNumber: Int) -> RDEPUBLocation? { + if let resolvedPage = resolvedRuntimePage(forPageNumber: pageNumber) { + let chapterLength = max(resolvedPage.chapter.typesetAttributedString.length, 1) + let startOffset = resolvedPage.page.pageStartOffset + let endOffset = max(startOffset, resolvedPage.page.pageEndOffset) + let fragmentID = nearestFragmentID( + beforeOrAt: startOffset, + fragmentOffsets: resolvedPage.chapter.chapterOffsetMap.fragmentOffsets + ) + let location = RDEPUBLocation( + bookIdentifier: currentBookIdentifier, + href: resolvedPage.page.href, + progression: Double(startOffset) / Double(chapterLength), + lastProgression: Double(endOffset) / Double(chapterLength), + fragment: fragmentID, + rangeAnchor: RDEPUBTextRangeAnchor( + start: RDEPUBTextAnchor( + fileIndex: resolvedPage.page.spineIndex, + row: 0, + column: 0, + chapterOffset: startOffset, + fragmentID: fragmentID + ), + end: RDEPUBTextAnchor( + fileIndex: resolvedPage.page.spineIndex, + row: 0, + column: 0, + chapterOffset: endOffset, + fragmentID: fragmentID + ) + ) + ) + if let publication { + return publication.resourceResolver.normalizedLocation( + location, + relativeToSpineIndex: nil, + bookIdentifier: currentBookIdentifier + ) ?? location + } + return location + } + guard let textBook, let publication, let page = textBook.page(at: pageNumber) else { @@ -154,6 +220,17 @@ extension RDEPUBReaderController: RDEPUBTextContentViewDelegate { /// 同步文本阅读状态到阅读会话(页码、位置、spine、章节等) func synchronizeTextReadingState(pageNumber: Int, location: RDEPUBLocation) { + if let resolvedPage = resolvedRuntimePage(forPageNumber: pageNumber) { + readingSession?.updateReadingContext( + pageNumber: pageNumber, + location: location, + spineIndex: resolvedPage.page.spineIndex, + chapterIndex: resolvedPage.chapterIndex, + bookIdentifier: currentBookIdentifier + ) + return + } + guard let textBook, let page = textBook.page(at: pageNumber) else { readingSession?.transition(to: .idle) @@ -184,4 +261,37 @@ extension RDEPUBReaderController: RDEPUBTextContentViewDelegate { } return (pages, chapters) } + + private func resolvedRuntimePage(forPageNumber pageNumber: Int) -> RDEPUBResolvedPage? { + runtime.pageResolver.resolvePage(absolutePageIndex: pageNumber - 1) + } + + func chapterOffset(for location: RDEPUBLocation, fallbackEntry: RDEPUBBookPageMapEntry) -> Int { + if let anchor = location.rangeAnchor?.start { + return anchor.chapterOffset + } + if let fragment = location.fragment, + let offset = fallbackEntry.fragmentOffsets[fragment] { + return offset + } + return 0 + } + + func fallbackLocalPageIndex(for location: RDEPUBLocation, pageCount: Int) -> Int { + guard pageCount > 1 else { return 0 } + return min( + pageCount - 1, + max(0, Int(round(location.navigationProgression * Double(pageCount - 1)))) + ) + } + + private func nearestFragmentID(beforeOrAt offset: Int, fragmentOffsets: [String: Int]) -> String? { + var bestID: String? + var bestOffset = Int.min + for (fragmentID, fragmentOffset) in fragmentOffsets where fragmentOffset <= offset && fragmentOffset > bestOffset { + bestOffset = fragmentOffset + bestID = fragmentID + } + return bestID + } } diff --git a/Sources/RDReaderView/EPUBUI/RDEPUBReaderController+DataSource.swift b/Sources/RDReaderView/EPUBUI/RDEPUBReaderController+DataSource.swift index ea173ef..ba191e9 100644 --- a/Sources/RDReaderView/EPUBUI/RDEPUBReaderController+DataSource.swift +++ b/Sources/RDReaderView/EPUBUI/RDEPUBReaderController+DataSource.swift @@ -15,11 +15,28 @@ import UIKit extension RDEPUBReaderController: RDReaderDataSource, RDReaderDelegate { /// 返回阅读器总页数 public func pageCountOfReaderView(readerView: RDReaderView) -> Int { - textBook?.pages.count ?? activePages.count + readerContext.bookPageMap?.totalPages ?? textBook?.pages.count ?? activePages.count } /// 为指定页码创建或复用内容视图(优先文本渲染,回退 Web 渲染) public func pageContentView(readerView: RDReaderView, pageNum: Int, containerView: UIView?) -> UIView { + if readerContext.bookPageMap != nil { + _ = runtime.prepareOnDemandChapter(forAbsolutePageNumber: pageNum + 1) + if let resolvedPage = runtime.pageResolver.resolvePage(absolutePageIndex: pageNum) { + let contentView = (containerView as? RDEPUBTextContentView) ?? RDEPUBTextContentView() + contentView.delegate = self + contentView.configure( + page: resolvedPage.page, + pageNumber: pageNum + 1, + totalPages: pageCountOfReaderView(readerView: readerView), + configuration: configuration, + highlights: textHighlights(for: resolvedPage.page), + searchState: searchState + ) + return contentView + } + } + if let textBook, let page = textBook.page(at: pageNum + 1) { let contentView = (containerView as? RDEPUBTextContentView) ?? RDEPUBTextContentView() contentView.delegate = self @@ -53,7 +70,7 @@ extension RDEPUBReaderController: RDReaderDataSource, RDReaderDelegate { /// 返回页面内容视图的重用标识符(区分文本与 Web 渲染) public func pageIdentifier(readerView: RDReaderView, pageNum: Int) -> String? { - textBook == nil + (textBook == nil && readerContext.bookPageMap == nil) ? NSStringFromClass(RDEPUBWebContentView.self) : NSStringFromClass(RDEPUBTextContentView.self) } @@ -94,13 +111,17 @@ extension RDEPUBReaderController: RDReaderDataSource, RDReaderDelegate { readerContext.markUserNavigationActivity() updateCurrentSelection(nil) reconcileTextPaginationSizeIfNeeded(for: pageNum) + if readerContext.bookPageMap != nil { + _ = runtime.prepareOnDemandChapter(forAbsolutePageNumber: pageNum + 1) + runtime.extendPartialBookPageMapIfNeeded(currentPageNumber: pageNum + 1) + } let totalPages = pageCountOfReaderView(readerView: readerView) if totalPages > 0, pageNum == totalPages - 1 { delegate?.epubReaderDidReachEnd(self) } - if textBook != nil, + if (textBook != nil || readerContext.bookPageMap != nil), let location = resolvedTextLocation(forPageNumber: pageNum + 1) { persist(location: location) synchronizeTextReadingState(pageNumber: pageNum + 1, location: location) @@ -123,7 +144,7 @@ extension RDEPUBReaderController: RDReaderDataSource, RDReaderDelegate { /// 检测页面尺寸变化并触发重新分页(避免布局错乱) private func reconcileTextPaginationSizeIfNeeded(for pageNum: Int) { - guard textBook != nil, + guard textBook != nil || readerContext.bookPageMap != nil, !isRepaginating, !isReconcilingTextPaginationSize, pageNum >= 0, @@ -147,7 +168,7 @@ extension RDEPUBReaderController: RDReaderDataSource, RDReaderDelegate { DispatchQueue.main.async { [weak self] in guard let self else { return } self.isReconcilingTextPaginationSize = false - guard self.textBook != nil, !self.isRepaginating else { return } + guard (self.textBook != nil || self.readerContext.bookPageMap != nil), !self.isRepaginating else { return } self.repaginatePreservingCurrentLocation() } } diff --git a/Sources/RDReaderView/EPUBUI/RDEPUBReaderController+PublicAPI.swift b/Sources/RDReaderView/EPUBUI/RDEPUBReaderController+PublicAPI.swift index c4225af..1c33a8e 100644 --- a/Sources/RDReaderView/EPUBUI/RDEPUBReaderController+PublicAPI.swift +++ b/Sources/RDReaderView/EPUBUI/RDEPUBReaderController+PublicAPI.swift @@ -50,8 +50,18 @@ extension RDEPUBReaderController { /// 获取当前页的原生文本语义摘要,用于调试和可访问性 /// - Returns: 包含页码、断行原因、块类型等信息的摘要字符串,无内容时返回 nil public func nativeTextSemanticSummary() -> String? { - guard let textBook, - let page = textBook.page(at: max(readerView.currentPage + 1, 1)) ?? textBook.pages.first else { + let resolvedPage: RDEPUBTextPage? + if let textBook { + resolvedPage = textBook.page(at: max(readerView.currentPage + 1, 1)) ?? textBook.pages.first + } else if readerContext.bookPageMap != nil { + let absolutePageIndex = max(readerView.currentPage, 0) + _ = runtime.prepareOnDemandChapter(forAbsolutePageNumber: absolutePageIndex + 1) + resolvedPage = runtime.pageResolver.resolvePage(absolutePageIndex: absolutePageIndex)?.page + } else { + resolvedPage = nil + } + + guard let page = resolvedPage else { return nil } @@ -242,4 +252,3 @@ extension RDEPUBReaderController { runtime.clearSearch() } } - diff --git a/Sources/RDReaderView/EPUBUI/RDEPUBReaderController+RuntimeBridge.swift b/Sources/RDReaderView/EPUBUI/RDEPUBReaderController+RuntimeBridge.swift index 73f66e6..3ab5087 100644 --- a/Sources/RDReaderView/EPUBUI/RDEPUBReaderController+RuntimeBridge.swift +++ b/Sources/RDReaderView/EPUBUI/RDEPUBReaderController+RuntimeBridge.swift @@ -233,6 +233,7 @@ extension RDEPUBReaderController { hideLoading() readerContext.clearActiveSnapshot() textBook = nil + readerContext.bookPageMap = nil readerView.reloadData() errorLabel.text = error.localizedDescription errorLabel.isHidden = false diff --git a/Sources/RDReaderView/EPUBUI/RDEPUBReaderController+TableOfContents.swift b/Sources/RDReaderView/EPUBUI/RDEPUBReaderController+TableOfContents.swift index f01c06c..cdb8ff4 100644 --- a/Sources/RDReaderView/EPUBUI/RDEPUBReaderController+TableOfContents.swift +++ b/Sources/RDReaderView/EPUBUI/RDEPUBReaderController+TableOfContents.swift @@ -13,18 +13,12 @@ import Foundation extension RDEPUBReaderController { /// 解析当前阅读位置对应的目录项,按页码或 href 匹配 func resolvedCurrentTableOfContentsItem() -> RDEPUBReaderTableOfContentsItem? { - let items = flattenedTableOfContents + let items = flattenedTableOfContentsItems( + from: publication?.tableOfContents ?? [], + includePageNumbers: false + ) guard !items.isEmpty else { return nil } - let currentPageNumber = max(readerView.currentPage + 1, 1) - let pageAnchoredMatch = items.last { item in - guard let pageNumber = item.pageNumber else { return false } - return pageNumber <= currentPageNumber - } - if let pageAnchoredMatch { - return pageAnchoredMatch - } - guard let publication, let currentLocation = currentVisibleLocation(), let normalizedCurrentHref = publication.resourceResolver.normalizedHref(currentLocation.href) else { @@ -60,28 +54,12 @@ extension RDEPUBReaderController { /// 将嵌套目录树递归扁平化为线性列表,并计算每项目标页码 func flattenedTableOfContentsItems( from items: [EPUBTableOfContentsItem], - depth: Int = 0 + depth: Int = 0, + includePageNumbers: Bool = true ) -> [RDEPUBReaderTableOfContentsItem] { items.flatMap { item in let location = RDEPUBLocation(bookIdentifier: currentBookIdentifier, href: item.href, progression: 0) - let pageNumber: Int? - if let textBook, let publication, - let chapterData = textBook.chapterData(for: location, resolver: publication.resourceResolver, bookIdentifier: currentBookIdentifier) { - let normalizedLocation = publication.resourceResolver.normalizedLocation( - location, - bookIdentifier: currentBookIdentifier - ) ?? location - pageNumber = chapterData.pageNumber(for: normalizedLocation) - ?? textBook.pageNumber( - for: location, - resolver: publication.resourceResolver, - bookIdentifier: currentBookIdentifier - ) - } else if let readingSession { - pageNumber = readingSession.pageIndex(for: location, bookIdentifier: currentBookIdentifier).map { $0 + 1 } - } else { - pageNumber = nil - } + let pageNumber = includePageNumbers ? resolvedTableOfContentsPageNumber(for: location) : nil let current = RDEPUBReaderTableOfContentsItem( title: item.title, @@ -89,8 +67,53 @@ extension RDEPUBReaderController { depth: depth, pageNumber: pageNumber ) - return [current] + flattenedTableOfContentsItems(from: item.children, depth: depth + 1) + return [current] + flattenedTableOfContentsItems( + from: item.children, + depth: depth + 1, + includePageNumbers: includePageNumbers + ) } } -} + private func resolvedTableOfContentsPageNumber(for location: RDEPUBLocation) -> Int? { + if let publication, + let bookPageMap = readerContext.bookPageMap, + let spineIndex = readerContext.normalizedSpineIndex(for: location), + let entry = bookPageMap.entry(forSpineIndex: spineIndex) { + let normalizedLocation = publication.resourceResolver.normalizedLocation( + location, + bookIdentifier: currentBookIdentifier + ) ?? location + let localPageIndex: Int + if let summary = readerContext.chapterSummary(forSpineIndex: spineIndex) { + let offset = chapterOffset(for: normalizedLocation, fallbackEntry: entry) + localPageIndex = summary.pageRanges.firstIndex { + let range = $0.nsRange + return offset >= range.location && offset <= max(range.location + range.length - 1, range.location) + } ?? fallbackLocalPageIndex(for: normalizedLocation, pageCount: entry.pageCount) + } else { + localPageIndex = fallbackLocalPageIndex(for: normalizedLocation, pageCount: entry.pageCount) + } + return bookPageMap.absolutePageIndex( + spineIndex: spineIndex, + localPageIndex: min(max(localPageIndex, 0), max(entry.pageCount - 1, 0)) + ).map { $0 + 1 } + } + + if let textBook, let publication, + let chapterData = textBook.chapterData(for: location, resolver: publication.resourceResolver, bookIdentifier: currentBookIdentifier) { + let normalizedLocation = publication.resourceResolver.normalizedLocation( + location, + bookIdentifier: currentBookIdentifier + ) ?? location + return chapterData.pageNumber(for: normalizedLocation) + ?? textBook.pageNumber( + for: location, + resolver: publication.resourceResolver, + bookIdentifier: currentBookIdentifier + ) + } + + return readingSession?.pageIndex(for: location, bookIdentifier: currentBookIdentifier).map { $0 + 1 } + } +} diff --git a/Sources/RDReaderView/EPUBUI/RDURLReaderController.swift b/Sources/RDReaderView/EPUBUI/RDURLReaderController.swift index 19a4e82..3c016bf 100644 --- a/Sources/RDReaderView/EPUBUI/RDURLReaderController.swift +++ b/Sources/RDReaderView/EPUBUI/RDURLReaderController.swift @@ -51,6 +51,8 @@ public final class RDURLReaderController: UIViewController { private var isRetryingPendingDemoPage = false private let maxPendingDemoPageAttempts = 24 private let pendingDemoPageRetryDelay: TimeInterval = 0.25 + private var demoStateTimer: Timer? + private var lastEmittedDemoState = "" /// 初始化方法 /// - Parameters: @@ -81,7 +83,17 @@ public final class RDURLReaderController: UIViewController { public override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - emitDemoState() + startDemoStateTimerIfNeeded() + refreshDemoState() + } + + public override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + stopDemoStateTimer() + } + + deinit { + stopDemoStateTimer() } /// 切换阅读器的翻页模式(Demo 用) @@ -155,8 +167,8 @@ public final class RDURLReaderController: UIViewController { edgeInsets: epubConfiguration.reflowableContentInsets, numberOfColumns: 1, columnGap: 20, - avoidOrphans: true, - avoidWidows: true, + avoidOrphans: false, + avoidWidows: false, avoidPageBreakInsideEnabled: true, hyphenation: true, imageMaxHeightRatio: 0.85 @@ -205,15 +217,17 @@ public final class RDURLReaderController: UIViewController { guard let readerController else { return false } guard pageNumber > 0 else { return false } - let totalPages = readerController.textBook?.pages.count ?? readerController.activePages.count + let knownPages = readerController.readerContext.bookPageMap?.totalPages + ?? readerController.textBook?.pages.count + ?? readerController.activePages.count let numPages = readerController.readerView.numberOfPages() - print("[ReadViewDemo] performDemoPageNavigation(\(pageNumber)): textBook.pages=\(totalPages), readerView.numberOfPages=\(numPages), currentPage=\(readerController.readerView.currentPage)") - guard pageNumber <= totalPages else { return false } - - readerController.readerView.transitionToPage(pageNum: pageNumber - 1, animated: animated) - print("[ReadViewDemo] performDemoPageNavigation(\(pageNumber)): after transition, currentPage=\(readerController.readerView.currentPage)") - emitDemoState(prefix: "page=\(pageNumber)") - return true + print("[ReadViewDemo] performDemoPageNavigation(\(pageNumber)): knownPages=\(knownPages), readerView.numberOfPages=\(numPages), currentPage=\(readerController.readerView.currentPage)") + let moved = readerController.go(toPageNumber: pageNumber, animated: animated) + print("[ReadViewDemo] performDemoPageNavigation(\(pageNumber)): moved=\(moved), after currentPage=\(readerController.readerView.currentPage)") + if moved { + emitDemoState(prefix: "page=\(pageNumber)") + } + return moved } private func queuePendingDemoPageNavigation(_ pageNumber: Int, animated: Bool) { @@ -288,19 +302,87 @@ public final class RDURLReaderController: UIViewController { ]) } + private func startDemoStateTimerIfNeeded() { + guard demoStateTimer == nil else { return } + demoStateTimer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { [weak self] _ in + self?.refreshDemoState(logIfChanged: false) + } + if let demoStateTimer { + RunLoop.main.add(demoStateTimer, forMode: .common) + } + } + + private func stopDemoStateTimer() { + demoStateTimer?.invalidate() + demoStateTimer = nil + } + private func emitDemoState(prefix: String? = nil) { + refreshDemoState(logPrefix: prefix, logIfChanged: true) + } + + private func refreshDemoState(logPrefix: String? = nil, logIfChanged: Bool = true) { let page = readerController?.currentPageNumber.map(String.init) ?? "nil" let display = readerController?.configuration.displayType.demoArgumentValue ?? epubConfiguration.displayType.demoArgumentValue let toolbar = readerController?.readerView.isShowToolView == true ? "visible" : "hidden" let highlights = readerController?.highlights.count ?? 0 let selection = readerController?.currentSelection == nil ? 0 : 1 - let state = "reader=opened page=\(page) display=\(display) toolbar=\(toolbar) highlights=\(highlights) selection=\(selection)" + let location = readerController?.currentLocation + let href = encodedDemoLocationHref(location?.href) + let progression = location.map { String(format: "%.4f", $0.navigationProgression) } ?? "nil" + let mapSnapshot = demoPaginationSnapshot() + let layoutConfig = readerController?.readerContext.currentTextLayoutConfig(pageSize: currentTextPageSize()) + let state = [ + "reader=opened", + "page=\(page)", + "display=\(display)", + "toolbar=\(toolbar)", + "highlights=\(highlights)", + "selection=\(selection)", + "href=\(href)", + "progression=\(progression)", + "mode=\(mapSnapshot.mode)", + "pagination=\(mapSnapshot.phase)", + "knownPages=\(mapSnapshot.knownPages)", + "knownChapters=\(mapSnapshot.knownChapters)", + "buildableChapters=\(mapSnapshot.buildableChapters)", + "avoidWidows=\(layoutConfig?.avoidWidows == true ? 1 : 0)", + "avoidOrphans=\(layoutConfig?.avoidOrphans == true ? 1 : 0)" + ].joined(separator: " ") demoStateLabel.text = state - if let prefix { - logDemoState(prefix: prefix) - } else { + if let logPrefix { + logDemoState(prefix: logPrefix) + } else if logIfChanged, state != lastEmittedDemoState { print("[ReadViewDemo] automation \(state)") } + lastEmittedDemoState = state + } + + private func encodedDemoLocationHref(_ href: String?) -> String { + guard let href, !href.isEmpty else { return "nil" } + return href.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? href.replacingOccurrences(of: " ", with: "%20") + } + + private func demoPaginationSnapshot() -> (mode: String, phase: String, knownPages: Int, knownChapters: Int, buildableChapters: Int) { + guard let readerController else { + return ("unavailable", "none", 0, 0, 0) + } + + if let bookPageMap = readerController.readerContext.bookPageMap { + let buildableChapters = readerController.publication?.spine.filter { + $0.linear && ($0.mediaType.contains("html") || $0.mediaType.contains("xhtml")) + }.count ?? 0 + let phase = buildableChapters > 0 && bookPageMap.totalChapters >= buildableChapters ? "full" : "partial" + return ("bookPageMap", phase, bookPageMap.totalPages, bookPageMap.totalChapters, buildableChapters) + } + + if let textBook = readerController.textBook { + return ("textBook", "full", textBook.pages.count, textBook.chapters.count, textBook.chapters.count) + } + + let snapshotChapters = readerController.activeChapters.count + let snapshotPages = readerController.activePages.count + return ("snapshot", snapshotPages > 0 ? "full" : "none", snapshotPages, snapshotChapters, snapshotChapters) } /// 计算文本分页的页面尺寸。 diff --git a/Sources/RDReaderView/EPUBUI/ReaderController/ChapterRuntime/RDEPUBBackgroundTrace.swift b/Sources/RDReaderView/EPUBUI/ReaderController/ChapterRuntime/RDEPUBBackgroundTrace.swift new file mode 100644 index 0000000..97c8f87 --- /dev/null +++ b/Sources/RDReaderView/EPUBUI/ReaderController/ChapterRuntime/RDEPUBBackgroundTrace.swift @@ -0,0 +1,39 @@ +import Foundation + +enum RDEPUBBackgroundTrace { + static func log(_ scope: String, _ message: String) { + let threadRole = Thread.isMainThread ? "main" : "bg" + let threadName = resolvedThreadName() + let queueLabel = resolvedQueueLabel() + print("[EPUB][\(scope)][\(threadRole)][queue=\(queueLabel)][thread=\(threadName)] \(message)") + } + + static func measure(_ scope: String, _ message: String, work: () throws -> T) rethrows -> T { + let startedAt = CFAbsoluteTimeGetCurrent() + log(scope, "START \(message)") + do { + let result = try work() + let elapsedMs = Int((CFAbsoluteTimeGetCurrent() - startedAt) * 1000) + log(scope, "END \(message) elapsedMs=\(elapsedMs)") + return result + } catch { + let elapsedMs = Int((CFAbsoluteTimeGetCurrent() - startedAt) * 1000) + log(scope, "FAIL \(message) elapsedMs=\(elapsedMs) error=\(error)") + throw error + } + } + + private static func resolvedThreadName() -> String { + if let name = Thread.current.name, !name.isEmpty { + return name + } + if Thread.isMainThread { + return "main" + } + return String(describing: Unmanaged.passUnretained(Thread.current).toOpaque()) + } + + private static func resolvedQueueLabel() -> String { + String(validatingUTF8: __dispatch_queue_get_label(nil)) ?? "unknown" + } +} diff --git a/Sources/RDReaderView/EPUBUI/ReaderController/ChapterRuntime/RDEPUBBookPageMap.swift b/Sources/RDReaderView/EPUBUI/ReaderController/ChapterRuntime/RDEPUBBookPageMap.swift new file mode 100644 index 0000000..f6b1b44 --- /dev/null +++ b/Sources/RDReaderView/EPUBUI/ReaderController/ChapterRuntime/RDEPUBBookPageMap.swift @@ -0,0 +1,126 @@ +import Foundation + +/// BookPageMap 中的单个条目,记录一个章节的轻量元数据。 +/// 不持有 NSAttributedString,每条约 100 字节。 +struct RDEPUBBookPageMapEntry { + let spineIndex: Int + let href: String + let title: String + /// 该章节的页数 + let pageCount: Int + /// 该章节在全书中的绝对起始页码(从 0 开始) + let absolutePageStart: Int + /// fragment ID → 字符偏移量映射 + let fragmentOffsets: [String: Int] +} + +/// 全书轻量页码映射:仅存储每章的页数和起始位置, +/// 内存成本约 100 字节/章,1000 章 ≈ 100KB。 +/// +/// 提供 spineIndex ↔ 绝对页码的双向查询, +/// 用于进度条、目录跳转、位置恢复等不依赖内容的场景。 +struct RDEPUBBookPageMap { + let entries: [RDEPUBBookPageMapEntry] + /// 按 spineIndex 索引的查找表 + private let indexBySpine: [Int: Int] // spineIndex -> entries 数组下标 + /// 全书总页数 + let totalPages: Int + + init(entries: [RDEPUBBookPageMapEntry]) { + self.entries = entries + var mapping: [Int: Int] = [:] + for (i, entry) in entries.enumerated() { + mapping[entry.spineIndex] = i + } + self.indexBySpine = mapping + self.totalPages = entries.last.map { $0.absolutePageStart + $0.pageCount } ?? 0 + } + + static let empty = RDEPUBBookPageMap(entries: []) + + // MARK: - 查询 + + /// spineIndex + 本地页码 → 全书绝对页码 + func absolutePageIndex(spineIndex: Int, localPageIndex: Int) -> Int? { + guard let idx = indexBySpine[spineIndex] else { return nil } + let entry = entries[idx] + guard localPageIndex >= 0, localPageIndex < entry.pageCount else { return nil } + return entry.absolutePageStart + localPageIndex + } + + /// 全书绝对页码 → spineIndex + func spineIndex(forAbsolutePage absolutePage: Int) -> Int? { + guard absolutePage >= 0, absolutePage < totalPages else { return nil } + // 二分查找:entries 按 absolutePageStart 有序 + var lo = 0, hi = entries.count + while lo < hi { + let mid = lo + (hi - lo) / 2 + if entries[mid].absolutePageStart <= absolutePage { + lo = mid + 1 + } else { + hi = mid + } + } + guard lo > 0 else { return nil } + return entries[lo - 1].spineIndex + } + + /// 全书绝对页码 → 本地页码(章节内偏移) + func localPageIndex(forAbsolutePage absolutePage: Int) -> Int? { + guard let si = spineIndex(forAbsolutePage: absolutePage), + let idx = indexBySpine[si] else { return nil } + let entry = entries[idx] + let local = absolutePage - entry.absolutePageStart + guard local >= 0, local < entry.pageCount else { return nil } + return local + } + + /// 获取指定 spineIndex 的条目 + func entry(forSpineIndex spineIndex: Int) -> RDEPUBBookPageMapEntry? { + guard let idx = indexBySpine[spineIndex] else { return nil } + return entries[idx] + } + + /// 获取指定 spineIndex 在 entries 中的章节序号。 + func chapterIndex(forSpineIndex spineIndex: Int) -> Int? { + indexBySpine[spineIndex] + } + + /// 获取指定 spineIndex 的页数 + func pageCount(forSpineIndex spineIndex: Int) -> Int? { + entry(forSpineIndex: spineIndex)?.pageCount + } + + /// 全书总章节数 + var totalChapters: Int { entries.count } + + // MARK: - 构建 + + /// Builder:从各章的 pageCount 逐步构建 BookPageMap + struct Builder { + private var items: [(spineIndex: Int, href: String, title: String, pageCount: Int, fragmentOffsets: [String: Int])] = [] + + mutating func add(spineIndex: Int, href: String, title: String, pageCount: Int, fragmentOffsets: [String: Int]) { + items.append((spineIndex, href, title, pageCount, fragmentOffsets)) + } + + func build() -> RDEPUBBookPageMap { + // 按 spineIndex 排序 + let sorted = items.sorted { $0.spineIndex < $1.spineIndex } + var entries: [RDEPUBBookPageMapEntry] = [] + var absolutePageStart = 0 + for item in sorted { + entries.append(RDEPUBBookPageMapEntry( + spineIndex: item.spineIndex, + href: item.href, + title: item.title, + pageCount: item.pageCount, + absolutePageStart: absolutePageStart, + fragmentOffsets: item.fragmentOffsets + )) + absolutePageStart += item.pageCount + } + return RDEPUBBookPageMap(entries: entries) + } + } +} diff --git a/Sources/RDReaderView/EPUBUI/ReaderController/ChapterRuntime/RDEPUBChapterCacheKey.swift b/Sources/RDReaderView/EPUBUI/ReaderController/ChapterRuntime/RDEPUBChapterCacheKey.swift new file mode 100644 index 0000000..487a1fe --- /dev/null +++ b/Sources/RDReaderView/EPUBUI/ReaderController/ChapterRuntime/RDEPUBChapterCacheKey.swift @@ -0,0 +1,8 @@ +import Foundation + +struct RDEPUBChapterCacheKey: Hashable { + let bookID: String + let spineIndex: Int + let renderSignature: String + let chapterContentHash: String +} \ No newline at end of file diff --git a/Sources/RDReaderView/EPUBUI/ReaderController/ChapterRuntime/RDEPUBChapterDataCache.swift b/Sources/RDReaderView/EPUBUI/ReaderController/ChapterRuntime/RDEPUBChapterDataCache.swift new file mode 100644 index 0000000..043e2c0 --- /dev/null +++ b/Sources/RDReaderView/EPUBUI/ReaderController/ChapterRuntime/RDEPUBChapterDataCache.swift @@ -0,0 +1,37 @@ +import Foundation + +final class RDEPUBChapterDataCache { + private var storage: [Int: RDEPUBRuntimeChapter] = [:] + private let lock = NSLock() + + subscript(_ spineIndex: Int) -> RDEPUBRuntimeChapter? { + get { + lock.lock() + defer { lock.unlock() } + return storage[spineIndex] + } + set { + lock.lock() + defer { lock.unlock() } + storage[spineIndex] = newValue + } + } + + var storedSpineIndices: [Int] { + lock.lock() + defer { lock.unlock() } + return Array(storage.keys) + } + + func remove(spineIndex: Int) { + lock.lock() + defer { lock.unlock() } + storage.removeValue(forKey: spineIndex) + } + + func removeAll() { + lock.lock() + defer { lock.unlock() } + storage.removeAll() + } +} \ No newline at end of file diff --git a/Sources/RDReaderView/EPUBUI/ReaderController/ChapterRuntime/RDEPUBChapterLoader.swift b/Sources/RDReaderView/EPUBUI/ReaderController/ChapterRuntime/RDEPUBChapterLoader.swift new file mode 100644 index 0000000..e10bf76 --- /dev/null +++ b/Sources/RDReaderView/EPUBUI/ReaderController/ChapterRuntime/RDEPUBChapterLoader.swift @@ -0,0 +1,534 @@ +import Foundation + +final class RDEPUBChapterLoader { + private unowned let context: RDEPUBReaderContext + private var summaryDiskCache: RDEPUBChapterSummaryDiskCache? + + init(context: RDEPUBReaderContext) { + self.context = context + } + + func setSummaryDiskCache(_ cache: RDEPUBChapterSummaryDiskCache) { + summaryDiskCache = cache + } + + // MARK: - 请求优先级 + + enum LoadPriority { + case navigation // 前台导航:用户主动跳章,完成后检查导航目标队列 + case prefetch // 后台预取:±1 相邻章,完成后仅回填缓存 + 刷新快照 + } + + // MARK: - 主入口:加载单个章节 + + /// 在 chapterLoadQueue 上构建单章,完成后回调到主线程 + func loadChapter( + spineIndex: Int, + store: RDEPUBChapterRuntimeStore, + priority: LoadPriority = .navigation, + completion: @escaping (Result) -> Void + ) { + // 1. 查内存缓存(统一回主线程,保证 completion 线程语义一致) + if let cached = store.chapterData(for: spineIndex) { + RDEPUBBackgroundTrace.log("ChapterLoader", "cache hit spine=\(spineIndex) priority=\(priority)") + DispatchQueue.main.async { + completion(.success(cached)) + } + return + } + + // 2-3. 构建缓存键 + 查内存级 pageCountCache 统一移到串行队列执行, + // 避免 contentHashForSpineIndex 的 SHA256 + 磁盘 I/O 阻塞主线程 + store.markBuilding(true) + store.chapterLoadQueue.async { + RDEPUBBackgroundTrace.log("ChapterLoader", "queue start spine=\(spineIndex) priority=\(priority)") + let cacheKey = self.makeCacheKey(spineIndex: spineIndex) + + // 仅当内存级 pageCountCache 未命中时才查磁盘摘要 + let precomputedPageRanges = store.pageCount(for: cacheKey)?.pageRanges + let diskSummary: RDEPUBChapterSummary? + if precomputedPageRanges == nil { + diskSummary = self.summaryDiskCache?.read(for: cacheKey) + if diskSummary != nil { + RDEPUBBackgroundTrace.log("ChapterLoader", "磁盘摘要缓存命中 spine=\(spineIndex)") + } else { + RDEPUBBackgroundTrace.log("ChapterLoader", "缓存未命中 spine=\(spineIndex)") + } + } else { + diskSummary = nil + RDEPUBBackgroundTrace.log("ChapterLoader", "页数缓存命中 spine=\(spineIndex)") + } + let diskPageRanges = diskSummary?.pageRanges.map { $0.nsRange } + let availablePageRanges = precomputedPageRanges ?? diskPageRanges + + do { + let chapter = try RDEPUBBackgroundTrace.measure( + "ChapterLoader", + "buildChapter spine=\(spineIndex) priority=\(priority) cachedRanges=\(availablePageRanges?.count ?? 0)" + ) { + try self.buildChapter( + spineIndex: spineIndex, + availablePageRanges: availablePageRanges, + diskSummary: diskSummary + ) + } + RDEPUBBackgroundTrace.log("ChapterLoader", "buildChapter OK: spine=\(spineIndex) pages=\(chapter.pages.count)") + + // 5. 回填缓存 + store.insertChapter(chapter) + let pc = RDEPUBRuntimePageCount( + cacheKey: cacheKey, + spineIndex: spineIndex, + pageRanges: chapter.pageRanges, + pageCount: chapter.pages.count, + renderSignature: cacheKey.renderSignature + ) + store.insertPageCount(pc, for: cacheKey) + + // 6. 按优先级处理完成逻辑 + switch priority { + case .navigation: + // 前台导航:检查是否有更新的导航目标(§14.4 取消语义) + let nextTarget = store.consumeNavigationTarget() + if let target = nextTarget, target != spineIndex { + // 当前结果不再是用户目标,丢弃,转而加载新目标 + store.markBuilding(false) + self.loadChapter(spineIndex: target, store: store, priority: .navigation, completion: completion) + return + } + store.markBuilding(false) + DispatchQueue.main.async { + completion(.success(chapter)) + } + + case .prefetch: + // 后台预取:仅回填缓存,标记预取目标完成 + // 不触发跳章,不检查导航目标队列 + store.removePrefetchTarget(spineIndex) + store.markBuilding(false) + DispatchQueue.main.async { + completion(.success(chapter)) + } + } + } catch { + RDEPUBBackgroundTrace.log("ChapterLoader", "buildChapter FAILED: spine=\(spineIndex) error=\(error)") + store.markBuilding(false) + DispatchQueue.main.async { + completion(.failure(error)) + } + } + } + } + + // MARK: - 同步加载入口(仅供 legacy 位置迁移使用) + + /// 约束: + /// - 必须复用同一个 chapterLoadQueue,保持 WXRead 的单章串行语义 + /// - 不允许恢复整书 RDEPUBTextBook + /// - 只允许从主线程或明确的非 chapterLoadQueue 上下文调用 + /// - 调用前必须执行 store.assertNotOnChapterLoadQueue() + /// - 只在首次迁移且目标章未命中缓存时使用 + func loadChapterSynchronouslyForMigration( + spineIndex: Int, + store: RDEPUBChapterRuntimeStore? + ) throws -> RDEPUBRuntimeChapter { + if let cached = store?.chapterData(for: spineIndex) { + return cached + } + + guard let store else { + throw RDEPUBChapterLoadError.missingParser + } + + store.assertNotOnChapterLoadQueue() + + var result: Result? + let semaphore = DispatchSemaphore(value: 0) + RDEPUBBackgroundTrace.log("ChapterLoader", "sync request spine=\(spineIndex)") + store.chapterLoadQueue.async { + do { + result = try RDEPUBBackgroundTrace.measure( + "ChapterLoader", + "sync buildChapter spine=\(spineIndex)" + ) { + try autoreleasepool { () -> Result in + let cacheKey = self.makeCacheKey(spineIndex: spineIndex) + let precomputedPageRanges = store.pageCount(for: cacheKey)?.pageRanges + let diskSummary: RDEPUBChapterSummary? + if precomputedPageRanges == nil { + diskSummary = self.summaryDiskCache?.read(for: cacheKey) + } else { + diskSummary = nil + } + let chapter = try self.buildChapter( + spineIndex: spineIndex, + availablePageRanges: precomputedPageRanges ?? diskSummary?.pageRanges.map(\.nsRange), + diskSummary: diskSummary + ) + store.insertChapter(chapter) + let pageCount = RDEPUBRuntimePageCount( + cacheKey: cacheKey, + spineIndex: spineIndex, + pageRanges: chapter.pageRanges, + pageCount: chapter.pages.count, + renderSignature: cacheKey.renderSignature + ) + store.insertPageCount(pageCount, for: cacheKey) + return .success(chapter) + } + } + } catch { + result = .failure(error) + } + semaphore.signal() + } + semaphore.wait() + return try result!.get() + } + + // MARK: - 单章构建(支持轻量缓存命中后跳过分页) + + private func buildChapter( + spineIndex: Int, + availablePageRanges: [NSRange]?, + diskSummary: RDEPUBChapterSummary? = nil + ) throws -> RDEPUBRuntimeChapter { + guard let parser = context.parser, + let publication = context.publication else { + throw RDEPUBChapterLoadError.missingParser + } + + let pageSize = context.currentTextPageSize() + let style = context.currentTextRenderStyle() + let layoutConfig = context.currentTextLayoutConfig(pageSize: pageSize) + + if let pageRanges = availablePageRanges { + // ---- 轻量路径:pageCountCache 或 chapterSummaryDiskCache 命中 ---- + RDEPUBBackgroundTrace.log("ChapterLoader", "轻量路径 spine=\(spineIndex) 缓存页数=\(pageRanges.count)") + return try buildChapterFromCachedPageRanges( + spineIndex: spineIndex, + pageRanges: pageRanges, + parser: parser, + publication: publication, + pageSize: pageSize, + style: style, + layoutConfig: layoutConfig, + diskSummary: diskSummary + ) + } + + // ---- 完整路径:无缓存,走全量渲染 + 分页 ---- + RDEPUBBackgroundTrace.log("ChapterLoader", "完整路径 spine=\(spineIndex)") + let builder = context.makeTextBookBuilder(layoutConfig: layoutConfig) + guard let result = try builder.buildChapter( + parser: parser, + publication: publication, + spineIndex: spineIndex, + pageSize: pageSize, + style: style + ) else { + throw RDEPUBChapterLoadError.emptyChapter(spineIndex: spineIndex) + } + + return try assembleRuntimeChapter( + from: result.chapter, + spineIndex: spineIndex, + pageSize: pageSize, + layoutConfig: layoutConfig + ) + } + + // MARK: - 轻量路径:复用已有 pageRanges,跳过完整分页 + + private func buildChapterFromCachedPageRanges( + spineIndex: Int, + pageRanges: [NSRange], + parser: RDEPUBParser, + publication: RDEPUBPublication, + pageSize: CGSize, + style: RDEPUBTextRenderStyle, + layoutConfig: RDEPUBTextLayoutConfig, + diskSummary: RDEPUBChapterSummary? = nil + ) throws -> RDEPUBRuntimeChapter { + let spineItem = publication.spine[spineIndex] + let href = spineItem.href + let title = spineItem.title ?? "" + let baseURL = parser.fileURL(forRelativePath: href)?.deletingLastPathComponent() + + // 1. 只做 HTML → NSAttributedString 渲染,不做分页 + let request = RDEPUBTextRendererSupport.makeChapterRenderRequest( + href: href, + title: title, + rawHTML: try requireHTMLString(parser, href: href), + baseURL: baseURL, + style: style, + resourceResolver: publication.resourceResolver, + pageSize: pageSize, + layoutConfig: layoutConfig + ) + let renderer = context.resolvedTextRenderer() + let rendered = try renderer.renderChapter(request: request) + + let typesetString = NSMutableAttributedString(attributedString: rendered.attributedString) + RDEPUBTextRendererSupport.normalizeReadingAttributes( + in: typesetString, style: style, layoutConfig: layoutConfig + ) + + // 2. metadata 来源策略: + // - diskSummary 非空(磁盘路径命中):从摘要恢复完整 metadata + // - diskSummary 为空(pageCountCache 命中但没走磁盘):从 attributedString 属性推断 + let metadataSource = diskSummary?.pageMetadataList + + // 3. 直接用缓存的 pageRanges 构建 pages(跳过 CoreText 分页) + let pages = buildPagesFromRanges( + pageRanges: pageRanges, + typesetString: typesetString, + spineIndex: spineIndex, + href: href, + title: title, + metadataSource: metadataSource + ) + + // 4. 构建 layouter(用于后续可能的重新分页场景) + let layouter = RDEPUBTextLayouter( + attributedString: typesetString, + pageSize: pageSize, + config: layoutConfig + ) + + // 5. 构建 chapterOffsetMap + let offsetMap = RDEPUBChapterOffsetMap( + fragmentOffsets: rendered.fragmentOffsets, + pageStartOffsets: pages.map { $0.pageStartOffset }, + pageEndOffsets: pages.map { $0.pageEndOffset } + ) + + return RDEPUBRuntimeChapter( + spineIndex: spineIndex, + href: href, + title: title, + sourceAttributedString: nil, // 轻量路径不保留原始 source,降低内存 + typesetAttributedString: typesetString, + layouter: layouter, + pageRanges: pageRanges, + pages: pages, + chapterOffsetMap: offsetMap + ) + } + + // MARK: - 从缓存的 pageRanges 直接构建 RDEPUBTextPage 数组 + + private func buildPagesFromRanges( + pageRanges: [NSRange], + typesetString: NSAttributedString, + spineIndex: Int, + href: String, + title: String, + metadataSource: [RDEPUBChapterSummary.PageMetadataSummary]? = nil + ) -> [RDEPUBTextPage] { + let totalPageCount = pageRanges.count + return pageRanges.enumerated().map { (pageIndex, range) in + let pageContent = typesetString.attributedSubstring(from: range) + let metadata: RDEPUBTextPageMetadata + if let metaList = metadataSource, pageIndex < metaList.count { + // 从摘要缓存恢复完整 metadata + metadata = metaList[pageIndex].toPageMetadata() + } else { + // 无缓存 metadata,从 attributedString 属性推断 + metadata = inferPageMetadata( + from: typesetString, + range: range, + isLastPage: pageIndex == totalPageCount - 1 + ) + } + return RDEPUBTextPage( + absolutePageIndex: -1, + chapterIndex: 0, + spineIndex: spineIndex, + href: href, + chapterTitle: title, + pageIndexInChapter: pageIndex, + totalPagesInChapter: totalPageCount, + chapterContent: typesetString, + content: pageContent, + contentRange: range, + pageStartOffset: range.location, + pageEndOffset: range.location + range.length - 1, + metadata: metadata + ) + } + } + + // MARK: - 从 attributedString 的自定义属性推断页 metadata + + private func inferPageMetadata( + from string: NSAttributedString, + range: NSRange, + isLastPage: Bool + ) -> RDEPUBTextPageMetadata { + var attachmentRanges: [NSRange] = [] + var attachmentKinds: [RDEPUBTextAttachmentKind] = [] + var blockKinds: [RDEPUBTextBlockKind] = [] + var semanticHints: [RDEPUBTextSemanticHint] = [] + var attachmentPlacements: [RDEPUBTextAttachmentPlacement] = [] + var trailingFragmentID: String? = nil + + string.enumerateAttribute(.rdPageAttachmentKind, in: range, options: []) { value, attrRange, _ in + if let rawValue = value as? String, + let kind = RDEPUBTextAttachmentKind(rawValue: rawValue) { + attachmentRanges.append(attrRange) + attachmentKinds.append(kind) + } + } + string.enumerateAttribute(.rdPageBlockKind, in: range, options: []) { value, _, _ in + if let rawValue = value as? String, + let kind = RDEPUBTextBlockKind(rawValue: rawValue), + !blockKinds.contains(kind) { + blockKinds.append(kind) + } + } + string.enumerateAttribute(.rdPageSemanticHints, in: range, options: []) { value, _, _ in + if let rawValue = value as? String { + let hints = rawValue + .split(separator: ",") + .compactMap { RDEPUBTextSemanticHint(rawValue: String($0)) } + for hint in hints where !semanticHints.contains(hint) { + semanticHints.append(hint) + } + } + } + string.enumerateAttribute(.rdPageAttachmentPlacement, in: range, options: []) { value, _, _ in + if let rawValue = value as? String, + let placement = RDEPUBTextAttachmentPlacement(rawValue: rawValue), + !attachmentPlacements.contains(placement) { + attachmentPlacements.append(placement) + } + } + string.enumerateAttribute(.rdPageFragmentID, in: range, options: [.reverse]) { value, _, stop in + if let fid = value as? String { + trailingFragmentID = fid + stop.pointee = true + } + } + + return RDEPUBTextPageMetadata( + breakReason: isLastPage ? .chapterEnd : .frameLimit, + blockRange: nil, + attachmentRanges: attachmentRanges, + attachmentKinds: attachmentKinds, + blockKinds: blockKinds, + semanticHints: semanticHints, + attachmentPlacements: attachmentPlacements, + trailingFragmentID: trailingFragmentID, + diagnostics: [] + ) + } + + // MARK: - 从完整构建结果组装 RDEPUBRuntimeChapter + + private func assembleRuntimeChapter( + from chapter: RDEPUBTextChapter, + spineIndex: Int, + pageSize: CGSize, + layoutConfig: RDEPUBTextLayoutConfig + ) throws -> RDEPUBRuntimeChapter { + let layouter = RDEPUBTextLayouter( + attributedString: chapter.attributedContent, + pageSize: pageSize, + config: layoutConfig + ) + + let offsetMap = RDEPUBChapterOffsetMap( + fragmentOffsets: chapter.fragmentOffsets, + pageStartOffsets: chapter.pages.map { $0.pageStartOffset }, + pageEndOffsets: chapter.pages.map { $0.pageEndOffset } + ) + + let pageRanges = chapter.pages.map { $0.contentRange } + + // 回填磁盘摘要(P2 阶段生效) + let cacheKey = makeCacheKey(spineIndex: spineIndex) + let summary = RDEPUBChapterSummary( + pageRanges: pageRanges.map { .init(location: $0.location, length: $0.length) }, + pageCount: chapter.pages.count, + fragmentOffsets: chapter.fragmentOffsets, + renderSignature: cacheKey.renderSignature, + schemaVersion: RDEPUBChapterSummary.currentSchemaVersion, + chapterContentHash: cacheKey.chapterContentHash, + pageMetadataList: chapter.pages.map { .from($0.metadata) } + ) + summaryDiskCache?.write(summary: summary, for: cacheKey) + + return RDEPUBRuntimeChapter( + spineIndex: spineIndex, + href: chapter.href, + title: chapter.title, + sourceAttributedString: nil, + typesetAttributedString: chapter.attributedContent, + layouter: layouter, + pageRanges: pageRanges, + pages: chapter.pages, + chapterOffsetMap: offsetMap + ) + } + + // MARK: - 缓存键 + + private func makeCacheKey(spineIndex: Int) -> RDEPUBChapterCacheKey { + let style = context.currentTextRenderStyle() + let layoutConfig = context.currentTextLayoutConfig(pageSize: context.currentTextPageSize()) + + // renderSignature 必须覆盖 §8.2 定义的全部参数 + let lineHeightMultiple = context.configuration.lineHeightMultiple + + let renderSignature = [ + style.font.fontName, + "\(style.font.pointSize)", + "\(lineHeightMultiple)", + "\(style.lineSpacing)", + layoutConfig.cacheSignature, + "\(RDEPUBChapterSummary.currentSchemaVersion)" + ].joined(separator: "|") + + let contentHash = contentHashForSpineIndex(spineIndex) + + return RDEPUBChapterCacheKey( + bookID: context.currentBookIdentifier ?? "", + spineIndex: spineIndex, + renderSignature: renderSignature, + chapterContentHash: contentHash + ) + } + + private func contentHashForSpineIndex(_ spineIndex: Int) -> String { + guard let parser = context.parser, + let publication = context.publication else { return "" } + let href = publication.spine[spineIndex].href + guard let html = parser.htmlString(forRelativePath: href) else { return "" } + return html.sha256Hex + } + + private func requireHTMLString(_ parser: RDEPUBParser, href: String) throws -> String { + guard let html = parser.htmlString(forRelativePath: href) else { + throw RDEPUBChapterLoadError.emptyChapterHref(href) + } + return html + } +} + +enum RDEPUBChapterLoadError: LocalizedError { + case missingParser + case emptyChapter(spineIndex: Int) + case emptyChapterHref(String) + + var errorDescription: String? { + switch self { + case .missingParser: + return "章节加载失败:缺少解析上下文。" + case .emptyChapter(let spineIndex): + return "章节加载失败:第 \(spineIndex) 章无法生成分页内容。" + case .emptyChapterHref(let href): + return "章节加载失败:未找到章节资源 \(href)。" + } + } +} diff --git a/Sources/RDReaderView/EPUBUI/ReaderController/ChapterRuntime/RDEPUBChapterLocation.swift b/Sources/RDReaderView/EPUBUI/ReaderController/ChapterRuntime/RDEPUBChapterLocation.swift new file mode 100644 index 0000000..d08a9bb --- /dev/null +++ b/Sources/RDReaderView/EPUBUI/ReaderController/ChapterRuntime/RDEPUBChapterLocation.swift @@ -0,0 +1,33 @@ +import Foundation + +/// 章节级位置模型——单章内的精确定位 +/// 取代旧版 RDEPUBLocation 的全局 progression 方式 +public struct RDEPUBChapterLocation: Codable, Equatable { + /// 章节在 spine 中的索引 + public var spineIndex: Int + /// 章内字符偏移(从章首算起,0-based) + public var chapterOffset: Int + /// HTML fragment ID(如章节内锚点 #section1) + public var fragmentID: String? + /// 章内 progression(可选,fragmentID 优先时为 nil) + public var progressionInChapter: Double? + /// schema 版本:1=粗估降级, 2=精确值 + public var schemaVersion: Int + + public init( + spineIndex: Int, + chapterOffset: Int, + fragmentID: String? = nil, + progressionInChapter: Double? = nil, + schemaVersion: Int = 2 + ) { + self.spineIndex = spineIndex + self.chapterOffset = chapterOffset + self.fragmentID = fragmentID.flatMap { $0.isEmpty ? nil : $0 } + self.progressionInChapter = progressionInChapter + self.schemaVersion = schemaVersion + } + + /// 粗估结果标记:schemaVersion == 1 表示 chapterOffset 由 progression 粗估得来 + var isFallbackEstimate: Bool { schemaVersion == 1 } +} \ No newline at end of file diff --git a/Sources/RDReaderView/EPUBUI/ReaderController/ChapterRuntime/RDEPUBChapterOffsetMap.swift b/Sources/RDReaderView/EPUBUI/ReaderController/ChapterRuntime/RDEPUBChapterOffsetMap.swift new file mode 100644 index 0000000..a202adc --- /dev/null +++ b/Sources/RDReaderView/EPUBUI/ReaderController/ChapterRuntime/RDEPUBChapterOffsetMap.swift @@ -0,0 +1,22 @@ +import Foundation + +struct RDEPUBChapterOffsetMap { + let fragmentOffsets: [String: Int] + let pageStartOffsets: [Int] + let pageEndOffsets: [Int] + + /// fragmentID -> 章内字符偏移 + func chapterOffset(forFragmentID fragmentID: String) -> Int? { + return fragmentOffsets[fragmentID] + } + + /// 章内字符偏移 -> 章内页码(从 0 开始) + func pageIndex(forChapterOffset offset: Int) -> Int? { + for i in 0..= pageStartOffsets[i] && offset <= pageEndOffsets[i] { + return i + } + } + return nil + } +} \ No newline at end of file diff --git a/Sources/RDReaderView/EPUBUI/ReaderController/ChapterRuntime/RDEPUBChapterRuntimeStore.swift b/Sources/RDReaderView/EPUBUI/ReaderController/ChapterRuntime/RDEPUBChapterRuntimeStore.swift new file mode 100644 index 0000000..0fe2ccc --- /dev/null +++ b/Sources/RDReaderView/EPUBUI/ReaderController/ChapterRuntime/RDEPUBChapterRuntimeStore.swift @@ -0,0 +1,186 @@ +import UIKit + +final class RDEPUBChapterRuntimeStore { + + // MARK: - 子缓存 + + /// 章节运行时主缓存(等价 WXRead chapterDataCache) + private let chapterDataCache = RDEPUBChapterDataCache() + + /// 轻量分页结构缓存(等价 WXRead pageCountCache) + private let pageCountCache = RDEPUBPageCountCache() + + /// 图片缓存(独立 NSCache,等价 WXRead imageCache) + let imageCache = NSCache() + + /// 串行加载队列(等价 WXRead com.weread.chapterload) + /// QoS .userInitiated:用户主动跳章/开书属于前台交互,需尽快完成 + let chapterLoadQueue = DispatchQueue(label: "com.rdreader.chapterload", qos: .userInitiated) + private let chapterLoadQueueKey = DispatchSpecificKey() + + // MARK: - 窗口状态 + + /// 当前章 spineIndex + private(set) var currentSpineIndex: Int? + + /// 当前窗口内的 spineIndex 集合(当前 + prev + next) + private(set) var windowSpineIndices: [Int] = [] + + // MARK: - 请求通道(前台导航 vs 后台预取,语义独立,互不抢占) + + /// 前台导航目标(用户主动跳章:目录/书签/搜索/翻章) + /// 仅保留最后一次目标,旧的排队请求可被取消 + private var pendingNavigationTarget: Int? + private let navigationLock = NSLock() + + /// 后台预取目标集合(±1 相邻章预取) + /// 预取不抢占前台导航通道,预取完成后仅刷新快照,不触发跳章 + private var pendingPrefetchTargets: Set = [] + private let prefetchLock = NSLock() + + /// 是否有章节正在构建中 + private(set) var isBuilding: Bool = false + private let buildingLock = NSLock() + + // MARK: - 初始化 + + init() { + imageCache.countLimit = 50 + chapterLoadQueue.setSpecific(key: chapterLoadQueueKey, value: ()) + } + + func assertNotOnChapterLoadQueue() { + dispatchPrecondition(condition: .notOnQueue(chapterLoadQueue)) + } + + // MARK: - 缓存查询(线程安全,通过 cache wrapper 的 lock 保护) + + func chapterData(for spineIndex: Int) -> RDEPUBRuntimeChapter? { + return chapterDataCache[spineIndex] + } + + func pageCount(for key: RDEPUBChapterCacheKey) -> RDEPUBRuntimePageCount? { + return pageCountCache[key] + } + + // MARK: - 缓存插入 + + func insertChapter(_ chapter: RDEPUBRuntimeChapter) { + chapterDataCache[chapter.spineIndex] = chapter + } + + func insertPageCount(_ pc: RDEPUBRuntimePageCount, for key: RDEPUBChapterCacheKey) { + pageCountCache[key] = pc + } + + // MARK: - 窗口管理 + + /// 设定当前章,自动计算 ±1 窗口 + func setCurrentChapter(spineIndex: Int, totalSpineCount: Int) { + currentSpineIndex = spineIndex + var window = [spineIndex] + if spineIndex > 0 { window.append(spineIndex - 1) } + if spineIndex < totalSpineCount - 1 { window.append(spineIndex + 1) } + windowSpineIndices = window + } + + /// 返回窗口外、应该淘汰的 spineIndex + func evictableSpineIndices() -> [Int] { + let windowSet = Set(windowSpineIndices) + return chapterDataCache.storedSpineIndices.filter { !windowSet.contains($0) } + } + + // MARK: - 淘汰 + + func evict(spineIndex: Int) { + chapterDataCache.remove(spineIndex: spineIndex) + pageCountCache.remove(forSpineIndex: spineIndex) + } + + func evictAllExceptCurrent() { + guard let current = currentSpineIndex else { + chapterDataCache.removeAll() + pageCountCache.removeAll() + return + } + let currentChapter = chapterDataCache[current] + chapterDataCache.removeAll() + if let ch = currentChapter { + chapterDataCache[current] = ch + } + // WXRead 语义:内存警告时 pageCountCache 全量清空 + pageCountCache.removeAll() + } + + // MARK: - 内存警告 + + func handleMemoryWarning() { + evictAllExceptCurrent() + imageCache.removeAllObjects() + } + + // MARK: - 前台导航请求管理(§14.4 取消语义) + + /// 注册前台导航目标(用户主动跳章时调用) + /// 仅保留最后一次目标,旧的排队请求可被取消 + func setNavigationTarget(spineIndex: Int) { + navigationLock.lock() + pendingNavigationTarget = spineIndex + navigationLock.unlock() + } + + /// 消费前台导航目标(章节构建完成后调用,检查是否有更新的目标) + func consumeNavigationTarget() -> Int? { + navigationLock.lock() + let target = pendingNavigationTarget + pendingNavigationTarget = nil + navigationLock.unlock() + return target + } + + // MARK: - 后台预取请求管理 + + /// 注册后台预取目标(±1 相邻章预取时调用) + /// 预取不抢占前台导航通道 + func addPrefetchTarget(_ spineIndex: Int) { + prefetchLock.lock() + pendingPrefetchTargets.insert(spineIndex) + prefetchLock.unlock() + } + + /// 标记预取目标已完成 + func removePrefetchTarget(_ spineIndex: Int) { + prefetchLock.lock() + pendingPrefetchTargets.remove(spineIndex) + prefetchLock.unlock() + } + + /// 清空所有预取目标(切章时调用,旧预取结果不再需要) + func clearPrefetchTargets() { + prefetchLock.lock() + pendingPrefetchTargets.removeAll() + prefetchLock.unlock() + } + + /// 检查是否有待处理的预取目标 + func hasPrefetchTarget(_ spineIndex: Int) -> Bool { + prefetchLock.lock() + let has = pendingPrefetchTargets.contains(spineIndex) + prefetchLock.unlock() + return has + } + + func markBuilding(_ building: Bool) { + buildingLock.lock() + isBuilding = building + buildingLock.unlock() + } + + // MARK: - P1: 排版参数变化后整体失效(§8.5) + + func invalidateAllForSettingsChange() { + chapterDataCache.removeAll() + pageCountCache.removeAll() + imageCache.removeAllObjects() + } +} \ No newline at end of file diff --git a/Sources/RDReaderView/EPUBUI/ReaderController/ChapterRuntime/RDEPUBChapterSummaryDiskCache.swift b/Sources/RDReaderView/EPUBUI/ReaderController/ChapterRuntime/RDEPUBChapterSummaryDiskCache.swift new file mode 100644 index 0000000..e9b0906 --- /dev/null +++ b/Sources/RDReaderView/EPUBUI/ReaderController/ChapterRuntime/RDEPUBChapterSummaryDiskCache.swift @@ -0,0 +1,154 @@ +import Foundation + +final class RDEPUBChapterSummaryDiskCache { + private let cacheDirectory: URL + private let fileManager = FileManager.default + private let queue = DispatchQueue(label: "com.rdreader.summarydiskcache", qos: .utility) + + init(cacheDirectory: URL) { + self.cacheDirectory = cacheDirectory + try? fileManager.createDirectory(at: cacheDirectory, withIntermediateDirectories: true) + } + + // MARK: - 写入(异步) + + func write(summary: RDEPUBChapterSummary, for key: RDEPUBChapterCacheKey) { + queue.async { + self.writeImmediately(summary: summary, for: key) + } + } + + /// 同步写入:用于后台整书元数据构建完成前,确保摘要文件已经真实落盘。 + func writeSynchronously(summary: RDEPUBChapterSummary, for key: RDEPUBChapterCacheKey) { + queue.sync { + self.writeImmediately(summary: summary, for: key) + } + } + + // MARK: - 读取(同步,因为 loadChapter 已在串行队列上) + + func read(for key: RDEPUBChapterCacheKey) -> RDEPUBChapterSummary? { + let fileURL = self.fileURL(for: key) + guard let data = try? Data(contentsOf: fileURL) else { return nil } + return try? JSONDecoder().decode(RDEPUBChapterSummary.self, from: data) + } + + // MARK: - 批量读取:二次打开时直接从磁盘构建 BookPageMap + + /// 批量读取指定缓存键列表的摘要,返回 spineIndex → summary 映射。 + /// 同步方法,应在后台线程调用。 + /// 由调用方负责构建完整的缓存键列表(含正确的 contentHash)。 + func readAll(keys: [(key: RDEPUBChapterCacheKey, spineIndex: Int, href: String, title: String)]) -> ( + summaries: [Int: RDEPUBChapterSummary], + mapBuilder: RDEPUBBookPageMap.Builder + ) { + var summaries: [Int: RDEPUBChapterSummary] = [:] + var mapBuilder = RDEPUBBookPageMap.Builder() + + for item in keys { + if let summary = read(for: item.key) { + summaries[item.spineIndex] = summary + mapBuilder.add( + spineIndex: item.spineIndex, + href: item.href, + title: item.title, + pageCount: summary.pageCount, + fragmentOffsets: summary.fragmentOffsets + ) + } + } + return (summaries, mapBuilder) + } + + /// 检查指定缓存键列表是否全部有对应的磁盘摘要。 + func isCacheComplete(keys: [RDEPUBChapterCacheKey]) -> Bool { + for key in keys { + if read(for: key) == nil { + return false + } + } + return true + } + + /// 语义化别名:用于判断当前 renderSignature 下是否具备完整章节摘要集合。 + func containsCompleteSet(keys: [RDEPUBChapterCacheKey]) -> Bool { + isCacheComplete(keys: keys) + } + + /// 清空所有缓存文件 + func removeAll() { + guard let files = try? fileManager.contentsOfDirectory(at: cacheDirectory, includingPropertiesForKeys: nil) else { return } + for fileURL in files where fileURL.pathExtension == "json" { + try? fileManager.removeItem(at: fileURL) + } + } + + // MARK: - key -> 文件路径 + + /// 使用确定性字符串拼接生成文件名,不依赖 Hashable.hashValue + private func fileURL(for key: RDEPUBChapterCacheKey) -> URL { + let rawKey = "\(key.bookID)_\(key.spineIndex)_\(key.renderSignature)_\(key.chapterContentHash)" + let digest = rawKey.sha256Hex + return cacheDirectory.appendingPathComponent("\(digest).json") + } + + private func writeImmediately(summary: RDEPUBChapterSummary, for key: RDEPUBChapterCacheKey) { + let fileURL = self.fileURL(for: key) + let data = try? JSONEncoder().encode(summary) + try? data?.write(to: fileURL) + } +} + +struct RDEPUBChapterSummary: Codable { + let pageRanges: [RangeData] + let pageCount: Int + let fragmentOffsets: [String: Int] + let renderSignature: String + let schemaVersion: Int + let chapterContentHash: String + let pageMetadataList: [PageMetadataSummary] + + static let currentSchemaVersion = 6 + + struct RangeData: Codable { + let location: Int + let length: Int + var nsRange: NSRange { NSRange(location: location, length: length) } + } + + struct PageMetadataSummary: Codable { + let breakReason: String + let attachmentRanges: [RangeData] + let attachmentKinds: [String] + let blockKinds: [String] + let semanticHints: [String] + let attachmentPlacements: [String] + let trailingFragmentID: String? + + func toPageMetadata() -> RDEPUBTextPageMetadata { + RDEPUBTextPageMetadata( + breakReason: RDEPUBTextPageBreakReason(rawValue: breakReason) ?? .frameLimit, + blockRange: nil, + attachmentRanges: attachmentRanges.map { $0.nsRange }, + attachmentKinds: attachmentKinds.compactMap { RDEPUBTextAttachmentKind(rawValue: $0) }, + blockKinds: blockKinds.compactMap { RDEPUBTextBlockKind(rawValue: $0) }, + semanticHints: semanticHints.compactMap { RDEPUBTextSemanticHint(rawValue: $0) }, + attachmentPlacements: attachmentPlacements.compactMap { RDEPUBTextAttachmentPlacement(rawValue: $0) }, + trailingFragmentID: trailingFragmentID, + diagnostics: [] + ) + } + + static func from(_ metadata: RDEPUBTextPageMetadata) -> PageMetadataSummary { + PageMetadataSummary( + breakReason: metadata.breakReason.rawValue, + attachmentRanges: metadata.attachmentRanges.map { .init(location: $0.location, length: $0.length) }, + attachmentKinds: metadata.attachmentKinds.map { $0.rawValue }, + blockKinds: metadata.blockKinds.map { $0.rawValue }, + semanticHints: metadata.semanticHints.map { $0.rawValue }, + attachmentPlacements: metadata.attachmentPlacements.map { $0.rawValue }, + trailingFragmentID: metadata.trailingFragmentID + ) + } + } +} diff --git a/Sources/RDReaderView/EPUBUI/ReaderController/ChapterRuntime/RDEPUBChapterWindowCoordinator.swift b/Sources/RDReaderView/EPUBUI/ReaderController/ChapterRuntime/RDEPUBChapterWindowCoordinator.swift new file mode 100644 index 0000000..b7b8d92 --- /dev/null +++ b/Sources/RDReaderView/EPUBUI/ReaderController/ChapterRuntime/RDEPUBChapterWindowCoordinator.swift @@ -0,0 +1,312 @@ +import Foundation + +final class RDEPUBChapterWindowCoordinator { + private unowned let context: RDEPUBReaderContext + private let store: RDEPUBChapterRuntimeStore + private let loader: RDEPUBChapterLoader + + /// 当前窗口快照 + private(set) var currentSnapshot: RDEPUBChapterWindowSnapshot? + + /// 窗口切换回调 + var onSnapshotChanged: ((RDEPUBChapterWindowSnapshot) -> Void)? + + init(context: RDEPUBReaderContext, store: RDEPUBChapterRuntimeStore, loader: RDEPUBChapterLoader) { + self.context = context + self.store = store + self.loader = loader + } + + /// 章节加载完成后恢复位置用 + private var restoreChapterOffset: Int? + + // MARK: - 打开书籍 + + func openBook(at targetSpineIndex: Int, restoreChapterOffset: Int? = nil) { + let totalSpineCount = context.publication?.spine.count ?? 0 + store.setCurrentChapter(spineIndex: targetSpineIndex, totalSpineCount: totalSpineCount) + self.restoreChapterOffset = restoreChapterOffset + + // 标记切章进行中 + isSwitchingChapter = true + + // 注册前台导航目标 + store.setNavigationTarget(spineIndex: targetSpineIndex) + // 清空旧预取目标 + store.clearPrefetchTargets() + + // 加载目标章(前台导航优先级) + loadChapterWithFallback(initialSpineIndex: targetSpineIndex, totalSpineCount: totalSpineCount) + } + + /// 加载章节,如果当前章节失败则自动尝试下一个可渲染的章节 + private func loadChapterWithFallback(initialSpineIndex: Int, totalSpineCount: Int) { + loader.loadChapter(spineIndex: initialSpineIndex, store: store, priority: .navigation) { [weak self] result in + guard let self = self else { return } + switch result { + case .success(let chapter): + self.isSwitchingChapter = false + self.buildSnapshotAroundCurrent(chapter: chapter) + case .failure(let error): + print("[EPUB][WindowCoord] loadChapter failed at spine=\(initialSpineIndex): \(error), trying next") + // 自动跳过不可渲染的章节(封面/版权页等 linear=false 的 spine 项) + let nextIndex = initialSpineIndex + 1 + if nextIndex < totalSpineCount { + self.store.setCurrentChapter(spineIndex: nextIndex, totalSpineCount: totalSpineCount) + self.store.setNavigationTarget(spineIndex: nextIndex) + self.loadChapterWithFallback(initialSpineIndex: nextIndex, totalSpineCount: totalSpineCount) + } else { + // 所有章节都不可渲染 + self.isSwitchingChapter = false + self.handle(error: error) + } + } + } + } + + // MARK: - 构建窗口快照 + + private func buildSnapshotAroundCurrent(chapter: RDEPUBRuntimeChapter) { + guard let current = store.currentSpineIndex else { + print("[EPUB][WindowCoord] buildSnapshot: currentSpineIndex is nil, ABORT") + return + } + let prev = current > 0 ? store.chapterData(for: current - 1) : nil + let next = store.chapterData(for: current + 1) + + let snapshot = RDEPUBChapterWindowSnapshot.from( + currentChapter: chapter, + previousChapter: prev, + nextChapter: next + ) + print("[EPUB][WindowCoord] snapshot: chapters=\(snapshot.chapters.count) pages=\(snapshot.pageCount) anchorPage=\(snapshot.anchorPageOffset)") + currentSnapshot = snapshot + isApplyingSnapshot = true + onSnapshotChanged?(snapshot) + isApplyingSnapshot = false + + // 首次打开时恢复到指定 chapterOffset + if let offset = restoreChapterOffset, + let chapter = snapshot.chapterForPage(flattenedPageIndex: snapshot.anchorPageOffset), + let pageIndex = chapter.chapterOffsetMap.pageIndex(forChapterOffset: offset) { + let targetPage = snapshot.anchorPageOffset + pageIndex + context.readerView?.transitionToPage(pageNum: targetPage, animated: false) + } else if snapshot.pageCount > 0 { + // 首次打开且没有恢复位置时,必须显式落到当前章首屏。 + // reloadData() 内部 switchReaderDisplayType 已将 currentPage 从 -1 置为 0, + // 但 0 不一定是目标章的起始页(anchorPageOffset),仍需显式 transition。 + context.readerView?.transitionToPage(pageNum: snapshot.anchorPageOffset, animated: false) + } + restoreChapterOffset = nil + + // 预取 ±1 + prefetchAdjacent(current: current) + } + + // MARK: - 预取(后台优先级,不抢占前台导航通道) + + private func prefetchAdjacent(current: Int) { + let totalSpineCount = context.publication?.spine.count ?? 0 + + // 预取 prev + if current > 0 && store.chapterData(for: current - 1) == nil { + let prevIndex = current - 1 + store.addPrefetchTarget(prevIndex) + loader.loadChapter(spineIndex: prevIndex, store: store, priority: .prefetch) { [weak self] result in + guard let self = self, case .success = result else { return } + self.refreshSnapshot() + } + } + + // 预取 next + if current < totalSpineCount - 1 && store.chapterData(for: current + 1) == nil { + let nextIndex = current + 1 + store.addPrefetchTarget(nextIndex) + loader.loadChapter(spineIndex: nextIndex, store: store, priority: .prefetch) { [weak self] result in + guard let self = self, case .success = result else { return } + self.refreshSnapshot() + } + } + } + + // MARK: - 翻章 + + /// 到达章末,翻到下一章 + func flipToNextChapter(completion: @escaping (Result) -> Void) { + guard let current = store.currentSpineIndex else { return } + let next = current + 1 + let totalSpineCount = context.publication?.spine.count ?? 0 + guard next < totalSpineCount else { return } + + flipToChapter(spineIndex: next, completion: completion) + } + + /// 到达章首,翻到上一章 + func flipToPreviousChapter(completion: @escaping (Result) -> Void) { + guard let current = store.currentSpineIndex, current > 0 else { return } + flipToChapter(spineIndex: current - 1, completion: completion) + } + + /// 跳转到指定章节(目录/书签/搜索) + func flipToChapter( + spineIndex: Int, + completion: @escaping (Result) -> Void + ) { + let totalSpineCount = context.publication?.spine.count ?? 0 + + // 注册前台导航目标 + store.setNavigationTarget(spineIndex: spineIndex) + // 清空后台预取目标 + store.clearPrefetchTargets() + // 标记切章进行中 + isSwitchingChapter = true + + // 先淘汰旧窗口外章节 + store.setCurrentChapter(spineIndex: spineIndex, totalSpineCount: totalSpineCount) + let evictable = store.evictableSpineIndices() + for idx in evictable { + store.evict(spineIndex: idx) + } + + // 如果目标章已在缓存中,直接构建快照 + if let cached = store.chapterData(for: spineIndex) { + buildSnapshotAroundCurrent(chapter: cached) + isSwitchingChapter = false + if let snap = currentSnapshot { + completion(.success(snap)) + } + return + } + + // 未命中缓存,走加载链路(前台导航优先级) + loader.loadChapter(spineIndex: spineIndex, store: store, priority: .navigation) { [weak self] result in + guard let self = self else { return } + self.isSwitchingChapter = false + switch result { + case .success(let chapter): + self.buildSnapshotAroundCurrent(chapter: chapter) + if let snap = self.currentSnapshot { + completion(.success(snap)) + } + case .failure(let error): + completion(.failure(error)) + } + } + } + + // MARK: - 刷新快照(预取完成后调用) + + func refreshSnapshot() { + guard let current = store.currentSpineIndex, + let currentChapter = store.chapterData(for: current) else { return } + + // 空闲门槛检查 + guard isReaderIdle() else { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { [weak self] in + self?.refreshSnapshot() + } + return + } + + let prev = current > 0 ? store.chapterData(for: current - 1) : nil + let next = store.chapterData(for: current + 1) + + let newSnapshot = RDEPUBChapterWindowSnapshot.from( + currentChapter: currentChapter, + previousChapter: prev, + nextChapter: next + ) + + if snapshotContentChanged(old: currentSnapshot, new: newSnapshot) { + currentSnapshot = newSnapshot + isApplyingSnapshot = true + onSnapshotChanged?(newSnapshot) + isApplyingSnapshot = false + } + } + + // MARK: - P1: 翻章后维护窗口 + + /// 当前章常驻,预取新的相邻章 + func maintainWindow(afterMovingTo spineIndex: Int) { + let totalSpineCount = context.publication?.spine.count ?? 0 + + // 淘汰窗口外章节 + store.setCurrentChapter(spineIndex: spineIndex, totalSpineCount: totalSpineCount) + for idx in store.evictableSpineIndices() { + store.evict(spineIndex: idx) + } + + // 预取 prev + if spineIndex > 0 && store.chapterData(for: spineIndex - 1) == nil { + let prevIndex = spineIndex - 1 + store.addPrefetchTarget(prevIndex) + loader.loadChapter(spineIndex: prevIndex, store: store, priority: .prefetch) { [weak self] result in + guard let self = self, case .success = result else { return } + self.refreshSnapshot() + } + } + + // 预取 next + if spineIndex < totalSpineCount - 1 && store.chapterData(for: spineIndex + 1) == nil { + let nextIndex = spineIndex + 1 + store.addPrefetchTarget(nextIndex) + loader.loadChapter(spineIndex: nextIndex, store: store, priority: .prefetch) { [weak self] result in + guard let self = self, case .success = result else { return } + self.refreshSnapshot() + } + } + } + + // MARK: - 内部辅助 + + private func handle(error: Error) { + // 日志记录,不中断当前阅读状态 + print("[RDEPUBChapterWindowCoordinator] chapter load error: \(error)") + // 确保 loading 指示器在加载失败时也被隐藏(避免永久白屏) + DispatchQueue.main.async { [weak self] in + guard let self else { return } + self.context.hideLoading() + // 如果有快照但没内容显示,显示错误提示 + if self.currentSnapshot == nil { + print("[RDEPUBChapterWindowCoordinator] No snapshot after error, page will be blank") + } + } + } + + private func snapshotContentChanged( + old: RDEPUBChapterWindowSnapshot?, + new: RDEPUBChapterWindowSnapshot + ) -> Bool { + guard let old = old else { return true } + + if old.chapters.count != new.chapters.count { return true } + + let oldSpines = old.chapters.map { $0.spineIndex } + let newSpines = new.chapters.map { $0.spineIndex } + if oldSpines != newSpines { return true } + + if old.pageCount != new.pageCount { return true } + + for (oldCh, newCh) in zip(old.chapters, new.chapters) { + if oldCh.pages.count != newCh.pages.count { return true } + } + + if old.anchorChapterIndex != new.anchorChapterIndex + || old.anchorPageOffset != new.anchorPageOffset { return true } + + return false + } + + private func isReaderIdle() -> Bool { + guard !store.isBuilding else { return false } + guard !isSwitchingChapter else { return false } + guard !isApplyingSnapshot else { return false } + return true + } + + /// 切章进行中标记 + private var isSwitchingChapter: Bool = false + /// 应用快照进行中标记 + private var isApplyingSnapshot: Bool = false +} diff --git a/Sources/RDReaderView/EPUBUI/ReaderController/ChapterRuntime/RDEPUBChapterWindowSnapshot.swift b/Sources/RDReaderView/EPUBUI/ReaderController/ChapterRuntime/RDEPUBChapterWindowSnapshot.swift new file mode 100644 index 0000000..fd18cde --- /dev/null +++ b/Sources/RDReaderView/EPUBUI/ReaderController/ChapterRuntime/RDEPUBChapterWindowSnapshot.swift @@ -0,0 +1,86 @@ +import Foundation + +struct RDEPUBChapterWindowSnapshot { + /// 窗口中的章节(有序:prev, current, next) + let chapters: [RDEPUBRuntimeChapter] + + /// 展平后的连续页数组(供 RDReaderView 消费) + /// 窗口内页码不写回 RDEPUBTextPage 模型; + /// flattenedPages 的数组下标就是窗口内连续页码(从 0 开始)。 + let flattenedPages: [RDEPUBTextPage] + + /// 当前章在 chapters 数组中的索引 + let anchorChapterIndex: Int + + /// 当前章在 flattenedPages 中的起始页码(从 0 开始,窗口内编号) + let anchorPageOffset: Int + + /// 当前窗口首章的 spineIndex,用于调试日志和跨窗口映射 + let windowStartSpineIndex: Int + + // MARK: - 构建 + + /// 从章节窗口构建快照 + static func from( + currentChapter: RDEPUBRuntimeChapter, + previousChapter: RDEPUBRuntimeChapter?, + nextChapter: RDEPUBRuntimeChapter? + ) -> RDEPUBChapterWindowSnapshot { + var chapters: [RDEPUBRuntimeChapter] = [] + var anchorIndex = 0 + var pageOffset = 0 + + if let prev = previousChapter { + chapters.append(prev) + anchorIndex = 1 + pageOffset = prev.pages.count + } + + chapters.append(currentChapter) + + if let next = nextChapter { + chapters.append(next) + } + + // 展平页数组 + var allPages: [RDEPUBTextPage] = [] + for (chIdx, ch) in chapters.enumerated() { + for var page in ch.pages { + page.chapterIndex = chIdx + allPages.append(page) + } + } + + let windowStartSpineIndex = chapters.first?.spineIndex ?? currentChapter.spineIndex + + return RDEPUBChapterWindowSnapshot( + chapters: chapters, + flattenedPages: allPages, + anchorChapterIndex: anchorIndex, + anchorPageOffset: pageOffset, + windowStartSpineIndex: windowStartSpineIndex + ) + } + + // MARK: - 查询 + + /// 窗口内页码(即 flattenedPages 下标)-> 所属章节 + func chapterForPage(flattenedPageIndex: Int) -> RDEPUBRuntimeChapter? { + var offset = 0 + for ch in chapters { + if flattenedPageIndex < offset + ch.pages.count { + return ch + } + offset += ch.pages.count + } + return nil + } + + /// 窗口内页码(即 flattenedPages 下标)-> 所属章节的 spineIndex + func spineIndexForPage(flattenedPageIndex: Int) -> Int? { + return chapterForPage(flattenedPageIndex: flattenedPageIndex)?.spineIndex + } + + /// 总页数(窗口内) + var pageCount: Int { flattenedPages.count } +} \ No newline at end of file diff --git a/Sources/RDReaderView/EPUBUI/ReaderController/ChapterRuntime/RDEPUBLocationConverter.swift b/Sources/RDReaderView/EPUBUI/ReaderController/ChapterRuntime/RDEPUBLocationConverter.swift new file mode 100644 index 0000000..6522b0e --- /dev/null +++ b/Sources/RDReaderView/EPUBUI/ReaderController/ChapterRuntime/RDEPUBLocationConverter.swift @@ -0,0 +1,111 @@ +import Foundation + +struct RDEPUBLocationConverter { + + // MARK: - 主路径:先构建目标章,拿到真实长度后再精确转换 + + /// 旧版 RDEPUBLocation -> 新版 RDEPUBChapterLocation + /// 主迁移路径:要求先构建目标章,用真实 chapterLength 做精确转换 + /// 仅在无法获取章节长度时才降级到粗估 fallback + static func convert( + legacy location: RDEPUBLocation, + parser: RDEPUBParser, + publication: RDEPUBPublication, + chapterLengthProvider: ((Int) -> Int?)? = nil + ) -> RDEPUBChapterLocation? { + // 1. 从 href 找到 spineIndex + guard let spineItem = publication.spine.first(where: { + $0.href == location.href || $0.href.contains(location.href) + }) else { return nil } + + let spineIndex = publication.spine.firstIndex(of: spineItem) ?? 0 + + // 2. 优先用 fragmentID 定位(最精确,不受 progression 精度影响) + if let fragmentID = location.fragment { + return RDEPUBChapterLocation( + spineIndex: spineIndex, + chapterOffset: 0, // fragmentID 由 chapterOffsetMap 精确解析 + fragmentID: fragmentID, + progressionInChapter: location.progression + ) + } + + // 3. 有 chapterLength 时做精确转换 + if let provider = chapterLengthProvider, + let chapterLength = provider(spineIndex), chapterLength > 0 { + return convert( + legacy: location, + spineIndex: spineIndex, + chapterLength: chapterLength + ) + } + + // 4. Fallback:无法获取章节长度时的粗估(仅作临时降级) + let estimatedOffset = Int(location.progression * 10000) + return RDEPUBChapterLocation( + spineIndex: spineIndex, + chapterOffset: estimatedOffset, + fragmentID: nil, + progressionInChapter: location.progression, + schemaVersion: 1 // 标记为降级结果,后续可被精确值覆盖 + ) + } + + /// 精确转换:已知章节实际长度 + static func convert( + legacy location: RDEPUBLocation, + spineIndex: Int, + chapterLength: Int + ) -> RDEPUBChapterLocation? { + let offset = Int(location.progression * Double(chapterLength)) + return RDEPUBChapterLocation( + spineIndex: spineIndex, + chapterOffset: offset, + fragmentID: location.fragment, + progressionInChapter: location.progression, + schemaVersion: 2 + ) + } + + /// 从已构建的 RDEPUBRuntimeChapter 做精确转换(推荐迁移路径) + static func convert( + legacy location: RDEPUBLocation, + chapter: RDEPUBRuntimeChapter + ) -> RDEPUBChapterLocation? { + // 优先用 fragmentID + if let fragmentID = location.fragment, + let fragmentOffset = chapter.chapterOffsetMap.chapterOffset(forFragmentID: fragmentID) { + return RDEPUBChapterLocation( + spineIndex: chapter.spineIndex, + chapterOffset: fragmentOffset, + fragmentID: fragmentID, + progressionInChapter: nil, + schemaVersion: 2 + ) + } + + // 用 progression + 真实长度 + let chapterLength = chapter.typesetAttributedString.length + return convert( + legacy: location, + spineIndex: chapter.spineIndex, + chapterLength: chapterLength + ) + } + + /// 新版 -> 旧版(兼容外部接口) + static func toLegacy( + chapterLocation: RDEPUBChapterLocation, + href: String, + chapterLength: Int + ) -> RDEPUBLocation { + let progression = chapterLength > 0 + ? Double(chapterLocation.chapterOffset) / Double(chapterLength) + : 0 + return RDEPUBLocation( + href: href, + progression: min(max(progression, 0), 1), + fragment: chapterLocation.fragmentID + ) + } +} \ No newline at end of file diff --git a/Sources/RDReaderView/EPUBUI/ReaderController/ChapterRuntime/RDEPUBPageCountCache.swift b/Sources/RDReaderView/EPUBUI/ReaderController/ChapterRuntime/RDEPUBPageCountCache.swift new file mode 100644 index 0000000..cd889c0 --- /dev/null +++ b/Sources/RDReaderView/EPUBUI/ReaderController/ChapterRuntime/RDEPUBPageCountCache.swift @@ -0,0 +1,37 @@ +import Foundation + +final class RDEPUBPageCountCache { + private var storage: [RDEPUBChapterCacheKey: RDEPUBRuntimePageCount] = [:] + private let lock = NSLock() + + subscript(key: RDEPUBChapterCacheKey) -> RDEPUBRuntimePageCount? { + get { + lock.lock() + defer { lock.unlock() } + return storage[key] + } + set { + lock.lock() + defer { lock.unlock() } + storage[key] = newValue + } + } + + func entriesForSpineIndex(_ spineIndex: Int) -> [(RDEPUBChapterCacheKey, RDEPUBRuntimePageCount)] { + lock.lock() + defer { lock.unlock() } + return storage.filter { $0.value.spineIndex == spineIndex }.map { ($0.key, $0.value) } + } + + func remove(forSpineIndex spineIndex: Int) { + lock.lock() + defer { lock.unlock() } + storage = storage.filter { $0.value.spineIndex != spineIndex } + } + + func removeAll() { + lock.lock() + defer { lock.unlock() } + storage.removeAll() + } +} \ No newline at end of file diff --git a/Sources/RDReaderView/EPUBUI/ReaderController/ChapterRuntime/RDEPUBPageResolver.swift b/Sources/RDReaderView/EPUBUI/ReaderController/ChapterRuntime/RDEPUBPageResolver.swift new file mode 100644 index 0000000..6036b0a --- /dev/null +++ b/Sources/RDReaderView/EPUBUI/ReaderController/ChapterRuntime/RDEPUBPageResolver.swift @@ -0,0 +1,35 @@ +import Foundation + +struct RDEPUBResolvedPage { + let page: RDEPUBTextPage + let chapter: RDEPUBRuntimeChapter + let chapterIndex: Int +} + +final class RDEPUBPageResolver { + private unowned let context: RDEPUBReaderContext + private let store: RDEPUBChapterRuntimeStore + + init(context: RDEPUBReaderContext, store: RDEPUBChapterRuntimeStore) { + self.context = context + self.store = store + } + + func resolvePage(absolutePageIndex: Int) -> RDEPUBResolvedPage? { + guard let bookPageMap = context.bookPageMap, + let spineIndex = bookPageMap.spineIndex(forAbsolutePage: absolutePageIndex), + let localPageIndex = bookPageMap.localPageIndex(forAbsolutePage: absolutePageIndex), + let chapter = store.chapterData(for: spineIndex), + chapter.pages.indices.contains(localPageIndex), + let chapterIndex = bookPageMap.chapterIndex(forSpineIndex: spineIndex) else { + return nil + } + + var page = chapter.pages[localPageIndex] + page.absolutePageIndex = absolutePageIndex + page.chapterIndex = chapterIndex + page.pageIndexInChapter = localPageIndex + page.totalPagesInChapter = chapter.pages.count + return RDEPUBResolvedPage(page: page, chapter: chapter, chapterIndex: chapterIndex) + } +} diff --git a/Sources/RDReaderView/EPUBUI/ReaderController/ChapterRuntime/RDEPUBRuntimeChapter.swift b/Sources/RDReaderView/EPUBUI/ReaderController/ChapterRuntime/RDEPUBRuntimeChapter.swift new file mode 100644 index 0000000..61cc2a7 --- /dev/null +++ b/Sources/RDReaderView/EPUBUI/ReaderController/ChapterRuntime/RDEPUBRuntimeChapter.swift @@ -0,0 +1,52 @@ +import Foundation + +final class RDEPUBRuntimeChapter { + let spineIndex: Int + let href: String + let title: String + + /// 原始富文本(可按策略释放,不强制常驻) + var sourceAttributedString: NSAttributedString? + + /// 排版后富文本 + let typesetAttributedString: NSAttributedString + + /// 排版器 + let layouter: RDEPUBTextLayouter + + /// 页范围 + let pageRanges: [NSRange] + + /// 页面数组 + let pages: [RDEPUBTextPage] + + /// 章节偏移映射 + let chapterOffsetMap: RDEPUBChapterOffsetMap + + init( + spineIndex: Int, + href: String, + title: String, + sourceAttributedString: NSAttributedString?, + typesetAttributedString: NSAttributedString, + layouter: RDEPUBTextLayouter, + pageRanges: [NSRange], + pages: [RDEPUBTextPage], + chapterOffsetMap: RDEPUBChapterOffsetMap + ) { + self.spineIndex = spineIndex + self.href = href + self.title = title + self.sourceAttributedString = sourceAttributedString + self.typesetAttributedString = typesetAttributedString + self.layouter = layouter + self.pageRanges = pageRanges + self.pages = pages + self.chapterOffsetMap = chapterOffsetMap + } + + /// 释放 sourceAttributedString 以降低内存 + func releaseSourceText() { + sourceAttributedString = nil + } +} \ No newline at end of file diff --git a/Sources/RDReaderView/EPUBUI/ReaderController/ChapterRuntime/RDEPUBRuntimePageCount.swift b/Sources/RDReaderView/EPUBUI/ReaderController/ChapterRuntime/RDEPUBRuntimePageCount.swift new file mode 100644 index 0000000..294007a --- /dev/null +++ b/Sources/RDReaderView/EPUBUI/ReaderController/ChapterRuntime/RDEPUBRuntimePageCount.swift @@ -0,0 +1,9 @@ +import Foundation + +struct RDEPUBRuntimePageCount { + let cacheKey: RDEPUBChapterCacheKey + let spineIndex: Int + let pageRanges: [NSRange] + let pageCount: Int + let renderSignature: String +} \ No newline at end of file diff --git a/Sources/RDReaderView/EPUBUI/ReaderController/ChapterRuntime/String+SHA256.swift b/Sources/RDReaderView/EPUBUI/ReaderController/ChapterRuntime/String+SHA256.swift new file mode 100644 index 0000000..fed980f --- /dev/null +++ b/Sources/RDReaderView/EPUBUI/ReaderController/ChapterRuntime/String+SHA256.swift @@ -0,0 +1,8 @@ +import CryptoKit + +extension String { + var sha256Hex: String { + let digest = SHA256.hash(data: Data(self.utf8)) + return digest.map { String(format: "%02x", $0) }.joined() + } +} \ No newline at end of file diff --git a/Sources/RDReaderView/EPUBUI/ReaderController/RDEPUBReaderAssemblyCoordinator.swift b/Sources/RDReaderView/EPUBUI/ReaderController/RDEPUBReaderAssemblyCoordinator.swift index b8f92a8..82267cf 100644 --- a/Sources/RDReaderView/EPUBUI/ReaderController/RDEPUBReaderAssemblyCoordinator.swift +++ b/Sources/RDReaderView/EPUBUI/ReaderController/RDEPUBReaderAssemblyCoordinator.swift @@ -50,8 +50,8 @@ final class RDEPUBReaderAssemblyCoordinator { } private func setupReaderView(_ readerView: RDReaderView, in containerView: UIView) { - readerView.dataSource = context.controller as? RDReaderDataSource - readerView.delegate = context.controller as? RDReaderDelegate + readerView.dataSource = context.controller + readerView.delegate = context.controller readerView.translatesAutoresizingMaskIntoConstraints = false containerView.addSubview(readerView) NSLayoutConstraint.activate([ diff --git a/Sources/RDReaderView/EPUBUI/ReaderController/RDEPUBReaderChromeCoordinator.swift b/Sources/RDReaderView/EPUBUI/ReaderController/RDEPUBReaderChromeCoordinator.swift index 69a8c7d..1ce475e 100644 --- a/Sources/RDReaderView/EPUBUI/ReaderController/RDEPUBReaderChromeCoordinator.swift +++ b/Sources/RDReaderView/EPUBUI/ReaderController/RDEPUBReaderChromeCoordinator.swift @@ -117,7 +117,10 @@ final class RDEPUBReaderChromeCoordinator { func presentTableOfContents() { guard let controller else { return } guard controller.configuration.showsTableOfContents else { return } - let items = controller.flattenedTableOfContents + let items = controller.flattenedTableOfContentsItems( + from: controller.publication?.tableOfContents ?? [], + includePageNumbers: false + ) guard !items.isEmpty else { return } let chapterController = RDEPUBReaderChapterListController( diff --git a/Sources/RDReaderView/EPUBUI/ReaderController/RDEPUBReaderContext.swift b/Sources/RDReaderView/EPUBUI/ReaderController/RDEPUBReaderContext.swift index c451871..5eab7a2 100644 --- a/Sources/RDReaderView/EPUBUI/ReaderController/RDEPUBReaderContext.swift +++ b/Sources/RDReaderView/EPUBUI/ReaderController/RDEPUBReaderContext.swift @@ -34,7 +34,10 @@ final class RDEPUBReaderContext { /// 当前阅读会话,管理页面和章节状态。 var readingSession: RDEPUBReadingSession? /// 原生文本排版生成的图书模型(仅文本重排模式)。 + /// 章节模式下为 nil,内容通过 ChapterRuntimeStore 访问。 var textBook: RDEPUBTextBook? + /// 全书轻量页码映射(章节模式)。约 100KB/1000章,不持有 NSAttributedString。 + var bookPageMap: RDEPUBBookPageMap? /// 当前书籍的所有书签。 var activeBookmarks: [RDEPUBBookmark] = [] /// 当前书籍的所有高亮标注。 @@ -131,8 +134,9 @@ final class RDEPUBReaderContext { edgeInsets: configuration.reflowableContentInsets, numberOfColumns: configuration.numberOfColumns, columnGap: configuration.columnGap, - avoidOrphans: true, - avoidWidows: true, + // 小说正文更看重尽量铺满页面,避免页尾出现明显留白。 + avoidOrphans: false, + avoidWidows: false, avoidPageBreakInsideEnabled: true, hyphenation: true, imageMaxHeightRatio: 0.85, @@ -186,6 +190,66 @@ final class RDEPUBReaderContext { dependencies.makeTextBookBuilder(resolvedTextRenderer(), textBookCache, layoutConfig) } + func makeChapterSummaryDiskCache() -> RDEPUBChapterSummaryDiskCache { + let cachesDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first + ?? FileManager.default.temporaryDirectory + let bookID = (currentBookIdentifier ?? "default").sha256Hex + let directory = cachesDirectory + .appendingPathComponent("RDEPUBChapterSummaryCache", isDirectory: true) + .appendingPathComponent(bookID, isDirectory: true) + return RDEPUBChapterSummaryDiskCache(cacheDirectory: directory) + } + + func chapterCacheKey(forSpineIndex spineIndex: Int) -> RDEPUBChapterCacheKey { + let style = currentTextRenderStyle() + let pageSize = currentTextPageSize() + let layoutConfig = currentTextLayoutConfig(pageSize: pageSize) + let renderSignature = [ + style.font.fontName, + "\(style.font.pointSize)", + "\(configuration.lineHeightMultiple)", + "\(style.lineSpacing)", + layoutConfig.cacheSignature, + "\(RDEPUBChapterSummary.currentSchemaVersion)" + ].joined(separator: "|") + + let contentHash: String + if let parser, + let publication, + publication.spine.indices.contains(spineIndex) { + let href = publication.spine[spineIndex].href + contentHash = parser.htmlString(forRelativePath: href)?.sha256Hex ?? "" + } else { + contentHash = "" + } + + return RDEPUBChapterCacheKey( + bookID: currentBookIdentifier ?? "", + spineIndex: spineIndex, + renderSignature: renderSignature, + chapterContentHash: contentHash + ) + } + + func chapterSummary(forSpineIndex spineIndex: Int) -> RDEPUBChapterSummary? { + runtime?.summaryDiskCache.read(for: chapterCacheKey(forSpineIndex: spineIndex)) + } + + func normalizedSpineIndex(for location: RDEPUBLocation) -> Int? { + guard let publication else { return nil } + let normalizedLocation = publication.resourceResolver.normalizedLocation( + location, + relativeToSpineIndex: nil, + bookIdentifier: currentBookIdentifier + ) ?? location + guard let normalizedHref = publication.resourceResolver.normalizedHref(normalizedLocation.href) else { + return nil + } + return publication.spine.firstIndex { + publication.resourceResolver.normalizedHref($0.href) == normalizedHref + } + } + /// 工厂方法:创建纯文本图书构建器。 func makePlainTextBookBuilder(layoutConfig: RDEPUBTextLayoutConfig) -> RDPlainTextBookBuilder { dependencies.makePlainTextBookBuilder(resolvedTextRenderer(), layoutConfig) diff --git a/Sources/RDReaderView/EPUBUI/ReaderController/RDEPUBReaderLocationCoordinator.swift b/Sources/RDReaderView/EPUBUI/ReaderController/RDEPUBReaderLocationCoordinator.swift index 9e9743a..6de74cf 100644 --- a/Sources/RDReaderView/EPUBUI/ReaderController/RDEPUBReaderLocationCoordinator.swift +++ b/Sources/RDReaderView/EPUBUI/ReaderController/RDEPUBReaderLocationCoordinator.swift @@ -25,7 +25,16 @@ final class RDEPUBReaderLocationCoordinator { return false } - if context.textBook == nil { + if context.bookPageMap != nil { + guard context.runtime?.prepareOnDemandChapter(forAbsolutePageNumber: targetPageNumber) == true else { + return false + } + _ = context.readingSession?.queueNavigation( + to: location, + relativeToSpineIndex: nil, + bookIdentifier: context.currentBookIdentifier + ) + } else if context.textBook == nil { _ = context.readingSession?.queueNavigation( to: location, relativeToSpineIndex: nil, @@ -44,7 +53,7 @@ final class RDEPUBReaderLocationCoordinator { let readerView = context.readerView else { return nil } - if context.textBook != nil, readerView.currentPage >= 0 { + if (context.textBook != nil || context.bookPageMap != nil), readerView.currentPage >= 0 { return controller.resolvedTextLocation(forPageNumber: readerView.currentPage + 1) } return context.readingSession?.currentReadingLocation(bookIdentifier: context.currentBookIdentifier) diff --git a/Sources/RDReaderView/EPUBUI/ReaderController/RDEPUBReaderPaginationCoordinator.swift b/Sources/RDReaderView/EPUBUI/ReaderController/RDEPUBReaderPaginationCoordinator.swift index 8f801fc..e3e14f0 100644 --- a/Sources/RDReaderView/EPUBUI/ReaderController/RDEPUBReaderPaginationCoordinator.swift +++ b/Sources/RDReaderView/EPUBUI/ReaderController/RDEPUBReaderPaginationCoordinator.swift @@ -4,56 +4,12 @@ import Foundation /// /// 职责: /// - 根据出版物类型(文本重排/Fixed Layout/Web 内容)选择分页策略 -/// - 后台构建文本图书模型并应用分页快照 +/// - 文本大书优先恢复分页摘要并切换到按需加载 /// - 重新分页时保持当前阅读位置 /// - 刷新可见内容并保持位置 /// - 重建外部纯文本图书 final class RDEPUBReaderPaginationCoordinator { private let backgroundInteractionCooldown: CFAbsoluteTime = 0.8 - private let backgroundChapterPause: TimeInterval = 0.04 - private let incrementalMergeChapterThreshold = 20 - private let stagedIncrementalApplyDelay: TimeInterval = 1.0 - private var stagedIncrementalApplyWorkItem: DispatchWorkItem? - private let stagedBookRequestLock = NSLock() - private var stagedBookRequest: StagedBookRequest? - - private struct QuickBuildState { - var quickWindow: [Int] - var chapterStore: IncrementalChapterStore - var book: RDEPUBTextBook - } - - private struct StagedBookRequest { - var chapterStore: IncrementalChapterStore - var orderedSpineIndices: [Int] - var restoreLocation: RDEPUBLocation? - var isComplete: Bool - } - - private final class IncrementalChapterStore { - private let lock = NSLock() - private var builtChapters: [Int: RDEPUBTextChapter] = [:] - - func insert(_ chapter: RDEPUBTextChapter, for spineIndex: Int) { - lock.lock() - builtChapters[spineIndex] = chapter - lock.unlock() - } - - func contains(_ spineIndex: Int) -> Bool { - lock.lock() - let contains = builtChapters[spineIndex] != nil - lock.unlock() - return contains - } - - func snapshot(orderedBy spineIndices: [Int]) -> [Int: RDEPUBTextChapter] { - lock.lock() - let chapters = builtChapters - lock.unlock() - return chapters - } - } private unowned let context: RDEPUBReaderContext @@ -61,7 +17,7 @@ final class RDEPUBReaderPaginationCoordinator { self.context = context } - /// 对出版物执行分页:文本重排走 TextBookBuilder,Fixed Layout 直接生成快照,Web 内容走 Paginator。 + /// 对出版物执行分页:文本重排优先走摘要恢复/按需加载,Fixed Layout 直接生成快照,Web 内容走 Paginator。 func paginatePublication(restoreLocation: RDEPUBLocation?) { guard let controller = context.controller, let parser = context.parser, @@ -78,7 +34,7 @@ final class RDEPUBReaderPaginationCoordinator { print("[EPUB][Pagination] profile=\(publication.readingProfile.rawValue) layout=\(publication.layout.rawValue) spine=\(publication.spine.count)") if publication.readingProfile == .textReflowable { - print("[EPUB][Pagination] path=text-reflowable-fast-entry") + print("[EPUB][Pagination] path=text-reflowable-on-demand") paginateTextPublication( parser: parser, publication: publication, @@ -122,10 +78,8 @@ final class RDEPUBReaderPaginationCoordinator { /// 应用文本图书模型:生成分页快照并完成分页流程。 func applyTextBook(_ textBook: RDEPUBTextBook, restoreLocation: RDEPUBLocation?) { guard let controller = context.controller else { return } - stagedIncrementalApplyWorkItem?.cancel() - stagedIncrementalApplyWorkItem = nil - clearStagedBookRequest() context.textBook = textBook + context.bookPageMap = nil let snapshot = controller.nativeTextSnapshot(from: textBook) context.replaceActiveSnapshot(snapshot) @@ -144,6 +98,7 @@ final class RDEPUBReaderPaginationCoordinator { ) { guard context.controller != nil else { return } context.textBook = nil + context.bookPageMap = nil context.replaceActiveSnapshot(snapshot) guard !snapshot.pages.isEmpty else { @@ -163,6 +118,7 @@ final class RDEPUBReaderPaginationCoordinator { readerView.reloadData() if let targetLocation = restoreLocation { controller.restoreReadingLocation(targetLocation) + context.readingSession?.transition(to: .idle) } else { readerView.transitionToPage(pageNum: 0) context.readingSession?.transition(to: .idle) @@ -203,7 +159,6 @@ final class RDEPUBReaderPaginationCoordinator { } } - /// 文本 EPUB 大书快速进入:先构建目标章节进入阅读器,再后台补齐完整 TextBook。 private func paginateTextPublication( parser: RDEPUBParser, publication: RDEPUBPublication, @@ -212,184 +167,136 @@ final class RDEPUBReaderPaginationCoordinator { token: UUID ) { guard let controller = context.controller else { return } + let context = self.context let pageSize = controller.currentTextPageSize() context.lastTextPaginationPageSize = pageSize - let layoutConfig = controller.currentTextLayoutConfig(pageSize: pageSize) - let builder = context.makeTextBookBuilder(layoutConfig: layoutConfig) - let renderStyle = controller.currentTextRenderStyle() - let quickSpineCandidates = prioritizedBuildableSpineIndices( - publication: publication, - readingSession: readingSession, - restoreLocation: restoreLocation - ) DispatchQueue.global(qos: .utility).async { [weak controller] in guard controller != nil else { return } - var didApplyQuickChapter = false + guard context.controller != nil else { return } + if let restoredPageMap = self.restoreBookPageMapIfPossible(publication: publication) { + DispatchQueue.main.async { + guard context.paginationToken == token, + context.controller != nil else { return } + context.runtime?.applyBookPageMap(restoredPageMap, restoreLocation: restoreLocation) + } + return + } + + let prioritizedCandidates = self.prioritizedBuildableSpineIndices( + publication: publication, + readingSession: readingSession, + restoreLocation: restoreLocation + ) + guard prioritizedCandidates.first != nil else { + DispatchQueue.main.async { + guard context.paginationToken == token, + context.controller != nil else { return } + context.handle(error: RDEPUBParserError.emptySpine) + } + return + } do { - self.waitForReadingInteractionToSettle() - let allBuildableIndices = self.allBuildableSpineIndices(in: publication) - let quickBuildState = try self.buildQuickTextBook( - builder: builder, - parser: parser, - publication: publication, - pageSize: pageSize, - style: renderStyle, - prioritizedCandidates: quickSpineCandidates + guard context.controller != nil, + let runtime = context.runtime else { return } + RDEPUBBackgroundTrace.log( + "QuickOpen", + "begin token=\(token.uuidString) candidates=\(prioritizedCandidates.count) restoreSpine=\(readingSession.initialSpineIndex(for: restoreLocation))" ) - - if let quickBook = quickBuildState?.book { - DispatchQueue.main.sync { - guard self.context.paginationToken == token else { return } - didApplyQuickChapter = true - self.context.runtime?.applyTextBook(quickBook, restoreLocation: restoreLocation) - } + let runtimeChapter = try RDEPUBBackgroundTrace.measure( + "QuickOpen", + "loadFirstRenderableRuntimeChapter" + ) { + try self.loadFirstRenderableRuntimeChapter( + prioritizedSpineIndices: prioritizedCandidates, + runtime: runtime + ) } - - let chapterStore = quickBuildState?.chapterStore ?? IncrementalChapterStore() - var pendingIncrementalCount = 0 - let incrementalBuildOrder = self.incrementalBuildOrder( - allBuildableIndices: allBuildableIndices, - quickWindow: quickBuildState?.quickWindow ?? [] - ) - - for spineIndex in incrementalBuildOrder where !chapterStore.contains(spineIndex) { - self.waitForReadingInteractionToSettle() - guard let result = try builder.buildChapter( - parser: parser, + let quickWindowChapters = try RDEPUBBackgroundTrace.measure( + "QuickOpen", + "loadInitialRuntimeChapters anchorSpine=\(runtimeChapter.spineIndex)" + ) { + try self.loadInitialRuntimeChapters( + anchorSpineIndex: runtimeChapter.spineIndex, publication: publication, - spineIndex: spineIndex, - pageSize: pageSize, - style: renderStyle - ) else { - continue - } - - chapterStore.insert(result.chapter, for: spineIndex) - pendingIncrementalCount += 1 - Thread.sleep(forTimeInterval: self.backgroundChapterPause) - - if pendingIncrementalCount >= self.incrementalMergeChapterThreshold { - pendingIncrementalCount = 0 - self.stageBookRequest( - chapterStore: chapterStore, - orderedSpineIndices: allBuildableIndices, - restoreLocation: restoreLocation, - isComplete: false - ) - DispatchQueue.main.async { - guard self.context.paginationToken == token else { return } - self.scheduleStagedIncrementalTextBookApplication() - } - } + runtime: runtime + ) } - - self.stageBookRequest( - chapterStore: chapterStore, - orderedSpineIndices: allBuildableIndices, - restoreLocation: restoreLocation, - isComplete: true + RDEPUBBackgroundTrace.log( + "QuickOpen", + "ready anchorSpine=\(runtimeChapter.spineIndex) quickWindow=\(quickWindowChapters.map { $0.spineIndex }) pages=\(quickWindowChapters.reduce(0) { $0 + $1.pages.count })" ) + DispatchQueue.main.async { - guard self.context.paginationToken == token else { return } - self.scheduleStagedIncrementalTextBookApplication() + guard context.paginationToken == token, + context.controller != nil else { return } + runtime.chapterRuntimeStore.setCurrentChapter( + spineIndex: runtimeChapter.spineIndex, + totalSpineCount: publication.spine.count + ) + let partialMap = self.makePartialPageMap(from: quickWindowChapters) + runtime.applyBookPageMap(partialMap, restoreLocation: restoreLocation) + self.paginateMetadataOnly(token: token, restoreLocation: restoreLocation) } } catch { DispatchQueue.main.async { - guard self.context.paginationToken == token else { return } - if didApplyQuickChapter { - self.context.isRepaginating = false - self.context.hideLoading() - } else { - self.context.handle(error: error) - } + guard context.paginationToken == token, + context.controller != nil else { return } + context.handle(error: error) } } } } - private func buildQuickTextBook( - builder: RDEPUBTextBookBuilder, - parser: RDEPUBParser, - publication: RDEPUBPublication, - pageSize: CGSize, - style: RDEPUBTextRenderStyle, - prioritizedCandidates: [Int] - ) throws -> QuickBuildState? { - var anchorResult: RDEPUBTextChapterBuildResult? - for spineIndex in prioritizedCandidates { - guard let result = try builder.buildChapter( - parser: parser, - publication: publication, - spineIndex: spineIndex, - pageSize: pageSize, - style: style - ), !result.chapter.pages.isEmpty else { - continue + private func loadFirstRenderableRuntimeChapter( + prioritizedSpineIndices: [Int], + runtime: RDEPUBReaderRuntime + ) throws -> RDEPUBRuntimeChapter { + var lastError: Error? + for spineIndex in prioritizedSpineIndices { + do { + return try runtime.chapterLoader.loadChapterSynchronouslyForMigration( + spineIndex: spineIndex, + store: runtime.chapterRuntimeStore + ) + } catch { + lastError = error + RDEPUBBackgroundTrace.log("QuickOpen", "skip spine=\(spineIndex) reason=\(error)") } - anchorResult = result - break } + throw lastError ?? RDEPUBParserError.emptySpine + } - guard let anchorResult else { - return nil - } - - let quickWindow = quickWindowSpineIndices( - around: anchorResult.chapter.spineIndex, + private func loadInitialRuntimeChapters( + anchorSpineIndex: Int, + publication: RDEPUBPublication, + runtime: RDEPUBReaderRuntime + ) throws -> [RDEPUBRuntimeChapter] { + let windowSpineIndices = initialWindowSpineIndices( + around: anchorSpineIndex, in: publication ) - - let chapterStore = IncrementalChapterStore() - for spineIndex in quickWindow { - let result: RDEPUBTextChapterBuildResult? - if spineIndex == anchorResult.chapter.spineIndex { - result = anchorResult - } else { - result = try builder.buildChapter( - parser: parser, - publication: publication, + var chapters: [RDEPUBRuntimeChapter] = [] + for spineIndex in windowSpineIndices { + do { + let chapter = try runtime.chapterLoader.loadChapterSynchronouslyForMigration( spineIndex: spineIndex, - pageSize: pageSize, - style: style + store: runtime.chapterRuntimeStore ) + chapters.append(chapter) + } catch { + if spineIndex == anchorSpineIndex { + throw error + } + RDEPUBBackgroundTrace.log("QuickOpen", "skip adjacent spine=\(spineIndex) reason=\(error)") } - - guard let chapter = result?.chapter, - !chapter.pages.isEmpty else { - continue - } - chapterStore.insert(chapter, for: spineIndex) } - - guard let book = textBook(from: chapterStore.snapshot(orderedBy: quickWindow), orderedBy: quickWindow) else { - return nil - } - - print("[EPUB][Pagination] quick-book chapters=\(book.chapters.count) pages=\(book.pages.count) anchor=\(anchorResult.chapter.href)") - return QuickBuildState( - quickWindow: quickWindow, - chapterStore: chapterStore, - book: book - ) + return chapters } - private func prioritizedBuildableSpineIndices( - publication: RDEPUBPublication, - readingSession: RDEPUBReadingSession, - restoreLocation: RDEPUBLocation? - ) -> [Int] { - let preferred = readingSession.initialSpineIndex(for: restoreLocation) - return publication.spine.indices - .filter { isBuildableTextSpine(at: $0, in: publication) } - .sorted { lhs, rhs in - abs(lhs - preferred) < abs(rhs - preferred) - } - } - - private func quickWindowSpineIndices( + private func initialWindowSpineIndices( around anchorSpineIndex: Int, in publication: RDEPUBPublication, maxChapterCount: Int = 3 @@ -399,7 +306,7 @@ final class RDEPUBReaderPaginationCoordinator { return [anchorSpineIndex] } - var selected: [Int] = [anchorSpineIndex] + var selected = [anchorSpineIndex] var nextPosition = anchorPosition + 1 var previousPosition = anchorPosition - 1 @@ -422,131 +329,209 @@ final class RDEPUBReaderPaginationCoordinator { return selected } + private func makePartialPageMap(from chapters: [RDEPUBRuntimeChapter]) -> RDEPUBBookPageMap { + var builder = RDEPUBBookPageMap.Builder() + for chapter in chapters { + builder.add( + spineIndex: chapter.spineIndex, + href: chapter.href, + title: chapter.title, + pageCount: chapter.pages.count, + fragmentOffsets: chapter.chapterOffsetMap.fragmentOffsets + ) + } + return builder.build() + } + + private func prioritizedBuildableSpineIndices( + publication: RDEPUBPublication, + readingSession: RDEPUBReadingSession, + restoreLocation: RDEPUBLocation? + ) -> [Int] { + let preferred = readingSession.initialSpineIndex(for: restoreLocation) + return publication.spine.indices + .filter { isBuildableTextSpine(at: $0, in: publication) } + .sorted { lhs, rhs in + abs(lhs - preferred) < abs(rhs - preferred) + } + } + private func allBuildableSpineIndices(in publication: RDEPUBPublication) -> [Int] { publication.spine.indices.filter { isBuildableTextSpine(at: $0, in: publication) } } - func scheduleStagedIncrementalTextBookApplication() { - stagedIncrementalApplyWorkItem?.cancel() - let workItem = DispatchWorkItem { [weak self] in - self?.applyStagedIncrementalTextBookIfPossible() - } - stagedIncrementalApplyWorkItem = workItem - DispatchQueue.main.asyncAfter(deadline: .now() + stagedIncrementalApplyDelay, execute: workItem) - } - - private func applyStagedIncrementalTextBookIfPossible() { - guard context.secondsSinceLastUserNavigation() >= backgroundInteractionCooldown, - !context.isRepaginating, - context.readingSession?.navigatorState == .idle else { - scheduleStagedIncrementalTextBookApplication() - return - } - - guard let staged = consumeStagedBookRequest() else { - stagedIncrementalApplyWorkItem = nil - return - } - - stagedIncrementalApplyWorkItem = nil - guard let stagedBook = textBook( - from: staged.chapterStore.snapshot(orderedBy: staged.orderedSpineIndices), - orderedBy: staged.orderedSpineIndices - ) else { - return - } - let restoreLocation = context.currentVisibleLocation() ?? staged.restoreLocation - let logPrefix = staged.isComplete ? "full-book" : "applied-staged-book" - print("[EPUB][Pagination] \(logPrefix) chapters=\(stagedBook.chapters.count) pages=\(stagedBook.pages.count)") - context.runtime?.applyTextBook(stagedBook, restoreLocation: restoreLocation) - } - - private func waitForReadingInteractionToSettle() { - while context.secondsSinceLastUserNavigation() < backgroundInteractionCooldown { + private func waitForReadingInteractionToSettle(using context: RDEPUBReaderContext) { + while context.controller != nil, + context.secondsSinceLastUserNavigation() < backgroundInteractionCooldown { Thread.sleep(forTimeInterval: 0.08) } } - private func incrementalBuildOrder( - allBuildableIndices: [Int], - quickWindow: [Int] - ) -> [Int] { - guard let firstWindowIndex = quickWindow.first, - let lastWindowIndex = quickWindow.last else { - return allBuildableIndices - } - - let forward = allBuildableIndices.filter { $0 > lastWindowIndex } - let backward = allBuildableIndices.filter { $0 < firstWindowIndex } - return quickWindow + forward + backward - } - - private func textBook( - from builtChapters: [Int: RDEPUBTextChapter], - orderedBy spineIndices: [Int] - ) -> RDEPUBTextBook? { - var chapters: [RDEPUBTextChapter] = [] - var pages: [RDEPUBTextPage] = [] - - for spineIndex in spineIndices { - guard var chapter = builtChapters[spineIndex], - !chapter.pages.isEmpty else { - continue - } - - chapter.chapterIndex = chapters.count - let normalizedPages = chapter.pages.enumerated().map { localPageIndex, page -> RDEPUBTextPage in - var page = page - page.absolutePageIndex = pages.count + localPageIndex - page.chapterIndex = chapters.count - page.pageIndexInChapter = localPageIndex - page.totalPagesInChapter = chapter.pages.count - return page - } - chapter.pages = normalizedPages - chapters.append(chapter) - pages.append(contentsOf: normalizedPages) - } - - guard !pages.isEmpty else { - return nil - } - return RDEPUBTextBook(chapters: chapters, pages: pages) - } - - private func stageBookRequest( - chapterStore: IncrementalChapterStore, - orderedSpineIndices: [Int], - restoreLocation: RDEPUBLocation?, - isComplete: Bool - ) { - stagedBookRequestLock.lock() - stagedBookRequest = StagedBookRequest( - chapterStore: chapterStore, - orderedSpineIndices: orderedSpineIndices, - restoreLocation: restoreLocation, - isComplete: isComplete - ) - stagedBookRequestLock.unlock() - } - - private func consumeStagedBookRequest() -> StagedBookRequest? { - stagedBookRequestLock.lock() - defer { stagedBookRequestLock.unlock() } - let request = stagedBookRequest - stagedBookRequest = nil - return request - } - - private func clearStagedBookRequest() { - stagedBookRequestLock.lock() - stagedBookRequest = nil - stagedBookRequestLock.unlock() - } - private func isBuildableTextSpine(at index: Int, in publication: RDEPUBPublication) -> Bool { guard publication.spine.indices.contains(index) else { return false } let item = publication.spine[index] return item.linear && (item.mediaType.contains("html") || item.mediaType.contains("xhtml")) } + + // MARK: - 元数据专用解析(Phase 0) + + /// 后台遍历所有章节,只提取轻量元数据(pageCount、pageRanges、fragmentOffsets), + /// 写入磁盘摘要缓存,不累积 RDEPUBTextBook。 + func paginateMetadataOnly(token: UUID, restoreLocation: RDEPUBLocation?) { + let context = self.context + guard let parser = context.parser, + let publication = context.publication else { return } + + let pageSize = context.currentTextPageSize() + let layoutConfig = context.currentTextLayoutConfig(pageSize: pageSize) + let builder = context.makeTextBookBuilder(layoutConfig: layoutConfig) + let style = context.currentTextRenderStyle() + let allBuildableIndices = allBuildableSpineIndices(in: publication) + let summaryDiskCache = context.runtime?.summaryDiskCache + + DispatchQueue.global(qos: .utility).async { [weak self] in + guard let self else { return } + guard context.controller != nil else { return } + let catalog = allBuildableIndices.map { spineIndex in + let item = publication.spine[spineIndex] + return ( + key: context.chapterCacheKey(forSpineIndex: spineIndex), + spineIndex: spineIndex, + href: item.href, + title: item.title + ) + } + let restored = summaryDiskCache?.readAll(keys: catalog) + RDEPUBBackgroundTrace.log( + "MetadataParse", + "begin token=\(token.uuidString) buildableChapters=\(allBuildableIndices.count)" + ) + var mapBuilder = restored?.mapBuilder ?? RDEPUBBookPageMap.Builder() + var lastAppliedCount = 0 + let cachedSpineIndices = Set((restored?.summaries ?? [:]).keys) + + if !cachedSpineIndices.isEmpty { + RDEPUBBackgroundTrace.log( + "MetadataParse", + "resumeFromCache cachedChapters=\(cachedSpineIndices.count) total=\(allBuildableIndices.count)" + ) + let cachedMap = mapBuilder.build() + DispatchQueue.main.async { + guard context.paginationToken == token, + context.controller != nil else { return } + context.runtime?.refreshBookPageMapInPlace(cachedMap) + } + } + + for (offset, spineIndex) in allBuildableIndices.enumerated() { + guard context.controller != nil, + context.paginationToken == token else { + RDEPUBBackgroundTrace.log("MetadataParse", "abort: reader dismissed or token changed") + return + } + if cachedSpineIndices.contains(spineIndex) { + continue + } + self.waitForReadingInteractionToSettle(using: context) + do { + RDEPUBBackgroundTrace.log("MetadataParse", "正在解析 spine=\(spineIndex) \(offset + 1)/\(allBuildableIndices.count)") + let lightweightEntry = try RDEPUBBackgroundTrace.measure( + "MetadataParse", + "spine=\(spineIndex)" + ) { + try autoreleasepool { () -> RDEPUBBookPageMapEntry? in + guard let result = try builder.buildChapter( + parser: parser, + publication: publication, + spineIndex: spineIndex, + pageSize: pageSize, + style: style + ) else { + return nil + } + + let chapter = result.chapter + let cacheKey = context.chapterCacheKey(forSpineIndex: spineIndex) + let summary = RDEPUBChapterSummary( + pageRanges: chapter.pages.map { .init(location: $0.contentRange.location, length: $0.contentRange.length) }, + pageCount: chapter.pages.count, + fragmentOffsets: chapter.fragmentOffsets, + renderSignature: cacheKey.renderSignature, + schemaVersion: RDEPUBChapterSummary.currentSchemaVersion, + chapterContentHash: cacheKey.chapterContentHash, + pageMetadataList: chapter.pages.map { .from($0.metadata) } + ) + summaryDiskCache?.writeSynchronously(summary: summary, for: cacheKey) + + return RDEPUBBookPageMapEntry( + spineIndex: spineIndex, + href: chapter.href, + title: chapter.title, + pageCount: chapter.pages.count, + absolutePageStart: 0, + fragmentOffsets: chapter.fragmentOffsets + ) + } + } + + if let lightweightEntry { + mapBuilder.add( + spineIndex: lightweightEntry.spineIndex, + href: lightweightEntry.href, + title: lightweightEntry.title, + pageCount: lightweightEntry.pageCount, + fragmentOffsets: lightweightEntry.fragmentOffsets + ) + let builtCount = offset + 1 + if builtCount - lastAppliedCount >= 16 || builtCount == allBuildableIndices.count { + lastAppliedCount = builtCount + let partialMap = mapBuilder.build() + DispatchQueue.main.async { + guard context.paginationToken == token, + context.controller != nil else { return } + context.runtime?.refreshBookPageMapInPlace(partialMap) + } + } + } + } catch { + RDEPUBBackgroundTrace.log("MetadataParse", "buildChapter FAILED: spine=\(spineIndex) error=\(error)") + } + } + + let pageMap = mapBuilder.build() + RDEPUBBackgroundTrace.log( + "MetadataParse", + "complete chapters=\(pageMap.totalChapters) pages=\(pageMap.totalPages)" + ) + + DispatchQueue.main.async { + guard context.paginationToken == token, + context.controller != nil else { return } + context.runtime?.refreshBookPageMapInPlace(pageMap) + } + } + } + + private func restoreBookPageMapIfPossible(publication: RDEPUBPublication) -> RDEPUBBookPageMap? { + guard let summaryDiskCache = context.runtime?.summaryDiskCache else { + return nil + } + let catalog = allBuildableSpineIndices(in: publication).map { spineIndex in + let item = publication.spine[spineIndex] + return ( + key: context.chapterCacheKey(forSpineIndex: spineIndex), + spineIndex: spineIndex, + href: item.href, + title: item.title + ) + } + guard summaryDiskCache.isCacheComplete(keys: catalog.map(\.key)) else { + return nil + } + let restored = summaryDiskCache.readAll(keys: catalog) + guard restored.summaries.count == catalog.count else { + return nil + } + return restored.mapBuilder.build() + } } diff --git a/Sources/RDReaderView/EPUBUI/ReaderController/RDEPUBReaderRuntime.swift b/Sources/RDReaderView/EPUBUI/ReaderController/RDEPUBReaderRuntime.swift index 9003576..acc63cb 100644 --- a/Sources/RDReaderView/EPUBUI/ReaderController/RDEPUBReaderRuntime.swift +++ b/Sources/RDReaderView/EPUBUI/ReaderController/RDEPUBReaderRuntime.swift @@ -6,6 +6,15 @@ import UIKit final class RDEPUBReaderRuntime { private unowned let context: RDEPUBReaderContext + lazy var chapterRuntimeStore = RDEPUBChapterRuntimeStore() + lazy var summaryDiskCache = context.makeChapterSummaryDiskCache() + lazy var chapterLoader: RDEPUBChapterLoader = { + let loader = RDEPUBChapterLoader(context: context) + loader.setSummaryDiskCache(summaryDiskCache) + return loader + }() + lazy var pageResolver = RDEPUBPageResolver(context: context, store: chapterRuntimeStore) + lazy var loadCoordinator = RDEPUBReaderLoadCoordinator(context: context) lazy var paginationCoordinator = RDEPUBReaderPaginationCoordinator(context: context) lazy var locationCoordinator = RDEPUBReaderLocationCoordinator(context: context) @@ -42,9 +51,11 @@ final class RDEPUBReaderRuntime { context.clearActiveSnapshot() context.readingSession = nil context.textBook = nil + context.bookPageMap = nil context.activeBookmarks = [] context.activeHighlights = [] context.searchState = nil + clearOnDemandPageModeState() viewportMonitor.resetForReload() annotationCoordinator.updateCurrentSelection(nil) readerView.reloadData() @@ -84,6 +95,17 @@ final class RDEPUBReaderRuntime { return true } + if context.bookPageMap != nil { + guard prepareOnDemandChapter(forAbsolutePageNumber: pageNumber) else { + return false + } + readerView.transitionToPage(pageNum: pageNumber - 1, animated: animated) + if let location = locationCoordinator.currentVisibleLocation() { + context.persist(location: location) + } + return true + } + guard context.activePages.indices.contains(pageNumber - 1) else { return false } @@ -282,6 +304,40 @@ final class RDEPUBReaderRuntime { paginationCoordinator.applyPaginationSnapshot(snapshot, restoreLocation: restoreLocation) } + func applyBookPageMap(_ bookPageMap: RDEPUBBookPageMap, restoreLocation: RDEPUBLocation?) { + context.textBook = nil + context.bookPageMap = bookPageMap + context.replaceActiveSnapshot(makeSnapshot(from: bookPageMap)) + paginationCoordinator.finishPagination(restoreLocation: restoreLocation) + } + + func refreshBookPageMapInPlace(_ bookPageMap: RDEPUBBookPageMap) { + guard let readerView = context.readerView, + let controller = context.controller else { return } + + let currentPage = max(readerView.currentPage, 0) + let currentLocation = locationCoordinator.currentVisibleLocation() + context.textBook = nil + context.bookPageMap = bookPageMap + context.replaceActiveSnapshot(makeSnapshot(from: bookPageMap)) + + // 仅刷新总页数,不重建页面内容(避免后台元数据解析期间刷新掉用户选区) + readerView.reloadPageCountOnly() + + // 用户正在选区时跳过页面跳转,避免打断选区 + if context.currentSelection == nil, bookPageMap.totalPages > 0 { + readerView.transitionToPage( + pageNum: min(currentPage, max(bookPageMap.totalPages - 1, 0)), + animated: false + ) + } + if let currentLocation { + locationCoordinator.persist(location: currentLocation) + } else if let resolvedLocation = controller.resolvedTextLocation(forPageNumber: currentPage + 1) { + locationCoordinator.persist(location: resolvedLocation) + } + } + /// 完成分页流程并恢复阅读位置 func finishPagination(restoreLocation: RDEPUBLocation?) { paginationCoordinator.finishPagination(restoreLocation: restoreLocation) @@ -325,4 +381,174 @@ final class RDEPUBReaderRuntime { ) { viewportMonitor.handleViewportChangeIfNeeded(reason: reason, viewportSignature: viewportSignature) } + + @discardableResult + func prepareOnDemandChapter(forAbsolutePageNumber pageNumber: Int) -> Bool { + guard let bookPageMap = context.bookPageMap, + let publication = context.publication else { + return false + } + let absolutePageIndex = pageNumber - 1 + guard let spineIndex = bookPageMap.spineIndex(forAbsolutePage: absolutePageIndex) else { + return false + } + + chapterRuntimeStore.setCurrentChapter( + spineIndex: spineIndex, + totalSpineCount: publication.spine.count + ) + RDEPUBBackgroundTrace.log( + "Runtime", + "prepareOnDemandChapter page=\(pageNumber) absoluteIndex=\(absolutePageIndex) spine=\(spineIndex)" + ) + + if chapterRuntimeStore.chapterData(for: spineIndex) == nil { + do { + _ = try chapterLoader.loadChapterSynchronouslyForMigration( + spineIndex: spineIndex, + store: chapterRuntimeStore + ) + } catch { + RDEPUBBackgroundTrace.log("Runtime", "prepareOnDemandChapter FAILED: spine=\(spineIndex) error=\(error)") + return false + } + } + + for evictable in chapterRuntimeStore.evictableSpineIndices() { + chapterRuntimeStore.evict(spineIndex: evictable) + } + + let adjacent = [spineIndex - 1, spineIndex + 1].filter { publication.spine.indices.contains($0) } + for adjacentSpineIndex in adjacent where chapterRuntimeStore.chapterData(for: adjacentSpineIndex) == nil { + chapterRuntimeStore.addPrefetchTarget(adjacentSpineIndex) + RDEPUBBackgroundTrace.log( + "Runtime", + "schedule prefetch currentSpine=\(spineIndex) adjacentSpine=\(adjacentSpineIndex)" + ) + chapterLoader.loadChapter( + spineIndex: adjacentSpineIndex, + store: chapterRuntimeStore, + priority: .prefetch + ) { _ in } + } + return true + } + + func extendPartialBookPageMapIfNeeded(currentPageNumber: Int, minimumTrailingPages: Int = 2, batchChapterCount: Int = 3) { + guard let publication = context.publication, + let currentMap = context.bookPageMap, + let readerView = context.readerView else { + return + } + + let buildableSpineIndices = publication.spine.indices.filter { + let item = publication.spine[$0] + return item.linear && (item.mediaType.contains("html") || item.mediaType.contains("xhtml")) + } + guard currentMap.totalChapters < buildableSpineIndices.count else { + return + } + guard currentMap.totalPages - currentPageNumber <= minimumTrailingPages else { + return + } + + let lastKnownSpineIndex = currentMap.entries.last?.spineIndex ?? -1 + let nextSpineIndices = buildableSpineIndices.filter { $0 > lastKnownSpineIndex }.prefix(batchChapterCount) + guard !nextSpineIndices.isEmpty else { + return + } + RDEPUBBackgroundTrace.log( + "Runtime", + "extendPartialBookPageMap currentPage=\(currentPageNumber) totalPages=\(currentMap.totalPages) appendSpines=\(Array(nextSpineIndices))" + ) + + var appendedEntries: [RDEPUBBookPageMapEntry] = [] + for spineIndex in nextSpineIndices { + do { + let chapter = try chapterLoader.loadChapterSynchronouslyForMigration( + spineIndex: spineIndex, + store: chapterRuntimeStore + ) + appendedEntries.append( + RDEPUBBookPageMapEntry( + spineIndex: chapter.spineIndex, + href: chapter.href, + title: chapter.title, + pageCount: chapter.pages.count, + absolutePageStart: 0, + fragmentOffsets: chapter.chapterOffsetMap.fragmentOffsets + ) + ) + } catch { + RDEPUBBackgroundTrace.log("Runtime", "extendPartialBookPageMap skip spine=\(spineIndex) error=\(error)") + } + } + + guard !appendedEntries.isEmpty else { + return + } + + let combinedEntries = (currentMap.entries.map { + RDEPUBBookPageMapEntry( + spineIndex: $0.spineIndex, + href: $0.href, + title: $0.title, + pageCount: $0.pageCount, + absolutePageStart: 0, + fragmentOffsets: $0.fragmentOffsets + ) + } + appendedEntries).sorted { $0.spineIndex < $1.spineIndex } + + var absolutePageStart = 0 + let normalizedEntries = combinedEntries.map { entry -> RDEPUBBookPageMapEntry in + let normalized = RDEPUBBookPageMapEntry( + spineIndex: entry.spineIndex, + href: entry.href, + title: entry.title, + pageCount: entry.pageCount, + absolutePageStart: absolutePageStart, + fragmentOffsets: entry.fragmentOffsets + ) + absolutePageStart += entry.pageCount + return normalized + } + + let newMap = RDEPUBBookPageMap(entries: normalizedEntries) + RDEPUBBackgroundTrace.log( + "Runtime", + "extendPartialBookPageMap applied chapters=\(newMap.totalChapters) pages=\(newMap.totalPages)" + ) + context.bookPageMap = newMap + context.replaceActiveSnapshot(makeSnapshot(from: newMap)) + readerView.reloadData() + readerView.transitionToPage(pageNum: max(currentPageNumber - 1, 0), animated: false) + } + + func clearOnDemandPageModeState() { + chapterRuntimeStore.invalidateAllForSettingsChange() + context.bookPageMap = nil + } + + private func makeSnapshot(from bookPageMap: RDEPUBBookPageMap) -> RDEPUBReadingSession.PaginationSnapshot { + let pages = bookPageMap.entries.flatMap { entry in + (0..