Documentación de PDFiumPas

Ejemplos de programación de PDFium VCL

Operaciones básicas con documentos

Cargar y mostrar un documento 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;

Crear un nuevo documento 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;

Operaciones de texto

Extraer texto de 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;

Buscar texto

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;

Añadir texto con formato

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;

Inspeccionar metadatos de caracteres y fuentes

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;

Los proyectos Demo\Delphi\FontProperties, Demo\CBuilder\FontProperties y Demo\Lazarus\FontProperties muestran los mismos metadatos tanto a través de TPdf como de TPdfView.

Construir un informe de extracción de contenido multicategoría

Los proyectos Demo\Delphi\ContentExtractionLab, Demo\CBuilder\ContentExtractionLab y Demo\Lazarus\ContentExtractionLab convierten la extracción de contenido en un flujo de trabajo único. Abren o crean un PDF, permiten a los usuarios elegir metadatos, texto de página, objetos de página, imágenes, archivos adjuntos, enlaces, anotaciones, marcadores y métricas de fuente / carácter, y a continuación generan un resumen junto con un informe detallado TXT o JSON. El laboratorio incluye también selección de categorías todas / ninguna, copia del informe al portapapeles y acciones de guardado del PDF de origen para que los usuarios puedan conservar el PDF de ejemplo generado junto a su informe de extracción. Los demos anteriores ExtractText, ExtractImages, Attachment y FontProperties siguen siendo ejemplos focalizados; ExtractText admite opciones de rango de páginas y separador de páginas para generar texto de salida por página en Delphi, C++Builder y Lazarus, mientras que ContentExtractionLab es la muestra de inventario transversal.

Operaciones con formularios

Trabajar con campos de formulario

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;

Aplanar los valores rellenados del formulario en la página

// Sin aplanar, FormField[i] := value escribe solo la entrada /V —
// la apariencia visible proviene del stream /AP y puede no refrescarse.
// GenerateFormAppearances regenera /AP para cada widget, luego
// FlattenAllPages incorpora las anotaciones y widgets de todas las
// páginas como contenido permanente no editable.
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;

Operaciones con imágenes

Extraer imágenes

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;

Añadir imágenes

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;

Operaciones con anotaciones

Trabajar con anotaciones

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;

Operaciones de dibujo

Dibujar formas

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;

Navegación y marcadores

Trabajar con marcadores

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;

Trabajar con destinos con nombre

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;

Operaciones con archivos adjuntos

Trabajar con archivos adjuntos

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;

Gestión de páginas

Operaciones con páginas

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

Guardado y compresión

Guardar con compresión

// Crear un documento PDF comprimido
Pdf1.CreateDocument;
Pdf1.Compressed := True; // garantizar que todos los streams se comprimen con FlateDecode (por defecto)
Pdf1.Active := True;
Pdf1.AddPage(0, 595, 842); // tamaño A4
Pdf1.AddText('Hello World!', 'Arial', 24, 100, 700, clBlack, $FF, 0.0);
Pdf1.SaveAs('C:\Compressed.pdf');
Pdf1.Active := False;

// Guardar con opciones
Pdf1.SaveAs('C:\Output.pdf', saIncremental); // guardado incremental
Pdf1.SaveAs('C:\NoSecurity.pdf', saRemoveSecurity); // eliminar seguridad

// Guardar para una versión PDF de destino específica. El validador de versión PDF rechaza
// cualquier característica introducida después de la versión de destino elegida (JBIG2Decode, MarkInfo,
// cifrado AES, anotaciones 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');

Crear salida de archivo PDF/A-1b

// SaveAsPdfA postprocesa el guardado base con una actualización incremental que
// inyecta un stream de metadatos XMP, un OutputIntent ICC sRGB y un catálogo
// de documento actualizado, de modo que el resultado sea conforme a PDF/A-1b.
// El perfil sRGB IEC61966-2.1 incluido se utiliza automáticamente cuando no se
// suministra ningún perfil personalizado.
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 con un perfil ICC personalizado (canalizaciones de impresión CMYK, por ejemplo)
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;

