PDFium Delphi Component 文件

PDFium VCL 编程示例

选择一个可运行的 demo

demo 集合共 32 个主题:32 个 Delphi 工程、31 个 C++Builder 工程和 32 个 Lazarus 工程,合计 95 个原生工程。每个平台的主目录保留 22 个完整工作流 demo。十个聚焦主题位于 Miscellaneous 下,三个平台合计 29 个原生工程;XfaLab 在 Delphi 和 Lazarus 中提供

先打开主目录里的工作流:PDFViewer 做特性导览,CreatePDF 做实时创作,ContentExtractionLab 做即时清点,FormFill 做可编辑表单,StandardsLab 做校验与转换,IntakeWorkbenchCli 做自动化收录。这六个入口立刻就能跑出可用结果;下面的聚焦型示例适合需要深入考察某个 API 时使用

主工作流 demo

Demo用途与相关 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对既有页面重复施加叠加内容摆放位置与批量页面盖章是对基础创作 API 的补充
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 写入与重载检查已由创作和提取工作流覆盖,不再单设 demo 工程

Demo用途与相关 demo 的差异
SecurityAudit交互式检查安全姿态GUI 侧重权限、脚本、签名和安全细节,而非文件路由
XfdfLab导入和导出 XFDF 数据交换加导入后 PDF 的显式保存演示了持久化;仅导出 XFDF 并不会保存修改过的 PDF
AccessibilityLab在控制台里走查 reader API紧凑的可执行走查,展示阅读单元、词框、结构和语音脚本输出
ExtractText从选定页面导出完整文本文本层输出、页面分隔符和取消操作有别于清点预览与表格检测;不执行 OCR
FontProperties检查字体与字符指标打开即显示第一个文本对象的真实字体与字符指标;选中另一行即可比较字体,无需导出文档载荷
NavigationLab书签、命名目标和导航动作紧凑的 API 走查,与 PDFViewer 中可见的导航控件互补
PDFtoJpg把单个文档渲染成页面图像紧凑的单文档渲染示例;Delphi 版支持离散页范围,BatchExport 则面向多个输入文档
PrintPreferencesN-up 拼版和 PDF 作者打印首选项份数、奇偶页过滤和作者提供的首选项是教学重点;它是 PrintPDF 的配套样例
ViewerInteractionLab诊断鼠标、键盘和坐标交互交互日志加上页面/设备坐标转换,让它成为一件 API 诊断工具
XfaLab静态 XFA packet 与运行时能力用普通运行时打开模板与数据集 packet;交互式 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 从真实的模板与数据集 packet 起步做静态 XFA 检视;交互式 XFA 渲染需要合适的运行时,属于另一项能力

每个查看、表单、提取和导航工程都自带 PDFiumDemoShowcase.pdf。复制整个工程目录,把这个内置输入保留在分发的可执行文件旁边即可;Lazarus 构建会自动把它摆到位。导览完全离线可用;本地 PDF 缺失时,常规查看器和提取样例也会提供生成的回退内容

复制和维护原生样例

每个原生 demo 都自带自己的示例实现。AccessibilityLab、AccessibilityWorkbenchCli、IntakeWorkbenchCli 和 PreflightReportCli 的 Delphi 与 Lazarus 入口把完整工作流直接放进工程目录,包括编译器相关的初始化和 JPEG 写入。附件、打印首选项和 XFA 示例也会在本地构建各自特性专用的输入数据。复制出去的工程之外,只需要库本身、对应的原生运行时和常规的编译器框架依赖

需要嵌入位图时选图像对象提取,需要可见页面时选页面渲染,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 报告。Lab 还提供全选/全清类别选择、报告复制到剪贴板、源 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;

把已填写的表单值扁平化进页面

// 不做扁平化时,FormField[i] := value 仅写入 /V 条目 ——
// 实际可见外观来自 /AP 流,可能不会刷新
// GenerateFormAppearances 为每个控件重新生成 /AP,
// FlattenAllPages 再把每页的注释和控件烘焙成永久、不可编辑的内容
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);

保存与压缩

保存并压缩

// 创建一份压缩后的 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');

创建 PDF/A-1b 归档输出

// SaveAsPdfA 会在基础保存之上做后处理,追加一个增量更新:
// 注入 XMP 元数据流、sRGB ICC OutputIntent 和更新后的
// 文档 catalog,使结果符合 PDF/A-1b。当未提供自定义
// ICC 配置文件时,会自动使用随附的 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;

校验 PDF/A 合规性

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

  // 轻量级 header 风格的探测 —— 读取合规级别,
  // 不会迭代完整校验器。结果: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;

在 StandardsLab 中检查多种 PDF 标准

