PDFium Component Docs

PDFium-VCL-Programmierbeispiele

Grundlegende Dokumentoperationen

Laden und Anzeigen eines PDF-Dokuments

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;

Neues PDF-Dokument erstellen

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;

Textoperationen

Text aus PDF extrahieren

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;

Text suchen

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;

Text mit Formatierung hinzufügen

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;

Zeichen- und Schriftmetadaten untersuchen

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;

Die Projekte Demo\Delphi\FontProperties, Demo\CBuilder\FontProperties und Demo\Lazarus\FontProperties zeigen dieselben Metadaten sowohl über TPdf als auch über TPdfView.

Mehrkategorigen Inhalts-Extraktionsbericht erstellen

Die Projekte Demo\Delphi\ContentExtractionLab, Demo\CBuilder\ContentExtractionLab und Demo\Lazarus\ContentExtractionLab machen aus der Inhaltsextraktion einen einzelnen Workflow. Sie öffnen oder erstellen ein PDF, lassen Benutzer Metadaten, Seitentext, Seitenobjekte, Bilder, Anhänge, Links, Annotationen, Lesezeichen sowie Schrift-/Zeichenmetriken auswählen und generieren dann eine Zusammenfassung sowie einen detaillierten TXT- oder JSON-Bericht. Das Lab enthält außerdem Alle-/Keine-Kategorieauswahl, Bericht-in-Zwischenablage-Kopieren und Aktionen zum Speichern der Quell-PDF, sodass Anwender das generierte Sample-PDF neben dem zugehörigen Extraktionsbericht aufbewahren können. Die älteren Demos ExtractText, ExtractImages, Attachment und FontProperties bleiben fokussierte Beispiele; ExtractText unterstützt Seitenbereich- und Seitentrennzeichen-Optionen zur Erzeugung seitenweise getrennter Textausgabe in Delphi, C++Builder und Lazarus, während ContentExtractionLab das übergreifende Bestandsbeispiel ist.

Formularoperationen

Mit Formularfeldern arbeiten

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;

Ausgefüllte Formularwerte in die Seite einbacken

// Ohne Flattening schreibt FormField[i] := value nur den /V-Eintrag —
// das sichtbare Erscheinungsbild stammt aus dem /AP-Stream und wird
// möglicherweise nicht aktualisiert. GenerateFormAppearances regeneriert
// /AP für jedes Widget; anschließend backt FlattenAllPages die Annotationen
// und Widgets jeder Seite dauerhaft als nicht editierbaren Inhalt ein.
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;

Bildoperationen

Bilder extrahieren

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;

Bilder hinzufügen

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;

Annotationsoperationen

Mit Annotationen arbeiten

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;

Zeichenoperationen

Formen zeichnen

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;

Navigation und Lesezeichen

Mit Lesezeichen arbeiten

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;

Mit benannten Zielorten arbeiten

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;

Anhangoperationen

Mit Anhängen arbeiten

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;

Seitenverwaltung

Seitenoperationen

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

Speichern und Komprimierung

Mit Komprimierung speichern

// Komprimiertes PDF-Dokument erstellen
Pdf1.CreateDocument;
Pdf1.Compressed := True; // sicherstellen, dass alle Streams FlateDecode-komprimiert sind (Standard)
Pdf1.Active := True;
Pdf1.AddPage(0, 595, 842); // A4-Größe
Pdf1.AddText('Hello World!', 'Arial', 24, 100, 700, clBlack, $FF, 0.0);
Pdf1.SaveAs('C:\Compressed.pdf');
Pdf1.Active := False;

// Mit Optionen speichern
Pdf1.SaveAs('C:\Output.pdf', saIncremental); // inkrementelles Speichern
Pdf1.SaveAs('C:\NoSecurity.pdf', saRemoveSecurity); // Sicherheit entfernen

