A HotPDF Delphi Component oldalrenderelője mostantól úgy lépteti a szöveget, hogy minden glyph elmozdulását szövegtérben számolja — tx = ((w0 − Tj/1000) × Tfs + Tc + Tw) × Th, ahogy az ISO 32000-1 §9.4.4-e definiálja —, majd a szövegmátrixot a lineáris részén át mozgatja a HPDFTranslateTextMatrix-szal. A clipelés q keretenként mentődik, és Q-nál áll helyre, de GDI régió csak akkor kerül rögzítésre, amikor az adott keret ténylegesen megváltoztatja a clipet. Mindkét javítás a HotPDF 2.754.0-ban landolt, és mindkettő valós világbeli oldalakból jött, amik összecsukott szavakkal vagy a Q-n túlszivárgó clip régiókkal rendereltek. Az első hiba olyan aritmetika, ami addig jónak néz ki, amíg egy producer a fontméretét a mátrixba nem írja. A második helyességjavítás, ami majdnem megzavarta a párhuzamos renderelési gyorsulást, és az, ahogy visszük a tempót, megéri megismerni, ha írsz bármilyen GDI-alapú PDF eszközt
Miért csomósodik össze a szöveg, amikor egy PDF Tf 1-et használ?
Mert a régi advance kód egy szövegtéri távolságot adott hozzá egyenesen a Tm transzlációs komponenséhez, mintha a szövegtér és a felhasználói tér mindig azonos skálán állna volna. Rengeteg valós világbeli producer 1-re állítja a fontméretet Tf-fel, és a valódi méretet a szövegmátrixban hordozza. /F1 1 Tf-fel és 12 0 0 12 72 700 Tm-mel egy 500 egység széles glyph 0,5-öt lép szövegtérben, ami 6 pont az oldalon, amint a Tm skálázza. A régi renderelő végrehajtotta a Tm.e := Tm.e + Adv-t, és 0,5 pontot mozdított a tollat. Minden glyph az előző után egy karakter tizenketted részével tolódott, így egy szövegtörzs-sor sötét foltként renderelt a bal margón, miközben ugyanaz a fájl tökéletesen nézett ki minden más megjelenítőben
// Tartalomstream egy olyan producertől, ami a méretet a Tm-be kódolja, nem a Tf-be:
// BT
// /F1 1 Tf
// 12 0 0 12 72 700 Tm
// [(Hel) 30 (lo) -250 (world)] TJ
// ET
// Régi advance (egyszerűsítve): a távolság a Tm.e-hez adódik, mintha az felhasználói tér lenne
Adv := W * FontSize / 1000; // 0,5 egy 500 egységes glyphra
if (HorizScale <> 0) and (HorizScale <> 100) then
Adv := Adv * HorizScale / 100; // Th csak a szélességre
Adv := Adv + CharSpace; // Tc-t nem skálázza a Th
if Code = 32 then
Adv := Adv + WordSpace * FontSize / 1000; // Tw tévesen a Tfs-szel skálázva
Tm.e := Tm.e + Adv; // figyelmen kívül hagyja a Tm.a, Tm.b, Tm.c, Tm.d-et
// Régi TJ korrekció: Th nélkül, és megint csak a Tm.e
Tm.e := Tm.e - NumValue * FontSize / 1000;
A Tm.e rövidút nem volt az egyetlen hiba abban a blokkban. A szóköz Tw-je skálázatlan szövegtéri egységekben van kifejezve, a régi kód mégis megszorozta a FontSize / 1000-nel, így Tf 12 alatt egy sorkizárt sor szinte az egész szavak közti hézagát elvesztette. A horizontális skálázás, a Th, a glyph szélességére vonatkozott, de nem a Tc-re vagy a Tw-re, és a TJ kerning korrekció teljesen kihagyta. A nem festő útvonal, ami a 3-as renderelési módú láthatatlan szöveget lépteti — az a fajta, amit az OCR szövegrétegek használnak —, és a rejtett optional contentben lévő szöveg ugyanazon aritmetika privát másolatát hordozta, így minden, amit egy láthatatlan futam után rajzoltak, rossz pozícióból indult. A szövegállapot hibák egy renderelőben ritkán buknak meg hangosan: akárcsak az operandumindex- és erőforrásnév-hibák, amik egykor hibadobás nélkül nullázták a Tc-t, a Tw-t és a Tz-t, ezek hihető oldalakat gyártottak a library saját kimenetén, és csak más producerek fájljain törtek meg
Hogyan definiálja az ISO 32000-1 §9.4.4-e a glyph advance-et?
Az ISO 32000-1 §9.4.4-e az advance-et teljes egészében szövegtérben definiálja, és transzlációs mátrixként alkalmazza a szövegmátrixra, tehát a válasz az, hogy előbb kiszámolod a tx-et, és hagyod, hogy a Tm végezze a skálázást, a forgatást és a nyújtást. Horizontális írásnál a tx egyenlő a ((w0 − Tj/1000) × Tfs + Tc + Tw) × Th-val, ahol a w0 a glyph szélessége egy em ezredeiben, a Tj a TJ korrekció, a Th pedig a Tz osztva 100-zal. Az új Tm [1 0 0 1 tx 0] × Tm, ami a HotPDF-ben a HPDFTranslateTextMatrix helper: X-et és Y-t a mátrix a, b, c és d együtthatóin át adja hozzá ahelyett, hogy közvetlenül az e-be és az f-be írna. A §9.3.3 szerint a Tw csak az egybájtos 32-es karakterkódra vonatkozik, így a többbájtos CID kódok soha nem kapnak szóközt a horizontális útvonalon. Ugyanez a helper mostantól hajtja a Td-t, a TD-t, a T*-t, a ' és " operátorokat, a TJ korrekciókat és a rejtett szövegútvonalat, ami azt jelenti, hogy egyetlen függvény birtokolja a szabályt
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;
// Horizontális glyph advance, 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 szövegtérben, skálázatlan
Adv := Adv * State.Text.HorizScale / 100; // a Th a teljes összegre vonatkozik
HPDFTranslateTextMatrix(Tm, Adv, 0);
// TJ szám elem: ugyanaz a tér, ugyanaz a Th
Adjustment := -Items[I].NumValue * State.Text.FontSize / 1000;
HPDFTranslateTextMatrix(State.Text.Tm, Adjustment * State.Text.HorizScale / 100, 0);
A glyph elhelyezésnek ugyanennek a logikának kellett követnie. Amikor nincs elérhető beágyazott körvonal, és a renderelő a GDI TextOutW-ra esik vissza, mostantól a teljes glyph mátrixot építi a CTM × Tm × rise × em skálából, Th-t is beleértve, és egy SaveDC / RestoreDC páron belül, GM_ADVANCED módban telepíti a SetWorldTransform-mel. A GDI font fix 1000 egységes magassággal készül, és a transzformáció végzi a méretezést, így a forgatott és nyújtott szöveg megtartja a tájolását ahelyett, hogy egy transzformált origóponton egyenesen állva rajzolódna. A vertikális írásmód az egyetlen szándékos aszimmetria: egy WMode 1 font az y tengelyen lefelé lép a vertikális metrikája szerint, és a horizontális skálázás nem vonatkozik arra a tengelyre
Mit is ment valójában a q/Q egy PDF grafikai állapotában?
Az ISO 32000-1 §8.4.2-e az aktuális vágóutat a grafikai állapot részeként listázza, így a Q-nak pontosan úgy kell helyreállítania a clipet, ahogy az volt a párosított q-nál, nem csak a numerikus paramétereket. A HotPDF már tartott grafikai állapotvermet a CTM-mel, a színekkel, a sorparaméterekkel és a szövegállapottal, de a GDI a clipet a device contextben tartja, azon a vermen kívül. A numerikus állapot másolata ezért mindent helyreállított a clip kivételével, és egy W n-nel telepített clip egy q ... Q blokkon belül továbbra is levágta az oldal minden későbbi műveletét. A Form XObjectek ugyanannak a hibának egy második útvonalát adták hozzá, mert a §8.10 implicit mentést és visszatöltést ad a formnak a tartalma köré, és a valós világbeli form tartalom néha kiegyensúlyozatlanul hagyja a saját q operátorait, hiába követeli meg a specifikáció, hogy párosodjanak. A renderelő mostantól meghívja a CaptureClipBeforeChange-t és a SaveDC-t, mielőtt futtatna egy formot, majd a forma befejezte után eldobja a belépési mélységnél mélyebben mentett régiókat, és meghívja a RestoreDC-t, így minden mentett HRGN-nek pontosan egy felszabadítási útvonala van
Lusta clip rögzítés a THPDFSavedClipState-tel
A kiszállított javítás q-nként egy THPDFSavedClipState rekordot ment, de a drága részt addig halasztja, amíg a keret először meg nem változtatja a clipet. A rekord tartja a régió handlét, a veremmélységet, amihez tartozik, a device contextet, amiből vették, és egy Captured flaget. A DevPushState csak a mélységet és a DC-t tölti ki, és a kerettömböt 16-tól induló duplázással növeli, így egy q 1 0 0 1 x y cm ... Q-val teli tartalomstream egyáltalán nem allokál GDI objektumot. Azok az operátorok, amik épp a clipelést akarják változtatni — értve ezalatt a függő W-vel vagy W*-vel való útfestést, az n operátort, a pattern kitöltéseket és a formba lépést —, előbb meghívják a CaptureClipBeforeChange-t
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; // már mentve, vagy nem a miénk
Region := CreateRectRgn(0, 0, 0, 0);
if Region = 0 then RaiseLastOSError;
ClipResult := GetClipRgn(FDC, Region); // 0 azt jelenti, hogy egyáltalán nincs 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); // a Region 0 eltávolítja a clipet
if FSavedClips[Index].Region <> 0 then
DeleteObject(FSavedClips[Index].Region);
Dec(FSavedClipCount);
end;
FGSStack.Pop;
end;
A lelkes verzió mért költsége az ok, amiért ez a dizájn létezik. Az első helyes implementáció minden q-n gyártott és olvasott egy GDI régiót, és a főleg numerikus transzformációkból álló oldalakon a renderelő szálak az időt GDI régió objektumokért versenyezve töltötték a raszterezés helyett. A párhuzamos render pipeline a várt nyereségéről nagyjából 1,13-szoros és 1,20-szoros egyszálas áteresztőképesség közé esett, és megbukta az 1,5-szörös gyorsulási kaput a benchmark suite-ban. Lusta rögzítéssel és újrahasznosított keretkapacitással ugyanez a benchmark újra átment az eredeti 1,5-szörös kapun. A kis TrueType glyph élsimítás ugyanabban a release-ben landolt, és a kézenfekvő gyanúsított volt, de a regresszió a régióallokációig vezetett vissza, ami jó emlékeztető arra, hogy mérj, mielőtt a legújabb funkciót hibáztatnád
Hol vannak ennek a megközelítésnek a határai?
A mentett clip GDI régió eszközpixelekben, így pontos a renderelt bitmapra, és értelmetlen bármely más célra. Ezért jegyzi minden keret a device contextjét, és a DevPopState kihagyja a visszaállítást, amikor a DC megváltozott, például amíg egy transparency group a saját rétegbitmapjába renderel. A GetClipRgn nulla visszaadása legitim eredmény, cliptelenséget jelent, és a SelectClipRgn(FDC, 0)-dal való visszaállítás az, ami helyesen távolítja el azt a clipt, ami a párosított q-nál nem létezett. A szöveg oldalon a javítás azt javítja, hová kerül minden glyph, de szélességeket nem feltalál: ha egy font kihagyja a /Widths tömbjét, és a beágyazott program nem érhető el, az advance továbbra is csak annyira jó, mint a szélesség-tartalék. Amikor ezt a területet regresszióteszteled, tartson meg legalább egy fixture-t Tf 1-fel és skálázott Tm-mel, egyet nemnulla Tz-vel és Tw-vel, és egyet q ... Q blokkon belüli cliptel, amit tőle kívüli tartalom követ, mert egyik sem bukkan fel a library által gyártott dokumentumokban
Ha alkalmazáskódból hajtod a renderelőt, a PDF oldal bitmapra renderelésében leírt hívási mintán semmi nem változik, és azok az oldalak, amik korábban foltozott sorokat vagy levágott tartalmat mutattak, egyszerűen helyesen renderelnek a 2.754.0-n és afölött. A komponensről, a támogatott Delphi és C++Builder verziókról és a licencelésről a HotPDF Delphi PDF Component termékoldalon találsz részleteket