import UIKit public extension NSAttributedString.Key { static let rdPageBlockRange = NSAttributedString.Key("com.RDEpubReader.epub.pageBlockRange") static let rdPageBlockIndex = NSAttributedString.Key("com.RDEpubReader.epub.pageBlockIndex") static let rdPageFragmentID = NSAttributedString.Key("com.RDEpubReader.epub.pageFragmentID") static let rdPageAttachmentKind = NSAttributedString.Key("com.RDEpubReader.epub.pageAttachmentKind") static let rdPageBlockKind = NSAttributedString.Key("com.RDEpubReader.epub.pageBlockKind") static let rdPageSemanticHints = NSAttributedString.Key("com.RDEpubReader.epub.pageSemanticHints") static let rdPageAttachmentPlacement = NSAttributedString.Key("com.RDEpubReader.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 frameWidth: CGFloat public var frameHeight: CGFloat public var edgeInsets: UIEdgeInsets public var numberOfColumns: Int public var columnGap: CGFloat public var avoidOrphans: Bool public var avoidWidows: Bool public var avoidPageBreakInsideEnabled: Bool public var hyphenation: Bool public var imageMaxHeightRatio: CGFloat public var fallbackViewportSize: CGSize public init( frameWidth: CGFloat = 0, frameHeight: CGFloat = 0, edgeInsets: UIEdgeInsets = .zero, numberOfColumns: Int = 1, columnGap: CGFloat = 20, avoidOrphans: Bool = true, avoidWidows: Bool = true, avoidPageBreakInsideEnabled: Bool = true, hyphenation: Bool = true, imageMaxHeightRatio: CGFloat = 0.85, fallbackViewportSize: CGSize = CGSize(width: 375, height: 667) ) { self.frameWidth = frameWidth self.frameHeight = frameHeight self.edgeInsets = edgeInsets self.numberOfColumns = max(1, numberOfColumns) self.columnGap = max(0, columnGap) self.avoidOrphans = avoidOrphans self.avoidWidows = avoidWidows self.avoidPageBreakInsideEnabled = avoidPageBreakInsideEnabled self.hyphenation = hyphenation self.imageMaxHeightRatio = imageMaxHeightRatio self.fallbackViewportSize = fallbackViewportSize } public static let `default` = RDEPUBTextLayoutConfig() public func resolvedFrameSize(fallback pageSize: CGSize) -> CGSize { CGSize( width: max(frameWidth > 0 ? frameWidth : pageSize.width, 1), height: max(frameHeight > 0 ? frameHeight : pageSize.height, 1) ) } public func contentRect(fallback pageSize: CGSize) -> CGRect { let size = resolvedFrameSize(fallback: pageSize) return CGRect(origin: .zero, size: size).inset(by: edgeInsets) } public func columnRects(fallback pageSize: CGSize) -> [CGRect] { let rect = contentRect(fallback: pageSize) let columns = max(1, numberOfColumns) guard columns > 1 else { return [rect] } let totalGap = CGFloat(columns - 1) * columnGap let columnWidth = max((rect.width - totalGap) / CGFloat(columns), 1) return (0.. 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 富文本导入失败" } } }