mPDF 的中文亂碼問題詳解(v8.0.1)

Wake Liu
Wake Liu
Jul 21 · 8 min read

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 中的 markScriptToLangLanguage / 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 設定上也要按需求對應到各種不同的字體檔,以下是 mPDF 附帶字體 freesans 的 fontdata 設定:

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

以台北黑體來說,因為沒有斜體和粗斜體,所以 fontdata 設定是:

// 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 設定

將上面 2、3 步驟整理起來:

// 預設字體目錄
$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
]);

然後要輸出成 PDF 的 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>

輸出結果:

噹噹~(鞠躬音效),可以用自訂的字體來正常顯示中文了。

興趣使然的程式猿

紀錄各種因愛而行的人生道路

Wake Liu

Written by

Wake Liu

興趣使然的程式猿

興趣使然的程式猿

紀錄各種因愛而行的人生道路

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade