~ubuntu-branches/debian/sid/pyx/sid

« back to all changes in this revision

Viewing changes to pyx/deco.py

  • Committer: Bazaar Package Importer
  • Author(s): Stuart Prescott
  • Date: 2011-05-20 00:13:52 UTC
  • mto: (9.1.1 experimental)
  • mto: This revision was merged to the branch mainline in revision 8.
  • Revision ID: james.westby@ubuntu.com-20110520001352-odcuqpdezuusbbw1
Tags: upstream-0.11.1
Import upstream version 0.11.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# -*- coding: ISO-8859-1 -*-
2
 
#
3
 
#
4
 
# Copyright (C) 2002-2006 J�rg Lehmann <joergl@users.sourceforge.net>
5
 
# Copyright (C) 2003-2004 Michael Schindler <m-schindler@users.sourceforge.net>
6
 
# Copyright (C) 2002-2006 Andr� Wobst <wobsta@users.sourceforge.net>
 
1
# -*- encoding: utf-8 -*-
 
2
#
 
3
#
 
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>
7
7
#
8
8
# This file is part of PyX (http://pyx.sourceforge.net/).
9
9
#
25
25
# - should we improve on the arc length -> arg parametrization routine or
26
26
#   should we at least factor it out?
27
27
 
28
 
from __future__ import nested_scopes
29
 
 
30
28
import sys, math
31
 
import attr, canvas, color, path, normpath, style, trafo, unit
32
 
 
33
 
try:
34
 
    from math import radians
35
 
except ImportError:
36
 
    # fallback implementation for Python 2.1 and below
37
 
    def radians(x): return x*math.pi/180
38
 
 
39
 
class _marker: pass
 
29
import attr, canvas, canvasitem, color, path, normpath, style, trafo, unit, deformer
 
30
 
 
31
_marker = object()
40
32
 
41
33
#
42
34
# Decorated path
43
35
#
44
36
 
45
 
class decoratedpath(canvas.canvasitem):
 
