讀者 GregYu 問到 PdfSharp 的中文支援問題

直覺認為 PdfSharp 身為資深元件,使用者眾,字型支援應該很成熟,不會遇到阻礙才對。但實際一試,馬上學到一些魔鬼細節。

用以下範例重現問題:

using System.Diagnostics;
using MigraDoc.DocumentObjectModel;
using MigraDoc.Rendering;
using PdfSharp.Pdf;

var document = new Document();
var normalStyle = document.Styles.Normal;
normalStyle.Font.Size = 14;
normalStyle.Font.Bold = false;

var kaiStyle = document.Styles.AddStyle("Kai", "Normal");
kaiStyle.Font.Name = "標楷體";

var mingStyle = document.Styles.AddStyle("Ming", "Normal");
mingStyle.Font.Name = "新細明體";

var section = document.AddSection();
var p = section.AddParagraph();
p.Add(new Text("Normal: 中文測試 Hello, World!"));
p.AddLineBreak();

p = section.AddParagraph();
p.Style = "Kai";
p.Add(new Text("標楷: 中文測試 Hello, World!"));
p.AddLineBreak();

/*
p = section.AddParagraph();
p.Style = "Ming";
p.Add(new Text("新細明: 中文測試 Hello, World!"));
p.AddLineBreak();
*/

var pdfRenderer = new PdfDocumentRenderer
{
    Document = document,
    PdfDocument = new PdfDocument()
};
pdfRenderer.RenderDocument();

var filename = Path.GetTempFileName() + ".pdf";
pdfRenderer.PdfDocument.Save(filename);
Process.Start(new ProcessStartInfo(filename) { UseShellExecute = true });

我準備用三種樣式顯示含中文的字串,字體分別為預設字體、標楷體、新細明體(新細明體部分先註解掉)。結果如下:

預設字型中文變方格無法顯示,標楷體倒是正常。接著取消註解,加入新細明體段落,程式便會出現 Font has no usable platform or encoding ID. It cannot be used with PDFsharp. at PdfSharp.Fonts.OpenType.CMapTable.Read() 錯誤:

查了一下,這是個已知問題。微軟 Windows 字型檔會分成 TTF 跟 TTC 兩種格式(參考:【三個常見字型】TTF. / OTF. / TTC. 副檔名的差異),TTC 為 True Type Collection,可將同一套字型的 Light/Regular/Bold 字體集合成一個文件,從而共同字型輪廓節省大量空間。當代字體諸如:新細明體(mingliu.ttc)、微軟正黑體(msjh.ttc)、微軟雅黑體(msyh.ttc)... 幾乎都是採用 TTC 格式,上古字型標楷體 (Kai.ttf)及新宋體-ExtB(simsunb.ttf) 是碩果僅存的 TTF 格式。而 PdfSharp 只支援 TTF 格式,登楞!

一個無腦解法是設法找 TTF 格式的中文字型檔,這裡以 翰字鑄造 JT Foundry 台北黑體為例,安裝字型後,用 var fontName = System.Drawing.FontFamily.Families.FirstOrDefault(f => f.Name.Contains("Taipei"))?.Name; 查得台北黑體對應的系統名稱為 "Taipei Sans TC Beta",指定正確字體名稱即可成功顯示:

var tpeStyle = document.Styles.AddStyle("TPE", "Normal");
tpeStyle.Font.Name = "Taipei Sans TC Beta";

//...

p = section.AddParagraph();
p.Style = "TPE";
p.Add(new Text("台北黑體: 中文測試 Hello, World!"));
p.AddLineBreak();

小測了一下,PdfSharp 產生包含自訂中文字型的 PDF,在 Linux 上檢視也是 OK 的。

除了找 TTF 將就 PdfSharp,另個思路是修改 PdfSharp 使其支援 TTC,網路上可找到網友分享的修改方式,但工程較大,這篇先留個筆記,待日後有實際需求再研究。

Research and Workaround for Chinese Font Support Issue in PdfSharp.


Comments

# by 打工仔

其實這跟telerik reporting在.net core列印時無法呈現中文字一樣問題,我是在base image裝上字型才正常....

# by GregYu

老實說, 我是執行了官方 Github 的範例程式發生異常, 才跑來詢問是否有碰過無法顯示中文的情況, 結果,您輸出中文的方式跟 Guthub 上的不同, 這下又多了很多東西需要研究了...

# by Jeffrey

to 打工仔, GregYu, 上回聽一位 PDF 開發經驗豐富的前輩分享,不管用哪一套 PDF 程式庫,中文字型都是需要注意的問題。

# by GregYu

怪了,用 Visual Studio 2022 跑這篇文章中的的範例程式,會出現 Exception,跟我使用 DrawString 有相同的結果,問題是,整段程式碼中都沒有 "Arial" 這個關鍵字,難道是我 NuGet 用了錯誤的 Package ?? No appropriate font found for family name "Arial". Implement IFontResolver and assign to "GlobalFontSettings.FontResolver" to use fonts.

# by Jeffrey

to GregYu, 試試 PDFsharp-GDI。

# by GregYu

