Te llega una solicitud: tomar un lote de estados ya renderizados, tapar los números de cuenta y entregar dos páginas por hoja para ahorrar papel. Ambas mitades de esa tarea son cirugía sobre el flujo de contenido de un PDF que no creaste, así que no hay un lienzo de página amable para dibujar ni un administrador de fuentes al que recurrir. Estás editando directamente el grafo de objetos de un documento cargado y agregando operadores de dibujo en bruto a una página que otra herramienta ya maquetó. HotPDF expone exactamente dos puntos de entrada para esto, y el más peligroso de los dos es el que parece inocente
HotPDF es un componente PDF VCL nativo para Delphi y C++Builder. Su API de documentos cargados de la versión 9 añadió los primeros métodos que crean contenido nuevo en una página abierta desde disco, en lugar de una que construiste desde cero. Dos de ellos son el tema aquí: RedactLoadedRect, que pinta un rectángulo opaco sobre una región, y StitchLoadedPage, que escala una página y la dibuja sobre otra. Ambos funcionan escribiendo operadores de flujo de contenido de ISO 32000-1 §8.5 en el flujo /Contents de la página. Entender qué hacen esos operadores, y también qué no hacen, marca la diferencia entre una herramienta que funciona y una fuga de datos
Agregar operadores a una página cargada
Cuando construyes una página con la API normal de HotPDF, el componente controla el flujo de contenido y serializa por ti tus llamadas a TextOut y a vectores. Una página cargada es distinta: su /Contents es un objeto de flujo existente, quizá compartido, quizá parte de un arreglo de contenido, y tienes que injertar ahí sin corromper lo que ya está. La versión 9 introdujo tres pequeños auxiliares que vuelven eso seguro. NewIndirectStream asigna un nuevo THPDFStreamObject indirecto, con un búfer vacío y una entrada /Length 0; ResolveLoadedStream sigue una referencia indirecta hasta el flujo subyacente; y AppendLoadedStream escribe bytes en bruto al final del flujo y vuelve a escribir /Length para que el objeto guardado siga bien formado
El patrón que siguen ambos métodos públicos es el mismo. Busca el /Contents de la página, resuélvelo a un flujo y, si no hay un flujo utilizable, crea uno y adjúntalo. Después agrega los operadores. Como los bytes nuevos van al final del flujo, el modelo del pintor garantiza que se rendericen por encima de todo lo que dibujó el diseño original. Ese orden es todo el mecanismo detrás del rectángulo de redacción, y también la razón por la que ese rectángulo no es lo que la mayoría supone
RedactLoadedRect: un cubrimiento opaco, no una eliminación
RedactLoadedRect toma un índice de página basado en cero, cuatro coordenadas en el espacio de usuario y tres componentes de color en el rango de 0 a 1:
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.LoadFromFile('statement.pdf') > 0 then
begin
// Cover the account-number band on page 1 with solid black.
// Coordinates are PDF user space: origin bottom-left, points.
Pdf.RedactLoadedRect(0, 56, 690, 320, 706, 0, 0, 0);
Pdf.SaveLoadedDocument('statement-covered.pdf');
end;
finally
Pdf.Free;
end;
end;
Por debajo, el método emite tres operadores en el flujo de contenido: una asignación de color de relleno en DeviceRGB (r g b rg), una ruta de rectángulo (x y w h re) y un relleno (f). El ancho y el alto se derivan como X2 - X1 y Y2 - Y1, así que pasas dos esquinas opuestas y dejas que el método calcule la extensión. Pasa 0, 0, 0 como color y obtienes una franja negra; pasa 1, 1, 1 para una blanca que combine con una página blanca. Las coordenadas usan el propio espacio de usuario de la página cargada, lo que significa que el origen está en la esquina inferior izquierda y las unidades son puntos, y también significa que necesitas el /MediaBox de la página para ubicar cualquier cosa con precisión; GetLoadedPageBox con pbMediaBox te da eso
Léelo dos veces: un rectángulo relleno cubre el contenido de forma visual, pero no lo elimina. El texto, la imagen o el arte vectorial que queda debajo del rectángulo sigue presente en el PDF, sigue en el grafo de objetos y sigue siendo extraíble por cualquiera que copie la página, ejecute un extractor de texto o simplemente borre tu rectángulo del flujo de contenido. Esto es enmascaramiento visual, no redacción en el sentido legal o de seguridad. Si estás ocultando datos realmente sensibles, como números de cuenta, historiales médicos, identidades o cualquier dato regulado, cubrirlos con una caja negra y enviar el archivo es una filtración esperando a ser descubierta. La redacción real exige borrar los objetos de contenido subyacentes, no pintar encima de ellos
El nombre del método dice "Redact", y eso sirve como advertencia sobre cómo puede malinterpretarse el resultado, no como promesa sobre lo que elimina. La implementación es honesta al respecto en su propio comentario: se llama a sí misma la "visual redaction primitive" y señala que la redacción que elimina contenido necesita un intérprete de flujo de contenido que recorra y reescriba los operadores existentes. La ruta de documentos cargados de HotPDF no hace eso aquí. Así que la regla segura es estrecha: usa RedactLoadedRect para enmascaramiento cosmético no sensible, como ocultar una marca de agua de borrador, vaciar una región antes de una captura de pantalla o cubrir un logotipo obsoleto en una prueba interna. En el momento en que lo que queda debajo de la caja importaría si se filtrara, este método es la herramienta equivocada, y la respuesta correcta es regenerar el documento sin esos datos o usar una canalización real de eliminación de contenido
StitchLoadedPage: escalar, trasladar, dibujar
La imposición N-up es un problema más amable porque no se oculta nada, solo se reorganiza. StitchLoadedPage toma un índice de página de destino, un índice de página de origen, un desplazamiento X/Y y un factor de escala, y dibuja la página de origen sobre la de destino en esa posición y tamaño:
// Overlay page 2 (index 1) onto page 1 (index 0),
// scaled to 70% and nudged up-right.
Pdf.StitchLoadedPage(0, 1, 40, 380, 0.7);
// Convenience 2-up: source page on the right half of the target.
Pdf.StitchLoadedPageSideBySide(0, 1);
La cadena de operadores que agrega es una secuencia estándar de transformación y pintura: q para guardar el estado gráfico, una matriz cm que lleva la escala en la diagonal y el desplazamiento en las ranuras de traslación, /StitchSrc Do para invocar un objeto externo y Q para restaurar el estado. El par q/Q importa, porque aísla la transformación para que la página ensamblada no derrame su sistema de coordenadas sobre nada que se agregue después. El método también protege contra los errores obvios, como índices fuera de rango, un destino igual al origen o una escala no positiva, que recorta a 1.0, y sale en silencio en vez de lanzar una excepción, así que revisa tus entradas porque una omisión silenciosa se ve igual que un éxito
StitchLoadedPageSideBySide es una comodidad mínima sobre el método general. Lee el ancho del media box del destino, lo divide a la mitad y llama a StitchLoadedPage con esa media anchura como desplazamiento X y una escala fija de 0.5, poniendo el origen en la mitad derecha. Ese 0.5 codificado asume que el origen y el destino comparten el mismo ancho; si no lo hacen, el origen no llenará limpiamente su mitad, y entonces querrás usar StitchLoadedPage de forma general, con una escala que calcules tú mismo a partir de ambos media boxes
La estrategia simplificada de XObject y su costo ISO
Aquí es donde la implementación toma un atajo deliberado que debes conocer antes de confiar en el resultado en distintos visores. Una imposición N-up correcta envuelve el contenido de la página de origen en un Form XObject, un objeto dibujable autocontenido que, según ISO 32000-1 §8.10.1, debe incluir /Type /XObject, /Subtype /Form y su propia caja de recorte /BBox. El ensamblado de la versión 9 de HotPDF no construye ese envoltorio. En su lugar registra el propio diccionario de la página de origen directamente debajo de /Resources /XObject del destino con el nombre StitchSrc, y luego lo dibuja con Do. Un diccionario de página y un Form XObject comparten suficiente del modelo de contenido, ambos referencian un flujo de contenido y un diccionario de recursos, como para que muchos lectores rendericen el resultado
Pero no es un Form XObject conforme. Le falta el marcador /Subtype /Form y su propia /BBox, lo que significa que un consumidor estricto está en su derecho de ignorar el Do o de recortarlo de una forma distinta a la que esperas. Las TechnicalNotes de esta versión lo dicen con claridad: el enfoque "renderiza en la mayoría de los lectores", pero "no es un Form XObject estrictamente conforme con ISO", y la conformidad total exige sintetizar un flujo real de Form XObject como paso separado. Así que trata la salida de stitch como tratarías cualquier construcción no conforme: verifícala en los visores concretos que usan tus clientes, no solo en el de tu máquina, y si necesitas PDFs aptos para archivo o limpios para validadores estrictos, no dependas de esta ruta. La misma disciplina aplica a todo lo que construyas sobre el grafo de objetos cargado, y por eso una pasada de preflight de PDF en Delphi gana su lugar en la cadena de publicación cada vez que mutas documentos de forma programática
Dónde encajan, y dónde no
Ambos métodos son herramientas de flujo de contenido, así que el modelo mental es el mismo que usarías para dibujo directo. Si has construido páginas desde cero con el componente, los operadores vectoriales y de color detrás de estas llamadas te resultarán familiares desde dibujo en canvas de HotPDF en Delphi; la diferencia es solo que aquí agregas contenido al flujo de otra persona en lugar de uno que te pertenece. Ten presentes tres límites:
- La redacción aquí es visual.
RedactLoadedRectpinta sobre el contenido y nunca lo borra. Para cualquier cosa sensible, regenera la fuente o usa eliminación real de contenido, una caja negra no es seguridad - Stitch no es conforme por diseño. La página de origen se referencia como un pseudo-XObject sin el §8.10.1
/Subtype /Formy/BBox, así que confirma el renderizado en tus visores destino y evítalo donde se requiera validación estricta - Las coordenadas usan el espacio de usuario de la página. Origen abajo a la izquierda, puntos, regidos por el media box propio de la página. Lee la caja con
GetLoadedPageBoxantes de colocar cualquier cosa, porque la página que cargaste puede no ser del tamaño que asumiste
Usados dentro de esos límites, el par cubre un flujo real: reorganizar páginas para impresión, enmascarar regiones no confidenciales y escribir el resultado de vuelta con SaveLoadedDocument, todo sin un re-render completo. La API de documentos cargados que incluye estas primitivas de ensamblado y máscara viene con el HotPDF Component para Delphi y C++Builder, junto con los métodos de campos de formulario, anotaciones y FDF de la misma versión