// Auf eine bestimmte PDF-Zielversion speichern. Die PDF-Versionsprüfung lehnt
// jede Funktion ab, die nach dem gewählten Ziel eingeführt wurde (JBIG2Decode,
// MarkInfo, AES-Verschlüsselung, Polygon-/Caret-/Watermark-/Redact-Annotationen, ...).
if not Pdf1.SaveAs('C:\Legacy15.pdf', saNone, pv15) then
  ShowMessage('Save rejected: document contains features newer than PDF 1.5');

PDF/A-1b-Archivausgabe erstellen

// SaveAsPdfA ergänzt den Basis-Speichervorgang durch ein inkrementelles Update,
// das einen XMP-Metadaten-Stream, ein sRGB-ICC-OutputIntent und einen
// aktualisierten Dokument-Catalog einfügt, sodass das Ergebnis PDF/A-1b-konform
// ist. Das mitgelieferte sRGB-IEC61966-2.1-Profil wird automatisch verwendet,
// wenn kein benutzerdefiniertes Profil bereitgestellt wird.
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 mit einem benutzerdefinierten ICC-Profil (z. B. CMYK-Druckpipelines)
var
  Opts: TPdfASaveOptions;
begin
  Opts := Default(TPdfASaveOptions);
  Opts.Conformance    := pac1b;
  Opts.IccProfileData := TFile.ReadAllBytes('C:\Profiles\Coated_GRACoL_2006.icc');
  Pdf1.SaveAsPdfA('C:\Report.cmyk.pdfa.pdf', Opts);
end;

// In-Memory-PDF/A-Ausgabe
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-Konformität validieren

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

  // Günstige Header-Stil-Prüfung — liest die Konformität, ohne den vollen
  // Validator zu durchlaufen. Ergebnis: pacNone, pac1a, pac1b, pac2b, pac3b
  // oder 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;

  // Vollständiger Validator mit detaillierten Befunden pro Issue
  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;

Mehrere PDF-Standards in StandardsLab untersuchen

Die Projekte Demo\Delphi\StandardsLab, Demo\CBuilder\StandardsLab und Demo\Lazarus\StandardsLab laden oder erstellen ein PDF, führen ValidatePdfA, ValidatePdfUa, ValidatePdfE, ValidatePdfX, ValidatePdfR und ValidatePdfVT aus und zeigen dann jede erkannte Konformitätsstufe und die Issue-Anzahl in einer Tabelle an. Ihre Speichern-Schaltflächen rufen SaveAsPdfA, SaveAsPdfUa, SaveAsPdfE, SaveAsPdfX, SaveAsPdfR und SaveAsPdfVT auf, sodass der Marker-Output-Workflow sichtbar wird, ohne ein eigenes Test-Harness schreiben zu müssen.

Preflight-Berichte erzeugen

Die Unit FPdfPreflightReport fasst ValidatePdfA, ValidatePdfUa, ValidatePdfE, ValidatePdfX, ValidatePdfR und ValidatePdfVT zu einem einzigen Berichtsobjekt zusammen. Demo\Delphi\PreflightReport, Demo\Lazarus\PreflightReport und Demo\CBuilder\PreflightReport lassen Benutzer Standards auswählen, eine Zusammenfassung erzeugen, Status, Priorität, Issue-Kategorien, Kategoriezähler, nächste Aktionen, Issue-Codes und empfohlene Aktionen prüfen, die Vorschau zwischen Text, Markdown, JSON und CSV umschalten und TXT-, HTML-, Markdown-, JSON- oder CSV-Berichte exportieren. Die Delphi-GUI-Beispielanwendung fügt außerdem eine Action Plan-Vorschau hinzu, die nicht bestandene Standards, Prioritäten, Issue-Codes und Empfehlungen in eine Maßnahmenliste zur Fehlerbehebung verdichtet. Demo\Delphi\PreflightReportCli stellt denselben Berichtspfad als Konsolen-Workflow mit Input-/Output-Argumenten, optionalem Passwort, Auswahl des Ausgabeformats einschließlich plan / actionplan, Standardfilterung, optionalem Single-File-attach=output.pdf-Berichtsembedding, failon=-CI-Gating-Exitcodes, batch=list.txt-Verarbeitung, batchdir=folder-Verzeichnis-Scannen mit optionaler Rekursion und stabiler pfad-sortierter Verarbeitung, outdir=reports-Berichtsrouting, kollisionssicheren Berichtsnamen für doppelte Eingabedateinamen, TXT-/JSON-/HTML-Zusammenfassungen mit Aggregat- und Standard-Status-Summen, CSV-Zeilenmanifesten mit pro Element wiederholten Run-Einstellungen und einem Sample-Modus ohne Argumente für Skripte und CI-Jobs bereit. Der erzeugte Bericht weist darauf hin, dass die integrierten Validatoren marker-bezogene und ausgewählte file-level Prüfungen abdecken, sodass vollständige content-level Preflights weiterhin an eine dedizierte Validierungs-Engine delegiert werden können.

