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 strokepath
= self
.strokepath()
136 # apply global styles
138 file.write("gsave\n")
140 _writestyles(self
.styles
, context
, registry
)
142 if self
.fillstyles
is not None:
143 file.write("newpath\n")
144 fillpath
.outputPS(file, writer
)
146 if self
.strokestyles
is not None and strokepath
is fillpath
:
147 # do efficient stroking + filling if respective paths are identical
148 file.write("gsave\n")
151 _writestyles(self
.fillstyles
, context(), registry
)
154 file.write("eofill\n")
157 file.write("grestore\n")
160 if self
.strokestyles
:
161 file.write("gsave\n")
162 _writestyles(self
.strokestyles
, acontext
, registry
)
164 file.write("stroke\n")
165 # take linewidth into account for bbox when stroking a path
166 bbox
+= strokepath
.bbox().enlarged_pt(0.5*acontext
.linewidth_pt
)
168 if self
.strokestyles
:
169 file.write("grestore\n")
171 # only fill fillpath - for the moment
173 file.write("gsave\n")
174 _writestyles(self
.fillstyles
, context(), registry
)
177 file.write("eofill\n")
180 bbox
+= fillpath
.bbox()
183 file.write("grestore\n")
185 if self
.strokestyles
is not None and (strokepath
is not fillpath
or self
.fillstyles
is None):
186 # this is the only relevant case still left
187 # Note that a possible filling has already been done.
189 if self
.strokestyles
:
190 file.write("gsave\n")
191 _writestyles(self
.strokestyles
, acontext
, registry
)
193 file.write("newpath\n")
194 strokepath
.outputPS(file, writer
)
195 file.write("stroke\n")
196 # take linewidth into account for bbox when stroking a path
197 bbox
+= strokepath
.bbox().enlarged_pt(0.5*acontext
.linewidth_pt
)
199 if self
.strokestyles
:
200 file.write("grestore\n")
202 # now, draw additional elements of decoratedpath
203 self
.ornaments
.processPS(file, writer
, context
, registry
, bbox
)
205 # restore global styles
207 file.write("grestore\n")
209 def processPDF(self
, file, writer
, context
, registry
, bbox
):
210 # draw (stroke and/or fill) the decoratedpath on the canvas
212 def _writestyles(styles
, context
, registry
):
214 style
.processPDF(file, writer
, context
, registry
)
216 def _writestrokestyles(strokestyles
, context
, registry
):
218 for style
in strokestyles
:
219 style
.processPDF(file, writer
, context
, registry
)
222 def _writefillstyles(fillstyles
, context
, registry
):
223 context
.strokeattr
= 0
224 for style
in fillstyles
:
225 style
.processPDF(file, writer
, context
, registry
)
226 context
.strokeattr
= 1
228 strokepath
= self
.strokepath()
231 # apply global styles
233 file.write("q\n") # gsave
235 _writestyles(self
.styles
, context
, registry
)
237 if self
.fillstyles
is not None:
238 fillpath
.outputPDF(file, writer
)
240 if self
.strokestyles
is not None and strokepath
is fillpath
:
241 # do efficient stroking + filling
242 file.write("q\n") # gsave
246 _writefillstyles(self
.fillstyles
, acontext
, registry
)
247 if self
.strokestyles
:
248 _writestrokestyles(self
.strokestyles
, acontext
, registry
)
253 file.write("B\n") # both stroke and fill
254 # take linewidth into account for bbox when stroking a path
255 bbox
+= strokepath
.bbox().enlarged_pt(0.5*acontext
.linewidth_pt
)
257 file.write("Q\n") # grestore
259 # only fill fillpath - for the moment
261 file.write("q\n") # gsave
262 _writefillstyles(self
.fillstyles
, context(), registry
)
267 file.write("f\n") # fill
268 bbox
+= fillpath
.bbox()
271 file.write("Q\n") # grestore
273 if self
.strokestyles
is not None and (strokepath
is not fillpath
or self
.fillstyles
is None):
274 # this is the only relevant case still left
275 # Note that a possible stroking has already been done.
278 if self
.strokestyles
:
279 file.write("q\n") # gsave
280 _writestrokestyles(self
.strokestyles
, acontext
, registry
)
282 strokepath
.outputPDF(file, writer
)
283 file.write("S\n") # stroke
284 # take linewidth into account for bbox when stroking a path
285 bbox
+= strokepath
.bbox().enlarged_pt(0.5*acontext
.linewidth_pt
)
287 if self
.strokestyles
:
288 file.write("Q\n") # grestore
290 # now, draw additional elements of decoratedpath
291 self
.ornaments
.processPDF(file, writer
, context
, registry
, bbox
)
293 # restore global styles
295 file.write("Q\n") # grestore
305 In contrast to path styles, path decorators depend on the concrete
306 path to which they are applied. In particular, they don't make
307 sense without any path and can thus not be used in canvas.set!
311 def decorate(self
, dp
, texrunner
):
312 """apply a style to a given decoratedpath object dp
314 decorate accepts a decoratedpath object dp, applies PathStyle
315 by modifying dp in place.
321 # stroked and filled: basic decos which stroked and fill,
322 # respectively the path
325 class _stroked(deco
, attr
.exclusiveattr
):
327 """stroked is a decorator, which draws the outline of the path"""
329 def __init__(self
, styles
=[]):
330 attr
.exclusiveattr
.__init
__(self
, _stroked
)
331 self
.styles
= attr
.mergeattrs(styles
)
332 attr
.checkattrs(self
.styles
, [style
.strokestyle
])
334 def __call__(self
, styles
=[]):
335 # XXX or should we also merge self.styles
336 return _stroked(styles
)
338 def decorate(self
, dp
, texrunner
):
339 if dp
.strokestyles
is not None:
340 raise RuntimeError("Cannot stroke an already stroked path")
341 dp
.strokestyles
= self
.styles
344 stroked
.clear
= attr
.clearclass(_stroked
)
347 class _filled(deco
, attr
.exclusiveattr
):
349 """filled is a decorator, which fills the interior of the path"""
351 def __init__(self
, styles
=[]):
352 attr
.exclusiveattr
.__init
__(self
, _filled
)
353 self
.styles
= attr
.mergeattrs(styles
)
354 attr
.checkattrs(self
.styles
, [style
.fillstyle
])
356 def __call__(self
, styles
=[]):
357 # XXX or should we also merge self.styles
358 return _filled(styles
)
360 def decorate(self
, dp
, texrunner
):
361 if dp
.fillstyles
is not None:
362 raise RuntimeError("Cannot fill an already filled path")
363 dp
.fillstyles
= self
.styles
366 filled
.clear
= attr
.clearclass(_filled
)
372 # helper function which constructs the arrowhead
374 def _arrowhead(anormpath
, arclenfrombegin
, direction
, size
, angle
, constriction
, constrictionlen
):
376 """helper routine, which returns an arrowhead from a given anormpath
378 - arclenfrombegin: position of arrow in arc length from the start of the path
379 - direction: +1 for an arrow pointing along the direction of anormpath or
380 -1 for an arrow pointing opposite to the direction of normpath
381 - size: size of the arrow as arc length
382 - angle. opening angle
383 - constriction: boolean to indicate whether the constriction point is to be taken into account or not
384 - constrictionlen: arc length of constriction. (not used when constriction is false)
387 # arc length and coordinates of tip
388 tx
, ty
= anormpath
.at(arclenfrombegin
)
390 # construct the template for the arrow by cutting the path at the
391 # corresponding length
392 arrowtemplate
= anormpath
.split([arclenfrombegin
, arclenfrombegin
- direction
* size
])[1]
394 # from this template, we construct the two outer curves of the arrow
395 arrowl
= arrowtemplate
.transformed(trafo
.rotate(-angle
/2.0, tx
, ty
))
396 arrowr
= arrowtemplate
.transformed(trafo
.rotate( angle
/2.0, tx
, ty
))
398 # now come the joining backward parts
400 # constriction point (cx, cy) lies on path
401 cx
, cy
= anormpath
.at(arclenfrombegin
- direction
* constrictionlen
)
402 arrowcr
= path
.line(*(arrowr
.atend() + (cx
,cy
)))
403 arrow
= arrowl
.reversed() << arrowr
<< arrowcr
405 arrow
= arrowl
.reversed() << arrowr
412 _base
= 6 * unit
.v_pt
414 class arrow(deco
, attr
.attr
):
416 """arrow is a decorator which adds an arrow to either side of the path"""
418 def __init__(self
, attrs
=[], pos
=1, reversed=0, size
=_base
, angle
=45, constriction
=0.8):
419 self
.attrs
= attr
.mergeattrs([style
.linestyle
.solid
, filled
] + attrs
)
420 attr
.checkattrs(self
.attrs
, [deco
, style
.fillstyle
, style
.strokestyle
])
422 self
.reversed = reversed
425 self
.constriction
= constriction
427 # calculate absolute arc length of constricition
428 # Note that we have to correct this length because the arrowtemplates are rotated
429 # by self.angle/2 to the left and right. Hence, if we want no constriction, i.e., for
430 # self.constriction = 1, we actually have a length which is approximately shorter
431 # by the given geometrical factor.
432 if self
.constriction
is not None:
433 self
.constrictionlen
= self
.size
* self
.constriction
* math
.cos(math
.radians(self
.angle
/2.0))
435 # if we do not want a constriction, i.e. constriction is None, we still
436 # need constrictionlen for cutting the path
437 self
.constrictionlen
= self
.size
* 1 * math
.cos(math
.radians(self
.angle
/2.0))
439 def __call__(self
, attrs
=None, pos
=None, reversed=None, size
=None, angle
=None, constriction
=_marker
):
445 reversed = self
.reversed
450 if constriction
is _marker
:
451 constriction
= self
.constriction
452 return arrow(attrs
=attrs
, pos
=pos
, reversed=reversed, size
=size
, angle
=angle
, constriction
=constriction
)
454 def decorate(self
, dp
, texrunner
):
458 arclenfrombegin
= (1-self
.reversed)*self
.constrictionlen
+ self
.pos
* (anormpath
.arclen() - self
.constrictionlen
)
459 direction
= self
.reversed and -1 or 1
460 arrowhead
= _arrowhead(anormpath
, arclenfrombegin
, direction
, self
.size
, self
.angle
,
461 self
.constriction
is not None, self
.constrictionlen
)
463 # add arrowhead to decoratedpath
464 dp
.ornaments
.draw(arrowhead
, self
.attrs
)
466 # exlude part of the path from stroking when the arrow is strictly at the begin or the end
467 if self
.pos
== 0 and self
.reversed:
468 dp
.excluderange(0, min(self
.size
, self
.constrictionlen
))
469 elif self
.pos
== 1 and not self
.reversed:
470 dp
.excluderange(anormpath
.end() - min(self
.size
, self
.constrictionlen
), anormpath
.end())
472 arrow
.clear
= attr
.clearclass(arrow
)
474 # arrows at begin of path
475 barrow
= arrow(pos
=0, reversed=1)
476 barrow
.SMALL
= barrow(size
=_base
/math
.sqrt(64))
477 barrow
.SMALl
= barrow(size
=_base
/math
.sqrt(32))
478 barrow
.SMAll
= barrow(size
=_base
/math
.sqrt(16))
479 barrow
.SMall
= barrow(size
=_base
/math
.sqrt(8))
480 barrow
.Small
= barrow(size
=_base
/math
.sqrt(4))
481 barrow
.small
= barrow(size
=_base
/math
.sqrt(2))
482 barrow
.normal
= barrow(size
=_base
)
483 barrow
.large
= barrow(size
=_base
*math
.sqrt(2))
484 barrow
.Large
= barrow(size
=_base
*math
.sqrt(4))
485 barrow
.LArge
= barrow(size
=_base
*math
.sqrt(8))
486 barrow
.LARge
= barrow(size
=_base
*math
.sqrt(16))
487 barrow
.LARGe
= barrow(size
=_base
*math
.sqrt(32))
488 barrow
.LARGE
= barrow(size
=_base
*math
.sqrt(64))
490 # arrows at end of path
492 earrow
.SMALL
= earrow(size
=_base
/math
.sqrt(64))
493 earrow
.SMALl
= earrow(size
=_base
/math
.sqrt(32))
494 earrow
.SMAll
= earrow(size
=_base
/math
.sqrt(16))
495 earrow
.SMall
= earrow(size
=_base
/math
.sqrt(8))
496 earrow
.Small
= earrow(size
=_base
/math
.sqrt(4))
497 earrow
.small
= earrow(size
=_base
/math
.sqrt(2))
498 earrow
.normal
= earrow(size
=_base
)
499 earrow
.large
= earrow(size
=_base
*math
.sqrt(2))
500 earrow
.Large
= earrow(size
=_base
*math
.sqrt(4))
501 earrow
.LArge
= earrow(size
=_base
*math
.sqrt(8))
502 earrow
.LARge
= earrow(size
=_base
*math
.sqrt(16))
503 earrow
.LARGe
= earrow(size
=_base
*math
.sqrt(32))
504 earrow
.LARGE
= earrow(size
=_base
*math
.sqrt(64))
507 class text(deco
, attr
.attr
):
508 """a simple text decorator"""
510 def __init__(self
, text
, textattrs
=[], angle
=0, relangle
=None, textdist
=0.2,
511 relarclenpos
=0.5, arclenfrombegin
=None, arclenfromend
=None,
513 if arclenfrombegin
is not None and arclenfromend
is not None:
514 raise ValueError("either set arclenfrombegin or arclenfromend")
516 self
.textattrs
= textattrs
518 self
.relangle
= relangle
519 self
.textdist
= textdist
520 self
.relarclenpos
= relarclenpos
521 self
.arclenfrombegin
= arclenfrombegin
522 self
.arclenfromend
= arclenfromend
523 self
.texrunner
= texrunner
525 def decorate(self
, dp
, texrunner
):
527 texrunner
= self
.texrunner
528 from . import text
as textmodule
529 textattrs
= attr
.mergeattrs([textmodule
.halign
.center
, textmodule
.vshift
.mathaxis
] + self
.textattrs
)
532 if self
.arclenfrombegin
is not None:
533 param
= dp
.path
.begin() + self
.arclenfrombegin
534 elif self
.arclenfromend
is not None:
535 param
= dp
.path
.end() - self
.arclenfromend
537 # relarcpos is used, when neither arcfrombegin nor arcfromend is given
538 param
= self
.relarclenpos
* dp
.path
.arclen()
539 x
, y
= dp
.path
.at(param
)
541 if self
.relangle
is not None:
542 a
= dp
.path
.trafo(param
).apply_pt(math
.cos(self
.relangle
*math
.pi
/180), math
.sin(self
.relangle
*math
.pi
/180))
543 b
= dp
.path
.trafo(param
).apply_pt(0, 0)
544 angle
= math
.atan2(a
[1] - b
[1], a
[0] - b
[0])
546 angle
= self
.angle
*math
.pi
/180
547 t
= texrunner
.text(x
, y
, self
.text
, textattrs
)
548 t
.linealign(self
.textdist
, math
.cos(angle
), math
.sin(angle
))
549 dp
.ornaments
.insert(t
)
551 class curvedtext(deco
, attr
.attr
):
552 """a text decorator for curved text
554 - text: is typeset along the path to which this decorator is applied
555 - relarclenpos: position for the base point of the text (default: 0)
556 - arlenfrombegin, arclenfromend: alternative ways of specifying the position of the base point;
557 use of relarclenpos, arclenfrombegin and arclenfromend is mutually exclusive
558 - textattrs, texrunner: standard text arguments (defaults: [] resp None)
562 # defaulttextattrs = [textmodule.halign.center] # TODO: not possible due to cyclic import issue
564 def __init__(self
, text
, textattrs
=[],
565 relarclenpos
=0.5, arclenfrombegin
=None, arclenfromend
=None,
566 texrunner
=None, exclude
=None):
567 if arclenfrombegin
is not None and arclenfromend
is not None:
568 raise ValueError("either set arclenfrombegin or arclenfromend")
570 self
.textattrs
= textattrs
571 self
.relarclenpos
= relarclenpos
572 self
.arclenfrombegin
= arclenfrombegin
573 self
.arclenfromend
= arclenfromend
574 self
.texrunner
= texrunner
575 self
.exclude
= exclude
577 def decorate(self
, dp
, texrunner
):
579 texrunner
= self
.texrunner
580 from . import text
as textmodule
581 self
.defaulttextattrs
= [textmodule
.halign
.center
]
584 if self
.arclenfrombegin
is not None:
585 textpos
= dp
.path
.begin() + self
.arclenfrombegin
586 elif self
.arclenfromend
is not None:
587 textpos
= dp
.path
.end() - self
.arclenfromend
589 # relarcpos is used if neither arcfrombegin nor arcfromend is given
590 textpos
= self
.relarclenpos
* dp
.path
.arclen()
592 textattrs
= self
.defaulttextattrs
+ self
.textattrs
593 t
= texrunner
.text(0, 0, self
.text
, textattrs
, singlecharmode
=1)
596 # we copy the style from the original textbox and modify the position for each dvicanvas item
597 c
= canvas
.canvas(t
.dvicanvas
.styles
)
598 for item
in t
.dvicanvas
.items
:
600 bbox
= bbox
.transformed(t
.texttrafo
)
602 atrafo
= dp
.path
.trafo(textpos
+x
)
603 c
.insert(item
, [t
.texttrafo
] + [trafo
.translate(-x
, 0)] + [atrafo
])
604 if self
.exclude
is not None:
605 dp
.excluderange(textpos
+bbox
.left()-self
.exclude
, textpos
+bbox
.right()+self
.exclude
)
607 dp
.ornaments
.insert(c
)
610 class shownormpath(deco
, attr
.attr
):
612 default_normline_attrs
= [color
.rgb
.blue
]
613 default_normcurve_attrs
= [color
.rgb
.green
]
614 default_endpoint_attrs
= []
615 default_controlline_attrs
= [color
.rgb
.red
, style
.linestyle
.dashed
]
616 default_controlpoint_attrs
= [color
.rgb
.red
]
618 def __init__(self
, normline_attrs
=[], normcurve_attrs
=[],
619 endpoint_size
=0.05*unit
.v_cm
, endpoint_attrs
=[],
620 controlline_attrs
=[],
621 controlpoint_size
=0.05*unit
.v_cm
, controlpoint_attrs
=[]):
622 self
.normline_attrs
= attr
.refineattrs(normline_attrs
, self
.default_normline_attrs
, [style
.strokestyle
])
623 self
.normcurve_attrs
= attr
.refineattrs(normcurve_attrs
, self
.default_normcurve_attrs
, [style
.strokestyle
])
624 self
.endpoint_size_pt
= unit
.topt(endpoint_size
)
625 self
.endpoint_attrs
= attr
.refineattrs(endpoint_attrs
, self
.default_endpoint_attrs
, [style
.fillstyle
])
626 self
.controlline_attrs
= attr
.refineattrs(controlline_attrs
, self
.default_controlline_attrs
, [style
.strokestyle
])
627 self
.controlpoint_size_pt
= unit
.topt(controlpoint_size
)
628 self
.controlpoint_attrs
= attr
.refineattrs(controlpoint_attrs
, self
.default_controlpoint_attrs
, [style
.fillstyle
])
630 def decorate(self
, dp
, texrunner
):
632 for normsubpath
in dp
.path
.normsubpaths
:
633 for i
, normsubpathitem
in enumerate(normsubpath
.normsubpathitems
):
634 p
= path
.path(path
.moveto_pt(*normsubpathitem
.atbegin_pt()), normsubpathitem
.pathitem())
635 if isinstance(normsubpathitem
, normpath
.normcurve_pt
):
636 if self
.normcurve_attrs
is not None:
637 dp
.ornaments
.stroke(p
, self
.normcurve_attrs
)
639 if self
.normline_attrs
is not None:
640 dp
.ornaments
.stroke(p
, self
.normline_attrs
)
641 for normsubpath
in dp
.path
.normsubpaths
:
642 for i
, normsubpathitem
in enumerate(normsubpath
.normsubpathitems
):
643 if isinstance(normsubpathitem
, normpath
.normcurve_pt
):
644 if self
.controlline_attrs
is not None:
645 dp
.ornaments
.stroke(path
.line_pt(normsubpathitem
.x0_pt
, normsubpathitem
.y0_pt
,
646 normsubpathitem
.x1_pt
, normsubpathitem
.y1_pt
), self
.controlline_attrs
)
647 dp
.ornaments
.stroke(path
.line_pt(normsubpathitem
.x2_pt
, normsubpathitem
.y2_pt
,
648 normsubpathitem
.x3_pt
, normsubpathitem
.y3_pt
), self
.controlline_attrs
)
649 if self
.controlpoint_attrs
is not None:
650 dp
.ornaments
.fill(path
.circle_pt(normsubpathitem
.x1_pt
, normsubpathitem
.y1_pt
, self
.controlpoint_size_pt
), self
.controlpoint_attrs
)
651 dp
.ornaments
.fill(path
.circle_pt(normsubpathitem
.x2_pt
, normsubpathitem
.y2_pt
, self
.controlpoint_size_pt
), self
.controlpoint_attrs
)
652 if self
.endpoint_attrs
is not None:
653 for normsubpath
in dp
.path
.normsubpaths
:
654 for i
, normsubpathitem
in enumerate(normsubpath
.normsubpathitems
):
656 x_pt
, y_pt
= normsubpathitem
.atbegin_pt()
657 dp
.ornaments
.fill(path
.circle_pt(x_pt
, y_pt
, self
.endpoint_size_pt
), self
.endpoint_attrs
)
658 x_pt
, y_pt
= normsubpathitem
.atend_pt()
659 dp
.ornaments
.fill(path
.circle_pt(x_pt
, y_pt
, self
.endpoint_size_pt
), self
.endpoint_attrs
)
662 class linehatched(deco
, attr
.exclusiveattr
, attr
.clearclass
):
663 """draws a pattern with explicit lines
665 This class acts as a drop-in replacement for postscript patterns
666 from the pattern module which are not understood by some printers"""
668 def __init__(self
, dist
, angle
, strokestyles
=[], cross
=0):
669 attr
.clearclass
.__init
__(self
, _filled
)
670 attr
.exclusiveattr
.__init
__(self
, linehatched
)
673 self
.strokestyles
= attr
.mergeattrs([style
.linewidth
.THIN
] + strokestyles
)
674 attr
.checkattrs(self
.strokestyles
, [style
.strokestyle
])
677 def __call__(self
, dist
=None, angle
=None, strokestyles
=None, cross
=None):
682 if strokestyles
is None:
683 strokestyles
= self
.strokestyles
686 return linehatched(dist
, angle
, strokestyles
, cross
)
688 def _decocanvas(self
, angle
, dp
, texrunner
):
690 dist_pt
= unit
.topt(self
.dist
)
692 c
= canvas
.canvas([canvas
.clip(dp
.path
)])
693 llx_pt
, lly_pt
, urx_pt
, ury_pt
= dp
.path
.bbox().highrestuple_pt()
694 center_pt
= 0.5*(llx_pt
+urx_pt
), 0.5*(lly_pt
+ury_pt
)
695 radius_pt
= 0.5*math
.hypot(urx_pt
-llx_pt
, ury_pt
-lly_pt
) + dist_pt
696 n
= int(2*radius_pt
/ dist_pt
) + 1
698 x_pt
= center_pt
[0] - radius_pt
+ i
*dist_pt
699 c
.stroke(path
.line_pt(x_pt
, center_pt
[1]-radius_pt
, x_pt
, center_pt
[1]+radius_pt
),
700 [trafo
.rotate_pt(angle
, center_pt
[0], center_pt
[1])] + self
.strokestyles
)
703 def decorate(self
, dp
, texrunner
):
704 dp
.ornaments
.insert(self
._decocanvas
(self
.angle
, dp
, texrunner
))
706 dp
.ornaments
.insert(self
._decocanvas
(self
.angle
+90, dp
, texrunner
))
708 def merge(self
, attrs
):
709 # act as attr.clearclass and as attr.exclusiveattr at the same time
710 newattrs
= attr
.exclusiveattr
.merge(self
, attrs
)
711 return attr
.clearclass
.merge(self
, newattrs
)
713 linehatched
.clear
= attr
.clearclass(linehatched
)
715 _hatch_base
= 0.1 * unit
.v_cm
717 linehatched0
= linehatched(_hatch_base
, 0)
718 linehatched0
.SMALL
= linehatched0(_hatch_base
/math
.sqrt(64))
719 linehatched0
.SMALL
= linehatched0(_hatch_base
/math
.sqrt(64))
720 linehatched0
.SMALl
= linehatched0(_hatch_base
/math
.sqrt(32))
721 linehatched0
.SMAll
= linehatched0(_hatch_base
/math
.sqrt(16))
722 linehatched0
.SMall
= linehatched0(_hatch_base
/math
.sqrt(8))
723 linehatched0
.Small
= linehatched0(_hatch_base
/math
.sqrt(4))
724 linehatched0
.small
= linehatched0(_hatch_base
/math
.sqrt(2))
725 linehatched0
.normal
= linehatched0(_hatch_base
)
726 linehatched0
.large
= linehatched0(_hatch_base
*math
.sqrt(2))
727 linehatched0
.Large
= linehatched0(_hatch_base
*math
.sqrt(4))
728 linehatched0
.LArge
= linehatched0(_hatch_base
*math
.sqrt(8))
729 linehatched0
.LARge
= linehatched0(_hatch_base
*math
.sqrt(16))
730 linehatched0
.LARGe
= linehatched0(_hatch_base
*math
.sqrt(32))
731 linehatched0
.LARGE
= linehatched0(_hatch_base
*math
.sqrt(64))
733 linehatched45
= linehatched(_hatch_base
, 45)
734 linehatched45
.SMALL
= linehatched45(_hatch_base
/math
.sqrt(64))
735 linehatched45
.SMALl
= linehatched45(_hatch_base
/math
.sqrt(32))
736 linehatched45
.SMAll
= linehatched45(_hatch_base
/math
.sqrt(16))
737 linehatched45
.SMall
= linehatched45(_hatch_base
/math
.sqrt(8))
738 linehatched45
.Small
= linehatched45(_hatch_base
/math
.sqrt(4))
739 linehatched45
.small
= linehatched45(_hatch_base
/math
.sqrt(2))
740 linehatched45
.normal
= linehatched45(_hatch_base
)
741 linehatched45
.large
= linehatched45(_hatch_base
*math
.sqrt(2))
742 linehatched45
.Large
= linehatched45(_hatch_base
*math
.sqrt(4))
743 linehatched45
.LArge
= linehatched45(_hatch_base
*math
.sqrt(8))
744 linehatched45
.LARge
= linehatched45(_hatch_base
*math
.sqrt(16))
745 linehatched45
.LARGe
= linehatched45(_hatch_base
*math
.sqrt(32))
746 linehatched45
.LARGE
= linehatched45(_hatch_base
*math
.sqrt(64))
748 linehatched90
= linehatched(_hatch_base
, 90)
749 linehatched90
.SMALL
= linehatched90(_hatch_base
/math
.sqrt(64))
750 linehatched90
.SMALl
= linehatched90(_hatch_base
/math
.sqrt(32))
751 linehatched90
.SMAll
= linehatched90(_hatch_base
/math
.sqrt(16))
752 linehatched90
.SMall
= linehatched90(_hatch_base
/math
.sqrt(8))
753 linehatched90
.Small
= linehatched90(_hatch_base
/math
.sqrt(4))
754 linehatched90
.small
= linehatched90(_hatch_base
/math
.sqrt(2))
755 linehatched90
.normal
= linehatched90(_hatch_base
)
756 linehatched90
.large
= linehatched90(_hatch_base
*math
.sqrt(2))
757 linehatched90
.Large
= linehatched90(_hatch_base
*math
.sqrt(4))
758 linehatched90
.LArge
= linehatched90(_hatch_base
*math
.sqrt(8))
759 linehatched90
.LARge
= linehatched90(_hatch_base
*math
.sqrt(16))
760 linehatched90
.LARGe
= linehatched90(_hatch_base
*math
.sqrt(32))
761 linehatched90
.LARGE
= linehatched90(_hatch_base
*math
.sqrt(64))
763 linehatched135
= linehatched(_hatch_base
, 135)
764 linehatched135
.SMALL
= linehatched135(_hatch_base
/math
.sqrt(64))
765 linehatched135
.SMALl
= linehatched135(_hatch_base
/math
.sqrt(32))
766 linehatched135
.SMAll
= linehatched135(_hatch_base
/math
.sqrt(16))
767 linehatched135
.SMall
= linehatched135(_hatch_base
/math
.sqrt(8))
768 linehatched135
.Small
= linehatched135(_hatch_base
/math
.sqrt(4))
769 linehatched135
.small
= linehatched135(_hatch_base
/math
.sqrt(2))
770 linehatched135
.normal
= linehatched135(_hatch_base
)
771 linehatched135
.large
= linehatched135(_hatch_base
*math
.sqrt(2))
772 linehatched135
.Large
= linehatched135(_hatch_base
*math
.sqrt(4))
773 linehatched135
.LArge
= linehatched135(_hatch_base
*math
.sqrt(8))
774 linehatched135
.LARge
= linehatched135(_hatch_base
*math
.sqrt(16))
775 linehatched135
.LARGe
= linehatched135(_hatch_base
*math
.sqrt(32))
776 linehatched135
.LARGE
= linehatched135(_hatch_base
*math
.sqrt(64))
778 crosslinehatched0
= linehatched(_hatch_base
, 0, cross
=1)
779 crosslinehatched0
.SMALL
= crosslinehatched0(_hatch_base
/math
.sqrt(64))
780 crosslinehatched0
.SMALl
= crosslinehatched0(_hatch_base
/math
.sqrt(32))
781 crosslinehatched0
.SMAll
= crosslinehatched0(_hatch_base
/math
.sqrt(16))
782 crosslinehatched0
.SMall
= crosslinehatched0(_hatch_base
/math
.sqrt(8))
783 crosslinehatched0
.Small
= crosslinehatched0(_hatch_base
/math
.sqrt(4))
784 crosslinehatched0
.small
= crosslinehatched0(_hatch_base
/math
.sqrt(2))
785 crosslinehatched0
.normal
= crosslinehatched0
786 crosslinehatched0
.large
= crosslinehatched0(_hatch_base
*math
.sqrt(2))
787 crosslinehatched0
.Large
= crosslinehatched0(_hatch_base
*math
.sqrt(4))
788 crosslinehatched0
.LArge
= crosslinehatched0(_hatch_base
*math
.sqrt(8))
789 crosslinehatched0
.LARge
= crosslinehatched0(_hatch_base
*math
.sqrt(16))
790 crosslinehatched0
.LARGe
= crosslinehatched0(_hatch_base
*math
.sqrt(32))
791 crosslinehatched0
.LARGE
= crosslinehatched0(_hatch_base
*math
.sqrt(64))
793 crosslinehatched45
= linehatched(_hatch_base
, 45, cross
=1)
794 crosslinehatched45
.SMALL
= crosslinehatched45(_hatch_base
/math
.sqrt(64))
795 crosslinehatched45
.SMALl
= crosslinehatched45(_hatch_base
/math
.sqrt(32))
796 crosslinehatched45
.SMAll
= crosslinehatched45(_hatch_base
/math
.sqrt(16))
797 crosslinehatched45
.SMall
= crosslinehatched45(_hatch_base
/math
.sqrt(8))
798 crosslinehatched45
.Small
= crosslinehatched45(_hatch_base
/math
.sqrt(4))
799 crosslinehatched45
.small
= crosslinehatched45(_hatch_base
/math
.sqrt(2))
800 crosslinehatched45
.normal
= crosslinehatched45
801 crosslinehatched45
.large
= crosslinehatched45(_hatch_base
*math
.sqrt(2))
802 crosslinehatched45
.Large
= crosslinehatched45(_hatch_base
*math
.sqrt(4))
803 crosslinehatched45
.LArge
= crosslinehatched45(_hatch_base
*math
.sqrt(8))
804 crosslinehatched45
.LARge
= crosslinehatched45(_hatch_base
*math
.sqrt(16))
805 crosslinehatched45
.LARGe
= crosslinehatched45(_hatch_base
*math
.sqrt(32))
806 crosslinehatched45
.LARGE
= crosslinehatched45(_hatch_base
*math
.sqrt(64))
809 class colorgradient(deco
, attr
.attr
):
810 """inserts pieces of the path in different colors"""
812 def __init__(self
, grad
, attrs
=[], steps
=20):
817 def decorate(self
, dp
, texrunner
):
821 colors
= [self
.grad
.select(n
, self
.steps
) for n
in range(self
.steps
)]
823 params
= dp
.path
.arclentoparam([l
*i
/float(self
.steps
) for i
in range(self
.steps
)])
827 # treat the end pieces separately
828 c
.stroke(dp
.path
.split(params
[1])[1], attr
.mergeattrs([colors
[0]] + self
.attrs
))
829 for n
in range(1,self
.steps
-1):
830 c
.stroke(dp
.path
.split([params
[n
-1],params
[n
+1]])[1], attr
.mergeattrs([colors
[n
]] + self
.attrs
))
831 c
.stroke(dp
.path
.split(params
[-2])[0], attr
.mergeattrs([colors
[-1]] + self
.attrs
))
832 dp
.ornaments
.insert(c
)
835 class brace(deco
, attr
.attr
):
836 r
"""draws a nicely curled brace
838 In most cases, the original line is not wanted use canvas.canvas.draw(..) for it
840 Geometrical parameters:
843 ____________/ \__________
847 totalheight distance from the jaws to the middle cap
848 barthickness thickness of the main bars
849 innerstrokesthickness thickness of the two ending strokes
850 outerstrokesthickness thickness of the inner strokes at the middle cap
851 innerstrokesrelheight height of the inner/outer strokes, relative to the total height
852 outerstrokesrelheight this determines the angle of the main bars!
854 Note: if innerstrokesrelheight + outerstrokesrelheight == 1 then the main bars
855 will be aligned parallel to the connecting line between the endpoints
856 outerstrokesangle angle of the two ending strokes
857 innerstrokesangle angle between the inner strokes at the middle cap
858 slantstrokesangle extra slanting of the inner/outer strokes
859 innerstrokessmoothness smoothing parameter for the inner + outer strokes
860 outerstrokessmoothness should be around 1 (allowed: [0,infty))
861 middlerelpos position of the middle cap (0 == left, 1 == right)
863 # This code is experimental because it is unclear
864 # how the brace fits into the concepts of PyX
867 # - a brace needs to be decoratable with text
868 # it needs stroking and filling attributes
869 # - the brace is not really a box:
870 # it has two "anchor" points that are important for aligning it to other things
871 # and one "anchor" point (plus direction) for aligning other things
872 # - a brace is not a deformer:
873 # it does not look at anything else than begin/endpoint of a path
874 # - a brace might be a connector (which is to be dissolved into the box concept later?)
876 def __init__(self
, reverse
=1, stretch
=None, dist
=None, fillattrs
=[],
877 totalheight
=12*unit
.x_pt
,
878 barthickness
=0.5*unit
.x_pt
, innerstrokesthickness
=0.25*unit
.x_pt
, outerstrokesthickness
=0.25*unit
.x_pt
,
879 innerstrokesrelheight
=0.6, outerstrokesrelheight
=0.7,
880 innerstrokesangle
=30, outerstrokesangle
=25, slantstrokesangle
=5,
881 innerstrokessmoothness
=2.0, outerstrokessmoothness
=2.5,
883 self
.fillattrs
= fillattrs
884 self
.reverse
= reverse
885 self
.stretch
= stretch
887 self
.totalheight
= totalheight
888 self
.barthickness
= barthickness
889 self
.innerstrokesthickness
= innerstrokesthickness
890 self
.outerstrokesthickness
= outerstrokesthickness
891 self
.innerstrokesrelheight
= innerstrokesrelheight
892 self
.outerstrokesrelheight
= outerstrokesrelheight
893 self
.innerstrokesangle
= innerstrokesangle
894 self
.outerstrokesangle
= outerstrokesangle
895 self
.slantstrokesangle
= slantstrokesangle
896 self
.innerstrokessmoothness
= innerstrokessmoothness
897 self
.outerstrokessmoothness
= outerstrokessmoothness
898 self
.middlerelpos
= middlerelpos
900 def __call__(self
, **kwargs
):
901 for name
in ["reverse", "stretch", "dist", "fillattrs",
902 "totalheight", "barthickness", "innerstrokesthickness", "outerstrokesthickness",
903 "innerstrokesrelheight", "outerstrokesrelheight", "innerstrokesangle", "outerstrokesangle", "slantstrokesangle",
904 "innerstrokessmoothness", "outerstrokessmoothness", "middlerelpos"]:
905 if name
not in kwargs
:
906 kwargs
[name
] = self
.__dict
__[name
]
907 return brace(**kwargs
)
909 def _halfbracepath_pt(self
, length_pt
, height_pt
, ilength_pt
, olength_pt
, # <<<
910 ithick_pt
, othick_pt
, bthick_pt
, cos_iangle
, sin_iangle
, cos_oangle
,
911 sin_oangle
, cos_slangle
, sin_slangle
):
913 ismooth
= self
.innerstrokessmoothness
914 osmooth
= self
.outerstrokessmoothness
916 # these two parameters are not important enough to be seen outside
917 inner_cap_param
= 1.5
918 outer_cap_param
= 2.5
919 outerextracurved
= 0.6 # in (0, 1]
920 # 1.0 will lead to F=G, the outer strokes will not be curved at their ends.
921 # The smaller, the more curvature
923 # build an orientation path (three straight lines)
928 # _/ \______________________________________q5
934 # get the points for that:
935 q1
= (0, height_pt
- inner_cap_param
* ithick_pt
+ 0.5*ithick_pt
/sin_iangle
)
936 q2
= (q1
[0] + ilength_pt
* sin_iangle
,
937 q1
[1] - ilength_pt
* cos_iangle
)
939 q5
= (q6
[0] - olength_pt
* sin_oangle
,
940 q6
[1] + olength_pt
* cos_oangle
)
941 bardir
= (q5
[0] - q2
[0], q5
[1] - q2
[1])
942 bardirnorm
= math
.hypot(*bardir
)
943 bardir
= (bardir
[0]/bardirnorm
, bardir
[1]/bardirnorm
)
944 ismoothlength_pt
= ilength_pt
* ismooth
945 osmoothlength_pt
= olength_pt
* osmooth
946 if bardirnorm
< ismoothlength_pt
+ osmoothlength_pt
:
947 ismoothlength_pt
= bardirnorm
* ismoothlength_pt
/ (ismoothlength_pt
+ osmoothlength_pt
)
948 osmoothlength_pt
= bardirnorm
* osmoothlength_pt
/ (ismoothlength_pt
+ osmoothlength_pt
)
949 q3
= (q2
[0] + ismoothlength_pt
* bardir
[0],
950 q2
[1] + ismoothlength_pt
* bardir
[1])
951 q4
= (q5
[0] - osmoothlength_pt
* bardir
[0],
952 q5
[1] - osmoothlength_pt
* bardir
[1])
958 # / \ B2C2________D2___________E2_______F2___G2
959 # \______________________________________ \
960 # B1,C1 D1 E1 F1 G1 \
966 # the halfbraces meet in P and A1:
968 A1
= (0, height_pt
- inner_cap_param
* ithick_pt
)
969 # A2 is A1, shifted by the inner thickness
970 A2
= (A1
[0] + ithick_pt
* cos_iangle
,
971 A1
[1] + ithick_pt
* sin_iangle
)
972 s
, t
= deformer
.intersection(P
, A2
, (cos_slangle
, sin_slangle
), (sin_iangle
, -cos_iangle
))
973 O
= (P
[0] + s
* cos_slangle
,
974 P
[1] + s
* sin_slangle
)
976 # from D1 to E1 is the straight part of the brace
977 # also back from E2 to D1
978 D1
= (q3
[0] + bthick_pt
* bardir
[1],
979 q3
[1] - bthick_pt
* bardir
[0])
980 D2
= (q3
[0] - bthick_pt
* bardir
[1],
981 q3
[1] + bthick_pt
* bardir
[0])
982 E1
= (q4
[0] + bthick_pt
* bardir
[1],
983 q4
[1] - bthick_pt
* bardir
[0])
984 E2
= (q4
[0] - bthick_pt
* bardir
[1],
985 q4
[1] + bthick_pt
* bardir
[0])
986 # I1, I2 are the control points at the outer stroke
987 I1
= (q6
[0] - 0.5 * othick_pt
* cos_oangle
,
988 q6
[1] - 0.5 * othick_pt
* sin_oangle
)
989 I2
= (q6
[0] + 0.5 * othick_pt
* cos_oangle
,
990 q6
[1] + 0.5 * othick_pt
* sin_oangle
)
991 # get the control points for the curved parts of the brace
992 s
, t
= deformer
.intersection(A1
, D1
, (sin_iangle
, -cos_iangle
), bardir
)
993 B1
= (D1
[0] + t
* bardir
[0],
994 D1
[1] + t
* bardir
[1])
995 s
, t
= deformer
.intersection(A2
, D2
, (sin_iangle
, -cos_iangle
), bardir
)
996 B2
= (D2
[0] + t
* bardir
[0],
997 D2
[1] + t
* bardir
[1])
998 s
, t
= deformer
.intersection(E1
, I1
, bardir
, (-sin_oangle
, cos_oangle
))
999 G1
= (E1
[0] + s
* bardir
[0],
1000 E1
[1] + s
* bardir
[1])
1001 s
, t
= deformer
.intersection(E2
, I2
, bardir
, (-sin_oangle
, cos_oangle
))
1002 G2
= (E2
[0] + s
* bardir
[0],
1003 E2
[1] + s
* bardir
[1])
1004 # at the inner strokes: use curvature zero at both ends
1007 # at the outer strokes: use curvature zero only at the connection to
1009 F1
= (outerextracurved
* G1
[0] + (1 - outerextracurved
) * E1
[0],
1010 outerextracurved
* G1
[1] + (1 - outerextracurved
) * E1
[1])
1011 F2
= (outerextracurved
* G2
[0] + (1 - outerextracurved
) * E2
[0],
1012 outerextracurved
* G2
[1] + (1 - outerextracurved
) * E2
[1])
1013 # the tip of the outer stroke, endpoints of the bezier curve
1014 H1
= (I1
[0] - outer_cap_param
* othick_pt
* sin_oangle
,
1015 I1
[1] + outer_cap_param
* othick_pt
* cos_oangle
)
1016 H2
= (I2
[0] - outer_cap_param
* othick_pt
* sin_oangle
,
1017 I2
[1] + outer_cap_param
* othick_pt
* cos_oangle
)
1019 #for qq in [A1,B1,C1,D1,E1,F1,G1,H1,I1,
1020 # A2,B2,C2,D2,E2,F2,G2,H2,I2,
1023 # cc.fill(path.circle(qq[0], qq[1], 0.5), [color.rgb.green])
1025 # now build the right halfbrace
1026 bracepath
= path
.path(path
.moveto_pt(*A1
))
1027 bracepath
.append(path
.curveto_pt(B1
[0], B1
[1], C1
[0], C1
[1], D1
[0], D1
[1]))
1028 bracepath
.append(path
.lineto_pt(E1
[0], E1
[1]))
1029 bracepath
.append(path
.curveto_pt(F1
[0], F1
[1], G1
[0], G1
[1], H1
[0], H1
[1]))
1030 # the tip of the right halfbrace
1031 bracepath
.append(path
.curveto_pt(I1
[0], I1
[1], I2
[0], I2
[1], H2
[0], H2
[1]))
1032 # the rest of the right halfbrace
1033 bracepath
.append(path
.curveto_pt(G2
[0], G2
[1], F2
[0], F2
[1], E2
[0], E2
[1]))
1034 bracepath
.append(path
.lineto_pt(D2
[0], D2
[1]))
1035 bracepath
.append(path
.curveto_pt(C2
[0], C2
[1], B2
[0], B2
[1], A2
[0], A2
[1]))
1036 # the tip in the middle of the brace
1037 bracepath
.append(path
.curveto_pt(O
[0], O
[1], O
[0], O
[1], P
[0], P
[1]))
1042 def _bracepath(self
, x0_pt
, y0_pt
, x1_pt
, y1_pt
): # <<<
1043 height_pt
= unit
.topt(self
.totalheight
)
1044 totallength_pt
= math
.hypot(x1_pt
- x0_pt
, y1_pt
- y0_pt
)
1045 leftlength_pt
= self
.middlerelpos
* totallength_pt
1046 rightlength_pt
= totallength_pt
- leftlength_pt
1047 ithick_pt
= unit
.topt(self
.innerstrokesthickness
)
1048 othick_pt
= unit
.topt(self
.outerstrokesthickness
)
1049 bthick_pt
= unit
.topt(self
.barthickness
)
1051 # create the left halfbrace with positive slanting
1052 # because we will mirror this part
1053 cos_iangle
= math
.cos(math
.radians(0.5*self
.innerstrokesangle
- self
.slantstrokesangle
))
1054 sin_iangle
= math
.sin(math
.radians(0.5*self
.innerstrokesangle
- self
.slantstrokesangle
))
1055 cos_oangle
= math
.cos(math
.radians(self
.outerstrokesangle
- self
.slantstrokesangle
))
1056 sin_oangle
= math
.sin(math
.radians(self
.outerstrokesangle
- self
.slantstrokesangle
))
1057 cos_slangle
= math
.cos(math
.radians(-self
.slantstrokesangle
))
1058 sin_slangle
= math
.sin(math
.radians(-self
.slantstrokesangle
))
1059 ilength_pt
= self
.innerstrokesrelheight
* height_pt
/ cos_iangle
1060 olength_pt
= self
.outerstrokesrelheight
* height_pt
/ cos_oangle
1062 bracepath
= self
._halfbracepath
_pt
(leftlength_pt
, height_pt
,
1063 ilength_pt
, olength_pt
, ithick_pt
, othick_pt
, bthick_pt
, cos_iangle
,
1064 sin_iangle
, cos_oangle
, sin_oangle
, cos_slangle
,
1065 sin_slangle
).reversed().transformed(trafo
.mirror(90))
1067 # create the right halfbrace with negative slanting
1068 cos_iangle
= math
.cos(math
.radians(0.5*self
.innerstrokesangle
+ self
.slantstrokesangle
))
1069 sin_iangle
= math
.sin(math
.radians(0.5*self
.innerstrokesangle
+ self
.slantstrokesangle
))
1070 cos_oangle
= math
.cos(math
.radians(self
.outerstrokesangle
+ self
.slantstrokesangle
))
1071 sin_oangle
= math
.sin(math
.radians(self
.outerstrokesangle
+ self
.slantstrokesangle
))
1072 cos_slangle
= math
.cos(math
.radians(-self
.slantstrokesangle
))
1073 sin_slangle
= math
.sin(math
.radians(-self
.slantstrokesangle
))
1074 ilength_pt
= self
.innerstrokesrelheight
* height_pt
/ cos_iangle
1075 olength_pt
= self
.outerstrokesrelheight
* height_pt
/ cos_oangle
1077 bracepath
= bracepath
<< self
._halfbracepath
_pt
(rightlength_pt
, height_pt
,
1078 ilength_pt
, olength_pt
, ithick_pt
, othick_pt
, bthick_pt
, cos_iangle
,
1079 sin_iangle
, cos_oangle
, sin_oangle
, cos_slangle
,
1082 return bracepath
.transformed(
1083 # two trafos for matching the given endpoints
1084 trafo
.translate_pt(x0_pt
, y0_pt
) *
1085 trafo
.rotate_pt(math
.degrees(math
.atan2(y1_pt
-y0_pt
, x1_pt
-x0_pt
))) *
1086 # one trafo to move the brace's left outer stroke to zero
1087 trafo
.translate_pt(leftlength_pt
, 0))
1090 def decorate(self
, dp
, texrunner
):
1092 x0_pt
, y0_pt
= dp
.path
.atbegin_pt()
1093 x1_pt
, y1_pt
= dp
.path
.atend_pt()
1095 x0_pt
, y0_pt
, x1_pt
, y1_pt
= x1_pt
, y1_pt
, x0_pt
, y0_pt
1096 if self
.stretch
is not None:
1097 xm
, ym
= 0.5*(x0_pt
+x1_pt
), 0.5*(y0_pt
+y1_pt
)
1098 x0_pt
, y0_pt
= xm
+ self
.stretch
*(x0_pt
-xm
), ym
+ self
.stretch
*(y0_pt
-ym
)
1099 x1_pt
, y1_pt
= xm
+ self
.stretch
*(x1_pt
-xm
), ym
+ self
.stretch
*(y1_pt
-ym
)
1100 if self
.dist
is not None:
1101 d
= unit
.topt(self
.dist
)
1102 dx
, dy
= dp
.path
.rotation_pt(dp
.path
.begin()).apply_pt(0, 1)
1103 x0_pt
+= d
*dx
; y0_pt
+= d
*dy
1104 dx
, dy
= dp
.path
.rotation_pt(dp
.path
.end()).apply_pt(0, 1)
1105 x1_pt
+= d
*dx
; y1_pt
+= d
*dy
1106 dp
.ornaments
.fill(self
._bracepath
(x0_pt
, y0_pt
, x1_pt
, y1_pt
), self
.fillattrs
)
1108 brace
.clear
= attr
.clearclass(brace
)
1110 leftbrace
= brace(reverse
=0, middlerelpos
=0.55, innerstrokesrelheight
=0.6, outerstrokesrelheight
=0.7, slantstrokesangle
=-10)
1111 rightbrace
= brace(reverse
=1, middlerelpos
=0.45, innerstrokesrelheight
=0.6, outerstrokesrelheight
=0.7, slantstrokesangle
=10)
1112 belowbrace
= brace(reverse
=1, middlerelpos
=0.55, innerstrokesrelheight
=0.7, outerstrokesrelheight
=0.9, slantstrokesangle
=-10)
1113 abovebrace
= brace(reverse
=0, middlerelpos
=0.45, innerstrokesrelheight
=0.7, outerstrokesrelheight
=0.9, slantstrokesangle
=-10)
1114 straightbrace
= brace(innerstrokesrelheight
=0.5, outerstrokesrelheight
=0.5,
1115 innerstrokesangle
=30, outerstrokesangle
=30, slantstrokesangle
=0,
1116 innerstrokessmoothness
=1.0, outerstrokessmoothness
=1.0)