Artículo técnico

El modelo de objetos lógicos de PDF: tipos, referencias y estructura

Un archivo PDF es, en esencia, una colección de objetos que se apuntan entre sí. Quite la compresión, la contabilidad de referencias cruzadas y los desplazamientos de bytes, y lo que queda es un gráfico: un pequeño conjunto de valores tipificados, conectados por referencias, enraizados en un solo objeto que el lector sabe cómo encontrar. Todo lo que un PDF puede expresar, desde un párrafo de texto hasta una fuente incrustada o una firma digital, se construye a partir de ocho tipos de objetos primitivos y la regla que permite que un objeto haga referencia a otro. Aprenda esos, y el resto del formato se lee como composición en lugar de misterio

Esta es la capa lógica de PDF, definida en la cláusula 7.3 de ISO 32000-1, y se encuentra un nivel por encima del diseño físico del archivo (el encabezado, el cuerpo, la tabla de referencias cruzadas y el tráiler, que es su propio tema en la descripción técnica general de la estructura del archivo PDF). El modelo lógico es lo que significan esos bytes una vez analizados. Un visor lee el archivo hacia atrás para encontrar el tráiler, lo sigue hasta la raíz, y desde allí el documento se despliega como objetos referenciando objetos. Esta es la parte sobre la que usted razona cuando depura una página malformada, escribe un analizador o confía en una biblioteca para ensamblar un documento

Ocho tipos de objetos, y nada más

PDF define exactamente ocho tipos de objetos básicos. Cada valor en un documento es uno de ellos, que es lo que mantiene el formato manejable a pesar de su alcance

Los booleanos son las palabras clave true y false. Activan y desactivan indicadores, como por ejemplo si se imprime una anotación

Los números vienen en dos variantes que la especificación trata como un solo tipo: enteros como 42 y reales como 3.14 o -0.002. PDF no tiene notación de exponente, por lo que nunca verá 1e6 en un archivo conforme a la especificación. Las coordenadas, los tamaños de fuente y los ángulos de rotación son todos números

Las cadenas (strings) contienen secuencias de bytes, escritas entre paréntesis, (Hello), o entre corchetes angulares como hexadecimal, <48656C6C6F>. Ambas notaciones codifican contenido idéntico; el formato hexadecimal es la vía de escape para los bytes que resultan incómodos dentro de los paréntesis. Las cadenas transportan texto, pero son bytes en primer lugar, lo cual es importante en el momento en que se maneja algo más allá de ASCII

Los nombres son tokens atómicos introducidos por una barra diagonal (slash): /Type, /Pages, /MediaBox. Un nombre no es una cadena; es un identificador, utilizado como clave de diccionario o como valor enumerado, y dos nombres son iguales solo si coinciden byte por byte. La barra diagonal es sintaxis, no parte del nombre. Esto confunde a los recién llegados que tratan /Times-Roman y la cadena (Times-Roman) como intercambiables; el formato no lo hace

Los arreglos (arrays) son listas ordenadas y heterogéneas entre corchetes: [0 0 612 792] es el rectángulo de una página, y un arreglo puede mezclar tipos libremente, incluidas las referencias a otros objetos. Los diccionarios son el caballo de batalla. Escrito entre << y >>, un diccionario asigna claves de nombre a valores de cualquier tipo, y casi todas las estructuras significativas en PDF (página, catálogo, fuente, anotación) son un diccionario con una clave /Type que declara lo que es

Los flujos (streams) son diccionarios con una cola de bytes sin procesar entre las palabras clave stream y endstream. El diccionario describe los bytes (su longitud y cualquier filtro como FlateDecode que los comprima), y los bytes transportan la carga voluminosa: instrucciones de contenido de la página, programas de fuentes incrustados, imágenes. Un flujo es donde PDF coloca todo lo que es demasiado grande o demasiado binario para estar en línea (inline)

El octavo tipo es el objeto nulo (null object), la palabra clave null. Es un valor real, distinto de la ausencia de una clave. Una entrada de diccionario establecida en null se trata como si no estuviera presente, y una referencia que se resuelve en un objeto inexistente también produce null en lugar de un error. Ese comportamiento indulgente es deliberado: permite que un archivo dañado se degrade en lugar de negarse a abrir. No hay un noveno tipo; todo lo que expresa PDF proviene de cómo se combinan estos ocho

