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