basePen.py revision 82ef2a52c9ab8a88f10daa9dc1050d1cf3901b57
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
20b369ef33fcbfd39bd5aaf2b079cef1c689095783jvrThe AbstractPen class defines the Pen protocol.
21b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr
22b369ef33fcbfd39bd5aaf2b079cef1c689095783jvrThe BasePen class is a base implementation useful for drawing pens. See the
23b369ef33fcbfd39bd5aaf2b079cef1c689095783jvrcomments in that class for which methods you need to override.
24b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr"""
25b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr
26b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr__all__ = ["AbstractPen", "BasePen"]
27b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr
28b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr
29b369ef33fcbfd39bd5aaf2b079cef1c689095783jvrclass AbstractPen:
30b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr
31b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr	def moveTo(self, pt):
32b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		"""Begin a new sub path, set the current point to 'pt'."""
33b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		raise NotImplementedError
34b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr
35b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr	def lineTo(self, pt):
36b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		"""Draw a straight line."""
37b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		raise NotImplementedError
38b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr
39b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr	def curveTo(self, *points):
40b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		"""Draw a curve with an *arbitrary* number of control points.
41b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr
42b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		Let n be the number of control points (which is the number of
43b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		arguments to this call minus 1). If n==2, a plain vanilla cubic
44b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		bezier is drawn. If n==1, we fall back to a quadratic segment and
45b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		if n==0 we draw a straight line. It gets interesting when n>2:
46b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		n-1 PostScript-style cubic segments will be drawn as if it were
47b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		one curve.
48b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr
49b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		The conversion algorithm used for n>2 is inspired by NURB
50b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		splines, and is conceptually equivalent to the TrueType "implied
51b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		points" principle. See also qCurve().
52b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		"""
53b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		raise NotImplementedError
54b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr
55b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr	def qCurveTo(self, *points):
56b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		"""Draw a whole string of quadratic curve segments.
57b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr
58b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		This implements TrueType-style curves, breaking up curves using
59b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		implied points: between each two consequtive off-curve points,
60b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		there is one 'implied' point exactly in the middle between them.
61b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr
62b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		'points' is a sequence of at least two points. Just like with
63b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		any segment drawing function, the first and the last point are
64b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		treated as onCurve, the rest as offCurve.
65b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		"""
66b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		raise NotImplementedError
67b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr
68b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr	def closePath(self):
69b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		"""Close the current sub path."""
70b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		pass
71b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr
72b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr	def addComponent(self, glyphName, transformation):
73b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		"""Add a sub glyph."""
74b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		raise NotImplementedError
75b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr
76b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr
77b369ef33fcbfd39bd5aaf2b079cef1c689095783jvrclass BasePen(AbstractPen):
78b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr
79b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr	"""Base class for drawing pens."""
80b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr
81b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr	def __init__(self, glyphSet):
82b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		self.glyphSet = glyphSet
83b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		self.__currentPoint = None
84b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr
85b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr	# must override
86b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr
87b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr	def _moveTo(self, pt):
88b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		raise NotImplementedError
89b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr
90b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr	def _lineTo(self, pt):
91b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		raise NotImplementedError
92b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr
93b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr	def _curveToOne(self, pt1, pt2, pt3):
94b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		raise NotImplementedError
95b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr
96b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr	# may override
97b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr
98b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr	def _closePath(self):
99b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		pass
100b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr
101b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr	def _qCurveToOne(self, pt1, pt2):
102b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		"""This method implements the basic quadratic curve type. The
103b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		default implementation delegates the work to the cubic curve
104b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		function. Optionally override with a native implementation.
105b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		"""
106b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		pt0x, pt0y = self.__currentPoint
107b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		pt1x, pt1y = pt1
108b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		pt2x, pt2y = pt2
109b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		mid1x = pt0x + 0.66666666666666667 * (pt1x - pt0x)
110b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		mid1y = pt0y + 0.66666666666666667 * (pt1y - pt0y)
111b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		mid2x = pt2x + 0.66666666666666667 * (pt1x - pt2x)
112b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		mid2y = pt2y + 0.66666666666666667 * (pt1y - pt2y)
113b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		self._curveToOne((mid1x, mid1y), (mid2x, mid2y), pt2)
114b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr
115b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr	def addComponent(self, glyphName, transformation):
116b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		"""This default implementation simply transforms the points
117b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		of the base glyph and draws it onto self.
118b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		"""
119b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		from fontTools.pens.transformPen import TransformPen
120b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		tPen = TransformPen(self, transformation)
121b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		self.glyphSet[glyphName].draw(tPen)
122b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr
123b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr	# don't override
124b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr
125b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr	def _getCurrentPoint(self):
126b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		"""Return the current point. This is not part of the public
127b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		interface, yet is useful for subclasses.
128b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		"""
129b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		return self.__currentPoint
130b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr
131b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr	def closePath(self):
132b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		self._closePath()
133b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		self.__currentPoint = None
134b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr
135b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr	def moveTo(self, pt):
136b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		self._moveTo(pt)
137b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		self.__currentPoint = pt
138b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr
139b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr	def lineTo(self, pt):
140b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		self._lineTo(pt)
141b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		self.__currentPoint = pt
142b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr
143b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr	def curveTo(self, *points):
144b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		n = len(points) - 1  # 'n' is the number of control points
145b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		assert n >= 0
146b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		if n == 2:
147b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr			# The common case, we have exactly two BCP's, so this is a standard
148b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr			# cubic bezier.
149b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr			self._curveToOne(*points)
150b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr			self.__currentPoint = points[-1]
151b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		elif n > 2:
152b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr			# n is the number of control points; split curve into n-1 cubic
153b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr			# bezier segments. The algorithm used here is inspired by NURB
154b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr			# splines and the TrueType "implied point" principle, and ensures
155b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr			# the smoothest possible connection between two curve segments,
156b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr			# with no disruption in the curvature. It is practical since it
157b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr			# allows one to construct multiple bezier segments with a much
158b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr			# smaller amount of points.
159b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr			pt1, pt2, pt3 = points[0], None, None
160b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr			for i in range(2, n+1):
161b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr				# calculate points in between control points.
162b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr				nDivisions = min(i, 3, n-i+2)
163b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr				d = float(nDivisions)
164b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr				for j in range(1, nDivisions):
165b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr					factor = j / d
166b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr					temp1 = points[i-1]
167b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr					temp2 = points[i-2]
168b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr					temp = (temp2[0] + factor * (temp1[0] - temp2[0]),
169b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr					        temp2[1] + factor * (temp1[1] - temp2[1]))
170b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr					if pt2 is None:
171b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr						pt2 = temp
172b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr					else:
17382ef2a52c9ab8a88f10daa9dc1050d1cf3901b57jvr						pt3 = (0.5 * (pt2[0] + temp[0]),
17482ef2a52c9ab8a88f10daa9dc1050d1cf3901b57jvr						       0.5 * (pt2[1] + temp[1]))
175b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr						self._curveToOne(pt1, pt2, pt3)
176b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr						pt1, pt2, pt3 = temp, None, None
177b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr			self._curveToOne(pt1, points[-2], points[-1])
178b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr			self.__currentPoint = points[-1]
179b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		elif n == 1:
180b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr			self._qCurveOne(*points)
181b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		elif n == 0:
182b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr			self.lineTo(points[0])
183b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		else:
184b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr			raise AssertionError, "can't get there from here"
185b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr
186b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr	def qCurveTo(self, *points):
187b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		n = len(points) - 1  # 'n' is the number of control points
188b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		assert n >= 0
189b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		if n > 0:
19082ef2a52c9ab8a88f10daa9dc1050d1cf3901b57jvr			# Split the string of points into discrete quadratic curve
19182ef2a52c9ab8a88f10daa9dc1050d1cf3901b57jvr			# segments. Between any two consecutive off-curve points
19282ef2a52c9ab8a88f10daa9dc1050d1cf3901b57jvr			# there's an implied on-curve point exactly in the middle.
19382ef2a52c9ab8a88f10daa9dc1050d1cf3901b57jvr			# This is where the segment splits.
194b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr			_qCurveToOne = self._qCurveToOne
195b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr			for i in range(len(points) - 2):
196b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr				x, y = points[i]
197b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr				nx, ny = points[i+1]
198b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr				impliedPt = (0.5 * (x + nx), 0.5 * (y + ny))
199b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr				_qCurveToOne(points[i], impliedPt)
200b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr				self.__currentPoint = impliedPt
201b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr			_qCurveToOne(points[-2], points[-1])
202b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr			self.__currentPoint = points[-1]
203b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		else:
204b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr			self.lineTo(points[0])
205b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr
206b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr
207b369ef33fcbfd39bd5aaf2b079cef1c689095783jvrclass _TestPen(BasePen):
208b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr	def _moveTo(self, pt):
209b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		print "%s %s moveto" % (pt[0], pt[1])
210b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr	def _lineTo(self, pt):
211b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		print "%s %s lineto" % (pt[0], pt[1])
212b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr	def _curveToOne(self, bcp1, bcp2, pt):
21382ef2a52c9ab8a88f10daa9dc1050d1cf3901b57jvr		print "%s %s %s %s %s %s curveto" % (bcp1[0], bcp1[1],
21482ef2a52c9ab8a88f10daa9dc1050d1cf3901b57jvr				bcp2[0], bcp2[1], pt[0], pt[1])
215b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr	def _closePath(self):
216b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr		print "closepath"
217b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr
218b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr
219b369ef33fcbfd39bd5aaf2b079cef1c689095783jvrif __name__ == "__main__":
220b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr	pen = _TestPen(None)
221b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr	pen.moveTo((0, 0))
222b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr	pen.lineTo((0, 100))
223b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr	pen.qCurveTo((50, 75), (60, 50), (50, 25), (0, 0))
224b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr	pen.closePath()
225b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr
226b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr	pen = _TestPen(None)
227b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr	pen.moveTo((0, 0))
228b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr	pen.lineTo((0, 100))
229b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr	pen.curveTo((50, 75), (60, 50), (50, 25), (0, 0))
230b369ef33fcbfd39bd5aaf2b079cef1c689095783jvr	pen.closePath()
231