Tokovi objektov PDF 1.5 zapakirajo veliko majhnih posrednih objektov v en s Flate stisnjen vsebnik, losLab PDF Library pa jih izda ob polnem shranjevanju prek zastavice PackObjectStreams. Dobiček je resničen: na stotine slovarjev strani, pisav in opomb, od katerih vsak nestisnjen stane deset ali dvajset bajtov, se skrčijo v peščico stisnjenih blobov. Cena je, da vsak zapakiran objekt zdaj potrebuje tok navzkrižnih referenc, ki ga opiše
Ta druga polovica je tista, kjer se pisci polomijo. Gradnja vsebnika /ObjStm je aritmetika; naučiti mehaniko navzkrižnih referenc, da kaže vanj, pa je preoblikovanje. Pisec, ki proizvede popolnoma veljaven vsebnik, nato pa njegove člane opiše z navadnimi odmiki tipa 1, je proizvedel datoteko, ki jo bo Acrobat odprl ravno dovolj dolgo, da jo razglasi za poškodovano. Ti dve funkciji sta ena funkcija, ta članek pa pokriva stran zapisovanja obeh, kot ju definira ISO 32000-1 §7.5.7 in §7.5.8
Kaj vsebnik ObjStm dejansko vsebuje
Tok objektov je tok, katerega dekodirani bajti so dve strnjeni regiji, ISO 32000-1 §7.5.7 pa slovarju daje natanko tri ključe, ki so pomembni za gradnjo. /Type /ObjStm ga identificira, /N pove število članov, /First pa pove bajtno dolžino regije glave — enakovredno, odmik, pri katerem se začne telo. Glava so z belim prostorom ločeni pari številke objekta in odmika; telo so člani, zaporedno serializirani, pri čemer se vsak odmik meri od začetka telesa, ne pa od začetka dekodirane vsebine. Branje popolnoma dekodiranega vsebnika to naredi očitno: spodaj je /First enak 14, ker tri vrstice glave zavzemajo štirinajst bajtov, objekt 7 pa sedi 55 bajtov v telesu, ker se je objekt 4 serializiral v 54 znakov plus ločilo
// Decoded payload of: 12 0 obj << /Type /ObjStm /N 3 /First 14
// /Filter /FlateDecode /Length 118 >> stream
4 0
7 55
9 90
<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>
<< /Type /ExtGState /CA 1 /ca 1 >>
[ 0 0 595 842 ]
Dve pravili članstva sta absolutni in obe izhajata neposredno iz §7.5.7. Objekt toka nikoli ne more biti član, ker tok nosi surove bajte, ki bi morali biti gnezdeni znotraj drugega toka. In član mora biti popolna vrednost objekta, nikoli gola posredna referenca — stisnjen objekt, ki je zgolj 5 0 R, ustvari posrednost, ki je bralnik ne more razrešiti, ne da bi že vedel, kam kaže. losLab PDF Library oba primera izloči med zbiranjem kandidatov, skupaj s slovarjem šifriranja in objektom 0, nato pa vse, kar preživi, zapakira v skupine po 200 na vsebnik. Ta zgornja meja je odločitev glede naključnega dostopa, ne omejitev specifikacije: bralnik, ki želi enega člana, mora napihniti celoten vsebnik, zato predimenzionirani vsebniki podražijo majhna iskanja
Zakaj morajo člani ObjStm uporabljati vnose navzkrižnih referenc tipa 2?
Ker zapakiran objekt nima bajtnega odmika za zapis. ISO 32000-1 §7.5.8 na to odgovori s tremi tipi vnosov v binarnem toku navzkrižnih referenc: tip 0 za proste objekte, tip 1 za navadne objekte v uporabi, shranjene pri bajtnem odmiku, in tip 2 za stisnjene objekte, katerih dve podatkovni polji nosita številko objekta vsebnika in indeks člana znotraj njega. Zapakiranega objekta ni mogoče izraziti v klasični besedilni tabeli xref, kar je natanko razlog, da je PDF 1.5 uvedel obe funkciji hkrati
Vrstni red, ki sledi, spotakne skoraj vsako prvo implementacijo, vključno z našo. Navadni objekti dobijo vnose tipa 1. Vsebniki /ObjStm sami dobijo vnose tipa 1, ker je vsebnik popolnoma običajen posreden objekt toka, zapisan pri resničnem odmiku. Le člani dobijo vnose tipa 2. In tok navzkrižnih referenc je sam posreden objekt v datoteki, zato potrebuje svoj lasten vnos tipa 1, ki kaže na odmik, kjer je bil pravkar zapisan — isti odmik, ki ga beleži startxref. Zgodnja različica našega pisca je iz zanke zapisovanja izključevala številke objektov vsebnikov namesto članov, rezultat pa je bila datoteka s tokom navzkrižnih referenc in brez ijkakega toka objektov: strukturno skladna, semantično prazna, zavrnjena naprej po verigi. Vrednost /Size skriva ujemajočo se napako za ena, saj je najvišja številka objekta plus ena, tok navzkrižnih referenc pa je dodeljen kot najvišja številka objekta, zato ga je treba tudi prešteti
Dimenzioniranje polja /W: zakaj štirje bajti ne zadostujejo
Polje /W deklarira bajtno širino vsakega od treh polj, losLab PDF Library pa ga zapiše kot /W [1 Field2 Field3], pri čemer je polje 1 fiksno pri enem bajtu za kodo tipa, polje 3 pa fiksno pri dveh bajtih, kar pokrije tako številke generacij do 65535 kot indekse članov. Polje 2 je tisto, ki ne more biti konstanta, ker nosi dve nepovezani količini: v vnosu tipa 1 je to bajtni odmik, omejen le z velikostjo datoteke, v vnosu tipa 2 je to številka objekta vsebnika, v vnosu tipa 0 pa naslednji prosti objekt v verigi. Fiksno štiribajtno polje 2 deluje dobro, dokler datoteka ne preseže 4 GB, ko se vsak odmik prek te meje tiho odreže in cela tabela postane smeti. Pisec zato preišče sestavljeno tabelo za največjo vrednost, ki jo bo kadarkoli nosilo katerokoli mesto polja 2, vključno z odmikom samega toka navzkrižnih referenc, in polje razširi do osmih bajtov
// Field 2 must hold the largest byte offset AND the largest
// ObjStm container number AND the largest free-chain target.
MaxField2Value := XRefStart;
for X := 0 to MaxObj do
begin
if XRefTable[X].InUse and (XRefTable[X].ObjStrNum > 0) then
Field2Value := XRefTable[X].ObjStrNum // type-2: container number
else
Field2Value := XRefTable[X].ObjPos; // type-1 offset / type-0 next-free
if Field2Value > MaxField2Value then
MaxField2Value := Field2Value;
end;
Field2 := 4;
while (Field2 < 8) and
(MaxField2Value > ((Int64(1) shl (Field2 * 8)) - 1)) do
Inc(Field2);
Field3 := 2; // generation numbers and member indices both fit
Ko so širine znane, je velikost vsebine znana natančno, zato pisec vnaprej dodeli celoten medpomnilnik in ga zapolni po indeksu; dodajanje vnosov bajt za bajtom v AnsiString naredi gradnjo tabele kvadratno, česar nihče ne opazi pri desetstranskem računu, opazi pa vsak pri dokumentu z dvesto tisoč objekti. Dve nadaljnji podrobnosti ohranjata stroge bralnike zadovoljne. /Index deklarira, katera območja številk objektov tabela pokriva, pri polnem prepisu pa je to preprosto [0 N] brez vrzeli. In vsako mesto, ki ga pisec dejansko ni izdal, mora privzeto biti prosto, ne pa v uporabi: objekt 0 vodi verigo prostih, vsako prosto mesto se poveže z naslednjim, mesto, ki je nekoč hranilo izbrisan objekt, pa obdrži svojo generacijo, povečano za ena. Spremljajoča opomba o varnosti pomnilnika pri razčlenjevanju nezaupanja vrednih PDF-jev podaja isti argument o mejah, gledano s strani branja
Zakaj tok navzkrižnih referenc nikoli ne sme biti šifriran?
Ker ga mora bralnik razčleniti, preden lahko sploh ve, kako karkoli dešifrirati. Tok navzkrižnih referenc je tisti, ki bralniku pove, kje živi slovar /Encrypt; če bi bili njegovi bajti sami šifrirani, bi bralnik potreboval ključ datoteke, da bi našel objekt, ki opisuje ključ datoteke. losLab PDF Library to uveljavlja v enem samem predikatu: ShouldCryptStreamData vrne False, kadarkoli slovar toka nosi /Type /XRef, tako da izjema velja ne glede na to, katera pot doseže serializator
Vsebnik /ObjStm dobi nasprotno obravnavo, asimetrija pa je namerna. Vsebnik je šifriran v celoti, s ključem po svoji lastni številki objekta, natanko tako kot vsak drug tok. Njegovi člani niso šifrirani posamično — zapakirani so v svoji dešifrirani obliki golega besedila, en sam prehod čez sestavljeni vsebnik pa jih pokrije, niti vključno. Dvojno šifriranje članov proizvede datoteko, ki se dešifrira v šifrirano besedilo, in ker zunanji sloj uspe, se napaka pokaže kot napaka razčlenjevanja globoko v grafu objektov, ne pa kot napaka avtentikacije. En objekt nato ostane povsem zunaj sheme: v šifriranem dokumentu je Katalog ohranjen kot neposreden objekt tipa 1 in nikoli zapakiran, ker bi njegovo pakiranje prisililo nalagalnik, da napihne in dešifrira tok objektov, da bi dosegel koren dokumenta, še preden je kontekst dešifriranja, ki ga koren pomaga vzpostaviti, popolnoma zgrajen
Vklop pakiranja iz Delphija
Javno stikalo je PackObjectStreams, izpostavljeno kot polje na TPDFlibSaveOptions, kot samostojen nastavljalnik SetPackObjectStreams in kot lastnost na objektu dokumenta. Privzeto je omogočeno in samodejno omejeno glede na različico: pisec pakira le, kadar je dokument že PDF 1.5 ali novejši, in pokliče notranje varovalo najnižje različice, tako da je zapakiran dokument povišan na 1.5, ne pa napačno označen. Po shranjevanju GetLastSaveUsedObjectStreams poroča, ali se je zapora dejansko odprla, kar je trditev, ki jo želite v regresijskem testu, ne pa primerjava velikosti v bajtih
var
Doc: TPDFlib;
Options: TPDFlibSaveOptions;
begin
Doc := TPDFlib.Create;
try
if Doc.LoadFromFile('report.pdf', '') <= 0 then
Exit;
Doc.SetInformation(0, '1.5'); // packing is gated on PDF 1.5+
FillChar(Options, SizeOf(Options), 0);
Options.CompressContent := True;
Options.GarbageCollect := True; // drop orphans before packing
Options.PackObjectStreams := True;
if Doc.SaveToFileOptions('report-packed.pdf', Options) = 1 then
if Doc.GetLastSaveUsedObjectStreams = 1 then
Writeln('Saved with ObjStm containers and an xref stream');
finally
Doc.Free;
end;
end;
Vrstni red med pakiranjem in zbiranjem smeti je pomemben. Analiza dosegljivosti mora teči najprej, ker član, ki preživi v vsebnik, s seboj potegne tudi vsebnik — če je živ objekt zapakiran, je njegova številka vsebnika po definiciji dosegljiva, pometanje vsebnika stran pa člana pusti nasedlega brez načina, da bi ga lociral. Zagon zbiralnika najprej tudi pomeni, da mrtvi objekti sploh nikoli ne vstopijo v vsebnik, od koder izhaja kumulativna prednost pri velikosti. Pakiranje dopolnjuje druge vzvode velikosti namesto da bi jih nadomeščalo; sprehod skozi optimizacijo velikosti datoteke PDF in podnabor pisav pokriva vzvode, ki delujejo na vsebino tokov, medtem ko tokovi objektov delujejo na strukturo
Meje, ki jih velja poznati, preden jih omogočite
Prirastna shranjevanja nikoli ne pakirajo. Prirastna posodobitev doda nove objekte in nov razdelek navzkrižnih referenc, medtem ko pusti prejšnje revizije fizično nedotaknjene, zato bi ponovno pakiranje obstoječih objektov v sveže vsebnike osirotilo vnose tipa 1, na katere se prejšnja revizija še vedno sklicuje; losLab PDF Library onemogoči pakiranje, kadarkoli je aktiven način dodajanja, članek o prirastnih posodobitvah in pretakanju v načinu dodajanja pa to pot pokriva v celoti. Dokumenti pod PDF 1.5 brezpogojno ohranijo besedilno tabelo navzkrižnih referenc: odjemalec 1.4 nima pojma, kaj pomeni /ObjStm, tiho poviševanje dokumenta, ker si je pisec zaželel manjšo datoteko, pa bi bila napačna odločitev v imenu klicatelja. En izbirni ključ, ki ga namerno ne izdajamo, je /Extends, ki ga ISO 32000-1 §7.5.7 definira, tako da lahko vsebnik poimenuje predhodnika, bralniki pa lahko verigo vsebnikov obravnavajo kot logično skupino. Res je izbiren, vsak vsebnik, ki ga zapišemo, je samostojen in neodvisno dekodirljiv, izpuščanje pa iz pisca odstrani razred ciklov in napak visečih referenc — čeprav morajo bralniki seveda še vedno spoštovati /Extends, kadar naletijo nanj v datotekah drugih proizvajalcev
Pakiranje tokov objektov in izpis toka navzkrižnih referenc sta del losLab PDF Library za Delphi in C++Builder, skupaj z zbiralnikom smeti in optimizatorjem vsebinskih tokov, s katerima se sestavljata; stran izdelka nosi celoten referenčni opis možnosti shranjevanja