Te llega una petición: toma un lote de extractos ya renderizados, tapa los números de cuenta y entrega dos páginas por hoja para ahorrar papel. Ambas partes de esa tarea son cirugía sobre el flujo de contenido de un PDF que no has creado, así que no hay un lienzo de página cómodo sobre el que dibujar ni un gestor de fuentes al que recurrir. Estás editando directamente el grafo de objetos de un documento cargado, añadiendo operadores de dibujo en bruto a una página que otra herramienta maquetó. HotPDF expone exactamente dos puntos de entrada para esto, y el más peligroso de los dos es el que parece inocuo
HotPDF es un componente PDF nativo de VCL para Delphi y C++Builder. Su API de documentos cargados de la ronda nueve añadió los primeros métodos que crean contenido completamente nuevo en una página abierta desde disco en lugar de una construida desde cero. Dos de ellos son los protagonistas 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 tan importante como eso, qué no hacen, es la diferencia entre una herramienta que funciona y una fuga de datos
Añadir 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 las llamadas de TextOut y de vectores. Una página cargada es distinta: su /Contents es un objeto de flujo existente, quizá compartido, quizá parte de un array de contenido, y tienes que integrarte en él sin corromper lo que ya hay. La ronda nueve introdujo tres pequeños auxiliares que hacen eso de forma segura. NewIndirectStream asigna un THPDFStreamObject indirecto nuevo 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 reescribe /Length para que el objeto guardado siga bien formado
El patrón que siguen ambos métodos públicos es el mismo. Localiza el /Contents de la página, resuélvelo hasta un flujo y, si no hay un flujo utilizable, crea uno y asígnalo. Después añade los operadores. Como los bytes nuevos se añaden al final del flujo, el modelo del pintor garantiza que se rendericen por encima de todo lo que dibujó la maquetación 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 de la gente supone
RedactLoadedRect: una cobertura opaca, no un borrado
RedactLoadedRect toma un índice de página basado en cero, cuatro coordenadas en 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: un ajuste de color de relleno en DeviceRGB (r g b rg), una ruta rectangular (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. Si pasas 0, 0, 0 como color obtienes una franja negra; si pasas 1, 1, 1, una blanca que coincide con una página blanca. Las coordenadas pertenecen al propio espacio de usuario de la página cargada, lo que significa que el origen está en la esquina inferior izquierda y que las unidades son puntos, y también que necesitas el /MediaBox de la página para colocar algo con precisión; GetLoadedPageBox con pbMediaBox te lo da
Lee esto dos veces: un rectángulo relleno cubre el contenido visualmente, no lo elimina. El texto, la imagen o el arte vectorial que hay debajo del rectángulo sigue presente en el PDF, sigue en el grafo de objetos, 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 enmascarado visual, no redacción en sentido legal o de seguridad. Si ocultas datos realmente sensibles, como números de cuenta, historiales médicos, identidades o cualquier cosa regulada, cubrirlos con un recuadro negro y enviar el archivo es una fuga de datos esperando a descubrirse. 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 es una advertencia útil sobre cómo se malinterpretará el resultado, no una promesa sobre lo que elimina. La propia implementación es honesta al respecto en su comentario: se llama a sí misma la "primitiva de redacción visual" 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 documento cargado de HotPDF no hace eso aquí. Así que la regla segura es estrecha: usa RedactLoadedRect para un enmascarado cosmético no sensible, como ocultar una marca de agua de borrador, dejar en blanco una región antes de una captura de pantalla o tapar un logotipo obsoleto en una prueba interna. En el momento en que lo que hay bajo el recuadro importaría si se filtrase, este método deja de ser la herramienta adecuada, 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 el problema más amable porque no se oculta nada, solo se reordena. StitchLoadedPage toma un índice de página destino, un índice de página origen, un desplazamiento X/Y y un factor de escala, y dibuja la página origen sobre la 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 añade 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, Do para invocar un objeto externo y Q para restaurar el estado. El par q/Q importa: aísla la transformación para que la página montada no contagie su sistema de coordenadas a nada que se añada después. El método también protege frente a los errores obvios, como índices fuera de rango, un destino igual al origen o una escala no positiva, que se ajusta a 1.0, y sale en silencio en lugar de lanzar una excepción, así que comprueba tus entradas porque un no-op silencioso parece exactamente un éxito
StitchLoadedPageSideBySide es una comodidad ligera sobre el método general. Lee el ancho del media box del destino, lo divide por dos y llama a StitchLoadedPage con ese medio ancho como desplazamiento X y una escala fija de 0.5, colocando el origen en la mitad derecha. Ese 0.5 codificado asume que origen y destino comparten el mismo ancho; si no es así, el origen no llenará bien su mitad y te convendrá usar el StitchLoadedPage general con una escala que calcules tú mismo a partir de ambos media boxes
La estrategia simplificada de XObject y su compromiso con ISO
Este es el punto en que la implementación hace un atajo deliberado que debes conocer antes de confiar en la salida entre visores. Una imposición N-up correcta envuelve el contenido de la página origen en un Form XObject, un objeto dibujable autocontenido que ISO 32000-1 §8.10.1 dice que debe llevar /Type /XObject, /Subtype /Form y su propio cuadro de recorte /BBox. El montaje de la ronda nueve de HotPDF no construye ese contenedor. En su lugar registra directamente el propio diccionario de página origen bajo /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 de su modelo de contenido, porque ambos referencian un flujo de contenido y un diccionario de recursos, como para que muchos lectores muestren el resultado
Pero no es un Form XObject conforme. Le falta el marcador /Subtype /Form y su propio /BBox, lo que significa que un consumidor estricto puede ignorar legítimamente Do o recortarlo de forma distinta a la que esperas. Las TechnicalNotes de esta ronda lo dicen con claridad: el enfoque "se renderiza en la mayoría de lectores", pero "no es un Form XObject estrictamente conforme con ISO", y el cumplimiento total exige sintetizar un verdadero flujo 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 de archivo o limpios para validadores estrictos, no dependas de esta ruta. La misma disciplina se aplica a cualquier cosa que construyas sobre el grafo de objetos cargado, por eso un paso de preflight de PDF en Delphi se gana su lugar en la canalización 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 usas para el dibujo directo. Si has construido páginas desde cero con el componente, los operadores de vector y color detrás de estas llamadas te resultarán familiares por el dibujo en lienzo de HotPDF en Delphi; la diferencia es solo que aquí añades a un flujo que otro autor ya creó en lugar de a uno propio. Ten presentes tres límites:
- La redacción es cosmética.
RedactLoadedRectpinta encima del contenido y nunca lo borra. Para cualquier cosa sensible, regenera el origen o usa una eliminación real de contenido, un recuadro negro no es seguridad - El montaje no es conforme por diseño. La página origen se referencia como un pseudo-XObject sin el
/Subtype /Formy el/BBoxde §8.10.1, así que confirma el renderizado en tus visores objetivo y evítalo donde se requiera validación estricta - Las coordenadas están en el espacio de usuario de la página. Origen en la esquina inferior izquierda, puntos, guiado por el media box propio de la página. Lee el cuadro con
GetLoadedPageBoxantes de colocar nada, porque la página que cargaste puede no tener el tamaño que suponías
Usados dentro de esos límites, el par cubre un flujo real: reordenar páginas para imprimir, enmascarar regiones no confidenciales y escribir el resultado de vuelta con SaveLoadedDocument, todo ello sin volver a renderizar por completo. La API de documento cargado que incluye estas primitivas de montaje y enmascarado se distribuye con HotPDF Component para Delphi y C++Builder, junto con los métodos de campos de formulario, anotaciones y FDF de la misma ronda