xref: /aosp_15_r20/external/fonttools/Tests/misc/bezierTools_test.py (revision e1fe3e4ad2793916b15cccdc4a7da52a7e1dd0e9)
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