EmacsでPDF.jsしたかった
WebViewの夢
環境: Emacs 28.0.50
はじめに
PDF.jsはMozillaが開発しているブラウザでPDFを表示するためのJavaScriptライブラリ兼PDFビューアアプリケーション。Mozilla FirefoxのPDFビューアとして利用されているが、PDFビューアの実態はWebアプリケーションなので他のモダンWebブラウザからでも利用できる。
PDF.jsの利用例として、VSCode拡張機能のvscode-pdfがある。VSCodeにはデフォルトでPDFビューア機能はないが、vscode-pdfはVSCodeのWebview上でPDF.jsのPDFビューアを表示することでPDFビューア機能を提供している。
...ム、WebView。筆者は最近GNU EmacsのWebKitによるWebView機能に熱心で、いくつか記事を書いている(これとかこれ)。VSCodeがWebview APIとPDF.jsを使って高機能PDFビューアになっているなら、これをEmacsでも試してみたくなった。
ということで、今回はEmacsのWebViewでPDF.jsのPDFビューアするメモ。
EmacsでPDF.jsしてみる
以前の記事同様、--with-xwidgets
でビルドしたNS版Emacsを使う。macOSのバージョンはCatalinaである。
GNU Emacs 28.0.50 (build 1, x86_64-apple-darwin18.7.0, NS appkit-1894.60 Version 10.15.7 (Build 19H2))
動作確認
https://github.com/mozilla/pdf.js#online-demo にあるデモを試す。
現在、PDF.jsは「async
/await
などのモダン機能をサポートしているブラウザ用」と「ES5互換にした古いブラウザ用」の二つが提供されている。ビルドしたEmacsでアクセスして動作確認する。
M-x widget-webkit-browse-url https://mozilla.github.io/pdf.js/web/viewer.html
してモダンブラウザ用のデモにアクセスすると、しっかり表示できた。
ということで、PDF.js WebサイトのDownloadからPrebuilt((ES5-compatible)でないほう)をローカルにダウンロードしていろいろ試す。
試したいこと
PDF.jsを使って、他のEmacsメジャーモードと同様、「PDFファイルを開くとpdfjs-mode
みたいなメジャーモードになって、そのPDFファイルをWebViewで閲覧できる」といったことが可能か確認する。
PDF.jsのPDFビューアには右上の「ファイルを開きます」アイコンをクリックすることでマシン上の任意のPDFファイルを開ける機能があるが、今回は上記の理由でそれは使わずに即座に目的のPDFを開く方法を模索する1。
HTTPアクセス編
ダウンロードしたzipを展開し、展開したディレクトリでHTTPサーバを建てる:
$ unzip pdfjs-2.6.347-dist.zip -d pdfjs-2.6.347-dist $ cd pdfjs-2.6.347-dist/ $ python3 -m http.server 8000 Serving HTTP on :: port 8000 (http://[::]:8000/) ...
PDF.jsはデフォルトでデモ用のPDFを付属している。試しに以下を評価してPDFに直接アクセスしてみる:
(let ((url "http://localhost:8000/web/compressed.tracemonkey-pldi-09.pdf")) (xwidget-webkit-browse-url url) (display-buffer (xwidget-buffer (xwidget-webkit-current-session))))
SafariでおなじみのPDFビューアで表示された。(GTK版Emacsだとどうなるのだろう?)
以下を評価すればデモを見た時と全く同じにPDFビューアが表示される:
(let ((url "http://localhost:8000/web/viewer.html")) (xwidget-webkit-browse-url url) (display-buffer (xwidget-buffer (xwidget-webkit-current-session))))
次に、HTTPサーバを動かしているディレクトリのweb/
以下にtl2020-sample.pdf
という適当なPDFを設置してそれをPDF.jsのPDFビューアから閲覧する。
以下を評価すればPDF.jsのPDFビューアにtl2020-sample.pdf
が表示される:
(let ((url "http://localhost:8000/web/viewer.html?file=./tl2020-sample.pdf")) (xwidget-webkit-browse-url url) (display-buffer (xwidget-buffer (xwidget-webkit-current-session))))
file:// アクセス編
上ではHTTPサーバを建ててPDFファイルを閲覧した。しかしvscode-pdfではHTTPサーバを建てずにPDF.jsのPDFビューアを利用している。EmacsでもHTTPサーバを建てずにPDF.jsできないだろうか?
ということでサーバを建てずにfile://
でアクセスしてみる。
まず、上でしたようにPDFファイルに直接アクセスしてみる。以下を評価する2:
(let ((url "file:///Users/(path/to)/pdfjs-2.6.347-es5-dist/web/compressed.tracemonkey-pldi-09.pdf")) (xwidget-webkit-browse-url url) (display-buffer (xwidget-buffer (xwidget-webkit-current-session))))
この場合、SafariなPDFビューアでしっかり閲覧できた。
では、PDF.jsを試す。以下を評価する:
(let ((url "file:///Users/(path/to)/pdfjs-2.6.347-es5-dist/web/viewer.html")) (xwidget-webkit-browse-url url) (display-buffer (xwidget-buffer (xwidget-webkit-current-session))))
この場合、以下のようになりPDF.jsのPDFビューアでデモPDFが表示されない。
これは何故かというと、PDF.jsのPDFビューアがデモ用のPDFファイルを得るのにXHRを使うが、file://
ではXHRが使えないため3Missing PDF Fileになってしまう。
file:// アクセスでもゴリ押し編
PDF.jsのPDFビューアで任意のPDFを閲覧するには「JavaScirptでwindow.PDFViewerApplication.open
を呼ぶ」という方法もある。ただしwindow.PDFViewerApplication.open
に"file://~"
のようなurl文字列を渡しても結局XHRされてしまうため開けない。
window.PDFViewerApplication.open
は引数にurl文字列ではなく、Uint8Array
といった型付き配列をPDFバイナリとして渡すことができる。ということで、PDFをEmacs Lispでバイナリ配列にしてブラウザに評価してもらい任意のPDFを開く。🙃
以下を評価する:
(progn (xwidget-webkit-browse-url "file:///Users/(path/to)/pdfjs-2.6.347-dist/web/viewer.html") (sit-for 0.1) (let* ((xw (xwidget-webkit-current-session)) (filename "/Users/(path/to)/pdfjs-2.6.347-dist/web/compressed.tracemonkey-pldi-09.pdf") (pdf-bytes (vconcat ;; from f-read-bytes (with-temp-buffer (set-buffer-multibyte nil) (setq buffer-file-coding-system 'binary) (insert-file-contents-literally filename) (buffer-substring-no-properties (point-min) (point-max))))) (js-array (replace-regexp-in-string " " "," (format "%s" pdf-bytes)))) (xwidget-webkit-execute-script xw (format "window.PDFViewerApplication.open(new Uint8Array(%s));" js-array)) (display-buffer (xwidget-buffer xw))))
上を評価するとちょっと時間はかかるがPDFを表示できた。上で貼ったデモPDF表示と同様の表示のため画像は載せないが、バイナリ列を与えることでサーバを建てずに任意のPDFを表示できた。
と、調子に乗ってtl2020-sample.pdf
も同様の手法で開いてみるとこの有様。
ToUnicode CMapが埋め込まれていないフォントを表示するのに必要なCMapファイルをXHRで取得できないため、日本語が表示できなかった。これは致命的なのでfile://
アクセスでのゴリ押しも諦めて、これにて検証終了とする。
VSCodeのWebview API
VSCodeのWebview APIではローカルにHTTPサーバを建てずともちゃんとWebアプリケーションが動く。
VSCodeは拡張機能でWebview API利用時にlocalResourceRoots
を設定し、それ以下のファイルに対してはしっかりXHRも働くようになっているのだと思われる4。
VSCodeのWebviewとEmacsのWebView
EmacsのWebViewはEmacs内でWebブラウジングするための機能という感じである。対してVSCodeのWebviewはWebの力を使ってクロスプラットフォームな機能を実現するためのもので、そもそもWebブラウジングするためのものではなさそう。VSCodeはデフォルトでPNGなどの画像を表示できるが、これもWebviewを使っている。
また、Emacsのxwidget-webkit機能はまだまだ発展途上だ。Webの力を使ってEmacs上でクロスプラットフォームなアプリケーションの実現はまだまだ先になりそう(来ないかもしれない)。
まとめ
EmacsのWebViewでは(VSCodeのとは違って)マトモにWebアプリケーションを動作させるにはHTTPサーバ建てる必要がありそう。
EmacsのWebViewは発展途上で操作性に難アリなので、やはりEmacsでPDF見るなら pdf-tools だろうか。
おしまい。
-
<input type="file">
なので、頑張っても.click()
呼んでユーザに選択してもらうところまでしかできないはず(多分)。↩ -
(path/to) は省略したもの。↩
-
使えるようにするにはパッチを当てる必要がある swift - iOS - WKWebView Cross origin requests are only supported for HTTP - Stack Overflow↩
-
VSCodeはelectoronのprotocol APIを使って、
file://
ではない独自のschemeを利用しているようだ。参考: https://github.com/microsoft/vscode/blob/6bebcbb58d95e59ffde0e372888c5eea3995bf82/src/vs/platform/webview/electron-main/webviewProtocolProvider.ts↩