diff --git a/Lib/fontTools/misc/bezierTools.py b/Lib/fontTools/misc/bezierTools.py index 01a870279..73053052d 100644 --- a/Lib/fontTools/misc/bezierTools.py +++ b/Lib/fontTools/misc/bezierTools.py @@ -13,10 +13,12 @@ __all__ = [ "approximateCubicArcLengthC", "approximateQuadraticArcLength", "approximateQuadraticArcLengthC", + "calcCubicArcLength", + "calcCubicArcLengthC", "calcQuadraticArcLength", "calcQuadraticArcLengthC", - "calcQuadraticBounds", "calcCubicBounds", + "calcQuadraticBounds", "splitLine", "splitQuadratic", "splitCubic", @@ -27,6 +29,32 @@ __all__ = [ ] +def calcCubicArcLength(pt1, pt2, pt3, pt4, tolerance=0.005): + """Return the arc length for a cubic bezier segment.""" + return calcCubicArcLengthC(complex(*pt1), complex(*pt2), complex(*pt3), complex(*pt4), tolerance) + + +def _split_cubic_into_two(p0, p1, p2, p3): + mid = (p0 + 3 * (p1 + p2) + p3) * .125 + deriv3 = (p3 + p2 - p1 - p0) * .125 + return ((p0, (p0 + p1) * .5, mid - deriv3, mid), + (mid, mid + deriv3, (p2 + p3) * .5, p3)) + +def _calcCubicArcLengthCRecurse(mult, p0, p1, p2, p3): + arch = abs(p0-p3) + box = abs(p0-p1) + abs(p1-p2) + abs(p2-p3) + if arch * mult >= box: + return (arch + box) * .5 + else: + one,two = _split_cubic_into_two(p0,p1,p2,p3) + return _calcCubicArcLengthCRecurse(mult, *one) + _calcCubicArcLengthCRecurse(mult, *two) + +def calcCubicArcLengthC(pt1, pt2, pt3, pt4, tolerance=0.005): + """Return the arc length for a cubic bezier segment using complex points.""" + mult = 1. + 1.5 * tolerance # The 1.5 is a empirical hack; no math + return _calcCubicArcLengthCRecurse(mult, pt1, pt2, pt3, pt4) + + epsilonDigits = 6 epsilon = 1e-10 diff --git a/Lib/fontTools/pens/perimeterPen.py b/Lib/fontTools/pens/perimeterPen.py index 10a2ff3bf..12e7e47e5 100644 --- a/Lib/fontTools/pens/perimeterPen.py +++ b/Lib/fontTools/pens/perimeterPen.py @@ -4,7 +4,7 @@ from __future__ import print_function, division, absolute_import from fontTools.misc.py23 import * from fontTools.pens.basePen import BasePen -from fontTools.misc.bezierTools import splitQuadraticAtT, splitCubicAtT, approximateQuadraticArcLengthC, calcQuadraticArcLengthC, approximateCubicArcLengthC +from fontTools.misc.bezierTools import approximateQuadraticArcLengthC, calcQuadraticArcLengthC, approximateCubicArcLengthC, calcCubicArcLengthC import math @@ -14,18 +14,12 @@ __all__ = ["PerimeterPen"] def _distance(p0, p1): return math.hypot(p0[0] - p1[0], p0[1] - p1[1]) -def _split_cubic_into_two(p0, p1, p2, p3): - mid = (p0 + 3 * (p1 + p2) + p3) * .125 - deriv3 = (p3 + p2 - p1 - p0) * .125 - return ((p0, (p0 + p1) * .5, mid - deriv3, mid), - (mid, mid + deriv3, (p2 + p3) * .5, p3)) - class PerimeterPen(BasePen): def __init__(self, glyphset=None, tolerance=0.005): BasePen.__init__(self, glyphset) self.value = 0 - self._mult = 1.+1.5*tolerance # The 1.5 is a empirical hack; no math + self.tolerance = tolerance # Choose which algorithm to use for quadratic and for cubic. # Quadrature is faster but has fixed error characteristic with no strong @@ -55,15 +49,8 @@ class PerimeterPen(BasePen): p0 = self._getCurrentPoint() self._addQuadratic(complex(*p0), complex(*p1), complex(*p2)) - def _addCubicRecursive(self, p0, p1, p2, p3): - arch = abs(p0-p3) - box = abs(p0-p1) + abs(p1-p2) + abs(p2-p3) - if arch * self._mult >= box: - self.value += (arch + box) * .5 - else: - one,two = _split_cubic_into_two(p0,p1,p2,p3) - self._addCubicRecursive(*one) - self._addCubicRecursive(*two) + def _addCubicRecursive(self, c0, c1, c2, c3): + self.value += calcCubicArcLengthC(c0, c1, c2, c3, self.tolerance) def _addCubicQuadrature(self, c0, c1, c2, c3): self.value += approximateCubicArcLengthC(c0, c1, c2, c3)