37
class decoratedpath(canvasitem.canvasitem):
46
38
    """Decorated path
47
39
 
48
40
    The main purpose of this class is during the drawing
52
44
 
53
45
    def __init__(self, path, strokepath=None, fillpath=None,
54
46
                 styles=None, strokestyles=None, fillstyles=None,
55
 
                 ornaments=None):
 
47
                 ornaments=None, fillrule=style.fillrule.nonzero_winding):
56
48
 
57
49
        self.path = path
58
50
 
70
62
        else:
71
63
            self.ornaments = ornaments
72
64
 
 
65
        # the fillrule is either fillrule.nonzero_winding or fillrule.even_odd
 
66
        self.fillrule = fillrule
 
67
 
73
68
        self.nostrokeranges = None
74
69
 
75
70
    def ensurenormpath(self):
165
160
                if self.fillstyles:
166
161
                    _writestyles(self.fillstyles, context(), registry, bbox)
167
162
 
168
 
                file.write("fill\n")
 
163
                if self.fillrule.even_odd:
 
164
                    file.write("eofill\n")
 
165
                else:
 
166
                    file.write("fill\n")
169
167
                file.write("grestore\n")
170
168
 
171
169
                acontext = context()
185
183
                    file.write("gsave\n")
186
184
                    _writestyles(self.fillstyles, context(), registry, bbox)
187
185
 
188
 
                file.write("fill\n")
 
186
                if self.fillrule.even_odd:
 
187
                    file.write("eofill\n")
 
188
                else:
 
189
                    file.write("fill\n")
189
190
                bbox += fillpath.bbox()
190
191
 
191
192
                if self.fillstyles:
263
264
                if self.strokestyles:
264
265
                    _writestrokestyles(self.strokestyles, acontext, registry, bbox)
265
266
 
266
 
                file.write("B\n") # both stroke and fill
 
267
                if self.fillrule.even_odd:
 
268
                    file.write("B*\n")
 
269
                else:
 
270
                    file.write("B\n") # both stroke and fill
267
271
                # take linewidth into account for bbox when stroking a path
268
272
                bbox += strokepath.bbox().enlarged_pt(0.5*acontext.linewidth_pt)
269
273
 
274
278
                    file.write("q\n") # gsave
275
279
                    _writefillstyles(self.fillstyles, context(), registry, bbox)
276
280
 
277
 
                file.write("f\n") # fill
 
281
                if self.fillrule.even_odd:
 
282
                    file.write("f*\n")
 
283
                else:
 
284
                    file.write("f\n") # fill
278
285
                bbox += fillpath.bbox()
279
286
 
280
287
                if self.fillstyles:
458
465
        # self.constriction = 1, we actually have a length which is approximately shorter
459
466
        # by the given geometrical factor.
460
467
        if self.constriction is not None:
461
 
            constrictionlen = arrowheadconstrictionlen = self.size * self.constriction * math.cos(radians(self.angle/2.0))
 
468
            constrictionlen = arrowheadconstrictionlen = self.size * self.constriction * math.cos(math.radians(self.angle/2.0))
462
469
        else:
463
470
            # if we do not want a constriction, i.e. constriction is None, we still
464
471
            # need constrictionlen for cutting the path
465
 
            constrictionlen = self.size * 1 * math.cos(radians(self.angle/2.0))
 
472
            constrictionlen = self.size * 1 * math.cos(math.radians(self.angle/2.0))
466
473
            arrowheadconstrictionlen = None
467
474
 
468
 
        arclenfrombegin = self.pos * anormpath.arclen()
 
475
        arclenfrombegin = (1-self.reversed)*constrictionlen + self.pos * (anormpath.arclen() - constrictionlen)
469
476
        direction = self.reversed and -1 or 1
470
477
        arrowhead = _arrowhead(anormpath, arclenfrombegin, direction, self.size, self.angle, arrowheadconstrictionlen)
471
478
 
516
523
class text(deco, attr.attr):
517
524
    """a simple text decorator"""
518
525
 
519
 
    def __init__(self, text, textattrs=[], angle=0, textdist=0.2,
 
526
    def __init__(self, text, textattrs=[], angle=0, relangle=None, textdist=0.2,
520
527
                       relarclenpos=0.5, arclenfrombegin=None, arclenfromend=None,
521
528
                       texrunner=None):
522
529
        if arclenfrombegin is not None and arclenfromend is not None:
524
531
        self.text = text
525
532
        self.textattrs = textattrs
526
533
        self.angle = angle
 
534
        self.relangle = relangle
527
535
        self.textdist = textdist
528
536
        self.relarclenpos = relarclenpos
529
537
        self.arclenfrombegin = arclenfrombegin
538
546
 
539
547
        dp.ensurenormpath()
540
548
        if self.arclenfrombegin is not None:
541
 
            x, y = dp.path.at(dp.path.begin() + self.arclenfrombegin)
 
549
            param = dp.path.begin() + self.arclenfrombegin
542
550
        elif self.arclenfromend is not None:
543
 
            x, y = dp.path.at(dp.path.end() - self.arclenfromend)
 
551
            param = dp.path.end() - self.arclenfromend
544
552
        else:
545
553
            # relarcpos is used, when neither arcfrombegin nor arcfromend is given
546
 
            x, y = dp.path.at(self.relarclenpos * dp.path.arclen())
 
554
            param = self.relarclenpos * dp.path.arclen()
 
555
        x, y = dp.path.at(param)
547
556
 
 
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])
 
561
        else:
 
562
            angle = self.angle*math.pi/180
548
563
        t = texrunner.text(x, y, self.text, textattrs)
549
 
        t.linealign(self.textdist, math.cos(self.angle*math.pi/180), math.sin(self.angle*math.pi/180))
 
564
        t.linealign(self.textdist, math.cos(angle), math.sin(angle))
550
565
        dp.ornaments.insert(t)
551
566
 
552
567
 
575
590
                    dp.ornaments.draw(path.circle_pt(x_pt, y_pt, r_pt), [filled])
576
591
                x_pt, y_pt = normsubpathitem.atend_pt()
577
592
                dp.ornaments.draw(path.circle_pt(x_pt, y_pt, r_pt), [filled])
 
593
 
 
594
 
 
595
class linehatched(deco, attr.exclusiveattr, attr.clearclass):
 
596
    """draws a pattern with explicit lines
 
597
 
 
598
    This class acts as a drop-in replacement for postscript patterns
 