Streaming-Laden und große Dokumente

Ein PDF bedarfsweise mit LoadCustomDocument laden

// LoadCustomDocument lässt PDFium Blöcke von einem beliebigen seekbaren
// TStream bedarfsweise abrufen. Der Stream wird NICHT in den Speicher
// kopiert, sodass mehrere Gigabyte große PDFs, Remote-HTTP-Body-Streams
// und datenbankgestützte Quellen alle ohne vorab erstellten Puffer
// nutzbar sind.
var
  FileStream: TFileStream;
begin
  // Streaming aus Datei lesen (keine Speicherkopie)
  FileStream := TFileStream.Create('C:\Huge.pdf',
    fmOpenRead or fmShareDenyWrite);
  Pdf1.LoadCustomDocument(FileStream, True{AOwnsStream});
  // ^^ Eigentum übertragen — Pdf1 gibt FileStream bei UnloadDocument frei.
  Pdf1.Active := True;
  ShowMessage('Pages: ' + IntToStr(Pdf1.PageCount));
end;

// Extern besitzter Stream (Aufrufer gibt ihn nach UnloadDocument frei)
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;

Async-Abbruch und progressives Rendering

Kooperative Cancellation-Tokens

// FPdfAsync stellt IPdfCancellationToken /
// IPdfCancellationTokenSource für Cancel-from-UI-Semantik bereit, die
// über die langlaufenden Export- und Render-Pfade hinweg funktioniert.
uses FPdfAsync;

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

  // ... Benutzer klickt auf Abbrechen ...
  CancelSource.Cancel;

  // Der Worker prüft regelmäßig CancelToken.IsCancelled, oder der Render-
  // Pfad fragt es über PDFiums IFSDK_PAUSE-Callback ab.
  if CancelToken.IsCancelled then
    Memo1.Lines.Add('Cancelled by user');
end;

Abbrechbares progressives Seitenrendering

// RenderPageProgressive ermöglicht es einem langen High-DPI-Rendering,
// mitten in der Seite abzubrechen, wenn der Benutzer Abbrechen klickt,
// anstatt den Aufrufer bis zum Abschluss zu blockieren.
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;

Renderings mit TPdfFuture auf einem Hintergrund-Thread ausführen

// TPdfFuture<T> startet einen Worker auf einem Hintergrund-Thread und
// sendet einen TPdfFutureResult<T>-Envelope zurück an den Haupt-Thread.
//
// Wichtig: TPdf serialisiert Render-Aufrufe pro Instanz, doch der Worker
// besitzt weiterhin den Dispatch und die Dokumentlebensdauer. Mutieren Sie
// keinen TPdf während eines laufenden Renderings; Batch-Exporte halten
// üblicherweise eine TPdf pro Datei.
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;

Transformationsmatrizen

Komposition von Translate / Scale / Rotate / Skew

// FPdfMatrix umschließt PDFiums FS_MATRIX, sodass Aufrufer eine
// Transformation deklarativ aufbauen und an FPDFPageObj_SetMatrix
// übergeben können.
uses FPdfMatrix, FPdfView;

var
  M: TPdfMatrix;
  RawMatrix: FS_MATRIX;
  PageObj: FPDF_PAGEOBJECT;
