From d88292b7428f5daa118600b20553c5fb02c22485 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Tue, 5 Dec 2023 15:10:19 -0700 Subject: [PATCH] Revert "[interpolatableTestStartingPoint] Try to rationalize the extended case" This reverts commit b950447e491ddf1e1a739d06711755db01ad5e4e. --- Lib/fontTools/varLib/interpolatable.py | 23 +----- .../varLib/interpolatableTestStartingPoint.py | 78 +++++++++++++++---- 2 files changed, 68 insertions(+), 33 deletions(-) diff --git a/Lib/fontTools/varLib/interpolatable.py b/Lib/fontTools/varLib/interpolatable.py index 81675052a..ae2de2a19 100644 --- a/Lib/fontTools/varLib/interpolatable.py +++ b/Lib/fontTools/varLib/interpolatable.py @@ -42,9 +42,7 @@ class Glyph: "controlVectors", "nodeTypes", "isomorphisms", - "isomorphismsNormalized", "points", - "pointsNormalized", "openContours", ) @@ -95,12 +93,12 @@ class Glyph: # Save a "normalized" version of the outlines try: rpen = DecomposingRecordingPen(glyphset) - transform = transform_from_stats(greenStats, inverse=True) - tpen = TransformPen(rpen, transform) + tpen = TransformPen( + rpen, transform_from_stats(greenStats, inverse=True) + ) contour.replay(tpen) self.recordingsNormalized.append(rpen) except ZeroDivisionError: - transform = Transform() self.recordingsNormalized.append(None) greenStats = StatisticsPen(glyphset=glyphset) @@ -114,7 +112,6 @@ class Glyph: assert nodeTypes[0] == "moveTo" assert nodeTypes[-1] in ("closePath", "endPath") - points = SimpleRecordingPointPen() converter = SegmentToPointPen(points, False) contour.replay(converter) @@ -123,26 +120,14 @@ class Glyph: # possible starting points. self.points.append(points.value) - pointsNormalized = SimpleRecordingPointPen() - converter = SegmentToPointPen(pointsNormalized, False) - converter = TransformPen(converter, transform) - contour.replay(converter) - self.pointsNormalized.append(pointsNormalized.value) - isomorphisms = [] self.isomorphisms.append(isomorphisms) + # Add rotations add_isomorphisms(points.value, isomorphisms, False) # Add mirrored rotations add_isomorphisms(points.value, isomorphisms, True) - isomorphisms = [] - self.isomorphismsNormalized.append(isomorphisms) - # Add rotations - add_isomorphisms(pointsNormalized.value, isomorphisms, False) - # Add mirrored rotations - add_isomorphisms(pointsNormalized.value, isomorphisms, True) - def draw(self, pen, countor_idx=None): if countor_idx is None: for contour in self.recordings: diff --git a/Lib/fontTools/varLib/interpolatableTestStartingPoint.py b/Lib/fontTools/varLib/interpolatableTestStartingPoint.py index 409297a60..a84a65184 100644 --- a/Lib/fontTools/varLib/interpolatableTestStartingPoint.py +++ b/Lib/fontTools/varLib/interpolatableTestStartingPoint.py @@ -9,6 +9,10 @@ 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] @@ -17,7 +21,6 @@ def test_starting_point(glyph0, glyph1, ix, tolerance, matching): proposed_point = contour1[min_cost_idx][1] reverse = contour1[min_cost_idx][2] - this_tolerance = min_cost / first_cost if first_cost else 1 if min_cost < first_cost * tolerance: # c0 is the first isomorphism of the m0 master @@ -37,22 +40,69 @@ def test_starting_point(glyph0, glyph1, ix, tolerance, matching): # pass. num_points = len(glyph1.points[ix]) - leeway = num_points // 4 - if proposed_point <= leeway or proposed_point >= num_points - leeway: + leeway = 3 + if not reverse and ( + proposed_point <= leeway or proposed_point >= num_points - leeway + ): # Try harder - contour0 = glyph0.isomorphismsNormalized[ix] - contour1 = glyph1.isomorphismsNormalized[matching[ix]] - c0 = contour0[0] - costs = [vdiff_hypot2_complex(c0[0], c1[0]) for c1 in contour1] - new_min_cost_idx, new_min_cost = min(enumerate(costs), key=lambda x: x[1]) - new_first_cost = costs[0] - new_this_tolerance = new_min_cost / new_first_cost if new_first_cost else 1 - if new_this_tolerance > this_tolerance: - proposed_point = contour1[new_min_cost_idx][1] - reverse = contour1[new_min_cost_idx][2] - this_tolerance = new_this_tolerance + # 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: + # Don't report this + # min_cost = first_cost + # reverse = False + # proposed_point = 0 # new_contour1[min_cost_idx][1] + pass + + this_tolerance = min_cost / first_cost if first_cost else 1 log.debug( "test-starting-point: tolerance %g", this_tolerance,