599
    from the pattern module which are not understood by some printers"""
 
600
 
 
601
    def __init__(self, dist, angle, strokestyles=[], cross=0):
 
602
        attr.clearclass.__init__(self, _filled)
 
603
        attr.exclusiveattr.__init__(self, linehatched)
 
604
        self.dist = dist
 
605
        self.angle = angle
 
606
        self.strokestyles = attr.mergeattrs([style.linewidth.THIN] + strokestyles)
 
607
        attr.checkattrs(self.strokestyles, [style.strokestyle])
 
608
        self.cross = cross
 
609
 
 
610
    def __call__(self, dist=None, angle=None, strokestyles=None, cross=None):
 
611
        if dist is None:
 
612
            dist = self.dist
 
613
        if angle is None:
 
614
            angle = self.angle
 
615
        if strokestyles is None:
 
616
            strokestyles = self.strokestyles
 
617
        if cross is None:
 
618
            cross = self.cross
 
619
        return linehatched(dist, angle, strokestyles, cross)
 
620
 
 
621
    def _decocanvas(self, angle, dp, texrunner):
 
622
        dp.ensurenormpath()
 
623
        dist_pt = unit.topt(self.dist)
 
624
 
 
625
        c = canvas.canvas([canvas.clip(dp.path)])
 
626
        llx_pt, lly_pt, urx_pt, ury_pt = dp.path.bbox().highrestuple_pt()
 
627
        center_pt = 0.5*(llx_pt+urx_pt), 0.5*(lly_pt+ury_pt)
 
628
        radius_pt = 0.5*math.hypot(urx_pt-llx_pt, ury_pt-lly_pt) + dist_pt
 
629
        n = int(2*radius_pt / dist_pt) + 1
 
630
        for i in range(n):
 
631
            x_pt = center_pt[0] - radius_pt + i*dist_pt
 
632
            c.stroke(path.line_pt(x_pt, center_pt[1]-radius_pt, x_pt, center_pt[1]+radius_pt),
 
633
                     [trafo.rotate_pt(angle, center_pt[0], center_pt[1])] + self.strokestyles)
 
634
        return c
 
635
 
 
636
    def decorate(self, dp, texrunner):
 
637
        dp.ornaments.insert(self._decocanvas(self.angle, dp, texrunner))
 
638
        if self.cross:
 
639
            dp.ornaments.insert(self._decocanvas(self.angle+90, dp, texrunner))
 
640
 
 
641
    def merge(self, attrs):
 
642
        # act as attr.clearclass and as attr.exclusiveattr at the same time
 
643
        newattrs = attr.exclusiveattr.merge(self, attrs)
 
644
        return attr.clearclass.merge(self, newattrs)
 
645
 
 
646
linehatched.clear = attr.clearclass(linehatched)
 
647
 
 
648
_hatch_base = 0.1 * unit.v_cm
 
649
 
 
650
linehatched0 = linehatched(_hatch_base, 0)
 
651
linehatched0.SMALL = linehatched0(_hatch_base/math.sqrt(64))
 
652
linehatched0.SMALL = linehatched0(_hatch_base/math.sqrt(64))
 
653
linehatched0.SMALl = linehatched0(_hatch_base/math.sqrt(32))
 
654
linehatched0.SMAll = linehatched0(_hatch_base/math.sqrt(16))
 
655
linehatched0.SMall = linehatched0(_hatch_base/math.sqrt(8))
 
656
linehatched0.Small = linehatched0(_hatch_base/math.sqrt(4))
 
657
linehatched0.small = linehatched0(_hatch_base/math.sqrt(2))
 
658
linehatched0.normal = linehatched0(_hatch_base)
 
659
linehatched0.large = linehatched0(_hatch_base*math.sqrt(2))
 
660
linehatched0.Large = linehatched0(_hatch_base*math.sqrt(4))
 
661
linehatched0.LArge = linehatched0(_hatch_base*math.sqrt(8))
 
662
linehatched0.LARge = linehatched0(_hatch_base*math.sqrt(16))
 
663
linehatched0.LARGe = linehatched0(_hatch_base*math.sqrt(32))
 
664
linehatched0.LARGE = linehatched0(_hatch_base*math.sqrt(64))
 
665
 
 
666
linehatched45 = linehatched(_hatch_base, 45)
 
667
linehatched45.SMALL = linehatched45(_hatch_base/math.sqrt(64))
 
668
linehatched45.SMALl = linehatched45(_hatch_base/math.sqrt(32))
 
669
linehatched45.SMAll = linehatched45(_hatch_base/math.sqrt(16))
 
670
linehatched45.SMall = linehatched45(_hatch_base/math.sqrt(8))
 
671
linehatched45.Small = linehatched45(_hatch_base/math.sqrt(4))
 
672
linehatched45.small = linehatched45(_hatch_base/math.sqrt(2))
 
673
linehatched45.normal = linehatched45(_hatch_base)
 
674
linehatched45.large = linehatched45(_hatch_base*math.sqrt(2))
 
675
linehatched45.Large = linehatched45(_hatch_base*math.sqrt(4))
 
676
linehatched45.LArge = linehatched45(_hatch_base*math.sqrt(8))
 
677
linehatched45.LARge = linehatched45(_hatch_base*math.sqrt(16))
 
678
linehatched45.LARGe = linehatched45(_hatch_base*math.sqrt(32))
 
679
linehatched45.LARGE = linehatched45(_hatch_base*math.sqrt(64))
 
680
 
 
681
linehatched90 = linehatched(_hatch_base, 90)
 
682
linehatched90.SMALL = linehatched90(_hatch_base/math.sqrt(64))
 
683
linehatched90.SMALl = linehatched90(_hatch_base/math.sqrt(32))
 
684
linehatched90.SMAll = linehatched90(_hatch_base/math.sqrt(16))
 
685
linehatched90.SMall = linehatched90(_hatch_base/math.sqrt(8))
 
686
linehatched90.Small = linehatched90(_hatch_base/math.sqrt(4))
 
687
linehatched90.small = linehatched90(_hatch_base/math.sqrt(2))
 
688
linehatched90.normal = linehatched90(_hatch_base)
 
689
linehatched90.large = linehatched90(_hatch_base*math.sqrt(2))
 
690
linehatched90.Large = linehatched90(_hatch_base*math.sqrt(4))
 
691
linehatched90.LArge = linehatched90(_hatch_base*math.sqrt(8))
 
692
linehatched90.LARge = linehatched90(_hatch_base*math.sqrt(16))
 
693
linehatched90.LARGe = linehatched90(_hatch_base*math.sqrt(32))
 
694
linehatched90.LARGE = linehatched90(_hatch_base*math.sqrt(64))
 
695
 
 
696
linehatched135 = linehatched(_hatch_base, 135)
 
697
linehatched135.SMALL = linehatched135(_hatch_base/math.sqrt(64))
 
698
linehatched135.SMALl = linehatched135(_hatch_base/math.sqrt(32))
 
699
linehatched135.SMAll = linehatched135(_hatch_base/math.sqrt(16))
 
700
linehatched135.SMall = linehatched135(_hatch_base/math.sqrt(8))
 
701
linehatched135.Small = linehatched135(_hatch_base/math.sqrt(4))
 
702
linehatched135.small = linehatched135(_hatch_base/math.sqrt(2))
 
703
linehatched135.normal = linehatched135(_hatch_base)
 
704
linehatched135.large = linehatched135(_hatch_base*math.sqrt(2))
 
705
linehatched135.Large = linehatched135(_hatch_base*math.sqrt(4))
 
706
linehatched135.LArge = linehatched135(_hatch_base*math.sqrt(8))
 
707
linehatched135.LARge = linehatched135(_hatch_base*math.sqrt(16))
 
708
linehatched135.LARGe = linehatched135(_hatch_base*math.sqrt(32))
 
709
linehatched135.LARGE = linehatched135(_hatch_base*math.sqrt(64))
 
710
 
 
711
crosslinehatched0 = linehatched(_hatch_base, 0, cross=1)
 
712
crosslinehatched0.SMALL = crosslinehatched0(_hatch_base/math.sqrt(64))
 
713
crosslinehatched0.SMALl = crosslinehatched0(_hatch_base/math.sqrt(32))
 
714
crosslinehatched0.SMAll = crosslinehatched0(_hatch_base/math.sqrt(16))
 
715
crosslinehatched0.SMall = crosslinehatched0(_hatch_base/math.sqrt(8))
 
716
crosslinehatched0.Small = crosslinehatched0(_hatch_base/math.sqrt(4))
 
717
crosslinehatched0.small = crosslinehatched0(_hatch_base/math.sqrt(2))
 
718
crosslinehatched0.normal = crosslinehatched0
 
719
crosslinehatched0.large = crosslinehatched0(_hatch_base*math.sqrt(2))
 
720
crosslinehatched0.Large = crosslinehatched0(_hatch_base*math.sqrt(4))
 
721
crosslinehatched0.LArge = crosslinehatched0(_hatch_base*math.sqrt(8))
 
722
crosslinehatched0.LARge = crosslinehatched0(_hatch_base*math.sqrt(16))
 
723
crosslinehatched0.LARGe = crosslinehatched0(_hatch_base*math.sqrt(32))
 
724
crosslinehatched0.LARGE = crosslinehatched0(_hatch_base*math.sqrt(64))
 
725
 
 
726
crosslinehatched45 = linehatched(_hatch_base, 45, cross=1)
 
727
crosslinehatched45.SMALL = crosslinehatched45(_hatch_base/math.sqrt(64))
 
728
crosslinehatched45.SMALl = crosslinehatched45(_hatch_base/math.sqrt(32))
 
729
crosslinehatched45.SMAll = crosslinehatched45(_hatch_base/math.sqrt(16))
 
730
crosslinehatched45.SMall = crosslinehatched45(_hatch_base/math.sqrt(8))
 
731
crosslinehatched45.Small = crosslinehatched45(_hatch_base/math.sqrt(4))
 
732
crosslinehatched45.small = crosslinehatched45(_hatch_base/math.sqrt(2))
 
733
crosslinehatched45.normal = crosslinehatched45
 
734
crosslinehatched45.large = crosslinehatched45(_hatch_base*math.sqrt(2))
 
735
crosslinehatched45.Large = crosslinehatched45(_hatch_base*math.sqrt(4))
 
736
crosslinehatched45.LArge = crosslinehatched45(_hatch_base*math.sqrt(8))
 
737
crosslinehatched45.LARge = crosslinehatched45(_hatch_base*math.sqrt(16))
 
738
crosslinehatched45.LARGe = crosslinehatched45(_hatch_base*math.sqrt(32))
 
739
crosslinehatched45.LARGE = crosslinehatched45(_hatch_base*math.sqrt(64))
 
740
 
 
741
 
 
742
class colorgradient(deco, attr.attr):
 
743
    """inserts pieces of the path in different colors"""
 
744
 
 
745
    def __init__(self, grad, attrs=[], steps=20):
 
746
        self.attrs = attrs
 
747
        self.grad = grad
 
748
        self.steps = steps
 
749
 
 
750
    def decorate(self, dp, texrunner):
 
751
        dp.ensurenormpath()
 
752
        l = dp.path.arclen()
 
753
 
 
754
        colors = [self.grad.select(n, self.steps) for n in range(self.steps)]
 
755
        colors.reverse()
 
756
        params = dp.path.arclentoparam([l*i/float(self.steps) for i in range(self.steps)])
 
757
        params.reverse()
 
758
 
 
759
        c = canvas.canvas()
 
760
        # treat the end pieces separately
 
761
        c.stroke(dp.path.split(params[1])[1], attr.mergeattrs([colors[0]] + self.attrs))
 
762
        for n in range(1,self.steps-1):
 
763
            c.stroke(dp.path.split([params[n-1],params[n+1]])[1], attr.mergeattrs([colors[n]] + self.attrs))
 
764
        c.stroke(dp.path.split(params[-2])[0], attr.mergeattrs([colors[-1]] + self.attrs))
 
765
        dp.ornaments.insert(c)
 
766
 
 
767
 
 
768
class brace(deco, attr.attr):
 
769
    r"""draws a nicely curled brace
 
