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 :
- PDF est une évolution de PostScript,
- EPS est un sous-ensemble de PostScript.
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 :
- PostScript language reference, third edition : la référence du langage PostScript par Adobe ; la liste des opérateurs se trouve au chapitre 8.1 Operator summary, page 508.
- PostScript FAQ sur Wikibooks : cette FAQ regorge de snippets et d’astuces bien pratiques pour se sortir des problèmatiques les plus courantes,
- The PostScript markup library / TinyDict : une bibliothèque de fonctions permettant, par exemple, de formater un paragraphe.
- GhostScript, un interpréteur PostScript : l’interpréteur utilisé pour les exemples de cette page.
Fonctionnalités
Ce langage offre les fonctionnalités suivantes :
- manipulation de 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.
É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 :
- place la chaîne "Hello world !" au sommet de la pile,
- appelle la fonction print.
Notes :
- les chaînes de caractères en PostScript sont entourées de parenthèses et non de quotes simples ou doubles,
- chaque élément est séparé par un ou plusieurs caractères blancs (espace, tabulation, retour à la ligne).
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 }
- etc.
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