"Instance as master" through Core API?


#1

In the past, @GeorgSeifert was very helpful in pointing out how to code the “instance as master” function in the Python API, by:

  1. getting the interpolated font of an instance
  2. appending this as a new master in the main font
  3. looping through glyphs of the added master, and setting the blank layer equal to the same layer of the interpolated font
  4. setting the kerning in the main font’s new master equal to the kerning of the interpolated font’s master

In the Python API, that looks like this:

instanceFont = Font.instances[2].interpolatedFont
Font.masters.append(instanceFont.masters[0])
newMasterID = instanceFont.masters[0].id
for glyph in Font.glyphs:
	instanceGlyph = instanceFont.glyphs[glyph.name]
	glyph.layers[newMasterID] = instanceGlyph.layers[newMasterID]
Font.kerning[newMasterID] = instanceFont.kerning[newMasterID]

I’m now trying to do something similar with the Core API in order to manipulate a font source during a build process. I’ve been able to get the interpolated font from an instance and append it as a new master (steps 1 and 2 from above), but I’m getting stuck on step 3.

# get interpolated font
currentLightFont = font.generateInstance_error_(font.instances()[0], None)
# get master of interpolated font
currentLightFontMasterID = currentLightFont.fontMasters()[0].id()
# add master of interpolated font as master to main font
font.addFontMaster_(currentLightFont.fontMasters()[0])
# loop through glyphs
for index,glyph in enumerate(font.glyphs()):
  # get interpolated font's glyph at this index
  instanceGlyph = currentLightFont.glyphs()[index]

  ## 🚨here's where I get stuck. This won't work, probably because PyObjC requires more explicit setting methods:
  glyph.layerForKey_(currentLightFontMasterID) = instanceGlyph.layerForKey_(currentLightFontMasterID)
  ## Returns: SyntaxError: can't assign to function call

  ## 🚨I figure I may need to draw a new path with a pen, but this doesn't work:  
  glyph.layerForKey_(currentLightFontMasterID).drawBezierPath(instanceGlyph.layerForKey_(currentLightFontMasterID).bezierData())

  ## 🚨neither does this: 
  glyph.layerForKey_(currentLightFontMasterID).drawInPen_(instanceGlyph.layerForKey_(currentLightFontMasterID).drawBezierPath())

  ## 🚨additionally, the paths() property doesn't seem to yield any data, so I'm not sure whether I can save those to draw in later:
  print(glyph.layerForKey_(currentLightFontMasterID).paths())
  ## returns: None
  print(instanceGlyph.layerForKey_(currentLightFontMasterID).paths())
  ## returns: None

Am I on the right path in trying to use a pen to draw in bezier info from the interpolated font? I suspect this may be the way to go, as I don’t see any sort of specific “add layer data” method. However, it’s pretty unclear whether this is correct, or whether it’s way off. If this is the right direction, I would love to see an example of how to do it, if possible.

I suspect I may have similar trouble on step 4, duplicating kerning, because there is not clear “set kerning data” method. Will I have to loop through kerning, using setKerningForFontMasterID:LeftKey:RightKey:Value:direction, or is there some simpler way?

Thank you for any pointers on this!


#2

Why do you think you need to use the code API? What do like to modify?

You can’t set glyph.layerForKey_(). The equivalent is glyph.setLayer_forKey_(layer, MasterID)

But there is really no difference doing glyph.setLayer_forKey_(layer, MasterID) or glyph.layers[MasterID] = layer

Check the wrapper if in the GlyphsSDK to see what it does internally. The Code API is there for people that use ObjectiveC or need more details then the python wrapper provides.


#3

I’m not 100% sure I need the core API, but maybe you can help me know if I don’t.

I am working on a fontmake-based build process. As part of it, I want to correct the designspace of a glyphs source in an automated way, without manually opening Glyphs or editing the primary source directly or manually.

In the immediate case, I have a file with two weight masters with instances that have values to treat this like a 2-axis designspace of weight and grade (or a slightly modified version of grade, in which weight & width metrics change slightly for reverse or “negative” type setting). Ultimately, I am taking this source and building a 2-axis variable font. However, I want the build process to be a one-command interaction, so that I (or another person) can make edits to glyphs in the two masters of the original source file, and let those edits be automatically moved into the output variable font without several manual steps.