Valores directos, objetos indirectos y referencias

Cualquiera de esos ocho tipos puede aparecer de dos maneras. Un objeto directo se escribe en su lugar, como el 612 dentro de un arreglo MediaBox. A un objeto indirecto se le da una identidad para que otros objetos puedan apuntar a él: dos números enteros, un número de objeto y un número de generación, envolviendo la definición en obj y endobj:

12 0 obj
<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>
endobj

Este es el objeto 12, generación 0, un diccionario de fuentes. En cualquier otro lugar del archivo, otro objeto se refiere a él con una referencia indirecta: los mismos dos números seguidos de la palabra clave R, 12 0 R. La referencia es un puntero. Cuando el diccionario de recursos de una página dice /Font << /F1 12 0 R >>, nombra al objeto 12 como la fuente detrás del nombre del recurso /F1, sin copiar la definición de la fuente en la página

El número de generación existe para eliminaciones y reutilización. Cuando se libera un objeto y se reutiliza su espacio, la generación se incrementa para que un 12 0 R obsoleto no pueda resolverse en el nuevo inquilino del espacio 12. Los archivos recién escritos son casi todos de la generación 0, pero un archivo muy editado puede llevar números más altos, y un analizador que ignora la generación eventualmente leerá el objeto equivocado

La indirección es lo que hace que PDF sea eficiente y editable. Una fuente, imagen o espacio de color se puede definir una vez y referenciar desde cien páginas. Un pequeño cambio se puede agregar como una nueva revisión que reemplaza un solo objeto en lugar de reescribir el archivo. La tabla de referencias cruzadas es el índice que convierte un número de objeto en un desplazamiento de bytes, por lo que el lector salta directamente a 12 0 obj sin escanear, pero eso es una optimización física. Lógicamente, todo lo que necesita saber es que 12 0 R significa "el objeto identificado como 12 0"

El catálogo: donde comienza todo documento

La resolución de referencias tiene que empezar en alguna parte, y ese lugar es la entrada /Root del tráiler, que apunta al catálogo del documento: la raíz del gráfico de objetos, un diccionario con /Type /Catalog. El lector lo alcanza primero porque el tráiler se encuentra primero, y desde allí se puede acceder a todas las demás partes del documento siguiendo las referencias

El catálogo solo contiene dos entradas estrictamente necesarias: su /Type y /Pages, una referencia indirecta a la raíz del árbol de páginas. El resto son opcionales y describen el comportamiento de todo el documento en lugar del contenido: /Outlines apunta al árbol de marcadores, /Names contiene árboles de nombres codificados por cadenas, /Metadata hace referencia a un flujo de metadatos XMP, y /PageMode y /PageLayout sugieren cómo un visor debería abrir el documento. Ninguno de ellos es necesario para representar una página; configuran la experiencia alrededor de las páginas. Las estructuras de marcadores, metadatos y anotaciones que cuelgan del catálogo se abordan en el artículo sobre metadatos, marcadores y anotaciones de PDF

El siguiente diagrama muestra dónde se encuentra el cuerpo del objeto en el archivo circundante. El catálogo y el árbol de páginas viven dentro de ese cuerpo como objetos indirectos ordinarios; el encabezado, la tabla de referencias cruzadas y el tráiler alrededor de ellos son el andamiaje físico que permite a un lector ubicarlos

Diagrama de las cuatro secciones físicas de un archivo PDF: un encabezado de versión, un cuerpo que contiene los objetos del documento, incluido el catálogo y el árbol de páginas, una tabla de referencias cruzadas de desplazamientos de objetos y un tráiler que apunta a la raíz.

El árbol de páginas: una jerarquía equilibrada de páginas

Desde /Pages, el documento se ramifica en el árbol de páginas, donde la elección de PDF de un gráfico sobre una lista plana vale la pena. Las páginas no se almacenan como una secuencia simple; cuelgan de un árbol cuyos nodos interiores son nodos del árbol de páginas (/Type /Pages) y cuyas hojas son objetos de página (/Type /Page). Un nodo interior enumera a sus hijos en un arreglo /Kids y registra, en /Count, cuántas páginas de hojas viven debajo de él. Todos los nodos, excepto la raíz, llevan una referencia /Parent hacia arriba, por lo que el árbol avanza en cualquier dirección

