PDF/A y PDF/UA responden a dos preguntas que no tienen nada que ver entre sí, y tratarlas como una única casilla de accesibilidad y archivo es la forma en que los archivos rotos llegan a un archivo luciendo una etiqueta de conformidad. PDF/A pregunta si un archivo seguirá renderizándose fielmente dentro de veinte años. PDF/UA pregunta si la tecnología de asistencia puede leerlo hoy. Un documento puede superar uno con holgura y suspender el otro, así que el único veredicto honesto viene de ejecutar ambos, y de ejecutarlos antes de escribir el archivo, no después de que un sistema posterior confíe en el identificador de conformidad grabado en sus metadatos. Ese identificador es una autodeclaración. Nada en el formato exige que sea cierto, y una aplicación que escribe "PDF/A-1b" en el XMP sin validar contra la norma produce un archivo que parece conforme a todo consumidor que solo lea la etiqueta. losLab PDF Library (PDF Library for Delphi) cierra esa brecha para Delphi y C++Builder integrando ambos validadores en la biblioteca, de modo que la comprobación se ejecuta en el propio proceso sin ningún servicio externo que levantar
Dos normas que suspenden archivos por razones opuestas
ISO 19005 (PDF/A) es un contrato de reproducción. Un archivo conforme tiene que renderizarse de forma idéntica dentro de décadas en software que nunca vio el sistema que lo produjo, así que las reglas atacan las dependencias externas: todas las fuentes incrustadas, el color anclado a un OutputIntent ICC incrustado o expresado en un espacio independiente del dispositivo, nada de cifrado en PDF/A-1, nada de JavaScript, metadatos XMP que concuerden con el diccionario de información del documento. ISO 14289 (PDF/UA) es en cambio un contrato de semántica. La tecnología de asistencia tiene que recorrer el documento y salir con significado, que vive en una capa totalmente distinta: un árbol de estructura completo, texto alternativo en las figuras, un título de documento configurado para mostrarse, niveles de encabezado que no saltan, relaciones de cabecera de tabla que sobreviven una vez que la página sale de la pantalla
Como las dos normas vigilan capas distintas, los archivos que muerden son los que se sitúan entre ambas. Un documento perfecto para archivo puede ser mudo para un lector de pantalla. Uno primorosamente etiquetado puede referenciar una fuente de escritorio que no existirá dentro de diez años. La publicación en el sector público es el lugar habitual donde ambos requisitos aterrizan a la vez, y un pipeline allí no puede colapsarlos en una sola puerta. Los hallazgos van a personas distintas. Las fuentes no incrustadas son un defecto del código que genera el PDF, mientras que el texto alternativo ausente pertenece a quien sea dueño de las plantillas de contenido, y un informe que mezcla ambos simplemente se reenvía dos veces
Qué parte de PDF/A se elige como objetivo importa tanto como alcanzarla. PDF/A-1 está congelado en PDF 1.4 y rechaza la transparencia y JPEG2000, dos cosas a las que la salida moderna de informes recurre sin pensarlo. PDF/A-2 (ISO 19005-2, construido sobre ISO 32000-1) acepta ambas y es la opción predeterminada sensata para un archivo nuevo. PDF/A-3 va más lejos y permite archivos incrustados de cualquier tipo, que es de lo que dependen los formatos regulados de facturación electrónica. Un equipo que sigue estandarizando en PDF/A-1b en 2026 suele arrastrar un requisito que alguien escribió hace quince años, y renegociar la parte objetivo suele ser más barato que eliminar la transparencia de cada gráfico que emite el sistema
Hallazgos estructurados en el momento de la ingesta
El punto de entrada de la API plana es CheckFileCompliance, con el selector de prueba 1 para PDF/A y 2 para PDF/UA. Devuelve un identificador de lista de cadenas cuyos elementos son hallazgos individuales, uno por línea, que es exactamente la forma que una puerta automatizada quiere recorrer:
function GateArchiveUpload(Pdf: TPDFlib; const FileName: string): Boolean;
var
ListId, I: Integer;
begin
ListId := Pdf.CheckFileCompliance(FileName, '', 1, 0); // 1 = PDF/A
if ListId = 0 then
begin
// 0 significa "sin hallazgos" O "archivo ilegible" -- desambiguar antes de aprobar
Result := Pdf.LastErrorCode = 0;
Exit;
end;
for I := 0 to Pdf.GetStringListCount(ListId) - 1 do
LogFinding(FileName, Pdf.GetStringListItem(ListId, I));
Pdf.ReleaseStringList(ListId);
Result := False;
end;
Dos detalles deciden si esto funciona sin supervisión. El primero es un valor de retorno que significa dos cosas opuestas. CheckFileCompliance devuelve 0 cuando el archivo es totalmente conforme y también cuando el archivo no se pudo abrir en absoluto, porque internamente una lista de resultados vacía colapsa a 0 en ambos casos. Una puerta que lea el 0 como aprobado dejará pasar subidas corruptas directamente al archivo, así que desambigua con LastErrorCode antes de confiar en el cero, como hace la puerta de arriba. El segundo tiene que ver con dónde está el archivo en su ciclo de vida. El comprobador se ejecuta sobre el lector en streaming de la biblioteca en lugar del modelo completo del documento, abriendo el archivo directamente con compartición de lectura y sin llamar nunca a LoadFromFile, y por eso puede masticar entradas de varios gigabytes sin construir un árbol de objetos. Esa misma apertura en streaming falla mientras otro proceso aún retiene el archivo para escritura, y una subida en curso es precisamente ese estado. Abre la puerta después de que termine la transferencia
El diseño en streaming vuelve a rendir bajo carga. Cada comprobación abre su entrada en solo lectura y la comparte para lectura, así que una auditoría de corpus escala horizontalmente entre hilos o procesos worker con una instancia de TPDFlib por worker y sin contención entre ellos. El recurso que necesita disciplina es el propio identificador. Cada resultado distinto de cero de CheckFileCompliance permanece asignado hasta que se llama a ReleaseStringList, y una puerta de larga duración que olvide liberarlos no se cae, simplemente sangra memoria lentamente hasta que alguien va a buscar por qué
Informes para humanos, diffs para puertas de compilación
Una lista de hallazgos es la forma correcta para una puerta y la forma incorrecta para un correo al equipo de plantillas. CreatePreflightReport renderiza el mismo análisis como prosa legible, CreatePreflightReportEx añade un selector de formato de informe, y SavePreflightReport lo escribe en disco para que el informe pueda viajar dentro del paquete de documentos entregado. Muchos contratos de archivo hacen de ese informe un entregable por derecho propio, no solo un artefacto interno
El miembro de esta familia que se gana su sitio con discreción es ComparePreflightReports. La conformidad es una superficie de regresión como cualquier otro comportamiento. Un retoque de plantilla, una fuente corporativa recién licenciada o una actualización de la biblioteca pueden introducir cada uno un hallazgo que no estaba en la release anterior, y ninguno se anuncia. Mantén informes de referencia para un conjunto de documentos representativos bajo control de versiones, regenéralos tras cada cambio y ejecuta ComparePreflightReports para calcular la diferencia. Un diff vacío es un artefacto de release que merece conservarse. Un hallazgo inesperado hace fallar la compilación, que es un lugar mucho más barato para descubrirlo que la auditoría
Generar salida que pase a la primera
El preflight se gana el sueldo con los archivos que llegan de fuera. Para los documentos que produce tu propio código, encontrar infracciones después de la generación y parchearlas de vuelta es el camino lento. PDF Library for Delphi lleva un modo del lado de generación para cada norma, y puedes activar ambos para el mismo documento:
var
Pdf: TPDFlib;
Diag: WideString;
begin
Pdf := TPDFlib.Create;
try
Pdf.NewDocument;
Pdf.SetPDFAMode(1);
Pdf.LoadOutputIntentProfile('sRGB-IEC61966-2.1.icc', 'RGB');
Pdf.SetPDFUAMode('en-US');
Pdf.SetInformation(1, 'Quarterly Statement'); // /Title: obligatorio para PDF/UA
// ... dibujar aquí el contenido etiquetado ...
Diag := Pdf.GetPDFUADiagnostics;
if Diag <> '' then
Writeln('fix before shipping: ', Diag);
Pdf.SaveToFile('statement.pdf');
// el preflight que cuenta se ejecuta sobre el archivo guardado:
Writeln(Pdf.CreatePreflightReport('statement.pdf', '', 1, 0));
finally
Pdf.Free;
end;
end;
La trampa se esconde en el momento de guardar. Varias de las reparaciones de conformidad ocurren mientras el documento se serializa y no cuando se activa el modo: forzar el indicador de impresión en las anotaciones, escribir el AFRelationship predeterminado para los archivos incrustados de PDF/A-3, normalizar el orden de tabulación y las descripciones de los campos de formulario para PDF/UA. El documento que está en memoria no es idéntico byte a byte al que aterriza en disco, así que el único veredicto de preflight que significa algo es el calculado a partir del archivo guardado. Valida el propio statement.pdf. No infieras la conformidad del objeto que sigue en memoria, porque los bytes que estarías juzgando no son los bytes que entregaste
Los escenarios de factura que llevan XML legible por máquina junto al documento visual siguen el patrón ZUGFeRD y Factur-X, que se construye sobre PDF/A-3. Esos deberían establecer explícitamente la relación del adjunto con SetPDFA3DefaultAFRelationship, ya que ISO 19005-3 exige que cada archivo incrustado declare su papel respecto al documento. Si se deja sin establecer, el XML incrustado es solo un blob sin propósito declarado, algo que el validador nota
Árbitros independientes: veraPDF y Acrobat
Un productor no debería ser el único juez de su propia salida. Los comprobadores de PDF Library for Delphi dan veredictos rápidos y estructurados en el propio proceso, que es lo que se quiere en la ruta crítica, pero la puerta de release de un lote de archivo debería seguir pasando la salida por un validador que nadie de tu equipo escribió. veraPDF es la implementación de referencia mantenida por la comunidad para PDF/A y la herramienta que la mayoría de los archivos nombran en sus criterios de aceptación, así que es la que hay que igualar. Los perfiles de preflight de Acrobat sirven de desempate útil cuando veraPDF y la comprobación en proceso discrepan. Registra el nombre del validador y su versión junto a cada informe almacenado. Afirmar que un archivo pasó veraPDF dice muy poco sin el número de compilación que lo aprobó, ya que la herramienta endurece sus reglas entre releases
Los validadores sí discrepan en los bordes de las normas, y cuando lo hacen la respuesta no es elegir la herramienta que te guste. Reduce el archivo a una muestra mínima que siga provocando la discrepancia y léela contra el texto de la norma. Una hora de eso suele sacar a la luz una de dos cosas: un error genuino de la herramienta que merece notificarse a sus autores, o una cláusula que tu equipo ha estado leyendo mal y que debería anotar en las notas de conformidad para que la siguiente persona no vuelva a litigarla
La entrada cifrada tiene un atajo. Ambos comprobadores aceptan un argumento de contraseña, pero un archivo PDF/A-1 con un diccionario de cifrado ya es no conforme, porque ISO 19005-1 prohíbe el cifrado sin excepciones, así que una entrega cifrada puede rechazarse antes de que se ejecute cualquier análisis más profundo. Averiguar qué concede realmente un diccionario de cifrado es una tarea aparte, cubierta en auditoría de cifrado y permisos de PDF
Los hallazgos de PDF/UA casi siempre se remontan a cómo se creó el árbol de estructura en primer lugar, y las técnicas de etiquetado que hay detrás viven en construir árboles de estructura de PDF etiquetado en Delphi. Los archivos que también exigen firmas digitales deberían emparejar esta puerta con el flujo de trabajo de firma y validación PAdES. La referencia completa de la API de preflight está en la página del producto losLab PDF Library for Delphi