begin
  M := TPdfMatrix.Create;
  try
    M.Translate(72, 200);       // 1 Zoll nach rechts, ~2,8 Zoll nach oben verschieben
    M.Scale(0.5, 0.5);          // halb großer Stempel
    M.Rotate(15);               // 15-Grad-Drehung gegen Uhrzeigersinn
    // M.HorizontalFlip;
    // M.VerticalFlip;
    // M.CentralFlip;
    // M.Skew(10, 5);
    // M.Multiply(Other);       // mit einer anderen Matrix post-multiplizieren

    // Auf ein beliebiges PDFium-Seitenobjekt anwenden (Text / Pfad / Bild / Form XObject)
    RawMatrix := M.Handle;
    FPDFPageObj_SetMatrix(PageObj, RawMatrix);
  finally
    M.Free;
  end;
end;

Seitenkomposition

Teilmenge von Seiten nach Index importieren

// ImportPagesByIndex kopiert ein explizites null-basiertes Index-Array
// aus einem Quell-PDF in dieses Dokument. InsertAt = 0 fügt vor Seite 1
// ein; PageCount hängt an.
var
  Source: TPdf;
begin
  Source := TPdf.Create(nil);
  try
    Source.FileName := 'C:\Report.pdf';
    Source.Active   := True;

    // Seiten 0, 2, 4 (eins-basiert: Seiten 1, 3, 5) aus Source importieren
    // und am Ende von Pdf1 anhängen.
    Pdf1.ImportPagesByIndex(Source, [0, 2, 4], Pdf1.PageCount);

    // Leeres Array importiert jede Quellseite
    // Pdf1.ImportPagesByIndex(Source, [], 0);
  finally
    Source.Free;
  end;
end;

N-up-Kompositionsausgabe

// ImportNPagesToOne liefert ein brandneues TPdf, dessen Seiten NumX*NumY-
// Kompositionen DIESES Dokuments sind. OutputWidth/Height sind PDF-
// Benutzereinheiten (1 Einheit = 1/72 Zoll).
var
  Composite: TPdf;
begin
  Pdf1.FileName := 'C:\Slides.pdf';
  Pdf1.Active   := True;
  // 4-up A4 Querformat: 842 x 595 pt, 2 Spalten x 2 Zeilen
  Composite := Pdf1.ImportNPagesToOne(842, 595, 2, 2);
  try
    if Composite <> nil then
      Composite.SaveAs('C:\Slides_4up.pdf');
  finally
    Composite.Free;
  end;
end;

Seiten an Ort und Stelle umsortieren

// MovePages nimmt die null-basierten Indizes der zu verschiebenden Seiten
// sowie den Zielindex für die erste verschobene Seite nach Abschluss der
// Verschiebung entgegen. Andere Seiten verschieben sich um den
// verschobenen Block herum, sodass die Dokumentlänge gleich bleibt.
//
// Beispiel: In einem 5-Seiten-Dokument mit Seiten [0,1,2,3,4]
// ordnet MovePages([2, 3], 0) zu [2, 3, 0, 1, 4] um.
if not Pdf1.MovePages([2, 3], 0) then
  ShowMessage('Invalid indices passed to MovePages');

Wasserzeichen über Wiederverwendung als Form XObject

// CreateXObjectFromPage erzeugt ein wiederverwendbares Form-XObject-Handle
// aus einer beliebigen Seite eines beliebigen anderen TPdf. Stempeln Sie
// dieses Handle auf so viele Seiten DIESES Dokuments wie Sie möchten —
// jeder Stempel ist ein einzelnes PDFium-Seitenobjekt, das über die
// Matrix-API positioniert, skaliert und gedreht werden kann.
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;

    // Seite 0 von WatermarkSource als wiederverwendbares XObject einhüllen
    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;

        // Wasserzeichen auf der Seite zentrieren
        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; // schließt das FPDF_XOBJECT-Handle
    end;

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

