def getWeightValue(t, curviness, axMin, axMax): curve = ((0,0), (0, H*curviness), (W,H-(H*curviness)), (W,H)) # slow to fast to slow (bell curve) split = splitCubicAtT(*curve, t) x, y = split[0][-1] # if t <= 0.75: if t <= 0.5: # Scale the y value to the range of 0 and 1, assuming it was in a range of 0 to 1000 f = x / W * 2 # f *= 2 value = interpolate(axMin, 800, f) else: # curve = ((W/2,H), (W/2,H-(H*curviness)), (W, H*curviness),(W,0)) # slow to fast to slow (bell curve) # curve = ((0,0), (0, H*curviness), (W/2,H-(H*curviness)), (W/2,H)) # slow to fast to slow (bell curve) split = splitCubicAtT(*curve, t) x, y = split[0][-1] # Scale the y value to the range of 0 and 1, assuming it was in a range of 0 to 1000 f = x / W # f = 1 - (f - 0.5) * 2 # reversed f = (f - 0.5) * 2 value = interpolate(800, axMax, f) return value, x, y
def test_splitCubicAtT(): assert splitCubicAtT( (0, 0), (25, 100), (75, 100), (100, 0), 0.5) == [((0, 0), (12.5, 50), (31.25, 75), (50, 75)), ((50, 75), (68.75, 75), (87.5, 50), (100, 0))] assert splitCubicAtT( (0, 0), (25, 100), (75, 100), (100, 0), 0.5, 0.75) == [((0, 0), (12.5, 50), (31.25, 75), (50, 75)), ((50, 75), (59.375, 75), (68.75, 68.75), (77.34375, 56.25)), ((77.34375, 56.25), (85.9375, 43.75), (93.75, 25), (100, 0))]
def test_splitCubicAtT(): assert splitCubicAtT( (0, 0), (25, 100), (75, 100), (100, 0), 0.5 ) == [((0, 0), (12.5, 50), (31.25, 75), (50, 75)), ((50, 75), (68.75, 75), (87.5, 50), (100, 0))] assert splitCubicAtT( (0, 0), (25, 100), (75, 100), (100, 0), 0.5, 0.75 ) == [((0, 0), (12.5, 50), (31.25, 75), (50, 75)), ((50, 75), (59.375, 75), (68.75, 68.75), (77.34375, 56.25)), ((77.34375, 56.25), (85.9375, 43.75), (93.75, 25), (100, 0))]
def get_extrema_points_vectors(roots, pt1, pt2, pt3, pt4): split_segments = [ p for p in splitCubicAtT(pt1, pt2, pt3, pt4, *roots)[:-1] ] points = [p[3] for p in split_segments] vectors = [get_vector(p[2], p[3]) for p in split_segments] return points, vectors
def append_point_rate(contour, rpoints, rate): """ Appends RPoint object to curve(RSegment object) by rate. 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. rate:: float A rate of location. It must be in [0, 1]. 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 = splitCubicAtT(points[0], points[1], points[2], points[3], rate) print("new_curve") assert (len(new_curve) > 1) _append_point_curve(contour, rpoints, new_curve)
def split(self, tValues): """ Split the segment according the t values """ if self.segmentType == "curve": on1 = self.previousOnCurve off1 = self.points[0].coordinates off2 = self.points[1].coordinates on2 = self.points[2].coordinates return bezierTools.splitCubicAtT(on1, off1, off2, on2, *tValues) elif self.segmentType == "line": segments = [] x1, y1 = self.previousOnCurve x2, y2 = self.points[0].coordinates dx = x2 - x1 dy = y2 - y1 pp = x1, y1 for t in tValues: np = (x1+dx*t, y1+dy*t) segments.append([pp, np]) pp = np segments.append([pp, (x2, y2)]) return segments elif self.segmentType == "qcurve": raise NotImplementedError else: raise NotImplementedError
def split(self, tValues): """ Split the segment according the t values """ if self.segmentType == "curve": on1 = self.previousOnCurve off1 = self.points[0].coordinates off2 = self.points[1].coordinates on2 = self.points[2].coordinates return bezierTools.splitCubicAtT(on1, off1, off2, on2, *tValues) elif self.segmentType == "line": segments = [] x1, y1 = self.previousOnCurve x2, y2 = self.points[0].coordinates dx = x2 - x1 dy = y2 - y1 pp = x1, y1 for t in tValues: np = (x1 + dx * t, y1 + dy * t) segments.append([pp, np]) pp = np segments.append([pp, x2, y2]) return segments elif self.segmentType == "qcurve": raise NotImplementedError else: raise NotImplementedError
def drawCurveWurst(self, p0, p1, p2, p3, radius, margin): if distance(p0, p3) < radius: return if p0 == p1 or p2 == p3: return s1 = splitCubicAtLength(p0, p1, p2, p3, radius+margin) s2 = 1-splitCubicAtLength(p3, p2, p1, p0, radius) curves = splitCubicAtT(p0, p1, p2, p3, s1, s2) p0, p1, p2, p3 = curves[1] dx1, dy1 = slope(p1, p0) dx2, dy2 = slope(p2, p3) n1 = normalise(dx1, dy1) n2 = normalise(dx2, dy2) m1 = n1[1], -n1[0] m2 = n2[1], -n2[0] cdistance = distance(p0, p3) path = self.getPath() newPath() start = offsetPoint(p0, m1, -radius) path.moveTo(start) self.drawWurstCap(path, p0, n1, m1, radius) self.drawWurstCurveSide(path, p0, p1, p2, p3, m1, m2, cdistance, radius) self.drawWurstCap(path, p3, n2, m2, radius) self.drawWurstCurveSide(path, p3, p2, p1, p0, m2, m1, cdistance, radius) path.closePath() drawPath()
def _testCurve(self, pts, flatPts, f0, f1, inRange=False): # Normalize and scale the BCPs scaled0 = (pts[1] - pts[0]) * f0 pts[1] = pts[1] + scaled0 dist1 = abs(self.pointClass(pts[3]).distance(pts[2])) scaled1 = (pts[3] - pts[2]) * f1 pts[2] = pts[2] + scaled1 # Split at a few locations splitFactors = [0.25, 0.5, 0.75] newSplit = splitCubicAtT(pts[0], pts[1], pts[2], pts[3], *splitFactors) newSplitLocs = [self.pointClass(pts[-1]) for pts in newSplit] newSplitLocs = newSplitLocs[:-1] # Measure these splits back to the flattened original segment # Prepare chunks of the flatPts, if we should be testing inRange # ...a factor of 0.25 should only be compared against 0.2-0.3, 0.5 should be 0.45-0.55, 0.75 should be 0.7-0.8 totalFlats = len(flatPts) flatRanges = [(int(totalFlats * 0.2), int(totalFlats * 0.3)), (int(totalFlats * 0.45), int(totalFlats * 0.55)), (int(totalFlats * 0.7), int(totalFlats * 0.8)) ] # These relate directly to the factors in splitFactors totalDiff = 0 for i, loc in enumerate(newSplitLocs): if inRange: # If it should only check a smaller range of flat points # (experimental) flatPtChunk = flatPts[flatRanges[i][0]:flatRanges[i][1]] else: flatPtChunk = flatPts closestLoc, closestDist = self.findClosest(loc, flatPtChunk) angle = loc.angle(self.pointClass(closestLoc)) expectedDistance = self.getThickness(angle) diff = expectedDistance - closestDist totalDiff += diff return pts, totalDiff
def easeCurve(f): curve = [(0, 0), (1000, 0), (0, 1000), (1000, 1000)] # ease in-out #curve = [(0, 0), (0, 900), (1000, 100), (1000, 1000)] # jump jump #curve = [(0, 0), (1000, 0), (1000, 0), (1000, 1000)] # ease in split = splitCubicAtT(curve[0], curve[1], curve[2], curve[3], f) split = split[0][-1][1] return split / 1000
def _curveToOne(self, pt1, pt2, pt3): if self.optimizeCurve: curves = splitCubicAtT(self.prevPoint, pt1, pt2, pt3, .5) else: curves = [(self.prevPoint, pt1, pt2, pt3)] for curve in curves: p1, h1, h2, p2 = curve self._processCurveToOne(h1, h2, p2)
def _addCubic(self, p0, p1, p2, p3): arch = distance(p0, p3) box = distance(p0, p1) + distance(p1, p2) + distance(p2, p3) if arch * self._mult >= box: self.value += (arch + box) * .5 else: for c in splitCubicAtT(p0, p1, p2, p3, .5): self._addCubic(*c)
def _addCubic(self, p0, p1, p2, p3): arch = _distance(p0, p3) box = _distance(p0, p1) + _distance(p1, p2) + _distance(p2, p3) if arch * self._mult >= box: self.value += (arch + box) * .5 else: for c in splitCubicAtT(p0,p1,p2,p3,.2,.4,.6,.8): self._addCubic(*c)
def splitCubicAtT_cached(a, b, c, d, t): global __split_cache abcdt = (a, b, c, d, t) sc = __split_cache.get(abcdt) if sc: return sc else: s = splitCubicAtT(a, b, c, d, t) __split_cache[abcdt] = s return s
def getAxisValue(t, curviness, axMin, axMax): curve = ((0, 0), (W * curviness, 0), (W - (W * curviness), H), (W, H)) # curve = ((0, 20), (-2689.98, 30), (-2000,H), (W,H)) split = splitCubicAtT(*curve, t) x, y = split[0][-1] # Scale the y value to the range of 0 and 1, assuming it was in a range of 0 to 1000 f = y / H # Use this value to interpolate between the start and end values on your axis value = interpolate(axMin, axMax, f) return value, x, y
def _getUnevenHandleShape(pt0, pt1, pt2, pt3, intersection, start, end, off): splitSegments = ftBezierTools.splitCubicAtT(pt0, pt1, pt2, pt3, *intersection.t) curves = [] for segment in splitSegments: if _roundPoint(segment[0]) != _roundPoint(start) and not curves: continue curves.append(segment[1:]) if _roundPoint(segment[-1]) == _roundPoint(end): break return curves + [off, start]
def split_curve(pts): p0, p1, p2, p3 = pts length_arc = calcCubicArcLength(p0, p1, p2, p3) if length_arc <= self.length: nc.append(["curveTo", pts[1:]]) else: d = self.length / length_arc b = (p0, p1, p2, p3) a, b = splitCubicAtT(*b, d) nc.append(["curveTo", a[1:]]) split_curve(b)
def _getUnevenHandleShape(pt0, pt1, pt2, pt3, intersection, start, end, off): # TODO: Here we use fontTools bezierTools splitSegments = rfBezierTools.splitCubicAtT(pt0, pt1, pt2, pt3, *intersection.t) curves = [] for segment in splitSegments: if _roundPoint(segment[0]) != _roundPoint(start) and not curves: continue curves.append(segment[1:]) if _roundPoint(segment[-1]) == _roundPoint(end): break return curves + [off, start]
def splitAtAngledExtremas(self, pt0, pt1, pt2, pt3): frontAngle = self.frontAngle segments = [] for i in range(101): t = i / 100 nx, ny = firstDerivative(pt0, pt1, pt2, pt3, t) tanAngle = atan2(ny, nx) if tan(frontAngle - _ANGLE_EPSILON) < tan(tanAngle) < tan(frontAngle + _ANGLE_EPSILON): newSegments = splitCubicAtT(pt0, pt1, pt2, pt3, t) if len(newSegments) > 1: segments = newSegments break return segments
def drawComplexCurves(self, contours, scale): impliedSCurveColor.set() for contourIndex, segments in contours.items(): for segment in segments: pt0, pt1, pt2, pt3 = segment path = NSBezierPath.bezierPath() path.moveToPoint_(pt0) path.curveToPoint_controlPoint1_controlPoint2_(pt3, pt1, pt2) path.setLineWidth_(3 * scale) path.setLineCapStyle_(NSRoundLineCapStyle) path.stroke() mid = splitCubicAtT(pt0, pt1, pt2, pt3, 0.5)[0][-1] drawString(mid, "Complex Path", 10, scale, impliedSCurveColor, backgroundColor=NSColor.whiteColor())
def split(self, tValues): """ Split the segment according the t values """ if self.segmentType == "curve": on1 = self.previousOnCurve off1 = self.points[0].coordinates off2 = self.points[1].coordinates on2 = self.points[2].coordinates return bezierTools.splitCubicAtT(on1, off1, off2, on2, *tValues) elif self.segmentType == "qcurve": raise NotImplementedError else: raise NotImplementedError
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 flattenCurve(curvePoints, steps=100): pts = [] for pt in curvePoints: pts += str(pt.x), str(pt.y) curvePointsName = "-".join(pts) if curvePointsName in flattenCache: return flattenCache[curvePointsName] else: flatPoints = [] for i in range(steps + 1): t = i / steps split = splitCubicAtT(*curvePoints, t) flatPoints.append(split[0][-1]) flattenCache[curvePointsName] = flatPoints return flatPoints
def visualizeWarp(initial_cubics, warp_cubic_fn, warp_pt_fn, num_seg=10, draw_ctls=False): warped_cubics = [] for cubic in initial_cubics: split_cubics = splitCubicAtT( *cubic, *(t / num_seg for t in range(0, num_seg + 1))) for split_cubic in split_cubics: split_cubic = tuple(Point(*t) for t in split_cubic) # naive; just move according to shift # in the end expecting control point movement to differ from start/end point warped_cubic = warp_cubic_fn(split_cubic) warped_cubics.append(warped_cubic) warped_cubics = tuple(warped_cubics) fill(None) stroke(0.5, 0.5, 0.5) strokeWidth(0.25) for cubic in initial_cubics: drawPath(bezOfCubics((cubic, ))) for warp_cubic in warped_cubics: fill(None) stroke(0.3, 0.3, 0.75) strokeWidth(0.5) drawPath(bezOfCubics((warp_cubic, ))) if draw_ctls: fill(None) stroke(0.6, 0.6, 0.6) strokeWidth(1) line(*warp_cubic[0:2]) line(*warp_cubic[2:4]) fill(0, 0, 0) stroke(None) dot(warp_cubic[0]) fill(None) stroke(0.75, 0.5, 0.75) strokeWidth(0.25) for cubic in initial_cubics: pts = tuple(warp_pt_fn(p) for p in points(cubic, 100)) for i in range(1, len(pts)): line(pts[i - 1], pts[i]) for pt in pts: dot(pt, radius=1)
def getSplits(self) -> list: # gets points that need to be added on selected curves collector: list = [] for cIndex, segIndexes in self.selectedCS.items(): for segIndex in segIndexes[::-1]: if self.g[cIndex][segIndex].type != "curve": continue segCoos = self.coos[cIndex][segIndex] segRotated = map(lambda x: self.rotatePoint(x), segCoos) extremes = self.getExtremes(segRotated) if len(extremes) == 1 and sum(extremes) in [0, 1]: # make more sofisticated conditions. Check if the splits themselves are of at least some length continue splits = splitCubicAtT(*segCoos, *extremes) splits = list(map(list, splits)) collector.append((cIndex, segIndex, splits)) return collector
def _splitAndInsertAtSegmentAndT(self, segmentIndex, t, insert): segments = self.segments segment = segments[segmentIndex] segment.insert(0, segments[segmentIndex - 1][-1]) firstPoint = segment[0] lastPoint = segment[-1] segmentType = lastPoint.segmentType segment = [(point.x, point.y) for point in segment] if segmentType == "line": (x1, y1), (x2, y2) = segment x = x1 + (x2 - x1) * t y = y1 + (y2 - y1) * t pointsToInsert = [((x, y), "line", False)] insertionPoint = (x, y) pointWillBeSmooth = False elif segmentType == "curve": pt1, pt2, pt3, pt4 = segment (pt1, pt2, pt3, pt4), (pt5, pt6, pt7, pt8) = bezierTools.splitCubicAtT(pt1, pt2, pt3, pt4, t) pointsToInsert = [(pt2, None, False), (pt3, None, False), (pt4, "curve", True), (pt6, None, False), (pt7, None, False)] insertionPoint = tuple(pt4) pointWillBeSmooth = True else: # XXX could be a quad. in that case, we could handle it. raise NotImplementedError("unknown segment type: %s" % segmentType) if insert: firstPointIndex = self._points.index(firstPoint) lastPointIndex = self._points.index(lastPoint) firstPoints = self._points[:firstPointIndex + 1] if firstPointIndex == len(self._points) - 1: firstPoints = firstPoints[lastPointIndex:] lastPoints = [] elif lastPointIndex == 0: lastPoints = [] else: lastPoints = self._points[lastPointIndex:] newPoints = [ self._pointClass(pos, segmentType=segmentType, smooth=smooth) for pos, segmentType, smooth in pointsToInsert ] self._points = firstPoints + newPoints + lastPoints self.dirty = True return insertionPoint, pointWillBeSmooth
def getValues(self,sender): glyph = CurrentGlyph() if glyph is not None: t = self.w.sliderVal.get() segmentPoints = self.returnSelectedPointsInSegment(glyph) if segmentPoints != None: self.w.sliderVal.enable(True) self.w.button.enable(True) r = bT.splitCubicAtT(segmentPoints[0],segmentPoints[1],segmentPoints[2],segmentPoints[3],t) UpdateCurrentGlyphView() return r
def splitSegment(self, index, t): segments = self._segments segment = segments[index] pts = segment.points pts_len = len(pts) if pts_len == 2: p1, p2 = pts p = Point(p1.x + (p2.x - p1.x) * t, p1.y + (p2.y - p1.y) * t, "line") self._points.insert(segment._start, p) newSegment = copy(segment) segments.insert(index, newSegment) for seg in segments[index + 1:]: seg._start += 1 seg._end += 1 elif pts_len == 4: # inline p1, p2, p3, p4 = [(p.x, p.y) for p in pts] (p1, p2, p3, p4), (p5, p6, p7, p8) = bezierTools.splitCubicAtT(p1, p2, p3, p4, t) points = self._points start = segment._start p = points[start] p.x, p.y = p6 p = points[start + 1] p.x, p.y = p7 p = points[start + 2] p.x, p.y = p8 points[start:start] = [ Point(*p2), Point(*p3), Point(*p4, "curve", smooth=True) ] newSegment = copy(segment) segments.insert(index, newSegment) for seg in segments[index + 1:]: seg._start += 3 seg._end += 3 else: raise ValueError("unattended len %d" % pts_len) return newSegment
def convertToQuadratic(p0, p1, p2, p3): # TODO: test for accuracy and subdivide further if needed p = [(i.x, i.y) for i in [p0, p1, p2, p3]] # if p[0][0] == p[1][0] and p[0][0] == p[2][0] and p[0][0] == p[2][0] and p[0][0] == p[3][0]: # return (p[0],p[1],p[2],p[3]) # if p[0][1] == p[1][1] and p[0][1] == p[2][1] and p[0][1] == p[2][1] and p[0][1] == p[3][1]: # return (p[0],p[1],p[2],p[3]) seg1, seg2 = bezierTools.splitCubicAtT(p[0], p[1], p[2], p[3], .5) pts1 = [array([i[0], i[1]]) for i in seg1] pts2 = [array([i[0], i[1]]) for i in seg2] on1 = seg1[0] on2 = seg2[3] try: off1 = calcIntersect(pts1[0], pts1[1], pts1[2], pts1[3]) off2 = calcIntersect(pts2[0], pts2[1], pts2[2], pts2[3]) except: return (p[0], p[1], p[2], p[3]) off1 = (on1 - off1) * OFFCURVE_VECTOR_CORRECTION + off1 off2 = (on2 - off2) * OFFCURVE_VECTOR_CORRECTION + off2 return (on1, off1, off2, on2)
def convertToQuadratic(p0,p1,p2,p3): # TODO: test for accuracy and subdivide further if needed p = [(i.x,i.y) for i in [p0,p1,p2,p3]] # if p[0][0] == p[1][0] and p[0][0] == p[2][0] and p[0][0] == p[2][0] and p[0][0] == p[3][0]: # return (p[0],p[1],p[2],p[3]) # if p[0][1] == p[1][1] and p[0][1] == p[2][1] and p[0][1] == p[2][1] and p[0][1] == p[3][1]: # return (p[0],p[1],p[2],p[3]) seg1,seg2 = bezierTools.splitCubicAtT(p[0], p[1], p[2], p[3], .5) pts1 = [array([i[0], i[1]]) for i in seg1] pts2 = [array([i[0], i[1]]) for i in seg2] on1 = seg1[0] on2 = seg2[3] try: off1 = calcIntersect(pts1[0], pts1[1], pts1[2], pts1[3]) off2 = calcIntersect(pts2[0], pts2[1], pts2[2], pts2[3]) except: return (p[0],p[1],p[2],p[3]) off1 = (on1 - off1) * OFFCURVE_VECTOR_CORRECTION + off1 off2 = (on2 - off2) * OFFCURVE_VECTOR_CORRECTION + off2 return (on1,off1,off2,on2)
def getCurveValue(t, curviness, axMin, axMax, loop="loop"): # curve = ((0,0), (W*curviness, 0), (W-(W*curviness),H), (W,H)) # fast to slow to fast (not sure?) curve = ((0,0), (0, H*curviness), (W,H-(H*curviness)), (W,H)) # slow to fast to slow, based on x over time (bell curve) # curve = ((0, 20), (-2689.98, 30), (-2000,H), (W,H)) split = splitCubicAtT(*curve, t) x, y = split[0][-1] # Scale the y value to the range of 0 and 1, assuming it was in a range of 0 to 1000 # f = y / H # for some reason, y isn't working as well for me as x to attain different curves... f = x / W # go up with curve for first half, then back down if loop is "loop": if t <= 0.5: f *= 2 else: f = 1 - (f - 0.5) * 2 value = interpolate(axMin, axMax, f) return value, x, y
def get_extrema_points_vectors(roots, pt1, pt2, pt3, pt4): split_segments = [p for p in splitCubicAtT(pt1, pt2, pt3, pt4, *roots)[:-1]] points = [p[3] for p in split_segments] vectors = [get_vector(p[2], p[3]) for p in split_segments] for s in split_segments: print " Split:", s save() stroke(None) fill(1, 0.6, 0, 0.8) for p in s: oval(p[0] - 1, p[1] - 1, 2, 2) fontSize(4) text("%i|%i" % p, p) #oval(s[-1][0] - 2, s[-1][1] - 2, 4, 4) fill(None) strokeWidth(0.5) stroke(1, 0.6, 0, 0.8) line(s[0], s[1]) line(s[2], s[3]) restore() return points, vectors
def getCurveValue(t, curviness, axMin, axMax, loop="loop"): from fontTools.misc.bezierTools import splitCubicAtT curve = ((0, 0), (0, H * curviness), (W, H - (H * curviness)), (W, H) ) # slow to fast to slow, based on x over time (bell curve) split = splitCubicAtT(*curve, t) x, y = split[0][-1] # Scale the y value to the range of 0 and 1, assuming it was in a range of 0 to 1000 f = x / W # go up with curve for first half, then back down if loop is "loop": if t <= 0.5: f *= 2 else: f = 1 - (f - 0.5) * 2 value = interpolate(axMin, axMax, f) # return value, x, y return value
def cubic_approx_spline(p, n): """Approximate a cubic bezier curve with a spline of n quadratics. Returns None if n is 1 and the cubic's control vectors are parallel, since no quadratic exists with this cubic's tangents. """ if n == 1: try: p1 = calc_intersect(p) except ValueError: return None return [p[0], p1, p[3]] spline = [p[0]] ts = [i / n for i in range(1, n)] segments = bezierTools.splitCubicAtT(p[0], p[1], p[2], p[3], *ts) for i in range(len(segments)): segment = cubic_approx(segments[i], i / (n - 1)) spline.append(segment[1]) spline.append(p[3]) return spline
def _splitAndInsertAtSegmentAndT(self, segmentIndex, t, insert): segments = self.segments segment = segments[segmentIndex] segment.insert(0, segments[segmentIndex-1][-1]) firstPoint = segment[0] lastPoint = segment[-1] segmentType = lastPoint.segmentType segment = [(point.x, point.y) for point in segment] if segmentType == "line": (x1, y1), (x2, y2) = segment x = x1 + (x2 - x1) * t y = y1 + (y2 - y1) * t pointsToInsert = [((x, y), "line", False)] insertionPoint = (x, y) pointWillBeSmooth = False elif segmentType == "curve": pt1, pt2, pt3, pt4 = segment (pt1, pt2, pt3, pt4), (pt5, pt6, pt7, pt8) = bezierTools.splitCubicAtT(pt1, pt2, pt3, pt4, t) pointsToInsert = [(pt2, None, False), (pt3, None, False), (pt4, "curve", True), (pt6, None, False), (pt7, None, False)] insertionPoint = tuple(pt4) pointWillBeSmooth = True else: # XXX could be a quad. in that case, we could handle it. raise NotImplementedError("unknown segment type: %s" % segmentType) if insert: firstPointIndex = self._points.index(firstPoint) lastPointIndex = self._points.index(lastPoint) firstPoints = self._points[:firstPointIndex + 1] if firstPointIndex == len(self._points) - 1: firstPoints = firstPoints[lastPointIndex:] lastPoints = [] elif lastPointIndex == 0: lastPoints = [] else: lastPoints = self._points[lastPointIndex:] newPoints = [self._pointClass(pos, segmentType=segmentType, smooth=smooth) for pos, segmentType, smooth in pointsToInsert] self._points = firstPoints + newPoints + lastPoints self.dirty = True return insertionPoint, pointWillBeSmooth
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 get_extrema_points_vectors(roots, pt1, pt2, pt3, pt4): split_segments = [ p for p in splitCubicAtT(pt1, pt2, pt3, pt4, *roots)[:-1] ] points = [p[3] for p in split_segments] vectors = [get_vector(p[2], p[3]) for p in split_segments] for s in split_segments: print " Split:", s save() stroke(None) fill(1, 0.6, 0, 0.8) for p in s: oval(p[0] - 1, p[1] - 1, 2, 2) fontSize(4) text("%i|%i" % p, p) #oval(s[-1][0] - 2, s[-1][1] - 2, 4, 4) fill(None) strokeWidth(0.5) stroke(1, 0.6, 0, 0.8) line(s[0], s[1]) line(s[2], s[3]) restore() return points, vectors
self.buildConnection() self.innerCurrentPoint = currentPoint - self.pointClass(cos(self.currentAngle), sin(self.currentAngle)) * thickness self.innerPen.lineTo(self.innerCurrentPoint) self.innerPrevPoint = self.innerCurrentPoint self.outerCurrentPoint = currentPoint + self.pointClass(cos(self.currentAngle), sin(self.currentAngle)) * thickness self.outerPen.lineTo(self.outerCurrentPoint) self.outerPrevPoint = self.outerCurrentPoint self.prevPoint = currentPoint self.prevAngle = self.currentAngle def _curveToOne(self, (x1, y1), (x2, y2), (x3, y3)): if self.optimizeCurve: curves = splitCubicAtT(self.prevPoint, (x1, y1), (x2, y2), (x3, y3), .5) else: curves = [(self.prevPoint, (x1, y1), (x2, y2), (x3, y3))] for curve in curves: p1, h1, h2, p2 = curve self._processCurveToOne(h1, h2, p2) def _processCurveToOne(self, (x1, y1), (x2, y2), (x3, y3)): if self.offset == 0: self.outerPen.curveTo((x1, y1), (x2, y2), (x3, y3)) self.innerPen.curveTo((x1, y1), (x2, y2), (x3, y3)) return self.originalPen.curveTo((x1, y1), (x2, y2), (x3, y3)) p1 = self.pointClass(x1, y1) p2 = self.pointClass(x2, y2)
def warpGlyph(g, angle=-40, floatingDist=120, axis=1): # axis = axis to warp along, 1 = use "y" axis for measurement if len(g.contours): # Get the bounds of the glyph drawing for the warp curve boundMax = g.bounds[axis + 2] boundMin = g.bounds[axis] """ # Optionally increase the bounds to include the BCP locations for c in g.contours: for pt in c.points: ptLoc = (pt.x, pt.y) if ptLoc[axis] > boundMax: boundMax = ptLoc[axis] if ptLoc[axis] < boundMin: boundMin = ptLoc[axis] """ # Find handle locations at 33.3 / 66.7 between these bounds p0 = [0, boundMax] p3 = [0, boundMin] p1y = interpolate(0.3333, boundMax, boundMin) p1x = getOpposite(angle, boundMax - p1y) p1 = [p1x, p1y] p2y = interpolate(0.6667, boundMax, boundMin) p2x = getOpposite(angle, p2y - boundMin) p2 = [p2x, p2y] if axis == 0: p0.reverse() p1.reverse() p2.reverse() p3.reverse() if False: # Draw the curve pen = g.getPen() pen.moveTo(p0) pen.curveTo(p1, p2, p3) pen.endPath() g.changed() # Shift this curve so that it's balanced at the floatingDist midPt = splitCubicAtT(p0, p1, p2, p3, 0.5)[0][-1][not axis] floatShift = int(round((-midPt * 0.5) + floatingDist)) p0[not axis] += floatShift p1[not axis] += floatShift p2[not axis] += floatShift p3[not axis] += floatShift warpCurve = [p0, p1, p2, p3] # Extrapolate the warp curve a little bit to account for any BCPs that are outside the glyph bounds pct = 0.2 # extrapolation percentage s = splitCubicAtT(*warpCurve, 1 + pct) # extrap a little on one end warpCurve = s[0] s = splitCubicAtT(*warpCurve, 0 - pct) # extrap a little on the other end warpCurve = s[1] # For each bpoint in the contour, find where it would it the curve # Move bPoint anchors to match the angle of that point on the curve. # Keep the "y" location of all points (including handles) as they move libData = {} for c in g.contours: # Take care of the first moveTo pt = c.points[0] ptLoc = (pt.x, pt.y) splitLoc, angle = splitWithAngle(warpCurve, ptLoc[axis], axis) anchorZ = splitLoc[not axis] if pt.name == None: pt.name = makeUniqueName() libData[pt.name] = int(round(anchorZ)) prevOnCurve = pt prevOnCurveLoc = [pt.x, pt.y] for s in c.segments: if len(s.points) == 3: # Split at the prevOnCurve to move this bcpOut bcpOut = absoluteBCP(prevOnCurve, s.points[0]) splitLoc, angle = splitWithAngle(warpCurve, prevOnCurveLoc[axis], axis) bcpOutZ = getOpposite( angle, bcpOut[axis]) + libData[prevOnCurve.name] # Split at the new on curve and move the bcpIn bcpIn = absoluteBCP(s.points[-1], s.points[1]) endAnchorLoc = (s.points[-1].x, s.points[-1].y) splitLoc, angle = splitWithAngle(warpCurve, endAnchorLoc[axis], axis) anchorZ = splitLoc[not axis] bcpInZ = getOpposite(angle, bcpIn[axis]) + anchorZ # Three points now have three "z" values # bcpOutZ, bcpInZ, anchorZ zLocs = [bcpOutZ, bcpInZ, anchorZ] for ptIdx, pt in enumerate(s.points): if pt.name == None: pt.name = makeUniqueName() libData[pt.name] = int(round(zLocs[ptIdx])) else: pt = s.points[0] ptLoc = (pt.x, pt.y) splitLoc, angle = splitWithAngle(warpCurve, ptLoc[axis], axis) anchorZ = splitLoc[not axis] if pt.name == None: pt.name = makeUniqueName() libData[pt.name] = int(round(anchorZ)) # Hold the previous onCurve for the next segment prevOnCurve = s.points[-1] prevOnCurveLoc = (prevOnCurve.x, prevOnCurve.y) if LIBKEY in g.lib.keys(): g.lib[LIBKEY].clear() g.lib[LIBKEY] = copy.deepcopy(libData) g.changed()
def get_extrema_points_vectors(roots, pt1, pt2, pt3, pt4): split_segments = [p for p in splitCubicAtT(pt1, pt2, pt3, pt4, *roots)[:-1]] points = [p[3] for p in split_segments] vectors = [get_vector(p[2], p[3]) for p in split_segments] return points, vectors