PDFium Delphi Component Docs

PDFium VCL 程式設計範例

選擇一個可執行的範例

示範集共有 32 個主題:32 個 Delphi 專案、31 個 C++Builder 專案及 32 個 Lazarus 專案,合計 95 個原生專案。每個平台的主目錄保留 22 個完整工作流程示範;另有十個專注於單一功能的主題放在 Miscellaneous,三個平台合計 29 個原生專案。XfaLab 提供 Delphi 與 Lazarus 版本

先從主目錄的工作流程開始:功能導覽用 PDFViewer,即時撰寫內容用 CreatePDF,快速清點內容用 ContentExtractionLab,可編輯表單用 FormFill,驗證與轉換用 StandardsLab,自動化進件用 IntakeWorkbenchCli。這六個入口點立刻就能產出實際結果;需要深入研究單一 API 時,下方各聚焦式範例就派上用場

主要工作流程 demo

範例用途與相關範例的差異
PDFViewer一般檢視與功能評估主檢視器整合導覽、搜尋、縮圖、顯示模式、列印與功能展示
MultiPageViewer連續、跨頁與封面頁版面配置使用檢視器內建選取 API 的聚焦式版面配置範例;需要更完整的檢視體驗請用 PDFViewer
SplitView在不同窗格中比較頁面或文件示範獨立的檢視位置與將命令路由至作用窗格,而非單一檢視內的雙頁版面配置
SearchAndSelect跨頁搜尋結果與選取結果清單與搜尋選項比檢視器工具列更直接地呈現搜尋工作流程
PrintPDF預覽與一般列印工作頁面範圍、縮放、進度與取消構成主要的列印工作流程
ContentExtractionLab一併檢視多種內容類別TXT/JSON 清單描述物件並預覽文字;無法取代完整的文字或影像檔匯出
ExtractImages匯出嵌入式影像物件擷取的是影像點陣圖而非整頁螢幕截圖;內建範例文件為兩頁、每頁各含兩個真實影像物件
TableExtractionLab偵測表格並匯出 CSV/JSON線框與空白對齊表格、合併儲存格、接續群組與信心度控制獨立於純文字擷取
Attachment讀取、新增與匯出嵌入式附件附件實際內容的操作比 ContentExtractionLab 僅列出名稱與大小更進一步
BatchExport從多份文件匯出頁面影像串流輸出、逐檔狀態與失敗隔離示範的是批次處理而非單一轉換
CreatePDF以即時預覽建立頁面內容三種原生變體都會建立可搜尋文字、向量形狀、真實點陣影像、表格與檢閱註解;各選項會在明確儲存前重建預覽
ImageToPDF從輸入影像檔建立頁面影像版面配置、適配、排序與 JPEG 處理構成一套文件轉換工作流程
MergePDF從多份 PDF 組裝頁面先檢查各輸入檔的頁數與輸出區間,調整來源順序,待所有輸入都通過驗證後再合併
SplitPDF將選取的頁面群組寫入個別 PDF輸出命名與頁面群組驗證有別於合併或頁面影像繪製
WatermarkStamp對既有頁面套用重複覆蓋選擇目標 PDF、可重複使用的印章 PDF 與頁面區間;印章會依各頁大小縮放,未選取的頁面維持原樣
FormFill互動式 AcroForm 填寫與扁平化欄位輸入與儲存後的外觀有別於 XFDF 交換與 XFA 專屬行為
AccessibilityReader互動閱讀與語音GUI 閱讀器示範閱讀導覽與標示,而非合規稽核
AccessibilityWorkbenchCli批次無障礙檢閱與報告產生發現項目、摘要與檢閱閘門為自動化而設計;發現項目不能取代人工的無障礙檢閱
StandardsLab標準專屬的驗證與轉換互動式實驗程式呈現各項作業與選項,而非只產生一份彙總報告
PreflightReport互動式標準報告GUI 支援人工檢查與報告匯出
PreflightReportCli自動化標準報告批次輸入、報告格式與結束代碼閘控提供 PreflightReport 的自動化對應版本
IntakeWorkbenchCli批次進件、分流與路由彙總報告、失敗閘門與路由決策使其成為超越 SecurityAudit GUI 的處理工作流程

Miscellaneous 中的聚焦式範例

請從 Demo\Delphi\Miscellaneous、Demo\CBuilder\Miscellaneous 或 Demo\Lazarus\Miscellaneous 開啟這些專案。它們保留實用的 API 用法與單一匯出任務,主目錄則聚焦於完整操作流程。基本 Unicode 檢查,以及固定的註解建立與列舉流程,已有文件建立、內容擷取、XFDF 交換範例與 API 測試涵蓋,因此不再提供獨立示範專案

範例用途與相關範例的差異
SecurityAudit互動式檢視安全態勢GUI 著重權限、指令碼、簽章與安全性細節,而非檔案路由
XfdfLab匯入與匯出 XFDF 資料交換功能加上明確儲存匯入後的 PDF,示範資料如何持久化;僅匯出 XFDF 並不會儲存修改過的 PDF
AccessibilityLab在主控台中走訪閱讀器 API精簡的可執行範例會走過閱讀單元、字詞週框、結構與語音腳本輸出
ExtractText從選取頁面匯出完整文字文字層輸出、頁面分隔符與取消功能有別於清單預覽與表格偵測;不執行 OCR
FontProperties檢視字型與字元度量啟動時即針對第一個文字物件顯示實際的字型與字元度量;選取另一行就能比較字型,無須匯出文件內容
NavigationLab書籤、目的地與導覽動作精簡的 API 走訪與 PDFViewer 中可見的導覽控制項互補
PDFtoJpg將單一文件轉為頁面影像精簡的單一文件繪製範例;Delphi 版提供獨立頁面範圍,BatchExport 則著重多份輸入文件
PrintPreferencesN-up 版面與 PDF 作者列印偏好份數、奇偶頁篩選與作者提供的偏好是教學重點;與 PrintPDF 搭配使用
ViewerInteractionLab診斷滑鼠、鍵盤與座標互動互動記錄與頁面 / 裝置座標轉換讓它成為一套 API 診斷工具
XfaLab靜態 XFA 封包與執行階段能力以一般執行階段開啟範本與 datasets 封包;互動式 XFA 繪製需要啟用 XFA 的執行階段,提供 Delphi 與 Lazarus 版本

先跑一輪可運作的功能導覽

PDFViewer 會開啟隨附的 PDFiumDemoShowcase.pdf,這份六頁文件包含可搜尋文字、向量圖表、兩張嵌入式影像、一個表格、四個 AcroForm 欄位、章節書籤、連結、一則檢閱註解與兩個檔案附件。體驗列提供 Restart showcase、Find live text、2-page spread、Night reading、Inspect this PDF 與 Explore forms 等動作;每個動作都作用於目前文件

