test_pathAnimInterpolation.xhtml (11511B)
1 <html xmlns="http://www.w3.org/1999/xhtml"> 2 <!-- 3 https://bugzilla.mozilla.org/show_bug.cgi?id=619498 4 --> 5 <head> 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" /> 9 </head> 10 <body> 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(); 16 17 var gSVG = document.getElementById("svg"); 18 19 // Array of all subtests to run. This is populated by addTest. 20 var gTests = []; 21 22 // Array of all path segment types. 23 var gTypes = "ZMmLlCcQqAaHhVvSsTt".split(""); 24 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 = { 28 Z: [], 29 M: ["x", "y"], 30 L: ["x", "y"], 31 C: ["x1", "y1", "x2", "y2", "x", "y"], 32 Q: ["x1", "y1", "x", "y"], 33 A: ["r1", "r2", "angle", "largeArcFlag", "sweepFlag", "x", "y"], 34 H: ["x"], 35 V: ["y"], 36 S: ["x2", "y2", "x", "y"], 37 T: ["x", "y"], 38 }; 39 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. 43 var gPrefixes = [ 44 [1, "M100,100"], 45 [2, "M50,50 M100,100"], 46 [2, "M50,50 m50,50"], 47 [2, "M50,50 L100,100"], 48 [2, "M50,50 l50,50"], 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"], 55 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"], 60 [2, "M75,75 t25,25"], 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"], 69 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"], 83 ]; 84 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: 87 // 88 // "<fromType><toType>": [fromArguments, 89 // toArguments, 90 // expectedArguments, 91 // expectedArgumentsAdditive] 92 // 93 // As an example: 94 // 95 // "Mm": [[10, 20], [30, 40], [-30, -20], [-120, -100]] 96 // 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: 100 // 101 // <path d="... M10,20"> 102 // <animate attributeName="d" from="... M10,20" to="... m30,40"/> 103 // </path> 104 // 105 // and 106 // 107 // <path d="... M10,20"> 108 // <animate attributeName="d" from="... M10,20" to="... m30,40" 109 // additive="sum"/> 110 // </path> 111 // 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.) 117 // 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". 123 // 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". 129 var gSuffixes = { 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]], 142 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]], 157 }; 158 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()]; 164 } 165 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); 169 if (aAttributes) { 170 for (let [name, value] of Object.entries(aAttributes)) { 171 e.setAttribute(name, value); 172 } 173 } 174 return e; 175 } 176 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); 180 } 181 182 // Creates a subtest and adds it to the document. 183 // 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 189 // duration 190 // * aAdditive whether the subtest is for an additive 191 // animation 192 function addTest(aPrefixLength, aPrefix, aFromType, aFromArguments, 193 aToType, aToArguments, aExpectedType, aExpectedArguments, 194 aAdditive) { 195 var fromPath = aPrefix + aFromType + aFromArguments, 196 toPath = aPrefix + aToType + aToArguments; 197 198 var path = newSVGElement("path", { d: fromPath }); 199 var animate = 200 newSVGElement("animate", { attributeName: "d", 201 from: fromPath, 202 to: toPath, 203 dur: "8s", 204 additive: aAdditive ? "sum" : "replace" }); 205 path.appendChild(animate); 206 gSVG.appendChild(path); 207 208 gTests.push({ element: path, 209 prefixLength: aPrefixLength, 210 from: fromPath, 211 to: toPath, 212 toType: aToType, 213 expectedType: aExpectedType, 214 expected: aExpectedArguments, 215 usesAddition: aAdditive }); 216 } 217 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; 224 } 225 if (aType == "A" || aType == "a") { 226 args[3] = 0; 227 args[4] = 0; 228 } 229 return args; 230 } 231 232 // Returns whether interpolating between the two given types is valid. 233 function isValidInterpolation(aFromType, aToType) { 234 return aFromType.toUpperCase() == aToType.toUpperCase(); 235 } 236 237 // Runs the test. 238 function run() { 239 for (let additive of [false, true]) { 240 let indexOfExpectedArguments = additive ? 3 : 2; 241 242 // Add subtests for each combination of prefix and suffix, and additive 243 // or not. 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]; 250 251 for (let prefixEntry of gPrefixes) { 252 let [prefixLength, prefix] = prefixEntry; 253 addTest(prefixLength, prefix, fromType, fromArguments, 254 toType, toArguments, toType, expectedArguments, additive); 255 } 256 } 257 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); 266 } 267 } 268 } 269 } 270 271 // Move the document time to half way through the animations. 272 gSVG.setCurrentTime(4); 273 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); 281 282 let seg = list.at(-1); 283 284 let actual = []; 285 for (let i = 0; i < test.expected.length; i++) { 286 actual.push(seg.values[i]); 287 } 288 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); 293 } 294 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. 297 gSVG.remove(); 298 299 SimpleTest.finish(); 300 } 301 302 window.addEventListener("load", run); 303 ]]></script> 304 </body> 305 </html>