Example #1
0
    def windingNumberOfPoint(self, pt):
        bounds = self.bounds()
        bounds.addMargin(10)
        ray1 = Line(Point(bounds.left, pt.y), pt)
        ray2 = Line(Point(bounds.right, pt.y), pt)
        leftIntersections = {}
        rightIntersections = {}
        leftWinding = 0
        rightWinding = 0
        for s in self.asSegments():
            for i in s.intersections(ray1):
                # print("Found left intersection with %s: %s" % (ray1, i.point))
                leftIntersections[i.point] = i

            for i in s.intersections(ray2):
                rightIntersections[i.point] = i

        for i in leftIntersections.values():
            # XXX tangents here are all positive? Really?
            # print(i.seg1, i.t1, i.point)
            tangent = s.tangentAtTime(i.t1)
            # print("Tangent at left intersection %s is %f" % (i.point,tangent.y))
            leftWinding += int(math.copysign(1, tangent.y))

        for i in rightIntersections.values():
            tangent = s.tangentAtTime(i.t1)
            # print("Tangent at right intersection %s is %f" % (i.point,tangent.y))
            rightWinding += int(math.copysign(1, tangent.y))

        # print("Left winding: %i right winding: %i " % (leftWinding,rightWinding))
        return max(abs(leftWinding), abs(rightWinding))
Example #2
0
    def thicknessAtX(path, x):
        """Returns the thickness of the path at x-coordinate ``x``."""
        bounds = path.bounds()
        bounds.addMargin(10)
        ray = Line(Point(x - 0.1, bounds.bottom), Point(x + 0.1, bounds.top))
        intersections = []
        for seg in path.asSegments():
            intersections.extend(seg.intersections(ray))
        if len(intersections) < 2:
            return None
        intersections = list(sorted(intersections, key=lambda i: i.point.y))
        i1, i2 = intersections[0:2]
        inorm1 = i1.seg1.normalAtTime(i1.t1)
        ray1 = Line(i1.point + (inorm1 * 1000), i1.point + (inorm1 * -1000))
        iii = i2.seg1.intersections(ray1)
        if iii:
            ll1 = i1.point.distanceFrom(iii[0].point)
        else:
            # Simple, vertical version
            return abs(i1.point.y - i2.point.y)

        inorm2 = i2.seg1.normalAtTime(i2.t1)
        ray2 = Line(i2.point + (inorm2 * 1000), i2.point + (inorm2 * -1000))
        iii = i1.seg1.intersections(ray2)
        if iii:
            ll2 = i2.point.distanceFrom(iii[0].point)
            return (ll1 + ll2) * 0.5
        else:
            return ll1
Example #3
0
 def flatten(self, degree=8):
   samples = self.regularSample(self.length/degree)
   ss = []
   for i in range(1,len(samples)):
     l = Line(samples[i-1], samples[i])
     l._orig = self
     ss.append(l)
   return ss
 def flatten(self, degree=8):
     samples = self.sample(self.length / degree)
     ss = []
     for i in range(1, len(samples)):
         l = Line(samples[i - 1], samples[i])
         l._orig = self
         ss.append(l)
     return ss
Example #5
0
    def drawWithBrush(self, other):
        """Assuming that `other` is a closed Bezier path representing a pen or
    brush of a certain shape and that `self` is an open path, this method
    traces the brush along the path, returning an array of Bezier paths.

    `other` may also be a function which, given a time `t` (0-1), returns a closed
    path representing the shape of the brush at the given time.

    This requires the `shapely` library to be installed.
    """
        from shapely.geometry import Polygon
        from shapely.ops import unary_union
        polys = []
        samples = self.sample(self.length / 2)

        def constantBrush(t):
            return other

        brush = other
        if not callable(brush):
            brush = constantBrush

        c = brush(0).centroid

        from itertools import tee

        def pairwise(iterable):
            "s -> (s0,s1), (s1,s2), (s2, s3), ..."
            a, b = tee(iterable)
            next(b, None)
            return zip(a, b)

        t = 0
        for n in samples:
            brushHere = brush(t).clone().flatten()
            brushHere.translate(n - brushHere.centroid)
            polys.append(
                Polygon([(x[0].x, x[0].y) for x in brushHere.asSegments()]))
            t = t + 1.0 / len(samples)
        concave_hull = unary_union(polys)
        ll = []
        for x, y in pairwise(concave_hull.exterior.coords):
            l = Line(Point(x[0], x[1]), Point(y[0], y[1]))
            ll.append(l)
        paths = [BezierPath.fromSegments(ll)]

        for interior in concave_hull.interiors:
            ll = []
            for x, y in pairwise(interior.coords):
                l = Line(Point(x[0], x[1]), Point(y[0], y[1]))
                ll.append(l)
            paths.append(BezierPath.fromSegments(ll))

        return paths
