Nuestro artículo complementario sobre el orden de páginas en PDF explica la regla básica: el orden de visualización proviene de un recorrido en profundidad, de izquierda a derecha, de los arrays /Kids en el árbol /Pages, nunca de los números de objeto. Este artículo observa el árbol desde otro ángulo: su forma. ¿Por qué los generadores de PDF maduros producen jerarquías de nodos intermedios cuando un único array plano sería perfectamente válido? ¿Qué cambia realmente cuando una herramienta aplana o reconstruye el árbol? ¿Y qué ocurre cuando la contabilidad de /Count que hace rápida toda la estructura deja de decir la verdad
El fan-out es una decisión de rendimiento
Nada obliga a un generador a anidar nodos. Un documento de 10.000 páginas con un único nodo /Pages raíz y 10.000 referencias hoja en un solo array /Kids cumple con la especificación. Aun así, el PDF Reference recomienda un árbol equilibrado para documentos grandes, y los generadores más usados siguen ese consejo con un fan-out modesto, típicamente unas pocas decenas de hijos por nodo intermedio
La razón está en lo que un visualizador debe leer antes de poder mostrar algo. Consideremos un salto directo a la página 8214 de ese archivo de 10.000 páginas. Con un árbol plano, el visualizador primero tiene que analizar el nodo raíz, y ese nodo raíz es un array enorme: con unos ocho bytes por referencia indirecta, resulta un objeto de 80 KB que debe tokenizarse de principio a fin antes de poder resolver la entrada 8213. Con un árbol equilibrado de fan-out 32, el mismo salto lee la raíz, compara los totales acumulados de /Count para elegir el hijo correcto, y desciende: en total tres o cuatro diccionarios pequeños, cada uno de unos cientos de bytes. Ese es el acceso aleatorio O(log n) para el que se diseñó el árbol, y es la razón completa por la que /Count existe en los nodos intermedios: le permite a un lector saltarse un subárbol entero sin abrir un solo objeto dentro de él
La forma del árbol también determina el costo de editar. Una actualización incremental que inserta una página debe reescribir cada nodo cuyo /Kids o /Count haya cambiado, es decir, la ruta desde el padre de la nueva hoja hasta la raíz. En un árbol equilibrado, esa ruta es un puñado de diccionarios pequeños añadidos al archivo. En un árbol plano, la "ruta" es el único array raíz gigante, duplicado por completo en cada revisión. Un contrato que pasa por treinta ciclos de revisión y anotación puede terminar arrastrando treinta copias superadas de ese mismo array de 80 KB en su flujo de bytes
Los nodos interiores llevan atributos heredados
Los nodos intermedios no son solo enrutamiento. Los cuatro atributos de página heredables —/Resources, /MediaBox, /CropBox y /Rotate— pueden colocarse en cualquier nodo /Pages, donde se aplican a todas las hojas debajo de él salvo que un descendiente los anule. Un generador que produce un informe con un apéndice apaisado puede expresar ese diseño directamente en el árbol:
5 0 obj % raíz del documento
<< /Type /Pages /Count 6 /Kids [6 0 R 7 0 R] >>
endobj
6 0 obj % cuerpo del informe: A4 vertical, fuente del cuerpo
<< /Type /Pages /Parent 5 0 R /Count 3
/Kids [30 0 R 31 0 R 32 0 R]
/MediaBox [0 0 595 842]
/Resources << /Font << /F1 8 0 R >> >> >>
endobj
7 0 obj % apéndice: A4 apaisado, rotado, con su propia fuente
<< /Type /Pages /Parent 5 0 R /Count 3
/Kids [40 0 R 41 0 R 42 0 R]
/MediaBox [0 0 842 595] /Rotate 90
/Resources << /Font << /F2 9 0 R >> >> >>
endobj
40 0 obj % página del apéndice: hereda tamaño, rotación, fuentes
<< /Type /Page /Parent 7 0 R /Contents 43 0 R >>
endobj
Los objetos 40 a 42 están casi vacíos. Su tamaño de página, rotación y recursos de fuente llegan todos por herencia desde el nodo 7, lo que mantiene el archivo compacto y autoconsistente: si se añade una cuarta página bajo el nodo del apéndice, sale apaisada automáticamente
El mismo mecanismo crea el clásico riesgo al mover páginas. Supongamos que una herramienta mueve el objeto 40 al cuerpo del informe editando los dos arrays /Kids y redirigiendo /Parent al nodo 6. El movimiento es estructuralmente válido, pero el objeto 40 ahora hereda el /MediaBox vertical, ninguna rotación y la fuente /F1, mientras que su flujo de contenido sigue seleccionando /F2, que ya no se resuelve. La página se encoge, pierde la rotación y pierde su texto en una sola edición. Por eso, el código de reordenamiento robusto materializa los valores resueltos de los cuatro atributos heredables en el diccionario de la página antes de reasignarle el padre. Si alguna vez arrastraste una página en un editor y la viste cambiar de tamaño u orientación, este es el mecanismo que presenciaste
Aplanado: legal, común, ocasionalmente costoso
Muchas herramientas van en sentido contrario. Los generadores mínimos producen un árbol de un solo nivel porque es simple, y muchas utilidades de fusión y división reconstruyen cualquier árbol que leen como un único array /Kids plano, porque generar una estructura equilibrada es trabajo adicional y una salida plana siempre es conforme. Una reconstrucción correcta debe resolver la herencia al mismo tiempo: cada atributo que una hoja heredaba debe copiarse en la hoja, o elevarse a la nueva raíz si es uniforme en todo el documento; de lo contrario, la salida cambia la geometría exactamente como en el caso de mover páginas
Para documentos típicos, aplanar es inofensivo. Duele a gran escala, de las dos formas ya descritas: el array raíz se convierte en un objeto grande que cada apertura y cada salto de página debe analizar por completo, y cada edición estructural lo reescribe entero. Lo que aplanar no destruye es la compartición mediante referencias indirectas: un árbol plano en el que las 10.000 páginas apuntan al mismo objeto diccionario /Resources sigue estando deduplicado. Lo único que se pierde es la opción de omitir la entrada en la página y dejar que un ancestro la proporcione
Cuando /Count miente
/Count es pura contabilidad: debe ser igual al número de páginas hoja en el subárbol del nodo, y nada en el formato de archivo lo obliga. Dos patrones de corrupción explican la mayoría de los recuentos falsos que se ven en la práctica
El primero es el recuento obsoleto que deja una actualización incremental. Un editor inserta una página, reescribe el padre inmediato con un nuevo /Kids y un /Count actualizado, añade ambos al archivo, y nunca toca a los ancestros:
% Revisión original
12 0 obj
<< /Type /Pages /Count 9 /Kids [13 0 R 14 0 R 15 0 R] >>
endobj
14 0 obj
<< /Type /Pages /Parent 12 0 R /Count 3
/Kids [50 0 R 51 0 R 52 0 R] >>
endobj
% Revisión añadida: se insertó una página en la rama intermedia.
% El objeto 14 queda reemplazado; el objeto 12 nunca se reescribe
14 0 obj
<< /Type /Pages /Parent 12 0 R /Count 4
/Kids [50 0 R 51 0 R 90 0 R 52 0 R] >>
endobj
El árbol ahora contiene diez hojas, pero la raíz sigue diciendo nueve. Un visualizador que confía en la raíz reporta nueve páginas en su contador. Uno que usa los recuentos internos para hacer una búsqueda binaria en un salto de página calcula un índice incorrecto para cada página posterior al punto de inserción. Un recorrido completo encuentra diez. Tres respuestas distintas, un solo archivo
El segundo patrón es el recuento que nunca podría ser correcto: negativo, cero en un nodo con contenido, o absurdamente enorme. Estos provienen de fuzzing, de daños durante la transmisión y, ocasionalmente, de errores aritméticos en los editores. Son peligrosos específicamente para el código que confía en /Count para reservar memoria: dimensionar un array a partir de un /Count de -3 provoca, en el mejor de los casos, un error de rango, y hacerlo a partir de un /Count de dos mil millones es una asignación que provoca denegación de servicio. El valor es una entrada no confiable, como cualquier otro número del archivo
Los analizadores se dividen en dos bandos frente a todo esto. Los consumidores estrictos —herramientas de preflight, validadores PDF/A, canalizaciones de archivo— comparan /Count con el resultado del recorrido y rechazan o marcan el archivo. Los visualizadores interactivos son casi universalmente permisivos: recorren el árbol, derivan el recuento real e ignoran silenciosamente el valor almacenado, que es exactamente la razón por la que un archivo con un recuento obsoleto puede circular durante años sin quejas hasta que se topa con un analizador más estricto dentro de algún flujo de trabajo automatizado. El punto medio defensivo para el código de biblioteca es tratar /Count como una pista útil para la reserva previa de memoria, y para saltar subárboles una vez verificado, mientras el recorrido sigue siendo la fuente de verdad
Para el algoritmo de recorrido en sí, las reglas de búsqueda de herencia y el recorrido del catálogo hasta la hoja, comienza con el artículo sobre el orden de páginas. Para ver cómo se manifiestan estos modos de fallo cuando un documento real de un cliente llega al código de producción, lee el caso de estudio sobre depuración del orden de páginas, que sigue un incidente de páginas mezcladas desde el síntoma hasta la causa raíz
El HotPDF Delphi Component se encarga de todo esto internamente: recorre árboles anidados de cualquier profundidad, resuelve los atributos heredados cuando las páginas se copian o se mueven, y verifica /Count contra el recuento real de hojas en lugar de confiar en él, de modo que los índices de página en su API siempre significan páginas lógicas