Aller au contenu
getnextpdf.com

L'économie de la taille d'un fichier PDF

Spec: ISO 32000-2, §7.5.7

Deux PDF peuvent paraître identiques pixel pour pixel à l’écran et différer d’un facteur dix sur le disque. La différence n’est presque jamais le contenu que tu vois ; c’est la manière dont le fichier a été assemblé en dessous. Cette page est un tour d’horizon de l’économie de taille : où vont réellement les octets d’un PDF, et les quatre leviers sur lesquels un auteur dépense un budget d’octets.

C’est le compagnon pourquoi les fichiers sont gros de Flux et filtres, qui couvre comment un filtre décode. Celui-ci reste sur le budget.

La taille d’un fichier est rarement une métrique de vanité. C’est de la bande passante à chaque téléchargement, du stockage à chaque archivage, et de la latence à chaque aperçu. Une facture de 12 Mo qui aurait dû peser 400 Ko n’est pas un problème cosmétique lorsque tu en génères un million par mois — c’est une facture trente fois plus salée.

Le frustrant, c’est que le surpoids est généralement invisible. Le document se rend correctement, s’ouvre bien, s’imprime bien. Rien ne te dit que le même artefact aurait pu peser une fraction de sa taille, car les octets gaspillés sont structurels, pas visuels. Pour les trouver, tu dois regarder le budget, pas la page.

Considère un PDF comme un budget que tu dépenses sur quatre postes.

  • Surcoût par objet. Chaque objet indirect porte une enveloppe N G obj / endobj, et est suivi par une entrée de références croisées. Un document chargé en pages a des milliers de petits objets, et les enveloppes s’accumulent. Les flux d’objets suppriment cette enveloppe pour tout un groupe d’objets ; chaque objet empaqueté conserve sa propre entrée de références croisées, mais sous forme d’une entrée compacte de type 2.
  • Octets d’image. Pour tout document comportant des photographies ou des scans, les images dominent, et le plus grand levier de loin est le choix de filtre — un codec sans perte par rapport à un codec d’image avec perte est la différence entre des mégaoctets et des kilooctets.
  • Octets de police. Une police incorporée complète représente des centaines de kilooctets de glyphes que tu n’utilises jamais. Le sous-ensemble ne conserve que les glyphes que le document dessine réellement.
  • L’index. La table de références croisées qui permet à un lecteur de trouver chaque objet peut elle-même être un flux compressé plutôt que du texte brut.

Réussis les quatre et le fichier est petit. Manque-en un et il domine tout le reste de ce que tu as bien fait.

L’écrivain de NextPDF est un sérialiseur en flux à passe unique : il ajoute les octets de chaque objet à mesure qu’ils sont produits et enregistre pour chacun une entrée classique de références croisées « en usage ». Cette valeur par défaut est rapide, prévisible, et produit un fichier stable en octets — mais ce n’est pas la disposition la plus petite possible, et NextPDF est honnête à ce sujet.

Un PDF est un graphe d’objets indirects. La plupart d’entre eux sont de petits dictionnaires : nœuds de page, dictionnaires d’annotation, éléments d’arbre de structure, entrées de plan. Chacun paie une taxe fixe — les mots-clés obj / endobj, les numéros d’objet et de génération, et une entrée de références croisées qui le localise. Sur un document comportant des milliers de petits objets, cette taxe est une tranche significative du fichier.

Un flux d’objets collecte beaucoup de ces petits objets hors flux dans un seul flux et les compresse ensemble (Spec: ISO 32000-2, §7.5.7). L’enveloppe obj / endobj est supprimée pour tout le groupe ; les valeurs à l’intérieur sont stockées bout à bout sans mots-clés par objet, puis dégonflées en un seul bloc — ce qui compresse aussi mieux, car le compresseur déduplicateur voit maintenant tous ces dictionnaires similaires d’un coup. L’entrée de références croisées ne disparaît pas — chaque objet empaqueté en a encore besoin d’une — mais elle rétrécit en une entrée binaire compacte de type 2 dans le flux de références croisées (plus à ce sujet au Levier 2).

Dans NextPDF, ceci est délivré par ObjectStreamPacker, un post-processeur autonome qui prend un PDF à flux de références croisées terminé et réécrit les objets éligibles dans un unique /Type /ObjStm. Les règles qu’il suit viennent directement de la norme : un objet éligible est un objet de génération zéro, hors flux, après exclusion des objets spéciaux qui doivent rester directement adressables. §7.5.7 interdit de stocker un objet flux à l’intérieur d’un flux d’objets, de sorte que les objets flux — contenu, polices, images — conservent leurs propres entrées ; et ObjectStreamPacker décline en outre le propre objet flux de références croisées du document (il est réécrit) et le dictionnaire /Encrypt, qui doivent tous deux rester directement adressables. Tout le reste de génération zéro et hors flux est empaqueté.