// Salida PDF/A en memoria
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;

Validar la conformidad PDF/A

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

  // Sondeo barato de tipo cabecera — lee la conformidad sin iterar
  // por el validador completo. Resultado: pacNone, pac1a, pac1b, pac2b, pac3b, o
  // 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;

  // Validador completo con diagnósticos por incidencia
  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;

Inspeccionar varios estándares PDF en StandardsLab

Los proyectos Demo\Delphi\StandardsLab, Demo\CBuilder\StandardsLab y Demo\Lazarus\StandardsLab cargan o crean un PDF, ejecutan ValidatePdfA, ValidatePdfUa, ValidatePdfE, ValidatePdfX, ValidatePdfR y ValidatePdfVT, y luego muestran cada nivel de conformidad detectado y el recuento de incidencias en una sola cuadrícula. Sus botones de guardado invocan SaveAsPdfA, SaveAsPdfUa, SaveAsPdfE, SaveAsPdfX, SaveAsPdfR y SaveAsPdfVT para que el flujo de salida con marcadores sea visible sin escribir un arnés personalizado.

Generar informes de preflight

La unidad FPdfPreflightReport envuelve ValidatePdfA, ValidatePdfUa, ValidatePdfE, ValidatePdfX, ValidatePdfR y ValidatePdfVT en un único objeto de informe. Demo\Delphi\PreflightReport, Demo\Lazarus\PreflightReport y Demo\CBuilder\PreflightReport permiten a los usuarios elegir estándares, generar un resumen, inspeccionar estado, prioridad, categorías de incidencia, recuentos por categoría, siguientes acciones, códigos de incidencia y acciones recomendadas, alternar la previsualización entre texto, Markdown, JSON y CSV, y exportar informes TXT, HTML, Markdown, JSON o CSV. La muestra GUI de Delphi también añade una vista previa de Action Plan que condensa los estándares no aprobados, prioridades, códigos de problema y recomendaciones en una lista de corrección. Demo\Delphi\PreflightReportCli expone la misma ruta de informe como un flujo de consola con argumentos de entrada / salida, contraseña opcional, selección de formato de salida que incluye plan / actionplan, filtrado de estándares, incrustación opcional del informe en un único fichero attach=output.pdf, códigos de salida para gating de CI failon=, procesamiento batch=list.txt, exploración de directorios batchdir=folder con recursión opcional y procesamiento estable ordenado por ruta, enrutamiento de informes outdir=reports, nombres de informe a prueba de colisiones para nombres de fichero de entrada duplicados, resúmenes TXT / JSON / HTML con totales agregados y de estado por estándar, manifiestos de filas CSV con la configuración de la ejecución repetida por elemento, y un modo de muestra sin argumentos para scripts y trabajos de CI. El informe generado indica que los validadores integrados cubren comprobaciones a nivel de marcador y a nivel de fichero seleccionado, por lo que el preflight completo a nivel de contenido se puede seguir delegando a un motor de validación dedicado.

Carga en streaming y documentos grandes

Cargar un PDF bajo demanda con LoadCustomDocument

// LoadCustomDocument permite a PDFium obtener bloques de cualquier TStream
// con seek bajo demanda. El stream NO se copia en memoria, por lo que los PDF de varios GB,
// los streams de cuerpo HTTP remotos y las fuentes respaldadas por base de datos son
// todos viables sin un búfer previo íntegro.
var
  FileStream: TFileStream;
begin
  // Streaming de lectura desde fichero (sin copia en memoria)
  FileStream := TFileStream.Create('C:\Huge.pdf',
    fmOpenRead or fmShareDenyWrite);
  Pdf1.LoadCustomDocument(FileStream, True{AOwnsStream});
  // ^^ propiedad transferida — Pdf1 libera FileStream en UnloadDocument.
  Pdf1.Active := True;
  ShowMessage('Pages: ' + IntToStr(Pdf1.PageCount));
