Documentation PDFiumPas

Exemples de programmation PDFium VCL

Opérations de base sur les documents

Charger et afficher un document 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;

Créer un nouveau document 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;

Opérations sur le texte

Extraire du texte d'un 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;

Rechercher du texte

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;

Ajouter du texte avec formatage

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;

Inspecter les métadonnées de caractère et de police

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;

Les projets Demo\Delphi\FontProperties, Demo\CBuilder\FontProperties et Demo\Lazarus\FontProperties montrent les mêmes métadonnées via TPdf et TPdfView.

Construire un rapport d'extraction de contenu multi-catégorie

Les projets Demo\Delphi\ContentExtractionLab, Demo\CBuilder\ContentExtractionLab et Demo\Lazarus\ContentExtractionLab transforment l'extraction de contenu en un workflow unique. Ils ouvrent ou créent un PDF, permettent aux utilisateurs de choisir les métadonnées, le texte de page, les objets de page, les images, les pièces jointes, les liens, les annotations, les signets et les métriques de police / caractère, puis génèrent une synthèse plus un rapport TXT ou JSON détaillé. Le lab inclut également la sélection toutes / aucune catégorie, la copie de rapport vers le presse-papiers et les actions d'enregistrement du PDF source afin que les utilisateurs puissent conserver le PDF d'exemple généré à côté de son rapport d'extraction. Les démos plus anciennes ExtractText, ExtractImages, Attachment et FontProperties demeurent des exemples ciblés ; ExtractText prend en charge les options de plage de pages et de séparateur de pages pour produire une sortie texte par page sous Delphi, C++Builder et Lazarus, tandis que ContentExtractionLab est l'exemple d'inventaire transversal.

Opérations sur les formulaires

Travailler avec les champs de formulaire

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;

Aplatir les valeurs de formulaire remplies dans la page

// Sans aplatissement, FormField[i] := value écrit uniquement l'entrée /V —
// l'apparence visible provient du flux /AP et peut ne pas se rafraîchir.
// GenerateFormAppearances régénère /AP pour chaque widget, puis
// FlattenAllPages intègre les annotations et widgets de chaque page dans
// du contenu permanent et non modifiable.
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;

Opérations sur les images

Extraire des images

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;

Ajouter des images

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;

Opérations sur les annotations

Travailler avec les annotations

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;

Opérations de dessin

Dessiner des formes

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 et signets

Travailler avec les signets

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;

Travailler avec les destinations nommées

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;

Opérations sur les pièces jointes

Travailler avec les pièces jointes

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;

Gestion des pages

Opérations sur les pages

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

Enregistrement et compression

Enregistrer avec compression

// Créer un document PDF compressé
Pdf1.CreateDocument;
Pdf1.Compressed := True; // garantir que tous les flux sont compressés FlateDecode (défaut)
Pdf1.Active := True;
Pdf1.AddPage(0, 595, 842); // format A4
Pdf1.AddText('Hello World!', 'Arial', 24, 100, 700, clBlack, $FF, 0.0);
Pdf1.SaveAs('C:\Compressed.pdf');
Pdf1.Active := False;

// Enregistrer avec options
Pdf1.SaveAs('C:\Output.pdf', saIncremental); // enregistrement incrémentiel
Pdf1.SaveAs('C:\NoSecurity.pdf', saRemoveSecurity); // suppression de la sécurité

// Enregistrer vers une cible de version PDF spécifique. Le validateur de
// version PDF rejette toute fonctionnalité introduite après la cible
// choisie (JBIG2Decode, MarkInfo, chiffrement AES, annotations 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');

Créer une sortie d'archivage PDF/A-1b

