O PDFiumPas, o wrapper Delphi e C++Builder em torno do motor PDFium do Google, salva um documento em uma versão PDF exata de 1.3 a 1.7 por meio do parâmetro PdfVersion do método TPdf.SaveAs. A própria chamada FPDF_SaveWithVersion do PDFium só reescreve o cabeçalho %PDF-M.m, sem verificar se o conteúdo real do documento é legal naquela versão. O PDFiumPas fecha essa lacuna com uma passada de conformidade pós-salvamento que percorre a cadeia de revisão de referência cruzada ativa e verifica declarações de Adobe Extension Level antes de o arquivo sair do método
Essa distinção importa mais em produção gráfica, onde um perfil PDF/X nomeia uma versão PDF exata e uma ferramenta de preflight ou um RIP rejeita qualquer coisa que silenciosamente discorde de seu próprio cabeçalho, um cenário coberto do lado da saída em validando documentos PDF/X prontos para impressão com o PDFiumPas. SaveAs expõe o alvo como o enum TPdfVersion, pv13 a pv17 ao lado dos valores mais antigos pv10 a pv12, mais um TSaveOption independente para reescritas incrementais ou completas. Passe PdfVersion e o PDFiumPas faz duas tarefas em uma chamada: pede ao PDFium para estampar o cabeçalho solicitado, depois relê os bytes recém-escritos e se recusa a devolver um arquivo cujo conteúdo ativo não pode existir legalmente naquela versão
var
Pdf: TPdf;
begin
Pdf:= TPdf.Create(nil);
try
Pdf.FileName:= 'source.pdf';
Pdf.Active:= True;
try
Pdf.SaveAs('press-ready.pdf', saNoIncremental, pv17);
except
on E: Exception do
// E.Message names the offending feature and the version or
// extension level it actually needs, for example:
// "RichMedia annotations and RichMediaExecute actions require
// /Extensions /ADBE with /BaseVersion /1.7 and /ExtensionLevel 3
// or newer."
raise;
end;
finally
Pdf.Free;
end;
end;
Por que a última definição de objeto no arquivo é a coisa errada para confiar?
O último objeto físico com um determinado número em um arquivo PDF não é necessariamente o objeto que um leitor em conformidade resolveria para esse número hoje. Um PDF que passou por várias atualizações incrementais não tem um único grafo de objetos, tem um histórico deles em camadas dentro de um único arquivo, e cada ciclo de anexação pode liberar um objeto, redefini-lo sob um novo número de geração, ou deixar seu corpo físico antigo sentado entre dois marcadores endobj sem nenhuma entrada de referência cruzada apontando mais para ele
O PDFiumPas caiu exatamente nesse modo de falha antes de rastrear revisões xref explicitamente: uma anotação Redact órfã por uma reescrita posterior de objeto de página, ou um dicionário /MarkInfo deixado fisicamente presente sem nenhuma entrada xref apontando para ele, ainda podia aparecer em uma varredura de bytes e ainda disparar uma verificação de recurso de versão que não se aplicava mais ao documento que um leitor de fato abriria. A direção da falha era rejeição falsa, não aceitação falsa: um arquivo que genuinamente havia superado um recurso em sua revisão atual ainda podia ser bloqueado de salvar em uma versão mais baixa por causa de conteúdo que ninguém mais conseguia alcançar
Como o PDFiumPas determina quais definições de objeto estão de fato ativas?
O PDFiumPas resolve o conjunto de objetos ativo da mesma forma que um leitor em conformidade faz, percorrendo a cadeia de referência cruzada em vez de varrer bytes em busca de cabeçalhos de objeto. O resolvedor começa no último deslocamento startxref do arquivo e segue cada link /Prev para trás através de revisões mais antigas, analisando tabelas de referência cruzada clássicas, streams híbridos vinculados a /XRefStm, e streams de referência cruzada puros ao longo do caminho. A varredura roda do mais novo para o mais antigo e resolve cada número de objeto na primeira vez que é visto, de modo que uma entrada livre em uma revisão posterior corretamente ofusca um corpo de objeto escrito em uma anterior, e uma redefinição sob um novo deslocamento ou geração sempre vence o que substituiu
Membros de object stream recebem uma verificação extra que uma simples busca de deslocamento não consegue fornecer sozinha, um mecanismo coberto com mais profundidade em validando streams de objeto e referência cruzada com o PDFiumPas. Um objeto comprimido recuperado de um /ObjStm precisa ter seu stream pai confirmado ativo na mesma varredura, e seu índice precisa concordar com a própria posição do membro dentro do cabeçalho desse stream antes de o PDFiumPas o tratar como conteúdo vivo. A ISO 32000-1 seção 7.5.8.4 até descreve um caso de referência híbrida onde uma tabela de compatibilidade clássica marca um objeto como livre enquanto a entrada /XRefStm do trailer simultaneamente define esse mesmo objeto como um membro comprimido em outro lugar; o PDFiumPas mescla o stream xref suplementar na mesma revisão antes de as entradas clássicas serem aplicadas, de modo que a definição comprimida vence da forma que a especificação pretende
Adobe Extension Levels: o portão acima do número de versão
Um cabeçalho %PDF-1.7 só promete o conjunto de recursos que a ISO 32000-1 padronizou em 2008, enquanto vários recursos com os quais produtores de PDF contam hoje foram lançados depois como suplementos exclusivos da Adobe sobrepostos ao mesmo número de versão. A Adobe registrou cada suplemento como um par BaseVersion e ExtensionLevel registrado no dicionário /Extensions do catálogo do documento sob um prefixo de desenvolvedor, ADBE para as próprias extensões da Adobe, de modo que um leitor consiga distinguir um arquivo PDF 1.7 simples de um que também implementa um nível de extensão numerado. Salvar em pv17 sem essa declaração não é um erro por si só; só se torna um no instante em que o conteúdo ativo de fato depende de um recurso que a declaração deveria cobrir
Quais recursos de versão alta disparam o portão de versão explícita?
O PDFiumPas verifica uma lista específica, orientada por especificação, em vez de adivinhar a partir do número de versão sozinho. Dicionários de imagem carregando uma entrada explícita /SMaskInData ou um valor /BitsPerComponent de 16 ambos exigem PDF 1.5, com o caso de dezesseis bits seguindo diretamente as regras de componente de imagem da PDF Reference 1.5 seção 4.8. Anotações RichMedia e ações RichMediaExecute exigem /BaseVersion /1.7 com /ExtensionLevel 3 ou superior. Streams 3D PRC, identificados por um dicionário carregando tanto /Type /3D quanto /Subtype /PRC, exigem a mesma versão base, mas apenas /ExtensionLevel 1. Dicionários Measure geoespaciais e anotações Projection exigem /BaseVersion /1.7 com /ExtensionLevel 3, o mesmo suplemento Adobe do qual RichMedia depende
A verificação geoespacial carrega um detalhe de leitura de especificação que vale a pena conhecer se você algum dia construir sua própria lógica com portão de versão sobre o PDFiumPas. A Tabela 254 da ISO 32000-1 marca a entrada /Type do dicionário Measure como opcional, observando apenas que "se presente, deve ser Measure", enquanto a Tabela 311 torna /Type obrigatório para o dicionário de stream 3D onde o conteúdo PRC vive. Saída GeoPDF do mundo real, de ferramentas de mapeamento, rotineiramente omite /Type no dicionário Measure e escreve apenas /Subtype /GEO, então o detector geoespacial do PDFiumPas casa apenas com /Subtype, em vez de exigir as duas chaves como seu detector de 3D PRC pode fazer com segurança. Exigir /Type em ambos os dicionários teria deixado conteúdo GeoPDF em conformidade escapar do portão sem detecção, pousando em um arquivo PDF 1.7 simples sem nenhuma declaração de nível de extensão para respaldá-lo
O PDFiumPas rebaixa automaticamente recursos não suportados?
Não como uma capacidade geral, e assumir o contrário é o erro a evitar aqui. SaveAs canaliza a versão alvo por meio de uma rotina interna, ValidatePdfVersionCompliance, e quando essa rotina encontra um recurso que a versão alvo ou sua declaração de nível de extensão não consegue suportar, SaveAs levanta uma exceção carregando o texto de erro da rotina, em vez de escrever o arquivo; quem chama recebe de volta um motivo preciso, nomeado por recurso, nunca um documento silenciosamente reescrito. O único lugar onde o PDFiumPas de fato reescreve conteúdo automaticamente é um alvo PDF 1.3, onde ele remove os padrões de transparência semanticamente neutros /BM /Normal, /CA 1 e /ca 1 que o PDFium sempre escreve em dicionários ExtGState independentemente da versão alvo, porque esses valores específicos não carregam nenhum significado visual e o PDF 1.3 antecede as chaves por completo
// PDF 1.3 targets rewrite the saved bytes to strip transparency
// defaults PDFium always emits, so incremental mode cannot apply
Pdf.SaveAs('legacy-archive.pdf', saIncremental, pv13);
// raises: PDF 1.3 normalization is incompatible with incremental
// save mode
Transparência genuína não padrão e máscaras suaves de imagem ainda falham completamente em um alvo PDF 1.3, porque removê-las mudaria como a página de fato parece, e o PDFiumPas não vai tomar essa decisão em seu nome. Dois limites relacionados valem a pena planejar antes de uma versão exata entrar em um pipeline em lote. A saída de versão explícita nunca carrega um dicionário /Encrypt; o salvamento falha imediatamente se a origem estiver protegida, o que por acaso se alinha com perfis PDF/X e PDF/A que proíbem criptografia de qualquer forma, mas significa que a descriptografia é uma etapa separada em seu fluxo de trabalho, em vez de algo que SaveAs faz por você. O PDFiumPas também não tem nenhum método público para escrever uma declaração /Extensions /ADBE em um catálogo, de modo que um arquivo de origem que contenha RichMedia, 3D PRC, ou conteúdo geoespacial, mas careça dessa declaração, não vai passar pelo portão não importa qual PdfVersion você solicite; a declaração precisa já existir na origem, tipicamente porque a ferramenta de autoria a escreveu, ou o recurso precisa ser removido antes do salvamento. A propriedade somente leitura TPdf.PdfVersion vale a pena verificar antes de sequer tentar um salvamento de versão exata, já que resolve a mesma versão efetiva com conhecimento de catálogo, cabeçalho ou substituição /Version, o que quer que seja atual, na qual o próprio validador de tempo de salvamento se apoia
Pdf.FileName:= 'incoming.pdf';
Pdf.Active:= True;
// PdfVersion resolves the same catalog-aware effective version the
// save-time validator uses, so a mismatch here is worth investigating
// before spending a full SaveAs attempt on it
LogSourceVersion('incoming.pdf', Pdf.PdfVersion);
Trate uma exceção de SaveAs em um alvo de versão exata como um relatório de preflight, e não como um bug: a mensagem nomeia a cláusula exata que o documento de origem está violando, que é precisamente a informação de que uma gráfica ou pipeline de arquivamento precisa antes de o arquivo ir mais adiante. O caminho de salvamento de versão explícita, o resolvedor de revisão xref ativa e as verificações de Adobe Extension Level descritas aqui vêm como parte do Componente PDFiumPas padrão para Delphi e C++Builder; a página do produto traz a referência completa de TPdf.SaveAs, ao lado do restante da API de conformidade e formulários