該頁面可以包含自動翻譯的文字。
如何在 C# 和 VB.NET 中建立 PDF 文件
本文說明如何在 .NET 中借助 Docotic.Pdf 函式庫建立 PDF 文件的不同方式。這是一個高效能、純 C# 的 .NET 函式庫,無需外部相依性,可用於建立、編輯、轉換與處理 PDF 文件。

在以下章節中,我會介紹使用 Docotic.Pdf 建立 PDF 的主要方法:
- 使用 Core API,提供對文字、圖形與 PDF 內部結構的低階控制。這個選項最適合自訂版面、圖形密集文件,以及進階功能。
- 使用高階 Layout API,支援段落、表格、頁首、頁尾與自動分頁。當你希望建立結構化文件而不需手動計算位置時,這個 API 非常理想。
- 將 HTML 轉換為 PDF,並支援 SVG 與其他網頁格式。當你的方案已經產生 HTML 文件,且需要這些 HTML 和 CSS 檔案的 PDF 版本時,這種方式特別有用。
- 從影像建立 PDF。這種方法適用於掃描文件、以影像為基礎的報表、收據,以及任何從點陣圖開始的工作流程。
- 合併或分割 PDF。這是彙整報表、處理使用者上傳、合併相關文件,以及重組大型 PDF 的好選擇。
- 從範本建立 PDF。當批次產生的文件需要一致格式時,這種方式很適合,例如收據、稅表、聘僱合約與其他可重複使用的文件類型。
本指南涵蓋的其他主題包括:
使用 Core API 建立 PDF
Core API 是 Docotic.Pdf 中 PDF 建立的基礎。它讓你能透過其 Canvas API,在 PDF 畫布上完整、低階地控制文字、影像與向量圖形的放置。這個繪圖 API 是 Core API 的子集,提供用來在頁面與其他具有畫布的物件上新增內容的方法與屬性。除了渲染之外,Core API 也支援註解、表單欄位、圖層、書籤與其他 PDF 功能。
以下是使用三個基本操作建立簡單 PDF 的 C# 程式碼:繪製文字、放置影像,以及在頁面畫布上呈現向量圖形。
using var pdf = new PdfDocument();
var canvas = pdf.Pages[0].Canvas;
canvas.Font = pdf.CreateFont(PdfBuiltInFont.HelveticaBold);
canvas.FontSize = 14;
canvas.DrawString(40, 100, "Core API demo: text, images, and vector graphics");
var image = pdf.CreateImage("image.png");
canvas.DrawImage(image, 40, 180, 120, 120, 0);
canvas.Pen.Color = new PdfRgbColor(30, 60, 160);
canvas.Pen.Width = 2;
canvas.Brush.Color = new PdfRgbColor(200, 230, 255);
canvas.DrawRectangle(new PdfRectangle(200, 200, 150, 80), PdfDrawMode.FillAndStroke);
pdf.Save("core-api-demo.pdf");
這個概覽只介紹 Core API 能做的很小一部分。若要了解進階主題,請參閱使用 Core API 建立 PDF 的詳細文章。該文章涵蓋文字量測、使用色彩空間、套用裁切、使用樣式填滿區域、處理透明度,以及其他功能。
使用 Layout API 產生 PDF
Layout API 是一個高階的文件建構引擎,提供建立複雜、內容豐富 PDF 的最簡單且最有效率的方式。
使用此 API 時,你會以頁面、容器、文字區段、影像、表格、連結、頁首、頁尾等結構元素來組成 PDF。你不需要計算座標或手動管理分頁,而是描述文件結構,讓版面引擎處理其餘部分。
此範例說明如何使用 Layout API 建立 PDF,依賴宣告式版面而非手動定位。
PdfDocumentBuilder.Create()
.Info(info => info.Title = "Docotic.Pdf Layout API demo")
.Generate("layout-api-demo.pdf", doc => doc.Pages(pages =>
{
pages.Content().Padding(100).Text(text =>
{
text.Span("The Layout API lets you compose PDFs from structural elements ");
text.Line("without manually calculating coordinates or handling pagination.")
.Style(s => s.Strong);
});
}));
請查看深入指南,了解如何在.NET 中產生 PDF 應用程式時使用 Layout API。
使用 HTML to PDF API 轉換網頁內容
Docotic.Pdf 搭配其免費的 HtmlToPdf 附加元件,提供一個現代、高品質、以 Chrome 為基礎的 HTML 轉 PDF 引擎。你可以使用該附加元件提供的 API,將現代 HTML 與其他網頁內容,例如 SVG 或 WebP 影像,轉換為高品質的 PDF 文件。
HTML to PDF API 可以從完整 HTML 頁面或 HTML 片段建立 PDF。你可以從 URL、原始 HTML 字串與本機 HTML 檔案轉換內容。後兩種選項讓你更容易從 HTML 範本產生 PDF。
以下範例示範如何從 HTML 範本產生 PDF:
public static async Task HelloHtmlTemplate()
{
static string GetUserName()
{
// 替換為實際邏輯:表單輸入、API 呼叫、設定檔等。
return "World";
}
string html = $@"
<h1>Hello, {GetUserName()}!</h1>
<p>This PDF was generated from an HTML template.</p>";
using var converter = await HtmlConverter.CreateAsync();
using var pdf = await converter.CreatePdfFromStringAsync(html);
pdf.Save("hello-html-template.pdf");
}
如需更多細節與範例,請查看我們的深入HTML 轉 PDF 概覽。
從影像建立 PDF
Docotic.Pdf 提供一種彈性且對開發者友善的方式,將影像轉換為 PDF。該函式庫透過 Core API 支援 JPEG、BMP、GIF、PNG、TIFF 與 JPEG 2000 影像格式。

