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;
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;
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\FontProperties、Demo\CBuilder\FontProperties 與 Demo\Lazarus\FontProperties 專案會透過 TPdf 與 TPdfView 兩者展示相同的中繼資料。
Demo\Delphi\ContentExtractionLab、Demo\CBuilder\ContentExtractionLab 與 Demo\Lazarus\ContentExtractionLab 專案將內容擷取整合為單一工作流程。它們會開啟或建立 PDF,讓使用者選擇中繼資料、頁面文字、頁面物件、影像、附件、連結、註解、書籤以及字型 / 字元度量,然後產生摘要與詳細的 TXT 或 JSON 報告。此實驗室也包括全部 / 全不選類別選擇、報告複製到剪貼簿,以及來源 PDF 儲存動作,因此使用者可將產生的範例 PDF 與其擷取報告放在一起。較舊的 ExtractText、ExtractImages、Attachment 與 FontProperties 範例仍是聚焦式範例;ExtractText 支援頁面範圍與頁面分隔符選項,可在 Delphi、C++Builder 與 Lazarus 中產生逐頁文字輸出,而 ContentExtractionLab 則是跨類別的清單範例。
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;
// 若未進行扁平化,FormField[i] := value 只會寫入 /V 項目 —
// 可見的外觀來自 /AP 串流,可能不會重新整理。
// GenerateFormAppearances 會為每個控制項重新產生 /AP,接著
// FlattenAllPages 會將每頁的註解與控制項燒入永久且
// 不可編輯的內容。
Pdf1.FileName := 'C:\Application.pdf';
Pdf1.Active := True;
Pdf1.FormField[0] := 'John Smith';
Pdf1.FormField[1] := 'john@example.com';
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);
// 建立壓縮的 PDF 文件
Pdf1.CreateDocument;
Pdf1.Compressed := True; // 確保所有串流皆採用 FlateDecode 壓縮(預設)
Pdf1.Active := True;
Pdf1.AddPage(0, 595, 842); // A4 大小
Pdf1.AddText('Hello World!', 'Arial', 24, 100, 700, clBlack, $FF, 0.0);
Pdf1.SaveAs('C:\Compressed.pdf');
Pdf1.Active := False;
// 帶選項的儲存
Pdf1.SaveAs('C:\Output.pdf', saIncremental); // 增量儲存
Pdf1.SaveAs('C:\NoSecurity.pdf', saRemoveSecurity); // 移除安全性
// 儲存為特定 PDF 版本目標。PDF 版本驗證器會拒絕
// 任何在所選目標版本之後才引入的功能(JBIG2Decode、MarkInfo、
// AES 加密、Polygon / Caret / Watermark / Redact 註解 ...)。
if not Pdf1.SaveAs('C:\Legacy15.pdf', saNone, pv15) then
ShowMessage('Save rejected: document contains features newer than PDF 1.5');
// SaveAsPdfA 會以增量更新的方式對基本儲存進行後處理,
// 注入 XMP 中繼資料串流、sRGB ICC OutputIntent 與更新後的
// 文件 catalog,使結果符合 PDF/A-1b 規範。當未提供
// 自訂設定檔時,會自動使用隨附的 sRGB IEC61966-2.1 設定檔。
Pdf1.FileName := 'C:\Report.pdf';
Pdf1.Active := True;
if Pdf1.SaveAsPdfA('C:\Report.pdfa.pdf', pac1b) then
ShowMessage('Saved PDF/A-1b archive');
// 使用自訂 ICC 設定檔的 PDF/A(例如 CMYK 列印流水線)
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;
// 記憶體內 PDF/A 輸出
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;
var
Validation: TPdfAValidationResult;
Issue: TPdfAValidationIssue;
begin
Pdf1.FileName := 'C:\Incoming\report.pdf';
Pdf1.Active := True;
// 廉價的標頭風格探查 — 讀取合規層級而毋須走訪
// 完整驗證器。結果:pacNone、pac1a、pac1b、pac2b、pac3b 或
// 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;
// 帶逐問題診斷的完整驗證器
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;
Demo\Delphi\StandardsLab、Demo\CBuilder\StandardsLab 與 Demo\Lazarus\StandardsLab 專案會載入或建立 PDF,執行 ValidatePdfA、ValidatePdfUa、ValidatePdfE、ValidatePdfX、ValidatePdfR 與 ValidatePdfVT,然後在同一個表格中顯示每個偵測到的合規層級與問題計數。其儲存按鈕會呼叫 SaveAsPdfA、SaveAsPdfUa、SaveAsPdfE、SaveAsPdfX、SaveAsPdfR 與 SaveAsPdfVT,因此標記輸出工作流程毋須撰寫自訂測試框架即可見。
FPdfPreflightReport 單元將 ValidatePdfA、ValidatePdfUa、ValidatePdfE、ValidatePdfX、ValidatePdfR 與 ValidatePdfVT 包裝為單一報告物件。Demo\Delphi\PreflightReport、Demo\Lazarus\PreflightReport 與 Demo\CBuilder\PreflightReport 讓使用者選擇標準、產生摘要、檢查狀態、優先順序、問題類別、類別計數、下一步動作、問題代碼與建議動作,在文字、Markdown、JSON 與 CSV 之間切換預覽,並匯出 TXT、HTML、Markdown、JSON 或 CSV 報告。Delphi GUI 範例亦增加 Action Plan 預覽,將未通過的標準、優先順序、問題代碼與建議動作彙整為修補檢核清單。Demo\Delphi\PreflightReportCli 以主控台工作流程的形式公開相同的報告路徑,具備輸入 / 輸出引數、選用密碼、輸出格式選擇中新增 plan / actionplan、標準過濾、選用的單一檔案 attach=output.pdf 報告嵌入、failon= CI 閘控進入碼、batch=list.txt 處理、batchdir=folder 目錄掃描(支援選用遞迴與穩定的路徑排序處理)、outdir=reports 報告路由、針對重複輸入檔名的安全防衝突報告命名、含彙總與標準狀態總計的 TXT / JSON / HTML 摘要、CSV 列資訊清單(每個項目皆重複執行設定),以及為指令碼與 CI 工作而設的無引數範例模式。產生的報告會說明內建驗證器涵蓋標記層級與選定的檔案層級檢查,因此完整的內容層級 preflight 仍可委派給專用驗證引擎。
// LoadCustomDocument 讓 PDFium 可從任何可定位的 TStream
// 隨選取得區塊。串流不會複製至記憶體,因此數 GB 大小的 PDF、
// 遠端 HTTP 主體串流以及資料庫支援的來源,均可不需預先
// 緩衝即能正常運作。
var
FileStream: TFileStream;
begin
// 從檔案讀取的串流(不在記憶體中複製)
FileStream := TFileStream.Create('C:\Huge.pdf',
fmOpenRead or fmShareDenyWrite);
Pdf1.LoadCustomDocument(FileStream, True{AOwnsStream});
// ^^ 所有權已移轉 — Pdf1 會於 UnloadDocument 時釋放 FileStream。
Pdf1.Active := True;
ShowMessage('Pages: ' + IntToStr(Pdf1.PageCount));
end;
// 外部擁有的串流(呼叫端於 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 公開 IPdfCancellationToken /
// IPdfCancellationTokenSource,以提供可跨長時間執行的匯出與
// 繪製路徑運作的「由 UI 取消」語意。
uses FPdfAsync;
var
CancelSource: IPdfCancellationTokenSource;
CancelToken: IPdfCancellationToken;
begin
CancelSource := PdfCancellationTokenSource;
CancelToken := CancelSource.Token;
// ... 使用者點按取消 ...
CancelSource.Cancel;
// worker 會定期檢查 CancelToken.IsCancelled,或者繪製路徑
// 透過 PDFium 的 IFSDK_PAUSE 回呼進行輪詢。
if CancelToken.IsCancelled then
Memo1.Lines.Add('Cancelled by user');
end;
// RenderPageProgressive 讓長時間的高 DPI 繪製可在使用者
// 點按取消時於頁面中途中止,而非封鎖呼叫端直到完成。
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<T> 會在背景執行緒上啟動 worker,並將
// TPdfFutureResult<T> 信封傳回主執行緒。
//
// 重要:TPdf 會逐執行個體序列化繪製呼叫,但 worker 仍負責
// 排程與文件生命週期。請勿在繪製進行中變更 TPdf;批次匯出
// 通常每個檔案保留一個 TPdf。
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;
// FPdfMatrix 封裝 PDFium 的 FS_MATRIX,因此呼叫端可宣告式地
// 建立變換,並交給 FPDFPageObj_SetMatrix。
uses FPdfMatrix, FPdfView;
var
M: TPdfMatrix;
RawMatrix: FS_MATRIX;
PageObj: FPDF_PAGEOBJECT;
begin
M := TPdfMatrix.Create;
try
M.Translate(72, 200); // 向右 1 英吋,向上約 2.8 英吋
M.Scale(0.5, 0.5); // 半尺寸戳印
M.Rotate(15); // 逆時鐘旋轉 15 度
// M.HorizontalFlip;
// M.VerticalFlip;
// M.CentralFlip;
// M.Skew(10, 5);
// M.Multiply(Other); // 與另一個矩陣後乘
// 套用至任何 PDFium 頁面物件(文字 / 路徑 / 影像 / Form XObject)
RawMatrix := M.Handle;
FPDFPageObj_SetMatrix(PageObj, RawMatrix);
finally
M.Free;
end;
end;
// ImportPagesByIndex 會從來源 PDF 將明確的零起始索引
// 陣列複製到本文件。InsertAt = 0 會插入第 1 頁之前;
// PageCount 會附加。
var
Source: TPdf;
begin
Source := TPdf.Create(nil);
try
Source.FileName := 'C:\Report.pdf';
Source.Active := True;
// 從 Source 匯入頁面 0、2、4(一起始:頁面 1、3、5)
// 並附加於 Pdf1 的尾端。
Pdf1.ImportPagesByIndex(Source, [0, 2, 4], Pdf1.PageCount);
// 空陣列會匯入所有來源頁面
// Pdf1.ImportPagesByIndex(Source, [], 0);
finally
Source.Free;
end;
end;
// ImportNPagesToOne 會傳回一個全新的 TPdf,其頁面為本文件的
// NumX*NumY 組合。OutputWidth/Height 為 PDF 使用者單位
// (1 單位 = 1/72 英吋)。
var
Composite: TPdf;
begin
Pdf1.FileName := 'C:\Slides.pdf';
Pdf1.Active := True;
// 4-up A4 橫向:842 x 595 點,2 欄 x 2 列
Composite := Pdf1.ImportNPagesToOne(842, 595, 2, 2);
try
if Composite <> nil then
Composite.SaveAs('C:\Slides_4up.pdf');
finally
Composite.Free;
end;
end;
// MovePages 接受要移動頁面的零起始索引,加上移動完成後
// 第一個移動頁面的目標索引。其他頁面會在被移動的區塊
// 周圍位移,因此文件長度保持相同。
//
// 範例:於 5 頁文件中,頁面編號為 [0,1,2,3,4],
// MovePages([2, 3], 0) 會重新排列為 [2, 3, 0, 1, 4]。
if not Pdf1.MovePages([2, 3], 0) then
ShowMessage('Invalid indices passed to MovePages');
// CreateXObjectFromPage 會從任何其他 TPdf 的任何頁面建立可
// 重複使用的 Form XObject 控制代碼。可將該控制代碼戳印到本
// 文件的任意多個頁面 — 每個戳印都是單一 PDFium 頁面物件,
// 可透過矩陣 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;
// 將 WatermarkSource 的第 0 頁包裝為可重複使用的 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;
// 將浮水印置中於頁面
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; // 關閉 FPDF_XOBJECT 控制代碼
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(FileName) 接受任何以 VCL 或 LCL 圖形單元註冊的
// 格式(BMP、PNG 透過 PngImage、JPG ...)。
Pdf1.AddImage('C:\Photo.png', 100, 500, 200, 150);
// AddImage(TBitmap) 會略過 TPicture 中介,適合點陣圖已從
// 繪製或批次產生工作而來的情境。
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
// 於檢視器目前頁面的每個符合項目上繪製 clYellow 遮罩。
// 切換到不同頁面會自動清除標示。
PdfView1.HighlightColor := clYellow;
MatchCount := PdfView1.HighlightSearchText('invoice', False{Case}, True{Word});
Status.Caption := Format('%d match(es) on this page', [MatchCount]);
// 在頁面變更之前以程式化方式移除重疊
PdfView1.ClearHighlight;
end;
// FitMode 會在 Resize 或頁面變更期間讓長文件保持框架對齊。
// 直接設定 Zoom 會取消 FitMode(還原為 pfmNone)。
PdfView1.FitMode := pfmFitPage; // 整頁置於檢視區中
// PdfView1.FitMode := pfmFitWidth; // 頁面寬度符合檢視區寬度
// PdfView1.FitMode := pfmActualSize; // 100%
// PageColor 將繪製的 PDF 頁面與主控制項的 Color 解耦
// — 典型的深色模式檢視器設定會讓捲動區域保持深色,而
// PDF 頁面保持紙白色。
PdfView1.Color := clBlack; // 捲動區域
PdfView1.PageColor := clWhite; // PDF 頁面背景
// 選用的投影與頁面邊框,可將頁面從深色背景上突顯
PdfView1.PageShadowSize := 6;
PdfView1.PageShadowColor := $00404040;
PdfView1.PageBorderColor := clGray;
Demo\Delphi\ViewerInteractionLab、Demo\CBuilder\ViewerInteractionLab 與 Demo\Lazarus\ViewerInteractionLab 專案將現代 TPdfView 互動介面集中於同一個小型表單中:開啟或建立範例 PDF、切換 DisplayMode、套用 FitMode、變更 PageColor、旋轉頁面、使用 HighlightSearchText 標示目前頁面的搜尋符合項目、啟用或停用使用者文字選取、檢查 SelectedText、呼叫 SelectAll、CopySelectionToClipboard 與 ClearSelection,並在滑鼠移過頁面時於狀態列觀察 DeviceToPage 座標更新。
Demo\Delphi\SearchAndSelect、Demo\CBuilder\SearchAndSelect 與 Demo\Lazarus\SearchAndSelect 專案將搜尋與選取的檢視器工作流程轉化為獨立範例。它們會開啟或建立 PDF,列出全頁面搜尋結果(含頁碼、字元索引與預覽文字),支援上一個 / 下一個導覽以及連按兩下結果跳轉,使用 HighlightSearchText 標示目前頁面的符合項目,公開區分大小寫與整字選項,切換 AllowUserTextSelection,預覽 SelectedText,透過按鈕驅動 SelectAll、CopySelectionToClipboard 與 ClearSelection,並讓使用者於單頁與跨頁顯示模式之間切換。
Demo\Delphi\PrintPreferences、Demo\CBuilder\PrintPreferences 與 Demo\Lazarus\PrintPreferences 專案會在列印之前讀取作者列印偏好設定。它們會顯示 PrintCopies、PrintPageRanges、PrintScaling 與 PrintPaperHandling,可將作者提供的份數與範圍複製至列印輸入,並展示 SetPdfPrintPaperHandlingDevMode 如何將 PDF 雙面意圖對應為 Windows DEVMODE。
Demo\Delphi\PrintPDF、Demo\CBuilder\PrintPDF 與 Demo\Lazarus\PrintPDF 專案展示標準列印工作流程:開啟 PDF、預覽頁面、於平台列印對話方塊中選擇頁面範圍與份數、遵循自動分頁模式、透過 TPdf.RenderPage 繪製每頁,並公開帶取消功能的列印進度。Delphi 範例也接受拖放 PDF 檔案以快速載入。
Demo\Delphi\MultiPageViewer、Demo\CBuilder\MultiPageViewer 與 Demo\Lazarus\MultiPageViewer 專案會以一般頁面導覽與縮放工作流程執行連續、跨頁與感知封面的檢視。它們也適用於測試超大型 PDF 的開啟至首頁速度與繪製回應性:在大型檔案測試中,可能讓 Acrobat 或 Foxit 花費 20-30 秒才出現首次檢視的 2.x GB 文件,透過 PDFiumPas 檢視器路徑幾乎可立即可用。
Demo\Delphi\SplitView、Demo\CBuilder\SplitView 與 Demo\Lazarus\SplitView 專案展示並排 PDF 審閱。C++Builder 與 Lazarus 範例著重於兩窗格或三窗格比較、作用檢視選擇、選用的同步導覽命令、共用縮放預設,以及對選取檢視或所有已載入檢視的旋轉。可使用這些範例比較大型文件,同時讓首次可用檢視與後續頁面繪製保持回應性;可能讓 Acrobat 或 Foxit 花費 20-30 秒開啟的相同 2.x GB PDF 檔案,透過 PDFiumPas 檢視器路徑幾乎可立即可用。
Demo\Delphi\SecurityAudit、Demo\CBuilder\SecurityAudit 與 Demo\Lazarus\SecurityAudit 專案以唯讀方式開啟 PDF 並列出權限狀態、嵌入式附件、文件 JavaScript 動作、URI 與 Launch 連結註解、網頁連結、簽章摘要、XFA 狀態、V8 輔助可用性,以及未支援功能的回呼。相同的摘要與發現項目列可以 TXT 稽核報告儲存或複製,或儲存為結構化的 JSON。它們設計為主應用程式的風險面板,並非惡意程式偵測工具。
// LinkOptions 控管四種 PDF 連結動作類型的自動處理。
// 預設只啟用 goto 與 URI;launch(執行程式)與
// embedded GotoR(跳轉至另一個檔案)保持關閉,因此誤觸
// 點按即無法執行任意程式碼。
PdfView1.LinkOptions :=
[loAutoGoto, loAutoOpenURI]; // 安全的預設值
// OnAnnotationLinkClick 會於文件內目的地(頁面跳轉、
// 具名目的地、動作鏈)觸發。將 Handled 標記為 True,
// 即可防止檢視器自動跟隨連結。
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; // 抑制自動處理
end;
end;
// OnWebLinkClick 會於 PDFium 的網頁連結掃描器探測到的
// URI 字串(在文字串流中自動偵測到的 URL,即便沒有
// /A <</S/URI...>> 註解)觸發。
procedure TForm1.PdfView1WebLinkClick(Sender: TObject;
WebLinkIndex: Integer; const Url: WString;
var Handled: Boolean);
begin
Log.Add('Web link clicked: ' + string(Url));
end;
// 於獲得焦點的 AcroForm 控制項上,六個新方法會驅動 PDFium 的
// 表單編輯歷程。若沒有控制項擁有焦點或文件未帶 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;
// 以上三者皆預設為 True,因此既有應用程式升級時行為不變。
// 將其設為 False 即可在不繼承 TPdfView 的情況下鎖定
// 自助服務終端、預覽窗格或唯讀嵌入。
PdfView1.AllowUserPageChange := False; // PgUp/PgDn/Ctrl+Home/End
PdfView1.ChangePageOnMouseScrolling := False; // 滾輪上下跳頁
PdfView1.AllowUserTextSelection := False; // 滑鼠/鍵盤文字選取
// Ctrl+滾輪保留給縮放使用,無論 ChangePageOnMouseScrolling
// 為何,導覽處理常式皆會略過。
// TPdfThumbnailView(單元 FPdfThumbnail)會在可捲動的
// 側邊面板中每列繪製一個頁面縮圖。
uses FPdfThumbnail;
procedure TForm1.FormCreate(Sender: TObject);
begin
ThumbView.Pdf := Pdf1;
ThumbView.ThumbnailWidth := 120;
ThumbView.ThumbnailHeight := 160;
ThumbView.SelectionColor := $00CFA85F; // 溫暖色標示
ThumbView.OnPageClick := ThumbViewPageClick;
end;
procedure TForm1.ThumbViewPageClick(Sender: TObject; PageIndex: Integer);
begin
PdfView1.PageNumber := PageIndex + 1; // PageIndex 為零起始
end;
procedure TForm1.PdfView1PageNumberChanged(Sender: TObject);
begin
ThumbView.CurrentPageIndex := PdfView1.PageNumber - 1;
end;
// Demo\Delphi\BatchExport、Demo\CBuilder\BatchExport 與
// Demo\Lazarus\BatchExport 所使用的模式。每個檔案一個 TPdf、
// IPdfCancellationToken 用於「由 UI 取消」、TPdfStreamAdapter
// 用於緩衝檔案載入。
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;
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;
// IsTagged 為廉價的 catalog 探查 — 當存在 /StructTreeRoot 時為 True。
// StructureElements 會將樹狀結構具體化為扁平陣列,內含
// TPdfStructureElement 記錄,具備類型、標題、替代文字、實際
// 文字、展開文字、語言、層級、父項索引以及子項 / 標記內容 /
// 屬性計數。
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;
// SetPdfPrintMode 是一個全域函式,對應至 PDFium 的
// FPDF_SetPrintMode。請依使用中的印表機驅動程式選擇相符的
// TPdfPrintMode 值。
SetPdfPrintMode(pmEmf); // GDI EMF(預設)
// SetPdfPrintMode(pmTextOnly); // 純文字繪製
// SetPdfPrintMode(pmPostScript2); // PostScript Level 2
// SetPdfPrintMode(pmPostScript3); // PostScript Level 3
// SetPdfPrintMode(pmPostScript2PassThrough);
// SetPdfPrintMode(pmPostScript3PassThrough);
// SetPdfPrintMode(pmEmfImageMasks); // EMF + 影像遮罩
// SetPdfPrintMode(pmPostScript3Type42); // PostScript 3 + Type 42 字型
// PrintCopies、PrintPageRanges、PrintScaling 與
// PrintPaperHandling 是檢視器偏好設定字典項目 — 它們會編碼
// 文件作者希望 PDF 閱讀器採用的預設值。
var
Ranges: TPrintPageRanges;
Range: TPrintPageRange;
begin
ShowMessage('Suggested copies: ' + IntToStr(Pdf1.PrintCopies));
ShowMessage('Print scaling: ' + GetEnumName(TypeInfo(TPrintScaling),
Ord(Pdf1.PrintScaling)));
Ranges := Pdf1.PrintPageRanges; // 一起始的範圍
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 儲存於 /Names /JavaScript 名稱樹中。
// JavaScriptAction[i] 會傳回一個項目;JavaScriptActions 則
// 傳回完整陣列。
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;
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;
// 零複製繪製路徑(自 v1.21.0 起為預設):當呼叫端傳入預先
// 配置的點陣圖時,RenderPage 會直接寫入目標 TBitmap 的
// DIB 緩衝區,避免一份 width * height * 4 位元組的複本。
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;