seriot.ch

About > Projects > Postscript Stairs

Postscript Stairs

Drawing isometric stairs in Postscript

2022-10

Isometric Stairs

I stubled upon this image.

Wondering how do to something similar in minimal Postscript, I came up with the following result in 11 lines (tweet).

stairs_tiny.ps

<</PageSize[800 600]>>setpagedevice/S 5 def/f{/M exch def/C exch def/h exch def
/w exch def gsave M concat C setgray 0 0 w h rectfill 0 setgray 0 0 w h
rectstroke grestore}def/b{/H exch def/W exch def gsave W H 2 div 0.8[1 0 -1 1 0
S]f W S 1[1 0 0 1 0 0]f H 2 div S 1[-1 1 0 1 0 0]f grestore}def/U{/H2 exch def{
H 2 div neg H 2 div S add translate W H2 b}repeat}def/D{/H exch def{H 2 div H 2
div neg S sub translate W H b}repeat}def/L{/W2 exch def{W2 neg S neg translate
W2 H b}repeat}def/R{/W2 exch def{W S translate W2 H b}repeat}def/s{save}def/r{
restore}def/W 60 def/H 60 def 100 200 translate 2 30 L 2 30 D s 12 5 D 1 60 D 1
265 R 9 10 R 4 5 R r 2 30 R 1 30 U 1 60 U 1 60 R 1 60 U 5 10 R 1 30 R 1 60 R s 
5 20 D s 10 5 L r s 18 5 D r 10 5 R r 10 10 R 1 60 R s 20 10 D 1 60 D 10 5 R 1
110 R r s 10 10 U 1 60 U 10 20 R r 10 10 R 1 60 R 20 10 D

A colleague of mine asked whether I worked for Poudlard, so here is a short walk-though of the full, expanded Postscript code stairs_full.ps.

First, we set the size of the page, no wonder.

<< /PageSize [780 600] >> setpagedevice

We set the height of steps.

/S 5 def

We now define a procedure to draw a face of a brick.

It takes 4 parameters on the stack: width, height, gray level, and a tranformation matrix.

/f {
    /M exch def
    /C exch def
    /h exch def
    /w exch def

    gsave
    M concat
    C setgray
    0 0 w h rectfill
    0 setgray
    0 0 w h rectstroke
    grestore
} def

We now define a procedure to draw a brick, ie its 3 faces.

The procedure takes 2 parameters on the stack: the width and height of the brick.

The procedure calls the f procedure that we've previously definded, with a transformation matrix for each of the three faces.

/b {
    /H exch def
    /W exch def

    gsave
    W       H 2 div 0.8 [  1 0 -1 1 0 S ] f
    W       S 1         [  1 0  0 1 0 0 ] f
    H 2 div S 1         [ -1 1  0 1 0 0 ] f
    grestore
} def

We now define the Up (U), Down (D), Left (L) and Right (R) procedures.

Each take 2 parameters on the stack: length and number of repeats.

Each of these procedures moves to the position where to draw the new brick (lower left corner) and repeatedly call the b procedure to draw several ones if needed.

/U { /H2 exch def { H 2 div neg H 2 div S add     translate W  H2 b } repeat } def
/D { /H  exch def { H 2 div     H 2 div neg S sub translate W  H  b } repeat } def
/L { /W2 exch def { W2 neg      S neg             translate W2 H  b } repeat } def
/R { /W2 exch def { W           S                 translate W2 H  b } repeat } def

Now we have defined the two procedures that we need.

We can set the initial width and height of the bricks.

/W 30 def
/H 30 def

We set the coordinates of the initial brick.

100 200 translate

And now we can call our U D L R procedures with the required lenght and repeat parameters.

We start with 2 bricks of width 30 to the left, and so on.

2 30 L
2 30 D

The save and restore operators will save and restore the whole context, including variables H and W.

In practice, the drawing goes on from the point of the last save.

save
    12 5 D
    1 60 D
    1 265 R
    9 10 R
    4 5 R
restore

The remaining code is now straightforward.

2 30 R
1 30 U
1 60 U
1 60 R
1 60 U
5 10 R
1 30 R
1 60 R

save
    5 20 D
    save
        10 5 L
    restore
    save
        18 5 D
    restore
    10 5 R
restore

10 10 R
1 60 R

save
    20 10 D
    1 60 D
    10 5 R
    1 110 R
restore

save
    10 10 U
    1 60 U
    10 20 R
restore

save
    10 10 R
    1 60 R
    20 10 D
restore

The elegance and conciseness of this code is partly due to transformation matrices and stacked context savings. These combined techniques free us from explicitely keeping track of x y z coordinates.

"At first, it looks complicated. But when you explain, it's easy!" (Émilien, 10 yo)