[1] partea a IV-a
Vom constitui un program "glyphCat.ps", prin care să tipărim glifele şi numele de caracter din unul sau altul dintre fonturile PS de bază ("Type 1"); ne interesează acum numai glifele (nu şi metricile asociate şi nici contururile componente, ca în [1]), încât pentru a scrie caracterele după numele lor, vom folosi glyphshow
.
Intenţionăm să formulăm pagina fixând un prim rând şi coborând succesiv ordonata acestuia pentru rândurile următoare, până ce se ajunge la baza paginii (ceva mai flexibil decât procedam în [1], unde fixam din start câte rânduri să scriem pe pagină); vom scrie câte un rând de glife (cu mărime de font şi distanţare potrivite), adăugându-i imediat dedesubt, un rând conţinând în aceeaşi ordine numele acestora (scrise cu un font stabilit în prealabil), separate prin spaţiu.
Formulăm întâi obişnuitele „proceduri ajutătoare” (de setat fonturi, de scris un rând de glife, de trecere pe rândul următor, pentru scrierea numelor, etc.); unele dintre acestea ar putea fi „importate” din alte programe PS.
Pentru a facilita căutarea în catalog, pare firesc să aşezăm glifele în ordinea alfabetică a numelor de caracter. O procedură de ordonare ar avea o utilitate generală, încât se cuvine să o tratăm într-un fişier separat, bbsort.ps; este drept că am putea să adoptăm una existentă deja (de exemplu, în GS lib/prfont.ps
, de la care am plecat în „partea I”, găsim o procedură /sort
foarte eficientă), dar preferăm să modelăm direct o metodă simplă de ordonare (v. Bubble_sort, pentru un pseudocod).
Fiindcă operatorii PS gt
sau lt
servesc pentru a compara numere sau string-uri (nu şi elemente de tip PS "name"), va trebui să folosim cvs
pentru a converti numele de caracter în string (de exemplu, din numele /Abreve
obţinem string-ul (Abreve)
). Vrem să ordonăm alfabetic indiferent de tipul primei litere (mare, sau mică) din numele de caracter; internalizăm în acest scop procedura /to_lower
(prin care, când codul primei litere este sub 97, i se adună 32 – obţinând litera mică omonimă).
Pentru a interschimba între ele două elemente din tabloul supus ordonării, angajăm stiva operanzilor drept intermediar şi folosim put
:
%! bbsort.ps /sort_cvs { % <Listă> sort_cvs (ordonează <Listă> după şirurile asociate prin 'cvs') /Lst exch def /nr Lst length def /str1 20 string def % pentru a converti valori din Lst la PS_string /str2 20 string def % (şi a le putea compara alfabetic) /to_lower { % (Abreve) --> (abreve) dup dup 0 get dup 97 lt {32 add 0 exch put} {pop pop} ifelse } def { % loop (repetă până când indexul ultimului "swap" devine 1) /top 0 def % iniţializează indexul ultimului "swap" /nr nr 1 sub def % compară elemente vecine, până la indexul ultimului "swap" 1 1 nr { % for i1:=1 to nr /i1 exch def /i2 i1 1 sub def % i2 := i1 - 1 Lst i1 get str1 cvs to_lower % valorile Lst[i1] şi Lst[i2], Lst i2 get str2 cvs to_lower % convertite la câte un string lt { % "swap" Lst[i1] <--> Lst[i2], dacă şirurile nu sunt în ordine Lst i1 Lst i2 get % Lst i1 Lst[i2] (pe stivă) Lst i2 Lst i1 get % Lst i2 Lst[i1] (adaugă în stivă) put % pune Lst[i1] din vârful stivei, pe locul i2 din Lst put % pune Lst[i2] salvat în stivă, pe locul i1 din Lst /top i1 def % reţine indexul acestui ultim "swap" } if } for /nr top def % se va repeta numai până la indexul ultimului "swap" nr 1 le {exit} if % încheie dacă s-a făcut un singur "swap" } loop } bind def
sort_cvs
va putea fi aplicată unui tablou cu elemente de orice tip:
vb@Home:~/20-iun$ gs -q GS> (bbsort.ps) run GS> /myar [/sdf /Sdf1 (Abc) 123 1111 /acd /.notdef (Bcd) /b /Q] def GS> myar sort_cvs GS> myar == [/.notdef 1111 123 (Abc) /acd /b (Bcd) /Q /sdf /Sdf1]
Obs. Lst
, nr
, to_lower
, etc. introduse mai sus în sort_cvs
, sunt „variabile globale” şi este posibilă suprapunerea (nedorită, în general) cu variabile ale fişierului PS în care am importa bbsort.ps; puteam evita aceasta dacă le încorporam într-un dicţionar:
sort_cvs { 8 dict begin /Lst exch def /nr ... /str1 ... /top ... end } bind def
Toate cele 8 variabile deveneau astfel, „variabile locale” procedurii.
De obicei, la început se specifică fişierele de importat (prin run
), o manieră sintetică de accesare şi scalare a fişierelor-font, precum şi fonturile care vor fi utilizate:
%! glyphCat.ps (bbsort.ps) run % importă /sort_cvs (ordonează alfabetic un tablou) /fnd_scl {findfont exch scalefont} bind def % <line-height> </FontName> fnd_scl /fontN {11 /Times-Italic fnd_scl} def % un font obişnuit, pentru numele de caracter /fontT {16 /Times-Roman fnd_scl} def % fontul titlului catalogului
La sfârşit vom specifica procedura principală; dar aceasta trebuie imaginată chiar acum, pentru a ne da seama ce sub-proceduri şi variabile ar fi de pus la dispoziţia ei. Imaginăm obţinerea catalogului glifelor unui font prin: <nume_font> glyphList
; primind de pe stivă numele fontului, procedura /glyphList
(pe care urmează să o definim la sfârşit) va putea seta fontul respectiv (folosind /fnt_scl
) şi va putea accesa din dicţionarul acestuia subdicţionarul "CharStrings", ale cărui chei sunt numele glifelor.
Următoarea procedură presupune că numele glifelor au fost scoase pe stivă într-un tablou şi salvează acest tablou în /keys
:
/storeNames {/keys exch def} bind def % tabloul cheilor din /CharStrings
Putem formula imediat (folosind /fontT
şi keys length
) o procedură pentru scrierea unui titlu, într-o poziţie fixată din exterior (îl vom scrie numai pe prima pagină):
/title { % titlu (pe prima pagină) gsave % salvează contextul (fontul curent şi "currentpoint") currentfont /FontName get % numele fontului catalogat fontT setfont % comută fontul curent pe Times-Roman (16) 30 string cvs show ( \() show % scrie numele fontului catalogat keys length 4 string cvs show % scrie numărul de caractere ( caractere\)) show grestore % revine la fontul catalogat (şi poziţia de scriere iniţială) } bind def
Avem în vedere formatul de pagină "A4" şi putem stabili ca primul rând să înceapă la 1in
=72bp
faţă de marginea stângă şi 800bp
faţă de marginea de jos (pentru formatul "Letter" am înlocui 800 cu 750); prin /newl
vom iniţia scrierea unui nou rând (fixând "currentpoint
" dedesubtul originii celui curent, la distanţa verticală indicată pe stivă):
/newpag {72 800 translate 0 0 moveto} bind def /newl { % newline (<dy> newl) /dy exch def currentpoint exch % y x pop dy sub 0 exch moveto % y dy | y1=y-dy 0 | 0 y1 | moveto } bind def
Ne-am propus din start, să scriem câte un rând de glife (distanţate suficient, pentru claritate), însoţit imediat dedesubt de un rând conţinând în aceeaşi ordine, numele acestora (separate cu câte două spaţii); fiindcă unele nume sunt cam lungi (şi vrem să le scriem cu o mărime de caracter obişnuită), stabilim să scriem câte (maximum) 8 glife (respectiv, numele asociate) pe fiecare rând.
Avem nevoie de o zonă de memorie pentru a converti (prin cvs
) la "string" numele glifei curente (pregătind scrierea acestuia, prin show
) şi de un tablou în care să cumulăm pe rând cele (maximum) 8 nume de scris pe rândul curent:
/nume 20 string def % pentru conversia la 'string' a numelui de glyph /arrN 8 array def % va înregistra succesiv câte 8 nume de caracter
Obs. Dacă lungimea 20 din /nume
este totuşi prea mică, interpretorul va furniza un mesaj de eroare (şi atunci, o vom mări); în "Adobe Glyph List" (dicţionarul /AdobeGlyphList
specificat în Resource/Init/gs_agl.ps
din Ghostscript) putem găsi şi nume foarte lungi, de exemplu: "whitediamondcontainingblacksmalldiamond
".
Prin procedura următoare scriem rândul numelor înregistrate în tabloul arrN
şi apoi, pregătim scrierea următorului rând de glife (dacă mai încape în pagina curentă – altfel, ejectăm prin /showpage
pagina curentă şi iniţializăm o nouă pagină):
/writeNames { % scrie dedesubtul rândului de glyph-uri, numele acestora 16 newl gsave % salvează 'currentfont' şi 'currentpoint' fontN setfont % setează drept 'currentfont', Times-Italic arrN {nume cvs show ( ) show} forall % nume1 nume2 nume3 ... grestore % restaurează vechile 'currentfont' şi 'currentpoint' currentpoint exch pop neg 700 lt % trece la un nou rând (dacă încape), sau la o nouă pagină {36 newl} {showpage newpag} ifelse } bind def
Pentru parcurgerea tabloului keys
pe secţiuni de câte opt nume, folosim o variabilă de indexare /idx
pe care o iniţializăm cu 0 după ce vom fi scris rândul curent de 8 glife şi o incrementăm apoi pe măsură ce parcurgem următoarele 8 elemente din keys
:
/id0 {/idx 0 def} def % index iniţial într-un tablou /id1 {/idx idx 1 add def} def % incrementează indexul curent
După fiecare scriere a unui caracter (fie prin show
, fie prin glyphshow
), în "currentpoint
" avem originea la care ar urma să fie scris următorul caracter; deplasând suficient "currentpoint
" (ţinând seama de mărimea de caracter aleasă pentru glife), putem aranja ca glifele de acelaşi index 0..7 scrise pe rânduri diferite, să aibă aceeaşi margine stângă în cadrul paginii. Alegem să deplasăm "currentpoint
" astfel încât distanţa dintre marginile-stânga a două glife consecutive pe un acelaşi rând să fie de 60 puncte (suficient, având în vedere că în glyphList
vom scala fontul indicat la 24 puncte):
/nextpos { % poziţia următorului glyph (rezervăm 60 puncte pentru fiecare) currentpoint pop 60 idx 1 add mul exch sub 0 rmoveto} bind def
Prin procedura următoare înscriem numele curent (din keys
), în tabloul arrN
pe poziţia indicată de idx
; dacă s-au completat toate cele 8 poziţii, atunci scriem rândul celor 8 nume (prin /writeNames
) şi iniţializăm idx
=0 pentru viitorul rând – altfel, avansăm "currentpoint
" la marginea stângă următoare (prin /nextpos
) şi incrementăm idx
:
/put_wr8 { % memorează şi în final scrie, numele glyph-urilor de pe un rând arrN exch % arrN Ng idx exch % arrN idx Ng put % înscrie Ng pe locul idx curent, în arrN idx 7 eq {writeNames id0} % scrie rândul celor 8 nume, sau {nextpos id1} % avansează pe rândul glyph-urilor şi idx++ ifelse } bind def
Vom putea scrie rândurile de glife şi de nume, iterând împreună glyphshow
şi put_wr8
, pe tot cuprinsul tabloului keys
(exceptând ultimul rând de glife din catalog, dacă acesta are mai puţin de 8 glife).
Procedura finală (în care ne îngrijim şi de ultimul rând) se poate formula astfel:
/glyphList { % <font> glyphList (cataloghează glyph-urile din fontul indicat) /Font exch def 24 Font fnd_scl setfont currentfont /CharStrings get {pop} forall % descarcă procedura (--string--) asociată cheii count array astore % în stivă avem acum tabloul cheilor din /CharString storeNames % salvează tabloul din stivă în /keys keys sort_cvs % ordonează alfabetic numele glyph-urilor newpag title % scrie titlul, pe prima pagină 72 newl id0 % iniţializează cu 0 indexul numelor din tabloul arrN % scrie câte un rând de glyph-uri şi unul de nume (întorcând eventual pagina) keys { dup glyphshow put_wr8 } forall idx 7 lt { % dacă ultimul rând de glyph-uri al ultimei pagini este incomplet 16 newl % trece pe rândul destinat numelor fontN setfont arrN 0 idx getinterval { % selectează numele glyph-urilor rămase nume cvs show ( ) show } forall % scrie numele glyph-urilor de pe rândul incomplet showpage % ejectează pagina finală } if } bind def
Precizăm că fişierul glyphCat.ps (larg desfăşurat mai sus) are sub 80 de linii (3.5kB
).
Să considerăm un program "maingly.ps", care (după ce „importă” fişierul glyphCat.ps) să folosească procedura glyphList
:
%! maingly.ps (glyphCat.ps) run /Times-Roman glyphList
Putem obţine catalogul în format PDF prin (v. şi [1]):
vb@Home:~/20-iun$ ps2pdf -dNOSAFER maingly.ps
Dar în fişierul PDF rezultat astfel, foarte multe caractere nu sunt reprezentate decât prin nume (lipsind glifele); în plus, unele caractere sunt redate prin alte glife decât cele existente în fontul catalogat:
Simbolul ₤ de pe al treilea rând de glife (asociat numelui /afii08941
) este un simbol monetar (pentru "lire") binecunoscut, dar diferă de simbolul produs prin /afii08941 glyphshow
în procedura glyphList
invocată mai sus pentru fontul Times-Roman
.
Explicaţia acestor neajunsuri constă în faptul că fontul de catalogat nu a fost încorporat în fişierul PDF (ceea ce putem constata folosind pdffonts
, sau deschizând fişierul PDF în evince
şi accesând meniul File/Properties).
Pentru a forţa încorporarea fonturilor în PDF, putem folosi opţiunea -dEmbedAllFonts
(dar trebuie combinată cu -dPDFSETTINGS=/prepress
):
ps2pdf -dNOSAFER -dPDFSETTINGS=/prepress -dEmbedAllFonts=true maingly.ps
Vedem acum că simbolul pentru /afii08941
(înregistrat în clona "NimbusRoman-Regular" de care putem dispune liber în Ghostscript pentru fontul "Times-Roman") nu este ₤, ci apare format prin alipirea unui "z" cu un "l tăiat" (al patrulea caracter de pe linia a treia, din prima pagină a catalogului obţinut). Putem selecta şi copia caracterul respectiv (direct de pe PDF-ul redat mai sus) şi pastându-l aici, obţinem simbolul obişnuit pentru „lira sterlină”: ₤. Dacă derulăm catalogul până la numele care încep cu litera "l" (pagina 4, jos) găsim numele "/lira
", având ca simbol tot combinaţia de "z" şi "l tăiat"; dacă derulăm până la litera "s" (pagina 6, jos), găsim numele /sterling
, cu simbolul £ (asemănător celui obişnuit pentru „liră”).
Putem face desigur, o ultimă probă (pentru a vedea de exemplu, dacă este cazul să adoptăm o procedură de ordonare mai eficientă decât cea din bbsort.ps) – iterând glyphList
pe fonturile „principale” înregistrate în fişierul Fontmap.GS
din Ghostscript:
%! maingly.ps (glyphCat.ps) run [/AvantGarde-Book /Bookman-Demi /Courier /Helvetica /NewCenturySchlbk-Roman /Palatino-Roman /Symbol /Times-Roman /ZapfChancery-MediumItalic /ZapfDingbats] {glyphList} forall
Fişierul PDF obţinut (în mai puţin de 15 secunde, pe sistemul meu) prin comanda ps2pdf
formulată mai sus, are 76 de pagini (1.1MB
), conţinând în ordine alfabetică numele şi glifele corespunzătoare, pentru toate fonturile indicate; n-ar fi greu de bănuit că (exceptând /Symbol
şi ZapfDingbats
) acestea conţin aceleaşi 1004 nume de caracter (diferind desigur, glifele asociate), selectate dintre cele vreo 4200 anticipate de Ghostscript (9.26) în dicţionarul iniţial /AdobeGlyphList
.
Vedem acum, că probabil era mai bine să fi procedat altfel: să luăm pe rând numele înscrise în "Adobe Glyph List" şi să le identificăm în dicţionarele "CharStrings" ale fonturilor PS de bază, scriind numele respectiv şi adăugând (când există) glifele asociate în fiecare font (această idee poate demara vreun alt „exerciţiu” şi desigur, nu ne vom putea lăuda niciodată că „am terminat”).
vezi Cărţile mele (de programare)