2 // FolioReaderKit.swift
5 // Created by Heberti Almeida on 08/04/15.
6 // Copyright (c) 2015 Folio Reader. All rights reserved.
12 // MARK: - Internal constants
14 internal let kApplicationDocumentsDirectory = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
15 internal let kCurrentFontFamily = "com.folioreader.kCurrentFontFamily"
16 internal let kCurrentFontSize = "com.folioreader.kCurrentFontSize"
17 internal let kCurrentMarginSize = "com.folioreader.kCurrentMarginSize"
18 internal let kCurrentInterlineSize = "com.folioreader.kCurrentInterlineSize"
19 internal let kCurrentAudioRate = "com.folioreader.kCurrentAudioRate"
20 internal let kCurrentHighlightStyle = "com.folioreader.kCurrentHighlightStyle"
21 internal let kCurrentMediaOverlayStyle = "com.folioreader.kMediaOverlayStyle"
22 internal let kCurrentScrollDirection = "com.folioreader.kCurrentScrollDirection"
23 internal let kNightMode = "com.folioreader.kNightMode"
24 internal let kCurrentTOCMenu = "com.folioreader.kCurrentTOCMenu"
25 internal let kHighlightRange = 30
26 internal let kReuseCellIdentifier = "com.folioreader.Cell.ReuseIdentifier"
28 public enum FolioReaderError: Error, LocalizedError {
32 case authorNameNotAvailable
33 case coverNotAvailable
34 case invalidImage(path: String)
35 case titleNotAvailable
38 public var errorDescription: String? {
40 case .bookNotAvailable:
41 return "Book not found"
42 case .errorInContainer, .errorInOpf:
43 return "Invalid book format"
44 case .authorNameNotAvailable:
45 return "Author name not available"
46 case .coverNotAvailable:
47 return "Cover image not available"
48 case let .invalidImage(path):
49 return "Invalid image at path: " + path
50 case .titleNotAvailable:
51 return "Book title not available"
53 return "Book corrupted"
58 /// Defines the media overlay and TTS selection
60 /// - `default`: The background is colored
61 /// - underline: The underlined is colored
62 /// - textColor: The text is colored
63 public enum MediaOverlayStyle: Int {
72 func className() -> String {
73 return "mediaOverlayStyle\(self.rawValue)"
77 /// FolioReader actions delegate
78 @objc public protocol FolioReaderDelegate: class {
80 /// Did finished loading book.
83 /// - folioReader: The FolioReader instance
84 /// - book: The Book instance
85 @objc optional func folioReader(_ folioReader: FolioReader, didFinishedLoading book: FRBook)
87 /// Called when reader did closed.
89 /// - Parameter folioReader: The FolioReader instance
90 @objc optional func folioReaderDidClose(_ folioReader: FolioReader)
92 /// Called when reader did closed.
93 @available(*, deprecated, message: "Use 'folioReaderDidClose(_ folioReader: FolioReader)' instead.")
94 @objc optional func folioReaderDidClosed()
97 /// Main Library class with some useful constants and methods
98 open class FolioReader: NSObject {
100 public override init() { }
106 /// Custom unzip path
107 open var unzipPath: String?
109 /// FolioReaderDelegate
110 open weak var delegate: FolioReaderDelegate?
112 open weak var readerContainer: FolioReaderContainer?
113 open weak var readerAudioPlayer: FolioReaderAudioPlayer?
114 open weak var readerCenter: FolioReaderCenter? {
115 return self.readerContainer?.centerViewController
118 /// Check if reader is open
119 var isReaderOpen = false
121 /// Check if reader is open and ready
122 var isReaderReady = false
124 /// Check if layout needs to change to fit Right To Left
125 var needsRTLChange: Bool {
126 return (self.readerContainer?.book.spine.isRtl == true && self.readerContainer?.readerConfig.scrollDirection == .horizontal)
129 func isNight<T>(_ f: T, _ l: T) -> T {
130 return (self.nightMode == true ? f : l)
133 /// UserDefault for the current ePub file.
134 fileprivate var defaults: FolioReaderUserDefaults {
135 return FolioReaderUserDefaults(withIdentifier: self.readerContainer?.readerConfig.identifier)
138 // Add necessary observers
139 fileprivate func addObservers() {
141 NotificationCenter.default.addObserver(self, selector: #selector(saveReaderState), name: .UIApplicationWillResignActive, object: nil)
142 NotificationCenter.default.addObserver(self, selector: #selector(saveReaderState), name: .UIApplicationWillTerminate, object: nil)
145 /// Remove necessary observers
146 fileprivate func removeObservers() {
147 NotificationCenter.default.removeObserver(self, name: .UIApplicationWillResignActive, object: nil)
148 NotificationCenter.default.removeObserver(self, name: .UIApplicationWillTerminate, object: nil)
151 public func getProgressValues() -> (currentPage: Int, totalPages: Int)? {
152 guard let center = readerCenter else { return nil}
153 let totalPages = center.totalPages
154 let currentPage = center.currentPageNumber
155 return (currentPage, totalPages)
160 // MARK: - Present FolioReader
162 extension FolioReader {
164 /// Present a Folio Reader Container modally on a Parent View Controller.
167 /// - parentViewController: View Controller that will present the reader container.
168 /// - epubPath: String representing the path on the disk of the ePub file. Must not be nil nor empty string.
169 /// - unzipPath: Path to unzip the compressed epub.
170 /// - config: FolioReader configuration.
171 /// - shouldRemoveEpub: Boolean to remove the epub or not. Default true.
172 /// - animated: Pass true to animate the presentation; otherwise, pass false.
173 open func presentReader(parentViewController: UIViewController, withEpubPath epubPath: String, unzipPath: String? = nil, andConfig config: FolioReaderConfig, shouldRemoveEpub: Bool = true, animated:
175 let readerContainer = FolioReaderContainer(withConfig: config, folioReader: self, epubPath: epubPath, unzipPath: unzipPath, removeEpub: shouldRemoveEpub)
176 self.readerContainer = readerContainer
177 parentViewController.present(readerContainer, animated: animated, completion: nil)
182 // MARK: - Getters and setters for stored values
184 extension FolioReader {
186 public func register(defaults: [String: Any]) {
187 self.defaults.register(defaults: defaults)
190 /// Check if current theme is Night mode
191 open var nightMode: Bool {
192 get { return self.defaults.bool(forKey: kNightMode) }
194 self.defaults.set(value, forKey: kNightMode)
196 if let readerCenter = self.readerCenter {
197 UIView.animate(withDuration: 0.6, animations: {
198 _ = readerCenter.currentPage?.webView?.js("nightMode(\(self.nightMode))")
199 readerCenter.pageIndicatorView?.reloadColors()
200 readerCenter.configureNavBar()
201 readerCenter.scrollScrubber?.reloadColors()
202 readerCenter.collectionView.backgroundColor = (self.nightMode == true ? self.readerContainer?.readerConfig.nightModeBackground : UIColor.white)
203 }, completion: { (finished: Bool) in
204 NotificationCenter.default.post(name: Notification.Name(rawValue: "needRefreshPageMode"), object: nil)
210 /// Check current font name. Default .andada
211 open var currentFont: FolioReaderFont {
214 let rawValue = self.defaults.value(forKey: kCurrentFontFamily) as? Int,
215 let font = FolioReaderFont(rawValue: rawValue) else {
222 self.defaults.set(font.rawValue, forKey: kCurrentFontFamily)
223 _ = self.readerCenter?.currentPage?.webView?.js("setFontName('\(font.cssIdentifier)')")
227 /// Check current font size. Default .m
228 open var currentFontSize: FolioReaderSliderParamSize {
231 let rawValue = self.defaults.value(forKey: kCurrentFontSize) as? Int,
232 let size = FolioReaderSliderParamSize(rawValue: rawValue) else {
239 self.defaults.set(value.rawValue, forKey: kCurrentFontSize)
241 guard let currentPage = self.readerCenter?.currentPage else {
245 currentPage.webView?.js("setFontSize('\(currentFontSize.cssIdentifier(sliderType: SliderType.font) )')")
249 /// Check current margin size. Default .m
250 open var currentMarginSize: FolioReaderSliderParamSize {
253 let rawValue = self.defaults.value(forKey: kCurrentMarginSize) as? Int,
254 let size = FolioReaderSliderParamSize(rawValue: rawValue) else {
261 self.defaults.set(value.rawValue, forKey: kCurrentMarginSize)
263 guard let currentPage = self.readerCenter?.currentPage else {
267 currentPage.webView?.js("setMarginSize('\(currentMarginSize.cssIdentifier(sliderType: SliderType.margin))')")
271 /// Check current interline size. Default .m
272 open var currentInterlineSize: FolioReaderSliderParamSize {
275 let rawValue = self.defaults.value(forKey: kCurrentInterlineSize) as? Int,
276 let size = FolioReaderSliderParamSize(rawValue: rawValue) else {
283 self.defaults.set(value.rawValue, forKey: kCurrentInterlineSize)
285 guard let currentPage = self.readerCenter?.currentPage else {
289 currentPage.webView?.js("setInterlineSize('\(currentInterlineSize.cssIdentifier(sliderType: SliderType.interline))')")
291 // print("\n\nhtmllllll\n\n" + currentPage.webView!.stringByEvaluatingJavaScript(from: "document.documentElement.outerHTML")!)
296 /// Check current audio rate, the speed of speech voice. Default 0
297 open var currentAudioRate: Int {
298 get { return self.defaults.integer(forKey: kCurrentAudioRate) }
300 self.defaults.set(value, forKey: kCurrentAudioRate)
304 /// Check the current highlight style.Default 0
305 open var currentHighlightStyle: Int {
306 get { return self.defaults.integer(forKey: kCurrentHighlightStyle) }
308 self.defaults.set(value, forKey: kCurrentHighlightStyle)
312 /// Check the current Media Overlay or TTS style
313 open var currentMediaOverlayStyle: MediaOverlayStyle {
315 guard let rawValue = self.defaults.value(forKey: kCurrentMediaOverlayStyle) as? Int,
316 let style = MediaOverlayStyle(rawValue: rawValue) else {
317 return MediaOverlayStyle.default
322 self.defaults.set(value.rawValue, forKey: kCurrentMediaOverlayStyle)
326 /// Check the current scroll direction. Default .defaultVertical
327 open var currentScrollDirection: Int {
329 guard let value = self.defaults.value(forKey: kCurrentScrollDirection) as? Int else {
330 return FolioReaderScrollDirection.defaultVertical.rawValue
336 self.defaults.set(value, forKey: kCurrentScrollDirection)
338 let direction = (FolioReaderScrollDirection(rawValue: currentScrollDirection) ?? .defaultVertical)
339 self.readerCenter?.setScrollDirection(direction)
343 open var currentMenuIndex: Int {
344 get { return self.defaults.integer(forKey: kCurrentTOCMenu) }
346 self.defaults.set(value, forKey: kCurrentTOCMenu)
350 open var savedPositionForCurrentBook: [String: Any]? {
352 guard let bookId = self.readerContainer?.book.name else {
355 return self.defaults.value(forKey: bookId) as? [String : Any]
358 guard let bookId = self.readerContainer?.book.name else {
361 self.defaults.set(newValue, forKey: bookId)
368 extension FolioReader {
370 // TODO QUESTION: The static `getCoverImage` function used the shared instance before and ignored the `unzipPath` parameter.
371 // Should we properly implement the parameter (what has been done now) or should change the API to only use the current FolioReader instance?
374 Read Cover Image and Return an `UIImage`
376 open class func getCoverImage(_ epubPath: String, unzipPath: String? = nil) throws -> UIImage {
377 return try FREpubParser().parseCoverImage(epubPath, unzipPath: unzipPath)
380 open class func getTitle(_ epubPath: String, unzipPath: String? = nil) throws -> String {
381 return try FREpubParser().parseTitle(epubPath, unzipPath: unzipPath)
384 open class func getAuthorName(_ epubPath: String, unzipPath: String? = nil) throws-> String {
385 return try FREpubParser().parseAuthorName(epubPath, unzipPath: unzipPath)
389 // MARK: - Exit, save and close FolioReader
391 extension FolioReader {
393 /// Save Reader state, book, page and scroll offset.
394 @objc open func saveReaderState() {
395 guard isReaderOpen else {
399 guard let currentPage = self.readerCenter?.currentPage, let webView = currentPage.webView else {
404 "pageNumber": (self.readerCenter?.currentPageNumber ?? 0),
405 "pageOffsetX": webView.scrollView.contentOffset.x,
406 "pageOffsetY": webView.scrollView.contentOffset.y
409 self.savedPositionForCurrentBook = position
412 /// Closes and save the reader current instance.
414 self.saveReaderState()
415 self.isReaderOpen = false
416 self.isReaderReady = false
417 self.readerAudioPlayer?.stop(immediate: true)
418 self.defaults.set(0, forKey: kCurrentTOCMenu)
419 self.delegate?.folioReaderDidClose?(self)