Una Chiave per il Desktop Publishing

Corso di PostScript - Marco A. Calamari

Terza Puntata


In questa puntata ci impadroniremo dell'uso di una sessantina di operatori PostScript; daremo le definizioni esatte di tutti gli operatori matematici, di gestione dei dizionari e di manipolazione dello stack, e definiremo anche gli operatori grafici e di gestione dei font fin qui utilizzati. Apprenderemo l'uso delle procedure e del meccanismo dei dizionari; faremo inoltre una breve digressione sul modo utilizzato dall'interprete PostScript per immagazzinare gli oggetti all'interno della memoria. Termineremo con un programma commentato ed una breve bibliografia di testi di riferimento.


Prima di parlare nuovamente di operatori PostScript è necessario conoscere come definire costanti, variabili, procedure ed altri oggetti di uso comune.

Numeri, numeri

Abbiamo già definito degli interi nel programma della seconda parte. In PostScript un intero è una sequenza di numeri, iniziante eventualmente con un segno, ma non contenente il punto decimale o spazi bianchi ; ad esempio

143 +78987 -14

sono numeri interi validi. I numeri reali invece contengono il punto decimale e/o l'esponente, ad esempio

32.3 0.25 .1111 2.76e6 -23.4E-05

Nel caso che un intero sia troppo grande per essere rappresentato come tale, esso viene automaticamente convertito a reale con esponente. In PostScript esistono anche i numeri interi con base diversa da 10, la base può essere un intero compreso fra 2 e 36 separata dal numero dal carattere #; le cifre del numero sono comprese da 0 a 9 e da A a Z, ad esempio

2#1100 16#123ABC 36#129az

E importante notare che la definizione di uno stesso numero intero in una qualunque base genera lo stesso oggetto; in parole povere l'oggetto numero intero non ricorda la base in cui è stato definito.

Stringhe e caratteri

Come già visto in precedenza, un oggetto stringa (per brevità d'ora in poi semplicemente stringa) viene definito racchiudendo una serie di caratteri ASCII fra parentesi tonde. All'interno di una coppia di parentesi tonde i soli caratteri che conservano un significato speciale sono \, (, ), tutti gli altri lo perdono.

All'interno di una stringa possono essere definiti, per mezzo del carattere \ (backslash), i seguenti caratteri speciali

Inoltre il carattere \ permette di definire, all'interno di una stringa, un qualunque carattere, anche non-ASCII, fornendo il suo valore ottale, ad esempio

\40 \040

definiscono ambedue uno spazio bianco del set di caratteri ASCII.

Una stringa lunga può essere spezzata su più righe di testo scrivendo un backslash come ultimo carattere della riga e facendolo seguire dal ritorno a capo, che viene così ignorato.

Un altro modo di definire una stringa è di inserire fra parentesi angolate i valori dei singoli caratteri in formato esadecimale; questo è il metodo normalmente utilizzato per includere valori binari (ad esempio bitmap) in un programma PostScript. Si noti che, limitandosi ai caratteri stampabili e di controllo, la stessa stringa può essere definita sia con l'uso delle parentesi tonde che con le angolate. Vediamo degli esempi

L'istruzione Postscript

(stringa normale)

seguita dall'operatore show produce

stringa normale

(stringa strana {}[]%\)\)\))

produce

stringa strana {}[]%)))

(stringa su due\nlinee)

produce

stringa su due

linee

(stringa su \

una linea)

produce

stringa su una linea

(AAA)

produce

AAA

(\101\101\101)

produce

AAA

<414141>

produce

AAA

Gli operatori

È necessario cominciare a definire ed elencare gli operatori più importanti che dovremo usare; ecco quindi un elenco dei principali operatori matematici, di manipolazione degli stack e dei dizionari, e una prima parte degli operatori grafici. Le definizioni degli operatori saranno scritte in questo modo

num1 num2 add num3 : spiegazione

La definizione è suddivisa in quattro parti:

Segnaleremo con un trattino - l'assenza di valori di input e/o di output, e sottointenderemo (salvo indicazione contraria) che i valori di input vengono rimossi dallo stack degli operandi, e che quelli di output vi vengono aggiunti.