Example #6
0
 def tunniPoint(self):
   """Returns the Tunni point of this Bezier (the intersection of
   the handles)."""
   h1 = Line(self[0], self[1])
   h2 = Line(self[2], self[3])
   i = h1.intersections(h2, limited = False)
   if len(i)<1: return
   i = i[0].point
   if i.distanceFrom(self[0]) > 5 * self.length:
     return
   else:
     return i
Example #7
0
 def tunniPoint(self):
     """Returns the Tunni point of this Bezier (the intersection of
 the handles)."""
     h1 = Line(self[0], self[1])
     h2 = Line(self[2], self[3])
     i = h1.intersections(h2, limited=False)
     if len(i) < 1: return
     i = i[0].point
     if i.distanceFrom(self[0]) > 5 * self.length:
         return
     else:
         return i
def Rectangle(width, height, origin=None):
    """Returns a path representing an rectangle of given width and height.
  You can specify the `origin` as a Point."""
    if not origin:
        origin = Point(0, 0)
    tl = origin + west * width / 2.0 + north * height / 2.0
    tr = origin + east * width / 2.0 + north * height / 2.0
    bl = origin + west * width / 2.0 + south * height / 2.0
    br = origin + east * width / 2.0 + south * height / 2.0

    return BezierPath.fromSegments(
        [Line(tl, tr), Line(tr, br),
         Line(br, bl), Line(bl, tl)])
Example #9
0
 def harmonize(self, seg1, seg2):
     if seg1.end.x != seg2.start.x or seg1.end.y != seg2.start.y: return
     a1, a2 = seg1[1], seg1[2]
     b1, b2 = seg2[1], seg2[2]
     intersections = Line(a1, a2).intersections(Line(b1, b2), limited=False)
     if not intersections[0]: return
     p0 = a1.distanceFrom(a2) / a2.distanceFrom(intersections[0].point)
     p1 = b1.distanceFrom(intersections[0].point) / b1.distanceFrom(b2)
     r = math.sqrt(p0 * p1)
     t = r / (r + 1)
     newA3 = a2.lerp(b1, t)
     fixup = seg2.start - newA3
     seg1[2] += fixup
     seg2[1] += fixup
 def test_cubic_line_2(self):
     s1 = CubicBezier.fromRepr(
         "B<<584.0,126.03783241124995>-<402.0,163.0378324112499>-<220.00000000000003,200.03783241124995>-<38.0,237.03783241124995>>"
     ),
     ray = Line.fromRepr(
         "L<<357.4,-9.99999999999999>--<357.6,250.2692949284206>>")
     assert (s1[0].intersections(ray))
Example #11
0
    def append(self, other, joinType="line"):
        """Append another path to this one. If the end point of the first
    path is not the same as the start point of the other path, a line
    will be drawn between them."""
        segs1 = self.asSegments()
        segs2 = other.asSegments()
        if len(segs1) < 1:
            self.activeRepresentation = SegmentRepresentation(self, segs2)
            return
        if len(segs2) < 1:
            self.activeRepresentation = SegmentRepresentation(self, segs1)
            return

        # Which way around should they go?
        dist1 = segs1[-1].end.distanceFrom(segs2[0].start)
        dist2 = segs1[-1].end.distanceFrom(segs2[-1].end)
        if dist2 > 2 * dist1:
            segs2 = list(reversed([x.reversed() for x in segs2]))

        # Add a line between if they don't match up
        if segs1[-1].end != segs2[0].start:
            segs1.append(Line(segs1[-1].end, segs2[0].start))

        # XXX Check for discontinuities and harmonize if needed

        segs1.extend(segs2)
        self.activeRepresentation = SegmentRepresentation(self, segs1)
        return self
