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-2011 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 import attr
, canvas
, canvasitem
, color
, path
, normpath
, style
, trafo
, unit
, deformer
37 class decoratedpath(canvasitem
.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,
47 ornaments
=None, fillrule
=style
.fillrule
.nonzero_winding
):
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 # the fillrule is either fillrule.nonzero_winding or fillrule.even_odd
66 self
.fillrule
= fillrule
68 self
.nostrokeranges
= None
70 def ensurenormpath(self
):
71 """convert self.path into a normpath"""
72 assert self
.nostrokeranges
is None or isinstance(self
.path
, path
.normpath
), "you don't understand what you are doing"
73 self
.path
= self
.path
.normpath()
75 def excluderange(self
, begin
, end
):
76 assert isinstance(self
.path
, path
.normpath
), "you don't understand what this is about"
77 if self
.nostrokeranges
is None:
78 self
.nostrokeranges
= [(begin
, end
)]
81 while ibegin
< len(self
.nostrokeranges
) and self
.nostrokeranges
[ibegin
][1] < begin
:
84 if ibegin
== len(self
.nostrokeranges
):
85 self
.nostrokeranges
.append((begin
, end
))
88 iend
= len(self
.nostrokeranges
) - 1
89 while 0 <= iend
and end
< self
.nostrokeranges
[iend
][0]:
93 self
.nostrokeranges
.insert(0, (begin
, end
))
96 if self
.nostrokeranges
[ibegin
][0] < begin
:
97 begin
= self
.nostrokeranges
[ibegin
][0]
98 if end
< self
.nostrokeranges
[iend
][1]:
99 end
= self
.nostrokeranges
[iend
][1]
101 self
.nostrokeranges
[ibegin
:iend
+1] = [(begin
, end
)]
104 pathbbox
= self
.path
.bbox()
105 ornamentsbbox
= self
.ornaments
.bbox()
106 if ornamentsbbox
is not None:
107 return ornamentsbbox
+ pathbbox
111 def strokepath(self
):
112 if self
.nostrokeranges
:
114 for begin
, end
in self
.nostrokeranges
:
115 splitlist
.append(begin
)
116 splitlist
.append(end
)
117 split
= self
.path
.split(splitlist
)
118 # XXX properly handle closed paths?
120 for i
in range(2, len(split
), 2):
126 def processPS(self
, file, writer
, context
, registry
, bbox
):
127 # draw (stroke and/or fill) the decoratedpath on the canvas
128 # while trying to produce an efficient output, e.g., by
129 # not writing one path two times
132 def _writestyles(styles
, context
, registry
, bbox
):
134 style
.processPS(file, writer
, context
, registry
, bbox
)
136 if self
.strokestyles
is None and self
.fillstyles
is None:
137 if not len(self
.ornaments
):
138 raise RuntimeError("Path neither to be stroked nor filled nor decorated in another way")
139 # just draw additional elements of decoratedpath
140 self
.ornaments
.processPS(file, writer
, context
, registry
, bbox
)
143 strokepath
= self
.strokepath()
146 # apply global styles
148 file.write("gsave\n")
150 _writestyles(self
.styles
, context
, registry
, bbox
)
152 if self
.fillstyles
is not None:
153 file.write("newpath\n")
154 fillpath
.outputPS(file, writer
)
156 if self
.strokestyles
is not None and strokepath
is fillpath
:
157 # do efficient stroking + filling if respective paths are identical
158 file.write("gsave\n")
161 _writestyles(self
.fillstyles
, context(), registry
, bbox
)
163 if self
.fillrule
.even_odd
:
164 file.write("eofill\n")
167 file.write("grestore\n")
170 if self
.strokestyles
:
171 file.write("gsave\n")
172 _writestyles(self
.strokestyles
, acontext
, registry
, bbox
)
174 file.write("stroke\n")
175 # take linewidth into account for bbox when stroking a path
176 bbox
+= strokepath
.bbox().enlarged_pt(0.5*acontext
.linewidth_pt
)
178 if self
.strokestyles
:
179 file.write("grestore\n")
181 # only fill fillpath - for the moment
183 file.write("gsave\n")
184 _writestyles(self
.fillstyles
, context(), registry
, bbox
)
186 if self
.fillrule
.even_odd
:
187 file.write("eofill\n")
190 bbox
+= fillpath
.bbox()
193 file.write("grestore\n")
195 if self
.strokestyles
is not None and (strokepath
is not fillpath
or self
.fillstyles
is None):
196 # this is the only relevant case still left
197 # Note that a possible stroking has already been done.
199 if self
.strokestyles
:
200 file.write("gsave\n")
201 _writestyles(self
.strokestyles
, acontext
, registry
, bbox
)
203 file.write("newpath\n")
204 strokepath
.outputPS(file, writer
)
205 file.write("stroke\n")
206 # take linewidth into account for bbox when stroking a path
207 bbox
+= strokepath
.bbox().enlarged_pt(0.5*acontext
.linewidth_pt
)
209 if self
.strokestyles
:
210 file.write("grestore\n")
212 # now, draw additional elements of decoratedpath
213 self
.ornaments
.processPS(file, writer
, context
, registry
, bbox
)
215 # restore global styles
217 file.write("grestore\n")
219 def processPDF(self
, file, writer
, context
, registry
, bbox
):
220 # draw (stroke and/or fill) the decoratedpath on the canvas
222 def _writestyles(styles
, context
, registry
, bbox
):
224 style
.processPDF(file, writer
, context
, registry
, bbox
)
226 def _writestrokestyles(strokestyles
, context
, registry
, bbox
):
228 for style
in strokestyles
:
229 style
.processPDF(file, writer
, context
, registry
, bbox
)
232 def _writefillstyles(fillstyles
, context
, registry
, bbox
):
233 context
.strokeattr
= 0
234 for style
in fillstyles
:
235 style
.processPDF(file, writer
, context
, registry
, bbox
)
236 context
.strokeattr
= 1
238 if self
.strokestyles
is None and self
.fillstyles
is None:
239 if not len(self
.ornaments
):
240 raise RuntimeError("Path neither to be stroked nor filled nor decorated in another way")
241 # just draw additional elements of decoratedpath
242 self
.ornaments
.processPDF(file, writer
, context
, registry
, bbox
)
245 strokepath
= self
.strokepath()
248 # apply global styles
250 file.write("q\n") # gsave
252 _writestyles(self
.styles
, context
, registry
, bbox
)
254 if self
.fillstyles
is not None:
255 fillpath
.outputPDF(file, writer
)
257 if self
.strokestyles
is not None and strokepath
is fillpath
:
258 # do efficient stroking + filling
259 file.write("q\n") # gsave
263 _writefillstyles(self
.fillstyles
, acontext
, registry
, bbox
)
264 if self
.strokestyles
:
265 _writestrokestyles(self
.strokestyles
, acontext
, registry
, bbox
)
267 if self
.fillrule
.even_odd
:
270 file.write("B\n") # both stroke and fill
271 # take linewidth into account for bbox when stroking a path
272 bbox
+= strokepath
.bbox().enlarged_pt(0.5*acontext
.linewidth_pt
)
274 file.write("Q\n") # grestore
276 # only fill fillpath - for the moment
278 file.write("q\n") # gsave
279 _writefillstyles(self
.fillstyles
, context(), registry
, bbox
)
281 if self
.fillrule
.even_odd
:
284 file.write("f\n") # fill
285 bbox
+= fillpath
.bbox()
288 file.write("Q\n") # grestore
290 if self
.strokestyles
is not None and (strokepath
is not fillpath
or self
.fillstyles
is None):
291 # this is the only relevant case still left
292 # Note that a possible stroking has already been done.
295 if self
.strokestyles
:
296 file.write("q\n") # gsave
297 _writestrokestyles(self
.strokestyles
, acontext
, registry
, bbox
)
299 strokepath
.outputPDF(file, writer
)
300 file.write("S\n") # stroke
301 # take linewidth into account for bbox when stroking a path
302 bbox
+= strokepath
.bbox().enlarged_pt(0.5*acontext
.linewidth_pt
)
304 if self
.strokestyles
:
305 file.write("Q\n") # grestore
307 # now, draw additional elements of decoratedpath
308 self
.ornaments
.processPDF(file, writer
, context
, registry
, bbox
)
310 # restore global styles
312 file.write("Q\n") # grestore
322 In contrast to path styles, path decorators depend on the concrete
323 path to which they are applied. In particular, they don't make
324 sense without any path and can thus not be used in canvas.set!
328 def decorate(self
, dp
, texrunner
):
329 """apply a style to a given decoratedpath object dp
331 decorate accepts a decoratedpath object dp, applies PathStyle
332 by modifying dp in place.
338 # stroked and filled: basic decos which stroked and fill,
339 # respectively the path
342 class _stroked(deco
, attr
.exclusiveattr
):
344 """stroked is a decorator, which draws the outline of the path"""
346 def __init__(self
, styles
=[]):
347 attr
.exclusiveattr
.__init
__(self
, _stroked
)
348 self
.styles
= attr
.mergeattrs(styles
)
349 attr
.checkattrs(self
.styles
, [style
.strokestyle
])
351 def __call__(self
, styles
=[]):
352 # XXX or should we also merge self.styles
353 return _stroked(styles
)
355 def decorate(self
, dp
, texrunner
):
356 if dp
.strokestyles
is not None:
357 raise RuntimeError("Cannot stroke an already stroked path")
358 dp
.strokestyles
= self
.styles
361 stroked
.clear
= attr
.clearclass(_stroked
)
364 class _filled(deco
, attr
.exclusiveattr
):
366 """filled is a decorator, which fills the interior of the path"""
368 def __init__(self
, styles
=[]):
369 attr
.exclusiveattr
.__init
__(self
, _filled
)
370 self
.styles
= attr
.mergeattrs(styles
)
371 attr
.checkattrs(self
.styles
, [style
.fillstyle
])
373 def __call__(self
, styles
=[]):
374 # XXX or should we also merge self.styles
375 return _filled(styles
)
377 def decorate(self
, dp
, texrunner
):
378 if dp
.fillstyles
is not None:
379 raise RuntimeError("Cannot fill an already filled path")
380 dp
.fillstyles
= self
.styles
383 filled
.clear
= attr
.clearclass(_filled
)
389 # helper function which constructs the arrowhead
391 def _arrowhead(anormpath
, arclenfrombegin
, direction
, size
, angle
, constriction
, constrictionlen
):
393 """helper routine, which returns an arrowhead from a given anormpath
395 - arclenfrombegin: position of arrow in arc length from the start of the path
396 - direction: +1 for an arrow pointing along the direction of anormpath or
397 -1 for an arrow pointing opposite to the direction of normpath
398 - size: size of the arrow as arc length
399 - angle. opening angle
400 - constriction: boolean to indicate whether the constriction point is to be taken into account or not
401 - constrictionlen: arc length of constriction. (not used when constriction is false)
404 # arc length and coordinates of tip
405 tx
, ty
= anormpath
.at(arclenfrombegin
)
407 # construct the template for the arrow by cutting the path at the
408 # corresponding length
409 arrowtemplate
= anormpath
.split([arclenfrombegin
, arclenfrombegin
- direction
* size
])[1]
411 # from this template, we construct the two outer curves of the arrow
412 arrowl
= arrowtemplate
.transformed(trafo
.rotate(-angle
/2.0, tx
, ty
))
413 arrowr
= arrowtemplate
.transformed(trafo
.rotate( angle
/2.0, tx
, ty
))
415 # now come the joining backward parts
417 # constriction point (cx, cy) lies on path
418 cx
, cy
= anormpath
.at(arclenfrombegin
- direction
* constrictionlen
)
419 arrowcr
= path
.line(*(arrowr
.atend() + (cx
,cy
)))
420 arrow
= arrowl
.reversed() << arrowr
<< arrowcr
422 arrow
= arrowl
.reversed() << arrowr
429 _base
= 6 * unit
.v_pt
431 class arrow(deco
, attr
.attr
):
433 """arrow is a decorator which adds an arrow to either side of the path"""
435 def __init__(self
, attrs
=[], pos
=1, reversed=0, size
=_base
, angle
=45, constriction
=0.8):
436 self
.attrs
= attr
.mergeattrs([style
.linestyle
.solid
, filled
] + attrs
)
437 attr
.checkattrs(self
.attrs
, [deco
, style
.fillstyle
, style
.strokestyle
])
439 self
.reversed = reversed
442 self
.constriction
= constriction
444 # calculate absolute arc length of constricition
445 # Note that we have to correct this length because the arrowtemplates are rotated
446 # by self.angle/2 to the left and right. Hence, if we want no constriction, i.e., for
447 # self.constriction = 1, we actually have a length which is approximately shorter
448 # by the given geometrical factor.
449 if self
.constriction
is not None:
450 self
.constrictionlen
= self
.size
* self
.constriction
* math
.cos(math
.radians(self
.angle
/2.0))
452 # if we do not want a constriction, i.e. constriction is None, we still
453 # need constrictionlen for cutting the path
454 self
.constrictionlen
= self
.size
* 1 * math
.cos(math
.radians(self
.angle
/2.0))
456 def __call__(self
, attrs
=None, pos
=None, reversed=None, size
=None, angle
=None, constriction
=_marker
):
462 reversed = self
.reversed
467 if constriction
is _marker
:
468 constriction
= self
.constriction
469 return arrow(attrs
=attrs
, pos
=pos
, reversed=reversed, size
=size
, angle
=angle
, constriction
=constriction
)
471 def decorate(self
, dp
, texrunner
):
475 arclenfrombegin
= (1-self
.reversed)*self
.constrictionlen
+ self
.pos
* (anormpath
.arclen() - self
.constrictionlen
)
476 direction
= self
.reversed and -1 or 1
477 arrowhead
= _arrowhead(anormpath
, arclenfrombegin
, direction
, self
.size
, self
.angle
,
478 self
.constriction
is not None, self
.constrictionlen
)
480 # add arrowhead to decoratedpath
481 dp
.ornaments
.draw(arrowhead
, self
.attrs
)
483 # exlude part of the path from stroking when the arrow is strictly at the begin or the end
484 if self
.pos
== 0 and self
.reversed:
485 dp
.excluderange(0, min(self
.size
, self
.constrictionlen
))
486 elif self
.pos
== 1 and not self
.reversed:
487 dp
.excluderange(anormpath
.end() - min(self
.size
, self
.constrictionlen
), anormpath
.end())
489 arrow
.clear
= attr
.clearclass(arrow
)
491 # arrows at begin of path
492 barrow
= arrow(pos
=0, reversed=1)
493 barrow
.SMALL
= barrow(size
=_base
/math
.sqrt(64))
494 barrow
.SMALl
= barrow(size
=_base
/math
.sqrt(32))
495 barrow
.SMAll
= barrow(size
=_base
/math
.sqrt(16))
496 barrow
.SMall
= barrow(size
=_base
/math
.sqrt(8))
497 barrow
.Small
= barrow(size
=_base
/math
.sqrt(4))
498 barrow
.small
= barrow(size
=_base
/math
.sqrt(2))
499 barrow
.normal
= barrow(size
=_base
)
500 barrow
.large
= barrow(size
=_base
*math
.sqrt(2))
501 barrow
.Large
= barrow(size
=_base
*math
.sqrt(4))
502 barrow
.LArge
= barrow(size
=_base
*math
.sqrt(8))
503 barrow
.LARge
= barrow(size
=_base
*math
.sqrt(16))
504 barrow
.LARGe
= barrow(size
=_base
*math
.sqrt(32))
505 barrow
.LARGE
= barrow(size
=_base
*math
.sqrt(64))
507 # arrows at end of path
509 earrow
.SMALL
= earrow(size
=_base
/math
.sqrt(64))
510 earrow
.SMALl
= earrow(size
=_base
/math
.sqrt(32))
511 earrow
.SMAll
= earrow(size
=_base
/math
.sqrt(16))
512 earrow
.SMall
= earrow(size
=_base
/math
.sqrt(8))
513 earrow
.Small
= earrow(size
=_base
/math
.sqrt(4))
514 earrow
.small
= earrow(size
=_base
/math
.sqrt(2))
515 earrow
.normal
= earrow(size
=_base
)
516 earrow
.large
= earrow(size
=_base
*math
.sqrt(2))
517 earrow
.Large
= earrow(size
=_base
*math
.sqrt(4))
518 earrow
.LArge
= earrow(size
=_base
*math
.sqrt(8))
519 earrow
.LARge
= earrow(size
=_base
*math
.sqrt(16))
520 earrow
.LARGe
= earrow(size
=_base
*math
.sqrt(32))
521 earrow
.LARGE
= earrow(size
=_base
*math
.sqrt(64))
524 class text(deco
, attr
.attr
):
525 """a simple text decorator"""
527 def __init__(self
, text
, textattrs
=[], angle
=0, relangle
=None, textdist
=0.2,
528 relarclenpos
=0.5, arclenfrombegin
=None, arclenfromend
=None,
530 if arclenfrombegin
is not None and arclenfromend
is not None:
531 raise ValueError("either set arclenfrombegin or arclenfromend")
533 self
.textattrs
= textattrs
535 self
.relangle
= relangle
536 self
.textdist
= textdist
537 self
.relarclenpos
= relarclenpos
538 self
.arclenfrombegin
= arclenfrombegin
539 self
.arclenfromend
= arclenfromend
540 self
.texrunner
= texrunner
542 def decorate(self
, dp
, texrunner
):
544 texrunner
= self
.texrunner
545 import text
as textmodule
546 textattrs
= attr
.mergeattrs([textmodule
.halign
.center
, textmodule
.vshift
.mathaxis
] + self
.textattrs
)
549 if self
.arclenfrombegin
is not None:
550 param
= dp
.path
.begin() + self
.arclenfrombegin
551 elif self
.arclenfromend
is not None:
552 param
= dp
.path
.end() - self
.arclenfromend
554 # relarcpos is used, when neither arcfrombegin nor arcfromend is given
555 param
= self
.relarclenpos
* dp
.path
.arclen()
556 x
, y
= dp
.path
.at(param
)
558 if self
.relangle
is not None:
559 a
= dp
.path
.trafo(param
).apply_pt(math
.cos(self
.relangle
*math
.pi
/180), math
.sin(self
.relangle
*math
.pi
/180))
560 b
= dp
.path
.trafo(param
).apply_pt(0, 0)
561 angle
= math
.atan2(a
[1] - b
[1], a
[0] - b
[0])
563 angle
= self
.angle
*math
.pi
/180
564 t
= texrunner
.text(x
, y
, self
.text
, textattrs
)
565 t
.linealign(self
.textdist
, math
.cos(angle
), math
.sin(angle
))
566 dp
.ornaments
.insert(t
)
568 class curvedtext(deco
, attr
.attr
):
569 """a text decorator for curved text
571 - text: is typeset along the path to which this decorator is applied
572 - relarclenpos: position for the base point of the text (default: 0)
573 - arlenfrombegin, arclenfromend: alternative ways of specifying the position of the base point;
574 use of relarclenpos, arclenfrombegin and arclenfromend is mutually exclusive
575 - textattrs, texrunner: standard text arguments (defaults: [] resp None)
579 # defaulttextattrs = [textmodule.halign.center] # TODO: not possible due to cyclic import issue
581 def __init__(self
, text
, textattrs
=[],
582 relarclenpos
=0.5, arclenfrombegin
=None, arclenfromend
=None,
583 texrunner
=None, exclude
=None):
584 if arclenfrombegin
is not None and arclenfromend
is not None:
585 raise ValueError("either set arclenfrombegin or arclenfromend")
587 self
.textattrs
= textattrs
588 self
.relarclenpos
= relarclenpos
589 self
.arclenfrombegin
= arclenfrombegin
590 self
.arclenfromend
= arclenfromend
591 self
.texrunner
= texrunner
592 self
.exclude
= exclude
594 def decorate(self
, dp
, texrunner
):
596 texrunner
= self
.texrunner
597 import text
as textmodule
598 self
.defaulttextattrs
= [textmodule
.halign
.center
]
601 if self
.arclenfrombegin
is not None:
602 textpos
= dp
.path
.begin() + self
.arclenfrombegin
603 elif self
.arclenfromend
is not None:
604 textpos
= dp
.path
.end() - self
.arclenfromend
606 # relarcpos is used if neither arcfrombegin nor arcfromend is given
607 textpos
= self
.relarclenpos
* dp
.path
.arclen()
609 textattrs
= self
.defaulttextattrs
+ self
.textattrs
610 t
= texrunner
.text(0, 0, self
.text
, textattrs
, singlecharmode
=1)
613 # we modify the original textbox to keep all its attributes and modify the position for each dvicanvas item
614 for item
in t
.dvicanvas
.items
:
617 bbox
= bbox
.transformed(t
.texttrafo
)
619 atrafo
= dp
.path
.trafo(textpos
+x
)
620 t
.dvicanvas
.insert(item
, [t
.texttrafo
] + [trafo
.translate(-x
, 0)] + [atrafo
], replace
=item
)
621 if self
.exclude
is not None:
622 dp
.excluderange(textpos
+bbox
.left()-self
.exclude
, textpos
+bbox
.right()+self
.exclude
)
624 # when inserting the original textbox, we reverse the texttrafo, as it has been applied to each dvicanvas individually
625 dp
.ornaments
.insert(t
, [t
.texttrafo
.inverse()])
628 class shownormpath(deco
, attr
.attr
):
630 def decorate(self
, dp
, texrunner
):
633 for normsubpath
in dp
.path
.normsubpaths
:
634 for i
, normsubpathitem
in enumerate(normsubpath
.normsubpathitems
):
635 if isinstance(normsubpathitem
, normpath
.normcurve_pt
):
636 dp
.ornaments
.stroke(normpath
.normpath([normpath
.normsubpath([normsubpathitem
])]), [color
.rgb
.green
])
638 dp
.ornaments
.stroke(normpath
.normpath([normpath
.normsubpath([normsubpathitem
])]), [color
.rgb
.blue
])
639 for normsubpath
in dp
.path
.normsubpaths
:
640 for i
, normsubpathitem
in enumerate(normsubpath
.normsubpathitems
):
641 if isinstance(normsubpathitem
, normpath
.normcurve_pt
):
642 dp
.ornaments
.stroke(path
.line_pt(normsubpathitem
.x0_pt
, normsubpathitem
.y0_pt
, normsubpathitem
.x1_pt
, normsubpathitem
.y1_pt
), [style
.linestyle
.dashed
, color
.rgb
.red
])
643 dp
.ornaments
.stroke(path
.line_pt(normsubpathitem
.x2_pt
, normsubpathitem
.y2_pt
, normsubpathitem
.x3_pt
, normsubpathitem
.y3_pt
), [style
.linestyle
.dashed
, color
.rgb
.red
])
644 dp
.ornaments
.draw(path
.circle_pt(normsubpathitem
.x1_pt
, normsubpathitem
.y1_pt
, r_pt
), [filled([color
.rgb
.red
])])
645 dp
.ornaments
.draw(path
.circle_pt(normsubpathitem
.x2_pt
, normsubpathitem
.y2_pt
, r_pt
), [filled([color
.rgb
.red
])])
646 for normsubpath
in dp
.path
.normsubpaths
:
647 for i
, normsubpathitem
in enumerate(normsubpath
.normsubpathitems
):
649 x_pt
, y_pt
= normsubpathitem
.atbegin_pt()
650 dp
.ornaments
.draw(path
.circle_pt(x_pt
, y_pt
, r_pt
), [filled
])
651 x_pt
, y_pt
= normsubpathitem
.atend_pt()
652 dp
.ornaments
.draw(path
.circle_pt(x_pt
, y_pt
, r_pt
), [filled
])
655 class linehatched(deco
, attr
.exclusiveattr
, attr
.clearclass
):
656 """draws a pattern with explicit lines
658 This class acts as a drop-in replacement for postscript patterns
659 from the pattern module which are not understood by some printers"""
661 def __init__(self
, dist
, angle
, strokestyles
=[], cross
=0):
662 attr
.clearclass
.__init
__(self
, _filled
)
663 attr
.exclusiveattr
.__init
__(self
, linehatched
)
666 self
.strokestyles
= attr
.mergeattrs([style
.linewidth
.THIN
] + strokestyles
)
667 attr
.checkattrs(self
.strokestyles
, [style
.strokestyle
])
670 def __call__(self
, dist
=None, angle
=None, strokestyles
=None, cross
=None):
675 if strokestyles
is None:
676 strokestyles
= self
.strokestyles
679 return linehatched(dist
, angle
, strokestyles
, cross
)
681 def _decocanvas(self
, angle
, dp
, texrunner
):
683 dist_pt
= unit
.topt(self
.dist
)
685 c
= canvas
.canvas([canvas
.clip(dp
.path
)])
686 llx_pt
, lly_pt
, urx_pt
, ury_pt
= dp
.path
.bbox().highrestuple_pt()
687 center_pt
= 0.5*(llx_pt
+urx_pt
), 0.5*(lly_pt
+ury_pt
)
688 radius_pt
= 0.5*math
.hypot(urx_pt
-llx_pt
, ury_pt
-lly_pt
) + dist_pt
689 n
= int(2*radius_pt
/ dist_pt
) + 1
691 x_pt
= center_pt
[0] - radius_pt
+ i
*dist_pt
692 c
.stroke(path
.line_pt(x_pt
, center_pt
[1]-radius_pt
, x_pt
, center_pt
[1]+radius_pt
),
693 [trafo
.rotate_pt(angle
, center_pt
[0], center_pt
[1])] + self
.strokestyles
)
696 def decorate(self
, dp
, texrunner
):
697 dp
.ornaments
.insert(self
._decocanvas
(self
.angle
, dp
, texrunner
))
699 dp
.ornaments
.insert(self
._decocanvas
(self
.angle
+90, dp
, texrunner
))
701 def merge(self
, attrs
):
702 # act as attr.clearclass and as attr.exclusiveattr at the same time
703 newattrs
= attr
.exclusiveattr
.merge(self
, attrs
)
704 return attr
.clearclass
.merge(self
, newattrs
)
706 linehatched
.clear
= attr
.clearclass(linehatched
)
708 _hatch_base
= 0.1 * unit
.v_cm
710 linehatched0
= linehatched(_hatch_base
, 0)
711 linehatched0
.SMALL
= linehatched0(_hatch_base
/math
.sqrt(64))
712 linehatched0
.SMALL
= linehatched0(_hatch_base
/math
.sqrt(64))
713 linehatched0
.SMALl
= linehatched0(_hatch_base
/math
.sqrt(32))
714 linehatched0
.SMAll
= linehatched0(_hatch_base
/math
.sqrt(16))
715 linehatched0
.SMall
= linehatched0(_hatch_base
/math
.sqrt(8))
716 linehatched0
.Small
= linehatched0(_hatch_base
/math
.sqrt(4))
717 linehatched0
.small
= linehatched0(_hatch_base
/math
.sqrt(2))
718 linehatched0
.normal
= linehatched0(_hatch_base
)
719 linehatched0
.large
= linehatched0(_hatch_base
*math
.sqrt(2))
720 linehatched0
.Large
= linehatched0(_hatch_base
*math
.sqrt(4))
721 linehatched0
.LArge
= linehatched0(_hatch_base
*math
.sqrt(8))
722 linehatched0
.LARge
= linehatched0(_hatch_base
*math
.sqrt(16))
723 linehatched0
.LARGe
= linehatched0(_hatch_base
*math
.sqrt(32))
724 linehatched0
.LARGE
= linehatched0(_hatch_base
*math
.sqrt(64))
726 linehatched45
= linehatched(_hatch_base
, 45)
727 linehatched45
.SMALL
= linehatched45(_hatch_base
/math
.sqrt(64))
728 linehatched45
.SMALl
= linehatched45(_hatch_base
/math
.sqrt(32))
729 linehatched45
.SMAll
= linehatched45(_hatch_base
/math
.sqrt(16))
730 linehatched45
.SMall
= linehatched45(_hatch_base
/math
.sqrt(8))
731 linehatched45
.Small
= linehatched45(_hatch_base
/math
.sqrt(4))
732 linehatched45
.small
= linehatched45(_hatch_base
/math
.sqrt(2))
733 linehatched45
.normal
= linehatched45(_hatch_base
)
734 linehatched45
.large
= linehatched45(_hatch_base
*math
.sqrt(2))
735 linehatched45
.Large
= linehatched45(_hatch_base
*math
.sqrt(4))
736 linehatched45
.LArge
= linehatched45(_hatch_base
*math
.sqrt(8))
737 linehatched45
.LARge
= linehatched45(_hatch_base
*math
.sqrt(16))
738 linehatched45
.LARGe
= linehatched45(_hatch_base
*math
.sqrt(32))
739 linehatched45
.LARGE
= linehatched45(_hatch_base
*math
.sqrt(64))
741 linehatched90
= linehatched(_hatch_base
, 90)
742 linehatched90
.SMALL
= linehatched90(_hatch_base
/math
.sqrt(64))
743 linehatched90
.SMALl
= linehatched90(_hatch_base
/math
.sqrt(32))
744 linehatched90
.SMAll
= linehatched90(_hatch_base
/math
.sqrt(16))
745 linehatched90
.SMall
= linehatched90(_hatch_base
/math
.sqrt(8))
746 linehatched90
.Small
= linehatched90(_hatch_base
/math
.sqrt(4))
747 linehatched90
.small
= linehatched90(_hatch_base
/math
.sqrt(2))
748 linehatched90
.normal
= linehatched90(_hatch_base
)
749 linehatched90
.large
= linehatched90(_hatch_base
*math
.sqrt(2))
750 linehatched90
.Large
= linehatched90(_hatch_base
*math
.sqrt(4))
751 linehatched90
.LArge
= linehatched90(_hatch_base
*math
.sqrt(8))
752 linehatched90
.LARge
= linehatched90(_hatch_base
*math
.sqrt(16))
753 linehatched90
.LARGe
= linehatched90(_hatch_base
*math
.sqrt(32))
754 linehatched90
.LARGE
= linehatched90(_hatch_base
*math
.sqrt(64))
756 linehatched135
= linehatched(_hatch_base
, 135)
757 linehatched135
.SMALL
= linehatched135(_hatch_base
/math
.sqrt(64))
758 linehatched135
.SMALl
= linehatched135(_hatch_base
/math
.sqrt(32))
759 linehatched135
.SMAll
= linehatched135(_hatch_base
/math
.sqrt(16))
760 linehatched135
.SMall
= linehatched135(_hatch_base
/math
.sqrt(8))
761 linehatched135
.Small
= linehatched135(_hatch_base
/math
.sqrt(4))
762 linehatched135
.small
= linehatched135(_hatch_base
/math
.sqrt(2))
763 linehatched135
.normal
= linehatched135(_hatch_base
)
764 linehatched135
.large
= linehatched135(_hatch_base
*math
.sqrt(2))
765 linehatched135
.Large
= linehatched135(_hatch_base
*math
.sqrt(4))
766 linehatched135
.LArge
= linehatched135(_hatch_base
*math
.sqrt(8))
767 linehatched135
.LARge
= linehatched135(_hatch_base
*math
.sqrt(16))
768 linehatched135
.LARGe
= linehatched135(_hatch_base
*math
.sqrt(32))
769 linehatched135
.LARGE
= linehatched135(_hatch_base
*math
.sqrt(64))
771 crosslinehatched0
= linehatched(_hatch_base
, 0, cross
=1)
772 crosslinehatched0
.SMALL
= crosslinehatched0(_hatch_base
/math
.sqrt(64))
773 crosslinehatched0
.SMALl
= crosslinehatched0(_hatch_base
/math
.sqrt(32))
774 crosslinehatched0
.SMAll
= crosslinehatched0(_hatch_base
/math
.sqrt(16))
775 crosslinehatched0
.SMall
= crosslinehatched0(_hatch_base
/math
.sqrt(8))
776 crosslinehatched0
.Small
= crosslinehatched0(_hatch_base
/math
.sqrt(4))
777 crosslinehatched0
.small
= crosslinehatched0(_hatch_base
/math
.sqrt(2))
778 crosslinehatched0
.normal
= crosslinehatched0
779 crosslinehatched0
.large
= crosslinehatched0(_hatch_base
*math
.sqrt(2))
780 crosslinehatched0
.Large
= crosslinehatched0(_hatch_base
*math
.sqrt(4))
781 crosslinehatched0
.LArge
= crosslinehatched0(_hatch_base
*math
.sqrt(8))
782 crosslinehatched0
.LARge
= crosslinehatched0(_hatch_base
*math
.sqrt(16))
783 crosslinehatched0
.LARGe
= crosslinehatched0(_hatch_base
*math
.sqrt(32))
784 crosslinehatched0
.LARGE
= crosslinehatched0(_hatch_base
*math
.sqrt(64))
786 crosslinehatched45
= linehatched(_hatch_base
, 45, cross
=1)
787 crosslinehatched45
.SMALL
= crosslinehatched45(_hatch_base
/math
.sqrt(64))
788 crosslinehatched45
.SMALl
= crosslinehatched45(_hatch_base
/math
.sqrt(32))
789 crosslinehatched45
.SMAll
= crosslinehatched45(_hatch_base
/math
.sqrt(16))
790 crosslinehatched45
.SMall
= crosslinehatched45(_hatch_base
/math
.sqrt(8))
791 crosslinehatched45
.Small
= crosslinehatched45(_hatch_base
/math
.sqrt(4))
792 crosslinehatched45
.small
= crosslinehatched45(_hatch_base
/math
.sqrt(2))
793 crosslinehatched45
.normal
= crosslinehatched45
794 crosslinehatched45
.large
= crosslinehatched45(_hatch_base
*math
.sqrt(2))
795 crosslinehatched45
.Large
= crosslinehatched45(_hatch_base
*math
.sqrt(4))
796 crosslinehatched45
.LArge
= crosslinehatched45(_hatch_base
*math
.sqrt(8))
797 crosslinehatched45
.LARge
= crosslinehatched45(_hatch_base
*math
.sqrt(16))
798 crosslinehatched45
.LARGe
= crosslinehatched45(_hatch_base
*math
.sqrt(32))
799 crosslinehatched45
.LARGE
= crosslinehatched45(_hatch_base
*math
.sqrt(64))
802 class colorgradient(deco
, attr
.attr
):
803 """inserts pieces of the path in different colors"""
805 def __init__(self
, grad
, attrs
=[], steps
=20):
810 def decorate(self
, dp
, texrunner
):
814 colors
= [self
.grad
.select(n
, self
.steps
) for n
in range(self
.steps
)]
816 params
= dp
.path
.arclentoparam([l
*i
/float(self
.steps
) for i
in range(self
.steps
)])
820 # treat the end pieces separately
821 c
.stroke(dp
.path
.split(params
[1])[1], attr
.mergeattrs([colors
[0]] + self
.attrs
))
822 for n
in range(1,self
.steps
-1):
823 c
.stroke(dp
.path
.split([params
[n
-1],params
[n
+1]])[1], attr
.mergeattrs([colors
[n
]] + self
.attrs
))
824 c
.stroke(dp
.path
.split(params
[-2])[0], attr
.mergeattrs([colors
[-1]] + self
.attrs
))
825 dp
.ornaments
.insert(c
)
828 class brace(deco
, attr
.attr
):
829 r
"""draws a nicely curled brace
831 In most cases, the original line is not wanted use canvas.canvas.draw(..) for it
833 Geometrical parameters:
836 ____________/ \__________
840 totalheight distance from the jaws to the middle cap
841 barthickness thickness of the main bars
842 innerstrokesthickness thickness of the two ending strokes
843 outerstrokesthickness thickness of the inner strokes at the middle cap
844 innerstrokesrelheight height of the inner/outer strokes, relative to the total height
845 outerstrokesrelheight this determines the angle of the main bars!
847 Note: if innerstrokesrelheight + outerstrokesrelheight == 1 then the main bars
848 will be aligned parallel to the connecting line between the endpoints
849 outerstrokesangle angle of the two ending strokes
850 innerstrokesangle angle between the inner strokes at the middle cap
851 slantstrokesangle extra slanting of the inner/outer strokes
852 innerstrokessmoothness smoothing parameter for the inner + outer strokes
853 outerstrokessmoothness should be around 1 (allowed: [0,infty))
854 middlerelpos position of the middle cap (0 == left, 1 == right)
856 # This code is experimental because it is unclear
857 # how the brace fits into the concepts of PyX
860 # - a brace needs to be decoratable with text
861 # it needs stroking and filling attributes
862 # - the brace is not really a box:
863 # it has two "anchor" points that are important for aligning it to other things
864 # and one "anchor" point (plus direction) for aligning other things
865 # - a brace is not a deformer:
866 # it does not look at anything else than begin/endpoint of a path
867 # - a brace might be a connector (which is to be dissolved into the box concept later?)
869 def __init__(self
, reverse
=1, stretch
=None, dist
=None, fillattrs
=[],
870 totalheight
=12*unit
.x_pt
,
871 barthickness
=0.5*unit
.x_pt
, innerstrokesthickness
=0.25*unit
.x_pt
, outerstrokesthickness
=0.25*unit
.x_pt
,
872 innerstrokesrelheight
=0.6, outerstrokesrelheight
=0.7,
873 innerstrokesangle
=30, outerstrokesangle
=25, slantstrokesangle
=5,
874 innerstrokessmoothness
=2.0, outerstrokessmoothness
=2.5,
876 self
.fillattrs
= fillattrs
877 self
.reverse
= reverse
878 self
.stretch
= stretch
880 self
.totalheight
= totalheight
881 self
.barthickness
= barthickness
882 self
.innerstrokesthickness
= innerstrokesthickness
883 self
.outerstrokesthickness
= outerstrokesthickness
884 self
.innerstrokesrelheight
= innerstrokesrelheight
885 self
.outerstrokesrelheight
= outerstrokesrelheight
886 self
.innerstrokesangle
= innerstrokesangle
887 self
.outerstrokesangle
= outerstrokesangle
888 self
.slantstrokesangle
= slantstrokesangle
889 self
.innerstrokessmoothness
= innerstrokessmoothness
890 self
.outerstrokessmoothness
= outerstrokessmoothness
891 self
.middlerelpos
= middlerelpos
893 def __call__(self
, **kwargs
):
894 for name
in ["reverse", "stretch", "dist", "fillattrs",
895 "totalheight", "barthickness", "innerstrokesthickness", "outerstrokesthickness",
896 "innerstrokesrelheight", "outerstrokesrelheight", "innerstrokesangle", "outerstrokesangle", "slantstrokesangle",
897 "innerstrokessmoothness", "outerstrokessmoothness", "middlerelpos"]:
898 if not kwargs
.has_key(name
):
899 kwargs
[name
] = self
.__dict
__[name
]
900 return brace(**kwargs
)
902 def _halfbracepath_pt(self
, length_pt
, height_pt
, ilength_pt
, olength_pt
, # <<<
903 ithick_pt
, othick_pt
, bthick_pt
, cos_iangle
, sin_iangle
, cos_oangle
,
904 sin_oangle
, cos_slangle
, sin_slangle
):
906 ismooth
= self
.innerstrokessmoothness
907 osmooth
= self
.outerstrokessmoothness
909 # these two parameters are not important enough to be seen outside
910 inner_cap_param
= 1.5
911 outer_cap_param
= 2.5
912 outerextracurved
= 0.6 # in (0, 1]
913 # 1.0 will lead to F=G, the outer strokes will not be curved at their ends.
914 # The smaller, the more curvature
916 # build an orientation path (three straight lines)
921 # _/ \______________________________________q5
927 # get the points for that:
928 q1
= (0, height_pt
- inner_cap_param
* ithick_pt
+ 0.5*ithick_pt
/sin_iangle
)
929 q2
= (q1
[0] + ilength_pt
* sin_iangle
,
930 q1
[1] - ilength_pt
* cos_iangle
)
932 q5
= (q6
[0] - olength_pt
* sin_oangle
,
933 q6
[1] + olength_pt
* cos_oangle
)
934 bardir
= (q5
[0] - q2
[0], q5
[1] - q2
[1])
935 bardirnorm
= math
.hypot(*bardir
)
936 bardir
= (bardir
[0]/bardirnorm
, bardir
[1]/bardirnorm
)
937 ismoothlength_pt
= ilength_pt
* ismooth
938 osmoothlength_pt
= olength_pt
* osmooth
939 if bardirnorm
< ismoothlength_pt
+ osmoothlength_pt
:
940 ismoothlength_pt
= bardirnorm
* ismoothlength_pt
/ (ismoothlength_pt
+ osmoothlength_pt
)
941 osmoothlength_pt
= bardirnorm
* osmoothlength_pt
/ (ismoothlength_pt
+ osmoothlength_pt
)
942 q3
= (q2
[0] + ismoothlength_pt
* bardir
[0],
943 q2
[1] + ismoothlength_pt
* bardir
[1])
944 q4
= (q5
[0] - osmoothlength_pt
* bardir
[0],
945 q5
[1] - osmoothlength_pt
* bardir
[1])
951 # / \ B2C2________D2___________E2_______F2___G2
952 # \______________________________________ \
953 # B1,C1 D1 E1 F1 G1 \
959 # the halfbraces meet in P and A1:
961 A1
= (0, height_pt
- inner_cap_param
* ithick_pt
)
962 # A2 is A1, shifted by the inner thickness
963 A2
= (A1
[0] + ithick_pt
* cos_iangle
,
964 A1
[1] + ithick_pt
* sin_iangle
)
965 s
, t
= deformer
.intersection(P
, A2
, (cos_slangle
, sin_slangle
), (sin_iangle
, -cos_iangle
))
966 O
= (P
[0] + s
* cos_slangle
,
967 P
[1] + s
* sin_slangle
)
969 # from D1 to E1 is the straight part of the brace
970 # also back from E2 to D1
971 D1
= (q3
[0] + bthick_pt
* bardir
[1],
972 q3
[1] - bthick_pt
* bardir
[0])
973 D2
= (q3
[0] - bthick_pt
* bardir
[1],
974 q3
[1] + bthick_pt
* bardir
[0])
975 E1
= (q4
[0] + bthick_pt
* bardir
[1],
976 q4
[1] - bthick_pt
* bardir
[0])
977 E2
= (q4
[0] - bthick_pt
* bardir
[1],
978 q4
[1] + bthick_pt
* bardir
[0])
979 # I1, I2 are the control points at the outer stroke
980 I1
= (q6
[0] - 0.5 * othick_pt
* cos_oangle
,
981 q6
[1] - 0.5 * othick_pt
* sin_oangle
)
982 I2
= (q6
[0] + 0.5 * othick_pt
* cos_oangle
,
983 q6
[1] + 0.5 * othick_pt
* sin_oangle
)
984 # get the control points for the curved parts of the brace
985 s
, t
= deformer
.intersection(A1
, D1
, (sin_iangle
, -cos_iangle
), bardir
)
986 B1
= (D1
[0] + t
* bardir
[0],
987 D1
[1] + t
* bardir
[1])
988 s
, t
= deformer
.intersection(A2
, D2
, (sin_iangle
, -cos_iangle
), bardir
)
989 B2
= (D2
[0] + t
* bardir
[0],
990 D2
[1] + t
* bardir
[1])
991 s
, t
= deformer
.intersection(E1
, I1
, bardir
, (-sin_oangle
, cos_oangle
))
992 G1
= (E1
[0] + s
* bardir
[0],
993 E1
[1] + s
* bardir
[1])
994 s
, t
= deformer
.intersection(E2
, I2
, bardir
, (-sin_oangle
, cos_oangle
))
995 G2
= (E2
[0] + s
* bardir
[0],
996 E2
[1] + s
* bardir
[1])
997 # at the inner strokes: use curvature zero at both ends
1000 # at the outer strokes: use curvature zero only at the connection to
1002 F1
= (outerextracurved
* G1
[0] + (1 - outerextracurved
) * E1
[0],
1003 outerextracurved
* G1
[1] + (1 - outerextracurved
) * E1
[1])
1004 F2
= (outerextracurved
* G2
[0] + (1 - outerextracurved
) * E2
[0],
1005 outerextracurved
* G2
[1] + (1 - outerextracurved
) * E2
[1])
1006 # the tip of the outer stroke, endpoints of the bezier curve
1007 H1
= (I1
[0] - outer_cap_param
* othick_pt
* sin_oangle
,
1008 I1
[1] + outer_cap_param
* othick_pt
* cos_oangle
)
1009 H2
= (I2
[0] - outer_cap_param
* othick_pt
* sin_oangle
,
1010 I2
[1] + outer_cap_param
* othick_pt
* cos_oangle
)
1012 #for qq in [A1,B1,C1,D1,E1,F1,G1,H1,I1,
1013 # A2,B2,C2,D2,E2,F2,G2,H2,I2,
1016 # cc.fill(path.circle(qq[0], qq[1], 0.5), [color.rgb.green])
1018 # now build the right halfbrace
1019 bracepath
= path
.path(path
.moveto_pt(*A1
))
1020 bracepath
.append(path
.curveto_pt(B1
[0], B1
[1], C1
[0], C1
[1], D1
[0], D1
[1]))
1021 bracepath
.append(path
.lineto_pt(E1
[0], E1
[1]))
1022 bracepath
.append(path
.curveto_pt(F1
[0], F1
[1], G1
[0], G1
[1], H1
[0], H1
[1]))
1023 # the tip of the right halfbrace
1024 bracepath
.append(path
.curveto_pt(I1
[0], I1
[1], I2
[0], I2
[1], H2
[0], H2
[1]))
1025 # the rest of the right halfbrace
1026 bracepath
.append(path
.curveto_pt(G2
[0], G2
[1], F2
[0], F2
[1], E2
[0], E2
[1]))
1027 bracepath
.append(path
.lineto_pt(D2
[0], D2
[1]))
1028 bracepath
.append(path
.curveto_pt(C2
[0], C2
[1], B2
[0], B2
[1], A2
[0], A2
[1]))
1029 # the tip in the middle of the brace
1030 bracepath
.append(path
.curveto_pt(O
[0], O
[1], O
[0], O
[1], P
[0], P
[1]))
1035 def _bracepath(self
, x0_pt
, y0_pt
, x1_pt
, y1_pt
): # <<<
1036 height_pt
= unit
.topt(self
.totalheight
)
1037 totallength_pt
= math
.hypot(x1_pt
- x0_pt
, y1_pt
- y0_pt
)
1038 leftlength_pt
= self
.middlerelpos
* totallength_pt
1039 rightlength_pt
= totallength_pt
- leftlength_pt
1040 ithick_pt
= unit
.topt(self
.innerstrokesthickness
)
1041 othick_pt
= unit
.topt(self
.outerstrokesthickness
)
1042 bthick_pt
= unit
.topt(self
.barthickness
)
1044 # create the left halfbrace with positive slanting
1045 # because we will mirror this part
1046 cos_iangle
= math
.cos(math
.radians(0.5*self
.innerstrokesangle
- self
.slantstrokesangle
))
1047 sin_iangle
= math
.sin(math
.radians(0.5*self
.innerstrokesangle
- self
.slantstrokesangle
))
1048 cos_oangle
= math
.cos(math
.radians(self
.outerstrokesangle
- self
.slantstrokesangle
))
1049 sin_oangle
= math
.sin(math
.radians(self
.outerstrokesangle
- self
.slantstrokesangle
))
1050 cos_slangle
= math
.cos(math
.radians(-self
.slantstrokesangle
))
1051 sin_slangle
= math
.sin(math
.radians(-self
.slantstrokesangle
))
1052 ilength_pt
= self
.innerstrokesrelheight
* height_pt
/ cos_iangle
1053 olength_pt
= self
.outerstrokesrelheight
* height_pt
/ cos_oangle
1055 bracepath
= self
._halfbracepath
_pt
(leftlength_pt
, height_pt
,
1056 ilength_pt
, olength_pt
, ithick_pt
, othick_pt
, bthick_pt
, cos_iangle
,
1057 sin_iangle
, cos_oangle
, sin_oangle
, cos_slangle
,
1058 sin_slangle
).reversed().transformed(trafo
.mirror(90))
1060 # create the right halfbrace with negative slanting
1061 cos_iangle
= math
.cos(math
.radians(0.5*self
.innerstrokesangle
+ self
.slantstrokesangle
))
1062 sin_iangle
= math
.sin(math
.radians(0.5*self
.innerstrokesangle
+ self
.slantstrokesangle
))
1063 cos_oangle
= math
.cos(math
.radians(self
.outerstrokesangle
+ self
.slantstrokesangle
))
1064 sin_oangle
= math
.sin(math
.radians(self
.outerstrokesangle
+ self
.slantstrokesangle
))
1065 cos_slangle
= math
.cos(math
.radians(-self
.slantstrokesangle
))
1066 sin_slangle
= math
.sin(math
.radians(-self
.slantstrokesangle
))
1067 ilength_pt
= self
.innerstrokesrelheight
* height_pt
/ cos_iangle
1068 olength_pt
= self
.outerstrokesrelheight
* height_pt
/ cos_oangle
1070 bracepath
= bracepath
<< self
._halfbracepath
_pt
(rightlength_pt
, height_pt
,
1071 ilength_pt
, olength_pt
, ithick_pt
, othick_pt
, bthick_pt
, cos_iangle
,
1072 sin_iangle
, cos_oangle
, sin_oangle
, cos_slangle
,
1075 return bracepath
.transformed(
1076 # two trafos for matching the given endpoints
1077 trafo
.translate_pt(x0_pt
, y0_pt
) *
1078 trafo
.rotate_pt(math
.degrees(math
.atan2(y1_pt
-y0_pt
, x1_pt
-x0_pt
))) *
1079 # one trafo to move the brace's left outer stroke to zero
1080 trafo
.translate_pt(leftlength_pt
, 0))
1083 def decorate(self
, dp
, texrunner
):
1085 x0_pt
, y0_pt
= dp
.path
.atbegin_pt()
1086 x1_pt
, y1_pt
= dp
.path
.atend_pt()
1088 x0_pt
, y0_pt
, x1_pt
, y1_pt
= x1_pt
, y1_pt
, x0_pt
, y0_pt
1089 if self
.stretch
is not None:
1090 xm
, ym
= 0.5*(x0_pt
+x1_pt
), 0.5*(y0_pt
+y1_pt
)
1091 x0_pt
, y0_pt
= xm
+ self
.stretch
*(x0_pt
-xm
), ym
+ self
.stretch
*(y0_pt
-ym
)
1092 x1_pt
, y1_pt
= xm
+ self
.stretch
*(x1_pt
-xm
), ym
+ self
.stretch
*(y1_pt
-ym
)
1093 if self
.dist
is not None:
1094 d
= unit
.topt(self
.dist
)
1095 dx
, dy
= dp
.path
.rotation_pt(dp
.path
.begin()).apply_pt(0, 1)
1096 x0_pt
+= d
*dx
; y0_pt
+= d
*dy
1097 dx
, dy
= dp
.path
.rotation_pt(dp
.path
.end()).apply_pt(0, 1)
1098 x1_pt
+= d
*dx
; y1_pt
+= d
*dy
1099 dp
.ornaments
.fill(self
._bracepath
(x0_pt
, y0_pt
, x1_pt
, y1_pt
), self
.fillattrs
)
1101 brace
.clear
= attr
.clearclass(brace
)
1103 leftbrace
= brace(reverse
=0, middlerelpos
=0.55, innerstrokesrelheight
=0.6, outerstrokesrelheight
=0.7, slantstrokesangle
=-10)
1104 rightbrace
= brace(reverse
=1, middlerelpos
=0.45, innerstrokesrelheight
=0.6, outerstrokesrelheight
=0.7, slantstrokesangle
=10)
1105 belowbrace
= brace(reverse
=1, middlerelpos
=0.55, innerstrokesrelheight
=0.7, outerstrokesrelheight
=0.9, slantstrokesangle
=-10)
1106 abovebrace
= brace(reverse
=0, middlerelpos
=0.45, innerstrokesrelheight
=0.7, outerstrokesrelheight
=0.9, slantstrokesangle
=-10)
1107 straightbrace
= brace(innerstrokesrelheight
=0.5, outerstrokesrelheight
=0.5,
1108 innerstrokesangle
=30, outerstrokesangle
=30, slantstrokesangle
=0,
1109 innerstrokessmoothness
=1.0, outerstrokessmoothness
=1.0)