end;

// Stream propiedad externa (el llamador lo libera tras 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;

Cancelación asíncrona y renderizado progresivo

Tokens de cancelación cooperativa

// FPdfAsync expone IPdfCancellationToken /
// IPdfCancellationTokenSource para semánticas de cancelación desde la UI que funcionan
// a lo largo de las rutas largas de exportación y renderizado.
uses FPdfAsync;

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

  // ... el usuario hace clic en Cancelar ...
  CancelSource.Cancel;

  // El trabajador comprueba periódicamente CancelToken.IsCancelled, o la
  // ruta de renderizado lo consulta a través del callback IFSDK_PAUSE de PDFium.
  if CancelToken.IsCancelled then
    Memo1.Lines.Add('Cancelled by user');
end;

Renderizado de página progresivo y cancelable

// RenderPageProgressive permite que un renderizado largo a alta DPI se cancele
// a mitad de página cuando el usuario hace clic en Cancelar, en lugar de bloquear al
// llamador hasta el final.
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;

Ejecutar renderizados en un hilo en segundo plano con TPdfFuture

// TPdfFuture<T> arranca un trabajador en un hilo en segundo plano y devuelve
// un sobre TPdfFutureResult<T> al hilo principal.
//
// Importante: TPdf serializa las llamadas de renderizado por instancia, pero el trabajador
// sigue siendo responsable del despacho y del ciclo de vida del documento. No mute un TPdf
// mientras hay un renderizado en curso; la exportación por lotes suele mantener un TPdf por fichero.
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 transformación

Componer Translate / Scale / Rotate / Skew

// FPdfMatrix envuelve FS_MATRIX de PDFium para que los llamadores puedan construir una transformación
// de forma declarativa y pasarla a FPDFPageObj_SetMatrix.
uses FPdfMatrix, FPdfView;

var
  M: TPdfMatrix;
  RawMatrix: FS_MATRIX;
  PageObj: FPDF_PAGEOBJECT;
begin
  M := TPdfMatrix.Create;
  try
    M.Translate(72, 200);       // mover 1in a la derecha, ~2.8in hacia arriba
    M.Scale(0.5, 0.5);          // sello a la mitad de tamaño
    M.Rotate(15);               // rotación CCW de 15 grados
    // M.HorizontalFlip;
    // M.VerticalFlip;
    // M.CentralFlip;
    // M.Skew(10, 5);
    // M.Multiply(Other);       // postmultiplicar con otra matriz

    // Aplicar a cualquier objeto de página PDFium (texto / trayectoria / imagen / form XObject)
    RawMatrix := M.Handle;
    FPDFPageObj_SetMatrix(PageObj, RawMatrix);
  finally
    M.Free;
  end;
end;

Composición de páginas

Importar un subconjunto de páginas por índice

// ImportPagesByIndex copia un array de índices basado en cero desde un
// PDF de origen al documento. InsertAt = 0 inserta antes de la página 1;
// PageCount añade al final.
var
  Source: TPdf;
begin
  Source := TPdf.Create(nil);
  try
    Source.FileName := 'C:\Report.pdf';
    Source.Active   := True;

    // Importar páginas 0, 2, 4 (basado en uno: páginas 1, 3, 5) desde Source
    // y añadirlas al final de Pdf1.
    Pdf1.ImportPagesByIndex(Source, [0, 2, 4], Pdf1.PageCount);

    // Un array vacío importa todas las páginas del origen
    // Pdf1.ImportPagesByIndex(Source, [], 0);
  finally
    Source.Free;
  end;
end;

Salida composite N-up

// ImportNPagesToOne devuelve un TPdf totalmente nuevo cuyas páginas son composiciones
// NumX*NumY de ESTE documento. OutputWidth/Height son unidades de usuario PDF
// (1 unidad = 1/72 in).
var
  Composite: TPdf;
