mPDF の中国語文字化け問題(v8.0.1)

Wake
By Wake2019年7月21日約 11 分で読了
mPDF の中国語文字化け問題(v8.0.1)

mPDF は、PHP で PDF ファイルを生成するためのライブラリです。うちの会社で導入している InvoicePlan も、PDF 出力の部分は mPDF で処理していて、案の定、中国語の文字化け問題にぶつかりました。

mPDF 中国語 文字化け というキーワードで Google を検索すると、出てくる情報の多くはかなりあっさりしていて、だいたいこんな感じです:

$mpdf = new \Mpdf\Mpdf([
  "autoScriptToLang" => true,
  "autoLangToFont" => true,
]);

言われたとおりに試すと、たしかに中国語は出力できます。ただ、フォントが僕の欲しいものではありません。フォント設定のほうは、Google で調べ続けても有用な情報があまり見つからない。そこで思いきって、公式ドキュメントと合わせてソースコードから、中で何が起きているのかを調べてみました。

lang と font-family

mPDF がやっていることは、基本的には HTML コードを解析して、一つの PDF ドキュメントに組み立てて出力することです。出力の過程で、mPDF は HTML の lang と CSS の font-family という 2 つの属性から、どのフォントを使うかを判断します。

ドキュメント Fonts & Languages / lang 6.x によると、lang は OTL のグリフセットの設定に影響し、CSS の lang selector としても使えます。font-family のほうは、HTML ブロックの内容をどのフォントで出力するかを指定します。対応している HTML タグの詳しい一覧は CSS & Stylesheets / Supported CSS を参照してください。

autoScriptToLangautoLangToFont を有効にするというのは、この 2 つの設定を省いて、システムに中身を力ずくで分解させ、どの langfont-family を使うかを判断させることです。具体的には Mpdf.phpmarkScriptToLang() や、Language/LanguageToFont.phpLanguage/ScriptToLang.php あたりのファイルを見てください……本当になかなか力ずくです。ふつう、本文に簡体字が含まれていれば言語は lang="und-Hans" と判定され、繁体字なら lang="und-Hant"、フォントは繁体・簡体を問わず自動で font-family: sun-exta が選ばれます。

Free Adobe CJK Asian fonts

古い情報の中には、中国語をちゃんと表示させるには useAdobeCJK を有効にしろ、と書いてあるものがあります。このオプションは、ある意味で完全に地雷です。useAdobeCJK の意味はこうです。もし PC に Adobe Acrobat か関連ソフトが入っていれば、理屈のうえでは Adobe の CJK Asian フォントも入っているはずです。このオプションを有効にすると、PC に入っているフォントをそのまま使って表示できるので、フォントを PDF に埋め込む必要がなくなり、その分ファイルサイズを効果的に減らせます。

PDF と Adobe Acrobat の普及率を考えれば、この発想は間違っていません。問題は、時代が進んでいることです。useAdobeCJK が使う Adobe 標準の繁体字フォントは MSungStd-Light-Acro —— AddBig5Font() を参照 —— で、これはもう骨董品のフォントです。最近の PC にはほとんど入っておらず、オプションを有効にすると、かえって表示の問題を引き起こします。

公式ドキュメント mPDF Variables / useAdobeCJK によると、useAdobeCJK は 5.0 版から対応しています。GitHub でたどれる最も古いバージョンは 2011 年 9 月 14 日リリースの v5.3.0 ……この機能がどれだけ古いか、想像がつきます。それに、公式は useAdobeCJK のデフォルトを有効と書いていますが、僕がいま使っている mPDF v8.0.1 では、デフォルトはすでに無効になっています。

カスタムフォントに戻る

というわけで、いちばん確実な中国語の表示方法は、mPDF 付属の sun-exta 以外だと、自分でフォントを追加することです。幸い、この作業は mPDF がとても簡単にしてくれていて、手順は次の 3 つだけです:

  1. フォントファイルを配置する
  2. フォントディレクトリを指定する
  3. font-family とフォントファイルをひもづける