CreatePDF 會開啟雙頁的即時報告預覽。編輯標題與作者,或切換形狀、表格與浮水印,即可重建實際的頁面內容;Unicode 選項會加入第三頁。在你儲存之前不會寫入任何東西,儲存後的 PDF 仍可搜尋

MultiPageViewer、SearchAndSelect、ContentExtractionLab 與 FormFill 會開啟同一份文件執行各自的工作流程,搜尋結果、擷取清單與可編輯欄位隨開即用。Miscellaneous\NavigationLab 則用自己的本機副本進行書籤與目的地 API 走訪

Attachment 開啟時就帶有兩個真實的嵌入式檔案。PrintPreferences 開啟時有四個頁面,並附上作者偏好:兩份列印、選定的頁面範圍、縮放與雙面列印;目前的列印工作流程會套用份數與頁面範圍,縮放與雙面設定則只檢查與回報。XfaLab 開啟時帶有實際的範本與 datasets 封包供靜態 XFA 檢查;互動式 XFA 繪製需要適當的執行階段,屬於另一項獨立能力

每個檢視、表單、擷取與導覽專案都內含自己的 PDFiumDemoShowcase.pdf。複製專案目錄時,把這份內建輸入留在發布後的可執行檔旁邊即可;Lazarus 建置時會自動安排到位。導覽可離線運作,本機 PDF 無法使用時,一般檢視器與擷取範例也會提供自動產生的替代內容

複製與維護原生範例

每個原生 demo 都內含自己的範例實作。AccessibilityLab、AccessibilityWorkbenchCli、IntakeWorkbenchCli 與 PreflightReportCli 的 Delphi 與 Lazarus 進入點把完整工作流程放在專案目錄內,包括編譯器專屬的初始化與 JPEG 寫入。附件、列印偏好與 XFA 範例也會在本機建立各自的功能特定輸入。複製出去的專案之外,只需要程式庫、對應的原生執行階段與一般的編譯器 framework 相依

需要嵌入式點陣圖時選擇影像物件擷取,需要可見頁面時選擇頁面繪製,PDF 已有文字層時則選擇文字擷取。只列出這些類別的報告並不會匯出它們的完整內容。這些邊界讓聚焦式範例保持實用,而不必把每個 demo 擴充成另一套通用檢視器或工作台

基本文件操作

載入並顯示 PDF 文件

Pdf1.FileName := 'C:\MyDocument.pdf';
Pdf1.Active := True;
ShowMessage('Pages: ' + IntToStr(Pdf1.PageCount));
ShowMessage('Title: ' + Pdf1.Title);
ShowMessage('Author: ' + Pdf1.Author);

var
  Stream: TMemoryStream;
begin
  Stream := TMemoryStream.Create;
  try
    Stream.LoadFromFile('C:\MyDocument.pdf');
    Pdf1.LoadDocument(Stream, True);
    Pdf1.Active := True;
    ShowMessage('PDF loaded from stream');
  finally
    Stream.Free;
  end;
end;

建立新的 PDF 文件

Pdf1.CreateDocument;
Pdf1.Active := True;
Pdf1.AddPage(0, 612, 792);
Pdf1.PageNumber := 1;
Pdf1.AddText('Hello World!', 'Arial', 12, 100, 700, clBlack, $FF, 0.0);
Pdf1.UpdatePage;
Pdf1.SaveAs('C:\NewDocument.pdf');
Pdf1.Active := False;

文字操作

從 PDF 擷取文字

var
  PageText: WString;
  RectText: WString;
begin
  Pdf1.PageNumber := 1;
  PageText := Pdf1.Text;
  Memo1.Text := PageText;
  RectText := Pdf1.TextInRectangle(100, 100, 500, 200);
  ShowMessage('Text in rectangle: ' + RectText);
end;

搜尋文字

var
  Position: Integer;
begin
  Position := Pdf1.FindFirst('Hello', [seCaseSensitive]);
  while Position >= 0 do
  begin
    ShowMessage('Found at character: ' + IntToStr(Position));
    Position := Pdf1.FindNext;
  end;
end;

新增帶格式的文字

Pdf1.PageNumber := 1;
Pdf1.AddText('Document Title', 'Times New Roman', 16, 100, 750, clBlue);
Pdf1.AddText('This is the document content.', 'Arial', 12, 100, 700, clBlack);
Pdf1.UpdatePage;

檢視字元與字型中繼資料

var
  I: Integer;
  MetricSize: Single;
  FontBytes: TBytes;
begin
  Pdf1.PageNumber := 1;
  for I := 0 to Pdf1.ObjectCount - 1 do
    if Pdf1.ObjectType[I] = otText then
    begin
      MetricSize := 12.0;
      FontBytes := Pdf1.FontData[I];
      Memo1.Lines.Add('Object ' + IntToStr(I));
      Memo1.Lines.Add('Base name: ' + Pdf1.FontBaseName[I]);
      Memo1.Lines.Add('Family: ' + Pdf1.FontFamilyName[I]);
      Memo1.Lines.Add('Embedded: ' + BoolToStr(Pdf1.FontIsEmbedded[I], True));
      Memo1.Lines.Add('Weight: ' + IntToStr(Pdf1.FontWeight[I]));
      Memo1.Lines.Add('Italic angle: ' + IntToStr(Pdf1.FontItalicAngle[I]));
      Memo1.Lines.Add('Ascent: ' + FloatToStr(Pdf1.FontAscent[I, MetricSize]));
      Memo1.Lines.Add('Descent: ' + FloatToStr(Pdf1.FontDescent[I, MetricSize]));
      Memo1.Lines.Add('Font data bytes: ' + IntToStr(Length(FontBytes)));
    end;

  if Pdf1.CharacterCount > 0 then
  begin
    Memo1.Lines.Add('First character size: ' + FloatToStr(Pdf1.CharacterFontSize[0]));
    Memo1.Lines.Add('First character weight: ' + IntToStr(Pdf1.CharacterFontWeight[0]));
    Memo1.Lines.Add('First character angle: ' + FloatToStr(Pdf1.CharacterAngle[0]));
  end;
end;

Demo\Delphi\Miscellaneous\FontProperties、Demo\CBuilder\Miscellaneous\FontProperties 與 Demo\Lazarus\Miscellaneous\FontProperties 專案會透過 TPdf 與 TPdfView 兩者展示相同的中繼資料

建立多類別內容擷取報告

