Una Chiave per il Desktop Publishing

Corso di PostScript - Marco A. Calamari

Nona Puntata


Nel corso delle precedenti otto puntate, abbiamo impiegato i font senza preoccuparci minimamente della loro struttura e del modo in cui l'interprete PostScript li utilizzava.

Per questo motivo dedicheremo questa parte del nostro corso a fare una indigestione di questo argomento. Alla fine, cominceremo appena ad apprezzare la nostra... ignoranza di una materia ahimè decisamente complicata. Consoliamoci, anche se non saremo (ancora) in grado di crearci da zero i nostri font, sapremo comunque operare su di essi in modi che ora neppure sospettiamo.

Una descrizione generale

Abbiamo già parlato di che cosa si intende per font di tipo outlined e di tipo stroked; pur senza perdere di generalità, nel seguito supporremo di operare sempre su font outlined; chi non si ricordasse, è pregato di ripassare la sesta puntata (voi non prestate MAI i numeri arretrati di Bit, vero ?).

Per inciso, durante la pubblicazione del corso alcune puntate sono state scambiate tra loro; nelle note faremo sempre riferimento all'ordine come avrebbe dovuto essere se Murphy non ci avesse messo lo zampino. In ogni caso le puntate sono state pubblicate in questo ordine 1, 2, 4, 5, 3, 6, 7, 8; per identificare le puntate dubbie potete fare riferimento al commento in testa ai relativi listati.

Orbene, cosa succede esattamente nella LaserWriter quando noi inviamo un programma che deve porre del testo sulla pagina? Per fissare le idee torniamo per un momento al semplicissimo programma della prima puntata

/Times-Bold findfont
72 scalefont setfont
144 432 moveto
(PostScript) show
showpage

L'operatore findfont preleva un nome dallo stack degli operandi e va a ricercarlo in uno speciale dizionario di sistema (FontDirectory), che contiene tutte le informazioni relative ai font conosciuti dalla macchina; se lo trova, pone una copia del dizionario che contiene le informazioni relative al font sullo stack.

A questo punto si rendono già necessarie due precisazioni:

Punto primo: FontDirectory è quindi un dizionario di dizionari? Dal nostro punto di vista la risposta è si. Ricordate che un dizionario non contiene effettivamente i dati che descrivono i suoi elementi, ma solo una serie di coppie nome/puntatore; questi ultimi puntano poi in zone della VM (memoria virtuale) dell'interprete PostScript che contengono realmente i dati. Coloro che conoscono il metodo solitamente usato dagli interpreti Basic per memorizzare le stringhe proveranno a questo punto una sensazione di déjà vu.

Il PostScript non pone limiti al modo di combinare tra loro gli oggetti; un dizionario (come qualunque altro oggetto PostScript di tipo composto) può contenere qualunque tipo di oggetto come elemento. Potete quindi agevolmente perdervi nei meandri della programmazione PostScript creando non solo dizionari di dizionari, ma anche mostri come dizionari di vettori di dizionari di ....

Punto secondo: i font conosciuti dalla macchina non sono necessariamente quelli contenuti nelle ROM del controller PostScript; in FontDirectory sono infatti presenti anche i font memorizzati sugli eventuali hard disk di cui possono essere dotate le periferiche PostScript, quelli temporaneamente downloadati nella memoria del controller, ed infine quelli eventualmente creati dal programma in esecuzione.

Ritorniamo al nostro esempio. A questo punto entra in gioco l'operatore scalefont, che preleva il font (cioè il dizionario che ne contiene le informazioni) ed un numero dallo stack, ne modifica internamente un elemento in modo da scalarlo (vedremo poi quale elemento) e lo rimette dove lo aveva preso.

La palla viene poi passata all'operatore setfont, che riceve e mette in rete; in termini più esatti, preleva il dizionario dallo stack e rende le informazioni ivi contenute disponibili alla cosiddetta Font Machinery, cioè a quella parte dell'interprete PostScript che si occupa di font. In ogni momento, ricordiamo, esiste un unico font corrente, che viene implicitamente utilizzato da tutti gli operatori fino a quando non ne viene reso corrente un altro.

