O PDFium Component para Delphi valida documentos PDF/X prontos para impressão através do método TPdf.ValidatePdfX, que implementa a verificação da norma ISO 15930 em duas camadas: oito verificações de conteúdo ao nível dos bytes (proibição de compressão LZW, JavaScript, campos de formulário, referências OPI, ausência de TrimBox, chave Trapped não definida, entre outras) e uma passagem pelo modelo de objetos do PDFium que recorre a FPDFFont_GetIsEmbedded para verificar a incorporação de tipos de letra (fonts) em todos os objetos de texto de cada página. O resultado é um registo TPdfXValidationResult que indica o nível de conformidade detetado e lista cada violação sob a forma de uma enumeração (enum) tipada, permitindo que a sua aplicação Delphi informe o cliente exatamente por que razão um ficheiro será rejeitado na gráfica antes de se produzir qualquer chapa
Se já enviou um trabalho para uma gráfica comercial e este lhe foi devolvido com uma rejeição de apenas uma linha — \"sem TrimBox\", \"tipos de letra não incorporados\", \"Trapped não definido\" —, conhece o custo de uma descoberta tardia. O PDF/X constitui a contrapartida de pré-impressão do PDF/A: enquanto o PDF/A de arquivo garante que um documento é renderizado de forma idêntica daqui a décadas, o PDF/X assegura que o documento é separado, processado e cortado exatamente da mesma forma no RIP de terceiros amanhã de manhã. As duas normas partilham mecanismos (identificação XMP, OutputIntents, perfis ICC incorporados) mas respondem a questões distintas, razão pela qual o componente disponibiliza validadores separados para cada uma — a vertente PDF/A é abordada na validação preflight de PDF/A com o PDFium Component
O que é que a ISO 15930 realmente exige de um PDF pronto para impressão?
A ISO 15930 existe para viabilizar a troca cega (blind exchange): um designer entrega um ficheiro a uma gráfica com a qual nunca falou, e a gráfica consegue produzir a impressão correta sem necessidade de chamadas telefónicas, e-mails sobre tipos de letra em falta ou imagens associadas que tenham ficado esquecidas no portátil do designer. Todas as regras da norma servem esse propósito. Os tipos de letra devem ser incorporados porque não se pode assumir que o RIP recetor os possui. Referências externas são proibidas porque o ficheiro deve ser autónomo. Funcionalidades interativas são igualmente proibidas porque a tinta não possui um manipulador onclick
O PDFium Component reconhece três famílias de conformidade e reporta-as através do enum TPdfXConformance no resultado de validação: pxc1a para PDF/X-1a:2001 (ISO 15930-1, a base estrita de CMYK e cores diretas em PDF 1.3/1.4), pxc3 para PDF/X-3:2002 (ISO 15930-3, que admite RGB, Lab e cor gerida por ICC) e pxc4 para PDF/X-4:2010 (ISO 15930-7, que permite transparência ativa e camadas sobre uma base PDF 1.6). Um ficheiro que não contenha qualquer identificação PDF/X é devolvido como pxcNone, o que constitui em si uma resposta útil: o documento nunca alegou estar pronto para impressão, e tudo o resto que o validador reporta descreve o que seria necessário para lá chegar
As proibições fazem sentido quando pensamos como um fabricante de RIP. O filtro /LZWDecode é proibido em todas as variantes de PDF/X para evitar que um consumidor em conformidade dependa de um filtro com um historial complexo de compatibilidade e licenciamento; o Flate desempenha a mesma função sem esses inconvenientes. JavaScript, campos AcroForm e dicionários de ações adicionais /AA são proibidos porque um ficheiro de impressão deve ser uma descrição fixa de marcas no papel — qualquer elemento capaz de alterar o aspeto no momento de abertura quebra a garantia de que o que foi provado é o que será impresso. Elementos de substituição OPI (Open Prepress Interface) são proibidos porque consistem, por conceção, em referências a imagens de alta resolução armazenadas noutro local, e esse \"outro local\" é precisamente o que a troca cega proíbe
Por que razão as gráficas rejeitam PDFs sem TrimBox?
O TrimBox representa a página finalizada — o retângulo que sobra após o corte da guilhotina. O MediaBox, que todas as páginas de PDF contêm, representa meramente a folha física: inclui a margem de sangria (bleed), marcas de corte, alvos de registo e barras de cores. O software de imposição posiciona as páginas na folha de impressão através dos seus TrimBoxes; sem este elemento, o operador precisa de adivinhar onde termina o seu cartão de visita, e um palpite errado corta a sangria ou deixa uma linha branca num dos lados. É por isso que a ISO 15930 exige um TrimBox (or um ArtBox) em todas as páginas, e porque o ValidatePdfX aciona pvxiMissingTrimBox quando não é encontrada qualquer chave /TrimBox em alguma página do documento
A chave /Trapped responde a uma questão de produção distinta. O trapping é a técnica de pré-impressão que consiste em sobrepor ligeiramente cores adjacentes para que desvios mínimos de registo na prensa não criem espaços brancos entre elas. A gráfica precisa de saber se esse trabalho já foi realizado: aplicar trapping a um ficheiro já processado duplica as sobreposições, e ignorar o trapping num ficheiro sem processamento arrisca a criação de lacunas visíveis. Deste modo, o PDF/X exige que o dicionário Info declare explicitamente /Trapped /True ou /Trapped /False — a ausência da chave ou o valor /Unknown obriga a uma inspeção manual do ficheiro, que é exatamente o tipo de contacto que a troca cega pretendia evitar. O componente assinala isto como pvxiTrappedNotSet
Executar a validação em duas camadas com TPdf.ValidatePdfX
O método TPdf.ValidatePdfX não recebe argumentos e devolve um registo TPdfXValidationResult com três membros: Conformance (a variante PDF/X detetada), Issues (um conjunto Pascal de valores TPdfXValidationIssue) e o helper IsCompliant. Internamente, serializa o documento carregado num fluxo em memória, corre o inspetor ao nível dos bytes e depois percorre o modelo de objetos do PDFium para a validação de incorporação por tipo de letra. Um filtro preflight mínimo afigura-se da seguinte forma:
uses PDFium, FPdfPdfx;
procedure CheckPrintReadiness(const FileName: string);
var
Pdf: TPdf;
Res: TPdfXValidationResult;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := FileName;
Pdf.Active := True;
Res := Pdf.ValidatePdfX;
Writeln('Detected conformance: ',
Ord(Res.Conformance)); // pxc1a, pxc3, pxc4, pxcNone...
if Res.IsCompliant then
Writeln('PDF/X checks passed')
else
begin
if pvxiMissingTrimBox in Res.Issues then
Writeln('REJECT: no /TrimBox on the pages');
if pvxiTrappedNotSet in Res.Issues then
Writeln('REJECT: /Trapped missing or /Unknown');
if pvxiPdfiumFontNotEmbedded in Res.Issues then
Writeln('REJECT: a page uses a non-embedded font');
if pvxiLzwForbidden in Res.Issues then
Writeln('REJECT: LZWDecode filter present');
end;
finally
Pdf.Free;
end;
end;
Visto que a propriedade Issues consiste num conjunto Pascal comum, pode dividi-la conforme as necessidades do seu fluxo de trabalho — trate problemas estruturais como rejeições imediatas, considere a pvxiMissingTitle (uma recomendação na norma, e não uma obrigação) como um aviso e registe o resto. O mesmo tipo de registo também alimenta o gerador de relatórios do componente, pelo que se preferir emitir um documento legível por humanos em vez de tomar decisões com base em enums, o padrão descrito no artigo sobre a criação de um CLI de relatórios preflight em lote com o PDFium Component aplica-se integralmente ao PDF/X
O que a camada ao nível dos bytes deteta — e o que lhe escapa
A camada ao nível dos bytes consiste num varrimento de tokens sobre os bytes estruturais do documento com os corpos dos fluxos limpos, pelo que um JPEG que contenha acidentalmente o padrão de bytes /JavaScript não acionará um falso positivo. Para além das validações de marcadores (XMP pdfxid:GTS_PDFXVersion, OutputIntent com perfil ICC incorporado, trailer /ID, proibição de encriptação), a análise de conteúdo adiciona oito verificações, cada uma com o seu próprio valor enum:
pvxiLzwForbidden— surge um filtro/LZWDecodeem qualquer local do ficheiro (proibido em todas as variantes de PDF/X)pvxiJavaScriptForbidden— existe uma ação de/JavaScriptou uma árvore de nomes correspondentepvxiFormFieldsForbidden— existe um dicionário/AcroFormou uma entrada/XFApvxiAdditionalActions— está presente um dicionário de ações adicionais/AApvxiEmbeddedFilesForbidden— existe/EmbeddedFilesou uma anotação de anexo de ficheiro/FileAttachmentpvxiOpiForbidden— uma entrada/OPIou/Alternatesreferencia conteúdo de imagem substituívelpvxiMissingTrimBox— não foi encontrado/TrimBoxem nenhuma páginapvxiTrappedNotSet— a propriedade/Trappedestá ausente ou definida como/Unknown
A análise de bytes é rápida e não requer um motor de renderização, mas possui um ponto cego inerente com tipos de letra: a esse nível, o inspetor apenas consegue aplicar uma heurística aproximada — assinala um documento apenas quando não localiza nenhum programa de tipo de letra incorporado. Um ficheiro com nove tipos de letra incorporados e um do sistema misturado parece correto a uma verificação de bytes. Essa limitação isolada justifica a existência da segunda camada
Incorporação de tipos de letra através do modelo de objetos do PDFium
A camada do modelo de objetos do PDFium Component responde à questão dos tipos de letra com precisão. Após a passagem ao nível dos bytes, o método TPdf.ValidatePdfX percorre todas as páginas, obtém a lista de objetos com FPDFPage_CountObjects e, para cada objeto de texto, resolve o identificador do tipo de letra via FPDFTextObj_GetFont e consulta a propriedade FPDFFont_GetIsEmbedded. A existência de um único tipo de letra não incorporado no documento adiciona pvxiPdfiumFontNotEmbedded ao conjunto de problemas. O varrimento encurta o percurso em dois níveis — para de analisar objetos numa página e interrompe o carregamento de outras páginas assim que o problema é confirmado —, fazendo com que o veredicto de um catálogo de 300 páginas com violações surja frequentemente logo após a primeira página
Duas notas de limites que convém reter. Primeiro, esta camada requer a biblioteca PDFium carregada e depende de compilações que exportem a FPDFFont_GetIsEmbedded; se a exportação não estiver presente, a validação é ignorada em vez de falhar, pelo que uma DLL antiga nunca gerará rejeições falsas. Segundo, a verificação responde apenas se está \"incorporado ou não\" — não distingue a incorporação total de subconjuntos (subsetting), nem inspeciona a cobertura de glifos. Quando um ficheiro falha e necessita de saber qual o tipo de letra em qual página, as técnicas de enumeração no artigo sobre análise de propriedades de tipos de letra PDF com o PDFium no Delphi continuam precisamente a partir do ponto onde o booleano do validador termina
Validar fluxos sem carregar documentos — ou a DLL
O inspetor ao nível dos bytes também é exposto sob a forma de uma função autónoma, ValidatePdfXCompliance(Source: TStream) na unidade FPdfPdfx, consistindo em Object Pascal puro sem dependências da DLL do PDFium. Isto permite a sua implementação em locais onde um motor de renderização não é desejável: um validador de carregamentos leve num servidor web, uma tarefa de CI para validar ilustrações geradas ou um serviço no Lazarus numa plataforma onde prefira não distribuir binários nativos. Forneça-lhe qualquer fluxo compatível com reposicionamento (seekable stream):
uses Classes, FPdfPdfx;
function QuickPdfXGate(const FileName: string): Boolean;
var
Fs: TFileStream;
Res: TPdfXValidationResult;
begin
Fs := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
try
Res := ValidatePdfXCompliance(Fs);
Result := Res.IsCompliant and (Res.Conformance <> pxcNone);
finally
Fs.Free;
end;
end;
O compromisso é explícito: o percurso autónomo executa as validações de marcadores e todas as oito verificações de conteúdo, mas não a camada do PDFium por tipo de letra, pelo que o veredicto da fonte recai na heurística aproximada. Uma arquitetura lógica utiliza a ValidatePdfXCompliance como um primeiro filtro rápido e reserva o método completo TPdf.ValidatePdfX para ficheiros aprovados na primeira fase
Onde este validador termina e uma análise preflight completa se inicia
A honestidade é relevante na documentação de ferramentas preflight, pelo que aqui está o limite. O método ValidatePdfX valida marcadores de identificação, proibições estruturais, chaves de geometria de página, a declaração Trapped e a incorporação de tipos de letra até objetos de texto individuais. Não mede a cobertura total de tinta, não valida se todos os espaços de cores são legais para a variante declarada (a regra de conformidade do X-1a, por exemplo), não verifica a resolução das imagens face à retícula de impressão nem avalia o comportamento de sobreposição de impressão (overprint) e achatamento de transparências — estes aspetos requerem um motor preflight com gestão de cores, e a própria documentação da unidade sugere a associação a um deles para certificação final. O que a verificação em duas camadas fornece são os 80% de rejeições estruturais detetáveis antecipadamente, capturados em milissegundos no seu próprio código Delphi em vez de num e-mail da gráfica no dia seguinte
Ambas as camadas de validação, as APIs de inserção de marcadores PDF/X para geração de saídas em conformidade, e os validadores de PDF/A, PDF/UA, PDF/E e PDF/VT que partilham a mesma arquitetura são fornecidos no PDFium Component para Delphi e C++Builder — um único componente, desde a renderização até ao controlo de pré-impressão