[varLib.interpolatable] Support discrete axes in .designspace

Fixes https://github.com/fonttools/fonttools/issues/3597
This commit is contained in:
Behdad Esfahbod 2024-08-03 17:34:01 -06:00
parent 16dbe3f5e2
commit ead2a18d4b
2 changed files with 39 additions and 10 deletions

View File

@ -135,6 +135,7 @@ def test_gen(
kinkiness=DEFAULT_KINKINESS,
upem=DEFAULT_UPEM,
show_all=False,
discrete_axes=[],
):
if tolerance >= 10:
tolerance *= 0.01
@ -150,7 +151,9 @@ def test_gen(
# ... risks the sparse master being the first one, and only processing a subset of the glyphs
glyphs = {g for glyphset in glyphsets for g in glyphset.keys()}
parents, order = find_parents_and_order(glyphsets, locations)
parents, order = find_parents_and_order(
glyphsets, locations, discrete_axes=discrete_axes
)
def grand_parent(i, glyphname):
if i is None:
@ -701,6 +704,7 @@ def main(args=None):
fonts = []
names = []
locations = []
discrete_axes = set()
upem = DEFAULT_UPEM
original_args_inputs = tuple(args.inputs)
@ -713,8 +717,13 @@ def main(args=None):
designspace = DesignSpaceDocument.fromfile(args.inputs[0])
args.inputs = [master.path for master in designspace.sources]
locations = [master.location for master in designspace.sources]
discrete_axes = {
a.name for a in designspace.axes if not hasattr(a, "minimum")
}
axis_triples = {
a.name: (a.minimum, a.default, a.maximum) for a in designspace.axes
a.name: (a.minimum, a.default, a.maximum)
for a in designspace.axes
if a.name not in discrete_axes
}
axis_mappings = {a.name: a.map for a in designspace.axes}
axis_triples = {
@ -879,7 +888,13 @@ def main(args=None):
glyphset[gn] = None
# Normalize locations
locations = [normalizeLocation(loc, axis_triples) for loc in locations]
locations = [
{
**normalizeLocation(loc, axis_triples),
**{k: v for k, v in loc.items() if k in discrete_axes},
}
for loc in locations
]
tolerance = args.tolerance or DEFAULT_TOLERANCE
kinkiness = args.kinkiness if args.kinkiness is not None else DEFAULT_KINKINESS
@ -896,6 +911,7 @@ def main(args=None):
tolerance=tolerance,
kinkiness=kinkiness,
show_all=args.show_all,
discrete_axes=discrete_axes,
)
problems = defaultdict(list)

View File

@ -293,17 +293,19 @@ def add_isomorphisms(points, isomorphisms, reverse):
)
def find_parents_and_order(glyphsets, locations):
def find_parents_and_order(glyphsets, locations, *, discrete_axes=set()):
parents = [None] + list(range(len(glyphsets) - 1))
order = list(range(len(glyphsets)))
if locations:
# Order base master first
bases = (i for i, l in enumerate(locations) if all(v == 0 for v in l.values()))
bases = [
i
for i, l in enumerate(locations)
if all(v == 0 for k, v in l.items() if k not in discrete_axes)
]
if bases:
base = next(bases)
logging.info("Base master index %s, location %s", base, locations[base])
logging.info("Found %s base masters: %s", len(bases), bases)
else:
base = 0
logging.warning("No base master location found")
# Form a minimum spanning tree of the locations
@ -317,9 +319,17 @@ def find_parents_and_order(glyphsets, locations):
axes = sorted(axes)
vectors = [tuple(l.get(k, 0) for k in axes) for l in locations]
for i, j in itertools.combinations(range(len(locations)), 2):
i_discrete_location = {
k: v for k, v in zip(axes, vectors[i]) if k in discrete_axes
}
j_discrete_location = {
k: v for k, v in zip(axes, vectors[j]) if k in discrete_axes
}
if i_discrete_location != j_discrete_location:
continue
graph[i][j] = vdiff_hypot2(vectors[i], vectors[j])
tree = minimum_spanning_tree(graph)
tree = minimum_spanning_tree(graph, overwrite=True)
rows, cols = tree.nonzero()
graph = defaultdict(set)
for row, col in zip(rows, cols):
@ -330,7 +340,7 @@ def find_parents_and_order(glyphsets, locations):
parents = [None] * len(locations)
order = []
visited = set()
queue = deque([base])
queue = deque(bases)
while queue:
i = queue.popleft()
visited.add(i)
@ -339,6 +349,9 @@ def find_parents_and_order(glyphsets, locations):
if j not in visited:
parents[j] = i
queue.append(j)
assert len(order) == len(
parents
), "Not all masters are reachable; report an issue"
except ImportError:
pass