Script to check if glyph is empty

Hi there,

I am a complete noob and I’m cobbling something together as I start learning the ropes of python and I figured I would try this exercise.
For my first attempt at python for glyphs I am writing a piece of code that prints the glyphs that are designed and those that aren’t.

This is my trial

for name in ['A','B','C','D']:
			thisLayer = Glyphs.font.glyphs[name].layers[masterIndex]
			myPath = thisLayer.paths

			for thisPath in myPath:
				if thisPath is None: # checks if the glyph is empty to determine if the glyph is drawn
					notDesignedNames.append(name) # adds the name of the character not drawn to the list NOT WORKING
				else:
					designedNames.append(name) # adds the name of the character that is drawn to the list
					break


print("Font Info: ", thisFont,)
print("Master Info: ", thisMaster, masterIndex, "\n")
print(designedNames)
print(notDesignedNames)

However, I realise two errors (I’m sure there are many)

  1. nothing is added to the notDesignedNames list
  2. this doesn’t work if a glyph is composed of only components

Is there any way I can circumvent this problem?

I think you mean to have:

masterIndex = 0
designedNames = []
notDesignedNames = []

at the top, right?

The reason nothing is added to notDesignedNames is because the statement if thisPath is None: will never be true. for thisPath in myPath: iterates over the paths that do exist in the layer. myPath won’t contain any that are None. Try adding print(len(myPath)) below myPath = thisLayer.paths and see if that gives you any ideas :slight_smile:

this doesn’t work if a glyph is composed of only components

You will need to check for shapes rather than paths in the layer for components

See the results of e.g.

for glyph in Glyphs.font.glyphs:
    for layer in glyph.layers:
        for shape in layer.shapes:
            print(shape.shapeType)

And check out the GSLayer and GSShape parts of the API docs for more info: https://docu.glyphsapp.com/

Try this:

empty_glyphs = {}  # create a dictionary to store each master
for master in Font.masters:  # cycle through the masters in the font
	empty_glyphs[master.name] = []  # create a list to store glyph names for each master
	for glyph in Font.glyphs:  # cycle through al glyphs
		if not glyph.layers[master.id].shapes:  # access the glyph layer corresponding to current master
			empty_glyphs[master.name].append(glyph.name)  # if layer.shapes is empty, append the glyph name to the list

print("Empty glyphs in masters:")
for master_name in empty_glyphs:  # cycle through the keys of the dictionary
	if empty_glyphs[master_name]:  # access the list stored for each dicionary key
		print(master_name + ":", ", ".join(empty_glyphs[master_name]))  # print the master name and the list of glyph names stored in the list
	else:
		print("%s: No empty glyphs." % master_name)  # if the list is empty, say that it is empty.

It will only print the names of the glyphs not designed for each master. But I’m sure you can figure out how to also print the names of the glyphs which do have contents :wink:
I added some comments to explain the code, feel free to ask if something doesn’t appear clear.

Ah, thank you so much

@tribby I did not realise that myPath in empty glyphs actually points to nothing and thus returns nothing!
And yes I have those variables and arrays declared on top!

So here’s a trial using your tip.
This seems to work!

for name in ['A', 'B']:
	thisLayer = Glyphs.font.glyphs[name].layers[masterIndex]
	myPath = thisLayer.paths
	myShape = thisLayer.shapes
	myPathLength = len(myPath)
	myShapeLength = len(myShape)
	
	if myPathLength > 0:
		print(name, " has paths!")
		designedNames.append(name) # adds the name of the character that is drawn to the list
	
	elif (myShapeLength > 0):
		print(name, " has components!")
		
	else:
		print(name, " is NOT DESIGNED YET!")
		notDesignedNames.append(name) # adds the name of the character not drawn to the list NOT WORKING


print("Font Info: ", thisFont,)
print("Master Info: ", thisMaster, masterIndex, "\n")
print(designedNames)
print(notDesignedNames)

@SCarewe Oh this is neat. I understood most of it except
if not glyph.layers[master.id].shapes:

So what would this statement return? I’m used to seeing ‘if’ statements with a an explicit statement containing and ‘=’ or ‘is’. So does it return a boolean value?

Thank you so much!

This does several things at the same time, I figured this would be the least clear line.

  1. You can access a specific layer by its layer id, which, for master layers, corresponds to the id of the master it is attached to. So if you want to access the layer “Bold” of a glyph, you can use the master ID of the “Bold” master. This is generally more robust than accessing layers by their index.
  2. layer.shapes returns a list of all shapes in the layer, be it paths or components.
  3. if layer.shapes or, more generally, if my_list is shorthand for if len(my_list) > 0. The len() function returns the length of (for example) a list. So, if a list is doesn’t exist or is empty, the check will fail, if the list exists and has at least one element, it will pass.
  4. if not layer.shapes thus checks whether the list layer.shapes has elements in it, if not is simply the inversion of if. So, if the list layer.shapes is empty, the condition is fulfilled. if not my_list in this case is shorthand for if len(my_list) == 0

