Umieść 80-megabajtowy zeskanowany raport pod linkiem, otwórz go w przeglądarce i obserwuj, co się stanie: przeglądarka pozostaje na pustym ekranie, dopóki nie zostanie pobrana znaczna część bajtów, po czym od razu rysuje całą pierwszą stronę. Przejdź do 40. strony, a w źle zbudowanym pliku całe pobieranie może ruszyć od nowa. Frustrujące jest to, że czytelnik od początku chciał tylko zobaczyć pierwszą stronę. Linearyzacja to strukturalna odpowiedź na ten problem. Zmienia układ PDF w taki sposób, aby przeglądarka mogła wyrenderować pierwszą stronę z początkowego, małego fragmentu pliku i pobierać resztę na żądanie, dlatego Adobe reklamuje tę funkcję jako „Fast Web View” (Szybki podgląd w internecie)
Nie jest to jednak inny format pliku. Zlinearyzowany PDF to zwykły PDF, który czytnik zgodny ze standardem otworzy bez żadnej specjalnej obsługi. Cała sztuczka polega wyłącznie na kolejności bajtów oraz na dwóch dodatkowych strukturach zawartych w pliku. ISO 32000-1 określa całą konstrukcję w Załączniku F, a kiedy już przyjrzysz się jej układowi, zachowanie to przestaje wyglądać jak magia, a zaczyna przypominać celową zamianę kolejności w pliku na rzecz opóźnienia pierwszego renderowania (first-paint latency)
Co tak naprawdę zmienia linearyzacja
Zwykły plik PDF może rozrzucać swoje obiekty w niemal dowolnej kolejności. Działa to dzięki tabeli odsyłaczy (cross-reference) na końcu pliku: czytnik przechodzi na koniec, odczytuje wskaźnik startxref, ładuje xref, a stamtąd może zlokalizować każdy obiekt po jego przesunięciu. Taka konstrukcja jest doskonała dla plików lokalnych, gdzie przejście na koniec nic nie kosztuje, ale słabo sprawdza się w przypadku pliku przesyłanego strumieniowo przez sieć, gdzie końcówka pliku dociera na samym końcu. Do wyrenderowania pierwszej strony konwencjonalny czytnik potrzebuje obiektu strony, jej strumienia zawartości, czcionek, do których się odwołuje, oraz wszelkich rysowanych obrazów, a w nieuporządkowanym pliku mogą one znajdować się gdziekolwiek, włącznie z ostatnim megabajtem
Linearyzacja ustala ten porządek. Obiekty potrzebne do wyświetlenia pierwszej strony są gromadzone w ciągłym bloku blisko początku, tuż po małej sekcji nagłówka, dzięki czemu pojawiają się wcześnie w strumieniu bajtów. Cała reszta, czyli pozostałe strony i współdzielone zasoby, następuje w przewidywalnej sekwencji. Druga, kompletna tabela odsyłaczy nadal znajduje się na końcu dla czytników ignorujących tę optymalizację, ale zlinearyzowany plik umieszcza z przodu również odsyłacze do pierwszej strony oraz parametry, których potrzebuje czytnik strumieniowy. Czytnik nie musi już docierać do ogona pliku, zanim będzie mógł cokolwiek narysować
Zestaw obiektów pierwszej strony i słownik parametrów linearyzacji
Pierwszym obiektem w zlinearyzowanym pliku, tuż po nagłówku %PDF, jest słownik parametrów linearyzacji. To właśnie jego szuka czytnik strumieniowy, aby zdecydować, czy optymalizacja jest obecna i jak z niej skorzystać. Słownik zapisuje długość całego pliku, przesunięcie w bajtach, w którym zaczyna się główna sekcja odsyłaczy, numer obiektu pierwszej strony oraz lokalizację i długość strumienia wskazówek (hint stream). Mając te liczby czytnik wie już z początkowych kilobajtów, ile musi pobrać, aby pokazać pierwszą stronę i gdzie szukać indeksu pozwalającego na skoki do innych części dokumentu
Załącznik F rygorystycznie określa, co oznacza tu „pierwsza strona”. Sekcja pierwszej strony musi zawierać sam obiekt strony, jej strumienie zawartości oraz zasoby, do których te strumienie się odwołują, tak aby strona była samowystarczalna po pobraniu tego prefiksu. Udostępniane zasoby, takie jak czcionka używana na każdej stronie lub logo powtarzające się w nagłówku, są traktowane w sposób szczególny: pojawiają się wystarczająco wcześnie, aby obsłużyć pierwszą stronę, ale są oflagowane jako współdzielone, aby czytnik nie pobierał ich ponownie podczas renderowania strony 30. To rozróżnienie między prywatnymi obiektami strony a współdzielonymi to obszar, w którym większość autorskich „optymalizatorów” popełnia błędy, a to właśnie ten błąd powoduje powstawanie pliku, który rzekomo jest zlinearyzowany, ale nadal zawiesza się podczas pobierania
Strumienie wskazówek: indeks, który czyni skakanie po stronach tanim
Szybkie pokazanie pierwszej strony to tylko połowa sukcesu. Druga to skakanie do dowolnej strony bez pobierania całej zawartości pomiędzy, co zapewniają strumienie wskazówek. Zlinearyzowany plik zawiera tabelę wskazówek przesunięcia strony (page offset hint table) oraz tabelę wskazówek obiektów współdzielonych, zapisane w postaci strumienia zreferowanego w słowniku parametrów. Tabela przesunięć stron odnotowuje, dla każdej strony, gdzie zaczynają się jej obiekty w pliku i jak długo trwają. Tabela obiektów współdzielonych robi to samo dla zasobów używanych na wielu stronach
Mając te tabele, czytnik chcący pobrać stronę 40 nie analizuje pliku sekwencyjnie. Sprawdza tabelę wskazówek, aby poznać zakres bajtów zajmowanych przez 40. stronę, prosi serwer o dokładnie ten zakres i renderuje stronę, gdy te bajty dotrą, pobierając w ten sam sposób wszelkie współdzielone zasoby, których jeszcze nie posiada. Strumień wskazówek jest w rzeczywistości mapą dostępu swobodnego nałożoną na dokument i to on sprawia, że dobrze zlinearyzowany, 500-stronicowy plik działa responsywnie na wolnym łączu, podczas gdy niezoptymalizowany plik o tym samym rozmiarze ładuje się długo
Dlaczego serwer musi współpracować
Linearyzacja zakłada, że protokół przesyłania może dostarczyć dowolne fragmenty pliku, co warto zweryfikować, zanim obwini się format za słabe wyniki. Zastosowano tu mechanizm obsługi bajtów HTTP: czytnik wysyła żądania zakresu (range requests), a serwer odpowiada komunikatami 206 Partial Content. Jeśli serwer nie wysyła nagłówka Accept-Ranges: bytes, lub jeśli postawione przed nim proxy czy CDN redukują żądania zakresu do pełnego transferu, czytnik nie ma możliwości pobrania wyłącznie strony 40 i zostaje zmuszony do pobrania całego pliku. Struktura wewnątrz PDF jest wówczas w pełni poprawna, lecz całkowicie niewykorzystana
Taka awaria jest najczęściej błędnie diagnozowana jako „linearyzacja nie działa”. Z plikiem wszystko w porządku – problem leży w sposobie dostarczania. Zanim zaczniesz przebudowywać dokument, sprawdź żądaniem warunkowym, czy host rzeczywiście zwraca zawartość częściową (partial content) dla danego adresu URL. Wiele hostów statycznych robi to domyślnie, ale wiele źle skonfigurowanych serwerów aplikacji i warstw buforujących tego nie zapewnia
Aktualizacje przyrostowe po cichu psują linearyzację
Oto ograniczenie, które zaskakuje ludzi tworzących poprawne, zlinearyzowane pliki, i dziwiących się, dlaczego optymalizacja nagle znika. Linearyzacja opiera się na pojedynczym, starannie uporządkowanym układzie z indeksem z przodu. Aktualizacja przyrostowa z samej swej natury to narusza. Gdy narzędzie dodaje podpis, wypełnia pole formularza lub wstawia adnotację poprzez zapis przyrostowy, nie przepisuje pliku od nowa. Zamiast tego dołącza zmienione obiekty, nową sekcję odsyłaczy i nowy zwiastun (trailer) na samym końcu, pozostawiając oryginalne bajty nietknięte. To właśnie jest istotą aktualizacji przyrostowych: jest to rozwiązanie szybkie i pozwala zachować poprzednią wersję pliku do celów audytu lub weryfikacji podpisu
Efektem ubocznym jest to, że plik posiada teraz swoje najnowsze dane odsyłaczy (cross-reference) na samym końcu, zaraz po starannie umieszczonym bloku pierwszej strony, a słownik parametrów linearyzacji z przodu opisuje układ, który już nie odpowiada rzeczywistej strukturze pliku. Zgodny ze standardami czytnik wykrywa tę niezgodność i traktuje dokument jako zwykły, niezlinearyzowany plik PDF. „Fast Web View” przestaje działać, mimo że oryginalna struktura linearyzacji nadal tkwi w pierwszej połowie pliku. Jeśli dodasz kilka aktualizacji, każda z nich położy kolejną rewizję na końcu, a różnica pomiędzy starym indeksem głównym a rzeczywistym stanem znacząco się powiększy
Jeśli twój przepływ pracy wymaga zarówno edycji, jak i szybkiego podglądu w internecie, zasada wynika bezpośrednio ze struktury: edytuj przyrostowo, dopóki dokument jest w toku, a na końcu wykonaj ponowną linearyzację. Całkowite przepisanie pliku (full rewrite) to coś, co przywraca odpowiedni układ. W terminologii HotPDF oznacza to, że edycja w toku przechodzi przez BeginIncrementalUpdate i SaveIncrementalUpdate, które dołączają odpowiednią deltę (różnicę), podczas gdy etap końcowy ładuje cały dokument i dokonuje świeżej serializacji przy użyciu LoadFromFile oraz SaveLoadedDocument. Odrzuca to nagromadzone, stare poprawki i emituje jeden, czysty układ. Ta sama prawidłowość występuje przy strumieniach obiektów: włączenie UseObjectStreams wraz z UseXRefStream kompresuje odsyłacze i ciasno pakuje obiekty, co pomaga w zmniejszeniu rozmiaru pliku, jednak jak każdy wybór strukturalny, musi to zostać zaaplikowane podczas ostatecznego przepisania, a nie dołączone do końcowej rewizji
// In-flight edits: append a delta, keep prior revisions intact.
// This leaves the file NOT linearized.
Pdf.BeginIncrementalUpdate('report.pdf');
Pdf.AddPage;
Pdf.CurrentPage.TextOut(72, 760, 0, 'Addendum');
Pdf.SaveIncrementalUpdate('report.pdf');
// Finishing step: full re-serialization produces one clean layout,
// dropping the stacked revisions. Re-run your linearizer on the output.
Pdf.LoadFromFile('report.pdf');
Pdf.SaveLoadedDocument('report-final.pdf');
Narzędzie HotPDF nie udostępnia funkcji szybkiej linearyzacji w ramach jednego wywołania, więc praktycznym podejściem jest wygenerowanie czystego, całkowicie przepisanego pliku i przepuszczenie go przez dedykowany optymalizator. Narzędzia wiersza poleceń zajmują się przebudową bezpośrednio. qpdf przepisuje plik do postaci zlinearyzowanej za pomocą jednej flagi:
qpdf --linearize report-final.pdf report-web.pdf
Jak sprawdzić, czy plik jest zlinearyzowany
Nie ufaj nazwie pliku ani narzędziu, które rzekomo go utworzyło; zweryfikuj bajty. Najbardziej bezpośrednim sprawdzeniem jest początek (head) pliku: otwórz go i poszukaj słownika parametrów linearyzacji jako pierwszego obiektu po nagłówku, niosącego klucz /Linearized. Dla czytelnika przydatnym skrótem jest okno Właściwości Dokumentu w programie Acrobat, które raportuje „Fast Web View: Yes” (Szybki podgląd w internecie: Tak) tylko wtedy, gdy ta struktura jest autentycznie obecna i aktualna
W przypadku zautomatyzowanych kontroli, narzędzie qpdf raportuje zarówno obecność, jak i integralność struktury, co ma znaczenie, ponieważ plik może posiadać słownik linearyzacji, który już nie odzwierciedla jego układu – czyli dokładnie to, co pozostaje po aktualizacji przyrostowej:
# Reports "File is linearized" and validates hint tables against the layout
qpdf --check report-web.pdf
# Dumps the linearization parameters and hint data in detail
qpdf --show-linearization report-web.pdf
Etap walidacji jest tym, który naprawdę przynosi korzyści. Sprawdzenie, które tylko potwierdza istnienie słownika, z łatwością zaakceptuje plik, którego indeks wskazuje na błędne przesunięcia; natomiast weryfikacja dopasowująca tabele wskazówek do rzeczywistych pozycji obiektów powie ci na 100%, że optymalizacja przetrwa żądania zakresu wysyłane przez prawdziwy czytnik
Linearyzacja pozostaje warta zastosowania do każdego dużego dokumentu udostępnianego w internecie, zwłaszcza dla użytkowników mobilnych o nierównych połączeniach, a kosztuje zaledwie kilka procent rozmiaru pliku przeznaczonych na indeks ładowany z przodu. Dwie najważniejsze kwestie, o których należy pamiętać, to to, że struktura wewnątrz PDF i obsługa bajtów (byte-serving) na zewnątrz muszą być poprawne, a także, że jakakolwiek późniejsza edycja cofa optymalizację do czasu ponownego przepisania pliku. Traktuj ponowną linearyzację jako absolutnie ostatni krok w procesie (pipeline), po wykonaniu każdej innej modyfikacji. Opisane tutaj zachowanie tabel odsyłaczy, strumieni obiektów i aktualizacji przyrostowych jest częścią strukturalnego modelu, który wdraża komponent HotPDF dla Delphi i C++Builder; w celu zapoznania się z szerszym tłem tworzenia układów plików zobacz jak zbudowany jest plik PDF, a informacje na temat aktualizacji przyrostowych oraz pracy z dużymi plikami z poziomu kodu znajdziesz na stronie przetwarzania dużych plików PDF w Delphi