Merge remote-tracking branch 'origin/develop' into new-winding

; Conflicts:
;	src/path/PathItem.Boolean.js
This commit is contained in:
Jürg Lehni 2016-07-17 19:52:20 +02:00
commit c9100a2b61
47 changed files with 584 additions and 414 deletions

2
.gitignore vendored
View file

@ -1,2 +1,2 @@
/node_modules/ /node_modules/
/dist/ /dist/*/

View file

@ -1,16 +1,17 @@
{ {
"esversion": 5,
"browser": true, "browser": true,
"node": true, "node": true,
"wsh": true, "wsh": true,
"evil": true, "evil": true,
"trailing": false,
"smarttabs": false,
"sub": true,
"supernew": true, "supernew": true,
"laxbreak": true, "laxbreak": true,
"eqeqeq": false, "eqeqeq": false,
"eqnull": true, "eqnull": true,
"loopfunc": true, "loopfunc": true,
"boss": true, "boss": true,
"shadow": true "shadow": true,
"funcscope": false,
"latedef": "nofunc",
"freeze": true
} }

View file

@ -38,6 +38,6 @@ script:
- gulp jshint - gulp jshint
- gulp minify - gulp minify
- gulp test - gulp test
- gulp dist - gulp zip
after_script: after_script:
- '[ "${TRAVIS_BRANCH}" = "develop" ] && [ "${TRAVIS_NODE_VERSION}" = "stable" ] && travis/deploy-prebuilt.sh' - '[ "${TRAVIS_BRANCH}" = "develop" ] && [ "${TRAVIS_NODE_VERSION}" = "stable" ] && travis/deploy-prebuilt.sh'

View file

@ -1,16 +1,38 @@
# Change Log # Change Log
All notable changes to Paper.js shall be documented in this file, following
common [CHANGELOG](http://keepachangelog.com/) conventions. As of `0.10.0`,
Paper.js adheres to [Semantic Versioning](http://semver.org/).
## `0.10.0` (Unreleased) ## `0.10.3` (Unreleased)
### Changed
- Loosely couple Node.js / Electron code to Canvas module, and treat its absence
like a headless web worker context in the browser (#1103).
### Fixed
- Prevent `Path#getStrokeBounds(matrix)` from accidentally modifying segments
(#1102).
- Compatibility with JSPM (#1104).
## `0.10.2`
### Fixed
- Get published version to work correctly in Bower again.
## `0.10.1`
### Fixed
- Correct a few issues with documentation and NPM publishing that slipped
through in the `0.10.0` release.
## `0.10.0`
### Preamble ### Preamble
This is a huge release for Paper.js as we aim for a version `1.0.0` release This is a huge release for Paper.js as we aim for a version `1.0.0` release
later this year. There are many items in the changelog (and many more items not later this year. As of this version, all notable changes are documented in the
in the changelog) so here a high-level overview to frame the long list of change-log following common [CHANGELOG](http://keepachangelog.com/) conventions.
changes: Paper.js now also adheres to [Semantic Versioning](http://semver.org/).
There are many items in the changelog (and many more items not in the changelog)
so here a high-level overview to frame the long list of changes:
- Boolean operations have been improved and overhauled for reliability and - Boolean operations have been improved and overhauled for reliability and
efficiency. These include the path functions to unite, intersect, subtract, efficiency. These include the path functions to unite, intersect, subtract,
@ -221,7 +243,7 @@ contribute to the code.
invertible transformations (#558). invertible transformations (#558).
- Scaling shadows now works correctly with browser- and view-zoom (#831). - Scaling shadows now works correctly with browser- and view-zoom (#831).
- `Path#arcTo()` correctly handles zero sizes. - `Path#arcTo()` correctly handles zero sizes.
- `#importSVG()` handles onLoad and onError callbacks for string inputs that - `#importSVG()` handles `onLoad` and `onError` callbacks for string inputs that
load external resources (#827). load external resources (#827).
- `#importJSON()` and `#exportJSON()` now handle non-`Item` objects correctly - `#importJSON()` and `#exportJSON()` now handle non-`Item` objects correctly
(#392). (#392).
@ -248,6 +270,8 @@ contribute to the code.
rounding (#1045). rounding (#1045).
- Improve reliability of fat-line clipping for curves that are very similar - Improve reliability of fat-line clipping for curves that are very similar
(#904). (#904).
- Improve precision of `Numerical.solveQuadratic()` and
`Numerical.solveCubic()` for edge-cases (#1085).
### Removed ### Removed
- Canvas attributes "resize" and "data-paper-resize" no longer cause paper to - Canvas attributes "resize" and "data-paper-resize" no longer cause paper to

View file

@ -14,14 +14,14 @@
], ],
"main": "dist/paper-full.js", "main": "dist/paper-full.js",
"ignore": [ "ignore": [
"build",
"components",
"dist/paper-node.js",
"projects",
"node_modules",
"package.json", "package.json",
"gulpfile.js",
"gulp",
"node_modules",
"projects",
"src", "src",
"test" "test",
"travis"
], ],
"keywords": [ "keywords": [
"vector", "vector",

View file

@ -1,34 +0,0 @@
{
"name": "paper",
"version": "0.9.25",
"description": "The Swiss Army Knife of Vector Graphics Scripting",
"license": "MIT",
"repo": "paperjs/paper.js",
"main": [
"dist/paper-full.js"
],
"scripts": [
"dist/paper-full.js"
],
"files": [
"AUTHORS.md",
"LICENSE.txt",
"README.md"
],
"keywords": [
"vector",
"graphic",
"graphics",
"2d",
"geometry",
"bezier",
"curve",
"curves",
"path",
"paths",
"canvas",
"svg",
"paper",
"paper.js"
]
}

1
dist/paper-core.js vendored Symbolic link
View file

@ -0,0 +1 @@
../src/load.js

1
dist/paper-full.js vendored Symbolic link
View file

@ -0,0 +1 @@
../src/load.js

@ -1 +1 @@
Subproject commit 4a90c0501d3d0a1f5fec2363e9dce623d0758401 Subproject commit b6f36edba33690cee908cadcb24515ec5be97b1d

View file

@ -16,8 +16,7 @@ var gulp = require('gulp'),
uncomment = require('gulp-uncomment'), uncomment = require('gulp-uncomment'),
whitespace = require('gulp-whitespace'), whitespace = require('gulp-whitespace'),
del = require('del'), del = require('del'),
extend = require('extend'), options = require('../utils/options.js');
options = require('../utils/options.js')({ suffix: true });
// Options to be used in Prepro.js preprocessing through the global __options // Options to be used in Prepro.js preprocessing through the global __options
// object, merged in with the options required above. // object, merged in with the options required above.
@ -48,10 +47,10 @@ buildNames.forEach(function(name) {
evaluate: ['src/constants.js'], evaluate: ['src/constants.js'],
setup: function() { setup: function() {
// Return objects to be defined in the preprocess-scope. // Return objects to be defined in the preprocess-scope.
// Note that this would be merge in with already existing // Note that this would be merged in with already existing
// objects. // objects.
return { return {
__options: extend({}, options, buildOptions[name]) __options: Object.assign({}, options, buildOptions[name])
}; };
} }
})) }))

View file

@ -15,7 +15,9 @@ var gulp = require('gulp'),
merge = require('merge-stream'), merge = require('merge-stream'),
zip = require('gulp-zip'); zip = require('gulp-zip');
gulp.task('dist', ['minify', 'docs', 'clean:dist'], function() { gulp.task('dist', ['build', 'minify', 'docs']);
gulp.task('zip', ['clean:zip', 'dist'], function() {
return merge( return merge(
gulp.src([ gulp.src([
'dist/paper-full*.js', 'dist/paper-full*.js',
@ -31,7 +33,7 @@ gulp.task('dist', ['minify', 'docs', 'clean:dist'], function() {
.pipe(gulp.dest('dist')); .pipe(gulp.dest('dist'));
}); });
gulp.task('clean:dist', function() { gulp.task('clean:zip', function() {
return del([ return del([
'dist/paperjs.zip' 'dist/paperjs.zip'
]); ]);

View file

@ -14,7 +14,7 @@ var gulp = require('gulp'),
del = require('del'), del = require('del'),
rename = require('gulp-rename'), rename = require('gulp-rename'),
shell = require('gulp-shell'), shell = require('gulp-shell'),
options = require('../utils/options.js')({ suffix: true }); options = require('../utils/options.js');
var docOptions = { var docOptions = {
local: 'docs', // Generates the offline docs local: 'docs', // Generates the offline docs
@ -22,7 +22,7 @@ var docOptions = {
}; };
gulp.task('docs', ['docs:local', 'build:full'], function() { gulp.task('docs', ['docs:local', 'build:full'], function() {
gulp.src('dist/paper-full.js') return gulp.src('dist/paper-full.js')
.pipe(rename({ basename: 'paper' })) .pipe(rename({ basename: 'paper' }))
.pipe(gulp.dest('dist/docs/assets/js/')); .pipe(gulp.dest('dist/docs/assets/js/'));
}); });

View file

@ -21,5 +21,5 @@ gulp.task('load', ['clean:load'], function() {
}); });
gulp.task('clean:load', function() { gulp.task('clean:load', function() {
return del([ 'dist/paper-full.js', 'dist/paper-core.js', 'dist/node/**' ]); return del([ 'dist/*.js', 'dist/node/**' ]);
}); });

View file

@ -11,35 +11,57 @@
*/ */
var gulp = require('gulp'), var gulp = require('gulp'),
addSrc = require('gulp-add-src'),
bump = require('gulp-bump'), bump = require('gulp-bump'),
git = require('gulp-git-streamed'), git = require('gulp-git-streamed'),
run = require('run-sequence'),
shell = require('gulp-shell'), shell = require('gulp-shell'),
options = require('../utils/options.js')({ suffix: false }); options = require('../utils/options.js');
gulp.task('publish', ['publish:bump', 'publish:release']); gulp.task('publish', function() {
gulp.task('publish:bump', function() {
return gulp.src([ 'package.json', 'component.json' ])
.pipe(bump({ version: options.version }))
.pipe(gulp.dest('./'))
.pipe(addSrc('src/options.js'))
.pipe(git.add());
});
gulp.task('publish:release', function() {
if (options.branch !== 'develop') { if (options.branch !== 'develop') {
throw new Error('Publishing is only allowed on the develop branch.'); throw new Error('Publishing is only allowed on the develop branch.');
} }
return run(
'publish:version',
'publish:dist',
'publish:commit',
'publish:release',
'publish:load'
);
});
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 }))
.pipe(gulp.dest('.'));
});
gulp.task('publish:dist', ['dist']);
gulp.task('publish:commit', function() {
var message = 'Release version ' + options.version; var message = 'Release version ' + options.version;
return gulp.src('.') return gulp.src('.')
.pipe(git.checkout('develop'))
.pipe(git.add())
.pipe(git.commit(message)) .pipe(git.commit(message))
.pipe(git.tag('v' + options.version, message)) .pipe(git.tag('v' + options.version, message));
});
gulp.task('publish:release', function() {
return gulp.src('.')
.pipe(git.checkout('master')) .pipe(git.checkout('master'))
.pipe(git.merge('develop', { args: '-X theirs' })) .pipe(git.merge('develop', { args: '-X theirs' }))
.pipe(git.push('origin', 'master' )) .pipe(git.push('origin', ['master', 'develop'], { args: '--tags' }))
.pipe(git.push('origin', 'develop' )) .pipe(shell('npm publish'));
.pipe(git.push(null, null, { args: '--tags' } )) });
.pipe(shell('npm publish'))
.pipe(git.checkout('develop')); gulp.task('publish:load', ['load'], function() {
return gulp.src('dist')
.pipe(git.checkout('develop'))
.pipe(git.add())
.pipe(git.commit('Switch back to load.js versions on develop branch.'))
.pipe(git.push('origin', 'develop'));
}); });

