[1] partea a II-a
Mai încolo vom schiţa o procedură PS prin care putem reda graficele unor caractere (fie „pline”, prin fill
, fie conturate, prin stroke
) din diverse fonturi (la o scară convenabil de mare), evidenţiind şi metricile principale asociate lor:
Fig.1
Pentru un caracter oarecare, pe lângă graficul acestuia, sunt trasate trei linii: „linia de bază” (de pe orizontala imaginară pe care se bazează scrierea unor caractere pe un acelaşi rând al paginii) şi verticalele boxei care ar încadra graficul caracterului; mai jos referim aceste linii şi prin notaţiile Lb, Vl ("left") şi respectiv Vr ("right").
Capătul din stânga al liniei de bază este originea O(0, 0), a sistemului de coordonate al caracterului; capătul final O(w, 0) al lui Lb este originea Ou(0, 0) a caracterului care ar putea urma pe acelaşi rând celui de lăţime w curent. Fixând astfel O şi Ou pentru fiecare caracter, se asigură în mod implicit o anumită distanţare între caracterele scrise consecutiv (prin operatorul show
) pe un acelaşi rând (dar în Fig.1, cele patru caractere au fost distanţate convenabil în mod explicit, prin translate
). Pentru PS, lăţimea caracterului este distanţa dintre O şi Ou (şi nu cea dintre Vl şi Vr).
Subliniem că—dependent de font şi de caracter—O se poate afla fie în stânga, fie în dreapta lui Vl, iar Ou se poate afla fie în dreapta, fie în stânga lui Vr. De exemplu, (v. Fig.1) în fontul Times-Italic, graficul lui "A" începe ceva mai la stânga originii (intrând puţin în zona graficului precedent pe linia curentă); în fontul URWGothic-BookOblique, graficul lui "g" se încheie puţin mai la dreapta originii graficului următor.
Iar Lb poate să taie sau să atingă graficul (şi implicit, Vl şi Vr), dar poate fi şi complet în afara acestuia (de exemplu în cazul unor accente, sau în cazul unor „cratime”).
Pentru un caracter oarecare, consemnăm codul şi numele acestuia, fontul din care provine, abscisele "x:" pentru Vl, Vr şi Ou (ordonate crescător), precum şi ordonatele "y:" ale capetelor verticalelor Vl şi Vr; precizăm că am „multiplicat” cu 1000 coordonatele respective, evitând astfel apariţia a câte două caractere suplimentare (scriind de exemplu "y: -36:591", în loc de "y: -0.36:0.591" cum ar fi fost „corect”).
Astfel, primul caracter reprezentat în Fig.1 are codul 103, numele "g" şi provine din fontul "Times-Roman"; notaţia "x: 28:470:500" ne spune că verticalele de încadrare au abscisele 28 şi 470, iar Ou are abscisa 500; notaţia "y: -218:460" ne indică ordonatele între care este cuprins graficul. Deducem imediat că boxa de încadrare a graficului acestui caracter are colţul din stânga-jos (28, -218) şi colţul din dreapta-sus (470, 460); în stânga şi în dreapta graficului caracterului sunt lăsate 28 şi respectiv, 500 - 470 = 30 de „puncte” (deci distanţa faţă de precedentul şi respectiv, următorul grafic de pe linia curentă este de cel puţin 28 şi respectiv, 30 de puncte tipografice).
Desigur, împărţind la 1000 şi înmulţind cu „mărimea fontului”, găsim şi coordonatele sau distanţele „reale”; de exemplu, dacă fontul respectiv a fost scalat la 24 de puncte, atunci lăţimea boxei de încadrare a caracterului respectiv este (470 - 28)*
0.024 = 10.608 puncte tipografice = 10.608/72 = 0.1473inch = 3.74mm; iar lăţimea caracterului este abscisa lui Ou, deci este 500*
0.024/72 = 0.166inch = 4.216mm (dar valorile „reale” depind puţin, până la urmă, şi de rezoluţia ecranului sau imprimantei care va produce în final caracterul).
În /usr/share/ghostscript/9.26/Resource/Font
găsim cele 35 de fişiere-font PS–2 „de bază” (v. PostScript_fonts), în versiunea gratuită produsă de firma URW++; în catalogul "Fontmap.GS" din Resource/Init
, găsim asocierile acestora cu fonturile PS originale (de la Adobe) – de exemplu, fişierul Font/NimbusRoman-Regular
corespunde fontului Times-Roman.
Fişierele-font găsite corespund formatului PS-Adobe-1
("Type 1") şi nu explicitează informaţii metrice pentru caracterele modelate (la o adică, acestea s-ar putea deduce dezasamblând secvenţele de cod care produc graficele caracterelor, încorporate într-o anumită criptare, în fişierele-font respective). Dar putem obţine fişierele metrice asociate "*.afm
", fie direct de la ArtifexSoftware, fie instalând pachetul fonts-urw-base35
de la Ubuntu.
Într-un fişier .afm
("Adobe Font Metric") găsim o secţiune de linii pe care se precizează pentru fiecare caracter din fontul respectiv, codul, numele, lăţimea caracterului şi două colţuri opuse ale boxei de încadrare a graficului; de exemplu, linia
C 103 ; WX 500 ; N g ; B 28 -218 470 460 ; % NimbusRoman-Regular (v. şi Fig.1)
corespunde caracterului cu numele "g", de cod 103
, având lăţimea 500
şi al cărui grafic este încadrat în boxa cu colţul stânga-jos (28, -218)
şi colţul dreapta-sus (470, 460)
. Coordonatele şi dimensiunile sunt referite faţă de scara de 1000 puncte tipografice.
Pentru caracterul ".notdef
" şi pentru caracterele necodificate (adică neînregistrate în tabloul iniţial "Encoding" din dicţionarul fontului), linia începe cu "C -1
"; de exemplu:
C 251 ; WX 500 ; N germandbls ; B 12 -9 468 683 ; % ultimul din tabloul "Encoding" C -1 ; WX 250 ; N .notdef ; B 125 0 125 0 ; % grafic vid (doar mută puţin originea) C -1 ; WX 444 ; N abreve ; B 37 -10 442 664 ; % pentru 'ă', în NimbusRoman-Regular
Este uşor de interpretat aceste informaţii în spiritul reprezentărilor şi notaţiilor introduse mai sus; de exemplu, pentru linia corespunzătoare caracterului "slash" în fontul Times-Italic:
C 47 ; WX 278 ; N slash ; B -65 -18 386 666 ; % NimbusRoman-Italic.afm
avem: Ou(278, 0), abscisa lui Vl este negativă (= -65), iar abscisa lui Vr (= 386) este mai mare ca a lui Ou – încât deducem că slash „intră” câte puţin (chiar „binişor”: 65 şi respectiv, 386-278=108 puncte tipografice) în zonele caracterelor între care este scris.
Mai departe vom schiţa modul în care putem folosi aceste linii din fişierele metrice .afm
, pentru a reprezenta caracterele precum în Fig.1.
Nu avem totuşi, un răspuns solid (afară de cel mai simplu, „la nimic”) pentru întrebarea firească: la ce ar folosi o astfel de reprezentare ? Este de presupus că programarea necesară este instructivă, implicând lămurirea anumitor aspecte legate de PS şi de fonturi; pe de altă parte, poate să ne amintim de începuturile tipografiei digitale, când asemenea reprezentări (la scară mare) erau folosite frecvent pentru a proba caracterul tocmai creat ("proof", sau "Hardcopy Proofs" în programul şi cartea de METAFONT ale lui Knuth), a-l compara cu celelalte şi a decide ce modificări ar mai fi încă de făcut asupra graficului pentru a-l integra sub toate aspectele fontului respectiv.
Dezvoltăm „pas cu pas” programul PS prchar.ps
(precizăm că unele aspecte implicate—de exemplu, „Schimbarea fontului”—au fost explicate deja, în părţile anterioare – v. [1]).
%! /FS {findfont exch scalefont} bind def % <24> </Courier> FS (v. „Partea I”) % setăm fonturi adecvate scrierii de numere şi respectiv, de nume /sz 8 def % mărimea de caracter pentru scrierea metricilor din AFM /_num {//sz /Courier FS setfont} bind def % pentru scrierea numerelor /_str {//sz /Helvetica-Narrow FS setfont} bind def % scrierea numelor /newl { % "newline" (mută punctul curent la începutul liniei următoare) currentpoint exch pop % x y | y x | y //sz sub 0 exch moveto % y-sz 0 | 0 y-8 | moveto } bind def % scrie număr de max. 4 cifre (<număr> s_num) /s_num {4 string cvs show} bind def /octch 1 string def % pentru conversia codului caracterului în "şir octal"
Am ales să scriem numerele (codul caracterului şi diversele coordonate) folosind fontul Courier, iar numele (de caracter, de font) folosind Helvetica-Narrow ("sans serif", condensat), fixând ca mărime valoarea indicată (8) în definiţia de constantă "/sz
"; precizăm că întâlnind ulterior "//sz
", interpretorul va înlocui imediat prin 8 (scutind la execuţie căutarea obişnuită, în dicţionarul procedurilor, a cheii "sz
").
Desigur, puteam defini mai general procedura "/newl
"; dar aici avem de scris numai rânduri de metrici, pentru care am prevăzut mărimea de caracter 8 – încât am coborât ordonata punctului curent cu 8.
Avem de scris (cu operatorul show
) numere de cel mult 4 cifre (putem avea de exemplu, Ou=1053); dar „număr” înseamnă ca de obicei „număr în baza 2” – încât în "/s_num
" am folosit operatorul PS cvs
, pentru a obţine secvenţa corespunzătoare de cifre zecimale (furnizată apoi ca obiect "string", lui show
).
Codul de caracter este şi el un număr (0..255) şi îl vom scrie ca atare, folosind s_num
. Dar, pe de altă parte, vom avea de folosit charpath
(v. eseu-PS.pdf) pentru a produce graficul caracterului; ori charpath
cere ca argument un "string", dar nu mai este vorba acum de şir de cifre zecimale, ci de un şir care conţine un singur element – anume, reprezentarea octală a codului caracterului (prefixată cu backslash). De exemplu, pentru caracterul de cod 103 – „şirul octal” este (\147)
(într-adevăr, 7+8*4+82*1 = 103) şi obţinem graficul prin (\147) true charpath
.
Din acest motiv am introdus mai sus definiţia /octch
, folosind-o mai încolo astfel:
//octch 0 C put % înscrie codul C ca "şir octal" în string-ul 'octch' //octch true charpath % produce graficul şirului
Mai departe, fixăm mărimea de redare a graficului caracterului (aici, 2 inch) şi prevedem o procedură prin care să putem obţine coordonatele „reale” din cele care iniţial se raportau la scara de 1000 de puncte tipografice:
/SZ 144 def % mărimea de redare a graficului caracterului (2 inch) /SZt {SZ 1000 div mul} bind def % transformă de la scara 1000 la cea "reală"
Dacă va vrea altfel, utilizatorul va putea redefini /SZ
în programul său, după ce a inclus cumva "prchar.ps"; de observat că dacă în definiţia /SZt
foloseam //SZ
în loc de SZ
, atunci ar fi trebuit redefinit şi /SZt
, nu numai /SZ
.
Vom modela procedura principală după următoarea schemă de lucru:
/chSketch { – Argumente: - numele fontului (ex.: /Times-Roman) - tablou [C WX (N) B] conform unei „linii metrice” din fişierul AFM - opţional: grafic cu 'stroke' (altfel, cu 'fill') – /wr_metrics: subprocedură de scriere a metricilor – dacă C=-1 (caracter necodificat): înscrie-l în "Encoding" (pe un loc „liber”) – setează fontul (după eventuala recodificare) la mărimea SZ – trasează (prin 'charpath') graficul caracterului (plin, sau -opţional- conturat) – trasează liniile Lb, Vl, Vr – fixează punctul curent dedesubtul graficului şi apelează 'wr-metrics' }
De exemplu, în „partea principală” a programului am putea utiliza chSketch
astfel:
36 600 translate /Times-Italic [47 278 (slash) -65 -18 386 666] chSketch 120 0 translate /Times-Roman [-1 444 (abreve) 37 -10 442 664] 1 chSketch
Prima invocare ar produce graficul şi metricile asociate pentru caracterul slash
din Times-Italic; graficul va fi „plin”, fiindcă pe stivă s-au transmis numai două argumente. A doua invocare ar produce graficul lui abreve
din Times-Roman (după ce acest caracter ar fi înscris în "Encoding") şi anume, sub forma de contur (dat fiind că acum este transmis şi un al treilea argument).
chSketch
pretinde să-i fie transmise iniţial (prin stiva operatorilor) fie două, fie trei anumite valori (precizăm că ne vom scuti de a verifica tipul şi corectitudinea acestora; dacă va fi cazul, interpretorul va furniza anumite mesaje de eroare). Dacă numărul de valori găsit la execuţie în stivă—dat de count
—este 3, atunci elimină din stivă al treilea argument (prin pop
) şi instituie „variabila” WO=true
; altfel, defineşte WO=false
:
/chSketch { % <font> <[C WX (N) B]> [<Opţional>: forţează 'stroke'] chSketch count 3 eq {pop /WO true def} {/WO false def} ifelse
În funcţie de valoarea găsită în WO
, graficul caracterului va fi conturat cu stroke
, sau va fi „umplut” prin fill
.
Preluăm şi diseminăm argumentele (optând aici pentru cea mai „directă” manieră):
/cwnb exch def % preia tabloul de 7 elemente [C WX (N) B] /Font exch def % preia numele fontului % Izolează componentele tabloului 'cwnb' /C cwnb 0 get def % Codul caracterului, sau -1 pentru „caracter necodificat” /N cwnb 2 get def % Numele caracterului /W cwnb 1 get def % lăţimea caracterului ("width") % coordonatele celor două colţuri ale boxei de încadrare 'B' /Lx cwnb 3 get def /Ly cwnb 4 get def /Ux cwnb 5 get def /Uy cwnb 6 get def % Adaptează lăţimea şi coordonatele la scara 'SZ' stabilită graficului /Ws W SZt def /Lxs Lx SZt def /Lys Ly SZt def /Uxs Ux SZt def /Uys Uy SZt def
Transformarea prin /SZt
a lăţimii şi coordonatelor ne va fi necesară pentru a trasa liniile Lb, Vl şi Vr pe graficul de mărime SZ
al caracterului (iar valorile respective iniţiale—la scara 1000—vor fi necesare pentru scrierea metricilor prin wr_metrics
).
Folosind _num
şi s_num
(şi după caz, forall
) scriem codul caracterului, abscisele Lx
, Ux
şi W
, precum şi ordonatele Ly
şi Uy
; în prealabil, prin subprocedura /xTick
, abscisele sunt ordonate crescător (reflectând astfel ordinea vizuală a verticalelor Vl şi Vr faţă de O(0,0) şi Ou(w,0)); iar folosind _str
, scriem numele caracterului, antetele "x:" şi "y:" şi respectiv, numele fontului (şi desigur, folosim newl
pentru a forţa scrierea pe rândul următor):
/wr_metrics { /xTick { % subprocedură pentru a ordona crescător abscisele Ux W lt {[Lx (:) Ux (:) W]} {[Lx (:) W (:) Ux]} ifelse } def _num C s_num % scrie codul şi numele caracterului _str (/) show N show % (ex.: 170/spade) newl (x: ) show _num xTick {s_num} forall % scrie abscisele (x: 113:629:753) newl _str (y: ) show _num [Ly (:) Uy] {s_num} forall % scrie ordonatele (y: -36:591) newl _str Font 30 string cvs show % scrie numele fontului (Symbol) } bind def
Bineînţeles că înainte de a instanţia wr_metrics
, va trebui să fixăm „punctul curent” (de unde să înceapă scrierea), undeva dedesubtul graficului.
Să mai observăm că dacă am viza caractere dintr-un acelaşi font (formulând un „catalog” al fontului), atunci s-ar cuveni să omitem scrierea la fiecare caracter, a numelui fontului.
Dacă numele preluat în variabila N
nu este înregistrat în tabloul "Encoding" din dicţionarul fontului Font
, atunci se copiază în stivă dicţionarul respectiv, se modifică pe copia respectivă tabloul "Encoding"—înscriind numele N
pe o poziţie ocupată iniţial de "/.notdef
", de exemplu la indexul 1—şi se salvează (sub un alt nume) fontul astfel modificat (după care, se activează acest font, la mărimea SZ
, prin selectfont
şi se redefineşte /C
conform indexului lui N
). Altfel—dacă C iniţial nu este -1—se setează Font
la mărimea SZ
, drept fontul curent:
C -1 eq { % pentru caracter necodificat iniţial (de exemplu, /abreve: 'ă') Font findfont dup length dict copy begin /Encoding Encoding 256 array copy def Encoding 1 N cvn put % înscrie N la indexul 1 în "Encoding" /bFnt currentdict definefont pop % salvează fontul astfel modificat end /C 1 def % redefineşte "/C" (codul 1, în loc de -1 cât era iniţial) /bFnt SZ selectfont % acum, caracterul s-ar scrie cu: (\001) show } {SZ Font FS setfont} ifelse
Precizăm că 'C -1 eq {...} {...} ifelse
' va asigura executarea unuia sau altuia dintre cele două blocuri {...}
, în funcţie de îndeplinirea sau nu, a condiţiei iniţiale 'C=-1
'.
"CharStrings" (care este un subdicţionar al dicţionarului fontului) asociază fiecărui nume de caracter câte o subrutină (care iniţial—în fişierul fontului—este criptată, ascunzând-o publicului) prin care se produce graficul caracterului, prin combinarea anumitor instrucţiuni moveto
, lineto
, curveto
(care trasează cubice Bézier) şi closepath
. În principiu, subrutina respectivă este executată numai la prima întâlnire a caracterului şi traseul rezultat este salvat în „memoria cache”, fiind apoi preluat de aici la următoarele apariţii ale caracterului.
Pentru o mică ilustrare intermediară, să alegem un caracter cât mai simplu ca formă grafică şi să producem traseul acestuia, folosind charpath
şi pathforall
:
%! ex_path.ps /Symb {/Symbol findfont 1000 scalefont} bind def % Symb /Encoding get == % 190: /arrowhorizex /str 4 string def % pentru 'cvs' (transformă în şir numere de max. 4 cifre) /prst { print % afişează şirul din vârful stivei round cvi % rotunjeşte şi reţine ca întreg, valoarea ajunsă în vârful stivei str cvs print % transformă în şir şi afişează } bind def /Prt {prst ( ) prst (\n) print} def % aplică 'prst' pe abscisă şi pe ordonată Symb setfont 0 0 moveto % originea graficului (\276) true charpath % 190 = 2*64 + 7*8 + 6 = (\276) {exch (move: ) Prt} % fără 'exch', în vârful stivei aveam ordonata (nu abscisa) {exch (line: ) Prt} {} % nu-i cazul de 'curveto' {(closepath) ==} pathforall % {...}{...}{...}{...} pathforall
vb@Home:~/20mai$ gs -q ex_path.ps move: -59 218 % -59 218 moveto line: 1040 218 % 1040 218 lineto line: 1040 273 % 1040 273 lineto line: -59 273 % -59 273 lineto line: -59 218 % -59 218 lineto (closepath) move: 1000 0 % 1000 0 moveto
Să nu ne cramponăm de faptul că valorile afişate diferă puţin—numai cu cel mult 1050-1040=10, adică cu max. o sutime de unitate—faţă de valorile din fişierul metric "StandardSymbolsPS.afm", reflectate mai sus pe imaginea produsă prin chSketch
; pe parcurs, până la tipărirea sau afişarea graficului, intervin şi se cumulează „erori de aproximare” şi anumite ajustări dictate de rezoluţia dispozitivului de ieşire.
Traseul rezultat astfel pentru caracterul de cod 190 "/arrowhorizex" este un dreptunghi cu laturile paralele axelor, creat unind succesiv colţurile prin lineto
; el poate fi produs ca atare prin stroke
, sau poate fi umplut cu nuanţa curentă de culoare prin fill
(desigur, în prealabil trebuie să rescalăm fontul, faţă de mărimea 1000 – folosită mai sus în ideea de a regăsi cumva valorile metrice standard, din fişierul AFM).
Revenind la programul nostru, producem prin charpath
graficul caracterului de cod C
, prevăzând fill
(şi o anumită nuanţă de gri) pentru cazul când WO
este false
:
0 0 moveto //octch 0 C put //octch true charpath WO false eq {0.5 setgray fill} if
În cazul când WO
este true
, conturul caracterului va fi trasat (prin stroke
) odată cu liniile Lb, Vl şi Vr:
0 0 moveto Ws 0 rlineto % uneşte originea caracterului cu originea celui care i-ar urma Lxs dup Lys moveto Uys lineto % verticala de mărginire în stânga Uxs dup Lys moveto Uys lineto % verticala de mărginire în dreapta 0.05 setlinewidth 0.1 setgray stroke
Rămâne să adăugăm metricile caracterului; dacă limita inferioară a boxei de încadrare este pozitivă (cum a fost mai sus, cazul caracterului /arrowhorizex
), atunci redefinim Lys
fixându-i ca valoare 0; apoi, fixăm „punctul curent” ceva mai jos de valoarea Lys
(şi la începutul rândului) şi invocăm /wr_metrics
:
Lys 0 gt {/Lys 0 def} if % pentru a scrie metricile SUB linia de bază 0 Lys 10 sub moveto wr_metrics % scrie metricile caracterului } bind def % încheie definiţia procedurii /chSketch
Cu aceasta, definiţia procedurii chSketch
este completă. Cu totul (inclusiv linii albe şi comentarii), fişierul "prchar.ps" măsoară 3175 octeţi (80 de linii).
Putem adăuga în fişier un „program principal”, cum am exemplificat deja mai înainte; însă ar fi de preferat să închidem acum fişierul "prchar.ps", urmând să-l „includem” (prin operatorul run
) în acele programe PS în care am vrea să folosim chSketch
:
%! % fişier PS nou, "main.ps" (prchar.ps) run % "include" fişierul 'prchar.ps' /SZ 108 def % redefineşte eventual, /SZ (mărimea graficelor de caractere) 36 680 translate % originea pentru un prim rând de caractere gsave 2 2 scale % eventual măreşte (pentru rândul curent), graficul şi textul asociat /C059-Roman [-1 556 (abreve) 44 -15 543 692] chSketch % /NewCenturySchlbk-Roman 100 0 translate % fixează originea următorului caracter de pe rând /P052-Roman [-1 500 (abreve) 32 -12 471 664] chSketch % /Palatino-Roman 90 0 translate /NimbusMonoPS-Regular [-1 600 (abreve) 67 -16 547 618] 1 chSketch % /Courier grestore
Lansând gs main.ps
obţinem pe ecran pagina redată mai sus, conţinând rezultatele celor trei invocări chSketch
din "main.ps".
Însă includerea unui fişier PS într-un altul este o operaţie care depinde de context şi este de regulă, obstrucţionată (pentru a evita execuţia, în cazul când programul de „inclus” ar fi unul rău-intenţionat); de exemplu, evince main.ps
nu ajunge să redea pagina respectivă (produce eroarea "invalidfileaccess"). Putem totuşi să folosim opţiunea "-dNOSAFER
", pentru a permite execuţia unui program „străin”, iar ps2pdf -dNOSAFER main.ps
conduce la formatul PDF al fişierului, "main.pdf
" (care apoi, poate fi accesat şi din evince
, de exemplu).
În „partea a IV-a” ne vom ocupa totuşi de catalogarea caracterelor dintr-un acelaşi font, rescriind în acest scop chSketch
(de exemplu, nu ar mai fi necesar ca argument al procedurii, numele fontului; recodificarea ar trebui să vizeze un grup de caractere cu "C=-1", în loc de câte un singur caracter).
vezi Cărţile mele (de programare)