Przejdź do głównej zawartości
getnextpdf.com

Ekonomia rozmiaru pliku PDF

Spec: ISO 32000-2, §7.5.7

Dwa pliki PDF mogą wyglądać na ekranie identycznie piksel po pikselu, a różnić się na dysku dziesięciokrotnie. Różnicą prawie nigdy nie jest treść, którą widzisz; jest nią to, jak plik został złożony pod spodem. Ta strona to przewodnik po ekonomii rozmiaru: dokąd naprawdę idą bajty PDF oraz cztery dźwignie, na które autor wydaje budżet bajtów.

To towarzysz dlaczego pliki są duże dla Strumieni i filtrów, które omawiają, jak filtr dekoduje. Ta strona trzyma się budżetu.

Rozmiar pliku rzadko jest metryką próżności. To przepustowość przy każdym pobraniu, magazyn przy każdym archiwum oraz opóźnienie przy każdym podglądzie. Faktura 12 MB, która powinna mieć 400 KB, nie jest problemem kosmetycznym, gdy generujesz milion takich miesięcznie — to trzydziestokrotny rachunek.

Frustrujące jest to, że przerost jest zwykle niewidoczny. Dokument renderuje się poprawnie, otwiera się dobrze, drukuje dobrze. Nic ci nie mówi, że ten sam artefakt mógł być ułamkiem rozmiaru, ponieważ zmarnowane bajty są strukturalne, a nie wizualne. Aby je znaleźć, musisz spojrzeć na budżet, a nie na stronę.

Pomyśl o PDF jako o budżecie, który wydajesz na cztery pozycje.

  • Narzut na obiekt. Każdy obiekt pośredni niesie obwiednię N G obj / endobj i jest śledzony przez jeden wpis odwołań krzyżowych. Dokument bogaty w strony ma tysiące maleńkich obiektów, a obwiednie się sumują. Strumienie obiektów zrzucają tę obwiednię dla całej grupy obiektów; każdy upakowany obiekt wciąż zachowuje własny wpis odwołań krzyżowych, ale jako zwięzły wpis typu 2.
  • Bajty obrazu. Dla każdego dokumentu ze zdjęciami lub skanami obrazy dominują, a największą pojedynczą dźwignią jest wybór filtra — bezstratny kodek kontra stratny kodek obrazu to różnica między megabajtami a kilobajtami.
  • Bajty czcionki. Pełna osadzona czcionka to setki kilobajtów glifów, których nigdy nie używasz. Tworzenie podzbioru zachowuje tylko glify, które dokument faktycznie rysuje.
  • Indeks. Tablica odwołań krzyżowych, która pozwala czytnikowi odnaleźć każdy obiekt, sama może być skompresowanym strumieniem, a nie zwykłym tekstem.

Zrób wszystkie cztery dobrze, a plik jest mały. Przegap jeden, a zdominuje wszystko inne, co zrobiłeś dobrze.

Komponent zapisujący NextPDF to jednoprzebiegowy serializator strumieniowy: dołącza bajty każdego obiektu w miarę ich wytwarzania i zapisuje klasyczny wpis odwołań krzyżowych „w użyciu” dla każdego. To ustawienie domyślne jest szybkie, przewidywalne i wytwarza plik stabilny bajtowo — ale nie jest najmniejszym możliwym układem, a NextPDF jest co do tego uczciwy.

PDF to graf obiektów pośrednich. Większość z nich to małe słowniki: węzły stron, słowniki adnotacji, elementy drzewa struktury, wpisy konspektu. Każdy płaci stały podatek — słowa kluczowe obj / endobj, numery obiektu i generacji oraz wpis odwołań krzyżowych, który go lokalizuje. W dokumencie z tysiącami małych obiektów ten podatek to znacząca część pliku.

