Skip to content

Commit 874b4b8

Browse files
committed
feat(iOS): inject css helpers via WKUserScript
simpler and more reliable
1 parent ba1e192 commit 874b4b8

File tree

3 files changed

+48
-88
lines changed

3 files changed

+48
-88
lines changed

flutter_readium/ios/flutter_readium/Sources/flutter_readium/FlutterReadiumPlugin.swift

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -215,28 +215,6 @@ public class FlutterReadiumPlugin: NSObject, FlutterPlugin, ReadiumShared.Warnin
215215
message: "Failed to deserialize TTSPreferences: \(error.localizedDescription)",
216216
details: nil))
217217
}
218-
case "audioStart":
219-
let args = call.arguments as! [Any?]
220-
let initialSpeed = args[0] as? Double
221-
var locator: Locator? = nil
222-
if let locatorStr = args[1] as? String {
223-
locator = try! Locator(jsonString: locatorStr, warnings: self)!
224-
}
225-
226-
Task {
227-
guard let currentPubId = await currentReaderView?.publicationIdentifier,
228-
let pub = openedReadiumPublications[currentPubId] else {
229-
return result(FlutterError.init(
230-
code: "AudioBookError",
231-
message: "Failed to start audiobook: no current publication found",
232-
details: nil))
233-
}
234-
235-
let prefs = AudioPreferences(speed: initialSpeed ?? 0.5)
236-
await setupAudiobookNavigator(publication: pub, locator: locator, initialPreferences: prefs)
237-
self.play()
238-
result(nil)
239-
}
240218
default:
241219
result(FlutterMethodNotImplemented)
242220
}

flutter_readium/ios/flutter_readium/Sources/flutter_readium/Readium.swift

Lines changed: 0 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,6 @@ final class Readium {
4848
pdfFactory: DefaultPDFDocumentFactory()
4949
),
5050
contentProtections: contentProtections,
51-
onCreatePublication: { manifest, container, services in
52-
container = TransformingContainer(container: container, transformer: self.injectCSS)
53-
}
5451
)
5552
}
5653

@@ -92,44 +89,6 @@ final class Readium {
9289
}
9390
#endif
9491

95-
96-
func injectCSS(_ anyUrl: AnyURL, _ resource: Resource) -> Resource {
97-
98-
// We only transform HTML resources.
99-
let props = resource.propertiesSync
100-
guard props.filename?.localizedCaseInsensitiveContains("html") == true else {
101-
print("\(TAG)::injectCSS skip non-html file: \(props.filename ?? "<no-filename>")")
102-
return resource
103-
}
104-
105-
let comicCssKey = FlutterReadiumPlugin.registrar?.lookupKey(forAsset: "assets/helpers/comics.css", fromPackage: "flutter_readium")
106-
let epubCssKey = FlutterReadiumPlugin.registrar?.lookupKey(forAsset: "assets/helpers/epub.css", fromPackage: "flutter_readium")
107-
108-
let sourceFiles = [comicCssKey, epubCssKey]
109-
let source = sourceFiles.compactMap { sourceFile -> String? in
110-
if let path = Bundle.main.path(forResource: sourceFile, ofType: nil),
111-
let data = FileManager().contents(atPath: path),
112-
let stringData = String(data: data, encoding: .utf8) {
113-
return stringData
114-
}
115-
print("\(TAG)::injectCSS No source found on \(String(describing: sourceFile))")
116-
117-
return nil
118-
}.joined(separator: "\n")
119-
120-
return resource.mapAsString { content -> String in
121-
var content = content
122-
123-
if let headEnd = content.startIndex(of: "</head>") {
124-
let style = "<style>\(source)</style>"
125-
content = content.insert(string: style, at: headEnd)
126-
} else {
127-
print("\(TAG)::injectCSS No head found on the document")
128-
}
129-
130-
return content
131-
}
132-
}
13392
}
13493

