Découverte de PostScript

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 :

É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 :

sudo apt-get install ghostscript

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

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 :

(Hello world !\n) print

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

Notes :

PostScript manipule plusieurs types de données :

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.

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 :

(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 et ifelse. if consomme deux éléments (une valeur booléenne et le code à exécuter si celle-ci est à true) de la pile tandis que ifelse 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 :

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

La fonction lt teste si 3 est inférieur à 4. lt retournant true, if exécute le code entre accolades et affiche la chaîne 3 < 4 !.

En mettant en forme, le code pourrait ressembler à :

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

Boucle for

En PostScript, la boucle for 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 ?

0 1 1 10 { add } for =

Cette fonction est l’équivalent en PHP de :

$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 fonction add 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 :

% 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 fonction def :

% total = 0
/total 0 def

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

Pour être plus précis, la fonction def définit un couple clé-valeur dans le dictionnaire courant. Quand l’interpréteur rencontre total, 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.

% 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 sans bind. bind permet de précompiler la fonction. Démonstration :

/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 de addthree1 est complètement dynamique tandis que l’édition de liens de addthree2 a été réalisée lors de sa définition.

Un programme un peu plus complet

Voici un exemple un peu plus élaboré :

%!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 :

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 :

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

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

%!PS-Adobe-3.0

Affichage du grand titre :

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

Initialisation du générateur de nombres aléatoires. On utilise les fonctions realtime et usertime 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.

% 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 fonction rand 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.

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

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

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

Boucle for pour l’affichage de 10 lignes

% Create 10 lines
0 1 9 {

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

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

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

    % 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.

        % 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 variable line, la pile est dans l’état "position line" alors que la fonction put requiert 3 éléments dans l’ordre "line position valeur". Le nombre 79 correspond au O majuscule en Ascii.

            line exch 79 put % display an O
        } {

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

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

Affichage de la ligne générée

    % 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.

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

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

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