Artigo Técnico

Impressão de dados variáveis PDF/VT no Delphi com PDFium VCL

Uma gráfica de impressão transaccional devolve a sua tiragem de 80 000 páginas com uma rejeição de uma linha: "não é PDF/VT, o RIP não consegue fazer cache". O ficheiro abre sem problemas em todos os visualizadores da sua mesa, as cores estão certas, os dados foram mesclados correctamente. Nada disso é o que a prensa digital pediu. A impressão de dados variáveis de alta velocidade vive ou morre com a capacidade da prensa reconhecer que o bloco do logótipo do cliente na página 1 é, byte por byte, o mesmo objecto da página 40 000, o renderizar uma vez e reutilizá-lo. O PDF/VT é o standard que torna essa promessa verificável por máquina, e o verdadeiro engano está em parecer correcto, porque a estrutura que o RIP lê é invisível no ecrã

PDFiumPas expõe essa estrutura através de uma superfície pequena sobre TPdf: SaveAsPdfVT escreve-a, ValidatePdfVT verifica-a. Este artigo explica o que essas duas methods colocam realmente em disco e inspeccionam, onde a ISO 16612-2 é mais exigente do que parece à primeira vista, e quais as partes que são âncoras estruturais honestas em vez de um preflight completo que pudesse facturar a um cliente

O que o PDF/VT padroniza, e porque o PDF/X vem primeiro

O PDF/VT (ISO 16612-2:2010) não é um formato de ficheiro novo. É uma camada de metadados de optimização acoplada a um ficheiro PDF/X, e essa ordem é estruturalmente importante. O standard define três níveis de conformidade, mas apenas dois deles nomeiam um ficheiro PDF: PDF/VT-1, um documento único e autónomo, e PDF/VT-2, um modelo de conjunto de ficheiros em que as páginas referem recursos externos partilhados. O terceiro token que poderá ver, pvc1, não é de todo um valor ao nível do ficheiro; vive num cabeçalho de stream MIME descrito no Anexo A. Se encontrar código a gravar pvc1 no XMP de um documento, esse código está errado

A regra inegociável para um ficheiro único é a base PDF/X. A ISO 16612-2 §6.2.1 exige que cada ficheiro PDF/VT-1 seja também um ficheiro PDF/X-4 válido. O conjunto de ficheiros de PDF/VT-2, pela §6.2.2, tem antes de assentar em PDF/X-4p, PDF/X-5g ou PDF/X-5pg. É por isso que um writer de PDF/VT não se pode limitar a acrescentar umas chaves identificadoras: tem de carregar consigo todo o conjunto de marcadores PDF/X-4, o que significa um OutputIntent, um perfil ICC de destino incorporado, as entradas XMP e Info do documento que combinam, um trailer /ID e nenhuma encriptação. Falhe qualquer uma dessas peças e fica com um ficheiro que se diz PDF/VT mas falha no momento em que um consumidor conforme verifica a base

Escrever um ficheiro com SaveAsPdfVT

A chamada mínima não precisa de mais nada além de um documento activo, porque TPdfVTSaveOptions.Default fornece um perfil ICC sRGB incorporado e conformidade pvc1. A gravação corre internamente em três passos: remove qualquer segurança, estabelece a correspondência entre o dicionário Info existente e o trailer /ID para que os valores XMP e Info coincidam, e depois acrescenta os objectos PDF/X-4 e PDF/VT através de uma actualizaçã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;

Em produção real, quase sempre vai querer substituir o OutputIntent pela caracterização da sua gráfica, e não pelo fallback genérico sRGB. Forneça os bytes ICC e os identificadores de condição através 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 excerto é uma protecção deliberada, não uma limitação com que possa discutir. Definir Opt.Conformance := pvc2 não produz um ficheiro PDF/VT-2. O writer normaliza qualquer pedido que não seja pvc1 de volta para pvc1, porque PDF/VT-2 é um formato de conjunto de ficheiros e um writer de ficheiro único que acrescenta um documento de saída fisicamente não consegue montar o conjunto de recursos externos que a §6.2.2 exige. O valor pvc2 existe para o caminho de leitura, para que ValidatePdfVT possa reconhecer e reportar um documento de conjunto de ficheiros já existente; não é um alvo de escrita

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

