def getExtremes(self, point=Tuple[int, ...]) -> list: # gets extremes of a cubic bezier curve pt1, pt2, pt3, pt4 = point (ax, ay), (bx, by), (cx, cy), _ = calcCubicParameters(pt1, pt2, pt3, pt4) ax3 = ax * 3.0 ay3 = ay * 3.0 bx2 = bx * 2.0 by2 = by * 2.0 collector: list = [] if self.horizontal: collector += [t for t in solveQuadratic(ax3, bx2, cx) if 0 <= t < 1] if self.vertical: collector += [t for t in solveQuadratic(ay3, by2, cy) if 0 <= t < 1] return sorted(collector)
def getExtremaForCubic(pt1, pt2, pt3, pt4, h=True, v=False): (ax, ay), (bx, by), c, d = calcCubicParameters(pt1, pt2, pt3, pt4) ax *= 3.0 ay *= 3.0 bx *= 2.0 by *= 2.0 h_roots = [] v_roots = [] if h: h_roots = [t for t in solveQuadratic(ay, by, c[1]) if 0 < t < 1] if v: v_roots = [t for t in solveQuadratic(ax, bx, c[0]) if 0 < t < 1] roots = h_roots + v_roots return [p[3] for p in splitCubicAtT(pt1, pt2, pt3, pt4, *roots)[:-1]]
def _tValueForPointOnQuadCurve(point, pts, isHorizontal=0): quadSegments = decomposeQuadraticSegment(pts[1:]) previousOnCurve = pts[0] solutionsDict = dict() for index, (pt1, pt2) in enumerate(quadSegments): a, b, c = bezierTools.calcQuadraticParameters(previousOnCurve, pt1, pt2) subSolutions = bezierTools.solveQuadratic( a[isHorizontal], b[isHorizontal], c[isHorizontal] - point[isHorizontal]) subSolutions = [t for t in subSolutions if 0 <= t < 1] for t in subSolutions: solutionsDict[(t, index)] = _getQuadPoint(t, previousOnCurve, pt1, pt2) previousOnCurve = pt2 solutions = solutionsDict.keys() if not solutions and not isHorizontal: return _tValueForPointOnQuadCurve(point, pts, isHorizontal=1) if len(solutions) > 1: intersectionLenghts = {} for t in solutions: tp = solutionsDict[t] dist = _distance(tp, point) intersectionLenghts[dist] = t minDist = min(intersectionLenghts.keys()) solutions = [intersectionLenghts[minDist]] return solutions
def getExtremaForCubic(pt1, pt2, pt3, pt4, h=True, v=False): (ax, ay), (bx, by), c, d = calcCubicParameters(pt1, pt2, pt3, pt4) ax *= 3.0 ay *= 3.0 bx *= 2.0 by *= 2.0 points = [] vectors = [] if h: roots = [t for t in solveQuadratic(ay, by, c[1]) if 0 < t < 1] points, vectors = get_extrema_points_vectors(roots, pt1, pt2, pt3, pt4) if v: roots = [t for t in solveQuadratic(ax, bx, c[0]) if 0 < t < 1] v_points, v_vectors = get_extrema_points_vectors(roots, pt1, pt2, pt3, pt4) points += v_points vectors += v_vectors return points, vectors
def getExtremaForCubic(pt1, pt2, pt3, pt4, h=True, v=False): (ax, ay), (bx, by), c, d = calcCubicParameters(pt1, pt2, pt3, pt4) ax *= 3.0 ay *= 3.0 bx *= 2.0 by *= 2.0 points = [] vectors = [] if h: roots = [t for t in solveQuadratic(ay, by, c[1]) if 0 < t < 1] points, vectors = get_extrema_points_vectors(roots, pt1, pt2, pt3, pt4) if v: roots = [t for t in solveQuadratic(ax, bx, c[0]) if 0 < t < 1] v_points, v_vectors = get_extrema_points_vectors( roots, pt1, pt2, pt3, pt4) points += v_points vectors += v_vectors return points, vectors
def getExtremaForCubic( pt0: Point, pt1: Point, pt2: Point, pt3: Point, h: bool = True, v: bool = False, include_start_end: bool = False, ) -> List[float]: """ Return a list of t values at which the cubic curve defined by pt0, pt1, pt2, pt3 has extrema. :param h: Calculate extrema for horizontal derivative == 0 (= what type designers call vertical extrema!). :type h: bool :param v: Calculate extrema for vertical derivative == 0 (= what type designers call horizontal extrema!). :type v: bool :param include_start_end: Also calculate extrema that lie at the start or end point of the curve. :type include_start_end: bool """ (ax, ay), (bx, by), c, _d = calcCubicParameters(pt0, pt1, pt2, pt3) ax *= 3.0 ay *= 3.0 bx *= 2.0 by *= 2.0 roots = [] if include_start_end: if h: roots = [t for t in solveQuadratic(ay, by, c[1]) if 0 <= t <= 1] if v: roots += [t for t in solveQuadratic(ax, bx, c[0]) if 0 <= t <= 1] else: if h: roots = [t for t in solveQuadratic(ay, by, c[1]) if 0 < t < 1] if v: roots += [t for t in solveQuadratic(ax, bx, c[0]) if 0 < t < 1] return roots
def split_at_extrema(pt1, pt2, pt3, pt4, transform=Transform()): """ Add extrema to a cubic curve, after applying a transformation. Example :: >>> # A segment in which no extrema will be added. >>> split_at_extrema((297, 52), (406, 52), (496, 142), (496, 251)) [((297, 52), (406, 52), (496, 142), (496, 251))] >>> from fontTools.misc.transform import Transform >>> split_at_extrema((297, 52), (406, 52), (496, 142), (496, 251), Transform().rotate(-27)) [((297.0, 52.0), (84.48072108963274, -212.56513799170233), (15.572491694678519, -361.3686192413668), (15.572491694678547, -445.87035970621713)), ((15.572491694678547, -445.8703597062171), (15.572491694678554, -506.84825401175414), (51.4551516055374, -534.3422304091257), (95.14950889754756, -547.6893014808263))] """ # Transform the points for extrema calculation; # transform is expected to rotate the points by - nib angle. t2, t3, t4 = transform.transformPoints([pt2, pt3, pt4]) # When pt1 is the current point of the path, it is already transformed, so # we keep it like it is. t1 = pt1 (ax, ay), (bx, by), c, d = calcCubicParameters(t1, t2, t3, t4) ax *= 3.0 ay *= 3.0 bx *= 2.0 by *= 2.0 # vertical roots = [t for t in solveQuadratic(ay, by, c[1]) if 0 < t < 1] # horizontal roots += [t for t in solveQuadratic(ax, bx, c[0]) if 0 < t < 1] # Use only unique roots and sort them # They should be unique before, or can a root be duplicated (in h and v?) roots = sorted(set(roots)) if not roots: return [(t1, t2, t3, t4)] return splitCubicAtT(t1, t2, t3, t4, *roots)
def _qCurveToOne_unfinished(self, bcp, point): # XXX need to finish this, for now doing it through a cubic # (BasePen implements _qCurveTo in terms of a cubic) will # have to do. x, y = self.testPoint x1, y1 = self._getCurrentPoint() x2, y2 = bcp x3, y3 = point c = y1 b = (y2 - c) * 2.0 a = y3 - c - b solutions = sorted(solveQuadratic(a, b, c - y)) solutions = [t for t in solutions if ZERO_MINUS_EPSILON <= t <= ONE_PLUS_EPSILON] if not solutions: return
def qcurveIntersections(x1, y1, x2, y2, *pts): """ Computes intersection between a cubic spline and a line segment. Adapted from: https://www.particleincell.com/2013/cubic-line-intersection/ Takes four defcon points describing curve and four scalars describing line parameters. """ sol = [] # PACK for fontTools points = [] for pt in pts: points.append((pt.x, pt.y)) p1 = (pts[0].x, pts[0].y) for p2, p3 in decomposeQuadraticSegment(points[1:]): bx, by = (y1 - y2), (x2 - x1) m = x1 * y2 - x2 * y1 a, b, c = bezierTools.calcQuadraticParameters(p1, p2, p3) # prepare for next turn p1 = p3 pc0 = bx * a[0] - by * a[1] pc1 = (bx * b[0] + by * b[1]) / pc0 pc2 = (bx * c[0] + by * c[1] + m) / pc0 r = bezierTools.solveQuadratic(pc0, pc1, pc2) for t in r: if t < 0 or t > 1: continue s0 = a[0] * (1 - t) ** 2 + b[0] * 2 * t * (1 - t) + c[0] * t ** 2 s1 = a[1] * (1 - t) ** 2 + b[1] * 2 * t * (1 - t) + c[1] * t ** 2 if (x2 - x1) != 0: s = (s0 - x1) / (x2 - x1) else: s = (s1 - y1) / (y2 - y1) if s < 0 or s > 1: continue sol.append((s0, s1, t)) return sol
def _tValueForPointOnQuadCurve(point, pts, isHorizontal=0): quadSegments = decomposeQuadraticSegment(pts[1:]) previousOnCurve = pts[0] solutionsDict = dict() for index, (pt1, pt2) in enumerate(quadSegments): a, b, c = bezierTools.calcQuadraticParameters(previousOnCurve, pt1, pt2) subSolutions = bezierTools.solveQuadratic(a[isHorizontal], b[isHorizontal], c[isHorizontal] - point[isHorizontal]) subSolutions = [t for t in subSolutions if 0 <= t < 1] for t in subSolutions: solutionsDict[(t, index)] = _getQuadPoint(t, previousOnCurve, pt1, pt2) previousOnCurve = pt2 solutions = list(solutionsDict.keys()) if not solutions and not isHorizontal: return _tValueForPointOnQuadCurve(point, pts, isHorizontal=1) if len(solutions) > 1: intersectionLenghts = {} for t in solutions: tp = solutionsDict[t] dist = _distance(tp, point) intersectionLenghts[dist] = t minDist = min(intersectionLenghts.keys()) solutions = [intersectionLenghts[minDist]] return solutions