import UIKit public extension NSAttributedString.Key { static let rdPageBlockRange = NSAttributedString.Key("com.rdreader.epub.pageBlockRange") static let rdPageBlockIndex = NSAttributedString.Key("com.rdreader.epub.pageBlockIndex") static let rdPageFragmentID = NSAttributedString.Key("com.rdreader.epub.pageFragmentID") static let rdPageAttachmentKind = NSAttributedString.Key("com.rdreader.epub.pageAttachmentKind") static let rdPageBlockKind = NSAttributedString.Key("com.rdreader.epub.pageBlockKind") static let rdPageSemanticHints = NSAttributedString.Key("com.rdreader.epub.pageSemanticHints") static let rdPageAttachmentPlacement = NSAttributedString.Key("com.rdreader.epub.pageAttachmentPlacement") } public enum RDEPUBTextBlockKind: String, Codable, Equatable, CaseIterable { case paragraph case list case table case code case blockquote case attachment case generic } public enum RDEPUBTextSemanticHint: String, Codable, Equatable, CaseIterable { case avoidPageBreakInside case keepWithNext case pageBreakBefore case pageBreakAfter case pageRelate } public enum RDEPUBTextAttachmentPlacement: String, Codable, Equatable { case inline case baseline case centered } public struct RDEPUBTextRenderStyle { public var font: UIFont public var lineSpacing: CGFloat public var textColor: UIColor? public var backgroundColor: UIColor? public init(font: UIFont, lineSpacing: CGFloat, textColor: UIColor? = nil, backgroundColor: UIColor? = nil) { self.font = font self.lineSpacing = lineSpacing self.textColor = textColor self.backgroundColor = backgroundColor } } public struct RDEPUBTextLayoutConfig: Equatable { public var avoidOrphans: Bool public var avoidWidows: Bool public var avoidPageBreakInsideEnabled: Bool public var imageMaxHeightRatio: CGFloat public init( avoidOrphans: Bool = true, avoidWidows: Bool = true, avoidPageBreakInsideEnabled: Bool = true, imageMaxHeightRatio: CGFloat = 0.85 ) { self.avoidOrphans = avoidOrphans self.avoidWidows = avoidWidows self.avoidPageBreakInsideEnabled = avoidPageBreakInsideEnabled self.imageMaxHeightRatio = imageMaxHeightRatio } public static let `default` = RDEPUBTextLayoutConfig() } public enum RDEPUBTextStyleSheetLayerKind: String, CaseIterable, Equatable { case `default` case replace case dark case epub case user } public struct RDEPUBTextStyleSheetLayer: Equatable { public var kind: RDEPUBTextStyleSheetLayerKind public var css: String public init(kind: RDEPUBTextStyleSheetLayerKind, css: String) { self.kind = kind self.css = css } } public struct RDEPUBTextStyleSheetPackage: Equatable { public var layers: [RDEPUBTextStyleSheetLayer] public init(layers: [RDEPUBTextStyleSheetLayer]) { self.layers = layers } public var combinedCSS: String { layers .filter { !$0.css.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty } .map { layer in "/* \(layer.kind.rawValue) */\n\(layer.css)" } .joined(separator: "\n\n") } } public enum RDEPUBTextResourceReferenceKind: String, Equatable { case stylesheet case image } public struct RDEPUBTextResourceReferenceDiagnostic: Equatable { public var kind: RDEPUBTextResourceReferenceKind public var chapterHref: String public var originalReference: String public var normalizedHref: String? public var resolvedFileURL: URL? public var existsOnDisk: Bool public init( kind: RDEPUBTextResourceReferenceKind, chapterHref: String, originalReference: String, normalizedHref: String?, resolvedFileURL: URL?, existsOnDisk: Bool ) { self.kind = kind self.chapterHref = chapterHref self.originalReference = originalReference self.normalizedHref = normalizedHref self.resolvedFileURL = resolvedFileURL self.existsOnDisk = existsOnDisk } } public struct RDEPUBTextChapterContext: Equatable { public var href: String public var title: String public var html: String public var baseURL: URL? public var stylesheet: RDEPUBTextStyleSheetPackage public var resourceDiagnostics: [RDEPUBTextResourceReferenceDiagnostic] public init( href: String, title: String, html: String, baseURL: URL?, stylesheet: RDEPUBTextStyleSheetPackage, resourceDiagnostics: [RDEPUBTextResourceReferenceDiagnostic] ) { self.href = href self.title = title self.html = html self.baseURL = baseURL self.stylesheet = stylesheet self.resourceDiagnostics = resourceDiagnostics } } public struct RDEPUBTextChapterRenderRequest { public var context: RDEPUBTextChapterContext public var style: RDEPUBTextRenderStyle public init(context: RDEPUBTextChapterContext, style: RDEPUBTextRenderStyle) { self.context = context self.style = style } } public struct RDEPUBRenderedChapterContent { public var attributedString: NSAttributedString public var fragmentOffsets: [String: Int] public var resourceDiagnostics: [RDEPUBTextResourceReferenceDiagnostic] public init( attributedString: NSAttributedString, fragmentOffsets: [String: Int], resourceDiagnostics: [RDEPUBTextResourceReferenceDiagnostic] = [] ) { self.attributedString = attributedString self.fragmentOffsets = fragmentOffsets self.resourceDiagnostics = resourceDiagnostics } } public protocol RDEPUBTextRenderer { func renderChapter( request: RDEPUBTextChapterRenderRequest ) throws -> RDEPUBRenderedChapterContent func renderChapter( html: String, baseURL: URL?, style: RDEPUBTextRenderStyle ) throws -> RDEPUBRenderedChapterContent } public extension RDEPUBTextRenderer { func renderChapter( html: String, baseURL: URL?, style: RDEPUBTextRenderStyle ) throws -> RDEPUBRenderedChapterContent { let context = RDEPUBTextChapterContext( href: "", title: "", html: html, baseURL: baseURL, stylesheet: RDEPUBTextStyleSheetPackage(layers: []), resourceDiagnostics: [] ) return try renderChapter(request: RDEPUBTextChapterRenderRequest(context: context, style: style)) } } public enum RDEPUBTextRenderingError: LocalizedError { case htmlEncodingFailed case htmlImportFailed public var errorDescription: String? { switch self { case .htmlEncodingFailed: return "HTML 编码失败" case .htmlImportFailed: return "HTML 富文本导入失败" } } }