begin
  Pdf1.FileName := 'C:\Slides.pdf';
  Pdf1.Active   := True;
  // A4 horizontal con 4-up: 842 x 595 pt, 2 columnas x 2 filas
  Composite := Pdf1.ImportNPagesToOne(842, 595, 2, 2);
  try
    if Composite <> nil then
      Composite.SaveAs('C:\Slides_4up.pdf');
  finally
    Composite.Free;
  end;
end;

Reordenar páginas in situ

// MovePages toma los índices basados en cero de las páginas a mover, más el
// índice de destino de la primera página movida tras completarse el movimiento.
// El resto de páginas se desplazan alrededor del bloque movido para que la longitud del documento
// se mantenga igual.
//
// Ejemplo: en un documento de 5 páginas con páginas numeradas [0,1,2,3,4],
// MovePages([2, 3], 0) reordena a [2, 3, 0, 1, 4].
if not Pdf1.MovePages([2, 3], 0) then
  ShowMessage('Invalid indices passed to MovePages');

Marca de agua mediante reutilización de página con Form XObject

// CreateXObjectFromPage fabrica un handle Form XObject reutilizable a partir
// de cualquier página de cualquier otro TPdf. Estampe ese handle sobre tantas páginas
// de ESTE documento como desee — cada sello es un único objeto de página PDFium
// que puede posicionarse, escalarse y rotarse mediante la API de matrices.
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;

    // Envolver la página 0 de WatermarkSource como un XObject reutilizable
    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;

        // Centrar la marca de agua en la página
        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; // cierra el handle FPDF_XOBJECT
    end;

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

Los proyectos Demo\Delphi\WatermarkStamp, Demo\Lazarus\WatermarkStamp y Demo\CBuilder\WatermarkStamp convierten este patrón en un flujo ejecutable: crean un PDF de destino y un PDF de sello reutilizable, envuelven la página del sello con CreateXObjectFromPage, insertan el mismo Form XObject en cada página de destino, aplican transformaciones TPdfMatrix para marcas de agua centradas y sellos en la esquina superior derecha, y añaden etiquetas opcionales con el número de página antes de guardar el resultado.

Inserción de imágenes (sobrecargas directas de AddImage)

Añadir un fichero de imagen registrado o un TBitmap

// AddImage(FileName) acepta cualquier formato registrado en las unidades gráficas
// VCL o LCL (BMP, PNG vía PngImage, JPG, ...).
Pdf1.AddImage('C:\Photo.png', 100, 500, 200, 150);

// AddImage(TBitmap) omite el intermediario TPicture, ideal cuando el
// bitmap ya proviene de un renderizado o de un trabajo de generación por lotes.
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;

Los proyectos Demo\Delphi\ImageToPDF, Demo\CBuilder\ImageToPDF y Demo\Lazarus\ImageToPDF convierten esto en un flujo de lote completo: seleccionar varios ficheros de imagen, previsualizar el elemento seleccionado, crear una página PDF por imagen, escalar cada imagen en una página A4 vertical u horizontal, guardar el resultado y abrir el PDF generado.

Resaltado de búsqueda en el visor

Resaltar cada coincidencia en la página actual

var
  MatchCount: Integer;
begin
  // Pintar una máscara clYellow sobre cada coincidencia en la página actual del visor.
  // Cambiar a una página diferente borra el resaltado automáticamente.
  PdfView1.HighlightColor := clYellow;
  MatchCount := PdfView1.HighlightSearchText('invoice', False{Case}, True{Word});
  Status.Caption := Format('%d match(es) on this page', [MatchCount]);

  // Eliminar la superposición programáticamente antes de cambiar de página
  PdfView1.ClearHighlight;
end;

Diseño del visor y color de página

Modo de ajuste y fondo de página

