Skip to content

Commit d79603d

Browse files
authored
1 parent 243e84c commit d79603d

12 files changed

+1647
-0
lines changed

svg/path/parsing/arc-commands.html

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8">
5+
<title>SVG Path Data: Elliptical arc (A/a)</title>
6+
<link rel="help" href="https://svgwg.org/svg2-draft/paths.html#PathDataBNF">
7+
<script src="/resources/testharness.js"></script>
8+
<script src="/resources/testharnessreport.js"></script>
9+
</head>
10+
<body>
11+
12+
<svg id="svg" width="400" height="400">
13+
<!-- Absolute arc -->
14+
<path id="test-A" d="M 100,100 A 50,50 0 0,1 200,100" fill="none" stroke="black" stroke-width="2"/>
15+
16+
<!-- Relative arc -->
17+
<path id="test-a" d="M 100,150 a 50,50 0 0,1 100,0" fill="none" stroke="blue" stroke-width="2"/>
18+
</svg>
19+
20+
<script>
21+
test(function() {
22+
const path = document.getElementById('test-A');
23+
const length = path.getTotalLength();
24+
25+
assert_greater_than(length, 0, "A command creates an arc");
26+
assert_greater_than(length, 100, "Arc is longer than straight line");
27+
}, "Absolute arc: A 50,50 0 0,1 200,100");
28+
29+
test(function() {
30+
const pathA = document.getElementById('test-A');
31+
const patha = document.getElementById('test-a');
32+
33+
// Both should create similar arcs
34+
assert_approx_equals(pathA.getTotalLength(), patha.getTotalLength(), 1,
35+
"Relative arc creates same curve as absolute");
36+
}, "Relative arc: a 50,50 0 0,1 100,0 from (100,150)");
37+
38+
test(function() {
39+
const path = document.getElementById('test-A');
40+
const endPoint = path.getPointAtLength(path.getTotalLength());
41+
42+
assert_approx_equals(endPoint.x, 200, 0.5, "Arc ends at specified x");
43+
assert_approx_equals(endPoint.y, 100, 0.5, "Arc ends at specified y");
44+
}, "Arc endpoint is correct");
45+
46+
test(function() {
47+
// Test large-arc-flag = 1
48+
const svg = document.getElementById('svg');
49+
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
50+
path.setAttribute('d', 'M 100,200 A 50,50 0 1,1 200,200');
51+
svg.appendChild(path);
52+
53+
const length = path.getTotalLength();
54+
assert_greater_than(length, 100, "Large arc flag creates larger arc");
55+
}, "Large arc flag: A 50,50 0 1,1 200,200");
56+
57+
test(function() {
58+
// Test sweep-flag variations
59+
const svg = document.getElementById('svg');
60+
const path0 = document.createElementNS('http://www.w3.org/2000/svg', 'path');
61+
path0.setAttribute('d', 'M 100,250 A 50,50 0 0,0 200,250');
62+
svg.appendChild(path0);
63+
64+
const path1 = document.createElementNS('http://www.w3.org/2000/svg', 'path');
65+
path1.setAttribute('d', 'M 100,250 A 50,50 0 0,1 200,250');
66+
svg.appendChild(path1);
67+
68+
// Both paths share the same start (100,250) and end (200,250) with r=50.
69+
// The chord length (100) equals the diameter (2×50), so both arcs are semicircles
70+
// with identical path lengths — comparing lengths would yield near-zero floating-point
71+
// noise instead of a meaningful difference.
72+
//
73+
// The arcs bow in opposite directions:
74+
//
75+
// (150, 200) ← midpoint of sweep=1 arc (bows up)
76+
// ●
77+
// ●─────────────●
78+
// (100,250) (200,250)
79+
// ●
80+
// (150, 300) ← midpoint of sweep=0 arc (bows down)
81+
//
82+
// Comparing the midpoint y-coordinates gives a ~100-unit difference on all platforms.
83+
const mid0 = path0.getPointAtLength(path0.getTotalLength() / 2);
84+
const mid1 = path1.getPointAtLength(path1.getTotalLength() / 2);
85+
assert_greater_than(Math.abs(mid0.y - mid1.y), 10,
86+
"Different sweep flags create different arcs");
87+
}, "Sweep flag variations: 0 vs 1");
88+
89+
test(function() {
90+
// Test x-axis-rotation
91+
const svg = document.getElementById('svg');
92+
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
93+
path.setAttribute('d', 'M 100,300 A 50,25 45 0,1 200,300');
94+
svg.appendChild(path);
95+
96+
const length = path.getTotalLength();
97+
assert_greater_than(length, 0, "Rotated elliptical arc works");
98+
}, "X-axis rotation: A 50,25 45 0,1 200,300");
99+
100+
test(function() {
101+
// Multiple arc arguments
102+
const svg = document.getElementById('svg');
103+
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
104+
path.setAttribute('d', 'M 50,350 A 25,25 0 0,1 100,350 25,25 0 0,1 150,350');
105+
svg.appendChild(path);
106+
107+
const endPoint = path.getPointAtLength(path.getTotalLength());
108+
assert_approx_equals(endPoint.x, 150, 0.5, "Multiple arcs end at last point");
109+
}, "Multiple arc arguments");
110+
111+
test(function() {
112+
// Flags without comma
113+
const svg = document.getElementById('svg');
114+
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
115+
path.setAttribute('d', 'M 200,100 A 50,50 0 01 300,100');
116+
svg.appendChild(path);
117+
118+
const length = path.getTotalLength();
119+
assert_greater_than(length, 0, "Flags without comma separator work");
120+
}, "Arc flags without comma: 01 instead of 0,1");
121+
122+
test(function() {
123+
// Flags with spaces
124+
const svg = document.getElementById('svg');
125+
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
126+
path.setAttribute('d', 'M 200,150 A 50,50 0 0 1 300,150');
127+
svg.appendChild(path);
128+
129+
const length = path.getTotalLength();
130+
assert_greater_than(length, 0, "Flags with space separator work");
131+
}, "Arc flags with spaces: 0 1 instead of 0,1");
132+
133+
test(function() {
134+
// Different radii (ellipse)
135+
const svg = document.getElementById('svg');
136+
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
137+
path.setAttribute('d', 'M 200,200 A 75,25 0 0,1 300,200');
138+
svg.appendChild(path);
139+
140+
const length = path.getTotalLength();
141+
assert_greater_than(length, 0, "Elliptical arc with different radii works");
142+
}, "Elliptical arc: A 75,25 (different rx and ry)");
143+
144+
test(function() {
145+
// Zero radius (should create line)
146+
const svg = document.getElementById('svg');
147+
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
148+
path.setAttribute('d', 'M 200,250 A 0,0 0 0,1 300,250');
149+
svg.appendChild(path);
150+
151+
const length = path.getTotalLength();
152+
assert_approx_equals(length, 100, 0.5, "Zero radius creates straight line");
153+
}, "Arc with zero radius becomes line");
154+
155+
test(function() {
156+
// Negative radius (should use absolute value)
157+
const svg = document.getElementById('svg');
158+
const path1 = document.createElementNS('http://www.w3.org/2000/svg', 'path');
159+
path1.setAttribute('d', 'M 200,300 A -50,50 0 0,1 300,300');
160+
svg.appendChild(path1);
161+
162+
const path2 = document.createElementNS('http://www.w3.org/2000/svg', 'path');
163+
path2.setAttribute('d', 'M 200,300 A 50,50 0 0,1 300,300');
164+
svg.appendChild(path2);
165+
166+
// Negative radius should be treated as absolute value
167+
assert_approx_equals(path1.getTotalLength(), path2.getTotalLength(), 0.5,
168+
"Negative radius treated as positive");
169+
}, "Arc with negative radius uses absolute value");
170+
</script>
171+
</body>
172+
</html>
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8">
5+
<title>SVG Path Data: Curveto commands (C/c, S/s)</title>
6+
<link rel="help" href="https://svgwg.org/svg2-draft/paths.html#PathDataBNF">
7+
<script src="/resources/testharness.js"></script>
8+
<script src="/resources/testharnessreport.js"></script>
9+
</head>
10+
<body>
11+
12+
<svg id="svg" width="400" height="400">
13+
<!-- Cubic Bezier curve -->
14+
<path id="test-C" d="M 50,50 C 100,25 150,75 200,50" fill="none" stroke="black" stroke-width="2"/>
15+
16+
<!-- Relative cubic Bezier -->
17+
<path id="test-c" d="M 50,50 c 50,-25 100,25 150,0" fill="none" stroke="blue" stroke-width="1" stroke-dasharray="2,2"/>
18+
19+
<!-- Smooth cubic Bezier -->
20+
<path id="test-S" d="M 50,150 C 75,100 100,100 125,150 S 175,200 200,150" fill="none" stroke="green" stroke-width="2"/>
21+
</svg>
22+
23+
<script>
24+
test(function() {
25+
const path = document.getElementById('test-C');
26+
const length = path.getTotalLength();
27+
28+
assert_greater_than(length, 0, "C command creates a curve");
29+
assert_greater_than(length, 100, "Curve is longer than straight distance");
30+
}, "Absolute curveto: C 100,25 150,75 200,50");
31+
32+
test(function() {
33+
const pathC = document.getElementById('test-C');
34+
const pathc = document.getElementById('test-c');
35+
36+
// Both should create similar curves
37+
assert_approx_equals(pathC.getTotalLength(), pathc.getTotalLength(), 1,
38+
"Relative curveto creates same curve as absolute");
39+
}, "Relative curveto: c 50,-25 100,25 150,0 from (50,50)");
40+
41+
test(function() {
42+
const pathC = document.getElementById('test-C');
43+
const startPoint = pathC.getPointAtLength(0);
44+
const endPoint = pathC.getPointAtLength(pathC.getTotalLength());
45+
46+
assert_approx_equals(startPoint.x, 50, 0.1, "Curve starts at M point");
47+
assert_approx_equals(startPoint.y, 50, 0.1, "Curve starts at M point");
48+
assert_approx_equals(endPoint.x, 200, 0.1, "Curve ends at specified point");
49+
assert_approx_equals(endPoint.y, 50, 0.1, "Curve ends at specified point");
50+
}, "Curveto endpoints are correct");
51+
52+
test(function() {
53+
// Multiple curve triplets
54+
const svg = document.getElementById('svg');
55+
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
56+
path.setAttribute('d', 'M 0,50 C 25,0 50,0 75,50 100,100 125,100 150,50');
57+
svg.appendChild(path);
58+
59+
const endPoint = path.getPointAtLength(path.getTotalLength());
60+
assert_approx_equals(endPoint.x, 150, 0.5, "Multiple curves end at last point");
61+
assert_approx_equals(endPoint.y, 50, 0.5, "Multiple curves end at last point");
62+
}, "Multiple curveto triplets: C x1,y1 x2,y2 x,y x1,y1 x2,y2 x,y");
63+
64+
test(function() {
65+
const path = document.getElementById('test-S');
66+
const length = path.getTotalLength();
67+
68+
assert_greater_than(length, 0, "S command creates a curve");
69+
}, "Smooth curveto: S 175,200 200,150");
70+
71+
test(function() {
72+
// S command reflects previous control point
73+
const svg = document.getElementById('svg');
74+
const path1 = document.createElementNS('http://www.w3.org/2000/svg', 'path');
75+
path1.setAttribute('d', 'M 0,50 C 25,0 50,0 75,50 S 125,100 150,50');
76+
svg.appendChild(path1);
77+
78+
const length = path1.getTotalLength();
79+
assert_greater_than(length, 0, "S after C creates smooth curve");
80+
}, "Smooth curveto after C: reflects control point");
81+
82+
test(function() {
83+
// Relative smooth curveto
84+
const svg = document.getElementById('svg');
85+
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
86+
path.setAttribute('d', 'M 50,50 C 75,25 100,75 125,50 s 50,-25 75,0');
87+
svg.appendChild(path);
88+
89+
const length = path.getTotalLength();
90+
assert_greater_than(length, 0, "Relative s command works");
91+
}, "Relative smooth curveto: s x2,y2 x,y");
92+
93+
test(function() {
94+
// S without preceding curve (uses current point as reflection)
95+
const svg = document.getElementById('svg');
96+
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
97+
path.setAttribute('d', 'M 50,50 S 100,25 150,50');
98+
svg.appendChild(path);
99+
100+
const length = path.getTotalLength();
101+
assert_greater_than(length, 0, "S without preceding C uses current point");
102+
}, "Smooth curveto without preceding curve");
103+
104+
test(function() {
105+
// Multiple S commands
106+
const svg = document.getElementById('svg');
107+
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
108+
path.setAttribute('d', 'M 0,50 C 25,0 50,0 75,50 S 125,100 150,50 175,0 200,50');
109+
svg.appendChild(path);
110+
111+
const endPoint = path.getPointAtLength(path.getTotalLength());
112+
assert_approx_equals(endPoint.x, 200, 0.5, "Multiple S commands");
113+
assert_approx_equals(endPoint.y, 50, 0.5, "Multiple S commands");
114+
}, "Multiple smooth curveto pairs");
115+
116+
test(function() {
117+
// Curveto with minimal spacing (no commas)
118+
const svg = document.getElementById('svg');
119+
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
120+
path.setAttribute('d', 'M 50 50 C 75 25 100 75 125 50');
121+
svg.appendChild(path);
122+
123+
const length = path.getTotalLength();
124+
assert_greater_than(length, 0, "C command works with space separators only");
125+
}, "Curveto with space separators (no commas)");
126+
127+
test(function() {
128+
// Curveto with all commas
129+
const svg = document.getElementById('svg');
130+
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
131+
path.setAttribute('d', 'M 50,50 C 75,25,100,75,125,50');
132+
svg.appendChild(path);
133+
134+
const length = path.getTotalLength();
135+
assert_greater_than(length, 0, "C command works with all comma separators");
136+
}, "Curveto with all comma separators");
137+
</script>
138+
</body>
139+
</html>

0 commit comments

Comments
 (0)