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?
30 import attr
, base
, canvas
, helper
, path
, style
, trafo
, unit
36 class decoratedpath(base
.PSCmd
):
39 The main purpose of this class is during the drawing
40 (stroking/filling) of a path. It collects attributes for the
41 stroke and/or fill operations.
44 def __init__(self
, path
, strokepath
=None, fillpath
=None,
45 styles
=None, strokestyles
=None, fillstyles
=None,
50 # path to be stroked or filled (or None)
51 self
.strokepath
= strokepath
52 self
.fillpath
= fillpath
54 # global style for stroking and filling and subdps
55 self
.styles
= helper
.ensurelist(styles
)
57 # styles which apply only for stroking and filling
58 self
.strokestyles
= helper
.ensurelist(strokestyles
)
59 self
.fillstyles
= helper
.ensurelist(fillstyles
)
61 # the canvas can contain additional elements of the path, e.g.,
64 self
.subcanvas
= canvas
.canvas()
66 self
.subcanvas
= subcanvas
70 return self
.subcanvas
.bbox() + self
.path
.bbox()
74 for style
in list(self
.styles
) + list(self
.fillstyles
) + list(self
.strokestyles
):
75 result
.extend(style
.prolog())
76 result
.extend(self
.subcanvas
.prolog())
79 def write(self
, file):
80 # draw (stroke and/or fill) the decoratedpath on the canvas
81 # while trying to produce an efficient output, e.g., by
82 # not writing one path two times
85 def _writestyles(styles
, file=file):
91 canvas
._gsave
().write(file)
92 _writestyles(self
.styles
)
94 if self
.fillpath
is not None:
95 canvas
._newpath
().write(file)
96 self
.fillpath
.write(file)
98 if self
.strokepath
==self
.fillpath
:
99 # do efficient stroking + filling
100 canvas
._gsave
().write(file)
103 _writestyles(self
.fillstyles
)
105 canvas
._fill
().write(file)
106 canvas
._grestore
().write(file)
108 if self
.strokestyles
:
109 canvas
._gsave
().write(file)
110 _writestyles(self
.strokestyles
)
112 canvas
._stroke
().write(file)
114 if self
.strokestyles
:
115 canvas
._grestore
().write(file)
117 # only fill fillpath - for the moment
119 canvas
._gsave
().write(file)
120 _writestyles(self
.fillstyles
)
122 canvas
._fill
().write(file)
125 canvas
._grestore
().write(file)
127 if self
.strokepath
is not None and self
.strokepath
!=self
.fillpath
:
128 # this is the only relevant case still left
129 # Note that a possible stroking has already been done.
131 if self
.strokestyles
:
132 canvas
._gsave
().write(file)
133 _writestyles(self
.strokestyles
)
135 canvas
._newpath
().write(file)
136 self
.strokepath
.write(file)
137 canvas
._stroke
().write(file)
139 if self
.strokestyles
:
140 canvas
._grestore
().write(file)
142 if not self
.strokepath
is not None and not self
.fillpath
:
143 raise RuntimeError("Path neither to be stroked nor filled")
145 # now, draw additional elements of decoratedpath
146 self
.subcanvas
.write(file)
148 # restore global styles
150 canvas
._grestore
().write(file)
156 class deco(attr
.attr
):
160 In contrast to path styles, path decorators depend on the concrete
161 path to which they are applied. In particular, they don't make
162 sense without any path and can thus not be used in canvas.set!
166 def decorate(self
, dp
):
167 """apply a style to a given decoratedpath object dp
169 decorate accepts a decoratedpath object dp, applies PathStyle
170 by modifying dp in place and returning the new dp.
176 # stroked and filled: basic decos which stroked and fill,
177 # respectively the path
182 """stroked is a decorator, which draws the outline of the path"""
184 def __init__(self
, *styles
):
185 self
.styles
= list(styles
)
187 def decorate(self
, dp
):
188 dp
.strokepath
= dp
.path
189 dp
.strokestyles
= self
.styles
192 stroked
.clear
= attr
.clearclass(stroked
)
197 """filled is a decorator, which fills the interior of the path"""
199 def __init__(self
, *styles
):
200 self
.styles
= list(styles
)
202 def decorate(self
, dp
):
203 dp
.fillpath
= dp
.path
204 dp
.fillstyles
= self
.styles
207 filled
.clear
= attr
.clearclass(filled
)
213 # two helper functions which construct the arrowhead and return its size, respectively
215 def _arrowheadtemplatelength(anormpath
, size
):
216 "calculate length of arrowhead template (in parametrisation of anormpath)"
218 tx
, ty
= anormpath
.begin()
220 # obtain arrow template by using path up to first intersection
221 # with circle around tip (as suggested by Michael Schindler)
222 ipar
= anormpath
.intersect(path
.circle(tx
, ty
, size
))
226 # if this doesn't work, use first order conversion from pts to
227 # the bezier curve's parametrization
228 tlen
= unit
.topt(anormpath
.tangent(0).arclength())
230 alen
= unit
.topt(size
)/tlen
231 except ArithmeticError:
232 # take maximum, we can get
233 alen
= anormpath
.range()
234 if alen
> anormpath
.range(): alen
= anormpath
.range()
239 def _arrowhead(anormpath
, size
, angle
, constriction
):
241 """helper routine, which returns an arrowhead for a normpath
243 returns arrowhead at begin of anormpath with size,
244 opening angle and relative constriction
247 alen
= _arrowheadtemplatelength(anormpath
, size
)
248 tx
, ty
= anormpath
.begin()
250 # now we construct the template for our arrow but cutting
251 # the path a the corresponding length
252 arrowtemplate
= anormpath
.split(alen
)[0]
254 # from this template, we construct the two outer curves
256 arrowl
= arrowtemplate
.transformed(trafo
.rotate(-angle
/2.0, tx
, ty
))
257 arrowr
= arrowtemplate
.transformed(trafo
.rotate( angle
/2.0, tx
, ty
))
259 # now come the joining backward parts
261 # arrow with constriction
263 # constriction point (cx, cy) lies on path
264 cx
, cy
= anormpath
.at(constriction
*alen
)
266 arrowcr
= path
.line(*(arrowr
.end()+(cx
,cy
)))
268 arrow
= arrowl
.reversed() << arrowr
<< arrowcr
269 arrow
.append(path
.closepath())
271 # arrow without constriction
272 arrow
= arrowl
.reversed() << arrowr
273 arrow
.append(path
.closepath())
282 """arrow is a decorator which adds an arrow to either side of the path"""
284 def __init__(self
, position
=0, size
=_base
, attrs
=[], angle
=45, constriction
=0.8):
285 self
.position
= position
286 self
.size
= unit
.length(size
, default_type
="v")
288 self
.constriction
= constriction
289 self
.attrs
= [stroked(), filled()] + helper
.ensurelist(attrs
)
290 attr
.mergeattrs(self
.attrs
)
291 attr
.checkattrs(self
.attrs
, [deco
, style
.fillstyle
, style
.strokestyle
])
293 def decorate(self
, dp
):
294 # XXX raise exception error, when strokepath is not defined
296 # convert to normpath if necessary
297 if isinstance(dp
.strokepath
, path
.normpath
):
298 anormpath
= dp
.strokepath
300 anormpath
= path
.normpath(dp
.path
)
302 anormpath
= anormpath
.reversed()
304 # add arrowhead to decoratedpath
305 dp
.subcanvas
.draw(_arrowhead(anormpath
, self
.size
, self
.angle
, self
.constriction
),
308 # calculate new strokepath
309 alen
= _arrowheadtemplatelength(anormpath
, self
.size
)
310 if self
.constriction
:
311 ilen
= alen
*self
.constriction
315 # correct somewhat for rotation of arrow segments
316 ilen
= ilen
*math
.cos(math
.pi
*self
.angle
/360.0)
318 # this is the rest of the path, we have to draw
319 anormpath
= anormpath
.split(ilen
)[1]
321 # go back to original orientation, if necessary
323 anormpath
=anormpath
.reversed()
325 # set the new (shortened) strokepath
326 dp
.strokepath
=anormpath
330 arrow
.clear
= attr
.clearclass(arrow
)
335 """arrow at begin of path"""
337 def __init__(self
, *args
, **kwargs
):
338 arrow
.__init
__(self
, 0, *args
, **kwargs
)
340 class _barrow_SMALL(barrow
):
341 def __init__(self
, *args
, **kwargs
):
342 barrow
.__init
__(self
, _base
/math
.sqrt(64), *args
, **kwargs
)
343 barrow
.SMALL
= _barrow_SMALL
345 class _barrow_SMALl(barrow
):
346 def __init__(self
, *args
, **kwargs
):
347 barrow
.__init
__(self
, _base
/math
.sqrt(32), *args
, **kwargs
)
348 barrow
.SMALl
= _barrow_SMALl
350 class _barrow_SMAll(barrow
):
351 def __init__(self
, *args
, **kwargs
):
352 barrow
.__init
__(self
, _base
/math
.sqrt(16), *args
, **kwargs
)
353 barrow
.SMAll
= _barrow_SMAll
355 class _barrow_SMall(barrow
):
356 def __init__(self
, *args
, **kwargs
):
357 barrow
.__init
__(self
, _base
/math
.sqrt(8), *args
, **kwargs
)
358 barrow
.SMall
= _barrow_SMall
360 class _barrow_Small(barrow
):
361 def __init__(self
, *args
, **kwargs
):
362 barrow
.__init
__(self
, _base
/math
.sqrt(4), *args
, **kwargs
)
363 barrow
.Small
= _barrow_Small
365 class _barrow_small(barrow
):
366 def __init__(self
, *args
, **kwargs
):
367 barrow
.__init
__(self
, _base
/math
.sqrt(2), *args
, **kwargs
)
368 barrow
.small
= _barrow_small
370 class _barrow_normal(barrow
):
371 def __init__(self
, *args
, **kwargs
):
372 barrow
.__init
__(self
, _base
, *args
, **kwargs
)
373 barrow
.normal
= _barrow_normal
375 class _barrow_large(barrow
):
376 def __init__(self
, *args
, **kwargs
):
377 barrow
.__init
__(self
, _base
*math
.sqrt(2), *args
, **kwargs
)
378 barrow
.large
= _barrow_large
380 class _barrow_Large(barrow
):
381 def __init__(self
, *args
, **kwargs
):
382 barrow
.__init
__(self
, _base
*math
.sqrt(4), *args
, **kwargs
)
383 barrow
.Large
= _barrow_Large
385 class _barrow_LArge(barrow
):
386 def __init__(self
, *args
, **kwargs
):
387 barrow
.__init
__(self
, _base
*math
.sqrt(8), *args
, **kwargs
)
388 barrow
.LArge
= _barrow_LArge
390 class _barrow_LARge(barrow
):
391 def __init__(self
, *args
, **kwargs
):
392 barrow
.__init
__(self
, _base
*math
.sqrt(16), *args
, **kwargs
)
393 barrow
.LARge
= _barrow_LARge
395 class _barrow_LARGe(barrow
):
396 def __init__(self
, *args
, **kwargs
):
397 barrow
.__init
__(self
, _base
*math
.sqrt(32), *args
, **kwargs
)
398 barrow
.LARGe
= _barrow_LARGe
400 class _barrow_LARGE(barrow
):
401 def __init__(self
, *args
, **kwargs
):
402 barrow
.__init
__(self
, _base
*math
.sqrt(64), *args
, **kwargs
)
403 barrow
.LARGE
= _barrow_LARGE
408 """arrow at end of path"""
410 def __init__(self
, *args
, **kwargs
):
411 arrow
.__init
__(self
, 1, *args
, **kwargs
)
413 class _earrow_SMALL(earrow
):
414 def __init__(self
, *args
, **kwargs
):
415 earrow
.__init
__(self
, _base
/math
.sqrt(64), *args
, **kwargs
)
416 earrow
.SMALL
= _earrow_SMALL
418 class _earrow_SMALl(earrow
):
419 def __init__(self
, *args
, **kwargs
):
420 earrow
.__init
__(self
, _base
/math
.sqrt(32), *args
, **kwargs
)
421 earrow
.SMALl
= _earrow_SMALl
423 class _earrow_SMAll(earrow
):
424 def __init__(self
, *args
, **kwargs
):
425 earrow
.__init
__(self
, _base
/math
.sqrt(16), *args
, **kwargs
)
426 earrow
.SMAll
= _earrow_SMAll
428 class _earrow_SMall(earrow
):
429 def __init__(self
, *args
, **kwargs
):
430 earrow
.__init
__(self
, _base
/math
.sqrt(8), *args
, **kwargs
)
431 earrow
.SMall
= _earrow_SMall
433 class _earrow_Small(earrow
):
434 def __init__(self
, *args
, **kwargs
):
435 earrow
.__init
__(self
, _base
/math
.sqrt(4), *args
, **kwargs
)
436 earrow
.Small
= _earrow_Small
438 class _earrow_small(earrow
):
439 def __init__(self
, *args
, **kwargs
):
440 earrow
.__init
__(self
, _base
/math
.sqrt(2), *args
, **kwargs
)
441 earrow
.small
= _earrow_small
443 class _earrow_normal(earrow
):
444 def __init__(self
, *args
, **kwargs
):
445 earrow
.__init
__(self
, _base
, *args
, **kwargs
)
446 earrow
.normal
= _earrow_normal
448 class _earrow_large(earrow
):
449 def __init__(self
, *args
, **kwargs
):
450 earrow
.__init
__(self
, _base
*math
.sqrt(2), *args
, **kwargs
)
451 earrow
.large
= _earrow_large
453 class _earrow_Large(earrow
):
454 def __init__(self
, *args
, **kwargs
):
455 earrow
.__init
__(self
, _base
*math
.sqrt(4), *args
, **kwargs
)
456 earrow
.Large
= _earrow_Large
458 class _earrow_LArge(earrow
):
459 def __init__(self
, *args
, **kwargs
):
460 earrow
.__init
__(self
, _base
*math
.sqrt(8), *args
, **kwargs
)
461 earrow
.LArge
= _earrow_LArge
463 class _earrow_LARge(earrow
):
464 def __init__(self
, *args
, **kwargs
):
465 earrow
.__init
__(self
, _base
*math
.sqrt(16), *args
, **kwargs
)
466 earrow
.LARge
= _earrow_LARge
468 class _earrow_LARGe(earrow
):
469 def __init__(self
, *args
, **kwargs
):
470 earrow
.__init
__(self
, _base
*math
.sqrt(32), *args
, **kwargs
)
471 earrow
.LARGe
= _earrow_LARGe
473 class _earrow_LARGE(earrow
):
474 def __init__(self
, *args
, **kwargs
):
475 earrow
.__init
__(self
, _base
*math
.sqrt(64), *args
, **kwargs
)
476 earrow
.LARGE
= _earrow_LARGE