Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save jmsole/b634c84643ce7405d2ed97a4731c78db to your computer and use it in GitHub Desktop.
Save jmsole/b634c84643ce7405d2ed97a4731c78db to your computer and use it in GitHub Desktop.
For DrawBot in GlyphsApp: Draw a glyph's outlines & nodes, for presentation / social media purposes
"""
Script to create an image of a glyph in the current layer.
Instructions:
- Use within the Drawbot plugin of GlyphsApp.
- Get this plugin via Window > Plugin Manager, then search for "Drawbot" and install it.
- Then, go to File > New Drawbot, and paste in this code and run it.
- You may need to restart glyphs for the Plugin to work.
- Configure options & colors below in the "Configuration" area.
- If you want, open the pdf or svg file in Adboe Illustrator, etc, then edit further!
Credits:
Started from https://forum.glyphsapp.com/t/showing-nodes-and-handles-in-drawbot-with-custom-colours/17495/16
"""
from GlyphsApp import *
from numpy import arctan2, rad2deg, pi
from math import atan2, degrees
font = Glyphs.font
# ---------------------------------------------------
# Configuration below
# name of glyph to draw
glyphsToDraw = ("A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q")
glyphsNoHandles = ("N", "O", "P", "U", "V", "W", "X", "Y")
glyphsWithFill = ("N", "O", "P", "U", "V", "W", "X", "Y", "d", "h", "i", "n", "o", "p", "q")
glyphsSmallHandles = ("Q", "R", "n", "o", "p", "q")
glyphsJustFill = ("j", "k", "l", "m")
# glyphsToDraw = ("I")
# folder to save to
folder = "~/Desktop/CSM - Type Design Workshop"
# filename + filetype. Possible types: png, svg, pdf, jpg
# filename = "wshop_" + glyphToDraw + ".pdf"
# how wide to make canvas (it will be a square)
imageSize = 2160
# use to adjust scaling of glyph
glyphScaling = 1.2
# how thick to make paths
strokeThickness = 10
# how big to make points
handleSize = 26
# glyph colors (R, G, B, Alpha)
insideColor = (0/255, 0/255, 0/255, 0)
strokeColor = (10/255,10/255,100/255, 1)
# point colors (R, G, B, Alpha)
handleInsideColor = (255/255, 255/255, 255/255, 1)
# handleStrokeColor = (10/255,10/255,100/255, 1)
handleStrokeColor = (255/255,83/255,80/255, 1)
# handleConnectionColor = (10/255,10/255,100/255, 0.4)
handleConnectionColor = (255/255,83/255,80/255, 0.9)
greenColor = (0/255,198/255,120/255, 1)
blueColor = (83/255,80/255,255/255, 1)
# Fill colours
colorCycle = {
"redSalsa": (255/255,89/255,94/255,0.3),
"yellowGreen": (138/255,201/255,38/255,0.3),
"greenBlueCrayola": (25/255,130/255,196/255,0.3),
"sunglow": (255/255,202/255,58/255,0.3),
# "royalPurple": (106/255,76/255,147/255,0.3)
}
# colorCycle = ("redSalsa", "sunglow", "yellowGreen", "greenBlueCrayola", "royalPurple")
# a color for the background (R, G, B, Alpha)
backgroundColor = (255/255, 255/255, 255/255, 0)
# Configuration above
# ---------------------------------------------------
# def angle_between (p1, p2):
# ang1 = arctan2(*p1[::-1])
# ang2 = arctan2(*p2[::-1])
# return rad2deg((ang1 - ang2) % (2 * pi))
def angle_between (p1, p2):
dx = p2[0] - p1[0]
dy = p2[1] - p1[1]
theta = degrees(atan2(dy, dx))
return theta
def drawOnNode(path, node, handleSize, strokeThickness):
if handleSize == 0:
return
pt = node.position
nodeType = node.type
nodeSmooth = node.smooth
nodeIndex = node.index
lastNode = path.nodes[len(path.nodes) - 1].index
pathClosed = path.closed
size = handleSize
fill(*handleInsideColor)
strokeWidth(strokeThickness)
lineJoin("miter")
# if nodeType == GSCURVE or nodeType == GSLINE:
# rect(pt.x - (size / 2), pt.y - (size / 2), size, size)
with savedState():
if nodeIndex == lastNode:
if nodeType == GSLINE:
stroke(*blueColor)
if nodeType == GSCURVE:
if nodeSmooth == True:
stroke(*greenColor)
else:
stroke(*blueColor)
if pathClosed == True:
ang = angle_between(pt, path.nodes[0].position)
rotate(ang, center=(pt.x, pt.y))
polygon(
(pt.x - (size * 0.3), pt.y - (size * 0.7)),
(pt.x + (size * 0.5), pt.y + (size * 0.001)),
(pt.x - (size * 0.3), pt.y + (size * 0.7)),
close=True)
# print(node, path.nodes[0])
# print(angle_between(pt, path.nodes[0].position))
# print(path.nodes[0].position)
elif nodeType == GSLINE:
if nodeSmooth == True:
stroke(*greenColor)
oval(pt.x - (size / 2), pt.y - (size / 2), size, size)
else:
stroke(*blueColor)
rect(pt.x - (size / 2), pt.y - (size / 2), size, size)
elif nodeType == GSCURVE:
if nodeSmooth == True:
stroke(*greenColor)
oval(pt.x - (size / 2), pt.y - (size / 2), size, size)
else:
stroke(*blueColor)
rect(pt.x - (size / 2), pt.y - (size / 2), size, size)
def drawOffNode(path, node, handleSize, strokeThickness):
if handleSize == 0:
return
pt = node.position
nodeType = node.type
size = handleSize * 0.75
fill(*handleInsideColor)
lineJoin("miter")
if nodeType != GSCURVE and nodeType != GSLINE:
index = node.parent.indexOfNode_(node)
prevNode = node.parent.nodes[index - 1]
nextNode = node.parent.nodes[index + 1]
stroke(*handleConnectionColor)
if prevNode.type == GSOFFCURVE:
strokeWidth(strokeThickness * 0.75)
newPath()
moveTo(node.position)
lineTo(nextNode.position)
drawPath()
else:
strokeWidth(strokeThickness * 0.75)
newPath()
moveTo(node.position)
lineTo(prevNode.position)
drawPath()
strokeWidth(strokeThickness)
stroke(*handleStrokeColor)
oval(pt.x - (size / 2), pt.y - (size / 2), size, size)
def drawGlyph(glf, filename, layerGlyph, imageSizing, scaling, strokeThickness = 1, handleSize = 10):
newPage(imageSizing, imageSizing)
# draw background
fill(*backgroundColor)
rect(0,0, imageSizing, imageSizing)
offsetX = (imageSizing - (layerGlyph.width * scaling)) / 2
# print(font.upm)
# print(layerGlyph.bounds.origin.y)
# print(layerGlyph.bounds.origin.y * scaling)
offsetY = ((imageSizing - (layerGlyph.bounds.size.height * scaling)) / 2) - ((layerGlyph.bounds.origin.y * scaling))
translate(offsetX,offsetY)
scale(scaling)
# draw glyph
fill(*insideColor)
# fill(random(), random(), random(), .3)
stroke(*strokeColor)
strokeWidth(strokeThickness)
# print(type(layerGlyph.bezierPath))
# print(type(layerGlyph.openBezierPath))
# print(layerGlyph, layerGlyph.bezierPath)
# drawPath(layerGlyph.bezierPath)
colors = []
localColorCycle = colorCycle.copy()
for layerPaths in layerGlyph.paths:
lineJoin("round")
if glf in glyphsWithFill:
# fill(random(), random(), random(), .3)
newCol = localColorCycle.popitem()
# print(newCol[0])
fill(*newCol[1])
if not localColorCycle:
localColorCycle = colorCycle.copy()
elif glf in glyphsJustFill:
stroke(None)
strokeWidth(None)
fill(0/255,0/255,0/255)
else:
fill(None)
drawPath(layerPaths.bezierPath)
# draw paths
stroke(*handleStrokeColor)
for p in layerGlyph.paths:
if glf in glyphsNoHandles:
pass
elif glf in glyphsSmallHandles:
for n in p.nodes:
drawOffNode(p, n, handleSize * 0.7, strokeThickness * 0.6)
elif glf in glyphsJustFill:
pass
else:
for n in p.nodes:
drawOffNode(p, n, handleSize, strokeThickness)
if glf in glyphsNoHandles:
for n in p.nodes:
drawOnNode(p, n, handleSize * 0.7, strokeThickness * 0.6)
elif glf in glyphsSmallHandles:
for n in p.nodes:
drawOnNode(p, n, handleSize * 0.7, strokeThickness * 0.6)
elif glf in glyphsJustFill:
pass
else:
for n in p.nodes:
drawOnNode(p, n, handleSize, strokeThickness)
saveImage(f"{folder}/{filename}", multipage=False)
layerId = Glyphs.font.selectedLayers[0].layerId
# print(layerId)
# note: make sure to use .copy(), or it will edit the source glyph!
# layerGlyph = Glyphs.font[glf].layers[layerId].copy()
# currently, this script can't show overlapping contours
# layerGlyph.removeOverlap()
glyphScaling = glyphScaling * imageSize/font.upm
for glf in glyphsToDraw:
# print(glf)
if glf.islower():
filename = "wshop_lc_" + glf + ".pdf"
else:
filename = "wshop_" + glf + ".pdf"
# print(Glyphs.font[glf].layers[layerId].paths[0])
# print(Glyphs.font[glf].layers[layerId].paths[0].bezierPath)
layerGlyph = Glyphs.font[glf].layers[layerId].copy()
# layerGlyph.removeOverlap()
drawGlyph(glf, filename, layerGlyph, imageSize, glyphScaling, strokeThickness=strokeThickness, handleSize=handleSize)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment