Flux et filtres
ISO 32000-2 §7.4 Evidence: Standard-backed
La plupart des octets d’un PDF réel se trouvent à l’intérieur de flux : contenu de page, polices, images, et même le flux de références croisées. Presque aucun de ces octets n’est stocké brut : ils passent d’abord par un ou plusieurs filtres. Cette page explique les filtres que tu rencontres, le rôle de chacun, là où ils posent problème, et pourquoi NextPDF verrouille sa compression pour qu’une même entrée produise toujours les mêmes octets.
Pourquoi c’est important
Section intitulée « Pourquoi c’est important »Un flux et son filtre forment un contrat : « ces octets sont compressés en deflate, puis encodés en base-85 — décode-les dans cet ordre pour obtenir les vraies données. » Si l’entrée /Filter contredit la nature réelle des octets, si la /Length est erronée, ou si deux filtres sont listés dans le mauvais ordre, le flux est indécodable et l’objet qu’il transportait est perdu. Un lecteur ne tente pas de deviner par heuristique ; il suit ce que lui indique le dictionnaire.
Il existe un second coût, plus discret. Si le compresseur d’une bibliothèque est non déterministe — build zlib différent, niveau différent, frontières de blocs internes différentes — alors deux exécutions qui devraient produire un PDF identique génèrent deux fichiers différents. Cela casse la reproductibilité octet par octet. Une reproductibilité cassée fait échouer à son tour les tests sur fichiers de référence, la vérification des builds signés, et tout pipeline qui compare la sortie. Les filtres déterminent à la fois si le PDF est correct et s’il reste le même.
La version courte
Section intitulée « La version courte »- Un objet flux est un dictionnaire plus un bloc d’octets, enveloppés dans
stream…endstream, avec une/Lengthet généralement un/Filter. - L’entrée
/Filternomme le filtre de décodage — ou un tableau de filtres appliqués en pipeline, dans l’ordre. - Les filtres se répartissent en deux familles : la compression (FlateDecode, LZWDecode, RunLengthDecode, DCTDecode, JPXDecode, JBIG2Decode) et le transport ASCII (ASCIIHexDecode, ASCII85Decode), plus le filtre Crypt spécial pour le chiffrement.
- Le filtre que tu rencontreras le plus souvent est FlateDecode — zlib/deflate. C’est le filtre par défaut pour le contenu, les polices et le flux de références croisées.
- NextPDF verrouille sa sortie Flate sur un niveau et un format fixes, pour que les mêmes octets en entrée donnent toujours les mêmes octets en sortie.
Comment NextPDF aborde la question
Section intitulée « Comment NextPDF aborde la question »NextPDF émet les objets flux via un seul utilitaire de tampon et compresse via un seul compresseur verrouillé — délibérément.
BinaryBuffer::writeStream() (src/Support/BinaryBuffer.php) enveloppe le contenu du flux dans son dictionnaire, en écrivant toujours une /Length égale à la longueur réelle en octets et en fusionnant toute entrée supplémentaire fournie par l’appelant, comme /Filter. Aucun chemin d’exécution ne permet à la longueur déclarée de contredire les octets écrits, parce que la longueur est tirée de la chaîne de contenu elle-même.
La compression passe par PinnedZlibCompressor (src/Writer/PinnedZlibCompressor.php). Cette classe existe pour une seule raison. gzcompress sans niveau explicite s’en remet au niveau par défaut du runtime zlib, qui a historiquement varié d’un build à l’autre. L’en-tête zlib de 2 octets encode même le niveau indirectement ; « le niveau par défaut » n’est donc pas une sortie stable. Le compresseur verrouille le niveau au maximum de RFC 1951 et émet toujours du deflate enveloppé en zlib (en-tête RFC 1950 + suffixe Adler-32), ce qui est exactement ce qu’attend /Filter /FlateDecode. Un échec dur de zlib devient une exception typée plutôt qu’un repli silencieux vers une sortie non compressée : un flux n’est jamais émis discrètement à l’état brut.
Le flux de références croisées lui-même illustre directement ce mécanisme : CrossReferenceStream (src/Core/CrossReferenceStream.php) construit une table binaire, la compresse, et l’émet comme objet flux avec /Type /XRef, un tableau de largeurs de champ /W, et /Filter /FlateDecode. L’index qui permet à un lecteur de trouver chaque objet est donc, lui aussi, un flux filtré.
| Filtre | Famille | À quoi il sert | Où il déraille |
|---|---|---|---|
| FlateDecode | Compression | zlib/deflate ; le filtre par défaut pour le contenu, les polices, les flux xref | Un build zlib non déterministe fait diverger octet par octet des PDF « identiques » |
| LZWDecode | Compression | Compression Lempel–Ziv–Welch plus ancienne | Hérité ; supplanté par Flate, encore rencontré occasionnellement dans de vieux fichiers |
| DCTDecode | Compression | Images JPEG couleur/niveaux de gris | Avec perte — réencoder une image déjà en DCT la dégrade à nouveau |
| JPXDecode | Compression | Données image en ondelettes JPEG 2000 | Interdit par certains profils d’archivage ; sa prise en charge large est inégale |
| JBIG2Decode | Compression | Compression d’images bilevel (1 bit) | Ne doit pas être utilisé avec les images en ligne ; les modes avec perte peuvent altérer les numérisations |
| RunLengthDecode | Compression | Codage par plages orienté octet | N’aide que les données ayant de longues plages d’un seul octet ; peut gonfler les autres données |
| ASCIIHexDecode | Transport | Du binaire en chiffres hexadécimaux | Double la taille ; uniquement pour les canaux compatibles 7 bits, jamais pour la taille |
| ASCII85Decode | Transport | Du binaire en ASCII base-85 | ~25 % de surcoût ; une commodité de transport, pas de la compression |
| Crypt | Sécurité | Applique le gestionnaire de sécurité du document | Un flux de références croisées ne doit pas utiliser de filtre Crypt |
Le jeu de filtres standard du PDF, par famille, avec le point de défaillance typique de chacun. NextPDF écrit FlateDecode pour le contenu, les polices et le flux de références croisées ; les filtres de transport ASCII servent aux canaux 7 bits, jamais à réduire la taille.
Ce que disent les preuves
Section intitulée « Ce que disent les preuves »Le mécanisme de filtre est défini par Spec: ISO 32000-2, §7.4 ISO 32000-2 §7.4 . Les filtres d’un flux sont spécifiés par l’entrée /Filter de son dictionnaire, et les filtres peuvent être cascadés pour former un pipeline qui fait passer le flux par deux ou plusieurs transformations de décodage en séquence. L’exemple donné par le standard est LZW suivi d’ASCII base-85, décodés dans cet ordre. Un rédacteur encode un flux pour le compresser ou pour le rendre compatible 7 bits. Un lecteur invoque les filtres de décodage correspondants pour récupérer les données d’origine. Evidence: Standard-backed
La table des filtres du standard classifie chaque filtre. FlateDecode décompresse les données encodées en zlib/deflate et reproduit le texte ou les données binaires d’origine. DCTDecode reproduit des échantillons d’image qui approximent l’original via JPEG : ce terme, dans le standard, indique qu’il s’agit d’un filtre avec perte. LZWDecode, RunLengthDecode, JBIG2Decode, JPXDecode et le filtre Crypt y sont également définis, JBIG2 étant explicitement interdit pour les images en ligne.
Le flux de références croisées applique à lui-même la mécanique propre au format : c’est un objet flux (/Type /XRef,
Spec: ISO 32000-2, §7.5.8 ISO 32000-2 §7.5.8 ) dont le tableau /W
indique la largeur en octets de chaque champ d’entrée dans le flux décodé. Le
standard exige qu’il ne soit pas chiffré et n’utilise pas de filtre Crypt.
Le CrossReferenceStream de NextPDF suit exactement cette règle — FlateDecode,
/W explicite, aucun chiffrement.
Exemple pratique
Section intitulée « Exemple pratique »Un flux de contenu de page, compressé avec Flate. C’est de très loin la forme la plus courante : un dictionnaire avec /Length et /Filter, puis les octets compressés entre stream et 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// endobjUn lecteur fait l’inverse : il lit /Length octets, les passe par FlateDecode puisque /Filter le dit, et récupère les opérateurs d’origine. Une fois le compresseur verrouillé, cet aller-retour n’est pas seulement correct : il est identique à chaque fois, ce sur quoi reposent les contrôles de fichiers de référence et de builds signés.
Idée fausse courante
Section intitulée « Idée fausse courante »Le piège consiste à traiter les filtres ASCII comme de la compression. ASCIIHexDecode et ASCII85Decode rendent un flux plus gros — à peu près le double et à peu près 25 % respectivement. Ils existent pour faire transiter des données binaires par un canal qui n’est sûr que pour le texte 7 bits, pas pour gagner de la place. Choisir ASCII85 pour « rétrécir » un PDF produit donc l’inverse. L’autre moitié de la même idée fausse consiste à croire que FlateDecode rend les images sans perte « gratuitement ». Flate est sans perte, mais si l’image était déjà encodée en DCT (JPEG), l’envelopper à nouveau ou la transcoder à travers un filtre avec perte la dégrade, quoi que fasse Flate autour. Le pipeline de filtres préserve exactement ce que tu lui donnes — y compris un artefact de recompression que tu lui as donné par accident.
Limites et frontières
Section intitulée « Limites et frontières »Cette page couvre la manière dont les filtres sont déclarés et appliqués, pas l’algorithme au niveau du bit à l’intérieur de chacun. La garantie de déterminisme porte spécifiquement sur la sortie Flate de NextPDF pour les flux qu’il écrit. Elle vaut pour les versions mineures de PHP et les builds zlib conformes au standard, mais le standard permet explicitement à un encodeur deflate de choisir des frontières de blocs internes différentes ; une sortie identique octet par octet entre des implémentations zlib réellement différentes (par exemple une zlib standard contre zlib-ng) n’est donc pas promise. L’environnement de build est verrouillé pour cette raison.
NextPDF choisit FlateDecode et les filtres de transport ASCII pour les données qu’il émet. Ce n’est pas un transcodeur d’images. Il ne promet pas de réempaqueter un flux JPEG2000 ou JBIG2 entrant arbitraire, et les compromis d’images avec perte sont une propriété des données sources, pas quelque chose qu’un rédacteur peut défaire.
Mini-FAQ
Section intitulée « Mini-FAQ »Pourquoi FlateDecode est-il partout ? Il est sans perte, polyvalent, bien pris en charge et bien adapté au contenu texte et opérateurs de la plupart des PDF. C’est le filtre par défaut sûr pour les flux de contenu, les polices embarquées et le flux de références croisées.
Puis-je désactiver la compression ? Tu peux omettre /Filter et stocker les octets bruts, et un lecteur l’acceptera. Le fichier grossit et rien d’autre ne s’améliore ; il y a rarement une raison en dehors du débogage.
Pourquoi verrouiller le niveau de compression, au juste ? Pour que la sortie soit reproductible. Un niveau non verrouillé (ou un build zlib) peut changer les octets compressés sans changer le contenu décompressé — correct, mais pas identique, ce qui met en échec la vérification octet par octet.
Docs associées
Section intitulée « Docs associées »- Ce qu’est réellement un PDF — le modèle objet dans lequel vivent les flux de cette page.
- Les polices : la partie difficile — les programmes de police embarqués sont des flux filtrés, avec leurs propres modes de défaillance.
- PDF 2.0 : ce qui a changé — comment la base 2.0 traite les flux et le flux de références croisées vers lequel NextPDF bascule par défaut.
Glossaire
Section intitulée « Glossaire »- Objet flux — un dictionnaire plus un bloc d’octets entre
streametendstream, portant une/Lengthet généralement un/Filter. - Filtre — une transformation de décodage nommée qu’un lecteur applique aux octets d’un flux (par exemple
FlateDecode). - Pipeline de filtres — un tableau de filtres appliqués en séquence ; l’ordre du tableau est l’ordre de décodage.
- FlateDecode — le filtre zlib/deflate ; la compression par défaut pour le contenu, les polices et les flux de références croisées.
- DCTDecode — le filtre d’image JPEG ; avec perte, donc le réencodage dégrade l’image à nouveau.
- Filtre de transport ASCII — ASCIIHexDecode / ASCII85Decode ; rend les données compatibles 7 bits au prix de la taille — ce n’est pas de la compression.
- Compression déterministe — produire une sortie compressée identique octet par octet pour une entrée identique, obtenue en verrouillant le niveau et le format du compresseur.