Tehnični članak

Upodobitelj PDF: premik besedila in obnovitev odreza q/Q

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

Zakaj se besedilo skrči pod Tf 1 v upodobitelju HotPDF: z 12 0 0 12 72 700 Tm mora glifa 500 enot premakniti 0.5 besedilnoprostorskih enot, kar Tm umeri v 6 točk, stara koda pa je dodala 0.5 naravnost Tm.e in upodobila vrstico telesnega besedila kot premaz ene dvanajstine znaka na glifo
Proizvajalci, ki kodirajo velikost pisave v besedilni matriki, so naredili, da je vsaka glifa pristala ena dvanajstina znaka za prejšnjo, napaka, nevidna na lastnem izhodu knjižnice
// 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

Premik glife ISO 32000-1 9.4.4 v upodobitelju HotPDF: tx izračunan v besedilnem prostoru iz w0, Tj, Tfs, Tc, Tw in Th, nato nanesen skozi HPDFTranslateTextMatrix, tako da pomik gre čez koeficiente matrike a, b, c in d ter Td, TD, TJ in skrita besedilna pot si delijo eno pravilo
Dodajanje premika neposredno Tm.e deluje le, ko je besedilni prostor enak uporabniškemu; usmerjanje skozi koeficiente matrike obdrži umerjeno, vrnjeno in poševno besedilo pravilno
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

Lenobno zajemanje odreza GDI v upodobitelju HotPDF: DevPushState zabeleži le globino in DC na q, CaptureClipBeforeChange prebere regijo tik pred tem, ko W, n ali vstop v formo spremeni odrezovanje, DevPopState jo obnovi in izbriše na Q, vese različica, ki je zajela na vsakem q, pa je spustila vzporedno pretočnost na približno 1.15 krat enonitno
Ustvarjanje regije GDI na vsakem q je stradalo niti upodabljanja, zato se zajemanje zdaj zgodi le, ko bo operator spremenil odrezovanje, in zapora pospeška 1.5 krat spet uspe
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