PDFium Component Docs

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;

Agregar 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 carácter y fuente

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\FontPropiedades, Demo\CBuilder\FontPropiedades y Demo\Lazarus\FontPropiedades muestran los mismos metadatos tanto a través de TPdf como de TPdfView.

Generar un reporte multicategoría de extracción de contenido

Los proyectos Demo\Delphi\ContentExtractionLab, Demo\CBuilder\ContentExtractionLab y Demo\Lazarus\ContentExtractionLab convierten la extracción de contenido en un flujo de trabajo unificado. Abren o crean un PDF, permiten al usuario 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 luego generan un resumen junto con un reporte detallado TXT o JSON. El laboratorio también incluye selección de categoría todos/ninguno, copia de reporte al portapapeles y acciones de guardado del PDF de origen, de modo que los usuarios puedan conservar el PDF de muestra generado junto a su reporte de extracción. Los demos más antiguos ExtractText, ExtractImages, Attachment y FontPropiedades permanecen como ejemplos focalizados; ExtractText admite opciones de rango de páginas y separador de páginas para producir salida de texto por página en Delphi, C++Builder y Lazarus, mientras que ContentExtractionLab es la muestra de inventario transversal.

Operaciones de 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 ingresados en formularios dentro de la página

// Sin aplanado, FormField[i] := value escribe únicamente la entrada /V —
// la apariencia visible proviene del stream /AP y podría no refrescarse.
// GenerateFormAppearances regenera /AP para cada widget; luego
// FlattenAllPages integra las anotaciones y widgets de cada página en
// contenido permanente y 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;

Agregar 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 de 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 de página

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 estén comprimidos con FlateDecode (default)
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 a 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 proporciona
// un 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 (por ejemplo, pipelines de impresión CMYK)
var
  Opts: TPdfASaveOptions;
begin
  Opts := Default(TPdfASaveOptions);
  Opts.Conformance    := pac1b;
  Opts.IccProfileData := TFile.ReadAllBytes('C:\Profiles\Coated_GRACoL_2006.icc');
  Pdf1.SaveAsPdfA('C:\Report.cmyk.pdfa.pdf', Opts);
end;

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

  // Sonda barata de estilo cabecera — lee la conformidad sin iterar
  // 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óstico por problema
  Validation := Pdf1.ValidatePdfA;
  Memo1.Lines.Add('Conformance: ' + GetEnumName(TypeInfo(TPdfAConformance),
    Ord(Validation.Conformance)));
  for Issue in Validation.Issues do
    Memo1.Lines.Add('- ' + Issue.Descripción);
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 conteo de problemas en una sola grilla. Sus botones de guardado llaman a SaveAsPdfA, SaveAsPdfUa, SaveAsPdfE, SaveAsPdfX, SaveAsPdfR y SaveAsPdfVT, de modo que el flujo de salida con marcadores es visible sin necesidad de escribir un arnés personalizado.

Generar reportes preflight

La unidad FPdfPreflightReport envuelve ValidatePdfA, ValidatePdfUa, ValidatePdfE, ValidatePdfX, ValidatePdfR y ValidatePdfVT en un único objeto de reporte. Demo\Delphi\PreflightReport, Demo\Lazarus\PreflightReport y Demo\CBuilder\PreflightReport permiten al usuario elegir estándares, generar un resumen, inspeccionar el estado, prioridad, categorías de problemas, conteos de categoría, próximas acciones, códigos de problema y acciones recomendadas, alternar la vista previa entre texto, Markdown, JSON y CSV, y exportar reportes TXT, HTML, Markdown, JSON o CSV. La muestra GUI de Delphi también añade una vista previa de Action Plan que condensa las normas no aprobadas, prioridades, códigos de problema y recomendaciones en una lista de remediación. Demo\Delphi\PreflightReportCli expone la misma ruta de reporte como un flujo de trabajo 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 de reporte en un solo archivo con attach=output.pdf, códigos de salida para gating en CI con failon=, procesamiento de batch=list.txt, escaneo de directorio batchdir=folder con recursión opcional y procesamiento estable ordenado por ruta, enrutamiento de reportes con outdir=reports, nombres de reporte resistentes a colisiones para nombres de archivo de entrada duplicados, resúmenes TXT / JSON / HTML con totales agregados y por estándar, manifiestos de fila CSV con la configuración de ejecución repetida por elemento y un modo de muestra sin argumentos para scripts y tareas de CI. El reporte generado indica que los validadores incorporados cubren verificaciones a nivel de marcador y a nivel de archivo seleccionadas, de modo que el preflight completo a nivel de contenido puede delegarse a un motor de validación dedicado.

