xref: /aosp_15_r20/external/fonttools/Lib/fontTools/pens/boundsPen.py (revision e1fe3e4ad2793916b15cccdc4a7da52a7e1dd0e9)
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