PostScript als Programmiersprache

PostScript ist eine Programmiersprache. Das Erscheinungsbild einer Druckseite wird durch ein Programm beschrieben, das auf einer Seite Punkte, Linien und Flächen erzeugt.

Zum Beispiel das folgende PostScript Programm:

%!PS-Adobe-3.0
%%Creator:  Anton Tandiono, Florian Nykrin
%%Title: Devilish Smiley

% horns
newpath 250 370 moveto -50 40 rlineto 250 310 lineto fill
newpath 350 370 moveto  50 40 rlineto 350 310 lineto fill

1 0 0 setrgbcolor

% face
newpath 300 300 100 0 360 arc fill

0 0 0 setrgbcolor

% eyes
newpath 250 350 10 0 360 arc fill
newpath 325 325 moveto 350 350 lineto stroke

% smile
newpath 300 300 70 230 350 arc stroke

% comment
showpage
quit

Wie das geht wir im folgenden kurz erklärt.

PostScript ist eine interpretierte Sprache, d.h. es existiert ein Interpreter, der den Quelltext (PostScript) einliest und direkt ausführt. Ein solcher Interpreter ist zum Beispiel ghostscript. Ghostscript wird aufgerufen durch den Befehl gs.

Zum Beispiel ergibt
$gs smily.ps
eine Darstellung vom Inhalt der PostScript Datei smily.ps am Bildschirm.

Ghostscript kann aber auch interaktiv verwendet werden. Dies ist besonders zum Kennenlernen der Programmiersprache sehr nützlich. Man ruft dazu das Kommando gs ohne eine Eingabedatei auf.

So ergibt sich:
$gs
AFPL Ghostscript 7.04 (2002-01-31)
Copyright (C) 2001 artofcode LLC, Benicia, CA.  All rights reserved.
This software comes with NO WARRANTY: see the file PUBLIC for details.
GS>

Danach wartet Ghostscript auf Eingaben.

Die Ausführung von PostScript Code

Eine Eingabe von PostScript besteht aus Daten und Operationen. (das ist nicht ganz richtig, da Operationen in PostScript auch nur Daten sind, aber davon später mehr).

Die Daten werden von PostScript auf einem Stack abgelegt. Operationen nehmen ihre Operanden vom Stack und legen das Ergebnis wieder auf dem Stack ab.

Das folgende Beispiel verwendet die Daten 10 und 5 und die Operation mul (Multiplikation). Die Operation stack dient um Anzeigen des Stack. Die Anzahl der Elemente auf dem Stack wird nach der Eingabeaufforderung in spitzen Klammern angezeigt.
GS> 10
GS<1> stack
10
GS<1> 5
GS<2> stack
5
10
GS<2> mul
GS<1> stack
50
GS<1>

Zahlen und Arithmetische Operatoren

Sowohl ganze Zahlen als auch Gleitpunktzahlen werden von PostScript erkannt. Operatoren nehmen Ihre Argumente alle vom Stack und legen das Ergebnis wieder auf dem Stack ab. Als arithmetische Operation stehen unter anderem zur Verfügung:

Argumente Name Beschreibung
x y add x + y
x y sub x - y
x neg - x
x y mul x * y
x y div x / y
x y idiv int(x / y)
x y mod x modulo y
x sin sin(x)
x cos cos(x)
x y atan arctan(x /y)
x sqrt Wurzel(x)

Boolesche Ausdrücke

Argumente Name Beschreibung
x y gt x greater than y
x y lt x less than y
x y ge x greater or equal than y
x y le x less or equal than y
x y eq x equal to y
x y ne x not equal to y
true wahr
false falsch
x not nicht x
x y and x und y
x y or x oder y

Stackmanipulation

Argumente Name Beschreibung
x dup x x (dupliziert x)
x1...xn n copy x1...xn x1...xn (dupliziert n Elemente)
x y exch y x (tauscht x und y)
x pop entfernt x vom Stack
n index kopiert den n-ten Eintrag des Stacks auf den Stack

Arrays

Arrays werden in PostScript dynamisch mit fester Länge erzeugt. Die Elemente können einen beliebigen Typ haben und werden von 0 bis Länge-1 indiziert.

Argumente Name Beschreibung
n array erzeugt ein Array mit n Elementen
a i get legt Element i von Array a auf den Stack
a i x put speichert x an der Position i im Array a
a length legt die Länge des Arrays a auf den Stack

Prozeduren und Control-Operatoren

Operatoren werden normalerweise unmittelbar ausgeführt. Wird ein oder mehrere Operatoren jedoch in geschweifte Klammern gesetzt, so entsteht ein neues Datenobjekt, eine Prozedur, die einfach auf dem Stack abgelegt wird. Die Prozedur kann dann später durch Kontrolloperatoren zur ausführung gebracht werden.