Strumień obiektów zbiera wiele z tych małych obiektów niebędących strumieniami w jeden strumień i kompresuje je razem (Spec: ISO 32000-2, §7.5.7). Obwiednia obj / endobj jest zrzucana dla całej grupy; wartości w środku są przechowywane jedna za drugą bez słów kluczowych dla pojedynczego obiektu, a następnie deflowane jako pojedynczy blok — co także kompresuje się lepiej, ponieważ deduplikujący kompresor widzi teraz wszystkie te podobne słowniki naraz. Wpis odwołań krzyżowych nie znika — każdy upakowany obiekt wciąż go potrzebuje — ale kurczy się do zwięzłego binarnego wpisu typu 2 w strumieniu odwołań krzyżowych (więcej o tym w Dźwigni 2).

W NextPDF dostarcza to ObjectStreamPacker, samodzielny postprocesor, który bierze gotowy PDF ze strumieniem odwołań krzyżowych i przepisuje kwalifikujące się obiekty w jeden /Type /ObjStm. Reguły, którymi się kieruje, pochodzą wprost ze standardu: kwalifikującym się obiektem jest obiekt o generacji zero, niebędący strumieniem, po wykluczeniu obiektów specjalnych, które muszą pozostać bezpośrednio adresowalne. §7.5.7 zabrania przechowywania obiektu strumienia wewnątrz strumienia obiektów, więc obiekty strumieni — treść, czcionki, obrazy — zachowują własne wpisy; a ObjectStreamPacker dodatkowo odmawia własnemu obiektowi strumienia odwołań krzyżowych dokumentu (jest on przepisywany) oraz słownikowi /Encrypt, które oba muszą pozostać bezpośrednio adresowalne. Wszystko inne o generacji zero i niebędące strumieniem jest upakowane.

Object stream packing (ObjStm) — edition availability
EditionAvailability
CoreFull support in the open-source core via ObjectStreamPacker. It is opt-in: the single-pass writer’s default emits classic in-use entries, so output stays byte-identical unless you enable packing. The packer is deterministic, with its own reproducible golden baseline.
ProNot in this edition
EnterpriseNot in this edition

Włączanie opcjonalne to celowa postawa, a nie ukryte ograniczenie. Domyślne wyjście jest stabilne bajtowo i pasuje do istniejących baz wzorcowych; włączenie pakowania wybiera inny, mniejszy, równie deterministyczny układ. Wybierasz kompromis, a silnik nigdy nie podejmuje go za twoimi plecami.

Gdy obiekty żyją wewnątrz strumienia obiektów, indeks, który na nie wskazuje, zmienia kształt. Czytnik odnajduje upakowany obiekt przez skompresowany wpis odwołań krzyżowych — wpis typu 2, który nazywa strumień obiektów i indeks w jego obrębie (Spec: ISO 32000-2, §7.5.8.3). Ponieważ całe odwołania krzyżowe są same strumieniem /Type /XRef, indeks dla tysięcy obiektów jest pakowany binarnie i deflowany, a nie zapisywany jako wiersze zwykłego tekstu. Mapa kurczy się wraz z terytorium.

ObjectStreamPacker odbudowuje dokładnie to: emituje pojedynczy strumień obiektów, a następnie przepisany strumień odwołań krzyżowych niosący zwięzły wpis typu 1 dla każdego zachowanego obiektu oraz wpis typu 2 dla każdego upakowanego, zachowując każdy numer obiektu, by istniejące odwołania pozostały ważne.

Dla każdego dokumentu z prawdziwymi obrazami ta dźwignia przyćmiewa pozostałe. Bajty to ten sam obraz; kodek to budżet. Nazwy filtrów żyją w standardowym zestawie filtrów (Spec: ISO 32000-2, §7.4), a wybór między nimi to decyzja o rozmiarze:

  • FlateDecode jest bezstratny. Idealny dla grafiki liniowej, zrzutów ekranu i wszystkiego z płaskim kolorem — i zgubny dla zdjęcia, gdzie bezstratny oznacza każdy bajt oryginału.
  • DCTDecode to JPEG: stratny i dla zdjęć właściwy wybór z dużym zapasem, często dziesięciokrotna redukcja przy spadku jakości, którego nikt nie zauważa.
  • JPXDecode to JPEG 2000: kompresja falkowa o innej krzywej jakość/rozmiar, ale nierównym wsparciu i zakazana przez niektóre profile archiwalne.

