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