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