231 lines
6.8 KiB
Swift
231 lines
6.8 KiB
Swift
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 富文本导入失败"
|
|
}
|
|
}
|
|
}
|