Show page source of pdf2text_pdfjs_v8 #97582

= PDF.jsとV8を使ってPDFからテキストを抽出するプログラム(exe形式)
== 概要
以前、 [extract_pdf PDFからテキストを抽出するプログラム] で、様々なライブラリを組み合わせて
辛うじて動くレベルのものを何とか作りましたが、
HTML5 と Javascript の機能だけを用いて、PDFを読み込み、ブラウザの画面に表示できる PDF.js と、
Javascript を実行できる V8 をうまく繋ぎ合わせる方式で作れば、 より確実に動くものが簡単に作れるの
ではないかと考え、ちょっと試してみました。 

結果、下記の処置で実現できましたので、成果物を公開します。
 * 非同期処理(setTimeout)再現用 Javascript ソース (計14 行) の作成
 * PDF.js 側のソースのコメントアウト(2箇所)
 * PDF.js を用いたテキスト抽出用ソース2点 (C++、Javascript それぞれ1つずつ) の作成

== 成果物
 * [http://sourceforge.jp/downloads/users/4/4043/pdf2text_pdfjs_v8_executable.zip 実行可能ファイル(Windows用)]
 * [http://sourceforge.jp/downloads/users/4/4042/pdf2text_pdfjs_v8.zip 本体のソース]
 * [http://sourceforge.jp/downloads/users/4/4039/dependencies_src.zip 依存ライブラリ(V8、PDF.js)のソース]

== ライセンス
私が作成した部分は BSD ライセンスとしました。他の部分はオリジナルのライセンスに従って下さい。

== ソフトの使い方
コマンドプロンプトで PDF ファイルを引数にしてソフトを実行すると、テキスト化された内容を標準出力に書き出します。

== 本体ソースのファイル構成
下記6ファイルで構成されています。
 * 非同期処理(setTimeout)再現用 Javascript ソース
   * messageLoop.js
   * postMessage.js
 * 部分的にコメントアウトした PDF.js ソース
   * pdf.js
   * pdf.worker.js
 * PDFのテキスト出力処理
   * pdf2text.js (Javascript側)
   * pdf2text_pdfjs_v8.cc (C++側)

== 説明
=== 非同期処理(setTimeout)再現用 Javascript ソース (計14 行) の作成
PDF.js は [http://mozilla.github.io/pdf.js/ こちらの配布元] のPre-built版(v1.0.21)をダウンロードしました。
配布物の中にある pdf.js、pdf.worker.js の 2 点を使います。

PDFを読み込んでテキスト等を表示する処理を作る場合、特に制約がなければ
PDFファイル読み込み→解析→結果表示 とストレートな処理の流れになるはずですが、
ブラウザでこの流れの通りに処理すると、特に PDF のページ数が多い場合やページ当たりのデータ量が多い場合などで、
しばらくブラウザが固まったような状態になってしまいます。
これを避けるため、PDF.js では、
 1. PDF読み込み関数を setTimeout に登録して一旦ブラウザに処理を戻す。
 2. setTimeout された関数がPDFを読み込み、1ページ目の内容を解析する関数を setTimeout に登録してブラウザに処理を戻す。
 3. setTimeout された関数が1ページ目の内容を解析し、結果を canvas に書き出す。
といった感じで非同期処理をしています。
(なお、この処理をスマートに記述するために、Promiseパターンと呼ばれる方法を採っているようです。)

PDF.js を大修正することなく素の V8 で動かすためには、setTimeout による非同期処理を再現する必要があります。
postMessage.js と messageLoop.js の2つがこの役割を担っています。

postMessage.js の中には、 配列 timeout_queue の初期化と setTimeout 関数が入っていて、 
setTimeout には、引数で与えられた関数を timeout_queue に追加する処理を記述してあります。

messageLoop.js の中には、 配列 timeout_queue の各要素を順番に実行する処理を記述してあります。

これを、postMessage.js、 pdf.js、 pdf.worker.js、 pdf2text.js (PDFのテキスト出力処理)、 messageLoop.js 
という順番で実行することで、上記の非同期処理と同じような流れで処理させることができるようになりました。

=== PDF.js 側のソースのコメントアウト(2箇所)
pdf.js には、 DOM(document.createElement(...) ) で pdf.worker.js を script要素として追加する処理が入っています。
また、pdf.worker.js も navigator オブジェクトから userAgent を取得して何らかの処理をしています。
素の V8 には DOM オブジェクトもnavigatorオブジェクトもありませんので、そのまま実行してもエラーとなってしまいます。
素の V8 で動作させる方法として、これらを再現するためのダミー関数、ダミーオブジェクト等を用意するという手もありますが、
今回は該当する処理をコメントアウトする方法で対処しました。
pdf.js や pdf.worker.js の他の場所にも同様の処理が含まれていますが、下記2箇所のコメントアウトをするだけでPDFからテキストに
変換する処理が動作しましたので、他の箇所はそのまま放置してあります。

==== コメントアウトした箇所
 *  pdf.js の Util_loadScript 関数 (723行目付近) 
  関数の中身をコメントアウトし、callback() だけ無条件に実行するように変更
 *  pdf.worker.js の // Workaround for seac on Windows. と書かれた部分(25251行付近) 
  ブラウザ固有の処理のようなので不要と判断し、 checkSeacSupport() 関数、及びその呼び出し処理を全てコメントアウト

=== PDF.js を用いたテキスト抽出用ソース2点 (C++、Javascript それぞれ1つずつ) の作成
PDF.js のAPI を呼び出して PDF データを テキストに変換する処理を pdf2text.js (Javascript側) に、
PDFファイルを読み込んで ArrayBuffer オブジェクトに変換する処理、postMessage.js、 pdf.js、 pdf.worker.js、 pdf2text.js、 messageLoop.js 
の順に.js ファイルを読み込んで V8 に投げる処理、変換したテキストを printf で出力する処理を pdf2text_pdfjs_v8.cc (C++側) に、それぞれ記述してあります。

== 参考サイト
 * http://mozilla.github.io/pdf.js/ PDF.js 配布元 - Mozilla 様
 * http://code.google.com/p/v8/ V8 配布元 - Google 様
 * http://kichipoyo.hatenablog.com/entry/2013/11/21/223527 PDF.js で遊んでみた (ページの描画,テキスト・注釈の表示など) - きちぽよ〜 様
 * http://blogs.msdn.com/b/ie_jp/archive/2011/10/05/10220180.aspx "promise" による JavaScript での非同期プログラミング - マイクロソフト 様
 * http://js-next.hatenablog.com/entry/2013/11/28/093230 Promiseが実装された - Hikaru 様
 * http://stackoverflow.com/questions/1554280/extract-text-from-pdf-in-javascript extract text from pdf in Javascript - stackoverflow 様

参考サイトリスト作成の際に 「Promiseが実装された」を読み返したところ、もしかしたら postMessage.js、 messageLoop.js 相当の処理は
V8 に含まれているのかもしれない、と思い始めました…。気が向いたら確認してみます。