Demo\Delphi\ContentExtractionLab、Demo\CBuilder\ContentExtractionLab 與 Demo\Lazarus\ContentExtractionLab 專案將內容擷取整合為單一工作流程。它們會開啟或建立 PDF,讓使用者選擇中繼資料、頁面文字、頁面物件、影像、附件、連結、註解、書籤以及字型 / 字元度量,然後產生摘要與詳細的 TXT 或 JSON 報告。此實驗室也包括全部 / 全不選類別選擇、報告複製到剪貼簿,以及來源 PDF 儲存動作,因此使用者可將產生的範例 PDF 與其擷取報告放在一起。ExtractImages 與 Attachment 提供實際內容的匯出工作流程,Miscellaneous\ExtractText 與 Miscellaneous\FontProperties 則保留聚焦式 API 範例;ExtractText 支援頁面範圍與頁面分隔符選項,可在 Delphi、C++Builder 與 Lazarus 中產生逐頁文字輸出,而 ContentExtractionLab 則是跨類別的清單範例

偵測與擷取表格

Demo\Delphi\TableExtractionLab、Demo\CBuilder\TableExtractionLab 與 Demo\Lazarus\TableExtractionLab 專案把表格偵測整合成單一工作流程。實驗程式會建立一個範例文件,內含帶有合併表頭儲存格的線框表格、無線框靠空白對齊的表格,以及一個跨頁斷開的長表格,然後對目前頁面執行 ExtractTables 或對整份文件執行 ExtractDocumentTables,並回報列、欄、RowSpan 與 ColumnSpan 值、表頭旗標、偵測模式、信心度與接續群組。DetectRuledTables、DetectWhitespaceTables、DetectContinuations、DetectFilledRulings 與 MinConfidence 選項可在介面中編輯,選取的表格可透過 TPdfTable.ToCsv 與 TPdfTable.ToJson 匯出。實驗程式還能整批處理資料夾:Batch folder 對每個 PDF 執行 ExtractDocumentTables,回報每份文件的表格數量,標記沒有文字層的掃描件,並用緊湊欄位預設值重試空白文件;Export all CSV 會把偵測到的每個表格寫到來源檔旁邊。每份文件都在自己的 TPdf 執行個體中開啟,因此一個無法讀取的檔案不會影響批次中的其餘檔案

表單操作

使用表單欄位

var
  I: Integer;
  FieldInfo: TPdfFormFieldInfo;
begin
  Pdf1.FormField[0] := 'John';
  for I := 0 to Pdf1.FormFieldCount - 1 do
  begin
    FieldInfo := Pdf1.FormFieldInfo[I];
    ShowMessage(FieldInfo.Name + ': ' + Pdf1.FormField[I]);
  end;
end;

將填寫的表單值扁平化至頁面中

// Without flattening, FormField[i] := value writes only the /V entry —
// the visible appearance comes from the /AP stream and may not refresh
// GenerateFormAppearances regenerates /AP for every widget, then
// FlattenAllPages bakes every page's annotations and widgets into
// permanent, non-editable content
Pdf1.FileName := 'C:\Application.pdf';
Pdf1.Active := True;
Pdf1.FormField[0] := 'John Smith';
Pdf1.FormField[1] := '[email protected]';
Pdf1.GenerateFormAppearances;
if Pdf1.FlattenAllPages then
  Pdf1.SaveAs('C:\Application.flat.pdf');
Pdf1.Active := False;

影像操作

擷取影像

var
  I: Integer;
  Bitmap: TBitmap;
begin
  for I := 0 to Pdf1.BitmapCount - 1 do
  begin
    Bitmap := Pdf1.Bitmap[I];
    try
      Bitmap.SaveToFile('C:\ExtractedImage' + IntToStr(I) + '.bmp');
    finally
      Bitmap.Free;
    end;
  end;
end;

新增影像

var
  JpegStream: TFileStream;
begin
  JpegStream := TFileStream.Create('C:\MyImage.jpg', fmOpenRead or fmShareDenyWrite);
  try
    Pdf1.AddJpegImage(JpegStream, 100, 500, 200, 150);
  finally
    JpegStream.Free;
  end;
  Pdf1.AddPicture(Image1.Picture, 100, 300, 150, 100);
  Pdf1.UpdatePage;
end;

註解操作

使用註解

var
  I: Integer;
  Annotation: TPdfAnnotation;
begin
  for I := 0 to Pdf1.AnnotationCount - 1 do
  begin
    Annotation := Pdf1.Annotation[I];
    ShowMessage('Annotation ' + IntToStr(I) + ': ' + Annotation.ContentsText);
  end;
end;

繪製操作

繪製形狀

Pdf1.CreatePath(100, 100, 200, 150, fmAlternate, clYellow, $FF, True, clBlack, $FF, 2.0);
Pdf1.AddPath;

Pdf1.CreatePath(100, 100, fmNone, clBlack, $FF, True, clRed, $FF, 3.0);
Pdf1.LineTo(200, 100);
Pdf1.LineTo(150, 200);
Pdf1.ClosePath;
Pdf1.AddPath;
Pdf1.UpdatePage;

導覽與書籤

使用書籤

var
  I: Integer;
  Bookmarks: TBookmarks;
begin
  Bookmarks := Pdf1.Bookmarks;
  for I := 0 to Length(Bookmarks) - 1 do
    ShowMessage(Bookmarks[I].Title + ' -> Page ' + IntToStr(Bookmarks[I].PageNumber));
end;

使用具名目的地

var
  I: Integer;
  Destination: TDestination;
begin
  for I := 0 to Pdf1.DestinationCount - 1 do
  begin
    Destination := Pdf1.Destination[I];
    if Destination.Name = 'Chapter1' then
    begin
      Pdf1.PageNumber := Destination.PageNumber;
      Break;
    end;
  end;
end;

附件操作

使用附件

var
  I: Integer;
  AttachmentData: TBytes;
  AttachmentName: WString;
  FileStream: TFileStream;
begin
  for I := 0 to Pdf1.AttachmentCount - 1 do
  begin
    AttachmentName := Pdf1.AttachmentName[I];
    AttachmentData := Pdf1.Attachment[I];
    FileStream := TFileStream.Create('C:\Extracted_' + string(AttachmentName), fmCreate);
    try
      if Length(AttachmentData) > 0 then
        FileStream.WriteBuffer(AttachmentData[0], Length(AttachmentData));
    finally
      FileStream.Free;
    end;
  end;
  if Pdf1.CreateAttachment('MyFile.txt') then
    Pdf1.Attachment[Pdf1.AttachmentCount - 1] := TFile.ReadAllBytes('C:\MyFile.txt');
end;

頁面管理

頁面操作

Pdf1.AddPage(Pdf1.PageCount + 1, 612, 792);
Pdf1.AddPage(Pdf1.PageCount + 1, 595, 842);
Pdf1.AddPage(Pdf1.PageCount + 1, 400, 600);

var
  SourcePdf: TPdf;
begin
  SourcePdf := TPdf.Create(nil);
  try
    SourcePdf.FileName := 'C:\SourceDocument.pdf';
    SourcePdf.Active := True;
    Pdf1.ImportPages(SourcePdf, '1', Pdf1.PageCount + 1);
  finally
    SourcePdf.Free;
  end;
