// // KeymanWebViewController.swift // KeymanEngine // // Created by Gabriel Wong on 2017-10-31. // Copyright © 2017 SIL International. All rights reserved. // import UIKit import WebKit // MARK: - UIViewController class KeymanWebViewController: UIViewController { let storage: Storage weak var delegate: KeymanWebDelegate? var webView: WKWebView! var frame: CGRect? { didSet(frame) { if let view = view, let frame = frame { view.frame = frame } } } init(storage: Storage) { self.storage = storage super.init(nibName: nil, bundle: nil) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func loadView() { let config = WKWebViewConfiguration() let prefs = WKPreferences() prefs.javaScriptEnabled = true config.preferences = prefs config.suppressesIncrementalRendering = false let userContentController = WKUserContentController() userContentController.add(self, name: "keyman") config.userContentController = userContentController webView = WKWebView(frame: frame ?? .zero, configuration: config) webView.isOpaque = false webView.backgroundColor = UIColor.clear webView.navigationDelegate = self webView.scrollView.isScrollEnabled = false view = webView } override func viewWillAppear(_ animated: Bool) { if let frame = frame { view.frame = frame } super.viewWillAppear(animated) } } // MARK: - JavaScript functions extension KeymanWebViewController { func languageMenuPosition(_ completion: @escaping (CGRect) -> Void) { webView.evaluateJavaScript("langMenuPos();") { result, _ in guard let result = result as? String, !result.isEmpty else { return } let components = result.components(separatedBy: ",") let x = CGFloat(Float(components[0])!) let y = CGFloat(Float(components[1])!) let w = CGFloat(Float(components[2])!) let h = CGFloat(Float(components[3])!) completion(KeymanWebViewController.keyFrame(x: x, y: y, w: w, h: h)) } } // FIXME: text is unused in the JS func executePopupKey(id: String, text: String) { // Text must be checked for ', ", and \ characters; they must be escaped properly! do { let encodingArray = [ text ]; let jsonString = try String(data: JSONSerialization.data(withJSONObject: encodingArray), encoding: .utf8)! let start = jsonString.index(jsonString.startIndex, offsetBy: 2) let end = jsonString.index(jsonString.endIndex, offsetBy: -2) let escapedText = jsonString[start.. [String: Any]? { guard let font = font else { return nil } // family does not have to match the name in the font file. It only has to be unique. return [ "family": "\(keyboardID)__\(isOsk ? "osk" : "display")", "files": font.source.map { storage.fontURL(forKeyboardID: keyboardID, filename: $0).absoluteString } ] } func setKeyboard(_ keyboard: InstallableKeyboard) { var stub: [String: Any] = [ "KI": "Keyboard_\(keyboard.id)", "KN": keyboard.name, "KLC": keyboard.languageID, "KL": keyboard.languageName, "KF": storage.keyboardURL(for: keyboard).absoluteString ] let displayFont = fontObject(from: keyboard.font, keyboardID: keyboard.id, isOsk: false) let oskFont = fontObject(from: keyboard.oskFont, keyboardID: keyboard.id, isOsk: true) ?? displayFont if let displayFont = displayFont { stub["KFont"] = displayFont } if let oskFont = oskFont { stub["KOskFont"] = oskFont } let data: Data do { data = try JSONSerialization.data(withJSONObject: stub, options: []) } catch { log.error("Failed to serialize keyboard stub: \(error)") return } guard let stubString = String(data: data, encoding: .utf8) else { log.error("Failed to create stub string") return } log.debug("Keyboard stub: \(stubString)") webView.evaluateJavaScript("setKeymanLanguage(\(stubString));", completionHandler: nil) } } // MARK: - WKScriptMessageHandler extension KeymanWebViewController: WKScriptMessageHandler { func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { guard let fragment = message.body as? String, !fragment.isEmpty else { return } if fragment.hasPrefix("#insertText-") { let dnRange = fragment.range(of: "+dn=")! let sRange = fragment.range(of: "+s=")! let dn = Int(fragment[dnRange.upperBound.. CGRect { // kmw adds w/2 to x. return CGRect(x: x - w / 2.0, y: y, width: w, height: h) } } // MARK: - WKNavigationDelegate extension KeymanWebViewController: WKNavigationDelegate { func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { guard let url = webView.url else { return } guard url.lastPathComponent == Resources.kmwFilename && (url.fragment?.isEmpty ?? true) else { return } keyboardLoaded(self) delegate?.keyboardLoaded(self) } } // MARK: - KeymanWebDelegate extension KeymanWebViewController: KeymanWebDelegate { }