Technical Article

Explicación de los metadatos, marcadores y anotaciones PDF

Si despoja a un documento de las descripciones de las páginas, le quedará una fina capa de estructura que nadie imprime pero de la que dependen todo lector, indexador y sistema de archivo. Un objeto de página no sabe nada acerca del capítulo al que pertenece, del autor que lo escribió o de la nota al pie que enlaza a otra parte. Ese conocimiento vive un nivel más arriba, en tres estructuras adjuntas al catálogo (catalog) del documento: los flujos de metadatos, el árbol de marcadores y las matrices de anotaciones por página. Comparten un rasgo que hace que sea fácil equivocarse con ellos. Ninguno de ellos conlleva marcas visibles en la página, por lo que un archivo puede renderizarse perfectamente y aun así carecer de sus marcadores, contradecir su propio campo de autor o apuntar un enlace a un objeto de página que ya no existe

Ésta es la capa que una biblioteca de PDF expone como propiedades de documento, API de marcadores y llamadas a enlaces o anotaciones, y la capa que lee un rastreador de búsqueda para decidir de qué trata su documento. El modelo de objetos subyacente se cubre en el recorrido por la estructura del documento PDF. Aquí la atención se centra estrictamente en lo que cuelga del catálogo

Las tres estructuras se adjuntan en el catálogo. Un catálogo completo que las une tiene el siguiente aspecto:

1 0 obj
<< /Type /Catalog
   /Pages 2 0 R
   /Outlines 3 0 R
   /Names << /EmbeddedFiles 4 0 R >>
   /Metadata 5 0 R
>>
endobj

Cuatro entradas, cuatro subsistemas independientes. /Pages es el documento visible; /Outlines es el árbol de marcadores; /Metadata apunta al flujo XMP; /Names llega al diccionario de nombres de todo el documento, que entre otras cosas alberga a los archivos adjuntos integrados. Cada uno de ellos es opcional, y un lector que no encuentre ninguno de ellos aún mostrará las páginas. Esa opcionalidad es exactamente el motivo por el cual la capa de navegación es la primera en corromperse cuando el archivo es editado mediante herramientas que sólo entienden de páginas

Dos almacenes de metadatos en desacuerdo

El formato PDF aloja los metadatos de los documentos en dos lugares a la vez, y el problema comienza cuando éstos dicen cosas diferentes. El mecanismo original es el diccionario de información (Info dictionary) del documento, al que se hace referencia a través de /Info en el tráiler: un conjunto plano de pares clave-valor para /Title, /Author, /Subject, /Keywords, /Creator, /Producer y las dos fechas. Es simple y todos los visores lo leen. PDF 2.0 deja obsoleta a la mayor parte de este mecanismo en favor de un segundo: el flujo de metadatos XMP

XMP es un documento XML independiente, escrito en RDF, almacenado en forma de flujo al que el catálogo llega a través de /Metadata y que está marcado como /Type /Metadata /Subtype /XML. A diferencia del diccionario Info, que está enterrado dentro de la estructura de objetos del PDF, un paquete XMP está diseñado para ser extraído y analizado sintácticamente por sí solo a través de herramientas que no saben nada sobre PDF. A continuación se muestra un paquete representativo:

5 0 obj
<< /Type /Metadata /Subtype /XML /Length 1235 >>
stream
<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>
<x:xmpmeta xmlns:x="adobe:ns:meta/">
  <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
    <rdf:Description rdf:about=""
        xmlns:dc="http://purl.org/dc/elements/1.1/"
        xmlns:xmp="http://ns.adobe.com/xap/1.0/"
        xmlns:pdf="http://ns.adobe.com/pdf/1.3/">
      <dc:title><rdf:Alt><rdf:li xml:lang="x-default">Quarterly Report</rdf:li></rdf:Alt></dc:title>
      <dc:creator><rdf:Seq><rdf:li>A. Author</rdf:li></rdf:Seq></dc:creator>
      <xmp:CreateDate>2026-06-16T10:46:27+08:00</xmp:CreateDate>
      <xmp:CreatorTool>Reporting Service 4.2</xmp:CreatorTool>
      <pdf:Producer>losLab PDF Library</pdf:Producer>
    </rdf:Description>
  </rdf:RDF>
