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