summaryrefslogtreecommitdiff
path: root/misc/pylib/robofab/pens/marginPen.py
blob: 03f13f91793194629e8900478ed0a74ba6b49427 (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
from fontTools.pens.basePen import AbstractPen, BasePen
from robofab.misc.bezierTools import splitLine, splitCubic


from sets import Set

class MarginPen(BasePen):

	"""
		Pen to calculate the margins at a given value.
			When isHorizontal is True, the margins at <value> are horizontal.
			When isHorizontal is False, the margins at <value> are vertical.
		
		When a glyphset or font is given, MarginPen will also calculate for glyphs with components.

		pen.getMargins() returns the minimum and maximum intersections of the glyph.
		pen.getContourMargins() returns the minimum and maximum intersections for each contour.
		
		
		Possible optimisation:
		Initialise the pen object with a list of points we want to measure,
		then draw the glyph once, but do the splitLine() math for all measure points.
		
	"""

	def __init__(self, glyphSet, value, isHorizontal=True):
		BasePen.__init__(self, glyphSet)
		self.value = value
		self.hits = {}
		self.filterDoubles = True
		self.contourIndex = None
		self.startPt = None
		self.isHorizontal = isHorizontal
		
	def _moveTo(self, pt):
		self.currentPt = pt
		self.startPt = pt
		if self.contourIndex is None:
			self.contourIndex = 0
		else:
			self.contourIndex += 1

	def _lineTo(self, pt):
		if self.filterDoubles:
			if pt == self.currentPt:
				return
		hits = splitLine(self.currentPt, pt, self.value, self.isHorizontal)
		if len(hits)>1:
			# result will be 2 tuples of 2 coordinates
			# first two points: start to intersect
			# second two points: intersect to end
			# so, second point in first tuple is the intersect
			# then, the first coordinate of that point is the x.
			if not self.contourIndex in self.hits:
				self.hits[self.contourIndex] = []
			if self.isHorizontal:
				self.hits[self.contourIndex].append(round(hits[0][-1][0], 4))
			else:
				self.hits[self.contourIndex].append(round(hits[0][-1][1], 4))
		if self.isHorizontal and pt[1] == self.value:
			# it could happen
			if not self.contourIndex in self.hits:
				self.hits[self.contourIndex] =	[]
			self.hits[self.contourIndex].append(pt[0])
		elif (not self.isHorizontal) and (pt[0] == self.value):
			# it could happen
			if not self.contourIndex in self.hits:
				self.hits[self.contourIndex] =	[]
			self.hits[self.contourIndex].append(pt[1])
		self.currentPt = pt

	def _curveToOne(self, pt1, pt2, pt3):
		hits = splitCubic(self.currentPt, pt1, pt2, pt3, self.value, self.isHorizontal)
		for i in range(len(hits)-1):
			# a number of intersections is possible. Just take the 
			# last point of each segment.
			if not self.contourIndex in self.hits:
				self.hits[self.contourIndex] = []
			if self.isHorizontal:
				self.hits[self.contourIndex].append(round(hits[i][-1][0], 4))
			else:
				self.hits[self.contourIndex].append(round(hits[i][-1][1], 4))
		if self.isHorizontal and pt3[1] == self.value:
			# it could happen
			if not self.contourIndex in self.hits:
				self.hits[self.contourIndex] = []
			self.hits[self.contourIndex].append(pt3[0])
		if (not self.isHorizontal) and (pt3[0] == self.value):
			# it could happen
			if not self.contourIndex in self.hits:
				self.hits[self.contourIndex] = []
			self.hits[self.contourIndex].append(pt3[1])
		self.currentPt = pt3
		
	def _closePath(self):
		if self.currentPt != self.startPt:
			self._lineTo(self.startPt)
		self.currentPt = self.startPt = None
	
	def _endPath(self):
		self.currentPt = None

	def addComponent(self, baseGlyph, transformation):
		from fontTools.pens.transformPen import TransformPen
		if self.glyphSet is None:
			return
		if baseGlyph in self.glyphSet:
			glyph = self.glyphSet[baseGlyph]
			if glyph is None:
				return
			tPen = TransformPen(self, transformation)
			glyph.draw(tPen)

	def getMargins(self):
		"""Get the horizontal margins for all contours combined, i.e. the whole glyph."""
		allHits = []
		for index, pts in self.hits.items():
			allHits.extend(pts)
		if allHits:
			return min(allHits), max(allHits)
		return None
		
	def getContourMargins(self):
		"""Get the horizontal margins for each contour."""
		allHits = {}
		for index, pts in self.hits.items():
			unique = list(Set(pts))
			unique.sort()
			allHits[index] = unique
		return allHits
		
	def getAll(self):
		"""Get all the slices."""
		allHits = []
		for index, pts in self.hits.items():
			allHits.extend(pts)
		unique = list(Set(allHits))
		unique = list(unique)
		unique.sort()
		return unique
		
		
if __name__ == "__main__":

	from robofab.world import CurrentGlyph, CurrentFont
	f = CurrentFont()
	g = CurrentGlyph()

	pt = (74, 216)

	pen = MarginPen(f, pt[1], isHorizontal=False)
	g.draw(pen) 
	print 'glyph Y margins', pen.getMargins()
	print pen.getContourMargins()