Abra um PDF de 200 páginas a partir de um link e o navegador muitas vezes fica em branco até que a maior parte do arquivo chegue. Fast Web View é o nome que o Adobe Acrobat dá a um PDF linearizado (Anexo F da ISO 32000-1): a página um e um pequeno índice ficam na frente, então um visualizador pinta a página um cedo e busca as páginas seguintes por requisição HTTP Range. HotPDF e PDFium Component podem ambos escrever arquivos linearizados a partir do Delphi
Nada disso é um formato de arquivo separado. Um PDF linearizado é um PDF comum que qualquer leitor em conformidade abre sem tratamento especial; a diferença está na ordem dos bytes e em duas estruturas extras, o dicionário de parâmetros de linearização e o hint stream. Duas condições decidem se um leitor sente o benefício algum dia. O arquivo precisa estar linearizado e continuar assim, o que uma edição anexada desfaz silenciosamente, e o servidor web precisa responder a requisições de byte-range com 206 Partial Content. Em um disco local, onde seek até o fim de um arquivo não custa nada, um PDF linearizado se comporta exatamente como qualquer outro, então o Fast Web View é puramente uma otimização de rede que compensa para documentos grandes abertos direto de uma URL
O que um PDF linearizado reorganiza?
Um PDF linearizado reúne tudo o que a página um precisa em um bloco contíguo perto do início do arquivo, à frente de todas as outras páginas. Em um PDF comum os objetos podem ficar em qualquer ordem, porque a tabela de referência cruzada no fim mapeia cada número de objeto ao seu offset de byte: um leitor faz seek até a cauda, lê o ponteiro startxref, carrega a tabela e então pode encontrar qualquer coisa (a visão geral da estrutura de arquivos PDF percorre esse layout de header, body, xref e trailer). O design é excelente em disco e ruim em rede, onde a cauda é exatamente a parte que chega por último. Para desenhar a página um, um leitor convencional precisa do objeto de página, do seu content stream e das fontes e imagens que ele usa, e em um arquivo sem ordem qualquer um deles pode estar no megabyte final
A linearização corrige a ordem. Depois do header vêm o dicionário de linearização e uma pequena seção de referência cruzada da primeira página, então os objetos de nível de documento como o catálogo, o hint stream primário e a primeira página com todos os seus recursos. As páginas restantes seguem uma a uma, depois os objetos compartilhados entre páginas posteriores, e por último a tabela de referência cruzada principal. Um leitor que ignora a otimização ainda começa pelo fim e encontra cada objeto pela tabela principal, razão pela qual um arquivo linearizado continua totalmente compatível com software mais antigo. A numeração de objetos parece invertida em um editor hexadecimal como resultado: o bloco da frente carrega os números mais altos, enquanto tudo atrás dele é numerado a partir de 1
O dicionário de linearização e as tabelas de hints
O primeiro objeto depois do header %PDF é o dicionário de parâmetros de linearização, e o Anexo F exige que ele esteja dentro dos primeiros 1024 bytes para que um leitor em streaming o encontre no pacote inicial. A Tabela F.1 lista suas entradas: /L é o comprimento do arquivo inteiro em bytes, /H o offset e o comprimento do hint stream primário, /O o número de objeto da primeira página, /E o offset onde a primeira página termina, /N a contagem de páginas, e /T o offset da primeira entrada na tabela de referência cruzada principal. A entrada /L carrega mais peso do que aparenta. Quando ela diverge do tamanho real do arquivo, a Tabela F.1 manda o leitor tratar o documento como um PDF comum e ignorar toda hint dentro dele
O hint stream é o que torna um salto para a página 40 barato. Ele guarda uma tabela de hints de offset de página, que registra para cada página onde seus objetos começam e quantos bytes eles cobrem, e uma tabela de hints de objeto compartilhado que faz o mesmo para fontes, logos e outros recursos usados em várias páginas. Um leitor que quer a página 40 consulta o byte range dela, pede exatamente essa fatia e renderiza assim que ela chega, puxando do mesmo jeito qualquer objeto compartilhado que ainda não tenha. Dois detalhes derrubam linearizadores caseiros. Os offsets de hints são lógicos, calculados como se o próprio hint stream estivesse ausente, então um writer que armazena offsets físicos manda toda requisição de range posterior errada pelo tamanho do objeto de hints. E um writer que segue o link /Parent de uma página ao coletar os recursos dela desce pela árvore de páginas e arrasta o documento inteiro para a seção da primeira página. Os dois arquivos continuam alegando estar linearizados. Nenhum carrega mais rápido
Por que o Fast Web View precisa da ajuda do servidor?
O Fast Web View só funciona quando o servidor entrega fatias arbitrárias do arquivo sob demanda. O leitor envia requisições HTTP com um header Range e espera receber 206 Partial Content; se o servidor nunca anuncia Accept-Ranges: bytes, ou um proxy ou CDN na frente dele colapsa requisições de range em transferências completas, o leitor não consegue buscar a página 40 por conta própria e baixa o arquivo inteiro em vez disso. A estrutura dentro do PDF então está correta e totalmente desperdiçada, e essa é a falha mais frequentemente diagnosticada errado como linearização quebrada. Antes de reconstruir um documento, requisite o primeiro kilobyte da URL exata que o visualizador abre, por exemplo com curl -r 0-1023, e confirme que o status é 206. Hosts de arquivos estáticos normalmente acertam isso por padrão, enquanto endpoints de aplicação que servem PDFs pelo próprio handler frequentemente não
Como verificar se um PDF tem Fast Web View?
Verifique os bytes em vez de confiar na ferramenta que produziu o arquivo, e verifique as duas metades da condição: o arquivo se declara linearizado, e essa declaração ainda bate com o arquivo no disco. Três verificações cobrem quase todo caso, de uma olhada rápida no desktop a um gate automatizado em um pipeline de publicação:
- Adobe Acrobat ou Reader: abra File > Properties; a aba Description mostra
Fast Web View: YesouNono canto inferior direito. O Acrobat reporta Yes apenas quando a estrutura está presente e atual, então um arquivo editado depois da linearização mostra No - qpdf na linha de comando:
qpdf --check-linearizationvalida as tabelas de hints contra as posições reais dos objetos, eqpdf --show-linearizationdespeja o dicionário e cada entrada de tabela de hints - O primeiro kilobyte do arquivo: abra-o em um editor de texto ou hexadecimal e procure um objeto contendo
/Linearized 1dentro dos primeiros 1024 bytes; quando ele estiver lá, compare o valor/Ldele com o tamanho de arquivo reportado pelo sistema operacional
# Valida o dicionário de linearização e as tabelas de hints contra o layout
qpdf --check-linearization report-web.pdf
# Despeja /L, /H, /O, /E, /N, /T e cada entrada de tabela de hints
qpdf --show-linearization report-web.pdf
A verificação do qpdf é a que vale a pena ligar a um pipeline, porque ela reconcilia as tabelas de hints com as posições reais dos objetos; uma verificação que só confirma que o dicionário existe vai abençoar um arquivo cujos offsets apontam para os bytes errados. A partir do Delphi, o THotPDF.IsLoadedLinearized do HotPDF reporta se um documento carregado se declara linearizado, escaneando os primeiros 1024 bytes por /Linearized antes de fazer o parse. O PDFium Component responde à mesma pergunta pela class function TPdf.InspectAvailability, cujo campo Linearization retorna plnLinearized, plnNotLinearized ou plnUnknown sem carregar o documento de forma alguma. Ambas as flags reportam o que o arquivo declara; a comparação do /L diz se essa declaração ainda bate com os bytes no disco:
uses
System.SysUtils, System.Classes, System.Math, System.StrUtils, PDFium;
type
TFastWebView = (fwvNo, fwvStale, fwvYes);
// Seu próprio helper: o PDFium decide se o arquivo está linearizado,
// e a comparação do /L captura edições anexadas depois
function FastWebViewState(const FileName: string): TFastWebView;
var
Stream: TFileStream;
Head: AnsiString;
Text: string;
P, Q: Integer;
begin
Result := fwvNo;
Stream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
try
if TPdf.InspectAvailability(Stream).Linearization <> plnLinearized then
Exit;
SetLength(Head, Min(Stream.Size, 1024)); // Anexo F: primeiros 1024 bytes
Stream.Position := 0;
if Length(Head) > 0 then
Stream.ReadBuffer(Head[1], Length(Head));
Text := string(Head);
P := Pos('/Linearized', Text);
if P = 0 then
Exit;
P := PosEx('/L ', Text, P + Length('/Linearized'));
if P = 0 then
Exit;
Inc(P, 3);
Q := P;
while (Q <= Length(Text)) and CharInSet(Text[Q], ['0'..'9']) do
Inc(Q);
if StrToInt64Def(Copy(Text, P, Q - P), -1) = Stream.Size then
Result := fwvYes
else
Result := fwvStale; // linearizado uma vez, depois modificado
finally
Stream.Free;
end;
end;
Ativando o Fast Web View no Acrobat, no qpdf e no Delphi
Qualquer ferramenta que reescreve o arquivo inteiro na ordem do Anexo F pode ligar o Fast Web View, e o que mais importa é que ela rode por último. No Acrobat, habilite Save As optimizes for Fast Web View em Preferences > Documents e então use File > Save As em vez de Save, porque um Save simples pode anexar uma atualização incremental. Em um servidor ou em um script de build, qpdf --linearize in.pdf out.pdf reescreve qualquer PDF em forma linearizada e deixa a entrada intocada. Código Delphi tem duas rotas, dependendo de você gerar o documento ou recebê-lo. O HotPDF escreve saída linearizada diretamente quando o THotPDF.LinearizeOutput é True; defina-o com as outras opções de saída antes do BeginDoc, e o layout do Anexo F é calculado e escrito dentro do EndDoc:
qpdf --linearize report-final.pdf report-web.pdf
var
PDF: THotPDF;
begin
PDF := THotPDF.Create(nil);
try
PDF.AutoLaunch := False; // não abre um visualizador depois do EndDoc
PDF.FileName := 'catalog-web.pdf';
PDF.LinearizeOutput := True; // defina antes do BeginDoc
PDF.BeginDoc; // a página 1 existe depois do BeginDoc
PDF.CurrentPage.TextOut(72, 72, 0, 'Spring catalog');
PDF.AddPage;
PDF.CurrentPage.TextOut(72, 72, 0, 'Price list');
PDF.EndDoc; // o layout linearizado é escrito aqui
finally
PDF.Free;
end;
end;
Para arquivos que já existem, o PDFium Component oferece o TPdf.SaveAsLinearized. Ele roda o salvamento PDFium comum para um armazenamento intermediário, então edições de página e valores de formulário passam pelo mesmo caminho de qualquer outro salvamento, e depois reestrutura essa saída de acordo com o Anexo F antes de escrever o seu destino. O método aceita os mesmos parâmetros TSaveOption e TPdfVersion do SaveAs, rejeita saIncremental e qualquer versão abaixo de PDF 1.2, e sua sobrecarga com nome de arquivo escreve para um arquivo temporário que substitui o destino só depois de o salvamento ter sucesso. Como o writer reconstrói o arquivo a partir da revisão atual, ele também é o jeito de restaurar o Fast Web View em um arquivo que acumulou edições anexadas. Entrada criptografada precisa de saRemoveSecurity: renumerar objetos invalida as chaves por objeto do security handler padrão (ISO 32000-1 §7.6.2), então a cópia web é escrita sem criptografia
var
Pdf: TPdf;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'manual-rev3.pdf'; // carrega edições anexadas
Pdf.Active := True;
if not Pdf.SaveAsLinearized('manual-web.pdf', saNoIncremental) then
raise Exception.Create('Linearized save failed');
finally
Pdf.Free;
end;
end;
Atualizações incrementais desligam o Fast Web View silenciosamente
Uma atualização incremental é o motivo mais comum de um arquivo corretamente linearizado perder o Fast Web View depois. Quando uma ferramenta adiciona uma assinatura, preenche um campo de formulário ou anexa uma nota adesiva por um salvamento incremental, ela deixa os bytes originais intocados e anexa ao fim os objetos alterados, uma nova seção de referência cruzada e um novo trailer. O dicionário de linearização na frente ainda descreve o layout antigo, o /L dele não bate mais com o tamanho do arquivo, e um leitor em conformidade volta a tratar o documento como um PDF comum, mesmo que a estrutura linearizada continue intacta na primeira parte do arquivo. O HotPDF anexa exatamente esse tipo de revisão por meio do BeginIncrementalUpdate e do SaveIncrementalUpdate, que é a ferramenta certa enquanto o documento ainda muda e o passo final errado para um arquivo destinado a um servidor web:
// Anexa uma revisão: rápido, mantém os bytes anteriores intactos,
// e deixa o resultado sem Fast Web View
PDF.BeginIncrementalUpdate('report.pdf');
PDF.AddPage;
PDF.CurrentPage.TextOut(72, 760, 0, 'Addendum');
PDF.SaveIncrementalUpdate('report-rev2.pdf');
A regra viável é editar incrementalmente enquanto o documento está em mudança, linearizar uma única vez como o último passo, e deixar o Fast Web View desligado para arquivos que são enviados por e-mail ou baixados antes de alguém abri-los, já que eles não ganham nada com o índice maior carregado na frente. Documentos assinados forçam uma escolha real: uma reescrita completa muda os bytes que o /ByteRange de cada assinatura cobre, e assinar depois anexa uma atualização que traz a divergência do /L de volta direto, então para contratos mantenha a assinatura e aceite downloads completos. O lado de escrita do Anexo F, incluindo como os offsets convergem antes de o primeiro byte ser escrito e por que a saída linearizada abre mão de object streams, está coberto em escrever PDFs linearizados com tabelas de hints do HotPDF e linearização Fast Web View com PDFium Component em Delphi. O LinearizeOutput e o IsLoadedLinearized vêm com o HotPDF Delphi Component, enquanto o SaveAsLinearized e o InspectAvailability fazem parte do PDFium Component para Delphi, C++Builder e Lazarus