Merge commit '7bb34e4' into merge-latest-paper

This commit is contained in:
adroitwhiz 2020-05-29 12:26:53 -04:00
commit c3c51d29f6
147 changed files with 4962 additions and 1100 deletions

View file

@ -1,7 +1,7 @@
<!--
This issues database is used to keep track of bugs and new features.
For questions and support, please visit the Gitter channel instead:
https://gitter.im/Vincit/objection.js
https://gitter.im/paperjs/paper.js
-->
# Description/Steps to reproduce

View file

@ -1,16 +1,15 @@
language: node_js
os: linux
# Follow https://github.com/nodejs/LTS to decide when to remove a version
node_js:
- 9
# Stable version is temporarily disabled due to a bug in resemblejs package with
# node v12 (https://github.com/orgs/paperjs/teams/contributors/discussions/12).
# - stable
- 11
- 10
- 8
# 6.13 and 6.14 causing unreasonable errors in SymbolDefinition.initialize.
# https://travis-ci.org/paperjs/paper.js/jobs/434854796
# To avoid these versions, we specify version to 6.12 as temporary solution.
# See https://github.com/paperjs/paper.js/issues/1523
- 6.12
sudo: false
env:
matrix:
jobs:
- CXX=g++-4.9
global:
- secure: o1fJ/suqcL0aX6PiOmip602dAM6Q6O5oU/BDhSueUOnYknbzWfWGOdsoMun0UYhvvdcXU4sHGH+wm0VeXa2igaxryx36uMxPsXB3mO4sATo/QlWX/r3wLCg2CViXhykE5l2gP45OHh5DJgKWs51cwGLDZ9y9JHlXSP/xIGBNkMGVC7qvyhTfEb0EVvirn9b7Kt8fmD8KYgNDrsmcR3d42f4jitt4Di9LsRyOG+SCXZfI3u831tHo1sgZuGK2rxx2SdEclIblEUCkFHLp0HPjq1+032Cg5D7HNloSCPfoSwcY+rOWHubNXhmXgZHFeSkaVglkdWlDE3NiyjNlYwc4m9zqfCip8jw/jUeSfFVtruncEumGLLBxE/aMBQjAQLTq24juabm3qZNgrNCFeFo+XNyx2Oz1jllGve6Vuu8Qg0wFqE+qlZKnxNbu5/3IOIawOE1uhaOG8oSuvlpQuNrHFIMEfzh2UKPiUHbElUDyoTzHlrhQr7ZSPWPJax4uIPOTscpK4Yks7FBS4I0Vnuhw41f/bVR0kLE9jNAQoUpp47ma9O2Sw9fhOwEiopVrADzARUiy0eNeLx8F2F73L0wyPBOtEL1cfCr5oY+yZ5ZfDYb/L8/GIlbMnljYxVbXesmwd8RFi8X2HUNnEmusjih9oWazVuZpiFfUO0oeu15JTBg=
@ -51,7 +50,7 @@ before_deploy:
- git config --global user.name $(git log --pretty=format:"%an" -n1)
deploy:
- provider: npm
node_js: 8
node_js: 10
on:
branch: develop
skip_cleanup: true

View file

@ -1,12 +1,12 @@
## Authors
- Jürg Lehni <juerg@scratchdisk.com>
- Jonathan Puckey <jonathan@studiomoniker.com>
- Jonathan Puckey <jonathan@puckey.studio>
## Contributors
- Harikrishnan Gopalakrishnan <hari.exeption@gmail.com>
- Jan Bösenberg <development@iconexperience.com>
- Jan Bösenberg <jan.boesenberg@gmail.com>
- Jt Whissel <jtwhissel@gmail.com>
- Andrew Roles <jaroles58@gmail.com>
- Jacob Lites <jnightlight@gmail.com>

View file

