chrisphan.com

Minnesota's new flag: PostScript version

An analog clock reading 11:20 

Categories: programming, Minnesota

Just the files, please

A diagram showing four pieces of paper, two letter-sized and two A4-sized. Centered on each piece of paper is a Minnesota flag, oriented verically. Color and line-art versions are shown.

In this post, I will explain how to make a PostScript version of the Minnesota state flag, both full-color and black-and-white line-art versions, suitable for printing. If you don't care about the code and just want the printable files, skip to the end of the post.

What is Minnesota?

A few months ago, I was watching the game show Jeopardy!. It was the Double Jeopardy! round, and the contestant asked Ken Jennings for It is a contest for $800. The clue was:

Andrew Prekker said the North Star was his focus in designing the winning contest entry that's now this state's flag

As Ken read the clue, the following image appeared on the screen:

Flag of Minnesota. The flag is divided into two parts. The right is water blue, and the left is night sky blue. The boundary between the two sides is a V-shape, pointing left. In the middle of the left portion is an eight-pointed star.

The correct response, of course, was What is Minnesota?

It's been two years since Minnesota adopted this flag, replacing the previous seal on a bedsheet style design commonly used for U.S. state flags, which is decried by vexillologists. I happen to love our new flag. Two years ago, I explained how to make an SVG version of flag can be written in just sixteen lines of code:

mn_flag.svg SVG
<svg viewBox="0 0 500 300" xmlns='http://www.w3.org/2000/svg'>
  <rect x="0" y="0" width="500" height="300" stroke="none" fill="#52c9e8" />
  <polygon points="0,0 280,0 195,150 280,300 0,300"
    stroke="none" fill="#002d5d" />
  <polyline points="
    160.00000000,150.00000000
    66.10912703,188.89087297
    105.00000000,95.00000000
    143.89087297,188.89087297
    50.00000000,150.00000000
    143.89087297,111.10912703
    105.00000000,205.00000000
    66.10912703,111.10912703
    160.00000000,150.00000000"
    stroke="none" fill="white" />
</svg>

I want to color

At this moment, I do not have a color printer. I have a black-and-white laser printer that works better than any color inkjet printer I've ever had (except, of course, that it can't print in color). I wanted to a line-art version of the flag which I could print out and color by hand.

Since I wanted something intended to print, I decided to recreate the Minnesota flag using PostScript, a computer language designed for printers (and the basis for PDF). PostScript isn't just a file format for describing printed documents, but a Turing complete programming language capable of handling loops and recursion. I previously used PostScript to make a (pseudo)random maze-like thing.

My goal is to create a file which can be easily customized to choose between a color and line-art version of the flag, as well as adjust the paper size or margins.

A diagram showing two pieces of paper. On the left, centered on the paper, is a color version of the Minnesota flag, oriented verically. On the right is a line-art version.
Figure 1. The file we create will be easily customizable to allow either a color or line-art version of the flag.

Three fun things about PostScript

  1. PostScript is stack-based, much like WebAssembly. You push values on the stack and then push a command. For example, in 1 3 add:

    • 1 pushes the number 1 onto the stack
    • 3 pushes the number 3 onto the stack
    • add takes the top two numbers on the stack (1 and 3), adds them, and then pushes the result (4) onto the stack.
  2. PostScript's coordinate system is different than SVG's. In SVG (and many computer graphics systems), the origin is in the upper-left corner of the image, the first coordinate increases from left-to-right, and the second coordinate increases from top-to-bottom. In PostScript1, the origin is in the lower-left corner of the page, the first coordinate increases from left-to-right, and the second coordinate increases from bottom-to-top. As a mathematician, PostScript's convention makes more sense to me, but it does mean we can't just use the coordinates from our SVG file.

    An illustration of the SVG and PostScript coordinate systems. In the SVG system, the point (x, y) is x units right and y units down from the upper-left corner of the image. In the PostScript system, the point (x, y) is x units right and y units up from the lower-left corner of the page.
    Figure 2. The coordinate systems for SVG and PostScript
  3. The units in PostScript are points. There are 72 points in an inch.

The configuration section

The first part of our PostScript file will be the "configuration section", which the user can easily edit to configure the file. There are four values we want to offer for configuration:

A diagram showing the flag of Minnesota centered both vertically and horizontally on a piece of paper. The width and height of the paper are labeled as w and h, respectively, while the distance between the edge of the paper and flag in the vertical and horizontal directions are labeled a and b, respectively.
Figure 3. The three lengths the user can configure will be the width of the paper (w), the height of the paper (h), and the minimum of the vertical and horizontal border widths (a and b, respectively).

We start the file as follows:

mn_flag.ps PostScript
%!ps

%%%%%%%%%%%%%%%%%%%%%%%%
% Configuration section
%%%%%%%%%%%%%%%%%%%%%%%%

% Length values are given in points
% 1 inch = 72 points

/width
  % paper width
  8.5 72 mul
def

/height
  % paper height
  11 72 mul
def

/min_border
  % minimum border width
  1 72 mul
def

/fill_with_color
  % change to true for full-color, false for line-art
  true
def

%%%%%%%%%%%%%%%%% Do not edit below this line %%%%%%%%%%%%%%%%%%

A few things to note here:

  1. Anything in a line after a % is ignored by the computer.
  2. Whitespace is ignored. Lines 10–13 are equivalent to /width 8.5 72 mul def.
  3. In Lines 10–13:
    • /width pushes the undefined variable width onto the stack.
    • 8.5 72 push the values 8.5 and 11 onto the stack.
    • mul takes the top two values (8.5 and 11) off the stack, multiplies them, and pushes the product (93.5) onto the stack.
    • def takes the top two values (width and 93.5) off the stack, and then assigns the value 93.5 to the variable width.

Here is the JavaScript analogue to lines 1–30:

JavaScript
/***********************
 * Configuration section
 ***********************/

// Length values are given in points
// 1 inch = 72 points

// paper width
const width = 8.5 * 72

// paper height
const height = 11 * 72

// minimum border width
const minBorder = 1 * 72

// change to true for full-color, false for line-art
const fillWithColor = true

/************** Do not edit below this line *****************/

Determine flag dimensions

Some notation: Let a and b be the vertical and horizontal border widths, respectively, and \(m = \min(a, b)\). The ratio of the height to the length of the Minnesota flag (hung vertically) is 5:3. Let \(n = w - 2b\) be the width of the flag. Then the height of the flag is \(5n/3 = h - 2a\). Note that \(m = a\) or \(m = b\). In our code, \(h\), \(w\), and \(b\) are called width, height, and min_border.

We will use flag_width and flag_height to denote the width (\(n\)) and height of the flag, respectively, as well as vborder and hborder for \(a\) and \(b\), respectively.

In the case where \(m = a\), we could use this code:

PostScript
/vborder min_border def
/flag_height height min_border 2 mul sub def
/flag_width flag_height 3 5 div mul def
/hborder height flag_height sub 2 div def

Note that \(b \leq m\) in this case, i.e. the expression hborder min_border lt evaluates to false.

On the other hand, in the case where \(m = b\), we could use this code instead:

PostScript
/hborder min_border def
/flag_width width min_border 2 mul sub def
/flag_height flag_width 5 3 div mul def
/vborder height flag_height sub 2 div def

We combine these with a conditional in the actual file:

mn_flag.ps PostScript

2 setlinewidth % thicker lines

% determine borders

% foo_a is foo when vborder = min_border
/flag_height_a height min_border 2 mul sub def
/flag_width_a flag_height_a 3 5 div mul def
/hborder_a width flag_width_a sub 2 div def

hborder_a min_border lt {
    min_border %hborder
    width min_border 2 mul sub % flag_width
    dup 5 3 div mul % flag_height
    dup height exch sub 2 div %vborder
}{
    hborder_a
    flag_width_a
    flag_height_a
    min_border % vborder
} ifelse

/vborder exch def
/flag_height exch def
/flag_width exch def
/hborder exch def


In lines 35–38, we compute the flag_height_a, flag_width_a and hborder_a, which are the height of the flag, width of the flag, and the size of the horizontal border, respectively, under the assumption that \(m = a\).

Lines 40–50 are a conditional. In general bool_val { [foo] }{ [bar] } ifelse will execute [foo] if bool_val is true, and execute [bar] otherwise. In our case:

Lines 30-55 are analogous to this JavaScript:

JavaScript

setLineWidth(2) // thicker lines

// determine borders

let vBorder = minBorder
let flagHeight = height - 2 * minBorder
let flagWidth = flagHeight * 3/5
let hBorder = (width - flagWidth)/2

if (hBorderA < minBorder) {
  hBorder = minBorder
  flagWidth = 2 * width - minBorder
  flagHeight = flagWidth * 5/3
  vBorder = (height - flagHeight)/2
}

Where is everything?

It is now time to start putting things on the paper. The Minnesota flag consists of three parts:

In the 2024 SVG post, I described these in detail and gave some coordinates in terms of n, the width of the flag. (Back then, this was the height, because we had the flag in landscape orientation.)

For our PostScript, we have to do some adjustments, since the coordinate system is based on the lower-left corner rather than the upper-left corner. The following diagram shows all the dimensions based on n:

A diagram showing the position of various elements of the Minnesota state flag on the paper. The width of the paper is w and the height of the paper is h. The width of the flag is n = w - 2b, and the height of the flag is 5n/3. The flag sits b = w/2 - n/2 from the left edge and a = h/2 - 5n/6 from the bottom edge of the paper. The center of the star and highest point of the sky blue area is a distance of n/2 from the left edge of the flag. The center of the star is a distance of 79n/60 from the bottom edge of the flag, and the distance from the center of the star to each of its points is 11n/60. The night blue area's lowest points are a distance of 11n/15 from the bottom edge of the flag, and the sky blue area's highest point is a distance of 61n/60 from the bottom edge of the flag.
Figure 4. The dimensions of the elements of the Minnesota flag, in terms of the width n of the flag.

The rectangle

The sky-blue rectangle is the easiest thing to add. First, we set the color based on whether we are making the full-color version or the line-art version:

mn_flag.ps PostScript

% water blue rectangle
fill_with_color {
  16#52 255 div
  16#C9 255 div
  16#E8 255 div
  setrgbcolor
}{
  0 setgray
} ifelse


This is another conditional. The prefix 16# in front of a number indicates that it's written in hexadecimal. If fill_with_color is true, we push 0x52 / 255, 0xc9 / 255, and 0xe8 / 255 onto the stack, and then call the setrgbcolor procedure, setting the color to #52c9e8. On the other hand, if fill_with_color is false, the color is set to black.

The next bit actually draws the shape:

mn_flag.ps PostScript

newpath
  hborder vborder moveto
  0 flag_height rlineto
  flag_width 0 rlineto
  0 flag_height neg rlineto
  closepath
fill_with_color { fill }{ stroke } ifelse


Here is a breakdown of what these statements do:

Line Statement Effect Resulting position
67 newpath Starts a new path
68 hborder vborder moveto Move to the position (b, a) (b, a)
69 0 flag_height rlineto Move up flag_height \(= 5n/3\) units, drawing a line as we go (b, a + 5n/3)
70 flag_width 0 rlineto Move right flag_width \(= n\) units, drawing a line as we go (b + n, a + 5n/3)
71 0 flag_height neg rlineto Move down flag_height \(= 5n/3\) units, drawing a line as we go (b + n, a)
72 closepath Move to the initial point, drawing a line as we go (b, a)
73 fill_with_color { fill }{ stroke } ifelse If fill_with_color is true, fill the shape with the current color. Otherwise, draw the outline of the shape in the current color.

The night-sky blue pentagon

We begin by defining some variables for our convenience later:

mn_flag.ps PostScript

% night sky blue pentagon

/pentagon_outer_y vborder flag_width 11 15 div mul add def
/pentagon_indent 17 60 div flag_width mul def


This sets pentagon_outer_y to \(a + \tfrac{11}{15}n\) and pentagon_indent to \(\tfrac{17}{60} n\).

mn_flag.ps PostScript

fill_with_color {
  0
  16#2D 255 div
  16#5D 255 div
  setrgbcolor
}{
  0 setgray
} ifelse


This is similar lines 57–65 above. If fill_with_color is true, the color is set to #002d5d; otherwise, it is set to black.

Next, we draw the shape:

mn_flag.ps PostScript

