Découverte de PostScript

Menu principal

Cet article présente PostScript du point de vue du développeur.

Vous avez probablement déjà entendu parler de PostScript. Non ? d’EPS alors. Toujours pas ? donc de PDF (ne répondez pas non, je sais que vous mentez).

Pour poser le décor, PostScript a été créé en 1982 et son développement stoppé en 2007 au profit de PDF :

PostScript est un langage de programmation. Oui, oui, vous avez bien lu : PostScript est un langage de programmation. Plus précisément,PostScript est un langage de programmation orienté pile à typage dynamique fort influencé par le langage Forth.

Ressources

Voici quelques ressources utiles sur PostScript :

Fonctionnalités

Ce langage offre les fonctionnalités suivantes :

manipulation de pile
le langage repose entièrement sur une pile
calcul
arithmétique, trigonométrie
manipulation de structures de données évoluées
tableaux, dictionnaires clé-valeur, chaînes de caractères, booléens
manipulation de fichiers
lecture, écriture
structures de contrôles
boucles for, if… then… else…
moteur graphique
transformations, chemins, tracé, remplissage, tests, polices de caractères
gestion des erreurs
à la manière des exceptions dans les autres langages

Étant donnée la destination de ce langage, l’interface utilisateur est réduite à la portion congrue : PostScript peut seulement afficher des informations textuelles ou graphiques, il n’est pas possible d’interagir avec un programme PostScript depuis une souris ou un clavier.

Avant de commencer

Les exemples donnés sur cette page nécessite un interpréteur PostScript. Vous pouvez utiliser GhostScript. Sous Debian/Ubuntu, vous pouvez l’installer avec la commande :

shell
sudo apt-get install ghostscript

Pour lancer l’interpréteur, il suffit de taper dans un terminal :

shell
gs

Grands principes

PostScript est un langage orienté pile. Pour les programmeurs habitués aux langages traditionnels, cela oblige à une gymnastique de l’esprit particulière qui rappelle la notation en polonaise inversée. Par exemple, le Hello world ! en PostScript s’écrit de la façon suivante :

postscript
(Hello world !\n) print

L’exemple donné ci-dessus se comprend comme :

Notes :

PostScript manipule plusieurs types de données :

chaîne de caractères
(chaîne de caractères)
litéral
/ValeurLitérale
nombre
2387
dictionnaire
<< /Clé valeur >>
tableau
[ 2 4 8 16 32 64 ]
procédure
{ (Hello world !) print }

Les commentaires sont signalés par un% et se poursuivent jusqu’à la fin de la ligne à l’instar de// en C++.

Quelques exemples

Calcul et pile

Vous pouvez saisir ces exemples dans l’interpréteur de GhostScript.

postscript
1 2 3 4 5 6 add sub add sub add =

Cet exemple doit afficher la valeur 7. Le calcul laisse une unique valeur au sommet de la pile. La fonction= permet de dépiler cette valeur et de l’afficher. Le pseudo-code suivant explique le fonctionnement de cet exemple :

postscript
(1 (2 (3 (4 (5 6 add) sub) add) sub) add) % Mise en évidence de la portée
(1+(2-(3+(4-(5+6)))))                     % notation in-fixe traditionnelle
(1+(2-(3+(4-11))))
(1+(2-(3+-7)))
(1+(2--4))
(1+6)
7

if… then… else

Il existe deux fonctions de test en PostScript :if etifelse.if consomme deux éléments (une valeur booléenne et le code à exécuter si celle-ci est àtrue) de la pile tandis queifelse en consomme trois (une valeur booléenne, le code à exécuter si la valeur booléenne est àtrue et le code à exécuter si elle est àfalse). Exemple :

postscript
3 4 lt { (3 < 4 !) = } if

La fonctionlt teste si 3 est inférieur à 4.lt retournanttrue,if exécute le code entre accolades et affiche la chaîne3 < 4 !.

En mettant en forme, le code pourrait ressembler à :

postscript
3 4 lt {
	(3 < 4 !) =
} if

Boucle for

En PostScript, la bouclefor prend 4 paramètres sur la pile : la valeur initiale, l’incrément, la valeur finale (incluse !) et enfin le code à exécuter à chaque itération. À chaque itération, for va placer la valeur courante au sommet de la pile à disposition du code. Il est de la responsabilité de la boucle de dépiler cette valeur. Saurez-vous dire le résultat affiché par la ligne suivante ?

postscript
0 1 1 10 { add } for =

Cette fonction est l’équivalent en PHP de :

php
$sum = 0;
for($i = 0; $i <= 10; $i++) {
	$sum += $i;
}   
echo $sum;

L’utilisation intensive de la pile en PostScript a tendance à faire disparaître les variables intermédiaires. Vous aurez sûrement remarqué qu’il y a 5 éléments et non 4 avant l’appel à for. Le premier 0 placé au sommet de la pile correspond à la valeur initiale de la somme. La fonctionadd consomme deux nombres sur la pile et en replace un (le résultat). Sans empiler une valeur au sommet de la pile avant la première itération,add n’aurait pas suffisamment de valeur. Après la dernière itération,add aura laissé la dernière valeur au sommet de la pile et= permet de l’afficher.

Au fait, vous devriez voir 55 ;-)

Une version un peu plus lisible de l’exemple :

postscript
% Initial value
0

% Compute the sum of 1 to 10
1 1 10 {
	add
} for