算初步找到問題點,後續還有待深入研究, 主因大概是我對開發工具設定不熟悉導致, 說出來讓大家笑一笑 參考您兩篇文章 A. .NET 小技巧 - 使用 PdfSharp / PdfSharpCore 合併 PDF、加浮水印 B. PDFSharp 中文字體問題研究 由於兩篇文章有相關,原本我假設開發環境的設定相同, 仔細比對 using 中的項目 (MigraDoc / MigraDocCore), 隱約感覺開發環境的設定似乎略有出入, 推測 A 使用 PdfSharpCore、MigraDocCore, B 則對應到 PdfSharp、MigraDoc, 大概是因為我發問時用的是 PdfSharp 這個關鍵字吧。 Visual Studio 2022 建立新專案時, 如果篩選條件訂為 [C#]、[所有平台]、[主控台], 會有 1. [主控台應用程式] 2. [主控台應用程式 (.NET Framework)] 3. [Standalone Code Anysis Tool] 大約三個月前,因為電腦硬體升級重灌的 Visual Studio 2022, 即使在 [個別元件] 中勾選了 [.NET Framework 3.5開發工具], 我也沒辦法透過 2 建立 .Net 3.5 的專案,最低版本只有 4.6 可以選 再來,在 NuGet 套件管理員中找不到 PDFsharp-GDI Ver. 1.5, 只有 Ver 6.0,試圖安裝會得到錯誤訊息 --- 該封裝不包含任何與架構相容的組件參考或內容檔 到此,暫且判定原廠不支援舊版,先不管這條路。 利用 1 建立的專案,搭配新版本的 PDFsharp 應該就沒問題了吧!? 專案建立後,在 NuGet 套件管理員中搜尋 [PDFsharp] 會得到兩個類似的結果 a. PDFsharp (This package does not depend on Windows and can be used on any .NET compatible platform including Linux and macOS.) b. PDFsharp-GDI (This package relies on Windows Forms (GDI+) and can be used under Windows only.) 想說,.NET 6 就是強調 [跨平台],直覺地就選 a 來用, 沒想到,B 文章中的程式執行後會出現下面這個錯誤 --- No appropriate font found for family name "Arial". Implement IFontResolver and assign to "GlobalFontSettings.FontResolver" to use fonts. 整組程式中根本沒有 Arial 這個關鍵字,還能出錯, 透過 Google 搜尋類似狀況,參考 stackoverflow 解答做調整, 增加一個 IFontResolver 的 Class https://stackoverflow.com/questions/48679265/loading-a-font-with-pdfsharp-net-standard-preview-from-xamarin-forms-fails-no 如果完全複製該解答,預設會在執行檔所在路徑尋找 Fonts 資料夾, 等於是要求開發人員把會用到的所有 [字型檔] 綁在這支程式中, 合理,但不實用,因此調整路徑,改吃 Windows 系統字型檔, 沒想到卻出現了存取權限問題 --- Access to the path 'C:\Windows\Fonts\arial.ttf' is denied. 不死心,開啟 [系統管理員] 模式的 [命令提示字元], 手動執行 exe 執行檔,總該不會有權限問題了吧?? 最差的狀況,就是啟動步驟稍微多了些 (捷徑?), 甚至,黑大 您有篇文章介紹程式自我提升權限, 沒想到,這麼做還是得面臨 Access denied 的錯誤, 除了 [使用者權限],能想到的另一個可能是 [File Lock], 把 [IFontResolver] 中的 [File.Open] 改為 [File.OpenRead], 終於成功地跑出正確結果。 最後,因為好奇 a、b 到底差在哪, 使用 1 再創一個新專案, 這次改為引用 b 套件, 黑大 文章中的程式碼, 完全不需要調整就可以正常執行

# by Jeffrey

to GregYu,GDI 是 Windows 專屬 API,故 GDI 版可直接引用 Windows 安裝字型,若選擇跨平台的 PdfSharp (無 GDI/WPF),字型部分得自行實作 IFontResolver 搞定。文章的範例程式是 .NET 8 Console Appliation,TargetFramework 選 net8.0-windows,參照 PdfSharp-MigraDoc-gdi 6.0.0 可直接執行。

# by GregYu

該怎麼說呢,大概就是太過 [想當然耳] 吧~ 基本上我是知道 GDI 屬於 Windows 平台的技術, 只是,看到某項技術強調自己有能力 [跨平台], 就直覺地以為該工具已針對不同平台有自己一套對應方式, 例如,當初 Java 針對不同平台有自己一套 GUI 元件, 沒想到 PdfSharp 不是這邏輯,最終被 [字型檔] 搞翻天。 我個人的想法是: 不同平台的系統字型檔儲存路徑應該是 [已知的], 套件本身也許應該要先做基本的判斷或處理, 使用者自行定義的才透過 IFontResolver 處理 不曉得算不算我自己的 [一廂情願]

# by Wade

最近剛好在研究這套件做浮水印,發現中文浮水印顯示時候,雖然我已經有設定旋轉角度,但是多頁PDF很容易出現文字顛倒狀態...

Post a comment