Skip to content

Instantly share code, notes, and snippets.

@arrowtype
Last active January 25, 2022 18:48
Show Gist options
  • Save arrowtype/98e98405d1575672f86fdce307c11b08 to your computer and use it in GitHub Desktop.
Save arrowtype/98e98405d1575672f86fdce307c11b08 to your computer and use it in GitHub Desktop.
Go through UFOs in directory and orient contour paths in a counter-clockwise direction. See https://github.com/googlefonts/fontmake/issues/846
"""
⚠️⚠️⚠️
OOPS. Don’t use this. FontParts already has BaseGlyph.correctDirection()
⚠️⚠️⚠️
Go through UFOs in directory and orient contour paths in a counter-clockwise direction,
as they should be for cubic/postscript/CFF/"OTF" curves.
DEPENDENCIES:
- fontParts
- ufonormalizer
USAGE:
On the command line, call this script and add a directory of UFOs as an argument.
Add "--normalize" to normalize the UFOs after saving.
python3 <path_to_script>/orient-glyph-contours-UFOs_in_dir.py <path_to_directory_of_UFOs> --normalize
DISCLAIMERS:
- May break compatibility on some glyphs, as start points can change. Consider using Prepolator, afterwards.
- May result in some broken drawings for certain edge cases (e.g. if a counter path
somehow goes further left than the main exterior path).
- Doesn't handle glyphs with more than two counters, unless they all have the same
path direction (i.e. no counters), but adds these to a list for manual review.
- May not work for your project. ¯\_(ツ)_/¯ ALWAYS USE GIT / VERSION CONTROL or another
form of backup before running any script like this!
LICENSE:
- MIT. Use/remix this if you want to.
"""
import argparse
import os
from fontParts.world import *
from ufonormalizer import normalizeUFO
def orientPathsCCW(glyph):
"""
Orient all paths in glyph counter-clockwise.
Only use this on glyphs with no counter/cutout shapes.
"""
for contour in glyph:
if contour.clockwise == True:
contour.reverse()
def main():
# get arguments from argparse
args = parser.parse_args()
sourceFolderPath = args.dir
# get UFO paths and open each of them
for subPath in os.listdir(sourceFolderPath):
if subPath.endswith(".ufo"):
ufoPath = os.path.join(sourceFolderPath, subPath)
f = OpenFont(ufoPath, showInterface=False)
print(f"Analyzing: {f.info.styleName}...")
# a list to track glyphs with many contours, for manual review
manyContours = []
# go through glyphs in the font
for g in f:
# if all contours have the same path direction, orient the paths CCW
if len(set([c.clockwise for c in g.contours])) == 1:
orientPathsCCW(g)
# else if contours have more than one direction
else:
# check for exactly two contours
if len(g.contours) == 2:
# find outer shape, then make it CCW
# if first contour is further left than second contour, assume it is exterior and orient first to CCW
# bounds is (xMin, yMin, xMax, yMax)
if g.contours[0].bounds[0] < g.contours[1].bounds[0]:
if g.contours[0].clockwise:
g.contours[0].reverse()
g.contours[1].reverse()
# if first contour is not further left, assume it is the counter, and make it CW
else:
if g.contours[0].clockwise == False:
g.contours[0].reverse()
g.contours[1].reverse()
# if too many contours, just add to a list for manual review/handling
if len(g.contours) == 3:
manyContours.append(g.name)
# report list for manual review
if len(manyContours) >= 1:
print("The following glyphs have more than two contours, and multiple path directions:")
print(" ".join(manyContours))
print()
f.save()
if args.normalize:
normalizeUFO(ufoPath)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Orient path directions in all glyphs for all UFOs in a directory.')
parser.add_argument('dir',
help='Relative path to a directory of one or more UFO font sources')
parser.add_argument("-n", "--normalize",
action='store_true',
help='Normalizes UFOs with ufoNormalizer.')
main()
"""
⚠️⚠️⚠️
OOPS. Don’t use this. FontParts already has BaseGlyph.correctDirection()
⚠️⚠️⚠️
Go through UFOs in directory and make all contours clockwise in specified glyphs.
Steps to usage:
1. First, use orient-glyph-contours-UFOs_in_dir.py to fix simpler glyphs and report complex glyphs.
2. Open one of the UFOs and manually check which of the complex glyphs have incorrect paths.
3. Add glyphs with backwards paths and counters to the "reverse" space-separated string, below.
4. Add glyphs with differing paths and no counters to the "coordinate" space-separated string, below.
5. Run the script on a directory of UFOs. Add "--normalize" arg to normalize UFOs.
DEPENDENCIES:
- fontParts
- ufonormalizer
DISCLAIMERS:
- May break compatibility on some glyphs, as start points might change. Consider using Prepolator, afterwards.
- May result in some broken drawings for certain edge cases (e.g. if a counter path
somehow goes further left than the main exterior path).
- May not work for your project. ¯\_(ツ)_/¯ ALWAYS USE GIT / VERSION CONTROL or another
form of backup before running any script like this!
LICENSE:
- MIT. Use/remix this if you want to.
"""
import argparse
import os
import collections
from fontParts.world import *
from ufonormalizer import normalizeUFO
# -----------------------------------------
# CONFIGURE GLYPHS BELOW
# Yes, these could instead be command line args, but this is easier. Feel free to modify if you want!
glyphsToReverse = "eth copyright"
glyphsToCoordinate = "divide"
# CONFIGURE GLYPHS ABOVE
# -----------------------------------------
def orientPathsCCW(glyph):
"""
Orient all paths in glyph counter-clockwise.
Only use this on glyphs with no counter/cutout shapes!
"""
for contour in glyph:
if contour.clockwise == True:
contour.reverse()
def main():
# get arguments from argparse
args = parser.parse_args()
sourceFolderPath = args.dir
for subPath in os.listdir(sourceFolderPath):
if subPath.endswith(".ufo"):
ufoPath = os.path.join(sourceFolderPath, subPath)
f = OpenFont(ufoPath, showInterface=False)
print(f"Fixing: {f.info.styleName}...")
try:
# go through list of glyphs to reverse
for name in glyphsToReverse.split(" "):
# make orderedDict of contours, by leftmost bound
contoursLeftBounds = {}
for i, c in enumerate(f[name].contours):
# add contours to dict, by bounds[0]: contourIndex
contoursLeftBounds[c.bounds[0]] = i
contoursLeftBoundsSorted = collections.OrderedDict(sorted(contoursLeftBounds.items()))
# get index of leftmost contour
leftmostContourIndex = list(contoursLeftBoundsSorted.items())[0][1]
# if the leftmost contour is clockwise, assume all paths are wrong and reverse them
if f[name].contours[leftmostContourIndex].clockwise == True:
for c in f[name].contours:
c.reverse()
# go through list to coordinate
for name in glyphsToCoordinate.split(" "):
orientPathsCCW(f[name])
except KeyError:
pass
f.save()
if args.normalize:
normalizeUFO(ufoPath)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Orient path directions in all glyphs for all UFOs in a directory.')
parser.add_argument('dir',
help='Relative path to a directory of one or more UFO font sources')
parser.add_argument("-n", "--normalize",
action='store_true',
help='Normalizes UFOs with ufoNormalizer.')
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment