1import fontTools.misc.bezierTools as bezierTools 2from fontTools.misc.bezierTools import ( 3 calcQuadraticBounds, 4 calcQuadraticArcLength, 5 calcCubicBounds, 6 curveLineIntersections, 7 curveCurveIntersections, 8 segmentPointAtT, 9 splitLine, 10 splitQuadratic, 11 splitCubic, 12 splitQuadraticAtT, 13 splitCubicAtT, 14 solveCubic, 15) 16import pytest 17 18 19def test_calcQuadraticBounds(): 20 assert calcQuadraticBounds((0, 0), (50, 100), (100, 0)) == (0, 0, 100, 50.0) 21 assert calcQuadraticBounds((0, 0), (100, 0), (100, 100)) == (0.0, 0.0, 100, 100) 22 23 24def test_calcCubicBounds(): 25 assert calcCubicBounds((0, 0), (25, 100), (75, 100), (100, 0)) == ( 26 (0, 0, 100, 75.0) 27 ) 28 assert calcCubicBounds((0, 0), (50, 0), (100, 50), (100, 100)) == ( 29 0.0, 30 0.0, 31 100, 32 100, 33 ) 34 assert calcCubicBounds((50, 0), (0, 100), (100, 100), (50, 0)) == pytest.approx( 35 (35.566243, 0.000000, 64.433757, 75.000000) 36 ) 37 38 39def test_splitLine(): 40 assert splitLine((0, 0), (100, 100), where=50, isHorizontal=True) == [ 41 ((0, 0), (50.0, 50.0)), 42 ((50.0, 50.0), (100, 100)), 43 ] 44 assert splitLine((0, 0), (100, 100), where=100, isHorizontal=True) == [ 45 ((0, 0), (100, 100)) 46 ] 47 assert splitLine((0, 0), (100, 100), where=0, isHorizontal=True) == [ 48 ((0, 0), (0, 0)), 49 ((0, 0), (100, 100)), 50 ] 51 assert splitLine((0, 0), (100, 100), where=0, isHorizontal=False) == [ 52 ((0, 0), (0, 0)), 53 ((0, 0), (100, 100)), 54 ] 55 assert splitLine((100, 0), (0, 0), where=50, isHorizontal=False) == [ 56 ((100, 0), (50, 0)), 57 ((50, 0), (0, 0)), 58 ] 59 assert splitLine((0, 100), (0, 0), where=50, isHorizontal=True) == [ 60 ((0, 100), (0, 50)), 61 ((0, 50), (0, 0)), 62 ] 63 assert splitLine((0, 100), (100, 100), where=50, isHorizontal=True) == [ 64 ((0, 100), (100, 100)) 65 ] 66 67 68def assert_curves_approx_equal(actual_curves, expected_curves): 69 assert len(actual_curves) == len(expected_curves) 70 for acurve, ecurve in zip(actual_curves, expected_curves): 71 assert len(acurve) == len(ecurve) 72 for apt, ept in zip(acurve, ecurve): 73 assert apt == pytest.approx(ept) 74 75 76def test_splitQuadratic(): 77 assert splitQuadratic( 78 (0, 0), (50, 100), (100, 0), where=150, isHorizontal=False 79 ) == [((0, 0), (50, 100), (100, 0))] 80 assert splitQuadratic( 81 (0, 0), (50, 100), (100, 0), where=50, isHorizontal=False 82 ) == [((0, 0), (25, 50), (50, 50)), ((50, 50), (75, 50), (100, 0))] 83 assert splitQuadratic( 84 (0, 0), (50, 100), (100, 0), where=25, isHorizontal=False 85 ) == [((0, 0), (12.5, 25), (25, 37.5)), ((25, 37.5), (62.5, 75), (100, 0))] 86 assert_curves_approx_equal( 87 splitQuadratic((0, 0), (50, 100), (100, 0), where=25, isHorizontal=True), 88 [ 89 ((0, 0), (7.32233, 14.64466), (14.64466, 25)), 90 ((14.64466, 25), (50, 75), (85.3553, 25)), 91 ((85.3553, 25), (92.6777, 14.64466), (100, -7.10543e-15)), 92 ], 93 ) 94 # XXX I'm not at all sure if the following behavior is desirable 95 assert splitQuadratic((0, 0), (50, 100), (100, 0), where=50, isHorizontal=True) == [ 96 ((0, 0), (25, 50), (50, 50)), 97 ((50, 50), (50, 50), (50, 50)), 98 ((50, 50), (75, 50), (100, 0)), 99 ] 100 101 102def test_splitCubic(): 103 assert splitCubic( 104 (0, 0), (25, 100), (75, 100), (100, 0), where=150, isHorizontal=False 105 ) == [((0, 0), (25, 100), (75, 100), (100, 0))] 106 assert splitCubic( 107 (0, 0), (25, 100), (75, 100), (100, 0), where=50, isHorizontal=False 108 ) == [ 109 ((0, 0), (12.5, 50), (31.25, 75), (50, 75)), 110 ((50, 75), (68.75, 75), (87.5, 50), (100, 0)), 111 ] 112 assert_curves_approx_equal( 113 splitCubic((0, 0), (25, 100), (75, 100), (100, 0), where=25, isHorizontal=True), 114 [ 115 ((0, 0), (2.293792, 9.17517), (4.798045, 17.5085), (7.47414, 25)), 116 ((7.47414, 25), (31.2886, 91.6667), (68.7114, 91.6667), (92.5259, 25)), 117 ((92.5259, 25), (95.202, 17.5085), (97.7062, 9.17517), (100, 1.77636e-15)), 118 ], 119 ) 120 121 122def test_splitQuadraticAtT(): 123 assert splitQuadraticAtT((0, 0), (50, 100), (100, 0), 0.5) == [ 124 ((0, 0), (25, 50), (50, 50)), 125 ((50, 50), (75, 50), (100, 0)), 126 ] 127 assert splitQuadraticAtT((0, 0), (50, 100), (100, 0), 0.5, 0.75) == [ 128 ((0, 0), (25, 50), (50, 50)), 129 ((50, 50), (62.5, 50), (75, 37.5)), 130 ((75, 37.5), (87.5, 25), (100, 0)), 131 ] 132 133 134def test_splitCubicAtT(): 135 assert splitCubicAtT((0, 0), (25, 100), (75, 100), (100, 0), 0.5) == [ 136 ((0, 0), (12.5, 50), (31.25, 75), (50, 75)), 137 ((50, 75), (68.75, 75), (87.5, 50), (100, 0)), 138 ] 139 assert splitCubicAtT((0, 0), (25, 100), (75, 100), (100, 0), 0.5, 0.75) == [ 140 ((0, 0), (12.5, 50), (31.25, 75), (50, 75)), 141 ((50, 75), (59.375, 75), (68.75, 68.75), (77.34375, 56.25)), 142 ((77.34375, 56.25), (85.9375, 43.75), (93.75, 25), (100, 0)), 143 ] 144 145 146def test_solveCubic(): 147 assert solveCubic(1, 1, -6, 0) == [-3.0, -0.0, 2.0] 148 assert solveCubic(-10.0, -9.0, 48.0, -29.0) == [-2.9, 1.0, 1.0] 149 assert solveCubic(-9.875, -9.0, 47.625, -28.75) == [-2.911392, 1.0, 1.0] 150 assert solveCubic(1.0, -4.5, 6.75, -3.375) == [1.5, 1.5, 1.5] 151 assert solveCubic(-12.0, 18.0, -9.0, 1.50023651123) == [0.5, 0.5, 0.5] 152 assert solveCubic(9.0, 0.0, 0.0, -7.62939453125e-05) == [-0.0, -0.0, -0.0] 153 154 155_segmentPointAtT_testData = [ 156 ([(0, 10), (200, 100)], 0.0, (0, 10)), 157 ([(0, 10), (200, 100)], 0.5, (100, 55)), 158 ([(0, 10), (200, 100)], 1.0, (200, 100)), 159 ([(0, 10), (100, 100), (200, 50)], 0.0, (0, 10)), 160 ([(0, 10), (100, 100), (200, 50)], 0.5, (100, 65.0)), 161 ([(0, 10), (100, 100), (200, 50)], 1.0, (200, 50.0)), 162 ([(0, 10), (100, 100), (200, 100), (300, 0)], 0.0, (0, 10)), 163 ([(0, 10), (100, 100), (200, 100), (300, 0)], 0.5, (150, 76.25)), 164 ([(0, 10), (100, 100), (200, 100), (300, 0)], 1.0, (300, 0)), 165] 166 167 168@pytest.mark.parametrize("segment, t, expectedPoint", _segmentPointAtT_testData) 169def test_segmentPointAtT(segment, t, expectedPoint): 170 point = segmentPointAtT(segment, t) 171 assert expectedPoint == point 172 173 174def test_intersections_straight_line(): 175 curve = ((548, 183), (548, 289), (450, 366), (315, 366)) 176 line1 = ((330, 376), (330, 286)) 177 pt = curveLineIntersections(curve, line1)[0][0] 178 assert pt[0] == 330 179 line = (pt, (330, 286)) 180 pt2 = (330.0001018806911, 295.5635754579425) 181 assert bezierTools._line_t_of_pt(*line, pt2) > 0 182 s = (19, 0) 183 e = (110, 0) 184 pt = (109.05194805194802, 0.0) 185 assert bezierTools._line_t_of_pt(s, e, pt) == pytest.approx(0.98958184) 186 187 188def test_calcQuadraticArcLength(): 189 # https://github.com/fonttools/fonttools/issues/3287 190 assert calcQuadraticArcLength( 191 (210, 333), (289, 333), (326.5, 290.5) 192 ) == pytest.approx(127.9225) 193 194 195def test_intersections_linelike(): 196 seg1 = [(0.0, 0.0), (0.0, 0.25), (0.0, 0.75), (0.0, 1.0)] 197 seg2 = [(0.0, 0.5), (0.25, 0.5), (0.75, 0.5), (1.0, 0.5)] 198 pt = curveCurveIntersections(seg1, seg2)[0][0] 199 assert pt == (0.0, 0.5) 200