Carga en streaming y documentos grandes

Cargar un PDF bajo demanda con LoadCustomDocument

// LoadCustomDocument permite que PDFium obtenga bloques desde cualquier TStream
// con búsqueda bajo demanda. El stream NO se copia a memoria, de modo que los PDF
// de varios GB, streams de cuerpo HTTP remotos y fuentes respaldadas por bases
// de datos son viables sin un búfer previo.
var
  FileStream: TFileStream;
begin
  // Streaming desde archivo (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 de propiedad externa (el llamador lo libera después de 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 cooperativos

// FPdfAsync expone IPdfCancellationToken /
// IPdfCancellationTokenSource para semántica de cancelar-desde-UI que funciona
// a lo largo de las rutas de exportación y renderizado de larga duración.
uses FPdfAsync;

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

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

  // El worker verifica 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 progresivo cancelable de página

// RenderPageProgressive permite que un renderizado largo a alto DPI aborte a la mitad
// de la página cuando el usuario hace clic en Cancelar, en lugar de bloquear al llamador hasta terminar.
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> lanza un worker en un hilo en segundo plano y envía
// un envoltorio TPdfFutureResult<T> de vuelta al hilo principal.
//
// Importante: TPdf serializa las llamadas de renderizado por instancia, pero el worker sigue
// siendo el propietario del despacho y del ciclo de vida del documento. No mute un TPdf mientras un renderizado
// está en curso; la exportación por lotes generalmente mantiene un TPdf por archivo.
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 el FS_MATRIX de PDFium para que los llamadores puedan construir
// una transformación de manera declarativa y entregarla 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 1 pulg a la derecha, ~2,8 pulg arriba
    M.Scale(0.5, 0.5);          // estampa a la mitad del tamaño
    M.Rotate(15);               // rotación CCW de 15 grados
    // M.HorizontalFlip;
    // M.VerticalFlip;
    // M.CentralFlip;
    // M.Skew(10, 5);
    // M.Multiply(Other);       // multiplicar a la derecha con otra matriz

    // Aplicar a cualquier objeto de página de PDFium (text / path / image / 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 arreglo explícito de índices base cero desde un
// PDF de origen a este documento. InsertAt = 0 inserta antes de la página 1;
// PageCount agrega 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 (base uno: páginas 1, 3, 5) desde Source
    // y agregarlas al final de Pdf1.
    Pdf1.ImportPagesByIndex(Source, [0, 2, 4], Pdf1.PageCount);

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

Salida compuesta N-up

// ImportNPagesToOne devuelve un TPdf nuevo cuyas páginas son compuestos
// NumX*NumY de ESTE documento. OutputWidth/Height son unidades de usuario PDF
// (1 unidad = 1/72 pulg).
var
  Composite: TPdf;
begin
  Pdf1.FileName := 'C:\Slides.pdf';
  Pdf1.Active   := True;
  // 4-up A4 horizontal: 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 en sitio

// MovePages toma los índices base cero de las páginas que se desean mover más
// el índice de destino para la primera página movida después de que el movimiento se completa.
// Las demás páginas se desplazan alrededor del bloque movido, de modo que la longitud del documento
// permanece la misma.
//
// 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 desde
// cualquier página de cualquier otro TPdf. Estampe ese handle en tantas páginas de
// ESTE documento como desee — cada estampa es un único objeto de página PDFium
// que se puede posicionar, escalar y rotar a través de 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 de trabajo ejecutable: crean un PDF de destino y un PDF de estampa reutilizable, envuelven la página de estampa 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 agregan etiquetas opcionales de número de página antes de guardar el resultado.

