Pasen un lector de pantalla por una factura generada típica y escuchen lo que encuentra: el total general anunciado antes de los renglones, el pie de página interrumpiendo un párrafo, la tabla de conceptos aplastada en un flujo indiferenciado de palabras. La página se ve perfecta y está semánticamente vacía, porque el content stream registra orden de dibujo, no significado. La respuesta de PDF es el árbol de estructura definido en ISO 32000-1 §14.7 — una jerarquía lógica de encabezados, párrafos, tablas y figuras superpuesta al contenido pintado — y la economía es desigual: emitir estructura mientras se genera cuesta minutos de código, mientras que injertarla en documentos terminados es un proyecto de remediación. losLab PDF Library (PDFlibPas) expone esta maquinaria a aplicaciones Delphi y C++Builder mediante un conjunto pequeño de llamadas que envuelven cada operación de dibujo en su rol lógico.
Cómo el contenido marcado se vincula con el árbol de estructura
Dos capas cooperan. En el content stream, las operaciones de dibujo se agrupan en secuencias de marked content, cada una con un MCID entero. En el catálogo del documento, el árbol de estructura mapea esos MCIDs a una jerarquía de elementos tipados — H1, P, Table, Figure — con atributos como texto alternativo e idioma. Los tipos de elemento personalizados son legales, pero deben resolver a roles estándar mediante el role map (ISO 32000-1 §14.8.4), y el contenido sin significado — líneas, fondos, mobiliario de página repetido — se marca como artefacto para que la tecnología de asistencia lo salte en lugar de leerlo a mitad de una oración.
PDFlibPas mantiene ambas capas detrás de un par de brackets: BeginTag abre un elemento de estructura e inicia la secuencia de marked content, las llamadas de dibujo caen dentro, y EndTag cierra ambas. La contabilidad — MCIDs, parent tree, referencias de página — ocurre internamente, lo que elimina la parte más propensa a errores de un etiquetado hecho a mano.
Dos switches de nivel documento enmarcan el trabajo antes de abrir cualquier etiqueta. SetMarkInfo escribe la bandera de catálogo que declara el documento etiquetado, e IsTaggedPDF la lee de vuelta — la primera sonda barata al decidir si un archivo entrante tiene estructura que valga preservar. Para idioma, SetDocumentLanguage define por sí solo el valor predeterminado del documento, mientras que SetPDFUAMode lo establece como parte de habilitar salida PDF/UA completa; un archivo puede estar útilmente etiquetado sin declarar conformidad PDF/UA, y una adopción por fases a menudo empieza exactamente allí.
Etiquetar mientras se dibuja, no después
El patrón de generación que funciona es tratar el bracket de etiqueta como parte de la firma de cada llamada de dibujo, nunca como una pasada posterior:
var
Lib: TPDFlib;
begin
Lib := TPDFlib.Create;
try
Lib.SetOrigin(1); // top-left origin
Lib.SetPDFUAMode('en-US'); // bumps the save version to PDF 1.7
Lib.SetInformation(1, 'Service Manual'); // /Title is mandatory for PDF/UA
Lib.AddRoleMap('ManualTitle', 'H1'); // custom type -> standard role
Lib.AddStandardFont(4);
Lib.SetTextSize(18);
Lib.BeginTagEx2('ManualTitle', '', '', 'en-US', '', 'h1-cover', '');
Lib.DrawText(72, 96, 'Service Manual');
Lib.EndTag;
Lib.BeginTag('Figure', 'Exploded view of the gearbox assembly', '');
Lib.AddImageFromFile('gearbox.png', 0);
Lib.EndTag;
Lib.BeginArtifact('Layout'); // page decoration: excluded from reading
// ... draw rules and background tint ...
Lib.EndArtifact;
Lib.SaveToFile('manual.pdf');
finally
Lib.Free;
end;
end;
Tres llamadas en esa secuencia tienen peso de cumplimiento. SetPDFUAMode habilita salida PDF/UA y eleva silenciosamente la versión del documento a PDF 1.7 — lo que choca con el version pinning: un documento bloqueado a PDF 1.4 con LockSaveVersion se niega a guardar con el código de error 602 una vez activo el modo UA, una combinación que aparece cuando perfiles archivísticos y requisitos de accesibilidad son configurados por equipos distintos. SetInformation(1, ...) escribe el título del documento, que ISO 14289 espera que los visores muestren en lugar del nombre de archivo; su ausencia es uno de los hallazgos PDF/UA más comunes en la práctica. Y AddRoleMap registra el tipo personalizado ManualTitle como H1 — si lo omiten, los diagnósticos descritos abajo marcarán el rol sin mapear.
La disciplina de encabezados merece una política deliberada en lugar de niveles improvisados. Los usuarios de lectores de pantalla navegan con atajos por encabezado, así que una plantilla que salta de H1 a H3 porque el nivel intermedio se veía demasiado grande en el diseño visual rompe la navegación de una forma que ninguna revisión visual detecta — y es precisamente el defecto que existe para nombrar el diagnóstico HEADING-LEVEL-SKIP. Mapear una sola vez, en un lugar, los estilos visuales de cada plantilla a una escalera fija de encabezados evita la deriva.
Tablas que un lector de pantalla sí puede navegar
Las líneas de cuadrícula dibujadas no significan nada fuera de pantalla. Lo que los lectores de pantalla navegan son relaciones estructurales: qué celdas son encabezados, qué gobierna cada encabezado y cómo las celdas de datos se vinculan con encabezados en diseños irregulares. Las llamadas de atributo de elemento de estructura manejan las tres:
Lib.BeginTag('Table', '', '');
Lib.BeginTag('TR', '', '');
Lib.BeginTagEx2('TH', '', '', '', '', 'col-part', '');
Lib.SetStructElemScope('Column'); // valid only while this TH is open
Lib.DrawText(72, 120, 'Part');
Lib.EndTag;
Lib.BeginTagEx2('TH', '', '', '', '', 'col-torque', '');
Lib.SetStructElemScope('Column');
Lib.SetStructElemColSpan(2); // header spans the value and unit columns
Lib.DrawText(200, 120, 'Tightening torque');
Lib.EndTag;
Lib.EndTag;
Lib.BeginTag('TR', '', '');
Lib.BeginTag('TD', '', '');
Lib.SetStructElemHeaders('col-part'); // explicit binding for irregular tables
Lib.DrawText(72, 140, 'M8 flange bolt');
Lib.EndTag;
Lib.EndTag;
Lib.EndTag; // Table
La regla de orden es estricta y se aplica en silencio: cada llamada SetStructElem* aplica a la etiqueta actualmente abierta — entre su BeginTag y EndTag — y devuelve 0 sin lanzar nada cuando no hay etiqueta abierta o el atributo no aplica. Una llamada fuera de lugar simplemente desaparece. Envolver los valores de retorno en assertions durante desarrollo captura la deriva temprano; en campo, un alcance perdido aparece solo cuando una auditoría de accesibilidad pasa un lector de pantalla real por la tabla. Los IDs de elemento pasados mediante BeginTagEx2 alimentan el ID tree (ISO 32000-1 §14.7.4), que es lo que vuelve resoluble el vínculo SetStructElemHeaders.
La misma familia de atributos cubre las estructuras restantes de las que depende la tecnología de asistencia. SetStructElemListNumbering declara cómo se etiquetan los elementos de lista, para que un lector de pantalla pueda anunciar la posición dentro de la lista en lugar de recitar glifos de viñeta; SetStructElemBBox registra el bounding box de figuras y tablas, que las vistas reflow usan para colocar contenido; SetStructElemActualText aporta texto de reemplazo para corridas cuyos glifos no corresponden a caracteres legibles, como capitulares armadas con arte vectorial. Cada una sigue la misma regla: la llamada se vincula a la etiqueta abierta, o desaparece.
Artefactos, idioma y la puerta de diagnósticos antes de guardar
El mobiliario repetido de página — encabezados corridos, marcas de doblez, marcas de agua, tintes de fondo — pertenece dentro de brackets BeginArtifact y EndArtifact para que nunca entre al flujo de lectura. El idioma es heredable: el valor predeterminado del documento proviene del argumento de SetPDFUAMode, y las corridas en otro idioma lo sobrescriben por elemento mediante BeginTagEx o SetStructElemLang, que es lo que mantiene pronunciable una cita en francés dentro de un manual en inglés.
Antes de guardar, GetPDFUADiagnostics ejecuta las comprobaciones estructurales de la biblioteca sobre el documento en memoria y devuelve hallazgos como texto — una cadena vacía significa que no se encontró nada. Los diagnósticos nombran directamente los errores clásicos de autoría: FIGURE-NO-ALT para imágenes sin texto alternativo, HEADING-LEVEL-SKIP para un H3 después de un H1, ROLEMAP-UNMAPPED para tipos personalizados nunca registrados. Integrarlo en el build — generar el conjunto de documentos, fallar si los diagnósticos no están vacíos — convierte regresiones de accesibilidad en fallos estilo compilación en lugar de hallazgos de auditoría. El veredicto completo de conformidad sigue perteneciendo al preflight sobre el archivo guardado, cubierto en preflight PDF/A y PDF/UA en Delphi, ya que algunas normalizaciones se aplican durante la serialización.
La navegación por anotaciones tiene su propio control. PDF/UA espera que el recorrido por teclado de campos de formulario y enlaces siga el orden de estructura, y SetTabOrderMode escribe la entrada de orden de tabulación de nivel página que respetan los visores, con GetTabOrderMode disponible para auditar archivos entrantes. Es el tipo de requisito que nadie nota hasta que un usuario que solo usa teclado reporta el bug, y cuesta una llamada por documento hacerlo bien.
Los árboles de estructura no sobreviven a toda fusión
Los documentos etiquetados siguen etiquetados solo si cada paso de procesamiento posterior preserva el árbol. Dentro de PDFlibPas, el borde filoso es la familia merge-list: MergeFileListFast intercambia preservación del árbol de estructura por velocidad, lo que es el intercambio correcto para lotes de imágenes escaneadas y exactamente el incorrecto para reportes etiquetados — la salida abre bien, se renderiza idéntica y ha perdido por completo su capa de accesibilidad. Usen el MergeFileList predeterminado o la variante estricta siempre que alguna entrada esté etiquetada, y hagan que IsTaggedPDF sea parte de las assertions posteriores al ensamblado para que no se entregue un lote aplanado silenciosamente. Las canalizaciones de ensamblado para grandes conjuntos de documentos tienen más intercambios de este tipo, explorados en fusión, división y acceso directo de PDFs grandes.
El ciclo de verificación se cierra fuera de la biblioteca: abran la salida en Acrobat, inspeccionen el panel de etiquetas y lean al menos un documento por familia de plantillas con un lector de pantalla real. Los diagnósticos capturan errores estructurales; solo un oído humano detecta un orden de lectura técnicamente válido y prácticamente desconcertante. Las compilaciones de evaluación y la referencia completa de la API de etiquetado están en la página del producto losLab PDF Library for Delphi.