Un PDF es, en el fondo, un contenedor de texto sin formato. Abra la mayoría de los archivos en un editor hexadecimal y la parte superior será legible: un comentario de versión, luego una serie de objetos numerados, después un pequeño índice y un puntero en la parte inferior que indica al lector dónde comenzar. Si se elimina la compresión, el formato es lo suficientemente accesible como para escribir un documento funcional en un editor de texto y hacer que un visor lo abra. Hacer esto una vez enseña más sobre cómo se estructura un PDF que cualquier cantidad de lectura de la especificación, porque obliga a conectar los objetos entre sí manualmente, y el archivo se niega a abrirse hasta que el cableado sea correcto
Este recorrido construye el PDF más pequeño que realmente renderiza algo: una página, las palabras "Hello, World!" en una fuente incorporada, en papel tamaño Carta (US Letter). El archivo terminado necesita exactamente cinco objetos y unas pocas líneas de contabilidad alrededor de ellos. Primero escribiremos los objetos, luego ensamblaremos el encabezado, la tabla de referencias cruzadas (xref) y el tráiler que los unen en un archivo que un lector aceptará
Los cinco objetos que exige un visor
Un lector no escanea un PDF de arriba a abajo en busca de contenido. Comienza en el tráiler, sigue una referencia al catálogo del documento y recorre una cadena de objetos a partir de ahí. Cada objeto en esa cadena tiene que existir o la apertura fallará. Para un documento de una página, la cadena es corta y cada eslabón tiene una sola función:
- Catalog es la raíz. Es el objeto al que apunta el tráiler, y su única entrada requerida aquí es una referencia al árbol de páginas
- Pages es el nodo del árbol de páginas. Enumera las páginas del documento e informa cuántas hay
- Page describe una página física: su tamaño, los recursos con los que dibuja y qué flujo de contenido (content stream) la pinta
- El flujo de contenido (content stream) contiene los operadores de dibujo, los comandos posfijos que colocan texto y gráficos en esa página
- Font declara el tipo de letra al que se refiere el flujo de contenido. Use una de las 14 fuentes estándar y no tendrá que incrustar nada
Cada objeto está numerado y es direccionable. Un objeto indirecto se escribe como N 0 obj ... endobj, donde N es el número de objeto y el 0 es su número de generación (siempre 0 en un archivo que escribe desde cero). En cualquier otra parte del archivo, se apunta a ese objeto con una referencia: 5 0 R significa "objeto 5". Esas referencias son el cableado. El catálogo contiene 2 0 R en nuestra numeración para llegar al árbol de páginas, el árbol de páginas contiene una referencia hacia la página, y así sucesivamente. Si se equivoca en un número, el lector sigue un puntero colgante hacia la nada
Nombres, diccionarios y flujos (streams)
Tres elementos de sintaxis abarcan casi todo. Un nombre comienza con una barra diagonal: /Type, /Page, /F0. Los nombres son identificadores que distinguen entre mayúsculas y minúsculas, no son cadenas, y PDF los usa para claves de diccionario y para etiquetar qué es un objeto. Un diccionario es un conjunto de pares clave-valor envueltos en corchetes angulares dobles, donde cada clave es un nombre: << /Type /Page /MediaBox [0 0 612 792] >>. Los valores pueden ser números, nombres, arreglos en corchetes, referencias o diccionarios anidados. La mayoría de los objetos PDF son diccionarios
Un flujo (stream) es un diccionario seguido por un bloque de bytes entre las palabras clave stream y endstream. Ahí es donde viven los operadores de dibujo de la página, y en archivos reales es donde también viven las imágenes comprimidas y las fuentes incrustadas. El diccionario del flujo describe los bytes; en un archivo de producción debe llevar una entrada /Length que da el recuento exacto de bytes, y a menudo un /Filter como /FlateDecode cuando los datos están comprimidos. Nos apoyaremos en una herramienta para completar /Length, porque contar bytes a mano es la parte de este ejercicio que no tiene beneficio educativo y sí una alta probabilidad de un error por uno que rompa el archivo
Escribiendo los objetos
Aquí están los cinco objetos en orden. El detalle de coordenadas a tener en cuenta antes de leer el flujo de contenido: PDF mide desde la esquina inferior izquierda de la página en puntos, donde un punto es 1/72 de pulgada, e Y crece hacia arriba. Una página Carta estadounidense mide 612 por 792 puntos, por lo que 50 700 se sitúa cerca de la parte superior izquierda, no en la inferior
1 0 obj
<< /Type /Catalog
/Pages 2 0 R
>>
endobj
2 0 obj
<< /Type /Pages
/Kids [3 0 R]
/Count 1
>>
endobj
3 0 obj
<< /Type /Page
/Parent 2 0 R
/MediaBox [0 0 612 792]
/Resources << /Font << /F0 4 0 R >> >>
/Contents 5 0 R
>>
endobj
4 0 obj
<< /Type /Font
/Subtype /Type1
/BaseFont /Helvetica
>>
endobj
5 0 obj
<< /Length 44 >>
stream
BT
/F0 36 Tf
50 700 Td
(Hello, World!) Tj
ET
endstream
endobj
Lea las referencias y la estructura se revela por sí sola. El objeto 1, el catálogo, apunta su entrada /Pages al objeto 2. El objeto 2, el árbol de páginas, enumera al objeto 3 en /Kids y declara /Count 1. El objeto 3, la página, apunta con /Parent hacia arriba al objeto 2 (el árbol y la página se referencian mutuamente, lo cual es obligatorio), se dimensiona con /MediaBox, expone la fuente bajo el nombre local /F0 en sus /Resources, y nombra al objeto 5 como su contenido. El objeto 4 es la fuente: /BaseFont /Helvetica elige uno de los 14 tipos de letra estándar que todo lector conforme ya tiene, por lo que no hay nada que incrustar. El objeto 5 es el flujo de contenido
Lo que realmente dice el flujo de contenido
El cuerpo del flujo es un programa diminuto en el lenguaje de descripción de página de PDF, que es posfijo: primero vienen los operandos, luego el operador que los consume. Cinco líneas hacen el trabajo. BT y ET abren y cierran un objeto de texto; todo lo que posiciona o muestra texto debe estar entre ellos. /F0 36 Tf establece la fuente actual al recurso llamado /F0 a 36 puntos (Tf significa "establecer la fuente y el tamaño del texto"). 50 700 Td mueve la posición del texto a (50, 700) en coordenadas de la página. (Hello, World!) Tj muestra la cadena, que PDF escribe como texto literal entre paréntesis, usando Tj para pintarla en la posición actual. Omita BT/ET y un lector estricto rechazará los operadores de texto; olvide establecer una fuente antes de Tj y no habrá fuente actual para dibujar
El /Length 44 en el diccionario del flujo es el recuento de bytes entre stream y endstream, y tiene que ser exacto. Este es el valor que vale la pena pasar a una herramienta en lugar de contar saltos de línea a mano, especialmente porque el hecho de que su editor escriba los finales de línea como LF o CRLF cambia el total
Encabezado, xref y tráiler
Los objetos son el contenido. Tres piezas estructurales los convierten en un archivo. La primera es el encabezado, la primera línea de todas, que nombra el formato y la versión:
%PDF-1.7
El % comienza un comentario en la sintaxis de PDF, pero un lector trata este comentario en particular como la firma del formato y lee la versión de él. Un escritor real lo sigue inmediatamente con una segunda línea de comentario de bytes de alto bit, una pista para las herramientas de transferencia de archivos de que el archivo es binario y no debe estropearse como texto
Al final del archivo viene la tabla de referencias cruzadas, el índice que hace posible el acceso aleatorio. Registra el desplazamiento de bytes (offset) de cada objeto desde el inicio del archivo, por lo que un lector puede ir directamente al objeto 3 sin analizar los objetos 1 y 2 primero. La tabla es rígida: las entradas tienen un ancho fijo de 20 bytes cada una, incluido el final de línea, formateadas como un desplazamiento de 10 dígitos, una generación de 5 dígitos, una palabra clave (n para en uso, f para libre) y un terminador de dos bytes. Una tabla correcta para nuestras seis entradas (el objeto 0 es siempre la cabecera de la lista libre) se ve así:
xref
0 6
0000000000 65535 f
0000000009 00000 n
0000000058 00000 n
0000000115 00000 n
0000000235 00000 n
0000000308 00000 n
trailer
<< /Size 6
/Root 1 0 R
>>
startxref
408
%%EOF
Esos desplazamientos son la parte frágil de escribir archivos PDF a mano. Cada uno es la posición exacta en bytes donde comienza el correspondiente N 0 obj, y cada desplazamiento cambia en el momento en que agrega un carácter en cualquier lugar por encima de él. El tráiler es el punto de entrada que un lector utiliza de último y de primero: /Root 1 0 R nombra el catálogo, /Size 6 establece el recuento de objetos, y startxref 408 da el desplazamiento en bytes de la propia palabra xref. Un lector abre el archivo, salta al final, lee startxref, busca la tabla de referencias cruzadas y desde allí llega al catálogo y a todo lo que hay debajo de él. %%EOF marca el último byte
Deje que una herramienta arregle los recuentos de bytes
Los desplazamientos anteriores son ilustrativos; en la práctica serán incorrectos para cuando termine de escribir, porque dependen de la disposición exacta de los bytes de su archivo. En lugar de volver a calcularlos, escriba la estructura con valores provisionales y deje que una utilidad reconstruya la tabla de referencias cruzadas y las longitudes de los flujos. La herramienta gratuita y multiplataforma pdftk hace esto en una sola pasada:
pdftk hello-draft.pdf output hello.pdf
Analiza sus objetos, recalcula cada desplazamiento de bytes, completa los valores /Length correctos, escribe una tabla xref y un tráiler válidos y emite hello.pdf. Abra eso en cualquier visor y obtendrá una página con "Hello, World!" en Helvetica de 36 puntos cerca de la parte superior. Qpdf hace el mismo trabajo, y muchos visores también repararán un archivo ligeramente malformado sobre la marcha. El objetivo de apoyarse en una herramienta aquí no es pereza; es que la aritmética de desplazamientos es la única parte del formato con cero contenido conceptual y la mayor tasa de errores, por lo que automatizarla permite que la estructura siga siendo lo que usted está aprendiendo
Por qué esto escala a documentos reales
Nada en un informe de cien páginas cambia la forma que acaba de construir. El catálogo todavía se encuentra en la raíz, el árbol de páginas todavía agrupa las páginas, y cada página todavía apunta a sus recursos y a un flujo de contenido. Lo que crece es la amplitud, no la columna vertebral: el árbol de páginas se ramifica para que un lector pueda omitir subárboles enteros, los flujos de contenido llevan cientos de operadores en lugar de cinco, las fuentes se incrustan como sus propios objetos de flujo con tablas de ancho y codificaciones, y las imágenes llegan como flujos con filtros específicos para imágenes. Los archivos modernos también tienden a empaquetar muchos objetos en flujos de objetos comprimidos y a reemplazar la tabla xref simple con un flujo de referencias cruzadas, razón por la cual abrir un PDF real en un editor de texto generalmente muestra un muro de datos binarios. El modelo subyacente es idéntico al de su archivo hecho a mano. Para el gráfico de objetos más amplio y cómo se relacionan el catálogo, el árbol de páginas y los diccionarios de recursos en un documento más grande, el recorrido detallado por la estructura del documento PDF retoma donde esto termina, y la descripción general de la estructura del archivo cubre las actualizaciones incrementales y cómo el tráiler se encadena a través de las revisiones
De escribir a mano a una biblioteca
Escribir objetos a mano es un ejercicio de aprendizaje, no una técnica de producción. En el instante en que necesite fuentes reales, texto ajustado, imágenes o más que una página trivial, la contabilidad de bytes que pdftk parcheó para usted se convierte en todo el trabajo, y usted deseará una biblioteca que se encargue de ello. Aún se escriben los mismos cinco objetos, pero una biblioteca calcula cada desplazamiento, gestiona los diccionarios de fuentes y recursos, y comprime los flujos de contenido sin que usted rastree un solo byte. En Delphi y C++Builder, HotPDF Component reduce este archivo completo a un puñado de llamadas: configure el documento, llame a BeginDoc, SetFont y TextOut para colocar el mismo saludo, y luego a EndDoc para escribir un catálogo, árbol de páginas, xref y tráiler correctos. Comprender los objetos subyacentes es lo que le permite razonar sobre el resultado cuando un documento no se renderiza como esperaba