Artigo Técnico

PDF/VT Variable Data Printing in Delphi with PDFium VCL

Uma gráfica transacional envia de volta sua execução de extratos de 80.000 páginas com uma rejeição de uma linha: "não é PDF/VT, o RIP não pode fazer cache". O arquivo abre perfeitamente em todos os visualizadores na sua mesa, as cores estão corretas, os dados foram mesclados corretamente. Nada disso é o que a impressora digital solicitou. A impressão de dados variáveis de alta velocidade vive ou morre com a capacidade da impressora de reconhecer que o bloco de logotipo do cliente na página 1 é byte por byte o mesmo objeto que o da página 40.000, renderizá-lo uma vez e reutilizá-lo. O PDF/VT é o padrão que torna essa promessa verificável por máquina, e "parece correto" é exatamente a armadilha, porque a estrutura que o RIP lê é invisível na tela

O PDFiumPas expõe essa estrutura por meio de uma pequena interface no TPdf: o SaveAsPdfVT a grava, o ValidatePdfVT a verifica. Este artigo é sobre o que esses dois métodos realmente colocam no disco e inspecionam, onde a ISO 16612-2 é mais estrita do que parece à primeira vista e quais partes são âncoras estruturais honestas em vez de um preflight completo contra o qual você pode cobrar um cliente

O que o PDF/VT padroniza e por que o PDF/X vem primeiro

O PDF/VT (ISO 16612-2:2010) não é um novo formato de arquivo. Ele é uma camada de metadados de otimização acoplada a um arquivo PDF/X, e essa ordenação é fundamental. O padrão define três níveis de conformidade, mas apenas dois deles nomeiam um arquivo PDF: PDF/VT-1, um único documento autônomo, e PDF/VT-2, um modelo de conjunto de arquivos onde as páginas referenciam recursos externos compartilhados. O terceiro token que você pode ver, PDF/VT-2s, não é de forma alguma um valor no nível do arquivo; ele reside em um cabeçalho de fluxo MIME descrito no Anexo A. Se você encontrar código inserindo GTS_PDFVTVersion = "PDF/VT-2s" no XMP de um documento, esse código está incorreto

A regra inegociável para um único arquivo é a base PDF/X. A ISO 16612-2 §6.2.1 exige que cada arquivo PDF/VT-1 também seja um arquivo PDF/X-4 válido. O conjunto de arquivos do PDF/VT-2, de acordo com o §6.2.2, deve, em vez disso, basear-se em PDF/X-4p, PDF/X-5g ou PDF/X-5pg. É por isso que um gravador de PDF/VT não pode simplesmente anexar algumas chaves identificadoras: ele precisa carregar todo o conjunto de marcadores PDF/X-4 consigo, o que significa um OutputIntent, um perfil de destino ICC incorporado, as entradas XMP e Info correspondentes do documento, um /ID no trailer e nenhuma criptografia. Pule qualquer um desses e você terá um arquivo que reivindica PDF/VT e falha no momento em que um consumidor conforme verifica a base. O PDFiumPas trata a camada PDF/X-4 as parte da gravação de PDF/VT, portanto você não precisa chamar um SaveAsPdfX separado primeiro; o injetor grava ambas as camadas em uma única passagem

Gravando um arquivo com SaveAsPdfVT

A chamada mínima não precisa de nada além de um documento ativo, pois o TPdfVTSaveOptions.Default fornece um perfil ICC sRGB integrado e conformidade pvc1. A gravação executa três etapas internamente: remove qualquer segurança (injetar marcadores de texto não criptografados em um fluxo de objetos criptografados o corromperia), conecta o dicionário Info existente do documento e o /ID do trailer ao conjunto de marcadores para que os valores XMP e Info coincidam, e então anexa os objetos PDF/X-4 e PDF/VT por meio de uma atualização incremental

var
  Pdf: TPdf;
begin
  Pdf := TPdf.Create(nil);
  try
    if Pdf.LoadFromFile('statements-merged.pdf') then
    begin
      // Default options: built-in sRGB OutputIntent, PDF/VT-1, synthesised DPart
      if Pdf.SaveAsPdfVT('statements-pdfvt.pdf') then
        Writeln('PDF/VT-1 written')
      else
        Writeln('Save failed (document not active?)');
    end;
  finally
    Pdf.Free;
  end;
end;

Para saídas de produção reais, você quase sempre deseja substituir o OutputIntent pela caracterização da sua impressora, e não pelo fallback genérico sRGB. Forneça os bytes ICC e os identificadores de condição por meio de TPdfVTSaveOptions:

var
  Pdf: TPdf;
  Opt: TPdfVTSaveOptions;
  Icc: TBytes;