Indicheremo inoltre con

any, any1, any2 ... degli oggetti di tipo qualunque;

num, num1, num2 ... degli oggetti numerici indifferentemente interi o reali;

int, int1, int2 ... degli oggetti numerici interi;

real, real1, real2 ... degli oggetti numerici reali;

bool, bool1, bool2 ... dei valori logici;

string, string1, string2 ... delle stringhe di caratteri;

proc, proc1, proc2 ... delle procedure, o vettori eseguibili;

vect, vect1, vect2 ... dei vettori non eseguibili;

name, name1, name2 ... degli oggetti nominali;

dict, dict1, dict2 ... dei dizionari

Tutti gli altri tipi di oggetti verranno indicati con i loro nomi scritti in corsivo.

Operatori matematici

Operatori di manipolazione dello stack

Operatori di gestione dei dizionari

Operatori di gestione dei font

Operatori grafici

Vettori, procedure, variabili e dizionari

Come abbiamo già visto, un oggetto vettore si definisce racchiudendo una qualunque sequenza di oggetti PostScript fra parentesi quadre. Una procedura PostScript, che non compare nell'elenco dei tipi di oggetti PostScript, non è altro che un vettore con l'attributo eseguibile; è per questo che talora parleremo di vettori eseguibili per indicare le procedure. Una procedura PostScript viene definita racchiudendo una sequenza di comandi validi fra parentesi graffe; quando viene incontrata la parentesi graffa chiusa, l'interprete PostScript genera un oggetto di tipo vettore eseguibile e lo pone sullo stack degli operandi.

Per dare un nome alla procedura, si devono porre due oggetti sullo stack, il nome della procedura ed il vettore eseguibile che la contiene, ed associarli poi con l'operatore def; ad esempio

/cubo { dup dup mul mul } def

definisce una procedura di nome cubo (si ricordi che per convenzione i nomi delle procedure iniziano con la minuscola), che calcola il cubo di un numero; e successivamente l'operatore def memorizza la procedura nel dizionario corrente.

Vediamo ora come definire una variabile; per definire la variabile Pippo (si ricordi che i nomi di variabile iniziano con la maiuscola) con valore 22 scriveremo

/Pippo 22 def

Ogni volta che il programma incontrerà il nome eseguibile Pippo, il valore 22 verrà posto sullo stack e potrà essere utilizzato da altri operatori. Per modificare il valore di una variabile la si deve ridefinire; ad esempio, per incrementarne il valore di Pippo di 1 (solo dopo averla definita) scriveremo

/Pippo Pippo 1 add def

Il valore di Pippo è adesso 23.

Ma dove sono state immagazzinate le procedure e le variabili che abbiamo definito? Tutti gli oggetti PostScript vengono memorizzati nella VM o memoria virtuale dell'interprete PostScript, che è gestita direttamente da esso; lo stack non contiene realmente gli oggetti, ma solo dei puntatori agli oggetti stessi, i quali si trovano ancora nel punto della VM in cui sono stati memorizzati all'atto della definizione. Un oggetto PostScript, una volta definito, non può più essere rimosso dalla VM; esistono comunque meccanismi che permettono di cancellare oggetti dalla VM, che esamineremo più avanti

È chiaro che deve esistere un meccanismo che permetta di suddividere gli oggetti definiti in memoria, permettendone il controllo della visibilità; qualcosa insomma di analogo al meccanismo di visibilità dei nomi di variabili e procedure nei linguaggi tipo Fortran, Pascal o C. Questo meccanismo esiste, ed è basato sull'uso degli oggetti di tipo dizionario; dato che il suo funzionamento è abbastanza diverso dalle normali regole che governano la visibilità dei nomi negli altri linguaggi, sarà necessario comprenderlo a fondo prima di proseguire.

