mirror of
https://github.com/scratchfoundation/paper.js.git
synced 2024-12-28 17:02:24 -05:00
Merge branch 'doubleUpstreamDevelop' into mergePaper
This commit is contained in:
commit
ab7fa1e091
52 changed files with 1475 additions and 380 deletions
23
.github/ISSUE_TEMPLATE.md
vendored
23
.github/ISSUE_TEMPLATE.md
vendored
|
@ -1,20 +1,21 @@
|
|||
<!--
|
||||
Questions:
|
||||
https://groups.google.com/group/paperjs
|
||||
https://gitter.im/paperjs/paper.js
|
||||
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
|
||||
-->
|
||||
|
||||
# Description/Steps to reproduce
|
||||
|
||||
<!--
|
||||
If feature: A description of the feature
|
||||
If bug: Steps to reproduce
|
||||
If a feature: A description of the feature.
|
||||
If a bug: The steps to reproduce the issue.
|
||||
-->
|
||||
|
||||
# Link to reproduction test-case
|
||||
|
||||
<!--
|
||||
Link to a test-case for reproduction
|
||||
Please link to a test-case for reproduction, ideally as a repository with a
|
||||
`packages.json` that installs all required dependencies to reduce confusion.
|
||||
|
||||
Note: Failure to provide a test-case for reproduction purposes will result in
|
||||
the issue being closed.
|
||||
|
@ -23,16 +24,16 @@ the issue being closed.
|
|||
# Expected result
|
||||
|
||||
<!--
|
||||
Also include actual results if bug
|
||||
Also include actual results when reporting a bug.
|
||||
-->
|
||||
|
||||
# Additional information
|
||||
|
||||
<!--
|
||||
Please include the versions of Operating System and Browser that the issue is
|
||||
encountered on.
|
||||
Please include the type and versions of Operating System, Node, as well as
|
||||
the underlying database that the issue is encountered on.
|
||||
|
||||
Examples:
|
||||
macOS 10.12.6, Chrome 60.0.3112.113
|
||||
Windows 10 Pro 10586.962, Edge 25.10586.672.0
|
||||
macOS 10.12.6, Node 8.9.0, PostgreSQL 10.0
|
||||
Windows 10 Pro 10586.962, Node 8.8.1, SQLite 3
|
||||
-->
|
||||
|
|
14
.github/PULL_REQUEST_TEMPLATE.md
vendored
14
.github/PULL_REQUEST_TEMPLATE.md
vendored
|
@ -4,21 +4,23 @@
|
|||
#### Related issues
|
||||
|
||||
<!--
|
||||
Please use the following link syntaxes:
|
||||
Please list related issues and discussion by using the following syntax:
|
||||
|
||||
- relates to #49 (to reference issues in the current repository)
|
||||
- Relates to #49
|
||||
(to reference issues in the Objection.js repository)
|
||||
- Relates to https://github.com/tgriesser/knex/issues/100
|
||||
(to reference issues in a related repository)
|
||||
-->
|
||||
|
||||
- relates to <link_to_referenced_issue>
|
||||
- Relates to <link_to_referenced_issue>
|
||||
|
||||
### Checklist
|
||||
|
||||
<!--
|
||||
- Please mark your choice with an "x" (i.e. [x], see
|
||||
https://github.com/blog/1375-task-lists-in-gfm-issues-pulls-comments)
|
||||
https://github.com/blog/1375-task-lists-in-gfm-issues-pulls-comments)
|
||||
- PR's without test coverage will be closed.
|
||||
-->
|
||||
|
||||
- [ ] New tests added or existing tests modified to cover all changes
|
||||
- [ ] Code conforms with the [style
|
||||
guide](https://github.com/paperjs/paper.js/blob/develop/RULES.md)
|
||||
- [ ] Code conforms with the JSHint rules (`npm run jshint` passes)
|
||||
|
|
20
.travis.yml
20
.travis.yml
|
@ -1,7 +1,14 @@
|
|||
language: node_js
|
||||
# Follow https://github.com/nodejs/LTS to decide when to remove a version
|
||||
node_js:
|
||||
- '6'
|
||||
- stable
|
||||
- 9
|
||||
- 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:
|
||||
|
@ -39,14 +46,3 @@ script:
|
|||
- gulp test
|
||||
- gulp zip
|
||||
- '[ "${TRAVIS_BRANCH}" = "develop" ] && [ "${TRAVIS_NODE_VERSION}" = "stable" ] && travis/deploy-prebuilt.sh || true'
|
||||
before_deploy:
|
||||
- npm --no-git-tag-version version 0.11.$(date +%Y%m%d%H%M%S)
|
||||
- git config --global user.email $(git log --pretty=format:"%ae" -n1)
|
||||
- git config --global user.name $(git log --pretty=format:"%an" -n1)
|
||||
deploy:
|
||||
- provider: npm
|
||||
on:
|
||||
branch: develop
|
||||
skip_cleanup: true
|
||||
email: $NPM_EMAIL
|
||||
api_key: $NPM_TOKEN
|
||||
|
|
|
@ -14,3 +14,5 @@
|
|||
- Andrew Wagenheim <abwagenheim@gmail.com>
|
||||
- Scott Kieronski <baroes0239@gmail.com>
|
||||
- DD Liu <liudi@media.mit.edu>
|
||||
- Samuel Asensi <asensi.samuel@gmail.com>
|
||||
- Takahiro Nishino <sapics.dev@gmail.com>
|
||||
|
|
56
CHANGELOG.md
56
CHANGELOG.md
|
@ -1,5 +1,61 @@
|
|||
# Change Log
|
||||
|
||||
## `0.11.8`
|
||||
|
||||
### News
|
||||
|
||||
This is the first release in quite a while, and it was made possible thanks to
|
||||
two new people on the team:
|
||||
|
||||
A warm welcome to [@sasensi](https://github.com/sasensi) and
|
||||
[@sapics](https://github.com/sapics), the two new and very active maintainers /
|
||||
contributors! :tada:
|
||||
|
||||
Their efforts mean that many issues are finally getting proper attention and
|
||||
solid fixes, as we are paving the way for the upcoming release of `1.0.0`. Here
|
||||
the fixes and additions from the past two weeks:
|
||||
|
||||
### Fixed
|
||||
|
||||
- Prevent `paper` object from polluting the global scope (#1544).
|
||||
- Make sure `Path#arcTo()` always passes through the provide through point
|
||||
(#1477).
|
||||
- Draw shadows on `Raster` images (#1437).
|
||||
- Fix boolean operation edge case (#1506, #1513, #1515).
|
||||
- Handle closed paths with only one segment in `Path#flatten()` (#1338).
|
||||
- Remove memory leak on gradient colors (#1499).
|
||||
- Support alpha channel in CSS colors (#1468, #1539, #1565).
|
||||
- Improve color CSS string parsing and documentation.
|
||||
- Improve caching of item positions (#1503).
|
||||
- Always draw selected position in global coordinates system (#1545).
|
||||
- Prevent empty `Symbol` items from causing issues with transformations (#1561).
|
||||
- Better detect when a cached global matrix is not valid anymore (#1448).
|
||||
- Correctly draw selected position when item is in a group with matrix not
|
||||
applied (#1535).
|
||||
- Improve handling of huge amounts of segments in paths (#1493).
|
||||
- Do not trigger error messages about passive event listeners on Chrome (#1501).
|
||||
- Fix errors with event listeners on mobile (#1533).
|
||||
- Prevent first mouse drag event from being emitted twice (#1553).
|
||||
- Support optional arguments in translate and rotate statements in SVG Import
|
||||
(#1487).
|
||||
- Make sure SVG import always applies imported attributes (#1416).
|
||||
- Correctly handle `Raster` images positions in SVG import (#1328).
|
||||
- Improve documentation for `Shape#toPath()` (#1374).
|
||||
- Improve documentation of hit test coordinate system (#1430).
|
||||
- Add documentation for `Item#locked` (#1436).
|
||||
- Support Webpack bundling in Node.js server (#1482).
|
||||
- Travis CI: Get unit tests to run correctly again.
|
||||
- Travis CI: Remove Node 4 and add Node 9.
|
||||
|
||||
### Added
|
||||
|
||||
- `Curve#getTimesWithTangent()` and `Path#getOffsetsWithTangent()` as a way to
|
||||
get the curve-times / offsets where the path is tangential to a given vector.
|
||||
- `Raster#smoothing` to control if pixels should be blurred or repeated when a
|
||||
raster is scaled up (#1521).
|
||||
- Allow `PaperScript`to export from executed code, supporting `export default`,
|
||||
named exports, as well as `module.exports`.
|
||||
|
||||
## `0.11.5`
|
||||
|
||||
### Fixed
|
||||
|
|
63
README.md
63
README.md
|
@ -4,12 +4,14 @@ 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/>
|
||||
- Discussion forum: <http://groups.google.com/group/paperjs>
|
||||
- Discussion forum: <https://groups.google.com/group/paperjs>
|
||||
- Mainline source code: <https://github.com/paperjs/paper.js>
|
||||
- Twitter: [@paperjs](http://twitter.com/paperjs)
|
||||
- Twitter: [@paperjs](https://twitter.com/paperjs)
|
||||
- Latest releases: <http://paperjs.org/download/>
|
||||
- Pre-built development versions: [`prebuilt/module`](https://github.com/paperjs/paper.js/tree/prebuilt/module)
|
||||
and [`prebuilt/dist`](https://github.com/paperjs/paper.js/tree/prebuilt/dist) branches.
|
||||
- Pre-built development versions:
|
||||
[`prebuilt/module`](https://github.com/paperjs/paper.js/tree/prebuilt/module)
|
||||
and [`prebuilt/dist`](https://github.com/paperjs/paper.js/tree/prebuilt/dist)
|
||||
branches.
|
||||
|
||||
## Installing Paper.js
|
||||
|
||||
|
@ -51,9 +53,9 @@ generally not recommended to install Node.js through OS-supplied package
|
|||
managers, as the its development cycles move fast and these versions are often
|
||||
out-of-date.
|
||||
|
||||
On macOS, [Homebrew](http://brew.sh/) is a good option if one version of
|
||||
On macOS, [Homebrew](https://brew.sh/) is a good option if one version of
|
||||
Node.js that is kept up to date with `brew upgrade` is enough:
|
||||
<http://treehouse.github.io/installation-guides/mac/node-mac.html>
|
||||
<https://treehouse.github.io/installation-guides/mac/node-mac.html>
|
||||
|
||||
[NVM](https://github.com/creationix/nvm) can be used instead to install and
|
||||
maintain multiple versions of Node.js on the same platform, as often required by
|
||||
|
@ -63,7 +65,7 @@ different projects:
|
|||
Homebrew is recommended on macOS also if you intend to install Paper.js with
|
||||
rendering to the Canvas on Node.js, as described in the next paragraph.
|
||||
|
||||
For Linux, see <http://nodejs.org/download/> to locate 32-bit and 64-bit Node.js
|
||||
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
|
||||
|
@ -81,11 +83,11 @@ different one:
|
|||
SVG importing and exporting through [jsdom](https://github.com/tmpvar/jsdom).
|
||||
|
||||
In order to install `paper-jsdom-canvas`, you need the [Cairo Graphics
|
||||
library](http://cairographics.org/) installed in your system:
|
||||
library](https://cairographics.org/) installed in your system:
|
||||
|
||||
##### Installing Cairo and Pango on macOS:
|
||||
|
||||
The easiest way to install Cairo is through [Homebrew](http://brew.sh/), by
|
||||
The easiest way to install Cairo is through [Homebrew](https://brew.sh/), by
|
||||
issuing the command:
|
||||
|
||||
brew install cairo pango
|
||||
|
@ -160,7 +162,7 @@ run:
|
|||
|
||||
### Setting Up For Building
|
||||
|
||||
As of 2016, Paper.js uses [Gulp.js](http://gulpjs.com/) for building, and has a
|
||||
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
|
||||
install these.
|
||||
|
@ -248,7 +250,7 @@ Your docs will then be located at `dist/docs`.
|
|||
### Testing
|
||||
|
||||
Paper.js was developed and tested from day 1 using proper unit testing through
|
||||
jQuery's [Qunit](http://docs.jquery.com/Qunit). To run the tests after any
|
||||
jQuery's [Qunit](https://qunitjs.com/). To run the tests after any
|
||||
change to the library's source, simply open `index.html` inside the `test`
|
||||
folder in your web browser. There should be a green bar at the top, meaning all
|
||||
tests have passed. If the bar is red, some tests have not passed. These will be
|
||||
|
@ -274,24 +276,30 @@ And to test both the PhantomJS and Node.js environments together, simply run:
|
|||
|
||||
gulp test
|
||||
|
||||
### Contributing
|
||||
### Contributing [![Open Source Helpers](https://www.codetriage.com/paperjs/paper.js/badges/users.svg)](https://www.codetriage.com/paperjs/paper.js)
|
||||
|
||||
The main Paper.js source tree is hosted on GitHub, thus you should create a fork
|
||||
of the repository in which you perform development. See
|
||||
<http://help.github.com/forking/>.
|
||||
<https://help.github.com/articles/fork-a-repo/>.
|
||||
|
||||
We prefer that you send a [pull request on GitHub
|
||||
](http://help.github.com/pull-requests/) which will then be merged into the
|
||||
official main line repository. You need to sign the Paper.js CLA to be able to
|
||||
contribute (see below).
|
||||
We prefer that you send a
|
||||
[pull request on GitHub](https://help.github.com/articles/about-pull-requests/)
|
||||
which will then be merged into the official main line repository.
|
||||
You need to sign the Paper.js CLA to be able to contribute (see below).
|
||||
|
||||
Also, in your first contribution, add yourself to the end of `AUTHORS.md` (which
|
||||
of course is optional).
|
||||
|
||||
In addition to contributing code you can also triage issues which may include
|
||||
reproducing bug reports or asking for vital information, such as version numbers
|
||||
or reproduction instructions. If you would like to start triaging issues, one
|
||||
easy way to get started is to
|
||||
[subscribe to paper.js on CodeTriage](https://www.codetriage.com/paperjs/paper.js).
|
||||
|
||||
**Get the source (for contributing):**
|
||||
|
||||
If you want to contribute to the project you will have to [make a
|
||||
fork](http://help.github.com/forking/). Then do this:
|
||||
fork](https://help.github.com/articles/fork-a-repo/). Then do this:
|
||||
|
||||
git clone --recursive git@github.com:yourusername/paper.js.git
|
||||
cd paper.js
|
||||
|
@ -304,11 +312,11 @@ To then fetch changes from upstream, run
|
|||
#### Creating and Submitting a Patch
|
||||
|
||||
As mentioned above, we prefer that you send a
|
||||
[pull request](http://help.github.com/pull-requests/) on GitHub:
|
||||
[pull request](https://help.github.com/articles/about-pull-requests/) on GitHub:
|
||||
|
||||
1. Create a fork of the upstream repository by visiting
|
||||
<https://github.com/paperjs/paper.js/fork>. If you feel insecure, here's a
|
||||
great guide: <http://help.github.com/forking/>
|
||||
great guide: <https://help.github.com/articles/fork-a-repo/>
|
||||
|
||||
2. Clone of your repository: `git clone
|
||||
https://yourusername@github.com/yourusername/paper.js.git`
|
||||
|
@ -326,7 +334,7 @@ As mentioned above, we prefer that you send a
|
|||
repository's site at GitHub (i.e. https://github.com/yourusername/paper.js)
|
||||
and press the "Pull Request" button. Make sure you are creating the pull
|
||||
request to the `develop` branch, not the `master` branch. Here's a good guide
|
||||
on pull requests: <http://help.github.com/pull-requests/>
|
||||
on pull requests: <https://help.github.com/articles/about-pull-requests/>
|
||||
|
||||
##### Use one topic branch per feature:
|
||||
|
||||
|
@ -335,7 +343,7 @@ together into your `develop` branch (or develop everything in your `develop`
|
|||
branch and then cherry-pick-and-merge into the different topic branches). Git
|
||||
provides for an extremely flexible workflow, which in many ways causes more
|
||||
confusion than it helps you when new to collaborative software development. The
|
||||
guides provided by GitHub at <http://help.github.com/> are a really good
|
||||
guides provided by GitHub at <https://help.github.com/> are a really good
|
||||
starting point and reference. If you are fixing an issue, a convenient way to
|
||||
name the branch is to use the issue number as a prefix, like this: `git checkout
|
||||
-tb issue-937-feature-add-text-styling`.
|
||||
|
@ -343,7 +351,7 @@ name the branch is to use the issue number as a prefix, like this: `git checkout
|
|||
#### Contributor License Agreement
|
||||
|
||||
Before we can accept any contributions to Paper.js, you need to sign this
|
||||
[CLA](http://en.wikipedia.org/wiki/Contributor_License_Agreement):
|
||||
[CLA](https://en.wikipedia.org/wiki/Contributor_License_Agreement):
|
||||
|
||||
[Contributor License Agreement](https://spreadsheets.google.com/a/paperjs.org/spreadsheet/embeddedform?formkey=dENxd0JBVDY2REo3THVuRmh4YjdWRlE6MQ)
|
||||
|
||||
|
@ -352,10 +360,11 @@ Before we can accept any contributions to Paper.js, you need to sign this
|
|||
> defend the project should there be a legal dispute regarding the software at
|
||||
> some future time.
|
||||
|
||||
For a list of authors and contributors, please see [AUTHORS
|
||||
](https://github.com/paperjs/paper.js/blob/master/AUTHORS.md).
|
||||
For a list of authors and contributors, please see
|
||||
[AUTHORS](https://github.com/paperjs/paper.js/blob/master/AUTHORS.md).
|
||||
|
||||
## License
|
||||
|
||||
Distributed under the MIT license. See [LICENSE
|
||||
](https://github.com/paperjs/paper.js/blob/master/LICENSE.txt) for details.
|
||||
Distributed under the MIT license. See
|
||||
[LICENSE](https://github.com/paperjs/paper.js/blob/master/LICENSE.txt)
|
||||
fo details.
|
||||
|
|
48
examples/Rasters/Smoothing.html
Normal file
48
examples/Rasters/Smoothing.html
Normal file
File diff suppressed because one or more lines are too long
73
examples/Scripts/PathTangents.html
Normal file
73
examples/Scripts/PathTangents.html
Normal file
|
@ -0,0 +1,73 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Path Tangents</title>
|
||||
<link rel="stylesheet" href="../css/style.css">
|
||||
<script type="text/javascript" src="../../dist/paper-full.js"></script>
|
||||
<script type="text/paperscript" canvas="canvas">
|
||||
|
||||
// draw path
|
||||
var path = new Path('M185.972,83.103c18.542-27.813,41.083-63.865,72.633-78.412c40.768-18.787,56.519,22.783,48.118,55.174c-7.694,29.634-23.246,56.887-33.519,85.651c-2.092,5.856-12.005,28.226,1.46,28.226c13.623,0,30.341-20.748,38.719-29.609c13.322-14.092,25.403-29.262,36.591-45.092c18.532,0,21.893,16.679,15.512,30.659c-15.041,32.952-45.633,61.693-74.315,82.812c-20.822,15.332-62.421,39.657-85.61,14.639c-26.43-28.497,5.643-88.375,16.151-117.448c15.172-41.98-26.439-5.818-37.874,8.852c-17.928,22.999-16.922,77.719-18.303,106.529c-21.793,21.793-63.141,0.942-66.759-26.731c-2.207-16.876,2.573-34.851,7.098-50.965c4.793-17.07,17.809-38.034,17.809-55.889c0-15.34-20.016,2.606-23.117,5.779c-12.837,13.14-18.843,22.942-21.953,41.102c-3.221,18.811-1.106,85.684-22.392,87.86c-29.787,3.014-51.93-20.085-55.6-48.556c-2.067-16.034,1.385-33.132,5.247-48.637c2.243-9.004,5.006-17.888,8.197-26.599c-4.147-9.616-4.988-20.426-4.988-30.78c0-33.391,44.299-71.678,77.411-65.772c31.311,5.585,6.408,61.28-0.642,76.722c16.999-29.448,43.73-77.256,83.217-77.256C211.992,5.36,197.721,59.599,185.972,83.103z');
|
||||
path.position = view.center;
|
||||
path.fitBounds(view.bounds.scale(0.7));
|
||||
path.fillColor = 'orange';
|
||||
|
||||
// create a marking layer to put temporary items
|
||||
var layer = new Layer();
|
||||
layer.activate();
|
||||
|
||||
// init drawing with a vertical vector
|
||||
drawTangentsToVector(new Point(0, 1));
|
||||
|
||||
function onMouseMove(event) {
|
||||
// draw tangents to vector between view center and mouse pointer
|
||||
drawTangentsToVector(event.point - view.center);
|
||||
}
|
||||
|
||||
function drawTangentsToVector(vector) {
|
||||
// adapt vector length for display
|
||||
vector.length = 50;
|
||||
|
||||
// remove existing marking items
|
||||
layer.removeChildren();
|
||||
|
||||
// draw a line at view center to show vector direction
|
||||
var line = new Path.Line({
|
||||
from: view.center + vector,
|
||||
to: view.center - vector,
|
||||
strokeColor: 'black',
|
||||
strokeWidth: 2
|
||||
});
|
||||
|
||||
// get path times where path is tangent to vector
|
||||
var offsets = path.getOffsetsWithTangent(vector);
|
||||
for (var i = 0; i < offsets.length; i++) {
|
||||
var point = path.getPointAt(offsets[i]);
|
||||
// draw a circle around point
|
||||
new Path.Circle({
|
||||
center: point,
|
||||
radius: 5,
|
||||
strokeColor: 'red'
|
||||
});
|
||||
// draw a line showing tangent
|
||||
new Path.Line({
|
||||
from: point + vector,
|
||||
to: point - vector,
|
||||
strokeColor: 'red'
|
||||
});
|
||||
// draw a line showing point precisely on the path
|
||||
new Path.Line({
|
||||
from: point + vector.rotate(90).normalize(10),
|
||||
to: point - vector.rotate(90).normalize(10),
|
||||
strokeColor: 'red'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<canvas id='canvas' resize></canvas>
|
||||
</body>
|
||||
</html>
|
|
@ -1 +1 @@
|
|||
Subproject commit 59faf79f13765dfd1fd8ec302d5627bbe548b2fa
|
||||
Subproject commit 2533ac8e1863262f3c28cd29bc940c6d2ecdf147
|
|
@ -92,7 +92,7 @@ packages.forEach(function(name) {
|
|||
gulp.task('publish:packages:' + name, ['publish:version'], function() {
|
||||
var path = 'packages/' + name,
|
||||
opts = { cwd: path };
|
||||
gulp.src(['package.json'], opts)
|
||||
return gulp.src(['package.json'], opts)
|
||||
.pipe(jsonEditor({
|
||||
version: options.version,
|
||||
dependencies: {
|
||||
|
|
|
@ -29,7 +29,7 @@ gulp.task('test:phantom', ['minify:acorn'], function() {
|
|||
return gulp.src('index.html', { cwd: 'test' })
|
||||
.pipe(qunits({
|
||||
checkGlobals: true,
|
||||
timeout: 20
|
||||
timeout: 40
|
||||
}));
|
||||
});
|
||||
|
||||
|
|
36
package.json
36
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@scratch/paper",
|
||||
"version": "0.11.5",
|
||||
"version": "0.11.8",
|
||||
"description": "The Swiss Army Knife of Vector Graphics Scripting",
|
||||
"license": "MIT",
|
||||
"homepage": "http://paperjs.org",
|
||||
|
@ -9,10 +9,7 @@
|
|||
"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@studiomoniker.com> (http://studiomoniker.com)"],
|
||||
"main": "dist/paper-full.js",
|
||||
"scripts": {
|
||||
"precommit": "gulp jshint --branch develop",
|
||||
|
@ -25,14 +22,7 @@
|
|||
"jshint": "gulp jshint",
|
||||
"test": "gulp test"
|
||||
},
|
||||
"files": [
|
||||
"AUTHORS.md",
|
||||
"CHANGELOG.md",
|
||||
"dist/",
|
||||
"examples/",
|
||||
"LICENSE.txt",
|
||||
"README.md"
|
||||
],
|
||||
"files": ["AUTHORS.md", "CHANGELOG.md", "dist/", "examples/", "LICENSE.txt", "README.md"],
|
||||
"engines": {
|
||||
"node": ">=4.0.0"
|
||||
},
|
||||
|
@ -42,7 +32,7 @@
|
|||
"del": "^2.2.1",
|
||||
"gulp": "^3.9.1",
|
||||
"gulp-cached": "^1.1.0",
|
||||
"gulp-git-streamed": "^2.4.0",
|
||||
"gulp-git-streamed": "^2.8.1",
|
||||
"gulp-jshint": "^2.0.0",
|
||||
"gulp-json-editor": "^2.2.1",
|
||||
"gulp-prepro": "^2.4.0",
|
||||
|
@ -79,21 +69,5 @@
|
|||
"./dist/node/self.js": false,
|
||||
"./dist/node/extend.js": false
|
||||
},
|
||||
"keywords": [
|
||||
"vector",
|
||||
"graphic",
|
||||
"graphics",
|
||||
"2d",
|
||||
"geometry",
|
||||
"bezier",
|
||||
"curve",
|
||||
"curves",
|
||||
"path",
|
||||
"paths",
|
||||
"canvas",
|
||||
"svg",
|
||||
"paper",
|
||||
"paper.js",
|
||||
"paperjs"
|
||||
]
|
||||
"keywords": ["vector", "graphic", "graphics", "2d", "geometry", "bezier", "curve", "curves", "path", "paths", "canvas", "svg", "paper", "paper.js", "paperjs"]
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit afd2bbbf1cea00f1f94ff89c8a3dd370888ac705
|
||||
Subproject commit f601084fc319734d0bf47da700d6b6bff95260ba
|
|
@ -1 +1 @@
|
|||
Subproject commit f6794e0749cfb65d5138f3512fc0eee755bc1829
|
||||
Subproject commit a07b7d149f02e980dfd837cd595f5000a9d5e052
|
|
@ -131,7 +131,7 @@ var Matrix = Base.extend(/** @lends Matrix# */{
|
|||
if (owner._applyMatrix) {
|
||||
owner.transform(null, true);
|
||||
} else {
|
||||
owner._changed(/*#=*/Change.GEOMETRY);
|
||||
owner._changed(/*#=*/Change.MATRIX);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -672,12 +672,11 @@ var Matrix = Base.extend(/** @lends Matrix# */{
|
|||
},
|
||||
|
||||
/**
|
||||
* Attempts to decompose the affine transformation described by this matrix
|
||||
* into `scaling`, `rotation` and `skewing`, and returns an object with
|
||||
* these properties if it succeeded, `null` otherwise.
|
||||
* Decomposes the affine transformation described by this matrix into
|
||||
* `scaling`, `rotation` and `skewing`, and returns an object with
|
||||
* these properties.
|
||||
*
|
||||
* @return {Object} the decomposed matrix, or `null` if decomposition is not
|
||||
* possible
|
||||
* @return {Object} the decomposed matrix
|
||||
*/
|
||||
decompose: function() {
|
||||
// http://dev.w3.org/csswg/css3-2d-transforms/#matrix-decomposition
|
||||
|
@ -795,7 +794,7 @@ var Matrix = Base.extend(/** @lends Matrix# */{
|
|||
* @see #decompose()
|
||||
*/
|
||||
getScaling: function() {
|
||||
return (this.decompose() || {}).scaling;
|
||||
return this.decompose().scaling;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -806,7 +805,7 @@ var Matrix = Base.extend(/** @lends Matrix# */{
|
|||
* @see #decompose()
|
||||
*/
|
||||
getRotation: function() {
|
||||
return (this.decompose() || {}).rotation;
|
||||
return this.decompose().rotation;
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -515,8 +515,7 @@ statics: /** @lends Base */{
|
|||
if (create) {
|
||||
res = create(type, args, isFirst || _isRoot);
|
||||
} else {
|
||||
res = Base.create(type.prototype);
|
||||
type.apply(res, args);
|
||||
res = new type(args);
|
||||
}
|
||||
}
|
||||
} else if (Base.isPlainObject(json)) {
|
||||
|
@ -572,6 +571,27 @@ statics: /** @lends Base */{
|
|||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Utility function for pushing a large amount of items to an array.
|
||||
*/
|
||||
push: function(list, items) {
|
||||
var itemsLength = items.length;
|
||||
// It seems for "small" amounts of items, this performs better,
|
||||
// but once it reaches a certain amount, some browsers start crashing:
|
||||
if (itemsLength < 4096) {
|
||||
list.push.apply(list, items);
|
||||
} else {
|
||||
// Use a loop as the best way to handle big arrays (see #1493).
|
||||
// Set new array length once before the loop for better performance.
|
||||
var startLength = list.length;
|
||||
list.length += itemsLength;
|
||||
for (var i = 0; i < itemsLength; i++) {
|
||||
list[startLength + i] = items[i];
|
||||
}
|
||||
}
|
||||
return list;
|
||||
},
|
||||
|
||||
/**
|
||||
* Utility function for adding and removing items from a list of which each
|
||||
* entry keeps a reference to its index in the list in the private _index
|
||||
|
@ -588,14 +608,14 @@ statics: /** @lends Base */{
|
|||
items[i]._index = index + i;
|
||||
if (append) {
|
||||
// Append them all at the end by using push
|
||||
list.push.apply(list, items);
|
||||
Base.push(list, items);
|
||||
// Nothing removed, and nothing to adjust above
|
||||
return [];
|
||||
} else {
|
||||
// Insert somewhere else and/or remove
|
||||
var args = [index, remove];
|
||||
if (items)
|
||||
args.push.apply(args, items);
|
||||
Base.push(args, items);
|
||||
var removed = list.splice.apply(list, args);
|
||||
// Erase the indices of the removed items
|
||||
for (var i = 0, l = removed.length; i < l; i++)
|
||||
|
|
|
@ -203,8 +203,11 @@ var PaperScope = Base.extend(/** @lends PaperScope# */{
|
|||
* @param {Object} [option] the compilation options
|
||||
*/
|
||||
execute: function(code, options) {
|
||||
paper.PaperScript.execute(code, this, options);
|
||||
View.updateFocus();
|
||||
/*#*/ if (__options.paperScript) {
|
||||
var exports = paper.PaperScript.execute(code, this, options);
|
||||
View.updateFocus();
|
||||
return exports;
|
||||
/*#*/ }
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -246,9 +249,10 @@ var PaperScope = Base.extend(/** @lends PaperScope# */{
|
|||
* Sets up an empty project for us. If a canvas is provided, it also creates
|
||||
* a {@link View} for it, both linked to this scope.
|
||||
*
|
||||
* @param {HTMLCanvasElement|String} element the HTML canvas element this
|
||||
* scope should be associated with, or an ID string by which to find the
|
||||
* element.
|
||||
* @param {HTMLCanvasElement|String|Size} element the HTML canvas element
|
||||
* this scope should be associated with, or an ID string by which to find
|
||||
* the element, or the size of the canvas to be created for usage in a web
|
||||
* worker.
|
||||
*/
|
||||
setup: function(element) {
|
||||
// Make sure this is the active scope, so the created project and view
|
||||
|
|
|
@ -284,6 +284,40 @@ Base.exports.PaperScript = function() {
|
|||
}
|
||||
}
|
||||
break;
|
||||
case 'ExportDefaultDeclaration':
|
||||
// Convert `export default` to `module.exports = ` statements:
|
||||
replaceCode({
|
||||
range: [node.start, node.declaration.start]
|
||||
}, 'module.exports = ');
|
||||
break;
|
||||
case 'ExportNamedDeclaration':
|
||||
// Convert named exports to `module.exports.NAME = NAME;`
|
||||
// statements both for new declarations and existing specifiers:
|
||||
var declaration = node.declaration;
|
||||
var specifiers = node.specifiers;
|
||||
if (declaration) {
|
||||
var declarations = declaration.declarations;
|
||||
if (declarations) {
|
||||
declarations.forEach(function(dec) {
|
||||
replaceCode(dec, 'module.exports.' + getCode(dec));
|
||||
});
|
||||
replaceCode({
|
||||
range: [
|
||||
node.start,
|
||||
declaration.start + declaration.kind.length
|
||||
]
|
||||
}, '');
|
||||
}
|
||||
} else if (specifiers) {
|
||||
var exports = specifiers.map(function(specifier) {
|
||||
var name = getCode(specifier);
|
||||
return 'module.exports.' + name + ' = ' + name + '; ';
|
||||
}).join('');
|
||||
if (exports) {
|
||||
replaceCode(node, exports);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -364,7 +398,11 @@ Base.exports.PaperScript = function() {
|
|||
};
|
||||
}
|
||||
// Now do the parsing magic
|
||||
walkAST(parse(code, { ranges: true, preserveParens: true }));
|
||||
walkAST(parse(code, {
|
||||
ranges: true,
|
||||
preserveParens: true,
|
||||
sourceType: 'module'
|
||||
}));
|
||||
if (map) {
|
||||
if (offsetCode) {
|
||||
// Adjust the line offset of the resulting code if required.
|
||||
|
@ -404,8 +442,7 @@ 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 {Object} an object holding the compiled PaperScript translated
|
||||
* into JavaScript code along with source-maps and other information.
|
||||
* @return the exports defined in the executed code
|
||||
*/
|
||||
function execute(code, scope, options) {
|
||||
// Set currently active scope.
|
||||
|
@ -450,21 +487,26 @@ Base.exports.PaperScript = function() {
|
|||
expose({ __$__: __$__, $__: $__, paper: scope, view: view, tool: tool },
|
||||
true);
|
||||
expose(scope);
|
||||
// Add a fake `module.exports` object so PaperScripts can export things.
|
||||
code = 'var module = { exports: {} }; ' + code;
|
||||
// Finally define the handler variable names as parameters and compose
|
||||
// the string describing the properties for the returned object at the
|
||||
// end of the code execution, so we can retrieve their values from the
|
||||
// function call.
|
||||
handlers = Base.each(handlers, function(key) {
|
||||
// Check for each handler explicitly and only return them if they
|
||||
// the string describing the properties for the returned exports object
|
||||
// at the end of the code execution, so we can retrieve their values
|
||||
// from the function call.
|
||||
var exports = Base.each(handlers, function(key) {
|
||||
// Check for each handler explicitly and only export them if they
|
||||
// seem to exist.
|
||||
if (new RegExp('\\s+' + key + '\\b').test(code)) {
|
||||
params.push(key);
|
||||
this.push(key + ': ' + key);
|
||||
this.push('module.exports.' + key + ' = ' + key + ';');
|
||||
}
|
||||
}, []).join(', ');
|
||||
// We need an additional line that returns the handlers in one object.
|
||||
if (handlers)
|
||||
code += '\nreturn { ' + handlers + ' };';
|
||||
}, []).join('\n');
|
||||
// Add the setting of the exported handlers to the end of the code.
|
||||
if (exports) {
|
||||
code += '\n' + exports;
|
||||
}
|
||||
// End by returning `module.exports` at the end of the generated code:
|
||||
code += '\nreturn module.exports;';
|
||||
var agent = paper.agent;
|
||||
if (document && (agent.chrome
|
||||
|| agent.firefox && agent.versionNumber < 40)) {
|
||||
|
@ -481,39 +523,42 @@ Base.exports.PaperScript = function() {
|
|||
if (agent.firefox)
|
||||
code = '\n' + code;
|
||||
script.appendChild(document.createTextNode(
|
||||
'paper._execute = function(' + params + ') {' + code + '\n}'
|
||||
'document.__paperscript__ = function(' + params + ') {' +
|
||||
code +
|
||||
'\n}'
|
||||
));
|
||||
head.appendChild(script);
|
||||
func = paper._execute;
|
||||
delete paper._execute;
|
||||
func = document.__paperscript__;
|
||||
delete document.__paperscript__;
|
||||
head.removeChild(script);
|
||||
} else {
|
||||
func = Function(params, code);
|
||||
}
|
||||
var res = func.apply(scope, args) || {};
|
||||
var exports = func && func.apply(scope, args);
|
||||
var obj = exports || {};
|
||||
// Now install the 'global' tool and view handlers, and we're done!
|
||||
Base.each(toolHandlers, function(key) {
|
||||
var value = res[key];
|
||||
var value = obj[key];
|
||||
if (value)
|
||||
tool[key] = value;
|
||||
});
|
||||
if (view) {
|
||||
if (res.onResize)
|
||||
view.setOnResize(res.onResize);
|
||||
if (obj.onResize)
|
||||
view.setOnResize(obj.onResize);
|
||||
// Emit resize event directly, so any user
|
||||
// defined resize handlers are called.
|
||||
view.emit('resize', {
|
||||
size: view.size,
|
||||
delta: new Point()
|
||||
});
|
||||
if (res.onFrame)
|
||||
view.setOnFrame(res.onFrame);
|
||||
if (obj.onFrame)
|
||||
view.setOnFrame(obj.onFrame);
|
||||
// Automatically request an update at the end. This is only needed
|
||||
// if the script does not actually produce anything yet, and the
|
||||
// used canvas contains previous content.
|
||||
view.requestUpdate();
|
||||
}
|
||||
return compiled;
|
||||
return exports;
|
||||
}
|
||||
|
||||
function loadScript(script) {
|
||||
|
|
|
@ -84,14 +84,14 @@ var DomElement = new function() {
|
|||
},
|
||||
|
||||
/**
|
||||
* Checks if element is invisibile (display: none, ...).
|
||||
* Checks if element is invisible (display: none, ...).
|
||||
*/
|
||||
isInvisible: function(el) {
|
||||
return DomElement.getSize(el).equals(new Size(0, 0));
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks if element is visibile in current viewport.
|
||||
* Checks if element is visible in current viewport.
|
||||
*/
|
||||
isInView: function(el) {
|
||||
// See if the viewport bounds intersect with the windows rectangle
|
||||
|
|
|
@ -23,8 +23,20 @@ var DomEvent = /** @lends DomEvent */{
|
|||
for (var type in events) {
|
||||
var func = events[type],
|
||||
parts = type.split(/[\s,]+/g);
|
||||
for (var i = 0, l = parts.length; i < l; i++)
|
||||
el.addEventListener(parts[i], func, false);
|
||||
for (var i = 0, l = parts.length; i < l; i++) {
|
||||
var name = parts[i];
|
||||
// For touchstart/touchmove events on document, we need to
|
||||
// explicitly declare that the event is not passive (can be
|
||||
// prevented). Otherwise chrome browser would ignore
|
||||
// `event.preventDefault()` calls and omit warnings.
|
||||
// See #1501 and:
|
||||
// https://www.chromestatus.com/features/5093566007214080
|
||||
var options = (
|
||||
el === document
|
||||
&& (name === 'touchstart' || name === 'touchmove')
|
||||
) ? { passive: false } : false;
|
||||
el.addEventListener(name, func, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
// NOTE: Do not create local variable `var paper` since it would shield the
|
||||
// global one in the whole scope.
|
||||
|
||||
paper = new (PaperScope.inject(Base.exports, {
|
||||
var paper = new (PaperScope.inject(Base.exports, {
|
||||
Base: Base,
|
||||
Numerical: Numerical,
|
||||
Key: Key,
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
// Node.js,only the files included in such a way see each other's variables in
|
||||
// their shared scope.
|
||||
|
||||
/* global document:true, window:true */
|
||||
// Set up a local `window` variable valid across the full the paper.js scope,
|
||||
// pointing to the native window in browsers and the one provided by JSDom in
|
||||
// Node.js
|
||||
|
|
|
@ -17,27 +17,29 @@ var ChangeFlag = {
|
|||
// A change in the item's children
|
||||
CHILDREN: 0x2,
|
||||
// A change of the item's place in the scene graph (removed, inserted,
|
||||
// moved).
|
||||
// moved)
|
||||
INSERTION: 0x4,
|
||||
// Item geometry (path, bounds)
|
||||
GEOMETRY: 0x8,
|
||||
// The item's matrix has changed
|
||||
MATRIX: 0x10,
|
||||
// Only segment(s) have changed, and affected curves have already been
|
||||
// notified. This is to implement an optimization in _changed() calls.
|
||||
SEGMENTS: 0x10,
|
||||
// notified. This is to implement an optimization in _changed() calls
|
||||
SEGMENTS: 0x20,
|
||||
// Stroke geometry (excluding color)
|
||||
STROKE: 0x20,
|
||||
STROKE: 0x40,
|
||||
// Fill style or stroke color / dash
|
||||
STYLE: 0x40,
|
||||
STYLE: 0x80,
|
||||
// Item attributes: visible, blendMode, locked, name, opacity, clipMask ...
|
||||
ATTRIBUTE: 0x80,
|
||||
ATTRIBUTE: 0x100,
|
||||
// Text content
|
||||
CONTENT: 0x100,
|
||||
CONTENT: 0x200,
|
||||
// Raster pixels
|
||||
PIXELS: 0x200,
|
||||
PIXELS: 0x400,
|
||||
// Clipping in one of the child items
|
||||
CLIPPING: 0x400,
|
||||
CLIPPING: 0x800,
|
||||
// The view has been transformed
|
||||
VIEW: 0x800
|
||||
VIEW: 0x1000
|
||||
};
|
||||
|
||||
// Shortcuts to often used ChangeFlag values including APPEARANCE
|
||||
|
@ -48,6 +50,7 @@ var Change = {
|
|||
// Changing the insertion can change the appearance through parent's matrix.
|
||||
INSERTION: ChangeFlag.INSERTION | ChangeFlag.APPEARANCE,
|
||||
GEOMETRY: ChangeFlag.GEOMETRY | ChangeFlag.APPEARANCE,
|
||||
MATRIX: ChangeFlag.MATRIX | ChangeFlag.GEOMETRY | ChangeFlag.APPEARANCE,
|
||||
SEGMENTS: ChangeFlag.SEGMENTS | ChangeFlag.GEOMETRY | ChangeFlag.APPEARANCE,
|
||||
STROKE: ChangeFlag.STROKE | ChangeFlag.STYLE | ChangeFlag.APPEARANCE,
|
||||
STYLE: ChangeFlag.STYLE | ChangeFlag.APPEARANCE,
|
||||
|
|
137
src/item/Item.js
137
src/item/Item.js
|
@ -216,8 +216,10 @@ new function() { // Injection scope for various item event handlers
|
|||
if (flags & /*#=*/ChangeFlag.GEOMETRY) {
|
||||
// Clear cached bounds, position and decomposed matrix whenever
|
||||
// geometry changes.
|
||||
this._bounds = this._position = this._decomposed =
|
||||
this._globalMatrix = undefined;
|
||||
this._bounds = this._position = this._decomposed = undefined;
|
||||
}
|
||||
if (flags & /*#=*/ChangeFlag.MATRIX) {
|
||||
this._globalMatrix = undefined;
|
||||
}
|
||||
if (cacheParent
|
||||
&& (flags & /*#=*/(ChangeFlag.GEOMETRY | ChangeFlag.STROKE))) {
|
||||
|
@ -411,7 +413,7 @@ new function() { // Injection scope for various item event handlers
|
|||
flags = {
|
||||
// #locked does not change appearance, all others do:
|
||||
locked: /*#=*/ChangeFlag.ATTRIBUTE,
|
||||
// #visible changes apperance
|
||||
// #visible changes appearance
|
||||
visible: /*#=*/(Change.ATTRIBUTE | Change.GEOMETRY)
|
||||
};
|
||||
this['get' + part] = function() {
|
||||
|
@ -433,12 +435,39 @@ new function() { // Injection scope for various item event handlers
|
|||
// injection scope above.
|
||||
|
||||
/**
|
||||
* Specifies whether the item is locked.
|
||||
* Specifies whether the item is locked. When set to `true`, item
|
||||
* interactions with the mouse are disabled.
|
||||
*
|
||||
* @name Item#locked
|
||||
* @type Boolean
|
||||
* @default false
|
||||
* @ignore
|
||||
*
|
||||
* @example {@paperscript}
|
||||
* var unlockedItem = new Path.Circle({
|
||||
* center: view.center - [35, 0],
|
||||
* radius: 30,
|
||||
* fillColor: 'springgreen',
|
||||
* onMouseDown: function() {
|
||||
* this.fillColor = Color.random();
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* var lockedItem = new Path.Circle({
|
||||
* center: view.center + [35, 0],
|
||||
* radius: 30,
|
||||
* fillColor: 'crimson',
|
||||
* locked: true,
|
||||
* // This event won't be triggered because the item is locked.
|
||||
* onMouseDown: function() {
|
||||
* this.fillColor = Color.random();
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* new PointText({
|
||||
* content: 'Click on both circles to see which one is locked.',
|
||||
* point: view.center - [0, 35],
|
||||
* justification: 'center'
|
||||
* });
|
||||
*/
|
||||
|
||||
/**
|
||||
|
@ -473,7 +502,7 @@ new function() { // Injection scope for various item event handlers
|
|||
* light', 'color-dodge', 'color-burn', 'darken', 'lighten',
|
||||
* 'difference', 'exclusion', 'hue', 'saturation', 'luminosity',
|
||||
* 'color', 'add', 'subtract', 'average', 'pin-light', 'negation',
|
||||
* 'source- over', 'source-in', 'source-out', 'source-atop',
|
||||
* 'source-over', 'source-in', 'source-out', 'source-atop',
|
||||
* 'destination-over', 'destination-in', 'destination-out',
|
||||
* 'destination-atop', 'lighter', 'darker', 'copy', 'xor'
|
||||
* @default 'normal'
|
||||
|
@ -746,20 +775,13 @@ new function() { // Injection scope for various item event handlers
|
|||
getPosition: function(_dontLink) {
|
||||
// Cache position value.
|
||||
// Pass true for _dontLink in getCenter(), so receive back a normal point
|
||||
var position = this._position,
|
||||
ctor = _dontLink ? Point : LinkedPoint;
|
||||
var ctor = _dontLink ? Point : LinkedPoint;
|
||||
// Do not cache LinkedPoints directly, since we would not be able to
|
||||
// use them to calculate the difference in #setPosition, as when it is
|
||||
// modified, it would hold new values already and only then cause the
|
||||
// calling of #setPosition.
|
||||
if (!position) {
|
||||
// If an pivot point is provided, use it to determine position
|
||||
// based on the matrix. Otherwise use the center of the bounds.
|
||||
var pivot = this._pivot;
|
||||
position = this._position = pivot
|
||||
? this._matrix._transformPoint(pivot)
|
||||
: this.getBounds().getCenter(true);
|
||||
}
|
||||
var position = this._position ||
|
||||
(this._position = this._getPositionFromBounds());
|
||||
return new ctor(position.x, position.y, this, 'setPosition');
|
||||
},
|
||||
|
||||
|
@ -770,6 +792,22 @@ new function() { // Injection scope for various item event handlers
|
|||
this.translate(Point.read(arguments).subtract(this.getPosition(true)));
|
||||
},
|
||||
|
||||
/**
|
||||
* Internal method used to calculate position either from pivot point or
|
||||
* bounds.
|
||||
* @param {Rectangle} bounds if provided, these bounds are used instead of
|
||||
* calling getBounds()
|
||||
* @return {Point} the transformed pivot point or the center of the bounds
|
||||
* @private
|
||||
*/
|
||||
_getPositionFromBounds: function(bounds) {
|
||||
// If an pivot point is provided, use it to determine position
|
||||
// based on the matrix. Otherwise use the center of the bounds.
|
||||
return this._pivot
|
||||
? this._matrix._transformPoint(this._pivot)
|
||||
: (bounds || this.getBounds()).getCenter(true);
|
||||
},
|
||||
|
||||
/**
|
||||
* The item's pivot point specified in the item coordinate system, defining
|
||||
* the point around which all transformations are hinging. This is also the
|
||||
|
@ -1204,17 +1242,33 @@ new function() { // Injection scope for various item event handlers
|
|||
* @type Matrix
|
||||
*/
|
||||
getGlobalMatrix: function(_dontClone) {
|
||||
var matrix = this._globalMatrix,
|
||||
updateVersion = this._project._updateVersion;
|
||||
// If #_globalMatrix is out of sync, recalculate it now.
|
||||
if (matrix && matrix._updateVersion !== updateVersion)
|
||||
matrix = null;
|
||||
var matrix = this._globalMatrix;
|
||||
if (matrix) {
|
||||
// If there's a cached global matrix for this item, check if all its
|
||||
// parents also have one. If it's missing in any of its parents, it
|
||||
// means the child's cached version isn't valid anymore.
|
||||
// For better performance, we also use the occasion of this loop to
|
||||
// clear cached version of items parents.
|
||||
var parent = this._parent;
|
||||
var parents = [];
|
||||
while (parent) {
|
||||
if (!parent._globalMatrix) {
|
||||
matrix = null;
|
||||
// Also clear global matrix of item's parents.
|
||||
for (var i = 0, l = parents.length; i < l; i++) {
|
||||
parents[i]._globalMatrix = null;
|
||||
}
|
||||
break;
|
||||
}
|
||||
parents.push(parent);
|
||||
parent = parent._parent;
|
||||
}
|
||||
}
|
||||
if (!matrix) {
|
||||
matrix = this._globalMatrix = this._matrix.clone();
|
||||
var parent = this._parent;
|
||||
if (parent)
|
||||
matrix.prepend(parent.getGlobalMatrix(true));
|
||||
matrix._updateVersion = updateVersion;
|
||||
}
|
||||
return _dontClone ? matrix : matrix.clone();
|
||||
},
|
||||
|
@ -1901,6 +1955,7 @@ new function() { // Injection scope for hit-test functions shared with project
|
|||
* fills for paths
|
||||
*
|
||||
* @param {Point} point the point where the hit-test should be performed
|
||||
* (in global coordinates system).
|
||||
* @param {Object} [options={ fill: true, stroke: true, segments: true,
|
||||
* tolerance: settings.hitTolerance }]
|
||||
* @return {HitResult} a hit result object describing what exactly was hit
|
||||
|
@ -1918,6 +1973,7 @@ new function() { // Injection scope for hit-test functions shared with project
|
|||
* @name Item#hitTestAll
|
||||
* @function
|
||||
* @param {Point} point the point where the hit-test should be performed
|
||||
* (in global coordinates system).
|
||||
* @param {Object} [options={ fill: true, stroke: true, segments: true,
|
||||
* tolerance: settings.hitTolerance }]
|
||||
* @return {HitResult[]} hit result objects for all hits, describing what
|
||||
|
@ -2667,6 +2723,8 @@ new function() { // Injection scope for hit-test functions shared with project
|
|||
var owner = this._getOwner(),
|
||||
project = this._project,
|
||||
index = this._index;
|
||||
if (this._style)
|
||||
this._style._dispose();
|
||||
if (owner) {
|
||||
// Handle named children separately from index:
|
||||
if (this._name)
|
||||
|
@ -3429,19 +3487,19 @@ new function() { // Injection scope for hit-test functions shared with project
|
|||
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
|
||||
transform = matrix && !matrix.isIdentity(),
|
||||
transformMatrix = matrix && !matrix.isIdentity(),
|
||||
applyMatrix = (_applyMatrix || this._applyMatrix)
|
||||
// Don't apply _matrix if the result of concatenating with
|
||||
// matrix would be identity.
|
||||
&& ((!_matrix.isIdentity() || transform)
|
||||
&& ((!_matrix.isIdentity() || transformMatrix)
|
||||
// Even if it's an identity matrix, we still need to
|
||||
// recursively apply the matrix to children.
|
||||
|| _applyMatrix && _applyRecursively && this._children);
|
||||
// Bail out if there is nothing to do.
|
||||
if (!transform && !applyMatrix)
|
||||
if (!transformMatrix && !applyMatrix)
|
||||
return this;
|
||||
// Simply prepend the internal matrix with the passed one:
|
||||
if (transform) {
|
||||
if (transformMatrix) {
|
||||
// Keep a backup of the last valid state before the matrix becomes
|
||||
// non-invertible. This is then used again in setBounds to restore.
|
||||
if (!matrix.isInvertible() && _matrix.isInvertible())
|
||||
|
@ -3487,13 +3545,13 @@ new function() { // Injection scope for hit-test functions shared with project
|
|||
// on matrix we can calculate and set them again, so preserve them.
|
||||
var bounds = this._bounds,
|
||||
position = this._position;
|
||||
if (transform || applyMatrix) {
|
||||
this._changed(/*#=*/Change.GEOMETRY);
|
||||
if (transformMatrix || applyMatrix) {
|
||||
this._changed(/*#=*/Change.MATRIX);
|
||||
}
|
||||
// Detect matrices that contain only translations and scaling
|
||||
// and transform the cached _bounds and _position without having to
|
||||
// fully recalculate each time.
|
||||
var decomp = transform && bounds && matrix.decompose();
|
||||
var decomp = transformMatrix && bounds && matrix.decompose();
|
||||
if (decomp && decomp.skewing.isZero() && decomp.rotation % 90 === 0) {
|
||||
// Transform the old bound by looping through all the cached
|
||||
// bounds in _bounds and transform each.
|
||||
|
@ -3515,11 +3573,12 @@ new function() { // Injection scope for hit-test functions shared with project
|
|||
// If we have cached bounds, try to determine _position as its
|
||||
// center. Use _boundsOptions do get the cached default bounds.
|
||||
var cached = bounds[this._getBoundsCacheKey(
|
||||
this._boundsOptions || {})];
|
||||
this._boundsOptions || {})];
|
||||
if (cached) {
|
||||
this._position = cached.rect.getCenter(true);
|
||||
// use this method to handle pivot case (see #1503)
|
||||
this._position = this._getPositionFromBounds(cached.rect);
|
||||
}
|
||||
} else if (transform && position && this._pivot) {
|
||||
} else if (transformMatrix && position && this._pivot) {
|
||||
// If the item has a pivot defined, it means that the default
|
||||
// position defined as the center of the bounds won't shift with
|
||||
// arbitrary transformations and we can therefore update _position:
|
||||
|
@ -4260,8 +4319,6 @@ new function() { // Injection scope for hit-test functions shared with project
|
|||
// Only keep track of transformation if told so. See Project#draw()
|
||||
matrices.push(globalMatrix);
|
||||
if (param.updateMatrix) {
|
||||
// Update the cached _globalMatrix and keep it versioned.
|
||||
globalMatrix._updateVersion = updateVersion;
|
||||
this._globalMatrix = globalMatrix;
|
||||
}
|
||||
|
||||
|
@ -4291,8 +4348,12 @@ new function() { // Injection scope for hit-test functions shared with project
|
|||
// Apply the parent's global matrix to the calculation of correct
|
||||
// bounds.
|
||||
var bounds = this.getStrokeBounds(viewMatrix);
|
||||
if (!bounds.width || !bounds.height)
|
||||
if (!bounds.width || !bounds.height) {
|
||||
// Item won't be drawn so its global matrix need to be removed
|
||||
// from the stack (#1561).
|
||||
matrices.pop();
|
||||
return;
|
||||
}
|
||||
// Store previous offset and save the main context, so we can
|
||||
// draw onto it later.
|
||||
prevOffset = param.offset;
|
||||
|
@ -4417,7 +4478,11 @@ new function() { // Injection scope for hit-test functions shared with project
|
|||
if (itemSelected)
|
||||
this._drawSelected(ctx, mx, selectionItems);
|
||||
if (positionSelected) {
|
||||
var point = this.getPosition(true),
|
||||
// Convert position from the parent's coordinates system to the
|
||||
// global one:
|
||||
var pos = this.getPosition(true),
|
||||
parent = this._parent,
|
||||
point = parent ? parent.localToGlobal(pos) : pos,
|
||||
x = point.x,
|
||||
y = point.y;
|
||||
ctx.beginPath();
|
||||
|
|
|
@ -44,9 +44,10 @@ var Project = PaperScopeItem.extend(/** @lends Project# */{
|
|||
* Note that when working with PaperScript, a project is automatically
|
||||
* created for us and the {@link PaperScope#project} variable points to it.
|
||||
*
|
||||
* @param {HTMLCanvasElement|String} element the HTML canvas element that
|
||||
* should be used as the element for the view, or an ID string by which to
|
||||
* find the element.
|
||||
* @param {HTMLCanvasElement|String|Size} element the HTML canvas element
|
||||
* that should be used as the element for the view, or an ID string by which
|
||||
* to find the element, or the size of the canvas to be created for usage in
|
||||
* a web worker.
|
||||
*/
|
||||
initialize: function Project(element) {
|
||||
// Activate straight away by passing true to PaperScopeItem constructor,
|
||||
|
|
|
@ -30,6 +30,7 @@ var Raster = Item.extend(/** @lends Raster# */{
|
|||
},
|
||||
// Prioritize `crossOrigin` over `source`:
|
||||
_prioritize: ['crossOrigin'],
|
||||
_smoothing: true,
|
||||
|
||||
// TODO: Implement type, width, height.
|
||||
// TODO: Have SymbolItem & Raster inherit from a shared class?
|
||||
|
@ -419,6 +420,30 @@ var Raster = Item.extend(/** @lends Raster# */{
|
|||
image.crossOrigin = crossOrigin;
|
||||
},
|
||||
|
||||
/**
|
||||
* Specifies if the raster should be smoothed when scaled up or if the
|
||||
* pixels should be scaled up by repeating the nearest neighboring pixels.
|
||||
*
|
||||
* @bean
|
||||
* @type Boolean
|
||||
* @default true
|
||||
*
|
||||
* @example {@paperscript}
|
||||
* var raster = new Raster({
|
||||
* source: 'http://assets.paperjs.org/images/marilyn.jpg',
|
||||
* smoothing: false
|
||||
* });
|
||||
* raster.scale(5);
|
||||
*/
|
||||
getSmoothing: function() {
|
||||
return this._smoothing;
|
||||
},
|
||||
|
||||
setSmoothing: function(smoothing) {
|
||||
this._smoothing = smoothing;
|
||||
this._changed(/*#=*/Change.ATTRIBUTE);
|
||||
},
|
||||
|
||||
// DOCS: document Raster#getElement
|
||||
getElement: function() {
|
||||
// Only return the internal element if the content is actually ready.
|
||||
|
@ -734,12 +759,23 @@ var Raster = Item.extend(/** @lends Raster# */{
|
|||
}
|
||||
},
|
||||
|
||||
_draw: function(ctx) {
|
||||
_draw: function(ctx, param, viewMatrix) {
|
||||
var element = this.getElement();
|
||||
if (element) {
|
||||
// Handle opacity for Rasters separately from the rest, since
|
||||
// Rasters never draw a stroke. See Item#draw().
|
||||
ctx.globalAlpha = this._opacity;
|
||||
|
||||
// Call _setStyles() to make sure shadow is drawn (#1437).
|
||||
this._setStyles(ctx, param, viewMatrix);
|
||||
|
||||
// Set context smoothing value according to raster property.
|
||||
// There's no need to restore original value after drawing due to
|
||||
// the call to ctx.restore() in Item#draw() after this method call.
|
||||
DomElement.setPrefixed(
|
||||
ctx, 'imageSmoothingEnabled', this._smoothing
|
||||
);
|
||||
|
||||
ctx.drawImage(element,
|
||||
-this._size.width / 2, -this._size.height / 2);
|
||||
}
|
||||
|
|
|
@ -162,7 +162,7 @@ var Shape = Item.extend(/** @lends Shape# */{
|
|||
* @param {Boolean} [insert=true] specifies whether the new path should be
|
||||
* inserted into the scene graph. When set to `true`, it is inserted
|
||||
* above the shape item
|
||||
* @return {Shape} the newly created path item with the same geometry as
|
||||
* @return {Path} the newly created path item with the same geometry as
|
||||
* this shape item
|
||||
* @see Path#toShape(insert)
|
||||
*/
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
// the browser, avoiding the step of having to manually preprocess it after each
|
||||
// change. This is very useful during development of the library itself.
|
||||
if (typeof window === 'object') {
|
||||
// Browser based loading through Prepro.js:
|
||||
// Browser based loading through Prepro.js:
|
||||
if (!window.include) {
|
||||
// Get the last script tag and assume it's the one that loaded this file
|
||||
// then get its src attribute and figure out the location of our root.
|
||||
|
@ -36,6 +36,13 @@ if (typeof window === 'object') {
|
|||
// the code the 2nd time around.
|
||||
load(root + 'src/load.js');
|
||||
} 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
|
||||
};
|
||||
|
||||
include('options.js');
|
||||
// Load constants.js, required by the on-the-fly preprocessing:
|
||||
include('constants.js');
|
||||
|
|
|
@ -19,7 +19,7 @@ module.exports = function(self, requireName) {
|
|||
var Canvas;
|
||||
try {
|
||||
Canvas = require('canvas');
|
||||
} catch(e) {
|
||||
} catch(error) {
|
||||
// Remove `self.window`, so we still have the global `self` reference,
|
||||
// but no `window` object:
|
||||
// - On the browser, this corresponds to a worker context.
|
||||
|
|
|
@ -17,7 +17,7 @@ var path = require('path');
|
|||
// Determine the name by which name the module was required (either 'paper',
|
||||
// 'paper-jsdom' or 'paper-jsdom-canvas'), and use this to determine if error
|
||||
// exceptions should be thrown or if loading should fail silently.
|
||||
var parent = module.parent.parent,
|
||||
var parent = module.parent && module.parent.parent,
|
||||
requireName = parent && path.basename(path.dirname(parent.filename));
|
||||
requireName = /^paper/.test(requireName) ? requireName : 'paper';
|
||||
|
||||
|
|
|
@ -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.5';
|
||||
var version = '0.11.8';
|
||||
|
||||
// If this file is loaded in the browser, we're in load.js mode.
|
||||
var load = typeof window === 'object';
|
||||
|
|
|
@ -213,8 +213,9 @@ var CompoundPath = PathItem.extend(/** @lends CompoundPath# */{
|
|||
getCurves: function() {
|
||||
var children = this._children,
|
||||
curves = [];
|
||||
for (var i = 0, l = children.length; i < l; i++)
|
||||
curves.push.apply(curves, children[i].getCurves());
|
||||
for (var i = 0, l = children.length; i < l; i++) {
|
||||
Base.push(curves, children[i].getCurves());
|
||||
}
|
||||
return curves;
|
||||
},
|
||||
|
||||
|
|
|
@ -1147,6 +1147,21 @@ statics: /** @lends Curve */{
|
|||
*/
|
||||
getParameterAt: '#getTimeAt',
|
||||
|
||||
/**
|
||||
* Calculates the curve-time parameters where the curve is tangential to
|
||||
* provided tangent. Note that tangents at the start or end are included.
|
||||
*
|
||||
* @param {Point} tangent the tangent to which the curve must be tangential
|
||||
* @return {Number[]} at most two curve-time parameters, where the curve is
|
||||
* tangential to the given tangent
|
||||
*/
|
||||
getTimesWithTangent: function (/* tangent */) {
|
||||
var tangent = Point.read(arguments);
|
||||
return tangent.isZero()
|
||||
? []
|
||||
: Curve.getTimesWithTangent(this.getValues(), tangent);
|
||||
},
|
||||
|
||||
/**
|
||||
* Calculates the curve offset at the specified curve-time parameter on
|
||||
* the curve.
|
||||
|
@ -1686,7 +1701,7 @@ new function() { // Scope for methods that require private functions
|
|||
* http://math.stackexchange.com/questions/1954845/bezier-curvature-extrema
|
||||
*
|
||||
* @param {Number[]} v the curve values array
|
||||
* @returns {Number[]} the roots of all found peaks
|
||||
* @return {Number[]} the roots of all found peaks
|
||||
*/
|
||||
getPeaks: function(v) {
|
||||
var x0 = v[0], y0 = v[1],
|
||||
|
@ -2128,7 +2143,7 @@ new function() { // Scope for bezier intersection using fat-line clipping
|
|||
// Flatten the list of location arrays to one array and return it.
|
||||
locations = [];
|
||||
for (var i = 0, l = arrays.length; i < l; i++) {
|
||||
locations.push.apply(locations, arrays[i]);
|
||||
Base.push(locations, arrays[i]);
|
||||
}
|
||||
return locations;
|
||||
}
|
||||
|
@ -2230,6 +2245,56 @@ new function() { // Scope for bezier intersection using fat-line clipping
|
|||
return pairs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method to calculates the curve-time parameters where the curve
|
||||
* is tangential to provided tangent.
|
||||
* Tangents at the start or end are included.
|
||||
*
|
||||
* @param {Number[]} v curve values
|
||||
* @param {Point} tangent the tangent to which the curve must be tangential
|
||||
* @return {Number[]} at most two curve-time parameters, where the curve is
|
||||
* tangential to the given tangent
|
||||
*/
|
||||
function getTimesWithTangent(v, tangent) {
|
||||
// Algorithm adapted from: https://stackoverflow.com/a/34837312/7615922
|
||||
var x0 = v[0], y0 = v[1],
|
||||
x1 = v[2], y1 = v[3],
|
||||
x2 = v[4], y2 = v[5],
|
||||
x3 = v[6], y3 = v[7],
|
||||
normalized = tangent.normalize(),
|
||||
tx = normalized.x,
|
||||
ty = normalized.y,
|
||||
ax = 3 * x3 - 9 * x2 + 9 * x1 - 3 * x0,
|
||||
ay = 3 * y3 - 9 * y2 + 9 * y1 - 3 * y0,
|
||||
bx = 6 * x2 - 12 * x1 + 6 * x0,
|
||||
by = 6 * y2 - 12 * y1 + 6 * y0,
|
||||
cx = 3 * x1 - 3 * x0,
|
||||
cy = 3 * y1 - 3 * y0,
|
||||
den = 2 * ax * ty - 2 * ay * tx,
|
||||
times = [];
|
||||
if (Math.abs(den) < Numerical.CURVETIME_EPSILON) {
|
||||
var num = ax * cy - ay * cx,
|
||||
den = ax * by - ay * bx;
|
||||
if (den != 0) {
|
||||
var t = -num / den;
|
||||
if (t >= 0 && t <= 1) times.push(t);
|
||||
}
|
||||
} else {
|
||||
var delta = (bx * bx - 4 * ax * cx) * ty * ty +
|
||||
(-2 * bx * by + 4 * ay * cx + 4 * ax * cy) * tx * ty +
|
||||
(by * by - 4 * ay * cy) * tx * tx,
|
||||
k = bx * ty - by * tx;
|
||||
if (delta >= 0 && den != 0) {
|
||||
var d = Math.sqrt(delta),
|
||||
t0 = -(k + d) / den,
|
||||
t1 = (-k + d) / den;
|
||||
if (t0 >= 0 && t0 <= 1) times.push(t0);
|
||||
if (t1 >= 0 && t1 <= 1) times.push(t1);
|
||||
}
|
||||
}
|
||||
return times;
|
||||
}
|
||||
|
||||
return /** @lends Curve# */{
|
||||
/**
|
||||
* Returns all intersections between two {@link Curve} objects as an
|
||||
|
@ -2252,7 +2317,8 @@ new function() { // Scope for bezier intersection using fat-line clipping
|
|||
getOverlaps: getOverlaps,
|
||||
// Exposed for use in boolean offsetting
|
||||
getIntersections: getIntersections,
|
||||
getCurveLineIntersections: getCurveLineIntersections
|
||||
getCurveLineIntersections: getCurveLineIntersections,
|
||||
getTimesWithTangent: getTimesWithTangent
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
@ -404,8 +404,8 @@ var Path = PathItem.extend(/** @lends Path# */{
|
|||
this._updateSelection(segment, 0, segment._selection);
|
||||
}
|
||||
if (append) {
|
||||
// Append them all at the end by using push
|
||||
segments.push.apply(segments, segs);
|
||||
// Append them all at the end.
|
||||
Base.push(segments, segs);
|
||||
} else {
|
||||
// Insert somewhere else
|
||||
segments.splice.apply(segments, [index, 0].concat(segs));
|
||||
|
@ -1012,7 +1012,7 @@ var Path = PathItem.extend(/** @lends Path# */{
|
|||
* path.strokeColor = 'black';
|
||||
*
|
||||
* // Split the path half-way:
|
||||
* var path2 = path.splitAt(path2.length / 2);
|
||||
* var path2 = path.splitAt(path.length / 2);
|
||||
*
|
||||
* // Give the resulting path a red stroke-color
|
||||
* // and move it 20px to the right:
|
||||
|
@ -1896,7 +1896,7 @@ var Path = PathItem.extend(/** @lends Path# */{
|
|||
return offset;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Calculates the point on the path at the given offset.
|
||||
|
@ -2124,6 +2124,42 @@ var Path = PathItem.extend(/** @lends Path# */{
|
|||
* the beginning of the path and {@link Path#length} at the end
|
||||
* @return {Number} the normal vector at the given offset
|
||||
*/
|
||||
|
||||
/**
|
||||
* Calculates path offsets where the path is tangential to the provided
|
||||
* tangent. Note that tangents at the start or end are included. Tangents at
|
||||
* segment points are returned even if only one of their handles is
|
||||
* collinear with the provided tangent.
|
||||
*
|
||||
* @param {Point} tangent the tangent to which the path must be tangential
|
||||
* @return {Number[]} path offsets where the path is tangential to the
|
||||
* provided tangent
|
||||
*/
|
||||
getOffsetsWithTangent: function(/* tangent */) {
|
||||
var tangent = Point.read(arguments);
|
||||
if (tangent.isZero()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
var offsets = [];
|
||||
var curveStart = 0;
|
||||
var curves = this.getCurves();
|
||||
for (var i = 0, l = curves.length; i < l; i++) {
|
||||
var curve = curves[i];
|
||||
// Calculate curves times at vector tangent...
|
||||
var curveTimes = curve.getTimesWithTangent(tangent);
|
||||
for (var j = 0, m = curveTimes.length; j < m; j++) {
|
||||
// ...and convert them to path offsets...
|
||||
var offset = curveStart + curve.getOffsetAtTime(curveTimes[j]);
|
||||
// ...avoiding duplicates.
|
||||
if (offsets.indexOf(offset) < 0) {
|
||||
offsets.push(offset);
|
||||
}
|
||||
}
|
||||
curveStart += curve.length;
|
||||
}
|
||||
return offsets;
|
||||
}
|
||||
}),
|
||||
new function() { // Scope for drawing
|
||||
|
||||
|
@ -2519,7 +2555,7 @@ new function() { // PostScript-style drawing commands
|
|||
}
|
||||
vector = from.subtract(center);
|
||||
extent = vector.getDirectedAngle(to.subtract(center));
|
||||
var centerSide = line.getSide(center);
|
||||
var centerSide = line.getSide(center, true);
|
||||
if (centerSide === 0) {
|
||||
// If the center is lying on the line, we might have gotten
|
||||
// the wrong sign for extent above. Use the sign of the side
|
||||
|
|
|
@ -95,7 +95,7 @@ var PathFlattener = Base.extend({
|
|||
segment1 = segment2;
|
||||
}
|
||||
if (path._closed)
|
||||
addCurve(segment2, segments[0]);
|
||||
addCurve(segment2 || segment1, segments[0]);
|
||||
this.curves = curves;
|
||||
this.parts = parts;
|
||||
this.length = length;
|
||||
|
|
|
@ -112,8 +112,8 @@ PathItem.inject(new function() {
|
|||
function collect(paths) {
|
||||
for (var i = 0, l = paths.length; i < l; i++) {
|
||||
var path = paths[i];
|
||||
segments.push.apply(segments, path._segments);
|
||||
curves.push.apply(curves, path.getCurves());
|
||||
Base.push(segments, path._segments);
|
||||
Base.push(curves, path.getCurves());
|
||||
// See if all encountered segments in a path are overlaps, to
|
||||
// be able to separately handle fully overlapping paths.
|
||||
path._overlapsOnly = true;
|
||||
|
@ -248,7 +248,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.
|
||||
* @returns {Item[]} the reoriented paths
|
||||
* @return {Item[]} the reoriented paths
|
||||
*/
|
||||
function reorientPaths(paths, isInside, clockwise) {
|
||||
var length = paths && paths.length;
|
||||
|
@ -748,13 +748,26 @@ PathItem.inject(new function() {
|
|||
// While subtracting, we need to omit this curve if it is
|
||||
// contributing to the second operand and is outside the
|
||||
// first operand.
|
||||
var wind = !(operator.subtract && path2 && (
|
||||
operand === path1 &&
|
||||
path2._getWinding(pt, dir, true).winding ||
|
||||
operand === path2 &&
|
||||
!path1._getWinding(pt, dir, true).winding))
|
||||
? getWinding(pt, curves, dir, true)
|
||||
: { winding: 0, quality: 1 };
|
||||
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);
|
||||
// Check if curve should be omitted.
|
||||
if (operand === path1 && pathWinding.winding ||
|
||||
operand === path2 && !pathWinding.winding) {
|
||||
// Check if quality is not good enough...
|
||||
if (pathWinding.quality < 1) {
|
||||
// ...and if so, skip this point...
|
||||
continue;
|
||||
} else {
|
||||
// ...otherwise, omit this curve.
|
||||
wind = { winding: 0, quality: 1 };
|
||||
}
|
||||
}
|
||||
}
|
||||
wind = wind || getWinding(pt, curves, dir, true);
|
||||
if (wind.quality > winding.quality)
|
||||
winding = wind;
|
||||
break;
|
||||
|
@ -1223,7 +1236,7 @@ PathItem.inject(new function() {
|
|||
clearCurveHandles(clearCurves);
|
||||
// Finally resolve self-intersections through tracePaths()
|
||||
paths = tracePaths(Base.each(paths, function(path) {
|
||||
this.push.apply(this, path._segments);
|
||||
Base.push(this, path._segments);
|
||||
}, []));
|
||||
}
|
||||
// Determine how to return the paths: First try to recycle the
|
||||
|
|
|
@ -53,60 +53,94 @@ var Color = Base.extend(new function() {
|
|||
// Parsers of values for setters, by type and property
|
||||
var componentParsers = {},
|
||||
// Cache and canvas context for color name lookup
|
||||
colorCache = {},
|
||||
namedColors = {
|
||||
// node-canvas appears to return wrong values for 'transparent'.
|
||||
// Fix it by having it pre-cashed here:
|
||||
transparent: [0, 0, 0, 0]
|
||||
},
|
||||
colorCtx;
|
||||
|
||||
// TODO: Implement hsv, etc. CSS parsing!
|
||||
function fromCSS(string) {
|
||||
var match = string.match(/^#(\w{1,2})(\w{1,2})(\w{1,2})$/),
|
||||
var match = string.match(
|
||||
/^#([\da-f]{2})([\da-f]{2})([\da-f]{2})([\da-f]{2})?$/i
|
||||
) || string.match(
|
||||
/^#([\da-f])([\da-f])([\da-f])([\da-f])?$/i
|
||||
),
|
||||
type = 'rgb',
|
||||
components;
|
||||
if (match) {
|
||||
// Hex
|
||||
components = [0, 0, 0];
|
||||
for (var i = 0; i < 3; i++) {
|
||||
// Hex with optional alpha channel:
|
||||
var amount = match[4] ? 4 : 3;
|
||||
components = new Array(amount);
|
||||
for (var i = 0; i < amount; i++) {
|
||||
var value = match[i + 1];
|
||||
components[i] = parseInt(value.length == 1
|
||||
? value + value : value, 16) / 255;
|
||||
}
|
||||
} else if (match = string.match(/^rgba?\((.*)\)$/)) {
|
||||
// RGB / RGBA
|
||||
components = match[1].split(',');
|
||||
for (var i = 0, l = components.length; i < l; i++) {
|
||||
var value = +components[i];
|
||||
components[i] = i < 3 ? value / 255 : value;
|
||||
}
|
||||
} else if (window) {
|
||||
// Named
|
||||
var cached = colorCache[string];
|
||||
if (!cached) {
|
||||
// Use a canvas to draw to with the given name and then retrieve
|
||||
// RGB values from. Build a cache for all the used colors.
|
||||
if (!colorCtx) {
|
||||
colorCtx = CanvasProvider.getContext(1, 1);
|
||||
colorCtx.globalCompositeOperation = 'copy';
|
||||
} else if (match = string.match(/^(rgb|hsl)a?\((.*)\)$/)) {
|
||||
// RGB / RGBA or HSL / HSLA
|
||||
type = match[1];
|
||||
components = match[2].split(/[,\s]+/g);
|
||||
var isHSL = type === 'hsl';
|
||||
for (var i = 0, l = Math.min(components.length, 4); i < l; i++) {
|
||||
var component = components[i];
|
||||
// Use `parseFloat()` instead of `+value` to parse '\d+%' to
|
||||
// float for HSL:
|
||||
var value = parseFloat(component);
|
||||
if (isHSL) {
|
||||
if (i === 0) {
|
||||
// handle 'deg', 'turn', 'rad' 'grad':
|
||||
var unit = component.match(/([a-z]*)$/)[1];
|
||||
value *= ({
|
||||
turn: 360,
|
||||
rad: 180 / Math.PI,
|
||||
grad: 0.9 // 360 / 400
|
||||
}[unit] || 1);
|
||||
} else if (i < 3) {
|
||||
// Percentages to 0..1
|
||||
value /= 100;
|
||||
}
|
||||
} else if (i < 3) {
|
||||
// RGB color values to 0..1
|
||||
value /= 255;
|
||||
}
|
||||
// Set the current fillStyle to transparent, so that it will be
|
||||
// transparent instead of the previously set color in case the
|
||||
// new color can not be interpreted.
|
||||
colorCtx.fillStyle = 'rgba(0,0,0,0)';
|
||||
// Set the fillStyle of the context to the passed name and fill
|
||||
// the canvas with it, then retrieve the data for the drawn
|
||||
// pixel:
|
||||
colorCtx.fillStyle = string;
|
||||
colorCtx.fillRect(0, 0, 1, 1);
|
||||
var data = colorCtx.getImageData(0, 0, 1, 1).data;
|
||||
cached = colorCache[string] = [
|
||||
data[0] / 255,
|
||||
data[1] / 255,
|
||||
data[2] / 255
|
||||
];
|
||||
components[i] = value;
|
||||
}
|
||||
components = cached.slice();
|
||||
} else {
|
||||
// Web-workers can't resolve CSS color names, for now.
|
||||
components = [0, 0, 0];
|
||||
// Named
|
||||
var color = namedColors[string];
|
||||
if (!color) {
|
||||
if (window) {
|
||||
// Use a canvas to draw with the given name, then retrieve
|
||||
// RGB values and build a cache for all the used colors.
|
||||
if (!colorCtx) {
|
||||
colorCtx = CanvasProvider.getContext(1, 1);
|
||||
colorCtx.globalCompositeOperation = 'copy';
|
||||
}
|
||||
// Set the current fillStyle to transparent, so that it will be
|
||||
// transparent instead of the previously set color in case the
|
||||
// new color can not be interpreted.
|
||||
colorCtx.fillStyle = 'rgba(0,0,0,0)';
|
||||
// Set the fillStyle of the context to the passed name and fill
|
||||
// the canvas with it, then retrieve the data for the drawn
|
||||
// pixel:
|
||||
colorCtx.fillStyle = string;
|
||||
colorCtx.fillRect(0, 0, 1, 1);
|
||||
var data = colorCtx.getImageData(0, 0, 1, 1).data;
|
||||
color = namedColors[string] = [
|
||||
data[0] / 255,
|
||||
data[1] / 255,
|
||||
data[2] / 255
|
||||
];
|
||||
} else {
|
||||
// Web-workers can't resolve CSS color names, for now.
|
||||
// TODO: Find a way to make this work there too?
|
||||
color = [0, 0, 0];
|
||||
}
|
||||
}
|
||||
components = color.slice();
|
||||
}
|
||||
return components;
|
||||
return [type, components];
|
||||
}
|
||||
|
||||
// For hsb-rgb conversion, used to lookup the right parameters in the
|
||||
|
@ -237,36 +271,40 @@ var Color = Base.extend(new function() {
|
|||
// hsb and hsl. Handle this here separately, by testing for
|
||||
// overlaps and skipping conversion if the type is /hs[bl]/
|
||||
hasOverlap = /^(hue|saturation)$/.test(name),
|
||||
// Produce value parser function for the given type / propeprty
|
||||
// name combination.
|
||||
parser = componentParsers[type][index] = name === 'gradient'
|
||||
? function(value) {
|
||||
var current = this._components[0];
|
||||
value = Gradient.read(Array.isArray(value) ? value
|
||||
: arguments, 0, { readNull: true });
|
||||
if (current !== value) {
|
||||
if (current)
|
||||
current._removeOwner(this);
|
||||
if (value)
|
||||
value._addOwner(this);
|
||||
// Produce value parser function for the given type / property
|
||||
parser = componentParsers[type][index] = type === 'gradient'
|
||||
? name === 'gradient'
|
||||
// gradient property of gradient color:
|
||||
? function(value) {
|
||||
var current = this._components[0];
|
||||
value = Gradient.read(
|
||||
Array.isArray(value)
|
||||
? value
|
||||
: arguments, 0, { readNull: true }
|
||||
);
|
||||
if (current !== value) {
|
||||
if (current)
|
||||
current._removeOwner(this);
|
||||
if (value)
|
||||
value._addOwner(this);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
: type === 'gradient'
|
||||
? function(/* value */) {
|
||||
// all other (point) properties of gradient color:
|
||||
: function(/* value */) {
|
||||
return Point.read(arguments, 0, {
|
||||
readNull: name === 'highlight',
|
||||
clone: true
|
||||
});
|
||||
}
|
||||
: function(value) {
|
||||
// NOTE: We don't clamp values here, they're only
|
||||
// clamped once the actual CSS values are produced.
|
||||
// Gotta love the fact that isNaN(null) is false,
|
||||
// while isNaN(undefined) is true.
|
||||
return value == null || isNaN(value) ? 0 : value;
|
||||
};
|
||||
|
||||
// Normal number component properties:
|
||||
: function(value) {
|
||||
// NOTE: We don't clamp values here, they're only
|
||||
// clamped once the actual CSS values are produced.
|
||||
// Gotta love the fact that isNaN(null) is false,
|
||||
// while isNaN(undefined) is true.
|
||||
return value == null || isNaN(value) ? 0 : +value;
|
||||
};
|
||||
this['get' + part] = function() {
|
||||
return this._type === type
|
||||
|| hasOverlap && /^hs[bl]$/.test(this._type)
|
||||
|
@ -411,6 +449,25 @@ var Color = Base.extend(new function() {
|
|||
* }
|
||||
* });
|
||||
*/
|
||||
/**
|
||||
* Creates a Color object from a CSS string. All common CSS color string
|
||||
* formats are supported:
|
||||
* - Named colors (e.g. `'red'`, `'fuchsia'`, …)
|
||||
* - Hex strings (`'#ffff00'`, `'#ff0'`, …)
|
||||
* - RGB strings (`'rgb(255, 128, 0)'`, `'rgba(255, 128, 0, 0.5)'`, …)
|
||||
* - HSL strings (`'hsl(180deg, 20%, 50%)'`,
|
||||
* `'hsla(3.14rad, 20%, 50%, 0.5)'`, …)
|
||||
*
|
||||
* @name Color#initialize
|
||||
* @param {String} color the color's CSS string representation
|
||||
*
|
||||
* @example {@paperscript}
|
||||
* var circle = new Path.Circle({
|
||||
* center: [80, 50],
|
||||
* radius: 30,
|
||||
* fillColor: new Color('rgba(255, 255, 0, 0.5)')
|
||||
* });
|
||||
*/
|
||||
/**
|
||||
* Creates a gradient Color object.
|
||||
*
|
||||
|
@ -544,8 +601,9 @@ var Color = Base.extend(new function() {
|
|||
if (values.length > length)
|
||||
values = Base.slice(values, 0, length);
|
||||
} else if (argType === 'string') {
|
||||
type = 'rgb';
|
||||
components = fromCSS(arg);
|
||||
var converted = fromCSS(arg);
|
||||
type = converted[0];
|
||||
components = converted[1];
|
||||
if (components.length === 4) {
|
||||
alpha = components[3];
|
||||
components.length--;
|
||||
|
|
|
@ -174,8 +174,10 @@ var Style = Base.extend(new function() {
|
|||
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)
|
||||
if (old && old._owner !== undefined) {
|
||||
old._owner = undefined;
|
||||
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
|
||||
|
@ -305,6 +307,16 @@ var Style = Base.extend(new function() {
|
|||
|| false;
|
||||
},
|
||||
|
||||
_dispose: function() {
|
||||
var color;
|
||||
color = this.getFillColor();
|
||||
if (color) color._canvasStyle = null;
|
||||
color = this.getStrokeColor();
|
||||
if (color) color._canvasStyle = null;
|
||||
color = this.getShadowColor();
|
||||
if (color) color._canvasStyle = null;
|
||||
},
|
||||
|
||||
// DOCS: Style#hasFill()
|
||||
hasFill: function() {
|
||||
var color = this.getFillColor();
|
||||
|
|
|
@ -222,9 +222,8 @@ new function() {
|
|||
// half of its size. We also need to take the raster's matrix
|
||||
// into account, which will be defined by the time the load
|
||||
// event is called.
|
||||
var center = this._matrix._transformPoint(
|
||||
getPoint(node).add(size.divide(2)));
|
||||
this.translate(center);
|
||||
var center = getPoint(node).add(size.divide(2));
|
||||
this._matrix.append(new Matrix().translate(center));
|
||||
});
|
||||
return raster;
|
||||
},
|
||||
|
@ -300,48 +299,10 @@ new function() {
|
|||
// TODO: Support for these is missing in Paper.js right now
|
||||
// rotate: character rotation
|
||||
// lengthAdjust:
|
||||
|
||||
// Scratch-specific: Do not use x/y attributes because they break multiline usage.
|
||||
var fontSize = parseFloat(node.getAttribute("font-size"));
|
||||
var alignmentBaseline = node.getAttribute("alignment-baseline");
|
||||
if (node.childElementCount === 0) {
|
||||
var text = new PointText();
|
||||
text.setContent(node.textContent.trim() || '');
|
||||
// Scratch-specific: Scratch2 SVGs are offset by 1 leading vertically.
|
||||
// Scratch3 SVGs use <tspan> method for all text (below)
|
||||
text.translate(0, text._style.getLeading());
|
||||
if (!isNaN(fontSize)) text.setFontSize(fontSize);
|
||||
return text;
|
||||
} else {
|
||||
// Scratch3 SVGs always use <tspan>'s for multiline string support.
|
||||
// Does not support x/y attribute or tspan positioning beyond left justified.
|
||||
var lines = [];
|
||||
var spacing = 1.2;
|
||||
for (var i = 0; i < node.childNodes.length; i++) {
|
||||
var child = node.childNodes[i];
|
||||
lines.push(child.textContent);
|
||||
var dyString = child.getAttribute('dy');
|
||||
if (dyString) {
|
||||
var dy = parseFloat(dyString);
|
||||
if (!isNaN(dy)) {
|
||||
if (dyString.endsWith('em')) {
|
||||
spacing = dy;
|
||||
} else if (dyString.endsWith('px') && !isNaN(fontSize)) {
|
||||
spacing = dy / fontSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var text = new PointText();
|
||||
if (!isNaN(fontSize)) text.setFontSize(fontSize);
|
||||
text.setLeading(text.fontSize * spacing);
|
||||
if (alignmentBaseline === 'text-before-edge') {
|
||||
text.setContent(' '); // No content results in 0 height
|
||||
text.translate(0, text.bounds.height);
|
||||
}
|
||||
text.setContent(lines.join('\n'));
|
||||
return text;
|
||||
}
|
||||
var text = new PointText(getPoint(node).add(
|
||||
getPoint(node, 'dx', 'dy')));
|
||||
text.setContent(node.textContent.trim() || '');
|
||||
return text;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -375,12 +336,10 @@ new function() {
|
|||
new Matrix(v[0], v[1], v[2], v[3], v[4], v[5]));
|
||||
break;
|
||||
case 'rotate':
|
||||
var v2 = (typeof v[1] === 'number' && typeof v[2] !== 'number') ? 0 : v[2];
|
||||
matrix.rotate(v[0], v[1], v2);
|
||||
matrix.rotate(v[0], v[1] || 0, v[2] || 0);
|
||||
break;
|
||||
case 'translate':
|
||||
var v1 = (typeof v[1] === 'number') ? v[1] : 0;
|
||||
matrix.translate(v[0], v1);
|
||||
matrix.translate(v[0], v[1] || 0);
|
||||
break;
|
||||
case 'scale':
|
||||
matrix.scale(v);
|
||||
|
@ -537,7 +496,7 @@ new function() {
|
|||
// First see if the given attribute is defined.
|
||||
var attr = node.attributes[name],
|
||||
value = attr && attr.value;
|
||||
if (!value) {
|
||||
if (!value && node.style) {
|
||||
// Fallback to using styles. See if there is a style, either set
|
||||
// directly on the object or applied to it through CSS rules.
|
||||
// We also need to filter out inheritance from their parents.
|
||||
|
@ -561,25 +520,23 @@ new function() {
|
|||
* @param {Item} item the item to apply the style and attributes to
|
||||
*/
|
||||
function applyAttributes(item, node, isRoot) {
|
||||
if (node.style) {
|
||||
// SVG attributes can be set both as styles and direct node
|
||||
// attributes, so we need to handle both.
|
||||
var parent = node.parentNode,
|
||||
styles = {
|
||||
node: DomElement.getStyles(node) || {},
|
||||
// Do not check for inheritance if this is root, to make the
|
||||
// default SVG settings stick. Also detect defs parents, of
|
||||
// which children need to explicitly inherit their styles.
|
||||
parent: !isRoot && !/^defs$/i.test(parent.tagName)
|
||||
&& DomElement.getStyles(parent) || {}
|
||||
};
|
||||
Base.each(attributes, function(apply, name) {
|
||||
var value = getAttribute(node, name, styles);
|
||||
// 'clip-path' attribute returns a new item, support it here:
|
||||
item = value !== undefined
|
||||
&& apply(item, value, name, node, styles) || item;
|
||||
});
|
||||
}
|
||||
// SVG attributes can be set both as styles and direct node
|
||||
// attributes, so we need to handle both.
|
||||
var parent = node.parentNode,
|
||||
styles = {
|
||||
node: DomElement.getStyles(node) || {},
|
||||
// Do not check for inheritance if this is root, to make the
|
||||
// default SVG settings stick. Also detect defs parents, of
|
||||
// which children need to explicitly inherit their styles.
|
||||
parent: !isRoot && !/^defs$/i.test(parent.tagName)
|
||||
&& DomElement.getStyles(parent) || {}
|
||||
};
|
||||
Base.each(attributes, function(apply, name) {
|
||||
var value = getAttribute(node, name, styles);
|
||||
// 'clip-path' attribute returns a new item, support it here:
|
||||
item = value !== undefined
|
||||
&& apply(item, value, name, node, styles) || item;
|
||||
});
|
||||
return item;
|
||||
}
|
||||
|
||||
|
|
|
@ -309,7 +309,9 @@ var Tool = PaperScopeItem.extend(/** @lends Tool# */{
|
|||
// so there's always a delta.
|
||||
toolPoint = move ? tool._point : (tool._downPoint || pt);
|
||||
if (move) {
|
||||
if (tool._moveCount && pt.equals(toolPoint)) {
|
||||
// After first move event was emitted, tool._moveCount = 0, so
|
||||
// we need to include 0 in this check.
|
||||
if (tool._moveCount >= 0 && pt.equals(toolPoint)) {
|
||||
return false;
|
||||
}
|
||||
if (toolPoint && (minDistance != null || maxDistance != null)) {
|
||||
|
|
|
@ -525,11 +525,9 @@ var View = Base.extend(Emitter, /** @lends View# */{
|
|||
* @see #getScaling()
|
||||
*/
|
||||
getZoom: function() {
|
||||
var decomposed = this._decompose(),
|
||||
scaling = decomposed && decomposed.scaling;
|
||||
// Use average since it can be non-uniform, and return 0 when it can't
|
||||
// be decomposed.
|
||||
return scaling ? (scaling.x + scaling.y) / 2 : 0;
|
||||
var scaling = this._decompose().scaling;
|
||||
// Use average since it can be non-uniform.
|
||||
return (scaling.x + scaling.y) / 2;
|
||||
},
|
||||
|
||||
setZoom: function(zoom) {
|
||||
|
@ -545,8 +543,7 @@ var View = Base.extend(Emitter, /** @lends View# */{
|
|||
* @type Number
|
||||
*/
|
||||
getRotation: function() {
|
||||
var decomposed = this._decompose();
|
||||
return decomposed && decomposed.rotation;
|
||||
return this._decompose().rotation;
|
||||
},
|
||||
|
||||
setRotation: function(rotation) {
|
||||
|
@ -565,11 +562,8 @@ var View = Base.extend(Emitter, /** @lends View# */{
|
|||
* @see #getZoom()
|
||||
*/
|
||||
getScaling: function() {
|
||||
var decomposed = this._decompose(),
|
||||
scaling = decomposed && decomposed.scaling;
|
||||
return scaling
|
||||
? new LinkedPoint(scaling.x, scaling.y, this, 'setScaling')
|
||||
: undefined;
|
||||
var scaling = this._decompose().scaling;
|
||||
return new LinkedPoint(scaling.x, scaling.y, this, 'setScaling');
|
||||
},
|
||||
|
||||
setScaling: function(/* scaling */) {
|
||||
|
@ -1271,11 +1265,12 @@ new function() { // Injection scope for event handling on the browser
|
|||
point, prevPoint)
|
||||
// Next handle the hit-item, if it's different from the drag-item
|
||||
// and not a descendant of it (in which case it would already have
|
||||
// received an event in the call above).
|
||||
// received an event in the call above). Translate mousedrag to
|
||||
// mousemove, since drag is handled above.
|
||||
|| hitItem && hitItem !== dragItem
|
||||
&& !hitItem.isDescendant(dragItem)
|
||||
&& emitMouseEvent(hitItem, null, type, event, point, prevPoint,
|
||||
dragItem)
|
||||
&& emitMouseEvent(hitItem, null, type === 'mousedrag' ?
|
||||
'mousemove' : type, event, point, prevPoint, dragItem)
|
||||
// Lastly handle the mouse events on the view, if we're still here.
|
||||
|| emitMouseEvent(view, dragItem || hitItem || view, type, event,
|
||||
point, prevPoint));
|
||||
|
@ -1440,8 +1435,16 @@ new function() { // Injection scope for event handling on the browser
|
|||
// which can call `preventDefault()` explicitly or return `false`.
|
||||
// - If this is a unhandled mousedown event, but the view or tools
|
||||
// respond to mouseup.
|
||||
if (called && !mouse.move || mouse.down && responds('mouseup'))
|
||||
//
|
||||
// Some events are not cancelable anyway (like during a scroll
|
||||
// inertia on mobile) so trying to prevent default in those case
|
||||
// would result in no effect and an error.
|
||||
if (
|
||||
event.cancelable !== false
|
||||
&& (called && !mouse.move || mouse.down && responds('mouseup'))
|
||||
) {
|
||||
event.preventDefault();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -1490,7 +1493,20 @@ new function() { // Injection scope for event handling on the browser
|
|||
* Loops through all views and sets the focus on the first
|
||||
* active one.
|
||||
*/
|
||||
updateFocus: updateFocus
|
||||
updateFocus: updateFocus,
|
||||
|
||||
/**
|
||||
* Clear all events handling state informations. Made for testing
|
||||
* purpose, to have a way to start with a fresh state before each
|
||||
* test.
|
||||
* @private
|
||||
*/
|
||||
_resetState: function() {
|
||||
dragging = mouseDown = called = wasInView = false;
|
||||
prevFocus = tempFocus = overView = downPoint = lastPoint =
|
||||
downItem = overItem = dragItem = clickItem = clickTime =
|
||||
dblClick = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
@ -34,6 +34,16 @@ if (isNode) {
|
|||
});
|
||||
}
|
||||
|
||||
// Some native javascript classes have name collisions with Paper.js classes.
|
||||
// If they have not already been stored in src/load.js, we dot it now.
|
||||
if (!isNode && typeof NativeClasses === 'undefined')
|
||||
{
|
||||
NativeClasses = {
|
||||
Event: Event,
|
||||
MouseEvent: MouseEvent
|
||||
};
|
||||
}
|
||||
|
||||
// The unit-tests expect the paper classes to be global.
|
||||
paper.install(scope);
|
||||
|
||||
|
@ -48,12 +58,16 @@ console.error = function() {
|
|||
errorHandler.apply(this, arguments);
|
||||
};
|
||||
|
||||
var currentProject;
|
||||
|
||||
QUnit.done(function(details) {
|
||||
console.error = errorHandler;
|
||||
// Clear all event listeners after final test.
|
||||
if (currentProject) {
|
||||
currentProject.remove();
|
||||
}
|
||||
});
|
||||
|
||||
var currentProject;
|
||||
|
||||
// NOTE: In order to "export" all methods into the shared Prepro.js scope when
|
||||
// using node-qunit, we need to define global functions as:
|
||||
// `var name = function() {}`. `function name() {}` does not work!
|
||||
|
@ -61,9 +75,16 @@ var test = function(testName, expected) {
|
|||
return QUnit.test(testName, function(assert) {
|
||||
// Since tests can be asynchronous, remove the old project before
|
||||
// running the next test.
|
||||
if (currentProject)
|
||||
if (currentProject) {
|
||||
currentProject.remove();
|
||||
currentProject = new Project();
|
||||
// This is needed for interactions tests, to make sure that test is
|
||||
// run with a fresh state.
|
||||
View._resetState();
|
||||
}
|
||||
|
||||
// Instantiate project with 100x100 pixels canvas instead of default
|
||||
// 1x1 to make interactions tests simpler by working with integers.
|
||||
currentProject = new Project(new Size(100, 100));
|
||||
expected(assert);
|
||||
});
|
||||
};
|
||||
|
@ -550,3 +571,63 @@ var compareSVG = function(done, actual, expected, message, options) {
|
|||
compare();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Interactions helpers
|
||||
//
|
||||
var MouseEventPolyfill = function(type, params) {
|
||||
var mouseEvent = document.createEvent('MouseEvent');
|
||||
mouseEvent.initMouseEvent(
|
||||
type,
|
||||
params.bubbles,
|
||||
params.cancelable,
|
||||
window,
|
||||
0,
|
||||
params.screenX,
|
||||
params.screenY,
|
||||
params.clientX,
|
||||
params.clientY,
|
||||
params.ctrlKey,
|
||||
params.altKey,
|
||||
params.shiftKey,
|
||||
params.metaKey,
|
||||
params.button,
|
||||
params.relatedTarget
|
||||
);
|
||||
return mouseEvent;
|
||||
};
|
||||
MouseEventPolyfill.prototype = typeof NativeClasses !== 'undefined'
|
||||
&& NativeClasses.Event.prototype || Event.prototype;
|
||||
|
||||
var triggerMouseEvent = function(type, point, target) {
|
||||
// Depending on event type, events have to be triggered on different
|
||||
// elements due to the event handling implementation (see `viewEvents`
|
||||
// and `docEvents` in View.js). And we cannot rely on the fact that event
|
||||
// will bubble from canvas to document, since the canvas used in tests is
|
||||
// not inserted in DOM.
|
||||
target = target || (type === 'mousedown' ? view._element : document);
|
||||
|
||||
// If `gulp load` was run, there is a name collision between paper Event /
|
||||
// MouseEvent and native javascript classes. In this case, we need to use
|
||||
// native classes stored in global NativeClasses object instead.
|
||||
var constructor = typeof NativeClasses !== 'undefined'
|
||||
&& NativeClasses.MouseEvent || MouseEvent;
|
||||
|
||||
// MouseEvent class does not exist in PhantomJS, so in that case, we need to
|
||||
// use a polyfill method.
|
||||
if (typeof constructor !== 'function') {
|
||||
constructor = MouseEventPolyfill;
|
||||
}
|
||||
|
||||
var event = new constructor(type, {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
composed: true,
|
||||
clientX: point.x,
|
||||
clientY: point.y,
|
||||
screenX: point.x,
|
||||
screenY: point.y
|
||||
});
|
||||
target.dispatchEvent(event);
|
||||
};
|
||||
|
|
|
@ -60,23 +60,39 @@ test('Creating Colors', function() {
|
|||
equals(new Color('red'), new Color(1, 0, 0),
|
||||
'Color from name (red)');
|
||||
|
||||
equals(new Color('transparent'), new Color(0, 0, 0, 0),
|
||||
'Color from name (transparent)');
|
||||
|
||||
equals(new Color('#ff0000'), new Color(1, 0, 0),
|
||||
'Color from hex code');
|
||||
'Color from hex string');
|
||||
|
||||
equals(new Color('#FF3300'), new Color(1, 0.2, 0),
|
||||
'Color from uppercase hex string');
|
||||
|
||||
equals(new Color('#f009'), new Color(1, 0, 0, .6),
|
||||
'Color from 4 characters hex code with alpha');
|
||||
|
||||
equals(new Color('#ff000099'), new Color(1, 0, 0, .6),
|
||||
'Color from 8 characters hex code with alpha');
|
||||
|
||||
equals(new Color('rgb(255, 0, 0)'), new Color(1, 0, 0),
|
||||
'Color from RGB code');
|
||||
'Color from rgb() string');
|
||||
|
||||
equals(new Color('rgba(255, 0, 0, 0.5)'), new Color(1, 0, 0, 0.5),
|
||||
'Color from RGBA code');
|
||||
'Color from rgba() string');
|
||||
|
||||
equals(new Color('hsl(180deg, 20%, 40%)'),
|
||||
new Color({ hue: 180, saturation: 0.2, lightness: 0.4 }),
|
||||
'Color from hsl() string');
|
||||
|
||||
equals(new Color({ red: 1, green: 0, blue: 1}),
|
||||
new Color(1, 0, 1), 'Color from rgb object literal');
|
||||
new Color(1, 0, 1), 'Color from RGB object literal');
|
||||
|
||||
equals(new Color({ gray: 0.2 }),
|
||||
new Color(0.2), 'Color from gray object literal');
|
||||
|
||||
equals(new Color({ hue: 0, saturation: 1, brightness: 1}),
|
||||
new Color(1, 0, 0).convert('hsb'), 'Color from hsb object literal');
|
||||
new Color(1, 0, 0).convert('hsb'), 'Color from HSB object literal');
|
||||
|
||||
equals(new Color([1, 0, 0]), new Color(1, 0, 0),
|
||||
'RGB Color from array');
|
||||
|
|
|
@ -347,3 +347,25 @@ test('Curve#divideAt(offset)', function() {
|
|||
return new Curve(point1, point2).divideAtTime(0.5).point1;
|
||||
}, middle);
|
||||
});
|
||||
|
||||
test('Curve#getTimesWithTangent()', function() {
|
||||
var curve = new Curve([0, 0], [100, 0], [0, -100], [200, 200]);
|
||||
equals(curve.getTimesWithTangent(), [], 'should return empty array when called without argument');
|
||||
equals(curve.getTimesWithTangent([1, 0]), [0], 'should return tangent at start');
|
||||
equals(curve.getTimesWithTangent([-1, 0]), [0], 'should return the same when called with opposite direction vector');
|
||||
equals(curve.getTimesWithTangent([0, 1]), [1], 'should return tangent at end');
|
||||
equals(curve.getTimesWithTangent([1, 1]), [0.5], 'should return tangent at middle');
|
||||
equals(curve.getTimesWithTangent([1, -1]), [], 'should return empty array when there is no tangent');
|
||||
|
||||
equals(
|
||||
new Curve([0, 0], [100, 0], [500, -500], [-500, -500]).getTimesWithTangent([1, 0]).length,
|
||||
2,
|
||||
'should return 2 values for specific self-intersecting path case'
|
||||
);
|
||||
|
||||
equals(
|
||||
new Curve([0, 0], [100, 0], [0, -100], [0, -100]).getTimesWithTangent([1, 0]).length,
|
||||
2,
|
||||
'should return 2 values for specific parabollic path case'
|
||||
);
|
||||
});
|
||||
|
|
347
test/tests/Interactions.js
Normal file
347
test/tests/Interactions.js
Normal file
|
@ -0,0 +1,347 @@
|
|||
/*
|
||||
* Paper.js - The Swiss Army Knife of Vector Graphics Scripting.
|
||||
* http://paperjs.org/
|
||||
*
|
||||
* Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey
|
||||
* http://scratchdisk.com/ & http://jonathanpuckey.com/
|
||||
*
|
||||
* Distributed under the MIT license. See LICENSE file for details.
|
||||
*
|
||||
* All rights reserved.
|
||||
*/
|
||||
|
||||
/**
|
||||
* These tests are focused on user interactions.
|
||||
* They trigger events and check callbacks.
|
||||
* Warning: when running tests locally from `gulp test:browser` command, don't
|
||||
* move your mouse over the window because that could perturbate tests
|
||||
* execution.
|
||||
*/
|
||||
QUnit.module('Interactions');
|
||||
|
||||
//
|
||||
// Mouse
|
||||
//
|
||||
test('Item#onMouseDown()', function(assert) {
|
||||
var done = assert.async();
|
||||
var item = new Path.Rectangle(new Point(0, 0), new Size(10));
|
||||
item.fillColor = 'red';
|
||||
var point = new Point(5, 5);
|
||||
item.onMouseDown = function(event) {
|
||||
equals(event.type, 'mousedown');
|
||||
equals(event.point, point);
|
||||
equals(event.target, item);
|
||||
equals(event.currentTarget, item);
|
||||
equals(event.delta, null);
|
||||
done();
|
||||
};
|
||||
triggerMouseEvent('mousedown', point);
|
||||
});
|
||||
|
||||
test('Item#onMouseDown() with stroked item', function(assert) {
|
||||
var done = assert.async();
|
||||
var item = new Path.Rectangle(new Point(0, 0), new Size(10));
|
||||
item.strokeColor = 'red';
|
||||
var point = new Point(0, 0);
|
||||
item.onMouseDown = function(event) {
|
||||
equals(event.type, 'mousedown');
|
||||
equals(event.point, point);
|
||||
equals(event.target, item);
|
||||
equals(event.currentTarget, item);
|
||||
equals(event.delta, null);
|
||||
done();
|
||||
};
|
||||
triggerMouseEvent('mousedown', point);
|
||||
});
|
||||
|
||||
test('Item#onMouseDown() is not triggered when item is not filled', function(assert) {
|
||||
var item = new Path.Rectangle(new Point(0, 0), new Size(10));
|
||||
item.onMouseDown = function(event) {
|
||||
throw 'this should not be called';
|
||||
};
|
||||
triggerMouseEvent('mousedown', new Point(5, 5));
|
||||
expect(0);
|
||||
});
|
||||
|
||||
test('Item#onMouseDown() is not triggered when item is not visible', function(assert) {
|
||||
var item = new Path.Rectangle(new Point(0, 0), new Size(10));
|
||||
item.fillColor = 'red';
|
||||
item.visible = false;
|
||||
item.onMouseDown = function(event) {
|
||||
throw 'this should not be called';
|
||||
};
|
||||
triggerMouseEvent('mousedown', new Point(5, 5));
|
||||
expect(0);
|
||||
});
|
||||
|
||||
test('Item#onMouseDown() is not triggered when item is locked', function(assert) {
|
||||
var item = new Path.Rectangle(new Point(0, 0), new Size(10));
|
||||
item.fillColor = 'red';
|
||||
item.locked = true;
|
||||
item.onMouseDown = function(event) {
|
||||
throw 'this should not be called';
|
||||
};
|
||||
triggerMouseEvent('mousedown', new Point(5, 5));
|
||||
expect(0);
|
||||
});
|
||||
|
||||
test('Item#onMouseDown() is not triggered when another item is in front', function(assert) {
|
||||
var item = new Path.Rectangle(new Point(0, 0), new Size(10));
|
||||
item.fillColor = 'red';
|
||||
var item2 = item.clone();
|
||||
item.onMouseDown = function(event) {
|
||||
throw 'this should not be called';
|
||||
};
|
||||
triggerMouseEvent('mousedown', new Point(5, 5));
|
||||
expect(0);
|
||||
});
|
||||
|
||||
test('Item#onMouseDown() is not triggered if event target is document', function(assert) {
|
||||
var item = new Path.Rectangle(new Point(0, 0), new Size(10));
|
||||
item.fillColor = 'red';
|
||||
item.onMouseDown = function(event) {
|
||||
throw 'this should not be called';
|
||||
};
|
||||
triggerMouseEvent('mousedown', new Point(5, 5), document);
|
||||
expect(0);
|
||||
});
|
||||
|
||||
test('Item#onMouseMove()', function(assert) {
|
||||
var done = assert.async();
|
||||
var item = new Path.Rectangle(new Point(0, 0), new Size(10));
|
||||
item.fillColor = 'red';
|
||||
var point = new Point(5, 5);
|
||||
item.onMouseMove = function(event) {
|
||||
equals(event.type, 'mousemove');
|
||||
equals(event.point, point);
|
||||
equals(event.target, item);
|
||||
equals(event.currentTarget, item);
|
||||
equals(event.delta, null);
|
||||
done();
|
||||
};
|
||||
triggerMouseEvent('mousemove', point);
|
||||
});
|
||||
|
||||
test('Item#onMouseMove() is not re-triggered if point is the same', function(assert) {
|
||||
var item = new Path.Rectangle(new Point(0, 0), new Size(10));
|
||||
item.fillColor = 'red';
|
||||
var point = new Point(5, 5);
|
||||
var counter = 0;
|
||||
item.onMouseMove = function(event) {
|
||||
equals(true, true);
|
||||
};
|
||||
triggerMouseEvent('mousemove', point);
|
||||
triggerMouseEvent('mousemove', point);
|
||||
expect(1);
|
||||
});
|
||||
|
||||
test('Item#onMouseUp()', function(assert) {
|
||||
var done = assert.async();
|
||||
var item = new Path.Rectangle(new Point(0, 0), new Size(10));
|
||||
item.fillColor = 'red';
|
||||
var point = new Point(5, 5);
|
||||
item.onMouseUp = function(event) {
|
||||
equals(event.type, 'mouseup');
|
||||
equals(event.point, point);
|
||||
equals(event.target, item);
|
||||
equals(event.currentTarget, item);
|
||||
equals(event.delta, new Point(0, 0));
|
||||
done();
|
||||
};
|
||||
triggerMouseEvent('mousedown', point);
|
||||
triggerMouseEvent('mouseup', point);
|
||||
});
|
||||
|
||||
test('Item#onMouseUp() is only triggered after mouse down', function(assert) {
|
||||
var item = new Path.Rectangle(new Point(0, 0), new Size(10));
|
||||
item.fillColor = 'red';
|
||||
item.onMouseUp = function(event) {
|
||||
throw 'this should not be called';
|
||||
};
|
||||
triggerMouseEvent('mouseup', new Point(5, 5));
|
||||
expect(0);
|
||||
});
|
||||
|
||||
test('Item#onClick()', function(assert) {
|
||||
var done = assert.async();
|
||||
var item = new Path.Rectangle(new Point(0, 0), new Size(10));
|
||||
item.fillColor = 'red';
|
||||
var point = new Point(5, 5);
|
||||
item.onClick = function(event) {
|
||||
equals(event.type, 'click');
|
||||
equals(event.point, point);
|
||||
equals(event.target, item);
|
||||
equals(event.currentTarget, item);
|
||||
equals(event.delta, new Point(0, 0));
|
||||
done();
|
||||
};
|
||||
triggerMouseEvent('mousedown', point);
|
||||
triggerMouseEvent('mouseup', point);
|
||||
});
|
||||
|
||||
test('Item#onClick() is not triggered if up point is not on item', function(assert) {
|
||||
var item = new Path.Rectangle(new Point(0, 0), new Size(10));
|
||||
item.fillColor = 'red';
|
||||
item.onClick = function(event) {
|
||||
throw 'this should not be called';
|
||||
};
|
||||
triggerMouseEvent('mousedown', new Point(5, 5));
|
||||
triggerMouseEvent('mouseup', new Point(15, 15));
|
||||
expect(0);
|
||||
});
|
||||
|
||||
test('Item#onClick() is not triggered if down point is not on item', function(assert) {
|
||||
var item = new Path.Rectangle(new Point(0, 0), new Size(10));
|
||||
item.fillColor = 'red';
|
||||
item.onClick = function(event) {
|
||||
throw 'this should not be called';
|
||||
};
|
||||
triggerMouseEvent('mousedown', new Point(15, 15));
|
||||
triggerMouseEvent('mouseup', new Point(5, 5));
|
||||
expect(0);
|
||||
});
|
||||
|
||||
test('Item#onDoubleClick()', function(assert) {
|
||||
var done = assert.async();
|
||||
var item = new Path.Rectangle(new Point(0, 0), new Size(10));
|
||||
item.fillColor = 'red';
|
||||
var point = new Point(5, 5);
|
||||
item.onDoubleClick = function(event) {
|
||||
equals(event.type, 'doubleclick');
|
||||
equals(event.point, point);
|
||||
equals(event.target, item);
|
||||
equals(event.currentTarget, item);
|
||||
equals(event.delta, new Point(0, 0));
|
||||
done();
|
||||
};
|
||||
triggerMouseEvent('mousedown', point);
|
||||
triggerMouseEvent('mouseup', point);
|
||||
triggerMouseEvent('mousedown', point);
|
||||
triggerMouseEvent('mouseup', point);
|
||||
});
|
||||
|
||||
test('Item#onDoubleClick() is not triggered if both clicks are not on same item', function(assert) {
|
||||
var item = new Path.Rectangle(new Point(0, 0), new Size(10));
|
||||
item.fillColor = 'red';
|
||||
var item2 = item.clone().translate(5);
|
||||
item.onDoubleClick = function(event) {
|
||||
throw 'this should not be called';
|
||||
};
|
||||
triggerMouseEvent('mousedown', new Point(5, 5));
|
||||
triggerMouseEvent('mouseup', new Point(5, 5));
|
||||
triggerMouseEvent('mousedown', new Point(6, 6));
|
||||
triggerMouseEvent('mouseup', new Point(6, 6));
|
||||
expect(0);
|
||||
});
|
||||
|
||||
test('Item#onDoubleClick() is not triggered if time between both clicks is too long', function(assert) {
|
||||
var done = assert.async();
|
||||
var item = new Path.Rectangle(new Point(0, 0), new Size(10));
|
||||
item.fillColor = 'red';
|
||||
var point = new Point(5, 5);
|
||||
item.onDoubleClick = function(event) {
|
||||
throw 'this should not be called';
|
||||
};
|
||||
triggerMouseEvent('mousedown', point);
|
||||
triggerMouseEvent('mouseup', point);
|
||||
setTimeout(function() {
|
||||
triggerMouseEvent('mousedown', point);
|
||||
triggerMouseEvent('mouseup', point);
|
||||
done();
|
||||
}, 301);
|
||||
expect(0);
|
||||
});
|
||||
|
||||
test('Item#onMouseEnter()', function(assert) {
|
||||
var done = assert.async();
|
||||
var item = new Path.Rectangle(new Point(0, 0), new Size(10));
|
||||
item.fillColor = 'red';
|
||||
var point = new Point(5, 5);
|
||||
item.onMouseEnter = function(event) {
|
||||
equals(event.type, 'mouseenter');
|
||||
equals(event.point, point);
|
||||
equals(event.target, item);
|
||||
equals(event.currentTarget, item);
|
||||
equals(event.delta, null);
|
||||
done();
|
||||
};
|
||||
triggerMouseEvent('mousemove', point);
|
||||
});
|
||||
|
||||
test('Item#onMouseEnter() is only re-triggered after mouse leave', function(assert) {
|
||||
var item = new Path.Rectangle(new Point(0, 0), new Size(10));
|
||||
item.fillColor = 'red';
|
||||
item.onMouseEnter = function(event) {
|
||||
equals(true, true);
|
||||
};
|
||||
// enter
|
||||
triggerMouseEvent('mousemove', new Point(5, 5));
|
||||
triggerMouseEvent('mousemove', new Point(6, 6));
|
||||
triggerMouseEvent('mousemove', new Point(7, 7));
|
||||
// leave
|
||||
triggerMouseEvent('mousemove', new Point(11, 11));
|
||||
// re-enter
|
||||
triggerMouseEvent('mousemove', new Point(10, 10));
|
||||
expect(2);
|
||||
});
|
||||
|
||||
test('Item#onMouseLeave()', function(assert) {
|
||||
var done = assert.async();
|
||||
var item = new Path.Rectangle(new Point(0, 0), new Size(10));
|
||||
item.fillColor = 'red';
|
||||
var point1 = new Point(5, 5);
|
||||
var point2 = new Point(15, 15);
|
||||
item.onMouseLeave = function(event) {
|
||||
equals(event.type, 'mouseleave');
|
||||
equals(event.point, point2);
|
||||
equals(event.target, item);
|
||||
equals(event.currentTarget, item);
|
||||
equals(event.delta, null);
|
||||
done();
|
||||
};
|
||||
triggerMouseEvent('mousemove', point1);
|
||||
triggerMouseEvent('mousemove', point2);
|
||||
});
|
||||
|
||||
test('Item#onMouseDrag()', function(assert) {
|
||||
var done = assert.async();
|
||||
var item = new Path.Rectangle(new Point(0, 0), new Size(10));
|
||||
item.fillColor = 'red';
|
||||
var point1 = new Point(5, 5);
|
||||
var point2 = new Point(15, 15);
|
||||
item.onMouseDrag = function(event) {
|
||||
equals(event.type, 'mousedrag');
|
||||
equals(event.point, point2);
|
||||
equals(event.target, item);
|
||||
equals(event.currentTarget, item);
|
||||
equals(event.delta, new Point(10, 10));
|
||||
done();
|
||||
};
|
||||
triggerMouseEvent('mousedown', point1);
|
||||
triggerMouseEvent('mousemove', point2);
|
||||
});
|
||||
|
||||
test('Item#onMouseDrag() is not triggered after mouse up', function(assert) {
|
||||
var item = new Path.Rectangle(new Point(0, 0), new Size(10));
|
||||
item.fillColor = 'red';
|
||||
item.onMouseDrag = function(event) {
|
||||
equals(true, true);
|
||||
};
|
||||
triggerMouseEvent('mousedown', new Point(5, 5));
|
||||
triggerMouseEvent('mousemove', new Point(6, 6));
|
||||
triggerMouseEvent('mouseup', new Point(7, 7));
|
||||
triggerMouseEvent('mousemove', new Point(8, 8));
|
||||
expect(1);
|
||||
});
|
||||
|
||||
test('Item#onMouseDrag() is not triggered if mouse down was on another item', function(assert) {
|
||||
var item = new Path.Rectangle(new Point(0, 0), new Size(10));
|
||||
item.fillColor = 'red';
|
||||
var item2 = item.clone().translate(10);
|
||||
item2.onMouseDrag = function(event) {
|
||||
throw 'this should not be called';
|
||||
};
|
||||
triggerMouseEvent('mousedown', new Point(5, 5));
|
||||
triggerMouseEvent('mousemove', new Point(11, 11));
|
||||
expect(0);
|
||||
});
|
|
@ -929,3 +929,19 @@ test('Item#scaling, #rotation', function() {
|
|||
equals(shape2.bounds, expected,
|
||||
'shape2.bounds, setting shape2.scaling before shape2.rotation');
|
||||
});
|
||||
|
||||
test('Item#position pivot point and caching (#1503)', function() {
|
||||
var item = Path.Rectangle(new Point(0, 0), new Size(20));
|
||||
item.pivot = new Point(0, 0);
|
||||
var bounds = item.bounds;
|
||||
item.translate(5, 5);
|
||||
equals(item.position, new Point(5, 5));
|
||||
});
|
||||
|
||||
test('Children global matrices are cleared after parent transformation', function() {
|
||||
var item = Path.Rectangle(new Point(0, 0), new Size(100));
|
||||
var group = new Group({ children: [item], applyMatrix: false });
|
||||
equals(item.localToGlobal(item.getPointAt(0)), new Point(0, 100));
|
||||
group.translate(100, 0);
|
||||
equals(item.localToGlobal(item.getPointAt(0)), new Point(100, 100));
|
||||
});
|
||||
|
|
|
@ -789,3 +789,12 @@ test('group.internalBounds with child and child.applyMatrix = false (#1250)', fu
|
|||
equals(group.internalBounds, new Rectangle(0, 0, 250, 250),
|
||||
'group.internalBounds after scaling item1');
|
||||
});
|
||||
|
||||
test('#1561 item._globalMatrix on item after empty symbol', function(){
|
||||
var symbol = new SymbolItem(new Path());
|
||||
symbol.opacity = 0.5;
|
||||
symbol.skew(10);
|
||||
var item = new Path.Circle(new Point(0,0), 10);
|
||||
view.update();
|
||||
equals(item._globalMatrix, new Matrix());
|
||||
});
|
||||
|
|
|
@ -249,7 +249,6 @@ test('After removing all segments of a selected path, it should still be selecte
|
|||
}, true);
|
||||
});
|
||||
|
||||
|
||||
test('After simplifying a path using #simplify(), the path should stay fullySelected', function() {
|
||||
var path = new Path();
|
||||
for (var i = 0; i < 30; i++) {
|
||||
|
@ -451,6 +450,13 @@ test('Path#flatten(maxDistance)', function() {
|
|||
}, true, 'The points of the last and before last segments should not be so close, that calling toString on them returns the same string value.');
|
||||
});
|
||||
|
||||
test('Path#single segment closed path flatten (#1338)', function() {
|
||||
var p = PathItem.create("m445.26701,223.69688c6.1738,8.7566 -7.05172,14.0468 0,0z");
|
||||
p.strokeColor = "red";
|
||||
p.flatten();
|
||||
expect(0);
|
||||
});
|
||||
|
||||
test('Path#curves after removing a segment - 1', function() {
|
||||
var path = new paper.Path([0, 0], [1, 1], [2, 2]);
|
||||
var prevCurves = path.curves.slice();
|
||||
|
@ -611,3 +617,31 @@ test('Path#arcTo(from, through, to); where from, through and to all share the sa
|
|||
}
|
||||
equals(error != null, true, 'We expect this arcTo() command to throw an error');
|
||||
});
|
||||
|
||||
test('Path#getOffsetsWithTangent()', function() {
|
||||
var path = new Path.Circle(new Point(0, 0), 50);
|
||||
var length = path.length;
|
||||
equals(path.getOffsetsWithTangent(), [], 'should return empty array when called without argument');
|
||||
equals(path.getOffsetsWithTangent([1, 0]), [0.25 * length, 0.75 * length], 'should not return duplicates when tangent is at segment point');
|
||||
equals(path.getOffsetsWithTangent([1, 1]).length, 2, 'should return 2 values when called on a circle with a diagonal vector');
|
||||
});
|
||||
|
||||
test('Path#add() with a lot of segments (#1493)', function() {
|
||||
var segments = [];
|
||||
for (var i = 0; i < 100000; i++) {
|
||||
segments.push(new Point(0, 0));
|
||||
}
|
||||
var path = new Path(segments);
|
||||
path.clone();
|
||||
expect(0);
|
||||
});
|
||||
|
||||
test('Path#arcTo(through, to) is on through point side (#1477)', function() {
|
||||
var p1 = new Point(16, 21.5);
|
||||
var p2 = new Point(22.5, 15);
|
||||
var p3 = new Point(16.000000000000004, 8.5);
|
||||
var path = new Path();
|
||||
path.add(p1);
|
||||
path.arcTo(p2, p3);
|
||||
equals(true, path.segments[1].point.x > p1.x);
|
||||
});
|
||||
|
|
|
@ -1189,3 +1189,17 @@ test('Isolated edge-cases from @iconexperience\'s boolean-test suite', function(
|
|||
compareBoolean(path1.intersect(path2), result[1], 'path1.intersect(path2); // Test ' + (i + 1));
|
||||
}
|
||||
});
|
||||
|
||||
test('#1506', function () {
|
||||
var path1 = new Path('M250,175c27.61424,0 50,22.38576 50,50c0,27.61424 -22.38576,50 -50,50c-9.10718,0 -17.64567,-2.43486 -25,-6.68911c14.94503,-8.64524 25,-24.80383 25,-43.31089c0,-18.50706 -10.05497,-34.66565 -25,-43.31089c7.35433,-4.25425 15.89282,-6.68911 25,-6.68911z');
|
||||
var path2 = new Path('M250,225c0,-27.61424 22.38576,-50 50,-50c27.61424,0 50,22.38576 50,50c0,27.61424 -22.38576,50 -50,50c-27.61424,0 -50,-22.38576 -50,-50z');
|
||||
var result = 'M250,175c9.10718,0 17.64567,2.43486 25,6.68911c-14.94503,8.64523 -25,24.80383 -25,43.31089c0,18.50706 10.05497,34.66566 25,43.31089c-7.35433,4.25425 -15.89282,6.68911 -25,6.68911c-9.10718,0 -17.64567,-2.43486 -25,-6.68911c14.94503,-8.64524 25,-24.80383 25,-43.31089c0,-18.50706 -10.05497,-34.66565 -25,-43.31089c7.35433,-4.25425 15.89282,-6.68911 25,-6.68911z';
|
||||
compareBoolean(path1.subtract(path2), result);
|
||||
});
|
||||
|
||||
test('#1513', function () {
|
||||
var path1 = PathItem.create('M100,200v-100h200v100z');
|
||||
var path2 = PathItem.create('M200,100c55.22847,0 100,44.77153 100,100h-200c0,-55.22847 44.77153,-100 100,-100z');
|
||||
var result = 'M100,100h200v100c0,-55.22847 -44.77153,-100 -100,-100c-55.22847,0 -100,44.77153 -100,100z';
|
||||
compareBoolean(path1.subtract(path2), result);
|
||||
});
|
|
@ -179,3 +179,30 @@ test('Raster#getAverageColor(path) with compound path', function() {
|
|||
equals(raster.getAverageColor(compoundPath), new Color(1, 0, 0), null,
|
||||
{ tolerance: 1e-3 });
|
||||
});
|
||||
|
||||
test('Raster#smoothing defaults to true', function() {
|
||||
var raster = new Raster();
|
||||
equals(raster.smoothing, true);
|
||||
});
|
||||
|
||||
test('Raster#smoothing', function() {
|
||||
var raster = new Raster({ smoothing: false });
|
||||
equals(raster.smoothing, false);
|
||||
|
||||
raster.smoothing = true;
|
||||
equals(raster.smoothing, true);
|
||||
});
|
||||
|
||||
test('Raster#setSmoothing setting does not impact canvas context', function(assert) {
|
||||
var done = assert.async();
|
||||
var raster = new Raster('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAABlJREFUeNpi+s/AwPCfgYmR4f9/hv8AAQYAHiAFAS8Lwy8AAAAASUVORK5CYII=');
|
||||
var view = raster.view;
|
||||
var context = view._context;
|
||||
raster.onLoad = function() {
|
||||
var originalValue = context.imageSmoothingEnabled;
|
||||
raster.smoothing = false;
|
||||
view.update();
|
||||
equals(context.imageSmoothingEnabled, originalValue);
|
||||
done();
|
||||
};
|
||||
});
|
||||
|
|
|
@ -114,6 +114,17 @@ test('Import SVG polyline', function() {
|
|||
equals(imported, path);
|
||||
});
|
||||
|
||||
test('Import SVG Image', function(assert) {
|
||||
var done = assert.async();
|
||||
var svg = '<?xml version="1.0" encoding="utf-8"?><svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><image style="overflow:visible;enable-background:new ;" width="300" height="67" id="e0" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=" transform="matrix(0.2149 0 0 0.2149 304.7706 197.8176)"></image></svg>';
|
||||
var imported = paper.project.importSVG(svg);
|
||||
var raster = imported.children[0];
|
||||
raster.on('load', function() {
|
||||
equals(raster.matrix, new Matrix(0.2149, 0, 0, 0.2149, 337.0056, 205.01675));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('Import complex CompoundPath and clone', function() {
|
||||
var svg = '<svg xmlns="http://www.w3.org/2000/svg"><path fill="red" d="M4,14h20v-2H4V14z M15,26h7v-2h-7V26z M15,22h9v-2h-9V22z M15,18h9v-2h-9V18z M4,26h9V16H4V26z M28,10V6H0v22c0,0,0,4,4,4 h25c0,0,3-0.062,3-4V10H28z M4,30c-2,0-2-2-2-2V8h24v20c0,0.921,0.284,1.558,0.676,2H4z"/></svg>';
|
||||
var item = paper.project.importSVG(svg);
|
||||
|
|
|
@ -62,3 +62,8 @@
|
|||
/*#*/ include('SvgExport.js');
|
||||
|
||||
/*#*/ include('Numerical.js');
|
||||
|
||||
// There is no need to test interactions in node context.
|
||||
if (!isNode) {
|
||||
/*#*/ include('Interactions.js');
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue