Tehnicki clanak

Puno poravnanje PDF teksta u Delphi-ju pomocu HotPDF-a

Puno poravnanje je raspored koji cini kolonu teksta poravnatom na oba leva i desna ivica, izgled koji se ocekuje od stampane knjige ili formalnog izvestaja. Lako ga je opisati i iznenadujuce ga je lako pogresiti, jer odgovor na pitanje "gde ide visak prostora" nije isti za engleski i za japanski, i jer naivni nacin merenja svake linije pretvara brzu stranicu u sporu. HotPDF vam daje pismu-svesno poravnanje kroz jedan box-layout poziv, a ispod tog poziva sedi udzbenicki ucelajanje performansi vredno razumevanja samo po sebi

Ovaj clanak prolazi kroz oba dela. Prvo, tipogerafsko pravilo koje odlucuje kako se slobodan prostor distribuise za pisma sa razmacima izmedju reci nasuprot pismima bez njih. Drugo, promena merenja koja je smanjila trošak po stranici poravnanja za otprilike osamdeset puta bez vidljive razlike u izlazu. Oba su vazna ako generisete dokumente u velikom obimu i zelite da izgledaju kao pravo slaganje teksta a ne monospace izlaz rastezan da pristaje

Sta puno poravnanje zapravo zahteva

Linija teksta nacrtana na svojoj prirodnoj sirini skoro nikad ne doseze desnu ivicu kolone. Uvek postoji ostatak, slobodan prostor, izmedju mesta gde se zavrsava poslednji glif i mesta gde sedi granica kolone. Levo poravnanje ostavlja taj slobodan prostor na desnoj strani. Desno poravnanje ga premesta na levu. Centriranje ga deli. Puno poravnanje ga uklanja prosirujuci samu liniju dok obe ivice ne dotaknu okvir, i jedini posten nacin da se to uradi je razdvajanje glifova iznutra

Pravilo koje razdvaja dobro poravnanje od loseg je gde stavljate slobodan prostor. Pismo koje pise reci sa razmacima izmedju njih, kao sto je engleski i ostatak latinske porodice, ima prirodne savove na svakom razmaku izmedju reci. Prosivanje tih razmaka je nevidljivo oku jer citaoci vec prihvataju da razmaci izmedju reci variraju. Pismo koje pise bez razmaka izmedju reci, kao sto su kineski Han znakovi, japanska kana ili korejski Hangul, nema takvih savova. Tamo slobodan prostor mora biti ravnomerno rasporeden izmedju susednih glifova, sto je princip koji japanski slagaci teksta zovu kintou-waritsuke, ravnomerno razmicanje. Stavljanje latinicnog rastezanja razmaka izmedju reci na CJK liniju, ili trpanje sav slobodan prostor na jedno mesto gde CJK linija slucajno sadrzi razmak, proizvodi reke i praznine koje oznacavaju amaterski izlaz

Kako HotPDF odlucuje gde ide prostor

HotPDF donosi tu odluku po granici, ne po liniji. Kada poravnava liniju, prolazi svaki susedni par glifova i pita ima li izmedju njih rastezljiva granica. Granica je rastezljiva kada je s jedne strane razmak ili tabulator, latinski slucaj, ili kada su obe strane CJK-lomivi znakovi, slucaj ravnomernog razmicanja. Broji te granice, deli slobodan prostor linije ravnomerno izmedju njih, i dodaje taj udeo svakoj kvalifikujucoj granici

Posledica se prirodno namece. Engleska linija ima rastezljive granice samo na razmacima izmedju reci, pa sav slobodan prostor tamo pristaje i reci se razdvajaju dok slova unutar svake reci zadrzavaju svoje prirodno razmicanje. Han ili kana linija ima rastezljivu granicu izmedju skoro svakog para glifova, pa se slobodan prostor ravnomerno distribuise kroz celu liniju, tacno ono ravnomerno meduglifno razmicanje koje ta pisma zahtevaju. Linija koja je jedna duga latinicna rec bez unutrasnjeg razmaka nema rastezljive granice uopste, pa je HotPDF ostavlja na prirodnoj sirini umesto da je razdvaja slovo po slovo. Ista logika obradjuje mesovite latinicne i CJK nizove u jednoj liniji bez posebnih slucajeva, jer je odluka lokalna svakoj granici

Jedna granica je namerno iskljucena svuda. Pozicija iza poslednjeg glifa linije se nikad ne tretira kao praznina, jer bi rastezanje tamo samo ponovo uvelo desni ostatak, sto je suprotno od poravnanja

Zasto se poslednja linija ostavlja na miru

Poslednja linija pasusa je posebna, i pogresno je resiti je je najcesca greška poravnanja. Poslednja linija pasusa je obicno kratka, cesto samo nekoliko reci, i rastezanje na punu sirinu kolone vuče te reci po stranici u retki, polomljeni red. Ispravna tipografija ostavlja poslednju liniju na prirodnoj sirini, poravnatu ulevo

HotPDF otkriva zavrsnu liniju po poziciji. Dok prelama tekst u linije, zna kada linija koju je upravo odvojila doseze kraj dostavljenog niza. Ta poslednja linija se emituje sa obicnim levim poravnanjem i zadrzava svoju prirodnu sirinu. Svaka linija ispred nje se poravnava na obe ivice. Tvrdi prelomi linija koje pisete u tekst se postuju kako su napisani, pa se namerno kratka linija nikad ne rasteze. Citaoc vidi cist pravougaoni blok teksta cija se poslednja linija zavrsava prirodno, sto je ono sto oko ocekuje

