Abra un PDF que haya sobrevivido varios años en producción y a menudo encontrará más contabilidad interna que contenido: miles de pequeños objetos de diccionario, cada uno precedido por su propia cabecera obj, más una tabla de referencias cruzadas que cuesta 20 bytes ASCII por objeto. En un caso de soporte que analizamos, un archivo de pólizas de 58 MB dedicaba menos de la mitad de sus bytes al contenido real de las páginas; el resto era sobrecarga estructural y revisiones antiguas. HotPDF, una biblioteca PDF VCL nativa para Delphi y C++Builder, expone los dos mecanismos del formato que atacan ambas mitades del problema: object streams para almacenamiento compacto y actualizaciones incrementales para modificaciones solo de anexado que no destruyen lo anterior.
Cómo los object streams y los xref streams remodelan el fichero
Hasta PDF 1.4, cada objeto indirecto reside sin comprimir en el cuerpo, y la tabla de referencias cruzadas del final es una estructura de texto de anchura fija: exactamente 20 bytes por entrada, sin compresión posible. Por tanto, un documento con 200.000 objetos arrastra unos 4 MB solo de datos xref, antes de dibujar un solo glifo. PDF 1.5 introdujo dos sustitutos, definidos en ISO 32000-1 §7.5.7 y §7.5.8: object streams (/Type /ObjStm), que agrupan objetos que no son streams en un único contenedor comprimido con Flate, y cross-reference streams, que almacenan la propia tabla de búsqueda como binario comprimido con campos de anchura variable.
El ahorro es mayor justo donde más daño hacen los generadores ingenuos: documentos cargados de formularios con miles de diccionarios de campos, árboles de marcadores profundos y elementos de estructura de PDF etiquetado. Esos objetos son minúsculos y muy repetitivos por separado, lo que los convierte en una entrada ideal para Flate cuando se empaquetan juntos. Los streams de contenido de página ya eran comprimibles antes de PDF 1.5, así que los object streams no reducen mucho los ficheros dominados por imágenes; reducen de forma drástica los ficheros dominados por estructura.
En HotPDF las dos funciones se activan con un par de propiedades, y la dependencia de orden importa:
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 requiere que UseXRefStream sea True, porque un objeto comprimido se direcciona mediante una entrada xref de tipo 2 (número del object stream más índice), y las entradas de tipo 2 sencillamente no se pueden expresar en una tabla de texto clásica de 20 bytes. Activar solo UseObjectStreams no aporta nada; activar ambas antes de BeginDoc es la configuración que funciona.
El límite de compatibilidad en PDF 1.4
Ambas propiedades tienen False como valor predeterminado por un motivo que muerde a los equipos con integraciones antiguas. Un lector limitado a la semántica de PDF 1.4 no informa de "objetos comprimidos no soportados" cuando encuentra un xref stream; normalmente informa de una tabla de referencias cruzadas dañada o rechaza el fichero directamente, porque la disposición de palabras clave del tráiler que espera no está ahí. Si su salida alimenta una pasarela de fax antigua, una impresora hardware con intérprete embebido o un analizador posterior escrito contra PDF 1.4, deje desactivadas ambas marcas para ese canal y acepte el fichero más grande. Para canales de archivo y entrega web, donde cualquier visor principal maneja PDF 1.5 desde hace dos décadas, activarlas es casi compresión gratuita.
Hay un efecto operativo que conviene señalar al equipo de soporte: una vez que los diccionarios se empaquetan en object streams, la comparación byte a byte de dos ficheros generados deja de tener sentido, porque un cambio de un solo campo puede volver a comprimir un contenedor entero. Las investigaciones de soporte deben comparar documentos de forma lógica, por contenido de objetos, no con un diff binario.
Por qué existen las actualizaciones incrementales: offsets, firmas y trazabilidad
Una firma digital en PDF cubre un /ByteRange explícito: dos tramos del fichero físico, medidos en desplazamientos absolutos de byte, sobre los que se calculó el resumen CMS. Reescriba el fichero, aunque el contenido visible sea idéntico, y todos los desplazamientos se moverán; el resumen ya no coincidirá y la firma aparecerá rota. Esta es la razón concreta por la que ISO 32000-1 §7.5.6 define las actualizaciones incrementales: los objetos modificados y añadidos se anexan después del %%EOF existente, seguidos por una nueva sección de referencias cruzadas cuya entrada /Prev apunta a la anterior. Los bytes originales nunca se tocan, así que una revisión firmada con anterioridad sigue siendo verificable, y Acrobat puede mostrar cada revisión firmada por separado en el panel de firmas.
HotPDF lo envuelve como un punto de entrada dedicado:
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
Hay dos detalles fáciles de equivocarse aquí. Primero, BeginIncrementalUpdate debe recibir el nombre del fichero original; la sección xref anexada almacena offsets que solo son válidos respecto a esos bytes originales exactos. Segundo, el guardado es solo de anexado por definición, así que la salida siempre es mayor que la entrada. No es un defecto que haya que optimizar hasta hacerlo desaparecer; es la propiedad que mantiene intactas las revisiones firmadas anteriores.
Editar documentos cargados: LoadFromFile, no BeginDoc
Otra trampa atrapa a los desarrolladores que aprendieron HotPDF a través de la API de generación. BeginDoc inicia un documento nuevo; es la llamada equivocada cuando el objetivo es modificar uno existente. La cirugía documental pasa por la ruta de documento cargado:
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');
El síntoma de mezclar ambos modelos es un fichero que contiene solo el contenido nuevo y nada del original, porque BeginDoc creó encantado un documento fresco junto al que usted creía estar editando. Al revisar código, trate LoadFromFile + SaveLoadedDocument como un vocabulario emparejado y BeginDoc + EndDoc como otro; un procedimiento que usa ambos para el mismo documento es casi siempre un bug.
Contener el crecimiento: cuándo compactar un fichero anexado
El guardado solo de anexado tiene un coste a largo plazo. Una tarea nocturna que estampa una línea de estado sobre el mismo PDF produce 365 revisiones al año, y cada revisión arrastra consigo una nueva sección xref. Cuando el historial de revisiones ya ha cumplido su cometido, y siempre que no tenga que sobrevivir ninguna firma, el fichero puede compactarse reserializándolo por la ruta de documento cargado:
Pdf.LoadFromFile('stamped.pdf');
Pdf.SaveLoadedDocument('compacted.pdf');
El re-guardado es una reescritura completa, lo que significa que descarta deliberadamente las revisiones anteriores e invalidará cualquier firma del fichero; por tanto, páselo por el mismo control de política que usaría antes de cualquier operación destructiva. Una regla razonable de producción que hemos visto funcionar: compactar cuando el recuento de revisiones cruza un umbral o cuando la sobrecarga anexada supera un porcentaje del fichero base, y no compactar nunca ficheros cuyo panel de firmas no esté vacío.
Comprobar el resultado antes de enviarlo
La verificación de este par de funciones es agradablemente mecánica. Abra la salida en Adobe Acrobat y compruebe tres cosas: las propiedades del documento indican PDF 1.5 o posterior cuando los object streams están activados; el panel de firmas sigue validando cada revisión firmada previamente después de una actualización incremental; y el número de páginas y los marcadores han sobrevivido a un ciclo cargar-modificar-guardar. Para canales de archivo, pase además el fichero por veraPDF, porque las estructuras xref comprimidas son justo el tipo de cosa que un analizador estricto examina con más cuidado que un visor tolerante. Si también procesa entradas muy grandes, las técnicas de inspección de nuestra guía de la Direct File API para flujos de trabajo con PDF grandes combinan bien con el guardado incremental, y la mecánica de firmas mencionada arriba se cubre a fondo en el artículo sobre firmas digitales y PAdES en HotPDF.
FAQ
¿Activar object streams romperá lectores PDF antiguos?
Los lectores que solo implementan PDF 1.4 no pueden analizar xref streams y normalmente informan de que el fichero está dañado. Mantenga UseXRefStream y UseObjectStreams en su valor predeterminado False para canales que alimenten intérpretes antiguos, y actívelas para visores modernos y canales de archivo.
¿Una actualización incremental mantiene válida mi firma digital?
Sí, ese es su propósito: los nuevos objetos se anexan después de los bytes firmados, así que el /ByteRange firmado sigue calculando el resumen correctamente. Una reescritura completa, incluida la compactación por cargar y guardar de nuevo, rompe todas las firmas existentes aunque el contenido visible no cambie.
¿Por qué mi fichero sigue creciendo tras guardados repetidos?
El guardado incremental anexa un delta en cada guardado y nunca recupera espacio. Compacte de vez en cuando con LoadFromFile más SaveLoadedDocument cuando ya no haga falta preservar el historial de revisiones ni las firmas.
Adónde ir ahora
Ambas funciones forman parte del conjunto estándar de prestaciones de HotPDF Component, junto con las API de generación, formularios, cifrado y firma mostradas en otros artículos de este blog. La página del producto enlaza la documentación completa de la API si quiere trasladar las llamadas anteriores a su propia canalización documental.