TCPDFでOTFフォントを使用する

PHPでPDFを扱うためのライブラリにTCPDFがあります。
TCPDFでOTFフォントを使用すると、AcrobatReaderでは正常に読めても、Illustratorで正常に読めず、テキストがラスタライズされて少々気持ちの悪い事態に遭遇することとなります。
Illutratorで開くことを想定しているなら、TCPDFで扱うフォントには少々の工夫が必要なので、ここに残しておきます。

まず、TCPDFで扱うフォントはTTFフォントで統一します。フォント名が同じであればTTFで作成したPDFでも、OTFフォントがインストールされた環境のIllustratorでも問題なく読むことが出来ます。

OTFフォントをTTFフォントに変換するにはFontForgeを使用します。
インストール方法を検索すると、Windowsのcygwin環境下でのインストール方法が沢山ヒットしますが、個人的にはうまくいかなかったので、LinuxのCentOS5を使用しました。アプリケーションはGUIでx11環境が必要なので、デスクトップ版を用意しましょう。
パッケージはhttp://sourceforge.net/projects/fontforge/files/fontforge-executables/から「fontforge-20090923-1.i386.rpm」をダウンロードします。少々古いものですが、特に問題ありません。ダウンロードが終わると、ファイルをダブルクリックするだけでインストールが始まり、あっさりと完了します。

アプリケーションはGUIなのですが、メニューには登録されませんのでファイルを直接起動するか、ターミナルから起動する必要があります。ターミナルで「fontforge」と入力して起動すると、読み込むファイルを要求されるのでTTFフォントに変換したいOTFフォントを選択します。

選択したOTFファイルの内容によってはCIDマップファイルが必要と言われるかもしれません。CIDマップファイルはhttp://fontforge.sourceforge.net/cidmaps.tgzにありますので、ダウンロードして展開したファイルを「/usr/share/fontforge/」にコピーします。

しばらくして読み込みが完了すると文字コード表が表示されるので、メニューより「CID>単一化」を選択してファイルを統合します。
そしてメニューより「ファイル>フォントを保存」を選択して、保存形式を「TrueType」に設定し、保存ファイル名を入力して保存すれば完了です。
これで変換作業は完了です。作成されたTTFファイルを開いて正常に読み込むことができれば成功です。

フォントファイルをTCPDFで扱える状態にする方法は、ライブラリ内のtcpdf/fonts/utils/README.TXTにある通りで問題ありません。むしろ他に方法がありません。
「$ ttf2ufm -a -F myfont.ttf」
「$ php -q makefont.php myfont.ttf myfont.ufm」
これで作成されたファイルをfontsディレクトリにコピーし、TCPDFよりPDFを作成すればAcrobatReaderでもIllustratorでも問題なく開くことが出来るPDFファイルの完成です。

いまいち文章が推敲されていないのでまとめます。

TCPDFはPDF作成ライブラリですごい
TCPDFのPDFはOTFフォントを使うとIllustratorでは読めない(ラスタライズされる)ことがある
TCPDFのPDFをIllustratorで読む可能性があるならOTFフォントをTTFフォントに変換しておく
OTFフォントをTTFフォントに変換するにはFontForgeを利用する
CIDマップがないとOTFファイルを展開できない(ことがあるかもしれない)

TCPDF5.8.034のフォント指定の不具合

PHPでPDFを扱うにはまずTCPDF一択なわけですが、バージョンをよく見ると4.6.026という3年ぐらい前のソースなので新しくしようと思い立ちました。
動いていたので特に問題はないのですが、これだけ古いとどうかなという感覚的なものです。
最新版の5.8.034を入れて、動作を確認したところ、特に問題なし。
次のステップとして自前で用意したIPAフォントを指定したところ、処理が戻ってこないままタイムアウトになりました。
古い4.6に戻すと正常に出力されます。
新しい5.8にするとタイムアウトします。
だめだこりゃ。
別のバージョンで試すしかない。
どのミラーも最新版に更新されていて、最新版しか置いてない。
あちこち探し回った結果、発見したのが
http://ftp.heanet.ie/disk1/sourceforge/t/project/tc/tcpdf/OldFiles/
これで1つづつ遡りながら、動くバージョンを選ぶ作業に移ります。

