Skip to content

Instantly share code, notes, and snippets.

@arrowtype
Last active August 26, 2021 15:37
Show Gist options
  • Save arrowtype/315a1016f6fd4fad2d9c883fbdbf1c78 to your computer and use it in GitHub Desktop.
Save arrowtype/315a1016f6fd4fad2d9c883fbdbf1c78 to your computer and use it in GitHub Desktop.
RoboFont script to find unexpected kerning exceptions in a UFO, then remove those kerns and replace them with kerns using groups.
"""
Find kerning exceptions in a UFO, then remove those kerns and replace them with kerns using groups.
Why?
Sometimes, if you add kerning to a font early, you can wind up with unexpected kerning exceptions
when you add new characters to the font. E.g. ('G', 'asterisk') makes sense until you add 'Gbreve'.
USAGE:
Configure side1_ignore & side2_ignore lists below, then run this script in RoboFont.
- It won’t autosave your UFOs, because you should probably review the results before saving.
- Better yet, you should use Git to version your files, so you can see exactly what changes
before sticking with these batch edits.
DISCLAIMER:
This code is messy and written to solve an immediate problem for a few UFOs. It could be further improved.
"""
from vanilla.dialogs import *
from mojo.UI import OutputWindow
# ---------------------------------------------
# glyph names to ignore on side 1 and side 2 of kerns (e.g. because T.ultra has intentional exceptions)
side1_ignore = "T T.ultra tcaron".split(" ")
side2_ignore = "T T.ultra Oslash".split(" ")
# ---------------------------------------------
# prompt user to open UFOs in RoboFont
files = getFile("Select files to edit",
allowsMultipleSelection=True, fileTypes=["ufo"])
OutputWindow().clear()
output = ""
for filepath in files:
font = OpenFont(filepath)
output += "\n"
output += "✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨\n"
output += "✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨\n"
output += "✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨\n"
output += "\n"
output += f"{font.info.familyName} {font.info.styleName}\n"
double_exceptions = []
side1_exceptions = []
side2_exceptions = []
for kern in font.kerning:
if "kern1" not in kern[0] and "kern2" not in kern[1]:
if "kern1" in " ".join(font.groups.findGlyph(kern[0])) and "kern2" in " ".join(font.groups.findGlyph(kern[1])):
double_exceptions.append(kern)
# move on to next kern to avoid putting into side1 / side2 lists
continue
# if side 1 is not a group
if "kern1" not in kern[0]:
# check if it is in a side 1 group
if "kern1" in " ".join(font.groups.findGlyph(kern[0])):
side1_exceptions.append(kern)
# if side 2 is not a group
if "kern2" not in kern[1]:
# check if it is in a side 2 group
if "kern2" in " ".join(font.groups.findGlyph(kern[1])):
side2_exceptions.append(kern)
## uncomment to see just lists of kerns with exceptions
# output += "\n\nℹ️ SIDE 1+2 EXCEPTIONS\n"
# output += "\n".join([" ".join(list(kern)) for kern in double_exceptions]) + "\n"
# output += "\n\nℹ️ SIDE 1 EXCEPTIONS\n"
# output += "\n".join([" ".join(list(kern)) for kern in side1_exceptions]) + "\n"
# output += "\n\nℹ️ SIDE 2 EXCEPTIONS\n"
# output += "\n".join([" ".join(list(kern)) for kern in side2_exceptions]) + "\n"
output += "\n"
output += "\n"
output += "----------------------------------------------------------------------------\n"
output += "SIDE 1 + 2 EXCEPTIONS REMOVED\n"
output += "\n"
for pair in double_exceptions:
# if side not in "ignore" list
if pair[0] not in side1_ignore and pair[1] not in side2_ignore:
# get kern value and remove pair
value = font.kerning.pop(pair)
# make kern with the group instead
newPair = tuple([font.groups.findGlyph(pair[0])[0], font.groups.findGlyph(pair[1])[-1]])
font.kerning[newPair] = value
oldPair, newPair = " | ".join(list(pair)), " | ".join(list(newPair))
output += f"{oldPair.rjust(40)} → {newPair.ljust(50,' ')}: {value}\n\n"
output += "\n"
output += "\n"
output += "----------------------------------------------------------------------------\n"
output += "SIDE 1 EXCEPTIONS REMOVED\n"
output += "\n"
for pair in side1_exceptions:
# if side not in "ignore" list
if pair[0] not in side1_ignore and pair[1] not in side2_ignore:
# get kern value and remove pair
value = font.kerning.pop(pair)
# make kern with the group instead
newPair = tuple([font.groups.findGlyph(pair[0])[0], pair[1]])
font.kerning[newPair] = value
oldPair, newPair = " | ".join(list(pair)), " | ".join(list(newPair))
output += f"{oldPair.rjust(40)} → {newPair.ljust(50,' ')}: {value}\n\n"
output += "\n"
output += "\n"
output += "----------------------------------------------------------------------------\n"
output += "SIDE 2 EXCEPTIONS REMOVED\n"
output += "\n"
for pair in side2_exceptions:
# if side not in "ignore" list
if pair[0] not in side1_ignore and pair[1] not in side2_ignore:
# get kern value and remove pair
value = font.kerning.pop(pair)
# make kern with the group instead
newPair = tuple([pair[0], font.groups.findGlyph(pair[1])[-1]])
font.kerning[newPair] = value
oldPair, newPair = " | ".join(list(pair)), " | ".join(list(newPair))
output += f"{oldPair.rjust(40)} → {newPair.ljust(50)}: {value}\n\n"
## uncomment to automatically save and close, but only do this if you are confident in the results
# font.save()
# font.close()
print(output)
OutputWindow().show()
@arrowtype
Copy link
Author

arrowtype commented Aug 26, 2021

This will output text to show what has changed, like:

✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨
✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨
✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨

Name Sans Text Ultra

----------------------------------------------------------------------------
SIDE 1 + 2 EXCEPTIONS REMOVED

                              L | hyphen   →   public.kern1.L | public.kern2.hyphen              : 0

                                   L | l   →   public.kern1.L | public.kern2.l               : -20

                                   L | t   →   public.kern1.L | public.kern2.t                   : -70

[etc]

----------------------------------------------------------------------------
SIDE 1 EXCEPTIONS REMOVED

                            G | asterisk   →   public.kern1.G | asterisk                         : -30

                           G | backslash   →   public.kern1.G | backslash                        : -95

                             G | percent   →   public.kern1.G | percent                          : -20

[etc]

----------------------------------------------------------------------------
SIDE 2 EXCEPTIONS REMOVED

                                   F | p   →   F | public.kern2.n                                : -10

                                   F | s   →   F | public.kern2.s                                : -105

                                   F | t   →   F | public.kern2.t                                : -10

[etc]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment