HotPDF piše linearizirane PDF datoteke, izgled koji Acrobat naziva Fast Web View, kroz svojstvo LinearizeOutput na THotPDF. Postavljanje prije BeginDoc tjera HotPDF da presloži gotov graf objekata tako da čitač svjestan raspona bajtova može prikazati stranicu jedan nakon dohvaćanja samo početnog dijela datoteke, umjesto preuzimanja cijelog dokumenta prvo. Mehanizam je ISO 32000-1 Prilog F
Razlog zašto je ovo bitno je nezanimljiv. Obična PDF datoteka stavlja svoju cross-reference tablicu na kraj, pa čitač mora doseći posljednji bajt prije nego zna gdje se što nalazi. Dajte pregledniku izvještaj od 200 skeniranih stranica i korisnik gleda u vrteći se indikator kroz cijeli prijenos, iako je jedino što je želio bila stranica 1. Linearizacija to popravlja plaćanjem cijene u trenutku pisanja. Ovaj se članak konkretno bavi tim putem pisanja, particioniranjem, petljom mjerenja i strogim granicama; za konceptualnu podlogu o tome što Fast Web View donosi, raniji objašnjenje PDF linearizacije i Fast Web View-a pokriva to područje
Što linearizirani izgled zapravo jamči
Linearizirana datoteka je obična PDF datoteka s izrazito specifičnim fizičkim redoslijedom, i svako jamstvo koje nudi dolazi iz tog redoslijeda, a ne iz bilo kojeg novog tipa objekta. HotPDF emitira dijelove u redoslijedu koji propisuje Prilog F: rječnik parametara linearizacije unutar prvih 1024 bajta, ranu cross-reference tablicu, objekte na razini dokumenta, primarni hint stream, prvu stranicu i njene privatne objekte, zatim preostale stranice, zatim zajedničke objekte, zatim sve ostalo, i naposljetku glavnu cross-reference tablicu
Particioniranje se izvodi, ne deklarira. HotPDF prolazi kroz graf referenci od svakog objekta stranice i bilježi, za svaki neizravni objekt, koliko stranica do njega doseže i koja ga je stranica prva dosegla. Objekt koji koristi točno jedna stranica postaje privatan toj stranici. Objekt koji dosegne više od jedne postaje zajednički. Katalog, plus sve na što upućuje pod /ViewerPreferences, /OpenAction, /Threads i /AcroForm, plus rječnik šifriranja kad je zaštita aktivna, čine skupinu na razini dokumenta koja mora prethoditi svemu ostalom. Čvorovi stabla stranica namjerno se zadržavaju kako ne bi zagadili sekciju prve stranice
Rječnik parametara nosi brojeve koji čitaču trebaju prije nego pročita bilo što drugo: /L za ukupnu duljinu datoteke, /H za offset i duljinu hint streama, /O za broj objekta prve stranice, /E za bajt na kojem sekcija prve stranice završava, /N za broj stranica i /T za offset unosa glavne cross-reference tablice. Svaki od njih je offset bajta u datoteku koja u trenutku kad ih trebate zapisati još ne postoji
Zašto offseti hint tablice moraju konvergirati?
Zato što brojevi u rječniku parametara opisuju datoteku koja ih sadrži, i mijenjanje bilo kojeg od njih mijenja datoteku. To je središnja teškoća lineariziranog pisača, i zbog toga HotPDF mjeri ponovljeno umjesto da piše jednom. Proširite /T sa 6 znamenki na 7 i rječnik parametara raste za jedan bajt; zaglavlje raste; svaki se objekt pomiče; glavna cross-reference tablica se pomiče; /T sad treba drugačiju vrijednost. Izgled mora doseći fiksnu točku prije nego se ijedan bajt stvarnog izlaza obveže
HotPDF ovo rješava ograničenom iteracijom. Prvo serijalizira svaki objekt u stream koji broji, koji bilježi duljinu bez čuvanja bajtova, tako da svaki objekt ima poznatu serijaliziranu veličinu. Zatim pokreće prolaz izgleda koji dodjeljuje offsete skupini razine dokumenta, hint streamu, skupini prve stranice, skupinama kasnijih stranica, zajedničkoj skupini i preostatku, te izvještava gdje bi glavna cross-reference tablica sletjela. Taj se rezultat vraća kao ulaz sljedećeg prolaza. Petlja je ograničena na osam pokušaja, a nekonvergencija izaziva iznimku umjesto proizvodnje datoteke s uvjerljivo izgledajućim pogrešnim offsetima
CandidateMainOffset := 0;
for Attempt := 0 to 7 do
begin
CalculateLayout(CandidateMainOffset, FirstXRefData,
HintOffset, EndFirstPage, NewMainOffset);
if NewMainOffset = CandidateMainOffset then
Break;
CandidateMainOffset := NewMainOffset;
end;
if NewMainOffset <> CandidateMainOffset then
raise Exception.Create('Linearization layout did not converge');
Dva detalja sprječavaju petlju od trzanja. Rječnik parametara zapisuje se u fiksni utor od 384 bajta, popunjen razmacima, tako da njegov vlastiti rast nikad ne može destabilizirati izgled; ako bi tekst rječnika ikad premašio tu rezervaciju, HotPDF izaziva iznimku umjesto tihog pomicanja svega. I nakon konvergencije HotPDF pokreće još jedan potvrdni prolaz izgleda i ponovno provjerava duljinu hint streama, jer sam hint stream kodira offsete koji su bili poznati tek kad se izgled slegao. Isplata svog tog mjerenja je da HotPDF nikad ne baferuje drugu kopiju dokumenta: kad su offseti fiksirani, objekti se serijaliziraju izravno u odredišni stream, s tvrdnjom na svakoj granici sekcije da zapisani bajtovi odgovaraju offsetu koji je obećan
Uključivanje iz Delphija
Površina API-ja je jedan Boolean, i jedini je zahtjev da ga postavite prije nego generiranje počne. LinearizeOutput zadano je False, a prolaz izgleda pokreće se kad se dokument piše, pa dodjeljivanje nakon EndDoc ne postiže ništa
var
PDF: THotPDF;
begin
PDF := THotPDF.Create(nil);
try
PDF.FileName := 'fast-view.pdf';
PDF.Version := pdf17;
PDF.LinearizeOutput := True; // must precede BeginDoc
PDF.BeginDoc;
PDF.Canvas.TextOut(72, 72, 'First page');
PDF.EndDoc;
finally
PDF.Free;
end;
end;
Jedno upozorenje o postavljanju nadmašuje sve na strani koda. Linearizacija se isplati samo kad transport podržava HTTP range zahtjeve. Poslužite istu datoteku s krajnje točke koja je stream-a cijelu, ili s CDN konfiguracijom koja zanemaruje Range, i kupili ste sebi sporiji put pisanja i veću datoteku bez ikakve koristi vidljive korisniku. Provjerite poslužitelj prije nego provjerite kod
Zašto linearizacija nadglasava UseXRefStream i UseObjectStreams?
Zato što lineariziranom pisaču svaki objekt treba imati vlastiti izravno adresabilni offset bajta, a obje te značajke to oduzimaju. HotPDF stoga emitira tradicionalne tekstualne cross-reference tablice i raspakirane neizravne objekte kad god je LinearizeOutput uključen, čak i ako je pozivatelj također postavio UseXRefStream ili UseObjectStreams. Ovo je namjerno nadglašavanje, a ne sukob koji morate sami razriješiti
Obrazloženje slijedi iz hint tablica. Hint tablica opisuje gdje sekcija stranice počinje i koliko je dugačka, tako da čitač može zatražiti točno taj raspon. Objekt spakiran u kontejner /ObjStm uopće nema neovisan offset; postoji samo kao isječak unutar drugog komprimiranog streama koji se mora dohvatiti i dekomprimirati kao cjelina. Ako ste računali na object streamove radi veličine datoteke, imajte na umu da linearizacija i kompresija ovdje povlače u suprotnim smjerovima, i pročitajte kompromis u popratnom tekstu o object streamovima i prirasnim ažuriranjima u HotPDF-u. Ista napetost oblikuje hibridne datoteke, koje postoje upravo kako bi starijim čitačima omogućile rad uz tablice temeljene na streamovima, kako je pokriveno u članku o hibridnim cross-reference streamovima u PDF-ovima generiranim iz Officea
Postoji i podna granica verzije. Linearizacija zahtijeva PDF 1.2 ili noviji. Ako je odabrana verzija starija, HotPDF je automatski podiže, osim ako je postavljen StrictVersionLock, u kojem slučaju pisanje izaziva iznimku umjesto tihog promoviranja dokumenta koji ste namjerno fiksirali
Zid od 4 GiB, i zašto HotPDF odbija umjesto skraćivanja
Hint tablice linearizacije pohranjuju offsete kao 32-bitne vrijednosti, pa linearizirana datoteka ne može adresirati ništa na ili iznad 4 GiB, a HotPDF odbija takav izlaz s eksplicitnom iznimkom umjesto pisanja datoteke s omotanim offsetima. Granica nije izbor implementacije HotPDF-a; to je širina polja koju definira Prilog F
Provjera se primjenjuje na tri mjesta, i sva tri su bitna. HotPDF provjerava svaki objekt čim je poznata njegova serijalizirana duljina, provjerava duljinu svake sekcije stranice tijekom izgradnje unosa hint tablice, i provjerava konačnu duljinu datoteke nakon što je glavna cross-reference tablica dimenzionirana. Rano zakazivanje je cijela poanta: hint tablica s tiho skraćenim offsetom proizvodi datoteku koja se ispravno otvara u čitaču koji je preuzima cijelu, a zakazuje samo za klijenta s rasponom bajtova kojemu je linearizacija služila, što je najgori mogući način zakazivanja jer ga vaš testni čitač nikad ne reproducira. Ako proizvodite izlaz od nekoliko gigabajta, linearizacija nije alat, a pristup streaminga opisan u bilješkama o Direct File API za velike PDF tijekove rada smjer je u koji treba gledati
Otkrivanje linearizacije na učitanoj datoteci
THotPDF.IsLoadedLinearized javlja je li trenutno učitani dokument već napisan u lineariziranom obliku, a odgovara iz snimke uzete prije raščlambe, a ne iz živog streama. HotPDF čita prvih 1024 bajta od pozicije nula izvornog streama, pretražuje ih za prvom ključnom riječi obj, a zatim za unosom /Linearized s vrijednošću 1, i predmemorira bulean rezultat
var
PDF: THotPDF;
PageCount: Integer;
begin
PDF := THotPDF.Create(nil);
try
PageCount := PDF.LoadFromFile('incoming.pdf');
if (PageCount > 0) and (not PDF.IsLoadedLinearized) then
Writeln('Source is not Fast Web View ready');
finally
PDF.Free;
end;
end;
Dva ograničenja u tom opisu su nosiva. Otkrivanje se ne može osloniti na poziciju streama, jer je do trenutka kad kod aplikacije postavi pitanje parser već pomaknuo poziciju, i ne može ponovno čitati na zahtjev jer LoadFromFile otpušta interni izvorni stream čim učitavanje završi. Otuda dizajn hvatanja-prije-raščlambe-i-predmemoriranja. Pretraga je i namjerno doslovna oko vrijednosti: prihvaća se samo /Linearized 1 ili numerički ekvivalentan oblik s posve nulastim razlomkom, jer datoteka čiji rječnik parametara kaže nešto drugo ne daje obećanje Priloga F
Zamka Delphi zapisa vrijedna krađe
Lokalni zapisi koji sadrže dinamičke nizove inicijaliziraju svoja upravljana polja i ništa drugo, a ako držite obično polje Count uz niz, morate ga sami obrisati. Ovo je ugrizlo particioniranje linearizacije tijekom razvoja, i vrsta je greške koja košta dan upravo zato što je jedna platforma sakrije
type
THPDFLinearIndexList = record
Values: THPDFIntegerArray; // managed field: cleared for you
Count: Integer; // plain field: whatever was on the stack
end;
// Required, not cosmetic:
Part4 := Default(THPDFLinearIndexList);
Part6 := Default(THPDFLinearIndexList);
Part8 := Default(THPDFLinearIndexList);
Part9 := Default(THPDFLinearIndexList);
Polje dinamičkog niza broji reference, pa ga prevoditelj nulira. Count uz njega je obično cijelo polje bez takvog jamstva, a neinicijalizirani Count šalje sam prvi dodatak na proizvoljan indeks. Pod Win32 utor stoga na stogu slučajno je sadržavao nulu, dodatak je sletio na indeks 0, i svaki test je prošao. Pod Win64 isti kod pisao je iza kraja niza. Pouka se generalizira daleko izvan linearizacije: kad zapis miješa upravljana i neupravljana polja, dodijelite Default(TRecord) i prestanite rasuđivati koja polja prevoditelj pokriva, i nikad ne tretirajte zeleno Win32 pokretanje kao dokaz da je inicijalizacija ispravna
Članovi LinearizeOutput i IsLoadedLinearized opisani ovdje isporučuju se sa standardnim HotPDF Component za Delphi i C++Builder; stranica proizvoda nosi potpunu referencu svojstava, uključujući pravila interakcije s cross-reference streamovima, object streamovima i zaključavanjem verzije