I'm currently drawing a **single line font** (also called open path font, open line font, monoline font, etc) in **Glyphs App** (see the image #1 bellow). The experimental **.ttf format** export in Glyphs allows single line glyphs.

Does anyone knows if **Drawbot can draw single line fonts** and how ?

For now, I only get a closed path (image #2)

Thank you !

*Image #1*

*Image #2*

Triggered by Mike Duggan: https://twitter.com/mickduggan/status/1090577885067386880

```
# triggered by Mike Duggan:
# https://twitter.com/mickduggan/status/1090577885067386880
def arrow(center, size, rotation, barThickness, arrowhead):
with savedState():
translate(*center)
scale(size)
rotate(rotation)
points = [
(-0.5, barThickness/2),
(0.5 - arrowhead, barThickness/2),
(0.5 - arrowhead, 0.5),
(0.5, 0),
(0.5 - arrowhead, -0.5),
(0.5 - arrowhead, -barThickness/2),
(-0.5, -barThickness/2),
]
polygon(*points)
def arrowGrid(numArrows, arrowSize, barThickness, arrowhead, rotation):
for i in range(numArrows):
x = i * arrowSize
for j in range(numArrows):
y = j * arrowSize
arrow((x, y), arrowSize, rotation, barThickness, arrowhead)
def easeInOut(t):
assert 0 <= t <= 1
return (1 - cos(t * pi)) / 2
canvasSize = 400
numArrows = 4
arrowSize = canvasSize / numArrows
barThickness = 0.5
arrowhead = 0.5
rotation = 180
duration = 4
framesPerSecond = 15
numFrames = duration * framesPerSecond
for frame in range(numFrames):
t = 4 * frame / numFrames
quadrant = floor(t)
t = easeInOut(t % 1)
angle = ((quadrant + t) * 90) % 380
flip = quadrant % 2
angle = -angle
newPage(canvasSize, canvasSize)
frameDuration(1/framesPerSecond)
if flip:
fill(1)
rect(0, 0, canvasSize, canvasSize)
fill(0)
arrowGrid(numArrows + 1, arrowSize, barThickness, arrowhead, angle)
else:
fill(0)
rect(0, 0, canvasSize, canvasSize)
fill(1)
translate(-arrowSize/2, -arrowSize/2)
arrowGrid(numArrows + 2, arrowSize, 1 - barThickness, arrowhead, angle + 180)
saveImage("Arrows.gif")
```

]]>A small extension to the original code.

