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 code 解析,並組合輸出成一份 pdf document。在輸出過程中,mPDF 會按 HTML langCSS font-family 兩個屬性判斷要使用哪種字體。

參考文件 Fonts & Languages / lang 6.xlang 會影響 OTL 外字集的設定,或也可以做為 CSS lang selector 使用; font-family 則是指定 HTML block 內容要輸出為哪種字體,所支援的 HTML tag 詳細清單可參考 CSS & Stylesheets / Supported CSS

啟用 autoScriptToLangautoLangToFont 就是省略掉上面兩個設定,讓系統去暴力拆你的內容判斷該用什麼 langfont-family,具體可以看 Mpdf.php 中的 markScriptToLang ()Language/LanguageToFont.phpLanguage/ScriptToLang.php 這幾支檔案…是真的滿暴力的。一般如果內文含有簡中的話,語系會被判定為 lang="und-Hans",繁中的話語系則是 lang="und-Hant,字體不分繁簡會自動選用 font-family: sun-exta

Free Adobe CJK Asian fonts

有些舊的資訊裡,會告訴你讓中文正常顯示要啟用 useAdobeCJK,這選項某方面來說完全是個雷。useAdobeCJK 的意思是,假設你的電腦裡有安裝 Adobe Acrobat 或任何相關套件,理論上也該裝有 Adobe 的 CJK Asian fonts;啟用這個選項,可以直接透過電腦內已安裝的字體來顯示,就不需要另外把字體塞進 PDF,進而有效減少檔案大小。

按 PDF 和 Adobe Acrobat 的普及率來說這個想法是沒錯,問題是時代在前進,useAdobeCJK 所採用的 Adobe 標準繁中字體是 MSungStd-Light-Acro — 見 AddBig5Font () ,這已經是老古董的字體了,現在的較新的電腦內很少有安裝,啟用選項後反而會造成顯示上的問題。

按官方文件 mPDF Variables / useAdobeCJK 所載, useAdobeCJK 這個選項是從 5.0 版開始支援;我們在 Github 上可以查到的最小版本號是 2011/09/14 發佈的 v5.3.0…可想而知這個功能有多古老。此外雖然官方標注 useAdobeCJK 預設是啟用,但我目前的 mPDF v8.0.1 預設已經是停用了。

回到自訂字體

所以最穩妥的中文顯示方式,除了 mPDF 內附的 sun-exta 外,就是自己增加字體了,幸好這個需求已經被 mPDF 做到非常簡單,只有以下三個步驟:

  1. 佈署字體檔案

  2. 指定字體檔案目錄

  3. 連結 font-family 和字體檔

以下我們會用目前最潮的 台北黑體 來做範例。

佈署字體檔案

簡單來說就是把字體檔放到你 mPDF 可存取的目錄下。台北黑體 目前共有 Light、Regular、Bold 三種,我們需要用到 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 當中,字體至少會有標準、粗體、斜體、粗斜體這四種變化,所以 font-family 設定上,也要按需求對應到各種不同的字體檔,下面是 fontdata 的設定:

// font-family 名稱
'freesans' => [
 // Regular 標準字體
 'R' => 'FreeSans.ttf',
 // Bold 粗體
 'B' => 'FreeSansBold.ttf',
 // Italic 斜體
 'I' => 'FreeSansBoldOblique.ttf',
 // Bold Italic 粗斜體
 'BI' => 'FreeSansOblique.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>

輸出結果:

大致上就是這樣。

留言

這個網誌中的熱門文章