Example #12
0
 def appendSegment(self, seg):
     seg = [Point(n[0], n[1]) for n in seg]
     if len(seg) == 2:
         self.segments.append(Line(*seg))
     elif len(seg) == 3:
         self.segments.append(QuadraticBezier(*seg))
     elif len(seg) == 4:
         self.segments.append(CubicBezier(*seg))
     else:
         raise ValueError("Unknown segment type")
Example #13
0
def thickness_at_x(path, x):
    """Find the path thickness at a given X coordinate

    This measure the thickness of the lowest horizontal stem at the given
    coordinate. If there is no stem at this X coordinate, ``None`` is
    returned.

    Args:
        path: A ``beziers.path.BezierPath`` object
        x: X coordinate to search

    Returns:
        The thickness of the path at this point, in font units.
    """
    bounds = path.bounds()
    bounds.addMargin(10)
    ray = Line(Point(x - 0.1, bounds.bottom), Point(x + 0.1, bounds.top))
    intersections = []
    for seg in path.asSegments():
        intersections.extend(seg.intersections(ray))
    if len(intersections) < 2:
        return None
    intersections = list(sorted(intersections, key=lambda i: i.point.y))
    i1, i2 = intersections[0:2]
    inorm1 = i1.seg1.normalAtTime(i1.t1)
    ray1 = Line(i1.point + (inorm1 * 1000), i1.point + (inorm1 * -1000))
    iii = i2.seg1.intersections(ray1)
    if iii:
        ll1 = i1.point.distanceFrom(iii[0].point)
    else:
        # Simple, vertical version
        return abs(i1.point.y - i2.point.y)

    inorm2 = i2.seg1.normalAtTime(i2.t1)
    ray2 = Line(i2.point + (inorm2 * 1000), i2.point + (inorm2 * -1000))
    iii = i1.seg1.intersections(ray2)
    if iii:
        ll2 = i2.point.distanceFrom(iii[0].point)
        return (ll1 + ll2) * 0.5
    else:
        return ll1
 def test_cubic_line(self):
     q = CubicBezier(Point(100, 240), Point(30, 60), Point(210, 230),
                     Point(160, 30))
     l = Line(Point(25, 260), Point(230, 20))
     path = BezierPath()
     path.closed = False
     path.activeRepresentation = SegmentRepresentation(path, [q])
     i = q.intersections(l)
     self.assertEqual(len(i), 3)
     self.assertEqual(i[0].point, q.pointAtTime(0.117517031451))
     self.assertEqual(i[1].point, q.pointAtTime(0.518591792307))
     self.assertEqual(i[2].point, q.pointAtTime(0.867886610031))
Example #15
0
 def get_yb_clearance(self, parser, bariye):
     font = parser.font
     paths = get_bezier_paths(font, bariye)
     path = paths[0]
     bounds = path.bounds()
     x_of_tail = get_rise(font.font, bariye)
     ray = Line(
         Point(x_of_tail - 0.1, bounds.bottom - 5),
         Point(x_of_tail + 0.1, bounds.top + 5),
     )
     intersections = []
     for seg in path.asSegments():
         intersections.extend(seg.intersections(ray))
     intersections = list(sorted(intersections, key=lambda i: i.point.y))
     i = intersections[-1]
     return i.point.y
Example #16
0
def xheight_intersections(ttFont, glyph):
    glyphset = ttFont.getGlyphSet()
    if glyph not in glyphset:
        return []

    paths = BezierPath.fromFonttoolsGlyph(ttFont, glyph)
    if len(paths) != 1:
        return []
    path = paths[0]

    xheight = ttFont["OS/2"].sxHeight

    bounds = path.bounds()
    bounds.addMargin(10)
    ray = Line(Point(bounds.left, xheight), Point(bounds.right, xheight))
    intersections = []
    for seg in path.asSegments():
        intersections.extend(seg.intersections(ray))
    return sorted(intersections, key=lambda i: i.point.x)
