Zmniejszanie rozmiaru pliku PDF przez kompresję i tworzenie podzbiorów czcionek
W skrócie
Dział zatytułowany „W skrócie”Celem jest uzyskanie najmniejszego pliku PDF, na jaki pozwala treść, bez utraty jakości. NextPDF udostępnia dwa mechanizmy kontroli rozmiaru pliku i oba są domyślnie włączone:
- Kompresja strumieni. Mechanizm zapisu opakowuje każdy strumień treści strony i każdy osadzony program czcionki w strumień FlateDecode (zlib). Za to ustawienie odpowiada flaga
NextPDF\Core\Configo nazwiecompress. Użyj witherawithCompress(), gdy budujesz dokument strumieniowy. - Tworzenie podzbioru czcionki. Gdy osadzasz czcionkę TrueType lub CFF, mechanizm zapisu przebudowuje program czcionki tak, aby zawierał tylko glify używane w dokumencie, a następnie kompresuje wynik za pomocą FlateDecode. Dzieje się to automatycznie. Nie trzeba ustawiać żadnej flagi ani wywoływać żadnej metody. Krój CJK liczący
20,000glifów, z których dokument wykorzystuje zaledwie kilkaset, zostaje osadzony jako ułamek rozmiaru, jaki zajmuje na dysku.
Dla jasności jedna uwaga na początek: NextPDF Core nie udostępnia ponownego próbkowania obrazów, regulacji jakości obrazu, przełącznika strumieni obiektów ani ustawienia deduplikacji zasobów. Dwa powyższe mechanizmy to jedyne sposoby kontroli rozmiaru. Pozostała część tego przepisu pokazuje, jak używać ich poprawnie i czego każdy z nich nie robi.
Wymagania wstępne: instalacja Core (composer require nextpdf/core:^3) oraz, na potrzeby ścieżki tworzenia podzbioru, plik czcionki, na którego osadzenie masz licencję.
Instalacja
Dział zatytułowany „Instalacja”composer require nextpdf/core:^3Przegląd koncepcyjny
Dział zatytułowany „Przegląd koncepcyjny”Plik PDF to drzewo obiektów. Największymi obiektami są zwykle strumienie treści (operatory rysowania dla każdej strony) oraz programy czcionek (osadzone kontury glifów). Obie grupy dobrze się kompresują, więc najskuteczniejszym sposobem kontroli rozmiaru jest skompresowanie ich za pomocą FlateDecode. FlateDecode to stosowana w PDF 2.0 nazwa strumienia DEFLATE opakowanego w zlib (ISO 32000-2:2020 §7.4.4); taki filtr emituje NextPDF.
Mechanizm zapisu ustawia poziom kompresji DEFLATE na stałe na 9, czyli maksimum według RFC 1951, za pośrednictwem NextPDF\Writer\PinnedZlibCompressor. Poziom 9 kosztuje nieco więcej czasu procesora, ale daje najmniejszy strumień. Stały poziom pomaga też utrzymać determinizm wyniku, ponieważ nagłówek zlib koduje poziom, a jego zmiana zmieniłaby bajty. Nie wybierasz poziomu — silnik ustala go tak, aby dwa przebiegi na tym samym wejściu dawały identyczne co do bajta strumienie.
Drugim mechanizmem jest tworzenie podzbioru czcionki. Plik czcionki na dysku zawiera wszystkie glify zdefiniowane przez krój pisma, ale dokument zawierający tekst „Invoice 2026” potrzebuje tylko kilku z nich. NextPDF\Typography\FontSubsetter (dla TrueType) oraz NextPDF\Typography\CffSubsetter (dla CFF / OpenType) analizują punkty kodowe faktycznie wyrenderowane w dokumencie, rozwiązują zależności glifów złożonych i przebudowują tylko wymagane tablice czcionki. Emitują poprawny binarny plik czcionki będącej podzbiorem z deterministycznym sześcioliterowym znacznikiem prefiksu podzbioru (ISO 32000-2:2020 §9.9). Mechanizm zapisu robi to za każdym razem, gdy znany jest zestaw używanych glifów osadzonej czcionki, a następnie kompresuje podzbiór za pomocą FlateDecode. Jeśli utworzenie podzbioru danego kroju pozwoliłoby zaoszczędzić mniej niż dziesięć procent, mechanizm tworzenia podzbioru zwraca zamiast tego oryginalny program, ponieważ marginalny zysk nie uzasadnia kosztu przebudowy.
W praktyce pliki PDF pozostają małe, gdy zostawiasz kompresję włączoną (domyślnie) i osadzasz rzeczywiste pliki czcionek (aby tworzenie podzbioru miało co zmniejszać), zamiast dostrajać długą listę opcji.
Powierzchnia API
Dział zatytułowany „Powierzchnia API”Jedyny parametr rozmiaru, który ustawiasz, znajduje się w obiekcie konfiguracji.
NextPDF\Core\Config to niemutowalny obiekt wartości final readonly z typowanymi metodami typu wither. Składowa związana z rozmiarem to:
compress(bool, domyślnietrue) — włącza kompresję FlateDecode. Zmieniasz ją za pomocąwithCompress(bool $compress): self, które zwraca nowy obiektConfigze zmienioną flagą i zachowanymi wszystkimi pozostałymi polami.
Przekaż obiekt Config do dokumentu w czasie konstrukcji:
NextPDF\Core\Document::createStandalone(?Config $config = null): selfbuduje dokument z efemerycznymi rejestrami dla skryptu CLI lub krótko żyjącego procesu, używając przekazanego obiektuConfig.
Dwie składowe określają materiał, na którym działają mechanizmy kontroli rozmiaru, ale żadna z nich sama nie steruje kompresją:
imageCacheBytes(int, domyślnie52_428_800) ogranicza pamięć podręczną obrazów w pamięci, awithImageCacheBytes(int $bytes): selfją zmienia. Ogranicza to szczytowe zużycie pamięci podczas budowy. Nie wykonuje ponownego próbkowania ani kompresji i w żaden inny sposób nie zmniejsza osadzanych obrazów — to limit pamięci, a nie kontrola rozmiaru wyniku.fontsDirectory(string) orazwithFontsDirectory(string $dir): selfustawiają domyślną ścieżkę wyszukiwania plików czcionek używaną przez ścieżkę tworzenia podzbioru.
Z czcionkami pracujesz przez powierzchnię typografii dokumentu:
setFont(string $family, string $style = '', float $size = 12.0): staticwybiera krój. Gdy rodzinie odpowiada osadzalny plik czcionki, mechanizm zapisu rejestruje renderowane punkty kodowe, aby móc utworzyć podzbiór tego kroju w momencie zapisu.addFontDirectory(string $directory): staticrejestruje dodatkowy katalog do wyszukiwania plików czcionek.
Za wyjście odpowiada standardowa trójka: getPdfData(): string zwraca bajty, save(string $path): void zapisuje je atomowo, a output(?string $filename, OutputDestination $dest): string obsługuje wysyłanie przez HTTP.
Nie ma publicznej metody ani flagi do tworzenia podzbioru. Wynika ono z osadzenia czcionki i renderowania tekstu. Mechanizm zapisu uruchamia FontSubsetter / CffSubsetter wewnątrz NextPDF\Writer\PdfFontWriter.
Przykład kodu — szybki start
Dział zatytułowany „Przykład kodu — szybki start”Ten przykład tworzy dokument z jawnie włączoną kompresją oraz osadzoną czcionką, z której powstaje podzbiór, a następnie zapisuje bajty. Pomija obsługę błędów, aby zachować przejrzystość wywołań. Poniższy przykład produkcyjny dodaje pełne zabezpieczenia.
<?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));Przykład kodu — produkcyjny
Dział zatytułowany „Przykład kodu — produkcyjny”To samodzielny program. Buduje dokument z włączoną kompresją, osadza czcionkę z kontrolowanego katalogu, renderuje tekst, aby mechanizm tworzenia podzbioru miał zestaw używanych glifów, i zapisuje wynik atomowo. Przechwytuje najbardziej szczegółowe wyjątki NextPDF zgłaszane przez ścieżki budowy i zapisu, a następnie zgłasza je ponownie z kontekstem zamiast połykać. Ustaw NEXTPDF_FONT_DIR na katalog zawierający krój TrueType lub CFF, na którego osadzenie masz licencję; program weryfikuje ścieżkę przed osadzeniem.
<?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);Oczekiwane STDOUT (liczba bajtów zależy od czcionki i przebiegu budowy):
Wrote <n> bytes to <path>.Przypadki brzegowe i pułapki
Dział zatytułowany „Przypadki brzegowe i pułapki”- Kompresja jest domyślnie włączona. Świeży obiekt
Configmacompressustawione natrue. Zwykle w ogóle nie potrzebujeszwithCompress(). Ustawiaj ją jawnie tylko po to, aby udokumentować intencję, albo aby zrezygnować z kompresji w budowie diagnostycznej, w której chcesz odczytać surowe strumienie. - Wyłączenie kompresji powiększa pliki, a nie zmniejsza.
withCompress(false)to narzędzie diagnostyczne do inspekcji nieskompresowanych strumieni. Nigdy nie jest to optymalizacja rozmiaru. Wdrażaj z włączoną kompresją. - Tworzenie podzbioru wymaga rzeczywistej osadzonej czcionki. Standardowe czcionki Base14 (Helvetica, Times, Courier i ich krewni) są wskazywane po nazwie i w zwykłym dokumencie nie zawierają osadzonego programu, więc nie ma z czego utworzyć podzbioru. Tworzenie podzbioru zmniejsza tylko kroje osadzane z pliku czcionki.
- Tworzenie podzbioru jest automatyczne i ciche. Nie ma flagi, metody ani potwierdzenia. Jeśli osadzisz czcionkę i wyrenderujesz nią tekst, mechanizm zapisu utworzy jej podzbiór. Osadzony program zawiera sześcioliterowy znacznik prefiksu podzbioru (na przykład
ABCDEF+LiberationSans), aby czytnik mógł odróżnić podzbiór od pełnego osadzenia. - Przy niewielkiej oszczędności zostaje pełna czcionka. Gdy podzbiór pozwoliłby zaoszczędzić mniej niż dziesięć procent rozmiaru programu, mechanizm tworzenia podzbioru zwraca oryginał. To celowy próg: marginalny zysk nie uzasadnia kosztu przebudowy. Osadzenie kroju, który jest już maleńki, lub wyrenderowanie niemal wszystkich jego glifów może trafić w ten przypadek.
imageCacheBytesnie jest pokrętłem rozmiaru obrazu. Ogranicza pamięć, a nie bajty wyniku. NextPDF Core osadza przekazane mu dane obrazu; nie ma kroku ponownego próbkowania, zmniejszania próbkowania ani ponownego kodowania. Aby uzyskać mniejsze obrazy, zmień ich rozmiar i zakoduj je ponownie przed osadzeniem.- Nie istnieje ustawienie strumieni obiektów ani deduplikacji. NextPDF Core nie udostępnia przełącznika strumieni obiektów PDF 2.0 ani deduplikacji zasobów. Nie szukaj go — mechanizmami kontroli rozmiaru są kompresja strumieni i tworzenie podzbioru czcionek.
Wydajność
Dział zatytułowany „Wydajność”Podczas zapisu strumienia kompresja na poziomie 9 zwykle dominuje koszt procesora. Kosztuje kilka procent czasu budowy w zamian za najmniejszy wynik. Koszt rośnie liniowo względem nieskompresowanej liczby bajtów, więc budżet wyznaczają liczba stron i ilość osadzonych danych czcionek. Dla każdego osadzonego kroju tworzenie podzbioru dodaje jednorazowy przebieg, który analizuje katalog tablic czcionki, rozwiązuje domknięcie używanych glifów i przebudowuje wymagane tablice. W przypadku dużego kroju CJK to droższy z dwóch mechanizmów, ale uruchamia się raz na czcionkę, a nie raz na stronę. Dziesięcioprocentowy próg oszczędności istnieje częściowo po to, aby utrzymać ten przebieg poza ścieżką krytyczną, gdy nie miałby się opłacić. Mały dokument z jednym osadzonym podzbiorem mieści się wygodnie w budżecie 1500 ms czasu rzeczywistego i 96 MB szczytowego zużycia pamięci. Ustaw imageCacheBytes zgodnie z rzeczywistym pułapem, aby budowa osadzająca wiele obrazów szybko zakończyła się błędem z powodu pamięci, zamiast korzystać z pliku wymiany.
Uwagi dotyczące bezpieczeństwa
Dział zatytułowany „Uwagi dotyczące bezpieczeństwa”Budowa odbywa się w procesie; żadne bajty dokumentu nie opuszczają hosta i nie dochodzi do żadnego wywołania sieciowego. Traktuj zewnętrzne czcionki i obrazy jako niezaufane dane wejściowe:
- Weryfikuj katalog czcionek. Przykład produkcyjny odczytuje ścieżkę czcionek z kontrolowanej przez serwer zmiennej środowiskowej i odrzuca brakujący lub nieczytelny katalog przed osadzeniem. Nigdy nie buduj ścieżki czcionki na podstawie pola żądania.
- Osadzaj tylko czcionki, na których rozpowszechnianie masz licencję. Podzbiór nadal jest osadzonym programem czcionki. Potwierdź, że licencja zezwala na osadzanie, zanim wdrożysz dokument zawierający dany krój.
- Uszkodzone czcionki powodują wyjątek, a nie ciche uszkodzenie danych. Plik czcionki, którego nie można sparsować, zgłasza
NextPDF\Exception\FontParsingException, a twardy błąd zlib zgłaszaNextPDF\Exception\CompressionException. Przechwyć najbardziej szczegółowy wyjątek i zareaguj na niego. Nigdy nie opakowuj budowy w pustycatch. - Nigdy nie wstawiaj danych wejściowych użytkownika do ścieżki wyjściowej. Przykład zapisuje do stałej ścieżki lub kontrolowanego przez serwer kanału bocznego i odrzuca wrappery strumieni oraz bajty null za pośrednictwem atomowego mechanizmu zapisu w
save(). Wyprowadzaj ścieżki wyjściowe z wartości kontrolowanych przez serwer, aby uniknąć ataków typu path traversal. - Żadnych sekretów w dokumencie. Nie osadzaj poświadczeń, tokenów ani wewnętrznych identyfikatorów w generowanym dokumencie zwracanym klientowi.
Zgodność
Dział zatytułowany „Zgodność”Ten przepis sam w sobie nie formułuje żadnego normatywnego twierdzenia o zgodności ze standardami. Wykorzystywane przez niego mechanizmy są zdefiniowane w specyfikacji PDF 2.0: kompresja strumieni FlateDecode (ISO 32000-2:2020 §7.4.4) oraz nazewnictwo podzbioru czcionki z sześcioznakowym prefiksem podzbioru (ISO 32000-2:2020 §9.9). NextPDF emituje oba w ramach standardowej ścieżki zapisu; nie konfigurujesz ich poza flagą compress. Profil odtwarzalności structural deklarowany przez tę stronę odzwierciedla fakt, że mechanizm zapisu ustawia poziom DEFLATE na stałe, dzięki czemu skompresowane strumienie są deterministyczne, podczas gdy identyfikatory dokumentu mogą nadal różnić się między przebiegami, o ile nie skonfigurujesz także ustawień deterministycznych. Mechanikę osadzania stojącą za tworzeniem podzbioru opisuje przepis o osadzaniu i tworzeniu podzbioru, do którego odnośnik znajduje się poniżej.
Zobacz też
Dział zatytułowany „Zobacz też”- Osadzanie i tworzenie podzbioru czcionki TrueType — zarejestruj krój, wyrenderuj nim tekst i sprawdź osadzony znacznik podzbioru.
- Komponowanie tekstu i czcionek — szersza powierzchnia API do komponowania tekstu i czcionek, która zasila ścieżkę tworzenia podzbioru.
- Dokumentacja modułu konfiguracji — pełny obiekt wartości
Config, jego withery i ich wartości domyślne. - Obsługa błędów oparta na wyjątkach — hierarchia wyjątków NextPDF związana z
CompressionException,FontParsingExceptioniInvalidConfigException.