// // RDReaderManager.swift // RDReaderDemo // // Created by yangsq on 2021/8/20. // import UIKit struct Book { var desc: String? var chapters: [Chapter] } struct Chapter { /// 第几章 var sort: Int /// 章节名称 var title: String? /// 划分了多少页 var pageCount: Int /// 内容 var content: String /// 分页 var pages: [ChapterPage] /// 文字的长度 var range: NSRange } struct ChapterPage { /// 第几章 var chapterSort: Int /// 第几页 var pageNum: Int /// 内容 var content: String /// 内容 var attrContent: NSAttributedString /// 文字的长度 var range: NSRange } public struct DemoBookItem { public var title: String public var source: BookSource public init(title: String, source: BookSource) { self.title = title self.source = source } } extension RDReaderManager { static let themeTypeKey = "themeType.key" static let brightValueKey = "brightValue.key" static let fontValueKey = "fontValue.key" static let lineSpaceKey = "lineSpace.key" static let pageCurlTypeKey = "pageCurlType.key" static let readProgressKey = "readProgress.key" } typealias BrightValue = CGFloat extension BrightValue: RDReaderObserverType { var cachesValue: CGFloat { return self } var value: CGFloat { return self } func transform(value: Any?) -> BrightValue { return (value as? BrightValue) ?? self } } extension Int: RDReaderObserverType { var cachesValue: Int { return self } var value: Int { return self } func transform(value: Any?) -> Int { return (value as? Int) ?? self } } extension String: RDReaderObserverType { var cachesValue: String { return self } var value: String { return self } func transform(value: Any?) -> String { return (value as? String) ?? self } } public class RDReaderManager: NSObject { public static let shared = RDReaderManager() let themeType = RDReaderObserver(key: RDReaderManager.themeTypeKey, defaultType: RDReaderCommon.Colors.white) let brightValue = RDReaderObserver(key: RDReaderManager.brightValueKey, defaultType: UIScreen.main.brightness) let fontValue = RDReaderObserver(key: RDReaderManager.fontValueKey, defaultType: CGFloat(15.0)) let lineSpaceType = RDReaderObserver(key: RDReaderManager.lineSpaceKey, defaultType: RDReaderCommon.SpaceType.min) let pageCurlTypeType = RDReaderObserver(key: RDReaderManager.pageCurlTypeKey, defaultType: RDReaderCommon.PageCurlType.pageCurl) let readProgress = RDReaderObserver(key: RDReaderManager.readProgressKey, defaultType: 0) let epubReadLocation = RDReaderObserver(key: "epubReadLocation.key", defaultType: "") let epubSelection = RDReaderObserver(key: "epubSelection.key", defaultType: "") let epubHighlights = RDReaderObserver(key: "epubHighlights.key", defaultType: "") } protocol RDReaderObserverType { associatedtype Value associatedtype CacheValue associatedtype CurrentType var cachesValue: CacheValue { get } var value: Value { get } func transform(value: Any?) -> CurrentType } class RDReaderObserver where CurrentType: RDReaderObserverType { private var _currentType: CurrentType? = nil private var key: String private var defaultType: CurrentType typealias Observer = ((CurrentType.Value) -> Void) private var observers = [WeakObserver]() var currentType: CurrentType { get { if _currentType != nil { return _currentType! } else { _currentType = (self.defaultType.transform(value: UserDefaults.standard.object(forKey: key)) as? CurrentType) ?? self.defaultType return _currentType! } } set { _currentType = newValue UserDefaults.standard.set(newValue.cachesValue, forKey: key) UserDefaults.standard.synchronize() observers.forEach { observer in if let observer = observer.observer?.observer { observer(_currentType!.value) } } } } init(key: String, defaultType: CurrentType) { self.key = key self.defaultType = defaultType } func observeChange(onNext: Observer?) { if let onNext = onNext { onNext(currentType.value) observers.append(WeakObserver(observer: onNext)) } } } private class WeakObserver { class _Observer: NSObject { var observer: T init(observer: T) { self.observer = observer super.init() } } weak var observer: _Observer? private var _strongObserver: _Observer? init(observer: T) { let t = _Observer(observer: observer) _strongObserver = t self.observer = _strongObserver } } extension RDReaderManager { func epubReaderConfiguration() -> RDEPUBReaderConfiguration { let theme = themeType.currentType.value return RDEPUBReaderConfiguration( fontSize: fontValue.currentType, lineHeightMultiple: lineSpaceType.currentType.mulitiple, displayType: pageCurlTypeType.currentType.value, landscapeDualPageEnabled: true, reflowableContentInsets: UIEdgeInsets(top: 40, left: 16, bottom: 40, right: 16), fixedContentInset: .zero, theme: RDEPUBReaderTheme( contentBackgroundColor: theme.contentBackgroudColor ?? .white, contentTextColor: theme.contentTextColor ?? .black, toolBackgroundColor: theme.toolBackgroudColor ?? .white, toolControlTextColor: theme.toolControlTextColor ?? .black, toolControlBorderUnselectColor: theme.toolControlBorderUnSelectColor ?? UIColor.lightGray.withAlphaComponent(0.5), toolLineColor: theme.toolLineColor ?? UIColor.lightGray.withAlphaComponent(0.5) ), fixedLayoutFit: .page, fixedLayoutSpreadMode: .automatic, textRenderingEngine: .dtCoreText ) } func epubPreferences( theme: RDReaderTheme? = nil, reflowableInsets: UIEdgeInsets, fixedContentInset: UIEdgeInsets ) -> RDEPUBPreferences { let resolvedTheme = theme ?? themeType.currentType.value let backgroundColorCSS = resolvedTheme.contentBackgroudColor?.ss_cssString return RDEPUBPreferences( fontSize: fontValue.currentType, lineHeightMultiple: lineSpaceType.currentType.mulitiple, reflowableContentInsets: reflowableInsets, fixedContentInset: fixedContentInset, themeBackgroundColor: backgroundColorCSS, themeTextColor: resolvedTheme.contentTextColor?.ss_cssString, fixedBackgroundColor: backgroundColorCSS, fixedLayoutFit: .page, fixedLayoutSpreadMode: .automatic ) } func bundledEPUBURLs(in bundle: Bundle = .main) -> [URL] { (bundle.urls(forResourcesWithExtension: "epub", subdirectory: nil) ?? []) .sorted { lhs, rhs in lhs.deletingPathExtension().lastPathComponent.localizedStandardCompare(rhs.deletingPathExtension().lastPathComponent) == .orderedAscending } } public func demoBookItems(in bundle: Bundle = .main) -> [DemoBookItem] { let textDemo = DemoBookItem(title: "宠她.txt", source: .textFile) let epubItems = bundledEPUBURLs(in: bundle).map { url in DemoBookItem(title: url.deletingPathExtension().lastPathComponent, source: .epubFile(url: url)) } return [textDemo] + epubItems } @discardableResult public func startReading(book item: DemoBookItem, from navigationController: UINavigationController?, animated: Bool = true) -> UIViewController? { let readerController: UIViewController switch item.source { case .textFile: guard let textURL = Bundle.main.url(forResource: "宠她", withExtension: "txt") else { return nil } readerController = RDURLReaderController(bookURL: textURL) case .epubFile(let url): readerController = RDURLReaderController( bookURL: url, epubConfiguration: epubReaderConfiguration() ) } readerController.title = item.title navigationController?.pushViewController(readerController, animated: animated) return readerController } func encodeTextFile(font: UIFont, lineSapce: CGFloat, size: CGSize) -> Book { let decodeString = String.encodeTextFile(url: URL(fileURLWithPath: Bundle.main.path(forResource: "宠她", ofType: ".txt")!)) let parten = "第[0-9一二三四五六七八九十百千]*[章回].*" var chapters = [Chapter]() var desc: String? if let expression = try? NSRegularExpression(pattern: parten, options: .caseInsensitive) { let results = expression.matches(in: decodeString, options: .reportCompletion, range: NSMakeRange(0, decodeString.count)) var startCount = -1 var lastTitle: String? for result in results { let index = results.firstIndex(of: result) let range = result.range if startCount != -1 && index! > 0 { let contentRange = NSMakeRange(startCount, range.location - startCount) let content = decodeString.substring(with: Range(contentRange, in: decodeString)!) let pages = chapterPages(content: content, font: font, lineSapce: lineSapce, size: size, chapterSort: index!) let chapter = Chapter(sort: index!,title: lastTitle, pageCount: pages.count, content: String(content), pages: pages, range: contentRange) chapters.append(chapter) if index == results.count - 1 { let contentRange = NSMakeRange(range.location, decodeString.count - range.location) let content = decodeString.substring(with: Range(contentRange, in: decodeString)!) let title = decodeString.substring(with: Range(range, in: decodeString)!) let pages = chapterPages(content: content, font: font, lineSapce: lineSapce, size: size, chapterSort: index!) let chapter = Chapter(sort: index! + 1, title: title, pageCount: pages.count, content: String(content), pages: pages, range: contentRange) chapters.append(chapter) } } startCount = range.location lastTitle = decodeString.substring(with: Range(range, in: decodeString)!) if index == 0 && range.location > 0 { desc = decodeString.substring(with: Range(NSMakeRange(0, range.location), in: decodeString)!) } } } return Book(desc: desc, chapters: chapters) } func chapterPages(content: String, font: UIFont, lineSapce: CGFloat, size: CGSize, chapterSort: Int) -> [ChapterPage] { let attrString = NSMutableAttributedString(string: content) let style = NSMutableParagraphStyle() style.lineSpacing = lineSapce attrString.addAttributes([NSAttributedString.Key.font : font, NSAttributedString.Key.paragraphStyle: style], range: NSMakeRange(0, content.count)) let pageRanges = attrString.pageRanges(size: size) let pages = pageRanges.map({ pageRange -> ChapterPage in let pageContent = content.substring(with: Range(pageRange, in: content)!) let pageAttrContent = attrString.attributedSubstring(from: pageRange) return ChapterPage(chapterSort: chapterSort, pageNum: pageRanges.firstIndex(of: pageRange)!, content: pageContent, attrContent: pageAttrContent, range: pageRange) }) return pages } }