I could do most of this with a script in the Python API, but as far as I know, this would have to be triggered manually while the source is open in Glyphs. That would make this a partly-manual process because it would require someone to open Glyphs and trigger the Python API script. Therefore, it would be harder to handoff to someone else.

SO, is there a way to trigger a Python API script in Glyphs, in a remote way? If there is, I wouldn’t need the core API for this. If there is not, then I think I need the core API.

I’ll try the methods you’ve suggested and update this thread with the results. Thank you for the advice so far!


#4

I remember, you need to run that outside of Glyphs. Yes, then you need the core API.


Add custom parameter through Core API?
#5
for index,glyph in enumerate(font.glyphs()):
                instanceGlyph = currentLightFont.glyphs()[index]
                lightFontLayer = instanceGlyph.layerForKey_(currentLightFontMasterID)
                glyph.setLayer_forKey_(lightFontLayer, currentLightFontMasterID)

Is working great!

However, I am having difficulty in bringing in kerning, now that glyph paths are brought in.

# among other variations, I've tried...
font.setKerning_(currentLightFont.fontMasters()[0].kerning(), currentLightFontMasterID)
font.setKerning_forKey_(currentLightFont.kerning(), currentLightFontMasterID)

# both return: objc.error: NSInternalInconsistencyException - decodeObjectForKey: class "MGOrderedDictionary" not loaded

I was hoping that the setKerning_ method from the GlyphsSDK might work … but maybe I’m using it wrong?


#6

This should work:

f.kerning()[newMasterID] = instanceFont.kerning()[newMasterID]
f.kerning().setObject_forKey_(instanceFont.kerning().objectForKey_(newMasterID), newMasterID)

#7

Thanks! I think I’m close, but it’s not quite working for me, yet … whether I run those lines together or independently (commenting out one or the other), I get this same error:

objc.error: NSInternalInconsistencyException - decodeObjectForKey: class "MGOrderedDictionary" not loaded

If it helps, heres the full function that these exist in:

def copyFromInterpolatedFont(instanceIndex):
    instanceFont = font.generateInstance_error_(font.instances()[instanceIndex], None)
    instanceFontMasterID = instanceFont.fontMasters()[0].id()
    font.addFontMaster_(instanceFont.fontMasters()[0])
    newMasterID = instanceFontMasterID # these are the same; copying for clarity below

    print("\n=================================")
    print("Instance Weight: " + str(font.instances()[instanceIndex].interpolationWeight()))

    # copy glyphs from instance font to new master
    for index,glyph in enumerate(font.glyphs()): # (you can use font.glyphs()[:10] to do the first 10 glyphs only while making/testing script)
        print(". ", end="", flush=True) # shows progress while running script
        instanceGlyph = instanceFont.glyphs()[index] # make variable for glyph of interpolated font
        instanceFontLayer = instanceGlyph.layerForKey_(instanceFontMasterID) # get layer of glyph in instance font
        glyph.setLayer_forKey_(instanceFontLayer, newMasterID) # set layer in new master

    # bring kerning in from interpolated font
    font.kerning()[newMasterID] = instanceFont.kerning()[newMasterID]
    font.kerning().setObject_forKey_(instanceFont.kerning().objectForKey_(newMasterID), newMasterID)

Add custom parameter through Core API?
Add custom parameter through Core API?
#8

The code looks good now. I check the error message.


#9

I tried that myself. And it seems, getting an object and putting it back into Glyphs breaks it. I have to investigate it a bit more, until then you can only call methods on objects (like the generate_ in the sample). I see if I can do something about this.


#10

I did work on this a bit. I got your code to work properly but it requires quite a few (small) fixes to the object model.


#11

Awesome, thanks for taking a look and sharing what you found!

So, just to be sure I understand, does this mean that I need to hang tight and wait for a GlyphsApp update before this specific workflow will work? (That’s fine, by the way, but I know this would be a helpful thing to have in the future).

Alternatively, by “fixes to the object model,” do you mean that something in my script could change to make this work?


#12

I need to annotate all methods you like to use. And there is another problem that is not solved, yet. So it might take w while.

You might be more successful with AppleScript/ScriptingBride.