Pular para o conteúdo
getnextpdf.com

A economia do tamanho de arquivo PDF

Spec: ISO 32000-2, §7.5.7

Dois PDFs podem parecer idênticos pixel a pixel na tela e diferir dez vezes em disco. A diferença quase nunca é o conteúdo que você vê; é como o arquivo foi montado por baixo. Esta página é um passeio pela economia de tamanho: para onde os bytes de um PDF de fato vão, e as quatro alavancas em que um autor gasta um orçamento de bytes.

É o companheiro por que os arquivos são grandes de Streams e filtros, que cobre como um filtro decodifica. Este aqui permanece no orçamento.

O tamanho de arquivo raramente é uma métrica de vaidade. É banda em cada download, armazenamento em cada arquivamento, e latência em cada prévia. Uma fatura de 12 MB que deveria ter sido 400 KB não é um problema cosmético quando você gera um milhão delas por mês — é uma conta trinta vezes maior.

A parte frustrante é que o inchaço costuma ser invisível. O documento renderiza corretamente, abre bem, imprime bem. Nada lhe diz que o mesmo artefato poderia ter sido uma fração do tamanho, porque os bytes desperdiçados são estruturais, não visuais. Para encontrá-los você tem de olhar para o orçamento, não para a página.

Pense em um PDF como um orçamento que você gasta em quatro itens de linha.

  • Sobrecarga por objeto. Todo objeto indireto carrega um envelope N G obj / endobj, e é rastreado por uma cross-reference entry. Um documento pesado em páginas tem milhares de objetos minúsculos, e os envelopes se acumulam. As object streams eliminam esse envelope para um grupo inteiro de objetos; cada objeto empacotado ainda mantém sua própria cross-reference entry, mas como uma type-2 entry compacta.
  • Bytes de imagem. Para qualquer documento com fotografias ou digitalizações, as imagens dominam, e a maior alavanca isolada é a escolha de filtro — um codec sem perdas versus um codec de imagem com perdas é a diferença entre megabytes e kilobytes.
  • Bytes de fonte. Uma fonte embutida completa são centenas de kilobytes de glifos que você nunca usa. O subsetting mantém apenas os glifos que o documento de fato desenha.
  • O índice. A cross-reference table que permite a um leitor encontrar cada objeto pode ela mesma ser um stream comprimido em vez de texto puro.

Acerte os quatro e o arquivo é pequeno. Erre um e ele domina todo o resto que você fez bem.

O escritor do NextPDF é um serializador de streaming de passagem única: ele anexa os bytes de cada objeto conforme são produzidos e registra uma clássica cross-reference entry em uso para cada um. Esse padrão é rápido, previsível e produz um arquivo byte-estável — mas não é o menor layout possível, e o NextPDF é honesto sobre isso.

Um PDF é um grafo de objetos indiretos. A maioria deles são dicionários pequenos: page nodes, annotation dictionaries, elementos de structure-tree, outline entries. Cada um paga um imposto fixo — as palavras-chave obj / endobj, os números de objeto e de geração, e uma cross-reference entry que o localiza. Em um documento com milhares de objetos pequenos, esse imposto é uma fatia significativa do arquivo.

Uma object stream reúne muitos desses objetos pequenos que não são streams em um stream e os comprime juntos (Spec: ISO 32000-2, §7.5.7). O envelope obj / endobj é eliminado para o grupo inteiro; os valores internos são armazenados um após o outro sem palavras-chave por objeto, então deflacionados como um único bloco — o que também comprime melhor, porque o compressor com deduplicação agora vê todos esses dicionários similares de uma vez. A cross-reference entry não desaparece — cada objeto empacotado ainda precisa de uma — mas ela encolhe para uma type-2 entry binária compacta no cross-reference stream (mais sobre isso na Alavanca 2).

No NextPDF isso é entregue pelo ObjectStreamPacker, um pós-processador autocontido que pega um PDF de cross-reference-stream finalizado e reescreve os objetos elegíveis em um único /Type /ObjStm. As regras que ele segue vêm direto do padrão: um objeto elegível é um objeto de geração-zero, que não é stream, após excluir os objetos especiais que precisam permanecer diretamente endereçáveis. A §7.5.7 proíbe armazenar um objeto stream dentro de uma object stream, então os objetos stream — conteúdo, fontes, imagens — mantêm suas próprias entries; e o ObjectStreamPacker adicionalmente recusa o próprio cross-reference stream object do documento (ele é reescrito) e o dicionário /Encrypt, ambos os quais precisam permanecer diretamente endereçáveis. Todo o resto que é geração-zero e não é stream é empacotado.