// FitMode mantiene los documentos largos encuadrados durante Resize o cambios de página.
// Establecer Zoom directamente cancela FitMode (vuelve a pfmNone).
PdfView1.FitMode := pfmFitPage;       // página completa en el viewport
// PdfView1.FitMode := pfmFitWidth;   // el ancho de la página coincide con el del viewport
// PdfView1.FitMode := pfmActualSize; // 100%

// PageColor desacopla la página PDF renderizada del Color del control anfitrión
// — la configuración típica de visor en modo oscuro mantiene el área de scroll oscura mientras
// la página PDF permanece blanca como papel.
PdfView1.Color     := clBlack;        // área de scroll
PdfView1.PageColor := clWhite;        // fondo de la página PDF

// Sombra paralela y borde de página opcionales para destacar la página sobre un fondo oscuro
PdfView1.PageShadowSize  := 6;
PdfView1.PageShadowColor := $00404040;
PdfView1.PageBorderColor := clGray;

Explorar la interacción del visor en ViewerInteractionLab

Los proyectos Demo\Delphi\ViewerInteractionLab, Demo\CBuilder\ViewerInteractionLab y Demo\Lazarus\ViewerInteractionLab mantienen la superficie moderna de interacción de TPdfView en un único formulario compacto: abra o cree un PDF de muestra, cambie DisplayMode, aplique FitMode, modifique PageColor, rote páginas, resalte coincidencias de búsqueda en la página actual con HighlightSearchText, habilite o deshabilite la selección de texto por parte del usuario, inspeccione SelectedText, llame a SelectAll, CopySelectionToClipboard y ClearSelection, y observe cómo las coordenadas DeviceToPage se actualizan en la barra de estado mientras el ratón se mueve sobre la página.

Buscar y seleccionar texto en SearchAndSelect

Los proyectos Demo\Delphi\SearchAndSelect, Demo\CBuilder\SearchAndSelect y Demo\Lazarus\SearchAndSelect convierten el flujo de búsqueda y selección del visor en muestras independientes. Abren o crean un PDF, listan los resultados de búsqueda de todas las páginas con número de página, índice de carácter y texto de previsualización, soportan navegación Previo / Siguiente y salto a resultado con doble clic, resaltan las coincidencias de la página actual con HighlightSearchText, exponen las opciones de distinción de mayúsculas y palabra completa, alternan AllowUserTextSelection, previsualizan SelectedText, controlan SelectAll, CopySelectionToClipboard y ClearSelection desde botones, y permiten a los usuarios alternar entre los modos de visualización de página única y de doble página.

Leer las preferencias de impresión PDF en PrintPreferences

Los proyectos Demo\Delphi\PrintPreferences, Demo\CBuilder\PrintPreferences y Demo\Lazarus\PrintPreferences leen las preferencias de impresión del autor antes de imprimir. Muestran PrintCopies, PrintPageRanges, PrintScaling y PrintPaperHandling, pueden copiar las copias y los rangos proporcionados por el autor en las entradas de impresión, y muestran cómo SetPdfPrintPaperHandlingDevMode mapea la intención de dúplex del PDF en un DEVMODE de Windows.

Impresión estándar de PDF en PrintPDF

Los proyectos Demo\Delphi\PrintPDF, Demo\CBuilder\PrintPDF y Demo\Lazarus\PrintPDF muestran el flujo de impresión estándar: abren un PDF, previsualizan las páginas, eligen rangos de páginas y copias en el cuadro de diálogo de impresión de la plataforma, respetan el modo de intercalado, renderizan cada página mediante TPdf.RenderPage, y exponen el progreso de impresión con cancelación. La muestra Delphi también acepta ficheros PDF arrastrados para una carga rápida.

Visualización rápida de múltiples páginas en MultiPageViewer

