Cómo Se Estructuran las Páginas de un PDF (Y Por Qué Reordenarlas No Pierde Calidad)
Pdf Tools

Cómo Se Estructuran las Páginas de un PDF (Y Por Qué Reordenarlas No Pierde Calidad)

Introducción

Arrastras una página del medio de un PDF hacia otro documento y el resultado se abre al instante, se renderiza correctamente y el texto sigue siendo seleccionable. Eso no es casualidad ni tampoco es que el lector de PDF "se las arregle" como haría un navegador con HTML mal formado. Funciona así porque un PDF está construido internamente como un grafo de objetos direccionables de forma independiente, no como una secuencia plana de bytes con forma de página. Entender esa estructura explica por qué algunos enfoques ingenuos para combinar o reordenar PDFs producen un archivo corrupto, mientras que una herramienta bien construida como nuestro organizador de páginas PDF puede reordenar, duplicar, eliminar y recombinar páginas de varios documentos de origen sin tocar un solo píxel del contenido.

Este artículo recorre el modelo de objetos real que define la especificación PDF: el árbol de páginas, el diccionario de página, el flujo de contenido, el diccionario de recursos y la tabla de referencias cruzadas que une todo. Dos artículos complementarios se apoyan en esta base — Escaneo Dúplex y el Problema del Orden Inverso y Rotación de Páginas PDF y Orientación, Explicada — pero este es el cimiento sobre el que se apoyan ambos.

En un PDF, Todo Es un Objeto Numerado

Un archivo PDF es, en el fondo, una colección plana de "objetos indirectos", cada uno identificado por un número de objeto y un número de generación, escritos en el archivo como n g obj ... endobj. Un objeto puede ser un diccionario, un array, un flujo (stream: un diccionario con un bloque de bytes crudos adjunto), una cadena, un número o una referencia a otro objeto. Ese último caso — una referencia indirecta, escrita n g R — es lo que convierte la lista plana de objetos en un grafo: un objeto de página no contiene su propio contenido directamente, sino que guarda una referencia al objeto que sí lo tiene.

Esta indirección es deliberada. Permite que dos partes completamente distintas del documento — por ejemplo, un diccionario de página y una entrada del esquema de marcadores — apunten exactamente al mismo objeto subyacente sin duplicarlo. También significa que un lector de PDF (o una librería como pdf-lib, que usan las herramientas de este sitio para manipular PDFs íntegramente en el navegador) puede cargar un documento resolviendo referencias de forma perezosa, leyendo solo los objetos que realmente necesita, en lugar de analizar el archivo entero como un documento lineal. La gramática completa de objetos, flujos y referencias está definida en la propia especificación PDF — ISO 32000-1, publicada libremente por Adobe como referencia base de PDF 1.7 — y todo PDF bien formado, sin importar qué aplicación lo generó, sigue este mismo modelo de objetos.

El Árbol de Páginas: Cómo Organiza un PDF Sus Páginas

En la raíz del grafo de objetos de todo PDF se encuentra el catálogo del documento, referenciado desde el trailer del archivo. El catálogo guarda, entre otras cosas, una referencia al árbol de páginas — la estructura que determina qué páginas existen y en qué orden. La raíz del árbol de páginas es un nodo /Pages, un diccionario con un array /Kids. Cada entrada de ese array es o bien un objeto /Page hoja, o bien otro nodo intermedio /Pages, de modo que los documentos grandes pueden anidar el árbol varios niveles para que el array Kids de cualquier nodo individual se mantenga pequeño — un equilibrio que importa para los lectores que quieren saltar directamente, por ejemplo, a la página 400 de un documento de 500 páginas sin recorrer antes cada nodo hermano.

