Merge branch 'develop'

This commit is contained in:
Jürg Lehni 2017-04-19 19:55:26 +02:00
commit 836210bb86
64 changed files with 3237 additions and 1112 deletions

View file

@ -1,23 +1,15 @@
# This file is for unifying the coding style for different editors and IDEs.
# More information at http://editorconfig.org
root = true
[*]
indent_style = space
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.{js,html,css}]
indent_style = space
indent_size = 4
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.json]
indent_size = 2
[*.md]
indent_size = 4
trim_trailing_whitespace = false
[*.sh]
indent_size = 4

1
.gitignore vendored
View file

@ -1,2 +1,3 @@
/node_modules/
/dist/*/
/.nvmrc

6
.gitmodules vendored
View file

@ -1,3 +1,9 @@
[submodule "jsdoc"]
path = gulp/jsdoc
url = https://github.com/paperjs/jsdoc.git
[submodule "paper-jsdom"]
path = packages/paper-jsdom
url = https://github.com/paperjs/paper-jsdom.git
[submodule "paper-jsdom-canvas"]
path = packages/paper-jsdom-canvas
url = https://github.com/paperjs/paper-jsdom-canvas.git

View file

@ -7,6 +7,7 @@
"supernew": true,
"laxbreak": true,
"eqeqeq": false,
"-W041": false,
"eqnull": true,
"loopfunc": true,
"boss": true,

View file

@ -1,6 +1,55 @@
# Change Log
## `0.10.3` (Unreleased)
## `0.10.4`
### Changed
- Separate `paper` module on NPM into: `paper`, `paper-jsdom` and
`paper-jsdom-canvas` (#1252):
- `paper` is the main library, and can be used directly in a browser
context, e.g. a web browser or worker.
- `paper-jsdom` is a shim module for Node.js, offering headless use with SVG
importing / exporting.
- `paper-jsdom-canvas` is a shim module for Node.js, offering rendering
through Node Canvas.
### Added
- PaperScript: Support newer, external versions of Acorn.js for PaperScript
parsing, opening the doors to ES 2015 (#1183, #1275).
- Hit Tests: Implement `options.position` to hit `Item#position` (#1249).
- Split `Item#copyTo()` into `#addTo()` and `#copyTo()`.
### Fixed
- Intersections: Bring back special handling of curve end-points (#1284).
- Intersections: Correctly handle `Item#applyMatrix = false` (#1289).
- Boolean: Bring back on-path winding handling (#1281).
- Boolean: Pass on options in `PathItem#subtract(path, options)` (#1221).
- Boolean: Implement `options.trace` as a way to perform boolean operations on
the strokes / traces instead of the fills / areas of the involved paths
(#1221).
- Boolean: Always return `CompoundPath items (#1221).
- Style: Fix handling of gradient matrices when `Item#applyMatrix = false`
(#1238).
- Style: Prevent cleaning pre-existing styles when setting `Item#style` to an
object (#1277).
- Mouse Events: Only handle dragItem if the hitItem responds to `mousedrag`
events (#1247, #1286).
- Bounds: Clear parent's bounds cache when item's visibility changes (#1248).
- Bounds: Fix calculation of internal bounds with children and
`Item#applyMatrix = false` (#1250).
- Hit-Tests: Fix issue with non-invertible matrices ( #1271).
- SVG Import: Improve handling of sizes in percent (#1242).
- Rectangle: Improve handling of dimension properties, dealing better with
`left` / `top` / `right` / `bottom` / `center` values (#1147).
- Scene Graph: Do not allow inserting same item as child multiple times.
- Path: Fix handling of `insert = false` in `new Path.Constructor()`
initialization (#1305).
- PaperScript: Fix positive unary operator.
- Docs: Fix parameter sequence in Matrix constructor (#1273).
- Docs: Add documentation for options.bound and options.matrix in `#exportSVG()`
(#1254).
- Docs: Fix wrong `@link` references to bean properties.
## `0.10.3`
### Changed
- Node.js: Support v7, and keep testing v4 up to v7 in Travis CI.
@ -77,6 +126,8 @@
- Hit Tests: Fix stroke hit-testing for rounded shape items (#1207).
- Fix matrix cloning for groups with `#applyMatrix = false` ( #1225).
- Correctly handle offset in `Curve#divideAt(offset)` (#1230).
- Fix issue with `Curve#isStraight()` where handles were checked incorrectly
for collinearity (#1269).
- Fix `Line#getSide()` imprecisions when points are on the line.
- Docs: Fix documentation of `Project#hitTestAll()` (#536).
- Docs: Improve description of `option.class` value in `Project#hitTest()`

View file

@ -78,7 +78,7 @@ And from there onwards, you should be able to use Bower like this:
bower search paperjs
### Installing Paper.js for Node.js through NPM
### Installing Paper.js for Node.js
NPM is used to install Paper.js for use in Node.js. But before installing, you
need the Cairo Graphics library installed, see: <http://cairographics.org/>.
@ -125,6 +125,17 @@ You should now be able to install the Paper.js module from NPM:
npm install paper
### Installing Paper.js for Electron
[Node-Canvas](https://github.com/Automattic/node-canvas) is a native dependency.
In order to build it for use in Electron, which is likely to use a different
version of V8 than the Node binary installed in your system, you need to
manually specify the location of Electrons headers. Follow these steps to do
so:
[Electron — Using Native Node
Modules](https://electron.atom.io/docs/tutorial/using-native-node-modules/)
## Development
The main Paper.js source tree is hosted on

View file

@ -18,6 +18,7 @@
"gulpfile.js",
"gulp",
"node_modules",
"packages",
"projects",
"src",
"test",
@ -37,6 +38,7 @@
"canvas",
"svg",
"paper",
"paper.js"
"paper.js",
"paperjs"
]
}

503
dist/paper-core.js vendored

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

1849
dist/paper-full.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -11,18 +11,23 @@
*/
var gulp = require('gulp'),
bump = require('gulp-bump'),
jsonEditor = require('gulp-json-editor'),
git = require('gulp-git-streamed'),
run = require('run-sequence'),
shell = require('gulp-shell'),
options = require('../utils/options.js');
var jsonOptions = {
end_with_newline: true
};
gulp.task('publish', function() {
if (options.branch !== 'develop') {
throw new Error('Publishing is only allowed on the develop branch.');
}
return run(
'publish:version',
'publish:packages',
'publish:dist',
'publish:commit',
'publish:release',
@ -34,11 +39,25 @@ gulp.task('publish:version', function() {
// Reset the version value since we're executing this on the develop branch,
// but we don't wan the published version suffixed with '-develop'.
options.resetVersion();
return gulp.src([ 'package.json' ])
.pipe(bump({ version: options.version }))
return gulp.src(['package.json'])
.pipe(jsonEditor({
version: options.version
}, jsonOptions))
.pipe(gulp.dest('.'));
});
gulp.task('publish:packages', function() {
options.resetVersion(); // See 'publish:version'
return gulp.src(['packages/**/*.json'])
.pipe(jsonEditor({
version: options.version,
dependencies: {
paper: options.version
}
}, jsonOptions))
.pipe(gulp.dest('packages'));
});
gulp.task('publish:dist', ['dist']);
gulp.task('publish:commit', function() {

View file

@ -1,6 +1,6 @@
{
"name": "paper",
"version": "0.10.3",
"version": "0.10.4",
"description": "The Swiss Army Knife of Vector Graphics Scripting",
"license": "MIT",
"homepage": "http://paperjs.org",
@ -36,21 +36,15 @@
"engines": {
"node": ">=4.0.0 <8.0.0"
},
"dependencies": {
"jsdom": "^9.4.0",
"source-map-support": "^0.4.0"
},
"optionalDependencies": {
"canvas": "^1.3.5"
},
"devDependencies": {
"acorn": "~0.5.0",
"canvas": "^1.3.5",
"del": "^2.2.1",
"gulp": "^3.9.1",
"gulp-bump": "^2.2.0",
"gulp-cached": "^1.1.0",
"gulp-git-streamed": "^1.8.0",
"gulp-jshint": "^2.0.0",
"gulp-json-editor": "^2.2.1",
"gulp-prepro": "^2.4.0",
"gulp-qunits": "^2.1.1",
"gulp-rename": "^1.2.2",
@ -63,6 +57,7 @@
"gulp-whitespace": "^0.1.0",
"gulp-zip": "^3.2.0",
"husky": "^0.11.4",
"jsdom": "^9.4.0",
"jshint": "^2.9.2",
"jshint-summary": "^0.4.0",
"merge-stream": "^1.0.0",
@ -72,15 +67,12 @@
"require-dir": "^0.3.0",
"resemblejs": "^2.2.1",
"run-sequence": "^1.2.2",
"source-map-support": "^0.4.0",
"stats.js": "0.16.0",
"straps": "^2.1.0"
},
"browser": {
"canvas": false,
"jsdom": false,
"jsdom/lib/jsdom/living/generated/utils": false,
"source-map-support": false,
"./dist/node/window.js": false,
"./dist/node/self.js": false,
"./dist/node/extend.js": false
},
"keywords": [
@ -97,6 +89,7 @@
"canvas",
"svg",
"paper",
"paper.js"
"paper.js",
"paperjs"
]
}

1
packages/paper-jsdom Submodule

@ -0,0 +1 @@
Subproject commit 2bed95b950805864a91999afeaed469ac4403338

@ -0,0 +1 @@
Subproject commit 807961a794f920e107c483ce5301867af8c0cb27

View file

@ -14,6 +14,9 @@
"path": "../gulp",
"folder_exclude_patterns": ["jsdoc"]
},
{
"path": "../packages",
},
{
"path": "../travis",
},

View file

@ -108,9 +108,17 @@ var Line = Base.extend(/** @lends Line# */{
* @return {Number}
*/
getDistance: function(point) {
return Math.abs(Line.getSignedDistance(
this._px, this._py, this._vx, this._vy,
point.x, point.y, true));
return Math.abs(this.getSignedDistance(point));
},
// DOCS: document Line#getSignedDistance(point)
/**
* @param {Point} point
* @return {Number}
*/
getSignedDistance: function(point) {
return Line.getSignedDistance(this._px, this._py, this._vx, this._vy,
point.x, point.y, true);
},
isCollinear: function(line) {

View file

@ -52,8 +52,8 @@ var Matrix = Base.extend(/** @lends Matrix# */{
*
* @name Matrix#initialize
* @param {Number} a the a property of the transform
* @param {Number} c the c property of the transform
* @param {Number} b the b property of the transform
* @param {Number} c the c property of the transform
* @param {Number} d the d property of the transform
* @param {Number} tx the tx property of the transform
* @param {Number} ty the ty property of the transform

View file

@ -267,23 +267,34 @@ var Rectangle = Base.extend(/** @lends Rectangle# */{
return new ctor(this.width, this.height, this, 'setSize');
},
// properties to keep track of fix-width / height: They are on by default,
// and switched off once properties are used that change the outside of the
// rectangle, so combinations of: left / top / right / bottom.
_fw: 1,
_fh: 1,
setSize: function(/* size */) {
var size = Size.read(arguments);
// Keep track of how dimensions were specified through this._fix*
var size = Size.read(arguments),
sx = this._sx,
sy = this._sy,
w = size.width,
h = size.height;
// Keep track of how dimensions were specified through this._s*
// attributes.
// _fixX / Y can either be 0 (l), 0.5 (center) or 1 (r), and is used as
// direct factors to calculate the x / y adujstments from the size
// differences.
// _fixW / H is either 0 (off) or 1 (on), and is used to protect
// widht / height values against changes.
if (this._fixX)
this.x += (this.width - size.width) * this._fixX;
if (this._fixY)
this.y += (this.height - size.height) * this._fixY;
this.width = size.width;
this.height = size.height;
this._fixW = 1;
this._fixH = 1;
// _sx / _sy can either be 0 (left), 0.5 (center) or 1 (right), and is
// used as direct factors to calculate the x / y adjustments from the
// size differences.
// _fw / _fh can either be 0 (off) or 1 (on), and is used to protect
// width / height values against changes.
if (sx) {
this.x += (this.width - w) * sx;
}
if (sy) {
this.y += (this.height - h) * sy;
}
this.width = w;
this.height = h;
this._fw = this._fh = 1;
},
/**
@ -300,10 +311,12 @@ var Rectangle = Base.extend(/** @lends Rectangle# */{
},
setLeft: function(left) {
if (!this._fixW)
this.width -= left - this.x;
if (!this._fw) {
var amount = left - this.x;
this.width -= this._sx === 0.5 ? amount * 2 : amount;
}
this.x = left;
this._fixX = 0;
this._sx = this._fw = 0;
},
/**
@ -318,10 +331,12 @@ var Rectangle = Base.extend(/** @lends Rectangle# */{
},
setTop: function(top) {
if (!this._fixH)
this.height -= top - this.y;
if (!this._fh) {
var amount = top - this.y;
this.height -= this._sy === 0.5 ? amount * 2 : amount;
}
this.y = top;
this._fixY = 0;
this._sy = this._fh = 0;
},
/**
@ -336,14 +351,13 @@ var Rectangle = Base.extend(/** @lends Rectangle# */{
},
setRight: function(right) {
// Turn _fixW off if we specify two _fixX values
if (this._fixX !== undefined && this._fixX !== 1)
this._fixW = 0;
if (this._fixW)
this.x = right - this.width;
else
this.width = right - this.x;
this._fixX = 1;
if (!this._fw) {
var amount = right - this.x;
this.width = this._sx === 0.5 ? amount * 2 : amount;
}
this.x = right - this.width;
this._sx = 1;
this._fw = 0;
},
/**
@ -358,14 +372,13 @@ var Rectangle = Base.extend(/** @lends Rectangle# */{
},
setBottom: function(bottom) {
// Turn _fixH off if we specify two _fixY values
if (this._fixY !== undefined && this._fixY !== 1)
this._fixH = 0;
if (this._fixH)
this.y = bottom - this.height;
else
this.height = bottom - this.y;
this._fixY = 1;
if (!this._fh) {
var amount = bottom - this.y;
this.height = this._sy === 0.5 ? amount * 2 : amount;
}
this.y = bottom - this.height;
this._sy = 1;
this._fh = 0;
},
/**
@ -376,12 +389,22 @@ var Rectangle = Base.extend(/** @lends Rectangle# */{
* @ignore
*/
getCenterX: function() {
return this.x + this.width * 0.5;
return this.x + this.width / 2;
},
setCenterX: function(x) {
this.x = x - this.width * 0.5;
this._fixX = 0.5;
// If we're asked to fix the width or if _sx is already in center mode,
// just keep moving the center.
if (this._fw || this._sx === 0.5) {
this.x = x - this.width / 2;
} else {
if (this._sx) {
this.x += (x - this.x) * 2 * this._sx;
}
this.width = (x - this.x) * 2;
}
this._sx = 0.5;
this._fw = 0;
},
/**
@ -392,12 +415,22 @@ var Rectangle = Base.extend(/** @lends Rectangle# */{
* @ignore
*/
getCenterY: function() {
return this.y + this.height * 0.5;
return this.y + this.height / 2;
},
setCenterY: function(y) {
this.y = y - this.height * 0.5;
this._fixY = 0.5;
// If we're asked to fix the height or if _sy is already in center mode,
// just keep moving the center.
if (this._fh || this._sy === 0.5) {
this.y = y - this.height / 2;
} else {
if (this._sy) {
this.y += (y - this.y) * 2 * this._sy;
}
this.height = (y - this.y) * 2;
}
this._sy = 0.5;
this._fh = 0;
},
/**
@ -599,12 +632,15 @@ var Rectangle = Base.extend(/** @lends Rectangle# */{
/**
* Tests if the interior of this rectangle intersects the interior of
* another rectangle. Rectangles just touching each other are considered
* as non-intersecting.
* another rectangle. Rectangles just touching each other are considered as
* non-intersecting, except if a `epsilon` value is specified by which this
* rectangle's dimensions are increased before comparing.
*
* @param {Rectangle} rect the specified rectangle
* @param {Number} [epsilon=0] the epsilon against which to compare the
* rectangle's dimensions
* @return {Boolean} {@true if the rectangle and the specified rectangle
* intersect each other}
* intersect each other}
*
* @example {@paperscript}
* // Checking whether the bounding box of one item intersects with
@ -641,20 +677,13 @@ var Rectangle = Base.extend(/** @lends Rectangle# */{
* }
* }
*/
intersects: function(/* rect */) {
var rect = Rectangle.read(arguments);
return rect.x + rect.width > this.x
&& rect.y + rect.height > this.y
&& rect.x < this.x + this.width
&& rect.y < this.y + this.height;
},
touches: function(/* rect */) {
var rect = Rectangle.read(arguments);
return rect.x + rect.width >= this.x
&& rect.y + rect.height >= this.y
&& rect.x <= this.x + this.width
&& rect.y <= this.y + this.height;
intersects: function(/* rect, epsilon */) {
var rect = Rectangle.read(arguments),
epsilon = Base.read(arguments) || 0;
return rect.x + rect.width > this.x - epsilon
&& rect.y + rect.height > this.y - epsilon
&& rect.x < this.x + this.width + epsilon
&& rect.y < this.y + this.height + epsilon;
},
/**

View file

@ -528,7 +528,7 @@ Base.inject(/** @lends Base# */{
exportJSON: function(obj, options) {
var json = Base.serialize(obj, options);
return options && options.asString === false
return options && options.asString == false
? json
: JSON.stringify(json);
},

View file

@ -90,7 +90,7 @@ var Emitter = {
if (setTarget)
event.currentTarget = this;
for (var i = 0, l = handlers.length; i < l; i++) {
if (handlers[i].apply(this, args) === false) {
if (handlers[i].apply(this, args) == false) {
// If the handler returns false, prevent the default behavior
// and stop propagation of the event by calling stop()
if (event && event.stop)

View file

@ -57,7 +57,6 @@ var PaperScope = Base.extend(/** @lends PaperScope# */{
this.project = null;
this.projects = [];
this.tools = [];
this.palettes = [];
// Assign a unique id to each scope .
this._id = PaperScope._id++;
PaperScope._scopes[this._id] = this;
@ -124,10 +123,10 @@ var PaperScope = Base.extend(/** @lends PaperScope# */{
*
* @option [settings.insertItems=true] {Boolean} controls whether newly
* created items are automatically inserted into the scene graph, by
* adding them to {@link Project#getActiveLayer()}
* adding them to {@link Project#activeLayer}
* @option [settings.applyMatrix=true] {Boolean} controls what value newly
* created items have their {@link Item#getApplyMatrix()} property set
* to (Note that not all items can set this to `false`)
* created items have their {@link Item#applyMatrix} property set to
* (Note that not all items can set this to `false`)
* @option [settings.handleSize=4] {Number} the size of the curve handles
* when drawing selections
* @option [settings.hitTolerance=0] {Number} the default tolerance for hit-
@ -277,14 +276,11 @@ var PaperScope = Base.extend(/** @lends PaperScope# */{
// Remove all projects, views and tools.
// This also removes the installed event handlers.
var projects = this.projects,
tools = this.tools,
palettes = this.palettes;
tools = this.tools;
for (var i = projects.length - 1; i >= 0; i--)
projects[i].remove();
for (var i = tools.length - 1; i >= 0; i--)
tools[i].remove();
for (var i = palettes.length - 1; i >= 0; i--)
palettes[i].remove();
},
remove: function() {

View file

@ -15,12 +15,33 @@
* @namespace
*/
Base.exports.PaperScript = function() {
// Locally turn of exports and define for inlined acorn.
// Just declaring the local vars is enough, as they will be undefined.
var exports, define,
// The scope into which the library is loaded.
scope = this;
/*#*/ include('../../node_modules/acorn/acorn.min.js', { exports: false });
// `this` == global scope, as the function is called with `.call(this);`
var global = this,
// See if there is a global Acorn in the browser already.
acorn = global.acorn;
// Also try importing an outside version of Acorn.
if (!acorn && typeof require !== 'undefined') {
try { acorn = require('acorn'); } catch(e) {}
}
// If no Acorn was found, load the bundled version.
if (!acorn) {
// Provide our own local exports and module object so that Acorn gets
// assigned to it and ends up in the local acorn object.
var exports, module;
acorn = exports = module = {};
/*#*/ include('../../node_modules/acorn/acorn.js', { exports: false });
// Clear object again if it wasn't loaded here; for load.js, see below.
if (!acorn.version)
acorn = null;
}
function parse(code, options) {
// NOTE: When using load.js, Acorn will end up in global.acorn and will
// not be immediately available, so we need to check for it here again.
// We also give global.acorn the preference over the bundled one, so
// people can load their own preferred version in sketch.paperjs.org
return (global.acorn || acorn).parse(code, options);
}
// Operators to overload
@ -37,7 +58,7 @@ Base.exports.PaperScript = function() {
var unaryOperators = {
'-': '__negate',
'+': null
'+': '__self'
};
// Inject underscored math methods as aliases to Point, Size and Color.
@ -48,7 +69,12 @@ Base.exports.PaperScript = function() {
// classes using Straps.js' #inject()
this['__' + name] = '#' + name;
},
{}
{
// Needed for '+' unary operator:
__self: function() {
return this;
}
}
);
Point.inject(fields);
Size.inject(fields);
@ -79,7 +105,7 @@ Base.exports.PaperScript = function() {
// Unary Operator Handler
function $__(operator, value) {
var handler = unaryOperators[operator];
if (handler && value && value[handler])
if (value && value[handler])
return value[handler]();
switch (operator) {
case '+': return +value;
@ -89,10 +115,6 @@ Base.exports.PaperScript = function() {
// AST Helpers
function parse(code, options) {
return scope.acorn.parse(code, options);
}
/**
* Compiles PaperScript code into JavaScript code.
*
@ -146,7 +168,7 @@ Base.exports.PaperScript = function() {
// Returns the code between two nodes, e.g. an operator and white-space.
function getBetween(left, right) {
return code.substring(getOffset(left.range[1]),
getOffset(right.range[0]));
getOffset(right.range[0]) - 1);
}
// Replaces the node's code with a new version and keeps insertions
@ -342,7 +364,7 @@ Base.exports.PaperScript = function() {
};
}
// Now do the parsing magic
walkAST(parse(code, { ranges: true }));
walkAST(parse(code, { ranges: true, preserveParens: true }));
if (map) {
if (offsetCode) {
// Adjust the line offset of the resulting code if required.

View file

@ -35,8 +35,9 @@ paper = new (PaperScope.inject(Base.exports, {
// If we're on node, require some additional functionality now before finishing:
// - PaperScript support in require() with sourceMaps
// - exportFrames / exportImage on CanvasView
if (paper.agent.node)
if (paper.agent.node) {
require('./node/extend.js')(paper);
}
// https://github.com/umdjs/umd
if (typeof define === 'function' && define.amd) {

View file

@ -17,14 +17,19 @@
// their shared scope.
/* global document:true, window:true */
// Create a window variable valid in the paper.js scope, that points to the
// native window in browsers and the emulated JSDom one in node.js
// In workers, and on Node.js when no Canvas is present, `window` is null (but
// `self` is defined), so we can use the validity of the local window object to
// detect a worker-like context in the library.
// Make sure `self` always points to a window object, also on Node.js.
self = self || require('./node/window.js');
// Set up a local `window` variable valid across the full the paper.js scope,
// pointing to the native window in browsers and the one provided by JSDom in
// Node.js
// In workers and on Node.js, the global `window` variable is null. In workers,
// `self` is defined as a `WorkerGlobalScope` object, while in Node.js, `self`
// is null.
// When `self` is null (Node.js only). './node/self.js' is required to provide
// a window object through JSDom and assigned it to `self`.
// When `self.window` and therefore the local `window` is still null after that,
// we know that we are in a worker-like context. This check is used all across
// the library, see for example `View.create()`.
// NOTE: We're not modifying the global `self` here. We receive its value passed
// to the paper.js function scope, and this is the one that is modified here.
self = self || require('./node/self.js');
var window = self.window,
document = self.document;

View file

@ -127,6 +127,8 @@ var HitResult = Base.extend(/** @lends HitResult# */{
// Only first or last segment hits on path (mutually exclusive
// with segments: true)
ends: false,
// Hit test the item position
position: false,
// Hit test the center of the bounds
center: false,
// Hit test the corners and side-centers of the bounding box

View file

@ -154,7 +154,7 @@ new function() { // Injection scope for various item event handlers
this._style = new Style(project._currentStyle, this, project);
// Do not add to the project if it's an internal path, or if
// props.insert or settings.isnertItems is false.
if (internal || hasProps && props.insert === false
if (internal || hasProps && props.insert == false
|| !settings.insertItems && !(hasProps && props.insert === true)) {
this._setProject(project);
} else {
@ -407,16 +407,20 @@ new function() { // Injection scope for various item event handlers
// call _changed() if a property was modified.
function(name) {
var part = Base.capitalize(name),
name = '_' + name;
key = '_' + name,
flags = {
// #locked does not change appearance, all others do:
locked: /*#=*/ChangeFlag.ATTRIBUTE,
// #visible changes apperance
visible: /*#=*/(Change.ATTRIBUTE | Change.GEOMETRY)
};
this['get' + part] = function() {
return this[name];
return this[key];
};
this['set' + part] = function(value) {
if (value != this[name]) {
this[name] = value;
// #locked does not change appearance, all others do:
this._changed(name === '_locked'
? /*#=*/ChangeFlag.ATTRIBUTE : /*#=*/Change.ATTRIBUTE);
if (value != this[key]) {
this[key] = value;
this._changed(flags[name] || /*#=*/Change.ATTRIBUTE);
}
};
},
@ -889,14 +893,14 @@ new function() { // Injection scope for various item event handlers
* Private method that deals with the calling of _getBounds, recursive
* matrix concatenation and handles all the complicated caching mechanisms.
*/
_getCachedBounds: function(matrix, options) {
_getCachedBounds: function(matrix, options, noInternal) {
// See if we can cache these bounds. We only cache the bounds
// transformed with the internally stored _matrix, (the default if no
// matrix is passed).
matrix = matrix && matrix._orNullIfIdentity();
// Do not transform by the internal matrix for internal, untransformed
// bounds.
var internal = options.internal,
var internal = options.internal && !noInternal,
cacheItem = options.cacheItem,
_matrix = internal ? null : this._matrix._orNullIfIdentity(),
// Create a key for caching, reflecting all bounds options.
@ -919,7 +923,7 @@ new function() { // Injection scope for various item event handlers
var cached = this._bounds[cacheKey] = {
rect: bounds.clone(),
// Mark as internal, so Item#transform() won't transform it
internal: options.internal
internal: internal
};
}
return bounds;
@ -1006,8 +1010,11 @@ new function() { // Injection scope for various item event handlers
for (var i = 0, l = items.length; i < l; i++) {
var item = items[i];
if (item._visible && !item.isEmpty()) {
// Pass true for noInternal, since even when getting
// internal bounds for this item, we need to apply the
// matrices to its children.
var rect = item._getCachedBounds(
matrix && matrix.appended(item._matrix), options);
matrix && matrix.appended(item._matrix), options, true);
x1 = Math.min(rect.x, x1);
y1 = Math.min(rect.y, y1);
x2 = Math.max(rect.x + rect.width, x2);
@ -1833,10 +1840,12 @@ new function() { // Injection scope for hit-test functions shared with project
* Segment#handleIn} / {@link Segment#handleOut}) of path segments.
* @option options.ends {Boolean} only hit-test for the first or last
* segment points of open path items
* @option options.bounds {Boolean} hit-test the corners and side-centers of
* the bounding rectangle of items ({@link Item#bounds})
* @option options.position {Boolean} hit-test the {@link Item#position} of
* of items, which depends on the setting of {@link Item#pivot}
* @option options.center {Boolean} hit-test the {@link Rectangle#center} of
* the bounding rectangle of items ({@link Item#bounds})
* @option options.bounds {Boolean} hit-test the corners and side-centers of
* the bounding rectangle of items ({@link Item#bounds})
* @option options.guides {Boolean} hit-test items that have {@link
* Item#guide} set to `true`
* @option options.selected {Boolean} only hit selected items
@ -1892,7 +1901,7 @@ new function() { // Injection scope for hit-test functions shared with project
// need to apply the inverted item matrix.
tolerancePadding = options._tolerancePadding = new Size(
Path._getStrokePadding(tolerance,
matrix.inverted()._shiftless()));
matrix._shiftless().invert()));
// Transform point to local coordinates.
point = matrix._inverseTransform(point);
// If the matrix is non-reversible, point will now be `null`:
@ -1924,32 +1933,39 @@ new function() { // Injection scope for hit-test functions shared with project
return hit;
}
function checkBounds(type, part) {
var pt = bounds['get' + part]();
function checkPoint(type, part) {
var pt = part ? bounds['get' + part]() : that.getPosition();
// Since there are transformations, we cannot simply use a numerical
// tolerance value. Instead, we divide by a padding size, see above.
if (point.subtract(pt).divide(tolerancePadding).length <= 1) {
return new HitResult(type, that,
{ name: Base.hyphenate(part), point: pt });
return new HitResult(type, that, {
name: part ? Base.hyphenate(part) : type,
point: pt
});
}
}
var checkPosition = options.position,
checkCenter = options.center,
checkBounds = options.bounds;
// Ignore top level layers by checking for _parent:
if (checkSelf && (options.center || options.bounds) && this._parent) {
// Don't get the transformed bounds, check against transformed
// points instead
bounds = this.getInternalBounds();
if (options.center) {
res = checkBounds('center', 'Center');
if (checkSelf && this._parent
&& (checkPosition || checkCenter || checkBounds)) {
if (checkCenter || checkBounds) {
// Get the internal, untransformed bounds, as we check against
// transformed points.
bounds = this.getInternalBounds();
}
if (!res && options.bounds) {
// TODO: Move these into a private scope
res = checkPosition && checkPoint('position') ||
checkCenter && checkPoint('center', 'Center');
if (!res && checkBounds) {
// TODO: Move these into a static property on Rectangle?
var points = [
'TopLeft', 'TopRight', 'BottomLeft', 'BottomRight',
'LeftCenter', 'TopCenter', 'RightCenter', 'BottomCenter'
];
for (var i = 0; i < 8 && !res; i++) {
res = checkBounds('bounds', points[i]);
res = checkPoint('bounds', points[i]);
}
}
res = filter(res);
@ -1964,7 +1980,7 @@ new function() { // Injection scope for hit-test functions shared with project
// If the item has a non-scaling stroke, we need to
// apply the inverted viewMatrix to stroke dimensions.
this.getStrokeScaling() ? null
: viewMatrix.inverted()._shiftless()))
: viewMatrix._shiftless().invert()))
|| null;
}
// Transform the point back to the outer coordinate system.
@ -2243,6 +2259,14 @@ new function() { // Injection scope for hit-test functions shared with project
* @name Item#exportSVG
* @function
*
* @option [options.bounds='view'] {String|Rectangle} the bounds of the area
* to export, either as a string ({@values 'view', content'}), or a
* {@link Rectangle} object: `'view'` uses the view bounds,
* `'content'` uses the stroke bounds of all content
* @option [options.matrix=paper.view.matrix] {Matrix} the matrix with which
* to transform the exported content: If `options.bounds` is set to
* `'view'`, `paper.view.matrix` is used, for all other settings of
* `options.bounds` the identity matrix is used.
* @option [options.asString=false] {Boolean} whether a SVG node or a
* `String` is to be returned
* @option [options.precision=5] {Number} the amount of fractional digits in
@ -2365,14 +2389,19 @@ new function() { // Injection scope for hit-test functions shared with project
// Remove the items from their parents first, since they might be
// inserted into their own parents, affecting indices.
// Use the loop also to filter invalid items.
var inserted = {};
for (var i = items.length - 1; i >= 0; i--) {
var item = items[i];
if (!item) {
var item = items[i],
id = item && item._id;
// If an item was inserted already, it must be included multiple
// times in the items array. Only insert once.
if (!item || inserted[id]) {
items.splice(i, 1);
} else {
// Notify parent of change. Don't notify item itself yet,
// as we're doing so when adding it to the new owner below.
item._remove(false, true);
inserted[id] = true;
}
}
Base.splice(children, items, index, 0);
@ -2389,7 +2418,7 @@ new function() { // Injection scope for hit-test functions shared with project
if (name)
item.setName(name);
if (notifySelf)
this._changed(/*#=*/Change.INSERTION);
item._changed(/*#=*/Change.INSERTION);
}
this._changed(/*#=*/Change.CHILDREN);
} else {
@ -2506,17 +2535,27 @@ new function() { // Injection scope for hit-test functions shared with project
moveBelow: '#insertBelow',
/**
* When passed a project, copies the item to the project,
* or duplicates it within the same project. When passed an item,
* copies the item into the specified item.
* Adds it to the specified owner, which can be either a {@link Item} or a
* {@link Project}.
*
* @param {Project|Layer|Group|CompoundPath} owner the item or project to
* add the item to
* @return {Item} the item itself, if it was successfully added
*/
addTo: function(owner) {
return owner._insertItem(undefined, this);
},
/**
* Clones the item and adds it to the specified owner, which can be either
* a {@link Item} or a {@link Project}.
*
* @param {Project|Layer|Group|CompoundPath} owner the item or project to
* copy the item to
* @return {Item} the new copy of the item
* @return {Item} the new copy of the item, if it was successfully added
*/
copyTo: function(owner) {
// Pass false for insert, since we're inserting at a specific location.
return owner._insertItem(undefined, this.clone(false));
return this.clone(false).addTo(owner);
},
/**
@ -3342,18 +3381,19 @@ new function() { // Injection scope for hit-test functions shared with project
if (matrix && matrix.isIdentity())
matrix = null;
var _matrix = this._matrix,
transform = matrix && !matrix.isIdentity(),
applyMatrix = (_applyMatrix || this._applyMatrix)
// Don't apply _matrix if the result of concatenating with
// matrix would be identity.
&& ((!_matrix.isIdentity() || matrix)
&& ((!_matrix.isIdentity() || transform)
// Even if it's an identity matrix, we still need to
// recursively apply the matrix to children.
|| _applyMatrix && _applyRecursively && this._children);
// Bail out if there is nothing to do.
if (!matrix && !applyMatrix)
if (!transform && !applyMatrix)
return this;
// Simply prepend the internal matrix with the passed one:
if (matrix) {
if (transform) {
// Keep a backup of the last valid state before the matrix becomes
// non-invertible. This is then used again in setBounds to restore.
if (!matrix.isInvertible() && _matrix.isInvertible())
@ -3364,28 +3404,39 @@ new function() { // Injection scope for hit-test functions shared with project
// internal _matrix transformations to the item's content.
// Application is not possible on Raster, PointText, SymbolItem, since
// the matrix is where the actual transformation state is stored.
if (applyMatrix = applyMatrix && this._transformContent(_matrix,
_applyRecursively, _setApplyMatrix)) {
// When the _matrix could be applied, we also need to transform
// color styles (only gradients so far) and pivot point:
var pivot = this._pivot,
style = this._style,
// pass true for _dontMerge so we don't recursively transform
if (applyMatrix) {
if (this._transformContent(_matrix, _applyRecursively,
_setApplyMatrix)) {
var pivot = this._pivot;
if (pivot)
_matrix._transformPoint(pivot, pivot, true);
// Reset the internal matrix to the identity transformation if
// it was possible to apply it.
_matrix.reset(true);
// Set the internal _applyMatrix flag to true if we're told to
// do so
if (_setApplyMatrix && this._canApplyMatrix)
this._applyMatrix = true;
} else {
applyMatrix = transform = false;
}
}
if (transform) {
// When a new matrix was applied, we also need to transform gradient
// color points. These always need transforming, regardless of
// #applyMatrix, as they are defined in the parent's coordinate
// system.
// TODO: Introduce options to control whether fills should be
// transformed or not.
var style = this._style,
// Pass true for _dontMerge so we don't recursively transform
// styles on groups' children.
fillColor = style.getFillColor(true),
strokeColor = style.getStrokeColor(true);
if (pivot)
_matrix._transformPoint(pivot, pivot, true);
if (fillColor)
fillColor.transform(_matrix);
fillColor.transform(matrix);
if (strokeColor)
strokeColor.transform(_matrix);
// Reset the internal matrix to the identity transformation if it
// was possible to apply it.
_matrix.reset(true);
// Set the internal _applyMatrix flag to true if we're told to do so
if (_setApplyMatrix && this._canApplyMatrix)
this._applyMatrix = true;
strokeColor.transform(matrix);
}
// Calling _changed will clear _bounds and _position, but depending
// on matrix we can calculate and set them again, so preserve them.
@ -4078,12 +4129,13 @@ new function() { // Injection scope for hit-test functions shared with project
_setStyles: function(ctx, param, viewMatrix) {
// We can access internal properties since we're only using this on
// items without children, where styles would be merged.
var style = this._style;
var style = this._style,
matrix = this._matrix;
if (style.hasFill()) {
ctx.fillStyle = style.getFillColor().toCanvasStyle(ctx);
ctx.fillStyle = style.getFillColor().toCanvasStyle(ctx, matrix);
}
if (style.hasStroke()) {
ctx.strokeStyle = style.getStrokeColor().toCanvasStyle(ctx);
ctx.strokeStyle = style.getStrokeColor().toCanvasStyle(ctx, matrix);
ctx.lineWidth = style.getStrokeWidth();
var strokeJoin = style.getStrokeJoin(),
strokeCap = style.getStrokeCap(),
@ -4229,8 +4281,8 @@ new function() { // Injection scope for hit-test functions shared with project
// on the temporary canvas.
ctx.translate(-itemOffset.x, -itemOffset.y);
}
// Apply globalMatrix when drawing into temporary canvas.
if (transform) {
// Apply viewMatrix when drawing into temporary canvas.
(direct ? matrix : viewMatrix).applyToContext(ctx);
}
if (clip) {

View file

@ -268,7 +268,7 @@ var Project = PaperScopeItem.extend(/** @lends Project# */{
/**
* @bean
* @deprecated use {@link #getSymbolDefinitions()} instead.
* @deprecated use {@link #symbolDefinitions} instead.
*/
getSymbols: 'getSymbolDefinitions',
@ -287,7 +287,7 @@ var Project = PaperScopeItem.extend(/** @lends Project# */{
for (var id in selectionItems) {
var item = selectionItems[id],
selection = item._selection;
if (selection & /*#=*/ItemSelection.ITEM && item.isInserted()) {
if ((selection & /*#=*/ItemSelection.ITEM) && item.isInserted()) {
items.push(item);
} else if (!selection) {
this._updateSelection(item);
@ -424,10 +424,12 @@ var Project = PaperScopeItem.extend(/** @lends Project# */{
* Segment#handleIn} / {@link Segment#handleOut}) of path segments.
* @option options.ends {Boolean} only hit-test for the first or last
* segment points of open path items
* @option options.bounds {Boolean} hit-test the corners and side-centers of
* the bounding rectangle of items ({@link Item#bounds})
* @option options.position {Boolean} hit-test the {@link Item#position} of
* of items, which depends on the setting of {@link Item#pivot}
* @option options.center {Boolean} hit-test the {@link Rectangle#center} of
* the bounding rectangle of items ({@link Item#bounds})
* @option options.bounds {Boolean} hit-test the corners and side-centers of
* the bounding rectangle of items ({@link Item#bounds})
* @option options.guides {Boolean} hit-test items that have {@link
* Item#guide} set to `true`
* @option options.selected {Boolean} only hit selected items
@ -758,6 +760,14 @@ var Project = PaperScopeItem.extend(/** @lends Project# */{
* @name Project#exportSVG
* @function
*
* @option [options.bounds='view'] {String|Rectangle} the bounds of the area
* to export, either as a string ({@values 'view', content'}), or a
* {@link Rectangle} object: `'view'` uses the view bounds,
* `'content'` uses the stroke bounds of all content
* @option [options.matrix=paper.view.matrix] {Matrix} the matrix with which
* to transform the exported content: If `options.bounds` is set to
* `'view'`, `paper.view.matrix` is used, for all other settings of
* `options.bounds` the identity matrix is used.
* @option [options.asString=false] {Boolean} whether a SVG node or a
* `String` is to be returned
* @option [options.precision=5] {Number} the amount of fractional digits in

View file

@ -219,7 +219,7 @@ var Raster = Item.extend(/** @lends Raster# */{
/**
* @private
* @bean
* @deprecated use {@link #getResolution()} instead.
* @deprecated use {@link #resolution} instead.
*/
getPpi: '#getResolution',

View file

@ -28,8 +28,8 @@ var Shape = Item.extend(/** @lends Shape# */{
radius: null
},
initialize: function Shape(props) {
this._initialize(props);
initialize: function Shape(props, point) {
this._initialize(props, point);
},
_equals: function(item) {
@ -63,7 +63,7 @@ var Shape = Item.extend(/** @lends Shape# */{
/**
* @private
* @bean
* @deprecated use {@link #getType()} instead.
* @deprecated use {@link #type} instead.
*/
getShape: '#getType',
setShape: '#setType',
@ -187,6 +187,10 @@ var Shape = Item.extend(/** @lends Shape# */{
toShape: '#clone',
_asPathItem: function() {
return this.toPath(false);
},
_draw: function(ctx, param, viewMatrix, strokeMatrix) {
var style = this._style,
hasFill = style.hasFill(),
@ -386,11 +390,11 @@ new function() { // Scope for _contains() and _hitTestSelf() code.
// Mess with indentation in order to get more line-space below:
statics: new function() {
function createShape(type, point, size, radius, args) {
var item = new Shape(Base.getNamed(args));
var item = new Shape(Base.getNamed(args), point);
item._type = type;
item._size = size;
item._radius = radius;
return item.translate(point);
return item;
}
return /** @lends Shape */{

View file

@ -122,7 +122,7 @@ var SymbolDefinition = Base.extend(/** @lends SymbolDefinition# */{
/**
* @bean
* @deprecated use {@link #getItem()} instead.
* @deprecated use {@link #item} instead.
*/
getDefinition: '#getItem',
setDefinition: '#setItem',

View file

@ -103,7 +103,7 @@ var SymbolItem = Item.extend(/** @lends SymbolItem# */{
/**
* @bean
* @deprecated use {@link #getDefinition()} instead.
* @deprecated use {@link #definition} instead.
*/
getSymbol: '#getDefinition',
setSymbol: '#setDefinition',
@ -118,7 +118,7 @@ var SymbolItem = Item.extend(/** @lends SymbolItem# */{
return item._getCachedBounds(item._matrix.prepended(matrix), options);
},
_hitTestSelf: function(point, options, viewMatrix, strokeMatrix) {
_hitTestSelf: function(point, options, viewMatrix) {
var res = this._definition._item._hitTest(point, options, viewMatrix);
// TODO: When the symbol's definition is a path, should hitResult
// contain information like HitResult#curve?

View file

@ -43,7 +43,7 @@ if (typeof window === 'object') {
include('../node_modules/stats.js/build/stats.min.js');
include('paper.js');
}
} else {
} else if (typeof require !== 'undefined') {
// Node.js based loading through Prepro.js:
var prepro = require('prepro/lib/node'),
// Load the default browser-based options for further amendments.

View file

@ -15,7 +15,7 @@
// - Various Node Canvas methods, routed through from HTMLCanvasElement:
// toBuffer, pngStream, createPNGStream, jpgStream, createJPGStream
module.exports = function(window) {
module.exports = function(self, requireName) {
var Canvas;
try {
Canvas = require('canvas');
@ -25,14 +25,17 @@ module.exports = function(window) {
// - On the browser, this corresponds to a worker context.
// - On Node.js, it basically means the canvas is missing or not working
// which can be treated the same way.
delete window.window;
console.info(
'Unable to load Canvas module. Running in a headless context.');
delete self.window;
// Check the required module's name to see if it contains canvas, and
// only complain about its lack if the module requires it.
if (/\bcanvas\b/.test(requireName)) {
throw new Error('Unable to load canvas module.');
}
return;
}
var idlUtils = require('jsdom/lib/jsdom/living/generated/utils'),
HTMLCanvasElement = window.HTMLCanvasElement;
var HTMLCanvasElement = self.HTMLCanvasElement,
idlUtils = require('jsdom/lib/jsdom/living/generated/utils');
// Add fake HTMLCanvasElement#type property:
Object.defineProperty(HTMLCanvasElement.prototype, 'type', {

View file

@ -57,8 +57,8 @@ module.exports = function(paper) {
paper.PaperScope.inject({
createCanvas: function(width, height, type) {
// Do not use CanvasProvider.getCanvas(), since we may be changing
// the underlying node-canvas and don't want to release it after
// back into the pool.
// the underlying node-canvas when requesting PDF support, and don't
// want to release it after back into the pool.
var canvas = paper.document.createElement('canvas');
canvas.width = width;
canvas.height = height;

60
src/node/self.js Normal file
View file

@ -0,0 +1,60 @@
/*
* Paper.js - The Swiss Army Knife of Vector Graphics Scripting.
* http://paperjs.org/
*
* Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey
* http://scratchdisk.com/ & http://jonathanpuckey.com/
*
* Distributed under the MIT license. See LICENSE file for details.
*
* All rights reserved.
*/
// Node.js emulation layer of browser environment, based on jsdom with node-
// canvas integration.
var path = require('path');
// Determine the name by which name the module was required (either 'paper',
// 'paper-jsdom' or 'paper-jsdom-canvas'), and use this to determine if error
// exceptions should be thrown or if loading should fail silently.
var parent = module.parent.parent,
requireName = parent && path.basename(path.dirname(parent.filename));
requireName = /^paper/.test(requireName) ? requireName : 'paper';
var jsdom,
self;
try {
jsdom = require('jsdom');
} catch(e) {
// Check the required module's name to see if it contains jsdom, and only
// complain about its lack if the module requires it.
if (/\bjsdom\b/.test(requireName)) {
throw new Error('Unable to load jsdom module.');
}
}
if (jsdom) {
// Create our document and window objects through jsdom.
/* global document:true, window:true */
var document = jsdom.jsdom('<html><body></body></html>', {
// Use the current working directory as the document's origin, so
// requests to local files work correctly with CORS.
url: 'file://' + process.cwd() + '/',
features: {
FetchExternalResources: ['img', 'script']
}
});
self = document.defaultView;
require('./canvas.js')(self, requireName);
require('./xml.js')(self);
} else {
self = {
navigator: {
userAgent: 'Node.js (' + process.platform + '; U; rv:' +
process.version + ')'
}
};
}
module.exports = self;

View file

@ -1,57 +0,0 @@
/*
* Paper.js - The Swiss Army Knife of Vector Graphics Scripting.
* http://paperjs.org/
*
* Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey
* http://scratchdisk.com/ & http://jonathanpuckey.com/
*
* Distributed under the MIT license. See LICENSE file for details.
*
* All rights reserved.
*/
// Node.js emulation layer of browser environment, based on jsdom with node-
// canvas integration.
var jsdom = require('jsdom');
// Create our document and window objects through jsdom.
/* global document:true, window:true */
var document = jsdom.jsdom('<html><body></body></html>', {
// Use the current working directory as the document's origin, so
// requests to local files work correctly with CORS.
url: 'file://' + process.cwd() + '/',
features: {
FetchExternalResources: ['img', 'script']
}
}),
window = document.defaultView;
require('./canvas.js')(window);
// Define XMLSerializer and DOMParser shims, to emulate browser behavior.
// Effort to bring this to jsdom: https://github.com/tmpvar/jsdom/issues/1368
function XMLSerializer() {
}
XMLSerializer.prototype.serializeToString = function(node) {
if (!node)
return '';
var text = node.outerHTML;
// Fix a jsdom issue where all SVG tagNames are lowercased:
// https://github.com/tmpvar/jsdom/issues/620
var tagNames = ['linearGradient', 'radialGradient', 'clipPath', 'textPath'];
for (var i = 0, l = tagNames.length; i < l; i++) {
var tagName = tagNames[i];
text = text.replace(
new RegExp('(<|</)' + tagName.toLowerCase() + '\\b', 'g'),
function(match, start) {
return start + tagName;
});
}
return text;
};
window.XMLSerializer = XMLSerializer;
module.exports = window;

40
src/node/xml.js Normal file
View file

@ -0,0 +1,40 @@
/*
* Paper.js - The Swiss Army Knife of Vector Graphics Scripting.
* http://paperjs.org/
*
* Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey
* http://scratchdisk.com/ & http://jonathanpuckey.com/
*
* Distributed under the MIT license. See LICENSE file for details.
*
* All rights reserved.
*/
module.exports = function(self) {
// Define XMLSerializer shim, to emulate browser behavior.
// Effort to bring XMLSerializer to jsdom:
// https://github.com/tmpvar/jsdom/issues/1368
self.XMLSerializer = function XMLSerializer() {
};
self.XMLSerializer.prototype = {
serializeToString: function(node) {
if (!node)
return '';
// Fix a jsdom issue where all SVG tagNames are lowercased:
// https://github.com/tmpvar/jsdom/issues/620
var text = node.outerHTML,
tagNames = ['linearGradient', 'radialGradient', 'clipPath',
'textPath'];
for (var i = 0, l = tagNames.length; i < l; i++) {
var tagName = tagNames[i];
text = text.replace(
new RegExp('(<|</)' + tagName.toLowerCase() + '\\b', 'g'),
function(match, start) {
return start + tagName;
});
}
return text;
}
};
};

View file

@ -17,7 +17,7 @@
// The paper.js version.
// NOTE: Adjust value here before calling `gulp publish`, which then updates and
// publishes the various JSON package files automatically.
var version = '0.10.3';
var version = '0.10.4';
// If this file is loaded in the browser, we're in load.js mode.
var load = typeof window === 'object';

View file

@ -181,7 +181,7 @@ var CompoundPath = PathItem.extend(/** @lends CompoundPath# */{
/**
* The first Segment contained within the compound-path, a short-cut to
* calling {@link Path#getFirstSegment()} on {@link Item#getFirstChild()}.
* calling {@link Path#firstSegment} on {@link Item#firstChild}.
*
* @bean
* @type Segment
@ -193,7 +193,7 @@ var CompoundPath = PathItem.extend(/** @lends CompoundPath# */{
/**
* The last Segment contained within the compound-path, a short-cut to
* calling {@link Path#getLastSegment()} on {@link Item#getLastChild()}.
* calling {@link Path#lastChild} on {@link Item#lastChild}.
*
* @bean
* @type Segment
@ -220,7 +220,7 @@ var CompoundPath = PathItem.extend(/** @lends CompoundPath# */{
/**
* The first Curve contained within the compound-path, a short-cut to
* calling {@link Path#getFirstCurve()} on {@link Item#getFirstChild()}.
* calling {@link Path#firstCurve} on {@link Item#firstChild}.
*
* @bean
* @type Curve
@ -232,7 +232,7 @@ var CompoundPath = PathItem.extend(/** @lends CompoundPath# */{
/**
* The last Curve contained within the compound-path, a short-cut to
* calling {@link Path#getLastCurve()} on {@link Item#getLastChild()}.
* calling {@link Path#lastCurve} on {@link Item#lastChild}.
*
* @bean
* @type Curve
@ -244,7 +244,7 @@ var CompoundPath = PathItem.extend(/** @lends CompoundPath# */{
/**
* The area that the compound-path's geometry is covering, calculated by
* getting th e{@link Path#getArea()} of each sub-path and it adding up.
* getting the {@link Path#area} of each sub-path and it adding up.
* Note that self-intersecting paths and sub-paths of different orientation
* can result in areas that cancel each other out.
*
@ -261,7 +261,7 @@ var CompoundPath = PathItem.extend(/** @lends CompoundPath# */{
/**
* The total length of all sub-paths in this compound-path, calculated by
* getting the {@link Path#getLength()} of each sub-path and it adding up.
* getting the {@link Path#length} of each sub-path and it adding up.
*
* @bean
* @type Number

View file

@ -632,10 +632,9 @@ statics: /** @lends Curve */{
* Splits the specified curve values into curves that are monotone in the
* specified coordinate direction.
*
* @param {Number[]} v the curve values, as returned by
* {@link Curve#getValues()}
* @param {Number} [dir=0] the direction in which the curves should be
* monotone, `0`: monotone in x-direction, `1`: monotone in y-direction
* @param {Number[]} v the curve values, as returned by {@link Curve#values}
* @param {Boolean} [dir=false] the direction in which the curves should be
* monotone, `false`: in x-direction, `true`: in y-direction
* @return {Number[][]} an array of curve value arrays of the resulting
* monotone curve. If the original curve was already monotone, an array
* only containing its values are returned.
@ -1716,17 +1715,18 @@ new function() { // Scope for methods that require private functions
},
new function() { // Scope for bezier intersection using fat-line clipping
function addLocation(locations, include, c1, t1, p1, c2, t2, p2, overlap) {
function addLocation(locations, include, c1, t1, c2, t2, overlap) {
// Determine if locations at the beginning / end of the curves should be
// excluded, in case the two curves are neighbors, but do not exclude
// connecting points between two curves if they were part of overlap
// checks, as they could be self-overlapping.
// NOTE: We don't pass p1 and p2, because v1 and v2 may be transformed
// by their path.matrix, while c1 and c2 are untransformed. Passing null
// for point in CurveLocation() will do the right thing.
var excludeStart = !overlap && c1.getPrevious() === c2,
excludeEnd = !overlap && c1 !== c2 && c1.getNext() === c2,
tMin = /*#=*/Numerical.CURVETIME_EPSILON,
tMax = 1 - tMin;
if (t1 == null)
t1 = c1.getTimeOf(p1);
// Check t1 and t2 against correct bounds, based on excludeStart/End:
// - excludeStart means the start of c1 connects to the end of c2
// - excludeEnd means the end of c1 connects to the start of c2
@ -1736,14 +1736,10 @@ new function() { // Scope for bezier intersection using fat-line clipping
// the beginning, and would be added twice otherwise.
if (t1 !== null && t1 >= (excludeStart ? tMin : 0) &&
t1 <= (excludeEnd ? tMax : 1)) {
if (t2 == null)
t2 = c2.getTimeOf(p2);
if (t2 !== null && t2 >= (excludeEnd ? tMin : 0) &&
t2 <= (excludeStart ? tMax : 1)) {
var loc1 = new CurveLocation(c1, t1,
p1 || c1.getPointAtTime(t1), overlap),
loc2 = new CurveLocation(c2, t2,
p2 || c2.getPointAtTime(t2), overlap);
var loc1 = new CurveLocation(c1, t1, null, overlap),
loc2 = new CurveLocation(c2, t2, null, overlap);
// Link the two locations to each other.
loc1._intersection = loc2;
loc2._intersection = loc1;
@ -1806,8 +1802,8 @@ new function() { // Scope for bezier intersection using fat-line clipping
var t = (tMinNew + tMaxNew) / 2,
u = (uMin + uMax) / 2;
addLocation(locations, include,
flip ? c2 : c1, flip ? u : t, null,
flip ? c1 : c2, flip ? t : u, null);
flip ? c2 : c1, flip ? u : t,
flip ? c1 : c2, flip ? t : u);
} else {
// Apply the result of the clipping to curve 1:
v1 = Curve.getPart(v1, tMinClip, tMaxClip);
@ -1989,12 +1985,11 @@ new function() { // Scope for bezier intersection using fat-line clipping
p1 = Curve.getPoint(v1, t1),
t2 = Curve.getTimeOf(v2, p1);
if (t2 !== null) {
var p2 = Curve.getPoint(v2, t2);
// Only use the time values if there was no recursion, and let
// addLocation() figure out the actual time values otherwise.
addLocation(locations, include,
flip ? c2 : c1, flip ? t2 : t1, flip ? p2 : p1,
flip ? c1 : c2, flip ? t1 : t2, flip ? p1 : p2);
flip ? c2 : c1, flip ? t2 : t1,
flip ? c1 : c2, flip ? t1 : t2);
}
}
}
@ -2004,7 +1999,9 @@ new function() { // Scope for bezier intersection using fat-line clipping
v1[0], v1[1], v1[6], v1[7],
v2[0], v2[1], v2[6], v2[7]);
if (pt) {
addLocation(locations, include, c1, null, pt, c2, null, pt);
addLocation(locations, include,
c1, Curve.getTimeOf(v1, pt),
c2, Curve.getTimeOf(v2, pt));
}
}
@ -2028,14 +2025,15 @@ new function() { // Scope for bezier intersection using fat-line clipping
for (var i = 0; i < 2; i++) {
var overlap = overlaps[i];
addLocation(locations, include,
c1, overlap[0], null,
c2, overlap[1], null, true);
c1, overlap[0],
c2, overlap[1], true);
}
} else {
var straight1 = Curve.isStraight(v1),
straight2 = Curve.isStraight(v2),
straight = straight1 && straight2,
flip = straight1 && !straight2;
flip = straight1 && !straight2,
before = locations.length;
// Determine the correct intersection method based on whether
// one or curves are straight lines:
(straight
@ -2050,6 +2048,25 @@ new function() { // Scope for bezier intersection using fat-line clipping
// addCurveIntersections():
// recursion, calls, tMin, tMax, uMin, uMax
0, 0, 0, 1, 0, 1);
// Handle the special case where the first curve's start- / end-
// point overlaps with the second curve's start- / end-point,
// but only if haven't found a line-line intersection already:
// #805#issuecomment-148503018
if (!straight || locations.length === before) {
for (var i = 0; i < 4; i++) {
var t1 = i >> 1, // 0, 0, 1, 1
t2 = i & 1, // 0, 1, 0, 1
i1 = t1 * 6,
i2 = t2 * 6,
p1 = new Point(v1[i1], v1[i1 + 1]),
p2 = new Point(v2[i2], v2[i2 + 1]);
if (p1.isClose(p2, epsilon)) {
addLocation(locations, include,
c1, t1,
c2, t2);
}
}
}
}
}
return locations;
@ -2060,8 +2077,8 @@ new function() { // Scope for bezier intersection using fat-line clipping
if (info.type === 'loop') {
var roots = info.roots;
addLocation(locations, include,
c1, roots[0], null,
c1, roots[1], null);
c1, roots[0],
c1, roots[1]);
}
return locations;
}

View file

@ -185,7 +185,7 @@ var CurveLocation = Base.extend(/** @lends CurveLocation# */{
/**
* @private
* @bean
* @deprecated use {@link #getTime()} instead.
* @deprecated use {@link #time} instead.
*/
getParameter: '#getTime',

View file

@ -22,13 +22,13 @@ Path.inject({ statics: new function() {
function createPath(segments, closed, args) {
var props = Base.getNamed(args),
path = new Path(props && props.insert === false && Item.NO_INSERT);
path = new Path(props && props.insert == false && Item.NO_INSERT);
path._add(segments);
// No need to use setter for _closed since _add() called _changed().
path._closed = closed;
// Set named arguments at the end, since some depend on geometry to be
// defined (e.g. #clockwise)
return path.set(props);
return path.set(props, { insert: true });
}
function createEllipse(center, radius, args) {
@ -335,7 +335,7 @@ Path.inject({ statics: new function() {
to = Point.readNamed(arguments, 'to'),
props = Base.getNamed(arguments),
// See createPath() for an explanation of the following sequence
path = new Path(props && props.insert === false
path = new Path(props && props.insert == false
&& Item.NO_INSERT);
path.moveTo(from);
path.arcTo(through, to);

View file

@ -1721,7 +1721,7 @@ var Path = PathItem.extend(/** @lends Path# */{
}
function checkSegmentStroke(segment) {
// Handle joins / caps that are not round specificelly, by
// Handle joins / caps that are not round specifically, by
// hit-testing their polygon areas.
var isJoin = closed || segment._index > 0
&& segment._index < numSegments - 1;
@ -2523,7 +2523,7 @@ new function() { // PostScript-style drawing commands
extent += extent < 0 ? 360 : -360;
}
}
var epsilon = /*#=*/Numerical.EPSILON,
var epsilon = /*#=*/Numerical.GEOMETRIC_EPSILON,
ext = abs(extent),
// Calculate the amount of segments required to approximate over
// `extend` degrees (extend / 90), but prevent ceil() from
@ -2834,19 +2834,20 @@ statics: {
// Style#strokeScaling.
var point = segment._point.transform(matrix),
loc = segment.getLocation(),
normal = loc.getNormal().multiply(radius).transform(strokeMatrix);
// Checking loc.getTime() for 0 is to see whether this is the first
// or the last segment of the open path, in order to determine in
// which direction to flip the normal.
normal = loc.getNormal()
.multiply(loc.getTime() === 0 ? radius : -radius)
.transform(strokeMatrix);
// For square caps, we need to step away from point in the direction of
// the tangent, which is the rotated normal.
// Checking loc.getTime() for 0 is to see whether this is the first
// or the last segment of the open path, in order to determine in which
// direction to move the point.
if (cap === 'square') {
if (isArea) {
addPoint(point.subtract(normal));
addPoint(point.add(normal));
}
point = point.add(normal.rotate(
loc.getTime() === 0 ? -90 : 90));
point = point.add(normal.rotate(-90));
}
addPoint(point.add(normal));
addPoint(point.subtract(normal));

View file

@ -61,13 +61,12 @@ PathItem.inject(new function() {
: res;
}
function createResult(ctor, paths, reduce, path1, path2, options) {
var result = new ctor(Item.NO_INSERT);
function createResult(paths, simplify, path1, path2, options) {
var result = new CompoundPath(Item.NO_INSERT);
result.addChildren(paths, true);
// See if the item can be reduced to just a simple Path.
if (reduce)
result = result.reduce({ simplify: true });
if (!(options && options.insert === false)) {
result = result.reduce({ simplify: simplify });
if (!(options && options.insert == false)) {
// Insert the resulting path above whichever of the two paths appear
// further up in the stack.
result.insertAbove(path2 && path1.isSibling(path2)
@ -78,12 +77,12 @@ PathItem.inject(new function() {
return result;
}
function computeBoolean(path1, path2, operation, options) {
function traceBoolean(path1, path2, operation, options) {
// Only support subtract and intersect operations when computing stroke
// based boolean operations.
if (options && options.stroke &&
// based boolean operations (options.split = true).
if (options && (options.trace == false || options.stroke) &&
/^(subtract|intersect)$/.test(operation))
return computeStrokeBoolean(path1, path2, operation === 'subtract');
return splitBoolean(path1, path2, operation);
// We do not modify the operands themselves, but create copies instead,
// fas produced by the calls to preparePath().
// NOTE: The result paths might not belong to the same type i.e.
@ -157,22 +156,26 @@ PathItem.inject(new function() {
});
}
return createResult(CompoundPath, paths, true, path1, path2, options);
return createResult(paths, true, path1, path2, options);
}
function computeStrokeBoolean(path1, path2, subtract) {
function splitBoolean(path1, path2, operation) {
var _path1 = preparePath(path1),
_path2 = preparePath(path2),
crossings = _path1.getCrossings(_path2),
subtract = operation === 'subtract',
divide = operation === 'divide',
added = {},
paths = [];
function addPath(path) {
// Simple see if the point halfway across the open path is inside
// path2, and include / exclude the path based on the operator.
if (_path2.contains(path.getPointAt(path.getLength() / 2))
^ subtract) {
if (!added[path._id] && (divide ||
_path2.contains(path.getPointAt(path.getLength() / 2))
^ subtract)) {
paths.unshift(path);
return true;
return added[path._id] = true;
}
}
@ -190,9 +193,9 @@ PathItem.inject(new function() {
_path1.getLastSegment().setHandleOut(0, 0);
}
}
// At the end, check what's left from our path after all the splitting.
// At the end, add what's left from our path after all the splitting.
addPath(_path1);
return createResult(Group, paths, false, path1, path2);
return createResult(paths, false, path1, path2);
}
/*
@ -450,10 +453,10 @@ PathItem.inject(new function() {
* @param {Point} point the location for which to determine the winding
* contribution
* @param {Curve[]} curves the curves that describe the shape against which
* to check, as returned by {@link Path#getCurves()} or
* {@link CompoundPath#getCurves()}
* @param {Number} [dir=0] the direction in which to determine the
* winding contribution, `0`: in x-direction, `1`: in y-direction
* to check, as returned by {@link Path#curves} or
* {@link CompoundPath#curves}
* @param {Boolean} [dir=false] the direction in which to determine the
* winding contribution, `false`: in x-direction, `true`: in y-direction
* @param {Boolean} [closed=false] determines how areas should be closed
* when a curve is part of an open path, `false`: area is closed with a
* straight line, `true`: area is closed taking the handles of the first
@ -479,7 +482,10 @@ PathItem.inject(new function() {
paR = pa + windingEpsilon,
windingL = 0,
windingR = 0,
pathWindingL = 0,
pathWindingR = 0,
onPath = false,
onAnyPath = false,
quality = 1,
roots = [],
vPrev,
@ -512,13 +518,17 @@ PathItem.inject(new function() {
// Bail out without updating vPrev at the end of the call.
return;
}
// Determine the curve-time value corresponding to the point.
var t = po === o0 ? 0
: po === o3 ? 1
// If the abscissa is outside the curve, we can use any
// value except 0 (requires special handling). Use 1, as it
// does not require additional calculations for the point.
: paL > max(a0, a1, a2, a3) || paR < min(a0, a1, a2, a3)
? 1
: Curve.solveCubic(v, io, po, roots, 0, 1) === 1
: Curve.solveCubic(v, io, po, roots, 0, 1) > 0
? roots[0]
: 0.5,
: 1,
a = t === 0 ? a0
: t === 1 ? a3
: Curve.getPoint(v, t)[dir ? 'y' : 'x'],
@ -528,9 +538,9 @@ PathItem.inject(new function() {
if (po !== o0) {
// Standard case, curve is not crossed at its starting point.
if (a < paL) {
windingL += winding;
pathWindingL += winding;
} else if (a > paR) {
windingR += winding;
pathWindingR += winding;
} else {
onPath = true;
}
@ -541,25 +551,25 @@ PathItem.inject(new function() {
if (a > pa - qualityEpsilon && a < pa + qualityEpsilon)
quality /= 2;
} else {
// Curve is crossed at starting point.
if (winding !== windingPrev) {
// Curve is crossed at starting point and winding changes
// from previous curve. Cancel winding from previous curve.
// Winding changes from previous curve, cancel its winding.
if (a0 < paL) {
windingL += winding;
pathWindingL += winding;
} else if (a0 > paR) {
windingR += winding;
pathWindingR += winding;
}
} else if (a0 != a3Prev) {
// Handle a horizontal curve between the current and
// Handle a horizontal curve between the current and
// previous non-horizontal curve. See
// #1261#issuecomment-282726147 for a detailed explanation:
if (a3Prev < paR && a > paR) {
// Right winding was not added before, so add it now.
windingR += winding;
pathWindingR += winding;
onPath = true;
} else if (a3Prev > paL && a < paL) {
// Left winding was not added before, so add it now.
windingL += winding;
pathWindingL += winding;
onPath = true;
}
}
@ -574,7 +584,7 @@ PathItem.inject(new function() {
// again with flipped direction and return that result instead.
return !dontFlip && a > paL && a < paR
&& Curve.getTangent(v, t)[dir ? 'x' : 'y'] === 0
&& getWinding(point, curves, dir ? 0 : 1, closed, true);
&& getWinding(point, curves, !dir, closed, true);
}
function handleCurve(v) {
@ -660,6 +670,23 @@ PathItem.inject(new function() {
// it now to treat the path as closed:
if (vClose && (res = handleCurve(vClose)))
return res;
if (onPath && !pathWindingL && !pathWindingR) {
// If the point is on the path and the windings canceled
// each other, we treat the point as if it was inside the
// path. A point inside a path has a winding of [+1,-1]
// for clockwise and [-1,+1] for counter-clockwise paths.
// If the ray is cast in y direction (dir == true), the
// windings always have opposite sign.
pathWindingL = pathWindingR = path.isClockwise(closed) ^ dir
? 1 : -1;
}
windingL += pathWindingL;
windingR += pathWindingR;
pathWindingL = pathWindingR = 0;
if (onPath) {
onAnyPath = true;
onPath = false;
}
vClose = null;
}
}
@ -674,7 +701,7 @@ PathItem.inject(new function() {
windingL: windingL,
windingR: windingR,
quality: quality,
onPath: onPath
onPath: onAnyPath
};
}
@ -698,8 +725,7 @@ PathItem.inject(new function() {
// sufficient quality is found, use it. Otherwise use the winding with
// the best quality.
var offsets = [0.5, 0.25, 0.75],
windingZero = { winding: 0, quality: 0 },
winding = windingZero,
winding = { winding: 0, quality: -1 },
tMin = /*#=*/Numerical.CURVETIME_EPSILON,
tMax = 1 - tMin;
for (var i = 0; i < offsets.length && winding.quality < 0.5; i++) {
@ -711,26 +737,24 @@ PathItem.inject(new function() {
var curve = entry.curve,
path = curve._path,
parent = path._parent,
operand = parent instanceof CompoundPath ? parent : path,
t = Numerical.clamp(curve.getTimeAt(length), tMin, tMax),
pt = curve.getPointAtTime(t),
// Determine the direction in which to check the winding
// from the point (horizontal or vertical), based on the
// curve's direction at that point. If tangent is less
// than 45°, cast the ray vertically, else horizontally.
dir = abs(curve.getTangentAtTime(t).normalize().y)
< Math.SQRT1_2 ? 1 : 0;
if (parent instanceof CompoundPath)
path = parent;
dir = abs(curve.getTangentAtTime(t).y) < Math.SQRT1_2;
// While subtracting, we need to omit this curve if it is
// contributing to the second operand and is outside the
// first operand.
var wind = !(operator.subtract && path2 && (
path === path1 &&
operand === path1 &&
path2._getWinding(pt, dir, true).winding ||
path === path2 &&
operand === path2 &&
!path1._getWinding(pt, dir, true).winding))
? getWinding(pt, curves, dir, true)
: windingZero;
: { winding: 0, quality: 1 };
if (wind.quality > winding.quality)
winding = wind;
break;
@ -1019,7 +1043,7 @@ PathItem.inject(new function() {
* @return {PathItem} the resulting path item
*/
unite: function(path, options) {
return computeBoolean(this, path, 'unite', options);
return traceBoolean(this, path, 'unite', options);
},
/**
@ -1029,15 +1053,18 @@ PathItem.inject(new function() {
* @option [options.insert=true] {Boolean} whether the resulting item
* should be inserted back into the scene graph, above both paths
* involved in the operation
* @option [options.stroke=false] {Boolean} whether the operation should
* be performed on the stroke or on the fill of the first path
* @option [options.trace=true] {Boolean} whether the tracing method is
* used, treating both paths as areas when determining which parts
* of the paths are to be kept in the result, or whether the first
* path is only to be split at intersections, keeping the parts of
* the curves that intersect with the area of the second path.
*
* @param {PathItem} path the path to intersect with
* @param {Object} [options] the boolean operation options
* @return {PathItem} the resulting path item
*/
intersect: function(path, options) {
return computeBoolean(this, path, 'intersect', options);
return traceBoolean(this, path, 'intersect', options);
},
/**
@ -1047,15 +1074,18 @@ PathItem.inject(new function() {
* @option [options.insert=true] {Boolean} whether the resulting item
* should be inserted back into the scene graph, above both paths
* involved in the operation
* @option [options.stroke=false] {Boolean} whether the operation should
* be performed on the stroke or on the fill of the first path
* @option [options.trace=true] {Boolean} whether the tracing method is
* used, treating both paths as areas when determining which parts
* of the paths are to be kept in the result, or whether the first
* path is only to be split at intersections, removing the parts of
* the curves that intersect with the area of the second path.
*
* @param {PathItem} path the path to subtract
* @param {Object} [options] the boolean operation options
* @return {PathItem} the resulting path item
*/
subtract: function(path) {
return computeBoolean(this, path, 'subtract');
subtract: function(path, options) {
return traceBoolean(this, path, 'subtract', options);
},
/**
@ -1068,10 +1098,10 @@ PathItem.inject(new function() {
*
* @param {PathItem} path the path to exclude the intersection of
* @param {Object} [options] the boolean operation options
* @return {PathItem} the resulting group item
* @return {PathItem} the resulting path item
*/
exclude: function(path, options) {
return computeBoolean(this, path, 'exclude', options);
return traceBoolean(this, path, 'exclude', options);
},
/**
@ -1083,18 +1113,22 @@ PathItem.inject(new function() {
* @option [options.insert=true] {Boolean} whether the resulting item
* should be inserted back into the scene graph, above both paths
* involved in the operation
* @option [options.stroke=false] {Boolean} whether the operation should
* be performed on the stroke or on the fill of the first path
* @option [options.trace=true] {Boolean} whether the tracing method is
* used, treating both paths as areas when determining which parts
* of the paths are to be kept in the result, or whether the first
* path is only to be split at intersections.
*
* @param {PathItem} path the path to divide by
* @param {Object} [options] the boolean operation options
* @return {Group} the resulting group item
* @return {PathItem} the resulting path item
*/
divide: function(path, options) {
return createResult(Group, [
this.subtract(path, options),
this.intersect(path, options)
], true, this, path, options);
return options && (options.trace == false || options.stroke)
? splitBoolean(this, path, 'divide')
: createResult([
this.subtract(path, options),
this.intersect(path, options)
], true, this, path, options);
},
/*

View file

@ -330,7 +330,8 @@ var PathItem = Item.extend(/** @lends PathItem# */{
: (_matrix || path._matrix)._orNullIfIdentity();
// First check the bounds of the two paths. If they don't intersect,
// we don't need to iterate through their curves.
return self || this.getBounds(matrix1).touches(path.getBounds(matrix2))
return self || this.getBounds(matrix1).intersects(
path.getBounds(matrix2), /*#=*/Numerical.EPSILON)
? Curve.getIntersections(
this.getCurves(), !self && path.getCurves(), include,
matrix1, matrix2, _returnFirst)

View file

@ -834,7 +834,7 @@ var Color = Base.extend(new function() {
+ components.join(',') + ')';
},
toCanvasStyle: function(ctx) {
toCanvasStyle: function(ctx, matrix) {
if (this._canvasStyle)
return this._canvasStyle;
// Normal colors are simply represented by their CSS string.
@ -846,10 +846,20 @@ var Color = Base.extend(new function() {
stops = gradient._stops,
origin = components[1],
destination = components[2],
highlight = components[3],
inverse = matrix && matrix.inverted(),
canvasGradient;
// If the item's content is transformed by a matrix, we need to
// inverse transform the gradient points, as they are defined in
// item's parent coordinate system.
if (inverse) {
origin = inverse._transformPoint(origin);
destination = inverse._transformPoint(destination);
if (highlight)
highlight = inverse._transformPoint(highlight);
}
if (gradient._radial) {
var radius = destination.getDistance(origin),
highlight = components[3];
var radius = destination.getDistance(origin);
if (highlight) {
var vector = highlight.subtract(origin);
if (vector.getLength() > radius)

View file

@ -69,9 +69,9 @@ var GradientStop = Base.extend(/** @lends GradientStop# */{
* Called by various setters whenever a value changes
*/
_changed: function() {
// Loop through the gradients that use this stop and notify them about
// the change, so they can notify their gradient colors, which in turn
// will notify the items they are used in:
// Notify the graident that uses this stop about the change, so it can
// notify its gradient colors, which in turn will notify the items they
// are used in:
if (this._owner)
this._owner._changed(/*#=*/Change.STYLE);
},
@ -127,7 +127,7 @@ var GradientStop = Base.extend(/** @lends GradientStop# */{
/**
* @private
* @bean
* @deprecated use {@link #getOffset()} instead.
* @deprecated use {@link #offset} instead.
*/
getRampPoint: '#getOffset',
setRampPoint: '#setOffset',

View file

@ -264,8 +264,7 @@ var Style = Base.extend(new function() {
return fields;
}, /** @lends Style# */{
set: function(style) {
this._values = {}; // Reset already set styles.
// If the passed style object is also a Style, clone its clonable
// If the passed style object is also a Style, clone its cloneable
// fields rather than simply copying them.
var isStyle = style instanceof Style,
// Use the other stlyle's _values object for iteration
@ -355,7 +354,7 @@ var Style = Base.extend(new function() {
/**
* @bean
* @private
* @deprecated use {@link #getFontFamily()} instead.
* @deprecated use {@link #fontFamily} instead.
*/
getFont: '#getFontFamily',
setFont: '#setFontFamily',

View file

@ -95,7 +95,7 @@ new function() {
attrs.y -= size.height / 2;
attrs.width = size.width;
attrs.height = size.height;
attrs.href = options.embedImages === false && image && image.src
attrs.href = options.embedImages == false && image && image.src
|| item.toDataURL();
return SvgElement.create('image', attrs, formatter);
}

View file

@ -389,13 +389,6 @@ new function() {
.translate(bounds.getPoint())
.scale(bounds.getSize()));
}
if (item instanceof Shape) {
// When applying gradient colors to shapes, we need
// to offset the shape's initial position to get the
// same results as SVG.
color.transform(new Matrix().translate(
item.getPosition(true).negate()));
}
}
}
}
@ -528,23 +521,25 @@ new function() {
* @param {Item} item the item to apply the style and attributes to
*/
function applyAttributes(item, node, isRoot) {
// SVG attributes can be set both as styles and direct node attributes,
// so we need to handle both.
var parent = node.parentNode,
styles = {
node: DomElement.getStyles(node) || {},
// Do not check for inheritance if this is root, since we want
// the default SVG settings to stick. Also detect defs parents,
// of which children need to explicitly inherit their styles.
parent: !isRoot && !/^defs$/i.test(parent.tagName)
&& DomElement.getStyles(parent) || {}
};
Base.each(attributes, function(apply, name) {
var value = getAttribute(node, name, styles);
// 'clip-path' attribute returns a new item, support it here:
item = value !== undefined && apply(item, value, name, node, styles)
|| item;
});
if (node.style) {
// SVG attributes can be set both as styles and direct node
// attributes, so we need to handle both.
var parent = node.parentNode,
styles = {
node: DomElement.getStyles(node) || {},
// Do not check for inheritance if this is root, to make the
// default SVG settings stick. Also detect defs parents, of
// which children need to explicitly inherit their styles.
parent: !isRoot && !/^defs$/i.test(parent.tagName)
&& DomElement.getStyles(parent) || {}
};
Base.each(attributes, function(apply, name) {
var value = getAttribute(node, name, styles);
// 'clip-path' attribute returns a new item, support it here:
item = value !== undefined
&& apply(item, value, name, node, styles) || item;
});
}
return item;
}
@ -578,9 +573,10 @@ new function() {
parent,
next;
if (isRoot && isElement) {
// Set rootSize root element size, fall-back to view size.
rootSize = getSize(node, null, null, true)
|| paper.getView().getSize();
// Set rootSize to view size, as getSize() may refer to it (#1242).
rootSize = paper.getView().getSize();
// Now set rootSize to the root element size, and fall-back to view.
rootSize = getSize(node, null, null, true) || rootSize;
// We need to move the SVG node to the current document, so default
// styles are correctly inherited! For this we create and insert a
// temporary SVG container which is removed again at the end. This

View file

@ -158,7 +158,7 @@ var TextItem = Item.extend(/** @lends TextItem# */{
/**
* @bean
* @private
* @deprecated use {@link #getStyle()} instead.
* @deprecated use {@link #style} instead.
*/
getCharacterStyle: '#getStyle',
setCharacterStyle: '#setStyle',
@ -166,7 +166,7 @@ var TextItem = Item.extend(/** @lends TextItem# */{
/**
* @bean
* @private
* @deprecated use {@link #getStyle()} instead.
* @deprecated use {@link #style} instead.
*/
getParagraphStyle: '#getStyle',
setParagraphStyle: '#setStyle'

View file

@ -608,7 +608,7 @@ var View = Base.extend(Emitter, /** @lends View# */{
* @name View#rotate
* @function
* @param {Number} angle the rotation angle
* @param {Point} [center={@link View#getCenter()}]
* @param {Point} [center={@link View#center}]
* @see Matrix#rotate(angle[, center])
*/
@ -619,7 +619,7 @@ var View = Base.extend(Emitter, /** @lends View# */{
* @name View#scale
* @function
* @param {Number} scale the scale factor
* @param {Point} [center={@link View#getCenter()}]
* @param {Point} [center={@link View#center}]
*/
/**
* Scales the view by the given values from its center point, or optionally
@ -629,7 +629,7 @@ var View = Base.extend(Emitter, /** @lends View# */{
* @function
* @param {Number} hor the horizontal scale factor
* @param {Number} ver the vertical scale factor
* @param {Point} [center={@link View#getCenter()}]
* @param {Point} [center={@link View#center}]
*/
/**
@ -639,7 +639,7 @@ var View = Base.extend(Emitter, /** @lends View# */{
* @name View#shear
* @function
* @param {Point} shear the horziontal and vertical shear factors as a point
* @param {Point} [center={@link View#getCenter()}]
* @param {Point} [center={@link View#center}]
* @see Matrix#shear(shear[, center])
*/
/**
@ -650,7 +650,7 @@ var View = Base.extend(Emitter, /** @lends View# */{
* @function
* @param {Number} hor the horizontal shear factor
* @param {Number} ver the vertical shear factor
* @param {Point} [center={@link View#getCenter()}]
* @param {Point} [center={@link View#center}]
* @see Matrix#shear(hor, ver[, center])
*/
@ -661,7 +661,7 @@ var View = Base.extend(Emitter, /** @lends View# */{
* @name View#skew
* @function
* @param {Point} skew the horziontal and vertical skew angles in degrees
* @param {Point} [center={@link View#getCenter()}]
* @param {Point} [center={@link View#center}]
* @see Matrix#shear(skew[, center])
*/
/**
@ -672,7 +672,7 @@ var View = Base.extend(Emitter, /** @lends View# */{
* @function
* @param {Number} hor the horizontal skew angle in degrees
* @param {Number} ver the vertical sskew angle in degrees
* @param {Point} [center={@link View#getCenter()}]
* @param {Point} [center={@link View#center}]
* @see Matrix#shear(hor, ver[, center])
*/
@ -1399,8 +1399,15 @@ new function() { // Injection scope for event handling on the browser
&& (Date.now() - clickTime < 300);
downItem = clickItem = hitItem;
// Only start dragging if the mousedown event has not
// prevented the default.
dragItem = !prevented && hitItem;
// prevented the default, and if the hitItem or any of its
// parents actually respond to mousedrag events.
if (!prevented && hitItem) {
var item = hitItem;
while (item && !item.responds('mousedrag'))
item = item._parent;
if (item)
dragItem = hitItem;
}
downPoint = point;
} else if (mouse.up) {
// Emulate click / doubleclick, but only on the hit-item,

View file

@ -144,9 +144,15 @@ var comparePixels = function(actual, expected, message, options) {
function rasterize(item, group, resolution) {
var raster = null;
if (group) {
var parent = item.parent,
index = item.index;
group.addChild(item);
raster = group.rasterize(resolution, false);
item.remove();
if (parent) {
parent.insertChild(index, item);
} else {
item.remove();
}
}
return raster;
}
@ -469,16 +475,34 @@ var compareBoolean = function(actual, expected, message, options) {
message = getFunctionMessage(actual);
actual = actual();
}
var style = {
strokeColor: 'black',
fillColor: expected &&
(expected.closed || expected.children && 'yellow') || null
};
if (actual)
var parent,
index,
style = {
strokeColor: 'black',
fillColor: expected && (expected.closed
|| expected.firstChild && expected.firstChild.closed && 'yellow')
|| null
};
if (actual) {
parent = actual.parent;
index = actual.index;
// Remove it from parent already now, in case we're comparing children
// of compound-paths, so we can apply styling to them.
if (parent && parent instanceof CompoundPath) {
actual.remove();
} else {
parent = null;
}
actual.style = style;
if (expected)
}
if (expected) {
expected.style = style;
}
equals(actual, expected, message, Base.set({ rasterize: true }, options));
if (parent) {
// Insert it back.
parent.insertChild(index, actual);
}
};
var createSVG = function(str, attrs) {

View file

@ -248,3 +248,38 @@ test('Gradient', function() {
equals(function() { return stop3.offset; }, 1);
equals(function() { return stop4.offset; }, 0.5);
});
test('Gradients with applyMatrix', function() {
var topLeft = [100, 100];
var bottomRight = [400, 400];
var gradientColor = {
gradient: {
stops: ['yellow', 'red', 'blue']
},
origin: topLeft,
destination: bottomRight
}
var path = new Path.Rectangle({
topLeft: topLeft,
bottomRight: bottomRight,
fillColor: gradientColor,
applyMatrix: true
});
var shape = new Shape.Rectangle({
topLeft: topLeft,
bottomRight: bottomRight,
fillColor: gradientColor,
applyMatrix: false
});
comparePixels(path, shape);
path.scale(2);
path.rotate(45);
shape.scale(2);
shape.rotate(45);
comparePixels(path, shape);
});

View file

@ -114,7 +114,9 @@ test('group.insertChildren(0, otherGroup.children)', function() {
test('group.addChildren()', function() {
var group = new Group();
var children = [new Path(), new Path()];
var path1 = new Path();
var path2 = new Path();
var children = [path1, path2];
group.addChildren(children);
equals(group.children.length, 2,
'group.children.length after adding 2 children');
@ -128,4 +130,8 @@ test('group.addChildren()', function() {
equals(group.children.length, 2,
'calling group.addChildren() with an array with 3 entries, ' +
'of which 2 are valid, group.children.length should be 2');
});
children = [path1, path1, path2];
group.addChildren(children);
equals(group.children.length, 2,
'adding the same item twice should only add it once.');
})

View file

@ -21,6 +21,7 @@ test('Hit-testing options', function() {
segments: true,
handles: false,
ends: false,
position: false,
center: false,
bounds: false,
guides: false,
@ -128,31 +129,46 @@ test('hitting path segments', function() {
});
});
test('hitting the center of a path', function() {
test('hitting the center and position of a path', function() {
var path = new Path([0, 0], [100, 100], [200, 0]);
path.closed = true;
var center = path.bounds.center,
position = path.position,
positionResult = {
type: 'position', item: path, point: position
},
centerResult = {
type: 'center', item: path, point: center
};
testHitResult(paper.project.hitTest(path.position, {
testHitResult(paper.project.hitTest(position, {
center: true
}), {
type: 'center',
item: path,
point: path.position
});
});
}), centerResult);
test('hitting the center of a path with tolerance', function() {
var path = new Path([0, 0], [100, 100], [200, 0]);
path.closed = true;
var offset = new Point(1, 1);
testHitResult(paper.project.hitTest(path.position.add(offset), {
testHitResult(paper.project.hitTest(position.add(offset), {
tolerance: offset.length,
center: true
}), centerResult, 'position with tolerance');
testHitResult(paper.project.hitTest(position, {
position: true
}), positionResult);
testHitResult(paper.project.hitTest(center, {
position: true
}), positionResult);
path.pivot = [100, 100];
testHitResult(paper.project.hitTest(center, {
position: true
}), null, 'with pivot, the position should not be in the center');
testHitResult(paper.project.hitTest(path.position, {
position: true
}), {
type: 'center',
item: path,
point: path.position
type: 'position', item: path, point: path.position
});
});
@ -838,4 +854,18 @@ test('hit-testing scaled items with different settings of view.zoom and item.str
testItem(Path, 2, true);
});
test('hit-testing items scaled to 0', function() {
var item = new Shape.Rectangle({
point: [0, 0],
size: [100, 100],
fillColor: 'red',
selected: true
});
item.scale(0);
testHitResult(project.hitTest(item.position), null,
'should not throw an exception.');
});
// TODO: project.hitTest(point, {type: AnItemType});

View file

@ -207,7 +207,6 @@ test('path.bounds & path.strokeBounds with stroke styles', function() {
}
var path = makePath();
path.fullySelected = true;
path.strokeColor = 'black';
path.strokeCap = 'butt';
path.strokeJoin = 'round';
@ -730,8 +729,41 @@ test('symbolItem.bounds with strokeScaling disabled', function() {
var placed = symbol.place([100, 100]);
equals(placed.bounds, new Rectangle(85, 85, 30, 30), 'placed.bounds');
placed.scale(4, 2);
equals(placed.bounds, new Rectangle(55, 75, 90, 50), 'placed.bounds after scaling');
equals(placed.bounds, new Rectangle(55, 75, 90, 50),
'placed.bounds after scaling');
path.strokeScaling = true;
equals(placed.bounds, new Rectangle(40, 70, 120, 60), 'placed.bounds after scaling, strokeScaling enabled');
equals(placed.bounds, new Rectangle(40, 70, 120, 60),
'placed.bounds after scaling, strokeScaling enabled');
});
test('item.visible and item.parents.bounds (#1248)', function() {
var item = new Path.Rectangle({
point: [0, 0],
size: [50, 100],
visible: false
});
equals(item.bounds, new Rectangle(0, 0, 50, 100), 'item.bounds');
equals(item.parent.bounds, new Rectangle(0, 0, 0, 0),
'item.parent.bounds with item.visible = false');
item.visible = true;
equals(item.parent.bounds, item.bounds,
'item.parent.bounds with item.visible = true');
});
test('group.internalBounds with child and child.applyMatrix = false (#1250)', function() {
var item1 = Shape.Rectangle({
point: [100, 100],
size: [200, 200]
});
var item2 = new Path.Rectangle({
point: [0, 0],
size: [100, 100]
});
var group = new Group([item1, item2]);
equals(item1.bounds, new Rectangle(100, 100, 200, 200), 'item.bounds');
equals(group.internalBounds, new Rectangle(0, 0, 300, 300),
'group.internalBounds before scaling item1');
item1.scale(0.5);
equals(group.internalBounds, new Rectangle(0, 0, 250, 250),
'group.internalBounds after scaling item1');
});

View file

@ -177,7 +177,7 @@ test('#719', function() {
compareBoolean(result, expected);
});
test('#757 (path1.intersect(pat2, { stroke: true }))', function() {
test('#757 (path1.intersect(pat2, { trace: false }))', function() {
var rect = new Path.Rectangle({
from: [100, 250],
to: [350, 350]
@ -194,7 +194,7 @@ test('#757 (path1.intersect(pat2, { stroke: true }))', function() {
]
});
var res = line.intersect(rect, { stroke: true });
var res = line.intersect(rect, { trace: false });
var children = res.removeChildren();
var first = children[0];
@ -864,7 +864,62 @@ test('#1123', function() {
'M100,200v-100h100v100zM180,180v-60h-60v60z');
});
test('#1239', function() {
test('#1221', function() {
var rect1 = new Path.Rectangle({
point: [100, 100],
size: [200, 200]
});
var circle = new Path.Circle({
center: [100, 100],
radius: 100
});
compareBoolean(function() { return rect1.subtract(circle, { trace: false }); },
'M200,100h100v200h-200v-100');
compareBoolean(function() { return rect1.subtract(circle, { trace: true }); },
'M100,300v-100c55.22847,0 100,-44.77153 100,-100h100v200z');
var blob = PathItem.create("M534,273C171.7,111,60.5,117.1,30,158c-40.5,54.3,31.5,210.2,111,222c60.8,9,88-71.9,159-66c81.6,6.8,99.6,118.3,179,128c33.8,4.1,83.1-9.7,150-90")
var rect2 = new Path.Rectangle({
point: [150, 100],
size: [300, 300]
});
compareBoolean(function() { return blob.subtract(rect2, { trace: false }); },
'M534,273c-29.65069,-13.2581 -57.61955,-25.39031 -84,-36.46967M150,138.13156c-71.67127,-11.53613 -105.25987,0.10217 -120,19.86844c-40.5,54.3 31.5,210.2 111,222c3.08303,0.45637 6.07967,0.68158 9,0.69867M409.85616,400c18.87105,20.95032 39.82014,38.41763 69.14384,42c33.8,4.1 83.1,-9.7 150,-90');
compareBoolean(function() { return blob.subtract(rect2, { trace: true }); },
'M629,352c-66.9,80.3 -116.2,94.1 -150,90c-29.3237,-3.58237 -50.27279,-21.04968 -69.14384,-42h40.14384v-163.46967c26.38045,11.07937 54.34931,23.21157 84,36.46967M141,380c-79.5,-11.8 -151.5,-167.7 -111,-222c14.74013,-19.76627 48.32873,-31.40457 120,-19.86844v242.56712c-2.92033,-0.01709 -5.91697,-0.24231 -9,-0.69867z');
var rect3 = new Path.Rectangle({
point: [150, 100],
size: [300, 150]
});
compareBoolean(function() { return blob.subtract(rect3, { trace: false }); },
'M534,273c-29.65069,-13.2581 -57.61955,-25.39031 -84,-36.46967M150,138.13156c-71.67127,-11.53613 -105.25987,0.10217 -120,19.86844c-40.5,54.3 31.5,210.2 111,222c60.8,9 88,-71.9 159,-66c81.6,6.8 99.6,118.3 179,128c33.8,4.1 83.1,-9.7 150,-90');
compareBoolean(function() { return blob.subtract(rect3, { trace: true }); },
'M629,352c-66.9,80.3 -116.2,94.1 -150,90c-79.4,-9.7 -97.4,-121.2 -179,-128c-71,-5.9 -98.2,75 -159,66c-79.5,-11.8 -151.5,-167.7 -111,-222c14.74013,-19.76627 48.32873,-31.40457 120,-19.86844v111.86844h300v-13.46967c26.38045,11.07937 54.34931,23.21157 84,36.46967');
var rect4 = new Path.Rectangle({
point: [200, 200],
size: [400, 200]
});
var line = new Path.Line({
from: [400, 300],
to: [400, 600]
});
var division = line.divide(rect4, { trace: false });
equals(function() { return division.children.length; }, 2);
compareBoolean(function() { return division.children[0]; }, 'M400,300v100');
compareBoolean(function() { return division.children[1]; }, 'M400,400v200');
});
test('#1239 / #1073', function() {
var p1 = new Path([[890.91, 7.52, 0, 0, 85.40999999999997, 94.02], [1024, 351.78, 0, -127.03999999999996, 0, 127.14999999999998], [890.69, 696.28, 85.54999999999995, -94.03999999999996, 0, 0], [843.44, 653.28, 0, 0, 75.20000000000005, -82.66999999999996], [960, 351.78, 0, 111.75, 0, -111.63], [843.65, 50.51999999999998, 75.07000000000005, 82.63999999999999, 0, 0], true]);
var p2 = new Path([[960, 352, -0.05999999999994543, 111.67999999999995, 0, 0], [1024, 352, 0, 0, -0.05999999999994543, 127.07], [890.69, 696.28, 85.5, -93.98000000000002, 0, 0], [843.44, 653.28, 0, 0, 75.14999999999998, -82.61000000000001], true]);
project.activeLayer.scale(0.25);
@ -945,6 +1000,47 @@ test('Selected edge-cases from @hari\'s boolean-test suite', function() {
'M283.8,324.71094l0,41.45422c1.42504,1.15843 2.79504,2.33005 4.11,3.51485c7.88,7.1 13.66333,14.91 17.35,23.43c3.68667,8.52667 4.97333,17.91 3.86,28.15c-0.36003,3.31143 -0.98987,6.71838 -1.88951,10.22084c1.02,0.27014 2.0765,0.50986 3.16951,0.71916c6.26667,1.2 13.8,1.8 22.6,1.8l8.39102,0c0.08444,-2.79841 -0.02257,-5.56508 -0.32102,-8.3c-0.78746,-7.20773 -2.77831,-14.50893 -5.97256,-21.90361c-1.74009,-0.09727 -3.3059,-0.2294 -4.69744,-0.39639c-3.33333,-0.4 -5.93333,-1.26667 -7.8,-2.6c-1.86667,-1.33333 -3.13333,-3.2 -3.8,-5.6c-0.66667,-2.4 -1,-5.6 -1,-9.6l0,-43.223z');
});
test('Selected test-cases provided by @iconexperience', function() {
// Test all of test cases in one batch.
// Read more in #784
var paths = [[
[[276.60479999999995, 265.6776, 2.6464000000000283, 5.646400000000028, 1.2208000000000538, -0.8512000000000057], [279.9632, 262.924, -1.0127999999999702, 1.0160000000000196, 0.019200000000012096, 3.302400000000034], [294.4, 269.31440000000003, -7.240000000000009, 0, 7.240000000000009, 0], [308.8384, 262.924, -0.020800000000008367, 3.302400000000034, 1.0112000000000307, 1.0160000000000196], [312.1968, 265.6776, -1.2224000000000501, -0.8512000000000057, -2.6464000000000283, 5.646400000000028], [294.4, 273.3128, 5.590400000000045, 0, -5.590399999999988, 0], true],
[[294.4, 273.3128, 0, 0, -7.240000000000009, 0], [279.9808, 266.9064, 0.014400000000023283, 3.3023999999999774, 0, 0], [279.91999999999996, 253.31279999999998, 0, 0, 9.652800000000013, 0], [308.88, 253.31279999999998, -9.652800000000013, 0, 0, 0], [308.82079999999996, 266.9064, 0, 0, -0.014865740466348143, 3.068288832262283], [295.93610344888236, 273.2670371105283, 6.683812628997487, -0.3953268817066373, 5.682599074647953, -0.3268447810676207], [312.1968, 265.6776, -2.4122653499520084, 5.146166079897625, 8.40640000000002, 5.8559999999999945], [294.4, 307.1864, 16, 0.001599999999996271, 0, 0], true]
],
[
[[512, 563.0515712, 0, 0, 0, 0], [462.4499456000001, 563.0515712, 0, 0, -3.031499856000039, 0], [456.94438400000007, 557.549450576, 0, 3.031499855999982, 0, 0], true],
[[462.4499456000001, 563.0515712, -3.031499856000039, 0, -3.0303527841790014, 0], [456.94438506256495, 557.5494505759262, 0.0018713092763391614, 3.0299308178812225, 0,0], [567.0556160000001, 564], true]
],
[
[[506.49443840000004, 600, 0, 0, 0, 0], [567.0556160000001, 623.6127488, 0, 0, 0, 3.031499856000096], [561.5500544, 629.1148694240001, 3.031499855999982, 0, 3.031499855999982, 0], [567.0556160000001, 634.620431024, 0, -3.031499855999982, 0, 0], true],
[[561.5500544, 629.1148694240001, 3.031499855999982, 0, 3.031499855999982, 0], [567.0556160000001, 634.620431024, 0, -3.0280588800000032, 0, 0], [567.0556160000001, 656.6461184, 0, 0, 0, 3.0280588800000032], true]
],
[
[[400.81546878700505, 400.81546878700505, 79.97937495065997, -79.9793749506602, -79.97937495066014, 79.97937495065997], [111.18453121299513, 400.81546878700505, 79.97937495066012, 79.97937495065997, -79.97937495066009, -79.9793749506602], [111.18453121299507, 111.18453121299507, -79.97937495066003, 79.97937495066017, 79.97937495066009, -79.97937495066007], [400.815468787005, 111.18453121299503, -79.97937495066009, -79.97937495066003, 79.97937495066003, 79.97937495066007], true],
[[400.815468787005, 400.815468787005, 79.97937495065997, -79.97937495066014, -79.97937495066014, 79.97937495065992], [111.18453121299507, 400.815468787005, 79.97937495066014, 79.97937495065992, -79.97937495066003, -79.9793749506602], [111.18453121299507, 111.18453121299504, -79.97937495066003, 79.97937495066014, 79.97937495066009, -79.97937495066006], [400.81546878700493, 111.184531212995, -79.97937495066009, -79.97937495066002, 79.97937495066003, 79.97937495066004], true]
],
[
[[512, 96, -125.89361307431193, 0, 168, 0], [736, 337.78, 0, -120.88999999999999, 0, 21.79000000000002], [729.34, 402.7, 4.319999999999936, -21.340000000000032, -25.649999999999977, 126.62999999999994], [512, 640, 95.85000000000002, 0, 0, 0], [288.04, 332.0799999999999, 100.000575152844760396, -0.000053268689669039304, 0.41767160812742077, -28.392122573051836], [299.43514767699014, 248.78534185388617, -7.421136238632187, 26.615225151165646, 23.67264213867054, -84.8997620019185], true],
[[512, 96, -125.89322163652065, 0, 168, 0], [736, 337.78, 0, -120.88999999999999, 0, 151.11], [512, 640, 112, 0, -112, 0], [288, 337.78, 0, 151.11, 0, -1.9002097681526493], [288.04172546304807, 332.07984018287976, -0.02788946906741785, 1.8997402808149104, 0.4168153242491144, -28.39210955294908], [299.4351476773096, 248.7853418527407, -7.42079681324509, 26.615144268685953, 23.671675930689275, -84.89992191301803], true]
]];
var results = [
['M276.6048,265.6776c1.22072,-0.85114 2.34544,-1.73748 3.35819,-2.75339l-0.04299,-9.61141c9.6528,0 19.3072,0 28.96,0l-0.04199,9.64131c0.0002,-0.01003 0.00032,-0.02007 0.00039,-0.03011c1.0112,1.016 2.136,1.9024 3.3584,2.7536c8.4064,5.856 -1.7968,41.5104 -17.7968,41.5088l0,-33.8736c-0.54424,0 -1.08806,-0.01754 -1.62844,-0.05139c-5.65571,-0.34382 -13.76286,-2.45483 -16.16676,-7.58381z M305.01293,271.38867c-2.95531,1.12961 -6.25621,1.70701 -8.98571,1.87282c-0.03036,0.0019 -0.06073,0.00375 -0.09111,0.00555c2.75482,-0.15845 6.09324,-0.73747 9.07682,-1.87836z'],
['M462.44995,563.05157c-3.0315,0 -5.50556,-2.47062 -5.50556,-5.50212l0,0l110.11123,6.45055c0,0 -107.63717,-0.94843 -104.60567,-0.94843z'],
['M506.49444,600l60.56118,23.61275c0,3.0315 -2.47406,5.50212 -5.50556,5.50212c3.0315,0 5.50556,2.47406 5.50556,5.50556l0,22.02569c0,2.39831 -1.552,-16.27299 -3.70373,-24.14296z'],
['M400.81547,400.81547c-79.97937,79.97937 -209.65156,79.97937 -289.63094,0c-79.97937,-79.97937 -79.97937,-209.65156 0,-289.63094c79.97937,-79.97937 209.65156,-79.97937 289.63094,0c79.97937,79.97937 79.97937,209.65156 0,289.63094z'],
['M551.12365,634.03909c-12.92155,3.89332 -26.02251,5.96091 -39.12365,5.96091c-112,0 -224,-151.11 -224,-302.22c0,-1.90016 0.01384,-3.80031 0.04172,-5.7c-0.00057,0 -0.00115,0 -0.00172,0c0.41767,-28.39212 3.97401,-56.67943 11.39515,-83.29466c23.67168,-84.89992 86.67163,-152.78534 212.56485,-152.78534c168,0 224,120.89 224,241.78c0,133.43422 -87.33052,266.86844 -184.87635,296.25909z']
];
for (var i = 0; i < paths.length; i++) {
var entry = paths[i],
result = results[i],
path1 = PathItem.create(entry[0]),
path2 = PathItem.create(entry[1]);
compareBoolean(path1.unite(path2), result[0], 'path1.unite(path2); // Test ' + (i + 1));
}
});
test('Isolated edge-cases from @iconexperience\'s boolean-test suite', function() {
// Test all of @iconexperience's isolated cases in one batch.
// Read more in #784

View file

@ -130,3 +130,20 @@ test('new Path.Line({ from: from, to: to })', function() {
});
equals(path.segments.toString(), '{ point: { x: 20, y: 20 } },{ point: { x: 40, y: 40 } }');
});
test('new Path.Line({ insert: false })', function() {
var path = new Path.Line({
from:[50, 50],
to:[100, 100],
insert: false
});
equals(function() {
return typeof path.insert;
}, 'function');
equals(function() {
return path.parent;
}, null);
equals(function() {
return path.isInserted();
}, false);
});

View file

@ -12,14 +12,20 @@
QUnit.module('Path Intersections');
function createPath(curve) {
return new Path([curve.segment1, curve.segment2]);
}
function testIntersections(intersections, results) {
equals(intersections.length, results.length, 'intersections.length');
for (var i = 0; i < results.length; i++) {
for (var i = 0, l = Math.min(results.length, intersections.length); i < l; i++) {
var inter = intersections[i];
var values = results[i];
var name = 'intersections[' + i + ']';
equals(inter.point, new Point(values.point), name + '.point');
equals(inter.index, values.index, name + '.index');
if (values.point != null)
equals(inter.point, new Point(values.point), name + '.point');
if (values.index != null)
equals(inter.index, values.index, name + '.index');
if (values.time != null)
equals(inter.time, values.time, name + '.time');
if (values.crossing != null)
@ -30,9 +36,9 @@ function testIntersections(intersections, results) {
test('#565', function() {
var curve1 = new Curve(new Point(421.75945, 416.40481), new Point(-181.49299, -224.94946), new Point(44.52004, -194.13319), new Point(397.47615, 331.34712));
var curve2 = new Curve(new Point(360.09446, 350.97254), new Point(-58.58867, -218.45806), new Point(-109.55091, -220.99561), new Point(527.83582, 416.79948));
var path1 = new Path([curve1.segment1, curve1.segment2]);
var path2 = new Path([curve2.segment1, curve2.segment2]);
testIntersections(curve1.getIntersections(curve2), [
var path1 = createPath(curve1);
var path2 = createPath(curve2);
testIntersections(path1.getIntersections(path2), [
{ point: { x: 354.13635, y: 220.81369 }, index: 0, time: 0.46725, crossing: true },
{ point: { x: 390.24772, y: 224.27351 }, index: 0, time: 0.71605, crossing: true }
]);
@ -40,9 +46,9 @@ test('#565', function() {
// Alternative pair of curves that has the same issue
var curve1 = new Curve(new Point(484.9026237381622, 404.11001967731863), new Point(-265.1185871567577, -204.00749347172678), new Point(-176.7118886578828, 111.96015905588865), new Point(438.8191690435633, 429.0297837462276));
var curve2 = new Curve(new Point(388.25280445162207, 490.95032326877117), new Point(-194.0586572047323, -50.77360603027046), new Point(-184.71034923568368, -260.5346686206758), new Point(498.41401199810207, 455.55853731930256)); var path1 = new Path([curve1.segment1, curve1.segment2]);
var path1 = new Path([curve1.segment1, curve1.segment2]);
var path2 = new Path([curve2.segment1, curve2.segment2]);
testIntersections(curve1.getIntersections(curve2), [
var path1 = createPath(curve1);
var path2 = createPath(curve2);
testIntersections(path1.getIntersections(path2), [
{ point: { x: 335.62744, y: 338.15939 }, index: 0, time: 0.26516, crossing: true }
]);
});
@ -50,18 +56,18 @@ test('#565', function() {
test('#568', function() {
var curve1 = new Curve(new Point(509.05465863179415, 440.1211663847789), new Point(233.6728838738054, -245.8216403145343), new Point(-270.755685120821, 53.14275110140443), new Point(514.079892472364, 481.95262297522277));
var curve2 = new Curve(new Point(542.1666181180626, 451.06309361290187), new Point(179.91238399408758, 148.68241581134498), new Point(193.42650789767504, -47.97609066590667), new Point(423.66228222381324, 386.3876062911004));
var path1 = new Path([curve1.segment1, curve1.segment2]);
var path2 = new Path([curve2.segment1, curve2.segment2]);
testIntersections(curve1.getIntersections(curve2), [
var path1 = createPath(curve1);
var path2 = createPath(curve2);
testIntersections(path1.getIntersections(path2), [
{ point: { x: 547.96568, y: 396.66339 }, index: 0, time: 0.07024, crossing: true },
{ point: { x: 504.79973, y: 383.37886 }, index: 0, time: 0.48077, crossing: true }
]);
var curve1 = new Curve(new Point(0, 0), new Point(20, 40) , new Point (-30, -50), new Point(50, 50));
var curve2 = new Curve(new Point(50, 50), new Point(20, 100), new Point (-30, -120), new Point(250, 250));
var path1 = new Path([curve1.segment1, curve1.segment2]);
var path2 = new Path([curve2.segment1, curve2.segment2]);
testIntersections(curve1.getIntersections(curve2), [
var path1 = createPath(curve1);
var path2 = createPath(curve2);
testIntersections(path1.getIntersections(path2), [
{ point: { x: 50, y: 50 }, index: 0, time: 1, crossing: false }
]);
});
@ -71,8 +77,7 @@ test('#570', function() {
var curve2 = new Curve(new Point(311.16034791674826, 406.2985255840872), new Point(39.997020018940304, -8.347079462067768), new Point(-73.86292504547487, -77.47859270504358), new Point(465, 467));
var path1 = new Path([curve1.segment1, curve1.segment2]);
var path2 = new Path([curve2.segment1, curve2.segment2]);
var ints = curve1.getIntersections(curve2);
testIntersections(curve1.getIntersections(curve2), [
testIntersections(path1.getIntersections(path2), [
{ point: { x: 311.16035, y: 406.29853 }, index: 0, time: 1, crossing: false }
]);
});
@ -82,7 +87,7 @@ test('#571', function() {
var curve2 = new Curve(new Point(420.1235851920127, 275.8351912321666), new Point(-10.77224553077383, -53.21262197949682), new Point(-259.2129470250785, -258.56165821345775), new Point(465, 467));
var path1 = new Path([curve1.segment1, curve1.segment2]);
var path2 = new Path([curve2.segment1, curve2.segment2]);
testIntersections(curve1.getIntersections(curve2), [
testIntersections(path1.getIntersections(path2), [
{ point: { x: 352.39945, y: 330.44135 }, index: 0, time: 0.41159, crossing: true },
{ point: { x: 420.12359, y: 275.83519 }, index: 0, time: 1, crossing: false }
]);
@ -106,6 +111,49 @@ test('circle and square (existing segments overlaps on curves)', function() {
]);
});
test('intersecting paths with applyMatrix = true / false', function() {
function test(ctor, applyMatrix) {
var name = ctor.name.toLowerCase();
var item1 = new ctor.Rectangle({
point: [0, 0],
size: [200, 200],
applyMatrix: applyMatrix
});
var offset = new Point(200, 200);
item1.translate(offset);
var item2 = new ctor.Rectangle({
point: [100, 100],
size: [200, 200],
applyMatrix: applyMatrix
});
if (!applyMatrix)
offset = new Point(0, 0);
if (ctor === Path) {
testIntersections(item1.getIntersections(item2), [{
point: new Point(0, 100).add(offset), index: 0, time: 0.5,
crossing: true
}, {
point: new Point(100, 0).add(offset), index: 1, time: 0.5,
crossing: true
}]);
}
equals(item1.intersects(item2), true,
name + '1.intersects(' + name + '2);');
}
test(Path, true);
test(Path, false);
// Also tests #intersects() on Shape
test(Shape, false);
});
test('#904', function() {
var path1 = new Path([
[347.65684372173973, 270.4315945523045, 0, 0, 22.844385382059784, -25.115215946843847],
@ -224,7 +272,7 @@ test('#1197', function() {
});
test('#1233', function() {
var p = new Path([
var path = new Path([
[274, 253],
[274.39360933873525, 252.93741452470064, 0, 0, -0.6159287834059803, 0.8638915013756937],
[271.7973629022481, 254.56035493761536, 1.0464052751698083, -0.17525065612978258, 0, 0],
@ -233,8 +281,8 @@ test('#1233', function() {
[273.5560469948133, 254.11108376712414],
true
]);
p.scale(100);
testIntersections(p.getIntersections(), [
path.scale(100);
testIntersections(path.getIntersections(), [
{ point: { x: 366.12645, y: 320.20927 }, index: 1, time: 0, crossing: false },
{ point: { x: 366.07584, y: 320.28024 }, index: 1, time: 0.00027, crossing: true },
{ point: { x: 366.02122, y: 320.3568 }, index: 1, time: 0.00057, crossing: true },
@ -270,4 +318,28 @@ test('#1270', function() {
{ point: { x: 500.36244, y: 522.99072 }, index: 0, time: 0.03613, crossing: true },
{ point: { x: 463.83556, y: 476.44545 }, index: 0, time: 0.52936, crossing: true }
]);
})
});
test('#1284', function() {
// Sketch 1
var curve1 = new Curve([664.0386774947868,382.45193387473927],[0.854973312711149,-17.099466254222136],[-17.09946625422214,-0.8549733127111154],[696.5480661252607,353.03867749478684]);
var curve2 = new Curve([664.0386774947868,382.4519338747394],[-0.854973312711093,17.099466254222147],[-17.09946625422214,-0.8549733127111177],[693.4519338747393,414.96132250521316]);
var expected = [
{ point: { x: 664.03868, y: 382.45193 }, time: 0 }
];
testIntersections(curve1.getIntersections(curve2), expected);
var path1 = createPath(curve1);
var path2 = createPath(curve2);
testIntersections(path1.getIntersections(path2), expected);
// Sketch 2
var curve1 = new Curve([725.9613225052132,385.5480661252606],[4.121287844621423,-82.42575689242983],[33.676217018891116,53.91523058931191],[670.1308066286094,179.78451157644233]);
var curve2 = new Curve([725.9613225052132,385.54806612526073],[1.752865851700387,-35.057317034006445],[17.232666369345022,25.982789528039472],[702.42903006491,293.5923946244755]);
var expected = [
{ point: { x: 725.96132, y: 385.54807 }, time: 0 }
];
testIntersections(curve1.getIntersections(curve2), expected);
var path1 = createPath(curve1);
var path2 = createPath(curve2);
testIntersections(path1.getIntersections(path2), expected);
});

View file

@ -12,190 +12,256 @@
QUnit.module('Rectangle');
test('new Rectangle(new Point(10, 20), new Size(30, 40));', function() {
test('new Rectangle(Point, Size);', function() {
var rect = new Rectangle(new Point(10, 20), new Size(30, 40));
equals(rect.toString(), '{ x: 10, y: 20, width: 30, height: 40 }');
equals(rect, new Rectangle(10, 20, 30, 40));
});
test('new Rectangle({ point: [10, 20], size: [30, 40] });', function() {
test('new Rectangle({ point, size });', function() {
var rect = new Rectangle({ point: [10, 20], size: [30, 40] });
equals(rect.toString(), '{ x: 10, y: 20, width: 30, height: 40 }');
});
test('new Rectangle({ point: new Point(10, 20), size: new Size(30, 40)});', function() {
equals(rect, new Rectangle(10, 20, 30, 40));
var rect = new Rectangle({ point: new Point(10, 20), size: new Size(30, 40)});
equals(rect.toString(), '{ x: 10, y: 20, width: 30, height: 40 }');
equals(rect, new Rectangle(10, 20, 30, 40));
});
test('new Rectangle([10, 20], [30, 40]);', function() {
test('new Rectangle(Array, Array);', function() {
var rect = new Rectangle([10, 20], [30, 40]);
equals(rect.toString(), '{ x: 10, y: 20, width: 30, height: 40 }');
equals(rect, new Rectangle(10, 20, 30, 40));
});
test('new Rectangle({from: [10, 20], to: [30, 40]});', function() {
var rect = new Rectangle({from: [10, 20], to: [30, 40]});
equals(rect.toString(), '{ x: 10, y: 20, width: 20, height: 20 }');
});
test('new Rectangle(new Point(10, 20), new Point(30, 40));', function() {
test('new Rectangle(Point, Point);', function() {
var rect = new Rectangle(new Point(10, 20), new Point(30, 40));
equals(rect.toString(), '{ x: 10, y: 20, width: 20, height: 20 }');
equals(rect, new Rectangle(10, 20, 20, 20));
});
test('new Rectangle(10, 20, 30, 40);', function() {
test('new Rectangle({ from, to });', function() {
var rect = new Rectangle({from: [10, 20], to: [30, 40]});
equals(rect, new Rectangle(10, 20, 20, 20));
});
test('new Rectangle(x, y, width, height);', function() {
var rect = new Rectangle(10, 20, 30, 40);
equals(rect.toString(), '{ x: 10, y: 20, width: 30, height: 40 }');
equals(rect, new Rectangle(10, 20, 30, 40));
});
test('new Rectangle({x: 10, y: 20, width: 30, height: 40});', function() {
test('new Rectangle({ x, y, width, height });', function() {
var rect = new Rectangle({x: 10, y: 20, width: 30, height: 40});
equals(rect.toString(), '{ x: 10, y: 20, width: 30, height: 40 }');
equals(rect, new Rectangle(10, 20, 30, 40));
});
test('get size', function() {
test('new Rectangle(object)', function() {
var expected = new Rectangle(100, 50, 100, 200);
equals(function() {
return new Rectangle({
top: expected.top,
right: expected.right,
bottom: expected.bottom,
left: expected.left
});
}, expected);
function testProperties(key1, key2) {
var obj = {};
obj[key1] = expected[key1];
obj[key2] = expected[key2];
var rect = new Rectangle(obj);
equals(rect, expected, 'new Rectangle({ ' + key1 + ', ' + key2 + ' });');
}
var tests = [
['center', 'size'],
['topLeft', 'size'],
['topRight', 'size'],
['bottomRight', 'size'],
['bottomLeft', 'size'],
['leftCenter', 'size'],
['topCenter', 'size'],
['rightCenter', 'size'],
['bottomCenter', 'size'],
['topLeft', 'bottomRight'],
['topRight', 'bottomLeft'],
['topLeft', 'bottomCenter'],
['topLeft', 'rightCenter'],
['topRight', 'bottomCenter'],
['topRight', 'leftCenter'],
['bottomLeft', 'topCenter'],
['bottomLeft', 'rightCenter'],
['bottomRight', 'topCenter'],
['bottomRight', 'leftCenter']
];
tests.forEach(function(test) {
testProperties(test[0], test[1]);
testProperties(test[1], test[0]);
});
});
test('rect.left / rect.top VS rect.right / rect.bottom', function() {
var rect = new Rectangle({
point: [0,0],
size: [100, 100],
});
rect.left -= 10;
rect.top -= 10;
equals(rect.right, 90);
equals(rect.bottom, 90);
var rect = new Rectangle([0, 0], [100, 100]);
rect.left -= 10;
rect.top -= 10;
equals(rect.right, 90);
equals(rect.bottom, 90);
var rect = new Rectangle({
topLeft: [0,0],
bottomRight: [100, 100],
});
rect.left -= 10;
rect.top -= 10;
equals(rect.right, 100);
equals(rect.bottom, 100);
});
test('rect.size', function() {
var rect = new Rectangle(10, 10, 20, 30);
equals(function() {
return rect.size.equals([20, 30]);
}, true);
rect.size = [30, 40];
equals(rect, new Rectangle(10, 10, 30, 40));
});
test('set size', function() {
var rect = new Rectangle(10, 10, 20, 20);
rect.size = new Size(30, 30);
equals(rect.toString(), '{ x: 10, y: 10, width: 30, height: 30 }');
test('rect.center', function() {
var rect = new Rectangle(10, 10, 20, 30);
equals(function() {
return rect.size;
}, new Size(20, 30));
equals(function() {
return rect.center;
}, new Point(20, 25));
rect.center = [100, 100];
equals(function() {
return rect.center;
}, new Point(100, 100));
equals(function() {
return rect.size;
}, new Size(20, 30));
rect.center = [200, 200];
equals(function() {
return rect.center;
}, new Point(200, 200));
equals(function() {
return rect.size;
}, new Size(20, 30));
});
test('topLeft', function() {
test('rect.topLeft', function() {
var rect = new Rectangle(10, 10, 20, 20);
var point = rect.topLeft;
equals(point.toString(), '{ x: 10, y: 10 }');
});
test('set topLeft', function() {
var rect = new Rectangle(10, 10, 20, 20);
equals(point, { x: 10, y: 10 });
rect.topLeft = [10, 15];
var point = rect.topLeft;
equals(point.toString(), '{ x: 10, y: 15 }');
equals(point, { x: 10, y: 15 });
});
test('get topRight', function() {
test('rect.topRight', function() {
var rect = new Rectangle(10, 10, 20, 20);
var point = rect.topRight;
equals(point.toString(), '{ x: 30, y: 10 }');
});
test('set topRight', function() {
equals(point, { x: 30, y: 10 });
var rect = new Rectangle(10, 10, 20, 20);
rect.topRight = [10, 15];
var point = rect.topRight;
equals(point.toString(), '{ x: 10, y: 15 }');
equals(point, { x: 10, y: 15 });
});
test('get bottomLeft', function() {
test('rect.bottomLeft', function() {
var rect = new Rectangle(10, 10, 20, 20);
var point = rect.bottomLeft;
equals(point.toString(), '{ x: 10, y: 30 }');
});
test('set bottomLeft', function() {
equals(point, { x: 10, y: 30 });
var rect = new Rectangle(10, 10, 20, 20);
rect.bottomLeft = [10, 15];
var point = rect.bottomLeft;
equals(point.toString(), '{ x: 10, y: 15 }');
equals(point, { x: 10, y: 15 });
});
test('get bottomRight', function() {
test('rect.bottomRight', function() {
var rect = new Rectangle(10, 10, 20, 20);
var point = rect.bottomRight;
equals(point.toString(), '{ x: 30, y: 30 }');
});
test('set bottomRight', function() {
equals(point, { x: 30, y: 30 });
var rect = new Rectangle(10, 10, 20, 20);
rect.bottomRight = [10, 15];
var point = rect.bottomRight;
equals(point.toString(), '{ x: 10, y: 15 }');
equals(point, { x: 10, y: 15 });
});
test('get bottomCenter', function() {
test('rect.bottomCenter', function() {
var rect = new Rectangle(10, 10, 20, 20);
var point = rect.bottomCenter;
equals(point.toString(), '{ x: 20, y: 30 }');
});
test('set bottomCenter', function() {
equals(point, { x: 20, y: 30 });
var rect = new Rectangle(10, 10, 20, 20);
rect.bottomCenter = [10, 15];
var point = rect.bottomCenter;
equals(point.toString(), '{ x: 10, y: 15 }');
equals(point, { x: 10, y: 15 });
});
test('get topCenter', function() {
test('rect.topCenter', function() {
var rect = new Rectangle(10, 10, 20, 20);
var point = rect.topCenter;
equals(point.toString(), '{ x: 20, y: 10 }');
});
test('set topCenter', function() {
equals(point, { x: 20, y: 10 });
var rect = new Rectangle(10, 10, 20, 20);
rect.topCenter = [10, 15];
var point = rect.topCenter;
equals(point.toString(), '{ x: 10, y: 15 }');
equals(point, { x: 10, y: 15 });
});
test('get leftCenter', function() {
test('rect.leftCenter', function() {
var rect = new Rectangle(10, 10, 20, 20);
var point = rect.leftCenter;
equals(point.toString(), '{ x: 10, y: 20 }');
});
test('set leftCenter', function() {
equals(point, { x: 10, y: 20 });
var rect = new Rectangle(10, 10, 20, 20);
rect.leftCenter = [10, 15];
var point = rect.leftCenter;
equals(point.toString(), '{ x: 10, y: 15 }');
equals(point, { x: 10, y: 15 });
});
test('get rightCenter', function() {
test('rect.rightCenter', function() {
var rect = new Rectangle(10, 10, 20, 20);
var point = rect.rightCenter;
equals(point.toString(), '{ x: 30, y: 20 }');
});
test('set rightCenter', function() {
equals(point, { x: 30, y: 20 });
var rect = new Rectangle(10, 10, 20, 20);
rect.rightCenter = [10, 15];
var point = rect.rightCenter;
equals(point.toString(), '{ x: 10, y: 15 }');
equals(point, { x: 10, y: 15 });
});
test('intersects(rect)', function() {
var rect1 = new Rectangle({ x: 160, y: 270, width: 20, height: 20 });
var rect2 = { x: 195, y: 301, width: 19, height: 19 };
test('rect1.intersects(rect2)', function() {
var rect1 = new Rectangle(160, 270, 20, 20);
var rect2 = new Rectangle(195, 301, 19, 19);
equals(function() {
return rect1.intersects(rect2);
}, false);
rect1 = new Rectangle({ x: 160, y: 270, width: 20, height: 20 });
rect2 = { x: 170.5, y: 280.5, width: 19, height: 19 };
rect1 = new Rectangle(160, 270, 20, 20);
rect2 = new Rectangle(170.5, 280.5, 19, 19);
equals(function() {
return rect1.intersects(rect2);
}, true);
});
test('contains(rect)', function() {
var rect1 = new Rectangle({ x: 160, y: 270, width: 20, height: 20 });
var rect2 = { x: 195, y: 301, width: 19, height: 19 };
test('rect1.contains(rect2)', function() {
var rect1 = new Rectangle(160, 270, 20, 20);
var rect2 = new Rectangle(195, 301, 19, 19);
equals(function() {
return rect1.contains(rect2);
}, false);
rect1 = new Rectangle({ x: 160, y: 270, width: 20, height: 20 });
rect2 = new Rectangle({ x: 170.5, y: 280.5, width: 19, height: 19 });
rect1 = new Rectangle(160, 270, 20, 20);
rect2 = new Rectangle(170.5, 280.5, 19, 19);
equals(function() {
return rect1.contains(rect2);
}, false);
rect1 = new Rectangle({ x: 299, y: 161, width: 137, height: 129 });
rect2 = new Rectangle({ x: 340, y: 197, width: 61, height: 61 });
rect1 = new Rectangle(299, 161, 137, 129);
rect2 = new Rectangle(340, 197, 61, 61);
equals(function() {
return rect1.contains(rect2);
}, true);
@ -204,8 +270,8 @@ test('contains(rect)', function() {
}, false);
});
test('contains(point)', function() {
var rect = new Rectangle({ x: 160, y: 270, width: 20, height: 20 });
test('rect.contains(point)', function() {
var rect = new Rectangle(160, 270, 20, 20);
var point = new Point(166, 280);
equals(function() {
return rect.contains(point);
@ -216,121 +282,33 @@ test('contains(point)', function() {
}, false);
});
test('intersect(rect)', function() {
var rect1 = new Rectangle({ x: 160, y: 270, width: 20, height: 20 });
var rect2 = { x: 170.5, y: 280.5, width: 19, height: 19 };
test('rect1.intersect(rect2)', function() {
var rect1 = new Rectangle(160, 270, 20, 20);
var rect2 = new Rectangle(170.5, 280.5, 19, 19);
var intersected = rect1.intersect(rect2);
equals(function() {
return intersected.equals({ x: 170.5, y: 280.5, width: 9.5, height: 9.5 });
return intersected.equals(new Rectangle(170.5, 280.5, 9.5, 9.5));
}, true);
});
test('unite(rect)', function() {
var rect1 = new Rectangle({ x: 160, y: 270, width: 20, height: 20 });
var rect2 = { x: 170.5, y: 280.5, width: 19, height: 19 };
test('rect1.unite(rect2)', function() {
var rect1 = new Rectangle(160, 270, 20, 20);
var rect2 = new Rectangle(170.5, 280.5, 19, 19);
var united = rect1.unite(rect2);
equals(function() {
return united.equals({ x: 160, y: 270, width: 29.5, height: 29.5 });
return united.equals(new Rectangle(160, 270, 29.5, 29.5));
}, true);
});
test('include(point)', function() {
var rect1 = new Rectangle({ x: 95, y: 151, width: 20, height: 20 });
test('rect.include(point)', function() {
var rect1 = new Rectangle(95, 151, 20, 20);
var included = rect1.include([50, 50]);
equals(function() {
return included.equals({ x: 50, y: 50, width: 65, height: 121 });
return included.equals(new Rectangle(50, 50, 65, 121));
}, true);
});
test('toString()', function() {
test('rect.toString()', function() {
var string = new Rectangle(10, 20, 30, 40).toString();
equals(string, '{ x: 10, y: 20, width: 30, height: 40 }');
});
test('new Rectangle(object)', function() {
equals(function() {
return new Rectangle({
center: [50, 100],
size: [100, 200]
}).toString();
}, '{ x: 0, y: 0, width: 100, height: 200 }');
equals(function() {
return new Rectangle({
topLeft: [100, 50],
size: [100, 200]
}).toString();
}, '{ x: 100, y: 50, width: 100, height: 200 }');
equals(function() {
return new Rectangle({
size: [100, 200],
topLeft: [100, 50]
}).toString();
}, '{ x: 100, y: 50, width: 100, height: 200 }');
equals(function() {
return new Rectangle({
topRight: [200, 50],
size: [100, 200]
}).toString();
}, '{ x: 100, y: 50, width: 100, height: 200 }');
equals(function() {
return new Rectangle({
size: [100, 200],
topRight: [200, 50]
}).toString();
}, '{ x: 100, y: 50, width: 100, height: 200 }');
equals(function() {
return new Rectangle({
bottomRight: [200, 250],
size: [100, 200]
}).toString();
}, '{ x: 100, y: 50, width: 100, height: 200 }');
equals(function() {
return new Rectangle({
size: [100, 200],
bottomRight: [200, 250]
}).toString();
}, '{ x: 100, y: 50, width: 100, height: 200 }');
equals(function() {
return new Rectangle({
bottomLeft: [100, 250],
size: [100, 200]
}).toString();
}, '{ x: 100, y: 50, width: 100, height: 200 }');
equals(function() {
return new Rectangle({
size: [100, 200],
bottomLeft: [100, 250]
}).toString();
}, '{ x: 100, y: 50, width: 100, height: 200 }');
equals(function() {
return new Rectangle({
topRight: [200, 50],
bottomLeft: [100, 250]
}).toString();
}, '{ x: 100, y: 50, width: 100, height: 200 }');
equals(function() {
return new Rectangle({
topLeft: [100, 50],
bottomRight: [200, 250]
}).toString();
}, '{ x: 100, y: 50, width: 100, height: 200 }');
equals(function() {
return new Rectangle({
top: 50,
right: 200,
bottom: 250,
left: 100
}).toString();
}, '{ x: 100, y: 50, width: 100, height: 200 }');
});

View file

@ -57,24 +57,38 @@ test('setting Project#currentStyle to an object', function() {
test('setting Path#style to an object', function() {
var path = new Path();
path.strokeWidth = 10;
path.style = {
fillColor: 'red',
strokeColor: 'green'
};
equals(path.fillColor, new Color('red'), 'path.fillColor');
equals(path.strokeColor, new Color('green'), 'path.strokeColor');
equals(path.strokeWidth, 10,
'path.strokeWidth, set outside object should not be cleared');
equals(path.style.fillColor, new Color('red'), 'path.style.fillColor');
equals(path.style.strokeColor, new Color('green'), 'path.style.strokeColor');
equals(path.style.strokeWidth, 10,
'path.style.strokeWidth, set outside object should not be cleared');
});
test('setting Group#style to an object', function() {
var group = new Group();
var path = new Path();
group.addChild(path);
group.strokeWidth = 10;
group.style = {
fillColor: 'red',
strokeColor: 'green'
};
equals(path.fillColor, new Color('red'), 'path.fillColor');
equals(path.strokeColor, new Color('green'), 'path.strokeColor');
equals(path.strokeWidth, 10,
'path.strokeWidth, set outside object should not be cleared');
equals(path.style.fillColor, new Color('red'), 'path.style.fillColor');
equals(path.style.strokeColor, new Color('green'), 'path.style.strokeColor');
equals(path.style.strokeWidth, 10,
'path.style.strokeWidth, set outside object should not be cleared');
});
test('getting Group#fillColor', function() {

View file

@ -97,7 +97,7 @@ test('SymbolDefinition item selection', function() {
path.selected = true;
var definition = new SymbolDefinition(path);
equals(function() {
return definition.item.selected === false;
return definition.item.selected == false;
}, true);
equals(function() {
return paper.project.selectedItems.length === 0;