Object stream packing (ObjStm) — edition availability
EditionAvailability
CoreSuporte completo no core open-source via ObjectStreamPacker. É opt-in: o padrão do escritor de passagem única emite entries em uso clássicas, então a saída permanece idêntica byte a byte a menos que você habilite o empacotamento. O packer é determinístico, com seu próprio golden baseline reproduzível.
ProNot in this edition
EnterpriseNot in this edition

Opt-in é uma postura deliberada, não uma limitação se escondendo. A saída padrão é byte-estável e corresponde aos golden baselines existentes; ligar o empacotamento opta por um layout diferente, menor e igualmente determinístico. Você escolhe a troca, e o mecanismo nunca a faz pelas suas costas.

Alavanca 2 — o índice também pode ser um stream

Seção intitulada “Alavanca 2 — o índice também pode ser um stream”

Uma vez que os objetos vivem dentro de uma object stream, o índice que aponta para eles muda de forma. Um leitor encontra um objeto empacotado por uma cross-reference entry comprimida — uma type-2 entry que nomeia a object stream e o índice dentro dela (Spec: ISO 32000-2, §7.5.8.3). Como toda a cross-reference é ela mesma um stream /Type /XRef, o índice para milhares de objetos é empacotado em binário e deflacionado em vez de escrito como linhas de texto puro. O mapa encolhe junto com o território.

O ObjectStreamPacker reconstrói exatamente isto: ele emite a única object stream, depois um cross-reference stream reescrito carregando uma type-1 entry compacta para cada objeto retido e uma type-2 entry para cada um empacotado, preservando cada número de objeto, de modo que as referências existentes permaneçam válidas.

Para qualquer documento com imagens reais, esta alavanca anã as outras. Os bytes são a mesma imagem; o codec é o orçamento. Os nomes de filtro vivem no conjunto de filtros padrão (Spec: ISO 32000-2, §7.4), e a escolha entre eles é uma decisão de tamanho:

  • FlateDecode é sem perdas. Perfeito para line art, screenshots e qualquer coisa com cor plana — e ruinoso para uma fotografia, onde sem perdas significa cada byte do original.
  • DCTDecode é JPEG: com perdas, e para fotografias a escolha certa por uma ampla margem, muitas vezes uma redução de dez vezes para uma queda de qualidade que ninguém nota.
  • JPXDecode é JPEG 2000: compressão por wavelets com uma curva qualidade/tamanho diferente, mas suporte irregular e proibido por alguns perfis de arquivamento.

É exatamente aqui que como um filtro decodifica importa, e esse é o trabalho do artigo Streams e filtros. O ponto econômico é mais estreito: uma fotografia armazenada sem perdas é a causa isolada mais comum de um PDF desnecessariamente enorme, e nenhuma quantidade de empacotamento em object stream vai salvar um arquivo cujo peso real é uma digitalização que não passou por JPEG.

Uma fonte embutida é um programa. Uma completa pode ter várias centenas de kilobytes, porque ela carrega cada glifo que o designer da tipografia já desenhou — milhares de caracteres em scripts que você nunca vai usar neste documento. O subsetting embute apenas os glifos que o documento de fato desenha, transformando esse programa em uma pequena fração de si mesmo. Uma carta de uma página não precisa de uma fonte inteira capaz de CJK; ela precisa das poucas dezenas de glifos que compõe. A mecânica de como os glifos são selecionados e reindexados é um assunto próprio — veja Fontes: a parte difícil.

  1. Bytes de imagemPara arquivos pesados em mídia, o maior item de linha. A escolha de filtro — Flate sem perdas vs um codec de imagem com perdas como DCT (ou JPX em um modo com perdas) — é a alavanca dominante de tamanho.
  2. Bytes de fonteUma fonte embutida completa é em sua maioria glifos que você nunca desenha. O subsetting mantém apenas os glifos usados, muitas vezes encolhendo o programa em uma ordem de magnitude.
  3. Sobrecarga por objetoMilhares de dicionários pequenos pagam cada um um envelope obj/endobj mais uma cross-reference entry. As object streams (ObjStm) eliminam o envelope para o grupo inteiro; cada objeto mantém uma type-2 cross-reference entry compacta.
  4. O índiceUm cross-reference stream comprimido com type-2 entries empacota em binário e deflaciona o mapa de cada objeto, substituindo as linhas de índice em texto puro.
Where the bytes go: the four levers an author spends a PDF's size budget on, in the order they typically dominate a real file.

