Artículo técnico

Flujos de objetos y actualizaciones incrementales en Delphi con HotPDF

PDF 1.5 introdujo dos estructuras de almacenamiento que el formato de archivo anterior no tenía forma de expresar: el flujo de objetos (object stream) y el flujo de referencias cruzadas (cross-reference stream). Un flujo de objetos es un contenedor comprimido con Flate, etiquetado como /Type /ObjStm, que alberga muchos objetos indirectos pequeños empaquetados uno tras otro en lugar de dispersarlos por el cuerpo del archivo. Un flujo de referencias cruzadas es la tabla de búsqueda del archivo reescrita como binario comprimido con campos de ancho variable, en lugar de la tabla ASCII de ancho fijo que cerraba cada PDF hasta la versión 1.4. Viajan juntos. Una vez que los objetos se pliegan en un flujo, la antigua tabla de texto ya no puede direccionarlos, por lo que la referencia cruzada (xref) binaria tiene que acompañarlo

Compare esto con el diseño clásico y el costo que elimina es fácil de ver. En un archivo PDF 1.4, cada objeto indirecto se encuentra sin comprimir detrás de su propio encabezado obj, y la tabla en la parte final gasta exactamente 20 bytes de ASCII por entrada, con compresión prohibida. Un documento con 200,000 objetos conlleva aproximadamente 4 MB de datos de referencias cruzadas antes de que se dibuje un solo glifo, con todos los cuerpos de los diccionarios sin comprimir apilados encima. PDF 1.5 ataca ambos números a la vez: los diccionarios se pliegan en contenedores Flate, y la tabla de 4 MB se reduce a unos pocos cientos de kilobytes de binario. La norma ISO 32000-1 define las dos estructuras en §7.5.7 y §7.5.8

Dónde se aplica realmente el ahorro

Los flujos de objetos solo tocan objetos que no son flujos, por lo que comprimen estructura, no píxeles. El contenido de la página ya estaba comprimido con Flate antes de 1.5, y los datos de imagen llevan sus propios códecs, por lo que un folleto con muchas imágenes apenas cambia. Los archivos que se reducen son los que tienen una gran estructura: AcroForms con miles de diccionarios de campos, árboles de esquemas profundos, elementos de estructura de PDF etiquetados. Esos objetos son pequeños, numerosos y casi idénticos entre sí, y esa repetición es exactamente lo que Flate aprovecha una vez que se asientan en un solo búfer en lugar de estar dispersos por el cuerpo con encabezados intercalados entre ellos

Es fácil subestimar qué parte de un archivo antiguo es sobrecarga. Un archivo de formularios que ha absorbido años de ediciones puede gastar más de la mitad de sus bytes en encabezados de diccionarios, relleno de xref y revisiones que ningún lector mirará jamás. Las dos características aquí recuperan las dos primeras. La tercera, las revisiones acumuladas, solo cede ante la compactación, una vez que el archivo ya no tiene que recordar su propia historia

En HotPDF se activan ambas a través de un par de propiedades, y cómo dependen entre sí importa más que el orden en que las escriba:

var
  Pdf: THotPDF;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'catalog-2026.pdf';
    Pdf.UseXRefStream := True;      // binary xref, prerequisite for ObjStm
    Pdf.UseObjectStreams := True;   // pack objects into /Type /ObjStm
    Pdf.BeginDoc;
    Pdf.CurrentPage.SetFont('Arial', [], 11);
    Pdf.CurrentPage.TextOut(50, 760, 0, 'Compressed structure demo');
    Pdf.EndDoc;                     // emits XRefStm + ObjStm containers
  finally
    Pdf.Free;
  end;
end;

UseObjectStreams necesita que UseXRefStream esté establecido en True. Se llega a un objeto comprimido a través de una entrada xref de tipo 2, que registra un número de flujo de objetos más un índice, y una fila de texto clásica de 20 bytes no tiene lugar para almacenar ese par. Así que UseObjectStreams por sí solo no hace nada visible; ambas banderas, establecidas antes de BeginDoc, son la configuración que funciona. Si las establece después de BeginDoc, HotPDF ya se habrá comprometido con el diseño más antiguo

Por qué ambas están desactivadas de forma predeterminada

HotPDF deja ambas propiedades en False de forma predeterminada, y la razón aparece en las integraciones con código descendente (downstream) antiguo. Un lector que solo entiende PDF 1.4 no anuncia que no puede manejar objetos comprimidos. Se encuentra con un flujo xref, no encuentra ninguna de las palabras clave del tráiler que espera, y reporta una tabla de referencias cruzadas dañada o simplemente se niega a abrir el archivo. Si su salida fluye hacia una antigua pasarela de fax, una impresora de hardware que ejecuta un intérprete incrustado, o un analizador que alguien escribió frente a la especificación 1.4 hace una década, mantenga ambas banderas desactivadas para ese canal y acepte el archivo más grande. Para almacenamiento de archivos y entrega web, donde todos los visores principales han leído PDF 1.5 durante veinte años, activarlas es una compresión que obtiene por casi nada

Hay un efecto de segundo orden del que vale la pena informar a su equipo de soporte. Una vez que los diccionarios se empaquetan en flujos de objetos, comparar dos archivos generados byte por byte deja de tener sentido, porque cambiar un solo campo puede volver a comprimir con Flate todo un contenedor y reorganizar todo lo que viene después. Compare las diferencias (diff) de tales archivos por el contenido del objeto, no con una comparación binaria