Quando l'interprete PostScript incontra un nome eseguibile (cioè un nome non preceduto dal carattere /) non cerca l'oggetto od il comando corrispondente a quel nome direttamente nella VM, ma solo tramite lo stack dei dizionari. Nelle prime due posizioni di questo stack sono permanentemente memorizzati due dizionari userdict e systemdict; systemdict contiene gli operatori PostScript predefiniti e non è modificabile, ed userdict contiene inizialmente alcuni operatori e variabili (come il numero di copie da stampare) ed è modificabile. La ricerca di un nome parte dal dizionario più in alto nello stack (detto dizionario corrente) dei dizionari (inizialmente userdict) e prosegue fino all'ultimo (sempre systemdict). I nuovi oggetti definiti (variabili, procedure, ecc.) vengono normalmente memorizzati nel dizionario corrente, quindi inizialmente userdict.

Se vogliamo memorizzare una serie di oggetti in modo da poterne controllare la visibilità possiamo quindi creare un nuovo dizionario, porlo sullo stack dei dizionari, immagazzinarvi gli oggetti e toglierlo dallo stack; a questo punto tutti i contenuti del dizionario sono invisibili al normale meccanismo di ricerca dei nomi. Possiamo renderli visibili a piacere caricando e scaricando il nuovo dizionario dallo stack dei dizionari.

Facciamo un esempio; supponiamo (programma 1) di voler definire le costanti Pi e E, ed usarle con diversi numeri di cifre significative secondo necessità. Nel programma seguente, le linee che iniziano con il carattere speciale % sono commenti PostScript; queste linee possono anche non essere inserite nel programma, cosi come le linee vuote. Un commento può inoltre essere iniziato anche in mezzo ad una linea; ponendo il carattere % nel punto in cui si desidera iniziare il commento, tutti i caratteri seguenti fino alla fine della linea saranno ignorati dall'interprete PostScript.


% Corso PostScript - III parte - programma 1
% Prova di utilizzo di un dizionario
% per controllare la visibilità di
% variabili e procedure.
/cost3dict 10 dict def
% Creiamo il nuovo dizionario di
% costanti con 3 cifre significative
cost3dict begin
% Poniamolo sullo stack dei dizionari
% che ora contiene:
% cost3dict
% userdict
% systemdict
% Definiamo Pi ed E a 3 cifre
% significative.
/Pi 3.14 def
/E 2.71 def
% Togliamo cost3dict dallo stack
% dei dizionari.
end
% Analogamente definiamo cost4dict
% e Pi ed E a 4 cifre significative.
/cost4dict 10 dict def
cost4dict begin
/Pi 3.141 def
/E 2.718 def
end
% Utilizziamo le costanti a 3 cifre
% Il risultato stampato a
% terminale sarà 5.85
cost3dict begin
Pi E add ==
end
% Utilizziamo le costanti a 4 cifre
% Il risultato stampato a
% terminale sarà 5.859
cost4dict begin
Pi E add ==
end

Il meccanismo dei dizionari ha numerosi altri usi, che presenteremo nei prossimi esempi di programmazione.

Un esempio di grafica

L'esempio che segue (programma 2) utilizzerà alcuni dei nuovi operatori che abbiamo definito. L'utilizzo di alcuni di essi è fatto a scopo didattico, e non sarebbe strettamente necessario; ad esempio non è necessario porre tutte le procedure che usiamo in un dizionario diverso da userdict, ma lo faremo per impratichirci nell'uso dei dizionari. In altri casi il programma potrebbe essere migliorato da un uso più intenso dello stack e dall'eliminazione delle variabili locali alle procedure, ma, trattandosi dei primi programmi, è stata privilegiata la chiarezza a scapito dell'efficienza del programma.

Il risultato grafico che otterremo dall'esempio (vedi figura 1) non sarà molto artistico (del resto siamo ancora alle elementari del PostScript), ma i prodotti dei nostri programmi PostScript miglioreranno rapidamente col tempo.

D'ora in poi, a definizione di ogni procedura sarà sempre preceduta da alcuni commenti:

  1. il primo commento contiene il nome della procedura;
  2. il secondo contiene gli eventuali argomenti prelevati dallo stack degli operandi da parte della procedura, uno slash, e quelli eventualmente depositati;
  3. il terzo è opzionale e contiene le eventuali variabili locali della procedura;
  4. il quarto è pure opzionale e contiene le variabili globali usate per l'input e/o per l'output dalla procedura;
  5. i commenti successivi descrivono brevemente lo scopo della procedura.

