summaryrefslogtreecommitdiff
path: root/misc/pylib/robofab/pens/pointPen.py
blob: dc81df38e9ba5c8352d45845fb87882bda6d4de5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
__all__ = ["AbstractPointPen", "BasePointToSegmentPen", "PrintingPointPen",
           "PrintingSegmentPen", "SegmentPrintingPointPen"]


class AbstractPointPen:

	def beginPath(self):
		"""Start a new sub path."""
		raise NotImplementedError

	def endPath(self):
		"""End the current sub path."""
		raise NotImplementedError

	def addPoint(self, pt, segmentType=None, smooth=False, name=None, **kwargs):
		"""Add a point to the current sub path."""
		raise NotImplementedError

	def addComponent(self, baseGlyphName, transformation):
		"""Add a sub glyph."""
		raise NotImplementedError


class BasePointToSegmentPen(AbstractPointPen):

	"""Base class for retrieving the outline in a segment-oriented
	way. The PointPen protocol is simple yet also a little tricky,
	so when you need an outline presented as segments but you have
	as points, do use this base implementation as it properly takes
	care of all the edge cases.
	"""

	def __init__(self):
		self.currentPath = None

	def beginPath(self):
		assert self.currentPath is None
		self.currentPath = []

	def _flushContour(self, segments):
		"""Override this method.

		It will be called for each non-empty sub path with a list
		of segments: the 'segments' argument.

		The segments list contains tuples of length 2:
			(segmentType, points)

		segmentType is one of "move", "line", "curve" or "qcurve".
		"move" may only occur as the first segment, and it signifies
		an OPEN path. A CLOSED path does NOT start with a "move", in
		fact it will not contain a "move" at ALL.

		The 'points' field in the 2-tuple is a list of point info
		tuples. The list has 1 or more items, a point tuple has
		four items:
			(point, smooth, name, kwargs)
		'point' is an (x, y) coordinate pair.

		For a closed path, the initial moveTo point is defined as
		the last point of the last segment.

		The 'points' list of "move" and "line" segments always contains
		exactly one point tuple.
		"""
		raise NotImplementedError

	def endPath(self):
		assert self.currentPath is not None
		points = self.currentPath
		self.currentPath = None
		if not points:
			return
		if len(points) == 1:
			# Not much more we can do than output a single move segment.
			pt, segmentType, smooth, name, kwargs = points[0]
			segments = [("move", [(pt, smooth, name, kwargs)])]
			self._flushContour(segments)
			return
		segments = []
		if points[0][1] == "move":
			# It's an open contour, insert a "move" segment for the first
			# point and remove that first point from the point list.
			pt, segmentType, smooth, name, kwargs = points[0]
			segments.append(("move", [(pt, smooth, name, kwargs)]))
			points.pop(0)
		else:
			# It's a closed contour. Locate the first on-curve point, and
			# rotate the point list so that it _ends_ with an on-curve
			# point.
			firstOnCurve = None
			for i in range(len(points)):
				segmentType = points[i][1]
				if segmentType is not None:
					firstOnCurve = i
					break
			if firstOnCurve is None:
				# Special case for quadratics: a contour with no on-curve
				# points. Add a "None" point. (See also the Pen protocol's
				# qCurveTo() method and fontTools.pens.basePen.py.)
				points.append((None, "qcurve", None, None, None))
			else:
				points = points[firstOnCurve+1:] + points[:firstOnCurve+1]

		currentSegment = []
		for pt, segmentType, smooth, name, kwargs in points:
			currentSegment.append((pt, smooth, name, kwargs))
			if segmentType is None:
				continue
			segments.append((segmentType, currentSegment))
			currentSegment = []

		self._flushContour(segments)

	def addPoint(self, pt, segmentType=None, smooth=False, name=None, **kwargs):
		self.currentPath.append((pt, segmentType, smooth, name, kwargs))


class PrintingPointPen(AbstractPointPen):
	def __init__(self):
		self.havePath = False
	def beginPath(self):
		self.havePath = True
		print "pen.beginPath()"
	def endPath(self):
		self.havePath = False
		print "pen.endPath()"
	def addPoint(self, pt, segmentType=None, smooth=False, name=None, **kwargs):
		assert self.havePath
		args = ["(%s, %s)" % (pt[0], pt[1])]
		if segmentType is not None:
			args.append("segmentType=%r" % segmentType)
		if smooth:
			args.append("smooth=True")
		if name is not None:
			args.append("name=%r" % name)
		if kwargs:
			args.append("**%s" % kwargs)
		print "pen.addPoint(%s)" % ", ".join(args)
	def addComponent(self, baseGlyphName, transformation):
		assert not self.havePath
		print "pen.addComponent(%r, %s)" % (baseGlyphName, tuple(transformation))


from fontTools.pens.basePen import AbstractPen

class PrintingSegmentPen(AbstractPen):
	def moveTo(self, pt):
		print "pen.moveTo(%s)" % (pt,)
	def lineTo(self, pt):
		print "pen.lineTo(%s)" % (pt,)
	def curveTo(self, *pts):
		print "pen.curveTo%s" % (pts,)
	def qCurveTo(self, *pts):
		print "pen.qCurveTo%s" % (pts,)
	def closePath(self):
		print "pen.closePath()"
	def endPath(self):
		print "pen.endPath()"
	def addComponent(self, baseGlyphName, transformation):
		print "pen.addComponent(%r, %s)" % (baseGlyphName, tuple(transformation))


class SegmentPrintingPointPen(BasePointToSegmentPen):
	def _flushContour(self, segments):
		from pprint import pprint
		pprint(segments)


if __name__ == "__main__":
	p = SegmentPrintingPointPen()
	from robofab.test.test_pens import TestShapes
	TestShapes.onCurveLessQuadShape(p)