Die Projekte Demo\Delphi\WatermarkStamp, Demo\Lazarus\WatermarkStamp und Demo\CBuilder\WatermarkStamp verwandeln dieses Muster in einen lauffähigen Workflow: Sie erstellen ein Ziel-PDF und ein wiederverwendbares Stempel-PDF, hüllen die Stempel-Seite mit CreateXObjectFromPage ein, fügen dasselbe Form XObject auf jeder Zielseite ein, wenden TPdfMatrix-Transformationen für zentrierte Wasserzeichen und Stempel oben rechts an und fügen vor dem Speichern des Ergebnisses optionale Seitenzahl-Beschriftungen hinzu.

Bildeinfügung (Direkte AddImage-Overloads)

Eine registrierte Bilddatei oder ein TBitmap hinzufügen

// AddImage(FileName) akzeptiert jedes Format, das mit den VCL- oder LCL-
// Grafikunits registriert ist (BMP, PNG über PngImage, JPG, ...).
Pdf1.AddImage('C:\Photo.png', 100, 500, 200, 150);

// AddImage(TBitmap) überspringt den TPicture-Zwischenschritt, ideal wenn
// das Bitmap bereits aus Rendering- oder Batch-Generierungsarbeiten stammt.
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;

Die Projekte Demo\Delphi\ImageToPDF, Demo\CBuilder\ImageToPDF und Demo\Lazarus\ImageToPDF machen daraus einen vollständigen Batch-Workflow: mehrere Bilddateien auswählen, das ausgewählte Element vorschauen, eine PDF-Seite pro Bild erstellen, jedes Bild in eine A4-Seite im Hoch- oder Querformat skalieren, das Ergebnis speichern und das erzeugte PDF öffnen.

Viewer-Such-Hervorhebung

Jeden Treffer auf der aktuellen Seite hervorheben

var
  MatchCount: Integer;
begin
  // Eine clYellow-Maske über jeden Treffer auf der aktuellen Ansichtsseite zeichnen.
  // Beim Wechsel zu einer anderen Seite wird die Hervorhebung automatisch gelöscht.
  PdfView1.HighlightColor := clYellow;
  MatchCount := PdfView1.HighlightSearchText('invoice', False{Case}, True{Word});
  Status.Caption := Format('%d match(es) on this page', [MatchCount]);

  // Das Overlay programmgesteuert vor einem Seitenwechsel entfernen
  PdfView1.ClearHighlight;
end;

Viewer-Layout und Seitenfarbe

Fit-Mode und Seitenhintergrund

// FitMode hält lange Dokumente bei Resize oder Seitenwechseln gerahmt.
// Das direkte Setzen von Zoom hebt FitMode auf (er wechselt zurück zu pfmNone).
PdfView1.FitMode := pfmFitPage;       // gesamte Seite im Ansichtsbereich
// PdfView1.FitMode := pfmFitWidth;   // Seitenbreite entspricht Ansichtsbereichsbreite
// PdfView1.FitMode := pfmActualSize; // 100 %

// PageColor trennt die gerenderte PDF-Seite von der Color des Host-Steuerelements
// — typische Dark-Mode-Viewer-Einrichtung hält den Bildlaufbereich dunkel,
// während die PDF-Seite papierweiß bleibt.
PdfView1.Color     := clBlack;        // Bildlaufbereich
PdfView1.PageColor := clWhite;        // PDF-Seitenhintergrund

// Optionaler Schlagschatten und Seitenrahmen, um die Seite von einem dunklen
// Hintergrund abzuheben
PdfView1.PageShadowSize  := 6;
PdfView1.PageShadowColor := $00404040;
PdfView1.PageBorderColor := clGray;

Viewer-Interaktion in ViewerInteractionLab erkunden

Die Projekte Demo\Delphi\ViewerInteractionLab, Demo\CBuilder\ViewerInteractionLab und Demo\Lazarus\ViewerInteractionLab halten die moderne Interaktionsoberfläche von TPdfView in einem kompakten Formular zusammen: ein Sample-PDF öffnen oder erstellen, DisplayMode umschalten, FitMode anwenden, PageColor ändern, Seiten drehen, Suchtreffer der aktuellen Seite mit HighlightSearchText hervorheben, Benutzer-Textauswahl aktivieren oder deaktivieren, SelectedText untersuchen, SelectAll, CopySelectionToClipboard und ClearSelection aufrufen und beobachten, wie sich die DeviceToPage-Koordinaten in der Statusleiste aktualisieren, während sich die Maus über die Seite bewegt.

