summaryrefslogtreecommitdiff
path: root/misc/tools/rewrite-glyphorder.py
diff options
context:
space:
mode:
authorRasmus Andersson <rasmus@notion.se>2018-09-03 22:55:49 +0300
committerRasmus Andersson <rasmus@notion.se>2018-09-03 22:55:49 +0300
commitc833e252c925e8dd68108660710ca835d95daa6f (patch)
tree6b2e28264ed45efd7f054e453b622098d0d875b8 /misc/tools/rewrite-glyphorder.py
parent8c1a4c181ef12000179dfec541f1af87e9b03122 (diff)
downloadinter-c833e252c925e8dd68108660710ca835d95daa6f.tar.xz
Major overhaul, moving from UFO2 to Glyphs and UFO3, plus a brand new and much simpler fontbuild
Diffstat (limited to 'misc/tools/rewrite-glyphorder.py')
-rwxr-xr-xmisc/tools/rewrite-glyphorder.py305
1 files changed, 305 insertions, 0 deletions
diff --git a/misc/tools/rewrite-glyphorder.py b/misc/tools/rewrite-glyphorder.py
new file mode 100755
index 000000000..3da0c1699
--- /dev/null
+++ b/misc/tools/rewrite-glyphorder.py
@@ -0,0 +1,305 @@
+#!/usr/bin/env python
+# encoding: utf8
+from __future__ import print_function
+import os, sys, plistlib, json, re
+from collections import OrderedDict
+from argparse import ArgumentParser
+from ConfigParser import RawConfigParser
+from fontTools import ttLib
+from robofab.objects.objectsRF import OpenFont
+
+
+# Regex matching "default" glyph names, like "uni2043" and "u01C5"
+uniNameRe = re.compile(r'^u(?:ni)([0-9A-F]{4,8})$')
+
+
+class PList:
+ def __init__(self, filename):
+ self.filename = filename
+ self.plist = None
+
+ def load(self):
+ self.plist = plistlib.readPlist(self.filename)
+
+ def save(self):
+ if self.plist is not None:
+ plistlib.writePlist(self.plist, self.filename)
+
+ def get(self, k, defaultValue=None):
+ if self.plist is None:
+ self.load()
+ return self.plist.get(k, defaultValue)
+
+ def __getitem__(self, k):
+ if self.plist is None:
+ self.load()
+ return self.plist[k]
+
+ def __setitem__(self, k, v):
+ if self.plist is None:
+ self.load()
+ self.plist[k] = v
+
+ def __delitem__(self, k):
+ if self.plist is None:
+ self.load()
+ del self.plist[k]
+
+
+def parseAGL(filename): # -> { 2126: 'Omega', ... }
+ m = {}
+ with open(filename, 'r') as f:
+ for line in f:
+ # Omega;2126
+ # dalethatafpatah;05D3 05B2 # higher-level combinations; ignored
+ line = line.strip()
+ if len(line) > 0 and line[0] != '#':
+ name, uc = tuple([c.strip() for c in line.split(';')])
+ if uc.find(' ') == -1:
+ # it's a 1:1 mapping
+ m[int(uc, 16)] = name
+ return m
+
+
+def revCharMap(ucToNames):
+ # {2126:['Omega','Omegagr']} -> {'Omega':2126, 'Omegagr':2126}
+ # {2126:'Omega'} -> {'Omega':2126}
+ m = {}
+ if len(ucToNames) == 0:
+ return m
+
+ lists = True
+ for v in ucToNames.itervalues():
+ lists = not isinstance(v, str)
+ break
+
+ if lists:
+ for uc, names in ucToNames.iteritems():
+ for name in names:
+ m[name] = uc
+ else:
+ for uc, name in ucToNames.iteritems():
+ m[name] = uc
+
+ return m
+
+
+def loadJSONGlyphOrder(jsonFilename):
+ gol = None
+ if jsonFilename == '-':
+ gol = json.load(sys.stdin)
+ else:
+ with open(jsonFilename, 'r') as f:
+ gol = json.load(f)
+ if not isinstance(gol, list):
+ raise Exception('expected [[string, int|null]')
+ if len(gol) > 0:
+ for v in gol:
+ if not isinstance(v, list):
+ raise Exception('expected [[string, int|null]]')
+ break
+ return gol
+
+
+def loadTTGlyphOrder(font):
+ if isinstance(font, str):
+ font = ttLib.TTFont(font)
+
+ if not 'cmap' in font:
+ raise Exception('missing cmap table')
+
+ bestCodeSubTable = None
+ bestCodeSubTableFormat = 0
+
+ for st in font['cmap'].tables:
+ if st.platformID == 0: # 0=unicode, 1=mac, 2=(reserved), 3=microsoft
+ if st.format > bestCodeSubTableFormat:
+ bestCodeSubTable = st
+ bestCodeSubTableFormat = st.format
+
+ ucmap = {}
+ if bestCodeSubTable is not None:
+ for cp, glyphname in bestCodeSubTable.cmap.items():
+ ucmap[glyphname] = cp
+
+ gol = []
+ for name in font.getGlyphOrder():
+ gol.append((name, ucmap.get(name)))
+
+ return gol
+
+
+def loadSrcGlyphOrder(jsonFilename, fontFilename): # -> [ ('Omegagreek', 2126|None), ...]
+ if jsonFilename:
+ return loadJSONGlyphOrder(jsonFilename)
+ elif fontFilename:
+ return loadTTGlyphOrder(fontFilename.rstrip('/ '))
+ return None
+
+
+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 saveUFOGlyphOrder(libPlist, orderedNames, dryRun):
+ libPlist['public.glyphOrder'] = orderedNames
+
+ roboSort = libPlist.get('com.typemytype.robofont.sort')
+ if roboSort is not None:
+ # lib['com.typemytype.robofont.sort'] has schema
+ # [ { type: "glyphList", ascending: [glyphname, ...] }, ...]
+ for i in range(len(roboSort)):
+ ent = roboSort[i]
+ if isinstance(ent, dict) and ent.get('type') == 'glyphList':
+ roboSort[i] = {'type':'glyphList', 'ascending':orderedNames}
+ break
+
+ print('Writing', libPlist.filename)
+ if not dryRun:
+ libPlist.save()
+
+
+def getConfigResFile(config, basedir, name):
+ fn = os.path.join(basedir, config.get("res", name))
+ if not os.path.isfile(fn):
+ basedir = os.path.dirname(basedir)
+ fn = os.path.join(basedir, config.get("res", name))
+ if not os.path.isfile(fn):
+ fn = None
+ return fn
+
+
+def main():
+ argparser = ArgumentParser(description='Rewrite glyph order of UFO fonts')
+
+ 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(
+ '-src-json', dest='srcJSONFile', metavar='<file>', type=str,
+ help='JSON file to read glyph order from.' +
+ ' Should be a list e.g. [["Omega", 2126], [".notdef", null], ...]')
+
+ argparser.add_argument(
+ '-src-font', dest='srcFontFile', metavar='<file>', type=str,
+ help='TrueType or OpenType font to read glyph order from.')
+
+ argparser.add_argument(
+ '-out', dest='outFile', metavar='<file>', type=str,
+ help='Write each name per line to <file>')
+
+ 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.')
+
+ srcGol = loadSrcGlyphOrder(args.srcJSONFile, args.srcFontFile)
+ if srcGol is None:
+ argparser.error('No source provided (-src-* argument missing)')
+
+ # Load Adobe Glyph List database
+ srcDir = os.path.dirname(args.dstFontsPaths[0])
+ config = RawConfigParser(dict_type=OrderedDict)
+ config.read(os.path.join(srcDir, 'fontbuild.cfg'))
+ aglUcToName = parseAGL(getConfigResFile(config, srcDir, 'agl_glyphlistfile'))
+ aglNameToUc = revCharMap(aglUcToName)
+
+ glyphorderUnion = OrderedDict()
+
+ for dstFontPath in args.dstFontsPaths:
+ glyphOrder, ucToNames, nameToUc, libPlist = loadUFOGlyphNames(dstFontPath)
+
+ newGol = OrderedDict()
+ for name, uc in srcGol:
+
+ if uc is None:
+ # if there's no unicode associated, derive from name if possible
+ m = uniNameRe.match(name)
+ if m:
+ try:
+ uc = int(m.group(1), 16)
+ except:
+ pass
+ if uc is None:
+ uc = aglNameToUc.get(name)
+
+ # has same glyph mapped to same unicode
+ names = ucToNames.get(uc)
+ if names is not None:
+ for name in names:
+ # print('U %s U+%04X' % (name, uc))
+ newGol[name] = uc
+ continue
+
+ # has same name in dst?
+ uc2 = glyphOrder.get(name)
+ if uc2 is not None:
+ # print('N %s U+%04X' % (name, uc2))
+ newGol[name] = uc2
+ continue
+
+ # Try AGL[uc] -> name == name
+ if uc is not None:
+ name2 = aglUcToName.get(uc)
+ if name2 is not None:
+ uc2 = glyphOrder.get(name2)
+ if uc2 is not None:
+ # print('A %s U+%04X' % (name2, uc2))
+ newGol[name2] = uc2
+ continue
+
+ # else: ignore glyph name in srcGol not found in target
+ # if uc is None:
+ # print('x %s -' % name)
+ # else:
+ # print('x %s U+%04X' % (name, uc))
+
+
+ # add remaining glyphs from original glyph order
+ for name, uc in glyphOrder.iteritems():
+ if name not in newGol:
+ # print('E %s U+%04X' % (name, uc))
+ newGol[name] = uc
+
+ orderedNames = []
+ for name in newGol.iterkeys():
+ orderedNames.append(name)
+ glyphorderUnion[name] = True
+
+ saveUFOGlyphOrder(libPlist, orderedNames, dryRun)
+
+ if args.outFile:
+ print('Write', args.outFile)
+ glyphorderUnionNames = glyphorderUnion.keys()
+ if not dryRun:
+ with open(args.outFile, 'w') as f:
+ f.write('\n'.join(glyphorderUnionNames) + '\n')
+
+
+if __name__ == '__main__':
+ main()