1

.NET 小技巧 - 使用 PdfSharp / PdfSharpCore 合併 PDF、加浮水印

 1 month ago
source link: https://blog.darkthread.net/blog/pdfsharp/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

PdfSharpCore 合併 PDF、加浮水印-黑暗執行緒

關於 .NET 用的開源 PDF 程式庫,先前介紹過 PdfPig,最近在看微軟 RAG 範例程式發現另一個程式庫選擇 - PdfSharpCore

PdfSharpCore 是老牌開源專案 PDFsharp 及 MigraDoc Foundation 的 .NET 6+ 「跨平台」移植版,網路上較少聽到人討論,網路資源也偏少,但 NuGet 的下載統計也累積近 1170 萬。PdfSharpCore 使用 SixLabors.ImageSharp 及 SixLabors.Fonts 解決 GDI+ 繪圖程式庫為 Windows 專屬的問題,實現跨平台使用(延伸閱讀:.NET 6 圖形處理跨平台注意事項,但這兩個資料庫))。但要留意,由於 SixLabors 程式庫不是採用 MIT 授權,依 Six Labors Split License,年營收超過 100 萬美金的企業直接引用(Direct Package Dependency)需付費,透過 PdfSharpCore 引用 SixLabors 程式庫的做法我認為屬於 Transitive Dependecy (any Work in Object form that is installed indirectly by a third party dependency unrelated to Six Labors),但稽查人員或管理權責單位是否願了解授權差異,會不會使出"我不要聽我不要聽"大絕就看大家的運氣了,願源力與你同在。

但研究後發現,PDFsharp 也仍在持續發展,目前最新版為 PDFsharp & MigraDoc 6,可在 Windows、Linux 及任何 .NET 相容平台上使用,它共提供三種實作:跨平台版、Windows GDI+ 及 Windows WPF 版,跨平台版看起來是自行實繪圖功能,在效能、支援度及可靠性估計不如 GDI+ 或 SixLabors 專業,但如果應用場景限 Windows 平台,使用 GDI+/WPF 版或是只支援 .NET Framework 用的 1.5 版,是更好的選擇。

因此,這篇文章的範例我會用 PdfSharpCore 示範,但它也適用於 PDFsharp & MigraDoc 6、PDFsharp 1.5,中間可能有些小差異,但大致精神相同。在 API 文件方向,新版網站仍在施工中,部分文件尚未完備,缺少部分可參考1.5 舊版 Wiki

在微軟的 RAG 範例中,只用 PdfSharpCore 將 PDF 分頁,一頁存成一個 PDF,但 PdfSharpCore 也可處理建立文件、合併文件、加浮水印等常見 PDF 基本操作,以下是簡單示範,共四組測試:用繪圖 API 產生 PDF、用文件模型產生 PDF、將兩個 PDF 合併、。

using System.Diagnostics;
using PdfSharpCore.Drawing;
using PdfSharpCore.Pdf;
using MigraDocCore.DocumentObjectModel;
using PdfSharpCore.Pdf.IO;

// 測試一,使用 PdfSharpCore 產生 PDF (畫布繪圖)
// REF: https://github.com/empira/PDFsharp.Samples/tree/master
var cover = new PdfDocument();
cover.Info.Title = "Created with PDFsharp";
cover.Info.Subject = "Just a simple Hello-World program.";

var page = cover.AddPage();
// 用繪圖方式製作 PDF (另有 MigraDoc 提供文件模型)
var gfx = XGraphics.FromPdfPage(page);
var width = page.Width;
var height = page.Height;

// 背景填色
gfx.DrawRectangle(XBrushes.SteelBlue, 0, 0, width, height);
// 繪製矩形,灰框白底
double margin = 80;
XPen grayPen = new XPen(XColors.Gray, 4);
gfx.DrawRectangle(grayPen, XBrushes.White, margin, margin, width - 2 * margin, 150);
// 繪製文字
var font = new XFont("Tahoma", 32, XFontStyle.Bold);
gfx.DrawString("THIS IS A BOOK.", font, XBrushes.DarkGray,
    new XRect(margin, margin, width - 2 * margin, 150), XStringFormats.Center);
var coverFilePath = Path.GetTempFileName() + ".pdf";
cover.Save(coverFilePath);
Process.Start(new ProcessStartInfo(coverFilePath) { UseShellExecute = true });

