Streams en filters
ISO 32000-2 §7.4 Evidence: Standard-backed
In een oogopslag
Sectie met titel “In een oogopslag”De meeste bytes in een echte PDF zitten in streams: pagina-inhoud, lettertypen, afbeeldingen en de cross-reference stream zelf. Vrijwel geen van die bytes wordt onbewerkt opgeslagen; ze gaan eerst door een of meer filters. Deze pagina legt uit welke filters je tegenkomt, waarvoor ze dienen, waar ze problemen geven en waarom NextPDF zijn compressie vastpint, zodat dezelfde invoer altijd dezelfde bytes oplevert.
Waarom dit belangrijk is
Sectie met titel “Waarom dit belangrijk is”Een stream en zijn filter vormen een contract: “deze bytes zijn gecomprimeerd met deflate en daarna base-85-gecodeerd — decodeer ze in die volgorde om de echte gegevens te krijgen.” Als de /Filter-vermelding niet overeenkomt met wat de bytes daadwerkelijk zijn, als de /Length verkeerd is, of als twee filters in de verkeerde volgorde worden vermeld, is de stream niet te decoderen en gaat de inhoud die hij bevatte verloren. Een reader gokt niet heuristisch; hij volgt de dictionary.
Daar is nog een tweede, minder zichtbare prijs. Als de compressor van een bibliotheek niet-deterministisch is — een andere zlib-build, een ander niveau, andere interne blokgrenzen — leveren twee runs die een identieke PDF zouden moeten produceren, toch twee verschillende bestanden op. Dat doorbreekt reproduceerbaarheid op byteniveau. Verbroken reproduceerbaarheid breekt vervolgens golden-file-tests, verificatie van ondertekende builds en elke pijplijn die uitvoer vergelijkt. Filters bepalen dus zowel of de PDF correct is als of de PDF hetzelfde is.
De korte versie
Sectie met titel “De korte versie”- Een stream-object is een dictionary plus een blok bytes, ingepakt tussen
stream…endstream, met een/Lengthen meestal een/Filter. - De
/Filter-vermelding noemt het decodeerfilter — of een array met filters die als pijplijn in volgorde worden toegepast. - De filters vallen uiteen in twee families: compressie (FlateDecode, LZWDecode, RunLengthDecode, DCTDecode, JPXDecode, JBIG2Decode) en ASCII-transport (ASCIIHexDecode, ASCII85Decode), plus het speciale Crypt-filter voor versleuteling.
- Het filter dat je het vaakst ziet, is FlateDecode — zlib/deflate. Het is de standaard voor inhoud, lettertypen en de cross-reference stream.
- NextPDF pint zijn Flate-uitvoer op een vast niveau en formaat, zodat dezelfde invoerbytes altijd naar dezelfde uitvoerbytes comprimeren.
Hoe NextPDF dit aanpakt
Sectie met titel “Hoe NextPDF dit aanpakt”NextPDF schrijft stream-objecten via één buffer-helper en comprimeert via één vastgepinde compressor — met opzet.
BinaryBuffer::writeStream() (src/Support/BinaryBuffer.php) verpakt streaminhoud met de bijbehorende dictionary, schrijft altijd een /Length die gelijk is aan de werkelijke bytelengte en voegt eventuele extra vermeldingen samen die de aanroeper meegeeft, zoals /Filter. Er is geen codepad waarin de gedeclareerde lengte kan afwijken van de geschreven bytes, omdat de lengte rechtstreeks uit de inhoudsstring zelf wordt afgeleid.
Compressie verloopt via PinnedZlibCompressor (src/Writer/PinnedZlibCompressor.php). Deze klasse bestaat om één reden: gzcompress zonder expliciet niveau valt terug op de runtime-standaard van zlib, die historisch per build kon verschillen. De 2-byte zlib-header codeert het niveau zelfs indirect, dus “de standaard” levert geen stabiele uitvoer op. De compressor pint het niveau vast op het RFC 1951-maximum en levert altijd zlib-omhulde deflate op (RFC 1950-header + Adler-32-trailer), precies wat /Filter /FlateDecode verwacht. Een harde zlib-fout wordt een getypeerde exception in plaats van een stille terugval naar niet-gecomprimeerde uitvoer — een stream wordt nooit stilletjes onbewerkt weggeschreven.
De cross-reference stream zelf is een concreet voorbeeld van dit alles: CrossReferenceStream (src/Core/CrossReferenceStream.php) bouwt een binaire tabel, comprimeert die en schrijft die weg als stream-object met /Type /XRef, een /W-array met veldbreedtes en /Filter /FlateDecode. De index waarmee een reader elk object kan vinden, is zelf een gefilterde stream.
| Filter | Familie | Waarvoor het dient | Waar het misgaat |
|---|---|---|---|
| FlateDecode | Compressie | zlib/deflate; de standaard voor inhoud, lettertypen, xref-streams | Een niet-deterministische zlib-build kan “identieke” PDF’s byte voor byte laten verschillen |
| LZWDecode | Compressie | Oudere Lempel–Ziv–Welch-compressie | Verouderd; vervangen door Flate, af en toe nog te zien in oude bestanden |
| DCTDecode | Compressie | JPEG-gecodeerde kleur- of grijswaardenafbeeldingen | Lossy — een al-DCT-afbeelding opnieuw coderen verslechtert die nogmaals |
| JPXDecode | Compressie | JPEG 2000-wavelet-beeldgegevens | Niet toegestaan door sommige archiveringsprofielen; brede ondersteuning is wisselend |
| JBIG2Decode | Compressie | Bi-level (1-bit) beeldcompressie | Mag niet worden gebruikt met inline-afbeeldingen; lossy modi kunnen scans wijzigen |
| RunLengthDecode | Compressie | Bytegeoriënteerde run-length-compressie | Helpt alleen bij gegevens met lange reeksen van één byte; kan andere gegevens juist groter maken |
| ASCIIHexDecode | Transport | Binaire gegevens als hexadecimale cijfers | Verdubbelt de omvang; alleen voor 7-bit-veilige kanalen, nooit om ruimte te besparen |
| ASCII85Decode | Transport | Binaire gegevens als base-85-ASCII | ~25% overhead; handig voor transport, geen compressie |
| Crypt | Beveiliging | Past de security handler van het document toe | Een cross-reference stream mag geen Crypt-filter gebruiken |
De standaardfilterset van PDF, per familie, met de faalwijze die bij elk filter hoort. NextPDF schrijft FlateDecode voor inhoud, lettertypen en de cross-reference stream; de ASCII-transportfilters zijn bedoeld voor 7-bit-kanalen, nooit om de omvang te verkleinen.
Wat het bewijs zegt
Sectie met titel “Wat het bewijs zegt”Het filtermechanisme wordt gedefinieerd door Spec: ISO 32000-2, §7.4 ISO 32000-2 §7.4 . Een stream-dictionary noemt zijn filters via /Filter. Wanneer de vermelding meer dan één filter bevat, vormen die filters een decodeerpijplijn en worden ze in volgorde toegepast. Een writer codeert een stream om die te comprimeren of 7-bit-veilig te maken. Een reader roept de bijbehorende decodeerfilters aan om de oorspronkelijke gegevens te herstellen. Evidence: Standard-backed
De filtertabel van de standaard deelt elk filter in. FlateDecode decomprimeert met zlib/deflate gecodeerde gegevens en reproduceert de oorspronkelijke tekst of binaire gegevens. DCTDecode reproduceert via JPEG beeldsamples die het origineel benaderen — het woord “benaderen” in de standaard vertelt je dat het lossy is. LZWDecode, RunLengthDecode, JBIG2Decode, JPXDecode en het Crypt-filter worden daar ook elk gedefinieerd, waarbij JBIG2 expliciet wordt verboden voor inline-afbeeldingen.
De cross-reference stream laat zien hoe het formaat zijn eigen mechaniek op zichzelf toepast: het is een stream-object (/Type /XRef,
Spec: ISO 32000-2, §7.5.8 ISO 32000-2 §7.5.8 ) waarvan de /W-array
de bytebreedte van elk vermeldingsveld in de gedecodeerde stream aangeeft. De
standaard vereist dat hij niet versleuteld is en geen Crypt-filter gebruikt.
De CrossReferenceStream van NextPDF houdt zich hier precies aan — FlateDecode,
expliciete /W, geen versleuteling.
Praktisch voorbeeld
Sectie met titel “Praktisch voorbeeld”Een pagina-inhoudsstream, gecomprimeerd met Flate. Dit is veruit de meest voorkomende vorm: een dictionary met /Length en /Filter, gevolgd door de gecomprimeerde bytes tussen stream en 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// endobjEen reader doet het omgekeerde: hij leest /Length bytes, haalt ze door FlateDecode omdat /Filter dat zegt, en krijgt de oorspronkelijke operatoren terug. Door de compressor vast te pinnen is die heen-en-terugweg niet alleen correct; hij is elke keer identiek. Daarop steunen golden-file-tests en controles op ondertekende builds.
Veelvoorkomend misverstand
Sectie met titel “Veelvoorkomend misverstand”De valkuil is dat je ASCII-filters als compressie behandelt. ASCIIHexDecode en ASCII85Decode maken een stream groter — respectievelijk ongeveer het dubbele en ongeveer 25%. Ze bestaan om binaire gegevens door een kanaal te verplaatsen dat alleen veilig is voor 7-bits tekst, niet om ruimte te besparen. Als je ASCII85 kiest om een PDF te “verkleinen”, doet dat het tegenovergestelde. De andere helft van hetzelfde misverstand is geloven dat FlateDecode voor afbeeldingen “gratis” lossless is. Flate is lossless, maar als de afbeelding al DCT (JPEG)-gecodeerd was, wordt die slechter wanneer je die opnieuw inpakt of via een lossy filter transcodeert, ongeacht wat Flate eromheen doet. De filterpijplijn behoudt precies wat je erin stopt — inclusief een hercompressie-artefact dat je er per ongeluk in hebt gestopt.
Grenzen en beperkingen
Sectie met titel “Grenzen en beperkingen”Deze pagina behandelt hoe filters worden gedeclareerd en toegepast, niet het algoritme op bitniveau in elk filter. De determinismegarantie betreft specifiek de Flate-uitvoer van NextPDF voor de streams die het schrijft. Die geldt over PHP-minorversies en standaardconforme zlib-builds heen, maar de standaard staat een deflate-encoder expliciet toe om andere interne blokgrenzen te kiezen, zodat bit voor bit gelijke uitvoer over werkelijk verschillende zlib-implementaties (bijvoorbeeld een standaard zlib versus zlib-ng) niet wordt beloofd. De build-omgeving is om die reden vastgepind.
NextPDF gebruikt FlateDecode en de ASCII-transportfilters voor de gegevens die het wegschrijft. Het is geen beeldtranscoder. Het belooft niet dat het een willekeurige binnenkomende JPEG2000- of JBIG2-stream opnieuw inpakt, en lossy compromissen in beeldkwaliteit zijn een eigenschap van de brongegevens, niet iets wat een writer ongedaan kan maken.
Mini-FAQ
Sectie met titel “Mini-FAQ”Waarom is FlateDecode overal? Het is lossless, breed bruikbaar, goed ondersteund en past goed bij de tekst- en operatorinhoud van de meeste PDF’s. Het is de veilige standaard voor inhoudsstreams, ingebedde lettertypen en de cross-reference stream.
Kan ik compressie uitschakelen? Je kunt /Filter weglaten en onbewerkte bytes opslaan; een reader accepteert dat. Het bestand wordt groter en verder verbetert er niets; buiten foutopsporing is er zelden een reden voor.
Waarom het compressieniveau überhaupt vastpinnen? Zodat de uitvoer reproduceerbaar is. Een niet-vastgepind niveau (of een andere zlib-build) kan de gecomprimeerde bytes wijzigen zonder de gedecomprimeerde inhoud te veranderen — correct, maar niet identiek; dat ondermijnt verificatie op byteniveau.
Gerelateerde documentatie
Sectie met titel “Gerelateerde documentatie”- Wat een PDF werkelijk is — het objectmodel waarin de streams op deze pagina zich bevinden.
- Lettertypen: het lastige deel — ingebedde lettertypeprogramma’s zijn gefilterde streams, met hun eigen faalwijzen.
- PDF 2.0: wat er is veranderd — hoe de 2.0-baseline streams behandelt, inclusief de cross-reference stream waarop NextPDF standaard terugvalt.
Woordenlijst
Sectie met titel “Woordenlijst”- Stream-object — een dictionary plus een blok bytes tussen
streamenendstream, met een/Lengthen meestal een/Filter. - Filter — een benoemde decodeertransformatie die een reader toepast op de bytes van een stream (bijvoorbeeld
FlateDecode). - Filterpijplijn — een array van filters die in volgorde worden toegepast; de volgorde in de array is de decodeervolgorde.
- FlateDecode — het zlib/deflate-filter; de standaardcompressie voor inhoud, lettertypen en cross-reference streams.
- DCTDecode — het JPEG-beeldfilter; lossy, dus opnieuw coderen verslechtert de afbeelding nogmaals.
- ASCII-transportfilter — ASCIIHexDecode / ASCII85Decode; maakt gegevens 7-bit-veilig ten koste van de omvang — geen compressie.
- Deterministische compressie — bit voor bit gelijke gecomprimeerde uitvoer produceren voor identieke invoer, bereikt door het niveau en formaat van de compressor vast te pinnen.