aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/fonttools/fontTools/varLib/interpolatableTestStartingPoint.py
blob: 9f742a14f52b70b49923f14d3fb1006f9f63cf2a (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
from .interpolatableHelpers import *


def test_starting_point(glyph0, glyph1, ix, tolerance, matching):
    if matching is None:
        matching = list(range(len(glyph0.isomorphisms)))
    contour0 = glyph0.isomorphisms[ix]
    contour1 = glyph1.isomorphisms[matching[ix]]
    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]

    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:
                # Don't report this
                # min_cost = first_cost
                # reverse = False
                # proposed_point = 0  # new_contour1[min_cost_idx][1]
                pass

    return proposed_point, reverse, min_cost, first_cost