xref: /aosp_15_r20/external/fonttools/Lib/fontTools/varLib/interpolatableTestStartingPoint.py (revision e1fe3e4ad2793916b15cccdc4a7da52a7e1dd0e9)
1*e1fe3e4aSElliott Hughesfrom .interpolatableHelpers import *
2*e1fe3e4aSElliott Hughes
3*e1fe3e4aSElliott Hughes
4*e1fe3e4aSElliott Hughesdef test_starting_point(glyph0, glyph1, ix, tolerance, matching):
5*e1fe3e4aSElliott Hughes    if matching is None:
6*e1fe3e4aSElliott Hughes        matching = list(range(len(glyph0.isomorphisms)))
7*e1fe3e4aSElliott Hughes    contour0 = glyph0.isomorphisms[ix]
8*e1fe3e4aSElliott Hughes    contour1 = glyph1.isomorphisms[matching[ix]]
9*e1fe3e4aSElliott Hughes    m0Vectors = glyph0.greenVectors
10*e1fe3e4aSElliott Hughes    m1Vectors = [glyph1.greenVectors[i] for i in matching]
11*e1fe3e4aSElliott Hughes
12*e1fe3e4aSElliott Hughes    c0 = contour0[0]
13*e1fe3e4aSElliott Hughes    # Next few lines duplicated below.
14*e1fe3e4aSElliott Hughes    costs = [vdiff_hypot2_complex(c0[0], c1[0]) for c1 in contour1]
15*e1fe3e4aSElliott Hughes    min_cost_idx, min_cost = min(enumerate(costs), key=lambda x: x[1])
16*e1fe3e4aSElliott Hughes    first_cost = costs[0]
17*e1fe3e4aSElliott Hughes    proposed_point = contour1[min_cost_idx][1]
18*e1fe3e4aSElliott Hughes    reverse = contour1[min_cost_idx][2]
19*e1fe3e4aSElliott Hughes
20*e1fe3e4aSElliott Hughes    if min_cost < first_cost * tolerance:
21*e1fe3e4aSElliott Hughes        # c0 is the first isomorphism of the m0 master
22*e1fe3e4aSElliott Hughes        # contour1 is list of all isomorphisms of the m1 master
23*e1fe3e4aSElliott Hughes        #
24*e1fe3e4aSElliott Hughes        # If the two shapes are both circle-ish and slightly
25*e1fe3e4aSElliott Hughes        # rotated, we detect wrong start point. This is for
26*e1fe3e4aSElliott Hughes        # example the case hundreds of times in
27*e1fe3e4aSElliott Hughes        # RobotoSerif-Italic[GRAD,opsz,wdth,wght].ttf
28*e1fe3e4aSElliott Hughes        #
29*e1fe3e4aSElliott Hughes        # If the proposed point is only one off from the first
30*e1fe3e4aSElliott Hughes        # point (and not reversed), try harder:
31*e1fe3e4aSElliott Hughes        #
32*e1fe3e4aSElliott Hughes        # Find the major eigenvector of the covariance matrix,
33*e1fe3e4aSElliott Hughes        # and rotate the contours by that angle. Then find the
34*e1fe3e4aSElliott Hughes        # closest point again.  If it matches this time, let it
35*e1fe3e4aSElliott Hughes        # pass.
36*e1fe3e4aSElliott Hughes
37*e1fe3e4aSElliott Hughes        num_points = len(glyph1.points[ix])
38*e1fe3e4aSElliott Hughes        leeway = 3
39*e1fe3e4aSElliott Hughes        if not reverse and (
40*e1fe3e4aSElliott Hughes            proposed_point <= leeway or proposed_point >= num_points - leeway
41*e1fe3e4aSElliott Hughes        ):
42*e1fe3e4aSElliott Hughes            # Try harder
43*e1fe3e4aSElliott Hughes
44*e1fe3e4aSElliott Hughes            # Recover the covariance matrix from the GreenVectors.
45*e1fe3e4aSElliott Hughes            # This is a 2x2 matrix.
46*e1fe3e4aSElliott Hughes            transforms = []
47*e1fe3e4aSElliott Hughes            for vector in (m0Vectors[ix], m1Vectors[ix]):
48*e1fe3e4aSElliott Hughes                meanX = vector[1]
49*e1fe3e4aSElliott Hughes                meanY = vector[2]
50*e1fe3e4aSElliott Hughes                stddevX = vector[3] * 0.5
51*e1fe3e4aSElliott Hughes                stddevY = vector[4] * 0.5
52*e1fe3e4aSElliott Hughes                correlation = vector[5] / abs(vector[0])
53*e1fe3e4aSElliott Hughes
54*e1fe3e4aSElliott Hughes                # https://cookierobotics.com/007/
55*e1fe3e4aSElliott Hughes                a = stddevX * stddevX  # VarianceX
56*e1fe3e4aSElliott Hughes                c = stddevY * stddevY  # VarianceY
57*e1fe3e4aSElliott Hughes                b = correlation * stddevX * stddevY  # Covariance
58*e1fe3e4aSElliott Hughes
59*e1fe3e4aSElliott Hughes                delta = (((a - c) * 0.5) ** 2 + b * b) ** 0.5
60*e1fe3e4aSElliott Hughes                lambda1 = (a + c) * 0.5 + delta  # Major eigenvalue
61*e1fe3e4aSElliott Hughes                lambda2 = (a + c) * 0.5 - delta  # Minor eigenvalue
62*e1fe3e4aSElliott Hughes                theta = atan2(lambda1 - a, b) if b != 0 else (pi * 0.5 if a < c else 0)
63*e1fe3e4aSElliott Hughes                trans = Transform()
64*e1fe3e4aSElliott Hughes                # Don't translate here. We are working on the complex-vector
65*e1fe3e4aSElliott Hughes                # that includes more than just the points. It's horrible what
66*e1fe3e4aSElliott Hughes                # we are doing anyway...
67*e1fe3e4aSElliott Hughes                # trans = trans.translate(meanX, meanY)
68*e1fe3e4aSElliott Hughes                trans = trans.rotate(theta)
69*e1fe3e4aSElliott Hughes                trans = trans.scale(sqrt(lambda1), sqrt(lambda2))
70*e1fe3e4aSElliott Hughes                transforms.append(trans)
71*e1fe3e4aSElliott Hughes
72*e1fe3e4aSElliott Hughes            trans = transforms[0]
73*e1fe3e4aSElliott Hughes            new_c0 = (
74*e1fe3e4aSElliott Hughes                [complex(*trans.transformPoint((pt.real, pt.imag))) for pt in c0[0]],
75*e1fe3e4aSElliott Hughes            ) + c0[1:]
76*e1fe3e4aSElliott Hughes            trans = transforms[1]
77*e1fe3e4aSElliott Hughes            new_contour1 = []
78*e1fe3e4aSElliott Hughes            for c1 in contour1:
79*e1fe3e4aSElliott Hughes                new_c1 = (
80*e1fe3e4aSElliott Hughes                    [
81*e1fe3e4aSElliott Hughes                        complex(*trans.transformPoint((pt.real, pt.imag)))
82*e1fe3e4aSElliott Hughes                        for pt in c1[0]
83*e1fe3e4aSElliott Hughes                    ],
84*e1fe3e4aSElliott Hughes                ) + c1[1:]
85*e1fe3e4aSElliott Hughes                new_contour1.append(new_c1)
86*e1fe3e4aSElliott Hughes
87*e1fe3e4aSElliott Hughes            # Next few lines duplicate from above.
88*e1fe3e4aSElliott Hughes            costs = [
89*e1fe3e4aSElliott Hughes                vdiff_hypot2_complex(new_c0[0], new_c1[0]) for new_c1 in new_contour1
90*e1fe3e4aSElliott Hughes            ]
91*e1fe3e4aSElliott Hughes            min_cost_idx, min_cost = min(enumerate(costs), key=lambda x: x[1])
92*e1fe3e4aSElliott Hughes            first_cost = costs[0]
93*e1fe3e4aSElliott Hughes            if min_cost < first_cost * tolerance:
94*e1fe3e4aSElliott Hughes                # Don't report this
95*e1fe3e4aSElliott Hughes                # min_cost = first_cost
96*e1fe3e4aSElliott Hughes                # reverse = False
97*e1fe3e4aSElliott Hughes                # proposed_point = 0  # new_contour1[min_cost_idx][1]
98*e1fe3e4aSElliott Hughes                pass
99*e1fe3e4aSElliott Hughes
100*e1fe3e4aSElliott Hughes    this_tolerance = min_cost / first_cost if first_cost else 1
101*e1fe3e4aSElliott Hughes    log.debug(
102*e1fe3e4aSElliott Hughes        "test-starting-point: tolerance %g",
103*e1fe3e4aSElliott Hughes        this_tolerance,
104*e1fe3e4aSElliott Hughes    )
105*e1fe3e4aSElliott Hughes    return this_tolerance, proposed_point, reverse
106