ReadViewSDK/Sources/RDReaderView/EPUBTextRendering/RDEPUBDTCoreTextRenderer.swift
2026-05-26 21:08:27 +08:00

155 lines
6.7 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.

import UIKit
#if canImport(DTCoreText)
import DTCoreText
#endif
/// DTCoreText EPUB HTML NSAttributedString
///
/// `RDEPUBTextRenderer`
/// HTML DTCoreText NSAttributedString fragment /
///
/// DTCoreText HTML
/// `willFlushCallback` DOM
/// DTCoreText 退
public struct RDEPUBDTCoreTextRenderer: RDEPUBTextRenderer {
public init() {}
/// DTCoreText
public static var isAvailable: Bool {
#if canImport(DTCoreText)
return true
#else
return false
#endif
}
/// HTML NSAttributedString fragment
///
///
/// 1. HTML Data
/// 2. DTCoreText 退
/// 3. ${rd-sem-start/end}
/// 4. fragment
/// 5.
public func renderChapter(
request: RDEPUBTextChapterRenderRequest
) throws -> RDEPUBRenderedChapterContent {
#if canImport(DTCoreText)
let chapterContext = request.context
guard let data = chapterContext.html.data(using: .utf8) else {
throw RDEPUBTextRenderingError.htmlEncodingFailed
}
guard let rendered = makeAttributedString(from: data, request: request) else {
return fallbackRenderedContent(request: request)
}
let attributedString = NSMutableAttributedString(attributedString: rendered)
RDEPUBTextRendererSupport.applyPaginationSemantics(in: attributedString)
let fragmentOffsets = RDEPUBTextRendererSupport.extractFragmentOffsets(from: attributedString)
RDEPUBTextRendererSupport.normalizeReadingAttributes(in: attributedString, style: request.style)
return RDEPUBRenderedChapterContent(
attributedString: attributedString,
fragmentOffsets: fragmentOffsets,
resourceDiagnostics: chapterContext.resourceDiagnostics
)
#else
return fallbackRenderedContent(request: request)
#endif
}
/// 便 HTML
public func renderChapter(
html: String,
baseURL: URL?,
style: RDEPUBTextRenderStyle
) throws -> RDEPUBRenderedChapterContent {
let request = RDEPUBTextRendererSupport.makeChapterRenderRequest(
href: "",
title: "",
rawHTML: html,
baseURL: baseURL,
style: style,
resourceResolver: nil
)
return try renderChapter(request: request)
}
/// 退 DTCoreText HTML
private func fallbackRenderedContent(request: RDEPUBTextChapterRenderRequest) -> RDEPUBRenderedChapterContent {
let attributedString = RDEPUBTextRendererSupport.fallbackAttributedString(for: request.context.html, style: request.style)
RDEPUBTextRendererSupport.applyPaginationSemantics(in: attributedString)
let fragmentOffsets = RDEPUBTextRendererSupport.extractFragmentOffsets(from: attributedString)
RDEPUBTextRendererSupport.normalizeReadingAttributes(in: attributedString, style: request.style)
return RDEPUBRenderedChapterContent(
attributedString: attributedString,
fragmentOffsets: fragmentOffsets,
resourceDiagnostics: request.context.resourceDiagnostics
)
}
#if canImport(DTCoreText)
/// 使 DTCoreText HTML Data
///
/// `willFlushCallback` DOM
/// /
private func makeAttributedString(from data: Data, request: RDEPUBTextChapterRenderRequest) -> NSAttributedString? {
let builder = DTHTMLAttributedStringBuilder(
html: data,
options: dtOptions(request: request),
documentAttributes: nil
)
builder?.willFlushCallback = { element in
guard let element else { return }
RDEPUBTextRendererSupport.prepareHTMLElementForReaderRendering(
element,
style: request.style,
maxImageSize: resolvedMaxImageSize(for: request)
)
}
return builder?.generatedAttributedString()
}
/// DTCoreText
///
/// - `( + ) / `
/// style.lineSpacing
private func dtOptions(request: RDEPUBTextChapterRenderRequest) -> [AnyHashable: Any] {
let style = request.style
let maxImageSize = resolvedMaxImageSize(for: request)
var options: [AnyHashable: Any] = [
NSTextSizeMultiplierDocumentOption: 1.0,
DTDefaultFontFamily: style.font.familyName,
DTDefaultFontName: style.font.fontName,
DTDefaultFontSize: style.font.pointSize,
DTDefaultLineHeightMultiplier: max((style.font.lineHeight + style.lineSpacing) / max(style.font.lineHeight, 1), 1),
DTUseiOS6Attributes: true,
DTMaxImageSize: NSValue(cgSize: maxImageSize)
]
if let baseURL = request.context.baseURL {
options[NSBaseURLDocumentOption] = baseURL
}
if let textColor = style.textColor {
options[DTDefaultTextColor] = textColor
}
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
}