Da biste otkrili gdje veličina PDF datoteke zapravo odlazi, losLab PDF Library izlaže AuditDocumentSpace, koji klasificira svaki neizravni objekt u dvanaest kategorija — slike, programe fontova, rječnike fontova, sadržajne streamove, form XObjecte, object streamove, ugrađene datoteke, metapodatke, stablo strukture, napomene, stablo stranica, ostalo — i izvještava broj objekata, pohranjene bajtove i postotni udio svake kategorije
Situacija za koju ovo postoji poznata je. Izvještaj od 40 stranica izlazi iz vašeg generatora s 80 MB, korisnik pita zašto, a sve što možete ponuditi je pretpostavka. Vjerojatno slike. Možda fontovi. Pa uključite podskaliranje, pošaljete to, a datoteka sleti na 74 MB jer je stvarna težina bila posve negdje drugdje. Naš popratni članak o subsetiranju fontova i podskaliranju slika pokriva kako smanjiti PDF; ovaj pokriva korak koji bi trebao doći prvi, a to je mjerenje onoga što ćete smanjiti
Zašto mjeriti prije kompresije?
Zato što tri standardna prolaza optimizacije imaju divlje različite dobitke na bilo kojoj danoj datoteci, a ništa u datoteci vam ne govori koji se primjenjuje dok ne izbrojite. Subsetiranje fontova na dokumentu čiji su fontovi već 2% njegovih bajtova je poslijepodne provedeno pomicanjem pogreške zaokruživanja. Podskaliranje slika u datoteci čiji je glavni dio nekomprimirani sadržajni streamovi proizvodi isto razočaranje. Optimizator nije teški dio — svaka knjižnica ga ima. Znati koji optimizator usmjeriti na ovu datoteku je teški dio, i to je pitanje računovodstva, a ne pitanje kompresije. Revizija također hvata slučajeve gdje optimizator uopće nije odgovor: datoteka koja se pokaže 60% ugrađenih privitaka ne treba bolju kompresiju, treba razgovor o tome pripadaju li ti privici dokumentu, a datoteka koja je 30% stablo strukture plaća za pristupačno označavanje, što je obično namjerni trošak koji ne biste trebali tiho ukloniti. Kad su bajtovi pripisani, donosite proizvodnu odluku s brojkama iza sebe umjesto posezanja za bilo kojim prekidačem koji je najbliži
Što sadrži izvještaj s dvanaest kategorija
AuditDocumentSpace vraća rukovatelj popisa nizova umjesto zapisa, tako da izvještaj preživi ravne DLL i COM fasade nepromijenjen. Popis sadrži sažetni redak Total,Objects,Bytes,100.0 nakon kojeg slijedi točno dvanaest redaka Category,Objects,Bytes,Percent u fiksnom redoslijedu koji je dio ugovora: Images, Font programs, Font dictionaries, Content streams, Form XObjects, Object streams, Embedded files, Metadata, Structure tree, Annotations, Page tree, Other. Trinaest redaka, uvijek, čak i kad 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;
Jedan Delphi detalj u toj petlji ugrist će vas točno jednom. GetStringListItem koristi indekse stavki koji počinju od 1, u skladu s GetStringListCount, a indeks izvan raspona vraća prazan niz umjesto da izazove iznimku. Napišete li petlju for I := 0 to Count - 1 iz navike, dobijete prazan prvi redak, tiho ispušten posljednji redak i nigdje iznimku koja bi vam rekla da je indeksiranje pogrešno. Izvještaj sam po sebi izgledat će gotovo ispravno, što je najgori mogući način na koji dijagnostički alat može zakazati
Zašto revizija koristi pohranjenu duljinu umjesto dekodirane veličine?
Zato što je pohranjena duljina i broj koji želite i broj koji je jeftino dobiti. Svaki neizravni objekt nosi TPDFIndObj.FLength, sirovu bajtovnu duljinu koju objekt zauzima u datoteci kako je raščlanjen. Njeno korištenje znači da se DCTDecode slika od 900 KB izvještava kao 900 KB — bajtovi koji vas koštaju na disku — umjesto 40 MB RGB uzoraka u koje se dekodira. To također znači da revizija nikad ne mora ništa dekodirati: lijeno učitani objekti ostaju lijeni, filtri ostaju nepokrenuti, a revizija datoteke od 500 MB je prolaz kroz zaglavlja objekata, a ne pun ciklus dekompresije
Drugo pravilo je obrana od dvostrukog brojanja. Kad objekt živi unutar komprimiranog object streama, na što ukazuje neniski FObjStrNum, njegov broj bajtova bilježi se kao nula. Njegova pohrana već je jednom plaćena kroz kontejnerski stream, koji ISO 32000-1 §7.5.7 definira kao stream /Type /ObjStm koji drži mnogo objekata u jednom Flate-komprimiranom sadržaju. Naplaćivanje svakom članu njegovog udjela, a zatim ponovno naplaćivanje kontejnera, napuhalo bi ukupnu vrijednost iznad stvarne veličine datoteke. To ima izravnu posljedicu na to kako čitate izlaz, obrađeno niže i detaljnije u našem članku o object streamovima i cross-reference streamovima
Zašto se program fonta ne može sam klasificirati?
Zato što TrueType datoteka fonta ugrađena u PDF nema oznaku koja to kaže. ISO 32000-1 §9.8.1 definira ugrađeni program fonta kao vrijednost /FontFile, /FontFile2 ili /FontFile3 u deskriptoru fonta, a rječnik streama na drugom kraju te reference nosi /Length1 i ključeve filtera, ali ne i /Type niti /Subtype koji ga identificira kao font. Gledano izolirano, to je anonimni binarni stream. Samo deskriptor koji na njega pokazuje zna što je. Ista asimetrija pojavljuje se kod napomena: §12.5.2 čini /Type /Annot neobveznim u rječniku napomene, tako da je pouzdan signal članstvo u nizu /Annots stranice, a ne sam rječnik
Zato klasifikacija ide dvaput. Prvi prolaz čita vlastiti /Type i /Subtype svakog objekta i uzima lake pobjede: /ObjStm, /Subtype /Image, /Subtype /Form, /Type /Font i /Type /FontDescriptor, /Metadata, /EmbeddedFile i /Filespec, /StructTreeRoot i /StructElem, /Annot, /Page i /Pages. Sve ostalo privremeno slijeće u Other. Drugi prolaz zatim prolazi kroz stranu koja referencira i nadglasava: svaki rječnik stranice ponovno dodjeljuje svoj /Contents sadržajnim streamovima, svoje unose /Annots napomenama, a svoj /Thumb slikama, dok svaki rječnik fonta prolazi svoj vlastiti lanac deskriptora
// 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]));
Čitanje izvještaja i odabir sljedećeg poteza
Čitajte udjele prvo, broj objekata drugo, i tretirajte svaki veliki jaz među njima kao signal. Moderan PDF stavlja većinu svojih malih rječnika unutar object streamova, pa Page tree i Structure tree redovito pokazuju desetke objekata nasuprot gotovo nula bajtova — njihov stvarni trošak preklopljen je u redak Object streams. Ako je sam Object streams velik, datoteka je gusta strukturom nalik metapodacima, a ne sadržajem, a poluga je izbacivanje objekata, a ne njihova kompresija. Streamovi izgleda napomena ponašaju se slično: nose /Subtype /Form, pa jako pečatiran dokument pokazuje svoju težinu pod Form XObjects, dok redak Annotations ostaje malen
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;
Dvije činjenice o formatiranju bitne su ako raščlanjujete postotke umjesto da ih samo prikazujete. Decimalni separator uvijek je doslovna točka bez obzira na regionalne postavke stroja, tako da raščlanjivanje s okolnim FormatSettings na njemačkoj ili francuskoj radnoj stanici propada ili, gore, pogrešno čita. A trailing nule su obrezane, pa se kategorija koja drži točno 40% bajtova ispisuje kao 40, ne 40.0 — nikad ne pretpostavljajte fiksan broj decimala. S udjelom u ruci usmjeravanje je mehaničko: dominantan udio Images upućuje na DownsampleImages, dominantan udio Font programs na SubsetEmbeddedFonts, a glomazni Content streams na CompressContent
Što revizija namjerno ne govori
Ukupna vrijednost je zbroj preko neizravnih objekata, a PDF datoteka je nešto više od svojih objekata. Zaglavlje datoteke, trailer, međuobjektni razmaci i klasična cross-reference tablica nisu neizravni objekti, pa se ti bajtovi ne pripisuju ničemu, a ukupna vrijednost revizije sleti malo ispod veličine na disku. Cross-reference stream je drugačiji — to je stvarni objekt s /Type /XRef, pa se u modernoj datoteci ti bajtovi doista pojavljuju, u kategoriji Other. Nijedno ponašanje nije nedostatak, ali ako usklađujete reviziju s brojem bajtova iz datotečnog sustava, odatle dolazi jaz
Vrijedi jasno naglasiti još dvije granice. Prvo, brojke opisuju datoteku koja je učitana, a ne onu koja se autorizira: za objekte izgrađene u memoriji koji još nemaju pohranjenu duljinu, veličina pada natrag na serijalizirani izlaz s nominalnim dodatkom za rječnik streama, što je procjena konačnog zapisa, a ne mjerenje. Provedite reviziju nakon spremanja-i-ponovnog-učitavanja ako želite točne brojke. Drugo, debeo redak Other je nalaz, a ne izvještaj o greški — obično znači napuštene objekte na koje se ništa više ne referencira, što je posao za mark-and-sweep prikupljanje smeća, a ne za bilo koji prolaz kompresije
Korištena na ovaj način, revizija mijenja oblik razgovora. Umjesto nagađanja oko izvještaja od 80 MB, otvorite ga, pokrenete jedan poziv i pročitate da su slike 8%, programi fontova 61%, a dokument ugrađuje devet punih programa fontova za kućni stil koji koristi tri pisma. To je odgovor koji se može popraviti s brojkom uz njega. AuditDocumentSpace, zajedno s prolazima optimizacije prema kojima vas usmjerava, isporučuje se u losLab PDF Library za Delphi i C++Builder, gdje referentne stranice dokumentiraju cijeli popis kategorija i API popisa nizova oko njega