El orden en que aparecen las páginas en el documento final está determinado enteramente por su posición en este árbol — concretamente, por un recorrido en orden de los arrays /Kids. Nada en el diccionario propio de una página dice "yo soy la página 12". El número de una página es puramente posicional, derivado de dónde se sitúa su objeto en el árbol al momento de renderizar. Este único hecho es todo el mecanismo detrás de reordenar páginas: cambia el orden de las entradas en un array /Kids y habrás cambiado el orden de páginas del documento, sin tocar los objetos de página, su contenido ni nada a lo que hagan referencia.

Dentro de un Diccionario de Página

Cada hoja del árbol de páginas es un objeto /Page — un diccionario que describe la geometría, la apariencia y el contenido de una página. No contiene las instrucciones de dibujo por sí mismo; en su lugar, referencia a los objetos que sí las tienen. Las claves que más importan para entender la manipulación de páginas son:

ClaveFunción
/ParentReferencia indirecta de vuelta al nodo de la página en el árbol de páginas.
/MediaBoxEl límite físico de la página — el tamaño del papel, en puntos, expresado como cuatro coordenadas.
/ResourcesUn diccionario con las fuentes, imágenes y demás recursos que el flujo de contenido de la página puede referenciar por nombre.
/ContentsUna referencia (o array de referencias) al flujo o flujos de contenido que contienen los operadores de dibujo reales.
/RotateEntero opcional, múltiplo de 90, que indica a los visores que roten la página en sentido horario al mostrarla, sin tocar su contenido — detallado en nuestro artículo sobre rotación.

Fíjate en lo que no aparece en esta lista: un número de página. Como la identidad de una página es posicional (su lugar en el árbol) y no intrínseca (un valor guardado en el propio objeto), copiar un objeto de página a un documento nuevo, o moverlo a otra posición dentro del árbol del mismo documento, es una operación estructural sobre el árbol — no una edición de los datos propios de la página.

El Flujo de Contenido: Lo Que Realmente Se Dibuja

El objeto al que apunta la entrada /Contents de una página es un flujo (stream): un diccionario (con metadatos como la longitud del flujo y el filtro de compresión usado) emparejado con un bloque de bytes crudos. Esos bytes, una vez descomprimidos — casi siempre con el filtro FlateDecode, el mismo algoritmo DEFLATE que usan zip y gzip — son una secuencia de operadores de dibujo PDF: BT/ET delimitan un bloque de texto, Tf selecciona una fuente y un tamaño, Tj muestra una cadena, re define un rectángulo, f rellena la trayectoria actual, y así sucesivamente. Un flujo de contenido PDF es, en la práctica, un pequeño programa de dibujo: un guion plano de instrucciones tipo "ve aquí, dibuja esto, pon este color", sin ninguna noción de páginas, documentos ni orden. Solo sabe pintar el espacio de coordenadas de una página.

Esta es la capa donde el texto sigue siendo seleccionable. Los operadores Tj y similares llevan los códigos de carácter reales (mapeados mediante la codificación de la fuente a texto Unicode real), razón por la cual copiar una página a nivel de objeto conserva texto buscable, seleccionable y copiable — los bytes del flujo de contenido se copian literalmente, nunca se renderizan de nuevo a píxeles ni se reinterpretan.

Cómo Se Estructuran las Páginas de un PDF (Y Por Qué Reordenarlas No Pierde Calidad)

El Diccionario de Recursos: Fuentes, Imágenes y Activos Compartidos

Los operadores de dibujo de un flujo de contenido referencian fuentes e imágenes por nombres cortos — /F1, /Im0 — en lugar de incrustarlos en línea. Esos nombres se resuelven a través del diccionario /Resources de la página, que mapea cada nombre al objeto de fuente o imagen real en algún otro lugar del archivo. Esta indirección es lo que hace que los PDF sean razonablemente compactos: un documento de cien páginas con la misma fuente de cuerpo de texto referencia ese único objeto de fuente cien veces, en vez de incrustar cien copias de la fuente.

