xref: /aosp_15_r20/external/fonttools/Lib/fontTools/pens/perimeterPen.py (revision e1fe3e4ad2793916b15cccdc4a7da52a7e1dd0e9)
1# -*- coding: utf-8 -*-
2"""Calculate the perimeter of a glyph."""
3
4from fontTools.pens.basePen import BasePen
5from fontTools.misc.bezierTools import (
6    approximateQuadraticArcLengthC,
7    calcQuadraticArcLengthC,
8    approximateCubicArcLengthC,
9    calcCubicArcLengthC,
10)
11import math
12
13
14__all__ = ["PerimeterPen"]
15
16
17def _distance(p0, p1):
18    return math.hypot(p0[0] - p1[0], p0[1] - p1[1])
19
20
21class PerimeterPen(BasePen):
22    def __init__(self, glyphset=None, tolerance=0.005):
23        BasePen.__init__(self, glyphset)
24        self.value = 0
25        self.tolerance = tolerance
26
27        # Choose which algorithm to use for quadratic and for cubic.
28        # Quadrature is faster but has fixed error characteristic with no strong
29        # error bound.  The cutoff points are derived empirically.
30        self._addCubic = (
31            self._addCubicQuadrature if tolerance >= 0.0015 else self._addCubicRecursive
32        )
33        self._addQuadratic = (
34            self._addQuadraticQuadrature
35            if tolerance >= 0.00075
36            else self._addQuadraticExact
37        )
38
39    def _moveTo(self, p0):
40        self.__startPoint = p0
41
42    def _closePath(self):
43        p0 = self._getCurrentPoint()
44        if p0 != self.__startPoint:
45            self._lineTo(self.__startPoint)
46
47    def _lineTo(self, p1):
48        p0 = self._getCurrentPoint()
49        self.value += _distance(p0, p1)
50
51    def _addQuadraticExact(self, c0, c1, c2):
52        self.value += calcQuadraticArcLengthC(c0, c1, c2)
53
54    def _addQuadraticQuadrature(self, c0, c1, c2):
55        self.value += approximateQuadraticArcLengthC(c0, c1, c2)
56
57    def _qCurveToOne(self, p1, p2):
58        p0 = self._getCurrentPoint()
59        self._addQuadratic(complex(*p0), complex(*p1), complex(*p2))
60
61    def _addCubicRecursive(self, c0, c1, c2, c3):
62        self.value += calcCubicArcLengthC(c0, c1, c2, c3, self.tolerance)
63
64    def _addCubicQuadrature(self, c0, c1, c2, c3):
65        self.value += approximateCubicArcLengthC(c0, c1, c2, c3)
66
67    def _curveToOne(self, p1, p2, p3):
68        p0 = self._getCurrentPoint()
69        self._addCubic(complex(*p0), complex(*p1), complex(*p2), complex(*p3))
70