├── Rakefile
├── matrix_test.html
├── jsdoc.conf
├── README.markdown
├── matrix-1.2.0.min.js
├── qunit.css
├── test
└── matrix.js
├── matrix.js
└── qunit.js
/Rakefile:
--------------------------------------------------------------------------------
1 | task :default => [:min, :doc]
2 |
3 | task :min do
4 | source_file = "matrix.js"
5 | version = "0.0.0"
6 |
7 | File.foreach(source_file) do |line|
8 | if line =~ /Matrix\.js v(\d+\.\d+\.\d+)/
9 | version = $1
10 | break
11 | end
12 | end
13 |
14 | puts `java -jar yuicompressor-2.4.2.jar #{source_file} > matrix-#{version}.min.js`
15 | end
16 |
17 | task :doc do
18 | puts `java -jar jsdoc-toolkit/jsrun.jar jsdoc-toolkit/app/run.js -c=jsdoc.conf`
19 | end
20 |
--------------------------------------------------------------------------------
/matrix_test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Matrix Demo
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/jsdoc.conf:
--------------------------------------------------------------------------------
1 | /*
2 | This is an example of one way you could set up a configuration file to more
3 | conveniently define some commandline options. You might like to do this if
4 | you frequently reuse the same options. Note that you don't need to define
5 | every option in this file, you can combine a configuration file with
6 | additional options on the commandline if your wish.
7 |
8 | You would include this configuration file by running JsDoc Toolkit like so:
9 | java -jar jsrun.jar app/run.js -c=conf/sample.conf
10 |
11 | */
12 |
13 | {
14 | // source files to use
15 | _: ['matrix.js'],
16 |
17 | // document all functions, even uncommented ones
18 | a: true,
19 |
20 | r: true,
21 |
22 | // including those marked @private
23 | p: true,
24 |
25 | // some extra variables I want to include
26 | D: {generatedBy: "STRd6", copyright: "2010"},
27 |
28 | // use this directory as the output directory
29 | d: "docs",
30 |
31 | // use this template
32 | t: "jsdoc-toolkit/templates/jsdoc"
33 | }
34 |
--------------------------------------------------------------------------------
/README.markdown:
--------------------------------------------------------------------------------
1 | # Matrix.js 1.2.0
2 |
3 | [Matrix.js Documentation](http://strd6.com/matrix.js/docs)
4 |
5 | [Unit Tests](http://strd6.com/matrix.js/matrix_test.html)
6 |
7 | [Guided Demo](http://strd6.com/2010/06/matrix-js-demo/)
8 |
9 | ## Using with HTML5 Canvas
10 |
11 | You'll probably want this (transformation is a Matrix instance):
12 |
13 | function withTransformation(transformation, block) {
14 | context.save();
15 |
16 | context.transform(
17 | transformation.a,
18 | transformation.b,
19 | transformation.c,
20 | transformation.d,
21 | transformation.tx,
22 | transformation.ty
23 | );
24 |
25 | try {
26 | block();
27 | } finally {
28 | context.restore();
29 | }
30 | }
31 |
32 | ## Changelog
33 |
34 | ### v1.2.0
35 |
36 | Added a bunch of utility methods to Points.
37 |
38 | `subtract`, `scale`, `equal`, and `magnitude`.
39 |
40 | ### v1.1.0
41 |
42 | Instance method transforamtions (scale, rotate, and translate) are now consistent with concatenation order.
43 |
44 | var m1 = Matrix().translate(tx, ty).scale(s).rotate(theta);
45 | var m2 = Matrix().concat(Matrix.translation(tx, ty)).concat(Matrix.scale(s)).concat(Matrix.rotation(theta));
46 |
47 | matrixEqual(m1, m2);
48 |
49 | This means that if you were using a version prior to 1.1.0 **the order of concatenation has changed**.
50 |
51 | Added an optional `aboutPoint` parameter to the scale instance and class methods.
52 |
--------------------------------------------------------------------------------
/matrix-1.2.0.min.js:
--------------------------------------------------------------------------------
1 | (function(){function b(c,d){return{x:c||0,y:d||0,equal:function(e){return this.x===e.x&&this.y===e.y},add:function(e){return b(this.x+e.x,this.y+e.y)},subtract:function(e){return b(this.x-e.x,this.y-e.y)},scale:function(e){return b(this.x*e,this.y*e)},magnitude:function(){return b.distance(b(0,0),this)}}}b.distance=function(d,c){return Math.sqrt(Math.pow(c.x-d.x,2)+Math.pow(c.y-d.y,2))};b.direction=function(d,c){return Math.atan2(c.y-d.y,c.x-d.x)};function a(h,f,j,i,g,e){h=h!==undefined?h:1;i=i!==undefined?i:1;return{a:h,b:f||0,c:j||0,d:i,tx:g||0,ty:e||0,concat:function(c){return a(this.a*c.a+this.c*c.b,this.b*c.a+this.d*c.b,this.a*c.c+this.c*c.d,this.b*c.c+this.d*c.d,this.a*c.tx+this.c*c.ty+this.tx,this.b*c.tx+this.d*c.ty+this.ty)},deltaTransformPoint:function(c){return b(this.a*c.x+this.c*c.y,this.b*c.x+this.d*c.y)},inverse:function(){var c=this.a*this.d-this.b*this.c;return a(this.d/c,-this.b/c,-this.c/c,this.a/c,(this.c*this.ty-this.d*this.tx)/c,(this.b*this.tx-this.a*this.ty)/c)},rotate:function(c,d){return this.concat(a.rotation(c,d))},scale:function(k,d,c){return this.concat(a.scale(k,d,c))},transformPoint:function(c){return b(this.a*c.x+this.c*c.y+this.tx,this.b*c.x+this.d*c.y+this.ty)},translate:function(d,c){return this.concat(a.translation(d,c))}}}a.rotation=function(d,e){var c=a(Math.cos(d),Math.sin(d),-Math.sin(d),Math.cos(d));if(e){c=a.translation(e.x,e.y).concat(c).concat(a.translation(-e.x,-e.y))}return c};a.scale=function(f,e,d){e=e||f;var c=a(f,0,0,e);if(d){c=a.translation(d.x,d.y).concat(c).concat(a.translation(-d.x,-d.y))}return c};a.translation=function(d,c){return a(1,0,0,1,d,c)};a.IDENTITY=a();a.HORIZONTAL_FLIP=a(-1,0,0,1);a.VERTICAL_FLIP=a(1,0,0,-1);window.Point=b;window.Matrix=a}());
--------------------------------------------------------------------------------
/qunit.css:
--------------------------------------------------------------------------------
1 | ol#qunit-tests {
2 | font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial;
3 | margin: 0;
4 | padding: 0;
5 | list-style-position: inside;
6 | font-size: smaller; }
7 | ol#qunit-tests li {
8 | padding: 0.4em 0.5em 0.4em 2.5em;
9 | border-bottom: 1px solid white;
10 | font-size: small;
11 | list-style-position: inside; }
12 | ol#qunit-tests li ol {
13 | box-shadow: inset 0px 2px 13px #999999;
14 | -moz-box-shadow: inset 0px 2px 13px #999999;
15 | -webkit-box-shadow: inset 0px 2px 13px #999999;
16 | margin-top: 0.5em;
17 | margin-left: 0;
18 | padding: 0.5em;
19 | background-color: white;
20 | border-radius: 15px;
21 | -moz-border-radius: 15px;
22 | -webkit-border-radius: 15px; }
23 | ol#qunit-tests li li {
24 | border-bottom: none;
25 | margin: 0.5em;
26 | background-color: white;
27 | list-style-position: inside;
28 | padding: 0.4em 0.5em 0.4em 0.5em; }
29 | ol#qunit-tests li li.pass {
30 | border-left: 26px solid #c6e746;
31 | background-color: white;
32 | color: #5e740b; }
33 | ol#qunit-tests li li.fail {
34 | border-left: 26px solid #ee5757;
35 | background-color: white;
36 | color: #710909; }
37 | ol#qunit-tests li.pass {
38 | background-color: #d2e0e6;
39 | color: #528ce0; }
40 | ol#qunit-tests li.fail {
41 | background-color: #ee5757;
42 | color: black; }
43 | ol#qunit-tests li.pass span.test-name {
44 | color: #366097; }
45 | ol#qunit-tests li.fail span.test-name {
46 | color: black; }
47 | ol#qunit-tests li.fail span.module-name {
48 | color: black; }
49 | ol#qunit-tests li li.fail span.test-actual {
50 | color: #ee5757; }
51 | ol#qunit-tests li li.pass span.test-expected, ol#qunit-tests li li.pass span.test-actual {
52 | color: #999999; }
53 | ol#qunit-tests li li.fail span.test-expected {
54 | color: green; }
55 | ol#qunit-tests li strong {
56 | cursor: pointer; }
57 |
58 | h1#qunit-header {
59 | background-color: #0d3349;
60 | margin: 0;
61 | padding: 0.5em 0 0.5em 1em;
62 | color: white;
63 | font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial;
64 | border-top-right-radius: 15px;
65 | border-top-left-radius: 15px;
66 | -moz-border-radius-topright: 15px;
67 | -moz-border-radius-topleft: 15px;
68 | -webkit-border-top-right-radius: 15px;
69 | -webkit-border-top-left-radius: 15px;
70 | text-shadow: rgba(0, 0, 0, 0.5) 4px 4px 1px; }
71 |
72 | h2#qunit-banner {
73 | font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial;
74 | height: 5px;
75 | margin: 0;
76 | padding: 0; }
77 | h2#qunit-banner.qunit-pass {
78 | background-color: #c6e746; }
79 | h2#qunit-banner.qunit-fail {
80 | background-color: #ee5757; }
81 |
82 | #qunit-testrunner-toolbar {
83 | background-color: #ee5757;
84 | font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial;
85 | padding: 0;
86 | /*width:80%; */
87 | padding: 0em 0 0.5em 2em;
88 | font-size: small; }
89 |
90 | h2#qunit-userAgent {
91 | font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial;
92 | background-color: #2b81af;
93 | margin: 0;
94 | padding: 0;
95 | color: white;
96 | font-size: small;
97 | padding: 0.5em 0 0.5em 2.5em;
98 | text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; }
99 |
100 | p#qunit-testresult {
101 | font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial;
102 | margin: 0;
103 | font-size: small;
104 | color: #2b81af;
105 | border-bottom-right-radius: 15px;
106 | border-bottom-left-radius: 15px;
107 | -moz-border-radius-bottomright: 15px;
108 | -moz-border-radius-bottomleft: 15px;
109 | -webkit-border-bottom-right-radius: 15px;
110 | -webkit-border-bottom-left-radius: 15px;
111 | background-color: #d2e0e6;
112 | padding: 0.5em 0.5em 0.5em 2.5em; }
113 |
114 | strong b.fail {
115 | color: #710909; }
116 | strong b.pass {
117 | color: #5e740b; }
118 |
119 | #qunit-fixture {
120 | position: absolute;
121 | top: -10000px;
122 | left: -10000px; }
123 |
--------------------------------------------------------------------------------
/test/matrix.js:
--------------------------------------------------------------------------------
1 | $(function() {
2 | var TOLERANCE = 0.00001;
3 |
4 | function equalEnough(expected, actual, tolerance, message) {
5 | message = message || "" + expected + " within " + tolerance + " of " + actual;
6 | ok(expected + tolerance >= actual && expected - tolerance <= actual, message);
7 | }
8 |
9 | function matrixEqual(m1, m2) {
10 | equalEnough(m1.a, m2.a, TOLERANCE);
11 | equalEnough(m1.b, m2.b, TOLERANCE);
12 | equalEnough(m1.c, m2.c, TOLERANCE);
13 | equalEnough(m1.d, m2.d, TOLERANCE);
14 | equalEnough(m1.tx, m2.tx, TOLERANCE);
15 | equalEnough(m1.ty, m2.ty, TOLERANCE);
16 | }
17 |
18 | test("Matrix() (Identity)", function() {
19 | var matrix = Matrix();
20 |
21 | equals(matrix.a, 1, "a");
22 | equals(matrix.b, 0, "b");
23 | equals(matrix.c, 0, "c");
24 | equals(matrix.d, 1, "d");
25 | equals(matrix.tx, 0, "tx");
26 | equals(matrix.ty, 0, "ty");
27 |
28 | matrixEqual(matrix, Matrix.IDENTITY);
29 | });
30 |
31 | test("Empty Matrix", function() {
32 | var matrix = Matrix(0, 0, 0, 0, 0, 0);
33 |
34 | equals(matrix.a, 0, "a");
35 | equals(matrix.b, 0, "b");
36 | equals(matrix.c, 0, "c");
37 | equals(matrix.d, 0, "d");
38 | equals(matrix.tx, 0, "tx");
39 | equals(matrix.ty, 0, "ty");
40 | });
41 |
42 | test("Matrix.scale", function() {
43 | var matrix = Matrix.scale(2, 2);
44 |
45 | equals(matrix.a, 2, "a");
46 | equals(matrix.b, 0, "b");
47 | equals(matrix.c, 0, "c");
48 | equals(matrix.d, 2, "d");
49 |
50 | matrix = Matrix.scale(3);
51 |
52 | equals(matrix.a, 3, "a");
53 | equals(matrix.b, 0, "b");
54 | equals(matrix.c, 0, "c");
55 | equals(matrix.d, 3, "d");
56 | });
57 |
58 | test("Matrix.scale (about a point)", function() {
59 | var p = Point(5, 17);
60 |
61 | var transformedPoint = Matrix.scale(3, 7, p).transformPoint(p);
62 |
63 | equals(transformedPoint.x, p.x, "Point should remain the same");
64 | equals(transformedPoint.y, p.y, "Point should remain the same");
65 | });
66 |
67 | test("Matrix#scale (about a point)", function() {
68 | var p = Point(3, 11);
69 |
70 | var transformedPoint = Matrix.IDENTITY.scale(3, 7, p).transformPoint(p);
71 |
72 | equals(transformedPoint.x, p.x, "Point should remain the same");
73 | equals(transformedPoint.y, p.y, "Point should remain the same");
74 | });
75 |
76 | test("Matrix.rotation", function() {
77 | var matrix = Matrix.rotation(Math.PI / 2);
78 |
79 | equalEnough(matrix.a, 0, TOLERANCE);
80 | equalEnough(matrix.b, 1, TOLERANCE);
81 | equalEnough(matrix.c,-1, TOLERANCE);
82 | equalEnough(matrix.d, 0, TOLERANCE);
83 | });
84 |
85 | test("Matrix.rotation (about a point)", function() {
86 | var p = Point(11, 7);
87 |
88 | var transformedPoint = Matrix.rotation(Math.PI / 2, p).transformPoint(p);
89 |
90 | equals(transformedPoint.x, p.x, "Point should remain the same");
91 | equals(transformedPoint.y, p.y, "Point should remain the same");
92 | });
93 |
94 | test("Matrix#rotate (about a point)", function() {
95 | var p = Point(8, 5);
96 |
97 | var transformedPoint = Matrix.IDENTITY.rotate(Math.PI / 2, p).transformPoint(p);
98 |
99 | equals(transformedPoint.x, p.x, "Point should remain the same");
100 | equals(transformedPoint.y, p.y, "Point should remain the same");
101 | });
102 |
103 | test("Matrix#inverse (Identity)", function() {
104 | var matrix = Matrix().inverse();
105 |
106 | equals(matrix.a, 1, "a");
107 | equals(matrix.b, 0, "b");
108 | equals(matrix.c, 0, "c");
109 | equals(matrix.d, 1, "d");
110 | equals(matrix.tx, 0, "tx");
111 | equals(matrix.ty, 0, "ty");
112 | });
113 |
114 | test("Matrix#concat", function() {
115 | var matrix = Matrix.rotation(Math.PI / 2).concat(Matrix.rotation(-Math.PI / 2));
116 |
117 | matrixEqual(matrix, Matrix.IDENTITY);
118 | });
119 |
120 | test("Maths", function() {
121 | var a = Matrix(12, 3, 3, 1, 7, 9);
122 | var b = Matrix(3, 8, 3, 2, 1, 5);
123 |
124 | var c = a.concat(b);
125 |
126 | equals(c.a, 60);
127 | equals(c.b, 17);
128 | equals(c.c, 42);
129 | equals(c.d, 11);
130 | equals(c.tx, 34);
131 | equals(c.ty, 17);
132 | });
133 |
134 | test("Order of transformations should match manual concat", function() {
135 | var tx = 10;
136 | var ty = 5;
137 | var theta = Math.PI/3;
138 | var s = 2;
139 |
140 | var m1 = Matrix().translate(tx, ty).scale(s).rotate(theta);
141 | var m2 = Matrix().concat(Matrix.translation(tx, ty)).concat(Matrix.scale(s)).concat(Matrix.rotation(theta));
142 |
143 | matrixEqual(m1, m2);
144 | });
145 |
146 | test("Point#add", function() {
147 | var p1 = Point(5, 6);
148 | var p2 = Point(7, 5);
149 |
150 | var result = p1.add(p2);
151 |
152 | equals(result.x, p1.x + p2.x);
153 | equals(result.y, p1.y + p2.y);
154 | });
155 |
156 | test("Point#subtract", function() {
157 | var p1 = Point(5, 6);
158 | var p2 = Point(7, 5);
159 |
160 | var result = p1.subtract(p2);
161 |
162 | equals(result.x, p1.x - p2.x);
163 | equals(result.y, p1.y - p2.y);
164 | });
165 |
166 | test("Point#scale", function() {
167 | var p1 = Point(5, 6);
168 | var scalar = 2;
169 |
170 | var result = p1.scale(scalar);
171 |
172 | equals(result.x, p1.x * scalar);
173 | equals(result.y, p1.y * scalar);
174 | });
175 |
176 | test("Point#equal", function() {
177 | ok(Point(7, 8).equal(Point(7, 8)));
178 | });
179 |
180 | test("Point#magnitude", function() {
181 | equals(Point(3, 4).magnitude(), 5);
182 | });
183 | });
184 |
--------------------------------------------------------------------------------
/matrix.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Matrix.js v1.2.0
3 | *
4 | * Copyright (c) 2010 STRd6
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | *
24 | * Loosely based on flash:
25 | * http://www.adobe.com/livedocs/flash/9.0/ActionScriptLangRefV3/flash/geom/Matrix.html
26 | */
27 | (function() {
28 |
29 | /**
30 | * Create a new point with given x and y coordinates. If no arguments are given
31 | * defaults to (0, 0).
32 | * @name Point
33 | * @param {Number} [x]
34 | * @param {Number} [y]
35 | * @constructor
36 | */
37 | function Point(x, y) {
38 | this.x = x || 0;
39 | this.y = y || 0;
40 | };
41 |
42 | Point.prototype = {
43 |
44 | clone: function() {
45 | return new Point(this.x,this.y);
46 | },
47 |
48 | /**
49 | * Check whether two points are equal. The x and y values must be exactly
50 | * equal for this method to return true.
51 | * @name equal
52 | * @methodOf Point#
53 | *
54 | * @param {Point} other The point to check for equality.
55 | * @returns true if this point is equal to the other point, false
56 | * otherwise.
57 | * @type Boolean
58 | */
59 | equal: function(other) {
60 | return this.x === other.x && this.y === other.y;
61 | },
62 | /**
63 | * Adds a point to this one and returns the new point.
64 | * @name add
65 | * @methodOf Point#
66 | *
67 | * @param {Point} other The point to add this point to.
68 | * @returns A new point, the sum of both.
69 | * @type Point
70 | */
71 | add: function(other) {
72 | return new Point(this.x + other.x, this.y + other.y);
73 | },
74 | /**
75 | * Subtracts a point from this one and returns the new point.
76 | * @name subtract
77 | * @methodOf Point#
78 | *
79 | * @param {Point} other The point to subtract from this point.
80 | * @returns A new point, the difference of both.
81 | * @type Point
82 | */
83 | subtract: function(other) {
84 | return new Point(this.x - other.x, this.y - other.y);
85 | },
86 | /**
87 | * Multiplies this point by a scalar value and returns the new point.
88 | * @name scale
89 | * @methodOf Point#
90 | *
91 | * @param {Point} scalar The value to scale this point by.
92 | * @returns A new point with x and y multiplied by the scalar value.
93 | * @type Point
94 | */
95 | scale: function(scalar) {
96 | return new Point(this.x * scalar, this.y * scalar);
97 | },
98 |
99 | /**
100 | * Returns the distance of this point from the origin. If this point is
101 | * thought of as a vector this distance is its magnitude.
102 | * @name magnitude
103 | * @methodOf Point#
104 | *
105 | * @returns The distance of this point from the origin.
106 | * @type Number
107 | */
108 | magnitude: function() {
109 | return Point.distance(new Point(0, 0), this);
110 | },
111 | multiply: function(d) {
112 | return new Point(this.x * d, this.y * d);
113 | },
114 |
115 | dot: function(other) {
116 | return this.x * other.x + this.y * other.y;
117 | },
118 |
119 | rotated: function() {
120 | // Return a new vector that's a copy of this vector rotated by a radians
121 | return new Vector(this.x * Math.cos(a) - this.y*Math.sin(a),
122 | this.x * Math.sin(a) + this.y*Math.cos(a));
123 | },
124 |
125 |
126 | angleTo: function(other) {
127 | return Math.acos(this.dot(other) / (Math.abs(this.dist()) * Math.abs(other.dist())));
128 | },
129 |
130 | toUnit: function() {
131 | return this.multiply(1/this.magnitude());
132 | }
133 | };
134 |
135 |
136 | /**
137 | * @param {Point} p1
138 | * @param {Point} p2
139 | * @returns The Euclidean distance between two points.
140 | */
141 | Point.distance = function(p1, p2) {
142 | return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2));
143 | };
144 |
145 | /**
146 | * If you have two dudes, one standing at point p1, and the other
147 | * standing at point p2, then this method will return the direction
148 | * that the dude standing at p1 will need to face to look at p2.
149 | * @param {Point} p1 The starting point.
150 | * @param {Point} p2 The ending point.
151 | * @returns The direction from p1 to p2 in radians.
152 | */
153 | Point.direction = function(p1, p2) {
154 | return Math.atan2(
155 | p2.y - p1.y,
156 | p2.x - p1.x
157 | );
158 | }
159 |
160 | /**
161 | *
162 | * _ _
163 | * | a c tx |
164 | * | b d ty |
165 | * |_0 0 1 _|
166 | *
167 | * Creates a matrix for 2d affine transformations.
168 | *
169 | * concat, inverse, rotate, scale and translate return new matrices with the
170 | * transformations applied. The matrix is not modified in place.
171 | *
172 | * Returns the identity matrix when called with no arguments.
173 | * @name Matrix
174 | * @param {Number} [a]
175 | * @param {Number} [b]
176 | * @param {Number} [c]
177 | * @param {Number} [d]
178 | * @param {Number} [tx]
179 | * @param {Number} [ty]
180 | * @constructor
181 | */
182 | function Matrix(a, b, c, d, tx, ty) {
183 | this.a = a !== undefined ? a : 1;
184 | this.b = b || 0;
185 | this.c = c || 0;
186 | this.d = d !== undefined ? d : 1;
187 | this.tx = tx || 0;
188 | this.ty = ty || 0;
189 | }
190 |
191 | Matrix.prototype = {
192 |
193 | clone: function() {
194 | return new Matrix(
195 | this.a,
196 | this.b,
197 | this.c,
198 | this.d,
199 | this.tx,
200 | this.ty
201 | );
202 | },
203 |
204 | /**
205 | * Returns the result of this matrix multiplied by another matrix
206 | * combining the geometric effects of the two. In mathematical terms,
207 | * concatenating two matrixes is the same as combining them using matrix multiplication.
208 | * If this matrix is A and the matrix passed in is B, the resulting matrix is A x B
209 | * http://mathworld.wolfram.com/MatrixMultiplication.html
210 | * @name concat
211 | * @methodOf Matrix#
212 | *
213 | * @param {Matrix} matrix The matrix to multiply this matrix by.
214 | * @returns The result of the matrix multiplication, a new matrix.
215 | * @type Matrix
216 | */
217 | concat: function(matrix) {
218 | return new Matrix(
219 | this.a * matrix.a + this.c * matrix.b,
220 | this.b * matrix.a + this.d * matrix.b,
221 | this.a * matrix.c + this.c * matrix.d,
222 | this.b * matrix.c + this.d * matrix.d,
223 | this.a * matrix.tx + this.c * matrix.ty + this.tx,
224 | this.b * matrix.tx + this.d * matrix.ty + this.ty
225 | );
226 | },
227 |
228 | /**
229 | * Given a point in the pretransform coordinate space, returns the coordinates of
230 | * that point after the transformation occurs. Unlike the standard transformation
231 | * applied using the transformnew Point() method, the deltaTransformnew Point() method's
232 | * transformation does not consider the translation parameters tx and ty.
233 | * @name deltaTransformPoint
234 | * @methodOf Matrix#
235 | * @see #transformPoint
236 | *
237 | * @return A new point transformed by this matrix ignoring tx and ty.
238 | * @type Point
239 | */
240 | deltaTransformPoint: function(point) {
241 | return new Point(
242 | this.a * point.x + this.c * point.y,
243 | this.b * point.x + this.d * point.y
244 | );
245 | },
246 |
247 | /**
248 | * Returns the inverse of the matrix.
249 | * http://mathworld.wolfram.com/MatrixInverse.html
250 | * @name inverse
251 | * @methodOf Matrix#
252 | *
253 | * @returns A new matrix that is the inverse of this matrix.
254 | * @type Matrix
255 | */
256 | inverse: function() {
257 | var determinant = this.a * this.d - this.b * this.c;
258 | return new Matrix(
259 | this.d / determinant,
260 | -this.b / determinant,
261 | -this.c / determinant,
262 | this.a / determinant,
263 | (this.c * this.ty - this.d * this.tx) / determinant,
264 | (this.b * this.tx - this.a * this.ty) / determinant
265 | );
266 | },
267 |
268 | /**
269 | * Returns a new matrix that corresponds this matrix multiplied by a
270 | * a rotation matrix.
271 | * @name rotate
272 | * @methodOf Matrix#
273 | * @see Matrix.rotation
274 | *
275 | * @param {Number} theta Amount to rotate in radians.
276 | * @param {Point} [aboutPoint] The point about which this rotation occurs. Defaults to (0,0).
277 | * @returns A new matrix, rotated by the specified amount.
278 | * @type Matrix
279 | */
280 | rotate: function(theta, aboutPoint) {
281 | return this.concat(Matrix.rotation(theta, aboutPoint));
282 | },
283 |
284 | /**
285 | * Returns a new matrix that corresponds this matrix multiplied by a
286 | * a scaling matrix.
287 | * @name scale
288 | * @methodOf Matrix#
289 | * @see Matrix.scale
290 | *
291 | * @param {Number} sx
292 | * @param {Number} [sy]
293 | * @param {Point} [aboutPoint] The point that remains fixed during the scaling
294 | * @type Matrix
295 | */
296 | scale: function(sx, sy, aboutPoint) {
297 | return this.concat(Matrix.scale(sx, sy, aboutPoint));
298 | },
299 |
300 | /**
301 | * Returns the result of applying the geometric transformation represented by the
302 | * Matrix object to the specified point.
303 | * @name transformPoint
304 | * @methodOf Matrix#
305 | * @see #deltaTransformPoint
306 | *
307 | * @returns A new point with the transformation applied.
308 | * @type Point
309 | */
310 | transformPoint: function(point) {
311 | return new Point(
312 | this.a * point.x + this.c * point.y + this.tx,
313 | this.b * point.x + this.d * point.y + this.ty
314 | );
315 | },
316 |
317 | /**
318 | * Translates the matrix along the x and y axes, as specified by the tx and ty parameters.
319 | * @name translate
320 | * @methodOf Matrix#
321 | * @see Matrix.translation
322 | *
323 | * @param {Number} tx The translation along the x axis.
324 | * @param {Number} ty The translation along the y axis.
325 | * @returns A new matrix with the translation applied.
326 | * @type Matrix
327 | */
328 | translate: function(tx, ty) {
329 | return this.concat(Matrix.translation(tx, ty));
330 | }
331 | };
332 |
333 | /**
334 | * Creates a matrix transformation that corresponds to the given rotation,
335 | * around (0,0) or the specified point.
336 | * @see Matrix#rotate
337 | *
338 | * @param {Number} theta Rotation in radians.
339 | * @param {Point} [aboutPoint] The point about which this rotation occurs. Defaults to (0,0).
340 | * @returns
341 | * @type Matrix
342 | */
343 | Matrix.rotation = function(theta, aboutPoint) {
344 | var rotationMatrix = new Matrix(
345 | Math.cos(theta),
346 | Math.sin(theta),
347 | -Math.sin(theta),
348 | Math.cos(theta)
349 | );
350 |
351 | if(aboutPoint) {
352 | rotationMatrix =
353 | Matrix.translation(aboutPoint.x, aboutPoint.y).concat(
354 | rotationMatrix
355 | ).concat(
356 | Matrix.translation(-aboutPoint.x, -aboutPoint.y)
357 | );
358 | }
359 |
360 | return rotationMatrix;
361 | };
362 |
363 | /**
364 | * Returns a matrix that corresponds to scaling by factors of sx, sy along
365 | * the x and y axis respectively.
366 | * If only one parameter is given the matrix is scaled uniformly along both axis.
367 | * If the optional aboutPoint parameter is given the scaling takes place
368 | * about the given point.
369 | * @see Matrix#scale
370 | *
371 | * @param {Number} sx The amount to scale by along the x axis or uniformly if no sy is given.
372 | * @param {Number} [sy] The amount to scale by along the y axis.
373 | * @param {Point} [aboutPoint] The point about which the scaling occurs. Defaults to (0,0).
374 | * @returns A matrix transformation representing scaling by sx and sy.
375 | * @type Matrix
376 | */
377 | Matrix.scale = function(sx, sy, aboutPoint) {
378 | sy = sy || sx;
379 |
380 | var scaleMatrix = new Matrix(sx, 0, 0, sy);
381 |
382 | if(aboutPoint) {
383 | scaleMatrix =
384 | Matrix.translation(aboutPoint.x, aboutPoint.y).concat(
385 | scaleMatrix
386 | ).concat(
387 | Matrix.translation(-aboutPoint.x, -aboutPoint.y)
388 | );
389 | }
390 |
391 | return scaleMatrix;
392 | };
393 |
394 | /**
395 | * Returns a matrix that corresponds to a translation of tx, ty.
396 | * @see Matrix#translate
397 | *
398 | * @param {Number} tx The amount to translate in the x direction.
399 | * @param {Number} ty The amount to translate in the y direction.
400 | * @return A matrix transformation representing a translation by tx and ty.
401 | * @type Matrix
402 | */
403 | Matrix.translation = function(tx, ty) {
404 | return new Matrix(1, 0, 0, 1, tx, ty);
405 | };
406 |
407 | /**
408 | * A constant representing the identity matrix.
409 | * @name IDENTITY
410 | * @fieldOf Matrix
411 | */
412 | Matrix.IDENTITY = new Matrix();
413 | /**
414 | * A constant representing the horizontal flip transformation matrix.
415 | * @name HORIZONTAL_FLIP
416 | * @fieldOf Matrix
417 | */
418 | Matrix.HORIZONTAL_FLIP = new Matrix(-1, 0, 0, 1);
419 | /**
420 | * A constant representing the vertical flip transformation matrix.
421 | * @name VERTICAL_FLIP
422 | * @fieldOf Matrix
423 | */
424 | Matrix.VERTICAL_FLIP = new Matrix(1, 0, 0, -1);
425 |
426 | // Export to window
427 | window["Point"] = Point;
428 | window["Matrix"] = Matrix;
429 | }());
430 |
--------------------------------------------------------------------------------
/qunit.js:
--------------------------------------------------------------------------------
1 | /*
2 | * QUnit - A JavaScript Unit Testing Framework
3 | *
4 | * http://docs.jquery.com/QUnit
5 | *
6 | * Copyright (c) 2009 John Resig, Jörn Zaefferer
7 | * Dual licensed under the MIT (MIT-LICENSE.txt)
8 | * and GPL (GPL-LICENSE.txt) licenses.
9 | */
10 |
11 | (function(window) {
12 |
13 | var QUnit = {
14 |
15 | // call on start of module test to prepend name to all tests
16 | module: function(name, testEnvironment) {
17 | config.currentModule = name;
18 |
19 | synchronize(function() {
20 | if ( config.currentModule ) {
21 | QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all );
22 | }
23 |
24 | config.currentModule = name;
25 | config.moduleTestEnvironment = testEnvironment;
26 | config.moduleStats = { all: 0, bad: 0 };
27 |
28 | QUnit.moduleStart( name, testEnvironment );
29 | });
30 | },
31 |
32 | asyncTest: function(testName, expected, callback) {
33 | if ( arguments.length === 2 ) {
34 | callback = expected;
35 | expected = 0;
36 | }
37 |
38 | QUnit.test(testName, expected, callback, true);
39 | },
40 |
41 | test: function(testName, expected, callback, async) {
42 | var name = '' + testName + '', testEnvironment, testEnvironmentArg;
43 |
44 | if ( arguments.length === 2 ) {
45 | callback = expected;
46 | expected = null;
47 | }
48 | // is 2nd argument a testEnvironment?
49 | if ( expected && typeof expected === 'object') {
50 | testEnvironmentArg = expected;
51 | expected = null;
52 | }
53 |
54 | if ( config.currentModule ) {
55 | name = '' + config.currentModule + ": " + name;
56 | }
57 |
58 | if ( !validTest(config.currentModule + ": " + testName) ) {
59 | return;
60 | }
61 |
62 | synchronize(function() {
63 |
64 | testEnvironment = extend({
65 | setup: function() {},
66 | teardown: function() {}
67 | }, config.moduleTestEnvironment);
68 | if (testEnvironmentArg) {
69 | extend(testEnvironment,testEnvironmentArg);
70 | }
71 |
72 | QUnit.testStart( testName, testEnvironment );
73 |
74 | // allow utility functions to access the current test environment
75 | QUnit.current_testEnvironment = testEnvironment;
76 |
77 | config.assertions = [];
78 | config.expected = expected;
79 |
80 | var tests = id("qunit-tests");
81 | if (tests) {
82 | var b = document.createElement("strong");
83 | b.innerHTML = "Running " + name;
84 | var li = document.createElement("li");
85 | li.appendChild( b );
86 | li.id = "current-test-output";
87 | tests.appendChild( li )
88 | }
89 |
90 | try {
91 | if ( !config.pollution ) {
92 | saveGlobal();
93 | }
94 |
95 | testEnvironment.setup.call(testEnvironment);
96 | } catch(e) {
97 | QUnit.ok( false, "Setup failed on " + name + ": " + e.message );
98 | }
99 | });
100 |
101 | synchronize(function() {
102 | if ( async ) {
103 | QUnit.stop();
104 | }
105 |
106 | try {
107 | callback.call(testEnvironment);
108 | } catch(e) {
109 | fail("Test " + name + " died, exception and test follows", e, callback);
110 | QUnit.ok( false, "Died on test #" + (config.assertions.length + 1) + ": " + e.message );
111 | // else next test will carry the responsibility
112 | saveGlobal();
113 |
114 | // Restart the tests if they're blocking
115 | if ( config.blocking ) {
116 | start();
117 | }
118 | }
119 | });
120 |
121 | synchronize(function() {
122 | try {
123 | checkPollution();
124 | testEnvironment.teardown.call(testEnvironment);
125 | } catch(e) {
126 | QUnit.ok( false, "Teardown failed on " + name + ": " + e.message );
127 | }
128 | });
129 |
130 | synchronize(function() {
131 | try {
132 | QUnit.reset();
133 | } catch(e) {
134 | fail("reset() failed, following Test " + name + ", exception and reset fn follows", e, reset);
135 | }
136 |
137 | if ( config.expected && config.expected != config.assertions.length ) {
138 | QUnit.ok( false, "Expected " + config.expected + " assertions, but " + config.assertions.length + " were run" );
139 | }
140 |
141 | var good = 0, bad = 0,
142 | tests = id("qunit-tests");
143 |
144 | config.stats.all += config.assertions.length;
145 | config.moduleStats.all += config.assertions.length;
146 |
147 | if ( tests ) {
148 | var ol = document.createElement("ol");
149 |
150 | for ( var i = 0; i < config.assertions.length; i++ ) {
151 | var assertion = config.assertions[i];
152 |
153 | var li = document.createElement("li");
154 | li.className = assertion.result ? "pass" : "fail";
155 | li.innerHTML = assertion.message || "(no message)";
156 | ol.appendChild( li );
157 |
158 | if ( assertion.result ) {
159 | good++;
160 | } else {
161 | bad++;
162 | config.stats.bad++;
163 | config.moduleStats.bad++;
164 | }
165 | }
166 | if (bad == 0) {
167 | ol.style.display = "none";
168 | }
169 |
170 | var b = document.createElement("strong");
171 | b.innerHTML = name + " (" + bad + ", " + good + ", " + config.assertions.length + ")";
172 |
173 | addEvent(b, "click", function() {
174 | var next = b.nextSibling, display = next.style.display;
175 | next.style.display = display === "none" ? "block" : "none";
176 | });
177 |
178 | addEvent(b, "dblclick", function(e) {
179 | var target = e && e.target ? e.target : window.event.srcElement;
180 | if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) {
181 | target = target.parentNode;
182 | }
183 | if ( window.location && target.nodeName.toLowerCase() === "strong" ) {
184 | window.location.search = "?" + encodeURIComponent(getText([target]).replace(/\(.+\)$/, "").replace(/(^\s*|\s*$)/g, ""));
185 | }
186 | });
187 |
188 | var li = id("current-test-output");
189 | li.id = "";
190 | li.className = bad ? "fail" : "pass";
191 | li.removeChild( li.firstChild );
192 | li.appendChild( b );
193 | li.appendChild( ol );
194 |
195 | if ( bad ) {
196 | var toolbar = id("qunit-testrunner-toolbar");
197 | if ( toolbar ) {
198 | toolbar.style.display = "block";
199 | id("qunit-filter-pass").disabled = null;
200 | id("qunit-filter-missing").disabled = null;
201 | }
202 | }
203 |
204 | } else {
205 | for ( var i = 0; i < config.assertions.length; i++ ) {
206 | if ( !config.assertions[i].result ) {
207 | bad++;
208 | config.stats.bad++;
209 | config.moduleStats.bad++;
210 | }
211 | }
212 | }
213 |
214 | QUnit.testDone( testName, bad, config.assertions.length );
215 |
216 | if ( !window.setTimeout && !config.queue.length ) {
217 | done();
218 | }
219 | });
220 |
221 | if ( window.setTimeout && !config.doneTimer ) {
222 | config.doneTimer = window.setTimeout(function(){
223 | if ( !config.queue.length ) {
224 | done();
225 | } else {
226 | synchronize( done );
227 | }
228 | }, 13);
229 | }
230 | },
231 |
232 | /**
233 | * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through.
234 | */
235 | expect: function(asserts) {
236 | config.expected = asserts;
237 | },
238 |
239 | /**
240 | * Asserts true.
241 | * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
242 | */
243 | ok: function(a, msg) {
244 | msg = escapeHtml(msg);
245 | QUnit.log(a, msg);
246 |
247 | config.assertions.push({
248 | result: !!a,
249 | message: msg
250 | });
251 | },
252 |
253 | /**
254 | * Checks that the first two arguments are equal, with an optional message.
255 | * Prints out both actual and expected values.
256 | *
257 | * Prefered to ok( actual == expected, message )
258 | *
259 | * @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." );
260 | *
261 | * @param Object actual
262 | * @param Object expected
263 | * @param String message (optional)
264 | */
265 | equal: function(actual, expected, message) {
266 | push(expected == actual, actual, expected, message);
267 | },
268 |
269 | notEqual: function(actual, expected, message) {
270 | push(expected != actual, actual, expected, message);
271 | },
272 |
273 | deepEqual: function(actual, expected, message) {
274 | push(QUnit.equiv(actual, expected), actual, expected, message);
275 | },
276 |
277 | notDeepEqual: function(actual, expected, message) {
278 | push(!QUnit.equiv(actual, expected), actual, expected, message);
279 | },
280 |
281 | strictEqual: function(actual, expected, message) {
282 | push(expected === actual, actual, expected, message);
283 | },
284 |
285 | notStrictEqual: function(actual, expected, message) {
286 | push(expected !== actual, actual, expected, message);
287 | },
288 |
289 | raises: function(fn, message) {
290 | try {
291 | fn();
292 | ok( false, message );
293 | }
294 | catch (e) {
295 | ok( true, message );
296 | }
297 | },
298 |
299 | start: function() {
300 | // A slight delay, to avoid any current callbacks
301 | if ( window.setTimeout ) {
302 | window.setTimeout(function() {
303 | if ( config.timeout ) {
304 | clearTimeout(config.timeout);
305 | }
306 |
307 | config.blocking = false;
308 | process();
309 | }, 13);
310 | } else {
311 | config.blocking = false;
312 | process();
313 | }
314 | },
315 |
316 | stop: function(timeout) {
317 | config.blocking = true;
318 |
319 | if ( timeout && window.setTimeout ) {
320 | config.timeout = window.setTimeout(function() {
321 | QUnit.ok( false, "Test timed out" );
322 | QUnit.start();
323 | }, timeout);
324 | }
325 | }
326 |
327 | };
328 |
329 | // Backwards compatibility, deprecated
330 | QUnit.equals = QUnit.equal;
331 | QUnit.same = QUnit.deepEqual;
332 |
333 | // Maintain internal state
334 | var config = {
335 | // The queue of tests to run
336 | queue: [],
337 |
338 | // block until document ready
339 | blocking: true
340 | };
341 |
342 | // Load paramaters
343 | (function() {
344 | var location = window.location || { search: "", protocol: "file:" },
345 | GETParams = location.search.slice(1).split('&');
346 |
347 | for ( var i = 0; i < GETParams.length; i++ ) {
348 | GETParams[i] = decodeURIComponent( GETParams[i] );
349 | if ( GETParams[i] === "noglobals" ) {
350 | GETParams.splice( i, 1 );
351 | i--;
352 | config.noglobals = true;
353 | } else if ( GETParams[i].search('=') > -1 ) {
354 | GETParams.splice( i, 1 );
355 | i--;
356 | }
357 | }
358 |
359 | // restrict modules/tests by get parameters
360 | config.filters = GETParams;
361 |
362 | // Figure out if we're running the tests from a server or not
363 | QUnit.isLocal = !!(location.protocol === 'file:');
364 | })();
365 |
366 | // Expose the API as global variables, unless an 'exports'
367 | // object exists, in that case we assume we're in CommonJS
368 | if ( typeof exports === "undefined" || typeof require === "undefined" ) {
369 | extend(window, QUnit);
370 | window.QUnit = QUnit;
371 | } else {
372 | extend(exports, QUnit);
373 | exports.QUnit = QUnit;
374 | }
375 |
376 | // define these after exposing globals to keep them in these QUnit namespace only
377 | extend(QUnit, {
378 | config: config,
379 |
380 | // Initialize the configuration options
381 | init: function() {
382 | extend(config, {
383 | stats: { all: 0, bad: 0 },
384 | moduleStats: { all: 0, bad: 0 },
385 | started: +new Date,
386 | updateRate: 1000,
387 | blocking: false,
388 | autostart: true,
389 | autorun: false,
390 | assertions: [],
391 | filters: [],
392 | queue: []
393 | });
394 |
395 | var tests = id("qunit-tests"),
396 | banner = id("qunit-banner"),
397 | result = id("qunit-testresult");
398 |
399 | if ( tests ) {
400 | tests.innerHTML = "";
401 | }
402 |
403 | if ( banner ) {
404 | banner.className = "";
405 | }
406 |
407 | if ( result ) {
408 | result.parentNode.removeChild( result );
409 | }
410 | },
411 |
412 | /**
413 | * Resets the test setup. Useful for tests that modify the DOM.
414 | */
415 | reset: function() {
416 | if ( window.jQuery ) {
417 | jQuery("#main, #qunit-fixture").html( config.fixture );
418 | }
419 | },
420 |
421 | /**
422 | * Trigger an event on an element.
423 | *
424 | * @example triggerEvent( document.body, "click" );
425 | *
426 | * @param DOMElement elem
427 | * @param String type
428 | */
429 | triggerEvent: function( elem, type, event ) {
430 | if ( document.createEvent ) {
431 | event = document.createEvent("MouseEvents");
432 | event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView,
433 | 0, 0, 0, 0, 0, false, false, false, false, 0, null);
434 | elem.dispatchEvent( event );
435 |
436 | } else if ( elem.fireEvent ) {
437 | elem.fireEvent("on"+type);
438 | }
439 | },
440 |
441 | // Safe object type checking
442 | is: function( type, obj ) {
443 | return QUnit.objectType( obj ) == type;
444 | },
445 |
446 | objectType: function( obj ) {
447 | if (typeof obj === "undefined") {
448 | return "undefined";
449 |
450 | // consider: typeof null === object
451 | }
452 | if (obj === null) {
453 | return "null";
454 | }
455 |
456 | var type = Object.prototype.toString.call( obj )
457 | .match(/^\[object\s(.*)\]$/)[1] || '';
458 |
459 | switch (type) {
460 | case 'Number':
461 | if (isNaN(obj)) {
462 | return "nan";
463 | } else {
464 | return "number";
465 | }
466 | case 'String':
467 | case 'Boolean':
468 | case 'Array':
469 | case 'Date':
470 | case 'RegExp':
471 | case 'Function':
472 | return type.toLowerCase();
473 | }
474 | if (typeof obj === "object") {
475 | return "object";
476 | }
477 | return undefined;
478 | },
479 |
480 | // Logging callbacks
481 | begin: function() {},
482 | done: function(failures, total) {},
483 | log: function(result, message) {},
484 | testStart: function(name, testEnvironment) {},
485 | testDone: function(name, failures, total) {},
486 | moduleStart: function(name, testEnvironment) {},
487 | moduleDone: function(name, failures, total) {}
488 | });
489 |
490 | if ( typeof document === "undefined" || document.readyState === "complete" ) {
491 | config.autorun = true;
492 | }
493 |
494 | addEvent(window, "load", function() {
495 | QUnit.begin();
496 |
497 | // Initialize the config, saving the execution queue
498 | var oldconfig = extend({}, config);
499 | QUnit.init();
500 | extend(config, oldconfig);
501 |
502 | config.blocking = false;
503 |
504 | var userAgent = id("qunit-userAgent");
505 | if ( userAgent ) {
506 | userAgent.innerHTML = navigator.userAgent;
507 | }
508 |
509 | var toolbar = id("qunit-testrunner-toolbar");
510 | if ( toolbar ) {
511 | toolbar.style.display = "none";
512 |
513 | var filter = document.createElement("input");
514 | filter.type = "checkbox";
515 | filter.id = "qunit-filter-pass";
516 | filter.disabled = true;
517 | addEvent( filter, "click", function() {
518 | var li = document.getElementsByTagName("li");
519 | for ( var i = 0; i < li.length; i++ ) {
520 | if ( li[i].className.indexOf("pass") > -1 ) {
521 | li[i].style.display = filter.checked ? "none" : "";
522 | }
523 | }
524 | });
525 | toolbar.appendChild( filter );
526 |
527 | var label = document.createElement("label");
528 | label.setAttribute("for", "qunit-filter-pass");
529 | label.innerHTML = "Hide passed tests";
530 | toolbar.appendChild( label );
531 |
532 | var missing = document.createElement("input");
533 | missing.type = "checkbox";
534 | missing.id = "qunit-filter-missing";
535 | missing.disabled = true;
536 | addEvent( missing, "click", function() {
537 | var li = document.getElementsByTagName("li");
538 | for ( var i = 0; i < li.length; i++ ) {
539 | if ( li[i].className.indexOf("fail") > -1 && li[i].innerHTML.indexOf('missing test - untested code is broken code') > - 1 ) {
540 | li[i].parentNode.parentNode.style.display = missing.checked ? "none" : "block";
541 | }
542 | }
543 | });
544 | toolbar.appendChild( missing );
545 |
546 | label = document.createElement("label");
547 | label.setAttribute("for", "qunit-filter-missing");
548 | label.innerHTML = "Hide missing tests (untested code is broken code)";
549 | toolbar.appendChild( label );
550 | }
551 |
552 | var main = id('main') || id('qunit-fixture');
553 | if ( main ) {
554 | config.fixture = main.innerHTML;
555 | }
556 |
557 | if (config.autostart) {
558 | QUnit.start();
559 | }
560 | });
561 |
562 | function done() {
563 | if ( config.doneTimer && window.clearTimeout ) {
564 | window.clearTimeout( config.doneTimer );
565 | config.doneTimer = null;
566 | }
567 |
568 | if ( config.queue.length ) {
569 | config.doneTimer = window.setTimeout(function(){
570 | if ( !config.queue.length ) {
571 | done();
572 | } else {
573 | synchronize( done );
574 | }
575 | }, 13);
576 |
577 | return;
578 | }
579 |
580 | config.autorun = true;
581 |
582 | // Log the last module results
583 | if ( config.currentModule ) {
584 | QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all );
585 | }
586 |
587 | var banner = id("qunit-banner"),
588 | tests = id("qunit-tests"),
589 | html = ['Tests completed in ',
590 | +new Date - config.started, ' milliseconds.
',
591 | '', config.stats.all - config.stats.bad, ' tests of ', config.stats.all, ' passed, ', config.stats.bad,' failed.'].join('');
592 |
593 | if ( banner ) {
594 | banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass");
595 | }
596 |
597 | if ( tests ) {
598 | var result = id("qunit-testresult");
599 |
600 | if ( !result ) {
601 | result = document.createElement("p");
602 | result.id = "qunit-testresult";
603 | result.className = "result";
604 | tests.parentNode.insertBefore( result, tests.nextSibling );
605 | }
606 |
607 | result.innerHTML = html;
608 | }
609 |
610 | QUnit.done( config.stats.bad, config.stats.all );
611 | }
612 |
613 | function validTest( name ) {
614 | var i = config.filters.length,
615 | run = false;
616 |
617 | if ( !i ) {
618 | return true;
619 | }
620 |
621 | while ( i-- ) {
622 | var filter = config.filters[i],
623 | not = filter.charAt(0) == '!';
624 |
625 | if ( not ) {
626 | filter = filter.slice(1);
627 | }
628 |
629 | if ( name.indexOf(filter) !== -1 ) {
630 | return !not;
631 | }
632 |
633 | if ( not ) {
634 | run = true;
635 | }
636 | }
637 |
638 | return run;
639 | }
640 |
641 | function escapeHtml(s) {
642 | s = s === null ? "" : s + "";
643 | return s.replace(/[\&"<>\\]/g, function(s) {
644 | switch(s) {
645 | case "&": return "&";
646 | case "\\": return "\\\\";;
647 | case '"': return '\"';;
648 | case "<": return "<";
649 | case ">": return ">";
650 | default: return s;
651 | }
652 | });
653 | }
654 |
655 | function push(result, actual, expected, message) {
656 | message = escapeHtml(message) || (result ? "okay" : "failed");
657 | message = '' + message + "";
658 | expected = escapeHtml(QUnit.jsDump.parse(expected));
659 | actual = escapeHtml(QUnit.jsDump.parse(actual));
660 | var output = message + ', expected: ' + expected + '';
661 | if (actual != expected) {
662 | output += ' result: ' + actual + ', diff: ' + QUnit.diff(expected, actual);
663 | }
664 |
665 | // can't use ok, as that would double-escape messages
666 | QUnit.log(result, output);
667 | config.assertions.push({
668 | result: !!result,
669 | message: output
670 | });
671 | }
672 |
673 | function synchronize( callback ) {
674 | config.queue.push( callback );
675 |
676 | if ( config.autorun && !config.blocking ) {
677 | process();
678 | }
679 | }
680 |
681 | function process() {
682 | var start = (new Date()).getTime();
683 |
684 | while ( config.queue.length && !config.blocking ) {
685 | if ( config.updateRate <= 0 || (((new Date()).getTime() - start) < config.updateRate) ) {
686 | config.queue.shift()();
687 |
688 | } else {
689 | setTimeout( process, 13 );
690 | break;
691 | }
692 | }
693 | }
694 |
695 | function saveGlobal() {
696 | config.pollution = [];
697 |
698 | if ( config.noglobals ) {
699 | for ( var key in window ) {
700 | config.pollution.push( key );
701 | }
702 | }
703 | }
704 |
705 | function checkPollution( name ) {
706 | var old = config.pollution;
707 | saveGlobal();
708 |
709 | var newGlobals = diff( old, config.pollution );
710 | if ( newGlobals.length > 0 ) {
711 | ok( false, "Introduced global variable(s): " + newGlobals.join(", ") );
712 | config.expected++;
713 | }
714 |
715 | var deletedGlobals = diff( config.pollution, old );
716 | if ( deletedGlobals.length > 0 ) {
717 | ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") );
718 | config.expected++;
719 | }
720 | }
721 |
722 | // returns a new Array with the elements that are in a but not in b
723 | function diff( a, b ) {
724 | var result = a.slice();
725 | for ( var i = 0; i < result.length; i++ ) {
726 | for ( var j = 0; j < b.length; j++ ) {
727 | if ( result[i] === b[j] ) {
728 | result.splice(i, 1);
729 | i--;
730 | break;
731 | }
732 | }
733 | }
734 | return result;
735 | }
736 |
737 | function fail(message, exception, callback) {
738 | if ( typeof console !== "undefined" && console.error && console.warn ) {
739 | console.error(message);
740 | console.error(exception);
741 | console.warn(callback.toString());
742 |
743 | } else if ( window.opera && opera.postError ) {
744 | opera.postError(message, exception, callback.toString);
745 | }
746 | }
747 |
748 | function extend(a, b) {
749 | for ( var prop in b ) {
750 | a[prop] = b[prop];
751 | }
752 |
753 | return a;
754 | }
755 |
756 | function addEvent(elem, type, fn) {
757 | if ( elem.addEventListener ) {
758 | elem.addEventListener( type, fn, false );
759 | } else if ( elem.attachEvent ) {
760 | elem.attachEvent( "on" + type, fn );
761 | } else {
762 | fn();
763 | }
764 | }
765 |
766 | function id(name) {
767 | return !!(typeof document !== "undefined" && document && document.getElementById) &&
768 | document.getElementById( name );
769 | }
770 |
771 | // Test for equality any JavaScript type.
772 | // Discussions and reference: http://philrathe.com/articles/equiv
773 | // Test suites: http://philrathe.com/tests/equiv
774 | // Author: Philippe Rathé
775 | QUnit.equiv = function () {
776 |
777 | var innerEquiv; // the real equiv function
778 | var callers = []; // stack to decide between skip/abort functions
779 | var parents = []; // stack to avoiding loops from circular referencing
780 |
781 | // Call the o related callback with the given arguments.
782 | function bindCallbacks(o, callbacks, args) {
783 | var prop = QUnit.objectType(o);
784 | if (prop) {
785 | if (QUnit.objectType(callbacks[prop]) === "function") {
786 | return callbacks[prop].apply(callbacks, args);
787 | } else {
788 | return callbacks[prop]; // or undefined
789 | }
790 | }
791 | }
792 |
793 | var callbacks = function () {
794 |
795 | // for string, boolean, number and null
796 | function useStrictEquality(b, a) {
797 | if (b instanceof a.constructor || a instanceof b.constructor) {
798 | // to catch short annotaion VS 'new' annotation of a declaration
799 | // e.g. var i = 1;
800 | // var j = new Number(1);
801 | return a == b;
802 | } else {
803 | return a === b;
804 | }
805 | }
806 |
807 | return {
808 | "string": useStrictEquality,
809 | "boolean": useStrictEquality,
810 | "number": useStrictEquality,
811 | "null": useStrictEquality,
812 | "undefined": useStrictEquality,
813 |
814 | "nan": function (b) {
815 | return isNaN(b);
816 | },
817 |
818 | "date": function (b, a) {
819 | return QUnit.objectType(b) === "date" && a.valueOf() === b.valueOf();
820 | },
821 |
822 | "regexp": function (b, a) {
823 | return QUnit.objectType(b) === "regexp" &&
824 | a.source === b.source && // the regex itself
825 | a.global === b.global && // and its modifers (gmi) ...
826 | a.ignoreCase === b.ignoreCase &&
827 | a.multiline === b.multiline;
828 | },
829 |
830 | // - skip when the property is a method of an instance (OOP)
831 | // - abort otherwise,
832 | // initial === would have catch identical references anyway
833 | "function": function () {
834 | var caller = callers[callers.length - 1];
835 | return caller !== Object &&
836 | typeof caller !== "undefined";
837 | },
838 |
839 | "array": function (b, a) {
840 | var i, j, loop;
841 | var len;
842 |
843 | // b could be an object literal here
844 | if ( ! (QUnit.objectType(b) === "array")) {
845 | return false;
846 | }
847 |
848 | len = a.length;
849 | if (len !== b.length) { // safe and faster
850 | return false;
851 | }
852 |
853 | //track reference to avoid circular references
854 | parents.push(a);
855 | for (i = 0; i < len; i++) {
856 | loop = false;
857 | for(j=0;j= 0) {
1002 | type = "array";
1003 | } else {
1004 | type = typeof obj;
1005 | }
1006 | return type;
1007 | },
1008 | separator:function() {
1009 | return this.multiline ? this.HTML ? '
' : '\n' : this.HTML ? ' ' : ' ';
1010 | },
1011 | indent:function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing
1012 | if ( !this.multiline )
1013 | return '';
1014 | var chr = this.indentChar;
1015 | if ( this.HTML )
1016 | chr = chr.replace(/\t/g,' ').replace(/ /g,' ');
1017 | return Array( this._depth_ + (extra||0) ).join(chr);
1018 | },
1019 | up:function( a ) {
1020 | this._depth_ += a || 1;
1021 | },
1022 | down:function( a ) {
1023 | this._depth_ -= a || 1;
1024 | },
1025 | setParser:function( name, parser ) {
1026 | this.parsers[name] = parser;
1027 | },
1028 | // The next 3 are exposed so you can use them
1029 | quote:quote,
1030 | literal:literal,
1031 | join:join,
1032 | //
1033 | _depth_: 1,
1034 | // This is the list of parsers, to modify them, use jsDump.setParser
1035 | parsers:{
1036 | window: '[Window]',
1037 | document: '[Document]',
1038 | error:'[ERROR]', //when no parser is found, shouldn't happen
1039 | unknown: '[Unknown]',
1040 | 'null':'null',
1041 | undefined:'undefined',
1042 | 'function':function( fn ) {
1043 | var ret = 'function',
1044 | name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE
1045 | if ( name )
1046 | ret += ' ' + name;
1047 | ret += '(';
1048 |
1049 | ret = [ ret, this.parse( fn, 'functionArgs' ), '){'].join('');
1050 | return join( ret, this.parse(fn,'functionCode'), '}' );
1051 | },
1052 | array: array,
1053 | nodelist: array,
1054 | arguments: array,
1055 | object:function( map ) {
1056 | var ret = [ ];
1057 | this.up();
1058 | for ( var key in map )
1059 | ret.push( this.parse(key,'key') + ': ' + this.parse(map[key]) );
1060 | this.down();
1061 | return join( '{', ret, '}' );
1062 | },
1063 | node:function( node ) {
1064 | var open = this.HTML ? '<' : '<',
1065 | close = this.HTML ? '>' : '>';
1066 |
1067 | var tag = node.nodeName.toLowerCase(),
1068 | ret = open + tag;
1069 |
1070 | for ( var a in this.DOMAttrs ) {
1071 | var val = node[this.DOMAttrs[a]];
1072 | if ( val )
1073 | ret += ' ' + a + '=' + this.parse( val, 'attribute' );
1074 | }
1075 | return ret + close + open + '/' + tag + close;
1076 | },
1077 | functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function
1078 | var l = fn.length;
1079 | if ( !l ) return '';
1080 |
1081 | var args = Array(l);
1082 | while ( l-- )
1083 | args[l] = String.fromCharCode(97+l);//97 is 'a'
1084 | return ' ' + args.join(', ') + ' ';
1085 | },
1086 | key:quote, //object calls it internally, the key part of an item in a map
1087 | functionCode:'[code]', //function calls it internally, it's the content of the function
1088 | attribute:quote, //node calls it internally, it's an html attribute value
1089 | string:quote,
1090 | date:quote,
1091 | regexp:literal, //regex
1092 | number:literal,
1093 | 'boolean':literal
1094 | },
1095 | DOMAttrs:{//attributes to dump from nodes, name=>realName
1096 | id:'id',
1097 | name:'name',
1098 | 'class':'className'
1099 | },
1100 | HTML:false,//if true, entities are escaped ( <, >, \t, space and \n )
1101 | indentChar:' ',//indentation unit
1102 | multiline:false //if true, items in a collection, are separated by a \n, else just a space.
1103 | };
1104 |
1105 | return jsDump;
1106 | })();
1107 |
1108 | // from Sizzle.js
1109 | function getText( elems ) {
1110 | var ret = "", elem;
1111 |
1112 | for ( var i = 0; elems[i]; i++ ) {
1113 | elem = elems[i];
1114 |
1115 | // Get the text from text nodes and CDATA nodes
1116 | if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
1117 | ret += elem.nodeValue;
1118 |
1119 | // Traverse everything else, except comment nodes
1120 | } else if ( elem.nodeType !== 8 ) {
1121 | ret += getText( elem.childNodes );
1122 | }
1123 | }
1124 |
1125 | return ret;
1126 | };
1127 |
1128 | /*
1129 | * Javascript Diff Algorithm
1130 | * By John Resig (http://ejohn.org/)
1131 | * Modified by Chu Alan "sprite"
1132 | *
1133 | * Released under the MIT license.
1134 | *
1135 | * More Info:
1136 | * http://ejohn.org/projects/javascript-diff-algorithm/
1137 | *
1138 | * Usage: QUnit.diff(expected, actual)
1139 | *
1140 | * QUnit.diff("the quick brown fox jumped over", "the quick fox jumps over") == "the quick brown fox jumped jumps over"
1141 | */
1142 | QUnit.diff = (function() {
1143 | function escape(s){
1144 | var n = s;
1145 | n = n.replace(/&/g, "&");
1146 | n = n.replace(//g, ">");
1148 | n = n.replace(/"/g, """);
1149 |
1150 | return n;
1151 | }
1152 |
1153 | function diff(o, n){
1154 | var ns = new Object();
1155 | var os = new Object();
1156 |
1157 | for (var i = 0; i < n.length; i++) {
1158 | if (ns[n[i]] == null)
1159 | ns[n[i]] = {
1160 | rows: new Array(),
1161 | o: null
1162 | };
1163 | ns[n[i]].rows.push(i);
1164 | }
1165 |
1166 | for (var i = 0; i < o.length; i++) {
1167 | if (os[o[i]] == null)
1168 | os[o[i]] = {
1169 | rows: new Array(),
1170 | n: null
1171 | };
1172 | os[o[i]].rows.push(i);
1173 | }
1174 |
1175 | for (var i in ns) {
1176 | if (ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1) {
1177 | n[ns[i].rows[0]] = {
1178 | text: n[ns[i].rows[0]],
1179 | row: os[i].rows[0]
1180 | };
1181 | o[os[i].rows[0]] = {
1182 | text: o[os[i].rows[0]],
1183 | row: ns[i].rows[0]
1184 | };
1185 | }
1186 | }
1187 |
1188 | for (var i = 0; i < n.length - 1; i++) {
1189 | if (n[i].text != null && n[i + 1].text == null && n[i].row + 1 < o.length && o[n[i].row + 1].text == null &&
1190 | n[i + 1] == o[n[i].row + 1]) {
1191 | n[i + 1] = {
1192 | text: n[i + 1],
1193 | row: n[i].row + 1
1194 | };
1195 | o[n[i].row + 1] = {
1196 | text: o[n[i].row + 1],
1197 | row: i + 1
1198 | };
1199 | }
1200 | }
1201 |
1202 | for (var i = n.length - 1; i > 0; i--) {
1203 | if (n[i].text != null && n[i - 1].text == null && n[i].row > 0 && o[n[i].row - 1].text == null &&
1204 | n[i - 1] == o[n[i].row - 1]) {
1205 | n[i - 1] = {
1206 | text: n[i - 1],
1207 | row: n[i].row - 1
1208 | };
1209 | o[n[i].row - 1] = {
1210 | text: o[n[i].row - 1],
1211 | row: i - 1
1212 | };
1213 | }
1214 | }
1215 |
1216 | return {
1217 | o: o,
1218 | n: n
1219 | };
1220 | }
1221 |
1222 | return function(o, n){
1223 | o = o.replace(/\s+$/, '');
1224 | n = n.replace(/\s+$/, '');
1225 | var out = diff(o == "" ? [] : o.split(/\s+/), n == "" ? [] : n.split(/\s+/));
1226 |
1227 | var str = "";
1228 |
1229 | var oSpace = o.match(/\s+/g);
1230 | if (oSpace == null) {
1231 | oSpace = ["\n"];
1232 | }
1233 | else {
1234 | oSpace.push("\n");
1235 | }
1236 | var nSpace = n.match(/\s+/g);
1237 | if (nSpace == null) {
1238 | nSpace = ["\n"];
1239 | }
1240 | else {
1241 | nSpace.push("\n");
1242 | }
1243 |
1244 | if (out.n.length == 0) {
1245 | for (var i = 0; i < out.o.length; i++) {
1246 | str += '' + escape(out.o[i]) + oSpace[i] + "";
1247 | }
1248 | }
1249 | else {
1250 | if (out.n[0].text == null) {
1251 | for (n = 0; n < out.o.length && out.o[n].text == null; n++) {
1252 | str += '' + escape(out.o[n]) + oSpace[n] + "";
1253 | }
1254 | }
1255 |
1256 | for (var i = 0; i < out.n.length; i++) {
1257 | if (out.n[i].text == null) {
1258 | str += '' + escape(out.n[i]) + nSpace[i] + "";
1259 | }
1260 | else {
1261 | var pre = "";
1262 |
1263 | for (n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++) {
1264 | pre += '' + escape(out.o[n]) + oSpace[n] + "";
1265 | }
1266 | str += " " + out.n[i].text + nSpace[i] + pre;
1267 | }
1268 | }
1269 | }
1270 |
1271 | return str;
1272 | }
1273 | })();
1274 |
1275 | })(this);
1276 |
--------------------------------------------------------------------------------