// RDEPUBTextPositionConverter.swift // EPUB 全书文本位置转换器,在多种坐标体系之间进行双向映射。 import Foundation /// 对标 WXRead `WREpubPositionConverter` 的全书位置转换器。 /// /// 负责在四套坐标之间做双向转换: /// - `(fileIndex, row, column)` 文件级语义锚点 /// - 章节内字符偏移 /// - 全书字符偏移 /// - 页码 / `RDEPUBLocation` public struct RDEPUBTextPositionConverter { /// 当前转换器所关联的 EPUB 文本对象。 public let book: RDEPUBTextBook /// 创建指定 EPUB 文本对象的位置转换器。 /// - Parameter book: 要关联的 EPUB 文本对象。 public init(book: RDEPUBTextBook) { self.book = book } /// 全书字符总数(跨所有文件累计)。 public var totalCharacterCount: Int { book.indexTable.totalCharacterCount } /// 将语义锚点转换为全书字符偏移。 /// - Parameter anchor: 由文件索引、行号、列号组成的语义锚点。 /// - Returns: 对应的全书字符偏移量。 public func globalIndex(for anchor: RDEPUBTextAnchor) -> Int { book.indexTable.globalIndex(for: anchor) } /// 将语义范围锚点转换为全书字符范围。 /// - Parameter rangeAnchor: 由起止锚点组成的语义范围。 /// - Returns: 对应的全书字符范围(`NSRange`)。 public func globalRange(for rangeAnchor: RDEPUBTextRangeAnchor) -> NSRange { book.indexTable.globalRange(for: rangeAnchor) } /// 根据全书字符偏移查找所属文件的索引。 /// - Parameter index: 全书字符偏移量。 /// - Returns: 对应的文件索引,超出范围时返回 `nil`。 public func fileIndex(forCharacterPosition index: Int) -> Int? { book.indexTable.fileIndex(forCharacterPosition: index) } /// 将全书字符偏移转换为指定文件内的本地偏移。 /// - Parameters: /// - fileIndex: 目标文件索引。 /// - index: 全书字符偏移量。 /// - Returns: 文件内的本地字符偏移,越界时返回 `nil`。 public func localOffsetInFile(at fileIndex: Int, forGlobalPosition index: Int) -> Int? { book.indexTable.localOffsetInFile(at: fileIndex, forGlobalPosition: index) } /// 将全书字符偏移转换为语义锚点。 /// - Parameter index: 全书字符偏移量。 /// - Returns: 对应的语义锚点,无效位置时返回 `nil`。 public func anchor(forCharacterPosition index: Int) -> RDEPUBTextAnchor? { book.indexTable.anchor(forGlobalIndex: index) } /// 将 `RDEPUBLocation` 转换为语义锚点。 /// - Parameter location: EPUB 位置对象。 /// - Returns: 对应的语义锚点,无法映射时返回 `nil`。 public func anchor(for location: RDEPUBLocation) -> RDEPUBTextAnchor? { book.indexTable.anchor(for: location) } /// 根据语义锚点获取页码(从 1 开始)。 /// - Parameter anchor: 由文件索引、行号、列号组成的语义锚点。 /// - Returns: 对应的页码,无法定位时返回 `nil`。 public func pageNumber(for anchor: RDEPUBTextAnchor) -> Int? { book.indexTable.pageNumber(for: anchor, in: book).map { $0 + 1 } } /// 根据全书字符偏移获取页码(从 1 开始)。 /// - Parameter index: 全书字符偏移量。 /// - Returns: 对应的页码,无效位置时返回 `nil`。 public func pageNumber(forCharacterPosition index: Int) -> Int? { guard let anchor = anchor(forCharacterPosition: index) else { return nil } return pageNumber(for: anchor) } /// 将语义锚点转换为可序列化的 `RDEPUBLocation`。 /// - Parameters: /// - anchor: 由文件索引、行号、列号组成的语义锚点。 /// - bookIdentifier: 书籍标识符,会写入 location 的 `bookId` 字段。 /// - Returns: 对应的 `RDEPUBLocation`,无法映射时返回 `nil`。 public func location( for anchor: RDEPUBTextAnchor, bookIdentifier: String? ) -> RDEPUBLocation? { guard let chapter = book.chapters.first(where: { $0.spineIndex == anchor.fileIndex }) else { return nil } return book.indexTable.location(for: anchor, in: chapter, bookIdentifier: bookIdentifier) } /// 将语义范围锚点转换为可序列化的 `RDEPUBLocation`。 /// - Parameters: /// - rangeAnchor: 由起止锚点组成的语义范围。 /// - bookIdentifier: 书籍标识符,会写入 location 的 `bookId` 字段。 /// - Returns: 对应的 `RDEPUBLocation`,无法映射时返回 `nil`。 public func location( for rangeAnchor: RDEPUBTextRangeAnchor, bookIdentifier: String? ) -> RDEPUBLocation? { guard let chapter = book.chapters.first(where: { $0.spineIndex == rangeAnchor.start.fileIndex }) else { return nil } return book.indexTable.location(for: rangeAnchor, in: chapter, bookIdentifier: bookIdentifier) } }