修改分页问题

This commit is contained in:
shen
2026-05-26 21:08:27 +08:00
parent 0e7d952fe5
commit 83b705b9ae
14 changed files with 227 additions and 50 deletions
@@ -104,7 +104,8 @@ public struct RDEPUBDTCoreTextRenderer: RDEPUBTextRenderer {
guard let element else { return }
RDEPUBTextRendererSupport.prepareHTMLElementForReaderRendering(
element,
style: request.style
style: request.style,
maxImageSize: resolvedMaxImageSize(for: request)
)
}
return builder?.generatedAttributedString()
@@ -116,7 +117,7 @@ public struct RDEPUBDTCoreTextRenderer: RDEPUBTextRenderer {
/// style.lineSpacing
private func dtOptions(request: RDEPUBTextChapterRenderRequest) -> [AnyHashable: Any] {
let style = request.style
let screenBounds = UIScreen.main.bounds.insetBy(dx: 20, dy: 28)
let maxImageSize = resolvedMaxImageSize(for: request)
var options: [AnyHashable: Any] = [
NSTextSizeMultiplierDocumentOption: 1.0,
DTDefaultFontFamily: style.font.familyName,
@@ -124,7 +125,7 @@ public struct RDEPUBDTCoreTextRenderer: RDEPUBTextRenderer {
DTDefaultFontSize: style.font.pointSize,
DTDefaultLineHeightMultiplier: max((style.font.lineHeight + style.lineSpacing) / max(style.font.lineHeight, 1), 1),
DTUseiOS6Attributes: true,
DTMaxImageSize: NSValue(cgSize: screenBounds.size)
DTMaxImageSize: NSValue(cgSize: maxImageSize)
]
if let baseURL = request.context.baseURL {
@@ -136,5 +137,18 @@ public struct RDEPUBDTCoreTextRenderer: RDEPUBTextRenderer {
return options
}
private func resolvedMaxImageSize(for request: RDEPUBTextChapterRenderRequest) -> CGSize {
if let pageSize = request.pageSize {
let layoutConfig = request.layoutConfig ?? .default
let contentRect = layoutConfig.contentRect(fallback: pageSize)
let maxWidth = max(round(contentRect.width), 1)
let maxHeight = max(round(contentRect.height * layoutConfig.imageMaxHeightRatio), 1)
return CGSize(width: maxWidth, height: maxHeight)
}
let screenBounds = UIScreen.main.bounds.insetBy(dx: 20, dy: 28)
return CGSize(width: max(round(screenBounds.width), 1), height: max(round(screenBounds.height * 0.85), 1))
}
#endif
}
@@ -293,7 +293,9 @@ public final class RDEPUBTextBookBuilder {
baseURL: parser.fileURL(forRelativePath: item.href)?.deletingLastPathComponent(),
style: style,
resourceResolver: publication.resourceResolver,
contentLanguageCode: publication.metadata.language
contentLanguageCode: publication.metadata.language,
pageSize: pageSize,
layoutConfig: layoutConfig
)
// HTML NSAttributedString
@@ -123,7 +123,7 @@ final class ChapterPaginationArchive: NSObject, NSSecureCoding {
public final class RDEPUBTextBookCache {
///
public var schemaVersion: Int = 1
public var schemaVersion: Int = 4
/// 线
private let queue = DispatchQueue(label: "com.rdreader.textbookcache", qos: .utility)
@@ -155,27 +155,31 @@ struct RDEPUBTextLayouter {
totalLength: attributedString.length,
lineRanges: lineRanges
)
let verifiedRange = verifiedDisplayRange(for: adjusted.range)
let trailingFragmentID = nearestTrailingFragmentID(
endingAt: adjusted.range.location + adjusted.range.length,
endingAt: verifiedRange.location + verifiedRange.length,
fragmentOffsets: fragmentOffsets
)
let diagnostics = verifiedRange == adjusted.range
? adjusted.diagnostics
: adjusted.diagnostics + ["verified-display-range \(NSStringFromRange(adjusted.range)) -> \(NSStringFromRange(verifiedRange))"]
frames.append(
RDEPUBTextLayoutFrame(
contentRange: adjusted.range,
contentRange: verifiedRange,
breakReason: adjusted.breakReason,
blockRange: adjusted.blockRange,
attachmentRanges: adjusted.attachmentRanges,
attachmentKinds: adjusted.attachmentKinds,
blockKinds: adjusted.blockKinds,
semanticHints: adjusted.semanticHints,
attachmentPlacements: adjusted.attachmentPlacements,
blockRange: blockRange(at: max(verifiedRange.location, verifiedRange.location + verifiedRange.length - 1)),
attachmentRanges: attachmentRanges(in: verifiedRange),
attachmentKinds: attachmentKinds(in: verifiedRange),
blockKinds: blockKinds(in: verifiedRange),
semanticHints: semanticHints(in: verifiedRange),
attachmentPlacements: attachmentPlacements(in: verifiedRange),
trailingFragmentID: trailingFragmentID,
diagnostics: adjusted.diagnostics
diagnostics: diagnostics
)
)
let nextLocation = adjusted.range.location + adjusted.range.length
let nextLocation = verifiedRange.location + verifiedRange.length
guard nextLocation > location else {
location += max(visibleRange.length, 1)
continue
@@ -185,6 +189,30 @@ struct RDEPUBTextLayouter {
return frames
}
private func verifiedDisplayRange(for range: NSRange) -> NSRange {
guard range.length > 0,
!attachmentRanges(in: range).isEmpty else {
return range
}
let pageContent = attributedString.attributedSubstring(from: range)
guard let layouter = DTCoreTextLayouter(attributedString: pageContent) else {
return range
}
layouter.shouldCacheLayoutFrames = false
guard let layoutFrame = layouter.layoutFrame(with: dtLayoutRect, range: NSRange(location: 0, length: 0)) else {
return range
}
let visibleRange = layoutFrame.visibleStringRange()
guard visibleRange.length > 0, visibleRange.length < range.length else {
return range
}
return NSRange(location: range.location, length: visibleRange.length)
}
#endif
private static func makeLayoutPath(pageSize: CGSize, config: RDEPUBTextLayoutConfig) -> CGPath {
@@ -286,10 +286,21 @@ public struct RDEPUBTextChapterContext: Equatable {
public struct RDEPUBTextChapterRenderRequest {
public var context: RDEPUBTextChapterContext
public var style: RDEPUBTextRenderStyle
/// page size
public var pageSize: CGSize?
/// WXRead
public var layoutConfig: RDEPUBTextLayoutConfig?
public init(context: RDEPUBTextChapterContext, style: RDEPUBTextRenderStyle) {
public init(
context: RDEPUBTextChapterContext,
style: RDEPUBTextRenderStyle,
pageSize: CGSize? = nil,
layoutConfig: RDEPUBTextLayoutConfig? = nil
) {
self.context = context
self.style = style
self.pageSize = pageSize
self.layoutConfig = layoutConfig
}
}
@@ -197,7 +197,9 @@ enum RDEPUBTextRendererSupport {
baseURL: URL?,
style: RDEPUBTextRenderStyle,
resourceResolver: RDEPUBResourceResolver?,
contentLanguageCode: String? = nil
contentLanguageCode: String? = nil,
pageSize: CGSize? = nil,
layoutConfig: RDEPUBTextLayoutConfig? = nil
) -> RDEPUBTextChapterRenderRequest {
let normalizedHTML = injectPaginationSemanticMarkers(into: normalizeHTML(rawHTML))
let stylesheetHrefReplacements = inlineLinkedStyleSheets(
@@ -255,7 +257,12 @@ enum RDEPUBTextRendererSupport {
stylesheet: RDEPUBTextStyleSheetPackage(layers: layers),
resourceDiagnostics: resourceDiagnostics
)
return RDEPUBTextChapterRenderRequest(context: context, style: style)
return RDEPUBTextChapterRenderRequest(
context: context,
style: style,
pageSize: pageSize,
layoutConfig: layoutConfig
)
}
/// HTML 规范化:清理冗余字符(CR、多余换行),规范化附件 HTML 标记
@@ -285,15 +292,20 @@ enum RDEPUBTextRendererSupport {
#if canImport(DTCoreText)
static func prepareHTMLElementForReaderRendering(
_ element: DTHTMLElement,
style: RDEPUBTextRenderStyle
style: RDEPUBTextRenderStyle,
maxImageSize: CGSize? = nil
) {
guard let attachment = element.textAttachment else { return }
let pointSize = max(element.fontDescriptor.pointSize, style.font.pointSize)
let maxSize = CGSize(
let fallbackSize = CGSize(
width: round(UIScreen.main.bounds.insetBy(dx: 20, dy: 28).width),
height: round(UIScreen.main.bounds.insetBy(dx: 20, dy: 28).height * 0.85)
)
normalizeAttachmentLayoutForWXRead(attachment, fontPointSize: pointSize, maxImageSize: maxSize)
normalizeAttachmentLayoutForWXRead(
attachment,
fontPointSize: pointSize,
maxImageSize: maxImageSize ?? fallbackSize
)
if isFootnoteAttachment(attachment) {
element.displayStyle = .inline
} else if isCoverAttachment(attachment) {
@@ -390,6 +402,38 @@ enum RDEPUBTextRendererSupport {
private static func normalizeAttachmentHTMLMarkers(in html: String) -> String {
var normalized = html
if let bodyPicContainerRegex = try? NSRegularExpression(
pattern: #"<div\b([^>]*class\s*=\s*["'][^"']*\b(?:qrbodyPic|bodyPic)\b[^"']*["'][^>]*)>([\s\S]*?)</div>"#,
options: [.caseInsensitive]
) {
normalized = replaceMatches(
using: bodyPicContainerRegex,
in: normalized
) { tag in
guard let imageTagRegex = try? NSRegularExpression(pattern: #"<img\b[^>]*>"#, options: [.caseInsensitive]) else {
return tag
}
return replaceMatches(
using: imageTagRegex,
in: tag
) { imageTag in
mergeHTMLAttributes(
into: imageTag,
requiredClass: "bodyPic",
styleFragments: [
"wr-vertical-center-style:2",
"max-width:100%",
"height:auto",
"display:block",
"margin-left:auto",
"margin-right:auto"
]
)
}
}
}
if let footnoteRegex = try? NSRegularExpression(
pattern: #"<img\b([^>]*class\s*=\s*["'][^"']*\bqqreader-footnote\b[^"']*["'][^>]*)>"#,
options: [.caseInsensitive]