Usage as a display system
With PostScript becoming a de-facto standard for printed output, it was natural to consider using the same language for describing the screen output as well. The rapid increase in CPU power in the late 1980s, combined with an interest in windowing systems, led to several attempts to create a display system that used PostScript as its primary display technology.
There are a number of advantages to using PS as the display system. One is that the fonts on other systems required the user to keep not only bitmaps for the screen, but also Type 1 for the printer. Using PS on the display would eliminate this and require only one set. Another advantage is that it allows for the "dumbing down" of printers. When the LaserWriter was released it was the most powerful (and expensive) machine in Apple's lineup, a result of needing considerable processing power and memory to render the page at a "high" resolution of 300 dpi in a reasonable amount of time. In contrast, the 400-dpi printer that shipped with the NeXT platform contained no CPU at all, instead using the computer's CPU to do the rendering and passing off the rendered page as a bitmap to the printer.
But the main advantage in using PostScript as a windowing system is that it allows one to write desktop publishing (DTP) and other graphically-intensive applications with a single set of graphics routines. The same code that is drawing to the window can be used to draw to the printer without any translation. DTP applications on traditional systems require the programmer to construct the GUI editor in the platform's own graphics system (for example, QuickDraw on the Macintosh, or GDI on Microsoft Windows) and then write additional code to translate the graphics into proper PostScript for printing. This often takes up the majority of the programming effort on such projects and is a major source of bugs.
The two main examples of PostScript as a display technology are Display PostScript (DPS) and NeWS. They differed dramatically in terms of where the display logic was applied; in DPS the view system was left to the hosting OS, whereas under NeWS the entire display was written in PS and ran in a single complex interpreter. While certainly very interesting, it's not clear that NeWS is in fact a better approach.
The language
PostScript is a full-fledged, Turing-complete, programming language. Typically, PostScript programs are not produced by humans, but by other programs. However, it is perfectly possible to produce graphics or to perform calculations by hand-crafting PostScript programs.
PostScript is an interpreted, stack-based language similar to Forth. The language syntax uses reverse Polish notation, which makes parentheses unnecessary, but reading a program requires some practice, because one has to keep the layout of the stack in mind. Most operators (what other languages term functions) take their arguments from the stack, and place their results onto the stack. Literals (for example numbers) have the effect of placing a copy of themselves on the stack.
Example:
3 4 add 5 1 sub mul
will compute (3 + 4) * (5 - 1).
A look at what happens in detail:
3 and 4 are both literals, so will push themselves onto the stack. So after these two instructions, the stack will look like this:
4
3
add is an operator, taking the two top-most elements from the stack (3 and 4 in our example), adds them together, and pushes the result onto the stack:
7
Next come two literals again, which will make the stack look like this (note that action is usually constrained to the top of the stack, leaving lower elements unchanged):
1
5
7
Another operator, sub, takes two elements from the top, subtracts the first (higher one) from the second, and pushes the result onto the stack:
4
7
It should be obvious that mul works like the other operators, taking its two arguments from the stack, and pushing their product:
28
But this did nothing more than an old RPN calculator. Of course, PostScript has variables. In detail, it has a dictionary where everything that is not a literal is looked up; on a match, the current value stored under the name is pushed; mismatches will result in an error. To place something in the dictionary one needs the def operator, which takes a name and a value as its arguments. Names are constructed by prefixing (or quoting) with a slash. So
/x1 15 def
will first push the name "x1" on the stack, then the value 15, then execute def which will take both from the stack, and write 15 into the dictionary under the name "x1". Later occurrences of "x1" (not to be confused with "/x1") will push 15 onto the stack as long as the variable is unchanged. This code will increment the content of x1 by 2:
/x1 x1 2 add def
Some real programming language power is offered by { and }. The opening brace puts the interpreter in deferred execution mode, so that everything is just placed on the stack, even operators and other executable objects. The one exception is the closing brace, which takes everything put on the stack since the opening brace, bundles it up into an (anonymous) procedure, and places that on the stack.
This construct is used in various ways, for subroutine definition (the anonymous procedure is assigned to a variable), loops, conditionals, etc. Example:
x1 0 eq { 0 } { 1 x1 div } ifelse
This code first uses the eq operator to test whether the value of x1 is equal to 0; depending on the outcome eq will push true or false onto the stack. After that, two procedures are pushed onto the stack. Then ifelse is executed, which takes three arguments from the stack, and will execute either the second (if the third is true) or first (if the third is false). In summary, 0 results if x1 is 0, 1/x1 is the result for all other cases.
/inc3 { 3 add } def
Here def is used to place something in the