Scripting Glyphs, part 2

Tutorial
by Rafał Buchner & Rainer Scheichelbauer
en zh

18 August 2022 Published on 28 June 2012

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.

Glyph layers

Let’s start with a reminder of 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).

Selected layers

Now, before we do anything, we need to determine which layers are selected. Here’s how. Bring up your Macro panel and type:

print( Glyphs.font.selectedLayers )

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 identification 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

Accessing paths

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 selectedLayers from font, so we got all the layers the user had selected, and passed that on into the variable myLayers.

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[0].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 print command.

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:

import random

And just between you and me, every time you import random, it’s a good idea to call random’s seed function:

random.seed()

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.

The operator += 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 x=x+5.

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?

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.
Update 2020-12-02: updated for Python3.
Update 2022-18-08: updated title, related articles, minor formatting.