1from __future__ import print_function, division, absolute_import
2from fontTools.misc.py23 import *
3from fontTools.misc.arrayTools import updateBounds, pointInRect, unionRect
4from fontTools.misc.bezierTools import calcCubicBounds, calcQuadraticBounds
5from fontTools.pens.basePen import BasePen
6
7
8__all__ = ["BoundsPen", "ControlBoundsPen"]
9
10
11class ControlBoundsPen(BasePen):
12
13	"""Pen to calculate the "control bounds" of a shape. This is the
14	bounding box of all control points, so may be larger than the
15	actual bounding box if there are curves that don't have points
16	on their extremes.
17
18	When the shape has been drawn, the bounds are available as the
19	'bounds' attribute of the pen object. It's a 4-tuple:
20		(xMin, yMin, xMax, yMax)
21	"""
22
23	def __init__(self, glyphSet):
24		BasePen.__init__(self, glyphSet)
25		self.bounds = None
26
27	def _moveTo(self, pt):
28		bounds = self.bounds
29		if bounds:
30			self.bounds = updateBounds(bounds, pt)
31		else:
32			x, y = pt
33			self.bounds = (x, y, x, y)
34
35	def _lineTo(self, pt):
36		self.bounds = updateBounds(self.bounds, pt)
37
38	def _curveToOne(self, bcp1, bcp2, pt):
39		bounds = self.bounds
40		bounds = updateBounds(bounds, bcp1)
41		bounds = updateBounds(bounds, bcp2)
42		bounds = updateBounds(bounds, pt)
43		self.bounds = bounds
44
45	def _qCurveToOne(self, bcp, pt):
46		bounds = self.bounds
47		bounds = updateBounds(bounds, bcp)
48		bounds = updateBounds(bounds, pt)
49		self.bounds = bounds
50
51
52class BoundsPen(ControlBoundsPen):
53
54	"""Pen to calculate the bounds of a shape. It calculates the
55	correct bounds even when the shape contains curves that don't
56	have points on their extremes. This is somewhat slower to compute
57	than the "control bounds".
58
59	When the shape has been drawn, the bounds are available as the
60	'bounds' attribute of the pen object. It's a 4-tuple:
61		(xMin, yMin, xMax, yMax)
62	"""
63
64	def _curveToOne(self, bcp1, bcp2, pt):
65		bounds = self.bounds
66		bounds = updateBounds(bounds, pt)
67		if not pointInRect(bcp1, bounds) or not pointInRect(bcp2, bounds):
68			bounds = unionRect(bounds, calcCubicBounds(
69					self._getCurrentPoint(), bcp1, bcp2, pt))
70		self.bounds = bounds
71
72	def _qCurveToOne(self, bcp, pt):
73		bounds = self.bounds
74		bounds = updateBounds(bounds, pt)
75		if not pointInRect(bcp, bounds):
76			bounds = unionRect(bounds, calcQuadraticBounds(
77					self._getCurrentPoint(), bcp, pt))
78		self.bounds = bounds
79
80
81if __name__ == "__main__":
82	def draw(pen):
83		pen.moveTo((0, 0))
84		pen.lineTo((0, 100))
85		pen.qCurveTo((50, 75), (60, 50), (50, 25), (0, 0))
86		pen.curveTo((-50, 25), (-60, 50), (-50, 75), (0, 100))
87		pen.closePath()
88
89	pen = ControlBoundsPen(None)
90	draw(pen)
91	print(pen.bounds)
92
93	pen = BoundsPen(None)
94	draw(pen)
95	print(pen.bounds)
96