def curve_pos_and_speed(curve, x): x1000 = x * 1000 for idx, (action, pts) in enumerate(curve.value): if action in ["moveTo", "endPath", "closePath"]: continue last_action, last_pts = curve.value[idx - 1] if action == "curveTo": o = -1 a = last_pts[-1] b, c, d = pts if x1000 == a[0]: o = a[1] / 1000 eb = a ec = b elif x1000 == d[0]: o = d[1] / 1000 eb = c ec = d elif x1000 > a[0] and x1000 < d[0]: e, f = splitCubic(a, b, c, d, x1000, isHorizontal=False) ez, ea, eb, ec = e o = ec[1] / 1000 else: continue tangent = math.degrees( math.atan2(ec[1] - eb[1], ec[0] - eb[0]) + math.pi * .5) #print(o, tangent) if tangent >= 90: t = (tangent - 90) / 90 else: t = tangent / 90 if o != -1: return o, t raise Exception("No curve value found!")
def intersect(glyph, where, isHorizontal): """ Intersection of a glyph with a horizontal or vertical line. Intersects each segment of a glyph using fontTools bezierTools.splitCubic and splitLine methods. """ pen = CollectSegmentsPen() glyph.draw(pen) nakedGlyph = pen.getSegments() glyphIntersections = [] for i, contour in enumerate(nakedGlyph): for segment in contour: length = len(segment) if length == 2: pt1, pt2 = segment returnedSegments = splitLine(pt1, pt2, where, int(isHorizontal)) elif length == 4: pt1, pt2, pt3, pt4 = segment returnedSegments = bezierTools.splitCubic(pt1, pt2, pt3, pt4, where, int(isHorizontal)) if len(returnedSegments) > 1: intersectionPoints = findDuplicatePoints(returnedSegments) if len(intersectionPoints): box = calcBounds(segment) intersectionPoints = [point for point in intersectionPoints if arrayTools.pointInRect(point, box)] glyphIntersections.extend(intersectionPoints) return glyphIntersections
def intersect(glyph, where, isHorizontal): """ Intersect a glyph with a horizontal or vertical line. Intersect each segment of a glyph using fontTools bezierTools.splitCubic and splitLine methods. """ pen = CollectSegmentsPen(glyph.layer) glyph.draw(pen) nakedGlyph = pen.getSegments() glyphIntersections = [] for i, contour in enumerate(nakedGlyph): for segment in contour: length = len(segment) if length == 2: pt1, pt2 = segment returnedSegments = splitLine(pt1, pt2, where, int(isHorizontal)) elif length == 4: pt1, pt2, pt3, pt4 = segment returnedSegments = bezierTools.splitCubic(pt1, pt2, pt3, pt4, where, int(isHorizontal)) if len(returnedSegments) > 1: intersectionPoints = findDuplicatePoints(returnedSegments) if len(intersectionPoints): box = calcBounds(segment) intersectionPoints = [point for point in intersectionPoints if arrayTools.pointInRect(point, box)] glyphIntersections.extend(intersectionPoints) return glyphIntersections
def append_point_coordinate(contour, rpoints, where, is_horizontal): """ Appends RPoint object to curve(RSegment object) by horizontal or vertical line. Args: contour:: RContour The RContour object that you want to add RPoint object. rpoints:: list A list of RPoint objects. It should be containing 4 RPoint objects like [startpoint, bcp(of startpoint), bcp(of endpoint), endpoint]. The order of start and end follows a index of contour.points. where:: int or float The coordinate value of line(x value if uses vertical line otherwise y value). is_horizontal:: bool If this is True, uses horizontal line(coordinate value of y). Otherwise uses vertical line(coordinate value of x). Raises: arguments value error:: ValueError If not found target segment in contour, raises this error. This can be occured when the rpoints(RPoint objects) are not in the contour.points. splitting error:: AssertionError If function of splitting is not done properly, raises this error. For example, if it split one line(or curve) but result is also one line(or curve). """ points = _r2t(rpoints) new_curve = splitCubic(points[0], points[1], points[2], points[3], where, is_horizontal) assert (len(new_curve) > 1) _append_point_curve(contour, rpoints, new_curve)
def test_splitCubic(): assert splitCubic( (0, 0), (25, 100), (75, 100), (100, 0), where=150, isHorizontal=False) == [((0, 0), (25, 100), (75, 100), (100, 0))] assert splitCubic( (0, 0), (25, 100), (75, 100), (100, 0), where=50, isHorizontal=False) == [((0, 0), (12.5, 50), (31.25, 75), (50, 75)), ((50, 75), (68.75, 75), (87.5, 50), (100, 0))] assert_curves_approx_equal( splitCubic((0, 0), (25, 100), (75, 100), (100, 0), where=25, isHorizontal=True), [((0, 0), (2.293792, 9.17517), (4.798045, 17.5085), (7.47414, 25)), ((7.47414, 25), (31.2886, 91.6667), (68.7114, 91.6667), (92.5259, 25)), ((92.5259, 25), (95.202, 17.5085), (97.7062, 9.17517), (100, 1.77636e-15))])
def test_splitCubic(): assert splitCubic( (0, 0), (25, 100), (75, 100), (100, 0), where=150, isHorizontal=False ) == [((0, 0), (25, 100), (75, 100), (100, 0))] assert splitCubic( (0, 0), (25, 100), (75, 100), (100, 0), where=50, isHorizontal=False ) == [((0, 0), (12.5, 50), (31.25, 75), (50, 75)), ((50, 75), (68.75, 75), (87.5, 50), (100, 0))] assert_curves_approx_equal( splitCubic( (0, 0), (25, 100), (75, 100), (100, 0), where=25, isHorizontal=True), [((0, 0), (2.293792, 9.17517), (4.798045, 17.5085), (7.47414, 25)), ((7.47414, 25), (31.2886, 91.6667), (68.7114, 91.6667), (92.5259, 25)), ((92.5259, 25), (95.202, 17.5085), (97.7062, 9.17517), (100, 1.77636e-15))])
def _seg_ending_at(self, x): x = clamp(self.minx, self.maxx, x) # Draw a line at pt.x through the warp; the pt on the warp is be the desired offset segments = splitCubic(*self.warp, x, False) # no kinks/overlaps; there should be <=2 curves when split # we are really just interested in there being only one unique start/end at pt.x assert len(segments) <= 2, f"Too many segments: {segments}" return segments[0]
def flagWarpVec(pt, accuracy=0.001): # only active between warp[0].x and warp[-1].x if pt.x < warp[0].x or pt.x > warp[-1].x: print("Out of bounds:", pt) return pt, Point(0, 0) # Draw a line at pt.x through the warp; the pt on the warp is be the desired offset segments = splitCubic(*warp, pt.x, False) # no kinks/overlaps; there should be <=2 curves when split # we are really just interested in there being only one unique start/end at pt.x assert len(segments) <= 2, f"Too many segments: {segments}" wpt = Point(*(segments[0][-1])) deriv_pt = cubic_deriv_pos(1, *(Point(*s) for s in segments[0])) return Point(0, wpt.y), deriv_pt
def splitWithAngle(curve, loc, axis): # Split a curve at location y and return the location and angle r = splitCubic(*curve, loc, isHorizontal=axis) if len(r) == 2: splitLoc = r[0][-1] a = getAngle(r[0][-1], r[1][1]) # handle to handle else: # The location is either exatly on the end of the curve, or off the curve # Return the angle and location either at the top or bottom if loc >= curve[0][axis]: splitLoc = curve[0] a = getAngle(curve[0], curve[1]) else: splitLoc = curve[-1] a = getAngle(curve[-2], curve[-1]) if axis == 0: a = 90 - a return splitLoc, a
def _curveToOne(self, pt1, pt2, pt3): hits = splitCubic(self.currentPt, pt1, pt2, pt3, self.value, self.isHorizontal) for i in range(len(hits) - 1): # a number of intersections is possible. Just take the # last point of each segment. if self.contourIndex not in self.hits: self.hits[self.contourIndex] = [] if self.isHorizontal: self.hits[self.contourIndex].append(round(hits[i][-1][0], 4)) else: self.hits[self.contourIndex].append(round(hits[i][-1][1], 4)) if self.isHorizontal and pt3[1] == self.value: # it could happen if self.contourIndex not in self.hits: self.hits[self.contourIndex] = [] self.hits[self.contourIndex].append(pt3[0]) if (not self.isHorizontal) and (pt3[0] == self.value): # it could happen if self.contourIndex not in self.hits: self.hits[self.contourIndex] = [] self.hits[self.contourIndex].append(pt3[1]) self.currentPt = pt3
def _curveToOne(self, pt1, pt2, pt3): hits = splitCubic(self.currentPt, pt1, pt2, pt3, self.value, self.isHorizontal) for i in range(len(hits) - 1): # a number of intersections is possible. Just take the # last point of each segment. if self.contourIndex not in self.hits: self.hits[self.contourIndex] = [] if self.isHorizontal: self.hits[self.contourIndex].append(round(hits[i][-1][0], 4)) else: self.hits[self.contourIndex].append(round(hits[i][-1][1], 4)) if self.isHorizontal and pt3[1] == self.value: # it could happen if self.contourIndex not in self.hits: self.hits[self.contourIndex] = [] self.hits[self.contourIndex].append(pt3[0]) if (not self.isHorizontal) and (pt3[0] == self.value): # it could happen if self.contourIndex not in self.hits: self.hits[self.contourIndex] = [] self.hits[self.contourIndex].append(pt3[1]) self.currentPt = pt3