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] 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] proposed_point = contour1[min_cost_idx][1] reverse = contour1[min_cost_idx][2] if min_cost < first_cost * tolerance: # 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. 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] if correlation: correlation /= 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, ) return this_tolerance, proposed_point, reverse