</x:xmpmeta>
<?xpacket end="w"?>
endstream
endobj

Tres detalles de ese bloque deciden si los metadatos sobreviven al contacto con las herramientas reales. Las instrucciones de procesamiento de xpacket no son una decoración: encuadran el paquete para que un extractor pueda encontrarlo dentro de un flujo de bytes más grande, y un escritor que omita el cierre <?xpacket end="w"?> producirá un archivo que se abrirá bien, pero hará tropezar a los validadores estrictos. Los tipos de datos de las propiedades también importan. dc:title es una alternativa de idioma envuelta en rdf:Alt, mientras que dc:creator es una lista ordenada y adopta la forma rdf:Seq; emitir cualquiera de ellos como un nodo de texto sin formato constituye el error XMP más común, tolerado por la mayoría de los visores hasta llegar al que no lo hace. Los prefijos de espacio de nombres son convencionales, pero los URI a los que se vinculan son normativos: un analizador sintáctico se basa en el URI, no en el prefijo

La regla estricta al usar dos almacenes es que deben estar de acuerdo. Si /Info dice que el autor es una persona y dc:creator nombra a otra, usted ha distribuido un documento que responde a la misma pregunta de dos maneras y cuál respuesta gane dependerá de qué campo lea la herramienta que se esté utilizando. Por lo general, una biblioteca escribe ambos campos, pero en el momento en que usted edita uno a mano o fusiona archivos de distintos generadores, ambos divergen. Trate al diccionario Info como compatibilidad heredada y a XMP como la fuente de la verdad, y regenere ambos a partir de un único conjunto de valores en lugar de parchearlos de forma independiente. Para PDF/A, esto se convierte en un requisito de conformidad: ISO 19005 exige el uso de XMP y prohíbe cualquier propiedad Info que contradiga a su homólogo XMP

El árbol de esquemas detrás del panel de marcadores

Lo que un visor muestra como un panel de marcadores (bookmarks panel) es, en el archivo, un árbol doblemente enlazado de diccionarios llamado esquema del documento (document outline). El catálogo apunta a un diccionario de esquemas raíz a través de /Outlines; la raíz apunta a sus primeros y últimos elementos de nivel superior; y cada elemento está entrelazado con sus vecinos y con su superior. No existe ninguna matriz de marcadores en ninguna parte. Toda la estructura se reconstruye siguiendo las referencias, que es precisamente la razón por la que un solo enlace roto puede hacer que una rama entera desaparezca del panel sin que se produzca ningún error

8 0 obj                                    % the outline root
<< /Type /Outlines /Count 4 /First 9 0 R /Last 9 0 R >>
endobj
9 0 obj                                    % top-level: a chapter
<< /Title (Chapter 1: Results)
   /Parent 8 0 R /Count 2
   /First 12 0 R /Last 15 0 R >>
endobj
12 0 obj                                   % first child
<< /Title (Introduction)
   /Parent 9 0 R /Next 15 0 R
   /Dest [3 0 R /XYZ 72 720 0] >>
endobj
15 0 obj                                   % second child, last sibling
<< /Title (Methodology)
   /Parent 9 0 R /Prev 12 0 R
   /Dest [3 0 R /Fit] >>
endobj

Lea los enlaces y las invariantes se vuelven obvias. Cada elemento apunta a su correspondiente /Parent (superior). Los elementos emparentados (hermanos) forman una cadena a través de /Prev (anterior) y /Next (siguiente); el primer elemento omite a /Prev y el último omite a /Next. Un superior nombra a su primer y último hijo (descendiente) a través de /First y /Last, y a los hijos que se encuentren entre ellos sólo se puede acceder recorriendo la cadena de emparentados (hermanos). Equivoque un elemento y la falla será silenciosa: un /Next obsoleto trunca un capítulo, un superior cuyo /Last no finalice la cadena dejará a sus hijos huérfanos y el visor renderizará todo lo que pueda alcanzar

