ReadViewSDK/Sources/RDEpubReaderView/EPUBCore/CFI/RDEPUBCFIMap.swift
shenlei d7fcda345d refactor: rename RDReaderView -> RDEpubReaderView, update pod config and docs
- Rename source module from RDReaderView to RDEpubReaderView
- Move all source files from Sources/RDReaderView/ to Sources/RDEpubReaderView/
- Update podspec: RDReaderView.podspec -> RDEpubReaderView.podspec
- Update Podfile, demo project, and CocoaPods config for new pod name
- Delete old RDReaderView pod support files from ReadViewDemo/Pods
- Add new RDEpubReaderView pod support files
- Update documentation (API ref, architecture, UML, conventions, etc.)
- Add FixedLayoutRotationTests
- Update .gitignore: exclude .DS_Store, manual unpack backups, _ssoft-output
2026-07-10 19:44:53 +09:00

183 lines
6.3 KiB
Swift

import Foundation
public struct RDEPUBCFIMap: Codable, Equatable {
public var href: String
public var renderVersion: Int
public var domVersion: Int
public var markers: [RDEPUBCFIMarker]
public var textAssertions: [String: RDEPUBCFITextAssertion]
public var pathRanges: [RDEPUBCFIPathRange]
public var recoveryMetadata: RDEPUBCFIRecoveryMetadata
/// Lazy O(1) lookup index for markers by cfiPath
private var markerByPath: [RDEPUBCFIPath: Int]?
public init(
href: String,
renderVersion: Int = 1,
domVersion: Int = 1,
markers: [RDEPUBCFIMarker] = [],
textAssertions: [String: RDEPUBCFITextAssertion] = [:],
pathRanges: [RDEPUBCFIPathRange] = [],
recoveryMetadata: RDEPUBCFIRecoveryMetadata = .empty
) {
self.href = href
self.renderVersion = renderVersion
self.domVersion = domVersion
self.markers = markers
self.textAssertions = textAssertions
self.pathRanges = pathRanges
self.recoveryMetadata = recoveryMetadata
}
public func marker(matching path: RDEPUBCFIPath) -> RDEPUBCFIMarker? {
if let index = markerByPath?[path] {
return markers[index]
}
return markers.first { $0.cfiPath == path }
}
/// Build or rebuild the marker lookup index. Call after mutating `markers`.
public mutating func rebuildMarkerIndex() {
markerByPath = Dictionary(
markers.enumerated().map { ($0.element.cfiPath, $0.offset) },
uniquingKeysWith: { _, last in last }
)
}
enum CodingKeys: String, CodingKey {
case href
case renderVersion
case domVersion
case markers
case textAssertions
case pathRanges
case recoveryMetadata
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
href = try container.decode(String.self, forKey: .href)
renderVersion = try container.decodeIfPresent(Int.self, forKey: .renderVersion) ?? 1
domVersion = try container.decodeIfPresent(Int.self, forKey: .domVersion) ?? 1
markers = try container.decodeIfPresent([RDEPUBCFIMarker].self, forKey: .markers) ?? []
textAssertions = try container.decodeIfPresent([String: RDEPUBCFITextAssertion].self, forKey: .textAssertions) ?? [:]
pathRanges = try container.decodeIfPresent([RDEPUBCFIPathRange].self, forKey: .pathRanges) ?? []
recoveryMetadata = try container.decodeIfPresent(RDEPUBCFIRecoveryMetadata.self, forKey: .recoveryMetadata) ?? .empty
markerByPath = nil
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(href, forKey: .href)
try container.encode(renderVersion, forKey: .renderVersion)
try container.encode(domVersion, forKey: .domVersion)
try container.encode(markers, forKey: .markers)
try container.encode(textAssertions, forKey: .textAssertions)
try container.encode(pathRanges, forKey: .pathRanges)
try container.encode(recoveryMetadata, forKey: .recoveryMetadata)
}
public static func == (lhs: RDEPUBCFIMap, rhs: RDEPUBCFIMap) -> Bool {
lhs.href == rhs.href
&& lhs.renderVersion == rhs.renderVersion
&& lhs.domVersion == rhs.domVersion
&& lhs.markers == rhs.markers
&& lhs.textAssertions == rhs.textAssertions
&& lhs.pathRanges == rhs.pathRanges
&& lhs.recoveryMetadata == rhs.recoveryMetadata
}
}
public struct RDEPUBCFIMarker: Codable, Equatable {
public var cfiPath: RDEPUBCFIPath
public var chapterOffset: Int?
public var fragmentID: String?
public var textNodeLength: Int?
public var textNodeChecksum: UInt64?
public var normalizedTextPreview: String?
public var domSiblingSignature: String?
public init(
cfiPath: RDEPUBCFIPath,
chapterOffset: Int? = nil,
fragmentID: String? = nil,
textNodeLength: Int? = nil,
textNodeChecksum: UInt64? = nil,
normalizedTextPreview: String? = nil,
domSiblingSignature: String? = nil
) {
self.cfiPath = cfiPath
self.chapterOffset = chapterOffset
self.fragmentID = fragmentID
self.textNodeLength = textNodeLength
self.textNodeChecksum = textNodeChecksum
self.normalizedTextPreview = normalizedTextPreview
self.domSiblingSignature = domSiblingSignature
}
}
public struct RDEPUBCFIPathRange: Codable, Equatable {
public var cfiPath: RDEPUBCFIPath
public var startOffset: Int
public var endOffset: Int
public var textNodeLength: Int
public init(
cfiPath: RDEPUBCFIPath,
startOffset: Int,
endOffset: Int,
textNodeLength: Int
) {
self.cfiPath = cfiPath
self.startOffset = startOffset
self.endOffset = endOffset
self.textNodeLength = textNodeLength
}
}
public struct RDEPUBCFIRecoveryMetadata: Codable, Equatable {
public var domFingerprint: String
public var normalizedTextChecksum: String
public var tokenIndex: [RDEPUBCFITokenAnchor]
public var fragmentPathMap: [String: RDEPUBCFIPath]
public init(
domFingerprint: String,
normalizedTextChecksum: String,
tokenIndex: [RDEPUBCFITokenAnchor] = [],
fragmentPathMap: [String: RDEPUBCFIPath] = [:]
) {
self.domFingerprint = domFingerprint
self.normalizedTextChecksum = normalizedTextChecksum
self.tokenIndex = tokenIndex
self.fragmentPathMap = fragmentPathMap
}
public static let empty = RDEPUBCFIRecoveryMetadata(
domFingerprint: "",
normalizedTextChecksum: "",
tokenIndex: [],
fragmentPathMap: [:]
)
}
public struct RDEPUBCFITokenAnchor: Codable, Equatable {
public var token: String
public var occurrence: Int
public var chapterOffset: Int
public var cfiPath: RDEPUBCFIPath
public init(
token: String,
occurrence: Int,
chapterOffset: Int,
cfiPath: RDEPUBCFIPath
) {
self.token = token
self.occurrence = occurrence
self.chapterOffset = chapterOffset
self.cfiPath = cfiPath
}
}