refactor: rename RDReaderView -> RDEpubReaderView, update pod config and docs
- Rename source module from RDReaderView to RDEpubReaderView - Move all source files from Sources/RDReaderView/ to Sources/RDEpubReaderView/ - Update podspec: RDReaderView.podspec -> RDEpubReaderView.podspec - Update Podfile, demo project, and CocoaPods config for new pod name - Delete old RDReaderView pod support files from ReadViewDemo/Pods - Add new RDEpubReaderView pod support files - Update documentation (API ref, architecture, UML, conventions, etc.) - Add FixedLayoutRotationTests - Update .gitignore: exclude .DS_Store, manual unpack backups, _ssoft-output
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
import UIKit
|
||||
|
||||
#if canImport(DTCoreText)
|
||||
import DTCoreText
|
||||
#endif
|
||||
|
||||
public struct RDEPUBDTCoreTextRenderer: RDEPUBTextRenderer {
|
||||
public init() {}
|
||||
|
||||
public static var isAvailable: Bool {
|
||||
#if canImport(DTCoreText)
|
||||
return true
|
||||
#else
|
||||
return false
|
||||
#endif
|
||||
}
|
||||
|
||||
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)
|
||||
RDEPUBSemanticMarkerInjector.applyPaginationSemantics(in: attributedString)
|
||||
let fragmentOffsets = RDEPUBFragmentMarkerInjector.extractFragmentOffsets(from: attributedString)
|
||||
RDEPUBTextRendererSupport.normalizeReadingAttributes(
|
||||
in: attributedString,
|
||||
style: request.style,
|
||||
layoutConfig: request.layoutConfig ?? .default
|
||||
)
|
||||
return RDEPUBRenderedChapterContent(
|
||||
attributedString: attributedString,
|
||||
fragmentOffsets: fragmentOffsets,
|
||||
resourceDiagnostics: chapterContext.resourceDiagnostics
|
||||
)
|
||||
#else
|
||||
return fallbackRenderedContent(request: request)
|
||||
#endif
|
||||
}
|
||||
|
||||
public func renderChapter(
|
||||
html: String,
|
||||
baseURL: URL?,
|
||||
style: RDEPUBTextRenderStyle
|
||||
) throws -> RDEPUBRenderedChapterContent {
|
||||
let request = RDEPUBTextTypesetterPipeline().makeRequest(
|
||||
from: RDEPUBTypesettingInput(
|
||||
href: "",
|
||||
spineIndex: nil,
|
||||
title: "",
|
||||
rawHTML: html,
|
||||
baseURL: baseURL,
|
||||
style: style,
|
||||
resourceResolver: nil
|
||||
)
|
||||
).request
|
||||
return try renderChapter(request: request)
|
||||
}
|
||||
|
||||
private func fallbackRenderedContent(request: RDEPUBTextChapterRenderRequest) -> RDEPUBRenderedChapterContent {
|
||||
let attributedString = RDEPUBTextRendererSupport.fallbackAttributedString(for: request.context.html, style: request.style)
|
||||
RDEPUBSemanticMarkerInjector.applyPaginationSemantics(in: attributedString)
|
||||
let fragmentOffsets = RDEPUBFragmentMarkerInjector.extractFragmentOffsets(from: attributedString)
|
||||
RDEPUBTextRendererSupport.normalizeReadingAttributes(
|
||||
in: attributedString,
|
||||
style: request.style,
|
||||
layoutConfig: request.layoutConfig ?? .default
|
||||
)
|
||||
return RDEPUBRenderedChapterContent(
|
||||
attributedString: attributedString,
|
||||
fragmentOffsets: fragmentOffsets,
|
||||
resourceDiagnostics: request.context.resourceDiagnostics
|
||||
)
|
||||
}
|
||||
|
||||
#if canImport(DTCoreText)
|
||||
|
||||
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 }
|
||||
RDEPUBAttachmentNormalizer.prepareHTMLElementForReaderRendering(
|
||||
element,
|
||||
style: request.style,
|
||||
maxImageSize: resolvedMaxImageSize(for: request)
|
||||
)
|
||||
}
|
||||
return builder?.generatedAttributedString()
|
||||
}
|
||||
|
||||
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 {
|
||||
let layoutConfig = request.layoutConfig ?? .default
|
||||
let fallbackPageSize = request.pageSize ?? layoutConfig.fallbackViewportSize
|
||||
let contentRect = layoutConfig.contentRect(fallback: fallbackPageSize)
|
||||
let maxWidth = max(round(contentRect.width), 1)
|
||||
let maxHeight = max(round(contentRect.height * layoutConfig.imageMaxHeightRatio), 1)
|
||||
return CGSize(width: maxWidth, height: maxHeight)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
Reference in New Issue
Block a user