basePen.py revision 23cb20053223695a2fb1dd68920297ed8471d77f
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 2040cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvrThe AbstractPen class defines the Pen protocol. It implements almost 2140cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvrnothing (only no-op closePath() and endPath() methods), but is useful 2240cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvrfor documentation purposes. Subclassing it basically tells the reader: 2340cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr"this class implements the Pen protocol.". An examples of an AbstractPen 2440cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvrsubclass is fontTools.pens.transformPen.TransformPen. 2540cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr 2640cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvrThe BasePen class is a base implementation useful for pens that actually 2740cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvrdraw (for example a pen renders outlines using a native graphics engine). 2840cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvrBasePen contains a lot of base functionality, making it very easy to build 2940cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvra pen that fully conforms to the pen protocol. Note that if you subclass 3040cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvrBasePen, you _don't_ override moveTo(), lineTo(), etc., but _moveTo(), 3140cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr_lineTo(), etc. See the BasePen doc string for details. Examples of 3240cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvrBasePen subclasses are fontTools.pens.boundsPen.BoundsPen and 3340cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvrfontTools.pens.cocoaPen.CocoaPen. 3440cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr 3540cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvrCoordinates are usually expressed as (x, y) tuples, but generally any 3640cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvrsequence of length 2 will do. 37b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr""" 38b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 3940cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr 4023cb20053223695a2fb1dd68920297ed8471d77fjvr__all__ = ["AbstractPen", "BasePen", 4123cb20053223695a2fb1dd68920297ed8471d77fjvr "decomposeSuperBezierSegment", "decomposeQuadraticSegment"] 42b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 43b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 44b369ef33fcbfd39bd5aaf2b079cef1c689095783jvrclass AbstractPen: 45b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 46b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def moveTo(self, pt): 4740cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr """Begin a new sub path, set the current point to 'pt'. You must 4840cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr end each sub path with a call to pen.closePath() or pen.endPath(). 4940cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr """ 50b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr raise NotImplementedError 51b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 52b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def lineTo(self, pt): 5340cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr """Draw a straight line from the current point to 'pt'.""" 54b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr raise NotImplementedError 55b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 56b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def curveTo(self, *points): 57934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr """Draw a cubic bezier with an arbitrary number of control points. 58934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr 59934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr The last point specified is on-curve, all others are off-curve 60934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr (control) points. If the number of control points is > 2, the 61934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr segment is split into multiple bezier segments. This works 62934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr like this: 63b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 64b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr Let n be the number of control points (which is the number of 65b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr arguments to this call minus 1). If n==2, a plain vanilla cubic 66b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr bezier is drawn. If n==1, we fall back to a quadratic segment and 67b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr if n==0 we draw a straight line. It gets interesting when n>2: 68b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr n-1 PostScript-style cubic segments will be drawn as if it were 6923cb20053223695a2fb1dd68920297ed8471d77fjvr one curve. See decomposeSuperBezierSegment(). 70b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 71b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr The conversion algorithm used for n>2 is inspired by NURB 72b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr splines, and is conceptually equivalent to the TrueType "implied 7323cb20053223695a2fb1dd68920297ed8471d77fjvr points" principle. See also decomposeQuadraticSegment(). 74b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr """ 75b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr raise NotImplementedError 76b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 77b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def qCurveTo(self, *points): 78b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr """Draw a whole string of quadratic curve segments. 79b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 80934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr The last point specified is on-curve, all others are off-curve 81934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr points. 82934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr 83934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr This method implements TrueType-style curves, breaking up curves 84934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr using 'implied points': between each two consequtive off-curve points, 8523cb20053223695a2fb1dd68920297ed8471d77fjvr there is one implied point exactly in the middle between them. See 8623cb20053223695a2fb1dd68920297ed8471d77fjvr also decomposeQuadraticSegment(). 87b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 88934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr The last argument (normally the on-curve point) may be None. 89934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr This is to support contours that have NO on-curve points (a rarely 90934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr seen feature of TrueType outlines). 91b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr """ 92b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr raise NotImplementedError 93b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 94b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def closePath(self): 9540cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr """Close the current sub path. You must call either pen.closePath() 9640cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr or pen.endPath() after each sub path. 9740cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr """ 9840cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr pass 9940cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr 10040cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr def endPath(self): 10140cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr """End the current sub path, but don't close it. You must call 10240cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr either pen.closePath() or pen.endPath() after each sub path. 10340cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr """ 104b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr pass 105b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 106b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def addComponent(self, glyphName, transformation): 107934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr """Add a sub glyph. The 'transformation' argument must be a 6-tuple 108934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr containing an affine transformation, or a Transform object from the 109934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr fontTools.misc.transform module. More precisely: it should be a 110934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr sequence containing 6 numbers. 111934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr """ 112b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr raise NotImplementedError 113b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 114b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 115b369ef33fcbfd39bd5aaf2b079cef1c689095783jvrclass BasePen(AbstractPen): 116b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 11740cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr """Base class for drawing pens. You must override _moveTo, _lineTo and 11840cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr _curveToOne. You may additionally override _closePath, _endPath, 11940cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr addComponent and/or _qCurveToOne. You should not override any other 12040cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr methods. 12140cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr """ 122b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 123b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def __init__(self, glyphSet): 124b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self.glyphSet = glyphSet 125b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self.__currentPoint = None 126b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 127b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr # must override 128b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 129b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def _moveTo(self, pt): 130b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr raise NotImplementedError 131b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 132b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def _lineTo(self, pt): 133b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr raise NotImplementedError 134b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 135b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def _curveToOne(self, pt1, pt2, pt3): 136b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr raise NotImplementedError 137b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 138b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr # may override 139b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 140b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def _closePath(self): 141b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr pass 142b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 14340cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr def _endPath(self): 14440cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr pass 14540cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr 146b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def _qCurveToOne(self, pt1, pt2): 147b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr """This method implements the basic quadratic curve type. The 148b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr default implementation delegates the work to the cubic curve 149b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr function. Optionally override with a native implementation. 150b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr """ 151b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr pt0x, pt0y = self.__currentPoint 152b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr pt1x, pt1y = pt1 153b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr pt2x, pt2y = pt2 154b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr mid1x = pt0x + 0.66666666666666667 * (pt1x - pt0x) 155b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr mid1y = pt0y + 0.66666666666666667 * (pt1y - pt0y) 156b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr mid2x = pt2x + 0.66666666666666667 * (pt1x - pt2x) 157b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr mid2y = pt2y + 0.66666666666666667 * (pt1y - pt2y) 158b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self._curveToOne((mid1x, mid1y), (mid2x, mid2y), pt2) 159b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 160b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def addComponent(self, glyphName, transformation): 161b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr """This default implementation simply transforms the points 162b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr of the base glyph and draws it onto self. 163b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr """ 164b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr from fontTools.pens.transformPen import TransformPen 165b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr tPen = TransformPen(self, transformation) 166b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self.glyphSet[glyphName].draw(tPen) 167b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 168b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr # don't override 169b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 170b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def _getCurrentPoint(self): 171b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr """Return the current point. This is not part of the public 172b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr interface, yet is useful for subclasses. 173b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr """ 174b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr return self.__currentPoint 175b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 176b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def closePath(self): 177b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self._closePath() 178b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self.__currentPoint = None 179b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 18040cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr def endPath(self): 18140cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr self._endPath() 18240cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr self.__currentPoint = None 18340cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr 184b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def moveTo(self, pt): 185b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self._moveTo(pt) 186b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self.__currentPoint = pt 187b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 188b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def lineTo(self, pt): 189b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self._lineTo(pt) 190b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self.__currentPoint = pt 191b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 192b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def curveTo(self, *points): 193b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr n = len(points) - 1 # 'n' is the number of control points 194b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr assert n >= 0 195b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr if n == 2: 196b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr # The common case, we have exactly two BCP's, so this is a standard 19723cb20053223695a2fb1dd68920297ed8471d77fjvr # cubic bezier. Even though decomposeSuperBezierSegment() handles 19823cb20053223695a2fb1dd68920297ed8471d77fjvr # this case just fine, we special-case it anyway since it's so 19923cb20053223695a2fb1dd68920297ed8471d77fjvr # common. 200b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self._curveToOne(*points) 201b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self.__currentPoint = points[-1] 202b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr elif n > 2: 203b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr # n is the number of control points; split curve into n-1 cubic 204b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr # bezier segments. The algorithm used here is inspired by NURB 205b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr # splines and the TrueType "implied point" principle, and ensures 206b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr # the smoothest possible connection between two curve segments, 207b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr # with no disruption in the curvature. It is practical since it 208b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr # allows one to construct multiple bezier segments with a much 209b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr # smaller amount of points. 21023cb20053223695a2fb1dd68920297ed8471d77fjvr _curveToOne = self._curveToOne 21123cb20053223695a2fb1dd68920297ed8471d77fjvr for pt1, pt2, pt3 in decomposeSuperBezierSegment(points): 21223cb20053223695a2fb1dd68920297ed8471d77fjvr _curveToOne(pt1, pt2, pt3) 21323cb20053223695a2fb1dd68920297ed8471d77fjvr self.__currentPoint = pt3 214b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr elif n == 1: 21549b5521f2edcbf91651cae78ef3233bf16bb48d2jvr self.qCurveTo(*points) 216b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr elif n == 0: 217b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self.lineTo(points[0]) 218b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr else: 219b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr raise AssertionError, "can't get there from here" 220b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 221b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def qCurveTo(self, *points): 222b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr n = len(points) - 1 # 'n' is the number of control points 223b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr assert n >= 0 224934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr if points[-1] is None: 225934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr # Special case for TrueType quadratics: it is possible to 226934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr # define a contour with NO on-curve points. BasePen supports 227934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr # this by allowing the final argument (the expected on-curve 228934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr # point) to be None. We simulate the feature by making the implied 229934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr # on-curve point between the last and the first off-curve points 230934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr # explicit. 231934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr x, y = points[-2] # last off-curve point 232934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr nx, ny = points[0] # first off-curve point 233934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr impliedStartPoint = (0.5 * (x + nx), 0.5 * (y + ny)) 234934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr self.__currentPoint = impliedStartPoint 235934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr self._moveTo(impliedStartPoint) 236934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr points = points[:-1] + (impliedStartPoint,) 237b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr if n > 0: 23882ef2a52c9ab8a88f10daa9dc1050d1cf3901b57jvr # Split the string of points into discrete quadratic curve 23982ef2a52c9ab8a88f10daa9dc1050d1cf3901b57jvr # segments. Between any two consecutive off-curve points 24082ef2a52c9ab8a88f10daa9dc1050d1cf3901b57jvr # there's an implied on-curve point exactly in the middle. 24182ef2a52c9ab8a88f10daa9dc1050d1cf3901b57jvr # This is where the segment splits. 242b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr _qCurveToOne = self._qCurveToOne 24323cb20053223695a2fb1dd68920297ed8471d77fjvr for pt1, pt2 in decomposeQuadraticSegment(points): 24423cb20053223695a2fb1dd68920297ed8471d77fjvr _qCurveToOne(pt1, pt2) 24523cb20053223695a2fb1dd68920297ed8471d77fjvr self.__currentPoint = pt2 246b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr else: 247b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self.lineTo(points[0]) 248b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 249b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 25023cb20053223695a2fb1dd68920297ed8471d77fjvrdef decomposeSuperBezierSegment(points): 25123cb20053223695a2fb1dd68920297ed8471d77fjvr """Split the SuperBezier described by 'points' into a list of regular 25223cb20053223695a2fb1dd68920297ed8471d77fjvr bezier segments. The 'points' argument must be a sequence with length 25323cb20053223695a2fb1dd68920297ed8471d77fjvr 3 or greater, containing (x, y) coordinates. The last point is the 25423cb20053223695a2fb1dd68920297ed8471d77fjvr destination on-curve point, the rest of the points are off-curve points. 25523cb20053223695a2fb1dd68920297ed8471d77fjvr The start point should not be supplied. 25623cb20053223695a2fb1dd68920297ed8471d77fjvr 25723cb20053223695a2fb1dd68920297ed8471d77fjvr This function returns a list of (pt1, pt2, pt3) tuples, which each 25823cb20053223695a2fb1dd68920297ed8471d77fjvr specify a regular curveto-style bezier segment. 25923cb20053223695a2fb1dd68920297ed8471d77fjvr """ 26023cb20053223695a2fb1dd68920297ed8471d77fjvr n = len(points) - 1 26123cb20053223695a2fb1dd68920297ed8471d77fjvr assert n > 1 26223cb20053223695a2fb1dd68920297ed8471d77fjvr bezierSegments = [] 26323cb20053223695a2fb1dd68920297ed8471d77fjvr pt1, pt2, pt3 = points[0], None, None 26423cb20053223695a2fb1dd68920297ed8471d77fjvr for i in range(2, n+1): 26523cb20053223695a2fb1dd68920297ed8471d77fjvr # calculate points in between control points. 26623cb20053223695a2fb1dd68920297ed8471d77fjvr nDivisions = min(i, 3, n-i+2) 26723cb20053223695a2fb1dd68920297ed8471d77fjvr d = float(nDivisions) 26823cb20053223695a2fb1dd68920297ed8471d77fjvr for j in range(1, nDivisions): 26923cb20053223695a2fb1dd68920297ed8471d77fjvr factor = j / d 27023cb20053223695a2fb1dd68920297ed8471d77fjvr temp1 = points[i-1] 27123cb20053223695a2fb1dd68920297ed8471d77fjvr temp2 = points[i-2] 27223cb20053223695a2fb1dd68920297ed8471d77fjvr temp = (temp2[0] + factor * (temp1[0] - temp2[0]), 27323cb20053223695a2fb1dd68920297ed8471d77fjvr temp2[1] + factor * (temp1[1] - temp2[1])) 27423cb20053223695a2fb1dd68920297ed8471d77fjvr if pt2 is None: 27523cb20053223695a2fb1dd68920297ed8471d77fjvr pt2 = temp 27623cb20053223695a2fb1dd68920297ed8471d77fjvr else: 27723cb20053223695a2fb1dd68920297ed8471d77fjvr pt3 = (0.5 * (pt2[0] + temp[0]), 27823cb20053223695a2fb1dd68920297ed8471d77fjvr 0.5 * (pt2[1] + temp[1])) 27923cb20053223695a2fb1dd68920297ed8471d77fjvr bezierSegments.append((pt1, pt2, pt3)) 28023cb20053223695a2fb1dd68920297ed8471d77fjvr pt1, pt2, pt3 = temp, None, None 28123cb20053223695a2fb1dd68920297ed8471d77fjvr bezierSegments.append((pt1, points[-2], points[-1])) 28223cb20053223695a2fb1dd68920297ed8471d77fjvr return bezierSegments 28323cb20053223695a2fb1dd68920297ed8471d77fjvr 28423cb20053223695a2fb1dd68920297ed8471d77fjvr 28523cb20053223695a2fb1dd68920297ed8471d77fjvrdef decomposeQuadraticSegment(points): 28623cb20053223695a2fb1dd68920297ed8471d77fjvr """Split the quadratic curve segment described by 'points' into a list 28723cb20053223695a2fb1dd68920297ed8471d77fjvr of "atomic" quadratic segments. The 'points' argument must be a sequence 28823cb20053223695a2fb1dd68920297ed8471d77fjvr with length 2 or greater, containing (x, y) coordinates. The last point 28923cb20053223695a2fb1dd68920297ed8471d77fjvr is the destination on-curve point, the rest of the points are off-curve 29023cb20053223695a2fb1dd68920297ed8471d77fjvr points. The start point should not be supplied. 29123cb20053223695a2fb1dd68920297ed8471d77fjvr 29223cb20053223695a2fb1dd68920297ed8471d77fjvr This function returns a list of (pt1, pt2) tuples, which each specify a 29323cb20053223695a2fb1dd68920297ed8471d77fjvr plain quadratic bezier segment. 29423cb20053223695a2fb1dd68920297ed8471d77fjvr """ 29523cb20053223695a2fb1dd68920297ed8471d77fjvr n = len(points) - 1 29623cb20053223695a2fb1dd68920297ed8471d77fjvr assert n > 0 29723cb20053223695a2fb1dd68920297ed8471d77fjvr quadSegments = [] 29823cb20053223695a2fb1dd68920297ed8471d77fjvr for i in range(n - 1): 29923cb20053223695a2fb1dd68920297ed8471d77fjvr x, y = points[i] 30023cb20053223695a2fb1dd68920297ed8471d77fjvr nx, ny = points[i+1] 30123cb20053223695a2fb1dd68920297ed8471d77fjvr impliedPt = (0.5 * (x + nx), 0.5 * (y + ny)) 30223cb20053223695a2fb1dd68920297ed8471d77fjvr quadSegments.append((points[i], impliedPt)) 30323cb20053223695a2fb1dd68920297ed8471d77fjvr quadSegments.append((points[-2], points[-1])) 30423cb20053223695a2fb1dd68920297ed8471d77fjvr return quadSegments 30523cb20053223695a2fb1dd68920297ed8471d77fjvr 30623cb20053223695a2fb1dd68920297ed8471d77fjvr 307b369ef33fcbfd39bd5aaf2b079cef1c689095783jvrclass _TestPen(BasePen): 30840cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr """Test class that prints PostScript to stdout.""" 309b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def _moveTo(self, pt): 310b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr print "%s %s moveto" % (pt[0], pt[1]) 311b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def _lineTo(self, pt): 312b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr print "%s %s lineto" % (pt[0], pt[1]) 313b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def _curveToOne(self, bcp1, bcp2, pt): 314934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr print "%s %s %s %s %s %s curveto" % (bcp1[0], bcp1[1], 31582ef2a52c9ab8a88f10daa9dc1050d1cf3901b57jvr bcp2[0], bcp2[1], pt[0], pt[1]) 316b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def _closePath(self): 317b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr print "closepath" 318b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 319b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 320b369ef33fcbfd39bd5aaf2b079cef1c689095783jvrif __name__ == "__main__": 321b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr pen = _TestPen(None) 322b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr pen.moveTo((0, 0)) 323b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr pen.lineTo((0, 100)) 32423cb20053223695a2fb1dd68920297ed8471d77fjvr pen.curveTo((50, 75), (60, 50), (50, 25), (0, 0)) 325b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr pen.closePath() 326b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 327b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr pen = _TestPen(None) 328934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr # testing the "no on-curve point" scenario 329934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr pen.qCurveTo((0, 0), (0, 100), (100, 100), (100, 0), None) 330b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr pen.closePath() 331