begin
  Pdf := TPdf.Create(nil);
  try
    Pdf.LoadFromFile('directmail-merged.pdf');
    Icc := LoadIccProfile('GRACoL2013_CRPC6.icc');  // your own loader

    Opt := TPdfVTSaveOptions.Default;
    Opt.Conformance := pvc1;            // pvc2 is normalised to pvc1 on write
    Opt.IccProfileData := Icc;
    Opt.OutputConditionIdentifier := 'CGATS21_CRPC6';
    Opt.OutputCondition := 'Commercial print, coated, CRPC6';
    Opt.RegistryName := 'http://www.color.org';
    Opt.Title := 'Spring 2026 Direct Mail Run';
    Opt.Trapped := ptvFalse;           // PDF/X Info /Trapped state

    Pdf.SaveAsPdfVT('directmail-pdfvt.pdf', Opt);
  finally
    Pdf.Free;
  end;
end;

Um detalhe nesse trecho é uma proteção deliberada, e não uma limitação com a qual você possa discutir. Definir Opt.Conformance := pvc2 não produz um arquivo PDF/VT-2. O gravador normaliza qualquer solicitação não-pvc1 de volta para pvc1, porque o PDF/VT-2 é um formato de conjunto de arquivos e um gravador de arquivo único que anexa um documento de saída fisicamente não pode montar o conjunto de recursos externos que o §6.2.2 exige. O valor pvc2 existe para o caminho de leitura, permitindo que o ValidatePdfVT reconheça e relate um documento de conjunto de arquivos existente; ele não é um alvo de gravação

A árvore DPart: a estrutura que o RIP realmente lê

O coração do PDF/VT is a hierarquia de Document Part (DPart). É o que permite que uma impressora divida uma longa tiragem em registros, agrupe registros em destinatários ou pacotes de correspondência e anexe metadados de Document Part (Document Part Metadata) para que os equipamentos downstream possam rotear e faturar cada peça. A norma ISO 16612-2 §6.5 define a fiação: o catálogo carrega um /DPartRoot, o nó DPart raiz carrega /DPartRootNode e uma /NodeNameList nomeando cada nível da hierarquia, DParts folhas cobrem intervalos da árvore de páginas, e cada página que pertence a uma parte aponta de volta para sua folha por meio de uma entrada /DPart no nível da página

Quando seu documento de origem já contém uma hierarquia utilizável, o SaveAsPdfVT a preserva. Quando não contém, o gravador sintetiza uma árvore mínima: uma DPart única no nível do documento que abrange a árvore de páginas atual em ordem, com uma referência de volta /DPart anexada a cada objeto de página ativo e uma /NodeNameList [/Document] de um nível. Seja honesto consigo mesmo sobre o que é essa árvore mínima. Ela é uma âncora estrutural que satisfaz os requisitos de formato do §6.5; ela não é metadados de negócios. Ela não pode inventar destinatários, limites de peças de correspondência ou lotes de produtos, porque essa informação nunca esteve na origem. Se você tiver dados por destinatário, espera-se que você mesmo crie uma árvore DPart mais profunda e estenda a /NodeNameList para corresponder aos níveis criados

Validação que vai além da presença de chaves

O ValidatePdfVT retorna um registro TPdfVTValidationResult contendo três itens: a conformidade (Conformance) detectada, um conjunto de problemas (Issues) e um auxiliar IsCompliant que é verdadeiro apenas quando a conformidade é um nível real e o conjunto de problemas está vazio. A enumeração de problemas é deliberadamente específica, para que um resultado com falha indique qual cláusula você perdeu, em vez de apenas "inválido":

var
  Pdf: TPdf;
  Res: TPdfVTValidationResult;
begin
  Pdf := TPdf.Create(nil);
  try
    Pdf.LoadFromFile('statements-pdfvt.pdf');
    Res := Pdf.ValidatePdfVT;

    if Res.IsCompliant then
      Writeln('PDF/VT compliant: ', VTLevelName(Res.Conformance))
    else
    begin
      if pvviMissingDPartRoot in Res.Issues then
        Writeln('DPart hierarchy missing or unusable');
      if pvviMissingPdfXIdentifier in Res.Issues then
        Writeln('PDF/X-4 base identifier absent');
      if pvviMissingOutputIntent in Res.Issues then
        Writeln('OutputIntent / ICC profile missing');
      if pvviEncryptionPresent in Res.Issues then
        Writeln('Encrypted - PDF/X forbids this');
    end;
  finally
    Pdf.Free;
  end;
end;

As duas verificações que valem a pena entender em profundidade são o emparelhamento de conformidade e o percurso DPart, pois ambas costumavam ser muito tolerantes e foram rigidificadas para corresponder à especificação. Do lado do emparelhamento, o validador faz uma correspondência exata, e não "qualquer PDF/X serve": um arquivo PDF/VT-1 é aceito apenas sob uma base PDF/X-4, e um arquivo PDF/VT-2 apenas sob PDF/X-4p, PDF/X-5g ou PDF/X-5pg. Um marcador PDF/VT-1 assentado sobre uma base PDF/X-1a é relatado, não aceito sem questionamentos

