Forked from arrowtype/draw-glyph-outline.drawbot.glyphsapp.py
Last active
March 29, 2023 10:08
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
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