1 <html xmlns=
"http://www.w3.org/1999/xhtml">
3 https://bugzilla.mozilla.org/show_bug.cgi?id=619498
6 <title>Test interpolation between different path segment types
</title>
7 <script src=
"/tests/SimpleTest/SimpleTest.js"></script>
8 <link rel=
"stylesheet" type=
"text/css" href=
"/tests/SimpleTest/test.css" />
11 <a target=
"_blank" href=
"https://bugzilla.mozilla.org/show_bug.cgi?id=619498">Mozilla Bug
619498</a>
12 <svg xmlns=
"http://www.w3.org/2000/svg" id=
"svg" visibility=
"hidden"
13 onload=
"this.pauseAnimations()"/>
14 <script type=
"application/javascript"><![CDATA[
15 SimpleTest.waitForExplicitFinish();
17 var gSVG = document.getElementById(
"svg");
19 // Array of all subtests to run. This is populated by addTest.
22 // Array of all path segment types.
23 var gTypes =
"ZMmLlCcQqAaHhVvSsTt".split(
"");
25 // Property names on the SVGPathSeg objects for the given segment type, in the
26 // order that they would appear in a path data string.
27 var gArgumentNames = {
31 C: [
"x1",
"y1",
"x2",
"y2",
"x",
"y"],
32 Q: [
"x1",
"y1",
"x",
"y"],
33 A: [
"r1",
"r2",
"angle",
"largeArcFlag",
"sweepFlag",
"x",
"y"],
36 S: [
"x2",
"y2",
"x",
"y"],
40 // All of these prefixes leave the current point at
100,
100. Some of them
41 // affect the implied control point if followed by a smooth quadratic or
42 // cubic segment, but no valid interpolations depend on those control points.
45 [
2,
"M50,50 M100,100"],
47 [
2,
"M50,50 L100,100"],
49 [
3,
"M50,50 H100 V100"],
50 [
3,
"M50,50 h50 V100"],
51 [
3,
"M50,50 H100 v50"],
52 [
2,
"M50,50 A10,10,10,0,0,100,100"],
53 [
2,
"M50,50 a10,10,10,0,0,50,50"],
54 [
4,
"M50,50 l50,50 Z m50,50"],
56 // These leave the quadratic implied control point at
125,
125.
57 [
2,
"M50,50 Q75,75,100,100"],
58 [
2,
"M50,50 q25,25,50,50"],
59 [
2,
"M75,75 T100,100"],
61 [
3,
"M50,50 T62.5,62.5 t37.5,37.5"],
62 [
3,
"M50,50 T62.5,62.5 T100,100"],
63 [
3,
"M50,50 t12.5,12.5 t37.5,37.5"],
64 [
3,
"M50,50 t12.5,12.5 T100,100"],
65 [
3,
"M50,50 Q50,50,62.5,62.5 t37.5,37.5"],
66 [
3,
"M50,50 Q50,50,62.5,62.5 T100,100"],
67 [
3,
"M50,50 q0,0,12.5,12.5 t37.5,37.5"],
68 [
3,
"M50,50 q0,0,12.5,12.5 T100,100"],
70 // These leave the cubic implied control point at
125,
125.
71 [
2,
"M50,50 C10,10,75,75,100,100"],
72 [
2,
"M50,50 c10,10,25,25,50,50"],
73 [
2,
"M50,50 S75,75,100,100"],
74 [
2,
"M50,50 s25,25,50,50"],
75 [
3,
"M50,50 S10,10,75,75 S75,75,100,100"],
76 [
3,
"M50,50 S10,10,75,75 s0,0,25,25"],
77 [
3,
"M50,50 s10,10,25,25 S75,75,100,100"],
78 [
3,
"M50,50 s10,10,25,25 s0,0,25,25"],
79 [
3,
"M50,50 C10,10,20,20,75,75 S75,75,100,100"],
80 [
3,
"M50,50 C10,10,20,20,75,75 s0,0,25,25"],
81 [
3,
"M50,50 c10,10,20,20,25,25 S75,75,100,100"],
82 [
3,
"M50,50 c10,10,20,20,25,25 s0,0,25,25"],
85 // These are all of the suffixes whose result is not dependent on whether the
86 // preceding segment types are quadratic or cubic types. Each entry is:
88 //
"<fromType><toType>": [fromArguments,
91 // expectedArgumentsAdditive]
95 //
"Mm": [[
10,
20], [
30,
40], [-
30, -
20], [-
120, -
100]]
97 // This will testing interpolating between
"M10,20" and
"m30,40". All of the
98 // these tests assume that the current point is left at
100,
100. So the above
99 // entry represents two kinds of tests, one where additive and one not:
101 //
<path d=
"... M10,20">
102 //
<animate attributeName=
"d" from=
"... M10,20" to=
"... m30,40"/>
107 //
<path d=
"... M10,20">
108 //
<animate attributeName=
"d" from=
"... M10,20" to=
"... m30,40"
112 // where the
"..." is some prefix that leaves the current point at
100,
100.
113 // Each of the suffixes here in gSuffixes will be paired with each of the
114 // prefixes in gPrefixes, all of which leave the current point at
100,
100.
115 // (Thus the above two tests for interpolating between
"M" and
"m" will be
116 // performed many times, with different preceding commands.)
118 // The expected result of the non-additive test is
"m-30,-20". Since the
119 // animation is from an absolute moveto to a relative moveto, we first
120 // convert the
"M10,20" into its relative form, which is
"m-90,-80" due to the
121 // current point being
100,
100. Half way through the animation between
122 //
"m-90,-80" and
"m30,40" is thus
"m-30,-20".
124 // The expected result of the additive test is
"m-120,-100". We take the
125 // halfway value of the animation,
"m-30,-20" and add it on to the underlying
126 // value. Since the underlying value
"M10,20" is an absolute moveto, we first
127 // convert it to relative,
"m-90,-80", and then add the
"m-30,-20" to it,
128 // giving us the result
"m-120,-100".
130 // Same path segment type, no conversion required.
131 MM: [[
10,
20], [
30,
40], [
20,
30], [
30,
50]],
132 LL: [[
10,
20], [
30,
40], [
20,
30], [
30,
50]],
133 CC: [[
10,
20,
30,
40,
50,
60], [
70,
80,
90,
100,
110,
120],
134 [
40,
50,
60,
70,
80,
90], [
50,
70,
90,
110,
130,
150]],
135 QQ: [[
10,
20,
30,
40], [
50,
60,
70,
80], [
30,
40,
50,
60], [
40,
60,
80,
100]],
136 AA: [[
10,
20,
30,
0,
0,
40,
50], [
60,
70,
80,
0,
0,
90,
100],
137 [
35,
45,
55,
0,
0,
65,
75], [
45,
65,
85,
0,
0,
105,
125]],
138 HH: [[
10], [
20], [
15], [
25]],
139 VV: [[
10], [
20], [
15], [
25]],
140 SS: [[
10,
20,
30,
40], [
50,
60,
70,
80], [
30,
40,
50,
60], [
40,
60,
80,
100]],
141 TT: [[
10,
20], [
30,
40], [
20,
30], [
30,
50]],
143 // Relative
<-> absolute conversion.
144 mM: [[
10,
20], [
30,
40], [
70,
80], [
180,
200]],
145 lL: [[
10,
20], [
30,
40], [
70,
80], [
180,
200]],
146 cC: [[
10,
20,
30,
40,
50,
60], [
70,
80,
90,
100,
110,
120],
147 [
90,
100,
110,
120,
130,
140], [
200,
220,
240,
260,
280,
300]],
148 qQ: [[
10,
20,
30,
40], [
50,
60,
70,
80],
149 [
80,
90,
100,
110], [
190,
210,
230,
250]],
150 aA: [[
10,
20,
30,
0,
0,
40,
50], [
60,
70,
80,
0,
0,
90,
100],
151 [
35,
45,
55,
0,
0,
115,
125], [
45,
65,
85,
0,
0,
255,
275]],
152 hH: [[
10], [
20], [
65], [
175]],
153 vV: [[
10], [
20], [
65], [
175]],
154 tT: [[
10,
20], [
30,
40], [
70,
80], [
180,
200]],
155 sS: [[
10,
20,
30,
40], [
50,
60,
70,
80],
156 [
80,
90,
100,
110], [
190,
210,
230,
250]],
159 // Returns an array of property names that exist on an SVGPathSeg object
160 // corresponding to the given segment type, in the order that they would
161 // be present in a path data string.
162 function argumentNames(aType) {
163 return gArgumentNames[aType.toUpperCase()];
166 // Creates and returns a new element and sets some attributes on it.
167 function newElement(aNamespaceURI, aLocalName, aAttributes) {
168 var e = document.createElementNS(aNamespaceURI, aLocalName);
170 for (let [name, value] of Object.entries(aAttributes)) {
171 e.setAttribute(name, value);
177 // Creates and returns a new SVG element and sets some attributes on it.
178 function newSVGElement(aLocalName, aAttributes) {
179 return newElement(
"http://www.w3.org/2000/svg", aLocalName, aAttributes);
182 // Creates a subtest and adds it to the document.
184 // * aPrefixLength/aPrefix the prefix to use
185 // * aFromType/aFromArguments the segment to interpolate from
186 // * aToType/aToArguments the segment to interpolate to
187 // * aExpectedType/aExpectedArguments the expected result of the interpolated
188 // segment half way through the animation
190 // * aAdditive whether the subtest is for an additive
192 function addTest(aPrefixLength, aPrefix, aFromType, aFromArguments,
193 aToType, aToArguments, aExpectedType, aExpectedArguments,
195 var fromPath = aPrefix + aFromType + aFromArguments,
196 toPath = aPrefix + aToType + aToArguments;
198 var path = newSVGElement(
"path", { d: fromPath });
200 newSVGElement(
"animate", { attributeName:
"d",
204 additive: aAdditive ?
"sum" :
"replace" });
205 path.appendChild(animate);
206 gSVG.appendChild(path);
208 gTests.push({ element: path,
209 prefixLength: aPrefixLength,
213 expectedType: aExpectedType,
214 expected: aExpectedArguments,
215 usesAddition: aAdditive });
218 // Generates an array of path segment arguments for the given type. aOffset
219 // is a number to add on to all non-Boolean segment arguments.
220 function generatePathSegmentArguments(aType, aOffset) {
221 var args = new Array(argumentNames(aType).length);
222 for (let i =
0; i < args.length; i++) {
223 args[i] = i *
10 + aOffset;
225 if (aType ==
"A" || aType ==
"a") {
232 // Returns whether interpolating between the two given types is valid.
233 function isValidInterpolation(aFromType, aToType) {
234 return aFromType.toUpperCase() == aToType.toUpperCase();
239 for (let additive of [false, true]) {
240 let indexOfExpectedArguments = additive ?
3 :
2;
242 // Add subtests for each combination of prefix and suffix, and additive
244 for (let [typePair, suffixEntry] of Object.entries(gSuffixes)) {
245 let fromType = typePair[
0],
246 toType = typePair[
1],
247 fromArguments = suffixEntry[
0],
248 toArguments = suffixEntry[
1],
249 expectedArguments = suffixEntry[indexOfExpectedArguments];
251 for (let prefixEntry of gPrefixes) {
252 let [prefixLength, prefix] = prefixEntry;
253 addTest(prefixLength, prefix, fromType, fromArguments,
254 toType, toArguments, toType, expectedArguments, additive);
258 // Test all pairs of segment types that cannot be interpolated between.
259 for (let fromType of gTypes) {
260 let fromArguments = generatePathSegmentArguments(fromType,
0);
261 for (let toType of gTypes) {
262 if (!isValidInterpolation(fromType, toType)) {
263 let toArguments = generatePathSegmentArguments(toType,
1000);
264 addTest(
1,
"M100,100", fromType, fromArguments,
265 toType, toArguments, toType, toArguments, additive);
271 // Move the document time to half way through the animations.
272 gSVG.setCurrentTime(
4);
274 // Inspect the results of each subtest.
275 for (let test of gTests) {
276 let list = test.element.getPathData();
277 is(list.length, test.prefixLength +
1,
278 "Length of animatedPathSegList for interpolation " +
279 (test.usesAddition ?
"with addition " :
"") +
280 " from " + test.from +
" to " + test.to);
282 let seg = list.at(-
1);
285 for (let i =
0; i < test.expected.length; i++) {
286 actual.push(seg.values[i]);
289 is(seg.type + actual, test.expectedType + test.expected,
290 "Path segment for interpolation " +
291 (test.usesAddition ?
"with addition " :
"") +
292 " from " + test.from +
" to " + test.to);
295 // Clear all the tests. We have tons of them attached to the DOM and refresh driver tick will
296 // go through them all by calling animation controller.
302 window.addEventListener(
"load", run);