Exporte um documento do Microsoft Word ou Excel com Salvar como PDF e o arquivo no disco é, na maioria das vezes, um arquivo de referência híbrida. Ele carrega suas informações de referência cruzada duas vezes: uma vez como a tabela clássica de largura fixa que terminava cada PDF até a versão 1.4, e uma vez como um fluxo de referência cruzada compactado do qual a maior parte do documento realmente depende. Uma única chave de trailer, /XRefStm, une as duas visualizações, e se uma ferramenta vê ou não o documento inteiro se resume a se ela segue essa chave
Este artigo examina os arquivos híbridos do lado do consumo: como são os bytes no final do arquivo, como as duas visualizações se distanciam durante a edição e como um pipeline Delphi pode detectar e rotear entradas híbridas. Como um carregador mescla as visualizações, e por que a ordem não é negociável, é o assunto do nosso artigo do HotPDF sobre o carregamento de arquivos de referência híbrida; este é sobre reconhecer o layout em primeiro lugar
Por que as exportações do Office gravam o índice duas vezes
O PDF 1.5 introduziu dois recursos que mudaram a forma do arquivo: fluxos de referência cruzada, que armazenam o índice de objetos como dados binários compactados em vez de uma tabela de texto sem formatação, e fluxos de objetos, que agrupam muitos pequenos objetos em um contêiner compactado por Flate. Um gravador que os usa produz arquivos menores, mas um leitor de PDF 1.4 não pode abrir o resultado, porque as estruturas nas quais ele se baseia, a palavra-chave xref e o dicionário trailer, sumiram
A ISO 32000-1 §7.5.8.4 define o meio-termo. Um arquivo de referência híbrida grava ambos: uma tabela de referência cruzada clássica endereçando os objetos que um leitor antigo deve alcançar, o catálogo e a árvore de páginas entre eles, e um fluxo de referência cruzada que indexa todo o resto. Os objetos dobrados em fluxos de objetos são marcados como livres na tabela clássica, então um leitor 1.4 os ignora sem reclamar; suas localizações reais existem apenas no fluxo. O trailer clássico então carrega uma chave /XRefStm mantendo o deslocamento de bytes desse fluxo. Um visualizador antigo nunca lê a chave e renderiza o arquivo da visualização de tabela. Um visualizador moderno a segue e vê o documento completo. O Word e o Excel emitem exatamente esse layout há anos, e é por isso que os arquivos híbridos não são um caso extremo exótico, mas uma grande parte do que os pipelines de negócios recebem
Como é o final de um arquivo híbrido
O layout é mais fácil de entender a partir dos bytes. Aqui está o final de um pequeno arquivo híbrido, com deslocamentos encurtados; em uma exportação real do Office, o valor de /XRefStm é normalmente um grande deslocamento próximo ao final do arquivo. A ordem de leitura é a caminhada de trás para frente descrita em nossa visão geral da estrutura de arquivo PDF: encontre %%EOF, leia startxref, pule para a tabela
% ... body objects, including object streams and, at byte 116,
% the cross-reference stream (a stream object with /Type /XRef) ...
xref % classic section: what startxref points at
0 4
0000000000 65535 f % slot 0: head of the free list, always present
0000000017 00000 n % object 1: the catalog, visible to any reader
0000000000 65535 f % object 2: marked free -- lives in an object stream
0000000000 65535 f % object 3: same; only the stream view locates it
trailer
<<
/Size 4
/Root 1 0 R
/XRefStm 116 % byte offset of the cross-reference stream
>>
startxref
7164 % byte offset of the 'xref' keyword above
%%EOF
Dois detalhes neste dump carregam todo o mecanismo. Primeiro, startxref aponta para a seção clássica, de propósito: esse é o endereço onde um leitor antigo deve chegar. O fluxo de referência cruzada é acessível apenas através da chave /XRefStm dentro do dicionário trailer, então um analisador que nunca procura por essa chave nunca aprende que o fluxo existe. Segundo, os objetos 2 e 3 são mentiras de um tipo benigno. A tabela clássica os declara livres, mas eles são objetos reais dentro de um contêiner compactado; a marcação livre é o que impede que um leitor 1.4 tropece em entradas que não pode usar. Um consumidor que confia apenas na visualização clássica conclui que a maior parte deste documento não existe
Como as duas visualizações se distanciam
Um arquivo híbrido recém-saído do Word é internamente consistente: ambas as visualizações descrevem o mesmo documento, cada uma dentro do seu escopo declarado. O problema começa quando o arquivo é editado por uma ferramenta que entende apenas uma das visualizações. Considere um utilitário de carimbo que anexa uma atualização incremental de estilo clássico: novos objetos, uma nova seção xref, uma cadeia /Prev para a seção anterior e um novo trailer. Se esse trailer omitir a chave /XRefStm, a visualização do fluxo fica órfã; se ele copiar o valor antigo, a visualização do fluxo ainda descreve o documento como ele era antes da edição. De qualquer forma, os dois índices agora discordam sobre o que o arquivo contém
O arquivo resultante tem uma assinatura de falha distinta: objetos visíveis em uma visualização estão ausentes ou desatualizados na outra. Um leitor que resolve através da visualização do fluxo encontra a versão pré-edição de um objeto atualizado, ou nenhuma entrada para um anexado. Um leitor na visualização da tabela vê a edição, mas perde a noção dos objetos compactados que apenas o fluxo localiza. Na prática, isso surge como campos de formulário que sobrevivem em um visualizador e desaparecem em outro, anotações que uma passagem de carimbo parece ter excluído ou buscas que chegam a um objeto totalmente errado
O que torna esses arquivos caros para depurar é que o Adobe Acrobat geralmente os abre sem reclamar: quando o índice discorda dos bytes, ele silenciosamente reconstrói os dados de referência cruzada procurando por cabeçalhos de objetos, de modo que quem produziu o arquivo quebrado não vê nada de errado. A falha surge mais tarde, quando o arquivo chega a um consumidor estrito, um validador preflight, um serviço de assinatura, um trabalho de ingestão de arquivamento, que confia na estrutura declarada e relata objetos ausentes ou uma incompatibilidade de referência cruzada. "Ele abre bem no Acrobat" é como quase todo ticket de dessincronização híbrida começa
Detectando um arquivo híbrido em Delphi puro
Classificar entradas não requer uma biblioteca de PDF. A chave /XRefStm só pode ocorrer dentro de um dicionário trailer clássico, e o trailer ativo fica nos últimos kilobytes do arquivo, porque a especificação exige que %%EOF apareça perto do final físico. Ler uma janela final delimitada e pesquisá-la é o suficiente para a triagem:
uses
System.SysUtils, System.Classes, System.StrUtils, System.Math;
function IsHybridReferencePdf(const FileName: string): Boolean;
const
TailWindow = 2048;
var
Stream: TFileStream;
Buf: TBytes;
Tail: string;
Len, TrailerPos, NextPos, KeyPos, StartXrefPos: Integer;
begin
Result := False;
Stream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
try
if Stream.Size < 48 then
Exit;
Len := Min(TailWindow, Integer(Stream.Size));
SetLength(Buf, Len);
Stream.Position := Stream.Size - Len;
Stream.ReadBuffer(Buf[0], Len);
finally
Stream.Free;
end;
// Every keyword involved is 7-bit ASCII, so a byte-wise decode is safe
Tail := TEncoding.ANSI.GetString(Buf);
// Find the LAST 'trailer' keyword: with incremental updates,
// the newest trailer is the one that governs the file
TrailerPos := 0;
NextPos := Pos('trailer', Tail);
while NextPos > 0 do
begin
TrailerPos := NextPos;
NextPos := PosEx('trailer', Tail, NextPos + 1);
end;
if TrailerPos = 0 then
Exit; // no classic trailer: a pure xref-stream file, not hybrid
// A hybrid trailer carries /XRefStm between 'trailer' and 'startxref'
KeyPos := PosEx('/XRefStm', Tail, TrailerPos);
StartXrefPos := PosEx('startxref', Tail, TrailerPos);
Result := (KeyPos > 0) and
((StartXrefPos = 0) or (KeyPos < StartXrefPos));
end;
Os três resultados se alinham com os três layouts. Um arquivo apenas clássico tem um trailer, mas nenhum /XRefStm: Falso. Um arquivo que se compromete totalmente com fluxos de referência cruzada não tem a palavra-chave trailer, suas chaves de trailer vivem no dicionário do fluxo: também Falso, corretamente, porque tal arquivo é compactado, não híbrido. Apenas o layout de índice duplo retorna Verdadeiro
Para uso em produção, dois endurecimentos valem as linhas extras. Analise o inteiro após /XRefStm, busque esse deslocamento e confirme se um objeto de fluxo com /Type /XRef realmente está lá; um arquivo truncado pode carregar a chave enquanto o fluxo sumiu, o que pertence a um balde diferente de um híbrido saudável. E trate o tamanho da janela como um parâmetro: 2 KB cobrem a saída comum do Office, mas um dicionário de trailer incomumente grande pode empurrar a palavra-chave para fora do alcance, e ampliar a janela é melhor do que declarar o arquivo clássico por acidente
Roteando arquivos híbridos por meio de um pipeline Delphi
A detecção compra uma decisão de roteamento. Para arquivos que são apenas lidos, renderizados ou validados, use um carregador que resolva ambas as visualizações e verifique o comportamento em vez dos bytes. O componente PDFium analisa a cadeia /XRefStm durante o carregamento, portanto a tabela de objetos que seu código vê é a mesclada, e as verificações descritas em nosso artigo sobre validação de objetos e fluxos de referência cruzada se aplicam inalteradas. Se um híbrido dessincronizado estiver danificado o suficiente para recusar o carregamento, o motor o relata através de seu conjunto de erros, FPDF_ERR_SUCCESS, FPDF_ERR_UNKNOWN, FPDF_ERR_FILE, FPDF_ERR_FORMAT, FPDF_ERR_PASSWORD, FPDF_ERR_SECURITY e FPDF_ERR_PAGE, sendo FPDF_ERR_FORMAT aquele produzido por danos estruturais. No entanto, não se apoie nesse sinal: o PDFium é tolerante por design e reconstrói arquivos mais inconsistentes silenciosamente, portanto, um carregamento bem-sucedido prova que o arquivo era recuperável, não que suas duas visualizações concordam. A verificação de consistência significativa é comparar o que uma caminhada completa pelo objeto encontra com o que o /Size do trailer declara
Para arquivos que seu pipeline modifica, a política mais segura é impedi-los de serem híbridos de forma alguma. Um carregamento seguido de um salvamento completo pelo HotPDF reescreve o documento com uma única referência cruzada autoconsistente em um formato: sem /XRefStm, sem segunda visualização para perder a sincronia, cada objeto de propriedade de exatamente uma entrada de índice. Essa normalização é o que você quer antes da ingestão em arquivo, antes de um RIP ou serviço de assinatura de downstream estrito, e depois de qualquer edição aplicada a uma entrada híbrida. Funciona porque o carregador mesclou as visualizações corretamente na entrada, o mecanismo que o artigo de referência híbrida do HotPDF detalha
A única classe de arquivos a ser deixada de lado são os documentos assinados digitalmente. Uma reescrita completa move cada byte, o que invalida qualquer assinatura calculada sobre os intervalos originais. Uma alteração em um híbrido assinado deve entrar como uma atualização incremental adequada que mantenha as duas visualizações; um arquivo que precisa apenas de leitura deve passar intacto. A normalização é para arquivos que você possui; arquivos assinados você apenas anexa a eles
Os PDFs de referência híbrida não são malformados; eles são a ponte de compatibilidade do próprio formato, e os aplicativos do Office continuarão a produzi-los enquanto os leitores PDF 1.4 sobreviverem na base de instalação. Um pipeline que pode detectar a chave /XRefStm, validar o documento mesclado com o PDFium Component e regenerar uma saída limpa de índice único com o HotPDF Component os trata como eles são: entradas comuns com um sinalizador extra no trailer