PDFsharp 6.2 中文操作練習
試著用 PDFsharp 開啟現有 PDF 檔用憑證簽署。
引言
生成 PDF 的方法有幾種。花錢的簡單多了。不花錢的就有點麻煩。
比較幾種方法,個人選擇 HtmlToPDF 方式,最主要的原因是不想再學一套定位部局技術。反正都做一樣的事。且 HtmlToPDF 產生的 PDF 檔也能滿足。最大的缺憾就是 PDF 簽署能力無法滿足。
經研究發現 PDFsharp 有支援 PDF 簽署就決定試看看。
另一個套件 PDFSharpCore 就多篇評論沒有 PDFsharp 那些毛。但 PDFSharpCore 相依 SixLabors 的套件。此 SixLabors 的套件開源但非免費,少量用不收錢大量用收費很高,這太危險了。
主要問題 - 版本
號稱支援多種版本。 PDFsharp 與平台相依版本 PDFsharp-GDI。
PDFsharp: 跨平台版本。字型只支援最基本的當然不包括中文字型。必需客製化把中文字型額外加入。
PDFsharp-GDI: 平台相依 windows 可在 .NET Framework 4.6.2 執行。就文件說明字型問題應該有解。但試用時又出現其他奇怪的問題。最後決定不採用。
主要問題 - 中文字型
中文字型在應用上除了『標楷體』外『微軟正黑體』也變成了基本中文字體之一了。這兩種字體 PDFsharp 都不支援。(囧)
開發環境
IDE: Visual Studio 2022 平台: NET8 骨架: Console App 套件: PDFsharp v6.2
關鍵程式碼紀錄 - 支援中文字型
第一步:自網路尋找中文字型檔下載,必需是 ".ttf" 格式的字型。PDFsharp 不支援 ".ttc" 的字型。放入專案後如圖。
第二步:自訂字型解析器
using PdfSharp.Fonts;
using System.Drawing.Text;
using System.Runtime.CompilerServices;
namespace PDFSharpLab_HelloWorld2;
public class CustomFontResolver : IFontResolver
{
//DirectoryInfo fontsFolder = new DirectoryInfo(Environment.GetFolderPath(Environment.SpecialFolder.Fonts)); // 取得字型資料夾路徑
readonly DirectoryInfo _fontsFolder = new DirectoryInfo(@"assets\pdfsharp-6.x\fonts"); // 取得字型資源路徑
public byte[]? GetFont(string faceName)
{
// Note: PDFsharp never calls GetFont twice with the same face name.
// Note: If a typeface is resolved by the PlatformFontResolver.ResolveTypeface
// you never come here.
string fontFileName = faceName;
//※ PDFsharp 只支援 TrueType 字型(.ttf)。
foreach (var fontFolder in _fontsFolder.GetDirectories())
{
foreach (FileInfo file in fontFolder.GetFiles("*.ttf"))
{
if(file.Name == fontFileName)
{
return File.ReadAllBytes(file.FullName);
}
}
}
throw new FileNotFoundException($"字型檔案 {fontFileName} 未找到!");
}
public FontResolverInfo? ResolveTypeface(string familyName, bool bold, bool italic)
{
//※ PDFsharp 只支援 TrueType 字型(.ttf)。
foreach (var fontFolder in _fontsFolder.GetDirectories())
{
foreach (FileInfo file in fontFolder.GetFiles("*.ttf"))
{
var pfc = new PrivateFontCollection();
pfc.AddFontFile(file.FullName);
if (pfc.Families.Length > 0)
{
foreach (var family in pfc.Families)
{
if (family.Name == familyName)
{
return new FontResolverInfo(file.Name, bold, italic); // 此處 faceName 等於字型檔案名稱。
}
}
}
}
}
// Alternatively forward call to PlatformFontResolver.
return PlatformFontResolver.ResolveTypeface(familyName, bold, italic);
//// 回傳 null 表示不支援該字型。
//return null;
}
}
第三步:應用
// 註冊自訂字型解析器
GlobalFontSettings.FontResolver = new CustomFontResolver();
GlobalFontSettings.UseWindowsFontsUnderWindows = true;
// 使用註冊過的微軟正黑體字型
XFont font = new XFont("微軟正黑體", 20);
// 建立 PDF 文件
using PdfDocument document = new PdfDocument();
document.Info.Title = "PDFsharp 中文練習";
document.Info.Subject = "PDFsharp 中文練習主旨";
PdfPage page = document.AddPage();
XGraphics gfx = XGraphics.FromPdfPage(page);
XFont fontJhengHei = new XFont("微軟正黑體", 36);
gfx.DrawString($"這是微軟正黑體 36", fontJhengHei, XBrushes.Black, 10, 60);
XFont fontKaiu = new XFont("標楷體", 36);
gfx.DrawString($"這是標楷體 36", fontKaiu, XBrushes.Black, 10, 100);
XFont fontArial = new XFont("Arial", 36);
gfx.DrawString($"This is Arial font 36", fontArial, XBrushes.Black, 10, 140);
關鍵程式碼紀錄 - 用憑證簽署
簽署呈現程式碼簽章圖與簽名文字
using PdfSharp.Drawing;
using PdfSharp.Drawing.Layout;
using PdfSharp.Pdf.Annotations;
namespace PDFSharpLab_HelloWorld2;
class SignatureAppearanceHandler : IAnnotationAppearanceHandler
{
private string _signatureText = "簽名";
public SignatureAppearanceHandler(string signatureText)
{
_signatureText = signatureText;
}
public void DrawAppearance(XGraphics gfx, XRect rect)
{
DirectoryInfo imageFolder = new DirectoryInfo(@"assets/pdfsharp-6.x/signatures"); // 取得字型資源路徑
var image = XImage.FromFile(Path.Combine(imageFolder.FullName, "JohnDoe.png"));
//string text = "John Doe\nSeattle, " + DateTime.Now.ToString(CultureInfo.GetCultureInfo("EN-US"));
string text = $"{_signatureText}, {DateTime.Now:F}";
//var font = new XFont("Verdana", 7.0, XFontStyleEx.Regular);
var font = new XFont("標楷體", 8.0, XFontStyleEx.Regular);
var textFormatter = new XTextFormatter(gfx);
double num = (double)image.PixelWidth / image.PixelHeight;
double signatureHeight = rect.Height * .4;
var point = new XPoint(rect.Width / 10, rect.Height / 10);
// Draw image.
gfx.DrawImage(image, point.X, point.Y, signatureHeight * num, signatureHeight);
// Adjust position for text. We draw it below image.
point = new XPoint(point.X, rect.Height / 2d);
//textFormatter.DrawString(text, font, new XSolidBrush(XColor.FromKnownColor(XKnownColor.Black)), new XRect(point.X, point.Y, rect.Width, rect.Height - point.Y), XStringFormats.TopLeft);
textFormatter.DrawString(text, font, XBrushes.Black, new XRect(point.X, point.Y, rect.Width, rect.Height - point.Y), XStringFormats.TopLeft);
}
}
應用
//§ 開啟現有的 PDF 文件並嘗試簽章
using PdfDocument document2 = PdfReader.Open(filename, PdfDocumentOpenMode.Modify);
int lastPageIndex = document2.Pages.Count - 1;
PdfPage lastPage = document2.Pages[lastPageIndex];
XGraphics lastGfx = XGraphics.FromPdfPage(lastPage);
//## 試著簽章
var signPosition = lastGfx.Transformer.WorldToDefaultPage(new XPoint(144, 600));
var pdfSignatureHandler = DigitalSignatureHandler.ForDocument(document2,
new PdfSharpDefaultSigner(GetCertificate(), PdfMessageDigestType.SHA256),
new DigitalSignatureOptions
{
ContactInfo = "Sky-Walker Kao",
Location = "新北市",
Reason = "測試 PDF 簽章",
Rectangle = new XRect(signPosition.X, signPosition.Y, 200, 50), // 簽章位置
AppearanceHandler = new SignatureAppearanceHandler("高天賜2\n新北市"),
PageIndex = lastPageIndex
});
string filename2 = "output2.pdf";
document2.Save(filename2);
document2.Close();
Console.WriteLine($"簽章 PDF 已儲存為 {filename2}");
/// 取簽署憑證
static X509Certificate2 GetCertificate()
{
DirectoryInfo certFolder = new DirectoryInfo(@"assets/pdfsharp-6.x/signatures"); // 取得字型資源路徑
var pfxFile = Path.Combine(certFolder.FullName , "test-cert_rsa_1024.pfx");
var rawData = File.ReadAllBytes(pfxFile);
// This code is for demonstration only. Do not use password literals for real certificates in source code.
var certificatePassword = "Seecrit1243";
var certificate = new X509Certificate2(rawData,
certificatePassword,
X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable);
return certificate;
}
完整原始碼
參考文件
PDFsharp 的範例文章直接讀程式碼
(EOF)
Last updated