ReadViewSDK/Sources/RDReaderView/EPUBUI/RDEPUBReaderController+ContentDelegates.swift
shen 948004eed1 docs: 补充注释、修正过时文档、清理重复内容
源码注释:
- 为 ~60 个 Swift 文件补充缺失的 doc comment(file header、类型、属性、方法)
- 修正 4 处错误注释:翻页模式数量、搜索行为描述、手势识别器描述、悬空文档块

文档维护:
- 删除重复文档:WXRead/读书EPUB阅读器实现架构.md(与微信读书版完全一致)
- 合并重叠文档:阅读器规划.md → 阅读器功能开发计划.md(单一真值)
- 修正过时内容:所有文档中"四种翻页模式"→"三种",移除 horizontalCoverScroll
- 更新架构图:补齐 EPUBUI/ReaderController、Paging/、Typesetter/ 等子目录
- 更新 index.md 索引:新增开发计划和架构对比文档引用
2026-06-01 09:33:23 +08:00

205 lines
8.4 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.

// RDEPUBReaderController+ContentDelegates.swift
// EPUB
// Web RDEPUBWebContentViewDelegate Native Text
// RDEPUBTextContentViewDelegate
import UIKit
/// RDEPUBReaderController
///
/// EPUB Web Native Text
///
// MARK: - Web EPUB /Web
extension RDEPUBReaderController: RDEPUBWebContentViewDelegate {
/// Web
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
)
}
/// Web
func epubWebContentView(_ contentView: RDEPUBWebContentView, didChangeSelection selection: RDEPUBSelection?, spineIndex: Int) {
if let selection {
updateCurrentSelection(scopedSelection(selection, relativeToSpineIndex: spineIndex))
} else {
updateCurrentSelection(nil)
}
}
/// Web
func epubWebContentView(_ contentView: RDEPUBWebContentView, didRequestSelectionAction action: RDEPUBAnnotationMenuAction) {
handleSelectionMenuAction(action, selection: currentSelection)
}
/// Web
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)
}
/// Web 使
func epubWebContentView(_ contentView: RDEPUBWebContentView, didActivateExternalLink url: URL) {
delegate?.epubReader(self, didActivateExternalLink: url)
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}
/// Web JavaScript
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,
selection: RDEPUBSelection?
) {
let normalizedSelection = selection.flatMap(normalizedTextSelection)
handleSelectionMenuAction(action, selection: normalizedSelection ?? 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
)
}
/// EPUB
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
}
/// spine
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)
}
}