À l’instar de Lisp ou Scheme, le formatage du code source et les commentaires sont primordiaux à une lecture aisée du code PostScript.

Variables ?

En PostScript, les variables sont gérées à l’aide de dictionnaires.

Définir ou affecter une valeur à une variable se fait avec la fonctiondef :

postscript
% total = 0
/total 0 def

% total = total + 1
/total total 1 add def

Pour être plus précis, la fonctiondef définit un couple clé-valeur dans le dictionnaire courant. Quand l’interpréteur rencontretotal, il recherche dans le dictionnaire courant la clétotal et empile sa valeur.

Fonctions ?

Le dictionnaire courant n’est pas limité à de simples valeurs. Il est également possible d’y stocker du code.

postscript
% Define x addthree x+3
/addthree { 3 add } def

% Display 5+3 = 8
5 addthree =

Note : il existe 2 méthodes pour la création de fonctions avec ou sansbind.bind permet de précompiler la fonction. Démonstration :

postscript
/addthree1 { 3 add } def
/addthree2 { 3 add } bind def

5 addthree1 = % print 8
5 addthree2 = % print 8

/add { sub } def % add becomes sub !

5 addthree1 = % print 2 !
5 addthree2 = % print 8

L’exécution deaddthree1 est complètement dynamique tandis que l’édition de liens deaddthree2 a été réalisée lors de sa définition.

Un programme un peu plus complet

Voici un exemple un peu plus élaboré :

postscript
%!PS-Adobe-3.0
(===========================) =
(Postscript language example) =
(===========================) =

% Initializes random seed
realtime usertime add srand

% Binary random value generator
/brand {
	rand 2 mod
} def

% Create a line of 10 characters
/line (0123456789) def

% Create 10 lines
0 1 9 {
	pop % discard counter (we don't use it)

	% Fill a line with 10 X or O character
	0 1 9 {
		% Test binary value
		brand 0 eq {
			line exch 79 put % display an O
		} {
			line exch 88 put % display an X
		} ifelse
	} for

	% Display a 10 characters line
	line =
} for

% Display the remaining stack (it should print nothing)
pstack

% Explicit exit. The interpreter would otherwise for user input
quit

Pour l’exécuter, il suffit d’utiliser la commande :

shell
gs -dQUIET exemple01.ps

L’option-dQUIET demande à l’interpréteur de ne pas afficher d’information sur sa version.

Après exécution, la sortie devrait ressembler à cela :

stdout
===========================
Postscript language example
===========================
XXOOXXXOXX
OOXOXOOXOO
OOXXXXOXOO
OOXXXOXXOX
OOOXXOOXOO
OXOOXXXOXO
OOOOOXOXOO
XXOXOOOOXO
OXXOXOOOXO
XXXXXXOXOO

Les fichiers PostScript (.ps) commencent par l’entête :

postscript
%!PS-Adobe-3.0

Affichage du grand titre :

postscript
(===========================) =
(Postscript language example) =
(===========================) =

Initialisation du générateur de nombres aléatoires. On utilise les fonctionsrealtime etusertime pour initialiser (srand) le générateur afin de générer un nombre réellement aléatoire mais le but de PostScript est de reproduire, non pas de créer, la séquence sera souvent la même.

postscript
% Initializes random seed
realtime usertime add srand

Création d’une fonction de génération de nombres binaires aléatoires (0 ou 1). Pour cela on utilise la fonctionrand qui retourne un nombre aléatoire entre 0 et 2^31-1. La valeur retournée est calculée modulo 2, ce qui ramène les valeurs entre 0 et 1.

postscript
% Binary random value generator
/brand {
	rand 2 mod
} def

Création d’une chaîne de caractèresline de 10 caractères

postscript
% Create a line of 10 characters
/line (0123456789) def

Bouclefor pour l’affichage de 10 lignes

postscript
% Create 10 lines
0 1 9 {

Comme on n’utiliseras pas la valeur d’itération de cette boucle, on la dépile avecpop sinon les valeurs successives viendront polluer la pile.

postscript
    pop % discard counter (we don't use it)

Remplissage de la ligne (10 caractères) avec des X et des O.

postscript
    % Fill a line with 10 X or O character
	0 1 9 {

Génère une valeur binaire aléatoire et teste les résultat.

postscript
        % Test binary value
		brand 0 eq {

La valeur binaire aléatoire est 0, on place la chaîne sur la pile et on échange sa place avec la valeur d’itération qui correspond à la position du caractère à modifier. L’échange est obligatoire car après l’empilement de la variableline, la pile est dans l’état "position line" alors que la fonctionput requiert 3 éléments dans l’ordre "line position valeur". Le nombre 79 correspond au O majuscule en Ascii.

postscript
            line exch 79 put % display an O
		} {

On fait la même chose pour l’autre possibilité avec un X majuscule (88 en Ascii).

postscript
            line exch 88 put % display an X
		} ifelse
	} for

Affichage de la ligne générée

postscript
    % Display a 10 characters line
	line =
} for

La ligne suivante permet d’afficher l’état de la pile après notre code afin de nous assurer qu’il n’y a aucune fuite mémoire.

postscript
% Display the remaining stack (it should print nothing)
pstack

Appel de la fonctionquit pour sortir de l’interpréteur. Si cet appel n’est pas fait, l’interpréteur reste en mode interactif.

postscript
% Explicit exit. The interpreter would otherwise for user input
quit