Il programma sposta quindi il punto corrente in una certa posizione, definisce una stringa ed esegue l'operatore show; improvvisamente succede una specie di '48, perché show tenta di stampare la bitmap corrispondente ad una P maiuscola corpo 72, va a guardare nella zona di memoria dove vengono conservate le bitmap dei caratteri (detta Font Cache), e non ci trova niente. L'esecuzione del programma viene quindi sospesa e la Font Machinery va a cercare fra le informazioni contenute nel font corrente quelle che descrivono il carattere in questione, le converte nell'opportuna bitmap, ed invece di passarla direttamente all'operatore che l'aveva richiesta, la memorizza nella Font Cache e fa riprendere l'esecuzione del programma dal passo precedente (quello della richiesta insoddisfatta). L'operatore show trova questa volta la bitmap, la copia opportunamente sulla pagina in memoria, ed il programma continua.

In cosa risiede il vantaggio di questo modo di operare? Semplicemente nel fatto che l'operazione di conversione di un carattere outlined nella corrispondente bitmap è un'operazione centinaia di volte più complessa di quella necessaria per copiare una bitmap già conservata in memoria. Poiché di solito i caratteri utilizzati in un testo sono solo una parte di quelli contenuti in un font, e sono usati in pochi corpi diversi, la Font Cache si riempie rapidamente delle informazioni necessarie a stampare quel particolare testo, e, dopo un avvio più lento, tutte le operazioni di stampa vengono velocizzate di due o tre ordini di grandezza.

È chiaro che la Font Cache, essendo come tutte le cose di questo mondo di capacità limitata, deve possedere un meccanismo che le permetta, una volta riempitasi, di eliminare le informazioni più vecchie per far posto a quelle nuove.

La Font Cache è anche la responsabile di quei misteriosi lampeggiamenti che il led giallo della LaserWriter compie ogni tanto anche quando nessuno stampa niente; infatti, per non sprecare tempo, Mr. Warnock o chi per lui ha pensato bene di non far poltrire un costoso controller, e, nei momenti liberi fra un lavoro e l'altro, la Font Machinery entra in azione da sola e riempie la Font Cache con una opportuna selezione di caratteri nei corpi più probabili. Se il job successivo contiene questi caratteri nei corpi previsti, la stampa uscirà in un tempo sensibilmente minore, altrimenti verrà stampata come se l'operazione di caching non fosse stata eseguita.

Solo per completezza, chi si addentrasse tra gli operatori che modificano i parametri persistenti di una stampante PostScript (elencati di solito in un apposito supplemento incluso nella documentazione della stampante), troverebbe l'operatore setidlefont che, come il nome lascia supporre, permette di far memorizzare permanentemente alla stampante una nuova serie di caratteri da elaborare nel tempo libero.

La struttura di un font

A questo punto è necessario ritornare seri e metterci a guardare da vicino come esattamente è composto un font; un font PostScript è un dizionario che contiene le informazioni necessarie a tracciare i caratteri che compongono il font stesso. Tra i contenuti di questo dizionario sono incluse le definizioni dei caratteri; queste ultime sono delle procedure PostScript che, una volta eseguite, tracciano ciascuna la sagoma di un particolare carattere, utilizzando niente altro che i normali operatori grafici PostScript che già conosciamo.

Un font viene generato ponendo sullo stack un nome ed un dizionario opportunamente costruito, ed eseguendo poi l'operatore definefont; definefont preleva dallo stack il nome ed il dizionario, controlla che quest'ultimo sia ben formato, ne impedisce ulteriori modifiche rendendolo read-only, ed infine associa nome e dizionario inserendo una nuova entry in FontDirectory. Oltre a queste operazioni definefont inserisce un ulteriore elemento nel dizionario che costituisce il font, e cioè un oggetto di nome FID e di tipo fontID; se ricordate, questo è un oggetto di tipo semplice di cui avevamo parlato nella seconda puntata, e che non avevamo poi più incontrato. L'oggetto tipo fontID permette alla Font Machinery di eseguire certi compiti interni che sono a conoscenza solo dei sacerdoti della Adobe System; è quindi indispensabile che il dizionario abbia almeno una entry libera nel momento in cui viene sottoposto all'operatore definefont.

