ReadViewSDK/Sources/RDReaderView/EPUBUI/ReaderController/ChapterRuntime/RDEPUBBookPageMap.swift

127 lines
4.6 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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