PDFium Component vyrábí z Delphi PDF ve stylu Fast Web View (linearizovaná) přes TPdf.SaveAsLinearized. Metoda uloží aktuální dokument s PDFium a pak výsledek přepíše podle přílohy F ISO 32000-1: objekty první stránky se přesunou dopředu, zapíší se dvě sekce křížových odkazů a hint tabulky posunů stránek a sdílených objektů řeknou prohlížeči, jaké rozsahy bajtů má pro kteroukoli stránku stáhnout
Důvod, proč se o to někdo zajímá, je síť. Katalog o 40 MB podávaný z object storage nebo dokumentového portálu má ukázat první stránku po prvních pár kilobajtech a skok na stránku 380 má stát pár HTTP Range požadavků, ne kompletní stažení. Prohlížeče to dokážou jen tehdy, je-li soubor linearizovaný. Většina PDF opouštějících editační pipeline linearizovaná není, protože přirozený způsob zápisu PDF dá tabulku křížových odkazů na konec a objekty stránek rozsype kam zapisovač zrovna vypustil
Proč PDFium umí linearizované soubory číst, ale ne zapisovat?
PDFium má kompletní čtečku linearizace a žádný zapisovač pro ni. Availability API FPDFAvail_* rozpozná linearizační slovník, dekóduje hint tabulky a ptá se hostitele přesně na rozsahy bajtů, které potřebuje — na tom stojí workflow progresivního stahování a rušení v PDFium Component. Veřejné ukládací ABI nabízí naopak jen prosté kopírovací uložení a uložení s cílovou verzí PDF. Nic v něm objekty neuspořádá ani nevypustí hint streamy
PDFium Component tu mezeru zavírá bez znovuimplementování toho, co PDFium už dělá dobře. TPdf.SaveAsLinearized nejdřív zavolá stávající SaveAs, takže sémantické ukládání si pořád drží PDFium: upravené stránky, stav formulářových polí i zacházení s verzí jdou přes cestu v kódu leštěnou léta. Komponenta pak pustí přes ten stabilní mezilehlý PDF průchod přestavbou podle přílohy F a linearizovaný výsledek zapíše na váš cíl. Rozdělení práce takhle znamená, že linearizace nemusí nikdy rozumět anotaci ani formuláři, jen objektům, odkazům a bajtovým posunům
Volání TPdf.SaveAsLinearized z Delphi
TPdf.SaveAsLinearized má dvě přetížení, jedno pro TStream a jedno pro název souboru, a obě berou tytéž volitelné parametry Option: TSaveOption a PdfVersion: TPdfVersion jako SaveAs. Obě vrátí True při úspěchu. Souborové přetížení zapisuje do dočasného souboru v cílovém adresáři a nahradí cíl atomicky, takže neúspěšné uložení nikdy nenechá napůl napsané PDF tam, kde bývalo dobré
var
Pdf: TPdf;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'catalog-2026.pdf';
Pdf.Active := True;
// ... upravujte stránky, vyplňujte formuláře, otiskujte, co pipeline dělá ...
if not Pdf.SaveAsLinearized('catalog-2026-web.pdf', saNoIncremental, pv17) then
raise Exception.Create('Linearized save failed');
finally
Pdf.Free;
end;
end;
Pravidla parametrů jsou přísná záměrně. saIncremental vyhodí chybu, protože inkrementální aktualizace přidává novou revizi na konec souboru a okamžitě by zničila dopředu naložené uspořádání. Cílová verze pod PDF 1.2 se taky odmítá; pvUnknown ponechá zdrojovou verzi. Zašifrovaný vstup potřebuje saRemoveSecurity, protože zapisovač přečíslovává objekty a PDF šifrovací klíče na úrovni objektů se odvozují z čísla objektu. A protože linearizace je kompletní přepis, každý existující digitální podpis se zneplatní. Musíte-li dodat podepsané a linearizované, nejprve linearizujte a pak podepište
Jak zapisovač seskupuje objekty pro Fast Web View
Linearizace není jen přesun první stránky na vrchol souboru. Zapisovač parsuje aktuální revizi, ať už její křížové odkazy jsou klasická tabulka, hybridní soubor, nebo stream křížových odkazů, a rozbalí každého člena aktivního object streamu do samostatného objektu. Pak postaví graf odkazů a roztřídí objekty do pěti skupin: objekty na úrovni dokumentu potřebné k otevření souboru, objekty první stránky, objekty privátní pro jednu pozdější stránku, objekty sdílené více pozdějšími stránkami a všechno ostatní
Pastí v tom průchodu grafem je /Parent. Každý slovník stránky ukazuje zpět nahoru po stromu stránek a naivní průchod sleduje ten ukazatel k nadřazenému uzlu, pak dolů přes /Kids do každé sesterské stránky a jejích content streamů. O krok později obsahuje skupina první stránky celý dokument a soubor je formálně linearizovaný, ale pro přístup s rozsahy napřed k ničemu. PDFium Component ignoruje odkaz stránky /Parent a uzly stromu stránek mimo sbíranou stránku, což je to, co drží sekci první stránky malou. Na 51stránkovém regresním vzorku s hlubokým stromem stránek má sekce první stránky 5289 bajtů, 0,48 % z 1105354bajtového výstupu
Object streamy a streamy křížových odkazů na vstupu se zploští na obyčejné nepřímé objekty generace 0, každý odkaz se přepíše na nová čísla a výstup používá klasické sekce křížových odkazů. To stojí trochu velikosti souboru oproti komprimovaným object streamům, ale zjednodušuje to pokrytí hint tabulek a je to uspořádání, které zvládne každý prohlížeč vnímající linearizaci. Záleží-li pro daný výstup na kompaktnosti víc než na Fast Web View, PDFium Component má na tu práci samostatný zapisovač object streamů
Co patří do linearizačního slovníku a dvou sekcí xref?
Linearizovaný soubor napsaný PDFium Component nese dvě klasické sekce křížových odkazů a linearizační slovník úplně v prvním objektu. První sekce xref následuje po linearizačním slovníku a pokrývá objekty na úrovni dokumentu a první stránky, takže prohlížeč dokáže vykreslit stránku jedna jen z přední části souboru. Hlavní sekce xref sedí na konci a pokrývá všechno ostatní
Sám linearizační slovník drží čísla, která prohlížeč potřebuje, než čte cokoli jiného: /L délku souboru, /H posun a délku hint streamu, /O číslo objektu první stránky, /E konec sekce první stránky, /N počet stránek a /T posun první položky v hlavní tabulce xref. Všechna ta čísla závisí na finálním uspořádání a slovník sedí před uspořádáním. Zapsat ho čísly přirozené šířky vytvoří smyčku: délka, která naroste z 999999 na 1000000, přidá bajt, posune každý následující posun, což může změnit šířku jiného čísla, a tak dále
PDFium Component smyčku láme tím, že si vyhradí pevných 320 bajtů pro linearizační slovník. Každý objekt, hint stream, obě sekce xref i přívěsky se zapisují nejdřív; jakmile jsou všechny posuny finální, slovník se vyplní na místě a doplní na vyhrazenou šířku. Jemný detail je /T: musí ukazovat na položku objektu 0 v hlavní tabulce xref, ne na klíčové slovo xref, které jí předchází. Přísné validátory flagují posun klíčového slova, i když shovívavé prohlížeče soubor stejně otevřou
Proč posuny hint tabulek ignorují hint stream?
Posuny hint tabulek v linearizovaném PDF se vykládají, jako by hint stream v souboru neexistoval. Příloha F to tak definuje, aby si hint stream mohl sedět mezi sekcí první stránky a zbytkem, aniž by se jeho vlastní velikost vracela do čísel, která obsahuje. Praktický důsledek pro zapisovače se snadno pokazí: každý skutečný posun ležící za hint streamem se musí zmenšit, než vejde do hint tabulky posunů stránek nebo sdílených objektů
Otázka zní, o kolik se zmenšit. Lákavá odpověď je délka dat streamu, protože to je část, která se cítí jako obsah hintu. Správná odpověď je délka celého nepřímého objektu hintu, včetně N 0 obj, slovníku streamu, klíčových slov stream a endstream a endobj. Odečtete-li jen délku dat, je každý pozdější posun mimo o pár desítek bajtů. Stránka jedna se pořád vykreslí, protože žije před hint streamem, takže chyba přežije rychlou vizuální kontrolu a objeví se jen, když prohlížeč skočí na pozdější stránku a žádá rozsah začínající uprostřed špatného objektu
Obě hint tabulky jsou baleny po bitech s nejvýznamnějším bitem napřed, opět podle přílohy F. Komponenta výsledek ověřuje tak, jak by to dělal skutečný klient: řídce zásobené úložiště rozsahů dodá jen prvních 1 KiB z 51stránkového vzorku a nativní zacházení s hinty v PDFium si vyžádá zbytek. Poslední stránka se otevře, aniž by kdy byl přítomen celý soubor
Ověření linearizovaného PDF v Delphi
Nejlevnější kontrola je zeptat se samotného PDFium. TPdf.InspectAvailability pustí availability API nad streamem bez načtení dokumentu a jeho pole Linearization hlásí plnLinearized, plnNotLinearized nebo plnUnknown
function IsFastWebView(const FileName: string): Boolean;
var
Stream: TFileStream;
Info: TPdfAvailability;
begin
Stream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
try
Info := TPdf.InspectAvailability(Stream);
Result := Info.Linearization = plnLinearized;
finally
Stream.Free;
end;
end;
To potvrzuje, že PDFium uspořádání rozpozná; nedokazuje to, že jsou hinty správné. Pro skutečný end-to-end test podávejte soubor transportem omezeným na rozsahy a otevřete pozdní stránku přes TPdfProgressiveDocument, stejně jako to dělá průvodce streamováním velkých PDF na požádání s PDFium VCL. Vyrenderuje-li se stránka po stažení jen malého zlomku souboru, hint tabulky dělají svou práci. Přidat do release pipeline druhý nezávislý kontrolor linearizace se taky vyplatí, protože prohlížeče se liší v shovívavosti k přemístěnému /T nebo mírně špatnému posunu
Dvě limity, na které nezapomínejte. Materializér objektů pořád odmítá pole o 2 GiB a více, takže extrémně velké dokumenty zatím potřebují jinou strategii. A linearizace se vyplatí jen tehdy, když server akceptuje HTTP Range požadavky; za proxy, která odstraní Accept-Ranges, se linearizovaný soubor stahuje úplně stejně jako jakýkoli jiný
FAQ
Drží TPdf.SaveAsLinearized digitální podpisy platné?
Ne. TPdf.SaveAsLinearized přečíslovává a přepisuje každý objekt, takže existující podpisy se zneplatní. Nejprve linearizujte, pak aplikujte podpis
Umí PDFium Component linearizovat zašifrované PDF?
Jen když přededáte saRemoveSecurity. Šifrovací klíče na úrovni objektů závisí na číslech objektů, která zapisovač mění, takže výstup se zapisuje bez šifrování
Které verze PDF podporuje linearizovaný výstup?
TPdf.SaveAsLinearized přijímá pvUnknown, aby ponechal zdrojovou verzi, nebo jakýkoli cíl od PDF 1.2 výš a odmítá saIncremental, protože přidaná revize by rozbila uspořádání
Výstup Fast Web View, progresivní načítání i availability API jsou součástí PDFium Component for Delphi and C++Builder, která obaluje PDFium pro projekty v Delphi, C++Builder a Lazarus