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
, 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 - constrictionlen: None (no constriction) or arc length of constriction.
403 # arc length and coordinates of tip
404 tx
, ty
= anormpath
.at(arclenfrombegin
)
406 # construct the template for the arrow by cutting the path at the
407 # corresponding length
408 arrowtemplate
= anormpath
.split([arclenfrombegin
, arclenfrombegin
- direction
* size
])[1]
410 # from this template, we construct the two outer curves of the arrow
411 arrowl
= arrowtemplate
.transformed(trafo
.rotate(-angle
/2.0, tx
, ty
))
412 arrowr
= arrowtemplate
.transformed(trafo
.rotate( angle
/2.0, tx
, ty
))
414 # now come the joining backward parts
415 if constrictionlen
is not None:
416 # constriction point (cx, cy) lies on path
417 cx
, cy
= anormpath
.at(arclenfrombegin
- direction
* constrictionlen
)
418 arrowcr
= path
.line(*(arrowr
.atend() + (cx
,cy
)))
419 arrow
= arrowl
.reversed() << arrowr
<< arrowcr
421 arrow
= arrowl
.reversed() << arrowr
428 _base
= 6 * unit
.v_pt
430 class arrow(deco
, attr
.attr
):
432 """arrow is a decorator which adds an arrow to either side of the path"""
434 def __init__(self
, attrs
=[], pos
=1, reversed=0, size
=_base
, angle
=45, constriction
=0.8):
435 self
.attrs
= attr
.mergeattrs([style
.linestyle
.solid
, filled
] + attrs
)
436 attr
.checkattrs(self
.attrs
, [deco
, style
.fillstyle
, style
.strokestyle
])
438 self
.reversed = reversed
441 self
.constriction
= constriction
443 def __call__(self
, attrs
=None, pos
=None, reversed=None, size
=None, angle
=None, constriction
=_marker
):
449 reversed = self
.reversed
454 if constriction
is _marker
:
455 constriction
= self
.constriction
456 return arrow(attrs
=attrs
, pos
=pos
, reversed=reversed, size
=size
, angle
=angle
, constriction
=constriction
)
458 def decorate(self
, dp
, texrunner
):
462 # calculate absolute arc length of constricition
463 # Note that we have to correct this length because the arrowtemplates are rotated
464 # by self.angle/2 to the left and right. Hence, if we want no constriction, i.e., for
465 # self.constriction = 1, we actually have a length which is approximately shorter
466 # by the given geometrical factor.
467 if self
.constriction
is not None:
468 constrictionlen
= arrowheadconstrictionlen
= self
.size
* self
.constriction
* math
.cos(math
.radians(self
.angle
/2.0))
470 # if we do not want a constriction, i.e. constriction is None, we still
471 # need constrictionlen for cutting the path
472 constrictionlen
= self
.size
* 1 * math
.cos(math
.radians(self
.angle
/2.0))
473 arrowheadconstrictionlen
= None
475 arclenfrombegin
= (1-self
.reversed)*constrictionlen
+ self
.pos
* (anormpath
.arclen() - constrictionlen
)
476 direction
= self
.reversed and -1 or 1
477 arrowhead
= _arrowhead(anormpath
, arclenfrombegin
, direction
, self
.size
, self
.angle
, arrowheadconstrictionlen
)
479 # add arrowhead to decoratedpath
480 dp
.ornaments
.draw(arrowhead
, self
.attrs
)
482 # exlude part of the path from stroking when the arrow is strictly at the begin or the end
483 if self
.pos
== 0 and self
.reversed:
484 dp
.excluderange(0, min(self
.size
, constrictionlen
))
485 elif self
.pos
== 1 and not self
.reversed:
486 dp
.excluderange(anormpath
.end() - min(self
.size
, constrictionlen
), anormpath
.end())
488 arrow
.clear
= attr
.clearclass(arrow
)
490 # arrows at begin of path
491 barrow
= arrow(pos
=0, reversed=1)
492 barrow
.SMALL
= barrow(size
=_base
/math
.sqrt(64))
493 barrow
.SMALl
= barrow(size
=_base
/math
.sqrt(32))
494 barrow
.SMAll
= barrow(size
=_base
/math
.sqrt(16))
495 barrow
.SMall
= barrow(size
=_base
/math
.sqrt(8))
496 barrow
.Small
= barrow(size
=_base
/math
.sqrt(4))
497 barrow
.small
= barrow(size
=_base
/math
.sqrt(2))
498 barrow
.normal
= barrow(size
=_base
)
499 barrow
.large
= barrow(size
=_base
*math
.sqrt(2))
500 barrow
.Large
= barrow(size
=_base
*math
.sqrt(4))
501 barrow
.LArge
= barrow(size
=_base
*math
.sqrt(8))
502 barrow
.LARge
= barrow(size
=_base
*math
.sqrt(16))
503 barrow
.LARGe
= barrow(size
=_base
*math
.sqrt(32))
504 barrow
.LARGE
= barrow(size
=_base
*math
.sqrt(64))
506 # arrows at end of path
508 earrow
.SMALL
= earrow(size
=_base
/math
.sqrt(64))
509 earrow
.SMALl
= earrow(size
=_base
/math
.sqrt(32))
510 earrow
.SMAll
= earrow(size
=_base
/math
.sqrt(16))
511 earrow
.SMall
= earrow(size
=_base
/math
.sqrt(8))
512 earrow
.Small
= earrow(size
=_base
/math
.sqrt(4))
513 earrow
.small
= earrow(size
=_base
/math
.sqrt(2))
514 earrow
.normal
= earrow(size
=_base
)
515 earrow
.large
= earrow(size
=_base
*math
.sqrt(2))
516 earrow
.Large
= earrow(size
=_base
*math
.sqrt(4))
517 earrow
.LArge
= earrow(size
=_base
*math
.sqrt(8))
518 earrow
.LARge
= earrow(size
=_base
*math
.sqrt(16))
519 earrow
.LARGe
= earrow(size
=_base
*math
.sqrt(32))
520 earrow
.LARGE
= earrow(size
=_base
*math
.sqrt(64))
523 class text(deco
, attr
.attr
):
524 """a simple text decorator"""
526 def __init__(self
, text
, textattrs
=[], angle
=0, relangle
=None, textdist
=0.2,
527 relarclenpos
=0.5, arclenfrombegin
=None, arclenfromend
=None,
529 if arclenfrombegin
is not None and arclenfromend
is not None:
530 raise ValueError("either set arclenfrombegin or arclenfromend")
532 self
.textattrs
= textattrs
534 self
.relangle
= relangle
535 self
.textdist
= textdist
536 self
.relarclenpos
= relarclenpos
537 self
.arclenfrombegin
= arclenfrombegin
538 self
.arclenfromend
= arclenfromend
539 self
.texrunner
= texrunner
541 def decorate(self
, dp
, texrunner
):
543 texrunner
= self
.texrunner
544 import text
as textmodule
545 textattrs
= attr
.mergeattrs([textmodule
.halign
.center
, textmodule
.vshift
.mathaxis
] + self
.textattrs
)
548 if self
.arclenfrombegin
is not None:
549 param
= dp
.path
.begin() + self
.arclenfrombegin
550 elif self
.arclenfromend
is not None:
551 param
= dp
.path
.end() - self
.arclenfromend
553 # relarcpos is used, when neither arcfrombegin nor arcfromend is given
554 param
= self
.relarclenpos
* dp
.path
.arclen()
555 x
, y
= dp
.path
.at(param
)
557 if self
.relangle
is not None:
558 a
= dp
.path
.trafo(param
).apply_pt(math
.cos(self
.relangle
*math
.pi
/180), math
.sin(self
.relangle
*math
.pi
/180))
559 b
= dp
.path
.trafo(param
).apply_pt(0, 0)
560 angle
= math
.atan2(a
[1] - b
[1], a
[0] - b
[0])
562 angle
= self
.angle
*math
.pi
/180
563 t
= texrunner
.text(x
, y
, self
.text
, textattrs
)
564 t
.linealign(self
.textdist
, math
.cos(angle
), math
.sin(angle
))
565 dp
.ornaments
.insert(t
)
567 class curvedtext(deco
, attr
.attr
):
568 """a text decorator for curved text
570 - text: is typeset along the path to which this decorator is applied
571 - relarclenpos: position for the base point of the text (default: 0)
572 - arlenfrombegin, arclenfromend: alternative ways of specifying the position of the base point;
573 use of relarclenpos, arclenfrombegin and arclenfromend is mutually exclusive
574 - textattrs, texrunner: standard text arguments (defaults: [] resp None)
578 # defaulttextattrs = [textmodule.halign.center] # TODO: not possible due to cyclic import issue
580 def __init__(self
, text
, textattrs
=[],
581 relarclenpos
=0.5, arclenfrombegin
=None, arclenfromend
=None,
582 texrunner
=None, exclude
=None):
583 if arclenfrombegin
is not None and arclenfromend
is not None:
584 raise ValueError("either set arclenfrombegin or arclenfromend")
586 self
.textattrs
= textattrs
587 self
.relarclenpos
= relarclenpos
588 self
.arclenfrombegin
= arclenfrombegin
589 self
.arclenfromend
= arclenfromend
590 self
.texrunner
= texrunner
591 self
.exclude
= exclude
593 def decorate(self
, dp
, texrunner
):
595 texrunner
= self
.texrunner
596 import text
as textmodule
597 self
.defaulttextattrs
= [textmodule
.halign
.center
]
600 if self
.arclenfrombegin
is not None:
601 textpos
= dp
.path
.begin() + self
.arclenfrombegin
602 elif self
.arclenfromend
is not None:
603 textpos
= dp
.path
.end() - self
.arclenfromend
605 # relarcpos is used if neither arcfrombegin nor arcfromend is given
606 textpos
= self
.relarclenpos
* dp
.path
.arclen()
608 textattrs
= self
.defaulttextattrs
+ self
.textattrs
609 t
= texrunner
.text(0, 0, self
.text
, textattrs
, singlecharmode
=1)
613 for item
in t
.dvicanvas
.items
:
616 x
= item
.bbox().center()[0]
617 atrafo
= dp
.path
.trafo(textpos
+x
)
618 c
.insert(item
, [trafo
.translate(-x
, 0), atrafo
])
619 if self
.exclude
is not None:
620 dp
.excluderange(textpos
+bbox
.left()-self
.exclude
, textpos
+bbox
.right()+self
.exclude
)
623 dp
.ornaments
.insert(c
)
627 class shownormpath(deco
, attr
.attr
):
629 def decorate(self
, dp
, texrunner
):
632 for normsubpath
in dp
.path
.normsubpaths
:
633 for i
, normsubpathitem
in enumerate(normsubpath
.normsubpathitems
):
634 if isinstance(normsubpathitem
, normpath
.normcurve_pt
):
635 dp
.ornaments
.stroke(normpath
.normpath([normpath
.normsubpath([normsubpathitem
])]), [color
.rgb
.green
])
637 dp
.ornaments
.stroke(normpath
.normpath([normpath
.normsubpath([normsubpathitem
])]), [color
.rgb
.blue
])
638 for normsubpath
in dp
.path
.normsubpaths
:
639 for i
, normsubpathitem
in enumerate(normsubpath
.normsubpathitems
):
640 if isinstance(normsubpathitem
, normpath
.normcurve_pt
):
641 dp
.ornaments
.stroke(path
.line_pt(normsubpathitem
.x0_pt
, normsubpathitem
.y0_pt
, normsubpathitem
.x1_pt
, normsubpathitem
.y1_pt
), [style
.linestyle
.dashed
, color
.rgb
.red
])
642 dp
.ornaments
.stroke(path
.line_pt(normsubpathitem
.x2_pt
, normsubpathitem
.y2_pt
, normsubpathitem
.x3_pt
, normsubpathitem
.y3_pt
), [style
.linestyle
.dashed
, color
.rgb
.red
])
643 dp
.ornaments
.draw(path
.circle_pt(normsubpathitem
.x1_pt
, normsubpathitem
.y1_pt
, r_pt
), [filled([color
.rgb
.red
])])
644 dp
.ornaments
.draw(path
.circle_pt(normsubpathitem
.x2_pt
, normsubpathitem
.y2_pt
, r_pt
), [filled([color
.rgb
.red
])])
645 for normsubpath
in dp
.path
.normsubpaths
:
646 for i
, normsubpathitem
in enumerate(normsubpath
.normsubpathitems
):
648 x_pt
, y_pt
= normsubpathitem
.atbegin_pt()
649 dp
.ornaments
.draw(path
.circle_pt(x_pt
, y_pt
, r_pt
), [filled
])
650 x_pt
, y_pt
= normsubpathitem
.atend_pt()
651 dp
.ornaments
.draw(path
.circle_pt(x_pt
, y_pt
, r_pt
), [filled
])
654 class linehatched(deco
, attr
.clearclass
):
655 """draws a pattern with explicit lines
657 This class acts as a drop-in replacement for postscript patterns
658 from the pattern module which are not understood by some printers"""
660 def __init__(self
, dist
, angle
, shift
=0, strokestyles
=[], center
=None):
661 attr
.clearclass
.__init
__(self
, _filled
)
664 self
.strokestyles
= attr
.mergeattrs([style
.linewidth
.THIN
] + strokestyles
)
665 attr
.checkattrs(self
.strokestyles
, [style
.strokestyle
])
669 def __call__(self
, dist
=None, angle
=None, shift
=None, strokestyles
=None, center
=_marker
):
676 if strokestyles
is None:
677 strokestyles
= self
.strokestyles
678 if center
is _marker
:
680 return linehatched(dist
, angle
, shift
, strokestyles
, center
)
682 def _decocanvas(self
, angle
, dp
, texrunner
):
684 dist_pt
= unit
.topt(self
.dist
)
685 shift_pt
= unit
.topt(self
.shift
)
687 c
= canvas
.canvas([canvas
.clip(dp
.path
)])
688 llx_pt
, lly_pt
, urx_pt
, ury_pt
= dp
.path
.bbox().highrestuple_pt()
689 if self
.center
is None:
690 center_pt
= 0.5*(llx_pt
+urx_pt
), 0.5*(lly_pt
+ury_pt
)
691 radius_pt
= 0.5*math
.hypot(urx_pt
-llx_pt
, ury_pt
-lly_pt
) + dist_pt
693 center_pt
= (unit
.topt(self
.center
[0]), unit
.topt(self
.center
[1]))
694 radius_pt
= dist_pt
+ max([math
.hypot(urx_pt
-center_pt
[0], ury_pt
-center_pt
[1]),
695 math
.hypot(llx_pt
-center_pt
[0], lly_pt
-center_pt
[1]),
696 math
.hypot(urx_pt
-center_pt
[0], lly_pt
-center_pt
[1]),
697 math
.hypot(llx_pt
-center_pt
[0], ury_pt
-center_pt
[1])])
698 n
= int(radius_pt
/ dist_pt
)
699 for i
in range(-n
-1, n
+2):
700 x_pt
= center_pt
[0] + i
*dist_pt
701 c
.stroke(path
.line_pt(x_pt
+shift_pt
, center_pt
[1]-radius_pt
, x_pt
+shift_pt
, center_pt
[1]+radius_pt
),
702 [trafo
.rotate_pt(angle
, center_pt
[0], center_pt
[1])] + self
.strokestyles
)
705 def decorate(self
, dp
, texrunner
):
706 dp
.ornaments
.insert(self
._decocanvas
(self
.angle
, dp
, texrunner
))
708 def merge(self
, attrs
):
709 newattrs
= attr
.clearclass
.merge(self
, attrs
)
710 newattrs
.append(self
)
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))
779 class colorgradient(deco
, attr
.attr
):
780 """inserts pieces of the path in different colors"""
782 def __init__(self
, grad
, attrs
=[], steps
=20):
787 def decorate(self
, dp
, texrunner
):
791 colors
= [self
.grad
.select(n
, self
.steps
) for n
in range(self
.steps
)]
793 params
= dp
.path
.arclentoparam([l
*i
/float(self
.steps
) for i
in range(self
.steps
)])
797 # treat the end pieces separately
798 c
.stroke(dp
.path
.split(params
[1])[1], attr
.mergeattrs([colors
[0]] + self
.attrs
))
799 for n
in range(1,self
.steps
-1):
800 c
.stroke(dp
.path
.split([params
[n
-1],params
[n
+1]])[1], attr
.mergeattrs([colors
[n
]] + self
.attrs
))
801 c
.stroke(dp
.path
.split(params
[-2])[0], attr
.mergeattrs([colors
[-1]] + self
.attrs
))
802 dp
.ornaments
.insert(c
)
805 class brace(deco
, attr
.attr
):
806 r
"""draws a nicely curled brace
808 In most cases, the original line is not wanted use canvas.canvas.draw(..) for it
810 Geometrical parameters:
813 ____________/ \__________
817 totalheight distance from the jaws to the middle cap
818 barthickness thickness of the main bars
819 innerstrokesthickness thickness of the two ending strokes
820 outerstrokesthickness thickness of the inner strokes at the middle cap
821 innerstrokesrelheight height of the inner/outer strokes, relative to the total height
822 outerstrokesrelheight this determines the angle of the main bars!
824 Note: if innerstrokesrelheight + outerstrokesrelheight == 1 then the main bars
825 will be aligned parallel to the connecting line between the endpoints
826 outerstrokesangle angle of the two ending strokes
827 innerstrokesangle angle between the inner strokes at the middle cap
828 slantstrokesangle extra slanting of the inner/outer strokes
829 innerstrokessmoothness smoothing parameter for the inner + outer strokes
830 outerstrokessmoothness should be around 1 (allowed: [0,infty))
831 middlerelpos position of the middle cap (0 == left, 1 == right)
833 # This code is experimental because it is unclear
834 # how the brace fits into the concepts of PyX
837 # - a brace needs to be decoratable with text
838 # it needs stroking and filling attributes
839 # - the brace is not really a box:
840 # it has two "anchor" points that are important for aligning it to other things
841 # and one "anchor" point (plus direction) for aligning other things
842 # - a brace is not a deformer:
843 # it does not look at anything else than begin/endpoint of a path
844 # - a brace might be a connector (which is to be dissolved into the box concept later?)
846 def __init__(self
, reverse
=1, stretch
=None, dist
=None, fillattrs
=[],
847 totalheight
=12*unit
.x_pt
,
848 barthickness
=0.5*unit
.x_pt
, innerstrokesthickness
=0.25*unit
.x_pt
, outerstrokesthickness
=0.25*unit
.x_pt
,
849 innerstrokesrelheight
=0.6, outerstrokesrelheight
=0.7,
850 innerstrokesangle
=30, outerstrokesangle
=25, slantstrokesangle
=5,
851 innerstrokessmoothness
=2.0, outerstrokessmoothness
=2.5,
853 self
.fillattrs
= fillattrs
854 self
.reverse
= reverse
855 self
.stretch
= stretch
857 self
.totalheight
= totalheight
858 self
.barthickness
= barthickness
859 self
.innerstrokesthickness
= innerstrokesthickness
860 self
.outerstrokesthickness
= outerstrokesthickness
861 self
.innerstrokesrelheight
= innerstrokesrelheight
862 self
.outerstrokesrelheight
= outerstrokesrelheight
863 self
.innerstrokesangle
= innerstrokesangle
864 self
.outerstrokesangle
= outerstrokesangle
865 self
.slantstrokesangle
= slantstrokesangle
866 self
.innerstrokessmoothness
= innerstrokessmoothness
867 self
.outerstrokessmoothness
= outerstrokessmoothness
868 self
.middlerelpos
= middlerelpos
870 def __call__(self
, **kwargs
):
871 for name
in ["reverse", "stretch", "dist", "fillattrs",
872 "totalheight", "barthickness", "innerstrokesthickness", "outerstrokesthickness",
873 "innerstrokesrelheight", "outerstrokesrelheight", "innerstrokesangle", "outerstrokesangle", "slantstrokesangle",
874 "innerstrokessmoothness", "outerstrokessmoothness", "middlerelpos"]:
875 if not kwargs
.has_key(name
):
876 kwargs
[name
] = self
.__dict
__[name
]
877 return brace(**kwargs
)
879 def _halfbracepath_pt(self
, length_pt
, height_pt
, ilength_pt
, olength_pt
, # <<<
880 ithick_pt
, othick_pt
, bthick_pt
, cos_iangle
, sin_iangle
, cos_oangle
,
881 sin_oangle
, cos_slangle
, sin_slangle
):
883 ismooth
= self
.innerstrokessmoothness
884 osmooth
= self
.outerstrokessmoothness
886 # these two parameters are not important enough to be seen outside
887 inner_cap_param
= 1.5
888 outer_cap_param
= 2.5
889 outerextracurved
= 0.6 # in (0, 1]
890 # 1.0 will lead to F=G, the outer strokes will not be curved at their ends.
891 # The smaller, the more curvature
893 # build an orientation path (three straight lines)
898 # _/ \______________________________________q5
904 # get the points for that:
905 q1
= (0, height_pt
- inner_cap_param
* ithick_pt
+ 0.5*ithick_pt
/sin_iangle
)
906 q2
= (q1
[0] + ilength_pt
* sin_iangle
,
907 q1
[1] - ilength_pt
* cos_iangle
)
909 q5
= (q6
[0] - olength_pt
* sin_oangle
,
910 q6
[1] + olength_pt
* cos_oangle
)
911 bardir
= (q5
[0] - q2
[0], q5
[1] - q2
[1])
912 bardirnorm
= math
.hypot(*bardir
)
913 bardir
= (bardir
[0]/bardirnorm
, bardir
[1]/bardirnorm
)
914 ismoothlength_pt
= ilength_pt
* ismooth
915 osmoothlength_pt
= olength_pt
* osmooth
916 if bardirnorm
< ismoothlength_pt
+ osmoothlength_pt
:
917 ismoothlength_pt
= bardirnorm
* ismoothlength_pt
/ (ismoothlength_pt
+ osmoothlength_pt
)
918 osmoothlength_pt
= bardirnorm
* osmoothlength_pt
/ (ismoothlength_pt
+ osmoothlength_pt
)
919 q3
= (q2
[0] + ismoothlength_pt
* bardir
[0],
920 q2
[1] + ismoothlength_pt
* bardir
[1])
921 q4
= (q5
[0] - osmoothlength_pt
* bardir
[0],
922 q5
[1] - osmoothlength_pt
* bardir
[1])
928 # / \ B2C2________D2___________E2_______F2___G2
929 # \______________________________________ \
930 # B1,C1 D1 E1 F1 G1 \
936 # the halfbraces meet in P and A1:
938 A1
= (0, height_pt
- inner_cap_param
* ithick_pt
)
939 # A2 is A1, shifted by the inner thickness
940 A2
= (A1
[0] + ithick_pt
* cos_iangle
,
941 A1
[1] + ithick_pt
* sin_iangle
)
942 s
, t
= deformer
.intersection(P
, A2
, (cos_slangle
, sin_slangle
), (sin_iangle
, -cos_iangle
))
943 O
= (P
[0] + s
* cos_slangle
,
944 P
[1] + s
* sin_slangle
)
946 # from D1 to E1 is the straight part of the brace
947 # also back from E2 to D1
948 D1
= (q3
[0] + bthick_pt
* bardir
[1],
949 q3
[1] - bthick_pt
* bardir
[0])
950 D2
= (q3
[0] - bthick_pt
* bardir
[1],
951 q3
[1] + bthick_pt
* bardir
[0])
952 E1
= (q4
[0] + bthick_pt
* bardir
[1],
953 q4
[1] - bthick_pt
* bardir
[0])
954 E2
= (q4
[0] - bthick_pt
* bardir
[1],
955 q4
[1] + bthick_pt
* bardir
[0])
956 # I1, I2 are the control points at the outer stroke
957 I1
= (q6
[0] - 0.5 * othick_pt
* cos_oangle
,
958 q6
[1] - 0.5 * othick_pt
* sin_oangle
)
959 I2
= (q6
[0] + 0.5 * othick_pt
* cos_oangle
,
960 q6
[1] + 0.5 * othick_pt
* sin_oangle
)
961 # get the control points for the curved parts of the brace
962 s
, t
= deformer
.intersection(A1
, D1
, (sin_iangle
, -cos_iangle
), bardir
)
963 B1
= (D1
[0] + t
* bardir
[0],
964 D1
[1] + t
* bardir
[1])
965 s
, t
= deformer
.intersection(A2
, D2
, (sin_iangle
, -cos_iangle
), bardir
)
966 B2
= (D2
[0] + t
* bardir
[0],
967 D2
[1] + t
* bardir
[1])
968 s
, t
= deformer
.intersection(E1
, I1
, bardir
, (-sin_oangle
, cos_oangle
))
969 G1
= (E1
[0] + s
* bardir
[0],
970 E1
[1] + s
* bardir
[1])
971 s
, t
= deformer
.intersection(E2
, I2
, bardir
, (-sin_oangle
, cos_oangle
))
972 G2
= (E2
[0] + s
* bardir
[0],
973 E2
[1] + s
* bardir
[1])
974 # at the inner strokes: use curvature zero at both ends
977 # at the outer strokes: use curvature zero only at the connection to
979 F1
= (outerextracurved
* G1
[0] + (1 - outerextracurved
) * E1
[0],
980 outerextracurved
* G1
[1] + (1 - outerextracurved
) * E1
[1])
981 F2
= (outerextracurved
* G2
[0] + (1 - outerextracurved
) * E2
[0],
982 outerextracurved
* G2
[1] + (1 - outerextracurved
) * E2
[1])
983 # the tip of the outer stroke, endpoints of the bezier curve
984 H1
= (I1
[0] - outer_cap_param
* othick_pt
* sin_oangle
,
985 I1
[1] + outer_cap_param
* othick_pt
* cos_oangle
)
986 H2
= (I2
[0] - outer_cap_param
* othick_pt
* sin_oangle
,
987 I2
[1] + outer_cap_param
* othick_pt
* cos_oangle
)
989 #for qq in [A1,B1,C1,D1,E1,F1,G1,H1,I1,
990 # A2,B2,C2,D2,E2,F2,G2,H2,I2,
993 # cc.fill(path.circle(qq[0], qq[1], 0.5), [color.rgb.green])
995 # now build the right halfbrace
996 bracepath
= path
.path(path
.moveto_pt(*A1
))
997 bracepath
.append(path
.curveto_pt(B1
[0], B1
[1], C1
[0], C1
[1], D1
[0], D1
[1]))
998 bracepath
.append(path
.lineto_pt(E1
[0], E1
[1]))
999 bracepath
.append(path
.curveto_pt(F1
[0], F1
[1], G1
[0], G1
[1], H1
[0], H1
[1]))
1000 # the tip of the right halfbrace
1001 bracepath
.append(path
.curveto_pt(I1
[0], I1
[1], I2
[0], I2
[1], H2
[0], H2
[1]))
1002 # the rest of the right halfbrace
1003 bracepath
.append(path
.curveto_pt(G2
[0], G2
[1], F2
[0], F2
[1], E2
[0], E2
[1]))
1004 bracepath
.append(path
.lineto_pt(D2
[0], D2
[1]))
1005 bracepath
.append(path
.curveto_pt(C2
[0], C2
[1], B2
[0], B2
[1], A2
[0], A2
[1]))
1006 # the tip in the middle of the brace
1007 bracepath
.append(path
.curveto_pt(O
[0], O
[1], O
[0], O
[1], P
[0], P
[1]))
1012 def _bracepath(self
, x0_pt
, y0_pt
, x1_pt
, y1_pt
): # <<<
1013 height_pt
= unit
.topt(self
.totalheight
)
1014 totallength_pt
= math
.hypot(x1_pt
- x0_pt
, y1_pt
- y0_pt
)
1015 leftlength_pt
= self
.middlerelpos
* totallength_pt
1016 rightlength_pt
= totallength_pt
- leftlength_pt
1017 ithick_pt
= unit
.topt(self
.innerstrokesthickness
)
1018 othick_pt
= unit
.topt(self
.outerstrokesthickness
)
1019 bthick_pt
= unit
.topt(self
.barthickness
)
1021 # create the left halfbrace with positive slanting
1022 # because we will mirror this part
1023 cos_iangle
= math
.cos(math
.radians(0.5*self
.innerstrokesangle
- self
.slantstrokesangle
))
1024 sin_iangle
= math
.sin(math
.radians(0.5*self
.innerstrokesangle
- self
.slantstrokesangle
))
1025 cos_oangle
= math
.cos(math
.radians(self
.outerstrokesangle
- self
.slantstrokesangle
))
1026 sin_oangle
= math
.sin(math
.radians(self
.outerstrokesangle
- self
.slantstrokesangle
))
1027 cos_slangle
= math
.cos(math
.radians(-self
.slantstrokesangle
))
1028 sin_slangle
= math
.sin(math
.radians(-self
.slantstrokesangle
))
1029 ilength_pt
= self
.innerstrokesrelheight
* height_pt
/ cos_iangle
1030 olength_pt
= self
.outerstrokesrelheight
* height_pt
/ cos_oangle
1032 bracepath
= self
._halfbracepath
_pt
(leftlength_pt
, height_pt
,
1033 ilength_pt
, olength_pt
, ithick_pt
, othick_pt
, bthick_pt
, cos_iangle
,
1034 sin_iangle
, cos_oangle
, sin_oangle
, cos_slangle
,
1035 sin_slangle
).reversed().transformed(trafo
.mirror(90))
1037 # create the right halfbrace with negative slanting
1038 cos_iangle
= math
.cos(math
.radians(0.5*self
.innerstrokesangle
+ self
.slantstrokesangle
))
1039 sin_iangle
= math
.sin(math
.radians(0.5*self
.innerstrokesangle
+ self
.slantstrokesangle
))
1040 cos_oangle
= math
.cos(math
.radians(self
.outerstrokesangle
+ self
.slantstrokesangle
))
1041 sin_oangle
= math
.sin(math
.radians(self
.outerstrokesangle
+ self
.slantstrokesangle
))
1042 cos_slangle
= math
.cos(math
.radians(-self
.slantstrokesangle
))
1043 sin_slangle
= math
.sin(math
.radians(-self
.slantstrokesangle
))
1044 ilength_pt
= self
.innerstrokesrelheight
* height_pt
/ cos_iangle
1045 olength_pt
= self
.outerstrokesrelheight
* height_pt
/ cos_oangle
1047 bracepath
= bracepath
<< self
._halfbracepath
_pt
(rightlength_pt
, height_pt
,
1048 ilength_pt
, olength_pt
, ithick_pt
, othick_pt
, bthick_pt
, cos_iangle
,
1049 sin_iangle
, cos_oangle
, sin_oangle
, cos_slangle
,
1052 return bracepath
.transformed(
1053 # two trafos for matching the given endpoints
1054 trafo
.translate_pt(x0_pt
, y0_pt
) *
1055 trafo
.rotate_pt(math
.degrees(math
.atan2(y1_pt
-y0_pt
, x1_pt
-x0_pt
))) *
1056 # one trafo to move the brace's left outer stroke to zero
1057 trafo
.translate_pt(leftlength_pt
, 0))
1060 def decorate(self
, dp
, texrunner
):
1062 x0_pt
, y0_pt
= dp
.path
.atbegin_pt()
1063 x1_pt
, y1_pt
= dp
.path
.atend_pt()
1065 x0_pt
, y0_pt
, x1_pt
, y1_pt
= x1_pt
, y1_pt
, x0_pt
, y0_pt
1066 if self
.stretch
is not None:
1067 xm
, ym
= 0.5*(x0_pt
+x1_pt
), 0.5*(y0_pt
+y1_pt
)
1068 x0_pt
, y0_pt
= xm
+ self
.stretch
*(x0_pt
-xm
), ym
+ self
.stretch
*(y0_pt
-ym
)
1069 x1_pt
, y1_pt
= xm
+ self
.stretch
*(x1_pt
-xm
), ym
+ self
.stretch
*(y1_pt
-ym
)
1070 if self
.dist
is not None:
1071 d
= unit
.topt(self
.dist
)
1072 dx
, dy
= dp
.path
.rotation_pt(dp
.path
.begin()).apply_pt(0, 1)
1073 x0_pt
+= d
*dx
; y0_pt
+= d
*dy
1074 dx
, dy
= dp
.path
.rotation_pt(dp
.path
.end()).apply_pt(0, 1)
1075 x1_pt
+= d
*dx
; y1_pt
+= d
*dy
1076 dp
.ornaments
.fill(self
._bracepath
(x0_pt
, y0_pt
, x1_pt
, y1_pt
), self
.fillattrs
)
1078 brace
.clear
= attr
.clearclass(brace
)
1080 leftbrace
= brace(reverse
=0, middlerelpos
=0.55, innerstrokesrelheight
=0.6, outerstrokesrelheight
=0.7, slantstrokesangle
=-10)
1081 rightbrace
= brace(reverse
=1, middlerelpos
=0.45, innerstrokesrelheight
=0.6, outerstrokesrelheight
=0.7, slantstrokesangle
=10)
1082 belowbrace
= brace(reverse
=1, middlerelpos
=0.55, innerstrokesrelheight
=0.7, outerstrokesrelheight
=0.9, slantstrokesangle
=-10)
1083 abovebrace
= brace(reverse
=0, middlerelpos
=0.45, innerstrokesrelheight
=0.7, outerstrokesrelheight
=0.9, slantstrokesangle
=-10)
1084 straightbrace
= brace(innerstrokesrelheight
=0.5, outerstrokesrelheight
=0.5,
1085 innerstrokesangle
=30, outerstrokesangle
=30, slantstrokesangle
=0,
1086 innerstrokessmoothness
=1.0, outerstrokessmoothness
=1.0)