summaryrefslogtreecommitdiffstats
path: root/lib/silfont/scripts/psfglyphs2ufo.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/silfont/scripts/psfglyphs2ufo.py')
-rw-r--r--lib/silfont/scripts/psfglyphs2ufo.py275
1 files changed, 275 insertions, 0 deletions
diff --git a/lib/silfont/scripts/psfglyphs2ufo.py b/lib/silfont/scripts/psfglyphs2ufo.py
new file mode 100644
index 0000000..3af2ddd
--- /dev/null
+++ b/lib/silfont/scripts/psfglyphs2ufo.py
@@ -0,0 +1,275 @@
+#!/usr/bin/env python
+__doc__ = '''Export fonts in a GlyphsApp file to UFOs'''
+__url__ = 'http://github.com/silnrsi/pysilfont'
+__copyright__ = 'Copyright (c) 2017 SIL International (http://www.sil.org)'
+__license__ = 'Released under the MIT License (http://opensource.org/licenses/MIT)'
+__author__ = 'Victor Gaultney'
+
+from silfont.core import execute
+from silfont.ufo import obsoleteLibKeys
+
+import glyphsLib
+import silfont.ufo
+import silfont.etutil
+from io import open
+import os, shutil
+
+argspec = [
+ ('glyphsfont', {'help': 'Input font file'}, {'type': 'filename'}),
+ ('masterdir', {'help': 'Output directory for masters'}, {}),
+ ('--nofixes', {'help': 'Bypass code fixing data', 'action': 'store_true', 'default': False}, {}),
+ ('--nofea', {'help': "Don't output features.fea", 'action': 'store_true', 'default': False}, {}),
+ ('--preservefea', {'help': "Retain the original features.fea in the UFO", 'action': 'store_true', 'default': False}, {}),
+ ('-l', '--log', {'help': 'Log file'}, {'type': 'outfile', 'def': '_glyphs2ufo.log'}),
+ ('-r', '--restore', {'help': 'List of extra keys to restore to fontinfo.plist or lib.plist'}, {})]
+
+def doit(args):
+ logger = args.logger
+ logger.log("Creating UFO objects from GlyphsApp file", "I")
+ with open(args.glyphsfont, 'r', encoding='utf-8') as gfile:
+ gfont = glyphsLib.parser.load(gfile)
+ ufos = glyphsLib.to_ufos(gfont, include_instances=False, family_name=None, propagate_anchors=False, generate_GDEF=False)
+
+ # Extract directory name for use with restores
+ (glyphsdir, filen) = os.path.split(args.glyphsfont)
+
+ keylists = {
+
+ "librestorekeys": ["org.sil.pysilfontparams", "org.sil.altLineMetrics", "org.sil.lcg.toneLetters",
+ "org.sil.lcg.transforms", "public.glyphOrder", "public.postscriptNames",
+ "com.schriftgestaltung.disablesLastChange", "com.schriftgestaltung.disablesAutomaticAlignment",
+ "public.skipExportGlyphs"],
+ "libdeletekeys": ("com.schriftgestaltung.customParameter.GSFont.copyright",
+ "com.schriftgestaltung.customParameter.GSFont.designer",
+ "com.schriftgestaltung.customParameter.GSFont.manufacturer",
+ "com.schriftgestaltung.customParameter.GSFont.note",
+ "com.schriftgestaltung.customParameter.GSFont.Axes",
+ "com.schriftgestaltung.customParameter.GSFont.Axis Mappings",
+ "com.schriftgestaltung.customParameter.GSFontMaster.Master Name"),
+ "libdeleteempty": ("com.schriftgestaltung.DisplayStrings",),
+ "inforestorekeys": ["openTypeHeadCreated", "openTypeNamePreferredFamilyName", "openTypeNamePreferredSubfamilyName",
+ "openTypeNameUniqueID", "openTypeOS2WeightClass", "openTypeOS2WidthClass", "postscriptFontName",
+ "postscriptFullName", "styleMapFamilyName", "styleMapStyleName", "note",
+ "woffMetadataCredits", "woffMetadataDescription"],
+ "integerkeys": ("openTypeOS2WeightClass", "openTypeOS2WidthClass"),
+ "infodeletekeys": ("openTypeVheaVertTypoAscender", "openTypeVheaVertTypoDescender", "openTypeVheaVertTypoLineGap"),
+ # "infodeleteempty": ("openTypeOS2Selection",)
+ }
+
+ if args.restore: # Extra keys to restore. Add to both lists, since should never be duplicated names
+ keylist = args.restore.split(",")
+ keylists["librestorekeys"] += keylist
+ keylists["inforestorekeys"].append(keylist)
+
+ loglists = []
+ obskeysfound={}
+ for ufo in ufos:
+ loglists.append(process_ufo(ufo, keylists, glyphsdir, args, obskeysfound))
+ for loglist in loglists:
+ for logitem in loglist: logger.log(logitem[0], logitem[1])
+ if obskeysfound:
+ logmess = "The following obsolete keys were found. They may have been in the original UFO or you may have an old version of glyphsLib installed\n"
+ for fontname in obskeysfound:
+ keys = obskeysfound[fontname]
+ logmess += " " + fontname + ": "
+ for key in keys:
+ logmess += key + ", "
+ logmess += "\n"
+ logger.log(logmess, "E")
+
+def process_ufo(ufo, keylists, glyphsdir, args, obskeysfound):
+ loglist=[]
+# sn = ufo.info.styleName # )
+# sn = sn.replace("Italic Italic", "Italic") # ) Temp fixes due to glyphLib incorrectly
+# sn = sn.replace("Italic Bold Italic", "Bold Italic") # ) forming styleName
+# sn = sn.replace("Extra Italic Light Italic", "Extra Light Italic") # )
+# ufo.info.styleName = sn # )
+ fontname = ufo.info.familyName.replace(" ", "") + "-" + ufo.info.styleName.replace(" ", "")
+
+ # Fixes to the data
+ if not args.nofixes:
+ loglist.append(("Fixing data in " + fontname, "P"))
+ # lib.plist processing
+ loglist.append(("Checking lib.plist", "P"))
+
+ # Restore values from original UFOs, assuming named as <fontname>.ufo in same directory as input .gylphs file
+
+ ufodir = os.path.join(glyphsdir, fontname + ".ufo")
+ try:
+ origlibplist = silfont.ufo.Uplist(font=None, dirn=ufodir, filen="lib.plist")
+ except Exception as e:
+ loglist.append(("Unable to open lib.plist in " + ufodir + "; values will not be restored", "E"))
+ origlibplist = None
+
+ if origlibplist is not None:
+
+ for key in keylists["librestorekeys"]:
+ current = None if key not in ufo.lib else ufo.lib[key]
+ if key in origlibplist:
+ new = origlibplist.getval(key)
+ if current == new:
+ continue
+ else:
+ ufo.lib[key] = new
+ logchange(loglist, " restored from backup ufo. ", key, current, new)
+ elif current:
+ ufo.lib[key] = None
+ logchange(loglist, " removed since not in backup ufo. ", key, current, None)
+
+ # Delete unneeded keys
+
+ for key in keylists["libdeletekeys"]:
+ if key in ufo.lib:
+ current = ufo.lib[key]
+ del ufo.lib[key]
+ logchange(loglist, " deleted. ", key, current, None)
+
+ for key in keylists["libdeleteempty"]:
+ if key in ufo.lib and (ufo.lib[key] == "" or ufo.lib[key] == []):
+ current = ufo.lib[key]
+ del ufo.lib[key]
+ logchange(loglist, " empty field deleted. ", key, current, None)
+
+ # Check for obsolete keys
+ for key in obsoleteLibKeys:
+ if key in ufo.lib:
+ if fontname not in obskeysfound: obskeysfound[fontname] = []
+ obskeysfound[fontname].append(key)
+
+ # Special processing for Axis Mappings
+ #key = "com.schriftgestaltung.customParameter.GSFont.Axis Mappings"
+ #if key in ufo.lib:
+ # current =ufo.lib[key]
+ # new = dict(current)
+ # for x in current:
+ # val = current[x]
+ # k = list(val.keys())[0]
+ # if k[-2:] == ".0": new[x] = {k[0:-2]: val[k]}
+ # if current != new:
+ # ufo.lib[key] = new
+ # logchange(loglist, " key names set to integers. ", key, current, new)
+
+ # Special processing for ufo2ft filters
+ key = "com.github.googlei18n.ufo2ft.filters"
+ if key in ufo.lib:
+ current = ufo.lib[key]
+ new = list(current)
+ for x in current:
+ if x["name"] == "eraseOpenCorners":
+ new.remove(x)
+
+ if current != new:
+ if new == []:
+ del ufo.lib[key]
+ else:
+ ufo.lib[key] = new
+ logchange(loglist, " eraseOpenCorners filter removed ", key, current, new)
+
+ # fontinfo.plist processing
+
+ loglist.append(("Checking fontinfo.plist", "P"))
+
+ try:
+ origfontinfo = silfont.ufo.Uplist(font=None, dirn=ufodir, filen="fontinfo.plist")
+ except Exception as e:
+ loglist.append(("Unable to open fontinfo.plist in " + ufodir + "; values will not be restored", "E"))
+ origfontinfo = None
+
+ if origfontinfo is not None:
+ for key in keylists["inforestorekeys"]:
+ current = None if not hasattr(ufo.info, key) else getattr(ufo.info, key)
+ if key in origfontinfo:
+ new = origfontinfo.getval(key)
+ if key in keylists["integerkeys"]: new = int(new)
+ if current == new:
+ continue
+ else:
+ setattr(ufo.info, key, new)
+ logchange(loglist, " restored from backup ufo. ", key, current, new)
+ elif current:
+ setattr(ufo.info, key, None)
+ logchange(loglist, " removed since not in backup ufo. ", key, current, None)
+
+ if getattr(ufo.info, "italicAngle") == 0: # Remove italicAngle if 0
+ setattr(ufo.info, "italicAngle", None)
+ logchange(loglist, " removed", "italicAngle", 0, None)
+
+ # Delete unneeded keys
+
+ for key in keylists["infodeletekeys"]:
+ if hasattr(ufo.info, key):
+ current = getattr(ufo.info, key)
+ setattr(ufo.info, key, None)
+ logchange(loglist, " deleted. ", key, current, None)
+
+# for key in keylists["infodeleteempty"]:
+# if hasattr(ufo.info, key) and getattr(ufo.info, key) == "":
+# setattr(ufo.info, key, None)
+# logchange(loglist, " empty field deleted. ", key, current, None)
+ if args.nofea or args.preservefea: ufo.features.text = "" # Suppress output of features.fea
+
+ for layer in ufo.layers:
+ for glyph in layer:
+ lib = glyph.lib
+ if "public.verticalOrigin" in lib: del lib["public.verticalOrigin"]
+
+ # Write ufo out
+ ufopath = os.path.join(args.masterdir, fontname + ".ufo")
+ if args.preservefea: # Move features.fea out of the ufo so that it can be restored afterward
+ origfea = os.path.join(ufopath, "features.fea")
+ hiddenfea = os.path.join(args.masterdir, fontname + "features.tmp")
+ if os.path.exists(origfea):
+ loglist.append((f'Renaming {origfea} to {hiddenfea}', "I"))
+ os.rename(origfea, hiddenfea)
+ else:
+ loglist.append((f"{origfea} does not exists so can't be restored", "E"))
+ origfea = None
+ loglist.append(("Writing out " + ufopath, "P"))
+ if os.path.exists(ufopath): shutil.rmtree(ufopath)
+ ufo.save(ufopath)
+ if args.preservefea and origfea:
+ loglist.append((f'Renaming {hiddenfea} back to {origfea}', "I"))
+ os.rename(hiddenfea, origfea)
+
+ # Now correct the newly-written fontinfo.plist with changes that can't be made via glyphsLib
+ if not args.nofixes:
+ fontinfo = silfont.ufo.Uplist(font=None, dirn=ufopath, filen="fontinfo.plist")
+ changes = False
+ for key in ("guidelines", "postscriptBlueValues", "postscriptFamilyBlues", "postscriptFamilyOtherBlues",
+ "postscriptOtherBlues"):
+ if key in fontinfo and fontinfo.getval(key) == []:
+ fontinfo.remove(key)
+ changes = True
+ logchange(loglist, " empty list deleted", key, None, [])
+ if changes:
+ # Create outparams. Just need any valid values, since font will need normalizing later
+ params = args.paramsobj
+ paramset = params.sets["main"]
+ outparams = {"attribOrders": {}}
+ for parn in params.classes["outparams"]: outparams[parn] = paramset[parn]
+ loglist.append(("Writing updated fontinfo.plist", "I"))
+ silfont.ufo.writeXMLobject(fontinfo, params=outparams, dirn=ufopath, filen="fontinfo.plist", exists=True,
+ fobject=True)
+ return loglist
+
+def logchange(loglist, logmess, key, old, new):
+ oldstr = str(old) if len(str(old)) < 22 else str(old)[0:20] + "..."
+ newstr = str(new) if len(str(new)) < 22 else str(new)[0:20] + "..."
+ logmess = key + logmess
+ if old is None:
+ logmess = logmess + " New value: " + newstr
+ else:
+ if new is None:
+ logmess = logmess + " Old value: " + oldstr
+ else:
+ logmess = logmess + " Old value: " + oldstr + ", new value: " + newstr
+ loglist.append((logmess, "I"))
+ # Extra verbose logging
+ if len(str(old)) > 21 :
+ loglist.append(("Full old value: " + str(old), "V"))
+ if len(str(new)) > 21 :
+ loglist.append(("Full new value: " + str(new), "V"))
+ loglist.append(("Types: Old - " + str(type(old)) + ", New - " + str(type(new)), "V"))
+
+def cmd(): execute(None, doit, argspec)
+if __name__ == "__main__": cmd()