O coração do PDF/VT é a hierarquia Document Part (DPart). É ela que permite a uma prensa dividir uma tiragem longa em registos, agrupar registos por destinatário ou mala postal e anexar Document Part Metadata para que o equipamento a jusante possa encaminhar e facturar cada peça. A ISO 16612-2 §6.5 define a cablagem: o catálogo transporta uma /DPartRoot, o nó raiz DPart transporta /DPart e uma /DPartNameList a nomear cada nível da hierarquia, os DParts folha cobrem intervalos da árvore de páginas, e cada página que pertence a uma parte aponta de volta para a sua folha através de uma entrada /DPart ao nível da página

Quando o seu documento de origem já contém uma hierarquia utilizável, SaveAsPdfVT preserva-a. Quando não contém, o writer sintetiza uma hierarquia mínima: um único DPart ao nível do documento que cobre a árvore de páginas corrente por ordem, com uma /DPart de referência de volta acrescentada a cada objecto de página vivo e uma lista /DPartNameList de um só nível. Seja honesto consigo próprio sobre o que essa árvore mínima é. Ela é uma âncora estrutural que cumpre os requisitos de forma da §6.5; não é metadados de negócio. Não consegue inventar destinatários, fronteiras de peça postal ou lotes de produto, porque essa informação nunca existiu no documento de origem

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

ValidatePdfVT devolve um registo TPdfVTValidationResult com três coisas: a conformidade detectada, um conjunto de problemas e um auxiliar IsCompliant que só é verdadeiro quando a conformidade é um nível real e o conjunto de problemas está vazio. É uma API de inspeção, não apenas de presença de chaves

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 vale a pena compreender em profundidade são o emparelhamento de conformidade e a caminhada DPart, porque ambas eram demasiado permissivas e foram apertadas para seguir a especificação. No emparelhamento, o validador faz correspondência exacta entre o valor de Conformance detectado e a estrutura XMP e do documento. Não basta uma marca solta de PDF/VT algures no ficheiro; a camada PDF/X tem de concordar com ela

A caminhada DPart é onde reside a maior parte do rigor. Não chega que o catálogo tenha uma chave /DPartRoot, porque um objecto vazio forjado ou um sem ligações de página continua a não poder ser consumido. HasValidDPartHierarchy e o conjunto de problemas associado verificam a estrutura alcançável, os vínculos de página e a consistência interna da árvore

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

Passagens sucessivas pela Table 4 da §6.5 revelaram formas que versões anteriores aceitavam, mas que o standard não aceita. São precisamente o tipo de coisa que uma árvore DPart montada à mão costuma errar, por isso vale a pena chamá-las pelo nome:

  • /DParts é um array de arrays, não um array plano. Cada elemento do array exterior tem de ser, ele próprio, um array de referências indirectas. Um /DParts [9 0 R] plano é rejeitado; a forma conforme é /DParts [[9 0 R] [10 0 R]]
  • Cada /DPart folha tem de possuir /P e /Type. O elemento de página e o tipo não são opcionais quando a folha representa conteúdo concreto
  • As hierarquias não podem saltar níveis nem misturar tipos incoerentes ao longo da mesma cadeia. Se um nó diz que representa um nível intermédio, os seus filhos e o seu /DPartNameList têm de reflectir isso

Marcadores XMP: duas formas de escrever a mesma propriedade

A identificação PDF/VT vive em XMP sob o namespace pdfvtid, concretamente GTS_PDFVTVersion e GTS_PDFVTModDate, juntamente com o standard xmp:CreateDate e xmp:ModifyDate. Um pormenor que causa falsos relatórios de «falta» é que o valor pvc1 deve aparecer no pacote XMP e também ecoar no documento e no trailer /ID; o validador trata-os como um par, não como campos independentes

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 importa operacionalmente. pvcNone significa «não existe qualquer marcador PDF/VT»; pvcUnknown significa «há um marcador, mas o valor não está na enumeração que esta versão conhece». Uma ferramenta de produção precisa de distinguir ausência de um marcador de presença de um valor inesperado

Onde a garantia termina

Vale a pena ser preciso sobre a fronteira do que estas methods prometem, porque a conformidade de impressão de dados variáveis tem dinheiro real em jogo. As verificações de DPart e de emparelhamento são validação estrutural ao nível dos bytes. Comprovam que um ficheiro afirma correctamente a sua conformidade e que a hierarquia necessária está lá, mas não garantem que o conteúdo empresarial do documento seja sensato

Se estiver a integrar estas verificações num gate de release mais amplo, a mesma abordagem de varrimento ao nível dos bytes suporta o resto do trabalho de standards da biblioteca, incluindo a validação de object streams e cross-reference streams antes de um ficheiro chegar a um consumidor mais exigente