Technical Article

Esquemas de Extensão PDF/A-3 para XMP Factur-X no Delphi

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