Un PDF accesible descansa sobre una estructura que la página visible nunca muestra: el árbol de estructura definido en ISO 32000-1 §14.7. Es una jerarquía lógica de encabezados, párrafos, tablas y figuras, superpuesta sobre el contenido pintado y mapeada a roles estándar a través de un mapa de roles. Un lector de pantalla lee ese árbol, no las marcas en la página. Sin él, una factura generada que luce impecable es semánticamente vacía, porque el flujo de contenido registra orden de dibujo y nada más. El total se puede anunciar antes de que las líneas, el pie puede cortar un párrafo, y la tabla de líneas puede colapsar en una corrida indiferenciada de palabras. El costo de prevenir eso es desigual a su favor. Emitir estructura mientras dibuja son minutos de código; retroajarla en documentos terminados es un proyecto de remediación. losLab PDF Library (PDF Library for Delphi) expone el árbol a Delphi y C++Builder a través de un conjunto pequeño de llamadas que envuelven cada operación de dibujo en su rol lógico
Cómo el contenido marcado se liga al árbol de estructura
Dos capas cooperan. En el flujo de contenido, las operaciones de dibujo se enmarcan en secuencias de contenido marcado, cada una cargando un MCID entero. En el catálogo del documento, el árbol de estructura mapea esos MCID 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 cada uno debe resolver a un rol estándar a través del mapa de roles (ISO 32000-1 §14.8.4). El contenido que no carga significado en absoluto, como reglas, fondos y mobiliario de página repetido, se marca como artefacto para que la tecnología asistiva lo salte en lugar de leerlo a mitad de frase
PDF Library for Delphi mantiene ambas capas detrás de un par de llaves. BeginTag abre un elemento de estructura e inicia la secuencia de contenido marcado, las llamadas de dibujo caen dentro, y EndTag cierra ambos. La contabilidad que tropieza al etiquetado hecho a mano — los MCID y el árbol padre y las referencias de página — ocurre internamente donde no puede equivocarla
Dos interruptores a nivel de documento enmarcan el trabajo antes de que se abra alguna etiqueta. SetMarkInfo escribe la bandera de catálogo que declara el documento etiquetado, e IsTaggedPDF la lee de vuelta, que es el primer sondeo barato al decidir si un archivo entrante tiene estructura que valga la pena preservar. El idioma tiene dos puntos de entrada. SetDocumentLanguage fija el predeterminado del documento por sí solo, mientras que SetPDFUAMode lo fija como parte de habilitar la salida PDF/UA completa. Un archivo puede ser etiquetado útilmente sin reclamar conformidad PDF/UA, y un despliegue por fases a menudo empieza justo ahí
Etiquetado al dibujar, no después
El patrón de generación que funciona es tratar la llave de etiqueta como parte de la firma de cada llamada de dibujo, nunca como un pase posterior:
var
Lib: TPDFlib;
begin
Lib := TPDFlib.Create;
try
Lib.SetOrigin(1); // origen en la esquina superior izquierda
Lib.SetPDFUAMode('en-US'); // eleva la versión de guardado a PDF 1.7
Lib.SetInformation(1, 'Service Manual'); // /Title es obligatorio para PDF/UA
Lib.AddRoleMap('ManualTitle', 'H1'); // tipo personalizado -> rol estándar
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'); // decoración de página: excluida de la lectura
// ... dibuja líneas y tinte de fondo ...
Lib.EndArtifact;
Lib.SaveToFile('manual.pdf');
finally
Lib.Free;
end;
end;
Tres llamadas en esa secuencia cargan peso de cumplimiento. SetPDFUAMode habilita la salida PDF/UA y silenciosamente eleva la versión del documento a PDF 1.7, lo que colisiona con el fijado de versión. Un documento bloqueado a PDF 1.4 con LockSaveVersion se rehúsa a guardar y devuelve el código de error 602 una vez que el modo UA está activo, un choque que tiende a salir a la luz cuando los perfiles de archivo y los requisitos de accesibilidad los configuran equipos distintos. SetInformation(1, ...) escribe el título del documento, que ISO 14289 espera que los visores muestren en lugar del nombre del archivo; su ausencia es uno de los hallazgos PDF/UA más comunes en el medio silvestre. AddRoleMap registra el tipo personalizado ManualTitle como un H1, y saltárselo deja la diagnóstica descrita abajo marcando un rol sin mapear
Los niveles de encabezado merecen una política deliberada, no decisiones ad-hoc tomadas por el aspecto de una página. Los usuarios de lector de pantalla saltan entre secciones por atajo de encabezado, así que una plantilla que va de H1 a H3 porque el nivel intermedio se veía demasiado grande en el diseño visual rompe silenciosamente esa navegación, y ninguna revisión visual nunca la atrapará. Es exactamente el defecto que la diagnóstica HEADING-LEVEL-SKIP existe para nombrar. Mapee los estilos visuales de cada plantilla a una escalera de encabezados fija una vez, en un solo lugar, y la deriva nunca empieza
Tablas que un lector de pantalla en realidad puede navegar
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 ligan a encabezados en diseños irregulares. Las llamadas de atributo de elemento de estructura manejan los tres:
Lib.BeginTag('Table', '', '');
Lib.BeginTag('TR', '', '');
Lib.BeginTagEx2('TH', '', '', '', '', 'col-part', '');
Lib.SetStructElemScope('Column'); // válido solo mientras este TH está abierto
Lib.DrawText(72, 120, 'Part');
Lib.EndTag;
Lib.BeginTagEx2('TH', '', '', '', '', 'col-torque', '');
Lib.SetStructElemScope('Column');
Lib.SetStructElemColSpan(2); // el encabezado abarca las columnas de valor y unidad
Lib.DrawText(200, 120, 'Tightening torque');
Lib.EndTag;
Lib.EndTag;
Lib.BeginTag('TR', '', '');
Lib.BeginTag('TD', '', '');
Lib.SetStructElemHeaders('col-part'); // vinculación explícita para tablas irregulares
Lib.DrawText(72, 140, 'M8 flange bolt');
Lib.EndTag;
Lib.EndTag;
Lib.EndTag; // Tabla
La regla de orden es estricta y se hace cumplir en silencio. Cada llamada SetStructElem* aplica a la etiqueta que está abierta en ese momento, entre su BeginTag y su EndTag, y devuelve 0 sin levantar nada cuando ninguna etiqueta está abierta o el atributo no aplica a la actual. Una llamada mal colocada simplemente se desvanece. Envolver los valores de retorno en aserciones durante el desarrollo atrapa la deriva mientras todavía puede verla; dejada sola, una visibilidad faltante sale a la luz solo cuando una auditoría de accesibilidad corre un lector de pantalla real a lo largo de la tabla. Los ID de elemento que se pasan por BeginTagEx2 alimentan el árbol de ID (ISO 32000-1 §14.7.4), y eso es lo que hace que la ligadura SetStructElemHeaders sea resoluble en primer lugar
La misma familia de atributos cubre el resto de lo que la tecnología asistiva necesita. SetStructElemListNumbering declara cómo se etiquetan los ítems de lista, así un lector de pantalla anuncia la posición dentro de la lista en lugar de recitar glifos de viñeta. SetStructElemBBox registra la caja delimitadora de figuras y tablas, que las vistas de reflujo usan para colocar contenido. SetStructElemActualText suministra texto de reemplazo para corridas cuyos glifos no se mapean a caracteres legibles, como una capital inicial ensamblada a partir de arte vectorial. Cada uno sigue la misma regla: se liga a la etiqueta abierta, o se desvanece
Artefactos, idioma y la compuerta diagnóstica de pre-guardado
El mobiliario de página repetido, es decir encabezados corrientes, marcas de pliegue, marcas de agua y tintes de fondo, pertenece dentro de llaves BeginArtifact y EndArtifact para que nunca entre al flujo de lectura. El idioma es heredable. El predeterminado del documento viene del argumento SetPDFUAMode, y una corrida en otro idioma lo sobreescribe por elemento a través de BeginTagEx o SetStructElemLang. Eso es lo que mantiene pronunciable una cita en francés dentro de un manual en inglés
Antes de guardar, GetPDFUADiagnostics corre los chequeos estructurales de la biblioteca sobre el documento en memoria y devuelve hallazgos como texto, donde una cadena vacía significa que no se encontró nada. Los códigos nombran los errores clásicos de autoría directamente: FIGURE-NO-ALT para una imagen sin texto alternativo, HEADING-LEVEL-SKIP para un H3 siguiendo a un H1, ROLEMAP-UNMAPPED para un tipo personalizado que nunca se registró. Cablee esto en el build (genere el conjunto de documentos, falle el paso con diagnóstica no vacía) y las regresiones de accesibilidad se convierten en fallas estilo-tiempo-de-compilación en lugar de hallazgos de auditoría meses después. El veredicto de conformidad completo todavía pertenece al preflight sobre el archivo guardado, cubierto en preflight de PDF/A y PDF/UA en Delphi, porque algunas normalizaciones se aplican solo durante la serialización
La navegación de anotaciones tiene su propia perilla. 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 a nivel de página que los visores honran, con GetTabOrderMode disponible para auditar archivos entrantes. Es el tipo de requisito que nadie nota hasta que un usuario solo de teclado reporta el error, y cuesta una llamada por documento hacerlo bien
Los árboles de estructura no sobreviven toda fusión
Los documentos etiquetados permanecen etiquetados solo cuando cada paso de procesamiento posterior preserva el árbol, y el borde afilado dentro de PDF Library for Delphi es la familia de lista de fusión. MergeFileListFast intercambia preservación de árbol de estructura por velocidad. Ese es el trato correcto para lotes de imágenes escaneadas y el equivocado para reportes etiquetados, porque la salida abre bien, se renderiza idéntica, y ha perdido silenciosamente su capa de accesibilidad. Use el MergeFileList predeterminado o la variante estricta siempre que cualquier entrada sea un PDF etiquetado, y haga de IsTaggedPDF parte de las aserciones post-ensamblaje para que un lote aplanado no pueda enviarse sin que alguien lo note. Los pipelines de ensamblaje para conjuntos grandes de documentos cargan más compromisos de este tipo, explorados en fusión, división y acceso directo de PDF grandes
El lazo de verificación se cierra fuera de la biblioteca: abra la salida en Acrobat, inspeccione el panel de etiquetas, y lea al menos un documento por familia de plantillas con un lector de pantalla real. La diagnóstica atrapa errores estructurales; solo un oído humano atrapa un orden de lectura que es 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 para Delphi