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