basePen.py revision 82ef2a52c9ab8a88f10daa9dc1050d1cf3901b57
1b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr"""fontTools.pens.basePen.py -- Tools and base classes to build pen objects. 2b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 3b369ef33fcbfd39bd5aaf2b079cef1c689095783jvrThe Pen Protocol 4b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 5b369ef33fcbfd39bd5aaf2b079cef1c689095783jvrA Pen is a kind of object that standardizes the way how to "draw" outlines: 6b369ef33fcbfd39bd5aaf2b079cef1c689095783jvrit is a middle man between an outline and a drawing. In other words: 7b369ef33fcbfd39bd5aaf2b079cef1c689095783jvrit is an abstraction for drawing outlines, making sure that outline objects 8b369ef33fcbfd39bd5aaf2b079cef1c689095783jvrdon't need to know the details about how and where they're being drawn, and 9b369ef33fcbfd39bd5aaf2b079cef1c689095783jvrthat drawings don't need to know the details of how outlines are stored. 10b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 11b369ef33fcbfd39bd5aaf2b079cef1c689095783jvrThe most basic pattern is this: 12b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 13b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr outline.draw(pen) # 'outline' draws itself onto 'pen' 14b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 15b369ef33fcbfd39bd5aaf2b079cef1c689095783jvrPens can be used to render outlines to the screen, but also to construct 16b369ef33fcbfd39bd5aaf2b079cef1c689095783jvrnew outlines. Eg. an outline object can be both a drawable object (it has a 17b369ef33fcbfd39bd5aaf2b079cef1c689095783jvrdraw() method) as well as a pen itself: you *build* an outline using pen 18b369ef33fcbfd39bd5aaf2b079cef1c689095783jvrmethods. 19b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 20b369ef33fcbfd39bd5aaf2b079cef1c689095783jvrThe AbstractPen class defines the Pen protocol. 21b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 22b369ef33fcbfd39bd5aaf2b079cef1c689095783jvrThe BasePen class is a base implementation useful for drawing pens. See the 23b369ef33fcbfd39bd5aaf2b079cef1c689095783jvrcomments in that class for which methods you need to override. 24b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr""" 25b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 26b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr__all__ = ["AbstractPen", "BasePen"] 27b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 28b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 29b369ef33fcbfd39bd5aaf2b079cef1c689095783jvrclass AbstractPen: 30b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 31b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def moveTo(self, pt): 32b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr """Begin a new sub path, set the current point to 'pt'.""" 33b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr raise NotImplementedError 34b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 35b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def lineTo(self, pt): 36b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr """Draw a straight line.""" 37b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr raise NotImplementedError 38b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 39b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def curveTo(self, *points): 40b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr """Draw a curve with an *arbitrary* number of control points. 41b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 42b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr Let n be the number of control points (which is the number of 43b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr arguments to this call minus 1). If n==2, a plain vanilla cubic 44b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr bezier is drawn. If n==1, we fall back to a quadratic segment and 45b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr if n==0 we draw a straight line. It gets interesting when n>2: 46b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr n-1 PostScript-style cubic segments will be drawn as if it were 47b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr one curve. 48b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 49b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr The conversion algorithm used for n>2 is inspired by NURB 50b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr splines, and is conceptually equivalent to the TrueType "implied 51b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr points" principle. See also qCurve(). 52b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr """ 53b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr raise NotImplementedError 54b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 55b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def qCurveTo(self, *points): 56b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr """Draw a whole string of quadratic curve segments. 57b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 58b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr This implements TrueType-style curves, breaking up curves using 59b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr implied points: between each two consequtive off-curve points, 60b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr there is one 'implied' point exactly in the middle between them. 61b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 62b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 'points' is a sequence of at least two points. Just like with 63b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr any segment drawing function, the first and the last point are 64b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr treated as onCurve, the rest as offCurve. 65b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr """ 66b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr raise NotImplementedError 67b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 68b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def closePath(self): 69b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr """Close the current sub path.""" 70b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr pass 71b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 72b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def addComponent(self, glyphName, transformation): 73b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr """Add a sub glyph.""" 74b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr raise NotImplementedError 75b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 76b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 77b369ef33fcbfd39bd5aaf2b079cef1c689095783jvrclass BasePen(AbstractPen): 78b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 79b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr """Base class for drawing pens.""" 80b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 81b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def __init__(self, glyphSet): 82b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self.glyphSet = glyphSet 83b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self.__currentPoint = None 84b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 85b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr # must override 86b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 87b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def _moveTo(self, pt): 88b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr raise NotImplementedError 89b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 90b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def _lineTo(self, pt): 91b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr raise NotImplementedError 92b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 93b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def _curveToOne(self, pt1, pt2, pt3): 94b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr raise NotImplementedError 95b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 96b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr # may override 97b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 98b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def _closePath(self): 99b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr pass 100b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 101b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def _qCurveToOne(self, pt1, pt2): 102b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr """This method implements the basic quadratic curve type. The 103b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr default implementation delegates the work to the cubic curve 104b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr function. Optionally override with a native implementation. 105b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr """ 106b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr pt0x, pt0y = self.__currentPoint 107b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr pt1x, pt1y = pt1 108b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr pt2x, pt2y = pt2 109b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr mid1x = pt0x + 0.66666666666666667 * (pt1x - pt0x) 110b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr mid1y = pt0y + 0.66666666666666667 * (pt1y - pt0y) 111b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr mid2x = pt2x + 0.66666666666666667 * (pt1x - pt2x) 112b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr mid2y = pt2y + 0.66666666666666667 * (pt1y - pt2y) 113b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self._curveToOne((mid1x, mid1y), (mid2x, mid2y), pt2) 114b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 115b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def addComponent(self, glyphName, transformation): 116b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr """This default implementation simply transforms the points 117b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr of the base glyph and draws it onto self. 118b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr """ 119b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr from fontTools.pens.transformPen import TransformPen 120b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr tPen = TransformPen(self, transformation) 121b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self.glyphSet[glyphName].draw(tPen) 122b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 123b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr # don't override 124b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 125b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def _getCurrentPoint(self): 126b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr """Return the current point. This is not part of the public 127b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr interface, yet is useful for subclasses. 128b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr """ 129b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr return self.__currentPoint 130b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 131b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def closePath(self): 132b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self._closePath() 133b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self.__currentPoint = None 134b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 135b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def moveTo(self, pt): 136b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self._moveTo(pt) 137b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self.__currentPoint = pt 138b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 139b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def lineTo(self, pt): 140b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self._lineTo(pt) 141b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self.__currentPoint = pt 142b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 143b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def curveTo(self, *points): 144b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr n = len(points) - 1 # 'n' is the number of control points 145b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr assert n >= 0 146b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr if n == 2: 147b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr # The common case, we have exactly two BCP's, so this is a standard 148b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr # cubic bezier. 149b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self._curveToOne(*points) 150b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self.__currentPoint = points[-1] 151b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr elif n > 2: 152b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr # n is the number of control points; split curve into n-1 cubic 153b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr # bezier segments. The algorithm used here is inspired by NURB 154b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr # splines and the TrueType "implied point" principle, and ensures 155b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr # the smoothest possible connection between two curve segments, 156b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr # with no disruption in the curvature. It is practical since it 157b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr # allows one to construct multiple bezier segments with a much 158b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr # smaller amount of points. 159b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr pt1, pt2, pt3 = points[0], None, None 160b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr for i in range(2, n+1): 161b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr # calculate points in between control points. 162b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr nDivisions = min(i, 3, n-i+2) 163b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr d = float(nDivisions) 164b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr for j in range(1, nDivisions): 165b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr factor = j / d 166b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr temp1 = points[i-1] 167b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr temp2 = points[i-2] 168b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr temp = (temp2[0] + factor * (temp1[0] - temp2[0]), 169b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr temp2[1] + factor * (temp1[1] - temp2[1])) 170b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr if pt2 is None: 171b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr pt2 = temp 172b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr else: 17382ef2a52c9ab8a88f10daa9dc1050d1cf3901b57jvr pt3 = (0.5 * (pt2[0] + temp[0]), 17482ef2a52c9ab8a88f10daa9dc1050d1cf3901b57jvr 0.5 * (pt2[1] + temp[1])) 175b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self._curveToOne(pt1, pt2, pt3) 176b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr pt1, pt2, pt3 = temp, None, None 177b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self._curveToOne(pt1, points[-2], points[-1]) 178b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self.__currentPoint = points[-1] 179b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr elif n == 1: 180b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self._qCurveOne(*points) 181b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr elif n == 0: 182b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self.lineTo(points[0]) 183b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr else: 184b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr raise AssertionError, "can't get there from here" 185b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 186b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def qCurveTo(self, *points): 187b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr n = len(points) - 1 # 'n' is the number of control points 188b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr assert n >= 0 189b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr if n > 0: 19082ef2a52c9ab8a88f10daa9dc1050d1cf3901b57jvr # Split the string of points into discrete quadratic curve 19182ef2a52c9ab8a88f10daa9dc1050d1cf3901b57jvr # segments. Between any two consecutive off-curve points 19282ef2a52c9ab8a88f10daa9dc1050d1cf3901b57jvr # there's an implied on-curve point exactly in the middle. 19382ef2a52c9ab8a88f10daa9dc1050d1cf3901b57jvr # This is where the segment splits. 194b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr _qCurveToOne = self._qCurveToOne 195b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr for i in range(len(points) - 2): 196b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr x, y = points[i] 197b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr nx, ny = points[i+1] 198b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr impliedPt = (0.5 * (x + nx), 0.5 * (y + ny)) 199b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr _qCurveToOne(points[i], impliedPt) 200b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self.__currentPoint = impliedPt 201b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr _qCurveToOne(points[-2], points[-1]) 202b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self.__currentPoint = points[-1] 203b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr else: 204b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self.lineTo(points[0]) 205b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 206b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 207b369ef33fcbfd39bd5aaf2b079cef1c689095783jvrclass _TestPen(BasePen): 208b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def _moveTo(self, pt): 209b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr print "%s %s moveto" % (pt[0], pt[1]) 210b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def _lineTo(self, pt): 211b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr print "%s %s lineto" % (pt[0], pt[1]) 212b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def _curveToOne(self, bcp1, bcp2, pt): 21382ef2a52c9ab8a88f10daa9dc1050d1cf3901b57jvr print "%s %s %s %s %s %s curveto" % (bcp1[0], bcp1[1], 21482ef2a52c9ab8a88f10daa9dc1050d1cf3901b57jvr bcp2[0], bcp2[1], pt[0], pt[1]) 215b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def _closePath(self): 216b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr print "closepath" 217b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 218b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 219b369ef33fcbfd39bd5aaf2b079cef1c689095783jvrif __name__ == "__main__": 220b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr pen = _TestPen(None) 221b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr pen.moveTo((0, 0)) 222b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr pen.lineTo((0, 100)) 223b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr pen.qCurveTo((50, 75), (60, 50), (50, 25), (0, 0)) 224b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr pen.closePath() 225b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 226b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr pen = _TestPen(None) 227b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr pen.moveTo((0, 0)) 228b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr pen.lineTo((0, 100)) 229b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr pen.curveTo((50, 75), (60, 50), (50, 25), (0, 0)) 230b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr pen.closePath() 231