770
 
 
771
    In most cases, the original line is not wanted use canvas.canvas.draw(..) for it
 
772
 
 
773
    Geometrical parameters:
 
774
 
 
775
                 inner /\ strokes
 
776
          ____________/  \__________
 
777
         /   bar            bar     \ outer
 
778
        /                            \ strokes
 
779
 
 
780
    totalheight  distance from the jaws to the middle cap
 
781
    barthickness  thickness of the main bars
 
782
    innerstrokesthickness  thickness of the two ending strokes
 
783
    outerstrokesthickness  thickness of the inner strokes at the middle cap
 
784
    innerstrokesrelheight  height of the inner/outer strokes, relative to the total height
 
785
    outerstrokesrelheight  this determines the angle of the main bars!
 
786
                           should be around 0.5
 
787
    Note: if innerstrokesrelheight + outerstrokesrelheight == 1 then the main bars
 
788
          will be aligned parallel to the connecting line between the endpoints
 
789
    outerstrokesangle  angle of the two ending strokes
 
790
    innerstrokesangle  angle between the inner strokes at the middle cap
 
791
    slantstrokesangle  extra slanting of the inner/outer strokes
 
792
    innerstrokessmoothness  smoothing parameter for the inner + outer strokes
 
793
    outerstrokessmoothness  should be around 1 (allowed: [0,infty))
 
