basePen.py revision 3ec6a258238b6068e4eef3fe579f1f5c0a06bbba
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 40285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod__all__ = ["AbstractPen", "NullPen", "BasePen", 4123cb20053223695a2fb1dd68920297ed8471d77fjvr "decomposeSuperBezierSegment", "decomposeQuadraticSegment"] 42b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 43b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 4491bca4244286fb519c93fe92329da96b0e6f32eejvrclass AbstractPen(object): 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 115285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbodclass NullPen(object): 116285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod 117285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod """A pen that does nothing. 118285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod """ 119285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod 120285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod def moveTo(self, pt): 121285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod pass 122285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod 123285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod def lineTo(self, pt): 124285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod pass 125285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod 126285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod def curveTo(self, *points): 127285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod pass 128285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod 129285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod def qCurveTo(self, *points): 130285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod pass 131285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod 132285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod def closePath(self): 133285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod pass 134285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod 135285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod def endPath(self): 136285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod pass 137285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod 138285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod def addComponent(self, glyphName, transformation): 139285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod pass 140285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod 141285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod 142b369ef33fcbfd39bd5aaf2b079cef1c689095783jvrclass BasePen(AbstractPen): 143b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 14440cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr """Base class for drawing pens. You must override _moveTo, _lineTo and 14540cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr _curveToOne. You may additionally override _closePath, _endPath, 14640cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr addComponent and/or _qCurveToOne. You should not override any other 14740cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr methods. 14840cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr """ 149b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 150b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def __init__(self, glyphSet): 151b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self.glyphSet = glyphSet 152b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self.__currentPoint = None 153b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 154b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr # must override 155b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 156b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def _moveTo(self, pt): 157b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr raise NotImplementedError 158b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 159b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def _lineTo(self, pt): 160b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr raise NotImplementedError 161b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 162b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def _curveToOne(self, pt1, pt2, pt3): 163b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr raise NotImplementedError 164b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 165b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr # may override 166b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 167b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def _closePath(self): 168b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr pass 169b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 17040cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr def _endPath(self): 17140cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr pass 17240cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr 173b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def _qCurveToOne(self, pt1, pt2): 174b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr """This method implements the basic quadratic curve type. The 175b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr default implementation delegates the work to the cubic curve 176b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr function. Optionally override with a native implementation. 177b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr """ 178b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr pt0x, pt0y = self.__currentPoint 179b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr pt1x, pt1y = pt1 180b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr pt2x, pt2y = pt2 181b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr mid1x = pt0x + 0.66666666666666667 * (pt1x - pt0x) 182b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr mid1y = pt0y + 0.66666666666666667 * (pt1y - pt0y) 183b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr mid2x = pt2x + 0.66666666666666667 * (pt1x - pt2x) 184b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr mid2y = pt2y + 0.66666666666666667 * (pt1y - pt2y) 185b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self._curveToOne((mid1x, mid1y), (mid2x, mid2y), pt2) 186b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 187b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def addComponent(self, glyphName, transformation): 188b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr """This default implementation simply transforms the points 189b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr of the base glyph and draws it onto self. 190b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr """ 191b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr from fontTools.pens.transformPen import TransformPen 192a5c92986dd7421474fd2eb7c86943d1921150978jvr try: 193a5c92986dd7421474fd2eb7c86943d1921150978jvr glyph = self.glyphSet[glyphName] 194a5c92986dd7421474fd2eb7c86943d1921150978jvr except KeyError: 195a5c92986dd7421474fd2eb7c86943d1921150978jvr pass 196a5c92986dd7421474fd2eb7c86943d1921150978jvr else: 1972e4cc02ca31c43eafb6f752e44dbca9b004a3a2fjvr tPen = TransformPen(self, transformation) 1982e4cc02ca31c43eafb6f752e44dbca9b004a3a2fjvr glyph.draw(tPen) 199b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 200b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr # don't override 201b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 202b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def _getCurrentPoint(self): 203b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr """Return the current point. This is not part of the public 204b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr interface, yet is useful for subclasses. 205b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr """ 206b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr return self.__currentPoint 207b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 208b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def closePath(self): 209b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self._closePath() 210b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self.__currentPoint = None 211b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 21240cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr def endPath(self): 21340cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr self._endPath() 21440cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr self.__currentPoint = None 21540cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr 216b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def moveTo(self, pt): 217b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self._moveTo(pt) 218b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self.__currentPoint = pt 219b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 220b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def lineTo(self, pt): 221b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self._lineTo(pt) 222b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self.__currentPoint = pt 223b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 224b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def curveTo(self, *points): 225b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr n = len(points) - 1 # 'n' is the number of control points 226b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr assert n >= 0 227b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr if n == 2: 228b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr # The common case, we have exactly two BCP's, so this is a standard 22923cb20053223695a2fb1dd68920297ed8471d77fjvr # cubic bezier. Even though decomposeSuperBezierSegment() handles 23023cb20053223695a2fb1dd68920297ed8471d77fjvr # this case just fine, we special-case it anyway since it's so 23123cb20053223695a2fb1dd68920297ed8471d77fjvr # common. 232b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self._curveToOne(*points) 233b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self.__currentPoint = points[-1] 234b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr elif n > 2: 235b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr # n is the number of control points; split curve into n-1 cubic 236b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr # bezier segments. The algorithm used here is inspired by NURB 237b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr # splines and the TrueType "implied point" principle, and ensures 238b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr # the smoothest possible connection between two curve segments, 239b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr # with no disruption in the curvature. It is practical since it 240b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr # allows one to construct multiple bezier segments with a much 241b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr # smaller amount of points. 24223cb20053223695a2fb1dd68920297ed8471d77fjvr _curveToOne = self._curveToOne 24323cb20053223695a2fb1dd68920297ed8471d77fjvr for pt1, pt2, pt3 in decomposeSuperBezierSegment(points): 24423cb20053223695a2fb1dd68920297ed8471d77fjvr _curveToOne(pt1, pt2, pt3) 24523cb20053223695a2fb1dd68920297ed8471d77fjvr self.__currentPoint = pt3 246b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr elif n == 1: 24749b5521f2edcbf91651cae78ef3233bf16bb48d2jvr self.qCurveTo(*points) 248b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr elif n == 0: 249b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self.lineTo(points[0]) 250b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr else: 251cd5aad92f23737ff93a110d5c73d624658a28da8Behdad Esfahbod raise AssertionError("can't get there from here") 252b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 253b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def qCurveTo(self, *points): 254b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr n = len(points) - 1 # 'n' is the number of control points 255b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr assert n >= 0 256934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr if points[-1] is None: 257934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr # Special case for TrueType quadratics: it is possible to 258934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr # define a contour with NO on-curve points. BasePen supports 259934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr # this by allowing the final argument (the expected on-curve 260934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr # point) to be None. We simulate the feature by making the implied 261934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr # on-curve point between the last and the first off-curve points 262934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr # explicit. 263934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr x, y = points[-2] # last off-curve point 264934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr nx, ny = points[0] # first off-curve point 265934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr impliedStartPoint = (0.5 * (x + nx), 0.5 * (y + ny)) 266934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr self.__currentPoint = impliedStartPoint 267934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr self._moveTo(impliedStartPoint) 268934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr points = points[:-1] + (impliedStartPoint,) 269b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr if n > 0: 27082ef2a52c9ab8a88f10daa9dc1050d1cf3901b57jvr # Split the string of points into discrete quadratic curve 27182ef2a52c9ab8a88f10daa9dc1050d1cf3901b57jvr # segments. Between any two consecutive off-curve points 27282ef2a52c9ab8a88f10daa9dc1050d1cf3901b57jvr # there's an implied on-curve point exactly in the middle. 27382ef2a52c9ab8a88f10daa9dc1050d1cf3901b57jvr # This is where the segment splits. 274b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr _qCurveToOne = self._qCurveToOne 27523cb20053223695a2fb1dd68920297ed8471d77fjvr for pt1, pt2 in decomposeQuadraticSegment(points): 27623cb20053223695a2fb1dd68920297ed8471d77fjvr _qCurveToOne(pt1, pt2) 27723cb20053223695a2fb1dd68920297ed8471d77fjvr self.__currentPoint = pt2 278b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr else: 279b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self.lineTo(points[0]) 280b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 281b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 28223cb20053223695a2fb1dd68920297ed8471d77fjvrdef decomposeSuperBezierSegment(points): 28323cb20053223695a2fb1dd68920297ed8471d77fjvr """Split the SuperBezier described by 'points' into a list of regular 28423cb20053223695a2fb1dd68920297ed8471d77fjvr bezier segments. The 'points' argument must be a sequence with length 28523cb20053223695a2fb1dd68920297ed8471d77fjvr 3 or greater, containing (x, y) coordinates. The last point is the 28623cb20053223695a2fb1dd68920297ed8471d77fjvr destination on-curve point, the rest of the points are off-curve points. 28723cb20053223695a2fb1dd68920297ed8471d77fjvr The start point should not be supplied. 28823cb20053223695a2fb1dd68920297ed8471d77fjvr 28923cb20053223695a2fb1dd68920297ed8471d77fjvr This function returns a list of (pt1, pt2, pt3) tuples, which each 29023cb20053223695a2fb1dd68920297ed8471d77fjvr specify a regular curveto-style bezier segment. 29123cb20053223695a2fb1dd68920297ed8471d77fjvr """ 29223cb20053223695a2fb1dd68920297ed8471d77fjvr n = len(points) - 1 29323cb20053223695a2fb1dd68920297ed8471d77fjvr assert n > 1 29423cb20053223695a2fb1dd68920297ed8471d77fjvr bezierSegments = [] 29523cb20053223695a2fb1dd68920297ed8471d77fjvr pt1, pt2, pt3 = points[0], None, None 29623cb20053223695a2fb1dd68920297ed8471d77fjvr for i in range(2, n+1): 29723cb20053223695a2fb1dd68920297ed8471d77fjvr # calculate points in between control points. 29823cb20053223695a2fb1dd68920297ed8471d77fjvr nDivisions = min(i, 3, n-i+2) 29923cb20053223695a2fb1dd68920297ed8471d77fjvr d = float(nDivisions) 30023cb20053223695a2fb1dd68920297ed8471d77fjvr for j in range(1, nDivisions): 30123cb20053223695a2fb1dd68920297ed8471d77fjvr factor = j / d 30223cb20053223695a2fb1dd68920297ed8471d77fjvr temp1 = points[i-1] 30323cb20053223695a2fb1dd68920297ed8471d77fjvr temp2 = points[i-2] 30423cb20053223695a2fb1dd68920297ed8471d77fjvr temp = (temp2[0] + factor * (temp1[0] - temp2[0]), 30523cb20053223695a2fb1dd68920297ed8471d77fjvr temp2[1] + factor * (temp1[1] - temp2[1])) 30623cb20053223695a2fb1dd68920297ed8471d77fjvr if pt2 is None: 30723cb20053223695a2fb1dd68920297ed8471d77fjvr pt2 = temp 30823cb20053223695a2fb1dd68920297ed8471d77fjvr else: 30923cb20053223695a2fb1dd68920297ed8471d77fjvr pt3 = (0.5 * (pt2[0] + temp[0]), 31023cb20053223695a2fb1dd68920297ed8471d77fjvr 0.5 * (pt2[1] + temp[1])) 31123cb20053223695a2fb1dd68920297ed8471d77fjvr bezierSegments.append((pt1, pt2, pt3)) 31223cb20053223695a2fb1dd68920297ed8471d77fjvr pt1, pt2, pt3 = temp, None, None 31323cb20053223695a2fb1dd68920297ed8471d77fjvr bezierSegments.append((pt1, points[-2], points[-1])) 31423cb20053223695a2fb1dd68920297ed8471d77fjvr return bezierSegments 31523cb20053223695a2fb1dd68920297ed8471d77fjvr 31623cb20053223695a2fb1dd68920297ed8471d77fjvr 31723cb20053223695a2fb1dd68920297ed8471d77fjvrdef decomposeQuadraticSegment(points): 31823cb20053223695a2fb1dd68920297ed8471d77fjvr """Split the quadratic curve segment described by 'points' into a list 31923cb20053223695a2fb1dd68920297ed8471d77fjvr of "atomic" quadratic segments. The 'points' argument must be a sequence 32023cb20053223695a2fb1dd68920297ed8471d77fjvr with length 2 or greater, containing (x, y) coordinates. The last point 32123cb20053223695a2fb1dd68920297ed8471d77fjvr is the destination on-curve point, the rest of the points are off-curve 32223cb20053223695a2fb1dd68920297ed8471d77fjvr points. The start point should not be supplied. 32323cb20053223695a2fb1dd68920297ed8471d77fjvr 32423cb20053223695a2fb1dd68920297ed8471d77fjvr This function returns a list of (pt1, pt2) tuples, which each specify a 32523cb20053223695a2fb1dd68920297ed8471d77fjvr plain quadratic bezier segment. 32623cb20053223695a2fb1dd68920297ed8471d77fjvr """ 32723cb20053223695a2fb1dd68920297ed8471d77fjvr n = len(points) - 1 32823cb20053223695a2fb1dd68920297ed8471d77fjvr assert n > 0 32923cb20053223695a2fb1dd68920297ed8471d77fjvr quadSegments = [] 33023cb20053223695a2fb1dd68920297ed8471d77fjvr for i in range(n - 1): 33123cb20053223695a2fb1dd68920297ed8471d77fjvr x, y = points[i] 33223cb20053223695a2fb1dd68920297ed8471d77fjvr nx, ny = points[i+1] 33323cb20053223695a2fb1dd68920297ed8471d77fjvr impliedPt = (0.5 * (x + nx), 0.5 * (y + ny)) 33423cb20053223695a2fb1dd68920297ed8471d77fjvr quadSegments.append((points[i], impliedPt)) 33523cb20053223695a2fb1dd68920297ed8471d77fjvr quadSegments.append((points[-2], points[-1])) 33623cb20053223695a2fb1dd68920297ed8471d77fjvr return quadSegments 33723cb20053223695a2fb1dd68920297ed8471d77fjvr 33823cb20053223695a2fb1dd68920297ed8471d77fjvr 339b369ef33fcbfd39bd5aaf2b079cef1c689095783jvrclass _TestPen(BasePen): 34040cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr """Test class that prints PostScript to stdout.""" 341b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def _moveTo(self, pt): 3423ec6a258238b6068e4eef3fe579f1f5c0a06bbbaBehdad Esfahbod print("%s %s moveto" % (pt[0], pt[1])) 343b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def _lineTo(self, pt): 3443ec6a258238b6068e4eef3fe579f1f5c0a06bbbaBehdad Esfahbod print("%s %s lineto" % (pt[0], pt[1])) 345b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def _curveToOne(self, bcp1, bcp2, pt): 3463ec6a258238b6068e4eef3fe579f1f5c0a06bbbaBehdad Esfahbod print("%s %s %s %s %s %s curveto" % (bcp1[0], bcp1[1], 3473ec6a258238b6068e4eef3fe579f1f5c0a06bbbaBehdad Esfahbod bcp2[0], bcp2[1], pt[0], pt[1])) 348b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def _closePath(self): 3493ec6a258238b6068e4eef3fe579f1f5c0a06bbbaBehdad Esfahbod print("closepath") 350b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 351b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 352b369ef33fcbfd39bd5aaf2b079cef1c689095783jvrif __name__ == "__main__": 353b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr pen = _TestPen(None) 354b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr pen.moveTo((0, 0)) 355b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr pen.lineTo((0, 100)) 35623cb20053223695a2fb1dd68920297ed8471d77fjvr pen.curveTo((50, 75), (60, 50), (50, 25), (0, 0)) 357b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr pen.closePath() 358b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 359b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr pen = _TestPen(None) 360934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr # testing the "no on-curve point" scenario 361934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr pen.qCurveTo((0, 0), (0, 100), (100, 100), (100, 0), None) 362b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr pen.closePath() 363