In the first installment, we learned how to output some font and glyph info. Now we want to go a step further and actually manipulate the font. So, make a copy of your favorite font creation and warm up your Python muscles.
This tutorial assumes that you have read Scripting Glyphs, part 1 first.
Glyphs and Layers
Let’s start with a reminder how Glyphs organizes letters. All the paths, anchors, guidelines etc., anything you deal with when you’re drawing and editing a letter, all these things are not simply properties of a glyph. Rather, they belong to one of the layers a glyph can have. Layers can be both manually inserted layers and master layers (the ones used for interpolation).
Now, before we do anything, we need to determine which layers are selected. Here’s how. Bring up your Macro panel and type:
And you’ll get something that looks like this:
( "GSLayer <0x7fe471526320>: [Bold Italic] (A)", "GSLayer <0x7fe47152fbb0>: [Bold Italic] (B)", "GSLayer <0x7fe471530d30>: [Bold Italic] (C)" )
So, what did we do here? First, we took the
Glyphs object and asked for its
font property, which should yield the current, i.e. frontmost, font. It gives you an error if no font is open. Admittedly, the result (‘GSLayer…’) is not very informative, but this is how Glyphs calls its layers: a hexadecimal identcation code, followed by the layer name, and the respective glyph name.
Luckily, we don’t really have to deal with these things directly. We can have Python do that job for us. So, our first line is going to be:
myLayers = Glyphs.font.selectedLayers
Let’s extend this a little bit:
myLayers = Glyphs.font.selectedLayers for thisLayer in myLayers: print thisLayer.parent.name print thisLayer.paths
Run this in your Macro Panel, and you’ll get something like this for an answer:
A (<GSPath 57 nodes and 29 segments>, <GSPath 10 nodes and 4 segments>) B (<GSPath 53 nodes and 23 segments>, <GSPath 42 nodes and 16 segments>) C (<GSPath 57 nodes and 23 segments>)
So, what happened? We took
font, so we got all the layers the user had selected, and passed that on into the variable
In the second line, we loop through all the layers we have in
myLayers. One layer after another, we do this: first we call it
thisLayer, then we ask
thisLayer about its
parent’s name and print it into the Macro Panel, and finally, we ask
thisLayer about its paths and print the answer in the Macro Panel as well. Then the loop steps on to the next layer in
myLayers and the whole thing starts again. We keep doing this until the last layer in
myLayers is processed.
There’s something I’ve sneaked in here:
parent. Remember how we can access an object’s sub-object by adding a period plus the name of that sub-object? If we want to know all the fonts currently open in Glyphs, we’ll type
Glyphs.fonts. If we want to know the family name of the first open font, we type
Glyphs.fonts.familyName etc. This way we can drill down in the object tree. Sometimes though, we already have an object and we need to drill up one storey in the object hierarchy. To do this, we refer to the
parent of the object.
In our case, we have a layer, which happened to be stored in
thisLayer, and we want to know which glyph the layer belongs to, so we ask the layer for its parent:
thisLayer.parent. And finally we ask that parent glyph for its name,
thisLayer.parent.name, and pass the result on to the
The last line is equally important to us, for we have arrived at the Holy Grail, the very paths that make up our font. I sure hope you’re working on a copy of your font because we’re going to do terrible things to the paths in the next step. You have been warned.
Paths and Nodes
Okay, we want to do something crazy to our selected layers. Let’s randomly shatter all their nodes left or right. So what we are going to do is this: we let Python come up with a random positive or negative number and add it to the x coordinate of the first node of the first path of the first selected layer. And we repeat this with all other nodes, paths and layers.
Python has a randomizer, but it needs a special invitation to our party. This is called importing a module or importing a library. So we start by importing the randomizer:
And just between you and me, every time you
import random, it’s a good idea to call random’s seed function:
Notice those parentheses after the word
seed? Parentheses mark a so-called method. That’s right, an object can have other objects, but it can also have methods (or ‘functions’). Methods do something with the object they are attached to. The
seed() function tells the randomizer to be really random and to not fuss about it. If we don’t do that, our randomizer may give you the same result twice, and we certainly don’t want that.
Alright, now it’s time to step through all selected layers and all their paths and all their nodes and do what we need to do to them:
import random random.seed() myLayers = Glyphs.font.selectedLayers for thisLayer in myLayers: for thisPath in thisLayer.paths: for thisNode in thisPath.nodes: thisNode.x += random.randint( -50, 50 )
Except for the last line, everything ought to be clear. The variable
thisNode holds an on-curve or off-curve point on a path. We’re iterating through all nodes in the line before. The property
x is, of course, the x coordinate of that point.
+= means ‘add what follows on the right to the variable on the left’, e.g.,
x+=5 increases x by 5. That is why
+= is also referred to as the increment operator. It is a shorter and more efficient way of saying
Now, for the random part. First, we take
random, the module we imported a few lines before. Then we get one of its methods, namely
randint(), short for random integer. Like all methods,
randint() has parentheses at the end. Inside those parentheses, you can pass input values to the method and you’ll get results based on that input. The method
randint() takes two comma-separated values, minimum and maximum, and will return a random integer between minimum and maximum. We pass
-50 as minimum and
50 as maximum to
randint() and hope to receive a random number in return.
So, in short, the last line does this: Take the x coordinate of the respective point and add a random number between -50 and 50 to it. Makes sense?
Pro tip: To see which methods and objects a class supports, you can run Python’s built-in
help()function on it. E.g., after you
import random, you can run
help()function outputs a technical description of what you place between its parentheses.
If you are overwhelmed by the sheer amount of output from the
help(random), try to get specific help about
Everything clear so far? Yes? So, what’s next? Let’s turn this script into an item of the Script menu, so we can access it any time we like. How? Read all about it in Scripting Glyphs, part 3.
Update 2014-10-04: Added links to parts 1 and 3.
Update 2016-12-08: Fixed some formatting, removed an outdated warning, added pro tip about help().
Update 2019-02-06: Minor text and formatting tweaks.
Update 2019-02-12: Corrected typo.