Pdf1.FileName := 'C:\MyDocument.pdf';
Pdf1.Active := True;
ShowMessage('Pages: ' + IntToStr(Pdf1.PageCount));
ShowMessage('Title: ' + Pdf1.Title);
ShowMessage('Author: ' + Pdf1.Author);
var
Stream: TMemoryStream;
begin
Stream := TMemoryStream.Create;
try
Stream.LoadFromFile('C:\MyDocument.pdf');
Pdf1.LoadDocument(Stream, True);
Pdf1.Active := True;
ShowMessage('PDF loaded from stream');
finally
Stream.Free;
end;
end;
Pdf1.CreateDocument;
Pdf1.Active := True;
Pdf1.AddPage(0, 612, 792);
Pdf1.PageNumber := 1;
Pdf1.AddText('Hello World!', 'Arial', 12, 100, 700, clBlack, $FF, 0.0);
Pdf1.UpdatePage;
Pdf1.SaveAs('C:\NewDocument.pdf');
Pdf1.Active := False;
var
PageText: WString;
RectText: WString;
begin
Pdf1.PageNumber := 1;
PageText := Pdf1.Text;
Memo1.Text := PageText;
RectText := Pdf1.TextInRectangle(100, 100, 500, 200);
ShowMessage('Text in rectangle: ' + RectText);
end;
var
Position: Integer;
begin
Position := Pdf1.FindFirst('Hello', [seCaseSensitive]);
while Position >= 0 do
begin
ShowMessage('Found at character: ' + IntToStr(Position));
Position := Pdf1.FindNext;
end;
end;
Pdf1.PageNumber := 1;
Pdf1.AddText('Document Title', 'Times New Roman', 16, 100, 750, clBlue);
Pdf1.AddText('This is the document content.', 'Arial', 12, 100, 700, clBlack);
Pdf1.UpdatePage;
var
I: Integer;
MetricSize: Single;
FontBytes: TBytes;
begin
Pdf1.PageNumber := 1;
for I := 0 to Pdf1.ObjectCount - 1 do
if Pdf1.ObjectType[I] = otText then
begin
MetricSize := 12.0;
FontBytes := Pdf1.FontData[I];
Memo1.Lines.Add('Object ' + IntToStr(I));
Memo1.Lines.Add('Base name: ' + Pdf1.FontBaseName[I]);
Memo1.Lines.Add('Family: ' + Pdf1.FontFamilyName[I]);
Memo1.Lines.Add('Embedded: ' + BoolToStr(Pdf1.FontIsEmbedded[I], True));
Memo1.Lines.Add('Weight: ' + IntToStr(Pdf1.FontWeight[I]));
Memo1.Lines.Add('Italic angle: ' + IntToStr(Pdf1.FontItalicAngle[I]));
Memo1.Lines.Add('Ascent: ' + FloatToStr(Pdf1.FontAscent[I, MetricSize]));
Memo1.Lines.Add('Descent: ' + FloatToStr(Pdf1.FontDescent[I, MetricSize]));
Memo1.Lines.Add('Font data bytes: ' + IntToStr(Length(FontBytes)));
end;
if Pdf1.CharacterCount > 0 then
begin
Memo1.Lines.Add('First character size: ' + FloatToStr(Pdf1.CharacterFontSize[0]));
Memo1.Lines.Add('First character weight: ' + IntToStr(Pdf1.CharacterFontWeight[0]));
Memo1.Lines.Add('First character angle: ' + FloatToStr(Pdf1.CharacterAngle[0]));
end;
end;
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.
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.
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;
// 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;
var
I: Integer;
Bitmap: TBitmap;
begin
for I := 0 to Pdf1.BitmapCount - 1 do
begin
Bitmap := Pdf1.Bitmap[I];
try
Bitmap.SaveToFile('C:\ExtractedImage' + IntToStr(I) + '.bmp');
finally
Bitmap.Free;
end;
end;
end;
var
JpegStream: TFileStream;
begin
JpegStream := TFileStream.Create('C:\MyImage.jpg', fmOpenRead or fmShareDenyWrite);
try
Pdf1.AddJpegImage(JpegStream, 100, 500, 200, 150);
finally
JpegStream.Free;
end;
Pdf1.AddPicture(Image1.Picture, 100, 300, 150, 100);
Pdf1.UpdatePage;
end;
var
I: Integer;
Annotation: TPdfAnnotation;
begin
for I := 0 to Pdf1.AnnotationCount - 1 do
begin
Annotation := Pdf1.Annotation[I];
ShowMessage('Annotation ' + IntToStr(I) + ': ' + Annotation.ContentsText);
end;
end;
Pdf1.CreatePath(100, 100, 200, 150, fmAlternate, clYellow, $FF, True, clBlack, $FF, 2.0);
Pdf1.AddPath;
Pdf1.CreatePath(100, 100, fmNone, clBlack, $FF, True, clRed, $FF, 3.0);
Pdf1.LineTo(200, 100);
Pdf1.LineTo(150, 200);
Pdf1.ClosePath;
Pdf1.AddPath;
Pdf1.UpdatePage;
var
I: Integer;
Bookmarks: TBookmarks;
begin
Bookmarks := Pdf1.Bookmarks;
for I := 0 to Length(Bookmarks) - 1 do
ShowMessage(Bookmarks[I].Title + ' -> Page ' + IntToStr(Bookmarks[I].PageNumber));
end;
var
I: Integer;
Destination: TDestination;
begin
for I := 0 to Pdf1.DestinationCount - 1 do
begin
Destination := Pdf1.Destination[I];
if Destination.Name = 'Chapter1' then
begin
Pdf1.PageNumber := Destination.PageNumber;
Break;
end;
end;
end;
var
I: Integer;
AttachmentData: TBytes;
AttachmentName: WString;
FileStream: TFileStream;
begin
for I := 0 to Pdf1.AttachmentCount - 1 do
begin
AttachmentName := Pdf1.AttachmentName[I];
AttachmentData := Pdf1.Attachment[I];
FileStream := TFileStream.Create('C:\Extracted_' + string(AttachmentName), fmCreate);
try
if Length(AttachmentData) > 0 then
FileStream.WriteBuffer(AttachmentData[0], Length(AttachmentData));
finally
FileStream.Free;
end;
end;
if Pdf1.CreateAttachment('MyFile.txt') then
Pdf1.Attachment[Pdf1.AttachmentCount - 1] := TFile.ReadAllBytes('C:\MyFile.txt');
end;
Pdf1.AddPage(Pdf1.PageCount + 1, 612, 792);
Pdf1.AddPage(Pdf1.PageCount + 1, 595, 842);
Pdf1.AddPage(Pdf1.PageCount + 1, 400, 600);
var
SourcePdf: TPdf;
begin
SourcePdf := TPdf.Create(nil);
try
SourcePdf.FileName := 'C:\SourceDocument.pdf';
SourcePdf.Active := True;
Pdf1.ImportPages(SourcePdf, '1', Pdf1.PageCount + 1);
finally
SourcePdf.Free;
end;
end;
if Pdf1.PageCount > 1 then
Pdf1.DeletePage(Pdf1.PageCount);
// 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');
// 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;
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;
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.
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.
// 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;
// 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;
// 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;
// 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;
// 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;
// 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;
// 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;
// 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');
// 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.
// 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.
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;
// 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;
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.
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.
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.
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.
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.
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.
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.
// 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;
// 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;
// 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.
// 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;
// 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;
procedure FitCurrentPage;
begin
PdfView1.Zoom := PdfView1.PageZoom[PdfView1.PageNumber];
end;
procedure InspectViewerPoint(X, Y: Integer);
var
PageNo: Integer;
PageX, PageY: Double;
CharIndex: Integer;
Ch: WString;
begin
PageNo := PdfView1.PageNumber;
if not PdfView1.DeviceToPage(X, Y, PageNo, PageX, PageY) then
Exit;
CharIndex := PdfView1.CharacterIndexAtPos(X, Y, 8, 8);
if CharIndex >= 0 then
begin
Ch := PdfView1.Text(CharIndex, 1);
ShowMessage(Format('Page %d, X %.2f, Y %.2f, character %s',
[PageNo, PageX, PageY, string(Ch)]));
end;
end;
function LoadPdfSafely(const FileName: string): Boolean;
begin
Result := False;
if not FileExists(FileName) then
Exit;
try
Pdf1.FileName := FileName;
Pdf1.Active := True;
Result := Pdf1.Active;
except
Result := False;
end;
end;
function ExtractTextSafely(PageNo: Integer): WString;
begin
Result := '';
if not Pdf1.Active then
Exit;
if (PageNo < 1) or (PageNo > Pdf1.PageCount) then
Exit;
Pdf1.PageNumber := PageNo;
Result := Pdf1.Text;
end;
// IsTagged 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;
// 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
// 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;
// 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;
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;