- 给全部 78 个 Swift 源文件添加详细的中文注释(文件级、类级、方法级) - 删除 LegacyRDReaderController/ 死代码目录(16 文件 4592 行) - 根目录翻页容器文件移入 ReaderView/ 目录 - Resources/ 移入 EPUBCore/Resources/(与使用者归属一致) - RDEPUBTextIndexTable.swift 移入 EPUBTextRendering/(消除反向依赖) - RDURLReaderController.swift 移入 EPUBUI/(入口控制器归入 UI 层) - 更新 podspec 资源路径
87 lines
3.1 KiB
Swift
87 lines
3.1 KiB
Swift
// RDEPUBTextAnchor.swift
|
||
// EPUB 文本锚点和范围锚点
|
||
// 定义精确的文本位置锚点(RDEPUBTextAnchor,含 fileIndex/row/column/chapterOffset/fragmentID),
|
||
// 以及文本范围锚点(RDEPUBTextRangeAnchor,起止锚点对)。
|
||
// 支持 Codable 序列化(兼容 spineIndex/fileIndex 两种 key)。
|
||
|
||
import Foundation
|
||
|
||
/// 文本锚点,精确定位到 EPUB 中的某个字符位置
|
||
/// 包含文件索引、行号、列号、章节内偏移和可选的 fragment ID
|
||
public struct RDEPUBTextAnchor: Codable, Equatable {
|
||
/// 文件索引(等同于 spineIndex)
|
||
public let fileIndex: Int
|
||
/// 行号
|
||
public let row: Int
|
||
/// 列号(行内偏移)
|
||
public let column: Int
|
||
/// 章节内的绝对字符偏移量
|
||
public let chapterOffset: Int
|
||
/// 最近的 fragment ID(用于 URL 锚点定位)
|
||
public let fragmentID: String?
|
||
|
||
/// spine 索引的别名(与 fileIndex 等价)
|
||
public var spineIndex: Int { fileIndex }
|
||
|
||
public init(
|
||
fileIndex: Int,
|
||
row: Int,
|
||
column: Int,
|
||
chapterOffset: Int,
|
||
fragmentID: String? = nil
|
||
) {
|
||
self.fileIndex = fileIndex
|
||
self.row = row
|
||
self.column = column
|
||
self.chapterOffset = chapterOffset
|
||
self.fragmentID = fragmentID
|
||
}
|
||
|
||
enum CodingKeys: String, CodingKey {
|
||
case fileIndex
|
||
case spineIndex
|
||
case row
|
||
case column
|
||
case chapterOffset
|
||
case fragmentID
|
||
}
|
||
|
||
public init(from decoder: Decoder) throws {
|
||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||
let decodedFileIndex = try container.decodeIfPresent(Int.self, forKey: .fileIndex)
|
||
?? container.decode(Int.self, forKey: .spineIndex)
|
||
self.fileIndex = decodedFileIndex
|
||
self.row = try container.decodeIfPresent(Int.self, forKey: .row) ?? 0
|
||
self.column = try container.decodeIfPresent(Int.self, forKey: .column) ?? 0
|
||
self.chapterOffset = try container.decode(Int.self, forKey: .chapterOffset)
|
||
self.fragmentID = try container.decodeIfPresent(String.self, forKey: .fragmentID)
|
||
}
|
||
|
||
public func encode(to encoder: Encoder) throws {
|
||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||
try container.encode(fileIndex, forKey: .fileIndex)
|
||
try container.encode(row, forKey: .row)
|
||
try container.encode(column, forKey: .column)
|
||
try container.encode(chapterOffset, forKey: .chapterOffset)
|
||
try container.encodeIfPresent(fragmentID, forKey: .fragmentID)
|
||
}
|
||
}
|
||
|
||
/// 文本范围锚点,由起止锚点组成的区间
|
||
public struct RDEPUBTextRangeAnchor: Codable, Equatable {
|
||
/// 起始锚点
|
||
public let start: RDEPUBTextAnchor
|
||
/// 结束锚点
|
||
public let end: RDEPUBTextAnchor
|
||
|
||
public init(start: RDEPUBTextAnchor, end: RDEPUBTextAnchor) {
|
||
self.start = start
|
||
self.end = end
|
||
}
|
||
|
||
/// 转换为 NSRange(基于 chapterOffset)
|
||
public var nsRange: NSRange {
|
||
NSRange(location: start.chapterOffset, length: max(end.chapterOffset - start.chapterOffset, 0))
|
||
}
|
||
}
|