A economia do tamanho de arquivo PDF
Spec: ISO 32000-2, §7.5.7ISO 32000-2 §7.5.7
Visão geral
Seção intitulada “Visão geral”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.
Por que isso importa
Seção intitulada “Por que isso importa”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.
A versão resumida
Seção intitulada “A versão resumida”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.
Como o NextPDF aborda isso
Seção intitulada “Como o NextPDF aborda isso”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.
Alavanca 1 — object streams (ObjStm)
Seção intitulada “Alavanca 1 — object streams (ObjStm)”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.7ISO 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.
| Edition | Availability |
|---|---|
| Core | Suporte 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. |
| Pro | Not in this edition |
| Enterprise | Not 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.3ISO 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.
Alavanca 3 — escolha de filtro de imagem
Seção intitulada “Alavanca 3 — escolha de filtro de imagem”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.4ISO 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.
Alavanca 4 — subsetting de fontes
Seção intitulada “Alavanca 4 — subsetting de fontes”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.
- 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.
- 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.
- 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.
- 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.
Exemplo prático
Seção intitulada “Exemplo prático”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
ObjectStreamPackerpara 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.
Equívoco comum
Seção intitulada “Equívoco comum”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.
Limites e fronteiras
Seção intitulada “Limites e fronteiras”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.
Documentos relacionados
Seção intitulada “Documentos relacionados”- Streams e filtros — o companheiro de como um filtro decodifica; esta página deliberadamente o complementa, não o duplica.
- O que um PDF realmente é — o modelo de objeto-indireto cuja sobrecarga por objeto a alavanca de object stream reduz.
- A anatomia de um arquivo PDF — a estrutura de cross-reference que se torna um stream comprimido.
- Fontes: a parte difícil — como o subsetting seleciona e reindexa os glifos que um documento desenha.
Glossário
Seção intitulada “Glossário”- 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/endobje rastreado pelo cross-reference index. O envelope é a sobrecarga por objeto. - Cross-reference stream — o stream
/Type /XRefque 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.