diff options
Diffstat (limited to '')
-rw-r--r-- | src/silfont/scripts/psfmakewoffmetadata.py | 225 |
1 files changed, 225 insertions, 0 deletions
diff --git a/src/silfont/scripts/psfmakewoffmetadata.py b/src/silfont/scripts/psfmakewoffmetadata.py new file mode 100644 index 0000000..c6feb45 --- /dev/null +++ b/src/silfont/scripts/psfmakewoffmetadata.py @@ -0,0 +1,225 @@ +#!/usr/bin/env python3 +__doc__ = 'Make the WOFF metadata xml file based on input UFO (and optionally FONTLOG.txt)' +__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 + +import re, os, datetime +from xml.etree import ElementTree as ET + +argspec = [ + ('font', {'help': 'Source font file'}, {'type': 'infont'}), + ('-n', '--primaryname', {'help': 'Primary Font Name', 'required': True}, {}), + ('-i', '--orgid', {'help': 'orgId', 'required': True}, {}), + ('-f', '--fontlog', {'help': 'FONTLOG.txt file', 'default': 'FONTLOG.txt'}, {'type': 'filename'}), + ('-o', '--output', {'help': 'Override output file'}, {'type': 'filename', 'def': None}), + ('--populateufowoff', {'help': 'Copy info from FONTLOG.txt to UFO', 'action': 'store_true', 'default': False},{}), + ('-l', '--log', {'help': 'Log file'}, {'type': 'outfile', 'def': '_makewoff.log'})] + +def doit(args): + font = args.font + pfn = args.primaryname + orgid = args.orgid + logger = args.logger + ofn = args.output + + # Find & process info required in the UFO + + fi = font.fontinfo + + ufofields = {} + missing = None + for field in ("versionMajor", "versionMinor", "openTypeNameManufacturer", "openTypeNameManufacturerURL", + "openTypeNameLicense", "copyright", "trademark"): + if field in fi: + ufofields[field] = fi[field][1].text + elif field != 'trademark': # trademark is no longer required + missing = field if missing is None else missing + ", " + field + if missing is not None: logger.log("Field(s) missing from fontinfo.plist: " + missing, "S") + + version = ufofields["versionMajor"] + "." + ufofields["versionMinor"].zfill(3) + + # Find & process WOFF fields if present in the UFO + + missing = None + ufofields["woffMetadataDescriptionurl"] = None + ufowoff = {"woffMetadataCredits": "credits", "woffMetadataDescription": "text"} # Field, dict name + for field in ufowoff: + fival = fi.getval(field) if field in fi else None + if fival is None: + missing = field if missing is None else missing + ", " + field + ufofields[field] = None + else: + ufofields[field] = fival[ufowoff[field]] + if field == "woffMetadataDescription" and "url" in fival: + ufofields["woffMetadataDescriptionurl"] = fival["url"] + + # Process --populateufowoff setting, if present + if args.populateufowoff: + if missing != "woffMetadataCredits, woffMetadataDescription": + logger.log("Data exists in the UFO for woffMetadata - remove manually to reuse --populateufowoff", "S") + + if args.populateufowoff or missing is not None: + if missing: logger.log("WOFF field(s) missing from fontinfo.plist will be generated from FONTLOG.txt: " + missing, "W") + # Open the fontlog file + try: + fontlog = open(args.fontlog) + except Exception as e: + logger.log(f"Unable to open {args.fontlog}: {str(e)}", "S") + # Parse the fontlog file + (section, match) = readuntil(fontlog, ("Basic Font Information",)) # Skip until start of "Basic Font Information" section + if match is None: logger.log("No 'Basic Font Information' section in fontlog", "S") + (fldescription, match) = readuntil(fontlog, ("Information for C", "Acknowledgements")) # Description ends when first of these sections is found + fldescription = [{"text": fldescription}] + if match == "Information for C": (section, match) = readuntil(fontlog, ("Acknowledgements",)) # If Info... section present then skip on to Acknowledgements + if match is None: logger.log("No 'Acknowledgements' section in fontlog", "S") + (acksection, match) = readuntil(fontlog, ("No match needed!!",)) + + flcredits = [] + credit = {} + acktype = "" + flog2woff = {"N": "name", "E": "Not used", "W": "url", "D": "role"} + for line in acksection.splitlines(): + if line == "": + if acktype != "": # Must be at the end of a credit section + if "name" in credit: + flcredits.append(credit) + else: + logger.log("Credit section found with no N: entry", "E") + credit = {} + acktype = "" + else: + match = re.match("^([NEWD]): (.*)", line) + if match is None: + if acktype == "N": credit["name"] = credit["name"] + line # Name entries can be multiple lines + else: + acktype = match.group(1) + if acktype in credit: + logger.log("Multiple " + acktype + " entries found in a credit section", "E") + else: + credit[flog2woff[acktype]] = match.group(2) + if flcredits == []: logger.log("No credits found in fontlog", "S") + if args.populateufowoff: + ufofields["woffMetadataDescription"] = fldescription # Force fontlog values to be used writing metadata.xml later + ufofields["woffMetadataCredits"] = flcredits + # Create xml strings and update fontinfo + xmlstring = "<dict>" + \ + "<key>text</key><array><dict>" + \ + "<key>text</key><string>" + textprotect(fldescription[0]["text"]) + "</string>" + \ + "</dict></array>" + \ + "<key>url</key><string>https://software.sil.org/</string>"\ + "</dict>" + fi.setelem("woffMetadataDescription", ET.fromstring(xmlstring)) + + xmlstring = "<dict><key>credits</key><array>" + for credit in flcredits: + xmlstring += '<dict><key>name</key><string>' + textprotect(credit["name"]) + '</string>' + if "url" in credit: xmlstring += '<key>url</key><string>' + textprotect(credit["url"]) + '</string>' + if "role" in credit: xmlstring += '<key>role</key><string>' + textprotect(credit["role"]) + '</string>' + xmlstring += '</dict>' + xmlstring += '</array></dict>' + fi.setelem("woffMetadataCredits", ET.fromstring(xmlstring)) + + fi.setval("openTypeHeadCreated", "string", datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S")) + logger.log("Writing updated fontinfo.plist with values from FONTLOG.txt", "P") + exists = True if os.path.isfile(os.path.join(font.ufodir, "fontinfo.plist")) else False + UFO.writeXMLobject(fi, font.outparams, font.ufodir, "fontinfo.plist", exists, fobject=True) + + description = ufofields["woffMetadataDescription"] + if description == None: description = fldescription + credits = ufofields["woffMetadataCredits"] + if credits == None : credits = flcredits + + # Construct output file name + (folder, ufoname) = os.path.split(font.ufodir) + filename = os.path.join(folder, pfn + "-WOFF-metadata.xml") if ofn is None else ofn + try: + file = open(filename, "w") + except Exception as e: + logger.log("Unable to open " + filename + " for writing:\n" + str(e), "S") + logger.log("Writing to : " + filename, "P") + + file.write('<?xml version="1.0" encoding="UTF-8"?>\n') + file.write('<metadata version="1.0">\n') + file.write(' <uniqueid id="' + orgid + '.' + pfn + '.' + version + '" />\n') + file.write(' <vendor name="' + attrprotect(ufofields["openTypeNameManufacturer"]) + '" url="' + + attrprotect(ufofields["openTypeNameManufacturerURL"]) + '" />\n') + file.write(' <credits>\n') + for credit in credits: + file.write(' <credit\n') + file.write(' name="' + attrprotect(credit["name"]) + '"\n') + if "url" in credit: file.write(' url="' + attrprotect(credit["url"]) + '"\n') + if "role" in credit: file.write(' role="' + attrprotect(credit["role"]) + '"\n') + file.write(' />\n') + file.write(' </credits>\n') + + if ufofields["woffMetadataDescriptionurl"]: + file.write(f' <description url="{ufofields["woffMetadataDescriptionurl"]}">\n') + else: + file.write(' <description>\n') + file.write(' <text lang="en">\n') + for entry in description: + for line in entry["text"].splitlines(): + file.write(' ' + textprotect(line) + '\n') + file.write(' </text>\n') + file.write(' </description>\n') + + file.write(' <license url="https://scripts.sil.org/OFL" id="org.sil.ofl.1.1">\n') + file.write(' <text lang="en">\n') + for line in ufofields["openTypeNameLicense"].splitlines(): file.write(' ' + textprotect(line) + '\n') + file.write(' </text>\n') + file.write(' </license>\n') + + file.write(' <copyright>\n') + file.write(' <text lang="en">\n') + for line in ufofields["copyright"].splitlines(): file.write(' ' + textprotect(line) + '\n') + file.write(' </text>\n') + file.write(' </copyright>\n') + + if 'trademark' in ufofields: + file.write(' <trademark>\n') + file.write(' <text lang="en">' + textprotect(ufofields["trademark"]) + '</text>\n') + file.write(' </trademark>\n') + + file.write('</metadata>') + + file.close() + +def readuntil(file, texts): # Read through file until line is in texts. Return section up to there and the text matched + skip = True + match = None + for line in file: + line = line.strip() + if skip: # Skip underlines and blank lines at start of section + if line == "" or line[0:5] == "-----": + pass + else: + section = line + skip = False + else: + for text in texts: + if line[0:len(text)] == text: match = text + if match: break + section = section + "\n" + line + while section[-1] == "\n": section = section[:-1] # Strip blank lines at end + return (section, match) + +def textprotect(txt): # Switch special characters in text to use &...; format + txt = re.sub(r'&', '&', txt) + txt = re.sub(r'<', '<', txt) + txt = re.sub(r'>', '>', txt) + return txt + +def attrprotect(txt): # Switch special characters in text to use &...; format + txt = re.sub(r'&', '&', txt) + txt = re.sub(r'<', '<', txt) + txt = re.sub(r'>', '>', txt) + txt = re.sub(r'"', '"', txt) + return txt + +def cmd(): execute("UFO", doit, argspec) +if __name__ == "__main__": cmd() |