También importa enormemente para las herramientas que copian páginas entre documentos. Cuando una herramienta organizadora de páginas copia una página, no le basta con copiar el diccionario de página y su flujo de contenido — tiene que recorrer todo el grafo de dependencias del que depende ese flujo: el diccionario de recursos, cada fuente que nombra, cada imagen XObject que nombra, y todo lo que esos objetos a su vez referencien (el flujo de un programa de fuente incrustado, el objeto de espacio de color de una imagen, etcétera). Si se pierde un solo enlace de esa cadena, la página copiada referenciará un objeto que no existe en el nuevo documento — la causa clásica de una página que se abre con fuentes que faltan o un recuadro de imagen en blanco tras una copia mal hecha.

La Tabla de Referencias Cruzadas: Encontrar Objetos Sin Leerlo Todo

Todo lo anterior — objetos, árbol de páginas, flujos de contenido, diccionarios de recursos — está disperso por el archivo en el orden en que la aplicación que lo generó decidió escribirlo. Encontrar un objeto concreto por número implicaría recorrer el archivo entero byte a byte, algo poco práctico para documentos grandes. PDF resuelve esto con una tabla de referencias cruzadas (o, desde PDF 1.5 en adelante, un flujo de referencias cruzadas más compacto): una tabla, normalmente cerca del final del archivo, que asocia cada número de objeto con su desplazamiento exacto en bytes. Un breve diccionario trailer al final del todo apunta al catálogo (el punto de entrada al árbol de páginas) y a esta tabla de referencias cruzadas, de modo que un lector puede abrir un PDF de 500 páginas y saltar directamente a los objetos de la página 300 sin analizar antes las páginas 1 a la 299.

Cada vez que cambia el grafo de objetos de un PDF — una página reordenada, un objeto añadido o eliminado — un archivo válido necesita una tabla de referencias cruzadas cuyos desplazamientos sigan siendo correctos, y un trailer que siga resolviéndose. Este detalle es precisamente lo que hace tan poco fiable la manipulación ingenua de bytes en un PDF, y es el tema de la siguiente sección.

Por Qué Concatenar Bytes Rompe Ambos Documentos

Es tentador pensar que podrías combinar dos PDFs simplemente pegando sus bytes en bruto uno tras otro — al fin y al cabo, eso funciona con archivos de texto plano. No funciona con PDFs, y el modelo de objetos anterior explica exactamente por qué. Cada PDF de origen es un grafo de objetos autocontenido: tiene su propia numeración de objetos empezando cerca de 1, su propia tabla de referencias cruzadas con desplazamientos calculados según la disposición de ese archivo, y su propio trailer único que identifica su propio catálogo. Concatena los bytes en bruto de dos archivos así y obtienes:

  • Números de objeto en colisión. Ambos archivos probablemente tienen un objeto 1, un objeto 2, etcétera. Un lector que intente resolver "el objeto 1" en el archivo combinado no tiene forma de saber a cuál documento pertenecía ese objeto 1.
  • Desplazamientos obsoletos. La tabla de referencias cruzadas del segundo archivo registra desplazamientos medidos desde el inicio de su propio archivo. Tras anteponer los bytes del primer archivo, cada uno de esos desplazamientos apunta al lugar equivocado — normalmente cayendo en mitad de los datos del primer documento.
  • Un trailer ambiguo o sobrescrito. Un lector de PDF busca el trailer, y por tanto el catálogo, trabajando hacia atrás desde el final del archivo. Solo un trailer sobrevive como "el" punto de entrada, así que solo el árbol de páginas de un documento resulta alcanzable — las páginas del otro documento quedan como bytes sin referencia a los que el lector no tiene ningún camino de acceso.

El resultado no es "los dos documentos, unidos". Es un único archivo gravemente corrupto cuyas referencias de objeto apuntan a los lugares equivocados, abierto (si es que se abre) por el visor que sea lo bastante permisivo como para adivinar su camino a través del daño.

Por Qué Sí Funciona la Copia a Nivel de Objeto