2 0 obj                                  % root of the page tree
<< /Type /Pages /Kids [3 0 R 4 0 R] /Count 3 >>
endobj

3 0 obj                                  % a leaf page
<< /Type /Page /Parent 2 0 R
   /MediaBox [0 0 612 792]
   /Resources << /Font << /F1 12 0 R >> >>
   /Contents 5 0 R >>
endobj

4 0 obj                                  % an interior node grouping two more pages
<< /Type /Pages /Parent 2 0 R /Kids [6 0 R 7 0 R] /Count 2 >>
endobj

Aquí el objeto 2 es la raíz, con tres páginas debajo de él: la página hoja 3, más dos accesibles a través del nodo interior 4. El /Count de 3 de la raíz tiene que ser igual al total de hojas debajo de él, y un recuento que no concuerda con la estructura real es una forma común en que un archivo editado a mano sale mal. El punto del árbol es la localidad de acceso. Un lector que abre la página 900 de un documento de mil páginas no recorre 900 objetos; desciende un puñado de nodos, porque un árbol bien formado se mantiene poco profundo y equilibrado. Construir un árbol de este tipo a mano es lo suficientemente complicado como para que valga la pena verlo de principio a fin, lo que hace el tutorial sobre cómo crear un documento PDF desde cero

El árbol gana su segunda utilidad a través de la herencia. Un puñado de atributos de página, /Resources, /MediaBox, /CropBox y /Rotate, se pueden establecer en un nodo interior y dejarse fuera de las páginas individuales, que luego heredan el valor del antepasado más cercano. Establezca /MediaBox una vez en la raíz y cada hoja obtiene el mismo tamaño de página sin repetirlo; una página que necesita diferir declara el suyo propio. Este es el único lugar en el modelo de objetos donde el significado de un valor depende de la posición de un objeto en el árbol, no solo de su propio contenido

Lo que realmente contiene una página hoja

Un objeto de página es el punto de unión entre el modelo estructural y el contenido visible. Su entrada /Contents hace referencia a uno o más flujos de contenido, los operadores de dibujo que pintan texto y gráficos en la página. Su diccionario /Resources nombra las fuentes, imágenes y espacios de color en los que se basan esos operadores, cada entrada una referencia indirecta a un objeto compartido entre páginas. /MediaBox proporciona el rectángulo de la página en puntos (1/72 de pulgada), y entradas como /Rotate y /CropBox ajustan cómo se presenta

Esa división del trabajo es todo el modelo en miniatura. El diccionario de la página es la estructura: entradas tipificadas y referencias que dicen qué es la página y con qué dibuja. El flujo de contenido son instrucciones: un bloque (blob) separado y comprimible que dice cómo dibujar. La fuente detrás de /F1 es un recurso compartido, definido una vez y señalado donde sea que se use. Diccionario, flujo y referencia cooperan para renderizar una página, y los mismos patrones se escalan a todo el documento. Los operadores del flujo de contenido dentro de ese bloque se tratan por separado para texto y fuentes y para gráficos y elementos visuales

Por qué vale la pena conocer este modelo

La mayoría de los desarrolladores se encuentran con el modelo de objetos solo cuando algo se rompe: una página se renderiza en blanco porque su referencia /Contents cuelga, el texto sale como cuadros porque un recurso de fuente nunca se incrustó, una herramienta informa un /Count que no coincide con las páginas que puede encontrar. Cada uno de ellos es una declaración sobre el gráfico, y leer el gráfico directamente es mejor que adivinar. Los ocho tipos y la regla de referencia son un vocabulario lo suficientemente pequeño como para retenerlo en la cabeza, y una vez que vea un PDF como objetos apuntando a objetos, los archivos malformados dejan de ser opacos

Dicho esto, escribir el modelo a mano rara vez es la decisión correcta más allá del aprendizaje. Mantener las compensaciones de referencias cruzadas, los números de generación, los recuentos del árbol de páginas y las longitudes de los flujos consistentes en todas las ediciones es el tipo de contabilidad que una biblioteca debe manejar. En producción, una biblioteca de desarrollo PDF madura gestiona el gráfico de objetos mientras le permite pensar en páginas y contenido. Conocer el modelo sigue dando sus frutos: usted entiende lo que la biblioteca construye debajo y por qué