Документация PDFiumPas

Примеры программирования PDFium VCL

Базовые операции с документами

Загрузка и отображение 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\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');

Создание архивного вывода PDF/A-1b

// SaveAsPdfA постобрабатывает базовое сохранение инкрементальным
// обновлением, которое внедряет поток метаданных XMP, ICC OutputIntent
// sRGB и обновлённый 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');

// PDF/A с пользовательским ICC-профилем (например, конвейеры печати 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;

  // Дешёвая проверка стиля «заголовок» — читает соответствие без
  // итерации полного валидатора. Результат: 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;

Изучение нескольких стандартов PDF в StandardsLab

Проекты 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, кодами выхода для CI-гейтов failon=, обработкой batch=list.txt, сканированием каталога batchdir=folder с опциональной рекурсией и устойчивой обработкой с сортировкой путей, маршрутизацией отчётов outdir=reports, безопасными от коллизий именами отчётов для дублирующихся имён входных файлов, сводками TXT / JSON / HTML с агрегатными и стандартными итогами статусов, манифестами строк CSV с настройками запуска, повторяющимися для каждого элемента, и режимом образца без аргументов для скриптов и CI-задач. Сгенерированный отчёт указывает, что встроенные валидаторы покрывают проверки маркерного уровня и выбранные проверки уровня файла, поэтому полная препроверка уровня содержимого по-прежнему может быть делегирована специализированному движку валидации.

Потоковая загрузка и большие документы

Загрузка PDF по требованию с LoadCustomDocument

// LoadCustomDocument позволяет PDFium забирать блоки из любого
// позиционируемого TStream по требованию. Поток НЕ копируется в память,
// поэтому многогигабайтные PDF, удалённые потоки тел HTTP и БД-источники
// все жизнеспособны без предварительного буфера.
var
  FileStream: TFileStream;
begin
  // Потоковое чтение из файла (без копии в памяти)
  FileStream := TFileStream.Create('C:\Huge.pdf',
    fmOpenRead or fmShareDenyWrite);
  Pdf1.LoadCustomDocument(FileStream, True{AOwnsStream});
  // ^^ передача владения — Pdf1 освобождает FileStream при UnloadDocument.
  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 для семантики «отмена из интерфейса»,
// которая работает в длительных путях экспорта и рендеринга.
uses FPdfAsync;

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

  // ... пользователь нажимает «Отмена» ...
  CancelSource.Cancel;

  // Worker периодически проверяет CancelToken.IsCancelled, либо путь
  // рендеринга опрашивает его через обратный вызов IFSDK_PAUSE PDFium.
  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

// 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 оборачивает FS_MATRIX из PDFium, чтобы вызывающие могли
// декларативно построить преобразование и передать его в
// 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;

    // Импортировать страницы 0, 2, 4 (от единицы: страницы 1, 3, 5) из Source
    // и добавить их в конец 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 дюйма).
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 принимает нулевые индексы перемещаемых страниц плюс индекс
// назначения для первой перемещённой страницы после завершения перемещения.
// Другие страницы сдвигаются вокруг перемещаемого блока, благодаря чему
// длина документа остаётся прежней.
//
// Пример: в 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');

Водяные знаки через повторное использование страниц Form XObject

// CreateXObjectFromPage создаёт переиспользуемый дескриптор Form XObject
// из любой страницы любого другого TPdf. Штампуйте этот дескриптор на
// столько страниц ЭТОГО документа, сколько нужно — каждый штамп
// представляет собой один объект страницы 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;

    // Обернуть страницу 0 из WatermarkSource как переиспользуемый 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 через 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 удерживает длинные документы в кадре при изменении размера
// или смене страницы. Прямая установка 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\ViewerInteractionLab, Demo\CBuilder\ViewerInteractionLab и Demo\Lazarus\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 с кнопок и позволяют пользователям переключаться между одиночными и развёрнутыми режимами отображения.

