Otwórz 200-stronicowy PDF z linku, a przeglądarka często pozostaje pusta, dopóki nie dotrze większość pliku. Fast Web View to nazwa Adobe Acrobata dla zlinearyzowanego PDF-a (załącznik F normy ISO 32000-1): strona pierwsza i mały indeks siedzą z przodu, więc przeglądarka renderuje stronę pierwszą wcześnie i pobiera kolejne strony żądaniami HTTP Range. HotPDF i PDFium Component potrafią zapisywać zlinearyzowane pliki z poziomu Delphi
Nic z tego nie jest osobnym formatem pliku. Zlinearyzowany PDF to zwykły PDF, który każdy zgodny ze standardem czytnik otworzy bez specjalnej obsługi; różnica leży w kolejności bajtów i w dwóch dodatkowych strukturach, słowniku parametrów linearyzacji oraz strumieniu wskazówek. O tym, czy czytnik kiedykolwiek poczuje korzyść, decydują dwa warunki. Plik musi być zlinearyzowany i taki pozostać, co dopisana na końcu edycja cicho unicestwia, a serwer WWW musi odpowiadać na żądania zakresów bajtów kodem 206 Partial Content. Na lokalnym dysku, gdzie przeskok na koniec pliku nic nie kosztuje, zlinearyzowany PDF zachowuje się dokładnie jak każdy inny, więc Fast Web View to czysto sieciowa optymalizacja opłacalna dla dużych dokumentów otwieranych prosto z URL-a
Co przestawia zlinearyzowany PDF?
Zlinearyzowany PDF gromadzi wszystko, czego potrzebuje strona pierwsza, w ciągłym bloku blisko początku pliku, przed wszystkimi pozostałymi stronami. W zwykłym PDF-ie obiekty mogą leżeć w dowolnej kolejności, bo tabela odsyłaczy na końcu mapuje każdy numer obiektu na jego przesunięcie bajtowe: czytnik przesuwa się na ogon, czyta wskaźnik startxref, ładuje tabelę i stamtąd może znaleźć cokolwiek (przegląd struktury pliku PDF krok po kroku przechodzi przez ten układ nagłówka, treści, xref i zwiastuna). Ta konstrukcja jest znakomita na dysku i słaba w sieci, gdzie ogon to dokładnie ta część, która dociera ostatnia. Żeby narysować stronę pierwszą, konwencjonalny czytnik potrzebuje obiektu strony, jej strumienia zawartości oraz używanych przez nią czcionek i obrazów, a w nieuporządkowanym pliku każdy z tych elementów może leżeć w ostatnim megabajcie
Linearyzacja ustala kolejność. Po nagłówku przychodzą słownik linearyzacji i mała sekcja odsyłaczy pierwszej strony, potem obiekty poziomu dokumentu, takie jak katalog, główny strumień wskazówek oraz strona pierwsza ze wszystkimi swoimi zasobami. Pozostałe strony idą jedna po drugiej, potem obiekty współdzielone między dalszymi stronami, a na samym końcu główna tabela odsyłaczy. Czytnik ignorujący optymalizację i tak startuje od końca i znajduje każdy obiekt przez główną tabelę, dlatego zlinearyzowany plik pozostaje w pełni kompatybilny ze starszym oprogramowaniem. Numeracja obiektów wygląda przez to w edytorze szesnastkowym odwrotnie: przedni blok niesie najwyższe numery, a wszystko za nim jest numerowane od 1
Słownik linearyzacji i tabele wskazówek
Pierwszym obiektem po nagłówku %PDF jest słownik parametrów linearyzacji, a załącznik F wymaga, aby leżał w pierwszych 1024 bajtach, więc czytnik strumieniowy znajduje go w otwierającym pakiecie. Tabela F.1 wylicza jego wpisy: /L to długość całego pliku w bajtach, /H to przesunięcie i długość głównego strumienia wskazówek, /O numer obiektu pierwszej strony, /E przesunięcie, gdzie kończy się strona pierwsza, /N liczba stron, a /T przesunięcie pierwszego wpisu głównej tabeli odsyłaczy. Wpis /L ma większe znaczenie, niż się wydaje. Gdy nie zgadza się z rzeczywistym rozmiarem pliku, tabela F.1 każe czytnikowi traktować dokument jako zwykły PDF i zignorować każdą wskazówkę w środku
Strumień wskazówek to to, co czyni skok do strony 40 tanim. Zawiera tabelę wskazówek przesunięć stron, która odnotowuje dla każdej strony, gdzie zaczynają się jej obiekty i ile bajtów pokrywają, oraz tabelę wskazówek obiektów współdzielonych robiącą to samo dla czcionek, logotypów i innych zasobów używanych na kilku stronach. Czytnik chcący strony 40 sprawdza jej zakres bajtów, żąda dokładnie tego kawałka i renderuje, jak tylko dotrze, ciągnąc w ten sam sposób każdy współdzielony obiekt, którego jeszcze nie trzyma. Dwa szczegóły wykładają domorosłych linearyzatorów. Przesunięcia wskazówek są logiczne, liczone tak, jakby samego strumienia wskazówek nie było, więc writer zapisujący przesunięcia fizyczne sprawia, że każde późniejsze żądanie zakresu mija się o rozmiar obiektu wskazówek. A writer, który przy zbieraniu zasobów strony idzie za jej odnośnikiem /Parent, schodzi w dół przez drzewo stron i wciąga cały dokument do sekcji pierwszej strony. Oba pliki dalej deklarują, że są zlinearyzowane. Żaden nie ładuje się szybciej
Czemu Fast Web View potrzebuje pomocy serwera?
Fast Web View działa tylko wtedy, gdy serwer na żądanie dostarcza dowolne kawałki pliku. Czytnik wysyła żądania HTTP z nagłówkiem Range i oczekuje w odpowiedzi 206 Partial Content; jeśli serwer nigdy nie reklamuje Accept-Ranges: bytes albo stojące przed nim proxy czy CDN zlepiają żądania zakresów w pełne transfery, czytnik nie zdoła sam pobrać strony 40 i ściąga cały plik. Struktura wewnątrz PDF-a jest wtedy poprawna i całkowicie zmarnowana, a to właśnie ta awaria jest najczęściej błędnie diagnozowana jako zepsuta linearyzacja. Zanim przebudujesz dokument, zażądaj pierwszego kilobajtu dokładnie tego URL-a, który otwiera przeglądarka, na przykład przez curl -r 0-1023, i potwierdź, że status to 206. Hosty plików statycznych zwykle robią to domyślnie dobrze, podczas gdy punkty końcowe aplikacji serwujące PDF-y przez własny handler często nie
Jak sprawdzić, czy PDF ma Fast Web View?
Sprawdź bajty zamiast ufać narzędziu, które wyprodukowało plik, i sprawdź obie połowy warunku: plik deklaruje, że jest zlinearyzowany, i ta deklaracja wciąż odpowiada plikowi na dysku. Trzy sprawdzenia pokrywają prawie każdy przypadek, od szybkiego rzutu oka na desktopie po automatyczną bramkę w potoku wydawniczym:
- Adobe Acrobat lub Reader: otwórz Plik > Właściwości; zakładka Opis pokazuje w prawym dolnym rogu
Fast Web View: YesalboNo. Acrobat raportuje Yes tylko wtedy, gdy struktura jest obecna i aktualna, więc plik edytowany po linearyzacji pokazuje No - qpdf z wiersza poleceń:
qpdf --check-linearizationwaliduje tabele wskazówek względem rzeczywistych pozycji obiektów, aqpdf --show-linearizationzrzuca słownik i każdy wpis tabel wskazówek - Pierwszy kilobajt pliku: otwórz go w edytorze tekstowym albo szesnastkowym i szukaj obiektu zawierającego
/Linearized 1w pierwszych 1024 bajtach; gdy tam jest, porównaj jego wartość/Lz rozmiarem pliku podanym przez system operacyjny
# Waliduje słownik linearyzacji i tabele wskazówek względem układu
qpdf --check-linearization report-web.pdf
# Zrzuca /L, /H, /O, /E, /N, /T i każdy wpis tabeli wskazówek
qpdf --show-linearization report-web.pdf
Sprawdzenie qpdf jest tym wartym podpięcia w potoku, bo uzgadnia tabele wskazówek z rzeczywistymi pozycjami obiektów; kontrola, która tylko potwierdza istnienie słownika, pobłogosławi plikowi, którego przesunięcia wskazują na złe bajty. Z poziomu Delphi THotPDF.IsLoadedLinearized w HotPDF raportuje, czy wczytany dokument deklaruje linearyzację, skanując pierwsze 1024 bajty w poszukiwaniu /Linearized przed parsowaniem. PDFium Component odpowiada na to samo pytanie przez funkcję klasy TPdf.InspectAvailability, której pole Linearization zwraca plnLinearized, plnNotLinearized albo plnUnknown bez wczytywania dokumentu w ogóle. Obie flagi raportują, co deklaruje plik; porównanie /L mówi ci, czy ta deklaracja wciąż odpowiada bajtom na dysku:
uses
System.SysUtils, System.Classes, System.Math, System.StrUtils, PDFium;
type
TFastWebView = (fwvNo, fwvStale, fwvYes);
// Twoja własna funkcja pomocnicza: PDFium rozstrzyga, czy plik jest zlinearyzowany,
// a porównanie /L łapie edycje dopisane później
function FastWebViewState(const FileName: string): TFastWebView;
var
Stream: TFileStream;
Head: AnsiString;
Text: string;
P, Q: Integer;
begin
Result := fwvNo;
Stream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
try
if TPdf.InspectAvailability(Stream).Linearization <> plnLinearized then
Exit;
SetLength(Head, Min(Stream.Size, 1024)); // załącznik F: pierwsze 1024 bajty
Stream.Position := 0;
if Length(Head) > 0 then
Stream.ReadBuffer(Head[1], Length(Head));
Text := string(Head);
P := Pos('/Linearized', Text);
if P = 0 then
Exit;
P := PosEx('/L ', Text, P + Length('/Linearized'));
if P = 0 then
Exit;
Inc(P, 3);
Q := P;
while (Q <= Length(Text)) and CharInSet(Text[Q], ['0'..'9']) do
Inc(Q);
if StrToInt64Def(Copy(Text, P, Q - P), -1) = Stream.Size then
Result := fwvYes
else
Result := fwvStale; // kiedyś zlinearyzowany, potem zmodyfikowany
finally
Stream.Free;
end;
end;
Włączanie Fast Web View w Acrobatcie, qpdf i Delphi
Każde narzędzie, które przepisuje cały plik w kolejności z załącznika F, potrafi włączyć Fast Web View, a najważniejsze jest, żeby działało jako ostatnie. W Acrobatcie włącz Zapisz jako optymalizuje dla Fast Web View w Preferencje > Dokumenty, a potem używaj Plik > Zapisz jako zamiast Zapisz, bo zwykły Zapis może dopisać aktualizację przyrostową. Na serwerze albo w skrypcie budowania qpdf --linearize in.pdf out.pdf przepisuje dowolny PDF do postaci zlinearyzowanej i zostawia wejście nietknięte. Kod Delphi ma dwie drogi, zależnie od tego, czy dokument generujesz, czy dostajesz. HotPDF pisze wyjście zlinearyzowane bezpośrednio, gdy THotPDF.LinearizeOutput ma wartość True; ustaw to wraz z pozostałymi opcjami wyjścia przed BeginDoc, a układ z załącznika F zostanie obliczony i zapisany wewnątrz EndDoc:
qpdf --linearize report-final.pdf report-web.pdf
var
PDF: THotPDF;
begin
PDF := THotPDF.Create(nil);
try
PDF.AutoLaunch := False; // nie otwieraj przeglądarki po EndDoc
PDF.FileName := 'catalog-web.pdf';
PDF.LinearizeOutput := True; // ustaw przed BeginDoc
PDF.BeginDoc; // strona 1 istnieje po BeginDoc
PDF.CurrentPage.TextOut(72, 72, 0, 'Spring catalog');
PDF.AddPage;
PDF.CurrentPage.TextOut(72, 72, 0, 'Price list');
PDF.EndDoc; // zlinearyzowany układ zapisuje się tutaj
finally
PDF.Free;
end;
end;
Dla plików, które już istnieją, PDFium Component dostarcza TPdf.SaveAsLinearized. Wykonuje zwykły zapis PDFium do pośredniego magazynu, więc edycje stron i wartości formularzy przechodzą tą samą ścieżką co każdy inny zapis, a potem restrukturyzuje to wyjście według załącznika F przed zapisaniem docelowego pliku. Metoda przyjmuje te same parametry TSaveOption i TPdfVersion co SaveAs, odrzuca saIncremental i każdą wersję poniżej PDF 1.2, a jej wariant przyjmujący nazwę pliku zapisuje do pliku tymczasowego, który zastępuje cel dopiero po udanym zapisie. Ponieważ writer przebudowuje plik z bieżącej rewizji, to także sposób, w jaki przywrócisz Fast Web View plikowi, który nazbierał dopisanych edycji. Zaszyfrowane wejście wymaga saRemoveSecurity: przenumerowanie obiektów unieważnia klucze per obiekt standardowego handlera bezpieczeństwa (ISO 32000-1 §7.6.2), więc kopia sieciowa jest zapisywana bez szyfrowania
var
Pdf: TPdf;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'manual-rev3.pdf'; // niesie dopisane edycje
Pdf.Active := True;
if not Pdf.SaveAsLinearized('manual-web.pdf', saNoIncremental) then
raise Exception.Create('Linearized save failed');
finally
Pdf.Free;
end;
end;
Aktualizacje przyrostowe po cichu wyłączają Fast Web View
Aktualizacja przyrostowa to najczęstszy powód, dla którego poprawnie zlinearyzowany plik traci później Fast Web View. Gdy narzędzie dodaje podpis, wypełnia pole formularza albo doczepia żółtą karteczę przez zapis przyrostowy, zostawia oryginalne bajty nietknięte i dokleja na koniec zmienione obiekty, nową sekcję odsyłaczy i nowy zwiastun. Słownik linearyzacji z przodu dalej opisuje stary układ, jego /L nie odpowiada już rozmiarowi pliku, a zgodny ze standardem czytnik wraca do traktowania dokumentu jako zwykłego PDF-a, choć zlinearyzowana struktura wciąż siedzi nietknięta w pierwszej części pliku. HotPDF dokłada dokładnie taką rewizję przez BeginIncrementalUpdate i SaveIncrementalUpdate, co jest właściwym narzędziem, dopóki dokument się zmienia, i złym krokiem końcowym dla pliku zmierzającego na serwer WWW:
// Dopisuje rewizję: szybko, zachowuje wcześniejsze bajty nietknięte,
// a wynik zostawia bez Fast Web View
PDF.BeginIncrementalUpdate('report.pdf');
PDF.AddPage;
PDF.CurrentPage.TextOut(72, 760, 0, 'Addendum');
PDF.SaveIncrementalUpdate('report-rev2.pdf');
Praktyczna zasada: edytuj przyrostowo, dopóki dokument jest w ruchu, zlinearyzuj raz jako bardzo ostatni krok i zostaw Fast Web View wyłączone dla plików wysyłanych mailem albo pobieranych, zanim ktokolwiek je otworzy, bo na większym indeksie ładowanym z przodu nic nie zyskują. Podpisane dokumenty wymuszają prawdziwy wybór: pełne przepisanie zmienia bajty, które pokrywa /ByteRange każdego podpisu, a podpisanie potem dokleja aktualizację, która wprost przywraca niezgodność /L, więc przy kontraktach zostaw podpis i zaakceptuj pełne pobieranie. Strona zapisu według załącznika F, czyli to, jak przesunięcia zbiegają się, zanim zostanie zapisany pierwszy bajt, i czemu zlinearyzowane wyjście rezygnuje ze strumieni obiektów, jest omówiona w pisaniu zlinearyzowanych PDF-ów z tabelami wskazówek HotPDF oraz linearyzacji Fast Web View z PDFium Component w Delphi. LinearizeOutput i IsLoadedLinearized są częścią HotPDF Delphi Component, a SaveAsLinearized i InspectAvailability wchodzą w skład PDFium Component dla Delphi, C++Builder i Lazarusa