13594
extension ReadiumShared.ReadError: UserErrorConvertible {

flutter_readium/ios/flutter_readium/Sources/flutter_readium/ReadiumReaderView.swift

Lines changed: 48 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,7 @@ class ReadiumBugLogger: ReadiumShared.WarningLogger {
1717
}
1818

1919
private let readiumBugLogger = ReadiumBugLogger()
20-
21-
private let scrollScripts = [
22-
false: WKUserScript(
23-
source: "setScrollMode(false);", injectionTime: .atDocumentEnd, forMainFrameOnly: false),
24-
true: WKUserScript(
25-
source: "setScrollMode(true);", injectionTime: .atDocumentEnd, forMainFrameOnly: false),
26-
]
20+
private var userScripts: [WKUserScript] = []
2721

2822
class ReadiumReaderView: NSObject, FlutterPlatformView, EPUBNavigatorDelegate {
2923

@@ -32,7 +26,6 @@ class ReadiumReaderView: NSObject, FlutterPlatformView, EPUBNavigatorDelegate {
3226
private var textLocatorStreamHandler: EventStreamHandler?
3327
private let _view: UIView
3428
private let readiumViewController: EPUBNavigatorViewController
35-
private let userScript: WKUserScript
3629
private var isVerticalScroll = false
3730

3831
var publicationIdentifier: String?
@@ -84,11 +77,12 @@ class ReadiumReaderView: NSObject, FlutterPlatformView, EPUBNavigatorDelegate {
8477
.compact: (top: 0, bottom: 0),
8578
.regular: (top: 0, bottom: 0),
8679
]
80+
// TODO: Make this config configurable from Flutter
8781
config.preloadPreviousPositionCount = 1
8882
config.preloadNextPositionCount = 1
8983
config.debugState = true
9084
if (defaultPreferences != nil) {
91-
config.preferences = defaultPreferences!;
85+
config.preferences = defaultPreferences!
9286
}
9387

9488
readiumViewController = try! EPUBNavigatorViewController(
@@ -97,17 +91,10 @@ class ReadiumReaderView: NSObject, FlutterPlatformView, EPUBNavigatorDelegate {
9791
config: config,
9892
httpServer: sharedReadium.httpServer!
9993
)
100-
101-
// Add epub.js script for highlighting etc. and comics.js script for handling Nota's guided comics.
102-
let comicJsAssetPath = registrar.lookupKey(forAsset: "assets/helpers/comics.js", fromPackage: "flutter_readium")
103-
let epubJsAssetPath = registrar.lookupKey(forAsset: "assets/helpers/epub.js", fromPackage: "flutter_readium")
104-
let sourceFiles = [comicJsAssetPath, epubJsAssetPath]
105-
let source = sourceFiles.map { sourceFile -> String in
106-
let path = Bundle.main.path(forResource: sourceFile, ofType: nil)!
107-
let data = FileManager().contents(atPath: path)!
108-
return String(data: data, encoding: .utf8)!
109-
}.joined(separator: "\n")
110-
userScript = WKUserScript(source: "const isAndroid=false,isIos=true;\n" + source, injectionTime: .atDocumentStart, forMainFrameOnly: false)
94+
95+
if userScripts.isEmpty {
96+
initUserScripts(registrar: registrar)
97+
}
11198

11299
_view = UIView()
113100
super.init()
@@ -133,8 +120,10 @@ class ReadiumReaderView: NSObject, FlutterPlatformView, EPUBNavigatorDelegate {
133120

134121
// override EPUBNavigatorDelegate::navigator:setupUserScripts
135122
func navigator(_ navigator: EPUBNavigatorViewController, setupUserScripts userContentController: WKUserContentController) {
136-
print(TAG, "setupUserScripts:")
137-
userContentController.addUserScript(userScript)
123+
print(TAG, "setupUserScripts: adding \(userScripts.count) scripts")
124+
for script in userScripts {
125+
userContentController.addUserScript(script)
126+
}
138127
}
139128

140129
// override EPUBNavigatorDelegate::middleTapHandler
@@ -223,10 +212,10 @@ class ReadiumReaderView: NSObject, FlutterPlatformView, EPUBNavigatorDelegate {
223212
switch await self.evaluateJavascript("window.epubPage.getLocatorFragments(\(locatorJson), \(isVerticalScroll));") {
224213
case .success(let jresult):
225214
let locatorWithFragments = try! Locator(json: jresult as? Dictionary<String, Any?>, warnings: readiumBugLogger)!
226-
return locatorWithFragments;
215+
return locatorWithFragments
227216
case .failure(let err):
228217
print(TAG, "getLocatorFragments failed! \(err)")
229-
return nil;
218+
return nil
230219
}
231220
}
232221

@@ -245,7 +234,7 @@ class ReadiumReaderView: NSObject, FlutterPlatformView, EPUBNavigatorDelegate {
245234

246235
if shouldGo {
247236
print(TAG, "goToLocator: Go to \(locator.href)")
248-
let goToSuccees = await readiumViewController.go(to: locator, options: NavigatorGoOptions(animated: false));
237+
let goToSuccees = await readiumViewController.go(to: locator, options: NavigatorGoOptions(animated: false))
249238
if (goToSuccees && shouldScroll) {
250239
await self.scrollTo(locations: locations, toStart: false)
251240
self.emitOnPageChanged()
@@ -418,6 +407,40 @@ class ReadiumReaderView: NSObject, FlutterPlatformView, EPUBNavigatorDelegate {
418407
}
419408
}
420409

410+
func initUserScripts(registrar: FlutterPluginRegistrar) {
411+
let comicJsKey = registrar.lookupKey(forAsset: "assets/helpers/comics.js", fromPackage: "flutter_readium")
412+
let comicCssKey = registrar.lookupKey(forAsset: "assets/helpers/comics.css", fromPackage: "flutter_readium")
413+
let epubJsKey = registrar.lookupKey(forAsset: "assets/helpers/epub.js", fromPackage: "flutter_readium")
414+
let epubCssKey = registrar.lookupKey(forAsset: "assets/helpers/epub.css", fromPackage: "flutter_readium")
415+
let jsScripts = [comicJsKey, epubJsKey].map { sourceFile -> String in
416+
let path = Bundle.main.path(forResource: sourceFile, ofType: nil)!
417+
let data = FileManager().contents(atPath: path)!
418+
return String(data: data, encoding: .utf8)!
419+
}
420+
let addCssScripts = [comicCssKey, epubCssKey].map { sourceFile -> String in
421+
let path = Bundle.main.path(forResource: sourceFile, ofType: nil)!
422+
let data = FileManager().contents(atPath: path)!.base64EncodedString()
423+
return """
424+
(function() {
425+
var parent = document.getElementsByTagName('head').item(0);
426+
var style = document.createElement('style');
427+
style.type = 'text/css';
428+
style.innerHTML = window.atob('\(data)');
429+
parent.appendChild(style)})();
430+
"""
431+
}
432+
/// Add JS scripts right away, before loading the rest of the document.
433+
for jsScript in jsScripts {
434+
userScripts.append(WKUserScript(source: jsScript, injectionTime: .atDocumentStart, forMainFrameOnly: false))
435+
}
436+
/// Add css injection scripts after primary document finished loading.
437+
for addCssScript in addCssScripts {
438+
userScripts.append(WKUserScript(source: addCssScript, injectionTime: .atDocumentEnd, forMainFrameOnly: false))
439+
}
440+
/// Add simple script used by our JS to detect OS
441+
userScripts.append(WKUserScript(source: "const isAndroid=false,isIos=true;", injectionTime: .atDocumentStart, forMainFrameOnly: false))
442+
}
443+
421444
private func canScroll(locations: Locator.Locations?) -> Bool {
422445
guard let locations = locations else { return false }
423446
return locations.domRange != nil || locations.cssSelector != nil || locations.progression != nil

0 commit comments

Comments
 (0)