basePen.py revision 7ed91eca1eaa96b79eae780778e89bb9ec44c1ee
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 397ed91eca1eaa96b79eae780778e89bb9ec44c1eeBehdad Esfahbodfrom fontTools.misc.py23 import * 4040cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr 41285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod__all__ = ["AbstractPen", "NullPen", "BasePen", 4223cb20053223695a2fb1dd68920297ed8471d77fjvr "decomposeSuperBezierSegment", "decomposeQuadraticSegment"] 43b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 44b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 4591bca4244286fb519c93fe92329da96b0e6f32eejvrclass AbstractPen(object): 46b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 47b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def moveTo(self, pt): 4840cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr """Begin a new sub path, set the current point to 'pt'. You must 4940cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr end each sub path with a call to pen.closePath() or pen.endPath(). 5040cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr """ 51b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr raise NotImplementedError 52b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 53b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def lineTo(self, pt): 5440cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr """Draw a straight line from the current point to 'pt'.""" 55b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr raise NotImplementedError 56b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 57b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def curveTo(self, *points): 58934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr """Draw a cubic bezier with an arbitrary number of control points. 59934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr 60934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr The last point specified is on-curve, all others are off-curve 61934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr (control) points. If the number of control points is > 2, the 62934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr segment is split into multiple bezier segments. This works 63934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr like this: 64b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 65b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr Let n be the number of control points (which is the number of 66b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr arguments to this call minus 1). If n==2, a plain vanilla cubic 67b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr bezier is drawn. If n==1, we fall back to a quadratic segment and 68b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr if n==0 we draw a straight line. It gets interesting when n>2: 69b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr n-1 PostScript-style cubic segments will be drawn as if it were 7023cb20053223695a2fb1dd68920297ed8471d77fjvr one curve. See decomposeSuperBezierSegment(). 71b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 72b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr The conversion algorithm used for n>2 is inspired by NURB 73b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr splines, and is conceptually equivalent to the TrueType "implied 7423cb20053223695a2fb1dd68920297ed8471d77fjvr points" principle. See also decomposeQuadraticSegment(). 75b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr """ 76b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr raise NotImplementedError 77b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 78b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def qCurveTo(self, *points): 79b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr """Draw a whole string of quadratic curve segments. 80b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 81934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr The last point specified is on-curve, all others are off-curve 82934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr points. 83934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr 84934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr This method implements TrueType-style curves, breaking up curves 85934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr using 'implied points': between each two consequtive off-curve points, 8623cb20053223695a2fb1dd68920297ed8471d77fjvr there is one implied point exactly in the middle between them. See 8723cb20053223695a2fb1dd68920297ed8471d77fjvr also decomposeQuadraticSegment(). 88b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 89934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr The last argument (normally the on-curve point) may be None. 90934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr This is to support contours that have NO on-curve points (a rarely 91934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr seen feature of TrueType outlines). 92b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr """ 93b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr raise NotImplementedError 94b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 95b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def closePath(self): 9640cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr """Close the current sub path. You must call either pen.closePath() 9740cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr or pen.endPath() after each sub path. 9840cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr """ 9940cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr pass 10040cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr 10140cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr def endPath(self): 10240cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr """End the current sub path, but don't close it. You must call 10340cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr either pen.closePath() or pen.endPath() after each sub path. 10440cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr """ 105b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr pass 106b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 107b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def addComponent(self, glyphName, transformation): 108934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr """Add a sub glyph. The 'transformation' argument must be a 6-tuple 109934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr containing an affine transformation, or a Transform object from the 110934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr fontTools.misc.transform module. More precisely: it should be a 111934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr sequence containing 6 numbers. 112934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr """ 113b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr raise NotImplementedError 114b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 115b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 116285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbodclass NullPen(object): 117285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod 118285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod """A pen that does nothing. 119285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod """ 120285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod 121285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod def moveTo(self, pt): 122285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod pass 123285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod 124285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod def lineTo(self, pt): 125285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod pass 126285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod 127285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod def curveTo(self, *points): 128285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod pass 129285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod 130285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod def qCurveTo(self, *points): 131285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod pass 132285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod 133285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod def closePath(self): 134285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod pass 135285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod 136285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod def endPath(self): 137285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod pass 138285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod 139285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod def addComponent(self, glyphName, transformation): 140285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod pass 141285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod 142285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod 143b369ef33fcbfd39bd5aaf2b079cef1c689095783jvrclass BasePen(AbstractPen): 144b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 14540cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr """Base class for drawing pens. You must override _moveTo, _lineTo and 14640cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr _curveToOne. You may additionally override _closePath, _endPath, 14740cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr addComponent and/or _qCurveToOne. You should not override any other 14840cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr methods. 14940cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr """ 150b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 151b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def __init__(self, glyphSet): 152b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self.glyphSet = glyphSet 153b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self.__currentPoint = None 154b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 155b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr # must override 156b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 157b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def _moveTo(self, pt): 158b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr raise NotImplementedError 159b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 160b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def _lineTo(self, pt): 161b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr raise NotImplementedError 162b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 163b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def _curveToOne(self, pt1, pt2, pt3): 164b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr raise NotImplementedError 165b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 166b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr # may override 167b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 168b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def _closePath(self): 169b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr pass 170b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 17140cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr def _endPath(self): 17240cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr pass 17340cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr 174b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def _qCurveToOne(self, pt1, pt2): 175b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr """This method implements the basic quadratic curve type. The 176b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr default implementation delegates the work to the cubic curve 177b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr function. Optionally override with a native implementation. 178b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr """ 179b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr pt0x, pt0y = self.__currentPoint 180b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr pt1x, pt1y = pt1 181b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr pt2x, pt2y = pt2 182b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr mid1x = pt0x + 0.66666666666666667 * (pt1x - pt0x) 183b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr mid1y = pt0y + 0.66666666666666667 * (pt1y - pt0y) 184b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr mid2x = pt2x + 0.66666666666666667 * (pt1x - pt2x) 185b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr mid2y = pt2y + 0.66666666666666667 * (pt1y - pt2y) 186b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self._curveToOne((mid1x, mid1y), (mid2x, mid2y), pt2) 187b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 188b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def addComponent(self, glyphName, transformation): 189b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr """This default implementation simply transforms the points 190b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr of the base glyph and draws it onto self. 191b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr """ 192b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr from fontTools.pens.transformPen import TransformPen 193a5c92986dd7421474fd2eb7c86943d1921150978jvr try: 194a5c92986dd7421474fd2eb7c86943d1921150978jvr glyph = self.glyphSet[glyphName] 195a5c92986dd7421474fd2eb7c86943d1921150978jvr except KeyError: 196a5c92986dd7421474fd2eb7c86943d1921150978jvr pass 197a5c92986dd7421474fd2eb7c86943d1921150978jvr else: 1982e4cc02ca31c43eafb6f752e44dbca9b004a3a2fjvr tPen = TransformPen(self, transformation) 1992e4cc02ca31c43eafb6f752e44dbca9b004a3a2fjvr glyph.draw(tPen) 200b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 201b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr # don't override 202b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 203b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def _getCurrentPoint(self): 204b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr """Return the current point. This is not part of the public 205b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr interface, yet is useful for subclasses. 206b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr """ 207b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr return self.__currentPoint 208b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 209b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def closePath(self): 210b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self._closePath() 211b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self.__currentPoint = None 212b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 21340cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr def endPath(self): 21440cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr self._endPath() 21540cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr self.__currentPoint = None 21640cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr 217b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def moveTo(self, pt): 218b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self._moveTo(pt) 219b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self.__currentPoint = pt 220b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 221b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def lineTo(self, pt): 222b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self._lineTo(pt) 223b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self.__currentPoint = pt 224b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 225b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def curveTo(self, *points): 226b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr n = len(points) - 1 # 'n' is the number of control points 227b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr assert n >= 0 228b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr if n == 2: 229b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr # The common case, we have exactly two BCP's, so this is a standard 23023cb20053223695a2fb1dd68920297ed8471d77fjvr # cubic bezier. Even though decomposeSuperBezierSegment() handles 23123cb20053223695a2fb1dd68920297ed8471d77fjvr # this case just fine, we special-case it anyway since it's so 23223cb20053223695a2fb1dd68920297ed8471d77fjvr # common. 233b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self._curveToOne(*points) 234b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self.__currentPoint = points[-1] 235b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr elif n > 2: 236b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr # n is the number of control points; split curve into n-1 cubic 237b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr # bezier segments. The algorithm used here is inspired by NURB 238b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr # splines and the TrueType "implied point" principle, and ensures 239b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr # the smoothest possible connection between two curve segments, 240b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr # with no disruption in the curvature. It is practical since it 241b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr # allows one to construct multiple bezier segments with a much 242b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr # smaller amount of points. 24323cb20053223695a2fb1dd68920297ed8471d77fjvr _curveToOne = self._curveToOne 24423cb20053223695a2fb1dd68920297ed8471d77fjvr for pt1, pt2, pt3 in decomposeSuperBezierSegment(points): 24523cb20053223695a2fb1dd68920297ed8471d77fjvr _curveToOne(pt1, pt2, pt3) 24623cb20053223695a2fb1dd68920297ed8471d77fjvr self.__currentPoint = pt3 247b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr elif n == 1: 24849b5521f2edcbf91651cae78ef3233bf16bb48d2jvr self.qCurveTo(*points) 249b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr elif n == 0: 250b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self.lineTo(points[0]) 251b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr else: 252cd5aad92f23737ff93a110d5c73d624658a28da8Behdad Esfahbod raise AssertionError("can't get there from here") 253b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 254b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def qCurveTo(self, *points): 255b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr n = len(points) - 1 # 'n' is the number of control points 256b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr assert n >= 0 257934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr if points[-1] is None: 258934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr # Special case for TrueType quadratics: it is possible to 259934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr # define a contour with NO on-curve points. BasePen supports 260934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr # this by allowing the final argument (the expected on-curve 261934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr # point) to be None. We simulate the feature by making the implied 262934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr # on-curve point between the last and the first off-curve points 263934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr # explicit. 264934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr x, y = points[-2] # last off-curve point 265934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr nx, ny = points[0] # first off-curve point 266934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr impliedStartPoint = (0.5 * (x + nx), 0.5 * (y + ny)) 267934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr self.__currentPoint = impliedStartPoint 268934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr self._moveTo(impliedStartPoint) 269934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr points = points[:-1] + (impliedStartPoint,) 270b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr if n > 0: 27182ef2a52c9ab8a88f10daa9dc1050d1cf3901b57jvr # Split the string of points into discrete quadratic curve 27282ef2a52c9ab8a88f10daa9dc1050d1cf3901b57jvr # segments. Between any two consecutive off-curve points 27382ef2a52c9ab8a88f10daa9dc1050d1cf3901b57jvr # there's an implied on-curve point exactly in the middle. 27482ef2a52c9ab8a88f10daa9dc1050d1cf3901b57jvr # This is where the segment splits. 275b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr _qCurveToOne = self._qCurveToOne 27623cb20053223695a2fb1dd68920297ed8471d77fjvr for pt1, pt2 in decomposeQuadraticSegment(points): 27723cb20053223695a2fb1dd68920297ed8471d77fjvr _qCurveToOne(pt1, pt2) 27823cb20053223695a2fb1dd68920297ed8471d77fjvr self.__currentPoint = pt2 279b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr else: 280b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self.lineTo(points[0]) 281b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 282b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 28323cb20053223695a2fb1dd68920297ed8471d77fjvrdef decomposeSuperBezierSegment(points): 28423cb20053223695a2fb1dd68920297ed8471d77fjvr """Split the SuperBezier described by 'points' into a list of regular 28523cb20053223695a2fb1dd68920297ed8471d77fjvr bezier segments. The 'points' argument must be a sequence with length 28623cb20053223695a2fb1dd68920297ed8471d77fjvr 3 or greater, containing (x, y) coordinates. The last point is the 28723cb20053223695a2fb1dd68920297ed8471d77fjvr destination on-curve point, the rest of the points are off-curve points. 28823cb20053223695a2fb1dd68920297ed8471d77fjvr The start point should not be supplied. 28923cb20053223695a2fb1dd68920297ed8471d77fjvr 29023cb20053223695a2fb1dd68920297ed8471d77fjvr This function returns a list of (pt1, pt2, pt3) tuples, which each 29123cb20053223695a2fb1dd68920297ed8471d77fjvr specify a regular curveto-style bezier segment. 29223cb20053223695a2fb1dd68920297ed8471d77fjvr """ 29323cb20053223695a2fb1dd68920297ed8471d77fjvr n = len(points) - 1 29423cb20053223695a2fb1dd68920297ed8471d77fjvr assert n > 1 29523cb20053223695a2fb1dd68920297ed8471d77fjvr bezierSegments = [] 29623cb20053223695a2fb1dd68920297ed8471d77fjvr pt1, pt2, pt3 = points[0], None, None 29723cb20053223695a2fb1dd68920297ed8471d77fjvr for i in range(2, n+1): 29823cb20053223695a2fb1dd68920297ed8471d77fjvr # calculate points in between control points. 29923cb20053223695a2fb1dd68920297ed8471d77fjvr nDivisions = min(i, 3, n-i+2) 30023cb20053223695a2fb1dd68920297ed8471d77fjvr d = float(nDivisions) 30123cb20053223695a2fb1dd68920297ed8471d77fjvr for j in range(1, nDivisions): 30223cb20053223695a2fb1dd68920297ed8471d77fjvr factor = j / d 30323cb20053223695a2fb1dd68920297ed8471d77fjvr temp1 = points[i-1] 30423cb20053223695a2fb1dd68920297ed8471d77fjvr temp2 = points[i-2] 30523cb20053223695a2fb1dd68920297ed8471d77fjvr temp = (temp2[0] + factor * (temp1[0] - temp2[0]), 30623cb20053223695a2fb1dd68920297ed8471d77fjvr temp2[1] + factor * (temp1[1] - temp2[1])) 30723cb20053223695a2fb1dd68920297ed8471d77fjvr if pt2 is None: 30823cb20053223695a2fb1dd68920297ed8471d77fjvr pt2 = temp 30923cb20053223695a2fb1dd68920297ed8471d77fjvr else: 31023cb20053223695a2fb1dd68920297ed8471d77fjvr pt3 = (0.5 * (pt2[0] + temp[0]), 31123cb20053223695a2fb1dd68920297ed8471d77fjvr 0.5 * (pt2[1] + temp[1])) 31223cb20053223695a2fb1dd68920297ed8471d77fjvr bezierSegments.append((pt1, pt2, pt3)) 31323cb20053223695a2fb1dd68920297ed8471d77fjvr pt1, pt2, pt3 = temp, None, None 31423cb20053223695a2fb1dd68920297ed8471d77fjvr bezierSegments.append((pt1, points[-2], points[-1])) 31523cb20053223695a2fb1dd68920297ed8471d77fjvr return bezierSegments 31623cb20053223695a2fb1dd68920297ed8471d77fjvr 31723cb20053223695a2fb1dd68920297ed8471d77fjvr 31823cb20053223695a2fb1dd68920297ed8471d77fjvrdef decomposeQuadraticSegment(points): 31923cb20053223695a2fb1dd68920297ed8471d77fjvr """Split the quadratic curve segment described by 'points' into a list 32023cb20053223695a2fb1dd68920297ed8471d77fjvr of "atomic" quadratic segments. The 'points' argument must be a sequence 32123cb20053223695a2fb1dd68920297ed8471d77fjvr with length 2 or greater, containing (x, y) coordinates. The last point 32223cb20053223695a2fb1dd68920297ed8471d77fjvr is the destination on-curve point, the rest of the points are off-curve 32323cb20053223695a2fb1dd68920297ed8471d77fjvr points. The start point should not be supplied. 32423cb20053223695a2fb1dd68920297ed8471d77fjvr 32523cb20053223695a2fb1dd68920297ed8471d77fjvr This function returns a list of (pt1, pt2) tuples, which each specify a 32623cb20053223695a2fb1dd68920297ed8471d77fjvr plain quadratic bezier segment. 32723cb20053223695a2fb1dd68920297ed8471d77fjvr """ 32823cb20053223695a2fb1dd68920297ed8471d77fjvr n = len(points) - 1 32923cb20053223695a2fb1dd68920297ed8471d77fjvr assert n > 0 33023cb20053223695a2fb1dd68920297ed8471d77fjvr quadSegments = [] 33123cb20053223695a2fb1dd68920297ed8471d77fjvr for i in range(n - 1): 33223cb20053223695a2fb1dd68920297ed8471d77fjvr x, y = points[i] 33323cb20053223695a2fb1dd68920297ed8471d77fjvr nx, ny = points[i+1] 33423cb20053223695a2fb1dd68920297ed8471d77fjvr impliedPt = (0.5 * (x + nx), 0.5 * (y + ny)) 33523cb20053223695a2fb1dd68920297ed8471d77fjvr quadSegments.append((points[i], impliedPt)) 33623cb20053223695a2fb1dd68920297ed8471d77fjvr quadSegments.append((points[-2], points[-1])) 33723cb20053223695a2fb1dd68920297ed8471d77fjvr return quadSegments 33823cb20053223695a2fb1dd68920297ed8471d77fjvr 33923cb20053223695a2fb1dd68920297ed8471d77fjvr 340b369ef33fcbfd39bd5aaf2b079cef1c689095783jvrclass _TestPen(BasePen): 34140cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr """Test class that prints PostScript to stdout.""" 342b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def _moveTo(self, pt): 3433ec6a258238b6068e4eef3fe579f1f5c0a06bbbaBehdad Esfahbod print("%s %s moveto" % (pt[0], pt[1])) 344b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def _lineTo(self, pt): 3453ec6a258238b6068e4eef3fe579f1f5c0a06bbbaBehdad Esfahbod print("%s %s lineto" % (pt[0], pt[1])) 346b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def _curveToOne(self, bcp1, bcp2, pt): 3473ec6a258238b6068e4eef3fe579f1f5c0a06bbbaBehdad Esfahbod print("%s %s %s %s %s %s curveto" % (bcp1[0], bcp1[1], 3483ec6a258238b6068e4eef3fe579f1f5c0a06bbbaBehdad Esfahbod bcp2[0], bcp2[1], pt[0], pt[1])) 349b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def _closePath(self): 3503ec6a258238b6068e4eef3fe579f1f5c0a06bbbaBehdad Esfahbod print("closepath") 351b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 352b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 353b369ef33fcbfd39bd5aaf2b079cef1c689095783jvrif __name__ == "__main__": 354b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr pen = _TestPen(None) 355b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr pen.moveTo((0, 0)) 356b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr pen.lineTo((0, 100)) 35723cb20053223695a2fb1dd68920297ed8471d77fjvr pen.curveTo((50, 75), (60, 50), (50, 25), (0, 0)) 358b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr pen.closePath() 359b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 360b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr pen = _TestPen(None) 361934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr # testing the "no on-curve point" scenario 362934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr pen.qCurveTo((0, 0), (0, 100), (100, 100), (100, 0), None) 363b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr pen.closePath() 364