<commit_before><commit_msg>Pen to calculate the horizontal margins of a glyph at any given height by intersecting the shape with a horizontal line. Results can be obtained for the whole glyph  with pen.getMargins() or for each individual contour with pen.getCounterMargins().<commit_after>from fontTools.pens.basePen import AbstractPen, BasePen
from fontTools.misc.bezierTools import splitLine, splitCubic


from sets import Set

class MarginPen(BasePen):

	"""
		Pen to calculate the horizontal margins at a given height.
		
		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.
		
	"""

	def __init__(self, glyphSet, height):
		BasePen.__init__(self, glyphSet)
		
		self.height = height
		self.hits = {}
		self.filterDoubles = True
		self.contourIndex = None
		
	def _moveTo(self, pt):
		self.currentPt = 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.height, True)
		#print "hits lineto", hits
		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] = []
			self.hits[self.contourIndex].append(round(hits[0][-1][0], 4))
		if pt[1] == self.height:
			# it could happen
			if not self.contourIndex in self.hits:
				self.hits[self.contourIndex] =  []
				
			self.hits[self.contourIndex].append(pt[0])
		self.currentPt = pt

	def _curveToOne(self, pt1, pt2, pt3):
		hits = splitCubic(self.currentPt, pt1, pt2, pt3, self.height, True)
		#print "hits curveto", hits
		if len(hits)==2:
			if not self.contourIndex in self.hits:
				self.hits[self.contourIndex] = []
			self.hits[self.contourIndex].append(round(hits[0][-1][0], 4))
		elif pt3[1] == self.height:
			# it could happen
			if not self.contourIndex in self.hits:
				self.hits[self.contourIndex] = []
			self.hits[self.contourIndex].append(pt3[0])
		self.currentPt = pt3
		
	def _closePath(self):
		self.currentPt = None
	
	def _endPath(self):
		self.currentPt = None
		
	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)
		unique = list(Set(allHits))
		unique.sort()
		if unique:
			return min(unique), max(unique)
		return None
		
	def getContourMargins(self):
		"""Get the horizontal margins for each contour."""
		print 'self.hits', self.hits
		allHits = {}
		for index, pts in self.hits.items():
			unique = list(Set(pts))
			unique.sort()
			allHits[index] = unique
		return allHits
	
	def addComponent(self, baseGlyph, transformation):
		if self.glyphSet is None:
			return
		if baseGlyph in self.glyphSet:
			glyph = self.glyphSet[baseGlyph]
		if glyph is not None:
			glyph.draw(self)
		
		
if __name__ == "__main__":

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

	pt = (100, 249)
	pen = MarginPen(f, pt[1])
	g.draw(pen)	
	print 'glyph margins', pen.getMargins()

	print pen.getContourMargins()

{'fraction_non_alphanumeric': 0.10474090407938258, 'fraction_numerical': 0.009371554575523704, 'mean_word_length': 3.2296037296037294, 'pattern_counts': {'":': 1, '<': 3, '<?xml version=': 0, '>': 4, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1181970', 'n_tokens_mistral': 1445, 'n_tokens_neox': 1189, 'n_words': 358}