added iOS source code
[wl-app.git] / iOS / Pods / FolioReaderKit / Source / FolioReaderKit.swift
diff --git a/iOS/Pods/FolioReaderKit/Source/FolioReaderKit.swift b/iOS/Pods/FolioReaderKit/Source/FolioReaderKit.swift
new file mode 100755 (executable)
index 0000000..ad1d07d
--- /dev/null
@@ -0,0 +1,421 @@
+//
+//  FolioReaderKit.swift
+//  FolioReaderKit
+//
+//  Created by Heberti Almeida on 08/04/15.
+//  Copyright (c) 2015 Folio Reader. All rights reserved.
+//
+
+import Foundation
+import UIKit
+
+// MARK: - Internal constants
+
+internal let kApplicationDocumentsDirectory = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
+internal let kCurrentFontFamily = "com.folioreader.kCurrentFontFamily"
+internal let kCurrentFontSize = "com.folioreader.kCurrentFontSize"
+internal let kCurrentMarginSize = "com.folioreader.kCurrentMarginSize"
+internal let kCurrentInterlineSize = "com.folioreader.kCurrentInterlineSize"
+internal let kCurrentAudioRate = "com.folioreader.kCurrentAudioRate"
+internal let kCurrentHighlightStyle = "com.folioreader.kCurrentHighlightStyle"
+internal let kCurrentMediaOverlayStyle = "com.folioreader.kMediaOverlayStyle"
+internal let kCurrentScrollDirection = "com.folioreader.kCurrentScrollDirection"
+internal let kNightMode = "com.folioreader.kNightMode"
+internal let kCurrentTOCMenu = "com.folioreader.kCurrentTOCMenu"
+internal let kHighlightRange = 30
+internal let kReuseCellIdentifier = "com.folioreader.Cell.ReuseIdentifier"
+
+public enum FolioReaderError: Error, LocalizedError {
+    case bookNotAvailable
+    case errorInContainer
+    case errorInOpf
+    case authorNameNotAvailable
+    case coverNotAvailable
+    case invalidImage(path: String)
+    case titleNotAvailable
+    case fullPathEmpty
+
+    public var errorDescription: String? {
+        switch self {
+        case .bookNotAvailable:
+            return "Book not found"
+        case .errorInContainer, .errorInOpf:
+            return "Invalid book format"
+        case .authorNameNotAvailable:
+            return "Author name not available"
+        case .coverNotAvailable:
+            return "Cover image not available"
+        case let .invalidImage(path):
+            return "Invalid image at path: " + path
+        case .titleNotAvailable:
+            return "Book title not available"
+        case .fullPathEmpty:
+            return "Book corrupted"
+        }
+    }
+}
+
+/// Defines the media overlay and TTS selection
+///
+/// - `default`: The background is colored
+/// - underline: The underlined is colored
+/// - textColor: The text is colored
+public enum MediaOverlayStyle: Int {
+    case `default`
+    case underline
+    case textColor
+
+    init() {
+        self = .default
+    }
+
+    func className() -> String {
+        return "mediaOverlayStyle\(self.rawValue)"
+    }
+}
+
+/// FolioReader actions delegate
+@objc public protocol FolioReaderDelegate: class {
+    
+    /// Did finished loading book.
+    ///
+    /// - Parameters:
+    ///   - folioReader: The FolioReader instance
+    ///   - book: The Book instance
+    @objc optional func folioReader(_ folioReader: FolioReader, didFinishedLoading book: FRBook)
+    
+    /// Called when reader did closed.
+    ///
+    /// - Parameter folioReader: The FolioReader instance
+    @objc optional func folioReaderDidClose(_ folioReader: FolioReader)
+    
+    /// Called when reader did closed.
+    @available(*, deprecated, message: "Use 'folioReaderDidClose(_ folioReader: FolioReader)' instead.")
+    @objc optional func folioReaderDidClosed()
+}
+
+/// Main Library class with some useful constants and methods
+open class FolioReader: NSObject {
+
+    public override init() { }
+
+    deinit {
+        removeObservers()
+    }
+
+    /// Custom unzip path
+    open var unzipPath: String?
+
+    /// FolioReaderDelegate
+    open weak var delegate: FolioReaderDelegate?
+    
+    open weak var readerContainer: FolioReaderContainer?
+    open weak var readerAudioPlayer: FolioReaderAudioPlayer?
+    open weak var readerCenter: FolioReaderCenter? {
+        return self.readerContainer?.centerViewController
+    }
+
+    /// Check if reader is open
+    var isReaderOpen = false
+
+    /// Check if reader is open and ready
+    var isReaderReady = false
+
+    /// Check if layout needs to change to fit Right To Left
+    var needsRTLChange: Bool {
+        return (self.readerContainer?.book.spine.isRtl == true && self.readerContainer?.readerConfig.scrollDirection == .horizontal)
+    }
+
+    func isNight<T>(_ f: T, _ l: T) -> T {
+        return (self.nightMode == true ? f : l)
+    }
+
+    /// UserDefault for the current ePub file.
+    fileprivate var defaults: FolioReaderUserDefaults {
+        return FolioReaderUserDefaults(withIdentifier: self.readerContainer?.readerConfig.identifier)
+    }
+
+    // Add necessary observers
+    fileprivate func addObservers() {
+        removeObservers()
+        NotificationCenter.default.addObserver(self, selector: #selector(saveReaderState), name: .UIApplicationWillResignActive, object: nil)
+        NotificationCenter.default.addObserver(self, selector: #selector(saveReaderState), name: .UIApplicationWillTerminate, object: nil)
+    }
+
+    /// Remove necessary observers
+    fileprivate func removeObservers() {
+        NotificationCenter.default.removeObserver(self, name: .UIApplicationWillResignActive, object: nil)
+        NotificationCenter.default.removeObserver(self, name: .UIApplicationWillTerminate, object: nil)
+    }
+    
+    public func getProgressValues() -> (currentPage: Int, totalPages: Int)? {
+        guard let center = readerCenter else { return nil}
+        let totalPages = center.totalPages
+        let currentPage = center.currentPageNumber
+        return (currentPage, totalPages)
+    }
+
+}
+
+// MARK: - Present FolioReader
+
+extension FolioReader {
+
+    /// Present a Folio Reader Container modally on a Parent View Controller.
+    ///
+    /// - Parameters:
+    ///   - parentViewController: View Controller that will present the reader container.
+    ///   - epubPath: String representing the path on the disk of the ePub file. Must not be nil nor empty string.
+       ///   - unzipPath: Path to unzip the compressed epub.
+    ///   - config: FolioReader configuration.
+    ///   - shouldRemoveEpub: Boolean to remove the epub or not. Default true.
+    ///   - animated: Pass true to animate the presentation; otherwise, pass false.
+    open func presentReader(parentViewController: UIViewController, withEpubPath epubPath: String, unzipPath: String? = nil, andConfig config: FolioReaderConfig, shouldRemoveEpub: Bool = true, animated:
+        Bool = true) {
+        let readerContainer = FolioReaderContainer(withConfig: config, folioReader: self, epubPath: epubPath, unzipPath: unzipPath, removeEpub: shouldRemoveEpub)
+        self.readerContainer = readerContainer
+        parentViewController.present(readerContainer, animated: animated, completion: nil)
+        addObservers()
+    }
+}
+
+// MARK: -  Getters and setters for stored values
+
+extension FolioReader {
+
+    public func register(defaults: [String: Any]) {
+        self.defaults.register(defaults: defaults)
+    }
+
+    /// Check if current theme is Night mode
+    open var nightMode: Bool {
+        get { return self.defaults.bool(forKey: kNightMode) }
+        set (value) {
+            self.defaults.set(value, forKey: kNightMode)
+
+            if let readerCenter = self.readerCenter {
+                UIView.animate(withDuration: 0.6, animations: {
+                    _ = readerCenter.currentPage?.webView?.js("nightMode(\(self.nightMode))")
+                    readerCenter.pageIndicatorView?.reloadColors()
+                    readerCenter.configureNavBar()
+                    readerCenter.scrollScrubber?.reloadColors()
+                    readerCenter.collectionView.backgroundColor = (self.nightMode == true ? self.readerContainer?.readerConfig.nightModeBackground : UIColor.white)
+                }, completion: { (finished: Bool) in
+                    NotificationCenter.default.post(name: Notification.Name(rawValue: "needRefreshPageMode"), object: nil)
+                })
+            }
+        }
+    }
+
+    /// Check current font name. Default .andada
+    open var currentFont: FolioReaderFont {
+        get {
+            guard
+                let rawValue = self.defaults.value(forKey: kCurrentFontFamily) as? Int,
+                let font = FolioReaderFont(rawValue: rawValue) else {
+                    return .andada
+            }
+
+            return font
+        }
+        set (font) {
+            self.defaults.set(font.rawValue, forKey: kCurrentFontFamily)
+            _ = self.readerCenter?.currentPage?.webView?.js("setFontName('\(font.cssIdentifier)')")
+        }
+    }
+
+    /// Check current font size. Default .m
+    open var currentFontSize: FolioReaderSliderParamSize {
+        get {
+            guard
+                let rawValue = self.defaults.value(forKey: kCurrentFontSize) as? Int,
+                let size = FolioReaderSliderParamSize(rawValue: rawValue) else {
+                    return .m
+            }
+
+            return size
+        }
+        set (value) {
+            self.defaults.set(value.rawValue, forKey: kCurrentFontSize)
+
+            guard let currentPage = self.readerCenter?.currentPage else {
+                return
+            }
+
+            currentPage.webView?.js("setFontSize('\(currentFontSize.cssIdentifier(sliderType: SliderType.font) )')")
+        }
+    }
+    
+    /// Check current margin size. Default .m
+    open var currentMarginSize: FolioReaderSliderParamSize {
+        get {
+            guard
+                let rawValue = self.defaults.value(forKey: kCurrentMarginSize) as? Int,
+                let size = FolioReaderSliderParamSize(rawValue: rawValue) else {
+                    return .m
+            }
+            
+            return size
+        }
+        set (value) {
+            self.defaults.set(value.rawValue, forKey: kCurrentMarginSize)
+            
+            guard let currentPage = self.readerCenter?.currentPage else {
+                return
+            }
+            
+            currentPage.webView?.js("setMarginSize('\(currentMarginSize.cssIdentifier(sliderType: SliderType.margin))')")
+        }
+    }
+
+    /// Check current interline size. Default .m
+    open var currentInterlineSize: FolioReaderSliderParamSize {
+        get {
+            guard
+                let rawValue = self.defaults.value(forKey: kCurrentInterlineSize) as? Int,
+                let size = FolioReaderSliderParamSize(rawValue: rawValue) else {
+                    return .m
+            }
+            
+            return size
+        }
+        set (value) {
+            self.defaults.set(value.rawValue, forKey: kCurrentInterlineSize)
+            
+            guard let currentPage = self.readerCenter?.currentPage else {
+                return
+            }
+            
+            currentPage.webView?.js("setInterlineSize('\(currentInterlineSize.cssIdentifier(sliderType: SliderType.interline))')")
+            
+//            print("\n\nhtmllllll\n\n" + currentPage.webView!.stringByEvaluatingJavaScript(from: "document.documentElement.outerHTML")!)
+
+        }
+    }
+
+    /// Check current audio rate, the speed of speech voice. Default 0
+    open var currentAudioRate: Int {
+        get { return self.defaults.integer(forKey: kCurrentAudioRate) }
+        set (value) {
+            self.defaults.set(value, forKey: kCurrentAudioRate)
+        }
+    }
+
+    /// Check the current highlight style.Default 0
+    open var currentHighlightStyle: Int {
+        get { return self.defaults.integer(forKey: kCurrentHighlightStyle) }
+        set (value) {
+            self.defaults.set(value, forKey: kCurrentHighlightStyle)
+        }
+    }
+
+    /// Check the current Media Overlay or TTS style
+    open var currentMediaOverlayStyle: MediaOverlayStyle {
+        get {
+            guard let rawValue = self.defaults.value(forKey: kCurrentMediaOverlayStyle) as? Int,
+                let style = MediaOverlayStyle(rawValue: rawValue) else {
+                return MediaOverlayStyle.default
+            }
+            return style
+        }
+        set (value) {
+            self.defaults.set(value.rawValue, forKey: kCurrentMediaOverlayStyle)
+        }
+    }
+
+    /// Check the current scroll direction. Default .defaultVertical
+    open var currentScrollDirection: Int {
+        get {
+            guard let value = self.defaults.value(forKey: kCurrentScrollDirection) as? Int else {
+                return FolioReaderScrollDirection.defaultVertical.rawValue
+            }
+
+            return value
+        }
+        set (value) {
+            self.defaults.set(value, forKey: kCurrentScrollDirection)
+
+            let direction = (FolioReaderScrollDirection(rawValue: currentScrollDirection) ?? .defaultVertical)
+            self.readerCenter?.setScrollDirection(direction)
+        }
+    }
+
+    open var currentMenuIndex: Int {
+        get { return self.defaults.integer(forKey: kCurrentTOCMenu) }
+        set (value) {
+            self.defaults.set(value, forKey: kCurrentTOCMenu)
+        }
+    }
+
+    open var savedPositionForCurrentBook: [String: Any]? {
+        get {
+            guard let bookId = self.readerContainer?.book.name else {
+                return nil
+            }
+            return self.defaults.value(forKey: bookId) as? [String : Any]
+        }
+        set {
+            guard let bookId = self.readerContainer?.book.name else {
+                return
+            }
+            self.defaults.set(newValue, forKey: bookId)
+        }
+    }
+}
+
+// MARK: - Metadata
+
+extension FolioReader {
+
+    // TODO QUESTION: The static `getCoverImage` function used the shared instance before and ignored the `unzipPath` parameter.
+    // Should we properly implement the parameter (what has been done now) or should change the API to only use the current FolioReader instance?
+
+    /**
+     Read Cover Image and Return an `UIImage`
+     */
+    open class func getCoverImage(_ epubPath: String, unzipPath: String? = nil) throws -> UIImage {
+        return try FREpubParser().parseCoverImage(epubPath, unzipPath: unzipPath)
+    }
+
+    open class func getTitle(_ epubPath: String, unzipPath: String? = nil) throws -> String {
+        return try FREpubParser().parseTitle(epubPath, unzipPath: unzipPath)
+    }
+
+    open class func getAuthorName(_ epubPath: String, unzipPath: String? = nil) throws-> String {
+        return try FREpubParser().parseAuthorName(epubPath, unzipPath: unzipPath)
+    }
+}
+
+// MARK: - Exit, save and close FolioReader
+
+extension FolioReader {
+
+    /// Save Reader state, book, page and scroll offset.
+    @objc open func saveReaderState() {
+        guard isReaderOpen else {
+            return
+        }
+
+        guard let currentPage = self.readerCenter?.currentPage, let webView = currentPage.webView else {
+            return
+        }
+
+        let position = [
+            "pageNumber": (self.readerCenter?.currentPageNumber ?? 0),
+            "pageOffsetX": webView.scrollView.contentOffset.x,
+            "pageOffsetY": webView.scrollView.contentOffset.y
+            ] as [String : Any]
+
+        self.savedPositionForCurrentBook = position
+    }
+
+    /// Closes and save the reader current instance.
+    open func close() {
+        self.saveReaderState()
+        self.isReaderOpen = false
+        self.isReaderReady = false
+        self.readerAudioPlayer?.stop(immediate: true)
+        self.defaults.set(0, forKey: kCurrentTOCMenu)
+        self.delegate?.folioReaderDidClose?(self)
+    }
+}