import UIKit public final class RDURLReaderController: UIViewController { private let bookURL: URL private let epubConfiguration: RDEPUBReaderConfiguration private var embeddedController: UIViewController? public init( bookURL: URL, epubConfiguration: RDEPUBReaderConfiguration = RDEPUBReaderConfiguration() ) { self.bookURL = bookURL self.epubConfiguration = epubConfiguration super.init(nibName: nil, bundle: nil) } @available(*, unavailable) required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } public override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .systemBackground title = bookURL.deletingPathExtension().lastPathComponent embedReaderController() } public func applyDemoDisplayType(_ displayType: RDReaderView.DisplayType) { readerController?.configuration.displayType = displayType logDemoState(prefix: "display=\(displayType.demoArgumentValue)") } @discardableResult public func goToDemoPage(_ pageNumber: Int, animated: Bool = false) -> Bool { let moved = readerController?.go(toPageNumber: pageNumber, animated: animated) ?? false if moved { logDemoState(prefix: "page=\(pageNumber)") } return moved } public func runDemoDisplaySequence( _ displayTypes: [RDReaderView.DisplayType], initialPageNumber: Int? = nil, stepDelay: TimeInterval = 1.0 ) { let normalizedDelay = max(stepDelay, 0.1) if let initialPageNumber { DispatchQueue.main.asyncAfter(deadline: .now() + normalizedDelay) { [weak self] in _ = self?.goToDemoPage(initialPageNumber) } } for (index, displayType) in displayTypes.enumerated() { let delay = normalizedDelay * Double(index + 1 + (initialPageNumber == nil ? 0 : 1)) DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in self?.applyDemoDisplayType(displayType) } } } private func embedReaderController() { let controller: UIViewController if bookURL.pathExtension.lowercased() == "epub" { controller = RDEPUBReaderController( epubURL: bookURL, configuration: epubConfiguration ) } else { let bookIdentifier = bookURL.lastPathComponent let bookTitle = bookURL.deletingPathExtension().lastPathComponent let pageSize = currentTextPageSize() let renderStyle = currentTextRenderStyle() let builder = RDPlainTextBookBuilder() if let textBook = try? builder.build(textFileURL: bookURL, pageSize: pageSize, style: renderStyle) { controller = RDEPUBReaderController( textBook: textBook, bookIdentifier: bookIdentifier, title: bookTitle, textFileURL: bookURL, configuration: epubConfiguration ) } else { // 分页失败时回退到纯文本展示 let fallback = UIViewController() let textView = UITextView() textView.isEditable = false textView.text = rd_decodeTextFile(url: bookURL) fallback.view = textView controller = fallback } } embeddedController = controller addChild(controller) controller.view.translatesAutoresizingMaskIntoConstraints = false view.addSubview(controller.view) NSLayoutConstraint.activate([ controller.view.topAnchor.constraint(equalTo: view.topAnchor), controller.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), controller.view.trailingAnchor.constraint(equalTo: view.trailingAnchor), controller.view.bottomAnchor.constraint(equalTo: view.bottomAnchor), ]) controller.didMove(toParent: self) } private var readerController: RDEPUBReaderController? { embeddedController as? RDEPUBReaderController } private func logDemoState(prefix: String) { guard let readerController else { return } let location = readerController.currentLocation let href = location?.href ?? "nil" let progression = location.map { String(format: "%.4f", $0.navigationProgression) } ?? "nil" let page = readerController.currentPageNumber.map(String.init) ?? "nil" print("[ReadViewDemo] automation \(prefix) -> page \(page) href \(href) progression \(progression)") } private func currentTextPageSize() -> CGSize { let viewportSize = UIScreen.main.bounds.size let insets = epubConfiguration.reflowableContentInsets return CGSize( width: max(viewportSize.width - insets.left - insets.right, 1), height: max(viewportSize.height - insets.top - insets.bottom, 1) ) } private func currentTextRenderStyle() -> RDEPUBTextRenderStyle { let font = UIFont.systemFont(ofSize: epubConfiguration.fontSize) let lineSpacing = max(font.lineHeight * (epubConfiguration.lineHeightMultiple - 1), 4) return RDEPUBTextRenderStyle( font: font, lineSpacing: lineSpacing, textColor: epubConfiguration.theme.contentTextColor, backgroundColor: epubConfiguration.theme.contentBackgroundColor ) } private func rd_decodeTextFile(url: URL) -> String { if let content = try? NSString(contentsOf: url, encoding: String.Encoding.utf8.rawValue) as String { return content } if let content = try? NSString(contentsOf: url, encoding: 0x80000632) as String { return content } if let content = try? NSString(contentsOf: url, encoding: 0x80000631) as String { return content } return "" } } private extension RDReaderView.DisplayType { var demoArgumentValue: String { switch self { case .pageCurl: return "pageCurl" case .horizontalScroll: return "horizontalScroll" case .verticalScroll: return "verticalScroll" } } }