PDFBoxとFXGraphics2Dを使って大きなPDFをレンダリングする

この記事は、JavaFX Advent Calendar 2015 - Qiita の 16 日目の記事です。

昨日は kimukou さんの 

basilisk-fw について試食した雑感 - exception think でした。

はじめに

先日お伺いしたJJUG CCC 2015 fall でセッションをさせて頂いた際に、SORACOMの業務システムにJavaFXを使っていますよ、という話をさせて頂きました。

セッションの中では、Swaggerの話やAWS Lambdaの話などをさせて頂きましたが、「javax.smartcardioでSIMカードを読む」というくだりが(さすがにJavaの人たちなので)一番反応が良かったので、このアドベントカレンダーでもJavaFXを使ったカードリーダーにしよう、と思ったわけなのですが、よく考えるとカード読み取りの説明ばっかりでほとんどJavaFX出てこないな、と思いまして、今回は別の内容にすることにしました。

PDFBoxとFXGraphics2Dを使って大きなPDFをレンダリングする

タイトルそのままですが、JavaFXを使ったPDFViewerの話です。

JavaオープンソースPDFと言えばiTextを連想される方も多いと思いますが、iTextはPDF生成ライブラリの色が強く、画面や画像レンダリングの機能はありません(少なくともAGPL以前のものにはない)

このため、作成したPDFをJava上で表示したり、PDFレンダリング非対応のプリンタで印刷しようとすると、レンダリング機能のあるライブラリが必要になりますが、以前は日本語が使えるのは、JPedalなどの商用ライブラリしかまともに動くものはなかったかと思います。

しかしながら、最近Apache PDFBoxがメキメキと実力をつけてきたようで、最新版の2.0はまだRC2ながらも既に日本語レンダリングが行えるようになり、弊社でも帳票出力などに活用しています。

よくあるPDFBoxを使った表示サンプルは、以下の様なものになります。PDFBoxが提供するPDFRendererを使って作ったImageを、JavaFXのImageViewで表示しています。

gist.github.com

これの結果は以下のとおりで、一旦BufferedImageにレンダリングされたPDFが、JavaFX上で表示できます。 

f:id:c9katayama:20151216221304p:plain

スケールを大きくする

上記のPDFは、スケールが1倍(等倍)で表示されています。PDFの拡大はよくあるシチュエーションだと思いますので、ためしに上記PDFを10倍に拡大してみます。

サンプルコードの「int pdfScale = 1;」を10にすれば、10倍サイズでレンダリングされます。

f:id:c9katayama:20151216221618p:plain

正しく拡大されていますね。では50倍ではどうでしょうか?

実は50倍にすると、renderer.renderImageの部分で、OutOfMemoryErrorが発生します。

PDFBoxの提供するPDFRenderer#renderImageは、描画のために引数のスケールに応じたBufferedImageを用意するため、倍率が高いと巨大なBufferedImageが作られてしまい、OutOfMemoryErrorが発生してしまいます。

FXGraphics2Dを使う

PDFRendererクラスには、renderImageメソッドの他に、renderPageToGraphicsというメソッドが用意されています。

これは引数にjava.awt.Graphics2Dを取るメソッドで、これを使うとSwingのコンポーネントなど、Graphics2Dをグラフィックコンテキストに使用するコンポーネントに対して直接描画を行うことが出来ます。

これを使って、JavaFXコンポーネントに直接描画を行うことで、無駄な画像描画を行わず、拡大したPDFを表示することが出来ます。

しかしながらGraphics2Dはjava.awt時代のもので、直接JavaFXでは扱うことができません(もしかしたらSwingとのインターオペラビリティクラスにそういうのがあるかもしれませんが)

ということで、FXGraphics2Dというクラスを使用します。これはjFreeチャートの人が作ったクラスのようで、Graphics2Dと、javafxCanvasに書き込めるGraphicsContextのアダプタとして使用することが出来ます。

これを使って、PDFをJavaFXCanvasに直接書き込んでみます。

gist.github.com

等倍での書き込み。これは先ほどのrenderImageのものと変わりません。

f:id:c9katayama:20151216223854p:plain

 50倍での書き込み。正しくレンダリングできているようですが、左上を起点にしているので、緑色しか出ません。

f:id:c9katayama:20151216224043p:plain

 FXGraphics2Dを使ったほうは、倍率を上げても、その分大きなBufferedImageが作られるわけではないので問題なく描画できます。

緑色だけでは面白く無いので、ドラッグできるものも作ってみました。

gist.github.com

 PDFをドラッグした分、描画の起点をずらす実装が入っています。

Graphics2DにAffineTransformを設定すると、PDFRendererの描画処理の座標を、GraphicsContextに伝える際に座標変換出来ます。GraphicsContextはCanvasのサイズでClipされていますが、その範囲外でPDFRendererが行った描画処理を、座標変換することでClip内に収め、見た目ドラッグして移動したようにしています。

出来たサンプルを動かしてドラッグすると、以下の様な感じになります。

 

f:id:c9katayama:20151216230241p:plain

「世界中の」の「世」の部分ですね。ちゃんと描画できているのが分かります。

まとめ

PDFBoxは日本語対応も進んでいるので、2.0のリリースが待ち遠しいですね。FXGraphics2Dがあれば、既存のチャートライブラリなんかはひと通り使えると思うので、安心ですね。

JavaFXはSceneBuilderとeclipseという環境で開発していますが、UIとロジックが綺麗に別れるので、個人的には非常に便利だと思っています。AdobeFlexはまさにこういうスタイルだったので、Flex亡き今、Javaで生産性の高い仕掛けがあるのは非常に嬉しいです。

 

JavaをやっておきながらJJUGは実は初参加で、しばらくJava実装もやっていなかったのでJavaコミュニティーに行くのも4年ぶりぐらいで若干ビビってましたが、そんなことはなくてJavaの話で盛り上がれて、本当に楽しかったです。@makingさんのリアクティブの話や@sugarlifeさんのGCの話は非常に勉強になりましたし、特にSpringMVCがリアクティブでも頑張ってくれるという話は個人的には大興奮のポイントでした。

またTwitterではよく見かけているうらがみさんや、相変わらずスベってるセロさんにも会えましたし、なによりきしださんがセッション前に資料作成が終わってたのには驚かされました。人は成長するものですね。

そんなわけでまた機会があればネタを作ってJavaイベントに参加したいと思います。

 

明日はskrbさんのブログです。