先週の土曜日に第2回shinagawa.redmine勉強会がありました。
自分はスタッフとして受付を担当していたのですが、せっかくの機会なので、今まで自分が修正してきたRedmine 1.2.0 のPDF出力機能の文字化け修正の内容をLTとして発表しました。
資料はSlideShareで公開しています。
内容は、Defect #61 Broken character encoding in pdf export とDefect #6505 PDF export broken with Japanese (Gannt and Issue Tracking) の話になります。
Redmine で使用しているPDFライブラリ"RFPDF"が下記の文字化けの問題を持っていました。
1. キリル語がNG
2. 日中韓がNG
元々RFPDFは、PHP言語で書かれたPDFライブラリ"FPDF"のRubyへの移植になります。
このFPDFをforkして、UTF-8組み込みフォント機能を追加した"TCPDF"というPDFライブラリがありました。
このTCPDFをedwinmossさんが再度RubyにRFPDFの拡張として移植してくれたので、これを使えばキリル語の問題は解消することがわかりました。ただし日中韓はNGでした。
では別の解は無いかということで、"prawn"というPDFライブラリが登場します。これは他言語からの移植ではなくRubyでオリジナルに書かれているため使い易く、かつキリル語OK、日中韓OKとかなりよかったのですが、残念ながら処理速度が遅いという問題がありました。
また、まったく別のライブラリになるためAPIの互換が無く、採用する場合はRedmineのPDF出力処理を全面的に書き換える必要がありました、これに対してedwinmossさんのRFPDFライブラリは互換性がありました。
決めてが無かったので、結局そのまま4年間放置され続けました。
ここでRFPDFのAPIの説明に移ります。
Cellメソッドは現在のXY座標にセルを書いてくれる単純なメソッドです。
改行処理などはやってくれません。
メソッド終了時のXY座標はlnで指定します。
MultiCellメソッドを使うと内部で文字列を分割してCellメソッドを呼び出してくれるので自動改行してくれます。
ここで文字コードが意図したもの以外が渡されると文字の間ではなく文字の中で分割してしまい、自動改行処理時に文字を破壊してしまいます。これが日・中・韓の文字化けの原因でした。
Redmineの中のRFPDFライブラリの構造と文字列処理の流れです。
GetStrigWidth メソッドは、実際にPDFに文字を書き出す処理に近い部分です。
1byte言語用のfpdf.rb をjapanese.rbで、MultiCellとGetStringWidthをオーバーライドしてマルチバイト(Shift_JIS)を扱えるように拡張しています。
さらにRedmine本体側でもpdf.rbで、Cellをiconvを使う形でオーバライドしています。
ただし、MulitiCellにiconv処理が無いので、そこからCellを呼ぶとき2byteしか処理できないのにUTF-8(3byte)で渡してしまい、文字を破壊し文字化けを発生させていました。
これを、pdf.rbにiconvを使用する形で、RDMMultiCell、RDMCellメソッドを定義することで文字コード正しく変換し文字化けを解消しました。
キリル語の文字化け問題が残っていたので、edwinmossさんの移植したtcpdf.rbにアップデートします。これで tcpdf.rb内のGetStringWidth等を使用すれば、UTF-8組み込みFontなどが使えるようになります。
でも残念ながら日本語は(UTF-8に対応した日本語フォントが無いので)、UTF-8以外の処理として前述の処理になります。
UTF-8を指定した場合は、UTF-8組み込みFontになりめでたくキリル語の文字化けが解決しました。
未解決の問題がまだあるので引き続き修正できればと思います。
(ちなみにRedmine 1.3.0 では、PDFのWiki書式対応と画像表示対応を行いました。)