[successivo] [precedente] [inizio] [fine] [indice generale] [violazione GPL] [translators] [docinfo] [indice analitico] [volume] [parte]


Capitolo 226.   PostScript: espressioni e funzioni

Il linguaggio PostScript è nato per essere interpretato in modo veloce da stampanti realizzate appositamente. In questo senso, la sua logica segue vagamente quella di un linguaggio assemblatore.

226.1   Lo stack

Per poter scrivere codice PostScript un po' più complesso, diventa necessario l'utilizzo di istruzioni che realizzano delle espressioni, fino ad arrivare alla costruzione di funzioni (procedure) che possono essere richiamate successivamente. Purtroppo, le espressioni realizzate con questo linguaggio, diventano un po' complicate da leggere. Infatti, queste funzioni ricevono i loro argomenti prelevandoli da uno stack (pila) ed emettono risultati inserendoli nello stesso stack.

dato... funzione

Osservando il modello, le informazioni che non sono riconducibili a nomi di funzione, vengono inserite in questo stack, che poi la prima funzione inizia a leggere. Si osservi l'istruzione seguente:

1 2 3 4 5 6 7 8 9 moveto lineto

I valori da uno a nove, vengono inseriti così come sono nello stack, poi ogni funzione preleva dallo stack la quantità di argomenti che la riguarda. In questo caso, moveto preleva gli ultimi due valori a essere stati inseriti, precisamente la coppia otto e nove, per cui sposterà le coordinate correnti in 8,9; successivamente è il turno di lineto, che preleva altri due valori, precisamente il sei e il sette, tracciando una linea fino al punto 6,7. Pertanto, tutto è come se fosse stato scritto nel modo seguente:

8 9 moveto 6 7 lineto

Tuttavia, rimangono ancora altri valori nello stack, per altre funzioni successive, ammesso che vogliano usarli, perché se si inseriscono altri valori, questi vengono poi estratti per primi.

Dato questo meccanismo, diventano importanti alcune funzioni che consentono di intervenire su questo stack: clear svuota completamente lo stack; pop preleva ed elimina l'ultimo valore inserito.(1)

Sono un po' più difficili da comprendere le funzioni exch e roll. La prima scambia l'ordine degli ultimi due valori inseriti nello stack; la seconda esegue uno scorrimento, verso sinistra o verso destra di una certa quantità di questi valori:

n_elementi_da_scorrere scorrimento roll

Per esempio, se nello stack ci fossero i valori 1, 2, 3, 4, 5, 6 e 7, in questo ordine, per cui il primo a essere prelevato sarebbe il numero 7, l'istruzione 3 2 roll trasformerebbe questa sequenza in 1, 2, 3, 4, 6, 7 e 5; al contrario, l'istruzione 3 -2 roll trasformerebbe questa sequenza in 1, 2, 3, 4, 7, 5 e 6. In pratica, il primo valore indica quanti elementi prendere in considerazione, a partire dall'ultimo, mentre il secondo indica quante volte scorrere e in quale direzione.

Figura 226.1. Esempio di funzionamento dell'istruzione roll, con uno scorrimento verso destra.

stack iniziale:         1 2 3 4 5 6 7

    3 2 roll                    7 5 6  (primo scorrimento verso destra)
                                6 7 5  (secondo scorrimento verso destra)
stack finale:           1 2 3 4 6 7 5

Figura 226.2. Esempio di funzionamento dell'istruzione roll, con uno scorrimento verso sinistra.

stack iniziale:         1 2 3 4 5 6 7

    3 -2 roll                   6 7 5  (primo scorrimento verso sinistra)
                                7 5 6  (secondo scorrimento verso sinistra)
stack finale:           1 2 3 4 7 5 6

Quando una funzione restituisce un valore, lo fa inserendolo implicitamente nello stack. In questo modo, l'assegnamento a una variabile, così come si è abituati nei linguaggi di programmazione comuni, non c'è. Al massimo si definisce una funzione che restituisce un valore, inserendolo nello stack.

A questo punto si può cominciare a comprendere che i dati inseriti nello stack, quando ciò non avviene per mezzo di una funzione che restituisce qualcosa, devono avere una rappresentazione formale. Può trattarsi di: valori numerici, che si scrivono come sono, utilizzando il punto per separare la parte decimale; stringhe, che sono delimitate da parentesi tonde e possono contenere delle sequenze di escape; espressioni, che sono delimitate tra parentesi graffe (si ricordi il caso della funzione repeat). I valori logici, Vero e Falso, non hanno una rappresentazione particolare e si indicano espressamente solo attraverso le funzioni true e false.

Tabella 226.1. Rappresentazione dei dati e gestione dello stack.

Istruzione Descrizione
intero[.decimale] Inserisce il valore numerico nello stack.
(stringa) Inserisce la stringa nello stack.
{espressione} Inserisce le istruzioni nello stack.
clear Svuota lo stack.
oggetto pop Preleva dallo stack l'ultimo valore inserito.
oggetto_1 oggetto_2 exch Scambia gli ultimi due valori nello stack.
m n roll Fa scorrere gli ultimi m elementi dello stack di n posizioni.
oggetto dup Preleva l'ultimo valore e ne inserisce due copie nello stack.