Small correction: an if statement will never contain an = all on its own, this is a common oversight/typo. = assigns values, comparisons are done with ==.

Hope this clarifies things.

AH, understood!
Thanks for the very clear information.

Just one more question;
I do not understand how to cycle through a group of glyph names.
Basically, I do not need to sweep the entire character set. I’m working on a collaborative assignment so I figured I could only sweep through glyphs I’ve designed. So I have made a group called myAssignment = [] with a few Devanagari characters.

Now i hope to only scan through that.

I tried this:


for master in Font.masters:  # cycle through the masters in the font
	designedGlyphs[master.name] = []  # create a list to store glyph names for each master
	print(master.name)
	for glyph in Font.glyphs:  # cycle through all glyphs
		for glyph.name in myAssignment: # only cycling through glyphs listed in myAssignment
			print(glyph.name)
			if not glyph.layers[master.id].shapes:  # access the glyph layer corresponding to current master
				#print(master.id)
				#print(glyph.name)
				designedGlyphs[master.name].append(glyph.name)  # if layer.shapes is empty, append the glyph name to the list

And I get an error saying "NameError: The glyph name “a-dev” already exists in the font.

What is the contents of myAssignment (by the way, bad habit, in Python, variables such as lists are always written lowercase, with underscores, so my_assignment)?

You are cycling through myAssignment but using glyph.name as the iterator. That won’t work. It’s a bit complicated to explain where exactly your code is wrong, because the basic structural idea won’t work.

I would re-write the code I posted above as follows:

empty_glyphs = {}  # create a dictionary to store each master
glyphs_to_check = [glyph for glyph in Font.glyphs if glyph.name in ["A", "B"]]  # build a list containing only glyphs that are called "A" or "B"
for master in Font.masters:  # cycle through the masters in the font
	empty_glyphs[master.name] = []  # create a list to store glyph names for each master
	for glyph in glyphs_to_check:  # cycle through all glyphs
		if not glyph.layers[master.id].shapes:  # access the glyph layer corresponding to current master
			empty_glyphs[master.name].append(glyph.name)  # if layer.shapes is empty, append the glyph name to the list

print("Empty glyphs in masters:")
for master_name in empty_glyphs:  # cycle through the keys of the dictionary
	if empty_glyphs[master_name]:  # access the list stored for each dicionary key
		print(master_name + ":", ", ".join(empty_glyphs[master_name]))  # print the master name and the list of glyph names stored in the list
	else:
		print("%s: No empty glyphs." % master_name)  # if the list is empty, say that it is empty.

What is happening on line 2 is one of my favourites in Python, it’s called list comprehension and might seem daunting at first. Basically, it is just a for loop, but all in one line.
You could also write the second line as follows:

glyphs_to_check = []
for glyph in Font.glyphs:
    if glyph.name in ["A", "B"]:
        glyphs_to_check.append(glyph)

This is exactly what is happening on the second line of the original code. I thus substituted Font.glyphs in my original code with glyphs_to_check, which contains a list of glyphs, not glyph names.

Hope this is clear.

2 Likes

Either way works fine. Especially for scripting beginners who don’t write Python in other contexts, sticking to a single naming convention is simpler, and the Glyphs API does not use underscores for word separation.

Exactly, which is why I wanted to recommend the correct naming convention before a bad habit is learned :wink:

1 Like

I would turn that around as this can be much quicker

glyphs_to_check = []
for glyph_name in ["A", "B"]:
    glyph = Font.glyphs[glyph_name]
    if glyph:
        glyphs_to_check.append[glyph]

This is so helpful and clear!
Thanks for the very very patient lesson.

And I see the beauty in brevity for list-comprehension. Illustrating the steps helped!
Although it’ll take me a bit to read it instinctively.

And as for the naming: Here to learn after all. I shall look through my code for any such transgressions and iron them out right away!

Thanks, good point. Wonder how this would look in a list comprehension.

my_glyphs = [Font.glyphs[glyph_name] for glyph_name in ["A", "B"] if Font.glyphs[glyph_name]]

Works, in any case.

If you’re looking for a really solid Python editor, which will also teach you proper PEP (style guidelines for writing Python), try PyCharm. Complete git integration, with support for many languages as well.
There’s a free Community edition available.