PDFium tiene reputación como un motor de visualización, el renderizador detrás de la pestaña de PDF de Chrome, por lo que lo primero que hay que aclarar es que PDFium Component también puede construir un documento que nunca antes existió. El lado de creación (authoring) envuelve la API de objetos de página de PDFium: usted crea un documento vacío, agrega páginas con dimensiones explícitas y coloca texto, rutas vectoriales e imágenes en cada página en las coordenadas que elija. No hay un lenguaje de descripción de páginas que aprender ni un controlador de impresión involucrado. Usted llama a métodos, la biblioteca ensambla objetos PDF y SaveAs serializa el resultado
Lo que no obtiene es un motor de diseño (layout). Esto es lo suficientemente importante como para decirlo por adelantado, porque moldea cada ejemplo a continuación. PDFium Component coloca el contenido donde usted le indique, en coordenadas absolutas y en ningún otro lugar. No envolverá un párrafo, no fluirá texto a través de un salto de página ni calculará una tabla a partir de filas y columnas. Esas son tareas suyas. Si llegó esperando algo que refluya prosa como lo hace un procesador de texto, calibre sus expectativas ahora: esta es una API de colocación precisa y de bajo nivel, más cercana a dibujar en un lienzo que a componer un documento. Para facturas generadas, certificados, etiquetas y páginas de informes donde ya sabe dónde va cada elemento, esa precisión es exactamente lo que desea
Lo mínimo que produce un archivo
Tres llamadas se interponen entre un TPdf vacío y un PDF guardado: crear el documento, agregar una página, escribirlo. Todo lo demás es contenido que usted superpone en medio
uses
Vcl.Graphics, // for clBlack and TColor
PDFium; // TPdf lives here
procedure CreateBlankPdf(const FileName: string);
var
Pdf: TPdf;
begin
Pdf := TPdf.Create(nil);
try
Pdf.CreateDocument; // empty in-memory document
Pdf.AddPage(0, 595, 842); // A4 portrait, in points
Pdf.AddText('First page', 'Arial', 18, 50, 780);
Pdf.SaveAs(FileName); // serialize to disk
finally
Pdf.Active := False;
Pdf.Free;
end;
end;
Un detalle hace tropezar a las personas que han visto fragmentos de código más antiguos: no se asigna Pdf.Active := True después de CreateDocument. La propiedad Active informa si existe un identificador (handle) de documento, y CreateDocument ya ha creado uno, por lo que la propiedad es True en el momento en que regresa esa llamada. Establecerlo nuevamente no hace nada en el mejor de los casos y es engañoso para el siguiente lector en el peor. Active se justifica a la salida: asignar False libera el documento subyacente antes de Free, que es el orden de desmontaje limpio. Trate CreateDocument y una apertura de carga de archivo como mutuamente excluyentes. La biblioteca se niega a crear un nuevo documento en un TPdf que ya tiene uno abierto, por lo que reutilizar significa cerrar primero el documento actual
Las coordenadas comienzan en la parte inferior izquierda
El segundo par de argumentos para AddText, y para cada llamada de colocación, es un punto en el espacio de usuario del PDF. El origen se sitúa en la esquina inferior izquierda de la página, X corre hacia la derecha y Y corre hacia arriba. Una unidad es un punto, 1/72 de pulgada, por lo que una página A4 mide 595 por 842 unidades y US Letter es 612 por 792. Esa Y hacia arriba es la fuente más común de confusión de "mi texto está fuera de la página", porque las coordenadas de pantalla y mapas de bits ponen el origen en la parte superior con Y creciendo hacia abajo. En una página de 842 puntos de alto, un encabezado cerca de la parte superior se sitúa alrededor de Y 780, no Y 60. Cuando un trazo (run) aterriza en un lugar inesperado, la altura de la página menos su Y es casi siempre el número que realmente quiso indicar
AddPage toma una posición de inserción como su primer argumento, expresada en base uno, con 0 como un atajo conveniente para "inicio del documento". Pase 0 o 1 para la primera página y la página se inserta al frente; pase el valor que coincida con el recuento al que está anexando para agregar al final. La página recién agregada también se convierte en la página actual, a la que apuntan las llamadas de dibujo subsiguientes, por lo que no hay un paso separado de "seleccionar esta página" después de agregarla. Si agrega varias páginas y luego necesita volver a dibujar en una anterior, configure PageNumber para mover el cursor; mientras esté llenando páginas en orden a medida que las crea, puede dejarlo tranquilo
Escribir texto y la regla de fuentes que muerde en silencio
La firma de AddText contiene todo lo que necesita un solo trazo: la cadena, un nombre de fuente, un tamaño en puntos, el ancla X e Y, y luego opcionalmente el color, un byte alfa para transparencia y un ángulo de rotación en grados
procedure WriteHeader(Pdf: TPdf; const Title, Author: string);
begin
// Título en negro, opacidad predeterminada, sin rotación
Pdf.AddText(Title, 'Arial', 20, 50, 780);
// Una línea de autor más clara 24 puntos debajo de él
Pdf.AddText('By ' + Author, 'Arial', 11, 50, 756, clGray);
// Un sello de borrador diagonal tenue a lo largo de la página
Pdf.AddText('DRAFT', 'Arial', 64, 180, 380, clGray, $30, 45.0);
end;
El byte alfa va de $00 (invisible) a $FF (opaco), lo cual es lo que hace que el sello de borrador sea una marca de agua en lugar de un bloque sólido: $30 es aproximadamente un diecinueve por ciento de opacidad, suficiente para leer a través de él. El ángulo rota el trazo en sentido antihorario alrededor de su ancla, por lo que 45 grados da el clásico sello de esquina a esquina. Nada de esto necesita una función de marca de agua separada. Una marca de agua es solo una llamada AddText grande, semitransparente y rotada, y dibujarla antes o después del cuerpo decide si se sitúa detrás o encima del contenido
Las fuentes merecen una oración cuidadosa, porque el modo de falla es silencioso. Cuando pasa un nombre de fuente, PDFium Component le pide al sistema operativo los datos TrueType de esa fuente y los incrusta en el documento, razón por la cual un archivo construido en su computadora se renderiza idénticamente en una que nunca ha tenido la fuente instalada. El truco es qué sucede cuando el nombre no se resuelve: un error tipográfico, o un tipo de letra que simplemente no está presente en la computadora de compilación. No hay una excepción. La biblioteca recurre a crear un objeto de texto que lleva el nombre solo como una etiqueta, sin incrustar nada, y deja que el visor sustituya lo que considere cercano. El texto aparece en sus pruebas, parece plausible, y cambia las métricas o los glifos en el momento en que el archivo se abre en algún lugar con diferentes fuentes instaladas. Use nombres que sepa que están presentes en la máquina que genera el archivo, trate la lista de fuentes como una dependencia de implementación y abra una muestra en un visor en un sistema limpio antes de confiar en la salida
Formas vectoriales: construya una ruta, luego confírmela
Las líneas, rectángulos y regiones rellenas pasan por una ruta (path). Usted abre una con CreatePath, que establece el punto de inicio y todo el estilo a la vez, el modo de relleno, los colores de relleno y trazo con sus propios bytes alfa, el ancho del trazo, los extremos y uniones de línea. Luego la extiende con LineTo, BezierTo y ClosePath, y finalmente AddPath confirma la ruta terminada en la página. El paso de confirmación (commit) es fácil de olvidar y no produce nada si lo omite
procedure DrawDivider(Pdf: TPdf; X, Y, Width: Single);
begin
// Una regla horizontal fina. La sobrecarga de rectángulo establece una caja directamente:
// X, Y, Ancho, Alto, luego el modo de relleno y los colores.
Pdf.CreatePath(X, Y, Width, 0.5, fmNone, clBlack, $FF,
True, clBlack, $FF, 1.0);
Pdf.AddPath;
end;
procedure DrawTriangle(Pdf: TPdf);
begin
// Sobrecarga de punto: inicie en el primer vértice, línea hacia el resto, cierre.
Pdf.CreatePath(200, 300, fmWinding, clBlue, $80, True, clNavy, $FF, 2.0);
Pdf.LineTo(300, 300);
Pdf.LineTo(250, 400);
Pdf.ClosePath;
Pdf.AddPath; // nada se dibuja hasta que esto se ejecuta
end;
Dos sobrecargas cubren los casos comunes. La forma de cuatro coordenadas toma X, Y, ancho y alto y le da un rectángulo alineado con los ejes en una sola llamada, que es a lo que recurre para dibujar una regla, el borde de una celda o un panel de fondo relleno. La forma de dos coordenadas establece solo un punto de inicio, y usted traza el resto del contorno usted mismo con LineTo y BezierTo. El modo de relleno controla cómo se pintan las regiones superpuestas: fmWinding (enrollamiento distinto de cero) se adapta a la mayoría de las formas sólidas, fmAlternate (par-impar) maneja recortes y contornos que se intersecan a sí mismos, y fmNone deja una ruta solo trazada sin relleno, que es lo que usa el divisor de arriba
Las tablas son rutas y texto, ensambladas a mano
Debido a que no hay una primitiva de tabla, una tabla es un bucle. Usted decide las compensaciones (offsets) X de las columnas y la altura de la fila, escribe cada celda con AddText y dibuja las reglas con rutas de rectángulo. La aritmética es suya, pero es simple, y una vez escrita se generaliza a cualquier cuadrícula que necesite
procedure DrawTable(Pdf: TPdf; Left, Top: Double);
const
ColX: array[0..2] of Double = (0, 110, 210); // compensaciones de columna
RowH = 20;
var
Y: Double;
Row: Integer;
begin
// Fila de encabezado
Pdf.AddText('Item', 'Arial', 10, Left + ColX[0], Top);
Pdf.AddText('Qty', 'Arial', 10, Left + ColX[1], Top);
Pdf.AddText('Price', 'Arial', 10, Left + ColX[2], Top);
// Regla debajo del encabezado
Pdf.CreatePath(Left, Top - 5, 260, 0.5, fmNone, clBlack, $FF);
Pdf.AddPath;
// Filas de datos, moviendo Y hacia abajo en cada iteración
Y := Top;
for Row := 1 to 3 do
begin
Y := Y - RowH;
Pdf.AddText('Item ' + IntToStr(Row), 'Arial', 9, Left + ColX[0], Y);
Pdf.AddText(IntToStr(Row * 2), 'Arial', 9, Left + ColX[1], Y);
Pdf.AddText('$' + IntToStr(Row * 10) + '.00', 'Arial', 9, Left + ColX[2], Y);
end;
end;
Note que la Y se mueve hacia abajo por la altura de la fila en cada pasada, nuevamente porque hacia arriba es positivo. Aquí es también donde se hace notar la ausencia de medición de texto: nada impide que el nombre largo de un artículo se desborde hacia la siguiente columna, porque la biblioteca no sabe cuán ancha se renderizó su cadena. Para salidas de formato fijo donde usted controla los datos, dimensiona las columnas generosamente y sigue adelante. Para contenido genuinamente variable, usted restringe las entradas o mide los anchos de los glifos usted mismo antes de colocarlos, que es el punto en el que una biblioteca de composición dedicada comienza a amortizarse
Imágenes y múltiples páginas
El contenido rasterizado entra a través de los ayudantes de imágenes. AddPicture toma una TPicture cargada y la coloca en un punto, con un ancho y alto opcionales para escalarla; AddImage acepta una ruta de archivo o un TBitmap directamente, y AddJpegImage transmite bytes JPEG sin un viaje de ida y vuelta a través de un mapa de bits. Al igual que con todo lo demás, las coordenadas de colocación son la esquina inferior izquierda de la imagen en el espacio de usuario, y el ancho y el alto son el tamaño en la página en puntos, no las dimensiones en píxeles del origen
procedure CreateMultiPageReport(const FileName: string; PageCount: Integer);
var
Pdf: TPdf;
P: Integer;
begin
Pdf := TPdf.Create(nil);
try
Pdf.CreateDocument;
for P := 1 to PageCount do
begin
Pdf.AddPage(P, 595, 842); // agregar al final; la nueva página se vuelve la actual
Pdf.AddText('Page ' + IntToStr(P) + ' of ' + IntToStr(PageCount),
'Arial', 10, 50, 30); // pie de página cerca del borde inferior
// ... dibuje el cuerpo de esta página aquí ...
end;
Pdf.SaveAs(FileName);
finally
Pdf.Active := False;
Pdf.Free;
end;
end;
Un documento de varias páginas es el patrón de una sola página en un bucle. Cada AddPage anexa una página y la convierte en la actual, por lo que el cuerpo y el pie de página que dibuje a continuación aterrizarán en la página que acaba de agregar. No se reasigna PageNumber dentro de este bucle, porque agregar una página ya movió el cursor allí; solo necesita PageNumber cuando vuelve a una página fuera del orden de creación. Llame a SaveAs una vez al final, después de que se llene la última página. Si necesita un perfil de archivo en lugar de un archivo simple, el mismo objeto de documento expone SaveAsPdfA y las otras variantes de conformidad, por lo que la elección del estándar de salida es una llamada de guardado diferente, no una ruta de construcción diferente
Dónde encaja esto
El marco honesto es que la API de creación de PDFium Component es una capa delgada y fiel sobre el modelo de objetos de página de PDFium: creación real de documentos, fuentes incrustadas reales, contenido vectorial y rasterizado real, serializado a un archivo que cumple con los estándares. No es, y no pretende ser, un motor de documentos refluido. La línea divisoria es el diseño de texto. Si su salida tiene plantilla, facturas, certificados, etiquetas, paneles (dashboards) renderizados a una cuadrícula fija, el modelo de coordenadas absolutas es directo y rápido y el código se mantiene legible. Si su salida es prosa extensa que debe envolverse y paginarse por sí sola, estará reconstruyendo un motor de diseño sobre estas llamadas, y esa es la herramienta equivocada para el trabajo. Saber de qué lado de esa línea se encuentra es la mayor parte de la decisión
Los métodos de creación descritos aquí forman parte del PDFium Component para Delphi, que combina esta ruta de creación con las funciones de renderizado y extracción de texto por las que PDFium es más conocido