Exporting a .ufo file with fontParts doesn't create a valid file



  • Hello there,

    Frederik helped me a few months ago and using his script, I managed to save shapes as glyphs in a .UFO file but when I open them they have:

    1. no width (I probably need to specify this when saving but I didn't know where);

    2. you can't type with them (in Glyphs at least). I suspect the unicode names for each glyph are not specified well enough.

    Everything happens in saveAsUFO(), where "bez" is my vector shape and "char" is specifying which glyph I want this shape to be saved in.

    from fontParts.world import RFont
    import math
    # create a font object
    myFont = RFont(showInterface=False)
        
    font = "LucidaGrande"
    charSet = ["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","0","1","2","3","4","5","6","7","8","9",".",",","!","?","[","]","-","—",'“','”','‘','’','"',"'"]
    
    def calculateDistance(pt1, pt2):  
        x1, y1 = pt1
        x2, y2 = pt2
        dist = math.sqrt((x2 - x1)**2 + (y2 - y1)**2)  
        return dist  
    
    def calculateHandles(pt1, pt2):
        x1, y1 = pt1
        x2, y2 = pt2
        dx = x2 - x1
        dy = y2 - y1
        offset = -0.2
        # vector (y, -x) is perpendicular to vector (x, y):
        handleOffsetX = offset * dy
        handleOffsetY = -offset * dx
        
        h1Pos = 0.25
        h2Pos = 0.75
    
        h1x = x1 + h1Pos * dx + handleOffsetX
        h1y = y1 + h1Pos * dy + handleOffsetY
    
        h2x = x1 + h2Pos * dx + handleOffsetX
        h2y = y1 + h2Pos * dy + handleOffsetY
    
        return (h1x, h1y), (h2x, h2y)
        
    def extractContours(bez):
        contours = []
        for cont in bez.contours:
            poly = []
            for seg in cont:
                for pt in seg:
                    poly.append(pt)
            contours.append(poly)
        return contours
        
    def saveAsUFO(path, char):
        # create a glyph object
        myGlyph = myFont.newGlyph(char)
        # get the pen
        myChar = myGlyph.getPen()
        path.drawToPen(myChar)
        
    def drawContours(contours, char):
        bez = BezierPath()
        for contour in contours:
            prevPoint = contour[0]
            bez.moveTo(prevPoint)
            for pt in contour[1:]:
                h1, h2 = calculateHandles(prevPoint, pt)
                bez.curveTo(h1, h2, pt)
                # bez.lineTo(pt)
                prevPoint = pt
            bez.closePath()
        drawPath(bez)
        
        saveAsUFO(bez, char)
    
        # Convert the letter into Bezier Path
    for i in range(len(charSet)):
        newPage(1000, 1000)
        bez = BezierPath()
        bez.text(charSet[i], font=font, fontSize=840)
        xMin, yMin, xMax, yMax = bez.bounds()
        cx = (xMin + xMax) / 6
        cy = (yMin + yMax) / 2
        bez.translate(cx, cy)
    
        contours = extractContours(bez)
        drawContours(contours, charSet[i])
        
    # save the ufo
    # myFont.save("Bogota.ufo")
    
    


  • hello @michelangelo,

    starting with (2): your character set is defined as unicode characters, but to create glyphs in a UFO font you need glyph names (for example AGL names, but there are other schemes like GNFUL).

    to get the glyph names you’ll need to convert first from unicode characters to unicode values. in Python3 this is easy: ord(char). then you can use the unicode value to look up the glyph name in your scheme of choice.

    here’s an example using fontTools, which is embedded in DrawBot like FontParts: (see this gist)

    from fontTools.agl import UV2AGL
    
    def saveAsUFO(path, char):
        # get unicode for character
        uni = ord(char)
        # get glyph name for unicode
        glyphName = UV2AGL.get(uni)
        # create a glyph object
        myGlyph = myFont.newGlyph(glyphName)
        # set unicode
        myGlyph.unicode = uni
        # get the pen
        pen = myGlyph.getPen()
        # draw path into glyph with pen
        path.drawToPen(pen)
    


  • regarding (1): you can get glyph widths by setting each character with the source font and measuring the text:

    # `font` is a DrawBot command and cannot be used as variable name
    fontName = "LucidaGrande"
    
    font(fontName)
    fontSize(1000)
    w, h = textSize(char)
    myGlyph.width = w
    

    the text font size is set to 1000 because this is the default UPM size when creating a new font, and we need a 1:1 scale in order to get width value.



  • @gferreira Thanks a lot. Everything works now!

    Follow-up questions will be:

    1. As I have an offsetValue to tweak the bending of the letters, would it be simple to do all of this inside Glyphs? This way I can open Glyphs, tweak it as much as I want and export right away as .OTF.

    2. Or how can I export an .OTF directly from DrawBot? I tried with myFont.generate() but something went wrong...

    formats = {
      'OpenType-CFF (.otf)' : 'otfcff',
      'OpenType-TTF (.ttf)' : 'otfttf',
      'PostScript (.pfa)'   : 'pctype1ascii'
    }
    
    # save the ufo
    if ExportUFO == True:
        myFont.save("Bogota.ufo")
        for format in formats.keys():
            print('Generating %s font...' % format)
            print(myFont.generate(formats[format]))
    
    ---------------------------------------------
    Generating OpenType-CFF (.otf) font...
    Traceback (most recent call last):
      File "Bogota5.py", line 126, in <module>
      File "/Applications/DrawBot.app/Contents/Resources/lib/python3.6/fontParts/base/font.py", line 362, in generate
      File "/Applications/DrawBot.app/Contents/Resources/lib/python3.6/fontParts/base/font.py", line 397, in _generate
      File "/Applications/DrawBot.app/Contents/Resources/lib/python3.6/fontParts/base/base.py", line 232, in raiseNotImplementedError
    NotImplementedError: The RFont subclass does not implement this method.
    


  • @frederik maybe you can help me with that one?


  • admin

    font.generate is RoboFont method and is not available inside DrawBot.

    However with ufo2fdk (which is embedded into DrawBot) you must be able to generate binaries (you will need to install FDK locally)

    or install fontMake and build a compiled font from a ufo.

    good luck!!!



  • @frederik Thank you! I don't exactly know how to use the ufo2fdk, but I will try.


  • admin

    Im pushing you to figure it out by yourself first 🙂

    see the documentation and the source of ufo2fdk

    and see documentation of fontMake



  • @frederik Thank you, Frederik. I tried both ufo2fdk I don't how to use, the documentation is a bit confusing for me.

    Anyways, with fontParts I ran the script but it gave me

    KeyError: 'unitsPerEm'
    

    Is it related to my font or to fontParts script in your opinion?


  • admin

    ufo2fdk needs some basic font info settings like descender, xHeight, ascender, capHeight, unitsPerEm

    # some default example values
    font.info.descender=-250
    font.info.xHeight=500
    font.info.ascender=750
    font.info.capHeight=750
    font.info.unitsPerEm=1000
    


  • @frederik Thank you, I managed! It works smoothly now—I was just missing the font.familyName but after adding this, it works.

    I was wondering if I can do it inside DrawBot right away? I tried to import fontmake or ufo2ft as a module but it doesn't work. Could it be that pip doesn't install it globally and DrawBot cannot find it—if that's the case where should I put it so DrawBot can see it?


  • admin

    yes you can use fontMake or ufo2ft, you just have to install it locally for the python3.6 (this is the embedded python in DrawBot)

    If you dont have python3.6 installed, you need to get it from https://www.python.org/downloads/.

    and install fontMake and/or ufo2ft with pip3.6 for py3.6

    good luck!