Não há uma API fabricada para mostrar aqui, porque as decisões de tamanho mais importantes são tomadas antes de os bytes chegarem ao escritor — e a única alavanca estrutural que o NextPDF expõe é um único opt-in. Conceitualmente, o orçamento se lê assim:

  • Alimente fotografias como JPEG, de modo que caiam sob DCTDecode, não recodificadas sem perdas. A maior vitória é uma escolha sobre os dados de origem, não um flag de escritor.
  • Deixe o escritor fazer subsetting das fontes embutidas, de modo que apenas os glifos desenhados sejam enviados.
  • Para um documento com muitos objetos pequenos, opte pelo empacotamento em object stream, que roteia o arquivo finalizado pelo ObjectStreamPacker para agrupar os objetos pequenos que não são streams e reescrever o cross-reference stream.

O caminho padrão — entries em uso clássicas, sem ObjStm — é a linha de base certa: determinístico, byte-estável e fácil de verificar. O empacotamento é o upgrade ponderado quando a contagem de objetos, não o peso da imagem, é o que está inflando o arquivo.

A armadilha é recorrer a um botão de “comprimir o PDF” e esperar que ele conserte tudo. A compressão não é uma alavanca; são quatro, e elas não substituem umas às outras. O empacotamento em object stream não consegue encolher uma fotografia — esse é o trabalho do filtro de imagem. Um JPEG perfeito não compensa uma fonte que você esqueceu de fazer subsetting. E nada disso ajuda se o inchaço real é uma digitalização de 10 MB armazenada sem perdas porque ninguém escolheu o DCTDecode para ela.

O segundo equívoco é que menor é sempre estritamente melhor. Não é. As object streams são incompatíveis com a linearização — o layout fast-web-view que fixa o posicionamento absoluto de objetos, de modo que a primeira página chegue por streaming cedo. E alguns perfis de arquivamento restringem quais filtros de imagem são sequer permitidos. Tamanho é um eixo. Ele troca contra streaming, conformidade de arquivamento e reprodutibilidade, e o ponto certo na curva depende de para que o documento serve.

As quatro alavancas são a economia estrutural do tamanho de arquivo. Elas não são uma garantia universal de “deixar menor”, e o NextPDF não finge ser um reotimizador para PDFs de entrada arbitrários.

O ObjectStreamPacker é uma otimização de melhor esforço que nunca arrisca a correção. Ele recusa — retornando a entrada inalterada — quando o arquivo não é um PDF de cross-reference-stream, quando está criptografado, quando carrega uma assinatura digital (rearranjar objetos deslocaria as faixas de bytes que uma assinatura protege), quando ele já contém object streams, ou quando não há objeto elegível para empacotar. Ele emite exatamente uma object stream para o documento inteiro; ele não se divide em uma coleção /Extends, o que está fora de escopo e em conformidade para os tamanhos de documento que o NextPDF produz.

As alavancas de imagem e de fonte são em grande parte decisões sobre a entrada. O escritor do NextPDF não transcodifica implicitamente um bitmap sem perdas em um stream de imagem com perdas — recodificar uma fotografia para DCTDecode ou JPXDecode é uma decisão explícita de codificação de imagem a montante, não algo que o serializador faça pelas suas costas — nem ele recupera glifos de uma fonte que o chamador pediu para embutir por completo. As maiores vitórias de tamanho são feitas a montante do serializador de bytes; o trabalho do mecanismo é não desperdiçar o orçamento que você traz a ele.

  • Object stream (ObjStm) — um único stream que guarda muitos objetos indiretos pequenos que não são streams, comprimidos juntos, de modo que o envelope obj / endobj é eliminado para o grupo. Cada objeto empacotado ainda mantém sua própria cross-reference entry, como uma type-2 entry compacta. Opt-in no NextPDF.
  • Objeto indireto — um objeto numerado no grafo de um PDF, envolvido em um envelope obj / endobj e rastreado pelo cross-reference index. O envelope é a sobrecarga por objeto.
  • Cross-reference stream — o stream /Type /XRef que indexa cada objeto, empacotado em binário e deflacionado em vez de escrito como linhas de texto puro.
  • Type-2 entry comprimida — uma cross-reference entry que aponta para um objeto que vive dentro de uma object stream, nomeando o stream e o índice dentro dele.
  • Subsetting de fontes — embutir apenas os glifos que um documento de fato desenha, em vez da tipografia completa, encolhendo o programa de fonte embutido.
  • Filtro sem perdas vs com perdas — um codec sem perdas (FlateDecode) reproduz cada byte; um codec de imagem com perdas (DCTDecode, ou JPXDecode em um modo com perdas) descarta detalhe imperceptível para um resultado muito menor. (JPEG 2000 / JPX também pode ser configurado sem perdas.) A escolha é a alavanca dominante em um arquivo pesado em mídia.