Example #17
0
 def bestcut(self, args=None):
     """ Find the best line that cuts this octabox and its segments so that the
         resulting two bounding octaboxes (of the two sets of segments) is minimised."""
     currbest = OctaScore(self, None, self.area)
     for x, d in enumerate(((1, 0), (0, 1), (-1, 1), (1, 1))):
         splitline = Line(Point(d[0] * self.xi, d[1] * self.yi),
                          Point(d[0] * self.xa, d[1] * self.ya))
         for sl in findshifts(self.segs, splitline):
             r, l = splitWith(self.segs, sl)
             rightbox = Octabox(r)
             leftbox = Octabox(l)
             score = rightbox.area + leftbox.area
             if args is not None and args.detail & 8:
                 print("    {}:L[{}, {}], R[{}, {}]".format(
                     "xysd"[x], leftbox.area,
                     sum(s.area for s in leftbox.segs), rightbox.area,
                     sum(s.area for s in rightbox.segs)))
             if score < currbest.score:
                 currbest = OctaScore(leftbox, rightbox, score)
     return currbest
Example #18
0
 def test_intercept(self):
   l = Line(Point(0,10),Point(20,20.4))
   self.assertAlmostEqual(l.intercept, 10)
Example #19
0
 def test_slope(self):
   l = Line(Point(0,10),Point(20,20.4))
   self.assertAlmostEqual(l.slope, 0.52)
    def clip(self, clip, cliptype, flat=False):
        splitlist1 = []
        splitlist2 = []
        intersections = {}
        cloned = self.clone()
        clip = clip.clone()

        # Split all segments at intersections
        for s1 in self.asSegments():
            for s2 in clip.asSegments():
                for i in s1.intersections(s2):
                    if i.t1 > 1e-8 and i.t1 < 1 - 1e-8:
                        if i.seg1 == s1:
                            splitlist1.append((i.seg1, i.t1))
                            splitlist2.append((i.seg2, i.t2))
                        else:
                            splitlist2.append((i.seg1, i.t1))
                            splitlist1.append((i.seg2, i.t2))
                        intersections[i.point] = i

        logging.debug("Split list: %s" % splitlist1)
        logging.debug("Split list 2: %s" % splitlist2)
        cloned.splitAtPoints(splitlist1)
        clip.splitAtPoints(splitlist2)
        logging.debug("Self:")
        logging.debug(cloned.asSegments())
        logging.debug("Clip:")
        logging.debug(clip.asSegments())

        segs1unflattened = cloned.asSegments()
        segs2unflattened = clip.asSegments()

        # Replace with flattened versions, building a dictionary of originals
        segs1 = []
        reconstructionLUT = {}
        precision = 100.

        def fillLUT(flats):
            for line in flats:
                key = ((line.start * precision).rounded(),
                       (line.end * precision).rounded())
                reconstructionLUT[key] = (line._orig or line)
                key2 = ((line.end * precision).rounded(),
                        (line.start * precision).rounded())
                reconstructionLUT[key2] = (line._orig or line).reversed()

        for s in segs1unflattened:
            flats = s.flatten(2)
            fillLUT(flats)
            segs1.extend(flats)

        segs2 = []
        for s in segs2unflattened:
            flats = s.flatten(2)
            fillLUT(flats)
            segs2.extend(flats)

        # Leave it to the professionals
        subj = [(s[0].x * precision, s[0].y * precision) for s in segs1]
        clip = [(s[0].x * precision, s[0].y * precision) for s in segs2]
        pc = pyclipper.Pyclipper()
        pc.AddPath(clip, pyclipper.PT_CLIP, True)
        pc.AddPath(subj, pyclipper.PT_SUBJECT, True)
        paths = pc.Execute(cliptype, pyclipper.PFT_EVENODD,
                           pyclipper.PFT_EVENODD)
        outpaths = []

        # Now reconstruct Bezier segments from flattened paths
        def pairwise(points):
            a = (p for p in points)
            b = (p for p in points)
            next(b)
            for curpoint, nextpoint in zip(a, b):
                yield curpoint, nextpoint

        newpaths = []
        from beziers.path import BezierPath
        for p in paths:
            newpath = []
            for scaledstart, scaledend in pairwise(p):
                key = (Point(*scaledstart), Point(*scaledend))
                if key in reconstructionLUT and not flat:
                    orig = reconstructionLUT[key]
                    if len(newpath) == 0 or newpath[-1] != orig:
                        newpath.append(orig)
                else:
                    newpath.append(Line(key[0] / precision,
                                        key[1] / precision))
            outpaths.append(BezierPath.fromSegments(newpath))
        return outpaths
 def test_cubic_line_3(self):
     seg = CubicBezier.fromRepr(
         "B<<320.0,454.0>-<277.0,454.0>-<230.0,439.0>-<189.0,417.0>>")
     ray = Line.fromRepr(
         "L<<254.5,221.5>--<254.5000000000001,887.6681469418963>>")
     assert seg.intersections(ray)