To dokładnie tam, gdzie jak filtr dekoduje ma znaczenie, a to zadanie artykułu Strumienie i filtry. Punkt ekonomiczny jest węższy: zdjęcie przechowywane bezstratnie to najczęstsza pojedyncza przyczyna niepotrzebnie ogromnego PDF, a żadne pakowanie strumieni obiektów nie uratuje pliku, którego prawdziwym ciężarem jest jeden nieskonwertowany na JPEG skan.

Osadzona czcionka to program. Pełny może mieć kilkaset kilobajtów, ponieważ niesie każdy glif, jaki projektant kroju kiedykolwiek narysował — tysiące znaków w pismach, których nigdy nie użyjesz w tym dokumencie. Tworzenie podzbioru osadza tylko glify, które dokument faktycznie rysuje, zamieniając ten program w mały ułamek samego siebie. Jednostronicowy list nie potrzebuje całej czcionki obsługującej CJK; potrzebuje tych kilkudziesięciu glifów, które składa. Mechanika tego, jak glify są wybierane i ponownie indeksowane, to osobny temat — zobacz Czcionki: trudna część.

  1. Image bytesFor media-heavy files, the largest line item. The filter choice — lossless Flate vs a lossy image codec like DCT (or JPX in a lossy mode) — is the dominant size lever.
  2. Font bytesA full embedded font is mostly glyphs you never draw. Subsetting keeps only the used glyphs, often shrinking the program by an order of magnitude.
  3. Per-object overheadThousands of small dictionaries each pay an obj/endobj envelope plus a cross-reference entry. Object streams (ObjStm) drop the envelope for the whole group; each object keeps a compact type-2 cross-reference entry.
  4. The indexA compressed cross-reference stream with type-2 entries binary-packs and deflates the map of every object, replacing plaintext index rows.
Dokąd idą bajty: cztery dźwignie, na które autor wydaje budżet rozmiaru PDF, w kolejności, w jakiej zwykle dominują w rzeczywistym pliku.

Nie ma tu zmyślonego API do pokazania, ponieważ najważniejsze decyzje o rozmiarze podejmuje się zanim bajty dotrą do komponentu zapisującego — a jedyna strukturalna dźwignia, którą NextPDF udostępnia, to pojedyncze włączanie opcjonalne. Koncepcyjnie budżet czyta się tak:

  • Podawaj zdjęcia jako JPEG, by lądowały pod DCTDecode, a nie zakodowane na nowo bezstratnie. Największą wygraną jest wybór dotyczący danych źródłowych, a nie flaga komponentu zapisującego.
  • Pozwól komponentowi zapisującemu tworzyć podzbiory osadzonych czcionek, by wysyłane były tylko narysowane glify.
  • Dla dokumentu z wieloma małymi obiektami włącz pakowanie strumieni obiektów, które prowadzi gotowy plik przez ObjectStreamPacker, by zgrupować małe obiekty niebędące strumieniami i przepisać strumień odwołań krzyżowych.

Ścieżka domyślna — klasyczne wpisy „w użyciu”, bez ObjStm — to właściwa baza: deterministyczna, stabilna bajtowo i łatwa do zweryfikowania. Pakowanie to przemyślane ulepszenie, gdy to liczba obiektów, a nie ciężar obrazu, jest tym, co nadyma plik.

Pułapką jest sięganie po przycisk „skompresuj PDF” i oczekiwanie, że naprawi wszystko. Kompresja to nie jedna dźwignia; to cztery, i nie zastępują się nawzajem. Pakowanie strumieni obiektów nie potrafi zmniejszyć zdjęcia — to zadanie filtra obrazu. Doskonały JPEG nie zrównoważy czcionki, której zapomniałeś zredukować do podzbioru. A nic z tego nie pomaga, jeśli prawdziwym przerostem jest skan 10 MB przechowywany bezstratnie, ponieważ nikt nie wybrał dla niego DCTDecode.