end;

if Pdf1.PageCount > 1 then
  Pdf1.DeletePage(Pdf1.PageCount);

儲存與壓縮

儲存並壓縮

// Create a compressed PDF document
Pdf1.CreateDocument;
Pdf1.Compressed := True; // ensure all streams are FlateDecode compressed (default)
Pdf1.Active := True;
Pdf1.AddPage(0, 595, 842); // A4 size
Pdf1.AddText('Hello World!', 'Arial', 24, 100, 700, clBlack, $FF, 0.0);
Pdf1.SaveAs('C:\Compressed.pdf');
Pdf1.Active := False;

// Save with options
Pdf1.SaveAs('C:\Output.pdf', saIncremental); // incremental save
Pdf1.SaveAs('C:\NoSecurity.pdf', saRemoveSecurity); // remove security

// Save to a specific PDF version target. The PDF version validator rejects
// any feature introduced after the chosen target (JBIG2Decode, MarkInfo,
// AES encryption, Polygon / Caret / Watermark / Redact annotations, ...)
if not Pdf1.SaveAs('C:\Legacy15.pdf', saNone, pv15) then
  ShowMessage('Save rejected: document contains features newer than PDF 1.5');

建立 PDF/A-1b 封存輸出

// SaveAsPdfA post-processes the base save with an incremental update that
// injects an XMP metadata stream, an sRGB ICC OutputIntent, and an updated
// document catalog so the result is PDF/A-1b conformant. The bundled
// sRGB IEC61966-2.1 profile is used automatically when no custom profile
// is supplied
Pdf1.FileName := 'C:\Report.pdf';
Pdf1.Active := True;
if Pdf1.SaveAsPdfA('C:\Report.pdfa.pdf', pac1b) then
  ShowMessage('Saved PDF/A-1b archive');

// PDF/A with a custom ICC profile (CMYK printing pipelines for example)
var
  Opts: TPdfASaveOptions;
begin
  Opts := Default(TPdfASaveOptions);
  Opts.Conformance    := pac1b;
  Opts.IccProfileData := TFile.ReadAllBytes('C:\Profiles\Coated_GRACoL_2006.icc');
  Pdf1.SaveAsPdfA('C:\Report.cmyk.pdfa.pdf', Opts);
end;

// In-memory PDF/A output
var
  Stream: TMemoryStream;
begin
  Stream := TMemoryStream.Create;
  try
    Opts := Default(TPdfASaveOptions);
    Opts.Conformance := pac1b;
    if Pdf1.SaveAsPdfAToStream(Stream, Opts) then
      UploadToArchive(Stream);
  finally
    Stream.Free;
  end;
end;

驗證 PDF/A 合規層級

var
  Validation: TPdfAValidationResult;
  Issue: TPdfAValidationIssue;
begin
  Pdf1.FileName := 'C:\Incoming\report.pdf';
  Pdf1.Active   := True;

  // Cheap header-style probe — reads the conformance without iterating
  // the full validator. Result: pacNone, pac1a, pac1b, pac2b, pac3b, or
  // pacUnknown
  case Pdf1.PdfAConformance of
    pac1b: Status.Caption := 'PDF/A-1b';
    pac2b: Status.Caption := 'PDF/A-2b';
    pacNone: Status.Caption := 'Not PDF/A';
  else
    Status.Caption := 'Unknown conformance';
  end;

  // Full validator with per-issue diagnostics
  Validation := Pdf1.ValidatePdfA;
  Memo1.Lines.Add('Conformance: ' + GetEnumName(TypeInfo(TPdfAConformance),
    Ord(Validation.Conformance)));
  for Issue in Validation.Issues do
    Memo1.Lines.Add('- ' + Issue.Description);
end;

在 StandardsLab 中檢查多種 PDF 標準

Demo\Delphi\StandardsLab、Demo\CBuilder\StandardsLab 與 Demo\Lazarus\StandardsLab 專案會載入或建立 PDF,執行 ValidatePdfA、ValidatePdfUa、ValidatePdfE、ValidatePdfX、ValidatePdfR 與 ValidatePdfVT,然後在同一個表格中顯示每個偵測到的合規層級與問題計數。其儲存按鈕會呼叫 SaveAsPdfA、SaveAsPdfUa、SaveAsPdfE、SaveAsPdfX、SaveAsPdfR 與 SaveAsPdfVT,因此標記輸出工作流程毋須撰寫自訂測試框架即可見

產生 Preflight 報告

FPdfPreflightReport 單元將 ValidatePdfA、ValidatePdfUa、ValidatePdfE、ValidatePdfX、ValidatePdfR 與 ValidatePdfVT 包裝為單一可查詢的報告物件,附 Info / Warning / Error 嚴重性、狀態與問題彙總、查詢、搜尋、篩選,以及穩定的 SHA-256 指紋。報告可以輸出為文字、HTML、Markdown、JSON、CSV、SARIF 2.1.0、JUnit XML、NDJSON 或原生 XML。Demo\Delphi\PreflightReport、Demo\Lazarus\PreflightReport 與 Demo\CBuilder\PreflightReport 提供互動式選擇與預覽工作流程,而 Demo\Delphi\PreflightReportCli 則增加可指令化的輸入 / 輸出引數、標準篩選、選用的報告附件嵌入、CI 結束代碼閘控、批次清單與目錄處理、防衝突路由、彙總摘要與 CSV 資訊清單。產生的報告會說明內建驗證器涵蓋標記層級與選定的檔案層級檢查,因此完整的內容層級 preflight 仍可委派給專用驗證引擎

串流載入與大型文件

使用 LoadCustomDocument 隨選載入 PDF

// LoadCustomDocument lets PDFium pull blocks from any seekable TStream on
// demand. The stream is NOT copied into memory, so multi-GB PDFs, remote
// HTTP body streams, and database-backed sources are all viable without
// an up-front buffer
var
  FileStream: TFileStream;
begin
  // Read-from-file streaming (no in-memory copy)
  FileStream := TFileStream.Create('C:\Huge.pdf',
    fmOpenRead or fmShareDenyWrite);
  Pdf1.LoadCustomDocument(FileStream, True{AOwnsStream});
  // ^^ ownership transferred — Pdf1 frees FileStream on UnloadDocument
  Pdf1.Active := True;
  ShowMessage('Pages: ' + IntToStr(Pdf1.PageCount));
end;

// Externally-owned stream (caller frees it after UnloadDocument)
var
  Stream: TFileStream;
begin
  Stream := TFileStream.Create('C:\Live.pdf',
    fmOpenRead or fmShareDenyNone);
  try
    Pdf1.LoadCustomDocument(Stream, False);
    Pdf1.Active := True;
    DoWork;
    Pdf1.Active := False;
  finally
    Stream.Free;
  end;
