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
Exemplo n.º 2
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.º 3
0
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)
Exemplo n.º 4
0
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
Exemplo n.º 5
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.º 6
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.º 7
0
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')
Exemplo n.º 8
0
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(']}')
Exemplo n.º 9
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.º 10
0
#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)
Exemplo n.º 11
0
	def setUpFont(self):
		self.font = OpenFont(ufoPath2)
		self.font.update()
Exemplo n.º 12
0
#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()
Exemplo n.º 13
0
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)
Exemplo n.º 14
0
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')
Exemplo n.º 15
0
                       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()
Exemplo n.º 16
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.º 17
0
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()
Exemplo n.º 18
0
	def setUpFont(self):
		self.dstDir = tempfile.mktemp()
		os.mkdir(self.dstDir)
		self.font = OpenFont(ufoPath2)
		self.font.save(self.dstDir)