Apache FOPでPDF生成

(2007.11.11更新)

Apache FOP (Formatting Objects Processor) は、XSL formatting objects (XSL-FO) 文書をPDF, PNGなどに変換して出力するツールです。

Ruby on RailsアプリケーションでPDF出力するために、内部で使えないか試してみました。

OSはFedora 7 Linuxです。

XSL FO

XSL FOは、印刷などでのレイアウトを指定するためのXML文書型で、直感的にはHTMLとCSSを混ぜたようなものです。XSL FO仕様の最新版は、2006.12.5付W3C勧告のバージョン1.1です。

普通のXML文書はレイアウト(見栄え)とは関係ないので、XSLTでXSL FOに変換することになります。今回は、直接XSL FO文書を用意してみます。

インストール

2007.10現在、Apache FOPの最新版はバージョン0.94です。バイナリ版をダウンロードしてきて展開します。fopコマンドへのシンボリックリンクを適当なところに張るだけでOKです。

Javaは、Fedora 7のgij版ではなく、SunのJava VMでないといけません。gij版だとFOPを走らせたときにエラーが発生したりしました。また、速度もSun版のほうが速いです。

やってみよう

最小の(と思う)XSL FO文書を作ってみます。ファイル名をfop_1.foとして保存します。

HTML
[POPUP]
  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
  3. <fo:layout-master-set>
  4. <fo:simple-page-master master-name="PageMaster">
  5. <fo:region-body margin-top="3cm"/>
  6. </fo:simple-page-master>
  7. </fo:layout-master-set>
  8. <fo:page-sequence master-reference="PageMaster">
  9. <fo:flow flow-name="xsl-region-body">
  10. <fo:block font-size="18pt" text-align="center">
  11. Hello: XSL-FO to PDF
  12. </fo:block>
  13. </fo:flow>
  14. </fo:page-sequence>
  15. </fo:root>

XSL FOの名前空間は、http://www.w3.org/1999/XSL/Format です。ルート要素はroot。

layout-master-setで余白などを指定します。流し込むテキストなどのデータはpage-sequenceで与えます。

テキストはblock要素の内容とし、font-sizeなどが指定できます。見栄えに関する属性は、CSS2とほぼ同じです。

これをPDFに変換します。

$ fop fop_1.fo -pdf fop_1.pdf

特にエラーもなく出力されました。表示も問題ありません。簡単。

日本語フォント

日本語を表示するために、TrueTypeフォントを使えるようにします。

手順としては、

  1. ttfファイルからフォントメトリクス (font metrics) ファイルを作成する
  2. FOP設定ファイルにフォント名などを登録する

Note.

(2007.11.9)

FOP 0.9.4では、フオントメトリクスファイルは不要です。FOP設定ファイルでTrueTypeフォントファイル (.ttf) を指定すれば、自動的にフォントデータが参照されます。

さざなみフォントで試してみます。まず、フォントメトリクスの作成は、

$ java -cp build/fop.jar:lib/commons-logging-1.0.4.jar:lib/commons-io-1.3.1.jar \
      org.apache.fop.fonts.apps.TTFReader \
      /usr/share/fonts/japanese/TrueType/sazanami-mincho.ttf \
      ~/sazanami-mincho.xml

設定ファイルは conf/fop.xconfファイルをひな形にします。このファイルはfopコマンドで明示的に指定 (-cオプション) しないと参照されません。最初これに気付かず、なぜ修正しても反映されないのか悩みました。example configuration fileと書いてますね。

次のようにします。

HTML
[POPUP]
  1. <?xml version="1.0" ?>
  2. <fop version="1.0">
  3. <renderers>
  4. <renderer mime="application/pdf">
  5. <fonts>
  6. <font metrics-url="メトリクスファイルのフルパス"
  7. embed-url="/usr/share/fonts/japanese/TrueType/sazanami-mincho.ttf"
  8. kerning="yes">
  9. <font-triplet name="SazanamiMincho" style="normal" weight="normal" />
  10. </font>
  11. </fonts>
  12. </renderer>
  13. </renderers>
  14. </fop>

fontタグのmetrics-url属性に相対パスを書くと、実行時のカレントディレクトリが基準になります。ちょっと扱いにくい。

font-tripletタグは複数書けますが、別名の指定になります。weight="bold"としたからといって、自動的に太字で表示できるわけではありません。

foファイルを更新します。

HTML
[POPUP]
  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
  3. <fo:layout-master-set>
  4. <fo:simple-page-master master-name="PageMaster">
  5. <fo:region-body margin-top="3cm"/>
  6. </fo:simple-page-master>
  7. </fo:layout-master-set>
  8. <fo:page-sequence master-reference="PageMaster">
  9. <fo:flow flow-name="xsl-region-body">
  10. <fo:block font-family="SazanamiMincho" font-size="18pt" text-align="center">
  11. こんにちは。明るい明日。
  12. </fo:block>
  13. <fo:block font-family="SazanamiMincho" font-size="25pt" font-weight="bold">
  14. こんにちは。明るい明日。
  15. </fo:block>
  16. </fo:flow>
  17. </fo:page-sequence>
  18. </fo:root>

fopコマンドを走らせると、bold フォントがないのでフォント幅normalにfallbackする旨のメッセージが出力されます。文字化けしたりはしません。

実用的なサンプル

(2007.11.9加筆)

実践的なFOファイルとして請求書を作ってみました。外部画像の参照、フォント選択、表、ヘッダ・フッタ、背景色の指定などをおこなっています。

FOファイル: fop-invoice.fo; 出力されたPDFファイル: fop-invoice.pdf

Adobe Readerでは問題なく表示できますが、Linux の Evince だと日本語部分が真っ白になります。なぜだろう?

(2007.11.1)

XSL FOの表オブジェクトは、HTMLの表と構造が似ています。表は行の集まりであり、行はセルの集まりです。

HTML XSL FO
table table
tr table-row
th, td table-cell

TODO:

まとめ

Apache FOPは、XML文書であるFOデータを与えればいいので、呼び出すほうのプログラミング言語やフレームワークを選びません。表現力も豊かです。

手書きでFOファイルを書こうとすると、タグ名(要素名)が長いのが難儀です。また、プレビュー & 試行錯誤がHTML + Webブラウザほど手軽ではありません。ラフにHTMLで書いて、XSLTでFOに変換して細部を調整するのがいいかもしれません。

Railsと組み合わせる場合、フォーム内のデータなどはデータベースから取得 (ActiveRecord) し、ERBでFOファイルを生成し、それからFOPに掛けることになります。

外部リンク

XSL-FOによるレイアウト見本
テーマごとにFO見本と結果PDFがあります。一部Apache FOPが対応していないタグ(要素)を使っていますが、FOを書く参考になります。