794
    middlerelpos  position of the middle cap (0 == left, 1 == right)
 
795
    """
 
796
    # This code is experimental because it is unclear
 
797
    # how the brace fits into the concepts of PyX
 
798
    #
 
799
    # Some thoughts:
 
800
    # - a brace needs to be decoratable with text
 
801
    #   it needs stroking and filling attributes
 
802
    # - the brace is not really a box:
 
803
    #   it has two "anchor" points that are important for aligning it to other things
 
804
    #   and one "anchor" point (plus direction) for aligning other things
 
805
    # - a brace is not a deformer:
 
806
    #   it does not look at anything else than begin/endpoint of a path
 
807
    # - a brace might be a connector (which is to be dissolved into the box concept later?)
 
808
 
 
809
    def __init__(self, reverse=1, stretch=None, dist=None, fillattrs=[],
 
810
        totalheight=12*unit.x_pt,
 
811
        barthickness=0.5*unit.x_pt, innerstrokesthickness=0.25*unit.x_pt, outerstrokesthickness=0.25*unit.x_pt,
 
812
        innerstrokesrelheight=0.6, outerstrokesrelheight=0.7,
 
813
        innerstrokesangle=30, outerstrokesangle=25, slantstrokesangle=5,
 
814
        innerstrokessmoothness=2.0, outerstrokessmoothness=2.5,
 
815
        middlerelpos=0.5):
 
816
        self.fillattrs = fillattrs
 
817
        self.reverse = reverse
 
818
        self.stretch = stretch
 
819
        self.dist = dist
 
820
        self.totalheight            = totalheight
 
821
        self.barthickness           = barthickness
 
822
        self.innerstrokesthickness  = innerstrokesthickness
 
823
        self.outerstrokesthickness  = outerstrokesthickness
 
824
        self.innerstrokesrelheight  = innerstrokesrelheight
 
825
        self.outerstrokesrelheight  = outerstrokesrelheight
 
826
        self.innerstrokesangle      = innerstrokesangle
 
827
        self.outerstrokesangle      = outerstrokesangle
 
828
        self.slantstrokesangle      = slantstrokesangle
 
829
        self.innerstrokessmoothness = innerstrokessmoothness
 
830
        self.outerstrokessmoothness = outerstrokessmoothness
 
831
        self.middlerelpos           = middlerelpos
 
832
 
 
833
    def __call__(self, **kwargs):
 
834
        for name in ["reverse", "stretch", "dist", "fillattrs",
 
835
            "totalheight", "barthickness", "innerstrokesthickness", "outerstrokesthickness",
 
836
            "innerstrokesrelheight", "outerstrokesrelheight", "innerstrokesangle", "outerstrokesangle", "slantstrokesangle",
 
837
            "innerstrokessmoothness", "outerstrokessmoothness", "middlerelpos"]:
 
838
            if not kwargs.has_key(name):
 
839
                kwargs[name] = self.__dict__[name]
 
840
        return brace(**kwargs)
 
841
 
 
842
    def _halfbracepath_pt(self, length_pt, height_pt, ilength_pt, olength_pt, # <<<
 
843
    ithick_pt, othick_pt, bthick_pt, cos_iangle, sin_iangle, cos_oangle,
 
844
    sin_oangle, cos_slangle, sin_slangle):
 
845
 
 
846
        ismooth = self.innerstrokessmoothness
 
847
        osmooth = self.outerstrokessmoothness
 
848
 
 
849
        # these two parameters are not important enough to be seen outside
 
850
        inner_cap_param = 1.5
 
851
        outer_cap_param = 2.5
 
852
        outerextracurved = 0.6 # in (0, 1]
 
853
        # 1.0 will lead to F=G, the outer strokes will not be curved at their ends.
 
854
        # The smaller, the more curvature
 
855
 
 
856
        # build an orientation path (three straight lines)
 
857
        #
 
858
        #      \q1
 
859
        #    /  \
 
860
        #   /    \
 
861
        # _/      \______________________________________q5
 
862
        #         q2         q3              q4           \
 
863
        #                                                  \
 
864
        #                                                   \
 
865
        #                                                    \q6
 
866
        #
 
867
        # get the points for that:
 
868
        q1 = (0, height_pt - inner_cap_param * ithick_pt + 0.5*ithick_pt/sin_iangle)
 
869
        q2 = (q1[0] + ilength_pt * sin_iangle,
 
870
              q1[1] - ilength_pt * cos_iangle)
 
871
        q6 = (length_pt, 0)
 
872
        q5 = (q6[0] - olength_pt * sin_oangle,
 
873
              q6[1] + olength_pt * cos_oangle)
 
874
        bardir = (q5[0] - q2[0], q5[1] - q2[1])
 
875
        bardirnorm = math.hypot(*bardir)
 
876
        bardir = (bardir[0]/bardirnorm, bardir[1]/bardirnorm)
 
877
        ismoothlength_pt = ilength_pt * ismooth
 
878
        osmoothlength_pt = olength_pt * osmooth
 
879
        if bardirnorm < ismoothlength_pt + osmoothlength_pt:
 
880
            ismoothlength_pt = bardirnorm * ismoothlength_pt / (ismoothlength_pt + osmoothlength_pt)
 
881
            osmoothlength_pt = bardirnorm * osmoothlength_pt / (ismoothlength_pt + osmoothlength_pt)
 
882
        q3 = (q2[0] + ismoothlength_pt * bardir[0],
 
883
              q2[1] + ismoothlength_pt * bardir[1])
 
884
        q4 = (q5[0] - osmoothlength_pt * bardir[0],
 
885
              q5[1] - osmoothlength_pt * bardir[1])
 
886
 
 
887
        #
 
888
        #    P _O
 
889
        #   / | \A2
 
890
        #  / A1\ \
 
891
        #   /   \ B2C2________D2___________E2_______F2___G2
 
892
        #        \______________________________________  \
 
893
        #       B1,C1         D1           E1      F1  G1  \
 
894
        #                                                \  \
 
895
        #                                                 \  \H2
 
896
        #                                                H1\_/I2
 
897
        #                                                  I1
 
898
        #
 
899
        # the halfbraces meet in P and A1:
 
900
        P = (0, height_pt)
 
901
        A1 = (0, height_pt - inner_cap_param * ithick_pt)
 
902
        # A2 is A1, shifted by the inner thickness
 
903
        A2 = (A1[0] + ithick_pt * cos_iangle,
 
904
              A1[1] + ithick_pt * sin_iangle)
 
905
        s, t = deformer.intersection(P, A2, (cos_slangle, sin_slangle), (sin_iangle, -cos_iangle))
 
906
        O = (P[0] + s * cos_slangle,
 
907
             P[1] + s * sin_slangle)
 
908
 
 
909
        # from D1 to E1 is the straight part of the brace
 
910
        # also back from E2 to D1
 
911
        D1 = (q3[0] + bthick_pt * bardir[1],
 
912
              q3[1] - bthick_pt * bardir[0])
 
913
        D2 = (q3[0] - bthick_pt * bardir[1],
 
914
              q3[1] + bthick_pt * bardir[0])
 
915
        E1 = (q4[0] + bthick_pt * bardir[1],
 
916
              q4[1] - bthick_pt * bardir[0])
 
917
        E2 = (q4[0] - bthick_pt * bardir[1],
 
918
              q4[1] + bthick_pt * bardir[0])
 
919
        # I1, I2 are the control points at the outer stroke
 
920
        I1 = (q6[0] - 0.5 * othick_pt * cos_oangle,
 
921
              q6[1] - 0.5 * othick_pt * sin_oangle)
 
922
        I2 = (q6[0] + 0.5 * othick_pt * cos_oangle,
 
923
              q6[1] + 0.5 * othick_pt * sin_oangle)
 
924
        # get the control points for the curved parts of the brace
 
925
        s, t = deformer.intersection(A1, D1, (sin_iangle, -cos_iangle), bardir)
 
926
        B1 = (D1[0] + t * bardir[0],
 
927
              D1[1] + t * bardir[1])
 
928
        s, t = deformer.intersection(A2, D2, (sin_iangle, -cos_iangle), bardir)
 
929
        B2 = (D2[0] + t * bardir[0],
 
930
              D2[1] + t * bardir[1])
 
931
        s, t = deformer.intersection(E1, I1, bardir, (-sin_oangle, cos_oangle))
 
932
        G1 = (E1[0] + s * bardir[0],
 
933
              E1[1] + s * bardir[1])
 
934
        s, t = deformer.intersection(E2, I2, bardir, (-sin_oangle, cos_oangle))
 
935
        G2 = (E2[0] + s * bardir[0],
 
936
              E2[1] + s * bardir[1])
 
937
        # at the inner strokes: use curvature zero at both ends
 
938
        C1 = B1
 
939
        C2 = B2
 
940
        # at the outer strokes: use curvature zero only at the connection to
 
941
        # the straight part
 
942
        F1 = (outerextracurved * G1[0] + (1 - outerextracurved) * E1[0],
 
943
              outerextracurved * G1[1] + (1 - outerextracurved) * E1[1])
 
944
        F2 = (outerextracurved * G2[0] + (1 - outerextracurved) * E2[0],
 
945
              outerextracurved * G2[1] + (1 - outerextracurved) * E2[1])
 
946
        # the tip of the outer stroke, endpoints of the bezier curve
 
947
        H1 = (I1[0] - outer_cap_param * othick_pt * sin_oangle,
 
948
              I1[1] + outer_cap_param * othick_pt * cos_oangle)
 
949
        H2 = (I2[0] - outer_cap_param * othick_pt * sin_oangle,
 
950
              I2[1] + outer_cap_param * othick_pt * cos_oangle)
 
951
 
 
952
        #for qq in [A1,B1,C1,D1,E1,F1,G1,H1,I1,
 
953
        #           A2,B2,C2,D2,E2,F2,G2,H2,I2,
 
954
        #           O,P
 
955
        #           ]:
 
956
        #    cc.fill(path.circle(qq[0], qq[1], 0.5), [color.rgb.green])
 
957
 
 
958
        # now build the right halfbrace
 
959
        bracepath = path.path(path.moveto_pt(*A1))
 
960
        bracepath.append(path.curveto_pt(B1[0], B1[1], C1[0], C1[1], D1[0], D1[1]))
 
961
        bracepath.append(path.lineto_pt(E1[0], E1[1]))
 
962
        bracepath.append(path.curveto_pt(F1[0], F1[1], G1[0], G1[1], H1[0], H1[1]))
 
963
        # the tip of the right halfbrace
 
964
        bracepath.append(path.curveto_pt(I1[0], I1[1], I2[0], I2[1], H2[0], H2[1]))
 
965
        # the rest of the right halfbrace
 
966
        bracepath.append(path.curveto_pt(G2[0], G2[1], F2[0], F2[1], E2[0], E2[1]))
 
967
        bracepath.append(path.lineto_pt(D2[0], D2[1]))
 
968
        bracepath.append(path.curveto_pt(C2[0], C2[1], B2[0], B2[1], A2[0], A2[1]))
 
969
        # the tip in the middle of the brace
 
970
        bracepath.append(path.curveto_pt(O[0], O[1], O[0], O[1], P[0], P[1]))
 
971
 
 
972
        return bracepath
 
973
    # >>>
 
974
 
 
975
    def _bracepath(self, x0_pt, y0_pt, x1_pt, y1_pt): # <<<
 
976
        height_pt = unit.topt(self.totalheight)
 
977
        totallength_pt = math.hypot(x1_pt - x0_pt, y1_pt - y0_pt)
 
978
        leftlength_pt = self.middlerelpos * totallength_pt
 
979
        rightlength_pt = totallength_pt - leftlength_pt
 
980
        ithick_pt = unit.topt(self.innerstrokesthickness)
 
981
        othick_pt = unit.topt(self.outerstrokesthickness)
 
982
        bthick_pt = unit.topt(self.barthickness)
 
983
 
 
984
        # create the left halfbrace with positive slanting
 
985
        # because we will mirror this part
 
986
        cos_iangle = math.cos(math.radians(0.5*self.innerstrokesangle - self.slantstrokesangle))
 
987
        sin_iangle = math.sin(math.radians(0.5*self.innerstrokesangle - self.slantstrokesangle))
 
988
        cos_oangle = math.cos(math.radians(self.outerstrokesangle - self.slantstrokesangle))
 
989
        sin_oangle = math.sin(math.radians(self.outerstrokesangle - self.slantstrokesangle))
 
990
        cos_slangle = math.cos(math.radians(-self.slantstrokesangle))
 
991
        sin_slangle = math.sin(math.radians(-self.slantstrokesangle))
 
992
        ilength_pt = self.innerstrokesrelheight * height_pt / cos_iangle
 
993
        olength_pt = self.outerstrokesrelheight * height_pt / cos_oangle
 
994
 
 
995
        bracepath = self._halfbracepath_pt(leftlength_pt, height_pt,
 
996
          ilength_pt, olength_pt, ithick_pt, othick_pt, bthick_pt, cos_iangle,
 
997
          sin_iangle, cos_oangle, sin_oangle, cos_slangle,
 
998
          sin_slangle).reversed().transformed(trafo.mirror(90))
 
999
 
 
1000
        # create the right halfbrace with negative slanting
 
1001
        cos_iangle = math.cos(math.radians(0.5*self.innerstrokesangle + self.slantstrokesangle))
 
1002
        sin_iangle = math.sin(math.radians(0.5*self.innerstrokesangle + self.slantstrokesangle))
 
1003
        cos_oangle = math.cos(math.radians(self.outerstrokesangle + self.slantstrokesangle))
 
1004
        sin_oangle = math.sin(math.radians(self.outerstrokesangle + self.slantstrokesangle))
 
1005
        cos_slangle = math.cos(math.radians(-self.slantstrokesangle))
 
1006
        sin_slangle = math.sin(math.radians(-self.slantstrokesangle))
 
1007
        ilength_pt = self.innerstrokesrelheight * height_pt / cos_iangle
 
1008
        olength_pt = self.outerstrokesrelheight * height_pt / cos_oangle
 
1009
 
 
1010
        bracepath = bracepath << self._halfbracepath_pt(rightlength_pt, height_pt,
 
1011
        ilength_pt, olength_pt, ithick_pt, othick_pt, bthick_pt, cos_iangle,
 
1012
        sin_iangle, cos_oangle, sin_oangle, cos_slangle,
 
1013
        sin_slangle)
 
1014
 
 
1015
        return bracepath.transformed(
 
1016
          # two trafos for matching the given endpoints
 
1017
          trafo.translate_pt(x0_pt, y0_pt) *
 
1018
          trafo.rotate_pt(math.degrees(math.atan2(y1_pt-y0_pt, x1_pt-x0_pt))) *
 
1019
          # one trafo to move the brace's left outer stroke to zero
 
1020
          trafo.translate_pt(leftlength_pt, 0))
 
1021
    # >>>
 
1022
 
 
1023
    def decorate(self, dp, texrunner):
 
1024
        dp.ensurenormpath()
 
1025
        x0_pt, y0_pt = dp.path.atbegin_pt()
 
1026
        x1_pt, y1_pt = dp.path.atend_pt()
 
1027
        if self.reverse:
 
1028
            x0_pt, y0_pt, x1_pt, y1_pt = x1_pt, y1_pt, x0_pt, y0_pt
 
1029
        if self.stretch is not None:
 
1030
            xm, ym = 0.5*(x0_pt+x1_pt), 0.5*(y0_pt+y1_pt)
 
1031
            x0_pt, y0_pt = xm + self.stretch*(x0_pt-xm), ym + self.stretch*(y0_pt-ym)
 
1032
            x1_pt, y1_pt = xm + self.stretch*(x1_pt-xm), ym + self.stretch*(y1_pt-ym)
 
1033
        if self.dist is not None:
 
1034
            d = unit.topt(self.dist)
 
1035
            dx, dy = dp.path.rotation_pt(dp.path.begin()).apply_pt(0, 1)
 
1036
            x0_pt += d*dx; y0_pt += d*dy
 
1037
            dx, dy = dp.path.rotation_pt(dp.path.end()).apply_pt(0, 1)
 
1038
            x1_pt += d*dx; y1_pt += d*dy
 
1039
        dp.ornaments.fill(self._bracepath(x0_pt, y0_pt, x1_pt, y1_pt), self.fillattrs)
 
1040
 
 
1041
brace.clear = attr.clearclass(brace)
 
1042
 
 
1043
leftbrace  = brace(reverse=0, middlerelpos=0.55, innerstrokesrelheight=0.6, outerstrokesrelheight=0.7, slantstrokesangle=-10)
 
1044
rightbrace = brace(reverse=1, middlerelpos=0.45, innerstrokesrelheight=0.6, outerstrokesrelheight=0.7, slantstrokesangle=10)
 
1045
belowbrace = brace(reverse=1, middlerelpos=0.55, innerstrokesrelheight=0.7, outerstrokesrelheight=0.9, slantstrokesangle=-10)
 
1046
abovebrace = brace(reverse=0, middlerelpos=0.45, innerstrokesrelheight=0.7, outerstrokesrelheight=0.9, slantstrokesangle=-10)
 
1047
straightbrace = brace(innerstrokesrelheight=0.5, outerstrokesrelheight=0.5,
 
1048
        innerstrokesangle=30, outerstrokesangle=30, slantstrokesangle=0,
 
1049
        innerstrokessmoothness=1.0, outerstrokessmoothness=1.0)
 
1050