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) } } }