Чтение настроек печати PDF в PrintPreferences

Проекты Demo\Delphi\PrintPreferences, Demo\CBuilder\PrintPreferences и Demo\Lazarus\PrintPreferences читают настройки печати автора перед печатью. Они отображают PrintCopies, PrintPageRanges, PrintScaling и PrintPaperHandling, могут копировать предоставленные автором копии и диапазоны во входные параметры печати и показывают, как SetPdfPrintPaperHandlingDevMode отображает намерение дуплекса PDF в Windows DEVMODE.

Стандартная печать PDF в PrintPDF

Проекты 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: в тестах с большими файлами документы по 2+ ГБ, на которые Acrobat или Foxit могут тратить 20–30 секунд до первого просмотра, могут стать пригодными к использованию практически мгновенно через путь просмотрщика PDFiumPas.

Параллельный обзор в SplitView

Проекты Demo\Delphi\SplitView, Demo\CBuilder\SplitView и Demo\Lazarus\SplitView показывают параллельный обзор PDF. Образцы C++Builder и Lazarus посвящены сравнению двух или трёх панелей, выбору активного представления, опциональным синхронизированным командам навигации, общим пресетам масштабирования и повороту для выбранного представления или всех загруженных представлений. Используйте эти образцы для сравнения больших документов, сохраняя при этом отзывчивость первого пригодного просмотра и последующего рендеринга страниц; те же PDF-файлы по 2+ ГБ, на открытие которых Acrobat или Foxit могут тратить 20–30 секунд, могут стать пригодными к использованию практически мгновенно через путь просмотрщика PDFiumPas.

Аудит поверхности рисков PDF в SecurityAudit

Проекты Demo\Delphi\SecurityAudit, Demo\CBuilder\SecurityAudit и Demo\Lazarus\SecurityAudit открывают PDF только для чтения и перечисляют состояние разрешений, встроенные вложения, действия JavaScript документа, аннотации-ссылки URI и Launch, веб-ссылки, сводки подписей, состояние XFA, доступность помощников V8 и обратные вызовы неподдерживаемых функций. Те же строки сводки и находок можно сохранить или скопировать как аудиторский отчёт TXT или сохранить как структурированный JSON. Они предназначены как панели рисков приложения-хоста, а не как обнаружение вредоносных программ.

Обработка ссылок в просмотрщике

Безопасный переход по клику со списками разрешённых действий

// LinkOptions управляет автоматической обработкой четырёх типов
// действий PDF-ссылок. По умолчанию включены только goto и URI; запуск
// программы (loAutoLaunch) и встроенный 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 срабатывает для строк URI, обнаруженных сканером
// веб-ссылок PDFium (автообнаруженные 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;

Редактирование виджетов формы в просмотрщике

Выделение и Undo / Redo

// На сфокусированном виджете 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.PdfView1PageNumberИзменено(Sender: TObject);
begin
  ThumbView.CurrentPageIndex := PdfView1.PageNumber - 1;
end;

Кооперативно отменяемый пакетный экспорт

Многофайловое преобразование PDF в JPG с прогрессом по файлу и по странице

// Шаблон, используемый Demo\Delphi\BatchExport, Demo\CBuilder\BatchExport
// и Demo\Lazarus\BatchExport. Один TPdf на файл, IPdfCancellationToken
// для «отмены из интерфейса», 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 — дешёвая проверка каталога — True, когда существует
// /StructTreeRoot. 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;

Выбор режима печати

Установка режима печати EMF / PostScript / маска изображения

// SetPdfPrintMode — глобальная функция, которая отображается в
// FPDF_SetPrintMode PDFium. Используйте подходящее значение 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 уровня документа

Перечисление 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
// пишет напрямую в буфер DIB назначения TBitmap, когда вызывающие
// передают предварительно выделенное растровое изображение, избегая
// одной копии размером ширина * высота * 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;