#!/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()