fix: 修复右对齐短文本显示不完整及多项功能改进
右对齐文本显示修复(如"巫鸿"只显示"鸿"的问题): - 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>
This commit is contained in:
@@ -3,7 +3,7 @@ import UIKit
|
||||
|
||||
extension RDEPUBReaderController {
|
||||
|
||||
func pageNumber(for location: RDEPUBLocation) -> Int? {
|
||||
func pageNumber(for location: RDEPUBLocation, rangeInfo: String? = nil) -> Int? {
|
||||
if let publication,
|
||||
let bookPageMap = readerContext.bookPageMap,
|
||||
let spineIndex = readerContext.normalizedSpineIndex(for: location),
|
||||
@@ -16,7 +16,8 @@ extension RDEPUBReaderController {
|
||||
let localPageIndex = resolvedLocalPageIndex(
|
||||
for: normalizedLocation,
|
||||
spineIndex: spineIndex,
|
||||
fallbackEntry: entry
|
||||
fallbackEntry: entry,
|
||||
rangeInfo: rangeInfo
|
||||
) ?? fallbackLocalPageIndex(for: normalizedLocation, pageCount: entry.pageCount)
|
||||
return bookPageMap.absolutePageIndex(
|
||||
spineIndex: spineIndex,
|
||||
@@ -25,17 +26,22 @@ extension RDEPUBReaderController {
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
if let pageNumber = pageNumberFromRangeInfo(rangeInfo, in: textBook, matching: normalizedLocation) {
|
||||
return pageNumber
|
||||
}
|
||||
|
||||
if let anchor = normalizedLocation.rangeAnchor?.start {
|
||||
if let page = textBook.indexTable.pageNumber(for: anchor, in: textBook) {
|
||||
return page + 1
|
||||
}
|
||||
}
|
||||
|
||||
return textBook.pageNumber(
|
||||
for: normalizedLocation,
|
||||
resolver: publication.resourceResolver,
|
||||
@@ -130,7 +136,10 @@ extension RDEPUBReaderController {
|
||||
return (pages, chapters)
|
||||
}
|
||||
|
||||
func chapterOffset(for location: RDEPUBLocation, fallbackEntry: RDEPUBBookPageMapEntry) -> Int {
|
||||
func chapterOffset(for location: RDEPUBLocation, fallbackEntry: RDEPUBBookPageMapEntry, rangeInfo: String? = nil) -> Int {
|
||||
if let decodedRangeInfo = decodedRangeInfo(from: rangeInfo, matching: location) {
|
||||
return decodedRangeInfo.start
|
||||
}
|
||||
if let spineIndex = readerContext.normalizedSpineIndex(for: location),
|
||||
let runtimeChapter = runtime.chapterRuntimeStore.chapterData(for: spineIndex),
|
||||
let cfi = primaryLocationCFI(for: location),
|
||||
@@ -167,9 +176,10 @@ extension RDEPUBReaderController {
|
||||
private func resolvedLocalPageIndex(
|
||||
for location: RDEPUBLocation,
|
||||
spineIndex: Int,
|
||||
fallbackEntry: RDEPUBBookPageMapEntry
|
||||
fallbackEntry: RDEPUBBookPageMapEntry,
|
||||
rangeInfo: String? = nil
|
||||
) -> Int? {
|
||||
let offset = chapterOffset(for: location, fallbackEntry: fallbackEntry)
|
||||
let offset = chapterOffset(for: location, fallbackEntry: fallbackEntry, rangeInfo: rangeInfo)
|
||||
|
||||
if let runtimeChapter = runtime.chapterRuntimeStore.chapterData(for: spineIndex),
|
||||
let pageIndex = runtimeChapter.chapterOffsetMap.pageIndex(forChapterOffset: offset) {
|
||||
@@ -193,6 +203,55 @@ extension RDEPUBReaderController {
|
||||
return location.cfi
|
||||
}
|
||||
|
||||
private func pageNumberFromRangeInfo(
|
||||
_ rangeInfo: String?,
|
||||
in textBook: RDEPUBTextBook,
|
||||
matching location: RDEPUBLocation
|
||||
) -> Int? {
|
||||
guard let decodedRangeInfo = decodedRangeInfo(from: rangeInfo, matching: location),
|
||||
let chapterData = chapterData(in: textBook, for: decodedRangeInfo),
|
||||
let page = chapterData.page(containing: decodedRangeInfo.start) else {
|
||||
return nil
|
||||
}
|
||||
return page.absolutePageIndex + 1
|
||||
}
|
||||
|
||||
private func decodedRangeInfo(
|
||||
from rangeInfo: String?,
|
||||
matching location: RDEPUBLocation
|
||||
) -> RDEPUBTextOffsetRangeInfo? {
|
||||
guard let decodedRangeInfo = RDEPUBTextOffsetRangeInfo.decode(from: rangeInfo),
|
||||
normalizedHref(decodedRangeInfo.href) == normalizedHref(location.href) else {
|
||||
return nil
|
||||
}
|
||||
return decodedRangeInfo
|
||||
}
|
||||
|
||||
private func chapterData(
|
||||
in textBook: RDEPUBTextBook,
|
||||
for rangeInfo: RDEPUBTextOffsetRangeInfo
|
||||
) -> RDEPUBChapterData? {
|
||||
if let chapterData = textBook.chapterData(for: rangeInfo.href) {
|
||||
return chapterData
|
||||
}
|
||||
|
||||
let targetHref = normalizedHref(rangeInfo.href)
|
||||
if let chapterData = textBook.chapterData(for: targetHref) {
|
||||
return chapterData
|
||||
}
|
||||
|
||||
guard let chapter = textBook.chapters.first(where: { normalizedHref($0.href) == targetHref }) else {
|
||||
return nil
|
||||
}
|
||||
return textBook.chapterData(for: chapter.href)
|
||||
}
|
||||
|
||||
private func normalizedHref(_ href: String) -> String {
|
||||
publication?.resourceResolver.normalizedHref(href)
|
||||
?? href.components(separatedBy: "#").first
|
||||
?? href
|
||||
}
|
||||
|
||||
private func nearestFragmentID(beforeOrAt offset: Int, fragmentOffsets: [String: Int]) -> String? {
|
||||
var bestID: String?
|
||||
var bestOffset = Int.min
|
||||
|
||||
Reference in New Issue
Block a user