(https://github.com/jwarby)",
17 | "repository": {
18 | "type": "git",
19 | "url": "git+https://github.com/jwarby/jquery-awesome-cursor.git"
20 | },
21 | "license": "MIT",
22 | "devDependencies": {
23 | "bower": "^1.7.9",
24 | "grunt": "^1.0.1",
25 | "grunt-contrib-clean": "^1.0.0",
26 | "grunt-contrib-concat": "^1.0.1",
27 | "grunt-contrib-connect": "^1.0.1",
28 | "grunt-contrib-jshint": "^1.0.0",
29 | "grunt-contrib-qunit": "^1.2.0",
30 | "grunt-contrib-uglify": "^1.0.1",
31 | "grunt-contrib-watch": "^1.0.0",
32 | "grunt-karma": "^0.12.2",
33 | "grunt-unified-manifest": "git://github.com/jwarby/grunt-unified-manifest.git",
34 | "jquery": "^2.2.3",
35 | "jshint-stylish": "^2.1.0",
36 | "karma": "^0.13.22",
37 | "karma-chrome-launcher": "^0.2.3",
38 | "karma-firefox-launcher": "^0.1.7",
39 | "karma-qunit": "^0.1.9",
40 | "load-grunt-tasks": "^3.5.0",
41 | "qunitjs": "^2.0.0-rc1",
42 | "time-grunt": "^1.3.0"
43 | },
44 | "optionalDependencies": {
45 | "font-awesome": "4.x"
46 | },
47 | "peerDependencies": {
48 | "jquery": "2.x"
49 | },
50 | "engines": {
51 | "node": ">=0.10.0"
52 | },
53 | "main": "dist/jquery.awesome-cursor",
54 | "scripts": {
55 | "test": "grunt test"
56 | },
57 | "directories": {
58 | "test": "test"
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "curly": true,
3 | "eqeqeq": true,
4 | "immed": true,
5 | "latedef": true,
6 | "newcap": true,
7 | "noarg": true,
8 | "sub": true,
9 | "undef": true,
10 | "unused": true,
11 | "boss": true,
12 | "eqnull": true,
13 | "browser": true,
14 | "predef": ["jQuery", "require", "define"]
15 | }
16 |
--------------------------------------------------------------------------------
/src/jquery.awesome-cursor.js:
--------------------------------------------------------------------------------
1 | ;(function(global, factory) {
2 | if (typeof define === 'function' && define.amd) {
3 | define(['jquery'], factory);
4 | } else if (typeof exports === 'object') {
5 | factory(require('jquery'));
6 | } else {
7 | factory(global.jQuery);
8 | }
9 | })(this, function($) {
10 | 'use strict';
11 |
12 | /**
13 | * Parse the user-supplied hotspot string. Hotspot values as strings are used
14 | * to set the cursor based on a human-readable value.
15 | *
16 | * ## Examples
17 | *
18 | * - `hotspot: 'center'`: the hotspot is in the center of the cursor
19 | * - `hotspot: 'center left'`: the hotspot is centered vertically, and fixed
20 | * to the left of the cursor horizontally
21 | * - `hotspot: 'top right'`: the hotspot is at the top right
22 | * - `hotspot: 'center top'`: the hotspot is centered horizontally, and fixed
23 | * to the top of the cursor vertically
24 | *
25 | * @param {String} hotspot The string descriptor for the hotspot location
26 | * @param {Number} size The size of the cursor
27 | *
28 | * @return {[Number]} an array with two elements, the x and y offsets for the
29 | * hotspot
30 | *
31 | * @throws {Error} if `hotspot` is not a string, or `cursorSize` is not a
32 | * number
33 | */
34 | var parseHotspotString = function(hotspot, cursorSize) {
35 | var xOffset = 0,
36 | yOffset = 0;
37 |
38 | if (typeof hotspot !== 'string') {
39 | $.error('Hotspot value is not a string and could not be parsed');
40 | }
41 |
42 | if (typeof cursorSize !== 'number') {
43 | $.error('Cursor size must be a number');
44 | }
45 |
46 | hotspot.split(' ').forEach(function(part) {
47 | switch (part) {
48 | case 'center':
49 | xOffset = cursorSize / 2;
50 | yOffset = cursorSize / 2;
51 | break;
52 | case 'top':
53 | yOffset = 0;
54 | break;
55 | case 'bottom':
56 |
57 | /* Browsers will default to 0 0 if yOffset is the very last pixel,
58 | * hence - 1
59 | */
60 | yOffset = cursorSize - 1;
61 | break;
62 | case 'left':
63 | xOffset = 0;
64 | break;
65 | case 'right':
66 | xOffset = cursorSize - 1;
67 | break;
68 | }
69 | });
70 |
71 | return [xOffset, yOffset];
72 | };
73 |
74 | /**
75 | * Returns a new canvas with the same contents as `canvas`, flipped
76 | * accordingly.
77 | *
78 | * @param {Canvas} canvas The canvas to flip
79 | * @param {String} direction The direction flip the canvas in. Can be one
80 | * of:
81 | * - 'horizontal'
82 | * - 'vertical'
83 | * - 'both'
84 | *
85 | * @return {Canvas} a new canvas with the flipped contents of the input canvas
86 | */
87 | function flipCanvas(canvas, direction) {
88 | if ($.inArray(direction, ['horizontal', 'vertical', 'both']) === -1) {
89 | $.error('Flip value must be one of horizontal, vertical or both');
90 | }
91 |
92 | var flippedCanvas = $('')[0],
93 | flippedContext;
94 |
95 | flippedCanvas.width = canvas.width;
96 | flippedCanvas.height = canvas.height;
97 |
98 | flippedContext = flippedCanvas.getContext('2d');
99 |
100 | if (direction === 'horizontal' || direction === 'both') {
101 | flippedContext.translate(canvas.width, 0);
102 | flippedContext.scale(-1, 1);
103 | }
104 |
105 | if (direction === 'vertical' || direction === 'both') {
106 | flippedContext.translate(0, canvas.height);
107 | flippedContext.scale(1, -1);
108 | }
109 |
110 | flippedContext.drawImage(canvas, 0, 0, canvas.width, canvas.height);
111 |
112 | return flippedCanvas;
113 | }
114 |
115 | $.fn.extend({
116 | awesomeCursor: function(iconName, options) {
117 | options = $.extend({}, $.fn.awesomeCursor.defaults, options);
118 |
119 | if (typeof iconName !== 'string' || !iconName) {
120 | $.error('First parameter must be the icon name, e.g. \'pencil\'');
121 | }
122 |
123 | options.size = typeof options.size === 'string' ?
124 | parseInt(options.size, 10) : options.size;
125 |
126 | if (typeof options.hotspot === 'string') {
127 | options.hotspot = parseHotspotString(options.hotspot, options.size);
128 | }
129 |
130 | // Clamp hotspot coordinates between 0 and size - 1
131 | options.hotspot = $.map(options.hotspot, function(coordinate) {
132 | return Math.min(options.size - 1, Math.max(0, coordinate));
133 | });
134 |
135 | var cssClass = (function(name, template) {
136 | if (typeof template === 'string') {
137 | return template.replace(/%s/g, name);
138 | } else if (typeof template === 'function') {
139 | return template(name);
140 | }
141 |
142 | return name;
143 | })(iconName, options.font.cssClass),
144 | srcElement = $('', {
145 | class: cssClass,
146 | style: 'display: inline; font-size: ' + options.size + 'px;'
147 | });
148 |
149 | // Wrap the icon inside an absolute element to remove it from doc flow
150 | var wrapper = $('', {
151 | style: 'position: absolute; left: -9999px; top: -9999px;'
152 | }).append(srcElement);
153 |
154 | // Render element to the DOM, otherwise `getComputedStyle` will not work
155 | $('body').append(wrapper);
156 |
157 | // Get the unicode value and dimensions of the icon
158 | var unicode = window.getComputedStyle(srcElement[0], ':before')
159 | .getPropertyValue('content'),
160 | clientRect = srcElement[0].getBoundingClientRect();
161 |
162 | var canvas = $('')[0],
163 | canvasSize = Math.max(clientRect.width, clientRect.height),
164 | hotspotOffset, dataURL, context;
165 |
166 | // Remove the source element from the DOM
167 | srcElement.remove();
168 |
169 | // Increase the size of the canvas to account for the cursor's outline
170 | if (options.outline) {
171 | canvasSize += 2;
172 | }
173 |
174 | if (options.rotate) {
175 |
176 | // @TODO: move this into it's own function
177 | canvasSize = Math.ceil(Math.sqrt(
178 | Math.pow(canvasSize, 2) + Math.pow(canvasSize, 2)
179 | ));
180 |
181 | hotspotOffset = (canvasSize - options.size) / 2;
182 | canvas.width = canvasSize;
183 | canvas.height = canvasSize;
184 |
185 | context = canvas.getContext('2d');
186 | context.translate(canvas.width / 2, canvas.height / 2);
187 |
188 | // Canvas API works in radians, not degrees, hence `* Math.PI / 180`
189 | context.rotate(options.rotate * Math.PI / 180);
190 | context.translate(-canvas.width / 2, -canvas.height / 2);
191 |
192 | // Translate hotspot offset
193 | options.hotspot[0] += options.hotspot[0] !== canvas.width / 2 ?
194 | hotspotOffset : 0;
195 |
196 | options.hotspot[1] += options.hotspot[1] !== canvas.height / 2 ?
197 | hotspotOffset : 0;
198 | } else {
199 |
200 | canvas.height = canvasSize;
201 | canvas.width = canvasSize;
202 |
203 | context = canvas.getContext('2d');
204 | }
205 |
206 | /* Firefox wraps the extracted unicode value in double quotes - #10
207 | * Chrome 43+ is wrapping the extracted value in single quotes - #14
208 | */
209 | unicode = unicode.replace(/['"]/g, '');
210 |
211 | // Draw the cursor to the canvas
212 | context.fillStyle = options.color;
213 | context.font = options.size + 'px ' + options.font.family;
214 | context.textAlign = 'center';
215 | context.textBaseline = 'middle';
216 | context.fillText(unicode, canvasSize / 2, canvasSize / 2);
217 |
218 | // Check for outline option
219 | if (options.outline) {
220 | context.lineWidth = 0.5;
221 | context.strokeStyle = options.outline;
222 | context.strokeText(unicode, canvasSize / 2, canvasSize / 2);
223 | }
224 |
225 | // Check flip option
226 | if (options.flip) {
227 | canvas = flipCanvas(canvas, options.flip);
228 | }
229 |
230 | dataURL = canvas.toDataURL('image/png');
231 |
232 | $(this)
233 |
234 | // Fixes issue with Chrome not setting cursor if already set
235 | .css('cursor', '')
236 | .css('cursor', [
237 | 'url(' + dataURL + ')',
238 | options.hotspot[0],
239 | options.hotspot[1],
240 | ',',
241 | 'auto'
242 | ].join(' '))
243 | ;
244 |
245 | // Maintain chaining
246 | return this;
247 | }
248 | });
249 |
250 | // Expose the defaults so that users can override them if they want to
251 | $.fn.awesomeCursor.defaults = {
252 | color: '#000000',
253 | size: 18,
254 | hotspot: [0, 0],
255 | flip: '',
256 | rotate: 0,
257 | outline: null,
258 | font: {
259 | family: 'FontAwesome',
260 | cssClass: 'fa fa-%s'
261 | }
262 | };
263 | });
264 |
--------------------------------------------------------------------------------
/test/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "curly": true,
3 | "eqeqeq": true,
4 | "immed": true,
5 | "latedef": true,
6 | "newcap": true,
7 | "noarg": true,
8 | "sub": true,
9 | "undef": true,
10 | "unused": true,
11 | "boss": true,
12 | "eqnull": true,
13 | "browser": true,
14 | "predef": [
15 | "console",
16 | "jQuery",
17 | "QUnit",
18 | "module",
19 | "test",
20 | "asyncTest",
21 | "expect",
22 | "start",
23 | "stop",
24 | "ok",
25 | "equal",
26 | "notEqual",
27 | "deepEqual",
28 | "notDeepEqual",
29 | "strictEqual",
30 | "notStrictEqual",
31 | "throws"
32 | ]
33 | }
34 |
--------------------------------------------------------------------------------
/test/awesome-cursor-test-font/fonts/AwesomeCursorTest.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jwarby/jquery-awesome-cursor/b3a4c2f88ff1f2b83d3619aefad1a39599093736/test/awesome-cursor-test-font/fonts/AwesomeCursorTest.eot
--------------------------------------------------------------------------------
/test/awesome-cursor-test-font/fonts/AwesomeCursorTest.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/test/awesome-cursor-test-font/fonts/AwesomeCursorTest.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jwarby/jquery-awesome-cursor/b3a4c2f88ff1f2b83d3619aefad1a39599093736/test/awesome-cursor-test-font/fonts/AwesomeCursorTest.ttf
--------------------------------------------------------------------------------
/test/awesome-cursor-test-font/fonts/AwesomeCursorTest.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jwarby/jquery-awesome-cursor/b3a4c2f88ff1f2b83d3619aefad1a39599093736/test/awesome-cursor-test-font/fonts/AwesomeCursorTest.woff
--------------------------------------------------------------------------------
/test/awesome-cursor-test-font/style.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'AwesomeCursorTest';
3 | src:url('fonts/AwesomeCursorTest.eot');
4 | src:url('fonts/AwesomeCursorTest.eot') format('embedded-opentype'),
5 | url('fonts/AwesomeCursorTest.woff') format('woff'),
6 | url('fonts/AwesomeCursorTest.ttf') format('truetype'),
7 | url('fonts/AwesomeCursorTest.svg#AwesomeCursorTest') format('svg');
8 | font-weight: normal;
9 | font-style: normal;
10 | }
11 |
12 | .act {
13 | font-family: 'AwesomeCursorTest';
14 | speak: none;
15 | font-style: normal;
16 | font-weight: normal;
17 | font-variant: normal;
18 | text-transform: none;
19 | line-height: 1;
20 |
21 | /* Better Font Rendering =========== */
22 | -webkit-font-smoothing: antialiased;
23 | -moz-osx-font-smoothing: grayscale;
24 | }
25 |
26 | .act-pencil:before {
27 | content: "\e600";
28 | }
29 | .act-brush:before {
30 | content: "\e601";
31 | }
32 |
--------------------------------------------------------------------------------
/test/expected/awesome-cursor-test-font/black-pencil-36.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jwarby/jquery-awesome-cursor/b3a4c2f88ff1f2b83d3619aefad1a39599093736/test/expected/awesome-cursor-test-font/black-pencil-36.png
--------------------------------------------------------------------------------
/test/expected/awesome-cursor-test-font/green-brush-30-effects.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jwarby/jquery-awesome-cursor/b3a4c2f88ff1f2b83d3619aefad1a39599093736/test/expected/awesome-cursor-test-font/green-brush-30-effects.png
--------------------------------------------------------------------------------
/test/expected/black-desktop-22.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jwarby/jquery-awesome-cursor/b3a4c2f88ff1f2b83d3619aefad1a39599093736/test/expected/black-desktop-22.png
--------------------------------------------------------------------------------
/test/expected/black-globe-32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jwarby/jquery-awesome-cursor/b3a4c2f88ff1f2b83d3619aefad1a39599093736/test/expected/black-globe-32.png
--------------------------------------------------------------------------------
/test/expected/black-globe-flip-b-32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jwarby/jquery-awesome-cursor/b3a4c2f88ff1f2b83d3619aefad1a39599093736/test/expected/black-globe-flip-b-32.png
--------------------------------------------------------------------------------
/test/expected/black-outline-paint-brush-32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jwarby/jquery-awesome-cursor/b3a4c2f88ff1f2b83d3619aefad1a39599093736/test/expected/black-outline-paint-brush-32.png
--------------------------------------------------------------------------------
/test/expected/black-outline-paint-brush-flip-both-32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jwarby/jquery-awesome-cursor/b3a4c2f88ff1f2b83d3619aefad1a39599093736/test/expected/black-outline-paint-brush-flip-both-32.png
--------------------------------------------------------------------------------
/test/expected/black-outline-paint-brush-flip-horizontal-32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jwarby/jquery-awesome-cursor/b3a4c2f88ff1f2b83d3619aefad1a39599093736/test/expected/black-outline-paint-brush-flip-horizontal-32.png
--------------------------------------------------------------------------------
/test/expected/black-outline-paint-brush-flip-vertical-32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jwarby/jquery-awesome-cursor/b3a4c2f88ff1f2b83d3619aefad1a39599093736/test/expected/black-outline-paint-brush-flip-vertical-32.png
--------------------------------------------------------------------------------
/test/expected/black-pencil-rotate45.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jwarby/jquery-awesome-cursor/b3a4c2f88ff1f2b83d3619aefad1a39599093736/test/expected/black-pencil-rotate45.png
--------------------------------------------------------------------------------
/test/expected/blue-outline-paint-brush-rotate45-flip-h.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jwarby/jquery-awesome-cursor/b3a4c2f88ff1f2b83d3619aefad1a39599093736/test/expected/blue-outline-paint-brush-rotate45-flip-h.png
--------------------------------------------------------------------------------
/test/expected/blue-outline-paint-brush-rotate45.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jwarby/jquery-awesome-cursor/b3a4c2f88ff1f2b83d3619aefad1a39599093736/test/expected/blue-outline-paint-brush-rotate45.png
--------------------------------------------------------------------------------
/test/expected/green-pencil-rotate45-flip-b.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jwarby/jquery-awesome-cursor/b3a4c2f88ff1f2b83d3619aefad1a39599093736/test/expected/green-pencil-rotate45-flip-b.png
--------------------------------------------------------------------------------
/test/expected/green-pencil-rotate45-flip-h.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jwarby/jquery-awesome-cursor/b3a4c2f88ff1f2b83d3619aefad1a39599093736/test/expected/green-pencil-rotate45-flip-h.png
--------------------------------------------------------------------------------
/test/expected/lime-flag-18.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jwarby/jquery-awesome-cursor/b3a4c2f88ff1f2b83d3619aefad1a39599093736/test/expected/lime-flag-18.png
--------------------------------------------------------------------------------
/test/expected/lime-flag-flip-v-18.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jwarby/jquery-awesome-cursor/b3a4c2f88ff1f2b83d3619aefad1a39599093736/test/expected/lime-flag-flip-v-18.png
--------------------------------------------------------------------------------
/test/expected/pink-usb-32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jwarby/jquery-awesome-cursor/b3a4c2f88ff1f2b83d3619aefad1a39599093736/test/expected/pink-usb-32.png
--------------------------------------------------------------------------------
/test/expected/red-pencil-18.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jwarby/jquery-awesome-cursor/b3a4c2f88ff1f2b83d3619aefad1a39599093736/test/expected/red-pencil-18.png
--------------------------------------------------------------------------------
/test/expected/red-pencil-flip-h-18.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jwarby/jquery-awesome-cursor/b3a4c2f88ff1f2b83d3619aefad1a39599093736/test/expected/red-pencil-flip-h-18.png
--------------------------------------------------------------------------------
/test/expected/red-wrench-rotate-45.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jwarby/jquery-awesome-cursor/b3a4c2f88ff1f2b83d3619aefad1a39599093736/test/expected/red-wrench-rotate-45.png
--------------------------------------------------------------------------------
/test/jquery-awesome-cursor.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | jQuery Awesome Cursor plugin Test Suite
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
22 |
23 |
24 |
25 |
26 |
27 | lame test markup
28 | normal test markup
29 | awesome test markup
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/test/jquery-awesome-cursor_test.js:
--------------------------------------------------------------------------------
1 | /* global QUnit */
2 |
3 | (function(global, $) {
4 | 'use strict';
5 |
6 | QUnit.test('force fonts to load', function(assert) {
7 | assert.expect(0);
8 |
9 | $('body')
10 | .append('')
11 | .append('')
12 | ;
13 |
14 | setTimeout(assert.async(), 500);
15 | });
16 |
17 | /* Regular expresssion which defines the expected value of the CSS cursor
18 | * property after the plugin has been called on an element
19 | */
20 | var CURSOR_REGEX = /^url\((?:")?data:image\/png;base64,.*\)(?: 0 0)?, auto$/;
21 |
22 | /**
23 | * Extract the cursor's x, y hotspot from the specified element.
24 | *
25 | * @param {jQuery} el The element to extract the cursor's hotspot from
26 | *
27 | * @return {Array|undefined} an array containing the cursor's x and y values,
28 | * or `undefined` if extraction fails
29 | */
30 | function extractHotspot(el) {
31 | var match = el.attr('style')
32 | .match(/^cursor: url\(.*\) ([0-9]+) ([0-9]+), auto;$/);
33 |
34 | if (match && match.length === 3) {
35 | return match.slice(1).map(function(val) {
36 | return parseFloat(val);
37 | });
38 | }
39 |
40 | return;
41 | }
42 |
43 | /**
44 | * Get a canvas whose contents represent the supplied image.
45 | *
46 | * @param {Image} img The image to render to the canvas
47 | *
48 | * @return {Canvas} the new canvas element, with `img` rendered to it
49 | */
50 | function getCanvasFromImage(img) {
51 |
52 | // Create an empty canvas element
53 | var canvas = document.createElement('canvas');
54 | canvas.width = img.width;
55 | canvas.height = img.height;
56 |
57 | // Copy the image contents to the canvas
58 | var ctx = canvas.getContext('2d');
59 | ctx.drawImage(img, 0, 0);
60 |
61 | return canvas;
62 | }
63 |
64 | /**
65 | * Get a canvas with the supplied data URI rendered to it.
66 | *
67 | * @param {String} uri The data URI
68 | *
69 | * @return {Canvas} a new canvas element, with the supplied data rendered to
70 | * it
71 | */
72 | function getCanvasFromDataURI(uri) {
73 | return getCanvasFromImage($('
', {
74 | src: uri
75 | })[0]);
76 | }
77 |
78 | /**
79 | * Compare the content of two canvasses, by checking each pixel in the first
80 | * canvas against the second canvas. Bails early and returns false if:
81 | *
82 | * - the two canvasses have different widths or heights
83 | * - the length of the canvasses data attribute is different
84 | *
85 | * @return {Boolean} true if every single pixel in `canvas1` matches every
86 | * pixel in `canvas2`
87 | */
88 | function canvasCompare(canvas1, canvas2) {
89 | if (!(canvas1.width === canvas2.width &&
90 | canvas1.height === canvas2.height)) {
91 | return false;
92 | }
93 |
94 | var ctx1 = canvas1.getContext('2d'),
95 | ctx2 = canvas2.getContext('2d'),
96 | data1 = ctx1.getImageData(0, 0, canvas1.width, canvas1.height),
97 | data2 = ctx2.getImageData(0, 0, canvas2.width, canvas2.height);
98 |
99 | if (data1.data.length !== data2.data.length) {
100 | return false;
101 | }
102 |
103 | for (var d = 0; d < data1.data.length; d++) {
104 | if (data1.data[d] !== data2.data[d]) {
105 |
106 | return false;
107 |
108 | }
109 | }
110 |
111 | return true;
112 | }
113 |
114 | /**
115 | * Check if the supplied image matches the cursor set on this element.
116 | * If element is not provided, it defaults to the body element.
117 | *
118 | * @param {String} imgSrc The src URL for the image to check
119 | * @param {Function} callback Gets called with true if the cursor matches the
120 | * image, or false if it doesn't. The 2nd and 3rd
121 | * parameters to the callback function are the
122 | * cursor's image data and the image data for
123 | * `imgSrc` respectively
124 | */
125 | $.fn.cursorMatchesImage = function(imgSrc, callback) {
126 | callback = typeof callback === 'function' ? callback : $.noop;
127 |
128 | var actualImgData = this.css('cursor').match(/^url\((.*)\)/),
129 | $expectedImg;
130 |
131 | if (!actualImgData || actualImgData.length !== 2) {
132 | return callback(false);
133 | }
134 |
135 | $expectedImg = $('
', {
136 | src: imgSrc
137 | });
138 |
139 | $expectedImg.load(function() {
140 | var expectedCanvas = getCanvasFromImage($expectedImg[0]),
141 | actualCanvas = getCanvasFromDataURI(actualImgData[1].replace(/^["']|["']$/g, ''));
142 |
143 | if (!canvasCompare(expectedCanvas, actualCanvas)) {
144 | $('body').append(actualCanvas, expectedCanvas, '' + imgSrc + '
');
145 |
146 | return callback(false);
147 | }
148 |
149 | return callback(true);
150 | });
151 | };
152 |
153 | QUnit.module('jQuery#awesomeCursor', {
154 |
155 | // This will run before each test in this module.
156 | beforeEach: function() {
157 | $('#qunit-fixture').remove();
158 |
159 | var $qunit = $('', {
160 | id: 'qunit'
161 | });
162 | var $fixture = $('', {
163 | id: 'qunit-fixture'
164 | });
165 |
166 | $fixture
167 | .append($('', {
168 | class: 'fa fa-pencil'
169 | }))
170 | .append($('', {
171 | class: 'act act-pencil'
172 | }))
173 | ;
174 |
175 | $('body').append($qunit).append($fixture);
176 | this.elems = $('#qunit-fixture').children();
177 | }
178 | });
179 |
180 | QUnit.test('is chainable', function(assert) {
181 | assert.expect(1);
182 |
183 | assert.strictEqual(
184 | this.elems.awesomeCursor('pencil'), this.elems, 'should be chainable'
185 | );
186 | });
187 |
188 | QUnit.test('css is correctly set', function(assert) {
189 | assert.expect(1);
190 | this.elems.awesomeCursor('pencil');
191 |
192 | assert.ok(
193 | CURSOR_REGEX.test(this.elems.css('cursor')),
194 | '\'' + this.elems.css('cursor') + '\' does not match expected RegExp'
195 | );
196 | });
197 |
198 | QUnit.test('throws an error if name parameter is missing or invalid', function(assert) {
199 | var subjects = ['', 0, -1, false, true, {}, [], null],
200 | that = this;
201 |
202 | assert.expect(subjects.length);
203 |
204 | subjects.forEach(function(subject) {
205 | assert.throws(
206 | function() {
207 | that.elems.awesomeCursor(subject);
208 | },
209 | 'Expected an error to be thrown when using `' + subject +
210 | '` as icon name parameter');
211 | });
212 | });
213 |
214 | QUnit.test('`hotspot` value can be a string', function(assert) {
215 | assert.expect(1);
216 |
217 | assert.ok(this.elems.awesomeCursor('pencil', {
218 | hotspot: 'bottom left'
219 | }));
220 | });
221 |
222 | QUnit.test('`hotspot` string values are correctly parsed', function(assert) {
223 | var size = $.fn.awesomeCursor.defaults.size,
224 | subjects = {
225 | 'top left': [0, 0],
226 | 'center top': [size / 2, 0],
227 | 'top right': [size - 1, 0],
228 | 'center left': [0, size / 2],
229 | 'center': [size / 2, size / 2],
230 | 'center right': [size - 1, size / 2],
231 | 'bottom left': [0, size - 1],
232 | 'center bottom': [size / 2, size - 1],
233 | 'bottom right': [size - 1, size - 1]
234 | },
235 | hotspot;
236 |
237 | assert.expect(Object.keys(subjects).length);
238 |
239 | for (var s in subjects) {
240 | this.elems.awesomeCursor('pencil', {
241 | hotspot: s
242 | });
243 |
244 | hotspot = extractHotspot(this.elems);
245 | assert.deepEqual(hotspot, subjects[s]);
246 | }
247 | });
248 |
249 | QUnit.test('`hotspot` values get clamped between 0 and cursor size - 1', function(assert) {
250 | var hotspot;
251 |
252 | assert.expect(3);
253 |
254 | this.elems.awesomeCursor('pencil', {
255 | size: 32,
256 | hotspot: [-3, 34]
257 | });
258 |
259 | hotspot = extractHotspot(this.elems);
260 |
261 | assert.ok(hotspot);
262 | assert.equal(hotspot[0], 0);
263 | assert.equal(hotspot[1], 31);
264 | });
265 |
266 | QUnit.test('can set the color of a cursor', function(assert) {
267 | var done = assert.async();
268 |
269 | assert.expect(2);
270 |
271 | var next = function() {
272 | this.elems
273 | .awesomeCursor('flag-checkered', {
274 | color: '#00ff00',
275 | size: 18
276 | })
277 | .cursorMatchesImage(
278 | 'expected/lime-flag-18.png', function(matches) {
279 | assert.ok(matches);
280 | done();
281 | });
282 | }.bind(this);
283 |
284 | this.elems
285 | .awesomeCursor('pencil', {
286 | color: 'red',
287 | size: 18
288 | })
289 | .cursorMatchesImage(
290 | 'expected/red-pencil-18.png', function(matches) {
291 | assert.ok(matches);
292 | next();
293 | });
294 |
295 | });
296 |
297 | QUnit.test('can set the size of a cursor', function(assert) {
298 | var done = assert.async();
299 | assert.expect(1);
300 |
301 | this.elems
302 | .awesomeCursor('globe', {
303 | color: 'black',
304 | size: 32
305 | })
306 | .cursorMatchesImage(
307 | 'expected/black-globe-32.png', function(matches) {
308 | assert.ok(matches);
309 | done();
310 | });
311 | });
312 |
313 | QUnit.test('can set the size of a cursor using a string value', function(assert) {
314 | var done = assert.async();
315 | assert.expect(1);
316 |
317 | this.elems
318 | .awesomeCursor('desktop', {
319 | color: 'black',
320 | size: '22px'
321 | })
322 | .cursorMatchesImage(
323 | 'expected/black-desktop-22.png', function(matches) {
324 | assert.ok(matches);
325 | done();
326 | });
327 | });
328 |
329 | QUnit.test('can flip a cursor horizontally', function(assert) {
330 | var done = assert.async();
331 | assert.expect(1);
332 |
333 | this.elems
334 | .awesomeCursor('pencil', {
335 | color: 'red',
336 | size: 18,
337 | flip: 'horizontal'
338 | })
339 | .cursorMatchesImage(
340 | 'expected/red-pencil-flip-h-18.png', function(matches) {
341 | assert.ok(matches);
342 | done();
343 | });
344 | });
345 |
346 | QUnit.test('can flip a cursor vertically', function(assert) {
347 | var done = assert.async();
348 | assert.expect(1);
349 |
350 | this.elems
351 | .awesomeCursor('flag-checkered', {
352 | color: '#00ff00',
353 | size: 18,
354 | flip: 'vertical'
355 | })
356 | .cursorMatchesImage(
357 | 'expected/lime-flag-flip-v-18.png', function(matches) {
358 | assert.ok(matches);
359 | done();
360 | });
361 | });
362 |
363 | QUnit.test('can flip a cursor vertically and horizontally', function(assert) {
364 | var done = assert.async();
365 | assert.expect(1);
366 |
367 | this.elems
368 | .awesomeCursor('globe', {
369 | color: 'black',
370 | size: 32,
371 | flip: 'both'
372 | })
373 | .cursorMatchesImage(
374 | 'expected/black-globe-flip-b-32.png', function(matches) {
375 | assert.ok(matches);
376 | done();
377 | });
378 | });
379 |
380 | QUnit.test('can rotate a cursor', function(assert) {
381 | var done = assert.async();
382 | assert.expect(2);
383 |
384 | var next = function() {
385 | this.elems.awesomeCursor('wrench', {
386 | color: 'red',
387 | size: '32px',
388 | rotate: -45
389 | }).cursorMatchesImage(
390 | 'expected/red-wrench-rotate-45.png', function(matches) {
391 | assert.ok(matches);
392 | done();
393 | }
394 | );
395 | }.bind(this);
396 |
397 | this.elems.awesomeCursor('pencil', {
398 | color: 'black',
399 | size: '32px',
400 | rotate: 45
401 | }).cursorMatchesImage(
402 | 'expected/black-pencil-rotate45.png', function(matches) {
403 | assert.ok(matches);
404 | next();
405 | }
406 | );
407 |
408 | });
409 |
410 | QUnit.test('can rotate and flip a cursor', function(assert) {
411 | var done = assert.async();
412 | assert.expect(2);
413 |
414 | var next = function() {
415 | this.elems.awesomeCursor('pencil', {
416 | color: 'green',
417 | size: '32px',
418 | rotate: 45,
419 | flip: 'both'
420 | }).cursorMatchesImage(
421 | 'expected/green-pencil-rotate45-flip-b.png', function(matches) {
422 | assert.ok(matches);
423 | done();
424 | }
425 | );
426 | }.bind(this);
427 |
428 | this.elems.awesomeCursor('pencil', {
429 | color: 'green',
430 | size: '32px',
431 | rotate: 45,
432 | flip: 'horizontal'
433 | }).cursorMatchesImage(
434 | 'expected/green-pencil-rotate45-flip-h.png', function(matches) {
435 | assert.ok(matches);
436 | next();
437 | }
438 | );
439 |
440 | });
441 |
442 | QUnit.test('hotspot gets translated when cursor rotated', function(assert) {
443 | var size = $.fn.awesomeCursor.defaults.size,
444 | newSize = Math.ceil(Math.sqrt(
445 | Math.pow(size, 2) + Math.pow(size, 2)
446 | )),
447 | subjects = {
448 | 45: [(newSize - size) / 2, (size / 2) + (newSize - size) / 2]
449 | },
450 | hotspot;
451 |
452 | assert.expect(Object.keys(subjects).length);
453 |
454 | for (var s in subjects) {
455 | this.elems.awesomeCursor('pencil', {
456 | hotspot: 'center left',
457 | rotate: s
458 | });
459 |
460 | hotspot = extractHotspot(this.elems);
461 | assert.deepEqual(hotspot, subjects[s]);
462 | }
463 | });
464 |
465 | QUnit.test('can add outline to cursor', function(assert) {
466 | var done = assert.async();
467 | assert.expect(1);
468 |
469 | this.elems.awesomeCursor('paint-brush', {
470 | color: 'white',
471 | size: 32,
472 | outline: 'black'
473 | }).cursorMatchesImage(
474 | 'expected/black-outline-paint-brush-32.png', function(matches) {
475 | assert.ok(matches);
476 | done();
477 | }
478 | );
479 | });
480 |
481 | QUnit.test('can add outlines to flipped cursors', function(assert) {
482 | var done = assert.async();
483 | assert.expect(3);
484 |
485 | var runTests = function(tests) {
486 | var current = tests.pop();
487 |
488 | if (!current) {
489 | done();
490 | } else {
491 |
492 | this.elems.awesomeCursor('paint-brush', {
493 | color: 'white',
494 | size: 32,
495 | outline: 'black',
496 | flip: current
497 | }).cursorMatchesImage(
498 | 'expected/black-outline-paint-brush-flip-' + current + '-32.png',
499 | function(matches) {
500 | assert.ok(matches);
501 | runTests(tests);
502 | }
503 | );
504 | }
505 | }.bind(this);
506 |
507 | runTests(['horizontal', 'vertical', 'both']);
508 |
509 | });
510 |
511 | QUnit.test('can add outline to rotated cursor', function(assert) {
512 | var done = assert.async();
513 | assert.expect(1);
514 |
515 | this.elems.awesomeCursor('pencil', {
516 | color: 'skyblue',
517 | size: 32,
518 | rotate: 45,
519 | outline: 'blue'
520 | }).cursorMatchesImage(
521 | 'expected/blue-outline-paint-brush-rotate45.png', function(matches) {
522 | assert.ok(matches);
523 | done();
524 | }
525 | );
526 | });
527 |
528 | QUnit.test('can add outline to rotated and flipped cursor', function(assert) {
529 | var done = assert.async();
530 | assert.expect(1);
531 |
532 | this.elems.awesomeCursor('pencil', {
533 | color: 'skyblue',
534 | size: 32,
535 | rotate: 45,
536 | outline: 'blue',
537 | flip: 'horizontal'
538 | }).cursorMatchesImage(
539 | 'expected/blue-outline-paint-brush-rotate45-flip-h.png', function(matches) {
540 | assert.ok(matches);
541 | done();
542 | }
543 | );
544 | });
545 |
546 | QUnit.test('can use a custom font instead of FontAwesome', function(assert) {
547 | var done = assert.async();
548 | assert.expect(1);
549 |
550 | this.elems.awesomeCursor('pencil', {
551 | font: {
552 | family: 'AwesomeCursorTest',
553 | cssClass: 'act act-%s'
554 | },
555 | size: 36,
556 | color: 'black'
557 | }).cursorMatchesImage(
558 | 'expected/awesome-cursor-test-font/black-pencil-36.png', function(matches) {
559 | assert.ok(matches);
560 | done();
561 | }
562 | );
563 | });
564 |
565 | QUnit.test('can apply effects to custom font cursors', function(assert) {
566 | var done = assert.async();
567 | assert.expect(1);
568 |
569 | this.elems.awesomeCursor('brush', {
570 | font: {
571 | family: 'AwesomeCursorTest',
572 | cssClass: 'act act-%s'
573 | },
574 | size: 30,
575 | color: 'limegreen',
576 | outline: 'forestgreen',
577 | rotate: 35,
578 | flip: 'horizontal'
579 | }).cursorMatchesImage(
580 | 'expected/awesome-cursor-test-font/green-brush-30-effects.png', function(matches) {
581 | assert.ok(matches);
582 | done();
583 | }
584 | );
585 | });
586 |
587 | QUnit.test('can set custom font `cssClass` using a function', function(assert) {
588 | var done = assert.async();
589 | assert.expect(1);
590 |
591 | this.elems.awesomeCursor('pencil', {
592 | font: {
593 | family: 'AwesomeCursorTest',
594 | cssClass: function(iconName) {
595 | return 'act act-' + iconName;
596 | }
597 | },
598 | size: 36,
599 | color: 'black'
600 | }).cursorMatchesImage(
601 | 'expected/awesome-cursor-test-font/black-pencil-36.png', function(matches) {
602 | assert.ok(matches);
603 | done();
604 | }
605 | );
606 | });
607 |
608 | QUnit.test('does not clip large icons', function(assert) {
609 | var done = assert.async();
610 | assert.expect(1);
611 |
612 | this.elems.awesomeCursor('usb', {
613 | size: 32,
614 | color: 'pink'
615 | }).cursorMatchesImage(
616 | 'expected/pink-usb-32.png', function(matches) {
617 | assert.ok(matches);
618 | done();
619 | }
620 | );
621 | });
622 | }(this, jQuery));
623 |
--------------------------------------------------------------------------------