時間をかけてマイナーバージョンごとに動作を確認して言ったのですが、どうも5.2から実装されたらしいサブセット埋め込みで不具合が発生している模様。
解説サイトでは5.4で解消されたと書いてあるけれど、検証していないみたいで実際に走らせるとタイムアウトになってしまう。
時間を大きくとれば言いなんて書いてあるけど、レスポンスが分単位なんて実際問題ありえないし、これは実装の不具合としか言いようがない。
結局、5.2の前のバージョン、5.1.002を導入してシステムを構築することにしました。

Windows7にもてあそばれる

一通り入れた後に、MySQLを入れ忘れたのに気がついて入れてみたところ、PHPからアクセスできない。実際にはphpMyAdmin3からアクセスできない。エラーメッセージも表示されず、1分ぐらい待たされて方真っ白な画面になってしまう。
コマンドからは問題なくアクセスできるものの、mod_phpからアクセスできない。
このときのPHPは5.3.3、MySQLは5.1.50(64bit)でした。
httpd.confやphp.iniやmy.iniをいじっては見たものの改善せず。
ここで不貞寝しました。

そして翌日。
まず最初にMySQLをノーマルからエッセンシャルに入れ替えたり、32bitにしたりバージョンを入れ替えたりしたけれどうまくいかない。PHPを入れる際にVCのライブラリを更新したのでそれに問題があるのではと考え、仕方がなくWindows7を入れなおすことに。

とりあえずWindows7を上書きでインストールしなおしてみたところ、前のシステムが複製されて残っていて、とっても気持ちが悪い状態に。仕方がないので改めてパーティションをフォーマットしなおしてから、改めて入れなおすことに。

まず最初にMySQL5.1.50(64bit)を入れて、次にApache2.2.16、そしてVCを更新せずにPHP5.3.3を入れなおしてみたところ、やはり現象は同じ。
ここでふとPHPのインストーラーにVCの対象を選択する新しい項目があったのを思い出して、前のバージョンで試してみようと思い立ちました。
PHP5.2.14を入れなおしたところ、zlib.dllがないといわれるのでネットから拾ってきて放り込んだところ、難なく起動。

問題だったのはPHP5.3.3.
データベースとの連携が高いはずのPHPで、このような障害が発生しました。
何でもかんでも組み込み関数にしてしまったPHPの成れの果てを垣間見た気がしました。

結局それ以外では、ActivePerlを64bitにするとMinGWと連携できないようなので、32bitで入れなおしたぐらいで、特に大きな問題もなく開発環境は整いました。
あとはデータを移していくだけです。これもかなり面倒なのですが。

PHPのセーフモードに悶え苦しむ

PHPの開発はなんだかんだで自前鯖や自宅鯖だったので、モジュールでフルの状態で動かしていたわけです。
プリントライはcoreserverに乗せたのですが、ここはセーフモードなのです。
あらかじめわかってはいましたがここまで時間をとられるとは思いませんでした。
まぁ、なんだかんだでCGIモードでなければならない部分はAjaxで逃げて事なきを得ています。
とりあえず行き詰った部分を。

・execが使えない(セーフモードというよりは鯖の設定か)
・mail関数で第5引数があるとワーニング8セーフモードのせい)
・ディレクトリのオーナーが違うと書き込めない(セーフモードのせい・Apacheとphpのユーザ)

PHPのGDで文字を描く

PHPで画像を扱うにはGDが楽なのですが、文字を書こうとすると思ったポイントで描画されません。
おそらくDPIを設定しないとだめなのでしょうが、PHPのリファレンスを探しても見つかりませんでした。
仕方がないのでいろいろ値をとりながら調べてみると、96dpiということが判明。
webではなくアプリケーション向けに72dpiで画像を生成するには以下のような手順となった。
ちなみに画像からはみ出にくいように調整もしてある。