@ -1,5 +1,145 @@
# Change Log
## `0.12.7`
### Fixed
- PaperScript: Actually make `options.paperFeatures.moduleExports` work
independently from `options.paperFeatures.operatorOverloading`.
## `0.12.6`
### Added
- PaperScript: Add option `options.paperFeatures.moduleExports` to control
module exports conversion.
## `0.12.5`
### Added
- PaperScript: Add option `options.paperFeatures.operatorOverloading` to control
operator overloading.
### Fixed
- Fix `new Raster(HTMLCanvasElement)` constructor (#1745).
- Handle `CurveLocation` on paths with only one segment.
- Fix recently introduced error in `CompoundPath.compare()` (#1769).
- Clamp opacity values to [0, 1] (#1814).
- Support closed `Path` items with blend mode and no segments (#1763).
- Fix error in `getCrossingSegments()` (#1773).
- SVG Import: Support SVG strings with leading line-breaks (#1813).
- Docs: Improve documentation for `Raster#drawImage(CanvasImageSource)` (#1784).
### Changed
- Use `'paper-'` prefix in generated view ids.
## `0.12.4`
### Added
- Allow paper core import in TypeScript (#1713).
- Boolean: Improve performance from `O(n^2)` to nearly `O(n)` by the use of the
sweep and prune algorithm (#1737).
- Docs: Add support for nullable values.
### Fixed
- Fix `PathItem#getCrossing()` to not return overlaps (#1409).
- Fix regression in `Curve.getIntersections()` (#1638).
- Fix edge cases in `CurveLocation.isCrossing()` (#1419, #1263).
- Fix `SymbolItem#hitTestAll()` to return only one match per symbol item
(#1680).
- Fix handling of negative `Shape` sizes (#1733).
- Fix parsing of RGB `Color` strings with percentages (#1736).
- Fix `Shape` bounds when passing position in constructor (#1686).
- Prevent nested group matrix from reset when transforming parent (#1711).
- Boolean: Fix edge cases in overlap detection (#1262).
- Boolean: Add check for paths with only one segment (#1351).
- Boolean: Correctly handle open filled paths (#1647).
- Boolean: Avoid winding number edge cases (#1619).
- Docs: Fix some documentation return types (#1679).
## `0.12.3`
### Added
- Add documentation for `Item#internalBounds`.
### Fixed
- Fix regression in `Color` change propagation (#1672, #1674).
- SVG Export: Fix viewport size of exported `Symbol` (#1668).
- Handle non-invertible matrices in `Item#contains()` (#1651).
- Improve documentation for `Item#clipMask` (#1673).
- Improve TypeScript definitions (#1659, #1663, #1664, #1667).
## `0.12.2`
### Fixed
- Fix drawing with compound-paths as clip-items (#1361).
- Fix drawing of path selection with small handle size (#1327).
- Do not ignore `Group#clipItem.matrix` in `Group#internalBounds` (#1427).
- Correctly calculate bounds with nested empty items (#1467).
- Fix color change propagation on groups (#1152).
- Fix `Path#arcTo()` where `from` and `to` points are equal (#1613).
- Improve `new Raster(size[, position])` constructor (#1621).
- SVG Export: Fix error when `Item#matrix` is not invertible (#1580).
- SVG Export: Include missing viewBox attribute (#1576).
- SVG Import: Use correct default values for gradients (#1632, #1660).
- SVG Import: Add basic `<switch/>` support (#1597).
- JSON Import: Prevent `Item#insert()` method from being overridden (#1392).
- PaperScript: Fix issues with increment/decrement operators (#1450, #1611).
## `0.12.1`
### Added
- Add TypeScript definition, automatically generated from JSDoc comments
(#1612).
- Support `new Raster(size[, position])` constructor.
- Expose `Raster#context` accessor.
- Implement `Raster#clear()` method to clear associated canvas context.
- Node.js: Add support for Node.js v11 and v12.
### Fixed
- Fix parsing of CSS colors with spaces in parentheses (#1629).
- Improve `Color.random()` documentation.
- Fix `Tween#then()` documentation.
### Removed
- Node.js: Remove support for Node.js v6.
## `0.12.0`
### News
Another release, another new member on the team: Please welcome
[@arnoson](https://github.com/arnoson), who has worked hard on the all new
animation support, exposed through the `Tween` class and its various methods on
the `Item` class, see below for details:
### Added
- Add new `Tween` class and related methods on `Item`, to animate and
interpolate their various properties, including colors, sub-properties, etc.:
`Item#tween(from, to, options)`, `Item#tween(to, options)`,
`Item#tween(options)`, `Item#tweenFrom(from, options)`,
`Item#tweenTo(to, options)`
### Fixed
- Only draw Raster if image is not empty (#1320).
- Emit mousedrag events on correct items when covered by other items (#1465).
- Fix drawing issues of bounds and position with `Group#selectedColor` (#1571).
- Fix `Item.once()` to actually only emit event once.
- Various documentation fixes and improvements (#1399).
## `0.11.8`
### News
@ -59,6 +199,7 @@ the fixes and additions from the past two weeks:
## `0.11.5`
### Fixed
- Fix `Curve#isSelected()` to correctly reflect the state of `#handle1` (#1378).
- Key Events: Fix auto-filling issue on Chrome (#1358, #1365).
- Boolean: Check that overlaps are on the right path (#1321).
@ -71,38 +212,42 @@ the fixes and additions from the past two weeks:
## `0.11.4`
### Changed
- Node.js: Add support for v8, and keep testing v4, v6, v7 in Travis CI.
## `0.11.3`
### Fixed
- Mouse Events: Fix item-based `doubleclick` events (#1316).
- Mouse Events: Fix item-based `doubleclick` events (#1316).
- Overhaul the caching of bounds and matrix decomposition, improving reliability
of `Item#rotation` and `#scaling` and fixing situations caused by wrongly
caching `Item#position` and `#bounds` values.
- Prevent consumed properties in object literal constructors from being set on
the instance.
### Changed
- Make all functions and accessors enumerable on all Paper.js classes.
## `0.11.2`
### Fixed
- PaperScript: Fix a parsing error in math operations without white-space
(#1314).
## `0.11.1`
### Fixed
- Bring back deactivation of Node.js modules on browsers. This has most probably
broken Webpack bundling in `0.11.0`.
## `0.11.0`
### 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
@ -115,12 +260,14 @@ the fixes and additions from the past two weeks:
[jsdom](https://github.com/tmpvar/jsdom).
### 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).
@ -154,6 +301,7 @@ the fixes and additions from the past two weeks:
## `0.10.3`
### Changed
- Node.js: Support v7, and keep testing v4 up to v7 in Travis CI.
- Node.js: Loosely couple Node.js / Electron code to `Canvas` module, and treat
its absence like a headless web worker context in the browser (#1103).
@ -178,6 +326,7 @@ the fixes and additions from the past two weeks:
`#reorient()` (#973).
### Added
- Implement `Path#divideAt(location)`, in analogy to `Curve#divideAt(location)`.
- Add `PathItem#compare()` as a way to compare the geometry of two paths to see
if they describe the same shape, handling cases where paths start in different
@ -196,6 +345,7 @@ the fixes and additions from the past two weeks:
(#1004, #1177).
### Fixed
- Many improvements to boolean operations:
- Improve performance of boolean operations when there no actual crossings
between the paths, but paths may be contained within each other.
@ -236,17 +386,20 @@ the fixes and additions from the past two weeks:
(#632).
### Removed
- Remove `Numerical.TOLERANCE = 1e-6` as there is no internal use for it
anymore.
## `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.
@ -284,6 +437,7 @@ Thank you all for using Paper.js, submitting bugs and ideas, and all those that
contribute to the code.
### Changed
- Significant overhaul and improvements of boolean path operations
`PathItem#unite()`, `#subtract()`, `#intersect()`, `#exclude()`, `#divide()`:
- Improve handling of self-intersecting paths and non-zero fill-rules.
@ -300,7 +454,7 @@ contribute to the code.
- `Curve#getParameterAt(offset)``#getTimeAt(offset)`
- `Curve#getParameterOf(point)``getTimeOf(point)`
- `Curve#getPointAt(time, true)``#getPointAtTime(time)`
- `Curve#getTangentAt(time, true)``#getTangenttTime(time)`
- `Curve#getTangentAt(time, true)``#getTangentAtTime(time)`
- `Curve#getNormalAt(time, true)``#getNormalAtTime(time)`
- `Curve#getCurvatureAt(time, true)``#getCurvatureAtTime(time)`
- `CurveLocation#parameter``#time`
@ -360,6 +514,7 @@ contribute to the code.
`Color` (#1043).
### Added
- Use unified code-base for browsers, Node.js, Electron, and anything
in-between, and enable npm install for browser use (#739).
- Start using automatic code testing and deployment of prebuilt versions through
@ -424,6 +579,7 @@ contribute to the code.
- Add `Raster#loaded` to reflect the loading state of its image.
### Fixed
- Fix calculations of `Item#strokeBounds` for all possible combinations of
`Item#strokeScaling` and `Item#applyMatrix` for `Path`, `Shape` and
`SymbolItem`, along with correct handling of such strokes in Item#hitTest()
@ -502,6 +658,7 @@ contribute to the code.
`Numerical.solveCubic()` for edge-cases (#1085).
### Removed
- Canvas attributes "resize" and "data-paper-resize" no longer cause paper to
resize the canvas when the viewport size changes; Additional CSS styles are
required since `0.9.22`, e.g.:
@ -520,6 +677,7 @@ contribute to the code.
It is replaced by `Project#addLayer()` and `Project#insertLayer()`.
### Deprecated
- `#windingRule` on `Item` and `Style``#fillRule`
- `Curve#getNormalAt(time, true)``#getNormalAtTime(true)`
- `Curve#divide()``#divideAt(offset)` / `#divideAtTime(time)`
@ -527,7 +685,7 @@ contribute to the code.
- `Curve#getParameterAt(offset)``#getTimeAt(offset)`
- `Curve#getParameterOf(point)``getTimeOf(point)`
- `Curve#getPointAt(time, true)``#getPointAtTime(time)`
- `Curve#getTangentAt(time, true)``#getTangenttTime(time)`
- `Curve#getTangentAt(time, true)``#getTangentAtTime(time)`
- `Curve#getNormalAt(time, true)``#getNormalAtTime(time)`
- `Curve#getCurvatureAt(time, true)``#getCurvatureAtTime(time)`
- `CurveLocation#parameter``#time`

View file

@ -1,5 +1,5 @@
Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey
http://scratchdisk.com/ & http://jonathanpuckey.com/
Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey
http://juerglehni.com/ & https://puckey.studio/
All rights reserved.
The MIT License (MIT)

View file

@ -1,9 +1,10 @@
# Paper.js - The Swiss Army Knife of Vector Graphics Scripting [![Build Status](https://travis-ci.org/paperjs/paper.js.svg?branch=develop)](https://travis-ci.org/paperjs/paper.js) [![NPM](https://img.shields.io/npm/v/paper.svg)](https://www.npmjs.com/package/paper) ![Bower](https://img.shields.io/bower/v/paper.svg)
# Paper.js - The Swiss Army Knife of Vector Graphics Scripting [![Build Status](https://travis-ci.org/paperjs/paper.js.svg?branch=develop)](https://travis-ci.org/paperjs/paper.js) [![NPM](https://img.shields.io/npm/v/paper.svg)](https://www.npmjs.com/package/paper)
If you want to work with Paper.js, simply download the latest "stable" version
from [http://paperjs.org/download/](http://paperjs.org/download/)
- Website: <http://paperjs.org/>
- Questions: <https://stackoverflow.com/questions/tagged/paperjs>
- Discussion forum: <https://groups.google.com/group/paperjs>
- Mainline source code: <https://github.com/paperjs/paper.js>
- Twitter: [@paperjs](https://twitter.com/paperjs)
@ -17,24 +18,22 @@ from [http://paperjs.org/download/](http://paperjs.org/download/)
The recommended way to install and maintain Paper.js as a dependency in your
project is through the [Node.js Package Manager (NPM)](https://www.npmjs.com/)
for browsers, Node.js or Electron, as well as through Bower for browsers.
for browsers, Node.js or Electron.
If NPM or Bower is already installed, simply type one of these
commands in your project folder:
If NPM is already installed, simply type one of these commands in your project
folder:
```sh
npm install paper
# Or:
bower install paper
```
Upon execution, you will find a `paper` folder inside the project's
`node_modules` / `bower_components` folder.
`node_modules` folder.
For more information on how to install Node.js and NPM, read the chapter
[Installing Node.js and NPM](#installing-nodejs-and-npm).
### Which Version to Use?
### Which Version to Use
The various distributions come with two different pre-build versions of
Paper.js, in minified and normal variants:
@ -68,7 +67,7 @@ rendering to the Canvas on Node.js, as described in the next paragraph.
For Linux, see <https://nodejs.org/download/> to locate 32-bit and 64-bit Node.js
binaries as well as sources, or use NVM, as described in the paragraph above.
### Installing Paper.js for Node.js
### Installing Paper.js Using NPM
Paper.js comes in three different versions on NPM: `paper`, `paper-jsdom` and
`paper-jsdom-canvas`. Depending on your use case, you need to required a
@ -85,10 +84,17 @@ different one:
In order to install `paper-jsdom-canvas`, you need the [Cairo Graphics
library](https://cairographics.org/) installed in your system:
##### Installing Cairo and Pango on macOS:
### Installing Native Dependencies
The easiest way to install Cairo is through [Homebrew](https://brew.sh/), by
issuing the command:
Paper.js relies on [Node-Canvas](https://github.com/Automattic/node-canvas) for
rendering, which in turn relies on the native libraries
[Cairo](https://cairographics.org/) and [Pango](https://www.pango.org/).
#### Installing Native Dependencies on macOS
Paper.js relies on Node-Canvas for rendering, which in turn relies on Cairo and
Pango. The easiest way to install Cairo is through
[Homebrew](https://brew.sh/), by issuing the command:
brew install cairo pango
@ -112,7 +118,7 @@ After adding this line, your commands should work in the expected way:
npm install paper
npm update
##### Installing Cairo, Pango and all other dependencies on Debian/Ubuntu Linux:
#### Installing Native Dependencies on Debian/Ubuntu Linux
sudo apt-get install pkg-config libcairo2-dev libpango1.0-dev libssl-dev libjpeg62-dev libgif-dev
@ -121,24 +127,23 @@ build from c++ sources:
sudo apt-get install build-essential
##### After Cairo has been installed:
#### Installing Native Dependencies for Electron
In order to build Node-Canvas for use of `paper-jsdom-canvas` 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/)
#### After Native Dependencies have been installed
You should now be able to install the Paper.js module with jsdom and Canvas
rendering from NPM:
npm install paper-jsdom-canvas
### Installing Paper.js with Node-Canvas for Electron
[Node-Canvas](https://github.com/Automattic/node-canvas) is a native dependency.
In order to build it for use of `paper-jsdom-canvas` 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
@ -163,8 +168,8 @@ run:
### Setting Up For Building
As of 2016, Paper.js uses [Gulp.js](https://gulpjs.com/) for building, and has a
couple of dependencies as Bower and NPM modules. Read the chapter [Installing
Node.js, NPM and Bower](#installing-nodejs-npm-and-bower) if you still need to
couple of dependencies as NPM modules. Read the chapter [Installing
Node.js and NPM](#installing-nodejs-and-npm) if you still need to
install these.
In order to be able to build Paper.js, after checking out the repository, paper

View file

@ -9,8 +9,8 @@
},
"bugs": "https://github.com/paperjs/paper.js/issues",
"authors": [
"Jürg Lehni <juerg@scratchdisk.com> (http://scratchdisk.com)",
"Jonathan Puckey <jonathan@studiomoniker.com> (http://studiomoniker.com)"
"Jürg Lehni <juerg@scratchdisk.com> (http://juerglehni.com)",
"Jonathan Puckey <jonathan@puckey.studio> (http://puckey.studio)"
],
"main": "dist/paper-full.js",
"ignore": [

View file

@ -24,7 +24,7 @@ raster.onLoad = function() {
var fs = require('fs');
var svg = new paper.XMLSerializer().serializeToString(project.exportSVG());
fs.writeFile(path.resolve('./out.svg'),svg, function (err) {
fs.writeFile(path.resolve('./out.svg'), svg, function (err) {
if (err) throw err;
console.log('Saved!');
});

View file

@ -45,7 +45,7 @@ with (paper) {
var svg = project.exportSVG({ asString: true });
console.log(svg);
fs.writeFile(path.resolve('./out.svg'),svg, function (err) {
fs.writeFile(path.resolve('./out.svg'), svg, function (err) {
if (err) throw err;
console.log('Saved!');
});

View file

@ -0,0 +1,115 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Path Intersections</title>
<link rel="stylesheet" href="../css/style.css">
<script type="text/javascript" src="../../dist/paper-full.js"></script>
<script type="text/paperscript" canvas="canvas">
var text = new PointText({
position: view.center + [0, 200],
fillColor: 'black',
justification: 'center',
fontSize: 20
});
var originals = new Group({ insert: false }); // Don't insert in DOM.
var square = new Path.Rectangle({
position: view.center,
size: 300,
parent: originals,
fillColor: 'white'
});
// Make a ring using subtraction of two circles:
var inner = new Path.Circle({
center: view.center,
radius: 100,
parent: originals,
fillColor: 'white'
});
var outer = new Path.Circle({
center: view.center,
radius: 140,
parent: originals,
fillColor: 'white'
});
var ring = outer.subtract(inner);
var operations = ['unite', 'intersect', 'subtract', 'exclude', 'divide'];
var colors = ['red', 'green', 'blue', 'black'];
var curIndex = -1;
var operation, result, activeItem;
// Change the mode every 3 seconds:
setInterval(setMode, 3000);
// Set the initial mode:
setMode();
function setMode() {
curIndex++;
if (curIndex == operations.length * 2)
curIndex = 0;
operation = operations[curIndex % operations.length];
}
function onMouseDown(event) {
var hitResult = originals.hitTest(event.point);
activeItem = hitResult && hitResult.item;
}
function onMouseDrag(event) {
if (activeItem)
activeItem.position = event.point;
}
function onMouseUp() {
activeItem = null;
square.position = view.center;
}
function onFrame(event) {
if (activeItem != ring) {
// Move the ring around:
var offset = new Point(140, 80) * [Math.sin(event.count / 60), Math.sin(event.count / 40)];
ring.position = view.center + offset;
}
// Remove the result of the last path operation:
if (result)
result.remove();
// Perform the path operation on the ring:
if (curIndex < operations.length) {
result = square[operation](ring);
text.content = 'square.' + operation + '(ring)';
} else {
result = ring[operation](square);
text.content = 'ring.' + operation + '(square)';
}
result.selected = true;
result.fillColor = colors[curIndex % colors.length];
result.moveBelow(text);
// If the result is a group, color each of its children differently:
if (result instanceof Group) {
for (var i = 0; i < result.children.length; i++) {
result.children[i].fillColor = colors[i];
}
}
};
function onResize() {
text.position = view.center + [0, 200];
square.position = view.center;
}
</script>
</head>
<body>
<canvas id="canvas" resize></canvas>
</body>
</html>

@ -1 +1 @@
Subproject commit 2533ac8e1863262f3c28cd29bc940c6d2ecdf147
Subproject commit 1d38968146973a4bb10f5495b73c9d5dd46188d7

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*
@ -22,6 +22,8 @@ gulp.task('zip', ['clean:zip', 'dist'], function() {
gulp.src([
'dist/paper-full*.js',
'dist/paper-core*.js',
'dist/paper.d.ts',
'dist/paper-core.d.ts',
'dist/node/**/*',
'LICENSE.txt',
'examples/**/*',

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*
@ -14,14 +14,15 @@ var gulp = require('gulp'),
del = require('del'),
rename = require('gulp-rename'),
shell = require('gulp-shell'),
options = require('../utils/options.js');
options = require('../utils/options.js'),
run = require('run-sequence');
var docOptions = {
local: 'docs', // Generates the offline docs
server: 'serverdocs' // Generates the website templates for the online docs
};
gulp.task('docs', ['docs:local', 'build:full'], function() {
gulp.task('docs', ['docs:local', 'docs:typescript', 'build:full'], function() {
return gulp.src('dist/paper-full.js')
.pipe(rename({ basename: 'paper' }))
.pipe(gulp.dest('dist/docs/assets/js/'));
@ -32,15 +33,58 @@ Object.keys(docOptions).forEach(function(name) {
var mode = docOptions[name];
return gulp.src('src')
.pipe(shell(
['java -cp jsrun.jar:lib/* JsRun app/run.js',
[
'java -cp jsrun.jar:lib/* JsRun app/run.js',
' -c=conf/', name, '.conf ',
' -D="renderMode:', mode, '" ',
' -D="version:', options.version, '"'].join(''),
' -D="version:', options.version, '"'
].join(''),
{ cwd: 'gulp/jsdoc' })
)
);
});
gulp.task('clean:docs:' + name, function() {
return del([ 'dist/' + docOptions[name] + '/**' ]);
return del(['dist/' + docOptions[name] + '/**']);
});
});
// The goal of the typescript task is to automatically generate a type
// definition for the library.
gulp.task('docs:typescript', function(callback) {
run(
'docs:typescript:clean:before',
'docs:typescript:build',
'docs:typescript:clean:after',
callback
);
});
// First clean eventually existing type definition...
gulp.task('docs:typescript:clean:before', function() {
return del('dist/paper.d.ts');
});
// ...then build the definition...
gulp.task('docs:typescript:build', function() {
// First parse JSDoc comments and store parsed data in a temporary file...
return gulp.src('src')
.pipe(shell(
[
'java -cp jsrun.jar:lib/* JsRun app/run.js',
' -c=conf/typescript.conf ',
' -D="file:../../gulp/typescript/typescript-definition-data.json"',
' -D="version:', options.version, '"',
' -D="date:', options.date, '"'
].join(''),
{ cwd: 'gulp/jsdoc' })
)
// ...then generate definition from parsed data...
.pipe(shell('node gulp/typescript/typescript-definition-generator.js'))
// ...finally test the definition by compiling a typescript file.
.pipe(shell('node node_modules/typescript/bin/tsc --project gulp/typescript'));
});
// ...finally remove all unneeded temporary files that were used for building.
gulp.task('docs:typescript:clean:after', function() {
return del([
'gulp/typescript/typescript-definition-data.json',
'gulp/typescript/typescript-definition-test.js'
]);
});

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*
@ -32,20 +32,21 @@ var packages = ['paper-jsdom', 'paper-jsdom-canvas'],
end_with_newline: true
};
gulp.task('publish', function() {
gulp.task('publish', function(callback) {
if (options.branch !== 'develop') {
throw new Error('Publishing is only allowed on the develop branch.');
}
// publish:website comes before publish:release, so paperjs.zip file is gone
// before npm publish:
return run(
run(
'publish:json',
'publish:dist',
'publish:packages',
'publish:commit',
'publish:website',
'publish:release',
'publish:load'
'publish:load',
callback
);
});
@ -108,11 +109,12 @@ packages.forEach(function(name) {
});
});
gulp.task('publish:website', function() {
gulp.task('publish:website', function(callback) {
if (fs.lstatSync(sitePath).isDirectory()) {
return run(
run(
'publish:website:build',
'publish:website:push'
'publish:website:push',
callback
);
}
});

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*

View file

@ -0,0 +1,9 @@
{
"compilerOptions": {
"target": "ES5",
"strictNullChecks": true
},
"files" : [
"typescript-definition-test.ts"
]
}

View file

@ -0,0 +1,357 @@
/**
* This script generates a type definition by taking JSDoc roughly parsed data,
* formatting it and passing it to a mustache template.
*/
const fs = require('fs');
const mustache = require('mustache');
// Retrieve JSDoc data.
const data = JSON.parse(fs.readFileSync(__dirname + '/typescript-definition-data.json', 'utf8'));
const classes = data.classes;
// Format classes.
classes.forEach(cls => {
// Format class.
// Store name as `className` and not simply `name`, to avoid name conflict
// in static constructors block.
cls.className = cls._name;
// Store closest parent if there is one.
cls.extends = cls.inheritsFrom && cls.inheritsFrom.length > 0
? cls.inheritsFrom[0]
: null;
// Store comment using class tag as description.
cls.comment = formatComment(cls.comment, 'class');
// Build a filter for deprecated or inherited methods or properties.
const filter = it => !it.deprecated && it.memberOf == cls.alias && !it.isNamespace;
// Format properties.
cls.properties = cls.properties
.filter(filter)
.map(it => ({
name: it._name,
type: formatType(it.type, { isProperty: true, isSettableProperty: !it.readOnly }),
static: formatStatic(it.isStatic),
readOnly: formatReadOnly(it.readOnly),
comment: formatComment(it.comment)
}));
// Format methods.
const methods = cls.methods
.filter(filter)
.map(it => {
const name = formatMethodName(it._name);
const isStaticConstructor = it.isStatic && it.isConstructor;
return {
name: name,
// Constructors don't need return type.
type: !it.isConstructor
? formatType(getMethodReturnType(it), { isMethodReturnType: true })
: '',
static: formatStatic(it.isStatic),
// This flag is only used below to filter methods.
isStaticConstructor: isStaticConstructor,
comment: formatComment(it.comment, 'desc', it.isConstructor),
params: it._params
? it._params
// Filter internal parameters (starting with underscore).
.filter(it => !/^_/.test(it.name))
.map(it => formatParameter(it, isStaticConstructor && cls))
.join(', ')
: ''
};
})
.sort(sortMethods);
// Divide methods in 2 parts: static constructors and other. Because static
// constructors need a special syntax in type definition.
cls.methods = [];
cls.staticConstructors = [];
methods.forEach(method => {
if (method.isStaticConstructor) {
// Group static constructors by method name.
let staticConstructors = cls.staticConstructors.find(it => it.name === method.name);
if (!staticConstructors) {
staticConstructors = {
name: method.name,
constructors: []
};
cls.staticConstructors.push(staticConstructors);
}
staticConstructors.constructors.push(method);
} else {
cls.methods.push(method);
}
});
// Store a conveniance flag to check whether class has static constructors.
cls.hasStaticConstructors = cls.staticConstructors.length > 0;
});
// PaperScope class needs to be handled slightly differently because it "owns"
// all the other classes as properties. Eg. we can do `new paperScope.Path()`.
// So we add a `classesPointers` property that the template will use.
const paperScopeClass = classes.find(it => it.className === 'PaperScope');
paperScopeClass.classesPointers = classes.map(it => ({ name: it.className }));
// Format data trough a mustache template.
// Prepare data for the template.
const context = {
classes: classes,
version: data.version,
date: data.date,
// {{#doc}} blocks are used in template to automatically generate a JSDoc
// comment with a custom indent.
doc: () => formatJSDoc
};
// Retrieve template content.
const template = fs.readFileSync(__dirname + '/typescript-definition-template.mustache', 'utf8');
// Render template.
const output = mustache.render(template, context);
// Write output in a file.
fs.writeFileSync(__dirname + '/../../dist/paper.d.ts', output, 'utf8');
//
// METHODS
//
function formatReadOnly(isReadOnly) {
return isReadOnly ? 'readonly ' : null;
}
function formatStatic(isStatic) {
return isStatic ? 'static ' : null;
}
function formatType(type, options) {
return ': ' + parseType(type, options);
}
function parseType(type, options) {
// Always return a type even if input type is empty. In that case, return
// `void` for method return type and `any` for the rest.
if (!type) {
return options.isMethodReturnType ? 'void' : 'any';
}
// Prefer `any[]` over `Array<any>` to be more consistent with other types.
if (type === 'Array') {
return 'any[]';
}
// Handle any type: `*` => `any`
type = type.replace('*', 'any');
// Check if type is a "rest" type (meaning that an infinite number of
// parameter of this type can be passed). In that case, we need to remove
// `...` prefix and add `[]` as a suffix:
// - `...Type` => `Type[]`
// - `...(TypeA|TypeB)` => `(TypeA|TypeB)[]`
const restPattern = /^\.\.\./;
const isRest = type.match(restPattern);
if (isRest) {
type = type.replace(restPattern, '');
}
const wrappedPattern = /^\(([^\)]+)\)$/;
const isWrapped = type.match(wrappedPattern);
if (isWrapped) {
type = type.replace(wrappedPattern, '$1');
}
// Handle multiple types possibility by splitting on `|` then re-joining
// back parsed types.
const types = type.split('|');
// Hanle nullable type:
// - `?Type` => `Type|null`
// - `?TypeA|TypeB` => `TypeA|TypeB|null`
// - `?TypeA|?TypeB` => `TypeA|TypeB|null`
// If at least one type is nullable, we add null type at the end of the
// list.
const nullablePattern = /^\?/;
let isNullable = false;
for (let i = 0; i < types.length; i++) {
if (types[i].match(nullablePattern)) {
types[i] = types[i].replace(nullablePattern, '');
isNullable = true;
}
}
if (isNullable) {
types.push('null');
}
type = types.map(splittedType => {
// Get type without array suffix `[]` for easier matching.
const singleType = splittedType.replace(/(\[\])+$/, '');
// Handle eventual type conflict in static constructors block. For
// example, in `Path.Rectangle(rectangle: Rectangle)` method,
// `rectangle` parameter type must be mapped to `paper.Rectangle` as it
// is declared inside a `Path` namespace and would otherwise be wrongly
// assumed as being the type of `Path.Rectangle` class.
if (options.staticConstructorClass && options.staticConstructorClass.methods.find(it => it.isStatic && it.isConstructor && formatMethodName(it._name) === singleType)
) {
return 'paper.' + splittedType;
}
// Convert primitive types to their lowercase equivalent to suit
// typescript best practices.
if (['Number', 'String', 'Boolean', 'Object'].indexOf(singleType) >= 0) {
splittedType = splittedType.toLowerCase();
}
// Properties `object` type need to be turned into `any` to avoid
// errors when reading object properties. Eg. if `property` is of type
// `object`, `property.key` access is forbidden.
if (options.isProperty && splittedType === 'object') {
return 'any';
}
return splittedType;
}).join(' | ');
// Regroup types.
if (isWrapped) {
type = `(${type})`;
}
if (isRest) {
type += '[]';
}
return type;
}
function formatMethodName(methodName) {
// Overloaded methods were parsed as `method^0`, `method^1`... here, we
// turn them back to `method` as typescript allow overloading.
methodName = methodName.replace(/\^[0-9]+$/, '');
// Real contructors are called `initialize` in the library.
methodName = methodName.replace(/^initialize$/, 'constructor');
return methodName;
}
function formatParameter(param, staticConstructorClass) {
let content = '';
// Handle rest parameter pattern `...Type`. Parameter name needs to be
// prefixed with `...` as in ES6. E.g. `...parameter: type[]`.
if (param.type.match(/^\.\.\.(.+)$/)) {
content += '...';
}
content += formatParameterName(param.name);
// Optional parameters are formatted as: `parameter?: type`.
if (param.isOptional) {
content += '?';
}
content += formatType(param.type, { staticConstructorClass });
return content;
}
function formatParameterName(parameterName) {
// Avoid usage of reserved keyword as parameter name.
// E.g. `function` => `callback`.
if (parameterName === 'function') {
return 'callback';
}
return parameterName;
}
function formatComment(comment, descriptionTagName = 'desc', skipReturn = false) {
const tags = comment.tags;
let content = '';
// Retrieve description tag.
const descriptionTag = tags.find(it => it.title === descriptionTagName);
if (descriptionTag) {
// Don't display group titles.
content += descriptionTag.desc.replace(/\{@grouptitle .+?\}/g, '').trim();
}
// Preserve some of the JSDoc tags that can be usefull even in type
// definition. Format their values to make sure that only informations
// that make sense are kept. E.g. method parameters types are already
// provided in the signature...
content += formatCommentTags(tags, 'see');
content += formatCommentTags(tags, 'option');
content += formatCommentTags(tags, 'param', it => it.name + ' - ' + it.desc);
if (!skipReturn) {
content += formatCommentTags(tags, 'return', it => it.desc.trim().replace(/^\{|\}$/g, '').replace(/@([a-zA-Z]+)/, '$1'));
}
// Make sure links are followable (e.g. by IDEs) by removing parameters.
// {@link Class#method(param)} => {@link Class#method}
content = content.replace(/(\{@link [^\}]+?)\(.*?\)(\})/g, '$1$2');
content = content.trim();
return content;
}
function formatCommentTags(tags, tagName, formatter) {
let content = '';
// Default formatter simply outputs description.
formatter = formatter || (it => it.desc);
// Only keep tags that have a description.
tags = tags.filter(it => it.desc && it.title === tagName);
if (tags.length > 0) {
content += '\n';
// Display tag as it was in original JSDoc, followed by formatted value.
tags.forEach(it => content += '\n@' + tagName + ' ' + formatter(it));
}
return content;
}
/**
* This outputs a JSDoc comment indented at the given offset and including the
* parsed comment for current mustache block.
* @param {Number} offset the number of spaces to use for indentation
* @param {Function} render the mustache render method
* @return {string} the formatted JSDoc comment
*/
function formatJSDoc(offset, render) {
// First render current block comment. Use `{{&}}` syntax to make sure
// special characters are not escaped.
let content = render('{{&comment}}');
if (!content) {
return '';
}
// Build indentation.
offset = parseInt(offset);
if (offset > 0) {
offset++;
}
const indentation = new Array(offset).join(' ');
// Prefix each line with the indentation.
content = content.split('\n')
.map(_ => indentation + ' * ' + _)
.join('\n');
// Wrap content in JSDoc delimiters: `/**` and `*/`.
return '/** \n' + content + '\n' + indentation + ' */';
}
function getMethodReturnType(method) {
return method.returnType || method.returns.length > 0 && method.returns[0].type;
}
function sortMethods(methodA, methodB) {
// This places constructors before other methods as it is a best practice.
// This also place constructors with only one object parameter after other
// constructors to avoid type inference errors due to constructors
// overloading order. E.g. if `constructor(object: object)` is defined
// before `constructor(instance: Class)`, calling `constructor(instance)`
// will always be mapped to `contructor(object: object)`, since everything
// is an object in JavaScript. This is problematic because most of Paper.js
// classes have a constructor accepting an object.
const aIsContructor = methodA.name === 'constructor';
const bIsContructor = methodB.name === 'constructor';
if (aIsContructor && bIsContructor) {
if (methodA.params === 'object: object') {
return 1;
}
if (methodB.params === 'object: object') {
return -1;
}
} else if (aIsContructor) {
return -1;
} else if (bIsContructor) {
return 1;
}
return 0;
}

View file

@ -0,0 +1,65 @@
/*!
* Paper.js v{{version}} - The Swiss Army Knife of Vector Graphics Scripting.
* http://paperjs.org/
*
* Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*
* All rights reserved.
*
* Date: {{date}}
*
* This is an auto-generated type definition.
*/
declare namespace paper {
{{#classes}}
{{#doc}}4{{/doc}}
class {{className}} {{#extends}}extends {{extends}}{{/extends}} {
{{#properties}}
{{#doc}}8{{/doc}}
{{static}}{{readOnly}}{{name}}{{type}}
{{/properties}}
{{#classesPointers}}
{{name}}: typeof {{name}}
{{/classesPointers}}
{{#methods}}
{{#doc}}8{{/doc}}
{{static}}{{name}}({{params}}){{type}}
{{/methods}}
}
{{#hasStaticConstructors}}
namespace {{className}} {
{{#staticConstructors}}
class {{name}} extends {{className}} {
{{#constructors}}
{{#doc}}12{{/doc}}
constructor({{params}})
{{/constructors}}
}
{{/staticConstructors}}
}
{{/hasStaticConstructors}}
{{/classes}}
}
declare module 'paper/dist/paper-core'
{
const paperCore: Pick<paper.PaperScope, Exclude<keyof paper.PaperScope, 'PaperScript'>>;
export = paperCore
}
declare module 'paper'
{
const paperFull: paper.PaperScope;
export = paperFull
}

File diff suppressed because it is too large Load diff

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*
@ -24,9 +24,10 @@ options.date = git('log -1 --pretty=format:%ad');
options.branch = git('rev-parse --abbrev-ref HEAD');
// If a specific branch is requested, quit without errors if we don't match.
if (argv.branch && argv.branch !== options.branch) {
console.log('Branch "' + options.branch + '" does not match "' +
argv.branch + '". There is nothing to do here.');
var ensureBranch = argv['ensure-branch'];
if (ensureBranch && ensureBranch !== options.branch) {
console.log('Branch "' + options.branch + '" does not match requested "' +
ensureBranch + '". There is nothing to do here.');
process.exit(0);
}

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*

View file

@ -1,6 +1,6 @@
{
"name": "@scratch/paper",
"version": "0.11.8",
"version": "0.12.7",
"description": "The Swiss Army Knife of Vector Graphics Scripting",
"license": "MIT",
"homepage": "http://paperjs.org",
@ -9,11 +9,10 @@
"url": "https://github.com/paperjs/paper.js"
},
"bugs": "https://github.com/paperjs/paper.js/issues",
"contributors": ["Jürg Lehni <juerg@scratchdisk.com> (http://scratchdisk.com)", "Jonathan Puckey <jonathan@studiomoniker.com> (http://studiomoniker.com)"],
"contributors": ["Jürg Lehni <juerg@scratchdisk.com> (http://scratchdisk.com)", "Jonathan Puckey <jonathan@puckey.studio> (http://studiomoniker.com)"],
"main": "dist/paper-full.js",
"types": "dist/paper.d.ts",
"scripts": {
"precommit": "gulp jshint --branch develop",
"prepush": "gulp test --branch develop",
"build": "gulp build",
"dist": "gulp dist",
"zip": "gulp zip",
@ -24,42 +23,13 @@
},
"files": ["AUTHORS.md", "CHANGELOG.md", "dist/", "examples/", "LICENSE.txt", "README.md"],
"engines": {
"node": ">=4.0.0"
"node": ">=8.0.0"
},
"devDependencies": {
"acorn": "~0.5.0",
"canvas-prebuilt": "^2.0.0-alpha.14",
"del": "^2.2.1",
"gulp": "^3.9.1",
"gulp-cached": "^1.1.0",
"gulp-git-streamed": "^2.8.1",
"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",
"gulp-shell": "^0.5.2",
"gulp-symlink": "^2.1.4",
"gulp-uglify": "^1.5.4",
"gulp-uncomment": "^0.3.0",
"gulp-util": "^3.0.7",
"gulp-webserver": "^0.9.1",
"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",
"minimist": "^1.2.0",
"prepro": "^2.4.0",
"qunitjs": "^1.23.0",
"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": "^3.0.1"
"husky": {
"hooks": {
"pre-commit": "gulp jshint --ensure-branch develop",
"pre-push": "gulp test --ensure-branch develop"
}
},
"browser": {
"canvas": false,
@ -69,5 +39,42 @@
"./dist/node/self.js": false,
"./dist/node/extend.js": false
},
"devDependencies": {
"acorn": "~0.5.0",
"canvas": "^2.4.1",
"del": "^4.1.0",
"gulp": "^3.9.1",
"gulp-cached": "^1.1.0",
"gulp-git-streamed": "^2.10.1",
"gulp-jshint": "^2.1.0",
"gulp-json-editor": "^2.5.2",
"gulp-prepro": "^2.4.0",
"gulp-qunits": "^2.1.2",
"gulp-rename": "^1.4.0",
"gulp-shell": "^0.7.0",
"gulp-symlink": "^2.1.4",
"gulp-uglify": "^1.5.4",
"gulp-uncomment": "^0.3.0",
"gulp-util": "^3.0.7",
"gulp-webserver": "^0.9.1",
"gulp-whitespace": "^0.1.0",
"gulp-zip": "^3.2.0",
"husky": "^2.3.0",
"jsdom": "^15.1.1",
"jshint": "^2.10.2",
"jshint-summary": "^0.4.0",
"merge-stream": "^2.0.0",
"minimist": "^1.2.0",
"mustache": "^3.0.1",
"prepro": "^2.4.0",
"qunitjs": "^1.23.0",
"require-dir": "^1.2.0",
"resemblejs": "^3.1.0",
"run-sequence": "^2.2.1",
"source-map-support": "^0.5.12",
"stats.js": "0.17.0",
"straps": "^3.0.1",
"typescript": "^3.1.6"
},
"keywords": ["vector", "graphic", "graphics", "2d", "geometry", "bezier", "curve", "curves", "path", "paths", "canvas", "svg", "paper", "paper.js", "paperjs"]
}

@ -1 +1 @@
Subproject commit f601084fc319734d0bf47da700d6b6bff95260ba
Subproject commit 33c25749460be037bf9afdb80700205c3cfd0942

@ -1 +1 @@
Subproject commit a07b7d149f02e980dfd837cd595f5000a9d5e052
Subproject commit 317d44cfa658b2ec242cbb2f54572e2e1b58a3d2

398
src/anim/Tween.js Normal file
View file

@ -0,0 +1,398 @@
/*
* Paper.js - The Swiss Army Knife of Vector Graphics Scripting.
* http://paperjs.org/
*
* Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*
* All rights reserved.
*/
/**
* @name Tween
*
* @class Allows tweening `Object` properties between two states for a given
* duration. To tween properties on Paper.js {@link Item} instances,
* {@link Item#tween(from, to, options)} can be used, which returns created
* tween instance.
*
* @see Item#tween(from, to, options)
* @see Item#tween(to, options)
* @see Item#tween(options)
* @see Item#tweenTo(to, options)
* @see Item#tweenFrom(from, options)
*/
var Tween = Base.extend(Emitter, /** @lends Tween# */{
_class: 'Tween',
statics: {
easings: {
// no easing, no acceleration
linear: function(t) {
return t;
},
// accelerating from zero velocity
easeInQuad: function(t) {
return t * t;
},
// decelerating to zero velocity
easeOutQuad: function(t) {
return t * (2 - t);
},
// acceleration until halfway, then deceleration
easeInOutQuad: function(t) {
return t < 0.5
? 2 * t * t
: -1 + 2 * (2 - t) * t;
},
// accelerating from zero velocity
easeInCubic: function(t) {
return t * t * t;
},
// decelerating to zero velocity
easeOutCubic: function(t) {
return --t * t * t + 1;
},
// acceleration until halfway, then deceleration
easeInOutCubic: function(t) {
return t < 0.5
? 4 * t * t * t
: (t - 1) * (2 * t - 2) * (2 * t - 2) + 1;
},
// accelerating from zero velocity
easeInQuart: function(t) {
return t * t * t * t;
},
// decelerating to zero velocity
easeOutQuart: function(t) {
return 1 - (--t) * t * t * t;
},
// acceleration until halfway, then deceleration
easeInOutQuart: function(t) {
return t < 0.5
? 8 * t * t * t * t
: 1 - 8 * (--t) * t * t * t;
},
// accelerating from zero velocity
easeInQuint: function(t) {
return t * t * t * t * t;
},
// decelerating to zero velocity
easeOutQuint: function(t) {
return 1 + --t * t * t * t * t;
},
// acceleration until halfway, then deceleration
easeInOutQuint: function(t) {
return t < 0.5
? 16 * t * t * t * t * t
: 1 + 16 * (--t) * t * t * t * t;
}
}
},
/**
* Creates a new tween.
*
* @param {Object} object the object to tween the properties on
* @param {Object} from the state at the start of the tweening
* @param {Object} to the state at the end of the tweening
* @param {Number} duration the duration of the tweening
* @param {String|Function} [easing='linear'] the type of the easing
* function or the easing function
* @param {Boolean} [start=true] whether to start tweening automatically
* @return {Tween} the newly created tween
*/
initialize: function Tween(object, from, to, duration, easing, start) {
this.object = object;
var type = typeof easing;
var isFunction = type === 'function';
this.type = isFunction
? type
: type === 'string'
? easing
: 'linear';
this.easing = isFunction ? easing : Tween.easings[this.type];
this.duration = duration;
this.running = false;
this._then = null;
this._startTime = null;
var state = from || to;
this._keys = state ? Object.keys(state) : [];
this._parsedKeys = this._parseKeys(this._keys);
this._from = state && this._getState(from);
this._to = state && this._getState(to);
if (start !== false) {
this.start();
}
},
/**
* Set a function that will be executed when the tween completes.
* @param {Function} function the function to execute when the tween
* completes
* @return {Tween}
*
* @example {@paperscript}
* // Tweens chaining:
* var circle = new Path.Circle({
* center: view.center,
* radius: 40,
* fillColor: 'blue'
* });
* // Tween color from blue to red.
* var tween = circle.tweenTo({ fillColor: 'red' }, 2000);
* // When the first tween completes...
* tween.then(function() {
* // ...tween color back to blue.
* circle.tweenTo({ fillColor: 'blue' }, 2000);
* });
*/
then: function(then) {
this._then = then;
return this;
},
/**
* Start tweening.
* @return {Tween}
*
* @example {@paperscript}
* // Manually start tweening.
* var circle = new Path.Circle({
* center: view.center,
* radius: 40,
* fillColor: 'blue'
* });
* var tween = circle.tweenTo(
* { fillColor: 'red' },
* { duration: 2000, start: false }
* );
* tween.start();
*/
start: function() {
this._startTime = null;
this.running = true;
return this;
},
/**
* Stop tweening.
* @return {Tween}
*
* @example {@paperscript}
* // Stop a tween before it completes.
* var circle = new Path.Circle({
* center: view.center,
* radius: 40,
* fillColor: 'blue'
* });
* // Start tweening from blue to red for 2 seconds.
* var tween = circle.tweenTo({ fillColor: 'red' }, 2000);
* // After 1 second...
* setTimeout(function(){
* // ...stop tweening.
* tween.stop();
* }, 1000);
*/
stop: function() {
this.running = false;
return this;
},
// DOCS: Document Tween#update(progress)
update: function(progress) {
if (this.running) {
if (progress > 1) {
// always finish the animation
progress = 1;
this.running = false;
}
var factor = this.easing(progress),
keys = this._keys,
getValue = function(value) {
return typeof value === 'function'
? value(factor, progress)
: value;
};
for (var i = 0, l = keys && keys.length; i < l; i++) {
var key = keys[i],
from = getValue(this._from[key]),
to = getValue(this._to[key]),
// Some paper objects have math functions (e.g.: Point,
// Color) which can directly be used to do the tweening.
value = (from && to && from.__add && to.__add)
? to.__subtract(from).__multiply(factor).__add(from)
: ((to - from) * factor) + from;
this._setProperty(this._parsedKeys[key], value);
}
if (!this.running && this._then) {
// TODO Look into what should be returned.
this._then(this.object);
}
if (this.responds('update')) {
this.emit('update', new Base({
progress: progress,
factor: factor
}));
}
}
return this;
},
/**
* {@grouptitle Event Handlers}
*
* The function to be called when the tween is updated. It receives an
* object as its sole argument, containing the current progress of the
* tweening and the factor calculated by the easing function.
*
* @name Tween#onUpdate
* @property
* @type ?Function
*
* @example {@paperscript}
* // Display tween progression values:
* var circle = new Path.Circle({
* center: view.center,
* radius: 40,
* fillColor: 'blue'
* });
* var tween = circle.tweenTo(
* { fillColor: 'red' },
* {
* duration: 2000,
* easing: 'easeInCubic'
* }
* );
* var progressText = new PointText(view.center + [60, -10]);
* var factorText = new PointText(view.center + [60, 10]);
*
* // Install event using onUpdate() property:
* tween.onUpdate = function(event) {
* progressText.content = 'progress: ' + event.progress.toFixed(2);
* };
*
* // Install event using on('update') method:
* tween.on('update', function(event) {
* factorText.content = 'factor: ' + event.factor.toFixed(2);
* });
*/
_events: {
onUpdate: {}
},
_handleFrame: function(time) {
var startTime = this._startTime,
progress = startTime
? (time - startTime) / this.duration
: 0;
if (!startTime) {
this._startTime = time;
}
this.update(progress);
},
_getState: function(state) {
var keys = this._keys,
result = {};
for (var i = 0, l = keys.length; i < l; i++) {
var key = keys[i],
path = this._parsedKeys[key],
current = this._getProperty(path),
value;
if (state) {
var resolved = this._resolveValue(current, state[key]);
// Temporarily set the resolved value, so we can retrieve the
// coerced value from paper's internal magic.
this._setProperty(path, resolved);
value = this._getProperty(path);
// Clone the value if possible to prevent future changes.
value = value && value.clone ? value.clone() : value;
this._setProperty(path, current);
} else {
// We want to get the current state at the time of the call, so
// we have to clone if possible to prevent future changes.
value = current && current.clone ? current.clone() : current;
}
result[key] = value;
}
return result;
},
_resolveValue: function(current, value) {
if (value) {
if (Array.isArray(value) && value.length === 2) {
var operator = value[0];
return (
operator &&
operator.match &&
// We're (unnecessarily) escaping '*/' here to not confuse
// the ol' JSDoc parser...
operator.match(/^[+\-\*\/]=/)
)
? this._calculate(current, operator[0], value[1])
: value;
} else if (typeof value === 'string') {
var match = value.match(/^[+\-*/]=(.*)/);
if (match) {
var parsed = JSON.parse(match[1].replace(
/(['"])?([a-zA-Z0-9_]+)(['"])?:/g,
'"$2": '
));
return this._calculate(current, value[0], parsed);
}
}
}
return value;
},
_calculate: function(left, operator, right) {
return paper.PaperScript.calculateBinary(left, operator, right);
},
_parseKeys: function(keys) {
var parsed = {};
for (var i = 0, l = keys.length; i < l; i++) {
var key = keys[i],
path = key
// Convert from JS property access notation to JSON pointer:
.replace(/\.([^.]*)/g, '/$1')
// Expand array property access notation ([])
.replace(/\[['"]?([^'"\]]*)['"]?\]/g, '/$1');
parsed[key] = path.split('/');
}
return parsed;
},
_getProperty: function(path, offset) {
var obj = this.object;
for (var i = 0, l = path.length - (offset || 0); i < l && obj; i++) {
obj = obj[path[i]];
}
return obj;
},
_setProperty: function(path, value) {
var dest = this._getProperty(path, 1);
if (dest) {
dest[path[path.length - 1]] = value;
}
}
});

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*
@ -12,8 +12,8 @@
/**
* @name Line
*
* @class The Line object represents..
* @private
*/
var Line = Base.extend(/** @lends Line# */{
_class: 'Line',
@ -141,7 +141,7 @@ var Line = Base.extend(/** @lends Line# */{
}
var cross = v1x * v2y - v1y * v2x;
// Avoid divisions by 0, and errors when getting too close to 0
if (!Numerical.isZero(cross)) {
if (!Numerical.isMachineZero(cross)) {
var dx = p1x - p2x,
dy = p1y - p2y,
u1 = (v2x * dy - v2y * dx) / cross,
@ -175,7 +175,7 @@ var Line = Base.extend(/** @lends Line# */{
v2y = y - py,
// ccw = v2.cross(v1);
ccw = v2x * vy - v2y * vx;
if (!isInfinite && Numerical.isZero(ccw)) {
if (!isInfinite && Numerical.isMachineZero(ccw)) {
// If the point is on the infinite line, check if it's on the
// finite line too: Project v2 onto v1 and determine ccw based
// on which side of the finite line the point lies. Calculate
@ -196,9 +196,13 @@ var Line = Base.extend(/** @lends Line# */{
vy -= py;
}
// Based on the error analysis by @iconexperience outlined in #799
return vx === 0 ? vy > 0 ? x - px : px - x
: vy === 0 ? vx < 0 ? y - py : py - y
: ((x-px) * vy - (y-py) * vx) / Math.sqrt(vx * vx + vy * vy);
return vx === 0 ? (vy > 0 ? x - px : px - x)
: vy === 0 ? (vx < 0 ? y - py : py - y)
: ((x - px) * vy - (y - py) * vx) / (
vy > vx
? vy * Math.sqrt(1 + (vx * vx) / (vy * vy))
: vx * Math.sqrt(1 + (vy * vy) / (vx * vx))
);
},
getDistance: function(px, py, vx, vy, x, y, asVector) {

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*
@ -71,10 +71,11 @@ var Matrix = Base.extend(/** @lends Matrix# */{
* @param {Matrix} matrix the matrix to copy the values from
*/
initialize: function Matrix(arg, _dontNotify) {
var count = arguments.length,
var args = arguments,
count = args.length,
ok = true;
if (count >= 6) { // >= 6 to pass on optional _dontNotify argument.
this._set.apply(this, arguments);
this._set.apply(this, args);
} else if (count === 1 || count === 2) {
// Support both Matrix and Array arguments through #_set(), and pass
// on the optional _dontNotify argument:
@ -104,6 +105,8 @@ var Matrix = Base.extend(/** @lends Matrix# */{
* also work for calls of `set()`.
*
* @function
* @param {...*} values
* @return {Point}
*/
set: '#initialize',
@ -183,15 +186,14 @@ var Matrix = Base.extend(/** @lends Matrix# */{
* Attempts to apply the matrix to the content of item that it belongs to,
* meaning its transformation is baked into the item's content or children.
*
* @param {Boolean} recursively controls whether to apply transformations
* recursively on children
* @param {Boolean} [recursively=true] controls whether to apply
* transformations recursively on children
* @return {Boolean} {@true if the matrix was applied}
*/
apply: function(recursively, _setApplyMatrix) {
var owner = this._owner;
if (owner) {
owner.transform(null, true, Base.pick(recursively, true),
_setApplyMatrix);
owner.transform(null, Base.pick(recursively, true), _setApplyMatrix);
// If the matrix was successfully applied, it will be reset now.
return this.isIdentity();
}
@ -245,8 +247,9 @@ var Matrix = Base.extend(/** @lends Matrix# */{
* @return {Matrix} this affine transform
*/
scale: function(/* scale, center */) {
var scale = Point.read(arguments),
center = Point.read(arguments, 0, { readNull: true });
var args = arguments,
scale = Point.read(args),
center = Point.read(args, 0, { readNull: true });
if (center)
this.translate(center);
this._a *= scale.x;
@ -326,8 +329,9 @@ var Matrix = Base.extend(/** @lends Matrix# */{
shear: function(/* shear, center */) {
// Do not modify point, center, since that would arguments of which
// we're reading from!
var shear = Point.read(arguments),
center = Point.read(arguments, 0, { readNull: true });
var args = arguments,
shear = Point.read(args),
center = Point.read(args, 0, { readNull: true });
if (center)
this.translate(center);
var a = this._a,
@ -362,8 +366,9 @@ var Matrix = Base.extend(/** @lends Matrix# */{
* @return {Matrix} this affine transform
*/
skew: function(/* skew, center */) {
var skew = Point.read(arguments),
center = Point.read(arguments, 0, { readNull: true }),
var args = arguments,
skew = Point.read(args),
center = Point.read(args, 0, { readNull: true }),
toRadians = Math.PI / 180,
shear = new Point(Math.tan(skew.x * toRadians),
Math.tan(skew.y * toRadians));
@ -449,7 +454,7 @@ var Matrix = Base.extend(/** @lends Matrix# */{
/**
* Returns a new matrix as the result of prepending the specified matrix
* to this matrix. This is the equivalent of multiplying
* `(specified matrix) s* (this matrix)`.
* `(specified matrix) * (this matrix)`.
*
* @param {Matrix} matrix the matrix to prepend
* @return {Matrix} the newly created matrix
@ -498,15 +503,15 @@ var Matrix = Base.extend(/** @lends Matrix# */{
},
/**
* @deprecated use use {@link #append(matrix)} instead.
* @deprecated use {@link #append(matrix)} instead.
*/
concatenate: '#append',
/**
* @deprecated use use {@link #prepend(matrix)} instead.
* @deprecated use {@link #prepend(matrix)} instead.
*/
preConcatenate: '#prepend',
/**
* @deprecated use use {@link #appended(matrix)} instead.
* @deprecated use {@link #appended(matrix)} instead.
*/
chain: '#appended',
@ -644,6 +649,7 @@ var Matrix = Base.extend(/** @lends Matrix# */{
* Inverse transforms a point and returns the result.
*
* @param {Point} point the point to be transformed
* @return {Point}
*/
inverseTransform: function(/* point */) {
return this._inverseTransform(Point.read(arguments));

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*
@ -170,6 +170,8 @@ var Point = Base.extend(/** @lends Point# */{
* for calls of `set()`.
*
* @function
* @param {...*} values
* @return {Point}
*/
set: '#initialize',
@ -431,11 +433,12 @@ var Point = Base.extend(/** @lends Point# */{
* @return {Number}
*/
getDistance: function(/* point, squared */) {
var point = Point.read(arguments),
var args = arguments,
point = Point.read(args),
x = point.x - this.x,
y = point.y - this.y,
d = x * x + y * y,
squared = Base.read(arguments);
squared = Base.read(args);
return squared ? d : Math.sqrt(d);
},
@ -709,8 +712,9 @@ var Point = Base.extend(/** @lends Point# */{
* @return {Boolean} {@true if it is within the given distance}
*/
isClose: function(/* point, tolerance */) {
var point = Point.read(arguments),
tolerance = Base.read(arguments);
var args = arguments,
point = Point.read(args),
tolerance = Base.read(args);
return this.getDistance(point) <= tolerance;
},
@ -768,7 +772,7 @@ var Point = Base.extend(/** @lends Point# */{
*
* @param {Number} quadrant the quadrant to check against
* @return {Boolean} {@true if either x or y are not a number}
* @see #getQuadrant()
* @see #quadrant
*/
isInQuadrant: function(q) {
// Map quadrant to x & y coordinate pairs and multiply with coordinates,
@ -935,8 +939,9 @@ var Point = Base.extend(/** @lends Point# */{
* [point1, point2, point3].reduce(Point.min) // {x: 60, y: 5}
*/
min: function(/* point1, point2 */) {
var point1 = Point.read(arguments),
point2 = Point.read(arguments);
var args = arguments,
point1 = Point.read(args),
point2 = Point.read(args);
return new Point(
Math.min(point1.x, point2.x),
Math.min(point1.y, point2.y)
@ -966,8 +971,9 @@ var Point = Base.extend(/** @lends Point# */{
* [point1, point2, point3].reduce(Point.max) // {x: 250, y: 100}
*/
max: function(/* point1, point2 */) {
var point1 = Point.read(arguments),
point2 = Point.read(arguments);
var args = arguments,
point1 = Point.read(args),
point2 = Point.read(args);
return new Point(
Math.max(point1.x, point2.x),
Math.max(point1.y, point2.y)

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*
@ -73,10 +73,11 @@ var Rectangle = Base.extend(/** @lends Rectangle# */{
* Creates a new rectangle object from the passed rectangle object.
*
* @name Rectangle#initialize
* @param {Rectangle} rt
* @param {Rectangle} rectangle
*/
initialize: function Rectangle(arg0, arg1, arg2, arg3) {
var type = typeof arg0,
var args = arguments,
type = typeof arg0,
read;
if (type === 'number') {
// new Rectangle(x, y, width, height)
@ -86,7 +87,7 @@ var Rectangle = Base.extend(/** @lends Rectangle# */{
// new Rectangle(), new Rectangle(null)
this._set(0, 0, 0, 0);
read = arg0 === null ? 1 : 0;
} else if (arguments.length === 1) {
} else if (args.length === 1) {
// This can either be an array, or an object literal.
if (Array.isArray(arg0)) {
this._set.apply(this, arg0);
@ -98,30 +99,31 @@ var Rectangle = Base.extend(/** @lends Rectangle# */{
arg0.width || 0, arg0.height || 0);
read = 1;
} else if (arg0.from === undefined && arg0.to === undefined) {
// Use Base.filter() to support whatever property the rectangle
// can take, but handle from/to separately below.
// Use `Base.readSupported()` to read and consume whatever
// property the rectangle can receive, but handle `from` / `to`
// separately below.
this._set(0, 0, 0, 0);
Base.filter(this, arg0);
if (Base.readSupported(args, this)) {
read = 1;
}
}
}
if (read === undefined) {
// Read a point argument and look at the next value to see whether
// it's a size or a point, then read accordingly.
// We're supporting both reading from a normal arguments list and
// covering the Rectangle({ from: , to: }) constructor, through
// Point.readNamed().
var frm = Point.readNamed(arguments, 'from'),
next = Base.peek(arguments),
var frm = Point.readNamed(args, 'from'),
next = Base.peek(args),
x = frm.x,
y = frm.y,
width,
height;
if (next && next.x !== undefined
|| Base.hasNamed(arguments, 'to')) {
if (next && next.x !== undefined || Base.hasNamed(args, 'to')) {
// new Rectangle(from, to)
// Read above why we can use readNamed() to cover both cases.
var to = Point.readNamed(arguments, 'to');
var to = Point.readNamed(args, 'to');
width = to.x - x;
height = to.y - y;
// Check if horizontal or vertical order needs to be reversed.
@ -135,19 +137,19 @@ var Rectangle = Base.extend(/** @lends Rectangle# */{
}
} else {
// new Rectangle(point, size)
var size = Size.read(arguments);
var size = Size.read(args);
width = size.width;
height = size.height;
}
this._set(x, y, width, height);
read = arguments.__index;
read = args.__index;
}
// arguments.__filtered wouldn't survive the function call even if a
// previous arguments list was passed through Function#apply().
// Return it on the object instead, see Base.read()
var filtered = arguments.__filtered;
var filtered = args.__filtered;
if (filtered)
this.__filtered = filtered;
}
if (this.__read)
this.__read = read;
return this;
@ -159,6 +161,8 @@ var Rectangle = Base.extend(/** @lends Rectangle# */{
* constructors also work for calls of `set()`.
*
* @function
* @param {...*} values
* @return {Rectangle}
*/
set: '#initialize',
@ -201,6 +205,7 @@ var Rectangle = Base.extend(/** @lends Rectangle# */{
/**
* Returns a copy of the rectangle.
* @return {Rectangle}
*/
clone: function() {
return new Rectangle(this.x, this.y, this.width, this.height);
@ -772,6 +777,8 @@ var Rectangle = Base.extend(/** @lends Rectangle# */{
* Rectangle#contains(point)} returns `false` for that point.
*
* @param {Point} point
* @return {Rectangle} the smallest rectangle that contains both the
* original rectangle and the specified point
*/
include: function(/* point */) {
var point = Point.read(arguments);
@ -783,17 +790,18 @@ var Rectangle = Base.extend(/** @lends Rectangle# */{
},
/**
* Expands the rectangle by the specified amount in horizontal and
* vertical directions.
* Returns a new rectangle expanded by the specified amount in horizontal
* and vertical directions.
*
* @name Rectangle#expand
* @function
* @param {Number|Size|Point} amount the amount to expand the rectangle in
* both directions
* @return {Rectangle} the expanded rectangle
*/
/**
* Expands the rectangle by the specified amounts in horizontal and
* vertical directions.
* Returns a new rectangle expanded by the specified amounts in horizontal
* and vertical directions.
*
* @name Rectangle#expand
* @function
@ -801,6 +809,7 @@ var Rectangle = Base.extend(/** @lends Rectangle# */{
* direction
* @param {Number} ver the amount to expand the rectangle in vertical
* direction
* @return {Rectangle} the expanded rectangle
*/
expand: function(/* amount */) {
var amount = Size.read(arguments),
@ -811,21 +820,23 @@ var Rectangle = Base.extend(/** @lends Rectangle# */{
},
/**
* Scales the rectangle by the specified amount from its center.
* Returns a new rectangle scaled by the specified amount from its center.
*
* @name Rectangle#scale
* @function
* @param {Number} amount
* @return {Rectangle} the scaled rectangle
*/
/**
* Scales the rectangle in horizontal direction by the specified `hor`
* amount and in vertical direction by the specified `ver` amount from its
* center.
* Returns a new rectangle scaled in horizontal direction by the specified
* `hor` amount and in vertical direction by the specified `ver` amount
* from its center.
*
* @name Rectangle#scale
* @function
* @param {Number} hor
* @param {Number} ver
* @return {Rectangle} the scaled rectangle
*/
scale: function(hor, ver) {
return this.expand(this.width * hor - this.width,

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*
@ -130,6 +130,8 @@ var Size = Base.extend(/** @lends Size# */{
* for calls of `set()`.
*
* @function
* @param {...*} values
* @return {Size}
*/
set: '#initialize',
@ -158,7 +160,7 @@ var Size = Base.extend(/** @lends Size# */{
* Checks whether the width and height of the size are equal to those of the
* supplied size.
*
* @param {Size}
* @param {Size} size the size to compare to
* @return {Boolean}
*
* @example
@ -176,6 +178,7 @@ var Size = Base.extend(/** @lends Size# */{
/**
* Returns a copy of the size.
* @return {Size}
*/
clone: function() {
return new Size(this.width, this.height);

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*
@ -269,11 +269,11 @@ statics: /** @lends Base */{
},
/**
* Allows using of Base.read() mechanism in combination with reading named
* arguments form a passed property object literal. Calling Base.readNamed()
* can read both from such named properties and normal unnamed arguments
* through Base.read(). In use for example for the various
* Path.Constructors.
* Allows using of `Base.read()` mechanism in combination with reading named
* arguments form a passed property object literal. Calling
* `Base.readNamed()` can read both from such named properties and normal
* unnamed arguments through `Base.read()`. In use for example for
* the various `Path` constructors in `Path.Constructors.js`.
*
* @param {Array} list the list to read from, either an arguments object or
* a normal array
@ -287,24 +287,68 @@ statics: /** @lends Base */{
*/
readNamed: function(list, name, start, options, amount) {
var value = this.getNamed(list, name),
hasObject = value !== undefined;
if (hasObject) {
// Create a _filtered object that inherits from list[0], and
hasValue = value !== undefined;
if (hasValue) {
// Create a _filtered object that inherits from `source`, and
// override all fields that were already read with undefined.
var filtered = list.__filtered;
if (!filtered) {
filtered = list.__filtered = Base.create(list[0]);
// Point _unfiltered to the original so Base#_set() can
// execute hasOwnProperty on it.
filtered.__unfiltered = list[0];
var source = this.getSource(list);
filtered = list.__filtered = Base.create(source);
// Point __unfiltered to the original, so `Base.filter()` can
// use it to get all keys to iterate over.
filtered.__unfiltered = source;
}
// delete wouldn't work since the masked parent's value would
// shine through.
filtered[name] = undefined;
}
var l = hasObject ? [value] : list,
res = this.read(l, start, options, amount);
return res;
return this.read(hasValue ? [value] : list, start, options, amount);
},
/**
* If `list[0]` is a source object, calls `Base.readNamed()` for each key in
* it that is supported on `dest`, consuming these values.
*
* @param {Array} list the list to read from, either an arguments object or
* a normal array
* @param {Object} dest the object on which to set the supported properties
* @return {Boolean} {@true if any property was read from the source object}
*/
readSupported: function(list, dest) {
var source = this.getSource(list),
that = this,
read = false;
if (source) {
// If `source` is a filtered object, we get the keys from the the
// original object (it's parent / prototype). See _filtered
// inheritance trick in the argument reading code.
Object.keys(source).forEach(function(key) {
if (key in dest) {
var value = that.readNamed(list, key);
// Due to the _filtered inheritance trick, undefined is used
// to mask already consumed named arguments.
if (value !== undefined) {
dest[key] = value;
}
read = true;
}
});
}
return read;
},
/**
* @return the arguments object if the list provides one at `list[0]`
*/
getSource: function(list) {
var source = list.__source;
if (source === undefined) {
var arg = list.length === 1 && list[0];
source = list.__source = arg && Base.isPlainObject(arg)
? arg : null;
}
return source;
},
/**
@ -314,12 +358,11 @@ statics: /** @lends Base */{
* provided, it returns the whole arguments object
*/
getNamed: function(list, name) {
var arg = list[0];
if (list._hasObject === undefined)
list._hasObject = list.length === 1 && Base.isPlainObject(arg);
if (list._hasObject)
var source = this.getSource(list);
if (source) {
// Return the whole arguments object if no name is provided.
return name ? arg[name] : list.__filtered || arg;
return name ? source[name] : list.__filtered || source;
}
},
/**
@ -558,8 +601,16 @@ statics: /** @lends Base */{
if (args.length === 1 && obj instanceof Item
&& (useTarget || !(obj instanceof Layer))) {
var arg = args[0];
if (Base.isPlainObject(arg))
if (Base.isPlainObject(arg)) {
arg.insert = false;
// When using target, make sure the `item.insert()`
// method is not overridden with the `arg.insert`
// property that was just set. Pass an exclude
// object to the call of `obj.set()` below (#1392).
if (useTarget) {
args = args.concat([{ insert: true }]);
}
}
}
// When reusing an object, initialize it through #set()
// instead of the constructor function:

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*
@ -68,9 +68,9 @@ var Emitter = {
},
once: function(type, func) {
return this.on(type, function() {
return this.on(type, function handler() {
func.apply(this, arguments);
this.off(type, func);
this.off(type, handler);
});
},

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*
@ -87,7 +87,7 @@ var PaperScope = Base.extend(/** @lends PaperScope# */{
// here: { chrome: true, webkit: false }, Mozilla missing is the
// only difference to jQuery.browser
user.replace(
/(opera|chrome|safari|webkit|firefox|msie|trident|atom|node)\/?\s*([.\d]+)(?:.*version\/([.\d]+))?(?:.*rv\:v?([.\d]+))?/g,
/(opera|chrome|safari|webkit|firefox|msie|trident|atom|node|jsdom)\/?\s*([.\d]+)(?:.*version\/([.\d]+))?(?:.*rv\:v?([.\d]+))?/g,
function(match, n, v1, v2, rv) {
// Do not set additional browsers once chrome is detected.
if (!agent.chrome) {
@ -95,7 +95,7 @@ var PaperScope = Base.extend(/** @lends PaperScope# */{
/^(node|trident)$/.test(n) ? rv : v1;
agent.version = v;
agent.versionNumber = parseFloat(v);
n = n === 'trident' ? 'msie' : n;
n = { trident: 'msie', jsdom: 'node' }[n] || n;
agent.name = n;
agent[n] = true;
}
@ -112,6 +112,7 @@ var PaperScope = Base.extend(/** @lends PaperScope# */{
* The version of Paper.js, as a string.
*
* @type String
* @readonly
*/
version: /*#=*/__options.version,
@ -200,7 +201,7 @@ var PaperScope = Base.extend(/** @lends PaperScope# */{
* mapping, in case the code that's passed in has already been mingled.
*
* @param {String} code the PaperScript code
* @param {Object} [option] the compilation options
* @param {Object} [options] the compilation options
*/
execute: function(code, options) {
/*#*/ if (__options.paperScript) {
@ -310,6 +311,7 @@ var PaperScope = Base.extend(/** @lends PaperScope# */{
* Retrieves a PaperScope object with the given scope id.
*
* @param id
* @return {PaperScope}
*/
get: function(id) {
return this._scopes[id] || null;

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*
@ -127,7 +127,7 @@ Base.exports.PaperScript = function() {
* mapping, in case the code that's passed in has already been mingled.
*
* @param {String} code the PaperScript code
* @param {Object} [option] the compilation options
* @param {Object} [options] the compilation options
* @return {Object} an object holding the compiled PaperScript translated
* into JavaScript code along with source-maps and other information.
*/
@ -177,7 +177,7 @@ Base.exports.PaperScript = function() {
var start = getOffset(node.range[0]),
end = getOffset(node.range[1]),
insert = 0;
// Sort insertions by their offset, so getOffest() can do its thing
// Sort insertions by their offset, so getOffset() can do its thing
for (var i = insertions.length - 1; i >= 0; i--) {
if (start > insertions[i][0]) {
insert = i + 1;
@ -188,26 +188,7 @@ Base.exports.PaperScript = function() {
code = code.substring(0, start) + str + code.substring(end);
}
// Recursively walks the AST and replaces the code of certain nodes
function walkAST(node, parent) {
if (!node)
return;
// The easiest way to walk through the whole AST is to simply loop
// over each property of the node and filter out fields we don't
// need to consider...
for (var key in node) {
if (key === 'range' || key === 'loc')
continue;
var value = node[key];
if (Array.isArray(value)) {
for (var i = 0, l = value.length; i < l; i++)
walkAST(value[i], node);
} else if (value && typeof value === 'object') {
// We cannot use Base.isPlainObject() for these since
// Acorn.js uses its own internal prototypes now.
walkAST(value, node);
}
}
function handleOverloading(node, parent) {
switch (node.type) {
case 'UnaryExpression': // -a
if (node.operator in unaryOperators
@ -256,12 +237,19 @@ Base.exports.PaperScript = function() {
exp = '__$__(' + arg + ', "' + node.operator[0]
+ '", 1)',
str = arg + ' = ' + exp;
// If this is not a prefixed update expression
// (++a, --a), assign the old value before updating it.
if (!node.prefix
&& (parentType === 'AssignmentExpression'
|| parentType === 'VariableDeclarator')) {
// Handle special issue #691 where the old value is
if (node.prefix) {
// A prefixed update expression (++a / --a),
// wrap expression in paranthesis. See #1611
str = '(' + str + ')';
} else if (
// A suffixed update expression (a++, a--),
// assign the old value before updating it.
// See #691, #1450
parentType === 'AssignmentExpression' ||
parentType === 'VariableDeclarator' ||
parentType === 'BinaryExpression'
) {
// Handle special case where the old value is
// assigned to itself, and the expression is just
// executed after, e.g.: `var x = ***; x = x++;`
if (getCode(parent.left || parent.id) === arg)
@ -284,6 +272,11 @@ Base.exports.PaperScript = function() {
}
}
break;
}
}
function handleExports(node) {
switch (node.type) {
case 'ExportDefaultDeclaration':
// Convert `export default` to `module.exports = ` statements:
replaceCode({
@ -321,9 +314,38 @@ Base.exports.PaperScript = function() {
}
}
// Recursively walks the AST and replaces the code of certain nodes
function walkAST(node, parent, paperFeatures) {
if (node) {
// The easiest way to walk through the whole AST is to simply
// loop over each property of the node and filter out fields we
// don't need to consider...
for (var key in node) {
if (key !== 'range' && key !== 'loc') {
var value = node[key];
if (Array.isArray(value)) {
for (var i = 0, l = value.length; i < l; i++) {
walkAST(value[i], node, paperFeatures);
}
} else if (value && typeof value === 'object') {
// Don't use Base.isPlainObject() for these since
// Acorn.js uses its own internal prototypes now.
walkAST(value, node, paperFeatures);
}
}
}
if (paperFeatures.operatorOverloading !== false) {
handleOverloading(node, parent);
}
if (paperFeatures.moduleExports !== false) {
handleExports(node);
}
}
}
// Source-map support:
// Encodes a Variable Length Quantity as a Base64 string.
// See: http://www.html5rocks.com/en/tutorials/developertools/sourcemaps
// See: https://www.html5rocks.com/en/tutorials/developertools/sourcemaps/
function encodeVLQ(value) {
var res = '',
base64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
@ -339,15 +361,16 @@ Base.exports.PaperScript = function() {
}
var url = options.url || '',
agent = paper.agent,
version = agent.versionNumber,
offsetCode = false,
sourceMaps = options.sourceMaps,
paperFeatures = options.paperFeatures || {},
// Include the original code in the sourceMap if there is no linked
// source file so the debugger can still display it correctly.
source = options.source || code,
lineBreaks = /\r\n|\n|\r/mg,
offset = options.offset || 0,
agent = paper.agent,
version = agent.versionNumber,
offsetCode = false,
lineBreaks = /\r\n|\n|\r/mg,
map;
// TODO: Verify these browser versions for source map support, and check
// other browsers.
@ -397,12 +420,17 @@ Base.exports.PaperScript = function() {
sourcesContent: [source]
};
}
if (
paperFeatures.operatorOverloading !== false ||
paperFeatures.moduleExports !== false
) {
// Now do the parsing magic
walkAST(parse(code, {
ranges: true,
preserveParens: true,
sourceType: 'module'
}));
}), null, paperFeatures);
}
if (map) {
if (offsetCode) {
// Adjust the line offset of the resulting code if required.
@ -441,8 +469,8 @@ Base.exports.PaperScript = function() {
*
* @param {String} code the PaperScript code
* @param {PaperScope} scope the scope for which the code is executed
* @param {Object} [option] the compilation options
* @return the exports defined in the executed code
* @param {Object} [options] the compilation options
* @return {Object} the exports defined in the executed code
*/
function execute(code, scope, options) {
// Set currently active scope.
@ -484,7 +512,7 @@ Base.exports.PaperScript = function() {
}
}
}
expose({ __$__: __$__, $__: $__, paper: scope, view: view, tool: tool },
expose({ __$__: __$__, $__: $__, paper: scope, tool: tool },
true);
expose(scope);
// Add a fake `module.exports` object so PaperScripts can export things.
@ -657,7 +685,9 @@ Base.exports.PaperScript = function() {
compile: compile,
execute: execute,
load: load,
parse: parse
parse: parse,
calculateBinary: __$__,
calculateUnary: $__
};
// Pass on `this` as the binding object, so we can reference Acorn both in
// development and in the built library.

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*
@ -39,7 +39,7 @@
*
* The project for which the PaperScript is executed.
*
* Note that when working with mulitple projects, this does not necessarily
* Note that when working with multiple projects, this does not necessarily
* reflect the currently active project. For this, use
* {@link PaperScope#project} instead.
*
@ -57,19 +57,20 @@
/**
* The reference to the project's view.
*
* Note that when working with mulitple projects, this does not necessarily
* Note that when working with multiple projects, this does not necessarily
* reflect the view of the currently active project. For this, use
* {@link PaperScope#view} instead.
*
* @name view
* @type View
* @readonly
*/
/**
* The reference to the tool object which is automatically created when global
* tool event handlers are defined.
*
* Note that when working with mulitple tools, this does not necessarily
* Note that when working with multiple tools, this does not necessarily
* reflect the currently active tool. For this, use {@link PaperScope#tool}
* instead.
*

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*
@ -171,8 +171,7 @@ var Group = Item.extend(/** @lends Group# */{
_getBounds: function _getBounds(matrix, options) {
var clipItem = this._getClipItem();
return clipItem
? clipItem._getCachedBounds(
matrix && matrix.appended(clipItem._matrix),
? clipItem._getCachedBounds(clipItem._matrix.prepended(matrix),
Base.set({}, options, { stroke: false }))
: _getBounds.base.call(this, matrix, options);
},

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*
@ -75,7 +75,7 @@ var HitResult = Base.extend(/** @lends HitResult# */{
*
* @name HitResult#color
* @property
* @type Color
* @type ?Color
*/
/**
@ -105,7 +105,7 @@ var HitResult = Base.extend(/** @lends HitResult# */{
*/
getOptions: function(args) {
var options = args && Base.read(args);
return Base.set({
return new Base({
// Type of item, for instanceof check: Group, Layer, Path,
// CompoundPath, Shape, Raster, SymbolItem, ...
type: null,

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*
@ -658,8 +658,8 @@ new function() { // Injection scope for various item event handlers
/**
* Specifies whether the item defines a clip mask. This can only be set on
* paths, compound paths, and text frame objects, and only if the item is
* already contained within a clipping group.
* paths and compound paths, and only if the item is already contained
* within a clipping group.
*
* @bean
* @type Boolean
@ -1075,7 +1075,9 @@ new function() { // Injection scope for various item event handlers
options = options || {};
for (var i = 0, l = items.length; i < l; i++) {
var item = items[i];
if (item._visible && !item.isEmpty()) {
// Item is handled if it is visible and not recursively empty.
// This avoid errors with nested empty groups (#1467).
if (item._visible && !item.isEmpty(true)) {
// Pass true for noInternal, since even when getting
// internal bounds for this item, we need to apply the
// matrices to its children.
@ -1120,6 +1122,17 @@ new function() { // Injection scope for various item event handlers
* @type Rectangle
*/
/**
* The bounding rectangle of the item without any matrix transformations.
*
* Typical use case would be drawing a frame around the object where you
* want to draw something of the same size, position, rotation, and scaling,
* like a selection frame.
*
* @name Item#internalBounds
* @type Rectangle
*/
/**
* The rough bounding rectangle of the item that is sure to include all of
* the drawing, including stroke width.
@ -1805,11 +1818,15 @@ new function() { // Injection scope for various item event handlers
* }
*
* @param {Point} point the point to check for
* @return {Boolean}
*/
contains: function(/* point */) {
// See CompoundPath#_contains() for the reason for !!
return !!this._contains(
this._matrix._inverseTransform(Point.read(arguments)));
var matrix = this._matrix;
return (
matrix.isInvertible() &&
!!this._contains(matrix._inverseTransform(Point.read(arguments)))
);
},
_contains: function(point) {
@ -1866,16 +1883,18 @@ new function() { // Injection scope for various item event handlers
},
new function() { // Injection scope for hit-test functions shared with project
function hitTest(/* point, options */) {
var args = arguments;
return this._hitTest(
Point.read(arguments),
HitResult.getOptions(arguments));
Point.read(args),
HitResult.getOptions(args));
}
function hitTestAll(/* point, options */) {
var point = Point.read(arguments),
options = HitResult.getOptions(arguments),
var args = arguments,
point = Point.read(args),
options = HitResult.getOptions(args),
all = [];
this._hitTest(point, Base.set({ all: all }, options));
this._hitTest(point, new Base({ all: all }, options));
return all;
}
@ -2353,6 +2372,7 @@ new function() { // Injection scope for hit-test functions shared with project
* items can have children.
*
* @param {String} json the JSON data to import from
* @return {Item}
*/
importJSON: function(json) {
// Try importing into `this`. If another item is returned, try adding
@ -2388,7 +2408,8 @@ new function() { // Injection scope for hit-test functions shared with project
* kept as a link to their external URL.
*
* @param {Object} [options] the export options
* @return {SVGElement} the item converted to an SVG node
* @return {SVGElement|String} the item converted to an SVG node or a
* `String` depending on `option.asString` value
*/
/**
@ -2772,6 +2793,7 @@ new function() { // Injection scope for hit-test functions shared with project
* Replaces this item with the provided new item which will takes its place
* in the project hierarchy instead.
*
* @param {Item} item the item that will replace this item
* @return {Boolean} {@true if the item was replaced}
*/
replaceWith: function(item) {
@ -2840,11 +2862,23 @@ new function() { // Injection scope for hit-test functions shared with project
* no children, a {@link TextItem} with no text content and a {@link Path}
* with no segments all are considered empty.
*
* @return Boolean
* @param {Boolean} [recursively=false] whether an item with children should be
* considered empty if all its descendants are empty
* @return {Boolean}
*/
isEmpty: function() {
isEmpty: function(recursively) {
var children = this._children;
return !children || !children.length;
var numChildren = children ? children.length : 0;
if (recursively) {
// In recursive check, item is empty if all its children are empty.
for (var i = 0; i < numChildren; i++) {
if (!children[i].isEmpty(recursively)) {
return false;
}
}
return true;
}
return !numChildren;
},
/**
@ -2907,7 +2941,7 @@ new function() { // Injection scope for hit-test functions shared with project
* defined in such a way, e.g. if one is a descendant of the other.
*/
_getOrder: function(item) {
// Private method that produces a list of anchestors, starting with the
// Private method that produces a list of ancestors, starting with the
// root and ending with the actual element as the last entry.
function getList(item) {
var list = [];
@ -3057,7 +3091,7 @@ new function() { // Injection scope for hit-test functions shared with project
*
* @name Item#strokeColor
* @property
* @type Color
* @type ?Color
*
* @example {@paperscript}
* // Setting the stroke color of a path:
@ -3194,7 +3228,7 @@ new function() { // Injection scope for hit-test functions shared with project
*
* @name Item#dashArray
* @property
* @type Array
* @type Number[]
* @default []
*/
@ -3219,7 +3253,7 @@ new function() { // Injection scope for hit-test functions shared with project
*
* @name Item#fillColor
* @property
* @type Color
* @type ?Color
*
* @example {@paperscript}
* // Setting the fill color of a path to red:
@ -3253,7 +3287,7 @@ new function() { // Injection scope for hit-test functions shared with project
*
* @property
* @name Item#shadowColor
* @type Color
* @type ?Color
*
* @example {@paperscript}
* // Creating a circle with a black shadow:
@ -3299,13 +3333,14 @@ new function() { // Injection scope for hit-test functions shared with project
*
* @name Item#selectedColor
* @property
* @type Color
* @type ?Color
*/
}, Base.each(['rotate', 'scale', 'shear', 'skew'], function(key) {
var rotate = key === 'rotate';
this[key] = function(/* value, center */) {
var value = (rotate ? Base : Point).read(arguments),
center = Point.read(arguments, 0, { readNull: true });
var args = arguments,
value = (rotate ? Base : Point).read(args),
center = Point.read(args, 0, { readNull: true });
return this.transform(new Matrix()[key](value,
center || this.getPosition(true)));
};
@ -3438,7 +3473,7 @@ new function() { // Injection scope for hit-test functions shared with project
*
* @name Item#shear
* @function
* @param {Point} shear the horziontal and vertical shear factors as a point
* @param {Point} shear the horizontal and vertical shear factors as a point
* @param {Point} [center={@link Item#position}]
* @see Matrix#shear(shear[, center])
*/
@ -3460,7 +3495,7 @@ new function() { // Injection scope for hit-test functions shared with project
*
* @name Item#skew
* @function
* @param {Point} skew the horziontal and vertical skew angles in degrees
* @param {Point} skew the horizontal and vertical skew angles in degrees
* @param {Point} [center={@link Item#position}]
* @see Matrix#shear(skew[, center])
*/
@ -3485,19 +3520,22 @@ new function() { // Injection scope for hit-test functions shared with project
// @param {String[]} flags array of any of the following: 'objects',
// 'children', 'fill-gradients', 'fill-patterns', 'stroke-patterns',
// 'lines'. Default: ['objects', 'children']
transform: function(matrix, _applyMatrix, _applyRecursively,
_setApplyMatrix) {
transform: function(matrix, _applyRecursively, _setApplyMatrix) {
var _matrix = this._matrix,
// If no matrix is provided, or the matrix is the identity, we might
// still have some work to do in case _applyMatrix is true
transformMatrix = matrix && !matrix.isIdentity(),
applyMatrix = (_applyMatrix || this._applyMatrix)
// If no matrix is provided, or the matrix is the identity, we might
// still have some work to do: _setApplyMatrix or _applyRecursively.
applyMatrix = (
_setApplyMatrix && this._canApplyMatrix ||
this._applyMatrix && (
// Don't apply _matrix if the result of concatenating with
// matrix would be identity.
&& ((!_matrix.isIdentity() || transformMatrix)
// Even if it's an identity matrix, we still need to
transformMatrix || !_matrix.isIdentity() ||
// Even if it's an identity matrix, we may still need to
// recursively apply the matrix to children.
|| _applyMatrix && _applyRecursively && this._children);
_applyRecursively && this._children
)
);
// Bail out if there is nothing to do.
if (!transformMatrix && !applyMatrix)
return this;
@ -3529,8 +3567,9 @@ 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))) {
if (applyMatrix && (applyMatrix = this._transformContent(
_matrix, _applyRecursively, _setApplyMatrix))) {
// Pivot is provided in the parent's coordinate system, so transform
// it along too.
var pivot = this._pivot;
@ -3594,9 +3633,9 @@ new function() { // Injection scope for hit-test functions shared with project
_transformContent: function(matrix, applyRecursively, setApplyMatrix) {
var children = this._children;
if (children) {
for (var i = 0, l = children.length; i < l; i++)
children[i].transform(matrix, true, applyRecursively,
setApplyMatrix);
for (var i = 0, l = children.length; i < l; i++) {
children[i].transform(matrix, applyRecursively, setApplyMatrix);
}
return true;
}
},
@ -3745,7 +3784,7 @@ new function() { // Injection scope for hit-test functions shared with project
*
* @name Item#onFrame
* @property
* @type Function
* @type ?Function
* @see View#onFrame
*
* @example {@paperscript}
@ -3772,7 +3811,7 @@ new function() { // Injection scope for hit-test functions shared with project
*
* @name Item#onMouseDown
* @property
* @type Function
* @type ?Function
* @see View#onMouseDown
*
* @example {@paperscript}
@ -3822,7 +3861,7 @@ new function() { // Injection scope for hit-test functions shared with project
*
* @name Item#onMouseDrag
* @property
* @type Function
* @type ?Function
* @see View#onMouseDrag
*
* @example {@paperscript height=240}
@ -3851,7 +3890,7 @@ new function() { // Injection scope for hit-test functions shared with project
*
* @name Item#onMouseUp
* @property
* @type Function
* @type ?Function
* @see View#onMouseUp
*
* @example {@paperscript}
@ -3881,7 +3920,7 @@ new function() { // Injection scope for hit-test functions shared with project
*
* @name Item#onClick
* @property
* @type Function
* @type ?Function
* @see View#onClick
*
* @example {@paperscript}
@ -3931,7 +3970,7 @@ new function() { // Injection scope for hit-test functions shared with project
*
* @name Item#onDoubleClick
* @property
* @type Function
* @type ?Function
* @see View#onDoubleClick
*
* @example {@paperscript}
@ -3981,7 +4020,7 @@ new function() { // Injection scope for hit-test functions shared with project
*
* @name Item#onMouseMove
* @property
* @type Function
* @type ?Function
* @see View#onMouseMove
*
* @example {@paperscript}
@ -4012,7 +4051,7 @@ new function() { // Injection scope for hit-test functions shared with project
*
* @name Item#onMouseEnter
* @property
* @type Function
* @type ?Function
* @see View#onMouseEnter
*
* @example {@paperscript}
@ -4074,7 +4113,7 @@ new function() { // Injection scope for hit-test functions shared with project
*
* @name Item#onMouseLeave
* @property
* @type Function
* @type ?Function
* @see View#onMouseLeave
*
* @example {@paperscript}
@ -4334,7 +4373,7 @@ new function() { // Injection scope for hit-test functions shared with project
// Exclude Raster items since they never draw a stroke and handle
// opacity by themselves (they also don't call _setStyles)
var blendMode = this._blendMode,
opacity = this._opacity,
opacity = Numerical.clamp(this._opacity, 0, 1),
normalBlend = blendMode === 'normal',
nativeBlend = BlendMode.nativeModes[blendMode],
// Determine if we can draw directly, or if we need to draw into a
@ -4417,8 +4456,10 @@ new function() { // Injection scope for hit-test functions shared with project
this._draw(ctx, param, viewMatrix, strokeMatrix);
ctx.restore();
matrices.pop();
if (param.clip && !param.dontFinish)
ctx.clip();
if (param.clip && !param.dontFinish) {
// Pass fill-rule to handle clipping with compound-paths (#1361).
ctx.clip(this.getFillRule());
}
// If a temporary canvas was created, composite it onto the main canvas:
if (!direct) {
// Use BlendMode.process even for processing normal blendMode with
@ -4672,4 +4713,176 @@ new function() { // Injection scope for hit-test functions shared with project
}
return this;
}
}));
}), /** @lends Item# */{
/**
* {@grouptitle Tweening Functions}
*
* Tween item between two states.
*
* @name Item#tween
*
* @option options.duration {Number} the duration of the tweening
* @option [options.easing='linear'] {Function|String} an easing function or the type
* of the easing: {@values 'linear' 'easeInQuad' 'easeOutQuad'
* 'easeInOutQuad' 'easeInCubic' 'easeOutCubic' 'easeInOutCubic'
* 'easeInQuart' 'easeOutQuart' 'easeInOutQuart' 'easeInQuint'
* 'easeOutQuint' 'easeInOutQuint'}
* @option [options.start=true] {Boolean} whether to start tweening automatically
*
* @function
* @param {Object} from the state at the start of the tweening
* @param {Object} to the state at the end of the tweening
* @param {Object|Number} options the options or the duration
* @return {Tween}
*
* @example {@paperscript height=100}
* // Tween fillColor:
* var path = new Path.Circle({
* radius: view.bounds.height * 0.4,
* center: view.center
* });
* path.tween(
* { fillColor: 'blue' },
* { fillColor: 'red' },
* 3000
* );
* @example {@paperscript height=100}
* // Tween rotation:
* var path = new Shape.Rectangle({
* fillColor: 'red',
* center: [50, view.center.y],
* size: [60, 60]
* });
* path.tween({
* rotation: 180,
* 'position.x': view.bounds.width - 50,
* 'fillColor.hue': '+= 90'
* }, {
* easing: 'easeInOutCubic',
* duration: 2000
* });
*/
/**
* Tween item to a state.
*
* @name Item#tween
*
* @function
* @param {Object} to the state at the end of the tweening
* @param {Object|Number} options the options or the duration
* @return {Tween}
*
* @example {@paperscript height=200}
* // Tween a nested property with relative values
* var path = new Path.Rectangle({
* size: [100, 100],
* position: view.center,
* fillColor: 'red',
* });
*
* var delta = { x: path.bounds.width / 2, y: 0 };
*
* path.tween({
* 'segments[1].point': ['+=', delta],
* 'segments[2].point.x': '-= 50'
* }, 3000);
*
* @see Item#tween(from, to, options)
*/
/**
* Tween item.
*
* @name Item#tween
*
* @function
* @param {Object|Number} options the options or the duration
* @return {Tween}
*
* @see Item#tween(from, to, options)
*
* @example {@paperscript height=100}
* // Start an empty tween and just use the update callback:
* var path = new Path.Circle({
* fillColor: 'blue',
* radius: view.bounds.height * 0.4,
* center: view.center,
* });
* var pathFrom = path.clone({ insert: false })
* var pathTo = new Path.Rectangle({
* position: view.center,
* rectangle: path.bounds,
* insert: false
* });
* path.tween(2000).onUpdate = function(event) {
* path.interpolate(pathFrom, pathTo, event.factor)
* };
*/
tween: function(from, to, options) {
if (!options) {
// If there are only two or one arguments, shift arguments to the
// left by one (omit `from`):
options = to;
to = from;
from = null;
if (!options) {
options = to;
to = null;
}
}
var easing = options && options.easing,
start = options && options.start,
duration = options != null && (
typeof options === 'number' ? options : options.duration
),
tween = new Tween(this, from, to, duration, easing, start);
function onFrame(event) {
tween._handleFrame(event.time * 1000);
if (!tween.running) {
this.off('frame', onFrame);
}
}
if (duration) {
this.on('frame', onFrame);
}
return tween;
},
/**
*
* Tween item to a state.
*
* @function
* @param {Object} to the state at the end of the tweening
* @param {Object|Number} options the options or the duration
* @return {Tween}
*
* @see Item#tween(to, options)
*/
tweenTo: function(to, options) {
return this.tween(null, to, options);
},
/**
*
* Tween item from a state to its state before the tweening.
*
* @function
* @param {Object} from the state at the start of the tweening
* @param {Object|Number} options the options or the duration
* @return {Tween}
*
* @see Item#tween(from, to, options)
*
* @example {@paperscript height=100}
* // Tween fillColor from red to the path's initial fillColor:
* var path = new Path.Circle({
* fillColor: 'blue',
* radius: view.bounds.height * 0.4,
* center: view.center
* });
* path.tweenFrom({ fillColor: 'red' }, { duration: 1000 });
*/
tweenFrom: function(from, options) {
return this.tween(from, null, options);
}
});

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*
@ -133,7 +133,7 @@ var Project = PaperScopeItem.extend(/** @lends Project# */{
/**
* Checks whether the project has any content or not.
*
* @return Boolean
* @return {Boolean}
*/
isEmpty: function() {
return !this._children.length;
@ -348,7 +348,7 @@ var Project = PaperScopeItem.extend(/** @lends Project# */{
* {@link #layers} list.
*
* @param {Number} index the index at which to insert the layer
* @param {Item} item the item to be inserted in the project
* @param {Layer} layer the layer to be inserted in the project
* @return {Layer} the added layer, or `null` if adding was not possible
*/
insertLayer: function(index, layer) {
@ -745,6 +745,7 @@ var Project = PaperScopeItem.extend(/** @lends Project# */{
* {@link Project#clear()} to do so.
*
* @param {String} json the JSON data to import from
* @return {Item} the imported item
*/
importJSON: function(json) {
this.activate();
@ -781,7 +782,8 @@ var Project = PaperScopeItem.extend(/** @lends Project# */{
* kept as a link to their external URL.
*
* @param {Object} [options] the export options
* @return {SVGElement} the project converted to an SVG node
* @return {SVGElement|String} the project converted to an SVG node or a
* `String` depending on `option.asString` value
*/
/**

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*
@ -31,15 +31,19 @@ var Raster = Item.extend(/** @lends Raster# */{
// Prioritize `crossOrigin` over `source`:
_prioritize: ['crossOrigin'],
_smoothing: false,
// Enforce creation of beans, as bean getters have hidden parameters.
// See #getContext(_change) below.
beans: true,
// TODO: Implement type, width, height.
// TODO: Have SymbolItem & Raster inherit from a shared class?
/**
* Creates a new raster item from the passed argument, and places it in the
* active layer. `object` can either be a DOM Image, a Canvas, or a string
* active layer. `source` can either be a DOM Image, a Canvas, or a string
* describing the URL to load the image from, or the ID of a DOM element to
* get the image from (either a DOM Image or a Canvas).
*
* @name Raster#initialize
* @param {HTMLImageElement|HTMLCanvasElement|String} [source] the source of
* the raster
* @param {Point} [position] the center position at which the raster item is
@ -77,22 +81,64 @@ var Raster = Item.extend(/** @lends Raster# */{
* raster.scale(0.5);
* raster.rotate(10);
*/
initialize: function Raster(object, position) {
// Support two forms of item initialization: Passing one object literal
// describing all the different properties to be set, or an image
// (object) and a point where it should be placed (point).
/**
* Creates a new empty raster of the given size, and places it in the
* active layer.
*
* @name Raster#initialize
* @param {Size} size the size of the raster
* @param {Point} [position] the center position at which the raster item is
* placed
*
* @example {@paperscript height=150}
* // Creating an empty raster and fill it with random pixels:
* var width = 100;
* var height = 100;
*
* // Create an empty raster placed at view center.
* var raster = new Raster(new Size(width, height), view.center);
*
* // For all of its pixels...
* for (var i = 0; i < width; i++) {
* for (var j = 0; j < height; j++) {
* // ...set a random color.
* raster.setPixel(i, j, Color.random());
* }
* }
*/
initialize: function Raster(source, position) {
// Support three forms of item initialization:
// - One object literal describing all the different properties.
// - An image (Image|Canvas|String) and an optional position (Point).
// - A size (Size) describing the canvas that will be created and an
// optional position (Point).
// If _initialize can set properties through object literal, we're done.
// Otherwise we need to check the type of object:
if (!this._initialize(object,
position !== undefined && Point.read(arguments, 1))) {
// object can be an image, canvas, URL or DOM-ID:
var image = typeof object === 'string'
? document.getElementById(object) : object;
if (!this._initialize(source,
position !== undefined && Point.read(arguments))) {
var image,
type = typeof source,
object = type === 'string'
? document.getElementById(source)
: type === 'object'
? source
: null;
if (object && object !== Item.NO_INSERT) {
if (object.getContext || object.naturalHeight != null) {
image = object;
} else if (object) {
// See if the arguments describe the raster size:
var size = Size.read(arguments);
if (!size.isZero()) {
image = CanvasProvider.getCanvas(size);
}
}
}
if (image) {
// #setImage() handles both canvas and image types.
this.setImage(image);
} else {
this.setSource(object);
this.setSource(source);
}
}
if (!this._size) {
@ -305,7 +351,7 @@ var Raster = Item.extend(/** @lends Raster# */{
* case `null` is returned instead.
*
* @bean
* @type HTMLCanvasELement
* @type HTMLCanvasElement
*/
getCanvas: function() {
if (!this._canvas) {
@ -330,15 +376,15 @@ var Raster = Item.extend(/** @lends Raster# */{
* The Canvas 2D drawing context of the raster.
*
* @bean
* @type Context
* @type CanvasRenderingContext2D
*/
getContext: function(modify) {
getContext: function(_change) {
if (!this._context)
this._context = this.getCanvas().getContext('2d');
// Support a hidden parameter that indicates if the context will be used
// to modify the Raster object. We can notify such changes ahead since
// to change the Raster object. We can notify such changes ahead since
// they are only used afterwards for redrawing.
if (modify) {
if (_change) {
// Also set _image to null since the Raster stops representing it.
// NOTE: This should theoretically be in our own _changed() handler
// for ChangeFlag.PIXELS, but since it's only happening in one place
@ -386,6 +432,10 @@ var Raster = Item.extend(/** @lends Raster# */{
crossOrigin = this._crossOrigin;
if (crossOrigin)
image.crossOrigin = crossOrigin;
// Prevent setting image source to `null`, as this isn't supported by
// browsers, and it would actually throw exceptions in JSDOM.
// TODO: Look into fixing this bug in JSDOM.
if (src)
image.src = src;
this.setImage(image);
},
@ -463,7 +513,7 @@ var Raster = Item.extend(/** @lends Raster# */{
* @param {Rectangle} rect the boundaries of the sub image in pixel
* coordinates
*
* @return {HTMLCanvasELement} the sub image as a Canvas object
* @return {HTMLCanvasElement} the sub image as a Canvas object
*/
getSubCanvas: function(/* rect */) {
var rect = Rectangle.read(arguments),
@ -519,7 +569,7 @@ var Raster = Item.extend(/** @lends Raster# */{
/**
* Draws an image on the raster.
*
* @param {HTMLImageELement|HTMLCanvasELement} image
* @param {CanvasImageSource} image
* @param {Point} point the offset of the image as a point in pixel
* coordinates
*/
@ -614,8 +664,8 @@ var Raster = Item.extend(/** @lends Raster# */{
*
* @name Raster#getPixel
* @function
* @param x the x offset of the pixel in pixel coordinates
* @param y the y offset of the pixel in pixel coordinates
* @param {Number} x the x offset of the pixel in pixel coordinates
* @param {Number} y the y offset of the pixel in pixel coordinates
* @return {Color} the color of the pixel
*/
/**
@ -623,7 +673,8 @@ var Raster = Item.extend(/** @lends Raster# */{
*
* @name Raster#getPixel
* @function
* @param point the offset of the pixel as a point in pixel coordinates
* @param {Point} point the offset of the pixel as a point in pixel
* coordinates
* @return {Color} the color of the pixel
*/
getPixel: function(/* point */) {
@ -639,21 +690,23 @@ var Raster = Item.extend(/** @lends Raster# */{
*
* @name Raster#setPixel
* @function
* @param x the x offset of the pixel in pixel coordinates
* @param y the y offset of the pixel in pixel coordinates
* @param color the color that the pixel will be set to
* @param {Number} x the x offset of the pixel in pixel coordinates
* @param {Number} y the y offset of the pixel in pixel coordinates
* @param {Color} color the color that the pixel will be set to
*/
/**
* Sets the color of the specified pixel to the specified color.
*
* @name Raster#setPixel
* @function
* @param point the offset of the pixel as a point in pixel coordinates
* @param color the color that the pixel will be set to
* @param {Point} point the offset of the pixel as a point in pixel
* coordinates
* @param {Color} color the color that the pixel will be set to
*/
setPixel: function(/* point, color */) {
var point = Point.read(arguments),
color = Color.read(arguments),
var args = arguments,
point = Point.read(args),
color = Color.read(args),
components = color._convert('rgb'),
alpha = color._alpha,
ctx = this.getContext(true),
@ -666,6 +719,14 @@ var Raster = Item.extend(/** @lends Raster# */{
ctx.putImageData(imageData, point.x, point.y);
},
/**
* Clears the image, if it is backed by a canvas.
*/
clear: function() {
var size = this._size;
this.getContext(true).clearRect(0, 0, size.width + 1, size.height + 1);
},
// DOCS: document Raster#createImageData
/**
* {@grouptitle Image Data}
@ -710,7 +771,7 @@ var Raster = Item.extend(/** @lends Raster# */{
*
* @name Raster#onLoad
* @property
* @type Function
* @type ?Function
*
* @example
* var url = 'http://assets.paperjs.org/images/marilyn.jpg';
@ -736,7 +797,7 @@ var Raster = Item.extend(/** @lends Raster# */{
*
* @name Raster#onError
* @property
* @type Function
* @type ?Function
*/
_getBounds: function(matrix, options) {
@ -761,10 +822,11 @@ var Raster = Item.extend(/** @lends Raster# */{
_draw: function(ctx, param, viewMatrix) {
var element = this.getElement();
if (element) {
// Only draw if image is not empty (#1320).
if (element && element.width > 0 && element.height > 0) {
// Handle opacity for Rasters separately from the rest, since
// Rasters never draw a stroke. See Item#draw().
ctx.globalAlpha = this._opacity;
ctx.globalAlpha = Numerical.clamp(this._opacity, 0, 1);
// Call _setStyles() to make sure shadow is drawn (#1437).
this._setStyles(ctx, param, viewMatrix);

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*
@ -82,7 +82,7 @@ var Shape = Item.extend(/** @lends Shape# */{
setSize: function(/* size */) {
var size = Size.read(arguments);
if (!this._size) {
// First time, e.g. whean reading from JSON...
// First time, e.g. when reading from JSON...
this._size = size.clone();
} else if (!this._size.equals(size)) {
var type = this._type,
@ -90,7 +90,7 @@ var Shape = Item.extend(/** @lends Shape# */{
height = size.height;
if (type === 'rectangle') {
// Shrink radius accordingly
this._radius.set(Size.min(this._radius, size.divide(2)));
this._radius.set(Size.min(this._radius, size.divide(2).abs()));
} else if (type === 'circle') {
// Use average of width and height as new size, then calculate
// radius as a number from that:
@ -130,7 +130,7 @@ var Shape = Item.extend(/** @lends Shape# */{
} else {
radius = Size.read(arguments);
if (!this._radius) {
// First time, e.g. whean reading from JSON...
// First time, e.g. when reading from JSON...
this._radius = radius.clone();
} else {
if (this._radius.equals(radius))
@ -390,10 +390,13 @@ 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), point);
// Use `Base.create()` to avoid calling `initialize()` until after the
// internal fields are set here, then call `_initialize()` directly:
var item = Base.create(Shape.prototype);
item._type = type;
item._size = size;
item._radius = radius;
item._initialize(Base.getNamed(args), point);
return item;
}
@ -427,10 +430,11 @@ statics: new function() {
* });
*/
Circle: function(/* center, radius */) {
var center = Point.readNamed(arguments, 'center'),
radius = Base.readNamed(arguments, 'radius');
var args = arguments,
center = Point.readNamed(args, 'center'),
radius = Base.readNamed(args, 'radius');
return createShape('circle', center, new Size(radius * 2), radius,
arguments);
args);
},
/**
@ -524,11 +528,12 @@ statics: new function() {
* });
*/
Rectangle: function(/* rectangle */) {
var rect = Rectangle.readNamed(arguments, 'rectangle'),
radius = Size.min(Size.readNamed(arguments, 'radius'),
var args = arguments,
rect = Rectangle.readNamed(args, 'rectangle'),
radius = Size.min(Size.readNamed(args, 'radius'),
rect.getSize(true).divide(2));
return createShape('rectangle', rect.getCenter(true),
rect.getSize(true), radius, arguments);
rect.getSize(true), radius, args);
},
/**
@ -567,10 +572,11 @@ statics: new function() {
* });
*/
Ellipse: function(/* rectangle */) {
var ellipse = Shape._readEllipse(arguments),
var args = arguments,
ellipse = Shape._readEllipse(args),
radius = ellipse.radius;
return createShape('ellipse', ellipse.center, radius.multiply(2),
radius, arguments);
radius, args);
},
// Private method to read ellipse center and radius from arguments list,

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*
@ -140,7 +140,7 @@ var SymbolDefinition = Base.extend(/** @lends SymbolDefinition# */{
/**
* Returns a copy of the symbol.
*
* @return {Symbol}
* @return {SymbolDefinition}
*/
clone: function() {
return new SymbolDefinition(this._item.clone(false));
@ -149,7 +149,7 @@ var SymbolDefinition = Base.extend(/** @lends SymbolDefinition# */{
/**
* Checks whether the symbol's definition is equal to the supplied symbol.
*
* @param {Symbol} symbol
* @param {SymbolDefinition} symbol
* @return {Boolean} {@true if they are equal}
*/
equals: function(symbol) {

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*
@ -31,7 +31,9 @@ var SymbolItem = Item.extend(/** @lends SymbolItem# */{
/**
* Creates a new symbol item.
*
* @param {Symbol} definition the symbol definition to place
* @name SymbolItem#initialize
* @param {SymbolDefinition|Item} definition the definition to place or an
* item to place as a symbol
* @param {Point} [point] the center point of the placed symbol
*
* @example {@paperscript split=true height=240}
@ -119,7 +121,11 @@ var SymbolItem = Item.extend(/** @lends SymbolItem# */{
},
_hitTestSelf: function(point, options, viewMatrix) {
var res = this._definition._item._hitTest(point, options, viewMatrix);
// We need to call definition item hit test with `options.all = false`,
// as otherwise it would populate the array with its own matches.
// Instead we want only returning one match per symbol-item, see #1680
var opts = options.extend({ all: false });
var res = this._definition._item._hitTest(point, opts, viewMatrix);
// TODO: When the symbol's definition is a path, should hitResult
// contain information like HitResult#curve?
if (res)

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*
@ -38,11 +38,10 @@ if (typeof window === 'object') {
} else {
// Some native javascript classes have name collisions with Paper.js
// classes. Store them to be able to use them later in tests.
NativeClasses = {
Event: Event,
MouseEvent: MouseEvent
this.nativeClasses = {
Event: window.Event,
MouseEvent: window.MouseEvent
};
include('options.js');
// Load constants.js, required by the on-the-fly preprocessing:
include('constants.js');

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*
@ -18,7 +18,7 @@
module.exports = function(self, requireName) {
var Canvas;
try {
Canvas = require('canvas');
Canvas = require('canvas').Canvas;
} catch(error) {
// Remove `self.window`, so we still have the global `self` reference,
// but no `window` object:

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*
@ -37,15 +37,13 @@ try {
if (jsdom) {
// Create our document and window objects through jsdom.
/* global document:true, window:true */
var document = jsdom.jsdom('<html><body></body></html>', {
var document = new 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']
}
resources: 'usable'
});
self = document.defaultView;
self = document.window;
require('./canvas.js')(self, requireName);
require('./xml.js')(self);
} else {

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*
@ -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.11.8';
var version = '0.12.7';
// If this file is loaded in the browser, we're in load.js mode.
var load = typeof window === 'object';

View file

@ -2,8 +2,8 @@
* Paper.js v*#=*__options.version - 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*
@ -15,15 +15,15 @@
*
* Straps.js - Class inheritance library with support for bean-style accessors
*
* Copyright (c) 2006 - 2016 Juerg Lehni
* http://scratchdisk.com/
* Copyright (c) 2006 - 2020 rg Lehni
* http://juerglehni.com/
*
* Distributed under the MIT license.
*
***
*
* Acorn.js
* http://marijnhaverbeke.nl/acorn/
* https://marijnhaverbeke.nl/acorn/
*
* Acorn is a tiny, fast JavaScript parser written in JavaScript,
* created by Marijn Haverbeke and released under an MIT license.
@ -42,6 +42,7 @@ var paper = function(self, undefined) {
/*#*/ include('core/PaperScope.js');
/*#*/ include('core/PaperScopeItem.js');
/*#*/ include('util/CollisionDetection.js');
/*#*/ include('util/Formatter.js');
/*#*/ include('util/Numerical.js');
/*#*/ include('util/UID.js');
@ -102,6 +103,8 @@ var paper = function(self, undefined) {
/*#*/ include('tool/ToolEvent.js');
/*#*/ include('tool/Tool.js');
/*#*/ include('anim/Tween.js');
/*#*/ include('net/Http.js');
/*#*/ include('canvas/CanvasProvider.js');

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*
@ -161,7 +161,7 @@ var CompoundPath = PathItem.extend(/** @lends CompoundPath# */{
*
* @bean
* @type Boolean
* @see Path#isClosed()
* @see Path#closed
*/
isClosed: function() {
var children = this._children;
@ -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#lastChild} on {@link Item#lastChild}.
* calling {@link Path#lastSegment} on {@link Item#lastChild}.
*
* @bean
* @type Segment

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*
@ -1476,7 +1476,7 @@ new function() { // Scope for methods that require private functions
// 2: normal, 1st derivative
// 3: curvature, 1st derivative & 2nd derivative
// Prevent tangents and normals of length 0:
// http://stackoverflow.com/questions/10506868/
// https://stackoverflow.com/questions/10506868/
if (t < tMin) {
x = cx;
y = cy;
@ -1698,7 +1698,7 @@ new function() { // Scope for methods that require private functions
* Peaks are locations sharing some qualities of curvature extrema but
* are cheaper to compute. They fulfill their purpose here quite well.
* See:
* http://math.stackexchange.com/questions/1954845/bezier-curvature-extrema
* https://math.stackexchange.com/questions/1954845/bezier-curvature-extrema
*
* @param {Number[]} v the curve values array
* @return {Number[]} the roots of all found peaks
@ -1822,9 +1822,10 @@ new function() { // Scope for bezier intersection using fat-line clipping
} else {
// Apply the result of the clipping to curve 1:
v1 = Curve.getPart(v1, tMinClip, tMaxClip);
var uDiff = uMax - uMin;
if (tMaxClip - tMinClip > 0.8) {
// Subdivide the curve which has converged the least.
if (tMaxNew - tMinNew > uMax - uMin) {
if (tMaxNew - tMinNew > uDiff) {
var parts = Curve.subdivide(v1, 0.5),
t = (tMinNew + tMaxNew) / 2;
calls = addCurveIntersections(
@ -1844,7 +1845,10 @@ new function() { // Scope for bezier intersection using fat-line clipping
recursion, calls, u, uMax, tMinNew, tMaxNew);
}
} else { // Iterate
if (uMax - uMin >= fatLineEpsilon) {
// For some unclear reason we need to check against uDiff === 0
// here, to prevent a regression from happening, see #1638.
// Maybe @iconexperience could shed some light on this.
if (uDiff === 0 || uDiff >= fatLineEpsilon) {
calls = addCurveIntersections(
v2, v1, c2, c1, locations, include, !flip,
recursion, calls, uMin, uMax, tMinNew, tMaxNew);
@ -2087,7 +2091,7 @@ new function() { // Scope for bezier intersection using fat-line clipping
return locations;
}
function getLoopIntersection(v1, c1, locations, include) {
function getSelfIntersection(v1, c1, locations, include) {
var info = Curve.classify(v1);
if (info.type === 'loop') {
var roots = info.roots;
@ -2100,50 +2104,50 @@ new function() { // Scope for bezier intersection using fat-line clipping
function getIntersections(curves1, curves2, include, matrix1, matrix2,
_returnFirst) {
var self = !curves2;
var epsilon = /*#=*/Numerical.GEOMETRIC_EPSILON,
self = !curves2;
if (self)
curves2 = curves1;
var length1 = curves1.length,
length2 = curves2.length,
values2 = [],
arrays = [],
locations,
current;
// Cache values for curves2 as we re-iterate them for each in curves1.
for (var i = 0; i < length2; i++)
values2[i] = curves2[i].getValues(matrix2);
for (var i = 0; i < length1; i++) {
var curve1 = curves1[i],
values1 = self ? values2[i] : curve1.getValues(matrix1),
path1 = curve1.getPath();
// NOTE: Due to the nature of getCurveIntersections(), we use
// separate location arrays per path1, to make sure the circularity
// checks are not getting confused by locations on separate paths.
// The separate arrays are then flattened in the end.
if (path1 !== current) {
current = path1;
values1 = new Array(length1),
values2 = self ? values1 : new Array(length2),
locations = [];
arrays.push(locations);
for (var i = 0; i < length1; i++) {
values1[i] = curves1[i].getValues(matrix1);
}
if (!self) {
for (var i = 0; i < length2; i++) {
values2[i] = curves2[i].getValues(matrix2);
}
}
var boundsCollisions = CollisionDetection.findCurveBoundsCollisions(
values1, values2, epsilon);
for (var index1 = 0; index1 < length1; index1++) {
var curve1 = curves1[index1],
v1 = values1[index1];
if (self) {
// First check for self-intersections within the same curve.
getLoopIntersection(values1, curve1, locations, include);
getSelfIntersection(v1, curve1, locations, include);
}
// Check for intersections with other curves.
// For self-intersection, we can start at i + 1 instead of 0.
for (var j = self ? i + 1 : 0; j < length2; j++) {
// Check for intersections with potentially intersecting curves.
var collisions1 = boundsCollisions[index1];
if (collisions1) {
for (var j = 0; j < collisions1.length; j++) {
// There might be already one location from the above
// self-intersection check:
if (_returnFirst && locations.length)
return locations;
getCurveIntersections(values1, values2[j], curve1, curves2[j],
locations, include);
var index2 = collisions1[j];
if (!self || index2 > index1) {
var curve2 = curves2[index2],
v2 = values2[index2];
getCurveIntersections(
v1, v2, curve1, curve2, locations, include);
}
}
}
// Flatten the list of location arrays to one array and return it.
locations = [];
for (var i = 0, l = arrays.length; i < l; i++) {
Base.push(locations, arrays[i]);
}
return locations;
}
@ -2310,7 +2314,7 @@ new function() { // Scope for bezier intersection using fat-line clipping
var v1 = this.getValues(),
v2 = curve && curve !== this && curve.getValues();
return v2 ? getCurveIntersections(v1, v2, this, curve, [])
: getLoopIntersection(v1, this, []);
: getSelfIntersection(v1, this, []);
},
statics: /** @lends Curve */{

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*
@ -57,13 +57,16 @@ var CurveLocation = Base.extend(/** @lends CurveLocation# */{
this._intersection = this._next = this._previous = null;
},
_setCurve: function(curve) {
var path = curve._path;
// We only store the path to verify versions for cachd values.
_setPath: function(path) {
// We only store the path to verify versions for cached values.
// To ensure we use the right path (e.g. after splitting), we shall
// always access the path on the result of getCurve().
this._path = path;
this._version = path ? path._version : 0;
},
_setCurve: function(curve) {
this._setPath(curve._path);
this._curve = curve;
this._segment = null; // To be determined, see #getSegment()
// Also store references to segment1 and segment2, in case path
@ -74,7 +77,14 @@ var CurveLocation = Base.extend(/** @lends CurveLocation# */{
},
_setSegment: function(segment) {
this._setCurve(segment.getCurve());
var curve = segment.getCurve();
if (curve) {
this._setCurve(curve);
} else {
this._setPath(segment._path);
this._segment1 = segment;
this._segment2 = null;
}
this._segment = segment;
this._time = segment === this._segment1 ? 0 : 1;
// To avoid issues with imprecision in getCurve() / trySegment()
@ -147,7 +157,7 @@ var CurveLocation = Base.extend(/** @lends CurveLocation# */{
* The path that this locations is situated on.
*
* @bean
* @type Item
* @type Path
*/
getPath: function() {
var curve = this.getCurve();
@ -159,7 +169,7 @@ var CurveLocation = Base.extend(/** @lends CurveLocation# */{
* it is part of a {@link Path} item.
*
* @bean
* @type Index
* @type Number
*/
getIndex: function() {
var curve = this.getCurve();
@ -284,7 +294,7 @@ var CurveLocation = Base.extend(/** @lends CurveLocation# */{
* @bean
* @type Number
* @see Curve#getNearestLocation(point)
* @see Path#getNearestLocation(point)
* @see PathItem#getNearestLocation(point)
*/
getDistance: function() {
return this._distance;
@ -424,9 +434,9 @@ var CurveLocation = Base.extend(/** @lends CurveLocation# */{
// both values point to the same curve, and the curve-time is to be
// handled accordingly further down.
var c2 = this.getCurve(),
c1 = t1 < tMin ? c2.getPrevious() : c2,
c1 = c2 && t1 < tMin ? c2.getPrevious() : c2,
c4 = inter.getCurve(),
c3 = t2 < tMin ? c4.getPrevious() : c4;
c3 = c4 && t2 < tMin ? c4.getPrevious() : c4;
// If t1 / t2 are at the end, then step to the next curve.
if (t1 > tMax)
c2 = c2.getNext();
@ -450,11 +460,12 @@ var CurveLocation = Base.extend(/** @lends CurveLocation# */{
var v = curve.getValues(),
roots = Curve.classify(v).roots || Curve.getPeaks(v),
count = roots.length,
t = end && count > 1 ? roots[count - 1]
: count > 0 ? roots[0]
: 0.5;
// Then use half of the offset, for extra measure.
offsets.push(Curve.getLength(v, end ? t : 0, end ? 1 : t) / 2);
offset = Curve.getLength(v,
end && count ? roots[count - 1] : 0,
!end && count ? roots[0] : 1);
// When no root was found, the full length was calculated. Use a
// fraction of it. By trial & error, 64 was determined to work well.
offsets.push(count ? offset : offset / 32);
}
function isInRange(angle, min, max) {
@ -491,7 +502,7 @@ var CurveLocation = Base.extend(/** @lends CurveLocation# */{
// Count how many times curve2 angles appear between the curve1 angles.
// If each pair of angles split the other two, then the edges cross.
// Use t1Inside to decide which angle pair to check against.
// If t1 is inside the curve, check against a3 & a4, othrwise a1 & a2.
// If t1 is inside the curve, check against a3 & a4, otherwise a1 & a2.
return !!(t1Inside
? (isInRange(a1, a3, a4) ^ isInRange(a2, a3, a4)) &&
(isInRange(a1, a4, a3) ^ isInRange(a2, a4, a3))

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*
@ -79,10 +79,11 @@ Path.inject({ statics: new function() {
* });
*/
Line: function(/* from, to */) {
var args = arguments;
return createPath([
new Segment(Point.readNamed(arguments, 'from')),
new Segment(Point.readNamed(arguments, 'to'))
], false, arguments);
new Segment(Point.readNamed(args, 'from')),
new Segment(Point.readNamed(args, 'to'))
], false, args);
},
/**
@ -114,9 +115,10 @@ Path.inject({ statics: new function() {
* });
*/
Circle: function(/* center, radius */) {
var center = Point.readNamed(arguments, 'center'),
radius = Base.readNamed(arguments, 'radius');
return createEllipse(center, new Size(radius), arguments);
var args = arguments,
center = Point.readNamed(args, 'center'),
radius = Base.readNamed(args, 'radius');
return createEllipse(center, new Size(radius), args);
},
/**
@ -210,8 +212,9 @@ Path.inject({ statics: new function() {
* });
*/
Rectangle: function(/* rectangle */) {
var rect = Rectangle.readNamed(arguments, 'rectangle'),
radius = Size.readNamed(arguments, 'radius', 0,
var args = arguments,
rect = Rectangle.readNamed(args, 'rectangle'),
radius = Size.readNamed(args, 'radius', 0,
{ readNull: true }),
bl = rect.getBottomLeft(true),
tl = rect.getTopLeft(true),
@ -242,7 +245,7 @@ Path.inject({ statics: new function() {
new Segment(br.subtract(rx, 0), [hx, 0])
];
}
return createPath(segments, true, arguments);
return createPath(segments, true, args);
},
/**
@ -286,8 +289,9 @@ Path.inject({ statics: new function() {
* });
*/
Ellipse: function(/* rectangle */) {
var ellipse = Shape._readEllipse(arguments);
return createEllipse(ellipse.center, ellipse.radius, arguments);
var args = arguments,
ellipse = Shape._readEllipse(args);
return createEllipse(ellipse.center, ellipse.radius, args);
},
/**
@ -330,10 +334,11 @@ Path.inject({ statics: new function() {
* });
*/
Arc: function(/* from, through, to */) {
var from = Point.readNamed(arguments, 'from'),
through = Point.readNamed(arguments, 'through'),
to = Point.readNamed(arguments, 'to'),
props = Base.getNamed(arguments),
var args = arguments,
from = Point.readNamed(args, 'from'),
through = Point.readNamed(args, 'through'),
to = Point.readNamed(args, 'to'),
props = Base.getNamed(args),
// See createPath() for an explanation of the following sequence
path = new Path(props && props.insert == false
&& Item.NO_INSERT);
@ -376,9 +381,10 @@ Path.inject({ statics: new function() {
* });
*/
RegularPolygon: function(/* center, sides, radius */) {
var center = Point.readNamed(arguments, 'center'),
sides = Base.readNamed(arguments, 'sides'),
radius = Base.readNamed(arguments, 'radius'),
var args = arguments,
center = Point.readNamed(args, 'center'),
sides = Base.readNamed(args, 'sides'),
radius = Base.readNamed(args, 'radius'),
step = 360 / sides,
three = sides % 3 === 0,
vector = new Point(0, three ? -radius : radius),
@ -387,7 +393,7 @@ Path.inject({ statics: new function() {
for (var i = 0; i < sides; i++)
segments[i] = new Segment(center.add(
vector.rotate((i + offset) * step)));
return createPath(segments, true, arguments);
return createPath(segments, true, args);
},
/**
@ -431,17 +437,18 @@ Path.inject({ statics: new function() {
* });
*/
Star: function(/* center, points, radius1, radius2 */) {
var center = Point.readNamed(arguments, 'center'),
points = Base.readNamed(arguments, 'points') * 2,
radius1 = Base.readNamed(arguments, 'radius1'),
radius2 = Base.readNamed(arguments, 'radius2'),
var args = arguments,
center = Point.readNamed(args, 'center'),
points = Base.readNamed(args, 'points') * 2,
radius1 = Base.readNamed(args, 'radius1'),
radius2 = Base.readNamed(args, 'radius2'),
step = 360 / points,
vector = new Point(0, -1),
segments = new Array(points);
for (var i = 0; i < points; i++)
segments[i] = new Segment(center.add(vector.rotate(step * i)
.multiply(i % 2 ? radius2 : radius1)));
return createPath(segments, true, arguments);
return createPath(segments, true, args);
}
};
}});

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*
@ -75,7 +75,6 @@ var Path = PathItem.extend(/** @lends Path# */{
* Creates a new path item from SVG path-data and places it at the top of
* the active layer.
*
* @param
* @name Path#initialize
* @param {String} pathData the SVG path-data that describes the geometry
* of this path
@ -99,15 +98,16 @@ var Path = PathItem.extend(/** @lends Path# */{
// check its first entry for object as well.
// But first see if segments are directly passed at all. If not, try
// _set(arg).
var segments = Array.isArray(arg)
var args = arguments,
segments = Array.isArray(arg)
? typeof arg[0] === 'object'
? arg
: arguments
: args
// See if it behaves like a segment or a point, but filter out
// rectangles, as accepted by some Path.Constructor constructors.
: arg && (arg.size === undefined && (arg.x !== undefined
|| arg.point !== undefined))
? arguments
? args
: null;
// Always call setSegments() to initialize a few related variables.
if (segments && segments.length > 0) {
@ -484,9 +484,11 @@ var Path = PathItem.extend(/** @lends Path# */{
* Adds one or more segments to the end of the {@link #segments} array of
* this path.
*
* @param {Segment|Point} segment the segment or point to be added.
* @return {Segment} the added segment. This is not necessarily the same
* object, e.g. if the segment to be added already belongs to another path
* @param {...(Segment|Point|Number[])} segment the segment or point to be
* added.
* @return {Segment|Segment[]} the added segment(s). This is not necessarily
* the same object, e.g. if the segment to be added already belongs to
* another path.
*
* @example {@paperscript}
* // Adding segments to a path using point objects:
@ -547,11 +549,12 @@ var Path = PathItem.extend(/** @lends Path# */{
* path.add(new Point(170, 75));
*/
add: function(segment1 /*, segment2, ... */) {
return arguments.length > 1 && typeof segment1 !== 'number'
var args = arguments;
return args.length > 1 && typeof segment1 !== 'number'
// addSegments
? this._add(Segment.readList(arguments))
? this._add(Segment.readList(args))
// addSegment
: this._add([ Segment.read(arguments) ])[0];
: this._add([ Segment.read(args) ])[0];
},
/**
@ -591,11 +594,12 @@ var Path = PathItem.extend(/** @lends Path# */{
* myPath.segments[2].selected = true;
*/
insert: function(index, segment1 /*, segment2, ... */) {
return arguments.length > 2 && typeof segment1 !== 'number'
var args = arguments;
return args.length > 2 && typeof segment1 !== 'number'
// insertSegments
? this._add(Segment.readList(arguments, 1), index)
? this._add(Segment.readList(args, 1), index)
// insertSegment
: this._add([ Segment.read(arguments, 1) ], index)[0];
: this._add([ Segment.read(args, 1) ], index)[0];
},
addSegment: function(/* segment */) {
@ -1215,6 +1219,8 @@ var Path = PathItem.extend(/** @lends Path# */{
/**
* Reduces the path by removing curves that have a length of 0,
* and unnecessary segments between two collinear flat curves.
*
* @return {Path} the reduced path
*/
reduce: function(options) {
var curves = this.getCurves(),
@ -2173,9 +2179,9 @@ new function() { // Scope for drawing
// performance.
function drawHandles(ctx, segments, matrix, size, isFullySelected) {
if (size === 0) {
return;
}
// Only draw if size is not null or negative.
if (size <= 0) return;
var half = size / 2,
coords = new Array(6),
pX, pY;
@ -2402,9 +2408,10 @@ new function() { // PostScript-style drawing commands
},
cubicCurveTo: function(/* handle1, handle2, to */) {
var handle1 = Point.read(arguments),
handle2 = Point.read(arguments),
to = Point.read(arguments),
var args = arguments,
handle1 = Point.read(args),
handle2 = Point.read(args),
to = Point.read(args),
// First modify the current segment:
current = getCurrentSegment(this);
// Convert to relative values:
@ -2414,8 +2421,9 @@ new function() { // PostScript-style drawing commands
},
quadraticCurveTo: function(/* handle, to */) {
var handle = Point.read(arguments),
to = Point.read(arguments),
var args = arguments,
handle = Point.read(args),
to = Point.read(args),
current = getCurrentSegment(this)._point;
// This is exact:
// If we have the three quad points: A E D,
@ -2430,9 +2438,10 @@ new function() { // PostScript-style drawing commands
},
curveTo: function(/* through, to, time */) {
var through = Point.read(arguments),
to = Point.read(arguments),
t = Base.pick(Base.read(arguments), 0.5),
var args = arguments,
through = Point.read(args),
to = Point.read(args),
t = Base.pick(Base.read(args), 0.5),
t1 = 1 - t,
current = getCurrentSegment(this)._point,
// handle = (through - (1 - t)^2 * current - t^2 * to) /
@ -2448,15 +2457,16 @@ new function() { // PostScript-style drawing commands
arcTo: function(/* to, clockwise | through, to
| to, radius, rotation, clockwise, large */) {
// Get the start point:
var abs = Math.abs,
var args = arguments,
abs = Math.abs,
sqrt = Math.sqrt,
current = getCurrentSegment(this),
from = current._point,
to = Point.read(arguments),
to = Point.read(args),
through,
// Peek at next value to see if it's clockwise, with true as the
// default value.
peek = Base.peek(arguments),
peek = Base.peek(args),
clockwise = Base.pick(peek, true),
center, extent, vector, matrix;
// We're handling three different approaches to drawing arcs in one
@ -2466,14 +2476,15 @@ new function() { // PostScript-style drawing commands
var middle = from.add(to).divide(2),
through = middle.add(middle.subtract(from).rotate(
clockwise ? -90 : 90));
} else if (Base.remain(arguments) <= 2) {
} else if (Base.remain(args) <= 2) {
// #2: arcTo(through, to)
through = to;
to = Point.read(arguments);
} else {
to = Point.read(args);
} else if (!from.equals(to)) {
// #3: arcTo(to, radius, rotation, clockwise, large)
// Drawing arcs in SVG style:
var radius = Size.read(arguments),
// Draw arc in SVG style, but only if `from` and `to` are not
// equal (#1613).
var radius = Size.read(args),
isZero = Numerical.isZero;
// If rx = 0 or ry = 0 then this arc is treated as a
// straight line joining the endpoints.
@ -2481,10 +2492,10 @@ new function() { // PostScript-style drawing commands
if (isZero(radius.width) || isZero(radius.height))
return this.lineTo(to);
// See for an explanation of the following calculations:
// http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes
var rotation = Base.read(arguments),
clockwise = !!Base.read(arguments),
large = !!Base.read(arguments),
// https://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes
var rotation = Base.read(args),
clockwise = !!Base.read(args),
large = !!Base.read(args),
middle = from.add(to).divide(2),
pt = from.subtract(middle).rotate(-rotation),
x = pt.x,
@ -2568,11 +2579,12 @@ new function() { // PostScript-style drawing commands
extent += extent < 0 ? 360 : -360;
}
}
if (extent) {
var epsilon = /*#=*/Numerical.GEOMETRIC_EPSILON,
ext = abs(extent),
// Calculate the amount of segments required to approximate over
// Calculate amount of segments required to approximate over
// `extend` degrees (extend / 90), but prevent ceil() from
// rounding up small imprecisions by subtracting epsilon first.
// rounding up small imprecisions by subtracting epsilon.
count = ext >= 360 ? 4 : Math.ceil((ext - epsilon) / 90),
inc = extent / count,
half = inc * Math.PI / 360,
@ -2609,6 +2621,7 @@ new function() { // PostScript-style drawing commands
}
// Add all segments at once at the end for higher performance
this._add(segments);
}
},
lineBy: function(/* to */) {
@ -2618,40 +2631,44 @@ new function() { // PostScript-style drawing commands
},
curveBy: function(/* through, to, parameter */) {
var through = Point.read(arguments),
to = Point.read(arguments),
parameter = Base.read(arguments),
var args = arguments,
through = Point.read(args),
to = Point.read(args),
parameter = Base.read(args),
current = getCurrentSegment(this)._point;
this.curveTo(current.add(through), current.add(to), parameter);
},
cubicCurveBy: function(/* handle1, handle2, to */) {
var handle1 = Point.read(arguments),
handle2 = Point.read(arguments),
to = Point.read(arguments),
var args = arguments,
handle1 = Point.read(args),
handle2 = Point.read(args),
to = Point.read(args),
current = getCurrentSegment(this)._point;
this.cubicCurveTo(current.add(handle1), current.add(handle2),
current.add(to));
},
quadraticCurveBy: function(/* handle, to */) {
var handle = Point.read(arguments),
to = Point.read(arguments),
var args = arguments,
handle = Point.read(args),
to = Point.read(args),
current = getCurrentSegment(this)._point;
this.quadraticCurveTo(current.add(handle), current.add(to));
},
// TODO: Implement version for: (to, radius, rotation, clockwise, large)
arcBy: function(/* to, clockwise | through, to */) {
var current = getCurrentSegment(this)._point,
point = current.add(Point.read(arguments)),
var args = arguments,
current = getCurrentSegment(this)._point,
point = current.add(Point.read(args)),
// Peek at next value to see if it's clockwise, with true as
// default value.
clockwise = Base.pick(Base.peek(arguments), true);
clockwise = Base.pick(Base.peek(args), true);
if (typeof clockwise === 'boolean') {
this.arcTo(point, clockwise);
} else {
this.arcTo(point, current.add(Point.read(arguments)));
this.arcTo(point, current.add(Point.read(args)));
}
},
@ -2780,16 +2797,19 @@ statics: {
}
var length = segments.length - (closed ? 0 : 1);
for (var i = 1; i < length; i++)
if (length > 0) {
for (var i = 1; i < length; i++) {
addJoin(segments[i], join);
}
if (closed) {
// Go back to the beginning
addJoin(segments[0], join);
} else if (length > 0) {
} else {
// Handle caps on open paths
addCap(segments[0], cap);
addCap(segments[segments.length - 1], cap);
}
}
return bounds;
},
@ -2846,8 +2866,9 @@ statics: {
normal1 = curve1.getNormalAtTime(1).multiply(radius)
.transform(strokeMatrix),
normal2 = curve2.getNormalAtTime(0).multiply(radius)
.transform(strokeMatrix);
if (normal1.getDirectedAngle(normal2) < 0) {
.transform(strokeMatrix),
angle = normal1.getDirectedAngle(normal2);
if (angle < 0 || angle >= 180) {
normal1 = normal1.negate();
normal2 = normal2.negate();
}

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*
@ -13,7 +13,7 @@
// An Algorithm for Automatically Fitting Digitized Curves
// by Philip J. Schneider
// from "Graphics Gems", Academic Press, 1990
// Modifications and optimizations of original algorithm by Juerg Lehni.
// Modifications and optimizations of original algorithm by Jürg Lehni.
/**
* @name PathFitter
@ -231,7 +231,7 @@ var PathFitter = Base.extend({
diff = pt.subtract(point),
df = pt1.dot(pt1) + diff.dot(pt2);
// u = u - f(u) / f'(u)
return Numerical.isZero(df) ? u : u - diff.dot(pt1) / df;
return Numerical.isMachineZero(df) ? u : u - diff.dot(pt1) / df;
},
// Evaluate a bezier curve at a particular parameter value

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*
@ -23,9 +23,8 @@
* - Boolean operations on self-intersecting Paths items
*
* @author Harikrishnan Gopalakrishnan <hari.exeption@gmail.com>
* @author Jan Boesenberg <development@iconexperience.com>
* @author Juerg Lehni <juerg@scratchdisk.com>
* http://hkrish.com/playground/paperjs/booleanStudy.html
* @author Jan Boesenberg <jan.boesenberg@gmail.com>
* @author Jürg Lehni <juerg@scratchdisk.com>
*/
PathItem.inject(new function() {
var min = Math.min,
@ -46,6 +45,10 @@ PathItem.inject(new function() {
exclude: { '1': true, '-1': true }
};
function getPaths(path) {
return path._children || [path];
}
/*
* Creates a clone of the path that we can modify freely, with its matrix
* applied to its geometry. Calls #reduce() to simplify compound paths and
@ -53,12 +56,28 @@ PathItem.inject(new function() {
* make sure all paths have correct winding direction.
*/
function preparePath(path, resolve) {
var res = path.clone(false).reduce({ simplify: true })
var res = path
.clone(false)
.reduce({ simplify: true })
.transform(null, true, true);
return resolve
? res.resolveCrossings().reorient(
res.getFillRule() === 'nonzero', true)
: res;
if (resolve) {
// For correct results, close open paths with straight lines:
var paths = getPaths(res);
for (var i = 0, l = paths.length; i < l; i++) {
var path = paths[i];
if (!path._closed && !path.isEmpty()) {
// Close with epsilon tolerance, to avoid tiny straight
// that would cause issues with intersection detection.
path.closePath(/*#=*/Numerical.EPSILON);
path.getFirstSegment().setHandleIn(0, 0);
path.getLastSegment().setHandleOut(0, 0);
}
}
res = res
.resolveCrossings()
.reorient(res.getFillRule() === 'nonzero', true);
}
return res;
}
function createResult(paths, simplify, path1, path2, options) {
@ -77,6 +96,17 @@ PathItem.inject(new function() {
return result;
}
function filterIntersection(inter) {
// TODO: Change isCrossing() to also handle overlaps (hasOverlap())
// that are actually involved in a crossing! For this we need proper
// overlap range detection / merging first... But as we call
// #resolveCrossings() first in boolean operations, removing all
// self-touching areas in paths, this works for the known use cases.
// The ideal implementation would deal with it in a way outlined in:
// https://github.com/paperjs/paper.js/issues/874#issuecomment-168332391
return inter.hasOverlap() || inter.isCrossing();
}
function traceBoolean(path1, path2, operation, options) {
// Only support subtract and intersect operations when computing stroke
// based boolean operations (options.split = true).
@ -101,15 +131,15 @@ PathItem.inject(new function() {
_path2.reverse();
// Split curves at crossings on both paths. Note that for self-
// intersection, path2 is null and getIntersections() handles it.
var crossings = divideLocations(
CurveLocation.expand(_path1.getCrossings(_path2))),
paths1 = _path1._children || [_path1],
paths2 = _path2 && (_path2._children || [_path2]),
var crossings = divideLocations(CurveLocation.expand(
_path1.getIntersections(_path2, filterIntersection))),
paths1 = getPaths(_path1),
paths2 = _path2 && getPaths(_path2),
segments = [],
curves = [],
paths;
function collect(paths) {
function collectPaths(paths) {
for (var i = 0, l = paths.length; i < l; i++) {
var path = paths[i];
Base.push(segments, path._segments);
@ -120,24 +150,51 @@ PathItem.inject(new function() {
}
}
function getCurves(indices) {
var list = [];
for (var i = 0, l = indices && indices.length; i < l; i++) {
list.push(curves[indices[i]]);
}
return list;
}
if (crossings.length) {
// Collect all segments and curves of both involved operands.
collect(paths1);
collectPaths(paths1);
if (paths2)
collect(paths2);
collectPaths(paths2);
var curvesValues = new Array(curves.length);
for (var i = 0, l = curves.length; i < l; i++) {
curvesValues[i] = curves[i].getValues();
}
var curveCollisions = CollisionDetection.findCurveBoundsCollisions(
curvesValues, curvesValues, 0, true);
var curveCollisionsMap = {};
for (var i = 0; i < curves.length; i++) {
var curve = curves[i],
id = curve._path._id,
map = curveCollisionsMap[id] = curveCollisionsMap[id] || {};
map[curve.getIndex()] = {
hor: getCurves(curveCollisions[i].hor),
ver: getCurves(curveCollisions[i].ver)
};
}
// Propagate the winding contribution. Winding contribution of
// curves does not change between two crossings.
// First, propagate winding contributions for curve chains starting
// in all crossings:
for (var i = 0, l = crossings.length; i < l; i++) {
propagateWinding(crossings[i]._segment, _path1, _path2, curves,
operator);
propagateWinding(crossings[i]._segment, _path1, _path2,
curveCollisionsMap, operator);
}
for (var i = 0, l = segments.length; i < l; i++) {
var segment = segments[i],
inter = segment._intersection;
if (!segment._winding) {
propagateWinding(segment, _path1, _path2, curves, operator);
propagateWinding(segment, _path1, _path2,
curveCollisionsMap, operator);
}
// See if all encountered segments in a path are overlaps.
if (!(inter && inter._overlap))
@ -155,14 +212,13 @@ PathItem.inject(new function() {
return !!operator[w];
});
}
return createResult(paths, true, path1, path2, options);
}
function splitBoolean(path1, path2, operation) {
var _path1 = preparePath(path1),
_path2 = preparePath(path2),
crossings = _path1.getCrossings(_path2),
crossings = _path1.getIntersections(_path2, filterIntersection),
subtract = operation === 'subtract',
divide = operation === 'divide',
added = {},
@ -269,31 +325,41 @@ PathItem.inject(new function() {
// Get reference to the first, largest path and insert it
// already.
first = sorted[0];
// create lookup containing potentially overlapping path bounds
var collisions = CollisionDetection.findItemBoundsCollisions(sorted,
null, Numerical.GEOMETRIC_EPSILON);
if (clockwise == null)
clockwise = first.isClockwise();
// Now determine the winding for each path, from large to small.
for (var i = 0; i < length; i++) {
var path1 = sorted[i],
entry1 = lookup[path1._id],
point = path1.getInteriorPoint(),
containerWinding = 0;
for (var j = i - 1; j >= 0; j--) {
var path2 = sorted[j];
// As we run through the paths from largest to smallest, for
// any current path, all potentially containing paths have
// already been processed and their orientation fixed.
// To achieve correct orientation of contained paths based
// on winding, we have to find one containing path with
// different "insideness" and set opposite orientation.
containerWinding = 0,
indices = collisions[i];
if (indices) {
var point = null; // interior point, only get it if required.
for (var j = indices.length - 1; j >= 0; j--) {
if (indices[j] < i) {
point = point || path1.getInteriorPoint();
var path2 = sorted[indices[j]];
// As we run through the paths from largest to
// smallest, for any current path, all potentially
// containing paths have already been processed and
// their orientation fixed. To achieve correct
// orientation of contained paths based on winding,
// find one containing path with different
// "insideness" and set opposite orientation.
if (path2.contains(point)) {
var entry2 = lookup[path2._id];
containerWinding = entry2.winding;
entry1.winding += containerWinding;
entry1.container = entry2.exclude ? entry2.container
: path2;
entry1.container = entry2.exclude
? entry2.container : path2;
break;
}
}
}
}
// Only keep paths if the "insideness" changes when crossing the
// path, e.g. the inside of the path is filled and the outside
// is not, or vice versa.
@ -306,8 +372,8 @@ PathItem.inject(new function() {
// If the containing path is not excluded, we're done
// searching for the orientation defining path.
var container = entry1.container;
path1.setClockwise(container ? !container.isClockwise()
: clockwise);
path1.setClockwise(
container ? !container.isClockwise() : clockwise);
}
}
}
@ -452,9 +518,9 @@ 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
* @param {Curve[]} curves The curves that describe the shape against which
* to check, as returned by {@link Path#curves} or
* {@link CompoundPath#curves}
* {@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
@ -468,6 +534,13 @@ PathItem.inject(new function() {
* @private
*/
function getWinding(point, curves, dir, closed, dontFlip) {
// `curves` can either be an array of curves, or an object containing of
// the form `{ hor: [], ver: [] }` (see `curveCollisionsMap`), with each
// key / value pair holding only those curves that can be crossed by a
// horizontal / vertical line through the point to be checked.
var curvesList = Array.isArray(curves)
? curves
: curves[dir ? 'hor' : 'ver'];
// Determine the index of the abscissa and ordinate values in the curve
// values arrays, based on the direction:
var ia = dir ? 1 : 0, // the abscissa index
@ -573,9 +646,7 @@ PathItem.inject(new function() {
onPath = true;
}
}
// TODO: Determine how to handle quality when curve is crossed
// at starting point. Do we always need to set to 0?
quality = 0;
quality /= 4;
}
vPrev = v;
// If we're on the curve, look at the tangent to decide whether to
@ -615,12 +686,12 @@ PathItem.inject(new function() {
}
}
for (var i = 0, l = curves.length; i < l; i++) {
var curve = curves[i],
for (var i = 0, l = curvesList.length; i < l; i++) {
var curve = curvesList[i],
path = curve._path,
v = curve.getValues(),
res;
if (!i || curves[i - 1]._path !== path) {
if (!i || curvesList[i - 1]._path !== path) {
// We're on a new (sub-)path, so we need to determine values of
// the last non-horizontal curve on this path.
vPrev = null;
@ -664,7 +735,7 @@ PathItem.inject(new function() {
if (res = handleCurve(v))
return res;
if (i + 1 === l || curves[i + 1]._path !== path) {
if (i + 1 === l || curvesList[i + 1]._path !== path) {
// We're at the last curve of the current (sub-)path. If a
// closing curve was calculated at the beginning of it, handle
// it now to treat the path as closed:
@ -705,7 +776,8 @@ PathItem.inject(new function() {
};
}
function propagateWinding(segment, path1, path2, curves, operator) {
function propagateWinding(segment, path1, path2, curveCollisionsMap,
operator) {
// Here we try to determine the most likely winding number contribution
// for the curve-chain starting with this segment. Once we have enough
// confidence in the winding contribution, we can propagate it until the
@ -715,10 +787,14 @@ PathItem.inject(new function() {
totalLength = 0,
winding;
do {
var curve = segment.getCurve(),
length = curve.getLength();
var curve = segment.getCurve();
// We can encounter paths with only one segment, which would not
// have a curve.
if (curve) {
var length = curve.getLength();
chain.push({ segment: segment, curve: curve, length: length });
totalLength += length;
}
segment = segment.getNext();
} while (segment && !segment._intersection && segment !== start);
// Determine winding at three points in the chain. If a winding with
@ -726,7 +802,8 @@ PathItem.inject(new function() {
// the best quality.
var offsets = [0.5, 0.25, 0.75],
winding = { winding: 0, quality: -1 },
tMin = /*#=*/Numerical.CURVETIME_EPSILON,
// Don't go too close to segments, to avoid special winding cases:
tMin = 1e-3,
tMax = 1 - tMin;
for (var i = 0; i < offsets.length && winding.quality < 0.5; i++) {
var length = totalLength * offsets[i];
@ -751,9 +828,8 @@ PathItem.inject(new function() {
var wind = null;
if (operator.subtract && path2) {
// Calculate path winding at point depending on operand.
var pathWinding = operand === path1
? path2._getWinding(pt, dir, true)
: path1._getWinding(pt, dir, true);
var otherPath = operand === path1 ? path2 : path1,
pathWinding = otherPath._getWinding(pt, dir, true);
// Check if curve should be omitted.
if (operand === path1 && pathWinding.winding ||
operand === path2 && !pathWinding.winding) {
@ -767,7 +843,9 @@ PathItem.inject(new function() {
}
}
}
wind = wind || getWinding(pt, curves, dir, true);
wind = wind || getWinding(
pt, curveCollisionsMap[path._id][curve.getIndex()],
dir, true);
if (wind.quality > winding.quality)
winding = wind;
break;
@ -865,8 +943,8 @@ PathItem.inject(new function() {
collect(inter);
// Find the beginning of the linked intersections and loop all
// the way back to start, to collect all valid intersections.
while (inter && inter._prev)
inter = inter._prev;
while (inter && inter._previous)
inter = inter._previous;
collect(inter, start);
}
return crossings;
@ -1125,7 +1203,7 @@ PathItem.inject(new function() {
/**
* Splits the geometry of this path along the geometry of the specified
* path returns the result as a new group item. This is equivalent to
* calling {@link #subtract(path)} and {@link #subtract(path)} and
* calling {@link #subtract(path)} and {@link #intersect(path)} and
* putting the results into a new group.
*
* @option [options.insert=true] {Boolean} whether the resulting item
@ -1156,7 +1234,7 @@ PathItem.inject(new function() {
* amount of resulting paths allows so, otherwise a new path /
* compound-path is created, replacing the current one.
*
* @return {PahtItem} the resulting path item
* @return {PathItem} the resulting path item
*/
resolveCrossings: function() {
var children = this._children,
@ -1168,7 +1246,7 @@ PathItem.inject(new function() {
return inter && inter._overlap && inter._path === path;
}
// First collect all overlaps and crossings while taking not of the
// First collect all overlaps and crossings while taking note of the
// existence of both.
var hasOverlaps = false,
hasCrossings = false,
@ -1278,7 +1356,7 @@ PathItem.inject(new function() {
* @param {Boolean} [clockwise] if provided, the orientation of the root
* paths will be set to the orientation specified by `clockwise`,
* otherwise the orientation of the largest root child is used.
* @return {PahtItem} a reference to the item itself, reoriented
* @return {PathItem} a reference to the item itself, reoriented
*/
reorient: function(nonZero, clockwise) {
var children = this._children;

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*
@ -102,8 +102,8 @@ var PathItem = Item.extend(/** @lends PathItem# */{
*
* @bean
* @type Boolean
* @see Path#getArea()
* @see CompoundPath#getArea()
* @see Path#area
* @see CompoundPath#area
*/
isClockwise: function() {
return this.getArea() >= 0;
@ -345,18 +345,13 @@ var PathItem = Item.extend(/** @lends PathItem# */{
* crossing each other, as opposed to simply touching.
*
* @param {PathItem} path the other item to find the crossings with
* @return {CurveLocation[]} the locations of all crossings between the
* paths
* @see #getIntersections(path)
*/
getCrossings: function(path) {
return this.getIntersections(path, function(inter) {
// TODO: Only return overlaps that are actually crossings! For this
// we need proper overlap range detection / merging first...
// But as we call #resolveCrossings() first in boolean operations,
// removing all self-touching areas in paths, this currently works
// as it should in the known use cases.
// The ideal implementation would deal with it in a way outlined in:
// https://github.com/paperjs/paper.js/issues/874#issuecomment-168332391
return inter.hasOverlap() || inter.isCrossing();
return inter.isCrossing();
});
},
@ -727,19 +722,23 @@ var PathItem = Item.extend(/** @lends PathItem# */{
matched = [],
count = 0;
ok = true;
var boundsOverlaps = CollisionDetection.findItemBoundsCollisions(paths1, paths2, Numerical.GEOMETRIC_EPSILON);
for (var i1 = length1 - 1; i1 >= 0 && ok; i1--) {
var path1 = paths1[i1];
ok = false;
for (var i2 = length2 - 1; i2 >= 0 && !ok; i2--) {
if (path1.compare(paths2[i2])) {
if (!matched[i2]) {
matched[i2] = true;
var pathBoundsOverlaps = boundsOverlaps[i1];
if (pathBoundsOverlaps) {
for (var i2 = pathBoundsOverlaps.length - 1; i2 >= 0 && !ok; i2--) {
if (path1.compare(paths2[pathBoundsOverlaps[i2]])) {
if (!matched[pathBoundsOverlaps[i2]]) {
matched[pathBoundsOverlaps[i2]] = true;
count++;
}
ok = true;
}
}
}
}
// Each path in path2 needs to be matched at least once.
ok = ok && count === length2;
}

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*
@ -234,8 +234,8 @@ var Segment = Base.extend(/** @lends Segment# */{
* Checks if the segment has any curve handles set.
*
* @return {Boolean} {@true if the segment has handles set}
* @see Segment#getHandleIn()
* @see Segment#getHandleOut()
* @see Segment#handleIn
* @see Segment#handleOut
* @see Curve#hasHandles()
* @see Path#hasHandles()
*/
@ -554,6 +554,9 @@ var Segment = Base.extend(/** @lends Segment# */{
return this._path ? !!this._path.removeSegment(this._index) : false;
},
/**
* @return {Segment}
*/
clone: function() {
return new Segment(this._point, this._handleIn, this._handleOut);
},

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*
@ -80,7 +80,7 @@ var Color = Base.extend(new function() {
} else if (match = string.match(/^(rgb|hsl)a?\((.*)\)$/)) {
// RGB / RGBA or HSL / HSLA
type = match[1];
components = match[2].split(/[,\s]+/g);
components = match[2].trim().split(/[,\s]+/g);
var isHSL = type === 'hsl';
for (var i = 0, l = Math.min(components.length, 4); i < l; i++) {
var component = components[i];
@ -102,7 +102,7 @@ var Color = Base.extend(new function() {
}
} else if (i < 3) {
// RGB color values to 0..1
value /= 255;
value /= /%$/.test(component) ? 100 : 255;
}
components[i] = value;
}
@ -691,6 +691,8 @@ var Color = Base.extend(new function() {
* constructors also work for calls of `set()`.
*
* @function
* @param {...*} values
* @return {Color}
*/
set: '#initialize',
@ -709,8 +711,13 @@ var Color = Base.extend(new function() {
*/
_changed: function() {
this._canvasStyle = null;
if (this._owner)
if (this._owner) {
if (this._setter) {
this._owner[this._setter](this);
} else {
this._owner._changed(/*#=*/Change.STYLE);
}
}
},
/**
@ -729,7 +736,7 @@ var Color = Base.extend(new function() {
},
/**
* Converts the color another type.
* Converts the color to another type.
*
* @param {String} type the color type to convert to. Possible values:
* {@values 'rgb', 'gray', 'hsb', 'hsl'}
@ -1183,9 +1190,35 @@ var Color = Base.extend(new function() {
// Export for backward compatibility code below.
_types: types,
/**
* Returns a color object with random {@link #red}, {@link #green}
* and {@link #blue} values between `0` and `1`.
*
* @return {Color} the newly created color object
* @static
*
* @example {@paperscript}
* var circle = new Path.Circle(view.center, 50);
* // Set a random color as circle fill color.
* circle.fillColor = Color.random();
*/
random: function() {
var random = Math.random;
return new Color(random(), random(), random());
},
_setOwner: function(color, owner, setter) {
if (color) {
// Clone color if owner changes:
if (color._owner && owner && color._owner !== owner) {
color = color.clone();
}
if (!color._owner ^ !owner) {
color._owner = owner || null;
color._setter = setter || null;
}
}
return color;
}
}
});

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*
@ -175,12 +175,10 @@ var GradientStop = Base.extend(/** @lends GradientStop# */{
},
setColor: function(/* color */) {
// Make sure newly set colors are cloned, since they can only have
// one owner.
var color = Color.read(arguments, 0, { clone: true });
if (color)
color._owner = this;
this._color = color;
// Clear old color owner before setting new one:
Color._setOwner(this._color, null);
this._color = Color._setOwner(Color.read(arguments, 0), this,
'setColor');
this._changed();
},

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*
@ -162,29 +162,35 @@ var Style = Base.extend(new function() {
// raw value is stored, and conversion only happens in the getter.
fields[set] = function(value) {
var owner = this._owner,
children = owner && owner._children;
children = owner && owner._children,
applyToChildren = children && children.length > 0
&& !(owner instanceof CompoundPath);
// Only unify styles on children of Groups, excluding CompoundPaths.
if (children && children.length > 0
&& !(owner instanceof CompoundPath)) {
if (applyToChildren) {
for (var i = 0, l = children.length; i < l; i++)
children[i]._style[set](value);
} else if (key in this._defaults) {
}
// Always store selectedColor in item _values to make sure that
// group selected bounds and position color is coherent whether it
// has children or not when the value is set.
if ((key === 'selectedColor' || !applyToChildren)
&& key in this._defaults) {
var old = this._values[key];
if (old !== value) {
if (isColor) {
// 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;
if (old) {
Color._setOwner(old, null);
old._canvasStyle = null;
}
if (value && value.constructor === Color) {
// Clone color if it already has an owner.
// NOTE: If value is not a Color, it is only
// converted and cloned in the getter further down.
if (value._owner)
value = value.clone();
value._owner = owner;
value = Color._setOwner(value, owner,
// Only provide a color-setter if the style
// is to be applied to the children:
applyToChildren && set);
}
}
// NOTE: We do not convert the values to Colors in the
@ -201,29 +207,13 @@ var Style = Base.extend(new function() {
fields[get] = function(_dontMerge) {
var owner = this._owner,
children = owner && owner._children,
applyToChildren = children && children.length > 0
&& !(owner instanceof CompoundPath),
value;
// If the owner has children, walk through all of them and see if
// they all have the same style.
// If true is passed for _dontMerge, don't merge children styles
if (key in this._defaults && (!children || !children.length
|| _dontMerge || owner instanceof CompoundPath)) {
var value = this._values[key];
if (value === undefined) {
value = this._defaults[key];
if (value && value.clone)
value = value.clone();
} else {
var ctor = isColor ? Color : isPoint ? Point : null;
if (ctor && !(value && value.constructor === ctor)) {
// Convert to a Color / Point, and stored result of the
// conversion.
this._values[key] = value = ctor.read([value], 0,
{ readNull: true, clone: true });
if (value && isColor)
value._owner = owner;
}
}
} else if (children) {
// If true is passed for _dontMerge, don't merge children styles.
if (applyToChildren && !_dontMerge) {
for (var i = 0, l = children.length; i < l; i++) {
var childValue = children[i]._style[get]();
if (!i) {
@ -234,6 +224,30 @@ var Style = Base.extend(new function() {
return undefined;
}
}
} else if (key in this._defaults) {
var value = this._values[key];
if (value === undefined) {
value = this._defaults[key];
// Clone defaults if available:
if (value && value.clone) {
value = value.clone();
}
} else {
var ctor = isColor ? Color : isPoint ? Point : null;
if (ctor && !(value && value.constructor === ctor)) {
// Convert to a Color / Point, and stored result of the
// conversion.
this._values[key] = value = ctor.read([value], 0,
{ readNull: true, clone: true });
}
}
}
if (value && isColor) {
// Color._setOwner() may clone the color if it already has a
// different owner (e.g. resulting from `childValue` above).
// Only provide a color-setter if the style is to be applied to
// the children:
value = Color._setOwner(value, owner, applyToChildren && set);
}
return value;
};
@ -397,7 +411,7 @@ var Style = Base.extend(new function() {
*
* @name Style#strokeColor
* @property
* @type Color
* @type ?Color
*
* @example {@paperscript}
* // Setting the stroke color of a path:
@ -530,7 +544,7 @@ var Style = Base.extend(new function() {
*
* @name Style#dashArray
* @property
* @type Array
* @type Number[]
* @default []
*/
@ -554,7 +568,7 @@ var Style = Base.extend(new function() {
*
* @name Style#fillColor
* @property
* @type Color
* @type ?Color
*
* @example {@paperscript}
* // Setting the fill color of a path to red:
@ -585,7 +599,7 @@ var Style = Base.extend(new function() {
*
* @property
* @name Style#shadowColor
* @type Color
* @type ?Color
*
* @example {@paperscript}
* // Creating a circle with a black shadow:
@ -629,7 +643,7 @@ var Style = Base.extend(new function() {
*
* @name Style#selectedColor
* @property
* @type Color
* @type ?Color
*/
/**

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*
@ -29,11 +29,16 @@ new function() {
// in rotate(). To do so, SVG requries us to inverse transform the
// translation point by the matrix itself, since they are provided
// in local coordinates.
var point;
if (matrix.isInvertible()) {
matrix = matrix._shiftless();
var point = matrix._inverseTransform(trans);
point = matrix._inverseTransform(trans);
trans = null;
} else {
point = new Point();
}
attrs[center ? 'cx' : 'x'] = point.x;
attrs[center ? 'cy' : 'y'] = point.y;
trans = null;
}
if (!matrix.isIdentity()) {
// See if we can decompose the matrix and can formulate it as a
@ -177,7 +182,7 @@ new function() {
definition = item._definition,
node = getDefinition(definition, 'symbol'),
definitionItem = definition._item,
bounds = definitionItem.getBounds();
bounds = definitionItem.getStrokeBounds();
if (!node) {
node = SvgElement.create('symbol', {
viewBox: formatter.rectangle(bounds)
@ -456,7 +461,7 @@ new function() {
if (rect) {
attrs.width = rect.width;
attrs.height = rect.height;
if (rect.x || rect.y)
if (rect.x || rect.x === 0 || rect.y || rect.y === 0)
attrs.viewBox = formatter.rectangle(rect);
}
var node = SvgElement.create('svg', attrs, formatter),

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*
@ -22,11 +22,12 @@ new function() {
var definitions = {},
rootSize;
function getValue(node, name, isString, allowNull, allowPercent) {
function getValue(node, name, isString, allowNull, allowPercent,
defaultValue) {
// Interpret value as number. Never return NaN, but 0 instead.
// If the value is a sequence of numbers, parseFloat will
// return the first occurring number, which is enough for now.
var value = SvgElement.get(node, name),
var value = SvgElement.get(node, name) || defaultValue,
res = value == null
? allowNull
? null
@ -43,9 +44,9 @@ new function() {
: res;
}
function getPoint(node, x, y, allowNull, allowPercent) {
x = getValue(node, x || 'x', false, allowNull, allowPercent);
y = getValue(node, y || 'y', false, allowNull, allowPercent);
function getPoint(node, x, y, allowNull, allowPercent, defaultX, defaultY) {
x = getValue(node, x || 'x', false, allowNull, allowPercent, defaultX);
y = getValue(node, y || 'y', false, allowNull, allowPercent, defaultY);
return allowNull && (x == null || y == null) ? null
: new Point(x, y);
}
@ -168,13 +169,16 @@ new function() {
'userSpaceOnUse';
// Allow percentages in all values if scaleToBounds is true:
if (radial) {
origin = getPoint(node, 'cx', 'cy', false, scaleToBounds);
origin = getPoint(node, 'cx', 'cy', false, scaleToBounds,
'50%', '50%');
destination = origin.add(
getValue(node, 'r', false, false, scaleToBounds), 0);
getValue(node, 'r', false, false, scaleToBounds, '50%'), 0);
highlight = getPoint(node, 'fx', 'fy', true, scaleToBounds);
} else {
origin = getPoint(node, 'x1', 'y1', false, scaleToBounds);
destination = getPoint(node, 'x2', 'y2', false, scaleToBounds);
origin = getPoint(node, 'x1', 'y1', false, scaleToBounds,
'0%', '0%');
destination = getPoint(node, 'x2', 'y2', false, scaleToBounds,
'100%', '0%');
}
var color = applyAttributes(
new Color(gradient, origin, destination, highlight), node);
@ -196,23 +200,23 @@ new function() {
return importNode(child, options, isRoot);
}
},
// http://www.w3.org/TR/SVG/struct.html#Groups
// https://www.w3.org/TR/SVG/struct.html#Groups
g: importGroup,
// http://www.w3.org/TR/SVG/struct.html#NewDocument
// https://www.w3.org/TR/SVG/struct.html#NewDocument
svg: importGroup,
clippath: importGroup,
// http://www.w3.org/TR/SVG/shapes.html#PolygonElement
// https://www.w3.org/TR/SVG/shapes.html#PolygonElement
polygon: importPoly,
// http://www.w3.org/TR/SVG/shapes.html#PolylineElement
// https://www.w3.org/TR/SVG/shapes.html#PolylineElement
polyline: importPoly,
// http://www.w3.org/TR/SVG/paths.html
// https://www.w3.org/TR/SVG/paths.html
path: importPath,
// http://www.w3.org/TR/SVG/pservers.html#LinearGradients
// https://www.w3.org/TR/SVG/pservers.html#LinearGradients
lineargradient: importGradient,
// http://www.w3.org/TR/SVG/pservers.html#RadialGradients
// https://www.w3.org/TR/SVG/pservers.html#RadialGradients
radialgradient: importGradient,
// http://www.w3.org/TR/SVG/struct.html#ImageElement
// https://www.w3.org/TR/SVG/struct.html#ImageElement
image: function (node) {
var raster = new Raster(getValue(node, 'href', true));
raster.on('load', function() {
@ -228,17 +232,17 @@ new function() {
return raster;
},
// http://www.w3.org/TR/SVG/struct.html#SymbolElement
// https://www.w3.org/TR/SVG/struct.html#SymbolElement
symbol: function(node, type, options, isRoot) {
return new SymbolDefinition(
// Pass true for dontCenter:
importGroup(node, type, options, isRoot), true);
},
// http://www.w3.org/TR/SVG/struct.html#DefsElement
// https://www.w3.org/TR/SVG/struct.html#DefsElement
defs: importGroup,
// http://www.w3.org/TR/SVG/struct.html#UseElement
// https://www.w3.org/TR/SVG/struct.html#UseElement
use: function(node) {
// Note the namespaced xlink:href attribute is just called href
// as a property on node.
@ -258,14 +262,14 @@ new function() {
: null;
},
// http://www.w3.org/TR/SVG/shapes.html#InterfaceSVGCircleElement
// https://www.w3.org/TR/SVG/shapes.html#InterfaceSVGCircleElement
circle: function(node) {
return new Shape.Circle(
getPoint(node, 'cx', 'cy'),
getValue(node, 'r'));
},
// http://www.w3.org/TR/SVG/shapes.html#InterfaceSVGEllipseElement
// https://www.w3.org/TR/SVG/shapes.html#InterfaceSVGEllipseElement
ellipse: function(node) {
// We only use object literal notation where the default one is not
// supported (e.g. center / radius fo Shape.Ellipse).
@ -275,7 +279,7 @@ new function() {
});
},
// http://www.w3.org/TR/SVG/shapes.html#RectElement
// https://www.w3.org/TR/SVG/shapes.html#RectElement
rect: function(node) {
return new Shape.Rectangle(new Rectangle(
getPoint(node),
@ -283,7 +287,7 @@ new function() {
), getSize(node, 'rx', 'ry'));
},
// http://www.w3.org/TR/SVG/shapes.html#LineElement
// https://www.w3.org/TR/SVG/shapes.html#LineElement
line: function(node) {
return new Path.Line(
getPoint(node, 'x1', 'y1'),
@ -345,7 +349,11 @@ new function() {
text.setContent(lines.join('\n'));
return text;
}
}
},
// https://www.w3.org/TR/SVG/struct.html#SwitchElement
// Conditional attributes are ignored and all children are rendered.
switch: importGroup
};
// Attributes and Styles
@ -356,7 +364,7 @@ new function() {
function applyTransform(item, value, name, node) {
if (item.transform) {
// http://www.w3.org/TR/SVG/types.html#DataTypeTransformList
// https://www.w3.org/TR/SVG/types.html#DataTypeTransformList
// Parse SVG transform string. First we split at /)\s*/, to separate
// commands
var transforms = (node.getAttribute(name) || '').split(/\)\s*/g),
@ -399,8 +407,8 @@ new function() {
}
function applyOpacity(item, value, name) {
// http://www.w3.org/TR/SVG/painting.html#FillOpacityProperty
// http://www.w3.org/TR/SVG/painting.html#StrokeOpacityProperty
// https://www.w3.org/TR/SVG/painting.html#FillOpacityProperty
// https://www.w3.org/TR/SVG/painting.html#StrokeOpacityProperty
var key = name === 'fill-opacity' ? 'getFillColor' : 'getStrokeColor',
color = item[key] && item[key]();
if (color)
@ -442,7 +450,7 @@ new function() {
},
'clip-path': function(item, value) {
// http://www.w3.org/TR/SVG/masking.html#ClipPathProperty
// https://www.w3.org/TR/SVG/masking.html#ClipPathProperty
var clip = getDefinition(value);
if (clip) {
clip = clip.clone();
@ -474,20 +482,20 @@ new function() {
},
'stop-color': function(item, value) {
// http://www.w3.org/TR/SVG/pservers.html#StopColorProperty
// https://www.w3.org/TR/SVG/pservers.html#StopColorProperty
if (item.setColor)
item.setColor(value);
},
'stop-opacity': function(item, value) {
// http://www.w3.org/TR/SVG/pservers.html#StopOpacityProperty
// https://www.w3.org/TR/SVG/pservers.html#StopOpacityProperty
// NOTE: It is important that this is applied after stop-color!
if (item._color)
item._color.setAlpha(parseFloat(value));
},
offset: function(item, value) {
// http://www.w3.org/TR/SVG/pservers.html#StopElementOffsetAttribute
// https://www.w3.org/TR/SVG/pservers.html#StopElementOffsetAttribute
if (item.setOffset) {
var percent = value.match(/(.*)%$/);
item.setOffset(percent ? percent[1] / 100 : parseFloat(value));
@ -495,7 +503,7 @@ new function() {
},
viewBox: function(item, value, name, node, styles) {
// http://www.w3.org/TR/SVG/coords.html#ViewBoxAttribute
// https://www.w3.org/TR/SVG/coords.html#ViewBoxAttribute
// TODO: implement preserveAspectRatio attribute
// viewBox will be applied both to the group that's created for the
// content in SymbolDefinition#item, and the SymbolItem itself.
@ -703,8 +711,12 @@ new function() {
function onLoad(svg) {
try {
var node = typeof svg === 'object' ? svg : new self.DOMParser()
.parseFromString(svg, 'image/svg+xml');
var node = typeof svg === 'object'
? svg
: new self.DOMParser().parseFromString(
svg,
'image/svg+xml'
);
if (!node.nodeName) {
node = null;
throw new Error('Unsupported SVG source: ' + source);
@ -734,8 +746,10 @@ new function() {
// Have the group not pass on all transformations to its children,
// as this is how SVG works too.
// See if it's a string but handle markup separately
if (typeof source === 'string' && !/^.*</.test(source)) {
// See if it's a string but handle markup separately, using `[\s\S]` to
// also match the first tag if it only starts on the second line in a
// multi-line string.
if (typeof source === 'string' && !/^[\s\S]*</.test(source)) {
// First see if we're meant to import an element with the given
// id.
var node = document.getElementById(source);

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*
@ -135,7 +135,7 @@ var Tool = PaperScopeItem.extend(/** @lends Tool# */{
*
* @name Tool#onMouseDown
* @property
* @type Function
* @type ?Function
*
* @example {@paperscript}
* // Creating circle shaped paths where the user presses the mouse button:
@ -157,7 +157,7 @@ var Tool = PaperScopeItem.extend(/** @lends Tool# */{
*
* @name Tool#onMouseDrag
* @property
* @type Function
* @type ?Function
*
* @example {@paperscript}
* // Draw a line by adding a segment to a path on every mouse drag event:
@ -180,7 +180,7 @@ var Tool = PaperScopeItem.extend(/** @lends Tool# */{
*
* @name Tool#onMouseMove
* @property
* @type Function
* @type ?Function
*
* @example {@paperscript}
* // Moving a path to the position of the mouse:
@ -206,7 +206,7 @@ var Tool = PaperScopeItem.extend(/** @lends Tool# */{
*
* @name Tool#onMouseUp
* @property
* @type Function
* @type ?Function
*
* @example {@paperscript}
* // Creating circle shaped paths where the user releases the mouse:
@ -234,7 +234,7 @@ var Tool = PaperScopeItem.extend(/** @lends Tool# */{
*
* @name Tool#onKeyDown
* @property
* @type Function
* @type ?Function
*
* @example {@paperscript}
* // Scaling a path whenever the user presses the space bar:
@ -268,7 +268,7 @@ var Tool = PaperScopeItem.extend(/** @lends Tool# */{
*
* @name Tool#onKeyUp
* @property
* @type Function
* @type ?Function
*
* @example
* tool.onKeyUp = function(event) {

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*

View file

@ -0,0 +1,269 @@
/*
* Paper.js - The Swiss Army Knife of Vector Graphics Scripting.
* http://paperjs.org/
*
* Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*
* All rights reserved.
*/
/**
* @name CollisionDetection
* @namespace
* @private
* @author Jan Boesenberg <jan.boesenberg@gmail.com>
*/
var CollisionDetection = /** @lends CollisionDetection */{
/**
* Finds collisions between axis aligned bounding boxes of items.
*
* This function takes the bounds of all items in the items1 and items2
* arrays and calls findBoundsCollisions().
*
* @param {Array} items1 Array of items for which collisions should be
* found.
* @param {Array} [items2] Array of items that the first array should be
* compared with. If not provided, collisions between items within
* the first array will be returned.
* @param {Number} [tolerance] If provided, the tolerance will be added to
* all sides of each bounds when checking for collisions.
* @returns {Array} Array containing for the bounds at the same index in
* items1 an array of the indexes of colliding bounds in items2
*/
findItemBoundsCollisions: function(items1, items2, tolerance) {
function getBounds(items) {
var bounds = new Array(items.length);
for (var i = 0; i < items.length; i++) {
var rect = items[i].getBounds();
bounds[i] = [rect.left, rect.top, rect.right, rect.bottom];
}
return bounds;
}
var bounds1 = getBounds(items1),
bounds2 = !items2 || items2 === items1
? bounds1
: getBounds(items2);
return this.findBoundsCollisions(bounds1, bounds2, tolerance || 0);
},
/**
* Finds collisions between curves bounds. For performance reasons this
* uses broad bounds of the curve, which can be calculated much faster than
* the actual bounds. Broad bounds guarantee to contain the full curve,
* but they are usually larger than the actual bounds of a curve.
*
* This function takes the broad bounds of all curve values in the curves1
* and curves2 arrays and calls findBoundsCollisions().
*
* @param {Array} curves1 Array of curve values for which collisions should
* be found.
* @param {Array} [curves2] Array of curve values that the first array
* should be compared with. If not provided, collisions between curve
* bounds within the first arrray will be returned.
* @param {Number} [tolerance] If provided, the tolerance will be added to
* all sides of each bounds when checking for collisions.
* @param {Boolean} [bothAxis] If true, the sweep is performed along both
* axis, and the results include collisions for both: `{ hor, ver }`.
* @returns {Array} Array containing for the bounds at the same index in
* curves1 an array of the indexes of colliding bounds in curves2
*/
findCurveBoundsCollisions: function(curves1, curves2, tolerance, bothAxis) {
function getBounds(curves) {
var min = Math.min,
max = Math.max,
bounds = new Array(curves.length);
for (var i = 0; i < curves.length; i++) {
var v = curves[i];
bounds[i] = [
min(v[0], v[2], v[4], v[6]),
min(v[1], v[3], v[5], v[7]),
max(v[0], v[2], v[4], v[6]),
max(v[1], v[3], v[5], v[7])
];
}
return bounds;
}
var bounds1 = getBounds(curves1),
bounds2 = !curves2 || curves2 === curves1
? bounds1
: getBounds(curves2);
if (bothAxis) {
var hor = this.findBoundsCollisions(
bounds1, bounds2, tolerance || 0, false, true),
ver = this.findBoundsCollisions(
bounds1, bounds2, tolerance || 0, true, true),
list = [];
for (var i = 0, l = hor.length; i < l; i++) {
list[i] = { hor: hor[i], ver: ver[i] };
}
return list;
}
return this.findBoundsCollisions(bounds1, bounds2, tolerance || 0);
},
/**
* Finds collisions between two sets of bounding rectangles.
*
* The collision detection is implemented as a sweep and prune algorithm
* with sweep either along the x or y axis (primary axis) and immediate
* check on secondary axis for potential pairs.
*
* Each entry in the bounds arrays must be an array of length 4 with
* x0, y0, x1, and y1 as the array elements.
*
* The returned array has the same length as bounds1. Each entry
* contains an array with all indices of overlapping bounds of
* bounds2 (or bounds1 if bounds2 is not provided) sorted
* in ascending order.
*
* If the second bounds array parameter is null, collisions between bounds
* within the first bounds array will be found. In this case the indexed
* returned for each bounds will not contain the bounds' own index.
*
*
* @param {Array} boundsA Array of bounds objects for which collisions
* should be found.
* @param {Array} [boundsB] Array of bounds that the first array should
* be compared with. If not provided, collisions between bounds within
* the first arrray will be returned.
* @param {Number} [tolerance] If provided, the tolerance will be added to
* all sides of each bounds when checking for collisions.
* @param {Boolean} [sweepVertical] If true, the sweep is performed along
* the y-axis.
* @param {Boolean} [onlySweepAxisCollisions] If true, no collision checks
* will be done on the secondary axis.
* @returns {Array} Array containing for the bounds at the same index in
* boundsA an array of the indexes of colliding bounds in boundsB
*/
findBoundsCollisions: function(boundsA, boundsB, tolerance,
sweepVertical, onlySweepAxisCollisions) {
var self = !boundsB || boundsA === boundsB,
allBounds = self ? boundsA : boundsA.concat(boundsB),
lengthA = boundsA.length,
lengthAll = allBounds.length;
// Binary search utility function.
// For multiple same entries, this returns the rightmost entry.
// https://en.wikipedia.org/wiki/Binary_search_algorithm#Procedure_for_finding_the_rightmost_element
function binarySearch(indices, coord, value) {
var lo = 0,
hi = indices.length;
while (lo < hi) {
var mid = (hi + lo) >>> 1; // Same as Math.floor((hi + lo) / 2)
if (allBounds[indices[mid]][coord] < value) {
lo = mid + 1;
} else {
hi = mid;
}
}
return lo - 1;
}
// Set coordinates for primary and secondary axis depending on sweep
// direction. By default we sweep in horizontal direction, which
// means x is the primary axis.
var pri0 = sweepVertical ? 1 : 0,
pri1 = pri0 + 2,
sec0 = sweepVertical ? 0 : 1,
sec1 = sec0 + 2;
// Create array with all indices sorted by lower boundary on primary
// axis.
var allIndicesByPri0 = new Array(lengthAll);
for (var i = 0; i < lengthAll; i++) {
allIndicesByPri0[i] = i;
}
allIndicesByPri0.sort(function(i1, i2) {
return allBounds[i1][pri0] - allBounds[i2][pri0];
});
// Sweep along primary axis. Indices of active bounds are kept in an
// array sorted by higher boundary on primary axis.
var activeIndicesByPri1 = [],
allCollisions = new Array(lengthA);
for (var i = 0; i < lengthAll; i++) {
var curIndex = allIndicesByPri0[i],
curBounds = allBounds[curIndex],
// The original index in boundsA or boundsB:
origIndex = self ? curIndex : curIndex - lengthA,
isCurrentA = curIndex < lengthA,
isCurrentB = self || !isCurrentA,
curCollisions = isCurrentA ? [] : null;
if (activeIndicesByPri1.length) {
// remove (prune) indices that are no longer active.
var pruneCount = binarySearch(activeIndicesByPri1, pri1,
curBounds[pri0] - tolerance) + 1;
activeIndicesByPri1.splice(0, pruneCount);
// Add collisions for current index.
if (self && onlySweepAxisCollisions) {
// All active indexes can be added, no further checks needed
curCollisions = curCollisions.concat(activeIndicesByPri1);
// Add current index to collisions of all active indexes
for (var j = 0; j < activeIndicesByPri1.length; j++) {
var activeIndex = activeIndicesByPri1[j];
allCollisions[activeIndex].push(origIndex);
}
} else {
var curSec1 = curBounds[sec1],
curSec0 = curBounds[sec0];
for (var j = 0; j < activeIndicesByPri1.length; j++) {
var activeIndex = activeIndicesByPri1[j],
activeBounds = allBounds[activeIndex],
isActiveA = activeIndex < lengthA,
isActiveB = self || activeIndex >= lengthA;
// Check secondary axis bounds if necessary.
if (
onlySweepAxisCollisions ||
(
isCurrentA && isActiveB ||
isCurrentB && isActiveA
) && (
curSec1 >= activeBounds[sec0] - tolerance &&
curSec0 <= activeBounds[sec1] + tolerance
)
) {
// Add current index to collisions of active
// indices and vice versa.
if (isCurrentA && isActiveB) {
curCollisions.push(
self ? activeIndex : activeIndex - lengthA);
}
if (isCurrentB && isActiveA) {
allCollisions[activeIndex].push(origIndex);
}
}
}
}
}
if (isCurrentA) {
if (boundsA === boundsB) {
// If both arrays are the same, add self collision.
curCollisions.push(curIndex);
}
// Add collisions for current index.
allCollisions[curIndex] = curCollisions;
}
// Add current index to active indices. Keep array sorted by
// their higher boundary on the primary axis.s
if (activeIndicesByPri1.length) {
var curPri1 = curBounds[pri1],
index = binarySearch(activeIndicesByPri1, pri1, curPri1);
activeIndicesByPri1.splice(index + 1, 0, curIndex);
} else {
activeIndicesByPri1.push(curIndex);
}
}
// Sort collision indices in ascending order.
for (var i = 0; i < allCollisions.length; i++) {
var collisions = allCollisions[i];
if (collisions) {
collisions.sort(function(i1, i2) { return i1 - i2; });
}
}
return allCollisions;
}
};

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*

View file

@ -2,8 +2,8 @@
* 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/
* Copyright (c) 2011 - 2020, rg Lehni & Jonathan Puckey
* http://juerglehni.com/ & https://puckey.studio/
*
* Distributed under the MIT license. See LICENSE file for details.
*
@ -168,6 +168,10 @@ var Numerical = new function() {
return val >= -EPSILON && val <= EPSILON;
},
isMachineZero: function(val) {
return val >= -MACHINE_EPSILON && val <= MACHINE_EPSILON;
},
/**
* Returns a number whose value is clamped by the given range.
*

Some files were not shown because too many files have changed in this diff Show more