當 PDF 格式支援時,Docotic.Pdf 會直接嵌入影像位元組,避免像素解碼與重新編碼,以保留原始壓縮。該函式庫也會在可行時保留色彩空間。
此外,透過 HTML to PDF API 也支援 SVG 與 WebP 格式。當你需要在影像旁放置標籤或描述時,Layout API 能以最少的工作量協助你排列與對齊元素。
如何將多個影像合併為一個 PDF
使用 Docotic.Pdf,你可以輕鬆將一組影像轉換為單一 PDF,並在每個頁面上放置一張影像。
以下範例從檔案載入影像,並將每張影像繪製到自己的頁面上。每張影像都會縮放以符合頁面,並置中以形成乾淨、一致的版面。
public static void ImagesOnToPdf(string[] imagePaths, string outputPath)
{
using var pdf = new PdfDocument();
foreach (string path in imagePaths)
{
var image = pdf.CreateImage(path);
var page = pdf.AddPage();
var pageWidth = page.Width;
var pageHeight = page.Height;
var scale = Math.Min(pageWidth / image.Width, pageHeight / image.Height);
var drawWidth = image.Width * scale;
var drawHeight = image.Height * scale;
var x = (pageWidth - drawWidth) / 2;
var y = (pageHeight - drawHeight) / 2;
page.Canvas.DrawImage(image, x, y, drawWidth, drawHeight, 0);
}
pdf.RemovePage(0);
pdf.Save(outputPath);
}
處理多頁 TIFF 與 GIF 影像
Docotic.Pdf 完整支援多頁 TIFF 與 GIF 檔案。當將影像加入 PDF 時,如果任何影像可能包含多個頁面,請使用 OpenImage 方法,而不是 CreateImage。
以下程式碼示範如何將 TIFF 轉為 PDF,且同時適用於單頁與多頁影像:
public static void OddFramesToPdf(string[] imagePaths, string outputPath)
{
using var pdf = new PdfDocument();
foreach (string path in imagePaths)
{
var imageFrames = pdf.OpenImage(path);
for (int i = 0; i < imageFrames.Count; i++)
{
if (i % 2 != 0)
continue;
var image = pdf.CreateImage(imageFrames[i]);
var page = pdf.AddPage();
page.Width = image.Width;
page.Height = image.Height;
page.Canvas.DrawImage(image, 0, 0, image.Width, image.Height, 0);
}
}
pdf.RemovePage(0);
pdf.Save(outputPath);
}
你也可以用相同方法將 GIF 轉為 PDF。這同樣適用於其他影像格式,不過對於只有單一影格的格式而言,這樣做會比必要的步驟更複雜。
合併與分割 PDF
作為一個功能完整的 .NET 函式庫,Docotic.Pdf 可以透過合併、擷取與重新整理既有文件中的頁面來建立新的 PDF。
當你合併 PDF 時,該函式庫不只會加入另一份文件的頁面,還會附加圖層、書籤、頁面標籤、共用 JavaScript、目的地(連結目標)與內嵌檔案。如需更多細節與如何縮小合併後 PDF 大小的指引,請參閱合併 PDF 文章。
數位簽章 PDF 不能在不使現有簽章失效的情況下進行合併。若要保留簽章,請改為建立 PDF portfolio,而不是附加文件。另一種做法是先合併 PDF,再對合併後的文件套用新的數位簽章。
Docotic.Pdf 也能讓你將頁面複製並擷取到新文件中。與複製頁面相關的所有內容都會保留,包括註解、表單控制項、結構化內容、圖層與其他相關資料。實務範例請參閱在 .NET 中分割 PDF 文章。該文章也說明如何擷取或移除頁面。
使用 PDF 範本
PDF 範本是預先設計的 PDF 檔案,可作為建立新文件的基礎結構。當你需要在提供不同資料的同時產生具有一致版面的 PDF 時,它們非常有用。如果你希望將視覺設計與資料本身分開,PDF 範本也是不錯的選擇。
範本可以是基於表單的 PDF,也可以是沒有表單的靜態 PDF。這兩種類型的用途相同。此外,基於表單的範本包含互動元素,若不扁平化,便可從使用者收集資訊。
從基於表單的範本建立 PDF
基於表單的範本通常包含 AcroForms,這是標準且廣泛支援的互動式 PDF 表單類型。若要從這類範本產生 PDF,通常需要:
- 填入每個預留欄位
- 將欄位扁平化以防止進一步編輯
- 將結果儲存為新的 PDF
以下是 C# 程式碼,會依名稱尋找預留文字欄位、指派值、將欄位扁平化,並儲存結果,從而從範本建立 PDF:
var nameOnCertificate = "Eva Marin";
using var pdf = new PdfDocument("certificate-template.pdf");
if (pdf.TryGetControl("name", out var field))
{
if (field is PdfTextBox nameField)
{
nameField.Text = nameOnCertificate;
nameField.Flatten();
}
}
pdf.Save($"certificate-{nameOnCertificate}.pdf");
如果你的範本包含許多預留位置,你可以匯入 FDF 資料,而不必逐一填寫每個欄位。你也可以使用 PdfDocument.FlattenControls 一次將所有欄位扁平化。
從沒有表單的靜態範本建立 PDF
如果你的範本不包含表單欄位,你就會直接在頁面畫布上繪製姓名與其他資料。靜態 PDF 範本通常包含固定的視覺預留位置,例如文字、影像或空白區域。若要從範本產生 PDF,你需要填入這些空白區域,並以程式方式取代預留文字與影像。
空白區域
使用 Canvas API 在空白區域放置文字與影像。在簡單情況下,當你只需要例如新增姓名與相片這類小幅變更時,這種做法很有效。你需要知道區域的座標與大小,而且若要正確定位文字,可能需要先量測,再據此對齊。
處理可變長度或多行文字更具挑戰性,但仍可實作。透過結合 DrawText、DrawString 與文字量測方法,你可以依需要換行並放置文字。若你的範本包含超過少數幾個這類區域,可以考慮使用其他方法,例如透過 Layout API 產生 PDF。
預留文字
Docotic.Pdf 也提供尋找與取代文字的方法。不過,將文字搜尋用作範本機制,通常不比處理空白預留區域更簡單。在插入新內容之前,你必須先定位準確的文字片段,並將其乾淨地移除。
預留影像
靜態範本可能包含供使用者頭像或產品照片使用的預留影像。若要找出預留影像,請列舉每個頁面上繪製的影像集合。對於每個繪製的影像,你都可以取得其可見大小與位置。若要取代預留影像,請使用 PdfImage.ReplaceWith。
using var pdf = new PdfDocument("invoice-template.pdf");
var paintedImages = pdf.Pages[0].GetPaintedImages();
var placeholder = paintedImages.First();
placeholder.Image.ReplaceWith("company-logo.jpg");
pdf.Save($"invoice.pdf");
另一種做法是在預留影像所占區域上繪製新影像,但這通常會在沒有充分理由的情況下增加結果 PDF 的大小。
設計易於取代的預留位置
對於靜態範本,最好為文字與影像設計可預測、明確定義的區域版面。為將包含可變長度內容的區域保留足夠內距,並使用與你預期稍後插入的內容長寬比相符的中性預留影像。
如果你的範本使用的是你打算取代的預留文字,可以改用文字方塊來簡化工作流程。將一個唯讀、無邊框的文字方塊加入範本,並把預留文字放在其中。產生最終 PDF 時,開啟範本、依名稱找到該文字方塊,然後直接指定新值,例如 box.Text = "new text";。接著將文字方塊扁平化,以防止進一步編輯。
新增互動元素
互動功能會將靜態 PDF 轉換為動態、易於導覽的文件,並附加註解與標記。動作與 JavaScript 可直接在 PDF 內啟用自動化。
註解
註解是附加到頁面的物件,用來表示評論、反白、檔案附件與其他互動式小工具。它們會顯示在頁面內容中,並支援審閱流程與協作。
以下 C# 範例示範如何使用 Docotic.Pdf 為 PDF 頁面新增文字註解,也就是便利貼。
using var pdf = new PdfDocument("example.pdf");
var page = pdf.Pages[0];
var textAnnot = page.AddTextAnnotation(new PdfPoint(50, 100), "Reviewer comment");
textAnnot.Contents = "Please check the figures on this page.";
pdf.Save("text-annotation.pdf");
下一個範例示範如何反白文字與其他內容,以吸引注意文件中的重點部分。
using var pdf = new PdfDocument("example.pdf");
var page = pdf.Pages[0];
var color = new PdfRgbColor(255, 255, 120);
var annotationText = "Please confirm this part.";
var bounds = new PdfRectangle(50, 250, 120, 40);
page.AddHighlightAnnotation(annotationText, bounds, color);
pdf.Save("highlight-annotation.pdf");
連結
PDF 標準定義了多種類型的 PDF 連結。最重要且最常用的是內部連結與超連結。
內部連結,也稱為 GoTo 動作,可讓你跳轉至同一份 PDF 內的某個頁面或已命名目的地。它們對交叉參照與內部導覽很有幫助。
以下是建立從第一頁到索引等於 5 的頁面的連結之 C# 程式碼:
using var pdf = new PdfDocument();
var page = pdf.Pages[0];
int targetPageIndex = 5;
for (int i = 0; i < targetPageIndex; i++)
pdf.AddPage();
var rect = new PdfRectangle(50, 50, 100, 40);
page.Canvas.DrawRectangle(rect);
page.AddLinkToPage(rect, targetPageIndex);
pdf.Pages[targetPageIndex].Canvas.DrawString(50, 50, "Glad to have you here.");
pdf.Save("link-to-page.pdf");
Layout API 提供另一種方式來建立不需要絕對定位的內部連結。
外部連結,也稱為 URI 動作,會開啟網頁 URL。你可以使用 PdfPage.AddHyperlink 方法為 PDF 頁面新增超連結。除此之外,方法與內部連結相同。
書籤
書籤,也稱為大綱,是特殊的捷徑或連結,可協助讀者快速導覽到特定章節或頁面。當讀者點擊書籤時,檢視器應用程式會跳到文件中指定的位置。
大綱會出現在檢視器的書籤面板中,並提供類似書籍目錄的階層式導覽樹,但具互動性。PDF 大綱可以包含主要書籤與巢狀書籤,這使大型文件更容易組織。
以下範例示範如何使用 C# 與 Docotic.Pdf 在 PDF 中建立書籤。程式碼建立三個第一層書籤。第二個書籤包含一個巢狀書籤。
using var pdf = new PdfDocument();
for (int i = 0; i < 5; i++)
{
var page = i == 0 ? pdf.Pages[0] : pdf.AddPage();
var canvas = page.Canvas;
canvas.FontSize = 14;
canvas.DrawString(50, 50, $"Page {i + 1}");
}
var root = pdf.OutlineRoot;
root.AddChild("Getting Started", 1);
var child = root.AddChild("Things You Can Do", 2);
child.AddChild("Making Quick Improvements", 3);
root.AddChild("Keeping Everything Running Smoothly", 4);
pdf.PageMode = PdfPageMode.UseOutlines;
pdf.Save("bookmarks.pdf");
書籤不同於你可能在實體書頁面上印出的目錄,或在 PDF 中看到的目錄。你可以透過量測標題並寫入帶有頁碼的項目,以程式方式建立目錄。
若要查看使用 Layout API 建立目錄的替代方法,請參考我們範例儲存庫中的相關程式碼。
PDF 指令碼
JavaScript 動作是最強大的互動功能之一。PDF JavaScript 是 JavaScript 的子集,並公開文件與檢視器 API。它可用於表單驗證、計算、使用者介面對話方塊,以及小型自動化工作。
你可以將指令碼附加到註解、書籤、表單控制項或開啟動作。使用 Docotic.Pdf 時,你可以將 JavaScript 程式碼嵌入 PDF。這些程式碼可驗證表單輸入、計算值、顯示或隱藏欄位,或執行檢視器互動。
共享 JavaScript 的集合包含儲存在文件層級的指令碼。這些指令碼可由多個動作重複使用。換句話說,共享指令碼適合用於工具函式與共用邏輯,可減少重複並簡化維護。
以下程式碼定義一個共享指令碼,會在 PDF 檢視器中顯示警示訊息,接著示範如何將它指派給按鈕的點擊動作來觸發該指令碼。
using var pdf = new PdfDocument();
pdf.SharedScripts.Add(
pdf.CreateJavaScriptAction("function messageBox(message) { app.alert(message,3); }")
);
var button = pdf.Pages[0].AddButton(50, 50, 100, 40);
button.Text = "Click me";
button.OnMouseUp = pdf.CreateJavaScriptAction("messageBox('Hello, dear!');");
pdf.Save("shared-javascript.pdf");
範例中的指令碼很簡單,但你可以建構 JavaScript 動作 到任何複雜度。Adobe JavaScript API 參考提供了許多可用的方法。請記住,非 Adobe 檢視器通常只支援該 API 的子集。
開啟動作
開啟動作是 PDF 檢視器在文件開啟時執行的動作。常見用途包括在特定頁面開啟、執行 JavaScript 初始化程序,或設定檢視器偏好設定。開啟動作的類型沒有任何限制。
以下範例示範如何建立 GoTo 開啟動作。程式碼會在第二頁加入文字,並設定一個開啟動作,讓檢視器在 PDF 開啟時自動導覽到該頁。
using var pdf = new PdfDocument();
var canvas = pdf.AddPage().Canvas;
canvas.FontSize = 14;
var message =
"If you see this immediately after opening the file, " +
"your PDF viewer supports open actions.";
var options = new PdfTextDrawingOptions(new PdfRectangle(100, 100, 100, 150));
canvas.DrawText(message, options);
pdf.OnOpenDocument = pdf.CreateGoToPageAction(1, 0);
pdf.Save("open-action.pdf");
請注意,並非所有檢視器都會執行 JavaScript 開啟動作。有些檢視器會忽略它們,或先提示使用者。有些檢視器會完全封鎖開啟動作。
若要檢查 PDF 是否包含開啟動作,請將它載入 PdfDocument,並檢查 OnOpenDocument 屬性。如果該屬性為 null,表示文件沒有定義開啟動作。
套用加密與數位簽章
加密與數位簽章處理你所建立 PDF 的兩個互補面向。加密控制誰可以開啟文件以及可對其執行哪些操作,而簽章則證明誰建立或核准了檔案,並確認它未被修改。
密碼保護可讓你在建立時設定存取規則。你可以指定開啟密碼以限制檢視,並指定擁有者密碼來定義列印、複製、編輯或填寫表單等權限。憑證加密提供更強、且以收件者為對象的保護,當你要向多人散發機密 PDF,且不想依賴共用密碼時,特別適合。更多細節請參閱使用密碼與憑證加密 PDF 的文章。
數位簽章可在建立時加入真實性與完整性。Docotic.Pdf 可以使用檔案中的憑證、Windows store、硬體權杖、HSM 或雲端金鑰服務來簽署 PDF。你可以加入時間戳記與長期驗證資料,讓簽章在文件產生很久之後仍可驗證。也支援外部簽署工作流程,包括 PKCS#11 與雲端 KMS。
設定 PDF 中繼資料
PDF 中繼資料是嵌入文件中的描述性資訊,例如標題、作者、主題、關鍵字、建立日期與類似欄位。它可協助軟體、搜尋引擎與文件管理系統在不開啟檔案的情況下,了解檔案內容。
PDF 文件可以在兩個共存系統中保存中繼資料:
- XMP 中繼資料
- 文件資訊字典(
Info字典)