// SaveAsPdfA post-traite l'enregistrement de base avec une mise à jour
// incrémentielle qui injecte un flux de métadonnées XMP, un OutputIntent
// ICC sRGB et un catalogue de document mis à jour, de sorte que le résultat
// soit conforme PDF/A-1b. Le profil sRGB IEC61966-2.1 intégré est utilisé
// automatiquement lorsqu'aucun profil personnalisé n'est fourni.
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 avec un profil ICC personnalisé (pipelines d'impression CMYK par exemple)
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;

// Sortie PDF/A en mémoire
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;

Valider la conformité PDF/A

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

  // Sonde de style header bon marché — lit la conformité sans itérer
  // le validateur complet. Résultat : pacNone, pac1a, pac1b, pac2b, pac3b ou
  // 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;

  // Validateur complet avec diagnostics par problème
  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;

Inspecter plusieurs normes PDF dans StandardsLab

Les projets Demo\Delphi\StandardsLab, Demo\CBuilder\StandardsLab et Demo\Lazarus\StandardsLab chargent ou créent un PDF, exécutent ValidatePdfA, ValidatePdfUa, ValidatePdfE, ValidatePdfX, ValidatePdfR et ValidatePdfVT, puis affichent chaque niveau de conformité détecté et le nombre de problèmes dans une seule grille. Leurs boutons d'enregistrement appellent SaveAsPdfA, SaveAsPdfUa, SaveAsPdfE, SaveAsPdfX, SaveAsPdfR et SaveAsPdfVT afin que le workflow de sortie avec marqueurs soit visible sans écrire de harnais personnalisé.

Générer des rapports de contrôle en amont

L'unité FPdfPreflightReport encapsule ValidatePdfA, ValidatePdfUa, ValidatePdfE, ValidatePdfX, ValidatePdfR et ValidatePdfVT dans un seul objet de rapport. Demo\Delphi\PreflightReport, Demo\Lazarus\PreflightReport et Demo\CBuilder\PreflightReport permettent aux utilisateurs de choisir les normes, générer une synthèse, inspecter statut, priorité, catégories de problèmes, comptes par catégorie, prochaines actions, codes de problème et actions recommandées, basculer la prévisualisation entre texte, Markdown, JSON et CSV, et exporter des rapports TXT, HTML, Markdown, JSON ou CSV. L'échantillon Delphi GUI ajoute également un aperçu Action Plan qui regroupe les standards non conformes, priorités, codes de problème et recommandations en une liste de corrections. Demo\Delphi\PreflightReportCli expose le même chemin de rapport en tant que workflow console avec arguments d'entrée / sortie, mot de passe facultatif, sélection du format de sortie incluant plan / actionplan, filtrage par norme, intégration facultative d'un rapport mono-fichier attach=output.pdf, codes de sortie de blocage CI failon=, traitement batch=list.txt, analyse de répertoires batchdir=folder avec récursion facultative et traitement stable trié par chemin, routage des rapports outdir=reports, noms de rapport résistant aux collisions pour les fichiers d'entrée à nom dupliqué, synthèses TXT / JSON / HTML avec totaux agrégés et de statut par norme, manifestes de lignes CSV avec paramètres d'exécution répétés par élément, et un mode d'exemple sans argument pour les scripts et les tâches CI. Le rapport généré indique que les validateurs intégrés couvrent les contrôles au niveau marqueur et certains contrôles au niveau fichier, de sorte que le contrôle complet au niveau contenu peut toujours être délégué à un moteur de validation dédié.

Chargement en flux et documents volumineux

Charger un PDF à la demande avec LoadCustomDocument

// LoadCustomDocument permet à PDFium de récupérer les blocs depuis tout
// TStream cherchable à la demande. Le flux n'est PAS copié en mémoire, de
// sorte que les PDF de plusieurs Go, les flux de corps HTTP distants et les
// sources adossées à une base de données sont tous viables sans tampon
// préalable.
var
  FileStream: TFileStream;
begin
  // Streaming en lecture depuis fichier (pas de copie en mémoire)
  FileStream := TFileStream.Create('C:\Huge.pdf',
    fmOpenRead or fmShareDenyWrite);
  Pdf1.LoadCustomDocument(FileStream, True{AOwnsStream});
  // ^^ propriété transférée — Pdf1 libère FileStream sur UnloadDocument.
  Pdf1.Active := True;
  ShowMessage('Pages: ' + IntToStr(Pdf1.PageCount));
end;

// Flux détenu en externe (l'appelant le libère après 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;

Annulation asynchrone et rendu progressif

Jetons d'annulation coopératifs

// FPdfAsync expose IPdfCancellationToken /
// IPdfCancellationTokenSource pour une sémantique d'annulation depuis
// l'UI qui fonctionne à travers les chemins longs d'export et de rendu.
uses FPdfAsync;

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

  // ... l'utilisateur clique sur Annuler ...
  CancelSource.Cancel;

  // Le worker vérifie périodiquement CancelToken.IsCancelled, ou le chemin
  // de rendu l'interroge via le callback IFSDK_PAUSE de PDFium.
  if CancelToken.IsCancelled then
    Memo1.Lines.Add('Cancelled by user');
end;

Rendu de page progressif annulable

// RenderPageProgressive permet à un long rendu haute résolution d'être
// interrompu en milieu de page lorsque l'utilisateur clique sur Annuler,
// au lieu de bloquer l'appelant jusqu'à la fin.
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;

Exécuter des rendus sur un thread d'arrière-plan avec TPdfFuture

// TPdfFuture<T> lance un worker sur un thread d'arrière-plan et renvoie
// une enveloppe TPdfFutureResult<T> vers le thread principal.
//
// Important : TPdf sérialise les appels de rendu par instance, mais le
// worker reste responsable de la répartition et de la durée de vie du
// document. Ne mutez pas un TPdf pendant qu'un rendu est en cours ;
// l'export par lot conserve généralement un TPdf par fichier.
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;

Matrices de transformation

Composer Translate / Scale / Rotate / Skew

// FPdfMatrix encapsule FS_MATRIX de PDFium afin que les appelants puissent
// construire une transformation de manière déclarative et la passer à
// FPDFPageObj_SetMatrix.
uses FPdfMatrix, FPdfView;

var
  M: TPdfMatrix;
  RawMatrix: FS_MATRIX;
  PageObj: FPDF_PAGEOBJECT;
begin
  M := TPdfMatrix.Create;
  try
    M.Translate(72, 200);       // déplacer 1in à droite, ~2,8in vers le haut
    M.Scale(0.5, 0.5);          // tampon à mi-taille
    M.Rotate(15);               // rotation de 15 degrés CCW
    // M.HorizontalFlip;
    // M.VerticalFlip;
    // M.CentralFlip;
    // M.Skew(10, 5);
    // M.Multiply(Other);       // post-multiplier avec une autre matrice

    // Appliquer à tout objet de page PDFium (text / path / image / form XObject)
    RawMatrix := M.Handle;
    FPDFPageObj_SetMatrix(PageObj, RawMatrix);
  finally
    M.Free;
  end;
end;

Composition de pages

Importer un sous-ensemble de pages par index

// ImportPagesByIndex copie un tableau d'index explicite base zéro depuis
// un PDF source dans ce document. InsertAt = 0 insère avant la page 1 ;
// PageCount ajoute à la fin.
var
  Source: TPdf;
begin
  Source := TPdf.Create(nil);
  try
    Source.FileName := 'C:\Report.pdf';
    Source.Active   := True;

    // Importer les pages 0, 2, 4 (base un : pages 1, 3, 5) depuis Source
    // et les ajouter à la fin de Pdf1.
    Pdf1.ImportPagesByIndex(Source, [0, 2, 4], Pdf1.PageCount);

    // Un tableau vide importe toutes les pages source
    // Pdf1.ImportPagesByIndex(Source, [], 0);
  finally
    Source.Free;
  end;
end;

Sortie composite N-up

// ImportNPagesToOne renvoie un nouveau TPdf dont les pages sont des
// composites NumX*NumY de CE document. OutputWidth/Height sont des unités
// utilisateur PDF (1 unité = 1/72 in).
var
  Composite: TPdf;
begin
  Pdf1.FileName := 'C:\Slides.pdf';
  Pdf1.Active   := True;
  // A4 paysage 4-up : 842 x 595 pt, 2 colonnes x 2 lignes
  Composite := Pdf1.ImportNPagesToOne(842, 595, 2, 2);
  try
    if Composite <> nil then
      Composite.SaveAs('C:\Slides_4up.pdf');
  finally
    Composite.Free;
  end;
end;

Réordonner les pages sur place

// MovePages prend les indices base zéro des pages à déplacer plus
// l'index de destination pour la première page déplacée après l'achèvement
// du déplacement. Les autres pages se décalent autour du bloc déplacé, de
// sorte que la longueur du document reste la même.
//
// Exemple : dans un document de 5 pages avec les pages numérotées [0,1,2,3,4],
// MovePages([2, 3], 0) réorganise en [2, 3, 0, 1, 4].
if not Pdf1.MovePages([2, 3], 0) then
  ShowMessage('Invalid indices passed to MovePages');

Filigrane via réutilisation de page Form XObject

// CreateXObjectFromPage fabrique un handle Form XObject réutilisable à
// partir de n'importe quelle page d'un autre TPdf. Tamponnez ce handle sur
// autant de pages de CE document que vous voulez — chaque tampon est un
// seul objet de page PDFium qui peut être positionné, mis à l'échelle et
// tourné via l'API matrice.
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;

    // Encapsuler la page 0 de WatermarkSource en tant que XObject réutilisable
    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;

        // Centrer le filigrane sur la page
        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; // ferme le handle FPDF_XOBJECT
    end;

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

Les projets Demo\Delphi\WatermarkStamp, Demo\Lazarus\WatermarkStamp et Demo\CBuilder\WatermarkStamp transforment ce modèle en un workflow exécutable : ils créent un PDF cible et un PDF de tampon réutilisable, encapsulent la page de tampon avec CreateXObjectFromPage, insèrent le même Form XObject sur chaque page cible, appliquent des transformations TPdfMatrix pour des filigranes centrés et des tampons en haut à droite, et ajoutent des étiquettes de numéro de page facultatives avant d'enregistrer le résultat.

Insertion d'image (surcharges AddImage directes)

Ajouter un fichier image enregistré ou un TBitmap

// AddImage(FileName) accepte tout format enregistré avec les unités
// graphiques VCL ou LCL (BMP, PNG via PngImage, JPG, ...).
Pdf1.AddImage('C:\Photo.png', 100, 500, 200, 150);

// AddImage(TBitmap) évite l'intermédiaire TPicture, idéal lorsque le
// bitmap provient déjà du rendu ou de la génération par lot.
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;

Les projets Demo\Delphi\ImageToPDF, Demo\CBuilder\ImageToPDF et Demo\Lazarus\ImageToPDF transforment cela en un workflow par lots complet : sélectionner plusieurs fichiers image, prévisualiser l'élément sélectionné, créer une page PDF par image, mettre à l'échelle chaque image dans une page A4 portrait ou paysage, enregistrer le résultat et ouvrir le PDF généré.

Surbrillance de recherche dans la visionneuse

Surligner chaque correspondance sur la page courante

var
  MatchCount: Integer;
begin
  // Peindre un masque clYellow sur chaque correspondance de la page courante
  // de la vue. Passer à une autre page efface automatiquement la surbrillance.
  PdfView1.HighlightColor := clYellow;
  MatchCount := PdfView1.HighlightSearchText('invoice', False{Case}, True{Word});
  Status.Caption := Format('%d match(es) on this page', [MatchCount]);

  // Supprimer la superposition par programmation avant le changement de page
  PdfView1.ClearHighlight;
end;

Mise en page de la visionneuse et couleur de page

Mode d'ajustement et arrière-plan de page

// FitMode conserve les longs documents cadrés lors d'un Resize ou d'un
// changement de page. Définir Zoom directement annule FitMode (il revient
// à pfmNone).
PdfView1.FitMode := pfmFitPage;       // page entière dans la fenêtre
// PdfView1.FitMode := pfmFitWidth;   // la largeur de la page correspond à la fenêtre
// PdfView1.FitMode := pfmActualSize; // 100 %

// PageColor dissocie la page PDF rendue du Color du contrôle hôte
// — configuration typique de visionneuse en mode sombre qui garde la
// zone de défilement sombre tandis que la page PDF reste blanc papier.
PdfView1.Color     := clBlack;        // zone de défilement
PdfView1.PageColor := clWhite;        // arrière-plan de la page PDF

// Ombre portée et bordure de page facultatives pour détacher la page d'un fond sombre
PdfView1.PageShadowSize  := 6;
PdfView1.PageShadowColor := $00404040;
PdfView1.PageBorderColor := clGray;

Explorer l'interaction de visionneuse dans ViewerInteractionLab

Les projets Demo\Delphi\ViewerInteractionLab, Demo\CBuilder\ViewerInteractionLab et Demo\Lazarus\ViewerInteractionLab conservent la surface d'interaction TPdfView moderne dans un formulaire compact : ouvrir ou créer un PDF d'exemple, changer de DisplayMode, appliquer FitMode, changer PageColor, faire pivoter les pages, surligner les correspondances de recherche de la page courante avec HighlightSearchText, activer ou désactiver la sélection de texte par l'utilisateur, inspecter SelectedText, appeler SelectAll, CopySelectionToClipboard et ClearSelection, et voir les coordonnées DeviceToPage se mettre à jour dans la barre d'état pendant que la souris se déplace sur la page.

Rechercher et sélectionner du texte dans SearchAndSelect

Les projets Demo\Delphi\SearchAndSelect, Demo\CBuilder\SearchAndSelect et Demo\Lazarus\SearchAndSelect transforment le workflow de visionneuse de recherche et sélection en exemples autonomes. Ils ouvrent ou créent un PDF, listent les résultats de recherche multi-pages avec numéro de page, index de caractère et texte de prévisualisation, prennent en charge la navigation Précédent / Suivant et le saut aux résultats par double-clic, surlignent les correspondances de la page courante avec HighlightSearchText, exposent les options sensible à la casse et mot entier, basculent AllowUserTextSelection, prévisualisent SelectedText, pilotent SelectAll, CopySelectionToClipboard et ClearSelection depuis des boutons, et laissent les utilisateurs basculer entre les modes d'affichage simple et double page.

Lire les préférences d'impression PDF dans PrintPreferences

Les projets Demo\Delphi\PrintPreferences, Demo\CBuilder\PrintPreferences et Demo\Lazarus\PrintPreferences lisent les préférences d'impression de l'auteur avant l'impression. Ils affichent PrintCopies, PrintPageRanges, PrintScaling et PrintPaperHandling, peuvent copier les copies et plages fournies par l'auteur dans les entrées d'impression, et montrent comment SetPdfPrintPaperHandlingDevMode mappe l'intention recto-verso PDF vers un DEVMODE Windows.

Impression PDF standard dans PrintPDF

Les projets Demo\Delphi\PrintPDF, Demo\CBuilder\PrintPDF et Demo\Lazarus\PrintPDF montrent le workflow d'impression standard : ouvrir un PDF, prévisualiser les pages, choisir les plages de pages et les copies dans la boîte de dialogue d'impression de la plateforme, respecter le mode d'assemblage, rendre chaque page via TPdf.RenderPage, et exposer la progression de l'impression avec annulation. L'exemple Delphi accepte également les fichiers PDF déposés pour un chargement rapide.

Visualisation rapide multi-pages dans MultiPageViewer

Les projets Demo\Delphi\MultiPageViewer, Demo\CBuilder\MultiPageViewer et Demo\Lazarus\MultiPageViewer exercent la visualisation continue, double page et tenant compte de la couverture avec des workflows réguliers de navigation et de zoom. Ils sont également utiles pour tester la vitesse d'ouverture à la première page et la réactivité du rendu sur de très grands PDF : dans les tests sur fichiers volumineux, des documents de 2,x Go qui peuvent faire passer Acrobat ou Foxit 20-30 secondes avant le premier rendu peuvent devenir utilisables presque immédiatement via le chemin de visionneuse PDFiumPas.

Revue côte à côte dans SplitView

Les projets Demo\Delphi\SplitView, Demo\CBuilder\SplitView et Demo\Lazarus\SplitView montrent une revue PDF côte à côte. Les exemples C++Builder et Lazarus se concentrent sur la comparaison à deux ou trois panneaux, la sélection de la vue active, des commandes de navigation synchronisées facultatives, des préréglages de zoom partagés et la rotation pour la vue sélectionnée ou toutes les vues chargées. Utilisez ces exemples pour comparer des documents volumineux tout en gardant le premier rendu utilisable et le rendu de page subséquent réactifs ; les mêmes fichiers PDF de 2,x Go qui peuvent prendre 20-30 secondes à Acrobat ou Foxit pour s'ouvrir peuvent devenir utilisables presque immédiatement dans le chemin de visionneuse PDFiumPas.

Auditer la surface de risque PDF dans SecurityAudit

Les projets Demo\Delphi\SecurityAudit, Demo\CBuilder\SecurityAudit et Demo\Lazarus\SecurityAudit ouvrent un PDF en lecture seule et listent l'état des permissions, les pièces jointes intégrées, les actions JavaScript du document, les annotations de lien URI et Launch, les liens Web, les synthèses de signature, l'état XFA, la disponibilité des helpers V8 et les callbacks de fonctionnalités non prises en charge. Les mêmes lignes de synthèse et de résultats peuvent être enregistrées ou copiées sous forme de rapport d'audit TXT ou enregistrées sous forme de JSON structuré. Ils sont conçus comme des panneaux de risque d'application hôte, et non comme une détection de logiciel malveillant.

Gestion des liens dans la visionneuse

Clic-pour-suivre sécurisé avec listes blanches par action

// LinkOptions contrôle la gestion automatique des quatre types d'actions
// de lien PDF. Les valeurs par défaut activent uniquement goto et URI ;
// launch (exécuter un programme) et embedded GotoR (sauter vers un autre
// fichier) restent DÉSACTIVÉS afin qu'un clic intempestif ne puisse pas
// exécuter de code arbitraire.
PdfView1.LinkOptions :=
  [loAutoGoto, loAutoOpenURI];  // valeurs par défaut sécurisées

// OnAnnotationLinkClick se déclenche pour les destinations dans le
// document (sauts de page, destinations nommées, chaînes d'actions).
// Marquez Handled = True pour empêcher la vue de suivre automatiquement le lien.
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;  // supprimer la gestion automatique
  end;
end;

// OnWebLinkClick se déclenche pour les chaînes URI découvertes par le
// scanner de liens Web de PDFium (URL auto-détectées dans le flux de texte,
// même sans annotation /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;

Édition de widget de formulaire dans la visionneuse

Sélection et Annuler / Rétablir

// Sur le widget AcroForm actif, six nouvelles méthodes pilotent
// l'historique d'édition de formulaire de PDFium. Les six court-circuitent
// en toute sécurité si aucun widget n'a le focus ou si le document ne porte
// pas d'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;

Verrouiller la visionneuse pour les bornes interactives

Désactiver les gestes de navigation de page

// Les trois ont pour valeur par défaut True, de sorte que les applications
// existantes se mettent à niveau sans changement de comportement. Passez-
// les à False pour les bornes interactives, panneaux de prévisualisation
// ou intégrations en lecture seule sans sous-classer TPdfView.
PdfView1.AllowUserPageChange       := False;  // PgUp/PgDn/Ctrl+Home/End
PdfView1.ChangePageOnMouseScrolling := False; // sauts molette haut/bas
PdfView1.AllowUserTextSelection    := False;  // sélection de texte souris/clavier

// Ctrl+molette est réservé au zoom et ignoré par le gestionnaire de
// navigation, quel que soit ChangePageOnMouseScrolling.

Panneau latéral de miniatures

Lier un TPdfThumbnailView à un TPdfView

// TPdfThumbnailView (unité FPdfThumbnail) rend une miniature de page par
// ligne dans un panneau latéral défilant.
uses FPdfThumbnail;

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

procedure TForm1.ThumbViewPageClick(Sender: TObject; PageIndex: Integer);
begin
  PdfView1.PageNumber := PageIndex + 1; // PageIndex est en base zéro
end;

procedure TForm1.PdfView1PageNumberModifié (Changed)(Sender: TObject);
begin
  ThumbView.CurrentPageIndex := PdfView1.PageNumber - 1;
end;

Export par lot annulable de manière coopérative

PDF multi-fichiers vers JPG avec progression par fichier et par page

// Modèle utilisé par Demo\Delphi\BatchExport, Demo\CBuilder\BatchExport
// et Demo\Lazarus\BatchExport. Un TPdf par fichier, IPdfCancellationToken
// pour annuler depuis l'UI, TPdfStreamAdapter pour le chargement de fichier mis en tampon.
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;

Coordonnées de la visionneuse et hit-test

Ajuster la page courante et inspecter le texte sous la souris

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;

Gestion des erreurs

Opérations PDF robustes

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 et accessibilité

Détecter Tagged PDF et parcourir l'arbre de structure

// IsTagged est une sonde de catalogue bon marché — True lorsque
// /StructTreeRoot existe. StructureElements matérialise l'arbre en un
// tableau plat de records TPdfStructureElement avec type, titre, texte
// alternatif, texte réel, texte d'expansion, langue, niveau, index parent
// et comptes d'enfants / contenu balisé / attributs.
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;

Sélection du mode d'impression

Définir le mode d'impression EMF / PostScript / masque d'image

// SetPdfPrintMode est une fonction globale qui mappe vers FPDF_SetPrintMode
// de PDFium. Utilisez la valeur TPdfPrintMode correspondant au pilote
// d'imprimante utilisé.
SetPdfPrintMode(pmEmf);                   // GDI EMF (défaut)
// SetPdfPrintMode(pmTextOnly);           // rendu texte seul
// SetPdfPrintMode(pmPostScript2);        // PostScript Level 2
// SetPdfPrintMode(pmPostScript3);        // PostScript Level 3
// SetPdfPrintMode(pmPostScript2PassThrough);
// SetPdfPrintMode(pmPostScript3PassThrough);
// SetPdfPrintMode(pmEmfImageMasks);      // EMF + masques d'image
// SetPdfPrintMode(pmPostScript3Type42);  // PostScript 3 + polices Type 42

Lire les paramètres d'impression voulus par l'auteur

// PrintCopies, PrintPageRanges, PrintScaling et PrintPaperHandling sont
// les entrées du dictionnaire des préférences de visionneuse — elles
// encodent ce que l'auteur du document voulait voir comme valeurs par
// défaut des lecteurs 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; // plages base un
  for Range in Ranges do
    Log.Add(Format('Pages %d..%d', [Range.First, Range.Last]));

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

Actions JavaScript du document

Énumérer le JavaScript de l'arbre de noms

// Le JavaScript de niveau document est stocké dans l'arbre de noms
// /Names /JavaScript. JavaScriptAction[i] renvoie une entrée ;
// JavaScriptActions renvoie le tableau complet.
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;

Conseils de performance

Optimiser les opérations 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;

// Chemin de rendu sans copie (par défaut depuis v1.21.0) : RenderPage
// écrit directement dans le tampon DIB du TBitmap de destination lorsque
// les appelants passent un bitmap pré-alloué, évitant une copie de
// largeur * hauteur * 4 octets.
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;