[1] partea a III-a
Ne propunem să folosim prchar.ps din [1] pentru a obţine „catalogul metric” al unui font, reflectând graficele şi metricile caracterelor conţinute; nu va mai fi necesar să scriem la fiecare caracter, numele fontului – încât comentăm (prefixând cu %
) ultima linie din definiţia /wr_metrics
.
Renunţăm la ideea avansată în [1], de a suplimenta cu o procedură de recodificare pe grupuri de caractere (de exemplu câte 128, ca în programul GS lib/prfont.ps
de la care am plecat în „partea I”), în loc de câte un singur caracter (practic, viteza de execuţie nu are de suferit); în [1] am adus pe rând caracterele necodificate iniţial, pe o aceeaşi poziţie din tabloul "Encoding" (la indexul 1, ocupat iniţial cu "./notdef
").
Dar nu are nici un rost (şi acum vom omite) scrierea codului 1 sub fiecare dintre aceste caractere; modificăm deci, linia din /wr_metrics
pe care formulam scrierea codului de caracter (scriem numai codurile celor aflate în tabloul "Encoding" iniţial):
_num C dup 1 ne {s_num}{pop} ifelse % scrie codul (dacă ≠1) şi numele caracterului
Pentru verificare şi (mai ales) pentru a aminti lucrurile din [1], reproducem aici reprezentările prin procedura /chSketch
din prchar.ps pentru caracterul cu numele /a14
şi codul 45 din fontul "ZapfDingbats" (dar acum nu mai scriem numele fontului) şi respectiv, caracterul /scommaaccent
, necodificat (deci nu mai scriem codul), din fontul "NewCenturySchlbk-Roman", în cele două variante, grafic-contur şi grafic-plin:
Afară de aceste două mici modificări în cadrul procedurii /wr_metrics
, nu este necesar să mai schimbăm ceva în prchar.ps.
Mai întâi, să extragem „liniile metrice” din fişierele "*.afm
" existente în pachetul (v. [1]) urw-base35
. Constituim un subdirector afmT1/
în care copiem fişierele *.afm
şi pregătim un fişier executabil "metrics.sh
" prin care să extragem liniile metrice:
vb@Home:~/20-iun$ mkdir afmT1 vb@Home:~/20-iun$ cp /usr/share/fonts/type1/urw-base35/*.afm afmT1/ vb@Home:~/20-iun$ touch metrics.sh ; chmod a+x metrics.sh
Extragem liniile metrice din fişierele "*.afm", în fişierele "*.met", folosind awk
prin:
#!/bin/bash "metrics.sh" cd afmT1 for fl in $(ls) # pentru toate fişierele din afmT1/ do awk '/^C / {print $2" "$5" ("$8") "$11" "$12" "$13" "$14}' $fl > ${fl%.afm}.met done cd ..
Aici, awk
selectează liniile din fişierul ".afm" curent care încep cu "C
" (şablonul acestora fiind /^C /
) şi scrie (cu redirectare într-un fişier de acelaşi nume, dar cu extensia ".met") pentru fiecare linie, câmpurile care ne interesează, separate prin câte un spaţiu; de exemplu, linia din fişierul ".afm" (pe care awk
vede 14 câmpuri, separate prin spaţiu):
C 33 ; WX 333 ; N exclam ; B 3 -15 336 737 ;
devine—ambalând al 8-lea câmp cu paranteze, prin " ("$8" )"
—în fişierul ".met" asociat:
33 333 (exclam) 3 -15 336 737
adică (dacă vom încadra cu paranteze pătrate) exact argumentul cerut de procedura chSketch
din prchar.ps, pentru a scrie graficul şi metricile caracterului respectiv. Vom putea obţine „catalogul” fontului, iterând chSketch
pe liniile fişierului ".met".
Ne va fi util un tablou PS conţinând numele acestor fişiere (din care vom putea separa numele de fonturi) şi unul care să conţină numărul de linii din fiecare; le putem obţine uşor printr-un program Bash:
#!/bin/bash cd afmT1 echo "%!" echo -n "/PS35 [" # /PS35 [ ... ] for fl in $(ls *.met) do echo -n "($fl) " # numele fişierului ".met" curent done echo "] def" echo -n "/PS35lg [" # /PS35lg [ ... ] for fl in $(ls *.met) do echo -n `wc -l < $fl`" " # nr. de linii din fişierul ".met" curent done echo "] def" cd ..
Redirectăm ieşirea acestui program pe fişierul PSfnt.ps, având în final:
%!PS "PSfnt.ps" /PS35 [(C059-BdIta.met) (C059-Bold.met) (C059-Italic.met) (C059-Roman.met) (D050000L.met) (NimbusMonoPS-Bold.met) (NimbusMonoPS-BoldItalic.met) (NimbusMonoPS-Italic.met) (NimbusMonoPS-Regular.met) (NimbusRoman-Bold.met) (NimbusRoman-BoldItalic.met) (NimbusRoman-Italic.met) (NimbusRoman-Regular.met) (NimbusSans-Bold.met) (NimbusSans-BoldItalic.met) (NimbusSans-Italic.met) (NimbusSansNarrow-BoldOblique.met) (NimbusSansNarrow-Bold.met) (NimbusSansNarrow-Oblique.met) (NimbusSansNarrow-Regular.met) (NimbusSans-Regular.met) (P052-Bold.met) (P052-BoldItalic.met) (P052-Italic.met) (P052-Roman.met) (StandardSymbolsPS.met) (URWBookman-Demi.met) (URWBookman-DemiItalic.met) (URWBookman-Light.met) (URWBookman-LightItalic.met) (URWGothic-Book.met) (URWGothic-BookOblique.met) (URWGothic-Demi.met) (URWGothic-DemiOblique.met) (Z003-MediumItalic.met)] def /PS35lg [853 853 853 853 202 855 855 855 855 855 855 855 854 854 854 854 855 855 855 855 854 854 854 854 854 190 853 853 853 853 853 853 853 853 853 ] def /Join { % str1 str2 Join str1str2 (concatenează două string-uri) 2 copy length exch length dup 3 1 roll add % însumează lungimile şirurilor string dup dup 5 3 roll exch putinterval % înscrie în şirul comun, al doilea şir 3 -1 roll 0 exch putinterval % înscrie la începutul şirului comun, primul şir } bind def
Faţă de ceea ce fusese scris de către programul Bash de mai sus, am adăugat definiţia /Join
(procedură generală pentru concatenarea a două şiruri PS); vom utiliza /Join
pentru a alipi numele directorului afmT1/
, cu numele de fişier din tabloul PS35
– ajungând apoi să constituim un obiect PS "file
" prin care să putem citi (din directorul „părinte” al lui /afmT1/
) liniile fişierului respectiv.
Majoritatea fişierelor aveau câte 855 de linii (dintre care, una singură reprezintă caracterul "./notdef
"); dar cam în fiecare, apăreau două-trei linii care corespund unor caractere „nule” (boxa de încadrare indicată în finalul liniei fiind "0 0 0 0
", la fel ca pentru "./notdef
") – şi am decis să le elimin din fişierele "*.met", rezultând în /PS35lg
lungimile (ceva mai variate) redate mai sus.
Alegem ca suport formatul A4 (8.3×11.7 inch) şi ca mărime a graficelor de caracter SZ
=54 (în prchar.ps foloseam SZ
=144 – prea mare acum, când vizăm toate caracterele); pe un rând pot încăpea suficient de bine 7 grafice (cu o anumită distanţare fixată, între ele) şi alegem să scriem pe pagină 7 astfel de rânduri. Deci pe o pagină a catalogului vor fi reprezentate 49 de caractere; va trebui să avem în vedere cazul când ultima pagină este incompletă şi cazul când ultimul rând al ei are mai puţin de 7 caractere.
În continuare vom desfăşura „pas cu pas” programul catalog.ps, evidenţiind pe cât se poate (sau se cuvine) logica lucrurilor şi diverse aspecte de programare PS.
„Includem” prin operatorul run
, fişierele PS PSfnt.ps şi prchar.ps şi demarăm procedura /buildCat
:
%!PS "catalog.ps" (PSfnt.ps) run % tablourile /PS35, /PS35lg; procedura /Join (prchar.ps) run % /FS, /SZ, /chSketch /SZ 54 def % redefineşte mărimea graficelor (în 'prchar.ps': 144) /buildCat { 35 mod % <idf> buildCat sau <Opţional> <idf> buildCat /idf exch def % idf = 0..34 indică un font din PS35 count 1 eq % Cu <Opţional> se alege 'stroke' (Fără: 'fill'; v. prchar.ps) {pop /WO true def} {/WO false def} ifelse
Prin /idf
, buildCat
va prelua din vârful stivei operanzilor argumentul său principal: un index 0..34 (pentru un element al tabloului PS35
) şi urmează să formuleze catalogul fontului corespunzător acelui index, folosind în acest scop chSketch
din prchar.ps.
Dar chSketch
produce graficul caracterului fie „plin” (cu fill
), fie prin contur (cu stroke
); pentru a viza ambele variante, am prevăzut pentru buildCat
un al doilea argument, „opţional” şi am introdus variabila /WO
– cu valoarea true
sau false
, după cum numărul de valori rămase în stivă (dat de count
) după preluarea argumentului „principal” este sau nu, 1. O invocare ca 3 buildCat
ar seta WO
pe false
, producând grafice „pline”; în schimb, 1 3 buildCat
(cu două argumente) ar produce contururile graficelor caracterelor (din fontul indicat pe a patra intrare din tabloul PS35
).
Obs. Această idee (probabil, „personală”) de montare a unui argument opţional are totuşi un anumit defect (dar care este uşor de ocolit). Dacă vrem grafice pline (invocând buildCat
cu un singur argument), iar apoi vrem să invocăm o altă procedură <parg> Proc
care primeşte la rândul ei un argument de pe stivă – atunci ar fi greşită înlănţuirea directă a invocărilor (cum se obişnuieşte în PS): <parg> 3 buildCat Proc
(greşit, fiindcă buildCat
ar consuma cele două argumente din stivă – producând contururi şi nu cum voiam, grafice pline – şi apoi, Proc
nu şi-ar mai găsi argumentul). Corectarea este evidentă – argumentele celor două proceduri trebuie separate: 3 buildCat <parg> Proc
.
Obs. Bineînţeles că este posibilă şi altă „corectură”, direct în buildCat
: dacă după ce se preia primul argument, count
este nenul – atunci preluăm efectiv şi următoarea valoare din stivă şi testăm dacă aceasta este aceea pe care am fixat-o în prealabil ca ţinând numai de buildCat
(cu grija de a o plasa înapoi pe stivă în cazul contrar şi cu defectul că ar trebui să explicităm cumva şi utilizatorului, acea valoare specială).
Mai departe, înfiinţăm rând pe rând o serie de proceduri „ajutătoare”; unele dintre ele (chiar toate!) ar putea fi localizate (scrise) în afara lui buildCat
(eventual, cu o mică dar subtilă modificare, semnalată mai jos), dar am preferat să le plasăm în interiorul acesteia fiindcă angajează idf
sau WO
(„locale” lui buildCat
), sau se angajează una într-o alta.
Prin /fMet
şi /nrChar
vom obţine (folosind get
) numele fişierului vizat în tabloul PS35
prin indexul idf
şi respectiv (din tabloul PS35lg
), numărul de linii ale acestuia:
/fMet PS35 idf get def % numele fişierului de metrici (*.met) /nrChar PS35lg idf get def % nr. de linii din fişier
Obs. Puteam să scriem aceste două proceduri şi în afara procedurii buildCat
—de exemplu, chiar în fişierul PSfnt.ps
care conţine tablourile implicate de ele—dar cu o „mică” modificare: trebuie să încadrăm cu acolade, de exemplu /fMet { PS35 idf get } def
– astfel, întâlnind "{...} def
", interpretorul doar va salva undeva definiţia respectivă, urmând ca idf
să fie căutat doar la momentul execuţiei programului (în timp ce fără acolade, s-ar semnala eroarea "undefined in idf").
Prin /FntName
, din şirul adus pe stivă de „apelul” fMet
obţinem numele fontului; de exemplu, pentru şirul (P052-Roman.met)
căutarea (.) search
ne lasă în vârful stivei true
(semnalând „succes”), iar pe nivelele următoare ale stivei ne lasă subşirul (P052-Roman)
(care precede pe cel căutat, (.)
), apoi însuşi subşirul căutat şi în sfârşit, subşirul rămas (met)
; folosim roll
şi pop
pentru a schimba ordinea elementelor din stivă şi a elimina pe cele trei de care nu avem nevoie, iar pe şirul rămas în stivă (P052-Roman)
aplicăm operatorul de conversie la „nume PS” cvn
– obţinând numele fontului, /P052-Roman
:
/FntName {fMet (.) search pop 3 1 roll pop pop cvn} def % extrage din numele fişierului, numele fontului
Obs. De obicei se foloseşte {...} bind def
; întâlnind bind
, interpretorul va „înlocui” operatorii existenţi în corpul procedurii prin „pointeri” la codurile executabile asociate lor, scutind căutarea ulterioară a acestora. Dar este suficient să specificăm bind
în finalul procedurii buildCat
– fiindcă bind
este aplicat recursiv, tuturor sub-procedurilor.
Cu /FL
ne punem la dispoziţie un obiect PS file
, prin care să putem citi liniile din fişierele "*.met"; trebuie să ţinem seama de faptul că aceste fişiere sunt în subdirectorul afmT1/
al directorului care conţine programul catalog.ps, folosind /Join
(din PSfnt.ps) pentru a face alipirea necesară:
/FL (afmT1/) fMet Join (r) file def
Prin /AFM
obţinem un tablou cu cele 7 valori de pe linia curent citită prin /FL
, din fişier; token
interpretează câte un grup de caractere citite rând pe rând din fişier, ca reprezentând una sau alta dintre entităţile lexicale din PS (rezultând în cazul de faţă, 6 numere şi un string), iar astore
înglobează valorile respective într-un tablou PS:
/AFM {7 {FL token pop} repeat 7 array astore} def
Subprocedurile descrise mai sus (/fMet
, /nrChar
, /FntName
, /FL
şi /AFM
– denumite mai mult sau mai puţin inspirat) ţin toate, de "input" (introducerea şi adaptarea datelor în program); următoarele proceduri ţin de "output", servind pentru formularea şi scrierea rândurilor şi paginilor.
Paginile catalogului sunt formate din câte 7 rânduri, exceptând eventual ultima pagină; fiecare rând este format din 7 „grafice” (exceptând eventual, ultimul rând al catalogului) – anume, graficele de caracter şi înscrisurile adăugate fiecăruia prin procedura /chSketch
din prchar.ps.
Să ne amintim că chSketch
are ca argumente numele fontului (obţinut mai sus prin /FntName
) şi „tabloul metric” al caracterului (dat acum de /AFM
) şi are un „argument opţional”, a cărui prezenţă (indicată prin /WO
) are ca efect producerea de contururi, în loc de grafice „pline”. Formulăm întâi o procedură ajutătoare, pentru a disocia între cele două cazuri reflectate de /WO
:
/stk_fill {WO {FntName AFM 1 chSketch 75 0 translate} % grafice cu 'stroke' {FntName AFM chSketch 75 0 translate} % grafice cu 'fill' ifelse} def % (în funcţie de prezenţa argumentului <Opţional>)
Să observăm că 75 0 translate
deplasează originea de la care chSketch
tocmai a produs graficul, cu 75 de puncte tipografice spre dreapta; altfel spus, pentru fiecare grafic se rezervă pe orizontală exact 75 de puncte – rezultând implicit, alinierea pe verticală (în cadrul paginii curente) a graficelor (desigur… nu ştiu de ce n-am ales 72, adică exact un inch; un rând de grafice ar fi măsurat atunci exact 7 inch, fiind ceva mai uşor de imaginat –fără calcule suplimentare– pe formatul A4).
Obs. Este drept că numele fontului (care aici este constant) se transmite procedurii /chSketch
de fiecare dată când va fi invocată; dar acest defect este de natură „teoretică” (practic, viteza de execuţie nu are de suferit) şi n-am vrut să „repar” /chSketch
.
Prin /ROW
se produce un rând complet, repetând de 7 ori stk_fill
:
/ROW {7 {stk_fill} repeat} def
Începem fiecare pagină la ordonata TOP
=800 (faţă de marginea de jos); rezervăm primul rând pentru ceva titlu sau antet de pagină. /PAG
primeşte de pe stivă numărul de rânduri care trebuie scrise pe pagină (va fi 7, exceptând totuşi ultima pagină), coboară TOP
cu câte 100 de puncte pentru fiecare nou rând şi scrie prin /ROW
rândul curent:
/TOP 800 def % ordonata primului rând (posibil vid) de pe pagină /PAG { /nrp exch def nrp { /TOP TOP 100 sub def % ordonata rândului următor al paginii gsave 50 TOP translate ROW % scrie rândul curent de 7 grafice grestore } repeat } def
Obs. 50 TOP translate ROW
înseamnă că rândul curent începe la punctul S(50, TOP
_curent); dacă n-am fi ambalat cu gsave
şi grestore
(prin care se salvează şi se reconstituie „contextul grafic” iniţial, inclusiv originea curentă a sistemului de coordonate), atunci S ar fi fost raportat la punctul lăsat drept „punct curent” de execuţia lui ROW
- adică la capătul din dreapta al rândului curent, încât fiecare nou rând ar fi fost scris cu 50 puncte mai la dreapta sfârşitului celui precedent (şi dedesubt, cu câte cele 100 de puncte cu care este coborât TOP
la fiecare iteraţie).
Desigur, ar fi fost şi alte posibilităţi (mai complicate decât folosind direct gsave
şi grestore
) pentru a separa între ele translaţiile orizontale aplicate în ROW
, de cele verticale din PAG
.
Încheiem seria procedurilor ajutătoare calculând numărul de pagini „întregi”, câte rânduri are ultima pagină (zero dacă este „completă”), câte rânduri complete are ultima pagină (dacă este incompletă) şi câte grafice are ultimul rând din catalog (dacă acesta este incomplet):
/nPag {nrChar 49 idiv} def % pagini "întregi" (7x7 grafice) /rPag {nrChar 49 mod} def % rânduri pe ultima pagină (dacă este incompletă) /rPagl {rPag 7 idiv} def % rânduri "întregi" pe ultima pagină (incompletă) /rPgl {rPag 7 mod} def % nenul dacă ultimul rând are sub 7 grafice
Obs. Cu div
în loc de idiv
trebuia să aplicăm şi cvi
, pentru a avea câtul întreg.
Începem „partea executivă” a procedurii buildCat
. Întâi înscriem un titlu (numele fontului catalogat şi numărul de caractere); ambalăm scrierea cu gsave
şi grestore
, fiindcă folosim alte fonturi decât cel catalogat şi în plus, folosim translate
:
gsave 200 TOP translate 0 0 moveto % rândul de început al primei pagini 18 /Times-Italic FS setfont % fontul cu care se scrie titlul FntName 30 string cvs show % scrie numele fontului 12 /Times-Roman FS setfont % pentru a specifica şi numărul de caractere ( \() show nrChar 4 string cvs show ( caractere\)) show grestore
Scriem câte o pagină „completă” (cu 7 rânduri de grafice) invocând PAG
, o ejectăm prin operatorul showpage
, redefinim TOP
=800 (pentru următoarea pagină completă) şi repetăm de câte ori am obţinut mai sus în nPag
:
nPag {7 PAG showpage /TOP 800 def} repeat
Dacă rPagl
este mai mare ca zero, atunci ştim ca ultima pagină a catalogului este incompletă; scriem rândurile „întregi” ale acesteia:
rPagl {/TOP TOP 100 sub def gsave 50 TOP translate ROW grestore} repeat
Dacă rPgl
este mai mare ca zero, înseamnă că ultimul rând al catalogului este incomplet; îl scriem repetând stk_fill
de rPgl
ori, apoi ejectăm şi ultima pagină:
50 TOP 100 sub translate rPgl {stk_fill} repeat showpage % ejectează ultima pagină } bind def % Încheie buildCat ('bind' este aplicat recursiv subprocedurilor)
Cu aceasta, definiţia procedurii buildCat
(pe mai puţin de 50 de linii de program) este completă; în principiu, putem închide şi fişierul catalog.ps care o conţine.
Cea mai banală exploatare a procedurii builCat
constă în adăugarea dedesubtul ei a unui „program principal” care să o invoce, ca de exemplu:
% Exemplu de folosire directă (gs catalog.ps): 3 buildCat % catalogul fontului 'C059-Roman' (/NewCenturySchlbk-Roman), cu 'fill' 1 24 buildCat % apoi, 'P052-Roman' (/Palatino-Roman), cu 'stroke' (contururi)
Executând programul de sub interpretorul Ghostscript (prin gs catalog.ps
), obţinem rând pe rând cele câte 18 pagini ale cataloagelor celor două fonturi indicate. Dar acest procedeu direct are în mod inerent un neajuns: după ce se redă pagina curentă (în fereastra grafică deschisă de la bun început de către GS), se afişează la consolă mesajul ">>showpage, press <return> to continue<<" şi apăsând tasta indicată – pagina precedentă „dispare”, fiind înlocuită cu următoarea pagină.
Eliminând „programul principal” de mai sus, putem obţine într-un fişier PS întregul catalog, folosind opţiunea GS -sDEVICE=ps2write
şi folosind opţiunea "-c
" pentru a invoca buildCat
:
gs -dNOPAUSE -dBATCH -sDEVICE=ps2write -sOutputFile=cat_3.ps catalog.ps \ -c 3 buildCat quit
Documentul "cat_3.ps
" rezultat astfel poate fi deschis de exemplu, din evince
(obişnuitul "Document Viewer", în Ubuntu-Linux) şi conţine toate cele 18 pagini ale celui de-al patrulea font înregistrat în tabloul PS35
.
Este drept că fişierul "cat_3.ps" este cam mare (peste 1.1MB
); dar îl putem prelucra mai departe, nu numai reducând dimensiunea de peste 3 ori, dar şi aducându-l la un format de calitate superioară – prin ps2pdf cat_3.ps
. Rezultă fişierul "cat_3.pdf
" (în format PDF, măsurând sub 290kB
), pe care îl şi redăm aici:
Pentru încă o exemplificare (acum în ambele variante – contururi, respectiv grafice pline), alegem a cincea intrare din tabloul PS35
(fontul corespunzător – numit în GS "ZapfDingbats" – având numai 202 caractere, cum vedem în tabloul PS35lg
):
gs -q -dNOPAUSE -dBATCH -sDEVICE=ps2write -sOutputFile=ZapfDingbats.ps catalog.ps \ -c 1 4 buildCat 4 buildCat quit ps2pdf ZapfDingbats.ps
Fişierul PDF rezultat (măsurând sub 160kB
) cataloghează caracterele fontului indicat, odată (pe primele 5 pagini) cu grafice conturate şi apoi, cu grafice pline.
Putem obţine desigur (chit că nu prea are vreun sens) şi un fişier PDF conţinând cataloagele tuturor celor 35 de fonturi PS de bază: adăugăm în catalog.ps definiţia unui tablou al indecşilor 0..34,
/IDX [0 1 34 {} for] bind def
şi folosim comenzile de mai sus, dar cu "-c IDX {buildCat} forall quit
" (şi cu -sOutputFile=all_cat.ps
, să zicem); fişierul final "all_cat.pdf" se obţine în aproape un minut şi are 603 pagini (măsurând 8.5MB
).
Nu este cazul să ne facem griji privitor la corectitudinea redării graficelor caracterelor din fonturile respective (oare nu cumva, graficul vreunuia nu a putut fi redat – fontul respectiv nefiind încorporat în fişierul PDF – şi a fost înlocuit cu un „grafic vid” ?); graficele respective au fost produse prin charpath
(din prchar.ps), încât nu a fost necesară „înglobarea” în PDF a fonturilor de care ţin caracterele respective (inspectând cu pdffonts
documentul final, se poate constata că niciunul dintre fonturile catalogate nu nu este menţionat, ca încorporat sau nu, în PDF).
În „partea a V-a” vom formula un catalog mai simplu – folosind nu charpath
, ci glyphshow
– şi vom vedea că va fi nevoie în acest caz, să forţăm încorporarea în PDF a fontului catalogat.
vezi Cărţile mele (de programare)