226.2   Funzioni comuni

Alcune funzioni operano su valori numerici, restituendo un risultato che, secondo la logica del linguaggio PostScript, viene inserito nello stack. Per esempio, la funzione add riceve due valori restituendo la somma di questi:

10 20 add 40 moveto

In questo caso, vengono sommati i valori 10 e 20, inserendo nello stack il valore 30. Così, si ottiene lo spostamento nelle coordinate 30,40, attraverso la funzione moveto.

I valori logici, come accennato, si indicano attraverso le funzioni true e false, che si limitano rispettivamente a inserire nello stack il valore corrispondente. Possono generare risultati logici anche alcune funzioni di confronto e i valori logici possono essere rielaborati attraverso funzioni booleane. Infine, in base a un valore logico è possibile eseguire o meno un gruppo di espressioni. Si osservino gli esempi seguenti.

Queste funzioni vengono descritte brevemente nella tabella 226.2.

Tabella 226.2. Espressioni matematiche, logiche e condizionali.

Istruzione Descrizione
n neg Inverte il segno del valore.
m n add Somma i due valori.
m n sub Sottrae n da m.
m n mul Moltiplica i valori.
m n div Divide m per n.
m n mod Il resto della divisione intera di m per n.
n round Arrotonda n.
n abs Calcola il valore assoluto di n.
n sin Calcola il seno di n.
n cos Calcola il coseno di n.
m n min Restituisce il minimo tra due valori.
m n max Restituisce il massimo tra due valori.
true Vero.
false Falso.
m n gt Vero se m è maggiore di n.
m n ge Vero se m è maggiore o uguale a n.
m n lt Vero se m è minore di n.
m n le Vero se m è minore o uguale a n.
m n eq Vero se i valori sono uguali.
m n ne Vero se i valori sono diversi.
bool {istruzioni} if Esegue le istruzioni se il valore logico è Vero.
bool {istr_1} {istr_2} ifelse Esegue il primo o il secondo gruppo di istruzioni in base al valore logico.

226.3   Operazioni sulle stringhe

A causa della struttura del linguaggio, la gestione delle stringhe non è affatto intuitiva: bisogna tradurre tutto nell'ottica dello stack. Tanto per cominciare, la cosa più semplice che si può fare con una stringa è misurarne la lunghezza con l'aiuto della funzione stringwidth. Per la precisione, si tratta di determinare la posizione finale di una stringa collocata a partire dalle coordinate 0,0:

stringa stringwidth

Se si osserva la figura 226.3, si può vedere la stringa composta dalla parola «Ciao», scritta con il carattere Helvetica, avente un corpo di 12 punti. Come si vede, la sua lunghezza è di 24,672 punti.

Figura 226.3. Lunghezza di una stringa.

rotazione

Quando c'è la necessità di convertire un valore in una stringa, si pone il problema dell'allocazione di memoria per la stringa stessa. Per esempio, la funzione cvs converte un valore in stringa, ma per farlo deve avere già una stringa da prelevare dallo stack:

valore stringa cvs

Volendo convertire il valore 23,45 in stringa, bisogna preparare prima una stringa di almeno cinque caratteri:

23.45 (     ) cvs

Per allocare una stringa, composta da caratteri <NUL>, ovvero 0008, si può usare la funzione string, che richiede l'indicazione della quantità di caratteri. Pertanto, la stessa cosa avrebbe potuto essere scritta nel modo seguente:

23.45 5 string cvs

Naturalmente, la funzione cvs si può usare per visualizzare la stringa generata, per esempio nel modo seguente:

10 10 moveto
23.45
5 string cvs
show

Si osservi che con cvs, anche se si alloca una stringa più grande del necessario, questa viene ridotta alla dimensione richiesta dalla conversione.

Tabella 226.3. Espressioni relative a stringhe.

Istruzione Descrizione
n string Alloca una string di n caratteri <NUL>.
stringa stringwidth Inserisce le coordinate finali della stringa nello stack.
valore stringa cvs Restituisce una stringa corrispondente al valore.

226.4   Funzioni

Una funzione si definisce attraverso la sintassi seguente:

/nome {istruzioni} def

In pratica, si vuole fare in modo che usando il nome indicato, si faccia riferimento automaticamente al gruppo di istruzioni contenuto tra parentesi graffe.(2)

Come per qualunque altra funzione normale, anche le funzioni definite in questo modo ricevono gli argomenti della chiamate dallo stack. Per esempio, la funzione quadrilatero che si potrebbe dichiarare nel modo seguente,

/quadrilatero { newpath moveto lineto lineto lineto closepath stroke } def