Los proyectos Demo\Delphi\MultiPageViewer, Demo\CBuilder\MultiPageViewer y Demo\Lazarus\MultiPageViewer ejercitan la visualización continua, doble página y con portada con flujos normales de navegación y zoom. También son útiles para probar la velocidad de apertura a la primera página y la capacidad de respuesta del renderizado en PDF muy grandes: en pruebas con ficheros grandes, documentos de 2.x GB que pueden hacer que Acrobat o Foxit tarden entre 20 y 30 segundos antes de la primera vista, pueden volverse usables casi instantáneamente mediante la ruta del visor de PDFiumPas.

Revisión lado a lado en SplitView

Los proyectos Demo\Delphi\SplitView, Demo\CBuilder\SplitView y Demo\Lazarus\SplitView muestran la revisión de PDF lado a lado. Las muestras de C++Builder y Lazarus se centran en la comparación de dos o tres paneles, selección de la vista activa, comandos opcionales de navegación sincronizada, ajustes de zoom compartidos y rotación para la vista seleccionada o todas las vistas cargadas. Utilice estas muestras para comparar documentos grandes mientras se mantiene la primera vista usable y el renderizado de páginas posteriores reactivos; los mismos ficheros PDF de 2.x GB que pueden tardar 20-30 segundos en abrirse en Acrobat o Foxit pueden ser usables casi instantáneamente mediante la ruta del visor de PDFiumPas.

Auditar la superficie de riesgo del PDF en SecurityAudit

Los proyectos Demo\Delphi\SecurityAudit, Demo\CBuilder\SecurityAudit y Demo\Lazarus\SecurityAudit abren un PDF en modo de solo lectura y enumeran el estado de permisos, los archivos adjuntos incrustados, las acciones JavaScript del documento, las anotaciones de enlace URI y Launch, los enlaces web, los resúmenes de firma, el estado XFA, la disponibilidad de helpers V8 y los callbacks de funciones no soportadas. El mismo resumen y las filas de hallazgos pueden guardarse o copiarse como un informe de auditoría TXT o guardarse como JSON estructurado. Están concebidos como paneles de riesgo de la aplicación anfitriona, no como detección de malware.

Manejo de enlaces en el visor

Seguimiento seguro de clics con listas de permitidos por acción

// LinkOptions controla el manejo automático de los cuatro tipos de acción
// de enlace PDF. Los valores por defecto habilitan únicamente goto y URI; launch (ejecutar un programa)
// y embedded GotoR (saltar a otro fichero) permanecen DESACTIVADOS para que un clic accidental no pueda
// ejecutar código arbitrario.
PdfView1.LinkOptions :=
  [loAutoGoto, loAutoOpenURI];  // valores por defecto seguros

// OnAnnotationLinkClick se dispara para destinos dentro del documento (saltos de página,
// destinos con nombre, cadenas de acciones). Marque Handled = True para evitar que la
// vista siga el enlace automáticamente.
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;  // suprimir el manejo automático
  end;
end;

// OnWebLinkClick se dispara para las cadenas URI descubiertas por el escáner de enlaces
// web de PDFium (URLs autodetectadas en el stream de texto, incluso sin una
// anotación /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;

Edición de widgets de formulario en el visor

Selección y deshacer / rehacer

// En el widget AcroForm con foco, seis nuevos métodos controlan el historial
// de edición de formulario de PDFium. Los seis cortocircuitan de forma segura si ningún widget tiene el foco
// o el documento no contiene un 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;

Bloquear el visor para quioscos

Deshabilitar los gestos de navegación por páginas

// Los tres tienen True como valor por defecto para que las aplicaciones existentes se actualicen sin
// cambios de comportamiento. Cámbielos a False para quioscos, paneles de previsualización o
// empotraciones de solo lectura sin necesidad de subclasear TPdfView.
PdfView1.AllowUserPageChange       := False;  // PgUp/PgDn/Ctrl+Inicio/Fin
PdfView1.ChangePageOnMouseScrolling := False; // saltos con rueda arriba/abajo
PdfView1.AllowUserTextSelection    := False;  // selección de texto con ratón/teclado

// Ctrl+rueda está reservado para el zoom y lo omite el manejador de navegación
// independientemente de ChangePageOnMouseScrolling.