Si noti che la distinzione fra variabili locali e globali è solo una convenzione, in realtà tutte le variabili definite nel prossimo esempio sono globali e contenute in userdict; è a carico del programmatore evitare conflitti fra i due tipi di variabili, scegliendo magari un prefisso od un suffisso particolare da assegnare a tutti i nomi di variabili locali


% Corso PostScript - III parte - programma 2
% Stampa di un paesaggio stilizzato
% Creiamo il nuovo dizionario e
% poniamolo sullo stack.
/paesaggiodict 10 dict def
paesaggiodict 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 ad un pollice dal margine.
/tracciaCornice {
newpath
1 inch 1 inch moveto
6 inch 0 inch rlineto
0 inch 10 inch rlineto
-6 inch 0 inch rlineto
closepath
.1 inch setlinewidth
0 setgray stroke
} def
% Procedura tracciaSfondo
% Argomenti: - / -
% Traccia lo sfondo del paesaggio
/tracciaSfondo {
newpath
1 inch 1 inch moveto
6 inch 0 inch rlineto
0 inch 4.5 inch rlineto
-6 inch 0 inch rlineto
closepath
.3 setgray fill
1 inch 4.5 inch moveto
6 inch 0 inch rlineto
0 inch 6.5 inch rlineto
-6 inch 0 inch rlineto
closepath
.9 setgray fill
} def
% Procedura tracciaTriangolo
% Argomenti Base, TonoGrigio / -
% Variabili locali: BaseTriangolo,
% TonoTriangolo
% Traccia un triangolo pieno a
% partire dal punto corrente.
/tracciaTriangolo {
/TonoTriangolo exch def
/BaseTriangolo exch def
currentpoint newpath moveto
BaseTriangolo 0 rlineto
BaseTriangolo 2 div neg dup neg
rlineto closepath
TonoTriangolo setgray fill
} def
% Procedura tracciaSole
% Argomenti Raggio, TonoGrigio / -
% Variabili locali: RaggioSole,
% TonoSole
% Traccia un cerchio pieno a
% partire dal punto corrente.
/tracciaSole{
/TonoSole exch def
/RaggioSole exch def
currentpoint newpath moveto
currentpoint RaggioSole 0 180 arc
closepath
TonoSole setgray fill
} def
% Togliamo il dizionario completo
% dallo stack
end
% Inizio programma principale
% Si rende disponibile il
% contenuto del dizionario.
paesaggiodict begin
% Sitraccia il paesaggio
tracciaSfondo
tracciaCornice
4 inch 4.5 inch moveto
.5 inch 1 inch tracciaSole
1.2 inch 4.4 inch moveto
2.5 inch .2 tracciaTriangolo
4 inch 4.5 inch moveto
2 inch .1 tracciaTriangolo
2.5 inch 4.3 inch moveto
2 inch .1 tracciaTriangolo
1.5 inch 4.3 inch moveto
1 inch .05 tracciaTriangolo
2.2 inch 4.2 inch moveto
1 inch .05 tracciaTriangolo
5 inch 4.3 inch moveto
1.5 inch .05 tracciaTriangolo
% Stampa della pagina
showpage
% Rimozione del dizionario dallo
% stack dei dizionari
end

Bibliografia

Nel seguito, elenchiamo alcuni testi che vi potranno essere utili per approfondire la conoscenza del PostScript; vi raccomando particolarmente i primi due, che sono la bibbia del PostScript, e sono reperibili in ogni buona libreria specializzata in testi tecnici.

1) Adobe Systems Inc., PostScript language reference manual, Addison-Wesley, Reading Massachusetts U.S.A., 1985

2) Adobe Systems Inc., PostScript language tutorial and cookbook, Addison-Wesley, Reading Massachusetts U.S.A., 1985

3) Adobe Systems Inc., PostScript language program design, Addison-Wesley, Reading Massachusetts U.S.A., 198

4) John Warnock e Douglas Wyatt, A device independent graphics imaging model for use with raster devices, su COMPUTER GRAPHICS volume 16 numero 3 del Giugno 1982 pagg. 313-320

5) David A. Holzgang, Understanding PostScript programming, Sybex Inc., Alameda California U.S.A.,1987


Copyright © 1985: Marco A. Calamari