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)
nothing is added to the notDesignedNames list
this doesn’t work if a glyph is composed of only components
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
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)
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
I added some comments to explain the code, feel free to ask if something doesn’t appear clear.
@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?
This does several things at the same time, I figured this would be the least clear line.
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.
layer.shapes returns a list of all shapes in the layer, be it paths or components.
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.
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 ==.
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.
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.
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.