Za ugotovitev, kam dejansko gre velikost PDF datoteke, losLab PDF Library izpostavi AuditDocumentSpace, ki vsak posredni objekt razvrsti v dvanajst kategorij — slike, programi pisav, slovarji pisav, vsebinski tokovi, obrazčne XObjekte, tokovi objektov, vgrajene datoteke, metapodatki, strukturno drevo, opombe, drevo strani, drugo — in poroča o številu objektov, shranjenih bajtih in odstotnem deležu vsake
Situacija, za katero to obstaja, je znana. 40-stranski poročilo pride iz vašega generatorja v velikosti 80 MB, stranka vpraša zakaj, vi pa lahko ponudite le ugibanje. Verjetno slike. Morda pisave. Zato vklopite podvzorčenje, pošljete datoteko, ta pa pristane pri 74 MB, ker je bila prava teža čisto drugje. Naš spremljevalni članek o podnaboru pisav in podvzorčenju slik pokriva, kako PDF skrčiti; ta pokriva korak, ki bi moral priti prej, torej merjenje tistega, kar boste skrčili
Zakaj meriti pred stiskanjem?
Ker imajo trije standardni postopki optimizacije na katerem koli danem datoteki povsem različne donose, dokler ne preštejete, pa vam nič v datoteki ne pove, kateri velja. Podnaborno oblikovanje pisav v dokumentu, katerega pisave so že 2 % njegovih bajtov, je popoldne, porabljeno za premikanje napake zaokroževanja. Podvzorčenje slik v datoteki, katere obseg je nestisnjen vsebinski tok, prinese enako razočaranje. Optimizator ni težji del — vsaka knjižnica ga ima. Vedeti, kateri optimizator uporabiti za to datoteko, je težji del, in to je računovodsko, ne kompresijsko vprašanje. Revizija ujame tudi primere, kjer je odgovor "brez optimizatorja": datoteka, ki se izkaže za 60-odstotno vgrajene priloge, ne potrebuje boljše kompresije, temveč pogovor o tem, ali te priloge sploh spadajo v dokument, datoteka, ki je 30-odstotno strukturno drevo, pa plačuje za označevanje dostopnosti, kar je običajno namerni strošek, ki ga ne smete tiho odstraniti. Ko so bajti pripisani, sprejemate produktno odločitev s številkami za seboj namesto da posežete po najbližjem stikalu
Kaj vsebuje poročilo z dvanajstimi kategorijami
AuditDocumentSpace vrne ročnik seznama nizov namesto zapisa, tako da poročilo nespremenjeno preživi ravno DLL in COM fasado. Seznam vsebuje povzetkovno vrstico Total,Objects,Bytes,100.0, ki ji sledi natanko dvanajst vrstic Category,Objects,Bytes,Percent v fiksnem vrstnem redu, ki je del pogodbe: Images, Font programs, Font dictionaries, Content streams, Form XObjects, Object streams, Embedded files, Metadata, Structure tree, Annotations, Page tree, Other. Trinajst vrstic, vedno, tudi kadar je kategorija prazna
var
Lib: TPDFlib;
ListID, I: Integer;
begin
Lib := TPDFlib.Create;
try
if Lib.LoadFromFile('report.pdf', '') <> 1 then
Exit;
ListID := Lib.AuditDocumentSpace; // 0 when no document is selected
if ListID = 0 then
Exit;
try
// GetStringListItem is 1-based: items run 1..GetStringListCount
for I := 1 to Lib.GetStringListCount(ListID) do
Memo1.Lines.Add(Lib.GetStringListItem(ListID, I));
finally
Lib.ReleaseStringList(ListID);
end;
finally
Lib.Free;
end;
end;
Ena Delphi podrobnost v tej zanki vas bo ugriznila natanko enkrat. GetStringListItem uporablja indekse elementov, ki se začnejo z ena, kar se ujema z GetStringListCount, indeks izven obsega pa vrne prazen niz namesto da bi sprožil izjemo. Napišite zanko kot for I := 0 to Count - 1 po navadi in dobite prazno prvo vrstico, tiho izpuščeno zadnjo vrstico in nikjer izjeme, ki bi vam povedala, da je indeksiranje napačno. Poročilo samo bo videti skoraj pravilno, kar je najhujši način odpovedi, ki ga ima lahko diagnostično orodje
Zakaj revizija uporablja shranjeno dolžino namesto dekodirane velikosti?
Ker je shranjena dolžina hkrati številka, ki jo želite, in številka, ki jo je poceni pridobiti. Vsak posredni objekt nosi TPDFIndObj.FLength, surovo dolžino v bajtih, ki jo objekt zaseda v razčlenjeni datoteki. Njena uporaba pomeni, da se 900 KB velika slika DCTDecode poroča kot 900 KB — bajti, ki vas stanejo na disku — namesto 40 MB vzorcev RGB, v katere se dekodira. Prav tako pomeni, da revizija nikoli ne dekodira ničesar: leno naloženi objekti ostanejo leni, filtri ostanejo nepognan, revizija 500 MB velike datoteke pa je prehod čez glave objektov, ne poln cikel dekompresije
Drugo pravilo je obramba pred dvojnim štetjem. Kadar objekt živi znotraj stisnjenega toka objektov, kar označuje neničelen FObjStrNum, se njegovo število bajtov zabeleži kot nič. Njegova shramba je bila že enkrat plačana s strani vsebnika toka, ki ga ISO 32000-1 §7.5.7 opredeljuje kot tok /Type /ObjStm, ki vsebuje veliko objektov v enem s Flate stisnjenem tovoru. Zaračunavanje vsakemu članu njegovega deleža in nato še vsebniku bi napihnilo skupno vsoto nad dejansko velikost datoteke. To ima neposredno posledico na to, kako berete izhod, kar je obravnavano spodaj in bolj poglobljeno v našem članku o tokovih objektov in navzkrižnih referenc
Zakaj se program pisave ne more razvrstiti sam?
Ker vgrajena datoteka pisave TrueType v PDF-ju nima nobene oznake, ki bi to povedala. ISO 32000-1 §9.8.1 opredeljuje vgrajen program pisave kot vrednost /FontFile, /FontFile2 ali /FontFile3 v opisovalcu pisave, slovar toka na drugem koncu te reference pa nosi ključa /Length1 in filtra, ne pa /Type in ne /Subtype, ki bi ga označil kot pisavo. Gledan ločeno je to anonimen binarni tok. Le opisovalec, ki nanj kaže, ve, kaj je. Ista asimetrija se pojavi pri opombah: §12.5.2 naredi /Type /Annot neobvezen v slovarju opombe, zato je zanesljiv signal članstvo v polju strani /Annots, ne slovar sam
Zato razvrščanje teče dvakrat. Prvi prehod prebere lastna /Type in /Subtype vsakega objekta in pobere lahke zmage: /ObjStm, /Subtype /Image, /Subtype /Form, /Type /Font in /Type /FontDescriptor, /Metadata, /EmbeddedFile in /Filespec, /StructTreeRoot in /StructElem, /Annot, /Page in /Pages. Vse drugo začasno pristane pod Other. Drugi prehod nato obiskuje referenčno stran in prepiše: vsak slovar strani ponovno dodeli svoj /Contents vsebinskim tokovom, svoje vnose /Annots opombam in svoj /Thumb slikam, medtem ko vsak slovar pisave obišče lastno verigo opisovalcev
// Shape of the second pass: the referrer names the object
Descriptor := DictOf(FontDict.FindValueByKeyName('FontDescriptor'));
if Assigned(Descriptor) then
begin
MarkRef(FontDict.FindValueByKeyName('FontDescriptor'), catFontDicts);
MarkRef(Descriptor.FindValueByKeyName('FontFile'), catFontPrograms);
MarkRef(Descriptor.FindValueByKeyName('FontFile2'), catFontPrograms);
MarkRef(Descriptor.FindValueByKeyName('FontFile3'), catFontPrograms);
end;
// Type0 fonts keep the descriptor one level down
Descendants := FontDict.FindValueByKeyName('DescendantFonts', True);
if (Descendants is TPDFArray) and (TPDFArray(Descendants).Count > 0) then
MarkFontProgramRefs(DictOf(TPDFArray(Descendants).Item[0]));
Branje poročila in izbira naslednje poteze
Najprej berite deleže, nato število objektov, vsako veliko vrzel med njima pa obravnavajte kot signal. Sodoben PDF postavi večino svojih majhnih slovarjev v tokove objektov, zato vrstici Page tree in Structure tree rutinsko kažeta na desetine objektov ob skoraj ničelnem številu bajtov — njun resnični strošek je zložen v vrstico Object streams. Če je Object streams sama po sebi velika, je datoteka gosta z metapodatkom podobno strukturo, ne z vsebino, vzvod pa je obrezovanje objektov, ne njihovo stiskanje. Tokovi videza opomb se obnašajo podobno: nosijo /Subtype /Form, zato močno žigosan dokument pokaže svojo težo pod Form XObjects, vrstica Annotations pa ostane majhna
function CategoryShare(Lib: TPDFlib; ListID: Integer;
const Category: string): Double;
var
I: Integer;
Parts: TArray<string>;
Inv: TFormatSettings;
begin
Result := 0;
Inv := FormatSettings;
Inv.DecimalSeparator := '.'; // the report is locale-independent
for I := 2 to Lib.GetStringListCount(ListID) do // line 1 is Total
begin
Parts := string(Lib.GetStringListItem(ListID, I)).Split([',']);
if (Length(Parts) = 4) and SameText(Parts[0], Category) then
Exit(StrToFloatDef(Parts[3], 0, Inv));
end;
end;
Dve dejstvi o obliki sta pomembni, če deleže razčlenjujete namesto zgolj prikazujete. Decimalno ločilo je vedno dobesedna pika ne glede na jezikovne nastavitve stroja, zato bo razčlenjevanje z okoliškim FormatSettings na nemški ali francoski delovni postaji spodletelo ali, še huje, napačno prebralo. Prav tako se sledeče ničle obrežejo, zato kategorija, ki ima natanko 40 % bajtov, natisne 40, ne 40.0 — nikoli ne predpostavljajte fiksnega števila decimalnih mest. S poznanim deležem je usmerjanje mehansko: prevladujoč delež Images kaže na DownsampleImages, prevladujoč delež Font programs na SubsetEmbeddedFonts, obsežni Content streams pa na CompressContent
Kaj vam revizija namerno ne pove
Skupna vsota je vsota po posrednih objektih, PDF datoteka pa je nekoliko več kot njeni objekti. Glava datoteke, sled, presledki med objekti in klasična tabela navzkrižnih referenc niso posredni objekti, zato ti bajti niso pripisani ničemur in skupna vsota revizije pristane malce pod velikostjo na disku. Tok navzkrižnih referenc je drugačen — je pravi objekt z /Type /XRef, zato se v sodobni datoteki ti bajti pojavijo, v kategoriji Other. Nobeno od teh obnašanj ni napaka, če pa revizijo usklajujete s številom bajtov iz datotečnega sistema, je tu razlog za vrzel
Vredno je jasno navesti še dve meji. Prvič, številke opisujejo naloženo datoteko, ne datoteke, ki nastaja: za objekte, zgrajene v pomnilniku, ki še nimajo shranjene dolžine, se velikost vrne na serializiran izhod z nominalnim dodatkom za slovar toka, kar je ocena morebitnega zapisa, ne meritev. Revidirajte po shranitvi in ponovnem nalaganju, če želite natančne podatke. Drugič, debela vrstica Other je ugotovitev, ne poročilo o napaki — običajno pomeni osirotele objekte, na katere ne kaže več nič, kar je delo za označi-in-pomedi zbiranje smeti, ne za noben prehod stiskanja
Uporabljena na ta način revizija spremeni obliko pogovora. Namesto ugibanja ob 80 MB poročilu ga odprete, izvedete en klic in preberete, da so slike 8 %, programi pisav 61 %, dokument pa vgrajuje devet celotnih programov pisav za hišni slog, ki uporablja tri fonte. To je popravljiv odgovor s pripeto številko. AuditDocumentSpace skupaj s postopki optimizacije, na katere vas usmerja, dostavlja losLab PDF Library za Delphi in C++Builder, kjer referenčne strani dokumentirajo celoten seznam kategorij in API seznama nizov okoli njega