PostScript is a page-description language and programming language developed by Adobe and released in 1984. Instead of storing a page as a finished grid of pixels, a PostScript program contains instructions for constructing it: create paths, draw and fill shapes, position text and images, change colors, transform the coordinate system, and so on. A PostScript interpreter executes those instructions and turns the resulting page into pixels for a screen, printer, or imagesetter.
One of PostScript's important ideas was device independence. The program describes the page in an abstract coordinate system rather than for one particular printer resolution. The same page description could therefore be rendered on a desktop laser printer or on the much higher-resolution equipment used by a commercial printer. This separation between creating a document and rasterizing it for the final output device became one of the foundations of digital publishing.
PostScript was a major part of the desktop-publishing revolution of the 1980s. Apple's PostScript-based LaserWriter, introduced in 1985, together with the Macintosh and Aldus PageMaker, made it practical to design complete pages on a personal computer and send them directly to high-quality printers. What had previously required specialized typesetting and prepress equipment could increasingly be done on the desktop.
EPS, or Encapsulated PostScript, adapted the same language for graphics intended to be placed inside other documents. An EPS file is essentially a self-contained, normally single-page PostScript program describing an illustration, logo, chart, or other graphic. For many years it was one of the standard ways of exchanging vector artwork between publishing applications.
Today, PostScript is no longer the normal format for exchanging finished documents. PDF has largely taken over that role, including in modern print production, and many current RIPs process PDF directly. PDF inherited the PostScript imaging model, but stores the resulting page description in a structured form rather than being a general-purpose program that must be executed.
PostScript has not disappeared, however. It is still encountered in professional and legacy
printing workflows, printer output, prepress systems, old document archives, and EPS artwork,
and applications such as Illustrator and InDesign still support PostScript-related workflows.
It is also interesting in its own right: a .ps file is not merely a graphics file
but an executable program, with variables, procedures, dictionaries, loops, conditionals, files,
and stacks.
PostScript was extended twice, and Adobe called the results language levels rather
than versions. A level is a capability set: an interpreter reports the highest one it supports
through the languagelevel operator, and a program can branch on it — Adobe's own
files do, and can draw a page quite differently depending on the answer.
| Level | Year | What it added |
|---|---|---|
| 1 | 1984 | The original language: a stack machine with variables, procedures and control flow; the imaging model of paths that can be filled, stroked or used to clip; a coordinate system that can be moved, scaled and rotated; scalable outline fonts (Type 1) and programmable ones (Type 3); images; and a stack of saved graphics states to draw inside and step back out of. Color is device color only — gray, RGB and CMYK. |
| 2 | 1991 | Filters — encoded and compressed data streams (ASCII-safe encodings, LZW, run-length, JPEG), which is what lets a program carry images and other binary payloads inline. Named resources and reusable forms. Patterns as a kind of color. Device-independent color, so a color can mean the same thing on two different devices, plus indexed palettes and separation (spot) colors. Composite fonts, for scripts needing more than 256 characters. Configuring the output device from inside the program. Better memory management, including garbage collection. And graphics states as objects that can be saved and reinstated in any order, rather than only unwound. |
| 3 | 1997 | Smooth shading — a real gradient for the first time, rather than one painted by hand. Zip compression, and embedded data a program can rewind and read again rather than only once. DeviceN color for multi-ink printing, and masked images. CID-keyed fonts for large character sets such as Chinese, Japanese and Korean. Idiom recognition, letting an interpreter substitute a faster implementation for a procedure it recognizes. |
The three formats share one imaging model but differ in what they are, and that decides how much of a document survives being opened.
PostScript is a program. The page does not exist in the file; it is what
happens when the file runs. A .ps file can compute its own artwork in a loop, so
the only general way to learn what is on the page is to execute it.
PDF (1993) is the same imaging model without the programming language. Adobe kept the paths, the color spaces, the font machinery and the filters, and threw away execution: a PDF is a structured collection of objects, and a page's marks are listed with no loops, procedures or variables. Everything is stated rather than computed, so a reader can find the fifth page without running the first four.
An Illustrator file is one of three containers, and which one decides how much of the document survives:
| Variant | What the file really is |
|---|---|
Pre-v9 .ai |
A PostScript program, written in Illustrator's own set of drawing procedures, so sending the file straight to a PostScript printer prints it. |
v9+ .ai |
A PDF file. It opens in any PDF reader because the PDF part is a complete rendering of the artwork; the editable Illustrator document rides alongside it in the same file. |
Illustrator .eps |
Both at once. The printable PostScript comes first, and the complete Illustrator document follows it, compressed and encoded. The artwork is in the file twice. |
Hence the difference that matters when you open one of these files: running the PostScript gives you a picture; reading the Illustrator syntax gives you a document. Layers, groups, object names, editable paths, gradients and spot colors survive only the second way — printing flattens every one of them into anonymous marks. So wherever Illustrator syntax is available, this application reads it rather than running it, and falls back to the interpreter only for a file that has none.
Each container has its own page with the full story of what this application reads and where the limits are: Adobe Illustrator, EPS, and PDF. This page documents the shared foundation they all draw on — the PostScript language and the interpreter behind it.
This application includes a built-in PostScript editor and debugger for
writing, running, and stepping through .ps and
.eps programs
directly, with breakpoints and live inspection of the operand, call, and dictionary stacks.
See the PostScript Debugger page for how to open it
and how it works.
The interpreter reports languagelevel 3. It implements 233 operators, which
covers the arithmetic, stack, dictionary, control, string, array, type, file and matrix
machinery in full, and the graphics operators partly; the rest are not supported.
| Category | Operators |
|---|---|
| Stack | pop dup exch index roll clear count counttomark cleartomark mark |
| Arithmetic | add sub neg abs div idiv mod mul exp sqrt ln log sin cos atan floor ceiling round truncate bitshift rand srand |
| Relational, boolean, bitwise | eq ne ge le lt gt not and or xor |
| Control | exec stopped stop if ifelse for repeat loop exit quit execstack countexecstack |
| Arrays | array packedarray aload astore forall |
| Dictionaries | dict begin end def undef known where load store currentdict systemdict userdict dictstack countdictstack cleardictstack maxlength internaldict |
| Polymorphic | length get put copy getinterval putinterval |
| Strings, types, conversion | string type cvn cvs cvrs cvi cvr cvx cvlit xcheck rcheck wcheck status readonly executeonly noaccess search anchorsearch token |
| Files | file run currentfile closefile flushfile resetfile filter read readstring readline readhexstring bytesavailable fileposition setfileposition status deletefile renamefile filenameforall echo print flush write writestring writehexstring |
| Path construction | newpath moveto lineto curveto rmoveto rlineto rcurveto arc arcn arcto arct closepath currentpoint pathforall pathbbox flattenpath reversepath strokepath setbbox |
| User paths | upath uappend ufill ueofill ustroke ustrokepath ucache ucachestatus setucacheparams
— both the plain-array and encoded forms |
| Painting | fill eofill stroke rectfill rectstroke image colorimage imagemask |
| Smooth shading | shfill — axial and radial shadings (types 2–3) become real gradients in
the document, and mesh shadings (types 4–7) become real meshes, in any color space and
with every function type. Not yet painted: function-based shadings (type 1), which are
read and reported rather than drawn |
| Clipping | clip eoclip rectclip clippath initclip clipsave cliprestore — the clip is
tracked, not applied to marks; see the caveat below |
| Graphics state | gsave grestore grestoreall save restore gstate setgstate currentgstate initgraphics setlinewidth setlinecap setlinejoin setmiterlimit setdash setflat setstrokeadjust setoverprint — dashes are real: setdash takes a pattern of any length with a phase, and both stroke and strokepath honor it and every matching current…. Caps, joins and the miter limit reach the mark too |
| Color | setgray setrgbcolor setcmykcolor sethsbcolor setcolorspace setcolor findcmykcustomcolor findrgbcustomcolor setcustomcolor setseparationgray and every matching current… — see the caveat below |
| Device-independent color | CIEBasedA CIEBasedABC CIEBasedDEF CIEBasedDEFG Lab ICCBased Separation DeviceN
— all converted through the real color pipeline, including an embedded
/ICCBased profile and a /Separation or /DeviceN
tint transform. Rendering intents are only remembered — the intent a profile's own
header asks for is the one used. |
| Coordinate system and matrices | matrix initmatrix defaultmatrix identmatrix currentmatrix setmatrix invertmatrix concat concatmatrix translate scale rotate transform dtransform itransform idtransform |
| Resources | defineresource undefineresource findresource resourcestatus resourceforall |
| Fonts | findfont selectfont scalefont makefont setfont currentfont rootfont definefont undefinefont findencoding show ashow widthshow awidthshow xshow yshow xyshow glyphshow cshow kshow stringwidth charpath, plus StandardEncoding and ISOLatin1Encoding — see Fonts |
| Pages | showpage copypage erasepage |
| Output and diagnostics | = == pstack |
| VM and interpreter parameters | bind null version languagelevel usertime realtime setglobal currentglobal setshared currentshared gcheck startjob setpacking currentpacking setpagedevice currentpagedevice setsystemparams currentsystemparams getsystemparam setuserparams currentuserparams getuserparam setvmthreshold setcachelimit cachestatus defineuserobject execuserobject undefineuserobject flushpage |
| Standard dictionaries | systemdict userdict globaldict statusdict shareddict errordict handleerror, and $error |
Using any of these raises an error, and the error says which operator it was.
| Group | Operators |
|---|---|
| Images | customcolorimage |
| Insideness testing | infill ineofill instroke inueofill inufill inustroke |
| Patterns | makepattern setpattern |
| Font machinery | composefont eexec setcachedevice setcachedevice2 setcharwidth |
| Forms | execform |
| Binary object sequences | printobject writeobject setobjectformat currentobjectformat |
| Devices | nulldevice banddevice framedevice renderbands |
(name) (w) file — and (w+) and (a) — write into
user/postscript, the same folder (name) run reads from, so a program
can write a file and a later run can read it back. deletefile and
renamefile work in that folder too, and filenameforall lists what is
in it — including files in subfolders, named the way run names them
(examples/lib.ps). Names must stay inside the folder: no ../, no
absolute paths, nothing outside it. Every change is announced in the Output pane. A program can
replace or remove a file that is already there, but a rename onto a name that is already taken
is refused rather than allowed to overwrite. If you have the file open in a tab, the tab stays:
a rename carries it to the new name, and a delete leaves the tab holding the only remaining copy
of the text, marked unsaved.
.ps you merely open as a document gets none of this and raises
invalidfileaccess instead — for run and (r) file as well
as for writing, and before it checks whether the file exists, so a refused program cannot use
the error to map what is on your disk.
status is the one exception, and it matches Ghostscript: it never refuses. A file
it may not look at answers false, the same as a file that is not there.
Anything a program writes to %stdout or %stderr appears in the Output
pane, and (%stdout) (w) file opens a file object on the same stream.
image, colorimage and imagemask handle the common forms —
the Level 1 operand form and the Level 2 dictionary form alike — at 1, 2, 4 or 8 bits per
sample. 12 and 16 bits per sample are not supported, and neither is
colorimage's one-source-per-component form.
An image drawn while a /CIEBased, /ICCBased, /Lab,
/Indexed, /Separation or /DeviceN space is current is
converted through the real color pipeline, with the image's /Decode array honored.
A space with four color components — a four-ink /ICCBased, a
/DeviceN with four inks, any /CIEBasedDEFG — reads as ink: the four
samples are kept as they were written, in a CMYK image, and the space converts them at draw
time. colorimage with four components is ink too; having no space to state, it is
interpreted in the working CMYK profile, the same rule the rest of the application applies to
untagged ink.
Only the << … >> image form reads the current color space. The
five-operand image is always DeviceGray and colorimage is
always a device space, whatever setcolorspace was last given — that is Ghostscript's
behavior too, and it is what PLRM describes. The spaces that cannot be carried into an image are
listed under Accepted, but with No Visible Effect below.
| Operators | What actually happens |
|---|---|
clip eoclip rectclip |
The clip is recorded on the graphics state — clippath reports it,
and gsave/grestore carry it — but marks outside it are still
painted, and a second clip replaces the first instead of intersecting with it. |
rectstroke with a matrix |
The rectangle is stroked; the optional matrix operand, which in PLRM skews the pen, is accepted and ignored. |
clipsave cliprestore |
They save and restore the recorded clip, so a program that brackets its drawing with them runs normally; the clip itself is still not applied to marks. |
<< … >> image in a space with two, or more than four,
color components |
A /DeviceN with two inks, or with five. An image is four bytes per pixel
and the meanings those bytes can carry are gray, RGB and CMYK, so a two- or five-ink
image has no shape to sit in: its first three samples are read as though they were RGB
device values, and it says so on %stderr. One, three and four components
are all carried — see Images in a device-independent space above.
An /Indexed image is not affected however wide its base is
— its samples are one-component indices and the palette is a table rather than an
image, so /Indexed over a five-ink /DeviceN works where a
direct one does not. A fill in any of them is converted normally. |
setcolorspace with /Pattern |
It is remembered, so operand counts stay right and currentcolorspace
answers properly — but it paints black, and says so on %stderr. Every other
family — the CIE spaces, /Lab, /ICCBased,
/Indexed, /Separation and /DeviceN — is converted
for real; see Implemented above. |
setscreen setcolorscreen sethalftone settransfer setcolortransfer setundercolorremoval setblackgeneration
and every matching current… |
Halftone screens and transfer functions describe a marking engine, and drawing into a
layer graph there is none. So each one is remembered and handed straight back:
what currentscreen answers is what setscreen was last given,
settransfer sets all four functions and currenttransfer reads
the gray one, and the whole set is carried by gsave/grestore.
That is what lets Illustrator's own prolog — which composes onto
currenttransfer and puts currentundercolorremoval back
afterwards — run instead of dying at its first line. Nothing about the marks changes.
With nothing set, currentscreen reports 60 lpi. |
setcolorrendering findcolorrendering setoverprintmode setsmoothness setcmykoverprint setcacheparams
and every matching current… |
The same bargain: stored, answered back, and with no effect on the page.
findcolorrendering always reports
/DefaultColorRendering false, which is Ghostscript's answer too when no
rendering dictionaries are installed. There is no glyph bitmap cache here, so the cache
parameters are purely a round trip. |
setsystemparams setuserparams setdevparams setvmthreshold
and the matching current… / get… |
Parameters are kept in a dictionary per family and read back from it, so a program can
test for a parameter by catching the undefined that an unknown key raises.
The devices setdevparams knows are the ones this interpreter really has.
setdistillerparams and currentdistillerparams are
deliberately absent: they exist only in Acrobat Distiller, and every Adobe
prolog asks /currentdistillerparams where to find out whether it is
running inside one. Answering yes led those files straight into a branch that reads
real Distiller settings — and killed them there. |
copypage |
Ends the page and warns. Level 1's behavior of carrying the retained marks onto the next page is not reproduced. |
flushfile, and flush |
Nothing is held back — a completed line has already reached the Output pane, and an output file's bytes are handed over by closefile. Note flush does not end a line, so (total: ) print flush 42 = stays on one. |
vmreclaim vmstatus |
Accepted; JavaScript's collector is not a PostScript program's to schedule, and vmstatus answers placeholders. A program that calls vmreclaim in a loop — which a real interpreter needs — runs unchanged. |
A PostScript program does not just draw with its fonts, it computes with them — asking
a font dictionary for its /FontMatrix and /FontBBox, positioning text
by measuring it with stringwidth — so every name it asks for has to resolve to a
real font dictionary in the middle of a run.
Each name is looked up when the program reaches findfont, in this order:
Courier-Oblique lands on an
italic face, not on the roman one. The dictionary keeps the name the program
asked for, which is what you will see in the debugger. Ghostscript renames it to
the substitute's; nothing in the language depends on either choice.
charpath appends the resolved face's real glyph outlines to the current path, so
outlined text and text used as a clipping path both work. The one narrow exception depends on
the font file rather than on anything the program did: PostScript reaches a glyph
by name, and a font that carries no glyph names of its own — which is what recent
system fonts on Windows ship — has its names reconstructed from its character map. That reaches
every glyph a character code selects, but not a ligature, a small capital or a stylistic
alternate, which no code selects and which charpath therefore has no name to ask
for. In the rarer case of a face with neither names nor a usable character map,
charpath reports it once on %stderr and the program carries on; text
drawn with show is unaffected either way.
PostScript has two numeric types: integer and real. The specification does not mandate their size; it requires only that an integer which overflows the implementation's range be converted to a real. Modern Ghostscript uses 64-bit integers, while this implementation is limited to 53 bits of integer precision, so it is at 253 that a result turns real. Reals are float32, as in Ghostscript.
The filter operator supports ASCIIHexDecode,
ASCII85Decode, RunLengthDecode, SubFileDecode,
LZWDecode (honoring /EarlyChange), FlateDecode and
DCTDecode, plus the one encoding filter, NullEncode.
Any other filter name is undefined: filter recognizes the names
above and raises undefined for anything else, naming the filter it did not know.
That covers ReusableStreamDecode and the encoding filters
ASCIIHexEncode, ASCII85Encode, RunLengthEncode,
LZWEncode and FlateEncode, which real interpreters do have.
Note that a filter name is not an operator and is not looked up in a dictionary — it is
an argument to filter, so /ASCIIHexEncode load is undefined even in
Ghostscript, where the filter itself works.
Decoding filters are not positionable: fileposition and
setfileposition on one raise ioerror, as PLRM specifies.
FlateDecode cannot read straight from currentfile and raises an error
if asked to: the end of a compressed stream cannot be found, so the rest of the program would
be read as part of it. Wrap it the way every real file does —
currentfile /ASCII85Decode filter /FlateDecode filter — and the outer filter finds
the end.
DCTDecode is JPEG, and unlike FlateDecode it can read straight from
currentfile, because a JPEG marks its own end. The width, height and component
count in the JPEG's own header are ignored in favor of the operands you give
image, and CMYK arrives as Adobe stores it, which is inverted. A
/ColorTransform 0 parameter on a three-component JPEG is refused: it asks for the
untransformed YCbCr samples, and the decoder's output space comes from the file's own markers.
PostScript source must be ASCII or Latin-1. Anything above 0xFF — smart quotes,
box-drawing characters pasted from a terminal, em dashes — is replaced with ?.
When a program fails, the message goes to the Output pane prefixed with error:,
the status line reads Stopped on an error, and the preview keeps whatever had been
drawn up to that point. The stacks stay as they were, so you can look at what the program was
holding when it went wrong. errordict, $error and
handleerror exist, so a program that installs its own error handler gets to run
it.
PostScript is old enough that most of what was written about it is now scattered across archives rather than sold in bookshops, but almost all of it survives. These are the places worth knowing: the specifications the language is defined by, and the programs — real hand-written PostScript, not printer output — that are the interesting thing to open in the editor.
The PostScript Language Reference, universally called the PLRM or the Red Book, is the definition of the language and settles every question about operator behavior. The Blue Book is the tutorial that came with Level 1 and is still the friendliest introduction. DSC is the comment convention that turns a program into a structured document, and is what tells a reader where the pages and the bounding box are.
Ghostscript's examples directory is the classic small corpus: a page of
interlocking Escher butterflies, nested color circles, a Vasarely grid, an alphabet specimen.
They are short, they are executable, and they are a good first test of any interpreter.
Don Lancaster's Guru's Lair is the other kind of resource entirely — decades of PostScript
written by someone who treated it as a general-purpose programming language rather than a
printer format. Fractal ferns, Sierpinski triangles, bubble sorts, typography experiments and
his own gonzo.ps utility library, all as downloadable source.
CTAN is enormous and unsorted for this purpose, but the TeX world produced PostScript for
decades and plenty of package manuals and examples are still shipped as .ps files.
alphabet.ps, colorcir.ps, escher.ps, doretree.ps, snowflak.ps, vasarely.ps, waterfal.psSun's NeWS window system, in the late 1980s, ran PostScript as the language applications themselves were written in: windows, menus, input events, lightweight processes and networking were all reached from PostScript code. People really did write applications in it — a HyperCard-like authoring environment (GoodNeWS, later HyperNeWS, then HyperLook), the PSIBER Space Deck, a version of SimCity, and the famously networked PizzaTool.
In 2022 the Computer History Museum released the source code of Adobe's original PostScript interpreter — a version from February 1984, before the LaserWriter shipped — with Adobe's permission. It is the implementation rather than anything you can run here, but it is the other half of the story this page tells.