Learning PostScript by comparing it to Python

Updated 2023-07-21

PostScript is primarily thought of as a document format (you may have come across .ps files), but it is, in-fact, a genuine Turing-complete programming language. And a fun one to learn and use.

This page presents a small PostScript program to generate the Chicago flag shown above. And as a learning exercise, I’ve written a line-by-line translation of the PostScript file into a Python script that does the same thing.

PostScript

I won’t attempt to write full tutorial of the PostScript language here, but with a few notes, I think someone with some programming experience can understand the following program pretty well. Over-simplyfying things, of course,

Code

Warning: this is not "idiomatic" Python. You would not want to write a python script organized in this way. I've done it to match the PostScript code as closely as possible. There are a few weird hacks (see flag_utils.py) that allow it to line-up with the PostScript and still run and generate the same output.

PostScriptPython
%
%
/width     720 def
/height    480 def
/numstar   4   def
/pw        16  def % pw,ph control size of points
/ph        60  def
/npoint     6  def % num points on a star
/barheight 80  def
/starRGB       [   1    0    0] def % red
/barRGB        [.702 .867 .949] def % light-blue
/backgroundRGB [   1    1    1] def % white

/setcolor {
    % this allows color to be specified as
    % an array [r,g,b] rather than 3 separate vars
    aload pop setrgbcolor
} def

/background {
  backgroundRGB setcolor
  0 0 width height rectfill
} def

/bars {
  0     120  moveto
  width 0    rlineto

  0     360  moveto
  width 0    rlineto
  
  barheight setlinewidth
  barRGB setcolor
  stroke
} def

% draw one point for in a star (a triangle)
/point  {
  pw neg  0      moveto
  pw      ph     rlineto
  pw      ph neg rlineto
} def

% draw one star
/star {
  /degrees 360 npoint div def
  npoint {
    point
    degrees rotate
  } repeat
} def

/stars {
    
   % compute horiz distance between stars
   % (equally spaced with pad 10 on both sides)
   % ie sdist = (width - 20)/(numstar+1)
   /sdist width 20 sub numstar 1 add div def

   % move to first:
   10 sdist add height 2 div translate
   numstar {
     star
     sdist 0 translate
   } repeat
   
   starRGB setcolor
   fill
} def

%---main---
background
bars
stars

showpage
from flag_utils import *

width     = 720
height    = 480
numstar   = 4
pw        = 16 # pw,ph control size of points
ph        = 60
npoint    =  6  # num points on a star
barheight = 80
starRGB       = [   1,    0,    0] # red
barRGB        = [.702, .867, .949] # light-blue
backgroundRGB = [   1,    1,    0] # white

def setcolor(rgb):
    setrgbcolor(*rgb)

    

    
def background():
    setcolor(backgroundRGB)
    rectfill(0,0,width,height)

    
def bars():
    moveto(0,     120)
    rlineto(width, 0)

    moveto(0,     360)
    rlineto(width, 0)
  
    setlinewidth(barheight)
    setcolor(barRGB)
    stroke()

    
def point():
    "draw one point in a star (a triangle)"
    moveto(-pw, 0)
    rlineto(pw, ph)
    rlineto(pw, -ph)


def star():
    "draw one star"
    degrees = 360 / float(npoint)
    for i in range(npoint):
        point()
        rotate(degrees)


        
def stars():
    
    # compute horiz distance between stars
    # (equally spaced with pad 10 on both sides)
    # 
    sdist = (width - 20)/(numstar+1)

    # move to first:
    translate(10+sdist, height/2)
    for i in range(numstar):
        star()
        translate(sdist,0)

        
    setcolor(starRGB)
    fill()

    
if __name__ == "__main__":
    background()
    bars()
    stars()

    write_png("/tmp/py-flag.png", globals())

Run it

One way to generate an image from a PostScript file is using the common utility convert (ImageMagick),

$ convert -page 720x480 flag.ps flag.png

Note, we set the page size to the same width and height defined in the file.

A popular PostScript interpreter is Ghostscript (convert uses Ghostscript internally). If you have it installed, you can similarly run,

$ gs -sDEVICE=png16m -g720x480 -o flag.png flag.ps

Variations

Note that flag.ps is written in a very parametric way: nearly all the constants are defined by variables at the top of file.

You can try changing a few,

/numstar     2 def
/barheight  20 def
/npoint     10 def
/ph        100 def
/starRGB       [ 1  1  0] def
/barRGB        [ 1 .5 .5] def
/backgroundRGB [.5 .7  1] def
    

and you'll get something like:


github.com/kts/ps-chicago-flag