, , , , ,

Building on the base documented in my earlier post, let’s make a font that simulates the hole patterns on punched paper tape.

Rolls of paper tape

As before, only a new file papertape.dotfont should be needed to fully specify our font outlines. However, testing revealed a number of areas where values that were assumed to be fixed really should be adjustable per font.

This post is one of several describing a utility that will read a variety of textual descriptions of characters and produce a usable TrueType font.

Punched paper tape

Once upon a time, before computers were invented, long distance communications wanted a way to store and reproduce messages in a medium that would allow the same message to be sent more than once without requiring lots of operator time. By this point, telegraph was being supplanted by teletype services which used a five-bit code carried in a serial protocol.

The serial protocol eventually evolved into the common asynchronous serial protocol used on RS-232 COM ports to this day.

The five-bit code quickly grew up to seven bits, and became standardized as ASCII.

The idea of storing patterns as holes punched in a material is even older than that, going back to the chain of cards used to program Jacquard looms, so storing a character at a time in a row of holes punched in a paper tape made sense.

A row of holes per character eliminates all the framing ambiguity. Holes are easy to punch even five to eight at a time. And they are easy to sense with pins that reach through, or even compressed air (player piano rolls are read with compressed air), electrical contacts, or light (if the tape itself is opaque enough).

You can see the bits, so you can edit the tape with scissors and tape. The assignment of the ASCII character named NUL to the code point 0x00 has no holes punched and is used as a tape leader, and the assignment of DEL to the code point 0x7F (or 1111111 in binary) makes sense on tape too: you can delete a character from a tape by punching out all the rest of the holes.

To move the tape precisely through the machinery, a row of small holes is provided by the manufacturer on blank tape stock. These provide a place for a sprocket wheel to alight the tape to the punch, or in an optical reader a way to time the reading of the data holes.

All of the details we need to know (and many more) are specified in the original international standard for paper tape, ECMA-10 1965. There we not only learn the dimensions of everything, but that there are two reference surfaces that define the orientation of the tape in the reader and on a spool. The eight data tracks are named track 1 through 8, and when ASCII data is carried, the 8th bit is used for parity.

The tracks are separated from each other by 2.54 mm (0.100 inch) as are the rows. The sprocket feed holes occupy the feed track between tracks 3 and 4, and are centered 9.96 mm from the reference edge. The reference edge is next to track 1. The reference surface is facing you when the tape is feeding toward the left with the reference edge down, and is conventionally wound to the outside of a spool and used for directional arrows and other labeling.


I created a new file papertape.dotfont, which will describe the font I named “Punched Paper Tape LtR”, arranged as a view of the reference face with the reference edge down, which would feed left to right into a tape reader. A sample is shown below (decoding it is left as an exercise).

paper tape demo

To follow along, refer to the tree view of files in the project, or clone and open your own working copy of the project. You’ll find the interesting files in the src folder.

Omitting most comments and fluff, we have a surprisingly short file, considering it defines 256 characters in the first Unicode Private Use Area. These characters map to all 256 combinations of eight bit tape.

We begin with a function that makes a decent circle in TrueType’s quadratic splines. It uses four arcs which are only roughly circular. But the result looks about as circular as many hole punches do after a little use. The scale factor of 0.91 was picked by trial and error. 1.00 was too squarish, and 0.71 would be on the circle and makes the figure too diamondish.