El enfoque correcto — el que implementa pdf-lib y el que hay detrás del organizador de PDF de este sitio — nunca toca bytes en bruto. Trabaja únicamente al nivel del grafo de objetos:

  1. Analiza cada documento de origen para obtener su grafo de objetos real en memoria — resolviendo correctamente el trailer, el catálogo y el árbol de páginas, tal como haría cualquier lector conforme a la especificación.
  2. Recorre el grafo de dependencias de cada página solicitada: el diccionario de página, su flujo de contenido, su diccionario de recursos, y cada fuente, imagen y objeto anidado que estos referencien.
  3. Copia todo ese subgrafo a un documento de destino completamente nuevo, asignando números de objeto frescos que no puedan colisionar con nada ya presente en el destino — porque el destino empezó vacío y construye su propia numeración desde cero.
  4. Adjunta los objetos de página copiados al árbol de páginas propio y coherente del destino, en el orden que se haya solicitado.
  5. Reconstruye una única tabla de referencias cruzadas y un único trailer para el documento de destino a partir de su grafo de objetos final y consistente, una sola vez, al guardar el archivo.

Como los bytes del flujo de contenido se copian literalmente en lugar de reinterpretarse, nada se vuelve a renderizar y nada se recomprime — no hay pérdida de calidad, ni la pérdida de generación que habría si una página se rasterizara a imagen y se reinsertara. La API PDFPage de pdf-lib que realiza esta copia también cachea los recursos compartidos por cada operación de copia, así que si cinco páginas de un documento de origen comparten la misma fuente incrustada, esa fuente se incrusta en el destino una sola vez — no cinco.

Qué Significa Esto al Reordenar, Dividir o Combinar Páginas

Una vez que ves las páginas como posiciones en un árbol en lugar de propiedades fijas, toda operación habitual sobre páginas se reduce al mismo movimiento subyacente: reordenar entradas de un array /Kids, o copiar un subgrafo a un árbol nuevo. Reordenar dos páginas intercambia dos entradas. Eliminar una página quita una entrada (y, si nada más referencia su contenido, ese contenido simplemente queda inalcanzable y se descarta la próxima vez que se guarde el archivo). Dividir un documento en dos archivos copia subconjuntos disjuntos del árbol de páginas a dos documentos de destino nuevos. Combinar documentos — ya sea uno tras otro o intercalando página a página, como se cubre en nuestro artículo sobre escaneo dúplex — copia páginas de varios orígenes a un único árbol de destino en la secuencia que el usuario elija.

Esto también explica por qué la rotación de una página viaja intacta a través de cualquiera de estas operaciones. Como /Rotate vive en el propio diccionario de página en lugar de estar horneado en el flujo de contenido, copiar, reordenar o combinar páginas arrastra ese valor de rotación de forma gratuita — nuestro artículo complementario, Rotación de Páginas PDF y Orientación, Explicada, cubre exactamente cómo se comporta ese valor y por qué debe sumarse en lugar de reemplazar la rotación que una página ya llevaba.

Conclusión

Un PDF no es una secuencia de rangos de bytes con forma de página que puedas cortar y pegar; es un grafo de objetos numerados, unidos por referencias, al que se entra por un árbol de páginas e indexado por una tabla de referencias cruzadas para que cualquier objeto pueda encontrarse sin leer el archivo entero. Esa estructura es precisamente lo que hace que reordenar, dividir y recombinar páginas sea una operación puramente estructural y sin pérdida cuando se hace a nivel de objeto — y precisamente por qué pegar bytes de archivo en bruto produce un documento roto en su lugar. La próxima vez que arrastres una página a otra posición, elimines una o combines dos escaneos en un solo archivo con una herramienta como nuestro organizador de PDF, esa operación está reescribiendo un pequeño árbol de punteros — no tocando ni una sola letra del texto subyacente.

← Volver al Blog