Você construiu uma nota fiscal Factur-X e todas as verificações de contêiner passam. O catálogo carrega um array /AF, a árvore de nomes EmbeddedFiles resolve para a especificação de arquivo correta, o factur-x.xml incorporado tem o /AFRelationship correto de Alternative, e o ValidateFacturXInvoice integrado retorna 1. Então você executa o mesmo arquivo pelo veraPDF, o verificador de referência que os portais fiscais usam, e ele determina que o documento inteiro não é um PDF/A-3 válido. A estrutura está certa. Os metadados são o problema, e a falha é uma das mais fáceis em todo o fluxo de trabalho de nota fiscal eletrônica de ignorar
A razão vale a pena entender por completo, porque ela explica uma classe de defeito PDF/A que não tem nada a ver com a página visível ou o anexo e tudo a ver com como o XMP se descreve. Essa é a armadilha que se esconde atrás de uma verificação de contêiner com sinal verde
As quatro propriedades que reprovam o arquivo
Uma nota fiscal Factur-X escreve quatro propriedades personalizadas em seu pacote XMP para que o software downstream possa ler o perfil da nota fiscal sem analisar o XML incorporado. Elas residem no namespace do Factur-X sob o prefixo fx: fx:DocumentFileName, fx:DocumentType, fx:Version e fx:ConformanceLevel. São exatamente os metadados que um leitor precisa para saber que este PDF carrega uma nota fiscal EN 16931 chamada factur-x.xml na versão 1.0
Nenhuma dessas quatro propriedades faz parte de qualquer esquema XMP que o PDF/A predefine. Os esquemas Dublin Core, XMP Basic, PDF e de identificação PDF/A são conhecidos de um leitor em conformidade, mas fx: não é. Quando o veraPDF percorre o XMP e chega a uma propriedade cujo namespace não reconhece, ele procura uma declaração que diria o que a propriedade significa. Se essa declaração estiver ausente, ele relata uma falha contra a cláusula 6.6.2.3.1 da ISO 19005-3, que exige que toda propriedade não extraída de um esquema predefinido seja descrita em um esquema de extensão PDF/A. Quatro propriedades não declaradas, quatro maneiras de o arquivo ser rejeitado, e nenhuma delas é visível para uma verificação de contêiner
Por que o PDF/A recusa uma propriedade personalizada simples
A regra parece pedante até você lembrar para que serve o PDF/A. O formato existe para que um arquivo possa ser aberto e compreendido décadas a partir de agora, por software que nunca foi informado sobre as convenções de 2026. Espera-se que um leitor em conformidade faça sentido do documento a partir do próprio documento, sem nenhum registro externo para consultar
Os metadados personalizados quebram essa promessa a menos que o arquivo carregue sua própria descrição. Dada uma propriedade fx:ConformanceLevel simples, um leitor futuro não consegue saber o URI de namespace ao qual o prefixo fx está vinculado, se o valor é texto, uma data ou um 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. Ele permite que o arquivo declare, em uma estrutura XMP fixa, o namespace, o prefixo e, para cada propriedade, um tipo de valor e uma categoria de 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 opção senão tratar a propriedade como ininteligível e reprovar o arquivo. A distinção de categoria importa aqui: propriedades de nota fiscal como estas descrevem dados que vêm de fora do processador PDF, portanto são declaradas como external em vez de internal
O que a declaração do esquema de extensão contém
A declaração é um rdf:Description no pacote XMP que usa os três namespaces definidos pela AIIM: pdfaExtension, pdfaSchema e pdfaProperty. Dentro de um bag pdfaExtension:schemas fica uma entrada de esquema que nomeia o esquema Factur-X, fornece seu pdfaSchema:namespaceURI e pdfaSchema:prefix, e então lista as quatro propriedades em uma sequência pdfaSchema:property. Cada propriedade carrega um nome, um pdfaProperty:valueType de Text e um pdfaProperty:category de external. O markup ilustrativo 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 strings fixas. Eles seguem o perfil. Um documento Factur-X usa urn:factur-x:pdfa:CrossIndustryDocument:invoice:1p0# com o prefixo fx, enquanto um arquivo ZUGFeRD 2.0 selecionado por meio de zugferd-invoice.xml resolve para um URI diferente sob seu próprio nome de esquema. O esquema de extensão precisa declarar o mesmo URI de namespace que o bloco de propriedade realmente usa, ou o validador ainda não consegue conectar os dois. O PDFlibPas deriva ambos os valores a partir do nome do arquivo e da versão que você passa, portanto a declaração e o bloco de propriedade sempre concordam
Como o auxiliar escreve as duas metades juntas
No PDFlibPas você não monta esse XML manualmente. Você coloca o documento em um modo PDF/A-3 e chama um método. A primeira coisa a definir é o 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 como 3 e pdfaid:conformance como U no esquema de identificação. O pacote XMP agora carrega a parte e a conformidade corretas antes de qualquer metadado de nota fiscal ser adicionado
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 para AddFacturXAssociatedFileFromString faz o trabalho que o arquivo com falha estava faltando. Ele incorpora o XML como um arquivo associado PDF/A-3 com o relacionamento que você nomeou, e registra as quatro propriedades fx junto com o nome do esquema, o URI de namespace e o prefixo para o perfil escolhido. Quando o documento é salvo, uma etapa interna chamada ApplyFacturXMetadata injeta tanto o bloco de propriedade quanto a declaração correspondente de pdfaExtension:schemas no pacote XMP, para que as propriedades personalizadas cheguem já descritas. O método retorna 0 se o documento não está em modo PDF/A-3 ou se o XML não corresponde ao perfil declarado, que é a mesma guarda que impede uma nota fiscal malformada de chegar ao arquivo em primeiro lugar
O ponto cego que a verificação de contêiner não consegue ver
Esta é a parte a nomear claramente, porque é a razão pela qual o bug se esconde. ValidateFacturXInvoice verifica o contêiner. Ele confirma que o catálogo tem uma entrada /AF, a árvore de nomes EmbeddedFiles está presente, o XML da nota fiscal existe, o nome do arquivo incorporado corresponde ao perfil, o ID de diretriz no XML concorda com o nível de conformidade e o /AFRelationship é um que o PDF/A-3 permite. Essas são verificações reais e detectam defeitos reais. GetFacturXValidationIssues os relata por nome, com identificadores como MissingCatalogAF, NotPDFA3, ConformanceGuidelineMismatch, InvalidAFRelationship e InvalidFileNameProfile
O que ele não verifica é se o esquema de extensão XMP está presente e correto. Um arquivo cujo contêiner está impecável, mas cujas propriedades fx não estão declaradas, passa por todas as verificações de problemas e retorna 1, porque nada nessa lista inspeciona o bloco pdfaExtension:schemas. É precisamente por isso que uma nota fiscal construída manualmente, ou produzida por um pipeline que escreveu o bloco de propriedades sem a declaração, pode passar facilmente pelo validador integrado e ainda falhar no veraPDF na cláusula 6.6.2.3.1. O validador de contêiner e o validador de metadados PDF/A respondem a perguntas diferentes, e apenas o verificador PDF/A completo responde à segunda
Lendo problemas para saber qual camada quebrou
Como as duas camadas falham independentemente, o hábito de diagnóstico correto é ler os problemas do contêiner primeiro e tratar um resultado limpo como uma declaração sobre o contêiner apenas, nunca sobre os metadados PDF/A. Execute a validação integrada, colete a lista de problemas e aja com base nela 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 retorna um nome de problema, a falha está no contêiner e a mensagem diz qual parte. Quando retorna limpa e o veraPDF ainda rejeita o arquivo, a falha é quase sempre o esquema de extensão XMP, e a correção é deixar o AddFacturXAssociatedFileFromString escrever os metadados em vez de construir o bloco de propriedades você mesmo. Manter as duas perguntas separadas em sua própria mente é o que transforma uma rejeição desconcertante em um diagnóstico de uma linha: problemas de contêiner surgem pela lista de problemas, problemas de declaração de esquema surgem apenas por um validador PDF/A, e confundir os dois é o que deixa o bug se esconder
O panorama mais amplo de conformidade com PDF/A e PDF/UA, incluindo como executar uma passagem de preflight antes que um arquivo saia do seu build, é abordado em o passo a passo de preflight de PDF/A e PDF/UA. Se sua nota fiscal também precisa ser acessível, a árvore de estrutura da qual o PDF/A-3a e o PDF marcado dependem é o assunto de o artigo sobre acessibilidade de PDF marcado. O tratamento de esquema de extensão descrito aqui é fornecido como parte da Biblioteca Delphi PDF do PDFlibPas junto com o suporte a perfis Factur-X, ZUGFeRD e XRechnung documentado neste blog