2 # -*- coding: ISO-8859-1 -*-
5 # Copyright (C) 2002-2004 Jörg Lehmann <joergl@users.sourceforge.net>
6 # Copyright (C) 2003-2004 Michael Schindler <m-schindler@users.sourceforge.net>
7 # Copyright (C) 2002-2004 André Wobst <wobsta@users.sourceforge.net>
9 # This file is part of PyX (http://pyx.sourceforge.net/).
11 # PyX is free software; you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License as published by
13 # the Free Software Foundation; either version 2 of the License, or
14 # (at your option) any later version.
16 # PyX is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # GNU General Public License for more details.
21 # You should have received a copy of the GNU General Public License
22 # along with PyX; if not, write to the Free Software
23 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
25 # TODO: - glue -> glue & glued
26 # - nocurrentpoint exception?
27 # - correct bbox for curveto and bpathel
28 # (maybe we still need the current bbox implementation (then maybe called
29 # cbox = control box) for bpathel for the use during the
30 # intersection of bpaths)
31 # - correct behaviour of closepath() in reversed()
33 import copy
, math
, string
, bisect
34 from math
import cos
, sin
, pi
36 from math
import radians
, degrees
38 # fallback implementation for Python 2.1 and below
39 def radians(x
): return x
*pi
/180
40 def degrees(x
): return x
*180/pi
41 import base
, bbox
, trafo
, unit
, helper
46 # fallback implementation for Python 2.2. and below
48 return reduce(lambda x
, y
: x
+y
, list, 0)
53 # fallback implementation for Python 2.2. and below
55 return zip(xrange(len(list)), list)
58 ################################################################################
59 # helper classes and routines for Bezier curves
60 ################################################################################
63 # bcurve_pt: Bezier curve segment with four control points (coordinates in pts)
68 """element of Bezier path (coordinates in pts)"""
70 def __init__(self
, x0
, y0
, x1
, y1
, x2
, y2
, x3
, y3
):
81 return "%g %g moveto %g %g %g %g %g %g curveto" % \
87 def __getitem__(self
, t
):
88 """return pathel at parameter value t (0<=t<=1)"""
89 assert 0 <= t
<= 1, "parameter t of pathel out of range [0,1]"
90 return ( unit
.t_pt(( -self
.x0
+3*self
.x1
-3*self
.x2
+self
.x3
)*t
*t
*t
+
91 ( 3*self
.x0
-6*self
.x1
+3*self
.x2
)*t
*t
+
92 (-3*self
.x0
+3*self
.x1
)*t
+
94 unit
.t_pt(( -self
.y0
+3*self
.y1
-3*self
.y2
+self
.y3
)*t
*t
*t
+
95 ( 3*self
.y0
-6*self
.y1
+3*self
.y2
)*t
*t
+
96 (-3*self
.y0
+3*self
.y1
)*t
+
103 return bbox
._bbox
(min(self
.x0
, self
.x1
, self
.x2
, self
.x3
),
104 min(self
.y0
, self
.y1
, self
.y2
, self
.y3
),
105 max(self
.x0
, self
.x1
, self
.x2
, self
.x3
),
106 max(self
.y0
, self
.y1
, self
.y2
, self
.y3
))
108 def isStraight(self
, epsilon
=1e-5):
109 """check wheter the bcurve_pt is approximately straight"""
111 # just check, whether the modulus of the difference between
112 # the length of the control polygon
113 # (i.e. |P1-P0|+|P2-P1|+|P3-P2|) and the length of the
114 # straight line between starting and ending point of the
115 # bcurve_pt (i.e. |P3-P1|) is smaller the epsilon
116 return abs(math
.sqrt((self
.x1
-self
.x0
)*(self
.x1
-self
.x0
)+
117 (self
.y1
-self
.y0
)*(self
.y1
-self
.y0
)) +
118 math
.sqrt((self
.x2
-self
.x1
)*(self
.x2
-self
.x1
)+
119 (self
.y2
-self
.y1
)*(self
.y2
-self
.y1
)) +
120 math
.sqrt((self
.x3
-self
.x2
)*(self
.x3
-self
.x2
)+
121 (self
.y3
-self
.y2
)*(self
.y3
-self
.y2
)) -
122 math
.sqrt((self
.x3
-self
.x0
)*(self
.x3
-self
.x0
)+
123 (self
.y3
-self
.y0
)*(self
.y3
-self
.y0
)))<epsilon
125 def split(self
, parameters
):
126 """return list of bcurve_pt corresponding to split at parameters"""
128 # first, we calculate the coefficients corresponding to our
129 # original bezier curve. These represent a useful starting
130 # point for the following change of the polynomial parameter
133 a1x
= 3*(-self
.x0
+self
.x1
)
134 a1y
= 3*(-self
.y0
+self
.y1
)
135 a2x
= 3*(self
.x0
-2*self
.x1
+self
.x2
)
136 a2y
= 3*(self
.y0
-2*self
.y1
+self
.y2
)
137 a3x
= -self
.x0
+3*(self
.x1
-self
.x2
)+self
.x3
138 a3y
= -self
.y0
+3*(self
.y1
-self
.y2
)+self
.y3
141 parameters
= [0] + parameters
142 if parameters
[-1]!=1:
143 parameters
= parameters
+ [1]
147 for i
in range(len(parameters
)-1):
149 dt
= parameters
[i
+1]-t1
153 # the new coefficients of the [t1,t1+dt] part of the bezier curve
154 # are then given by expanding
155 # a0 + a1*(t1+dt*u) + a2*(t1+dt*u)**2 +
156 # a3*(t1+dt*u)**3 in u, yielding
158 # a0 + a1*t1 + a2*t1**2 + a3*t1**3 +
159 # ( a1 + 2*a2 + 3*a3*t1**2 )*dt * u +
160 # ( a2 + 3*a3*t1 )*dt**2 * u**2 +
163 # from this values we obtain the new control points by inversion
165 # XXX: we could do this more efficiently by reusing for
166 # (x0, y0) the control point (x3, y3) from the previous
169 x0
= a0x
+ a1x
*t1
+ a2x
*t1
*t1
+ a3x
*t1
*t1
*t1
170 y0
= a0y
+ a1y
*t1
+ a2y
*t1
*t1
+ a3y
*t1
*t1
*t1
171 x1
= (a1x
+2*a2x
*t1
+3*a3x
*t1
*t1
)*dt
/3.0 + x0
172 y1
= (a1y
+2*a2y
*t1
+3*a3y
*t1
*t1
)*dt
/3.0 + y0
173 x2
= (a2x
+3*a3x
*t1
)*dt
*dt
/3.0 - x0
+ 2*x1
174 y2
= (a2y
+3*a3y
*t1
)*dt
*dt
/3.0 - y0
+ 2*y1
175 x3
= a3x
*dt
*dt
*dt
+ x0
- 3*x1
+ 3*x2
176 y3
= a3y
*dt
*dt
*dt
+ y0
- 3*y1
+ 3*y2
178 result
.append(bcurve_pt(x0
, y0
, x1
, y1
, x2
, y2
, x3
, y3
))
182 def MidPointSplit(self
):
183 """splits bpathel at midpoint returning bpath with two bpathels"""
185 # for efficiency reason, we do not use self.split(0.5)!
187 # first, we have to calculate the midpoints between adjacent
189 x01
= 0.5*(self
.x0
+self
.x1
)
190 y01
= 0.5*(self
.y0
+self
.y1
)
191 x12
= 0.5*(self
.x1
+self
.x2
)
192 y12
= 0.5*(self
.y1
+self
.y2
)
193 x23
= 0.5*(self
.x2
+self
.x3
)
194 y23
= 0.5*(self
.y2
+self
.y3
)
196 # In the next iterative step, we need the midpoints between 01 and 12
197 # and between 12 and 23
198 x01_12
= 0.5*(x01
+x12
)
199 y01_12
= 0.5*(y01
+y12
)
200 x12_23
= 0.5*(x12
+x23
)
201 y12_23
= 0.5*(y12
+y23
)
203 # Finally the midpoint is given by
204 xmidpoint
= 0.5*(x01_12
+x12_23
)
205 ymidpoint
= 0.5*(y01_12
+y12_23
)
207 return (bcurve_pt(self
.x0
, self
.y0
,
210 xmidpoint
, ymidpoint
),
211 bcurve_pt(xmidpoint
, ymidpoint
,
216 def arclength_pt(self
, epsilon
=1e-5):
217 """computes arclength of bpathel in pts using successive midpoint split"""
218 if self
.isStraight(epsilon
):
219 return math
.sqrt((self
.x3
-self
.x0
)*(self
.x3
-self
.x0
)+
220 (self
.y3
-self
.y0
)*(self
.y3
-self
.y0
))
222 (a
, b
) = self
.MidPointSplit()
223 return a
.arclength_pt(epsilon
) + b
.arclength_pt(epsilon
)
225 def arclength(self
, epsilon
=1e-5):
226 """computes arclength of bpathel using successive midpoint split"""
227 return unit
.t_pt(self
.arclength_pt(epsilon
))
229 def seglengths(self
, paraminterval
, epsilon
=1e-5):
230 """returns the list of segment line lengths (in pts) of the bpathel
231 together with the length of the parameterinterval"""
233 # lower and upper bounds for the arclength
235 math
.sqrt((self
.x3
-self
.x0
)*(self
.x3
-self
.x0
) + (self
.y3
-self
.y0
)*(self
.y3
-self
.y0
))
237 math
.sqrt((self
.x1
-self
.x0
)*(self
.x1
-self
.x0
) + (self
.y1
-self
.y0
)*(self
.y1
-self
.y0
)) + \
238 math
.sqrt((self
.x2
-self
.x1
)*(self
.x2
-self
.x1
) + (self
.y2
-self
.y1
)*(self
.y2
-self
.y1
)) + \
239 math
.sqrt((self
.x3
-self
.x2
)*(self
.x3
-self
.x2
) + (self
.y3
-self
.y2
)*(self
.y3
-self
.y2
))
241 # instead of isStraight method:
242 if abs(upperlen
-lowerlen
)<epsilon
:
243 return [( 0.5*(upperlen
+lowerlen
), paraminterval
)]
245 (a
, b
) = self
.MidPointSplit()
246 return a
.seglengths(0.5*paraminterval
, epsilon
) + b
.seglengths(0.5*paraminterval
, epsilon
)
248 def _lentopar(self
, lengths
, epsilon
=1e-5):
249 """computes the parameters [t] of bpathel where the given lengths (in pts) are assumed
250 returns ( [parameter], total arclength)"""
252 # create the list of accumulated lengths
253 # and the length of the parameters
254 cumlengths
= self
.seglengths(1, epsilon
)
256 parlengths
= [cumlengths
[i
][1] for i
in range(l
)]
257 cumlengths
[0] = cumlengths
[0][0]
259 cumlengths
[i
] = cumlengths
[i
][0] + cumlengths
[i
-1]
261 # create the list of parameters to be returned
263 for length
in lengths
:
264 # find the last index that is smaller than length
266 lindex
= bisect
.bisect_left(cumlengths
, length
)
267 except: # workaround for python 2.0
268 lindex
= bisect
.bisect(cumlengths
, length
)
269 while lindex
and (lindex
>= len(cumlengths
) or
270 cumlengths
[lindex
] >= length
):
273 t
= length
* 1.0 / cumlengths
[0]
278 t
= (length
- cumlengths
[lindex
]) * 1.0 / (cumlengths
[lindex
+1] - cumlengths
[lindex
])
279 t
*= parlengths
[lindex
+1]
280 for i
in range(lindex
+1):
284 return [tt
, cumlengths
[-1]]
287 # bline_pt: Bezier curve segment corresponding to straight line (coordinates in pts)
290 class bline_pt(bcurve_pt
):
292 """bcurve_pt corresponding to straight line (coordiates in pts)"""
294 def __init__(self
, x0
, y0
, x1
, y1
):
297 xb
= x0
+2.0*(x1
-x0
)/3.0
298 yb
= y0
+2.0*(y1
-y0
)/3.0
300 bcurve_pt
.__init
__(self
, x0
, y0
, xa
, ya
, xb
, yb
, x1
, y1
)
302 ################################################################################
303 # Bezier helper functions
304 ################################################################################
306 def _arctobcurve(x
, y
, r
, phi1
, phi2
):
307 """generate the best bpathel corresponding to an arc segment"""
311 if dphi
==0: return None
313 # the two endpoints should be clear
314 (x0
, y0
) = ( x
+r
*cos(phi1
), y
+r
*sin(phi1
) )
315 (x3
, y3
) = ( x
+r
*cos(phi2
), y
+r
*sin(phi2
) )
317 # optimal relative distance along tangent for second and third
319 l
= r
*4*(1-cos(dphi
/2))/(3*sin(dphi
/2))
321 (x1
, y1
) = ( x0
-l
*sin(phi1
), y0
+l
*cos(phi1
) )
322 (x2
, y2
) = ( x3
+l
*sin(phi2
), y3
-l
*cos(phi2
) )
324 return bcurve_pt(x0
, y0
, x1
, y1
, x2
, y2
, x3
, y3
)
327 def _arctobezierpath(x
, y
, r
, phi1
, phi2
, dphimax
=45):
332 dphimax
= radians(dphimax
)
335 # guarantee that phi2>phi1 ...
336 phi2
= phi2
+ (math
.floor((phi1
-phi2
)/(2*pi
))+1)*2*pi
338 # ... or remove unnecessary multiples of 2*pi
339 phi2
= phi2
- (math
.floor((phi2
-phi1
)/(2*pi
))-1)*2*pi
341 if r
==0 or phi1
-phi2
==0: return []
343 subdivisions
= abs(int((1.0*(phi1
-phi2
))/dphimax
))+1
345 dphi
=(1.0*(phi2
-phi1
))/subdivisions
347 for i
in range(subdivisions
):
348 apath
.append(_arctobcurve(x
, y
, r
, phi1
+i
*dphi
, phi1
+(i
+1)*dphi
))
353 def _bcurveIntersect(a
, a_t0
, a_t1
, b
, b_t0
, b_t1
, epsilon
=1e-5):
354 """intersect two bpathels
356 a and b are bpathels with parameter ranges [a_t0, a_t1],
357 respectively [b_t0, b_t1].
358 epsilon determines when the bpathels are assumed to be straight
362 # intersection of bboxes is a necessary criterium for intersection
363 if not a
.bbox().intersects(b
.bbox()): return ()
365 if not a
.isStraight(epsilon
):
366 (aa
, ab
) = a
.MidPointSplit()
367 a_tm
= 0.5*(a_t0
+a_t1
)
369 if not b
.isStraight(epsilon
):
370 (ba
, bb
) = b
.MidPointSplit()
371 b_tm
= 0.5*(b_t0
+b_t1
)
373 return ( _bcurveIntersect(aa
, a_t0
, a_tm
,
374 ba
, b_t0
, b_tm
, epsilon
) +
375 _bcurveIntersect(ab
, a_tm
, a_t1
,
376 ba
, b_t0
, b_tm
, epsilon
) +
377 _bcurveIntersect(aa
, a_t0
, a_tm
,
378 bb
, b_tm
, b_t1
, epsilon
) +
379 _bcurveIntersect(ab
, a_tm
, a_t1
,
380 bb
, b_tm
, b_t1
, epsilon
) )
382 return ( _bcurveIntersect(aa
, a_t0
, a_tm
,
383 b
, b_t0
, b_t1
, epsilon
) +
384 _bcurveIntersect(ab
, a_tm
, a_t1
,
385 b
, b_t0
, b_t1
, epsilon
) )
387 if not b
.isStraight(epsilon
):
388 (ba
, bb
) = b
.MidPointSplit()
389 b_tm
= 0.5*(b_t0
+b_t1
)
391 return ( _bcurveIntersect(a
, a_t0
, a_t1
,
392 ba
, b_t0
, b_tm
, epsilon
) +
393 _bcurveIntersect(a
, a_t0
, a_t1
,
394 bb
, b_tm
, b_t1
, epsilon
) )
396 # no more subdivisions of either a or b
397 # => try to intersect a and b as straight line segments
399 a_deltax
= a
.x3
- a
.x0
400 a_deltay
= a
.y3
- a
.y0
401 b_deltax
= b
.x3
- b
.x0
402 b_deltay
= b
.y3
- b
.y0
404 det
= b_deltax
*a_deltay
- b_deltay
*a_deltax
406 ba_deltax0
= b
.x0
- a
.x0
407 ba_deltay0
= b
.y0
- a
.y0
410 a_t
= ( b_deltax
*ba_deltay0
- b_deltay
*ba_deltax0
)/det
411 b_t
= ( a_deltax
*ba_deltay0
- a_deltay
*ba_deltax0
)/det
412 except ArithmeticError:
415 # check for intersections out of bound
416 if not (0<=a_t
<=1 and 0<=b_t
<=1): return ()
418 # return rescaled parameters of the intersection
419 return ( ( a_t0
+ a_t
* (a_t1
- a_t0
),
420 b_t0
+ b_t
* (b_t1
- b_t0
) ),
423 def _bcurvesIntersect(a
, a_t0
, a_t1
, b
, b_t0
, b_t1
, epsilon
=1e-5):
424 """ returns list of intersection points for list of bpathels """
433 if not bbox_a
.intersects(bbox_b
): return ()
445 return ( _bcurvesIntersect(aa
, a_t0
, a_tm
,
446 ba
, b_t0
, b_tm
, epsilon
) +
447 _bcurvesIntersect(ab
, a_tm
, a_t1
,
448 ba
, b_t0
, b_tm
, epsilon
) +
449 _bcurvesIntersect(aa
, a_t0
, a_tm
,
450 bb
, b_tm
, b_t1
, epsilon
) +
451 _bcurvesIntersect(ab
, a_tm
, a_t1
,
452 bb
, b_tm
, b_t1
, epsilon
) )
454 return ( _bcurvesIntersect(aa
, a_t0
, a_tm
,
455 b
, b_t0
, b_t1
, epsilon
) +
456 _bcurvesIntersect(ab
, a_tm
, a_t1
,
457 b
, b_t0
, b_t1
, epsilon
) )
464 return ( _bcurvesIntersect(a
, a_t0
, a_t1
,
465 ba
, b_t0
, b_tm
, epsilon
) +
466 _bcurvesIntersect(a
, a_t0
, a_t1
,
467 bb
, b_tm
, b_t1
, epsilon
) )
469 # no more subdivisions of either a or b
470 # => intersect bpathel a with bpathel b
471 assert len(a
)==len(b
)==1, "internal error"
472 return _bcurveIntersect(a
[0], a_t0
, a_t1
,
473 b
[0], b_t0
, b_t1
, epsilon
)
477 # now comes the real stuff...
480 class PathException(Exception): pass
482 ################################################################################
483 # _pathcontext: context during walk along path
484 ################################################################################
488 """context during walk along path"""
490 def __init__(self
, currentpoint
=None, currentsubpath
=None):
491 """ initialize context
493 currentpoint: position of current point
494 currentsubpath: position of first point of current subpath
498 self
.currentpoint
= currentpoint
499 self
.currentsubpath
= currentsubpath
501 ################################################################################
502 # pathel: element of a PS style path
503 ################################################################################
505 class pathel(base
.PSOp
):
507 """element of a PS style path"""
509 def _updatecontext(self
, context
):
510 """update context of during walk along pathel
512 changes context in place
516 def _bbox(self
, context
):
517 """calculate bounding box of pathel
519 context: context of pathel
521 returns bounding box of pathel (in given context)
523 Important note: all coordinates in bbox, currentpoint, and
524 currrentsubpath have to be floats (in unit.topt)
530 def _normalized(self
, context
):
531 """returns list of normalized version of pathel
533 context: context of pathel
535 returns list consisting of corresponding normalized pathels
536 normline and normcurve as well as the two pathels moveto_pt and
543 def write(self
, file):
544 """write pathel to file in the context of canvas"""
551 # Each one comes in two variants:
552 # - one which requires the coordinates to be already in pts (mainly
553 # used for internal purposes)
554 # - another which accepts arbitrary units
556 class closepath(pathel
):
558 """Connect subpath back to its starting point"""
563 def _updatecontext(self
, context
):
564 context
.currentpoint
= None
565 context
.currentsubpath
= None
567 def _bbox(self
, context
):
568 x0
, y0
= context
.currentpoint
569 x1
, y1
= context
.currentsubpath
571 return bbox
._bbox
(min(x0
, x1
), min(y0
, y1
),
572 max(x0
, x1
), max(y0
, y1
))
574 def _normalized(self
, context
):
577 def write(self
, file):
578 file.write("closepath\n")
581 class moveto_pt(pathel
):
583 """Set current point to (x, y) (coordinates in pts)"""
585 def __init__(self
, x
, y
):
590 return "%g %g moveto" % (self
.x
, self
.y
)
592 def _updatecontext(self
, context
):
593 context
.currentpoint
= self
.x
, self
.y
594 context
.currentsubpath
= self
.x
, self
.y
596 def _bbox(self
, context
):
599 def _normalized(self
, context
):
600 return [moveto_pt(self
.x
, self
.y
)]
602 def write(self
, file):
603 file.write("%g %g moveto\n" % (self
.x
, self
.y
) )
606 class lineto_pt(pathel
):
608 """Append straight line to (x, y) (coordinates in pts)"""
610 def __init__(self
, x
, y
):
615 return "%g %g lineto" % (self
.x
, self
.y
)
617 def _updatecontext(self
, context
):
618 context
.currentsubpath
= context
.currentsubpath
or context
.currentpoint
619 context
.currentpoint
= self
.x
, self
.y
621 def _bbox(self
, context
):
622 return bbox
._bbox
(min(context
.currentpoint
[0], self
.x
),
623 min(context
.currentpoint
[1], self
.y
),
624 max(context
.currentpoint
[0], self
.x
),
625 max(context
.currentpoint
[1], self
.y
))
627 def _normalized(self
, context
):
628 return [normline(context
.currentpoint
[0], context
.currentpoint
[1], self
.x
, self
.y
)]
630 def write(self
, file):
631 file.write("%g %g lineto\n" % (self
.x
, self
.y
) )
634 class curveto_pt(pathel
):
636 """Append curveto (coordinates in pts)"""
638 def __init__(self
, x1
, y1
, x2
, y2
, x3
, y3
):
647 return "%g %g %g %g %g %g curveto" % (self
.x1
, self
.y1
,
651 def _updatecontext(self
, context
):
652 context
.currentsubpath
= context
.currentsubpath
or context
.currentpoint
653 context
.currentpoint
= self
.x3
, self
.y3
655 def _bbox(self
, context
):
656 return bbox
._bbox
(min(context
.currentpoint
[0], self
.x1
, self
.x2
, self
.x3
),
657 min(context
.currentpoint
[1], self
.y1
, self
.y2
, self
.y3
),
658 max(context
.currentpoint
[0], self
.x1
, self
.x2
, self
.x3
),
659 max(context
.currentpoint
[1], self
.y1
, self
.y2
, self
.y3
))
661 def _normalized(self
, context
):
662 return [normcurve(context
.currentpoint
[0], context
.currentpoint
[1],
667 def write(self
, file):
668 file.write("%g %g %g %g %g %g curveto\n" % ( self
.x1
, self
.y1
,
673 class rmoveto_pt(pathel
):
675 """Perform relative moveto (coordinates in pts)"""
677 def __init__(self
, dx
, dy
):
681 def _updatecontext(self
, context
):
682 context
.currentpoint
= (context
.currentpoint
[0] + self
.dx
,
683 context
.currentpoint
[1] + self
.dy
)
684 context
.currentsubpath
= context
.currentpoint
686 def _bbox(self
, context
):
689 def _normalized(self
, context
):
690 x
= context
.currentpoint
[0]+self
.dx
691 y
= context
.currentpoint
[1]+self
.dy
692 return [moveto_pt(x
, y
)]
694 def write(self
, file):
695 file.write("%g %g rmoveto\n" % (self
.dx
, self
.dy
) )
698 class rlineto_pt(pathel
):
700 """Perform relative lineto (coordinates in pts)"""
702 def __init__(self
, dx
, dy
):
706 def _updatecontext(self
, context
):
707 context
.currentsubpath
= context
.currentsubpath
or context
.currentpoint
708 context
.currentpoint
= (context
.currentpoint
[0]+self
.dx
,
709 context
.currentpoint
[1]+self
.dy
)
711 def _bbox(self
, context
):
712 x
= context
.currentpoint
[0] + self
.dx
713 y
= context
.currentpoint
[1] + self
.dy
714 return bbox
._bbox
(min(context
.currentpoint
[0], x
),
715 min(context
.currentpoint
[1], y
),
716 max(context
.currentpoint
[0], x
),
717 max(context
.currentpoint
[1], y
))
719 def _normalized(self
, context
):
720 x0
= context
.currentpoint
[0]
721 y0
= context
.currentpoint
[1]
722 return [normline(x0
, y0
, x0
+self
.dx
, y0
+self
.dy
)]
724 def write(self
, file):
725 file.write("%g %g rlineto\n" % (self
.dx
, self
.dy
) )
728 class rcurveto_pt(pathel
):
730 """Append rcurveto (coordinates in pts)"""
732 def __init__(self
, dx1
, dy1
, dx2
, dy2
, dx3
, dy3
):
740 def write(self
, file):
741 file.write("%g %g %g %g %g %g rcurveto\n" % ( self
.dx1
, self
.dy1
,
743 self
.dx3
, self
.dy3
) )
745 def _updatecontext(self
, context
):
746 x3
= context
.currentpoint
[0]+self
.dx3
747 y3
= context
.currentpoint
[1]+self
.dy3
749 context
.currentsubpath
= context
.currentsubpath
or context
.currentpoint
750 context
.currentpoint
= x3
, y3
753 def _bbox(self
, context
):
754 x1
= context
.currentpoint
[0]+self
.dx1
755 y1
= context
.currentpoint
[1]+self
.dy1
756 x2
= context
.currentpoint
[0]+self
.dx2
757 y2
= context
.currentpoint
[1]+self
.dy2
758 x3
= context
.currentpoint
[0]+self
.dx3
759 y3
= context
.currentpoint
[1]+self
.dy3
760 return bbox
._bbox
(min(context
.currentpoint
[0], x1
, x2
, x3
),
761 min(context
.currentpoint
[1], y1
, y2
, y3
),
762 max(context
.currentpoint
[0], x1
, x2
, x3
),
763 max(context
.currentpoint
[1], y1
, y2
, y3
))
765 def _normalized(self
, context
):
766 x0
= context
.currentpoint
[0]
767 y0
= context
.currentpoint
[1]
768 return [normcurve(x0
, y0
, x0
+self
.dx1
, y0
+self
.dy1
, x0
+self
.dx2
, y0
+self
.dy2
, x0
+self
.dx3
, y0
+self
.dy3
)]
771 class arc_pt(pathel
):
773 """Append counterclockwise arc (coordinates in pts)"""
775 def __init__(self
, x
, y
, r
, angle1
, angle2
):
783 """Return starting point of arc segment"""
784 return (self
.x
+self
.r
*cos(radians(self
.angle1
)),
785 self
.y
+self
.r
*sin(radians(self
.angle1
)))
788 """Return end point of arc segment"""
789 return (self
.x
+self
.r
*cos(radians(self
.angle2
)),
790 self
.y
+self
.r
*sin(radians(self
.angle2
)))
792 def _updatecontext(self
, context
):
793 if context
.currentpoint
:
794 context
.currentsubpath
= context
.currentsubpath
or context
.currentpoint
796 # we assert that currentsubpath is also None
797 context
.currentsubpath
= self
._sarc
()
799 context
.currentpoint
= self
._earc
()
801 def _bbox(self
, context
):
802 phi1
= radians(self
.angle1
)
803 phi2
= radians(self
.angle2
)
805 # starting end end point of arc segment
806 sarcx
, sarcy
= self
._sarc
()
807 earcx
, earcy
= self
._earc
()
809 # Now, we have to determine the corners of the bbox for the
810 # arc segment, i.e. global maxima/mimima of cos(phi) and sin(phi)
811 # in the interval [phi1, phi2]. These can either be located
812 # on the borders of this interval or in the interior.
815 # guarantee that phi2>phi1
816 phi2
= phi2
+ (math
.floor((phi1
-phi2
)/(2*pi
))+1)*2*pi
818 # next minimum of cos(phi) looking from phi1 in counterclockwise
819 # direction: 2*pi*floor((phi1-pi)/(2*pi)) + 3*pi
821 if phi2
<(2*math
.floor((phi1
-pi
)/(2*pi
))+3)*pi
:
822 minarcx
= min(sarcx
, earcx
)
824 minarcx
= self
.x
-self
.r
826 # next minimum of sin(phi) looking from phi1 in counterclockwise
827 # direction: 2*pi*floor((phi1-3*pi/2)/(2*pi)) + 7/2*pi
829 if phi2
<(2*math
.floor((phi1
-3.0*pi
/2)/(2*pi
))+7.0/2)*pi
:
830 minarcy
= min(sarcy
, earcy
)
832 minarcy
= self
.y
-self
.r
834 # next maximum of cos(phi) looking from phi1 in counterclockwise
835 # direction: 2*pi*floor((phi1)/(2*pi))+2*pi
837 if phi2
<(2*math
.floor((phi1
)/(2*pi
))+2)*pi
:
838 maxarcx
= max(sarcx
, earcx
)
840 maxarcx
= self
.x
+self
.r
842 # next maximum of sin(phi) looking from phi1 in counterclockwise
843 # direction: 2*pi*floor((phi1-pi/2)/(2*pi)) + 1/2*pi
845 if phi2
<(2*math
.floor((phi1
-pi
/2)/(2*pi
))+5.0/2)*pi
:
846 maxarcy
= max(sarcy
, earcy
)
848 maxarcy
= self
.y
+self
.r
850 # Finally, we are able to construct the bbox for the arc segment.
851 # Note that if there is a currentpoint defined, we also
852 # have to include the straight line from this point
853 # to the first point of the arc segment
855 if context
.currentpoint
:
856 return (bbox
._bbox
(min(context
.currentpoint
[0], sarcx
),
857 min(context
.currentpoint
[1], sarcy
),
858 max(context
.currentpoint
[0], sarcx
),
859 max(context
.currentpoint
[1], sarcy
)) +
860 bbox
._bbox
(minarcx
, minarcy
, maxarcx
, maxarcy
)
863 return bbox
._bbox
(minarcx
, minarcy
, maxarcx
, maxarcy
)
865 def _normalized(self
, context
):
866 # get starting and end point of arc segment and bpath corresponding to arc
867 sarcx
, sarcy
= self
._sarc
()
868 earcx
, earcy
= self
._earc
()
869 barc
= _arctobezierpath(self
.x
, self
.y
, self
.r
, self
.angle1
, self
.angle2
)
871 # convert to list of curvetos omitting movetos
875 nbarc
.append(normcurve(bpathel
.x0
, bpathel
.y0
,
876 bpathel
.x1
, bpathel
.y1
,
877 bpathel
.x2
, bpathel
.y2
,
878 bpathel
.x3
, bpathel
.y3
))
880 # Note that if there is a currentpoint defined, we also
881 # have to include the straight line from this point
882 # to the first point of the arc segment.
883 # Otherwise, we have to add a moveto at the beginning
884 if context
.currentpoint
:
885 return [normline(context
.currentpoint
[0], context
.currentpoint
[1], sarcx
, sarcy
)] + nbarc
890 def write(self
, file):
891 file.write("%g %g %g %g %g arc\n" % ( self
.x
, self
.y
,
897 class arcn_pt(pathel
):
899 """Append clockwise arc (coordinates in pts)"""
901 def __init__(self
, x
, y
, r
, angle1
, angle2
):
909 """Return starting point of arc segment"""
910 return (self
.x
+self
.r
*cos(radians(self
.angle1
)),
911 self
.y
+self
.r
*sin(radians(self
.angle1
)))
914 """Return end point of arc segment"""
915 return (self
.x
+self
.r
*cos(radians(self
.angle2
)),
916 self
.y
+self
.r
*sin(radians(self
.angle2
)))
918 def _updatecontext(self
, context
):
919 if context
.currentpoint
:
920 context
.currentsubpath
= context
.currentsubpath
or context
.currentpoint
921 else: # we assert that currentsubpath is also None
922 context
.currentsubpath
= self
._sarc
()
924 context
.currentpoint
= self
._earc
()
926 def _bbox(self
, context
):
927 # in principle, we obtain bbox of an arcn element from
928 # the bounding box of the corrsponding arc element with
929 # angle1 and angle2 interchanged. Though, we have to be carefull
930 # with the straight line segment, which is added if currentpoint
933 # Hence, we first compute the bbox of the arc without this line:
935 a
= arc_pt(self
.x
, self
.y
, self
.r
,
940 arcbb
= a
._bbox
(_pathcontext())
942 # Then, we repeat the logic from arc.bbox, but with interchanged
943 # start and end points of the arc
945 if context
.currentpoint
:
946 return bbox
._bbox
(min(context
.currentpoint
[0], sarc
[0]),
947 min(context
.currentpoint
[1], sarc
[1]),
948 max(context
.currentpoint
[0], sarc
[0]),
949 max(context
.currentpoint
[1], sarc
[1]))+ arcbb
953 def _normalized(self
, context
):
954 # get starting and end point of arc segment and bpath corresponding to arc
955 sarcx
, sarcy
= self
._sarc
()
956 earcx
, earcy
= self
._earc
()
957 barc
= _arctobezierpath(self
.x
, self
.y
, self
.r
, self
.angle2
, self
.angle1
)
960 # convert to list of curvetos omitting movetos
964 nbarc
.append(normcurve(bpathel
.x3
, bpathel
.y3
,
965 bpathel
.x2
, bpathel
.y2
,
966 bpathel
.x1
, bpathel
.y1
,
967 bpathel
.x0
, bpathel
.y0
))
969 # Note that if there is a currentpoint defined, we also
970 # have to include the straight line from this point
971 # to the first point of the arc segment.
972 # Otherwise, we have to add a moveto at the beginning
973 if context
.currentpoint
:
974 return [normline(context
.currentpoint
[0], context
.currentpoint
[1], sarcx
, sarcy
)] + nbarc
979 def write(self
, file):
980 file.write("%g %g %g %g %g arcn\n" % ( self
.x
, self
.y
,
986 class arct_pt(pathel
):
988 """Append tangent arc (coordinates in pts)"""
990 def __init__(self
, x1
, y1
, x2
, y2
, r
):
997 def write(self
, file):
998 file.write("%g %g %g %g %g arct\n" % ( self
.x1
, self
.y1
,
1001 def _path(self
, currentpoint
, currentsubpath
):
1002 """returns new currentpoint, currentsubpath and path consisting
1003 of arc and/or line which corresponds to arct
1005 this is a helper routine for _bbox and _normalized, which both need
1006 this path. Note: we don't want to calculate the bbox from a bpath
1010 # direction and length of tangent 1
1011 dx1
= currentpoint
[0]-self
.x1
1012 dy1
= currentpoint
[1]-self
.y1
1013 l1
= math
.sqrt(dx1
*dx1
+dy1
*dy1
)
1015 # direction and length of tangent 2
1016 dx2
= self
.x2
-self
.x1
1017 dy2
= self
.y2
-self
.y1
1018 l2
= math
.sqrt(dx2
*dx2
+dy2
*dy2
)
1020 # intersection angle between two tangents
1021 alpha
= math
.acos((dx1
*dx2
+dy1
*dy2
)/(l1
*l2
))
1023 if math
.fabs(sin(alpha
))>=1e-15 and 1.0+self
.r
!=1.0:
1024 cotalpha2
= 1.0/math
.tan(alpha
/2)
1026 # two tangent points
1027 xt1
= self
.x1
+dx1
*self
.r
*cotalpha2
/l1
1028 yt1
= self
.y1
+dy1
*self
.r
*cotalpha2
/l1
1029 xt2
= self
.x1
+dx2
*self
.r
*cotalpha2
/l2
1030 yt2
= self
.y1
+dy2
*self
.r
*cotalpha2
/l2
1032 # direction of center of arc
1033 rx
= self
.x1
-0.5*(xt1
+xt2
)
1034 ry
= self
.y1
-0.5*(yt1
+yt2
)
1035 lr
= math
.sqrt(rx
*rx
+ry
*ry
)
1037 # angle around which arc is centered
1042 phi
= degrees(math
.atan(ry
/rx
))
1044 phi
= degrees(math
.atan(rx
/ry
))+180
1046 # half angular width of arc
1047 deltaphi
= 90*(1-alpha
/pi
)
1049 # center position of arc
1050 mx
= self
.x1
-rx
*self
.r
/(lr
*sin(alpha
/2))
1051 my
= self
.y1
-ry
*self
.r
/(lr
*sin(alpha
/2))
1053 # now we are in the position to construct the path
1054 p
= path(moveto_pt(*currentpoint
))
1057 p
.append(arc_pt(mx
, my
, self
.r
, phi
-deltaphi
, phi
+deltaphi
))
1059 p
.append(arcn_pt(mx
, my
, self
.r
, phi
+deltaphi
, phi
-deltaphi
))
1061 return ( (xt2
, yt2
) ,
1062 currentsubpath
or (xt2
, yt2
),
1066 # we need no arc, so just return a straight line to currentpoint to x1, y1
1067 return ( (self
.x1
, self
.y1
),
1068 currentsubpath
or (self
.x1
, self
.y1
),
1069 line_pt(currentpoint
[0], currentpoint
[1], self
.x1
, self
.y1
) )
1071 def _updatecontext(self
, context
):
1072 r
= self
._path
(context
.currentpoint
,
1073 context
.currentsubpath
)
1075 context
.currentpoint
, context
.currentsubpath
= r
[:2]
1077 def _bbox(self
, context
):
1078 return self
._path
(context
.currentpoint
,
1079 context
.currentsubpath
)[2].bbox()
1081 def _normalized(self
, context
):
1083 return normpath(self
._path
(context
.currentpoint
,
1084 context
.currentsubpath
)[2]).subpaths
[0].normpathels
1087 # now the pathels that convert from user coordinates to pts
1090 class moveto(moveto_pt
):
1092 """Set current point to (x, y)"""
1094 def __init__(self
, x
, y
):
1095 moveto_pt
.__init
__(self
, unit
.topt(x
), unit
.topt(y
))
1098 class lineto(lineto_pt
):
1100 """Append straight line to (x, y)"""
1102 def __init__(self
, x
, y
):
1103 lineto_pt
.__init
__(self
, unit
.topt(x
), unit
.topt(y
))
1106 class curveto(curveto_pt
):
1108 """Append curveto"""
1110 def __init__(self
, x1
, y1
, x2
, y2
, x3
, y3
):
1111 curveto_pt
.__init
__(self
,
1112 unit
.topt(x1
), unit
.topt(y1
),
1113 unit
.topt(x2
), unit
.topt(y2
),
1114 unit
.topt(x3
), unit
.topt(y3
))
1116 class rmoveto(rmoveto_pt
):
1118 """Perform relative moveto"""
1120 def __init__(self
, dx
, dy
):
1121 rmoveto_pt
.__init
__(self
, unit
.topt(dx
), unit
.topt(dy
))
1124 class rlineto(rlineto_pt
):
1126 """Perform relative lineto"""
1128 def __init__(self
, dx
, dy
):
1129 rlineto_pt
.__init
__(self
, unit
.topt(dx
), unit
.topt(dy
))
1132 class rcurveto(rcurveto_pt
):
1134 """Append rcurveto"""
1136 def __init__(self
, dx1
, dy1
, dx2
, dy2
, dx3
, dy3
):
1137 rcurveto_pt
.__init
__(self
,
1138 unit
.topt(dx1
), unit
.topt(dy1
),
1139 unit
.topt(dx2
), unit
.topt(dy2
),
1140 unit
.topt(dx3
), unit
.topt(dy3
))
1143 class arcn(arcn_pt
):
1145 """Append clockwise arc"""
1147 def __init__(self
, x
, y
, r
, angle1
, angle2
):
1148 arcn_pt
.__init
__(self
,
1149 unit
.topt(x
), unit
.topt(y
), unit
.topt(r
),
1155 """Append counterclockwise arc"""
1157 def __init__(self
, x
, y
, r
, angle1
, angle2
):
1158 arc_pt
.__init
__(self
, unit
.topt(x
), unit
.topt(y
), unit
.topt(r
),
1162 class arct(arct_pt
):
1164 """Append tangent arc"""
1166 def __init__(self
, x1
, y1
, x2
, y2
, r
):
1167 arct_pt
.__init
__(self
, unit
.topt(x1
), unit
.topt(y1
),
1168 unit
.topt(x2
), unit
.topt(y2
),
1172 # "combined" pathels provided for performance reasons
1175 class multilineto_pt(pathel
):
1177 """Perform multiple linetos (coordinates in pts)"""
1179 def __init__(self
, points
):
1180 self
.points
= points
1182 def _updatecontext(self
, context
):
1183 context
.currentsubpath
= context
.currentsubpath
or context
.currentpoint
1184 context
.currentpoint
= self
.points
[-1]
1186 def _bbox(self
, context
):
1187 xs
= [point
[0] for point
in self
.points
]
1188 ys
= [point
[1] for point
in self
.points
]
1189 return bbox
._bbox
(min(context
.currentpoint
[0], *xs
),
1190 min(context
.currentpoint
[1], *ys
),
1191 max(context
.currentpoint
[0], *xs
),
1192 max(context
.currentpoint
[1], *ys
))
1194 def _normalized(self
, context
):
1196 x0
, y0
= context
.currentpoint
1197 for x
, y
in self
.points
:
1198 result
.append(normline(x0
, y0
, x
, y
))
1202 def write(self
, file):
1203 for x
, y
in self
.points
:
1204 file.write("%g %g lineto\n" % (x
, y
) )
1207 class multicurveto_pt(pathel
):
1209 """Perform multiple curvetos (coordinates in pts)"""
1211 def __init__(self
, points
):
1212 self
.points
= points
1214 def _updatecontext(self
, context
):
1215 context
.currentsubpath
= context
.currentsubpath
or context
.currentpoint
1216 context
.currentpoint
= self
.points
[-1]
1218 def _bbox(self
, context
):
1219 xs
= [point
[0] for point
in self
.points
] + [point
[2] for point
in self
.points
] + [point
[2] for point
in self
.points
]
1220 ys
= [point
[1] for point
in self
.points
] + [point
[3] for point
in self
.points
] + [point
[5] for point
in self
.points
]
1221 return bbox
._bbox
(min(context
.currentpoint
[0], *xs
),
1222 min(context
.currentpoint
[1], *ys
),
1223 max(context
.currentpoint
[0], *xs
),
1224 max(context
.currentpoint
[1], *ys
))
1226 def _normalized(self
, context
):
1228 x0
, y0
= context
.currentpoint
1229 for point
in self
.points
:
1230 result
.append(normcurve(x0
, y0
, *point
))
1234 def write(self
, file):
1235 for point
in self
.points
:
1236 file.write("%g %g %g %g %g %g curveto\n" % tuple(point
))
1239 ################################################################################
1240 # path: PS style path
1241 ################################################################################
1243 class path(base
.PSCmd
):
1247 def __init__(self
, *args
):
1248 if len(args
)==1 and isinstance(args
[0], path
):
1249 self
.path
= args
[0].path
1251 self
.path
= list(args
)
1253 def __add__(self
, other
):
1254 return path(*(self
.path
+other
.path
))
1256 def __iadd__(self
, other
):
1257 self
.path
+= other
.path
1260 def __getitem__(self
, i
):
1264 return len(self
.path
)
1266 def append(self
, pathel
):
1267 self
.path
.append(pathel
)
1269 def arclength_pt(self
, epsilon
=1e-5):
1270 """returns total arc length of path in pts with accuracy epsilon"""
1271 return normpath(self
).arclength_pt(epsilon
)
1273 def arclength(self
, epsilon
=1e-5):
1274 """returns total arc length of path with accuracy epsilon"""
1275 return normpath(self
).arclength(epsilon
)
1277 def lentopar(self
, lengths
, epsilon
=1e-5):
1278 """returns (t,l) with t the parameter value(s) matching given length,
1279 l the total length"""
1280 return normpath(self
).lentopar(lengths
, epsilon
)
1283 """return coordinates in pts of corresponding normpath at parameter value t"""
1284 return normpath(self
).at_pt(t
)
1287 """return coordinates of corresponding normpath at parameter value t"""
1288 return normpath(self
).at(t
)
1291 context
= _pathcontext()
1294 for pel
in self
.path
:
1295 nbbox
= pel
._bbox
(context
)
1296 pel
._updatecontext
(context
)
1305 """return coordinates of first point of first subpath in path (in pts)"""
1306 return normpath(self
).begin_pt()
1309 """return coordinates of first point of first subpath in path"""
1310 return normpath(self
).begin()
1313 """return coordinates of last point of last subpath in path (in pts)"""
1314 return normpath(self
).end_pt()
1317 """return coordinates of last point of last subpath in path"""
1318 return normpath(self
).end()
1320 def glue(self
, other
):
1321 """return path consisting of self and other glued together"""
1322 return normpath(self
).glue(other
)
1324 # << operator also designates glueing
1327 def intersect(self
, other
, epsilon
=1e-5):
1328 """intersect normpath corresponding to self with other path"""
1329 return normpath(self
).intersect(other
, epsilon
)
1332 """return maximal value for parameter value t for corr. normpath"""
1333 return normpath(self
).range()
1336 """return reversed path"""
1337 return normpath(self
).reversed()
1339 def split(self
, parameters
):
1340 """return corresponding normpaths split at parameter value t"""
1341 return normpath(self
).split(parameters
)
1343 def tangent(self
, t
, length
=None):
1344 """return tangent vector at parameter value t of corr. normpath"""
1345 return normpath(self
).tangent(t
, length
)
1347 def transformed(self
, trafo
):
1348 """return transformed path"""
1349 return normpath(self
).transformed(trafo
)
1351 def write(self
, file):
1352 if not (isinstance(self
.path
[0], moveto_pt
) or
1353 isinstance(self
.path
[0], arc_pt
) or
1354 isinstance(self
.path
[0], arcn_pt
)):
1355 raise PathException("first path element must be either moveto, arc, or arcn")
1356 for pel
in self
.path
:
1359 ################################################################################
1360 # normpath and corresponding classes
1361 ################################################################################
1364 # normpathel: normalized element
1369 """element of a normalized sub path"""
1372 """returns coordinates of point in pts at parameter t (0<=t<=1) """
1375 def arclength_pt(self
, epsilon
=1e-5):
1376 """returns arc length of normpathel in pts with given accuracy epsilon"""
1380 """return bounding box of normpathel"""
1383 def intersect(self
, other
, epsilon
=1e-5):
1384 # XXX make this more efficient by treating special cases separately
1385 return _bcurvesIntersect([self
._bcurve
()], 0, 1, [other
._bcurve
()], 0, 1, epsilon
)
1387 def lentopar(self
, lengths
, epsilon
=1e-5):
1388 """returns tuple (t,l) with
1389 t the parameter where the arclength of normpathel is length and
1390 l the total arclength
1392 length: length (in pts) to find the parameter for
1393 epsilon: epsilon controls the accuracy for calculation of the
1394 length of the Bezier elements
1396 # Note: _lentopar returns both, parameters and total lengths
1397 # while lentopar returns only parameters
1401 """return reversed normpathel"""
1404 def split(self
, parameters
):
1405 """splits normpathel
1407 parameters: list of parameter values (0<=t<=1) at which to split
1409 returns None or list of tuple of normpathels corresponding to
1410 the orginal normpathel.
1416 def tangent(self
, t
):
1417 """returns tangent vector of _normpathel at parameter t (0<=t<=1)"""
1420 def transformed(self
, trafo
):
1421 """return transformed normpathel according to trafo"""
1424 def write(self
, file):
1425 """write normpathel (in the context of a normsubpath) to file"""
1429 # there are only two normpathels: normline and normcurve
1432 class normline(normpathel
):
1434 """Straight line from (x0, y0) to (x1, y1) (coordinates in pts)"""
1436 def __init__(self
, x0
, y0
, x1
, y1
):
1443 return "normline(%g, %g, %g, %g)" % (self
.x0
, self
.y0
, self
.x1
, self
.y1
)
1446 return bline_pt(self
.x0
, self
.y0
, self
.x1
, self
.y1
)
1448 def arclength_pt(self
, epsilon
=1e-5):
1449 return math
.sqrt((self
.x0
-self
.x1
)*(self
.x0
-self
.x1
)+(self
.y0
-self
.y1
)*(self
.y0
-self
.y1
))
1452 return (self
.x0
+(self
.x1
-self
.x0
)*t
, self
.y0
+(self
.y1
-self
.y0
)*t
)
1455 return bbox
._bbox
(min(self
.x0
, self
.x1
), min(self
.y0
, self
.y1
),
1456 max(self
.x0
, self
.x1
), max(self
.y0
, self
.y1
))
1459 return self
.x0
, self
.y0
1462 return self
.x1
, self
.y1
1464 def lentopar(self
, lengths
, epsilon
=1e-5):
1465 l
= math
.sqrt((self
.x0
-self
.x1
)*(self
.x0
-self
.x1
)+(self
.y0
-self
.y1
)*(self
.y0
-self
.y1
))
1466 return ([max(min(1.0*length
/l
,1),0) for length
in lengths
], l
)
1469 self
.x0
, self
.y0
, self
.x1
, self
.y1
= self
.x1
, self
.y1
, self
.x0
, self
.y0
1472 return normline(self
.x1
, self
.y1
, self
.x0
, self
.y0
)
1474 def split(self
, parameters
):
1475 x0
, y0
= self
.x0
, self
.y0
1476 x1
, y1
= self
.x1
, self
.y1
1481 if parameters
[0] == 0:
1483 parameters
= parameters
[1:]
1486 for t
in parameters
:
1487 xs
, ys
= x0
+ (x1
-x0
)*t
, y0
+ (y1
-y0
)*t
1488 result
.append(normline(xl
, yl
, xs
, ys
))
1491 if parameters
[-1]!=1:
1492 result
.append(normline(xs
, ys
, x1
, y1
))
1496 result
.append(normline(x0
, y0
, x1
, y1
))
1501 def tangent(self
, t
):
1502 tx
, ty
= self
.x0
+ (self
.x1
-self
.x0
)*t
, self
.y0
+ (self
.y1
-self
.y0
)*t
1503 tvectx
, tvecty
= self
.x1
-self
.x0
, self
.y1
-self
.y0
1504 # XXX should we return a normpath instead?
1505 return line_pt(tx
, ty
, tx
+tvectx
, ty
+tvecty
)
1507 def transformed(self
, trafo
):
1508 return normline(*(trafo
._apply
(self
.x0
, self
.y0
) + trafo
._apply
(self
.x1
, self
.y1
)))
1510 def write(self
, file):
1511 file.write("%g %g lineto\n" % (self
.x1
, self
.y1
))
1514 class normcurve(normpathel
):
1516 """Bezier curve with control points x0, y0, x1, y1, x2, y2, x3, y3 (coordinates in pts)"""
1518 def __init__(self
, x0
, y0
, x1
, y1
, x2
, y2
, x3
, y3
):
1529 return "normcurve(%g, %g, %g, %g, %g, %g, %g, %g)" % (self
.x0
, self
.y0
, self
.x1
, self
.y1
,
1530 self
.x2
, self
.y2
, self
.x3
, self
.y3
)
1533 xt
= ( (-self
.x0
+3*self
.x1
-3*self
.x2
+self
.x3
)*t
*t
*t
+
1534 (3*self
.x0
-6*self
.x1
+3*self
.x2
)*t
*t
+
1535 (-3*self
.x0
+3*self
.x1
)*t
+
1537 yt
= ( (-self
.y0
+3*self
.y1
-3*self
.y2
+self
.y3
)*t
*t
*t
+
1538 (3*self
.y0
-6*self
.y1
+3*self
.y2
)*t
*t
+
1539 (-3*self
.y0
+3*self
.y1
)*t
+
1544 return bcurve_pt(self
.x0
, self
.y0
, self
.x1
, self
.y1
, self
.x2
, self
.y2
, self
.x3
, self
.y3
)
1546 def arclength_pt(self
, epsilon
=1e-5):
1547 return self
._bcurve
().arclength_pt(epsilon
)
1550 return bbox
._bbox
(min(self
.x0
, self
.x1
, self
.x2
, self
.x3
),
1551 min(self
.y0
, self
.y1
, self
.y2
, self
.y3
),
1552 max(self
.x0
, self
.x1
, self
.x2
, self
.x3
),
1553 max(self
.y0
, self
.y1
, self
.y2
, self
.y3
))
1556 return self
.x0
, self
.y0
1559 return self
.x3
, self
.y3
1561 def lentopar(self
, lengths
, epsilon
=1e-5):
1562 return self
._bcurve
()._lentopar
(lengths
, epsilon
)
1565 self
.x0
, self
.y0
, self
.x1
, self
.y1
, self
.x2
, self
.y2
, self
.x3
, self
.y3
= \
1566 self
.x3
, self
.y3
, self
.x2
, self
.y2
, self
.x1
, self
.y1
, self
.x0
, self
.y0
1569 return normcurve(self
.x3
, self
.y3
, self
.x2
, self
.y2
, self
.x1
, self
.y1
, self
.x0
, self
.y0
)
1571 def split(self
, parameters
):
1574 bps
= self
._bcurve
().split(list(parameters
))
1576 if parameters
[0]==0:
1580 result
= [normcurve(self
.x0
, self
.y0
, bp0
.x1
, bp0
.y1
, bp0
.x2
, bp0
.y2
, bp0
.x3
, bp0
.y3
)]
1584 result
.append(normcurve(bp
.x0
, bp
.y0
, bp
.x1
, bp
.y1
, bp
.x2
, bp
.y2
, bp
.x3
, bp
.y3
))
1586 if parameters
[-1]==1:
1592 def tangent(self
, t
):
1593 tpx
, tpy
= self
.at_pt(t
)
1594 tvectx
= (3*( -self
.x0
+3*self
.x1
-3*self
.x2
+self
.x3
)*t
*t
+
1595 2*( 3*self
.x0
-6*self
.x1
+3*self
.x2
)*t
+
1596 (-3*self
.x0
+3*self
.x1
))
1597 tvecty
= (3*( -self
.y0
+3*self
.y1
-3*self
.y2
+self
.y3
)*t
*t
+
1598 2*( 3*self
.y0
-6*self
.y1
+3*self
.y2
)*t
+
1599 (-3*self
.y0
+3*self
.y1
))
1600 return line_pt(tpx
, tpy
, tpx
+tvectx
, tpy
+tvecty
)
1602 def transform(self
, trafo
):
1603 self
.x0
, self
.y0
= trafo
._apply
(self
.x0
, self
.y0
)
1604 self
.x1
, self
.y1
= trafo
._apply
(self
.x1
, self
.y1
)
1605 self
.x2
, self
.y2
= trafo
._apply
(self
.x2
, self
.y2
)
1606 self
.x3
, self
.y3
= trafo
._apply
(self
.x3
, self
.y3
)
1608 def transformed(self
, trafo
):
1609 return normcurve(*(trafo
._apply
(self
.x0
, self
.y0
)+
1610 trafo
._apply
(self
.x1
, self
.y1
)+
1611 trafo
._apply
(self
.x2
, self
.y2
)+
1612 trafo
._apply
(self
.x3
, self
.y3
)))
1614 def write(self
, file):
1615 file.write("%g %g %g %g %g %g curveto\n" % (self
.x1
, self
.y1
, self
.x2
, self
.y2
, self
.x3
, self
.y3
))
1618 # normpaths are made up of normsubpaths, which represent connected line segments
1623 """sub path of a normalized path
1625 A subpath consists of a list of normpathels, i.e., lines and bcurves
1626 and can either be closed or not.
1628 Some invariants, which have to be obeyed:
1629 - The last point of a normpathel and the first point of the next
1630 element have to be equal.
1631 - When the path is closed, the last normpathel has to be a
1632 normline and the last point of this normline has to be equal
1633 to the first point of the first normpathel
1637 def __init__(self
, normpathels
, closed
):
1638 self
.normpathels
= normpathels
1639 self
.closed
= closed
1642 return "subpath(%s, [%s])" % (self
.closed
and "closed" or "open",
1643 ", ".join(map(str, self
.normpathels
)))
1645 def arclength_pt(self
, epsilon
=1e-5):
1646 """returns total arc length of normsubpath in pts with accuracy epsilon"""
1647 return sum([npel
.arclength_pt(epsilon
) for npel
in self
.normpathels
])
1650 """return coordinates in pts of sub path at parameter value t
1652 Negative values of t count from the end of the path. The absolute
1653 value of t must be smaller or equal to the number of segments in
1654 the normpath, otherwise None is returned.
1659 if 0<=t
<self
.range():
1660 return self
.normpathels
[int(t
)].at_pt(t
-int(t
))
1662 return self
.end_pt()
1665 if self
.normpathels
:
1666 abbox
= self
.normpathels
[0].bbox()
1667 for anormpathel
in self
.normpathels
[1:]:
1668 abbox
+= anormpathel
.bbox()
1674 return self
.normpathels
[0].begin_pt()
1677 return self
.normpathels
[-1].end_pt()
1679 def intersect(self
, other
, epsilon
=1e-5):
1680 """intersect self with other normsubpath
1682 returns a tuple of lists consisting of the parameter values
1683 of the intersection points of the corresponding normsubpath
1686 intersections
= ([], [])
1687 # Intersect all subpaths of self with the subpaths of other
1688 for t_a
, pel_a
in enumerate(self
.normpathels
):
1689 for t_b
, pel_b
in enumerate(other
.normpathels
):
1690 for intersection
in pel_a
.intersect(pel_b
, epsilon
):
1691 # check whether an intersection occurs at the end
1692 # of a closed subpath. If yes, we don't include it
1693 # in the list of intersections to prevent a
1694 # duplication of intersection points
1695 if not ((self
.closed
and self
.range()-intersection
[0]-t_a
<epsilon
) or
1696 (other
.closed
and other
.range()-intersection
[1]-t_b
<epsilon
)):
1697 intersections
[0].append(intersection
[0]+t_a
)
1698 intersections
[1].append(intersection
[1]+t_b
)
1699 return intersections
1702 """return maximal parameter value, i.e. number of line/curve segments"""
1703 return len(self
.normpathels
)
1706 self
.normpathels
.reverse()
1707 for npel
in self
.normpathels
:
1712 for i
in range(len(self
.normpathels
)):
1713 nnormpathels
.append(self
.normpathels
[-(i
+1)].reversed())
1714 return normsubpath(nnormpathels
, self
.closed
)
1716 def split(self
, ts
):
1717 """split normsubpath at list of parameter values ts and return list
1720 Negative values of t count from the end of the sub path.
1721 After taking this rule into account, the parameter list ts has
1722 to be sorted and all parameters t have to fulfil
1723 0<=t<=self.range(). Note that each element of the resulting
1724 list is an _open_ normsubpath.
1728 for i
in range(len(ts
)):
1730 ts
[i
] += self
.range()
1731 if not (0<=ts
[i
]<=self
.range()):
1732 raise RuntimeError("parameter for split of subpath out of range")
1736 for t
, pel
in enumerate(self
.normpathels
):
1737 # determine list of splitting parameters relevant for pel
1744 # now we split the path at the filtered parameter values
1745 # This yields a list of normpathels and possibly empty
1746 # segments marked by None
1747 splitresult
= pel
.split(nts
)
1751 if splitresult
[0] is None:
1752 # mark split at the beginning of the normsubpath
1755 result
.append(normsubpath([splitresult
[0]], 0))
1757 npels
.append(splitresult
[0])
1758 result
.append(normsubpath(npels
, 0))
1759 for npel
in splitresult
[1:-1]:
1760 result
.append(normsubpath([npel
], 0))
1761 if len(splitresult
)>1 and splitresult
[-1] is not None:
1762 npels
= [splitresult
[-1]]
1772 result
.append(normsubpath(npels
, 0))
1774 # mark split at the end of the normsubpath
1777 # glue last and first segment together if the normsubpath was originally closed
1779 if result
[0] is None:
1781 elif result
[-1] is None:
1782 result
= result
[:-1]
1784 result
[-1].normpathels
.extend(result
[0].normpathels
)
1788 def tangent(self
, t
):
1791 if 0<=t
<self
.range():
1792 return self
.normpathels
[int(t
)].tangent(t
-int(t
))
1794 return self
.normpathels
[-1].tangent(1)
1796 def transform(self
, trafo
):
1797 """transform sub path according to trafo"""
1798 for pel
in self
.normpathels
:
1799 pel
.transform(trafo
)
1801 def transformed(self
, trafo
):
1802 """return sub path transformed according to trafo"""
1804 for pel
in self
.normpathels
:
1805 nnormpathels
.append(pel
.transformed(trafo
))
1806 return normsubpath(nnormpathels
, self
.closed
)
1808 def write(self
, file):
1809 # if the normsubpath is closed, we must not output the last normpathel
1811 normpathels
= self
.normpathels
[:-1]
1813 normpathels
= self
.normpathels
1815 file.write("%g %g moveto\n" % self
.begin_pt())
1816 for anormpathel
in normpathels
:
1817 anormpathel
.write(file)
1819 file.write("closepath\n")
1822 # the normpath class
1825 class normpath(path
):
1829 a normalized path consits of a list of normsubpaths
1833 def __init__(self
, *args
):
1834 if len(args
)==1 and isinstance(args
[0], normpath
):
1835 self
.subpaths
= copy
.copy(args
[0].subpaths
)
1837 elif len(args
)==1 and isinstance(args
[0], path
):
1838 pathels
= args
[0].path
1842 # split path in sub paths
1844 currentsubpathels
= []
1845 context
= _pathcontext()
1847 for npel
in pel
._normalized
(context
):
1848 if isinstance(npel
, moveto_pt
):
1849 if currentsubpathels
:
1850 # append open sub path
1851 self
.subpaths
.append(normsubpath(currentsubpathels
, 0))
1852 # start new sub path
1853 currentsubpathels
= []
1854 elif isinstance(npel
, closepath
):
1855 if currentsubpathels
:
1856 # append closed sub path
1857 currentsubpathels
.append(normline(context
.currentpoint
[0], context
.currentpoint
[1],
1858 context
.currentsubpath
[0], context
.currentsubpath
[1]))
1859 self
.subpaths
.append(normsubpath(currentsubpathels
, 1))
1860 currentsubpathels
= []
1862 currentsubpathels
.append(npel
)
1863 pel
._updatecontext
(context
)
1865 if currentsubpathels
:
1866 # append open sub path
1867 self
.subpaths
.append(normsubpath(currentsubpathels
, 0))
1869 def __add__(self
, other
):
1870 result
= normpath(other
)
1871 result
.subpaths
= self
.subpaths
+ result
.subpaths
1874 def __iadd__(self
, other
):
1875 self
.subpaths
+= normpath(other
).subpaths
1880 return len(self
.subpaths
)
1883 return "normpath(%s)" % ", ".join(map(str, self
.subpaths
))
1885 def _findsubpath(self
, t
):
1886 """return a tuple (subpath, relativet),
1887 where subpath is the subpath containing the parameter value t and t is the
1888 renormalized value of t in this subpath
1890 Negative values of t count from the end of the path. At
1891 discontinuities in the path, the limit from below is returned.
1892 None is returned, if the parameter t is out of range.
1899 for sp
in self
.subpaths
:
1900 sprange
= sp
.range()
1901 if spt
<= t
<= sprange
+spt
:
1906 def append(self
, pathel
):
1907 # XXX factor parts of this code out
1908 if self
.subpaths
[-1].closed
:
1909 context
= _pathcontext(self
.end_pt(), None)
1910 currensubpathels
= []
1912 context
= _pathcontext(self
.end_pt(), self
.subpaths
[-1].begin_pt())
1913 currentsubpathels
= self
.subpaths
[-1].normpathels
1914 self
.subpaths
= self
.subpaths
[:-1]
1915 for npel
in pathel
._normalized
(context
):
1916 if isinstance(npel
, moveto_pt
):
1917 if currentsubpathels
:
1918 # append open sub path
1919 self
.subpaths
.append(normsubpath(currentsubpathels
, 0))
1920 # start new sub path
1921 currentsubpathels
= []
1922 elif isinstance(npel
, closepath
):
1923 if currentsubpathels
:
1924 # append closed sub path
1925 currentsubpathels
.append(normline(context
.currentpoint
[0], context
.currentpoint
[1],
1926 context
.currentsubpath
[0], context
.currentsubpath
[1]))
1927 self
.subpaths
.append(normsubpath(currentsubpathels
, 1))
1928 currentsubpathels
= []
1930 currentsubpathels
.append(npel
)
1932 if currentsubpathels
:
1933 # append open sub path
1934 self
.subpaths
.append(normsubpath(currentsubpathels
, 0))
1936 def arclength_pt(self
, epsilon
=1e-5):
1937 """returns total arc length of normpath in pts with accuracy epsilon"""
1938 return sum([sp
.arclength_pt(epsilon
) for sp
in self
.subpaths
])
1940 def arclength(self
, epsilon
=1e-5):
1941 """returns total arc length of normpath with accuracy epsilon"""
1942 return unit
.t_pt(self
.arclength_pt(epsilon
))
1945 """return coordinates in pts of path at parameter value t
1947 Negative values of t count from the end of the path. The absolute
1948 value of t must be smaller or equal to the number of segments in
1949 the normpath, otherwise None is returned.
1950 At discontinuities in the path, the limit from below is returned
1953 result
= self
._findsubpath
(t
)
1955 return result
[0].at_pt(result
[1])
1960 """return coordinates of path at parameter value t
1962 Negative values of t count from the end of the path. The absolute
1963 value of t must be smaller or equal to the number of segments in
1964 the normpath, otherwise None is returned.
1965 At discontinuities in the path, the limit from below is returned
1968 result
= self
.at_pt(t
)
1970 return unit
.t_pt(result
[0]), unit
.t_pt(result
[1])
1976 for sp
in self
.subpaths
:
1985 """return coordinates of first point of first subpath in path (in pts)"""
1987 return self
.subpaths
[0].begin_pt()
1992 """return coordinates of first point of first subpath in path"""
1993 result
= self
.begin_pt()
1995 return unit
.t_pt(result
[0]), unit
.t_pt(result
[1])
2000 """return coordinates of last point of last subpath in path (in pts)"""
2002 return self
.subpaths
[-1].end_pt()
2007 """return coordinates of last point of last subpath in path"""
2008 result
= self
.end_pt()
2010 return unit
.t_pt(result
[0]), unit
.t_pt(result
[1])
2014 def glue(self
, other
):
2015 if not self
.subpaths
:
2016 raise PathException("cannot glue to end of empty path")
2017 if self
.subpaths
[-1].closed
:
2018 raise PathException("cannot glue to end of closed sub path")
2019 other
= normpath(other
)
2020 if not other
.subpaths
:
2021 raise PathException("cannot glue empty path")
2023 self
.subpaths
[-1].normpathels
+= other
.subpaths
[0].normpathels
2024 self
.subpaths
+= other
.subpaths
[1:]
2027 def intersect(self
, other
, epsilon
=1e-5):
2028 """intersect self with other path
2030 returns a tuple of lists consisting of the parameter values
2031 of the intersection points of the corresponding normpath
2034 if not isinstance(other
, normpath
):
2035 other
= normpath(other
)
2037 # here we build up the result
2038 intersections
= ([], [])
2040 # Intersect all subpaths of self with the subpaths of
2041 # other. Here, st_a, st_b are the parameter values
2042 # corresponding to the first point of the subpaths sp_a and
2043 # sp_b, respectively.
2045 for sp_a
in self
.subpaths
:
2047 for sp_b
in other
.subpaths
:
2048 for intersection
in zip(*sp_a
.intersect(sp_b
, epsilon
)):
2049 intersections
[0].append(intersection
[0]+st_a
)
2050 intersections
[1].append(intersection
[1]+st_b
)
2051 st_b
+= sp_b
.range()
2052 st_a
+= sp_a
.range()
2053 return intersections
2055 def lentopar(self
, lengths
, epsilon
=1e-5):
2056 # XXX TODO for Michael
2057 """returns [t,l] with t the parameter value(s) matching given length(s)
2058 and l the total length"""
2060 context
= _pathcontext()
2061 l
= len(helper
.ensuresequence(lengths
))
2063 # split the list of lengths apart for positive and negative values
2065 rests
= [[],[]] # first the positive then the negative lengths
2066 retrafo
= [] # for resorting the rests into lengths
2067 for length
in helper
.ensuresequence(lengths
):
2068 length
= unit
.topt(length
)
2070 rests
[0].append(length
)
2071 retrafo
.append( [0, len(rests
[0])-1] )
2074 rests
[1].append(-length
)
2075 retrafo
.append( [1, len(rests
[1])-1] )
2078 # go through the positive lengths
2079 for pel
in self
.path
:
2080 # we need arclength for knowing when all the parameters are done
2081 pars
, arclength
= pel
._lentopar
(context
, rests
[0], epsilon
)
2083 for i
in range(len(rests
[0])):
2085 rests
[0][i
] -= arclength
2086 if rests
[0][i
]<0: finis
+= 1
2087 if finis
==len(rests
[0]): break
2088 pel
._updatecontext
(context
)
2090 # go through the negative lengths
2091 for pel
in self
.reversed().path
:
2092 pars
, arclength
= pel
._lentopar
(context
, rests
[1], epsilon
)
2094 for i
in range(len(rests
[1])):
2096 rests
[1][i
] -= arclength
2097 if rests
[1][i
]<0: finis
+= 1
2098 if finis
==len(rests
[1]): break
2099 pel
._updatecontext
(context
)
2101 # re-sort the positive and negative values into one list
2102 tt
= [ t
[p
[0]][p
[1]] for p
in retrafo
]
2103 if not helper
.issequence(lengths
): tt
= tt
[0]
2108 """return maximal value for parameter value t"""
2109 return sum([sp
.range() for sp
in self
.subpaths
])
2113 self
.subpaths
.reverse()
2114 for sp
in self
.subpaths
:
2118 """return reversed path"""
2119 nnormpath
= normpath()
2120 for i
in range(len(self
.subpaths
)):
2121 nnormpath
.subpaths
.append(self
.subpaths
[-(i
+1)].reversed())
2124 def split(self
, parameters
):
2125 """split path at parameter values parameters
2127 Note that the parameter list has to be sorted.
2131 # XXX support negative arguments
2132 # XXX None at the end of last subpath is not handled correctly
2134 # check whether parameter list is really sorted
2135 sortedparams
= list(parameters
)
2137 if sortedparams
!=list(parameters
):
2138 raise ValueError("split parameters have to be sorted")
2140 # we build up this list of normpaths
2143 # the currently built up normpath
2149 for subpath
in self
.subpaths
:
2150 tf
= t0
+subpath
.range()
2151 if parameters
and t0
< parameters
[0]:
2152 if tf
< parameters
[0]:
2153 np
.subpaths
.append(subpath
)
2156 # we have to split this subpath
2158 # first we determine the relevant splitting
2160 for i
in range(len(parameters
)):
2161 if parameters
[i
]>tf
: break
2165 for sp
in subpath
.split([x
-t0
for x
in parameters
[:i
]]):
2173 np
.subpaths
.append(sp
)
2178 parameters
= parameters
[i
:]
2180 np
.subpaths
.append(subpath
)
2185 # mark split at the end of the normsubpath
2186 #result.append(None)
2191 def tangent(self
, t
, length
=None):
2192 """return tangent vector of path at parameter value t
2194 Negative values of t count from the end of the path. The absolute
2195 value of t must be smaller or equal to the number of segments in
2196 the normpath, otherwise None is returned.
2197 At discontinuities in the path, the limit from below is returned
2199 if length is not None, the tangent vector will be scaled to
2203 result
= self
._findsubpath
(t
)
2205 tvec
= result
[0].tangent(result
[1])
2206 tlen
= tvec
.arclength_pt()
2207 if length
is None or tlen
==0:
2210 sfactor
= unit
.topt(length
)/tlen
2211 return tvec
.transformed(trafo
.scale(sfactor
, sfactor
, *tvec
.begin()))
2215 def transform(self
, trafo
):
2216 """transform path according to trafo"""
2217 for sp
in self
.subpaths
:
2220 def transformed(self
, trafo
):
2221 """return path transformed according to trafo"""
2222 nnormpath
= normpath()
2223 for sp
in self
.subpaths
:
2224 nnormpath
.subpaths
.append(sp
.transformed(trafo
))
2227 def write(self
, file):
2228 for sp
in self
.subpaths
:
2231 ################################################################################
2232 # some special kinds of path, again in two variants
2233 ################################################################################
2235 class line_pt(path
):
2237 """straight line from (x1, y1) to (x2, y2) (coordinates in pts)"""
2239 def __init__(self
, x1
, y1
, x2
, y2
):
2240 path
.__init
__(self
, moveto_pt(x1
, y1
), lineto_pt(x2
, y2
))
2243 class curve_pt(path
):
2245 """Bezier curve with control points (x0, y1),..., (x3, y3)
2246 (coordinates in pts)"""
2248 def __init__(self
, x0
, y0
, x1
, y1
, x2
, y2
, x3
, y3
):
2251 curveto_pt(x1
, y1
, x2
, y2
, x3
, y3
))
2254 class rect_pt(path
):
2256 """rectangle at position (x,y) with width and height (coordinates in pts)"""
2258 def __init__(self
, x
, y
, width
, height
):
2259 path
.__init
__(self
, moveto_pt(x
, y
),
2260 lineto_pt(x
+width
, y
),
2261 lineto_pt(x
+width
, y
+height
),
2262 lineto_pt(x
, y
+height
),
2266 class circle_pt(path
):
2268 """circle with center (x,y) and radius"""
2270 def __init__(self
, x
, y
, radius
):
2271 path
.__init
__(self
, arc_pt(x
, y
, radius
, 0, 360),
2275 class line(line_pt
):
2277 """straight line from (x1, y1) to (x2, y2)"""
2279 def __init__(self
, x1
, y1
, x2
, y2
):
2280 line_pt
.__init
__(self
,
2281 unit
.topt(x1
), unit
.topt(y1
),
2282 unit
.topt(x2
), unit
.topt(y2
)
2286 class curve(curve_pt
):
2288 """Bezier curve with control points (x0, y1),..., (x3, y3)"""
2290 def __init__(self
, x0
, y0
, x1
, y1
, x2
, y2
, x3
, y3
):
2291 curve_pt
.__init
__(self
,
2292 unit
.topt(x0
), unit
.topt(y0
),
2293 unit
.topt(x1
), unit
.topt(y1
),
2294 unit
.topt(x2
), unit
.topt(y2
),
2295 unit
.topt(x3
), unit
.topt(y3
)
2299 class rect(rect_pt
):
2301 """rectangle at position (x,y) with width and height"""
2303 def __init__(self
, x
, y
, width
, height
):
2304 rect_pt
.__init
__(self
,
2305 unit
.topt(x
), unit
.topt(y
),
2306 unit
.topt(width
), unit
.topt(height
))
2309 class circle(circle_pt
):
2311 """circle with center (x,y) and radius"""
2313 def __init__(self
, x
, y
, radius
):
2314 circle_pt
.__init
__(self
,
2315 unit
.topt(x
), unit
.topt(y
),