A PDF Library for Delphi a mentéskori peephole tartalomstream-optimalizálása során akkor távolítja el az identity text matrix operátort, az 1 0 0 1 0 0 Tm-et, ha a text matrix és a text line matrix már identity: közvetlenül BT után, vagy egy korábbi identity Tm után. Az identity cm-et továbbra is mindig eldobja, mert a cm a CTM-et szorozza, a Tm pedig mindkét text mátrixot egy az egyben lecseréli. A v3.539.28 óta minden más identity Tm a streamben marad
Az a hiba, amit ez javít, a csendes fajta. Egy riportgenerátor kiadja a BT (Invoice) Tj 1 0 0 1 0 0 Tm (Total) Tj ET sort, és az identity Tm-re támaszkodik, hogy a második stringet a szövegtér origójába küldje vissza, mielőtt a saját pozicionálási logikája lépne életbe. A régebbi optimizer hat számot látott, amik az identity mátrixot írják le, eldöntötte, hogy az operátor biztosan nem változtat semmin, és törölte. Semmi nem bukott el, semmi nem írt figyelmeztetést, és a mentett oldal a Total-t közvetlenül az Invoice után rajzolta ugyanazon az alapvonalon, ami pontosan az a hibaosztály, amit senki nem vesz észre, amíg egy ügyfél ki nem nyomtatja a PDF-et
Miért nem mindig no-op az 1 0 0 1 0 0 Tm?
Egy identity Tm csak akkor no-op, ha két, már identity-t tartó mátrixot cserélne le, és ez az őt megelőző operátorok tulajdonsága, nem a saját operandusaié. Az ISO 32000-1 §9.4.1-e szerint a BT mind a text matrixot (Tm), mind a text line matrixot (Tlm) identity-re inicializálja, a §9.4.2 pedig a Tm-et úgy definiálja, hogy mindkettőt a megadott értékekre állítja, nem rájuk fűzi őket. Hasonlítsd össze a cm-mel (§8.4.4), ami jobbról szorozza az aktuális transzformációs mátrixot: identity-vel szorozni bármilyen CTM-et változatlanul hagy, így az 1 0 0 1 0 0 cm bármhol biztonságosan törölhető. Egy text objektumon belül más a kép. A Td, a TD, a T* és egy nem identity Tm mind mozgatja a Tlm-et, és minden szöveget megjelenítő operátor (Tj, TJ, ', ") a kirajzolt glyphok szélességével lépteti a Tm-et. Ezek bármelyike után egy identity Tm valódi visszállítás az origóra. Ha valaha kézzel követtél nyomon szövegpozíciókat a tartalomstream CTM és text matrix állapotkövetőjével, ez ugyanaz a különbség az állapot fűzése és cseréje közt
Hogyan dönti el a visszafelé scannelés, melyik identity Tm-et dobjuk el
A TPDFContentPeepholeOptimizer.RemoveIdentityMatrices mostantól minden identity Tm-től visszafelé jár, és csak akkor dobja el, ha a scan előbb BT-t vagy egy másik identity Tm-et ér el. A korábbi identity Tm akkor is számít, ha megtartották, vagy ha épp törlésre jelölték, mert mindkét esetben mindkét mátrixot identity-en hagyta, pontosan úgy, mint a BT. A szabály minden találkozható operátort a két csoport egyikébe sorol:
- Állj meg és tartsd meg a Tm-et:
Td,TD,T*, egy nem identityTm,Tj,TJ,',",ET, bármilyen operátor, amit a parser nem ismer fel, vagy a stream eleje - Lépj át és folytasd a scannelést: operátorok, amik sosem nyúlnak a Tm-hez vagy a Tlm-hez, mint a
Tf, aTc, a színbeállítók, ags, a marked-content operátorok és acm
A konzervatív esetek szándékosak. Egy ismeretlen operátor bármi lehet, így a scan nem hajlandó rajta túl gondolkodni. Az ET lezárja a text objektumot, így utána egy Tm-nek nincs BT-je, ami igazolná a mátrixértékeket. A scan egy content streamen dolgozik egyszerre, ami azoknál az oldalaknál számít, amiknek a /Contents-e tömb: egy olyan réteg, ami egy text objektum közepén indul, saját BT nélkül, megtartja az identity Tm-ét akkor is, ha az előző réteg redundánssá tette volna. Ez néhány bájtot költségez furcsa fájlokon, és sosem mozdít glyphot. Ha utasításszinten szerkeszted az oldalszöveget, mint a karakter-content bájt leképezési bemutatóban, ugyanaz a TPDFContentProgram modell az, amit az optimizer átír
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; // sérült stream: a bájtokat békén hagyjuk
Optimizer := TPDFContentPeepholeOptimizer.Create(Prog);
try
Optimizer.Run; // a törölt utasítások számát adja vissza
finally
Optimizer.Free;
end;
Result := Prog.Emit; // soronként egy utasítás
finally
Prog.Free;
end;
end;
// Törölve: Tm közvetlenül BT után, két egymás utáni identity Tm közül a második
// OptimizeSnippet('BT /F1 12 Tf 1 0 0 1 0 0 Tm (hello) Tj ET')
// Megtartva: Tm Td után, Tj után, nem identity Tm után, vagy BT-n kívül
// OptimizeSnippet('BT (Invoice) Tj 1 0 0 1 0 0 Tm (Total) Tj ET')
Futtasd a helpert a nyitó invoice streamen, és az identity Tm túléli, mert a visszafelé scannelés BT előtt Tj-be ütközik. Tegyél /F1 12 Tf-et, 2 Tc-t és 0 g-t a BT és az identity Tm közé, és az még mindig megy, mert egyik sem nyúl a text mátrixokhoz. Egy BT 10 20 Td 1 0 0 1 0 0 Tm 1 0 0 1 0 0 Tm sorozat pontosan egy operátort veszít: az első identity Tm visszaállítja azt a mátrixot, amit a Td elmozdított, és csak a második a redundáns
Mikor fut valójában a peephole optimizer?
Az optimizer csak a tömörítési fázisban fut, a TPDFPageTree.Compress-en belül, és csak olyan tartalomstreameken, amik még nem Flate-tömörítettek. A TPDFlib.SetOptimizeContentStreams(1) az alapértelmezett, és ugyanez a kapcsoló a TPDFlibSaveOptions OptimizeContentStreams mezőjeként is elérhető; a CompressContent és a CompressPage is figyelembe veszi. Az a stream, aminek a /Filter-e már /FlateDecode, teljesen kimarad, így egy meglévő tömörített PDF betöltése és újramentése nem írja újra az operátorait. Ha a stream parseolása megbukik, az eredeti dekódolt bájtok változatlanul tömörülnek. A TPDFlib.NormalizeContentStreams kanonikus szóközökkel és számokkal parseol és bocsát ki tartalmat, de az optimizert sosem hívja meg, ami hasznos bázisvonal, ha látni akarod, mekkora méretkülönbséget hoznak a peephole szabályok, a font szubsettinges PDF fájlméret-optimalizálás nagyobb nyereségei mellett
var
Lib: TPDFlib;
Options: TPDFlibSaveOptions;
begin
Lib := TPDFlib.Create;
try
if Lib.LoadFromFile('report.pdf', '') <> 1 then
Exit;
// A tömörítetlen streamek a peephole szabályokon mennek át, majd Flate-en
Lib.SetOptimizeContentStreams(1);
Lib.CompressContent;
Lib.SaveToFile('report-optimized.pdf');
// Ugyanez a választás a mellékelt mentési opciókon át; a False kizárja magát
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;
Mit garantált valójában a régi regressziós teszt?
A régi regressziós teszt egyetlen alakot garantált: egy identity Tm közvetlenül BT után törlődik. A Peephole_RemovesIdentityTextMatrix a BT 1 0 0 1 0 0 Tm (hello) Tj ET sort adja az optimizernak, és azt állítja, hogy Tm nem maradt. Egy korábbi kiadás már megjegyezte, hogy egy identity Tm eldobása nem biztonságos, ha a Tlm nem identity, majd ettől függetlenül megtartotta a viselkedést, mert a teszt „bezárta" azt. Alaposabban elolvasva a teszt semmit sem mond egy identity Tm-ről Td után vagy megjelenített szöveg után; egyetlen minta lefedettségét a teljes szabály szerződéseként kezelni volt a tényleges hiba. A javítás megtartja azt az eredeti esetet, és hat esetet ad hozzá, amik a törölhető és a megtartott alakokat is rögzítik, köztük egy Tm-et bármilyen text objektumon kívül, és egyet, ami ET után következik
A kompromisszum leírva könnyen elfogadható. Azok a generátorok, amik minden text objektumot BT 1 0 0 1 0 0 Tm ... alakban csomagolnak, továbbra is megkapják a redundáns operátor törlését, és a megtakarítás majdnem egésze onnan jött. Amiről az optimizer lemond, az a text objektum közepén bújkáló alkalmi identity Tm, oldalanként egy maroknyi bájt, még mielőtt a Flate látná őket, cserébe egy garantálásért, amit a modul fejléce egyenesen kimond: minden transzformáció kimenet-ekvivalens, és sosem változtatja meg a látható oldalt. A szöveget mozdító méretoptimalizáló nem optimizer, hanem jó tömörítési aránnyal bíró renderelési hiba
Az itt tárgyalt tartalomstream-parser, peephole optimizer és mentéskori tömörítési opciók a PDF Library for Delphi and C++Builder részét képezik, ami a NormalizeContentStreams-t, a CompressContent-et és a TPDFlibSaveOptions-t is feltárja, hogy minden dokumentumot ízlésed szerint írhass ki