Merge pull request #3377 from fonttools/interpolatable-refactor3

Interpolatable refactor3
This commit is contained in:
Behdad Esfahbod 2023-12-06 16:03:16 -05:00 committed by GitHub
commit 39f6142f2c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 121 additions and 54 deletions

View File

@ -186,7 +186,11 @@ def test_gen(
if not ignore_missing:
yield (
glyph_name,
{"type": "missing", "master": name, "master_idx": master_idx},
{
"type": InterpolatableProblem.MISSING,
"master": name,
"master_idx": master_idx,
},
)
continue
@ -198,10 +202,10 @@ def test_gen(
yield (
glyph_name,
{
"type": InterpolatableProblem.OPEN_PATH,
"master": name,
"master_idx": master_idx,
"contour": ix,
"type": "open_path",
},
)
if has_open:
@ -230,7 +234,7 @@ def test_gen(
yield (
glyph_name,
{
"type": "path_count",
"type": InterpolatableProblem.PATH_COUNT,
"master_1": names[m0idx],
"master_2": names[m1idx],
"master_1_idx": m0idx,
@ -249,7 +253,7 @@ def test_gen(
yield (
glyph_name,
{
"type": "node_count",
"type": InterpolatableProblem.NODE_COUNT,
"path": pathIx,
"master_1": names[m0idx],
"master_2": names[m1idx],
@ -265,7 +269,7 @@ def test_gen(
yield (
glyph_name,
{
"type": "node_incompatibility",
"type": InterpolatableProblem.NODE_INCOMPATIBILITY,
"path": pathIx,
"node": nodeIx,
"master_1": names[m0idx],
@ -279,7 +283,7 @@ def test_gen(
continue
#
# "contour_order" check
# InterpolatableProblem.CONTOUR_ORDER check
#
this_tolerance, matching = test_contour_order(glyph0, glyph1)
@ -287,7 +291,7 @@ def test_gen(
yield (
glyph_name,
{
"type": "contour_order",
"type": InterpolatableProblem.CONTOUR_ORDER,
"master_1": names[m0idx],
"master_2": names[m1idx],
"master_1_idx": m0idx,
@ -300,7 +304,7 @@ def test_gen(
matchings[m1idx] = matching
#
# "wrong_start_point" / weight check
# wrong-start-point / weight check
#
m0Isomorphisms = glyph0.isomorphisms
@ -354,7 +358,7 @@ def test_gen(
yield (
glyph_name,
{
"type": "wrong_start_point",
"type": InterpolatableProblem.WRONG_START_POINT,
"contour": ix,
"master_1": names[m0idx],
"master_2": names[m1idx],
@ -400,7 +404,10 @@ def test_gen(
t = tolerance**power
for overweight, problem_type in enumerate(
("underweight", "overweight")
(
InterpolatableProblem.UNDERWEIGHT,
InterpolatableProblem.OVERWEIGHT,
)
):
if overweight:
expectedSize = sqrt(size0 * size1)
@ -579,7 +586,7 @@ def test_gen(
yield (
glyph_name,
{
"type": "kink",
"type": InterpolatableProblem.KINK,
"contour": ix,
"master_1": names[m0idx],
"master_2": names[m1idx],
@ -598,7 +605,7 @@ def test_gen(
yield (
glyph_name,
{
"type": "nothing",
"type": InterpolatableProblem.NOTHING,
"master_1": names[m0idx],
"master_2": names[m1idx],
"master_1_idx": m0idx,
@ -947,16 +954,16 @@ def main(args=None):
print(f" Masters: %s:" % ", ".join(master_names), file=f)
last_master_idxs = master_idxs
if p["type"] == "missing":
if p["type"] == InterpolatableProblem.MISSING:
print(
" Glyph was missing in master %s" % p["master"], file=f
)
elif p["type"] == "open_path":
elif p["type"] == InterpolatableProblem.OPEN_PATH:
print(
" Glyph has an open path in master %s" % p["master"],
file=f,
)
elif p["type"] == "path_count":
elif p["type"] == InterpolatableProblem.PATH_COUNT:
print(
" Path count differs: %i in %s, %i in %s"
% (
@ -967,7 +974,7 @@ def main(args=None):
),
file=f,
)
elif p["type"] == "node_count":
elif p["type"] == InterpolatableProblem.NODE_COUNT:
print(
" Node count differs in path %i: %i in %s, %i in %s"
% (
@ -979,7 +986,7 @@ def main(args=None):
),
file=f,
)
elif p["type"] == "node_incompatibility":
elif p["type"] == InterpolatableProblem.NODE_INCOMPATIBILITY:
print(
" Node %o incompatible in path %i: %s in %s, %s in %s"
% (
@ -992,7 +999,7 @@ def main(args=None):
),
file=f,
)
elif p["type"] == "contour_order":
elif p["type"] == InterpolatableProblem.CONTOUR_ORDER:
print(
" Contour order differs: %s in %s, %s in %s"
% (
@ -1003,7 +1010,7 @@ def main(args=None):
),
file=f,
)
elif p["type"] == "wrong_start_point":
elif p["type"] == InterpolatableProblem.WRONG_START_POINT:
print(
" Contour %d start point differs: %s in %s, %s in %s; reversed: %s"
% (
@ -1016,7 +1023,7 @@ def main(args=None):
),
file=f,
)
elif p["type"] == "underweight":
elif p["type"] == InterpolatableProblem.UNDERWEIGHT:
print(
" Contour %d interpolation is underweight: %s, %s"
% (
@ -1026,7 +1033,7 @@ def main(args=None):
),
file=f,
)
elif p["type"] == "overweight":
elif p["type"] == InterpolatableProblem.OVERWEIGHT:
print(
" Contour %d interpolation is overweight: %s, %s"
% (
@ -1036,7 +1043,7 @@ def main(args=None):
),
file=f,
)
elif p["type"] == "kink":
elif p["type"] == InterpolatableProblem.KINK:
print(
" Contour %d has a kink at %s: %s, %s"
% (
@ -1047,7 +1054,7 @@ def main(args=None):
),
file=f,
)
elif p["type"] == "nothing":
elif p["type"] == InterpolatableProblem.NOTHING:
print(
" Showing %s and %s"
% (
@ -1060,6 +1067,8 @@ def main(args=None):
for glyphname, problem in problems_gen:
problems[glyphname].append(problem)
problems = sort_problems(problems)
if args.pdf:
log.info("Writing PDF to %s", args.pdf)
from .interpolatablePlot import InterpolatablePDF

View File

@ -4,6 +4,7 @@ from fontTools.pens.recordingPen import RecordingPen, DecomposingRecordingPen
from fontTools.misc.transform import Transform
from collections import defaultdict, deque
from math import sqrt, copysign, atan2, pi
from enum import Enum
import itertools
import logging
@ -11,6 +12,53 @@ import logging
log = logging.getLogger("fontTools.varLib.interpolatable")
class InterpolatableProblem:
NOTHING = "nothing"
MISSING = "missing"
OPEN_PATH = "open_path"
PATH_COUNT = "path_count"
NODE_COUNT = "node_count"
NODE_INCOMPATIBILITY = "node_incompatibility"
CONTOUR_ORDER = "contour_order"
WRONG_START_POINT = "wrong_start_point"
KINK = "kink"
UNDERWEIGHT = "underweight"
OVERWEIGHT = "overweight"
severity = {
MISSING: 1,
OPEN_PATH: 2,
PATH_COUNT: 3,
NODE_COUNT: 4,
NODE_INCOMPATIBILITY: 5,
CONTOUR_ORDER: 6,
WRONG_START_POINT: 7,
KINK: 8,
UNDERWEIGHT: 9,
OVERWEIGHT: 10,
NOTHING: 11,
}
def sort_problems(problems):
"""Sort problems by severity, then by glyph name, then by problem message."""
return dict(
sorted(
problems.items(),
key=lambda _: -min(
(
InterpolatableProblem.severity[p["type"]]
for p in _[1]
if InterpolatableProblem.severity[p["type"]]
!= InterpolatableProblem.severity[InterpolatableProblem.NOTHING]
),
default=InterpolatableProblem.severity[InterpolatableProblem.NOTHING],
),
reverse=True,
)
)
def rot_list(l, k):
"""Rotate list by k items forward. Ie. item at position 0 will be
at position k in returned list. Negative k is allowed."""

View File

@ -1,3 +1,4 @@
from .interpolatableHelpers import *
from fontTools.ttLib import TTFont
from fontTools.pens.recordingPen import (
RecordingPen,
@ -397,7 +398,7 @@ class InterpolatablePlot:
)
master_indices = [problems[0][k] for k in master_keys]
if problem_type == "missing":
if problem_type == InterpolatableProblem.MISSING:
sample_glyph = next(
i for i, m in enumerate(self.glyphsets) if m[glyphname] is not None
)
@ -457,12 +458,12 @@ class InterpolatablePlot:
if any(
pt
in (
"nothing",
"wrong_start_point",
"contour_order",
"kink",
"underweight",
"overweight",
InterpolatableProblem.NOTHING,
InterpolatableProblem.WRONG_START_POINT,
InterpolatableProblem.CONTOUR_ORDER,
InterpolatableProblem.KINK,
InterpolatableProblem.UNDERWEIGHT,
InterpolatableProblem.OVERWEIGHT,
)
for pt in problem_types
):
@ -489,7 +490,12 @@ class InterpolatablePlot:
+ [
p
for p in problems
if p["type"] in ("kink", "underweight", "overweight")
if p["type"]
in (
InterpolatableProblem.KINK,
InterpolatableProblem.UNDERWEIGHT,
InterpolatableProblem.OVERWEIGHT,
)
],
None,
x=x,
@ -502,9 +508,9 @@ class InterpolatablePlot:
if any(
pt
in (
"wrong_start_point",
"contour_order",
"kink",
InterpolatableProblem.WRONG_START_POINT,
InterpolatableProblem.CONTOUR_ORDER,
InterpolatableProblem.KINK,
)
for pt in problem_types
):
@ -525,14 +531,14 @@ class InterpolatablePlot:
glyphset2[glyphname].draw(perContourPen2)
for problem in problems:
if problem["type"] == "contour_order":
if problem["type"] == InterpolatableProblem.CONTOUR_ORDER:
fixed_contours = [
perContourPen2.value[i] for i in problems[0]["value_2"]
]
perContourPen2.value = fixed_contours
for problem in problems:
if problem["type"] == "wrong_start_point":
if problem["type"] == InterpolatableProblem.WRONG_START_POINT:
# Save the wrong contours
wrongContour1 = perContourPen1.value[problem["contour"]]
wrongContour2 = perContourPen2.value[problem["contour"]]
@ -578,7 +584,7 @@ class InterpolatablePlot:
for problem in problems:
# If we have a kink, try to fix it.
if problem["type"] == "kink":
if problem["type"] == InterpolatableProblem.KINK:
# Save the wrong contours
wrongContour1 = perContourPen1.value[problem["contour"]]
wrongContour2 = perContourPen2.value[problem["contour"]]
@ -673,11 +679,11 @@ class InterpolatablePlot:
else:
emoticon = self.shrug
if "underweight" in problem_types:
if InterpolatableProblem.UNDERWEIGHT in problem_types:
emoticon = self.underweight
elif "overweight" in problem_types:
elif InterpolatableProblem.OVERWEIGHT in problem_types:
emoticon = self.overweight
elif "nothing" in problem_types:
elif InterpolatableProblem.NOTHING in problem_types:
emoticon = self.yay
self.draw_emoticon(emoticon, x=x, y=y)
@ -793,7 +799,7 @@ class InterpolatablePlot:
pen = CairoPen(glyphset, cr)
decomposedRecording.replay(pen)
if self.fill_color and problem_type != "open_path":
if self.fill_color and problem_type != InterpolatableProblem.OPEN_PATH:
cr.set_source_rgb(*self.fill_color)
cr.fill_preserve()
@ -804,11 +810,17 @@ class InterpolatablePlot:
cr.new_path()
if "underweight" in problem_types or "overweight" in problem_types:
if (
InterpolatableProblem.UNDERWEIGHT in problem_types
or InterpolatableProblem.OVERWEIGHT in problem_types
):
perContourPen = PerContourOrComponentPen(RecordingPen, glyphset=glyphset)
recording.replay(perContourPen)
for problem in problems:
if problem["type"] in ("underweight", "overweight"):
if problem["type"] in (
InterpolatableProblem.UNDERWEIGHT,
InterpolatableProblem.OVERWEIGHT,
):
contour = perContourPen.value[problem["contour"]]
contour.replay(CairoPen(glyphset, cr))
cr.set_source_rgba(*self.weight_issue_contour_color)
@ -817,9 +829,9 @@ class InterpolatablePlot:
if any(
t in problem_types
for t in {
"nothing",
"node_count",
"node_incompatibility",
InterpolatableProblem.NOTHING,
InterpolatableProblem.NODE_COUNT,
InterpolatableProblem.NODE_INCOMPATIBILITY,
}
):
cr.set_line_cap(cairo.LINE_CAP_ROUND)
@ -873,7 +885,7 @@ class InterpolatablePlot:
matching = None
for problem in problems:
if problem["type"] == "contour_order":
if problem["type"] == InterpolatableProblem.CONTOUR_ORDER:
matching = problem["value_2"]
colors = cycle(self.contour_colors)
perContourPen = PerContourOrComponentPen(
@ -889,7 +901,10 @@ class InterpolatablePlot:
cr.fill()
for problem in problems:
if problem["type"] in ("nothing", "wrong_start_point"):
if problem["type"] in (
InterpolatableProblem.NOTHING,
InterpolatableProblem.WRONG_START_POINT,
):
idx = problem.get("contour")
# Draw suggested point
@ -967,7 +982,7 @@ class InterpolatablePlot:
cr.restore()
if problem["type"] == "kink":
if problem["type"] == InterpolatableProblem.KINK:
idx = problem.get("contour")
perContourPen = PerContourOrComponentPen(
RecordingPen, glyphset=glyphset

View File

@ -9,16 +9,11 @@ def test_starting_point(glyph0, glyph1, ix, tolerance, matching):
m0Vectors = glyph0.greenVectors
m1Vectors = [glyph1.greenVectors[i] for i in matching]
proposed_point = 0
reverse = False
min_cost = first_cost = 1
c0 = contour0[0]
# Next few lines duplicated below.
costs = [vdiff_hypot2_complex(c0[0], c1[0]) for c1 in contour1]
min_cost_idx, min_cost = min(enumerate(costs), key=lambda x: x[1])
first_cost = costs[0]
proposed_point = contour1[min_cost_idx][1]
reverse = contour1[min_cost_idx][2]