Technical Article

Linearização de PDF e Fast Web View: Como Funciona

Coloque um relatório digitalizado de 80 MB atrás de uma ligação, abra-o num navegador e observe o que acontece: o leitor mostra uma página em branco até que uma grande parte desses bytes tenha chegado, desenhando a primeira página de uma só vez. Salte para a página 40 e, num ficheiro mal construído, toda a descarga poderá reiniciar. O aspeto mais frustrante é que o leitor apenas queria ver a primeira página. A linearização é la resposta estrutural a este problema. Reorganiza o PDF de modo a que o leitor consiga renderizar a página inicial a partir de um pequeno prefixo do ficheiro e obter o restante a pedido, razão pela qual a Adobe comercializa esta funcionalidade como "Fast Web View".

Nada disto constitui um formato de ficheiro diferente. Um PDF linearizado é um PDF comum que um leitor compatível abrirá sem qualquer tratamento especial. O segredo reside inteiramente na forma como os bytes são ordenados e em duas estruturas adicionais que o ficheiro contém. A norma ISO 32000-1 especifica toda esta disposição no Anexo F e, depois de ver o esquema, o comportamento deixa de parecer magia e passa a parecer uma troca deliberada da ordem do ficheiro pela redução da latência de primeira renderização.

O que a linearização realmente reorganiza

Um PDF normal pode dispersar os seus objetos em quase qualquer ordem. A tabela de referências cruzadas no final do ficheiro é o que permite este funcionamento: o leitor desloca-se até ao fim, lê o ponteiro startxref, carrega a tabela de referências cruzadas (xref) e, a partir daí, consegue localizar cada objeto através do seu desvio (offset). Este design é excelente para ficheiros locais, onde o deslocamento até ao final não tem qualquer custo, mas ineficiente para um ficheiro transmitido através de uma rede, onde o fim é precisamente a parte que chega em último lugar. Para renderizar a primeira página, um leitor convencional necessita do objeto de página, do seu fluxo de conteúdo, dos tipos de letra referenciados e das imagens desenhadas. Num ficheiro não ordenado, estes elementos podem estar localizados em qualquer parte, incluindo o último megabyte.

A linearização corrige a ordem. Os objetos necessários para apresentar a primeira página são reunidos num bloco contíguo perto do início do ficheiro, logo após uma pequena secção de cabeçalho, chegando assim mais cedo no fluxo de bytes. Tudo o resto, ou seja, as páginas restantes e os recursos partilhados, segue-se numa sequência previsível. Uma segunda tabela de referências cruzadas completa permanece no fim para os leitores que ignoram esta otimização, mas um ficheiro linearizado também disponibiliza uma tabela de referências cruzadas da primeira página e os parâmetros necessários para o leitor de fluxo logo no início. O leitor já não precisa de alcançar o final do ficheiro antes de conseguir desenhar o que quer que seja.

O conjunto de objetos da primeira página e o dicionário de parâmetros de linearização

O primeiríssimo objeto num ficheiro linearizado, após o cabeçalho %PDF, é o dicionário de parâmetros de linearização. É este o dicionário que o leitor de fluxo procura para decidir se a otimização está presente e como utilizá-la. O dicionário regista o comprimento total do ficheiro, o desvio em bytes onde se inicia a secção principal de referências cruzadas, o número do objeto da primeira página e a localização e comprimento do fluxo de dicas (hint stream) que se segue. Com estes números, o leitor sabe, apenas pelos primeiros kilobytes, o quanto tem de descarregar para apresentar a página um e onde procurar o índice que lhe permite saltar para outro local.

O Anexo F é rigoroso quanto ao significado de "primeira página" neste contexto. A secção da primeira página tem de conter o próprio objeto de página, os seus fluxos de conteúdo e os recursos referenciados por esses fluxos, para que a página seja autónoma assim que esse prefixo inicial for descarregado. Os recursos partilhados, como um tipo de letra utilizado em todas as páginas ou um logótipo repetido num cabeçalho, são tratados de forma especial: surgem suficientemente cedo para servir a primeira página, mas são sinalizados como partilhados para que o leitor não os volte a descarregar quando renderizar mais tarde a página 30. Esta distinção entre objetos privados da página e objetos partilhados é o aspeto que a maioria dos "otimizadores" caseiros falha, resultando num ficheiro que afirma ser linearizado mas que continua a bloquear.