Text in SearchAndSelect suchen und auswählen

Die Projekte Demo\Delphi\SearchAndSelect, Demo\CBuilder\SearchAndSelect und Demo\Lazarus\SearchAndSelect machen aus dem Such- und Auswahl-Viewer-Workflow eigenständige Beispiele. Sie öffnen oder erstellen ein PDF, listen seitenübergreifende Suchergebnisse mit Seitennummer, Zeichenindex und Vorschautext auf, unterstützen Vorheriger-/Nächster-Navigation und Doppelklick-Sprung zum Treffer, heben Treffer der aktuellen Seite mit HighlightSearchText hervor, stellen Match-Case- und Whole-Word-Optionen bereit, schalten AllowUserTextSelection um, zeigen eine Vorschau von SelectedText, steuern SelectAll, CopySelectionToClipboard und ClearSelection über Schaltflächen und ermöglichen Benutzern den Wechsel zwischen Einzel- und Spread-Anzeigemodi.

PDF-Druckvoreinstellungen in PrintPreferences lesen

Die Projekte Demo\Delphi\PrintPreferences, Demo\CBuilder\PrintPreferences und Demo\Lazarus\PrintPreferences lesen die Druckvoreinstellungen des Autors vor dem Drucken. Sie zeigen PrintCopies, PrintPageRanges, PrintScaling und PrintPaperHandling an, können die vom Autor bereitgestellten Kopien und Bereiche in die Druckeingaben übernehmen und veranschaulichen, wie SetPdfPrintPaperHandlingDevMode die PDF-Duplex-Intent auf ein Windows-DEVMODE abbildet.

Standard-PDF-Druck in PrintPDF

Die Projekte Demo\Delphi\PrintPDF, Demo\CBuilder\PrintPDF und Demo\Lazarus\PrintPDF zeigen den Standard-Druck-Workflow: ein PDF öffnen, Seiten vorschauen, Seitenbereiche und Kopien im plattform-eigenen Druckdialog wählen, Collate-Modus respektieren, jede Seite über TPdf.RenderPage rendern und Druckfortschritt mit Abbruch bereitstellen. Das Delphi-Beispiel akzeptiert außerdem per Drag-and-Drop fallengelassene PDF-Dateien zum schnellen Laden.

Schnelles mehrseitiges Anzeigen in MultiPageViewer

Die Projekte Demo\Delphi\MultiPageViewer, Demo\CBuilder\MultiPageViewer und Demo\Lazarus\MultiPageViewer üben fortlaufendes, Spread- und deckblattbewusstes Betrachten mit regulären Seitennavigations- und Zoom-Workflows. Sie eignen sich auch zum Testen der Open-to-First-Page-Geschwindigkeit und der Rendering-Reaktionsfähigkeit bei sehr großen PDFs: In Tests mit großen Dateien können 2.x GB große Dokumente, bei denen Acrobat oder Foxit 20-30 Sekunden bis zur ersten Ansicht brauchen, über den PDFiumPas-Viewer-Pfad nahezu sofort nutzbar werden.

Nebeneinander-Prüfung in SplitView

Die Projekte Demo\Delphi\SplitView, Demo\CBuilder\SplitView und Demo\Lazarus\SplitView zeigen Nebeneinander-PDF-Prüfung. Die C++Builder- und Lazarus-Beispiele konzentrieren sich auf Zwei- oder Drei-Panel-Vergleich, Auswahl der aktiven Ansicht, optionale synchronisierte Navigationsbefehle, gemeinsame Zoom-Voreinstellungen und Drehung für die ausgewählte Ansicht oder alle geladenen Ansichten. Verwenden Sie diese Beispiele, um große Dokumente zu vergleichen und dabei die erste nutzbare Ansicht sowie das nachfolgende Seitenrendering reaktionsfähig zu halten; dieselben 2.x GB großen PDF-Dateien, für die Acrobat oder Foxit 20-30 Sekunden zum Öffnen brauchen, können im PDFiumPas-Viewer-Pfad nahezu sofort nutzbar werden.

