contributors/aryehgregor/incoming/2d-transforms.html

Fri, 13 Jan 2012 11:10:27 -0700

author
Aryeh Gregor <ayg@aryeh.name>
date
Fri, 13 Jan 2012 11:10:27 -0700
changeset 2507
4aa327aaf29b
parent 2506
9b6e69af53d0
child 2521
5c796f2fe4d3
permissions
-rw-r--r--

Test gibberish keywords for transform

     1 <!doctype html>
     2 <title>CSS 2D Transforms tests</title>
     3 <link rel=author title="Aryeh Gregor" href="ayg@aryeh.name">
     4 <script src=http://w3c-test.org/resources/testharness.js></script>
     5 <script src=http://w3c-test.org/resources/testharnessreport.js></script>
     6 <style>
     7 /* Everything we care about is relative to the border box: in particular,
     8  * percentages are all computed relative to the border box, and
     9  * getBoundingClientRect() is relative to the border box.  The default
    10  * transform-origin is the center of the border box.
    11  *
    12  * Our setup is the test div is nested in two other divs nested in the body.
    13  * The test div has width 80px and height 30px, then 5px each of padding,
    14  * border, and margin.  (This is to catch UAs that compute things relative to
    15  * the content, padding, or margin boxes instead of border.)  Thus its border
    16  * box is 100px wide and 50px tall, and its margin box is 110px by 60px.
    17  *
    18  * The test div's parent has a content area large enough to contain its child,
    19  * and then five pixels of padding.  The same holds for the test div's
    20  * grandparent.  Finally, the body has a content area large enough to hold its
    21  * div child, and -15px of margin so that the test div's border box lines up
    22  * with the top left of the viewport (makes calculations easier).
    23  *
    24  * In this way, the border boxes of the test div and its parent and grandparent
    25  * all have the same center, so the transform-origin is the same with no extra
    26  * work.  However, they have different sizes, so percentages need to be
    27  * computed differently. */
    28 body {
    29 	margin: -15px;
    30 	width: 130px;
    31 	height: 80px;
    32 }
    33 body > div {
    34 	width: 120px;
    35 	height: 70px;
    36 	padding: 5px;
    37 	background: orange;
    38 }
    39 body > div > div {
    40 	width: 110px;
    41 	height: 60px;
    42 	padding: 5px;
    43 	background: yellow;
    44 }
    45 body, div {
    46 	position: relative;
    47 }
    48 #test {
    49 	position: static;
    50 	height: 30px;
    51 	width: 80px;
    52 	padding: 5px;
    53 	border: 5px solid black;
    54 	margin: 5px;
    55 	background: blue;
    56 }
    57 #log { display: none }
    58 </style>
    59 <!-- Extra styles we switch between.  They should all look the same, so which
    60 one we use shouldn't affect results.  To ensure that they look the same even
    61 when transforms are applied to different divs, they're designed to not move the
    62 border box of any of the three nested divs. -->
    63 <style class=switch></style>
    64 <style class=switch>
    65 div { float: left }
    66 </style>
    67 <style class=switch>
    68 div { float: right }
    69 </style>
    70 <style class=switch>
    71 div { float: right }
    72 body { width: 180px; margin-left: -65px }
    73 </style>
    74 <style class=switch>
    75 #test { position: absolute }
    76 </style>
    77 <style class=switch>
    78 body > div > div { border-right: 10px solid transparent; width: 100px }
    79 #test { position: absolute; right: -5px }
    80 </style>
    81 <style class=switch>
    82 body > div > div { padding-left: 10px; width: 105px }
    83 #test { position: relative; left: -5px }
    84 </style>
    85 <style class=switch>
    86 #test { display: inline-block }
    87 </style>
    88 <style class=switch>
    89 #test { display: table }
    90 </style>
    91 <div><div><div id=test></div></div></div>
    92 <div id=log></div>
    93 <script src="transforms.js"></script>
    94 <script>
    95 "use strict";
    97 // Test case-sensitivity and other parsing issues
    98 [
    99 	// "none" and parse errors
   100 	[[// "none"
   101 	  "none", "NONE", "nOnE",
   102 	  // Gibberish
   103 	  "quasit", "QUASIT", "qUaSiT",
   104 	  // No arguments
   105 	  "matrix()", "translate()", "translateX()", "translateY()", "scale()",
   106 	  "scaleX()", "scaleY()", "rotate()", "skewX()", "skewY()",
   107 	  // Too few arguments
   108 	  "matrix(1)", "matrix(1,2)", "matrix(1,2,3)", "matrix(1,2,3,4)",
   109 	  "matrix(1,2,3,4,5)",
   110 	  // Too many arguments
   111 	  "matrix(1,2,3,4,5,6,7)", "translateX(0, 0)", "translateY(0, 0)",
   112 	  "scaleX(1, 1)", "scaleY(1, 1)", "rotate(90deg, 90deg)",
   113 	  "rotate(1, 90deg)", "rotate(90deg, 1)", "rotate(1, 1, 90deg)",
   114 	  "rotate(90deg, 1, 1)", "rotate(90deg, 1, 1, 1)", "skewX(90deg, 90deg)",
   115 	  "skewY(90deg, 90deg)",
   116 	  // <angle> doesn't allow "0" with no unit
   117 	  "rotate(0)", "skewX(0)", "skewY(0)",
   118 	  // The spec says <number> for all entries, so pixels are not allowed in
   119 	  // the last two entries
   120 	  "matrix(1,2,3,4,5px,6)", "matrix(1,2,3,4,5,6px)", "matrix(1,2,3,4,5px,6px)",
   121 	  // The 3D transform spec requires that the 3D versions of these all have
   122 	  // "3d" (matrix3d, translate3d, scale3d, rotate3d), so these are all
   123 	  // parse errors
   124 	  "matrix(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16)",
   125 	  "translate(1px, 1px, 1px)", "scale(1, 1, 1)", "rotate(1, 1, 1, 90deg)"]],
   126 	// Case-sensitivity
   127 	[["matrix(1,2,3,4,5,6)", "MATRIX(1,2,3,4,5,6)", "mAtRiX(1,2,3,4,5,6)"], 1, 2, 3, 4, 5, 6],
   128 	[["translate(1px)", "TRANSLATE(1PX)", "tRaNsLaTe(1Px)"], 1, 0, 0, 1, 1, 0],
   129 	[["translatex(1pt)", "TRANSLATEX(1PT)", "tRaNsLaTeX(1pT)"], 1, 0, 0, 1, 1/0.75, 0],
   130 	[["translatey(1in)", "TRANSLATEY(1IN)", "tRaNsLaTeY(1iN)"], 1, 0, 0, 1, 0, 96],
   131 	[["scale(2)", "SCALE(2)", "sCaLe(2)"], 2, 0, 0, 2, 0, 0],
   132 	[["scalex(2)", "SCALEX(2)", "sCaLeX(2)"], 2, 0, 0, 1, 0, 0],
   133 	[["scaley(2)", "SCALEY(2)", "sCaLeY(2)"], 1, 0, 0, 2, 0, 0],
   134 	// Nothing much uses angle units, so I'll test case-insensitivity for those
   135 	// while I'm here
   136 	[["rotate(90deg)", "ROTATE(90DEG)", "rOtAtE(90dEg)",
   137 	  "rotate(100grad)", "ROTATE(100GRAD)", "rOtAtE(100gRaD)",
   138 	  "rotate(1.57079633rad)", "ROTATE(1.57079633RAD)", "rOtAtE(1.57079633rAd)",
   139 	  "rotate(0.25turn)", "ROTATE(0.25TURN)", "rOtAtE(0.25tUrN)"], 0, 1, -1, 0, 0, 0],
   140 	[["skewx(45deg)", "SKEWX(45DEG)", "sKeWx(45DeG)"], 1, 0, 1, 1, 0, 0],
   141 	[["skewy(45deg)", "SKEWY(45DEG)", "sKeWy(45DeG)"], 1, 1, 0, 1, 0, 0],
   142 ].forEach(function(arr) {
   143 	arr[0].forEach(function(transform, i) {
   144 		var transformKeyword = {0: "TRANSFORM", 1: "tRAnSFoRM", 2: "transform"}[i%3];
   145 		test(function() {
   146 			div.removeAttribute("style");
   147 			div.style[prop.replace("transform", transformKeyword)] = transform;
   148 			testTransformParsing(arr.slice(1));
   149 		}, "Computed value for " + transformKeyword + ": " + transform + " set via CSSOM");
   150 	});
   151 });
   152 div.removeAttribute("style");
   154 test(function() {
   155 	testTransformParsing([]);
   156 }, "Computed value for transform with no style attribute");
   157 test(function() {
   158 	// Not really a transform test, just included for completeness.  It can
   159 	// serve as something of a sanity check.
   160 	testTransformedBoundaryAsserts(0, divWidth, divHeight, 0);
   161 }, "Boundaries with no style attribute");
   163 testTransform("none", []);
   165 // Test style="transform: matrix(*)" (4^6 = 4096 permutations, but we only run
   166 // every 13th to avoid creating excessive numbers of tests)
   167 (function(){
   168 var matrixValues = [-1, 0, 1, 1.72];
   169 var i = 0;
   170 matrixValues.forEach(function(a) {
   171 matrixValues.forEach(function(b) {
   172 matrixValues.forEach(function(c) {
   173 matrixValues.forEach(function(d) {
   174 matrixValues.forEach(function(e) {
   175 matrixValues.forEach(function(f) {
   176 	if (i % 13 == 0) {
   177 		testTransform(
   178 			"matrix(" + a + ", " + b + ", " + c + ", " + d + ", " + e + ", " + f + ")",
   179 			[a, b, c, d, e, f]
   180 		);
   181 	}
   182 	i++;
   183 	i %= 13;
   184 });
   185 });
   186 });
   187 });
   188 });
   189 });
   190 })();
   192 // Test translate()/translateX()/translateY()
   193 percentagesAndLengths.forEach(function(tx) {
   194 	testTransform(
   195 		"translateX(" + tx + ")",
   196 		[1, 0, 0, 1, convertToPx(tx, divWidth), 0]
   197 	);
   198 	// tx is poorly named, since it's used for y here.
   199 	testTransform(
   200 		"translateY(" + tx + ")",
   201 		[1, 0, 0, 1, 0, convertToPx(tx, divHeight)]
   202 	);
   203 	testTransform(
   204 		"translate(" + tx + ")",
   205 		[1, 0, 0, 1, convertToPx(tx, divWidth), 0]
   206 	);
   208 	percentagesAndLengths.forEach(function(ty) {
   209 		testTransform(
   210 			"translate(" + tx + ", " + ty + ")",
   211 			[1, 0, 0, 1, convertToPx(tx, divWidth), convertToPx(ty, divHeight)]
   212 		);
   213 	});
   214 });
   216 // Test scale()/scaleX()/scaleY()
   217 (function(){
   218 var scales = [-2, -1, -0.12, 0, 0.12, 1, 2];
   219 scales.forEach(function(sx) {
   220 	testTransform(
   221 		"scaleX(" + sx + ")",
   222 		[sx, 0, 0, 1, 0, 0]
   223 	);
   224 	// sx is poorly named, since it's used for y here, then for both x and y.
   225 	testTransform(
   226 		"scaleY(" + sx + ")",
   227 		[1, 0, 0, sx, 0, 0]
   228 	);
   229 	testTransform(
   230 		"scale(" + sx + ")",
   231 		[sx, 0, 0, sx, 0, 0]
   232 	);
   234 	scales.forEach(function(sy) {
   235 		testTransform(
   236 			"scale(" + sx + ", " + sy + ")",
   237 			[sx, 0, 0, sy, 0, 0]
   238 		);
   239 	});
   240 });
   241 })();
   243 // Test rotate()
   244 [
   245 	"-7deg", "0deg", "22.5deg", "45deg", "86.451deg", "90deg", "180deg",
   246 	"270deg", "452deg",
   247 	"-1rad", "0rad", "1rad", "6.28rad",
   248 	"0.721turn", "256grad",
   249 ].forEach(function(angle) {
   250 	var rads = convertToRad(angle);
   251 	testTransform(
   252 		"rotate(" + angle + ")",
   253 		[Math.cos(rads), Math.sin(rads), -Math.sin(rads), Math.cos(rads),
   254 		0, 0]
   255 	);
   256 });
   258 // Test skewX()/skewY()
   259 //
   260 // Do not test values close to 90 degrees, because this will cause coordinates
   261 // to get large.  The maximum values for coordinates are (of course) not
   262 // defined, and even if they were, the result would be extremely sensitive to
   263 // rounding error.
   264 [
   265 	"-80deg", "-45deg", "-32.6deg", "-0.05deg", "0deg", "0.05deg", "32.6deg",
   266 	"45deg", "80deg", "300deg",
   267 	"-0.3rad", "0rad", "0.3rad", "2.9rad",
   268 	"0.921turn", "22grad"
   269 ].forEach(function(angle) {
   270 	testTransform(
   271 		"skewX(" + angle + ")",
   272 		[1, 0, Math.tan(convertToRad(angle)), 1, 0, 0]
   273 	);
   274 	testTransform(
   275 		"skewY(" + angle + ")",
   276 		[1, Math.tan(convertToRad(angle)), 0, 1, 0, 0]
   277 	);
   278 });
   280 // Test multiple transformations
   281 (function(){
   282 var transforms = [
   283 	["matrix(4, -7, 2.3, -3.8, 6, 6)", 4, -7, 2.3, -3.8, 6, 6],
   284 	["translate(0.23in, -17pt)",
   285 		1, 0, 0, 1, convertToPx("0.23in"), convertToPx("-17pt")],
   286 	["scale(1.3, -5.6)", 1.3, 0, 0, -5.6, 0, 0],
   287 	["rotate(0.759rad)", Math.cos(0.759), Math.sin(0.759),
   288 		-Math.sin(0.759), Math.cos(0.759), 0, 0],
   289 	["skewX(-0.221rad)", 1, 0, Math.tan(-0.221), 1, 0, 0],
   290 ];
   291 transforms.forEach(function(trans1) {
   292 	testTransform(trans1[0], trans1.slice(1));
   294 	transforms.forEach(function(trans2) {
   295 		var mx = mxmul32(trans1.slice(1), trans2.slice(1));
   297 		// First put both transforms on the test div
   298 		testTransform(trans1[0] + " " + trans2[0], mx);
   300 		// Now put the first on its grandparent, and the second on its parent.
   301 		// No need to test parsing.
   302 		testTransformedBoundary([trans1[0], trans2[0], "none"], mx);
   304 		transforms.forEach(function(trans3) {
   305 			var mx = mxmul32(mxmul32(trans1.slice(1), trans2.slice(1)), trans3.slice(1));
   306 			testTransform(trans1[0] + " " + trans2[0] + " " + trans3[0], mx);
   307 			testTransformedBoundary([trans1[0], trans2[0], trans3[0]], mx);
   308 		});
   309 	});
   310 });
   311 })();
   314 // Test transform-origin with one argument
   315 [
   316 	["none", "50%", "50%"],
   317 	["NONE", "50%", "50%"],
   318 	["nOnE", "50%", "50%"],
   319 	["quasit", "50%", "50%"],
   320 	["top", "50%", "0%"],
   321 	["TOP", "50%", "0%"],
   322 	["tOp", "50%", "0%"],
   323 	["right", "100%", "50%"],
   324 	["RIGHT", "100%", "50%"],
   325 	["rIgHt", "100%", "50%"],
   326 	["bottom", "50%", "100%"],
   327 	["BOTTOM", "50%", "100%"],
   328 	["bOtToM", "50%", "100%"],
   329 	["left", "0%", "50%"],
   330 	["LEFT", "0%", "50%"],
   331 	["lEfT", "0%", "50%"],
   332 	["center", "50%", "50%"],
   333 	["CENTER", "50%", "50%"],
   334 	["cEnTeR", "50%", "50%"],
   335 	["37%", "37%", "50%"],
   336 	["117%", "117%", "50%"],
   337 	["41.2px", "41.2px", "50%"],
   338 	["-31.8px", "-31.8px", "50%"],
   339 ].forEach(function(arr) {
   340 	testTransformOrigin(arr[0], arr[1], arr[2]);
   341 });
   343 // Test transform-origin with two arguments.
   344 ["left", "center", "right"].concat(percentagesAndLengths).forEach(function(arg1) {
   345 	["top", "center", "bottom"].concat(percentagesAndLengths).forEach(function(arg2) {
   346 		testTransformOrigin(arg1 + " " + arg2, arg1, arg2);
   347 	});
   348 });
   350 // FIXME: Three- and four-value variants are not generally implemented; see
   351 // revision history for tests
   352 // https://www.w3.org/Bugs/Public/show_bug.cgi?id=15432
   354 [].forEach.call(document.querySelectorAll("style"), function(style) {style.disabled = true});
   355 </script>

mercurial