PDFlibPas (losLab PDF Library) rellena plantillas de bloques PDF desde Delphi: un bloque es un rectángulo con nombre en una página plantilla, tipado como texto, imagen o PDF, y FillTextBlock, FillImageBlock y FillPDFBlock dibujan datos variables en él. Los bloques se encuentran como anotaciones de página PDFlibBlock:Text, PDFlibBlock:Image y PDFlibBlock:PDF, o en un diccionario page-piece bajo /PieceInfo
El caso de uso es del tipo aburrido y de alto volumen. Un diseñador maqueta un certificado, una entrada de evento o una carta de cobranza una vez, y el programa estampa un nombre, un número de asiento, una imagen de firma o una página de código de barras pre-renderizada en ranuras fijas para diez mil registros. Los campos de formulario pueden hacer parte de eso, pero un campo AcroForm es un objeto interactivo con su propio flujo de apariencia y sus propias reglas, y normalmente termina aplanándolo de todos modos. Un bloque es más simple: un rectángulo más un puñado de pistas de estilo, consumido una vez y luego desechado
¿Qué es exactamente un bloque PDF en PDFlibPas?
Un bloque de PDFlibPas es una anotación de página cuyo /Subtype es PDFlibBlock:Text, PDFlibBlock:Image o PDFlibBlock:PDF, cuyo /Rect es el área del placeholder, y cuya cadena /Contents contiene name;key=value;key=value. El primer segmento es el nombre del bloque, todo lo demás es una lista de propiedades. TPDFlib.AddBlock escribe la anotación con /F 4 (Print) y sin flujo de apariencia, así que un bloque sin rellenar no ocupa píxeles en pantalla ni en papel
El lado de lectura tiene una segunda fuente. Después de enumerar las anotaciones de bloque, PDFlibPas recurre a la ruta del diccionario de página /PieceInfo << /PDFlib << /Blocks ... >> >> y su variante /PieceInfo << /PDFlib << /Private << /Blocks ... >> >> >>. Los diccionarios page-piece son PDF estándar, definidos en ISO 32000-1 §14.5 como el lugar donde una aplicación guarda datos privados adjuntos a una página, y ahí es donde viven normalmente las definiciones de bloques de herramientas externas de autoría de plantillas. El contenedor /Blocks puede ser un diccionario indexado por nombre de bloque o un arreglo simple; para la forma de arreglo PDFlibPas nombra las entradas Block1, Block2 y así sucesivamente a menos que un bloque traiga su propio /Name
Dentro de un bloque page-piece, el valor /Subtype (o /Type) Image se mapea a un bloque de imagen, PDF o Page a un bloque PDF, y cualquier otra cosa se trata como texto. Todas las demás llaves excepto /Type, /Subtype, /Name, /Rect y /Custom se vuelven propiedades, y las llaves de un diccionario /Custom anidado también se aplanan, así que una entrada /Custom << /department (Finance) >> se lee como GetBlockProperty(Page, Index, 'department'). Los índices de bloque son base 1 por página, bloques de anotación primero, bloques page-piece después
Autoría de una plantilla de bloques con AddBlock
TPDFlib.AddBlock(BlockType, BlockName, Left, Top, Width, Height, Properties) agrega un bloque a la página seleccionada actualmente y devuelve 1 en caso de éxito. Devuelve 0 para un nombre vacío, un ancho o alto cero o negativo, o un BlockType distinto de 'text', 'image' o 'pdf' (sin distinguir mayúsculas). Properties se anexa al nombre con un punto y coma, así que usa la misma sintaxis key=value;key=value que la cadena guardada
var
Lib: TPDFlib;
begin
Lib := TPDFlib.Create;
try
// arte estático: borde, logo, texto fijo ...
Lib.DrawText(72, 200, 'This certifies that');
Lib.AddBlock('text', 'recipient', 72, 230, 450, 44,
'fontname=Times-Bold;fontsize=30;fillcolor={cmyk 0 0.55 1 0.1};alignment=center');
Lib.AddBlock('text', 'course', 72, 290, 450, 24,
'fontname=Helvetica;fontsize=14;alignment=center;fitmethod=word');
Lib.AddBlock('image', 'signature', 360, 600, 160, 60, '');
Lib.AddBlock('pdf', 'seal', 72, 580, 100, 100, '');
if Lib.GetBlockCount(1) <> 4 then
raise Exception.Create('Template blocks missing');
Lib.SaveToFile('certificate-template.pdf');
finally
Lib.Free;
end;
end;
El parser de propiedades es deliberadamente flexible. Las llaves se comparan sin distinguir mayúsculas, los valores se recortan pero por lo demás se devuelven verbatim (un valor escrito como {top left} vuelve con sus llaves), y una llave faltante devuelve una cadena vacía. El único límite duro es el separador: un valor no puede contener punto y coma, porque el punto y coma termina el segmento
¿Cómo aplica FillTextBlock fuente, color y alineación?
TPDFlib.FillTextBlock(Page, BlockIndex, Text, Options) lee las propiedades del bloque, las aplica al estado de dibujo, ajusta el texto en el rectángulo del bloque con FitTextBox, y luego restaura la fuente, el tamaño de texto, el color de texto y la alineación del llamador en un bloque finally. Esa restauración importa en un bucle de mezcla: un bloque de encabezado rojo de 30 pt no puede filtrar su estilo en la siguiente llamada DrawText que usted haga en la misma página
fontname(ofont): primero reutiliza una fuente ya cargada en el documento con ese nombre, luego mapea los nombres Standard 14 comoHelvetica-Bold,Times-RomanoCourier, y por último intentaAddTrueTypeFontcon el nombrefontsize(osize): el tamaño de texto que se pasa al ajustador, que lo reduce si el texto no cabefillcolor(otextcolor,color):#RRGGBB,{rgb r g b},{cmyk c m y k},{gray g}, listas de 3 o 4 números sueltas, o los nombres black, white, red, green, blue y gray; los componentes RGB mayores que 1 se leen como 0–255alignment,alignypositionse leen juntos: left, right, center (o centre) y justify fijan la alineación horizontal, top y bottom fijan la colocación vertical, y el valor por defecto es centrado verticalmentefitmethod:wordonofitsplitevita que las palabras largas se partan,scaleoexpanddeja que el tamaño crezca para llenar la caja
La ruta CMYK es la razón por la que las imprentas se interesan. {cmyk 0 0.55 1 0.1} llama a SetTextColorCMYK, así que el texto variable se emite en DeviceCMYK y pega en el color de marca en la prensa sin un viaje de ida y vuelta por RGB. FillTextBlock devuelve 1 cuando el texto se dibujó; devuelve 0 para un Text vacío, un bloque faltante, o un bloque que no es de texto, y en todos esos casos el bloque queda en su lugar
Rellenar bloques de imagen y PDF, y los bits de Options
FillImageBlock(Page, BlockIndex, ImageID, Options) dibuja una imagen agregada previamente, y FillPDFBlock(Page, BlockIndex, SourceDocumentID, SourcePage, Options) importa una página de otro documento cargado como un Form XObject y la dibuja, lo que mantiene contenido vectorial como un sello, una página de código QR o un membrete totalmente vectorial. Ambos preservan la relación de aspecto del origen y centran el resultado dentro del rectángulo del bloque. Las tres funciones de relleno comparten los mismos bits de Options: el bit 1 (valor 1) elimina el bloque tras un relleno exitoso, ya sea borrando la anotación o quitando la entrada del contenedor /Blocks, y el bit 2 (valor 2) estira el contenido de imagen y PDF al rectángulo exacto en lugar de ajustarlo
function FillCertificate(const TemplateFile, SealFile, SignatureFile,
OutFile: WideString; Values: TStrings): Boolean;
var
Lib: TPDFlib;
SealID, SignatureID, I: Integer;
BlockName, BlockType: WideString;
begin
Result := False;
Lib := TPDFlib.Create;
try
if Lib.LoadFromFile(SealFile, '') <> 1 then Exit;
SealID := Lib.SelectedDocument; // el ID real del documento
if Lib.LoadFromFile(TemplateFile, '') <> 1 then Exit;
// la plantilla es ahora el documento seleccionado
SignatureID := Lib.AddImageFromFile(SignatureFile, 0);
// recorra hacia atrás: el bit 1 elimina cada bloque, lo que renumera los posteriores
for I := Lib.GetBlockCount(1) downto 1 do
begin
BlockName := Lib.GetBlockName(1, I);
BlockType := Lib.GetBlockType(1, I);
if BlockType = 'text' then
Lib.FillTextBlock(1, I, Values.Values[BlockName], 1)
else if (BlockType = 'image') and (SignatureID <> 0) then
Lib.FillImageBlock(1, I, SignatureID, 1)
else if BlockType = 'pdf' then
Lib.FillPDFBlock(1, I, SealID, 1, 1);
end;
Result := Lib.SaveToFile(OutFile) = 1;
finally
Lib.Free;
end;
end;
Dos detalles en ese bucle son fáciles de equivocar. El bucle corre desde el índice más alto hacia abajo porque eliminar el bloque 2 convierte el viejo bloque 3 en bloque 2; contar hacia arriba con el bit 1 activo se saltea un bloque de por medio. Y el documento de origen para FillPDFBlock se carga primero y su ID se captura antes de cargar la plantilla, porque los IDs de imagen pertenecen al documento seleccionado, así que AddImageFromFile tiene que correr mientras la plantilla está seleccionada. Para corridas por lotes donde el sello o el membrete cambia por registro, el cosido de páginas con Form XObject y las plantillas de marca de agua componibles en PDFlibPas usan el mismo mecanismo de capturar y dibujar sobre páginas enteras
¿Por qué las coordenadas de los bloques salen invertidas?
Los rectángulos de bloque salen invertidos cuando el código mezcla los dos sistemas de coordenadas con los que PDFlibPas trabaja. Un /Rect de anotación se guarda en el espacio de usuario de PDF, medido desde la parte inferior de la página (ISO 32000-1 §12.5.2), mientras que la API de dibujo de TPDFlib mide desde la parte superior. AddBlock toma entrada basada en superior y guarda el borde inferior como PageHeight - Top - Height; GetBlockRect y las tres funciones de relleno leen el rectángulo de vuelta y lo convierten con PageHeight - URy
En la práctica eso significa: use GetBlockRect(Page, BlockIndex, Left, Top, Width, Height) cuando necesite la geometría de la ranura, por ejemplo para dibujar un marco de depuración o colocar una leyenda junto a un bloque, y no lea el /Rect mediante una llamada genérica de anotación para luego alimentarlo directo a DrawText. Un bloque page-piece con /Rect [40 700 240 730] en una página carta de 792 pt reporta Top = 62, no 700. El bug que esto previene es el clásico donde cada valor rellenado cae espejado cerca del margen inferior y nada da error
Trampas de las que vale la pena aprender
El bug más instructivo en el código de bloques fue un desliz de un carácter. Las anotaciones de bloque se reconocen con una prueba de prefijo sobre el subtipo, y 'PDFlibBlock:' tiene 12 caracteres. Una versión temprana comparaba Copy(Subtype, 1, 13) contra ese literal de 12 caracteres, lo que nunca puede ser igual, así que GetBlockCount devolvía 0 para cada plantilla y cada relleno reportaba "no such block". Nada se colgó y nada quedó registrado. La lección se generaliza a cualquier motor de plantillas: aserte el conteo de bloques de una plantilla conocida en una prueba, porque una enumeración silenciosa sin coincidencias se ve exactamente como una página vacía
La segunda trampa son los IDs de documento. TPDFlib.LoadFromFile devuelve un código de estado, 1 para éxito y 0 para falla, no el ID del documento que acaba de cargar. Pasar ese 1 como SourceDocumentID a FillPDFBlock falla porque 1 no es un ID de documento válido. Llame a SelectedDocument justo después de una carga exitosa para obtener el ID real, y a SelectDocument para volver a la plantilla si cargó el origen después
Una más pequeña: FillTextBlock con una cadena vacía no hace nada y conserva el bloque. Para una mezcla de correspondencia con campos opcionales, como una segunda línea de dirección faltante, eso normalmente es lo que usted quiere, pero la anotación sin rellenar se queda entonces en el archivo de salida. Elimínela explícitamente con DeleteAnnotation o rellene un espacio con el bit 1 si la salida debe estar limpia. Todas las llamadas de bloque guardan y restauran la página seleccionada, así que un relleno en la página 3 no cambia adónde va su siguiente llamada de dibujo
¿Cuáles son los límites de interoperabilidad de las anotaciones de bloque?
Las anotaciones de bloque son una convención privada, no una función estándar de PDF. PDFlibBlock:Text y sus hermanos no están entre los tipos de anotación listados en ISO 32000-1 §12.5.6.1, así que un visor las trata como anotaciones desconocidas. Como el flag Invisible está apagado y no hay flujo de apariencia, los visores conformes no dibujan nada por ellas, pero algunos visores pueden listarlas en un panel de comentarios, y otras herramientas PDF no entenderán ni preservarán su semántica
- Rellene con el bit 1 activo para cualquier cosa que salga de su sistema, así el archivo final contiene solo contenido de página ordinario
- PDF/A no permite tipos de anotación fuera de la especificación PDF (ISO 19005-1 §6.5.2), así que una salida archivada no debe llevar anotaciones de bloque sin rellenar
- Los bloques page-piece son más seguros de conservar en una plantilla, ya que
/PieceInfoes estándar y los consumidores ignoran los datos que no les pertenecen - Los bloques no son campos de formulario: no hay interactividad, no hay valor de campo en el archivo ni exportación a FDF o XFDF; para datos que deben permanecer editables vea el intercambio de datos de formulario FDF, XFDF y XFA en PDFlibPas
Si la plantilla tiene que declarar sus campos variables de forma estandarizada y verificable por un validador para un flujo de impresión, los bloques son la herramienta equivocada. Para eso sirve PDF/VCR-1, y el manifiesto de placeholders de plantilla PDF/VCR-1 en PDFlibPas cubre cómo viven esos placeholders en el árbol de estructura en lugar de en anotaciones
FAQ
¿Puede PDFlibPas rellenar plantillas de bloques creadas por otras herramientas?
Sí, cuando la herramienta guarda los bloques bajo /PieceInfo /PDFlib /Blocks o /PieceInfo /PDFlib /Private /Blocks, en forma de diccionario o arreglo, con un /Rect de cuatro números. GetBlockCount los cuenta después de cualquier anotación PDFlibBlock:*, y las llaves personalizadas se exponen a través de GetBlockProperty
¿FillTextBlock cambia la fuente que seleccioné antes de la llamada?
No. FillTextBlock guarda la fuente, el tamaño de texto, el color de texto y la alineación seleccionados, aplica las propiedades fontname, fontsize, fillcolor y alignment del bloque, y restaura el estado original después, incluso si el ajuste falla
¿Cómo coloco un logo vectorial o una página de código de barras en un bloque?
Cargue el PDF de origen, guarde SelectedDocument como su ID, seleccione de nuevo el documento plantilla si hace falta, y llame a FillPDFBlock(Page, BlockIndex, SourceID, SourcePage, 1). La página se importa como un Form XObject, así que se mantiene vectorial y se ajusta al bloque con su relación de aspecto preservada a menos que agregue el bit 2
Las plantillas de bloques, la importación de páginas como Form XObject y el resto del kit de datos variables vienen en PDFlibPas, la losLab PDF Library for Delphi, que cubre creación, edición y producción de impresión de PDF para proyectos Delphi y C++Builder