Construiu uma fatura Factur-X e todas as verificações de contentor passam. O catálogo contém uma matriz /AF, a árvore de nomes EmbeddedFiles resolve para a especificação de ficheiro correta, o factur-x.xml incorporado tem o /AFRelationship correto de Alternative, e o ValidateFacturXInvoice incorporado devolve 1. Depois executa o mesmo ficheiro através do veraPDF, o verificador de referência que os portais fiscais utilizam, e este declara que o documento não é um PDF/A-3 válido. A estrutura está correta. Os metadados são o problema, e a falha é uma das mais fáceis de deixar passar em todo o fluxo de trabalho de faturas eletrónicas
A razão merece ser compreendida em detalhe, porque explica uma classe de defeito PDF/A que nada tem a ver com a página visível ou com o anexo e tudo a ver com a forma como o XMP se descreve a si próprio. Esta é a armadilha que se esconde atrás de uma verificação de contentor verde
As quatro propriedades que fazem falhar o ficheiro
Uma fatura Factur-X escreve quatro propriedades personalizadas no seu pacote XMP para que o software downstream possa ler o perfil da fatura sem processar o XML incorporado. Residem no namespace Factur-X sob o prefixo fx: fx:DocumentFileName, fx:DocumentType, fx:Version e fx:ConformanceLevel. São exatamente os metadados de que um leitor necessita para saber que este PDF transporta uma fatura EN 16931 denominada factur-x.xml na versão 1.0
Nenhuma dessas quatro propriedades faz parte de qualquer esquema XMP predefinido pelo PDF/A. Os esquemas Dublin Core, XMP Basic, PDF e de identificação PDF/A são conhecidos por um leitor conforme, mas fx: não é. Quando o veraPDF percorre o XMP e encontra uma propriedade cujo namespace não reconhece, procura uma declaração que lhe diga o que a propriedade significa. Se essa declaração estiver ausente, reporta uma falha contra a cláusula 6.6.2.3.1 da ISO 19005-3, que exige que cada propriedade não retirada de um esquema predefinido seja descrita num esquema de extensão PDF/A. Quatro propriedades não declaradas, quatro formas de o ficheiro ser rejeitado, e nenhuma delas é visível para uma verificação de contentor
Por que o PDF/A recusa uma propriedade personalizada não declarada
A regra parece pedante até se recordar para que serve o PDF/A. O formato existe para que um ficheiro possa ser aberto e compreendido daqui a décadas, por software que nunca foi informado sobre as convenções de 2026. Espera-se que um leitor conforme compreenda o documento apenas a partir do próprio documento, sem nenhum registo externo para consultar
Os metadados personalizados violam essa promessa a menos que o ficheiro transporte a sua própria descrição. Perante uma propriedade fx:ConformanceLevel não declarada, um leitor futuro não pode saber o URI de namespace ao qual o prefixo fx está ligado, se o valor é texto, data ou inteiro, ou se a propriedade descreve o próprio documento ou algum recurso externo. O mecanismo de esquema de extensão PDF/A fecha essa lacuna. Permite que o ficheiro declare, numa estrutura XMP fixa, o namespace, o prefixo e, para cada propriedade, um tipo de valor e uma categoria internal ou external. Uma vez presente essa declaração, a propriedade é autodescritiva e a cláusula 6.6.2.3.1 é satisfeita. Sem ela, o validador não tem outra hipótese senão tratar a propriedade como ininteligível e reprovar o ficheiro. A distinção de categoria é importante aqui: propriedades de fatura como estas descrevem dados que provêm de fora do processador de PDF, pelo que são declaradas como external e não internal
O que contém a declaração do esquema de extensão
A declaração é um rdf:Description no pacote XMP que utiliza os três namespaces definidos pela AIIM: pdfaExtension, pdfaSchema e pdfaProperty. Dentro de um conjunto pdfaExtension:schemas encontra-se uma entrada de esquema que designa o esquema Factur-X, fornece o seu pdfaSchema:namespaceURI e pdfaSchema:prefix, e lista as quatro propriedades numa sequência pdfaSchema:property. Cada propriedade contém um nome, um pdfaProperty:valueType de Text e um pdfaProperty:category de external. A marcação ilustrativa abaixo mostra a forma desse bloco
<rdf:Description rdf:about=""
xmlns:pdfaExtension="http://www.aiim.org/pdfa/ns/extension/"
xmlns:pdfaSchema="http://www.aiim.org/pdfa/ns/schema#"
xmlns:pdfaProperty="http://www.aiim.org/pdfa/ns/property#">
<pdfaExtension:schemas>
<rdf:Bag>
<rdf:li rdf:parseType="Resource">
<pdfaSchema:schema>Factur-X PDFA Extension Schema</pdfaSchema:schema>
<pdfaSchema:namespaceURI>urn:factur-x:pdfa:CrossIndustryDocument:invoice:1p0#</pdfaSchema:namespaceURI>
<pdfaSchema:prefix>fx</pdfaSchema:prefix>
<pdfaSchema:property>
<rdf:Seq>
<rdf:li rdf:parseType="Resource">
<pdfaProperty:name>DocumentFileName</pdfaProperty:name>
<pdfaProperty:valueType>Text</pdfaProperty:valueType>
<pdfaProperty:category>external</pdfaProperty:category>
<pdfaProperty:description>name of the embedded XML invoice file</pdfaProperty:description>
</rdf:li>
<!-- DocumentType, Version, ConformanceLevel declared the same way -->
</rdf:Seq>
</pdfaSchema:property>
</rdf:li>
</rdf:Bag>
</pdfaExtension:schemas>
</rdf:Description>
O URI de namespace e o prefixo não são cadeias de caracteres fixas. Seguem o perfil. Um documento Factur-X usa urn:factur-x:pdfa:CrossIndustryDocument:invoice:1p0# com o prefixo fx, enquanto um ficheiro ZUGFeRD 2.0 selecionado através de zugferd-invoice.xml resolve para um URI diferente sob o seu próprio nome de esquema. O esquema de extensão tem de declarar o mesmo URI de namespace que o bloco de propriedades efetivamente utiliza, ou o validador ainda não consegue ligar os dois. O PDFlibPas deriva ambos os valores a partir do nome do ficheiro e da versão que passa, pelo que a declaração e o bloco de propriedades concordam sempre
Como o auxiliar escreve ambas as metades em conjunto
No PDFlibPas não se monta esse XML manualmente. Coloca-se o documento em modo PDF/A-3 e chama-se um método. O primeiro passo é a definição do sinalizador de conformidade, porque o Factur-X requer PDF/A-3. Chamar SetPDFAMode(7) seleciona o nível PDF/A-3u, que define pdfaid:part para 3 e pdfaid:conformance para U no esquema de identificação. O pacote XMP contém agora a parte e a conformidade corretas antes de quaisquer metadados de fatura serem adicionados
var
FileID: Integer;
begin
PDF.SetPDFAMode(7); // PDF/A-3u: pdfaid:part=3, conformance=U
PDF.NewDocument;
// draw the human-readable invoice page here
FileID := PDF.AddFacturXAssociatedFileFromString(
InvoiceXML, // raw UTF-8 XML bytes
'EN16931', // ConformanceLevel
'factur-x.xml', // embedded file name
'Factur-X invoice XML', // /Desc text
'Alternative', // /AFRelationship
'1.0', // profile version
''); // optional country code
if FileID = 0 then
Exit; // not PDF/A-3, or XML/profile mismatch
PDF.SaveToFile('factur-x.pdf');
end;
Uma única chamada a AddFacturXAssociatedFileFromString faz o trabalho que o ficheiro com falha estava a omitir. Incorpora o XML como ficheiro associado PDF/A-3 com a relação nomeada e regista as quatro propriedades fx juntamente com o nome do esquema, o URI de namespace e o prefixo para o perfil escolhido. Quando o documento é guardado, um passo interno denominado ApplyFacturXMetadata injeta tanto o bloco de propriedades como a declaração pdfaExtension:schemas correspondente no pacote XMP, para que as propriedades personalizadas cheguem já descritas. O método devolve 0 se o documento não estiver em modo PDF/A-3 ou se o XML não corresponder ao perfil declarado, que é a mesma verificação que impede que uma fatura malformada chegue ao ficheiro
O ponto cego que a verificação de contentor não consegue ver
Esta é a parte a nomear claramente, porque é a razão pela qual o erro se oculta. ValidateFacturXInvoice verifica o contentor. Confirma que o catálogo tem uma entrada /AF, que a árvore de nomes EmbeddedFiles está presente, que o XML da fatura existe, que o nome do ficheiro incorporado corresponde ao perfil, que o ID de guia no XML concorda com o nível de conformidade e que o /AFRelationship é um que o PDF/A-3 permite. Estas são verificações reais que detetam defeitos reais. GetFacturXValidationIssues reporta-os pelo nome, com identificadores como MissingCatalogAF, NotPDFA3, ConformanceGuidelineMismatch, InvalidAFRelationship e InvalidFileNameProfile
O que não verifica é se o esquema de extensão XMP está presente e correto. Um ficheiro cujo contentor é impecável mas cujas propriedades fx não estão declaradas passa todas as verificações de problema e devolve 1, porque nada nessa lista inspeciona o bloco pdfaExtension:schemas. É precisamente por isso que uma fatura construída manualmente, ou produzida por um pipeline que escreveu o bloco de propriedades sem a declaração, pode passar pelo validador incorporado e ainda assim falhar no veraPDF na cláusula 6.6.2.3.1. O validador de contentor e o validador de metadados PDF/A respondem a perguntas diferentes, e só o verificador PDF/A completo responde à segunda
Ler os problemas para saber qual a camada que falhou
Como as duas camadas falham de forma independente, o hábito de diagnóstico correto é ler primeiro os problemas do contentor e tratar um resultado limpo como uma declaração sobre o contentor apenas, nunca sobre os metadados PDF/A. Execute a validação incorporada, recolha a lista de problemas e atue sobre ela antes de recorrer a uma ferramenta externa
var
Issues: WideString;
begin
if PDF.ValidateFacturXInvoice = 0 then
begin
Issues := PDF.GetFacturXValidationIssues('|');
// container-level identifiers, for example:
// MissingCatalogAF, NotPDFA3, MissingEmbeddedFilesNameTree,
// ConformanceGuidelineMismatch, InvalidAFRelationship
WriteLn('Container issues: ', Issues);
end
else
WriteLn('Container OK; verify XMP extension schema with a PDF/A checker.');
end;
Quando essa chamada devolve um nome de problema, a falha está no contentor e a mensagem indica qual a parte. Quando devolve um resultado limpo e o veraPDF ainda rejeita o ficheiro, a falha é quase sempre o esquema de extensão XMP, e a correção é deixar AddFacturXAssociatedFileFromString escrever os metadados em vez de construir o bloco de propriedades manualmente. Manter as duas perguntas separadas na sua mente é o que transforma uma rejeição desconcertante num diagnóstico de uma linha: os problemas de contentor surgem através da lista de problemas, os problemas de declaração de esquema surgem apenas através de um validador PDF/A, e confundir os dois é o que permite que o erro se oculte
O quadro geral de conformidade PDF/A e PDF/UA, incluindo como executar uma passagem de preflight antes de um ficheiro sair do seu build, está descrito em o guia de preflight PDF/A e PDF/UA. Se a sua fatura também tiver de ser acessível, a árvore de estrutura da qual o PDF/A-3a e o PDF com etiquetas dependem é o tema do artigo sobre acessibilidade de PDF com etiquetas. O tratamento do esquema de extensão descrito aqui é fornecido como parte da Biblioteca PDF PDFlibPas para Delphi, juntamente com o suporte de perfis Factur-X, ZUGFeRD e XRechnung documentado ao longo deste blogue