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\FontProperties, Demo\CBuilder\FontProperties y Demo\Lazarus\FontProperties 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 ú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.
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 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;
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 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');
// 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;
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;
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.
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.
// 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;
// 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;
// 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;
// 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;
// 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;
// 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;
// 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;
// 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');
// 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.
// 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.
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;
// 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;
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.
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.
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.
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.
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.
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.
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.
// 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;
// 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;
// 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.
// 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;
// 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;
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 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;
// 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
// 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;
// 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;
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;