1*e1fe3e4aSElliott Hughes"""fontTools.pens.basePen.py -- Tools and base classes to build pen objects. 2*e1fe3e4aSElliott Hughes 3*e1fe3e4aSElliott HughesThe Pen Protocol 4*e1fe3e4aSElliott Hughes 5*e1fe3e4aSElliott HughesA Pen is a kind of object that standardizes the way how to "draw" outlines: 6*e1fe3e4aSElliott Hughesit is a middle man between an outline and a drawing. In other words: 7*e1fe3e4aSElliott Hughesit is an abstraction for drawing outlines, making sure that outline objects 8*e1fe3e4aSElliott Hughesdon't need to know the details about how and where they're being drawn, and 9*e1fe3e4aSElliott Hughesthat drawings don't need to know the details of how outlines are stored. 10*e1fe3e4aSElliott Hughes 11*e1fe3e4aSElliott HughesThe most basic pattern is this:: 12*e1fe3e4aSElliott Hughes 13*e1fe3e4aSElliott Hughes outline.draw(pen) # 'outline' draws itself onto 'pen' 14*e1fe3e4aSElliott Hughes 15*e1fe3e4aSElliott HughesPens can be used to render outlines to the screen, but also to construct 16*e1fe3e4aSElliott Hughesnew outlines. Eg. an outline object can be both a drawable object (it has a 17*e1fe3e4aSElliott Hughesdraw() method) as well as a pen itself: you *build* an outline using pen 18*e1fe3e4aSElliott Hughesmethods. 19*e1fe3e4aSElliott Hughes 20*e1fe3e4aSElliott HughesThe AbstractPen class defines the Pen protocol. It implements almost 21*e1fe3e4aSElliott Hughesnothing (only no-op closePath() and endPath() methods), but is useful 22*e1fe3e4aSElliott Hughesfor documentation purposes. Subclassing it basically tells the reader: 23*e1fe3e4aSElliott Hughes"this class implements the Pen protocol.". An examples of an AbstractPen 24*e1fe3e4aSElliott Hughessubclass is :py:class:`fontTools.pens.transformPen.TransformPen`. 25*e1fe3e4aSElliott Hughes 26*e1fe3e4aSElliott HughesThe BasePen class is a base implementation useful for pens that actually 27*e1fe3e4aSElliott Hughesdraw (for example a pen renders outlines using a native graphics engine). 28*e1fe3e4aSElliott HughesBasePen contains a lot of base functionality, making it very easy to build 29*e1fe3e4aSElliott Hughesa pen that fully conforms to the pen protocol. Note that if you subclass 30*e1fe3e4aSElliott HughesBasePen, you *don't* override moveTo(), lineTo(), etc., but _moveTo(), 31*e1fe3e4aSElliott Hughes_lineTo(), etc. See the BasePen doc string for details. Examples of 32*e1fe3e4aSElliott HughesBasePen subclasses are fontTools.pens.boundsPen.BoundsPen and 33*e1fe3e4aSElliott HughesfontTools.pens.cocoaPen.CocoaPen. 34*e1fe3e4aSElliott Hughes 35*e1fe3e4aSElliott HughesCoordinates are usually expressed as (x, y) tuples, but generally any 36*e1fe3e4aSElliott Hughessequence of length 2 will do. 37*e1fe3e4aSElliott Hughes""" 38*e1fe3e4aSElliott Hughes 39*e1fe3e4aSElliott Hughesfrom typing import Tuple, Dict 40*e1fe3e4aSElliott Hughes 41*e1fe3e4aSElliott Hughesfrom fontTools.misc.loggingTools import LogMixin 42*e1fe3e4aSElliott Hughesfrom fontTools.misc.transform import DecomposedTransform 43*e1fe3e4aSElliott Hughes 44*e1fe3e4aSElliott Hughes__all__ = [ 45*e1fe3e4aSElliott Hughes "AbstractPen", 46*e1fe3e4aSElliott Hughes "NullPen", 47*e1fe3e4aSElliott Hughes "BasePen", 48*e1fe3e4aSElliott Hughes "PenError", 49*e1fe3e4aSElliott Hughes "decomposeSuperBezierSegment", 50*e1fe3e4aSElliott Hughes "decomposeQuadraticSegment", 51*e1fe3e4aSElliott Hughes] 52*e1fe3e4aSElliott Hughes 53*e1fe3e4aSElliott Hughes 54*e1fe3e4aSElliott Hughesclass PenError(Exception): 55*e1fe3e4aSElliott Hughes """Represents an error during penning.""" 56*e1fe3e4aSElliott Hughes 57*e1fe3e4aSElliott Hughes 58*e1fe3e4aSElliott Hughesclass OpenContourError(PenError): 59*e1fe3e4aSElliott Hughes pass 60*e1fe3e4aSElliott Hughes 61*e1fe3e4aSElliott Hughes 62*e1fe3e4aSElliott Hughesclass AbstractPen: 63*e1fe3e4aSElliott Hughes def moveTo(self, pt: Tuple[float, float]) -> None: 64*e1fe3e4aSElliott Hughes """Begin a new sub path, set the current point to 'pt'. You must 65*e1fe3e4aSElliott Hughes end each sub path with a call to pen.closePath() or pen.endPath(). 66*e1fe3e4aSElliott Hughes """ 67*e1fe3e4aSElliott Hughes raise NotImplementedError 68*e1fe3e4aSElliott Hughes 69*e1fe3e4aSElliott Hughes def lineTo(self, pt: Tuple[float, float]) -> None: 70*e1fe3e4aSElliott Hughes """Draw a straight line from the current point to 'pt'.""" 71*e1fe3e4aSElliott Hughes raise NotImplementedError 72*e1fe3e4aSElliott Hughes 73*e1fe3e4aSElliott Hughes def curveTo(self, *points: Tuple[float, float]) -> None: 74*e1fe3e4aSElliott Hughes """Draw a cubic bezier with an arbitrary number of control points. 75*e1fe3e4aSElliott Hughes 76*e1fe3e4aSElliott Hughes The last point specified is on-curve, all others are off-curve 77*e1fe3e4aSElliott Hughes (control) points. If the number of control points is > 2, the 78*e1fe3e4aSElliott Hughes segment is split into multiple bezier segments. This works 79*e1fe3e4aSElliott Hughes like this: 80*e1fe3e4aSElliott Hughes 81*e1fe3e4aSElliott Hughes Let n be the number of control points (which is the number of 82*e1fe3e4aSElliott Hughes arguments to this call minus 1). If n==2, a plain vanilla cubic 83*e1fe3e4aSElliott Hughes bezier is drawn. If n==1, we fall back to a quadratic segment and 84*e1fe3e4aSElliott Hughes if n==0 we draw a straight line. It gets interesting when n>2: 85*e1fe3e4aSElliott Hughes n-1 PostScript-style cubic segments will be drawn as if it were 86*e1fe3e4aSElliott Hughes one curve. See decomposeSuperBezierSegment(). 87*e1fe3e4aSElliott Hughes 88*e1fe3e4aSElliott Hughes The conversion algorithm used for n>2 is inspired by NURB 89*e1fe3e4aSElliott Hughes splines, and is conceptually equivalent to the TrueType "implied 90*e1fe3e4aSElliott Hughes points" principle. See also decomposeQuadraticSegment(). 91*e1fe3e4aSElliott Hughes """ 92*e1fe3e4aSElliott Hughes raise NotImplementedError 93*e1fe3e4aSElliott Hughes 94*e1fe3e4aSElliott Hughes def qCurveTo(self, *points: Tuple[float, float]) -> None: 95*e1fe3e4aSElliott Hughes """Draw a whole string of quadratic curve segments. 96*e1fe3e4aSElliott Hughes 97*e1fe3e4aSElliott Hughes The last point specified is on-curve, all others are off-curve 98*e1fe3e4aSElliott Hughes points. 99*e1fe3e4aSElliott Hughes 100*e1fe3e4aSElliott Hughes This method implements TrueType-style curves, breaking up curves 101*e1fe3e4aSElliott Hughes using 'implied points': between each two consequtive off-curve points, 102*e1fe3e4aSElliott Hughes there is one implied point exactly in the middle between them. See 103*e1fe3e4aSElliott Hughes also decomposeQuadraticSegment(). 104*e1fe3e4aSElliott Hughes 105*e1fe3e4aSElliott Hughes The last argument (normally the on-curve point) may be None. 106*e1fe3e4aSElliott Hughes This is to support contours that have NO on-curve points (a rarely 107*e1fe3e4aSElliott Hughes seen feature of TrueType outlines). 108*e1fe3e4aSElliott Hughes """ 109*e1fe3e4aSElliott Hughes raise NotImplementedError 110*e1fe3e4aSElliott Hughes 111*e1fe3e4aSElliott Hughes def closePath(self) -> None: 112*e1fe3e4aSElliott Hughes """Close the current sub path. You must call either pen.closePath() 113*e1fe3e4aSElliott Hughes or pen.endPath() after each sub path. 114*e1fe3e4aSElliott Hughes """ 115*e1fe3e4aSElliott Hughes pass 116*e1fe3e4aSElliott Hughes 117*e1fe3e4aSElliott Hughes def endPath(self) -> None: 118*e1fe3e4aSElliott Hughes """End the current sub path, but don't close it. You must call 119*e1fe3e4aSElliott Hughes either pen.closePath() or pen.endPath() after each sub path. 120*e1fe3e4aSElliott Hughes """ 121*e1fe3e4aSElliott Hughes pass 122*e1fe3e4aSElliott Hughes 123*e1fe3e4aSElliott Hughes def addComponent( 124*e1fe3e4aSElliott Hughes self, 125*e1fe3e4aSElliott Hughes glyphName: str, 126*e1fe3e4aSElliott Hughes transformation: Tuple[float, float, float, float, float, float], 127*e1fe3e4aSElliott Hughes ) -> None: 128*e1fe3e4aSElliott Hughes """Add a sub glyph. The 'transformation' argument must be a 6-tuple 129*e1fe3e4aSElliott Hughes containing an affine transformation, or a Transform object from the 130*e1fe3e4aSElliott Hughes fontTools.misc.transform module. More precisely: it should be a 131*e1fe3e4aSElliott Hughes sequence containing 6 numbers. 132*e1fe3e4aSElliott Hughes """ 133*e1fe3e4aSElliott Hughes raise NotImplementedError 134*e1fe3e4aSElliott Hughes 135*e1fe3e4aSElliott Hughes def addVarComponent( 136*e1fe3e4aSElliott Hughes self, 137*e1fe3e4aSElliott Hughes glyphName: str, 138*e1fe3e4aSElliott Hughes transformation: DecomposedTransform, 139*e1fe3e4aSElliott Hughes location: Dict[str, float], 140*e1fe3e4aSElliott Hughes ) -> None: 141*e1fe3e4aSElliott Hughes """Add a VarComponent sub glyph. The 'transformation' argument 142*e1fe3e4aSElliott Hughes must be a DecomposedTransform from the fontTools.misc.transform module, 143*e1fe3e4aSElliott Hughes and the 'location' argument must be a dictionary mapping axis tags 144*e1fe3e4aSElliott Hughes to their locations. 145*e1fe3e4aSElliott Hughes """ 146*e1fe3e4aSElliott Hughes # GlyphSet decomposes for us 147*e1fe3e4aSElliott Hughes raise AttributeError 148*e1fe3e4aSElliott Hughes 149*e1fe3e4aSElliott Hughes 150*e1fe3e4aSElliott Hughesclass NullPen(AbstractPen): 151*e1fe3e4aSElliott Hughes """A pen that does nothing.""" 152*e1fe3e4aSElliott Hughes 153*e1fe3e4aSElliott Hughes def moveTo(self, pt): 154*e1fe3e4aSElliott Hughes pass 155*e1fe3e4aSElliott Hughes 156*e1fe3e4aSElliott Hughes def lineTo(self, pt): 157*e1fe3e4aSElliott Hughes pass 158*e1fe3e4aSElliott Hughes 159*e1fe3e4aSElliott Hughes def curveTo(self, *points): 160*e1fe3e4aSElliott Hughes pass 161*e1fe3e4aSElliott Hughes 162*e1fe3e4aSElliott Hughes def qCurveTo(self, *points): 163*e1fe3e4aSElliott Hughes pass 164*e1fe3e4aSElliott Hughes 165*e1fe3e4aSElliott Hughes def closePath(self): 166*e1fe3e4aSElliott Hughes pass 167*e1fe3e4aSElliott Hughes 168*e1fe3e4aSElliott Hughes def endPath(self): 169*e1fe3e4aSElliott Hughes pass 170*e1fe3e4aSElliott Hughes 171*e1fe3e4aSElliott Hughes def addComponent(self, glyphName, transformation): 172*e1fe3e4aSElliott Hughes pass 173*e1fe3e4aSElliott Hughes 174*e1fe3e4aSElliott Hughes def addVarComponent(self, glyphName, transformation, location): 175*e1fe3e4aSElliott Hughes pass 176*e1fe3e4aSElliott Hughes 177*e1fe3e4aSElliott Hughes 178*e1fe3e4aSElliott Hughesclass LoggingPen(LogMixin, AbstractPen): 179*e1fe3e4aSElliott Hughes """A pen with a ``log`` property (see fontTools.misc.loggingTools.LogMixin)""" 180*e1fe3e4aSElliott Hughes 181*e1fe3e4aSElliott Hughes pass 182*e1fe3e4aSElliott Hughes 183*e1fe3e4aSElliott Hughes 184*e1fe3e4aSElliott Hughesclass MissingComponentError(KeyError): 185*e1fe3e4aSElliott Hughes """Indicates a component pointing to a non-existent glyph in the glyphset.""" 186*e1fe3e4aSElliott Hughes 187*e1fe3e4aSElliott Hughes 188*e1fe3e4aSElliott Hughesclass DecomposingPen(LoggingPen): 189*e1fe3e4aSElliott Hughes """Implements a 'addComponent' method that decomposes components 190*e1fe3e4aSElliott Hughes (i.e. draws them onto self as simple contours). 191*e1fe3e4aSElliott Hughes It can also be used as a mixin class (e.g. see ContourRecordingPen). 192*e1fe3e4aSElliott Hughes 193*e1fe3e4aSElliott Hughes You must override moveTo, lineTo, curveTo and qCurveTo. You may 194*e1fe3e4aSElliott Hughes additionally override closePath, endPath and addComponent. 195*e1fe3e4aSElliott Hughes 196*e1fe3e4aSElliott Hughes By default a warning message is logged when a base glyph is missing; 197*e1fe3e4aSElliott Hughes set the class variable ``skipMissingComponents`` to False if you want 198*e1fe3e4aSElliott Hughes to raise a :class:`MissingComponentError` exception. 199*e1fe3e4aSElliott Hughes """ 200*e1fe3e4aSElliott Hughes 201*e1fe3e4aSElliott Hughes skipMissingComponents = True 202*e1fe3e4aSElliott Hughes 203*e1fe3e4aSElliott Hughes def __init__(self, glyphSet): 204*e1fe3e4aSElliott Hughes """Takes a single 'glyphSet' argument (dict), in which the glyphs 205*e1fe3e4aSElliott Hughes that are referenced as components are looked up by their name. 206*e1fe3e4aSElliott Hughes """ 207*e1fe3e4aSElliott Hughes super(DecomposingPen, self).__init__() 208*e1fe3e4aSElliott Hughes self.glyphSet = glyphSet 209*e1fe3e4aSElliott Hughes 210*e1fe3e4aSElliott Hughes def addComponent(self, glyphName, transformation): 211*e1fe3e4aSElliott Hughes """Transform the points of the base glyph and draw it onto self.""" 212*e1fe3e4aSElliott Hughes from fontTools.pens.transformPen import TransformPen 213*e1fe3e4aSElliott Hughes 214*e1fe3e4aSElliott Hughes try: 215*e1fe3e4aSElliott Hughes glyph = self.glyphSet[glyphName] 216*e1fe3e4aSElliott Hughes except KeyError: 217*e1fe3e4aSElliott Hughes if not self.skipMissingComponents: 218*e1fe3e4aSElliott Hughes raise MissingComponentError(glyphName) 219*e1fe3e4aSElliott Hughes self.log.warning("glyph '%s' is missing from glyphSet; skipped" % glyphName) 220*e1fe3e4aSElliott Hughes else: 221*e1fe3e4aSElliott Hughes tPen = TransformPen(self, transformation) 222*e1fe3e4aSElliott Hughes glyph.draw(tPen) 223*e1fe3e4aSElliott Hughes 224*e1fe3e4aSElliott Hughes def addVarComponent(self, glyphName, transformation, location): 225*e1fe3e4aSElliott Hughes # GlyphSet decomposes for us 226*e1fe3e4aSElliott Hughes raise AttributeError 227*e1fe3e4aSElliott Hughes 228*e1fe3e4aSElliott Hughes 229*e1fe3e4aSElliott Hughesclass BasePen(DecomposingPen): 230*e1fe3e4aSElliott Hughes """Base class for drawing pens. You must override _moveTo, _lineTo and 231*e1fe3e4aSElliott Hughes _curveToOne. You may additionally override _closePath, _endPath, 232*e1fe3e4aSElliott Hughes addComponent, addVarComponent, and/or _qCurveToOne. You should not 233*e1fe3e4aSElliott Hughes override any other methods. 234*e1fe3e4aSElliott Hughes """ 235*e1fe3e4aSElliott Hughes 236*e1fe3e4aSElliott Hughes def __init__(self, glyphSet=None): 237*e1fe3e4aSElliott Hughes super(BasePen, self).__init__(glyphSet) 238*e1fe3e4aSElliott Hughes self.__currentPoint = None 239*e1fe3e4aSElliott Hughes 240*e1fe3e4aSElliott Hughes # must override 241*e1fe3e4aSElliott Hughes 242*e1fe3e4aSElliott Hughes def _moveTo(self, pt): 243*e1fe3e4aSElliott Hughes raise NotImplementedError 244*e1fe3e4aSElliott Hughes 245*e1fe3e4aSElliott Hughes def _lineTo(self, pt): 246*e1fe3e4aSElliott Hughes raise NotImplementedError 247*e1fe3e4aSElliott Hughes 248*e1fe3e4aSElliott Hughes def _curveToOne(self, pt1, pt2, pt3): 249*e1fe3e4aSElliott Hughes raise NotImplementedError 250*e1fe3e4aSElliott Hughes 251*e1fe3e4aSElliott Hughes # may override 252*e1fe3e4aSElliott Hughes 253*e1fe3e4aSElliott Hughes def _closePath(self): 254*e1fe3e4aSElliott Hughes pass 255*e1fe3e4aSElliott Hughes 256*e1fe3e4aSElliott Hughes def _endPath(self): 257*e1fe3e4aSElliott Hughes pass 258*e1fe3e4aSElliott Hughes 259*e1fe3e4aSElliott Hughes def _qCurveToOne(self, pt1, pt2): 260*e1fe3e4aSElliott Hughes """This method implements the basic quadratic curve type. The 261*e1fe3e4aSElliott Hughes default implementation delegates the work to the cubic curve 262*e1fe3e4aSElliott Hughes function. Optionally override with a native implementation. 263*e1fe3e4aSElliott Hughes """ 264*e1fe3e4aSElliott Hughes pt0x, pt0y = self.__currentPoint 265*e1fe3e4aSElliott Hughes pt1x, pt1y = pt1 266*e1fe3e4aSElliott Hughes pt2x, pt2y = pt2 267*e1fe3e4aSElliott Hughes mid1x = pt0x + 0.66666666666666667 * (pt1x - pt0x) 268*e1fe3e4aSElliott Hughes mid1y = pt0y + 0.66666666666666667 * (pt1y - pt0y) 269*e1fe3e4aSElliott Hughes mid2x = pt2x + 0.66666666666666667 * (pt1x - pt2x) 270*e1fe3e4aSElliott Hughes mid2y = pt2y + 0.66666666666666667 * (pt1y - pt2y) 271*e1fe3e4aSElliott Hughes self._curveToOne((mid1x, mid1y), (mid2x, mid2y), pt2) 272*e1fe3e4aSElliott Hughes 273*e1fe3e4aSElliott Hughes # don't override 274*e1fe3e4aSElliott Hughes 275*e1fe3e4aSElliott Hughes def _getCurrentPoint(self): 276*e1fe3e4aSElliott Hughes """Return the current point. This is not part of the public 277*e1fe3e4aSElliott Hughes interface, yet is useful for subclasses. 278*e1fe3e4aSElliott Hughes """ 279*e1fe3e4aSElliott Hughes return self.__currentPoint 280*e1fe3e4aSElliott Hughes 281*e1fe3e4aSElliott Hughes def closePath(self): 282*e1fe3e4aSElliott Hughes self._closePath() 283*e1fe3e4aSElliott Hughes self.__currentPoint = None 284*e1fe3e4aSElliott Hughes 285*e1fe3e4aSElliott Hughes def endPath(self): 286*e1fe3e4aSElliott Hughes self._endPath() 287*e1fe3e4aSElliott Hughes self.__currentPoint = None 288*e1fe3e4aSElliott Hughes 289*e1fe3e4aSElliott Hughes def moveTo(self, pt): 290*e1fe3e4aSElliott Hughes self._moveTo(pt) 291*e1fe3e4aSElliott Hughes self.__currentPoint = pt 292*e1fe3e4aSElliott Hughes 293*e1fe3e4aSElliott Hughes def lineTo(self, pt): 294*e1fe3e4aSElliott Hughes self._lineTo(pt) 295*e1fe3e4aSElliott Hughes self.__currentPoint = pt 296*e1fe3e4aSElliott Hughes 297*e1fe3e4aSElliott Hughes def curveTo(self, *points): 298*e1fe3e4aSElliott Hughes n = len(points) - 1 # 'n' is the number of control points 299*e1fe3e4aSElliott Hughes assert n >= 0 300*e1fe3e4aSElliott Hughes if n == 2: 301*e1fe3e4aSElliott Hughes # The common case, we have exactly two BCP's, so this is a standard 302*e1fe3e4aSElliott Hughes # cubic bezier. Even though decomposeSuperBezierSegment() handles 303*e1fe3e4aSElliott Hughes # this case just fine, we special-case it anyway since it's so 304*e1fe3e4aSElliott Hughes # common. 305*e1fe3e4aSElliott Hughes self._curveToOne(*points) 306*e1fe3e4aSElliott Hughes self.__currentPoint = points[-1] 307*e1fe3e4aSElliott Hughes elif n > 2: 308*e1fe3e4aSElliott Hughes # n is the number of control points; split curve into n-1 cubic 309*e1fe3e4aSElliott Hughes # bezier segments. The algorithm used here is inspired by NURB 310*e1fe3e4aSElliott Hughes # splines and the TrueType "implied point" principle, and ensures 311*e1fe3e4aSElliott Hughes # the smoothest possible connection between two curve segments, 312*e1fe3e4aSElliott Hughes # with no disruption in the curvature. It is practical since it 313*e1fe3e4aSElliott Hughes # allows one to construct multiple bezier segments with a much 314*e1fe3e4aSElliott Hughes # smaller amount of points. 315*e1fe3e4aSElliott Hughes _curveToOne = self._curveToOne 316*e1fe3e4aSElliott Hughes for pt1, pt2, pt3 in decomposeSuperBezierSegment(points): 317*e1fe3e4aSElliott Hughes _curveToOne(pt1, pt2, pt3) 318*e1fe3e4aSElliott Hughes self.__currentPoint = pt3 319*e1fe3e4aSElliott Hughes elif n == 1: 320*e1fe3e4aSElliott Hughes self.qCurveTo(*points) 321*e1fe3e4aSElliott Hughes elif n == 0: 322*e1fe3e4aSElliott Hughes self.lineTo(points[0]) 323*e1fe3e4aSElliott Hughes else: 324*e1fe3e4aSElliott Hughes raise AssertionError("can't get there from here") 325*e1fe3e4aSElliott Hughes 326*e1fe3e4aSElliott Hughes def qCurveTo(self, *points): 327*e1fe3e4aSElliott Hughes n = len(points) - 1 # 'n' is the number of control points 328*e1fe3e4aSElliott Hughes assert n >= 0 329*e1fe3e4aSElliott Hughes if points[-1] is None: 330*e1fe3e4aSElliott Hughes # Special case for TrueType quadratics: it is possible to 331*e1fe3e4aSElliott Hughes # define a contour with NO on-curve points. BasePen supports 332*e1fe3e4aSElliott Hughes # this by allowing the final argument (the expected on-curve 333*e1fe3e4aSElliott Hughes # point) to be None. We simulate the feature by making the implied 334*e1fe3e4aSElliott Hughes # on-curve point between the last and the first off-curve points 335*e1fe3e4aSElliott Hughes # explicit. 336*e1fe3e4aSElliott Hughes x, y = points[-2] # last off-curve point 337*e1fe3e4aSElliott Hughes nx, ny = points[0] # first off-curve point 338*e1fe3e4aSElliott Hughes impliedStartPoint = (0.5 * (x + nx), 0.5 * (y + ny)) 339*e1fe3e4aSElliott Hughes self.__currentPoint = impliedStartPoint 340*e1fe3e4aSElliott Hughes self._moveTo(impliedStartPoint) 341*e1fe3e4aSElliott Hughes points = points[:-1] + (impliedStartPoint,) 342*e1fe3e4aSElliott Hughes if n > 0: 343*e1fe3e4aSElliott Hughes # Split the string of points into discrete quadratic curve 344*e1fe3e4aSElliott Hughes # segments. Between any two consecutive off-curve points 345*e1fe3e4aSElliott Hughes # there's an implied on-curve point exactly in the middle. 346*e1fe3e4aSElliott Hughes # This is where the segment splits. 347*e1fe3e4aSElliott Hughes _qCurveToOne = self._qCurveToOne 348*e1fe3e4aSElliott Hughes for pt1, pt2 in decomposeQuadraticSegment(points): 349*e1fe3e4aSElliott Hughes _qCurveToOne(pt1, pt2) 350*e1fe3e4aSElliott Hughes self.__currentPoint = pt2 351*e1fe3e4aSElliott Hughes else: 352*e1fe3e4aSElliott Hughes self.lineTo(points[0]) 353*e1fe3e4aSElliott Hughes 354*e1fe3e4aSElliott Hughes 355*e1fe3e4aSElliott Hughesdef decomposeSuperBezierSegment(points): 356*e1fe3e4aSElliott Hughes """Split the SuperBezier described by 'points' into a list of regular 357*e1fe3e4aSElliott Hughes bezier segments. The 'points' argument must be a sequence with length 358*e1fe3e4aSElliott Hughes 3 or greater, containing (x, y) coordinates. The last point is the 359*e1fe3e4aSElliott Hughes destination on-curve point, the rest of the points are off-curve points. 360*e1fe3e4aSElliott Hughes The start point should not be supplied. 361*e1fe3e4aSElliott Hughes 362*e1fe3e4aSElliott Hughes This function returns a list of (pt1, pt2, pt3) tuples, which each 363*e1fe3e4aSElliott Hughes specify a regular curveto-style bezier segment. 364*e1fe3e4aSElliott Hughes """ 365*e1fe3e4aSElliott Hughes n = len(points) - 1 366*e1fe3e4aSElliott Hughes assert n > 1 367*e1fe3e4aSElliott Hughes bezierSegments = [] 368*e1fe3e4aSElliott Hughes pt1, pt2, pt3 = points[0], None, None 369*e1fe3e4aSElliott Hughes for i in range(2, n + 1): 370*e1fe3e4aSElliott Hughes # calculate points in between control points. 371*e1fe3e4aSElliott Hughes nDivisions = min(i, 3, n - i + 2) 372*e1fe3e4aSElliott Hughes for j in range(1, nDivisions): 373*e1fe3e4aSElliott Hughes factor = j / nDivisions 374*e1fe3e4aSElliott Hughes temp1 = points[i - 1] 375*e1fe3e4aSElliott Hughes temp2 = points[i - 2] 376*e1fe3e4aSElliott Hughes temp = ( 377*e1fe3e4aSElliott Hughes temp2[0] + factor * (temp1[0] - temp2[0]), 378*e1fe3e4aSElliott Hughes temp2[1] + factor * (temp1[1] - temp2[1]), 379*e1fe3e4aSElliott Hughes ) 380*e1fe3e4aSElliott Hughes if pt2 is None: 381*e1fe3e4aSElliott Hughes pt2 = temp 382*e1fe3e4aSElliott Hughes else: 383*e1fe3e4aSElliott Hughes pt3 = (0.5 * (pt2[0] + temp[0]), 0.5 * (pt2[1] + temp[1])) 384*e1fe3e4aSElliott Hughes bezierSegments.append((pt1, pt2, pt3)) 385*e1fe3e4aSElliott Hughes pt1, pt2, pt3 = temp, None, None 386*e1fe3e4aSElliott Hughes bezierSegments.append((pt1, points[-2], points[-1])) 387*e1fe3e4aSElliott Hughes return bezierSegments 388*e1fe3e4aSElliott Hughes 389*e1fe3e4aSElliott Hughes 390*e1fe3e4aSElliott Hughesdef decomposeQuadraticSegment(points): 391*e1fe3e4aSElliott Hughes """Split the quadratic curve segment described by 'points' into a list 392*e1fe3e4aSElliott Hughes of "atomic" quadratic segments. The 'points' argument must be a sequence 393*e1fe3e4aSElliott Hughes with length 2 or greater, containing (x, y) coordinates. The last point 394*e1fe3e4aSElliott Hughes is the destination on-curve point, the rest of the points are off-curve 395*e1fe3e4aSElliott Hughes points. The start point should not be supplied. 396*e1fe3e4aSElliott Hughes 397*e1fe3e4aSElliott Hughes This function returns a list of (pt1, pt2) tuples, which each specify a 398*e1fe3e4aSElliott Hughes plain quadratic bezier segment. 399*e1fe3e4aSElliott Hughes """ 400*e1fe3e4aSElliott Hughes n = len(points) - 1 401*e1fe3e4aSElliott Hughes assert n > 0 402*e1fe3e4aSElliott Hughes quadSegments = [] 403*e1fe3e4aSElliott Hughes for i in range(n - 1): 404*e1fe3e4aSElliott Hughes x, y = points[i] 405*e1fe3e4aSElliott Hughes nx, ny = points[i + 1] 406*e1fe3e4aSElliott Hughes impliedPt = (0.5 * (x + nx), 0.5 * (y + ny)) 407*e1fe3e4aSElliott Hughes quadSegments.append((points[i], impliedPt)) 408*e1fe3e4aSElliott Hughes quadSegments.append((points[-2], points[-1])) 409*e1fe3e4aSElliott Hughes return quadSegments 410*e1fe3e4aSElliott Hughes 411*e1fe3e4aSElliott Hughes 412*e1fe3e4aSElliott Hughesclass _TestPen(BasePen): 413*e1fe3e4aSElliott Hughes """Test class that prints PostScript to stdout.""" 414*e1fe3e4aSElliott Hughes 415*e1fe3e4aSElliott Hughes def _moveTo(self, pt): 416*e1fe3e4aSElliott Hughes print("%s %s moveto" % (pt[0], pt[1])) 417*e1fe3e4aSElliott Hughes 418*e1fe3e4aSElliott Hughes def _lineTo(self, pt): 419*e1fe3e4aSElliott Hughes print("%s %s lineto" % (pt[0], pt[1])) 420*e1fe3e4aSElliott Hughes 421*e1fe3e4aSElliott Hughes def _curveToOne(self, bcp1, bcp2, pt): 422*e1fe3e4aSElliott Hughes print( 423*e1fe3e4aSElliott Hughes "%s %s %s %s %s %s curveto" 424*e1fe3e4aSElliott Hughes % (bcp1[0], bcp1[1], bcp2[0], bcp2[1], pt[0], pt[1]) 425*e1fe3e4aSElliott Hughes ) 426*e1fe3e4aSElliott Hughes 427*e1fe3e4aSElliott Hughes def _closePath(self): 428*e1fe3e4aSElliott Hughes print("closepath") 429*e1fe3e4aSElliott Hughes 430*e1fe3e4aSElliott Hughes 431*e1fe3e4aSElliott Hughesif __name__ == "__main__": 432*e1fe3e4aSElliott Hughes pen = _TestPen(None) 433*e1fe3e4aSElliott Hughes pen.moveTo((0, 0)) 434*e1fe3e4aSElliott Hughes pen.lineTo((0, 100)) 435*e1fe3e4aSElliott Hughes pen.curveTo((50, 75), (60, 50), (50, 25), (0, 0)) 436*e1fe3e4aSElliott Hughes pen.closePath() 437*e1fe3e4aSElliott Hughes 438*e1fe3e4aSElliott Hughes pen = _TestPen(None) 439*e1fe3e4aSElliott Hughes # testing the "no on-curve point" scenario 440*e1fe3e4aSElliott Hughes pen.qCurveTo((0, 0), (0, 100), (100, 100), (100, 0), None) 441*e1fe3e4aSElliott Hughes pen.closePath() 442