PDF-Risikofläche in SecurityAudit prüfen

Die Projekte Demo\Delphi\SecurityAudit, Demo\CBuilder\SecurityAudit und Demo\Lazarus\SecurityAudit öffnen ein PDF schreibgeschützt und listen Berechtigungszustand, eingebettete Anhänge, Dokument-JavaScript-Aktionen, URI- und Launch-Link-Annotationen, Web-Links, Signaturzusammenfassungen, XFA-Status, V8-Helfer-Verfügbarkeit und Callbacks für nicht unterstützte Funktionen auf. Dieselben Zusammenfassungs- und Befundzeilen können als TXT-Audit-Bericht gespeichert oder kopiert oder als strukturiertes JSON gespeichert werden. Sie sind als Risikopanels für Host-Anwendungen gedacht, nicht als Malware-Erkennung.

Viewer-Link-Behandlung

Sicheres Click-to-Follow mit aktionsspezifischen Erlaubnislisten

// LinkOptions steuert die automatische Behandlung der vier PDF-Link-Aktionstypen.
// Standardmäßig sind nur goto und URI aktiviert; launch (ein Programm starten) und
// embedded GotoR (zu einer anderen Datei springen) bleiben AUS, sodass ein
// versehentlicher Klick keinen beliebigen Code ausführen kann.
PdfView1.LinkOptions :=
  [loAutoGoto, loAutoOpenURI];  // sichere Standardwerte

// OnAnnotationLinkClick wird für In-Dokument-Ziele ausgelöst (Seitensprünge,
// benannte Zielorte, Aktionsketten). Setzen Sie Handled = True, um zu verhindern,
// dass die Ansicht den Link automatisch verfolgt.
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;  // automatische Behandlung unterdrücken
  end;
end;

// OnWebLinkClick wird für URI-Strings ausgelöst, die vom Web-Link-Scanner von
// PDFium entdeckt wurden (automatisch erkannte URLs im Textstream, auch ohne
// /A <</S/URI...>>-Annotation).
procedure TForm1.PdfView1WebLinkClick(Sender: TObject;
  WebLinkIndex: Integer; const Url: WString;
  var Handled: Boolean);
begin
  Log.Add('Web link clicked: ' + string(Url));
end;

Viewer-Formular-Widget-Bearbeitung

Auswahl und Undo / Redo

// Auf dem fokussierten AcroForm-Widget steuern sechs neue Methoden die
// Formular-Bearbeitungshistorie von PDFium. Alle sechs verhalten sich sicher
// als No-op, wenn kein Widget fokussiert ist oder das Dokument kein AcroForm
// enthält.
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;

Den Viewer für Kioske sperren

Seitennavigationsgesten deaktivieren

// Alle drei stehen standardmäßig auf True, sodass bestehende Apps ohne
// Verhaltensänderung aktualisieren. Setzen Sie sie auf False für Kioske,
// Vorschaupanels oder schreibgeschützte Einbettungen ohne Subclassing von
// TPdfView.
PdfView1.AllowUserPageChange       := False;  // PgUp/PgDn/Ctrl+Home/End
PdfView1.ChangePageOnMouseScrolling := False; // Mausrad hoch/runter springt Seiten
PdfView1.AllowUserTextSelection    := False;  // Maus/Tastatur-Textauswahl

// Ctrl+Mausrad ist für Zoom reserviert und wird vom Navigations-Handler
// unabhängig von ChangePageOnMouseScrolling übersprungen.

Miniaturansicht-Seitenpanel

Eine TPdfThumbnailView mit einer TPdfView verknüpfen

// TPdfThumbnailView (Unit FPdfThumbnail) rendert eine Seiten-Miniaturansicht
// pro Zeile in einem scrollbaren Seitenpanel.
uses FPdfThumbnail;

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

