PDF Library for Delphi poistaa identity-tekstimatriisioperaattorin 1 0 0 1 0 0 Tm tallennusaikaisen peephole-sisältöstreamioptimoinnin yhteydessä vain silloin, kun tekstimatriisi ja tekstirivimatriisi ovat jo identityä: heti BT:n jälkeen tai heti aiemman identity-Tm:n jälkeen. Identity-cm pudotetaan yhä aina, koska cm kertoo CTM:n, kun taas Tm korvaa molemmat tekstimatriisit suoraan. Versiosta v3.539.28 alkaen jokainen muu identity-Tm jää streamiin
Tämä korjaus koskee hiljaista vikaa. Raporttigeneraattori emittoi rivin BT (Invoice) Tj 1 0 0 1 0 0 Tm (Total) Tj ET ja nojaa identity-Tmihin siinä, että se lähettää toisen merkkijonon takaisin tekstitilan alkupisteeseen ennen kuin soveltaa omaa sijoituslogiikkaansa. Vanhempi optimoija näki kuusi lukua, jotka muodostavat identiteettimatriisin, päätti, ettei operaattori voi mitenkään muuttaa mitään, ja poisti sen. Mikään ei kaatunut, mikään ei kirjannut varoitusta, ja tallennettu sivu piirsi sanan "Total" heti sanan "Invoice" perään samalle peruslinjalle, mikä on täsmälleen sitä vikaluokkaa, jota kukaan ei huomaa, ennen kuin asiakas tulostaa PDF:n
Miksi 1 0 0 1 0 0 Tm ei aina ole no-op?
Identity-Tm on no-op vain silloin, kun se korvaisi kaksi matriisia, jotka jo pitävät identityä, ja tuo on ominaisuus sitä edeltävistä operaattoreista, ei sen omista operandeista. ISO 32000-1 §9.4.1 sanoo, että BT alustaa sekä tekstimatriisin (Tm) että tekstirivimatriisin (Tlm) identityksi, ja §9.4.2 määrittelee Tmin asettavaksi molemmat annettuihin arvoihin, ei ketjutettavaksi niiden päälle. Vertaa cm:een (§8.4.4), joka kertoo oikealta nykyisen muunnosmatriisin: kertominen identityllä jättää minkä tahansa CTM:n ennalleen, joten 1 0 0 1 0 0 cm on turvallista poistaa mistä tahansa. Tekstiobjektin sisällä tilanne on toinen. Td, TD, T* ja ei-identity-Tm siirtävät kaikkia Tlm:ää, ja jokainen tekstiä näyttävä operaattori (Tj, TJ, ', ") etenee Tm:ää piirtämien glyyfiensä leveydellä. Minkä tahansa niistä jälkeen identity-Tm on aito palautus alkupisteeseen. Jos olet joskus jäljittänyt tekstipaikkoja käsin artikkelin sisältöstreamin CTM- ja tekstimatriisin tilaseurannan avulla, tässä on sama ero tilan ketjuttamisen ja korvaamisen välillä
Miten taaksepäin kulkeva skannaus päättää, mitkä identity-Tm pudotetaan
TPDFContentPeepholeOptimizer.RemoveIdentityMatrices kävelee nyt taaksepäin jokaisesta identity-Tmstä ja pudottaa sen vain, jos skannaus saavuttaa ensin BT:n tai toisen identity-Tmn. Aiempi identity-Tm kelpaa riippumatta siitä, säilytetäänkö se vai onko se itse juuri merkitty poistettavaksi, koska kummassakin tapauksessa se jätti molemmat matriisit identityksi, täsmälleen kuten BT tekee. Sääntö lajittelee jokaisen kohtaamansa operaattorin kahteen ryhmään:
- Pysähdy ja säilytä Tm:
Td,TD,T*, ei-identity-Tm,Tj,TJ,',",ET, mikä tahansa operaattori, jota jäsennin ei tunnista, tai streamin alku - Astu yli ja jatka skannausta: operaattorit, jotka eivät koskaan kosketa Tm:tä tai Tlm:ää, kuten
Tf,Tc, värin asettajat,gs, marked-content-operaattorit jacm
Konservatiiviset tapaukset ovat tahallisia. Tuntematon operaattori voi olla mitä tahansa, joten skannaus kieltäytyy päättelemästä sen ohi. ET sulkee tekstiobjektin, joten Tm sen jälkeen ei saa BTltä takuuta matriisiarvoille. Skannaus toimii lisäksi yhden sisältöstreamin kerrallaan, mikä merkitsee sivuja, joiden /Contents on taulukko: kerros, joka alkaa tekstiobjektin keskeltä ilman omaa BTään, pitää identity-Tmnsä, vaikka edellinen kerros olisi tehnyt sen tarpeettomaksi. Se maksaa muutaman tavun oudoissa tiedostoissa eikä koskaan siirrä glyyfiä. Jos muokkaat sivun tekstiä käskytasolla, kuten artikkelin merkkien ja sisältötavujen mappauksen läpikäynti tekee, sama jäsennetty TPDFContentProgram-malli on se, jonka optimoija kirjoittaa uudelleen
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; // vioittunut stream: jätä tavut rauhaan
Optimizer := TPDFContentPeepholeOptimizer.Create(Prog);
try
Optimizer.Run; // palauttaa poistettujen käskyjen määrän
finally
Optimizer.Free;
end;
Result := Prog.Emit; // yksi käsky per rivi
finally
Prog.Free;
end;
end;
// Poistettu: Tm suoraan BT:n jälkeen, kahden peräkkäisen identity Tm:n jälkimmäinen
// OptimizeSnippet('BT /F1 12 Tf 1 0 0 1 0 0 Tm (hello) Tj ET')
// Säilytetty: Tm Td:n jälkeen, Tj:n jälkeen, ei-identity-Tm:n jälkeen tai BT:n ulkopuolella
// OptimizeSnippet('BT (Invoice) Tj 1 0 0 1 0 0 Tm (Total) Tj ET')
Aja apufunktio aloituksen laskutusskriptille ja identity-Tm selviää hengissä, koska taaksepäin kulkeva skannaus osuu Tj:hin ennen kuin se saavuttaa BT:n. Laita /F1 12 Tf, 2 Tc ja 0 g BT:n ja identity-Tmn väliin, ja se katoaa yhä, koska yksikään niistä ei kosketa tekstimatriiseja. Sekvenssi kuten BT 10 20 Td 1 0 0 1 0 0 Tm 1 0 0 1 0 0 Tm menettää täsmälleen yhden operaattorin: ensimmäinen identity-Tm nollaa matriisin, jonka Td siirsi, ja vain jälkimmäinen on tarpeeton
Milloin peephole-optimoija oikeasti ajaa?
Optimoija ajaa vain pakkausvaiheen sisällä, TPDFPageTree.Compressin sisällä, ja vain sisältöstreameissa, jotka eivät ole jo Flate-pakattuja. TPDFlib.SetOptimizeContentStreams(1) on oletus, ja sama kytkin on näkyvillä myös TPDFlibSaveOptions-rakenteen OptimizeContentStreams-kenttänä; sekä CompressContent että CompressPage noudattavat sitä. Stream, jonka /Filter on jo /FlateDecode, ohitetaan kokonaan, joten olemassa olevan pakatun PDF:n lataaminen ja uudelleentallennus ei kirjoita sen käskyjä uudelleen. Jos streamin jäsentäminen epäonnistuu, alkuperäiset dekoodatut tavut pakataan muuttumattomina. TPDFlib.NormalizeContentStreams jäsentää ja emittoi sisällön kanonisella välilyönnityksellä ja luvuilla, mutta ei koskaan kutsu optimoijaa, mikä tekee siitä hyödyllisen vertailukohdan, kun haluat nähdä, kuinka suuren kokoeron peephole-säännöt tuovat, rinnalla suuremmat säästöt, jotka on käsitelty kirjoituksessa PDF-tiedostokoon optimointi fonttisubsettingilla
var
Lib: TPDFlib;
Options: TPDFlibSaveOptions;
begin
Lib := TPDFlib.Create;
try
if Lib.LoadFromFile('report.pdf', '') <> 1 then
Exit;
// Pakkaamattomat streamit kulkevat peephole-sääntöjen läpi, sitten Flate
Lib.SetOptimizeContentStreams(1);
Lib.CompressContent;
Lib.SaveToFile('report-optimized.pdf');
// Sama valinta mukana toimitettavien tallennusasetusten kautta; False kieltäytyy
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ä vanha regressiotesti oikeasti takasi?
Vanha regressiotesti takasi täsmälleen yhden muodon: identity-Tm suoraan BT:n jälkeen poistetaan. Peephole_RemovesIdentityTextMatrix syöttää optimoijalle rivin BT 1 0 0 1 0 0 Tm (hello) Tj ET ja assertoi, ettei yhtään Tmä jää jäljelle. Aiempi julkaisu oli jo todennut, että identity-Tmn pudottaminen on turvatonta, kun Tlm ei ole identity, ja piti silti käyttäytymisen ennallaan, koska testi "lukitsi" sen. Huolella uudelleen luettuna testi ei sanonut mitään identity-Tmstä Td:n jälkeen tai näytetyn tekstin jälkeen; yhden otannan kattavuuden käsittely koko säännön sopimuksena oli varsinainen virhe. Korjaus pitää alkuperäisen tapauksen läpäisyn ja lisää kuusi tapausta, jotka lukitsevat sekä poistettavat muodot että säilytettävät, mukaanlukien Tm tekstiobjektin ulkopuolella ja yksi, joka seuraa ETä
Kompromissi on helppo hyväksyä, kun se on kirjoitettu ylös. Generaattorit, jotka käärittävät jokaisen tekstiobjektin muotoon BT 1 0 0 1 0 0 Tm ..., saavat yhä tuon tarpeettoman operaattorin poistetuksi, ja juuri siinä tuli lähes kaikki säästöt. Se, mistä optimoija luopuu, on satunnainen identity-Tm tekstiobjektin keskellä, kourallinen tavuja sivua kohti ennen kuin Flate edes näkee ne, vastineeksi takuusta, jonka moduulin otsikko sanoo suoraan: jokainen muunnos on tulosteekvivalentti eikä koskaan muuta näkyvää sivua. Koon optimoija, joka siirtää tekstiä, ei ole optimoija, vaan renderöintivika hyvillä pakkaussuhteilla
Sisältöstreamin jäsennin, peephole-optimoija ja tässä kuvatut tallennusaikaiset pakkausasetukset toimitetaan mukana tuotteessa PDF Library for Delphi and C++Builder, joka näyttää myös NormalizeContentStreams, CompressContent ja TPDFlibSaveOptions kunkin asiakirjan kirjoitustavan säätöön