$fontSize = 72;
$dpi = 96;
$margin = $fontSize / 8;
$baseline = $fontSize + $margin;
$size = $fontSize + $margin * 2;

$image = imagecreatetruecolor( $size, $size );

imageantialias( $image, true );
imagealphablending( $image, false );
imageSaveAlpha( $image, true );

$fillcolor = imagecolorallocatealpha( $image, 0, 0, 0, 127 );
imagefill( $image, 0, 0, $fillcolor );

$black = imagecolorallocate( $image, 0, 0, 0 );

$text = mb_convert_encoding( "あ", 'utf8' );
$font = 'xxxx.otf'; // 適当に置き換えて
imagettftext( $image, $fontSize/$dpi*$fontSize, 0, 0, $baseline, $black, $font, $text );

imagepng( $image, 'output.png', 9 );
imagedestroy( $image );

印刷も考えるとEPSで描いてから画像にするのが一番確実だけど、imagemagic(正確にはghostscript)を経由するのも面倒だしこうなった。

PHPにPEAR::SOAPをインストールする

CentOS5.3での話です。
SOAPが必要になったのでPEARからインストールすることにしました。
PHP5ではあらかじめ用意されているみたいだけど、公式にすらまともな解説がないのであきらめました。
pear install SOAP-beta
Did not download optional dependencies: pear/Mail, pear/Mail_Mime, pear/Net_DIME, use –alldeps to download automatically
pear/SOAP requires PEAR Installer (version >= 1.5.4), installed version is 1.4.9
pear/SOAP can optionally use package “pear/Mail”
pear/SOAP can optionally use package “pear/Mail_Mime”
pear/SOAP can optionally use package “pear/Net_DIME”
No valid packages found
install failed
どうやら、インストーラーが古いのと、いろいろと足らないものがあるので–alldepsスイッチをつけろとのこと。

pear install --alldeps SOAP-beta
pear/SOAP requires PEAR Installer (version >= 1.5.4), installed version is 1.4.9
pear/Mail_Mime requires PEAR Installer (version >= 1.6.0), installed version is 1.4.9
pear/Mail_mimeDecode requires PEAR Installer (version >= 1.6.0), installed version is 1.4.9
pear/Mail_mimeDecode requires package "pear/Mail_Mime" (version >= 1.4.0, excluded versions: 1.4.0)
downloading Mail-1.2.0b2.tgz ...
Starting to download Mail-1.2.0b2.tgz (21,972 bytes)
........done: 21,972 bytes
downloading Net_DIME-1.0.1.tgz ...
Starting to download Net_DIME-1.0.1.tgz (7,535 bytes)
...done: 7,535 bytes
downloading Net_SMTP-1.3.3.tgz ...
Starting to download Net_SMTP-1.3.3.tgz (10,944 bytes)
...done: 10,944 bytes
downloading Auth_SASL-1.0.3.tgz ...
Starting to download Auth_SASL-1.0.3.tgz (5,724 bytes)
...done: 5,724 bytes
install ok: channel://pear.php.net/Auth_SASL-1.0.3
install ok: channel://pear.php.net/Net_SMTP-1.3.3
install ok: channel://pear.php.net/Net_DIME-1.0.1
install ok: channel://pear.php.net/Mail-1.2.0b2


まだ何か文句を言っているので、PEARをアップグレードしてみる

