Fluxos e filtros
ISO 32000-2 §7.4 Evidence: Standard-backed
Visão geral
Seção intitulada “Visão geral”A maior parte dos bytes de um PDF de verdade fica dentro de fluxos: conteúdo de página, fontes, imagens e o próprio fluxo de referências cruzadas. Praticamente nenhum desses bytes é armazenado em bruto; antes disso, eles passam por um ou mais filtros. Esta página explica quais filtros você encontra, para que cada um serve, onde eles causam problemas e por que o NextPDF fixa a compressão para que a mesma entrada produza sempre os mesmos bytes.
Por que isso importa
Seção intitulada “Por que isso importa”Um fluxo e seu filtro são um contrato: “estes bytes estão comprimidos com deflate e depois codificados em base-85 — decodifique nessa ordem para obter os dados reais.” Se a entrada /Filter discordar do que os bytes realmente são, se o /Length estiver errado ou se dois filtros forem listados na ordem errada, o fluxo fica indecodificável e o objeto que ele carregava é perdido. Um leitor não adivinha por heurística; ele faz o que o dicionário manda.
Há um segundo custo, mais discreto. Se o compressor de uma biblioteca for não determinístico — build de zlib diferente, nível diferente, fronteiras de bloco internas diferentes — duas execuções que deveriam produzir um PDF idêntico acabam gerando dois arquivos diferentes. Isso quebra a reprodutibilidade no nível dos bytes. Quando a reprodutibilidade quebra, também quebram os testes de golden file, a verificação de builds assinados e qualquer pipeline que compare a saída. Os filtros determinam tanto se o PDF está correto quanto se o PDF é o mesmo.
A versão resumida
Seção intitulada “A versão resumida”- Um objeto de fluxo é um dicionário mais um bloco de bytes, envolvido em
stream…endstream, com um/Lengthe geralmente um/Filter. - A entrada
/Filternomeia o filtro de decodificação — ou um array de filtros aplicados como um pipeline, em ordem. - Os filtros se dividem em duas famílias: compressão (FlateDecode, LZWDecode, RunLengthDecode, DCTDecode, JPXDecode, JBIG2Decode) e transporte ASCII (ASCIIHexDecode, ASCII85Decode), mais o filtro especial Crypt para criptografia.
- O que você verá com mais frequência é o FlateDecode — zlib/deflate. É o padrão para conteúdo, fontes e o fluxo de referências cruzadas.
- O NextPDF fixa a saída Flate em um nível e formato definidos para que os mesmos bytes de entrada comprimam sempre nos mesmos bytes de saída.
Como o NextPDF aborda isso
Seção intitulada “Como o NextPDF aborda isso”O NextPDF emite objetos de fluxo por um único auxiliar de buffer e comprime por um único compressor fixo — de propósito.
BinaryBuffer::writeStream() (src/Support/BinaryBuffer.php) envolve o conteúdo do fluxo no dicionário correspondente, sempre escrevendo um /Length igual ao comprimento real em bytes e mesclando quaisquer entradas extras que o chamador forneça, como /Filter. Não há caminho em que o comprimento declarado possa divergir dos bytes escritos, porque o comprimento é obtido da própria string de conteúdo.
A compressão passa pelo PinnedZlibCompressor (src/Writer/PinnedZlibCompressor.php). Esta classe existe por um único motivo. O gzcompress sem um nível explícito recorre ao padrão do runtime do zlib, que historicamente variou entre builds. O cabeçalho zlib de 2 bytes inclusive codifica o nível de forma indireta, então “o padrão” não é uma saída estável. O compressor fixa o nível no máximo da RFC 1951 e sempre emite deflate envolto em zlib (cabeçalho RFC 1950 + trailer Adler-32), exatamente o que o /Filter /FlateDecode espera. Uma falha grave do zlib vira uma exceção tipada, em vez de cair silenciosamente para uma saída não comprimida — um fluxo nunca é emitido em bruto sem aviso.
O próprio fluxo de referências cruzadas é um exemplo prático de tudo isso: o CrossReferenceStream (src/Core/CrossReferenceStream.php) constrói uma tabela binária, comprime-a e emite-a como um objeto de fluxo com /Type /XRef, um array de larguras de campo /W e /Filter /FlateDecode. O índice que permite a um leitor encontrar cada objeto é, ele próprio, um fluxo filtrado.
| Filtro | Família | Para que serve | Onde falha |
|---|---|---|---|
| FlateDecode | Compressão | zlib/deflate; o padrão para conteúdo, fontes e fluxos xref | Um build de zlib não determinístico faz PDFs “idênticos” diferirem byte a byte |
| LZWDecode | Compressão | Compressão Lempel–Ziv–Welch mais antiga | Legado; substituída pelo Flate, mas ocasionalmente ainda vista em arquivos antigos |
| DCTDecode | Compressão | Imagens JPEG colour/grayscale | Com perdas — recodificar uma imagem que já é DCT a degrada de novo |
| JPXDecode | Compressão | Dados de imagem wavelet JPEG 2000 | Não permitido por alguns perfis de arquivamento; o suporte amplo é irregular |
| JBIG2Decode | Compressão | Compressão de imagens bilevel (1 bit) | Não deve ser usado com imagens inline; modos com perdas podem alterar digitalizações |
| RunLengthDecode | Compressão | Run-length orientado a bytes | Só ajuda com dados que têm longas sequências de um único byte; pode aumentar outros dados |
| ASCIIHexDecode | Transporte | Binário como dígitos hexadecimais | Dobra o tamanho; só para canais seguros de 7 bits, nunca para tamanho |
| ASCII85Decode | Transporte | Binário como ASCII base-85 | ~25% de sobrecarga; uma conveniência de transporte, não compressão |
| Crypt | Segurança | Aplica o manipulador de segurança do documento | Um fluxo de referências cruzadas não deve usar um filtro Crypt |
O conjunto de filtros padrão do PDF, por família, com a falha associada a cada um. O NextPDF escreve FlateDecode para conteúdo, fontes e o fluxo de referências cruzadas; os filtros de transporte ASCII servem para canais de 7 bits, nunca para reduzir tamanho.
O que dizem as evidências
Seção intitulada “O que dizem as evidências”O mecanismo de filtros é definido pela Spec: ISO 32000-2, §7.4 ISO 32000-2 §7.4 . Um dicionário de fluxo nomeia seus filtros por meio do /Filter. Quando a entrada lista mais de um filtro, esses filtros formam um pipeline de decodificação e são aplicados em sequência. Um escritor codifica um fluxo para comprimi-lo ou para torná-lo seguro em 7 bits. Um leitor invoca os filtros de decodificação correspondentes para recuperar os dados originais. Evidence: Standard-backed
A tabela de filtros do padrão classifica cada filtro. O FlateDecode descomprime dados codificados em zlib/deflate-encoded, reproduzindo o texto ou os dados binários originais. O DCTDecode reproduz amostras de imagem que aproximam o original via JPEG — a palavra “aproximam” é o padrão indicando que o processo é com perdas. LZWDecode, RunLengthDecode, JBIG2Decode, JPXDecode e o filtro Crypt também estão definidos ali, com o JBIG2 explicitamente proibido em imagens inline.
O fluxo de referências cruzadas aplica a própria maquinaria do formato a si mesmo: é um objeto de fluxo (/Type /XRef,
Spec: ISO 32000-2, §7.5.8 ISO 32000-2 §7.5.8 ) cujo array /W
indica a largura em bytes de cada campo de entrada no fluxo decodificado. O
padrão exige que ele não seja criptografado e não use um filtro Crypt.
O NextPDF mantém seu CrossReferenceStream seguindo isso exatamente — FlateDecode,
/W explícito, sem criptografia.
Exemplo prático
Seção intitulada “Exemplo prático”Um fluxo de conteúdo de página, comprimido com Flate. Esta é a forma mais comum de longe: um dicionário com /Length e /Filter e, em seguida, os bytes comprimidos entre stream e endstream.
<?php
declare(strict_types=1);
use NextPDF\Writer\PinnedZlibCompressor;
// The marking operators a page content stream carries, uncompressed.$content = "BT /F1 12 Tf 72 712 Td (Hello) Tj ET\n";
// NextPDF compresses through the pinned compressor: fixed level,// fixed zlib-wrapped format. The same $content always yields the// same $compressed bytes, on any supported PHP/zlib build.$compressed = PinnedZlibCompressor::compress($content);
// Emitted as a stream object. /Length is the real byte length of// $compressed; /Filter names the decode the reader must apply.// N 0 obj// << /Length <strlen($compressed)> /Filter /FlateDecode >>// stream// <$compressed bytes>// endstream// endobjUm leitor faz o inverso: lê /Length bytes, passa-os pelo FlateDecode porque o /Filter assim manda e recupera os operadores originais. Fixe o compressor, e essa ida e volta não é apenas correta. Ela é idêntica todas as vezes, que é o que sustenta as verificações de golden file e de builds assinados.
Equívoco comum
Seção intitulada “Equívoco comum”A armadilha é tratar os filtros ASCII como compressão. O ASCIIHexDecode e o ASCII85Decode tornam um fluxo maior — aproximadamente o dobro e aproximadamente 25%, respectivamente. Eles existem para mover dados binários por um canal que só é seguro para texto de 7 bits, não para economizar espaço. Escolher o ASCII85 para “encolher” um PDF faz o oposto. A segunda metade do mesmo equívoco é acreditar que o FlateDecode torna imagens sem perdas “de graça”. O Flate é sem perdas, mas, se a imagem já estava codificada em DCT (JPEG), envolvê-la de novo ou transcodificá-la por um filtro com perdas a degrada, independentemente do que o Flate faça ao redor. O pipeline de filtros preserva exatamente o que você lhe dá — incluindo um artefato de recompressão que você entregou por acidente.
Limites e fronteiras
Seção intitulada “Limites e fronteiras”Esta página aborda como os filtros são declarados e aplicados, não o algoritmo em nível de bits dentro de cada um. A garantia de determinismo diz respeito especificamente à saída Flate do NextPDF para os fluxos que ele escreve. Ela vale em versões menores do PHP e em builds de zlib conformes ao padrão, mas o padrão permite explicitamente que um codificador deflate escolha fronteiras de bloco internas diferentes; portanto, uma saída byte a byte idêntica entre implementações de zlib genuinamente diferentes (por exemplo, um zlib comum versus zlib-ng) não é prometida. O ambiente de build é fixado por esse motivo.
O NextPDF escolhe o FlateDecode e os filtros de transporte ASCII para os dados que emite. Ele não é um transcodificador de imagens. Ele não promete reempacotar um fluxo JPEG2000 ou JBIG2 de entrada arbitrário, e os compromissos de imagens com perdas são uma propriedade dos dados de origem, não algo que um escritor consiga desfazer.
Mini-FAQ
Seção intitulada “Mini-FAQ”Por que o FlateDecode está em toda parte? Ele é sem perdas, de propósito geral, bem suportado e um bom ajuste para o conteúdo de texto e operadores da maioria dos PDFs. É o padrão seguro para fluxos de conteúdo, fontes embutidas e o fluxo de referências cruzadas.
Posso desligar a compressão? Você pode omitir o /Filter e armazenar bytes em bruto, e um leitor vai aceitar. O arquivo fica maior e nada mais melhora; raramente há motivo para isso fora de depuração.
Por que fixar o nível de compressão, afinal? Para que a saída seja reprodutível. Um nível não fixado (ou um build de zlib) pode mudar os bytes comprimidos sem mudar o conteúdo descomprimido — correto, mas não idêntico, o que anula a verificação em nível de bytes.
Documentos relacionados
Seção intitulada “Documentos relacionados”- O que um PDF realmente é — o modelo de objetos dentro do qual vivem os fluxos desta página.
- Fontes: a parte difícil — programas de fonte embutidos são fluxos filtrados, com os seus próprios modos de falha.
- PDF 2.0: o que mudou — como a baseline 2.0 trata os fluxos e o fluxo de referências cruzadas usado por padrão pelo NextPDF.
Glossário
Seção intitulada “Glossário”- Objeto de fluxo — um dicionário mais um bloco de bytes entre
streameendstream, carregando um/Lengthe geralmente um/Filter. - Filtro — uma transformação de decodificação nomeada que um leitor aplica aos bytes de um fluxo (por exemplo,
FlateDecode). - Pipeline de filtros — um array de filtros aplicados em sequência; a ordem do array é a ordem de decodificação.
- FlateDecode — o filtro zlib/deflate; a compressão padrão para conteúdo, fontes e fluxos de referências cruzadas.
- DCTDecode — o filtro de imagem JPEG; com perdas, portanto recodificar degrada a imagem de novo.
- Filtro de transporte ASCII — ASCIIHexDecode / ASCII85Decode; torna os dados seguros em 7 bits ao custo do tamanho — não compressão.
- Compressão determinística — produzir uma saída comprimida byte a byte idêntica para uma entrada idêntica, conseguida fixando o nível e o formato do compressor.