El campo /Count contiene información de estado que sorprende a la gente. En la raíz y en cualquier elemento expandido, contiene la cantidad de hijos (descendientes) actualmente visibles; en un elemento contraído, es un número negativo cuya magnitud indica cuántos hijos aparecerían al expandirse. De modo que /Count no es un dato estructural fijo sobre el árbol, es el estado guardado, abierto o cerrado, del panel, y un generador que lo codifica de forma rígida como un total positivo reabre cada rama que el autor pretendía dejar cerrada

Cada elemento se gana su lugar al apuntar a algún lado. El /Title es lo que muestra el panel; el /Dest (destino) es donde un clic aterriza. Un destino puede estar en línea en el elemento, como el mostrado arriba, o ser un nombre que se resuelva a través del diccionario de nombres del documento, lo cual es la mejor opción cuando muchos marcadores y enlaces apuntan a los mismos lugares, porque usted arregla un objetivo desplazado en un solo lugar. Una biblioteca por lo general oculta este árbol detrás de un identificador de raíz de esquema (outline-root) y los métodos que agregan entradas hijas; en HotPDF el documento expone un OutlineRoot del tipo THPDFDocOutlineObject y encadena los enlaces /Prev, /Next, /Parent y /Count por usted a medida que agrega los elementos. Vale la pena aprovechar esto, debido a que el mantenimiento manual de esas invariantes a través de las ediciones es donde se rompen los esquemas

Destinos: la gramática de adónde va un clic

Tanto los marcadores como las anotaciones de enlaces apuntan a destinos, y un destino es algo más que un número de página. Se trata de una matriz que da nombre a un objeto de página y luego especifica, a través de un verbo que se encuentra en la segunda ranura, de qué modo el visor debería encuadrarla. El más común y el más abusado es /XYZ, que tiene la forma [página /XYZ izquierda superior ampliación]. Sus tres operandos son independientes y cualquiera puede ser null para significar "deje esto tal como lo tenía el lector". Por consiguiente, [página /XYZ null null null] salta a la página sin tocar la posición de desplazamiento ni la ampliación, que es lo que usualmente se desea de un enlace de "ir a la página". Los números se encuentran en el espacio de usuario predeterminado, se miden desde la parte inferior izquierda con el eje 'y' incrementándose hacia arriba, que es el mismo sistema de coordenadas que utiliza el contenido de la página. Los autores que provienen del diseño de pantallas miden por reflejo desde la parte superior y envían al lector al extremo equivocado de la página

La familia /Fit intercambia el posicionamiento preciso por la resiliencia. [página /Fit] escala toda la página en la ventana, [página /FitH superior] ajusta el ancho de la página con un borde superior dado, y [página /FitR i a d a] amplía un rectángulo para que llene la vista. Dado que estos calculan la escala a partir de la geometría de la página en lugar de usar coordenadas fijas, un destino /Fit aún hace lo sensato después de que se cambie el tamaño de la página, mientras que un destino /XYZ con una ampliación incorporada puede dejar al lector mirando al margen. Para una tabla de contenido, /FitH con la coordenada superior de la sección envejece mejor que /XYZ con una ampliación adivinada

Anotaciones: todo aquello interactivo que no es contenido de página

Una anotación es un objeto que se superpone a la página sin ser parte de su flujo de contenido. Los enlaces, las notas adhesivas, los resaltados, los widgets de formulario, los íconos de archivos adjuntos y los sellos: todos son anotaciones y se enumeran en la matriz /Annots de la página en la que se ubican. Eliminar una anotación de esa matriz la elimina de la página, incluso si el contenido subyacente permanece intacto. Ése es precisamente el punto: las anotaciones son una capa de edición, separada de las marcas sobre las que se ubican

