PDF-Dateigröße durch Komprimierung und Subsetting reduzieren
Auf einen Blick
Abschnitt betitelt „Auf einen Blick“Sie möchten das kleinste PDF erzeugen, das der Inhalt erlaubt, ohne Wiedergabetreue einzubüßen. NextPDF stellt Ihnen zwei Größenhebel bereit, beide standardmäßig aktiv:
- Stream-Komprimierung. Der Writer schreibt jeden Seiteninhalts-Stream und jedes eingebettete Font-Programm als FlateDecode-Stream (zlib). Das
NextPDF\Core\Config-Flagcompresssteuert diese Einstellung. Geben Sie sie mit dem WitherwithCompress()explizit vor, wenn Sie ein Streaming-Dokument bauen. - Font-Subsetting. Wenn Sie eine TrueType- oder CFF-Schriftart einbetten, baut der Writer das Font-Programm so um, dass es nur die Glyphen enthält, die das Dokument tatsächlich nutzt, und komprimiert das Ergebnis anschließend per FlateDecode. Das geschieht automatisch: Es gibt kein Flag zu setzen und keinen Aufruf auszuführen. Eine CJK-Schrift mit
20,000Glyphen, die nur ein paar hundert Glyphen zu einem Dokument beisteuert, wird mit einem Bruchteil ihrer Größe auf der Festplatte eingebettet.
Wichtig vorweg: NextPDF Core stellt weder ein Bild-Resampling noch einen Bildqualitätsregler, einen Object-Stream-Schalter oder eine Einstellung zur Ressourcen-Deduplizierung bereit. Die vorhandenen Größensteuerungen sind die beiden oben genannten. Der Rest dieses Recipe zeigt, wie Sie sie korrekt einsetzen und was jede von ihnen nicht leistet.
Voraussetzungen: eine Core-Installation (composer require nextpdf/core:^3) und, für den Subsetting-Pfad, eine Schriftdatei, die Sie einbetten dürfen.
Installation
Abschnitt betitelt „Installation“composer require nextpdf/core:^3Konzeptioneller Überblick
Abschnitt betitelt „Konzeptioneller Überblick“Ein PDF ist ein Baum aus Objekten. Die größten Objekte sind in der Regel Inhalts-Streams (die Zeichenoperatoren jeder Seite) und Font-Programme (die eingebetteten Glyphen-Umrisse). Beide sind hochgradig komprimierbare Text- und Binärdaten; deshalb ist der wirksamste einzelne Größenhebel, sie per FlateDecode zu komprimieren. FlateDecode ist der PDF 2.0-Name für einen zlib-gekapselten DEFLATE-Stream (ISO 32000-2:2020 §7.4.4) und der Filter, den NextPDF ausgibt.
Der Writer fixiert die DEFLATE-Komprimierungsstufe über NextPDF\Writer\PinnedZlibCompressor bei 9, dem Maximum laut RFC 1951. Stufe 9 tauscht etwas zusätzliche CPU-Zeit gegen den kleinsten Stream. Die festgelegte Stufe hält die Ausgabe außerdem deterministisch, denn der zlib-Header kodiert die Stufe, und eine driftende Stufe würde die Bytes verändern. Sie wählen die Stufe nicht: Die Engine legt sie fest, sodass zwei Läufe über dieselbe Eingabe exakt gleiche Streams erzeugen.
Der zweite Hebel ist Font-Subsetting. Eine Schriftdatei auf der Festplatte enthält jede Glyphe, die die Schrift definiert, aber ein Dokument, das „Invoice 2026“ druckt, braucht nur ein Dutzend davon. NextPDF\Typography\FontSubsetter (für TrueType) und NextPDF\Typography\CffSubsetter (für CFF / OpenType) durchlaufen die Codepoints, die das Dokument tatsächlich gerendert hat, lösen Abhängigkeiten zusammengesetzter Glyphen auf und bauen nur die benötigten Font-Tabellen neu. Sie geben ein gültiges Subset-Font-Binary mit einem deterministischen sechsbuchstabigen Subset-Präfix-Tag aus (ISO 32000-2:2020 §9.9). Der Writer wendet dies immer dann an, wenn die Menge der genutzten Glyphen einer eingebetteten Schrift bekannt ist, und komprimiert das Subset anschließend per FlateDecode. Wenn das Subsetting einer bestimmten Schrift weniger als zehn Prozent einsparen würde, gibt der Subsetter stattdessen das Originalprogramm zurück, weil sich der Aufwand des Neuaufbaus für einen geringfügigen Gewinn nicht lohnt.
Die zentrale Erkenntnis: Sie halten PDFs klein, indem Sie die Komprimierung aktiviert lassen (der Standard) und echte Schriftdateien einbetten (damit das Subsetting überhaupt etwas zum Verkleinern hat), nicht durch Feinjustieren einer langen Optionsliste.
API-Oberfläche
Abschnitt betitelt „API-Oberfläche“Der einzige Größenregler, den Sie setzen, liegt am Konfigurationsobjekt.
NextPDF\Core\Config ist ein unveränderliches final readonly-Value-Object mit typisierten Wither-Methoden. Relevant ist:
compress(bool, Standardtrue) — aktiviert die FlateDecode-Komprimierung. Setzen Sie diesen Wert mitwithCompress(bool $compress): self; die Methode gibt eine neueConfigmit geändertem Flag und ansonsten unveränderten Feldern zurück.
Übergeben Sie einem Dokument zur Konstruktionszeit eine Config:
NextPDF\Core\Document::createStandalone(?Config $config = null): selfbaut ein Dokument mit kurzlebigen Registries für ein CLI-Skript oder einen kurzlebigen Prozess und wendet dabei IhreConfigan.
Zwei Member beeinflussen, womit die Größenhebel arbeiten müssen, aber keines davon ist selbst eine Komprimierungssteuerung:
imageCacheBytes(int, Standard52_428_800) begrenzt den In-Memory-Bild-Cache, undwithImageCacheBytes(int $bytes): selfändert ihn. Das begrenzt den Spitzenspeicher während eines Builds. Es resampelt, rekomprimiert oder verkleinert die Bilder, die Sie einbetten, nicht — es ist eine Speicherobergrenze, keine Steuerung der Ausgabegröße.fontsDirectory(string) undwithFontsDirectory(string $dir): selfsetzen den Standard-Suchpfad für Schriftdateien, der den Subsetting-Pfad speist.
Die Arbeit mit Schriften erfolgt über die Typografie-Oberfläche am Dokument:
setFont(string $family, string $style = '', float $size = 12.0): staticwählt eine Schrift aus. Wenn die Familie auf eine einbettbare Schriftdatei aufgelöst wird, erfasst der Writer die Codepoints, die Sie rendern, sodass er diese Schrift zum Speicherzeitpunkt subsetten kann.addFontDirectory(string $directory): staticregistriert ein zusätzliches Verzeichnis, in dem nach Schriftdateien gesucht wird.
Die Ausgabe erfolgt über das übliche Trio: getPdfData(): string gibt die Bytes zurück, save(string $path): void schreibt sie atomar, und output(?string $filename, OutputDestination $dest): string übernimmt die HTTP-Auslieferung.
Subsetting hat keine öffentliche Methode und kein Flag. Es ergibt sich aus dem Einbetten einer Schrift und dem Rendern von Text — der Writer steuert FontSubsetter / CffSubsetter für Sie innerhalb von NextPDF\Writer\PdfFontWriter an.
Codebeispiel — Schnellstart
Abschnitt betitelt „Codebeispiel — Schnellstart“Dieses Beispiel baut ein Dokument mit explizit aktivierter Komprimierung und einer eingebetteten, gesubsetteten Schrift und schreibt anschließend die Bytes. Es lässt die Fehlerbehandlung weg, um die Aufrufform zu zeigen; das Produktionsbeispiel weiter unten ergänzt die vollständigen Absicherungen.
<?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));Codebeispiel — Produktion
Abschnitt betitelt „Codebeispiel — Produktion“Dies ist ein eigenständiges Programm. Es baut ein Dokument mit aktivierter Komprimierung, bettet eine Schrift aus einem von Ihnen kontrollierten Verzeichnis ein, rendert Text, damit der Subsetter über eine Menge genutzter Glyphen verfügt, und schreibt das Ergebnis atomar. Es fängt die spezifischsten NextPDF-Exceptions ab, die der Build- und der Save-Pfad auslösen, und löst dann jede davon mit Kontext erneut aus, statt sie zu verschlucken. Richten Sie NEXTPDF_FONT_DIR auf ein Verzeichnis, das eine TrueType- oder CFF-Schrift enthält, die Sie einbetten dürfen; das Programm validiert den Pfad, bevor es die Schrift einbettet.
<?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);Erwartete STDOUT-Ausgabe (die Byte-Anzahl hängt von der Schrift und vom Build ab):
Wrote <n> bytes to <path>.Randfälle & Stolpersteine
Abschnitt betitelt „Randfälle & Stolpersteine“- Komprimierung ist standardmäßig aktiv. Eine frische
Confighatcompressauftruegesetzt.withCompress()brauchen Sie in der Regel gar nicht. Setzen Sie es nur explizit, um die Absicht zu dokumentieren oder um es für einen Debugging-Build zu deaktivieren, bei dem Sie die rohen Streams lesen wollen. - Die Komprimierung auszuschalten macht Dateien größer, nicht kleiner.
withCompress(false)ist ein Diagnosehilfsmittel, um unkomprimierte Streams zu inspizieren. Es ist niemals eine Größenoptimierung. Liefern Sie mit aktivierter Komprimierung aus. - Subsetting braucht eine echte eingebettete Schrift. Die Base14-Standardschriften (Helvetica, Times, Courier und ihre Verwandten) werden über den Namen referenziert und tragen in einem einfachen Dokument kein eingebettetes Programm, sodass es nichts zu subsetten gibt. Subsetting verkleinert nur Schriften, die Sie aus einer Schriftdatei einbetten.
- Subsetting ist automatisch und still. Es gibt kein Flag, keine Methode und keine Bestätigung. Wenn Sie eine Schrift eingebettet und damit Text gerendert haben, hat der Writer sie gesubsettet. Das eingebettete Programm trägt ein sechsbuchstabiges Subset-Präfix-Tag (zum Beispiel
ABCDEF+LiberationSans), damit ein Reader ein Subset von einer vollständigen Einbettung unterscheiden kann. - Bei geringer Einsparung bleibt die vollständige Schrift erhalten. Wenn ein Subset weniger als zehn Prozent der Programmgröße einsparen würde, gibt der Subsetter das Original zurück. Das ist eine bewusste Untergrenze: Der Neuaufbau lohnt sich nicht für einen geringfügigen Gewinn. Das Einbetten einer bereits winzigen Schrift oder das Rendern nahezu aller ihrer Glyphen kann in diesen Fall fallen.
imageCacheBytesist kein Bildgrößenregler. Es begrenzt Speicher, nicht Ausgabe-Bytes. NextPDF Core bettet die Bilddaten ein, die Sie ihm geben; es gibt keinen Resampling-, Downsampling- oder Re-Encoding-Schritt. Wenn Sie kleinere Bilder brauchen, ändern Sie ihre Größe und kodieren Sie sie neu, bevor Sie sie einbetten.- Es gibt keine Object-Stream- oder Dedup-Einstellung. NextPDF Core stellt keinen Schalter für PDF 2.0-Object-Streams oder für Ressourcen-Deduplizierung bereit. Suchen Sie nicht danach — die Größenhebel sind Stream-Komprimierung und Font-Subsetting.
Performance
Abschnitt betitelt „Performance“Komprimierung auf Stufe 9 ist der dominierende CPU-Kostenfaktor beim Schreiben eines Streams. Sie tauscht ein paar Prozent Build-Zeit gegen die kleinste Ausgabe. Die Kosten skalieren linear mit der unkomprimierten Byte-Anzahl; deshalb bestimmen die Seitenzahl und die Menge der eingebetteten Font-Daten das Budget. Subsetting fügt pro eingebetteter Schrift einen einmaligen Durchlauf hinzu, der das Tabellenverzeichnis der Schrift parst, die Abhängigkeiten der genutzten Glyphen auflöst und die benötigten Tabellen neu baut. Für eine große CJK-Schrift ist dies der teurere der beiden Hebel, aber er läuft einmal pro Schrift, nicht einmal pro Seite. Die Einsparungsuntergrenze von zehn Prozent existiert zum Teil, um diesen Durchlauf vom heißen Pfad fernzuhalten, wenn er sich nicht auszahlen würde. Ein kleines Dokument mit einem eingebetteten Subset liegt problemlos innerhalb einer Wall-Clock-Grenze von 1500 ms und eines Spitzenbudgets von 96 MB. Begrenzen Sie imageCacheBytes auf Ihre reale Obergrenze, damit ein Build, der viele Bilder einbettet, beim Speicher schnell fehlschlägt, statt auszulagern.
Sicherheitshinweise
Abschnitt betitelt „Sicherheitshinweise“Der Build läuft im Prozess; keine Dokument-Bytes verlassen den Host, und es erfolgt kein Netzwerkaufruf. Behandeln Sie jede extern gelieferte Schrift und jedes extern gelieferte Bild als nicht vertrauenswürdige Eingabe:
- Validieren Sie das Schriftverzeichnis. Das Produktionsbeispiel liest den Schriftpfad aus einer servergesteuerten Umgebungsvariablen und weist ein fehlendes oder nicht lesbares Verzeichnis vor dem Einbetten zurück. Leiten Sie einen Schriftpfad niemals aus einem Request-Feld ab.
- Betten Sie nur Schriften ein, die Sie weiterverbreiten dürfen. Ein Subset ist immer noch ein eingebettetes Font-Programm. Stellen Sie sicher, dass die Lizenz das Einbetten erlaubt, bevor Sie ein Dokument ausliefern, das die Schrift enthält.
- Fehlerhafte Schriften lösen Exceptions aus, statt stillschweigend beschädigte Ausgabe zu erzeugen. Eine Schriftdatei, die nicht geparst werden kann, löst
NextPDF\Exception\FontParsingExceptionaus, und ein harter zlib-Fehler löstNextPDF\Exception\CompressionExceptionaus. Fangen Sie die spezifischste Exception ab und behandeln Sie sie entsprechend. Wickeln Sie den Build niemals in ein leerescatch. - Interpolieren Sie niemals Benutzereingaben in den Ausgabepfad. Das Beispiel schreibt an einen festen Pfad oder einen servergesteuerten Nebenkanal und weist Stream-Wrapper und Null-Bytes über den atomaren Writer in
save()zurück. Leiten Sie Ausgabepfade aus servergesteuerten Werten ab, um Path Traversal zu vermeiden. - Keine Geheimnisse im Dokument. Betten Sie keine Zugangsdaten, Tokens oder internen Bezeichner in ein generiertes Dokument ein, das Sie an einen Client zurückgeben.
Konformität
Abschnitt betitelt „Konformität“Dieses Recipe erhebt keinen eigenen normativen Standardanspruch. Die Mechanismen, die es nutzt, sind durch die PDF 2.0-Spezifikation definiert: FlateDecode-Stream-Komprimierung (ISO 32000-2:2020 §7.4.4) und Font-Subset-Benennung mit einem sechszeichigen Subset-Präfix (ISO 32000-2:2020 §9.9). NextPDF gibt beide als Teil seines Standard-Schreibpfads aus; über das compress-Flag hinaus konfigurieren Sie sie nicht. Das structural-Reproduzierbarkeitsprofil, das diese Seite deklariert, spiegelt wider, dass der Writer die DEFLATE-Stufe fixiert, sodass die komprimierten Streams deterministisch sind, während dokumentbezogene Bezeichner zwischen Läufen weiterhin variieren können, sofern Sie nicht zusätzlich deterministische Einstellungen konfigurieren. Weitere Details zur Einbettungsmechanik hinter dem Subsetting finden Sie im unten verlinkten Embed-und-Subset-Recipe.
Siehe auch
Abschnitt betitelt „Siehe auch“- Eine TrueType-Schrift einbetten und subsetten — eine Schrift registrieren, mit ihr rendern und das eingebettete Subset-Tag inspizieren.
- Text und Schriften komponieren — die umfassendere Oberfläche zur Text- und Schriftkomposition, die den Subsetting-Pfad speist.
- Referenz des Konfigurationsmoduls — das vollständige
Config-Value-Object, seine Wither-Methoden und deren Standardwerte. - Exception-bewusste Fehlerbehandlung — die NextPDF-Exception-Hierarchie hinter
CompressionException,FontParsingExceptionundInvalidConfigException.