Verklein de bestandsgrootte van een PDF met compressie en subsetting
In één oogopslag
Sectie met titel “In één oogopslag”Je wilt de kleinst mogelijke PDF die de inhoud toelaat, zonder verlies aan getrouwheid. NextPDF biedt daarvoor twee instellingen voor de bestandsgrootte, en beide staan standaard aan:
- Streamcompressie. De writer verpakt elke paginacontentstream en elk ingesloten lettertypeprogramma in een FlateDecode-stream (zlib). De vlag
compressvanNextPDF\Core\Configbewaart deze instelling. Je leest die terug met de witherwithCompress()wanneer je een streaming-document opbouwt. - Lettertype-subsetting. Wanneer je een TrueType- of CFF-lettertype insluit, bouwt de writer het lettertypeprogramma opnieuw op zodat het alleen de glyphs bevat die het document gebruikt, en comprimeert het resultaat vervolgens met FlateDecode. Dit gebeurt automatisch. Er is geen vlag om in te stellen en er is geen aanroep nodig. Een CJK-lettertype met
20,000glyphs dat enkele honderden glyphs aan een document bijdraagt, wordt ingesloten op een fractie van de schijfgrootte.
Eerst een eerlijke kanttekening: NextPDF Core biedt geen herbemonstering van afbeeldingen, geen instelling voor afbeeldingskwaliteit, geen schakelaar voor object-streams en geen instelling voor resource-deduplicatie. De twee bovengenoemde instellingen zijn de enige instellingen voor de grootte. De rest van dit recipe laat zien hoe je ze correct gebruikt en wat elk ervan niet doet.
Vereisten: een Core-installatie (composer require nextpdf/core:^3) en, voor het subsetting-traject, een lettertypebestand dat je mag insluiten.
Installeren
Sectie met titel “Installeren”composer require nextpdf/core:^3Conceptueel overzicht
Sectie met titel “Conceptueel overzicht”Een PDF is een boom van objecten. De grootste objecten zijn meestal contentstreams (de tekenoperatoren voor elke pagina) en lettertypeprogramma’s (de ingesloten glyph-omtrekken). Beide laten zich goed comprimeren, dus de effectiefste instelling voor de grootte is om ze met FlateDecode te comprimeren. FlateDecode is de PDF 2.0-naam voor een in zlib verpakte DEFLATE-stream (ISO 32000-2:2020 §7.4.4), en dat is het filter dat NextPDF toepast.
De writer legt het DEFLATE-compressieniveau vast op 9, het maximum volgens RFC 1951, via NextPDF\Writer\PinnedZlibCompressor. Niveau 9 kost iets meer CPU in ruil voor de kleinste stream. Door het niveau vast te leggen blijft de uitvoer ook deterministisch, omdat de zlib-header het niveau codeert en een afwijkend niveau de bytes zou veranderen. Je kiest het niveau niet — de engine legt het vast zodat twee runs over dezelfde invoer byte-identieke streams opleveren.
De tweede hefboom is lettertype-subsetting. Een lettertypebestand op schijf bevat elke glyph die het lettertype definieert, maar een document dat „Invoice 2026” afdrukt, heeft er maar een paar nodig. NextPDF\Typography\FontSubsetter (voor TrueType) en NextPDF\Typography\CffSubsetter (voor CFF / OpenType) doorlopen de codepunten die het document daadwerkelijk heeft weergegeven, lossen afhankelijkheden van samengestelde glyphs op en bouwen alleen de vereiste lettertypetabellen opnieuw op. Ze produceren een geldige binaire subset van het lettertype met een deterministische subset-prefixtag van zes letters (ISO 32000-2:2020 §9.9). De writer past dit toe telkens wanneer de set gebruikte glyphs van een ingesloten lettertype bekend is, en comprimeert de subset vervolgens met FlateDecode. Als subsetting van een bepaald lettertype minder dan tien procent zou besparen, geeft de subsetter in plaats daarvan het oorspronkelijke programma terug, omdat de kosten van het opnieuw opbouwen een marginale winst niet waard zijn.
De kernboodschap: je houdt PDF’s klein door compressie aan te laten staan (de standaard) en door echte lettertypebestanden in te sluiten (zodat subsetting iets heeft om te verkleinen), niet door een lange lijst opties te finetunen.
API-oppervlak
Sectie met titel “API-oppervlak”De enige grootte-instelling die je zelf instelt, staat op het configuratieobject.
NextPDF\Core\Config is een onveranderlijk, final readonly value-object met getypeerde wither-methoden. De groottegerelateerde member is:
compress(bool, standaardtrue) — schakelt FlateDecode-compressie in. Je leest die terug metwithCompress(bool $compress): self, die een nieuweConfigteruggeeft met de gewijzigde vlag en alle andere velden behoudt.
Koppel bij het aanmaken een Config aan een document:
NextPDF\Core\Document::createStandalone(?Config $config = null): selfbouwt een document met tijdelijke registries voor een CLI-script of een kortlevend proces, en past daarbij jeConfigtoe.
Twee members bepalen waarmee de mechanismen voor bestandsgrootte moeten werken, maar geen van beide is zelf een compressie-instelling:
imageCacheBytes(int, standaard52_428_800) begrenst de in-memory afbeeldingscache, enwithImageCacheBytes(int $bytes): selfwijzigt die. Dit begrenst het piekgeheugen tijdens een build. Het herbemonstert, hercomprimeert of verkleint de afbeeldingen die je insluit niet op een andere manier — het is een geheugenplafond, geen instelling voor de uitvoergrootte.fontsDirectory(string) enwithFontsDirectory(string $dir): selfstellen het standaard zoekpad voor lettertypebestanden in, dat het subsetting-traject voedt.
Werken met lettertypen verloopt via het typografie-oppervlak op het document:
setFont(string $family, string $style = '', float $size = 12.0): staticselecteert een lettertype. Wanneer de familie verwijst naar een insluitbaar lettertypebestand, registreert de writer de codepunten die je weergeeft, zodat hij dat lettertype bij het opslaan kan subsetten.addFontDirectory(string $directory): staticregistreert een extra map om in te zoeken naar lettertypebestanden.
Uitvoer verloopt via het standaardtrio: getPdfData(): string geeft de bytes terug, save(string $path): void schrijft ze atomisch weg, en output(?string $filename, OutputDestination $dest): string verzorgt de HTTP-response.
Subsetting heeft geen publieke methode en geen vlag. Het komt vanzelf voort uit het insluiten van een lettertype en het weergeven van tekst. De writer stuurt FontSubsetter / CffSubsetter voor je aan binnen NextPDF\Writer\PdfFontWriter.
Codevoorbeeld — Snelstart
Sectie met titel “Codevoorbeeld — Snelstart”Dit voorbeeld bouwt een document met compressie expliciet ingeschakeld en een ingesloten, gesubset lettertype, en schrijft vervolgens de bytes weg. Het laat foutafhandeling achterwege om de structuur van de aanroepen helder te houden. Het productievoorbeeld hieronder voegt de volledige beveiligingen toe.
<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Config;use NextPDF\Core\Document;
// compress defaults to true; setting it explicitly documents intent.$config = (new Config())->withCompress(true);
$doc = Document::createStandalone($config);$doc->addFontDirectory(__DIR__ . '/fonts');$doc->addPage();
// Selecting an embeddable face records the glyphs used, so the writer// subsets this font automatically when the document is built.$doc->setFont('LiberationSans', '', 12);$doc->cell(0, 10, 'Invoice 2026 - subsetted, compressed output.', newLine: true);
$pdf = $doc->getPdfData();
file_put_contents(__DIR__ . '/small.pdf', $pdf);
printf("Wrote %d bytes.\n", strlen($pdf));Codevoorbeeld — Productie
Sectie met titel “Codevoorbeeld — Productie”Dit is een op zichzelf staand programma. Het bouwt een document met compressie aan, sluit een lettertype in vanuit een map die je beheert, geeft tekst weer zodat de subsetter over een set gebruikte glyphs beschikt, en schrijft het resultaat atomisch weg. Het vangt de meest specifieke NextPDF-uitzonderingen op die de build- en opslagtrajecten kunnen opwerpen, en werpt elk daarvan opnieuw op met context in plaats van die in te slikken. Laat NEXTPDF_FONT_DIR verwijzen naar een map met een TrueType- of CFF-lettertype dat je mag insluiten; het programma valideert het pad voordat het iets insluit.
<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Config;use NextPDF\Core\Document;use NextPDF\Exception\CompressionException;use NextPDF\Exception\InvalidConfigException;
/** * Resolve and validate the font directory from a server-controlled source. * * Reading the directory from the environment keeps the path off the request * surface. The function rejects a missing or unreadable directory so the * embedding path never runs against untrusted or absent input. */function resolveFontDirectory(): string{ $configured = getenv('NEXTPDF_FONT_DIR'); $dir = $configured !== false && $configured !== '' ? $configured : __DIR__ . '/fonts';
$real = realpath($dir); if ($real === false || !is_dir($real) || !is_readable($real)) { throw new RuntimeException(sprintf('Font directory "%s" is not a readable directory.', $dir)); }
return $real;}
/** * Build a compressed, font-subsetted document and return its bytes. * * @param non-empty-string $fontDirectory Validated directory of embeddable fonts. * * @return string Raw PDF bytes. */function buildCompactPdf(string $fontDirectory): string{ // compress is true by default; pin it so the intent is explicit and the // streaming writer path honours it regardless of any wrapper defaults. $config = (new Config()) ->withCompress(true) ->withFontsDirectory($fontDirectory) // Bound the image cache so a build cannot exhaust memory. This is a // memory ceiling, not an output-size control. ->withImageCacheBytes(16 * 1024 * 1024);
$doc = Document::createStandalone($config); $doc->addFontDirectory($fontDirectory); $doc->addPage();
// Rendering with an embeddable face records the used codepoints, which the // writer turns into a font subset at build time. $doc->setFont('LiberationSans', '', 12); $doc->cell(0, 10, 'Invoice 2026', newLine: true); $doc->cell(0, 10, 'Compressed streams plus an automatic font subset.', newLine: true);
// getPdfData() triggers the build: page streams and the subset font program // are FlateDecode-compressed before the bytes are returned. return $doc->getPdfData();}
try { $fontDirectory = resolveFontDirectory(); $pdf = buildCompactPdf($fontDirectory);} catch (CompressionException $e) { // Raised if the zlib encoder hard-fails while compressing a stream. throw new RuntimeException( sprintf('Compression failed for a %s stream.', $e->getAlgorithm()), previous: $e, );} catch (InvalidConfigException $e) { // Raised by the output path for an invalid destination configuration. throw new RuntimeException( sprintf('Output configuration "%s" was rejected.', $e->getConfigKey()), previous: $e, );}
$out = getenv('NEXTPDF_COOKBOOK_OUTPUT');$path = $out !== false && $out !== '' ? $out : __DIR__ . '/small.pdf';
if (file_put_contents($path, $pdf) === false) { throw new RuntimeException(sprintf('Could not write PDF to "%s".', $path));}
printf("Wrote %d bytes to %s.\n", strlen($pdf), $path);Verwachte STDOUT (het aantal bytes hangt af van het lettertype en de build):
Wrote <n> bytes to <path>.Randgevallen en valkuilen
Sectie met titel “Randgevallen en valkuilen”- Compressie staat standaard aan. Een nieuwe
Configheeftcompressoptruestaan. Je hebtwithCompress()zelden nodig. Stel die alleen expliciet in om de bedoeling vast te leggen, of om af te zien van compressie voor een debug-build waarin je de ruwe streams wilt lezen. - Compressie uitzetten maakt bestanden groter, niet kleiner.
withCompress(false)is een diagnostisch hulpmiddel om ongecomprimeerde streams te inspecteren. Het is nooit een optimalisatie van de grootte. Lever aan met compressie. - Subsetting vereist een echt ingesloten lettertype. Naar de Base14-standaardlettertypen (Helvetica, Times, Courier en verwante varianten) wordt op naam verwezen. In een gewoon document bevatten ze geen ingesloten programma, dus er valt niets te subsetten. Subsetting verkleint alleen lettertypen die je vanuit een lettertypebestand insluit.
- Subsetting is automatisch en stil. Er is geen vlag, geen methode en geen bevestiging. Als je een lettertype hebt ingesloten en er tekst mee hebt weergegeven, heeft de writer het gesubset. Het ingesloten programma draagt een subset-prefixtag van zes letters (bijvoorbeeld
ABCDEF+LiberationSans) zodat een lezer een subset van een volledige insluiting kan onderscheiden. - Een kleine besparing behoudt het volledige lettertype. Wanneer een subset minder dan tien procent van de programmagrootte zou besparen, geeft de subsetter het origineel terug. Dit is een bewuste ondergrens: de kosten van het opnieuw opbouwen zijn een marginale winst niet waard. Het insluiten van een lettertype dat al heel klein is, of het weergeven van bijna al zijn glyphs, kan in dit geval belanden.
imageCacheBytesis geen instelling voor de afbeeldingsgrootte. Het begrenst geheugen, niet uitvoerbytes. NextPDF Core sluit de afbeeldingsgegevens in die je aanlevert; er is geen herbemonsterings-, neerbemonsterings- of hercoderingsstap. Als je kleinere afbeeldingen nodig hebt, verklein en hercodeer je ze voordat je ze insluit.- Er bestaat geen instelling voor object-streams of deduplicatie. NextPDF Core biedt geen schakelaar voor PDF 2.0-object-streams of voor resource-deduplicatie. Zoek er niet naar — de grootte-hefbomen zijn streamcompressie en lettertype-subsetting.
Prestaties
Sectie met titel “Prestaties”Compressie op niveau 9 is de dominante CPU-kost bij het wegschrijven van een stream. Het ruilt enkele procenten buildtijd in voor de kleinste uitvoer. De kost is lineair in het aantal ongecomprimeerde bytes, dus het aantal pagina’s en de hoeveelheid ingesloten lettertypegegevens bepalen het budget. Subsetting voegt per ingesloten lettertype een eenmalige doorgang toe die de tabeldirectory van het lettertype parseert, de gebruikte-glyph-afsluiting oplost en de vereiste tabellen opnieuw opbouwt. Voor een groot CJK-lettertype is dit de duurste van de twee hefbomen, maar het draait eenmaal per lettertype, niet eenmaal per pagina. De besparingsondergrens van tien procent bestaat deels om die doorgang van het kritieke pad te houden wanneer hij niet zou lonen. Een klein document met één ingesloten subset blijft ruim binnen een wandkloktijd van 1500 ms en een piekbudget van 96 MB. Begrens imageCacheBytes tot je werkelijke plafond zodat een build die veel afbeeldingen insluit, snel faalt op geheugen in plaats van te gaan swappen.
Beveiligingsnotities
Sectie met titel “Beveiligingsnotities”De build draait in het proces; er verlaten geen documentbytes de host en er wordt geen netwerkaanroep gedaan. Behandel elk extern aangeleverd lettertype en elke extern aangeleverde afbeelding als niet-vertrouwde invoer:
- Valideer de lettertypemap. Het productievoorbeeld leest het lettertypepad uit een door de server beheerde omgevingsvariabele en wijst een ontbrekende of onleesbare map af vóór het insluiten. Leid een lettertypepad nooit af uit een verzoekveld.
- Sluit alleen lettertypen in die je mag herdistribueren. Een subset is nog steeds een ingesloten lettertypeprogramma. Bevestig dat de licentie insluiting toestaat voordat je een document uitlevert dat het lettertype bevat.
- Misvormde lettertypen werpen een uitzondering op; ze raken niet stil beschadigd. Een lettertypebestand dat niet kan worden geparseerd, werpt
NextPDF\Exception\FontParsingExceptionop, en een harde zlib-fout werptNextPDF\Exception\CompressionExceptionop. Vang de meest specifieke uitzondering op en handel ernaar. Verpak de build nooit in een legecatch. - Interpoleer nooit gebruikersinvoer in het uitvoerpad. Het voorbeeld schrijft naar een vast pad of een door de server beheerd zijkanaal, en het wijst stream wrappers en null-bytes af via de atomische writer in
save(). Leid uitvoerpaden af uit door de server beheerde waarden om path traversal te voorkomen. - Geen geheimen in het document. Sluit geen referenties, tokens of interne identificatoren in een gegenereerd document in dat je aan een client teruggeeft.
Conformiteit
Sectie met titel “Conformiteit”Dit recipe doet zelf geen normatieve standaardclaim. De mechanismen die het gebruikt, zijn gedefinieerd in de PDF 2.0-specificatie: FlateDecode-streamcompressie (ISO 32000-2:2020 §7.4.4) en lettertype-subsetbenoeming met een subset-prefix van zes tekens (ISO 32000-2:2020 §9.9). NextPDF voert beide uit als onderdeel van zijn standaard schrijftraject; je configureert ze niet verder dan de vlag compress. Het structural reproduceerbaarheidsprofiel dat deze pagina declareert, weerspiegelt dat de writer het DEFLATE-niveau vastlegt, zodat de gecomprimeerde streams deterministisch zijn, terwijl identificatoren op documentniveau nog steeds tussen runs kunnen variëren tenzij je ook deterministische instellingen configureert. Zie het hieronder gelinkte embed-and-subset-recipe voor de insluitingsmechanismen achter subsetting.
Zie ook
Sectie met titel “Zie ook”- Een TrueType-lettertype insluiten en subsetten — registreer een lettertype, geef er tekst mee weer en inspecteer de ingesloten subsettag.
- Tekst en lettertypen samenstellen — het bredere oppervlak voor tekst- en lettertypesamenstelling dat het subsetting-traject voedt.
- Referentie van de configuratiemodule — het volledige
Configvalue-object, de withers en hun standaardwaarden. - Uitzonderingsbewuste foutafhandeling — de NextPDF uitzonderingshiërarchie achter
CompressionException,FontParsingExceptionenInvalidConfigException.