diff options
Diffstat (limited to 'examples/psftidyfontlabufo.py')
-rwxr-xr-x | examples/psftidyfontlabufo.py | 140 |
1 files changed, 140 insertions, 0 deletions
diff --git a/examples/psftidyfontlabufo.py b/examples/psftidyfontlabufo.py new file mode 100755 index 0000000..d69f16a --- /dev/null +++ b/examples/psftidyfontlabufo.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python3 +__doc__ = '''Make changes to a backup UFO to match some changes made to another UFO by FontLab +When a UFO is first round-tripped through Fontlab 7, many changes are made including adding 'smooth="yes"' to many points +in glifs and removing it from others. Also if components are after contours in a glif, then they get moved to before them. +These changes make initial comparisons hard and can mask other changes. +This script takes the backup of the original font that Fontlab made and writes out a new version with contours changed +to match those in the round-tripped UFO so a diff can then be done to look for other differences. +A glif is only changed if there are no other changes to contours. +If also moves components to match. +''' +__url__ = 'https://github.com/silnrsi/pysilfont' +__copyright__ = 'Copyright (c) 2021 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, splitfn +from xml.etree import ElementTree as ET +from silfont.ufo import Ufont +import os, glob +from difflib import ndiff + +argspec = [ + ('ifont',{'help': 'post-fontlab ufo'}, {'type': 'infont'}), + ('-l','--log',{'help': 'Log file'}, {'type': 'outfile', 'def': '_tidyfontlab.log'})] + +def doit(args) : + + flfont = args.ifont + logger = args.logger + params = args.paramsobj + fontname = args.ifont.ufodir + + # Locate the oldest backup + (path, base, ext) = splitfn(fontname) + backuppath = os.path.join(path, base + ".*-*" + ext) # Backup has date/time added in format .yymmdd-hhmm + backups = glob.glob(backuppath) + if len(backups) == 0: + logger.log("No backups found matching %s so aborting..." % backuppath, "P") + return + backupname = sorted(backups)[0] # Choose the oldest backup - date/time format sorts alphabetically + logger.log(f"Opening backup font {backupname}", "P") + bfont = Ufont(backupname, params=params) + outufoname = os.path.join(path, base + ".tidied.ufo") + + fllayers = {} # Dictionary of flfont layers by layer name + for layer in flfont.layers: fllayers[layer.layername] = layer + + for layer in bfont.layers: + if layer.layername not in fllayers: + logger.log(f"layer {layer.layername} missing", "E") + continue + fllayer = fllayers[layer.layername] + glifchangecount = 0 + smoothchangecount = 0 + duplicatenodecount = 0 + compchangecount = 0 + for gname in layer: + glif = layer[gname] + glifchange = False + flglif = fllayer[gname] + if "outline" in glif and "outline" in flglif: + changestomake = [] + otherchange = False + outline = glif["outline"] + floutline = flglif["outline"] + contours = outline.contours + if len(contours) != len(floutline.contours): break # Different number so can't all be identical! + flcontours = iter(floutline.contours) + for contour in contours: + flc = next(flcontours) + points = contour["point"] + flpoints = flc["point"] + duplicatenode = False + smoothchanges = True + if len(points) != len(flpoints): # Contours must be different! + if len(flpoints) - len(points) == 1: # Look for duplicate node issue + (different, plus, minus) = sdiff(str(ET.tostring(points[0]).strip()), str(ET.tostring(flpoints[0]).strip())) + if ET.tostring(points[0]).strip() == ET.tostring(flpoints[-1]).strip(): # With duplicate node issue first point is appended to the end + if plus == "lin" and minus == "curv": # On first point curve changed to line. + duplicatenode = True # Also still need check all the remaining points are the same + break # but next check does that + otherchange = True # Duplicate node issue above is only case where contour count can be different + break + + firstpoint = True + for point in points: + flp = flpoints.pop(0) + if firstpoint and duplicatenode: # Ignore the first point since that will be different + firstpoint = False + continue + firstpoint = False + (different, plus, minus) = sdiff(str(ET.tostring(point).strip()), str(ET.tostring(flp).strip())) + if different: # points are different + if plus.strip() + minus.strip() == 'smooth="yes"': + smoothchanges = True # Only difference is addition or removal of smooth="yes" + else: # Other change to glif,so can't safely make changes + otherchange = True + + if (smoothchanges or duplicatenode) and not otherchange: # Only changes to contours in glif are known issues that should be reset + flcontours = iter(floutline.contours) + for contour in list(contours): + flcontour = next(flcontours) + outline.replaceobject(contour, flcontour, "contour") + if smoothchanges: + logger.log(f'Smooth changes made to {gname}', "I") + smoothchangecount += 1 + if duplicatenode: + logger.log(f'Duplicate node changes made to {gname}', "I") + duplicatenodecount += 1 + glifchange = True + + # Now need to move components to the front... + components = outline.components + if len(components) > 0 and len(contours) > 0 and list(outline)[0] == "contour": + oldcontours = list(contours) # Easiest way to 'move' components is to delete contours then append back at the end + for contour in oldcontours: outline.removeobject(contour, "contour") + for contour in oldcontours: outline.appendobject(contour, "contour") + logger.log(f'Component position changes made to {gname}', "I") + compchangecount += 1 + glifchange = True + if glifchange: glifchangecount += 1 + + logger.log(f'{layer.layername}: {glifchangecount} glifs changed', 'P') + logger.log(f'{layer.layername}: {smoothchangecount} changes due to smooth, {duplicatenodecount} due to duplicate nodes and {compchangecount} due to components position', "P") + + bfont.write(outufoname) + return + +def sdiff(before, after): # Returns strings with the differences between the supplited strings + if before == after: return(False,"","") # First returned value is True if the strings are different + diff = ndiff(before, after) + plus = "" # Plus will have the extra characters that are only in after + minus = "" # Minus will have the characters missing from after + for d in diff: + if d[0] == "+": plus += d[2] + if d[0] == "-": minus += d[2] + return(True, plus, minus) + +def cmd() : execute("UFO",doit, argspec) +if __name__ == "__main__": cmd() |