Drawbot Plugin code fail

Dear Glyph experts,

currently I am - as a complete newbie - trying to design a single line Tibetan font in Glyph.
Of course building on already existing Tibetan fonts.

As most vector programs cannot read single line fonts exported as OTF or TTF (as these violate standards), or if they can, they cannot take a Unicode Tibetan font, I am looking to export text via the DrawBot plugin.

I succesfully installed it via the plugin manager.
Glyphs 2.6.4
Drawbot latest install, DrawBot plugin installed today.

I try to run the following code through the plugin, and get the following code:

from GlyphsApp import *

fill(20/255, 67/255, 55/255)
rect(0,0,1000,1000)

B = BezierPath()
B.text(‘ཨོཾ’,font=Glyphs.font,fontSize=800, offset=(100,200))

strokeWidth(20)
stroke(1,1,1)
drawPath(B)
strokeWidth(0)
fill(27/255,133/255, 58/255)
drawPath(B)

Here comes the console message (and the code does not work)

Traceback (most recent call last):
File “”, line 7, in
File “/Users/martin/Library/Application Support/Glyphs/Repositories/DrawBot/DrawBot.glyphsPlugin/Contents/Resources/drawBot/context/baseContext.py”, line 216, in text
context.font(font, fontSize)
File “/Users/martin/Library/Application Support/Glyphs/Repositories/DrawBot/DrawBot.glyphsPlugin/Contents/Resources/drawBot/context/baseContext.py”, line 2065, in font
return self._state.text.font(fontName, fontSize)
File “/Users/martin/Library/Application Support/Glyphs/Repositories/DrawBot/DrawBot.glyphsPlugin/Contents/Resources/drawBot/context/baseContext.py”, line 1145, in font
font = _tryInstallFontFromFontName(font)
File “/Users/martin/Library/Application Support/Glyphs/Repositories/DrawBot/DrawBot.glyphsPlugin/Contents/Resources/drawBot/context/baseContext.py”, line 23, in _tryInstallFontFromFontName
return _drawBotDrawingTool._tryInstallFontFromFontName(fontName)
File “/Users/martin/Library/Application Support/Glyphs/Repositories/DrawBot/DrawBot.glyphsPlugin/Contents/Resources/drawBot/drawBotDrawingTools.py”, line 2159, in _tryInstallFontFromFontName
if os.path.exists(fontName) and not os.path.isdir(fontName):
File “/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/genericpath.py”, line 26, in exists
os.stat(path)
TypeError: coercing to Unicode: need string or buffer, NSKVONotifying_GSFont found

Any clues about this?
I would be thankful for any tips.
Maybe installed Python versions collide?

The BezierPath.textmethod is to draw a text with an existing font and the argument is the font name:

path.text("Hello world", font="Helvetica", fontSize=30, offset=(210, 210))

You need to go a bit more low level:

layer = Glyphs.font.glyphs["a-tibet"].layers[0]
scale(0.3)
translate(210,210)
drawPath(layer.bezierPath)

I can put to gather a bit more sample code if needed. Specially the shaping (the positioning of all the marks and stuff needs to be handled).

ah. ok, with the unicode name for the glyph I get something.
Still not the single line font (I thought it would take the currently selected one), but this is a step into the right direction!

I would be really grateful for more sample code, though.

So, I modified the code.

from GlyphsApp import *

fill(20/255, 67/255, 55/255)
rect(0,0,1000,1000)

strokeWidth(30)
stroke(1,1,1,1)

layer = Glyphs.font.glyphs[“GterYigMgoUmRnamBcadMa.”].layers[1]
print(layer.name)
scale(0.3)
translate(410,1000)
drawPath(layer.bezierPath)

It does work in a way.
Most of the Glyphs are not shown fully as intended.
Here one example:

This is the Glyph in the Glyph mode:
33
This is the Glyph in the Overview:
04
And this is the Glyph in the Drawbot plugin:
47

Quite a difference!
Any tips what I can do?

There is another property on the layer:

layer.openBezierPath

that contains the open part of the layer.
have a look at docu.glyphsapp.com

I had a look into this. It doesn’t look nice but should work.

from GlyphsApp import *
from Foundation import *
from AppKit import *
from CoreText import *

## page setup and font scale
fill(1, 1, 1, 1)
rect(0,0,1000,1000)
scale(200.0 / upm)
##

Font = Glyphs.font
upm = Font.unitsPerEm()
instance = Font.instances[0]

##  the next three lines are rather slow. Comment them out and put the path to the exported font in the forth line
instance.generate()
print(instance.lastExportedFilePath)
fontPath = instance.lastExportedFilePath.UTF8String()
#fontPath = "Path to the font"
##

dataProvider = CGDataProviderCreateWithFilename(fontPath)
_CTFont = CGFontCreateWithDataProvider(dataProvider)
_NSFont = CTFontCreateWithGraphicsFont(_CTFont, upm, None, None)