end;

非同步取消與漸進式繪製

協調式取消權杖

// FPdfAsync exposes IPdfCancellationToken /
// IPdfCancellationTokenSource for cancel-from-UI semantics that work
// across the long-running export and render paths
uses FPdfAsync;

var
  CancelSource: IPdfCancellationTokenSource;
  CancelToken:  IPdfCancellationToken;
begin
  CancelSource := PdfCancellationTokenSource;
  CancelToken  := CancelSource.Token;

  // user clicks Cancel
  CancelSource.Cancel;

  // The worker periodically checks CancelToken.IsCancelled, or the render
  // path polls it through PDFium's IFSDK_PAUSE callback
  if CancelToken.IsCancelled then
    Memo1.Lines.Add('Cancelled by user');
end;

可取消的漸進式頁面繪製

// RenderPageProgressive lets a long high-DPI render abort mid-page when
// the user clicks Cancel, instead of blocking the caller until done
var
  Status: TPdfProgressiveStatus;
  Bitmap: TBitmap;
begin
  Bitmap := TBitmap.Create;
  try
    Bitmap.PixelFormat := pf32bit;
    Bitmap.SetSize(2480, 3508); // A4 @ 300 DPI

    Status := Pdf1.RenderPageProgressive(Bitmap, 0, 0, Bitmap.Width,
      Bitmap.Height, CancelToken, ro0, [reAnnotations], clWhite);

    case Status of
      prsDone:      SaveBitmap(Bitmap);
      prsCancelled: Memo1.Lines.Add('Render cancelled — partial result available');
      prsFailed:    Memo1.Lines.Add('PDFium reported FPDF_RENDER_FAILED');
    end;
  finally
    Bitmap.Free;
  end;
end;

使用 TPdfFuture 於背景執行緒執行繪製

// TPdfFuture<T> spins up a worker on a background thread and posts a
// TPdfFutureResult<T> envelope back to the main thread
//
// Important: TPdf serializes render calls per instance, but the worker still
// owns dispatch and document lifetime. Do not mutate a TPdf while a render is
// in flight; batch export usually keeps one TPdf per file
type
  TRenderResult = record
    Bitmap : TBitmap;
    PageNo : Integer;
  end;

var
  Future: TPdfFuture<TRenderResult>;
begin
  Future := TPdfFuture<TRenderResult>.Create(
    function (const ACancelToken: IPdfCancellationToken): TRenderResult
    var
      LocalPdf: TPdf;
    begin
      LocalPdf := TPdf.Create(nil);
      try
        LocalPdf.FileName := 'C:\Huge.pdf';
        LocalPdf.Active   := True;
        LocalPdf.PageNumber := 1;
        Result.Bitmap := LocalPdf.RenderPage(0, 0, 2480, 3508);
        Result.PageNo := 1;
      finally
        LocalPdf.Free;
      end;
    end,
    procedure (const AResult: TPdfFutureResult<TRenderResult>)
    begin
      case AResult.State of
        pfsSuccess:   PaintBitmapToView(AResult.Value.Bitmap);
        pfsCancelled: Log.Add('Cancelled');
        pfsFailed:    Log.Add('Worker failed: ' + AResult.ErrorMessage);
      end;
    end);
end;

變換矩陣

組合 Translate / Scale / Rotate / Skew

// FPdfMatrix wraps PDFium's FS_MATRIX so callers can build a transform
// declaratively and hand it to FPDFPageObj_SetMatrix
uses FPdfMatrix, FPdfView;

var
  M: TPdfMatrix;
  RawMatrix: FS_MATRIX;
  PageObj: FPDF_PAGEOBJECT;
begin
  M := TPdfMatrix.Create;
  try
    M.Translate(72, 200);       // move 1in right, ~2.8in up
    M.Scale(0.5, 0.5);          // half-size stamp
    M.Rotate(15);               // 15-degree CCW rotation
    // M.HorizontalFlip;
    // M.VerticalFlip;
    // M.CentralFlip;
    // M.Skew(10, 5);
    // M.Multiply(Other);       // post-multiply with another matrix

    // Apply to any PDFium page object (text / path / image / form XObject)
    RawMatrix := M.Handle;
    FPDFPageObj_SetMatrix(PageObj, RawMatrix);
  finally
    M.Free;
  end;
end;

頁面組合

依索引匯入頁面子集

// ImportPagesByIndex copies an explicit zero-based index array from a
// source PDF into this document. InsertAt = 0 inserts before page 1;
// PageCount appends
var
  Source: TPdf;
begin
  Source := TPdf.Create(nil);
  try
    Source.FileName := 'C:\Report.pdf';
    Source.Active   := True;

    // Import pages 0, 2, 4 (one-based: pages 1, 3, 5) from Source
    // and append them at the end of Pdf1
    Pdf1.ImportPagesByIndex(Source, [0, 2, 4], Pdf1.PageCount);

    // Empty array imports every source page
    // Pdf1.ImportPagesByIndex(Source, [], 0);
  finally
    Source.Free;
  end;
end;

N-up 合成輸出

// ImportNPagesToOne returns a brand-new TPdf whose pages are NumX*NumY
// composites of THIS document. OutputWidth/Height are PDF user units
// (1 unit = 1/72 in)
var
  Composite: TPdf;
begin
  Pdf1.FileName := 'C:\Slides.pdf';
  Pdf1.Active   := True;
  // 4-up A4 landscape: 842 x 595 pt, 2 columns x 2 rows
  Composite := Pdf1.ImportNPagesToOne(842, 595, 2, 2);
  try
    if Composite <> nil then
      Composite.SaveAs('C:\Slides_4up.pdf');
  finally
    Composite.Free;
  end;
end;

原地重新排列頁面

// MovePages takes the zero-based indices of the pages to move plus the
// destination index for the first moved page after the move completes
// Other pages shift around the moved block so the document length stays
// the same
//
// Example: in a 5-page document with pages numbered [0,1,2,3,4],
// MovePages([2, 3], 0) reorders to [2, 3, 0, 1, 4]
if not Pdf1.MovePages([2, 3], 0) then
  ShowMessage('Invalid indices passed to MovePages');

透過 Form XObject 頁面重複使用實作浮水印

// CreateXObjectFromPage manufactures a reusable Form XObject handle from
// any page of any other TPdf. Stamp that handle onto as many pages of
// THIS document as you want — each stamp is a single PDFium page object
// that can be positioned, scaled, and rotated through the matrix API
uses FPdfMatrix, FPdfView;

var
  WatermarkSource: TPdf;
  XObj:            TPdfXObject;
  PageObj:         FPDF_PAGEOBJECT;
  M:               TPdfMatrix;
  RawMatrix:       FS_MATRIX;
  I:               Integer;