Fluxos de dicas: o índice que torna os saltos de página económicos

Apresentar rapidamente a primeira página é apenas metade da vantagem. A outra metade é a possibilidade de saltar para uma página qualquer sem ter de descarregar tudo o que se encontra pelo meio, e é precisamente isso que os fluxos de dicas (hint streams) proporcionam. Um ficheiro linearizado contém uma tabela de dicas de desvio de página e uma tabela de dicas de objetos partilhados, armazenadas sob a forma de um fluxo referenciado no dicionário de parâmetros. A tabela de desvio de página regista, para cada página, onde começam os seus objetos no ficheiro e qual a sua extensão. A tabela de objetos partilhados faz o mesmo para recursos utilizados em várias páginas.

Perante estas tabelas, um leitor que pretenda aceder à página 40 não analisa o ficheiro de forma sequencial. Consulta a tabela de dicas para conhecer o intervalo de bytes que a página 40 ocupa, solicita ao servidor exatamente esse intervalo e renderiza a página assim que esses bytes chegam, descarregando eventuais recursos partilhados que ainda não possua através do mesmo mecanismo. O fluxo de dicas funciona, na prática, como um mapa de acesso aleatório sobreposto ao documento, sendo a razão pela qual um ficheiro de 500 páginas bem linearizado parece responsivo numa ligação lenta, enquanto um ficheiro não otimizado do mesmo tamanho não o parece.

Por que razão o servidor tem de cooperar

A linearização pressupõe que o meio de transporte consiga entregar frações arbitrárias do ficheiro, e vale a pena verificar essa premissa antes de culpar o formato por maus resultados. O mecanismo utilizado é o HTTP byte-serving: o leitor envia pedidos de intervalo (range requests) e o servidor responde com respostas 206 Partial Content. Se o servidor não anunciar Accept-Ranges: bytes, ou se um proxy ou CDN localizado à frente dele colapsar os pedidos de intervalo em transferências completas, o leitor não terá forma de obter a página 40 de forma isolada, recorrendo à descarga de todo o ficheiro. A estrutura interna do PDF estará perfeitamente correta, mas será totalmente desperdiçada.

Esta é a falha que é mais frequentemente diagnosticada, de forma errónea, como "a linearização não funciona". O ficheiro está em perfeitas condições; a rota de entrega é que não está. Antes de reconstruir um documento, confirme através de um pedido condicional se o servidor de alojamento realmente devolve conteúdo parcial para o URL de acesso do leitor. Muitos servidores de conteúdo estático fazem-no por predefinição, mas muitos servidores de aplicações e camadas de cache mal configurados não o fazem.

As atualizações incrementais quebram silenciosamente a linearização

Eis a limitação que surpreende quem gera ficheiros linearizados corretamente e depois não compreende por que razão a otimização desaparece. A linearização depende de uma disposição única, cuidadosamente ordenada, com o seu índice no início. Uma atualização incremental viola este princípio por definição. Quando uma ferramenta adiciona uma assinatura, preenche um campo de formulário ou anexa uma anotação através de uma gravação incremental, não reescreve o ficheiro. Limita-se a anexar os objetos alterados, uma nova secção de referências cruzadas e um novo trailer no fim, mantendo intactos os bytes originais. Este anexo é todo o propósito das atualizações incrementais: é rápido e preserva a revisão anterior para auditoria ou validação de assinaturas.

O efeito secundário é que o ficheiro passa a ter os dados de referências cruzadas mais recentes no final, após o bloco da primeira página cuidadosamente posicionado, e o dicionário de parâmetros de linearização no início passa a descrever um esquema que já não corresponde ao ficheiro. Um leitor compatível deteta essa discrepância e trata o documento como um PDF normal, não linearizado. O Fast Web View deixa de estar disponível, mesmo que a estrutura linearizada original ainda permaneça na primeira metade do ficheiro. Se anexar várias atualizações, cada uma empilhará mais uma revisão no final, aumentando a distância entre o índice inicial desatualizado e o estado real.

