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