Un font PostScript normale, come quelli memorizzati nelle ROM del controller, contiene le seguenti entità (l'operatore definefont richiede che le prime quattro siano obbligatoriamente presenti):

Il dizionario FontInfo (che può anche mancare completamente) contiene le seguenti informazioni:

Qualche esempio

È stata una bella indigestione, e per questo motivo rimandiamo il completamento delle informazioni sulla struttura dei font e sulla possibilità di modificarla alla prossima puntata; nel frattempo consigliamo i possessori del già più volte nominato programma LaserTalk, di usare il Dictionary Browser che vi è incluso per esaminare la struttura di qualche font.

Il Programma 1, riportato nel Listato 1 ed il cui output è mostrato in Figura 1, è un semplice esempio di modifica strutturale di un font; vengono generati, partendo dal normale Times neretto, tre font italici con angoli diversi, anche anomali come il terzo, che ha inclinazione negativa.

Il nucleo del programma è l'operatore makefont, che preleva dallo stack un font ed un vettore, concatena quest'ultimo alla FontMatrix, e pone il font così modificato nuovamente sullo stack, da dove lo preleva l'operatore setfont, che lo fa diventare il font corrente.

Il Programma 2 (riportato nel Listato 2) è un esempio di generazione di una gradevole forma a spirale, ottenuta con l'utilizzo degli operatori di trasformazione del sistema di coordinate, e di charpath, che, come ricordiamo, permette di estrarre il contorno di un font outlined, rendendolo la path corrente. La Figura 2 mostra risultato del programma.

Il Programma 3 (Listato 3 e Figura 3) utilizza nuovamente l'operatore charpath per generare un effetto tipo sandwich nella stampa realizzata utilizzando il solito Times.

Qualche parola in più merita il Programma 4 (Listato 4 e Figura 4); questo programma è il più complesso tra quelli che abbiamo finora incontrato; proprio per questo motivo vogliamo lasciare un mese di tempo per permettervi di fare (ovviamente se siete d'accordo) un utile esercizio esaminandone il funzionamento. Rimanderemo quindi alla prossima puntata i dettagli sul funzionamento del programma; alcuni aspetti del programma sui quali è opportuno soffermare l'attenzione sono l'impiego di un dizionario che contiene tutte le procedure utilizzate, e l'uso degli operatori transform ed itransform per eseguire calcoli vettoriali.

... to be continued.

Nella prossima puntata continueremo ad occuparci di font, mettendo in pratica le nozioni oggi apprese e generando alcuni semplici esempi di font utente.


%!PS-Adobe-2.0
%%Title: Esempio di generazione di tre font obliqui
%%Creator: Corso PostScript - parte IX - programma 1
%%For: Corso PostScript BIT
%%CreationDate: (4-10-1989) (15:56)
%%BoundingBox: 28 30 566 811
%%EndComments
%%EndProlog
%
% Procedura inch
% Argomenti: XInches / XPoints
% Converte misure da pollici
% a punti tipografici.
/inch { 72 mul } def
%
% Procedura tracciaCornice
% Argomenti: - / -
% Traccia una cornice su pagina
% A4 a mezzo pollice dal margine.
/tracciaCornice {
gsave newpath
0.5 inch 0.5 inch moveto
7.3 inch 0 inch rlineto
0 inch 10.5 inch rlineto
-7.3 inch 0 inch rlineto
closepath
.05 inch setlinewidth
0 setgray stroke
grestore
} def
%
% Procedura FontObliquo:
% Argomenti: font angolo corpo / -
% Variabili Globali: -
% Variabili Locali: Angolo Corpo
% Procedura per la modifica del font corrente
% trsformandolo in obliquo con angolo dato.
/FontObliquo {
/Corpo exch def /Angolo exch def
findfont
[ Corpo 0 Corpo Angolo sin Angolo cos div
mul Corpo 0 0 ]
makefont setfont } def
%
%
% Programma principale
%
% Incapsulamento e tracciatura della cornice
gsave
tracciaCornice
%
% Tracciamento del Times normale
/Times-Bold findfont
48 scalefont setfont
2.00 inch 3.0 inch moveto
(Times normale) show
%
% Tracciamento del primo font
/Times-Bold 30 48 FontObliquo
1.25 inch 5.0 inch moveto
(Times Obliquo +30\312) show
%
% Tracciamento del secondo font
/Times-Bold 15 48 FontObliquo
1.25 inch 7.0 inch moveto
(Times Obliquo +15\312) show
%
% Tracciamento del terzo font
/Times-Bold -20 48 FontObliquo
1.25 inch 9.0 inch moveto
(Times Obliquo -20\312) show
%
% Rimozione incapsulamento e stampa
grestore
showpage
%
%%Trailer
%%EOF

%!PS-Adobe-2.0
%%Title: Stampa in stile outlined di una spirale di parole
%%Creator: Corso PostScript - parte IX - programma 2
%%For: Corso PostScript BIT
%%CreationDate: (4-10-1989) (15:56)
%%BoundingBox: 28 30 566 811
%%EndComments
%%EndProlog
%
% Procedura inch
% Argomenti: XInches / XPoints
% Converte misure da pollici
% a punti tipografici.
/inch { 72 mul } def
%
% Procedura tracciaCornice
% Argomenti: - / -
% Traccia una cornice su pagina
% A4 a mezzo pollice dal margine.
/tracciaCornice {
gsave newpath
0.5 inch 0.5 inch moveto
7.3 inch 0 inch rlineto
0 inch 10.5 inch rlineto
-7.3 inch 0 inch rlineto
closepath
.05 inch setlinewidth
0 setgray stroke
grestore
} def
%
% Procedura stampaInOutlined:
% Argomenti: stringa / -
% Variabili Globali: -
% Variabili Locali: -
% Procedura di stampa in stile outlined.
/stampaInOutlined
{true charpath stroke} def
%
% Procedura stampaSpirale:
% Argomenti: - / -
% Variabili Globali: Stringa
% Variabili Locali: -
% Procedura di stampa di una
% spirale di parole.
/stampaSpirale
{15 15 705
{gsave
dup 240 div 1 add 1 exch div dup mul dup scale
rotate
100 0 moveto
Stringa
stampaInOutlined grestore}
for} def
%
%
% Programma principale
%
% Incapsulamento e tracciatura della cornice
gsave
tracciaCornice
%
% Inizializzazioni
/Times-BoldItalic findfont
60 scalefont setfont
200 425 translate
0.5 setlinewidth
/Stringa (PostScript) def
%
% Stampa della spirale
stampaSpirale
%
% Stampa dell stringa in grigio
100 0 moveto
Stringa true charpath
gsave
0.3 setgray fill
grestore
stroke
%
% Rimozione incapsulamento e stampa
grestore
showpage
%
%%Trailer
%%EOF

%!PS-Adobe-2.0
%%Title: Stampa in stile "sandwich" di una scritta
%%Creator: Corso PostScript - parte IX - programma 3
%%For: Corso PostScript BIT
%%CreationDate: (4-10-1989) (15:56)
%%BoundingBox: 28 30 566 811
%%EndComments
%%EndProlog
%
% Procedura inch
% Argomenti: XInches / XPoints
% Converte misure da pollici
% a punti tipografici.
/inch { 72 mul } def
%
% Procedura tracciaCornice
% Argomenti: - / -
% Traccia una cornice su pagina
% A4 a mezzo pollice dal margine.
/tracciaCornice {
gsave newpath
0.5 inch 0.5 inch moveto
7.3 inch 0 inch rlineto
0 inch 10.5 inch rlineto
-7.3 inch 0 inch rlineto
closepath
.05 inch setlinewidth
0 setgray stroke
grestore
} def
%
% Procedura stampaASandwich
% Argomenti: stringa / -
% Variabili Globali: -
% Variabili Locali: Stringa
% Stampa una stringa con effetto "sandwich".
/stampaASandwich {
/Stringa exch def
gsave Stringa show grestore
gsave 3 0 rmoveto 1 setgray
Stringa true charpath gsave fill grestore
1 setlinewidth 0 setgray stroke grestore
6 0 rmoveto Stringa show
} def
%
%
% Programma principale
%
% Incapsulamento e tracciatura della cornice
gsave
tracciaCornice
%
% Inizializzazioni
1.0 inch 5.5 inch moveto
/Times-BoldItalic findfont 1.5 inch scalefont setfont
%
% Chiamata della procedura principale
(PostScript) stampaASandwich
%
% Rimozione incapsulamento e stampa
grestore
showpage
%
%%Trailer
%%EOF

%!PS-Adobe-2.0
%%Title: Stampa di un testo lungo una path qualunque
%%Creator: Corso PostScript - parte IX - programma 4
%%For: Corso PostScript BIT
%%CreationDate: (4-10-1989) (15:56)
%%BoundingBox: 28 30 566 811
%%EndComments
%%EndProlog
%
% Creazione del dizionario che conterrà le procedure
/stampaSuLineaDict 30 dict def
%
% Apertura del dizionario
stampaSuLineaDict begin
%
% Procedura inch
% Argomenti: XInches / XPoints
% Converte misure da pollici
% a punti tipografici.
/inch { 72 mul } def
%
% Procedura tracciaCornice
% Argomenti: - / -
% Traccia una cornice su pagina
% A4 a mezzo pollice dal margine.
/tracciaCornice {
gsave newpath
0.5 inch 0.5 inch moveto
7.3 inch 0 inch rlineto
0 inch 10.5 inch rlineto
-7.3 inch 0 inch rlineto
closepath
.05 inch setlinewidth
0 setgray stroke
grestore
} def
%
% Sottoprocedura per trattare un moveto della path
/proceduraMoveto {
/NuovoY exch def /NuovoX exch def
/PrimoX NuovoX def /PrimoY NuovoY def
/DistanzaInEccesso 0 def
NuovoX NuovoY transform
/AttualeY exch def /AttualeX exch def
} def
%
% Sottoprocedura per trattare un lineto della path
/proceduraLineto {
/VecchioX NuovoX def /VecchioY NuovoY def
/NuovoY exch def /NuovoX exch def
/DeltaX NuovoX VecchioX sub def
/DeltaY NuovoY VecchioY sub def
/Distanza DeltaX dup mul DeltaY dup mul add sqrt def
Distanza 0 ne
{
/DistanzaX DeltaX Distanza div DistanzaInEccesso mul def
/DistanzaY DeltaY Distanza div DistanzaInEccesso mul def
VecchioX DistanzaX add VecchioY DistanzaY add transform
/AttualeY exch def /AttualeX exch def
/LunghezzaPercorsa LunghezzaPercorsa Distanza add def
{
LunghezzaStampata LunghezzaPercorsa le
{NumeroCaratteriStampati Stringa length lt
{stampaCarattere} {exit} ifelse }
{ /DistanzaInEccesso LunghezzaStampata
LunghezzaPercorsa sub def exit }
ifelse } loop
} if
} def
%
% Sottoprocedura per trattare un curveto della path.
% (non ci possono essere curveto dopo un flattenpath,
% quindi la procedura si riduce ad una eventuale
% segnalazione di errore).
/proceduraCurveto {
(Non ci possono essere curveto dopo un flattenpath!\n)
(L'impossibile è avvenuto !\n)
(ERRORE: \n)
print print print
} def
%
% Sottoprocedura per trattare un closepath della path
/proceduraClosepath {
PrimoX PrimoY proceduraLineto
PrimoX PrimoY proceduraMoveto
} def
%
% Procedura per stampare un carattere sulla path
/stampaCarattere {
/CarattereDaStampare Stringa
NumeroCaratteriStampati 1 getinterval def
/NumeroCaratteriStampati NumeroCaratteriStampati 1 add def
/larghezzaCarattere CarattereDaStampare stringwidth pop def
%
gsave
AttualeX AttualeY itransform translate
DeltaY DeltaX atan rotate 0 0 moveto CarattereDaStampare show
currentpoint transform
/AttualeY exch def /AttualeX exch def
grestore
%
/LunghezzaStampata LunghezzaStampata larghezzaCarattere
add def } def
%
% Chiusura del dizionario
end
%
% Procedura principale stampaSuLinea
% Argomenti: stringa offset/ -
% Variabili Globali: -
% Variabili Locali: Stringa Offset LunghezzaPercorsa
% LunghezzaStampata NumeroCaratteriStampati
% Richiede che siano definiti font e path correnti;
% continua a stampare fino all'esaurimento della path
% o della stringa; modifica ma non resetta la path corrente.
/stampaSuLinea {
%
%Apre il dizionario contenente le procedure
stampaSuLineaDict begin
%
% Memorizza in variabili locali gli argomenti
/Offset exch def
/Stringa exch def
%
% Inizializza le variabili principali
/LunghezzaPercorsa 0 def % Distanza percorsa sulla linea
/LunghezzaStampata Offset def % Offset del testo
/NumeroCaratteriStampati 0 def % Contatore dei caratteri
%
gsave
flattenpath
{proceduraMoveto} {proceduraLineto}
{proceduraCurveto} {proceduraClosepath}
pathforall
grestore
%
% Cancella la path corrente
newpath
%
% Chiude il dizionario contenente le procedure
end
} def
%
%
% Programma principale
%
% Incapsulamento, tracciatura cornice
% e inizializzazioni
gsave
tracciaCornice
/Times-Bold findfont 24 scalefont setfont
%
% Viene tracciata la path
newpath
2.25 inch 5.5 inch moveto
5.25 inch 7.5 inch 2 inch 270 180 arc
3.25 inch 3.5 inch lineto
2.25 inch 3.5 inch 1 inch 0 180 arcn
1.25 inch 11.0 inch lineto
%
% Vengono posti sullo stack la stringa da stampare...
(Programmare in PostScript è un pò contorto all'inizio, \
ma i superstiti poi si abituano. D'altronde, come diceva \
John Belushi, "... quando il gioco si fa duro, i duri \
cominciano a giocare".)
%
% ...e l'offset a cui cominciare a stampare
0
stampaSuLinea
%
% Rimozione incapsulamento e stampa della pagina
grestore
showpage
%
%%Trailer
%%EOF

Copyright © 1985: Marco A. Calamari