2013-01-28 21:03:27 -05:00
|
|
|
/*
|
|
|
|
* Paper.js - The Swiss Army Knife of Vector Graphics Scripting.
|
|
|
|
* http://paperjs.org/
|
|
|
|
*
|
2014-01-03 19:47:16 -05:00
|
|
|
* Copyright (c) 2011 - 2014, Juerg Lehni & Jonathan Puckey
|
|
|
|
* http://scratchdisk.com/ & http://jonathanpuckey.com/
|
2013-01-28 21:03:27 -05:00
|
|
|
*
|
|
|
|
* Distributed under the MIT license. See LICENSE file for details.
|
|
|
|
*
|
|
|
|
* All rights reserved.
|
|
|
|
*/
|
|
|
|
|
2013-06-11 16:43:29 -04:00
|
|
|
// Register a jsDump parser for Base.
|
2013-06-11 16:38:08 -04:00
|
|
|
QUnit.jsDump.setParser('Base', function (obj, stack) {
|
2014-08-16 13:24:54 -04:00
|
|
|
// Just compare the string representation of classes inheriting from Base,
|
|
|
|
// since they hide the internal values.
|
|
|
|
return obj.toString();
|
2013-06-11 16:38:08 -04:00
|
|
|
});
|
|
|
|
|
2013-06-11 16:43:29 -04:00
|
|
|
// Override the default object parser to handle Base objects.
|
|
|
|
// We need to keep a reference to the previous implementation.
|
|
|
|
var objectParser = QUnit.jsDump.parsers.object;
|
|
|
|
|
2013-06-11 16:38:08 -04:00
|
|
|
QUnit.jsDump.setParser('object', function (obj, stack) {
|
2014-08-16 13:24:54 -04:00
|
|
|
return (obj instanceof Base
|
|
|
|
? QUnit.jsDump.parsers.Base
|
|
|
|
: objectParser).call(this, obj, stack);
|
2013-06-11 16:38:08 -04:00
|
|
|
});
|
|
|
|
|
2013-07-21 19:11:09 -04:00
|
|
|
function getFunctionBody(func) {
|
2014-08-16 13:24:54 -04:00
|
|
|
return func.toString().match(
|
|
|
|
/^\s*function[^\{]*\{([\s\S]*)\}\s*$/)[1]
|
|
|
|
.replace(/ /g, '')
|
|
|
|
.replace(/^\s+|\s+$/g, '');
|
2013-07-21 19:11:09 -04:00
|
|
|
}
|
|
|
|
|
2011-05-14 13:59:04 -04:00
|
|
|
// Override equals to convert functions to message and execute them as tests()
|
2013-02-08 22:37:58 -05:00
|
|
|
function equals(actual, expected, message, tolerance) {
|
2014-11-30 14:27:14 -05:00
|
|
|
// Allow the use of functions for actual, which will get called and their
|
|
|
|
// source content extracted for readable reports.
|
2014-08-16 13:24:54 -04:00
|
|
|
if (typeof actual === 'function') {
|
|
|
|
if (!message) {
|
|
|
|
message = getFunctionBody(actual);
|
|
|
|
if (/^return /.test(message)) {
|
|
|
|
message = message
|
|
|
|
.replace(/^return /, '')
|
|
|
|
.replace(/;$/, '');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
actual = actual();
|
|
|
|
}
|
2014-11-30 14:27:14 -05:00
|
|
|
if (typeof expected === 'number') {
|
|
|
|
// Compare with a default tolerance of Numerical.TOLERANCE:
|
|
|
|
var ok = Math.abs(actual - expected)
|
|
|
|
<= Base.pick(tolerance, Numerical.TOLERANCE);
|
2014-08-16 13:24:54 -04:00
|
|
|
return QUnit.push(ok, ok ? expected : actual, expected, message);
|
|
|
|
} else if (actual && actual.equals) {
|
|
|
|
// Support calling of #equals() on the actual or expected value, and
|
|
|
|
// automatically convert displayed values to strings.
|
|
|
|
return QUnit.push(actual.equals(expected), actual, expected, message);
|
|
|
|
} else if (expected && expected.equals) {
|
|
|
|
return QUnit.push(expected.equals(actual), actual, expected, message);
|
|
|
|
}
|
|
|
|
// Let's be strict
|
|
|
|
return strictEqual(actual, expected, message);
|
2011-05-07 12:25:34 -04:00
|
|
|
}
|
2011-05-05 08:38:20 -04:00
|
|
|
|
2011-05-14 13:59:04 -04:00
|
|
|
function test(testName, expected) {
|
2014-08-16 13:24:54 -04:00
|
|
|
return QUnit.test(testName, function() {
|
|
|
|
var project = new Project();
|
|
|
|
expected();
|
|
|
|
project.remove();
|
|
|
|
});
|
2011-05-14 13:59:04 -04:00
|
|
|
}
|
|
|
|
|
2013-03-17 13:13:55 -04:00
|
|
|
function asyncTest(testName, expected) {
|
2014-08-16 13:24:54 -04:00
|
|
|
return QUnit.asyncTest(testName, function() {
|
|
|
|
var project = new Project();
|
|
|
|
expected(function() {
|
|
|
|
project.remove();
|
|
|
|
start();
|
|
|
|
});
|
|
|
|
});
|
2013-03-17 13:13:55 -04:00
|
|
|
}
|
|
|
|
|
2014-11-30 14:27:14 -05:00
|
|
|
function compareArrays(array1, array2, message, tolerance) {
|
|
|
|
equals(array1.length, array2.length, (message || '') + ' length');
|
|
|
|
for (var i = 0, l = array1.length; i < l; i++) {
|
|
|
|
equals(array1[i], array2[i], (message || '') + ' [' + i + ']',
|
|
|
|
tolerance);
|
|
|
|
}
|
2013-04-08 10:07:41 -04:00
|
|
|
}
|
|
|
|
|
2014-11-30 14:27:14 -05:00
|
|
|
function comparePoints(point1, point2, message, tolerance) {
|
|
|
|
equals(point1.x, point2.x, (message || '') + ' x', tolerance);
|
|
|
|
equals(point1.y, point2.y, (message || '') + ' y', tolerance);
|
2011-02-13 10:48:20 -05:00
|
|
|
}
|
|
|
|
|
2014-11-30 14:27:14 -05:00
|
|
|
function compareSize(size1, size2, message, tolerance) {
|
|
|
|
equals(size1.width, size2.width, (message || '') + ' width', tolerance);
|
|
|
|
equals(size1.height, size2.height, (message || '') + ' height', tolerance);
|
2011-02-13 09:35:48 -05:00
|
|
|
}
|
|
|
|
|
2014-11-30 14:27:14 -05:00
|
|
|
function compareRectangles(rect1, rect2, message, tolerance) {
|
|
|
|
comparePoints(rect1, rect2, message, tolerance);
|
|
|
|
compareSize(rect1, rect2, message, tolerance);
|
2011-02-13 10:48:20 -05:00
|
|
|
}
|
2011-02-19 11:11:17 -05:00
|
|
|
|
2014-11-30 14:27:14 -05:00
|
|
|
function compareColors(color1, color2, message, tolerance) {
|
2014-08-16 13:24:54 -04:00
|
|
|
color1 = color1 && new Color(color1);
|
|
|
|
color2 = color2 && new Color(color2);
|
|
|
|
if (color1 && color2) {
|
|
|
|
equals(color1.type, color2.type, (message || '') + ' type');
|
|
|
|
compareArrays(color1.components, color2.components,
|
2014-11-30 14:27:14 -05:00
|
|
|
(message || '') + ' components', tolerance);
|
2014-08-16 13:24:54 -04:00
|
|
|
} else {
|
2014-11-30 14:27:14 -05:00
|
|
|
equals(color1, color2, message, tolerance);
|
2014-08-16 13:24:54 -04:00
|
|
|
}
|
2011-05-20 13:39:12 -04:00
|
|
|
}
|
|
|
|
|
2013-04-09 19:46:20 -04:00
|
|
|
function compareStyles(style, style2, checkIdentity) {
|
2014-08-16 13:24:54 -04:00
|
|
|
if (checkIdentity) {
|
|
|
|
equals(function() {
|
|
|
|
return style !== style2;
|
|
|
|
}, true);
|
|
|
|
}
|
|
|
|
Base.each(['fillColor', 'strokeColor'], function(key) {
|
|
|
|
if (style[key]) {
|
|
|
|
// The color should not point to the same color object:
|
|
|
|
if (checkIdentity) {
|
|
|
|
equals(function() {
|
|
|
|
return style[key] !== style2[key];
|
|
|
|
}, true, 'The ' + key
|
|
|
|
+ ' should not point to the same color object:');
|
|
|
|
}
|
|
|
|
if (style[key] instanceof Color) {
|
|
|
|
if (style[key].type === 'gradient' && checkIdentity) {
|
|
|
|
equals(function() {
|
|
|
|
return style[key].gradient === style2[key].gradient;
|
|
|
|
}, true, 'The ' + key
|
|
|
|
+ '.gradient should point to the same object:');
|
|
|
|
}
|
|
|
|
compareColors(style[key], style2[key],
|
|
|
|
'Compare Style#' + key);
|
|
|
|
} else {
|
|
|
|
equals(style[key] && style[key].toString(),
|
|
|
|
style2[key] && style2[key].toString(),
|
|
|
|
'Compare Style#' + key);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
compareObjects('Style', ['strokeCap', 'strokeJoin', 'dashArray',
|
|
|
|
'dashOffset', 'miterLimit', 'strokeOverprint', 'fillOverprint',
|
|
|
|
'fontSize', 'font', 'leading', 'justification'],
|
|
|
|
style, style2, checkIdentity);
|
2011-05-21 12:38:49 -04:00
|
|
|
}
|
|
|
|
|
2011-05-26 05:55:39 -04:00
|
|
|
function compareObjects(name, keys, obj, obj2, checkIdentity) {
|
2014-08-16 13:24:54 -04:00
|
|
|
if (checkIdentity) {
|
|
|
|
equals(function() {
|
|
|
|
return obj !== obj2;
|
|
|
|
}, true);
|
|
|
|
}
|
|
|
|
Base.each(keys, function(key) {
|
|
|
|
var val = obj[key], val2 = obj2[key],
|
|
|
|
message = 'Compare ' + name + '#' + key;
|
|
|
|
if (typeof val === 'number') {
|
2014-11-30 14:27:14 -05:00
|
|
|
equals(val, val2, message);
|
2014-08-16 13:24:54 -04:00
|
|
|
} else if (Array.isArray(val)) {
|
|
|
|
compareArrays(val, val2, message);
|
|
|
|
} else {
|
|
|
|
equals(val, val2, message);
|
|
|
|
}
|
|
|
|
});
|
2011-05-26 04:20:21 -04:00
|
|
|
}
|
2011-05-21 12:38:49 -04:00
|
|
|
|
2011-05-21 14:36:30 -04:00
|
|
|
function compareSegmentPoints(segmentPoint, segmentPoint2, checkIdentity) {
|
2014-08-16 13:24:54 -04:00
|
|
|
compareObjects('SegmentPoint', ['x', 'y', 'selected'],
|
|
|
|
segmentPoint, segmentPoint2, checkIdentity);
|
2011-05-21 14:36:30 -04:00
|
|
|
}
|
|
|
|
|
2011-05-21 13:41:02 -04:00
|
|
|
function compareSegments(segment, segment2, checkIdentity) {
|
2014-08-16 13:24:54 -04:00
|
|
|
if (checkIdentity) {
|
|
|
|
equals(function() {
|
|
|
|
return segment !== segment2;
|
|
|
|
}, true);
|
|
|
|
}
|
|
|
|
equals(function() {
|
|
|
|
return segment.selected == segment2.selected;
|
|
|
|
}, true);
|
|
|
|
Base.each(['handleIn', 'handleOut', 'point'], function(key) {
|
|
|
|
compareSegmentPoints(segment[key], segment2[key]);
|
|
|
|
});
|
2011-05-21 13:41:02 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
function compareSegmentLists(segmentList, segmentList2, checkIdentity) {
|
2014-08-16 13:24:54 -04:00
|
|
|
if (checkIdentity) {
|
|
|
|
equals(function() {
|
|
|
|
return segmentList !== segmentList2;
|
|
|
|
}, true);
|
|
|
|
}
|
|
|
|
equals(segmentList.toString(), segmentList2.toString(),
|
|
|
|
'Compare Item#segments');
|
|
|
|
if (checkIdentity) {
|
|
|
|
for (var i = 0, l = segmentList.length; i < l; i++) {
|
|
|
|
var segment = segmentList[i],
|
|
|
|
segment2 = segmentList2[i];
|
|
|
|
compareSegments(segment, segment2, checkIdentity);
|
|
|
|
}
|
|
|
|
}
|
2011-05-21 13:41:02 -04:00
|
|
|
}
|
|
|
|
|
2013-03-17 09:42:20 -04:00
|
|
|
function compareItems(item, item2, cloned, checkIdentity, dontShareProject) {
|
2014-08-16 13:24:54 -04:00
|
|
|
if (checkIdentity) {
|
|
|
|
equals(function() {
|
|
|
|
return item !== item2;
|
|
|
|
}, true);
|
|
|
|
|
|
|
|
equals(function() {
|
|
|
|
return item.id !== item2.id;
|
|
|
|
}, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
equals(function() {
|
|
|
|
return item.constructor == item2.constructor;
|
|
|
|
}, true);
|
|
|
|
|
|
|
|
var itemProperties = ['opacity', 'locked', 'visible', 'blendMode', 'name',
|
|
|
|
'selected', 'clipMask', 'guide'];
|
|
|
|
Base.each(itemProperties, function(key) {
|
|
|
|
var value = item[key];
|
|
|
|
// When item was cloned and had a name, the name will be versioned
|
|
|
|
equals(
|
|
|
|
key == 'name' && cloned && value
|
|
|
|
? value + ' 1'
|
|
|
|
: value,
|
|
|
|
item2[key],
|
|
|
|
'compare Item#' + key);
|
|
|
|
});
|
|
|
|
|
|
|
|
if (checkIdentity) {
|
|
|
|
equals(function() {
|
|
|
|
return item.bounds !== item2.bounds;
|
|
|
|
}, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
equals(item.bounds.toString(), item2.bounds.toString(),
|
|
|
|
'Compare Item#bounds');
|
|
|
|
|
|
|
|
if (checkIdentity) {
|
|
|
|
equals(function() {
|
|
|
|
return item.position !== item2.position;
|
|
|
|
}, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
equals(item.position.toString(), item2.position.toString(),
|
|
|
|
'Compare Item#position');
|
|
|
|
|
|
|
|
equals(function() {
|
|
|
|
return Base.equals(item.data, item2.data);
|
|
|
|
}, true);
|
|
|
|
|
|
|
|
if (item.matrix) {
|
|
|
|
if (checkIdentity) {
|
|
|
|
equals(function() {
|
|
|
|
return item.matrix !== item2.matrix;
|
|
|
|
}, true);
|
|
|
|
}
|
|
|
|
equals(item.matrix.toString(), item2.matrix.toString(),
|
|
|
|
'Compare Item#matrix');
|
|
|
|
}
|
|
|
|
|
|
|
|
// Path specific
|
|
|
|
if (item instanceof Path) {
|
|
|
|
var keys = ['closed', 'fullySelected', 'clockwise'];
|
|
|
|
for (var i = 0, l = keys.length; i < l; i++) {
|
|
|
|
var key = keys[i];
|
|
|
|
equals(item[key], item2[key], 'Compare Path#' + key);
|
|
|
|
}
|
2014-11-30 14:27:14 -05:00
|
|
|
equals(item.length, item2.length, 'Compare Path#length');
|
2014-08-16 13:24:54 -04:00
|
|
|
compareSegmentLists(item.segments, item2.segments, checkIdentity);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Shape specific
|
|
|
|
if (item instanceof Shape) {
|
|
|
|
var keys = ['shape', 'size', 'radius'];
|
|
|
|
for (var i = 0, l = keys.length; i < l; i++) {
|
|
|
|
var key = keys[i];
|
|
|
|
equals(item[key], item2[key], 'Compare Shape#' + key);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Group specific
|
|
|
|
if (item instanceof Group) {
|
|
|
|
equals(function() {
|
|
|
|
return item.clipped == item2.clipped;
|
|
|
|
}, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Layer specific
|
|
|
|
if (item instanceof Layer) {
|
|
|
|
equals(function() {
|
|
|
|
return dontShareProject
|
|
|
|
? item.project != item2.project
|
|
|
|
: item.project == item2.project;
|
|
|
|
}, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
// PlacedSymbol specific
|
|
|
|
if (item instanceof PlacedSymbol) {
|
|
|
|
if (dontShareProject) {
|
|
|
|
compareItems(item.symbol.definition, item2.symbol.definition,
|
|
|
|
cloned, checkIdentity, dontShareProject,
|
|
|
|
'Compare Symbol#definition');
|
|
|
|
} else {
|
|
|
|
equals(function() {
|
|
|
|
return item.symbol == item2.symbol;
|
|
|
|
}, true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Raster specific
|
|
|
|
if (item instanceof Raster) {
|
|
|
|
equals(item.size.toString(), item2.size.toString(),
|
|
|
|
'Compare Raster#size');
|
2014-11-30 14:27:14 -05:00
|
|
|
equals(item.width, item2.width, 'Compare Raster#width');
|
|
|
|
equals(item.height, item2.height, 'Compare Raster#height');
|
2014-08-16 13:24:54 -04:00
|
|
|
|
|
|
|
equals(item.ppi.toString(), item2.ppi.toString(),
|
|
|
|
'Compare Raster#ppi');
|
|
|
|
|
|
|
|
equals(item.source, item2.source, 'Compare Raster#source');
|
|
|
|
if (checkIdentity) {
|
|
|
|
equals(item.image, item2.image, 'Compare Raster#image');
|
|
|
|
}
|
|
|
|
equals(item.size.toString(), item2.size.toString(),
|
|
|
|
'Compare Raster#size');
|
|
|
|
equals(item.toDataURL() == item2.toDataURL(), true,
|
|
|
|
'Compare Raster#toDataUrl()');
|
|
|
|
}
|
|
|
|
|
|
|
|
// TextItem specific:
|
|
|
|
if (item instanceof TextItem) {
|
|
|
|
equals(item.content, item2.content, 'Compare Item#content');
|
|
|
|
}
|
|
|
|
|
|
|
|
// PointText specific:
|
|
|
|
if (item instanceof PointText) {
|
|
|
|
if (checkIdentity) {
|
|
|
|
equals(function() {
|
|
|
|
return item.point !== item2.point;
|
|
|
|
}, true);
|
|
|
|
}
|
|
|
|
equals(item.point.toString(), item2.point.toString(),
|
|
|
|
'Compare Item#point');
|
|
|
|
}
|
|
|
|
|
|
|
|
if (item.style) {
|
|
|
|
// Style
|
|
|
|
compareStyles(item.style, item2.style, checkIdentity);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check length of children and recursively compare them:
|
|
|
|
if (item.children) {
|
|
|
|
equals(function() {
|
|
|
|
return item.children.length == item2.children.length;
|
|
|
|
}, true);
|
|
|
|
for (var i = 0, l = item.children.length; i < l; i++) {
|
|
|
|
compareItems(item.children[i], item2.children[i], cloned,
|
|
|
|
checkIdentity, dontShareProject);
|
|
|
|
}
|
|
|
|
}
|
2011-05-26 04:20:21 -04:00
|
|
|
}
|
2012-12-01 14:05:48 -05:00
|
|
|
|
2013-03-17 09:42:20 -04:00
|
|
|
function compareProjects(project, project2) {
|
2014-08-16 13:24:54 -04:00
|
|
|
// Compare Project#symbols:
|
|
|
|
equals(function() {
|
|
|
|
return project.symbols.length == project2.symbols.length;
|
|
|
|
}, true);
|
|
|
|
for (var i = 0, l = project.symbols.length; i < l; i++) {
|
|
|
|
var definition1 = project.symbols[i].definition;
|
|
|
|
var definition2 = project2.symbols[i].definition;
|
|
|
|
compareItems(definition1, definition2, false, false, true,
|
|
|
|
'Compare Symbol#definition');
|
|
|
|
}
|
|
|
|
|
|
|
|
// Compare Project#layers:
|
|
|
|
equals(function() {
|
|
|
|
return project.layers.length == project2.layers.length;
|
|
|
|
}, true);
|
|
|
|
for (var i = 0, l = project.layers.length; i < l; i++) {
|
|
|
|
compareItems(project.layers[i], project2.layers[i], false, false, true);
|
|
|
|
}
|
2013-03-17 09:42:20 -04:00
|
|
|
}
|
|
|
|
|
2012-12-01 14:05:48 -05:00
|
|
|
// SVG
|
|
|
|
|
2013-04-23 10:19:08 -04:00
|
|
|
function createSVG(xml) {
|
2014-08-16 13:24:54 -04:00
|
|
|
return new DOMParser().parseFromString(
|
|
|
|
'<svg xmlns="http://www.w3.org/2000/svg">' + xml + '</svg>',
|
|
|
|
'text/xml');
|
2014-04-06 07:48:03 -04:00
|
|
|
}
|