# Source code for fontTools.pens.boundsPen

    from fontTools.misc.arrayTools import updateBounds, pointInRect, unionRect
    from fontTools.misc.bezierTools import calcCubicBounds, calcQuadraticBounds
    from fontTools.pens.basePen import BasePen

    __all__ = ["BoundsPen", "ControlBoundsPen"]

    class ControlBoundsPen(BasePen):
        """Pen to calculate the "control bounds" of a shape. This is the
        bounding box of all control points, so may be larger than the
        actual bounding box if there are curves that don't have points
        on their extremes.

        When the shape has been drawn, the bounds are available as the
        ``bounds`` attribute of the pen object. It's a 4-tuple::

                (xMin, yMin, xMax, yMax).

        If ``ignoreSinglePoints`` is True, single points are ignored.
        """

        def __init__(self, glyphSet, ignoreSinglePoints=False):
            BasePen.__init__(self, glyphSet)
            self.ignoreSinglePoints = ignoreSinglePoints
            self.init()

        def init(self):
            self.bounds = None
            self._start = None

        def _moveTo(self, pt):
            self._start = pt
            if not self.ignoreSinglePoints:
                self._addMoveTo()

        def _addMoveTo(self):
            if self._start is None:
                return
            bounds = self.bounds
            if bounds:
                self.bounds = updateBounds(bounds, self._start)
            else:
                x, y = self._start
                self.bounds = (x, y, x, y)
            self._start = None

        def _lineTo(self, pt):
            self._addMoveTo()
            self.bounds = updateBounds(self.bounds, pt)

        def _curveToOne(self, bcp1, bcp2, pt):
            self._addMoveTo()
            bounds = self.bounds
            bounds = updateBounds(bounds, bcp1)
            bounds = updateBounds(bounds, bcp2)
            bounds = updateBounds(bounds, pt)
            self.bounds = bounds

        def _qCurveToOne(self, bcp, pt):
            self._addMoveTo()
            bounds = self.bounds
            bounds = updateBounds(bounds, bcp)
            bounds = updateBounds(bounds, pt)
            self.bounds = bounds

    class BoundsPen(ControlBoundsPen):
        """Pen to calculate the bounds of a shape. It calculates the
        correct bounds even when the shape contains curves that don't
        have points on their extremes. This is somewhat slower to compute
        than the "control bounds".

        When the shape has been drawn, the bounds are available as the
        ``bounds`` attribute of the pen object. It's a 4-tuple::

                (xMin, yMin, xMax, yMax)
        """

        def _curveToOne(self, bcp1, bcp2, pt):
            self._addMoveTo()
            bounds = self.bounds
            bounds = updateBounds(bounds, pt)
            if not pointInRect(bcp1, bounds) or not pointInRect(bcp2, bounds):
                bounds = unionRect(
                    bounds, calcCubicBounds(self._getCurrentPoint(), bcp1, bcp2, pt)
                )
            self.bounds = bounds

        def _qCurveToOne(self, bcp, pt):
            self._addMoveTo()
            bounds = self.bounds
            bounds = updateBounds(bounds, pt)
            if not pointInRect(bcp, bounds):
                bounds = unionRect(
                    bounds, calcQuadraticBounds(self._getCurrentPoint(), bcp, pt)
                )
            self.bounds = bounds