Norint sužinoti, kur iš tikrųjų dingsta PDF failo dydis, losLab PDF Library siūlo AuditDocumentSpace, kuri kiekvieną netiesioginį objektą priskiria vienai iš dvylikos kategorijų — vaizdai, šriftų programos, šriftų žodynai, turinio srautai, formos XObject'ai, objektų srautai, įterpti failai, metaduomenys, struktūros medis, anotacijos, puslapių medis, kita — ir praneša kiekvienos objektų skaičių, saugomų baitų kiekį bei procentinę dalį
Situacija, dėl kurios tai egzistuoja, pažįstama. 40 puslapių ataskaita iš jūsų generatoriaus išeina 80 MB dydžio, klientas klausia kodėl, o jūs galite pasiūlyti tik spėjimą. Tikriausiai vaizdai. Gal šriftai. Taigi įjungiate mažinimą, siunčiate, o failas sumažėja iki 74 MB, nes tikrasis svoris slypėjo visai kitur. Mūsų papildomas straipsnis apie šriftų sutraukimą ir vaizdų mažinimą aprašo, kaip sumažinti PDF; šis straipsnis skirtas žingsniui, kuris turėtų būti pirmas — matavimui to, ką ketinate mažinti
Kodėl matuoti prieš glaudinant?
Todėl, kad trys standartiniai optimizavimo etapai duoda labai skirtingą naudą bet kuriam konkrečiam failui, ir niekas apie failą nepasako, kuris iš jų tinka, kol neįskaičiuosite. Šriftų sutraukimas dokumente, kurio šriftai jau sudaro 2% jo baitų, yra popietė, praleista koreguojant apvalinimo paklaidą. Vaizdų mažinimas faile, kurio pagrindinė dalis — nesuglaudinti turinio srautai, duoda tą pačią nusivylimo dozę. Optimizatorius nėra sunkioji dalis — kiekviena biblioteka jį turi. Žinoti, kurį optimizatorių nukreipti į šį failą, ir yra sunkioji dalis, o tai — apskaitos klausimas, ne glaudinimo klausimas. Auditas taip pat atskleidžia atvejus, kai atsakymas — jokio optimizatoriaus: failas, kuris pasirodo esąs 60% įterptų priedų, nereikalauja geresnio glaudinimo, jam reikia pokalbio apie tai, ar tie priedai apskritai turėtų būti dokumente, o failas, kuris yra 30% struktūros medis, moka už prieinamumo žymėjimą, kuris paprastai yra sąmoninga kaina, kurios neverta tyliai pašalinti. Kai baitai priskirti, jūs priimate produkto sprendimą, paremtą skaičiais, o ne siekiate artimiausio jungiklio
Ką turi dvylikos kategorijų ataskaita
AuditDocumentSpace grąžina eilučių sąrašo rankeną, o ne įrašą, todėl ataskaita nepakinta perduodama per plokščią DLL ir COM fasadus. Sąraše yra Total,Objects,Bytes,100.0 santraukos eilutė, po kurios eina lygiai dvylika Category,Objects,Bytes,Percent eilučių fiksuota tvarka, kuri yra kontrakto dalis: Images, Font programs, Font dictionaries, Content streams, Form XObjects, Object streams, Embedded files, Metadata, Structure tree, Annotations, Page tree, Other. Trylika eilučių, visada, net kai kategorija tuščia
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;
Viena Delphi detalė šiame cikle įkas jus lygiai vieną kartą. GetStringListItem naudoja nuo vieneto skaičiuojamus elementų indeksus, kaip ir GetStringListCount, o už ribų esantis indeksas grąžina tuščią eilutę, o ne iškelia išimtį. Parašykite ciklą for I := 0 to Count - 1 pagal įprotį, ir gausite tuščią pirmą eilutę, tyliai praleistą paskutinę eilutę ir jokios išimties, kuri praneštų, kad indeksavimas neteisingas. Pati ataskaita atrodys beveik teisinga, o tai — blogiausias diagnostikos įrankio gedimo tipas
Kodėl auditas naudoja saugomą ilgį, o ne dekoduotą dydį?
Todėl, kad saugomas ilgis yra ir tas skaičius, kurio jums reikia, ir tas skaičius, kurį pigu gauti. Kiekvienas netiesioginis objektas neša TPDFIndObj.FLength, žalią baitų ilgį, kurį objektas užima faile taip, kaip jis buvo perskaitytas. Jį naudojant 900 KB DCTDecode vaizdas praneštas kaip 900 KB — kiek jis kainuoja jums diske — o ne kaip 40 MB RGB pavyzdžių, į kuriuos jis dekoduojasi. Tai taip pat reiškia, kad auditui niekada nereikia nieko dekoduoti: tinginiu būdu įkeliami objektai lieka tingūs, filtrai lieka nepaleisti, o 500 MB failo auditas yra ėjimas per objektų antraštes, o ne pilnas dekompresijos ciklas
Antra taisyklė — dvigubo skaičiavimo gynyba. Kai objektas gyvena suglaudintame objektų sraute, nurodomame nenuliniu FObjStrNum, jo baitų skaičius užrašomas kaip nulis. Jo saugojimas jau apmokėtas vieną kartą konteinerio srautu, kurį ISO 32000-1 §7.5.7 apibrėžia kaip /Type /ObjStm srautą, laikantį daug objektų viename Flate suglaudintame turinyje. Priskirti kiekvienam nariui savo dalį ir tada dar kartą apmokestinti konteinerį padidintų sumą virš tikrojo failo dydžio. Tai turi tiesioginę pasekmę tam, kaip skaitote išvestį, aprašytą toliau ir dar išsamiau mūsų straipsnyje apie objektų srautus ir kryžminių nuorodų srautus
Kodėl šriftų programa negali savęs klasifikuoti?
Todėl, kad į PDF įterptas TrueType šrifto failas neturi jokio žymens, apie tai pranešančio. ISO 32000-1 §9.8.1 apibrėžia įterptą šrifto programą kaip /FontFile, /FontFile2 ar /FontFile3 reikšmę šrifto aprašiklyje, o srauto žodynas kitame tos nuorodos gale neša /Length1 ir filtrų raktus, bet neturi nei /Type, nei /Subtype, kuris jį identifikuotų kaip šriftą. Žiūrint atskirai, tai — anoniminis dvejetainis srautas. Tik aprašiklis, nurodantis į jį, žino, kas tai yra. Tas pats asimetrijos atvejis pasitaiko ir anotacijoms: §12.5.2 padaro /Type /Annot nebūtiną anotacijos žodyne, todėl patikimas signalas — priklausymas puslapio /Annots masyvui, o ne pats žodynas
Todėl klasifikacija vyksta du kartus. Pirmasis etapas nuskaito kiekvieno objekto paties /Type ir /Subtype ir paima lengvas pergales: /ObjStm, /Subtype /Image, /Subtype /Form, /Type /Font ir /Type /FontDescriptor, /Metadata, /EmbeddedFile ir /Filespec, /StructTreeRoot ir /StructElem, /Annot, /Page ir /Pages. Viskas kita laikinai patenka į Other. Antrasis etapas tada eina per nuorodinę pusę ir perrašo: kiekvienas puslapio žodynas savo /Contents perpriskiria turinio srautams, savo /Annots įrašus — anotacijoms, o savo /Thumb — vaizdams, o kiekvienas šrifto žodynas eina per savo pačios aprašiklio grandinę
// 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]));
Ataskaitos skaitymas ir kito žingsnio pasirinkimas
Skaitykite pirmiausia dalis, tada objektų skaičius, ir bet kokį didelį atotrūkį tarp jų vertinkite kaip signalą. Šiuolaikinis PDF savo mažus žodynus daugiausia laiko objektų srautuose, todėl Page tree ir Structure tree įprastai rodo dešimtis objektų prieš beveik nulinius baitus — jų tikroji kaina įtraukta į Object streams eilutę. Jei pati Object streams eilutė didelė, failas sausakimšas metaduomenų tipo struktūros, o ne turinio, ir svertas — objektų šalinimas, ne glaudinimas. Anotacijų išvaizdos srautai elgiasi panašiai: jie neša /Subtype /Form, todėl gausiai antspauduotas dokumentas savo svorį rodo po Form XObjects, o Annotations eilutė lieka maža
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;
Du formatavimo faktai svarbūs, jei skaidote procentus, o ne juos rodote. Dešimtainis skirtukas visada — pažodinis taškas, nepriklausomai nuo mašinos lokalės, todėl skaidymas su aplinkos FormatSettings vokiškoje ar prancūziškoje darbo stotyje nepavyks arba, dar blogiau, bus neteisingai perskaitytas. Ir galiniai nuliai apkarpomi, todėl kategorija, sudaranti lygiai 40% baitų, atspausdinama kaip 40, o ne 40.0 — niekada nespėkite fiksuotos dešimtainės vietos. Turint dalį rankoje, maršrutizavimas mechaniškas: dominuojanti Images dalis nurodo į DownsampleImages, dominuojanti Font programs dalis — į SubsetEmbeddedFonts, o didelis Content streams — į CompressContent
Ko auditas jums sąmoningai nepasako
Suma yra netiesioginių objektų suma, o PDF failas yra šiek tiek daugiau nei jo objektai. Failo antraštė, trailer, tarpobjektiniai tarpai ir klasikinė kryžminių nuorodų lentelė nėra netiesioginiai objektai, todėl tie baitai niekam nepriskirti, ir audito suma nusileidžia šiek tiek žemiau nei dydis diske. Kryžminių nuorodų srautas yra kitoks — tai realus objektas su /Type /XRef, todėl šiuolaikiniame faile tie baitai pasirodo, kategorijoje Other. Nei vienas iš šių elgesių nėra defektas, bet jei suderinate auditą su baitų skaičiumi iš failų sistemos, štai iš kur kyla skirtumas
Dar dvi ribos verta aiškiai įvardyti. Pirma, skaičiai apibūdina įkeltą failą, ne kuriamą: objektams, sukurtiems atmintyje, kurie dar neturi saugomo ilgio, dydis grįžta prie serializuotos išvesties su nominalia priedu srauto žodynui, kas yra būsimo įrašymo įvertis, o ne matavimas. Auditą atlikite po išsaugojimo ir pakartotinio įkėlimo, jei norite tikslių skaičių. Antra, riebi Other eilutė yra atradimas, ne klaidos pranešimas — ji dažniausiai reiškia niekieno nebenurodomus našlaičius objektus, ir tai — darbas žymėjimo ir šlavimo šiukšlių surinkimui, o ne bet kokiam glaudinimo etapui
Naudojant tokiu būdu, auditas keičia pokalbio formą. Vietoj spėjimo apie 80 MB ataskaitą, jūs ją atverkite, paleiskite vieną kvietimą ir perskaitykite, kad vaizdai sudaro 8%, šriftų programos — 61%, o dokumentas įterpia devynias pilnas šriftų programas namų stiliui, naudojančiam tris šriftus. Tai — sprendžiamas atsakymas su prie jo pridėtu skaičiumi. AuditDocumentSpace kartu su optimizavimo etapais, į kuriuos ji nukreipia, dalyvauja losLab PDF Library Delphi ir C++Builder platformoms, kur dokumentacijos puslapiai aprašo pilną kategorijų sąrašą ir aplink jį esančią eilučių sąrašo API