XMP 是更豐富、結構化且標準化的描述性中繼資料嵌入格式。Info 字典簡單且廣泛支援,但功能有限,而且在 PDF 2.0 標準(ISO 32000-2)中已被棄用,改以 XMP 中繼資料取代。Docotic.Pdf 可讀寫這兩種系統,並提供輔助方法讓它們保持同步。
在儲存 PDF 檔案之前,Docotic.Pdf 會自動更新某些中繼資料。例如,該函式庫預設會設定 Producer 與 Creator 值。請使用儲存選項來變更此行為,並保留明確設定的中繼資料值。
XMP 中繼資料
使用 PdfDocument.Metadata 屬性可存取與修改 PDF 中的 XMP 中繼資料。透過此屬性,你可以處理 XMP Core、Dublin Core 與 PDF schema 等常見 schema,也可以管理自己的自訂中繼資料。
using var pdf = new PdfDocument();
var xmp = pdf.Metadata;
xmp.Pdf.Creator = new XmpString("Second-line authoring terminal");
xmp.Pdf.Title = new XmpString("Quarterly Report");
var creators = new XmpArray(XmpArrayType.Ordered);
creators.Values.Add(new XmpString("Second-line authoring terminal"));
creators.Values.Add(new XmpString("Assistive authoring terminal"));
xmp.DublinCore.Creators = creators;
var descriptions = new XmpArray(XmpArrayType.Alternative);
descriptions.Values.Add(new XmpLanguageAlternative("x-default", "Quarterly Report"));
descriptions.Values.Add(new XmpLanguageAlternative("fr", "Rapport trimestriel"));
descriptions.Values.Add(new XmpLanguageAlternative("de", "Quartalsbericht"));
xmp.DublinCore.Descriptions = descriptions;
var author1 = new XmpString("First Author");
author1.Qualifiers.Add("role", "main author");
var author2 = new XmpString("Second Author");
author2.Qualifiers.Add("role", "co-author");
var authors = new XmpArray(XmpArrayType.Unordered);
authors.Values.Add(author1);
authors.Values.Add(author2);
xmp.Custom.Properties.Add("authors", authors);
pdf.Save("with-xmp-metadata.pdf");
XMP 支援陣列、結構與型別值,因此很適合豐富的中繼資料。上面的程式碼也示範了如何在自訂 XMP schema 中儲存應用程式專屬屬性。
文件資訊字典
Info 字典主要儲存文字字串值。它緊湊且廣泛支援,但功能有限。若要與較舊的工具相容,請使用 Info 字典;在其他情況下,則優先使用 XMP。
同步中繼資料
最佳做法是讓兩種中繼資料系統保持同步,以避免不一致而混淆讀者與自動化工具。
使用 PdfDocument.SyncMetadata 可對齊 XMP 與 Info 值,使對應欄位一致。此方法會以 XMP 填補缺少的 Info 屬性,反之亦然,亦會用 Info 填補缺少的 XMP 欄位。當 XMP 是你的權威來源時,請設定 preferXmp: true;若應以 Info 字典為準,則設定為 false。
pdf.SyncMetadata(preferXmp: true);
如需詳細資訊,請參閱 SyncMetadata 文件中的 Remarks 區段,了解此方法同步哪些屬性。
設定頁面標籤與檢視器偏好設定
新建立的 PDF 可受益於明確的頁碼編排、精細調整的檢視器偏好設定,以及選定的頁面版面,以更有效地呈現文件內容。這些設定會影響讀者首次看到與導覽檔案的方式。
頁面標籤
頁面標籤是通知 PDF 檢視器要為每頁顯示何種標籤的中繼資料。當可見編號必須與實際頁面索引不同時使用。例如,當你希望 PDF 的前置部分使用 i, ii, iii,而正文使用 1, 2, 3 時。
以下 C# 程式碼示範如何為前 3 頁標示小寫羅馬數字,並為其餘頁面使用從 1 開始的阿拉伯數字。
using var pdf = new PdfDocument();
for (int i = 0; i < 8; i++)
pdf.AddPage();
pdf.PageLabels.AddRange(0, 2, PdfPageNumberingStyle.LowercaseRoman);
pdf.PageLabels.AddRange(3, PdfPageNumberingStyle.DecimalArabic);
pdf.Save("with-page-labels.pdf");
PDF 檢視器偏好設定
PDF 檢視器偏好設定是嵌入文件中的建議,用來提示檢視器應如何呈現文件。例如,你可以指定檢視器應隱藏工具列、將視窗置中,或讓視窗符合頁面。檢視器偏好設定可與頁面版面及開啟動作設定互補。
以下示範如何使用 Docotic.Pdf 變更 PDF 檢視偏好設定:
using var pdf = new PdfDocument();
pdf.ViewerPreferences.DisplayTitle = false;
pdf.ViewerPreferences.FitWindow = true;
pdf.ViewerPreferences.HideToolBar = true;
pdf.ViewerPreferences.HideMenuBar = true;
pdf.ViewerPreferences.HideWindowUI = true;
pdf.ViewerPreferences.CenterWindow = true;
pdf.Save("with-viewer-prefs.pdf");
請注意,依其設定不同,Adobe Acrobat 與其他檢視器可能會忽略這些偏好設定。
頁面版面與頁面模式
頁面版面決定文件開啟時頁面如何排列:一次顯示單頁、單欄連續顯示,或雙頁跨頁。頁面模式控制開啟時可見哪些 UI 面板:書籤/大綱、附件、縮圖,或不顯示任何面板。
以下示範如何指定建立的 PDF 以雙頁跨頁方式顯示,左頁優先,並在開啟時顯示縮圖面板:
using var pdf = new PdfDocument();
for (int i = 0; i < 7; i++)
{
var page = i > 0 ? pdf.AddPage() : pdf.Pages[0];
page.Canvas.FontSize = 36;
page.Canvas.DrawString(100, 100, $"Page {i + 1}");
}
pdf.PageLayout = PdfPageLayout.TwoPageLeft;
pdf.PageMode = PdfPageMode.UseThumbs;
pdf.Save("with-layout-and-mode.pdf");
儲存 PDF
Docotic.Pdf 可以從你建立或編輯的同一份文件產生不同的 PDF 檔案或串流。這些輸出可符合不同版本的 PDF 格式、具有不同的位元組長度,並需要不同的記憶體量來產生。
函式庫產生 PDF 位元組的方式取決於儲存選項。當你未明確指定儲存選項時,PdfDocument 物件的 Save、SignAndSave 與 TimestampAndSave 方法會使用預設設定。這些預設值經過仔細挑選,適用於大多數情境,但你仍可能需要調整。
如需可用選項及其預設值的詳細資訊,請參閱 PdfSaveOptions 類別文件。以下章節會強調其中一些較重要的選項,並提供實務建議。
PDF 版本
Docotic.Pdf 預設使用物件串流,以改善它所產生檔案的壓縮效果。因此,該函式庫預設會建立 PDF 1.5 檔案與串流。
PDF 1.5 需要 Adobe Reader 6(2003 年發行)或更新版本才能檢視產生的文件。這通常不是問題,除非你必須支援舊工具、較舊的檢視器,或只接受較舊 PDF 版本的嵌入式裝置。
以下示範如何以較舊的 PDF 檔案版本儲存:
using var pdf = new PdfDocument();
var options = new PdfSaveOptions
{
Version = PdfVersion.Pdf14,
UseObjectStreams = false,
};
pdf.Save("version-1.4.pdf", options);
若要以 PDF 1.4 版本儲存,也必須停用物件串流。若文件包含後期版本才引入的功能,該函式庫不會使用較舊版本。
檔案大小縮減
若將某些儲存選項設為 true,會使 Docotic.Pdf 產生較小的檔案(以位元組計):RemoveUnusedObjects、OptimizeIndirectObjects、WriteWithoutFormatting 與 UseObjectStreams。
以下示範如何在沒有未參照物件與多餘空白的情況下產生 PDF,並將資料緊密打包到物件串流中:
using var pdf = new PdfDocument();
var options = new PdfSaveOptions
{
UseObjectStreams = true,
RemoveUnusedObjects = true,
OptimizeIndirectObjects = true,
WriteWithoutFormatting = true,
};
pdf.Save("optimized.pdf", options);
當 PDF 被完整重寫時,這些選項效果最佳。在增量儲存期間,它們只會套用到新加入的修訂版本,無法清理或最佳化檔案較早的部分。
增量更新
Docotic.Pdf 可以以增量方式更新 PDF。當 WriteIncrementally 為 true 時,該函式庫會將變更附加到現有檔案,而不是重寫整個檔案。先前的交叉參照與物件資料會保持不變。附加的資料稱為增量更新,而目前更新連同所有先前更新構成檔案的新修訂版本。
新建立的文件不可能使用增量更新,因為沒有可附加的先前修訂版本。該函式庫會忽略新文件的此選項,並以非增量模式寫入。
何時需要增量更新
當為已包含簽章的文件新增數位簽章時,必須以增量方式儲存檔案。當你以新的註解或表單資料更新先前已簽署的檔案時,也同樣適用。在這些情況下若重寫整個檔案,會使既有簽章失效。
同時,在套用第一個數位簽章之前,最好先進行非增量(完整)儲存,如此簽署的基準檔案才會是乾淨且完整重寫的檔案。若對包含較早修訂版本結構問題的文件進行簽署,可能導致意外的簽章驗證問題。
在必須保留可稽核修訂歷史或強制只附加儲存的工作流程中,也需要增量附加。
使用增量更新的好處
增量更新可讓同一檔案擁有多個簽章,並允許有限的簽章後修改,例如填寫表單欄位,而不會使既有簽章失效。
這種做法也能讓小幅變更儲存得更快,因為只會寫入被修改的資料。它同時也保留文件的修訂歷史,這對稽核與其他受法規約束的工作流程非常重要。
應避免的問題與陷阱
增量更新無法對整個檔案套用全域壓縮,或移除整份檔案中的過時物件,因為它只會附加已修改的物件。因此,相較於完整重寫,它們通常會產生更大且最佳化程度較低的檔案。
檔案大小會隨每次修訂而增加,即使沒有未使用的物件也是如此,因為所有先前修訂都會保留在檔案中並持續占用空間。
先前修訂中的敏感或錯誤資訊仍可被還原,而既有的 PDF 格式問題或先前修訂中的結構缺陷,也不會因為附加新資料而被修正。
最後,有些檢視器與處理工具難以處理多修訂 PDF。在依賴增量更新之前,請確保所有使用你文件的系統都能處理含有多個修訂版本的檔案。
測試 PDF 輸出
自動化 PDF 測試可透過將產生的 PDF 與儲存在儲存庫或成品儲存中的基準 PDF 進行比較,保護版本發佈免於內容與版面回歸。基準可幫助你偵測文字、字型、影像或版面的意外變更,並減少每次建置都需要人工 QA 的情況。
結合結構檢查、文字擷取與視覺比較可獲得最可靠的結果。
方法快速比較
| 方法 | 速度 | 敏感度 | 最適合 |
|---|---|---|---|
| 結構比較 | 快 | 高:可偵測物件層級變更 | 必須確認同一文件兩個版本在結構上完全相同的回歸測試 |
| 文字擷取 | 快 | 中:通常會忽略版面變更 | 驗證語意內容與表格 |
| 視覺差異比對 | 較慢 | 高:可偵測內容與渲染/版面變更 | 找出視覺回歸 |
比較文件結構
使用 PdfDocument.DocumentsAreEqual 比較 PDF 物件圖、PDF 版本與文件安全儲存區(DSS),同時忽略與時間相關的文件屬性。此方法也會忽略文件中繼資料、trailer IDs 與其他自動產生的屬性。
這個方法非常適合需要確保沒有新增或移除任何意外物件的 PDF 文件測試工作流程。DocumentsAreEqual 支援檔案與串流多載,且可比較已加密的 PDF。
在 Docotic.Pdf 範例中有一個完整示範此技術的範例。除了示範如何在一般 .NET 應用程式中使用此方法外,該範例也示範如何在Native AOT 中使用 DocumentsAreEqual 應用程式。
透過擷取的文字驗證 PDF
從整份文件一次擷取文字,或逐頁擷取,然後比較字串。你可以使用文字擷取選項來微調擷取流程,例如排除頁尾所在的矩形區域。為了讓比較更容易,你可以將擷取出的文字拆成多行或多個單字。
若要進行結構化檢查,請先擷取每個區塊、單字或字元的文字,並取得位置、字型與其他詳細資訊。接著將每個擷取元素與對應的基準元素比較。
偵測視覺差異
先將PDF 頁面轉為影像,再將每張影像與基準影像比較。使用 ImageSharp.Compare 或 Magick.NET 等專門函式庫偵測影像差異。
建議使用嚴格的逐像素比較,確保兩張影像中對應的每個像素都必須一致。如果你的需求允許少量渲染差異,你可以調整比較邏輯以容忍些微差異,但精確像素相等會提供最可靠的結果。
可考慮使用雜湊作為快速預檢,在不做完整像素比較的情況下判斷兩張影像是否很可能相同。為每張渲染後影像計算 SHA-256 雜湊,如果雜湊相符,影像幾乎可以確定相同;若雜湊不同,則執行完整的逐像素比較。
結論
Docotic.Pdf 提供一套完整、多層次的工具組,可在 .NET 中建立與操作 PDF。開發人員可以選擇使用 Core API 進行低階控制、使用 Layout API 進行高階文件產生,或在已圍繞網頁技術建置的工作流程中使用 HTML 轉 PDF 轉換。
該函式庫也支援以影像為基礎的 PDF、以範本驅動的產生,以及豐富的互動功能,例如註解、連結、書籤、JavaScript 動作與開啟動作。
為了確保可靠性,Docotic.Pdf 包含用於測試 PDF 輸出的方法,以避免你應用程式中的變更引入回歸或意外差異。