O PDFium Component produz PDFs Fast Web View (linearizados) a partir do Delphi através do TPdf.SaveAsLinearized. O método salva o documento atual com o PDFium, e depois reescreve o resultado de acordo com o Anexo F da ISO 32000-1: os objetos da primeira página vão para a frente, duas seções de cross-reference são escritas, e as hint tables de offset de página e objetos compartilhados dizem ao viewer quais intervalos de bytes buscar para qualquer página
O motivo de alguém se importar é a rede. Um catálogo de 40 MB servido de um object storage ou um portal de documentos deveria mostrar a página um depois dos primeiros kilobytes, e pular para a página 380 deveria custar alguns requests de HTTP Range, não um download completo. Viewers só conseguem fazer isso quando o arquivo está linearizado. A maioria dos PDFs que saem de um pipeline de edição não está, porque o jeito natural de escrever um PDF põe a tabela de cross-reference no fim e espalha os objetos de página onde quer que o escritor tenha emitido
Por que o PDFium lê arquivos linearizados mas não os escreve?
O PDFium tem um leitor completo para linearização e nenhum escritor para ela. A API de disponibilidade FPDFAvail_* detecta o dicionário de linearização, decodifica hint tables e pede ao host exatamente os intervalos de bytes de que precisa, que é a base do workflow de download progressivo e cancelamento no PDFium Component. A ABI pública de salvamento, por outro lado, só oferece um salvamento de cópia simples e um salvamento com versão de PDF alvo. Nada nela reordena objetos ou emite hint streams
O PDFium Component fecha essa lacuna sem reimplementar o que o PDFium já faz bem. O TPdf.SaveAsLinearized primeiro chama o SaveAs existente, então o PDFium continua dono do salvamento semântico: páginas editadas, estado de campos de formulário e tratamento de versão passam todos pelo caminho de código exercitado há anos. O componente então roda uma passada de reestruturação do Anexo F sobre esse PDF intermediário estável e escreve o resultado linearizado no seu alvo. Dividir o trabalho assim significa que a linearização nunca precisa entender uma anotação ou um formulário, apenas objetos, referências e offsets de bytes
Chamando TPdf.SaveAsLinearized a partir do Delphi
O TPdf.SaveAsLinearized tem dois overloads, um para um TStream e um para um nome de arquivo, e ambos recebem os mesmos parâmetros opcionais Option: TSaveOption e PdfVersion: TPdfVersion que o SaveAs. Ambos retornam True em caso de sucesso. O overload de arquivo escreve em um arquivo temporário no diretório alvo e substitui o destino atomicamente, então um salvamento que falha nunca deixa um PDF pela metade onde havia um bom
var
Pdf: TPdf;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'catalog-2026.pdf';
Pdf.Active := True;
// ... edite páginas, preencha formulários, carimbe, o que o pipeline fizer ...
if not Pdf.SaveAsLinearized('catalog-2026-web.pdf', saNoIncremental, pv17) then
raise Exception.Create('Linearized save failed');
finally
Pdf.Free;
end;
end;
As regras de parâmetros são estritas de propósito. saIncremental levanta um erro, porque um update incremental anexa uma nova revisão ao fim do arquivo e destruiria imediatamente o layout carregado na frente. Uma versão alvo abaixo do PDF 1.2 também é rejeitada; pvUnknown mantém a versão de origem. Entrada criptografada precisa de saRemoveSecurity, já que o escritor renumera objetos e as chaves de criptografia em nível de objeto do PDF derivam do número do objeto. E como a linearização é uma reescrita completa, qualquer assinatura digital existente é invalidada. Se você precisa entregar assinado e linearizado, linearize primeiro e assine depois
Como o escritor agrupa objetos para Fast Web View
Linearização não é só mover a página um para o topo do arquivo. O escritor faz o parse da revisão atual, seja o seu cross-reference uma tabela clássica, um arquivo híbrido ou um cross-reference stream, e expande todo membro de um object stream ativo em um objeto autônomo. Ele então constrói um grafo de referências e classifica os objetos em cinco grupos: objetos de nível de documento necessários para abrir o arquivo, objetos da primeira página, objetos privados de uma página posterior, objetos compartilhados por várias páginas posteriores, e todo o resto
A armadilha nessa caminhada pelo grafo é o /Parent. Todo dicionário de página aponta para cima na árvore de páginas, e uma travessia ingênua segue esse ponteiro até o nó pai, e depois desce pelos /Kids para cada página irmã e os seus content streams. Um passo depois, o grupo da primeira página contém o documento inteiro, e o arquivo é tecnicamente linearizado mas inútil para acesso range-first. O PDFium Component ignora o link /Parent da página e os nós da árvore de páginas fora da página sendo coletada, que é o que mantém a seção da primeira página pequena. Em uma amostra de regressão de 51 páginas com uma árvore de páginas funda, a seção da primeira página é de 5289 bytes, 0.48% da saída de 1105354 bytes
Object streams e cross-reference streams na entrada são achatados em objetos indiretos comuns de generation 0, toda referência é reescrita para os novos números, e a saída usa seções de cross-reference clássicas. Isso custa um pouco de tamanho de arquivo comparado com object streams comprimidos, mas simplifica a cobertura das hint tables e é o layout que todo viewer ciente de linearização trata. Se compacidade importa mais do que Fast Web View para uma dada saída, o PDFium Component tem um escritor de object streams separado para esse trabalho
O que entra no dicionário de linearização e nas duas seções xref?
Um arquivo linearizado escrito pelo PDFium Component carrega duas seções de cross-reference clássicas e um dicionário de linearização no próprio primeiro objeto. A primeira seção xref vem depois do dicionário de linearização e cobre os objetos de nível de documento e da primeira página, então um viewer consegue renderizar a página um só pela frente do arquivo. A seção xref principal fica no fim e cobre todo o resto
O dicionário de linearização em si guarda os números que um viewer precisa antes de ler qualquer outra coisa: /L o comprimento do arquivo, /H o offset e o comprimento do hint stream, /O o número do objeto da primeira página, /E o fim da seção da primeira página, /N a contagem de páginas, e /T o offset da primeira entrada na tabela xref principal. Todos dependem do layout final, e o dicionário fica antes do layout. Escrevê-lo com números de largura natural cria um loop: um comprimento que cresce de 999999 para 1000000 adiciona um byte, desloca todo offset seguinte, o que pode mudar a largura de outro número, e assim por diante
O PDFium Component quebra o loop reservando 320 bytes fixos para o dicionário de linearização. Todo objeto, o hint stream, as duas seções xref e os trailers são escritos primeiro; uma vez que todos os offsets estão finais, o dicionário é preenchido no lugar e completado com padding até a largura reservada. Um detalhe sutil é o /T: ele precisa apontar para a entrada do objeto 0 da tabela xref principal, não para a palavra-chave xref que a precede. Validadores estritos sinalizam o offset da palavra-chave mesmo que viewers tolerantes abram o arquivo de qualquer jeito
Por que os offsets das hint tables ignoram o hint stream?
Os offsets das hint tables em um PDF linearizado são interpretados como se o hint stream não existisse no arquivo. O Anexo F define assim para que o hint stream possa ficar entre a seção da primeira página e o resto sem que o seu próprio tamanho retroalimente os números que ele contém. A consequência prática para um escritor é fácil de errar: todo offset real que fica depois do hint stream precisa ser reduzido antes de entrar na hint table de offset de página ou de objetos compartilhados
A pergunta é: reduzido por quanto. A resposta tentadora é o comprimento dos dados do stream, porque essa é a parte que parece o conteúdo da hint. A resposta correta é o comprimento do objeto indireto da hint inteiro, incluindo N 0 obj, o dicionário do stream, as palavras-chave stream e endstream, e o endobj. Subtraia só o comprimento dos dados e todo offset posterior fica errado em algumas dezenas de bytes. A página um ainda renderiza porque vive antes do hint stream, então o bug sobrevive a uma checagem visual rápida e só aparece quando um viewer pula para uma página posterior e pede um range que começa no meio do objeto errado
Ambas as hint tables são empacotadas bit a bit com o bit mais significativo primeiro, de novo conforme o Anexo F. O componente verifica o resultado do jeito que um cliente real faria: um armazenamento de ranges esparso fornece só os primeiros 1 KiB da amostra de 51 páginas, e o tratamento de hints nativo do PDFium pede o resto. A última página abre sem o arquivo completo nunca estar presente
Verificando um PDF linearizado em Delphi
A verificação mais barata é perguntar ao próprio PDFium. O TPdf.InspectAvailability roda a API de disponibilidade contra um stream sem carregar o documento, e o campo Linearization dele reporta plnLinearized, plnNotLinearized ou plnUnknown
function IsFastWebView(const FileName: string): Boolean;
var
Stream: TFileStream;
Info: TPdfAvailability;
begin
Stream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
try
Info := TPdf.InspectAvailability(Stream);
Result := Info.Linearization = plnLinearized;
finally
Stream.Free;
end;
end;
Isso confirma que o PDFium reconhece o layout; não prova que as hints estão corretas. Para um teste real de ponta a ponta, sirva o arquivo por um transporte limitado por ranges e abra uma página tardia com o TPdfProgressiveDocument, do mesmo jeito que o guia de streaming de PDFs grandes sob demanda com PDFium VCL faz. Se a página renderiza depois de buscar só uma pequena fração do arquivo, as hint tables estão fazendo o trabalho delas. Adicionar um segundo verificador de linearização independente ao pipeline de release também vale a pena, porque viewers diferem em quão tolerantes são quanto a um /T deslocado ou um offset levemente errado
Dois limites para ter em mente. O materializador de objetos ainda rejeita arrays de 2 GiB ou mais, então documentos extremamente grandes precisam de uma estratégia diferente por enquanto. E a linearização só compensa quando o servidor honra requests de HTTP Range; atrás de um proxy que remove Accept-Ranges, um arquivo linearizado baixa exatamente como qualquer outro
FAQ
O TPdf.SaveAsLinearized mantém assinaturas digitais válidas?
Não. O TPdf.SaveAsLinearized renumera e reescreve todo objeto, então assinaturas existentes são invalidadas. Linearize primeiro, depois aplique a assinatura
O PDFium Component lineariza um PDF criptografado?
Só quando você passa saRemoveSecurity. As chaves de criptografia em nível de objeto dependem dos números de objeto, que o escritor muda, então a saída é escrita sem criptografia
Quais versões de PDF a saída linearizada suporta?
O TPdf.SaveAsLinearized aceita pvUnknown para manter a versão de origem ou qualquer alvo do PDF 1.2 para cima, e rejeita saIncremental porque uma revisão anexada quebraria o layout
Saída Fast Web View, carregamento progressivo e a API de disponibilidade fazem todos parte do PDFium Component para Delphi e C++Builder, que envolve o PDFium para projetos Delphi, C++Builder e Lazarus