Drugie nieporozumienie jest takie, że mniejszy jest zawsze ściśle lepszy. Nie jest. Strumienie obiektów są niezgodne z linearyzacją — układem szybkiego podglądu w sieci, który ustala bezwzględne rozmieszczenie obiektów, by pierwsza strona strumieniowała się wcześnie. A niektóre profile archiwalne ograniczają, które filtry obrazu są w ogóle dozwolone. Rozmiar to jedna oś. Wymienia się go ze strumieniowaniem, zgodnością archiwalną oraz powtarzalnością, a właściwy punkt na krzywej zależy od tego, do czego dokument służy.

Cztery dźwignie to strukturalna ekonomia rozmiaru pliku. Nie są uniwersalną gwarancją „zmniejsz to”, a NextPDF nie udaje, że jest reoptymalizatorem dla dowolnych przychodzących plików PDF.

ObjectStreamPacker to optymalizacja w najlepszym razie, która nigdy nie ryzykuje poprawności. Odmawia — zwracając dane wejściowe niezmienione — gdy plik nie jest PDF ze strumieniem odwołań krzyżowych, gdy jest zaszyfrowany, gdy niesie podpis cyfrowy (przekładanie obiektów przesunęłoby zakresy bajtów, które podpis chroni), gdy już zawiera strumienie obiektów lub gdy nie ma kwalifikującego się obiektu do upakowania. Emituje dokładnie jeden strumień obiektów dla całego dokumentu; nie dzieli na kolekcję /Extends, co jest poza zakresem i zgodne dla rozmiarów dokumentów, które wytwarza NextPDF.

Dźwignie obrazu i czcionki to w dużej mierze decyzje o danych wejściowych. Komponent zapisujący NextPDF nie transkoduje niejawnie bezstratnej bitmapy w stratny strumień obrazu — przekodowanie zdjęcia na DCTDecode lub JPXDecode to jawna nadrzędna decyzja kodowania obrazu, a nie coś, co serializator robi za twoimi plecami — ani nie odzyskuje glifów z czcionki, którą wywołujący poprosił o osadzenie w całości. Największe wygrane na rozmiarze podejmuje się wyżej w łańcuchu niż serializator bajtów; zadaniem silnika jest nie marnować budżetu, który mu przynosisz.

  • Strumienie i filtry — towarzysz jak filtr dekoduje; ta strona celowo go uzupełnia, a nie powiela.
  • Czym właściwie jest PDF — model obiektów pośrednich, którego narzut na obiekt redukuje dźwignia strumienia obiektów.
  • Anatomia pliku PDF — struktura odwołań krzyżowych, która staje się skompresowanym strumieniem.
  • Czcionki: trudna część — jak tworzenie podzbioru wybiera i ponownie indeksuje glify, które rysuje dokument.
  • Strumień obiektów (ObjStm) — pojedynczy strumień, który trzyma wiele małych obiektów pośrednich niebędących strumieniami, skompresowanych razem, dzięki czemu obwiednia obj / endobj jest zrzucana dla grupy. Każdy upakowany obiekt wciąż zachowuje własny wpis odwołań krzyżowych, jako zwięzły wpis typu 2. W NextPDF włączany opcjonalnie.
  • Obiekt pośredni — numerowany obiekt w grafie PDF, opakowany w obwiednię obj / endobj i śledzony przez indeks odwołań krzyżowych. Obwiednia to narzut na obiekt.
  • Strumień odwołań krzyżowych — strumień /Type /XRef, który indeksuje każdy obiekt, pakowany binarnie i deflowany, a nie zapisywany jako wiersze zwykłego tekstu.
  • Skompresowany wpis (typu 2) — wpis odwołań krzyżowych, który wskazuje na obiekt żyjący wewnątrz strumienia obiektów, nazywając strumień i indeks w jego obrębie.
  • Tworzenie podzbioru czcionek — osadzanie tylko glifów, które dokument faktycznie rysuje, zamiast pełnego kroju, zmniejszając osadzony program czcionki.
  • Filtr bezstratny kontra stratny — bezstratny kodek (FlateDecode) odtwarza każdy bajt; stratny kodek obrazu (DCTDecode lub JPXDecode w trybie stratnym) odrzuca niezauważalny szczegół na rzecz znacznie mniejszego wyniku. (JPEG 2000 / JPX można też skonfigurować bezstratnie.) Ten wybór to dominująca dźwignia w pliku bogatym w media.