Inserción de imágenes (Overloads directos de AddImage)

Agregar un archivo de imagen registrado o un TBitmap

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

// AddImage(TBitmap) omite el intermediario TPicture, ideal cuando el
// bitmap ya proviene de renderizado o de 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 trabajo completo por lotes: seleccionar múltiples archivos 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 limpia 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 programáticamente la superposición antes de que cambie la 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 (revierte a pfmNone).
PdfView1.FitMode := pfmFitPage;       // página completa en el viewport
// PdfView1.FitMode := pfmFitWidth;   // el ancho de la página coincide con el ancho 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 desplazamiento oscura mientras la
// página PDF permanece blanca como papel.
PdfView1.Color     := clBlack;        // área de desplazamiento
PdfView1.PageColor := clWhite;        // fondo de la página PDF

// Sombra paralela y borde de página opcionales para elevar 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 formulario compacto: abrir o crear un PDF de muestra, cambiar DisplayMode, aplicar FitMode, cambiar PageColor, rotar páginas, resaltar coincidencias de búsqueda de la página actual con HighlightSearchText, habilitar o deshabilitar la selección de texto por el usuario, inspeccionar SelectedText, invocar SelectAll, CopySelectionToClipboard y ClearSelection, y observar las coordenadas DeviceToPage actualizándose en la barra de estado mientras el mouse 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 trabajo de búsqueda y selección del visor en muestras independientes. Abren o crean un PDF, listan resultados de búsqueda en todas las páginas con número de página, índice de carácter y texto de vista previa, soportan navegación Anterior / Siguiente y salto a un resultado con doble clic, resaltan coincidencias de la página actual con HighlightSearchText, exponen opciones de coincidencia de mayúsculas y palabra completa, alternan AllowUserTextSelection, previsualizan SelectedText, invocan SelectAll, CopySelectionToClipboard y ClearSelection desde botones, y permiten al usuario cambiar entre modos de visualización simple y en vista doble.

Leer 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 rangos proporcionados por el autor en las entradas de impresión, y muestran cómo SetPdfPrintPaperHandlingDevMode mapea la intención dúplex PDF a 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 trabajo de impresión estándar: abrir un PDF, previsualizar páginas, elegir rangos de páginas y copias en el diálogo de impresión de la plataforma, respetar el modo de colación, renderizar cada página a través de TPdf.RenderPage, y exponer el progreso de impresión con cancelación. La muestra Delphi también acepta archivos PDF arrastrados para una carga rápida.

Visualización rápida de varias páginas en MultiPageViewer

Los proyectos Demo\Delphi\MultiPageViewer, Demo\CBuilder\MultiPageViewer y Demo\Lazarus\MultiPageViewer ejercitan la visualización continua, en vista doble y consciente de portada con flujos de trabajo regulares de navegación de páginas y zoom. También son útiles para probar la velocidad de apertura en la primera página y la responsividad de renderizado en PDF muy grandes: en pruebas con archivos grandes, documentos de 2,x GB que pueden hacer que Acrobat o Foxit tarden 20-30 segundos antes de la primera vista pueden volverse utilizables casi de inmediato a través de la ruta del visor 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 vista activa, comandos opcionales de navegación sincronizada, presets de zoom compartidos y rotación para la vista seleccionada o todas las vistas cargadas. Use estas muestras para comparar documentos grandes manteniendo la primera vista utilizable y el renderizado de páginas posterior responsivo; los mismos archivos PDF de 2,x GB que pueden tardar 20-30 segundos en abrirse en Acrobat o Foxit pueden volverse utilizables casi de inmediato en la ruta del visor PDFiumPas.

Auditoría de la superficie de riesgo PDF en SecurityAudit