procedure TForm1.ThumbViewPageClick(Sender: TObject; PageIndex: Integer);
begin
  PdfView1.PageNumber := PageIndex + 1; // PageIndex ist null-basiert
end;

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

Kooperativ abbrechbarer Batch-Export

Mehrdatei-PDF-zu-JPG mit Fortschritt pro Datei und pro Seite

// Muster, verwendet von Demo\Delphi\BatchExport, Demo\CBuilder\BatchExport
// und Demo\Lazarus\BatchExport. Eine TPdf pro Datei, IPdfCancellationToken
// für Cancel-from-UI, TPdfStreamAdapter für gepuffertes Dateiladen.
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;

Viewer-Koordinaten und Hit-Testing

Die aktuelle Seite anpassen und Text unter der Maus untersuchen

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;

Fehlerbehandlung

Robuste PDF-Operationen

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 und Barrierefreiheit

Tagged PDF erkennen und den Strukturbaum durchlaufen

// IsTagged ist eine günstige Catalog-Prüfung — True, wenn /StructTreeRoot
// existiert. StructureElements materialisiert den Baum in ein flaches Array
// von TPdfStructureElement-Records mit Typ, Titel, Alternativtext, Actual
// Text, Expansionstext, Sprache, Ebene, Eltern-Index und Zählungen für
// Children / 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;

Druckmodus-Auswahl

EMF / PostScript / Image-Mask-Druckmodus festlegen

// SetPdfPrintMode ist eine globale Funktion, die auf PDFiums
// FPDF_SetPrintMode abbildet. Verwenden Sie den passenden TPdfPrintMode-Wert
// für den eingesetzten Druckertreiber.
SetPdfPrintMode(pmEmf);                   // GDI EMF (Standard)
// SetPdfPrintMode(pmTextOnly);           // nur-Text-Rendering
// SetPdfPrintMode(pmPostScript2);        // PostScript Level 2
// SetPdfPrintMode(pmPostScript3);        // PostScript Level 3
// SetPdfPrintMode(pmPostScript2PassThrough);
// SetPdfPrintMode(pmPostScript3PassThrough);
// SetPdfPrintMode(pmEmfImageMasks);      // EMF + Image Masks
// SetPdfPrintMode(pmPostScript3Type42);  // PostScript 3 + Type-42-Schriftarten

Vom Autor beabsichtigte Druckeinstellungen lesen

// PrintCopies, PrintPageRanges, PrintScaling und PrintPaperHandling sind
// die Einträge des Viewer-Preference-Dictionary — sie kodieren, was der
// Dokumentautor als Standard für PDF-Reader gewünscht hat.
var
  Ranges: TPrintPageRanges;
  Range: TPrintPageRange;
begin
  ShowMessage('Suggested copies: ' + IntToStr(Pdf1.PrintCopies));
  ShowMessage('Print scaling: ' + GetEnumName(TypeInfo(TPrintScaling),
    Ord(Pdf1.PrintScaling)));

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

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

Dokument-JavaScript-Aktionen

JavaScript der Namensbaumstruktur auflisten

// JavaScript auf Dokumentebene wird im /Names /JavaScript-Namensbaum
// gespeichert. JavaScriptAction[i] liefert einen Eintrag;
// JavaScriptActions liefert das vollständige Array.
var
  I: Integer;
  Action: TPdfJavaScriptAction;
begin
  for I := 0 to Pdf1.JavaScriptActionCount - 1 do
  begin
    Action := Pdf1.JavaScriptAction[I];
    Memo1.Lines.Add('Name:   ' + string(Action.Name));
    Memo1.Lines.Add('Script: ' + string(Action.Script));
    Memo1.Lines.Add('---');
  end;
end;

Leistungstipps

PDF-Operationen optimieren

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

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

// Zero-Copy-Render-Pfad (Standard seit v1.21.0): RenderPage schreibt direkt
// in den DIB-Puffer des Ziel-TBitmap, wenn Aufrufer ein vorab allokiertes
// Bitmap übergeben, und vermeidet so eine Kopie von Breite * Höhe * 4 Bytes.
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;