basePen.py revision 2e4cc02ca31c43eafb6f752e44dbca9b004a3a2f
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 1652e4cc02ca31c43eafb6f752e44dbca9b004a3a2fjvr glyph = self.glyphSet.get(glyphName) 1662e4cc02ca31c43eafb6f752e44dbca9b004a3a2fjvr if glyph is not None: 1672e4cc02ca31c43eafb6f752e44dbca9b004a3a2fjvr tPen = TransformPen(self, transformation) 1682e4cc02ca31c43eafb6f752e44dbca9b004a3a2fjvr glyph.draw(tPen) 169b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 170b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr # don't override 171b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 172b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def _getCurrentPoint(self): 173b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr """Return the current point. This is not part of the public 174b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr interface, yet is useful for subclasses. 175b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr """ 176b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr return self.__currentPoint 177b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 178b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def closePath(self): 179b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self._closePath() 180b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self.__currentPoint = None 181b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 18240cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr def endPath(self): 18340cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr self._endPath() 18440cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr self.__currentPoint = None 18540cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr 186b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def moveTo(self, pt): 187b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self._moveTo(pt) 188b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self.__currentPoint = pt 189b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 190b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def lineTo(self, pt): 191b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self._lineTo(pt) 192b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self.__currentPoint = pt 193b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 194b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def curveTo(self, *points): 195b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr n = len(points) - 1 # 'n' is the number of control points 196b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr assert n >= 0 197b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr if n == 2: 198b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr # The common case, we have exactly two BCP's, so this is a standard 19923cb20053223695a2fb1dd68920297ed8471d77fjvr # cubic bezier. Even though decomposeSuperBezierSegment() handles 20023cb20053223695a2fb1dd68920297ed8471d77fjvr # this case just fine, we special-case it anyway since it's so 20123cb20053223695a2fb1dd68920297ed8471d77fjvr # common. 202b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self._curveToOne(*points) 203b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self.__currentPoint = points[-1] 204b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr elif n > 2: 205b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr # n is the number of control points; split curve into n-1 cubic 206b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr # bezier segments. The algorithm used here is inspired by NURB 207b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr # splines and the TrueType "implied point" principle, and ensures 208b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr # the smoothest possible connection between two curve segments, 209b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr # with no disruption in the curvature. It is practical since it 210b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr # allows one to construct multiple bezier segments with a much 211b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr # smaller amount of points. 21223cb20053223695a2fb1dd68920297ed8471d77fjvr _curveToOne = self._curveToOne 21323cb20053223695a2fb1dd68920297ed8471d77fjvr for pt1, pt2, pt3 in decomposeSuperBezierSegment(points): 21423cb20053223695a2fb1dd68920297ed8471d77fjvr _curveToOne(pt1, pt2, pt3) 21523cb20053223695a2fb1dd68920297ed8471d77fjvr self.__currentPoint = pt3 216b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr elif n == 1: 21749b5521f2edcbf91651cae78ef3233bf16bb48d2jvr self.qCurveTo(*points) 218b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr elif n == 0: 219b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self.lineTo(points[0]) 220b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr else: 221b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr raise AssertionError, "can't get there from here" 222b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 223b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def qCurveTo(self, *points): 224b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr n = len(points) - 1 # 'n' is the number of control points 225b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr assert n >= 0 226934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr if points[-1] is None: 227934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr # Special case for TrueType quadratics: it is possible to 228934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr # define a contour with NO on-curve points. BasePen supports 229934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr # this by allowing the final argument (the expected on-curve 230934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr # point) to be None. We simulate the feature by making the implied 231934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr # on-curve point between the last and the first off-curve points 232934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr # explicit. 233934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr x, y = points[-2] # last off-curve point 234934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr nx, ny = points[0] # first off-curve point 235934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr impliedStartPoint = (0.5 * (x + nx), 0.5 * (y + ny)) 236934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr self.__currentPoint = impliedStartPoint 237934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr self._moveTo(impliedStartPoint) 238934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr points = points[:-1] + (impliedStartPoint,) 239b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr if n > 0: 24082ef2a52c9ab8a88f10daa9dc1050d1cf3901b57jvr # Split the string of points into discrete quadratic curve 24182ef2a52c9ab8a88f10daa9dc1050d1cf3901b57jvr # segments. Between any two consecutive off-curve points 24282ef2a52c9ab8a88f10daa9dc1050d1cf3901b57jvr # there's an implied on-curve point exactly in the middle. 24382ef2a52c9ab8a88f10daa9dc1050d1cf3901b57jvr # This is where the segment splits. 244b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr _qCurveToOne = self._qCurveToOne 24523cb20053223695a2fb1dd68920297ed8471d77fjvr for pt1, pt2 in decomposeQuadraticSegment(points): 24623cb20053223695a2fb1dd68920297ed8471d77fjvr _qCurveToOne(pt1, pt2) 24723cb20053223695a2fb1dd68920297ed8471d77fjvr self.__currentPoint = pt2 248b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr else: 249b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self.lineTo(points[0]) 250b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 251b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 25223cb20053223695a2fb1dd68920297ed8471d77fjvrdef decomposeSuperBezierSegment(points): 25323cb20053223695a2fb1dd68920297ed8471d77fjvr """Split the SuperBezier described by 'points' into a list of regular 25423cb20053223695a2fb1dd68920297ed8471d77fjvr bezier segments. The 'points' argument must be a sequence with length 25523cb20053223695a2fb1dd68920297ed8471d77fjvr 3 or greater, containing (x, y) coordinates. The last point is the 25623cb20053223695a2fb1dd68920297ed8471d77fjvr destination on-curve point, the rest of the points are off-curve points. 25723cb20053223695a2fb1dd68920297ed8471d77fjvr The start point should not be supplied. 25823cb20053223695a2fb1dd68920297ed8471d77fjvr 25923cb20053223695a2fb1dd68920297ed8471d77fjvr This function returns a list of (pt1, pt2, pt3) tuples, which each 26023cb20053223695a2fb1dd68920297ed8471d77fjvr specify a regular curveto-style bezier segment. 26123cb20053223695a2fb1dd68920297ed8471d77fjvr """ 26223cb20053223695a2fb1dd68920297ed8471d77fjvr n = len(points) - 1 26323cb20053223695a2fb1dd68920297ed8471d77fjvr assert n > 1 26423cb20053223695a2fb1dd68920297ed8471d77fjvr bezierSegments = [] 26523cb20053223695a2fb1dd68920297ed8471d77fjvr pt1, pt2, pt3 = points[0], None, None 26623cb20053223695a2fb1dd68920297ed8471d77fjvr for i in range(2, n+1): 26723cb20053223695a2fb1dd68920297ed8471d77fjvr # calculate points in between control points. 26823cb20053223695a2fb1dd68920297ed8471d77fjvr nDivisions = min(i, 3, n-i+2) 26923cb20053223695a2fb1dd68920297ed8471d77fjvr d = float(nDivisions) 27023cb20053223695a2fb1dd68920297ed8471d77fjvr for j in range(1, nDivisions): 27123cb20053223695a2fb1dd68920297ed8471d77fjvr factor = j / d 27223cb20053223695a2fb1dd68920297ed8471d77fjvr temp1 = points[i-1] 27323cb20053223695a2fb1dd68920297ed8471d77fjvr temp2 = points[i-2] 27423cb20053223695a2fb1dd68920297ed8471d77fjvr temp = (temp2[0] + factor * (temp1[0] - temp2[0]), 27523cb20053223695a2fb1dd68920297ed8471d77fjvr temp2[1] + factor * (temp1[1] - temp2[1])) 27623cb20053223695a2fb1dd68920297ed8471d77fjvr if pt2 is None: 27723cb20053223695a2fb1dd68920297ed8471d77fjvr pt2 = temp 27823cb20053223695a2fb1dd68920297ed8471d77fjvr else: 27923cb20053223695a2fb1dd68920297ed8471d77fjvr pt3 = (0.5 * (pt2[0] + temp[0]), 28023cb20053223695a2fb1dd68920297ed8471d77fjvr 0.5 * (pt2[1] + temp[1])) 28123cb20053223695a2fb1dd68920297ed8471d77fjvr bezierSegments.append((pt1, pt2, pt3)) 28223cb20053223695a2fb1dd68920297ed8471d77fjvr pt1, pt2, pt3 = temp, None, None 28323cb20053223695a2fb1dd68920297ed8471d77fjvr bezierSegments.append((pt1, points[-2], points[-1])) 28423cb20053223695a2fb1dd68920297ed8471d77fjvr return bezierSegments 28523cb20053223695a2fb1dd68920297ed8471d77fjvr 28623cb20053223695a2fb1dd68920297ed8471d77fjvr 28723cb20053223695a2fb1dd68920297ed8471d77fjvrdef decomposeQuadraticSegment(points): 28823cb20053223695a2fb1dd68920297ed8471d77fjvr """Split the quadratic curve segment described by 'points' into a list 28923cb20053223695a2fb1dd68920297ed8471d77fjvr of "atomic" quadratic segments. The 'points' argument must be a sequence 29023cb20053223695a2fb1dd68920297ed8471d77fjvr with length 2 or greater, containing (x, y) coordinates. The last point 29123cb20053223695a2fb1dd68920297ed8471d77fjvr is the destination on-curve point, the rest of the points are off-curve 29223cb20053223695a2fb1dd68920297ed8471d77fjvr points. The start point should not be supplied. 29323cb20053223695a2fb1dd68920297ed8471d77fjvr 29423cb20053223695a2fb1dd68920297ed8471d77fjvr This function returns a list of (pt1, pt2) tuples, which each specify a 29523cb20053223695a2fb1dd68920297ed8471d77fjvr plain quadratic bezier segment. 29623cb20053223695a2fb1dd68920297ed8471d77fjvr """ 29723cb20053223695a2fb1dd68920297ed8471d77fjvr n = len(points) - 1 29823cb20053223695a2fb1dd68920297ed8471d77fjvr assert n > 0 29923cb20053223695a2fb1dd68920297ed8471d77fjvr quadSegments = [] 30023cb20053223695a2fb1dd68920297ed8471d77fjvr for i in range(n - 1): 30123cb20053223695a2fb1dd68920297ed8471d77fjvr x, y = points[i] 30223cb20053223695a2fb1dd68920297ed8471d77fjvr nx, ny = points[i+1] 30323cb20053223695a2fb1dd68920297ed8471d77fjvr impliedPt = (0.5 * (x + nx), 0.5 * (y + ny)) 30423cb20053223695a2fb1dd68920297ed8471d77fjvr quadSegments.append((points[i], impliedPt)) 30523cb20053223695a2fb1dd68920297ed8471d77fjvr quadSegments.append((points[-2], points[-1])) 30623cb20053223695a2fb1dd68920297ed8471d77fjvr return quadSegments 30723cb20053223695a2fb1dd68920297ed8471d77fjvr 30823cb20053223695a2fb1dd68920297ed8471d77fjvr 309b369ef33fcbfd39bd5aaf2b079cef1c689095783jvrclass _TestPen(BasePen): 31040cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr """Test class that prints PostScript to stdout.""" 311b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def _moveTo(self, pt): 312b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr print "%s %s moveto" % (pt[0], pt[1]) 313b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def _lineTo(self, pt): 314b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr print "%s %s lineto" % (pt[0], pt[1]) 315b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def _curveToOne(self, bcp1, bcp2, pt): 316934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr print "%s %s %s %s %s %s curveto" % (bcp1[0], bcp1[1], 31782ef2a52c9ab8a88f10daa9dc1050d1cf3901b57jvr bcp2[0], bcp2[1], pt[0], pt[1]) 318b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def _closePath(self): 319b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr print "closepath" 320b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 321b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 322b369ef33fcbfd39bd5aaf2b079cef1c689095783jvrif __name__ == "__main__": 323b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr pen = _TestPen(None) 324b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr pen.moveTo((0, 0)) 325b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr pen.lineTo((0, 100)) 32623cb20053223695a2fb1dd68920297ed8471d77fjvr pen.curveTo((50, 75), (60, 50), (50, 25), (0, 0)) 327b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr pen.closePath() 328b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 329b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr pen = _TestPen(None) 330934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr # testing the "no on-curve point" scenario 331934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr pen.qCurveTo((0, 0), (0, 100), (100, 100), (100, 0), None) 332b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr pen.closePath() 333