diff options
Diffstat (limited to 'src/silfont/scripts/psfcopymeta.py')
-rw-r--r-- | src/silfont/scripts/psfcopymeta.py | 148 |
1 files changed, 148 insertions, 0 deletions
diff --git a/src/silfont/scripts/psfcopymeta.py b/src/silfont/scripts/psfcopymeta.py new file mode 100644 index 0000000..8b67505 --- /dev/null +++ b/src/silfont/scripts/psfcopymeta.py @@ -0,0 +1,148 @@ +#!/usr/bin/env python3 +__doc__ = '''Copy metadata between fonts in different (related) families +Usually run against the master (regular) font in each family then data synced within family afterwards''' +__url__ = 'https://github.com/silnrsi/pysilfont' +__copyright__ = 'Copyright (c) 2017 SIL International (https://www.sil.org)' +__license__ = 'Released under the MIT License (https://opensource.org/licenses/MIT)' +__author__ = 'David Raymond' + +from silfont.core import execute +import silfont.ufo as UFO +from xml.etree import ElementTree as ET + +argspec = [ + ('fromfont',{'help': 'From font file'}, {'type': 'infont'}), + ('tofont',{'help': 'To font file'}, {'type': 'infont'}), + ('-l','--log',{'help': 'Log file'}, {'type': 'outfile', 'def': '_copymeta.log'}), + ('-r','--reportonly', {'help': 'Report issues but no updating', 'action': 'store_true', 'default': False},{}) + ] + +def doit(args) : + + fields = ["copyright", "openTypeNameDescription", "openTypeNameDesigner", "openTypeNameDesignerURL", "openTypeNameLicense", # General feilds + "openTypeNameLicenseURL", "openTypeNameManufacturer", "openTypeNameManufacturerURL", "openTypeOS2CodePageRanges", + "openTypeOS2UnicodeRanges", "openTypeOS2VendorID", "trademark", + "openTypeNameVersion", "versionMajor", "versionMinor", # Version fields + "ascender", "descender", "openTypeHheaAscender", "openTypeHheaDescender", "openTypeHheaLineGap", # Design fields + "openTypeOS2TypoAscender", "openTypeOS2TypoDescender", "openTypeOS2TypoLineGap", "openTypeOS2WinAscent", "openTypeOS2WinDescent"] + libfields = ["public.postscriptNames", "public.glyphOrder", "com.schriftgestaltung.glyphOrder"] + + fromfont = args.fromfont + tofont = args.tofont + logger = args.logger + reportonly = args.reportonly + + updatemessage = " to be updated: " if reportonly else " updated: " + precision = fromfont.paramset["precision"] + # Increase screen logging level to W unless specific level supplied on command-line + if not(args.quiet or "scrlevel" in args.paramsobj.sets["command line"]) : logger.scrlevel = "W" + + # Process fontinfo.plist + ffi = fromfont.fontinfo + tfi = tofont.fontinfo + fupdated = False + for field in fields: + if field in ffi : + felem = ffi[field][1] + ftag = felem.tag + ftext = felem.text + if ftag == 'real' : ftext = processnum(ftext,precision) + message = field + updatemessage + + if field in tfi : # Need to compare values to see if update is needed + telem = tfi[field][1] + ttag = telem.tag + ttext = telem.text + if ttag == 'real' : ttext = processnum(ttext,precision) + + if ftag in ("real", "integer", "string") : + if ftext != ttext : + if field == "openTypeNameLicense" : # Too long to display all + addmess = " Old: '" + ttext[0:80] + "...' New: '" + ftext[0:80] + "...'" + else: addmess = " Old: '" + ttext + "' New: '" + str(ftext) + "'" + telem.text = ftext + logger.log(message + addmess, "W") + fupdated = True + elif ftag in ("true, false") : + if ftag != ttag : + fti.setelem(field, ET.fromstring("<" + ftag + "/>")) + logger.log(message + " Old: '" + ttag + "' New: '" + str(ftag) + "'", "W") + fupdated = True + elif ftag == "array" : # Assume simple array with just values to compare + farray = [] + for subelem in felem : farray.append(subelem.text) + tarray = [] + for subelem in telem : tarray.append(subelem.text) + if farray != tarray : + tfi.setelem(field, ET.fromstring(ET.tostring(felem))) + logger.log(message + "Some values different Old: " + str(tarray) + " New: " + str(farray), "W") + fupdated = True + else : logger.log("Non-standard fontinfo field type: "+ ftag + " in " + fontname, "S") + else : + tfi.addelem(field, ET.fromstring(ET.tostring(felem))) + logger.log(message + "is missing from destination font so will be copied from source font", "W") + fupdated = True + else: # Field not in from font + if field in tfi : + logger.log( field + " is missing from source font but present in destination font", "E") + else : + logger.log( field + " is in neither font", "W") + + # Process lib.plist - currently just public.postscriptNames and glyph order fields which are all simple dicts or arrays + flib = fromfont.lib + tlib = tofont.lib + lupdated = False + for field in libfields: + action = None + if field in flib: + if field in tlib: # Need to compare values to see if update is needed + if flib.getval(field) != tlib.getval(field): + action = "Updatefield" + else: + action = "Copyfield" + else: + action = "Error" if field == ("public.GlyphOrder", "public.postscriptNames") else "Warn" + issue = field + " not in source font lib.plist" + + # Process the actions, create log messages etc + if action is None or action == "Ignore": + pass + elif action == "Warn": + logger.log(field + " needs manual correction: " + issue, "W") + elif action == "Error": + logger.log(field + " needs manual correction: " + issue, "E") + elif action in ("Updatefield", "Copyfield"): # Updating actions + lupdated = True + message = field + updatemessage + if action == "Copyfield": + message = message + "is missing so will be copied from source font" + tlib.addelem(field, ET.fromstring(ET.tostring(flib[field][1]))) + elif action == "Updatefield": + message = message + "Some values different" + tlib.setelem(field, ET.fromstring(ET.tostring(flib[field][1]))) + logger.log(message, "W") + else: + logger.log("Uncoded action: " + action + " - oops", "X") + + # Now update on disk + if not reportonly: + if fupdated: + logger.log("Writing updated fontinfo.plist", "P") + UFO.writeXMLobject(tfi, tofont.outparams, tofont.ufodir, "fontinfo.plist", True, fobject=True) + if lupdated: + logger.log("Writing updated lib.plist", "P") + UFO.writeXMLobject(tlib, tofont.outparams, tofont.ufodir, "lib.plist", True, fobject=True) + + return + + +def processnum(text, precision) : # Apply same processing to real numbers that normalization will + if precision is not None: + val = round(float(text), precision) + if val == int(val) : val = int(val) # Removed trailing decimal .0 + text = str(val) + return text + + +def cmd(): execute("UFO",doit, argspec) +if __name__ == "__main__": cmd() |