Pdf1.FileName := 'C:\MyDocument.pdf';
Pdf1.Active := True;
ShowMessage('Pages: ' + IntToStr(Pdf1.PageCount));
ShowMessage('Title: ' + Pdf1.Title);
ShowMessage('Author: ' + Pdf1.Author);
var
Stream: TMemoryStream;
begin
Stream := TMemoryStream.Create;
try
Stream.LoadFromFile('C:\MyDocument.pdf');
Pdf1.LoadDocument(Stream, True);
Pdf1.Active := True;
ShowMessage('PDF loaded from stream');
finally
Stream.Free;
end;
end;
Pdf1.CreateDocument;
Pdf1.Active := True;
Pdf1.AddPage(0, 612, 792);
Pdf1.PageNumber := 1;
Pdf1.AddText('Hello World!', 'Arial', 12, 100, 700, clBlack, $FF, 0.0);
Pdf1.UpdatePage;
Pdf1.SaveAs('C:\NewDocument.pdf');
Pdf1.Active := False;
var
PageText: WString;
RectText: WString;
begin
Pdf1.PageNumber := 1;
PageText := Pdf1.Text;
Memo1.Text := PageText;
RectText := Pdf1.TextInRectangle(100, 100, 500, 200);
ShowMessage('Text in rectangle: ' + RectText);
end;
var
Position: Integer;
begin
Position := Pdf1.FindFirst('Hello', [seCaseSensitive]);
while Position >= 0 do
begin
ShowMessage('Found at character: ' + IntToStr(Position));
Position := Pdf1.FindNext;
end;
end;
Pdf1.PageNumber := 1;
Pdf1.AddText('Document Title', 'Times New Roman', 16, 100, 750, clBlue);
Pdf1.AddText('This is the document content.', 'Arial', 12, 100, 700, clBlack);
Pdf1.UpdatePage;
var
I: Integer;
MetricSize: Single;
FontBytes: TBytes;
begin
Pdf1.PageNumber := 1;
for I := 0 to Pdf1.ObjectCount - 1 do
if Pdf1.ObjectType[I] = otText then
begin
MetricSize := 12.0;
FontBytes := Pdf1.FontData[I];
Memo1.Lines.Add('Object ' + IntToStr(I));
Memo1.Lines.Add('Base name: ' + Pdf1.FontBaseName[I]);
Memo1.Lines.Add('Family: ' + Pdf1.FontFamilyName[I]);
Memo1.Lines.Add('Embedded: ' + BoolToStr(Pdf1.FontIsEmbedded[I], True));
Memo1.Lines.Add('Weight: ' + IntToStr(Pdf1.FontWeight[I]));
Memo1.Lines.Add('Italic angle: ' + IntToStr(Pdf1.FontItalicAngle[I]));
Memo1.Lines.Add('Ascent: ' + FloatToStr(Pdf1.FontAscent[I, MetricSize]));
Memo1.Lines.Add('Descent: ' + FloatToStr(Pdf1.FontDescent[I, MetricSize]));
Memo1.Lines.Add('Font data bytes: ' + IntToStr(Length(FontBytes)));
end;
if Pdf1.CharacterCount > 0 then
begin
Memo1.Lines.Add('First character size: ' + FloatToStr(Pdf1.CharacterFontSize[0]));
Memo1.Lines.Add('First character weight: ' + IntToStr(Pdf1.CharacterFontWeight[0]));
Memo1.Lines.Add('First character angle: ' + FloatToStr(Pdf1.CharacterAngle[0]));
end;
end;
Die Projekte Demo\Delphi\FontProperties, Demo\CBuilder\FontProperties und Demo\Lazarus\FontProperties zeigen dieselben Metadaten sowohl über TPdf als auch über TPdfView.
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.
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;
// 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;
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);
// 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');
// 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;
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;
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.
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.
// 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;
// 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;
// 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;
// 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;
// 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;
// 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;
// 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;
// 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');
// 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.
// 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.
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;
// 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;
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.
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.
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.
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.
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.
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.
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.
// 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;
// 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;
// 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.
// 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;
// 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;
procedure FitCurrentPage;
begin
PdfView1.Zoom := PdfView1.PageZoom[PdfView1.PageNumber];
end;
procedure InspectViewerPoint(X, Y: Integer);
var
PageNo: Integer;
PageX, PageY: Double;
CharIndex: Integer;
Ch: WString;
begin
PageNo := PdfView1.PageNumber;
if not PdfView1.DeviceToPage(X, Y, PageNo, PageX, PageY) then
Exit;
CharIndex := PdfView1.CharacterIndexAtPos(X, Y, 8, 8);
if CharIndex >= 0 then
begin
Ch := PdfView1.Text(CharIndex, 1);
ShowMessage(Format('Page %d, X %.2f, Y %.2f, character %s',
[PageNo, PageX, PageY, string(Ch)]));
end;
end;
function LoadPdfSafely(const FileName: string): Boolean;
begin
Result := False;
if not FileExists(FileName) then
Exit;
try
Pdf1.FileName := FileName;
Pdf1.Active := True;
Result := Pdf1.Active;
except
Result := False;
end;
end;
function ExtractTextSafely(PageNo: Integer): WString;
begin
Result := '';
if not Pdf1.Active then
Exit;
if (PageNo < 1) or (PageNo > Pdf1.PageCount) then
Exit;
Pdf1.PageNumber := PageNo;
Result := Pdf1.Text;
end;
// IsTagged 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;
// 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
// 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;
// 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;
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;