// 測試二,使用 MigraDocCore 產生 PDF (文件模型)
//REF: https://github.com/empira/PDFsharp.Samples/blob/master/src/samples/src/MigraDoc/src/HelloMigraDoc/Styles.cs
var document = new MigraDocCore.DocumentObjectModel.Document()
{
    Info = {
        Title = "Created with MigraDoc",
        Subject = "Hello, World!",
        Author = "Jeffrey Lee"
    }
};
var style = document.Styles["Normal"] ?? throw new InvalidOperationException("Style Normal not found.");
style.Font.Name = "Segoe UI";
style = document.Styles["Heading1"];
style.Font.Size = 16;
style.Font.Bold = true;
style.Font.Color = Colors.DarkBlue;
style.ParagraphFormat.PageBreakBefore = true;
style.ParagraphFormat.SpaceAfter = 6;
style.ParagraphFormat.Alignment = ParagraphAlignment.Center;
style.ParagraphFormat.KeepWithNext = true;

var sec = document.AddSection();
var para = sec.AddParagraph();
para.AddFormattedText("Header Test", "Heading1");
para = sec.AddParagraph();
para.AddLineBreak();
para.AddText("Hello, World!");

var pdfRenderer = new MigraDocCore.Rendering.PdfDocumentRenderer()
{
    Document = document,
    PdfDocument = new PdfDocument()
};
pdfRenderer.RenderDocument();
var pdfFilePath = Path.GetTempFileName() + ".pdf";
pdfRenderer.PdfDocument.Save(pdfFilePath);
Process.Start(new ProcessStartInfo(pdfFilePath) { UseShellExecute = true });

// 測試三,合併兩份 PDF
var pdf1 = PdfReader.Open(coverFilePath, PdfDocumentOpenMode.Import);
var pdf2 = PdfReader.Open(pdfFilePath, PdfDocumentOpenMode.Import);
var pdf3 = new PdfDocument();
for (int i = 0; i < pdf1.PageCount; i++)
{
    pdf3.AddPage(pdf1.Pages[i]);
}
for (int i = 0; i < pdf2.PageCount; i++)
{
    pdf3.AddPage(pdf2.Pages[i]);
}
var mergedFilePath = Path.GetTempFileName() + ".pdf";
pdf3.Save(mergedFilePath);
Process.Start(new ProcessStartInfo(mergedFilePath) { UseShellExecute = true });

// 測試四,為 PDF 加上浮水印
// https://www.pdfsharp.net/wiki/Watermark-sample.ashx
var mergedPdf = PdfReader.Open(mergedFilePath, PdfDocumentOpenMode.Modify);
string wartermarkText = "TOP SECRET";
var formatWM = new XStringFormat()
{
    Alignment = XStringAlignment.Near,
    LineAlignment = XLineAlignment.Near
};
var fontWM = new XFont("Tahoma", 48, XFontStyle.Bold);
XBrush brushWM = new XSolidBrush(XColor.FromArgb(96, 255, 0, 0));

foreach (var pg in mergedPdf.Pages)
{
    var gfxWM = XGraphics.FromPdfPage(pg, XGraphicsPdfPageOptions.Append);
    var size = gfxWM.MeasureString(wartermarkText, fontWM);
    gfxWM.TranslateTransform(pg.Width.Point / 2, pg.Height.Point / 2);
    gfxWM.RotateTransform(-Math.Atan(pg.Height / pg.Width) * 180 / Math.PI);
    gfxWM.TranslateTransform(-pg.Width.Point / 2, -pg.Height.Point / 2);
    gfxWM.DrawString(wartermarkText, fontWM, brushWM,
        new XPoint((pg.Width.Point - size.Width) / 2, (pg.Height.Point - size.Height) / 2),
        formatWM);
}
var watermarkedFilePath = Path.GetTempFileName() + ".pdf";
mergedPdf.Save(watermarkedFilePath);
Process.Start(new ProcessStartInfo(watermarkedFilePath) { UseShellExecute = true }); 

專案需參照 PdfSharpCoreMigraDocCore.DocumentObjectModelMigraDocCore.Rendering三個程式庫,實測成功:

Fig1_638468934337934821.png

另外,順便也實測 .NET Framework 3.5 + PDFsharp 1.50:

Fig2_638468934341044444.png

程式碼搬進 .NET 3.5 專案,將 Namespace 從 PdfSharpCore 改成 PdfSharp、MigraDocCore 改成 MigraDoc,可得到相同結果:

Fig3_638468934344394770.png

結論:.NET Framework 要做 PDF 合併及浮水印,可考慮使用 PDFsharp 1.50;要配合 .NET 6+,可考慮 PdfSharp 6.0 GDI+ 或 WPF 版,若需跨平台則可使用 PdfSharp 6.0 跨平台版或 PdfSharpCore。PdfSharpCore 有引用非 MIT 授權的 SixLabors 圖形程式庫,雖屬 Transitive Depdency 理論可免費商用,但實務上要以稽查單位心證為準,較麻煩些。

.NET 程式工具箱再添好用工具一枚。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK