PDF Library for Delphi fjerner en identitets-tekstmatrise-operator, 1 0 0 1 0 0 Tm, i sin peephole-optimalisering av content streams ved lagring, bare når tekstmatrisen og tekstlinjematrisen allerede er identiteten: rett etter BT, eller rett etter en tidligere identitets-Tm. En identitets-cm blir fortsatt alltid droppet, for cm multipliserer CTM-en mens Tm erstatter begge tekstmatrisene fullstendig. Siden v3.539.28 blir alle andre identitets-Tm igjen i strømmen
Bug-en denne fikser, er den stille typen. En rapportgenerator sender BT (Invoice) Tj 1 0 0 1 0 0 Tm (Total) Tj ET og stoler på identitets-Tm-en for å sende den andre strengen tilbake til tekstrommets origo før den anvender sin egen posisjoneringslogikk. Den eldre optimalisatoren så seks tall som staver identitetsmatrisen, bestemte at operatoren umulig kunne endre noe, og slettet den. Ingenting feilet, ingenting logget en advarsel, og den lagrede siden tegnet «Total» rett etter «Invoice» på samme grunnlinje, som er nøyaktig den klassen av defekter ingen legger merke til før en kunde skriver ut PDF-en
Hvorfor er 1 0 0 1 0 0 Tm ikke alltid en no-op?
En identitets-Tm er en no-op bare når den ville erstatte to matriser som allerede holder identiteten, og det er en egenskap ved operatorene før den, ikke ved egne operander. ISO 32000-1 §9.4.1 sier at BT initialiserer både tekstmatrisen (Tm) og tekstlinjematrisen (Tlm) til identiteten, og §9.4.2 definerer Tm som å sette begge til de gitte verdiene, ikke å konkatenere på dem. Sammenlign med cm (§8.4.4), som høyremultipliserer den gjeldende transformasjonsmatrisen: å multiplisere med identiteten lar enhver CTM være uendret, så 1 0 0 1 0 0 cm er trygt å slette hvor som helst. Inne i et tekstobjekt er bildet annerledes. Td, TD, T* og en ikke-identitets-Tm flytter alle Tlm, og hver tekstvisende operator (Tj, TJ, ', ") flytter Tm fremover med bredden av glyfene den tegnet. Etter en av dem er en identitets-Tm en ekte tilbakestilling til origo. Hvis du noen gang har sporet tekstposisjoner for hånd med content stream CTM- og tekstmatrise-sporeren, er dette samme skillet mellom å konkatenere tilstand og erstatte den
Hvordan bakover-skanningen bestemmer hvilken identitets-Tm som droppes
TPDFContentPeepholeOptimizer.RemoveIdentityMatrices går nå baklengs fra hver identitets-Tm og dropper den bare hvis skanningen når BT eller en annen identitets-Tm først. Den tidligere identitets-Tm-en teller enten den beholdes eller selv nettopp er planlagt slettet, for uansett etterlot den begge matriser i identiteten, nøyaktig som BT gjør. Regelen sorterer hver operator den kan treffe, inn i én av to grupper:
- Stopp og behold Tm-en:
Td,TD,T*, en ikke-identitets-Tm,Tj,TJ,',",ET, enhver operatoren parseren ikke gjenkjenner, eller starten på strømmen - Gå videre og fortsett å skanne: operatorer som aldri rører Tm eller Tlm, slik som
Tf,Tc, fargesettere,gs, marked-content-operatorer ogcm
De konservative tilfellene er bevisste. En ukjent operator kan være hva som helst, så skanningen nekter å resonnere forbi den. ET lukker tekstobjektet, så en Tm etter den har ingen BT som garanterer for matriseverdiene. Skanningen jobber også med én content stream om gangen, noe som betyr noe for sider hvis /Contents er en array: et lag som starter midt i et tekstobjekt, uten egen BT, beholder sin identitets-Tm selv om forrige lag ville ha gjort den overflødig. Det koster noen byte på rare filer og flytter aldri en glyf. Hvis du redigerer pagetekst på instruksjonsnivå, som i gjennomgangen av tegn-til-content-byte-mapping, er samme parsede TPDFContentProgram-modell det optimalisatoren omskriver
uses
PDFlibContentModel, PDFlibContentOptimize;
function OptimizeSnippet(const Source: AnsiString): AnsiString;
var
Prog: TPDFContentProgram;
Optimizer: TPDFContentPeepholeOptimizer;
begin
Result := Source;
Prog := TPDFContentProgram.Create;
try
if not Prog.Parse(Source) then
Exit; // ødelagt strøm: la bytene være i fred
Optimizer := TPDFContentPeepholeOptimizer.Create(Prog);
try
Optimizer.Run; // returnerer antallet fjernede instruksjoner
finally
Optimizer.Free;
end;
Result := Prog.Emit; // én instruksjon per linje
finally
Prog.Free;
end;
end;
// Fjernet: Tm rett etter BT, den andre av to identitets-Tm på rad
// OptimizeSnippet('BT /F1 12 Tf 1 0 0 1 0 0 Tm (hello) Tj ET')
// Beholdt: Tm etter Td, etter Tj, etter en ikke-identitets-Tm eller utenfor BT
// OptimizeSnippet('BT (Invoice) Tj 1 0 0 1 0 0 Tm (Total) Tj ET')
Kjør hjelperen på fakturastrømmen fra innledningen, så overlever identitets-Tm-en, for bakover-skanningen treffer Tj før den når BT. Sett /F1 12 Tf, 2 Tc og 0 g mellom BT og identitets-Tm-en, så forsvinner den likevel, siden ingen av dem rører tekstmatrisene. En sekvens som BT 10 20 Td 1 0 0 1 0 0 Tm 1 0 0 1 0 0 Tm mister nøyaktig én operator: den første identitets-Tm-en tilbakestiller matrisen som Td flyttet, og bare den andre er overflødig
Når kjører peephole-optimalisatoren egentlig?
Optimalisatoren kjører bare under kompresjonspasset, inne i TPDFPageTree.Compress, og bare på content streams som ikke allerede er Flate-komprimert. TPDFlib.SetOptimizeContentStreams(1) er standardverdien, og samme bryter er eksponert som OptimizeContentStreams-feltet i TPDFlibSaveOptions; både CompressContent og CompressPage respekterer den. En strøm hvis /Filter allerede er /FlateDecode hoppes helt over, så å laste en eksisterende komprimert PDF og lagre den på nytt omskriver ikke operatorene. Feiler strømmen å parse, komprimeres de opprinnelige dekodede bytene uendret. TPDFlib.NormalizeContentStreams parser og sender ut content med kanonisk mellomrom og tall, men kaller aldri optimalisatoren, noe som gjør den til et nyttig utgangspunkt når du vil se hvor mye av størrelsesforskjellen peephole-reglene bidrar med, ved siden av de større gevinstene dekket i PDF-filstørrelse-optimalisering med font subsetting
var
Lib: TPDFlib;
Options: TPDFlibSaveOptions;
begin
Lib := TPDFlib.Create;
try
if Lib.LoadFromFile('report.pdf', '') <> 1 then
Exit;
// Ukomprimerte strømmer går gjennom peephole-reglene, så Flate
Lib.SetOptimizeContentStreams(1);
Lib.CompressContent;
Lib.SaveToFile('report-optimized.pdf');
// Samme valg gjennom de medfølgende lagringsvalgene; False takker nei
Options.CompressContent := True;
Options.CompressFonts := True;
Options.CompressImages := True;
Options.Linearize := False;
Options.KeepModDate := False;
Options.OptimizeContentStreams := False;
Options.GarbageCollect := False;
Options.PackObjectStreams := True;
Lib.SaveToFileOptions('report-plain.pdf', Options);
finally
Lib.Free;
end;
end;
Hva garanterte den gamle regresjonstesten egentlig?
Den gamle regresjonstesten garanterte én form alene: en identitets-Tm rett etter BT fjernes. Peephole_RemovesIdentityTextMatrix mater BT 1 0 0 1 0 0 Tm (hello) Tj ET til optimalisatoren og hevder at ingen Tm gjenstår. En tidligere utgivelse hadde allerede bemerket at å droppe en identitets-Tm er utrygt når Tlm ikke er identiteten, og beholdt likevel oppførselen fordi testen «låste» den. Lest nøye, sier testen ingenting om en identitets-Tm etter Td eller etter vist tekst; å behandle dekningen av én prøve som kontrakten til hele regelen var selve feilen. Fiksen holder det opprinnelige tilfellet grønt og legger til seks tilfeller som fester både de fjernbare formene og de beholdte, inkludert en Tm utenfor ethvert tekstobjekt og én som følger ET
Avveiningen er lett å akseptere når den er skrevet ned. Generatorer som pakker hvert tekstobjekt som BT 1 0 0 1 0 0 Tm ... får fortsatt den overflødige operatoren fjernet, og det er der nesten alle besparelsene kom fra. Det optimalisatoren gir slipp på, er den innimellom-identitets-Tm-en i et tekstobjekt, en håndfull byte per side før Flate engang ser dem, i bytte mot en garanti modulhodet sier rent ut: hver transform er output-ekvivalent og endrer aldri den synlige siden. En størrelsesoptimalisator som flytter tekst, er ikke en optimalisator, det er en rendering-bug med gode kompresjonsrater
Content stream-parseren, peephole-optimalisatoren og lagringstidens kompresjonsvalg omtalt her følger alle med i PDF Library for Delphi og C++Builder, som også eksponerer NormalizeContentStreams, CompressContent og TPDFlibSaveOptions for å styre hvordan hvert dokument skrives