Object stream packing (ObjStm) — edition availability
EditionAvailability
CoreFull support in the open-source core via ObjectStreamPacker. It is opt-in: the single-pass writer’s default emits classic in-use entries, so output stays byte-identical unless you enable packing. The packer is deterministic, with its own reproducible golden baseline.
ProNot in this edition
EnterpriseNot in this edition

L’activation à la demande est une posture délibérée, pas une limitation qui se cache. La sortie par défaut est stable en octets et correspond aux références dorées existantes ; activer l’empaquetage opte pour une disposition différente, plus petite, tout aussi déterministe. Tu choisis le compromis, et le moteur ne le fait jamais dans ton dos.

Une fois que les objets vivent à l’intérieur d’un flux d’objets, l’index qui pointe vers eux change de forme. Un lecteur trouve un objet empaqueté à travers une entrée de références croisées compressée — une entrée de type 2 qui nomme le flux d’objets et l’index à l’intérieur de celui-ci (Spec: ISO 32000-2, §7.5.8.3). Parce que l’ensemble des références croisées est lui-même un flux /Type /XRef, l’index pour des milliers d’objets est empaqueté en binaire et dégonflé plutôt qu’écrit en lignes de texte brut. La carte rétrécit en même temps que le territoire.

ObjectStreamPacker reconstruit exactement ceci : il émet le flux d’objets unique, puis un flux de références croisées réécrit portant une entrée compacte de type 1 pour chaque objet conservé et une entrée de type 2 pour chaque objet empaqueté, préservant chaque numéro d’objet pour que les références existantes restent valides.

Pour tout document comportant de vraies images, ce levier écrase les autres. Les octets sont la même image ; le codec est le budget. Les noms de filtre vivent dans le jeu de filtres standard (Spec: ISO 32000-2, §7.4), et le choix entre eux est une décision de taille :

  • FlateDecode est sans perte. Parfait pour les dessins au trait, les captures d’écran et tout ce qui a des aplats de couleur — et ruineux pour une photographie, où sans perte signifie chaque octet de l’original.
  • DCTDecode est le JPEG : avec perte, et pour les photographies le bon choix de loin, souvent une réduction d’un facteur dix pour une baisse de qualité que personne ne remarque.
  • JPXDecode est le JPEG 2000 : compression par ondelettes avec une courbe qualité/taille différente, mais un support inégal et interdit par certains profils d’archivage.

C’est exactement là où comment un filtre décode importe, et c’est le travail de l’article Flux et filtres. Le point économique est plus étroit : une photographie stockée sans perte est la cause unique la plus courante d’un PDF inutilement énorme, et aucune quantité d’empaquetage en flux d’objets ne sauvera un fichier dont le vrai poids est un scan non passé au JPEG.

Une police incorporée est un programme. Une police complète peut représenter plusieurs centaines de kilooctets, car elle porte chaque glyphe que le créateur de la fonte ait jamais dessiné — des milliers de caractères à travers des écritures que tu n’utiliseras jamais dans ce document. Le sous-ensemble n’incorpore que les glyphes que le document dessine réellement, transformant ce programme en une petite fraction de lui-même. Une lettre d’une page n’a pas besoin de la totalité d’une police compatible CJK ; elle a besoin des quelques dizaines de glyphes qu’elle compose. Les mécaniques de la façon dont les glyphes sont sélectionnés et réindexés sont leur propre sujet — vois Polices : la partie difficile.

  1. Image bytesFor media-heavy files, the largest line item. The filter choice — lossless Flate vs a lossy image codec like DCT (or JPX in a lossy mode) — is the dominant size lever.
  2. Font bytesA full embedded font is mostly glyphs you never draw. Subsetting keeps only the used glyphs, often shrinking the program by an order of magnitude.
  3. Per-object overheadThousands of small dictionaries each pay an obj/endobj envelope plus a cross-reference entry. Object streams (ObjStm) drop the envelope for the whole group; each object keeps a compact type-2 cross-reference entry.
  4. The indexA compressed cross-reference stream with type-2 entries binary-packs and deflates the map of every object, replacing plaintext index rows.
Où vont les octets : les quatre leviers sur lesquels un auteur dépense le budget de taille d'un PDF, dans l'ordre où ils dominent généralement un fichier réel.