begin
  WatermarkSource := TPdf.Create(nil);
  try
    WatermarkSource.FileName := 'C:\Watermarks\Confidential.pdf';
    WatermarkSource.Active   := True;

    // Wrap page 0 of WatermarkSource as a reusable XObject
    XObj := Pdf1.CreateXObjectFromPage(WatermarkSource, 0);
    try
      for I := 1 to Pdf1.PageCount do
      begin
        Pdf1.PageNumber := I;
        PageObj := Pdf1.InsertFormObjectFromXObject(XObj);
        if PageObj = nil then Continue;

        // Centre the watermark on the page
        M := TPdfMatrix.Create;
        try
          M.Scale(0.5, 0.5);
          M.Translate(0.25 * Pdf1.PageWidth, 0.5 * Pdf1.PageHeight);
          RawMatrix := M.Handle;
          FPDFPageObj_SetMatrix(PageObj, RawMatrix);
        finally
          M.Free;
        end;

        Pdf1.UpdatePage;
      end;
    finally
      XObj.Free; // closes the FPDF_XOBJECT handle
    end;

    Pdf1.SaveAs('C:\Watermarked.pdf');
  finally
    WatermarkSource.Free;
  end;
end;

Demo\Delphi\WatermarkStamp、Demo\Lazarus\WatermarkStamp 與 Demo\CBuilder\WatermarkStamp 專案將此模式轉化為可執行的工作流程:它們會建立目標 PDF 與可重複使用的戳印 PDF,使用 CreateXObjectFromPage 包裝戳印頁面,在每個目標頁面上插入相同的 Form XObject,套用 TPdfMatrix 變換以實作置中浮水印與右上角戳印,並在儲存結果之前加上選用的頁碼標籤

影像插入(直接 AddImage 多載版本)

新增已註冊的影像檔案或 TBitmap

// AddImage(FileName) accepts any format registered with the VCL or LCL
// graphics units (BMP, PNG via PngImage, JPG, ...)
Pdf1.AddImage('C:\Photo.png', 100, 500, 200, 150);

// AddImage(TBitmap) skips the TPicture intermediary, ideal when the
// bitmap already comes from rendering or batch generation work
var
  Bitmap: TBitmap;
begin
  Bitmap := Pdf1.RenderPage(0, 0, 595, 842);
  try
    Pdf1.PageNumber := 2;
    Pdf1.AddImage(Bitmap, 50, 50, 250, 350);
    Pdf1.UpdatePage;
  finally
    Bitmap.Free;
  end;
end;

Demo\Delphi\ImageToPDF、Demo\CBuilder\ImageToPDF 與 Demo\Lazarus\ImageToPDF 專案將此轉化為完整的批次工作流程:選取多個影像檔案、預覽選取項目、為每張影像建立一個 PDF 頁面、將每張影像縮放至 A4 直向或橫向頁面、儲存結果並開啟產生的 PDF

檢視器搜尋標示

於目前頁面標示所有符合項目

var
  MatchCount: Integer;
begin
  // Paint a clYellow mask over every match on the view's current page
  // Switching to a different page clears the highlight automatically
  PdfView1.HighlightColor := clYellow;
  MatchCount := PdfView1.HighlightSearchText('invoice', False{Case}, True{Word});
  Status.Caption := Format('%d match(es) on this page', [MatchCount]);

  // Programmatically remove the overlay before the page changes
  PdfView1.ClearHighlight;
end;

檢視器版面配置與頁面色彩

符合模式與頁面背景

// FitMode keeps long documents framed during Resize or page changes
// Setting Zoom directly cancels FitMode (it reverts to pfmNone)
PdfView1.FitMode := pfmFitPage;       // whole page in viewport
// PdfView1.FitMode := pfmFitWidth;   // page width matches viewport width
// PdfView1.FitMode := pfmActualSize; // 100%

// PageColor decouples the rendered PDF page from the host control's Color
// — typical dark-mode viewer setup keeps the scroll area dark while the
// PDF page stays paper-white
PdfView1.Color     := clBlack;        // scroll area
PdfView1.PageColor := clWhite;        // PDF page background

// Optional drop shadow and page border to lift the page off a dark bg
PdfView1.PageShadowSize  := 6;
PdfView1.PageShadowColor := $00404040;
PdfView1.PageBorderColor := clGray;

於 ViewerInteractionLab 中探索檢視器互動

Demo\Delphi\Miscellaneous\ViewerInteractionLab、Demo\CBuilder\Miscellaneous\ViewerInteractionLab 與 Demo\Lazarus\Miscellaneous\ViewerInteractionLab 專案將現代 TPdfView 互動介面集中於同一個小型表單中:開啟或建立範例 PDF、切換 DisplayMode、套用 FitMode、變更 PageColor、旋轉頁面、使用 HighlightSearchText 標示目前頁面的搜尋符合項目、啟用或停用使用者文字選取、檢查 SelectedText、呼叫 SelectAll、CopySelectionToClipboard 與 ClearSelection,並在滑鼠移過頁面時於狀態列觀察 DeviceToPage 座標更新

於 SearchAndSelect 中搜尋並選取文字

Demo\Delphi\SearchAndSelect、Demo\CBuilder\SearchAndSelect 與 Demo\Lazarus\SearchAndSelect 專案將搜尋與選取的檢視器工作流程轉化為獨立範例。它們會開啟或建立 PDF,列出全頁面搜尋結果(含頁碼、字元索引與預覽文字),支援上一個 / 下一個導覽以及連按兩下結果跳轉,使用 HighlightSearchText 標示目前頁面的符合項目,公開區分大小寫與整字選項,切換 AllowUserTextSelection,預覽 SelectedText,透過按鈕驅動 SelectAll、CopySelectionToClipboard 與 ClearSelection,並讓使用者於單頁與跨頁顯示模式之間切換

於 PrintPreferences 中讀取 PDF 列印偏好設定

Demo\Delphi\Miscellaneous\PrintPreferences、Demo\CBuilder\Miscellaneous\PrintPreferences 與 Demo\Lazarus\Miscellaneous\PrintPreferences 專案會在列印之前讀取作者列印偏好設定。它們會顯示 PrintCopies、PrintPageRanges、PrintScaling 與 PrintPaperHandling,可將作者提供的份數與範圍複製至列印輸入,並展示 SetPdfPrintPaperHandlingDevMode 如何將 PDF 雙面意圖對應為 Windows DEVMODE

於 PrintPDF 中執行標準 PDF 列印

Demo\Delphi\PrintPDF、Demo\CBuilder\PrintPDF 與 Demo\Lazarus\PrintPDF 專案展示標準列印工作流程:開啟 PDF、預覽頁面、於平台列印對話方塊中選擇頁面範圍與份數、遵循自動分頁模式、透過 TPdf.RenderPage 繪製每頁,並公開帶取消功能的列印進度。Delphi 範例也接受拖放 PDF 檔案以快速載入

