右对齐文本显示修复(如"巫鸿"只显示"鸿"的问题): - RDEPUBChapterTailNormalizer: 短尾页合并时检查 avoidPageBreakInside 语义, 避免将带有此标记的短文本(如 right-info 署名)错误合并回上一页 - RDEPUBTextContentView: 续段归一化时跳过右对齐/居中对齐的段落, 防止错误修改 firstLineHeadIndent 导致首字不可见 - RDEPUBCSSCompatibilityLayer: 为含 text-align:right/center 的 CSS 块 自动注入 text-indent:0 !important,确保右对齐/居中文本无首行缩进 - RDEPUBTextRendererSupport: 排版属性归一化时将右对齐/居中段落的 firstLineHeadIndent 重置为 0 图片查看器及脚注点击功能: - 新增 RDEPUBImageViewController 和 RDEPUBImageViewerCoordinator, 支持从 WebView 和 TextPage 两种模式查看图片 - epub-bridge.js: 检测图片和脚注图片的点击事件,脚注图片显示弹窗 - RDEPUBJavaScriptBridge: 新增 imageDidTap/footnoteDidTap 桥接消息 - RDEPUBWebView: 新增图片和脚注点击的 delegate 方法 - RDEPUBAttachmentNormalizer: 改进脚注检测,优先使用 alt 文本判断 - RDEPUBPaginationModels: 新增 .footnote 附件类型 - RDEPUBPageLayoutSnapshot: 运行时动态解析附件类型,脚注优先级高于图片 位置解析改进: - RDEPUBReaderController+LocationResolution: 利用 rangeInfo 提升页码定位精度 其他: - RDEPUBTextBookCache: schema 版本升级至 13 - RDEPUBReaderTheme: 主题更新 - Pod 项目文件更新 Co-Authored-By: Claude <noreply@anthropic.com>
178 lines
6.8 KiB
Swift
178 lines
6.8 KiB
Swift
|
|
import UIKit
|
|
|
|
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) {
|
|
if presentNotePopupIfPossible(for: location, fromSpineIndex: fromSpineIndex) {
|
|
return
|
|
}
|
|
|
|
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)
|
|
openExternalURLIfAllowed(url)
|
|
}
|
|
|
|
func epubWebContentView(_ contentView: RDEPUBWebContentView, didLogJavaScriptError message: String) {
|
|
#if DEBUG
|
|
print("EPUB JS Error: \(message)")
|
|
#endif
|
|
}
|
|
|
|
func epubWebContentView(_ contentView: RDEPUBWebContentView, didTapImageWithSource src: String, sourceRect: CGRect?) {
|
|
guard let publication else { return }
|
|
let baseHref = contentView.currentHref
|
|
let coordinator = RDEPUBImageViewerCoordinator(presentingController: self, theme: configuration.theme)
|
|
coordinator.presentImageFromWebView(src: src, baseHref: baseHref, resourceResolver: publication.resourceResolver)
|
|
}
|
|
|
|
func epubWebContentView(_ contentView: RDEPUBWebContentView, didTapFootnoteWithAltText altText: String, sourceRect: CGRect?) {
|
|
guard !altText.isEmpty else { return }
|
|
let rect = sourceRect ?? .zero
|
|
let sourcePoint = rect.isNull ? CGPoint(x: contentView.bounds.midX, y: contentView.bounds.midY) : CGPoint(x: rect.midX, y: rect.midY)
|
|
presentAttachmentTooltip(text: altText, sourceView: contentView, sourceRect: rect, sourcePoint: sourcePoint)
|
|
}
|
|
}
|
|
|
|
extension RDEPUBReaderController: RDEPUBTextContentViewDelegate {
|
|
|
|
func textContentView(_ contentView: RDEPUBTextContentView, didChangeSelection selection: RDEPUBSelection?) {
|
|
guard let selection else {
|
|
updateCurrentSelection(nil)
|
|
return
|
|
}
|
|
updateCurrentSelection(selection)
|
|
}
|
|
|
|
func textContentView(_ contentView: RDEPUBTextContentView, didRequestReaderTapAt point: CGPoint) {
|
|
readerView.handleContentTap(at: point, in: contentView)
|
|
}
|
|
|
|
func textContentView(
|
|
_ contentView: RDEPUBTextContentView,
|
|
didRequestSelectionAction action: RDEPUBAnnotationMenuAction,
|
|
selection: RDEPUBSelection?
|
|
) {
|
|
handleSelectionMenuAction(action, selection: selection ?? currentSelection)
|
|
contentView.clearSelection()
|
|
}
|
|
|
|
func textContentView(
|
|
_ contentView: RDEPUBTextContentView,
|
|
didActivateAttachmentText text: String,
|
|
sourceRect: CGRect,
|
|
sourcePoint: CGPoint
|
|
) {
|
|
presentAttachmentTooltip(text: text, sourceView: contentView, sourceRect: sourceRect, sourcePoint: sourcePoint)
|
|
}
|
|
|
|
func textContentView(
|
|
_ contentView: RDEPUBTextContentView,
|
|
didActivateImage image: UIImage,
|
|
sourceRect: CGRect,
|
|
altText: String?
|
|
) {
|
|
let coordinator = RDEPUBImageViewerCoordinator(presentingController: self, theme: configuration.theme)
|
|
coordinator.presentImage(image, sourceRect: sourceRect, altText: altText)
|
|
}
|
|
|
|
func textContentView(
|
|
_ contentView: RDEPUBTextContentView,
|
|
didRequestHighlightActions highlight: RDEPUBHighlight,
|
|
sourceRect: CGRect
|
|
) {
|
|
runtime.presentHighlightActions(for: highlight, sourceView: contentView, sourceRect: sourceRect)
|
|
}
|
|
|
|
private func presentNotePopupIfPossible(for location: RDEPUBLocation, fromSpineIndex: Int) -> Bool {
|
|
guard let publication else { return false }
|
|
let sourceHref = publication.resourceResolver.href(forSpineIndex: fromSpineIndex) ?? location.href
|
|
let sourceLocation = currentVisibleLocation()
|
|
let sourceCFI = sourceLocation?.cfi
|
|
let resolver = RDEPUBNoteResolver(resourceResolver: publication.resourceResolver)
|
|
guard let note = resolver.resolveInternalLink(
|
|
sourceHref: sourceHref,
|
|
sourceCFI: sourceCFI,
|
|
targetLocation: location,
|
|
sourceLocation: sourceLocation,
|
|
relativeToSpineIndex: fromSpineIndex
|
|
) else {
|
|
return false
|
|
}
|
|
|
|
RDEPUBNotePopupCoordinator.present(
|
|
note,
|
|
from: self,
|
|
onReturnToSource: { [weak self] in
|
|
guard let self, let sourceLocation = note.sourceLocation else { return }
|
|
self.navigateToLocation(sourceLocation, relativeToSpineIndex: nil, animated: true)
|
|
},
|
|
onOpenNoteLocation: { [weak self] in
|
|
guard let self else { return }
|
|
self.navigateToLocation(note.targetLocation, relativeToSpineIndex: nil, animated: true)
|
|
}
|
|
)
|
|
return true
|
|
}
|
|
|
|
private func navigateToLocation(
|
|
_ location: RDEPUBLocation,
|
|
relativeToSpineIndex spineIndex: Int?,
|
|
animated: Bool
|
|
) {
|
|
guard let pageNumber = readingSession?.queueNavigation(
|
|
to: location,
|
|
relativeToSpineIndex: spineIndex,
|
|
bookIdentifier: currentBookIdentifier
|
|
) else {
|
|
return
|
|
}
|
|
readerView.transitionToPage(pageNum: max(pageNumber - 1, 0), animated: animated)
|
|
}
|
|
|
|
}
|