pax_global_header00006660000000000000000000000064143230142410014504gustar00rootroot0000000000000052 comment=5884c2b9689f9e902f720284b37f3031cb273fd4 python-beziers-0.5.0+dfsg1/000077500000000000000000000000001432301424100154705ustar00rootroot00000000000000python-beziers-0.5.0+dfsg1/.github/000077500000000000000000000000001432301424100170305ustar00rootroot00000000000000python-beziers-0.5.0+dfsg1/.github/workflows/000077500000000000000000000000001432301424100210655ustar00rootroot00000000000000python-beziers-0.5.0+dfsg1/.github/workflows/ci.yml000066400000000000000000000012621432301424100222040ustar00rootroot00000000000000name: Python package on: [push] jobs: build: runs-on: ubuntu-latest strategy: matrix: python-version: [3.6] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v1 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m pip install --upgrade pip pip install dotmap pip install shapely pip install scipy pip install numpy pip install pyclipper pip install matplotlib - name: Test with pytest run: | pip install pytest PYTHONPATH=. pytest python-beziers-0.5.0+dfsg1/.gitignore000066400000000000000000000003071432301424100174600ustar00rootroot00000000000000__pycache__/ *.py[cod] *$py.class *.so .Python env/ build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ *.egg-info/ .installed.cfg *.egg .python-version .env venv/ ENV/ python-beziers-0.5.0+dfsg1/LICENSE000066400000000000000000000020551432301424100164770ustar00rootroot00000000000000MIT License Copyright (c) 2018 Simon Cozens Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. python-beziers-0.5.0+dfsg1/README.rst000066400000000000000000000021311432301424100171540ustar00rootroot00000000000000beziers.py ---------- Beziers provides a variety of classes for constructing, manipulating and drawing Bezier curves and paths. Principally designed for font design software, it allows you to join, split, offset, and perform many other operations on paths. Here is an example session:: from beziers.point import Point from beziers.path import BezierPath from beziers.cubicbezier import CubicBezier b1 = CubicBezier( Point(412.0,500.0), Point(308.0,665.0), Point(163.0,589.0), Point(163.0,504.0) ) b2 = CubicBezier( Point(163.0,504.0), Point(163.0,424.0), Point(364.0,321.0), Point(366.0,216.0) ) b3 = CubicBezier( Point(366.0,216.0), Point(368.0,94.0), Point(260.0,54.0), Point(124.0,54.0) ) path = BezierPath.fromSegments([b1,b2,b3]) path.closed = False path.addExtremes() path.balance() path.translate(Point(-100.0,-100.0)) import matplotlib.pyplot as plt fig, ax = plt.subplots() path.addExtremes() path.plot(ax) plt.show() Full documentation is available at https://simoncozens.github.io/beziers.py/index.html python-beziers-0.5.0+dfsg1/beziers/000077500000000000000000000000001432301424100171335ustar00rootroot00000000000000python-beziers-0.5.0+dfsg1/beziers/__init__.py000066400000000000000000000000001432301424100212320ustar00rootroot00000000000000python-beziers-0.5.0+dfsg1/beziers/affinetransformation.py000066400000000000000000000072211432301424100237260ustar00rootroot00000000000000import math from beziers.utils import isclose class AffineTransformation(object): def __init__(self, matrix = None): if not matrix: self.matrix = [ [ 1, 0, 0], [ 0, 1, 0], [ 0, 0, 1] ] else: self.matrix = matrix def __str__(self): m = self.matrix return "[ {:> 8.3f} {:> 8.3f} {:> 8.3f} ],\n[ {:> 8.3f} {:> 8.3f} {:> 8.3f} ],\n[ {:> 8.3f} {:> 8.3f} {:> 8.3f} ]".format( m[0][0],m[0][1],m[0][2],m[1][0],m[1][1],m[1][2],m[2][0],m[2][1],m[2][2]) def apply(self, other): m1 = self.matrix m2 = other.matrix self.matrix =[[m1[0][0] * m2[0][0] + m1[0][1] * m2[1][0] + m1[0][2] * m2[2][0], m1[0][0] * m2[0][1] + m1[0][1] * m2[1][1] + m1[0][2] * m2[2][1], m1[0][0] * m2[0][2] + m1[0][1] * m2[1][2] + m1[0][2] * m2[2][2]], [m1[1][0] * m2[0][0] + m1[1][1] * m2[1][0] + m1[1][2] * m2[2][0], m1[1][0] * m2[0][1] + m1[1][1] * m2[1][1] + m1[1][2] * m2[2][1], m1[1][0] * m2[0][2] + m1[1][1] * m2[1][2] + m1[1][2] * m2[2][2]], [m1[2][0] * m2[0][0] + m1[2][1] * m2[1][0] + m1[2][2] * m2[2][0], m1[2][0] * m2[0][1] + m1[2][1] * m2[1][1] + m1[2][2] * m2[2][1], m1[2][0] * m2[0][2] + m1[2][1] * m2[1][2] + m1[2][2] * m2[2][2]]] def apply_backwards(self, other): m2 = self.matrix m1 = other.matrix self.matrix =[[m1[0][0] * m2[0][0] + m1[0][1] * m2[1][0] + m1[0][2] * m2[2][0], m1[0][0] * m2[0][1] + m1[0][1] * m2[1][1] + m1[0][2] * m2[2][1], m1[0][0] * m2[0][2] + m1[0][1] * m2[1][2] + m1[0][2] * m2[2][2]], [m1[1][0] * m2[0][0] + m1[1][1] * m2[1][0] + m1[1][2] * m2[2][0], m1[1][0] * m2[0][1] + m1[1][1] * m2[1][1] + m1[1][2] * m2[2][1], m1[1][0] * m2[0][2] + m1[1][1] * m2[1][2] + m1[1][2] * m2[2][2]], [m1[2][0] * m2[0][0] + m1[2][1] * m2[1][0] + m1[2][2] * m2[2][0], m1[2][0] * m2[0][1] + m1[2][1] * m2[1][1] + m1[2][2] * m2[2][1], m1[2][0] * m2[0][2] + m1[2][1] * m2[1][2] + m1[2][2] * m2[2][2]]] @classmethod def translation(klass, vector): return klass([ [ 1, 0, vector.x ], [ 0, 1, vector.y ], [ 0, 0, 1] ]) def translate(self, vector): self.apply_backwards(type(self).translation(vector)) @classmethod def scaling(klass, factorX, factorY = None): if not factorY: factorY = factorX return klass([ [ factorX, 0, 0], [ 0, factorY, 0], [ 0, 0, 1] ]) def scale(self, factorX, factorY = None): self.apply_backwards(type(self).scaling(factorX, factorY)) @classmethod def reflection(klass): return klass([ [ -1, 0, 0], [ 0, 1, 0], [ 0, 0, 1] ]) def reflect(self): self.apply_backwards(type(self).reflection) @classmethod def rotation(klass, angle): return klass([ [ math.cos(-angle), math.sin(-angle), 0 ], [ -math.sin(-angle), math.cos(-angle), 0 ], [ 0, 0, 1 ] ]) def rotate(self, angle): self.apply_backwards(type(self).rotation(angle)) def invert(self): m = self.matrix det = m[0][0] * (m[1][1]*m[2][2] - m[1][2]*m[2][1]) - \ m[0][1] * (m[1][0]*m[2][2] - m[1][2]*m[2][0]) + \ m[0][2] * (m[1][0]*m[2][1] - m[1][1]*m[2][0]) if isclose(det, 0.): return None adj = [[(m[1][1]*m[2][2] - m[2][1]*m[1][2])/det, (m[2][1]*m[0][2] - m[0][1]*m[2][2])/det, (m[0][1]*m[1][2] - m[1][1]*m[0][2])/det], [(m[1][2]*m[2][0] - m[1][0]*m[2][2])/det, (m[0][0]*m[2][2] - m[0][2]*m[2][0])/det, (m[0][2]*m[1][0] - m[0][0]*m[1][2])/det], [(m[1][0]*m[2][1] - m[1][1]*m[2][0])/det, (m[0][1]*m[2][0] - m[0][0]*m[2][1])/det, (m[0][0]*m[1][1] - m[0][1]*m[1][0])/det]] self.matrix = adj python-beziers-0.5.0+dfsg1/beziers/boundingbox.py000066400000000000000000000056101432301424100220250ustar00rootroot00000000000000from beziers.point import Point class BoundingBox: """A representation of a rectangle within the Beziers world, used to store bounding boxes.""" def __init__(self): self.bl = None self.tr = None def __str__(self): return "BB[%s -> %s]" % (self.bl,self.tr) """Determine the bounding box - returns the bounding box itself.""" def bounds(self): return self @property def area(self): """Returns the area of the bounding box.""" vec = self.tr-self.bl return vec.x * vec.y @property def left(self): """Returns the X coordinate of the left edge of the box.""" return self.bl.x @property def right(self): """Returns the X coordinate of the right edge of the box.""" return self.tr.x @property def top(self): """Returns the Y coordinate of the top edge of the box.""" return self.tr.y @property def bottom(self): """Returns the Y coordinate of the bottom edge of the box.""" return self.bl.y @property def width(self): """Returns the width of the box.""" return self.tr.x - self.bl.x @property def height(self): """Returns the height of the box.""" return self.tr.y - self.bl.y @property def centroid(self): """Returns a `Point` representing the centroid of the box.""" return Point((self.left + self.right)*0.5, (self.top+self.bottom)*0.5) def extend(self,other): """Add an object to the bounding box. Object can be a `Point`, another `BoundingBox`, or something which has a `bounds()` method.""" if isinstance(other, Point): if not(self.bl): self.bl = other.clone() if not(self.tr): self.tr = other.clone() if other.x < self.bl.x: self.bl.x = other.x if other.y < self.bl.y: self.bl.y = other.y if other.x > self.tr.x: self.tr.x = other.x if other.y > self.tr.y: self.tr.y = other.y elif isinstance(other, BoundingBox): self.extend(other.bl) self.extend(other.tr) else: # Try getting its bb self.extend(other.bounds()) def translated(self, point): """Returns a new BoundingBox translated by the vector""" bb2 = BoundingBox() bb2.bl = self.bl + point bb2.tr = self.tr + point return bb2 def includes(self, point): """Returns True if the point is included in this bounding box.""" return self.bl.x >= point.x and self.tr.x <= point.x and self.bl.y >= point.y and self.tr.y <= point.y def overlaps(self,other): """Returns True if the given bounding box overlaps with this bounding box.""" if other.left > self.right: return False if other.right < self.left: return False if other.bottom > self.top: return False if other.top < self.bottom: return False return True def addMargin(self,size): """Adds a few units of margin around the edges of the bounding box.""" self.bl = self.bl + Point(-size,-size) self.tr = self.tr + Point(size,size) python-beziers-0.5.0+dfsg1/beziers/cubicbezier.py000066400000000000000000000166301432301424100220010ustar00rootroot00000000000000from beziers.segment import Segment from beziers.line import Line from beziers.point import Point from beziers.quadraticbezier import QuadraticBezier from beziers.utils.arclengthmixin import ArcLengthMixin import math from beziers.utils.legendregauss import Tvalues, Cvalues from beziers.utils import quadraticRoots class CubicBezier(ArcLengthMixin,Segment): def __init__(self, start, c1,c2,end): self.points = [start,c1,c2,end] self._range = [0,1] def __repr__(self): return "B<%s-%s-%s-%s>" % (self[0],self[1],self[2],self[3]) @classmethod def fromRepr(klass,text): import re p = re.compile("^B<(<.*?>)-(<.*?>)-(<.*?>)-(<.*?>)>$") m = p.match(text) points = [ Point.fromRepr(m.group(t)) for t in range(1,5) ] return klass(*points) def pointAtTime(self,t): """Returns the point at time t (0->1) along the curve.""" x = (1 - t) * (1 - t) * (1 - t) * self[0].x + 3 * (1 - t) * (1 - t) * t * self[1].x + 3 * (1 - t) * t * t * self[2].x + t * t * t * self[3].x; y = (1 - t) * (1 - t) * (1 - t) * self[0].y + 3 * (1 - t) * (1 - t) * t * self[1].y + 3 * (1 - t) * t * t * self[2].y + t * t * t * self[3].y; return Point(x,y) def tOfPoint(self,p): precision = 1.0/50.0 bestDist = float("inf") bestT = -1 samples = self.regularSampleTValue(50) for t in samples: dist = self.pointAtTime(t).distanceFrom(p) if dist < bestDist: bestDist = dist bestT = t while precision > 1e-5: precision = precision / 2 lower = bestT - precision if lower < 0: lower = 0 upper = bestT + precision if upper > 1: upper = 1 ldist = self.pointAtTime(lower).distanceFrom(p) rdist = self.pointAtTime(lower).distanceFrom(p) if ldist < bestDist: bestT = lower bestDist = ldist if rdist < bestDist: bestT = upper bestDist = rdist return bestT def splitAtTime(self,t): """Returns two segments, dividing the given segment at a point t (0->1) along the curve.""" p4 = self[0].lerp(self[1],t) p5 = self[1].lerp(self[2],t) p6 = self[2].lerp(self[3],t) p7 = p4.lerp(p5,t) p8 = p5.lerp(p6,t) p9 = p7.lerp(p8,t) return (CubicBezier(self[0],p4,p7,p9), CubicBezier(p9,p8,p6,self[3])) def join(self,other): """Not currently implemented: join two `CubicBezier` together.""" raise "Not implemented" def toQuadratic(self): """Not currently implemented: reduce this to a `QuadraticBezier`.""" raise "Not implemented" def derivative(self): """Returns a `QuadraticBezier` representing the derivative of this curve.""" return QuadraticBezier( (self[1]-self[0])*3, (self[2]-self[1])*3, (self[3]-self[2])*3 ) 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 _findRoots(self,dimension): def cuberoot(v): if v<0: return -math.pow(-v,1/3.0) return math.pow(v,1/3.0) if dimension == "x": pa,pb,pc,pd = self[0].x,self[1].x,self[2].x,self[3].x elif dimension == "y": pa,pb,pc,pd = self[0].y,self[1].y,self[2].y,self[3].y else: raise SyntaxError("Meh.") a = (3*pa - 6*pb + 3*pc) b = (-3*pa + 3*pb) c = pa d = (-pa + 3*pb - 3*pc + pd) if d == 0: return [] a = a/d b = b/d c = c/d p = (3*b - a*a)/3 p3 = p/3 q = (2*a*a*a - 9*a*b + 27*c)/27.0 q2 = q/2 discriminant = q2*q2 + p3*p3*p3 if discriminant < 0: mp3 = -p/3 mp33 = mp3*mp3*mp3 r = math.sqrt( mp33 ) t = -q / (2*r) cosphi = max(min(t,1),-1) phi = math.acos(cosphi) crtr = cuberoot(r) t1 = 2*crtr root1 = t1 * math.cos(phi/3) - a/3 root2 = t1 * math.cos((phi+2*math.pi)/3) - a/3 root3 = t1 * math.cos((phi+4*math.pi)/3) - a/3 roots = [root1, root2, root3] return sorted([x for x in roots if x >= 0 and x <= 1]) if discriminant == 0: if q2 < 0: u1 = cuberoot(-q2) else: u1 =-cuberoot(q2); root1 = 2*u1 - a/3.0; root2 = -u1 - a/3.0; roots = [root1,root2] return sorted([x for x in roots if x >= 0 and x <= 1]) sd = math.sqrt(discriminant); u1 = cuberoot(sd - q2); v1 = cuberoot(sd + q2); root1 = u1 - v1 - a/3; return [x for x in [root1] if x >= 0 and x <= 1] def _findDRoots(self): d = self.derivative() roots = [] # We have f(t) = w1 (1-t)^2 + 2 w2 (1-t) t + w3 t^2 # We want f(t) = a t^2 + b^t + c to solve with the quadratic formula roots.extend(quadraticRoots(d[0].x - 2*d[1].x + d[2].x, 2 * (d[1].x-d[0].x), d[0].x)) roots.extend(quadraticRoots(d[0].y - 2*d[1].y + d[2].y, 2 * (d[1].y-d[0].y), d[0].y)) return roots def findExtremes(self, inflections = False): """Returns a list of time `t` values for extremes of the curve.""" r = self._findDRoots() if inflections: r.extend(self.derivative()._findDRoots()) r.sort() return [ root for root in r if root >= 0.01 and root <= 0.99 ] def curvatureAtTime(self,t): """Returns the C curvature at time `t`..""" d = self.derivative() d2 = d.derivative() return d.pointAtTime(t).x * d2.pointAtTime(t).y - d.pointAtTime(t).y * d2.pointAtTime(t).x @property 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 balance(self): """Perform Tunni balancing on this Bezier.""" p = self.tunniPoint if not p: return if self[0].distanceFrom(p) == 0.0: fraction1 = 0.43 else: fraction1 = self[0].distanceFrom(self[1]) / self[0].distanceFrom(p) if self[3].distanceFrom(p) == 0.0: fraction2 = 0.73 else: fraction2 = self[3].distanceFrom(self[2]) / self[3].distanceFrom(p) avg = (fraction2 + fraction1) / 2.0 if avg > 0 and avg < 1: self[1] = self[0].lerp(p, avg) self[2] = self[3].lerp(p, avg) @property def hasLoop(self): a1 = self[0].x * (self[3].y - self[2].y) + self[0].y * (self[2].x - self[3].x) + self[3].x * self[2].y - self[3].y * self[2].x a2 = self[1].x * (self[0].y - self[3].y) + self[1].y * (self[3].x - self[0].x) + self[0].x * self[3].y - self[0].y * self[3].x a3 = self[2].x * (self[1].y - self[0].y) + self[2].y * (self[0].x - self[1].x) + self[1].x * self[0].y - self[1].y * self[0].x d3 = 3 * a3 d2 = d3 - a2 d1 = d2 - a2 + a1 l = math.sqrt(d1 * d1 + d2 * d2 + d3 * d3) s = 0 if l != 0: s = 1 / l d1 *= s d2 *= s d3 *= s d = 3 * d2 * d2 - 4 * d1 * d3 if d >= 0: return False f1 = math.sqrt(-d) f2 = 2 * d1 return ((d2 + f1) / f2, (d2 - f1) / f2) @property def area(self): """Returns the signed rea between the curve and the y-axis""" return (10 * (self[3].x*self[3].y - self[0].x*self[0].y) + 6 * (self[1].x*self[0].y - self[0].x*self[1].y + self[3].x*self[2].y - self[2].x*self[3].y) + 3 * (self[2].x*self[0].y - self[0].x*self[2].y + self[2].x*self[1].y - self[1].x*self[2].y + self[3].x*self[1].y - self[1].x*self[3].y) + self[3].x*self[0].y - self[0].x*self[3].y) / 20. python-beziers-0.5.0+dfsg1/beziers/line.py000066400000000000000000000054421432301424100204410ustar00rootroot00000000000000from beziers.segment import Segment from beziers.point import Point from beziers.utils import isclose import math import sys class Line(Segment): """Represents a line segment within a Bezier path.""" def __init__(self, start, end): self.points = [start,end] self._orig = None def __repr__(self): return "L<%s--%s>" % (self.points[0], self.points[1]) @classmethod def fromRepr(klass,text): import re p = re.compile("^L<(<.*?>)--(<.*?>)>$") m = p.match(text) return klass(Point.fromRepr(m.group(1)),Point.fromRepr(m.group(2))) def pointAtTime(self,t): """Returns the point at time t (0->1) along the line.""" return self.start.lerp(self.end, t) # XXX One of these is wrong def tangentAtTime(self,t): """Returns the tangent at time t (0->1) along the line.""" return Point.fromAngle(math.atan2(self.end.y-self.start.y,self.end.x-self.start.x)) def normalAtTime(self,t): """Returns the normal at time t (0->1) along the line.""" return self.tangentAtTime(t).rotated(Point(0,0),math.pi/2) def curvatureAtTime(self,t): return sys.float_info.epsilon # Avoid divide-by-zero def splitAtTime(self,t): """Returns two segments, dividing the given segment at a point t (0->1) along the line.""" return (Line(self.start, self.pointAtTime(t)), Line(self.pointAtTime(t), self.end)) def _findRoots(self, dimension): if dimension == "x": t = self[0].x / (self[0].x - self[1].x) if not isclose(self[0].x, self[1].x) else 100. elif dimension == "y": t = self[0].y / (self[0].y - self[1].y) if not isclose(self[0].y, self[1].y) else 100. else: raise SyntaxError("Meh") if 0. <= t <= 1.: return [t] return [] def tOfPoint(self, point, its_on_the_line_i_swear=False): """Returns the t (0->1) value of the given point, assuming it lies on the line, or -1 if it does not.""" # Just find one and hope the other fits # point = self.start * (1-t) + self.end * t if not isclose(self.end.x, self.start.x): t = (point.x - self.start.x) / (self.end.x-self.start.x) elif not isclose(self.end.y, self.start.y): t = (point.y - self.start.y) / (self.end.y-self.start.y) else: print("! Line %s is actually a point..." % self) return -1 if its_on_the_line_i_swear or self.pointAtTime(t).distanceFrom(point) < 2e-7: return t return -1 def flatten(self,degree=8): return [self] @property def slope(self): v = self[1]-self[0] if v.x == 0: return 0 return v.y / v.x @property def intercept(self): return self[1].y - self.slope * self[1].x @property def length(self): return self[0].distanceFrom(self[1]) def findExtremes(self): return [] @property def area(self): return 0.5 * (self[1].x - self[0].x)*(self[0].y + self[1].y) python-beziers-0.5.0+dfsg1/beziers/path/000077500000000000000000000000001432301424100200675ustar00rootroot00000000000000python-beziers-0.5.0+dfsg1/beziers/path/__init__.py000066400000000000000000000631721432301424100222110ustar00rootroot00000000000000from beziers.path.representations.Segment import SegmentRepresentation from beziers.path.representations.Nodelist import NodelistRepresentation, Node from beziers.point import Point from beziers.boundingbox import BoundingBox from beziers.utils.samplemixin import SampleMixin from beziers.utils.booleanoperationsmixin import BooleanOperationsMixin from beziers.segment import Segment from beziers.line import Line from beziers.cubicbezier import CubicBezier import math if not hasattr(math, "isclose"): def isclose(a, b, rel_tol=1e-9, abs_tol=0.0): return abs(a-b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol) math.isclose = isclose class BezierPath(BooleanOperationsMixin,SampleMixin,object): """`BezierPath` represents a collection of `Segment` objects - the curves and lines that make up a path. One of the really fiddly things about manipulating Bezier paths in a computer is that there are various ways to represent them. Different applications prefer different representations. For instance, when you're drawing a path on a canvas, you often want a list of nodes like so:: { "x":255.0, "y":20.0, "type":"curve"}, { "x":385.0, "y":20.0, "type":"offcurve"}, { "x":526.0, "y":79.0, "type":"offcurve"}, { "x":566.0, "y":135.0, "type":"curve"}, { "x":585.0, "y":162.0, "type":"offcurve"}, { "x":566.0, "y":260.0, "type":"offcurve"}, { "x":484.0, "y":281.0, "type":"curve"}, ... But when you're doing Clever Bezier Mathematics, you generally want a list of segments instead:: [ (255.0,20.0), (385.0,20.0), (526.0,79.0), (566.0,135.0)], [ (566.0,135.0), (585.0,162.0), (566.0,260.0), (484.0,281.0)], The Beziers module is designed to allow you to move fluidly between these different representations depending on what you're wanting to do. """ def __init__(self): self.activeRepresentation = None self.closed = True @classmethod def fromPoints(self, points, error = 50.0, cornerTolerance = 20.0, maxSegments = 20): """Fit a poly-bezier curve to the points given. This operation should be familiar from 'pencil' tools in a vector drawing application: the application samples points where your mouse pointer has been dragged, and then turns the sketch into a Bezier path. The goodness of fit can be controlled by tuning the `error` parameter. Corner detection can be controlled with `cornerTolerance`. Here are some points fit with `error=100.0`: .. figure:: curvefit1.png :scale: 75 % :alt: curvefit1 And with `error=10.0`: .. figure:: curvefit2.png :scale: 75 % :alt: curvefit1 """ from beziers.utils.curvefitter import CurveFit segs = CurveFit.fitCurve(points, error, cornerTolerance, maxSegments) path = BezierPath() path.closed = False path.activeRepresentation = SegmentRepresentation(path,segs) return path @classmethod def fromSegments(klass, array): """Construct a path from an array of Segment objects.""" self = klass() for a in array: assert(isinstance(a, Segment)) self.activeRepresentation = SegmentRepresentation(self,array) return self @classmethod def fromNodelist(klass, array, closed=True): """Construct a path from an array of Node objects.""" self = klass() for a in array: assert(isinstance(a, Node)) self.closed = closed self.activeRepresentation = NodelistRepresentation(self,array) self.asSegments() # Resolves a few problems return self @classmethod def fromGlyphsLayer(klass, layer): """Returns an *array of BezierPaths* from a Glyphs GSLayer object.""" from beziers.path.representations.GSPath import GSPathRepresentation bezpaths = [] for p in layer.paths: path = BezierPath() path.activeRepresentation = GSPathRepresentation(path,p) bezpaths.append(path) return bezpaths @classmethod def fromDrawable(klass, layer, *penArgs, **penKwargs): """Returns an *array of BezierPaths* from any object conforming to the pen protocol.""" from beziers.utils.pens import BezierPathCreatingPen pen = BezierPathCreatingPen(*penArgs, **penKwargs) layer.draw(pen) return pen.paths @classmethod def fromFonttoolsGlyph(klass,font,glyphname): """Returns an *array of BezierPaths* from a FontTools font object and glyph name.""" glyphset = font.getGlyphSet() from beziers.utils.pens import BezierPathCreatingPen pen = BezierPathCreatingPen(glyphset) glyph = font.getGlyphSet()[glyphname] glyph.draw(pen) return pen.paths def asSegments(self): """Return the path as an array of segments (either Line, CubicBezier, or, if you are exceptionally unlucky, QuadraticBezier objects).""" if not isinstance(self.activeRepresentation, SegmentRepresentation): nl = self.activeRepresentation.toNodelist() assert isinstance(nl, list) self.activeRepresentation = SegmentRepresentation.fromNodelist(self,nl) return self.activeRepresentation.data() def asNodelist(self): """Return the path as an array of Node objects.""" if not isinstance(self.activeRepresentation, NodelistRepresentation): nl = self.activeRepresentation.toNodelist() assert isinstance(nl, list) self.activeRepresentation = NodelistRepresentation(self,nl) return self.activeRepresentation.data() def asSVGPath(self): """Return the path as a string suitable for a SVG 2 and nl[i-2].type == "offcurve": codes.append(Path.CURVE4) else: codes.append(Path.CURVE3) elif n.type == "line": codes.append(Path.LINETO) else: raise "Unknown node type" if self.closed: verts.append((nl[0].x,nl[0].y)) codes.append(Path.CLOSEPOLY) return Path(verts, codes) def plot(self,ax, **kwargs): """Plot the path on a Matplot subplot which you supply :: import matplotlib.pyplot as plt fig, ax = plt.subplots() path.plot(ax) """ import matplotlib.pyplot as plt from matplotlib.lines import Line2D from matplotlib.path import Path import matplotlib.patches as patches path = self.asMatplot() if not "lw" in kwargs: kwargs["lw"] = 2 if not "fill" in kwargs: kwargs["fill"] = False drawNodes = (not("drawNodes" in kwargs) or kwargs["drawNodes"] != False) if "drawNodes" in kwargs: kwargs.pop("drawNodes") patch = patches.PathPatch(path, **kwargs) ax.add_patch(patch) left, right = ax.get_xlim() top, bottom = ax.get_ylim() bounds = self.bounds() bounds.addMargin(50) if not (left == 0.0 and right == 1.0 and top == 0.0 and bottom == 1.0): bounds.extend(Point(left,top)) bounds.extend(Point(right,bottom)) ax.set_xlim(bounds.left,bounds.right) ax.set_ylim(bounds.bottom,bounds.top) if drawNodes: nl = self.asNodelist() for i in range(0,len(nl)): n = nl[i] if n.type =="offcurve": circle = plt.Circle((n.x, n.y), 2, fill=True,color="black",alpha=0.5) ax.add_artist(circle) if i+1 < len(nl) and nl[i+1].type != "offcurve": l = Line2D([n.x, nl[i+1].x], [n.y, nl[i+1].y], linewidth=2,color="black",alpha=0.3) ax.add_artist(l) if i-0 >= 0 and nl[i-1].type != "offcurve": l = Line2D([n.x, nl[i-1].x], [n.y, nl[i-1].y], linewidth=2,color="black",alpha=0.3) ax.add_artist(l) else: circle = plt.Circle((n.x, n.y), 3,color="black",alpha=0.3) ax.add_artist(circle) def clone(self): """Return a new path which is an exact copy of this one""" p = BezierPath.fromSegments(self.asSegments()) p.closed = self.closed return p def round(self): """Rounds the points of this path to integer coordinates.""" segs = self.asSegments() for s in segs: s.round() self.activeRepresentation = SegmentRepresentation(self,segs) def bounds(self): """Determine the bounding box of the path, returned as a `BoundingBox` object.""" bbox = BoundingBox() for seg in self.asSegments(): bbox.extend(seg) return bbox def splitAtPoints(self,splitlist): def mapx(v,ds): return (v-ds)/(1-ds) segs = self.asSegments() newsegs = [] # Cluster splitlist by seg newsplitlist = {} for (seg,t) in splitlist: if not seg in newsplitlist: newsplitlist[seg] = [] newsplitlist[seg].append(t) for k in newsplitlist: newsplitlist[k] = sorted(newsplitlist[k]) # Now walk the path for seg in segs: if seg in newsplitlist: tList = newsplitlist[seg] while len(tList) > 0: t = tList.pop(0) if t < 1e-8: continue seg1,seg2 = seg.splitAtTime(t) newsegs.append(seg1) seg = seg2 for i in range(0,len(tList)): tList[i] = mapx(tList[i],t) newsegs.append(seg) self.activeRepresentation = SegmentRepresentation(self,newsegs) def addExtremes(self): """Add extreme points to the path.""" def mapx(v,ds): return (v-ds)/(1-ds) segs = self.asSegments() splitlist = [] for seg in segs: for t in seg.findExtremes(): splitlist.append((seg,t)) self.splitAtPoints(splitlist) return self @property def length(self): """Returns the length of the whole path.""" segs = self.asSegments() length = 0 for s in segs: length += s.length return length def pointAtTime(self,t): """Returns the point at time t (0->1) along the curve, where 1 is the end of the whole curve.""" segs = self.asSegments() if t == 1.0: return segs[-1].pointAtTime(1) t *= len(segs) seg = segs[int(math.floor(t))] return seg.pointAtTime(t-math.floor(t)) def lengthAtTime(self,t): """Returns the length of the subset of the path from the start up to the point t (0->1), where 1 is the end of the whole curve.""" segs = self.asSegments() t *= len(segs) length = 0 for s in segs[:int(math.floor(t))]: length += s.length seg = segs[int(math.floor(t))] s1,s2 = seg.splitAtTime(t-math.floor(t)) length += s1.length return length def offset(self, vector, rotateVector = True): """Returns a new BezierPath which approximates offsetting the current Bezier path by the given vector. Note that the vector will be rotated around the normal of the curve so that the offsetting always happens on the same 'side' of the curve: .. figure:: offset1.png :scale: 75 % :alt: offset1 If you don't want that and you want 'straight' offsetting instead (which may intersect with the original curve), pass `rotateVector=False`: .. figure:: offset2.png :scale: 75 % :alt: offset1 """ # Method 1 - curve fit newsegs = [] points = [] def finishPoints(newsegs, points): if len(points) > 0: bp = BezierPath.fromPoints(points, error=0.1, cornerTolerance= 1) newsegs.extend(bp.asSegments()) while len(points)>0:points.pop() for seg in self.asSegments(): if isinstance(seg,Line): finishPoints(newsegs,points) newsegs.append(seg.translated(vector)) else: t = 0.0 while t <1.0: if rotateVector: points.append( seg.pointAtTime(t) + vector.rotated(Point(0,0), seg.normalAtTime(t).angle)) else: points.append( seg.pointAtTime(t) + vector) step = max(abs(seg.curvatureAtTime(t)),0.1) t = t + min(seg.length / step,0.1) finishPoints(newsegs,points) newpath = BezierPath() newpath.activeRepresentation = SegmentRepresentation(newpath, newsegs) return newpath 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 def reverse(self): """Reverse this path (mutates path).""" seg2 = [ x.reversed() for x in self.asSegments()] self.activeRepresentation = SegmentRepresentation(self, list(reversed(seg2))) return self def translate(self, vector): """Translates the path by a given vector.""" seg2 = [ x.translated(vector) for x in self.asSegments()] self.activeRepresentation = SegmentRepresentation(self, seg2) return self def rotate(self, about, angle): """Rotate the path by a given vector.""" seg2 = [ x.rotated(about, angle) for x in self.asSegments()] self.activeRepresentation = SegmentRepresentation(self, seg2) return self def scale(self, by): """Scales the path by a given magnitude.""" seg2 = [ x.scaled(by) for x in self.asSegments()] self.activeRepresentation = SegmentRepresentation(self, seg2) return self def balance(self): """Performs Tunni balancing on the path.""" segs = self.asSegments() for x in segs: if isinstance(x, CubicBezier): x.balance() self.activeRepresentation = SegmentRepresentation(self, segs) return self def findDiscontinuities(self): """Not implemented yet""" def harmonize(self): """Not implemented yet""" def roundCorners(self): """Not implemented yet""" def dash(self, lineLength = 50, gapLength = None): """Returns a list of BezierPath objects created by chopping this path into a dashed line:: paths = path.dash(lineLength = 20, gapLength = 50) .. figure:: dash.png :scale: 75 % :alt: path.dash(lineLength = 20, gapLength = 50) """ if not gapLength: gapLength = lineLength granularity = self.length newpaths = [] points = [] for t in self.regularSampleTValue(granularity): lenSoFar = self.lengthAtTime(t) # Super inefficient. But simple! lenSoFar = lenSoFar % (lineLength + gapLength) if lenSoFar >= lineLength and len(points) > 0: # When all you have is a hammer... bp = BezierPath.fromPoints(points, error=1, cornerTolerance= 1) points = [] if len(bp.asSegments()) > 0: newpaths.append(bp) elif lenSoFar <= lineLength: points.append(self.pointAtTime(t)) return newpaths def segpairs(self): segs = self.asSegments() for i in range(0,len(segs)-1): yield (segs[i],segs[i+1]) 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 flatten(self,degree=8): segs = [] for s in self.asSegments(): segs.extend(s.flatten(degree)) return BezierPath.fromSegments(segs) 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)) def pointIsInside(self,pt): """Returns true if the given point lies on the "inside" of the path, assuming an 'even-odd' winding rule where self-intersections are considered outside.""" li = self.windingNumberOfPoint(pt) return li % 2 == 1 @property def signed_area(self): """Approximates the area under a closed path by flattening and treating as a polygon. Returns the signed area; positive means the path is counter-clockwise, negative means it is clockwise.""" flat = self.flatten() area = 0 for s in flat.asSegments(): area = area + (s.start.x * s.end.y) - (s.start.y * s.end.x) area = area / 2.0 return area @property def area(self): """Approximates the area under a closed path by flattening and treating as a polygon. Returns the unsigned area. Use :py:meth:`signed_area` if you want the signed area.""" return abs(self.signed_area) @property def direction(self): """Returns the direction of the path: -1 for clockwise and 1 for counterclockwise.""" return math.copysign(1, self.signed_area) @property def centroid(self): if not self.closed: return None return self.bounds().centroid # Really? 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 def quadraticsToCubics(self): """Converts all quadratic segments in the path to cubic Beziers.""" segs = self.asSegments() for i,s in enumerate(segs): if len(s) == 3: segs[i] = s.toCubicBezier() return self 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 # midpoint = (i1.point + i2.point) / 2 # # Find closest path to midpoint # # Find the tangent at that time # inorm2 = i2.seg1.normalAtTime(i2.t1) def distanceToPath(self, other, samples = 10): """Finds the distance to the other curve at its closest point, along with the t values for the closest point at each segment and the relevant segments. Returns: ``distance, t1, t2, seg1, seg2``.""" from beziers.utils.curvedistance import curveDistance segs1 = self.asSegments() segs2 = other.asSegments() minDistance = None # Find closest segment pair. for s1 in segs1: samples1 = s1.sample(samples) for s2 in segs2: samples2 = s2.sample(samples) d = min([ p1.squareDistanceFrom(p2) for p1 in samples1 for p2 in samples2]) if not minDistance or d < minDistance: minDistance = d closestPair = (s1,s2) c = curveDistance(closestPair[0], closestPair[1]) return (c[0],c[1],c[2], closestPair[0], closestPair[1]) def tidy(self, **kwargs): """Tidies a curve by adding extremes, and then running ``removeIrrelevantSegments`` and ``smooth``. ``relLength``, ``absLength``, ``maxCollectionSize``, ``lengthLimit`` and ``cornerTolerance`` parameters are passed to the relevant routine.""" self.addExtremes() self.removeIrrelevantSegments(**{k:v for k,v in kwargs.items() if k in ["relLength", "absLength"] }) self.smooth(**{k:v for k,v in kwargs.items() if k in ["maxCollectionSize", "lengthLimit", "cornerTolerance"] }) def removeIrrelevantSegments(self, relLength=1/50000, absLength=0): """Removes small and collinear line segments. Collinear line segments are adjacent line segments which are heading in the same direction, and hence can be collapsed into a single segment. Small segments (those less than ``absLength`` units, or less than ``relLength`` as a fraction of the path's total length) are removed entirely.""" segs = self.asSegments() newsegs = [segs[0]] smallLength = self.length * relLength for i in range(1,len(segs)): prev = newsegs[-1] this = segs[i] if this.length < smallLength or this.length < absLength: this[0] = prev[0] newsegs[-1] = this continue if len(prev) == 2 and len(this) == 2: if math.isclose(prev.tangentAtTime(0).angle, this.tangentAtTime(0).angle): this[0] = prev[0] newsegs[-1] = this continue newsegs.append(this) self.activeRepresentation = SegmentRepresentation(self, newsegs) return self def smooth(self, maxCollectionSize = 30, lengthLimit = 20, cornerTolerance = 10): """Smooths a curve, by collating lists of small (at most ``lengthLimit`` units long) segments at most ``maxCollectionSize`` segments at a time, and running them through a curve fitting algorithm. The list collation also stops when one segment turns more than ``cornerTolerance`` degrees away from the previous one, so that corners are not smoothed.""" smallLineLength = lengthLimit segs = self.asSegments() i = 0 collection = [] while i < len(segs): s = segs[i] if s.length < smallLineLength and len(collection) <= maxCollectionSize: collection.append(s) else: corner = False if len(collection) > 1: last = collection[-1] if abs(last.tangentAtTime(1).angle - s.tangentAtTime(0).angle) > math.radians(cornerTolerance): corner = True if len(collection) > maxCollectionSize or corner or i == len(segs)-2: points = [x.start for x in collection] bp = BezierPath.fromPoints(points) if len(bp.asSegments()) > 0: segs[i-len(collection):i] = bp.asSegments() i -= len(collection) collection = [] i += 1 if len(collection) > 0: points = [x.start for x in collection] bp = BezierPath.fromPoints(points) if len(bp.asSegments()) > 0: segs[i-(1+len(collection)):i-1] = bp.asSegments() self.activeRepresentation = SegmentRepresentation(self, segs) return self python-beziers-0.5.0+dfsg1/beziers/path/geometricshapes.py000066400000000000000000000044221432301424100236250ustar00rootroot00000000000000from beziers.point import Point from beziers.path import BezierPath from beziers.cubicbezier import CubicBezier from beziers.line import Line import math CIRCULAR_SUPERNESS = 4./3.*(math.sqrt(2)-1) west = Point(-1,0) east = Point(1,0) north = Point(0,1) south = Point(0,-1) def Circle(x_radius, origin=None, superness=CIRCULAR_SUPERNESS): """Returns a path representing a circle of given radius. You can specify the `origin` as a Point and the `superness` of the circle.""" return Ellipse(x_radius, x_radius, origin=origin, superness=superness) def Ellipse(x_radius, y_radius, origin=None, superness=CIRCULAR_SUPERNESS): """Returns a path representing an ellipse of given x and y radii. You can specify the `origin` as a Point and the `superness` of the ellipse.""" if not origin: origin = Point(0,0) w = origin + west * x_radius e = origin + east * x_radius n = origin + north * y_radius s = origin + south * y_radius w_n = CubicBezier(w, w + north * y_radius * superness, n + west * x_radius * superness, n) n_e = CubicBezier(n, n + east * x_radius * superness, e + north * y_radius * superness, e) e_s = CubicBezier(e, e + south * y_radius * superness, s + east * x_radius * superness, s) s_w = CubicBezier(s, s + west * x_radius * superness, w + south * y_radius * superness, w) return BezierPath.fromSegments([w_n, n_e, e_s, s_w]) 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) ]) def Square(width, origin=None): """Returns a path representing a square of given width. You can specify the `origin` as a Point.""" return Rectangle(width, width, origin=origin) python-beziers-0.5.0+dfsg1/beziers/path/representations/000077500000000000000000000000001432301424100233145ustar00rootroot00000000000000python-beziers-0.5.0+dfsg1/beziers/path/representations/GSPath.py000066400000000000000000000007001432301424100250110ustar00rootroot00000000000000from beziers.path.representations.Nodelist import Node class GSPathRepresentation(object): def __init__(self, path, gspath = None): self.nodes = [] from beziers.path import BezierPath assert isinstance(path, BezierPath) self.path = path if (gspath): self.nodes = gspath.nodes path.closed = gspath.closed def toNodelist(self): return list(map( lambda n: Node(n.position.x,n.position.y,n.type), self.nodes)) python-beziers-0.5.0+dfsg1/beziers/path/representations/Nodelist.py000066400000000000000000000012501432301424100254450ustar00rootroot00000000000000from beziers.point import Point class Node(object): def __init__(self, x,y,type): self.point = Point(x,y) self.type = type @property def x(self): return self.point.x @property def y(self): return self.point.y def __repr__(self): return "" % (self.x,self.y,self.type) class NodelistRepresentation(object): def __init__(self, path, nl = None): self.path = path from beziers.path import BezierPath assert isinstance(path, BezierPath) self.nodes = [] if nl: self.nodes = nl def data(self): return self.nodes def toNodelist(self): return self.nodes def fromNodelist(self): return self python-beziers-0.5.0+dfsg1/beziers/path/representations/Segment.py000066400000000000000000000050631432301424100252740ustar00rootroot00000000000000from beziers.path.representations.Nodelist import Node from beziers.line import Line from beziers.cubicbezier import CubicBezier from beziers.quadraticbezier import QuadraticBezier from beziers.point import Point from beziers.utils import isclose class SegmentRepresentation(object): def __init__(self, path, segments=[]): self.path = path from beziers.path import BezierPath assert isinstance(path, BezierPath) self.segments = [] if segments: self.segments = segments def data(self): return self.segments def toNodelist(self): first = self.segments[0][0] nodelist=[] if len(self.segments[0]) == 2: nodelist.append(Node(first.x, first.y, "line")) else: nodelist.append(Node(first.x, first.y, "curve")) for seg in self.segments: if len(seg) == 4: nodelist.append(Node(seg[1].x, seg[1].y, "offcurve")) nodelist.append(Node(seg[2].x, seg[2].y, "offcurve")) nodelist.append(Node(seg[3].x, seg[3].y, "curve")) elif len(seg) == 3: nodelist.append(Node(seg[1].x, seg[1].y, "offcurve")) nodelist.append(Node(seg[2].x, seg[2].y, "curve")) else: nodelist.append(Node(seg[1].x, seg[1].y, "line")) return nodelist 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") @classmethod def fromNodelist(cls, path, nodelist): self = SegmentRepresentation(path) # Find first oncurve firstOncurve = -1 for ix, n in enumerate(nodelist): if n.type != "offcurve": firstOncurve = ix break first = nodelist[firstOncurve] seg = [(first.x,first.y)] for n in nodelist[firstOncurve+1:]: if n.type == "offcurve": seg.append((n.x,n.y)) if n.type == "line" or n.type == "curve": seg.append((n.x,n.y)) self.appendSegment(seg) seg = [(n.x,n.y)] for n in nodelist[:firstOncurve]: if n.type == "offcurve": seg.append((n.x,n.y)) if n.type == "line" or n.type == "curve": seg.append((n.x,n.y)) self.appendSegment(seg) seg = [(n.x,n.y)] # Closed? if self.path.closed: if len(seg) == 1 and isclose(seg[-1][0], first.x) and isclose(seg[-1][1], first.y): pass else: seg.append((first.x,first.y)) self.appendSegment(seg) return self python-beziers-0.5.0+dfsg1/beziers/path/representations/__init__.py000066400000000000000000000000001432301424100254130ustar00rootroot00000000000000python-beziers-0.5.0+dfsg1/beziers/path/representations/fontparts.py000066400000000000000000000027141432301424100257120ustar00rootroot00000000000000from beziers.line import Line from beziers.cubicbezier import CubicBezier from beziers.path import BezierPath from beziers.path.representations.Nodelist import NodelistRepresentation, Node class FontParts: @classmethod def fromFontpartsGlyph(klass, glyph): """Returns an *array of BezierPaths* from a FontParts glyph object.""" paths = [] if hasattr(glyph, "contours"): contouriterator = glyph.contours else: contouriterator = glyph for c in contouriterator: path = BezierPath() path.closed = False nodeList = [] if hasattr(c, "points"): pointiterator = c.points else: pointiterator = c for p in pointiterator: if hasattr(p, "segmentType"): t = p.segmentType else: t = p.type nodeList.append(Node(p.x,p.y,t)) path.activeRepresentation = NodelistRepresentation(path, nodeList) if nodeList[0].point == nodeList[-1].point: path.closed = True paths.append(path) return paths @classmethod def drawToFontpartsGlyph(klass,glyph,path): pen = glyph.getPen() segs = path.asSegments() pen.moveTo((segs[0][0].x,segs[0][0].y)) for seg in segs: if isinstance(seg, Line): pen.lineTo((seg[1].x,seg[1].y)) elif isinstance(seg, CubicBezier): pen.curveTo( (seg[1].x,seg[1].y), (seg[2].x,seg[2].y), (seg[3].x,seg[3].y)) if path.closed: pen.closePath() python-beziers-0.5.0+dfsg1/beziers/point.py000066400000000000000000000111201432301424100206310ustar00rootroot00000000000000import math class Point(object): """A representation of a point within the Beziers world. Here are some things you can do with points. You can interpret them as vectors, and add them together:: >>> a = Point(5,5) >>> b = Point(10,10) >>> a + b <15.0,15.0> You can multiply them by a scalar to scale them:: >>> a * 2 <10.0,10.0> You can adjust them:: >>> a += b >>> a <15.0,15.0> If you're using Python 3, you can abuse operator overloading and compute the dot product of two vectors: >>> a = Point(5,5) >>> b = Point(10,10) >>> a @ b 100.0 """ def __init__(self, x,y): self.x = float(x) self.y = float(y) def __repr__(self): return "<%s,%s>" % (self.x,self.y) @classmethod def fromRepr(klass,text): import re p = re.compile("^<([^,]+),([^>]+)>$") m = p.match(text) return klass(m.group(1), m.group(2)) def __eq__(self, other): def isclose(a, b, rel_tol=1e-09, abs_tol=0.0): return abs(a-b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol) return isclose(self.x, other.x) and isclose(self.y, other.y) def __hash__(self): return hash(self.x) << 32 ^ hash(self.y) def __mul__(self, other): """Multiply a point by a scalar.""" return Point(self.x * other, self.y * other) def __div__(self, other): return Point(self.x / other, self.y / other) def __truediv__(self, other): return Point(self.x / other, self.y / other) def __add__(self, other): return Point(self.x + other.x, self.y + other.y) def __sub__(self, other): return Point(self.x - other.x, self.y - other.y) def __iadd__(self, other): self.x += other.x self.y += other.y return self def __isub__(self, other): self.x -= other.x self.y -= other.y return self def __matmul__(self,other): # Dot product. Abusing overloading. Sue me. return self.dot(other) def dot(self, other): return self.x * other.x + self.y * other.y def clone(self): """Clone a point, returning a new object with the same co-ordinates.""" return Point(self.x,self.y) def rounded(self): """Return a point with the co-ordinates truncated to integers""" return Point(int(self.x),int(self.y)) def lerp(self, other, t): """Interpolate between two points, at time t.""" return self * (1-t) + other * (t) @property def squareMagnitude(self): """Interpreting this point as a vector, returns the squared magnitude (Euclidean length) of the vector.""" return self.x*self.x + self.y*self.y @property def magnitude(self): """Interpreting this point as a vector, returns the magnitude (Euclidean length) of the vector.""" return math.sqrt(self.squareMagnitude) def toUnitVector(self): """Divides this point by its magnitude, returning a vector of length 1.""" mag = self.magnitude if mag == 0.0: mag = 1.0 return Point(self.x/mag, self.y/mag) @property def angle(self): """Interpreting this point as a vector, returns the angle in radians of the vector.""" return math.atan2(self.y,self.x) @property def slope(self): """Returns slope y/x""" if self.x == 0: return 0 return self.y / self.x @classmethod def fromAngle(self,angle): """Given an angle in radians, return a unit vector representing that angle.""" return Point(math.cos(angle), math.sin(angle)).toUnitVector() def rotated(self,around,by): """Return a new point found by rotating this point around another point, by an angle given in radians.""" delta = around - self oldangle = delta.angle newangle = oldangle + by unitvector = Point.fromAngle(newangle) new = around - unitvector * delta.magnitude return new def rotate(self,around,by): """Mutate this point by rotating it around another point, by an angle given in radians.""" new = self.rotated(around, by) self.x = new.x self.y = new.y def squareDistanceFrom(self,other): """Returns the squared Euclidean distance between this point and another.""" return (self.x - other.x) * (self.x - other.x) + (self.y - other.y) * (self.y - other.y) def distanceFrom(self,other): """Returns the Euclidean distance between this point and another.""" return math.sqrt(self.squareDistanceFrom(other)) def transformed(self, transformation): m = transformation.matrix x, y = self.x, self.y a1, a2, b1 = m[0] a3, a4, b2 = m[1] xPrime = a1 * x + a2 * y + b1 yPrime = a3 * x + a4 * y + b2 return Point(xPrime, yPrime) def transform(self, transformation): new = self.transformed(transformation) self.x = new.x self.y = new.y python-beziers-0.5.0+dfsg1/beziers/quadraticbezier.py000066400000000000000000000066301432301424100226700ustar00rootroot00000000000000from beziers.segment import Segment from beziers.line import Line from beziers.point import Point from beziers.utils import quadraticRoots, isclose from beziers.utils.arclengthmixin import ArcLengthMixin my_epsilon = 2e-7 class QuadraticBezier(ArcLengthMixin,Segment): def __init__(self, start, c1,end): self.points = [start,c1,end] self._range = [0,1] def __repr__(self): return "B<%s-%s-%s>" % (self[0],self[1],self[2]) @classmethod def fromRepr(klass,text): import re p = re.compile("^B<(<.*?>)-(<.*?>)-(<.*?>)>$") m = p.match(text) points = [ Point.fromRepr(m.group(t)) for t in range(1,4) ] return klass(*points) def pointAtTime(self,t): """Returns the point at time t (0->1) along the curve.""" x = (1 - t) * (1 - t) * self[0].x + 2 * (1 - t) * t * self[1].x + t * t * self[2].x; y = (1 - t) * (1 - t) * self[0].y + 2 * (1 - t) * t * self[1].y + t * t * self[2].y; return Point(x,y) def tOfPoint(self,p): """Returns the time t (0->1) of a point on the curve.""" xroots = quadraticRoots(self[0].x - 2*self[1].x + self[2].x, 2 * (self[1].x-self[0].x), self[0].x - p.x) yroots = quadraticRoots(self[0].y - 2*self[1].y + self[2].y, 2 * (self[1].y-self[0].y), self[0].y - p.y) if not len(xroots) or not len(yroots): return -1 for x in xroots: for y in yroots: if -my_epsilon < x - y < my_epsilon: return x return -1 def splitAtTime(self,t): """Returns two segments, dividing the given segment at a point t (0->1) along the curve.""" p4 = self[0].lerp(self[1],t) p5 = self[1].lerp(self[2],t) p7 = p4.lerp(p5,t) return (QuadraticBezier(self[0],p4,p7), QuadraticBezier(p7,p5,self[2])) def derivative(self): """Returns a `Line` representing the derivative of this curve.""" return Line( (self[1]-self[0])*2, (self[2]-self[1])*2 ) 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 def _findRoots(self,dimension): if dimension == "x": return quadraticRoots(self[0].x - 2*self[1].x + self[2].x, 2 * (self[1].x-self[0].x), self[0].x) elif dimension == "y": return quadraticRoots(self[0].y - 2*self[1].y + self[2].y, 2 * (self[1].y-self[0].y), self[0].y) else: raise "Meh" def _findDRoots(self): d1 = (self[0].x-2*self[1].x+self[2].x) d2 = (self[0].y-2*self[1].y+self[2].y) roots = [] if d1 != 0: r1 = (self[0].x-self[1].x)/d1 roots.append(r1) if d2 != 0: r2 = (self[0].y-self[1].y)/d2 roots.append(r2) return [ r for r in roots if r >= 0.01 and r <= 0.99 ] def findExtremes(self): """Returns a list of time `t` values for extremes of the curve.""" return self._findDRoots() @property def area(self): """Returns the signed area between the curve and the y-axis""" return (2*(self[1].x*self[0].y - self[0].x*self[1].y - self[1].x*self[2].y + self[2].x*self[1].y) + 3*(self[2].x*self[2].y - self[0].x*self[0].y) + self[2].x*self[0].y - self[0].x*self[2].y) / 6. def toCubicBezier(self): """Converts the quadratic bezier to a CubicBezier""" from beziers.cubicbezier import CubicBezier return CubicBezier( self[0], self[0]*(1/3.) + self[1]*(2/3.), self[1]*(2/3.) + self[2]*(1/3.), self[2] ) python-beziers-0.5.0+dfsg1/beziers/segment.py000066400000000000000000000131041432301424100211460ustar00rootroot00000000000000from beziers.point import Point from beziers.affinetransformation import AffineTransformation from beziers.utils.samplemixin import SampleMixin from beziers.utils.intersectionsmixin import IntersectionsMixin from beziers.boundingbox import BoundingBox class Segment(IntersectionsMixin,SampleMixin,object): """A segment is part of a path. Although this package is called `beziers.py`, it's really for font people, and paths in the font world are made up of cubic Bezier curves, lines and (if you're dealing with TrueType) quadratic Bezier curves. Each of these things is represented as an object derived from the Segment base class. So, when you inspect the path in the segment representation, you will get a list of CubicBezier, Line and QuadraticBezier objects, all of which derive from Segment. Because of this, a Segment can have two, three or four elements: lines have two end points; quadratic Beziers have a start, a control point and an end point; cubic have a start, two control points and an end point. You can pretend that a Segment object is an array and index it like one:: q = CubicBezier( Point(122,102), Point(35,200), Point(228,145), Point(190,46) ) start, cp1, cp2, end = q[0],q[1],q[2],q[3] You can also access the start and end points like so:: start = q.start end = q.end """ def __getitem__(self, item): return self.points[item] def __setitem__(self, key, item): self.points[key] = item def __len__(self): return len(self.points) def __eq__(self,other): if self.order != other.order: return False for p in range(0,self.order): if self[p] != other[p]: return False return True def __hash__(self): return hash(tuple(self.points)) def __ne__(self,other): return not self.__eq__(other) def clone(self): """Returns a new Segment which is a copy of this segment.""" klass = self.__class__ return klass(*[ p.clone() for p in self.points ]) def round(self): """Rounds the points of segment to integer coordinates.""" self.points = [ p.rounded() for p in self.points ] @property def order(self): return len(self.points) @property def start(self): """Returns a Point object representing the start of this segment.""" return self.points[0] @property def end(self): """Returns a Point object representing the end of this segment.""" return self.points[-1] @property def startAngle(self): return (self.points[1]-self.points[0]).angle @property def endAngle(self): return (self.points[-1]-self.points[-2]).angle def tangentAtTime(self,t): """Returns a `Point` representing the unit vector of tangent at time `t`.""" return self.derivative().pointAtTime(t).toUnitVector() def normalAtTime(self,t): """Returns a `Point` representing the normal (rotated tangent) at time `t`.""" tan = self.tangentAtTime(t) return Point(-tan.y,tan.x) def translated(self,vector): """Returns a *new Segment object* representing the translation of this segment by the given vector. i.e.:: >>> l = Line(Point(0,0), Point(10,10)) >>> l.translated(Point(5,5)) L<<5.0,5.0>--<15.0,15.0>> >>> l L<<0.0,0.0>--<10.0,10.0>> """ klass = self.__class__ return klass(*[ p+vector for p in self.points ]) def rotated(self,around, by): """Returns a *new Segment object* representing the rotation of this segment around the given point and by the given angle. i.e.:: >>> l = Line(Point(0,0), Point(10,10)) >>> l.rotated(Point(5,5), math.pi/2) L<<10.0,-8.881784197e-16>--<-8.881784197e-16,10.0>> """ klass = self.__class__ pNew = [ p.clone() for p in self.points] for p in pNew: p.rotate(around,by) return klass(*pNew) def scaled(self,bx): """Returns a *new Segment object* representing the scaling of this segment by the given magnification. i.e.:: >>> l = Line(Point(0,0), Point(10,10)) >>> l.scaled(2) L<<0,0>--<20,20>> """ klass = self.__class__ pNew = [ p * bx for p in self.points] return klass(*pNew) def transformed(self, transformation): """Returns a *new Segment object* transformed by the given AffineTransformation matrix.""" klass = self.__class__ pNew = [ p.clone() for p in self.points] for p in pNew: p.transform(transformation) return klass(*pNew) def alignmentTransformation(self): m = AffineTransformation.translation(self.start * -1) m.rotate((self.end.transformed(m)).angle * -1) return m def aligned(self): """Returns a *new Segment object* aligned to the origin. i.e. with the first point translated to the origin (0,0) and the last point with y=0. Obviously, for a `Line` this is a bit pointless, but it's quite handy for higher-order curves.""" return self.transformed(self.alignmentTransformation()) def lengthAtTime(self, t): """Returns the length of the subset of the path from the start up to the point t (0->1), where 1 is the end of the whole curve.""" s1,_ = self.splitAtTime(t) return s1.length def reversed(self): """Returns a new segment with the points reversed.""" klass = self.__class__ return klass(*list(reversed(self.points))) def bounds(self): """Returns a BoundingBox object for this segment.""" bounds = BoundingBox() ex = self.findExtremes() ex.append(0) ex.append(1) for t in ex: bounds.extend(self.pointAtTime(t)) return bounds @property def hasLoop(self): """Returns True if the segment has a loop. (Only possible for cubics.)""" return False python-beziers-0.5.0+dfsg1/beziers/utils/000077500000000000000000000000001432301424100202735ustar00rootroot00000000000000python-beziers-0.5.0+dfsg1/beziers/utils/__init__.py000066400000000000000000000007321432301424100224060ustar00rootroot00000000000000from math import sqrt try: from math import isclose except ImportError: def isclose(a, b): return -1e-9 < a - b < 1e-9 def quadraticRoots(a, b, c): """Returns real roots of at^2 + bt + c = 0 if 0 < root < 1""" roots = [] if a != 0.0 and b*b-4*a*c > 0.0: x = -b / (2 * a) y = sqrt(b*b - 4*a*c) / (2*a) t1 = x - y if 0.0 <= t1 <= 1.0: roots.append(t1) t2 = x + y if 0.0 <= t2 <= 1.0: roots.append(t2) return roots python-beziers-0.5.0+dfsg1/beziers/utils/alphashape.py000066400000000000000000000030731432301424100227560ustar00rootroot00000000000000from shapely.ops import cascaded_union, polygonize from scipy.spatial import Delaunay import shapely.geometry as geometry import numpy as np import math def alpha_shape(points, alpha): """ Compute the alpha shape (concave hull) of a set of points. @param points: Iterable container of points. @param alpha: alpha value to influence the gooeyness of the border. Smaller numbers don't fall inward as much as larger numbers. Too large, and you lose everything! """ if len(points) < 4: # When you have a triangle, there is no sense # in computing an alpha shape. return geometry.MultiPoint(list(points)).convex_hull coords = np.array([point.coords[0] for point in points]) tri = Delaunay(coords) triangles = coords[tri.vertices] a = ((triangles[:,0,0] - triangles[:,1,0]) ** 2 + (triangles[:,0,1] - triangles[:,1,1]) ** 2) ** 0.5 b = ((triangles[:,1,0] - triangles[:,2,0]) ** 2 + (triangles[:,1,1] - triangles[:,2,1]) ** 2) ** 0.5 c = ((triangles[:,2,0] - triangles[:,0,0]) ** 2 + (triangles[:,2,1] - triangles[:,0,1]) ** 2) ** 0.5 s = ( a + b + c ) / 2.0 areas = (s*(s-a)*(s-b)*(s-c)) ** 0.5 circums = a * b * c / (4.0 * areas) filtered = triangles[circums < (1.0 / alpha)] edge1 = filtered[:,(0,1)] edge2 = filtered[:,(1,2)] edge3 = filtered[:,(2,0)] edge_points = np.unique(np.concatenate((edge1,edge2,edge3)), axis = 0).tolist() m = geometry.MultiLineString(edge_points) triangles = list(polygonize(m)) return cascaded_union(triangles), edge_points python-beziers-0.5.0+dfsg1/beziers/utils/arclengthmixin.py000066400000000000000000000005521432301424100236630ustar00rootroot00000000000000from beziers.utils.legendregauss import Tvalues, Cvalues import math class ArcLengthMixin: @property def length(self): d = self.derivative() z = 0.5 _sum = 0 for i in range(0,len(Tvalues)): t = z * Tvalues[i] + z p = d.pointAtTime(t) arc = math.sqrt(p.x * p.x + p.y * p.y) _sum += Cvalues[i] * arc return _sum * zpython-beziers-0.5.0+dfsg1/beziers/utils/booleanoperationsmixin.py000066400000000000000000000146221432301424100254420ustar00rootroot00000000000000import sys from beziers.path.representations.Segment import SegmentRepresentation from beziers.utils.intersectionsmixin import Intersection import logging import pyclipper from beziers.line import Line from beziers.point import Point class BooleanOperationsMixin: def getSelfIntersections(self): """Returns a list of a path's self-intersections as Intersection objects.""" segs = self.asSegments() intersections = [] for seg in segs: l = seg.hasLoop if l and l[0]>0 and l[0]<1 and l[1]>0 and l[0]<1: intersections.append(Intersection(seg,l[0], seg,l[1])) for i1 in range(0, len(segs)): for i2 in range (i1+1, len(segs)): for i in segs[i1].intersections(segs[i2]): if i.t1 > 1e-2 and i.t1 < 1-1e-2: intersections.append(i) return intersections def removeOverlap(self): """Resolves a path's self-intersections by 'walking around the outside'.""" if not self.closed: raise "Can only remove overlap on closed paths" splitlist = [] splitpoints = {} def roundoff(point): return (int(point.x*1),int(point.y*1)) for i in self.getSelfIntersections(): splitlist.append((i.seg1,i.t1)) splitlist.append((i.seg2,i.t2)) splitpoints[roundoff(i.point)] = {"in":[], "out": []} self.splitAtPoints(splitlist) # Trace path segs = self.asSegments() for i in range(0,len(segs)): seg = segs[i] if i < len(segs)-1: seg.next = segs[i+1] else: seg.next = segs[0] seg.visited = False segWinding = self.windingNumberOfPoint(seg.pointAtTime(0.5)) seg.windingNumber = segWinding if roundoff(seg.end) in splitpoints: splitpoints[roundoff(seg.end)]["in"].append(seg) if roundoff(seg.start) in splitpoints: splitpoints[roundoff(seg.start)]["out"].append(seg) newsegs = [] copying = True logging.debug("Split points:", splitpoints) seg = segs[0] while not seg.visited: logging.debug("Starting at %s, visiting %s" % (seg.start, seg)) newsegs.append(seg) seg.visited = True if roundoff(seg.end) in splitpoints and len(splitpoints[roundoff(seg.end)]["out"]) > 0: logging.debug("\nI am at %s and have a decision: " % seg.end) inAngle = seg.tangentAtTime(1).angle logging.debug("My angle is %s" % inAngle) # logging.debug("Options are: ") # for s in splitpoints[roundoff(seg.end)]["out"]: # logging.debug(s.end, s.tangentAtTime(0).angle, self.windingNumberOfPoint(s.pointAtTime(0.5))) # Filter out the inside points splitpoints[roundoff(seg.end)]["out"] = [ o for o in splitpoints[roundoff(seg.end)]["out"] if o.windingNumber < 2] splitpoints[roundoff(seg.end)]["out"].sort(key = lambda x: x.tangentAtTime(0).angle-inAngle) seg = splitpoints[roundoff(seg.end)]["out"].pop(-1) # seg = seg.next # logging.debug("I chose %s\n" % seg) else: seg = seg.next self.activeRepresentation = SegmentRepresentation(self,newsegs) 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 union(self,other, flat=False): """Returns a list of Bezier paths representing the union of the two input paths.""" return self.clip(other, pyclipper.CT_UNION, flat) def intersection(self,other, flat=False): """Returns a list of Bezier paths representing the intersection of the two input paths.""" return self.clip(other, pyclipper.CT_INTERSECTION, flat) def difference(self,other, flat=False): """Returns a list of Bezier paths representing the first input path subtracted from the second.""" return self.clip(other, pyclipper.CT_DIFFERENCE, flat) python-beziers-0.5.0+dfsg1/beziers/utils/curvedistance.py000066400000000000000000000150031432301424100235030ustar00rootroot00000000000000from beziers.cubicbezier import CubicBezier from beziers.quadraticbezier import QuadraticBezier from beziers.line import Line from beziers.point import Point import math """ This implements (possibly badly) the algorithm in "Computing the minimum distance between two Bézier curves", Chen et al., *Journal of Computational and Applied Mathematics* 229(2009) 294-301 I could not get the clever subdivision heuristic working (divide the parameter space into two) but divided it into four instead. Using their heuristic of dividing at i/2n instead of 1/2 does seem to give better results, though. The checks for Property 1 and Property 2 never seem to be fired either. So I'm probably doing something wrong. The results look fine but there's probably more computations happening than needed. I use memoization and an initial sanity check (whether alpha is more than the current best alpha) to cut down on the computations but I'm sure it could still be improved. """ def C(y, x): try: binom = math.factorial(x) // math.factorial(y) // math.factorial(x - y) except ValueError: binom = 0 return binom def A_r(r, P): n = len(P) - 1 # Order upsilon = min(r, n) theta = max(0, r - n) summand = 0 for i in range(theta, upsilon + 1): summand += P[i].dot(P[r - i]) * C(i, n) * C(r - i, n) / C(r, 2 * n) return summand B_k = A_r # But with P -> Q, n -> m, r -> k def C_rk(r, k, bez1, bez2): # These also have parallel factors def factor(P, r_or_k): n = len(P) - 1 upsilon = min(r_or_k, n) theta = max(0, r_or_k - n) summand = Point(0, 0) for i in range(theta, upsilon + 1): summand += P[i] * C(i, n) * C(r_or_k - i, n) / C(r_or_k, 2 * n) return summand left = factor(bez1, r) right = factor(bez2, k) return left.dot(right) def basis_function(n, i, u): res = C(i, n) * (1 - u) ** (n - i) * u ** i return res class MinimumCurveDistanceFinder: def __init__(self, bez1, bez2): self.bez1 = bez1 self.bez2 = bez2 self.dCache = {} self.sCache = {} self.bestAlpha = None self.iterations = 0 def D(self, r, k): if (r, k) not in self.dCache: self.dCache[(r, k)] = ( A_r(r, self.bez1) + B_k(k, self.bez2) - 2 * C_rk(r, k, self.bez1, self.bez2) ) return self.dCache[(r, k)] def S(self, u, v): # u,v are times if (u, v) in self.sCache: return self.sCache[(u, v)] n = len(self.bez1) - 1 m = len(self.bez2) - 1 summand = 0 for r in range(0, 2 * n + 1): for k in range(0, 2 * m + 1): summand += ( self.D(r, k) * basis_function(2 * n, r, u) * basis_function(2 * m, k, v) ) self.sCache[(u, v)] = summand return summand def minDist(self, uinterval=(0, 1), vinterval=(0, 1), epsilon=0.001): n = len(self.bez1) - 1 m = len(self.bez2) - 1 self.iterations = self.iterations + 1 umin, umax = uinterval vmin, vmax = vinterval umid = (umin + umax) / 2 vmid = (vmin + vmax) / 2 svalues = [ [self.S(umin, vmin), umin, vmin], [self.S(umin, vmax), umin, vmax], [self.S(umax, vmin), umax, vmin], [self.S(umax, vmax), umax, vmax], ] alpha = min(svalues, key=lambda x: x[0])[0] if self.bestAlpha and alpha > self.bestAlpha: return [alpha, umid, vmid] self.bestAlpha = alpha if abs(umax - umin) <= epsilon or abs(vmax - vmin) <= epsilon: return [alpha, umid, vmid] # # Property 1: Check if any D(r>k) < alpha # # Also find out where the min value is for division isOutside = True minDRK = None minIJ = (None, None) for r in range(0, 2 * n): for k in range(0, 2 * m): drk = self.D(r, k) if drk < alpha: isOutside = False if not minDRK or drk < minDRK: minDrk = drk minIJ = (r, k) if isOutside: return [alpha, umid, vmid] # This one never seems to work # # Property 2: Boundary check atBoundary0onBez1 = True atBoundary1onBez1 = True atBoundary0onBez2 = True atBoundary1onBez2 = True for i in range(0, 2 * n): for j in range(0, 2 * m): dij = self.D(i, j) dkj = self.D(0, j) if dij < dkj: atBoundary0onBez1 = False dkj = self.D(2 * n, j) if dij < dkj: atBoundary1onBez1 = False dkj = self.D(i, 0) if dij < dkj: atBoundary0onBez2 = False dkj = self.D(i, 2 * n) if dij < dkj: atBoundary1onBez2 = False if atBoundary0onBez1 and atBoundary0onBez2: return svalues[0] if atBoundary0onBez1 and atBoundary1onBez2: return svalues[1] if atBoundary1onBez1 and atBoundary0onBez2: return svalues[2] if atBoundary1onBez1 and atBoundary1onBez2: return svalues[3] newuMid = umin + (umax - umin) * (minIJ[0] / (2 * n)) newvMid = vmin + (vmax - vmin) * (minIJ[1] / (2 * m)) # 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) python-beziers-0.5.0+dfsg1/beziers/utils/curvefitter.py000066400000000000000000000225241432301424100232140ustar00rootroot00000000000000from beziers.point import Point from beziers.cubicbezier import CubicBezier import sys import math def B0(u): return (1.0 - u) * (1.0 - u) * (1.0 - u) def B1(u): return 3 * u * (1.0 - u) * (1.0 - u) def B2(u): return 3 * u * u * (1.0 - u) def B3(u): return u * u * u class CurveFit: # Algorithm lifted from Inkscape @classmethod def fitCurve(self, data, error, cornerTolerance, maxSegments): # We want to uniqify the points but maintaining order # (so we can't use a set). An ordered set would be too heavy for this. keys = {} def filterSeen(x): if hash(x) in keys: return False keys[hash(x)] = 1 return True data = list(filter(filterSeen, data)) if len(data) < 2: return return self._fitCurve(data, None, None, error, cornerTolerance, maxSegments) @classmethod def fitLine(self, data, tHat1, tHat2): p0, p3 = data[0], data[-1] dist = p0.distanceFrom(p3)/3.0 if tHat1: p1 = p0 + tHat1 * dist else: p1 = ((p0 * 2.0) + p3) / 3.0 if tHat2: p2 = p3 + tHat2 * dist else: p2 = ((p3 * 2.0) + p0) / 3.0 return CubicBezier(p0, p1, p2, p3) @classmethod def estimateBi(self, bez, data, u): num = Point(0, 0) den = 0.0 for (coeff, datum) in zip(u, data): b0,b1,b2,b3 = B0(coeff), B1(coeff), B2(coeff), B3(coeff) num.x += b1 * (b0 * bez[0].x + b2 * bez[2].x + b3 * bez[3].x - datum.x) num.y += b1 * (b0 * bez[0].y + b2 * bez[2].y + b3 * bez[3].y - datum.y) den -= b1 * b1 if den != 0.0: bez[1] = num/den else: bez[1] = bez[0].lerp(bez[3], 1/3.0) @classmethod def _leftTangent(self, data): return (data[1]-data[0]).toUnitVector() @classmethod def _rightTangent(self, data): return (data[-1]-data[-2]).toUnitVector() @classmethod def centerTangent(self, data, center): if data[center + 1] == data[center-1]: ret = data[center] - data[center - 1] ret.rotate(Point(0,0), math.pi / 2.0) return ret.toUnitVector() else: ret = data[center - 1] - data[center+1] return ret.toUnitVector() @classmethod def leftTangent(self, data, tolerance): i = 1 while True: pi = data[i] t = pi - data[0] distSq = t.__matmul__(t) if (tolerance < distSq): return t.toUnitVector() i = i + 1 if i == len(data): if distSq == 0: return self._leftTangent(data) else: return t.toUnitVector() @classmethod def rightTangent(self, data, tolerance): i = len(data)-2 while True: pi = data[i] t = pi - data[-1] distSq = t.__matmul__(t) if (tolerance < distSq): return t.toUnitVector() i = i - 1 if i == 0: if distSq == 0: return self._rightTangent(data) else: return t.toUnitVector() @classmethod def generateBezier(self, data, u, tHat1, tHat2, tolerance_sq): est_tHat1 = tHat1 est_tHat2 = tHat2 if not est_tHat1: est_tHat1 = self.leftTangent(data, tolerance_sq) if not est_tHat2: est_tHat2 = self.rightTangent(data, tolerance_sq) bez = self.estimateLengths(data, u, est_tHat1, est_tHat2) if not tHat1: # print("Refining estimate %s" % bez) self.estimateBi(bez, data, u) # print("Result of estimateBi %s" % bez) if bez[1].distanceFrom(bez[0]) > sys.float_info.epsilon: est_tHat1 = (bez[1]-bez[0]).toUnitVector() # print("tHat1 is now %s" % est_tHat1) bez = self.estimateLengths(data, u, est_tHat1, est_tHat2) return bez @classmethod def estimateLengths(self, data, u, tHat1, tHat2): C = [ [ 0.0, 0.0], [ 0.0, 0.0 ]] X = [ 0.0, 0.0 ] # bez = SCBezier() for (coeff,datum) in zip(u, data): (b0,b1,b2,b3) = (B0(coeff), B1(coeff), B2(coeff), B3(coeff)) (a1, a2) = (tHat1 * b1, tHat2 * b2) C[0][0] += a1.__matmul__(a1) C[0][1] += a1.__matmul__(a2) C[1][0] = C[0][1] C[1][1] += a2.__matmul__(a2) s1 = data[0] * (b0+b1) shortfall = datum - s1 shortfall = shortfall - (data[-1] * (b2+b3)) X[0] += a1.__matmul__(shortfall) X[1] += a2.__matmul__(shortfall) det_C0_C1 = C[0][0] * C[1][1] - C[1][0] * C[0][1] if det_C0_C1 != 0.0: det_C0_X = C[0][0] * X[1] - C[0][1] * X[0] det_X_C1 = X[0] * C[1][1] - X[1] * C[0][1] alpha_l = det_X_C1 / det_C0_C1 alpha_r = det_C0_X / det_C0_C1 else: c0 = C[0][0] + C[0][1] if c0 != 0: alpha_l = X[0] / c0 alpha_r = X[0] / c0 else: alpha_l = 0.0 alpha_r = 0.0 if alpha_l < 1.0e-6 or alpha_r < 1.0e-6: alpha_l = data[-1].distanceFrom(data[0]) / 3.0 alpha_r = alpha_l return CubicBezier(data[0], tHat1 * alpha_l + data[0], tHat2 * alpha_r + data[-1], data[-1]) @classmethod def chordLengthParameterize(self, points): u = [0.0] v = 0 for i in range(1, len(points)): v += points[i].distanceFrom(points[i-1]) u.append(v) return [ n/v for n in u ] @classmethod def newtonRaphsonFind(self, bez, point, u): q0 = bez.pointAtTime(u) q1 = bez.derivative().pointAtTime(u) q2 = bez.derivative().derivative().pointAtTime(u) diff = q0 - point numerator = diff.__matmul__(q1) denominator = (q1.__matmul__(q1)) + (diff.__matmul__(q2)) if denominator > 0.0: improvedU = u - ( numerator / denominator ) else: if numerator > 0.0: improvedU = u * 0.98 - 0.01 elif numerator < 0.0: improvedU = 0.031 + u * 0.98 else: improvedU = u if (improvedU < 0): improvedU = 0 if (improvedU > 1): improvedU = 1 dist2 = q0.distanceFrom(point) proportion = 0.125 while True: proportion += 0.125 newDistance2 = point.distanceFrom(bez.pointAtTime(improvedU)) if newDistance2 > dist2: if proportion > 1.0: improvedU = u break improvedU = ( ( 1 - proportion ) * improvedU + proportion * u) else: break return improvedU @classmethod def reparameterize(self, bez, points, params): for i in range(0, len(points)): params[i] = self.newtonRaphsonFind(bez, points[i], params[i]) @classmethod def computeHook(self, ffrom, to, parameter, bez, cornerTolerance): p = bez.pointAtTime(parameter) dist = p.distanceFrom(ffrom.lerp(to, 0.5)) if dist < cornerTolerance: return 0 allowed = ffrom.distanceFrom(to) + cornerTolerance return dist / allowed @classmethod def computeMaxError(self, bez, points, params, tolerance, cornerTolerance): maxSqDist = 0.0 maxHookRatio = 0.0 splitPoint = 0 snapEnd = 0 prev = bez[0] for i in range(1, len(points)): cur = bez.pointAtTime(params[i]) distSq = cur.squareDistanceFrom(points[i]) if distSq > maxSqDist: maxSqDist = distSq splitPoint = i hookRatio = self.computeHook(prev, cur, (params[i]+params[i-1])/(2.0), bez, cornerTolerance) if maxHookRatio < hookRatio: maxHookRatio = hookRatio snapEnd = i prev = cur distRatio = math.sqrt(maxSqDist) / tolerance if maxHookRatio <= distRatio: return (distRatio, splitPoint) else: splitPoint = snapEnd-1 return (-maxHookRatio, splitPoint) @classmethod def _fitCurve(self, points, tangent1, tangent2, error, cornerTolerance, maxSegments): if len(points) == 0: return if len(points) == 2: return [self.fitLine(points, tangent1, tangent2)] maxIterations = 3 isCorner = False u = self.chordLengthParameterize(points) if u[-1] == 0.0: return [] bez = self.generateBezier(points, u, tangent1, tangent2, error) self.reparameterize(bez, points, u) tolerance = math.sqrt(error + 1e-9) (maxErrorRatio, splitPoint) = self.computeMaxError(bez, points, u, tolerance, cornerTolerance) if abs(maxErrorRatio) <= 1.0: return [bez] if ( 0.0 <= maxErrorRatio and maxErrorRatio <= 3.0 ): for _ in range(0, maxIterations+1): bez = self.generateBezier(points, u, tangent1, tangent2, error) (maxErrorRatio, splitPoint) = self.computeMaxError( bez, points, u, tolerance, cornerTolerance) if abs(maxErrorRatio) <= 1.0: return [bez] isCorner = maxErrorRatio < 0 if isCorner: if splitPoint == 0: if tangent1 is None: splitPoint = splitPoint + 1 else: return self._fitCurve( points, Point(0.0,0.0), tangent2, error, cornerTolerance, maxSegments) elif splitPoint == len(points) - 1: if tangent2 is None: splitPoint = splitPoint - 1 else: return self._fitCurve(points, tangent1, Point(0.0,0.0), error, cornerTolerance, maxSegments) if 1 < maxSegments: segmentsRemaining = maxSegments - 1 if isCorner: if not (0 < splitPoint and splitPoint < len(points) - 1): return [] recTHat1 = Point(0.0,0.0) recTHat2 = Point(0.0,0.0) else: recTHat2 = self.centerTangent(points, splitPoint) recTHat1 = recTHat2 * -1 lPoints = points[:splitPoint+1] rPoints = points[splitPoint:] lbeziers = self._fitCurve(lPoints, tangent1, recTHat2, error, cornerTolerance, segmentsRemaining) if lbeziers: segmentsRemaining = segmentsRemaining - len(lbeziers) rbeziers = self._fitCurve(rPoints, recTHat1, tangent2, error, cornerTolerance, segmentsRemaining) return lbeziers + rbeziers else: return [] python-beziers-0.5.0+dfsg1/beziers/utils/intersectionsmixin.py000066400000000000000000000127571432301424100246170ustar00rootroot00000000000000import sys from beziers.point import Point from beziers.utils import isclose from decimal import Decimal my_epsilon = 2e-7 class Intersection: """An object representing an intersection between two segments. The location of the intersection on the first segment is accessible as `i.seg1`, `i.t1`. The location of the intersection on the second segments is `i.seg2`, `i.t2` and `i.point2` respectively. You can get a Point object by calling `i.point`.""" def __init__(self,seg1,t1,seg2,t2): self.seg1 = seg1 self.t1 = t1 self.point = seg1.pointAtTime(t1) self.seg2 = seg2 self.t2 = t2 def __repr__(self): return "I<%s t1=%f t2=%f>" % (self.point, self.t1, self.t2) class IntersectionsMixin: # This isn't something we mix into different classes but I'm # just putting it here to keep the code tidy. def intersections(self, other, limited = True): """Returns an array of `Intersection` objects representing the intersections between this Segment and another Segment.""" # Arrange by degree if len(other.points) > len(self.points): self,other = other,self if len(self.points) == 4 or len(self.points)==3: if len(other.points) == 4 or len(other.points)==3: inter = self._curve_curve_intersections(other) if len(other.points) == 2: inter = self._curve_line_intersections(other) elif len(self.points) == 2 and len(other.points) == 2: inter = self._line_line_intersections(other) else: raise ValueError("Couldn't work out which intersection function to use") def withinRange(t): if t < my_epsilon: return False if t > 1.0 + my_epsilon: return False return True if limited: return [ i for i in inter if withinRange(i.t1) and withinRange(i.t2) ] else: return inter raise "Don't know how to compute intersections of a %s and a %s" % (type(self), type(other)) def _bothPointsAreOnSameSideOfOrigin(self, a,b,c): xDiff = (a.x-c.x) * (b.x-c.x) yDiff = (a.y-c.y) * (b.y-c.y) return not (xDiff <= 0.0 and yDiff <= 0.0) def _line_line_intersections(self, other): a = self.start b = self.end c = other.start d = other.end if isclose(c.x, d.x) and isclose(a.x, b.x): return [] if isclose(c.y, d.y) and isclose(a.y, b.y): return [] if c == d or a == b: return [] if isclose(b.x,a.x): x = a.x slope34 = ( d.y - c.y) / ( d.x - c.x ) y = slope34 * ( x - c.x ) + c.y p = Point(x,y) i = Intersection(self,self.tOfPoint(p), other, other.tOfPoint(p)) return [ i ] if isclose(c.x,d.x): x = c.x slope12 = ( b.y - a.y) / ( b.x - a.x ) y = slope12 * ( x - a.x ) + a.y p = Point(x,y) i = Intersection(self,self.tOfPoint(p), other, other.tOfPoint(p)) return [ i ] slope12 = ( b.y - a.y) / ( b.x - a.x ) slope34 = ( d.y - c.y) / ( d.x - c.x ) if abs(slope12 - slope34) < my_epsilon: return [ ] x = ( slope12 * a.x - a.y - slope34 * c.x + c.y ) / ( slope12 - slope34 ) y = slope12 * ( x - a.x ) + a.y intersection = Point(x,y) if (self._bothPointsAreOnSameSideOfOrigin(intersection, b, a) and self._bothPointsAreOnSameSideOfOrigin(intersection, c, d)): return [ Intersection(self,self.tOfPoint(intersection, its_on_the_line_i_swear=True), other, other.tOfPoint(intersection, its_on_the_line_i_swear=True)) ] return [] def _curve_line_intersections_t(self,line): t = line.alignmentTransformation() c1 = self.transformed(t) intersections = c1._findRoots("y") return sorted(intersections) def _curve_line_intersections(self,line): inter = [] for t in self._curve_line_intersections_t(line): inter.append(Intersection(self,t,line,line.tOfPoint(self.pointAtTime(t), its_on_the_line_i_swear=True))) return inter def _curve_curve_intersections_t(self,other, precision=1e-3): assert(len(self.points) > 2 and len(other.points) > 2) if not (self.bounds().overlaps(other.bounds())): return [] if self.bounds().area < precision and other.bounds().area < precision: return [ [ 0.5*(self._range[0] + self._range[1]), 0.5*(other._range[0] + other._range[1]), ] ] def xmap(v,ts,te): return ts+(te-ts)*v c11, c12 = self.splitAtTime(0.5) c11._range = [ self._range[0], xmap(0.5,self._range[0],self._range[1])] c12._range = [ xmap(0.5,self._range[0],self._range[1]), self._range[1]] c21, c22 = other.splitAtTime(0.5) c21._range = [ other._range[0], xmap(0.5,other._range[0],other._range[1])] c22._range = [xmap(0.5,other._range[0],other._range[1]), other._range[1]] assert(c11._range[0] < c11._range[1]) assert(c12._range[0] < c12._range[1]) assert(c21._range[0] < c21._range[1]) assert(c22._range[0] < c22._range[1]) found = [] for this in [c11,c12]: for that in [c21,c22]: if this.bounds().overlaps(that.bounds()): found.extend(this._curve_curve_intersections_t(that, precision)) seen = {} numPrecisionDigits = abs(Decimal(str(precision)).as_tuple().exponent) def filterSeen(n): # use (precision - 1) digits to check whether we have already # seen this value key = f"%.{numPrecisionDigits - 1}f" % n[0] if key in seen: return False seen[key] = 1 return True found = filter(filterSeen, found) return found def _curve_curve_intersections(self,other): assert(len(self.points) > 2 and len(other.points) > 2) return [Intersection(self,t[0],other,t[1]) for t in self._curve_curve_intersections_t(other)] python-beziers-0.5.0+dfsg1/beziers/utils/legendregauss.py000066400000000000000000000046111432301424100234770ustar00rootroot00000000000000Tvalues = [ -0.0640568928626056260850430826247450385909, 0.0640568928626056260850430826247450385909, -0.1911188674736163091586398207570696318404, 0.1911188674736163091586398207570696318404, -0.3150426796961633743867932913198102407864, 0.3150426796961633743867932913198102407864, -0.4337935076260451384870842319133497124524, 0.4337935076260451384870842319133497124524, -0.5454214713888395356583756172183723700107, 0.5454214713888395356583756172183723700107, -0.6480936519369755692524957869107476266696, 0.6480936519369755692524957869107476266696, -0.7401241915785543642438281030999784255232, 0.7401241915785543642438281030999784255232, -0.8200019859739029219539498726697452080761, 0.8200019859739029219539498726697452080761, -0.8864155270044010342131543419821967550873, 0.8864155270044010342131543419821967550873, -0.9382745520027327585236490017087214496548, 0.9382745520027327585236490017087214496548, -0.9747285559713094981983919930081690617411, 0.9747285559713094981983919930081690617411, -0.9951872199970213601799974097007368118745, 0.9951872199970213601799974097007368118745 ] Cvalues = [ 0.1279381953467521569740561652246953718517, 0.1279381953467521569740561652246953718517, 0.1258374563468282961213753825111836887264, 0.1258374563468282961213753825111836887264, 0.121670472927803391204463153476262425607, 0.121670472927803391204463153476262425607, 0.1155056680537256013533444839067835598622, 0.1155056680537256013533444839067835598622, 0.1074442701159656347825773424466062227946, 0.1074442701159656347825773424466062227946, 0.0976186521041138882698806644642471544279, 0.0976186521041138882698806644642471544279, 0.086190161531953275917185202983742667185, 0.086190161531953275917185202983742667185, 0.0733464814110803057340336152531165181193, 0.0733464814110803057340336152531165181193, 0.0592985849154367807463677585001085845412, 0.0592985849154367807463677585001085845412, 0.0442774388174198061686027482113382288593, 0.0442774388174198061686027482113382288593, 0.0285313886289336631813078159518782864491, 0.0285313886289336631813078159518782864491, 0.0123412297999871995468056670700372915759, 0.0123412297999871995468056670700372915759 ]python-beziers-0.5.0+dfsg1/beziers/utils/linesweep.py000066400000000000000000000034101432301424100226360ustar00rootroot00000000000000from collections import deque def dequefilter(deck, condition): for _ in range(0, len(deck)): item = deck.popleft() if condition(item): deck.append(item) def bbox_intersections(seta, setb): active_a = deque([]) active_b = deque([]) instructions = [] intersections = [] def add_to(o, bounds, l): l.append( (o, bounds) ) if l == active_a: other = active_b else: other = active_a for (o2, bounds2) in other: if bounds.overlaps(bounds2): intersections.append( (o, o2) ) def remove_from(o, bounds, l): dequefilter(l, lambda i: i[0] != o) for a in seta: bounds = a.bounds() instructions.append((bounds.left, a, bounds, add_to, active_a)) instructions.append((bounds.right, a, bounds, remove_from, active_a)) for b in setb: bounds = b.bounds() instructions.append((bounds.left, b, bounds, add_to, active_b)) instructions.append((bounds.right, b, bounds, remove_from, active_b)) instructions = sorted(instructions, key=lambda i:i[0]) for key, o, bounds, verb, activelist in instructions: verb(o, bounds, activelist) return intersections if __name__ == "__main__": from beziers.path.geometricshapes import Rectangle from beziers.point import Point left = [ Rectangle(100,70,origin=Point(50,50)), Rectangle(30,100,origin=Point(80,150)), Rectangle(50,50,origin=Point(200,0)), ] right = [ Rectangle(100,30,origin=Point(120,0)), Rectangle(50,50,origin=Point(0,0)), Rectangle(100,250,origin=Point(30,200)), ] import matplotlib.pyplot as plt fig, ax = plt.subplots() for p in left: p.clone().plot(ax,color="blue") for p in right: p.clone().plot(ax,color="red") intersections = bbox_intersections(left,right) for l,r in intersections: [p.plot(ax,fill="green") for p in l.intersection(r)] plt.show() python-beziers-0.5.0+dfsg1/beziers/utils/pens.py000066400000000000000000000020561432301424100216150ustar00rootroot00000000000000from fontTools.pens.basePen import BasePen from beziers.path.representations.Nodelist import NodelistRepresentation, Node from beziers.path import BezierPath class BezierPathCreatingPen(BasePen): def __init__(self, *args, **kwargs): super(BezierPathCreatingPen, self).__init__(*args, **kwargs) self.paths = [] self.path = BezierPath() self.nodeList = [] def _moveTo(self, p): self.nodeList = [Node(p[0], p[1], "move")] def _lineTo(self, p): self.nodeList.append(Node(p[0], p[1], "line")) def _curveToOne(self, p1, p2, p3): self.nodeList.append(Node(p1[0], p1[1], "offcurve")) self.nodeList.append(Node(p2[0], p2[1], "offcurve")) self.nodeList.append(Node(p3[0], p3[1], "curve")) def _qCurveToOne(self, p1, p2): self.nodeList.append(Node(p1[0], p1[1], "offcurve")) self.nodeList.append(Node(p2[0], p2[1], "curve")) def _closePath(self): self.path.closed = True self.path.activeRepresentation = NodelistRepresentation(self.path, self.nodeList) self.paths.append(self.path) self.path = BezierPath() python-beziers-0.5.0+dfsg1/beziers/utils/samplemixin.py000066400000000000000000000043571432301424100232040ustar00rootroot00000000000000class SampleMixin(object): def sample(self,samples): """Samples a segment or path a given number of times, returning a list of Point objects. Remember that for a Bezier path, the points are not guaranteed to be distributed at regular intervals along the path. If you want to space your points regularly, use the `regularSample` method instead. .. figure:: sampling.png :scale: 50 % :alt: sample versus regularSample In the figure, the green ticks are generated by `sample`. These are evenly distributed by curve time, but because of the curvature of the curve, there are concentrations of samples around the tighter parts of the curve. The red ticks are generated by `regularSample`, which evenly spaces the samples along the length of the curve. """ step = 1.0 / float(samples) t = 0.0 samples = [] while t <= 1.0: samples.append(self.pointAtTime(t)) t += step if t != 1.0: samples.append(self.pointAtTime(1)) return samples def regularSample(self,samples): """Samples a segment or path a given number of times, returning a list of Point objects, but ensuring that the points are regularly distributed along the length of the curve. This is an expensive operation because I am a lazy programmer.""" return [ self.pointAtTime(t) for t in self.regularSampleTValue(samples) ] def regularSampleTValue(self,samples): """Sometimes you don't want the points, you just want a set of time values (t) which represent regular spaced samples along the curve. Use this method to get a list of time values instead of Point objects.""" # Build LUT; could cache it *if* we knew when to invalidate lut = [] length = self.length if length == 0: return [] step = 1.0 / length t = 0 while t <= 1.0: lut.append( (t,self.lengthAtTime(t)) ) # Inefficient algorithm but computers are getting faster t += step desiredLength = 0.0 rSamples = [] while desiredLength < length: while len(lut) > 0 and lut[0][1] < desiredLength: lut.pop(0) if len(lut) == 0: break rSamples.append(lut[0][0]) desiredLength += length / samples if rSamples[-1] != 1.0: rSamples.append(1.0) return rSamples python-beziers-0.5.0+dfsg1/doc/000077500000000000000000000000001432301424100162355ustar00rootroot00000000000000python-beziers-0.5.0+dfsg1/doc/.gitignore000066400000000000000000000000071432301424100202220ustar00rootroot00000000000000_build python-beziers-0.5.0+dfsg1/doc/Makefile000066400000000000000000000012311432301424100176720ustar00rootroot00000000000000# Minimal makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build SOURCEDIR = . BUILDDIR = _build # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) publish: html cd _build/html && git add . && git commit -m "New docs" && git push python-beziers-0.5.0+dfsg1/doc/conf.py000066400000000000000000000126641432301424100175450ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Configuration file for the Sphinx documentation builder. # # This file does only contain a selection of the most common options. For a # full list see the documentation: # http://www.sphinx-doc.org/en/master/config # -- Path setup -------------------------------------------------------------- # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # import os import sys sys.path.insert(0, os.path.abspath('..')) # -- Project information ----------------------------------------------------- project = 'beziers' copyright = '2018, Simon Cozens' author = 'Simon Cozens' # The short X.Y version version = '' # The full version, including alpha/beta/rc tags release = '' # -- General configuration --------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. # # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.todo', 'sphinx.ext.coverage', ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] source_suffix = '.rst' # The master toctree document. master_doc = 'index' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = None # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] # The name of the Pygments (syntax highlighting) style to use. pygments_style = None # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = 'alabaster' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # # html_theme_options = {} # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Custom sidebar templates, must be a dictionary that maps document names # to template names. # # The default sidebars (for documents that don't match any pattern) are # defined by theme itself. Builtin themes are using these templates by # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', # 'searchbox.html']``. # # html_sidebars = {} # -- Options for HTMLHelp output --------------------------------------------- # Output file base name for HTML help builder. htmlhelp_basename = 'beziersdoc' # -- Options for LaTeX output ------------------------------------------------ latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # # 'preamble': '', # Latex figure (float) alignment # # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ (master_doc, 'beziers.tex', 'beziers Documentation', 'Simon Cozens', 'manual'), ] # -- Options for manual page output ------------------------------------------ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ (master_doc, 'beziers', 'beziers Documentation', [author], 1) ] # -- Options for Texinfo output ---------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ (master_doc, 'beziers', 'beziers Documentation', author, 'beziers', 'One line description of project.', 'Miscellaneous'), ] # -- Options for Epub output ------------------------------------------------- # Bibliographic Dublin Core info. epub_title = project # The unique identifier of the text. This can be a ISBN number # or the project homepage. # # epub_identifier = '' # A unique identification for the text. # # epub_uid = '' # A list of files that should not be packed into the epub file. epub_exclude_files = ['search.html'] # -- Extension configuration ------------------------------------------------- # -- Options for todo extension ---------------------------------------------- # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = True autodoc_member_order = 'bysource' autodoc_default_flags = ['members', 'inherited-members'] python-beziers-0.5.0+dfsg1/doc/index.rst000066400000000000000000000010011432301424100200660ustar00rootroot00000000000000.. beziers documentation master file, created by sphinx-quickstart on Sat Oct 6 22:29:33 2018. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. Beziers.py - a library for manipulating Bezier paths ==================================================== .. toctree:: :maxdepth: 2 :caption: Contents: source/beziers source/beziers.path Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` python-beziers-0.5.0+dfsg1/doc/source/000077500000000000000000000000001432301424100175355ustar00rootroot00000000000000python-beziers-0.5.0+dfsg1/doc/source/beziers.path.representations.rst000066400000000000000000000015021432301424100261070ustar00rootroot00000000000000beziers.path.representations package ==================================== Submodules ---------- beziers.path.representations.GSPath module ------------------------------------------ .. automodule:: beziers.path.representations.GSPath :members: :undoc-members: :show-inheritance: beziers.path.representations.Nodelist module -------------------------------------------- .. automodule:: beziers.path.representations.Nodelist :members: :undoc-members: :show-inheritance: beziers.path.representations.Segment module ------------------------------------------- .. automodule:: beziers.path.representations.Segment :members: :undoc-members: :show-inheritance: Module contents --------------- .. automodule:: beziers.path.representations :members: :undoc-members: :show-inheritance: python-beziers-0.5.0+dfsg1/doc/source/beziers.path.rst000066400000000000000000000003411432301424100226630ustar00rootroot00000000000000Handling Bezier Paths (Splines) =============================== .. automodule:: beziers.path :members: :undoc-members: :show-inheritance: Subpackages ----------- .. toctree:: beziers.path.representations python-beziers-0.5.0+dfsg1/doc/source/beziers.rst000066400000000000000000000021601432301424100217310ustar00rootroot00000000000000Core Concepts ============= A `Point` --------- Beziers.py provides a rich abstraction over the concept of a two-dimensional point, containing the kind of methods that someone manipulating Bezier curves would find handy. .. automodule:: beziers.point :members: :undoc-members: :show-inheritance: A `Segment` ----------- .. automodule:: beziers.segment :members: :undoc-members: :show-inheritance: A `Line` -------- .. automodule:: beziers.line :members: :undoc-members: :show-inheritance: A `QuadraticBezier` curve ------------------------- .. automodule:: beziers.quadraticbezier :members: :undoc-members: :show-inheritance: A `CubicBezier` curve --------------------- .. automodule:: beziers.cubicbezier :members: :undoc-members: :show-inheritance: A `BoundingBox` --------------- .. automodule:: beziers.boundingbox :members: :undoc-members: :show-inheritance: Helpful utility classes ======================= Geometric shapes ---------------- .. automodule:: beziers.path.geometricshapes :members: :undoc-members: :show-inheritance: python-beziers-0.5.0+dfsg1/doc/source/curvefit1.png000066400000000000000000001025261432301424100221610ustar00rootroot00000000000000PNG  IHDR XvpsBIT|d pHYsaa?i IDATxy\T*o+d悊ZVj*hV[7Y2[,oi]JQqwQ\Cbe([c™3g>`6͈؀ȃCDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlF8x nݺxxx鉯/AAA9=|0={dɒxzz3qϟOƍqwwA̚5OEDDDDĮ9]=8{,'#Gjժ$''9}4SN &&N:QlY>O8p;vzoq1`&O̖-[x饗HNN_7TEDDDD `6FaL&sƏ… 9rիW`t֍oѣGB5ec>e˖q9ʔ)c1`݆#իW֪F>}k׮4hЀ붍7300$V\Y' """"b@nL\\'N`̙Y]\r6mx\۶mٳgo GGG[g!""""b4&&Mbܹ899_0fbccRJURxqvv&66bŊQ|lɅ  LDDDDDM&NȠApAAAL0wwwFAJJ 9X~8;;Kj=֭Xf j=JDDDDKJJ OG9l{r ҰaC~iz+9 `ݝ\#55b͚5)X-bذaFQ()SO=ڵk9r䈵UVWi^J233˖҈jժ>_Zhݺ5%Kv_=ٳ'`i9sfO^5?zM^zjf̘AfͬٳgMdurttZjTP;woǎj{֭عs'~~~QQQLl,ed޼yxyyݶҥK~=&Gkb&ˋl*޽ooou͂\r%Ƕt.\'M6,-"+V &&ƺ뉎fm=ʕcَ9{l<<<ݻw}S 0fԩUVŋq1;+[oŒ%Kҥ /2|Ǵhтg}z<777MF``  {DDDӵ<@!C0|fϞNRh߾=f͢k׮W͛4iSLՕ>}0cƌl 7gggf̘˩Y&}/OODDDDn(fo&MXz=;j(Frǔkb&Gkb?l6Դk. @C^; BQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ1LF """S7|*V8pDDD "RnʨQh9 ׯl641S/KYC֯o8ddd]@عs'&LiӦ(Qzm#Gָq\;|7n; 4`֬Y8C] _}<u)QORTC>d >,F*"""p2{Gm6H-e֬YxyyIӦM2l/]tc~770ydlK/Drr2z-]˖Ap0[УA߾ddtb޼c\R/// dt"""b:bm6ڶmӍ|8˖-ܹs)S&vލ7v+N`\?l ᐚ :СSP?]坺`>>>@zhҤ Gɶl6c2B6nH||<Ǐ϶=00$V\ŋm8|9 gvLذFʕ' :{Μ͛>DDD@nl6s%r%LR(S L0lٳ6md兣#{-E)#̓qՍj"|`6CTL5j@׮q#Lž=kP'#"""ƀFPP.\߷nZ*o^^^L&~gkǦM(V+V,Gxqqqӓ .\Dn'ˋps?L'q6]ib㏊,L~)}{ZDDD 3\9r@|}}1buӳ7h 4hԩS ee Kvuu%%%GׂYY(YI.P5 ILȢEYSݻR^=I]nqEzMٲe ._N8GGG֯_oNZZZ5/*e48[L!SQ '\r 6mo ȝ& q5"""\]Fr刏nR e놕F||S9wݻwgٲet&""RPr8k^ dl߾%Kо}\~ۧM@Ϟ={1ʕ+ٳ;{l<<<ݻw>HxRlCtd߾6G4Cw4\"H#liՊ?%J?t{gٲe6:3)꫄O\\-vO?Mll,[fС4l5k?G~1m44hݻw'"" O""iD|s?Rip^dƏȑRlYғ=kז,1%S͚~UѰaC֬YСCIKKc,][BZ ҥ [l! 233IHH_$22 .I6l'ON{y1c N:E͚50a/mʚNI̝'WT~/τ ڵ+s=@Rصkf"IkybGu2,YeY Ha kqt$ 7|MX&BZEl۶ŋsAky1 "̵kp! Uye{n8;eԨ=zEhpttd4n3v\[EDD"R\˖AP/&PdfV 5_ /om DϏ?hADNef† 0j%t<DGo$/o_Μy AӫW/Ϝ9sY!57nܘsZs=NDDD\ "vl(4 jԀ]aFxE73S_f/i֬k֬aʕ4i-ߜ?w}؂DDDĮ(؁GwaCha@aܸ V •+W(S | {5knnn|߿DDD^(ySh53CXbbϡ}{ ZD&MX|O>$f̘1Nm:u0uTرc1LW%"""@DĆoKKނZ , .] y>|8 TTP¨\'sum۶`+{"R_?\ƎggKظt BC'K>|gh>|8⩧2,+_)SX<@D @z:3 +!'W K|O6m(^8 .d…+Wζ'G=C g̙W$"""FS'&+BժЫeF)S,##ᥗ, ?ɓygHNN,3\EEE1|pIzqrrO?%..DDDH "yt&ԩcD|9<,oCݺw>FJJ  bƌm?<۷o.^Xխ[D>#+#) N@Т̝ ={p Ъ88X/_#,, bŊ1{l͛G Ll̚5 .\ED] f-c 9sS'pUǎ&22%Jرc ,QZ5&L@jj*Q@D5XҺQ*L/_ \\GGԩS"_~"^L2%JrI+#(uX  J`HI!<?ѣGҥ hтHZlOgaʗ/ϤI_@DLذF'hx=8{2,yukhժ6lzy?4ie˖, :tDDD@e6Ν0ieU]aFxE8x2kY/9ǺȕdS.])S`6y HDDDlMD8G;@ЮeKӦMؾ};!!!WT,\^{vѽV:ň.KDDd,Uϖ]ZO@Ҷiԩ lVXAXR999ӫW/2EoJ;vҥKS|30t56n|φ !""&Hn ÇCŊ0dO,]?9Ҙ e˖? C={[n>}/FHC4l茿ZzL+lwѾ=)ժY 7ׯɏqWOy8x1y?jfuwۛ]v".L&غ/|;leuΜ9CV?x ʾ۷֭[c6)]4'Nx#G0w\RRR޽;ᦁN ǎ#p?!-ͲOɒjR.8;OZEgh;ږ{hz5˗/)@^;;7ovbի[Свe "_tԉH @HHH?xx H22ԩ!3vp^ʕbū/5S:eR ILRiq5G5ċH뵼{NYGpe}r, :=^[Qvm͛qӦM~ 99@ׯotY$̙3g8z(DGGsq9u@%8175o:R 0ӑ/iaq*\Gp-%TDD"veKת`ض ᣏ[7pq1ۋ. FVj՘|e)f'OZƶmؿ?w}գF.]2ePtix⤤LRR Pg8Emg8 9LcџkcpX:!!ǁ)^,͚9ѡCyymڴF %""Eu VsSSS'+0 xO>Çжm[ :G%J`̙0~x|||WnlذpVXALL/W۷Eԯ_z\}_ptLsxz_}Ņ|xnٹs'QQQ[|5CInYFRS'vJѡ';Ш(Q'+"|f+ v(`8~*U-];67~ @9|0%tڵ Xv-?Uٗ46md gΜu+ҧOy {l6sl$**k׮GrF@u1*WNY3'k ZפrY$"(^ٚ%XZ9an(U |tuHHHW^_ =y{{3}t^{5޽{T+55kO?DBBB5oi׮8O@ڵ]6d2qĉld׮$٭C{ IDATsb#^m͎-ʘLKιbFVڢy%"b*">,cf4}ԩЫܡE_sEK 4ik׮/^gϞlذe]M%''zjBCC ?̱3]tߟ>}PV-jGGG@ 222سg6mbӦMDDDD/_u)SƇ5{К]",̕DKs3ԯ34l\""y.Xv5%'[f d ]ZW=[ŢԪU/c4s]L{Vŋiӆо}{֮]Kɒ% DC矬\PVZErrr}J*?{6QXedd{l$ r*oLbbJXWp~5j &Y;HVخ) otX2ҥ[BǠA SOjpE+$$W^yX6mʂ h׮sQ:u˗ܹ3Vx(f',,իWceү_? ?~77/68::cVjcs#GaTʔɽ;W%RT5{bG m2֭wÆYf[ a2h֬uݏHڷoopU4oޜ=L; Vwgg;>v<\zGy~jժ٢a69z(VbʕDDDX߬|< 0.]J_zzzM]\Nkرu48qF Ϭlr\aU^ ;bOohŋY^8-`ʕ:apEk͚5ٓ>:?ь4xy9Lvж-4hڹs']v%11\ѽ{wG^i&V\ɪU8uTUT'|ЩS'Miaȑ#WT)z聿?z3USf8>`{c5sƍB}Ί#{^+@=O`8xʕ-C/T>(7oয়~o߾WdСǎZ::U-GӲv\eƴm-S4gݪVܷc sˬHSNG /K8yxP \ qik+dž rZPV-8p +Vƕ?~F"""ȱ#֑FuA?LS~k89qFwesgR6-"b{^+@Qo˗-]a6(^n,]$;wZ/4lؐCN JRR4oޜCZ/v;,o|C\ڍ0Ұas1֦z<9pJ$;[u/_ڵ׳j*k[999ѩS'zE^UfYj[֭ˠA6lM6HK)ɭ$gsQ"vI$@-׮eбngOKW}f0w\FmpEE7Ɏs'$& ~ʕہ4a?;IDžsg,E?␏!1+ldݢUT\3WrlݺpVXqۮZ-Z`СPfͿ|fe܂_Cw*_^ݹDH)ؑ~CZ +,wd @.]%N^zL&*Vș3gp+ e%+lܘđ#.8q/Ўc9D-BO?Tbbb8{,Νݻa,r~akhժZ9jm޼ɔc;2tP @|lzݹ(W.{7kRw.w v$?'N?ʕ+iӆ^HdU+K22ܟɓ'3c MompETiqJ'Жf TXz+Y$餥YLKK#))\rϵ,Y֭[Mv֭[t!!!~'''zСC۷/|se-/j!\RHw v$oӧO퍛;ի'*ø~ݓ:u,# 4)fur)x.24HIO2 T N{.@9`M]@}?_ɒ%z_I'Od:&xߟCҽ{wLl2ݾ;ץK}ܝKAw v$o蠠 ~i>w|%Jɟ._~ytڕu\l:ӧcn}s s>׮5$=%P0JIjըQF[&M6侙fGpp0/ε ' bȑm֐.{Wf_d1ϓ'ot=Ԭ\`P;;7}hӦ mz^DDGJ۷}0M>S_/\d96@e@qW-uߴL"# 22+9ါ֭];[iDDD,Y$ٴ;v,C-.ZuLѝ-\ ;- ybG vZrϨRJ>Wzپ};`^V-c l/\GoR~ =,旒{fyq}e\R>y zjY|9)))/UÇgر4k̠*ods/_`[zJaw vDohvEkkѢZo%YU5kf$PիWڷ_n aΜ9DEE帿C7z WWW*?w:uFwoߝK^z-@oe̘1L6f8{6{(,hlh"">^Fs^-ZİaÌ._ (̙CpppVs1f֭kP_jsee&[I^"FZ)훿?+V`ǎmdfZ.{'zQNŒ.U ?ٳg:V=;v,}3sk5ɚjνդ\9cNĎ m(_};2zh&MC=dlyrYj*;=l Z(^};WŊ՝Kt@)==%JF&M8x%*..Yf_;2e YeޥΕ5;}х`R^\̛7ݻwSbE^|E*i"Cky1 "wq1""|LM`` k櫯ʽ+_:vѣOa*5k6zufTW Pёm66mDϞ=]t &иqcͦ%uS~?<_ X(EV)S իW7Ν;?{n `ɓ 6 ///~g4GW~}6lիW9z(O>%;lѢf"u,Y;wҵkW}ѫW/tBddHQ"rW"RiӆuֱvZ7oތO<6B)j@D@?DA㏳cBBB_ueh֬?<Ν3B)*@Dȑ#֟5kf`%""ёrA̙c]d2`ׯɓ=k AD\bY Hȃٙ^xǏPti_Ό3[.f"33JE0RxϞV""b{ŋgʔ)#?γ>kݾg|||=ze=SvɄ hڴ)%JࡇbDGGٓ%K37ݝ 0k֬>gY+WNSZj,X_-[Zϛ7 j!C+࣏>bҥt֍/1cưe8xu:uɓ'=ɃW""7NRK1cݻ PDY[n5gmvss3?mƍ3{xxϝ;gݶn:yܹmfOOOc>%J^kv2]viI3`ر刈إ . f̎'Rz-m[zhҤIiXӧիWnڵ+ 4 $$ĺmƍ3~l $))+WЙH~yzrv3g <~n:!Naj<J*,Z 6иqc2mW_}EÆ Yp!f9?"B6f3.]|?+WЦMmۖ={X}pttd޽X䗛T.XCvP zAŊpr*"ruk[??P #]ta޽˗/3b:w =y)FPP.\`QJ@bcc)V5dqqqӓ .p@n ii uQ͑#G ח#FkE'%%\jO[`_f ȀS`޼}0cƌlƍٙ3f|rj֬g}K/d󒼹yqʿ@,DD ݺYnP1a{L0?:ˤ- XV.WM4aQUi`*34s-Y_uܙ{u0k,"## 塇2Jo"rKDDFYYfY[=b͚5W'"MD6@DDlё@"##t{iu"DD6@DDle˖DEElwߟx"r""b2eʰl2On]vժUx{{{nRHZZ}ڸDoQ 777ʖ-  FDW_}… qww`ݴmۖ۷\;jl6\ >Hի@\\]taW&"JDHJJ ׮]3hѢ;wG,O<g60' "wq ""L2^L&Ǐ7Ԣ"vNDVjY@DD싫+-7ްnyg4}S7GGG>Cf͚AAA`pu";P)qss`Æ tЁs\JD@DD qF>>>8pDf "w""Rlp5""r*Vƍӧ~z+hڴ)`YmW#""Ã0!6l02]dX/BBB!$%%>}hL@DBDDp !},!wlڴDP "w""Rd!dW&Q !+dz"bc "wQLtl6\]![l12=jҥKW#""yBz!W&`P7w DDDCV ))>} ()zѣpc/\HѦ"r@DD&777ٳӇ$+)@DV"""Ãpj֬ ] 33D&{PT)ׯݻIMM5"OUTaժU*U p&NhpU"E= --]v\䷦M_~%U= "GyMם1|=)ٗ(d-laȒ$-KT""K eȒ}^XƘ5 3sx̣s={:.sg˖-Hzyg9s|""9tܙÇ`i׮aaaS#'OnݪEDȑ#С4oޜpéD܃ ]9<ǎ3HDDҋfh,t IDATc֬YTy֭[ &òexG8|0zp" DD$sɓ'+V G|̝;l( ND$TbݞQDD$s(Y$~w}:[l1Jĵܣƍ[* ""WѢE=z[nL$T@DQX"{ٳ)AAATZp̒{N$T@DðV^m0ɧ~[=#GN%T@Dx k޽;ɆSPF vn+"ɽ>ǦM={D"GD>xyyѠA.\͛ 'e'|b=~70Hܧ-ZX!!!+hР:uҥK ˴ihܸ1IIIvxzzx},Yȗ/gΜqvtIKAA>P+V|ر7rAΝǐm}P% H*31c/Һuk̙ѣٲe K,%K,݇Ί,im*hư0x)l ?.ED$h)>qw* ixxx?X|}}vm抐 hpsѾ}{y?K2ehذ!|8fjB S úvQQQq'W\)ֵmۖmۦ}xI_u//WOYlgϞe!"""}N$xb/^bݥK q* i ::ϓ?~k]Ŋعs'M4ڵdkۙ ˗/L*"".3ŵ }4"Σ3 7:u*/^P|rN< @߾}bŊk׎2ezj[4is=g+k֬ӻwoZnMÆ ٴi .d̘1.#^6l=axyyѱcG&NHBB-o߾fryT@DW^!k̙֬3GN:$%%k.iD҇ ͛mp%.\h85kZ۶m3D$v4""*|IkYDܕ +Wf駟شiD"" J.M޼yu Jܑ !AAAĉ &Wa٬Ο?ϯj8HS1u?`Ŋ:tp"q%NDoooo=Yoݺ`"bP׮]ɝ;7 ,ӆiժU+΀;R1(GĄ L2p"1Ϗp 'I[* "f̘ŋ 'n JNN&,,p"b؃>H`` ̘1l 1r#G &I{* ".`l6LB||D""bRŭpIDҞ (U-Zٳ,Xp"1O8a,HzPqo1x`ÉDD^dg =ӦIzSq3Ç/`˖-Hje ]@NPT * "n&w[GrrD""Z^@Nv3ft|N%΢"↺v?ݻ?D""rvU@ɝ;Dik&8ѭDl* "nˋ)SXJttD""rO:aÇBz#{7Tn:"ի?ٳg;vD""r7qի<>}`xAө76qDdOxxD""onyH &IO> =1^2)7VD ^{5y7 'vZk^zܿիJۡm[ӉaÆܡlܸp"hm@Rx衇 '7v;M@d73gNFm=ׯIIIȝlܸD4h`8ͽ|Za୷`Jȓt*q%* 0b7nL޼y`޼y4nܘ9r/_>:uoٳ)[,.]S!r[TXgN$""fkg1> ժ?7~۔"## ѣTPvvNN:o;j*4h@BBBmg̘AnxǙ:u*5k֤o߾?)? )n;l0.]d0΍Aݺu IegM'Wr?gϞ%<< &q1c˺u bС~Νkm˰a $$.]0o<ڷoOpp0/^tO%?u_aY""bީS8|0ժU0&%oBиb2eLWr],Y(PNBCC H"ֺStiBBBuׯ'**^zx}޽aժUi &)S_ 'nUF㯿iSx=?BB GөթӧJ*~s||PtwCʕ %rJ-[dʕ1n8~xO,իG޼y>}zO>ٳӬY3e/p +OOGiBIͳ8z=`ݺU>$ MNŋ;T-_Do߾̙7|uү_?0a˗s־fJpp0{u4lؐM6pBƌa&cV;Gouv//l<㸩{F󉈸hܹsL~cbaʔ)CjՌer1ǠAoU""l6>#ei 3JDĽݸ.8.>1^Q:,qLi$d* "C=ȑ#уDéDDSrru+FXq$ \"IDD,`߾}|G͛78f>/RSr2> u:GٲN X1c5 `Sɓ'[/_Y.\!8FK!gNFLNDDRY&ݻw &&~N$"^~gk:Á!W۶ Æ~'9رc)PE+ 'qcǎ费?j??ص 7vʷ "O<)c0{_/x3)1 maV(^<ݿmmۖgy'O; $"{=xȞ={~sAׂp"ƨml6M`rVĥȿ[.:uŋd.\? K, 80Ϳƍ=Μ͛K4"iFDDĉɛ7/-b͚5d }t4۷S@PhPĕ]ɟ??ǏՋ8DD\_DD&Lӓپ^9dE\ ܵΝ;SV-1֘1c 'qm ʕ+tڕbŊ~ jքvp>axyɮEҝ 5f̘ƍǑ#G qM6mb͛ѣG~qGL mڤnEFDDR%O=+4Wooع*9* "rWl63gdݺ[WSPa֬Yg}f8Y;wd֬Y̙q~ƍ!0>( _Mav(U*}8n&"wM6bly+iYu0ƀD܅΀H,XzTa޼4n< ˗/ӣG LiܹP\9z/ u\Qn;nqĞǩ.%= l{ZC܎ ܳQ'a(P|熓8WTTC Gx{{ƏOq5<:Q-'T>=""" /D ^8>ׯfÉ8nW_%22p W[ 9kxG2,f)qW.szSR0,X'pE^}U La|WcI& (L=1|BfӅ;OL )9* "r6a<˗/gņ_5Ō~) niC8:`&6㞻>E S4;P XO>&cڷoϕ+WxWhٲ忾n?_Cjy8yo%ׇ,[_[R4 / y [6E2qyal@%`۷og̘1c;^uޝ-[7|C c\u (H{ 8]ݺx3sرt,Əop̜ߨhQسj6T$R#aʖ=%)(:tZ_3tPyR Э] o.HF""Flqc)||b~$ 0KD2}ѡCW.Z?g=>> M,Y T@DĘY8 x̙3Y|X"Ʉ@LL m۶孷bg:rչٜ"BDDzᅢk H׮]?MLɓԫWӧOPzuf͚6yʗwQ"nDDD'&22']t,"Μ9C>,^.]|0^1yfs1ƦMebժ̘1t,qcΝ~dJb֬<\V? qC* "ɓe)=zt,qC< GXbV6-@\cV qc* "2SO:t !!t,q#/^aÆ8p…&0j׆0(WpH7"".eժȑc90].$"n"::ƍg (E22%#G_C܆Cd* "RgʕESW=[%"\LL ͚5cǎ`?VC8j"rԩD>СCM *66g}M6H\raNhp@LFDD\](Yr2Ѕ_~#HO˖-YnI,Sre=ɶmP" $OOO^^ ̙K%"H||,ZٳN(9*VSځp` ]̙3cHpe6m7|TfCXF`N(yK޽ | …v&99t,qaɾ}4o~}rN'"""@| y Le:tt,q1;wVZ8l0rd˗?H֬ÉEDD2 ҫ1(׮-u.ƚ%".駟2Jbe~h"7* "aLLo<ؗ^o:МW;ɕ,f% h"r* "a7eL/LCv;Ǐ_%)i60%WPt.DT@D$C)W3fqq^y#?n:8Yrr2gE4ˑ#MȑC|2Tڰa KÇiܸ19r _|tԉJ.>in7pyl qqqcӮ];> I> Ye<==Me:@Fկ_?Vb]%SNQNرcfĉ8p0Yml6f[o_q@_^}Ӧ"˼B ֭ ,VGQu3MD =]6-Zcƌ!66{RHUF ;w.ݺ/J_OrWIHX?!nt4I' ͛ȑ!@ 8>t4I Gvho|hh(V_>K&$$Y1E?δiIt<_t,I=*ȑy3u`ӦyfH*ܣΝ;+W.|}}Wwﶞ;}4TRUZ{:3[ҥ mtu+{aglhޜ]LGT=FK|O*^HG9'{6}{TR=5 TfMj֬i=_| :o֚g5`ll.DI#z_/ec9SxL +ĸq٦ V`6ѥdFv,b  ,+~Cddz%Jϲ~zv;6wV7W| l`xSp%4dDq}޳g+W6=b…/\| P~}FASl{!^u6oތ͚5cҤIϟqJo%rKdTdΖtӿΩԧOsW>|wHDE$yx|)]""""""N""""""N""""""N""""""N""""""N""""""N""""""N""""""N""""""N""""""N""""""N""""""N""""""N""""""N""""""N""""""N""""""N""""""N""""""N""""""N""""""N""""""N""""""N""""""N""""""N""""""N""""""N""""""N""""""N""""""N""""""N""""""N""""""N""""""N""""""N""""""N""""""N""""""N""""""N""""""N""""""N""""""N""""""N""""""N""""""N""""""N""""""N""""""N""""""N""""""N""""""N""""""N""""""N""""""N""""""N""""""N""""""N""""""N""""""N""""""N""""""N""""""N""""""N""""""N""""""N""""""NI= $44ST)̙CӦMY~Ajժ)֕(QZ>uu!O<;h&Nȁ ّBdd$q=&snwelyݲz=_|͛x}~~~ .O>9rtL֭[ )?~ܞ5kV{uz8OJJJWP^hQk'Ρ!X%K{:t¶m8}tn'::>J@@Eկ_ҥK⬘n'K,(Pp;ׯ'**^zx}޽aժUi=&7ONNwFլY/J,ɣ>ʑ#Guz8$)⬆'ΡݻҥKb!A3+Sܹ3rחz[HT_1QC@)T 1*¨R:Bx4nCH]DЅPd$AQՅ!Q9V"P'3{?}sd}? 8k/=O}fŋm?~ԗ/_qƀ۴i^|isW:Y#A/-q*..Nn[55551F>}RBB$wcMf'vk``@:K'e*999`|f.5NJJJTXXuuuQ[nՓ'O%+IhppP߾}s Az  22Rn^ZB))):qrrr4==-ǣK.z%IKKtIǚHI(?~\͒+V }bd w\<ݻW%%%ڰaN:%_ՈY EFFvؓb=|PSXGEEirrr|)p8&K_{ѪUB}:d>.fIDATIpedd(??_x<ڱcjkkg#}|e 99yCy3 SRRl_o͚5ؘPL=G-fONNjpp^ 2˥x ǨVAAFFFt=%%%'o5}b5<g5}b̩R>)tWB?6sٟc^2f߾}#G9~U+,d'&&n&>ό̯7aaaΝ;1j릦Lqq4gy= }b\'''MNNIHH0SSSĖ0c~r!wtm)--M7nPgg۵e˖P+??_ի577tӧȐ;fgg+..NǎΝ;kתS~ŋ544>]rEtQ.j/_jh׮]zn޼>TTT gݺu&,,̄a>-f]f2334… ^g52&==e֯_oΞ=G}v ~r8'v,&]n2;w4III&""¸nSXXh >k !k !k !k !k !k !k !k !k !kD gMIENDB`python-beziers-0.5.0+dfsg1/doc/source/curvefit2.png000066400000000000000000001021301432301424100221510ustar00rootroot00000000000000PNG  IHDR XvpsBIT|d pHYsaa?i IDATx{|ǵm9#fX64YB|D~QBSr!_1s c" 31v\ yݮ[>5ydX,؀CDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlF8z(;wbŊAzXd]m?SO=EwϧJ*ìY+"""""9Ν͛OOOYbz_~a̘1DGGOPH&MDBB.Ga޽8::Z9w\ DNxعs'C !11ÇUC,"r"L͚5ٳ,ZHʔ)֭[i֬s ))eR^=֬Yc=f^Xz5ϟp¶4/QLL+WuФI|||Xlu۶mۈ_t̐nݺ볿"""""9$&&?̙3ٴiuԅ r jպkڵkswm`` vvv}HJJ$%%/_{~''',..M6Q|y\\\W""""U_hѢ]6˿+Wrٓ-Z+l )))w헜 `mBjj=Grr_M6ѳgdŋӣGȕ@Fǎٲe ֩WwbQLL J"==L855x<==ʗ/@@@ Z-xꩧ[r̙3.CAMΤ%gsə/7ndӦM^KHH6 T);;;J.Mb޽{Q뀀iٲu}0͙ѝO?,wnnnz/r(79ޗIKΥ&g2zL߿5kjU+Wܵ---Eၟ1"n:nJTT;wnkܸ1̙3'1̙+Zʦl @BBO<ƲdN<… `,_F/i_~9;;3aBBBҥ ͛7g׮],Y'&""""Rv3gW^PB1k,4ibmWLva9r$NNNnݚӧga!ApttdY///{= b @pp0mժUٸqjss=Hibnݺ]79ޗIKΥ&gLbt΢-HɁt]DDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDlb]p "@_>)RS*_r0ڵkǭ[t=題1 *ЩS'KH# "ܺ_}eĉɜ?%KQKsYJgQ@ʕ+Gpp0QQQ;;UTqϟO*UpqqLJYf٢;"&5֮ݡD Dbͯ'Ãm]PL2P:w?111̚5@urrbwssseРAtԉ^{;w2d>|xI$t)\ ժ *xfM#BCqskO)QeHeh(Ԯ]ةS^::u/2F@VZō7xIII-[zf^zzjΟ?O…oԬYw"b0+X bcBu """#=8M֭)|x{{SjU"##3mX, !۶m#>>_|1nݺ볮x!f>NYCèQa^=X<#tN;(|ȃQ K.QhL)T Ãs֭Lm8@Z2m Ύfo"lfWDڵnۖ>>.Ɓ3N }̓a68f΄ΈH5 aɒ%\x~ۺӓ#Flc:t۷coo@LL w|ŋmy8m߀Faqp Π퇎IjǥKeɗ/ƍo0iRaZt _>N"## ^zǺ}ĉuƌÊ+2ր38'''x֭< _(Fb)%T6HM]ƍIC4h#hذQ勈H)XKV(R+VN[nnsqq!55퓓qqqҚEҥ7(&x3 f.$~\tV͛lܸF솈AׯӲeKnܸ](Y?쌻;mJ"==LӰRSSo9tл.ۭ[7uv=wmg7"g%h* f`…cر۷eXv-SNeuHDD K.eҥ]~ݠj$''ӦMN:/!!8+fde˖l6SF=̙3uY7/ii{G>.R}g XFY3 Xɇ}Pvm^xf3 .dԨQ\r$^z%._[o#"""yɽ>s^4 HOO'88={|rjBBB]'LSO=eָqcݙ3gNsՕVZeqat]^ÇqRY`Ci8D<K:>,'O$$$ĺ}„ ˘i^}U֮]K6mcř^ٳ'111н{w*W Mhٲ%m۶wvvf„ ХK7oή]Xd 'NME 3n.^4a2ŲX RJ7.;k_~ݍKs o?(\0f͢r 2?T̙t'tQFܹ{0Lsu^z%¸x"TT=zkY/G~)ӧO̙3xyy1x`ܽΚWK3?Cf ĩS0ʕ+رcի]7|-0w\ eMt@r@>8 …cGR3g…ֶߧ@Rŋիq?;wsH^5 "9HL ԩ+;@j |QH/_-|@4;wtؑ˗/gKD`'и1. ÇCS._/gO>E~ 6lȡCԩM|wiР.\_ED7o’%кuF8Oҥ]5ɓy饗2eڬfGGG-[fʕ+Y~; "$..nݺQdI*VȌYBp0/={µk0c\[@PǰX,5ʺm̘1{Av+UӧO~­[l^^ "٤{lڴƍ'c2}ƫcG'Nرp /%KӦMwޱ~=qD~mC/۳gO7n ٳgy뭷 EDDDrlrQ:uHhh_L89}̀q 7ndȑ֯?L#!F1L̙3|0c >lpU"""[(d]'(TII0ѯ_S֭4cǎʹh>>>=t^x%]DDDl2m4-ZD͚i߾ԩ$!!v믿жm[|.9XhHnuKLի~bgѣTREag>+prrb4m֭NNNW&"""9Y;y\˗ի)TU&MSOp9ΝkpE""")ׯ_mO>Nq&Nh}o``5""")F"&&֭[* .]p{=+LD$ ?Օ>{}ܯ &XorU+JD`w.c{璻&LƍL763rH ,9yHN"bٜi`ܸqj-Z2FwnpE"""(dʕ;v zѤI+Æ TR|l۶˗/ӱcG)S ӧO^@DD$R1o}>v\?qk6t+ʻuΝ;ib>*6?_dtY"""KDGeѬY3+Z{&00\QtIڶm?tbÆ@4tV-~V#G 9jEDD2rͳ>vvv̘1 0ftB-,ѣSL/d}ЧO>3c FQQQ3'OlٲF%@J :9?n&2nʘ`dhѫ-{GIb+3g bOǿ 88PY zzڼ""EkN# "6`XITf̙$''3zh zETTNԩS={ x}ۗ]HeL4dUėHҥoTb*T?/.\ODFزe KVZWd;,]n޼ɢEhڴ)z2\l6~֭[Ǻu눈˶ԪU:uPN(SLR)5 W 0&g'O&c - "Kz/e<*K蘃oo]W\ βn)Ȇ HMM]v=\fܹA|ngݺu_{+^8M6nݺԭ[yއrsBN%%, pma,T///2.Eg㫭XXQҚ_s~7SĽ/~=;wjժDDD ZԔuڕ&M\1…  44'''yΝ;ڵkYn۶m#%%hݺ5[VZ2;v1<ݺ=GӦHK'N~w=ȇPh< ܼykҢE RD =8D?yWJJ Ŋ#!!wwwbcc֭[ԬY'NK/\UpIV\ʕ+rj3M6M6CJ.͈#., GaʕZ~ʔ)chԨqSP!:uDNHOO'44_ݻٽ{7/2}aR<^`ȑ8;;/ۢLCY,g8u=о}{yK6= 4A̘1/sr19s&3g'do>Ӕ?LZڳXDϞ=0aQ]4+Gѐ^UV-"""0L\z5݈… ۗm۶YĄ q=ɓ5j9s0p.#ϟguұcGڷoO l\e`Xؽ{7sew)Z(cx{{[c2rYt@r@M .\L9|%e'|(7 Z5ٳ2hРwܸq[֯ǎo}vؼy3}7nZ;;;|I:t@)]^W^eѢE̝;׺DΝy7VAH^:oڼy;/"}eGyzb"#3 ,+͚vm]j70 IDATX,5)S~]Æ Yd 3DDDw߱a˽~:::ҴiS:t@۶m)V-;v0w\V\yUիT%:_{pZ"ve}ޠA+> `є) qv :L98~[OXIP4innnfoN52-͜ٸ'OR:v_cW^4qʕ{+X5tnݚº^2L4lؐ rϟ̙3|2˗/gtؑ7|+yi$QΛ5j8|l[la֭ϟ}ٜqY޽p T{ qp`̘\xz3rHlDُ?mkWLvm4O|M9pvr{V-[e˖ԯ_|e{}D>cNʥK2־}{|MjԨaPu"|) {RSSqss#99sKRSȑIx8;Vtk6{p^g>[֠YZc||< ""W(PM6efi=$&&2oadzM6;5kT&:_{pZ"7@x\l݁RSOywxwxx&as/^ŋSX1\]]IMM%---oݺEtt_ٸggg}QUM6N3Ņ!C0`kx"k׮eڵ<3L6 4(Q=mڴaݺu;v*U\Qsa}2[ xj׹;;Ӷ2' @Y&UT?>'O&::ںoHڃSAf-ʵkpuuW^Gcǎ}GcǸםAR1Ҿ**tܼ6p'-xzzRlYʖ-K2e}}}\r;))),X~:"[o /HN:o駟p{J.Å g޽;K,1& Dd$f2bo HŊiïѣ9tȉ'IO7l! j6Q6<x{2֭[L6S/3f̠e˖V'"9d0v>"%ƌŗ_~ɵk ,q*T''f^~֍xڵ Ծr9Sȟߑ͋1fL1ҕGq?I&ʗ7aqowwhFohG3WWWƍlj'ѣu{dd$O?4-[رcV("wh$Q[^x͛@2HMWSNe>?^|1+W]dD׮]9qo6\~aÆQZ5֬Y&lpAn߾ KJudJ{w9fy4lK2%0fNKR^ѹsg>t.ϯcϞjՊ<==Y`n, K PD$w)H6}G-]+H#Ygرc;w/;PfMv+xG|'TT)Sbp""9H6@:aH`2ر#ǎc,XF֭3JID$]~'NL۶& .JD3#F **ZϴiӆVZep""9HYURRѱ刈 (QyLC6l@j5j7o4BCD$Ω4ibh9"bC>(;v`„ ϟT&O/K.e{E䡧"@7NrDv)^Oj.\{<:t ED", wӧ%}wұ^_?Bprh}}׮]B||CD$ 9sX=vv)<%ըQ_‰IM=NӦjd\w0w\ ZDv@DP@||P刈Y`Gp yosXYΟ߈_$OpUHڵCD$oSB'%<>yX׏Cq)>C^݅ӧOLU&9y;ŋ2FD8@ ի111/"@DPFiRrD$qrgc`J l8g0m4RSS XD${(dk׮qQ FW"r7{{[AkN/pfÇz_^ Ν; mQqhx N>ͤIxX~=͚5#---1Ν?Oՙ5kueȐ!L:8pɍgHQ#W!6֙Vd 3g]DDr4X~GKZZZmQQQgggKϞ= dquu?޺￷L&˼y-6md:fϞ=- \vuDDDXKDDDVtKlh„ Xjٳ.GDXKf-p YRʖ+VXf%y:_{p֭CmTZH붕+WҺukʔ)c֤I|||Xlu۶mۈ_t̐nݺ볩'bki=ǎVXO?mtY"G.]͛sD~4xHVNc 2]71.ӦghNXl6~dXtE… \rZjնv8pnSSޤ  mf Uk#/"ǧv!,,?5j  طkZJm:®X1'h$|:wB>'F0|y)%KpETRw-U9111[Ã/fs~U'?qL$u9Γ+VE +APPfZG*Uj lpF?Oyߦԩ8t!=EFFBzӧIII8999S$wc;99YItvV?r'||C{:ʺs JduDF~t.QY gl~>G?xIOM5nyx)Ill,ZH"X )))w퓜 =99NrM2Jsغckm۶eP *g)H&x]G=Pʜg.YDRquZlɍ7صk%Kvg՝XֶeJ||<[СCqss˴[nt?MN?9I%q`n  qe""\*6AKɱ.]ҥK3m~A!F_+HJJ< X٦x.]ܵҴiSׯL&ˆ 2۽{d2Y/^|nGh)OKX1Y< oĴe{jF("))|N~'y`^ۦ]heyɆEYnݺet"`g/_NPP=uؑumݶuVܹu[ƍqwwgΜ93gj*{:"6*c\4mYḓU1@yhˇ[>{`&cT֑t ^؞.d^ǎл&J(s=ݻꫬ]6mŋ3޳gOFiԨ/2 L6 gm̄  K.4oޜ]vd&NH…m?z/hVĞ?qU_}'+V4<yVbW8};|)}}꫄/N7Y`!Ӧ":7aw?*o߾;ӽDDɢ9hԨ;w'>&tǎcذa899ѪU+ONbO?e9s///̐!CSfM"""o̘י8/ ۷ݻ1{֮N5#L_i֬m۶:_ 9~s9hAL.Z "ܺus73c Z1t.0ݺu_~Ԯ]zHր+GT~j "빺2lX{۱we|?vvO~$1AAATVwy'O]B "ہZ7ohl1""Yvr4..cJ6ǎ_r3~x?np"[(˗U4mrDDg{sZfJD&^`+ #G0vXVcǎ姟~ҕD/)7o'0l!!\Ɗ_L@N;vSzuToCFD$t9nG)X%؄=t:P~ekJŊS'k'NoSF |||=z4W|y(PMMͰo4jTӧbnѦԫ2UN:ŤIY&ތ1p}Z4 H+CfMX Nvٴ>?LFagӧ:u*=<aaaf{!""r, Gx[)P {DDrJ`<8>,[uidٳL>uR\9ʞ={42")܇IM ^z8::]HRLرre::UС͍%KҬY3EGG{QNy9v영="r͋L èQ0o wl:__Lll,1.]b…j*:OwϏ5j0eΞ=k\gD$K)܇͛ + ""B`` X[Z{ lٲ.]www˺u|2 ,iӦ֌:t#GR|y4hٳr劁="/ݸgTVPvmc ʕ+Ν;)_ .|F صkNNN.\~e.\O:u2ٽ{7!!!*U-[h"nܸaވHV0Y+ؿ?5k$""@ˑ?K3=z^-q?^O櫯bɒ%\L֭޽;-[9kHIIxxx?,?.:_{p-J~%"b#*T`OXʕ̊+С%Jlٲ۷og_jxyy|%y) II}{~4JD& &Mę3gӱ+fms .\H)S C !,,?_ڵkt҅&isyٿVuK䡤"/l)) 1d^z̚5/qFzMm.]ć~Hݺu `$%%IHH ==59xp`ϵkײG"a*pv> d2QfMK-Zs%/_N2-r?t>,e˖e̘1DGGc-[}2i@nhɡCxy-^z?HK5k,ܾ Ș\P!?sqqSN\K.`_z'R|yvʏ?ӳL& ,`ͼnK9ʹs|-#g);5o @ʕ HDD#,,0wnzzz:_5/ %%1L4k֌!Cp?=z@p-{#(Ud$ ""dΞ=oi}ݻ7ʕcܸqqL&7*WvU[T/( V=aݦ"";yzz2~xΝ;g}F@@K.[oE^8z=??^ 7oB׮EWy(Pm ""3}!""]vѹsgHKKcsemeEr?1 ndݦ""7L&4he8}4#Ģ~7cǎ͛m}7tՋ^ "b ɓ,X%K\d5///&MoڵkPɌ7緗3`L#Êds* "XfT@DDbо}{"""=zc}BLXuK`Êdc* " _ߌx&ˋN5hط>hN'܊ N_~O'i4"""i_28ɓMI""r>%""~4jժ\K-غʰa6d?* "7 M!TEML&""Upp0;wɴsb/75Hv"ömLO*Q.B(""?~֮]˛ob.^qcp"Ά `د&&&… JDDr ¨QRG_f6mId * "鄅APvrr2ɩgɓd""lْ]vQbi)))>jv4өM2NJHHplJDDr+SJ>!ԭ?h"0;Hvq#\mODDDlB&M8xp0WΣAx֓> $t,ev}sؽltL".!9ڶǦMLLɓYLDD$%KpܹLo_!+WC$$$S䮨  Ǧ?!PӰDD|RfUOE%֭cٲeF+* ^WiT@DDHifql$%% qs+b!I'H*..1cТE qss]^zvӿUfq,YBժURJ̛7_d⫯ !ڵǧ/ ʢT"""vl'&&tba֬Y|7c{{F+f.Ο?τ ([,kf֭X,Lfɒ%,X.\H߾}СC~`\vÇ;̅AZz.{{ɢT"""KHHH &Wٳ'7f,Xzwww#cT={bŊ믿rc===ڵ~xFEVXz5=V &痥_d.1~aw1TfϞ=$&&fxwJDD$+YVRʕ3 HT^^^+V '{lXV._|clBLL ˰^"..7r[6m+W4 ))gA2]xѱ, L.\v G…߿?qqqIS^ qssc޽uuaaPTvAx9> {ܹ9aEDDXHn)Xw(00#Fj/we߾}lݺ12** www)^^^.\3gΘ$'çB/#8~:u]psț_~ܹ;1MזHM81N:QR%Fڵkܹ3`?+ӏM||ӳ #DGxhΞ߶ .oLI!<__[ŝQ\ZX*7̄Ù3Y,LI:|p"""Y%!!qEsɍ4|||'&&Ʊ/ 3LJJJ"&&o:K.t%bVoӯ<< >`'x"kŠJM2[hh(]tɤ4 Hr -ZԱN:ڵ{̱_~jRv[~Yf._;XN[ww.)ǎߪHpc[\{n֭kRAS@bb"W\i hѢc#>>L0^zN:ѬY3mʕ+8qp2 ֭6m2rw[a  8R̟OGAl߾'NPlYg| ""8خYIDC$3fp ~bG*T'xoe˖BŊ4iCOOOf̘ (S gfFi.?`Μ;|,[?CBTU8i֬۷oӰzEDեm5LL"* Ko/_~Gwzj0(Py.?@дiw5k֌1ciXz~ED$+ɓGS$W9 +AV^z)t})ZWDDqQRMr}WK~u<<tɭT@$ y9Znk:ޭ/үɭT@$W9}ھx_G௿"<<ܹPDD\ U><<';[ǎk֬q'\f9`/^EH9T@$W /[?W۶m5 KDDٳgpT@$׈w4հN8]""+ *T@$3ZM>giXV2H?UH%J9۵k˗/'11ѸO."" \o1nU:t X~DD$H;͍jժFyT@$W HJv}ql/Z""%%%@Ŋ59HPqTX-[pCH~WiDKDrظWi, ?{gNV++WdСL2/fXI~&q>+ ={>.ȍwNnO?~SˆHG}[ož}GC8q۷AڵMN)\* c-XJaٲJLLyO""___+Fhh(&m@N> /@cnH'S)44}Rl8yr6]t15?LPP}yDD$qssoۛ)SУGO!!!fE1 H  8x8e@׮U2\ nnnq{&VZ/q* #jՊDV@­Xp;v4;zOtɉDD$X,mdɒ&1 ֭K˖-믿XxɉDDL61XwwwGD cƌqlĘFDDt9+#jC\ ABBBx709ѣRJDx* "2e y`8pD""bD# jT@D TdIFjeРAl6S4"LD`v,e֭[gr"1ڙ3g%J71T@D ̙3J||DDHgv$/zҎYf1 ZnMӦM8qӦM398[rB[k&A8өsL'bX,fϞX}ɜ>>ÇHDD%9ؑW<>=]O"S4hի|L:KfOD\ H /OpyyL=z#G0jZlyp8`tdKr;w. .pVW"Y,,YBꫯ7oMǕ-[ŋ3o^oʔ7?*Wltd{ӦMTZO>Lr{ Tqqq3-Z˖-HZhA)\0=z唖%KPjU|}}TR/EFѢE3 6 ˗m۶Qv-<=_%)$}#00""||1]KB>F@u:<&Lvڀ]:u|G2i$ƍiڴMW^p!}F̛7 2p@]DNL-8p `_Uk׮$$$d8bŊү;KriEDF+7ɍT@RrY;ƴinyĉgߟ^{իWo߾ F3j(ZjիyXlO?4&L 66րJ\)SWI߿?#G3ƈd""r;ʗ/fĉ&q>T^^^+V _/n:ZjER4iBJXzcߖ-[_~K/ƍ+WG}7fo"EcB7#Sȭ9=jbc܁ӧOsyիw}!!!ٳq;mcqssc޽ +.FLWL/< ĉ/};꒞Ocx{{;*nnn,]… c| }$'4 8+@RyEDDD\  (mՊc|}}I+q"Y) %KYg03 ac|C# J,xRD\rWiSҋpxzz:MII!:::4$bbb: `u҅.]dŗ!\6my!غ_~tرt ODDnNr8|0Gfez)1^hh(]tɤ4 (Y$Ee׮]7~@:uصk=c/jpf͚Eppp&WIU [`@⌟0v,̝k`@ɠ|>|8Ν;G͎$dݻ[IrMC۷?ԩS}6mȑ#tѱGߟ7o?>ye˖eדfM= 7-tc?w.J{3z4̟QDDRJ4μy󈍍uPaN< W*P#Gd͚5< 4+W0m4j֬3/~R x饗ԩ͚5c۶m\'g(#`6^Lu'ubr}0a}u,`X^yҴ"".6lhbRIgƌ+Z,֯_OXX=zP@J*ϐ!CxWUV̘1qG}Ɍ3ذaeʔa 8Ќ/O\B?I&{ڶeu{{ôinƠDWαcnjժW_ֱ{w%]sww?f͚D~=-W^k> 2d<(ХED_c… &&q>"˕.] :nK:t(1 ̚ɓDZ}58 ԩc$ڵktq4ks1cFJץ"DDETZ3tЛyMǏ7:kSW""իwa)^Fwށfi+Qq!իWv~g+4JLHDDInU@=z4>,ӦM#>>ތx"YJDqXvJrr~77ߘRDĵxzza_4DGGS~}~{{_iӦU?Sq1EQ\9vرc3Ӿ=<}YtDDD(o޼?vɡ IDATdΝ;v%"sƍ[9uꔙ1E3Ghhݶ'i&i<RDul6ipw=(U8+VAB(VQE3ՠAz-[n;wqݺЫ &q$&&\J*+qq刎~Bزe fFT@D\СCi֬gϞgϞXVo 0aY ED\CTTc;\ |]¥Kٶmj2+HQqannn,_ŋW_1sLk0g>lVJٳ%J0s°" +^8+Vbk aJ("8r,L @!" 裏ꫯSO=ťKܰҝ.""Y(})Qcǂ" 0n86lcӧ6 N``""Y/ {^ 21`Vhh(~~~Y{/;{68KRD$wJme˪P4DHDDʖ-˒t cDDD=zߑK%""Yf?Մ͛3q"x{LIT@D$'|~@ΝW8"HV:tOguB&q"Ɍ3Y& <%at?03Hߦnu*Lnz&E&>>>Z}N?}L(ǟaafG1 ܖ_~-[pywNɒV 31ds6mJݳQxGtc,c vBBl!E ""btRشiSLap(R~}ܥ'Hm:q޼Pj^311T@D)R>+d=}v0y2Y۶PD$v| RX@1 ܑƍ3zhRRR֭[_!$^~VdCk/oRܘ̫4bRzOH* "r^u~;ưa0{6 ˗ND$r n4Ѓd}Ԫ)E""wÃ˗7o^/^ŋ^{ ^59H6׺5yboҁUgyo_i E""w|̚5qcĈ.^ɓM &" YVzŞ(V?kNXL\E۱4[o=+ؘ>N09H62rHn^%,Y1z(XD ""wb{Qpa֭[1 ND$Xxq\|a ,hv,ӨRD .\=t vU`3o۷/PxmҦM#cJDDӭ[7.]ƍOlӲ""""С)))$Bh}f1 dsRT)6oFֱk\ir0={-[re>=cv2󩀈Hcҥۋuy˼*řKDhM4DjyKZ)t릗]"""YI& 8ΜNtSM&"b6lC\\-fpGGADɓR l~Ler0'۶m5U!y<4ibr8lDDD/˗/;:fr0'Ohڴ)ԫW 篿2p"ٌ dF XV69,Xۓ@-̜^F sd7* "#G^zd9/fr0,b3f }Śxx|&ɆT@D)<==DGwcN 5;wUzt c,]dL2J41H6""NSJ8&?ϗ f5Sc'{nݺX±o֬YL2777FapCdc* "TI/ 9{& 6wy paǚ5k4h?3og2c@&ɦT@Dĩ(X pu&'&JHÓO>I'gZhAӦMiذ!]Psdc* "tKf޼y")"k ۷oN:|'}fǎTP?M61~O@ 0`شHT@DO?4:t.cb͚<돳d_V'ҸqcN< ?}3g@BB ?~"E~ty@D\bal۶^kb9RСCӇѱ࣏>TRѣ6l`F˗܂F@D0Eaɒ%̟w͎%"␔Ą U|X,ƌ͛o*yaƍ޽͛7sQڵkgttCDD ղeKl>M]WSD|?3$%%Ė-[;v,8bXS?0E5*H""6mPx̎$".ʕ+ 41bq&']T@Dp L]`.\Xc /^:sqZ̮]={n0;`?3*T 00 2b6mիWouSOIϞ=uiQDLŋ[.ɟzu=FZ*MDrFv(Y,!!_ S$44q\ҥ?>-[41f͚ : m-XVcHcٸxы儅U⧟GʇbaDDD|HDD7xdʬYcDDg7pLRRkˎ֭Kxx8s!&|}}Y`0ȑɜ={X"l0o4nlb9ȗ/seΝԫW̘"JDDG}[cIJN^͎$"\\t̀2˗QtܙnnPqPleƌ~[qfGlˬ^tf$W_}ŪU 4;@DDE2kT`<)W^5;d3KDSZ'NDaVѣ9p͛77;܂ d;ݻwI??QƛID׭4o޽5D~Ə1E_HcXX`^^#r̝kcfq7Á+ˊ+شiUT1;;uV2HZhA)\0=z ::ڤ"9K 3#0uf3;j2b:w'6  /Cxu%sDWBK "$$$þ Sx)T&Mʕ+L>ёEr!Ch8э;F͎%":~=ơC=@ʖ7#OD ]zx'oyĉgϞ=*U ӴiS.]J>}*coж8`n`xxeOCxg6m.&(i ]l\rL_nZr&MPR%V^mTLu4irL>H"d>ؓkNSX`ʇHry ,/<ӧOsLž={*Y,̙CG}#_ Sɒl{.:evDKֶm /^= Cj|ؽ;U*gÁhّ֬E$ !ooo:t9sذao&`޽DEٯp ű+ N9sFKrlY~c# =|uR9ro!\D|jN@@kb2/`fG,s@PÆ iذvVС5k^/$>>En5 xlX$u'3O8?5|oy'4Btc_тAB |̌*"N,D֭ٲe 6qlbbM&$$$Ztb&]'P^`!C4(e;u9E>/yWvr/_т7x/P@3ch$.]$SҦbE…uc7 5w҅.]dmh … 1ohFИ_ht4[WbSoFpEf+Qx)IIx1Chh(]tɤ4 H9z(˗|QhQvuqԮ]_?֬YvVT%ukNzxR2:@QʸcuTߴ"rg=<۷;nwc%O R.^{U>d{n֭kRASoڷo>6lؐaus* =6mȑ#tѐ"L:%|$V`3_I ENUуu+EK+@ō+WNDblfIyCÆ )VdѢEx{{O?Qre~%:uǠAr ӦML2ڵ+)Xi_ ~_~Gޒ;j$55;!#F}j]8ΡڵgrsE2k ;w.+W?+V&M0fʗ/؃2d~Giٲ%3f̠hѢ~l}Cܞ׮M"jcs@Ѐ0`m[Z5+''q=ZnW$w|}("""""QèaT@DDDDD0* """""b1 FDDDDDD """"""QèaT@DDDDD0* """""b1 FDDDDDD """"""QèaT@DDDDD0* """""b1 FDDDDDD """"""QèaT@DDDDD0* """""b1 FDDDDDD """"""QèaT@DDDDD0* """""b1 FDDDDDD """"""QèaT@DDDDD0* """""b1 FDDDDDD """"""QèaT@DDDDD0* """""b1 FDDDDDD """"""QèaT@DDDDD0* """""b1 FDDDDDD """"""Qè8Qbb"#F 00㉈B# NѣYb/^VZL0MMDDDD* """""b]DDDDDD ".k֭e/<<<ñhтSpazAttIs8ƌC-͍e˖ez<K,jժRR%͛/#WW^ UZ5ӏٵk{!_|-[Ν;sȑόqnyϋ"""ر#AAA͛… s}rʛ?$;lYJZkEPIAAh?3FmE6FD?1Q\A(ȢF/-ʛgD_}ns;׋wc^CJKKk---͹hŚ4i[A߿_oֵkcG&ٳGu\}Cȑ#***R^^o߮/DPH_}͇6l$ѣu]VW}}8L]zU͓6\$Ŧ'O诿ƍP(JVwļDF*r̩S^ǛY;q\<҇9ߦc׍2Ǐ7 B|&77͟G2 6B;fx\|tvvZ33c`sa^ۤӧ;k̋} 1F`P]]]^)\RӦMs֖.]YfɓsDUrr9 dTUU)/ub̙3Ff lGvvƎ3fhܹq֘ļDִizGi&%$$*''G7np{`eee͛6uTJ.ݛ)ۭ[nE`GP( &hĉ|ڲeZ[[{!1ƨQ¼ ܬ~йs眷K1/g@0jy<iŊJLLԝ;w~-ZH/_Vzz%I)))aOIIQ Pgg'dP__1cƄS]]cMRSS_+33S===:x~7]pAcƌ$ oWEb^/Tyy$iر*++͛%1/Bl+W*//O׮]&eY+..NFd֦~8w^[NfݻUYY?\&BjjjT\\?Prhٶm֭[:UTTh˖-z*,,d^`UVJy^I/վvIr 2UGGGNVl۶Mn[F6ïA|&MJ,eLt @ț={rrr/߯Kj֭-( @:::$%ՏJ)));w{GGRSS#P\\&O@ ଑z⅖/_={VSLqcfu yo͚5zjjj(}fƮ¼iMWW1y1o8;0BhܸqVrr޽ry<]rEgϖR3224qD* j߾}>}y [:p?:>|XWvE*))ф CT\\<-[L.]?{jΝzKo&(##CׯwܹsZ|y9mݺUeeeڵkîϗ4 &ZŲ>L`P/VjjTQQ{رcڰa$%*݀h)++3 .4>ĘS 6sM||0}b_dv,Yv3\N8a>#3ec|>Yb9|^.^` B` 5P@XC` 5P@XC` 5P@XC` 5P@XC` 5P@XC` 5P@XC` 5P@XC` 5P@XC` 5P@XC` 5'IENDB`python-beziers-0.5.0+dfsg1/doc/source/dash-offset.png000066400000000000000000001436761432301424100224670ustar00rootroot00000000000000PNG  IHDR }vsBIT|d pHYsaa?i IDATxyxLgLdP4"R^QTjU҅Z~E+jEABf9?&HH$󺮹2Μdg}dmsɽDDDDD.KADDDDDR`DDDDD.KADDDDDR`DDDDD.KADDDDDR`DDDDD.KADDDDDR`DDDDD.KADDDDDR`DDDDD.KADDDDDR`DDDDD.KADDDDDR`DDDDD.KADDDDDR`DDDDD.KADDDDDR`DDDDD.KADDDDDR`DDDDD.KADDDDDR`DDDDD.KADDDDDR`DDDDD.KADDDDDR`DDDDD.KADDDDDR`DDDDD.KADDDDDR`tѬ_ *刈̬YpJ7ߤVZY""9dܩөnݺ۷__ߜ.GDY||<'$%%Nxx8aaawU tG PKҵk,3..DD@ `oA̜9%Jd0Lm]xVdJ!66޽jOlt|4m4Kͭ/'ӁjQlWO ~6c T[Lr:t%!"S`t<V+ `oIJJp5VZqIN<޽Yr?ؾj03 *qpV`20www>cuٜuYXVMFpp0aɁ_dv!44~iZa68q"C:s O>GddD3!ԯ_1cpumFpp0&%KrjDž H]/lP䦭 HdjB۶nY{S`t<جV+tԉm}*hڣ*.8_ZrBԭ[ʔ)CLL >}oӰaCfΜIz7[ws2x`җ0> ŋ޽;M@@-x~TTΝc͚53&6[`Zr gϞeٲe;wJ*ٳg9}:ÇcIJLgժU\dη2O!,`Z,_^+.oy8:FҨQ}|||3# kBB!!!/_>ƏhϞ=>>Kn:-}'''cΜ9Y۴iQpmTT?2^jj~zcΜ9wz<%5[,| p1,نF[ y88p(Th?Uf,]HMM'''|Qh*T`,_L0xx套aj'bŊ&ggggbbbptt$ RJv̛%ݻw}8{,ڵѣH@@=ۤIW6mpN(S -o8$_r,YzeJ$V)0:Eϕ+WxDG`lE8vH 0ظq#ƍc^T#GG8::bccˋ;vзo߿dWdd:Dn[nJf̀Mmԧ.x_ Oz;Ӱ͇$.c6իW)X=KN2ܛQ7˗:ofs:uM4<䓬[noҤ P ۍn Fo ӓMҬY35k :<\@H8}4'O$..;w.X׀ߦ֭&MʲQalٲdzu y{{ocǎ>`9BժU1͜;wɓ'9~<hLn@[ڣ6Ip eYiW- {؂m4'k?.P p!8s *T}HJJgeu~o`0a}ܷ۶mc„ _ $w:obwh-]Q(JB4h(JFll,˖}ORR<7I&X<όSNDJJ uϲ9s&ɉ6mгgOl+Wcǎh l\Jb _wP;.Ӡm۶ŋ|ٍPgť ezЭ1Lռn:ڵk@|ضmmHJJbϞ=zjv ^6]K cl ،C! #B ~4hErη2O!,ba9r/9sVcl,]AF o{=O- QQQX,<<S`tK^Eٲ^XEׁ߷de˖ѥKl⁺q, ldgf~3gпȼW^y~D*TܹsZ=+w:<\@ݻp?+V`ӦMiBM^vcSf`3nnGpsKӓÇӬY,"[Y>|T2@JJ#)Y %C4k,.]RJ\~BBBRJkl׮֭*ap/fs*7HLty `yAtt4ww,U^]z=zzV c ی/涏>???5Q9\p 斾lΝ,X%Kbm􌜜pv.Dr%c„ 3Lf29MRhUSuMNBJ涟k_bŊ:<//pcYfXh Wٿ>3gΤUV8::rL6 m3ߘc֬YDFFf6@4ŀ8НGyHHHH\@WrR<0&E0h:uDsL6kڵk XbtЁB tY"k׮a~{ eEa]Ƀ|Ƕok֬IÆ UTѽ"K.駟rI ԩZCl'=&ٱc 6m9s}i&ׯ{'Yx%_͛ $Sty `-V+Ǐg߾}̝= Tv3lfaNnUX14h ׯObŰZڵʖ-KڵMHrQΝ̙Oicǎwŋ)]4aPzu~wٵ+ HB 3^zη2O!,_?αc.ؾmfӺaԬYvsN9jc*Uʕk\|)}Y F3Ɗ<@ àyc큂&$0%JT7f:u+0ooz fs(;?S"Ahh(?3/z`rQ)m6UtR|}}=I$ zwG&((یoKZu>sG&MWV H+²eIIz)y7ot҅;qZs 6wټ 3gnݺc"bŊ>}`bcc)Z-=lٲI&{HI9 LĉMSST IDAT9|-[qREl71gTvy=SVL5ٙ$ ~&O#vaĉgcǎѶm[ *={2W_ル+UTO?EZ̞=e@}la!xիW#9ݻqttL_/_cȐ![oСCnIru4h3ˋ@la꩷bFXXmZtǸzP'ٳ2=6͛E*T"Es\@l&dȉ>ƍx{{ӱcG= @ųvC!COz˕I&(WקUV,\}ȑ#  00>}`Z0aq"YH"<쳜8QN:qgAeX{ %443gdx~ʕ۴h6bpƀmDg6<|WBCH[Za|ɼy?$+_> 2-JJ~…/^!|r<Ȁnh-ZPJÖ-[|2oF0%Kzjw;$yZJ8|m_Qm_z5=@~z11 \dk| Ⱦ<^z<‰'HJJݝE1ej]g֤2|۶Sd ".`X8p }ΝҥKԫWq8/ffq"YH"ԪU;2h -Z?S11U&EaAD#5jԠnݺxzz2zhV7l1bzATT-m H?'<< &PL[^+S /_&%%%}]J(a='''܈EOv?ٳg9<Dlڴ?>}?ŋ>˰> о}s"""yHyw Oժ]T …2tB^l l·HٲٿV/ E 5kFLL GI_A:u(Z( L:OOOٓa>sѺuk~Wk&Mİanۧ.IyL||<16m!5B 6lX&[~=?#ԨQkTsLˆ0@ᴥ#pqI||n'88MfpO˳0c2nǤ\rlݺ? 6 ggg6mZȴiXr%̘1z~<RRRoŞ={Zl >g ٜ0L|#((۷c6W0cK2#^{={J*9""Eǎ~*K+ J>oUXXjY7eӖbbei"lٲ˫UƺuU*jV%?] ^mȢE50 uRDG$&&FaADǔ)S8z8Whz0SN붚6mGMz` 6"#c0U^I{nM4*ED<^^^OL<mrڵ{K.NA>ZL<ѣۀBCμ(^xVma`Zv;oL 1`)<<9""!C0j(,*D:%]~… 6f͚t"FA>ZYq>f޾7_)P=Kc;W\GlNJʗ@`ogյ'5j늈HFNbP~}/^L``WcNر l"DA>۷/3~c&}b=z`͚d`7pG̚Յ~6( dcǎ?VNNN|͒%Kn@"PK;3z^J.]Xf òeok),df6>>>mD-[PulWyGx)R$)0d={ШQ#1͍9[6|}} L[r`INnUՕ;w2pezbҥ>AA$Zf͚qE||*sQ/G_OhѢ,_M6Qfl]DDf-TL&fΜoU޽;۷V `ܹԯ߀;޶m^^^CѿxΟ_ r:w]勈H՚~_R%oYd21k,^{%/fMsի䡡 o,{|av҅SXƉ'h~(^zSB*]DDnFBB;ߙf̙CRW`JL">,dZ={1Ѷm[,YronOݺܹohoq/k.^Hpp0[?88{,ׯ_lf޽T L[oسg}X$hXU,bͯOҳ? .F 0/2mKfDXfٿ""]hѢ%/'" #[nrqaue?6 5j੧?@R`G?6l`};h܆{Ν;cDDDٳgӟGDDBTT0r^`H .'"l۶ [ RSSɗlfѢGK$.FjDP7 "Jzs%xxa֬YDDD9=@...SUF D-.P…Kwvvã6oN8pӧ3rH  I&MJ SM6 """  '_>Gx1})BDDu,\E^waFrr &`&*VkI_E0dҥKҞ=ܸ96 \r/_ry…tSNߏR:-DD$kՋݻs%V\I]o:u g8rҠA嗎'P ]e>dLַh ;)U [llٲwww>P&L2s, ,̙S)SmӦ n\p҇CRr"YC]D2wޜ={͟t/}nVyk|ZԩS>>9]HS`xwY[l4n܀ӧOk.VmF˖-)Y$f]wUVmVcpMAxg ڎ2"4t!K#z4nܘ֭[Ӻukԩʕ+c1cj0nܸ  yaob M[PLJ֭[ӪU+6mJߛ@bkpr/#G& L&j|N8ϟO޽7)) Ʉa,ѠAO""""ЫW/)V̉?^6L}&Mj [.>T+"""r(0Hɫ@~Сf)9y$3fٙ@]]&n<-99^{жY:va믿~'HII`Ȑ!TX>T,"""r i}a@#GDFFҳgO`NN3>|x6W*"""3tA5_T\ߓ?~f?!p??? ( *L0b=6ljJ0֮]euI<رGȄ ~\\/f̘.`g"_>5t$Ϻ1Ax'oMd9`:ɴ_HR$sRSS:t( .H"8ȑ#9>8ތ9&{a ,\SbGԭ[ ޲a_vڱ|r 8bZc""""$ISL"x/^̌38vMD偋8;-"""$yǀ(y8wg/E߾}1LPD zꅇHA@=W_}{2c IMMͰO>;O?ѐDDD$ON`03g˜1o\Η/]taРAԫW/Gj-$OHNNӳ11شۉ7PW#4 'lٲKb{%%KdĉtWWל+RDDD$Ұ'-[6F ?W_}UaADDD6$OQzhٲ%/R'"""K ,W^]TDDDDr53L&͛7y9]C]DDDDD.KADDDDDR`tӳm$$$xbBCCtݺuYf9]} 7aaa4oފӘLZcXk/.ODDDR$={61X@4)):tDN&"""r)0$11 , ZLDDD$g(0ٷoueϞ=_siqSNDDDDraAI1sdNǙmƆ 3a 6l1331CJ!%t??a]pys]oǏ_>k׮Yf̟?$IBBNNNO'`!BHKHH`ڵ$%%qvIff&*I&1gk׎M@0+V)E/BtI kgdeiQ(J<^Ə?H֭8o,]?>F!>>1|XhΝ#((}Vb 8I&qQƍGFF'O~ZZ9;;ciiIFF?& [laڴiT֭ [fԭ[ E+BQv]VYf$''sUƌڵkx"ժUtԉUV1rH233^:Zb۶m> ֭[v+V|4k֌6mINNvܾ}JEff&fffڜ8q___>}:|W!%L[+S& IDATFVSZBO 6oLϞ=k.ΡCHNNf̘1;v,ܹo@Y6ls} !B-0Dtt4K.eϞ=C$&&y͛7 mӦMQ՜9sB*U(1K@OݺunݺCzjY BA9_P`ĉ|n Mccc+F@BBHrr2ڙbooKNģ|Ԩ-c&NϞ=Ã͛7cjjB!(; 0aڵkwOY|wssBm2339433ӷO1cƌn]@{"",W^!88-+BQt[nlkѥKƏ ;;  ''ҷ{ &<0X2dC ) Gz5kf̸L(ժ v*NE!Ϧ 6aÆBRRRR4>Ib۷/"LM_BBҎh4 MK!99''ҥKe~)jlܸ || \`ӦMxxx<BQb ׂ]Dɔ(:Vqvvri+ I&===h{)Zmܹs#\9@ZZSL!* CbbrssYv-4h=qرcqqqv **ARR%V\Yϕ+WbeeE=JNĿ P PtO}d}B!Õ)IF"-- qׯ'225k`ddiشi:t#-- ҨQ#ܜs2vX^~e:wL@@ׯg޼y-&޽{S qqo˗qttO9:!B\' =+WТE /_]j8r'NdԩѳgO/^\ѣ111al۶  ƍoO9W^fʔ)lٲ+++&cl\ nB!L((ToeʔܽcǾٳHBQvxp EoIM>[ĝ;wYrO!I D;v #h4CǞj\B!\$DQyyyф !B񬒄A"?^}U`lrxO78!BR ER, {ӢE#B!(Y0QDԔ' oU{w9]tc˖-O7@!B$ Epe._ @VR 5^5x7?fB!%F!СC̙3m07.J zJQ !BI(HW`ggǦMHHe˖ܽ{???9TB!()0/)O.yŊٻw/۷޽ʴoߑQF=PB!J$ BK$$$ドmY~=Z; .QFDB!( 0/رC`:TZΝ;C iƗݻwsA"""J=^!B`KHH7޽jݻwh48u/ݺ N0` ֮KKҿ !B!gϾ<==y,t*7_^J !B I* ?՟`E#ܻw_[o{@.ЅB!J$ B<ƦM0~h*Wvx`"_-`?~<[n*_/gϞB!DH cXZZxbnݺNNNsa@oÿI_/^~ôiS<@AQ]!$aǟ΢V@}3i2[kQFgϞ-HB!O! PvmhS Yf:uyxxK Bg$ BɉO>ysΟ?K/֖5jp9Z Y!$aONff,'yЧ ӒҸzW!$a4jԈ@̙zX7e˖-ܿrx+W2j(vF߾}h4S!~0QBLLL9s&Nxɺcܹddd:Ow߭g z- {7#BO!JXƍyw XCv>(ww/mmFFFAٻw/w$a!`;6Ί+HII)!BH D)={6۶f=j)ׯO'~+5*Unpz7;֒ʕ_WL4} !Q)R= Yfдiӧ(<lX@#(2ԧV&tC:@Xk|}ѱL!x6xpA'^z870&[j/^Dxx8ƍ?[m'WtC_bbb=z4δnݚEq%8-B$ OД)Sۀ;0 ڰuaɒ%ԩSxCR.FU+pwo9}%$$DB!0 0ݻwP1뱲:@۶mp!'###f͚>ȈUVq]rr̙3iР}gn@Q׏880hbcS SSs6l޽{B!] 9u$h4<<<t(um߿?aa@g#0dVС<;B!J 'OxN)ìY>ݝgr=ڝ+"c}N -TO6BQH sdРA)G'N`Ϟ=lRm۶=;~%57^|T5@:M7~" BE!#j?X?=exѭ7i*Bg$ BwhiiiDEEX !H sFVӬY3@W_-qL2HO,--2e {H7ncǎUvTR>}qΝ_!%G!3ׯ_g͚5A@.qã> 6ʕ+zn܈Ξ|Z-U$a( ,$VÇ1bWMKla„ tܙ^yݧ|t֍ ( : 6FPP*T`ٲe>}[[[bcc %>>Y@p>eTQ'pBBl$a( ,EQHMMEQ?ξ}طoϟo'&'$yThͱc0666( ڴiӧԩ/99_s?R,W>&M*!''-y!)J-+V{,]0_ԩS12: ~A7-rr\[oep,lݺʕ+o>Nж*Um۶/CFv⦰{w-uT' !$ BB?>7o^ 4tNlY~U{=] &...o /f\J3]taǎ̘1 VY8qH'++۷oO=i޼% $((Į!B0!OOO5kƻ/#!a'jFn`0Z]ǧo!!EFV;w.ii)ҽ{wݻG݉+vtޝ=zSeZlҥKINN.!O$ B"xb/&&+\9Yf5zh,͛7܏իWgӦMx{{OnHII)vjl ǁ5(ymLx{tI?իO:ux79y$B!) E8BM8q¢W2~xLLL߭[hٲ~ڔ+=zgi4Z'|_;@ ݮTnnnԪUwG׮]:t(wePV-u& !de8I Ç EqD:۱u>¢hٲ%o^cڵ*O ݂Ҡ7oq5zv7 1@4qԨG9JeҢE+SR%BH[3lD!(e-V0t58|W*VcMXcooKff&mڴa̘1|j%++~ >Sctvv&8DGG3uT\q6`ffʕ+ ڋťKv٘䨉lbԩ|̞=ฅBAQ&(~3no$$@BB&"mgټ }4hקnݺT^SSSԩC˖-0`0oP?&&&ɞÇӰaC\]]Uyyy,X3ҳgy}eo!eXbYnzOFX[PVM&Mϰa zdɒBӦ;};C߿1cnq5#GXs1>zY\'׵M`}c˖-E!-ɯv咗w$͛7iҤ ...%~aÆsN6nt޽֜;oݻypss_''N$--?QFaeeŐ!C cǎDF^ ''cǎѣGHJJ"..u֢VX[[s._LLL /_&,,5n IՇ\kn}d)!e$ BrKVӢERNFFGaC6L<8\\;ШQ#prrz`G}DZZ/FQ %}18NSSS|}}ٲe {&77uaiiʕ+qqq'U 6|x޽{\|FXX0022x%""cnnNVXb_C!IV@]G7%"P]"LFddqBwƍ?̌5k( Օqop-֭[F_~899QjUfΜipеkW6nȠAh4Z KKK/^OAyW8( fO_G5B7X~=_}AAn=7K.%r !(d C sxeee1v컬Y_GmЭ[bccܹtlm]翫nAr\2 fܸ/48 60tP}3f`ܹwغu+ի1bظqc~²] `)pwwՕ5jpMӧ$D9!-IPX!))7o /ϟg׮]XZZjtp%rrlF]aZ@ 03bZP=o<>b  vZRTO-bcT Q8 7כpuu V-[|NNyyycccS"O[)IB8884h@ (޽;v`iy +sDGHH4'@5pD#F ##C?mhڴi$&&ҷo_j׮Mժ[xs!==K( :Xv튓 7oz_U?=ԩS:uo5ajG6ԭ[WWWV?o`.E΁E>?q !DY'Oxq^z-s 0Mعs'666žg}ԩSiЭC066eΜ:uAɉ(=UVZ۶mk׮Ǚ… 9u*SնTªU`ڵ-VVfܾ})x/M3'`-6m*x%O[ /9s&T\3gRF_baaMNN#`5ہyhQ-E*UDFFM4'*曬[sssvMvsBB>>>\t ta ի́Mvv%jw4[E>`3&΂ xp2%I!,\OOO'''x (Rmms 寅ÙFQV-Zj-%33~,zɾ}x饗#m۶F׮]ٿ?055wwwƌLQnݺO&}+(ttu&֡$;wsV.Ye>htv/{xt XaӦZ#pyڵk۷]u۷cnnnpݛuԮ]>A5=KtNz,ZvNԮ]ʕ+7UT)uxxp0B<222r 111'6VKaݿl`СC駟Khh(ЫW/6oތ}޻wN:qIWαc ZTt;_+XXDOO]6u֥^zԬYZ-[ C0!(5ԯ_-]4LMMIII'.g֬(0%7TӦMsΤ}v<==ٳ'^^^Z]>}={k׮ѩS' 9'NdѢyտgMrjj88}4O.ԗMR4}^zcjjÇٿ?yyy7B< C B=z^ Je€}xw+kܹC˖-SNܹX(XXX=]M \3@<=hݺ6-Zf͚XXXQ ݅o$ e|3eVl,,;mFA55//yytO1η~kР;::-Z+b;XXjp#>f E.! ]RƎ}mm !N[ /BJIIɟ26`nLhEׯGݺuPj^z 7 ???rss]3gw WF:nbRxM]mׯ_,,,^F#/oN !Ϡw% *-ph\DW$pctKƍW 6^zKmۖիWo0qD^|Ez!4iBdBBBXn+W&ƍ?~}Wrr^d… \ph t࣏^ӣGڵkGʕsgΜAapm!#(TPP2vX~⢼Jddm/\tEV*U 6LILL|hWV֭+ʲeKHH(!!!ž/!ȑ@12M0SLL<T~PwP@_*J(S֭[DEE)ӦMӷ111Q̚5KIKK38^VGo۶m\g999˗7*'NTZh?W`Rj;\ߧ7ߔb픤r  DFHHH`ܻw'OҠAΎqƑƢEpqq!((Mgbjz &B:jxzz 3;w#ۧ￳`np^]LDtOY2 [.x8mp 7o^hQإK`[1cưvZ.^Hj8p:ubժU9QzuZjŶm}6[rڵGnO'_`!OF!66p.\s8~8.F@?mJF7ݩt>$÷=qm۶EѠRػw/;vW禦r1>퇸xX_xxp6axf͚V ^C/խ[իo>ٳ'tUɓjՊu1tЇ^SB!ߏJ"..gqt2WXt5@Nqb]?gʔ)3g vnn.-Z̙(Jo /T"2666ŊQ<_de8,Q7o->>DhۼysN>6mZ]"n !ՕQF1rHfϞ͖-sa>50tCW uJI`y&1gfbbB@!fF.L1JaI~z_HHH񁶎$''뷝KHHHl055^_S!xVL<ݻiذ2U݄~Xî]= Wլ]'''@7wر9sݏf?Cyܾ}ٳg+>!_$awEƎKV[eff`ff@- dffbjjоBgI.]8w DFFoPJ!*W?_NͪUݽAAAEo…:˖-ߎUQ\07nܠGo+ZO篽U##B9B!Dyn9{>Il3>>طo/>C@{ܸqRM!nKII[nPhUTIKHH^#BӒrrrHNN?r'&Lֶб!C0dM!(^nPaOѹѨQNNj>|xEnҤIʕ+8p[ү_bHM‚~z2uT`a6lPXJJSW,zťKؿ?u-3+Wt 4i`u?~)ZmtRY/\bE{}x7'& B!KUV%004rsϊ+066 1:uΝ;jR 8gO?+̍xiO$ B!EiIcddСC g…] _LB$ B!EХKݻwc[&MDBBf 4"vm ~~At>`ܹnBH!B"prrqs*W̺u ن `*> Xf^ ,(0$ B!Ejpsŋ76mʉ۩@Kp&ǴiHOO/(:IB!`Μ9>|v6I&1uZNU忄({$aB!(#GJtWoaFZ{t舮\%KΝU.$aB!(/Tq65aahҤʊPZ5 ct>}z/$aB!(3y.ZRU9wn3>>GRŝ1cƐ~j5ԬYoo'E% B!D`22INN&/aþ`'X2iӦ=T}Pz'A!FFF19s1cw`-p/3fiSEM!"dA$7B!% B!d B!Dŋ1 .\ ;$H B!Db„ ~4l7ܹܡTD!B4jϞ=1A= OѬY H%$aB!H>(՞HMd B!Dc0>}:#GtG_ 44l؄ٳ7HjH B!Dr ֭@8po:)RiaB!HR/ߟ'O8'Op0еkWHuA!">Z˔,˗]v1m4"##1>0 fU>0!Bb֭t}ݻwٳԩS0VZ[&OlXE$]B!R@ϟϲeL7o-[4{YX]2e0sLVJx彽y)6mJ+B!Dzrb0>۶mw<66yQpaFATT 4t@^odɂ^j:axSBUXӧO~~s˕+V̙3IB!Ҭ{n±cKϵkRtiz͝;wj|~~~,\UVqTbKT.ʕcrÇ`iiɝ;wtdϞ=yVVVd˖dY!iO| O~طoÆ 3%ϵjՊqQD *I"""kk뗎ؘα$""++WcmmmK!}?~c°N͛SF &MDժUH$ax [[[~EFF;֖Wi:O!]ݿ-[rE;`ҥK3qD4iHIyW]^teˆ\^σuKÇΝ5hРYձcG:vB!D*d0(X(OFkGXرcܹ3:ά$+V`Ŋ=~LѤ~0E||v;wW^ M#ҁ p !k׮MaX[[ӴiS|||ptt| c*_пɓ% y&?#?e˖j! !fu9xj")Ց@nM2aþAӾKp)ivB!'cDa0:kt"6stB!$ "0kajy[`App9CB!H$a)%KE`̀B!Dz% HQfD A\x|A !Bc0C)ŴiL?|ˀ9bB!H: "8x q ""be˺qA*WlHBʕ+,\rIrdjr($a)i{h4j֬Ⱥu]ݛӧOB!n!3J`ÇP|ys'R9$R+WaɃ'm۶hWܹsB\t>}^ p >7?i$ "E>}:ׯVVV8;;sE-6;`;f8BΝ;L:SD o!OpH3$afC/^ @ իWZ/// @LL -[Ba.aaa,Y̗_~ɩS;q(`ψ#^]Aav<{ =z`oo&MDŊ{ 2 B!Ңh6l@ɑ#^^^ܹ`:|t9 7["AlȑqQb,tܙ'Č>|8BB!DTTӦMcݺDFF{lٲt~ܹ3:uxoa~t1ٯA]0زe ͚5G( '_yL2ڵknj3aҥKڵkr.B$?nQgWٳӾ}{:wL*UL3Μ9?P3~AǎD&]YlܸJV[rs|}fg`^^kXl7n@׿B҄mf,=VVVtԉM6̙3Z)Yk 666f6I ̢`>7(qvֲ:#O`rv %JɓIB2eDƌ=|GXj+M4.\ep0wH7$af1p@V;s~$o-h@gpgv:DEE%YB!Dbtx g&MDXvq[-;~~~IH$afaaaO?M;8|z[>5&ׯ_"Pa8aܽ{7IB!KXXz;w"n`СGr6m@޼y5c$DbA#իWgv_lm{z '""e+WL,@u`20'%J!o޼B0xxx`mޣwȑe[ȑ#e6d$a)BbX50Ϯ]ԬYeXZOs^ZB|^^^:hӦ;=z4w2-[6wQNDQvm`4(?˗/c[n a/C*B$TXX[n4r` ~k9'N Mbʕ[7QbwΎ;^[fϞ=:u$uB!D/y1O|_{*Tr֭#:%z$a)JÆ ZnکRw WVZ)B$֭[Y`3fd߾}xxx\hh(}f1~۶m{h!Jh4fϞM˖-Ãr۷r `!CdU!x_7nܠ}}||pqqyclBV,C3|p'a"Et,_777@Ǔ'hٲm .oeBZjY'SNQpq4i޽h̚,d! Hlmmρ``#FL$44 B!R+WXX8^9`ٳ)SPv=1RY~}/- s ětڕClYc/^ڵΔ0X[[SJ3G*B١CuKJ'Xo\|@Eyar_H$a)/fЋ{ɓcP#ngpwwƼ !'tƍ@"#sKdއbjгgO.^R  S@AtСC!$aC%ؿ߇WM`` y1wB!ɱcǨZ&JezÀG@s<ϲe_Yښ> *>ׯ_Qp[ BbD& H5ʔ) 4ܻE=ٶm{yakeFŊ6l6!"Q,^?總;1ediiI2e%Jzi]2eмy ]x$a^G AV@}ܺ{5tPLR%֭'NNNB`q[1Nquf-ӧOݝ2e`mmmx*U*he@-`%^^o SLa@$` Q !HCܹ@n QMTPA*H HU5jÇ7nƏ_C֭߫v auD[!Dӹsg:wٳgٽ{7vvvnݚٳ;4!ދ$ "թT.AuXXXРA]o4&9|#FBWWWs!.I"ڶmn *OLtB!IDְaCAͨUk8p~;C"K,=z| !B$ "]{iٙ;vo +B!9I ҵO찰`֬d2h#22N:ѩS'=zdXB!A`gg@ɒ%}m۶+VՕ]v%{B!" H^laȐ!i;cƌԨQUV|rd۷W3~zr+B$aګZcǎ;wB Ձ+JF?~MSy Kz XW ^ߋ?e^@-үVh4GpppdA %IK>D͌^b0 ֭><{^ԢE ϟR N4ԭ2 BR,Xի3`QRElllL[[ۗ;w.ǏGC^\ I ҥv1}LËfΜɎ9s ϏᣏvLINx{{u%J/^đ !HL۷oՕ1NdQˋ2owN >>>|wX!ВyH R-ISxqoٳgӨQ#\ڲٲe8$ B:Ҿ}{6lCȑuر;ZhW_};FbJȅH^0 FIƗJ8tҌ92/zp-#"i={mR@LWٳǘ1c޹SlY6md7p`nĖ-["uAWشi'N,ǧDGG?PT)6nR$)R;v/_֬3,YSLw/<.+W[b% Νc4oޜ֭[nݺ!H|}}b,^UkӠ!!?7ҭ[cHbٹ3+PreO6l4$Ho!C&NHnر31""Sa\x!C$JI&͸}&` Yn=7b͉r=BօRdܹ'#XboeЯR߹n^ܹs6l!O.̟?\r%a~ ;*Qv"@7 #Jb̘2(0nEPw̘1#.YXqlٲ千Bۛ;wU@,㘴@I:Svʔ)˗YlvҥKś/22___+Kٲ|E(O akێf}$Dj"- B 2pe?ta |1֭[iڴ9 'gΜ\DDDܖ3tuz}ҥ }iB)ظq#` T~=3d@ܹɖ-.IM~+S`ݻ㋃C_Qo`^$SNQ|yNtI"t_y(U ډTRϟOFh۶-g ؙC_~}eРAmۖ=zPV-Z$Hߊ)rˬqqqu\v&MZpREt(NgƌeI)0 ;v`|"Z%nnl۶,Y|P}QQQ̙Pw|Z×YYPha-ZDŊzYBǕ+WȔ)mڴyk!R&yp0r-J)4 ѧO|}}Xt)]tw̙3,Z!H zs!x IH)X v"00зo_^=ǘ1t8@y Q>5hӦ{"իxx +++><ǎ3wXBALјZR,_绸0rH/_έ[;8q9[J@VJya0~R_BRdd$*? jΝPF-8"各AO>1m/]h4TP P` Zm +TwQx))! !k?Lw܉'N ゖnܸ:p=(3 I C x>}`J@uo2"wӣGڷoOɒ%Z`r/еkŸ~zzG]H.sf{MٲeT"e7oÇCXS,,tT\ƍsaɓ22˖-'k֬(P=z={~ׯ_7%AAA 4x~&Ghf͚E۶m%i0rKև.IBpYfe۶͜?wD"Eذa9F Ml،7RD ڵkGv(UT7l4nܘ-[pMfΜW_}1޸q t@~ )WnvvTh3x`J(!oH"##:u*#G_RD,,SNF,,(TÆMJ Xv͊+Xv-aaaqG~pr>}ŅX>}J̙ex%yp0rRJqi߿O@@WСCbpp̙ݖ-Z0{l-[@6mT9s&#G~(ڵr%e0&gp{WWWrҥK9z(:<==@dK͚59r(1YG"E鉧'eʔyW3͛<{s4ltQtҎ  և!X$ ֬Y/ɓg]@ǁXXl%6y XXXݗI&ammA߫[.E_3a°' GP <cL,Ρ^`8FS6w`_aA ""3grA4Y& :W\ԩS8::Rvm A V: J` _Qce/dC Ǐc0z}F40sQtDgϞGn\z9777ƌCӦMٳ̘1DE_;!DFy4'''L^$i;m+ =o05.7j2f̘$y`^b'cƌ,YsrxT\1cP~D!88___f͚ݻw_8c{ &0|Dn!M,CӰcǎ)F|||L"##U…+Ȩ}={Liڸ٘"0[}QBANUH5d|ru9R{UVVVaƌq9rDeɒTŊUHH{c0ԪU]&S]ƏFبk&Z}\/DgO2/~ *֭['5) b_ V )د5ג1kll\b]X+h+:Zjg&5`07*77^ڳgO#&&Fm۶M5kL+z*{ :*?O9s?3(D "[NZСC駟xa>'NdĈܺu}:w%<<sqN7}UJ~zFScǎ͍`ɔ)Ku_=zkkkn޼I@@ϟqݐc+np2TZQF"M0oԫWO*U;wTF/WW^UjƌjJ)bccեKʕ+Uٲn (py͛bWpF ( [JsPZN-]4Q>ʑ#)ŋ:wbbb͛UѢEU,6w۷oXu:ӧ_ɓ'CNZIo[XX'&gVrVvvY]xף_qFUPx-Z,kzrJUx}Fgڮ]CMm:ij̷:`&??]_mYti} `Ԙ7fѢEf妬ǓLTԽF*6I9#䀞СC&55?KHH0+W4~ihhq׮]kBCC1b9rH[d8˲̢EyKKyZ111}6j.\0kp8Liiw^QNUU)**S~)))1O6^;ZZZ̘1c4߸ Ns?ˌ4DDLtF,SQQa>ss.mp3ay; ƍ3'Nl[h6pS1ÆlprϜ;w.:vhu}]qmf&ܹ3-7涙O4[nmsF)))I_|aF? 9lbn6p:ÍfBCwkv]iJd &۷SNuypn6֭k77nx9rrrt ٳMOR~~~}<\/wէO?a֔)S{o,WX%#;H񑒌>~P銍ULLh>}Z{ր~W }ݧT*,,=,Xy?iiiZ~233ؒTWWmذ_7|SFwhҥ3g5o},EDDhرzjhhPmm6mڤ/R)))ҥKU]]- ɒfK eȣڷoy͝;_GQ~5s.9ou!fϞ%K[,.\9s$ TTTX7 uARM{Iw$KRRRo嗕H7Y%y{4uj|N:I [nI=SO=ա1zUVVKq(33SIII[O+11Jӵm۶+Jʲ,yʺ];Vn[ vBCׯv^^oI.IWmk; `R]]ѣǨ^OFI;ԫWnQcc4@כnvKyU%IHl$eIerUUU={V#GTzz߯_q I) Tt۷iذa˼^ϟyIZ,=G oQWmwj$-T{V#颤pT-Y2>̷: 6GVQQ~_ԩSJKKӚ5kt<]v_Ԯ]zt:zU__JUTTw,ٳgui׫A;wKr)2jiKR_Iђ [|Vsd+;%oӬ.kl|wH,V$ !VZn]V̲,+\KծO}U||#˥h߿_nO4rH)66Mw̙3zҮ]/IիiرQڵkϫ\9r}]3H\\^zY%˥<}qΝ;'өpcܬz͞=[w.r!MPjjj#!!oQO[V-GoRj|_ɗGuk֭jjjRdddG묡CG|BBI}h]v|@ױn\ l"0E``-[ l"0E``-[ l"0E``-[ l"0E``-[ l"0]7&` l?bgѳ` l?-[HMMM{vsKk׮n @F8.ϳ.ϻpݍ***T\\z4unMp͛n@ijjÇnM=E``-pΝ;_SLL,Қ5k:w^O>r\6mjkk;zjvm| tReefΜaÆ)**Jɚ2e8Ю.|٣"wr\3f***ե{^xAeiĈ1t kr!p8LJJ?~q8f͚5=zƚt/ ,IDATiZZZ]bq8ȬZL68_,\hf͚eV^m,X`MTT?G?&//̟?߬Z,[̌78`=g8zի2#Fh1`C`х LMM1Ƙ;wgwѣ-[a^}U2'Nl&**wѕ3>3s6e0_Fdffaʔ)7fm1Jp4`IK6lؠ~XIII &hze[nU]]x6?c ;wN^lSv뭷jСڷo9,RRRBCCe_ 6跿1r8m3 @?~\Ownߏ~#}?_YYY,KwcTSS_1~ϟWmmk-YD7oٳ%=QiiOaÆ@r*Uuu$)!!ݾŋ UuuBBB-O&˥'N|/mFUTTĉZ`$'xꫯJN-[IOb 9rD|IVaIn_DDD:MMM 8z1۷O3fИ1ccI{zJ[lڵk5a͜94?9sFsܹsr:@@ .ܦNddZZZ:|XjiiQUUZ nũݾ;v(33QFIR;wmS7fM8QԻᆱ!CO<6bYĎ?.׫2;v諯ҠA_1ͯunJߺC^:|ʕ+eMMM&r%3i$f}zp:uTebccͥK157n46mo7n4Ç7)))fӦMFcX|t XB'O TVVh;vLFR~4k,n-^XTeeew+1c h*//… veO>-['bI#<"ۭq)11Q'OTEE+6m$֙3g_JݝXIJJq8p˲eYcrssM޽MLLyG;c^{53dnҥKw7˲ԥ[oerrrL|| 5.pƍ4hǏ;1Zr<%Z2<%Z2q]v-z}nwg)EMjܸ1-Z9MVJa m k𸖌:N]$IR`, $Ic$I I={ ;1Zr<%Z2<%Z2+WN:s9tԉ{}mFff&3gk%0bGժUK~$IR)X7mȑ#سgHqs!333V>:vHFFFh,\< PdϞ=̟?D$I*, /Lʕټy3~:*UJ* 0eoNV{֭Yresl-gժU%7$IRe֯_Oaa!/ҥ O=y衇ӧ999t8p@llBB5j(6.994nZ{$I$N^|ݻ3a~_PPPɓ뮻 %%+W8xGRR$''wĶ%I$o<гgb~ccM}Njj*G]틍$I7j׮͚5k8-U_|kT!---vt-6 ضɠARJe={< I$̘13f[sΐ;, X_&;;N;-е5k֤vԬYK%Kpg^7oKҥKe˖D=ӢEOTOpBv7)ddUW]E6mŽ'8t/WXA˖-CJtl_~9w}7SL}_HJJ{L:حx,X[cСիWgҤI ȤIP]v f$M6gGzzzsO9眶l߾)hG`/*'>YgFU5k֤VZ\WRʕ+o}׿Gvꫯ2{lΉ'Ù5k۷禛nb{4k,v,8xQ#8p _~9;wfѢEL>QFB ?>7< [ެY Owy!%+^~em qֿ^}w;_XH͚ر#'|2uԉ]NN8QJh4 ;DiPXXȨQ_֭[_>$++ظ5kp-oB׮]7n5k# ٽ{76l`lذ?.5|`cHN^gm=.o;v:XHL"<5_suuj`7n?>>T"nsO碋2iذ! 6~$&Nn߾}DHKK!g)EZ NQQ6mbڵ[k2gppW@޽ 8D&Rlٲ+7džB^x.\ȿoRRRhР\pIIIl۶۷m6>s֭[ŋ7(,_8?8/ '줰Cv&) >>~ƨQ#hݺuk?S$y'9s&۶m#>>}~-k\ l_|jG^fm 8MBB";v{fX>~Dveddݻ-[yfΎi:"3 d]D8x(v+L;qbwG$}IeRaa!-O>իӴiS6o̪UX|Kdj@s`( C݊ߥrW_|1͛nݺر8ҨPBIsiϏ7a<֯ƺuQg@ODNO4N"yW^l~<@c%ORX ##FѸqc5jmTbEvA>~C^{>bO=tƏ;IQbT\uZkRX14nΪGŊ]6:E˖-c9Xn5| 999DQjժE $}+IeN֭?ō%jk$&~x']FrZ5ק~aǐcDRd'APj5~N gqF%I7H*sʗ/ϐ!C2dHQ$Iwca%I$"I$)0I$IH$I D$IR`, $Ic$I $IX@$I$'KmΝl߾8j֬IʕÎ$ $IΖ,Ywzvi4lؐUѾXlY$bI|t3-N$2 |B4:W_Jv/Ž)H廊UI@woa˸,/^L$ 5"I5j 䬳"@m xK^Lx#*իm۟0K*, $;;Yr),_+GoE#1+П;_㭷eȗSc;w.Fm! ,VLp'W^yԕ$I TN8~oPzpCJ$"z_W/??Gnnˇ$I:X@մ]Dc5 IMM 7$IR TBZlO?Mf;@%))G 9$IR<"//ԩnh{ ꫯ?K,)6vڵ\xTT4jrss)SиqcRSS`ĉA$I:4mڔ7|/^ 4>k.vʻrB tMnݺز ~Φ]vTVѣG{nƎիYd III'Oу믓޽{:th`$Iʮx̙Ô)S?F֭[{GV۸ۊ}Jh4 ;D^}U:tٳKq܀6m֭\`:ub|N>dڶmܹscݻ7< 7ojժmŊlْ˗ӢE$[p!?-;S/IZBLw|F޽#3g\vؑb^p!yyy 0Ȟ={?~$I:oߞw}o6n-ӽe Ӈ*UJX|ylݖ-[ؾ}{Ⱟkݺ5+W>mѢZ@$RRR뮻=zWKFr5Xvm{ܹsիO+ 999t8p@llBB5j(6.994nZ{$IuC_'?tZЬ];;wo߾cJmڴa̙W"33 v픔_\bcINN>JIIݒ$ر#oo{4XHa 9U둚J'w픂f 4[n,\h4Jjj*?loIMMݷo_l$Ip?w;x;z??N.R/N>d سgOlաX_CZZZx[lVAAyyyԮ]AQJbzIϞ=.Ic޽{F @#%hdgo5_Y4c f̘QlΝ;CJs찀1TX+RfM.]zظ%Kpg^7oKҥKe˖D={[7I{Oݻq-~zSN}:|eё>t^}No~زw}sҹsزݻ3oYfѾ}{n&vͽKfӧOl\r1b/s,Zӧ3jԨ#>P$hHNNM6aǐK>}:Ǐg׮]ԪU=z?SO=56N:kr- 62337n\CORRƍcܹԭ[ &(۱c+V/|4k숷g$Ih4:М˗{ H)w^:fΜI$Rġqqq\pAxbiiiaǔ$I%k?׀Hȑ#g>F^aȐ!$I*, ҷT^=^"E$If[۷/>(͛N\\PnrBI"}KqqqӇߡ;8X a ;$IRgxnrʃ|YgZj>eV$鿱HSvv67fd>dR$HI'Dn݀ ^V^ѣCN&ITzY@)))g};esbˇ΃>b2I"@+Wfذa}ݱe7p{/>S$8 t墋. }&MpPsI$&(VPxu*㏇J$HGAԩxk&L9$IR`իswq;\uY`6}N-:t(> $I- TyIqhVc>MV-ԯ cǎ ;$IR, R Yb|(.#]O$HC0䔒$IJ ;tjԨ 6_G47 RT迂$TRefԩ̛>|& ԯ_=seQ$)pUR,Ž"IT*x $IX@$I$"I$)0I$IH$I D$IR`, $Ic$I $IX@$I$"I$)0I$IH$I D$IR`, $Ic$I $IX@$I$"I$)0I$IH$I D$IR`, $Ic$I $I$@RFy7?>۷o'!!:up饗ҤII2"[9pݻ_s=Kbb-ѶD[~ >#GS$rNZ{FaaEEO,!\ʨQ(((;$I*, &Mмy+n~]?>h4$tHV*To.GzkFP| `0%K$3Mjn̵^G}vlITX@$}kʕk;k?`L^N< gͣVO;aG$IDUW]źu/2dȧ)1b6lϞ={Œ*IJ ">>1cưzde}JFƂغM6qM7q's뭷{DBL+IbtTyw}|,^]}O 9[R*O>dI%IR, JL۶m7oWK._-x/\ĕWΣ[,^8̘$)@>PR;3y3f 'Ds<4Ȣeˆ|TTtЁK") ww;wfqlK`$~ǁIB IDATS3x՗9Î/I`I \*U2d7?O b ?^?bĉf$IGDRh6 0Ebbv$I:8KR222xXr5{CJ9׿I&aG$IGDRڴiC6mŽ!I,I$IH$I FI||>UV$IREEEaƕ$;HR)U\9MF^M;[[ϓ '@.]>}:H$Ē$oI*UF޽={6s=ԯ_?~ǎsx <+V/$IDʐ5j0tP6lYHNN?YZ|֭;O~Oaaa%I:"IePBB=z 77~6r:p:YzΧ\+W$jժMVV۶m ;$8f2RJx㍼ҋ>{iР=0X|=󟟠c B,I:%@ttTP~Ѽys9NE@+|F(qqqaƕ$<"Iǘ֭[3a*U4R*@'{!I g@$tM7ѿyrssILLnݺ4k!I DQ? ;$I8K$IR`, $Ic$I $IX@$I$"I$)0I$IH$I D$IR`, $Ic$I $IX@$I$"I$)0I$IH$I D$IR`, $Ic$I $IX@$I$"I:f3~xZիעRjԭۀnݺIİHt\qƯ7F4H/_G'fjr4hvLI:yDtxw3F?Qq"H!%8g$3M+ժmI3Gyge5ԩS)((1$, c\@NN6/'?Ą }o_%P܏8SxÎ-I$SܹseHy1bĥ,[ hr>$vСCN'$G<"I:ӭ[7.]o~fKaa!F~xlڴ)䴒tlH+={B>}HJJ`߾}L8O!# Æ c׮]!cDt\jҤ >(7n[o|D]XYggq6~I%b$N:$ƎI'|>:u;1g233ccǎddd0sز ǀmsٳH$IkKnn.׿?~>UV^H$I׀|-? @bb"? ''ޗN^^ ))QFqɤuI$t|͠Aٺu+ӧOn 55k|RRR{_r倃~$%%OrrGJJJl[$I5~:~:z .o+ 55}ƤRPPp߱o߾8I$xc/wK/ĺubSMbSTTDnnniXQv{ D*U-ٳ'={$Ioiƌ̘1ز;wa/M礓Nf͚,]qK,ώn޼9K.K.˖-#{$Ǐn$I!;nëϻ`۷o?lف6miiiq3"#;;;6n_.,CT^I&IP]v-=$IJ7πcݴk׎ڵkg1}t>#׿Ù5k۷禛nb{4k֌>}ĶW\9Fܹ3-b5!$I+dʔ)L4;vPre~1qD:vWN^{5n FJJ 7 ORRƍcܹԭ[ &I$IF\4BS|r$I*y $IX@$I$"I$)0I$IH$I D$IR`, $Ic$I.Iql<3+|$''ӠAJ7nv]vW1"Iq|ݛ{$;'=^RFM^z饰J:X@$IRL||I?Xt)7pgq+V^z\q_k׮ /RJqW{N2ƍJFF'N,]$k۶-SNN 99{K.I, =O?MNׯ:-Z>Φ]v|nj=3|:uāmsۗM2qDڴiCVVcƌ z$I*s֬YæMPXe̙!'tx,[ou$&qWдiSnqFE~~>+WN:s9tԉ{}mFfff?^{-H#FЯ_?V^JTv <={vqWҵkW*Tr2I?g@6m+ 6I&[.lΜ9dff@ǎ(… cŶ9p@KhO$I:6\z@Ѓ7|H 7F|ԨQ-[}vZju֭[rC?-ZϪUJ0$IeYgD9\w]g&Mv$IGL>[rW@zzacˋ]CBBBLZZ[n-$m~iSN9%$6 [Ҷm[u)))/W\1$''q)))q$p;wd@} l۶-T ڵ+ժUwQ_3L ɐVv!( ʪ @ ɪZ'ǦU,-B xZ H$)1 $@dy>ΙCr[kν+V]/$$DTTTTgNg5!!!r\t>Sm߾]ҫVi۶mZ|pp)))Iڶmbcc}]9ʩXWΖP``oQNNNӰ\.rss:RSS^jۨQ4jԨ>6j~)..^? 0@Æ wY.]KږjeNS:r>sk׮M6UTTvUg322ԵkWݺuTrä$ݻwZ[7|S7s8Z*.n.)N!!hƍZ9i+={{~nfIG#G_˗.wõvZeee}:|RRR|2Wxw!CTρPh}(((HC ܹsUg}͝;WǎS5eM6:7p ^پ}^I3%$vBIŲQŲl0<Qɝ+CڰaC֎?^ǏѲ7N%'Ӛ5/+ ]I?(%^z kΝZ~rrrdԬY~ai%E FX,ٳzRT#4!0 i LC`@4!0 i LC`@4!0 i LC`@4!0 i LC`2Rp]NSڷ,=H6M5փ>-[<\Oju`W/.^|V|r\W_}.|aJ"[Jڧ#G{_j &׫=oKR7I%="lM0QGoj ޽{S1D }}qTTTzꥈ !r 6LW*1Cs+53Y,?W"R.^T^ZRI% WTW>Pq,_[nz/o[w@N8g}V+VaP-X@ƍ+ P-]TR˗7a<%IСۧ_WzXƌo$!$iΜ9jݺ?PGq ą i&={VGՒ%땕5Eҗ2Ԩm4~ѣPG@x ‹***T c%}_&i j5jȯu qҌ3$=*)MR II%=)eWhhP_@㢣s6 $颤dmP۶*|:.""B5eʣj|45lZޡ瞛m6fLP4kLӼy] zw@4!0 i LC`@4!\v%͚5K RddV>r8p@ RFp84f䔻vj߾BBBԶm[-\: ;wNiii:t萺v*IX,eeeeѣG5g=Zn~Zj„ ܹ.\={jڴizL9& w5E||N>h}7뮻]7{lYAIDATj޽JHH$Ch„ B͜9SCղe$IO<^OqPCev]ђ$0*\rJ :>$j۶/hH͛I&ɓ'ҥKZn->#Tɓ'u9ye뮻w^WDYV۷zj HdggK<\@eԤIRvN:U5  %IAAAe .Pv  BBB$IEEEes:քr['\ zuTeggp(00з(''iX.KIMMUxxxmFҨQna.]KږjH4mTQQQڵkW222|nݺIvڥ$ݻwZSooa*WVVn9PVy/ٳGݻwSEu`UõvZeee}:|RRR|H;~wQhh bZˣnN;wVRRuw+&?<7x* .}WZzN8!I6mk۷~i]pAtq4Mm4HR%)LI$͑պY^IUBBV8l٢-[h۶mxU+>ONI)6vMPR@t:x*zn>͟?_ٳgvWb 6w ܋ 0t niӦ2 C;w۵ukڭDI%KRoӧѶm[?PfرWmmq$K2$I.jJ3f p@-DҘ1c$ {%5ty$sL{GSvճVj?n`:&IX\2BI.I% Գsj֬+j `{G˖-SvY~))^R /~A{҈#P/RRR3gnWLLBCC]@~0 `0 i LC`@4oՒ%Kt!q0[>"""]5zuYeeeWg}&)A`IZhT/kǎUV~^1 C/^ӧueggW_}Saa#=zTEEE WnԽ{w?^gΜӧuiرSgcxnݪiӦ@o8jW_}U;vPJJtl-\Pڹs:v(I}ݧ͙3G.\oP``o}ת8q.^\*((Њ+c)33S3gΔļVUe*17#++KgVhhhwJlg5XQQq0 c݆b1>2Ǝk4j+((0\jѣ ?)۾}Q\\\jÇ`cѾm'N4BCC޷?7,{+z<ѵkWym+9r1`O>FNJ=޸k8 5nWtt$ɸ-k ÐU~~~k6oެ\M4ɓ'ҥKZn] Smݺ:t蠃\RCUBBo[նm[-[̷l_%fYV%$$zyyUb^o֭[rJ͛7Oay1t Sƍp84e]tԚ{JReZo>i Й3g|wu=yΝ;WWt]wz)ki_`^@9997|S7n"޸k5kx<M:U&L(u썹^_`fk6>:!>>^o(׫ַ~-[fIe2hp8t)_#ԩS WWfm\\rssU\\@z ?ļިӧޓ$hz'%17Z}?I'NЦM}1 g.#Զm[͜9S+Vȑ#%I#((H^kMtAMc_alUTl@A[oiiiĉ b >HKK3Ҍ<رcFƍI&7ϟo <ذX,6,b,Z3faX9s駟6,~aѤIu[oe̞=ۈ0r'}\_ת{衇/h"#--h׮aZK VMeʼ:{.sMR] }Ȁ3!,^ ʕZ8BT^+=iB!*瞃G!#:t+FBQ:@BJeKm\Ȑ!)S =k%%#D!xT0>,Zݺ+]+!0!!/pTt8@BLv{ېYѵB"BUѵB$!x]ص .^6mBQHB!1=z@P< 3gPѵB! ~}͛]+!"B 6nԺbkGVtU!8PRtsR]+!DU!D!ׇ}`6 F*"BTQwz6SV`u!x$!D9PJ3xpȼy]-!x [[~Ed !BTW\aرB`47G1o^^^5puܛ+88NW5UEÆp LuϚ7W !5@ѳgOYv4i28u ..k,-~vtie ~I>y2UмyEL("ʊݻwEVVuEw_Fj*DGCdHncbrmff_H1''mel!2n6E Ä ]+!ģBB#OOm+LFT )QQpv{z~&&Z)5E 3eCY3m)S`DK?hB!xXX&+ n?DEɓef_V)ٛՃ{UGaJhҤk&xIBG\] `кuRyؽ[do2#Gj j!DIHB*.kn]~JH CSRrֶ*V1mpѰ?̞M Ɛr@>c8@ZZ 4`ɼ9;w7|`aaoUV-Zٳ ^zL6S[B2Aֲ͚e) k-D;!E#" uDG;mXY+8SYO:5nn&9AQJEK? ÊE[B{Ic۶m <Rzuڵkу5j矓ٳ9uGQ ,W_eĈ;۷iӦ»[oQ! NBz$'ŧœQY( Xuc \ хDW’\KtS.p]!-`YvtMTwL1pA-L\\NuZ`cnCu[dㄳ3fyOKKƍ(prrZF\BǎZv`2k&xXH8q"fժUgʉ'[.;vo߾,^^z T3h VX/``̙L<ƄUZZV9[DBv$'("/k[a2-!ΝJt!#ɅD\~U 2)N_˂סzFmpj5ptN_ xe t13`AÏq>W?C;g jBIq~)X[[cr^WfРA9w4i҄+VݻwkSLϏM6ߕQ7蹑|#W?\D$Fb3;K;l-l2DgNDgE<~cY,3IL&)#䌛$gEoI9A]!v?ٙeJ ps:'+ kWaީ8ã5qs]]եaԩ^'Ϻ.UYsJ˗=&" ;ի 2K.accÄ ﰴ$""7oҡCXZ!%$RRRxW6l,XO>T,oJ\EDz)KQ5g5|y* 7[7-LTBڱV (^Y[ӡIoKLL W^`0Pn]sKJv- aR%.\$$6*9w;TnFp ]٘;ZaH-L꺚Ҩ~u䙦FTaFKX !=$@,'cǎرcY`Yfy}ZZZrɸ{YUxt%e$ȱc-JZ./ƌѺd-[zU\Օg IM۸YJ8+ֽptt̙^OLLL?dU7|{xƎ' !*VzV:'X1GX1Ŝ+vبgWf儌ۺvu1ѕdf&lBJ)@gfϞ͘1cqׯ_'...W(h\KK|=n5k̹_F LMML,Wm<ťq֥E. 939 :_03&5!78{ށ͹u+k̴JL)֮۷кu>S7n@>' >}O`RHc-[e˖z,>>j6X|ǎ\v-/kՕڵksرf۶mo|׸sر\+ϛ7)S0bz)ҥKxCv!YX\;CvvFBסYfxyn޴vnyx 9s gϞʕ+9#,,okBGgW~n-331&&ws{NדNjj*j(OԫW 兗͚5I&e>/P@B"ѺNkzIO<8vvUTڸC/+z!'k'䎬,>3SLaڴi;{,o 8oڵk)s…|7ԩSw/9(?͎+;¿ pZ6nsiWR\~ӧOΜ9CRRR1K A FlA iXZZ䄳3NNNԬY[[[WnV%e0HOO'--Ԝ{gߦǭ[ RR o*k:\$ѱ̎s(9uBCm괡@RúFթ( Y_}C _B<,z$T"rB eq @N Ȼ߃NnvK{j8ydqin?zXL'B3hv@ +[T^77\"lmm iii]Fxx8DGG?ik׮'hтuɿŽȵ# KPtPm]m!8VGQ6nV|RғR -ʊRsq-7o^߸>,=AA9HJw_ss:L}ӠޮޘoQ8s Gȑ#>|sΕm{qc:P~/Ѧ-9[͚5DxP222 <<K@Ҿ}gW/l_ֳG}ӠOz>su/z=۷Çs]VWV͛ӲeKZl{[۱q=갱ax[[L(pf[||`NJ %ej;do66yYSQ+r 2lݺp23]8tȃ]\y(;RpS^–-Z6x)-t ]s[V\ !!zwBFfk<'ji+իm͚$7""Bs2-Zagݜ?.]I׮]ٳ'={aÆw+崐q*Lutw&t0M0r$?s‹/V0QEZIDe)0ڷxNℎM|$CK;LM_:22;w愎շAtԉN:ѹsgڴiez=٣ի!1:vBT@DGk-4TzU+A,5ƦS!5֖AɲF^zۧ\ kE׮ᄆj]?N@@@BkNٳ'Î+;pq/m&.- ԱiNZkIM~&Ly B<`rVz@*9EYz1cTQtjiEVbb"v Ν3vvv9a#{ojnIt2mF||q{vNNӧ)mpA aaP52͡vS S]RJ{vKJXu bb?߼ iOB`5aCh65nlUrZlڤ 6io_<>U3H;F```.@l8uM h(6gk'q8U,ڂ tap7OBcЦ m֌l IDATgڅwǎycff&gϞСCݻ={m$ ԬYRnf6\~]ʇ-Gj[dmz~^f*E0\JDNhQr3&N,bŇy4C)ũSXv-AAA~=z M6-ֱVy3de)§v]_#@T-[BVEkVм֊QU[/*4|YwvvqsFo==AHt׻R/gϞ@bbbBnj1D-Nbҥ^+W5tޝ>}ЧOڵkY1XU+ 'ѡe| K?:\/:6$;dd6h!>11ŋmppfӂH֮z4g‚5GWM iРANѣGfSJqi6\s ̳% §2> #Fh0,ZuZIDeZƏ~D\ѿq^O7z٫bbbXl/&00M~lll߿?Çg888uSGS:Z=|/>ь y_`b]VE C<22 AApvR[[ u#}w6vo 7k02lX ${eF[[[߿-WnҞGʇ{|#8y C|;2(ʏ\JDNhQ*KtA3ugM뚼^ k64Llŋٸq#vVZ 2aÆѧOH~@_]o '6F҅CΝ[7mYw)MFիNVծ/+u-}oJ.]b׮]lڴ;v{PNK. 4AѲ)8qS~,?7fƘcicNNddgϮ]0B\JDNhQ*2 z6]ڔ3~\;0{ [ܸpI/^7o,rwww֭[VAܖC1߿<؛% a=[Nqu֭prQܺu7= h@ ZN7ipI1f.Сv͆ ظqQԯ_aÆ1vX:vht2dNNZ2Ա)sn ca0xQDz$T"rBW-GԫW={:kITDIJc|{[s=gijɘc=o7oMJJbɒ%/Et|U-ﴣ gܹ/WʊÇsѻwoLhRaZRGV2Ԥ '/c7hm0`6C(IIw[GC'cnzx%Qҩa _`Kv;|#::M6qFmVE[n͸q3f mjUgWwʏ<_™$&ixz$T"rBW?3g)p ɠAilذLQ$So'c澙yߨ?ShPCXf z}vs=ǨQ/d )))cooip0,[% >SwhQ1`-'Ded0h 8M˶cܸ;"k60?|B:y^b Mƞ={rZGË~н{wƍȑ#:(E g٩eoy6;ϵQff:/pt4BHJOH%"'tӡ/}"UgW9s.s^Qe?3w\]V~:3uTz]13?}-S~x xX_2G 㞅!CRhJKӺim߮mWğq2 4Ùhɴ]6Fu5TJ?;QQQEښ!C0n8뇥e? s3& `o_4`ǰ?LVw6᝷M(TbғR ] 7o®]e_vY-߹IfIz]*/))9s0{l Ɔ^xiӦѨ#h{y1DZ0x0 E <2jbc_P8AWJǯJLJʔ& PmtPFtպrzsݳԩPtդ I7R4CRgOI1F+^+= U^Rs}A~:&W7@]hCBB ݻ:x`PjT5YUdJ=^-j(W_)# B,oWzQA;A${;SsJUtzzZbz' ":N 8P_^eeeJARNRbVJ!Ć@JO:@ QLLA|Jߥ}ymk$g&nΆ4n:##/͛b.]صk;vK.9^ĭf "(I(LJu!8O(/ЧW,#߀-@3 .ļ_},^ Emaaȑ#ٹs'.\ߦf͚bӦM 2OOOfΜYu[:'0Wi}f$(:ȈGQRHªi`b{Ʋ-1 J)~wCRFRM맾Ψٳ^{s|۶m5k/t Ip0RMp5bҚF>V%&&˞={H]mƦc{a+С|,V1d\u_dഴ4VZ9p@3aONb QI1l vQ6:MȧF^+#%MzUo(մiٗ[XؔX5PV?-Մ  "䤖.] ]ƪI&)wNY=RRi9+'U&wuԩS%8-$D3uNj.ST'iWtZX=uꔚ:u+{ְaԡC,3-M_~͌THtgrb0ESگfR]eK2pDe"k'Q 4lM?[HȤ#n9ڨrK֭ٲeKtBPP>l ֭aϞ g̙`ccT݄֬0?zi9Bg.Є7 ߰ [^3 Ҿ(?5 *99Y_UtU]͘eLnxSJU(OrVz@*9+Aۚk SmǝrqYz[oǸ_~*::xef*Gao >B:^_Ǝݒ@uT?@ebbPJ ƀiӦjժD̙3UbbRJdHoTzsze9;)ZkխzY}wegQ9ZI,!*H醵5x+WFh>sSbo<oyf.M fA{{w8yƌ)v}"MaB w;;lIbKz;" .|_gS&M҆]寿ZP~}̙Cxx8y|@Æ ;w.,_sO?̒i>kp˩)+ԟ6m@6EGQE' q$+!A&Vk_>e=:g:v\Hqǫ޽{W_}U 9.]RaûJ}""e-.NZN7>qV5h`P/o&"""+RPK,QYYYQm&5ڰDžk+&y+թNjֿԉȠl!k'- BTPvZ@ND`@RRxYYӧ;wK.et :|XF\]a~03+v}Okgh8穦ɪ;xE`: ʷ8WWW͛ǹs7n\BCC?A֭\G`{w<{j=z,Hgs="~FNIx#~Y VH"D%QxcRb|RO7zg03)~VVFرcaӦM?>㑑|9rOBLs˖wB7D BTNū7ztۙգVcifY2Ryprrb޽N*Vqݻwg߾}lڴ֭[϶mۘ4ɋnݦҢE:i˙kefŻ%tsOZÀӤLgC/xTHh!9}7\0ՙ|r܌:ŋr=feeiڴi`޼ @ {φ+GO;_t)XXU!x`LL`H8},Oϻn =D1` IDATaÆbŏ8ӿAfTׯ^+;է=7i^wa^?zՁg] QIȞʕNfwKzz4X۶m㥗^Nߟ]_~eKE _|q>LQ!*SS8ΟyI2@k] zËnm011aر;w]=ټĉm ]N->a;˞]FuHLvme~޶!*/ҊַKQljfرd7M? }+aL{ }b,2Ũz!DW^fUK{\EY},ܜ_~K._bgggEG?G^/~w}ݛ랽׎G5xa)@Ϟ0bD ߜ1u|"#at]"|7p|`bb!Cq:uJc>D_ÇCÆ1x3ȼz8esm_ua܋AD6#D $ )FRqzzd5ɚزe qښ6mʔOR%XCŘyklDJ] !WèQDd00SݒeuUTȑ#VĉP}oDF+,,"8sFmrpi_@F#]Bݩ2\RHB!RUr;E|t.9Y?N,ʚ5+K.EMWф*8M5 uB˒t爴hwP n UR%?>?>YR<}KŊQ 5gip.̵>p,Lz5ueߒ$ B % ž1 ҫ~Y5d˖-{)Oni®]`B,[# Y},;frӇ7n(ƍHR7xL]rxui1ӽ!}ocV!!!+ķ ) Gsu"Eܹ7ikڴ)ZRu׊{?J_JZnB|WV՝!d |&"B_X1X5.gΊ+8uVVV\\&\~ bsW`u.HWۮf;÷];s?& )HºV ;;cPAɊ( Æ Ӗ9sf.\)Jt4b%r\\6YcBt;#d ׵?|P"S4͂ ֝5?WxXUL#}#rxۍNrV5CF&|'t嚜]Hl$"ɛW$*&Ygf^wׯ‚:-L}CfBhF]IJcdA!REu\ъ7Z-[6-[ӧ ZY_~)IڃKcgX  EGf;yI@HA@K/PǬrJvӧOy=`ߌpeyt'IPR_!R:uE;WWte{۵]Ua*TsXh3gк"9xpffUTRzI.U ø;}#}CiH$"֎k;b?-255qXt)7nIOF5aB7OWY%tGE%FOO={rO  O/iժ3/Ԝ^i*#9?j#֣lntCi I"D 1Qu FE'+˗/, L[zoq(c딮TUJ; !?xz¶m}  -[66mĦM055EWժh͖-07o®]Tdq) Ny1O܌̀:=KNQߌ$ B0ffy^=xJ*d1Β8ǎZ__?AtinݺxpTCao_B06WwoWw< `ѢEL>$%ZϏM~hY y7@Ν)!Z}U n &ȈQ\dϑ*Ufbb$! I@Ha }yWl[bM帏4h@ܹ?{=V@2p-/XZZaooq!#Cpw'u˰bb9E|!CrccS?#09rgݺuJ^ʢ+ٻիS S=4<@=y%#`Iuzц7}7[ !H`dHM|!4 ۷Ϗa#pѣԫL U54ix74S +Ѓih49uJr$ B0 m])Gc}c̲%+Fhh(>Nս~W9?`h]K6m022Jfh#FU5`#ŎȏF!{v SN?'+DH"D cl s+<{P,k1_ܹsߵ >5yg[1Mz_!PxyaJd8.s1G]39S%ytFɉ˗/SV-Rmܻ׏5ҧO޼yXj"(̙3wIٳPtiB|$ #Gj }?u%}ұcG?`˗Sxq)Z(tD*+_8ʋg5Og=pʊ4i$y=eQqUK{ !:w+W^=^2ÌX)Iɗ/'/&mh% GY`'VVV`bbBfׯ{~| V@QͿtB!sㆢT(R% !y) dV` )?'ck_N|BQBCCJROOO6lHO_2JTt!Z-}ҥKTP ]! Ŷmg,WoMO8991cF^:>>>>|ȳgϰw_ٲe9|g_kkkZ-.\J3EN6CO5kCЋ+ݻ7n6Y ҝɐ+Wxʕ} AAA5k8 155ѣG_yF{%Vƍ16N Tf|h,Fi4jԨСC/Aw$!BYY_?h35.=8`o v a``ԩSٻw/Y^2 0|pnܸ5g#D$Aw>ܹ͛3 6dС9sFP">V'<<CC(I- i?)S ~- CAׯϟEOOEyBC40c>L'9I%Ƙbۧ<@U,^z\p*U} 58E>}xxx|I@affFƍ9|0`ll wcllLdd•,"""b a`rq^ Qi֬%==Dz??g+)S*\u{  RcWr]ݓ~ ///ޢ;Ћ(JN'!ʛ7/.SAAAΕ+<<ΒHɝ;gwd̘1N[۶mi۶NIpaaa8:vdm`3&tR,_뫊cnnLJ0~x-L@xOD `FB!-CXlW߹1.&p|Ka/i:I2>cǎ_~u,$*NN,R7q8m^FI=$I.ҥ#[lx{{w9ʔ) oooի_oBfϞjL2۷F1p'OӬY BC_:,lٲq^?z3gPbwa(0q+=ծBh_0(W9-[b}͗QKt Ċ '''J*yLi~9UT&}K `___lllшRY<{,^ŋٹs'k׎mk޼9wm^^^ܼy-[ƶU^,YhѢ81-ZDڴii (DJ<mjȑ#=@)P`M6%y߆ 6́Y=2_kX Bo)Y2ȍA(]Kv/^޽{ط,Y"ɇ4Jb@W vvvdϞWtR8}4Ŋt'[YY)S&Ohh(ӧO'_|x{{fzѢEݛ-ZPvm?ڵkquueذa cF#O@~PpBq֭kq@BU:rp~ ,TX=z,a5p\r$aAQ⛹&&n;-!Uxk-#p;-~(y_-OAL)͛/_^155U ~Iرrx}:u(iӦUdɢtAyiq=<<sssH)R2wώCNzbܾu?O[Ğ(e7rQ S0_9ݵDEE%wB!M~~JdRJ)(]YDt(aazd? H "q3mH9sA|}ADU _|ci,FxG8m^90m+:ȷmBͼ} }rE =Y5xZ!'#׾"Zy9ꮀu8~&e˾(^`` nnnI;pf p8Ƕt ??/B/`b˗㴦:njjH>lt鞰ID#Cp|8C !(4~)ϱ[kڴiܺu|<\Sw/L!_Q]OqTg ;:t`-8qo=J!DNص9x 'KC A_ۑ 0۳f̮uVO;\SHOD!Ŀ‚`sM7xt]%Zj=yzB! ߁-Neխ켾Sfff7iٳg۶m}:tM>}Jf5LO8wB cF&@/iZE" ߁2gmGp^מ^< {cǒ3g8m:uʕ+Oѐ-[6 ǒgB0#=n!23#F_& $;h5&pj0Bzkkbԭ͐!ӧOJÆ yI42ÚR39Aef>u g*B!e+*Zm+ k˗ɛB!~8ɝ496Y+p~3o߿U‚[{D߿Ə(I?=˅Y2J֨!!B$;ϟkhl&8v77];U1jժł ⵏ7$'DU[6'}}}uIȳgɛB!~236mc~fvwkm=۪.ۣG&O}޼ytԉyab=6ko{/\euɈB!?H"wj0Y7ڰ^vmN;QTTF… h4q׭[G͓,ۮ.,A]{P"Zy !"uD%4jS@Ke;6Z{wi?J*qQr4mڔ#G཮ ٮ3:.>} իÜ9lI!?$I@ժ66 ukv\Au QtҜ=2.FWەߨ"!3fe2gﺇK&cƌ=ر\fi Osxfȍ`o;TD*e !$ B"&&+V@UrjL6mP!ғK.e߫E\ruƻvpsslٲ/^F@@__yPAצ(0w.*GB!R>I@Heu6l|M9,LB|l21 4Osf޽,Yiƹ( 3g5v't˰Ҥѵ@jг'|]!/I@He2e%![$ݷxz}!j}yWi4wΥKwիTP#FӃAE]ߏ,"ETK!)$ BBJ"""3ghߌi26;GJ8xҋK:U *Ç>}:qEEEF%TJTѢp(̛@߾MՕB!D# (єmlҥK?{VeL1| k:l@[϶zo===/߽{ вeK>LDvVpܸ;#??U 6۷UK!)$ B2={:7o7'r\qoНCPD Μ9Ø1c0x'n݊9s%*%Usp +w`aÇCh !ےDThڴi?m'e>K2n ݼ)$1T_]~cl2?N%]1;wlٲ=z4ffm8[^,wo!B!T)ݼXa`l{G) .]}~#xʕeʔ)ǻ닃͚5֭[IY.\2(XP ++BoN!ji0,t8@̅b/0gӕM 144dС\z $g۶mXXX0h BJ Oҫ1c ]:]{xnzBaa+B$ BdY&~~wO4< }D6Z[kϯU@v޽{)^x߿g֬Y)Rwww޿23B{˗0b. *h !?' F? cُEƶ{ԢR ;87oTh4ԫWK.`fϋ/۷/޽;'-ٳÜ9 t?=)17u :ZB$B|B .v{L=9 V˲qvv͛`'_NF]6.]J:hb\͛6_s_;tWx"g! I@Fq*cHOwɃh%uceʔiӦqUZh`beeEnxqA[9B( f0Cvbe.;wdI!I@ccW++\/@,Y2ka7e?m111,["EJxxxA˖,{uǓM]~]NUB!I@Y Fݲ,nX,`6˲@Wٳ]˽.S۬vl[XhgB՟Vnbbѣy&NNNh4x}._LfͰQW1KF(wuq#8I?QLsHyYXZ! I"O5-ʾlI y!ZD5/U˝;7+V?FQ|yۧ.W kp ;mӈ]bo{1]^.˗9"B!I!FЬx3{38N+-'\)4ON'XYYq!oNɒ%Mz\2^^^5)Es#k ťk0A*% !B!70-c/ a!_b-wh44i҄/oann`SNQfM8v옚@Ss̆U;ӏy,;ǹg4w 8NVP;}!& TPn_۷oH-m6fם͍>7\3ZWЇtݝ KoYZ֭[s֮]K…w1VJ͚59uꔺۓf\OU^ͮf"iE'Նs!B@!R!'''6l@ڵٿ7ϔMVr%k~ ZmmEY 8:b (`?///*UDz8wvvd:r^/f0Zat U=^!G" Ёb3g|ѨW"{ Ω_NQ%v jE55~:>NNN\~K7o۷ӸqcΟ?.xٲݻh<%kRH N ƍ/TW!H"D*~zpvḺv[)YqKc۽xQnY9ZlnT344[nܼy ;wڵ kkk5k˗hf]#Y#O?:rB4ԗB!R3W닍 >>>X[["(1lQG jzjfLFbYt)nnn Tؘɚ5k6oLɒ%qttƍ/vJޞ97h@ZhRKY_x,Q; !$ B!IJ%p~'z%,Ьtٕ[T411aܹsWWW2g(_ŋD@@ YN'k@0%%rԾ& a̙\2rBD \“WOX} }!{ vzζ!#G`-Kׯ_3w\fΜɫW죯OΝ1b T<$9?Ë{@0 [PVǤI>|x-#׾$ )"^}-X枙m-]Ҽ=#Gb*Y1CBB={6s!4rzzziӆaÆ%z{޼aJ6 s ݓ<~#rʕ1 !o/'K5SSvϳMe1iʯs}a==tؑϟ'g/^ccc-4i`߃ǿed585fFz`<ۯm'FQ3W\̚5@Ǝ`,ǩUVXXXb ",hn]ɸuנ7nR,ԛvs9o}MOB!HY$Jڴi}3TB@@nnn <={PV-޿%K֭cggG~6m9!~xzt+3Y*zw0卑Lpp0qݻ7aaaٳND(FK͸涆=;)=- ^=N}QՎ-];U1ڵ+׮]cӦM)S&~/^`رϟ#uΛy3ȕc69JuSipvOATvuɄBoE۷/ݺuD?|gϞakkZٲe9|g_kkkZ-.\G/H.FC"9?7< a7=x<^Ɯ3sx> ӣUVPJy3gR`Awέ[*Oo75EoZ>vw1dF;![X*B|xb8qb׃&ʕ+@#k֬qbjj[O!Wh^:lƊԜNy:8'L91^Yn]=ʉ'hРA"##XbiFiӢПqbmLvҚXmfe/.j]B!ķ ecƌa̘1&'<<-g|aqb !RJ*qfZۜFB>/bxg+rqd(=UR%vŋi۶-Zm_111lڴ +++ׯ70@ө#+ؿ#%P~a%Xן|`DV=f!k5jYfo߾166ݻk#""166NfDDDl?!Ddۆ6qi3FӢe'lN\;6!OٛU,U6lƍ#/)>.ݲg޽Mjqc]ZY\9A?Q7Ƅ TY!~͛xxxзo_g(JٕVZŋQhQf͚٣h4e޽q|x~( QyY\GIpLV3hz᧟BTH>} B| ֹz7L$Wƀ#P^ +D>՝/_۷s:t耞^>uVlllSR_AN ]l8p~ʽb(+B$ Be1Wz4ںo] 3{~v~u^JtL%J`͚5ܺu޽{&M߿5jP\9lBtr4992j3)DYXB&pꔪxB!ħ$BlȑlܸMȟ4sV 5 ';d-{̵>\U ݻw>|82dH_EV(Z(-"<\]|M3#qb?đ:Kw]jP211B!~l!WjiӦ ݛ=&7vc1!x>w^r` xBU9rJ`` nnndϞ=~8;;?~&Nȋ3'dOpE\UzB0dY%aJTcBM)ljǣ( 0yBmmVJІR0sA1Yj3f ~&&&tڕR@=Laݫd&v! ȐC!C '2B=k_NDA c <7G9kl _4Z5ƐJ.XR!::Sh?===Zn eʔQ?}9y")1=m{ronZA5N2%<l5o?~fϞM`` &M"[l s}!?ϟt0u*} ǎAÆPe}Y IDATY~#"D!zZ=[4Ҩ-5v-j\%t՝ϯ%KFɽ{Xx1 N7no߾ܹsGݠ3f=s-bv tff0{6)& B|g4waU{ǿ^Dl("ͮ؍]D{,{ĮƂ]Q `AV@#(u1QDɓ3sf5Όy*s .oϥЫ2 WudKmhĉ['TijjJ׮] `͔*U*v舧'ϟW7hcc^ʕ{Sw vv0jܻO!!H f+rsk7zNIXxm#Q~H*,+>\__ƍÇSNY~=nnnԬY9 q8z{S1LЭ\ ""_\s1c26cdn C=cVP0K-嫗OPJ~w.^H֭100HqwwD_^YT۷^jvz,gghNVSBHBH&L > 6PdIjԨ}휥ɓDD ҳnB/!Feϐ F`{@F#_o{#G Zu~x᝭4 *UbΝѶmwuAjժEYv4\vҽպ5$^jΝK͏B!g$D!q20JmGi䵈^qϯg~%U> * adH.^ȏ?#w*\V`v抏 D pwe,!HBJ#lLsމ[NE gԿ$扪>s͌3uSL!;[ƍ{{{&NHddAp&xyA֬o5uadzP{>BOJB$4z4*А:̸>[{aӞ2puU}fΜaÆqu.\s=zĘ1ccС[>>yl8w.DES!'!D!D4 UVXowC:C5 %r'6y3vvZ5U}йsg^֭[)SL?δiȓ'=z 44TݠMM[7 u BCW/ݬ葺>B$! 77NG{7vPDZ\|\hW՟> 6ĉ9r;g|D6mzCӦp4<j{ƍ>}t!l$!Pؘk6*&#w0x`2X+ln^]?j]FCŊٹs'.\E%OVeժU,XFqiuhZ5سG*V˖pW~u;jY!x@BZNLs~7TmY,+R~iy~ x%^UEeݺuҹsg Slm6J.M͚5uAb`v zֽPnY!EBfijɈ#sKyN ?Ph^!V\XA6VU,\YOժU)_<;vPDooYǃ\Qte*͚V|)Z ٳgC!;ibOܹs(QgBT{W62KR;cnH'NÇx{{/w+\0ÇiӦ<1EϞvΚ>>|S d`ǢE%ԩ_}ߓ!=Znn鹓 vn=Ec?(J.TYda„ ܸq~gѲeK\]]Yh/_T7h 2D} Ȗ @gVg&OnɎrB@B4 urQ?Gb]Dt;ϭ'T1cF Bhh(>vtfϞ m;{69X tqxvF pA]B!"⓪`W;B_ۅ**.9spvo௪?SSSwNPP+W$)s ޞ &nffЯ7F9sbg'u{ 77]u !Б"(0Zk* @ndWF櫪?CCCZn˗ٺu+%KL]DDcǎޞ!CpWmb05;A P{့қV=)u!PKB*Oa_SszIh嶶̚57n0zh2f̘M||<+WՕnв%\ cVЁQL"'H_]);KB|$!U6ӟ;b{*U '~#]R_ꡡ9 2$kjY|9...tԉׯ>4o.M"p\2g>Hu!7IBt% /&o(eZZkrQU[ZYYEhh(Æ =5rONtW&r.7 /!鞹9Zka%L 77Weƌ3 _~$kۗw4kW`x3lƟ'{R1GO3,BHB07u\~-Sζ\bݥ{a^\=LzEEXrerٳ w'kKqpp`ܻnk` cG "lҝ#TҎ4?Ep)0 QU,Ν;G%8{,nnni=!HEa_>F;I{۞%l HL#yx{}6SLaѢEƦԔ={2x`e˦~Ϟ9Og g N_5 #{ BFc-|;[(hb]}DCC}>>\vnݺxztt43f o޼ 6jg0,,`hBi5Ȇ@㢌auNL"nIA!(@B|4 ]s966H,! G n歯pvy@}wsfѹsg ⧟~"o޼9u1 cTׇG)<6;m,\^}C!Ҙ! =M 6ůuEy9A:v|jB|$!үl?B0TL!JNбW_lYh_puue͚5\|&MT\ n ԩf,ʌJ WPٕ%ZfܹZT|Z=WڄiNBF 0о2!I%nwm%CI\BӦMɗ/X[[S|y֬Y?kkkkڴiÇSwɒ%ϟSSSԏ"" U?<\v8NEۛHU}լYsαl2r̙>&&)Sꚺ՝17zo5teG݃5vÉ_.ݼen! ͛7y9ڵۛqahhH֭4iRb0*UDHHSLaРAڵ www.8\`;wpP\9ôi> !4 \pe|%KˡСT4IW׽{=z( }}}O>;+JzʟTTJpZEi&%B]&;V6\əo%tMZDhruueϞ=l۶IyˀȖ-NNN :oootݻwIv @޽~mΝ;IB|J,61t8.#ZA>p9{Mǥ ³NTz*ƍK C___J.M.]޹aJ2&&0f fg ho֗"E5jEÀ>IyK9p+WzՋ+V qOhQallN!ķI^?YVhgf&Ҍ՗VԔcrU~dh"7oZV $ wX՜g!gCLMuEB/@Bjhժwz׏L_)d$155%DzݗBoJv#d Fκa5.ZwxA9.{m7o޼l۶ݻw䔬>22={wqe{ؐZԸz|nIt.vдK5}}06! Ѹqcu%i=/͙3Gh4ŋEQlٲ)͚5KYQF]FI;vLh4իS>M!(J^ E)(T0,(2Y+M6SΟ?~OOWWWѣ˗2q)dV0C1䥒;#e(k+ IDATEȇH2fNm=A[?űrJ)X ٹs'aaa|ggdTX={Su32Qy,˸0A\UgSx̱cGf@H$]tFL0ŋE"Ep3gD__#F`ffFժUaʔ)4mڔ"Eо}LLL8q";wYf,^m۲fFI̙QBTQ`#Xj'3C-R /^dĈ$7o `ǎX;ghv-4O* F 쇗c"t!ҝ_^qwwWrȡ*8p Y+W(jR+++uSwѢEbll899)?{!SzB!ަj{(zz 'V` '̽)/S{pRdwլY3%<<͔d!"rա>+U Ό(,=6EWgk4ڴi?~pŋٳWlŊy""B|tL~Eq7OŽ?̞u\t $ \rxyy+u37Et3eҕݸ* ժ}wv/DEB03`h\J@JgoFXyb~fʶmXx1IZ-Grʄ栏ME`XZ>p !+lᶇ߳%zW:א5P<Ÿ`;rʔ)+V+VzG{{7NNQݫZH""B|4zt+ >~x ^.ป-|~SёG2vX3ڵkG˖-/P70|'""fM<"fj&D!D!fe{k7,BJ^~CCCƍ_CSlY]~`* P+z8ݘJ-B4&D!i4ZjWA\W[x|S\9.\@]|%Kk.;t'7j͊DE^""B|#gcN~; Z]b8{1YXXdlB]^{ ?x5@_f̀eHWv. Lo B@BoFO>|VШ D,On1DF~Fq *nܸq4hЀǩ9p];BltP,$HB!AEL3(սUs K˲e|,GGGNVZ`RÆ `j+;zjՂ'O !B 7O`rs ҥKߖ;wn9BNխ[zsj ++ɓP +%HB!D%sTSTW‰t-chdMLLXh , Iݾ}^:>LŀJvʒEw} T"t!! B!H&y6>@b&HMMy.]sNjv)*T͛7HȞ]w}T !B%0,4_o² |.<ԪUCamm<00s*XPBllt~~P*f(\z?!''D!h_?;=w8#/PzQiN:ҥK_aggTXǏ+'ꮯ^}=KҬm۶,؂UO3g.6mڤ;!B|P:x|UVTaŕwuuرc,X0Iydd$5j`׮] !٫D-]n%69u` G-ŋ/$!BYɗjy2dUrʗ/<:: o>qp펕7/fDq,T ו۷KbfJ1zh} !>) B!P̚=?{e#d)bԭ[7Iy\\5z=s=9( 4օgϰc ?(~!B!cZie#`_SUoffƶmhӦM/^~0*DT׻c] -[V @v#CoWߥӒ"B2IBÙvl{xАe˖除ѣGԬY004&T% Æ5e媻B|b@B7| =0ǦWOO˗Sf$nݢf͚}JVxU+~yTKiGB!>ө`WamVս-[uIʎ?Wffny2]@9d}z͘1#k׮E__?,>>VZIJY%l&̈bł??!D!T<Tz[kB"C~ԩ]Kr̜9'qݻwO`S7&/hld 9D4"D!ܠhcyGg/>|8aaxx-6mJ*%i~z< f33ڱY84_?"$!h4,L =5ڙ3yd]10iӦԯ_իW9s$mG(f@`h*qY>o9]4 D!E&L|x=`"#>|8nn i޼7n${ΝS&iиЎl_;GB| B!l<=hR 0tݼx[ȗ/_QFalWU12 h 0c2 "g&D!ϵs$Z-% L0!I˗ٽ{37.q׾?B|$ B!rZdbՉדNJR !s'E.\8IɣèQgR>|?#D!]ג]`mKK8$3 zzzL4a`U?м9 ',u%ر>#IB!ggb`$^= ၛ[2&:U$u:XL-*ܹsSXߑ"B4ѥDg`㕍o@@2fLROеk$eqqOw頻jTR‰| vW^,^cI!B 3C3ʋ+u+o<B^pUU߫jy>@^@ wI!RCB!Lbm~Ywy?YXXТEJouȈm۶#up؍FӛM1dȐw W$!"Ȑy™;gTFuEx%kբ&R9r!ǐ"B4(# 淗Gsss*V* 0KUy 7>OݽB"D!iԒ9Kz#"UTy`ƕ+WR3b|VWQ$!"͹ټ>nC @_OwYDbB4Os^!DIB!D+aSBB\˟?OW߿ϣGRz87Q*]!B9̑h4%`ETjEϐ ͓A !>!B9k݇'1ݻwSR%%P |?ϥv(3dA !RCB!Ҝ1z̷UsAElZGAll/=r<#@!B| uk@2Rվo߾JC f+ӧO댐x@{1CB!B| in3P>w'O!`h3g6 Ro˯R;l!D*IB!D G{^5bbbTghhĄ̙33d6Uz@ӧOӫW/ ,H y%kOڵښ6m]d ggg|||>!J\\m۶#g\_{e*f ٲٰtT@LH m !>য়~b۶mM.]8rnnnINT RJ0e Į]pww'...I ,s.\ʕ+G>}6m~hd^@r咄GGG (@@@@bٖ-[H իW91h>|z葤Ϟ={ vD!H_j׮ͦMp-||5X"""ș3{ѿd<==gB!T/_|yZT6o [vGWmݺu[.Iٓ'Oh4_ P^=]ƁpuuMR+W.fӧ{)+x]xq@wa:uϜ9C|||)={6nnnqBt|ybeM^T{̙ןto#X[۾qJBB|Ξs= E|R sQD4A^Z-͛7חM6QL5nܘ;wXvAhڴibYjհbI?>ԭ[<B ; N (buJݗ'8)'C--Z wAzx!WNRߪU+FMZ*}ٳgL>"Eо}&&&L8={ҬY3j֬ѣGYf 'OCB;/3Oq;]~UM}[dU` 0mڴC ŋh4ر;v$h4֖?0l0`̙?tCCCfΜ۱cΜ9=B$.@U֩S\k4/13˚/O 2ghž?wreԨ9Kt'U@٣mNԩK!yvOnBtifW}ׯtLL≉Iw@2%K֭[/(D!iP!݇3 g17'UGmXFB!̊+tS@8w\L>>Tߑ"B4q-gLMԞ{ȑăX["D"B4* fk׮Mr]xq,, π(ʛbeyn!Ŀ#D!(,<ΰׯbbbزeK-[bjr$l[,hRx]HB!gw"AATSUݻwɓkFC-01IEYfT$/HB!gŠ튵# ׽J*akk~+ظQ6T9j!AB!>h_Y97R@>}`OOOLMQ!_n]Ș15w|TṲBBBNH"Ȣ ."]tm X Z0`)*]-gd!d&$@~gn9sΓs=r@DDDħml75IJ,,ZW~~~1=(G{s"R@DDDgNgf{ Qa̛7eߠA{G$3g; ~9 """3/2j?deAZ4ܒZ IDATuֹ˿ l_ioGD(O$I`[w`~@= <%\ <W"NDDDD|ϒ_իYz˾{vЏQQЯADΟT={{y̓rnGF;\($fAV>~(A@\ {HQ aw~q'Gou]E7ӧ.;ty-XӧCn>ug3HQ O acQ\yG8|˾ӧbz@7""JrN.-?|jlRRj*~m}+sKyp8 8>kl6cƌ`Ȑ!/9 """R2s3pf=͟-hQ΄ \ԨQyaX=O[ᣏH2k2z_E>vM֭"RVKDDDգ./_rOڵ 5k͚5+<.$3Ǝ-!4~:ر# 4ۉRkGp)<dÆ .{ɓKlAO<;v;:v;Qn]xZOLLL*"R@DDD\|Ƿ.}ڗPC11 ǎcĈ8l̟?6c@~ ~Ⱥp4n _maÖA)KDDDۦ#n$Ǒ_:I]'TۡVnF9rM6]7]p̙;Ú5Xx9/ws݂HI`Xa=mnm:u|}Wذa˱޽{%Supռ0#o ,ӉHQ2;|0?ȩ 3I\jg.lG.6mʧ~gdQ3.]̽SL @`""""er0 ub s?Q;˖vʹ/$$ŋ;wE0&/o1{6̞  ! t h;OED YBX8o̜9x>.brs!1x=\8VO#"""""^9v?G7Pf=]Fݚu=>4h7y G||<}a@m{?FO<= .ъ+Xa "Uxd㑍ߓ?Y6dXp ):w̿o&|x6Н@JHSR}W\5L? @l=>ǁӧ(75Xd !$?!e%me0 #ӈ)Hoϟ>9ݵQF}&{ў_ps0q]vgCj6'#T@DDD-0xvLbvnfmK  <#>>eiѢ+ x^vƸ~Og<]cb|=AyP@DDDFwOzʹoj #nUW]6|\T"#=<ѣ;_󘳭63 "U+""".6J|jAh}l~cǒCX(N³r(i-_c)y p) ^]z@DDD_׿{9GDPo]U0 {Ç>Ys `쏓V"E"RթDDDD8v8]yɕ|4#5<L0>ȱ^z:up*osw$33믤O|ۓ8M-VEW~}[D>jnՁU9  ]\ ~V*;vaÆ?9vw[oPp˔bۙ6mۤb LƏAbAD-X"""T=W=M9GtH4Ko[s*|lݺnݺ ٳgﺄp@|M^|E6~0$LZXXס"RDDDZh=wy7Nls׬!j<_Gylcq׻}oRZ8lyk퀦@6-)l~~X%TE>T#))=\+j2,mWѣ 4S ͚5cņ(ۑ];vҗǀۀ@0C%xM !">j0 ܷ>=߹^gK׆]:ߢE0aSD!zb…DEExS <l6 !ͣn$+ē<&k"!BIV EjQOϤ'<*?Lz5ٳL:w}x{'+tv-#1?g z{|]HFHU"""rʱʆWxzd̾!Wشicƌa޽E5hЀ{{|S9Hs78,&B^][ykT) """0/ylc8ùahC\7amaX<>_FF3g'77Ç3w\oN-Mg5$R^Әu%Ow}=/رcqLKƒg؃p<BWHե"""r؝x~#/_0A^3!!3f0oL d8翌䍗3@cǴw-X"UHáxYu0x-;:߯; Bxud^z%^}U<.rz-vn:۶=uƍ_rfE, H0|ۗ. cbL>:oZZs^ %%m0f͚]w݅͛G9O=n> 8B [aK#a媫 7"Ru)TYY,ض~x݉6djy^呝ͼy1cǎs[&00{{(o&4 ~8zԹ{G.c!n \z**9DE.J۩ߘ<HLOt9.|QF`  n'Z~d {1ץgO3 sSo)MDDDćJ0p~u+[n̜9X>YxxEs=_&qp(S&-ş*?TMDDD]w1xԨ^z%>cyݹ+5kW_}w <ǏQxG6?oy%i"R})T0xd#qXmA?/ݗMn\_|e˖XvZnK^GG} IIIꫴk׎>l6Fb˖-,[x9_}=zȑcعz3!8D:SH'O2}t4iBNXj>|0{&""Yfq^~emƍw;w.'NdĈ1qE͍}^0{;֯_ܹsO,5j`„ L:MzEsϮz6~t7Ǧ;%p5}HA;vh~bg~9s&lݺFЭ[7 N|?ΐ!CO_`u]ˉH;v/mPb嚦0hnj{A~jj*|se%bʔ)L8Ho̙k`,3m={` Άk˼8 %J' hKSq.\Ȑ!C_~'8w}GRRs'MĂ ꫯ3fL|(GN߾ݟb FvgTQfg*A6mbܹőQʟcbb:u*Ǐ'88ػʂ߇矇}\uO<72|2ajhػsLPDPsgT ^8r'OK.EuڕoƹuV"ecccZ ""UazW>99nv[ҨK~4[鳎9|/RbYn&n._W_Ç]]u<$ zYDRZ= .Ru)x!!!E~$%%? l6q}b@@=z',""ɱ&~ ,=\&j?QF!!HIIa…,XUVмys.nvgWo9s!=؀f[y&Na̷{RةSƀTO ^t3RPP?=O```]"";)),} ,曽ߐ\zz(C[se |qJ,o6lw}7Z9s໢s0d<0ga$x5)CDzSBcD2dgg=Off銈Hu&[ηe:rӧiOa`Yz5 ,`…85i҄;3;m{|ũYƏɓMbO +̟oC"weHߊUXBB֯_NbbmX$%%ѠAb?gԩ5jF*!"RIov߷|b{9ƒj0Cc2 ‚ܖ+Mnn.k׮eѢE|z=z4}V_-܁ZZJ\v.zM6q͛͞0X7/%"3<ƿhذ}oڊ0)BfϞM|@=g}?, ƍVZ4jԈիWs裏Ȑ!C={sG'ٳiܸ1*SL'"rrv8J SNfh>_EիYlK.e׹ 66aÆqM7Ѯ]]'fذq??sɓ2_FvۮvGaw4k*F{TK/eɒ%0a&L8vdaK6ڃk$e$[jҹ^gj|W^Daٹ8QHgظUtߙ .>{,ׯcҥlٲūGEE1tP F~>kך=/{rV&0zo_9Sӄ<4X+T_ ""0 & z>0udkgF]T5j5&%%i&6n|=~k׮ӇrWpcK23C/}#Gy/WIDAT[U_~1'D ~jnrhjLDD;O0vݤfz|5iS m봥Ct6Jz +`֭.a~ؗ"((=zлwozM=9b9ƍ͹:}+Y\ifV;G?ɣ2rd_>p={J""rS ,gs[DՈmT[[jtvv6m`j֬ɕW^ ]v%0<Ǘdg;ݗ󃫮*^L5z=9zz8{rD\\H5""rHL%>5xS9r=./15 k6hD({f >>7:O?DZZZUW]W_M޽ܹ3~1ðl}$ԫW8*ٳaXx]ίo2s9җΝTHP 1=H(9~ݪHhٚ5ڵ;vsNvO?ɓ'|Π bcc֭]v[nh{c 1X֭ݗZGѱ9fxx1xJ|)߿/CD*H%s$0sts|9AED mTv\!wj]vΠѭ[7ڷoy<67~5p"#GI]=\{m{SVgi0iRe_+ cC:_Fq~4kL&4 oB&4 oJ&݂Fa_SRR;vpȑr9&MA[nRfFTH C mRM /+n'!!8~vYIchժڵsvs-0?o⺔vX֮iӪ1B֮5'Po|*pÆ}E""%S*0 2r3HH&93̔3Ip]O)#]ݱZDDS7.j֣nͺ!u]#kDV wǎsAǓSmGeGLL3`䇍M0Dun()Z6tlޏԫZwUO?n=*""Q eY,Ҳ8uL!<{%)*,& uk%28B\0 g8#?`dgm {iqז-[aޢa#Ճ 6 pQ3U9s[? "6۞MZNii>\/z6l}'-; a ""(p"#vpm^* 3 'N {0233+hݺKh׮-Z(ݺv.T1,͚]зSyiᥗ.!+"R)+++e˖p8K/_ B̀|Jȷg>LVhܸszz@n{. t ͚5sَاj¾}A#ٽΜ͚Νn݊ ę30|8^ qqp뭕}E""e"Fb؎.oAnf.?r?-Es X,جVV+6ņjjf+ضYm> ? ?͹nڰYּ{A,%nS8U=)SG.餧֭[8p`^yXV?OL4iR$X䇍h? {pػ3',)6""*/@ǎs ,Y\SW$"Rv rAݻ˖-jq+ C#{:u#}^ϖt#+VXbV,y۶Vb sffj3[l:{+~ylW+~yf`XlX[,ּVo[-~X0Fvqrs~NO>mAn=\v\snc'777oQ ۞þ}Jkߟ&MۋQ^=|IIE{3v /b>m[si|첋vxyf^+Cm>r*DDΏ\MFfxwаQ=F[ނ@KǼjs/^*$''BJJsY+PիGz[sp_>6o?s8̱,{KL ԨQq"t F͊g/ bz4kV5nM9 rAX,=ѣGW9 ++t̙3O① ׯ(ܭ׭[#<9 T2=^Xhkh*j{5aVX lv:gq)_ j0 233rgtw %Њ퉌 8p}ؿ߳3ܩ[hoF۶РA{ 0ƢERF=%\Rٗ%"R.@Zp8dff|?or88\mw<)S[.v!!!DDD.XԫWGѣ#!lQ|TkҼ9fC3*Mpp03f`ƌʾ"/3_`yb7c0 }R9psDsU$z0Euo`fƍf *L= lݺwA""rSϟ',8oa>"Uk⽋jFԬYPjժEhhrAAA^H9.#Is9q\ o< 9=aRڑ| F7lŏ8xiݺuL:wﺬ^'jKD.ZG `Jဳgرʾ_.[*XR޲?tɹ} è:H"#Fp}|6yGlb\l6˶2cϿ2,nWoOHv dHNqWx)keNDGC:U|8^9/_ζm e)|W@eZyWx'駟طovv9o_mwܕw.~~~Euo|̄N^K: x_ QQ֨/91tr-pme_\@A+2{N.9 *K7QN DiKt^H@D;U(kIPO 0/x--T;Ï?HVVݺu#T)32>c͚59sZjѷo_Z#_5zYלrPcs8s{h8aCq!̥F\\#8{P/v?v;E#o=hy]O ?@۶m}Vo"""/ҳgov?X>|s-Za7;,\s~dK ,su3(-Jg2ηj.6[ XINIIyÇ+VXΝ  #\\>c#gG?K,c1Y l6#l6 6goo[0\ l`` b a a -|zCf,ǹVo߳eK qqqdee1x7 0|!TC "^ի7СFg,$b+mV> #jQzoj[,Xm h !8^kM@ >Ve\@DŶm[yƀLZ{;v!!!}""""UHԪUɓ'3yʾ QQQQQQQQQQQQQ WٗpARlToez+V)Hпez+V6QjSQQ ڵe˖ʾ lToez+V6Yo,a}bZ`v[e_?d̘1}$*$11KҴiS+rDDDD8pRNʾ ((((Hxꩧ4hkj{-k. Dhh(7DeϟO۶m &&&7x"yZoZEmۺ=\o6mbɴk׎5kҤIn[ZOMmՎ;9r$-Z $$Hzɂ U{siݩ͕jҡC"W\]X4ɓL>&MЩS'VZb)Rݛf͚ř3gxٶm7nYvܹL8#FfLBzz:?/^?˾".z{X~=#G.#!!7xX6l@vsyZoV9{,iР|駌;joxZw6WÇ3sLBBBAmx%]P deeǏ7 06olX,+RnĉFHHq!˗xwӍHnpymfԬYHNNo[RW~0rrr\ݻ 2n6>5W֛ZvѩS'q}joqWwjsŻ[}1ڷorLmx%՛ۅE`I(aʘ 2d5jׯ_?bbbO;{\?i$ꫯTO-ŖvW)۲eK.RvܧzXV5jf7ϸ;Psg͚5,\W_}0%_mν .$ rގ9ɓ'ҥKc]ve֭sbZ+btjժExx8L<42յ Ιf檢A<#p8ox7_Xj6 ۂ 8z(3fSnjYowfҤIٓjkpWoVSr7sQ,Xɓ fjo(@m\N''$22m<7P{,9odee9R&88ltΦNjeŊ}խގ;_ODD~^_WoQ[֭[ӷo_n67^quWx ԩý[lbC5>"K>5WԛZQÆ 3g<3Ƽyӧmڴ1V ފ::wZ.e xRojkE}ƀzFdd1x`cEʪ<ӧOCE͕zS{X iEDDDDDʉ((((((((((((((((((((((((((((((((((((((?cjT$IENDB`python-beziers-0.5.0+dfsg1/doc/source/offset2.png000066400000000000000000001740721432301424100216260ustar00rootroot00000000000000PNG  IHDR XvpsBIT|d pHYsaa?i IDATxw|M͞$&1*^*UZڟQ:tjV(EլMW YB{~ed$|y={oO9]EQB!*B!BG!B!HB!B!B!HB!B!B!HB!B!B!HB!B!B!HB!B!B!HB!B!B!HB!B!B!HB!B!B!HB!B!B!HB!B!B!HB!B!B!HB!B!B!HB!B!B!HB!B!B!HB!B!B!HB!B!B!HB!B!B!HB!B!B!HB!B!B!HB!B!B!HB!B!B!HB!B!B!HB!B!B!HB!B!B!HB!B!B!HB!B!B!HB!B!B!HB!B!B!TtDxmۆ]!BQ@ZZ.]{899Utu$ ȶm6lXEWC!BҥK:thEWJRxxx ݠALyי9sfEWʒ߯7,= K*AA0e C].] G; S§A$2k% $!x`DEEh4}xz4hos'\ :]E\%cc#س`Ɗ3 !z9}4+V )gZz&M!$BC!$D+*bnVRm*}{8qFRI0c[! "D<==y=z/~m~=lYXc rɝfM '#'̙or:{BIBrej //3{jm1֭S_eiN 5e=BQi ]3!De"D!D13Su<3SugX\N $w>wspRQ0FP͝.@BTJffned䅓;ʕj8Qu^K1'Nʓ-,^ ]Z!ģMB*|}խ$7,_y&/ (5jH8)+#Fsϩ_}*2 B!*4 ~/=].Cpֶn]rwQgڹ-R !=2ׯX[[ӤI̙̹sѣ8::2b?L ۛ_C!D1,,Q#&O \[)uB N/۶-=4kCXQ9̚Δ!]+!DE۶oN߾}i֬SNƆ m"##i߾=իW?'99SNbjj/pBƎ|8ׯ'""{{B Yf=z2BQt:HHhuv-y?'&>TKbE AB"-ר/go6 Rzf1«%ގy+HJɁiOi{.Uғ`g%FVZv-}ч.]իdϞ=$$$0nܸ|?~<˖-c :BEt4Ĩ+:PB]Pɿܖ[ 551wbXs) 5x$GKMuրnrI@K0Z0:@[3GBݺB(A:`Ϸ(`jjE>:vT[Cn}Tẗ́MsNUFDD#$$kkk̙3177'**87o^-Z`֭?;v Pـ8~!D呓EuA`e}ࠆ[ۼ Qԟ{neeeECɝϋz/%㱉/&}p}]A"[JrCtZv$w!6xݶV[*Fi|ƟW"V32e fffJC8~\gӷ:H} ü$ !;;zѣG3c Ü9sy&˗/ڵk deeajjʵk066)_9333zAξ}055{';._.r_X ##uZWWu?W׼mmソFQ͡z+;[11{#&K TWK6Ԁׁ4HZ^>GZ§;-k.= ''ذ́RZ:&xHRRRucǎe֬Y0/LnwZZ{\/!(Ӿ}{S:_oӘRt Phj_&&oVčO@L%.^$189͝jPxsc󷜘^ƫnǬ֪:8׾_ L>H"""wnVsssZ-O@@u-RtI]c>n-zFF(ۼ!] 4&9B94m|:mej+ k'ZgO?L ]VEn_g[Tgb7761aUl,;SS)AVVVFٹ.Ro 'ʥKI$JZ/R"ɝ.n`@f^zUPr7n.ޡʓ]ɰ{CbblQǒ%K>|xۧïB>#k'ZNXZoʌ3*jUWN:Yrg{V9 xyyѰaCjɩ(((GN.ۘ(+.]4-2nsȿ\ɝKT Å p{Opt_Z5}(ݼ ߅+6k!9yR E16V4z&kWv^BbR͚5ӯUP|:.d&x5 5 Q^z$T"rBԷo_6m01iŊo1pR7--ݻwD˖-_~+[YedjX '-,؜^ p266FҰaC5j.9$g$LrF2))wyyFϳr`bd5fXZamjF'3.1Iq#j cQtj0ƭN*=F4ۄ5+ 'ϟ/ӷ4k֌  uGn k*~}[mh߾d(0gڠrJ(rVz@*9EyRu.1p'w'ҩݽ?tYYYrb0LMM )PL`>!\\\hٲ%~~~4jԈFj;ho]':%h\?/7vU@/Hu7[X\zIg&Z-45WjXԸ,( &'OѣܸqTm۶oߞ`؅aǐ@׮j \]  R v χaʹ$k'Z hL],N4\]]IJzf0Ry1v"%$ac:ضM`^@* i[[[7oN˖-iѢ-[ݽȮS|؀C<-3ll1 F:UHA_p+n>f9%pܨw;P p,\"^:.u㙁S1cOQ Lpv5333Zh$mڴ1:%jͰeK5woullXƍS'-X SeғR -[y˗> Ug"3G^[;CjС;w'iӦ( RrpM%5dӶia]77\m\qq6﹍Vd})xk7n뚿;Wwj'˕N;v(7)B ءִk׎N:ѹsgH ZǢq.!Cƭn1//pA c#YdCTro\oёۙAp-䐷:<`~S߭ .c x[Kl]x![C`߿@e i֬zW^h03mEFadfع֖~vAGxxZID<7 &N,}&s<̣l(j"T ڴiCΝi߱=p>@ML V6JQ>3-ɉy̫c%;(Э+f,nu֌K!Q5:ԢCƸW+z\PyxZ`GGGzA^޽;OK={Ԗkթ `;Q fMiס,*-^+= ТG5 (})!oڶ SQO-5mڔ}{] Oq,'cNW);F]%G^VVϟBܜ7 ̅ \ng##+nn])ny;H9'D{odCtl掓a/8x>dee獌hժuϯVȊDwL_I-g931q IDATn ғR -[yc}/hQ6,ΝKܹC؄m]F's&}2U˶nOSԱ#!Ce벹AjԼfMqrg}y>xyA_4lFV/L<Ȗ-[زe gΜϚO[G5jTRR?0m{`(Ō7[`5ԭ[~uU\JDNhQ#̟HOC;-N㟍I:v'ORpY&@-æ fd)w)1n\(;⑦( WHn0H*Q0Z^8\ͼ;JYΨ{enʖ-[صkxyy1h L&M7$$ڵ/̨_b}yo0'Sխ2 \QTzrVz@*9Ey+l\\6{ ,=Y/x?!S)Z:r4Uz=:@-p KLqosV p(ɕfM_]7фuٳ3]u%+Ֆ/2jt݁0\\vD͚5ﺯʞ={زe 7oʕ+9???}W^NK ]c6W3k%UzdeeabbR!S)'k'Z .?@ k3]:ytG':xtޣ|**D%+wiW9sزFt@mڻ'.8cccZnUtETGj*V^}aE 4AQNٶ\.YwwX}<~…s[EAAK9きQ&O}QMT<^+= Т$ٽ{7 LEUDQO^+t /_NLf_X J7]]]!CyX1tJы 8ǻi[m/( bլ^WZnF39V~/ǧTU\JDNhQΝ# >zj^lٲ 7C>.>SO8r.Ȯ!ÚiߧqŅ(oo]g[66oп~rCot삥e)++={j*֭[͛7Z^еkWƌC~033+q##>X<_KL ,^`˼ATi:Um=U\JDNhQII*+V怜…@͌ 01yφOYR|ݤtw݆'1+Զ+ke'}ٸE1O4BuHff&;wdʕ] :;;3rHFO "a4Sh^-Vt:ظQ8rt->c ƖLؘ㡄yHT ^+= Т$ΞF`~he:| :uЊSd( 8_@0fAQШWTQo9d372s2 qt`` i2vuڕ$VXO?đ#GY]v3gygڻUo,^ {S!uȺuj0bq#>^{&_ζ੧eFD k'2Q^ TiDFqy7qƯt>\}> 0ɫb%|QyVdB 5/}3ovv2 i wά: G<}VVW^yÇs1Ə]ψ#Y&&L}1p[PxC #<:KGp0LVV $MÀбI`9}bj7$Ft~S>97X䯔>|8?ɓ'\l#G0zh֭˧~zٶ44I 01YSKР=qX+l=Too %:d_Z*@U܃t*KRW{[ԎuiVCj'ԿL%lldl zr4?Ξѐ7T)>fףϭĤ{͏W_}Edd$;ݻw/vXNJ:u;v,wwfa){S,,iؼNyŋI|nj¢|BTE@**$&Bjjޱ^K_;yIg_ٟWp]sFx~vRWB<,0]#vFϢUV<Ĥ&>ӝ#lG䯿ŋL:X`ߟbǯo7{+12^ԵDsK^\݃aҤkxxH>^7=Nŝ贸.+| l)l36L0i2ѰBT87[7&=>C61靧ӤFٺl֞[K?':%_.]ƍy.a:t@VXj5b:Ӡ:{jLO;+fRW{cOrziyjXп~3wO홵z öy1}aǎ;vÇcRFˋ3g\:.W!!>+ ˗éSTa%9KC䴌_\̴[BT @’ծP >"ǓK䭵^S߸6lQq[GÚ5A!C/x=5Ϯ[nr[E/l/^dHzz:111ŶP܋K,ҥK;N{ex y뭷( ̞ ׫4[5UМ'@8{_Tr$D*gRVh^#֟_}a'm'=!w Hw FqQƎ&6}6o矇Yە\tk_M cJ)ycE4psG::e0cFdj;w.L6 "˭[M2d.\lܨL3G<4h]xe B P2F_3ˇz1j(,YB'"* BTae7VLJ Vuϓlҧرzw,e Ѹquo6l;>oJӥ ,Z.%̳':Ok/4tnaEc6&YM)9xj}q2e /^믿ɩPEQXr% 6dȑ\k&&:nuB|ĝNH 7K`ȑ5ƍ8'WBTae3i8aVAqxaVO]͜s |g7EÎsWWX J_9!x&鱧:{vV߰J ЍmH{[qPʊ7|pON t,Yƌeٲ_Vd1x>K}wߏLUXiHFvL`Z=~w_cٰaCύ=n` L>72RoA! ASۓ]#vrC Xc N#:s"<[C~Qs`cc{Gxx81ժU+T&''~ V˻/WXJg~DɏSMc4~>?[ ,D"Du떺nIȕ+=sտlg93 |/u|133cҥoօdee1oF1\O(NS^uȷ+{UEQ_EQN?L)(J]T!ʹ=BLR#8e[e~XeɊecs7+++7W^~9MEy)E~ )ʟ*1-0O1"["XrkE(<zD**"B}|A[iLzu<=8A^ P/9sfYYYe:t1=* OW 댍K,x͛\xO/ů:} zߦ?G8 [Wv-"|W1qD̊'w-v:k׺ž} ~~p@)@~8R9aՆtr:Ռ AiS)""DaZW>G×ϸ'5& Xh޹z9[k֬u^hɱcq~!08ScS6JAn`Ls̯5>6)㺹w+I2ׯ_gt&:iyK,9LLkZғ8i}s \Vʃqw@"#Y* _<ϬֿIO0d-YZnʘ1c }vѢEٳбb%~ h4tׅmödBG4k^8u( Rvm,X@pp0F¸wp>>3uB׮pj~A9v.dw]ݬYܼy &D~@w$z-/|'|C4_qqq 2lذ;W !BQŵ݆w8*N=a|C6Gn]9;E옞/;wgy9;79DӦ [1 w q[y޽$D BTQ@nݠNWÂ\ǻ(;3 7nm NN0zt)BT>c~q^࿈؍2s3ɹo]psr7M8u$u@3Q!+'r0 5kp`4Wի?>kh4{Y VuD!*7FÓIi9y: |Siy?v(c0vXBBBxW8]a4/Mƍŋ40!߂#(cG!t+Dx."Xͼ  SѣG``\y3" uKmjh4tΡiʛ| :pzt{]|S1eRٳ9qO>dw2n=-Z"!-̙Zv6| ?q#IUPTXTysۛ,?scs6<7 > IDATB,YRh!C0p;P$NaAQ/ UFwo?4M 3$v6F?^jrYLWjԨ۷o?~w FrH=\]#={_u { 8v LϝS_4 RR8"D*H?@Yuz7 #GB>+}>w"A: !C@ϧ'`l/jNڭ(ʼnn>zͼhס#Ceڵf|Ǚ3g/%!LԊƍqRG@7m9͛) ̞}{G<2$QahB(c61^ʕM1#;wרw.K|$"weiaatBR<o/_-ZwÇGA]ҠA/_"c}cFux4 ǝziFbccU/_>~WN>M*U޴FCdԨNK4u"ԫ3{r_Wֺ#B$ Bd@kľ?ܒ^19e (H!U7 _c``p6'h/%0 ˦>Q-^ʕ?Yj{ plW3TK~i׵+&󁙼>'M{_$I@ȀBBޭ8vnF2ek,X''+رcə376%9Y=ruˆ#{/_6mpM<<&2*;a5Y!?y"~x"c^SҵkWӧϛ{@=`2?wX9ժ*]AӈH=8Xʵ/$ Bd@o5} -ynC sl”)StߴN&*z$3gd͚5i}B*Pnnn̛7<Ȣ&ds yٸfDg_ZڮUٲecҥ߿Eq;(Ņ +Pa DRFL-,(g-7ypodl3b]t{ M@/'n:Uqs~y'o{,Y۷o ur Fsz 4=K!ioן?a?XcNPɣ=~Tgooŋquu}3P8C\&ND U8{:5h׎|AtSόܶܩ dqI@`uSksyCcg7ߕr;|׎dɒԮ][Rka6lw"_&lI<O-{YME 6NiY2eb;v%KG`JPUtfȑDDDܹx܂3h} P: -D VǞ8vvy5j|P֭[sI- t޻vD!DJ,n>p#Ԝ3+Ҩ]3/^45j0 dGQ|1e˖ѣ螆 D5yD.f=YY&K<=Yv-7nP'p$"y[d/@|<ѣGܿ/Qۇ& oԮMY5lڔsB$մDSf$c ]D^dߖ0JcccL™3g(W;u\u4abCTg+Es[B.](^8-ZV0$9VM̙3Ӹqc2gΌ9ݻwٳg^wٲeXYYabbU⿄ٹq0l:Q$[!! Z00zK%:ĉ^WZͣԱnnA$&ܡXB$dΒ^eܺ#y؆#U0vXZ*]!$đ&MJ ;X,YCCcf,hS0)B9|h-[bff$8p@h4WBׯsssyڵ)S&^W__tkBK (TZn@E2f̘4ſLEA3Eɟ_Qbct!) y8uTp'+rJ||jqbccOOOD#bҥKsYCy^Lɞ=B9x KO(X%t˖-8::Rm8AXZZqƄÇBxx8;w8o@|]~Ytk9>ǎ;8vEcǎqT/XΜLza[! f)wGoֵ]GNӜ<|No8vcС\tk@+.ŋcUbggx! }ŽS~}UއX$^z5kVLLL_> ߿ӧOKr^JOxEށ<YB03urJZhgEqsD0+W'444U5.A0VeRB|FC2 v kٮ nf '=S%Vb8p ,x/!w?`ZjW!RCȈv1g4i/^VZ Çȗ/_Ghhh:GΜ9344ܜoQ"n>I\ U?9}4!0Mx>{ɳ 7ZB$4'ZbW]R1qF5*q4 bc L&>Cٲe9x*KZjlܸ={ѣ9u a8##$'ad9f{| iA;kk+x[+"{lllR=zِ0~).%YMȠJРTHb8tӧcmm͙3gpuu~Vy>}#rC^ Lw<$hтÇ( &&&DEE%v}LLLR,Ohu HK@ŋ4jxFҭ[WNFI8U!,NuR}!(3sXcXf-:{mxMLLW^iLbb(8:*o.(ǧzB!읳JY(E*SJ\||QxBSP (FׯJBO`A%ϟ?7 mm۶'Ѣ݃r5ڷoV~}r… ]s…ѬY.ėjȑۀU>իz|cDFB۶pLB#}>#I&8Y hb.cU e+]1ʔ)#U˅:>#9s*U(抁R@{ʍ7 T5j)9rPuоr~E3?~~~TX___lmm?pgՊs%(l?to4d˶mЦ 6tHB|y3X`\rxe\~R|u$"ɛ)/EKDSkB^p4]ɩCׇ_M5 tσu`dWOЪջiYB!֩ J8zz]芞zy_I@`koK5)\pÇo%O~=({}44m iBӧ>>>4mZ"EV7ʔw ԩdʔSQ|e$"qkU-A[x2055eѢEozMll_* wnص rн>~7ׯS=V!׬Y3sU>ŋL4ܹsꡉ$ Bd0mt<&"6֧:M4SNv?x=5<~ ,[ ֭uBBՒD r䌁Lc"_Zf}oBu o޽X}ЮDGiB!$"*[ƀ|<{tܹsm6w]?Ϸ6iXoU {s'tQQiB!26I@Ȁ!Jh n=kժU+W&iٳ'GyFFxߛTKlؐq !"cD (OȞ]g i wmtMOO5kPZD^iӦY&9VV?/_NEE -HN4]!$ Bd@ͻZZ)+ί+611aԯ_?Q{ll,]ve̙KA@)[6]>}޵]@XXF.B@!2V!/4Z,K86x`N;kg͚]vѡC$FDD<#WWlx[R,YSk[*V4_!/I@ȠuӞt,ӑ!UCMx4722bڵ :41OOOt͛7)VOݷ)VǏO] l,Ytm׮/\(SB/$ BdPwkhAB5t".>.1Z- I_-Z nymڀ?ֳֆ| w!B|$"z?^&f [ӵ(tc1UVX`` FFF(<-]ƍ`k X!$ BdP BLI˜6 ݮJBvΝ;133K+/_ν{~̞ [Xaj0s&ĥB!>=I@Ƞ4 VZj FO|y7"INdɒI͞=ZjqsңukݔʕuaU \Q'B!$"{or:tbyh˩_v~c)Ssѹs$Μ9C رcGz!ǎẌ I(Wf̐!B!D$ aJ6zWw5A~_JL2zj/^Qcϟ?E534%GB(9j!"ÐD JW|k-&~oici4ǩS(^xTT tIP6ۻ!NA N!B|T%VrVg#τ .y3i/_ɱ .PR%ƏOTTTcaj[~8XZڢzuݚ!B|$"+Z;Zu(o ,]LuyYdaÆ ̛7D4ivvv;w.ݱ][iٳ Bh:qB:I@T_ѣ|z{֯yh4\\\8u666I_tU2vXu8Kb uOG/EB!gH!2}8;_*=TП-Ν~HR0..)S`kk˙3gTGjp ^^`nkOO(V ƍV'B!R$ \2e۷/:t K,^.\겋 ;P~QyoMh4t֍+W0tPٵkKf„ ~:AZW7-kH]Gp<LWK!ɒDV廚q f)Oi-7wgʚ5+Sv$ǣ駟(U+W$N9rܸ..vcf͂TB!Dkߟ%[&m܀|k6mV5 Gaխsi IDAT͛7zAŊٷo:AytODz=!ݚatkD &FxB!D~9Ms6V^P=맴MN< Oֿhҥ W\-iYϟQF4lu- =_Wn~jPBD4 ]v!%VZ%o ڄk6nR5^,Y9s&ϟ~9pt֍;wT)ذ]Э+[B!DfVm&9xNh-w_U5^ҥ9p;wtYz51Pu/;vɓ(0ڶJ`IDB4D*N6D6 [b5ߊG'mFCӦM9<˗/@IDGG3sL,,,Hu1U 꾪V} M[6K!JH"HʎYZ-Ǐgر-8z0 8EMYloRa]u!B$" B5eH#mͤco(*OS`9su&Y]vڵkill̏&q>*!sx6 CBkcuݛE|(jec !_ I@zHʠ+1nwi= W5$iq+UġC޻c֚5k(UݺuG۩-o±8rDž~t^`3?V'BI"(2fb4.:_!ୃ]TFwߪY˖-#Idzzjܹ3AAAjǦE0lgFS8S qP}pvB!R!>9KuFEeə]ǦjL===z͵kט2e 3gNGQ֭[G2eprr… re,.`e{B|X4҆1i!:A@z1B B!GhhU.Px1PlN1fjHcѣ133KGQ6mDrhӦ j&%Kbz6Yx55T_?~$QS8|XBՐDcb`{]w]QZ#I\K-%&.Fոrbڴiܾ}1c$D`۶mTP-[r9PƞSO,]1 lԾ,וmV!3% _/_cΝ;nTQ4[Q~k/޺}B{k^\KxΜ9lٲ1aܹɓɑ#GK5ѣ @_:uXN*AHLG~%VOj;OW!4ʧ!bŊbkk#0lٲ`FPnO=yr~Ofr,\ c̘1gϞدN:ԭ[F N$r/L5U89cH7uc !`r~|FZwE[6.ޘI&Q1ŏ;<ݫ1#~GG1nঙEᘍ9!wr~2KYh44/oKs˄c{nͼqYc1|pnݺ'yMqppFٳG)bh[N9<ԛƵY\Y;y\ YB!2I@5FK2u o9-chC?c2tPn޼ܹs)P@N>>&" 'v~UnddE\K\ "BB!G" "CӫB/ BȺ EI]qX[T_nbb Aq .p;{,͛7ΎM6 a?ƄXwv[h?/Gu@3mL!$Bd(FF;Pt-rO ?`T^R[W10`׮]ˋE&''',--?>_Vo0`ٯ߫sf7ӋXP7U~lʦCv\^\!B%!2ypB^@ؽ=C.#EXqsvڰoQQ744o߾\z˗caal7o2h .;OJW-0:v^g]ad& صX.\joD!gB!Dfb` jxr*kRl@0z7p]|;E=0zjlz˗Yb%JH_ĉ)\0몎;;4imraxRxcɣf Ĩ[!H%ن3"ۺ ~>ƭ¹͵RG0 dA2rV=v\\7o__iZڴiȑ#\#cy.~؞%%C9WqA>Z[gf7|Cƍ155UB' ɘ LW ZwJ*JXW!@r~K{{{nݺJ2esvvV̔(FJh{bnn4o<]vU2eʤ<<-"D3J1jW!y xs m;*V4S:wUdΜH|iӦ97T4Z)GoJ]+aU(͊zl!RZ8z([la֬Y(FIt|˖-8::R` `iiƍ>Lhh(Lt ܹ!D f)gyr?n&˷/ز;`Uسvu*T3f?LC; . Qm ۘ9g*W~B|)͎JUq3B71~Ś0b !oIF\\o߾.]:ST ~ᄊhZT"5r`|q<\y뮑dS(r{pvZ{)u-7U5kVFŭ[_ _~Xbtޝ .;Æ <9'rӲ }X<Q4(d"(= ;B$ o,ZwO?%{Ç˗/ɱ|J̛->|9sLsss&{##~BσV6 gGnnKϥ+zVZf͚qΜ9Cjh޻w/ٱ~zbccF VF\?R(HTZ7[x{v !H79s2xrddd>&&&DG'?m#222hhZ)'Ҷ8BX>^-O]Z]zC*Ubƍ\z?+ԩ%J`Μ9;2e0͋n0~+{'tb=ŏ.cVCYV kuc !} ȵkXd ޽{ܾ}۷oItt4w SNbnn>Htt4).>}kذah"׺uTzBQPuvtJ1t_8 mF`MwZ@:s uD̛7wd:[ofȐ!*TcUA"[n]{aÆ}ae|xOÊFyװaEQܹs+NNNIaii'޹sh]v%w E(WNv,;Q P7msӔ 6*1q1%vxx`[)S\Qơ)RIJV Rz\_^QzP?Nl!LZiE_e0ǏO客(7W^1{l,,,(]4dŊ\r%a+ރҰaC-ZD~4 Rzuۭ[7oNHHٲeK2???*V/ !ys.f־<> SYKFպ1+&Qfcűm6<<<8sL4 -[dȑT^]qi Y) ]G!xPnиnmB|A~M8lթS'I!·Ћ/P ={JrTB_`AB%%K$TB:uj1%"㈊RVPJRMaB;~>-{G)!/BBi{J5۷+qqqe,ʽ{J1jӾJ9P *kD[U%K%"BO@Oԭ[WI4jH133SrȡtMyIXdRT)H)Q2{Ɣh!2xeotRKD+ڶݕ{(?Z@wފ{%K*^^^JJ”)ح2aK(B$kO#=!2'V~CzUgQUFwqh5̙âExEɃ+dϞ]q;vpZ:cF8Yb aJB k' gD~2ҍ{tceAkpqkԍ-! Bɓ}qwMZO)Qj0y,kg ߃=h Zܥ fаm݂=DB~-d B|t(;9 ]a$ܭI??% sLu"t ???:c}fDDD6g'a }:`mQ9GY'х,>U7B I@3hQ;zQ V!?R{RUgŨ^zر`q}\€(\0&LѣGCC{wݟ9)* !`%:p^*pB|T23"y缘, !}2v2nO%qVc?{E1o<?~b?CCCt邛eʔQ}8~=3w[Z1ر&77{Bܯ$ BOt\472y6.h^4.RokW###Yn .]zo_ppp@1߽Ԅ̀+,E*-[ʂu!G!k'S"03kٮo4́qY#+a}{69q)UcӫW/.\}hԨQ}GƍaDFF: &bwn*I iJ…B<HF-H+Ϯqx!+Vh=19gP FTNRӪ4ҥK̚5UVrܹ͒s₳3rRu aaa_@DZ\ͿΘ @ܪB|~-$"~S6/!%>N23+&Qfc?~ 2|={b?cccwСCR}( 3f>,-Տ-jZ,!4y,7zBv?q)8 "EɃ;wˋRJ%/22///i֬Tu;a4huǢK75U+8~\!D! FFōo 5K8˩xOQt611o߾sN4hb]vaooO Xr{pIjܼ{͓EZzuزԍ-$B/FEC\¥5| sB7g_e-sO}śx%^ZMr޽;=<=zhѢL:P@B)Y!!0}:(ةSЮnJnl!ɒ5 S(z̬K$?s (zϠx >z`fhz0|.\SgjjJϞ=:t(%JP}DGÆ …rx`=Oc !r~D!y2ajq<=gĘڝ\ IQ0졪ɓ a)&_f,Y-[rQu׉Bn{BÆO?A"Я\^\! $B *%d'>ء`~|?S(Z>!ipvv"2 IDATԩS'~M:uTk׮%&&Fh45+ %KJW1Y.*D!RZfM97iGrhvqSQzr3:rUDhZZh#G8w;wFm?::kwↄ`x]4x@[qRQC h"xPhq \{ md4Z]=sμ^lg޽{Ȟ=;FݻV[ >OXy:?B P,Yu+PD?z No |BQA2}i;؃ĸx"'Ndʔ)ܻw\]]i׮z"[lV_Yv@ƍ˗˖ z6m2"SEM&6MmN w^+773gdܸq={lllhҤ w[~tݕ.Z,!c2WE_0w}"`*. _Ka827 zzdѣ,[el6pB-JjX~֛75\ޒ3̝;0t%aC8vz)BM9=iRʽa҆ Sˇ {7Xun[[[4hʮ]hҤ 66ƍQ fDDDXo!&崬섴l=a}t:u`6% ""L&Uf/h'`\Z{7Nrvuw"%Kh"N>M^p}Iȑ#Ǐ硵 (gٳЯ$OP",iyu˚Aip>wmV`0\6x-UTشb,8ug͚crEJ_?={9sf>S_nu1ŋ0z3 CHuy) &yܻw3ΰ;v?s ]v$sLQ.44QF~NNNiӆ>}eu ̙39s }%YsP>L-bܸqX;iw`K1T#6ap@`` ]k$7D.$,g0vX=^U틴W޽˔)S7nW^}i?wwwtB_zVÂB'OFW,%n,lC3g!" kyeQ݃Yh2wzMa_)Sd;wӧ+WXݹsC%K:uk%aP̝ /c< a6_oUh]DS+ggcGMlm9'{r;Gl^ٚ[unGGGڵkGPP+Wtұ{ SL!W\4jԈݻw[uZN:zχl L.seәMVMX^zܹ_u0 -[FRX"?uOβfXS޼.Ux{L""vS"""o[ ʕbGO3OH>`7)76fwu9dVvȪsH~qɑ#Gȓ'g϶nQC Ճ={`*(V ЌC?*33Ќ57cDD @DD`2?6c]ѡHm0G0 N.H5pzU3i۶-ǎcܹyzb_ӺukrŴi 0vmwK%%p1Ǎ\Tm(;Yq'FD['wL33 R9zϭN){hU8|0-"D:{,;v$G|֭fn2A͚k[K,}SΞ\E[>Dž3:.KD< @DD䭕-C B LHvQ^;H->>;wjº-M4XՐ/^HnȞ=;ƍѣGV[&Ԩ;v P`mزˉ9<@DD @DDJ]9$K/;%]il&{6 gϞ=^%Kʕ+-[6FEHHրU¶mi6ӆY$'=o27&EoR.!|YDD$d\rw=N"%=4|xIc_;lyUƶmغu+UT߭[4hYf?Ν;V[+Ͱm^Ulėl)ޫ,m|Mb2b edϞΝ;sN:v`hO)g 5ld>tΰF5OyIҜVzۚߒ=#dGrܻ;;Ž h7/uʗ/ ,رcl[[}˜ׇ̛+W.fϞ͉'h׮vvv1@@~!̝ ֛53 3`>SIh"ΑN:=5eVˍǸհ84͎×(XР[7֦ӧOԩSt }߿ϠA#,,:d'v&7>Լ>-Rq<{v˩Q#GfȐ *ą ^D  """puCvu-e60CvU2yFټ?í3o,Y4iOk׮s5vJϏ*Uk. /Nl92XCg;L-YV&N,YR<'ί{9"Ixeu b{s <@Jи=$O?ݻ D *ڵkٺu+J֭[)]4 4رcV 6%ém';WĕTyz1|Ѵ)> &pQ/"qDDD$lr 'MȻ 9!M;tr"+VdΝ\XX|Ѯ];qv)~f;Sg%}k;_,ҫg_esuG^*D """`kcK80 .$o/0 ""j&~ق`4LԫWC1sL2elfƌN 1N痢0 <Ơ l03h Bby=X-9-&_TР/#6}/JXz5'lmmiݺ5'Od̘1xxxo8ME`$p pP3f@D6퀈 @DDDdC㼍9 ˚,#Ю לǽ[>js:99ӧ- c>ΕfFX^M{-666d̘{{e˖Qvm2fKΜ9YxqTۖ-[}6]t6f׮]y!k֬\q0gz!i1;: ?YI_w"wσѳ"6m@11b1cf:@6mW%%ŃfѶv<\ ,UF ",:a`R cɤN:k׮Ѯ];/ۭ6/SC` Uyg"֗'dJ0W+%\\,*ywޤMooo ر#W&yzzxӓ۷oG\r[[rwppÃ?^¡2̌z38W ]BC\9ғop 6`lmm & D}QB[ռB; 3~60Ξ\qlD"qF̙/ݺuc<~GG999Ecb13ye vCGCj؃cCzsЈ}WYm)S2f9BZbxbryd_G o~4j9p8 ­rJ*4oޜ~ ___zEhh(΀_Dqvv&,,,9^KDDJg*V[>wE[-z;rbլ[iL6LD?Mdy~r ʕkƍ1Ù3g͛숬^K.E۴i4n8J*JI&EsҤI][8:ЫT/|Wa~}xf~l@Ϟ=0a׮]K|L0P\߿O)]4Y//Eah.ï;wǬw"^uG@o_|aL62d;wnƘ={vT/S6raL06ln,XK0LFƍiӦ-[4L&1|CG3dc]n)a2N1v*22 4RJnѫW/1ܱ0r0`,3)hwxI=xLf$iSb… jժӧ7 f͚ƍc=zQF H*ѢE 뱎;m4#w܆m|7ZDD*$$iײ0 g@FlVFV^ZM=cƌ˭:XHatf`<dž+nɓ̆5sq1q?$x&x $VhѢݻ"E刈0 j׮ڵ1ٔ07xw3|{d2Ymޭ[ҩS'N8ڵk3qDdb9ٴ ڴO>f8shEAp}kOJ :MZS[d2+X`fdEd aTR#׏XmJ*qA kmիW?~qЦ lZ88@P)4Ю:WUD$֑Gc/~ !Tޝ`\\6שSҥ 6^VZL6-({X :tk0cXHhFnܸ]QƎMO; """!cs5,(>^1iG IDAT0iB>Yubʑ#?3']t1YypBI:p(4h ͙-9nJ.Wδ")1L=N~Ͽ9]`K1N.Zmf͚qqڶm;wh֬1 &,] cΎ+<>H~?^C^WMU .qx77O aRLI`` V"}1/^|zj̇z֭ɐ @ޛ71Uly*okDS"""#'ޗPz4zqj)]mj׮͑#GhҤI{׮]N:k׎[g²ea>\r {C9")IL&ѩe~¬a)k k<<ZWpgg |iՎȑ#ԪU+ƽ .G}DD^a!eJK[p0, Ǵ")IJe,Ş{ݱ.*@펄H7j4W2'Vb鸹Ÿ?b|}}rUNػ \?~ -[ A慸*y$Qv6v.ݛcݏP &s|?9r=؝q[&vqa*R|۶m.\[&~2aNhyС!m+X"$.s̬_H`tɋ9!.C|AUɚ5+6mbرGw5|}}>|8f999i0~,@h0ZġWD^= """{yH#4(ޯ kp:T*w/pz簱W^l߾̙3Gg68p uۉ {wXzʔ%@ "UdXՙ :i'u@%n>֯_OgSaaat҅-[ɓMP%9r}"."*)2L/ҞCQ1KEHwZVض ɟ j`ܸq#A~ʕ+ܹs^:wm$X~JEE)=f\q8;A 9R~92d͛ׯ_{۶mlٲ?>1_"]:X`ya`"HؘlY'><@. 18:^zꉚޞ#GrJ=Kرc.]$j'5kpnqy&~l[ @DDD$^rv;i_= GgDn*ԭ`9իǶm~ʗ/?#..p>5vYDH9914f֛SJ'HȔ" ^k9 *Į]BZ5kVpMa\E- W­[[DbDDDDuj /wRwBPk~-mD952g̯J wV"""hӦ C I1? 9sZ.u+"/DDDD`G >skpaNz;ׯ?ƽO?;]\,>MA;cPrJԩCr~|ܽ{7As$U @DDD$R8`y匪6 [-nvD۷o{ӧOCxkk O fk>r%tiРWfǎ|L4)_K$IR""""Va2[/[m&e'T]AK= Fod2E7k,zꕠױ\\^(DX0,_ɓaذɒ%iӦdOjD( """bUT`ŒF%M2~=Xt)'L=gV UK5|~e2 XbS$)S""""V-=ZnM6t]ەO|g4h%K>tPF<8:͛QŊa2)%__+l>jmVw"œթS>X `q5o_S5)*d^$R""""0a1.m꾩4^Ҙ?iӦL:5F{.];wnpq+XϘL0u*xxX-nc YHҦDDDDu=KdAXW_I5#l۷oѣA֭Yr?>nJnp2` @" @DDDh)k?X/l\9cݛ>,Z[dd$޽o}3YrBޅm0ȓΞUBR""""LUj+i\p(É'=g}F@@@0sKg&N ,ׯ񱤄HE """JPmw-e6.ܻ@rDk2=z4۷~yڷoӷqfx~=hy2>H|)WÛvR0]A.ݿN퀈ēyrbҠjբO>ڂԩS 3$/'j"Iy튿Sbk`sT~!!!ߟJQp5FEdwK F%͟?3gF]X5شiKaԩ8>+" @DDDӣlgժU5ÇߟGtL.\H)׏w- ﮮxL&|$N+c"Iyc.Ԛ*uǶl8!B T\8qx{{ٲecn߾_ Xv@ YB:&؊xX$iS""""oҹXg"4Z(F;͛ ˰hbqFѰahm| .]W@nD|X$R""""o UO6Iq玉?l0lmmCCC YXk $$$M @DDDckcˬx';l:x^Hg|jp̙;Fk5kWbcjc0rx 4)7RJ̬sI,[O?;Wlf1@v@jć Bq0HңDDDDXռѵxWG}A6nH@q< KD """FQu9R`ץ]a}iñ>}?֭KtE2 """FsupeN9ؘ,?[>A7ȓ'; ɒ%E/X޽?"^3RJ1r$$ ~~~|a/L$P""""oҙJ3 8z܉x: }$^cL> C(ID&f.K( ɘ<#OQ@^ʗ/e˖}618M:`-d2a"Iy+8;e/'퍍, {ȱckѺukN<ScJf 2$aI[eK?nl#;PfbˋOڶ1!WKSP-H$P""""o [[::}AA ˵CPL7B ޽{B@ FZw~$[^HRDDDDJS&{vHsܸz/^NL,h l޽^wD" """V2Li0nfzoƐ:uPpȈN}z!}xA$)R""""oZ޵,sczD\t777 pӶm۸MxA$)R""""oGqp,u;kWpvvƒ :n|]$LЭ[7͋Ydߟ};ƻKde˖ܼy3qɓ'̙'_EDD$I R%_zvK,m`̍.]yy,K$222 yNƞMO\D$)Q1+VPZ5ƏOǎٶmEVҥKTP3g0|p˚5kVƜ2e :t L8ҥKӣGFիW]F`DDldJ4Ndd$oRxxx}!Ӏ!LHؽ Cű{?믿`ذa<~1cFhj՘5k:t|'Ԯ]ŋ-Rl׮f!CбcGRTHBeʔΝ0iwlb+$Ki[n=-۷o'}QzU]rwF===͛^ dȐo@)5k֌f͚%W|4 w9~ŋ~ ɓ-[.Y׍ @k,X ݻw5CSԩSSNqFr;C4i֧~w *u]paR/}Ϟ=h}c3vX)#""dzt+; GbXtav\w&qgs1}ZZnMyhѢiE z K"v͒%K(Yd6lիtRTۦMqQmUT!UTL4)&MՕZj;_DDD$ z )<`ƍ:u%ɞ=o~&M(Yl=3:t`„ q^HRK!UVQNn޼ܹso޼9dɒ%T\={FEhӦMT''' B׮]iҤ իWg̛7aÆzl$Y]Y6m~0ibV(nx  ZD 1LZUVEg23/лwo>#]6GqfxΝgdΜqѣGWDDD"͑~|bٱ1#te$y @DDDXʞ$<<NzyL }) """V}y%2يbcϧ`mذ7nDkk4O?Z>Z%f"Iy+=5Rij0nnׯJ,7l_QP""""o!W9z(+Wrhm͛7 EAEaCk,_$IS""""oGb6ecʔ)ܿ WrJgU`ذaF]ucc<`2Y$5 @DDDR>N:r/{qh/\ԩSmۖ4iD]@ӧ-TU=D" """V ľ+(Yk%K`bŊ1ch|WE];880hРh} VZ>;9 eQ[eQ[hA\8}4888-[6L/ gΜa̙ر#2e\m>1f +E$)R""""o afyZ>ڒ;wX/vrrb'[=cvo޴4֭ :Y[$mzKDDDo½ TN:t1ڵ+1>16l4xzB`^2 """x1E0 0Qm 0 a&p4̙S'~"y+r%AKpsp^z?00uEkٳg<|}:;y8c9I ,l0(ʹsb)"y >:W*OڴP[mit?ӧޗIL3 g;gUҾO_~2eĘ1cbv0>,~t۷Cv&ƌq%y""(7|NX%W^x&y.@DDmڴѣGI"EsBϯGf}}~S""""oA98N)WK}hm:uZj;Z[G]%=fe&MR!oRۻčߙdҀtR" IDATXP""*"*vR+b kUdq]]AEPT頀H @HI&u L2i$39{̹#w-rz'p}9qaq=Xp85kaaaG^mNm$Ϝ%_>@۶6l;49>)""""'lF|~NO^~lܸaÆys=a8r5Wu% lVU-9z4vp~=8pW7SO=7|t? W_Mq|8O]ZzxgQONXCB@D|MDDDDN;2wp߼ܯ.[{q:\wuXcᅬb>㫯xx,=^+J9RhRu{۹Murrr2d-X޺uk̙CPP W_M4r9'W "s@DDD̏ϰ"Ŝhі7|c}vv6>7nW_}Ell,] F6ZYzZT} 4%0IX cŊ4nܘgyիW{l΂ ҥ ̝ˡ᣹ &~[u oCD@DDDA͟3yGy+Xp!gu ^X`fwx~ 11j,i0\eI?<r8h]ӦM9+4s.f \|k_u 48k [n]d;>c2m?< S 5i҄AW^pqKFC #~`^Ut^iD4""""~2\]|Kx̥ ]MV޽,o.1OHu%\sul:OD@DDD/ 07sf#cȞ|"lGkvV:/pafn5p>of@D|ODDDD|bW.4Mi`u"-\6l*( pM^ΣM}fuL@P_ڬ"{ """RV]v/?-r[s;]% p 2_]&sWci_ %%PTv_?Z䴡HO??ik[cYCW~ V&<Ƌg1-R`Q "a/y`[N$H|8 XOwF![ W--?[ysliܸ!rJS:+,)dWx07pF;/}\@k+1xN1_.A4" """R'nڙ/ a)W`+]_Жۙr˹u,SbOg{"p=\:'qx0 """RM9j3|܄,dpWib`gpW?E''~ T*-7<̏ `r*J:e.}6 { n9.O-x'jAD@DDDoI8z 6 ?z{䢷&r6ݻadZ"K """?sG7kkB f^7? /s[/}֮Nz/_p/)I;~7cqp2|"g,w)‹~K =6o΃u'`sd"[ """_1|O;##|[Ҵf~{o& .7ހV`28ޓ"[ """!מ=qx|t%%6[˘!^K0`;ᅦc!$a>)"+>"ckaZv~Lu%<zcX0 5<<, .w)Ivm 婧bDFFbZ1c׺III 8FmFFF׺:u"88:0e_|>#)bS{V ZX`8xw-P%c2՛7_2`, p0b  6@D|M3 ә8q" уŋcX*ۻw/^x!r1qDFIxxvNDDN}Ű|9SlIWn~M0mθ?Տaöp8<2'~N+ރ9R-U*00X èg}K.Ch":}ѣe޼y""rIO?q@~X^}GFcai4Q>rOidS*aLxst`nȩK3 Bzz:{O>̟?zݺu[|`9\ 7XKfe(kO_spUXz kߥ^?z ^|ov|K'`ط[!rRTǡC(**nf#::ڣ^`` QQQӿl""Rٰp:χݫT49gľC3È2gg!..Gm`d]Ǐ'{uB!X"R OSvN'^pKDDăa@R{~2bE < =1zA;S=Xr}JJW;w+WB>طo)Bpp0{ 뉈a.r#>Z=gƶLj`ж޹n9? 'BP RC:Vjj*QQQQRRBFFaX:t͛W9> M4X6|p^!"" ֬Ka"UҲ Jt&.@p=$x9xQ><DG u/OZ!! b={6gX@9u(B||<111Zº+W: UVqW^Qɓ'ӳgzl4\3p,] ˗W 3p| l݀sXϺ00czpߠcGXʼ7—_]wиq}O3 Rk׮W^ ԢSH-]̘1{/߳m6~aw/H~moUW]F́kj"JJ\NdzE|LSL!++}/dƍGƍyˀ9r/ݺu;tWPP'Ndx\~,]Yf1i$݄PDT|tvcR2RU ,e+.@"Pq Z]qmkL>ЯΆ3kwmӓO>sx*-[fqW4tDN)fڴiݻ^&0 , ;wUV¸׿͒՝f@sܹ3 ,Q{{.nBv-XaZu`K,~ "?].JV LG'Rc:sknbC=*?0* {[N0<ǣH2"G͞=W_BZo'l&r@DDDHM=6ʞ=UnVz,yEE+7ǜ U\(]sK[޴{6Lv6 ~]^&#F+ծVu娋//!rJSӏaMY6vSgp0k2+.k)FDbD &&nI .f~?Qegq0bxyxiHL4wKjwKDo@DDrg6vӢ`DGii,)aӉQ5ZC`e(lH-sK- j?t_ Fmvy'|MRS [† Gg7֭#Gݴ$<-- 2gv8ӘMEGÇ [[U55k̙;8ƍ~aom~YEDMDDDN>YY敨ʗ;o06Hl cɑ#wv[S%؈(6g?NGzpK-&Z4nQ..I`DQ;{w |FK/AhhwCD@DDTvRߦeKr:t`Gx8+ V"81}!8RrPuxknz3t1$JlbFf <8\ ؑ-ʢ"6pi ].BHkƖ-$(wc^qU j?Ϯp\[0~01z4˽5gGzˬ_i ""R3{cƮ]OeCA#eK6v$))M6flRSS͛ӻwozMĖEk|;f/Y&8 \̠~[ש :x<O`p?!"{B5 ""'"g~=6hpQyZZ._a.U.dЦ !)) ˖DfffbDD{fwtٝ ~JKrp]) 2 pgFYk*'ǼW;WzjQ0w\v/m[ycS)9Y9f*\g^yY`Q.dGaq10HHJJb˖-ϡYhܸ1zM[4eE ^[oV\GVۢ/q/7LM5xUZZ۷r|>Q9QBzzK ]- 53o1>U+safkwؾ};uoY6ڶmKNԩ]vO>k׎,~NKh2UgGEpA 0BGϸu>y6 O׵Zv\mq:wom_0 xz)3 FlBJ\.ݻͲf wf$%%gON dDFFV{vv~;\4vfe<0jvaT;`=> <ӧWôiдiCJDwi`ח2øR+)a4.4`0 ذجcM=jb 8crXnY`ve٬X,I5=ʶ(..deeU(bΝٳӉa|G|G 9숋:s@VhԨfIAjN;dl95nO5v݉Nѝە^{.]]ח~n4f~o*("")ѴiҧO/صk''w 6mQʇ-[z=2.ETvfdGvfdgN6glfsf j~^I=c݉vzsO< \7S@ƌØ1c)'0((( // *6mJllle5:{Xd:3+׻v܌cEDg3FNh├Ѩư n/h^b0R "UΝajHLlV@D|0 JJJ(..Xs_/**"??ԝw&MSipٟ}Gr8#);d3hd & m"& gD~}F~nͼcSOA=9-)i/o97nL@@Wy0ܥוx|DDDxcrl::adž#)6=PJghդ(p}B6l@@@]vvd~-K,eӦ ˉd ~CNDԠ">;u.c@3\ c 64,xUPD(i~l\c+coF[a!:dG0f'*-|I)jYf@+&񛈊9(ia„ ,__~c5!OQl6[.V66eժ;|.J\%搕E34챨^؈bCc )} q? %"8┸!7. l2V+wݻwofU+$99+Vڝ""RS/H9QQQ,[7o[n˞Ul~^cYLcG .e:ׇ KKU`!*$woO|x7jnU<40xwaw+0gO.-[e˖ SVvJ׮]$"0ܢ\r s-,},x^B((1!1=FGb'R@DN3E%E^CBmݯ@[ AAGlEaPExPIyLDN 8:w^ 1 bW1%R*2KN;-ʥu/6pw({q@crvFbD6izt}i/""r2Pa*}mJmv@ݬS<,0 G@駟ȑ#w}7 ))) :#֭Cy,ӧׯof4(IMM%..ewDDDDD8NGAAAٳg7tNj꿺S֝ԇu;aݩSPPPay~~{@֍ԇu>;aݨN}Xw`H\\ìRSSh޼y>4ic>|x6RDDDD*5{lwXr%C !;;[uS:ŋs5j^b zQ鶓'OgϞ>oT _|ڵkիWC|nS0}tK|||NDDDDahG>l_Wh۶-3f 99ӧ{ݦ$6էYvmC7㤥;aݩN}X7꿺S]Y}O;].( 0nĩo̜9Lwĉ˼֟5kz[)""""5sLn喆nII7|C֭OdLN'v+ ::sRRI"""""7 """""7 """""7 """""7 R\z)Hdd$V3fxiԨQQQvmdddx{ѩS'СSLn4w܁jP:u}O>\jcƌK.aضm[մ5+i&J۶m %**;YfUq]MP}YV+iLe}q;T+=='@=Xx1B{rs=Ǒ#Gxٰa+Wn5nGy%K0n8?~?w/jڇ{cY&M*;^_~aСt֍TLBϞ=Y|9]t4R>$''wAO?eĈڵ &UiaMݻI&ZoaTՇq3H5 aW6,1cƌ Fe{q/ bL:ս,//ψ2joV#,,ў4n4jԨ;pٲeFQQDzm۶AAAƭ^1X`픔=z0Zj^qX;Pf f\zF]z8PwtT+00X*ng1x`Zh^v%СC>Eq!>GMnn.ͫ=hx5ò.ÇWZts= vѹsg6o^1X!h ֆjE&k֎>,Y>^{ 0*zqX4}EDEJJ ݻº>}n:}\^^7&<<(ƌCnnGGj ^>,1X<222ؾ};'OoqbqX3Uu4+))aر{O8^u}XF7tԋT*СCaIMMfUHTTKOD͛7gϞ\.ϟ[oůŋlC`֬Y۷(߇1X=SN 7x#G5UUauy8^u}"tZvN'^pt4i$7x#:t`„ | 6 ͛73zh;ȍ7Ⱦ}5kcƌ!88o]㰆C8y'y'ZGj5C8%%"88 =SXX}bZN>ܿ?W]u|cv5k>Ơ3aPzQ6[6{T'QRRRZ䅅:t͛' "##9t{ڇ\y>| ЬY3:+1X믿l6oެqxp˖-8m۶1m4Ǝ޽{ٵkv"??BvMffajڇ8 R/㉉aժU֭\=z_uY^QWȑ#ddd^v:a~~>W_}5_};vX1X2U+jjc2.qqgʕ+ٺu+mڴaĉUiVF㰞4jժ*{<]YPG5,1gӭ!C+1X`*,+,,4ziDGGņahV&}qX cΜ9ܹseΜ9F׮]֭[s56nhaejڇ"5o'N4FeX,7&NhL86 0cDGGڵ3|McҤIFDDѽ{wz-bC5Mfvmb1{=wiw1h b ~Sb1 b|Jդ5v\rOӦM3&NhtѰZ?(hV&}qX{]tQiN>8-֭[ŰX,j5Vݻ6md\qFhhi1/^aӦM3:vh8}믿iaVV1b}Fhhd$&&?N>߿QWcл`>c.35kfv#**4hwUq]MPo$&&VXqXsPз,QmEDDDDDNBQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ1ƽz~ SyIENDB`python-beziers-0.5.0+dfsg1/doc/source/sampling.png000066400000000000000000001161741432301424100220670ustar00rootroot00000000000000PNG  IHDR XvpsBIT|d pHYsaa?i IDATxy|Ld }MVVhUJRJk%Ji)ZKR}}"Zj%v2s82 3X#;9=7osdX,8SZw@DDDDD """""0 """""0 """""0 """""0 """""0 """""0 """""0 """""0 """""0 """""0 """""0 """""0 """""0 """""0 """""0 """""0 """""0 """""0 """""0 """""0 """""0 """""0 """""0 """""0 """""0 """""0 """""0 """""0 """""0 """""0 """""0 """""0 """""0 """""0 """""0 """""0 """""0 """""0 """""0 """""0 """""0 """""0 """""0 """""0 """""0 """""0 """""0 """""0 """""0 """""0 """""0 """""0 """""0 """""0 """""0 """""0 """""0.iIpeV\/iHBBBxɚ5kZw牤Yr%-ZHn̚5͛u7H ;9s&֭ɓPbE _c|8?#C%22ݻw7o^*T@ڵ1c~!` Oy7_h۶-fG)S&ߥH*X@J… SX1:d믿oX@͚5yA`ݺuѩSDmvܙ[ntTǛ}X,.\`ʥK(W=ǖ/_ݻw[_2eĞ={R"""""/={6gϞiӦ;w\rsl\ #66z=ٳ{yV5k, }9G,m($СCtܙW^y0usGc"##qssKmwwwq"H9GLAϟAdΜc29'***1$~TTVMgVשW7n`ӦM̙^ЫXw;w>>>exsE\\/_N4 +&&0rΝl?z職w}͚5SJq~~)ѾׯQo wDEEѣG?(RHClرc=_*Uʺ]ti(pX^=;wb6Jk~lٲiԣ`qqq4mڔ۷3o<^~$kܸ1K,̙3}k֬ȑ#;}5j K,L81'Nˋ ΍<ŋ̚5+-Z_~̛7իӭ[75j%KM6=<<mHQyEE[o7@pbRïeUmoSDDDҔȓ9c}P:?~ܾ v{ 0 Bt)"""iJDIsagl8`<) ul?0}_mK?MIs "O…R}BNr\=mHSyZ? )X>v!!!=ȺufP!x}džӤ@#!!F9v,@t锷c$~1p Ե{SRy C"SF9|8yD Jy[+W–-EY3w7࣏qcqþ틈H@DFs!xqc;4USzwC\ږW |Y_OT[78zx m_DDD"ʑØKQ}BTؓ{RJo֯?M3^Ogx$KDi-]0'E#klߞ:$..܏ɎD>x)DDDġ@Dv>>ƊRɯ\1?ixk׮qU"##1͉.+~KBAŗ_ RNReKvnD+k8@̰z5ԫgzjքU=X,۷%Kp!Μ9Chh(gΜ!"xxx ߳g) ʖSP! .L̙o'l`25X }gEDD)<+ׇ͛uU l6Y`;!Y7$::h_`3ɓVRJTT+RT)\spZ5Qƛˎ C`,#b Mc.<4gI búu Kժ,_*)nə%Jdd$DEEq n\IBHH?+WJ*QvmT%4xNܼ ͛OX*UlowHȢEk{"""gxw'g'W[oEj趭kϮ%8Nތy5UvrYc1-=uGr1<Ȯ]͛ټy3Fˋ RIօʕwݺ%.Ջ RrelY+5a|AQ#yh;nݺNݺuɒ% NNN̜9Zn=EMݩSRhQ<==y0aBjߊ}Y,wΒ%K0Lt҅ GB/iUznDm ;J(^bsuu%kn?4wܒZިIb@d$lj[DDDP hС :4iǖ-[?Hܒl=Qm_ە8y$1q1Z '3~_3mKJfOzO?:q`v7rxjIȟ??$$$%KаaC- h|( o߾;v,&ucūw n {v o 1iC='V=zп֬YCӦM$&DEE.n~8tK>5,, 66@`+;y2!oƼɐ<IOq? - ZH_9k_ú.{% ?3޼{1&. CFAFɞL hРr`e".\` >5kҵkW\[$.Ψq](̙wCy$'N@ݺ?gAӣG>#2dȐ̙0jKyÛ7%K ]'""-[6ztPy$ uݺujԨA,Y8qb2qDhРc:-O !gN#ܺe|y߿ߋ_)GzݴJS8sn] FB(;ZN$⭋,?׌}z?)GnەiGƘlnď~$k:c9ӧ1˗/gll< W^ 2N:Hᣏ7X*%bc! X wnX||R^(pl=L_%{nܤٯ͈5,X㜜sEnȑ#Yp!׮]cС=NN􊉡0@)'7e6C۶FȔx?ڋw2 wyWW^5YfKtt4)@lӺ5)~'pw7搔( WBڰgԩ˗k֦HQˉ'x?Hmk׎vڥK"6i۵!,20D\13 W">"oG3҇Q›\^rqvQo^2GIڗk4L3nxJXѢE6m VZYub ̻ty+RfMCZ՘1 sHm ׯ㋗z{CDDDP "Ow=Ys` QkFxL#ՙgpr-\B h\ Mo(%eŧΑrG8v# \tHX9tݛ\[jl J؞82e7~$9rڵPP版<@Drw?Ywp6[9r+>>xyaB`-u2xROXluͨRC]64zu-K>w 0P3MfΜɨQ wMfO M6Y ZN80Yš5pgȓJD)%]?nə,Yt>x=}Iwמ. MX,lNfIs~vMՋQG= 8/侙|`si{+Z#h2]i׮ ,`Ĉڵ Ǐӹsg D~Сs'vŠdÀܧD'ۙ3Pm튈<@Drrۛׯj&7ZEFTw#9&y+}B]1[|V3dPmo:&g=ou 7/o@˒-JE$yw`ڵ9UVpEwѣر#ƌ%KF7-c,z1V2DDD$!"-'''*TxA IDAT(VyS0sA2yd[x&S"{ ѣb:jU/hn . RJ|4ۈ7NFd2QfMV\Ipp0ӧOӶ_?_/`aJ»ҥƶU@y(<^~e۷aOo5v+qwqK[CCU[#_bai@ yXd.u~ҥ_~Mrׯ|:,# 0pl fl{zA^5?6o6&1g#mm\:^Ʌ2?PN=y#3Jvyϯg`/YBre.]Ki&VL←_>UTaӦM֨ |sCRR2) B͚Ƥ)""B "{pʷ;QaJ2 @o3 6"p),?Wlݞ NqA X@΄7oJ*ԫW7d6‚PM7ϘScLj7>튈&qƏKywGWOMlו+>uBCJW.Mw 4+Vb y )roC ../F{5 9͛C^ɟ#""@DDqqp=ACʕ$O@Xj 6Lmm_9|Oظq1aǏR9KQ9_e;ܠPBl` cK0ܐ7hT Obn`N^>@,Yrٽ;1k,>sN:y_iݺ5 @FCvr2Z0ԩu5ymEDD@Dq 46jE< ș09d-5Qwqyr<\<($rTN/-}J2Ynn~#KyM8vr~iկظX=Ɩ3}NVXEvкuk5kɓ2d/^l63m4f͚E׷/9ƍ1cM&9do]cG09"""idI-bo-[]vQLm` ȓ"EgΜƒ={v_nǀkeKҹJ' ~>~8=]t]֕ ;(ecLJ;(@3[̴ݛm6R2Gs-ƍȑ#vu:WW1 !Xmۦo|왰ݣm[m5="D%{&*~?..PAHȐᾧo?@ p 3-!TwsxZk5|:[l Af ^^^ۗ:0j(k"""e(-ɛoҭysn16 K?y(YIŊ1j(~>/Hg ""р`Iٲp \e ט\H@ڵ® y Uzj@E^s8N&'zT?‡'ɜq9sdܸq>|6mtgNƉ'FݻXO#L؁$C`\z?-嗍9I"""6Ry4kI&aORW|l,FޝP?gŞ)Z~m x lf1fbbbnl6VwuE-h~ Ξ5گ}y&)|mZÇnݺ8pxѢ8(,_^BC?mooX w yfcDJ7o^"##ɐ!_S䵓>?k ?7%Tw^*ǮRJn/7/?b>Yu{t3c֭7osvv/# O#~~."kÝJ +VK/""O0}_.l,YW۾^a5?T4i?_~%Ç'""0k\cltԮ]N] o 7o%KO>r岽mhcDZ"úumDEY߮lzʕԩS0컸86HDlDpqr|PZ.TrٴTZ;|0f"Y<0n8fR}V4Em9s}2k֬D=z4~~~)䯿{K1c"Ȕ)e^ +gy o "rācDecÝz#njl>5pօW<[qjMBRSUy7}(| 6( o޼tܙnݺ_xbVXA`` |(S@Ʋ`L4;׶0v,iϜ7Ϙ(/""r#FsL0uZꬂs)1m4>Zuϫ}Rc-#GpjB6Xyزe K&((.]:X?&46|8} vv6kyܹ7>1}=;f,sӓ}ZNZB3̤ݢ/ݽ*bXav ~f;Vˌ3N>)!!ҽ#]:c‡܇d2h ͛ӧ/m5R%?$O<,\]v٩ώyfa,zT#>3Wtg7PjNZطo 4h;svRDww?P:dlg6@)oSDDz "O>3*T9f.*_Ã~wMF8p]}S n3ڮ#GE8A←^u+W(ww'nRiHJ\ U<+6ED䙠"$(R8Ҷm[%KرcG]iɗ 7E/>bbhk3ɉoΠK.ɞ /f~f @8}I׌ E _yX`|yb(.ѯre붞<_]Gdl$o}yե\\0KPL&ZlɿKeڵ *Э[7nܸpX``hn65j>>t_ܺ/y"(>'y{ٲe矩v 7/0i$&휔jx҅G`N1mxx݅ x䶲d”)Sؼyui^̸q(Z(+ɖ}>XZ7^׮Ʋ-<{ʵpDQ""TRyÂ*YrAŊ0dpw~% ?˄&P}furMifnxZEQڬ YYV4_A=z6ꫯΞ=K@@o!!!t'5 ڶ*{@ llg'""tRy?A:0ƍatx1q޶w[EtSO1ϱTVcuyWև37|.ئk1~Yx;4.֘9K֏8|09rj^b\TU3(QO?' Bb<9x Ɠ e|EDsf;UBy\j,d ,_nTNJoj#5jDժUٰaǎc„ %ۅW3|dAI,6mUڲ°85֭cKDD/.6lڵ Ç?~|wLƹsT^'Mòz?Ю];5kPD &vm93a`ԩn~1dpc;2ͳuDDĮ@D$16m]!o^V ĉ.?xCQij%^ k_ 6͔)SXruE[nѩwojnq0j̛|b[9r(`tl8:R#tlݺA|$ y!e<|8e Ua֬}!!"""4XH?u|y5 NL8ՃӍбd sL6m2o`„ ر#5<Q&oϦrU F? +Oݱn*Ls螑-SNT^]Z߲e /cƌ!../P1 ]:crAd &4"4ʖ#ĉƲ.eF̙y___ Gܾ}ێ7ͧ6nQ;r~kOOWOV\͗uɔz/wMpc^l`+~zL7nׯН!RQQQkq(~yrf0|/Pl ΝF;C($yעQN}&OPesޞCSreCPP5n߾ҥKcǚ5 0yQն8s5iR¾N`T߈dQ%Fpp0e˖e׮])S&#P_N"E8giܸq ;{ Z=euִ,ْ|R_=N9 8t٘ԆSiQMnݺ֭['z3SF(X*eiScYi0m3Gl'[|m&MйsgUO£Ùw`3`өMMMi]5VB.ig˩-5-.G\ k7*=+޽={2ydea\ہ QwZ@-,C\__#ؔ.m[""b%"_? IDATvβ.\K.1j5%9G‡ɉ O\9&ORJ Ϧ5H"lo.#&Mb…d3?C-&MuK/ԩPN;;eR߻""I!X"h!!е+d kE?™34ڹw},j0\[6lp̞=J^5Gacԯ[@޼Iyл$n߆͉ |ws3b?tW 20fN:ٖ蕧_*TljZyk9eQzbi_9 @4je{Y~=[ 4jԈ&MpP3Xawra\sUR.m+ضED$Q@D^dV+_-Z@ƌдk:L`>{ú˿OkРmYް0ڷoXʓa0ԚUwoP(}!F[BBCCiܸlƌ)Re1@ڴyw?ZADYV5j$nȋsȞz ~n݊;#|%= [C[iԨQOŋ3c _Y"o|s PZ@}[&uL>3g*U*N:ś_Mp"| *%C2Ev̡W9GI89IЦ85'̙&ҦMرcmtٳgkyzr_sfnL?oԨwB @YN U8c2&k.""I""zeUF sh0~9NCp}]Йk׮ѦM zA;J͛%Ykkƺu4h\;\lYFﯻwKsSU%""I"s2懮{;xo+VZgC7j'[Zofٙ^ze@xx8:uf͚\p*aƬY8;@ǦMŻw{vh;ʝ+msFnL vt7ꥳv۴iEeҥf搫..`c/5POWfMy(9sڜCRVddrݣϝ3W*]6\r7n̍7cW{Mo BM<{; OV [b{X$Yڞ:2p%hNi_qW^VZ|u"+UI3Æ IreRJfLC$n(X/5+ ǏOo;H|}?~<⤈ưϨN3~5ŋgǎiӆy0tP3gٳgOx.^4VZWo.{o7y4nm~͟\!acn_jG|uf#GTR1gFe{m6+… ցuHaOA6lPI y?lbN*CXH*3fxҼw0ydJM(E %swخ-cMWulq*߱RJP;i&r ש[.ݺun$?*Uy,cFsݯ6M2sNDDdˉc7c,K6lԐ;̴\ҒmgN)Kt*ى=9b÷ dLߚG>’S???vAÆ meÇߟcǎ?)xM0\nvJ{[q ~\9Ask$7a?MbgNaL1\f;hƢ9=2H|>~Ky&k7b峖ggZ>Iu{{{3{lƌ!!!+V_~liP +!8z{\-zEDb."'4 3fq{{S$"\~P3N_ٶ>ͭǡ+leb@8חb})S rnܸA \0ލ[ٲYP]fh4-a0/"qp:ʖ5z+.|äISDy""q Y@m];s }p>M6[4lؐ't8 (!mCϱkx{{3sLƍk W/;s4ԩȡCf4(n>IŊ{7ԯED "b?QQ0WH<wP!=ΞcLa͛]v&W)ɏ.Тh 6D4Q JDT(1nFD^` ;w&222qL~~cYjvED^ "tOgf5{Rnn8 sHȪRLɼypww`̘1̙3w O~Oxz$K;L |QF[oqܹǫe[ڶ;Λ|*_"bz"8VPfrߛcܧNroB9r}֭9r䈝o@f)SP7di0 FlAIe8|0)Sdztn8mdQxq~5i(\-+k| g[y&([ 5j%fsuajs{Ϟ6mhٲe|[hР惈]swK_c*8;qX,ڴiA{äΝ;G 7nF<{Z5.AdLzoތy(K{93|}z֬YCme&MB 9sƜh>z4/nwH 1+fʔ·CNAp`IFڈPDgs2rHZj@۶m)^8yqx9{,4fÉ Y+0 2{wvuueȐ!(Q6m֭[)^rM,3gs@a6sG2%"DD'G͛ӬR%ҰaCÓ}y>,?"c‡ʼnu%(|ܯq-[6.^L͛]#* ?$6|OV֭^Kz""HDD^ׯðaXguW 8"c"鹪'5gd_䜨zcwO\2@7[o>hxaͧALYVVZWDIKD>Cǎ朒ǽ|Ǎ 򟎆'~7!VZjY'~~Wzm+osĉWl߸!\Di""OXE`As۷WE0VԶm[:l]qF%I7k,v[dX0O̷~ܹs`Ν(Qk>~'N{43pllru[EDy}Eԉs}v-Ԯ-[ҬY3 yDD1{lL,CY֎ׇ>s3&fSMt-K7|\ 4`e.{Uw}Bg0i*7eUH9JD9""OЦ dz+9sYlboݻ֭ݻw5*cxoĖFvi_nZ)!mC}}}ٶme+jҫW/}:yZ[̲Y̡XO""2"|}CFxй3Z///͛Gɒ% g„ +W> ]ktl\Euر.c;|}7_Dq(c7*eNe*U2'Ѱaرc۶m,Y)_ʕ0\\\(X0/uԢVZO/2vXFoͅ՗; ]Ju_^HXٛgiZm+˟6?N9ɗ/[ly,Z0|B@T̠޸]~&jQOsזȿ ZCÎ;(^8!!!9~ժK/CVٳ۽;wRtY;@ gmXAWr6YfݘOdJg.M2=.ZT/ݲvʺ ʃp}pҸje׶*Vє)YnYJrlg2ʖ/5ۮsHגN`sm H& NNNL28p^z%|||h֬/_~蹓&M"xxx'OFyǛ>!ƍnbb`:-11c版h׽a?‡ʼn2ͭ7{ G\4>2Ī&^}C}˗/'UTuRuFGàAPHѾ=c \ts!$ևM6=s ˗رc 8>˗SJ;n8ڴiCB9r$e˖k׮ <8YI+[\jaM <ӧj 週'щwRҳlOu=s)jYO/"c05t~鰇9&իi&rsx7Xp};{7)|7+gNX\B+lȓbaq]… a bL2:t`xyyO]ְX,mew1|||wy'M41RLi\zCBB  m<׬Vidž94{v]gLDTku/K x雗)V5Qu^x70,1hРDgsa|a};9ƇI[Dy)R&2-fԬY,Y*UDmfW'WUɖ:[psscԩV6i$zj+ ^!W{esY."Py Ν SL˔)aaay Νٙi;/EpEܸCbɓݾI@\dzLΝ,;s UUǪE +6[ g'geX۷/f 0)SGwaN2;s]b}x{u<-@Cxx8N)Z<y ǏC%q:DE`pY89UbClzͺlﻗ) dÇ)]4/y̳|y8r,Kl뫈HR(<siŻw#FDD`PCIQ/sv89} dٹ PK4gʕh ݈*" 0v`Fp,3k,f͚u-dTm2gLtضmǶnj?Xbl۶իʷoߎjw? 2D;kʋ+, ƍ#s2eND_B-ZSYJ'[[1n?'":܁ЎBYpzŊ0a=[gnRxܓs̟VyQ<;K^z,[3g֭[Ǒ#GhР쭷"M43&cƌˋ~;,L8t:t0?|iQL 'Ng=4|<^>_5rN6HP ӽ""x8ek>.]͡rb٣!"v' 9r$׮]PdN:@׮]O?e޼yTXnݺqM; .ﯮߟN:ѰaCV3oH:G?qc@Zd"ϑhk4?nm- o}gG׮]ə3'ܺu@ԩYV,:,0`#r%\9? LZ""wB|d˖ͰX,b1 ''''O۷oQZ5H&ѴiSŋs„ F| 777#wưaپv֔Vq;6{yF.qȓ콰(9mWu=}leF~6, Z ‡3{*|Ho| =J鉥߆e@ljo+ o4)tiر 5H(YdL$_nJN-y!mC(]Xb^^^TPϛV57tɼ==( Nz]]ڴ5usOs@DDDhŽ4^Иv|2dJRU\SF ._Lhh( -r?ɼ~}6 ^y%Z0m9Elf=\V[D_ 9Y8xٜglqolo!#Vɒ%?y8d$^{ .y>BCͽy> .|xz7@zI_D{ """vT ]z+ g(6[Wn.no7o޼^>\*Hqfͤ7ptb +W>})">;Glk LɷG⃋2W޻Y̲TSE%b~䁑#yV/OYDDHDDD\\H"Y:}4y:sp-wR^=&ݿ VB -ZMdanscRyXcyF`'rVc_}}M4jҺuk[ ʯ^ΝD ش)~}sO>p+I4%"" 9Be+Ѱ`C, SL!]t 2>}pE{ S@qO<#JޗDDDqMzڃRK 틷`烼-|a?meC yDEEů|-‡Y "b7z""" X|p1Wv6yޗq5Qrb'6mZڵkje鄅1o<<ޅ?co$ذ!dStky.|Q >,!ή u֤M@޽ˊ+RKHwb|pʕ}+""`%e@CHt4saȑ~"o vF&".3Cީx Yz5+ŋfYp(tT"SyGoc?}sBW(ֽʗ#իCjwo\yp  ;EDm """/0>G _@:tLxg:ncïZ*B+x:t…_K?s!G<g `ŽTVsI-t/ݝ]گ5NxxxhѢx!zq!$"ܰ0W.;]9|&("mD(""~/6|m1blsTfX0 +5!weժUgO^g8y2)wd#y s([GGYBV7Y+6TV $䫯36|89A۶>}>"#6D乡"""ԟPvq% |]kwO|u{T,YBCa8Ȑ17 'C<;׈3ADDD9qޟ>oL~w6PC|V3]·5{v= s怯/li:xOwL{"@DDDq|7Y{gʋd(62LxgIt !o~Xe.~~ "gؚPptA>3DÇ1o!m峖K; !ʙCBCx~VEގ<@DDDa^c8Y\3}8;9۵-͛R%qŋ{lhy(^~qZI;Dv)E\>I;'+/„w&&iӦeժUd̘M@#''vc`DžE se-y .":F3ʽݼ醧֓-[6V\Iy&KV:ED06]:2E>$45 lv(Q *Щ{G (Ӄc]> -… q7|~6 ذV5q#uj/%""""1 GV7ޏ拚s),XhZ):j?铨+UĔ)Sl &NN[d ++š5q2d~{/i툈i $|(WugÉ rmo)]iԨgϞ壏>}d̘5k>^E!~ {?=;|v鷈8@.޾/|x뚭cev zI=aÆl޼9aGD1'4i?|Œp0o!QyTY9*̩?VO`` ԬYC=7TٳCǎpxܱ2eaXࢁ""䊈`; \yPkf\rɔ)S܉.0r$\UOP<[@DDD^0SftxFR  \'NPF 6n܈0~|]-[ ŋ;"|@DDDnOzxfvL:S'Ok.Iw 4mjN.ϗ/{/"ADDDDl㉍-^y,Mbp 0O YZS'hz^s\G CøD0I0Xw|6S;5UVއ~<_|,]J* o.: f/ɓ rL;" ZKDDDa8r?ʴ*GΗsS8J>b+W9sd~ 矬ضq  @ʰh9%" """" b.䄒=m6#O^;ҢX\aVZ|7z4iMS'Ȝtyx-,"!X"""򯬆_z!4ޱ ҷ|_c۫W/6o̢Ez*#((dt4,]j>/;C:E@DDDb17;V4cQK|up$n@bdɒ9r;wҩS'&M%/^aX8}:1OOsENPDQDODDDDjU-ߗyj&<$TR`J.͝;wřwK6o?-xEC习""""qeƇg}xx_:k^{ڞ={i&,XիWiٲ%+WtDxk)R8}a7"ˏY9=Nmo>ܤp„ dʔ UV1vXt0`3ȕ_DDFO@DDD$b1,9a[x,X]Kw}|ӤIO?DHJȓ'}:}0̙gvz=>>m@DDD$Ѯ_eI:O;-SNx9Gcǎ=p6mJPP..s{ٰcǃǝ U*sC-+ """(aP콸7^y^t-ݕfE2E$3x`֬YÑ#Gغu+o߾ y7 ?=EJ["""""bXhQ=Wzt+ݍ*9d4S///M?111|WT^%J<+W`3tlV(a W_w """h-䵓t,ّi:~4lߐYfCV ? X WCt "@DDD$RfXa0 δӘw6\yؽ{7#G{۶|:/w(W.h  :"p """T9q3v`ir(3PidW_5tBbCD$@DDD䉻qy1m48ݜݨMIJI&q+:̎=)Cs>G` )ch%"Oy""c"Yyd%vOcDD>pNhR '{jxb._uT*Tgd y\ """l `L=9p%KҸPcqL˖-t(qSy&(wOg ;^iۈ?wKo޼9'N$88Ç?駟:"bG """W\aL=Mg6=pŃ:дpS䬂S?8991faРAoߞ4iD@DDD!=_̂EMy7xy'… ӢE &Nȍ70`@R-"!DDD!6.+tӬmE')|quu`ذa\t)u>ŋdwUqsAnBEF֬YiԼS'N 4>[pAp轹+CIoC""""=UމY*VO40j(zIL[`ָi\s@D@DDDDjaV5g7P  8Ç{NŅ` G"(P=nK䅥""""O/ZV=L,ψG ꫯx͓̀q8s;>6ʕ3RŤ""""O\dL$YjVv<ܬR-g5[߲:#F>}c[ΝG7no\(Wr0E@DDD$ѰznEz蹞TV:rЍ ۵kLj#7nF//(S&.l)RD_)HY{l-g_8~#+8rVܫpsO ߟ rYƅrP!pG 'E?}"""lĝ(O9R5GUBƔU۶m `| 12gNREľ@DDD$TY;'믽N՜UE2ɒ4h@nݸvsla'/ۡ"b? """lFKy3ۛL{xxЬY3NDDK,yvoGDODDDDM*TSn݊Ki?.cXCl׮]"Qͻ79p2.gߥ}컴SO=xrj2a]5KD yjܸ{R\iÃ#ϟg߾}:"8@DDDu~;`}q_Mt(`Y0c=z̧ "Oy">sYyyS0]̏ZUJkҽ{w[DFDDDD R"m\( c(PR8p]$P'N:+Rfdq«{" ;w_תe." """"ODWRղ?sݻwr% яoW@D<1CpQ1QDX,IDATYFs5*Fq(򐭝W&nG,\R'2Tn{7P$xz<<^'rpLES """ĸ89k'B` """"O̢lKRR"On\\quv'\}W:_; sדM$k WvNjX L~eXbɃ=}uƌoI:IQ1Q,<;&!eE\c?VIьEGLf8))S< .^gȐ~7""I""""v̩?'1a\ 7&zD!t0me)SP(}xOK g(Oooo;ݡ$ӓfÆ 899=֭[{ۻs"7IKPAQ)::EVqk~RNS52JFݾ Z:huuO~;KH9I9gk,>;v>y#իÇ+33G=>vnבww/QR%w;hcyig S۷ꩿPm굑$}\W>Wiz٣:{HO|v%I;^O>)s4s*͛7O<>Le)8񂂂Ru%UIEȃ}$8Cf^Npmzf¢$IOൃuع2Q ۢö*QM?Hio6% KIIRfcIWKe 4iD.]RNN֫[p8qN]6bʒVyG۴q鮻*&|_<ܤ+33S ,иqfi߾}!I7͛W^Qrr-[\wRXXO?TSNՐ!C4tP%&&jǎ*Gٖ\d)#W{٣jZ$iwKչ|brc->ڶm͛7kƌT4atwWNҜkD5no@q !Q|,-[LSN[5j4Rqq$Kv]|F5…ohРA7l kIIRoS!u}W_AEEEϵzj[NniԨ4|pVNfȗ_>߬s:O}nʔzguU\?˧$Tf/W'a~T kR=xbQV-͙3GǎӨQ<>$)$D9R 饗z&|PMW_HJ4vIeYk$ԬY~Zmڴ)SyPgϞٳgui)%%E)))1I,ګ)o7N{6VV(m|֘1l+?**6IͶB5`*..Vqq,rX1鰤_ռy:sΝ;W~֭>B)ҀAvw**SPqq-SZګjpVԮ];ĨC߯}{纋{>p>B@FQWG+%祍{Thhׯ ۬Y3uQ[. R^iӤYTs`&ȑ#%7JJfC=$.&~\P GU=IK[`Ϟyt+z{|,!!AbQQ[HRޕ>QݻԬYnFX-e\ IN>sH>+կ_vm>Q\\ITFCI$͕4GRKz^F%hÆ_ $7~ J.9CͬYo@ÅVjzjrjmÚ>}RSS;o+((bFF:xQͭ|'_}U u/Y">\6`IRllbcc5w7^ :sF/*^εVNʵ '<۰$iIK[x]F@y#;:U**L!F>!i2ϵ vv~s:-Y֕+"Rx%uٶx W@w),Ν4T3^Z" (k@w#/EF:@-͛;*܂c !0c !0c !0c !0c !0c !0c !0c !0c !0c !0c !0c !0c !0c !0c !0c !0c !0c !0c !0c ^TPPDEEEf͚ܹnn>C#Gj6l|Mח_~>Q]ٳGVRrrL"I6lڶm_~;W@dڵQ~ߺsiΝ:y{KRSSPN:I8n>EáR%eN2% ^RAAA%*(((U:ix DFFy$EEEɓ'vneC ѐ!C/5m4%&&jɒ%޽N8y{M]{L`ƙo@ӤI׿U'NTQQ6nܨ]kM1L`ƙoċ5o#Tg%狋ur0p.]F -^w}:z Qq&1U:y222:שS'^_[7&&Fv]ngQVZ WDDƏ:3TeY:ski2xõsYr8R"##BpϯD:uHQ=EEE)11Q111*..֦M[oھ}$q YbN:ٳgKb.w\;$沪TSyyy ﯼ<N``-,sq{=h EGGkƌZv%q:zƍzH#F\+kIeU `Tpp$Թ|:tR5ydvm۶U8>}Z}Q:uvZטI即0JnW(}jCGWFFF3/],EEEyønݺr1P .w޺x6oެ 1S7\jԨի{:goCT}T\\Vleff^z2ʒ~ƍժU+e0 7*==Um6?~\tu]uբE޿h"O>ꥠ@٥ʓ$Izr1p"kZf|212*2Θ˪U… uy6֯_'NH^|EժUKӧOך5k_Z'NTvvϟviԨQqiРAzǴcXBsQxxO>#|F,++K:tСCղeKI'|M6wz']m1pSjÆ ׯ233ﻝOHH$2TJEƙ`.J| "s=fl6e-:׿媗f ֭k 6:{lmVV@Eoa㠊8;5l0EVHHdkf]|6g(#Ե?vݭ.snUEsYbl @C` 1@C` 1@C` 1@C` 1@C` 1@C` 1@C` 1@C` 1@C` 1@C`hp>n/IENDB`python-beziers-0.5.0+dfsg1/setup.py000066400000000000000000000014471432301424100172100ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- from setuptools import setup, find_packages config = { 'name': 'beziers', 'author': 'Simon Cozens', 'author_email': 'simon@simon-cozens.org', 'url': 'https://github.com/simoncozens/beziers.py', 'description': 'Bezier curve manipulation library', 'long_description': open('README.rst', 'r').read(), 'license': 'MIT', 'version': '0.5.0', 'install_requires': [ 'pyclipper' ], 'classifiers': [ "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 3", "License :: OSI Approved :: MIT License", "Development Status :: 4 - Beta" ], 'packages': find_packages(), } if __name__ == '__main__': setup(**config) python-beziers-0.5.0+dfsg1/test/000077500000000000000000000000001432301424100164475ustar00rootroot00000000000000python-beziers-0.5.0+dfsg1/test/test_arclength.py000066400000000000000000000013451432301424100220320ustar00rootroot00000000000000import unittest from beziers.quadraticbezier import QuadraticBezier from beziers.cubicbezier import CubicBezier from beziers.point import Point class ArcLengthMethods(unittest.TestCase): def test_quadraticLength(self): b1 = QuadraticBezier( Point(150,40), Point(80,30), Point(105,150) ) self.assertAlmostEqual(b1.length, 144.79561403359523) b2 = QuadraticBezier( Point(707,596), Point(645,596), Point(592,623) ) self.assertAlmostEqual(b2.length, 119.25113694489232) def test_cubicLength(self): b1 = CubicBezier( Point(100,25), Point(10,90), Point(110,100), Point(132,192) ) self.assertAlmostEqual(b1.length, 202.20118972656385) python-beziers-0.5.0+dfsg1/test/test_booleanshapeoperations.py000066400000000000000000000012621432301424100246250ustar00rootroot00000000000000import unittest from beziers.path import BezierPath from beziers.path.representations.Nodelist import NodelistRepresentation, Node from beziers.point import Point from beziers.path.geometricshapes import Circle, Square class BooleanShapeOperations(unittest.TestCase): def drawIt(self, s,c,paths): import matplotlib.pyplot as plt fig, ax = plt.subplots() s.plot(ax,drawNodes=False) c.plot(ax) for p in paths: p.plot(ax,drawNodes=False,color="red") plt.show() def not_a_test_square_circle_union(self): subject = Square(10, origin=Point(5,5)) clip = Circle(10, origin=Point(15,15)) paths = subject.union(clip) self.drawIt(subject,clip,paths)python-beziers-0.5.0+dfsg1/test/test_bounds.py000066400000000000000000000015141432301424100213530ustar00rootroot00000000000000import unittest from beziers.quadraticbezier import QuadraticBezier from beziers.point import Point from beziers.boundingbox import BoundingBox class BBoxMethods(unittest.TestCase): def test_overlap(self): b1 = BoundingBox() b2 = BoundingBox() b1.extend(Point(5,5)) b1.extend(Point(10,10)) b2.extend(Point(7,7)) b2.extend(Point(14,14)) print("%s v %s" % (b1,b2)) self.assertTrue(b1.overlaps(b2)) self.assertTrue(b2.overlaps(b1)) def test_quadratic_bounds(self): # console.log((new Bezier(150,40,80,30,105,150)).bbox()) q = QuadraticBezier( Point(150,40), Point(80,30), Point(105,150)) b = q.bounds() self.assertAlmostEqual(b.bl.x,98.42105263157895) self.assertAlmostEqual(b.tr.x,150) self.assertAlmostEqual(b.bl.y,39.23076923076923) self.assertAlmostEqual(b.tr.y,150) python-beziers-0.5.0+dfsg1/test/test_corners.py000066400000000000000000000013341432301424100215340ustar00rootroot00000000000000import unittest from beziers.utils.curvefitter import CurveFit from beziers.path import BezierPath from beziers.point import Point from beziers.path.representations.Nodelist import NodelistRepresentation, Node class CornerMethods(unittest.TestCase): def test_corners(self): nl = [ Node(302.0,492.0,"line"), Node(176.0,432.0,"line"), Node(-51.0,325.0,"offcurve"), Node(-74.0,484.0,"offcurve"), Node(73.0,570.0,"curve"), Node(85.0,764.0,"offcurve"), Node(290.0,748.0,"offcurve"), Node(418.0,688.0,"curve"), ] path = BezierPath.fromNodelist(nl) path.closed = False for seg1, seg2 in path.segpairs(): print(seg1.endAngle * 57.2958, seg2.startAngle * 57.2958) python-beziers-0.5.0+dfsg1/test/test_cubic.py000066400000000000000000000036051432301424100211510ustar00rootroot00000000000000import unittest from beziers.cubicbezier import CubicBezier from beziers.point import Point from beziers.path import BezierPath class CubicMethods(unittest.TestCase): def test_extremes(self): q = CubicBezier( Point(65,59), Point(194,90), Point(220,260), Point(70,261) ) # console.log(Bezier(65,59, 194,90, 220,260, 70,261).extrema()) r = q.findExtremes() self.assertEqual(len(r), 1) self.assertAlmostEqual(r[0], 0.5275787707261016) r = q.findExtremes(inflections = True) self.assertEqual(len(r), 2) self.assertAlmostEqual(r[0], 0.4512987012987013) self.assertAlmostEqual(r[1], 0.5275787707261016) def test_length(self): q = CubicBezier( Point(120,160), Point(35,200), Point(220,260), Point(220,40) ) self.assertAlmostEqual(q.length,272.87003168) def test_align(self): q = CubicBezier( Point(120,160), Point(35,200), Point(220,260), Point(220,40) ) s = q.aligned() self.assertAlmostEqual(s[0].x,0.0) self.assertAlmostEqual(s[0].y,0.0) self.assertAlmostEqual(s[1].x,-85.14452515537582) self.assertAlmostEqual(s[1].y,-39.69143277919774) self.assertAlmostEqual(s[2].x,-12.803687993289572) self.assertAlmostEqual(s[2].y,140.84056792618557) self.assertAlmostEqual(s[3].x,156.2049935181331) self.assertAlmostEqual(s[3].y,0.0) def test_curvature(self): q = CubicBezier( Point(122,102), Point(35,200), Point(228,145), Point(190,46) ) self.assertAlmostEqual(q.curvatureAtTime(0.5),-103450.5) def test_loop(self): q = CubicBezier( Point(171,272), Point(388,249), Point(167,444), Point(388,176) ) self.assertTrue(not q.hasLoop) q = CubicBezier( Point(171,272), Point(595,249), Point(167,444), Point(388,176) ) roots = q.hasLoop p1 = q.pointAtTime(roots[0]) p2 = q.pointAtTime(roots[1]) self.assertTrue(q.hasLoop) self.assertEqual(p1,p2)python-beziers-0.5.0+dfsg1/test/test_curvefit.py000066400000000000000000000032231432301424100217070ustar00rootroot00000000000000import unittest from beziers.utils.curvefitter import CurveFit from beziers.path import BezierPath from beziers.point import Point class CurveFitterMethods(unittest.TestCase): def test_cf1(self): nodes = [ Point(122,102), Point(35,200), Point(228,145), Point(190,46) ] t = CurveFit._leftTangent(nodes) self.assertAlmostEqual(t.x, -0.663890062102) self.assertAlmostEqual(t.y,0.747830184896) t = CurveFit._rightTangent(nodes) self.assertAlmostEqual(t.x,-0.3583470773350791) self.assertAlmostEqual(t.y, -0.9335884383203376) def test_cf2(self): nodes = [ Point(100,50), Point(50,150), Point(100,220), Point(200,200), Point(250,80), Point(220,50) ] path = BezierPath.fromPoints(nodes) segs = path.asSegments() self.assertEqual(len(segs),2) self.assertEqual(segs[0].start, Point(100.0, 50.0)) self.assertAlmostEqual(segs[0][1].x, 83.333333333) self.assertAlmostEqual(segs[0][1].y, 83.333333333) self.assertEqual(segs[0].end, Point(50.0, 150.0)) self.assertAlmostEqual(segs[1][1].x, 50) self.assertEqual(segs[1].end, Point(220.0, 50.0)) def not_a_test_cf3(self): import matplotlib.pyplot as plt import math fig, ax = plt.subplots() points = [ Point(100,50), Point(50,150), Point(150,250), Point(200,220), Point(250,80), Point(220,50) ] path = BezierPath.fromPoints(points) centroid = path.bounds().centroid path.rotate(centroid, math.pi/2) path.balance() path.plot(ax) path.offset(Point(5,5)).plot(ax, color="red") path.offset(Point(-5,-5)).plot(ax, color="green") plt.show()python-beziers-0.5.0+dfsg1/test/test_distance.py000066400000000000000000000015431432301424100216550ustar00rootroot00000000000000import unittest from beziers.path.geometricshapes import Circle from beziers.point import Point from beziers.path import BezierPath def drawIt(s, c, segs): import matplotlib.pyplot as plt fig, ax = plt.subplots() s.plot(ax, drawNodes=False) c.plot(ax) for s in segs: BezierPath.fromSegments([s]).plot(ax, drawNodes=False, color="red") plt.show() class DistanceMethods(unittest.TestCase): def test_distance(self): p1 = Circle(50) p2 = Circle(50, origin=Point(200, 0)) d = p1.distanceToPath(p2) # drawIt(p1, p2, [d[3], d[4]]) self.assertAlmostEqual(d[0], 100) def test_distance2(self): p1 = Circle(50) p2 = Circle(50, origin=Point(100, 100)) d = p1.distanceToPath(p2) # drawIt(p1, p2, [d[3], d[4]]) self.assertAlmostEqual(d[0], 41.4531774254) python-beziers-0.5.0+dfsg1/test/test_intersections.py000066400000000000000000000064471432301424100227640ustar00rootroot00000000000000import unittest from beziers.cubicbezier import CubicBezier from beziers.line import Line from beziers.point import Point from beziers.path import BezierPath from beziers.path.representations.Segment import SegmentRepresentation class IntersectionMethods(unittest.TestCase): 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) 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)) # print q.intersections(l) # import matplotlib.pyplot as plt # fig, ax = plt.subplots() # path.plot(ax) # path = BezierPath() # path.closed = False # path.activeRepresentation = SegmentRepresentation(path,[l]) # path.plot(ax) # for n in q.intersections(l): # circle = plt.Circle((n.point.x, n.point.y), 1, fill=False) # ax.add_artist(circle) # plt.show() def test_cubic_cubic(self): # q1 = Bezier(10,100, 90,30, 40,140, 220,220) # q2 = Bezier(5,150, 180,20, 80,250, 210,190) # console.log(q1.intersects(q2)) q1 = CubicBezier( Point(10,100), Point(90,30), Point(40,140), Point(220,220) ) q2 = CubicBezier( Point(5,150), Point(180,20), Point(80,250), Point(210,190) ) i = q1.intersections(q2) # self.assertEqual(len(i),3) # self.assertAlmostEqual(i[0].point.x,81.7904225873) # self.assertAlmostEqual(i[0].point.y,109.899396337) # self.assertAlmostEqual(i[1].point.x,133.186831292) # self.assertAlmostEqual(i[1].point.y,167.148173322) # self.assertAlmostEqual(i[2].point.x,179.869157678) # self.assertAlmostEqual(i[2].point.y,199.661989162) import matplotlib.pyplot as plt fig, ax = plt.subplots() path = BezierPath() path.closed = False path.activeRepresentation = SegmentRepresentation(path,[q1]) path.plot(ax) path.activeRepresentation = SegmentRepresentation(path,[q2]) path.plot(ax) for n in i: circle = plt.Circle((n.point.x, n.point.y), 2, fill=True, color="red") ax.add_artist(circle) #plt.show() 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)) 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) def test_cubic_line_4(self): seg = CubicBezier.fromRepr("B<<315.0,296.0>-<404.0,296.0>-<468.0,251.0>-<468.0,183.0>>") ray = Line.fromRepr("L<<330.0000432054082,365.6789020602332>--<330,286>>") assert seg.intersections(ray) python-beziers-0.5.0+dfsg1/test/test_line.py000066400000000000000000000005231432301424100210070ustar00rootroot00000000000000import unittest from beziers.line import Line from beziers.point import Point class LineMethods(unittest.TestCase): def test_slope(self): l = Line(Point(0,10),Point(20,20.4)) self.assertAlmostEqual(l.slope, 0.52) def test_intercept(self): l = Line(Point(0,10),Point(20,20.4)) self.assertAlmostEqual(l.intercept, 10) python-beziers-0.5.0+dfsg1/test/test_offset.py000066400000000000000000000030321432301424100213440ustar00rootroot00000000000000 import unittest from beziers.path.representations.GSPath import GSPathRepresentation from beziers.path.representations.Segment import SegmentRepresentation from beziers.path.representations.Nodelist import Node from beziers.point import Point from beziers.cubicbezier import CubicBezier from beziers.line import Line from beziers.path import BezierPath from dotmap import DotMap class PathTests(unittest.TestCase): def not_a_test_offset(self): b = DotMap({ "closed": False, "nodes": [ {"x": 412.0, "y":500.0, "type":"line"}, {"x": 308.0, "y":665.0, "type":"offcurve"}, {"x": 163.0, "y":589.0, "type":"offcurve"}, {"x": 163.0, "y":504.0, "type":"curve"}, {"x": 163.0, "y":424.0, "type":"offcurve"}, {"x": 364.0, "y":321.0, "type":"offcurve"}, {"x": 366.0, "y":216.0, "type":"curve"}, {"x": 368.0, "y":94.0, "type":"offcurve"}, {"x": 260.0, "y":54.0, "type":"offcurve"}, {"x": 124.0, "y":54.0, "type":"curve"} ]}) path = BezierPath() path.activeRepresentation = GSPathRepresentation(path,b) import matplotlib.pyplot as plt fig, ax = plt.subplots() path.addExtremes() path.plot(ax) for n in path.asSegments(): p = n.tunniPoint if p: circle = plt.Circle((p.x, p.y), 1, fill=False, color="blue") ax.add_artist(circle) n.balance() path.translate(Point(5,5)) path.plot(ax, color="red") # o1 = path.offset(Point(10,10)) # o2 = path.offset(Point(-10,-10)) # o2.reverse() # o1.append(o2) # o1.plot(ax) plt.show()python-beziers-0.5.0+dfsg1/test/test_path.py000066400000000000000000000114721432301424100210210ustar00rootroot00000000000000import unittest from beziers.path.representations.GSPath import GSPathRepresentation from beziers.path.representations.Segment import SegmentRepresentation from beziers.path.representations.Nodelist import Node from beziers.point import Point from beziers.cubicbezier import CubicBezier from beziers.line import Line from beziers.path import BezierPath from dotmap import DotMap from beziers.path.geometricshapes import Rectangle class PathTests(unittest.TestCase): # def test_representations(self): # b = DotMap({ "closed": True, # "nodes": [ # {"x":385.0, "y":20.0, "type":"offcurve"}, # { "x":526.0, "y":79.0, "type":"offcurve"}, # { "x":566.0, "y":135.0, "type":"curve"}, # { "x":585.0, "y":162.0, "type":"offcurve"}, # { "x":566.0, "y":260.0, "type":"offcurve"}, # { "x":484.0, "y":281.0, "type":"curve"}, # { "x":484.0, "y":407.0, "type":"offcurve"}, # { "x":381.0, "y":510.0, "type":"offcurve"}, # { "x":255.0, "y":510.0, "type":"curve"}, # { "x":26.0, "y":281.0, "type":"line"}, # { "x":26.0, "y":155.0, "type":"offcurve"}, # { "x":129.0, "y":20.0, "type":"offcurve"}, # { "x":255.0, "y":20.0, "type":"curve"} # ]}) # path = BezierPath() # path.activeRepresentation = GSPathRepresentation(path,b) # nl = path.asNodelist() # self.assertEqual(len(nl), 13) # self.assertIsInstance(nl[1], Node) # self.assertEqual(nl[1].type,"offcurve") # self.assertAlmostEqual(nl[1].x,526.0) # segs = path.asSegments() # self.assertEqual(len(segs), 5) # self.assertIsInstance(segs[1], CubicBezier) # self.assertIsInstance(segs[2], Line) def test_addextremes(self): q = CubicBezier( Point(42,135), Point(129,242), Point(167,77), Point(65,59) ) ex = q.findExtremes() self.assertEqual(len(ex),2) path = BezierPath() path.closed = False path.activeRepresentation = SegmentRepresentation(path,[q]) path.addExtremes() path.balance() segs = path.asSegments() self.assertEqual(len(segs), 3) # import matplotlib.pyplot as plt # fig, ax = plt.subplots() # path.plot(ax) # plt.show() def test_overlap(self): nodes = [ Node(698.0,413.0,"offcurve"), Node(401.0,179.0,"offcurve"), Node(401.0,274.0,"curve"), Node(401.0,368.0,"offcurve"), Node(315.0,445.0,"offcurve"), Node(210.0,445.0,"curve"), Node(104.0,445.0,"offcurve"), Node(18.0,368.0,"offcurve"), Node(18.0,274.0,"curve"), Node(18.0,179.0,"offcurve"), Node(439.0,400.0,"offcurve"), Node(533.0,405.0,"curve") ] p = BezierPath.fromNodelist(nodes) p.closed = True i = p.getSelfIntersections() self.assertEqual(len(i),1) self.assertAlmostEqual(i[0].point.x, 377.71521068) # import matplotlib.pyplot as plt # fig, ax = plt.subplots() # p.plot(ax) # for n in i: # circle = plt.Circle((n.point.x, n.point.y), 2, fill=True, color="red") # ax.add_artist(circle) # plt.show() p = BezierPath.fromNodelist([ Node(310.0,389.0,"line"), Node(453.0,222.0,"line"), Node(289.0,251.0,"line"), Node(447.0,367.0,"line"), Node(578.0,222.0,"line"), Node(210.0,-8.0,"line"), ]) i = p.getSelfIntersections() self.assertEqual(len(i),1) self.assertEqual(i[0].point,Point(374.448829525,313.734583702)) def test_splitatpoints(self): p = BezierPath.fromNodelist([ Node(297.0,86.0,"offcurve"), Node(344.0,138.0,"offcurve"), Node(344.0,203.0,"curve"), Node(344.0,267.0,"offcurve"), Node(297.0,319.0,"offcurve"), Node(240.0,319.0,"curve"), Node(183.0,319.0,"offcurve"), Node(136.0,267.0,"offcurve"), Node(136.0,203.0,"curve"), Node(136.0,138.0,"offcurve"), Node(183.0,86.0,"offcurve"), Node(240.0,86.0,"curve"), ]) splitlist = [] for seg in p.asSegments(): for t in seg.regularSampleTValue(5): splitlist.append((seg,t)) p.splitAtPoints(splitlist) self.assertEqual(len(p.asSegments()),24) def test_inside(self): p = BezierPath.fromNodelist([ Node(329,320,"line"), Node(564,190,"line"), Node(622,332,"offcurve"), Node(495,471,"offcurve"), Node(329,471,"curve"), Node(164,471,"offcurve"), Node(34,334,"offcurve"), Node(93,190,"curve") ]) self.assertTrue(p.pointIsInside(Point(326,423))) self.assertFalse(p.pointIsInside(Point(326,123))) self.assertFalse(p.pointIsInside(Point(326,251))) self.assertTrue(p.pointIsInside(Point(526,251))) self.assertTrue(p.pointIsInside(Point(126,251))) def test_area(self): p = Rectangle(200, 100) self.assertEqual(p.area, 200 * 100) self.assertEqual(p.signed_area, -200 * 100) self.assertEqual(p.direction, -1) p.reverse() self.assertEqual(p.signed_area, 200 * 100) self.assertEqual(p.direction, 1) python-beziers-0.5.0+dfsg1/test/test_quadratic.py000066400000000000000000000032321432301424100220350ustar00rootroot00000000000000import unittest from beziers.quadraticbezier import QuadraticBezier from beziers.point import Point class QuadraticMethods(unittest.TestCase): def test_split(self): # console.log((new Bezier(150,40,80,30,105,150)).split(0.2)) q = QuadraticBezier( Point(150,40), Point(80,30), Point(105,150)) left, right = q.splitAtTime(0.2) self.assertEqual(left[1],Point(136,38)) self.assertEqual(right[1],Point(85,54)) def test_extremes(self): q = QuadraticBezier( Point(70,250), Point(13,187), Point(209,58)) r = q.findExtremes() self.assertEqual(len(r), 1) self.assertAlmostEqual(r[0], 0.22529644268774704) def test_extremes2(self): # console.log((new Bezier(127,242,71,150,210,60)).extrema()) q = QuadraticBezier( Point(127,242), Point(71,150), Point(210,60)) r = q.findExtremes() self.assertEqual(len(r), 1) self.assertAlmostEqual(r[0], 0.28717948717948716) def test_extremes3(self): # console.log((new Bezier(127,242,27,5,210,60)).extrema()) q = QuadraticBezier( Point(127,242), Point(27,5), Point(210,60)) r = q.findExtremes() self.assertEqual(len(r), 2) self.assertAlmostEqual(r[0], 0.35335689045936397) self.assertAlmostEqual(r[1], 0.8116438356164384) # from beziers.path import BezierPath # import matplotlib.pyplot as plt # path = BezierPath.fromSegments([q]) # fig, ax = plt.subplots() # path.closed = False # path.addExtremes() # path.plot(ax) # plt.show() def test_extremes4(self): q = QuadraticBezier( Point(664,1075), Point(732,1167), Point(800,1239) ) r = q.findExtremes() self.assertEqual(len(r), 0) python-beziers-0.5.0+dfsg1/test/test_transformation.py000066400000000000000000000023651432301424100231340ustar00rootroot00000000000000import unittest from beziers.point import Point from beziers.cubicbezier import CubicBezier from beziers.affinetransformation import AffineTransformation import math class AffineTransformationMethods(unittest.TestCase): def test_translate(self): p = Point(30,70) p.rotate(Point(0,0), math.pi/4) self.assertEqual(p.x, -28.284271247461906) self.assertEqual(p.y, 70.71067811865476) p = Point(30,70) m = AffineTransformation.rotation(math.pi/4) p.transform(m) self.assertAlmostEqual(p.x, -28.284271247461906) self.assertAlmostEqual(p.y, 70.71067811865476) p = Point(0,10) m = AffineTransformation.translation(Point(5,-2)) p.transform(m) self.assertEqual(p.x, 5) self.assertEqual(p.y, 8) def test_scale(self): p = Point(4,5) m = AffineTransformation.scaling(2) p.transform(m) self.assertEqual(p.x, 8) self.assertEqual(p.y, 10) p = Point(4,5) m = AffineTransformation.scaling(1.5, -2) p.transform(m) self.assertEqual(p.x, 6) self.assertEqual(p.y, -10) def test_multiple_application(self): p = Point(10,10) m = AffineTransformation() m.translate(Point(6,5)) m.scale(1.5,2) p.transform(m) self.assertEqual(p.x, 24) self.assertEqual(p.y, 30)