newpath
  hborder pentagon_outer_y moveto
  flag_width 2 div pentagon_indent rlineto
  flag_width 2 div pentagon_indent neg rlineto
  hborder flag_width add vborder flag_height add lineto
  flag_width neg 0 rlineto
  closepath
fill_with_color { fill }{ stroke } ifelse


Here's what's going on:

Line Statement Effect Resulting position
89 newpath Starts a new path
90 hborder pentagon_outer_y moveto Move to the position (b, a + 11 n / 15) (b, a + 11 n / 15)
91 flag_width 2 div pentagon_indent rlineto Move right \(n/2\) units and up \(17n/60\) units, drawing a line as we go (b + n/2, a + 61 n / 60)
92 flag_width 2 div pentagon_indent neg rlineto Move right \(n/2\) units and down \(17n/60\) units, drawing a line as we go (b + n, a + 11 n / 15)
93 hborder flag_width add vborder flag_height add lineto Move to the position (b + n, a + 5 n / 3), drawing a line as we go (b + n, a + 5 n / 3)
94 flag_width neg 0 rlineto Move left \(n\) units, drawing a line as we go (b, a + 5 n / 3)
95 closepath Move to the initial point, drawing a line as we go (b, a + 11 n / 15)
96 fill_with_color { fill }{ stroke } ifelse If fill_with_color is true, fill the shape with the current color. Otherwise, draw the outline of the shape in the current color.

The white star

It's now time to deal with the star. This is going to require some more math.

In my original version, the star was filled and the strokes used to make the star were suppressed. I made the star by considering 8 equally-spaced points around a circle, then connecting the jth point to the \((j + 3)\)rd point. However, if we do this for the outline version, we will get all those unwanted inner lines.

Eight points are equally spaced around a circle. Each point is connected with a line segment to the point three away. The points are numbered 1 through 8 so that 1 connects to 2, 2 to 3, and so on, up to 8 connecting with 1. Eight points are equally spaced around a circle, and another eight points are equally spaced around a smaller circle with the same center.  Each of the inner points lies on an angle halfway between that of two outer points. There is a zig-zag between the inner and outer points to make an eight-pointed star.
Figure 5. Left: This was how the star was constructed for our SVG. It turned out okay because it was filled with the color white. We will need to do something different for the outline version. Right: We need to find the coordinates of the inner corners of the star.

The inner corners are also equally-spaced along a circle with a smaller radius. Let's find the distance of the inner corners from the center of the star.

We start by re-coordinatizing the old star (from the SVG) at the origin, so that the outer circle points are at \[\{(r \cos (k \pi/4), r \sin (k \pi/4)): 0 \leq k < 8\},\] where \(r\) is the radius of the outer circle. We take two line segments of the star and find the intersection.

The eight-pointed star with two of its defining line segements: the line segment from (0, r) to (-sqrt(2)r/2,-sqrt(2)r/2) and the line segment from (-sqrt(2)r/2, sqrt(2)r/2) to (r, 0).
Figure 6. We need to find the intersection of these lines, and then the distance from that point to the origin.

The equations for the lines are \(y = \left(1 + \sqrt{2}\right)x + r\) and \(y = \left(1- \sqrt{2}\right)x - \left(1 - \sqrt{2}\right)r\). The intersection is at \(\left(\left(1 - \sqrt{2}\right)r/2, r/2\right)\), and the distance from the intersection to the origin is \[\rho = \sqrt{\frac{2 - \sqrt{2}}{2}}r.\]

Now, recall that the distance from the center of the star to the outer points is \(r = 11n/60,\) where \(n\) is the width of the flag. Moreover, the center of the star has a distance of \(79n/60 + a\) from the bottom edge of the paper and a distance of \(n/2 + b\) from the right edge of the paper. (See Figure 4.)

Hence, we make the following definitions:

mn_flag.ps PostScript

% white star
/star_outer_radius 11 60 div flag_width mul def
/star_inner_radius 2 2 sqrt sub 2 div sqrt star_outer_radius mul def
/star_center_x flag_width 2 div hborder add def
/star_center_y 79 60 div flag_width mul vborder add def

Next, we define two functions. Each takes one argument (an angle, in degrees2), and returns a pair of numbers as outputs:

mn_flag.ps PostScript

