PDF Library for Delphi odstrani enotski operator tekstovne matrike, 1 0 0 1 0 0 Tm, med peephole optimizacijo tokov vsebine ob shranjevanju le, kadar sta tekstovna matrika in tekstovna matrika vrstice že enotski: takoj za BT ali takoj za prejšnjim enotskim Tm. Enotski cm se še naprej vedno odvrže, ker cm množi CTM, Tm pa obe tekstovni matriki prepiše kar v celoti. Od v3.539.28 vsak drugi enotski Tm ostane v toku
Hrošč, ki ga to popravlja, je tihe vrste. Generator poročil izda BT (Invoice) Tj 1 0 0 1 0 0 Tm (Total) Tj ET in se zanaša na enotski Tm, da pošlje drugi niz nazaj na izhodišče tekstovnega prostora, preden uveljavi lastno logiko pozicioniranja. Starejši optimizator je videl šest števil, ki črkujejo enotsko matriko, sklenil, da operator ne more ničesar spremeniti, in ga izbrisal. Nič ni odpovedalo, nič ni zabeležilo opozorila, shranjena stran pa je narisala »Total« takoj za »Invoice« na isti osnovni črti — točno tista vrsta napake, ki je nihče ne opazi, dokler stranka ne natisne PDF
Zakaj 1 0 0 1 0 0 Tm ni vedno no-op?
Enotski Tm je no-op le, kadar bi zamenjal dve matriki, ki že nosita enoto, to pa je lastnost operatorjev pred njim, ne njegovih lastnih operandov. ISO 32000-1 §9.4.1 pravi, da BT inicializira tekstovno matriko (Tm) in tekstovno matriko vrstice (Tlm) na enoto, §9.4.2 pa definira Tm kot nastavitev obeh na dane vrednosti, ne zlivanja nanje. Primerjajte s cm (§8.4.4), ki trenutno transformacijsko matriko množi z desne: množenje z enoto pusti vsak CTM nespremenjen, zato je 1 0 0 1 0 0 cm varno izbrisati kjerkoli. Znotraj tekstovnega objekta je slika drugačna. Td, TD, T* in ne-enotski Tm vsi premaknejo Tlm, vsak operator prikazovanja teksta (Tj, TJ, ', ") pa poveča Tm za širino narisanih glifov. Po katerem koli od njih je enotski Tm pravi povratek na izhodišče. Če ste kdaj ročno sledili položajem teksta z sledilnikom stanja CTM in tekstovne matrike v toku vsebine, je to ista razlika med zlivanjem stanja in njegovo zamenjavo
Kako povratni pregled odloča, kateri enotski Tm odvrže
TPDFContentPeepholeOptimizer.RemoveIdentityMatrices zdaj hodi od vsakega enotskega Tm nazaj in ga odvrže le, če pregled najprej doseže BT ali drug enotski Tm. Prejšnji enotski Tm šteje ne glede na to, ali je ohranjen ali je bil sam ravnokar določen za brisanje, ker je v obeh primerih pustil obe matriki na enoti, točno tako kot BT. Pravilo vsakega operatorja, ki ga lahko sreča, razvrsti v eno od dveh skupin:
- Ustavitev in ohranitev Tm:
Td,TD,T*, ne-enotskiTm,Tj,TJ,',",ET, vsak operator, ki ga razčlenjevalnik ne prepozna, ali začetek toka - Preskok in nadaljevanje pregleda: operatorji, ki se Tm ali Tlm nikoli ne dotaknejo, na primer
Tf,Tc, nastavitve barv,gs, operatorji označene vsebine incm
Konzervativni primeri so namerni. Neznan operator je lahko karkoli, zato pregled odkloni sklepanje čez njega. ET zapre tekstovni objekt, zato Tm za njim nima BT, ki bi jamčil za vrednosti matrik. Pregled dela tudi na enem toku vsebine naenkrat, kar je pomembno za strani, katerih /Contents je polje: plast, ki se začne na sredini tekstovnega objekta, brez lastnega BT, obdrži svoj enotski Tm, tudi če bi jo prejšnja plast naredila odvečno. To stane nekaj bajtov na čudnih datotekah in nikoli ne premakne glifa. Če urejate tekst strani na ravni ukazov, kot v vodniku o preslikavi znakov v bajte vsebine, je isti razčlenjeni model TPDFContentProgram tisto, kar optimizator prepiše
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; // pokvarjen tok: bajti ostanejo pri miru
Optimizer := TPDFContentPeepholeOptimizer.Create(Prog);
try
Optimizer.Run; // vrne število odstranjenih ukazov
finally
Optimizer.Free;
end;
Result := Prog.Emit; // en ukaz na vrstico
finally
Prog.Free;
end;
end;
// Odstranjeno: Tm takoj za BT, drugi od dveh enotskih Tm zapored
// OptimizeSnippet('BT /F1 12 Tf 1 0 0 1 0 0 Tm (hello) Tj ET')
// Ohranjeno: Tm za Td, za Tj, za ne-enotskim Tm ali zunaj BT
// OptimizeSnippet('BT (Invoice) Tj 1 0 0 1 0 0 Tm (Total) Tj ET')
Pomožnik poženite na toku računa iz uvoda in enotski Tm preživi, ker povratni pregled naleti na Tj, preden doseže BT. Postavite /F1 12 Tf, 2 Tc in 0 g med BT in enotski Tm ter tudi tak gre, ker se noben od njih ne dotakne tekstovnih matrik. Zaporedje, kot je BT 10 20 Td 1 0 0 1 0 0 Tm 1 0 0 1 0 0 Tm, izgubi točno enega operatorja: prvi enotski Tm ponastavi matriko, ki jo je premaknil Td, odvečen pa je samo drugi
Kdaj peephole optimizator sploh teče?
Optimizator teče le med prehodom stiskanja, znotraj TPDFPageTree.Compress, in le na tokovih vsebine, ki še niso Flate stisnjeni. TPDFlib.SetOptimizeContentStreams(1) je privzeto, isto stikalo pa je izpostavljeno kot polje OptimizeContentStreams v TPDFlibSaveOptions; upoštevata ga tako CompressContent kot CompressPage. Tok, katerega /Filter je že /FlateDecode, se v celoti preskoči, zato nalaganje obstoječega stisnjenega PDF in ponovno shranjevanje ne prepisuje njegovih operatorjev. Če toka ni mogoče razčleniti, se izvirni dekodirani bajti stisnejo nespremenjeni. TPDFlib.NormalizeContentStreams razčleni in znova izda vsebino s kanoničnim razmakom in števili, optimizatorja pa nikoli ne pokliče, zato je uporabna izhodiščna črta, kadar želite videti, koliko velikostne razlike prispevajo peephole pravila, poleg večjih dobičkov, pokritih v optimizaciji velikosti datotek PDF s podnabori pisav
var
Lib: TPDFlib;
Options: TPDFlibSaveOptions;
begin
Lib := TPDFlib.Create;
try
if Lib.LoadFromFile('report.pdf', '') <> 1 then
Exit;
// Nestisnjeni tokovi gredo skozi peephole pravila, nato Flate
Lib.SetOptimizeContentStreams(1);
Lib.CompressContent;
Lib.SaveToFile('report-optimized.pdf');
// Isti izbor skozi priložene možnosti shranjevanja; False se odloči proti
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;
Kaj je stari regresijski preizkus res zagotavljal?
Stari regresijski preizkus je zagotavljal samo eno obliko: enotski Tm takoj za BT se odstrani. Peephole_RemovesIdentityTextMatrix poda BT 1 0 0 1 0 0 Tm (hello) Tj ET optimizatorju in trdi, da noben Tm ne ostane. Zgodnejša izdaja je že opazila, da je odvreči enotski Tm nevarno, kadar Tlm ni enota, obnašanje pa je vseeno ohranila, ker ga je preizkus »zaklenil«. Če ga preberete pozorno, preizkus o enotskem Tm za Td ali za prikazanim tekstom ne pravi nič; obravnavati pokritost enega vzorca kot pogodbo celega pravila je bila prava napaka. Popravek ohrani izvirni primer zelen in doda šest primerov, ki pribijejo odstranljive in ohranjene oblike, vključno s Tm zunaj vsakega tekstovnega objekta in enim, ki sledi ET
Kompromis je lahko sprejeti, ko je enkrat zapisan. Generatorji, ki vsak tekstovni objekt ovijejo kot BT 1 0 0 1 0 0 Tm ..., še vedno dobijo odstranjenega tega odvečnega operatorja, od koder je prišlo skoraj vse privarčevanega. Kar optimizator odstopi, je občasni enotski Tm na sredini tekstovnega objekta, peščica bajtov na stran, preden jih Flate sploh vidi, v zameno za zagotovilo, ki ga glava modula jasno izraža: vsaka transformacija je izhodno enakovredna in nikoli ne spremeni vidne strani. Optimizator velikosti, ki premika tekst, ni optimizator, je izrisovalna napaka z dobrimi razmerji stiskanja
Razčlenjevalnik tokov vsebine, peephole optimizator in možnosti stiskanja ob shranjevanju, opisani tukaj, so vsi del PDF Library for Delphi and C++Builder, ki izpostavlja tudi NormalizeContentStreams, CompressContent in TPDFlibSaveOptions za nastavitev pisanja vsakega dokumenta