Blokkjustering er oppsettet som får en tekstspalte til å stille seg på linje på både venstre og høyre kant, det utseendet du forventer fra en trykt bok eller en formell rapport. Det er enkelt å beskrive og overraskende enkelt å gjøre feil, fordi svaret på spørsmålet "hvor blir det av den ekstra plassen" ikke er det samme for engelsk som for japansk, og fordi den naive måten å måle hver linje på gjør en rask side til en treg en. HotPDF gir deg skriptbevisst justering gjennom et enkelt boksoppsetts-kall, og under det kallet ligger en klassisk ytelsesfiks som er verdt å forstå i seg selv
Hva blokkjustering faktisk krever
En tekstlinje tegnet med sin naturlige bredde når nesten aldri høyre kant av spalten sin. Det er alltid en rest, slakken, mellom der den siste glyfen slutter og der spaltegrensen sitter. Venstrejustering etterlater den slakken til høyre. Høyrejustering flytter den til venstre. Midtstilling deler den. Blokkjustering fjerner den ved å utvide selve linjen til begge kanter møter boksen, og den eneste ærlige måten å gjøre det på er å skyve glyfene fra hverandre fra innsiden
Regelen som skiller god justering fra dårlig, er hvor du plasserer slakken. Et skript som skriver ord med mellomrom mellom dem, som engelsk og resten av den latinske familien, har naturlige sømmer ved hvert mellomrom mellom ord. Å utvide disse mellomrommene er usynlig for øyet fordi lesere allerede aksepterer at ordmellomrom varierer. Et skript som skriver uten ordmellomrom, som kinesiske Han-tegn, japansk kana eller koreansk Hangul, har ingen slike sømmer. Der må slakken fordeles jevnt mellom tilstøtende glyfer, som er prinsippet japanske typografer kaller kintou-waritsuke, jevn avstand. Å bruke latin-stil ordmellomromsstrekking på en CJK-linje, eller presse all slakken inn på det ene stedet en CJK-linje tilfeldigvis inneholder et mellomrom, produserer elvene og hullene som kjennetegner amatørutdata
Hvordan HotPDF bestemmer hvor plassen går
HotPDF tar den beslutningen per gap, ikke per linje. Når den justerer en linje, går den gjennom hvert tilstøtende par av glyfer og spør om en strekkbar grense sitter mellom dem. En grense er strekkbar når en av sidene er et mellomrom eller tabulator, det latinske tilfellet, eller når begge sider er CJK-brytbare tegn, det jevnt-fordelte tilfellet. Den teller de grensene, deler linjens slakk likt mellom dem, og legger den andelen til hvert kvalifiserende gap
Konsekvensen faller ut naturlig. En engelsk linje har strekkbare grenser kun ved ordmellomrommene sine, så all slakk lander der og ordene spres fra hverandre mens bokstaver inni hvert ord beholder sin naturlige avstand. En Han- eller kana-linje har en strekkbar grense mellom nesten hvert par av glyfer, så slakken fordeles jevnt over hele linjen, akkurat den jevne inter-glyf-avstanden de skriptene krever. En linje som er et enkelt langt latinsk ord uten internt mellomrom, har ingen strekkbar grense i det hele tatt, så HotPDF lar den være i sin naturlige bredde i stedet for å rive ordet fra hverandre bokstav for bokstav. Den samme logikken håndterer blandede latinske og CJK-kjøringer på én linje uten spesialtilfeller, fordi beslutningen er lokal for hver grense
Én grense er bevisst utelatt overalt. Posisjonen etter den siste glyfen på en linje behandles aldri som et gap, fordi strekking der bare ville gjeninnføre en rest på høyre side, noe som er det motsatte av justering
Hvorfor den siste linjen blir latt være i fred
Den siste linjen i et avsnitt er spesiell, og å gjøre det feil er den vanligste justeringsfeilen. Et avsnitts siste linje er vanligvis kort, ofte bare noen få ord, og å strekke den til full spaltebredde drar de ordene over siden i en glissen, oppbrutt rekke. Korrekt typografi lar den siste linjen være i sin naturlige bredde, justert til venstre
HotPDF oppdager den avsluttende linjen etter posisjon. Etter hvert som den bryter teksten i linjer, vet den når linjen den nettopp skilte ut, når slutten på den oppgitte strengen. Den siste linjen sendes ut med vanlig venstrejustering og beholder sin naturlige bredde. Hver linje før den er justert til begge kanter. Harde linjeskift du skriver inn i teksten, respekteres som skrevet, så en tilsiktet kort linje strekkes heller aldri. Leseren ser en ren rektangulær tekstblokk der den siste linjen slutter naturlig, noe som er hva øyet forventer
Målekostnaden som gjorde justering treg
For å justere en linje må du vite den nøyaktige bredden, og du må vite fremrykket for hver glyf slik at du kan plassere den ekstra plassen nøyaktig. Den første implementasjonen hentet de tallene på den åpenbare måten. Den målte hele linjen med en full Unicode-breddespørring, deretter målte den prefiks etter prefiks for å gjenopprette hver glyfs fremrykk ved differensiering. For en linje med N glyfer er det N+1 kall inn i målemotoren, og hvert kall er en full GDI-rundtur som ber operativsystemet om å forme og måle tekst og levere svaret tilbake
Per linje høres det billig ut. Over en side er det ikke det. Ta en tett A4-side med brødtekst, omtrent førtifem linjer med omtrent åtti tegn hver. Med N+1 rundturer per linje er det rundt 81 rundturer for hver linje og grovt regnet 3 645 for siden, hvorav nesten alle brukes på å måle tekst som motoren allerede hadde sett på øyeblikk tidligere, på nytt. På en batch-jobb som produserer tusenvis av sider, dominerer den faste kostnaden layout-tiden, og hver rundtur krysser grensen mellom prosessen din og grafikk-undersystemet
Ett kall i stedet for N pluss én
Fiksen er den typen endring som ser liten ut og gir stor uttelling. GDI kan allerede rapportere en strengs totale bredde og posisjonen til hver glyf i en enkelt spørring. HotPDF eksponerer dette gjennom GetWideCharAdvances, som fyller en matrise med hver glyfs naturlige fremrykk, inkludert kniping (kerning), og returnerer den totale bredden i ett kall i stedet for N+1. Justeringsrutinen, internt _HPDFEmitJustifiedWideLine, ber om alle fremrykkene én gang, beregner slakken, fordeler den over de strekkbare grensene og sender ut linjen
For den samme A4-siden faller målingen per linje fra omtrent 81 rundturer til én, så siden faller fra grovt regnet 3 645 rundturer til omtrent 45, i nærheten av en åttidobbel reduksjon. Utdataene er byte-for-byte identiske, fordi ingenting ved målingen endret seg bortsett fra hvor mange ganger den ble bedt om. Den samme GDI-motoren, de samme skriftmetrikkene, den samme knipingen mater de samme tallene. Bare rundtur-tellingen falt. Når en måling allerede er riktig, er den rette optimaliseringen å slutte å be om den gjentatte ganger, ikke å tilnærme den
Hvordan linjen når siden
Når slakken er fordelt, sender HotPDF ut linjen med ExtTextOut og en fremrykksmatrise per glyf, Dx-matrisen. Hver oppføring er avstanden fra en glyfs opprinnelse til den neste, som er den glyfens naturlige fremrykk pluss dens andel av slakken når en strekkbar grense følger den. Dette kartlegges direkte til PDF-avbildningsmodellen. Posisjonert tekst skrives med TJ-operatoren, en matrise som fletter inn glyf-kjøringer med eksplisitte horisontale justeringer, og Dx-verdiene blir nøyaktig de justeringene. Det er grunnen til at den ekstra plassen lander mellom glyfer i presise under-punkt-posisjoner i stedet for å simuleres med utfyllingstegn, og grunnen til at en blokkjustert HotPDF-linje måler riktig hvis et nedstrømsverktøy leser den tilbake
Du kaller ikke ExtTextOut selv for blokkjusterte avsnitt. Inngangspunktet er WideTextOutBox, som bryter en Unicode-streng inn i en boks og bruker justeringen du ber om. Den deler teksten i linjer som passer til boksens bredde, plasserer hver linje nedover boksens høyde, og returnerer antall tegn den klarte å få plass til før den gikk tom for vertikal plass. Justeringen velges av justification enum-en
type
THPDFJustificationType = (jtLeft, jtCenter, jtRight, jtJustify);
De tre første er selvforklarende venstre-, midt- og høyrejustering. Den fjerde, jtJustify, er blokkjusteringen til begge kanter beskrevet her, og det er verdien WideTextOutBox leser for å slå på den skriptbevisste avstanden
Blokkjustering av et avsnitt i praksis
Et komplett eksempel oppretter et dokument, setter en skrifttype og heller et avsnitt i en boks med full blokkjustering. Den samme koden justerer latinsk og CJK-tekst uten en flagg-endring, fordi skriptbevisstheten lever under API-et
uses
HPDFDoc;
procedure JustifyParagraph;
var
Pdf: THotPDF;
Body: WideString;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'Justified.pdf';
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('Arial', 11);
Body :=
'Full justification spreads the slack on each filled line so both ' +
'edges meet the column, while the last line keeps its natural width. ' +
'For scripts with word gaps the space lands between words; for ' +
'scripts without them it spreads evenly between glyphs.';
// X, Y, LineSpacing, BoxWidth, BoxHeight, Text, Align
Pdf.CurrentPage.WideTextOutBox(72, 72, 4, 380, 240, Body, jtJustify);
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
For å tegne den samme blokken venstrejustert, midtstilt eller høyrejustert, endrer du bare det siste argumentet til jtLeft, jtCenter eller jtRight. Tekstbrytingen, linjeplasseringen og returverdien forblir de samme. Den målte bredden som driver alle de fire veiene, kommer fra GetWideTextWidth, den Unicode-bevisste breddespørringen som måler en WideString riktig der den eldre byte-vise målingen ville feildimensjonert alt forbi Latin-1, noe som er det som får boksen til å bryte CJK og surrogat-par-tekst på riktig sted til å begynne med
Blokkjustering er ett lag av en større tekstformingsstakk. Når en linje inneholder skripter som omorganiserer eller kobler sammen sine glyfer, sitter avstandsbeslutningene her på toppen av arbeidet beskrevet i vår artikkel om forming av tekst for komplekse skripter, og når en skrifttype har typografiske varianter du vil velge, se hvordan du driver OpenType GSUB stilistiske alternativer. Alt sammen følger med i HotPDF Component for Delphi og C++Builder, sammen med de bredere tekst-, layout- og dokument-API-ene som dekkes på tvers av denne bloggen