def loadLocalNamesDB(agl, diacriticComps): # { 2126: ['Omega', ...], ...} uc2names = None for fontPath in srcFontPaths: font = OpenFont(fontPath) if uc2names is None: uc2names = font.getCharacterMapping( ) # { 2126: ['Omega', ...], ...} else: for uc, names in font.getCharacterMapping().iteritems(): names2 = uc2names.get(uc, []) for name in names: if name not in names2: names2.append(name) uc2names[uc] = names2 # agl { 2126: 'Omega', ...} -> { 'Omega': [2126, ...], ...} aglName2Ucs = {} for uc, name in agl.iteritems(): aglName2Ucs.setdefault(name, []).append(uc) for glyphName, comp in diacriticComps.iteritems(): for uc in aglName2Ucs.get(glyphName, []): names = uc2names.get(uc, []) if glyphName not in names: names.append(glyphName) uc2names[uc] = names name2ucs = {} for uc, names in uc2names.iteritems(): for name in names: name2ucs.setdefault(name, set()).add(uc) return uc2names, name2ucs
def main(): argparser = ArgumentParser( description='Generate tabular number glyphs from regular number glyphs') argparser.add_argument( '-dry', dest='dryRun', action='store_const', const=True, default=False, help='Do not modify anything, but instead just print what would happen.') argparser.add_argument( 'fontPaths', metavar='<ufofile>', type=str, nargs='+', help='UFO fonts') args = argparser.parse_args() dryRun = args.dryRun # Strip trailing slashes from font paths and iterate for fontPath in [s.rstrip('/ ') for s in args.fontPaths]: fontName = os.path.basename(fontPath) font = OpenFont(fontPath) # Find widest glyph width = 0 for name in numNames: g = font[name] width = max(width, g.width) print('[%s] tnums width:' % fontName, width) # Create tnum glyphs for name in numNames: g = font[name] tnum = font.newGlyph(name + '.tnum') tnum.width = width # calculate component x-offset xoffs = 0 if g.width != width: print('[%s] gen (adjust width)' % fontName, tnum.name) # center shape, ignoring existing margins # xMin, yMin, xMax, yMax = g.box # graphicWidth = xMax - xMin # leftMargin = round((width - graphicWidth) / 2) # xoffs = leftMargin - g.leftMargin # adjust margins widthDelta = width - g.width leftMargin = g.leftMargin + int(floor(widthDelta / 2)) rightMargin = g.rightMargin + int(ceil(widthDelta / 2)) xoffs = leftMargin - g.leftMargin else: print('[%s] gen (same width)' % fontName, tnum.name) tnum.appendComponent(name, (xoffs, 0)) if dryRun: print('[%s] save [dry run]' % fontName) else: print('[%s] save' % fontName) font.save()
class ReadUFOFormatVersion2TestCase(unittest.TestCase): def setUpFont(self): self.font = OpenFont(ufoPath2) self.font.update() def tearDownFont(self): self.font.close() self.font = None def compareToUFO(self, doInfo=True): reader = UFOReader(ufoPath2) results = {} # info infoMatches = True info = self.font.info for attr, expectedValue in fontInfoVersion2.items(): writtenValue = getattr(info, attr) if expectedValue != writtenValue: infoMatches = False break results["info"]= infoMatches # kerning kerning = self.font.kerning.asDict() expectedKerning = reader.readKerning() results["kerning"] = expectedKerning == kerning # groups groups = dict(self.font.groups) expectedGroups = reader.readGroups() results["groups"] = expectedGroups == groups # features features = self.font.features.text expectedFeatures = reader.readFeatures() results["features"] = expectedFeatures == features # lib lib = dict(self.font.lib) expectedLib = reader.readLib() results["lib"] = expectedLib == lib return results def testFull(self): self.setUpFont() otherResults = self.compareToUFO() self.assertEqual(otherResults["info"], True) self.assertEqual(otherResults["kerning"], True) self.assertEqual(otherResults["groups"], True) self.assertEqual(otherResults["features"], True) self.assertEqual(otherResults["lib"], True) self.tearDownFont() def testInfo(self): self.setUpFont() info = self.font.info for attr, expectedValue in fontInfoVersion2.items(): writtenValue = getattr(info, attr) self.assertEqual((attr, expectedValue), (attr, writtenValue)) self.tearDownFont()
def main(): argparser = ArgumentParser( description='Generate kerning samples by providing the left-hand side glyph') argparser.add_argument( '-u', dest='formatAsUnicode', action='store_const', const=True, default=False, help='Format output as unicode escape sequences instead of glyphnames. ' + 'E.g. "\\u2126" instead of "\\Omega"') argparser.add_argument( '-suffix', dest='suffix', metavar='<text>', type=str, help='Text to append after each pair') argparser.add_argument( '-all-in-groups', dest='includeAllInGroup', action='store_const', const=True, default=False, help='Include all glyphs for groups rather than just the first glyph listed.') argparser.add_argument( 'fontPath', metavar='<ufofile>', type=str, help='UFO font source') argparser.add_argument( 'glyphnames', metavar='<glyphname>', type=str, nargs='+', help='Name of glyphs to generate samples for. '+ 'You can also provide a Unicode code point using the syntax "U+XXXX"') args = argparser.parse_args() font = OpenFont(args.fontPath) groupsFilename = os.path.join(args.fontPath, 'groups.plist') kerningFilename = os.path.join(args.fontPath, 'kerning.plist') groups = plistlib.readPlist(groupsFilename) # { groupName => [glyphName] } kerning = plistlib.readPlist(kerningFilename) # { leftName => {rightName => kernVal} } groupmap = mapGroups(groups) # expand any unicode codepoints glyphnames = [] for glyphname in args.glyphnames: if len(glyphname) > 2 and glyphname[:2] == 'U+': cp = int(glyphname[2:], 16) ucmap = font.getCharacterMapping() # { 2126: ['Omega', ...], ...} for glyphname2 in ucmap[cp]: glyphnames.append(glyphname2) else: glyphnames.append(glyphname) for glyphname in glyphnames: samplesForGlyphname(font, groups, groupmap, kerning, glyphname, args)
def loadUFOGlyphNames(ufoPath): font = OpenFont(ufoPath) libPlist = PList(os.path.join(ufoPath, 'lib.plist')) orderedNames = libPlist['public.glyphOrder'] # [ 'Omega', ...] # append any glyphs that are missing in orderedNames allNames = set(font.keys()) for name in orderedNames: allNames.discard(name) for name in allNames: orderedNames.append(name) ucToNames = font.getCharacterMapping() # { 2126: [ 'Omega', ...], ...} nameToUc = revCharMap(ucToNames) # { 'Omega': 2126, ...} gol = OrderedDict() # OrderedDict{ ('Omega', 2126|None), ...} for name in orderedNames: gol[name] = nameToUc.get(name) # gol.append((name, nameToUc.get(name))) return gol, ucToNames, nameToUc, libPlist
def main(): argparser = ArgumentParser( description='Operate on UFO glyf "lib" properties') argparser.add_argument( '-dry', dest='dryRun', action='store_const', const=True, default=False, help='Do not modify anything, but instead just print what would happen.') argparser.add_argument( '-m', dest='renameProps', metavar='<currentName>=<newName>[,...]', type=str, help='Rename properties') argparser.add_argument( 'fontPaths', metavar='<ufofile>', type=str, nargs='+', help='UFO fonts to update') args = argparser.parse_args() dryRun = args.dryRun renames = [] if args.renameProps: renames = [tuple(s.split('=')) for s in args.renameProps.split(',')] # TODO: verify data structure print('renaming properties:') for rename in renames: print(' %r => %r' % rename) # Strip trailing slashes from font paths and iterate for fontPath in [s.rstrip('/ ') for s in args.fontPaths]: font = OpenFont(fontPath) if len(renames): print('Renaming properties in %s' % fontPath) renameProps(font, renames) if dryRun: print('Saving changes to %s (dry run)' % fontPath) if not dryRun: print('Saving changes to %s' % fontPath) font.save()
def main(): argparser = ArgumentParser( description='Set robofont color marks on glyphs based on unicode categories') argparser.add_argument( '-dry', dest='dryRun', action='store_const', const=True, default=False, help='Do not modify anything, but instead just print what would happen.') argparser.add_argument( '-ucd', dest='ucdFile', metavar='<file>', type=str, help='UnicodeData.txt file from http://www.unicode.org/') argparser.add_argument( 'fontPaths', metavar='<ufofile>', type=str, nargs='+', help='UFO fonts to update') args = argparser.parse_args() dryRun = args.dryRun markLibKey = 'com.typemytype.robofont.mark' ucd = {} if args.ucdFile: ucd = parseUnicodeDataFile(args.ucdFile) for fontPath in args.fontPaths: font = OpenFont(fontPath) for g in font: rgba = colorForGlyph(g.name, g.unicodes, ucd) if rgba is None: if markLibKey in g.lib: del g.lib[markLibKey] else: g.lib[markLibKey] = [float(n) for n in rgba] print('Write', fontPath) if not dryRun: font.save()
def main(argv=None): argparser = ArgumentParser( description='Remove glyphs from all UFOs in src dir') argparser.add_argument( '-dry', dest='dryRun', action='store_const', const=True, default=False, help='Do not modify anything, but instead just print what would happen.') argparser.add_argument( '-decompose', dest='decompose', action='store_const', const=True, default=False, help='When deleting a glyph which is used as a component by another glyph '+ 'which is not being deleted, instead of refusing to delete the glyph, '+ 'decompose the component instances in other glyphs.') argparser.add_argument( '-ignore-git-state', dest='ignoreGitState', action='store_const', const=True, default=False, help='Skip checking with git if there are changes to the target UFO file.') argparser.add_argument( 'glyphs', metavar='<glyph>', type=str, nargs='+', help='Glyph to remove. '+ 'Can be a glyphname, '+ 'a Unicode code point formatted as "U+<CP>", '+ 'or a Unicode code point range formatted as "U+<CP>-<CP>"') args = argparser.parse_args(argv) global dryRun dryRun = args.dryRun srcDir = os.path.join(BASEDIR, 'src') # check if src font has modifications if not args.ignoreGitState: gitStatus = subprocess.check_output( ['git', '-C', BASEDIR, 'status', '-s', '--', os.path.relpath(os.path.abspath(srcDir), BASEDIR) ], shell=False) gitIsDirty = False gitStatusLines = gitStatus.splitlines() for line in gitStatusLines: if len(line) > 3 and line[:2] != '??': gitIsDirty = True break if gitIsDirty: if len(gitStatusLines) > 5: gitStatusLines = gitStatusLines[:5] + [' ...'] print( ("%s has uncommitted changes. It's strongly recommended to run this "+ "script on an unmodified UFO path so to allow \"undoing\" any changes. "+ "Run with -ignore-git-state to ignore this warning.\n%s") % ( srcDir, '\n'.join(gitStatusLines)), file=sys.stderr) sys.exit(1) # Find UFO fonts fontPaths = glob.glob(os.path.join(srcDir, '*.ufo')) if len(fontPaths) == 0: print('No UFOs found in', srcDir, file=sys.stderr) sys.exit(1) # load fontbuild config config = RawConfigParser(dict_type=OrderedDict) configFilename = os.path.join(srcDir, 'fontbuild.cfg') config.read(configFilename) glyphOrderFile = configFindResFile(config, srcDir, 'glyphorder') diacriticsFile = configFindResFile(config, srcDir, 'diacriticfile') featuresFile = os.path.join(srcDir, 'features.fea') # load AGL and diacritics agl = loadAGL(os.path.join(srcDir, 'glyphlist.txt')) # { 2126: 'Omega', ... } comps = loadGlyphCompositions(diacriticsFile) # { glyphName => (baseName, accentNames, offset) } # find glyphnames to remove that are composed (removal happens later) rmnamesUnion = getGlyphNamesComps(comps, agl, args.glyphs) # find glyphnames to remove from UFOs (and remove them) for fontPath in fontPaths: relFontPath = os.path.relpath(fontPath, BASEDIR) print('Loading glyph data for %s...' % relFontPath) font = OpenFont(fontPath) ucmap = font.getCharacterMapping() # { 2126: [ 'Omega', ...], ...} cnmap = font.getReverseComponentMapping() # { 'A' : ['Aacute', 'Aring'], 'acute' : ['Aacute'] ... } glyphnames = getGlyphNamesFont(font, ucmap, args.glyphs) if len(glyphnames) == 0: print('None of the glyphs requested exist in', relFontPath, file=sys.stderr) print('Preparing to remove %d glyphs — resolving component usage...' % len(glyphnames)) # Check component usage cnConflicts = {} for gname in glyphnames: cnUses = cnmap.get(gname) if cnUses: extCnUses = [n for n in cnUses if n not in glyphnames] if len(extCnUses) > 0: cnConflicts[gname] = extCnUses if len(cnConflicts) > 0: if args.decompose: componentsToDecompose = set() for gname in cnConflicts.keys(): componentsToDecompose.add(gname) for gname, dependants in cnConflicts.iteritems(): print('decomposing %s in %s' % (gname, ', '.join(dependants))) for depname in dependants: decomposeComponentInstances(font, font[depname], componentsToDecompose) else: print( '\nComponent conflicts.\n\n'+ 'Some glyphs to-be deleted are used as components in other glyphs.\n'+ 'You need to either decompose the components, also delete glyphs\n'+ 'using them, or not delete the glyphs at all.\n', file=sys.stderr) for gname, dependants in cnConflicts.iteritems(): print('%s used by %s' % (gname, ', '.join(dependants)), file=sys.stderr) sys.exit(1) # find orphaned pure-components for gname in glyphnames: try: g = font[gname] except: print('no glyph %r in %s' % (gname, relFontPath), file=sys.stderr) sys.exit(1) useCount = 0 for cn in g.components: usedBy = cnmap.get(cn.baseGlyph) if usedBy: usedBy = [name for name in usedBy if name not in glyphnames] if len(usedBy) == 0: cng = font[cn.baseGlyph] if len(cng.unicodes) == 0: print('Note: pure-component %s orphaned' % cn.baseGlyph) # remove glyphs from UFO print('Removing %d glyphs' % len(glyphnames)) libPlistFilename = os.path.join(fontPath, 'lib.plist') libPlist = plistlib.readPlist(libPlistFilename) glyphOrder = libPlist.get('public.glyphOrder') if glyphOrder is not None: v = [name for name in glyphOrder if name not in glyphnames] libPlist['public.glyphOrder'] = v roboSort = libPlist.get('com.typemytype.robofont.sort') if roboSort is not None: for entry in roboSort: if isinstance(entry, dict) and entry.get('type') == 'glyphList': asc = entry.get('ascending') if asc is not None: entry['ascending'] = [name for name in asc if name not in glyphnames] desc = entry.get('descending') if desc is not None: entry['descending'] = [name for name in desc if name not in glyphnames] for gname in glyphnames: font.removeGlyph(gname) rmnamesUnion.add(gname) if not dryRun: print('Writing changes to %s' % relFontPath) font.save() plistlib.writePlist(libPlist, libPlistFilename) else: print('Writing changes to %s (dry run)' % relFontPath) print('Cleaning up kerning') if dryRun: cleanup_kerning.main(['-dry', fontPath]) else: cleanup_kerning.main([fontPath]) # end for fontPath in fontPaths # fontbuild config updateDiacriticsFile(diacriticsFile, rmnamesUnion) updateConfigFile(config, configFilename, rmnamesUnion) featuresChanged = updateFeaturesFile(featuresFile, rmnamesUnion) # TMP for testing fuzzy # rmnamesUnion = set() # featuresChanged = False # with open('_local/rmlog') as f: # for line in f: # line = line.strip() # if len(line): # rmnamesUnion.add(line) print('\n————————————————————————————————————————————————————\n'+ 'Removed %d glyphs:\n %s' % ( len(rmnamesUnion), '\n '.join(sorted(rmnamesUnion)))) print('\n————————————————————————————————————————————————————\n') # find possibly-missed instances print('Fuzzy matches:') fuzzyMatchCount = 0 fuzzyMatchCount += grep(diacriticsFile, rmnamesUnion) fuzzyMatchCount += grep(configFilename, rmnamesUnion) fuzzyMatchCount += grep(featuresFile, rmnamesUnion) for fontPath in fontPaths: fuzzyMatchCount += grep(os.path.join(fontPath, 'lib.plist'), rmnamesUnion) if fuzzyMatchCount == 0: print(' (none)\n') else: print('You may want to look into those ^\n') if featuresChanged: print('You need to manually edit features.\n'+ '- git diff src/features.fea\n'+ '- $EDITOR %s/features.fea\n' % '/features.fea\n- $EDITOR '.join(fontPaths)) print(('You need to re-generate %s via\n'+ '`make src/glyphorder.txt` (or misc/gen-glyphorder.py)' ) % glyphOrderFile) print('\nFinally, you should build the Medium weight and make sure it all '+ 'looks good and that no mixglyph failures occur. E.g. `make Medium -j`')
#FLM: UFO Remove Overlap """ Remove overlap on all glyphs in a .ufo font. This script sis more than a little silly, but it demonstrates how objectsRF and objectsFL can work hand in hand. """ from robofab.objects.objectsRF import OpenFont from robofab.objects.objectsFL import NewFont from robofab.interface.all.dialogs import ProgressBar ufoFont = OpenFont(note="Select a .ufo") if ufoFont: bar = ProgressBar('Removing Overlap...', len(ufoFont)) flFont = NewFont() flGlyph = flFont.newGlyph('OverlapRemover') for ufoGlyph in ufoFont: flPen = flGlyph.getPointPen() ufoGlyph.drawPoints(flPen) flGlyph.removeOverlap() ufoPen = ufoGlyph.getPointPen() ufoGlyph.clear() flGlyph.drawPoints(ufoPen) flGlyph.clear() bar.tick() flFont.close(save=0) bar.close() ufoFont.save(doProgress=True)
def main(): argparser = argparse.ArgumentParser(description='Enrich UFO glyphnames') argparser.add_argument( '-dry', dest='dryRun', action='store_const', const=True, default=False, help='Do not modify anything, but instead just print what would happen.' ) argparser.add_argument( '-list-missing', dest='listMissing', action='store_const', const=True, default=False, help= 'List glyphs with unicodes found in source files but missing in any of the target UFOs.' ) argparser.add_argument( '-list-unnamed', dest='listUnnamed', action='store_const', const=True, default=False, help= "List glyphs with unicodes in target UFOs that don't have symbolic names." ) argparser.add_argument( '-backfill-agl', dest='backfillWithAgl', action='store_const', const=True, default=False, help= "Use glyphnames from Adobe Glyph List for any glyphs that no names in any of" + " the input font files") argparser.add_argument( '-src', dest='srcFonts', metavar='<fontfile>', type=str, nargs='*', help='TrueType, OpenType or UFO fonts to gather glyph info from. ' + 'Names found in earlier-listed fonts are prioritized over later listings.' ) argparser.add_argument('dstFonts', metavar='<ufofile>', type=str, nargs='+', help='UFO fonts to update') args = argparser.parse_args() # Load UFO fonts dstFonts = [] dstFontPaths = {} # keyed by RFont object srcDir = None for fn in args.dstFonts: fn = fn.rstrip('/') font = OpenFont(fn) dstFonts.append(font) dstFontPaths[font] = fn srcDir2 = os.path.dirname(fn) if srcDir is None: srcDir = srcDir2 elif srcDir != srcDir2: raise Exception('All <ufofile>s must be rooted in same directory') # load fontbuild configuration config = RawConfigParser(dict_type=OrderedDict) configFilename = os.path.join(srcDir, 'fontbuild.cfg') config.read(configFilename) glyphOrderFile = configFindResFile(config, srcDir, 'glyphorder') diacriticsFile = configFindResFile(config, srcDir, 'diacriticfile') glyphOrder = readGlyphOrderFile(glyphOrderFile) fallbackGlyphNames = {} # { 2126: 'Omega', ... } if args.backfillWithAgl: fallbackGlyphNames = parseAGL( configFindResFile(config, srcDir, 'agl_glyphlistfile')) # find glyph names uc2names, extraUc2names, name2ucsv = buildGlyphNames( dstFonts, args.srcFonts, glyphOrder, fallbackGlyphNames) # Note: name2ucsv has same order as parameters to buildGlyphNames if args.listMissing: print('# Missing glyphs: (found in -src but not in any <ufofile>)') for uc, names in extraUc2names.iteritems(): print('U+%04X\t%s' % (uc, ', '.join(names))) return elif args.listUnnamed: print('# Unnamed glyphs:') unnamed = set() for name in glyphOrder: if len(name) > 7 and name.startswith('uni'): unnamed.add(name) for gl in name2ucsv[:len(dstFonts)]: for name, ucs in gl.iteritems(): for uc in ucs: if isDefaultGlyphNameForUnicode(name, uc): unnamed.add(name) break for name in unnamed: print(name) return printDry = lambda *args: print(*args) if args.dryRun: printDry = lambda *args: print('[dry-run]', *args) newNames = {} renameGlyphsQueue = {} # keyed by RFont object for font in dstFonts: renameGlyphsQueue[font] = {} for uc, names in uc2names.iteritems(): if len(names) < 2: continue dstGlyphName = names[0] if isDefaultGlyphNameForUnicode(dstGlyphName, uc): newGlyphName = getFirstNonDefaultGlyphName(uc, names[1:]) # if newGlyphName is None: # # if we found no symbolic name, check in fallback list # newGlyphName = fallbackGlyphNames.get(uc) # if newGlyphName is not None: # printDry('Using fallback %s' % newGlyphName) if newGlyphName is not None: printDry('Rename %s -> %s' % (dstGlyphName, newGlyphName)) for font in dstFonts: if dstGlyphName in font: renameGlyphsQueue[font][dstGlyphName] = newGlyphName newNames[dstGlyphName] = newGlyphName if len(newNames) == 0: printDry('No changes') return # rename component instances for font in dstFonts: componentMap = font.getReverseComponentMapping() for currName, newName in renameGlyphsQueue[font].iteritems(): for depName in componentMap.get(currName, []): depG = font[depName] for c in depG.components: if c.baseGlyph == currName: c.baseGlyph = newName c.setChanged() # rename glyphs for font in dstFonts: for currName, newName in renameGlyphsQueue[font].iteritems(): font[currName].name = newName # save fonts and update font data for font in dstFonts: fontPath = dstFontPaths[font] printDry('Saving %d glyphs in %s' % (len(newNames), fontPath)) if not args.dryRun: font.save() renameUFODetails(font, fontPath, newNames, dryRun=args.dryRun, print=printDry) # update resource files renameGlyphOrderFile(glyphOrderFile, newNames, dryRun=args.dryRun, print=printDry) renameDiacriticsFile(diacriticsFile, newNames, dryRun=args.dryRun, print=printDry) renameConfigFile(config, configFilename, newNames, dryRun=args.dryRun, print=printDry)
class WriteUFOFormatVersion2TestCase(unittest.TestCase): def setUpFont(self): self.dstDir = tempfile.mktemp() os.mkdir(self.dstDir) self.font = OpenFont(ufoPath2) self.font.save(self.dstDir) def tearDownFont(self): shutil.rmtree(self.dstDir) def compareToUFO(self): readerExpected = UFOReader(ufoPath2) readerWritten = UFOReader(self.dstDir) results = {} # info matches = True expectedPath = os.path.join(ufoPath2, "fontinfo.plist") writtenPath = os.path.join(self.dstDir, "fontinfo.plist") if not os.path.exists(writtenPath): matches = False else: expected = readPlist(expectedPath) written = readPlist(writtenPath) for attr, expectedValue in expected.items(): if expectedValue != written[attr]: matches = False break results["info"] = matches # kerning matches = True expectedPath = os.path.join(ufoPath2, "kerning.plist") writtenPath = os.path.join(self.dstDir, "kerning.plist") if not os.path.exists(writtenPath): matches = False else: matches = readPlist(expectedPath) == readPlist(writtenPath) results["kerning"] = matches # groups matches = True expectedPath = os.path.join(ufoPath2, "groups.plist") writtenPath = os.path.join(self.dstDir, "groups.plist") if not os.path.exists(writtenPath): matches = False else: matches = readPlist(expectedPath) == readPlist(writtenPath) results["groups"] = matches # features matches = True expectedPath = os.path.join(ufoPath2, "features.fea") writtenPath = os.path.join(self.dstDir, "features.fea") if not os.path.exists(writtenPath): matches = False else: f = open(expectedPath, "r") expectedText = f.read() f.close() f = open(writtenPath, "r") writtenText = f.read() f.close() # FontLab likes to add lines to the features, so skip blank lines. expectedText = [line for line in expectedText.splitlines() if line] writtenText = [line for line in writtenText.splitlines() if line] matches = "\n".join(expectedText) == "\n".join(writtenText) results["features"] = matches # lib matches = True expectedPath = os.path.join(ufoPath2, "lib.plist") writtenPath = os.path.join(self.dstDir, "lib.plist") if not os.path.exists(writtenPath): matches = False else: writtenLib = readPlist(writtenPath) matches = readPlist(expectedPath) == writtenLib results["lib"] = matches return results def testFull(self): self.setUpFont() otherResults = self.compareToUFO() self.assertEqual(otherResults["info"], True) self.assertEqual(otherResults["kerning"], True) self.assertEqual(otherResults["groups"], True) self.assertEqual(otherResults["features"], True) self.assertEqual(otherResults["lib"], True) self.tearDownFont()
help='Path to UFO packages') argparser.add_argument('glyphs', metavar='<glyphname>', type=str, nargs='*', help='Only generate specific glyphs.') args = argparser.parse_args() if len(args.scale): scale = float(args.scale) ufopath = args.ufopath.rstrip('/') font = OpenFont(ufopath) effectiveAscender = max(font.info.ascender, font.info.unitsPerEm) srcdir = os.path.abspath(os.path.join(__file__, '..', '..')) # print('\n'.join(font.keys())) # sys.exit(0) agl = loadAGL(os.path.join(srcdir, 'src', 'glyphlist.txt')) # { 2126: 'Omega', ... } ignoreGlyphs = set(['.notdef', '.null']) glyphnames = args.glyphs if len(args.glyphs) else font.keys() glyphnameSet = set(glyphnames) generatedGlyphNames = set()
def main(): argparser = ArgumentParser( description= 'Generate info on name, unicodes and color mark for all glyphs') argparser.add_argument( '-ucd', dest='ucdFile', metavar='<file>', type=str, help='UnicodeData.txt file from http://www.unicode.org/') argparser.add_argument('fontPaths', metavar='<ufofile>', type=str, nargs='+', help='UFO fonts to update') args = argparser.parse_args() markLibKey = 'com.typemytype.robofont.mark' fontPaths = [] for fontPath in args.fontPaths: fontPath = fontPath.rstrip('/ ') if 'regular' or 'Regular' in fontPath: fontPaths = [fontPath] + fontPaths else: fontPaths.append(fontPath) fonts = [OpenFont(fontPath) for fontPath in args.fontPaths] agl = loadAGL('src/glyphlist.txt') # { 2126: 'Omega', ... } diacriticComps = loadGlyphCompositions('src/diacritics.txt') uc2names, name2ucs, allNames = loadLocalNamesDB(fonts, agl, diacriticComps) ucd = {} if args.ucdFile: ucd = parseUnicodeDataFile(args.ucdFile) glyphorder = OrderedDict() with open( os.path.join(os.path.dirname(args.fontPaths[0]), 'glyphorder.txt'), 'r') as f: for name in f.read().splitlines(): if len(name) and name[0] != '#': glyphorder[name] = True for name in diacriticComps.iterkeys(): glyphorder[name] = True glyphNames = glyphorder.keys() visitedGlyphNames = set() glyphs = [] for font in fonts: for name, v in glyphorder.iteritems(): if name in visitedGlyphNames: continue g = None ucs = [] try: g = font[name] ucs = g.unicodes except: ucs = name2ucs.get(name) if ucs is None: continue color = None if g is not None and markLibKey in g.lib: # TODO: translate from (r,g,b,a) to #RRGGBB (skip A) rgba = g.lib[markLibKey] if isinstance(rgba, list) or isinstance(rgba, tuple): color = rgbaToCSSColor(*rgba) elif name in diacriticComps: color = '<derived>' # name[, unicode[, unicodeName[, color]]] if len(ucs): for uc in ucs: ucName = unicodeName(ucd.get(uc)) if not ucName and uc >= 0xE000 and uc <= 0xF8FF: ucName = '[private use %04X]' % uc if color: glyph = [name, uc, ucName, color] elif ucName: glyph = [name, uc, ucName] else: glyph = [name, uc] glyphs.append(glyph) else: glyph = [name, None, None, color] if color else [name] glyphs.append(glyph) visitedGlyphNames.add(name) print('{"glyphs":[') prefix = ' ' for g in glyphs: print(prefix + json.dumps(g)) if prefix == ' ': prefix = ', ' print(']}')
def main(argv=None): argparser = ArgumentParser(description='Remove unused kerning') argparser.add_argument( '-dry', dest='dryRun', action='store_const', const=True, default=False, help='Do not modify anything, but instead just print what would happen.' ) argparser.add_argument('fontPaths', metavar='<ufofile>', type=str, nargs='+', help='UFO fonts to update') args = argparser.parse_args(argv) dryRun = args.dryRun agl = loadAGL('src/glyphlist.txt') # { 2126: 'Omega', ... } diacriticComps = loadGlyphCompositions( 'src/diacritics.txt') # {glyphName => (baseName, a, o)} for fontPath in args.fontPaths: print(fontPath) groupsFilename = os.path.join(fontPath, 'groups.plist') kerningFilename = os.path.join(fontPath, 'kerning.plist') groups = plistlib.readPlist( groupsFilename) # { groupName => [glyphName] } kerning = plistlib.readPlist( kerningFilename) # { leftName => {rightName => kernVal} } font = OpenFont(fontPath) uc2names, name2ucs, allNames = loadLocalNamesDB([font], agl, diacriticComps) # start with eliminating non-existent glyphs from groups and completely # eliminate groups with all-dead glyphs. eliminatedGroups = set() for groupName, glyphNames in list(groups.items()): glyphNames2 = [] for name in glyphNames: if name in allNames: glyphNames2.append(name) else: name2 = canonicalGlyphName(name, uc2names) if name2 != name and name2 in allNames: print('group: rename glyph', name, '->', name2) glyphNames2.append(name2) if len(glyphNames2) == 0: print('group: eliminate', groupName) eliminatedGroups.add(groupName) del groups[groupName] elif len(glyphNames2) != len(glyphNames): print('group: shrink', groupName) groups[groupName] = glyphNames2 # now eliminate kerning groupRefs = RefTracker( ) # tracks group references, so we can eliminate unreachable ones for leftName, right in list(kerning.items()): leftIsGroup = leftName[0] == '@' if leftIsGroup: if leftName in eliminatedGroups: print('kerning: eliminate LHS', leftName) del kerning[leftName] continue groupRefs.incr(leftName) else: if leftName not in allNames: print('kerning: eliminate LHS', leftName) del kerning[leftName] continue right2 = {} for rightName, kernVal in right.iteritems(): rightIsGroup = rightName[0] == '@' if rightIsGroup: if rightIsGroup in eliminatedGroups: print('kerning: eliminate RHS group', rightName) else: groupRefs.incr(rightName) right2[rightName] = kernVal else: if rightName not in allNames: # maybe an unnamed glyph? rightName2 = canonicalGlyphName(rightName, uc2names) if rightName2 != rightName: print('kerning: rename & update RHS glyph', rightName, '->', rightName2) right2[rightName2] = kernVal else: print('kerning: eliminate RHS glyph', rightName) else: right2[rightName] = kernVal if len(right2) == 0: print('kerning: eliminate LHS', leftName) del kerning[leftName] if leftIsGroup: groupRefs.decr(leftName) else: kerning[leftName] = right2 # eliminate any unreferenced groups for groupName, glyphNames in list(groups.items()): if not groupName in groupRefs: print('group: eliminate unreferenced group', groupName) del groups[groupName] # verify that there are no conflicting kerning pairs pairs = {} # { key => [...] } conflictingPairs = set() for leftName, right in kerning.iteritems(): # expand LHS group -> names topLeftName = leftName for leftName in groups[leftName] if leftName[0] == '@' else [ leftName ]: if leftName not in allNames: raise Exception('unknown LHS glyph name ' + repr(leftName)) keyPrefix = leftName + '+' for rightName, kernVal in right.iteritems(): # expand RHS group -> names topRightName = rightName for rightName in groups[rightName] if rightName[ 0] == '@' else [rightName]: if rightName not in allNames: raise Exception('unknown RHS glyph name ' + repr(rightName)) # print(leftName, '+', rightName, '=>', kernVal) key = keyPrefix + rightName isConflict = key in pairs pairs.setdefault(key, []).append( (topLeftName, topRightName, kernVal)) if isConflict: conflictingPairs.add(key) # # resolve pair conflicts by preferring pairs defined via group kerning # for key in conflictingPairs: # pairs = pairs[key] # print('kerning: conflicting pairs %r: %r' % (key, pairs)) # bestPair = None # redundantPairs = [] # for pair in pairs: # leftName, rightName, kernVal = pair # if bestPair is None: # bestPair = pair # else: # bestLeftName, bestRightName, _ = bestPair # bestScore = 0 # score = 0 # if bestLeftName[0] == '@': bestScore += 1 # if bestRightName[0] == '@': bestScore += 1 # if leftName[0] == '@': score += 1 # if rightName[0] == '@': score += 1 # if bestScore == 2: # # doesn't get better than this # break # elif score > bestScore: # redundantPairs.append(bestPair) # bestPair = pair # else: # redundantPairs.append(pair) # print('- keeping', bestPair) # print('- eliminating', redundantPairs) # for redundantPairs # # eliminate any unreferenced groups # for groupName, glyphNames in list(groups.items()): # if not groupName in groupRefs: # print('group: eliminate unreferenced group', groupName) # del groups[groupName] print('Write', groupsFilename) if not dryRun: plistlib.writePlist(groups, groupsFilename) print('Write', kerningFilename) if not dryRun: plistlib.writePlist(kerning, kerningFilename)
def main(): argparser = ArgumentParser(description='Fixup diacritic names') argparser.add_argument( '-dry', dest='dryRun', action='store_const', const=True, default=False, help='Do not modify anything, but instead just print what would happen.' ) argparser.add_argument('fontPaths', metavar='<ufofile>', type=str, nargs='+', help='UFO fonts') args = argparser.parse_args() dryRun = args.dryRun uc2names = {} name2ucs = {} for fontPath in args.fontPaths: font = OpenFont(fontPath) _uc2names, _name2ucs = loadFontGlyphs(font) for uc, _names in _uc2names.iteritems(): names = uc2names.setdefault(uc, []) for name in _names: if name not in names: names.append(name) for name, _ucs in _name2ucs.iteritems(): ucs = name2ucs.setdefault(name, []) for uc in _ucs: if uc not in ucs: ucs.append(uc) agl = loadAGL('src/glyphlist.txt') # { 2126: 'Omega', ... } diacriticsFilename = 'src/diacritics.txt' diacriticComps = loadGlyphCompositions( diacriticsFilename) # {glyphName => (baseName, a, o)} for glyphName, comp in list(diacriticComps.items()): if glyphName not in name2ucs: uc = unicodeForDefaultGlyphName(glyphName) if uc is not None: aglName = agl.get(uc) if aglName is not None: if aglName in diacriticComps: raise Exception( 'composing same glyph with different names:', aglName, glyphName) print('rename', glyphName, '->', aglName, '(U+%04X)' % uc) del diacriticComps[glyphName] diacriticComps[aglName] = comp lines = [] for glyphName, comp in diacriticComps.iteritems(): lines.append(fmtGlyphComposition(glyphName, *comp)) # print('\n'.join(lines)) print('Write', diacriticsFilename) if not dryRun: with open(diacriticsFilename, 'w') as f: for line in lines: f.write(line + '\n')
def setUpFont(self): self.font = OpenFont(ufoPath2) self.font.update()
class ReadUFOFormatVersion1TestCase(unittest.TestCase): def setUpFont(self): self.font = OpenFont(ufoPath1) self.font.update() def tearDownFont(self): self.font.close() self.font = None def compareToUFO(self, doInfo=True): reader = UFOReader(ufoPath1) results = {} # info infoMatches = True info = self.font.info for attr, expectedValue in expectedFontInfo1To2Conversion.items(): writtenValue = getattr(info, attr) if expectedValue != writtenValue: infoMatches = False break results["info"]= infoMatches # kerning kerning = self.font.kerning.asDict() expectedKerning = reader.readKerning() results["kerning"] = expectedKerning == kerning # groups groups = dict(self.font.groups) expectedGroups = reader.readGroups() results["groups"] = expectedGroups == groups # features features = self.font.features.text f = open(os.path.join(ufoPath2, "features.fea"), "r") expectedFeatures = f.read() f.close() match = True features = [line for line in features.splitlines() if line] expectedFeatures = [line for line in expectedFeatures.splitlines() if line] if expectedFeatures != features or reader.readFeatures() != "": match = False results["features"] = match # lib lib = dict(self.font.lib) expectedLib = reader.readLib() for key in removeFromFormatVersion1Lib: if key in expectedLib: del expectedLib[key] results["lib"] = expectedLib == lib return results def testFull(self): self.setUpFont() otherResults = self.compareToUFO() self.assertEqual(otherResults["info"], True) self.assertEqual(otherResults["kerning"], True) self.assertEqual(otherResults["groups"], True) self.assertEqual(otherResults["features"], True) self.assertEqual(otherResults["lib"], True) self.tearDownFont() def testInfo(self): self.setUpFont() info = self.font.info for attr, expectedValue in expectedFontInfo1To2Conversion.items(): writtenValue = getattr(info, attr) self.assertEqual((attr, expectedValue), (attr, writtenValue)) self.tearDownFont()
def setUpFont(self): self.dstDir = tempfile.mktemp() os.mkdir(self.dstDir) self.font = OpenFont(ufoPath2) self.font.save(self.dstDir)
def main(): argparser = ArgumentParser(description='Fixup features.fea') argparser.add_argument( '-dry', dest='dryRun', action='store_const', const=True, default=False, help='Do not modify anything, but instead just print what would happen.' ) argparser.add_argument('fontPaths', metavar='<ufofile>', type=str, nargs='+', help='UFO fonts to update') args = argparser.parse_args() dryRun = args.dryRun agl = loadAGL('src/glyphlist.txt') # { 2126: 'Omega', ... } diacriticComps = loadGlyphCompositions( 'src/diacritics.txt') # {glyphName => (baseName, a, o)} # collect glyph names fonts = [OpenFont(fontPath) for fontPath in args.fontPaths] uc2names, name2ucs, allNames = loadLocalNamesDB(fonts, agl, diacriticComps) includeRe = re.compile(r'^include\(([^\)]+)\);\s*$') # open features.fea featuresLines = loadFeaturesFile(os.path.join(fontPath, 'features.fea')) classDefRe = re.compile(r'^@([^\s=]+)\s*=\s*\[([^\]]+)\]\s*;\s*$') subRe = re.compile(r'^\s*sub\s+(.+)(\'?)\s+by\s+(.+)\s*;\s*$') sub2Re = re.compile( r'^\s*sub\s+([^\[]+)\s+\[\s*([^\]]+)\s*\](\'?)\s+by\s+(.+)\s*;\s*$') # sub lmidtilde [uni1ABB uni1ABD uni1ABE]' by uni1ABE.w2; # sub lmidtilde uni1ABC' by uni1ABC.w2; spacesRe = re.compile(r'[\s\r\n]+') classDefs = {} featuresLines2 = [] for line in featuresLines: clsM = classDefRe.match(line) if clsM is not None: clsName = clsM.group(1) names = spacesRe.split(clsM.group(2).strip()) if clsName in classDefs: raise Exception('duplicate class definition ' + clsName) # print('classdef', clsName, ' '.join(names)) # print('classdef', clsName) names2 = [] for name in names: if name == '-': # e.g. A - Z names2.append(name) continue if name[0] != '@': canonName = canonicalGlyphName(name, uc2names) if canonName != name: # print('renaming ' + name + ' -> ' + canonName) names2.append(canonName) elif name not in allNames: print('skipping unknown glyph ' + name) else: names2.append(name) else: raise Exception('todo: class-ref ' + name + ' in class-def ' + clsName) classDefs[clsName] = names2 line = '@%s = [ %s ];' % (clsName, ' '.join(names2)) featuresLines2.append(line) continue # sub2M = sub2Re.match(line) # if sub2M is not None: # findNames1 = spacesRe.split(sub2M.group(1)) # findNames2 = spacesRe.split(sub2M.group(2)) # apos = sub2M.group(3) # rightName = sub2M.group(4) # print('TODO: sub2', findNames1, findNames2, apos, rightName) # featuresLines2.append(line) # continue sub2M = sub2Re.match(line) subM = None if sub2M is None: subM = subRe.match(line) if subM is not None or sub2M is not None: findNamesStr = '' findNamesHasBrackets = False findNames = [] findNamesBStr = '' findNamesBHasBrackets = False findNamesB = [] newNamesStr = '' newNamesHasBrackets = False newNames = [] apos0 = '' if subM is not None: findNamesStr = subM.group(1) apos0 = subM.group(2) newNamesStr = subM.group(3) else: # sub2M findNamesStr = sub2M.group(1) findNamesBStr = sub2M.group(2) apos0 = sub2M.group(3) newNamesStr = sub2M.group(4) if newNamesStr[0] == '[': newNamesHasBrackets = True newNamesStr = newNamesStr.strip('[ ]') newNames = spacesRe.split(newNamesStr) if findNamesStr[0] == '[': findNamesHasBrackets = True findNamesStr = findNamesStr.strip('[ ]') findNames = spacesRe.split(findNamesStr) if findNamesBStr != '': if findNamesBStr[0] == '[': findNamesBHasBrackets = True findNamesBStr = findNamesBStr.strip('[ ]') findNamesB = spacesRe.split(findNamesBStr) names22 = [] for names in [findNames, findNamesB, newNames]: names2 = [] for name in names: if name[0] == '@': clsName = name[1:].rstrip("'") if clsName not in classDefs: raise Exception('sub: missing target class ' + clsName + ' at\n' + line) names2.append(name) else: apos = name[-1] == "'" if apos: name = name[:-1] if name not in allNames: canonName = canonicalGlyphName(name, uc2names) if canonName != name: print('renaming ' + name + ' -> ' + canonName) name = canonName else: raise Exception('TODO: unknown name', name) # if we remove names, we also need to remove subs (that become empty), and so on. if apos: name += "'" names2.append(name) names22.append(names2) findNames2, findNamesB2, newNames2 = names22 findNamesStr = ' '.join(findNames2) if findNamesHasBrackets: findNamesStr = '[' + findNamesStr + ']' if findNamesBStr != '': findNamesBStr = ' '.join(findNamesB2) if findNamesBHasBrackets: findNamesBStr = '[' + findNamesBStr + ']' newNamesStr = ' '.join(newNames2) if newNamesHasBrackets: newNamesStr = '[' + newNamesStr + ']' if subM is not None: line = ' sub %s%s by %s;' % (findNamesStr, apos0, newNamesStr) else: # if subM is None: # sub bbar [uni1ABB uni1ABD uni1ABE]' by uni1ABE.w2; line = ' sub %s [%s]%s by %s;' % (findNamesStr, findNamesBStr, apos0, newNamesStr) featuresLines2.append(line) print('Write', featuresFilename) if not dryRun: with open(featuresFilename + '2', 'w') as f: for line in featuresLines2: f.write(line + '\n')
#FLM: Single Glyph from a UFO """Import one glyph from a .ufo, i.e. a single .glif file. """ from robofab.world import CurrentFont from robofab.objects.objectsRF import OpenFont from robofab.interface.all.dialogs import SelectGlyph, Message flFont = CurrentFont() if flFont is None: Message("Please have a FontLab destination font ready..") else: # pick a .ufo rfFont = OpenFont() if rfFont is not None: # pick a glyph in the .ufo rfGlyph = SelectGlyph(rfFont) if rfGlyph is not None: # make a new glyph in the FL font flGlyph = flFont.newGlyph(rfGlyph.name, clear=True) # draw the glyph into the FL font pen = flGlyph.getPointPen() rfGlyph.drawPoints(pen) # set the width, unicodes and lib flGlyph.width = rfGlyph.width flGlyph.unicodes = rfGlyph.unicodes flGlyph.lib = rfGlyph.lib flGlyph.note = rfGlyph.note flGlyph.update() flFont.update()
def main(): jsonSchemaDescr = '{[unicode:int]: glyphname:string, ...}' argparser = ArgumentParser( description= 'Rename glyphnames in UFO kerning and remove unused groups and glyphnames.' ) argparser.add_argument( '-dry', dest='dryRun', action='store_const', const=True, default=False, help='Do not modify anything, but instead just print what would happen.' ) argparser.add_argument('-no-stats', dest='noStats', action='store_const', const=True, default=False, help='Do not print statistics at the end.') argparser.add_argument('-save-stats', dest='saveStatsPath', metavar='<file>', type=str, help='Write detailed statistics to JSON file.') argparser.add_argument('-src-json', dest='srcJSONFile', metavar='<file>', type=str, help='JSON file to read glyph names from.' + ' Expected schema: ' + jsonSchemaDescr + ' (e.g. {2126: "Omega"})') argparser.add_argument( '-src-font', dest='srcFontFile', metavar='<file>', type=str, help='TrueType or OpenType font to read glyph names from.') argparser.add_argument('dstFontsPaths', metavar='<ufofile>', type=str, nargs='+', help='UFO fonts to update') args = argparser.parse_args() dryRun = args.dryRun if args.srcJSONFile and args.srcFontFile: argparser.error( 'Both -src-json and -src-font specified -- please provide only one.' ) # Strip trailing slashes from font paths args.dstFontsPaths = [s.rstrip('/ ') for s in args.dstFontsPaths] # Load source char map srcCharMap = None if args.srcJSONFile: try: srcCharMap = loadJSONCharMap(args.srcJSONFile) except Exception as err: argparser.error('Invalid JSON: Expected schema %s (%s)' % (jsonSchemaDescr, err)) elif args.srcFontFile: srcCharMap = getTTCharMap( args.srcFontFile.rstrip('/ ')) # -> { 2126: 'Omegagreek', ...} else: argparser.error('No source provided (-src-* argument missing)') if len(srcCharMap) == 0: print('Empty character map', file=sys.stderr) sys.exit(1) # Find project source dir srcDir = '' for dstFontPath in args.dstFontsPaths: s = os.path.dirname(dstFontPath) if not srcDir: srcDir = s elif srcDir != s: raise Exception( 'All <ufofile>s must be rooted in the same directory') # Load font project config # load fontbuild configuration config = RawConfigParser(dict_type=OrderedDict) configFilename = os.path.join(srcDir, 'fontbuild.cfg') config.read(configFilename) diacriticsFile = configFindResFile(config, srcDir, 'diacriticfile') for dstFontPath in args.dstFontsPaths: dstFont = OpenFont(dstFontPath) dstCharMap = dstFont.getCharacterMapping( ) # -> { 2126: [ 'Omega', ...], ...} dstRevCharMap = revCharMap(dstCharMap) # { 'Omega': 2126, ...} srcToDstMap = getGlyphNameDifferenceMap(srcCharMap, dstCharMap, dstRevCharMap) stats = Stats() groups, glyphToGroups = fixupGroups(dstFontPath, dstRevCharMap, srcToDstMap, dryRun, stats) fixupKerning(dstFontPath, dstRevCharMap, srcToDstMap, groups, glyphToGroups, dryRun, stats) # stats if args.saveStatsPath or not args.noStats: if not args.noStats: print('stats for %s:' % dstFontPath) print(' Deleted %d groups and %d glyphs.' % (len(stats.removedGroups), len(stats.removedGlyphs))) print(' Renamed %d glyphs.' % len(stats.renamedGlyphs)) print(' Simplified %d kerning pairs.' % len(stats.simplifiedKerningPairs)) if args.saveStatsPath: statsObj = { 'deletedGroups': stats.removedGroups, 'deletedGlyphs': stats.removedGlyphs, 'simplifiedKerningPairs': stats.simplifiedKerningPairs, 'renamedGlyphs': stats.renamedGlyphs, } f = sys.stdout try: if args.saveStatsPath != '-': f = open(args.saveStatsPath, 'w') print('Writing stats to', args.saveStatsPath) json.dump(statsObj, sys.stdout, indent=2, separators=(',', ': ')) finally: if f is not sys.stdout: f.close()
class WriteUFOFormatVersion1TestCase(unittest.TestCase): def setUpFont(self): self.dstDir = tempfile.mktemp() os.mkdir(self.dstDir) self.font = OpenFont(ufoPath2) self.font.save(self.dstDir, formatVersion=1) def tearDownFont(self): shutil.rmtree(self.dstDir) def compareToUFO(self): readerExpected = UFOReader(ufoPath1) readerWritten = UFOReader(self.dstDir) results = {} # info matches = True expectedPath = os.path.join(ufoPath1, "fontinfo.plist") writtenPath = os.path.join(self.dstDir, "fontinfo.plist") if not os.path.exists(writtenPath): matches = False else: expected = readPlist(expectedPath) written = readPlist(writtenPath) for attr, expectedValue in expected.items(): if expectedValue != written.get(attr): matches = False break results["info"] = matches # kerning matches = True expectedPath = os.path.join(ufoPath1, "kerning.plist") writtenPath = os.path.join(self.dstDir, "kerning.plist") if not os.path.exists(writtenPath): matches = False else: matches = readPlist(expectedPath) == readPlist(writtenPath) results["kerning"] = matches # groups matches = True expectedPath = os.path.join(ufoPath1, "groups.plist") writtenPath = os.path.join(self.dstDir, "groups.plist") if not os.path.exists(writtenPath): matches = False else: matches = readPlist(expectedPath) == readPlist(writtenPath) results["groups"] = matches # features matches = True expectedPath = os.path.join(ufoPath1, "features.fea") writtenPath = os.path.join(self.dstDir, "features.fea") if os.path.exists(writtenPath): matches = False results["features"] = matches # lib matches = True expectedPath = os.path.join(ufoPath1, "lib.plist") writtenPath = os.path.join(self.dstDir, "lib.plist") if not os.path.exists(writtenPath): matches = False else: writtenLib = readPlist(writtenPath) matches = readPlist(expectedPath) == writtenLib results["lib"] = matches return results def testFull(self): self.setUpFont() otherResults = self.compareToUFO() self.assertEqual(otherResults["info"], True) self.assertEqual(otherResults["kerning"], True) self.assertEqual(otherResults["groups"], True) self.assertEqual(otherResults["features"], True) self.assertEqual(otherResults["lib"], True) self.tearDownFont()