För att ta reda på var en PDF-filstorlek faktiskt tar vägen exponerar losLab PDF Library AuditDocumentSpace, som klassificerar varje indirekt objekt i tolv kategorier — bilder, typsnittsprogram, typsnittsordböcker, innehållsströmmar, form-XObjects, objektströmmar, inbäddade filer, metadata, strukturträd, annoteringar, sidträd, övrigt — och rapporterar objektantal, lagrade byte och procentandel för var och en
Situationen detta finns till för är bekant. En 40-sidig rapport kommer ut ur din generator på 80 MB, kunden frågar varför, och allt du kan erbjuda är en gissning. Förmodligen bilderna. Kanske typsnitten. Så du slår på nedsampling, levererar den, och filen landar på 74 MB eftersom den verkliga vikten satt någon helt annan stans. Vår följdartikel om typsnittsdelmängdsbildning och bildnedsampling täcker hur man krymper en PDF; den här täcker steget som bör komma först, vilket är att mäta vad du är på väg att krympa
Varför mäta innan du komprimerar?
Eftersom de tre standardoptimeringspassen har vitt skilda utdelningar på en given fil, och inget i filen talar om för dig vilken som gäller förrän du räknar. Att göra delmängder av typsnitt på ett dokument vars typsnitt redan är 2% av dess byte är en eftermiddag tillbringad med att flytta ett avrundningsfel. Nedsampling av bilder i en fil vars massa är okomprimerade innehållsströmmar producerar samma besvikelse. Optimeraren är inte den svåra delen — varje bibliotek har en. Att veta vilken optimerare som ska riktas mot den här filen är den svåra delen, och det är en bokföringsfråga, inte en komprimeringsfråga. En revision fångar också fallen där ingen optimerare är svaret: en fil som visar sig vara 60% inbäddade bilagor behöver inte bättre komprimering, den behöver ett samtal om huruvida de bilagorna hör hemma i dokumentet, och en fil som är 30% strukturträd betalar för tillgänglighetstaggning, vilket vanligtvis är en medveten kostnad du inte bör strippa tyst. När byten väl är attribuerade fattar du ett produktbeslut med siffror bakom sig i stället för att gripa efter vilken brytare som är närmast
Vad tolv-kategori-rapporten innehåller
AuditDocumentSpace returnerar ett stränglistehandtag snarare än en post, så rapporten överlever den platta DLL:en och COM-fasaderna oförändrad. Listan håller en Total,Objects,Bytes,100.0-sammanfattningsrad följd av exakt tolv Category,Objects,Bytes,Percent-rader i en fast ordning som är del av kontraktet: Images, Font programs, Font dictionaries, Content streams, Form XObjects, Object streams, Embedded files, Metadata, Structure tree, Annotations, Page tree, Other. Tretton rader, alltid, även när en kategori är tom
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;
En Delphi-detalj i den loopen kommer bita dig exakt en gång. GetStringListItem använder ett-baserade postindex, matchande GetStringListCount, och ett index utanför intervallet returnerar en tom sträng snarare än att kasta ett undantag. Skriv loopen for I := 0 to Count - 1 av vana och du får en tom första rad, en tyst bortsläppt sista rad, och inget undantag någonstans som talar om att indexeringen är fel. Rapporten själv kommer se nästan rätt ut, vilket är det värsta felläget ett diagnostikverktyg kan ha
Varför använder revisionen lagrad längd i stället för avkodad storlek?
Eftersom lagrad längd både är siffran du vill ha och siffran som är billig att skaffa fram. Varje indirekt objekt bär TPDFIndObj.FLength, den råa bytelängd objektet upptar i filen så som den tolkades. Att använda den innebär att en 900 KB DCTDecode-bild rapporteras som 900 KB — byten den kostar dig på disk — snarare än de 40 MB RGB-samplingar den avkodas till. Det innebär också att revisionen aldrig behöver avkoda något: lat-laddade objekt förblir lata, filter förblir okörda, och att revidera en 500 MB-fil är ett pass över objekthuvuden snarare än en fullständig dekomprimeringscykel
Den andra regeln är ett försvar mot dubbelräkning. När ett objekt lever inuti en komprimerad objektström, indikerat av ett icke-noll FObjStrNum, registreras dess bytantal som noll. Dess lagring har redan betalats för en gång av containerströmmen, som ISO 32000-1 §7.5.7 definierar som en /Type /ObjStm-ström som håller många objekt i en Flate-komprimerad nyttolast. Att debitera varje medlem sin egen andel och sedan debitera containern igen skulle blåsa upp totalen förbi den verkliga filstorleken. Detta har en direkt konsekvens för hur du läser utdatan, täckt nedan och mer djupgående i vår artikel om objektströmmar och korsreferensströmmar
Varför kan ett typsnittsprogram inte klassificera sig självt?
Eftersom en TrueType-typsnittsfil inbäddad i en PDF inte har någon markör som säger det. ISO 32000-1 §9.8.1 definierar det inbäddade typsnittsprogrammet som värdet av /FontFile, /FontFile2 eller /FontFile3 i en typsnittsdeskriptor, och strömordboken i andra änden av den referensen bär /Length1 och filternycklar men ingen /Type och ingen /Subtype som identifierar den som ett typsnitt. Betraktad isolerat är den en anonym binärström. Bara deskriptorn som pekar på den vet vad den är. Samma asymmetri visar sig för annoteringar: §12.5.2 gör /Type /Annot valfri i en annoteringsordbok, så den pålitliga signalen är medlemskap i en sids /Annots-array, inte ordboken själv
Så klassificeringen körs två gånger. Det första passet läser varje objekts egen /Type och /Subtype och tar de enkla vinsterna: /ObjStm, /Subtype /Image, /Subtype /Form, /Type /Font och /Type /FontDescriptor, /Metadata, /EmbeddedFile och /Filespec, /StructTreeRoot och /StructElem, /Annot, /Page och /Pages. Allt annat hamnar provisoriskt i Other. Det andra passet vandrar sedan genom den refererande sidan och skriver över: varje sidordbok tilldelar om sin /Contents till innehållsströmmar, sina /Annots-poster till annoteringar, och sin /Thumb till bilder, medan varje typsnittsordbok vandrar genom sin egen deskriptorkedja
// 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]));
Att läsa rapporten och välja nästa drag
Läs andelarna först, objektantalen andra, och behandla varje stort gap mellan dem som en signal. En modern PDF lägger merparten av sina små ordböcker inuti objektströmmar, så Page tree och Structure tree visar rutinmässigt dussintals objekt mot nästan noll byte — deras verkliga kostnad har vikts in i Object streams-raden. Om Object streams i sig är stor är filen tät med metadataliknande struktur snarare än innehåll, och spaken är att beskära objekt, inte komprimera dem. Annoteringsutseendeströmmar beter sig liknande: de bär /Subtype /Form, så ett kraftigt stämplat dokument visar sin vikt under Form XObjects medan Annotations-raden förblir liten
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;
Två formateringsfakta spelar roll om du parsar procenttalen i stället för att bara visa dem. Decimalseparatorn är alltid en bokstavlig punkt oavsett maskinens lokal, så att parsa med den omgivande FormatSettings på en tysk eller fransk arbetsstation kommer misslyckas eller, värre, misstolkas. Och avslutande nollor trimmas, så en kategori som håller exakt 40% av byten skrivs ut som 40, inte 40.0 — anta aldrig ett fast antal decimaler. Med andelen i hand är dirigeringen mekanisk: en dominerande Images-andel pekar mot DownsampleImages, en dominerande Font programs-andel mot SubsetEmbeddedFonts, och skrymmande Content streams mot CompressContent
Vad revisionen medvetet inte talar om för dig
Totalen är en summa över indirekta objekt, och en PDF-fil är lite mer än sina objekt. Filhuvudet, trailern, mellanobjektsblanksteg och en klassisk korsreferenstabell är inte indirekta objekt, så de byten attribueras till ingenting och revisionstotalen landar lite under storleken på disk. En korsreferensström är annorlunda — den är ett riktigt objekt med /Type /XRef, så i en modern fil dyker de byten faktiskt upp, i Other-kategorin. Inget av beteendena är en defekt, men om du stämmer av revisionen mot ett byteantal från filsystemet är det där gapet kommer ifrån
Ytterligare två gränser är värda att uttrycka rakt på sak. För det första beskriver siffrorna en fil som laddades, inte en som håller på att skapas: för objekt byggda i minnet som ännu inte har någon lagrad längd faller storleken tillbaka på den serialiserade utdatan med ett nominellt tillägg för strömordboken, vilket är en uppskattning av den slutgiltiga skrivningen snarare än en mätning. Revidera efter en spara-och-ladda-om om du vill ha exakta siffror. För det andra är en tjock Other-rad ett fynd, inte en felrapport — det betyder vanligtvis föräldralösa objekt som inget längre refererar till, vilket är ett jobb för mark-and-sweep-skräpsamling snarare än för något komprimeringspass
Använd på det här sättet ändrar revisionen formen på samtalet. I stället för att gissa på den 80 MB-rapporten öppnar du den, kör ett anrop, och läser att bilder är 8%, typsnittsprogram är 61%, och dokumentet bäddar in nio kompletta typsnittsprogram för en husstil som använder tre typsnitt. Det är ett åtgärdbart svar med en siffra fäst vid sig. AuditDocumentSpace, tillsammans med optimeringspassen den pekar dig mot, levereras i losLab PDF Library för Delphi och C++Builder, där referenssidorna dokumenterar den fullständiga kategorilistan och strängliste-API:et runt den