Upodobitelj strani HotPDF Delphi Component zdaj premika besedilo tako, da izračuna vsak pomik glife v besedilnem prostoru, tx = ((w0 − Tj/1000) × Tfs + Tc + Tw) × Th, kot ga definira ISO 32000-1 §9.4.4, in nato premakne besedilno matriko skozi njen linearni del s HPDFTranslateTextMatrix. Odrezavanje se shrani na okvir q in obnovi na Q, regija GDI pa se zajame le, kadar ta okvir dejansko spremeni odrez. Oba popravka sta pristala v HotPDF 2.754.0 in oba sta prišla iz pravih strani, ki so se upodobile s skrčenimi besedami ali z odreznimi regijami, ki so puščale čez njihov Q. Prva napaka je aritmetika, ki izgleda pravilna, dokler producent ne zapiše svoje velikosti pisave v matriko. Druga je popravek pravilnosti, ki nas je skoraj stal vzporedni pospešek upodabljanja, način, kako smo hitrost dobili nazaj, pa je vreden vedenja, če pišete katero koli napravo PDF na osnovi GDI
Zakaj se besedilo skrči v kepo, kadar PDF uporabi Tf 1?
Ker je stara koda premika dodala besedilnoprostorsko razdaljo naravnost prevodni komponenti Tm, kot da bi besedilni prostor in uporabniški prostor vselej imela isto merilo. Precej resničnih proizvajalcev nastavi velikost pisave na 1 s Tf in pravo velikost nosi v besedilni matriki. Z /F1 1 Tf in 12 0 0 12 72 700 Tm glifa, široka 500 enot, premakne 0.5 v besedilnem prostoru, kar je 6 točk na strani, ko jo Tm umeri. Stari upodobitelj je izvedel Tm.e := Tm.e + Adv in premaknil perilo 0.5 točke. Vsaka glifa je pristala ena dvanajstina znaka za prejšnjo, tako da se je vrstica telesnega besedila upodobila kot temen premaz pri levem robu, medtem ko je ista datoteka izgledala popolno v vsakem drugem pregledovalniku
// Tok vsebine od proizvajalca, ki kodira velikost v Tm, ne Tf:
// BT
// /F1 1 Tf
// 12 0 0 12 72 700 Tm
// [(Hel) 30 (lo) -250 (world)] TJ
// ET
// Stari premik (poenostavljeno): razdalja dodana Tm.e, kot da bi bila uporabniški prostor
Adv := W * FontSize / 1000; // 0.5 za glifo 500 enot
if (HorizScale <> 0) and (HorizScale <> 100) then
Adv := Adv * HorizScale / 100; // Th le na širini
Adv := Adv + CharSpace; // Tc ne umerjen z Th
if Code = 32 then
Adv := Adv + WordSpace * FontSize / 1000; // Tw napačno umerjen z Tfs
Tm.e := Tm.e + Adv; // prezre Tm.a, Tm.b, Tm.c, Tm.d
// Stara prilagoditev TJ: brez Th in spet le Tm.e
Tm.e := Tm.e - NumValue * FontSize / 1000;
Bližnjica Tm.e ni bila edina napaka v tem bloku. Besedni razmik Tw je izražen v neumerjenih enotah besedilnega prostora, stara koda pa ga je pomnožila z FontSize / 1000, zato je pod Tf 12 opravičena vrstica izgubila skoraj ves svoj razmik med besedami. Horizontalno umerjanje Th je veljalo za širino glife, ne pa za Tc ali Tw, prilagoditev kerninga TJ pa ga je povsem preskočila. Ne barvalna pot, ki premika nevidno besedilo načina upodabljanja 3, take vrste, kot jo uporabljajo plasti besedila OCR, in besedilo znotraj skrite izbirne vsebine je nosilo zasebno kopijo iste aritmetike, zato je karkoli narisano po nevidnem teku začelo z napačnim položajem. Napake stanja besedila v upodobitelju redko odpovejo glasno: kot napaki kazala operandov in imena virov, ki sta nekdaj poničili Tc, Tw in Tz brez ene same napake, so tudi te izdelale verjetne strani na lastnem izhodu knjižnice in se pokvarile le na datotekah od drugih proizvajalcev
Kako ISO 32000-1 §9.4.4 definira premik glife?
ISO 32000-1 §9.4.4 definira premik povsem v besedilnem prostoru in ga nanese na besedilno matriko kot translacijsko matriko, zato je odgovor, da najprej izračunate tx in pustite Tm, da stori umerjanje, vrtenje in poševnost. Za horizontalno pisanje je tx enak ((w0 − Tj/1000) × Tfs + Tc + Tw) × Th, kjer je w0 širina glife v tisočinkah em, Tj je prilagoditev TJ, Th pa Tz deljen z 100. Nov Tm je [1 0 0 1 tx 0] × Tm, kar je v HotPDF pomožnik HPDFTranslateTextMatrix: X in Y dodaja skozi koeficiente matrike a, b, c in d, namesto da bi pisala v e in f neposredno. Po §9.3.3 Tw velja le za enobajtno kodo znaka 32, zato večbajtne kode CID nikoli ne poberejo besednega razmika na horizontalni poti. Isti pomožnik zdaj poganja Td, TD, T*, operatorja ' in ", prilagoditve TJ in skrito besedilno pot, kar pomeni, da ena sama funkcija lasti pravilo
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;
// Horizontalni premik glife, 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 v besedilnem prostoru, neumerjen
Adv := Adv * State.Text.HorizScale / 100; // Th velja za celotno vsoto
HPDFTranslateTextMatrix(Tm, Adv, 0);
// TJ številski element: isti prostor, isti Th
Adjustment := -Items[I].NumValue * State.Text.FontSize / 1000;
HPDFTranslateTextMatrix(State.Text.Tm, Adjustment * State.Text.HorizScale / 100, 0);
Postavitev glif je morala slediti isti logiki. Ko vgrajen oris ni na voljo in upodobitelj pade nazaj na GDI TextOutW, zdaj zgradi celotno glifno matriko iz CTM × Tm × dvig × em merila, vključno s Th, in jo namesti s SetWorldTransform v načinu GM_ADVANCED znotraj para SaveDC / RestoreDC. Pisava GDI se ustvari na fiksni višini 1000 enot, ureditev pa stori dimenzioniranje, tako da vrnjeno in poševno besedilo obdrži svojo usmerjenost, namesto da bi bilo narisano pokončno na preoblikovani točki izhodišča. Vertikalni način pisanja je edina namerna asimetrija: pisava WMode 1 premika po osi y navzdol po svoji vertikalni metriki, horizontalno umerjanje pa ne velja za to os
Kaj q/Q dejansko shrani v grafičnem stanju PDF?
ISO 32000-1 §8.4.2 našteje trenutno pot odrezavanja kot del grafičnega stanja, zato mora Q obnoviti odrez točno takšnega, kot je bil pri ujemajočem q, in ne le številskih parametrov. HotPDF je že vodil sklad grafičnih stanj s CTM, barvami, parametri črt in stanjem besedila, GDI pa drži odrez v kontekstu naprave, izven tega sklada. Kopija števskega stanja je zato obnovila vse razen odreza, odrez, nameščen z W n znotraj bloka q ... Q, pa je obrezoval vsako poznejšo operacijo na strani. Form XObjects so dodali drugo pot do iste odpovedi, ker §8.10 daje formi implicitno shranjevanje in obnovo okoli njene vsebine, resnična vsebina form pa včasih pusti svoje operatorje q neuravnotežene, čeprav specifikacija zahteva, da se parijo. Upodobitelj zdaj pokliče CaptureClipBeforeChange in SaveDC, preden požene formo, po zaključku forme pa zavrže vse shranjene regije, globlje od vstopne globine, in pokliče RestoreDC, tako da ima vsak shranjeni HRGN točno eno pot sprostitve
Lenobno zajemanje odreza s THPDFSavedClipState
Popravek, ki je odšel ven, shrani en zapis THPDFSavedClipState na q, a dragi del odloži, dokler okvir prvič ne spremeni odreza. Zapis nosi ročaj regije, globino sklada, ki ji pripada, kontekst naprave, iz katerega je bil vzet, in zastavico Captured. DevPushState zapolni le globino in DC ter poveča polje okvirjev z podvajanjem od 16, tako da tok vsebine, poln q 1 0 0 1 x y cm ... Q, ne alocira nobenega objekta GDI. Operatorji, ki bodo spremenili odrezavanje, kar pomeni barvanje poti z obešenim W ali W*, operator n, polnila vzorcev in vstop v formo, najprej pokličejo CaptureClipBeforeChange
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; // že shranjeno ali ni naše
Region := CreateRectRgn(0, 0, 0, 0);
if Region = 0 then RaiseLastOSError;
ClipResult := GetClipRgn(FDC, Region); // 0 pomeni sploh brez odreza
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); // Regija 0 odstrani odrez
if FSavedClips[Index].Region <> 0 then
DeleteObject(FSavedClips[Index].Region);
Dec(FSavedClipCount);
end;
FGSStack.Pop;
end;
Izmerjeni strošek vese različice je razlog, da ta zasnova obstaja. Prva pravilna implementacija je ustvarila in prebrala regijo GDI na vsakem q, na straneh, narejenih večinoma iz številskih transformacij, pa so niti upodabljanja porabile svoj čas za tekmovanje za objekte regij GDI namesto za rastreiranje. Vzporedni cevovod upodabljanja je padel s pričakovanega prirastka na približno 1.13 do 1.20 krat enonitna pretočnost in odpovedal zaporo pospeška 1.5 krat v zbirki merilnih preizkusov. Z lenobnim zajemanjem in ponovno uporabljeno zmogljivostjo okvirjev isti merilni preizkus spet prestane izvirno zaporo 1.5 krat. Majhno glajenje robov glif TrueType je pristalo v isti izdaji in bilo očiten sumnik, regresija pa se je izsledila nazaj do alokacije regij, kar je dober opomnik, da merite, preden okrivite najnovejšo zmožnost
Kje so meje tega pristopa?
Shranjeni odrez je regija GDI v napravinih pikslih, zato je točen za bitno sliko, ki se upodablja, in brez pomena za kateri koli drug cilj. Zato vsak okvir zabeleži svoj kontekst naprave in DevPopState preskoči obnovo, ko se je DC spremenil, na primer medtem ko skupina prosojnosti upodablja v svojo bitno sliko plasti. Vrnitev nič od GetClipRgn je zakonit rezultat, ki pomeni brez odreza, obnoviti pa ga s SelectClipRgn(FDC, 0) je točno to, kar pravilno odstrani odrez, ki pri ujemajočem q ni obstajal. Na strani besedila popravek popravi, kamor gre vsaka glifa, si pa ne izmišljuje širin: če pisava izpusti svoje polje /Widths in vgrajen program ni na voljo, je premik še vedno samo tako dober, kot je rezerva širin. Ko to področje regresijsko preizkušate, obdržite vsaj eno vtičnico s Tf 1 in umerjeno Tm, eno z ne-ničelnima Tz in Tw ter eno z odrezom znotraj q ... Q, ki mu sledi vsebina zunaj njega, ker se nobeno od teh ne pokaže v dokumentih, ustvarjenih s strani same knjižnice
Če upodobitelj poganjate iz kode aplikacije, se v klicnem vzorcu, opisanem v upodabljanju strani PDF v bitno sliko, nič ne spremeni, strani, ki so prej kazale premazane vrstice ali obrezano vsebino, pa se bi naj preprosto upodobile pravilno na 2.754.0 in novejših. Podrobnosti o komponenti, podprtih različicah Delphi in C++Builder ter licenciranju so na strani izdelka HotPDF Delphi PDF Component