SOLVED Error "fs.errors.ResourceNotFound" when trying to save script-generated, slanted UFO



  • I am working on a script to take in UFO paths and copy them as generated slanted fonts. Ultimately, I am prototyping sources for the ital axis of a variable font, and once I am happy with the overall slant, I will manually fix up the shapes.

    I have borrowed heavily from the Slanter extension for this, though I’ve added a bit of logic, e.g. to avoid adding extreme points (which break compatibility).

    However, even though I got this working pretty well earlier today, over time I started getting the following error. It became worse once I started adding in some handling for copying groups & kerning, but there was seemingly no single moment that it started crashing. Does this seem like an upstream issue, or could it be something to do with my computer’s RAM, or have I made some mistake in how I’m approaching this?

    Thanks so much for any insight! I’m happy to share my script as GitHub Gist or something, once I get this working well.

    Traceback (most recent call last):
      File "/Applications/RoboFont.app/Contents/Resources/lib/python3.7/fs/osfs.py", line 646, in open
    FileNotFoundError: [Errno 2] No such file or directory: b'/var/folders/x0/q5zt3sx15ssdz5mjcm9rgvh40000gn/T/tmp7ncr5598/temp.ufo/glyphs/E_.glif'
    
    During handling of the above exception, another exception occurred:
    
    Traceback (most recent call last):
      File "/Applications/RoboFont.app/Contents/Resources/lib/python3.7/fontTools/ufoLib/glifLib.py", line 291, in getGLIF
      File "/Applications/RoboFont.app/Contents/Resources/lib/python3.7/fs/wrapfs.py", line 342, in readbytes
      File "/Applications/RoboFont.app/Contents/Resources/lib/python3.7/fs/base.py", line 603, in readbytes
      File "/Applications/RoboFont.app/Contents/Resources/lib/python3.7/fs/osfs.py", line 646, in open
      File "/Applications/RoboFont.app/Contents/Resources/lib/python3.7/fs/error_tools.py", line 90, in __exit__
      File "six.pyc", line 702, in reraise
      File "/Applications/RoboFont.app/Contents/Resources/lib/python3.7/fs/osfs.py", line 646, in open
    fs.errors.ResourceNotFound: resource 'E_.glif' not found
    
    During handling of the above exception, another exception occurred:
    
    Traceback (most recent call last):
      File "prep-faux-italics.py", line 187, in <module>
      File "lib/fontObjects/fontPartsWrappers.pyc", line 1516, in save
      File "/Applications/RoboFont.app/Contents/Resources/lib/python3.7/fontParts/base/font.py", line 208, in save
      File "lib/fontObjects/fontPartsWrappers.pyc", line 1521, in _save
      File "lib/fontObjects/doodleFont.pyc", line 249, in save
      File "/Applications/RoboFont.app/Contents/Resources/lib/python3.7/defcon/objects/layer.py", line 581, in _stampGlyphDataState
      File "/Applications/RoboFont.app/Contents/Resources/lib/python3.7/fontTools/ufoLib/glifLib.py", line 295, in getGLIF
    fontTools.ufoLib.errors.GlifLibError: The file 'E_.glif' associated with glyph 'E' in contents.plist does not exist on <osfs '/var/folders/x0/q5zt3sx15ssdz5mjcm9rgvh40000gn/T/tmp7ncr5598/temp.ufo'>/glyphs
    

    Here’s my script so far:

    """
        A script to take the sources of Name Sans and output slanted versions of these, 
        for the purposes of A) prototyping & B) jumpstarting the italic drawings.
    
        Based largely on the Slanter extension:
        https://github.com/roboDocs/slanterRoboFontExtension/blob/57d7ac8ad9c91b0dce480cccb5a5dc2d4d4c51c4/Slanter.roboFontExt/lib/slanter.py
    
        GOALS:
    
        Copy all sources as italic sources, then:
    
        1. Start from the slanter extension and slant to 10.24 degrees, but:
        2. Slant round glyphs by less – maybe 7 degrees? # TODO
        3. don’t add extreme points (or any points)
        
        (Then, outside of script) make a designspace that will build these into Italic statics and an ital axis.
    """
    
    from vanilla.dialogs import *
    from math import radians
    from fontTools.misc.transform import Transform
    from mojo.roboFont import CurrentGlyph, CurrentFont, RGlyph, RPoint
    import os
    
    # ------------------------------
    # set preferences below
    
    openNewFonts = False
    saveNewFonts = True
    addExtremesToNewFonts = False
    
    outputFolder = "faux-italics"
    
    # glyphs that get double-slanted by slanter code
    # glyphsToDecompose = "b q u IJ arrowleft arrowright greater".split()
    setSkew = 10.24
    setRotation = 0
    rounds = "O o e c".split()
    roundSkew = 10.24 # TODO: set this to 7?
    # TODO: if you skew rounds differently, you must move them to match the movement of everything else
    
    dialogMessage = "Select UFOs to copy into faux-italics"
    
    # ------------------------------
    
    # Function mostly copied from the Slanter extension
    def getGlyph(glyph, skew, rotation, addComponents=False, skipComponents=False, addExtremes=False):
        skew = radians(skew)
        rotation = radians(-rotation)
    
        dest = glyph.copy()
    
        if not addComponents:
            for component in dest.components:
                pointPen = DecomposePointPen(glyph.layer, dest.getPointPen(), component.transformation)
                component.drawPoints(pointPen)
                dest.removeComponent(component)
    
        for contour in list(dest):
            if contour.open:
                dest.removeContour(contour)
    
        if skew == 0 and rotation == 0:
            return dest
    
        for contour in dest:
            for bPoint in contour.bPoints:
                bcpIn = bPoint.bcpIn
                bcpOut = bPoint.bcpOut
                if bcpIn == (0, 0):
                    continue
                if bcpOut == (0, 0):
                    continue
                if bcpIn[0] == bcpOut[0] and bcpIn[1] != bcpOut[1]:
                    bPoint.anchorLabels = ["extremePoint"]
                if rotation and bcpIn[0] != bcpOut[0] and bcpIn[1] == bcpOut[1]:
                    bPoint.anchorLabels = ["extremePoint"]
    
        cx, cy = 0, 0
        box = glyph.bounds
        if box:
            cx = box[0] + (box[2] - box[0]) * .5
            cy = box[1] + (box[3] - box[1]) * .5
    
        t = Transform()
        t = t.skew(skew)
        t = t.translate(cx, cy).rotate(rotation).translate(-cx, -cy)
    
        if not skipComponents:
            dest.transformBy(tuple(t))
        else:
            for contour in dest.contours:
                contour.transformBy(tuple(t))
    
            # this seems to work !!!
            for component in dest.components:
                # get component center
                _box = glyph.layer[component.baseGlyph].bounds
                if not _box:
                    continue
                _cx = _box[0] + (_box[2] - _box[0]) * .5
                _cy = _box[1] + (_box[3] - _box[1]) * .5
                # calculate origin in relation to base glyph
                dx = cx - _cx
                dy = cy - _cy
                # create transformation matrix
                tt = Transform()
                tt = tt.skew(skew)
                tt = tt.translate(dx, dy).rotate(rotation).translate(-dx, -dy)
                # apply transformation matrix to component offset
                P = RPoint()
                P.position = component.offset
                P.transformBy(tuple(tt))
                # set component offset position
                component.offset = P.position
    
        # check if "add extremes" is set to True
        if addExtremes:
            dest.extremePoints(round=0)
            for contour in dest:
                for point in contour.points:
                    if "extremePoint" in point.labels:
                        point.selected = True
                        point.smooth = True
                    else:
                        point.selected = False
    
        dest.removeSelection()
        dest.round()
        return dest
    
    # Function adapted from the Slanter extension
    def generateFont(fontToCopy):
    
        outFont = RFont(showInterface=False)
        outFont.info.update(fontToCopy.info.asDict())
        outFont.features.text = fontToCopy.features.text
    
        for glyph in fontToCopy:
            outFont.newGlyph(glyph.name)
            outGlyph = outFont[glyph.name]
            outGlyph.width = glyph.width
            outGlyph.unicodes = glyph.unicodes
    
            if glyph.name not in rounds:
                resultGlyph = getGlyph(glyph, setSkew, setRotation, addComponents=True, skipComponents=True, addExtremes=addExtremesToNewFonts)
            else:
                resultGlyph = getGlyph(glyph, roundSkew, setRotation, addComponents=True, skipComponents=True, addExtremes=addExtremesToNewFonts)
    
            outGlyph.appendGlyph(resultGlyph)
    
        # copy glyph order
        outFont.templateGlyphOrder = fontToCopy.templateGlyphOrder
    
        # copy groups & kerning
        outFont.groups.update(fontToCopy.groups.asDict())
        outFont.kerning.update(fontToCopy.kerning.asDict())
    
    
        # quick/lazy update to relative feature link
        outFont.features.text = "include(../../features/features.fea);"
    
        outFont.info.styleName = outFont.info.styleName + " Italic"
    
        return outFont
    
    
    # Get input font paths
    inputFonts = getFile(dialogMessage, allowsMultipleSelection=True, fileTypes=["ufo"])
    
    # Go through input paths & use to generate slanted fonts
    for fontPath in inputFonts:
        font = OpenFont(fontPath, showInterface=False)
    
        slantedFont = generateFont(font)
    
        fontDir, fontFile = os.path.split(fontPath)
        italicDir = fontDir + "/" + outputFolder
    
        if not os.path.exists(italicDir):
            os.makedirs(italicDir)
    
        slantedFontPath = italicDir + "/" + f"{fontFile.replace('.ufo','_Italic.ufo')}"
    
        if saveNewFonts:
            slantedFont.save(slantedFontPath)
    
        if openNewFonts:
            slantedFont.openInterface()
        else:
            slantedFont.close()
        
        font.close()
    


  • Okay, I think that fixes it!

    I will probably evolve this a bit further, but here is the code now:

    https://gist.github.com/arrowtype/a3c4445cd6b9a6174f885eccff38e1c9

    (Caveat: Not sure whether it would work for UFOZ)



  • Thanks for these questions!

    Does this traceback occur every time?
    are you overwriting ufos?

    Ahh this seems like it might be where the issue is.

    Regardless of the UFO (based on a few tests), I don’t get the error when writing to a brand-new italic UFO, but I always get this error if I am generating a faux-italic UFO for the second time, i.e. overwriting an existing one.

    That would explain why I didn’t get this right away, but started to after I had already generated UFOs.

    So, I think probably the solution will be checking if the UFO already exists, and deleting it before making a new one.

    are you using ufozzzz?

    Nope!


  • admin

    does this traceback occurs every time?

    are you overwriting ufos?
    are you using ufozzzz?



  • Worth mentioning:

    The font seems to save just fine, and the E is there, slanted.

    651d513c-d53d-44fb-8172-e8ebabb5e770-image.png

    In previous runs, it was flagging other glyphs, like G.RECT, which were also saving.

    The main problem with this error is that it prevents me from running through all my sources in one go.

    For now, I’ve made the lazy fix of just adding a try/except:

        try:
            if saveNewFonts:
                slantedFont.save(slantedFontPath)
        except:
            print("file not found error")
    

    ...but, I’m worried that this might introduce an error I’m missing. So, any recommendations about how I might want to proceed would be amazing. Thanks again!


Log in to reply