O PDFlibPas valida ficheiros PDF/UA-2 (ISO 14289-2) em Delphi através do TPDFlib.CheckFilePDFUACompliance com uma parte alvo de 2, o que executa um perfil PDF/UA-2 separado: resolve os papéis da estrutura através dos namespaces do PDF 2.0 e do /RoleMapNS, reporta entrada truncada ou ilegível como falha e devolve um relatório de cobertura que nunca reivindica certificação completa
Esta última parte importa mais do que parece. A validação de acessibilidade é uma área onde um visto verde é copiado para uma folha de cálculo de compras e nunca mais é posto em causa. Um validador que salta em silêncio metade da norma e ainda imprime "pass" é pior do que nenhum validador, porque transforma um desconhecido num conhecido falso. O PDFlibPas toma a posição oposta: verificar o que pode ser verificado mecanicamente, dizer em voz alta o que não foi verificado e tratar cada "não consegui terminar" como um achado e não como um sucesso
Porque é que não se consegue validar PDF/UA-2 com regras do PDF/UA-1?
O PDF/UA-2 não é o PDF/UA-1 com o número de parte incrementado. O PDF/UA-1 assenta no PDF 1.7 e num único role map plano; o PDF/UA-2 assenta no PDF 2.0 (ISO 32000-2), onde cada elemento de estrutura pode pertencer a um namespace e cada namespace traz o seu próprio role map. Um verificador UA-1 apontado a um ficheiro UA-2 ao mesmo tempo perde problemas reais e inventa problemas falsos
Os falsos são a parte embaraçosa. Os validadores PDF/UA-1 tipicamente impõem regras sobre elementos Note terem de transportar um ID e sobre a forma como o título não numerado H pode ser usado. Nenhuma dessas regras transfere. No namespace de estrutura do PDF 2.0, as notas de rodapé exprimem-se com FENote, e os títulos vão de H1 a Hn sem teto de seis níveis. O PDF/UA-2 vai mais longe e proíbe logo o papel não numerado H. Aplique a lógica de títulos UA-1 a essa árvore e ou se queixa de coisas que a norma permite ou deixa passar a única coisa que ela proíbe
Por isso o perfil PDF/UA-2 do PDFlibPas é um caminho de código separado, não uma flag no antigo. Também se recusa a reutilizar números de cláusula do UA-1. Os diagnósticos partilhados com o verificador UA-1 são reatribuídos à ISO 14289-2 nos relatórios UA-2, e onde um número de cláusula não foi verificado contra o texto da norma, o relatório deixa o campo da cláusula vazio em vez de adivinhar. Uma cláusula vazia é menos satisfatória do que um número de cláusula confiante, mas é o único resultado defensável quando ninguém confirmou o mapeamento
Como o PDFlibPas resolve papéis através do RoleMapNS
O PDFlibPas resolve cada papel de estrutura para um papel padrão num namespace conhecido, seguindo cadeias de /RoleMapNS através dos namespaces, e reporta qualquer cadeia que termine num papel personalizado, entre em ciclo ou viole as regras de mapeamento. Este é o núcleo da validação de estrutura do PDF/UA-2, porque a tecnologia de assistência só compreende papéis padrão; um /InvoiceLine personalizado não significa nada até aterrar em algo como P ou TR
O resolver parte do nome /S do elemento e do seu namespace /NS. Sem namespace, o elemento está implicitamente no namespace padrão do PDF 1.7 e aplica-se o /RoleMap legado na raiz da árvore de estrutura. Com namespace, aplica-se o /RoleMapNS do próprio dicionário do namespace, e um mapeamento pode apontar para um papel num namespace diferente através de um array de dois elementos. As verificações que daqui saem são concretas:
- Um namespace referenciado a partir de um elemento de estrutura tem de ser um dicionário indireto com um URI
/NSválido e tem de estar listado no array/Namespacesda raiz da árvore de estrutura (diagnóstico 10054) - Um papel personalizado tem eventualmente de resolver para um papel padrão no namespace do PDF 1.7, no namespace do PDF 2.0 (
http://iso.org/pdf2/ssn) ou no namespace MathML; cadeias circulares e becos sem saída são reportados como 10055 - Um papel padrão não pode ser remapeado para um personalizado, e um papel não pode ser remapeado dentro do seu próprio namespace explícito
- A raiz da estrutura tem de conter exatamente um elemento
Documentno namespace do PDF 2.0 (10057), e o/Pde cada elemento tem de apontar para o seu pai real (10058) - Um
Hnão numerado, ou umNotelegado do PDF 1.7 que não esteja mapeado para um papel do PDF 2.0, é reportado como 10056
Um detalhe que vale a pena copiar para as suas próprias ferramentas: o PDFlibPas valida todos os mapeamentos declarados, não apenas os alcançáveis a partir da árvore. Uma entrada partida num role map que nenhum elemento usa atualmente continua a ser um defeito latente, porque a próxima atualização de conteúdo começará a usá-la. O MathML também é tratado com honestidade. O namespace MathML é reconhecido como um alvo de resolução válido, mas o vocabulário MathML em si não é validado, e o relatório de cobertura refere-o
Executar uma verificação PDF/UA-2 a partir do Delphi
O TPDFlib.CheckFilePDFUACompliance é o ponto de entrada de chamada única: passe o ficheiro, a palavra-passe, a parte alvo e as opções, e recebe de volta um handle de lista de strings com as ocorrências codificadas. Um handle zero com LastErrorCode igual a zero significa que as verificações implementadas não encontraram nada; um LastErrorCode diferente de zero significa que a própria chamada falhou
var
Lib: TPDFlib;
Issues, I: Integer;
begin
Lib := TPDFlib.Create;
try
// TargetPart: 0 = deteta de XMP pdfuaid:part, 1 = PDF/UA-1, 2 = PDF/UA-2
// Options: 1 = parar à primeira falha, 0 = recolher todas as ocorrências
Issues := Lib.CheckFilePDFUACompliance('report.pdf', '', 2, 0);
if Issues = 0 then
begin
if Lib.LastErrorCode <> 0 then
Writeln('Validation did not run, error ', Lib.LastErrorCode)
else
Writeln('No issues from the implemented PDF/UA-2 checks');
end
else
try
for I := 1 to Lib.GetStringListCount(Issues) do // itens são 1-based
Writeln(Lib.GetStringListItem(Issues, I));
finally
Lib.ReleaseStringList(Issues);
end;
finally
Lib.Free;
end;
end;
Escolha a parte alvo com intenção. Um alvo 0 lê o pdfuaid:part dos metadados XMP e escolhe o perfil a partir daí, o que é adequado para um validador genérico de caixa de entrada. Para o seu próprio pipeline de output, passe 2 explicitamente. Se o seu gerador se esqueceu de escrever a identificação, a deteção automática cai nas regras do UA-1 e valida a norma errada; um 2 explícito produz antes o diagnóstico 10050 para a parte em falta, mais 10051 se o pdfuaid:rev não for 2024 e 10052 se a versão PDF efetiva não for 2.0. No lado da autoria, o TPDFlib.SetPDFUA2Mode escreve essa identificação, sobe a versão para 2.0 e seleciona o namespace de estrutura do PDF 2.0 como predefinição; o modelo de namespaces em si é explicado no passo a passo dos namespaces PDF 2.0, PDF/A-4 e PDF/UA-2
O que acontece quando o validador fica sem orçamento?
O PDFlibPas falha fechado: quando o verificador PDF/UA-2 atinge um limite de travessia, um ciclo, um limite do parser ou um documento que não consegue abrir, emite um diagnóstico, e um resultado sem diagnósticos nunca é promovido a passe. O diagnóstico 10059 está reservado exatamente para este caso de "o leitor não conseguiu terminar"
Os orçamentos são explícitos e publicados no JSON de cobertura: profundidade de estrutura 256, 100.000 objetos de estrutura e 256 saltos ao longo de qualquer cadeia de role map. Esses números existem porque as árvores de estrutura vêm de entrada não confiável. Um ficheiro hostil ou simplesmente partido pode aninhar elementos milhares de níveis abaixo, apontar um filho de volta a um ancestral ou construir um role map que encadeie através de centenas de namespaces. Um validador sem orçamentos ou rebenta ou bloqueia, e um validador com orçamentos mas sem diagnósticos faz algo mais subtil e pior: termina cedo, não encontra nada de errado na parte que leu e reporta sucesso
O mesmo princípio aplica-se um nível acima. Se o leitor subjacente devolve uma pontuação abaixo de 100 mas nenhuma linha de ocorrências, o que pode acontecer quando o parsing aborta antes de qualquer regra correr, o PDFlibPas insere uma linha 10059 para a lista vazia não ser confundida com um resultado limpo. Uma exceção durante a verificação também se torna numa linha 10059. Isso é pouco código, e fecha a forma mais comum de os pipelines de validação mentirem aos seus donos
Partilhar uma análise com o PDF/A e construir um relatório
Para trabalho em lote, a API de pipeline de validação analisa um ficheiro uma vez e corre várias verificações contra a análise partilhada. O RunPDFUAValidationPipelineTask é o membro PDF/UA dessa família e aceita os mesmos valores de parte alvo que a função de chamada única, o que evita que um preflight PDF/A mais PDF/UA-2 pague duas leituras completas de um documento grande
var
Pipe, PDFAIssues, UAIssues: Integer;
Json: WideString;
begin
Pipe := Lib.BeginValidationPipeline('archive.pdf', '');
if Pipe = 0 then
Exit;
try
PDFAIssues := Lib.RunValidationPipelineTask(Pipe, 1, 0); // 1 = PDF/A
UAIssues := Lib.RunPDFUAValidationPipelineTask(Pipe, 2, 0); // PDF/UA-2
// ... inspecionar e libertar ambos os handles como no exemplo anterior ...
Writeln('Parses: ', Lib.GetValidationPipelineParseCount(Pipe));
// ComplianceTests: 1 = PDF/A, 2 = PDF/UA, 0 = ambos; ReportFormat 1 = JSON
Json := Lib.CreateValidationPipelineReport(Pipe, 0, 0, 1);
finally
Lib.ReleaseValidationPipeline(Pipe);
end;
end;
O relatório estruturado rotula a secção de acessibilidade como PDF/UA-2 e ISO 14289-2 quando o ficheiro se identifica como parte 2, e cada secção PDF/UA traz "fullCertification": false junto com o objeto de cobertura incorporado. O relatório de preflight em texto acrescenta uma linha a declarar que só correram verificações implementadas e que a certificação de acessibilidade completa precisa de validação adicional e revisão humana. Os formatos de relatório mais abrangentes, os IDs de regras e os padrões de gating de CI estão descritos no guia de preflight PDF/A e PDF/UA
O que garante afinal o relatório de cobertura?
O TPDFlib.GetPDFUAReadValidationCoverageJSON garante uma coisa: uma lista exata do que o verificador PDF/UA-2 examinou e do que não examinou. Para a parte 2 devolve fullCertification fixo em false, um array checked, um array unverified e os limites de travessia. Não garante que um ficheiro é acessível, e está desenhado de forma que nenhum caminho de código o possa fazer dizer o contrário
var
Coverage: WideString;
begin
Coverage := Lib.GetPDFUAReadValidationCoverageJSON(2);
// Guarde-o junto do resultado da validação, não em vez dele
TFile.WriteAllText('report.coverage.json', Coverage, TEncoding.UTF8);
if Pos('"fullCertification":false', Coverage) > 0 then
Writeln('Machine checks only; schedule a human accessibility review');
end;
A lista checked cobre identificação (parte, revisão, prefixo), versão PDF efetiva, metadados do título, /MarkInfo, DisplayDocTitle, referências de namespaces, resolução de role maps, a raiz do documento, pais de estrutura, ciclos da árvore e as regras de H e Note. A lista unverified é igualmente específica: etiquetagem de conteúdo e ordem de leitura, contenção da estrutura, fontes e mapeamento Unicode, anotações e formulários, língua natural, descrições alternativas, o vocabulário MathML e revisão humana de acessibilidade. Vários desses pontos nem são mecanicamente decidíveis. Nenhum parser lhe diz se um alt text descreve realmente o gráfico, ou se a ordem de leitura faz sentido para alguém que a esteja a ouvir
Trate o JSON de cobertura como parte do resultado. Arquive-o com cada corrida de validação, e quando um auditor perguntar o que "passou" significava numa determinada data, pode entregar o âmbito exato em vez de o reconstruir de memória e a partir de um número de versão
Verificação cruzada com o veraPDF
O núcleo PDF/UA-2 foi verificado de forma cruzada contra o validador de linha de comandos veraPDF 1.30.3 usando o seu perfil ua2, e os resultados alinham-se onde a cobertura se sobrepõe. Uma amostra válida do núcleo gerada de forma independente passou nas duas ferramentas; uma amostra que declara pdfuaid:rev 2023 e uma amostra que usa o papel não numerado H falharam nas duas ferramentas
A quarta amostra é a que vale a pena comentar. Continha conteúdo de desenho que não estava etiquetado de todo, e o veraPDF assinalou-o enquanto as verificações de núcleo do PDFlibPas não, porque a etiquetagem de conteúdo está fora da cobertura atual. Essa lacuna é exatamente a razão pela qual content-tagging-and-reading-order encabeça a lista unverified. Correr um segundo validador é um seguro barato, e uma discordância entre duas ferramentas é muitas vezes a forma mais rápida de encontrar o limite do que cada uma verifica. Para o lado da autoria do mesmo problema, construir árvores que passem à primeira, veja o guia da árvore de estrutura PDF etiquetado
FAQ
O PDFlibPas certifica um ficheiro como conforme PDF/UA-2?
Não. O PDFlibPas executa as verificações mecânicas implementadas para PDF/UA-2 e devolve um relatório de cobertura com fullCertification sempre false, listando todas as áreas que ainda precisam de outras ferramentas ou revisão humana
Que valor TargetPart passar a CheckFilePDFUACompliance?
Passe 2 quando sabe que o ficheiro tem de ser PDF/UA-2, para que a identificação em falta seja reportada. Passe 0 para detetar automaticamente a parte a partir do XMP pdfuaid:part, e 1 para forçar as regras do PDF/UA-1
O que significa o diagnóstico 10059 num relatório PDF/UA-2?
O diagnóstico 10059 significa que o validador não conseguiu completar: o ficheiro não pôde ser analisado nem aberto, um limite de travessia foi excedido, foi encontrado um ciclo ou ocorreu uma exceção. Conta sempre como falha, nunca como passe
A validação PDF/UA-2, o pipeline de validação partilhado e as APIs de autoria PDF 2.0 vêm no losLab PDF Library for Delphi, ao lado do preflight PDF/A e dos relatórios de validação estruturados