以下では、いま人気の 台北黑體(Taipei Sans TC) を例に使います。

フォントファイルを配置する

かんたんに言えば、フォントファイルを mPDF がアクセスできるディレクトリに置くだけです。台北黑體 にはいま Light・Regular・Bold の 3 種類があり、必要なのは Regular と Bold です。ダウンロードしたら、そのまま mpdf/ttfonts/ ディレクトリに置きます。

フォントディレクトリを指定する

カスタムフォントのディレクトリが mpdf/ttfonts/ でない場合(たとえば mPDF が vendor として置かれているだけの場合)は、フォントディレクトリを別途指定する必要があります。公式の例 はこんな感じです:

// デフォルトのフォントディレクトリ
$defaultConfig = (new \Mpdf\Config\ConfigVariables())->getDefaults();
$fontDirs = $defaultConfig["fontDir"];

$mpdf = new \Mpdf\Mpdf([
  "fontDir" => array_merge($fontDirs, [
    __DIR__ . '/custom/font/directory',
  ])
]);

font-family とフォントファイルをひもづける

HTML では、フォントに少なくとも標準・太字・斜体・太字斜体の 4 つのバリエーションがあります。そのため font-family の設定でも、必要に応じてそれぞれのフォントファイルに対応づけます。以下が fontdata の設定です:

// font-family 名
'freesans' => [
  // 標準(Regular)
  'R' => 'FreeSans.ttf',
  // 太字(Bold)
  'B' => 'FreeSansBold.ttf',
  // 斜体(Italic)
  'I' => 'FreeSansOblique.ttf',
  // 太字斜体(Bold Italic)
  'BI' => 'FreeSansBoldOblique.ttf',
],

台北黑體なら、こうなります:

// font-family 名
'taipei' => [
  // 標準(Regular)
  'R' => 'TaipeiSansTCBeta-Regular.ttf',
  // 太字(Bold)
  'B' => 'TaipeiSansTCBeta-Bold.ttf',
],

上の R B I のほかにも、フォント設定には SIP-extensionuseOTL など、たくさんのパラメータがあります。詳しくは Fonts & Languages / Fonts in mPDF v7+ を参照してください。

fontDir と fontdata の設定をまとめる

// デフォルトのフォントディレクトリ
$defaultConfig = (new \Mpdf\Config\ConfigVariables())->getDefaults();
$fontDirs = $defaultConfig["fontDir"];

// カスタムフォントディレクトリを追加
$fontDirs = array_merge($fontDirs, [__DIR__ . '/custom/font/directory']);

// デフォルトのフォント設定
$defaultFontConfig = (new \Mpdf\Config\FontVariables())->getDefaults();
$fontData = $defaultFontConfig["fontdata"];

// フォント(font-family)を追加
$fontData = [
  'taipei' => [
    // 標準(Regular)
    'R' => 'TaipeiSansTCBeta-Regular.ttf',
    // 太字(Bold)
    'B' => 'TaipeiSansTCBeta-Bold.ttf'
  ]
] + $fontData; // 元の $fontData を必ず足すこと。でないと mPDF 内蔵のフォントが消えて使えなくなる

$mpdf = new \Mpdf\Mpdf([
  "fontDir" => $fontDirs,
  "fontdata" => $fontData
]);

テスト用の HTML:

<div style="font-size: 30px;">
  <p style="font-family: taipei;">台北黑體 Regular:覺形創意有限公司</p>
  <p style="font-family: taipei; font-weight: bold;">台北黑體 Bold:覺形創意有限公司</p>
  <p style="font-family: sun-exta;">宋體 Sun-ExtA:覺形創意有限公司</p>
</div>

出力すると、台北黑體の標準と太字、それに内蔵の宋体 sun-exta も、どれも中国語をちゃんと表示できます。だいたい、こんなところです。

カテゴリー:
Wake

Wake

著者について

趣味が高じてフロントエンドとバックエンドの技術を少しかじっているエンジニアです。大好きなプログラミングのほかに、バドミントン、ボードゲーム、読書、料理、ピアノも好きです。今は AI も積極的に使っています。