diff --git a/Lib/fontTools/misc/bezierTools.py b/Lib/fontTools/misc/bezierTools.py index a1a707b09..5411ff99f 100644 --- a/Lib/fontTools/misc/bezierTools.py +++ b/Lib/fontTools/misc/bezierTools.py @@ -18,6 +18,9 @@ except (AttributeError, ImportError): COMPILED = False +EPSILON = 1e-9 + + Intersection = namedtuple("Intersection", ["pt", "t1", "t2"]) @@ -92,7 +95,7 @@ def _split_cubic_into_two(p0, p1, p2, 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: + if arch * mult + EPSILON >= box: return (arch + box) * 0.5 else: one, two = _split_cubic_into_two(p0, p1, p2, p3) diff --git a/Tests/misc/bezierTools_test.py b/Tests/misc/bezierTools_test.py index ce8a9e17e..4030f0704 100644 --- a/Tests/misc/bezierTools_test.py +++ b/Tests/misc/bezierTools_test.py @@ -3,6 +3,7 @@ from fontTools.misc.bezierTools import ( calcQuadraticBounds, calcQuadraticArcLength, calcCubicBounds, + calcCubicArcLength, curveLineIntersections, curveCurveIntersections, segmentPointAtT, @@ -192,6 +193,25 @@ def test_calcQuadraticArcLength(): ) == pytest.approx(127.9225) +@pytest.mark.parametrize( + "segment, expectedLength", + [ + ( + # https://github.com/fonttools/fonttools/issues/3502 + ((377, 469), (377, 468), (377, 472), (377, 472)), # off by one unit + 3.32098765445, + ), + ( + # https://github.com/fonttools/fonttools/issues/3502 + ((242, 402), (242, 403), (242, 399), (242, 399)), # off by one unit + 3.32098765445, + ), + ], +) +def test_calcCubicArcLength(segment, expectedLength): + assert calcCubicArcLength(*segment) == pytest.approx(expectedLength) + + def test_intersections_linelike(): seg1 = [(0.0, 0.0), (0.0, 0.25), (0.0, 0.75), (0.0, 1.0)] seg2 = [(0.0, 0.5), (0.25, 0.5), (0.75, 0.5), (1.0, 0.5)]