O percurso DPart é onde reside a maior parte do rigor. Não basta que o catálogo tenha uma chave /DPartRoot, porque um objeto vazio forjado ou um sem links de página ainda não pode ser consumido. HasValidDPartHierarchy and a função recursiva ValidateDPartNode rastreiam toda a estrutura: eles seguem links pais, rejeitam filhos duplicados e ciclos, exigem que /Start e /DParts sejam mutuamente exclusivos, e requerem que os intervalos de páginas folha cubram a árvore de páginas em ordem depth-first (primeiro em profundidade), com o /DPart de cada página apontando para a folha que a contém. Todas essas falhas internas colapsam no bit único do problema pvviMissingDPartRoot em vez de expandir o enum público, portanto, trate esse sinalizador único como "a hierarquia DPart está inutilizável", e não literalmente como "a chave raiz está ausente"

Três armadilhas sintáticas que o validador agora impõe

Passagens sucessivas em relação à Tabela 4 do §6.5 revelaram formatos que as versões anteriores aceitavam, mas o padrão não. Esses são o tipo de coisa que uma árvore DPart construída manualmente costuma errar, por isso vale a pena chamá-los explicitamente:

  • /DParts é uma matriz de matrizes, não uma matriz plana. Cada elemento da matriz externa deve ser uma matriz de referência indireta. Um /DParts [9 0 R] plano é rejeitado; a forma conforme é /DParts [[9 0 R] [10 0 R]]. Isso impede que uma estrutura não hierárquica se mascare como um nível válido
  • /End apenas marca um intervalo de várias páginas genuíno. Uma DPart folha pode carregar /End apenas quando também tiver /Start, e /End deve cair depois de /Start na ordem da árvore de páginas. Um /Start 3 0 R /End 3 0 R degenerado agora torna a hierarquia inutilizável, em vez de ler como uma parte de página única
  • Os nomes na /NodeNameList devem sobreviver à conversão de nomes PDF como NMTOKENs XML. Um nome como /Bad#20Name se expande para um que contém um espaço, o que não é um token válido. A implementação faz uma verificação ASCII leve (letras, dígitos, ., -, _, :, além de bytes não ASCII) que detecta erros de espaço em branco e de delimitadores sem rejeitar nomes legítimos localizados ou específicos de fornecedores

Marcadores XMP: duas maneiras de gravar a mesma propriedade

A identificação de PDF/VT reside no XMP sob o namespace pdfvtid, especificamente GTS_PDFVTVersion e GTS_PDFVTModDate, ao lado dos padrão xmp:CreateDate e xmp:ModifyDate. Uma sutileza que causa falsos relatórios "ausentes" em leitores ingênuos é que qualquer um deles pode ser serializado de duas maneiras: como texto de elemento (<pdfvtid:GTS_PDFVTVersion>PDF/VT-1</pdfvtid:GTS_PDFVTVersion>) ou como um atributo RDF no elemento de descrição. O PDFiumPas lê ambas as formas, de modo que um arquivo gravado por outra ferramenta no estilo de atributo não é penalizado. Ele também impõe a regra de consistência do §6.3 de que GTS_PDFVTModDate deve ser igual a xmp:ModifyDate; uma incompatibilidade gera pvviModDateMismatch

Mais uma regra da mesma cláusula: um valor desconhecido de GTS_PDFVTVersion é preservado como pvcUnknown em vez de ser reduzido a pvcNone. Essa distinção é importante operacionalmente. pvcNone significa "nenhum marcador PDF/VT, um PDF comum", enquanto pvcUnknown significa "algo estampou uma versão que este validador não reconhece" (o caso PDF/VT-2s entre eles). Combinar os dois ocultaria um arquivo malformado dentro do mesmo balde de um documento comum

Onde a garantia termina

Vale a pena ser preciso sobre o limite do que esses métodos prometem, porque a conformidade da impressão de dados variáveis tem dinheiro real envolvido. As verificações DPart e de emparelhamento são validações estruturais no nível de bytes. Elas confirmam que o esqueleto de otimização, os marcadores base PDF/X-4, o OutputIntent e o XMP estão presentes e consistentes internamente. Elas não são um preflight do PDF/X-4 no nível do conteúdo: não verificam se cada cor está dentro da condição de saída declarada, se todas as fontes estão incorporadas ou se nenhum caso limite proibido de mesclagem de transparência passou. Para um trabalho que você está colocando em uma impressora de contrato, emparelhe a validação estrutural do PDFiumPas com um mecanismo de preflight PDF/X dedicado e uma impressão de teste, da mesma forma que você faria para verificar qualquer outra alegação de conformidade. A camada estrutural captura as falhas que silenciosamente quebram o cache do RIP; ela é metade de uma verificação completa, não o todo

Se você estiver incorporando essas verificações em uma porta de liberação mais ampla, a mesma abordagem de varredura no nível de bytes serve de base para os outros trabalhos de padrões da biblioteca, incluindo a validação de fluxos de objetos e de referência cruzada antes mesmo que um arquivo chegue ao preflight, e a disciplina de objetos compartilhados por trás de carimbos de página reutilizáveis com Form XObjects que torna um documento amigável ao RIP em primeiro lugar. As APIs de gravação e validação PDF/VT e PDF/X descritas aqui fazem parte do componente PDFium VCL para Delphi e C++Builder, cuja página de produto contém a referência de conformidade completa