Il n’y a pas d’API fabriquée à montrer ici, car les décisions de taille les plus importantes sont prises avant que les octets n’atteignent l’écrivain — et le seul levier structurel que NextPDF expose est une unique activation à la demande. Conceptuellement, le budget se lit comme ceci :

  • Fournis les photographies en JPEG pour qu’elles atterrissent sous DCTDecode, et non ré-encodées sans perte. Le plus grand gain est un choix sur les données source, pas un drapeau d’écrivain.
  • Laisse l’écrivain sous-ensembler les polices incorporées pour que seuls les glyphes dessinés soient expédiés.
  • Pour un document comportant de nombreux petits objets, opte pour l’empaquetage en flux d’objets, qui route le fichier terminé à travers ObjectStreamPacker pour regrouper les petits objets hors flux et réécrire le flux de références croisées.

Le chemin par défaut — entrées classiques « en usage », pas d’ObjStm — est la bonne base de référence : déterministe, stable en octets et facile à vérifier. L’empaquetage est l’amélioration réfléchie quand c’est le nombre d’objets, et non le poids des images, qui gonfle le fichier.

Le piège est de se tourner vers un bouton « compresser le PDF » en s’attendant à ce qu’il règle tout. La compression n’est pas un levier ; ce sont quatre, et ils ne se substituent pas les uns aux autres. L’empaquetage en flux d’objets ne peut pas rétrécir une photographie — c’est le travail du filtre d’image. Un JPEG parfait ne peut pas compenser une police que tu as oublié de sous-ensembler. Et rien de tout cela n’aide si le vrai surpoids est un scan de 10 Mo stocké sans perte parce que personne n’a choisi DCTDecode pour lui.

La seconde idée fausse est que plus petit est toujours strictement mieux. Ce n’est pas le cas. Les flux d’objets sont incompatibles avec la linéarisation — la disposition fast-web-view qui fixe le placement absolu des objets pour que la première page se diffuse tôt. Et certains profils d’archivage restreignent quels filtres d’image sont même autorisés. La taille est un axe. Elle s’arbitre contre le streaming, la conformité d’archivage et la reproductibilité, et le bon point sur la courbe dépend de ce à quoi sert le document.

Les quatre leviers sont l’économie structurelle de la taille de fichier. Ils ne sont pas une garantie universelle « rends-le plus petit », et NextPDF ne prétend pas être un ré-optimiseur pour des PDF entrants arbitraires.

ObjectStreamPacker est une optimisation au mieux qui ne risque jamais la correction. Il décline — en retournant l’entrée inchangée — lorsque le fichier n’est pas un PDF à flux de références croisées, lorsqu’il est chiffré, lorsqu’il porte une signature numérique (re-disposer les objets décalerait les plages d’octets qu’une signature protège), lorsqu’il contient déjà des flux d’objets, ou lorsqu’il n’y a aucun objet éligible à empaqueter. Il émet exactement un flux d’objets pour tout le document ; il ne se scinde pas en une collection /Extends, ce qui est hors périmètre et conforme pour les tailles de document que NextPDF produit.

Les leviers d’image et de police sont en grande partie des décisions sur l’entrée. L’écrivain de NextPDF ne transcode pas implicitement un bitmap sans perte en un flux d’image avec perte — ré-encoder une photographie vers DCTDecode ou JPXDecode est une décision explicite d’encodage d’image en amont, pas quelque chose que le sérialiseur fait dans ton dos — ni ne récupère de glyphes d’une police que l’appelant a demandé d’incorporer en entier. Les plus grands gains de taille se font en amont du sérialiseur d’octets ; le travail du moteur est de ne pas gaspiller le budget que tu lui apportes.

  • Flux d’objets (ObjStm) — un seul flux qui contient de nombreux petits objets indirects hors flux, compressés ensemble, de sorte que l’enveloppe obj / endobj est supprimée pour le groupe. Chaque objet empaqueté conserve toujours sa propre entrée de références croisées, sous forme d’entrée compacte de type 2. À activer à la demande dans NextPDF.
  • Objet indirect — un objet numéroté dans le graphe d’un PDF, enveloppé dans une enveloppe obj / endobj et suivi par l’index de références croisées. L’enveloppe est le surcoût par objet.
  • Flux de références croisées — le flux /Type /XRef qui indexe chaque objet, empaqueté en binaire et dégonflé plutôt qu’écrit en lignes de texte brut.
  • Entrée compressée (type 2) — une entrée de références croisées qui pointe vers un objet vivant à l’intérieur d’un flux d’objets, nommant le flux et l’index à l’intérieur de celui-ci.
  • Sous-ensemble de polices — incorporer uniquement les glyphes qu’un document dessine réellement, plutôt que la fonte complète, rétrécissant le programme de police incorporé.
  • Filtre sans perte vs avec perte — un codec sans perte (FlateDecode) reproduit chaque octet ; un codec d’image avec perte (DCTDecode, ou JPXDecode en mode avec perte) écarte les détails imperceptibles pour un résultat bien plus petit. (Le JPEG 2000 / JPX peut aussi être configuré sans perte.) Le choix est le levier dominant sur un fichier riche en médias.