Beispiel: die Prozedur {20 add} wird auf den Stack gelegt und durch exec ausgeführt.
GS>10
GS<1>{20 add}
GS<2>stack
--nostringval--
10
GS<2>exec
GS<1>stack
30

Argumente Name Beschreibung
p exec führe Prozedur p aus
b p if falls b wahr so führe Prozedur p aus
b p q ifelse falls b wahr so führe Prozedur p aus sonst q
n p repeat führe Prozedur p n-mal aus
a s e p for für alle Werte von a bis e mit Schrittweite s lege jeweils den Wert auf den Stack und führe die Prozedur p aus.
a p forall führe Prozedur p für alle Elemente von Array a aus
p loop Wiederhole Prozedur p endlos
  exit Beende die innerste Prozedur

Zeichnen mit PostScript

Das Zeichen erfolgt in PostScript durch die Definition eines Pfades, der dann entweder als Strich dargestellt werden kann oder als Umrandung einer Fläche dient, die gefüllt wird. Ein Pfad beginnt mit dem Befehl newpath und endet entweder mit dem Befehl stroke (Linie) oder fill (Füllen). Mehrere Pfade können so nacheinander auf einer Seite platziert werden. Mit dem Befehl showpage wechselt man bei mehrseitigen Dokumenten zur nächsten Seite.

Pfade baut man (unter anderem) mit den folgenden Befehlen:

Argumente Name Beschreibung
  newpath beginnt einen neuen Pfad
  stroke beendet den Pfad, zeichnet einen Strich
  closepath macht aus einem Pfad einen geschlossenen Pfad (Anfangspunkt = Endpunkt)
  fill beendet den Pfad, füllt die Fläche
x y moveto bewegt (absolut) Schreibposition nach x,y
x y lineto Strich von der Schreibposition nach x,y
x y rmoveto bewegt (relativ) Schreibposition um x,y
x y rlineto Strich (relativ) von Schreibposition um x,y
x y r a e arc ein Bogen von Winkel a bis Winkel e um den Mittelpunkt x y mit Radius r
x1 y1 x2 y2 x3 y3 a e curveto eine Bezier Kurve nach x3 y3.
Erklärungsbedürftig ist vielleicht noch der Befehl curveto. Er zeichnet eine Bezier Kurve vom augenblicklichen Punkt nach (x3,y3). Die Punkte (x1,y1) und (x2,y2) sind Kontrollpunkte. Die Kurve die entsteht ist anschaulich die eines geworfenen Steins, dessen Geschwindigkeit am augenblicklichen Punkt gerade der Vektor nach (x1,y1) ist und dessen Geschwindigkeit bei (x3, y3) gerade der Vektor nach (x2, y2) ist. Dazwischen ändert sich die Geschwindigkeit "so wenig wie möglich".

Die Graphik Maschine und ihr Zustand

Die PostScript Graphik Maschine, die für das Zeichnen von Pfaden und die Ausgabe von Fonts (siehe unten) zuständig ist, kann durch verschiedene Parameter beeinflusst werden.

Zum Beispiel kann die Art des Striches und der Füung mit setlinewidth und setrgbcolor beeinflusst werden.

Weiter gibt es Befehle um den Ursprung des Koordinatensystems zu verschieben (translate), zu skalieren (scale) oder zu rotieren (rotate). Wirklich flexibel werden diese Mechanismen aber erst dadurch, dass man den Zustand der Graphikmaschine einfach sichern (gsave) und wieder herstellen (grestore) kann. Dabei wird zum Sichern und Restaurieren ein Stack benutzt, so dass diese Befehle beliebig geschachtelt werden können.

Ein weiterer interessanter Effekt ist Clipping (was hier aber nicht weiter besprochen wird).

Argumente Name Beschreibung
w setlinewidth setzt Strichstärke
r g b setrgbcolor setzt Farbe. Die Werte r, g, und b sind zwischen 0.0 und 1.0
x y translate setzt den Koordinatenursprung
x rotate dreht das Koordinatensystem um x Grad gegen den Uhrzeigersinn
fx fy scale skaliert das Koordinatensystem, mit einem Faktor für die x und die y Richtung
  gsave sichert den Zustand
  grestore stellt den Zustand wieder her.
  currentpoint speichert die aktuelle x und y Koordinate auf dem Stack.

Variable und Dictionaries

Zum Speichern von Werten in Variablen benutzt man in PostScript Dictionaries. Variablen werden durch Namen angesprochen. Namen sind ein eigener Datentyp. Man erkennt Namen daran, dass sie mit einem Schrägstrich anfangen. Zum Beispiel /counter.

