refactor: split reader architecture and chrome handling
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
import UIKit
|
||||
|
||||
// MARK: - Web 内容视图代理(EPUB 固定布局/Web 渲染路径)
|
||||
|
||||
extension RDEPUBReaderController: RDEPUBWebContentViewDelegate {
|
||||
func epubWebContentView(_ contentView: RDEPUBWebContentView, didUpdateLocation location: RDEPUBLocation, spineIndex: Int) {
|
||||
guard readerView.currentPage >= 0,
|
||||
activePages.indices.contains(readerView.currentPage) else {
|
||||
return
|
||||
}
|
||||
|
||||
let currentPage = activePages[readerView.currentPage]
|
||||
guard readingSession?.pageContains(spineIndex: spineIndex, in: currentPage) == true else {
|
||||
return
|
||||
}
|
||||
|
||||
persist(location: location)
|
||||
readingSession?.updateReadingContext(
|
||||
pageNumber: readerView.currentPage + 1,
|
||||
location: location,
|
||||
spineIndex: spineIndex,
|
||||
chapterIndex: currentPage.chapterIndex,
|
||||
bookIdentifier: currentBookIdentifier
|
||||
)
|
||||
}
|
||||
|
||||
func epubWebContentView(_ contentView: RDEPUBWebContentView, didChangeSelection selection: RDEPUBSelection?, spineIndex: Int) {
|
||||
if let selection {
|
||||
updateCurrentSelection(scopedSelection(selection, relativeToSpineIndex: spineIndex))
|
||||
} else {
|
||||
updateCurrentSelection(nil)
|
||||
}
|
||||
}
|
||||
|
||||
func epubWebContentView(_ contentView: RDEPUBWebContentView, didRequestSelectionAction action: RDEPUBAnnotationMenuAction) {
|
||||
handleSelectionMenuAction(action, selection: currentSelection)
|
||||
}
|
||||
|
||||
func epubWebContentView(_ contentView: RDEPUBWebContentView, didActivateInternalLink location: RDEPUBLocation, fromSpineIndex: Int) {
|
||||
guard let readingSession,
|
||||
let pageNumber = readingSession.queueNavigation(
|
||||
to: location,
|
||||
relativeToSpineIndex: fromSpineIndex,
|
||||
bookIdentifier: currentBookIdentifier
|
||||
) else {
|
||||
return
|
||||
}
|
||||
readerView.transitionToPage(pageNum: max(pageNumber - 1, 0), animated: true)
|
||||
}
|
||||
|
||||
func epubWebContentView(_ contentView: RDEPUBWebContentView, didActivateExternalLink url: URL) {
|
||||
delegate?.epubReader(self, didActivateExternalLink: url)
|
||||
UIApplication.shared.open(url, options: [:], completionHandler: nil)
|
||||
}
|
||||
|
||||
func epubWebContentView(_ contentView: RDEPUBWebContentView, didLogJavaScriptError message: String) {
|
||||
print("EPUB JS Error: \(message)")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 文本内容视图代理(Native Text 渲染路径)
|
||||
|
||||
extension RDEPUBReaderController: RDEPUBTextContentViewDelegate {
|
||||
func textContentView(_ contentView: RDEPUBTextContentView, didChangeSelection selection: RDEPUBSelection?) {
|
||||
guard let selection else {
|
||||
updateCurrentSelection(nil)
|
||||
return
|
||||
}
|
||||
updateCurrentSelection(normalizedTextSelection(selection))
|
||||
}
|
||||
|
||||
func textContentView(_ contentView: RDEPUBTextContentView, didRequestSelectionAction action: RDEPUBAnnotationMenuAction) {
|
||||
handleSelectionMenuAction(action, selection: currentSelection)
|
||||
contentView.clearSelection()
|
||||
}
|
||||
|
||||
private func normalizedTextSelection(_ selection: RDEPUBSelection) -> RDEPUBSelection? {
|
||||
guard let textBook,
|
||||
let chapterData = textBook.chapterData(for: selection.location.href) else {
|
||||
return scopedSelection(selection, relativeToSpineIndex: nil)
|
||||
}
|
||||
guard let payload = RDEPUBTextOffsetRangeInfo.decode(from: selection.rangeInfo) else {
|
||||
return scopedSelection(selection, relativeToSpineIndex: nil)
|
||||
}
|
||||
|
||||
let contentLength = max(chapterData.attributedContent.length, 1)
|
||||
let lastInclusiveOffset = max(contentLength - 1, 1)
|
||||
let start = max(0, min(payload.start, lastInclusiveOffset))
|
||||
let endExclusive = max(start + 1, min(payload.end, contentLength))
|
||||
let absoluteRange = NSRange(location: start, length: endExclusive - start)
|
||||
let location = chapterData.location(for: absoluteRange, bookIdentifier: currentBookIdentifier)
|
||||
return RDEPUBSelection(
|
||||
bookIdentifier: currentBookIdentifier,
|
||||
location: location,
|
||||
text: selection.text,
|
||||
rangeInfo: selection.rangeInfo,
|
||||
createdAt: selection.createdAt
|
||||
)
|
||||
}
|
||||
|
||||
func pageNumber(for location: RDEPUBLocation) -> Int? {
|
||||
if let textBook, let publication {
|
||||
if let anchor = location.rangeAnchor?.start {
|
||||
if let page = textBook.indexTable.pageNumber(for: anchor, in: textBook) {
|
||||
return page + 1
|
||||
}
|
||||
}
|
||||
|
||||
let normalizedLocation = publication.resourceResolver.normalizedLocation(
|
||||
location,
|
||||
relativeToSpineIndex: nil,
|
||||
bookIdentifier: currentBookIdentifier
|
||||
) ?? location
|
||||
return textBook.pageNumber(
|
||||
for: normalizedLocation,
|
||||
resolver: publication.resourceResolver,
|
||||
bookIdentifier: currentBookIdentifier
|
||||
)
|
||||
}
|
||||
|
||||
return readingSession?.queueNavigation(
|
||||
to: location,
|
||||
relativeToSpineIndex: nil,
|
||||
bookIdentifier: currentBookIdentifier
|
||||
)
|
||||
}
|
||||
|
||||
func resolvedTextLocation(forPageNumber pageNumber: Int) -> RDEPUBLocation? {
|
||||
guard let textBook,
|
||||
let publication,
|
||||
let page = textBook.page(at: pageNumber) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let location = textBook.chapterData(forPageNumber: pageNumber)?.location(forPage: page, bookIdentifier: currentBookIdentifier)
|
||||
?? textBook.location(forPageNumber: pageNumber, bookIdentifier: currentBookIdentifier)
|
||||
guard let location else { return nil }
|
||||
|
||||
return publication.resourceResolver.normalizedLocation(
|
||||
location,
|
||||
relativeToSpineIndex: nil,
|
||||
bookIdentifier: currentBookIdentifier
|
||||
) ?? location
|
||||
}
|
||||
|
||||
func synchronizeTextReadingState(pageNumber: Int, location: RDEPUBLocation) {
|
||||
guard let textBook,
|
||||
let page = textBook.page(at: pageNumber) else {
|
||||
readingSession?.transition(to: .idle)
|
||||
return
|
||||
}
|
||||
|
||||
readingSession?.updateReadingContext(
|
||||
pageNumber: pageNumber,
|
||||
location: location,
|
||||
spineIndex: page.spineIndex,
|
||||
chapterIndex: page.chapterIndex,
|
||||
bookIdentifier: currentBookIdentifier
|
||||
)
|
||||
}
|
||||
|
||||
func nativeTextSnapshot(from textBook: RDEPUBTextBook) -> RDEPUBNativeTextSnapshot {
|
||||
let chapters = textBook.chapterInfos
|
||||
let pages = textBook.pages.map {
|
||||
EPUBPage(
|
||||
spineIndex: $0.spineIndex,
|
||||
chapterIndex: $0.chapterIndex,
|
||||
pageIndexInChapter: $0.pageIndexInChapter,
|
||||
totalPagesInChapter: $0.totalPagesInChapter,
|
||||
chapterTitle: $0.chapterTitle,
|
||||
fixedSpread: nil
|
||||
)
|
||||
}
|
||||
return (pages, chapters)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user