Volledige uitvulling is de opmaak die een kolom tekst aan zowel de linker- als de rechterrand laat uitlijnen, de uitstraling die u verwacht van een gedrukt boek of een formeel rapport. Het is gemakkelijk te beschrijven en verbazingwekkend gemakkelijk fout te doen, omdat het antwoord op de vraag "waar gaat de extra ruimte heen" voor het Engels niet hetzelfde is als voor het Japans, en omdat de naïeve manier om elke regel te meten een snelle pagina in een trage verandert. HotPDF biedt u scriptbewuste uitvulling via een enkele box-layout-aanroep, en onder die aanroep zit een prestatieverbetering volgens het boekje die op zichzelf de moeite van het begrijpen waard is
Dit artikel loopt door beide heen. Ten eerste, de typografische regel die beslist hoe de extra ruimte ("slack") wordt verdeeld voor scripts met woordhiaten in tegenstelling tot scripts zonder hiaten. Ten tweede, de wijziging in meting die de kosten per pagina voor uitvulling met grofweg tachtig keer verminderde zonder zichtbaar verschil in de uitvoer. Beide doen ertoe als u documenten op volume genereert en wilt dat ze lezen als echt zetwerk in plaats van uitgerekte monospaced uitvoer
Wat volledige uitvulling daadwerkelijk vereist
Een tekstregel die op zijn natuurlijke breedte wordt getekend, bereikt bijna nooit de rechterrand van de kolom. Er is altijd een restwaarde, de slack, tussen de plek waar de laatste glyph eindigt en de plek waar de kolomgrens zit. Linksuitlijning laat die slack aan de rechterkant. Rechtsuitlijning verplaatst het naar links. Centreren splitst het. Volledige uitvulling verwijdert het door de regel zelf te verbreden totdat beide randen de box raken, en de enige eerlijke manier om dat te doen is door de glyphs van binnenuit uit elkaar te duwen
De regel die goede uitvulling van slechte scheidt, is de plek waar u de slack stopt. Een script dat woorden met spaties ertussen schrijft, zoals het Engels en de rest van de Latijnse familie, heeft natuurlijke naden bij elke spatie tussen woorden. Het verbreden van die spaties is onzichtbaar voor het oog, omdat lezers al accepteren dat woordhiaten variëren. Een script dat zonder woordhiaten schrijft, zoals Chinese Han-karakters, Japanse kana of Koreaanse Hangul, heeft niet van dat soort naden. Daar moet de slack gelijkmatig worden verdeeld tussen aangrenzende glyphs, wat het principe is dat Japanse zetters kintou-waritsuke, gelijkmatige spatiëring, noemen. Latijnse woordhiaat-uitrekking toepassen op een CJK-regel, of alle slack op de ene plek proppen waar een CJK-regel toevallig een spatie bevat, produceert de "rivers" en gaten die amateur-uitvoer kenmerken
Hoe HotPDF beslist waar de ruimte naartoe gaat
HotPDF neemt die beslissing per hiaat, niet per regel. Wanneer het een regel uitvult, wandelt het langs elk aangrenzend paar glyphs en vraagt het of er zich een uitrekbare grens tussen bevindt. Een grens is uitrekbaar wanneer één van de kanten een spatie of tab is, het Latijnse geval, of wanneer beide kanten CJK-breekbare tekens zijn, het "even-spacing" geval. Het telt die grenzen, verdeelt de slack van de regel evenredig hierover, en voegt dat deel toe aan elk gekwalificeerd hiaat
De consequentie rolt er van nature uit. Een Engelse regel heeft alleen uitrekbare grenzen bij zijn woordspaties, dus de hele slack belandt daar en de woorden spreiden zich uit, terwijl de letters binnen elk woord hun natuurlijke spatiëring behouden. Een Han- of kana-regel heeft een uitrekbare grens tussen bijna elk paar glyphs, dus de slack wordt gelijkmatig over de hele regel verdeeld, exact de gelijkmatige inter-glyph-spatiëring die die scripts vereisen. Een regel die een enkel lang Latijns woord is zonder interne spatie, heeft helemaal geen uitrekbare grens, dus HotPDF laat het op zijn natuurlijke breedte in plaats van het woord letter voor letter uit elkaar te trekken. Dezelfde logica handelt gemengde reeksen van Latijns en CJK in één regel af zonder speciale uitzonderingen, omdat de beslissing lokaal per grens is
Één grens is overal met opzet weggelaten. De positie ná de laatste glyph van een regel wordt nooit als hiaat behandeld, omdat uitrekken op die plek gewoon weer een restwaarde aan de rechterkant zou introduceren, wat het tegenovergestelde is van uitvulling
Waarom de laatste regel met rust wordt gelaten
De laatste regel van een alinea is speciaal, en dit fout doen is de meest voorkomende bug bij uitvulling. De laatste regel van een alinea is doorgaans kort, vaak slechts een paar woorden, en het tot de volledige kolombreedte uitrekken ervan trekt die woorden over de pagina in een spaarzame, kapotte rij. Correcte typografie laat de laatste regel op zijn natuurlijke breedte, links uitgelijnd
HotPDF detecteert de afsluitende regel op basis van positie. Naarmate het de tekst omloopt in regels, weet het wanneer de regel die het zojuist heeft afgesplitst het einde van de aangeleverde string bereikt. Die laatste regel wordt uitgestuurd met gewone linksuitlijning en behoudt zijn natuurlijke breedte. Elke regel ervoor wordt naar beide randen uitgevuld. Harde regeleindes die u in de tekst schrijft, worden gehonoreerd zoals ze geschreven zijn, dus een opzettelijk korte regel wordt ook nooit uitgerekt. De lezer ziet een strak rechthoekig tekstblok waarvan de laatste regel natuurlijk eindigt, wat is wat het oog verwacht
De meetkosten die uitvulling traag maakten
Om een regel uit te vullen, moet u de exacte breedte kennen, en moet u de advance (voortgang) van elke glyph kennen, zodat u de extra ruimte precies kunt plaatsen. De eerste implementatie verkreeg die cijfers op de voor de hand liggende manier. Het meette de hele regel met een volledige Unicode-breedtequery, en meette vervolgens voorvoegsel na voorvoegsel om de advance van elke glyph te achterhalen door verschillen te bereken. Voor een regel van N glyphs zijn dat N+1 calls naar de meetengine, en elke call is een volledige GDI-round-trip, waarbij het besturingssysteem wordt gevraagd om tekst te shapen en meten, en het antwoord terug te geven
Per regel klinkt dat goedkoop. Maar over een pagina gemeten is het dat niet. Neem een dichtbevolkte A4-pagina met broodtekst, ruwweg vijfenveertig regels van ongeveer tachtig tekens per stuk. Met N+1 round-trips per regel is dat ongeveer 81 round-trips voor elke regel en ruwweg 3.645 voor de pagina, waarbij bijna alle tijd opgaat aan het opnieuw meten van tekst die de engine kort daarvoor al had bekeken. Bij een batch-opdracht die duizenden pagina's produceert, domineert die overhead de layout-tijd, en elke round-trip doorkruist de grens tussen uw proces en het grafische subsysteem
Eén aanroep in plaats van N plus één
De oplossing is het soort verandering dat klein lijkt, maar veel oplevert. GDI kan de totale breedte van een string en de positie van elke glyph al in een enkele query rapporteren. HotPDF stelt dat beschikbaar via GetWideCharAdvances, dat een array vult met de natuurlijke advance van elke glyph, kerning inbegrepen, en de totale breedte retourneert, in één aanroep in plaats van N+1. De uitvullingsroutine, intern _HPDFEmitJustifiedWideLine, vraagt één keer naar alle advances, berekent de slack, verdeelt het over de uitrekbare grenzen, en stuurt de regel uit
Voor diezelfde A4-pagina zakt de meting per regel van ongeveer 81 round-trips naar één, dus de pagina daalt van ruwweg 3.645 round-trips naar ongeveer 45, wat dicht in de buurt komt van een tachtigvoudige reductie. De uitvoer is byte-voor-byte identiek, omdat er niets aan de meting is veranderd, behalve hoe vaak deze wordt aangevraagd. Dezelfde GDI-engine, dezelfde lettertype-metrics, dezelfde kerning leveren dezelfde getallen aan. Alleen de round-trip-teller zakte. Wanneer een meting al correct is, is de juiste optimalisatie om er niet herhaaldelijk om te blijven vragen, en niet om het te gaan benaderen
Hoe de regel de pagina bereikt
Zodra de slack is verdeeld, stuurt HotPDF de regel uit met ExtTextOut en een per-glyph advance-array, de Dx-array. Elk element is de afstand van de oorsprong van de ene glyph naar de volgende, wat de natuurlijke advance van die glyph is plus zijn aandeel van de slack wanneer er een uitrekbare grens op volgt. Dit wordt rechtstreeks gemapt op het PDF imaging-model. Gepositioneerde tekst wordt geschreven met de TJ-operator, een array die glyph-reeksen verweeft met expliciete horizontale aanpassingen, en de Dx-waarden worden precies die aanpassingen. Dat is de reden waarom de extra ruimte tussen glyphs belandt op precieze sub-puntposities in plaats van nagemaakt te worden met opvultekens, en waarom een uitgevulde HotPDF-regel correct meet als een stroomafwaartse tool deze weer uitleest
U roept zelf ExtTextOut niet aan voor uitgevulde alinea's. Het ingangspunt is WideTextOutBox, dat een Unicode-string in een box omloopt en de uitlijning toepast waarom u vraagt. Het splitst de tekst op in regels die in de breedte van de box passen, plaatst elke regel langs de hoogte van de box, en retourneert het aantal tekens dat erin past voordat de verticale ruimte op is. De uitlijning wordt gekozen door de justification-enum
type
THPDFJustificationType = (jtLeft, jtCenter, jtRight, jtJustify);
De eerste drie wijzen zichzelf: links, gecentreerd en rechts. De vierde, jtJustify, is de volledige uitvulling die hier wordt beschreven, en het is de waarde die WideTextOutBox uitleest om de scriptbewuste spatiëring in te schakelen
Een alinea in de praktijk uitvullen
Een compleet voorbeeld maakt een document, stelt een lettertype in, en giet een alinea in een box met volledige uitvulling. Dezelfde code vult Latijnse en CJK-tekst uit zonder een flag-wijziging, omdat de scriptbewustheid zich onder de API bevindt
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;
Om hetzelfde blok links-uitgelijnd, gecentreerd of rechts-uitgelijnd te tekenen, hoeft u alleen het laatste argument te wijzigen in jtLeft, jtCenter of jtRight. De omloop, de regelplaatsing en de retourwaarde blijven hetzelfde. De gemeten breedte die al deze vier paden aandrijft, komt van GetWideTextWidth, de Unicode-bewuste breedtequery die een WideString correct meet, waar de oudere bytegewijze meting de grootte van alles voorbij Latin-1 verkeerd zou inschatten; dit is überhaupt wat ervoor zorgt dat de box CJK-tekst en surrogaatparen op de juiste plaats omloopt
Uitvulling is één laag van een grotere text-shaping-stack. Wanneer een regel scripts bevat die hun glyphs herordenen of samenvoegen, rusten de spatiëringsbeslissingen die hier worden besproken bovenop het werk dat wordt beschreven in ons artikel over complex-script text shaping, en wanneer een lettertype typografische varianten met zich meebrengt die u wilt selecteren, kijk dan hoe u OpenType GSUB stilistische alternatieven aanstuurt. Het wordt allemaal meegeleverd in de HotPDF Component voor Delphi en C++Builder, naast de bredere tekst-, layout- en document-API's die overal op deze blog worden behandeld