Renderer stranica HotPDF Delphi Component sada pomera tekst računajući svako pomeranje glifa u text prostoru, tx = ((w0 − Tj/1000) × Tfs + Tc + Tw) × Th kako ga definiše ISO 32000-1 §9.4.4, pa onda pomera text matricu kroz njen linearni deo sa HPDFTranslateTextMatrix. Isecanje se čuva po q frejmu i vraća na Q, ali se GDI region hvata samo kada taj frejm zaista menja clip. Obe popravke su stigle u HotPDF 2.754.0, i obe su došle sa stranica iz stvarnog sveta koje su se prikazivale sa zgužvanim rečima ili sa clip regionima koji cure iza svog Q. Prvi bug je aritmetika koja izgleda ispravno dok proizvođač ne upiše veličinu fonta u matricu. Drugi je popravka ispravnosti koja nas je skoro koštala paralelnog ubrzanja renderinga, i način na koji smo vratili brzinu vredi znati ako pišete bilo koji GDI-podržan PDF uređaj
Zašto se tekst zgužva u grudvu kad PDF koristi Tf 1?
Jer stari advance kod dodavao je text-prostor udaljenost pravo na translacionu komponentu Tm-a, kao da text prostor i user prostor uvek imaju istu razmeru. Mnogo proizvođača iz stvarnog sveta postavi veličinu fonta na 1 sa Tf i nosi pravu veličinu u text matrici. Sa /F1 1 Tf i 12 0 0 12 72 700 Tm, glif širok 500 jedinica pomera se 0,5 u text prostoru, što je 6 tačaka na stranici čim ga Tm skalira. Stari renderer je izvršio Tm.e := Tm.e + Adv i pomerio pero 0,5 tačaka. Svaki glif je sleteo jednu dvanaestinu znaka posle prethodnog, pa je linija teksta prikazana kao tamna mrlja kod leve margine dok je isti fajl u svakom drugom pregledaču izgledao savršeno
// Content stream od proizvođača koji upisuje veličinu u Tm, a ne Tf:
// BT
// /F1 1 Tf
// 12 0 0 12 72 700 Tm
// [(Hel) 30 (lo) -250 (world)] TJ
// ET
// Stari advance (pojednostavljeno): udaljenost dodata na Tm.e kao da je user prostor
Adv := W * FontSize / 1000; // 0,5 za glif od 500 jedinica
if (HorizScale <> 0) and (HorizScale <> 100) then
Adv := Adv * HorizScale / 100; // Th samo na širinu
Adv := Adv + CharSpace; // Tc se ne skalira sa Th
if Code = 32 then
Adv := Adv + WordSpace * FontSize / 1000; // Tw pogrešno skalirano sa Tfs
Tm.e := Tm.e + Adv; // ignoriše Tm.a, Tm.b, Tm.c, Tm.d
// Stara TJ korekcija: bez Th, i opet samo Tm.e
Tm.e := Tm.e - NumValue * FontSize / 1000;
Tm.e prečica nije bila jedini defekt u tom bloku. Word spacing Tw izražava se u neskaliranim text-prostor jedinicama, a stari kod ga je množio sa FontSize / 1000, pa je pod Tf 12 poravnata linija izgubila skoro sav svoj međurečni razmak. Horizontalno skaliranje Th odnosilo se na širinu glifa ali ne na Tc ili Tw, a TJ kerning korekcija ga je preskakala sasvim. Ne-bojeća putanja koja pomera nevidljivi tekst render mode 3, vrstu koju koriste OCR text slojevi, i tekst unutar skrivenog optional content-a nosila je privatnu kopiju iste aritmetike, pa je sve što se nacrta posle nevidljivog niza startovalo sa pogrešne pozicije. Bugovi text stanja u rendereru retko padaju glasno: poput bugova operand indeksa i imena resursa koji su jednom bez ijedne greške postavili Tc, Tw i Tz na nulu, ovi su proizvodili uverljive stranice na sopstvenom izlazu biblioteke i padali samo na fajlovima drugih proizvođača
Kako ISO 32000-1 §9.4.4 definiše advance glifa?
ISO 32000-1 §9.4.4 definiše advance potpuno u text prostoru i primenjuje ga na text matricu kao translacionu matricu, pa je odgovor da se prvo izračuna tx i pusti Tm da radi skaliranje, rotaciju i kosinu. Za horizontalno pisanje, tx je ((w0 − Tj/1000) × Tfs + Tc + Tw) × Th, gde je w0 širina glifa u hiljaditim delovima em, Tj je TJ korekcija, a Th je Tz podeljen sa 100. Novi Tm je [1 0 0 1 tx 0] × Tm, što je u HotPDF-u helper HPDFTranslateTextMatrix: dodaje X i Y kroz koeficijente matrice a, b, c i d umesto da piše u e i f direktno. Po §9.3.3, Tw važi samo za jednobajtni znakovni kod 32, pa višebajtni CID kodovi nikada ne hvataju word spacing na horizontalnoj putanji. Isti helper sada pokreće Td, TD, T*, operatore ' i ", TJ korekcije i skriveni text put, što znači da jedna funkcija poseduje 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 advance glifa, 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 u text prostoru, neskalirano
Adv := Adv * State.Text.HorizScale / 100; // Th važi za celu sumu
HPDFTranslateTextMatrix(Tm, Adv, 0);
// TJ brojevni element: isti prostor, isti Th
Adjustment := -Items[I].NumValue * State.Text.FontSize / 1000;
HPDFTranslateTextMatrix(State.Text.Tm, Adjustment * State.Text.HorizScale / 100, 0);
Postavljanje glifova je moralo pratiti istu logiku. Kada ugrađena kontura nije dostupna i renderer se vraća na GDI TextOutW, sada gradi punu matricu glifa iz CTM × Tm × rise × em razmere, uključujući Th, i instalira je sa SetWorldTransform u GM_ADVANCED režimu unutar SaveDC / RestoreDC para. GDI font se pravi na fiksnoj visini od 1000 jedinica a transformacija radi dimenzionisanje, pa rotirani i iskošeni tekst zadržava orijentaciju umesto da se crta uspravno na transformisanoj tački porekla. Vertikalni režim pisanja je jedina namerna asimetrija: font sa WMode 1 pomera se niz y osu svojom vertikalnom metrikom, i horizontalno skaliranje se ne odnosi na tu osu
Šta q/Q zapravo čuva u PDF graphics stanju?
ISO 32000-1 §8.4.2 navodi tekuću isečnu putanju kao deo graphics stanja, pa Q mora vratiti clip tačno onakav kakav je bio na odgovarajućem q-u, a ne samo numeričke parametre. HotPDF je već držao graphics stek stanja sa CTM, bojama, parametrima linija i text stanjem, ali GDI drži clip u device kontekstu, van tog steka. Kopija numeričkog stanja je zato vratila sve osim clip-a, i clip instaliran sa W n unutar q ... Q bloka nastavljao je da iseče svaku kasniju operaciju na stranici. Form XObject-i su dodali drugu rutu do istog kvara, jer §8.10 daje formi implicitno čuvanje i vraćanje oko svog sadržaja, i forma sadržaj iz stvarnog sveta ponekad ostavi svoje q operatore neuparene iako specifikacija traži da se upare. Renderer sada zove CaptureClipBeforeChange i SaveDC pre pokretanja forme, pa pošto forma završi odbacuje sve sačuvane regione dublje od dubine ulaska i zove RestoreDC, tako da svaki sačuvani HRGN ima tačno jednu putanju oslobađanja
Lenjo hvatanje clip-a sa THPDFSavedClipState
Popravka koja je isporučena čuva jedan THPDFSavedClipState zapis po q-u, ali odlaže skupi deo dok frejm prvi put ne izmeni clip. Zapis drži handle regiona, dubinu steka kojoj pripada, device kontekst sa koga je uzet i flag Captured. DevPushState samo upisuje dubinu i DC i uvećava niz frejmova udvajanjem od 16, pa content stream pun q 1 0 0 1 x y cm ... Q ne alocira nikakav GDI objekat. Operatori koji su baš da promene iseicanje, naime bojenje putanje sa poslatim W ili W*, operator n, pattern popune i ulaz u formu, prvo zovu 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; // već sačuvano, ili nije naše
Region := CreateRectRgn(0, 0, 0, 0);
if Region = 0 then RaiseLastOSError;
ClipResult := GetClipRgn(FDC, Region); // 0 znači nikakav clip
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 uklanja clip
if FSavedClips[Index].Region <> 0 then
DeleteObject(FSavedClips[Index].Region);
Dec(FSavedClipCount);
end;
FGSStack.Pop;
end;
Izmereni trošak eager verzije je razlog što ovaj dizajn postoji. Prva ispravna implementacija pravila je i čitala GDI region na svakom q, i na stranicama napravljenim uglavnom od numeričkih transformacija render niti su trošile vreme takmičeći se za GDI region objekte umesto da rasterizuju. Paralelni render pipeline pao je sa očekivanog dobitka na otprilike 1,13 do 1,20 puta jednonitna propusnost i pao je na 1,5 puta kapiji ubrzanja u benchmark suite-u. Sa lenjim hvatanjem i ponovo korišćenim kapacitetom frejmova, isti benchmark ponovo prolazi prvobitnu 1,5 puta kapiju. Sitno TrueType antialiasiranje glifova stiglo je u isto izdanje i bilo je očigledan osumnjičeni, ali se regresija pratila nazad do alociranja regiona, što je dobra podsetnica da se meri pre nego što se okrivi najnovija mogućnost
Gde su granice ovog pristupa?
Sačuvani clip je GDI region u pikselima uređaja, pa je tačan za bitmapu koja se prikazuje i besmislen za bilo koji drugi cilj. Zato svaki frejm beleži svoj device kontekst i DevPopState preskače vraćanje kad se DC promenio, na primer dok transparency grupa prikazuje u svoju layer bitmapu. GetClipRgn koji vrati nulu je legitimna ishod koji znači nikakav clip, i vraćanje sa SelectClipRgn(FDC, 0) je ono što ispravno skida clip koji nije postojao na odgovarajućem q-u. Na strani teksta, popravka ispravlja gde svaki glif odlazi, ali ne izmišlja širine: ako font izostavi svoj /Widths niz a ugrađeni program nije dostupan, advance je i dalje samo onoliko dobar koliko width rezerva. Kad testirate ovu oblast regresijom, držite bar jedan fixture sa Tf 1 i skaliranim Tm-om, jedan sa ne-nultim Tz i Tw, i jedan sa clip-om unutar q ... Q pa sadržajem van njega, jer se nijedno od toga ne pojavljuje u dokumentima koje generiše sama biblioteka
Ako renderer pokrećete iz aplikacijskog koda, ništa se ne menja u obrascu pozivanja opisanom u renderovanju PDF stranice u bitmapu, i stranice koje su ranije pokazivale razmazane linije ili isečen sadržaj treba jednostavno da se prikažu ispravno na 2.754.0 i kasnijim verzijama. Detalji o komponenti, podržanim Delphi i C++Builder verzijama i licenciranju su na stranici proizvoda HotPDF Delphi PDF Component