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 391ae29591efbb29492ce05378909ccf4028d7c1eeBehdad Esfahbodfrom __future__ import print_function, division, absolute_import 407ed91eca1eaa96b79eae780778e89bb9ec44c1eeBehdad Esfahbodfrom fontTools.misc.py23 import * 4140cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr 42285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod__all__ = ["AbstractPen", "NullPen", "BasePen", 4323cb20053223695a2fb1dd68920297ed8471d77fjvr "decomposeSuperBezierSegment", "decomposeQuadraticSegment"] 44b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 45b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 4691bca4244286fb519c93fe92329da96b0e6f32eejvrclass AbstractPen(object): 47b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 48b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def moveTo(self, pt): 4940cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr """Begin a new sub path, set the current point to 'pt'. You must 5040cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr end each sub path with a call to pen.closePath() or pen.endPath(). 5140cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr """ 52b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr raise NotImplementedError 53b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 54b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def lineTo(self, pt): 5540cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr """Draw a straight line from the current point to 'pt'.""" 56b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr raise NotImplementedError 57b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 58b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def curveTo(self, *points): 59934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr """Draw a cubic bezier with an arbitrary number of control points. 60934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr 61934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr The last point specified is on-curve, all others are off-curve 62934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr (control) points. If the number of control points is > 2, the 63934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr segment is split into multiple bezier segments. This works 64934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr like this: 65b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 66b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr Let n be the number of control points (which is the number of 67b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr arguments to this call minus 1). If n==2, a plain vanilla cubic 68b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr bezier is drawn. If n==1, we fall back to a quadratic segment and 69b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr if n==0 we draw a straight line. It gets interesting when n>2: 70b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr n-1 PostScript-style cubic segments will be drawn as if it were 7123cb20053223695a2fb1dd68920297ed8471d77fjvr one curve. See decomposeSuperBezierSegment(). 72b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 73b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr The conversion algorithm used for n>2 is inspired by NURB 74b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr splines, and is conceptually equivalent to the TrueType "implied 7523cb20053223695a2fb1dd68920297ed8471d77fjvr points" principle. See also decomposeQuadraticSegment(). 76b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr """ 77b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr raise NotImplementedError 78b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 79b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def qCurveTo(self, *points): 80b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr """Draw a whole string of quadratic curve segments. 81b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 82934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr The last point specified is on-curve, all others are off-curve 83934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr points. 84934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr 85934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr This method implements TrueType-style curves, breaking up curves 86934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr using 'implied points': between each two consequtive off-curve points, 8723cb20053223695a2fb1dd68920297ed8471d77fjvr there is one implied point exactly in the middle between them. See 8823cb20053223695a2fb1dd68920297ed8471d77fjvr also decomposeQuadraticSegment(). 89b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 90934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr The last argument (normally the on-curve point) may be None. 91934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr This is to support contours that have NO on-curve points (a rarely 92934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr seen feature of TrueType outlines). 93b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr """ 94b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr raise NotImplementedError 95b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 96b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def closePath(self): 9740cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr """Close the current sub path. You must call either pen.closePath() 9840cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr or pen.endPath() after each sub path. 9940cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr """ 10040cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr pass 10140cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr 10240cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr def endPath(self): 10340cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr """End the current sub path, but don't close it. You must call 10440cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr either pen.closePath() or pen.endPath() after each sub path. 10540cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr """ 106b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr pass 107b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 108b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def addComponent(self, glyphName, transformation): 109934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr """Add a sub glyph. The 'transformation' argument must be a 6-tuple 110934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr containing an affine transformation, or a Transform object from the 111934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr fontTools.misc.transform module. More precisely: it should be a 112934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr sequence containing 6 numbers. 113934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr """ 114b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr raise NotImplementedError 115b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 116b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 117285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbodclass NullPen(object): 118285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod 119285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod """A pen that does nothing. 120285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod """ 121285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod 122285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod def moveTo(self, pt): 123285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod pass 124285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod 125285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod def lineTo(self, pt): 126285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod pass 127285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod 128285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod def curveTo(self, *points): 129285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod pass 130285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod 131285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod def qCurveTo(self, *points): 132285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod pass 133285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod 134285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod def closePath(self): 135285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod pass 136285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod 137285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod def endPath(self): 138285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod pass 139285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod 140285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod def addComponent(self, glyphName, transformation): 141285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod pass 142285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod 143285d7b81d3a1d9d060864438580f05c2b44366ffBehdad Esfahbod 144b369ef33fcbfd39bd5aaf2b079cef1c689095783jvrclass BasePen(AbstractPen): 145b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 14640cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr """Base class for drawing pens. You must override _moveTo, _lineTo and 14740cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr _curveToOne. You may additionally override _closePath, _endPath, 14840cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr addComponent and/or _qCurveToOne. You should not override any other 14940cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr methods. 15040cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr """ 151b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 152b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def __init__(self, glyphSet): 153b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self.glyphSet = glyphSet 154b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self.__currentPoint = None 155b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 156b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr # must override 157b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 158b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def _moveTo(self, pt): 159b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr raise NotImplementedError 160b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 161b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def _lineTo(self, pt): 162b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr raise NotImplementedError 163b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 164b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def _curveToOne(self, pt1, pt2, pt3): 165b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr raise NotImplementedError 166b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 167b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr # may override 168b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 169b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def _closePath(self): 170b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr pass 171b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 17240cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr def _endPath(self): 17340cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr pass 17440cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr 175b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def _qCurveToOne(self, pt1, pt2): 176b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr """This method implements the basic quadratic curve type. The 177b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr default implementation delegates the work to the cubic curve 178b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr function. Optionally override with a native implementation. 179b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr """ 180b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr pt0x, pt0y = self.__currentPoint 181b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr pt1x, pt1y = pt1 182b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr pt2x, pt2y = pt2 183b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr mid1x = pt0x + 0.66666666666666667 * (pt1x - pt0x) 184b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr mid1y = pt0y + 0.66666666666666667 * (pt1y - pt0y) 185b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr mid2x = pt2x + 0.66666666666666667 * (pt1x - pt2x) 186b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr mid2y = pt2y + 0.66666666666666667 * (pt1y - pt2y) 187b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self._curveToOne((mid1x, mid1y), (mid2x, mid2y), pt2) 188b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 189b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def addComponent(self, glyphName, transformation): 190b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr """This default implementation simply transforms the points 191b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr of the base glyph and draws it onto self. 192b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr """ 193b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr from fontTools.pens.transformPen import TransformPen 194a5c92986dd7421474fd2eb7c86943d1921150978jvr try: 195a5c92986dd7421474fd2eb7c86943d1921150978jvr glyph = self.glyphSet[glyphName] 196a5c92986dd7421474fd2eb7c86943d1921150978jvr except KeyError: 197a5c92986dd7421474fd2eb7c86943d1921150978jvr pass 198a5c92986dd7421474fd2eb7c86943d1921150978jvr else: 1992e4cc02ca31c43eafb6f752e44dbca9b004a3a2fjvr tPen = TransformPen(self, transformation) 2002e4cc02ca31c43eafb6f752e44dbca9b004a3a2fjvr glyph.draw(tPen) 201b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 202b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr # don't override 203b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 204b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def _getCurrentPoint(self): 205b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr """Return the current point. This is not part of the public 206b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr interface, yet is useful for subclasses. 207b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr """ 208b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr return self.__currentPoint 209b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 210b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def closePath(self): 211b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self._closePath() 212b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self.__currentPoint = None 213b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 21440cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr def endPath(self): 21540cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr self._endPath() 21640cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr self.__currentPoint = None 21740cde70f1640b8a25655fba4ee3ce7a9d5ca962ejvr 218b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def moveTo(self, pt): 219b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self._moveTo(pt) 220b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self.__currentPoint = pt 221b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 222b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def lineTo(self, pt): 223b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self._lineTo(pt) 224b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self.__currentPoint = pt 225b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 226b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def curveTo(self, *points): 227b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr n = len(points) - 1 # 'n' is the number of control points 228b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr assert n >= 0 229b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr if n == 2: 230b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr # The common case, we have exactly two BCP's, so this is a standard 23123cb20053223695a2fb1dd68920297ed8471d77fjvr # cubic bezier. Even though decomposeSuperBezierSegment() handles 23223cb20053223695a2fb1dd68920297ed8471d77fjvr # this case just fine, we special-case it anyway since it's so 23323cb20053223695a2fb1dd68920297ed8471d77fjvr # common. 234b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self._curveToOne(*points) 235b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self.__currentPoint = points[-1] 236b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr elif n > 2: 237b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr # n is the number of control points; split curve into n-1 cubic 238b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr # bezier segments. The algorithm used here is inspired by NURB 239b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr # splines and the TrueType "implied point" principle, and ensures 240b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr # the smoothest possible connection between two curve segments, 241b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr # with no disruption in the curvature. It is practical since it 242b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr # allows one to construct multiple bezier segments with a much 243b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr # smaller amount of points. 24423cb20053223695a2fb1dd68920297ed8471d77fjvr _curveToOne = self._curveToOne 24523cb20053223695a2fb1dd68920297ed8471d77fjvr for pt1, pt2, pt3 in decomposeSuperBezierSegment(points): 24623cb20053223695a2fb1dd68920297ed8471d77fjvr _curveToOne(pt1, pt2, pt3) 24723cb20053223695a2fb1dd68920297ed8471d77fjvr self.__currentPoint = pt3 248b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr elif n == 1: 24949b5521f2edcbf91651cae78ef3233bf16bb48d2jvr self.qCurveTo(*points) 250b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr elif n == 0: 251b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self.lineTo(points[0]) 252b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr else: 253cd5aad92f23737ff93a110d5c73d624658a28da8Behdad Esfahbod raise AssertionError("can't get there from here") 254b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 255b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr def qCurveTo(self, *points): 256b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr n = len(points) - 1 # 'n' is the number of control points 257b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr assert n >= 0 258934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr if points[-1] is None: 259934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr # Special case for TrueType quadratics: it is possible to 260934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr # define a contour with NO on-curve points. BasePen supports 261934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr # this by allowing the final argument (the expected on-curve 262934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr # point) to be None. We simulate the feature by making the implied 263934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr # on-curve point between the last and the first off-curve points 264934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr # explicit. 265934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr x, y = points[-2] # last off-curve point 266934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr nx, ny = points[0] # first off-curve point 267934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr impliedStartPoint = (0.5 * (x + nx), 0.5 * (y + ny)) 268934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr self.__currentPoint = impliedStartPoint 269934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr self._moveTo(impliedStartPoint) 270934fe5fb8fd2ef80d9ec014513ca5e3a0fb9e325jvr points = points[:-1] + (impliedStartPoint,) 271b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr if n > 0: 27282ef2a52c9ab8a88f10daa9dc1050d1cf3901b57jvr # Split the string of points into discrete quadratic curve 27382ef2a52c9ab8a88f10daa9dc1050d1cf3901b57jvr # segments. Between any two consecutive off-curve points 27482ef2a52c9ab8a88f10daa9dc1050d1cf3901b57jvr # there's an implied on-curve point exactly in the middle. 27582ef2a52c9ab8a88f10daa9dc1050d1cf3901b57jvr # This is where the segment splits. 276b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr _qCurveToOne = self._qCurveToOne 27723cb20053223695a2fb1dd68920297ed8471d77fjvr for pt1, pt2 in decomposeQuadraticSegment(points): 27823cb20053223695a2fb1dd68920297ed8471d77fjvr _qCurveToOne(pt1, pt2) 27923cb20053223695a2fb1dd68920297ed8471d77fjvr self.__currentPoint = pt2 280b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr else: 281b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr self.lineTo(points[0]) 282b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 283b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr 28423cb20053223695a2fb1dd68920297ed8471d77fjvrdef decomposeSuperBezierSegment(points): 28523cb20053223695a2fb1dd68920297ed8471d77fjvr """Split the SuperBezier described by 'points' into a list of regular 28623cb20053223695a2fb1dd68920297ed8471d77fjvr bezier segments. The 'points' argument must be a sequence with length 28723cb20053223695a2fb1dd68920297ed8471d77fjvr 3 or greater, containing (x, y) coordinates. The last point is the 28823cb20053223695a2fb1dd68920297ed8471d77fjvr destination on-curve point, the rest of the points are off-curve points. 28923cb20053223695a2fb1dd68920297ed8471d77fjvr The start point should not be supplied. 29023cb20053223695a2fb1dd68920297ed8471d77fjvr 29123cb20053223695a2fb1dd68920297ed8471d77fjvr This function returns a list of (pt1, pt2, pt3) tuples, which each 29223cb20053223695a2fb1dd68920297ed8471d77fjvr specify a regular curveto-style bezier segment. 29323cb20053223695a2fb1dd68920297ed8471d77fjvr """ 29423cb20053223695a2fb1dd68920297ed8471d77fjvr n = len(points) - 1 29523cb20053223695a2fb1dd68920297ed8471d77fjvr assert n > 1 29623cb20053223695a2fb1dd68920297ed8471d77fjvr bezierSegments = [] 29723cb20053223695a2fb1dd68920297ed8471d77fjvr pt1, pt2, pt3 = points[0], None, None 29823cb20053223695a2fb1dd68920297ed8471d77fjvr for i in range(2, n+1): 29923cb20053223695a2fb1dd68920297ed8471d77fjvr # calculate points in between control points. 30023cb20053223695a2fb1dd68920297ed8471d77fjvr nDivisions = min(i, 3, n-i+2) 30123cb20053223695a2fb1dd68920297ed8471d77fjvr for j in range(1, nDivisions): 30232c10eecffb4923e0721c395e4b80fb732543f18Behdad Esfahbod factor = j / nDivisions 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