Panel lateral de miniaturas

Vincular un TPdfThumbnailView a un TPdfView

// TPdfThumbnailView (unidad FPdfThumbnail) renderiza una miniatura de página por
// fila en un panel lateral desplazable.
uses FPdfThumbnail;

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

procedure TForm1.ThumbViewPageClick(Sender: TObject; PageIndex: Integer);
begin
  PdfView1.PageNumber := PageIndex + 1; // PageIndex es basado en cero
end;

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

Exportación por lotes cancelable de forma cooperativa

PDF a JPG para varios ficheros con progreso por fichero y por página

// Patrón utilizado por Demo\Delphi\BatchExport, Demo\CBuilder\BatchExport,
// y Demo\Lazarus\BatchExport. Un TPdf por fichero, IPdfCancellationToken
// para cancelar desde la UI, TPdfStreamAdapter para carga de fichero con búfer.
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;

Coordenadas del visor y hit testing

Ajustar la página actual e inspeccionar el texto bajo el ratón

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;

Manejo de errores

Operaciones PDF robustas

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;

PDF etiquetado y accesibilidad

Detectar PDF etiquetado y recorrer el árbol de estructura

// IsTagged es un sondeo barato del catálogo — True cuando /StructTreeRoot existe.
// StructureElements materializa el árbol en un array plano de
// records TPdfStructureElement con tipo, título, texto alternativo, texto real,
// texto de expansión, idioma, nivel, índice de padre y recuentos de hijos / contenido
// marcado / atributos.
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;

Selección de modo de impresión

Establecer modo de impresión EMF / PostScript / Image-Mask

// SetPdfPrintMode es una función global que mapea a
// FPDF_SetPrintMode de PDFium. Utilice el valor TPdfPrintMode correspondiente para el
// controlador de impresora en uso.
SetPdfPrintMode(pmEmf);                   // GDI EMF (por defecto)
// SetPdfPrintMode(pmTextOnly);           // renderizado solo de texto
// SetPdfPrintMode(pmPostScript2);        // PostScript Nivel 2
// SetPdfPrintMode(pmPostScript3);        // PostScript Nivel 3
// SetPdfPrintMode(pmPostScript2PassThrough);
// SetPdfPrintMode(pmPostScript3PassThrough);
// SetPdfPrintMode(pmEmfImageMasks);      // EMF + máscaras de imagen
// SetPdfPrintMode(pmPostScript3Type42);  // PostScript 3 + fuentes Type 42

Leer la configuración de impresión sugerida por el autor

// PrintCopies, PrintPageRanges, PrintScaling y PrintPaperHandling son
// las entradas del diccionario de preferencias del visor — codifican lo que el
// autor del documento quería que los lectores PDF eligieran por defecto.
var
  Ranges: TPrintPageRanges;
  Range: TPrintPageRange;
begin
  ShowMessage('Suggested copies: ' + IntToStr(Pdf1.PrintCopies));
  ShowMessage('Print scaling: ' + GetEnumName(TypeInfo(TPrintScaling),
    Ord(Pdf1.PrintScaling)));

  Ranges := Pdf1.PrintPageRanges; // rangos basados en uno
  for Range in Ranges do
    Log.Add(Format('Pages %d..%d', [Range.First, Range.Last]));

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

Acciones JavaScript del documento

Enumerar el JavaScript del árbol de nombres

// El JavaScript a nivel de documento se almacena en el árbol de nombres
// /Names /JavaScript. JavaScriptAction[i] devuelve una entrada; JavaScriptActions devuelve
// el array completo.
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;

Consejos de rendimiento

Optimizar las operaciones 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;

// Ruta de renderizado zero-copy (por defecto desde v1.21.0): RenderPage escribe
// directamente en el búfer DIB del TBitmap de destino cuando los llamadores pasan un
// bitmap preasignado, evitando una copia de anchura * altura * 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;