Strumienie i filtry
ISO 32000-2 §7.4 Evidence: Standard-backed
W skrócie
Dział zatytułowany „W skrócie”Większość bajtów w rzeczywistym pliku PDF znajduje się wewnątrz strumieni: zawartość stron, czcionki, obrazy i sam strumień odsyłaczy. Prawie żadnych z tych bajtów nie przechowuje się w surowej postaci; najpierw przechodzą przez jeden lub więcej filtrów. Ta strona wyjaśnia, które filtry spotkasz, do czego służy każdy z nich, gdzie sprawiają kłopoty i dlaczego NextPDF ustawia kompresję na stałe, tak aby te same dane wejściowe zawsze dawały te same bajty.
Dlaczego to ma znaczenie
Dział zatytułowany „Dlaczego to ma znaczenie”Strumień i jego filtr stanowią umowę: „te bajty są skompresowane algorytmem deflate, a następnie zakodowane w base-85 — zdekoduj je w tej kolejności, aby uzyskać rzeczywiste dane.” Jeśli wpis /Filter nie zgadza się z tym, czym faktycznie są bajty, wartość /Length jest błędna albo dwa filtry wymieniono w złej kolejności, strumienia nie da się zdekodować, a obiekt, który niesie, zostaje utracony. Czytnik nie zgaduje na podstawie heurystyk; robi to, co nakazuje słownik.
Istnieje też drugi, mniej widoczny koszt. Jeśli kompresor biblioteki jest niedeterministyczny — inna kompilacja zlib, inny poziom, inne wewnętrzne granice bloków — dwa uruchomienia, które powinny wytworzyć identyczny plik PDF, dają dwa różne pliki. To psuje powtarzalność na poziomie bajtów. Zepsuta powtarzalność psuje z kolei testy na plikach wzorcowych, weryfikację podpisanych kompilacji oraz każdy potok porównujący wyjście. Filtry decydują zarówno o tym, czy plik PDF jest poprawny, jak i o tym, czy jest taki sam.
Wersja skrócona
Dział zatytułowany „Wersja skrócona”- Każdy obiekt strumienia to słownik z blokiem bajtów, ujęty w
stream…endstream, z wpisem/Lengthi zwykle/Filter. - Wpis
/Filterwskazuje filtr dekodujący — albo tablicę filtrów stosowanych jako potok, po kolei. - Filtry dzielą się na dwie rodziny: kompresja (FlateDecode, LZWDecode, RunLengthDecode, DCTDecode, JPXDecode, JBIG2Decode) oraz transport ASCII (ASCIIHexDecode, ASCII85Decode), plus specjalny filtr Crypt do szyfrowania.
- Tym, który zobaczysz najczęściej, jest FlateDecode — zlib/deflate. Jest domyślny dla treści, czcionek i strumienia odsyłaczy.
- NextPDF ustala na stałe swoje wyjście Flate na określony poziom i format, tak aby te same bajty wejściowe zawsze kompresowały się do tych samych bajtów wyjściowych.
Jak podchodzi do tego NextPDF
Dział zatytułowany „Jak podchodzi do tego NextPDF”NextPDF wytwarza obiekty strumieni za pomocą jednej funkcji pomocniczej bufora i kompresuje je przez jeden, ustalony na stałe kompresor — celowo.
BinaryBuffer::writeStream() (src/Support/BinaryBuffer.php) zapisuje zawartość strumienia wraz z jego słownikiem, zawsze ustawiając /Length na rzeczywistą długość w bajtach i scalając wszelkie dodatkowe wpisy dostarczone przez wywołującego, takie jak /Filter. Nie ma ścieżki, w której zadeklarowana długość mogłaby nie zgadzać się z zapisanymi bajtami, ponieważ długość jest pobierana z samego ciągu zawartości.
Kompresja przebiega przez PinnedZlibCompressor (src/Writer/PinnedZlibCompressor.php). Ta klasa istnieje z jednego powodu. gzcompress bez jawnego poziomu polega na domyślnym ustawieniu środowiska wykonawczego zlib, które w przeszłości różniło się między kompilacjami. 2-bajtowy nagłówek zlib pośrednio koduje nawet poziom, więc „ustawienie domyślne” nie jest stabilnym wyjściem. Kompresor ustala poziom na stałe na maksimum RFC 1951 i zawsze wytwarza deflate w opakowaniu zlib (nagłówek RFC 1950 + końcowa suma Adler-32), co jest dokładnie tym, czego oczekuje /Filter /FlateDecode. Błąd zlib staje się typowanym wyjątkiem, a nie cichym powrotem do nieskompresowanego wyjścia — strumień nigdy nie jest po cichu wytwarzany w surowej postaci.
Sam strumień odsyłaczy jest praktycznym przykładem wszystkich tych zasad: CrossReferenceStream (src/Core/CrossReferenceStream.php) buduje binarną tablicę, kompresuje ją i wytwarza jako obiekt strumienia z /Type /XRef, tablicą szerokości pól /W oraz /Filter /FlateDecode. Indeks, który pozwala czytnikowi odnaleźć każdy obiekt, również jest filtrowanym strumieniem.
| Filtr | Rodzina | Do czego służy | Gdzie zawodzi |
|---|---|---|---|
| FlateDecode | Kompresja | zlib/deflate; domyślny dla treści, czcionek, strumieni xref | Niedeterministyczna kompilacja zlib sprawia, że „identyczne” pliki PDF różnią się bajt po bajcie |
| LZWDecode | Kompresja | Starsza kompresja Lempel–Ziv–Welch | Przestarzała; wyparta przez Flate, sporadycznie wciąż spotykana w starych plikach |
| DCTDecode | Kompresja | Obrazy colour/grayscale zakodowane w JPEG | Stratny — ponowne kodowanie obrazu już w DCT pogarsza go raz jeszcze |
| JPXDecode | Kompresja | Falkowe dane obrazu JPEG 2000 | Niedozwolony przez niektóre profile archiwalne; wsparcie bywa nierówne |
| JBIG2Decode | Kompresja | Kompresja obrazów dwupoziomowych (1-bitowych) | Nie wolno go używać z obrazami osadzonymi; tryby stratne mogą zmienić skany |
| RunLengthDecode | Kompresja | Kodowanie długości serii zorientowane na bajty | Pomaga tylko danym z długimi seriami jednego bajtu; może powiększyć inne dane |
| ASCIIHexDecode | Transport | Dane binarne jako cyfry szesnastkowe | Podwaja rozmiar; tylko dla kanałów bezpiecznych dla 7 bitów, nigdy dla rozmiaru |
| ASCII85Decode | Transport | Dane binarne jako ASCII base-85 | Narzut około 25%; udogodnienie transportowe, nie kompresja |
| Crypt | Bezpieczeństwo | Stosuje moduł obsługi zabezpieczeń dokumentu | Strumień odsyłaczy nie może używać filtra Crypt |
Standardowy zestaw filtrów PDF według rodziny, wraz z trybem awarii, z którym każdy z nich jest związany. NextPDF zapisuje FlateDecode dla treści, czcionek oraz strumienia odsyłaczy; filtry transportu ASCII są przeznaczone dla kanałów 7-bitowych, nigdy do zmniejszania rozmiaru.
Co mówią dowody
Dział zatytułowany „Co mówią dowody”Mechanizm filtrów jest zdefiniowany w Spec: ISO 32000-2, §7.4 ISO 32000-2 §7.4 . Słownik strumienia wskazuje swoje filtry za pomocą /Filter. Gdy wpis wymienia więcej niż jeden filtr, filtry tworzą potok dekodujący i są stosowane w kolejności. Zapisujący koduje strumień, aby go skompresować lub uczynić bezpiecznym dla 7 bitów. Czytnik wywołuje odpowiednie filtry dekodujące, aby odzyskać oryginalne dane. Evidence: Standard-backed
Tablica filtrów w standardzie klasyfikuje każdy filtr. FlateDecode dekompresuje dane zakodowane w zlib/deflate, odtwarzając oryginalny tekst lub dane binarne. DCTDecode odtwarza próbki obrazu, które przybliżają oryginał za pomocą JPEG — słowo „przybliżają” oznacza w standardzie stratność. LZWDecode, RunLengthDecode, JBIG2Decode, JPXDecode oraz filtr Crypt również są tam zdefiniowane, przy czym JBIG2 jest wyraźnie wykluczony z obrazów osadzonych.
Strumień odsyłaczy stosuje ten mechanizm formatu także do siebie: jest obiektem strumienia (/Type /XRef,
Spec: ISO 32000-2, §7.5.8 ISO 32000-2 §7.5.8 ) którego tablica /W
podaje szerokość w bajtach każdego pola wpisu w zdekodowanym strumieniu. Standard
wymaga, aby nie był zaszyfrowany i nie używał filtra Crypt.
Klasa CrossReferenceStream w NextPDF realizuje to dokładnie — FlateDecode,
jawne /W, bez szyfrowania.
Praktyczny przykład
Dział zatytułowany „Praktyczny przykład”Strumień treści strony, skompresowany za pomocą Flate. To zdecydowanie najczęstsza postać: słownik z /Length i /Filter, a następnie skompresowane bajty pomiędzy stream a 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// endobjCzytnik wykonuje operację odwrotną: odczytuje /Length bajtów, przepuszcza je przez FlateDecode, ponieważ /Filter tak nakazuje, i odzyskuje oryginalne operatory. Gdy kompresor jest ustalony na stałe, ta podróż w obie strony jest nie tylko poprawna, lecz także identyczna za każdym razem, na czym opierają się kontrole plików wzorcowych i podpisanych kompilacji.
Częste nieporozumienie
Dział zatytułowany „Częste nieporozumienie”Pułapką jest traktowanie filtrów ASCII jako kompresji. ASCIIHexDecode i ASCII85Decode czynią strumień większym — odpowiednio mniej więcej dwukrotnie i o około 25%. Służą do przenoszenia danych binarnych kanałem, który jest bezpieczny tylko dla tekstu 7-bitowego, a nie do oszczędzania miejsca. Wybranie ASCII85, aby „zmniejszyć” plik PDF, daje skutek odwrotny. Druga część tego samego nieporozumienia to przekonanie, że FlateDecode jest bezstratny dla obrazów „za darmo”. Flate jest bezstratny, ale jeśli obraz był już zakodowany w DCT (JPEG), ponowne opakowanie lub przekodowanie przez filtr stratny pogarsza go niezależnie od tego, co Flate robi wokół niego. Potok filtrów zachowuje dokładnie to, co mu podasz — łącznie z artefaktem ponownej kompresji, który wprowadzono przez przypadek.
Ograniczenia i granice
Dział zatytułowany „Ograniczenia i granice”Ta strona omawia sposób deklarowania i stosowania filtrów, a nie algorytmy na poziomie bitów wewnątrz każdego z nich. Gwarancja determinizmu dotyczy konkretnie wyjścia Flate w NextPDF dla strumieni, które zapisuje NextPDF. Obowiązuje ona dla pomniejszych wersji PHP i kompilacji zlib zgodnych ze standardem, ale standard wyraźnie pozwala enkoderowi deflate wybrać inne wewnętrzne granice bloków, więc wyjście identyczne bajt po bajcie między naprawdę różnymi implementacjami zlib (na przykład standardowy zlib a zlib-ng) nie jest gwarantowane. Z tego powodu środowisko kompilacji jest ustalone na stałe.
NextPDF stosuje FlateDecode oraz filtry transportu ASCII dla danych, które wytwarza. Nie jest transkoderem obrazów. Nie obiecuje przepakowania dowolnego wejściowego strumienia JPEG2000 ani JBIG2, a stratne kompromisy dotyczące obrazu są właściwością danych źródłowych, a nie czymś, co komponent zapisujący mógłby cofnąć.
Mini-FAQ
Dział zatytułowany „Mini-FAQ”Dlaczego FlateDecode jest wszędzie? Jest bezstratny, uniwersalny, ma dobre wsparcie i dobrze pasuje do treści złożonej z tekstu i operatorów, jaką ma większość plików PDF. Jest bezpiecznym ustawieniem domyślnym dla strumieni treści, osadzonych czcionek oraz strumienia odsyłaczy.
Czy mogę wyłączyć kompresję? Możesz pominąć /Filter i przechowywać surowe bajty, a czytnik to zaakceptuje. Plik staje się większy i nic innego się nie poprawia; poza debugowaniem rzadko jest ku temu powód.
Dlaczego w ogóle ustalać na stałe poziom kompresji? Aby wyjście było powtarzalne. Nieustalony poziom kompresji (lub kompilacja zlib) może zmienić skompresowane bajty bez zmiany zdekompresowanej zawartości — wynik pozostaje poprawny, ale nie identyczny, co niweczy weryfikację na poziomie bajtów.
Powiązane dokumenty
Dział zatytułowany „Powiązane dokumenty”- Czym właściwie jest PDF — model obiektów, w którym funkcjonują strumienie z tej strony.
- Czcionki: trudna część — osadzone programy czcionek to filtrowane strumienie, z własnymi trybami awarii.
- PDF 2.0: co się zmieniło — jak PDF 2.0 traktuje strumienie oraz strumień odsyłaczy, który NextPDF wybiera domyślnie.
Słownik pojęć
Dział zatytułowany „Słownik pojęć”- Obiekt strumienia — słownik wraz z blokiem bajtów pomiędzy
streamaendstream, zawierający/Lengthi zwykle/Filter. - Filtr — nazwana transformacja dekodująca, którą czytnik stosuje do bajtów strumienia (na przykład
FlateDecode). - Potok filtrów — tablica filtrów stosowanych w kolejności; kolejność w tablicy to kolejność dekodowania.
- FlateDecode — filtr zlib/deflate; domyślna kompresja dla treści, czcionek i strumieni odsyłaczy.
- DCTDecode — filtr obrazu JPEG; stratny, więc ponowne kodowanie raz jeszcze pogarsza obraz.
- Filtr transportu ASCII — ASCIIHexDecode / ASCII85Decode; czyni dane bezpiecznymi dla 7 bitów kosztem rozmiaru — nie jest kompresją.
- Kompresja deterministyczna — wytwarzanie skompresowanego wyjścia identycznego bajt po bajcie dla identycznego wejścia, osiągane przez ustalenie na stałe poziomu i formatu kompresora.