The above Version (with the rectangle size of cell size / sqrt(2) is only one of a series of four. Depending on the size the rectangles they touch in different ways.

So here is the updated code to use same rotation pattern on four pages with different rectangle sizes.

First let’s store the size of the canvas (1000) in the variable *canvas_s*.

The *amount* variable and calculation of the cell size stays the same:

```
canvas_s = 1000
amount = 24
cell_s = canvas_s / (amount + 2)
```

The four different rectangle sizes are stored in a list.

```
rect_sizes = [ cell_s / 2, cell_s / sqrt(2), 2 * cell_s / ( 1 + sqrt(2) ), cell_s]
```

The sizes are calculated to get the various ‘touchings’. Just add other rectangle sizes to the list and see that you get.

`cell_s / 2`

: no touching`cell_s / sqrt(2)`

: the rotated rectangles tough on the corners`2 * cell_s / (1 + sqrt(2))`

: a rotated and default rectangle touch on the corner and side`cell_s`

: the not rotated rectangles touch on the side

To use the same rotation grid we have to generate and store it somehow.

Let’s use the nested loops to store this information in the *grid* variable.

It is a list of lists (rows or columns whatever you prefere).

Declare the variable and store an empty list:

```
grid = []
```

Loop though the amount

```
for y in range(amount):
```

For every row we assign a new empty list to the *newRow* variable

```
newRow = []
```

Then loop through the amount and append either *True* or *False* to the *newRow*

```
for x in range(amount):
if random() > .5:
newRow.append(True)
else:
newRow.append(False)
```

When we are finished with the row loop let’s append the *newRow* to the grid

```
grid.append( newRow )
```

The above grid generation could actually be done in one line as well.

So this:

```
grid = []
for y in range(amount):
newRow = []
for x in range(amount):
if random() > .5:
newRow.append(True)
else:
newRow.append(False)
grid.append( newRow )
```

Will do the same as this:

```
grid = [[True if random() > .5 else False for x in range(amount)] for y in range(amount)]
```

List comprehension and ternary operators are great and will save you some lines but they can be a bit confusing.

The rest of the script basically stays the same.

There is one more loop added to generate the pattern for each size:

```
for rect_s in rect_sizes:
```

For each size we need a new canvas so we call *newPage()* and use the variable *canvas_s* to make them all the same size:

```
newPage(canvas_s, canvas_s)
```

Translation for the margin

```
translate( cell_s, cell_s)
```

To loop through the stored grid let’s use the *enumerate()* function.

This is very helpful if you need the index and the actual element of a list.

```
for index, value in enumerate(['a', 'b', 'c']):
print index, value
```

will print (in python 2.7)

0 a

1 b

2 c

We will use the index for the transformation and the element (True or False) for the rotation.

```
for r, row in enumerate(grid):
for c, cell in enumerate(row):
with savedState():
translate( c * cell_s + cell_s/2, r * cell_s + cell_s/2 )
if cell: rotate(45)
rect(-rect_s/2, -rect_s/2, rect_s, rect_s)
```

If you want to store seperate images (.jpg or .png) we call the *saveImage()* inside the size loop.

```
saveImage('~/Desktop/Vera_Molnar_rotating_rects_%f.png' % rect_s)
```

For a multipage pdf or gif the *saveImage()* should be outside of the loops.

```
canvas_s = 1000
amount = 24
cell_s = canvas_s / (amount + 2)
rect_sizes = [ cell_s / 2, cell_s / sqrt(2), 2 * cell_s / ( 1 + sqrt(2) ), cell_s]
grid = [[True if random() > .5 else False for x in range(amount)] for y in range(amount)]
for rect_s in rect_sizes:
newPage(canvas_s, canvas_s)
translate( cell_s, cell_s)
for r, row in enumerate(grid):
for c, cell in enumerate(row):
with savedState():
translate( c * cell_s + cell_s/2, r * cell_s + cell_s/2 )
if cell: rotate(45)
rect(-rect_s/2, -rect_s/2, rect_s, rect_s)
# saveImage('~/Desktop/Vera_Molnar_rotating_rects_f.png' % rect_s)
```

If you want to jump directly into the code, here's the source:

```
##############################
# Draw Wiggles using Drawbot #
##############################
"""
Script by Roberto Arista, you can find the related tutorial here: https://medium.com/@roberto_arista/how-to-draw-a-wiggle-between-two-points-with-python-and-drawbot-788006c18fb0
You can find drawbot here: http://www.drawbot.com/
Code distributed with no guarantee, use at your own risk
"""
### Modules
from math import radians, atan2, sqrt, sin, cos
from collections import namedtuple
### Constants
BLACK = (0, 0, 0)
Point = namedtuple('Point', ['x', 'y'])
### Function & procedures
def calcAngle(pt1, pt2):
return atan2((pt2.y - pt1.y), (pt2.x - pt1.x))
def calcDistance(pt1, pt2):
return sqrt((pt1.x - pt2.x)**2 + (pt1.y - pt2.y)**2)
def calcWiggle(pt1, pt2, waveLength, waveHeight, curveSquaring=.57):
assert 0 <= curveSquaring <= 1, 'curveSquaring should be a value between 0 and 1: {}'.format(curveSquaring)
assert waveLength > 0, 'waveLength smaller or equal to zero: {}'.format(waveLength)
diagonal = calcDistance(pt1, pt2)
angleRad = calcAngle(pt1, pt2)
howManyWaves = diagonal//int(waveLength)
waveInterval = diagonal/float(howManyWaves)
maxBcpLength = sqrt((waveInterval/4.)**2+(waveHeight/2.)**2)
bcpLength = maxBcpLength*curveSquaring
bcpInclination = calcAngle(Point(0,0), Point(waveInterval/4., waveHeight/2.))
wigglePoints = [pt1]
prevFlexPt = pt1
polarity = 1
for waveIndex in range(0, int(howManyWaves*2)):
bcpOutAngle = angleRad+bcpInclination*polarity
bcpOut = Point(prevFlexPt.x+cos(bcpOutAngle)*bcpLength, prevFlexPt.y+sin(bcpOutAngle)*bcpLength)
flexPt = Point(prevFlexPt.x+cos(angleRad)*waveInterval/2., prevFlexPt.y+sin(angleRad)*waveInterval/2.)
bcpInAngle = angleRad+(radians(180)-bcpInclination)*polarity
bcpIn = Point(flexPt.x+cos(bcpInAngle)*bcpLength, flexPt.y+sin(bcpInAngle)*bcpLength)
wigglePoints.append((bcpOut, bcpIn, flexPt))
polarity *= -1
prevFlexPt = flexPt
return wigglePoints
def drawCurvesSequence(wigglePoints):
myBez = BezierPath()
myBez.moveTo(wigglePoints[0])
for eachBcpOut, eachBcpIn, eachAnchor in wigglePoints[1:]:
myBez.curveTo(eachBcpOut, eachBcpIn, eachAnchor)
myBez.endPath()
drawPath(myBez)
### Variables
pt1 = Point(50, 50)
pt2 = Point(150, 60)
### Instructions
size(400, 400)
oval(pt1.x-1, pt1.y-1, 2, 2)
oval(pt2.x-1, pt2.y-1, 2, 2)
stroke(*BLACK)
strokeWidth(.5)
fill(None)
wigglePoints = calcWiggle(pt1, pt2, 16, 36, .7)
drawCurvesSequence(wigglePoints)
```

]]>