fonttools/Lib/fontTools/varLib/interpolatable.py

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

234 lines
7.9 KiB
Python
Raw Normal View History

"""
Tool to find wrong contour order between different masters, and
other interpolatability (or lack thereof) issues.
Call as:
$ fonttools varLib.interpolatable font1 font2 ...
"""
from ._interpolatable import test
from collections import defaultdict
2020-11-20 10:02:46 +00:00
def recursivelyAddGlyph(glyphname, glyphset, ttGlyphSet, glyf):
if glyphname in glyphset:
return
glyphset[glyphname] = ttGlyphSet[glyphname]
for component in getattr(glyf[glyphname], "components", []):
recursivelyAddGlyph(component.glyphName, glyphset, ttGlyphSet, glyf)
def main(args=None):
2020-11-20 10:02:46 +00:00
"""Test for interpolatability issues between fonts"""
import argparse
parser = argparse.ArgumentParser(
"fonttools varLib.interpolatable",
description=main.__doc__,
)
2023-06-27 16:59:05 -06:00
parser.add_argument(
"--glyphs",
action="store",
help="Space-separate name of glyphs to check",
)
parser.add_argument(
"--json",
action="store_true",
help="Output report in JSON format",
)
2020-11-20 10:02:46 +00:00
parser.add_argument(
"--quiet",
action="store_true",
help="Only exit with code 1 or 0, no output",
)
parser.add_argument(
"--ignore-missing",
action="store_true",
help="Will not report glyphs missing from sparse masters as errors",
)
parser.add_argument(
"inputs",
metavar="FILE",
type=str,
nargs="+",
help="Input a single DesignSpace/Glyphs file, or multiple TTF/UFO files",
2020-11-20 10:02:46 +00:00
)
args = parser.parse_args(args)
2023-06-27 16:59:05 -06:00
glyphs = args.glyphs.split() if args.glyphs else None
2020-11-20 10:02:46 +00:00
from os.path import basename
fonts = []
names = []
if len(args.inputs) == 1:
if args.inputs[0].endswith(".designspace"):
from fontTools.designspaceLib import DesignSpaceDocument
2022-12-13 11:26:36 +00:00
designspace = DesignSpaceDocument.fromfile(args.inputs[0])
args.inputs = [master.path for master in designspace.sources]
elif args.inputs[0].endswith(".glyphs"):
from glyphsLib import GSFont, to_ufos
2022-12-13 11:26:36 +00:00
gsfont = GSFont(args.inputs[0])
fonts.extend(to_ufos(gsfont))
names = ["%s-%s" % (f.info.familyName, f.info.styleName) for f in fonts]
args.inputs = []
elif args.inputs[0].endswith(".ttf"):
from fontTools.ttLib import TTFont
2022-12-13 11:26:36 +00:00
font = TTFont(args.inputs[0])
if "gvar" in font:
# Is variable font
gvar = font["gvar"]
glyf = font["glyf"]
# Gather all glyphs at their "master" locations
ttGlyphSets = {}
glyphsets = defaultdict(dict)
if glyphs is None:
glyphs = sorted(gvar.variations.keys())
for glyphname in glyphs:
for var in gvar.variations[glyphname]:
locDict = {}
loc = []
for tag, val in sorted(var.axes.items()):
locDict[tag] = val[1]
loc.append((tag, val[1]))
locTuple = tuple(loc)
if locTuple not in ttGlyphSets:
ttGlyphSets[locTuple] = font.getGlyphSet(
location=locDict, normalized=True
)
recursivelyAddGlyph(
glyphname, glyphsets[locTuple], ttGlyphSets[locTuple], glyf
)
names = ["()"]
fonts = [font.getGlyphSet()]
for locTuple in sorted(glyphsets.keys(), key=lambda v: (len(v), v)):
names.append(str(locTuple))
fonts.append(glyphsets[locTuple])
args.ignore_missing = True
args.inputs = []
2020-11-20 10:02:46 +00:00
for filename in args.inputs:
if filename.endswith(".ufo"):
from fontTools.ufoLib import UFOReader
2022-12-13 11:26:36 +00:00
2020-11-20 10:02:46 +00:00
fonts.append(UFOReader(filename))
else:
from fontTools.ttLib import TTFont
2022-12-13 11:26:36 +00:00
2020-11-20 10:02:46 +00:00
fonts.append(TTFont(filename))
names.append(basename(filename).rsplit(".", 1)[0])
glyphsets = []
for font in fonts:
if hasattr(font, "getGlyphSet"):
glyphset = font.getGlyphSet()
else:
glyphset = font
glyphsets.append({k: glyphset[k] for k in glyphset.keys()})
if not glyphs:
glyphs = sorted(set([gn for glyphset in glyphsets for gn in glyphset.keys()]))
glyphsSet = set(glyphs)
for glyphset in glyphsets:
glyphSetGlyphNames = set(glyphset.keys())
diff = glyphsSet - glyphSetGlyphNames
if diff:
for gn in diff:
glyphset[gn] = None
problems = test(
glyphsets, glyphs=glyphs, names=names, ignore_missing=args.ignore_missing
)
if not args.quiet:
if args.json:
import json
print(json.dumps(problems))
else:
for glyph, glyph_problems in problems.items():
print(f"Glyph {glyph} was not compatible: ")
for p in glyph_problems:
if p["type"] == "missing":
print(" Glyph was missing in master %s" % p["master"])
if p["type"] == "open_path":
print(" Glyph has an open path in master %s" % p["master"])
if p["type"] == "path_count":
print(
" Path count differs: %i in %s, %i in %s"
% (p["value_1"], p["master_1"], p["value_2"], p["master_2"])
)
if p["type"] == "node_count":
print(
" Node count differs in path %i: %i in %s, %i in %s"
% (
p["path"],
p["value_1"],
p["master_1"],
p["value_2"],
p["master_2"],
)
)
if p["type"] == "node_incompatibility":
print(
" Node %o incompatible in path %i: %s in %s, %s in %s"
% (
p["node"],
p["path"],
p["value_1"],
p["master_1"],
p["value_2"],
p["master_2"],
)
)
if p["type"] == "contour_order":
print(
" Contour order differs: %s in %s, %s in %s"
% (
p["value_1"],
p["master_1"],
p["value_2"],
p["master_2"],
)
)
if p["type"] == "wrong_start_point":
print(
" Contour %d start point differs: %s, %s"
% (
p["contour"],
p["master_1"],
p["master_2"],
)
)
if p["type"] == "math_error":
print(
" Miscellaneous error in %s: %s"
% (
p["master"],
p["error"],
)
)
2020-11-20 10:49:31 +00:00
if problems:
return problems
2020-11-20 10:02:46 +00:00
if __name__ == "__main__":
import sys
2020-11-20 10:49:31 +00:00
problems = main()
sys.exit(int(bool(problems)))