From b8554fdad5bfcfaa2d99847057bfb37f0052b273 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Fri, 1 Dec 2023 10:02:00 -0500 Subject: [PATCH 1/9] [interpolatablePlot] Draw \o/ if nothing wrong In --show-all mostly --- Lib/fontTools/varLib/interpolatablePlot.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Lib/fontTools/varLib/interpolatablePlot.py b/Lib/fontTools/varLib/interpolatablePlot.py index 3f68e436a..eef4a4716 100644 --- a/Lib/fontTools/varLib/interpolatablePlot.py +++ b/Lib/fontTools/varLib/interpolatablePlot.py @@ -107,6 +107,7 @@ class InterpolatablePlot: /O\ / \ """ + yay = r""" \o/ """ def __init__(self, out, glyphsets, names=None, **kwargs): self.out = out @@ -501,7 +502,6 @@ class InterpolatablePlot: if any( pt in ( - "nothing", "wrong_start_point", "contour_order", "kink", @@ -677,6 +677,8 @@ class InterpolatablePlot: emoticon = self.underweight elif "overweight" in problem_types: emoticon = self.overweight + elif "nothing" in problem_types: + emoticon = self.yay self.draw_emoticon(emoticon, x=x, y=y) if show_page_number: @@ -1062,7 +1064,7 @@ class InterpolatablePlot: font_ascent = font_extents[0] for line in text: extents = cr.text_extents(line) - text_width = max(text_width, extents.width) + text_width = max(text_width, extents.x_advance) text_height += font_line_height if not text_width: return From ee0936a7819aa371a35806673311d65462a10d5e Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Fri, 1 Dec 2023 18:15:02 -0500 Subject: [PATCH 2/9] [interpolatable] Fixup --- Lib/fontTools/varLib/interpolatable.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/fontTools/varLib/interpolatable.py b/Lib/fontTools/varLib/interpolatable.py index fca21c459..2215b35c1 100644 --- a/Lib/fontTools/varLib/interpolatable.py +++ b/Lib/fontTools/varLib/interpolatable.py @@ -322,8 +322,8 @@ def test_gen( # If contour-order is wrong, adjust it matching = matchings[m1idx] - if matching is not None and m1: # m1 is empty for composite glyphs - m1 = [m1[i] for i in matching] + if matching is not None and m1Isomorphisms: # m1 is empty for composite glyphs + m1Isomorphisms = [m1Isomorphisms[i] for i in matching] m1Vectors = [m1Vectors[i] for i in matching] m1VectorsNormalized = [m1VectorsNormalized[i] for i in matching] recording1 = [recording1[i] for i in matching] From 2dc53dbc4d278365c44a829689499729eaade0aa Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Fri, 1 Dec 2023 18:32:32 -0500 Subject: [PATCH 3/9] [interpolatable] Fixup --- Lib/fontTools/varLib/interpolatable.py | 4 +++- Lib/fontTools/varLib/interpolatableTestStartingPoint.py | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Lib/fontTools/varLib/interpolatable.py b/Lib/fontTools/varLib/interpolatable.py index 2215b35c1..f03e94620 100644 --- a/Lib/fontTools/varLib/interpolatable.py +++ b/Lib/fontTools/varLib/interpolatable.py @@ -322,7 +322,9 @@ def test_gen( # If contour-order is wrong, adjust it matching = matchings[m1idx] - if matching is not None and m1Isomorphisms: # m1 is empty for composite glyphs + if ( + matching is not None and m1Isomorphisms + ): # m1 is empty for composite glyphs m1Isomorphisms = [m1Isomorphisms[i] for i in matching] m1Vectors = [m1Vectors[i] for i in matching] m1VectorsNormalized = [m1VectorsNormalized[i] for i in matching] diff --git a/Lib/fontTools/varLib/interpolatableTestStartingPoint.py b/Lib/fontTools/varLib/interpolatableTestStartingPoint.py index e630b2c95..35e636541 100644 --- a/Lib/fontTools/varLib/interpolatableTestStartingPoint.py +++ b/Lib/fontTools/varLib/interpolatableTestStartingPoint.py @@ -9,7 +9,7 @@ def test_starting_point(glyph0, glyph1, ix, tolerance, matching): m0Vectors = glyph0.greenVectors m1Vectors = [glyph1.greenVectors[i] for i in matching] - starting_point = 0 + proposed_point = 0 reverse = False min_cost = first_cost = 1 @@ -99,4 +99,4 @@ def test_starting_point(glyph0, glyph1, ix, tolerance, matching): this_tolerance = min_cost / first_cost proposed_point = new_contour1[min_cost_idx][1] - return starting_point, reverse, min_cost, first_cost + return proposed_point, reverse, min_cost, first_cost From 8c505452aac18d4dba467746170f896c683d7773 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Fri, 1 Dec 2023 18:43:09 -0500 Subject: [PATCH 4/9] [interpolatable] Fixup --- .../varLib/interpolatableTestStartingPoint.py | 81 +------------------ 1 file changed, 1 insertion(+), 80 deletions(-) diff --git a/Lib/fontTools/varLib/interpolatableTestStartingPoint.py b/Lib/fontTools/varLib/interpolatableTestStartingPoint.py index 35e636541..49b9a9efa 100644 --- a/Lib/fontTools/varLib/interpolatableTestStartingPoint.py +++ b/Lib/fontTools/varLib/interpolatableTestStartingPoint.py @@ -18,85 +18,6 @@ def test_starting_point(glyph0, glyph1, ix, tolerance, matching): 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] - - if min_cost < first_cost * tolerance: - this_tolerance = min_cost / first_cost - # c0 is the first isomorphism of the m0 master - # contour1 is list of all isomorphisms of the m1 master - # - # If the two shapes are both circle-ish and slightly - # rotated, we detect wrong start point. This is for - # example the case hundreds of times in - # RobotoSerif-Italic[GRAD,opsz,wdth,wght].ttf - # - # If the proposed point is only one off from the first - # point (and not reversed), try harder: - # - # Find the major eigenvector of the covariance matrix, - # and rotate the contours by that angle. Then find the - # closest point again. If it matches this time, let it - # pass. - - proposed_point = contour1[min_cost_idx][1] - reverse = contour1[min_cost_idx][2] - num_points = len(glyph1.points[ix]) - leeway = 3 - if not reverse and ( - proposed_point <= leeway or proposed_point >= num_points - leeway - ): - # Try harder - - # Recover the covariance matrix from the GreenVectors. - # This is a 2x2 matrix. - transforms = [] - for vector in (m0Vectors[ix], m1Vectors[ix]): - meanX = vector[1] - meanY = vector[2] - stddevX = vector[3] * 0.5 - stddevY = vector[4] * 0.5 - correlation = vector[5] / abs(vector[0]) - - # https://cookierobotics.com/007/ - a = stddevX * stddevX # VarianceX - c = stddevY * stddevY # VarianceY - b = correlation * stddevX * stddevY # Covariance - - delta = (((a - c) * 0.5) ** 2 + b * b) ** 0.5 - lambda1 = (a + c) * 0.5 + delta # Major eigenvalue - lambda2 = (a + c) * 0.5 - delta # Minor eigenvalue - theta = atan2(lambda1 - a, b) if b != 0 else (pi * 0.5 if a < c else 0) - trans = Transform() - # Don't translate here. We are working on the complex-vector - # that includes more than just the points. It's horrible what - # we are doing anyway... - # trans = trans.translate(meanX, meanY) - trans = trans.rotate(theta) - trans = trans.scale(sqrt(lambda1), sqrt(lambda2)) - transforms.append(trans) - - trans = transforms[0] - new_c0 = ( - [complex(*trans.transformPoint((pt.real, pt.imag))) for pt in c0[0]], - ) + c0[1:] - trans = transforms[1] - new_contour1 = [] - for c1 in contour1: - new_c1 = ( - [ - complex(*trans.transformPoint((pt.real, pt.imag))) - for pt in c1[0] - ], - ) + c1[1:] - new_contour1.append(new_c1) - - # Next few lines duplicate from above. - costs = [ - vdiff_hypot2_complex(new_c0[0], new_c1[0]) for new_c1 in new_contour1 - ] - min_cost_idx, min_cost = min(enumerate(costs), key=lambda x: x[1]) - first_cost = costs[0] - if min_cost < first_cost * tolerance: - this_tolerance = min_cost / first_cost - proposed_point = new_contour1[min_cost_idx][1] + proposed_point = contour1[min_cost_idx][1] return proposed_point, reverse, min_cost, first_cost From cc8cb0e1ee37474038c02ab5cbe173f367edc78d Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Fri, 1 Dec 2023 22:45:27 -0500 Subject: [PATCH 5/9] Revert "[interpolatable] Fixup" This reverts commit 8c505452aac18d4dba467746170f896c683d7773. --- .../varLib/interpolatableTestStartingPoint.py | 81 ++++++++++++++++++- 1 file changed, 80 insertions(+), 1 deletion(-) diff --git a/Lib/fontTools/varLib/interpolatableTestStartingPoint.py b/Lib/fontTools/varLib/interpolatableTestStartingPoint.py index 49b9a9efa..35e636541 100644 --- a/Lib/fontTools/varLib/interpolatableTestStartingPoint.py +++ b/Lib/fontTools/varLib/interpolatableTestStartingPoint.py @@ -18,6 +18,85 @@ def test_starting_point(glyph0, glyph1, ix, tolerance, matching): 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] + + if min_cost < first_cost * tolerance: + this_tolerance = min_cost / first_cost + # c0 is the first isomorphism of the m0 master + # contour1 is list of all isomorphisms of the m1 master + # + # If the two shapes are both circle-ish and slightly + # rotated, we detect wrong start point. This is for + # example the case hundreds of times in + # RobotoSerif-Italic[GRAD,opsz,wdth,wght].ttf + # + # If the proposed point is only one off from the first + # point (and not reversed), try harder: + # + # Find the major eigenvector of the covariance matrix, + # and rotate the contours by that angle. Then find the + # closest point again. If it matches this time, let it + # pass. + + proposed_point = contour1[min_cost_idx][1] + reverse = contour1[min_cost_idx][2] + num_points = len(glyph1.points[ix]) + leeway = 3 + if not reverse and ( + proposed_point <= leeway or proposed_point >= num_points - leeway + ): + # Try harder + + # Recover the covariance matrix from the GreenVectors. + # This is a 2x2 matrix. + transforms = [] + for vector in (m0Vectors[ix], m1Vectors[ix]): + meanX = vector[1] + meanY = vector[2] + stddevX = vector[3] * 0.5 + stddevY = vector[4] * 0.5 + correlation = vector[5] / abs(vector[0]) + + # https://cookierobotics.com/007/ + a = stddevX * stddevX # VarianceX + c = stddevY * stddevY # VarianceY + b = correlation * stddevX * stddevY # Covariance + + delta = (((a - c) * 0.5) ** 2 + b * b) ** 0.5 + lambda1 = (a + c) * 0.5 + delta # Major eigenvalue + lambda2 = (a + c) * 0.5 - delta # Minor eigenvalue + theta = atan2(lambda1 - a, b) if b != 0 else (pi * 0.5 if a < c else 0) + trans = Transform() + # Don't translate here. We are working on the complex-vector + # that includes more than just the points. It's horrible what + # we are doing anyway... + # trans = trans.translate(meanX, meanY) + trans = trans.rotate(theta) + trans = trans.scale(sqrt(lambda1), sqrt(lambda2)) + transforms.append(trans) + + trans = transforms[0] + new_c0 = ( + [complex(*trans.transformPoint((pt.real, pt.imag))) for pt in c0[0]], + ) + c0[1:] + trans = transforms[1] + new_contour1 = [] + for c1 in contour1: + new_c1 = ( + [ + complex(*trans.transformPoint((pt.real, pt.imag))) + for pt in c1[0] + ], + ) + c1[1:] + new_contour1.append(new_c1) + + # Next few lines duplicate from above. + costs = [ + vdiff_hypot2_complex(new_c0[0], new_c1[0]) for new_c1 in new_contour1 + ] + min_cost_idx, min_cost = min(enumerate(costs), key=lambda x: x[1]) + first_cost = costs[0] + if min_cost < first_cost * tolerance: + this_tolerance = min_cost / first_cost + proposed_point = new_contour1[min_cost_idx][1] return proposed_point, reverse, min_cost, first_cost From 1243f97cc7ae53e7b467050e16b785dd582afec2 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Fri, 1 Dec 2023 22:46:54 -0500 Subject: [PATCH 6/9] Another try --- Lib/fontTools/varLib/interpolatableTestStartingPoint.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Lib/fontTools/varLib/interpolatableTestStartingPoint.py b/Lib/fontTools/varLib/interpolatableTestStartingPoint.py index 35e636541..c4d2307e8 100644 --- a/Lib/fontTools/varLib/interpolatableTestStartingPoint.py +++ b/Lib/fontTools/varLib/interpolatableTestStartingPoint.py @@ -96,7 +96,8 @@ def test_starting_point(glyph0, glyph1, ix, tolerance, matching): min_cost_idx, min_cost = min(enumerate(costs), key=lambda x: x[1]) first_cost = costs[0] if min_cost < first_cost * tolerance: - this_tolerance = min_cost / first_cost - proposed_point = new_contour1[min_cost_idx][1] + # Don't report this + min_cost = first_cost + proposed_point = 0 # new_contour1[min_cost_idx][1] return proposed_point, reverse, min_cost, first_cost From aa73b6b4d3b9cf13210343204262df94c969ad3c Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Fri, 1 Dec 2023 23:09:13 -0500 Subject: [PATCH 7/9] Fixup --- Lib/fontTools/varLib/interpolatableTestStartingPoint.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/fontTools/varLib/interpolatableTestStartingPoint.py b/Lib/fontTools/varLib/interpolatableTestStartingPoint.py index c4d2307e8..4632df5e1 100644 --- a/Lib/fontTools/varLib/interpolatableTestStartingPoint.py +++ b/Lib/fontTools/varLib/interpolatableTestStartingPoint.py @@ -98,6 +98,7 @@ def test_starting_point(glyph0, glyph1, ix, tolerance, matching): if min_cost < first_cost * tolerance: # Don't report this min_cost = first_cost + reverse = False proposed_point = 0 # new_contour1[min_cost_idx][1] return proposed_point, reverse, min_cost, first_cost From e22584785c1a24aa9f1ce18dd9b1ec1c06d05ab8 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Fri, 1 Dec 2023 23:19:50 -0500 Subject: [PATCH 8/9] Another try --- Lib/fontTools/varLib/interpolatableTestStartingPoint.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Lib/fontTools/varLib/interpolatableTestStartingPoint.py b/Lib/fontTools/varLib/interpolatableTestStartingPoint.py index 4632df5e1..dcd7d00fa 100644 --- a/Lib/fontTools/varLib/interpolatableTestStartingPoint.py +++ b/Lib/fontTools/varLib/interpolatableTestStartingPoint.py @@ -98,7 +98,8 @@ def test_starting_point(glyph0, glyph1, ix, tolerance, matching): if min_cost < first_cost * tolerance: # Don't report this min_cost = first_cost - reverse = False - proposed_point = 0 # new_contour1[min_cost_idx][1] + # reverse = False + # proposed_point = 0 # new_contour1[min_cost_idx][1] + pass return proposed_point, reverse, min_cost, first_cost From 0db0b355ba57696aabe1446765dd41f49569fd21 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Fri, 1 Dec 2023 23:41:25 -0500 Subject: [PATCH 9/9] Fixup --- Lib/fontTools/varLib/interpolatableTestStartingPoint.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/fontTools/varLib/interpolatableTestStartingPoint.py b/Lib/fontTools/varLib/interpolatableTestStartingPoint.py index dcd7d00fa..9f742a14f 100644 --- a/Lib/fontTools/varLib/interpolatableTestStartingPoint.py +++ b/Lib/fontTools/varLib/interpolatableTestStartingPoint.py @@ -97,7 +97,7 @@ def test_starting_point(glyph0, glyph1, ix, tolerance, matching): first_cost = costs[0] if min_cost < first_cost * tolerance: # Don't report this - min_cost = first_cost + # min_cost = first_cost # reverse = False # proposed_point = 0 # new_contour1[min_cost_idx][1] pass