於 MultiPageViewer 中執行快速多頁檢視

Demo\Delphi\MultiPageViewer、Demo\CBuilder\MultiPageViewer 與 Demo\Lazarus\MultiPageViewer 專案會以一般頁面導覽與縮放工作流程執行連續、跨頁與感知封面的檢視。它們也適用於測試超大型 PDF 的開啟至首頁速度與繪製回應性:在大型檔案測試中,可能讓 Acrobat 或 Foxit 花費 20-30 秒才出現首次檢視的 2.x GB 文件,透過 PDFium Delphi Component 檢視器路徑幾乎可立即可用

於 SplitView 中執行並排審閱

Demo\Delphi\SplitView、Demo\CBuilder\SplitView 與 Demo\Lazarus\SplitView 專案展示並排 PDF 審閱。C++Builder 與 Lazarus 範例著重於兩窗格或三窗格比較、作用檢視選擇、選用的同步導覽命令、共用縮放預設,以及對選取檢視或所有已載入檢視的旋轉。可使用這些範例比較大型文件,同時讓首次可用檢視與後續頁面繪製保持回應性;可能讓 Acrobat 或 Foxit 花費 20-30 秒開啟的相同 2.x GB PDF 檔案,透過 PDFium Delphi Component 檢視器路徑幾乎可立即可用

於 SecurityAudit 中稽核 PDF 風險面

Demo\Delphi\Miscellaneous\SecurityAudit、Demo\CBuilder\Miscellaneous\SecurityAudit 與 Demo\Lazarus\Miscellaneous\SecurityAudit 專案會以唯讀方式開啟 PDF,列出權限狀態、內嵌附件、文件 JavaScript 動作、URI 與 Launch 連結註解、網頁連結、簽章摘要、XFA 狀態、V8 輔助功能可用性,以及不支援功能的 Callback。同一份摘要與發現項目可以儲存或複製為 TXT 稽核報告,也可以儲存為結構化 JSON。這些專案適合用來建立宿主應用程式的風險面板,並非惡意軟體偵測工具

檢視器連結處理

具動作允許清單的安全點按即跟隨

// LinkOptions controls automatic handling of the four PDF link action
// types. Defaults enable goto and URI only; launch (run a program) and
// embedded GotoR (jump to another file) stay OFF so a stray click cannot
// run arbitrary code
PdfView1.LinkOptions :=
  [loAutoGoto, loAutoOpenURI];  // safe defaults

// OnAnnotationLinkClick fires for in-document destinations (page jumps,
// named destinations, action chains). Mark Handled = True to prevent the
// view from auto-following the link
procedure TForm1.PdfView1AnnotationLinkClick(Sender: TObject;
  LinkIndex: Integer; const Action: TPdfAction;
  var Handled: Boolean);
begin
  if Action.Kind = paUri then
  begin
    if MessageDlg('Open ' + Action.Uri + '?', mtConfirmation, [mbYes, mbNo], 0)
      <> mrYes then
      Handled := True;  // suppress auto-handling
  end;
end;

// OnWebLinkClick fires for URI strings discovered by PDFium's web-link
// scanner (auto-detected URLs in the text stream, even without an
// /A <</S/URI...>> annotation)
procedure TForm1.PdfView1WebLinkClick(Sender: TObject;
  WebLinkIndex: Integer; const Url: WString;
  var Handled: Boolean);
begin
  Log.Add('Web link clicked: ' + string(Url));
end;

檢視器表單控制項編輯

選取與復原 / 重做

// On the focused AcroForm widget, six new methods drive PDFium's form
// edit history. All six short-circuit safely if no widget is focused or
// the document carries no AcroForm
procedure TForm1.btnSelectAllClick(Sender: TObject);
begin
  PdfView1.SelectAllFormText;
  EditMenuCopy.Caption := 'Copy: ' + string(PdfView1.GetSelectedFormText);
end;

procedure TForm1.btnUndoClick(Sender: TObject);
begin
  btnUndo.Enabled := PdfView1.FormCanUndo;
  btnRedo.Enabled := PdfView1.FormCanRedo;
  if PdfView1.FormCanUndo then
    PdfView1.FormUndo;
end;

procedure TForm1.btnRedoClick(Sender: TObject);
begin
  if PdfView1.FormCanRedo then
    PdfView1.FormRedo;
end;

為自助服務終端鎖定檢視器

停用頁面導覽手勢

// All three default to True so existing apps upgrade with no behaviour
// change. Flip them False for kiosks, preview panes, or read-only
// embeddings without subclassing TPdfView
PdfView1.AllowUserPageChange       := False;  // PgUp/PgDn/Ctrl+Home/End
PdfView1.ChangePageOnMouseScrolling := False; // wheel up/down jumps
PdfView1.AllowUserTextSelection    := False;  // mouse/keyboard text selection

// Ctrl+wheel is reserved for zoom and is skipped by the navigation
// handler regardless of ChangePageOnMouseScrolling.

縮圖側邊面板

將 TPdfThumbnailView 連結至 TPdfView

// TPdfThumbnailView (unit FPdfThumbnail) renders one page thumbnail per
// row in a scrollable side panel
uses FPdfThumbnail;

procedure TForm1.FormCreate(Sender: TObject);
begin
  ThumbView.Pdf              := Pdf1;
  ThumbView.ThumbnailWidth   := 120;
  ThumbView.ThumbnailHeight  := 160;
  ThumbView.SelectionColor   := $00CFA85F;  // warm highlight
  ThumbView.OnPageClick      := ThumbViewPageClick;
end;

procedure TForm1.ThumbViewPageClick(Sender: TObject; PageIndex: Integer);
begin
  PdfView1.PageNumber := PageIndex + 1; // PageIndex is zero-based
end;

procedure TForm1.PdfView1PageNumberChanged(Sender: TObject);
begin
  ThumbView.CurrentPageIndex := PdfView1.PageNumber - 1;
end;

可協調式取消的批次匯出

具逐檔與逐頁進度的多檔 PDF 轉 JPG

// Pattern used by Demo\Delphi\BatchExport, Demo\CBuilder\BatchExport,
// and Demo\Lazarus\BatchExport. One TPdf per file, IPdfCancellationToken
// for cancel-from-UI, TPdfStreamAdapter for buffered file load
uses FPdfAsync;

procedure ExportBatch(const Files: TArray<string>;
  const OutDir: string; DPI: Integer; Quality: Integer;
  const ACancelToken: IPdfCancellationToken);
var
  I, J: Integer;
  Job:  TPdf;
  Bmp:  TBitmap;
  Jpeg: TJPEGImage;