attributedString = NSAttributedString.alloc().initWithString_attributes_(u"क्कि", {NSFontAttributeName: _NSFont})
line = CTLineCreateWithAttributedString(attributedString)
runArray = CTLineGetGlyphRuns(line)
fill(None)

for runIndex in range(CFArrayGetCount(runArray)):
    run = CFArrayGetValueAtIndex(runArray, runIndex)
    for runGlyphIndex in range(CTRunGetGlyphCount(run)):
        thisGlyphRange = CFRangeMake(runGlyphIndex, 1)
        glyphID, = CTRunGetGlyphs(run, thisGlyphRange, None)
        position, = CTRunGetPositions(run, thisGlyphRange, None)
        glyphName = CGFontCopyGlyphNameForGlyph(_CTFont, glyphID)
        glyph = Font.glyphs[glyphName]
        if glyph is None:
            glyphName = Font.glyphsInfo().glyphInfoForName_(glyphName).name
            glyph = Font.glyphs[glyphName]
        layer = glyph.layers[0]
        save()
        translate(position.x, position.y)
        
        strokeWidth(10)
        stroke(255/(runGlyphIndex + 1)/255, 133/255, 158/255)
        drawPath(layer.bezierPath)
        #strokeWidth(0)
        #fill(255/(runGlyphIndex + 1)/255, 133/255, 158/255)
        #drawPath(layer.bezierPath)
        restore()

Ok, thanks!

so I added

drawPath(layer.openBezierPath)

to the above code and I get what I want.
I could also export it to SVG and import it into a vector drawing program (Amadine)
Thank you!

I will have a look at the more complex glyph stacking code later this weekend.

Hello again,

so, I got a little piece further.
I changed the code slightly to adapt to my needs:

from GlyphsApp import *
from Foundation import *
from AppKit import *
from CoreText import *

Font = Glyphs.font
upm = Font.unitsPerEm()
instance = Font.instances[0]

page setup and font scale

fill(1,1,1,1)
rect(0,0,1000,1000)
scale(200.0 / upm)

strokeWidth(50)
stroke(0, 0, 0, 1)

the next three lines are rather slow. Comment them out and put the path to the exported font in the forth line

#instance.generate()
#print(instance.lastExportedFilePath)
#fontPath = instance.lastExportedFilePath.UTF8String()
fontPath = “/Users/martin/Documents/Sherpa Watches/Mantra/mantra-watches und lasern/Single Line Fonts/KailasaSL-Regular.otf”

dataProvider = CGDataProviderCreateWithFilename(fontPath)
_CTFont = CGFontCreateWithDataProvider(dataProvider)
_NSFont = CTFontCreateWithGraphicsFont(_CTFont, upm, None, None)

attributedString = NSAttributedString.alloc().initWithString_attributes_(u"ཨོཾ་མ་ཎི་པདྨེ་ཧཱུྃ", {NSFontAttributeName: _NSFont})
line = CTLineCreateWithAttributedString(attributedString)
runArray = CTLineGetGlyphRuns(line)
fill(None)

for runIndex in range(CFArrayGetCount(runArray)):
run = CFArrayGetValueAtIndex(runArray, runIndex)
for runGlyphIndex in range(CTRunGetGlyphCount(run)):
thisGlyphRange = CFRangeMake(runGlyphIndex, 1)
glyphID, = CTRunGetGlyphs(run, thisGlyphRange, None)
position, = CTRunGetPositions(run, thisGlyphRange, None)
glyphName = CGFontCopyGlyphNameForGlyph(CTFont, glyphID)
glyph = Font.glyphs[glyphName]
if glyph is None:
glyphName = Font.glyphsInfo().glyphInfoForName
(glyphName).name
glyph = Font.glyphs[glyphName]
layer = glyph.layers[1]
save()
translate(position.x, position.y)

    strokeWidth(10)
    stroke(255/(runGlyphIndex + 1)/255, 133/255, 158/255)
    drawPath(layer.bezierPath)
    drawPath(layer.openBezierPath)
    #strokeWidth(0)
    #fill(255/(runGlyphIndex + 1)/255, 133/255, 158/255)
    #drawPath(layer.bezierPath)
    restore()

It then actually draws the open path of one glyph, but then fails to draw further.
Error code:

Traceback (most recent call last):
File “Skript2.py”, line 50, in
layer = glyph.layers[1]
AttributeError: ‘NoneType’ object has no attribute ‘layers’

Do you have another tip for me?

Thank you so much so far!

All the best,

Martin

