Siderendereren i HotPDF Delphi Component flytter nå teksten fremover ved å beregne hver glyff-forskyvning i tekstrom, tx = ((w0 − Tj/1000) × Tfs + Tc + Tw) × Th slik ISO 32000-1 §9.4.4 definerer det, og flytter så tekstmatrisen gjennom sin lineære del med HPDFTranslateTextMatrix. Klipping lagres per q-ramme og gjenopprettes på Q, men en GDI-region fanges bare inn når den rammen faktisk endrer klippet. Begge fiksene landet i HotPDF 2.754.0, og begge kom fra sider i virkeligheten som rendret med sammenklemte ord eller klippregioner som lekket forbi sin Q. Den første feilen er aritmetikk som ser riktig ut helt til en produsent skriver sin fontstørrelse inn i matrisen. Den andre er en korrekthetsfiks som nesten kostet oss den parallelle render-hastighetsgevinsten, og måten vi fikk hastigheten tilbake på, er verdt å kjenne hvis du skriver noen GDI-basert PDF-enhet
Hvorfor klemmer tekst seg sammen i en klump når en PDF bruker Tf 1?
Fordi den gamle avstandskoden la en tekstromsavstand rett til translasjonskomponenten av Tm, som om tekstrom og brukerrom alltid hadde samme skala. Masse produsenter i virkeligheten setter fontstørrelsen til 1 med Tf og bærer den ekte størrelsen i tekstmatrisen. Med /F1 1 Tf og 12 0 0 12 72 700 Tm rykker en glyff på 500 enheter 0,5 i tekstrom, som er 6 punkter på siden når Tm skalerer den. Den gamle rendereren utførte Tm.e := Tm.e + Adv og flyttet pennen 0,5 punkter. Hver glyff landet en tolvtedel av et tegn etter den forrige, så en linje med brødtekst rendret som en mørk flekk ved venstre marg, mens samme fil så perfekt ut i alle andre visningsprogrammer
// Innholdsstrøm fra en produsent som enkode størrelse i Tm, ikke Tf:
// BT
// /F1 1 Tf
// 12 0 0 12 72 700 Tm
// [(Hel) 30 (lo) -250 (world)] TJ
// ET
// Gammel avstand (forenklet): avstand lagt til Tm.e som om den var brukerrom
Adv := W * FontSize / 1000; // 0,5 for en glyff på 500 enheter
if (HorizScale <> 0) and (HorizScale <> 100) then
Adv := Adv * HorizScale / 100; // Th kun på bredden
Adv := Adv + CharSpace; // Tc ikke skalert av Th
if Code = 32 then
Adv := Adv + WordSpace * FontSize / 1000; // Tw feilaktig skalert av Tfs
Tm.e := Tm.e + Adv; // ignorerer Tm.a, Tm.b, Tm.c, Tm.d
// Gammel TJ-justering: ingen Th, og igjen bare Tm.e
Tm.e := Tm.e - NumValue * FontSize / 1000;
Tm.e-snarveien var ikke den eneste defekten i den blokken. Ordmellomrom Tw uttrykkes i uskalerte tekstrom-enheter, likevel multipliserte den gamle koden det med FontSize / 1000, så under Tf 12 mistet en justert linje nesten alt sitt mellomrom mellom ord. Horisontal skalering Th anvendtes på glyffbredden men ikke på Tc eller Tw, og TJ-kerningjusteringen hoppet over den helt. Den ikke-malende stien som flytter frem modus 3 usynlig tekst, den typen OCR-tekstlag bruker, og tekst inne i skjult optional content, bar en privat kopi av samme aritmetikk, så alt tegnet etter et usynlig løp startet fra feil posisjon. Teksttilstandsfeil i en renderer feiler sjelden høyt: som operandindeks- og ressursnavn-feilene som en gang nullstilte Tc, Tw og Tz uten én eneste feilmelding, produserte disse plausige sider på bibliotekets eget resultat og brøt bare på filer fra andre produsenter
Hvordan definerer ISO 32000-1 §9.4.4 glyffavstanden?
ISO 32000-1 §9.4.4 definerer avstanden fullt ut i tekstrom og anvender den på tekstmatrisen som en translasjonsmatrise, så svaret er å beregne tx først og la Tm gjøre skaleringen, rotasjonen og skjevheten. For horisontal skriving er tx lik ((w0 − Tj/1000) × Tfs + Tc + Tw) × Th, der w0 er glyffbredden i tusendedeler av en em, Tj er TJ-justeringen, og Th er Tz delt på 100. Den nye Tm-en er [1 0 0 1 tx 0] × Tm, som i HotPDF er hjelperen HPDFTranslateTextMatrix: den legger til X og Y gjennom matrisekoeffisientene a, b, c og d i stedet for å skrive til e og f direkte. Per §9.3.3 anvendes Tw bare på enkeltbyte-tegncoden 32, så flerbyte CID-koder plukker aldri opp ordmellomrom på den horisontale stien. Samme hjelper driver nå Td, TD, T*, '- og "-operatorene, TJ-justeringer og den skjulte tekststien, noe som betyr at én enkelt funksjon eier regelen
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;
// Horisontal glyffavstand, 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 i tekstrom, uskalert
Adv := Adv * State.Text.HorizScale / 100; // Th gjelder hele summen
HPDFTranslateTextMatrix(Tm, Adv, 0);
// TJ-tallelement: samme rom, samme Th
Adjustment := -Items[I].NumValue * State.Text.FontSize / 1000;
HPDFTranslateTextMatrix(State.Text.Tm, Adjustment * State.Text.HorizScale / 100, 0);
Glyffplassering måtte følge samme logikk. Når ingen innebygd omriss er tilgjengelig og rendereren faller tilbake til GDI TextOutW, bygger den nå den fulle glyffmatrisen fra CTM × Tm × rise × em-skala, inkludert Th, og installerer den med SetWorldTransform i GM_ADVANCED-modus inne i et SaveDC / RestoreDC-par. GDI-fonten opprettes ved en fast 1000-enhets høyde og transformasjonen gjør størrelsessettingen, så rotert og skjev tekst beholder sin orientering i stedet for å tegnes oppreist på et transformert origopunkt. Vertikal skrivemodus er den eneste bevisste asymmetrien: en WMode 1-font rykker nedover y-aksen med sin vertikale metrikk, og horisontal skalering gjelder ikke den aksen
Hva lagrer egentlig q/Q i en PDF-grafikktilstand?
ISO 32000-1 §8.4.2 lister den gjeldende klippstien som del av grafikktilstanden, så Q må gjenopprette klippet nøyaktig slik det var ved den matchende q, ikke bare de numeriske parametrene. HotPDF holdt allerede en grafikktilstandsstakk med CTM, farger, linjeparametre og teksttilstand, men GDI holder klippet i enhetskonteksten, utenfor den stakken. En kopi av den numeriske tilstanden gjenopprettet derfor alt unntatt klippet, og et klipp installert med W n inne i en q ... Q-blokk holdt seg beskjærende enhver senere operasjon på siden. Form XObjects la til en andre rute til samme feil, fordi §8.10 gir en form en implisitt lagring og gjenoppretting rundt innholdet sitt, og virkelighetsformsinnhold etterlater noen ganger sine egne q-operatorer ubalanserte selv om spesifikasjonen krever at de parer opp. Rendereren kaller nå CaptureClipBeforeChange og SaveDC før den kjører en form, og etter at formen er ferdig kaster den enhver lagret region dypere enn inngangsdypden og kaller RestoreDC, så hver lagret HRGN har nøyaktig én frigjøringssti
Lat klippinnfanging med THPDFSavedClipState
Fiksen som shipper lagrer én THPDFSavedClipState-post per q, men utsetter den dyre delen til rammen først endrer klippet. Posten holder regionhandleet, stakkdypden den tilhører, enhetskonteksten den ble tatt fra og et Captured-flagg. DevPushState fyller bare inn dypden og DC-en og vokser ramme-arrayen ved å doble fra 16, så en innholdsstrøm full av q 1 0 0 1 x y cm ... Q allokerer intet GDI-objekt i det hele tatt. Operatorer som er i ferd med å endre klipping, altså stmaling med en ventende W eller W*, n-operatoren, mønstrefyll og forminngang, kaller CaptureClipBeforeChange først
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; // allerede lagret, eller ikke vår
Region := CreateRectRgn(0, 0, 0, 0);
if Region = 0 then RaiseLastOSError;
ClipResult := GetClipRgn(FDC, Region); // 0 betyr intet klipp i det hele tatt
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 fjerner klippet
if FSavedClips[Index].Region <> 0 then
DeleteObject(FSavedClips[Index].Region);
Dec(FSavedClipCount);
end;
FGSStack.Pop;
end;
Den målte kostnaden av den ivrige versjonen er grunnen til at dette designet finnes. Den første korrekte implementeringen opprettet og leste en GDI-region på hver q, og på sider hovedsakelig bestående av numeriske transformasjoner brukte renderer-trådene tiden sin på å konkurrere om GDI-regionobjekter i stedet for å rasterisere. Den parallelle render-pipelinen falt fra sin forventede gevinst til omtrent 1,13 til 1,20 ganger enkelttrådet gjennomstrømning og feilet 1,5 ganger-hastighetsporten i benchmark-suiten. Med lat innfanging og gjenbrukt rammekapasitet går samme benchmark gjennom den opprinnelige 1,5 ganger-porten igjen. Liten TrueType glyff-antialiasing landet i samme utgivelse og var den opplagte mistenkte, men regresjonen sporet tilbake til region-allokering, noe som er en god påminnelse om å måle før du skylder på den nyeste funksjonen
Hvor er grensene for denne tilnærmingen?
Det lagrede klippet er en GDI-region i enhetspiksler, så det er eksakt for bitmapet som rendres og meningsløst for ethvert annet mål. Det er grunnen til at hver ramme registrerer sin enhetskontekst, og DevPopState hopper over gjenopprettingen når DC-en har endret seg, for eksempel mens en transparansgruppe rendrer inn i sitt eget lagbitmap. GetClipRgn som returnerer null, er et legitimt resultat som betyr intet klipp, og å gjenopprette det med SelectClipRgn(FDC, 0) er det som korrekt fjerner et klipp som ikke fantes ved den matchende q. På tekstsiden korrigerer fiksen hvor hver glyff går, men den oppfinner ikke bredder: hvis en font utelater /Widths-arrayen sin og det innebygde programmet er utilgjengelig, er avstanden fortsatt bare så god som bredde-fallbacken. Når du regresjonstester dette området, behold minst én fixtur med Tf 1 og en skalert Tm, én med ikke-null Tz og Tw, og én med et klipp inne i q ... Q fulgt av innhold utenfor det, for ingen av dem dukker opp i dokumenter generert av biblioteket selv
Hvis du driver rendereren fra applikasjonskode, endres ingenting i kallmønsteret beskrevet i å rendre en PDF-side til et bitmap, og sider som tidligere viste utsmurte linjer eller beskåret innhold, bør rett og slett rendre korrekt på 2.754.0 og senere. Detaljer om komponenten, støttede Delphi- og C++Builder-versjoner og lisensiering finnes på produktsiden for HotPDF Delphi PDF Component