diff --git a/src/calibre/gui2/viewer2/mathjax.py b/src/calibre/gui2/viewer2/mathjax.py new file mode 100644 index 0000000000..ce9cafbe30 --- /dev/null +++ b/src/calibre/gui2/viewer2/mathjax.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python2 +# vim:fileencoding=utf-8 +# License: GPL v3 Copyright: 2018, Kovid Goyal + +from __future__ import absolute_import, division, print_function, unicode_literals + +PATCHED_MATHJAX = ''' + +function postprocess_mathjax(link_uid) { + Array.prototype.forEach.call(document.getElementsByTagName('a'), function(a) { + var href = a.getAttribute('href'); + if (href && href.startsWith('#')) { + a.setAttribute('href', 'javascript: void(0)') + a.setAttribute('data-' + link_uid, JSON.stringify({'frag':href.slice(1)})) + } + }); + document.documentElement.dispatchEvent(new CustomEvent("calibre-mathjax-init-done")); +} + +function monkeypatch(mathjax_files) { + var orig = window.MathJax.Ajax.fileURL.bind(window.MathJax.Ajax); + + window.MathJax.Ajax.fileURL = function(mathjax_name) { + var ans = orig(mathjax_name); + var name = ans.replace(/^\\//g, ''); + if (name.startsWith('../fonts')) name = name.slice(3); + ans = mathjax_files[name]; + if (!ans) ans = ''; + if (typeof ans !== 'string') { + mathjax_files[name] = window.URL.createObjectURL(ans); + ans = mathjax_files[name]; + } + if (ans === name && !name.startsWith('blob:')) { + if (ans.endsWith('.eot') || ans.endsWith('.otf')) return ''; + console.log('WARNING: Failed to resolve MathJax file: ' + mathjax_name); + } + return ans; + } + window.MathJax.Ajax.fileRev = function(mathjax_name) { return ''; } +} + +function init_mathjax(link_uid, mathjax_files) { + monkeypatch(mathjax_files); + window.MathJax.Hub.Register.StartupHook("End", postprocess_mathjax.bind(this, link_uid)) +} + +document.documentElement.addEventListener("calibre-mathjax-init", function(ev) { + +window.MathJax = ev.detail.MathJax; +window.MathJax.AuthorInit = init_mathjax.bind(this, ev.detail.link_uid, ev.detail.mathjax_files); + +__SRC__ +}); +''' + + +def monkeypatch_mathjax(src): + return PATCHED_MATHJAX.replace('__SRC__', src, 1) diff --git a/src/calibre/gui2/viewer2/web_view.py b/src/calibre/gui2/viewer2/web_view.py index 7a29b89a20..92f851181a 100644 --- a/src/calibre/gui2/viewer2/web_view.py +++ b/src/calibre/gui2/viewer2/web_view.py @@ -62,7 +62,7 @@ class UrlSchemeHandler(QWebEngineUrlSchemeHandler): def __init__(self, parent=None): QWebEngineUrlSchemeHandler.__init__(self, parent) - self.mathjax_tdir = None + self.mathjax_tdir = self.mathjax_manifest = None def requestStarted(self, rq): if bytes(rq.requestMethod()) != b'GET': @@ -99,16 +99,28 @@ class UrlSchemeHandler(QWebEngineUrlSchemeHandler): data = b'[' + manifest + b',' + metadata + b']' self.send_reply(rq, mime_type, data) elif name.startswith('mathjax/'): - if self.mathjax_tdir is None: - from calibre.srv.books import get_mathjax_manifest - self.mathjax_tdir = PersistentTemporaryDirectory(prefix='v2mjx-') - get_mathjax_manifest(self.mathjax_tdir) - + from calibre.gui2.viewer2.mathjax import monkeypatch_mathjax + if name == 'mathjax/manifest.json': + if self.mathjax_tdir is None: + import json + from calibre.srv.books import get_mathjax_manifest + self.mathjax_tdir = PersistentTemporaryDirectory(prefix='v2mjx-') + self.mathjax_manifest = json.dumps(get_mathjax_manifest(self.mathjax_tdir)['files']) + self.send_reply(rq, 'application/json', self.mathjax_manifest) + return path = os.path.abspath(os.path.join(self.mathjax_tdir, name)) if path.startswith(self.mathjax_tdir): mt = guess_type(name) - with lopen(path, 'rb') as f: - raw = f.read() + try: + with lopen(path, 'rb') as f: + raw = f.read() + except EnvironmentError as err: + prints("Failed to get mathjax file: {} with error: {}".format(name, err)) + rq.fail(rq.RequestFailed) + return + if 'MathJax.js' in name: + raw = monkeypatch_mathjax(raw.decode('utf-8')).encode('utf-8') + self.send_reply(rq, mt, raw) def send_reply(self, rq, mime_type, data): diff --git a/src/pyj/read_book/mathjax.pyj b/src/pyj/read_book/mathjax.pyj index f0ce401494..1de72145b1 100644 --- a/src/pyj/read_book/mathjax.pyj +++ b/src/pyj/read_book/mathjax.pyj @@ -15,6 +15,7 @@ def get_url(mathjax_files, name): ans = mathjax_files[name] = window.URL.createObjectURL(ans) return ans + def monkeypatch(mathjax_files): StyleString = window.MathJax.Ajax.StyleString.bind(window.MathJax.Ajax) @@ -72,7 +73,18 @@ def apply_mathjax(mathjax_files, link_uid, proceed): script = E.script(type='text/javascript') document.head.appendChild(script) if runtime.is_standalone_viewer: - script.src = f'{runtime.FAKE_PROTOCOL}://{runtime.FAKE_HOST}/mathjax/MathJax.js' + script.onload = def(): + ev = new CustomEvent("calibre-mathjax-init", { + 'detail': { + 'MathJax': m, + 'link_uid': link_uid, + 'mathjax_files': mathjax_files, + } + }) + document.documentElement.dispatchEvent(ev) + document.documentElement.addEventListener("calibre-mathjax-init-done", def(ev): + proceed() + ) else: m.AuthorInit = init_mathjax.bind(this, mathjax_files, link_uid, proceed) - script.src = get_url(mathjax_files, 'MathJax.js') + script.src = get_url(mathjax_files, 'MathJax.js') diff --git a/src/pyj/read_book/view.pyj b/src/pyj/read_book/view.pyj index b37c2673ff..2b7cfbd3f1 100644 --- a/src/pyj/read_book/view.pyj +++ b/src/pyj/read_book/view.pyj @@ -168,7 +168,7 @@ class View: 'human_scroll': self.on_human_scroll, } entry_point = None if runtime.is_standalone_viewer else 'read_book.iframe' - self.iframe_wrapper = IframeWrapper(handlers, document.getElementById(iframe_id), entry_point, _('Bootstrapping book reader...')) + self.iframe_wrapper = IframeWrapper(handlers, document.getElementById(iframe_id), entry_point, _('Bootstrapping book reader...'), runtime.FAKE_PROTOCOL, runtime.FAKE_HOST) self.search_overlay = SearchOverlay(self) self.content_popup_overlay = ContentPopupOverlay(self) self.overlay = Overlay(self) diff --git a/src/pyj/viewer-main.pyj b/src/pyj/viewer-main.pyj index 641e1f4d62..5dda2545f8 100644 --- a/src/pyj/viewer-main.pyj +++ b/src/pyj/viewer-main.pyj @@ -59,9 +59,57 @@ def get_file(book, name, proceed): xhr.responseType = 'blob' xhr.send() +def mathjax_file_received(name, proceed, end_type, xhr, ev): + if end_type is 'abort': + return + if end_type is not 'load': + show_error(_('Failed to load MathJax file'), _( + 'Could not load the file: {} with error: {}').format(name, xhr.error_html)) + return + if not xhr.responseType or xhr.responseType is 'text': + result = xhr.responseText + else if xhr.responseType is 'blob' or xhr.responseType is 'json': + result = xhr.response + else: + show_error(_('Failed to load MathJax file'), _( + 'Could not load the file: {} unknown response type: {}') +.format(name, xhr.responseType)) + return + if name is 'manifest.json': + get_mathjax_files.manifest = result + get_mathjax_files_stage2.files_to_get = list(Object.keys(result)) + get_mathjax_files_stage2.file_data = {} + get_mathjax_files_stage2(proceed) + return + + get_mathjax_files_stage2.file_data[name] = result + get_mathjax_files_stage2.files_to_get.remove(name) + if not get_mathjax_files_stage2.files_to_get.length: + proceed(get_mathjax_files_stage2.file_data) + +def get_mathjax_manifest(proceed): + xhr = ajax('mathjax/manifest.json', mathjax_file_received.bind(None, 'manifest.json', proceed), ok_code=0) + xhr.responseType = 'json' + xhr.send() + + +def get_mathjax_files_stage2(proceed): + if not get_mathjax_files_stage2.files_to_get.length: + proceed(get_mathjax_files_stage2.file_data) + return + for filename in get_mathjax_files_stage2.files_to_get: + xhr = ajax(f'mathjax/{filename}', mathjax_file_received.bind(None, filename, proceed), ok_code=0) + xhr.responseType = 'blob' + xhr.send() + + def get_mathjax_files(proceed): - proceed({}) + if not get_mathjax_files.manifest: + get_mathjax_manifest(proceed) + else: + get_mathjax_files_stage2(proceed) + def update_url_state(replace):