1 # -*- encoding: utf-8 -*-
4 # Copyright (C) 2002-2011 Jörg Lehmann <joergl@users.sourceforge.net>
5 # Copyright (C) 2003-2011 Michael Schindler <m-schindler@users.sourceforge.net>
6 # Copyright (C) 2002-2013 André Wobst <wobsta@users.sourceforge.net>
8 # This file is part of PyX (http://pyx.sourceforge.net/).
10 # PyX is free software; you can redistribute it and/or modify
11 # it under the terms of the GNU General Public License as published by
12 # the Free Software Foundation; either version 2 of the License, or
13 # (at your option) any later version.
15 # PyX is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 # GNU General Public License for more details.
20 # You should have received a copy of the GNU General Public License
21 # along with PyX; if not, write to the Free Software
22 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
25 # - should we improve on the arc length -> arg parametrization routine or
26 # should we at least factor it out?
29 from . import attr
, baseclasses
, canvas
, color
, path
, normpath
, style
, trafo
, unit
, deformer
37 class decoratedpath(baseclasses
.canvasitem
):
40 The main purpose of this class is during the drawing
41 (stroking/filling) of a path. It collects attributes for the
42 stroke and/or fill operations.
45 def __init__(self
, path
, strokepath
=None, fillpath
=None,
46 styles
=None, strokestyles
=None, fillstyles
=None,
51 # global style for stroking and filling and subdps
54 # styles which apply only for stroking and filling
55 self
.strokestyles
= strokestyles
56 self
.fillstyles
= fillstyles
58 # the decoratedpath can contain additional elements of the
59 # path (ornaments), e.g., arrowheads.
61 self
.ornaments
= canvas
.canvas()
63 self
.ornaments
= ornaments
65 self
.nostrokeranges
= None
67 def ensurenormpath(self
):
68 """convert self.path into a normpath"""
69 assert self
.nostrokeranges
is None or isinstance(self
.path
, path
.normpath
), "you don't understand what you are doing"
70 self
.path
= self
.path
.normpath()
72 def excluderange(self
, begin
, end
):
73 assert isinstance(self
.path
, path
.normpath
), "you don't understand what this is about"
74 if self
.nostrokeranges
is None:
75 self
.nostrokeranges
= [(begin
, end
)]
78 while ibegin
< len(self
.nostrokeranges
) and self
.nostrokeranges
[ibegin
][1] < begin
:
81 if ibegin
== len(self
.nostrokeranges
):
82 self
.nostrokeranges
.append((begin
, end
))
85 iend
= len(self
.nostrokeranges
) - 1
86 while 0 <= iend
and end
< self
.nostrokeranges
[iend
][0]:
90 self
.nostrokeranges
.insert(0, (begin
, end
))
93 if self
.nostrokeranges
[ibegin
][0] < begin
:
94 begin
= self
.nostrokeranges
[ibegin
][0]
95 if end
< self
.nostrokeranges
[iend
][1]:
96 end
= self
.nostrokeranges
[iend
][1]
98 self
.nostrokeranges
[ibegin
:iend
+1] = [(begin
, end
)]
101 pathbbox
= self
.path
.bbox()
102 ornamentsbbox
= self
.ornaments
.bbox()
103 if ornamentsbbox
is not None:
104 return ornamentsbbox
+ pathbbox
108 def strokepath(self
):
109 if self
.nostrokeranges
:
111 for begin
, end
in self
.nostrokeranges
:
112 splitlist
.append(begin
)
113 splitlist
.append(end
)
114 split
= self
.path
.split(splitlist
)
115 # XXX properly handle closed paths?
117 for i
in range(2, len(split
), 2):
123 def processPS(self
, file, writer
, context
, registry
, bbox
):
124 # draw (stroke and/or fill) the decoratedpath on the canvas
125 # while trying to produce an efficient output, e.g., by
126 # not writing one path two times
129 def _writestyles(styles
, context
, registry
):
131 style
.processPS(file, writer
, context
, registry
)
133 if self
.strokestyles
is None and self
.fillstyles
is None:
134 if not len(self
.ornaments
):
135 raise RuntimeError("Path neither to be stroked nor filled nor decorated in another way")
136 # just draw additional elements of decoratedpath
137 self
.ornaments
.processPS(file, writer
, context
, registry
, bbox
)
140 strokepath
= self
.strokepath()
143 # apply global styles
145 file.write("gsave\n")
147 _writestyles(self
.styles
, context
, registry
)
149 if self
.fillstyles
is not None:
150 file.write("newpath\n")
151 fillpath
.outputPS(file, writer
)
153 if self
.strokestyles
is not None and strokepath
is fillpath
:
154 # do efficient stroking + filling if respective paths are identical
155 file.write("gsave\n")
158 _writestyles(self
.fillstyles
, context(), registry
)
161 file.write("eofill\n")
164 file.write("grestore\n")
167 if self
.strokestyles
:
168 file.write("gsave\n")
169 _writestyles(self
.strokestyles
, acontext
, registry
)
171 file.write("stroke\n")
172 # take linewidth into account for bbox when stroking a path
173 bbox
+= strokepath
.bbox().enlarged_pt(0.5*acontext
.linewidth_pt
)
175 if self
.strokestyles
:
176 file.write("grestore\n")
178 # only fill fillpath - for the moment
180 file.write("gsave\n")
181 _writestyles(self
.fillstyles
, context(), registry
)
184 file.write("eofill\n")
187 bbox
+= fillpath
.bbox()
190 file.write("grestore\n")
192 if self
.strokestyles
is not None and (strokepath
is not fillpath
or self
.fillstyles
is None):
193 # this is the only relevant case still left
194 # Note that a possible stroking has already been done.
196 if self
.strokestyles
:
197 file.write("gsave\n")
198 _writestyles(self
.strokestyles
, acontext
, registry
)
200 file.write("newpath\n")
201 strokepath
.outputPS(file, writer
)
202 file.write("stroke\n")
203 # take linewidth into account for bbox when stroking a path
204 bbox
+= strokepath
.bbox().enlarged_pt(0.5*acontext
.linewidth_pt
)
206 if self
.strokestyles
:
207 file.write("grestore\n")
209 # now, draw additional elements of decoratedpath
210 self
.ornaments
.processPS(file, writer
, context
, registry
, bbox
)
212 # restore global styles
214 file.write("grestore\n")
216 def processPDF(self
, file, writer
, context
, registry
, bbox
):
217 # draw (stroke and/or fill) the decoratedpath on the canvas
219 def _writestyles(styles
, context
, registry
):
221 style
.processPDF(file, writer
, context
, registry
)
223 def _writestrokestyles(strokestyles
, context
, registry
):
225 for style
in strokestyles
:
226 style
.processPDF(file, writer
, context
, registry
)
229 def _writefillstyles(fillstyles
, context
, registry
):
230 context
.strokeattr
= 0
231 for style
in fillstyles
:
232 style
.processPDF(file, writer
, context
, registry
)
233 context
.strokeattr
= 1
235 if self
.strokestyles
is None and self
.fillstyles
is None:
236 if not len(self
.ornaments
):
237 raise RuntimeError("Path neither to be stroked nor filled nor decorated in another way")
238 # just draw additional elements of decoratedpath
239 self
.ornaments
.processPDF(file, writer
, context
, registry
, bbox
)
242 strokepath
= self
.strokepath()
245 # apply global styles
247 file.write("q\n") # gsave
249 _writestyles(self
.styles
, context
, registry
)
251 if self
.fillstyles
is not None:
252 fillpath
.outputPDF(file, writer
)
254 if self
.strokestyles
is not None and strokepath
is fillpath
:
255 # do efficient stroking + filling
256 file.write("q\n") # gsave
260 _writefillstyles(self
.fillstyles
, acontext
, registry
)
261 if self
.strokestyles
:
262 _writestrokestyles(self
.strokestyles
, acontext
, registry
)
267 file.write("B\n") # both stroke and fill
268 # take linewidth into account for bbox when stroking a path
269 bbox
+= strokepath
.bbox().enlarged_pt(0.5*acontext
.linewidth_pt
)
271 file.write("Q\n") # grestore
273 # only fill fillpath - for the moment
275 file.write("q\n") # gsave
276 _writefillstyles(self
.fillstyles
, context(), registry
)
281 file.write("f\n") # fill
282 bbox
+= fillpath
.bbox()
285 file.write("Q\n") # grestore
287 if self
.strokestyles
is not None and (strokepath
is not fillpath
or self
.fillstyles
is None):
288 # this is the only relevant case still left
289 # Note that a possible stroking has already been done.
292 if self
.strokestyles
:
293 file.write("q\n") # gsave
294 _writestrokestyles(self
.strokestyles
, acontext
, registry
)
296 strokepath
.outputPDF(file, writer
)
297 file.write("S\n") # stroke
298 # take linewidth into account for bbox when stroking a path
299 bbox
+= strokepath
.bbox().enlarged_pt(0.5*acontext
.linewidth_pt
)
301 if self
.strokestyles
:
302 file.write("Q\n") # grestore
304 # now, draw additional elements of decoratedpath
305 self
.ornaments
.processPDF(file, writer
, context
, registry
, bbox
)
307 # restore global styles
309 file.write("Q\n") # grestore
319 In contrast to path styles, path decorators depend on the concrete
320 path to which they are applied. In particular, they don't make
321 sense without any path and can thus not be used in canvas.set!
325 def decorate(self
, dp
, texrunner
):
326 """apply a style to a given decoratedpath object dp
328 decorate accepts a decoratedpath object dp, applies PathStyle
329 by modifying dp in place.
335 # stroked and filled: basic decos which stroked and fill,
336 # respectively the path
339 class _stroked(deco
, attr
.exclusiveattr
):
341 """stroked is a decorator, which draws the outline of the path"""
343 def __init__(self
, styles
=[]):
344 attr
.exclusiveattr
.__init
__(self
, _stroked
)
345 self
.styles
= attr
.mergeattrs(styles
)
346 attr
.checkattrs(self
.styles
, [style
.strokestyle
])
348 def __call__(self
, styles
=[]):
349 # XXX or should we also merge self.styles
350 return _stroked(styles
)
352 def decorate(self
, dp
, texrunner
):
353 if dp
.strokestyles
is not None:
354 raise RuntimeError("Cannot stroke an already stroked path")
355 dp
.strokestyles
= self
.styles
358 stroked
.clear
= attr
.clearclass(_stroked
)
361 class _filled(deco
, attr
.exclusiveattr
):
363 """filled is a decorator, which fills the interior of the path"""
365 def __init__(self
, styles
=[]):
366 attr
.exclusiveattr
.__init
__(self
, _filled
)
367 self
.styles
= attr
.mergeattrs(styles
)
368 attr
.checkattrs(self
.styles
, [style
.fillstyle
])
370 def __call__(self
, styles
=[]):
371 # XXX or should we also merge self.styles
372 return _filled(styles
)
374 def decorate(self
, dp
, texrunner
):
375 if dp
.fillstyles
is not None:
376 raise RuntimeError("Cannot fill an already filled path")
377 dp
.fillstyles
= self
.styles
380 filled
.clear
= attr
.clearclass(_filled
)
386 # helper function which constructs the arrowhead
388 def _arrowhead(anormpath
, arclenfrombegin
, direction
, size
, angle
, constriction
, constrictionlen
):
390 """helper routine, which returns an arrowhead from a given anormpath
392 - arclenfrombegin: position of arrow in arc length from the start of the path
393 - direction: +1 for an arrow pointing along the direction of anormpath or
394 -1 for an arrow pointing opposite to the direction of normpath
395 - size: size of the arrow as arc length
396 - angle. opening angle
397 - constriction: boolean to indicate whether the constriction point is to be taken into account or not
398 - constrictionlen: arc length of constriction. (not used when constriction is false)
401 # arc length and coordinates of tip
402 tx
, ty
= anormpath
.at(arclenfrombegin
)
404 # construct the template for the arrow by cutting the path at the
405 # corresponding length
406 arrowtemplate
= anormpath
.split([arclenfrombegin
, arclenfrombegin
- direction
* size
])[1]
408 # from this template, we construct the two outer curves of the arrow
409 arrowl
= arrowtemplate
.transformed(trafo
.rotate(-angle
/2.0, tx
, ty
))
410 arrowr
= arrowtemplate
.transformed(trafo
.rotate( angle
/2.0, tx
, ty
))
412 # now come the joining backward parts
414 # constriction point (cx, cy) lies on path
415 cx
, cy
= anormpath
.at(arclenfrombegin
- direction
* constrictionlen
)
416 arrowcr
= path
.line(*(arrowr
.atend() + (cx
,cy
)))
417 arrow
= arrowl
.reversed() << arrowr
<< arrowcr
419 arrow
= arrowl
.reversed() << arrowr
426 _base
= 6 * unit
.v_pt
428 class arrow(deco
, attr
.attr
):
430 """arrow is a decorator which adds an arrow to either side of the path"""
432 def __init__(self
, attrs
=[], pos
=1, reversed=0, size
=_base
, angle
=45, constriction
=0.8):
433 self
.attrs
= attr
.mergeattrs([style
.linestyle
.solid
, filled
] + attrs
)
434 attr
.checkattrs(self
.attrs
, [deco
, style
.fillstyle
, style
.strokestyle
])
436 self
.reversed = reversed
439 self
.constriction
= constriction
441 # calculate absolute arc length of constricition
442 # Note that we have to correct this length because the arrowtemplates are rotated
443 # by self.angle/2 to the left and right. Hence, if we want no constriction, i.e., for
444 # self.constriction = 1, we actually have a length which is approximately shorter
445 # by the given geometrical factor.
446 if self
.constriction
is not None:
447 self
.constrictionlen
= self
.size
* self
.constriction
* math
.cos(math
.radians(self
.angle
/2.0))
449 # if we do not want a constriction, i.e. constriction is None, we still
450 # need constrictionlen for cutting the path
451 self
.constrictionlen
= self
.size
* 1 * math
.cos(math
.radians(self
.angle
/2.0))
453 def __call__(self
, attrs
=None, pos
=None, reversed=None, size
=None, angle
=None, constriction
=_marker
):
459 reversed = self
.reversed
464 if constriction
is _marker
:
465 constriction
= self
.constriction
466 return arrow(attrs
=attrs
, pos
=pos
, reversed=reversed, size
=size
, angle
=angle
, constriction
=constriction
)
468 def decorate(self
, dp
, texrunner
):
472 arclenfrombegin
= (1-self
.reversed)*self
.constrictionlen
+ self
.pos
* (anormpath
.arclen() - self
.constrictionlen
)
473 direction
= self
.reversed and -1 or 1
474 arrowhead
= _arrowhead(anormpath
, arclenfrombegin
, direction
, self
.size
, self
.angle
,
475 self
.constriction
is not None, self
.constrictionlen
)
477 # add arrowhead to decoratedpath
478 dp
.ornaments
.draw(arrowhead
, self
.attrs
)
480 # exlude part of the path from stroking when the arrow is strictly at the begin or the end
481 if self
.pos
== 0 and self
.reversed:
482 dp
.excluderange(0, min(self
.size
, self
.constrictionlen
))
483 elif self
.pos
== 1 and not self
.reversed:
484 dp
.excluderange(anormpath
.end() - min(self
.size
, self
.constrictionlen
), anormpath
.end())
486 arrow
.clear
= attr
.clearclass(arrow
)
488 # arrows at begin of path
489 barrow
= arrow(pos
=0, reversed=1)
490 barrow
.SMALL
= barrow(size
=_base
/math
.sqrt(64))
491 barrow
.SMALl
= barrow(size
=_base
/math
.sqrt(32))
492 barrow
.SMAll
= barrow(size
=_base
/math
.sqrt(16))
493 barrow
.SMall
= barrow(size
=_base
/math
.sqrt(8))
494 barrow
.Small
= barrow(size
=_base
/math
.sqrt(4))
495 barrow
.small
= barrow(size
=_base
/math
.sqrt(2))
496 barrow
.normal
= barrow(size
=_base
)
497 barrow
.large
= barrow(size
=_base
*math
.sqrt(2))
498 barrow
.Large
= barrow(size
=_base
*math
.sqrt(4))
499 barrow
.LArge
= barrow(size
=_base
*math
.sqrt(8))
500 barrow
.LARge
= barrow(size
=_base
*math
.sqrt(16))
501 barrow
.LARGe
= barrow(size
=_base
*math
.sqrt(32))
502 barrow
.LARGE
= barrow(size
=_base
*math
.sqrt(64))
504 # arrows at end of path
506 earrow
.SMALL
= earrow(size
=_base
/math
.sqrt(64))
507 earrow
.SMALl
= earrow(size
=_base
/math
.sqrt(32))
508 earrow
.SMAll
= earrow(size
=_base
/math
.sqrt(16))
509 earrow
.SMall
= earrow(size
=_base
/math
.sqrt(8))
510 earrow
.Small
= earrow(size
=_base
/math
.sqrt(4))
511 earrow
.small
= earrow(size
=_base
/math
.sqrt(2))
512 earrow
.normal
= earrow(size
=_base
)
513 earrow
.large
= earrow(size
=_base
*math
.sqrt(2))
514 earrow
.Large
= earrow(size
=_base
*math
.sqrt(4))
515 earrow
.LArge
= earrow(size
=_base
*math
.sqrt(8))
516 earrow
.LARge
= earrow(size
=_base
*math
.sqrt(16))
517 earrow
.LARGe
= earrow(size
=_base
*math
.sqrt(32))
518 earrow
.LARGE
= earrow(size
=_base
*math
.sqrt(64))
521 class text(deco
, attr
.attr
):
522 """a simple text decorator"""
524 def __init__(self
, text
, textattrs
=[], angle
=0, relangle
=None, textdist
=0.2,
525 relarclenpos
=0.5, arclenfrombegin
=None, arclenfromend
=None,
527 if arclenfrombegin
is not None and arclenfromend
is not None:
528 raise ValueError("either set arclenfrombegin or arclenfromend")
530 self
.textattrs
= textattrs
532 self
.relangle
= relangle
533 self
.textdist
= textdist
534 self
.relarclenpos
= relarclenpos
535 self
.arclenfrombegin
= arclenfrombegin
536 self
.arclenfromend
= arclenfromend
537 self
.texrunner
= texrunner
539 def decorate(self
, dp
, texrunner
):
541 texrunner
= self
.texrunner
542 from . import text
as textmodule
543 textattrs
= attr
.mergeattrs([textmodule
.halign
.center
, textmodule
.vshift
.mathaxis
] + self
.textattrs
)
546 if self
.arclenfrombegin
is not None:
547 param
= dp
.path
.begin() + self
.arclenfrombegin
548 elif self
.arclenfromend
is not None:
549 param
= dp
.path
.end() - self
.arclenfromend
551 # relarcpos is used, when neither arcfrombegin nor arcfromend is given
552 param
= self
.relarclenpos
* dp
.path
.arclen()
553 x
, y
= dp
.path
.at(param
)
555 if self
.relangle
is not None:
556 a
= dp
.path
.trafo(param
).apply_pt(math
.cos(self
.relangle
*math
.pi
/180), math
.sin(self
.relangle
*math
.pi
/180))
557 b
= dp
.path
.trafo(param
).apply_pt(0, 0)
558 angle
= math
.atan2(a
[1] - b
[1], a
[0] - b
[0])
560 angle
= self
.angle
*math
.pi
/180
561 t
= texrunner
.text(x
, y
, self
.text
, textattrs
)
562 t
.linealign(self
.textdist
, math
.cos(angle
), math
.sin(angle
))
563 dp
.ornaments
.insert(t
)
565 class curvedtext(deco
, attr
.attr
):
566 """a text decorator for curved text
568 - text: is typeset along the path to which this decorator is applied
569 - relarclenpos: position for the base point of the text (default: 0)
570 - arlenfrombegin, arclenfromend: alternative ways of specifying the position of the base point;
571 use of relarclenpos, arclenfrombegin and arclenfromend is mutually exclusive
572 - textattrs, texrunner: standard text arguments (defaults: [] resp None)
576 # defaulttextattrs = [textmodule.halign.center] # TODO: not possible due to cyclic import issue
578 def __init__(self
, text
, textattrs
=[],
579 relarclenpos
=0.5, arclenfrombegin
=None, arclenfromend
=None,
580 texrunner
=None, exclude
=None):
581 if arclenfrombegin
is not None and arclenfromend
is not None:
582 raise ValueError("either set arclenfrombegin or arclenfromend")
584 self
.textattrs
= textattrs
585 self
.relarclenpos
= relarclenpos
586 self
.arclenfrombegin
= arclenfrombegin
587 self
.arclenfromend
= arclenfromend
588 self
.texrunner
= texrunner
589 self
.exclude
= exclude
591 def decorate(self
, dp
, texrunner
):
593 texrunner
= self
.texrunner
594 from . import text
as textmodule
595 self
.defaulttextattrs
= [textmodule
.halign
.center
]
598 if self
.arclenfrombegin
is not None:
599 textpos
= dp
.path
.begin() + self
.arclenfrombegin
600 elif self
.arclenfromend
is not None:
601 textpos
= dp
.path
.end() - self
.arclenfromend
603 # relarcpos is used if neither arcfrombegin nor arcfromend is given
604 textpos
= self
.relarclenpos
* dp
.path
.arclen()
606 textattrs
= self
.defaulttextattrs
+ self
.textattrs
607 t
= texrunner
.text(0, 0, self
.text
, textattrs
, singlecharmode
=1)
610 # we copy the style from the original textbox and modify the position for each dvicanvas item
611 c
= canvas
.canvas(t
.dvicanvas
.styles
)
612 for item
in t
.dvicanvas
.items
:
614 bbox
= bbox
.transformed(t
.texttrafo
)
616 atrafo
= dp
.path
.trafo(textpos
+x
)
617 c
.insert(item
, [t
.texttrafo
] + [trafo
.translate(-x
, 0)] + [atrafo
])
618 if self
.exclude
is not None:
619 dp
.excluderange(textpos
+bbox
.left()-self
.exclude
, textpos
+bbox
.right()+self
.exclude
)
621 dp
.ornaments
.insert(c
)
624 class shownormpath(deco
, attr
.attr
):
626 default_normline_attrs
= [color
.rgb
.blue
]
627 default_normcurve_attrs
= [color
.rgb
.green
]
628 default_endpoint_attrs
= []
629 default_controlline_attrs
= [color
.rgb
.red
, style
.linestyle
.dashed
]
630 default_controlpoint_attrs
= [color
.rgb
.red
]
632 def __init__(self
, normline_attrs
=[], normcurve_attrs
=[],
633 endpoint_size
=0.05*unit
.v_cm
, endpoint_attrs
=[],
634 controlline_attrs
=[],
635 controlpoint_size
=0.05*unit
.v_cm
, controlpoint_attrs
=[]):
636 self
.normline_attrs
= attr
.refineattrs(normline_attrs
, self
.default_normline_attrs
, [style
.strokestyle
])
637 self
.normcurve_attrs
= attr
.refineattrs(normcurve_attrs
, self
.default_normcurve_attrs
, [style
.strokestyle
])
638 self
.endpoint_size_pt
= unit
.topt(endpoint_size
)
639 self
.endpoint_attrs
= attr
.refineattrs(endpoint_attrs
, self
.default_endpoint_attrs
, [style
.fillstyle
])
640 self
.controlline_attrs
= attr
.refineattrs(controlline_attrs
, self
.default_controlline_attrs
, [style
.strokestyle
])
641 self
.controlpoint_size_pt
= unit
.topt(controlpoint_size
)
642 self
.controlpoint_attrs
= attr
.refineattrs(controlpoint_attrs
, self
.default_controlpoint_attrs
, [style
.fillstyle
])
644 def decorate(self
, dp
, texrunner
):
646 for normsubpath
in dp
.path
.normsubpaths
:
647 for i
, normsubpathitem
in enumerate(normsubpath
.normsubpathitems
):
648 p
= path
.path(path
.moveto_pt(*normsubpathitem
.atbegin_pt()), normsubpathitem
.pathitem())
649 if isinstance(normsubpathitem
, normpath
.normcurve_pt
):
650 if self
.normcurve_attrs
is not None:
651 dp
.ornaments
.stroke(p
, self
.normcurve_attrs
)
653 if self
.normline_attrs
is not None:
654 dp
.ornaments
.stroke(p
, self
.normline_attrs
)
655 for normsubpath
in dp
.path
.normsubpaths
:
656 for i
, normsubpathitem
in enumerate(normsubpath
.normsubpathitems
):
657 if isinstance(normsubpathitem
, normpath
.normcurve_pt
):
658 if self
.controlline_attrs
is not None:
659 dp
.ornaments
.stroke(path
.line_pt(normsubpathitem
.x0_pt
, normsubpathitem
.y0_pt
,
660 normsubpathitem
.x1_pt
, normsubpathitem
.y1_pt
), self
.controlline_attrs
)
661 dp
.ornaments
.stroke(path
.line_pt(normsubpathitem
.x2_pt
, normsubpathitem
.y2_pt
,
662 normsubpathitem
.x3_pt
, normsubpathitem
.y3_pt
), self
.controlline_attrs
)
663 if self
.controlpoint_attrs
is not None:
664 dp
.ornaments
.fill(path
.circle_pt(normsubpathitem
.x1_pt
, normsubpathitem
.y1_pt
, self
.controlpoint_size_pt
), self
.controlpoint_attrs
)
665 dp
.ornaments
.fill(path
.circle_pt(normsubpathitem
.x2_pt
, normsubpathitem
.y2_pt
, self
.controlpoint_size_pt
), self
.controlpoint_attrs
)
666 if self
.endpoint_attrs
is not None:
667 for normsubpath
in dp
.path
.normsubpaths
:
668 for i
, normsubpathitem
in enumerate(normsubpath
.normsubpathitems
):
670 x_pt
, y_pt
= normsubpathitem
.atbegin_pt()
671 dp
.ornaments
.fill(path
.circle_pt(x_pt
, y_pt
, self
.endpoint_size_pt
), self
.endpoint_attrs
)
672 x_pt
, y_pt
= normsubpathitem
.atend_pt()
673 dp
.ornaments
.fill(path
.circle_pt(x_pt
, y_pt
, self
.endpoint_size_pt
), self
.endpoint_attrs
)
676 class linehatched(deco
, attr
.exclusiveattr
, attr
.clearclass
):
677 """draws a pattern with explicit lines
679 This class acts as a drop-in replacement for postscript patterns
680 from the pattern module which are not understood by some printers"""
682 def __init__(self
, dist
, angle
, strokestyles
=[], cross
=0):
683 attr
.clearclass
.__init
__(self
, _filled
)
684 attr
.exclusiveattr
.__init
__(self
, linehatched
)
687 self
.strokestyles
= attr
.mergeattrs([style
.linewidth
.THIN
] + strokestyles
)
688 attr
.checkattrs(self
.strokestyles
, [style
.strokestyle
])
691 def __call__(self
, dist
=None, angle
=None, strokestyles
=None, cross
=None):
696 if strokestyles
is None:
697 strokestyles
= self
.strokestyles
700 return linehatched(dist
, angle
, strokestyles
, cross
)
702 def _decocanvas(self
, angle
, dp
, texrunner
):
704 dist_pt
= unit
.topt(self
.dist
)
706 c
= canvas
.canvas([canvas
.clip(dp
.path
)])
707 llx_pt
, lly_pt
, urx_pt
, ury_pt
= dp
.path
.bbox().highrestuple_pt()
708 center_pt
= 0.5*(llx_pt
+urx_pt
), 0.5*(lly_pt
+ury_pt
)
709 radius_pt
= 0.5*math
.hypot(urx_pt
-llx_pt
, ury_pt
-lly_pt
) + dist_pt
710 n
= int(2*radius_pt
/ dist_pt
) + 1
712 x_pt
= center_pt
[0] - radius_pt
+ i
*dist_pt
713 c
.stroke(path
.line_pt(x_pt
, center_pt
[1]-radius_pt
, x_pt
, center_pt
[1]+radius_pt
),
714 [trafo
.rotate_pt(angle
, center_pt
[0], center_pt
[1])] + self
.strokestyles
)
717 def decorate(self
, dp
, texrunner
):
718 dp
.ornaments
.insert(self
._decocanvas
(self
.angle
, dp
, texrunner
))
720 dp
.ornaments
.insert(self
._decocanvas
(self
.angle
+90, dp
, texrunner
))
722 def merge(self
, attrs
):
723 # act as attr.clearclass and as attr.exclusiveattr at the same time
724 newattrs
= attr
.exclusiveattr
.merge(self
, attrs
)
725 return attr
.clearclass
.merge(self
, newattrs
)
727 linehatched
.clear
= attr
.clearclass(linehatched
)
729 _hatch_base
= 0.1 * unit
.v_cm
731 linehatched0
= linehatched(_hatch_base
, 0)
732 linehatched0
.SMALL
= linehatched0(_hatch_base
/math
.sqrt(64))
733 linehatched0
.SMALL
= linehatched0(_hatch_base
/math
.sqrt(64))
734 linehatched0
.SMALl
= linehatched0(_hatch_base
/math
.sqrt(32))
735 linehatched0
.SMAll
= linehatched0(_hatch_base
/math
.sqrt(16))
736 linehatched0
.SMall
= linehatched0(_hatch_base
/math
.sqrt(8))
737 linehatched0
.Small
= linehatched0(_hatch_base
/math
.sqrt(4))
738 linehatched0
.small
= linehatched0(_hatch_base
/math
.sqrt(2))
739 linehatched0
.normal
= linehatched0(_hatch_base
)
740 linehatched0
.large
= linehatched0(_hatch_base
*math
.sqrt(2))
741 linehatched0
.Large
= linehatched0(_hatch_base
*math
.sqrt(4))
742 linehatched0
.LArge
= linehatched0(_hatch_base
*math
.sqrt(8))
743 linehatched0
.LARge
= linehatched0(_hatch_base
*math
.sqrt(16))
744 linehatched0
.LARGe
= linehatched0(_hatch_base
*math
.sqrt(32))
745 linehatched0
.LARGE
= linehatched0(_hatch_base
*math
.sqrt(64))
747 linehatched45
= linehatched(_hatch_base
, 45)
748 linehatched45
.SMALL
= linehatched45(_hatch_base
/math
.sqrt(64))
749 linehatched45
.SMALl
= linehatched45(_hatch_base
/math
.sqrt(32))
750 linehatched45
.SMAll
= linehatched45(_hatch_base
/math
.sqrt(16))
751 linehatched45
.SMall
= linehatched45(_hatch_base
/math
.sqrt(8))
752 linehatched45
.Small
= linehatched45(_hatch_base
/math
.sqrt(4))
753 linehatched45
.small
= linehatched45(_hatch_base
/math
.sqrt(2))
754 linehatched45
.normal
= linehatched45(_hatch_base
)
755 linehatched45
.large
= linehatched45(_hatch_base
*math
.sqrt(2))
756 linehatched45
.Large
= linehatched45(_hatch_base
*math
.sqrt(4))
757 linehatched45
.LArge
= linehatched45(_hatch_base
*math
.sqrt(8))
758 linehatched45
.LARge
= linehatched45(_hatch_base
*math
.sqrt(16))
759 linehatched45
.LARGe
= linehatched45(_hatch_base
*math
.sqrt(32))
760 linehatched45
.LARGE
= linehatched45(_hatch_base
*math
.sqrt(64))
762 linehatched90
= linehatched(_hatch_base
, 90)
763 linehatched90
.SMALL
= linehatched90(_hatch_base
/math
.sqrt(64))
764 linehatched90
.SMALl
= linehatched90(_hatch_base
/math
.sqrt(32))
765 linehatched90
.SMAll
= linehatched90(_hatch_base
/math
.sqrt(16))
766 linehatched90
.SMall
= linehatched90(_hatch_base
/math
.sqrt(8))
767 linehatched90
.Small
= linehatched90(_hatch_base
/math
.sqrt(4))
768 linehatched90
.small
= linehatched90(_hatch_base
/math
.sqrt(2))
769 linehatched90
.normal
= linehatched90(_hatch_base
)
770 linehatched90
.large
= linehatched90(_hatch_base
*math
.sqrt(2))
771 linehatched90
.Large
= linehatched90(_hatch_base
*math
.sqrt(4))
772 linehatched90
.LArge
= linehatched90(_hatch_base
*math
.sqrt(8))
773 linehatched90
.LARge
= linehatched90(_hatch_base
*math
.sqrt(16))
774 linehatched90
.LARGe
= linehatched90(_hatch_base
*math
.sqrt(32))
775 linehatched90
.LARGE
= linehatched90(_hatch_base
*math
.sqrt(64))
777 linehatched135
= linehatched(_hatch_base
, 135)
778 linehatched135
.SMALL
= linehatched135(_hatch_base
/math
.sqrt(64))
779 linehatched135
.SMALl
= linehatched135(_hatch_base
/math
.sqrt(32))
780 linehatched135
.SMAll
= linehatched135(_hatch_base
/math
.sqrt(16))
781 linehatched135
.SMall
= linehatched135(_hatch_base
/math
.sqrt(8))
782 linehatched135
.Small
= linehatched135(_hatch_base
/math
.sqrt(4))
783 linehatched135
.small
= linehatched135(_hatch_base
/math
.sqrt(2))
784 linehatched135
.normal
= linehatched135(_hatch_base
)
785 linehatched135
.large
= linehatched135(_hatch_base
*math
.sqrt(2))
786 linehatched135
.Large
= linehatched135(_hatch_base
*math
.sqrt(4))
787 linehatched135
.LArge
= linehatched135(_hatch_base
*math
.sqrt(8))
788 linehatched135
.LARge
= linehatched135(_hatch_base
*math
.sqrt(16))
789 linehatched135
.LARGe
= linehatched135(_hatch_base
*math
.sqrt(32))
790 linehatched135
.LARGE
= linehatched135(_hatch_base
*math
.sqrt(64))
792 crosslinehatched0
= linehatched(_hatch_base
, 0, cross
=1)
793 crosslinehatched0
.SMALL
= crosslinehatched0(_hatch_base
/math
.sqrt(64))
794 crosslinehatched0
.SMALl
= crosslinehatched0(_hatch_base
/math
.sqrt(32))
795 crosslinehatched0
.SMAll
= crosslinehatched0(_hatch_base
/math
.sqrt(16))
796 crosslinehatched0
.SMall
= crosslinehatched0(_hatch_base
/math
.sqrt(8))
797 crosslinehatched0
.Small
= crosslinehatched0(_hatch_base
/math
.sqrt(4))
798 crosslinehatched0
.small
= crosslinehatched0(_hatch_base
/math
.sqrt(2))
799 crosslinehatched0
.normal
= crosslinehatched0
800 crosslinehatched0
.large
= crosslinehatched0(_hatch_base
*math
.sqrt(2))
801 crosslinehatched0
.Large
= crosslinehatched0(_hatch_base
*math
.sqrt(4))
802 crosslinehatched0
.LArge
= crosslinehatched0(_hatch_base
*math
.sqrt(8))
803 crosslinehatched0
.LARge
= crosslinehatched0(_hatch_base
*math
.sqrt(16))
804 crosslinehatched0
.LARGe
= crosslinehatched0(_hatch_base
*math
.sqrt(32))
805 crosslinehatched0
.LARGE
= crosslinehatched0(_hatch_base
*math
.sqrt(64))
807 crosslinehatched45
= linehatched(_hatch_base
, 45, cross
=1)
808 crosslinehatched45
.SMALL
= crosslinehatched45(_hatch_base
/math
.sqrt(64))
809 crosslinehatched45
.SMALl
= crosslinehatched45(_hatch_base
/math
.sqrt(32))
810 crosslinehatched45
.SMAll
= crosslinehatched45(_hatch_base
/math
.sqrt(16))
811 crosslinehatched45
.SMall
= crosslinehatched45(_hatch_base
/math
.sqrt(8))
812 crosslinehatched45
.Small
= crosslinehatched45(_hatch_base
/math
.sqrt(4))
813 crosslinehatched45
.small
= crosslinehatched45(_hatch_base
/math
.sqrt(2))
814 crosslinehatched45
.normal
= crosslinehatched45
815 crosslinehatched45
.large
= crosslinehatched45(_hatch_base
*math
.sqrt(2))
816 crosslinehatched45
.Large
= crosslinehatched45(_hatch_base
*math
.sqrt(4))
817 crosslinehatched45
.LArge
= crosslinehatched45(_hatch_base
*math
.sqrt(8))
818 crosslinehatched45
.LARge
= crosslinehatched45(_hatch_base
*math
.sqrt(16))
819 crosslinehatched45
.LARGe
= crosslinehatched45(_hatch_base
*math
.sqrt(32))
820 crosslinehatched45
.LARGE
= crosslinehatched45(_hatch_base
*math
.sqrt(64))
823 class colorgradient(deco
, attr
.attr
):
824 """inserts pieces of the path in different colors"""
826 def __init__(self
, grad
, attrs
=[], steps
=20):
831 def decorate(self
, dp
, texrunner
):
835 colors
= [self
.grad
.select(n
, self
.steps
) for n
in range(self
.steps
)]
837 params
= dp
.path
.arclentoparam([l
*i
/float(self
.steps
) for i
in range(self
.steps
)])
841 # treat the end pieces separately
842 c
.stroke(dp
.path
.split(params
[1])[1], attr
.mergeattrs([colors
[0]] + self
.attrs
))
843 for n
in range(1,self
.steps
-1):
844 c
.stroke(dp
.path
.split([params
[n
-1],params
[n
+1]])[1], attr
.mergeattrs([colors
[n
]] + self
.attrs
))
845 c
.stroke(dp
.path
.split(params
[-2])[0], attr
.mergeattrs([colors
[-1]] + self
.attrs
))
846 dp
.ornaments
.insert(c
)
849 class brace(deco
, attr
.attr
):
850 r
"""draws a nicely curled brace
852 In most cases, the original line is not wanted use canvas.canvas.draw(..) for it
854 Geometrical parameters:
857 ____________/ \__________
861 totalheight distance from the jaws to the middle cap
862 barthickness thickness of the main bars
863 innerstrokesthickness thickness of the two ending strokes
864 outerstrokesthickness thickness of the inner strokes at the middle cap
865 innerstrokesrelheight height of the inner/outer strokes, relative to the total height
866 outerstrokesrelheight this determines the angle of the main bars!
868 Note: if innerstrokesrelheight + outerstrokesrelheight == 1 then the main bars
869 will be aligned parallel to the connecting line between the endpoints
870 outerstrokesangle angle of the two ending strokes
871 innerstrokesangle angle between the inner strokes at the middle cap
872 slantstrokesangle extra slanting of the inner/outer strokes
873 innerstrokessmoothness smoothing parameter for the inner + outer strokes
874 outerstrokessmoothness should be around 1 (allowed: [0,infty))
875 middlerelpos position of the middle cap (0 == left, 1 == right)
877 # This code is experimental because it is unclear
878 # how the brace fits into the concepts of PyX
881 # - a brace needs to be decoratable with text
882 # it needs stroking and filling attributes
883 # - the brace is not really a box:
884 # it has two "anchor" points that are important for aligning it to other things
885 # and one "anchor" point (plus direction) for aligning other things
886 # - a brace is not a deformer:
887 # it does not look at anything else than begin/endpoint of a path
888 # - a brace might be a connector (which is to be dissolved into the box concept later?)
890 def __init__(self
, reverse
=1, stretch
=None, dist
=None, fillattrs
=[],
891 totalheight
=12*unit
.x_pt
,
892 barthickness
=0.5*unit
.x_pt
, innerstrokesthickness
=0.25*unit
.x_pt
, outerstrokesthickness
=0.25*unit
.x_pt
,
893 innerstrokesrelheight
=0.6, outerstrokesrelheight
=0.7,
894 innerstrokesangle
=30, outerstrokesangle
=25, slantstrokesangle
=5,
895 innerstrokessmoothness
=2.0, outerstrokessmoothness
=2.5,
897 self
.fillattrs
= fillattrs
898 self
.reverse
= reverse
899 self
.stretch
= stretch
901 self
.totalheight
= totalheight
902 self
.barthickness
= barthickness
903 self
.innerstrokesthickness
= innerstrokesthickness
904 self
.outerstrokesthickness
= outerstrokesthickness
905 self
.innerstrokesrelheight
= innerstrokesrelheight
906 self
.outerstrokesrelheight
= outerstrokesrelheight
907 self
.innerstrokesangle
= innerstrokesangle
908 self
.outerstrokesangle
= outerstrokesangle
909 self
.slantstrokesangle
= slantstrokesangle
910 self
.innerstrokessmoothness
= innerstrokessmoothness
911 self
.outerstrokessmoothness
= outerstrokessmoothness
912 self
.middlerelpos
= middlerelpos
914 def __call__(self
, **kwargs
):
915 for name
in ["reverse", "stretch", "dist", "fillattrs",
916 "totalheight", "barthickness", "innerstrokesthickness", "outerstrokesthickness",
917 "innerstrokesrelheight", "outerstrokesrelheight", "innerstrokesangle", "outerstrokesangle", "slantstrokesangle",
918 "innerstrokessmoothness", "outerstrokessmoothness", "middlerelpos"]:
919 if name
not in kwargs
:
920 kwargs
[name
] = self
.__dict
__[name
]
921 return brace(**kwargs
)
923 def _halfbracepath_pt(self
, length_pt
, height_pt
, ilength_pt
, olength_pt
, # <<<
924 ithick_pt
, othick_pt
, bthick_pt
, cos_iangle
, sin_iangle
, cos_oangle
,
925 sin_oangle
, cos_slangle
, sin_slangle
):
927 ismooth
= self
.innerstrokessmoothness
928 osmooth
= self
.outerstrokessmoothness
930 # these two parameters are not important enough to be seen outside
931 inner_cap_param
= 1.5
932 outer_cap_param
= 2.5
933 outerextracurved
= 0.6 # in (0, 1]
934 # 1.0 will lead to F=G, the outer strokes will not be curved at their ends.
935 # The smaller, the more curvature
937 # build an orientation path (three straight lines)
942 # _/ \______________________________________q5
948 # get the points for that:
949 q1
= (0, height_pt
- inner_cap_param
* ithick_pt
+ 0.5*ithick_pt
/sin_iangle
)
950 q2
= (q1
[0] + ilength_pt
* sin_iangle
,
951 q1
[1] - ilength_pt
* cos_iangle
)
953 q5
= (q6
[0] - olength_pt
* sin_oangle
,
954 q6
[1] + olength_pt
* cos_oangle
)
955 bardir
= (q5
[0] - q2
[0], q5
[1] - q2
[1])
956 bardirnorm
= math
.hypot(*bardir
)
957 bardir
= (bardir
[0]/bardirnorm
, bardir
[1]/bardirnorm
)
958 ismoothlength_pt
= ilength_pt
* ismooth
959 osmoothlength_pt
= olength_pt
* osmooth
960 if bardirnorm
< ismoothlength_pt
+ osmoothlength_pt
:
961 ismoothlength_pt
= bardirnorm
* ismoothlength_pt
/ (ismoothlength_pt
+ osmoothlength_pt
)
962 osmoothlength_pt
= bardirnorm
* osmoothlength_pt
/ (ismoothlength_pt
+ osmoothlength_pt
)
963 q3
= (q2
[0] + ismoothlength_pt
* bardir
[0],
964 q2
[1] + ismoothlength_pt
* bardir
[1])
965 q4
= (q5
[0] - osmoothlength_pt
* bardir
[0],
966 q5
[1] - osmoothlength_pt
* bardir
[1])
972 # / \ B2C2________D2___________E2_______F2___G2
973 # \______________________________________ \
974 # B1,C1 D1 E1 F1 G1 \
980 # the halfbraces meet in P and A1:
982 A1
= (0, height_pt
- inner_cap_param
* ithick_pt
)
983 # A2 is A1, shifted by the inner thickness
984 A2
= (A1
[0] + ithick_pt
* cos_iangle
,
985 A1
[1] + ithick_pt
* sin_iangle
)
986 s
, t
= deformer
.intersection(P
, A2
, (cos_slangle
, sin_slangle
), (sin_iangle
, -cos_iangle
))
987 O
= (P
[0] + s
* cos_slangle
,
988 P
[1] + s
* sin_slangle
)
990 # from D1 to E1 is the straight part of the brace
991 # also back from E2 to D1
992 D1
= (q3
[0] + bthick_pt
* bardir
[1],
993 q3
[1] - bthick_pt
* bardir
[0])
994 D2
= (q3
[0] - bthick_pt
* bardir
[1],
995 q3
[1] + bthick_pt
* bardir
[0])
996 E1
= (q4
[0] + bthick_pt
* bardir
[1],
997 q4
[1] - bthick_pt
* bardir
[0])
998 E2
= (q4
[0] - bthick_pt
* bardir
[1],
999 q4
[1] + bthick_pt
* bardir
[0])
1000 # I1, I2 are the control points at the outer stroke
1001 I1
= (q6
[0] - 0.5 * othick_pt
* cos_oangle
,
1002 q6
[1] - 0.5 * othick_pt
* sin_oangle
)
1003 I2
= (q6
[0] + 0.5 * othick_pt
* cos_oangle
,
1004 q6
[1] + 0.5 * othick_pt
* sin_oangle
)
1005 # get the control points for the curved parts of the brace
1006 s
, t
= deformer
.intersection(A1
, D1
, (sin_iangle
, -cos_iangle
), bardir
)
1007 B1
= (D1
[0] + t
* bardir
[0],
1008 D1
[1] + t
* bardir
[1])
1009 s
, t
= deformer
.intersection(A2
, D2
, (sin_iangle
, -cos_iangle
), bardir
)
1010 B2
= (D2
[0] + t
* bardir
[0],
1011 D2
[1] + t
* bardir
[1])
1012 s
, t
= deformer
.intersection(E1
, I1
, bardir
, (-sin_oangle
, cos_oangle
))
1013 G1
= (E1
[0] + s
* bardir
[0],
1014 E1
[1] + s
* bardir
[1])
1015 s
, t
= deformer
.intersection(E2
, I2
, bardir
, (-sin_oangle
, cos_oangle
))
1016 G2
= (E2
[0] + s
* bardir
[0],
1017 E2
[1] + s
* bardir
[1])
1018 # at the inner strokes: use curvature zero at both ends
1021 # at the outer strokes: use curvature zero only at the connection to
1023 F1
= (outerextracurved
* G1
[0] + (1 - outerextracurved
) * E1
[0],
1024 outerextracurved
* G1
[1] + (1 - outerextracurved
) * E1
[1])
1025 F2
= (outerextracurved
* G2
[0] + (1 - outerextracurved
) * E2
[0],
1026 outerextracurved
* G2
[1] + (1 - outerextracurved
) * E2
[1])
1027 # the tip of the outer stroke, endpoints of the bezier curve
1028 H1
= (I1
[0] - outer_cap_param
* othick_pt
* sin_oangle
,
1029 I1
[1] + outer_cap_param
* othick_pt
* cos_oangle
)
1030 H2
= (I2
[0] - outer_cap_param
* othick_pt
* sin_oangle
,
1031 I2
[1] + outer_cap_param
* othick_pt
* cos_oangle
)
1033 #for qq in [A1,B1,C1,D1,E1,F1,G1,H1,I1,
1034 # A2,B2,C2,D2,E2,F2,G2,H2,I2,
1037 # cc.fill(path.circle(qq[0], qq[1], 0.5), [color.rgb.green])
1039 # now build the right halfbrace
1040 bracepath
= path
.path(path
.moveto_pt(*A1
))
1041 bracepath
.append(path
.curveto_pt(B1
[0], B1
[1], C1
[0], C1
[1], D1
[0], D1
[1]))
1042 bracepath
.append(path
.lineto_pt(E1
[0], E1
[1]))
1043 bracepath
.append(path
.curveto_pt(F1
[0], F1
[1], G1
[0], G1
[1], H1
[0], H1
[1]))
1044 # the tip of the right halfbrace
1045 bracepath
.append(path
.curveto_pt(I1
[0], I1
[1], I2
[0], I2
[1], H2
[0], H2
[1]))
1046 # the rest of the right halfbrace
1047 bracepath
.append(path
.curveto_pt(G2
[0], G2
[1], F2
[0], F2
[1], E2
[0], E2
[1]))
1048 bracepath
.append(path
.lineto_pt(D2
[0], D2
[1]))
1049 bracepath
.append(path
.curveto_pt(C2
[0], C2
[1], B2
[0], B2
[1], A2
[0], A2
[1]))
1050 # the tip in the middle of the brace
1051 bracepath
.append(path
.curveto_pt(O
[0], O
[1], O
[0], O
[1], P
[0], P
[1]))
1056 def _bracepath(self
, x0_pt
, y0_pt
, x1_pt
, y1_pt
): # <<<
1057 height_pt
= unit
.topt(self
.totalheight
)
1058 totallength_pt
= math
.hypot(x1_pt
- x0_pt
, y1_pt
- y0_pt
)
1059 leftlength_pt
= self
.middlerelpos
* totallength_pt
1060 rightlength_pt
= totallength_pt
- leftlength_pt
1061 ithick_pt
= unit
.topt(self
.innerstrokesthickness
)
1062 othick_pt
= unit
.topt(self
.outerstrokesthickness
)
1063 bthick_pt
= unit
.topt(self
.barthickness
)
1065 # create the left halfbrace with positive slanting
1066 # because we will mirror this part
1067 cos_iangle
= math
.cos(math
.radians(0.5*self
.innerstrokesangle
- self
.slantstrokesangle
))
1068 sin_iangle
= math
.sin(math
.radians(0.5*self
.innerstrokesangle
- self
.slantstrokesangle
))
1069 cos_oangle
= math
.cos(math
.radians(self
.outerstrokesangle
- self
.slantstrokesangle
))
1070 sin_oangle
= math
.sin(math
.radians(self
.outerstrokesangle
- self
.slantstrokesangle
))
1071 cos_slangle
= math
.cos(math
.radians(-self
.slantstrokesangle
))
1072 sin_slangle
= math
.sin(math
.radians(-self
.slantstrokesangle
))
1073 ilength_pt
= self
.innerstrokesrelheight
* height_pt
/ cos_iangle
1074 olength_pt
= self
.outerstrokesrelheight
* height_pt
/ cos_oangle
1076 bracepath
= self
._halfbracepath
_pt
(leftlength_pt
, height_pt
,
1077 ilength_pt
, olength_pt
, ithick_pt
, othick_pt
, bthick_pt
, cos_iangle
,
1078 sin_iangle
, cos_oangle
, sin_oangle
, cos_slangle
,
1079 sin_slangle
).reversed().transformed(trafo
.mirror(90))
1081 # create the right halfbrace with negative slanting
1082 cos_iangle
= math
.cos(math
.radians(0.5*self
.innerstrokesangle
+ self
.slantstrokesangle
))
1083 sin_iangle
= math
.sin(math
.radians(0.5*self
.innerstrokesangle
+ self
.slantstrokesangle
))
1084 cos_oangle
= math
.cos(math
.radians(self
.outerstrokesangle
+ self
.slantstrokesangle
))
1085 sin_oangle
= math
.sin(math
.radians(self
.outerstrokesangle
+ self
.slantstrokesangle
))
1086 cos_slangle
= math
.cos(math
.radians(-self
.slantstrokesangle
))
1087 sin_slangle
= math
.sin(math
.radians(-self
.slantstrokesangle
))
1088 ilength_pt
= self
.innerstrokesrelheight
* height_pt
/ cos_iangle
1089 olength_pt
= self
.outerstrokesrelheight
* height_pt
/ cos_oangle
1091 bracepath
= bracepath
<< self
._halfbracepath
_pt
(rightlength_pt
, height_pt
,
1092 ilength_pt
, olength_pt
, ithick_pt
, othick_pt
, bthick_pt
, cos_iangle
,
1093 sin_iangle
, cos_oangle
, sin_oangle
, cos_slangle
,
1096 return bracepath
.transformed(
1097 # two trafos for matching the given endpoints
1098 trafo
.translate_pt(x0_pt
, y0_pt
) *
1099 trafo
.rotate_pt(math
.degrees(math
.atan2(y1_pt
-y0_pt
, x1_pt
-x0_pt
))) *
1100 # one trafo to move the brace's left outer stroke to zero
1101 trafo
.translate_pt(leftlength_pt
, 0))
1104 def decorate(self
, dp
, texrunner
):
1106 x0_pt
, y0_pt
= dp
.path
.atbegin_pt()
1107 x1_pt
, y1_pt
= dp
.path
.atend_pt()
1109 x0_pt
, y0_pt
, x1_pt
, y1_pt
= x1_pt
, y1_pt
, x0_pt
, y0_pt
1110 if self
.stretch
is not None:
1111 xm
, ym
= 0.5*(x0_pt
+x1_pt
), 0.5*(y0_pt
+y1_pt
)
1112 x0_pt
, y0_pt
= xm
+ self
.stretch
*(x0_pt
-xm
), ym
+ self
.stretch
*(y0_pt
-ym
)
1113 x1_pt
, y1_pt
= xm
+ self
.stretch
*(x1_pt
-xm
), ym
+ self
.stretch
*(y1_pt
-ym
)
1114 if self
.dist
is not None:
1115 d
= unit
.topt(self
.dist
)
1116 dx
, dy
= dp
.path
.rotation_pt(dp
.path
.begin()).apply_pt(0, 1)
1117 x0_pt
+= d
*dx
; y0_pt
+= d
*dy
1118 dx
, dy
= dp
.path
.rotation_pt(dp
.path
.end()).apply_pt(0, 1)
1119 x1_pt
+= d
*dx
; y1_pt
+= d
*dy
1120 dp
.ornaments
.fill(self
._bracepath
(x0_pt
, y0_pt
, x1_pt
, y1_pt
), self
.fillattrs
)
1122 brace
.clear
= attr
.clearclass(brace
)
1124 leftbrace
= brace(reverse
=0, middlerelpos
=0.55, innerstrokesrelheight
=0.6, outerstrokesrelheight
=0.7, slantstrokesangle
=-10)
1125 rightbrace
= brace(reverse
=1, middlerelpos
=0.45, innerstrokesrelheight
=0.6, outerstrokesrelheight
=0.7, slantstrokesangle
=10)
1126 belowbrace
= brace(reverse
=1, middlerelpos
=0.55, innerstrokesrelheight
=0.7, outerstrokesrelheight
=0.9, slantstrokesangle
=-10)
1127 abovebrace
= brace(reverse
=0, middlerelpos
=0.45, innerstrokesrelheight
=0.7, outerstrokesrelheight
=0.9, slantstrokesangle
=-10)
1128 straightbrace
= brace(innerstrokesrelheight
=0.5, outerstrokesrelheight
=0.5,
1129 innerstrokesangle
=30, outerstrokesangle
=30, slantstrokesangle
=0,
1130 innerstrokessmoothness
=1.0, outerstrokessmoothness
=1.0)