Los proyectos Demo\Delphi\SecurityAudit, Demo\CBuilder\SecurityAudit y Demo\Lazarus\SecurityAudit abren un PDF de solo lectura y listan el estado de permisos, archivos adjuntos incrustados, acciones JavaScript del documento, anotaciones de enlace URI y Launch, enlaces web, resúmenes de firma, estado XFA, disponibilidad de helpers V8 y callbacks de características no soportadas. Las mismas filas de resumen y de hallazgo pueden guardarse o copiarse como un reporte de auditoría TXT o guardarse como JSON estructurado. Están pensados como paneles de riesgo de la aplicación anfitriona, no como detección de malware.

Manejo de enlaces en el visor

Clic-para-seguir seguro con listas de permitidos por acción

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

// OnAnnotationLinkClick se dispara para destinos dentro del documento (saltos de página,
// destinos con nombre, cadenas de acción). Marque Handled = True para evitar que el
// visor siga automáticamente el enlace.
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 cadenas URI descubiertas por el escáner de enlaces web
// de PDFium (URL 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

// Sobre el widget AcroForm enfocado, seis métodos nuevos manejan el historial
// de edición de formularios de PDFium. Los seis cortocircuitan de manera segura si ningún widget está enfocado o
// si el documento no lleva 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;

Restringir el visor para kioscos

Deshabilitar los gestos de navegación de páginas

// Los tres tienen True como valor predeterminado, de modo que las aplicaciones existentes se actualizan sin cambios
// de comportamiento. Cámbielos a False para kioscos, paneles de vista previa o integraciones
// de solo lectura sin necesidad de subclasear TPdfView.
PdfView1.AllowUserPageChange       := False;  // PgUp/PgDn/Ctrl+Inicio/End
PdfView1.ChangePageOnMouseScrolling := False; // saltos con rueda arriba/abajo
PdfView1.AllowUserTextSelection    := False;  // selección de texto por mouse/teclado

// Ctrl+rueda está reservado para zoom y es omitido por el manejador
// de navegación independientemente de ChangePageOnMouseScrolling.

Panel lateral de miniaturas

Vincular un TPdfThumbnailView con 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 base cero
end;

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

Exportación por lotes cancelable cooperativamente

Varios archivos PDF a JPG con progreso por archivo y por página

// Patrón utilizado por Demo\Delphi\BatchExport, Demo\CBuilder\BatchExport
// y Demo\Lazarus\BatchExport. Un TPdf por archivo, IPdfCancellationToken
// para cancelar-desde-UI, TPdfStreamAdapter para carga de archivo 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 prueba de impacto

Ajustar la página actual e inspeccionar el texto bajo el mouse

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 una sonda barata del catálogo — True cuando existe /StructTreeRoot.
// StructureElements materializa el árbol en un arreglo plano de
// registros TPdfStructureElement con tipo, título, texto alternativo, texto real,
// texto de expansión, idioma, nivel, índice del padre, y conteos 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 el modo de impresión EMF / PostScript / image-mask

// SetPdfPrintMode es una función global que mapea al
// FPDF_SetPrintMode de PDFium. Use el valor TPdfPrintMode correspondiente para el
// controlador de impresora en uso.
SetPdfPrintMode(pmEmf);                   // GDI EMF (default)
// SetPdfPrintMode(pmTextOnly);           // renderizado solo de texto
// SetPdfPrintMode(pmPostScript2);        // PostScript Level 2
// SetPdfPrintMode(pmPostScript3);        // PostScript Level 3
// SetPdfPrintMode(pmPostScript2PassThrough);
// SetPdfPrintMode(pmPostScript3PassThrough);
// SetPdfPrintMode(pmEmfImageMasks);      // EMF + image masks
// SetPdfPrintMode(pmPostScript3Type42);  // PostScript 3 + fuentes Type 42

Leer la configuración de impresión deseada 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 tomaran como valor predeterminado.
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 base 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 arreglo 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 (default 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 ancho * alto * 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;