Demo\Delphi\StandardsLab、Demo\CBuilder\StandardsLab 和 Demo\Lazarus\StandardsLab 工程会加载或新建一份 PDF,依次运行 ValidatePdfA、ValidatePdfUa、ValidatePdfE、ValidatePdfX、ValidatePdfR 和 ValidatePdfVT,并在一个网格里展示每项检测到的合规级别与问题数。它们的 Save 按钮会分别调用 SaveAsPdfA、SaveAsPdfUa、SaveAsPdfE、SaveAsPdfX、SaveAsPdfR 和 SaveAsPdfVT,使你无需自写 harness 就能看清标识符输出工作流

生成 Preflight 报告

FPdfPreflightReport 单元把 ValidatePdfA、ValidatePdfUa、ValidatePdfE、ValidatePdfX、ValidatePdfR 和 ValidatePdfVT 封装成一个可查询的报告对象,提供 Info / Warning / Error 严重级别、状态与问题聚合、查找、搜索、过滤以及稳定的 SHA-256 指纹。报告可以输出为 text、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 让 PDFium 从任何可 seek 的 TStream 按需
// 读取数据块。流不会被复制到内存里,因此多 GB PDF、远程
// HTTP body 流以及数据库支持的来源都无需预先缓冲就能使用
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;

  // ... 用户点击 Cancel
  CancelSource.Cancel;

  // worker 周期性检查 CancelToken.IsCancelled,
  // 渲染路径则通过 PDFium 的 IFSDK_PAUSE 回调进行轮询
  if CancelToken.IsCancelled then
    Memo1.Lines.Add('Cancelled by user');
end;

可取消的渐进式页面渲染

// RenderPageProgressive 让高 DPI 的长耗时渲染在用户点击 Cancel 时
// 可以中途取消,而不是阻塞调用方直到完成
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> 在后台线程上启动 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;

变换矩阵

组合 Translate / Scale / Rotate / Skew

// 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;

N-up 拼版输出

// ImportNPagesToOne 返回一份新的 TPdf,其页面是
// 本文档若干页按 NumX*NumY 的复合页。OutputWidth/Height
// 单位为 PDF 用户单位(1 单位 = 1/72 in)
var
  Composite: TPdf;
begin
  Pdf1.FileName := 'C:\Slides.pdf';
  Pdf1.Active   := True;
  // 4-up A4 横向:842 x 595 pt,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 接收要移动的页面零基索引,以及移动完成后
// 第一个被移动页面的目标索引。其他页面会在被移动块周围
// 移位,使文档长度保持不变
//
// 例:在页面编号为 [0,1,2,3,4] 的 5 页文档中,
// MovePages([2, 3], 0) 会重排为 [2, 3, 0, 1, 4]
if not Pdf1.MovePages([2, 3], 0) then
  ShowMessage('Invalid indices passed to MovePages');

通过 Form XObject 页面复用做水印

// 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 重载)

添加已注册的图像文件或 TBitmap

// AddImage(FileName) 接收 VCL 或 LCL 图形单元中注册的任何格式
// (BMP、PNG via PngImage、JPG 等)
Pdf1.AddImage('C:\Photo.png', 100, 500, 200, 150);

// AddImage(TBitmap) 跳过 TPicture 中间层,
// 适合调用方已经手握来自渲染或批量生成的 TBitmap
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;

查看器布局与页面颜色

Fit 模式与页面背景

// 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;

在 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、预览页面、在平台打印对话框中选择页面范围与份数、尊重 collate 模式、通过 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 样例聚焦于二/三面板对比、活动视图选择、可选的同步导航命令、共享缩放预设以及对所选视图或全部已加载视图的旋转。可用它们对比大型文档,同时保持首屏可用视图和后续翻页渲染的响应;同样的 2.x GB PDF 文件在 Acrobat 或 Foxit 中可能需要 20-30 秒才能打开,但通过 PDFium Delphi Component 查看器路径几乎立即可用

在 SecurityAudit 中审计 PDF 风险面

Demo\Delphi\Miscellaneous\SecurityAudit、Demo\CBuilder\Miscellaneous\SecurityAudit 和 Demo\Lazarus\Miscellaneous\SecurityAudit 工程以只读方式打开 PDF,并列出权限状态、嵌入附件、文档级 JavaScript 动作、URI 与 Launch 链接注释、Web 链接、签名汇总、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 的 Web 链接扫描器
// 发现的 URI 字符串触发(即便没有 /A <</S/URI...>> 注释,
// 也能自动检出文本流中的 URL)
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 与 TPdfView 绑定

// 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;

协作式可取消批量导出

多文件 PDF 到 JPG,含逐文件与逐页进度

// 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;

错误处理

稳健的 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 是一个轻量级 catalog 探测 —— 当 /StructTreeRoot 存在时为 True
// StructureElements 把树物化为 TPdfStructureElement 记录的扁平数组,
// 含 type、title、alternate text、actual text、expansion text、language、
// level、parent index 以及 child / marked content / attribute 计数
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 是一个全局函数,映射到 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 + image masks
// 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 动作

枚举名树中的 JavaScript

// 文档级 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;

性能提示

优化 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;

// 零拷贝渲染路径(自 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;