Exemplo n.º 1
0
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()
Exemplo n.º 2
0
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()
Exemplo n.º 3
0
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()
Exemplo n.º 4
0
font = OpenFont(ufopath)
effectiveAscender = max(font.info.ascender, font.info.unitsPerEm)

svgdir = args.svgdir
if len(svgdir) == 0:
  svgdir = os.path.join(
    os.path.dirname(ufopath),
    'svg',
    font.info.familyName,
    font.info.styleName
  )

print 'svgsync sync %s (%s)' % (font.info.familyName, font.info.styleName)

createSVGs = len(args.glyphs) > 0
glyphnames = args.glyphs if len(args.glyphs) else font.keys()

modifiedGlifFiles = []
for glyphname in glyphnames:
  glyphFile, mtime = syncGlyph(glyphname, createSVG=createSVGs)
  if glyphFile is not None:
    modifiedGlifFiles.append((glyphFile, mtime))

if len(modifiedGlifFiles) > 0:
  font.save()
  for glyphFile, mtime in modifiedGlifFiles:
    os.utime(glyphFile, (mtime, mtime))
    print 'svgsync write', glyphFile

Exemplo n.º 5
0
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`')
Exemplo n.º 6
0
"""
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)
Exemplo n.º 7
0
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)
Exemplo n.º 8
0
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()
Exemplo n.º 9
0
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()