Full justering är layouten som gör att en textkolumn ramar in mot både vänster och höger kant, det utseende du förväntar dig av en tryckt bok eller en formell rapport. Det är lätt att beskriva och förvånansvärt lätt att göra fel, eftersom svaret på frågan "var hamnar det extra utrymmet" inte är detsamma för engelska som det är för japanska, och eftersom det naiva sättet att mäta varje rad gör en snabb sida till en långsam. HotPDF ger dig skriptmedveten justering via ett enda boxlayout-anrop, och under det anropet sitter en lärobok prestandafix värd att förstå på sin egna rätt
Den här artikeln går igenom båda. Först den typografiska regeln som bestämmer hur slack fördelas för skript med ordmellanrum kontra skript utan dem. För det andra den mätförändring som minskade kostnaden per sida för justering med ungefär åttio gånger utan synlig skillnad i utdata. Båda spelar roll om du genererar dokument i stora volymer och vill att de ska se ut som riktig sättning snarare än monospace-utdata sträckt för att passa
Vad full justering faktiskt kräver
En textrad ritad vid sin naturliga bredd når nästan aldrig den högra kanten av sin kolumn. Det finns alltid en rest, slacket, mellan där den sista glyfen slutar och var kolumngränsen sitter. Vänsterjustering lämnar det slacket till höger. Högerjustering flyttar det till vänster. Centrering delar det. Full justering tar bort det genom att bredda raden sig själv tills båda kanterna möter boxen, och det enda ärliga sättet att göra det är att trycka glyferna ifrån varandra inifrån
Regeln som skiljer bra justering från dålig är var du sätter slacket. Ett skript som skriver ord med mellanslag mellan dem, som engelska och resten av den latinska familjen, har naturliga sömmar vid varje mellanord-mellanrum. Att bredda dessa mellanrum är osynligt för ögat eftersom läsare redan accepterar att ordmellanrum varierar. Ett skript som skriver utan ordmellanrum, som kinesiska Han-tecken, japansk kana eller koreansk Hangul, har inga sådana sömmar. Där måste slacket spridas jämnt mellan angränsande glyfer, vilket är principen japanska sättare kallar kintou-waritsuke, jämnt avstånd. Att sätta latinsk ordmellanrum-stretning på en CJK-rad, eller att trycka in allt slack på det enda stället en CJK-rad råkar innehålla ett mellanslag, producerar de floder och glapp som utmärker amatörutdata
Hur HotPDF bestämmer var utrymmet hamnar
HotPDF fattar det beslutet per mellanrum, inte per rad. När det justerar en rad går det igenom varje angränsande glyfpar och frågar om ett sträckbart gräns sitter mellan dem. En gräns är sträckbar när endera sidan är ett mellanslag eller tab, det latinska fallet, eller när båda sidor är CJK-brytnbara tecken, fallet med jämnt avstånd. Det räknar dessa gränser, delar radens slack jämnt bland dem och lägger den andelen till varje kvalificerande mellanrum
Konsekvensen faller ut naturligt. En engelsk rad har sträckbara gränser bara vid sina ordmellanrum, så allt slack hamnar där och orden sprids isär medan bokstäverna inuti varje ord behåller sin naturliga avståndssättning. En Han- eller kana-rad har en sträckbar gräns mellan nästan varje glyfpar, så slacket fördelas jämnt över hela raden, exakt den jämna inter-glyph-avståndssättning dessa skript kräver. En rad som är ett enda långt latinskt ord utan internt mellanrum har ingen sträckbar gräns alls, så HotPDF lämnar det vid sin naturliga bredd snarare än att riva isär ordet bokstav för bokstav. Samma logik hanterar blandade latinska och CJK-körningar på en rad utan specialhantering, eftersom beslutet är lokalt för varje gräns
En gräns är avsiktligt utesluten överallt. Positionen efter den sista glyfen på en rad behandlas aldrig som ett mellanrum, eftersom sträckning där bara skulle återinföra en höger-hand-rest, vilket är motsatsen till justering
Varför sista raden lämnas ifred
Den sista raden i ett stycke är speciell, och att göra det fel är det vanligaste justeringsbugg. Ett styckes sista rad är vanligtvis kort, ofta bara ett fåtal ord, och att sträcka den till full kolumnbredd drar dessa ord tvärs över sidan till en gles, bruten rad. Korrekt typografi lämnar den sista raden vid sin naturliga bredd, justerad till vänster
HotPDF detekterar den avslutande raden efter position. När den omsluter texten i rader vet den när raden den just delade av når slutet av den levererade strängen. Den sista raden skickas ut med vanlig vänsterjustering och behåller sin naturliga bredd. Varje rad före den justeras till båda kanter. Hårda radbrytningar du skriver in i texten hedras som skrivna, så en avsiktlig kort rad sträcks inte heller. Läsaren ser ett rent rektangulärt textblock vars sista rad slutar naturligt, vilket är vad ögat förväntar sig
Mätkostnaden som gjorde justering långsam
För att justera en rad måste du känna till dess exakta bredd, och du måste känna till varje glyfs framsteg så att du kan placera det extra utrymmet exakt. Den första implementationen fick dessa siffror på det uppenbara sättet. Den mätte hela raden med en fullständig Unicode-breddsfråga, sedan mätte den prefixprefix efter prefixprefix för att återställa varje glyfs framsteg genom differentiering. För en rad med N glyfer är det N+1 anrop in i mätningsmotorn, och varje anrop är en fullständig GDI-rundtur, och frågar operativsystemet om att forma och mäta text och lämna tillbaka svaret
Per rad låter det billigt. Över en sida är det inte det. Ta en tät A4-sida med brödtext, ungefär fyrtiofem rader med ungefär åttio tecken vardera. Vid N+1 rundturer per rad är det ungefär 81 rundturer för varje rad och ungefär 3 645 för sidan, nästan alla spenderade på att mäta om text som motorn redan hade tittat på ögonblick tidigare. På ett batchjobb som producerar tusentals sidor dominerar det overheaden layouttiden, och varje rundtur korsar gränsen mellan din process och grafikdelsystemet
Ett anrop istället för N plus ett
Lösningen är den typ av förändring som ser liten ut och betalar sig stor. GDI kan redan rapportera en strängs totala bredd och positionen för varje glyph i en enda fråga. HotPDF exponerar det via GetWideCharAdvances, som fyller en array med varje glyfs naturliga framsteg, kerning inkluderat, och returnerar den totala bredden, i ett anrop snarare än N+1. Justeringsrutinen, _HPDFEmitJustifiedWideLine internt, frågar efter alla framstegen en gång, beräknar slacket, fördelar det över de sträckbara gränserna och skickar ut raden
För samma A4-sida sjunker per-rad-mätningen från ungefär 81 rundturer till en, så sidan faller från ungefär 3 645 rundturer till ungefär 45, nära en åttiofaldsminskning. Utdata är byte-för-byte identiska, eftersom ingenting om mätningen förändrades förutom hur många gånger den begärs. Samma GDI-motor, samma typsnittsmätvärden, samma kerning matar samma siffror. Bara rundtursantalet sjönk. När en mätning redan är korrekt är rätt optimering att sluta fråga efter den upprepade gånger, inte att approximera den
Hur raden når sidan
När slacket är tilldelat skickar HotPDF ut raden med ExtTextOut och en per-glyph-framstegsarray, Dx-arrayen. Varje post är avståndet från en glyfs ursprung till nästa, vilket är den glyfens naturliga framsteg plus dess andel av slacket när en sträckbar gräns följer den. Detta mappas direkt mot PDF-avbildningsmodellen. Positionerad text skrivs med TJ-operatorn, en array som varvar glyfkörningar med explicita horisontella justeringar, och Dx-värdena blir exakt dessa justeringar. Det är därför det extra utrymmet hamnar mellan glyfer vid exakta sub-punkt-positioner snarare än att förfalskas med utfyllnadstecken, och varför en justerad HotPDF-rad mäts korrekt om ett nedströmsverktyg läser tillbaka den
Du anropar inte ExtTextOut själv för justerade stycken. Ingångspunkten är WideTextOutBox, som omsluter en Unicode-sträng i en box och tillämpar den justering du begär. Den delar texten i rader som passar boxbredden, placerar varje rad nedåt i boxhöjden och returnerar antalet tecken den lyckades passa in innan det vertikala utrymmet tog slut. Justeringen väljs av justeringsuppräknaren
type
THPDFJustificationType = (jtLeft, jtCenter, jtRight, jtJustify);
De tre första är självförklarande vänster-, centrerad och högerjustering. Den fjärde, jtJustify, är den fullständiga båda-kanter-justeringen som beskrivs här, och det är värdet WideTextOutBox läser för att slå på den skriptmedvetna avståndssättningen
Att justera ett stycke i praktiken
Ett fullständigt exempel skapar ett dokument, ställer in ett typsnitt och häller ett stycke i en box med full justering. Samma kod justerar latinska och CJK-text utan en flaggändring, eftersom skriptmedvetenheten bor 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;
För att rita samma block vänsterjusterat, centrerat eller högerjusterat, ändra bara det sista argumentet till jtLeft, jtCenter eller jtRight. Omslutningen, radplaceringen och returvärdet är desamma. Den uppmätta bredden som driver alla fyra vägarna kommer från GetWideTextWidth, den Unicode-medvetna breddsfrågan som mäter en WideString korrekt där den äldre byte-vis mätningen skulle felmäta allt förbi Latin-1, vilket är vad som gör att boxen omsluter CJK och surrogatpar-text vid rätt plats till att börja med
Justering är ett lager i en större textformningsstack. När en rad innehåller skript som omordnar eller förenar sina glyfer sitter avståndsbesluten här ovanpå arbetet som beskrivs i vår artikel om formning av komplex-skript-text, och när ett typsnitt bär typografiska varianter du vill välja, se hur man kör OpenType GSUB stilistiska alternativ. Allt det levereras i HotPDF Component för Delphi och C++Builder, bredvid de bredare text-, layout- och dokument-API:erna som täcks på hela den här bloggen