refactor: split reader architecture and chrome handling

This commit is contained in:
shen
2026-05-31 21:47:54 +08:00
parent ea21c6a831
commit 44202357c0
80 changed files with 10635 additions and 8522 deletions
@@ -0,0 +1,135 @@
import UIKit
// MARK: -
/// EPUB
///
public enum RDEPUBTextRenderingEngine: Equatable {
/// DTCoreText HTML/CSS NSAttributedString
case dtCoreText
}
// MARK: -
/// EPUB
/// EPUBUI
/// RDEPUBReaderController
public struct RDEPUBReaderConfiguration: Equatable {
// MARK:
/// pt 15
public var fontSize: CGFloat
/// 1.6 1.6
public var lineHeightMultiple: CGFloat
///
public var numberOfColumns: Int
/// 20pt
public var columnGap: CGFloat
/// .pageCurl仿 .scroll
public var displayType: RDReaderView.DisplayType
///
public var landscapeDualPageEnabled: Bool
// MARK: UI
///
public var showsTableOfContents: Bool
///
public var allowsHighlights: Bool
///
public var showsSettingsPanel: Bool
// MARK:
/// reflowable 40pt 16pt
public var reflowableContentInsets: UIEdgeInsets
/// fixed layout
public var fixedContentInset: UIEdgeInsets
// MARK:
/// // .light
public var theme: RDEPUBReaderTheme
///
public var fixedLayoutFit: RDEPUBFixedLayoutFit
/// //
public var fixedLayoutSpreadMode: RDEPUBFixedLayoutSpreadMode
/// 使 DTCoreText
public var textRenderingEngine: RDEPUBTextRenderingEngine
// MARK:
///
/// - Parameters:
/// - fontSize: 15pt
/// - lineHeightMultiple: 1.6
/// - displayType: .pageCurl
/// - landscapeDualPageEnabled: true
/// - showsTableOfContents: true
/// - allowsHighlights: true
/// - showsSettingsPanel: true
/// - reflowableContentInsets:
/// - fixedContentInset:
/// - theme: .light
/// - fixedLayoutFit:
/// - fixedLayoutSpreadMode:
/// - textRenderingEngine:
public init(
fontSize: CGFloat = 15,
lineHeightMultiple: CGFloat = 1.6,
numberOfColumns: Int = 1,
columnGap: CGFloat = 20,
displayType: RDReaderView.DisplayType = .pageCurl,
landscapeDualPageEnabled: Bool = true,
showsTableOfContents: Bool = true,
allowsHighlights: Bool = true,
showsSettingsPanel: Bool = true,
reflowableContentInsets: UIEdgeInsets = UIEdgeInsets(top: 40, left: 16, bottom: 40, right: 16),
fixedContentInset: UIEdgeInsets = .zero,
theme: RDEPUBReaderTheme = .light,
fixedLayoutFit: RDEPUBFixedLayoutFit = .page,
fixedLayoutSpreadMode: RDEPUBFixedLayoutSpreadMode = .automatic,
textRenderingEngine: RDEPUBTextRenderingEngine = .dtCoreText
) {
self.fontSize = fontSize
self.lineHeightMultiple = lineHeightMultiple
self.numberOfColumns = max(1, numberOfColumns)
self.columnGap = max(0, columnGap)
self.displayType = displayType
self.landscapeDualPageEnabled = landscapeDualPageEnabled
self.showsTableOfContents = showsTableOfContents
self.allowsHighlights = allowsHighlights
self.showsSettingsPanel = showsSettingsPanel
self.reflowableContentInsets = reflowableContentInsets
self.fixedContentInset = fixedContentInset
self.theme = theme
self.fixedLayoutFit = fixedLayoutFit
self.fixedLayoutSpreadMode = fixedLayoutSpreadMode
self.textRenderingEngine = textRenderingEngine
}
/// 使
public static let `default` = RDEPUBReaderConfiguration()
}
// MARK: -
extension RDEPUBReaderConfiguration {
/// RDEPUBPreferences
/// - Returns: Core 使
func makePreferences() -> RDEPUBPreferences {
RDEPUBPreferences(
fontSize: fontSize,
lineHeightMultiple: lineHeightMultiple,
reflowableContentInsets: reflowableContentInsets,
fixedContentInset: fixedContentInset,
numberOfColumns: numberOfColumns,
columnGap: columnGap,
themeBackgroundColor: theme.themeBackgroundColorCSS,
themeTextColor: theme.themeTextColorCSS,
fixedBackgroundColor: theme.themeBackgroundColorCSS,
fixedLayoutFit: fixedLayoutFit,
fixedLayoutSpreadMode: fixedLayoutSpreadMode
)
}
}
@@ -0,0 +1,184 @@
import UIKit
// MARK: -
///
/// RDReaderView.DisplayType
public enum RDEPUBReaderDisplayMode: String, Codable, Equatable {
/// 仿
case pageCurl
///
case horizontalScroll
///
case verticalScroll
///
case horizontalCoverScroll
/// DisplayType UI DisplayMode
init(displayType: RDReaderView.DisplayType) {
switch displayType {
case .pageCurl:
self = .pageCurl
case .horizontalScroll:
self = .horizontalScroll
case .verticalScroll:
self = .verticalScroll
}
}
/// RDReaderView DisplayType
/// horizontalCoverScroll horizontalScroll
var displayType: RDReaderView.DisplayType {
switch self {
case .pageCurl:
return .pageCurl
case .horizontalScroll, .horizontalCoverScroll:
return .horizontalScroll
case .verticalScroll:
return .verticalScroll
}
}
}
// MARK: -
///
/// RDEPUBReaderTheme
public enum RDEPUBReaderThemePreset: String, Codable, CaseIterable, Equatable {
///
case light
///
case yellow
/// 绿
case green
///
case pink
///
case blue
/// /
case dark
///
public var theme: RDEPUBReaderTheme {
switch self {
case .light:
return .light
case .yellow:
return .yellow
case .green:
return .green
case .pink:
return .pink
case .blue:
return .blue
case .dark:
return .dark
}
}
///
/// nil
public init?(theme: RDEPUBReaderTheme) {
switch theme {
case .light:
self = .light
case .yellow:
self = .yellow
case .green:
self = .green
case .pink:
self = .pink
case .blue:
self = .blue
case .dark:
self = .dark
default:
return nil
}
}
}
// MARK: -
///
///
///
public struct RDEPUBReaderSettings: Codable, Equatable {
// MARK:
/// 0.0 ~ 1.0nil 使
public var brightness: CGFloat?
/// ptnil 使
public var fontSize: CGFloat?
/// nil 使
public var lineHeightMultiple: CGFloat?
/// nil 使
public var numberOfColumns: Int?
/// nil 使
public var displayMode: RDEPUBReaderDisplayMode?
/// nil 使
public var themePreset: RDEPUBReaderThemePreset?
/// nil
public init(
brightness: CGFloat? = nil,
fontSize: CGFloat? = nil,
lineHeightMultiple: CGFloat? = nil,
numberOfColumns: Int? = nil,
displayMode: RDEPUBReaderDisplayMode? = nil,
themePreset: RDEPUBReaderThemePreset? = nil
) {
self.brightness = brightness
self.fontSize = fontSize
self.lineHeightMultiple = lineHeightMultiple
self.numberOfColumns = numberOfColumns
self.displayMode = displayMode
self.themePreset = themePreset
}
///
/// nil
/// - Parameter configuration: RDEPUBReaderConfiguration.default
/// - Returns:
public func applying(to configuration: RDEPUBReaderConfiguration) -> RDEPUBReaderConfiguration {
var resolvedConfiguration = configuration
if let fontSize {
resolvedConfiguration.fontSize = fontSize
}
if let lineHeightMultiple {
resolvedConfiguration.lineHeightMultiple = lineHeightMultiple
}
if let numberOfColumns {
resolvedConfiguration.numberOfColumns = max(1, numberOfColumns)
}
if let displayMode {
resolvedConfiguration.displayType = displayMode.displayType
}
if let themePreset {
resolvedConfiguration.theme = themePreset.theme
}
return resolvedConfiguration
}
///
/// 0.0 ~ 1.0
/// - Parameters:
/// - configuration:
/// - brightness:
/// - Returns:
public static func capture(
configuration: RDEPUBReaderConfiguration,
brightness: CGFloat
) -> RDEPUBReaderSettings {
RDEPUBReaderSettings(
brightness: max(0, min(1, brightness)),
fontSize: configuration.fontSize,
lineHeightMultiple: configuration.lineHeightMultiple,
numberOfColumns: configuration.numberOfColumns,
displayMode: RDEPUBReaderDisplayMode(displayType: configuration.displayType),
themePreset: RDEPUBReaderThemePreset(theme: configuration.theme)
)
}
}
@@ -0,0 +1,324 @@
import UIKit
// MARK: -
/// EPUB
///
/// iOS 13+ SF Symbols 退
final class RDEPUBReaderSettingsViewController: UIViewController {
// MARK:
/// 0.0 ~ 1.0
var onBrightnessChange: ((CGFloat) -> Void)?
/// pt
var onFontSizeChange: ((CGFloat) -> Void)?
///
var onLineHeightChange: ((CGFloat) -> Void)?
///
var onColumnCountChange: ((Int) -> Void)?
///
var onDisplayTypeChange: ((RDReaderView.DisplayType) -> Void)?
///
var onThemeChange: ((RDEPUBReaderTheme) -> Void)?
// MARK:
///
private enum ThemePreset: Int, CaseIterable {
case light
case yellow
case green
case pink
case blue
case dark
var title: String {
switch self {
case .light: return "浅色"
case .yellow: return "米黄"
case .green: return "青绿"
case .pink: return "粉色"
case .blue: return "蓝灰"
case .dark: return "夜间"
}
}
var theme: RDEPUBReaderTheme {
switch self {
case .light: return .light
case .yellow: return .yellow
case .green: return .green
case .pink: return .pink
case .blue: return .blue
case .dark: return .dark
}
}
}
private let scrollView = UIScrollView()
private let contentStack: UIStackView = {
let stackView = UIStackView()
stackView.axis = .vertical
stackView.spacing = 20
return stackView
}()
private let brightnessSlider = UISlider()
private let fontValueLabel = UILabel()
private let decreaseFontButton = UIButton(type: .system)
private let increaseFontButton = UIButton(type: .system)
private let lineHeightControl = UISegmentedControl(items: ["紧凑", "标准", "宽松"])
private let columnCountControl = UISegmentedControl(items: ["单栏", "双栏"])
private let displayTypeControl = UISegmentedControl(items: ["仿真", "横滑", "竖滑"])
private let themeStackView: UIStackView = {
let stackView = UIStackView()
stackView.axis = .horizontal
stackView.distribution = .fillEqually
stackView.spacing = 12
return stackView
}()
private var themeButtons: [UIButton] = []
/// 1.3 1.6 1.9
private let lineHeightValues: [CGFloat] = [1.3, 1.6, 1.9]
///
private var currentConfiguration: RDEPUBReaderConfiguration
init(configuration: RDEPUBReaderConfiguration, brightness: CGFloat) {
self.currentConfiguration = configuration
super.init(nibName: nil, bundle: nil)
brightnessSlider.value = Float(brightness)
title = "样式设置"
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
setupNavigationItems()
setupViews()
syncControls()
applyTheme(currentConfiguration.theme)
}
private func setupNavigationItems() {
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "完成", style: .done, target: self, action: #selector(doneAction))
}
private func setupViews() {
view.addSubview(scrollView)
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(contentStack)
contentStack.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
contentStack.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor, constant: 16),
contentStack.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor, constant: -16),
contentStack.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 20),
contentStack.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: -24),
contentStack.widthAnchor.constraint(equalTo: scrollView.widthAnchor, constant: -32)
])
brightnessSlider.minimumValue = 0
brightnessSlider.maximumValue = 1
brightnessSlider.addTarget(self, action: #selector(brightnessChanged(_:)), for: .valueChanged)
fontValueLabel.font = UIFont.monospacedDigitSystemFont(ofSize: 16, weight: .semibold)
fontValueLabel.textAlignment = .center
fontValueLabel.setContentHuggingPriority(.required, for: .horizontal)
configureFontButton(decreaseFontButton, title: "A-")
configureFontButton(increaseFontButton, title: "A+")
decreaseFontButton.addTarget(self, action: #selector(decreaseFontAction), for: .touchUpInside)
increaseFontButton.addTarget(self, action: #selector(increaseFontAction), for: .touchUpInside)
lineHeightControl.addTarget(self, action: #selector(lineHeightChanged(_:)), for: .valueChanged)
columnCountControl.addTarget(self, action: #selector(columnCountChanged(_:)), for: .valueChanged)
displayTypeControl.addTarget(self, action: #selector(displayTypeChanged(_:)), for: .valueChanged)
ThemePreset.allCases.forEach { preset in
let button = UIButton(type: .system)
button.tag = preset.rawValue
button.layer.cornerRadius = 18
button.layer.borderWidth = 1.5
button.backgroundColor = preset.theme.contentBackgroundColor
button.accessibilityLabel = preset.title
button.addTarget(self, action: #selector(themeButtonAction(_:)), for: .touchUpInside)
themeButtons.append(button)
themeStackView.addArrangedSubview(button)
NSLayoutConstraint.activate([
button.heightAnchor.constraint(equalToConstant: 36)
])
}
contentStack.addArrangedSubview(makeSection(title: "亮度", content: brightnessSlider))
contentStack.addArrangedSubview(makeSection(title: "字号", content: makeFontSizeRow()))
contentStack.addArrangedSubview(makeSection(title: "行距", content: lineHeightControl))
contentStack.addArrangedSubview(makeSection(title: "分栏", content: columnCountControl))
contentStack.addArrangedSubview(makeSection(title: "翻页方式", content: displayTypeControl))
contentStack.addArrangedSubview(makeSection(title: "主题", content: themeStackView))
}
private func makeSection(title: String, content: UIView) -> UIView {
let container = UIStackView()
container.axis = .vertical
container.spacing = 10
let titleLabel = UILabel()
titleLabel.text = title
titleLabel.font = UIFont.systemFont(ofSize: 14, weight: .semibold)
content.translatesAutoresizingMaskIntoConstraints = false
container.addArrangedSubview(titleLabel)
container.addArrangedSubview(content)
return container
}
private func makeFontSizeRow() -> UIView {
let stackView = UIStackView(arrangedSubviews: [decreaseFontButton, fontValueLabel, increaseFontButton])
stackView.axis = .horizontal
stackView.alignment = .center
stackView.distribution = .fill
stackView.spacing = 12
NSLayoutConstraint.activate([
decreaseFontButton.widthAnchor.constraint(equalToConstant: 64),
increaseFontButton.widthAnchor.constraint(equalToConstant: 64),
decreaseFontButton.heightAnchor.constraint(equalToConstant: 36),
increaseFontButton.heightAnchor.constraint(equalToConstant: 36)
])
return stackView
}
private func configureFontButton(_ button: UIButton, title: String) {
button.setTitle(title, for: .normal)
button.titleLabel?.font = UIFont.systemFont(ofSize: 16, weight: .semibold)
button.layer.cornerRadius = 18
button.layer.borderWidth = 1
}
private func syncControls() {
fontValueLabel.text = String(Int(currentConfiguration.fontSize.rounded()))
let lineHeightIndex = lineHeightValues.enumerated().min { abs($0.element - currentConfiguration.lineHeightMultiple) < abs($1.element - currentConfiguration.lineHeightMultiple) }?.offset ?? 1
lineHeightControl.selectedSegmentIndex = lineHeightIndex
columnCountControl.selectedSegmentIndex = currentConfiguration.numberOfColumns > 1 ? 1 : 0
switch currentConfiguration.displayType {
case .pageCurl:
displayTypeControl.selectedSegmentIndex = 0
case .horizontalScroll:
displayTypeControl.selectedSegmentIndex = 1
case .verticalScroll:
displayTypeControl.selectedSegmentIndex = 2
}
let selectedPreset = ThemePreset.allCases.first(where: { $0.theme == currentConfiguration.theme }) ?? .light
updateThemeSelection(selectedPreset)
}
private func applyTheme(_ theme: RDEPUBReaderTheme) {
view.backgroundColor = theme.contentBackgroundColor
scrollView.backgroundColor = theme.contentBackgroundColor
contentStack.arrangedSubviews
.compactMap { $0 as? UIStackView }
.flatMap { $0.arrangedSubviews }
.compactMap { $0 as? UILabel }
.forEach { $0.textColor = theme.contentTextColor }
fontValueLabel.textColor = theme.contentTextColor
[decreaseFontButton, increaseFontButton].forEach { button in
button.setTitleColor(theme.toolControlTextColor, for: .normal)
button.layer.borderColor = theme.toolControlBorderUnselectColor.cgColor
button.backgroundColor = theme.toolBackgroundColor
}
[lineHeightControl, columnCountControl, displayTypeControl].forEach { control in
control.backgroundColor = theme.toolBackgroundColor
if #available(iOS 13.0, *) {
control.selectedSegmentTintColor = theme.toolControlTextColor.withAlphaComponent(0.14)
} else {
control.tintColor = theme.toolControlTextColor
}
control.setTitleTextAttributes([.foregroundColor: theme.contentTextColor], for: .normal)
control.setTitleTextAttributes([.foregroundColor: theme.toolControlTextColor], for: .selected)
}
navigationController?.navigationBar.tintColor = theme.toolControlTextColor
navigationController?.navigationBar.barTintColor = theme.toolBackgroundColor
navigationController?.navigationBar.titleTextAttributes = [.foregroundColor: theme.toolControlTextColor]
updateThemeSelection(ThemePreset.allCases.first(where: { $0.theme == theme }) ?? .light)
}
private func updateThemeSelection(_ preset: ThemePreset) {
themeButtons.forEach { button in
let isSelected = button.tag == preset.rawValue
button.layer.borderWidth = isSelected ? 2 : 1
button.layer.borderColor = isSelected ? currentConfiguration.theme.toolControlTextColor.cgColor : currentConfiguration.theme.toolControlBorderUnselectColor.cgColor
}
}
@objc private func doneAction() {
dismiss(animated: true)
}
@objc private func brightnessChanged(_ slider: UISlider) {
onBrightnessChange?(CGFloat(slider.value))
}
@objc private func decreaseFontAction() {
let nextValue = max(12, currentConfiguration.fontSize - 1)
guard nextValue != currentConfiguration.fontSize else { return }
currentConfiguration.fontSize = nextValue
fontValueLabel.text = String(Int(nextValue.rounded()))
onFontSizeChange?(nextValue)
}
@objc private func increaseFontAction() {
let nextValue = min(36, currentConfiguration.fontSize + 1)
guard nextValue != currentConfiguration.fontSize else { return }
currentConfiguration.fontSize = nextValue
fontValueLabel.text = String(Int(nextValue.rounded()))
onFontSizeChange?(nextValue)
}
@objc private func lineHeightChanged(_ control: UISegmentedControl) {
let index = max(0, min(control.selectedSegmentIndex, lineHeightValues.count - 1))
let value = lineHeightValues[index]
currentConfiguration.lineHeightMultiple = value
onLineHeightChange?(value)
}
@objc private func columnCountChanged(_ control: UISegmentedControl) {
let numberOfColumns = control.selectedSegmentIndex == 1 ? 2 : 1
currentConfiguration.numberOfColumns = numberOfColumns
onColumnCountChange?(numberOfColumns)
}
@objc private func displayTypeChanged(_ control: UISegmentedControl) {
let displayType: RDReaderView.DisplayType
switch control.selectedSegmentIndex {
case 1:
displayType = .horizontalScroll
case 2:
displayType = .verticalScroll
default:
displayType = .pageCurl
}
currentConfiguration.displayType = displayType
onDisplayTypeChange?(displayType)
}
@objc private func themeButtonAction(_ sender: UIButton) {
guard let preset = ThemePreset(rawValue: sender.tag) else { return }
currentConfiguration.theme = preset.theme
applyTheme(preset.theme)
onThemeChange?(preset.theme)
}
}
@@ -0,0 +1,126 @@
import UIKit
// MARK: -
/// EPUB
///
/// 绿
public struct RDEPUBReaderTheme: Equatable {
// MARK:
///
public var contentBackgroundColor: UIColor
///
public var contentTextColor: UIColor
// MARK:
///
public var toolBackgroundColor: UIColor
/// /
public var toolControlTextColor: UIColor
///
public var toolControlBorderUnselectColor: UIColor
/// 线
public var toolLineColor: UIColor
///
/// - Parameters:
/// - contentBackgroundColor:
/// - contentTextColor:
/// - toolBackgroundColor:
/// - toolControlTextColor:
/// - toolControlBorderUnselectColor:
/// - toolLineColor: 线
public init(
contentBackgroundColor: UIColor,
contentTextColor: UIColor,
toolBackgroundColor: UIColor,
toolControlTextColor: UIColor,
toolControlBorderUnselectColor: UIColor,
toolLineColor: UIColor
) {
self.contentBackgroundColor = contentBackgroundColor
self.contentTextColor = contentTextColor
self.toolBackgroundColor = toolBackgroundColor
self.toolControlTextColor = toolControlTextColor
self.toolControlBorderUnselectColor = toolControlBorderUnselectColor
self.toolLineColor = toolLineColor
}
// MARK:
///
public static let light = RDEPUBReaderTheme(
contentBackgroundColor: .white,
contentTextColor: .black,
toolBackgroundColor: .white,
toolControlTextColor: .black,
toolControlBorderUnselectColor: UIColor.lightGray.withAlphaComponent(0.5),
toolLineColor: UIColor.lightGray.withAlphaComponent(0.5)
)
///
public static let dark = RDEPUBReaderTheme(
contentBackgroundColor: .black,
contentTextColor: .white,
toolBackgroundColor: .black,
toolControlTextColor: .white,
toolControlBorderUnselectColor: UIColor.lightGray.withAlphaComponent(0.5),
toolLineColor: UIColor.lightGray.withAlphaComponent(0.5)
)
///
public static let yellow = RDEPUBReaderTheme(
contentBackgroundColor: UIColor(red: 0.89, green: 0.87, blue: 0.79, alpha: 1),
contentTextColor: .black,
toolBackgroundColor: UIColor(red: 0.89, green: 0.87, blue: 0.79, alpha: 1),
toolControlTextColor: .black,
toolControlBorderUnselectColor: UIColor.lightGray.withAlphaComponent(0.5),
toolLineColor: UIColor.lightGray.withAlphaComponent(0.5)
)
/// 绿绿
public static let green = RDEPUBReaderTheme(
contentBackgroundColor: UIColor(red: 0.87, green: 0.91, blue: 0.82, alpha: 1),
contentTextColor: .black,
toolBackgroundColor: UIColor(red: 0.87, green: 0.91, blue: 0.82, alpha: 1),
toolControlTextColor: .black,
toolControlBorderUnselectColor: UIColor.lightGray.withAlphaComponent(0.5),
toolLineColor: UIColor.lightGray.withAlphaComponent(0.5)
)
///
public static let pink = RDEPUBReaderTheme(
contentBackgroundColor: UIColor(red: 1, green: 0.89, blue: 0.91, alpha: 1),
contentTextColor: .black,
toolBackgroundColor: UIColor(red: 1, green: 0.89, blue: 0.91, alpha: 1),
toolControlTextColor: .black,
toolControlBorderUnselectColor: UIColor.lightGray.withAlphaComponent(0.5),
toolLineColor: UIColor.lightGray.withAlphaComponent(0.5)
)
///
public static let blue = RDEPUBReaderTheme(
contentBackgroundColor: UIColor(red: 0.8, green: 0.84, blue: 0.89, alpha: 1),
contentTextColor: .black,
toolBackgroundColor: UIColor(red: 0.8, green: 0.84, blue: 0.89, alpha: 1),
toolControlTextColor: .black,
toolControlBorderUnselectColor: UIColor.lightGray.withAlphaComponent(0.5),
toolLineColor: UIColor.lightGray.withAlphaComponent(0.5)
)
}
// MARK: - CSS
extension RDEPUBReaderTheme {
/// CSS EPUB HTML
var themeBackgroundColorCSS: String {
contentBackgroundColor.ss_cssString
}
/// CSS EPUB HTML
var themeTextColorCSS: String {
contentTextColor.ss_cssString
}
}