va usata mettendo davanti, ordinatamente gli argomenti per le varie funzioni utilizzate. Per esempio, volendo disegnare un quadrato con gli angoli nelle coordinate 0,0, 0,10, 10,10 e 10,0, si dovrà usare la funzione quadrilatero nel modo seguente:

10 0 10 10 0 10 0 0 rettangolo

È importante osservare che la prima coppia di coordinate è quella presa in considerazione dall'ultima funzione lineto contenuta nel raggruppamento di quadrilatero.

Così come si definisce una funzione, si può attribuire a un nome un valore costante. In questi casi eccezionali, è consentito l'eliminazione delle parentesi graffe:

/nome costante def

Con la definizione di costanti, si può stabilire una volta per tutte il valore di qualcosa, come nell'esempio seguente:

/Margine_Sinistro 80 def
/Margine_Destro 80 def
/Margine_Superiore 100 def
/Margine_Inferiore 100 def

226.4.1   Variabili

Nel linguaggio PostScript non è prevista la gestione di variabili: tutto viene elaborato attraverso lo stack. Tuttavia, esiste un trucco per ottenere qualcosa che assomigli a delle variabili; si tratta di sfruttare opportunamente la definizione di funzioni. È già stato visto l'assegnamento di un valore costante a un nome:

/nome costante def

oppure:

/nome { costante } def

Se si vuole attribuire a una funzione un valore diverso, occorre un trucco, che si può schematizzare come segue:

/nome_1 { /nome_2 exch def } def

Si tratta di una funzione che ne dichiara un'altra, ma si osservi con attenzione: la parola chiave exch non è racchiusa tra parentesi graffe e non può esserlo, se si vuole che il meccanismo funzioni.

Per assegnare un valore alla funzione nome_2, si utilizza una chiamata alla funzione nome_1:

n nome_1

Per leggere il valore, si fa riferimento alla funzione nome_2, come nell'esempio seguente in cui si utilizza questo dato come coordinata Y per uno spostamento:

n nome_1
m nome_2 moveto

226.5   Dizionari

La dichiarazione delle funzioni può essere inserita in un dizionario, da richiamare quando serve e da sostituire eventualmente con altre di un altro dizionario, quando la situazione lo richiede. In pratica, la definizione di dizionari di funzioni consente di fare riferimento a gruppi di funzioni solo nell'ambito di un certo contesto, ripristinando un utilizzo differente delle stesse subito dopo.

/dizionario n dict def
dizionario begin
    dichiarazione_di_funzione
    ...
    ...
end

Il modello sintattico mostra in che modo procedere alla dichiarazione di un dizionario. Si può osservare che prima della parola chiave dict occorre indicare un numero, allo scopo di definire una quantità di memoria da allocare per il dizionario stesso. Per abilitare l'uso delle funzioni dichiarate nel dizionario, si deve dichiarare espressamente:

dizionario begin
    istruzione
    ...
    ...
end

Eventualmente, la dichiarazione di utilizzo di un dizionario si può annidare; quando si raggiunge la parola chiave end, termina il campo di azione dell'ultimo dizionario ancora aperto.

In generale, potrebbe essere conveniente inserire la dichiarazione dei dizionari nell'ambito delle istruzioni speciali %%BeginProlog e %%EndProlog. L'esempio seguente mostra un estratto di un file PostScript ipotetico, in cui si dichiara un dizionario (molto breve) e lo si utilizza immediatamente nell'ambito della pagina (i puntini di sospensione indicano una parte mancante del file che non viene mostrata).

%!PS-Adobe-2.0
%%DocumentPaperSizes: a4
%%EndComments
%%BeginProlog
/Mio_Dizionario 50 dict def
Mio_Dizionario begin
    /quadrilatero
    {
        newpath moveto lineto lineto lineto closepath stroke
    } def
    /Margine_Sinistro 80 def
    /Margine_Destro 80 def
    /Margine_Superiore 100 def
    /Margine_Inferiore 100 def
end     % Fine della dichiarazione del dizionario «Mio_Dizionario»
%%EndProlog
Mio_Dizionario begin
%%Page: 1 1
...
...
...
showpage
%%Page: 2 2
...
...
...
showpage
end     % Fine del campo di azione del dizionario «Mio_Dizionario»
%%Trailer
%%EOF

226.6   Riferimenti

Appunti di informatica libera 2003.01.01 --- Copyright © 2000-2003 Daniele Giacomini -- daniele @ swlibero.org

1) A fianco di pop si potrebbe immaginare la presenza di una funzione con il nome push, ma in questo caso non serve, perché l'azione di inserimento nello stack avviene in modo implicito.

2) Eccezionalmente, se si tratta di definire una costante o se si vuole ridefinire il nome di un'altra funzione, non sono necessarie le parentesi graffe.


Dovrebbe essere possibile fare riferimento a questa pagina anche con il nome postscript_espressioni_e_funzioni.html

[successivo] [precedente] [inizio] [fine] [indice generale] [violazione GPL] [translators] [docinfo] [indice analitico]