/outer_star_point {
  1 dict begin
    /a exch def
    star_outer_radius a cos mul star_center_x add
    star_outer_radius a sin mul star_center_y add
  end
} def
/inner_star_point {
  1 dict begin
    /a exch def
    star_inner_radius a 22.5 add cos mul star_center_x add
    star_inner_radius a 22.5 add sin mul star_center_y add
  end
} def


Here is what happens when outer_star_point is called with a number t on the top of the stack:

Line PostScript Effect Stack afterwards
t (some number, given as an argument)
104 1 dict begin Set up a new dictionary for local variables t
105 /a Push the undefined variable a onto the stack t, a
105 exch Swap the positions of the last two items on the stack a, t
105 def Remove the last two items from the stack, and assign the last item to the second-to-last item
106 star_outer_radius Push star_outer_radius onto the stack r
106 a Push a onto the stack r, t
106 cos Remove the last item from the stack, find the cosine of that number, and push the result onto the stack r, cos(t)
106 mul Remove the last two items from the stack, multiply them, and push the product onto the stack r × cos(t)
106 star_center_x Push star_center_x onto the stack r × cos(t), n/2 + b
106 add Remove the last two items from the stack, add them, and push the sum onto the stack r × cos(t) + n/2 + b
107 star_outer_radius Push star_outer_radius onto the stack r × cos(t) + n/2 + b, r
107 a Push a onto the stack r × cos(t) + n/2 + b, r, t
107 sin Remove the last item from the stack, find the sine of that number, and push the result onto the stack r × cos(t) + n/2 + b, r, sin(t)
107 mul Remove the last two items from the stack, multiply them, and push the product onto the stack r × cos(t) + n/2 + b, r × sin(t)
107 star_center_y Push star_center_y onto the stack r × cos(t) + n/2 + b, r × sin(t), 79n/60 + a
107 add Remove the last two items from the stack, add them, and push the sum onto the stack r × cos(t) + n/2 + b, r × sin(t) + 79n/60 + a
108 end Stop using the new dictionary for local variables r × cos(t) + n/2 + b, r × sin(t) + 79n/60 + a

The end result is to leave \(r \cos(t) + n / 2 + b\) and \(r \sin(t) + 79n/60 + a\) on the top of the stack. Lines 103–116 are equivalent to this JavaScript:

JavaScript
function outerStarPoint(a) {
  return [
    starOuterRadius * Math.cos(a) + starCenterX,
    starOuterRadius * Math.sin(a) + starCenterY
  ]
}
function innerStarPoint(a) {
  return [
    starInnerRadius * Math.cos(a + 22.5) + starCenterX,
    starInnerRadius * Math.sin(a + 22.5) + starCenterY
  ]
}

Next we set the color to white if we are making a color version, and black if we are making the line art version:

mn_flag.ps PostScript

fill_with_color {
    1 setgray
}{
    0 setgray
} ifelse


Now, we actually draw the star. We use a for loop. The syntax for a PostScript for loop is start step end { [stuff] } for.

mn_flag.ps PostScript

newpath
    0 1 8 {
        /k exch def
        k 45 mul outer_star_point k 0 eq { moveto }{ lineto } ifelse
        k 45 mul inner_star_point lineto
    } for
    closepath
fill_with_color { fill }{ stroke } ifelse


Here's what this code does:

Line PostScript Effect Stack afterwards
124 newpath Starts a new path
125 0 1 8 Push 0, 1, and 8 on the stack 0, 1, 8
125–129 { [lines 126–128] } Push the instructions on lines 126–128 onto the stack 0, 1, 8, Instructions in lines 126–128
129 for For each integer 0, 1, … 7, push that number onto the stack and then execute lines 126–128
130 closepath Move to the initial point, drawing a line as we go
131 fill_with_color { fill }{ stroke } ifelse If fill_with_color is true, fill the shape with the color. If fill_with_color is false, draw the outline of the shape.

Lines 126–128 are executed 7 times by the for loop:

Line PostScript Effect Stack afterwards
t (an integer between 0 and 7, inclusive, pushed onto the stack by the for loop)
126 /k Push the undefined variable k onto the stack t, k
126 exch Swap the positions of the last two items on the stack k, t
126 def Remove the last two items from the stack, and assign the last item to the second-to-last item
127 k 45 Push k and 45 onto the stack t, 45
127 mul Remove the last two items from the stack, multiply them, and push the product onto the stack 45 × t
127 outer_star_point Call outer_star_point (which will consume the last item on the stack) r cos(45t) + n/2 + b, r sin(45t) + 79n/60 + a
127 k 0 Push k and 0 onto the stack r cos(45t) + n/2 + b, r sin(45t) + 79n/60 + a, t, 0
127 eq Remove the last two items from the stack, compare them, and push true onto the stack if the are equal, and false otherwise r cos(45t) + n/2 + b, r sin(45t) + 79n/60 + a, t = 0
127 { moveto }{ lineto } ifelse Remove the last item from the stack. Move to the coordinates given by next two items on the stack, drawing a line if that item is false
128 k 45 Push k and 45 onto the stack t, 45
128 mul Remove the last two items from the stack, multiply them, and push the product onto the stack 45 × t
128 inner_star_point Call inner_star_point (which will consume the last item on the stack) ρ cos(45t + 22.5) + n/2 + b, ρ sin(45t + 22.5) + 79n/60 + a
128 lineto Draw a line to the coordinates given by the last two items on the stack

Wrapping up the PostScript

The last thing to do is to tell PostScript to actually show or print everything:

mn_flag.ps PostScript

showpage

Here is the complete PostScript file, in case you want to customize it: mn_flag.ps

Creating PDFs

To create a PDF version, we will use the ps2pdf command in GhostScript, an open-source PostScript interpreter. Converting a PostScript file to PDF is fairly straightforward3:

Bash
ps2pdf mn_flag.ps mn_flag_letter_color.pdf

Here is the resulting PDF: mn_flag_letter_color.pdf.

Next, let's make the black-and-white line-art version. We edit mn_flag.ps to change the definition of fill_with_color to false:

mn_flag.ps PostScript

/fill_with_color
  % change to true for full-color, false for line-art
  false
def

Then we use ps2pdf to make the PDF version:

Bash
ps2pdf mn_flag.ps mn_flag_letter_bw.pdf

Here is the resulting PDF: mn_flag_letter_bw.pdf.

Next, let's make the A4 versions. A4 paper, the standard stationery size for most of the world, is 210 mm × 297 mm. An inch is exactly 25.4 mm, so this adjustment to mn_flag.ps will do:

mn_flag.ps PostScript

/width
  % paper width
  210 25.4 div 72 mul
def

/height
  % paper height
  297 25.4 div 72 mul
def

/min_border
  % minimum border width
  25 25.4 div 72 mul
def

/fill_with_color
  % change to true for full-color, false for line-art
  false
def

Then we use ps2pdf command with the -sPAPERSIZE=a4 switch to convert to A4-sized PDF:

Bash
ps2pdf -sPAPERSIZE=a4 mn_flag.ps mn_flag_a4_bw.pdf

Here is the resulting PDF: mn_flag_a4_bw.pdf.

Finally, we make the color A4 version:

mn_flag.ps PostScript

/width
  % paper width
  210 25.4 div 72 mul
def

/height
  % paper height
  297 25.4 div 72 mul
def

/min_border
  % minimum border width
  25 25.4 div 72 mul
def

/fill_with_color
  % change to true for full-color, false for line-art
  true
def

We use ps2pdf with -sPAPERSIZE=a4 switch again:

Bash
ps2pdf -sPAPERSIZE=a4 mn_flag.ps mn_flag_a4_color.pdf

Here is the resulting PDF: mn_flag_a4_color.pdf.

The resulting files

A diagram showing four pieces of paper, two letter-sized and two A4-sized. Centered on each piece of paper is a Minnesota flag, oriented verically. Color and line-art versions are shown.

Here are the PDF files we created:

Footnotes

  1. There's actually multiple coordinate systems and the ability to define your own in PostScript, but I'm talking about the default one we will be using in this file.

  2. Unfortunately, the sin and cos functions in PostScript take their argument in degrees, not radians. Kinda gross, I know.

  3. By default, GhostScript is configured to output to letter-sized (8.5 in × 11 in) paper, the standard stationery size in North America. If this has been changed on your system (e.g. so that the default size is A4), you need to add the -sPAPERSIZE=letter switch, as described below.