local function circle(r,cx,cy)
    local rc = math.ceil(r*0.91)
    return {
        {cx-r,  cy,    1},
        {cx-rc, cy+rc, 0},
        {cx,    cy+r,  1},
        {cx+rc, cy+rc, 0},
        {cx+r,  cy,    1},
        {cx+rc, cy-rc, 0},
        {cx,    cy-r,  1},
        {cx-rc, cy-rc, 0}

We define contours for the two sizes of holes, based on the 1024 units per inch, centering them in a 0.1 inch square.

local holes = {
    data = circle(37,51,51),
    timing = circle(23,51,51)

Since we want to make the tape look right with data entered left to right, we need to orient bit zero to the bottom of the character cell. For one inch wide eight bit tape, there are nine hole tracks, and the fourth track is always the sprocket which is centered 0.4 inches from the reference edge. This function places the hole contour h on to track n.

local function hole(n,h)
    local t={}
    local no = math.floor(n*102.4+51.2+0.5)
    for i=1,#h do
        t[i] = { h[i][1], h[i][2]+no, h[i][3] }
    return t

A character has a number of properties as well as a list of contours. This function fills out the properties that can be computed, and transforms a character code (a pattern of fixed segments for other dotfonter fonts defined so far) into the list of contours.

local function Glyph(t)
    local pat = assert(t.pat)

    -- default metrics
    t.width = t.width or 103
    t.lsb = t.lsb or 14
    t.a, t.d = t.a or 1024, t.d or 0

    -- default mapping
    if not t.unicode then t.unicode = t.name:byte() end
    if not t.mac and t.unicode < 127 then t.mac = t.unicode end
    if not t.win then t.win = t.unicode end

    -- fill out array of contours
    local n = tonumber(pat)
    t[#t+1] = hole(3, holes.timing)
    for bit=0,7 do
        if n%2 == 1 then
            t[#t+1] = hole(bit > 2 and bit+1 or bit, holes.data)
        n = math.floor(n / 2)

    -- correct LSB on code 0
    if t.unicode == 0xEE00 then
        t.lsb = 28

    -- add tape edges with LSB to match
    t[#t+1] = {{-2,0},{-2,8},{105,8},{105,0}}
    t[#t+1] = {{-2,1016},{-2,1024},{105,1024},{105,1016}}
    t.lsb = -2

    return t

Now, we can construct a glyph for each of the 256 bit patterns. We’ll use a span of code points in Unicode’s first private use area. This avoids a number of complications with tools that would otherwise assume that certain code points have other meanings (space, tab, form feed, carriage return, and line feed all leap to mind),

local Glyphs = {}
for i=0,255,1 do
    local c = Glyph{unicode=0xee00+i, name=("U+EE%02X"):format(i), pat=i}
    Glyphs[#Glyphs+1] = c

Finally, we create and return our font as a table for dotfonter to process. The largest chunk of data is the table of glyphs just computed. Then we include a flag to have the 96 printable ASCII characters aliased to their ASCII codes, making construction of some samples easier.

The copy in the repository sets additional name strings, including identifying the license as CC-BY-4.0.

return {
    Glyphs = Glyphs,
    ASCIIAliasBase = 0xee00,
    Names = {
        copy='Copyright Ross Berteig 2016.',
        ffam='Punched Paper Tape LtR',
        ufid='Punched Paper Tape LtR '.. os.date"%Y-%m-%d" ,
        vers='Version 001.003',

The last section is a collection of values where the default supplied by dotfonter is wrong or insufficient. A future version will make the common cases easier to specify, or will simply compute the right values. Notable here is the marker that says that this font includes characters in the Unicode First Private Use Area (U+E000 through U+F8FF), and since we’ve aliased the printable ASCII characters, we’ll set bits for the Latin1 code page. The other overrides cause the font to truly be a fixed pitch font by overriding the widths of some of the internal glyphs.

    Overrides = {
        OS_2 = {
            xAvgCharWidth = "103",
            ulUnicodeRange1 = "00000000 00000000 00000000 00000001",
            ulUnicodeRange2 = "00010000 00000000 00000000 00000000",
            ulCodePageRange1 = "00000000 00000000 00000000 00000001",
        head = {
            xMax = "103"
        hhea = {
            descent = -1
        hmtx = {
            [1] = { width = 103 }, -- override width of .notdef
            [3] = { width = 103 }, -- override width of nonmarkingreturn
            [4] = { width = 103 }, -- override width of space


Supporting changes in Dotfonter

We made small to moderate changes in dotfonter.lua, ttfhead.lua, ttfname.lua, and ttfont.lua.

The largest change was support for the new Overrides member of the font, implemented in ttfont.lua as a new feature of GetFont(). The provided table is scanned and names that correspond to fields in known TTF tables have their values replaced. All the remaining values in those tables are ignored, and it is not possible for Overrides to introduce new values or entire new tables.

The new mapping of ASCII codes to a span of other characters is also provided in ttfont.lua by the function MapASCIIAliases().

For consistency (and to silence an error reported by FontValidator) ttfhead.lua now provides a way to make the version number recorded in the head table match the value in name string 5.

Other changes are cosmetic or minor.

Playing Along at Home

Clone the public fossil repository.

The version of the code described in this post is from checkin [f19c56a4b9].

There are still features missing from the script, but the bones are there to build upon. dotfonter.lua defers to an external file for both the shapes of the segments and the ROM image itself. This post shows that with only minor extensions, the external file alone had enough power to define a punched paper tape hole pattern font.

All of the code is licensed under the MIT license. Please do let me know if dotfonter is useful to you, or if you find any issues.

Supporting Cast

Here are some tools I’m using to develop this utility:

Documentation and references:


Watch this space for more articles about work in progress on the utility.

The code I have running today has created a TTF font from scratch covering all 256 possible hole patterns in 8-bit 1 inch wide punched paper tape. For foolish consistency I should consider making one for the much older 5 bit narrow tape.

The next step is to complete the 5×7 dot matrix display font.


After making a dot matrix display font by hand using the impressive capabilities of FontStruct, I decided I wanted a tool to make the whole process easier. The result is the work in progress currently named dotfonter.

If you have a project involving embedded systems, micro-controllers, electronics design, audio, video, or more we can help. Check out our main site and call or email us with your needs. No project is too small!

+1 626 303-1602
Cheshire Engineering Corp.
710 S Myrtle Ave, #315
Monrovia, CA 91016

(Written with StackEdit.)