| 1 | from fontTools.pens.basePen import AbstractPen, BasePen
|
|---|
| 2 | from robofab.misc.bezierTools import splitLine, splitCubic
|
|---|
| 3 |
|
|---|
| 4 |
|
|---|
| 5 | from sets import Set
|
|---|
| 6 |
|
|---|
| 7 | class 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 |
|
|---|
| 143 | if __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 |
|
|---|