pear upgrade PEAR
downloading PEAR-1.9.0.tgz ...
Starting to download PEAR-1.9.0.tgz (291,634 bytes)
.............................................................done: 291,634 bytes
upgrade ok: channel://pear.php.net/PEAR-1.9.0
PEAR: Optional feature webinstaller available (PEAR's web-based installer)
PEAR: Optional feature gtkinstaller available (PEAR's PHP-GTK-based installer)
PEAR: Optional feature gtk2installer available (PEAR's PHP-GTK2-based installer)
To install use "pear install pear/PEAR#featurename"


もう一度SOAPを入れなおしてみる

pear install -f --alldeps SOAP-beta
WARNING: "pear/HTTP_Request" is deprecated in favor of "pear/HTTP_Request2"
downloading SOAP-0.12.0.tgz ...
Starting to download SOAP-0.12.0.tgz (71,233 bytes)
.................done: 71,233 bytes
install ok: channel://pear.php.net/SOAP-0.12.0


まだしつこく何か言ってるけど、疲れたので放置。

PHPのPEARで覚書

CENTOS5.3での話です。
PEARをアップグレードしようとすると以下のように怒られました。

pear upgrade PEAR
WARNING: channel "pear.php.net" has updated its protocols, use "channel-update pear.php.net" to update
pear/Archive_Tar requires PEAR Installer (version >= 1.5.4), installed version is 1.4.9
pear/PEAR dependency package "pear/Archive_Tar" installed version 1.3.1 is not the recommended version 1.3.3, but may be compatible, use --force to install
No valid packages found
upgrade failed


PEARが古いのと、Archive_Tarが古いといわれてしまいました。

pear channel-update pear.php.net
Retrieving channel.xml from remote server
Update of Channel "pear.php.net" succeeded


まずPEARを更新して、

pear upgrade pear/Archive_Tar
pear/Archive_Tar requires PEAR Installer (version >= 1.5.4), installed version is 1.4.9
No valid packages found
upgrade failed


あれ、失敗?

pear install -f pear/Archive_Tar
warning: pear/Archive_Tar requires PEAR Installer (version >= 1.5.4), installed version is 1.4.9
downloading Archive_Tar-1.3.3.tgz ...
Starting to download Archive_Tar-1.3.3.tgz (18,119 bytes)
......done: 18,119 bytes
install ok: channel://pear.php.net/Archive_Tar-1.3.3


強引にねじ込みました。

PHPのparse_ini_fileでハマる

parse_ini_fileは設定ファイルをパースしてくれる関数ですが、使い勝手が良いのか悪いのか。
文字列に「”」ダブルクオートを含む場合、そのままでは記述できません。
エスケープしてみたり、文字列をほかのもので囲んでみたりしたけど、希望するとおりにならない。
しかたなくphpのリファレンスを見ると、定数で逃げればいいとの事。
iniを動的に作成したりする場合は、

define( 'QUOTE', '"' );

必須ということですね。

こういうやり方が残っているとは、PHPもまだまだだなぁと思う今日この頃でした。
ちなみに、検索で調べた中で文字列自体を定数にしろという意見もありましたが、定数と変数の違いを理解していない人が多いのは残念です。
とはいえ、自分も定数に逃げている部分もあるので、大きいことはいえませんが。

HTTP_REFERERは使うな

PHPで組んだものをIEデミルとどうも動きが怪しい。
ちょっと調べてみると、IEがセキュリティ設定の都合でHTTP_REFERERを吐いていないらしく、一部画面の推移に影響が出ていた。
てっきりJavaScriptだけだと思っていたのに、サーバにも情報を隠蔽していたらしい。
仕方がないので、HTTP_REFERERを使わないようにい変更するしかない・・・

Spreadsheet_Excel_Readerのバグ

セルの書式に文字列を指定すると、

Notice: Undefined variable: formatstr in D:\xxx\xxx\Excel\reader.php on line 636


という注意が出ます。
xxxは適当なパスだと思ってください。

このエラーメッセージの出るところを見てみると、変数の初期化がきちんと行われていなため、例外処理が不完全だということのようです。
このあたりの一連の処理が始まるところで、$formatstrを初期化してしまいましょう。
630行目に
$formatstr=”;
とでもしておきます。
これで注意が表示されなくなりました。
このあたりは、PHPのバージョンが違うと注意は表示されないのかもしれません。
このライブラリのようなコードの書き方はあまり馴染めません。
中括弧({})を使ったほうが見やすいと思うのですが。