Actualizaciones incrementales y los desplazamientos de bytes que protegen

Una firma digital cubre un /ByteRange explícito: dos rangos del archivo físico, dados como desplazamientos de bytes absolutos, sobre los cuales se tomó el resumen CMS. Si reescribe el archivo, incluso en algo que parece idéntico en la pantalla, esos desplazamientos se mueven todos. El resumen ya no coincide y la firma se lee como rota. Ese es el problema exacto que ISO 32000-1 §7.5.6 resuelve con actualizaciones incrementales. Los objetos nuevos y modificados se anexan después del %%EOF existente, y luego se escribe una nueva sección de referencias cruzadas cuya entrada /Prev apunta a la anterior. Los bytes originales nunca se perturban, por lo que una revisión firmada sigue siendo verificable y Acrobat puede presentar cada revisión firmada por sí sola en el panel de firmas

HotPDF expone esto a través de su propio punto de entrada:

Pdf.BeginIncrementalUpdate('contract-signed.pdf');
Pdf.AddPage;
Pdf.CurrentPage.SetFont('Arial', [], 10);
Pdf.CurrentPage.TextOut(50, 760, 0, 'Addendum recorded 2026-06-11');
Pdf.SaveIncrementalUpdate('contract-updated.pdf');  // appends the delta only

Dos cosas hacen tropezar a la gente. BeginIncrementalUpdate tiene que recibir el nombre de archivo original, porque la sección xref anexada registra desplazamientos que solo son significativos contra esos bytes originales exactos; apúntelo a una copia renombrada o guardada de nuevo y los desplazamientos describirán un archivo que ya no existe. Y el guardado es solo para anexar por diseño, por lo que la salida siempre es más grande que la entrada. Ese crecimiento no es un desperdicio que deba eliminarse. Es la misma propiedad que deja intactas las revisiones firmadas anteriores

La modificación de un archivo cargado pasa por LoadFromFile

Los desarrolladores que conocieron por primera vez HotPDF a través de su API de generación tienden a chocar con un muro en particular. BeginDoc abre un documento completamente nuevo, que es la herramienta equivocada cuando su intención es cambiar uno que ya existe. La edición de un archivo existente se ejecuta a través de las llamadas de documentos cargados en su lugar:

PageCount := Pdf.LoadFromFile('base.pdf');
Pdf.InsertPagesFromDocument(OtherDoc, '1-3', 5);  // pages 1-3 after page 5
Pdf.MovePage(2, 5);
Pdf.SaveLoadedDocument('modified.pdf');

Mezcle las dos y el síntoma es un archivo de salida que contiene su nuevo contenido y nada del original, porque BeginDoc alegremente construyó un documento nuevo junto al que usted creía que estaba editando. Lea LoadFromFile con SaveLoadedDocument como un vocabulario y BeginDoc con EndDoc como otro. Una rutina que utiliza ambos contra el mismo archivo es casi siempre incorrecta

Cuándo compactar un archivo anexado

El guardado de solo anexo conlleva un costo lento. Un trabajo nocturno que sella una línea de estado en el mismo PDF produce 365 revisiones a lo largo de un año, y cada revisión arrastra una nueva sección xref detrás de ella. Cuando ese historial ha sobrevivido a su utilidad, y ninguna firma en el archivo necesita sobrevivir, puede aplanar todo el asunto volviendo a serializarlo a través de la ruta del documento cargado:

Pdf.LoadFromFile('stamped.pdf');
Pdf.SaveLoadedDocument('compacted.pdf');

Este nuevo guardado es una reescritura completa. Desecha las revisiones anteriores a propósito y rompe cualquier firma que aún esté en el archivo, así que póngalo detrás de la misma puerta de política que aplica a cualquier otro paso destructivo. Una regla de producción que se sostiene: compacte cuando el recuento de revisiones pase un umbral, o cuando la sobrecarga anexada crezca más allá de una parte del archivo base, y nunca compacte un documento cuyo panel de firmas tenga algo en él

Comprobación de la salida antes de su envío

La verificación de este par de características es refrescantemente concreta. Abra el resultado en Adobe Acrobat y confirme tres puntos: las propiedades del documento reportan PDF 1.5 o posterior una vez que los flujos de objetos están activados; el panel de firmas aún valida cada revisión firmada previamente después de una actualización incremental; y el recuento de páginas y los marcadores pasaron ilesos por un ciclo de carga, modificación y guardado. Para salida de archivo, pase el archivo también por veraPDF, ya que un xref comprimido es precisamente el tipo de estructura que un validador estricto escudriña más de cerca que un visor indulgente jamás lo hará. Si su trabajo también involucra entradas muy grandes, los métodos de inspección en nuestro recorrido por la API de archivos directos para flujos de trabajo de PDF grandes se combinan naturalmente con el guardado incremental, y la mecánica de firmas detrás de los rangos de bytes anteriores se trata en profundidad en el artículo sobre firmas digitales y PAdES en HotPDF

Ambas características se incluyen como parte del Componente HotPDF para Delphi y C++Builder, junto a las API de generación, formularios, cifrado y firmas cubiertas en otras partes de este blog. La página del producto enlaza la referencia completa de la API si desea alinear las llamadas anteriores con su propia tubería (pipeline) de documentos