Todas las anotaciones comparten una pequeña columna vertebral. /Subtype da nombre a la clase, /Rect da a su cuadro delimitador en coordenadas de página y /Contents aloja al texto que también sirve como descripción accesible. La anotación de enlace (link) es el caso que vale la pena estudiar, porque se presenta en dos formas: un destino puro y una acción

12 0 obj                                    % link to a destination
<< /Type /Annot /Subtype /Link
   /Rect [100 200 300 250]
   /Border [0 0 0]
   /Dest [5 0 R /XYZ null null null] >>
endobj
13 0 obj                                    % link that runs an action
<< /Type /Annot /Subtype /Link
   /Rect [50 50 200 100]
   /Border [0 0 0]
   /A << /Type /Action /S /URI /URI (https://www.example.com) >> >>
endobj

El /Rect es un punto de acceso (hotspot); hacer clic dentro de él envía al lector al destino, reutilizando la misma gramática que usa el esquema. El /Border [0 0 0] está haciendo un trabajo real: suprime el feo rectángulo predeterminado que los visores dibujan alrededor de los enlaces. La segunda forma cambia al /Dest puro por una acción /A, cuyo subtipo /S selecciona el comportamiento: /GoTo dentro de este archivo, /GoToR para otro archivo, /URI para una dirección web y /Launch para ejecutar un programa externo. Éste último comportamiento merece sospecha. Un /Launch que inicie un ejecutable es el comportamiento que hace a los PDF un vector de malware, por lo que los visores conformes lo bloquean o advierten de ello en voz alta y el enlace falla para la mayoría de los lectores. Vaya por /URI y /GoTo y deje a /Launch en paz

Las anotaciones de marcado tales como los resaltados y las notas adhesivas, y las anotaciones de forma tales como /Square, añaden un inconveniente: su aspecto en la pantalla no está implícito en su tipo. Un visor representa su propia versión a menos que usted le fije la apariencia con un flujo de apariencia, la entrada /AP, que hace referencia a un objeto XObject de formulario que alberga a los operadores de dibujo. Si lo omite, el mismo resaltado puede verse diferente en dos lectores distintos, o antes y después de un ciclo de edición. Para cualquier elemento cuyo aspecto exacto sea parte del documento, suministre el /AP. De paso, los archivos adjuntos reutilizan esta misma maquinaria: un flujo de archivo integrado y un diccionario de especificación de archivos, que se presentan como una anotación /FileAttachment o a través del árbol de nombres /EmbeddedFiles debajo del /Names del catálogo

Dónde se rompe esta capa y cómo detectarlo

El error recurrente en todo esto es la referencia colgante. Los marcadores dejan de aparecer cuando el catálogo no tiene ninguna entrada /Outlines o una cadena de elementos hermanos se rompe a mitad del árbol; los metadatos se ignoran cuando el flujo XMP carece de su marca /Type /Metadata /Subtype /XML o la envoltura xpacket está mal formada. En todos los casos, el contenido de la página está bien, por lo que una apertura casual parece correcta y el defecto aparece sólo en el panel que nadie revisó

Dos hábitos baratos detectan a la mayor parte de este problema. Abra el archivo terminado en un visor real y haga clic en el panel de marcadores y en una muestra de los enlaces, lo que ejercita al gráfico de referencias del mismo modo que lo hará un lector. Luego, vuelva a leer los metadatos con una herramienta distinta y confirme que el diccionario Info y el XMP coincidan, que es el único desacuerdo que no revelará ninguna cantidad de clics. Genere esta capa a través de una biblioteca que sea dueña del mantenimiento de los registros de los enlaces y la mayoría de estas trampas no se abrirán jamás. El Componente HotPDF para Delphi y C++Builder expone el esquema, las anotaciones y las estructuras de metadatos a través de las API a nivel de documento, por lo que usted describe la jerarquía de los marcadores y los enlaces y la deja que entrelace las referencias. Con relación al modelo de objetos al que se unen estas estructuras, el resumen técnico de la estructura del archivo PDF cubre el catálogo y la tabla de referencias cruzadas de las que dependen