Una Chiave per il Desktop Publishing
Corso di PostScript
- Marco A. Calamari
Ottava Puntata
Apriamo questa puntata a favore di chi sta cimentandosi nel
debugging dei primi programmi PostScript, utilizzando i noti
operatori pstack e ==, già definiti nelle prime
puntate di questo corso.
Un esempio di debugging
Come ricorderete, questi operatori stampano sullo standard output (cioè sul video del terminale che utilizzate) rispettivamente una copia dei contenuti dello stack, che viene lasciato inalterato, oppure l'oggetto in cima allo stack, rimuovendolo da esso. In effetti, la rappresentazione degli oggetti PostScript che i due operatori producono è uguale (pstack usa proprio == come subroutine), ed è simile a quella che verrebbe utilizzata per definire in PostScript gli oggetti stessi; i nomi sono perciò preceduti dal carattere /, le stringhe sono racchiuse fra parentesi tonde, le procedure fra parentesi graffe, i vettori fra parentesi quadre, ed i valori numerici o booleani hanno la normale rappresentazione. Nel caso lo stack contenga oggetti di tipo non rappresentabile, questi operatori tentano di fornire comunque qualche informazione; ad esempio, nel caso di un oggetto operatore producono --operatorname--, cioè il nome dell'operatore racchiuso tra doppi trattini, nel caso di un oggetto composito come un dizionario producono la stringa -dicttype-, e nello stesso modo si comportano per oggetti che non possiedono una rappresentazione come l'oggetto tipo mark, per il quale viene prodotta la stringa -marktype-.
In molti casi si preferisce avere una rappresentazione più semplice degli oggetti; possono allora essere utili i due operatori stack e =, i quali producono (utilizzando internamente l'operatore cvs), la rappresentazione del valore degli oggetti tipo numero, stringa, valore logico, nome od operatore, e si limitano a stampare la stringa --nostringval-- in tutti gli altri casi.
Nell'utilizzo degli operatori = ed == è necessario prestare attenzione al fatto che i loro nomi non sono formati da caratteri speciali, come ad esempio [ o /, e quindi devono essere preceduti e seguiti, come tutti gli altri nomi composti da soli caratteri alfabetici, da spazi bianchi od altri caratteri delimitatori.
Merito una tirata d'orecchi per l'omissione, fino a questo momento, della definizione di un operatore indispensabile in qualunque attività di debugging, cioè quello che permette di far comparire un messaggio sullo standard output quando il programma passa in un certo punto; spero che la banalità del nome dell'operatore, che è print, abbia permesso, a coloro che ne avessero sentito il bisogno, di scoprirlo da soli. L'operatore print preleva dallo stack un oggetto, che deve essere di tipo stringa, e lo stampa sullo standard output. La stringa può essere sia definita esplicitamente che ottenuta convertendo un altro oggetto a stringa; è consigliabile che ogni operatore print sia seguito da un flush, il quale, forzando la scrittura del buffer di output, assicura che il messaggio desiderato appaia sul terminale nel momento esatto in cui viene emesso.
Un semplice esempio di debugging utilizzando l'operatore print
è il seguente:
...
(\nSono passato di qui,\n) print
(\n\n) X 20 string cvs ( e X vale )
print print print flush
...
che produce a terminale, supponendo che X sia una variabile
reale col valore di 3.14 al momento dell'esecuzione, la stampa
Sono passato di qui,
e X vale 3.14
La stampa di immagine digitalizzate
Le immagini grafiche che abbiamo stampate durante questo corso sono state realizzate per mezzo degli operatori PostScript in modo equivalente a quello che un artista grafico usa quando lavora con china e retini, oppure con i programmi Illustrator '88 e FreeHand; sono cioè immagini di tipo vettoriale.
La maggior parte delle immagini che vengono trattate in forma elettronica non appartengono però a questo tipo, ma a quello delle immagini digitalizzate o bitmap; esempi tipici sono le immagini ottenute da scanner o da un segnale di tipo televisivo. Una bitmap viene ottenuta dividendo l'immagine in piccole zone di uguali dimensioni, dette pixel, e registrando per ciascuna di esse il valore del tono di grigio (eventualmente solo bianco e nero), sotto forma di caratteri binari su di un opportuno supporto informatico.
Il linguaggio PostScript permette di stampare questo tipo di immagini con l'utilizzo dell'operatore image; questo operatore interpreta i codici di carattere di una stringa, e li converte in una serie di bit che descrivono i valori dei toni di grigio dei singoli pixel, iniziando dall'angolo in basso a sinistra dell'immagine.
Si noti che i caratteri che formano la stringa interpretata non si limiteranno in generale al solo set ASCII, con codici da 0 a 127, ma potranno assumere anche i valori da 128 a 255, che non hanno solitamente nessun equivalente stampabile. Questo modo di memorizzare le stringhe è molto efficiente, ma trasforma il file del programma che lo contiene da un semplice file di testo ad un file binario, che a differenza del primo non è facilmente trasportabile attraverso linee di comunicazione, e nemmeno interpretabile su tipi diversi di computer.
Per questo motivo, sacrificando la compattezza in favore della generalità e della trasportabilità, si preferisce, all'interno di programmi PostScript, memorizzare queste stringhe sotto forma di caratteri esadecimali; in questo modo un carattere (8 bit) di una bitmap viene memorizzato usando due caratteri stampabili, che appartengono sempre al subset ASCII 0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F. Ne consegue che un file contenente una bitmap memorizzata in questo modo è lungo il doppio di quello che sarebbe necessario memorizzandola come caratteri binari.
Facciamo un esempio, e supponiamo che quattro pixel
consecutivi di una bitmap, digitalizzata con 16 toni di grigio,
siano
8 1 8 15
significando grigio-medio, quasi-nero, grigio-medio, bianco;
espressi come codici binari essi saranno
0100 0001 0100 1111
poiché un carattere di otto bit può contenerne due, essi
verrebbero memorizzati come
01000001 01001111
corrispondenti ai codici carattere
65 79
che essendo inferiori a 127 corrispondono alle lettere
A O
La memorizzazione come caratteri esadecimali avverrà invece
traducendo direttamente i precedenti gruppi di 4 bit, ed
esprimendo i valori con i codici esadecimali
8 1 8 F
compattati poi nella stringa 818F.
L'operatore image stampa i pixel, risultanti dal processo di conversione della stringa esadecimale, in un quadrato unitario, iniziando dall'angolo in basso a sinistra che si trova nell'origine. In questo modo, l'immagine riprodotta dall'operatore image sarà compressa in un quadrato di un punto di lato (1/72 di pollice). Prima di utilizzare image, sarà quindi necessario traslare l'origine opportunamente con l'operatore translate, e scalare gli assi con scale; il precedente sistema di coordinate dovrà poi ovviamente essere ripristinato.
L'operatore image richiede cinque argomenti, che
nell'ordine sono:
La procedura deve lasciare sullo stack un oggetto di tipo
stringa che contenga i dati della bitmap; l'operatore image
preleverà questa stringa dallo stack e la interpreterà per
ottenere i pixel. Se la stringa non descrive l'immagine completa, image
chiamerà nuovamente la procedura per ottenere nuovi dati, e
ripeterà la chiamata fino a quando la bitmap non sarà stata
completata. Un eventuale residuo della stringa al termine di ogni
linea della bitmap o della bitmap stessa verrà semplicemente ignorato.
La procedura è libera di usare qualunque mezzo per ottenere la
stringa; può infatti semplicemente contenerla, ottenerla
leggendo un file esterno, od addirittura leggendo i dati nel file stesso
del programma in esecuzione.
Un esempio
Il Programma 1, che è riportato nel Listato 1, produce, una volta downloadato su una stampante PostScript, la stampa riportata in Figura 1.
Tralasciando le solite procedure iniziali di conversione di unità di misura e di stampa cornice, le parti più importanti del programma sono la procedura stampaBitmap e le inizializzazioni svolte nel programma principale. Le variabili inizializzate sono autoesplicative; si noti che si è scelto di fornire il numero di pixel che compongono orizzontalmente e verticalmente la bitmap e le dimensioni geometriche di un singolo pixel, ricavando come variabili dipendenti le dimensioni geometriche della bitmap da usare per scalare gli assi; questo è stato fatto per evidenziare che image produrrebbe una bitmap inscritta in un quadrato unitario, e che i valori per cui è necessario scalare gli assi sono proprio le dimensioni geometriche della bitmap. In realtà è più probabile che vengano fornite queste ultime ed il numero di pixel che compongono orizzontalmente e verticalmente la bitmap, e che si debbano ricavare le dimensioni del pixel come variabili dipendenti.
La procedura stampaBitmap pone ordinatamente sullo stack gli argomenti richiesti dall'operatore image, ed esegue poi quest'ultimo, provocando così la lettura dei dati posti nel programma principale e la stampa della bitmap.
Tralasciamo per un attimo il modo di funzionamento della procedura di ottenimento dati, che analizzeremo più avanti, ed esaminiamo il risultato del programma. L'immagine è stata stampata in verticale e con i pixel della grandezza da noi richiesta; se però il sistema di coordinate fosse stato precedentemente scalato e ruotato, l'immagine avrebbe risentito anche di queste modifiche, risultando di dimensioni ed orientamento corretti, ma rispetto al sistema di riferimento attivo al momento dell'esecuzione della procedura. Questo modo di funzionare dell'operatore image (e più in generale del PostScript) permette, come già evidenziato nelle precedenti puntate, di scrivere programmi di stampa bitmap che possono poi essere inseriti in programmi di stampa di documenti senza nessuna modifica.
Per coloro che sono dotati di pazienza, il Listato 2
contiene una bitmap che può essere sostituita alla parte
corrispondente del Programma 1, e che produrrà un
risultato a sorpresa (l'immagine originale è più grande e con i
toni di grigio, ma ho avuto pietà delle vostre dita).
Gli oggetti di tipo file
La procedura che preleva i dati, passata come argomento
all'operatore image, era
{currentfile StringaBitmap readhexstring pop}
e contiene due operatori che non abbiamo mai incontrato, e che sono essenziali per la comprensione del suo funzionamento.
Il secondo di essi, readhexstring, preleva dallo stack due argomenti, un oggetto di tipo file ed una stringa, legge dal file un certo numero di caratteri ASCII, che vengono interpretati come valori esadecimali, convertiti in caratteri binari, ed infine immagazzinati nella stringa passata come argomento. La stringa viene poi posta sullo stack insieme al valore logico true; se durante la lettura dei caratteri dal file viene incontrata la sua fine, sullo stack viene posta una sottostringa contenente solo i caratteri effettivamente letti, insieme al valore logico false.
Esistono altri due operatori simili che utilizzano gli stessi
argomenti di readhexstring:
- l'operatore readline legge una intera linea dal file, fino al primo carattere di newline e la memorizza nella stringa;
- l'operatore readstring legge un numero di caratteri pari alla lunghezza della stringa, senza trattare in maniera particolare i caratteri speciali come newline.
L'operatore currentfile, che è il cuore della procedura, pone il file correntemente eseguito, cioè lo standard input, sullo stack degli operandi, permettendo di prelevare i caratteri che readhexstring deve leggere dallo stesso file che contiene il programma, iniziando dal carattere successivo a quello di invocazione della procedura (in questo caso la riga successiva alla chiamata di stampaBitmap).
Un momento, direte voi, la LaserWriter che sto utilizzando non possiede nessun disco, quindi non può certo avere dei file al suo interno. Giusto! Si deve però tener presente che gli oggetti di tipo file del PostScript sono generalizzazioni dei semplici file che conosciamo; per chiarire meglio il concetto è necessario fare una breve digressione.
In PostScript un oggetto di tipo file è definito come una sequenza di caratteri, terminata da un carattere di fine file; queste sequenze di caratteri possono essere memorizzate permanentemente da qualche parte (ad esempio su un file di un disco), o possono giungere attraverso uno dei canali di comunicazione di cui è dotato l'interprete PostScript. Gli oggetti di tipo file sono il mezzo tramite cui l'interprete PostScript riceve i comandi e scambia dati con l'ambiente esterno. I file PostScript possono essere di due tipi; di input e di output; esistono poi due file predefiniti, che sono interni all'interprete PostScript stesso, e che sono lo standard input e lo standard output. I caratteri che rappresentano il programma da eseguire giungono normalmente attraverso lo standard input, ed i messaggi che la stampante invia al terminale vengono scritti sullo standard output.
Esistono, in PostScript come negli altri linguaggi, tutta una serie di comandi che permettono di creare file, aprirli, chiuderli, farne la directory, cancellarli e così via. Accade però che la maggior parte delle periferiche PostScript esistenti (con eccezione di modelli molto costosi come la Apple LaserWriter NTX o la QMS 810 Turbo o delle unità di fotocomposizione) non siano dotate, per motivi di economia, di un filesystem (cioè di dischi) proprio, e quindi i relativi interpreti PostScript non possiedano questi operatori.
È questo il caso della Apple LaserWriter; la versione dell'interprete PostScript contenuto in questa stampante non possiede i comandi che gestiscono i file su disco, ma si limita ai soli file interni dell'interprete ed ai comandi che su di essi agiscono. L'utilizzo di questi operatori, dei quali abbiamo già esaminato i principali, rappresenta il solo modo che i possessori di una stampante laser non dotata di filesystem (cioè la maggior parte di noi) ha per utilizzare gli oggetti di tipo file.
Per questo motivo limiteremo il discorso agli operatori che
abbiamo descritto, riservandoci di ampliarlo con la trattazione
di quelli utilizzabili in presenza di un filesystem nel caso che
i numero di lettori ce lo richiedessero.
Modalità di funzionamento di una periferica PostScript
Le modalità di funzionamento di una periferica PostScript
sono tre; rispettivamente server, interattiva ed emulazione.
La più importante è senza dubbio quella di server, in cui essa
esegue ininterrottamente la stessa sequenza di operazioni:
La modalità interattiva è quella che viene attivata quando si esegue il comando executive; da questo momento in poi la periferica PostScript si comporta, come ben sappiamo, nello stesso modo di un personal computer che stia eseguendo l'interprete Basic. I comandi vengono scritti una linea per volta con possibilità di eseguire correzioni, ed eseguiti quando la linea viene terminata battendo RETURN. In questa modalità l'interprete PostScript deve preoccuparsi di fare l'eco dei caratteri ricevuti, fornire il prompt e controllare il cursore.
L'ultima modalità, quella di emulazione, carica sull'interprete un programma scritto in PostScript e memorizzato nelle ROM del controller, che simula il funzionamento di una particolare varietà di stampante o di plotter. Da questo momento in poi tutti i caratteri ricevuti non verranno più interpretati come operatori PostScript, ma come comandi della particolare periferica emulata. In particolari implementazioni del PostScript, la modalità di emulazione può non essere presente, oppure possono esserne previste più di una, selezionabili di solito mediante uno switch oppure via software.
Un comune tipo di emulazione, che tutte le stampanti laser a
300 punti/pollice (tipo Apple LaserWriter) possiedono, è quella
Diablo 630, una stampante a margherita molto diffusa; altri tipi possono
essere quello HPGL (presente ad esempio nella stampante
PostScript QMS 810 Turbo), che è lo standard di comunicazione
supportato dai plotter Hewlett-Packard e di molte altre marche, oppure
HP-JetScript, che è il protocollo delle diffusissime stampanti
laser Hewlett-Packard.
I canali di comunicazione
Una periferica PostScript, come abbiamo detto, riceve i
comandi eseguendo un oggetto di tipo file; questo oggetto può
essere collegato con un file reale su disco, ma più comunemente
è associato ad un canale di comunicazione. I canali di
comunicazione possono essere di tipi diversi;
Alcune periferiche PostScript possiedono poi canali di
comunicazione dedicati al filesystem, che nella maggior parte dei
casi sono canali SCSI (Small Computer System Interface). A questi
canali vengono collegati i dischi che il controller utilizza per
il suo filesystem.
La EEPROM ed i parametri non volatili
Le periferiche PostScript sono dotate di un notevole numero di parametri che controllano il loro funzionamento. Alcuni di questi parametri necessitano di essere permanentemente conservati; è per questo motivo che tutti i controller PostScript in commercio sono dotati di una EEPROM (Electrically Erasable Programmable Read Only Memory - Memoria a sola lettura programmabile e cancellabile elettricamente), in cui tali parametri vengono conservati a sistema non alimentato.
Il numero ed il tipo dei parametri presenti in una particolare periferica può variare; essi sono di solito elencati, insieme agli operatori da utilizzare per modificarli, in una sezione apposita del manuale d'uso della periferica.
Tutti questi parametri sono conservati ed acceduti per mezzo di uno speciale dizionario statusdict, che è separato da systemdict e da userdict, dove, come sappiamo, sono normalmente definiti rispettivamente gli operatori PostScript standard e le procedure definite dall'utente.
Per modificare uno dei parametri persistenti, è necessario utilizzare un programma PostScript che acceda statusdict esplicitamente, eseguendo i comandi statusdict begin.
Come abbiamo visto nel programma di soppressione della pagina di prova (settima puntata), per poter modificare i parametri in statusdict è necessario uscire da loop di server mediante l'apposito operatore exitserver, a cui deve essere inoltre fornito un numero intero che costituisce una password di protezione. Una volta usciti dal loop di server, e prima della successiva fine file, potranno essere eseguiti tutti gli operatori che cambiano il valore di parametri persistenti
La protezione della password è necessaria per due motivi:
- Le stampanti PostScript sono spesso installate in ambiente di rete, non è consigliabile che chiunque possa eseguire modifiche permanenti allo stato della stampante, che potrebbero compromettere il lavoro di altri;
- La EEPROM installata nel controller può essere riscritta per un numero limitato di volte (da 10.000 a 100.000); potrebbe accadere che continui cambiamenti dei parametri danneggino questo componente, rendendo quindi inutilizzabile la funzione di memorizzazione dei parametri persistenti.
Per limitare ulteriormente il numero di riscritture è consigliabile, dovendo scrivere un programma che debba modificare un parametro persistente, strutturarlo in modo da testare lo stato del parametro, ed eseguire l'operazione di scrittura solo se effettivamente necessaria.
Definiremo ora alcuni operatori che leggono o scrivono i più
comuni parametri persistenti:
Si ponga particolare cautela nel modificare la password, che
normalmente a macchina nuova è uguale a 0. Infatti nel caso essa
venga modificata per errore o se ne dimentichi il nuovo valore,
non è più possibile modificare i parametri persistenti, e si
rende necessario l'intervento di un tecnico specializzato per
rendere di nuovo completamente operativa la stampante.
... to be continued.
Nella prossima puntata torneremo ad occuparci di font;
otterremo alcuni effetti speciali interessanti e modificheremo
uno dei font residenti nella stampante.
Copyright © 1985: Marco A. Calamari