Example #22
0
        # Subdivision
        r1 = self.minDist(uinterval=(umin, newuMid), vinterval=(vmin, newvMid))
        r2 = self.minDist(uinterval=(umin, newuMid), vinterval=(newvMid, vmax))
        r3 = self.minDist(uinterval=(newuMid, umax), vinterval=(vmin, newvMid))
        r4 = self.minDist(uinterval=(newuMid, umax), vinterval=(newvMid, vmax))
        results = min([r1, r2, r3, r4], key=lambda x: x[0])
        return results


def curveDistance(bez1, bez2):
    """Find the distance between two curves."""
    c = MinimumCurveDistanceFinder(bez1, bez2)
    dist, t1, t2 = c.minDist()
    return math.sqrt(dist), t1, t2


if __name__ == "__main__":
    bez1 = CubicBezier(Point(129, 139), Point(190, 139), Point(201, 364),
                       Point(90, 364))
    bez2 = CubicBezier(Point(309, 159), Point(178, 159), Point(215, 408),
                       Point(309, 408))
    bez3 = Line(Point(309, 159), Point(309, 408))

    c = MinimumCurveDistanceFinder(bez1, bez3)
    dist, t1, t2 = c.minDist()
    print(bez1.pointAtTime(t1))
    print(bez3.pointAtTime(t2))
    print(math.sqrt(dist))
    print(c.iterations)
Example #23
0
 def derivative(self):
     """Returns a `Line` representing the derivative of this curve."""
     return Line((self[1] - self[0]) * 2, (self[2] - self[1]) * 2)
Example #24
0
def splitWith(segs, splitline, args=None):
    """ Splits a list of segments given a straight splitting line. Returns
        2 lists of segments such that the resulting segments are closed paths. """
    trans = splitline.alignmentTransformation()
    lefts = []
    rights = []
    splits = []
    for _s in segs:
        # transform so splitline is horizontal and is y=0
        s = _s.transformed(trans)
        roots = s._findRoots("y")
        # does y=0 cut this segment?
        if len(roots) == 2:
            # chop a quadratic cuver and put parts in left and right collection
            l, r = s.splitAtTime(roots[0])
            (rights if s.start.y < 0 else lefts).append(l)
            l, r = r.splitAtTime(roots[1])
            (rights if s.end.y < 0 else lefts).append(r)
            # keep split points so we can add joinup lines later
            splits.append(
                (l.start.x, (l.start.x < s.start.x) ^ (s.start.y < 0)))
            splits.append((l.end.x, (l.end.x > s.end.x) ^ (s.start.y < 0)))
        elif len(roots) == 1:
            # chop a straight line
            l, r = s.splitAtTime(roots[0])
            if s.start.y < 0:
                rights.append(l)
                lefts.append(r)
                splits.append((r.start.x, True))
            else:
                lefts.append(l)
                rights.append(r)
                splits.append((r.start.x, False))
        # otherwise no cut and simply allocate the segment
        elif s.start.y < 0 or s.end.y < 0:
            rights.append(s)
        else:
            lefts.append(s)
    # to convert from y=0 back to original co-ordinates
    backt = AffineTransformation()
    backt.apply_backwards(trans)
    backt.invert()
    if args is not None and args.detail & 8:
        print("      ", [(Point(s[0], 0).transformed(backt), s[1])
                         for s in sorted(splits)])
    curr = False
    lastP = None
    # find adjacent pairs of bounding points on the splitline and join them up on either side
    # thus making closed paths, even if the segments aren't end to start all the way round.
    for x, d in sorted(splits):
        newP = Point(x, 0)
        if d != curr:
            curr = d
        if lastP is None:
            lastP = newP
            continue
        l = Line(lastP, newP)
        r = Line(newP, lastP)
        if not d:
            rights.append(l)
            lefts.append(r)
        lastP = newP
    rights = [s.transformed(backt) for s in rights]
    lefts = [s.transformed(backt) for s in lefts]
    return (rights, lefts)
 def test_line_line(self):
     l1 = Line(Point(310, 389), Point(453, 222))
     l2 = Line(Point(289, 251), Point(447, 367))
     # Sanity check
     self.assertEqual(len(l1.intersections(l2)), 1)