2 # -*- coding: ISO-8859-1 -*-
5 # Copyright (C) 2002, 2003 Jörg Lehmann <joergl@users.sourceforge.net>
6 # Copyright (C) 2003 Michael Schindler <m-schindler@users.sourceforge.net>
7 # Copyright (C) 2002, 2003 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
26 # - should we improve on the arc length -> arg parametrization routine or
27 # should we at least factor it out?
28 # - How should we handle the passing of stroke and fill styles to
29 # arrows? Calls, new instances, ...?
32 import attr
, base
, canvas
, helper
, path
, trafo
, unit
38 class decoratedpath(base
.PSCmd
):
41 The main purpose of this class is during the drawing
42 (stroking/filling) of a path. It collects attributes for the
43 stroke and/or fill operations.
47 path
, strokepath
=None, fillpath
=None,
48 styles
=None, strokestyles
=None, fillstyles
=None,
53 # path to be stroked or filled (or None)
54 self
.strokepath
= strokepath
55 self
.fillpath
= fillpath
57 # global style for stroking and filling and subdps
58 self
.styles
= helper
.ensurelist(styles
)
60 # styles which apply only for stroking and filling
61 self
.strokestyles
= helper
.ensurelist(strokestyles
)
62 self
.fillstyles
= helper
.ensurelist(fillstyles
)
64 # additional elements of the path, e.g., arrowheads,
65 # which are by themselves decoratedpaths
66 self
.subdps
= helper
.ensurelist(subdps
)
68 def addsubdp(self
, subdp
):
69 """add a further decorated path to the list of subdps"""
70 self
.subdps
.append(subdp
)
73 return reduce(lambda x
,y
: x
+y
.bbox(),
79 for style
in list(self
.styles
) + list(self
.fillstyles
) + list(self
.strokestyles
):
80 result
.extend(style
.prolog())
83 def write(self
, file):
84 # draw (stroke and/or fill) the decoratedpath on the canvas
85 # while trying to produce an efficient output, e.g., by
86 # not writing one path two times
89 def _writestyles(styles
, file=file):
95 canvas
._gsave
().write(file)
96 _writestyles(self
.styles
)
98 if self
.fillpath
is not None:
99 canvas
._newpath
().write(file)
100 self
.fillpath
.write(file)
102 if self
.strokepath
==self
.fillpath
:
103 # do efficient stroking + filling
104 canvas
._gsave
().write(file)
107 _writestyles(self
.fillstyles
)
109 canvas
._fill
().write(file)
110 canvas
._grestore
().write(file)
112 if self
.strokestyles
:
113 canvas
._gsave
().write(file)
114 _writestyles(self
.strokestyles
)
116 canvas
._stroke
().write(file)
118 if self
.strokestyles
:
119 canvas
._grestore
().write(file)
121 # only fill fillpath - for the moment
123 canvas
._gsave
().write(file)
124 _writestyles(self
.fillstyles
)
126 canvas
._fill
().write(file)
129 canvas
._grestore
().write(file)
131 if self
.strokepath
is not None and self
.strokepath
!=self
.fillpath
:
132 # this is the only relevant case still left
133 # Note that a possible stroking has already been done.
135 if self
.strokestyles
:
137 _writestyles(self
.strokestyles
)
139 canvas
._newpath
().write(file)
140 self
.strokepath
.write(file)
141 canvas
._stroke
().write(file)
143 if self
.strokestyles
:
144 canvas
._grestore
().write(file)
146 if not self
.strokepath
is not None and not self
.fillpath
:
147 raise RuntimeError("Path neither to be stroked nor filled")
149 # now, draw additional subdps
150 for subdp
in self
.subdps
:
153 # restore global styles
155 canvas
._grestore
().write(file)
161 class deco(attr
.attr
):
165 In contrast to path styles, path decorators depend on the concrete
166 path to which they are applied. In particular, they don't make
167 sense without any path and can thus not be used in canvas.set!
171 def decorate(self
, dp
):
172 """apply a style to a given decoratedpath object dp
174 decorate accepts a decoratedpath object dp, applies PathStyle
175 by modifying dp in place and returning the new dp.
181 # stroked and filled: basic decos which stroked and fill,
182 # respectively the path
187 """stroked is a decorator, which draws the outline of the path"""
189 def __init__(self
, *styles
):
190 self
.styles
= list(styles
)
192 def decorate(self
, dp
):
193 dp
.strokepath
= dp
.path
194 dp
.strokestyles
= self
.styles
198 stroked
.clear
= attr
.clearclass(stroked
)
203 """filled is a decorator, which fills the interior of the path"""
205 def __init__(self
, *styles
):
206 self
.styles
= list(styles
)
208 def decorate(self
, dp
):
209 dp
.fillpath
= dp
.path
210 dp
.fillstyles
= self
.styles
214 filled
.clear
= attr
.clearclass(filled
)
217 def _arrowheadtemplatelength(anormpath
, size
):
218 "calculate length of arrowhead template (in parametrisation of anormpath)"
220 tx
, ty
= anormpath
.begin()
222 # obtain arrow template by using path up to first intersection
223 # with circle around tip (as suggested by Michael Schindler)
224 ipar
= anormpath
.intersect(path
.circle(tx
, ty
, size
))
228 # if this doesn't work, use first order conversion from pts to
229 # the bezier curve's parametrization
230 tlen
= unit
.topt(anormpath
.tangent(0).arclength())
232 alen
= unit
.topt(size
)/tlen
233 except ArithmeticError:
234 # take maximum, we can get
235 alen
= anormpath
.range()
236 if alen
> anormpath
.range(): alen
= anormpath
.range()
241 def _arrowhead(anormpath
, size
, angle
, constriction
):
243 """helper routine, which returns an arrowhead for a normpath
245 returns arrowhead at begin of anormpath with size,
246 opening angle and relative constriction
249 alen
= _arrowheadtemplatelength(anormpath
, size
)
250 tx
, ty
= anormpath
.begin()
252 # now we construct the template for our arrow but cutting
253 # the path a the corresponding length
254 arrowtemplate
= anormpath
.split(alen
)[0]
256 # from this template, we construct the two outer curves
258 arrowl
= arrowtemplate
.transformed(trafo
.rotate(-angle
/2.0, tx
, ty
))
259 arrowr
= arrowtemplate
.transformed(trafo
.rotate( angle
/2.0, tx
, ty
))
261 # now come the joining backward parts
263 # arrow with constriction
265 # constriction point (cx, cy) lies on path
266 cx
, cy
= anormpath
.at(constriction
*alen
)
268 arrowcr
= path
.line(*(arrowr
.end()+(cx
,cy
)))
270 arrow
= arrowl
.reversed() << arrowr
<< arrowcr
271 arrow
.append(path
.closepath())
273 # arrow without constriction
274 arrow
= arrowl
.reversed() << arrowr
275 arrow
.append(path
.closepath())
279 # XXX rewrite arrow without using __call__
280 # XXX do not forget arrow.clear
284 """arrow is a decorator which adds an arrow to either side of the path"""
287 position
, size
, angle
=45, constriction
=0.8,
288 styles
=None, strokestyles
=None, fillstyles
=None):
289 self
.position
= position
292 self
.constriction
= constriction
293 self
.styles
= helper
.ensurelist(styles
)
294 self
.strokestyles
= helper
.ensurelist(strokestyles
)
295 self
.fillstyles
= helper
.ensurelist(fillstyles
)
297 def __call__(self
, *styles
):
298 fillstyles
= [ style
for s
in styles
if isinstance(s
, filled
)
299 for style
in s
.styles
]
301 strokestyles
= [ style
for s
in styles
if isinstance(s
, stroked
)
302 for style
in s
.styles
]
304 styles
= [ style
for style
in styles
305 if not (isinstance(style
, filled
) or
306 isinstance(style
, stroked
)) ]
308 return arrow(position
=self
.position
,
311 constriction
=self
.constriction
,
313 strokestyles
=strokestyles
,
314 fillstyles
=fillstyles
)
316 def decorate(self
, dp
):
318 # TODO: error, when strokepath is not defined
320 # convert to normpath if necessary
321 if isinstance(dp
.strokepath
, path
.normpath
):
322 anormpath
=dp
.strokepath
324 anormpath
=path
.normpath(dp
.path
)
327 anormpath
=anormpath
.reversed()
329 ahead
= _arrowhead(anormpath
, self
.size
, self
.angle
, self
.constriction
)
331 dp
.addsubdp(decoratedpath(ahead
,
332 strokepath
=ahead
, fillpath
=ahead
,
334 strokestyles
=self
.strokestyles
,
335 fillstyles
=self
.fillstyles
))
337 alen
= _arrowheadtemplatelength(anormpath
, self
.size
)
339 if self
.constriction
:
340 ilen
= alen
*self
.constriction
344 # correct somewhat for rotation of arrow segments
345 ilen
= ilen
*math
.cos(math
.pi
*self
.angle
/360.0)
347 # this is the rest of the path, we have to draw
348 anormpath
= anormpath
.split(ilen
)[1]
350 # go back to original orientation, if necessary
352 anormpath
=anormpath
.reversed()
354 # set the new (shortened) strokepath
355 dp
.strokepath
=anormpath
362 """arrow at begin of path"""
364 def __init__(self
, size
, angle
=45, constriction
=0.8,
365 styles
=None, strokestyles
=None, fillstyles
=None):
370 constriction
=constriction
,
372 strokestyles
=strokestyles
,
373 fillstyles
=fillstyles
)
377 barrow
.SMALL
= barrow(_base
/math
.sqrt(64))
378 barrow
.SMALl
= barrow(_base
/math
.sqrt(32))
379 barrow
.SMAll
= barrow(_base
/math
.sqrt(16))
380 barrow
.SMall
= barrow(_base
/math
.sqrt(8))
381 barrow
.Small
= barrow(_base
/math
.sqrt(4))
382 barrow
.small
= barrow(_base
/math
.sqrt(2))
383 barrow
.normal
= barrow(_base
)
384 barrow
.large
= barrow(_base
*math
.sqrt(2))
385 barrow
.Large
= barrow(_base
*math
.sqrt(4))
386 barrow
.LArge
= barrow(_base
*math
.sqrt(8))
387 barrow
.LARge
= barrow(_base
*math
.sqrt(16))
388 barrow
.LARGe
= barrow(_base
*math
.sqrt(32))
389 barrow
.LARGE
= barrow(_base
*math
.sqrt(64))
394 """arrow at end of path"""
396 def __init__(self
, size
, angle
=45, constriction
=0.8,
397 styles
=[], strokestyles
=[], fillstyles
=[]):
402 constriction
=constriction
,
404 strokestyles
=strokestyles
,
405 fillstyles
=fillstyles
)
408 earrow
.SMALL
= earrow(_base
/math
.sqrt(64))
409 earrow
.SMALl
= earrow(_base
/math
.sqrt(32))
410 earrow
.SMAll
= earrow(_base
/math
.sqrt(16))
411 earrow
.SMall
= earrow(_base
/math
.sqrt(8))
412 earrow
.Small
= earrow(_base
/math
.sqrt(4))
413 earrow
.small
= earrow(_base
/math
.sqrt(2))
414 earrow
.normal
= earrow(_base
)
415 earrow
.large
= earrow(_base
*math
.sqrt(2))
416 earrow
.Large
= earrow(_base
*math
.sqrt(4))
417 earrow
.LArge
= earrow(_base
*math
.sqrt(8))
418 earrow
.LARge
= earrow(_base
*math
.sqrt(16))
419 earrow
.LARGe
= earrow(_base
*math
.sqrt(32))
420 earrow
.LARGE
= earrow(_base
*math
.sqrt(64))