1 #rrrrrrr!/usr/bin/env python
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)
57 ################################################################################
58 # Bezier helper functions
59 ################################################################################
61 def _arctobcurve(x
, y
, r
, phi1
, phi2
):
62 """generate the best bpathel corresponding to an arc segment"""
66 if dphi
==0: return None
68 # the two endpoints should be clear
69 (x0
, y0
) = ( x
+r
*cos(phi1
), y
+r
*sin(phi1
) )
70 (x3
, y3
) = ( x
+r
*cos(phi2
), y
+r
*sin(phi2
) )
72 # optimal relative distance along tangent for second and third
74 l
= r
*4*(1-cos(dphi
/2))/(3*sin(dphi
/2))
76 (x1
, y1
) = ( x0
-l
*sin(phi1
), y0
+l
*cos(phi1
) )
77 (x2
, y2
) = ( x3
+l
*sin(phi2
), y3
-l
*cos(phi2
) )
79 return normcurve(x0
, y0
, x1
, y1
, x2
, y2
, x3
, y3
)
82 def _arctobezierpath(x
, y
, r
, phi1
, phi2
, dphimax
=45):
87 dphimax
= radians(dphimax
)
90 # guarantee that phi2>phi1 ...
91 phi2
= phi2
+ (math
.floor((phi1
-phi2
)/(2*pi
))+1)*2*pi
93 # ... or remove unnecessary multiples of 2*pi
94 phi2
= phi2
- (math
.floor((phi2
-phi1
)/(2*pi
))-1)*2*pi
96 if r
==0 or phi1
-phi2
==0: return []
98 subdivisions
= abs(int((1.0*(phi1
-phi2
))/dphimax
))+1
100 dphi
=(1.0*(phi2
-phi1
))/subdivisions
102 for i
in range(subdivisions
):
103 apath
.append(_arctobcurve(x
, y
, r
, phi1
+i
*dphi
, phi1
+(i
+1)*dphi
))
108 def _bcurveIntersect(a
, a_t0
, a_t1
, b
, b_t0
, b_t1
, epsilon
=1e-5):
109 """intersect two bpathels
111 a and b are bpathels with parameter ranges [a_t0, a_t1],
112 respectively [b_t0, b_t1].
113 epsilon determines when the bpathels are assumed to be straight
117 # intersection of bboxes is a necessary criterium for intersection
118 if not a
.bbox().intersects(b
.bbox()): return ()
120 if not a
.isstraight(epsilon
):
121 (aa
, ab
) = a
.midpointsplit()
122 a_tm
= 0.5*(a_t0
+a_t1
)
124 if not b
.isstraight(epsilon
):
125 (ba
, bb
) = b
.midpointsplit()
126 b_tm
= 0.5*(b_t0
+b_t1
)
128 return ( _bcurveIntersect(aa
, a_t0
, a_tm
,
129 ba
, b_t0
, b_tm
, epsilon
) +
130 _bcurveIntersect(ab
, a_tm
, a_t1
,
131 ba
, b_t0
, b_tm
, epsilon
) +
132 _bcurveIntersect(aa
, a_t0
, a_tm
,
133 bb
, b_tm
, b_t1
, epsilon
) +
134 _bcurveIntersect(ab
, a_tm
, a_t1
,
135 bb
, b_tm
, b_t1
, epsilon
) )
137 return ( _bcurveIntersect(aa
, a_t0
, a_tm
,
138 b
, b_t0
, b_t1
, epsilon
) +
139 _bcurveIntersect(ab
, a_tm
, a_t1
,
140 b
, b_t0
, b_t1
, epsilon
) )
142 if not b
.isstraight(epsilon
):
143 (ba
, bb
) = b
.midpointsplit()
144 b_tm
= 0.5*(b_t0
+b_t1
)
146 return ( _bcurveIntersect(a
, a_t0
, a_t1
,
147 ba
, b_t0
, b_tm
, epsilon
) +
148 _bcurveIntersect(a
, a_t0
, a_t1
,
149 bb
, b_tm
, b_t1
, epsilon
) )
151 # no more subdivisions of either a or b
152 # => try to intersect a and b as straight line segments
154 a_deltax
= a
.x3
- a
.x0
155 a_deltay
= a
.y3
- a
.y0
156 b_deltax
= b
.x3
- b
.x0
157 b_deltay
= b
.y3
- b
.y0
159 det
= b_deltax
*a_deltay
- b_deltay
*a_deltax
161 ba_deltax0
= b
.x0
- a
.x0
162 ba_deltay0
= b
.y0
- a
.y0
165 a_t
= ( b_deltax
*ba_deltay0
- b_deltay
*ba_deltax0
)/det
166 b_t
= ( a_deltax
*ba_deltay0
- a_deltay
*ba_deltax0
)/det
167 except ArithmeticError:
170 # check for intersections out of bound
171 if not (0<=a_t
<=1 and 0<=b_t
<=1): return ()
173 # return rescaled parameters of the intersection
174 return ( ( a_t0
+ a_t
* (a_t1
- a_t0
),
175 b_t0
+ b_t
* (b_t1
- b_t0
) ),
178 def _bcurvesIntersect(a
, a_t0
, a_t1
, b
, b_t0
, b_t1
, epsilon
=1e-5):
179 """ returns list of intersection points for list of bpathels """
188 if not bbox_a
.intersects(bbox_b
): return ()
200 return ( _bcurvesIntersect(aa
, a_t0
, a_tm
,
201 ba
, b_t0
, b_tm
, epsilon
) +
202 _bcurvesIntersect(ab
, a_tm
, a_t1
,
203 ba
, b_t0
, b_tm
, epsilon
) +
204 _bcurvesIntersect(aa
, a_t0
, a_tm
,
205 bb
, b_tm
, b_t1
, epsilon
) +
206 _bcurvesIntersect(ab
, a_tm
, a_t1
,
207 bb
, b_tm
, b_t1
, epsilon
) )
209 return ( _bcurvesIntersect(aa
, a_t0
, a_tm
,
210 b
, b_t0
, b_t1
, epsilon
) +
211 _bcurvesIntersect(ab
, a_tm
, a_t1
,
212 b
, b_t0
, b_t1
, epsilon
) )
219 return ( _bcurvesIntersect(a
, a_t0
, a_t1
,
220 ba
, b_t0
, b_tm
, epsilon
) +
221 _bcurvesIntersect(a
, a_t0
, a_t1
,
222 bb
, b_tm
, b_t1
, epsilon
) )
224 # no more subdivisions of either a or b
225 # => intersect bpathel a with bpathel b
226 assert len(a
)==len(b
)==1, "internal error"
227 return _bcurveIntersect(a
[0], a_t0
, a_t1
,
228 b
[0], b_t0
, b_t1
, epsilon
)
232 # now comes the real stuff...
235 class PathException(Exception): pass
237 ################################################################################
238 # _pathcontext: context during walk along path
239 ################################################################################
243 """context during walk along path"""
245 def __init__(self
, currentpoint
=None, currentsubpath
=None):
246 """ initialize context
248 currentpoint: position of current point
249 currentsubpath: position of first point of current subpath
253 self
.currentpoint
= currentpoint
254 self
.currentsubpath
= currentsubpath
256 ################################################################################
257 # pathel: element of a PS style path
258 ################################################################################
260 class pathel(base
.PSOp
):
262 """element of a PS style path"""
264 def _updatecontext(self
, context
):
265 """update context of during walk along pathel
267 changes context in place
271 def _bbox(self
, context
):
272 """calculate bounding box of pathel
274 context: context of pathel
276 returns bounding box of pathel (in given context)
278 Important note: all coordinates in bbox, currentpoint, and
279 currrentsubpath have to be floats (in unit.topt)
285 def _normalized(self
, context
):
286 """returns list of normalized version of pathel
288 context: context of pathel
290 returns list consisting of corresponding normalized pathels
291 normline and normcurve as well as the two pathels moveto_pt and
298 def outputPS(self
, file):
299 """write pathel to file in the context of canvas"""
308 # Each one comes in two variants:
309 # - one which requires the coordinates to be already in pts (mainly
310 # used for internal purposes)
311 # - another which accepts arbitrary units
313 class closepath(pathel
):
315 """Connect subpath back to its starting point"""
320 def _updatecontext(self
, context
):
321 context
.currentpoint
= None
322 context
.currentsubpath
= None
324 def _bbox(self
, context
):
325 x0
, y0
= context
.currentpoint
326 x1
, y1
= context
.currentsubpath
328 return bbox
._bbox
(min(x0
, x1
), min(y0
, y1
),
329 max(x0
, x1
), max(y0
, y1
))
331 def _normalized(self
, context
):
334 def outputPS(self
, file):
335 file.write("closepath\n")
337 def outputPDF(self
, file):
341 class moveto_pt(pathel
):
343 """Set current point to (x, y) (coordinates in pts)"""
345 def __init__(self
, x
, y
):
350 return "%g %g moveto" % (self
.x
, self
.y
)
352 def _updatecontext(self
, context
):
353 context
.currentpoint
= self
.x
, self
.y
354 context
.currentsubpath
= self
.x
, self
.y
356 def _bbox(self
, context
):
359 def _normalized(self
, context
):
360 return [moveto_pt(self
.x
, self
.y
)]
362 def outputPS(self
, file):
363 file.write("%g %g moveto\n" % (self
.x
, self
.y
) )
365 def outputPDF(self
, file):
366 file.write("%g %g m\n" % (self
.x
, self
.y
) )
369 class lineto_pt(pathel
):
371 """Append straight line to (x, y) (coordinates in pts)"""
373 def __init__(self
, x
, y
):
378 return "%g %g lineto" % (self
.x
, self
.y
)
380 def _updatecontext(self
, context
):
381 context
.currentsubpath
= context
.currentsubpath
or context
.currentpoint
382 context
.currentpoint
= self
.x
, self
.y
384 def _bbox(self
, context
):
385 return bbox
._bbox
(min(context
.currentpoint
[0], self
.x
),
386 min(context
.currentpoint
[1], self
.y
),
387 max(context
.currentpoint
[0], self
.x
),
388 max(context
.currentpoint
[1], self
.y
))
390 def _normalized(self
, context
):
391 return [normline(context
.currentpoint
[0], context
.currentpoint
[1], self
.x
, self
.y
)]
393 def outputPS(self
, file):
394 file.write("%g %g lineto\n" % (self
.x
, self
.y
) )
396 def outputPDF(self
, file):
397 file.write("%g %g l\n" % (self
.x
, self
.y
) )
400 class curveto_pt(pathel
):
402 """Append curveto (coordinates in pts)"""
404 def __init__(self
, x1
, y1
, x2
, y2
, x3
, y3
):
413 return "%g %g %g %g %g %g curveto" % (self
.x1
, self
.y1
,
417 def _updatecontext(self
, context
):
418 context
.currentsubpath
= context
.currentsubpath
or context
.currentpoint
419 context
.currentpoint
= self
.x3
, self
.y3
421 def _bbox(self
, context
):
422 return bbox
._bbox
(min(context
.currentpoint
[0], self
.x1
, self
.x2
, self
.x3
),
423 min(context
.currentpoint
[1], self
.y1
, self
.y2
, self
.y3
),
424 max(context
.currentpoint
[0], self
.x1
, self
.x2
, self
.x3
),
425 max(context
.currentpoint
[1], self
.y1
, self
.y2
, self
.y3
))
427 def _normalized(self
, context
):
428 return [normcurve(context
.currentpoint
[0], context
.currentpoint
[1],
433 def outputPS(self
, file):
434 file.write("%g %g %g %g %g %g curveto\n" % ( self
.x1
, self
.y1
,
438 def outputPDF(self
, file):
439 file.write("%g %g %g %g %g %g c\n" % ( self
.x1
, self
.y1
,
444 class rmoveto_pt(pathel
):
446 """Perform relative moveto (coordinates in pts)"""
448 def __init__(self
, dx
, dy
):
452 def _updatecontext(self
, context
):
453 context
.currentpoint
= (context
.currentpoint
[0] + self
.dx
,
454 context
.currentpoint
[1] + self
.dy
)
455 context
.currentsubpath
= context
.currentpoint
457 def _bbox(self
, context
):
460 def _normalized(self
, context
):
461 x
= context
.currentpoint
[0]+self
.dx
462 y
= context
.currentpoint
[1]+self
.dy
463 return [moveto_pt(x
, y
)]
465 def outputPS(self
, file):
466 file.write("%g %g rmoveto\n" % (self
.dx
, self
.dy
) )
471 class rlineto_pt(pathel
):
473 """Perform relative lineto (coordinates in pts)"""
475 def __init__(self
, dx
, dy
):
479 def _updatecontext(self
, context
):
480 context
.currentsubpath
= context
.currentsubpath
or context
.currentpoint
481 context
.currentpoint
= (context
.currentpoint
[0]+self
.dx
,
482 context
.currentpoint
[1]+self
.dy
)
484 def _bbox(self
, context
):
485 x
= context
.currentpoint
[0] + self
.dx
486 y
= context
.currentpoint
[1] + self
.dy
487 return bbox
._bbox
(min(context
.currentpoint
[0], x
),
488 min(context
.currentpoint
[1], y
),
489 max(context
.currentpoint
[0], x
),
490 max(context
.currentpoint
[1], y
))
492 def _normalized(self
, context
):
493 x0
= context
.currentpoint
[0]
494 y0
= context
.currentpoint
[1]
495 return [normline(x0
, y0
, x0
+self
.dx
, y0
+self
.dy
)]
497 def outputPS(self
, file):
498 file.write("%g %g rlineto\n" % (self
.dx
, self
.dy
) )
503 class rcurveto_pt(pathel
):
505 """Append rcurveto (coordinates in pts)"""
507 def __init__(self
, dx1
, dy1
, dx2
, dy2
, dx3
, dy3
):
515 def outputPS(self
, file):
516 file.write("%g %g %g %g %g %g rcurveto\n" % ( self
.dx1
, self
.dy1
,
518 self
.dx3
, self
.dy3
) )
522 def _updatecontext(self
, context
):
523 x3
= context
.currentpoint
[0]+self
.dx3
524 y3
= context
.currentpoint
[1]+self
.dy3
526 context
.currentsubpath
= context
.currentsubpath
or context
.currentpoint
527 context
.currentpoint
= x3
, y3
530 def _bbox(self
, context
):
531 x1
= context
.currentpoint
[0]+self
.dx1
532 y1
= context
.currentpoint
[1]+self
.dy1
533 x2
= context
.currentpoint
[0]+self
.dx2
534 y2
= context
.currentpoint
[1]+self
.dy2
535 x3
= context
.currentpoint
[0]+self
.dx3
536 y3
= context
.currentpoint
[1]+self
.dy3
537 return bbox
._bbox
(min(context
.currentpoint
[0], x1
, x2
, x3
),
538 min(context
.currentpoint
[1], y1
, y2
, y3
),
539 max(context
.currentpoint
[0], x1
, x2
, x3
),
540 max(context
.currentpoint
[1], y1
, y2
, y3
))
542 def _normalized(self
, context
):
543 x0
= context
.currentpoint
[0]
544 y0
= context
.currentpoint
[1]
545 return [normcurve(x0
, y0
, x0
+self
.dx1
, y0
+self
.dy1
, x0
+self
.dx2
, y0
+self
.dy2
, x0
+self
.dx3
, y0
+self
.dy3
)]
548 class arc_pt(pathel
):
550 """Append counterclockwise arc (coordinates in pts)"""
552 def __init__(self
, x
, y
, r
, angle1
, angle2
):
560 """Return starting point of arc segment"""
561 return (self
.x
+self
.r
*cos(radians(self
.angle1
)),
562 self
.y
+self
.r
*sin(radians(self
.angle1
)))
565 """Return end point of arc segment"""
566 return (self
.x
+self
.r
*cos(radians(self
.angle2
)),
567 self
.y
+self
.r
*sin(radians(self
.angle2
)))
569 def _updatecontext(self
, context
):
570 if context
.currentpoint
:
571 context
.currentsubpath
= context
.currentsubpath
or context
.currentpoint
573 # we assert that currentsubpath is also None
574 context
.currentsubpath
= self
._sarc
()
576 context
.currentpoint
= self
._earc
()
578 def _bbox(self
, context
):
579 phi1
= radians(self
.angle1
)
580 phi2
= radians(self
.angle2
)
582 # starting end end point of arc segment
583 sarcx
, sarcy
= self
._sarc
()
584 earcx
, earcy
= self
._earc
()
586 # Now, we have to determine the corners of the bbox for the
587 # arc segment, i.e. global maxima/mimima of cos(phi) and sin(phi)
588 # in the interval [phi1, phi2]. These can either be located
589 # on the borders of this interval or in the interior.
592 # guarantee that phi2>phi1
593 phi2
= phi2
+ (math
.floor((phi1
-phi2
)/(2*pi
))+1)*2*pi
595 # next minimum of cos(phi) looking from phi1 in counterclockwise
596 # direction: 2*pi*floor((phi1-pi)/(2*pi)) + 3*pi
598 if phi2
<(2*math
.floor((phi1
-pi
)/(2*pi
))+3)*pi
:
599 minarcx
= min(sarcx
, earcx
)
601 minarcx
= self
.x
-self
.r
603 # next minimum of sin(phi) looking from phi1 in counterclockwise
604 # direction: 2*pi*floor((phi1-3*pi/2)/(2*pi)) + 7/2*pi
606 if phi2
<(2*math
.floor((phi1
-3.0*pi
/2)/(2*pi
))+7.0/2)*pi
:
607 minarcy
= min(sarcy
, earcy
)
609 minarcy
= self
.y
-self
.r
611 # next maximum of cos(phi) looking from phi1 in counterclockwise
612 # direction: 2*pi*floor((phi1)/(2*pi))+2*pi
614 if phi2
<(2*math
.floor((phi1
)/(2*pi
))+2)*pi
:
615 maxarcx
= max(sarcx
, earcx
)
617 maxarcx
= self
.x
+self
.r
619 # next maximum of sin(phi) looking from phi1 in counterclockwise
620 # direction: 2*pi*floor((phi1-pi/2)/(2*pi)) + 1/2*pi
622 if phi2
<(2*math
.floor((phi1
-pi
/2)/(2*pi
))+5.0/2)*pi
:
623 maxarcy
= max(sarcy
, earcy
)
625 maxarcy
= self
.y
+self
.r
627 # Finally, we are able to construct the bbox for the arc segment.
628 # Note that if there is a currentpoint defined, we also
629 # have to include the straight line from this point
630 # to the first point of the arc segment
632 if context
.currentpoint
:
633 return (bbox
._bbox
(min(context
.currentpoint
[0], sarcx
),
634 min(context
.currentpoint
[1], sarcy
),
635 max(context
.currentpoint
[0], sarcx
),
636 max(context
.currentpoint
[1], sarcy
)) +
637 bbox
._bbox
(minarcx
, minarcy
, maxarcx
, maxarcy
)
640 return bbox
._bbox
(minarcx
, minarcy
, maxarcx
, maxarcy
)
642 def _normalized(self
, context
):
643 # get starting and end point of arc segment and bpath corresponding to arc
644 sarcx
, sarcy
= self
._sarc
()
645 earcx
, earcy
= self
._earc
()
646 barc
= _arctobezierpath(self
.x
, self
.y
, self
.r
, self
.angle1
, self
.angle2
)
648 # convert to list of curvetos omitting movetos
652 nbarc
.append(normcurve(bpathel
.x0
, bpathel
.y0
,
653 bpathel
.x1
, bpathel
.y1
,
654 bpathel
.x2
, bpathel
.y2
,
655 bpathel
.x3
, bpathel
.y3
))
657 # Note that if there is a currentpoint defined, we also
658 # have to include the straight line from this point
659 # to the first point of the arc segment.
660 # Otherwise, we have to add a moveto at the beginning
661 if context
.currentpoint
:
662 return [normline(context
.currentpoint
[0], context
.currentpoint
[1], sarcx
, sarcy
)] + nbarc
667 def outputPS(self
, file):
668 file.write("%g %g %g %g %g arc\n" % ( self
.x
, self
.y
,
676 class arcn_pt(pathel
):
678 """Append clockwise arc (coordinates in pts)"""
680 def __init__(self
, x
, y
, r
, angle1
, angle2
):
688 """Return starting point of arc segment"""
689 return (self
.x
+self
.r
*cos(radians(self
.angle1
)),
690 self
.y
+self
.r
*sin(radians(self
.angle1
)))
693 """Return end point of arc segment"""
694 return (self
.x
+self
.r
*cos(radians(self
.angle2
)),
695 self
.y
+self
.r
*sin(radians(self
.angle2
)))
697 def _updatecontext(self
, context
):
698 if context
.currentpoint
:
699 context
.currentsubpath
= context
.currentsubpath
or context
.currentpoint
700 else: # we assert that currentsubpath is also None
701 context
.currentsubpath
= self
._sarc
()
703 context
.currentpoint
= self
._earc
()
705 def _bbox(self
, context
):
706 # in principle, we obtain bbox of an arcn element from
707 # the bounding box of the corrsponding arc element with
708 # angle1 and angle2 interchanged. Though, we have to be carefull
709 # with the straight line segment, which is added if currentpoint
712 # Hence, we first compute the bbox of the arc without this line:
714 a
= arc_pt(self
.x
, self
.y
, self
.r
,
719 arcbb
= a
._bbox
(_pathcontext())
721 # Then, we repeat the logic from arc.bbox, but with interchanged
722 # start and end points of the arc
724 if context
.currentpoint
:
725 return bbox
._bbox
(min(context
.currentpoint
[0], sarc
[0]),
726 min(context
.currentpoint
[1], sarc
[1]),
727 max(context
.currentpoint
[0], sarc
[0]),
728 max(context
.currentpoint
[1], sarc
[1]))+ arcbb
732 def _normalized(self
, context
):
733 # get starting and end point of arc segment and bpath corresponding to arc
734 sarcx
, sarcy
= self
._sarc
()
735 earcx
, earcy
= self
._earc
()
736 barc
= _arctobezierpath(self
.x
, self
.y
, self
.r
, self
.angle2
, self
.angle1
)
739 # convert to list of curvetos omitting movetos
743 nbarc
.append(normcurve(bpathel
.x3
, bpathel
.y3
,
744 bpathel
.x2
, bpathel
.y2
,
745 bpathel
.x1
, bpathel
.y1
,
746 bpathel
.x0
, bpathel
.y0
))
748 # Note that if there is a currentpoint defined, we also
749 # have to include the straight line from this point
750 # to the first point of the arc segment.
751 # Otherwise, we have to add a moveto at the beginning
752 if context
.currentpoint
:
753 return [normline(context
.currentpoint
[0], context
.currentpoint
[1], sarcx
, sarcy
)] + nbarc
758 def outputPS(self
, file):
759 file.write("%g %g %g %g %g arcn\n" % ( self
.x
, self
.y
,
767 class arct_pt(pathel
):
769 """Append tangent arc (coordinates in pts)"""
771 def __init__(self
, x1
, y1
, x2
, y2
, r
):
778 def outputPS(self
, file):
779 file.write("%g %g %g %g %g arct\n" % ( self
.x1
, self
.y1
,
785 def _path(self
, currentpoint
, currentsubpath
):
786 """returns new currentpoint, currentsubpath and path consisting
787 of arc and/or line which corresponds to arct
789 this is a helper routine for _bbox and _normalized, which both need
790 this path. Note: we don't want to calculate the bbox from a bpath
794 # direction and length of tangent 1
795 dx1
= currentpoint
[0]-self
.x1
796 dy1
= currentpoint
[1]-self
.y1
797 l1
= math
.sqrt(dx1
*dx1
+dy1
*dy1
)
799 # direction and length of tangent 2
800 dx2
= self
.x2
-self
.x1
801 dy2
= self
.y2
-self
.y1
802 l2
= math
.sqrt(dx2
*dx2
+dy2
*dy2
)
804 # intersection angle between two tangents
805 alpha
= math
.acos((dx1
*dx2
+dy1
*dy2
)/(l1
*l2
))
807 if math
.fabs(sin(alpha
))>=1e-15 and 1.0+self
.r
!=1.0:
808 cotalpha2
= 1.0/math
.tan(alpha
/2)
811 xt1
= self
.x1
+dx1
*self
.r
*cotalpha2
/l1
812 yt1
= self
.y1
+dy1
*self
.r
*cotalpha2
/l1
813 xt2
= self
.x1
+dx2
*self
.r
*cotalpha2
/l2
814 yt2
= self
.y1
+dy2
*self
.r
*cotalpha2
/l2
816 # direction of center of arc
817 rx
= self
.x1
-0.5*(xt1
+xt2
)
818 ry
= self
.y1
-0.5*(yt1
+yt2
)
819 lr
= math
.sqrt(rx
*rx
+ry
*ry
)
821 # angle around which arc is centered
826 phi
= degrees(math
.atan(ry
/rx
))
828 phi
= degrees(math
.atan(rx
/ry
))+180
830 # half angular width of arc
831 deltaphi
= 90*(1-alpha
/pi
)
833 # center position of arc
834 mx
= self
.x1
-rx
*self
.r
/(lr
*sin(alpha
/2))
835 my
= self
.y1
-ry
*self
.r
/(lr
*sin(alpha
/2))
837 # now we are in the position to construct the path
838 p
= path(moveto_pt(*currentpoint
))
841 p
.append(arc_pt(mx
, my
, self
.r
, phi
-deltaphi
, phi
+deltaphi
))
843 p
.append(arcn_pt(mx
, my
, self
.r
, phi
+deltaphi
, phi
-deltaphi
))
845 return ( (xt2
, yt2
) ,
846 currentsubpath
or (xt2
, yt2
),
850 # we need no arc, so just return a straight line to currentpoint to x1, y1
851 return ( (self
.x1
, self
.y1
),
852 currentsubpath
or (self
.x1
, self
.y1
),
853 line_pt(currentpoint
[0], currentpoint
[1], self
.x1
, self
.y1
) )
855 def _updatecontext(self
, context
):
856 r
= self
._path
(context
.currentpoint
,
857 context
.currentsubpath
)
859 context
.currentpoint
, context
.currentsubpath
= r
[:2]
861 def _bbox(self
, context
):
862 return self
._path
(context
.currentpoint
,
863 context
.currentsubpath
)[2].bbox()
865 def _normalized(self
, context
):
867 return normpath(self
._path
(context
.currentpoint
,
868 context
.currentsubpath
)[2]).subpaths
[0].normpathels
871 # now the pathels that convert from user coordinates to pts
874 class moveto(moveto_pt
):
876 """Set current point to (x, y)"""
878 def __init__(self
, x
, y
):
879 moveto_pt
.__init
__(self
, unit
.topt(x
), unit
.topt(y
))
882 class lineto(lineto_pt
):
884 """Append straight line to (x, y)"""
886 def __init__(self
, x
, y
):
887 lineto_pt
.__init
__(self
, unit
.topt(x
), unit
.topt(y
))
890 class curveto(curveto_pt
):
894 def __init__(self
, x1
, y1
, x2
, y2
, x3
, y3
):
895 curveto_pt
.__init
__(self
,
896 unit
.topt(x1
), unit
.topt(y1
),
897 unit
.topt(x2
), unit
.topt(y2
),
898 unit
.topt(x3
), unit
.topt(y3
))
900 class rmoveto(rmoveto_pt
):
902 """Perform relative moveto"""
904 def __init__(self
, dx
, dy
):
905 rmoveto_pt
.__init
__(self
, unit
.topt(dx
), unit
.topt(dy
))
908 class rlineto(rlineto_pt
):
910 """Perform relative lineto"""
912 def __init__(self
, dx
, dy
):
913 rlineto_pt
.__init
__(self
, unit
.topt(dx
), unit
.topt(dy
))
916 class rcurveto(rcurveto_pt
):
918 """Append rcurveto"""
920 def __init__(self
, dx1
, dy1
, dx2
, dy2
, dx3
, dy3
):
921 rcurveto_pt
.__init
__(self
,
922 unit
.topt(dx1
), unit
.topt(dy1
),
923 unit
.topt(dx2
), unit
.topt(dy2
),
924 unit
.topt(dx3
), unit
.topt(dy3
))
929 """Append clockwise arc"""
931 def __init__(self
, x
, y
, r
, angle1
, angle2
):
932 arcn_pt
.__init
__(self
,
933 unit
.topt(x
), unit
.topt(y
), unit
.topt(r
),
939 """Append counterclockwise arc"""
941 def __init__(self
, x
, y
, r
, angle1
, angle2
):
942 arc_pt
.__init
__(self
, unit
.topt(x
), unit
.topt(y
), unit
.topt(r
),
948 """Append tangent arc"""
950 def __init__(self
, x1
, y1
, x2
, y2
, r
):
951 arct_pt
.__init
__(self
, unit
.topt(x1
), unit
.topt(y1
),
952 unit
.topt(x2
), unit
.topt(y2
),
956 # "combined" pathels provided for performance reasons
959 class multilineto_pt(pathel
):
961 """Perform multiple linetos (coordinates in pts)"""
963 def __init__(self
, points
):
966 def _updatecontext(self
, context
):
967 context
.currentsubpath
= context
.currentsubpath
or context
.currentpoint
968 context
.currentpoint
= self
.points
[-1]
970 def _bbox(self
, context
):
971 xs
= [point
[0] for point
in self
.points
]
972 ys
= [point
[1] for point
in self
.points
]
973 return bbox
._bbox
(min(context
.currentpoint
[0], *xs
),
974 min(context
.currentpoint
[1], *ys
),
975 max(context
.currentpoint
[0], *xs
),
976 max(context
.currentpoint
[1], *ys
))
978 def _normalized(self
, context
):
980 x0
, y0
= context
.currentpoint
981 for x
, y
in self
.points
:
982 result
.append(normline(x0
, y0
, x
, y
))
986 def outputPS(self
, file):
987 for x
, y
in self
.points
:
988 file.write("%g %g lineto\n" % (x
, y
) )
990 def outputPDF(self
, file):
991 for x
, y
in self
.points
:
992 file.write("%g %g l\n" % (x
, y
) )
995 class multicurveto_pt(pathel
):
997 """Perform multiple curvetos (coordinates in pts)"""
999 def __init__(self
, points
):
1000 self
.points
= points
1002 def _updatecontext(self
, context
):
1003 context
.currentsubpath
= context
.currentsubpath
or context
.currentpoint
1004 context
.currentpoint
= self
.points
[-1]
1006 def _bbox(self
, context
):
1007 xs
= [point
[0] for point
in self
.points
] + [point
[2] for point
in self
.points
] + [point
[2] for point
in self
.points
]
1008 ys
= [point
[1] for point
in self
.points
] + [point
[3] for point
in self
.points
] + [point
[5] for point
in self
.points
]
1009 return bbox
._bbox
(min(context
.currentpoint
[0], *xs
),
1010 min(context
.currentpoint
[1], *ys
),
1011 max(context
.currentpoint
[0], *xs
),
1012 max(context
.currentpoint
[1], *ys
))
1014 def _normalized(self
, context
):
1016 x0
, y0
= context
.currentpoint
1017 for point
in self
.points
:
1018 result
.append(normcurve(x0
, y0
, *point
))
1022 def outputPS(self
, file):
1023 for point
in self
.points
:
1024 file.write("%g %g %g %g %g %g curveto\n" % tuple(point
))
1026 def outputPDF(self
, file):
1027 for point
in self
.points
:
1028 file.write("%g %g %g %g %g %g c\n" % tuple(point
))
1031 ################################################################################
1032 # path: PS style path
1033 ################################################################################
1035 class path(base
.PSCmd
):
1039 def __init__(self
, *args
):
1040 if len(args
)==1 and isinstance(args
[0], path
):
1041 self
.path
= args
[0].path
1043 self
.path
= list(args
)
1045 def __add__(self
, other
):
1046 return path(*(self
.path
+other
.path
))
1048 def __iadd__(self
, other
):
1049 self
.path
+= other
.path
1052 def __getitem__(self
, i
):
1056 return len(self
.path
)
1058 def append(self
, pathel
):
1059 self
.path
.append(pathel
)
1061 def arclength_pt(self
, epsilon
=1e-5):
1062 """returns total arc length of path in pts with accuracy epsilon"""
1063 return normpath(self
).arclength_pt(epsilon
)
1065 def arclength(self
, epsilon
=1e-5):
1066 """returns total arc length of path with accuracy epsilon"""
1067 return normpath(self
).arclength(epsilon
)
1069 def lentopar(self
, lengths
, epsilon
=1e-5):
1070 """returns (t,l) with t the parameter value(s) matching given length,
1071 l the total length"""
1072 return normpath(self
).lentopar(lengths
, epsilon
)
1075 """return coordinates in pts of corresponding normpath at parameter value t"""
1076 return normpath(self
).at_pt(t
)
1079 """return coordinates of corresponding normpath at parameter value t"""
1080 return normpath(self
).at(t
)
1083 context
= _pathcontext()
1086 for pel
in self
.path
:
1087 nbbox
= pel
._bbox
(context
)
1088 pel
._updatecontext
(context
)
1097 """return coordinates of first point of first subpath in path (in pts)"""
1098 return normpath(self
).begin_pt()
1101 """return coordinates of first point of first subpath in path"""
1102 return normpath(self
).begin()
1105 """return coordinates of last point of last subpath in path (in pts)"""
1106 return normpath(self
).end_pt()
1109 """return coordinates of last point of last subpath in path"""
1110 return normpath(self
).end()
1112 def glue(self
, other
):
1113 """return path consisting of self and other glued together"""
1114 return normpath(self
).glue(other
)
1116 # << operator also designates glueing
1119 def intersect(self
, other
, epsilon
=1e-5):
1120 """intersect normpath corresponding to self with other path"""
1121 return normpath(self
).intersect(other
, epsilon
)
1124 """return maximal value for parameter value t for corr. normpath"""
1125 return normpath(self
).range()
1128 """return reversed path"""
1129 return normpath(self
).reversed()
1131 def split(self
, parameters
):
1132 """return corresponding normpaths split at parameter value t"""
1133 return normpath(self
).split(parameters
)
1135 def tangent(self
, t
, length
=None):
1136 """return tangent vector at parameter value t of corr. normpath"""
1137 return normpath(self
).tangent(t
, length
)
1139 def transformed(self
, trafo
):
1140 """return transformed path"""
1141 return normpath(self
).transformed(trafo
)
1143 def outputPS(self
, file):
1144 if not (isinstance(self
.path
[0], moveto_pt
) or
1145 isinstance(self
.path
[0], arc_pt
) or
1146 isinstance(self
.path
[0], arcn_pt
)):
1147 raise PathException("first path element must be either moveto, arc, or arcn")
1148 for pel
in self
.path
:
1151 def outputPDF(self
, file):
1152 if not (isinstance(self
.path
[0], moveto_pt
) or
1153 isinstance(self
.path
[0], arc_pt
) or # outputPDF
1154 isinstance(self
.path
[0], arcn_pt
)): # outputPDF
1155 raise PathException("first path element must be either moveto, arc, or arcn")
1156 for pel
in self
.path
:
1159 ################################################################################
1160 # normpath and corresponding classes
1161 ################################################################################
1164 # normpathel: normalized element
1169 """element of a normalized sub path"""
1172 """returns coordinates of point in pts at parameter t (0<=t<=1) """
1175 def arclength_pt(self
, epsilon
=1e-5):
1176 """returns arc length of normpathel in pts with given accuracy epsilon"""
1180 """return bounding box of normpathel"""
1183 def intersect(self
, other
, epsilon
=1e-5):
1184 # XXX make this more efficient and _clean_ by treating special cases separately
1185 if isinstance(self
, normline
):
1186 self
= self
._bcurve
()
1187 if isinstance(other
, normline
):
1188 other
= other
._bcurve
()
1189 return _bcurvesIntersect([self
], 0, 1, [other
], 0, 1, epsilon
)
1191 def _lentopar_pt(self
, lengths
, epsilon
=1e-5):
1192 """returns tuple (t,l) with
1193 t the parameter where the arclength of normpathel is length and
1194 l the total arclength
1196 length: length (in pts) to find the parameter for
1197 epsilon: epsilon controls the accuracy for calculation of the
1198 length of the Bezier elements
1200 # Note: _lentopar returns both, parameters and total lengths
1201 # while lentopar returns only parameters
1205 """return reversed normpathel"""
1208 def split(self
, parameters
):
1209 """splits normpathel
1211 parameters: list of parameter values (0<=t<=1) at which to split
1213 returns None or list of tuple of normpathels corresponding to
1214 the orginal normpathel.
1220 def tangent(self
, t
):
1221 """returns tangent vector of _normpathel at parameter t (0<=t<=1)"""
1224 def transformed(self
, trafo
):
1225 """return transformed normpathel according to trafo"""
1228 def outputPS(self
, file):
1229 """write normpathel (in the context of a normsubpath) to file"""
1235 # there are only two normpathels: normline and normcurve
1238 class normline(normpathel
):
1240 """Straight line from (x0, y0) to (x1, y1) (coordinates in pts)"""
1242 def __init__(self
, x0
, y0
, x1
, y1
):
1249 return "normline(%g, %g, %g, %g)" % (self
.x0
, self
.y0
, self
.x1
, self
.y1
)
1252 xa
= self
.x0
+(self
.x1
-self
.x0
)/3.0
1253 ya
= self
.y0
+(self
.y1
-self
.y0
)/3.0
1254 xb
= self
.x0
+2.0*(self
.x1
-self
.x0
)/3.0
1255 yb
= self
.y0
+2.0*(self
.y1
-self
.y0
)/3.0
1256 return normcurve(self
.x0
, self
.y0
, xa
, ya
, xb
, yb
, self
.x1
, self
.y1
)
1258 def arclength_pt(self
, epsilon
=1e-5):
1259 return math
.sqrt((self
.x0
-self
.x1
)*(self
.x0
-self
.x1
)+(self
.y0
-self
.y1
)*(self
.y0
-self
.y1
))
1262 return (self
.x0
+(self
.x1
-self
.x0
)*t
, self
.y0
+(self
.y1
-self
.y0
)*t
)
1265 return bbox
._bbox
(min(self
.x0
, self
.x1
), min(self
.y0
, self
.y1
),
1266 max(self
.x0
, self
.x1
), max(self
.y0
, self
.y1
))
1269 return self
.x0
, self
.y0
1272 return self
.x1
, self
.y1
1274 def _lentopar_pt(self
, lengths
, epsilon
=1e-5):
1275 l
= self
.arclength_pt(epsilon
)
1276 return ([max(min(1.0*length
/l
,1),0) for length
in lengths
], l
)
1279 self
.x0
, self
.y0
, self
.x1
, self
.y1
= self
.x1
, self
.y1
, self
.x0
, self
.y0
1282 return normline(self
.x1
, self
.y1
, self
.x0
, self
.y0
)
1284 def split(self
, parameters
):
1285 x0
, y0
= self
.x0
, self
.y0
1286 x1
, y1
= self
.x1
, self
.y1
1291 if parameters
[0] == 0:
1293 parameters
= parameters
[1:]
1296 for t
in parameters
:
1297 xs
, ys
= x0
+ (x1
-x0
)*t
, y0
+ (y1
-y0
)*t
1298 result
.append(normline(xl
, yl
, xs
, ys
))
1301 if parameters
[-1]!=1:
1302 result
.append(normline(xs
, ys
, x1
, y1
))
1306 result
.append(normline(x0
, y0
, x1
, y1
))
1311 def tangent(self
, t
):
1312 tx
, ty
= self
.x0
+ (self
.x1
-self
.x0
)*t
, self
.y0
+ (self
.y1
-self
.y0
)*t
1313 tvectx
, tvecty
= self
.x1
-self
.x0
, self
.y1
-self
.y0
1314 # XXX should we return a normpath instead?
1315 return line_pt(tx
, ty
, tx
+tvectx
, ty
+tvecty
)
1317 def transformed(self
, trafo
):
1318 return normline(*(trafo
._apply
(self
.x0
, self
.y0
) + trafo
._apply
(self
.x1
, self
.y1
)))
1320 def outputPS(self
, file):
1321 file.write("%g %g lineto\n" % (self
.x1
, self
.y1
))
1323 def outputPDF(self
, file):
1324 file.write("%g %g l\n" % (self
.x1
, self
.y1
))
1327 class normcurve(normpathel
):
1329 """Bezier curve with control points x0, y0, x1, y1, x2, y2, x3, y3 (coordinates in pts)"""
1331 def __init__(self
, x0
, y0
, x1
, y1
, x2
, y2
, x3
, y3
):
1342 return "normcurve(%g, %g, %g, %g, %g, %g, %g, %g)" % (self
.x0
, self
.y0
, self
.x1
, self
.y1
,
1343 self
.x2
, self
.y2
, self
.x3
, self
.y3
)
1345 def arclength_pt(self
, epsilon
=1e-5):
1346 """computes arclength of bpathel in pts using successive midpoint split"""
1347 if self
.isstraight(epsilon
):
1348 return math
.sqrt((self
.x3
-self
.x0
)*(self
.x3
-self
.x0
)+
1349 (self
.y3
-self
.y0
)*(self
.y3
-self
.y0
))
1351 (a
, b
) = self
.midpointsplit()
1352 return a
.arclength_pt(epsilon
) + b
.arclength_pt(epsilon
)
1355 xt
= ( (-self
.x0
+3*self
.x1
-3*self
.x2
+self
.x3
)*t
*t
*t
+
1356 (3*self
.x0
-6*self
.x1
+3*self
.x2
)*t
*t
+
1357 (-3*self
.x0
+3*self
.x1
)*t
+
1359 yt
= ( (-self
.y0
+3*self
.y1
-3*self
.y2
+self
.y3
)*t
*t
*t
+
1360 (3*self
.y0
-6*self
.y1
+3*self
.y2
)*t
*t
+
1361 (-3*self
.y0
+3*self
.y1
)*t
+
1366 return bbox
._bbox
(min(self
.x0
, self
.x1
, self
.x2
, self
.x3
),
1367 min(self
.y0
, self
.y1
, self
.y2
, self
.y3
),
1368 max(self
.x0
, self
.x1
, self
.x2
, self
.x3
),
1369 max(self
.y0
, self
.y1
, self
.y2
, self
.y3
))
1372 return self
.x0
, self
.y0
1375 return self
.x3
, self
.y3
1377 def isstraight(self
, epsilon
=1e-5):
1378 """check wheter the normcurve is approximately straight"""
1380 # just check, whether the modulus of the difference between
1381 # the length of the control polygon
1382 # (i.e. |P1-P0|+|P2-P1|+|P3-P2|) and the length of the
1383 # straight line between starting and ending point of the
1384 # normcurve (i.e. |P3-P1|) is smaller the epsilon
1385 return abs(math
.sqrt((self
.x1
-self
.x0
)*(self
.x1
-self
.x0
)+
1386 (self
.y1
-self
.y0
)*(self
.y1
-self
.y0
)) +
1387 math
.sqrt((self
.x2
-self
.x1
)*(self
.x2
-self
.x1
)+
1388 (self
.y2
-self
.y1
)*(self
.y2
-self
.y1
)) +
1389 math
.sqrt((self
.x3
-self
.x2
)*(self
.x3
-self
.x2
)+
1390 (self
.y3
-self
.y2
)*(self
.y3
-self
.y2
)) -
1391 math
.sqrt((self
.x3
-self
.x0
)*(self
.x3
-self
.x0
)+
1392 (self
.y3
-self
.y0
)*(self
.y3
-self
.y0
)))<epsilon
1394 def _lentopar_pt(self
, lengths
, epsilon
=1e-5):
1395 return self
._bcurve
()._lentopar
_pt
(lengths
, epsilon
)
1397 def midpointsplit(self
):
1398 """splits bpathel at midpoint returning bpath with two bpathels"""
1400 # for efficiency reason, we do not use self.split(0.5)!
1402 # first, we have to calculate the midpoints between adjacent
1404 x01
= 0.5*(self
.x0
+self
.x1
)
1405 y01
= 0.5*(self
.y0
+self
.y1
)
1406 x12
= 0.5*(self
.x1
+self
.x2
)
1407 y12
= 0.5*(self
.y1
+self
.y2
)
1408 x23
= 0.5*(self
.x2
+self
.x3
)
1409 y23
= 0.5*(self
.y2
+self
.y3
)
1411 # In the next iterative step, we need the midpoints between 01 and 12
1412 # and between 12 and 23
1413 x01_12
= 0.5*(x01
+x12
)
1414 y01_12
= 0.5*(y01
+y12
)
1415 x12_23
= 0.5*(x12
+x23
)
1416 y12_23
= 0.5*(y12
+y23
)
1418 # Finally the midpoint is given by
1419 xmidpoint
= 0.5*(x01_12
+x12_23
)
1420 ymidpoint
= 0.5*(y01_12
+y12_23
)
1422 return (normcurve(self
.x0
, self
.y0
,
1425 xmidpoint
, ymidpoint
),
1426 normcurve(xmidpoint
, ymidpoint
,
1432 self
.x0
, self
.y0
, self
.x1
, self
.y1
, self
.x2
, self
.y2
, self
.x3
, self
.y3
= \
1433 self
.x3
, self
.y3
, self
.x2
, self
.y2
, self
.x1
, self
.y1
, self
.x0
, self
.y0
1436 return normcurve(self
.x3
, self
.y3
, self
.x2
, self
.y2
, self
.x1
, self
.y1
, self
.x0
, self
.y0
)
1438 def seglengths(self
, paraminterval
, epsilon
=1e-5):
1439 """returns the list of segment line lengths (in pts) of the bpathel
1440 together with the length of the parameterinterval"""
1442 # lower and upper bounds for the arclength
1444 math
.sqrt((self
.x3
-self
.x0
)*(self
.x3
-self
.x0
) + (self
.y3
-self
.y0
)*(self
.y3
-self
.y0
))
1446 math
.sqrt((self
.x1
-self
.x0
)*(self
.x1
-self
.x0
) + (self
.y1
-self
.y0
)*(self
.y1
-self
.y0
)) + \
1447 math
.sqrt((self
.x2
-self
.x1
)*(self
.x2
-self
.x1
) + (self
.y2
-self
.y1
)*(self
.y2
-self
.y1
)) + \
1448 math
.sqrt((self
.x3
-self
.x2
)*(self
.x3
-self
.x2
) + (self
.y3
-self
.y2
)*(self
.y3
-self
.y2
))
1450 # instead of isstraight method:
1451 if abs(upperlen
-lowerlen
)<epsilon
:
1452 return [( 0.5*(upperlen
+lowerlen
), paraminterval
)]
1454 (a
, b
) = self
.midpointsplit()
1455 return a
.seglengths(0.5*paraminterval
, epsilon
) + b
.seglengths(0.5*paraminterval
, epsilon
)
1457 def _lentopar_pt(self
, lengths
, epsilon
=1e-5):
1458 """computes the parameters [t] of bpathel where the given lengths (in pts) are assumed
1459 returns ( [parameters], total arclength)
1460 A negative length gives a parameter 0"""
1462 # create the list of accumulated lengths
1463 # and the length of the parameters
1464 cumlengths
= self
.seglengths(1, epsilon
)
1466 parlengths
= [cumlengths
[i
][1] for i
in range(l
)]
1467 cumlengths
[0] = cumlengths
[0][0]
1468 for i
in range(1,l
):
1469 cumlengths
[i
] = cumlengths
[i
][0] + cumlengths
[i
-1]
1471 # create the list of parameters to be returned
1473 for length
in lengths
:
1474 # find the last index that is smaller than length
1476 lindex
= bisect
.bisect_left(cumlengths
, length
)
1477 except: # workaround for python 2.0
1478 lindex
= bisect
.bisect(cumlengths
, length
)
1479 while lindex
and (lindex
>= len(cumlengths
) or
1480 cumlengths
[lindex
] >= length
):
1483 param
= length
* 1.0 / cumlengths
[0]
1484 param
*= parlengths
[0]
1488 param
= (length
- cumlengths
[lindex
]) * 1.0 / (cumlengths
[lindex
+1] - cumlengths
[lindex
])
1489 param
*= parlengths
[lindex
+1]
1490 for i
in range(lindex
+1):
1491 param
+= parlengths
[i
]
1492 param
= max(min(param
,1),0)
1493 params
.append(param
)
1494 return [params
, cumlengths
[-1]]
1496 def _split(self
, parameters
):
1497 """return list of normcurve corresponding to split at parameters"""
1499 # first, we calculate the coefficients corresponding to our
1500 # original bezier curve. These represent a useful starting
1501 # point for the following change of the polynomial parameter
1504 a1x
= 3*(-self
.x0
+self
.x1
)
1505 a1y
= 3*(-self
.y0
+self
.y1
)
1506 a2x
= 3*(self
.x0
-2*self
.x1
+self
.x2
)
1507 a2y
= 3*(self
.y0
-2*self
.y1
+self
.y2
)
1508 a3x
= -self
.x0
+3*(self
.x1
-self
.x2
)+self
.x3
1509 a3y
= -self
.y0
+3*(self
.y1
-self
.y2
)+self
.y3
1511 if parameters
[0]!=0:
1512 parameters
= [0] + parameters
1513 if parameters
[-1]!=1:
1514 parameters
= parameters
+ [1]
1518 for i
in range(len(parameters
)-1):
1520 dt
= parameters
[i
+1]-t1
1524 # the new coefficients of the [t1,t1+dt] part of the bezier curve
1525 # are then given by expanding
1526 # a0 + a1*(t1+dt*u) + a2*(t1+dt*u)**2 +
1527 # a3*(t1+dt*u)**3 in u, yielding
1529 # a0 + a1*t1 + a2*t1**2 + a3*t1**3 +
1530 # ( a1 + 2*a2 + 3*a3*t1**2 )*dt * u +
1531 # ( a2 + 3*a3*t1 )*dt**2 * u**2 +
1534 # from this values we obtain the new control points by inversion
1536 # XXX: we could do this more efficiently by reusing for
1537 # (x0, y0) the control point (x3, y3) from the previous
1540 x0
= a0x
+ a1x
*t1
+ a2x
*t1
*t1
+ a3x
*t1
*t1
*t1
1541 y0
= a0y
+ a1y
*t1
+ a2y
*t1
*t1
+ a3y
*t1
*t1
*t1
1542 x1
= (a1x
+2*a2x
*t1
+3*a3x
*t1
*t1
)*dt
/3.0 + x0
1543 y1
= (a1y
+2*a2y
*t1
+3*a3y
*t1
*t1
)*dt
/3.0 + y0
1544 x2
= (a2x
+3*a3x
*t1
)*dt
*dt
/3.0 - x0
+ 2*x1
1545 y2
= (a2y
+3*a3y
*t1
)*dt
*dt
/3.0 - y0
+ 2*y1
1546 x3
= a3x
*dt
*dt
*dt
+ x0
- 3*x1
+ 3*x2
1547 y3
= a3y
*dt
*dt
*dt
+ y0
- 3*y1
+ 3*y2
1549 result
.append(normcurve(x0
, y0
, x1
, y1
, x2
, y2
, x3
, y3
))
1553 def split(self
, parameters
):
1556 bps
= self
._split
(list(parameters
))
1558 if parameters
[0]==0:
1562 result
= [normcurve(self
.x0
, self
.y0
, bp0
.x1
, bp0
.y1
, bp0
.x2
, bp0
.y2
, bp0
.x3
, bp0
.y3
)]
1566 result
.append(normcurve(bp
.x0
, bp
.y0
, bp
.x1
, bp
.y1
, bp
.x2
, bp
.y2
, bp
.x3
, bp
.y3
))
1568 if parameters
[-1]==1:
1574 def tangent(self
, t
):
1575 tpx
, tpy
= self
.at_pt(t
)
1576 tvectx
= (3*( -self
.x0
+3*self
.x1
-3*self
.x2
+self
.x3
)*t
*t
+
1577 2*( 3*self
.x0
-6*self
.x1
+3*self
.x2
)*t
+
1578 (-3*self
.x0
+3*self
.x1
))
1579 tvecty
= (3*( -self
.y0
+3*self
.y1
-3*self
.y2
+self
.y3
)*t
*t
+
1580 2*( 3*self
.y0
-6*self
.y1
+3*self
.y2
)*t
+
1581 (-3*self
.y0
+3*self
.y1
))
1582 return line_pt(tpx
, tpy
, tpx
+tvectx
, tpy
+tvecty
)
1584 def transform(self
, trafo
):
1585 self
.x0
, self
.y0
= trafo
._apply
(self
.x0
, self
.y0
)
1586 self
.x1
, self
.y1
= trafo
._apply
(self
.x1
, self
.y1
)
1587 self
.x2
, self
.y2
= trafo
._apply
(self
.x2
, self
.y2
)
1588 self
.x3
, self
.y3
= trafo
._apply
(self
.x3
, self
.y3
)
1590 def transformed(self
, trafo
):
1591 return normcurve(*(trafo
._apply
(self
.x0
, self
.y0
)+
1592 trafo
._apply
(self
.x1
, self
.y1
)+
1593 trafo
._apply
(self
.x2
, self
.y2
)+
1594 trafo
._apply
(self
.x3
, self
.y3
)))
1596 def outputPS(self
, file):
1597 file.write("%g %g %g %g %g %g curveto\n" % (self
.x1
, self
.y1
, self
.x2
, self
.y2
, self
.x3
, self
.y3
))
1599 def outputPDF(self
, file):
1600 file.write("%g %g %g %g %g %g c\n" % (self
.x1
, self
.y1
, self
.x2
, self
.y2
, self
.x3
, self
.y3
))
1603 # normpaths are made up of normsubpaths, which represent connected line segments
1608 """sub path of a normalized path
1610 A subpath consists of a list of normpathels, i.e., lines and bcurves
1611 and can either be closed or not.
1613 Some invariants, which have to be obeyed:
1614 - The last point of a normpathel and the first point of the next
1615 element have to be equal.
1616 - When the path is closed, the last normpathel has to be a
1617 normline and the last point of this normline has to be equal
1618 to the first point of the first normpathel
1622 def __init__(self
, normpathels
, closed
):
1623 self
.normpathels
= normpathels
1624 self
.closed
= closed
1627 return "subpath(%s, [%s])" % (self
.closed
and "closed" or "open",
1628 ", ".join(map(str, self
.normpathels
)))
1630 def arclength_pt(self
, epsilon
=1e-5):
1631 """returns total arc length of normsubpath in pts with accuracy epsilon"""
1632 return sum([npel
.arclength_pt(epsilon
) for npel
in self
.normpathels
])
1635 """return coordinates in pts of sub path at parameter value t
1637 Negative values of t count from the end of the path. The absolute
1638 value of t must be smaller or equal to the number of segments in
1639 the normpath, otherwise None is returned.
1644 if 0<=t
<self
.range():
1645 return self
.normpathels
[int(t
)].at_pt(t
-int(t
))
1647 return self
.end_pt()
1650 if self
.normpathels
:
1651 abbox
= self
.normpathels
[0].bbox()
1652 for anormpathel
in self
.normpathels
[1:]:
1653 abbox
+= anormpathel
.bbox()
1659 return self
.normpathels
[0].begin_pt()
1662 return self
.normpathels
[-1].end_pt()
1664 def intersect(self
, other
, epsilon
=1e-5):
1665 """intersect self with other normsubpath
1667 returns a tuple of lists consisting of the parameter values
1668 of the intersection points of the corresponding normsubpath
1671 intersections
= ([], [])
1672 # Intersect all subpaths of self with the subpaths of other
1673 for t_a
, pel_a
in enumerate(self
.normpathels
):
1674 for t_b
, pel_b
in enumerate(other
.normpathels
):
1675 for intersection
in pel_a
.intersect(pel_b
, epsilon
):
1676 # check whether an intersection occurs at the end
1677 # of a closed subpath. If yes, we don't include it
1678 # in the list of intersections to prevent a
1679 # duplication of intersection points
1680 if not ((self
.closed
and self
.range()-intersection
[0]-t_a
<epsilon
) or
1681 (other
.closed
and other
.range()-intersection
[1]-t_b
<epsilon
)):
1682 intersections
[0].append(intersection
[0]+t_a
)
1683 intersections
[1].append(intersection
[1]+t_b
)
1684 return intersections
1686 def _lentopar_pt(self
, lengths
, epsilon
=1e-5):
1687 """returns [t, l] where t are parameter value(s) matching given length(s)
1688 and l is the total length of the normsubpath
1689 The parameters are with respect to the normsubpath: t in [0, self.range()]
1690 lengths that are < 0 give parameter 0"""
1693 allparams
= [0]*len(lengths
)
1694 rests
= [length
for length
in lengths
]
1696 for pel
in self
.normpathels
:
1697 params
, arclength
= pel
._lentopar
_pt
(rests
, epsilon
)
1698 allarclength
+= arclength
1699 for i
in range(len(rests
)):
1701 rests
[i
] -= arclength
1702 allparams
[i
] += params
[i
]
1704 return [allparams
, allarclength
]
1707 """return maximal parameter value, i.e. number of line/curve segments"""
1708 return len(self
.normpathels
)
1711 self
.normpathels
.reverse()
1712 for npel
in self
.normpathels
:
1717 for i
in range(len(self
.normpathels
)):
1718 nnormpathels
.append(self
.normpathels
[-(i
+1)].reversed())
1719 return normsubpath(nnormpathels
, self
.closed
)
1721 def split(self
, ts
):
1722 """split normsubpath at list of parameter values ts and return list
1725 Negative values of t count from the end of the sub path.
1726 After taking this rule into account, the parameter list ts has
1727 to be sorted and all parameters t have to fulfil
1728 0<=t<=self.range(). Note that each element of the resulting
1729 list is an _open_ normsubpath.
1733 for i
in range(len(ts
)):
1735 ts
[i
] += self
.range()
1736 if not (0<=ts
[i
]<=self
.range()):
1737 raise RuntimeError("parameter for split of subpath out of range")
1741 for t
, pel
in enumerate(self
.normpathels
):
1742 # determine list of splitting parameters relevant for pel
1749 # now we split the path at the filtered parameter values
1750 # This yields a list of normpathels and possibly empty
1751 # segments marked by None
1752 splitresult
= pel
.split(nts
)
1756 if splitresult
[0] is None:
1757 # mark split at the beginning of the normsubpath
1760 result
.append(normsubpath([splitresult
[0]], 0))
1762 npels
.append(splitresult
[0])
1763 result
.append(normsubpath(npels
, 0))
1764 for npel
in splitresult
[1:-1]:
1765 result
.append(normsubpath([npel
], 0))
1766 if len(splitresult
)>1 and splitresult
[-1] is not None:
1767 npels
= [splitresult
[-1]]
1777 result
.append(normsubpath(npels
, 0))
1779 # mark split at the end of the normsubpath
1782 # glue last and first segment together if the normsubpath was originally closed
1784 if result
[0] is None:
1786 elif result
[-1] is None:
1787 result
= result
[:-1]
1789 result
[-1].normpathels
.extend(result
[0].normpathels
)
1793 def tangent(self
, t
):
1796 if 0<=t
<self
.range():
1797 return self
.normpathels
[int(t
)].tangent(t
-int(t
))
1799 return self
.normpathels
[-1].tangent(1)
1801 def transform(self
, trafo
):
1802 """transform sub path according to trafo"""
1803 for pel
in self
.normpathels
:
1804 pel
.transform(trafo
)
1806 def transformed(self
, trafo
):
1807 """return sub path transformed according to trafo"""
1809 for pel
in self
.normpathels
:
1810 nnormpathels
.append(pel
.transformed(trafo
))
1811 return normsubpath(nnormpathels
, self
.closed
)
1813 def outputPS(self
, file):
1814 # if the normsubpath is closed, we must not output the last normpathel
1816 normpathels
= self
.normpathels
[:-1]
1818 normpathels
= self
.normpathels
1820 file.write("%g %g moveto\n" % self
.begin_pt())
1821 for anormpathel
in normpathels
:
1822 anormpathel
.outputPS(file)
1824 file.write("closepath\n")
1826 def outputPDF(self
, file):
1827 # if the normsubpath is closed, we must not output the last normpathel
1829 normpathels
= self
.normpathels
[:-1]
1831 normpathels
= self
.normpathels
1833 file.write("%g %g m\n" % self
.begin_pt())
1834 for anormpathel
in normpathels
:
1835 anormpathel
.outputPDF(file)
1837 file.write("closepath\n")
1840 # the normpath class
1843 class normpath(path
):
1847 a normalized path consits of a list of normsubpaths
1851 def __init__(self
, arg
=[]):
1852 """ construct a normpath from another normpath passed as arg,
1853 a path or a list of normsubpaths """
1854 if isinstance(arg
, normpath
):
1855 self
.subpaths
= copy
.copy(arg
.subpaths
)
1857 elif isinstance(arg
, path
):
1858 # split path in sub paths
1860 currentsubpathels
= []
1861 context
= _pathcontext()
1862 for pel
in arg
.path
:
1863 for npel
in pel
._normalized
(context
):
1864 if isinstance(npel
, moveto_pt
):
1865 if currentsubpathels
:
1866 # append open sub path
1867 self
.subpaths
.append(normsubpath(currentsubpathels
, 0))
1868 # start new sub path
1869 currentsubpathels
= []
1870 elif isinstance(npel
, closepath
):
1871 if currentsubpathels
:
1872 # append closed sub path
1873 currentsubpathels
.append(normline(context
.currentpoint
[0], context
.currentpoint
[1],
1874 context
.currentsubpath
[0], context
.currentsubpath
[1]))
1875 self
.subpaths
.append(normsubpath(currentsubpathels
, 1))
1876 currentsubpathels
= []
1878 currentsubpathels
.append(npel
)
1879 pel
._updatecontext
(context
)
1881 if currentsubpathels
:
1882 # append open sub path
1883 self
.subpaths
.append(normsubpath(currentsubpathels
, 0))
1885 # we expect a list of normsubpaths
1886 self
.subpaths
= list(arg
)
1888 def __add__(self
, other
):
1889 result
= normpath(other
)
1890 result
.subpaths
= self
.subpaths
+ result
.subpaths
1893 def __iadd__(self
, other
):
1894 self
.subpaths
+= normpath(other
).subpaths
1899 return len(self
.subpaths
)
1902 return "normpath(%s)" % ", ".join(map(str, self
.subpaths
))
1904 def _findsubpath(self
, t
):
1905 """return a tuple (subpath, relativet),
1906 where subpath is the subpath containing the parameter value t and t is the
1907 renormalized value of t in this subpath
1909 Negative values of t count from the end of the path. At
1910 discontinuities in the path, the limit from below is returned.
1911 None is returned, if the parameter t is out of range.
1918 for sp
in self
.subpaths
:
1919 sprange
= sp
.range()
1920 if spt
<= t
<= sprange
+spt
:
1925 def append(self
, pathel
):
1926 # XXX factor parts of this code out
1927 if self
.subpaths
[-1].closed
:
1928 context
= _pathcontext(self
.end_pt(), None)
1929 currensubpathels
= []
1931 context
= _pathcontext(self
.end_pt(), self
.subpaths
[-1].begin_pt())
1932 currentsubpathels
= self
.subpaths
[-1].normpathels
1933 self
.subpaths
= self
.subpaths
[:-1]
1934 for npel
in pathel
._normalized
(context
):
1935 if isinstance(npel
, moveto_pt
):
1936 if currentsubpathels
:
1937 # append open sub path
1938 self
.subpaths
.append(normsubpath(currentsubpathels
, 0))
1939 # start new sub path
1940 currentsubpathels
= []
1941 elif isinstance(npel
, closepath
):
1942 if currentsubpathels
:
1943 # append closed sub path
1944 currentsubpathels
.append(normline(context
.currentpoint
[0], context
.currentpoint
[1],
1945 context
.currentsubpath
[0], context
.currentsubpath
[1]))
1946 self
.subpaths
.append(normsubpath(currentsubpathels
, 1))
1947 currentsubpathels
= []
1949 currentsubpathels
.append(npel
)
1951 if currentsubpathels
:
1952 # append open sub path
1953 self
.subpaths
.append(normsubpath(currentsubpathels
, 0))
1955 def arclength_pt(self
, epsilon
=1e-5):
1956 """returns total arc length of normpath in pts with accuracy epsilon"""
1957 return sum([sp
.arclength_pt(epsilon
) for sp
in self
.subpaths
])
1959 def arclength(self
, epsilon
=1e-5):
1960 """returns total arc length of normpath with accuracy epsilon"""
1961 return unit
.t_pt(self
.arclength_pt(epsilon
))
1964 """return coordinates in pts of path at parameter value t
1966 Negative values of t count from the end of the path. The absolute
1967 value of t must be smaller or equal to the number of segments in
1968 the normpath, otherwise None is returned.
1969 At discontinuities in the path, the limit from below is returned
1972 result
= self
._findsubpath
(t
)
1974 return result
[0].at_pt(result
[1])
1979 """return coordinates of path at parameter value t
1981 Negative values of t count from the end of the path. The absolute
1982 value of t must be smaller or equal to the number of segments in
1983 the normpath, otherwise None is returned.
1984 At discontinuities in the path, the limit from below is returned
1987 result
= self
.at_pt(t
)
1989 return unit
.t_pt(result
[0]), unit
.t_pt(result
[1])
1995 for sp
in self
.subpaths
:
2004 """return coordinates of first point of first subpath in path (in pts)"""
2006 return self
.subpaths
[0].begin_pt()
2011 """return coordinates of first point of first subpath in path"""
2012 result
= self
.begin_pt()
2014 return unit
.t_pt(result
[0]), unit
.t_pt(result
[1])
2019 """return coordinates of last point of last subpath in path (in pts)"""
2021 return self
.subpaths
[-1].end_pt()
2026 """return coordinates of last point of last subpath in path"""
2027 result
= self
.end_pt()
2029 return unit
.t_pt(result
[0]), unit
.t_pt(result
[1])
2033 def glue(self
, other
):
2034 if not self
.subpaths
:
2035 raise PathException("cannot glue to end of empty path")
2036 if self
.subpaths
[-1].closed
:
2037 raise PathException("cannot glue to end of closed sub path")
2038 other
= normpath(other
)
2039 if not other
.subpaths
:
2040 raise PathException("cannot glue empty path")
2042 self
.subpaths
[-1].normpathels
+= other
.subpaths
[0].normpathels
2043 self
.subpaths
+= other
.subpaths
[1:]
2046 def intersect(self
, other
, epsilon
=1e-5):
2047 """intersect self with other path
2049 returns a tuple of lists consisting of the parameter values
2050 of the intersection points of the corresponding normpath
2053 if not isinstance(other
, normpath
):
2054 other
= normpath(other
)
2056 # here we build up the result
2057 intersections
= ([], [])
2059 # Intersect all subpaths of self with the subpaths of
2060 # other. Here, st_a, st_b are the parameter values
2061 # corresponding to the first point of the subpaths sp_a and
2062 # sp_b, respectively.
2064 for sp_a
in self
.subpaths
:
2066 for sp_b
in other
.subpaths
:
2067 for intersection
in zip(*sp_a
.intersect(sp_b
, epsilon
)):
2068 intersections
[0].append(intersection
[0]+st_a
)
2069 intersections
[1].append(intersection
[1]+st_b
)
2070 st_b
+= sp_b
.range()
2071 st_a
+= sp_a
.range()
2072 return intersections
2074 def lentopar(self
, lengths
, epsilon
=1e-5):
2075 """returns the parameter value(s) matching given length(s)"""
2077 # split the list of lengths apart for positive and negative values
2078 rests
= [[],[]] # first the positive then the negative lengths
2079 remap
= [] # for resorting the rests into lengths
2080 for length
in helper
.ensuresequence(lengths
):
2081 length
= unit
.topt(length
)
2083 rests
[0].append(length
)
2084 remap
.append([0,len(rests
[0])-1])
2086 rests
[1].append(-length
)
2087 remap
.append([1,len(rests
[1])-1])
2089 allparams
= [[0]*len(rests
[0]),[0]*len(rests
[1])]
2091 # go through the positive lengths
2092 for sp
in self
.subpaths
:
2093 # we need arclength for knowing when all the parameters are done
2094 # for lengths that are done: rests[i] is negative
2095 # sp._lentopar has to ignore such lengths
2096 params
, arclength
= sp
._lentopar
_pt
(rests
[0], epsilon
)
2097 finis
= 0 # number of lengths that are done
2098 for i
in range(len(rests
[0])):
2099 if rests
[0][i
] >= 0:
2100 rests
[0][i
] -= arclength
2101 allparams
[0][i
] += params
[i
]
2104 if finis
== len(rests
[0]): break
2106 # go through the negative lengths
2107 for sp
in self
.reversed().subpaths
:
2108 params
, arclength
= sp
._lentopar
_pt
(rests
[1], epsilon
)
2110 for i
in range(len(rests
[1])):
2111 if rests
[1][i
] >= 0:
2112 rests
[1][i
] -= arclength
2113 allparams
[1][i
] -= params
[i
]
2116 if finis
==len(rests
[1]): break
2118 # re-sort the positive and negative values into one list
2119 allparams
= [allparams
[p
[0]][p
[1]] for p
in remap
]
2120 if not helper
.issequence(lengths
): allparams
= allparams
[0]
2125 """return maximal value for parameter value t"""
2126 return sum([sp
.range() for sp
in self
.subpaths
])
2130 self
.subpaths
.reverse()
2131 for sp
in self
.subpaths
:
2135 """return reversed path"""
2136 nnormpath
= normpath()
2137 for i
in range(len(self
.subpaths
)):
2138 nnormpath
.subpaths
.append(self
.subpaths
[-(i
+1)].reversed())
2141 def split(self
, parameters
):
2142 """split path at parameter values parameters
2144 Note that the parameter list has to be sorted.
2148 # XXX support negative arguments
2149 # XXX None at the end of last subpath is not handled correctly
2151 # check whether parameter list is really sorted
2152 sortedparams
= list(parameters
)
2154 if sortedparams
!=list(parameters
):
2155 raise ValueError("split parameters have to be sorted")
2157 # we build up this list of normpaths
2160 # the currently built up normpath
2164 for subpath
in self
.subpaths
:
2165 tf
= t0
+subpath
.range()
2166 if parameters
and tf
>=parameters
[0]:
2167 # split this subpath
2168 # determine the relevant splitting parameters
2169 for i
in range(len(parameters
)):
2170 if parameters
[i
]>tf
: break
2174 splitsubpaths
= subpath
.split([x
-t0
for x
in parameters
[:i
]])
2175 # handle first element, which may be None, separately
2176 if splitsubpaths
[0] is None:
2182 splitsubpaths
.pop(0)
2184 for sp
in splitsubpaths
[:-1]:
2185 np
.subpaths
.append(sp
)
2189 # handle last element which may be None, separately
2191 if splitsubpaths
[-1] is None:
2196 np
.subpaths
.append(splitsubpaths
[-1])
2198 parameters
= parameters
[i
:]
2200 # append whole subpath to current normpath
2201 np
.subpaths
.append(subpath
)
2207 # mark split at the end of the normsubpath
2212 def tangent(self
, t
, length
=None):
2213 """return tangent vector of path at parameter value t
2215 Negative values of t count from the end of the path. The absolute
2216 value of t must be smaller or equal to the number of segments in
2217 the normpath, otherwise None is returned.
2218 At discontinuities in the path, the limit from below is returned
2220 if length is not None, the tangent vector will be scaled to
2224 result
= self
._findsubpath
(t
)
2226 tvec
= result
[0].tangent(result
[1])
2227 tlen
= tvec
.arclength_pt()
2228 if length
is None or tlen
==0:
2231 sfactor
= unit
.topt(length
)/tlen
2232 return tvec
.transformed(trafo
.scale(sfactor
, sfactor
, *tvec
.begin()))
2236 def transform(self
, trafo
):
2237 """transform path according to trafo"""
2238 for sp
in self
.subpaths
:
2241 def transformed(self
, trafo
):
2242 """return path transformed according to trafo"""
2243 nnormpath
= normpath()
2244 for sp
in self
.subpaths
:
2245 nnormpath
.subpaths
.append(sp
.transformed(trafo
))
2248 def outputPS(self
, file):
2249 for sp
in self
.subpaths
:
2252 def outputPDF(self
, file):
2253 for sp
in self
.subpaths
:
2256 ################################################################################
2257 # some special kinds of path, again in two variants
2258 ################################################################################
2260 class line_pt(path
):
2262 """straight line from (x1, y1) to (x2, y2) (coordinates in pts)"""
2264 def __init__(self
, x1
, y1
, x2
, y2
):
2265 path
.__init
__(self
, moveto_pt(x1
, y1
), lineto_pt(x2
, y2
))
2268 class curve_pt(path
):
2270 """Bezier curve with control points (x0, y1),..., (x3, y3)
2271 (coordinates in pts)"""
2273 def __init__(self
, x0
, y0
, x1
, y1
, x2
, y2
, x3
, y3
):
2276 curveto_pt(x1
, y1
, x2
, y2
, x3
, y3
))
2279 class rect_pt(path
):
2281 """rectangle at position (x,y) with width and height (coordinates in pts)"""
2283 def __init__(self
, x
, y
, width
, height
):
2284 path
.__init
__(self
, moveto_pt(x
, y
),
2285 lineto_pt(x
+width
, y
),
2286 lineto_pt(x
+width
, y
+height
),
2287 lineto_pt(x
, y
+height
),
2291 class circle_pt(path
):
2293 """circle with center (x,y) and radius"""
2295 def __init__(self
, x
, y
, radius
):
2296 path
.__init
__(self
, arc_pt(x
, y
, radius
, 0, 360),
2300 class line(line_pt
):
2302 """straight line from (x1, y1) to (x2, y2)"""
2304 def __init__(self
, x1
, y1
, x2
, y2
):
2305 line_pt
.__init
__(self
,
2306 unit
.topt(x1
), unit
.topt(y1
),
2307 unit
.topt(x2
), unit
.topt(y2
)
2311 class curve(curve_pt
):
2313 """Bezier curve with control points (x0, y1),..., (x3, y3)"""
2315 def __init__(self
, x0
, y0
, x1
, y1
, x2
, y2
, x3
, y3
):
2316 curve_pt
.__init
__(self
,
2317 unit
.topt(x0
), unit
.topt(y0
),
2318 unit
.topt(x1
), unit
.topt(y1
),
2319 unit
.topt(x2
), unit
.topt(y2
),
2320 unit
.topt(x3
), unit
.topt(y3
)
2324 class rect(rect_pt
):
2326 """rectangle at position (x,y) with width and height"""
2328 def __init__(self
, x
, y
, width
, height
):
2329 rect_pt
.__init
__(self
,
2330 unit
.topt(x
), unit
.topt(y
),
2331 unit
.topt(width
), unit
.topt(height
))
2334 class circle(circle_pt
):
2336 """circle with center (x,y) and radius"""
2338 def __init__(self
, x
, y
, radius
):
2339 circle_pt
.__init
__(self
,
2340 unit
.topt(x
), unit
.topt(y
),