Se o seu fluxo de trabalho exige edições e Fast Web View, a regra decorre diretamente da estrutura: edite de forma incremental enquanto o documento estiver em desenvolvimento e volte a linearizar uma vez no final. Uma reescrita completa é o que repõe a estrutura correta. Em termos do HotPDF, isto significa que uma edição em curso utiliza BeginIncrementalUpdate e SaveIncrementalUpdate, que anexam um delta, ao passo que a etapa de finalização carrega o documento completo e o serializa de novo com LoadFromFile seguido de SaveLoadedDocument, o que descarta as revisões antigas acumuladas e emite uma estrutura limpa. O mesmo acontece com os fluxos de objetos (object streams): a ativação de UseObjectStreams em conjunto com UseXRefStream comprime as referências cruzadas e compacta os objetos, o que beneficia o tamanho do ficheiro, mas, tal como qualquer decisão estrutural, tem de ser aplicada durante a reescrita final e não acoplada a uma revisão anexada.

// In-flight edits: append a delta, keep prior revisions intact.
// This leaves the file NOT linearized.
Pdf.BeginIncrementalUpdate('report.pdf');
Pdf.AddPage;
Pdf.CurrentPage.TextOut(72, 760, 0, 'Addendum');
Pdf.SaveIncrementalUpdate('report.pdf');

// Finishing step: full re-serialization produces one clean layout,
// dropping the stacked revisions. Re-run your linearizer on the output.
Pdf.LoadFromFile('report.pdf');
Pdf.SaveLoadedDocument('report-final.pdf');

O HotPDF não disponibiliza uma rotina de linearização direta, pelo que o padrão prático consiste em produzir um ficheiro limpo e totalmente reescrito e correr um otimizador dedicado sobre o mesmo. As ferramentas de linha de comandos efetuam esta reorganização diretamente. O qpdf reescreve um ficheiro no formato linearizado através de uma única opção:

qpdf --linearize report-final.pdf report-web.pdf

Como saber se um ficheiro está linearizado

Não confie no nome do ficheiro ou na ferramenta que alega tê-lo gerado; verifique os bytes. A validação mais direta realiza-se no início do ficheiro: abra-o e procure pelo dicionário de parâmetros de linearização como o primeiro objeto após o cabeçalho, contendo a chave /Linearized. Um atalho visual ao nível do utilizador é a caixa de diálogo Propriedades do Documento do Acrobat, que reporta "Fast Web View: Sim" apenas quando a estrutura está verdadeiramente presente e atualizada.

Para verificações automatizadas, o qpdf indica a presença e a integridade da estrutura, o que é importante dado que um ficheiro pode conter um dicionário de linearização que já não reflete o seu esquema real, que é exatamente o estado em que uma atualização incremental o deixa:

# Reports "File is linearized" and validates hint tables against the layout
qpdf --check report-web.pdf

# Dumps the linearization parameters and hint data in detail
qpdf --show-linearization report-web.pdf

A etapa de validação é a mais importante. Uma verificação que apenas confirme a existência do dicionário aprovará sem problemas um ficheiro cujo índice aponta para desvios incorretos; uma verificação que concilie as tabelas de dicas com as posições reais dos objetos é o que garante que a otimização funcionará sob os pedidos de intervalo de um leitor real.

Continua a valer a pena aplicar a linearização a qualquer documento de grande dimensão disponibilizado na web, em especial para utilizadores de dispositivos móveis com ligações instáveis, e o seu custo resume-se a uma pequena percentagem do tamanho do ficheiro dedicada ao índice inicial. Os dois aspetos fundamentais a reter são que a estrutura interna do PDF e o byte-serving no servidor têm ambos de estar corretos, e que qualquer edição posterior anula a otimização até que o ficheiro seja reescrito. Trate a re-linearização como a última etapa do processo, depois de todas as outras alterações estarem concluídas. O comportamento de referências cruzadas, fluxos de objetos e atualizações incrementais aqui descrito faz parte do modelo estrutural implementado pelo HotPDF Component para Delphi e C++Builder complica; para o contexto geral de estrutura de ficheiros, consulte como um PDF é estruturado, e para o fluxo de trabalho em código com atualizações incrementais e ficheiros grandes, consulte processamento de PDFs grandes a partir de Delphi.