La combinación de páginas (page stitching) compone el contenido de páginas existentes en un lienzo compartido, lo cual es una operación diferente a la fusión de archivos. losLab PDF Library implementa la combinación de páginas Form XObject y plantillas de marcas de agua componibles para Delphi: los métodos StitchPageOverlay, StitchPagesSideBySide y StitchPagesVertically colocan las páginas capturadas a cualquier escala con total fidelidad vectorial, y TPDFlibCompositeWatermark superpone marcas de agua de texto e imagen a lo largo de un rango de páginas en una sola llamada. Si lo que realmente necesita es anexar páginas completas de varios documentos en un solo archivo, esto corresponde a la familia de fusión (merge), cubierta en el artículo sobre la fusión rápida de PDF a nivel de bytes; este artículo trata sobre la combinación del contenido de las páginas dentro de un único espacio de coordenadas
Las solicitudes que conducen aquí son comunes y recurrentes. Una escuela desea imponer dos páginas de examen en una sola hoja apaisada para reducir a la mitad el uso de papel. Un revisor de localización quiere ver la página en inglés y su traducción lado a lado en una sola página, con desplazamiento conjunto. Un equipo legal quiere que cada borrador se selle con tres capas a la vez: un logotipo en la esquina, la palabra CONFIDENCIAL en diagonal en el centro y una pequeña línea de pie de página, únicamente en las páginas 1 a 3. Los tres casos representan el mismo problema subyacente: colocar el contenido de una página ya compuesta en otro lugar y a otro tamaño sin degradarlo a un mapa de bits
¿Por qué falla la concatenación de flujos de contenido PDF?
La combinación ingenua, consistente en copiar el flujo /Contents de la página de origen en la página de destino, falla por tres razones estructurales. En primer lugar, las coordenadas entran en conflicto: ambos flujos de contenido asumen su propio origen en la parte inferior izquierda de su propia MediaBox, por lo que el contenido pegado aterriza sobre el contenido existente en el lugar equivocado. En segundo lugar, los nombres de los recursos entran en conflicto: cada página resuelve operadores como /F1 Tf y /Im0 Do frente a su propio diccionario /Resources, y dos páginas asocian de forma habitual el mismo nombre a diferentes fuentes o imágenes, por lo que una de ellas se renderiza silenciosamente con el recurso incorrecto. En tercer lugar, una copia de flujo sin procesar no le proporciona ningún tipo de escala; lo máximo que puede hacer es trasladar
Los objetos Form XObject resuelven los tres problemas a la vez, y por eso losLab PDF Library basa su combinación en ellos. La norma ISO 32000-1 §8.10 define un objeto Form XObject como un flujo de contenido autónomo con su propio diccionario /Resources y su propio espacio de coordenadas, que se coloca en una página invocando su nombre bajo cualquier matriz de transformación actual activa. El método CapturePage empaqueta una página existente completa (contenido más recursos) en un objeto XObject /Subtype /Form, y las funciones de combinación lo posicionan con una matriz cm: escala, traducción y rotación a voluntad. Dado que el origen sigue siendo vectorial, el texto sigue siendo texto y las rutas siguen siendo rutas a cualquier nivel de zoom, la misma propiedad que hace que los gráficos vectoriales y los patrones de sombreado sean independientes de la resolución. Nada se rasteriza, y el diccionario de recursos aislado evita que la página capturada entre en conflicto con la de destino por el nombre /F1
¿Cómo combinar dos páginas PDF en una sola página?
StitchPagesSideBySide(Page1, Page2, Gap, TargetHeight) es la respuesta directa: captura ambas páginas de origen, crea una nueva página más ancha y coloca las dos capturas a izquierda y derecha con un espacio intermedio (gutter) entre ellas, escalando cada una para adaptarse a TargetHeight. La función devuelve el nuevo número de página (basado en 1) o 0 en caso de fallo, y ambas páginas de origen se capturan y ocultan como parte de la operación. El caso del examen de dos páginas por hoja se resuelve en tres líneas
var
Lib: TPDFlib;
NewPage: Integer;
begin
Lib := TPDFlib.Create;
try
Lib.LoadFromFile('exam.pdf', '');
// Páginas 1 y 2 en una nueva página más ancha con un espacio de 18pt,
// ambas escaladas a una altura de salida de 595pt (A4 apaisado)
NewPage := Lib.StitchPagesSideBySide(1, 2, 18, 595);
if NewPage > 0 then
Lib.SaveToFile('exam-2up.pdf');
finally
Lib.Free;
end;
end;
Para la composición vertical, StitchPagesVertically(PageRanges, Gap, TargetWidth) apila un rango completo de páginas en una sola página alta estilo póster. PageRanges acepta la sintaxis de rango estándar como '1-3,5'. Cada página de origen se escala para que su ancho coincida con TargetWidth, por lo que la contribución de altura de cada página es su propia altura multiplicada por su propio factor de escala, y la altura de la página de salida es la suma de estas alturas más un Gap (espacio) entre cada par. Este es el formato ideal para documentos de revisión de desplazamiento continuo o recibos largos ensamblados a partir de orígenes de tamaño carta
¿Cómo superponer una página sobre otra?
StitchPageOverlay(SourcePage, TargetPage, Left, Top, Width, Height, Opacity) pinta una página existente sobre otra página existente, en lugar de crear una nueva. La página de origen se captura como un objeto Form XObject y se dibuja sobre la de destino en el rectángulo que indique, con una opacidad (Opacity) de 0 a 1 (donde 1 es opaco). El ancho y la altura son parámetros libres, por lo que la colocación se estirará si su proporción difiere de la relación de aspecto original de la página de origen; pase un par proporcional cuando desee una miniatura sin distorsiones de la página origen. Un uso típico es un documento cuyo bloque de aprobación se mantiene como una página independiente y se estampa en la portada
var
Lib: TPDFlib;
begin
Lib := TPDFlib.Create;
try
Lib.LoadFromFile('report.pdf', '');
// La página 5 contiene el bloque de aprobación; colocarlo en la página 1
// en un cuadro de 260 x 180 con una opacidad del 45%. La página 5 se consume:
// se captura y se oculta, por lo que las páginas 6..n pasan a ser 5..n-1
if Lib.StitchPageOverlay(5, 1, 300, 500, 260, 180, 0.45) = 1 then
Lib.SaveToFile('report-approved.pdf');
finally
Lib.Free;
end;
end;
¿Qué sucede con los números de página después de una captura?
El método CapturePage consume la página de origen: su contenido se mueve al objeto XObject y la página ahora vacía se oculta, por lo que cada página numerada por encima de la de origen se desplaza inmediatamente una posición hacia abajo. Esta es la regla semántica más importante en toda la API de combinación. losLab PDF Library gestiona los cálculos matemáticos dentro de cada llamada de combinación, StitchPageOverlay ajusta el índice de destino cuando este se sitúa por encima del origen, y StitchPagesSideBySide captura primero la página con el número más alto para que el índice de la segunda captura siga siendo válido; no obstante, su propio código debe tener en cuenta este desplazamiento entre llamadas. Dos reglas mantienen correcto el código de combinación múltiple. Registre los tamaños o números de página que necesite antes de la primera captura, porque cambiarán después. Y cuando capture varias páginas en un bucle, realice la iteración en orden descendente de páginas, para que la ocultación de una página posterior nunca altere el índice de una página anterior que aún no haya capturado
¿Cómo aplicar una marca de agua multicapa a un rango de páginas?
La capa de plantilla de marca de agua en PDFlibWatermark.pas convierte la idea de "definir una vez, aplicar a muchas páginas" en objetos. TPDFlibWatermarkBase contiene lo que comparten todas las marcas de agua: una posición (Position) de nueve puntos (de wpTopLeft a wpBottomRight, más wpCustom que utiliza coordenadas X/Y brutas), un ángulo (Angle) en grados, una opacidad (Opacity) de 0 a 1 y una cadena de rango de páginas (PageRange) donde el valor vacío significa todas las páginas. TPDFlibTextWatermark añade texto, un ID de fuente (por defecto Helvetica), tamaño y un color RGB; TPDFlibImageWatermark añade un ID de imagen de una imagen añadida previamente más un ancho y una altura de colocación. TPDFlibCompositeWatermark contiene una lista de sub-marcas de agua y las aplica de forma recursiva, y TPDFlib.ApplyWatermark representa el punto de entrada de una sola llamada que respeta el rango de páginas propio de la plantilla y devuelve el número de páginas selladas. El sello legal de tres capas tiene este aspecto
var
Lib: TPDFlib;
Logo: TPDFlibImageWatermark;
Diagonal, Footer: TPDFlibTextWatermark;
Stack: TPDFlibCompositeWatermark;
begin
Lib := TPDFlib.Create;
try
Lib.LoadFromFile('draft.pdf', '');
Stack := TPDFlibCompositeWatermark.Create;
try
Logo := TPDFlibImageWatermark.Create;
Logo.ImageID := Lib.AddImageFromFile('logo.png', 0);
Logo.Position := wpTopRight;
Logo.Width := 90;
Logo.Height := 32;
Stack.Add(Logo);
Diagonal := TPDFlibTextWatermark.Create;
Diagonal.Text := 'CONFIDENTIAL';
Diagonal.Position := wpCenter;
Diagonal.Angle := 45;
Diagonal.FontSize := 60;
Diagonal.Opacity := 0.15;
Stack.Add(Diagonal);
Footer := TPDFlibTextWatermark.Create;
Footer.Text := 'Internal review copy';
Footer.Position := wpBottomCenter;
Footer.FontSize := 9;
Footer.Opacity := 0.6;
Stack.Add(Footer);
Stack.PageRange := '1-3'; // los hijos heredan este rango
Lib.ApplyWatermark(Stack);
Lib.SaveToFile('draft-marked.pdf');
finally
Stack.Free; // el compuesto libera a sus hijos
end;
finally
Lib.Free;
end;
end;
Límites que conviene conocer
Se aplican dos límites honestos, y ambos se derivan del funcionamiento del mecanismo. En primer lugar, una captura envuelve únicamente el flujo de contenido de la página y sus recursos. Las anotaciones, los enlaces, los campos de formulario y los comentarios existen como objetos independientes en el diccionario de la página, no en el flujo de contenido, por lo que no se transfieren al objeto XObject; un diseño combinado incluye el contenido de la página visible pero no la capa interactiva de sus orígenes. En segundo lugar, el posicionamiento de texto de nueve puntos es una aproximación: sin un motor de diseño completo, la biblioteca no puede conocer el ancho exacto renderizado de una cadena, por lo que estima el cuadro de texto en un 40% del ancho de la página al resolver posiciones como wpCenter. Esto es lo suficientemente preciso para centrar una etiqueta corta, pero para una colocación exacta de cadenas largas a nivel de píxel, cambie a wpCustom y calcule las coordenadas usted mismo
La escala no representa una restricción práctica en este caso, ya que la captura y la combinación manipulan referencias de objetos en lugar de volver a codificar el contenido de la página; si sus orígenes ocupan cientos de megabytes, se aplica la misma metodología descrita en la guía de fusión y división de archivos grandes con acceso directo antes de realizar la combinación. Las funciones de combinación y las clases de plantilla de marca de agua mostradas aquí se distribuyen en la versión actual de losLab PDF Library para Delphi, C# y VB.NET, junto con las primitivas de nivel inferior CapturePage, DrawCapturedPage y DrawCapturedPageMatrix cuando necesite un diseño que las tres llamadas de combinación predefinidas no cubren