Trosak merenja koji je cinio poravnanje sporim

Da biste poravnali liniju morate znati njenu tacnu sirinu, i morate znati napredak svakog glifa da biste mogli precizno postaviti dodatni prostor. Prva implementacija je dobivala te brojeve na ocigledni nacin. Merila je celu liniju sa punim Unicode upitom za sirinu, zatim merila prefiks za prefiksom da bi povratila napredak svakog glifa oduzimanjem. Za liniju od N glifova to je N+1 poziva u engine za merenje, a svaki poziv je puni GDI round-trip, trazeci od operativnog sistema da oblikuje i izmeri tekst i vrati odgovor

Po liniji to zvuci jeftino. Kroz stranicu nije. Uzmite gustu A4 stranicu teksta tela, otprilike cetrdeset i pet linija od oko osamdeset znakova svaki. Na N+1 round-tripova po liniji to je oko 81 round-tripa za svaku liniju i grube 3645 za stranicu, skoro sve ih provedenih u ponovnom merenju teksta koji je engine vec gledao trenutke ranije. Na serijskom poslu koji proizvodi hiljade stranica, taj overhead dominira vremenom rasporeda, a svaki round-trip prelazi granicu izmedju vaseg procesa i grafickog podsistema

Jedan poziv umesto N plus jedan

Ispravak je vrsta promene koja izgleda mala i isplati se u velikom. GDI vec moze prijaviti ukupnu sirinu niza i poziciju svakog glifa u jednom upitu. HotPDF to izlaze kroz GetWideCharAdvances, koji puni niz sa napretkom svakog glifa u prirodnom napretku, ukljucujuci kerning, i vraca ukupnu sirinu, u jednom pozivu umesto N+1. Rutina poravnanja, _HPDFEmitJustifiedWideLine interno, trazi sve napretke jedanput, izracunava slobodan prostor, distribuise ga po rastezljivim granicama i emituje liniju

Za tu istu A4 stranicu merenje po liniji pada sa oko 81 round-tripa na jedan, pa stranica pada sa grubih 3645 round-tripova na oko 45, blizu osamdesetostrukog smanjenja. Izlaz je identican bajt-po-bajt, jer se merenje nije promenilo ni u cemu osim koliko puta se trazi. Isti GDI engine, iste metrike fonta, isti kerning hrane iste brojeve. Samo je broj round-tripova pao. Kada je merenje vec ispravno, prava optimizacija je prestati ga traziti vise puta, ne aproksimirati ga

Kako linija doseze stranicu

Jednom kada je slobodan prostor rasporeden, HotPDF emituje liniju sa ExtTextOut i nizom napretka po glifu, Dx nizom. Svaki unos je udaljenost od ishodista jednog glifa do sledeceg, sto je prirodni napredak tog glifa plus njegov udeo slobodnog prostora kada iza njega sledi rastezljiva granica. Ovo se direktno preslikava na PDF imaging model. Pozicionirani tekst se pise sa operatorom TJ, nizom koji isprepliće nizove glifova sa eksplicitnim horizontalnim prilagodavanjima, a vrednosti Dx postaju tacno te prilagodbe. Zato dodatni prostor pristaje izmedju glifova na preciznim pod-taccastim pozicijama umesto da se lazi znakovima za punjenje, i zasto HotPDF poravnana linija ispravno meri ako je downstream alat procita

Ne pozivate ExtTextOut sami za poravnate pasuse. Ulazna tacka je WideTextOutBox, koji obmotava Unicode niz u okvir i primenjuje poravnanje koje trazite. Deli tekst u linije koje pristaju sirini okvira, postavlja svaku liniju niz visinu okvira i vraca broj znakova kojima je uspelo stati pre nego sto mu je ponestalo vertikalnog prostora. Poravnanje se bira enumom za poravnanje

type
  THPDFJustificationType = (jtLeft, jtCenter, jtRight, jtJustify);

Prva tri su samo po sebi razumljivo levo, centrirano i desno poravnanje. Cetvrto, jtJustify, je puno poravnanje na obe ivice opisano ovde, i to je vrednost koju WideTextOutBox cita da ukljuci pismu-svesno razmicanje

Poravnavanje pasusa u praksi

Potpuni primer kreira dokument, postavlja font i sipa pasUS u okvir sa punim poravnanjem. Isti kod poravnava latinicni i CJK tekst bez promene zastavice, jer pismu-svesnost zivi ispod API-ja

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;

Da nacrtate isti blok levo poravnat, centriran ili desno poravnat, promenite samo poslednji argument na jtLeft, jtCenter ili jtRight. Prelamanje, postavljanje linija i povratna vrednost ostaju isti. Izmerena sirina koja pokrace sva cetiri puta dolazi od GetWideTextWidth, Unicode-svesnog upita za sirinu koji meri WideString ispravno gde bi starije bajtno merenje pogresno procenilo sve sto je izvan Latin-1, sto je ono sto cini da okvir prelama CJK i surrogate-pair tekst na pravom mestu za pocetak

Poravnanje je jedan sloj veceg steka oblikovanja teksta. Kada linija sadrzi pisma koja preuređuju ili spajaju svoje glifove, odluke o razmicanju ovde sede na vrhu rada opisanog u nasem clanku o oblikovanju teksta slozenih pisama, a kada font nosi tipogerafske varijante koje zelite odabrati, pogledajte kako pokrenuti OpenType GSUB stilske alternative. Sve se to isporucuje u HotPDF Component za Delphi i C++Builder, zajedno sa sirim API-jima za tekst, raspored i dokument obradenim kroz ovaj blog