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