[varLib.interpolatable] Support discrete axes in .designspace
Fixes https://github.com/fonttools/fonttools/issues/3597
This commit is contained in:
parent
16dbe3f5e2
commit
ead2a18d4b
@ -135,6 +135,7 @@ def test_gen(
|
|||||||
kinkiness=DEFAULT_KINKINESS,
|
kinkiness=DEFAULT_KINKINESS,
|
||||||
upem=DEFAULT_UPEM,
|
upem=DEFAULT_UPEM,
|
||||||
show_all=False,
|
show_all=False,
|
||||||
|
discrete_axes=[],
|
||||||
):
|
):
|
||||||
if tolerance >= 10:
|
if tolerance >= 10:
|
||||||
tolerance *= 0.01
|
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
|
# ... 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()}
|
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):
|
def grand_parent(i, glyphname):
|
||||||
if i is None:
|
if i is None:
|
||||||
@ -701,6 +704,7 @@ def main(args=None):
|
|||||||
fonts = []
|
fonts = []
|
||||||
names = []
|
names = []
|
||||||
locations = []
|
locations = []
|
||||||
|
discrete_axes = set()
|
||||||
upem = DEFAULT_UPEM
|
upem = DEFAULT_UPEM
|
||||||
|
|
||||||
original_args_inputs = tuple(args.inputs)
|
original_args_inputs = tuple(args.inputs)
|
||||||
@ -713,8 +717,13 @@ def main(args=None):
|
|||||||
designspace = DesignSpaceDocument.fromfile(args.inputs[0])
|
designspace = DesignSpaceDocument.fromfile(args.inputs[0])
|
||||||
args.inputs = [master.path for master in designspace.sources]
|
args.inputs = [master.path for master in designspace.sources]
|
||||||
locations = [master.location 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 = {
|
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_mappings = {a.name: a.map for a in designspace.axes}
|
||||||
axis_triples = {
|
axis_triples = {
|
||||||
@ -879,7 +888,13 @@ def main(args=None):
|
|||||||
glyphset[gn] = None
|
glyphset[gn] = None
|
||||||
|
|
||||||
# Normalize locations
|
# 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
|
tolerance = args.tolerance or DEFAULT_TOLERANCE
|
||||||
kinkiness = args.kinkiness if args.kinkiness is not None else DEFAULT_KINKINESS
|
kinkiness = args.kinkiness if args.kinkiness is not None else DEFAULT_KINKINESS
|
||||||
|
|
||||||
@ -896,6 +911,7 @@ def main(args=None):
|
|||||||
tolerance=tolerance,
|
tolerance=tolerance,
|
||||||
kinkiness=kinkiness,
|
kinkiness=kinkiness,
|
||||||
show_all=args.show_all,
|
show_all=args.show_all,
|
||||||
|
discrete_axes=discrete_axes,
|
||||||
)
|
)
|
||||||
problems = defaultdict(list)
|
problems = defaultdict(list)
|
||||||
|
|
||||||
|
@ -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))
|
parents = [None] + list(range(len(glyphsets) - 1))
|
||||||
order = list(range(len(glyphsets)))
|
order = list(range(len(glyphsets)))
|
||||||
if locations:
|
if locations:
|
||||||
# Order base master first
|
# 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:
|
if bases:
|
||||||
base = next(bases)
|
logging.info("Found %s base masters: %s", len(bases), bases)
|
||||||
logging.info("Base master index %s, location %s", base, locations[base])
|
|
||||||
else:
|
else:
|
||||||
base = 0
|
|
||||||
logging.warning("No base master location found")
|
logging.warning("No base master location found")
|
||||||
|
|
||||||
# Form a minimum spanning tree of the locations
|
# Form a minimum spanning tree of the locations
|
||||||
@ -317,9 +319,17 @@ def find_parents_and_order(glyphsets, locations):
|
|||||||
axes = sorted(axes)
|
axes = sorted(axes)
|
||||||
vectors = [tuple(l.get(k, 0) for k in axes) for l in locations]
|
vectors = [tuple(l.get(k, 0) for k in axes) for l in locations]
|
||||||
for i, j in itertools.combinations(range(len(locations)), 2):
|
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])
|
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()
|
rows, cols = tree.nonzero()
|
||||||
graph = defaultdict(set)
|
graph = defaultdict(set)
|
||||||
for row, col in zip(rows, cols):
|
for row, col in zip(rows, cols):
|
||||||
@ -330,7 +340,7 @@ def find_parents_and_order(glyphsets, locations):
|
|||||||
parents = [None] * len(locations)
|
parents = [None] * len(locations)
|
||||||
order = []
|
order = []
|
||||||
visited = set()
|
visited = set()
|
||||||
queue = deque([base])
|
queue = deque(bases)
|
||||||
while queue:
|
while queue:
|
||||||
i = queue.popleft()
|
i = queue.popleft()
|
||||||
visited.add(i)
|
visited.add(i)
|
||||||
@ -339,6 +349,9 @@ def find_parents_and_order(glyphsets, locations):
|
|||||||
if j not in visited:
|
if j not in visited:
|
||||||
parents[j] = i
|
parents[j] = i
|
||||||
queue.append(j)
|
queue.append(j)
|
||||||
|
assert len(order) == len(
|
||||||
|
parents
|
||||||
|
), "Not all masters are reachable; report an issue"
|
||||||
|
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
Loading…
x
Reference in New Issue
Block a user