use double quotes
[PyX.git] / deco.py
blob05951c24c0dda4ededc86192e41406c1ced22bf4
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
24 # TODO:
25 # - should we improve on the arc length -> arg parametrization routine or
26 # should we at least factor it out?
28 import sys, math
29 from . import attr, baseclasses, canvas, color, path, normpath, style, trafo, unit, deformer
31 _marker = object()
34 # Decorated path
37 class decoratedpath(baseclasses.canvasitem):
38 """Decorated path
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.
43 """
45 def __init__(self, path, strokepath=None, fillpath=None,
46 styles=None, strokestyles=None, fillstyles=None,
47 ornaments=None):
49 self.path = path
51 # global style for stroking and filling and subdps
52 self.styles = styles
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.
60 if ornaments is None:
61 self.ornaments = canvas.canvas()
62 else:
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)]
76 else:
77 ibegin = 0
78 while ibegin < len(self.nostrokeranges) and self.nostrokeranges[ibegin][1] < begin:
79 ibegin += 1
81 if ibegin == len(self.nostrokeranges):
82 self.nostrokeranges.append((begin, end))
83 return
85 iend = len(self.nostrokeranges) - 1
86 while 0 <= iend and end < self.nostrokeranges[iend][0]:
87 iend -= 1
89 if iend == -1:
90 self.nostrokeranges.insert(0, (begin, end))
91 return
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)]
100 def bbox(self):
101 pathbbox = self.path.bbox()
102 ornamentsbbox = self.ornaments.bbox()
103 if ornamentsbbox is not None:
104 return ornamentsbbox + pathbbox
105 else:
106 return pathbbox
108 def strokepath(self):
109 if self.nostrokeranges:
110 splitlist = []
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?
116 result = split[0]
117 for i in range(2, len(split), 2):
118 result += split[i]
119 return result
120 else:
121 return self.path
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
128 # small helper
129 def _writestyles(styles, context, registry):
130 for style in styles:
131 style.processPS(file, writer, context, registry)
133 if self.strokestyles is None and self.fillstyles is None:
134 if not len(self.ornaments):
135 raise RuntimeError("Path neither to be stroked nor filled nor decorated in another way")
136 # just draw additional elements of decoratedpath
137 self.ornaments.processPS(file, writer, context, registry, bbox)
138 return
140 strokepath = self.strokepath()
141 fillpath = self.path
143 # apply global styles
144 if self.styles:
145 file.write("gsave\n")
146 context = context()
147 _writestyles(self.styles, context, registry)
149 if self.fillstyles is not None:
150 file.write("newpath\n")
151 fillpath.outputPS(file, writer)
153 if self.strokestyles is not None and strokepath is fillpath:
154 # do efficient stroking + filling if respective paths are identical
155 file.write("gsave\n")
157 if self.fillstyles:
158 _writestyles(self.fillstyles, context(), registry)
160 if context.fillrule:
161 file.write("eofill\n")
162 else:
163 file.write("fill\n")
164 file.write("grestore\n")
166 acontext = context()
167 if self.strokestyles:
168 file.write("gsave\n")
169 _writestyles(self.strokestyles, acontext, registry)
171 file.write("stroke\n")
172 # take linewidth into account for bbox when stroking a path
173 bbox += strokepath.bbox().enlarged_pt(0.5*acontext.linewidth_pt)
175 if self.strokestyles:
176 file.write("grestore\n")
177 else:
178 # only fill fillpath - for the moment
179 if self.fillstyles:
180 file.write("gsave\n")
181 _writestyles(self.fillstyles, context(), registry)
183 if context.fillrule:
184 file.write("eofill\n")
185 else:
186 file.write("fill\n")
187 bbox += fillpath.bbox()
189 if self.fillstyles:
190 file.write("grestore\n")
192 if self.strokestyles is not None and (strokepath is not fillpath or self.fillstyles is None):
193 # this is the only relevant case still left
194 # Note that a possible stroking has already been done.
195 acontext = context()
196 if self.strokestyles:
197 file.write("gsave\n")
198 _writestyles(self.strokestyles, acontext, registry)
200 file.write("newpath\n")
201 strokepath.outputPS(file, writer)
202 file.write("stroke\n")
203 # take linewidth into account for bbox when stroking a path
204 bbox += strokepath.bbox().enlarged_pt(0.5*acontext.linewidth_pt)
206 if self.strokestyles:
207 file.write("grestore\n")
209 # now, draw additional elements of decoratedpath
210 self.ornaments.processPS(file, writer, context, registry, bbox)
212 # restore global styles
213 if self.styles:
214 file.write("grestore\n")
216 def processPDF(self, file, writer, context, registry, bbox):
217 # draw (stroke and/or fill) the decoratedpath on the canvas
219 def _writestyles(styles, context, registry):
220 for style in styles:
221 style.processPDF(file, writer, context, registry)
223 def _writestrokestyles(strokestyles, context, registry):
224 context.fillattr = 0
225 for style in strokestyles:
226 style.processPDF(file, writer, context, registry)
227 context.fillattr = 1
229 def _writefillstyles(fillstyles, context, registry):
230 context.strokeattr = 0
231 for style in fillstyles:
232 style.processPDF(file, writer, context, registry)
233 context.strokeattr = 1
235 if self.strokestyles is None and self.fillstyles is None:
236 if not len(self.ornaments):
237 raise RuntimeError("Path neither to be stroked nor filled nor decorated in another way")
238 # just draw additional elements of decoratedpath
239 self.ornaments.processPDF(file, writer, context, registry, bbox)
240 return
242 strokepath = self.strokepath()
243 fillpath = self.path
245 # apply global styles
246 if self.styles:
247 file.write("q\n") # gsave
248 context = context()
249 _writestyles(self.styles, context, registry)
251 if self.fillstyles is not None:
252 fillpath.outputPDF(file, writer)
254 if self.strokestyles is not None and strokepath is fillpath:
255 # do efficient stroking + filling
256 file.write("q\n") # gsave
257 acontext = context()
259 if self.fillstyles:
260 _writefillstyles(self.fillstyles, acontext, registry)
261 if self.strokestyles:
262 _writestrokestyles(self.strokestyles, acontext, registry)
264 if context.fillrule:
265 file.write("B*\n")
266 else:
267 file.write("B\n") # both stroke and fill
268 # take linewidth into account for bbox when stroking a path
269 bbox += strokepath.bbox().enlarged_pt(0.5*acontext.linewidth_pt)
271 file.write("Q\n") # grestore
272 else:
273 # only fill fillpath - for the moment
274 if self.fillstyles:
275 file.write("q\n") # gsave
276 _writefillstyles(self.fillstyles, context(), registry)
278 if context.fillrule:
279 file.write("f*\n")
280 else:
281 file.write("f\n") # fill
282 bbox += fillpath.bbox()
284 if self.fillstyles:
285 file.write("Q\n") # grestore
287 if self.strokestyles is not None and (strokepath is not fillpath or self.fillstyles is None):
288 # this is the only relevant case still left
289 # Note that a possible stroking has already been done.
290 acontext = context()
292 if self.strokestyles:
293 file.write("q\n") # gsave
294 _writestrokestyles(self.strokestyles, acontext, registry)
296 strokepath.outputPDF(file, writer)
297 file.write("S\n") # stroke
298 # take linewidth into account for bbox when stroking a path
299 bbox += strokepath.bbox().enlarged_pt(0.5*acontext.linewidth_pt)
301 if self.strokestyles:
302 file.write("Q\n") # grestore
304 # now, draw additional elements of decoratedpath
305 self.ornaments.processPDF(file, writer, context, registry, bbox)
307 # restore global styles
308 if self.styles:
309 file.write("Q\n") # grestore
312 # Path decorators
315 class deco:
317 """decorators
319 In contrast to path styles, path decorators depend on the concrete
320 path to which they are applied. In particular, they don't make
321 sense without any path and can thus not be used in canvas.set!
325 def decorate(self, dp, texrunner):
326 """apply a style to a given decoratedpath object dp
328 decorate accepts a decoratedpath object dp, applies PathStyle
329 by modifying dp in place.
332 pass
335 # stroked and filled: basic decos which stroked and fill,
336 # respectively the path
339 class _stroked(deco, attr.exclusiveattr):
341 """stroked is a decorator, which draws the outline of the path"""
343 def __init__(self, styles=[]):
344 attr.exclusiveattr.__init__(self, _stroked)
345 self.styles = attr.mergeattrs(styles)
346 attr.checkattrs(self.styles, [style.strokestyle])
348 def __call__(self, styles=[]):
349 # XXX or should we also merge self.styles
350 return _stroked(styles)
352 def decorate(self, dp, texrunner):
353 if dp.strokestyles is not None:
354 raise RuntimeError("Cannot stroke an already stroked path")
355 dp.strokestyles = self.styles
357 stroked = _stroked()
358 stroked.clear = attr.clearclass(_stroked)
361 class _filled(deco, attr.exclusiveattr):
363 """filled is a decorator, which fills the interior of the path"""
365 def __init__(self, styles=[]):
366 attr.exclusiveattr.__init__(self, _filled)
367 self.styles = attr.mergeattrs(styles)
368 attr.checkattrs(self.styles, [style.fillstyle])
370 def __call__(self, styles=[]):
371 # XXX or should we also merge self.styles
372 return _filled(styles)
374 def decorate(self, dp, texrunner):
375 if dp.fillstyles is not None:
376 raise RuntimeError("Cannot fill an already filled path")
377 dp.fillstyles = self.styles
379 filled = _filled()
380 filled.clear = attr.clearclass(_filled)
383 # Arrows
386 # helper function which constructs the arrowhead
388 def _arrowhead(anormpath, arclenfrombegin, direction, size, angle, constriction, constrictionlen):
390 """helper routine, which returns an arrowhead from a given anormpath
392 - arclenfrombegin: position of arrow in arc length from the start of the path
393 - direction: +1 for an arrow pointing along the direction of anormpath or
394 -1 for an arrow pointing opposite to the direction of normpath
395 - size: size of the arrow as arc length
396 - angle. opening angle
397 - constriction: boolean to indicate whether the constriction point is to be taken into account or not
398 - constrictionlen: arc length of constriction. (not used when constriction is false)
401 # arc length and coordinates of tip
402 tx, ty = anormpath.at(arclenfrombegin)
404 # construct the template for the arrow by cutting the path at the
405 # corresponding length
406 arrowtemplate = anormpath.split([arclenfrombegin, arclenfrombegin - direction * size])[1]
408 # from this template, we construct the two outer curves of the arrow
409 arrowl = arrowtemplate.transformed(trafo.rotate(-angle/2.0, tx, ty))
410 arrowr = arrowtemplate.transformed(trafo.rotate( angle/2.0, tx, ty))
412 # now come the joining backward parts
413 if constriction:
414 # constriction point (cx, cy) lies on path
415 cx, cy = anormpath.at(arclenfrombegin - direction * constrictionlen)
416 arrowcr= path.line(*(arrowr.atend() + (cx,cy)))
417 arrow = arrowl.reversed() << arrowr << arrowcr
418 else:
419 arrow = arrowl.reversed() << arrowr
421 arrow[-1].close()
423 return arrow
426 _base = 6 * unit.v_pt
428 class arrow(deco, attr.attr):
430 """arrow is a decorator which adds an arrow to either side of the path"""
432 def __init__(self, attrs=[], pos=1, reversed=0, size=_base, angle=45, constriction=0.8):
433 self.attrs = attr.mergeattrs([style.linestyle.solid, filled] + attrs)
434 attr.checkattrs(self.attrs, [deco, style.fillstyle, style.strokestyle])
435 self.pos = pos
436 self.reversed = reversed
437 self.size = size
438 self.angle = angle
439 self.constriction = constriction
441 # calculate absolute arc length of constricition
442 # Note that we have to correct this length because the arrowtemplates are rotated
443 # by self.angle/2 to the left and right. Hence, if we want no constriction, i.e., for
444 # self.constriction = 1, we actually have a length which is approximately shorter
445 # by the given geometrical factor.
446 if self.constriction is not None:
447 self.constrictionlen = self.size * self.constriction * math.cos(math.radians(self.angle/2.0))
448 else:
449 # if we do not want a constriction, i.e. constriction is None, we still
450 # need constrictionlen for cutting the path
451 self.constrictionlen = self.size * 1 * math.cos(math.radians(self.angle/2.0))
453 def __call__(self, attrs=None, pos=None, reversed=None, size=None, angle=None, constriction=_marker):
454 if attrs is None:
455 attrs = self.attrs
456 if pos is None:
457 pos = self.pos
458 if reversed is None:
459 reversed = self.reversed
460 if size is None:
461 size = self.size
462 if angle is None:
463 angle = self.angle
464 if constriction is _marker:
465 constriction = self.constriction
466 return arrow(attrs=attrs, pos=pos, reversed=reversed, size=size, angle=angle, constriction=constriction)
468 def decorate(self, dp, texrunner):
469 dp.ensurenormpath()
470 anormpath = dp.path
472 arclenfrombegin = (1-self.reversed)*self.constrictionlen + self.pos * (anormpath.arclen() - self.constrictionlen)
473 direction = self.reversed and -1 or 1
474 arrowhead = _arrowhead(anormpath, arclenfrombegin, direction, self.size, self.angle,
475 self.constriction is not None, self.constrictionlen)
477 # add arrowhead to decoratedpath
478 dp.ornaments.draw(arrowhead, self.attrs)
480 # exlude part of the path from stroking when the arrow is strictly at the begin or the end
481 if self.pos == 0 and self.reversed:
482 dp.excluderange(0, min(self.size, self.constrictionlen))
483 elif self.pos == 1 and not self.reversed:
484 dp.excluderange(anormpath.end() - min(self.size, self.constrictionlen), anormpath.end())
486 arrow.clear = attr.clearclass(arrow)
488 # arrows at begin of path
489 barrow = arrow(pos=0, reversed=1)
490 barrow.SMALL = barrow(size=_base/math.sqrt(64))
491 barrow.SMALl = barrow(size=_base/math.sqrt(32))
492 barrow.SMAll = barrow(size=_base/math.sqrt(16))
493 barrow.SMall = barrow(size=_base/math.sqrt(8))
494 barrow.Small = barrow(size=_base/math.sqrt(4))
495 barrow.small = barrow(size=_base/math.sqrt(2))
496 barrow.normal = barrow(size=_base)
497 barrow.large = barrow(size=_base*math.sqrt(2))
498 barrow.Large = barrow(size=_base*math.sqrt(4))
499 barrow.LArge = barrow(size=_base*math.sqrt(8))
500 barrow.LARge = barrow(size=_base*math.sqrt(16))
501 barrow.LARGe = barrow(size=_base*math.sqrt(32))
502 barrow.LARGE = barrow(size=_base*math.sqrt(64))
504 # arrows at end of path
505 earrow = arrow()
506 earrow.SMALL = earrow(size=_base/math.sqrt(64))
507 earrow.SMALl = earrow(size=_base/math.sqrt(32))
508 earrow.SMAll = earrow(size=_base/math.sqrt(16))
509 earrow.SMall = earrow(size=_base/math.sqrt(8))
510 earrow.Small = earrow(size=_base/math.sqrt(4))
511 earrow.small = earrow(size=_base/math.sqrt(2))
512 earrow.normal = earrow(size=_base)
513 earrow.large = earrow(size=_base*math.sqrt(2))
514 earrow.Large = earrow(size=_base*math.sqrt(4))
515 earrow.LArge = earrow(size=_base*math.sqrt(8))
516 earrow.LARge = earrow(size=_base*math.sqrt(16))
517 earrow.LARGe = earrow(size=_base*math.sqrt(32))
518 earrow.LARGE = earrow(size=_base*math.sqrt(64))
521 class text(deco, attr.attr):
522 """a simple text decorator"""
524 def __init__(self, text, textattrs=[], angle=0, relangle=None, textdist=0.2,
525 relarclenpos=0.5, arclenfrombegin=None, arclenfromend=None,
526 texrunner=None):
527 if arclenfrombegin is not None and arclenfromend is not None:
528 raise ValueError("either set arclenfrombegin or arclenfromend")
529 self.text = text
530 self.textattrs = textattrs
531 self.angle = angle
532 self.relangle = relangle
533 self.textdist = textdist
534 self.relarclenpos = relarclenpos
535 self.arclenfrombegin = arclenfrombegin
536 self.arclenfromend = arclenfromend
537 self.texrunner = texrunner
539 def decorate(self, dp, texrunner):
540 if self.texrunner:
541 texrunner = self.texrunner
542 from . import text as textmodule
543 textattrs = attr.mergeattrs([textmodule.halign.center, textmodule.vshift.mathaxis] + self.textattrs)
545 dp.ensurenormpath()
546 if self.arclenfrombegin is not None:
547 param = dp.path.begin() + self.arclenfrombegin
548 elif self.arclenfromend is not None:
549 param = dp.path.end() - self.arclenfromend
550 else:
551 # relarcpos is used, when neither arcfrombegin nor arcfromend is given
552 param = self.relarclenpos * dp.path.arclen()
553 x, y = dp.path.at(param)
555 if self.relangle is not None:
556 a = dp.path.trafo(param).apply_pt(math.cos(self.relangle*math.pi/180), math.sin(self.relangle*math.pi/180))
557 b = dp.path.trafo(param).apply_pt(0, 0)
558 angle = math.atan2(a[1] - b[1], a[0] - b[0])
559 else:
560 angle = self.angle*math.pi/180
561 t = texrunner.text(x, y, self.text, textattrs)
562 t.linealign(self.textdist, math.cos(angle), math.sin(angle))
563 dp.ornaments.insert(t)
565 class curvedtext(deco, attr.attr):
566 """a text decorator for curved text
568 - text: is typeset along the path to which this decorator is applied
569 - relarclenpos: position for the base point of the text (default: 0)
570 - arlenfrombegin, arclenfromend: alternative ways of specifying the position of the base point;
571 use of relarclenpos, arclenfrombegin and arclenfromend is mutually exclusive
572 - textattrs, texrunner: standard text arguments (defaults: [] resp None)
576 # defaulttextattrs = [textmodule.halign.center] # TODO: not possible due to cyclic import issue
578 def __init__(self, text, textattrs=[],
579 relarclenpos=0.5, arclenfrombegin=None, arclenfromend=None,
580 texrunner=None, exclude=None):
581 if arclenfrombegin is not None and arclenfromend is not None:
582 raise ValueError("either set arclenfrombegin or arclenfromend")
583 self.text = text
584 self.textattrs = textattrs
585 self.relarclenpos = relarclenpos
586 self.arclenfrombegin = arclenfrombegin
587 self.arclenfromend = arclenfromend
588 self.texrunner = texrunner
589 self.exclude = exclude
591 def decorate(self, dp, texrunner):
592 if self.texrunner:
593 texrunner = self.texrunner
594 from . import text as textmodule
595 self.defaulttextattrs = [textmodule.halign.center]
597 dp.ensurenormpath()
598 if self.arclenfrombegin is not None:
599 textpos = dp.path.begin() + self.arclenfrombegin
600 elif self.arclenfromend is not None:
601 textpos = dp.path.end() - self.arclenfromend
602 else:
603 # relarcpos is used if neither arcfrombegin nor arcfromend is given
604 textpos = self.relarclenpos * dp.path.arclen()
606 textattrs = self.defaulttextattrs + self.textattrs
607 t = texrunner.text(0, 0, self.text, textattrs, singlecharmode=1)
608 t.ensuredvicanvas()
610 # we copy the style from the original textbox and modify the position for each dvicanvas item
611 c = canvas.canvas(t.styles)
612 for item in t.dvicanvas.items:
613 bbox = item.bbox()
614 bbox = bbox.transformed(t.texttrafo)
615 x = bbox.center()[0]
616 atrafo = dp.path.trafo(textpos+x)
617 c.insert(item, [t.texttrafo] + [trafo.translate(-x, 0)] + [atrafo])
618 if self.exclude is not None:
619 dp.excluderange(textpos+bbox.left()-self.exclude, textpos+bbox.right()+self.exclude)
621 dp.ornaments.insert(c)
624 class shownormpath(deco, attr.attr):
626 def decorate(self, dp, texrunner):
627 r_pt = 2
628 dp.ensurenormpath()
629 for normsubpath in dp.path.normsubpaths:
630 for i, normsubpathitem in enumerate(normsubpath.normsubpathitems):
631 if isinstance(normsubpathitem, normpath.normcurve_pt):
632 dp.ornaments.stroke(normpath.normpath([normpath.normsubpath([normsubpathitem])]), [color.rgb.green])
633 else:
634 dp.ornaments.stroke(normpath.normpath([normpath.normsubpath([normsubpathitem])]), [color.rgb.blue])
635 for normsubpath in dp.path.normsubpaths:
636 for i, normsubpathitem in enumerate(normsubpath.normsubpathitems):
637 if isinstance(normsubpathitem, normpath.normcurve_pt):
638 dp.ornaments.stroke(path.line_pt(normsubpathitem.x0_pt, normsubpathitem.y0_pt, normsubpathitem.x1_pt, normsubpathitem.y1_pt), [style.linestyle.dashed, color.rgb.red])
639 dp.ornaments.stroke(path.line_pt(normsubpathitem.x2_pt, normsubpathitem.y2_pt, normsubpathitem.x3_pt, normsubpathitem.y3_pt), [style.linestyle.dashed, color.rgb.red])
640 dp.ornaments.draw(path.circle_pt(normsubpathitem.x1_pt, normsubpathitem.y1_pt, r_pt), [filled([color.rgb.red])])
641 dp.ornaments.draw(path.circle_pt(normsubpathitem.x2_pt, normsubpathitem.y2_pt, r_pt), [filled([color.rgb.red])])
642 for normsubpath in dp.path.normsubpaths:
643 for i, normsubpathitem in enumerate(normsubpath.normsubpathitems):
644 if not i:
645 x_pt, y_pt = normsubpathitem.atbegin_pt()
646 dp.ornaments.draw(path.circle_pt(x_pt, y_pt, r_pt), [filled])
647 x_pt, y_pt = normsubpathitem.atend_pt()
648 dp.ornaments.draw(path.circle_pt(x_pt, y_pt, r_pt), [filled])
651 class linehatched(deco, attr.exclusiveattr, attr.clearclass):
652 """draws a pattern with explicit lines
654 This class acts as a drop-in replacement for postscript patterns
655 from the pattern module which are not understood by some printers"""
657 def __init__(self, dist, angle, strokestyles=[], cross=0):
658 attr.clearclass.__init__(self, _filled)
659 attr.exclusiveattr.__init__(self, linehatched)
660 self.dist = dist
661 self.angle = angle
662 self.strokestyles = attr.mergeattrs([style.linewidth.THIN] + strokestyles)
663 attr.checkattrs(self.strokestyles, [style.strokestyle])
664 self.cross = cross
666 def __call__(self, dist=None, angle=None, strokestyles=None, cross=None):
667 if dist is None:
668 dist = self.dist
669 if angle is None:
670 angle = self.angle
671 if strokestyles is None:
672 strokestyles = self.strokestyles
673 if cross is None:
674 cross = self.cross
675 return linehatched(dist, angle, strokestyles, cross)
677 def _decocanvas(self, angle, dp, texrunner):
678 dp.ensurenormpath()
679 dist_pt = unit.topt(self.dist)
681 c = canvas.canvas([canvas.clip(dp.path)])
682 llx_pt, lly_pt, urx_pt, ury_pt = dp.path.bbox().highrestuple_pt()
683 center_pt = 0.5*(llx_pt+urx_pt), 0.5*(lly_pt+ury_pt)
684 radius_pt = 0.5*math.hypot(urx_pt-llx_pt, ury_pt-lly_pt) + dist_pt
685 n = int(2*radius_pt / dist_pt) + 1
686 for i in range(n):
687 x_pt = center_pt[0] - radius_pt + i*dist_pt
688 c.stroke(path.line_pt(x_pt, center_pt[1]-radius_pt, x_pt, center_pt[1]+radius_pt),
689 [trafo.rotate_pt(angle, center_pt[0], center_pt[1])] + self.strokestyles)
690 return c
692 def decorate(self, dp, texrunner):
693 dp.ornaments.insert(self._decocanvas(self.angle, dp, texrunner))
694 if self.cross:
695 dp.ornaments.insert(self._decocanvas(self.angle+90, dp, texrunner))
697 def merge(self, attrs):
698 # act as attr.clearclass and as attr.exclusiveattr at the same time
699 newattrs = attr.exclusiveattr.merge(self, attrs)
700 return attr.clearclass.merge(self, newattrs)
702 linehatched.clear = attr.clearclass(linehatched)
704 _hatch_base = 0.1 * unit.v_cm
706 linehatched0 = linehatched(_hatch_base, 0)
707 linehatched0.SMALL = linehatched0(_hatch_base/math.sqrt(64))
708 linehatched0.SMALL = linehatched0(_hatch_base/math.sqrt(64))
709 linehatched0.SMALl = linehatched0(_hatch_base/math.sqrt(32))
710 linehatched0.SMAll = linehatched0(_hatch_base/math.sqrt(16))
711 linehatched0.SMall = linehatched0(_hatch_base/math.sqrt(8))
712 linehatched0.Small = linehatched0(_hatch_base/math.sqrt(4))
713 linehatched0.small = linehatched0(_hatch_base/math.sqrt(2))
714 linehatched0.normal = linehatched0(_hatch_base)
715 linehatched0.large = linehatched0(_hatch_base*math.sqrt(2))
716 linehatched0.Large = linehatched0(_hatch_base*math.sqrt(4))
717 linehatched0.LArge = linehatched0(_hatch_base*math.sqrt(8))
718 linehatched0.LARge = linehatched0(_hatch_base*math.sqrt(16))
719 linehatched0.LARGe = linehatched0(_hatch_base*math.sqrt(32))
720 linehatched0.LARGE = linehatched0(_hatch_base*math.sqrt(64))
722 linehatched45 = linehatched(_hatch_base, 45)
723 linehatched45.SMALL = linehatched45(_hatch_base/math.sqrt(64))
724 linehatched45.SMALl = linehatched45(_hatch_base/math.sqrt(32))
725 linehatched45.SMAll = linehatched45(_hatch_base/math.sqrt(16))
726 linehatched45.SMall = linehatched45(_hatch_base/math.sqrt(8))
727 linehatched45.Small = linehatched45(_hatch_base/math.sqrt(4))
728 linehatched45.small = linehatched45(_hatch_base/math.sqrt(2))
729 linehatched45.normal = linehatched45(_hatch_base)
730 linehatched45.large = linehatched45(_hatch_base*math.sqrt(2))
731 linehatched45.Large = linehatched45(_hatch_base*math.sqrt(4))
732 linehatched45.LArge = linehatched45(_hatch_base*math.sqrt(8))
733 linehatched45.LARge = linehatched45(_hatch_base*math.sqrt(16))
734 linehatched45.LARGe = linehatched45(_hatch_base*math.sqrt(32))
735 linehatched45.LARGE = linehatched45(_hatch_base*math.sqrt(64))
737 linehatched90 = linehatched(_hatch_base, 90)
738 linehatched90.SMALL = linehatched90(_hatch_base/math.sqrt(64))
739 linehatched90.SMALl = linehatched90(_hatch_base/math.sqrt(32))
740 linehatched90.SMAll = linehatched90(_hatch_base/math.sqrt(16))
741 linehatched90.SMall = linehatched90(_hatch_base/math.sqrt(8))
742 linehatched90.Small = linehatched90(_hatch_base/math.sqrt(4))
743 linehatched90.small = linehatched90(_hatch_base/math.sqrt(2))
744 linehatched90.normal = linehatched90(_hatch_base)
745 linehatched90.large = linehatched90(_hatch_base*math.sqrt(2))
746 linehatched90.Large = linehatched90(_hatch_base*math.sqrt(4))
747 linehatched90.LArge = linehatched90(_hatch_base*math.sqrt(8))
748 linehatched90.LARge = linehatched90(_hatch_base*math.sqrt(16))
749 linehatched90.LARGe = linehatched90(_hatch_base*math.sqrt(32))
750 linehatched90.LARGE = linehatched90(_hatch_base*math.sqrt(64))
752 linehatched135 = linehatched(_hatch_base, 135)
753 linehatched135.SMALL = linehatched135(_hatch_base/math.sqrt(64))
754 linehatched135.SMALl = linehatched135(_hatch_base/math.sqrt(32))
755 linehatched135.SMAll = linehatched135(_hatch_base/math.sqrt(16))
756 linehatched135.SMall = linehatched135(_hatch_base/math.sqrt(8))
757 linehatched135.Small = linehatched135(_hatch_base/math.sqrt(4))
758 linehatched135.small = linehatched135(_hatch_base/math.sqrt(2))
759 linehatched135.normal = linehatched135(_hatch_base)
760 linehatched135.large = linehatched135(_hatch_base*math.sqrt(2))
761 linehatched135.Large = linehatched135(_hatch_base*math.sqrt(4))
762 linehatched135.LArge = linehatched135(_hatch_base*math.sqrt(8))
763 linehatched135.LARge = linehatched135(_hatch_base*math.sqrt(16))
764 linehatched135.LARGe = linehatched135(_hatch_base*math.sqrt(32))
765 linehatched135.LARGE = linehatched135(_hatch_base*math.sqrt(64))
767 crosslinehatched0 = linehatched(_hatch_base, 0, cross=1)
768 crosslinehatched0.SMALL = crosslinehatched0(_hatch_base/math.sqrt(64))
769 crosslinehatched0.SMALl = crosslinehatched0(_hatch_base/math.sqrt(32))
770 crosslinehatched0.SMAll = crosslinehatched0(_hatch_base/math.sqrt(16))
771 crosslinehatched0.SMall = crosslinehatched0(_hatch_base/math.sqrt(8))
772 crosslinehatched0.Small = crosslinehatched0(_hatch_base/math.sqrt(4))
773 crosslinehatched0.small = crosslinehatched0(_hatch_base/math.sqrt(2))
774 crosslinehatched0.normal = crosslinehatched0
775 crosslinehatched0.large = crosslinehatched0(_hatch_base*math.sqrt(2))
776 crosslinehatched0.Large = crosslinehatched0(_hatch_base*math.sqrt(4))
777 crosslinehatched0.LArge = crosslinehatched0(_hatch_base*math.sqrt(8))
778 crosslinehatched0.LARge = crosslinehatched0(_hatch_base*math.sqrt(16))
779 crosslinehatched0.LARGe = crosslinehatched0(_hatch_base*math.sqrt(32))
780 crosslinehatched0.LARGE = crosslinehatched0(_hatch_base*math.sqrt(64))
782 crosslinehatched45 = linehatched(_hatch_base, 45, cross=1)
783 crosslinehatched45.SMALL = crosslinehatched45(_hatch_base/math.sqrt(64))
784 crosslinehatched45.SMALl = crosslinehatched45(_hatch_base/math.sqrt(32))
785 crosslinehatched45.SMAll = crosslinehatched45(_hatch_base/math.sqrt(16))
786 crosslinehatched45.SMall = crosslinehatched45(_hatch_base/math.sqrt(8))
787 crosslinehatched45.Small = crosslinehatched45(_hatch_base/math.sqrt(4))
788 crosslinehatched45.small = crosslinehatched45(_hatch_base/math.sqrt(2))
789 crosslinehatched45.normal = crosslinehatched45
790 crosslinehatched45.large = crosslinehatched45(_hatch_base*math.sqrt(2))
791 crosslinehatched45.Large = crosslinehatched45(_hatch_base*math.sqrt(4))
792 crosslinehatched45.LArge = crosslinehatched45(_hatch_base*math.sqrt(8))
793 crosslinehatched45.LARge = crosslinehatched45(_hatch_base*math.sqrt(16))
794 crosslinehatched45.LARGe = crosslinehatched45(_hatch_base*math.sqrt(32))
795 crosslinehatched45.LARGE = crosslinehatched45(_hatch_base*math.sqrt(64))
798 class colorgradient(deco, attr.attr):
799 """inserts pieces of the path in different colors"""
801 def __init__(self, grad, attrs=[], steps=20):
802 self.attrs = attrs
803 self.grad = grad
804 self.steps = steps
806 def decorate(self, dp, texrunner):
807 dp.ensurenormpath()
808 l = dp.path.arclen()
810 colors = [self.grad.select(n, self.steps) for n in range(self.steps)]
811 colors.reverse()
812 params = dp.path.arclentoparam([l*i/float(self.steps) for i in range(self.steps)])
813 params.reverse()
815 c = canvas.canvas()
816 # treat the end pieces separately
817 c.stroke(dp.path.split(params[1])[1], attr.mergeattrs([colors[0]] + self.attrs))
818 for n in range(1,self.steps-1):
819 c.stroke(dp.path.split([params[n-1],params[n+1]])[1], attr.mergeattrs([colors[n]] + self.attrs))
820 c.stroke(dp.path.split(params[-2])[0], attr.mergeattrs([colors[-1]] + self.attrs))
821 dp.ornaments.insert(c)
824 class brace(deco, attr.attr):
825 r"""draws a nicely curled brace
827 In most cases, the original line is not wanted use canvas.canvas.draw(..) for it
829 Geometrical parameters:
831 inner /\ strokes
832 ____________/ \__________
833 / bar bar \ outer
834 / \ strokes
836 totalheight distance from the jaws to the middle cap
837 barthickness thickness of the main bars
838 innerstrokesthickness thickness of the two ending strokes
839 outerstrokesthickness thickness of the inner strokes at the middle cap
840 innerstrokesrelheight height of the inner/outer strokes, relative to the total height
841 outerstrokesrelheight this determines the angle of the main bars!
842 should be around 0.5
843 Note: if innerstrokesrelheight + outerstrokesrelheight == 1 then the main bars
844 will be aligned parallel to the connecting line between the endpoints
845 outerstrokesangle angle of the two ending strokes
846 innerstrokesangle angle between the inner strokes at the middle cap
847 slantstrokesangle extra slanting of the inner/outer strokes
848 innerstrokessmoothness smoothing parameter for the inner + outer strokes
849 outerstrokessmoothness should be around 1 (allowed: [0,infty))
850 middlerelpos position of the middle cap (0 == left, 1 == right)
852 # This code is experimental because it is unclear
853 # how the brace fits into the concepts of PyX
855 # Some thoughts:
856 # - a brace needs to be decoratable with text
857 # it needs stroking and filling attributes
858 # - the brace is not really a box:
859 # it has two "anchor" points that are important for aligning it to other things
860 # and one "anchor" point (plus direction) for aligning other things
861 # - a brace is not a deformer:
862 # it does not look at anything else than begin/endpoint of a path
863 # - a brace might be a connector (which is to be dissolved into the box concept later?)
865 def __init__(self, reverse=1, stretch=None, dist=None, fillattrs=[],
866 totalheight=12*unit.x_pt,
867 barthickness=0.5*unit.x_pt, innerstrokesthickness=0.25*unit.x_pt, outerstrokesthickness=0.25*unit.x_pt,
868 innerstrokesrelheight=0.6, outerstrokesrelheight=0.7,
869 innerstrokesangle=30, outerstrokesangle=25, slantstrokesangle=5,
870 innerstrokessmoothness=2.0, outerstrokessmoothness=2.5,
871 middlerelpos=0.5):
872 self.fillattrs = fillattrs
873 self.reverse = reverse
874 self.stretch = stretch
875 self.dist = dist
876 self.totalheight = totalheight
877 self.barthickness = barthickness
878 self.innerstrokesthickness = innerstrokesthickness
879 self.outerstrokesthickness = outerstrokesthickness
880 self.innerstrokesrelheight = innerstrokesrelheight
881 self.outerstrokesrelheight = outerstrokesrelheight
882 self.innerstrokesangle = innerstrokesangle
883 self.outerstrokesangle = outerstrokesangle
884 self.slantstrokesangle = slantstrokesangle
885 self.innerstrokessmoothness = innerstrokessmoothness
886 self.outerstrokessmoothness = outerstrokessmoothness
887 self.middlerelpos = middlerelpos
889 def __call__(self, **kwargs):
890 for name in ["reverse", "stretch", "dist", "fillattrs",
891 "totalheight", "barthickness", "innerstrokesthickness", "outerstrokesthickness",
892 "innerstrokesrelheight", "outerstrokesrelheight", "innerstrokesangle", "outerstrokesangle", "slantstrokesangle",
893 "innerstrokessmoothness", "outerstrokessmoothness", "middlerelpos"]:
894 if name not in kwargs:
895 kwargs[name] = self.__dict__[name]
896 return brace(**kwargs)
898 def _halfbracepath_pt(self, length_pt, height_pt, ilength_pt, olength_pt, # <<<
899 ithick_pt, othick_pt, bthick_pt, cos_iangle, sin_iangle, cos_oangle,
900 sin_oangle, cos_slangle, sin_slangle):
902 ismooth = self.innerstrokessmoothness
903 osmooth = self.outerstrokessmoothness
905 # these two parameters are not important enough to be seen outside
906 inner_cap_param = 1.5
907 outer_cap_param = 2.5
908 outerextracurved = 0.6 # in (0, 1]
909 # 1.0 will lead to F=G, the outer strokes will not be curved at their ends.
910 # The smaller, the more curvature
912 # build an orientation path (three straight lines)
914 # \q1
915 # / \
916 # / \
917 # _/ \______________________________________q5
918 # q2 q3 q4 \
921 # \q6
923 # get the points for that:
924 q1 = (0, height_pt - inner_cap_param * ithick_pt + 0.5*ithick_pt/sin_iangle)
925 q2 = (q1[0] + ilength_pt * sin_iangle,
926 q1[1] - ilength_pt * cos_iangle)
927 q6 = (length_pt, 0)
928 q5 = (q6[0] - olength_pt * sin_oangle,
929 q6[1] + olength_pt * cos_oangle)
930 bardir = (q5[0] - q2[0], q5[1] - q2[1])
931 bardirnorm = math.hypot(*bardir)
932 bardir = (bardir[0]/bardirnorm, bardir[1]/bardirnorm)
933 ismoothlength_pt = ilength_pt * ismooth
934 osmoothlength_pt = olength_pt * osmooth
935 if bardirnorm < ismoothlength_pt + osmoothlength_pt:
936 ismoothlength_pt = bardirnorm * ismoothlength_pt / (ismoothlength_pt + osmoothlength_pt)
937 osmoothlength_pt = bardirnorm * osmoothlength_pt / (ismoothlength_pt + osmoothlength_pt)
938 q3 = (q2[0] + ismoothlength_pt * bardir[0],
939 q2[1] + ismoothlength_pt * bardir[1])
940 q4 = (q5[0] - osmoothlength_pt * bardir[0],
941 q5[1] - osmoothlength_pt * bardir[1])
944 # P _O
945 # / | \A2
946 # / A1\ \
947 # / \ B2C2________D2___________E2_______F2___G2
948 # \______________________________________ \
949 # B1,C1 D1 E1 F1 G1 \
950 # \ \
951 # \ \H2
952 # H1\_/I2
953 # I1
955 # the halfbraces meet in P and A1:
956 P = (0, height_pt)
957 A1 = (0, height_pt - inner_cap_param * ithick_pt)
958 # A2 is A1, shifted by the inner thickness
959 A2 = (A1[0] + ithick_pt * cos_iangle,
960 A1[1] + ithick_pt * sin_iangle)
961 s, t = deformer.intersection(P, A2, (cos_slangle, sin_slangle), (sin_iangle, -cos_iangle))
962 O = (P[0] + s * cos_slangle,
963 P[1] + s * sin_slangle)
965 # from D1 to E1 is the straight part of the brace
966 # also back from E2 to D1
967 D1 = (q3[0] + bthick_pt * bardir[1],
968 q3[1] - bthick_pt * bardir[0])
969 D2 = (q3[0] - bthick_pt * bardir[1],
970 q3[1] + bthick_pt * bardir[0])
971 E1 = (q4[0] + bthick_pt * bardir[1],
972 q4[1] - bthick_pt * bardir[0])
973 E2 = (q4[0] - bthick_pt * bardir[1],
974 q4[1] + bthick_pt * bardir[0])
975 # I1, I2 are the control points at the outer stroke
976 I1 = (q6[0] - 0.5 * othick_pt * cos_oangle,
977 q6[1] - 0.5 * othick_pt * sin_oangle)
978 I2 = (q6[0] + 0.5 * othick_pt * cos_oangle,
979 q6[1] + 0.5 * othick_pt * sin_oangle)
980 # get the control points for the curved parts of the brace
981 s, t = deformer.intersection(A1, D1, (sin_iangle, -cos_iangle), bardir)
982 B1 = (D1[0] + t * bardir[0],
983 D1[1] + t * bardir[1])
984 s, t = deformer.intersection(A2, D2, (sin_iangle, -cos_iangle), bardir)
985 B2 = (D2[0] + t * bardir[0],
986 D2[1] + t * bardir[1])
987 s, t = deformer.intersection(E1, I1, bardir, (-sin_oangle, cos_oangle))
988 G1 = (E1[0] + s * bardir[0],
989 E1[1] + s * bardir[1])
990 s, t = deformer.intersection(E2, I2, bardir, (-sin_oangle, cos_oangle))
991 G2 = (E2[0] + s * bardir[0],
992 E2[1] + s * bardir[1])
993 # at the inner strokes: use curvature zero at both ends
994 C1 = B1
995 C2 = B2
996 # at the outer strokes: use curvature zero only at the connection to
997 # the straight part
998 F1 = (outerextracurved * G1[0] + (1 - outerextracurved) * E1[0],
999 outerextracurved * G1[1] + (1 - outerextracurved) * E1[1])
1000 F2 = (outerextracurved * G2[0] + (1 - outerextracurved) * E2[0],
1001 outerextracurved * G2[1] + (1 - outerextracurved) * E2[1])
1002 # the tip of the outer stroke, endpoints of the bezier curve
1003 H1 = (I1[0] - outer_cap_param * othick_pt * sin_oangle,
1004 I1[1] + outer_cap_param * othick_pt * cos_oangle)
1005 H2 = (I2[0] - outer_cap_param * othick_pt * sin_oangle,
1006 I2[1] + outer_cap_param * othick_pt * cos_oangle)
1008 #for qq in [A1,B1,C1,D1,E1,F1,G1,H1,I1,
1009 # A2,B2,C2,D2,E2,F2,G2,H2,I2,
1010 # O,P
1011 # ]:
1012 # cc.fill(path.circle(qq[0], qq[1], 0.5), [color.rgb.green])
1014 # now build the right halfbrace
1015 bracepath = path.path(path.moveto_pt(*A1))
1016 bracepath.append(path.curveto_pt(B1[0], B1[1], C1[0], C1[1], D1[0], D1[1]))
1017 bracepath.append(path.lineto_pt(E1[0], E1[1]))
1018 bracepath.append(path.curveto_pt(F1[0], F1[1], G1[0], G1[1], H1[0], H1[1]))
1019 # the tip of the right halfbrace
1020 bracepath.append(path.curveto_pt(I1[0], I1[1], I2[0], I2[1], H2[0], H2[1]))
1021 # the rest of the right halfbrace
1022 bracepath.append(path.curveto_pt(G2[0], G2[1], F2[0], F2[1], E2[0], E2[1]))
1023 bracepath.append(path.lineto_pt(D2[0], D2[1]))
1024 bracepath.append(path.curveto_pt(C2[0], C2[1], B2[0], B2[1], A2[0], A2[1]))
1025 # the tip in the middle of the brace
1026 bracepath.append(path.curveto_pt(O[0], O[1], O[0], O[1], P[0], P[1]))
1028 return bracepath
1029 # >>>
1031 def _bracepath(self, x0_pt, y0_pt, x1_pt, y1_pt): # <<<
1032 height_pt = unit.topt(self.totalheight)
1033 totallength_pt = math.hypot(x1_pt - x0_pt, y1_pt - y0_pt)
1034 leftlength_pt = self.middlerelpos * totallength_pt
1035 rightlength_pt = totallength_pt - leftlength_pt
1036 ithick_pt = unit.topt(self.innerstrokesthickness)
1037 othick_pt = unit.topt(self.outerstrokesthickness)
1038 bthick_pt = unit.topt(self.barthickness)
1040 # create the left halfbrace with positive slanting
1041 # because we will mirror this part
1042 cos_iangle = math.cos(math.radians(0.5*self.innerstrokesangle - self.slantstrokesangle))
1043 sin_iangle = math.sin(math.radians(0.5*self.innerstrokesangle - self.slantstrokesangle))
1044 cos_oangle = math.cos(math.radians(self.outerstrokesangle - self.slantstrokesangle))
1045 sin_oangle = math.sin(math.radians(self.outerstrokesangle - self.slantstrokesangle))
1046 cos_slangle = math.cos(math.radians(-self.slantstrokesangle))
1047 sin_slangle = math.sin(math.radians(-self.slantstrokesangle))
1048 ilength_pt = self.innerstrokesrelheight * height_pt / cos_iangle
1049 olength_pt = self.outerstrokesrelheight * height_pt / cos_oangle
1051 bracepath = self._halfbracepath_pt(leftlength_pt, height_pt,
1052 ilength_pt, olength_pt, ithick_pt, othick_pt, bthick_pt, cos_iangle,
1053 sin_iangle, cos_oangle, sin_oangle, cos_slangle,
1054 sin_slangle).reversed().transformed(trafo.mirror(90))
1056 # create the right halfbrace with negative slanting
1057 cos_iangle = math.cos(math.radians(0.5*self.innerstrokesangle + self.slantstrokesangle))
1058 sin_iangle = math.sin(math.radians(0.5*self.innerstrokesangle + self.slantstrokesangle))
1059 cos_oangle = math.cos(math.radians(self.outerstrokesangle + self.slantstrokesangle))
1060 sin_oangle = math.sin(math.radians(self.outerstrokesangle + self.slantstrokesangle))
1061 cos_slangle = math.cos(math.radians(-self.slantstrokesangle))
1062 sin_slangle = math.sin(math.radians(-self.slantstrokesangle))
1063 ilength_pt = self.innerstrokesrelheight * height_pt / cos_iangle
1064 olength_pt = self.outerstrokesrelheight * height_pt / cos_oangle
1066 bracepath = bracepath << self._halfbracepath_pt(rightlength_pt, height_pt,
1067 ilength_pt, olength_pt, ithick_pt, othick_pt, bthick_pt, cos_iangle,
1068 sin_iangle, cos_oangle, sin_oangle, cos_slangle,
1069 sin_slangle)
1071 return bracepath.transformed(
1072 # two trafos for matching the given endpoints
1073 trafo.translate_pt(x0_pt, y0_pt) *
1074 trafo.rotate_pt(math.degrees(math.atan2(y1_pt-y0_pt, x1_pt-x0_pt))) *
1075 # one trafo to move the brace's left outer stroke to zero
1076 trafo.translate_pt(leftlength_pt, 0))
1077 # >>>
1079 def decorate(self, dp, texrunner):
1080 dp.ensurenormpath()
1081 x0_pt, y0_pt = dp.path.atbegin_pt()
1082 x1_pt, y1_pt = dp.path.atend_pt()
1083 if self.reverse:
1084 x0_pt, y0_pt, x1_pt, y1_pt = x1_pt, y1_pt, x0_pt, y0_pt
1085 if self.stretch is not None:
1086 xm, ym = 0.5*(x0_pt+x1_pt), 0.5*(y0_pt+y1_pt)
1087 x0_pt, y0_pt = xm + self.stretch*(x0_pt-xm), ym + self.stretch*(y0_pt-ym)
1088 x1_pt, y1_pt = xm + self.stretch*(x1_pt-xm), ym + self.stretch*(y1_pt-ym)
1089 if self.dist is not None:
1090 d = unit.topt(self.dist)
1091 dx, dy = dp.path.rotation_pt(dp.path.begin()).apply_pt(0, 1)
1092 x0_pt += d*dx; y0_pt += d*dy
1093 dx, dy = dp.path.rotation_pt(dp.path.end()).apply_pt(0, 1)
1094 x1_pt += d*dx; y1_pt += d*dy
1095 dp.ornaments.fill(self._bracepath(x0_pt, y0_pt, x1_pt, y1_pt), self.fillattrs)
1097 brace.clear = attr.clearclass(brace)
1099 leftbrace = brace(reverse=0, middlerelpos=0.55, innerstrokesrelheight=0.6, outerstrokesrelheight=0.7, slantstrokesangle=-10)
1100 rightbrace = brace(reverse=1, middlerelpos=0.45, innerstrokesrelheight=0.6, outerstrokesrelheight=0.7, slantstrokesangle=10)
1101 belowbrace = brace(reverse=1, middlerelpos=0.55, innerstrokesrelheight=0.7, outerstrokesrelheight=0.9, slantstrokesangle=-10)
1102 abovebrace = brace(reverse=0, middlerelpos=0.45, innerstrokesrelheight=0.7, outerstrokesrelheight=0.9, slantstrokesangle=-10)
1103 straightbrace = brace(innerstrokesrelheight=0.5, outerstrokesrelheight=0.5,
1104 innerstrokesangle=30, outerstrokesangle=30, slantstrokesangle=0,
1105 innerstrokessmoothness=1.0, outerstrokessmoothness=1.0)