Der einfachste Befehl ist def. Er nimmt einen Namen und einen Wert vom Stack und speichert den Wert unter diesem Namen ab. Der Wert selbst kann dann einfach durch Angabe des Namens wieder auf den Stack geholt werden.

Zum Beispiel:

GS>/counter 50 def
GS>counter
GS<1>stack
50

Dictionaries kann man auch selbst erzeugen. Dazu dient der Befehle dict. Dieser Befehl nimmt die Anfangsgrösse vom Stack, erzeugt ein Dictionary und legt es auf den Stack.

Zum Speichern eines Wertes gibt es den Befehl def. Dieser Befehle speichert Paare von Name und Wert im aktuellen Dictionary. PostScript verwaltet einen eigenen Stack von Dictionaries, den Dictionary Stack. Der Befehl def greift dabei immer auf das oberste Dictionary im Dictionary Stack zu. Beim Nachschlagen eines Wertes durch blosse Angabe des Namens wird der ganze Dictionary Stack von oben nach unten durchsucht um einen passenden Namen zu finden.

Der Dictionary Stack wird mit den Befehlen begin und end bearbeitet. Der Befehl begin nimmt ein Dictionary vom Operand Stack und legt es auf den Dictionary Stack, der Befehl end entfernt ein Dictionary vom Dictionary Stack.

Ein Beispiel:
GS>/hallo 111 def
GS>hallo
GS<1>5 dict begin
GS<1>/hallo 222 def
GS<2>hallo
GS<2>end
GS<3>hallo
GS<3>stack
111
222
111

In diesem Zusammenhang ist noch der store Befehl wichtig: store verhält sich wie def es braucht einen Namen und einen Wert und speichert den Wert unter dem gegebenen Namen auf den Dictionary Stack. Während def aber immer auf das oberste Dictionary zugreift durchsucht store den ganzen Dictionary Stack von oben her nach dem genannten Namen und speichert den Wert dort. Nur wenn der Name überhaupt nicht gefunden wird legt store den Namen im obersten Dictionary neu an (so wie def).

Argumente Name Beschreibung
i dict erzeugt ein Dictionary mit i Einträgen
d begin legt Dictionary d oben auf den Dictionary Stack
end entfernt das oberste Dictionary vom Dictionary Stack
n x def legt den Wert x unter dem Namen n im obersten Dictionary ab
n x store sucht im Dictionary Stack nach n, legt dort den Wert x ab. Sonst wie def.

Dictionaries kann man auch explizit verwenden. Dazu gibt es die Befehle get und put.

Argumente Name Beschreibung
d n w put speichert Wert w unter dem Namen n in Dictionary d
d n get legt den Wert des Namens n im Dictionary d auf den Stack

Beispiel:
GS>/mydict 10 dict def
GS>mydict /x 123 put
GS>mydict /x get
GS<1>stack
123

Strings und Fonts

Fonts sind unter PostScript eigentlich nichts anderes als Sammlungen von Code, die für jeden Buchstaben einen Pfad zeichnen und ausfüllen. Es gibt aber einige High-level Funktionen, die die Arbeit mit Fonts und Strings erleichtern. Fonts und Strings sind zwei neue Datentypen. Fonts werden mit findfont erzeugt. Strings erzeugt man durch einschließen einer Zeichenkette in runde Klammern.
Argumente Name Beschreibung
n findfont sucht einen Font mit Namen n und legt ihn auf den Stack.
f i scalefont nimmt Font f und Zahl i, skaliert den Font auf i Punkt und legt ihn wieder auf den Stack.
f setfont nimmt den Font f vom Stack und macht ihn zum aktuellen Font
s show nimmt den String s vom Stack und zeigt ihn im aktuellen Font an.
Beispiel
GS>/Helvetica findfont
Loading Helvetica ...
GS<1>36 scalefont
GS<1>setfont
GS>100 100 moveto
GS>(Hello world!) show

Format eines PostScript Files

Comments beginnen mit einem Prozent Zeichen. Eine Besonderheit ist der Kommentar %!PS, der in der Regel in der ersten Zeile einer PostScript Datei steht.
%!PS-Adobe-2.0
%%Creator: I myself, Copyright 2005 Martin Ruckert
%%Title: The explanation file
%%Pages: 5
%%PageOrder: Ascend
%%BoundingBox: 0 0 596 842

Er identifiziert den nachfolgenden Text als PostScript Code. Das veranlasst dann zum Beispiel den Drucker diesen Text nicht direkt als Text auszugeben sondern ihn als PostScript Programm zu interpretieren.