begin
  for I := 0 to High(Files) do
  begin
    if ACancelToken.IsCancelled then Break;
    OverallProgress.Position := I;

    Job := TPdf.Create(nil);
    try
      Job.FileName := Files[I];
      Job.Active   := True;
      PageProgress.Max := Job.PageCount;

      for J := 1 to Job.PageCount do
      begin
        if ACancelToken.IsCancelled then Break;
        PageProgress.Position := J;

        Job.PageNumber := J;
        Bmp := Job.RenderPage(
          0, 0,
          Round(Job.PageWidth  * DPI / 72),
          Round(Job.PageHeight * DPI / 72));
        try
          Jpeg := TJPEGImage.Create;
          try
            Jpeg.CompressionQuality := Quality;
            Jpeg.Assign(Bmp);
            Jpeg.SaveToFile(Format('%s\%s.p%.3d.jpg',
              [OutDir, ExtractFileName(Files[I]), J]));
          finally
            Jpeg.Free;
          end;
        finally
          Bmp.Free;
        end;
      end;
    finally
      Job.Free;
    end;
  end;
end;

檢視器座標與命中測試

讓目前頁面符合,並檢查滑鼠下方的文字

procedure FitCurrentPage;
begin
  PdfView1.Zoom := PdfView1.PageZoom[PdfView1.PageNumber];
end;

procedure InspectViewerPoint(X, Y: Integer);
var
  PageNo: Integer;
  PageX, PageY: Double;
  CharIndex: Integer;
  Ch: WString;
begin
  PageNo := PdfView1.PageNumber;
  if not PdfView1.DeviceToPage(X, Y, PageNo, PageX, PageY) then
    Exit;

  CharIndex := PdfView1.CharacterIndexAtPos(X, Y, 8, 8);
  if CharIndex >= 0 then
  begin
    Ch := PdfView1.Text(CharIndex, 1);
    ShowMessage(Format('Page %d, X %.2f, Y %.2f, character %s',
      [PageNo, PageX, PageY, string(Ch)]));
  end;
end;

錯誤處理

穩健的 PDF 操作

function LoadPdfSafely(const FileName: string): Boolean;
begin
  Result := False;
  if not FileExists(FileName) then
    Exit;
  try
    Pdf1.FileName := FileName;
    Pdf1.Active := True;
    Result := Pdf1.Active;
  except
    Result := False;
  end;
end;

function ExtractTextSafely(PageNo: Integer): WString;
begin
  Result := '';
  if not Pdf1.Active then
    Exit;
  if (PageNo < 1) or (PageNo > Pdf1.PageCount) then
    Exit;
  Pdf1.PageNumber := PageNo;
  Result := Pdf1.Text;
end;

Tagged PDF 與無障礙

偵測 Tagged PDF 並走訪結構樹

// IsTagged is a cheap catalog probe — True when /StructTreeRoot exists
// StructureElements materialises the tree into a flat array of
// TPdfStructureElement records with type, title, alternate text, actual
// text, expansion text, language, level, parent index, and child / marked
// content / attribute counts
var
  Elements: TPdfStructureElements;
  I: Integer;
begin
  if not Pdf1.IsTagged then
  begin
    Log.Add('Document is NOT tagged — accessibility tools may struggle.');
    Exit;
  end;
  if Pdf1.Language = '' then
    Log.Add('Tagged PDF without /Lang — fails PDF/UA.');

  Elements := Pdf1.StructureElements;
  for I := 0 to High(Elements) do
    Log.Add(StringOfChar(' ', Elements[I].Level * 2)
      + Elements[I].StructType + ' "' + Elements[I].Title + '"');
end;

列印模式選擇

設定 EMF / PostScript / Image-Mask 列印模式

// SetPdfPrintMode is a global function that maps to PDFium's
// FPDF_SetPrintMode. Use the matching TPdfPrintMode value for the
// printer driver in use
SetPdfPrintMode(pmEmf);                   // GDI EMF (default)
// SetPdfPrintMode(pmTextOnly);           // text-only rendering
// SetPdfPrintMode(pmPostScript2);        // PostScript Level 2
// SetPdfPrintMode(pmPostScript3);        // PostScript Level 3
// SetPdfPrintMode(pmPostScript2PassThrough);
// SetPdfPrintMode(pmPostScript3PassThrough);
// SetPdfPrintMode(pmEmfImageMasks);      // EMF + image masks
// SetPdfPrintMode(pmPostScript3Type42);  // PostScript 3 + Type 42 fonts

讀取作者意圖的列印設定

// PrintCopies, PrintPageRanges, PrintScaling, and PrintPaperHandling are
// the viewer-preference dictionary entries — they encode what the
// document author wanted PDF readers to default to
var
  Ranges: TPrintPageRanges;
  Range: TPrintPageRange;
begin
  ShowMessage('Suggested copies: ' + IntToStr(Pdf1.PrintCopies));
  ShowMessage('Print scaling: ' + GetEnumName(TypeInfo(TPrintScaling),
    Ord(Pdf1.PrintScaling)));

  Ranges := Pdf1.PrintPageRanges; // one-based ranges
  for Range in Ranges do
    Log.Add(Format('Pages %d..%d', [Range.First, Range.Last]));

  if SetPdfPrintPaperHandlingDevMode(PrinterDevMode, Pdf1.PrintPaperHandling) then
    ApplyPrinterDevMode(PrinterDevMode);
end;

文件 JavaScript 動作

列舉名稱樹 JavaScript

// Document-level JavaScript is stored in the /Names /JavaScript name
// tree. JavaScriptAction[i] returns one entry; JavaScriptActions returns
// the full array
var
  I: Integer;
  Action: TPdfJavaScriptAction;
begin
  for I := 0 to Pdf1.JavaScriptActionCount - 1 do
  begin
    Action := Pdf1.JavaScriptAction[I];
    Memo1.Lines.Add('Name:   ' + string(Action.Name));
    Memo1.Lines.Add('Script: ' + string(Action.Script));
    Memo1.Lines.Add('---');
  end;
end;

效能技巧

最佳化 PDF 操作

procedure ProcessMultiplePages;
var
  I: Integer;
  PageText: WString;
begin
  for I := 1 to Pdf1.PageCount do
  begin
    Pdf1.PageNumber := I;
    PageText := Pdf1.Text;
    ProcessPageText(PageText);
  end;
end;

procedure SetOptimalRenderOptions;
begin
  PdfView1.Options := [reAnnotations, reLcd];
end;

// Zero-copy render path (default since v1.21.0): RenderPage writes
// directly into the destination TBitmap's DIB buffer when callers pass a
// pre-allocated bitmap, avoiding one width * height * 4 bytes copy
procedure RenderIntoCallerBitmap(Bitmap: TBitmap);
begin
  Bitmap.PixelFormat := pf32bit;
  Bitmap.SetSize(2480, 3508); // A4 @ 300 DPI
  Pdf1.RenderPage(Bitmap, 0, 0, Bitmap.Width, Bitmap.Height);
end;