View file

@ -11,8 +11,8 @@
*/ */
var gulp = require('gulp'), var gulp = require('gulp'),
qunits = require('gulp-qunits'),
gutil = require('gulp-util'), gutil = require('gulp-util'),
qunits = require('gulp-qunits'),
webserver = require('gulp-webserver'); webserver = require('gulp-webserver');
gulp.task('test', ['test:phantom', 'test:node']); gulp.task('test', ['test:phantom', 'test:node']);

View file

@ -14,7 +14,6 @@ var gulp = require('gulp'),
path = require('path'), path = require('path'),
gutil = require('gulp-util'); gutil = require('gulp-util');
gulp.task('watch', function () { gulp.task('watch', function () {
gulp.watch('src/**/*.js', ['jshint']) gulp.watch('src/**/*.js', ['jshint'])
.on('change', function(event) { .on('change', function(event) {

View file

@ -11,7 +11,6 @@
*/ */
var execSync = require('child_process').execSync, var execSync = require('child_process').execSync,
extend = require('extend'),
// Require the __options object, so we have access to the version number and // Require the __options object, so we have access to the version number and
// make amendments, e.g. the release date. // make amendments, e.g. the release date.
options = require('../../src/options.js'); options = require('../../src/options.js');
@ -20,19 +19,20 @@ function git(command) {
return execSync('git ' + command).toString().trim(); return execSync('git ' + command).toString().trim();
} }
options.date = git('log -1 --pretty=format:%ad');
options.branch = git('rev-parse --abbrev-ref HEAD');
// Get the date of the last commit from this branch for release date: // Get the date of the last commit from this branch for release date:
var date = git('log -1 --pretty=format:%ad'), var version = options.version,
branch = git('rev-parse --abbrev-ref HEAD'); branch = options.branch;
extend(options, { // If we're not on the master branch, use the branch name as a suffix:
date: date, if (branch !== 'master')
branch: branch, options.version += '-' + branch;
// If we're not on the master branch, use the branch name as a suffix:
suffix: branch === 'master' ? '' : '-' + branch
});
module.exports = function(opts) { // Allow the removal of the suffix again, as needed by the publish task.
return extend({}, options, opts && opts.suffix && { options.resetVersion = function() {
version: options.version + options.suffix options.version = version;
}); }
};
module.exports = options;

View file

@ -1,6 +1,6 @@
{ {
"name": "paper", "name": "paper",
"version": "0.9.25", "version": "0.10.2",
"description": "The Swiss Army Knife of Vector Graphics Scripting", "description": "The Swiss Army Knife of Vector Graphics Scripting",
"license": "MIT", "license": "MIT",
"homepage": "http://paperjs.org", "homepage": "http://paperjs.org",
@ -15,12 +15,19 @@
], ],
"main": "dist/paper-full.js", "main": "dist/paper-full.js",
"scripts": { "scripts": {
"lint": "jshint src", "precommit": "gulp jshint",
"prepublish": "gulp minify", "prepush": "gulp test",
"build": "gulp build",
"dist": "gulp dist",
"zip": "gulp zip",
"docs": "gulp docs",
"load": "gulp load",
"jshint": "gulp jshint",
"test": "gulp test" "test": "gulp test"
}, },
"files": [ "files": [
"AUTHORS.md", "AUTHORS.md",
"CHANGELOG.md",
"dist/", "dist/",
"examples/", "examples/",
"LICENSE.txt", "LICENSE.txt",
@ -30,7 +37,7 @@
"node": ">=4.0.0 <7.0.0" "node": ">=4.0.0 <7.0.0"
}, },
"dependencies": { "dependencies": {
"jsdom": "^8.3.0", "jsdom": "^9.4.0",
"source-map-support": "^0.4.0" "source-map-support": "^0.4.0"
}, },
"optionalDependencies": { "optionalDependencies": {
@ -38,38 +45,39 @@
}, },
"devDependencies": { "devDependencies": {
"acorn": "~0.5.0", "acorn": "~0.5.0",
"del": "^2.2.0", "del": "^2.2.1",
"extend": "^3.0.0", "gulp": "^3.9.1",
"gulp": "^3.9.0", "gulp-bump": "^2.2.0",
"gulp-add-src": "^0.2.0",
"gulp-bump": "^1.0.0",
"gulp-cached": "^1.1.0", "gulp-cached": "^1.1.0",
"gulp-git-streamed": "^1.0.0", "gulp-git-streamed": "^1.8.0",
"gulp-jshint": "^2.0.0", "gulp-jshint": "^2.0.0",
"gulp-prepro": "^2.4.0", "gulp-prepro": "^2.4.0",
"gulp-qunits": "^2.0.1", "gulp-qunits": "^2.1.0",
"gulp-rename": "^1.2.2", "gulp-rename": "^1.2.2",
"gulp-shell": "^0.5.2", "gulp-shell": "^0.5.2",
"gulp-symlink": "^2.1.3", "gulp-symlink": "^2.1.4",
"gulp-uglify": "^1.5.1", "gulp-uglify": "^1.5.4",
"gulp-uncomment": "^0.3.0", "gulp-uncomment": "^0.3.0",
"gulp-util": "^3.0.0", "gulp-util": "^3.0.7",
"gulp-webserver": "^0.9.1", "gulp-webserver": "^0.9.1",
"gulp-whitespace": "^0.1.0", "gulp-whitespace": "^0.1.0",
"gulp-zip": "^3.0.2", "gulp-zip": "^3.2.0",
"jshint": "2.8.x", "husky": "^0.11.4",
"jshint": "^2.9.2",
"jshint-summary": "^0.4.0", "jshint-summary": "^0.4.0",
"merge-stream": "^1.0.0", "merge-stream": "^1.0.0",
"prepro": "^2.4.0", "prepro": "^2.4.0",
"qunitjs": "^1.20.0", "qunitjs": "^1.23.0",
"require-dir": "^0.3.0", "require-dir": "^0.3.0",
"resemblejs": "^2.1.0", "resemblejs": "^2.2.1",
"stats.js": "0.0.14-master", "run-sequence": "^1.2.2",
"stats.js": "0.16.0",
"straps": "^1.9.0" "straps": "^1.9.0"
}, },
"browser": { "browser": {
"canvas": false, "canvas": false,
"jsdom": false, "jsdom": false,
"jsdom/lib/jsdom/living/generated/utils": false,
"source-map-support": false, "source-map-support": false,
"./dist/node/window.js": false, "./dist/node/window.js": false,
"./dist/node/extend.js": false "./dist/node/extend.js": false

View file

@ -80,10 +80,15 @@ var Emitter = {
var handlers = this._callbacks && this._callbacks[type]; var handlers = this._callbacks && this._callbacks[type];
if (!handlers) if (!handlers)
return false; return false;
var args = [].slice.call(arguments, 1); var args = [].slice.call(arguments, 1),
// Set the current target to `this` if the event object defines
// #target but not #currentTarget.
setTarget = event && event.target && !event.currentTarget;
// Create a clone of the handlers list so changes caused by on / off // Create a clone of the handlers list so changes caused by on / off
// won't throw us off track here: // won't throw us off track here:
handlers = handlers.slice(); handlers = handlers.slice();
if (setTarget)
event.currentTarget = this;
for (var i = 0, l = handlers.length; i < l; i++) { 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 // If the handler returns false, prevent the default behavior
@ -94,6 +99,8 @@ var Emitter = {
break; break;
} }
} }
if (setTarget)
delete event.currentTarget;
return true; return true;
}, },

View file

@ -14,7 +14,7 @@
* @name PaperScript * @name PaperScript
* @namespace * @namespace
*/ */
Base.exports.PaperScript = (function() { Base.exports.PaperScript = function() {
// Locally turn of exports and define for inlined acorn. // Locally turn of exports and define for inlined acorn.
// Just declaring the local vars is enough, as they will be undefined. // Just declaring the local vars is enough, as they will be undefined.
var exports, define, var exports, define,
@ -347,7 +347,7 @@ Base.exports.PaperScript = (function() {
} }
if (/^(inline|both)$/.test(sourceMaps)) { if (/^(inline|both)$/.test(sourceMaps)) {
code += "\n//# sourceMappingURL=data:application/json;base64," code += "\n//# sourceMappingURL=data:application/json;base64,"
+ window.btoa(unescape(encodeURIComponent( + self.btoa(unescape(encodeURIComponent(
JSON.stringify(map)))); JSON.stringify(map))));
} }
code += "\n//# sourceURL=" + (url || 'paperscript'); code += "\n//# sourceURL=" + (url || 'paperscript');
@ -590,4 +590,4 @@ Base.exports.PaperScript = (function() {
}; };
// Pass on `this` as the binding object, so we can reference Acorn both in // Pass on `this` as the binding object, so we can reference Acorn both in
// development and in the built library. // development and in the built library.
}).call(this); }.call(this);

View file

@ -45,7 +45,7 @@ var Key = new function() {
keyMap = {}, // Map for currently pressed keys keyMap = {}, // Map for currently pressed keys
charMap = {}, // key -> char mappings for pressed keys charMap = {}, // key -> char mappings for pressed keys
metaFixMap, // Keys that will not receive keyup events due to Mac bug metaFixMap, // Keys that will not receive keyup events due to Mac bug
downKey; // The key from the keydown event, if it wasn't handled already downKey, // The key from the keydown event, if it wasn't handled already
// Use new Base() to convert into a Base object, for #toString() // Use new Base() to convert into a Base object, for #toString()
modifiers = new Base({ modifiers = new Base({

View file

@ -30,7 +30,7 @@ var MouseEvent = Event.extend(/** @lends MouseEvent# */{
this.type = type; this.type = type;
this.event = event; this.event = event;
this.point = point; this.point = point;
this._target = target; this.target = target;
this.delta = delta; this.delta = delta;
}, },
@ -51,19 +51,24 @@ var MouseEvent = Event.extend(/** @lends MouseEvent# */{
* @type Point * @type Point
*/ */
// DOCS: document MouseEvent#target
/** /**
* The item that dispatched the event. It is different from
* {@link #currentTarget} when the event handler is called during
* the bubbling phase of the event.
*
* @name MouseEvent#target * @name MouseEvent#target
* @type Item * @type Item
*/ */
getTarget: function() {
// #_target may be a hitTest() function, in which case we need to /**
// execute and override it the first time #target is requested. * The current target for the event, as the event traverses the scene graph.
var target = this._target; * It always refers to the element the event handler has been attached to as
if (typeof target === 'function') * opposed to {@link #target} which identifies the element on
target = this._target = target(); * which the event occurred.
return target; *
}, * @name MouseEvent#currentTarget
* @type Item
*/
// DOCS: document MouseEvent#delta // DOCS: document MouseEvent#delta
/** /**
@ -77,7 +82,7 @@ var MouseEvent = Event.extend(/** @lends MouseEvent# */{
toString: function() { toString: function() {
return "{ type: '" + this.type return "{ type: '" + this.type
+ "', point: " + this.point + "', point: " + this.point
+ ', target: ' + this.getTarget() + ', target: ' + this.target
+ (this.delta ? ', delta: ' + this.delta : '') + (this.delta ? ', delta: ' + this.delta : '')
+ ', modifiers: ' + this.getModifiers() + ', modifiers: ' + this.getModifiers()
+ ' }'; + ' }';

View file

@ -36,7 +36,7 @@ paper = new (PaperScope.inject(Base.exports, {
// - PaperScript support in require() with sourceMaps // - PaperScript support in require() with sourceMaps
// - exportFrames / exportImage on CanvasView // - exportFrames / exportImage on CanvasView
if (paper.agent.node) if (paper.agent.node)
require('./node/extend')(paper); require('./node/extend.js')(paper);
// https://github.com/umdjs/umd // https://github.com/umdjs/umd
if (typeof define === 'function' && define.amd) { if (typeof define === 'function' && define.amd) {

View file

@ -19,11 +19,12 @@
/* global document:true, window:true */ /* global document:true, window:true */
// Create a window variable valid in the paper.js scope, that points to the // 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 // native window in browsers and the emulated JSDom one in node.js
// In workers, window is null (but self is defined), so we can use the validity // In workers, and on Node.js when no Canvas is present, `window` is null (but
// of the local window object to detect a worker-like context in the library. // `self` is defined), so we can use the validity of the local window object to
var window = self ? self.window : require('./node/window'), // detect a worker-like context in the library.
document = window && window.document; // Make sure `self` always points to a window object, also on Node.js.
// Make sure 'self' always points to a window object, also on Node.js. self = self || require('./node/window.js');
// NOTE: We're not modifying the global `self` here. We receive its value passed // 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. // to the paper.js function scope, and this is the one that is modified here.
self = self || window; var window = self.window,
document = self.document;

View file

@ -1893,6 +1893,7 @@ new function() { // Injection scope for hit-test functions shared with project
|| options.class && !(this instanceof options.class)), || options.class && !(this instanceof options.class)),
callback = options.match, callback = options.match,
that = this, that = this,
bounds,
res; res;
function match(hit) { function match(hit) {
@ -1913,7 +1914,7 @@ new function() { // Injection scope for hit-test functions shared with project
if (checkSelf && (options.center || options.bounds) && this._parent) { if (checkSelf && (options.center || options.bounds) && this._parent) {
// Don't get the transformed bounds, check against transformed // Don't get the transformed bounds, check against transformed
// points instead // points instead
var bounds = this.getInternalBounds(); bounds = this.getInternalBounds();
if (options.center) { if (options.center) {
res = checkBounds('center', 'Center'); res = checkBounds('center', 'Center');
} }

View file

@ -379,7 +379,7 @@ var Raster = Item.extend(/** @lends Raster# */{
}, },
setSource: function(src) { setSource: function(src) {
var image = new window.Image(), var image = new self.Image(),
crossOrigin = this._crossOrigin; crossOrigin = this._crossOrigin;
if (crossOrigin) if (crossOrigin)
image.crossOrigin = crossOrigin; image.crossOrigin = crossOrigin;

View file

@ -13,7 +13,7 @@
var Http = { var Http = {
request: function(options) { request: function(options) {
// Code borrowed from Coffee Script and extended: // Code borrowed from Coffee Script and extended:
var xhr = new window.XMLHttpRequest(); var xhr = new self.XMLHttpRequest();
xhr.open((options.method || 'get').toUpperCase(), options.url, xhr.open((options.method || 'get').toUpperCase(), options.url,
Base.pick(options.async, true)); Base.pick(options.async, true));
if (options.mimeType) if (options.mimeType)

View file

@ -10,35 +10,41 @@
* All rights reserved. * All rights reserved.
*/ */
var Canvas = require('canvas'),
idlUtils = require('jsdom/lib/jsdom/living/generated/utils');
// Add some useful extensions to HTMLCanvasElement: // Add some useful extensions to HTMLCanvasElement:
// - HTMLCanvasElement#type, so we can switch to a PDF canvas // - HTMLCanvasElement#type, so we can switch to a PDF canvas
// - Various Node Canvas methods, routed through from HTMLCanvasElement: // - Various Node Canvas methods, routed through from HTMLCanvasElement:
// toBuffer, pngStream, createPNGStream, jpgStream, createJPGStream // toBuffer, pngStream, createPNGStream, jpgStream, createJPGStream
module.exports = function(window) { module.exports = function(window) {
var HTMLCanvasElement = window.HTMLCanvasElement; var Canvas;
try {
function getImplementation(obj) { Canvas = require('canvas');
// Try implForWrapper() first, fall back on obj. This appears to be } catch(e) {
// necessary on v7.2.2, but not anymore once we can switch to 8.0.0 // Remove `self.window`, so we still have the global `self` reference,
var impl = idlUtils.implForWrapper(obj); // but no `window` object:
return impl && impl._canvas ? impl : obj; // - 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.');
return;
} }
var idlUtils = require('jsdom/lib/jsdom/living/generated/utils'),
HTMLCanvasElement = window.HTMLCanvasElement;
// Add fake HTMLCanvasElement#type property: // Add fake HTMLCanvasElement#type property:
Object.defineProperty(HTMLCanvasElement.prototype, 'type', { Object.defineProperty(HTMLCanvasElement.prototype, 'type', {
get: function() { get: function() {
var canvas = getImplementation(this)._canvas; var canvas = idlUtils.implForWrapper(this)._canvas;
return canvas && canvas.type || 'image'; return canvas && canvas.type || 'image';
}, },
set: function(type) { set: function(type) {
// Allow replacement of internal node-canvas, so we can switch to a // Allow replacement of internal node-canvas, so we can switch to a
// PDF canvas. // PDF canvas.
var impl = getImplementation(this), var impl = idlUtils.implForWrapper(this),
size = impl._canvas || impl; size = impl._canvas || impl;
impl._canvas = new Canvas(size.width, size.height, type); impl._canvas = new Canvas(size.width, size.height, type);
impl._context = null; impl._context = null;
@ -49,7 +55,7 @@ module.exports = function(window) {
['toBuffer', 'pngStream', 'createPNGStream', 'jpgStream', 'createJPGStream'] ['toBuffer', 'pngStream', 'createPNGStream', 'jpgStream', 'createJPGStream']
.forEach(function(key) { .forEach(function(key) {
HTMLCanvasElement.prototype[key] = function() { HTMLCanvasElement.prototype[key] = function() {
var canvas = getImplementation(this)._canvas; var canvas = idlUtils.implForWrapper(this)._canvas;
return canvas[key].apply(canvas, arguments); return canvas[key].apply(canvas, arguments);
}; };
}); });

View file

@ -27,7 +27,7 @@ var document = jsdom.jsdom('<html><body></body></html>', {
}), }),
window = document.defaultView; window = document.defaultView;
require('./canvas')(window); require('./canvas.js')(window);
// Define XMLSerializer and DOMParser shims, to emulate browser behavior. // Define XMLSerializer and DOMParser shims, to emulate browser behavior.
// Effort to bring this to jsdom: https://github.com/tmpvar/jsdom/issues/1368 // Effort to bring this to jsdom: https://github.com/tmpvar/jsdom/issues/1368
@ -52,25 +52,6 @@ XMLSerializer.prototype.serializeToString = function(node) {
return text; return text;
}; };
function DOMParser() {
}
DOMParser.prototype.parseFromString = function(string, contentType) {
// Create a new document, since we're supposed to always return one.
var doc = document.implementation.createHTMLDocument(''),
body = doc.body,
last;
// Set the body's HTML, then change the DOM according the specs.
body.innerHTML = string;
// Remove all top-level children (<html><head/><body/></html>)
while (last = doc.lastChild)
doc.removeChild(last);
// Insert the first child of the body at the top.
doc.appendChild(body.firstChild);
return doc;
};
window.XMLSerializer = XMLSerializer; window.XMLSerializer = XMLSerializer;
window.DOMParser = DOMParser;
module.exports = window; module.exports = window;

View file

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

View file

@ -123,4 +123,4 @@ var paper = function(self, undefined) {
/*#*/ include('export.js'); /*#*/ include('export.js');
return paper; return paper;
}(typeof self === 'object' ? self : null); }.call(this, typeof self === 'object' ? self : null);

View file

@ -170,7 +170,32 @@ var CompoundPath = PathItem.extend(/** @lends CompoundPath# */{
}, },
/** /**
* The first Segment contained within the path. * Specifies whether the compound-path is fully closed, meaning all its
* contained sub-paths are closed path.
*
* @bean
* @type Boolean
* @see Path#isClosed()
*/
isClosed: function() {
var children = this._children;
for (var i = 0, l = children.length; i < l; i++) {
if (!children[i]._closed)
return false;
}
return true;
},
setClosed: function(closed) {
var children = this._children;
for (var i = 0, l = children.length; i < l; i++) {
children[i].setClosed(closed);
}
},
/**
* The first Segment contained within the compound-path, a short-cut to
* calling {@link Path#getFirstSegment()} on {@link Item#getFirstChild()}.
* *
* @bean * @bean
* @type Segment * @type Segment
@ -181,7 +206,8 @@ var CompoundPath = PathItem.extend(/** @lends CompoundPath# */{
}, },
/** /**
* The last Segment contained within the path. * The last Segment contained within the compound-path, a short-cut to
* calling {@link Path#getLastSegment()} on {@link Item#getLastChild()}.
* *
* @bean * @bean
* @type Segment * @type Segment
@ -207,7 +233,8 @@ var CompoundPath = PathItem.extend(/** @lends CompoundPath# */{
}, },
/** /**
* The first Curve contained within the path. * The first Curve contained within the compound-path, a short-cut to
* calling {@link Path#getFirstCurve()} on {@link Item#getFirstChild()}.
* *
* @bean * @bean
* @type Curve * @type Curve
@ -218,14 +245,15 @@ var CompoundPath = PathItem.extend(/** @lends CompoundPath# */{
}, },
/** /**
* The last Curve contained within the path. * The last Curve contained within the compound-path, a short-cut to
* calling {@link Path#getLastCurve()} on {@link Item#getLastChild()}.
* *
* @bean * @bean
* @type Curve * @type Curve
*/ */
getLastCurve: function() { getLastCurve: function() {
var last = this.getLastChild(); var last = this.getLastChild();
return last && last.getFirstCurve(); return last && last.getLastCurve();
}, },
/** /**

View file

@ -680,10 +680,16 @@ statics: /** @lends Curve */{
c1 = v[coord + 2], c1 = v[coord + 2],
c2 = v[coord + 4], c2 = v[coord + 4],
p2 = v[coord + 6], p2 = v[coord + 6],
c = 3 * (c1 - p1), res = 0;
b = 3 * (c2 - c1) - c, // If val is outside the curve values, no solution is possible.
a = p2 - p1 - c - b; if ( !(p1 < val && p2 < val && c1 < val && c2 < val ||
return Numerical.solveCubic(a, b, c, p1 - val, roots, min, max); p1 > val && p2 > val && c1 > val && c2 > val)) {
var c = 3 * (c1 - p1),
b = 3 * (c2 - c1) - c,
a = p2 - p1 - c - b;
res = Numerical.solveCubic(a, b, c, p1 - val, roots, min, max);
}
return res;
}, },
getTimeOf: function(v, point) { getTimeOf: function(v, point) {
@ -835,7 +841,6 @@ statics: /** @lends Curve */{
* NOTE: padding is only used for Path.getBounds(). * NOTE: padding is only used for Path.getBounds().
*/ */
_addBounds: function(v0, v1, v2, v3, coord, padding, min, max, roots) { _addBounds: function(v0, v1, v2, v3, coord, padding, min, max, roots) {
padding /= 2; // strokePadding is in width, not radius
// Code ported and further optimised from: // Code ported and further optimised from:
// http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html // http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html
function add(value, padding) { function add(value, padding) {
@ -846,32 +851,49 @@ statics: /** @lends Curve */{
if (right > max[coord]) if (right > max[coord])
max[coord] = right; max[coord] = right;
} }
// Calculate derivative of our bezier polynomial, divided by 3.
// Doing so allows for simpler calculations of a, b, c and leads to the padding /= 2; // strokePadding is in width, not radius
// same quadratic roots. var minPad = min[coord] - padding,
var a = 3 * (v1 - v2) - v0 + v3, maxPad = max[coord] + padding;
b = 2 * (v0 + v2) - 4 * v1, // Perform a rough bounds checking first: The curve can only extend the
c = v1 - v0, // current bounds if at least one value is outside the min-max range.
count = Numerical.solveQuadratic(a, b, c, roots), if ( v0 < minPad || v1 < minPad || v2 < minPad || v3 < minPad ||
// Add some tolerance for good roots, as t = 0, 1 are added v0 > maxPad || v1 > maxPad || v2 > maxPad || v3 > maxPad) {
// separately anyhow, and we don't want joins to be added with radii if (v1 < v0 != v1 < v3 && v2 < v0 != v2 < v3) {
// in getStrokeBounds() // If the values of a curve are sorted, the extrema are simply
tMin = /*#=*/Numerical.CURVETIME_EPSILON, // the start and end point.
tMax = 1 - tMin; add(v0, padding);
// Only add strokeWidth to bounds for points which lie within 0 < t < 1 add(v3, padding);
// The corner cases for cap and join are handled in getStrokeBounds() } else {
add(v3, 0); // Calculate derivative of our bezier polynomial, divided by 3.
for (var i = 0; i < count; i++) { // Doing so allows for simpler calculations of a, b, c and leads
var t = roots[i], // to the same quadratic roots.
u = 1 - t; var a = 3 * (v1 - v2) - v0 + v3,
// Test for good roots and only add to bounds if good. b = 2 * (v0 + v2) - 4 * v1,
if (tMin < t && t < tMax) c = v1 - v0,
// Calculate bezier polynomial at t. count = Numerical.solveQuadratic(a, b, c, roots),
add(u * u * u * v0 // Add some tolerance for good roots, as t = 0, 1 are added
+ 3 * u * u * t * v1 // separately anyhow, and we don't want joins to be added
+ 3 * u * t * t * v2 // with radii in getStrokeBounds()
+ t * t * t * v3, tMin = /*#=*/Numerical.CURVETIME_EPSILON,
padding); tMax = 1 - tMin;
// Only add strokeWidth to bounds for points which lie within 0
// < t < 1 The corner cases for cap and join are handled in
// getStrokeBounds()
add(v3, 0);
for (var i = 0; i < count; i++) {
var t = roots[i],
u = 1 - t;
// Test for good roots and only add to bounds if good.
if (tMin < t && t < tMax)
// Calculate bezier polynomial at t.
add(u * u * u * v0
+ 3 * u * u * t * v1
+ 3 * u * t * t * v2
+ t * t * t * v3,
padding);
}
}
} }
} }
}}, Base.each( }}, Base.each(

View file

@ -1284,6 +1284,13 @@ var Path = PathItem.extend(/** @lends Path# */{
// NOTE: Documentation is in PathItem#smooth() // NOTE: Documentation is in PathItem#smooth()
smooth: function(options) { smooth: function(options) {
var that = this,
opts = options || {},
type = opts.type || 'asymmetric',
segments = this._segments,
length = segments.length,
closed = this._closed;
// Helper method to pick the right from / to indices. // Helper method to pick the right from / to indices.
// Supports numbers and segment objects. // Supports numbers and segment objects.
// For numbers, the `to` index is exclusive, while for segments and // For numbers, the `to` index is exclusive, while for segments and
@ -1312,15 +1319,10 @@ var Path = PathItem.extend(/** @lends Path# */{
: index < 0 ? index + length : index, length - 1); : index < 0 ? index + length : index, length - 1);
} }
var that = this, var loop = closed && opts.from === undefined && opts.to === undefined,
opts = options || {},
type = opts.type || 'asymmetric',
segments = this._segments,
length = segments.length,
closed = this._closed,
loop = closed && opts.from === undefined && opts.to === undefined,
from = getIndex(opts.from, 0), from = getIndex(opts.from, 0),
to = getIndex(opts.to, length - 1); to = getIndex(opts.to, length - 1);
if (from > to) { if (from > to) {
if (closed) { if (closed) {
from -= length; from -= length;
@ -2047,7 +2049,9 @@ new function() { // Scope for drawing
// performance. // performance.
function drawHandles(ctx, segments, matrix, size) { function drawHandles(ctx, segments, matrix, size) {
var half = size / 2; var half = size / 2,
coords = new Array(6),
pX, pY;
function drawHandle(index) { function drawHandle(index) {
var hX = coords[index], var hX = coords[index],
@ -2063,13 +2067,12 @@ new function() { // Scope for drawing
} }
} }
var coords = new Array(6);
for (var i = 0, l = segments.length; i < l; i++) { for (var i = 0, l = segments.length; i < l; i++) {
var segment = segments[i]; var segment = segments[i],
selection = segment._selection;
segment._transformCoordinates(matrix, coords); segment._transformCoordinates(matrix, coords);
var selection = segment._selection, pX = coords[0];
pX = coords[0], pY = coords[1];
pY = coords[1];
if (selection & /*#=*/SegmentSelection.HANDLE_IN) if (selection & /*#=*/SegmentSelection.HANDLE_IN)
drawHandle(2); drawHandle(2);
if (selection & /*#=*/SegmentSelection.HANDLE_OUT) if (selection & /*#=*/SegmentSelection.HANDLE_OUT)
@ -2699,22 +2702,19 @@ statics: {
_addBevelJoin: function(segment, join, radius, miterLimit, matrix, _addBevelJoin: function(segment, join, radius, miterLimit, matrix,
strokeMatrix, addPoint, isArea) { strokeMatrix, addPoint, isArea) {
// Handles both 'bevel' and 'miter' joins, as they share a lot of code. // Handles both 'bevel' and 'miter' joins, as they share a lot of code,
// using different matrices to transform segment points and stroke
// vectors to support Style#strokeScaling.
var curve2 = segment.getCurve(), var curve2 = segment.getCurve(),
curve1 = curve2.getPrevious(), curve1 = curve2.getPrevious(),
point = curve2.getPointAtTime(0), point = curve2.getPoint1().transform(matrix),
normal1 = curve1.getNormalAtTime(1), normal1 = curve1.getNormalAtTime(1).multiply(radius)
normal2 = curve2.getNormalAtTime(0), .transform(strokeMatrix),
step = normal1.getDirectedAngle(normal2) < 0 ? -radius : radius; normal2 = curve2.getNormalAtTime(0).multiply(radius)
normal1.setLength(step); .transform(strokeMatrix);
normal2.setLength(step); if (normal1.getDirectedAngle(normal2) < 0) {
// use different matrices to transform segment points and stroke vectors normal1 = normal1.negate();
// to support Style#strokeScaling. normal2 = normal2.negate();
if (matrix)
matrix._transformPoint(point, point);
if (strokeMatrix) {
strokeMatrix._transformPoint(normal1, normal1);
strokeMatrix._transformPoint(normal2, normal2);
} }
if (isArea) { if (isArea) {
addPoint(point); addPoint(point);
@ -2744,16 +2744,13 @@ statics: {
_addSquareCap: function(segment, cap, radius, matrix, strokeMatrix, _addSquareCap: function(segment, cap, radius, matrix, strokeMatrix,
addPoint, isArea) { addPoint, isArea) {
// Handles both 'square' and 'butt' caps, as they share a lot of code. // Handles both 'square' and 'butt' caps, as they share a lot of code.
// Calculate the corner points of butt and square caps // Calculate the corner points of butt and square caps, using different
var point = segment._point, // matrices to transform segment points and stroke vectors to support
// Style#strokeScaling.
var point = segment._point.transform(matrix),
loc = segment.getLocation(), loc = segment.getLocation(),
normal = loc.getNormal().multiply(radius); // normal is normalized // NOTE: normal is normalized, so multiply instead of normalize.
// use different matrices to transform segment points and stroke vectors normal = loc.getNormal().multiply(radius).transform(strokeMatrix);
// to support Style#strokeScaling.
if (matrix)
matrix._transformPoint(point, point);
if (strokeMatrix)
strokeMatrix._transformPoint(normal, normal);
if (isArea) { if (isArea) {
addPoint(point.subtract(normal)); addPoint(point.subtract(normal));
addPoint(point.add(normal)); addPoint(point.add(normal));

View file

@ -47,10 +47,12 @@ PathItem.inject(new function() {
* remove empty curves, #resolveCrossings() to resolve self-intersection * remove empty curves, #resolveCrossings() to resolve self-intersection
* make sure all paths have correct winding direction. * make sure all paths have correct winding direction.
*/ */
function preparePath(path, resolve) { function preparePath(path, closed) {
var res = path.clone(false).reduce({ simplify: true }) var res = path.clone(false).reduce({ simplify: true })
.transform(null, true, true); .transform(null, true, true);
return resolve ? res.resolveCrossings() : res; if (closed)
res.setClosed(true);
return closed ? res.resolveCrossings() : res;
} }
function createResult(ctor, paths, reduce, path1, path2) { function createResult(ctor, paths, reduce, path1, path2) {
@ -74,8 +76,10 @@ PathItem.inject(new function() {
// Add a simple boolean property to check for a given operation, // Add a simple boolean property to check for a given operation,
// e.g. `if (operator.unite)` // e.g. `if (operator.unite)`
operator[operation] = true; operator[operation] = true;
// If path1 is open, delegate to computeOpenBoolean() // If path1 is open, delegate to computeOpenBoolean().
if (!path1._children && !path1._closed) // NOTE: Do not access private _closed property here, since path1 may
// be a CompoundPath.
if (!path1.isClosed())
return computeOpenBoolean(path1, path2, operator); return computeOpenBoolean(path1, path2, operator);
// We do not modify the operands themselves, but create copies instead, // We do not modify the operands themselves, but create copies instead,
// fas produced by the calls to preparePath(). // fas produced by the calls to preparePath().
@ -139,11 +143,11 @@ PathItem.inject(new function() {
function computeOpenBoolean(path1, path2, operator) { function computeOpenBoolean(path1, path2, operator) {
// Only support subtract and intersect operations between an open // Only support subtract and intersect operations between an open
// and a closed path. Assume that compound-paths are closed. // and a closed path.
// TODO: Should we complain about not supported operations? if (!path2 || !operator.subtract && !operator.intersect) {
if (!path2 || !path2._children && !path2._closed throw new Error('Boolean operations on open paths only support ' +
|| !operator.subtract && !operator.intersect) 'subtraction and intersection with another path.');
return null; }
var _path1 = preparePath(path1, false), var _path1 = preparePath(path1, false),
_path2 = preparePath(path2, false), _path2 = preparePath(path2, false),
crossings = _path1.getCrossings(_path2), crossings = _path1.getCrossings(_path2),
@ -612,7 +616,7 @@ PathItem.inject(new function() {
// are bringing us back to the beginning, and are both valid, // are bringing us back to the beginning, and are both valid,
// meaning they are part of the boolean result. // meaning they are part of the boolean result.
if (seg !== exclude && (isStart(seg) || isStart(nextSeg) if (seg !== exclude && (isStart(seg) || isStart(nextSeg)
|| !seg._visited && !nextSeg._visited || nextSeg && !seg._visited && !nextSeg._visited
// Self-intersections (!operator) don't need isValid() calls // Self-intersections (!operator) don't need isValid() calls
&& (!operator || isValid(seg) && (isValid(nextSeg) && (!operator || isValid(seg) && (isValid(nextSeg)
// If the next segment isn't valid, its intersection // If the next segment isn't valid, its intersection

View file

@ -99,8 +99,10 @@ var PathItem = Item.extend(/** @lends PathItem# */{
coords = part.match(/[+-]?(?:\d*\.\d+|\d+\.?)(?:[eE][+-]?\d+)?/g); coords = part.match(/[+-]?(?:\d*\.\d+|\d+\.?)(?:[eE][+-]?\d+)?/g);
var length = coords && coords.length; var length = coords && coords.length;
relative = command === lower; relative = command === lower;
// Fix issues with z in the middle of SVG path data, not followed by
// a m command, see #413:
if (previous === 'z' && !/[mz]/.test(lower)) if (previous === 'z' && !/[mz]/.test(lower))
this.moveTo(current = start); this.moveTo(current);
switch (lower) { switch (lower) {
case 'm': case 'm':
case 'l': case 'l':
@ -170,6 +172,8 @@ var PathItem = Item.extend(/** @lends PathItem# */{
// Merge first and last segment with Numerical.EPSILON tolerance // Merge first and last segment with Numerical.EPSILON tolerance
// to address imprecisions in relative SVG data. // to address imprecisions in relative SVG data.
this.closePath(/*#=*/Numerical.EPSILON); this.closePath(/*#=*/Numerical.EPSILON);
// Correctly handle relative m commands, see #1101:
current = start;
break; break;
} }
previous = lower; previous = lower;
@ -184,7 +188,7 @@ var PathItem = Item.extend(/** @lends PathItem# */{
_contains: function(point) { _contains: function(point) {
// NOTE: point is reverse transformed by _matrix, so we don't need to // NOTE: point is reverse transformed by _matrix, so we don't need to
// apply here. // apply the matrix here.
/*#*/ if (__options.nativeContains || !__options.booleanOperations) { /*#*/ if (__options.nativeContains || !__options.booleanOperations) {
// To compare with native canvas approach: // To compare with native canvas approach:
var ctx = CanvasProvider.getContext(1, 1); var ctx = CanvasProvider.getContext(1, 1);

View file

@ -130,7 +130,7 @@ var Segment = Base.extend(/** @lends Segment# */{
} else { } else {
point = arg0; point = arg0;
} }
} else if (typeof arg0 === 'object') { } else if (arg0 == null || typeof arg0 === 'object') {
// It doesn't matter if all of these arguments exist. // It doesn't matter if all of these arguments exist.
// new SegmentPoint() produces creates points with (0, 0) otherwise. // new SegmentPoint() produces creates points with (0, 0) otherwise.
point = arg0; point = arg0;

View file

@ -171,7 +171,9 @@ var Style = Base.extend(new function() {
var old = this._values[key]; var old = this._values[key];
if (old !== value) { if (old !== value) {
if (isColor) { if (isColor) {
if (old) // The old value may be a native string or other color
// description that wasn't coerced to a color object yet
if (old && old._owner !== undefined)
old._owner = undefined; old._owner = undefined;
if (value && value.constructor === Color) { if (value && value.constructor === Color) {
// Clone color if it already has an owner. // Clone color if it already has an owner.

View file

@ -369,7 +369,7 @@ new function() {
definitions = null; definitions = null;
} }
return options.asString return options.asString
? new window.XMLSerializer().serializeToString(svg) ? new self.XMLSerializer().serializeToString(svg)
: svg; : svg;
} }

View file

@ -19,7 +19,8 @@ new function() {
// objects, dealing with baseVal, and item lists. // objects, dealing with baseVal, and item lists.
// index is option, and if passed, causes a lookup in a list. // index is option, and if passed, causes a lookup in a list.
var rootSize; var definitions = {},
rootSize;
function getValue(node, name, isString, allowNull, allowPercent) { function getValue(node, name, isString, allowNull, allowPercent) {
// Interpret value as number. Never return NaN, but 0 instead. // Interpret value as number. Never return NaN, but 0 instead.
@ -547,16 +548,17 @@ new function() {
return item; return item;
} }
var definitions = {};
function getDefinition(value) { function getDefinition(value) {
// When url() comes from a style property, '#'' seems to be missing on // When url() comes from a style property, '#'' seems to be missing on
// WebKit. We also get variations of quotes or no quotes, single or // WebKit. We also get variations of quotes or no quotes, single or
// double, so handle it all with one regular expression: // double, so handle it all with one regular expression:
var match = value && value.match(/\((?:["'#]*)([^"')]+)/), var match = value && value.match(/\((?:["'#]*)([^"')]+)/),
res = match && definitions[match[1] name = match && match[1],
// This is required by Firefox, which can produce absolute urls res = name && definitions[window
// for local gradients, see #1001: // This is required by Firefox, which can produce absolute
.replace(window.location.href.split('#')[0] + '#', '')]; // urls for local gradients, see #1001:
? name.replace(window.location.href.split('#')[0] + '#', '')
: name];
// Patch in support for SVG's gradientUnits="objectBoundingBox" through // Patch in support for SVG's gradientUnits="objectBoundingBox" through
// Color#_scaleToBounds // Color#_scaleToBounds
if (res && res._scaleToBounds) { if (res && res._scaleToBounds) {
@ -659,7 +661,7 @@ new function() {
function onLoad(svg) { function onLoad(svg) {
try { try {
var node = typeof svg === 'object' ? svg : new window.DOMParser() var node = typeof svg === 'object' ? svg : new self.DOMParser()
.parseFromString(svg, 'image/svg+xml'); .parseFromString(svg, 'image/svg+xml');
if (!node.nodeName) { if (!node.nodeName) {
node = null; node = null;

View file

@ -60,6 +60,11 @@ var Numerical = new function() {
var abs = Math.abs, var abs = Math.abs,
sqrt = Math.sqrt, sqrt = Math.sqrt,
pow = Math.pow, pow = Math.pow,
// Fallback to polyfill:
log2 = Math.log2 || function(x) {
return Math.log(x) * Math.LOG2E;
},
// Constants
EPSILON = 1e-12, EPSILON = 1e-12,
MACHINE_EPSILON = 1.12e-16; MACHINE_EPSILON = 1.12e-16;
@ -67,6 +72,44 @@ var Numerical = new function() {
return value < min ? min : value > max ? max : value; return value < min ? min : value > max ? max : value;
} }
function getDiscriminant(a, b, c) {
// Ported from @hkrish's polysolve.c
function split(v) {
var x = v * 134217729,
y = v - x,
hi = y + x, // Don't optimize y away!
lo = v - hi;
return [hi, lo];
}
var D = b * b - a * c,
E = b * b + a * c;
if (abs(D) * 3 < E) {
var ad = split(a),
bd = split(b),
cd = split(c),
p = b * b,
dp = (bd[0] * bd[0] - p + 2 * bd[0] * bd[1]) + bd[1] * bd[1],
q = a * c,
dq = (ad[0] * cd[0] - q + ad[0] * cd[1] + ad[1] * cd[0])
+ ad[1] * cd[1];
D = (p - q) + (dp - dq); // Dont omit parentheses!
}
return D;
}
function getNormalizationFactor() {
// Normalize coefficients à la Jenkins & Traub's RPOLY.
// Normalization is done by scaling coefficients with a power of 2, so
// that all the bits in the mantissa remain unchanged.
// Use the infinity norm (max(sum(abs(a)…)) to determine the appropriate
// scale factor. See @hkrish in #1087#issuecomment-231526156
var norm = Math.max.apply(Math, arguments);
return norm && (norm < 1e-8 || norm > 1e8)
? pow(2, -Math.round(log2(norm)))
: 0;
}
return /** @lends Numerical */{ return /** @lends Numerical */{
TOLERANCE: 1e-6, TOLERANCE: 1e-6,
/** /**
@ -202,6 +245,8 @@ var Numerical = new function() {
* Kahan W. - "To Solve a Real Cubic Equation" * Kahan W. - "To Solve a Real Cubic Equation"
* http://www.cs.berkeley.edu/~wkahan/Math128/Cubic.pdf * http://www.cs.berkeley.edu/~wkahan/Math128/Cubic.pdf
* Blinn J. - "How to solve a Quadratic Equation" * Blinn J. - "How to solve a Quadratic Equation"
* Harikrishnan G.
* https://gist.github.com/hkrish/9e0de1f121971ee0fbab281f5c986de9
* *
* @param {Number} a the quadratic term * @param {Number} a the quadratic term
* @param {Number} b the linear term * @param {Number} b the linear term
@ -215,58 +260,52 @@ var Numerical = new function() {
* @author Harikrishnan Gopalakrishnan <hari.exeption@gmail.com> * @author Harikrishnan Gopalakrishnan <hari.exeption@gmail.com>
*/ */
solveQuadratic: function(a, b, c, roots, min, max) { solveQuadratic: function(a, b, c, roots, min, max) {
var count = 0, var x1, x2 = Infinity;
eMin = min - EPSILON,
eMax = max + EPSILON,
x1, x2 = Infinity,
B = b,
D;
// a, b, c are expected to be the coefficients of the equation:
// Ax² - 2Bx + C == 0, so we take b = -B/2:
b /= -2;
D = b * b - a * c; // Discriminant
// If the discriminant is very small, we can try to pre-condition
// the coefficients, so that we may get better accuracy
if (D !== 0 && abs(D) < MACHINE_EPSILON) {
// If the geometric mean of the coefficients is small enough
var gmC = pow(abs(a * b * c), 1 / 3);
if (gmC < 1e-8) {
// We multiply with a factor to normalize the coefficients.
// The factor is just the nearest exponent of 10, big enough
// to raise all the coefficients to nearly [-1, +1] range.
var mult = gmC === 0 ? 0 : pow(10,
abs(Math.floor(Math.log(gmC) * Math.LOG10E)));
a *= mult;
b *= mult;
c *= mult;
// Recalculate the discriminant
D = b * b - a * c;
}
}
if (abs(a) < EPSILON) { if (abs(a) < EPSILON) {
// This could just be a linear equation // This could just be a linear equation
if (abs(B) < EPSILON) if (abs(b) < EPSILON)
return abs(c) < EPSILON ? -1 : 0; return abs(c) < EPSILON ? -1 : 0;
x1 = -c / B; x1 = -c / b;
} else if (D >= -MACHINE_EPSILON) { // No real roots if D < 0 } else {
var Q = D < 0 ? 0 : sqrt(D), // a, b, c are expected to be the coefficients of the equation:
R = b + (b < 0 ? -Q : Q); // Ax² - 2Bx + C == 0, so we take b = -b/2:
// Try to minimize floating point noise. b *= -0.5;
if (R === 0) { var D = getDiscriminant(a, b, c);
x1 = c / a; // If the discriminant is very small, we can try to normalize
x2 = -x1; // the coefficients, so that we may get better accuracy.
} else { if (D && abs(D) < MACHINE_EPSILON) {
x1 = R / a; var f = getNormalizationFactor(abs(a), abs(b), abs(c));
x2 = c / R; if (f) {
a *= f;
b *= f;
c *= f;
D = getDiscriminant(a, b, c);
}
}
if (D >= -MACHINE_EPSILON) { // No real roots if D < 0
var Q = D < 0 ? 0 : sqrt(D),
R = b + (b < 0 ? -Q : Q);
// Try to minimize floating point noise.
if (R === 0) {
x1 = c / a;
x2 = -x1;
} else {
x1 = R / a;
x2 = c / R;
}
} }
} }
var count = 0,
boundless = min == null,
minB = min - EPSILON,
maxB = max + EPSILON;
// We need to include EPSILON in the comparisons with min / max, // We need to include EPSILON in the comparisons with min / max,
// as some solutions are ever so lightly out of bounds. // as some solutions are ever so lightly out of bounds.
if (isFinite(x1) && (min == null || x1 > eMin && x1 < eMax)) if (isFinite(x1) && (boundless || x1 > minB && x1 < maxB))
roots[count++] = min == null ? x1 : clamp(x1, min, max); roots[count++] = boundless ? x1 : clamp(x1, min, max);
if (x2 !== x1 if (x2 !== x1
&& isFinite(x2) && (min == null || x2 > eMin && x2 < eMax)) && isFinite(x2) && (boundless || x2 > minB && x2 < maxB))
roots[count++] = min == null ? x2 : clamp(x2, min, max); roots[count++] = boundless ? x2 : clamp(x2, min, max);
return count; return count;
}, },
@ -283,6 +322,8 @@ var Numerical = new function() {
* References: * References:
* Kahan W. - "To Solve a Real Cubic Equation" * Kahan W. - "To Solve a Real Cubic Equation"
* http://www.cs.berkeley.edu/~wkahan/Math128/Cubic.pdf * http://www.cs.berkeley.edu/~wkahan/Math128/Cubic.pdf
* Harikrishnan G.
* https://gist.github.com/hkrish/9e0de1f121971ee0fbab281f5c986de9
* *
* W. Kahan's paper contains inferences on accuracy of cubic * W. Kahan's paper contains inferences on accuracy of cubic
* zero-finding methods. Also testing methods for robustness. * zero-finding methods. Also testing methods for robustness.
@ -300,8 +341,25 @@ var Numerical = new function() {
* @author Harikrishnan Gopalakrishnan <hari.exeption@gmail.com> * @author Harikrishnan Gopalakrishnan <hari.exeption@gmail.com>
*/ */
solveCubic: function(a, b, c, d, roots, min, max) { solveCubic: function(a, b, c, d, roots, min, max) {
var count = 0, var f = getNormalizationFactor(abs(a), abs(b), abs(c), abs(d)),
x, b1, c2; x, b1, c2, qd, q;
if (f) {
a *= f;
b *= f;
c *= f;
d *= f;
}
function evaluate(x0) {
x = x0;
// Evaluate q, q', b1 and c2 at x
var tmp = a * x;
b1 = tmp + b;
c2 = b1 * x + c;
qd = (tmp + b1) * x + c2;
q = c2 * x + d;
}
// If a or d is zero, we only need to solve a quadratic, so we set // If a or d is zero, we only need to solve a quadratic, so we set
// the coefficients appropriately. // the coefficients appropriately.
if (abs(a) < EPSILON) { if (abs(a) < EPSILON) {
@ -314,38 +372,24 @@ var Numerical = new function() {
c2 = c; c2 = c;
x = 0; x = 0;
} else { } else {
var ec = 1 + MACHINE_EPSILON, // 1.000...002
x0, q, qd, t, r, s, tmp;
// Here onwards we iterate for the leftmost root. Proceed to // Here onwards we iterate for the leftmost root. Proceed to
// deflate the cubic into a quadratic (as a side effect to the // deflate the cubic into a quadratic (as a side effect to the
// iteration) and solve the quadratic. // iteration) and solve the quadratic.
x = -(b / a) / 3; evaluate(-(b / a) / 3);
// Evaluate q, q', b1 and c2 at x
tmp = a * x;
b1 = tmp + b;
c2 = b1 * x + c;
qd = (tmp + b1) * x + c2;
q = c2 * x + d;
// Get a good initial approximation. // Get a good initial approximation.
t = q / a; var t = q / a,
r = pow(abs(t), 1/3); r = pow(abs(t), 1/3),
s = t < 0 ? -1 : 1; s = t < 0 ? -1 : 1,
t = -qd / a; td = -qd / a,
// See Kahan's notes on why 1.324718*... works. // See Kahan's notes on why 1.324718*... works.
r = t > 0 ? 1.3247179572 * Math.max(r, sqrt(t)) : r; rd = td > 0 ? 1.324717957244746 * Math.max(r, sqrt(td)) : r,
x0 = x - s * r; x0 = x - s * rd;
if (x0 !== x) { if (x0 !== x) {
do { do {
x = x0; evaluate(x0);
// Evaluate q, q', b1 and c2 at x // Newton's. Divide by 1 + MACHINE_EPSILON (1.000...002)
tmp = a * x; // to avoid x0 crossing over a root.
b1 = tmp + b; x0 = qd === 0 ? x : x - q / qd / (1 + MACHINE_EPSILON);
c2 = b1 * x + c;
qd = (tmp + b1) * x + c2;
q = c2 * x + d;
// Newton's. Divide by ec to avoid x0 crossing over a
// root.
x0 = qd === 0 ? x : x - q / qd / ec;
} while (s * x0 > s * x); } while (s * x0 > s * x);
// Adjust the coefficients for the quadratic. // Adjust the coefficients for the quadratic.
if (abs(a) * x * x > abs(d / x)) { if (abs(a) * x * x > abs(d / x)) {
@ -355,10 +399,12 @@ var Numerical = new function() {
} }
} }
// The cubic has been deflated to a quadratic. // The cubic has been deflated to a quadratic.
var count = Numerical.solveQuadratic(a, b1, c2, roots, min, max); var count = Numerical.solveQuadratic(a, b1, c2, roots, min, max),
if (isFinite(x) && (count === 0 || x !== roots[count - 1]) boundless = min == null;
&& (min == null || x > min - EPSILON && x < max + EPSILON)) if (isFinite(x) && (count === 0
roots[count++] = min == null ? x : clamp(x, min, max); || count > 0 && x !== roots[0] && x !== roots[1])
&& (boundless || x > min - EPSILON && x < max + EPSILON))
roots[count++] = boundless ? x : clamp(x, min, max);
return count; return count;
} }
}; };

View file

@ -1152,7 +1152,18 @@ new function() { // Injection scope for event handling on the browser
fallbacks = { fallbacks = {
doubleclick: 'click', doubleclick: 'click',
mousedrag: 'mousemove' mousedrag: 'mousemove'
}; },
// Various variables required by #_handleMouseEvent()
wasInView = false,
overView,
downPoint,
lastPoint,
downItem,
overItem,
dragItem,
clickItem,
clickTime,
dblClick;
// Returns true if event was prevented, false otherwise. // Returns true if event was prevented, false otherwise.
function emitMouseEvent(obj, target, type, event, point, prevPoint, function emitMouseEvent(obj, target, type, event, point, prevPoint,
@ -1196,8 +1207,7 @@ new function() { // Injection scope for event handling on the browser
} }
// Returns true if event was stopped, false otherwise. // Returns true if event was stopped, false otherwise.
function emitMouseEvents(view, hitItem, hitTest, type, event, point, function emitMouseEvents(view, hitItem, type, event, point, prevPoint) {
prevPoint) {
// Before handling events, process removeOn() calls for cleanup. // Before handling events, process removeOn() calls for cleanup.
// NOTE: As soon as there is one event handler receiving mousedrag // NOTE: As soon as there is one event handler receiving mousedrag
// events, non of the removeOnMove() items will be removed while the // events, non of the removeOnMove() items will be removed while the
@ -1219,9 +1229,7 @@ new function() { // Injection scope for event handling on the browser
&& emitMouseEvent(hitItem, null, fallbacks[type] || type, event, && emitMouseEvent(hitItem, null, fallbacks[type] || type, event,
point, prevPoint, dragItem) point, prevPoint, dragItem)
// Lastly handle the mouse events on the view, if we're still here. // Lastly handle the mouse events on the view, if we're still here.
// Choose from the potential targets in the right sequence, with the || emitMouseEvent(view, dragItem || hitItem || view, type, event,
// hitTest() function as the fall-back getter for MouseEvent#target.
|| emitMouseEvent(view, dragItem || hitItem || hitTest, type, event,
point, prevPoint)); point, prevPoint));
} }
@ -1251,20 +1259,6 @@ new function() { // Injection scope for event handling on the browser
} }
}; };
/**
* Various variables required by #_handleMouseEvent()
*/
var downPoint,
lastPoint,
downItem,
overItem,
dragItem,
clickItem,
clickTime,
dblClick,
overView,
wasInView = false;
return { return {
_viewEvents: viewEvents, _viewEvents: viewEvents,
@ -1299,7 +1293,12 @@ new function() { // Injection scope for event handling on the browser
// Run the hit-test on items first, but only if we're required to do // Run the hit-test on items first, but only if we're required to do
// so for this given mouse event, see hitItems, #_countItemEvent(): // so for this given mouse event, see hitItems, #_countItemEvent():
var inView = this.getBounds().contains(point), var inView = this.getBounds().contains(point),
hitItem, hit = hitItems && inView && view._project.hitTest(point, {
tolerance: 0,
fill: true,
stroke: true
}),
hitItem = hit && hit.item || null,
// Keep track if view event should be handled, so we can use it // Keep track if view event should be handled, so we can use it
// to decide if tool._handleMouseEvent() shall be called after. // to decide if tool._handleMouseEvent() shall be called after.
handle = false, handle = false,
@ -1308,28 +1307,6 @@ new function() { // Injection scope for event handling on the browser
// mouse event types. // mouse event types.
mouse[type.substr(5)] = true; mouse[type.substr(5)] = true;
// Provide a hit-test function that makes sure to only perform the
// hit-test once, and only when it's actually required. This method
// is passed to emitMouseEvents() and as target to emitMouseEvent(),
// as the fall-back getter for MouseEvent#target.
function hitTest() {
//
if (hitItem === undefined) {
var hit = inView && view._project.hitTest(point, {
tolerance: 0,
fill: true,
stroke: true
});
hitItem = hit && hit.item || null;
}
// Return the target with view as the fall-back, as expected by
// MouseEvent#target.
return hitItem || view;
}
// Execute hitTest right away if we have events relying on hitItem.
if (hitItems)
hitTest();
// Handle mouseenter / leave between items and views first. // Handle mouseenter / leave between items and views first.
if (hitItems && hitItem !== overItem) { if (hitItems && hitItem !== overItem) {
if (overItem) { if (overItem) {
@ -1355,9 +1332,8 @@ new function() { // Injection scope for event handling on the browser
if ((inView || mouse.drag) && !point.equals(lastPoint)) { if ((inView || mouse.drag) && !point.equals(lastPoint)) {
// Handle mousemove even if this is not actually a mousemove // Handle mousemove even if this is not actually a mousemove
// event but the mouse has moved since the last event. // event but the mouse has moved since the last event.
emitMouseEvents(this, hitItem, hitTest, emitMouseEvents(this, hitItem, nativeMove ? type : 'mousemove',
nativeMove ? type : 'mousemove', event, event, point, lastPoint);
point, lastPoint);
handle = true; handle = true;
} }
wasInView = inView; wasInView = inView;
@ -1365,8 +1341,7 @@ new function() { // Injection scope for event handling on the browser
// We emit mousedown only when in the view, and mouseup regardless, // We emit mousedown only when in the view, and mouseup regardless,
// as long as the mousedown event was inside. // as long as the mousedown event was inside.
if (mouse.down && inView || mouse.up && downPoint) { if (mouse.down && inView || mouse.up && downPoint) {
emitMouseEvents(this, hitItem, hitTest, type, event, emitMouseEvents(this, hitItem, type, event, point, downPoint);
point, downPoint);
if (mouse.down) { if (mouse.down) {
// See if we're clicking again on the same item, within the // See if we're clicking again on the same item, within the
// double-click time. Firefox uses 300ms as the max time // double-click time. Firefox uses 300ms as the max time
@ -1383,9 +1358,8 @@ new function() { // Injection scope for event handling on the browser
// not the view. // not the view.
if (!prevented && hitItem === downItem) { if (!prevented && hitItem === downItem) {
clickTime = Date.now(); clickTime = Date.now();
emitMouseEvents(this, hitItem, hitTest, emitMouseEvents(this, hitItem, dblClick ? 'doubleclick'
dblClick ? 'doubleclick' : 'click', event, : 'click', event, point, downPoint);
point, downPoint);
dblClick = false; dblClick = false;
} }
downItem = dragItem = null; downItem = dragItem = null;

45
test/tests/Numerical.js Normal file
View file

@ -0,0 +1,45 @@
/*
* 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.
*/
QUnit.module('Numerical');
test('Numerical.solveQuadratic()', function() {
function solve(s) {
var roots = [],
count = Numerical.solveQuadratic(s, 0, -s, roots);
return roots;
}
var expected = [1, -1];
equals(solve(1), expected,
'Numerical.solveQuadratic().');
equals(solve(Numerical.EPSILON), expected,
'Numerical.solveQuadratic() with an identical set of' +
'coefficients at different scale.');
});
test('Numerical.solveCubic()', function() {
function solve(s) {
var roots = [],
count = Numerical.solveCubic(0.5 * s, -s, -s, -s, roots);
return roots;
}
var expected = [2.919639565839418];
equals(solve(1), expected,
'Numerical.solveCubic().');
equals(solve(Numerical.EPSILON), expected,
'Numerical.solveCubic() with an identical set of' +
'coefficients at different scale.');
});

View file

@ -17,7 +17,7 @@ function testPoint(item, point, inside, message) {
+ ' should be ' + (inside ? 'inside' : 'outside') + '.')); + ' should be ' + (inside ? 'inside' : 'outside') + '.'));
} }
test('Path#contains() (Regular Polygon)', function() { test('Path#contains() (regular polygon: #208)', function() {
var path = new Path.RegularPolygon([0, 0], 6, 20); var path = new Path.RegularPolygon([0, 0], 6, 20);
testPoint(path, new Point(0, -20), true); testPoint(path, new Point(0, -20), true);
@ -39,7 +39,7 @@ test('Path#contains() (Regular Polygon)', function() {
testPoint(path, new Point(10, 20), false); testPoint(path, new Point(10, 20), false);
}); });
test('Path#contains() (Circle Contours)', function() { test('Path#contains() (circle contours)', function() {
var path = new Path.Circle({ var path = new Path.Circle({
center: [100, 100], center: [100, 100],
radius: 50, radius: 50,
@ -56,7 +56,7 @@ test('Path#contains() (Circle Contours)', function() {
testPoint(path, path.bounds.bottomRight, false); testPoint(path, path.bounds.bottomRight, false);
}); });
test('Path#contains() (Transformed Circle Contours)', function() { test('Path#contains() (transformed circle contours)', function() {
var path = new Path.Circle({ var path = new Path.Circle({
center: [200, 200], center: [200, 200],
radius: 50, radius: 50,
@ -74,7 +74,7 @@ test('Path#contains() (Transformed Circle Contours)', function() {
testPoint(path, path.bounds.bottomRight, false); testPoint(path, path.bounds.bottomRight, false);
}); });
test('Path#contains() (Round Rectangle)', function() { test('Path#contains() (round rectangle: #227)', function() {
var rectangle = new Rectangle({ var rectangle = new Rectangle({
point: new Point(0, 0), point: new Point(0, 0),
size: new Size(200, 40) size: new Size(200, 40)
@ -83,14 +83,14 @@ test('Path#contains() (Round Rectangle)', function() {
testPoint(path, new Point(100, 20), true); testPoint(path, new Point(100, 20), true);
}); });
test('Path#contains() (Open Circle)', function() { test('Path#contains() (open circle)', function() {
var path = new Path.Circle([100, 100], 100); var path = new Path.Circle([100, 100], 100);
path.closed = false; path.closed = false;
path.fillColor = '#ff0000'; path.fillColor = '#ff0000';
testPoint(path, new Point(40, 160), false); testPoint(path, new Point(40, 160), false);
}); });
test('CompoundPath#contains() (Donut)', function() { test('CompoundPath#contains() (donut)', function() {
var path = new CompoundPath([ var path = new CompoundPath([
new Path.Circle([0, 0], 50), new Path.Circle([0, 0], 50),
new Path.Circle([0, 0], 25) new Path.Circle([0, 0], 25)
@ -163,7 +163,7 @@ test('Shape#contains()', function() {
testPoint(shape, new Point(1.1, 0).multiply(half), false); testPoint(shape, new Point(1.1, 0).multiply(half), false);
}); });
test('Path#contains() (Rectangle Contours)', function() { test('Path#contains() (rectangle contours)', function() {
var path = new Path.Rectangle(new Point(100, 100), [200, 200]), var path = new Path.Rectangle(new Point(100, 100), [200, 200]),
curves = path.getCurves(); curves = path.getCurves();
@ -174,7 +174,7 @@ test('Path#contains() (Rectangle Contours)', function() {
}); });
test('Path#contains() (Rotated Rectangle Contours)', function() { test('Path#contains() (rotated rectangle contours)', function() {
var path = new Path.Rectangle(new Point(100, 100), [200, 200]), var path = new Path.Rectangle(new Point(100, 100), [200, 200]),
curves = path.getCurves(); curves = path.getCurves();
@ -200,7 +200,7 @@ test('Path#contains() (touching stationary point with changing orientation)', fu
testPoint(path, new Point(200, 200), true); testPoint(path, new Point(200, 200), true);
}); });
test('Path#contains() (complex shape)', function() { test('Path#contains() (complex shape: #400)', function() {
var path = new Path({ var path = new Path({
pathData: 'M301 162L307 154L315 149L325 139.5L332.5 135.5L341 128.5L357.5 117.5L364.5 114.5L368.5 110.5L380 105.5L390.5 102L404 96L410.5 96L415 97.5L421 104L425.5 113.5L428.5 126L429.5 134L429.5 141L429.5 148L425.5 161.5L425.5 169L414 184.5L409.5 191L401 201L395 209L386 214.5L378.5 217L368 220L348 219.5L338 218L323.5 212.5L312 205.5L302.5 197.5L295.5 189L291.5 171.5L294 168L298 165.5L301 162z', pathData: 'M301 162L307 154L315 149L325 139.5L332.5 135.5L341 128.5L357.5 117.5L364.5 114.5L368.5 110.5L380 105.5L390.5 102L404 96L410.5 96L415 97.5L421 104L425.5 113.5L428.5 126L429.5 134L429.5 141L429.5 148L425.5 161.5L425.5 169L414 184.5L409.5 191L401 201L395 209L386 214.5L378.5 217L368 220L348 219.5L338 218L323.5 212.5L312 205.5L302.5 197.5L295.5 189L291.5 171.5L294 168L298 165.5L301 162z',
fillColor: 'blue', fillColor: 'blue',
@ -215,7 +215,7 @@ test('Path#contains() (complex shape)', function() {
}); });
test('Path#contains() (straight curves with zero-winding)', function() { test('Path#contains() (straight curves with zero-winding: #943)', function() {
var pointData = [ var pointData = [
[[250, 230], true, true, false, true], [[250, 230], true, true, false, true],
[[200, 230], true, true, true, true], [[200, 230], true, true, true, true],
@ -282,7 +282,7 @@ test('Path#contains() (straight curves with zero-winding)', function() {
} }
}); });
test('CompoundPath#contains() (nested touching circles)', function() { test('CompoundPath#contains() (nested touching circles: #944)', function() {
var c1 = new Path.Circle({ var c1 = new Path.Circle({
center: [200, 200], center: [200, 200],
radius: 100 radius: 100

View file

@ -1013,7 +1013,7 @@ test('Isolated edge-cases from @iconexperience\'s boolean-test suite', function(
]]; ]];
var results = [ var results = [
['M240,270l4.48298,-20.84932l-14.48298,0.84932l7.67824,-56.20204l82.32176,46.20204z M237.67824,193.79796z', 'M244.48298,249.15068l6.05168,-28.14496l12.77751,27.04076z'], ['M240,270l4.48298,-20.84932l-14.48298,0.84932l7.67824,-56.20204l82.32176,46.20204z M237.67824,193.79796z', 'M244.48298,249.15068l6.05168,-28.14496l12.77751,27.04076z'],
['M450,230l-87.53067,34.43944l-0.01593,-0.02371c-0.91969,-0.32388 -30.35274,-0.48069 -30.67835,2.89141l-2.76789,-52.67014z', 'M362.46933,264.43944l-0.01593,-0.02371c0.02221,0.00782 0.02779,0.01574 0.01593,0.02371z'], ['M450,230l-87.53067,34.43944l-0.01593,-0.02371l0,0c-0.91969,-0.32388 -30.35274,-0.48069 -30.67835,2.89141l-2.76789,-52.67014z', 'M362.46933,264.43944l-0.01593,-0.02371c0.02221,0.00782 0.02779,0.01574 0.01593,0.02371z'],
['M211.76471,391.55301c-1.13066,-11.28456 3.81977,12.25688 -5.47954,24.76763l-28.00902,-21.41225l-26.21252,2.62636l13.04584,-12.69198l-22.93592,-17.53397l30.159,10.50681l46.32376,-45.06725c-9.58101,10.09791 -8.78302,39.92732 -6.89161,58.80464z', 'M178.27615,394.90839l-13.16668,-10.06562l7.22309,-7.02716l39.43215,13.7374z'], ['M211.76471,391.55301c-1.13066,-11.28456 3.81977,12.25688 -5.47954,24.76763l-28.00902,-21.41225l-26.21252,2.62636l13.04584,-12.69198l-22.93592,-17.53397l30.159,10.50681l46.32376,-45.06725c-9.58101,10.09791 -8.78302,39.92732 -6.89161,58.80464z', 'M178.27615,394.90839l-13.16668,-10.06562l7.22309,-7.02716l39.43215,13.7374z'],
['M138.31417,456.06811c1.62465,-1.18877 -18.69614,16.61617 -34.61033,15.37458l22.34488,-66.7556l-23.16516,-97.04078l209.03396,72.10619c-68.45789,0.5466 -139.96009,51.6986 -173.60335,76.31563z', 'M126.04871,404.68708l21.5947,-64.51445l-9.32924,115.89547z'], ['M138.31417,456.06811c1.62465,-1.18877 -18.69614,16.61617 -34.61033,15.37458l22.34488,-66.7556l-23.16516,-97.04078l209.03396,72.10619c-68.45789,0.5466 -139.96009,51.6986 -173.60335,76.31563z', 'M126.04871,404.68708l21.5947,-64.51445l-9.32924,115.89547z'],
['M300.04122,200l-0.02301,55.81328l19.98178,24.18672l-19.98771,-9.82138l-0.01229,29.82138l-29.04124,-44.09743l-40.1367,-19.722z', 'M300.01822,255.81328l-0.00592,14.36534l-29.05353,-14.27606l-10.39571,-15.78528l29.74019,3.93662z'], ['M300.04122,200l-0.02301,55.81328l19.98178,24.18672l-19.98771,-9.82138l-0.01229,29.82138l-29.04124,-44.09743l-40.1367,-19.722z', 'M300.01822,255.81328l-0.00592,14.36534l-29.05353,-14.27606l-10.39571,-15.78528l29.74019,3.93662z'],
@ -1035,9 +1035,9 @@ test('Isolated edge-cases from @iconexperience\'s boolean-test suite', function(
['M150,290l30,-30l50,10l-24,16l14,14l-50,10l21.17647,-14.11765z', 'M206,286l14,14l-28.82353,-4.11765z'], ['M150,290l30,-30l50,10l-24,16l14,14l-50,10l21.17647,-14.11765z', 'M206,286l14,14l-28.82353,-4.11765z'],
['M324.40827,86.1091l0.45123,-0.29442l-16.06828,-25.33943l22.46895,-3.97479l18.72801,12.91832c4.53222,6.94617 -10.24683,11.75354 -25.05014,16.51976l15.98481,25.2078l-22.46194,3.97305l6.36357,-29.14428c-0.13874,0.04467 -0.27748,0.08933 -0.4162,0.134z M324.40827,86.1091l-24.66974,16.09646c-4.37001,-6.69756 10.04945,-11.38915 24.66974,-16.09646z', 'M324.8595,85.81469l0,0l0,0z M324.93803,85.93854c-0.03785,0.01219 -0.07571,0.02438 -0.11356,0.03656l0.03503,-0.16042z'], ['M324.40827,86.1091l0.45123,-0.29442l-16.06828,-25.33943l22.46895,-3.97479l18.72801,12.91832c4.53222,6.94617 -10.24683,11.75354 -25.05014,16.51976l15.98481,25.2078l-22.46194,3.97305l6.36357,-29.14428c-0.13874,0.04467 -0.27748,0.08933 -0.4162,0.134z M324.40827,86.1091l-24.66974,16.09646c-4.37001,-6.69756 10.04945,-11.38915 24.66974,-16.09646z', 'M324.8595,85.81469l0,0l0,0z M324.93803,85.93854c-0.03785,0.01219 -0.07571,0.02438 -0.11356,0.03656l0.03503,-0.16042z'],
['M388.92139,223.32159c120.27613,-2.24369 208.69681,19.62691 208.69681,19.62691l0.0116,0.00003l0,0l21.89025,-21.07283c0,0 8.54573,10.72909 8.51542,21.16115c-0.03104,10.68629 -208.75655,121.23392 -208.75655,121.23392z', 'M597.62979,242.94854l-21.33555,20.53884l-8.25852,-20.6248z'], ['M388.92139,223.32159c120.27613,-2.24369 208.69681,19.62691 208.69681,19.62691l0.0116,0.00003l0,0l21.89025,-21.07283c0,0 8.54573,10.72909 8.51542,21.16115c-0.03104,10.68629 -208.75655,121.23392 -208.75655,121.23392z', 'M597.62979,242.94854l-21.33555,20.53884l-8.25852,-20.6248z'],
['M272.11155,196.81853l-22.11155,-66.81853l32.64261,64.05832c-6.17669,-1.04412 -8.29,-0.01335 -8.40866,0.04617l-0.00118,-0.00313c-0.09777,0.03858 -0.90866,0.4932 -2.12123,2.71717z', ''], ['M272.11155,196.81853l-22.11155,-66.81853l32.64261,64.05832c-6.17669,-1.04412 -8.29,-0.01335 -8.40866,0.04617l-0.00118,-0.00313c-0.09777,0.03858 -0.90866,0.4932 -2.12123,2.71717z', 'M274.23395,194.10449l-0.00118,-0.00313c0.01275,-0.00503 0.01337,-0.00299 0.00118,0.00313z'],
['M386.40934,270.73027c77.4169,15.77719 129.39044,24.64733 142.22581,18.90659l5.47489,12.24096l0,0l40.60627,23.71608l-21.58416,18.81428z', 'M528.63515,289.63686l5.47489,12.24096l-11.2043,-6.54387c2.3982,-4.10616 5.41393,-5.55599 5.72941,-5.69709z'], ['M386.40934,270.73027c77.4169,15.77719 129.39044,24.64733 142.22581,18.90659l5.47489,12.24096l0,0l40.60627,23.71608l-21.58416,18.81428z', 'M528.63515,289.63686l5.47489,12.24096l-11.2043,-6.54387c2.3982,-4.10616 5.41393,-5.55599 5.72941,-5.69709z'],
['M525,345l32.31797,18.04379l23.72504,-18.37449l6.0793,21.87157l-87.1223,33.45913z', 'M557.31797,363.04379l-23.71188,18.3643l-6.07498,-21.85933z'], ['M525,345l32.31797,18.04379l23.72504,-18.37449l6.0793,21.87157l-87.1223,33.45913z', 'M557.31797,363.04379l0,0l0,0z M557.31797,363.04379l-23.71188,18.3643l-6.07498,-21.85933z'],
['M250,150l-15.25375,32.05024l29.10275,-7.1942z M225.84314,153.10482l-45.84314,36.89518l63.48255,20.45333z', 'M205.60228,189.25463l0,0.2l0.01028,-0.20254z'], ['M250,150l-15.25375,32.05024l29.10275,-7.1942z M225.84314,153.10482l-45.84314,36.89518l63.48255,20.45333z', 'M205.60228,189.25463l0,0.2l0.01028,-0.20254z'],
['M598.5487,408.11025l-42.28034,59.10612l0,0l-37.73535,-87.53938l36.05709,27.56285l0.78705,28.12706z', 'M556.26835,467.21638l-3.83913,-29.98828l2.94792,-1.86119z'], ['M598.5487,408.11025l-42.28034,59.10612l0,0l-37.73535,-87.53938l36.05709,27.56285l0.78705,28.12706z', 'M556.26835,467.21638l-3.83913,-29.98828l2.94792,-1.86119z'],
['M570,290l5.8176,33.58557l-28.17314,-14.439c-14.32289,2.0978 -28.17688,4.12693 -28.17688,4.12693l19.08162,-8.78834l-16.12739,-8.26544l27.9654,2.81326z', 'M538.5492,304.48516l11.83801,-5.45218l9.61279,0.96702l15.8176,23.58557z'], ['M570,290l5.8176,33.58557l-28.17314,-14.439c-14.32289,2.0978 -28.17688,4.12693 -28.17688,4.12693l19.08162,-8.78834l-16.12739,-8.26544l27.9654,2.81326z', 'M538.5492,304.48516l11.83801,-5.45218l9.61279,0.96702l15.8176,23.58557z'],

View file

@ -12,6 +12,11 @@
QUnit.module('Segment'); QUnit.module('Segment');
test('new Segment()', function() {
var segment = new Segment(null, null, null);
equals(segment.toString(), '{ point: { x: 0, y: 0 } }');
});
test('new Segment(point)', function() { test('new Segment(point)', function() {
var segment = new Segment(new Point(10, 10)); var segment = new Segment(new Point(10, 10));
equals(segment.toString(), '{ point: { x: 10, y: 10 } }'); equals(segment.toString(), '{ point: { x: 10, y: 10 } }');
@ -22,6 +27,11 @@ test('new Segment(x, y)', function() {
equals(segment.toString(), '{ point: { x: 10, y: 10 } }'); equals(segment.toString(), '{ point: { x: 10, y: 10 } }');
}); });
test('new Segment(undefined)', function() {
var segment = new Segment(undefined);
equals(segment.toString(), '{ point: { x: 0, y: 0 } }');
});
test('new Segment(object)', function() { test('new Segment(object)', function() {
var segment = new Segment({ point: { x: 10, y: 10 }, handleIn: { x: 5, y: 5 }, handleOut: { x: 15, y: 15 } }); var segment = new Segment({ point: { x: 10, y: 10 }, handleIn: { x: 5, y: 5 }, handleOut: { x: 15, y: 15 } });
equals(segment.toString(), '{ point: { x: 10, y: 10 }, handleIn: { x: 5, y: 5 }, handleOut: { x: 15, y: 15 } }'); equals(segment.toString(), '{ point: { x: 10, y: 10 }, handleIn: { x: 5, y: 5 }, handleOut: { x: 15, y: 15 } }');
@ -32,6 +42,16 @@ test('new Segment(point, handleIn, handleOut)', function() {
equals(segment.toString(), '{ point: { x: 10, y: 10 }, handleIn: { x: 5, y: 5 }, handleOut: { x: 15, y: 15 } }'); equals(segment.toString(), '{ point: { x: 10, y: 10 }, handleIn: { x: 5, y: 5 }, handleOut: { x: 15, y: 15 } }');
}); });
test('new Segment(null, null, null)', function() {
var segment = new Segment(null, null, null);
equals(segment.toString(), '{ point: { x: 0, y: 0 } }');
});
test('new Segment(undefined, null, null)', function() {
var segment = new Segment(undefined, null, null);
equals(segment.toString(), '{ point: { x: 0, y: 0 } }');
});
test('new Segment(x, y, inX, inY, outX, outY)', function() { test('new Segment(x, y, inX, inY, outX, outY)', function() {
var segment = new Segment(10, 10, 5, 5, 15, 15); var segment = new Segment(10, 10, 5, 5, 15, 15);
equals(segment.toString(), '{ point: { x: 10, y: 10 }, handleIn: { x: 5, y: 5 }, handleOut: { x: 15, y: 15 } }'); equals(segment.toString(), '{ point: { x: 10, y: 10 }, handleIn: { x: 5, y: 5 }, handleOut: { x: 15, y: 15 } }');

View file

@ -58,3 +58,5 @@
/*#*/ include('SvgImport.js'); /*#*/ include('SvgImport.js');
/*#*/ include('SvgExport.js'); /*#*/ include('SvgExport.js');
/*#*/ include('Numerical.js');