Can you post the code enclosed in three grave accents ```? That way it will properly format.

The error means that the font doesn’t contain a glyph with the name you asked it. Add a print ("glyphName", glyphName) in line 50.

Ok, sorry:

from GlyphsApp import *
from Foundation import *
from AppKit import *
from CoreText import *

Font = Glyphs.font
upm = Font.unitsPerEm()
instance = Font.instances[0]

## page setup and font scale
fill(1,1,1,1)
rect(0,0,1000,1000)
scale(200.0 / upm)

strokeWidth(50)
stroke(0, 0, 0, 1)

##  the next three lines are rather slow. Comment them out and put the path to the exported font in the forth line
#instance.generate()
#print(instance.lastExportedFilePath)
#fontPath = instance.lastExportedFilePath.UTF8String()
fontPath = "/Users/martin/Documents/Sherpa Watches/Mantra/mantra-watches und lasern/Single Line Fonts/KailasaSL-Regular.otf"
##

dataProvider = CGDataProviderCreateWithFilename(fontPath)
_CTFont = CGFontCreateWithDataProvider(dataProvider)
_NSFont = CTFontCreateWithGraphicsFont(_CTFont, upm, None, None)

attributedString = NSAttributedString.alloc().initWithString_attributes_(u"ཨོཾ་མ་ཎི་པདྨེ་ཧཱུྃ", {NSFontAttributeName: _NSFont})
line = CTLineCreateWithAttributedString(attributedString)
runArray = CTLineGetGlyphRuns(line)
fill(None)

for runIndex in range(CFArrayGetCount(runArray)):
    run = CFArrayGetValueAtIndex(runArray, runIndex)
    for runGlyphIndex in range(CTRunGetGlyphCount(run)):
        thisGlyphRange = CFRangeMake(runGlyphIndex, 1)
        glyphID, = CTRunGetGlyphs(run, thisGlyphRange, None)
        position, = CTRunGetPositions(run, thisGlyphRange, None)
        glyphName = CGFontCopyGlyphNameForGlyph(_CTFont, glyphID)
        glyph = Font.glyphs[glyphName]
        if glyph is None:
            glyphName = Font.glyphsInfo().glyphInfoForName_(glyphName).name
            glyph = Font.glyphs[glyphName]
        layer = glyph.layers[1]
        save()
        translate(position.x, position.y)
        
        strokeWidth(10)
        stroke(0,0,0,1)
#        stroke(255/(runGlyphIndex + 1)/255, 133/255, 158/255)
        drawPath(layer.bezierPath)
        drawPath(layer.openBezierPath)
        #strokeWidth(0)
        #fill(255/(runGlyphIndex + 1)/255, 133/255, 158/255)
        #drawPath(layer.bezierPath)
        restore()

I will try the print to see what glyph is missing.
I was sure I created the needed ones… but maybe not!

The glyph is there but the export process has changed it and the script can’t find it. If you post the name, I might be able to find a way.

Dear Georg,

wow, thank you very much!
Actually, it is the first Glyph that is printed within the Tibetan phrase:

('glyphName', u'aa.')
Traceback (most recent call last):
  File "Skript2.py", line 45, in <module>
    layer = glyph.layers[1]
AttributeError: 'NoneType' object has no attribute 'layers'

And you are right (mind reader?), the glyph is there, it is “aa.”.

I would prefer if the script would access directly the active Glyph font, instead of the exported one.
Simply because I could not figure out how to change the code that way, I simply exported the new font and used your script.

It needs to use both fonts (the glyphs font and the exported OTF). The first for the outlines and the second for the layout information. And to make sure both are in sync, it is better to export it right away. To speed up debugging, you can use an exported font, but you really make sure that both match the glyphs.

Thank you for the clarification!
They are both in sync (just exported again to make sure).
Still the same error.
Yet, the exported one has a different naming of the file.
The glyph file is called Kailasa SL, yet the exported one is called Kailasa SL regular.
Could this be a topic?

Can you send me the .glyphs file?

Done :slight_smile:

The font uses very unusual glyph names. But if used as is by adding a “Don’t use Production Names” in the instance, it works fine.

But the rendering is not good as there are no OpenType features. Have a look at the Noto Tibetan https://github.com/googlefonts/noto-source/blob/master/src/NotoSerifTibetan-MM.glyphs. It has all the glyphs and features.

Thank you - I also looked at Noto Sans, but used in the first step the “standard” font for Tibetan on my Mac.
As I have no idea about fonts and font rules and conventions I could not see what you see.
So, Noto Sans actually could be better for single line because of more regular spacings between features / lines.

How do I add “Don’t use Production Names” in the instance?
Sorry, after reading the documentation it was not evident for me.

I added one line to the first block of code:

Font = Glyphs.font
upm = Font.unitsPerEm()
instance = Font.instances[0]
instance.customParameters["Don't use Production Names"] = True

It does not throw me an error, but does not solve the problem, either.

The custom parameter are added in Font Info > Instances > Custom Parameters > +.