source: trunk/Lib/robofab/pens/marginPen.py @ 599

Revision 593, 4.7 KB checked in by erik, 2 years ago (diff)

Incorporate a properly transformed component in the margin calculation.

Line 
1from fontTools.pens.basePen import AbstractPen, BasePen
2from robofab.misc.bezierTools import splitLine, splitCubic
3
4
5from sets import Set
6
7class MarginPen(BasePen):
8
9        """
10                Pen to calculate the margins at a given value.
11                        When isHorizontal is True, the margins at <value> are horizontal.
12                        When isHorizontal is False, the margins at <value> are vertical.
13               
14                When a glyphset or font is given, MarginPen will also calculate for glyphs with components.
15
16                pen.getMargins() returns the minimum and maximum intersections of the glyph.
17                pen.getContourMargins() returns the minimum and maximum intersections for each contour.
18               
19               
20                Possible optimisation:
21                Initialise the pen object with a list of points we want to measure,
22                then draw the glyph once, but do the splitLine() math for all measure points.
23               
24        """
25
26        def __init__(self, glyphSet, value, isHorizontal=True):
27                BasePen.__init__(self, glyphSet)
28                self.value = value
29                self.hits = {}
30                self.filterDoubles = True
31                self.contourIndex = None
32                self.startPt = None
33                self.isHorizontal = isHorizontal
34               
35        def _moveTo(self, pt):
36                self.currentPt = pt
37                self.startPt = pt
38                if self.contourIndex is None:
39                        self.contourIndex = 0
40                else:
41                        self.contourIndex += 1
42
43        def _lineTo(self, pt):
44                if self.filterDoubles:
45                        if pt == self.currentPt:
46                                return
47                hits = splitLine(self.currentPt, pt, self.value, self.isHorizontal)
48                if len(hits)>1:
49                        # result will be 2 tuples of 2 coordinates
50                        # first two points: start to intersect
51                        # second two points: intersect to end
52                        # so, second point in first tuple is the intersect
53                        # then, the first coordinate of that point is the x.
54                        if not self.contourIndex in self.hits:
55                                self.hits[self.contourIndex] = []
56                        if self.isHorizontal:
57                                self.hits[self.contourIndex].append(round(hits[0][-1][0], 4))
58                        else:
59                                self.hits[self.contourIndex].append(round(hits[0][-1][1], 4))
60                if self.isHorizontal and pt[1] == self.value:
61                        # it could happen
62                        if not self.contourIndex in self.hits:
63                                self.hits[self.contourIndex] =  []
64                        self.hits[self.contourIndex].append(pt[0])
65                elif (not self.isHorizontal) and (pt[0] == self.value):
66                        # it could happen
67                        if not self.contourIndex in self.hits:
68                                self.hits[self.contourIndex] =  []
69                        self.hits[self.contourIndex].append(pt[1])
70                self.currentPt = pt
71
72        def _curveToOne(self, pt1, pt2, pt3):
73                hits = splitCubic(self.currentPt, pt1, pt2, pt3, self.value, self.isHorizontal)
74                for i in range(len(hits)-1):
75                        # a number of intersections is possible. Just take the
76                        # last point of each segment.
77                        if not self.contourIndex in self.hits:
78                                self.hits[self.contourIndex] = []
79                        if self.isHorizontal:
80                                self.hits[self.contourIndex].append(round(hits[i][-1][0], 4))
81                        else:
82                                self.hits[self.contourIndex].append(round(hits[i][-1][1], 4))
83                if self.isHorizontal and pt3[1] == self.value:
84                        # it could happen
85                        if not self.contourIndex in self.hits:
86                                self.hits[self.contourIndex] = []
87                        self.hits[self.contourIndex].append(pt3[0])
88                if (not self.isHorizontal) and (pt3[0] == self.value):
89                        # it could happen
90                        if not self.contourIndex in self.hits:
91                                self.hits[self.contourIndex] = []
92                        self.hits[self.contourIndex].append(pt3[1])
93                self.currentPt = pt3
94               
95        def _closePath(self):
96                if self.currentPt != self.startPt:
97                        self._lineTo(self.startPt)
98                self.currentPt = self.startPt = None
99       
100        def _endPath(self):
101                self.currentPt = None
102
103        def addComponent(self, baseGlyph, transformation):
104                from fontTools.pens.transformPen import TransformPen
105                if self.glyphSet is None:
106                        return
107                if baseGlyph in self.glyphSet:
108                        glyph = self.glyphSet[baseGlyph]
109                        if glyph is None:
110                                return
111                        tPen = TransformPen(self, transformation)
112                        glyph.draw(tPen)
113
114        def getMargins(self):
115                """Get the horizontal margins for all contours combined, i.e. the whole glyph."""
116                allHits = []
117                for index, pts in self.hits.items():
118                        allHits.extend(pts)
119                if allHits:
120                        return min(allHits), max(allHits)
121                return None
122               
123        def getContourMargins(self):
124                """Get the horizontal margins for each contour."""
125                allHits = {}
126                for index, pts in self.hits.items():
127                        unique = list(Set(pts))
128                        unique.sort()
129                        allHits[index] = unique
130                return allHits
131               
132        def getAll(self):
133                """Get all the slices."""
134                allHits = []
135                for index, pts in self.hits.items():
136                        allHits.extend(pts)
137                unique = list(Set(allHits))
138                unique = list(unique)
139                unique.sort()
140                return unique
141               
142               
143if __name__ == "__main__":
144
145        from robofab.world import CurrentGlyph, CurrentFont
146        f = CurrentFont()
147        g = CurrentGlyph()
148
149        pt = (74, 216)
150
151        pen = MarginPen(f, pt[1], isHorizontal=False)
152        g.draw(pen)
153        print 'glyph Y margins', pen.getMargins()
154        print pen.getContourMargins()
155
Note: See TracBrowser for help on using the repository browser.