HotPDF Delphi Componentin sivurenderoija etenee nyt tekstissä laskemalla jokaisen glyyfin siirtymän tekstitilassa, tx = ((w0 − Tj/1000) × Tfs + Tc + Tw) × Th, niin kuin ISO 32000-1 §9.4.4 määrittelee, ja liikuttamalla sitten tekstimatriisia sen lineaarisen osan kautta HPDFTranslateTextMatrixilla. Clippaus tallennetaan per q-kehys ja palautetaan Qssa, mutta GDI-region kaapataan vain, kun kyseinen kehys oikeasti muuttaa clippiä. Molemmat korjaukset saapuivat HotPDF 2.754.0:aan, ja molemmat tulivat oikeista sivuista, jotka renderöityivät lomittain kasaantunein sanoin tai vuotavilla clip-regioneilla Qnsa ohi. Ensimmäinen bugi on aritmetiikkaa, joka näyttää oikealta siihen asti, kunnes tuottaja kirjoittaa fonttikokonsa matriisiin. Toinen on korrektiuskorjaus, joka melkein maksoi meille rinnakkaisen renderöinnin nopeutuksen, ja tapa, jolla saimme nopeuden takaisin, on tiedon arvoinen, jos kirjoitat mitään GDI-pohjaista PDF-laitetta
Miksi teksti lomaantuu rykelmäksi, kun PDF käyttää arvoa Tf 1?
Koska vanha siirtymäkoodi lisäsi tekstitilan etäisyyden suoraan Tmin translaatiokomponenttiin, ikään kuin tekstitilalla ja käyttäjätilalla olisi aina sama skaala. Paljon oikeita tuottajia asettaa fonttikooksi 1 Tfllä ja kantaa oikeaa kokoa tekstimatriisissa. Arvoilla /F1 1 Tf ja 12 0 0 12 72 700 Tm 500 yksikön levyinen glyyfi siirtyy 0,5 tekstitilan yksikköä, mikä on 6 pistettä sivulla, kun Tm skaalaa sen. Vanha renderoija suoritti lausekkeen Tm.e := Tm.e + Adv ja siirsi kynää 0,5 pistettä. Jokainen glyyfi laskeutui yhden kahdennentoistaosan merkin verran edellisen jälkeen, joten runkotekstin rivi renderöityi tummana tahmana vasemmassa reunassa, kun taas sama tiedosto näytti täydelliseltä kaikissa muissa katselimissa
// Sisältöstream tuottajalta, joka koodaa koon Tm:ään, ei Tf:ään:
// BT
// /F1 1 Tf
// 12 0 0 12 72 700 Tm
// [(Hel) 30 (lo) -250 (world)] TJ
// ET
// Vanha siirtymä (yksinkertaistettu): etäisyys lisätty Tm.een ikään kuin se olisi käyttäjätilaa
Adv := W * FontSize / 1000; // 0,5 500-yksikköiselle glyyfille
if (HorizScale <> 0) and (HorizScale <> 100) then
Adv := Adv * HorizScale / 100; // Th vain leveydelle
Adv := Adv + CharSpace; // Tc:tä ei skaalata Th:lla
if Code = 32 then
Adv := Adv + WordSpace * FontSize / 1000; // Tw väärin skaalattuna Tfs:llä
Tm.e := Tm.e + Adv; // ohittaa Tm.a:n, Tm.b:n, Tm.c:n ja Tm.d:n
// Vanha TJ-säätö: ei Th:ta, ja taas vain Tm.e
Tm.e := Tm.e - NumValue * FontSize / 1000;
Tm.e-oikotie ei ollut ainoa vika kyseisessä lohkossa. Sanaväli Tw ilmaistaan skaalaamattomissa tekstitilan yksiköissä, mutta vanha koodi kertoi sen arvolla FontSize / 1000, joten arvolla Tf 12 tasattu rivi menetti lähes koko sanavälinsä. Vaakaskaalaus Th kohdistui glyyfin leveyteen mutta ei Tchen tai Twhen, ja TJ-kerningssäätö ohitti sen kokonaan. Ei-maalaava polku, joka siirtää renderöintitilan 3 näkymätöntä tekstiä — sellaista, jota OCR-tekstikerrokset käyttävät — sekä piilotetun valinnaisen sisällön sisällä oleva teksti kantoi saman aritmetiikan yksityistä kopiota, joten mikä tahansa näkymättömän ajon jälkeen piirretty alkoi väärästä sijainnista. Tekstitilan bugit renderoijassa kaatuvat harvoin äänekkäästi: kuten operandi-indeksin ja resurssinimen bugit, jotka kerran nollasivat Tcn, Twn ja Tzn ilman ainuttakaan virhettä, nämä tuottivat uskottavia sivuja kirjaston omassa tuotoksessa ja rikoivat vain muiden tuottajien tiedostoissa
Miten ISO 32000-1 §9.4.4 määrittelee glyyfin siirtymän?
ISO 32000-1 §9.4.4 määrittelee siirtymän täysin tekstitilassa ja soveltaa sitä tekstimatriisiin translaatiomatriisina, joten vastaus on laskea tx ensin ja antaa Tmin hoitaa skaalaus, kierto ja vinouma. Vaakakirjoituksella tx on ((w0 − Tj/1000) × Tfs + Tc + Tw) × Th, jossa w0 on glyyfin leveys tuhannesosina em:stä, Tj on TJ-säätö, ja Th on Tz jaettuna 100:lla. Uusi Tm on [1 0 0 1 tx 0] × Tm, mikä HotPDF:ssä on apurifunktio HPDFTranslateTextMatrix: se lisää X:n ja Y:n matriisikertoimien a:n, b:n, c:n ja d:n kautta kirjoittamatta suoraan ehen ja fhen. Kohdan §9.3.3 mukaan Tw koskee vain yksitavuista merkintäkoodia 32, joten monitavuiset CID-koodit eivät koskaan poimi sanaväliä vaakapolulla. Sama apurifunktio ajaa nyt Tdn, TDn, T*n, '- ja "-operaattorit, TJ-säädöt ja piilotetun tekstipolun, mikä tarkoittaa, että yksi funktio omistaa säännön
procedure HPDFTranslateTextMatrix(var Matrix: THPDFAffineMatrix; X, Y: Double);
begin
Matrix.e := Matrix.e + Matrix.a * X + Matrix.c * Y;
Matrix.f := Matrix.f + Matrix.b * X + Matrix.d * Y;
end;
// Vaakaglyyfin siirtymä, ISO 32000-1 9.4.4
W := HPDFFontCharWidth(F, Code);
Adv := W * State.Text.FontSize / 1000 + State.Text.CharSpace;
if (Code = 32) and not F.CID2Byte then
Adv := Adv + State.Text.WordSpace; // Tw tekstitilassa, skaalaamaton
Adv := Adv * State.Text.HorizScale / 100; // Th koskee koko summaa
HPDFTranslateTextMatrix(Tm, Adv, 0);
// TJ-numeroelementti: sama tila, sama Th
Adjustment := -Items[I].NumValue * State.Text.FontSize / 1000;
HPDFTranslateTextMatrix(State.Text.Tm, Adjustment * State.Text.HorizScale / 100, 0);
Glyyfien sijoittelun piti seurata samaa logiikkaa. Kun upotettua ääriviivaa ei ole saatavilla ja renderoija putoaa GDI:n TextOutWille, se rakentaa nyt täyden glyyfimatriisin kaavasta CTM × Tm × rise × em-skaala, mukaanlukien Th, ja asentaa sen SetWorldTransformilla GM_ADVANCED-tilassa SaveDC / RestoreDC-parin sisällä. GDI-fontti luodaan kiinteällä 1000 yksikön korkeudella ja muunnos tekee koon, joten kiertynyt ja vääntynyt teksti säilyttää suuntansa sen sijaan että piirrettäisiin pystyssä muunnetusta alkuperäpisteestä. Pystykirjoitustila on ainoa tahallinen epäsymmetria: WMode 1 -fontti siirtyy y-akselia alaspäin pystymetriikallaan, ja vaakaskaalaus ei koske kyseistä akselia
Mitä q/Q oikeastaan tallentaa PDF:n grafiikkatilaan?
ISO 32000-1 §8.4.2 luettelee nykyisen clipauspolun osaksi grafiikkatilaa, joten Qin on palautettava clip täsmälleen sellaisena kuin se oli vastaavassa qssa, ei vain numeerisia parametreja. HotPDF piti jo grafiikkatilapinoa, jossa on CTM, värit, viivaparametrit ja tekstitila, mutta GDI pitää clipin laitekontekstissa, tuon pinon ulkopuolella. Kopio numeerisesta tilasta palautti siksi kaiken muun paitsi clipin, ja W nilla asennettu clip q ... Q-lohkon sisällä jatkoi jokaisen myöhemmän operaation sivulla rajaamista. Form XObjectit lisäsivät toisen reitin samaan vikaan, koska §8.10 antaa formille implisiittisen tallennuksen ja palautuksen sisällön ympärille, ja oikean maailman formisisältö joskus jättää omat q-operaattorinsa epätasapainoisiksi, vaikka spesifikaatio vaatii niiden parituvan. Renderoija kutsuu nyt CaptureClipBeforeChangeia ja SaveDCia ennen formin ajamista, ja formin valmistuttua se hylkää kaikki tallennetut regionit, jotka ovat syvemmällä kuin sisääntulosyvyys, ja kutsuu RestoreDCia, joten jokaisella tallennetulla HRGN:llä on täsmälleen yksi vapautuspolku
Laiska clipin kaappaus THPDFSavedClipStateilla
Toimitettu korjaus tallentaa yhden THPDFSavedClipState-tietueen per q, mutta lykkää kallista osaa siihen asti, kun kehys ensimmäisen kerran muuttaa clippiä. Tietue pitää sisällään regionin kahvan, pinon syvyyden, johon se kuuluu, laitekontekstin, josta se otettiin, ja Captured-lipun. DevPushState täyttää vain syvyyden ja DC:n ja kasvattaa kehyksen taulukkoa kaksinkertaistaen 16:sta alkaen, joten q 1 0 0 1 x y cm ... Q-kutsuilla täynnä oleva sisältöstream ei varaa yhtään GDI-objektia. Operaattorit, jotka ovat muuttamassa clippausta — eli polun maalaus odottavalla Wllä tai W*llä, n-operaattori, kuviotäytöt ja formiin saapuminen — kutsuvat ensin CaptureClipBeforeChangeia
procedure THPDFPageRenderer.CaptureClipBeforeChange;
var
Index, ClipResult: Integer;
Region: HRGN;
begin
ClearSavedClipRegions(FGSStack.Count);
Index := FSavedClipCount - 1;
if (Index < 0) or FSavedClips[Index].Captured or
(FSavedClips[Index].StackDepth <> FGSStack.Count) or
(FSavedClips[Index].DC <> FDC) then Exit; // jo tallennettu, tai ei meidän
Region := CreateRectRgn(0, 0, 0, 0);
if Region = 0 then RaiseLastOSError;
ClipResult := GetClipRgn(FDC, Region); // 0 tarkoittaa ei clippiä lainkaan
if ClipResult <= 0 then
begin
DeleteObject(Region);
Region := 0;
if ClipResult < 0 then RaiseLastOSError;
end;
FSavedClips[Index].Region := Region;
FSavedClips[Index].Captured := True;
end;
procedure THPDFPageRenderer.DevPopState;
var
Index: Integer;
begin
ClearSavedClipRegions(FGSStack.Count);
Index := FSavedClipCount - 1;
if (Index >= 0) and (FSavedClips[Index].StackDepth = FGSStack.Count) then
begin
if FSavedClips[Index].Captured and (FSavedClips[Index].DC = FDC) then
SelectClipRgn(FDC, FSavedClips[Index].Region); // Region 0 poistaa clipin
if FSavedClips[Index].Region <> 0 then
DeleteObject(FSavedClips[Index].Region);
Dec(FSavedClipCount);
end;
FGSStack.Pop;
end;
Innokkaan version mitattu kustannus on syy, miksi tämä suunnittelu on olemassa. Ensimmäinen oikea toteutus loi ja luki GDI-regionin jokaisella qlla, ja sivuilla, jotka olivat pääosin numeerisia muunnoksia, renderöintisäikeet käyttivät aikansa GDI-regionobjekteista kilpailemiseen rasteroinnin sijaan. Rinnakkainen renderöintiputki putosi odotetusta hyödystään noin 1,13–1,20-kertaiseksi yksisäikeiseen läpäisyyn verrattuna ja kaatui 1,5-kertaisen nopeutusportin kohdalla benchmark-sarjassa. Laiskalla kaappauksella ja uudelleenkäytetyllä kehyskapasiteetilla sama benchmark menee alkuperäisen 1,5-kertaisen portin läpi taas. Pienten TrueType-glyyfien antialiasointi saapui samassa julkaisussa ja oli ilmeinen epäilty, mutta regressio jäljitettiin regionin varaukseen, mikä on hyvä muistutus mitata ennen kuin syytät uusinta ominaisuutta
Missä tämän lähestymistavan rajat ovat?
Tallennettu clip on GDI-region laitepikseleinä, joten se on täsmällinen renderöitävälle bittikartalle ja merkityksetön mille tahansa muulle kohteelle. Siksi jokainen kehys kirjaa laitekontekstinsa, ja DevPopState ohittaa palautuksen, kun DC on vaihtunut, esimerkiksi silloin kun läpinäkyvyysryhmä renderöityy omaan kerrosbittikarttaansa. GetClipRgnin palauttama nolla on laillinen tulos, joka tarkoittaa ei clippiä lainkaan, ja sen palauttaminen kutsulla SelectClipRgn(FDC, 0) on se, mikä oikein poistaa clipin, jota ei ollut olemassa vastaavassa qssa. Tekstin puolella korjaus oikaisee, minne jokainen glyyfi menee, mutta se ei keksi leveyksiä: jos fontti jättää /Widths-taulukon pois ja upotettu ohjelma ei ole saatavilla, siirtymä on yhä vain niin hyvä kuin leveysvarajärjestely. Kun regressiotestaat tätä aluetta, pidä mukana vähintään yksi fixture arvolla Tf 1 ja skaalatulla Tmillä, yksi nollasta poikkeavalla Tzllä ja Twllä ja yksi, jossa on clip q ... Q-lohkon sisällä ja sen jälkeistä sisältöä ulkopuolella, koska mikään näistä ei näy kirjaston itsensä tuottamissa asiakirjoissa
Jos ajat renderoijaa sovelluskoodista, kutsukaavassa, jonka artikkeli PDF-sivun renderöinnistä bittikartaksi kuvaa, ei muutu mitään, ja sivut, jotka aiemmin näyttivät tahrisia rivejä tai rajattua sisältöä, renderöityvät nyt yksinkertaisesti oikein versioissa 2.754.0 ja myöhemmissä. Tiedot komponentista, tuetuista Delphi- ja C++Builder-versioista ja lisensoinnista ovat HotPDF Delphi PDF Component -tuotesivulla