diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index dbd9148a..abe9d9a2 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,7 +1,7 @@ # Description/Steps to reproduce diff --git a/.travis.yml b/.travis.yml index 0954607e..24c86ffb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,16 +1,15 @@ language: node_js +os: linux # Follow https://github.com/nodejs/LTS to decide when to remove a version node_js: -- 9 +# Stable version is temporarily disabled due to a bug in resemblejs package with +# node v12 (https://github.com/orgs/paperjs/teams/contributors/discussions/12). +# - stable +- 11 +- 10 - 8 -# 6.13 and 6.14 causing unreasonable errors in SymbolDefinition.initialize. -# https://travis-ci.org/paperjs/paper.js/jobs/434854796 -# To avoid these versions, we specify version to 6.12 as temporary solution. -# See https://github.com/paperjs/paper.js/issues/1523 -- 6.12 -sudo: false env: - matrix: + jobs: - CXX=g++-4.9 global: - secure: o1fJ/suqcL0aX6PiOmip602dAM6Q6O5oU/BDhSueUOnYknbzWfWGOdsoMun0UYhvvdcXU4sHGH+wm0VeXa2igaxryx36uMxPsXB3mO4sATo/QlWX/r3wLCg2CViXhykE5l2gP45OHh5DJgKWs51cwGLDZ9y9JHlXSP/xIGBNkMGVC7qvyhTfEb0EVvirn9b7Kt8fmD8KYgNDrsmcR3d42f4jitt4Di9LsRyOG+SCXZfI3u831tHo1sgZuGK2rxx2SdEclIblEUCkFHLp0HPjq1+032Cg5D7HNloSCPfoSwcY+rOWHubNXhmXgZHFeSkaVglkdWlDE3NiyjNlYwc4m9zqfCip8jw/jUeSfFVtruncEumGLLBxE/aMBQjAQLTq24juabm3qZNgrNCFeFo+XNyx2Oz1jllGve6Vuu8Qg0wFqE+qlZKnxNbu5/3IOIawOE1uhaOG8oSuvlpQuNrHFIMEfzh2UKPiUHbElUDyoTzHlrhQr7ZSPWPJax4uIPOTscpK4Yks7FBS4I0Vnuhw41f/bVR0kLE9jNAQoUpp47ma9O2Sw9fhOwEiopVrADzARUiy0eNeLx8F2F73L0wyPBOtEL1cfCr5oY+yZ5ZfDYb/L8/GIlbMnljYxVbXesmwd8RFi8X2HUNnEmusjih9oWazVuZpiFfUO0oeu15JTBg= @@ -51,7 +50,7 @@ before_deploy: - git config --global user.name $(git log --pretty=format:"%an" -n1) deploy: - provider: npm - node_js: 8 + node_js: 10 on: branch: develop skip_cleanup: true diff --git a/AUTHORS.md b/AUTHORS.md index 292ecf78..a5f025ab 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -1,12 +1,12 @@ ## Authors - Jürg Lehni -- Jonathan Puckey +- Jonathan Puckey ## Contributors - Harikrishnan Gopalakrishnan -- Jan Bösenberg +- Jan Bösenberg - Jt Whissel - Andrew Roles - Jacob Lites diff --git a/CHANGELOG.md b/CHANGELOG.md index c8b5b5ff..68cf659d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,145 @@ # Change Log +## `0.12.7` + +### Fixed + +- PaperScript: Actually make `options.paperFeatures.moduleExports` work + independently from `options.paperFeatures.operatorOverloading`. + +## `0.12.6` + +### Added + +- PaperScript: Add option `options.paperFeatures.moduleExports` to control + module exports conversion. + +## `0.12.5` + +### Added + +- PaperScript: Add option `options.paperFeatures.operatorOverloading` to control + operator overloading. + +### Fixed + +- Fix `new Raster(HTMLCanvasElement)` constructor (#1745). +- Handle `CurveLocation` on paths with only one segment. +- Fix recently introduced error in `CompoundPath.compare()` (#1769). +- Clamp opacity values to [0, 1] (#1814). +- Support closed `Path` items with blend mode and no segments (#1763). +- Fix error in `getCrossingSegments()` (#1773). +- SVG Import: Support SVG strings with leading line-breaks (#1813). +- Docs: Improve documentation for `Raster#drawImage(CanvasImageSource)` (#1784). + +### Changed + +- Use `'paper-'` prefix in generated view ids. + +## `0.12.4` + +### Added + +- Allow paper core import in TypeScript (#1713). +- Boolean: Improve performance from `O(n^2)` to nearly `O(n)` by the use of the + sweep and prune algorithm (#1737). +- Docs: Add support for nullable values. + +### Fixed + +- Fix `PathItem#getCrossing()` to not return overlaps (#1409). +- Fix regression in `Curve.getIntersections()` (#1638). +- Fix edge cases in `CurveLocation.isCrossing()` (#1419, #1263). +- Fix `SymbolItem#hitTestAll()` to return only one match per symbol item + (#1680). +- Fix handling of negative `Shape` sizes (#1733). +- Fix parsing of RGB `Color` strings with percentages (#1736). +- Fix `Shape` bounds when passing position in constructor (#1686). +- Prevent nested group matrix from reset when transforming parent (#1711). +- Boolean: Fix edge cases in overlap detection (#1262). +- Boolean: Add check for paths with only one segment (#1351). +- Boolean: Correctly handle open filled paths (#1647). +- Boolean: Avoid winding number edge cases (#1619). +- Docs: Fix some documentation return types (#1679). + +## `0.12.3` + +### Added + +- Add documentation for `Item#internalBounds`. + +### Fixed + +- Fix regression in `Color` change propagation (#1672, #1674). +- SVG Export: Fix viewport size of exported `Symbol` (#1668). +- Handle non-invertible matrices in `Item#contains()` (#1651). +- Improve documentation for `Item#clipMask` (#1673). +- Improve TypeScript definitions (#1659, #1663, #1664, #1667). + +## `0.12.2` + +### Fixed + +- Fix drawing with compound-paths as clip-items (#1361). +- Fix drawing of path selection with small handle size (#1327). +- Do not ignore `Group#clipItem.matrix` in `Group#internalBounds` (#1427). +- Correctly calculate bounds with nested empty items (#1467). +- Fix color change propagation on groups (#1152). +- Fix `Path#arcTo()` where `from` and `to` points are equal (#1613). +- Improve `new Raster(size[, position])` constructor (#1621). +- SVG Export: Fix error when `Item#matrix` is not invertible (#1580). +- SVG Export: Include missing viewBox attribute (#1576). +- SVG Import: Use correct default values for gradients (#1632, #1660). +- SVG Import: Add basic `` support (#1597). +- JSON Import: Prevent `Item#insert()` method from being overridden (#1392). +- PaperScript: Fix issues with increment/decrement operators (#1450, #1611). + +## `0.12.1` + +### Added + +- Add TypeScript definition, automatically generated from JSDoc comments + (#1612). +- Support `new Raster(size[, position])` constructor. +- Expose `Raster#context` accessor. +- Implement `Raster#clear()` method to clear associated canvas context. +- Node.js: Add support for Node.js v11 and v12. + +### Fixed + +- Fix parsing of CSS colors with spaces in parentheses (#1629). +- Improve `Color.random()` documentation. +- Fix `Tween#then()` documentation. + +### Removed + +- Node.js: Remove support for Node.js v6. + +## `0.12.0` + +### News + +Another release, another new member on the team: Please welcome +[@arnoson](https://github.com/arnoson), who has worked hard on the all new +animation support, exposed through the `Tween` class and its various methods on +the `Item` class, see below for details: + +### Added + +- Add new `Tween` class and related methods on `Item`, to animate and + interpolate their various properties, including colors, sub-properties, etc.: + `Item#tween(from, to, options)`, `Item#tween(to, options)`, + `Item#tween(options)`, `Item#tweenFrom(from, options)`, + `Item#tweenTo(to, options)` + +### Fixed + +- Only draw Raster if image is not empty (#1320). +- Emit mousedrag events on correct items when covered by other items (#1465). +- Fix drawing issues of bounds and position with `Group#selectedColor` (#1571). +- Fix `Item.once()` to actually only emit event once. +- Various documentation fixes and improvements (#1399). + ## `0.11.8` ### News @@ -59,6 +199,7 @@ the fixes and additions from the past two weeks: ## `0.11.5` ### Fixed + - Fix `Curve#isSelected()` to correctly reflect the state of `#handle1` (#1378). - Key Events: Fix auto-filling issue on Chrome (#1358, #1365). - Boolean: Check that overlaps are on the right path (#1321). @@ -71,38 +212,42 @@ the fixes and additions from the past two weeks: ## `0.11.4` ### Changed + - Node.js: Add support for v8, and keep testing v4, v6, v7 in Travis CI. ## `0.11.3` ### Fixed -- Mouse Events: Fix item-based `doubleclick` events (#1316). +- Mouse Events: Fix item-based `doubleclick` events (#1316). - Overhaul the caching of bounds and matrix decomposition, improving reliability of `Item#rotation` and `#scaling` and fixing situations caused by wrongly caching `Item#position` and `#bounds` values. - - Prevent consumed properties in object literal constructors from being set on the instance. ### Changed + - Make all functions and accessors enumerable on all Paper.js classes. ## `0.11.2` ### Fixed + - PaperScript: Fix a parsing error in math operations without white-space (#1314). ## `0.11.1` ### Fixed + - Bring back deactivation of Node.js modules on browsers. This has most probably broken Webpack bundling in `0.11.0`. ## `0.11.0` ### Changed + - Separate `paper` module on NPM into: `paper`, `paper-jsdom` and `paper-jsdom-canvas` (#1252): - `paper` is the main library, and can be used directly in a browser @@ -115,12 +260,14 @@ the fixes and additions from the past two weeks: [jsdom](https://github.com/tmpvar/jsdom). ### Added + - PaperScript: Support newer, external versions of Acorn.js for PaperScript parsing, opening the doors to ES 2015 (#1183, #1275). - Hit Tests: Implement `options.position` to hit `Item#position` (#1249). - Split `Item#copyTo()` into `#addTo()` and `#copyTo()`. ### Fixed + - Intersections: Bring back special handling of curve end-points (#1284). - Intersections: Correctly handle `Item#applyMatrix = false` (#1289). - Boolean: Bring back on-path winding handling (#1281). @@ -154,6 +301,7 @@ the fixes and additions from the past two weeks: ## `0.10.3` ### Changed + - Node.js: Support v7, and keep testing v4 up to v7 in Travis CI. - Node.js: Loosely couple Node.js / Electron code to `Canvas` module, and treat its absence like a headless web worker context in the browser (#1103). @@ -178,6 +326,7 @@ the fixes and additions from the past two weeks: `#reorient()` (#973). ### Added + - Implement `Path#divideAt(location)`, in analogy to `Curve#divideAt(location)`. - Add `PathItem#compare()` as a way to compare the geometry of two paths to see if they describe the same shape, handling cases where paths start in different @@ -196,6 +345,7 @@ the fixes and additions from the past two weeks: (#1004, #1177). ### Fixed + - Many improvements to boolean operations: - Improve performance of boolean operations when there no actual crossings between the paths, but paths may be contained within each other. @@ -236,17 +386,20 @@ the fixes and additions from the past two weeks: (#632). ### Removed + - Remove `Numerical.TOLERANCE = 1e-6` as there is no internal use for it anymore. ## `0.10.2` ### Fixed + - Get published version to work correctly in Bower again. ## `0.10.1` ### Fixed + - Correct a few issues with documentation and NPM publishing that slipped through in the `0.10.0` release. @@ -284,6 +437,7 @@ Thank you all for using Paper.js, submitting bugs and ideas, and all those that contribute to the code. ### Changed + - Significant overhaul and improvements of boolean path operations `PathItem#unite()`, `#subtract()`, `#intersect()`, `#exclude()`, `#divide()`: - Improve handling of self-intersecting paths and non-zero fill-rules. @@ -300,7 +454,7 @@ contribute to the code. - `Curve#getParameterAt(offset)` → `#getTimeAt(offset)` - `Curve#getParameterOf(point)` → `getTimeOf(point)` - `Curve#getPointAt(time, true)` → `#getPointAtTime(time)` - - `Curve#getTangentAt(time, true)` → `#getTangenttTime(time)` + - `Curve#getTangentAt(time, true)` → `#getTangentAtTime(time)` - `Curve#getNormalAt(time, true)` → `#getNormalAtTime(time)` - `Curve#getCurvatureAt(time, true)` → `#getCurvatureAtTime(time)` - `CurveLocation#parameter` → `#time` @@ -360,6 +514,7 @@ contribute to the code. `Color` (#1043). ### Added + - Use unified code-base for browsers, Node.js, Electron, and anything in-between, and enable npm install for browser use (#739). - Start using automatic code testing and deployment of prebuilt versions through @@ -424,6 +579,7 @@ contribute to the code. - Add `Raster#loaded` to reflect the loading state of its image. ### Fixed + - Fix calculations of `Item#strokeBounds` for all possible combinations of `Item#strokeScaling` and `Item#applyMatrix` for `Path`, `Shape` and `SymbolItem`, along with correct handling of such strokes in Item#hitTest() @@ -502,10 +658,11 @@ contribute to the code. `Numerical.solveCubic()` for edge-cases (#1085). ### Removed + - Canvas attributes "resize" and "data-paper-resize" no longer cause paper to resize the canvas when the viewport size changes; Additional CSS styles are required since `0.9.22`, e.g.: - + ```css /* Scale canvas with resize attribute to full size */ canvas[resize] { @@ -520,6 +677,7 @@ contribute to the code. It is replaced by `Project#addLayer()` and `Project#insertLayer()`. ### Deprecated + - `#windingRule` on `Item` and `Style` → `#fillRule` - `Curve#getNormalAt(time, true)` → `#getNormalAtTime(true)` - `Curve#divide()` → `#divideAt(offset)` / `#divideAtTime(time)` @@ -527,7 +685,7 @@ contribute to the code. - `Curve#getParameterAt(offset)` → `#getTimeAt(offset)` - `Curve#getParameterOf(point)` → `getTimeOf(point)` - `Curve#getPointAt(time, true)` → `#getPointAtTime(time)` -- `Curve#getTangentAt(time, true)` → `#getTangenttTime(time)` +- `Curve#getTangentAt(time, true)` → `#getTangentAtTime(time)` - `Curve#getNormalAt(time, true)` → `#getNormalAtTime(time)` - `Curve#getCurvatureAt(time, true)` → `#getCurvatureAtTime(time)` - `CurveLocation#parameter` → `#time` diff --git a/LICENSE.txt b/LICENSE.txt index 28c2e83f..84e905d8 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,5 +1,5 @@ -Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey -http://scratchdisk.com/ & http://jonathanpuckey.com/ +Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey +http://juerglehni.com/ & https://puckey.studio/ All rights reserved. The MIT License (MIT) diff --git a/README.md b/README.md index 70ee71e5..d82ec3f1 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,10 @@ -# Paper.js - The Swiss Army Knife of Vector Graphics Scripting [![Build Status](https://travis-ci.org/paperjs/paper.js.svg?branch=develop)](https://travis-ci.org/paperjs/paper.js) [![NPM](https://img.shields.io/npm/v/paper.svg)](https://www.npmjs.com/package/paper) ![Bower](https://img.shields.io/bower/v/paper.svg) +# Paper.js - The Swiss Army Knife of Vector Graphics Scripting [![Build Status](https://travis-ci.org/paperjs/paper.js.svg?branch=develop)](https://travis-ci.org/paperjs/paper.js) [![NPM](https://img.shields.io/npm/v/paper.svg)](https://www.npmjs.com/package/paper) If you want to work with Paper.js, simply download the latest "stable" version from [http://paperjs.org/download/](http://paperjs.org/download/) - Website: +- Questions: - Discussion forum: - Mainline source code: - Twitter: [@paperjs](https://twitter.com/paperjs) @@ -17,24 +18,22 @@ from [http://paperjs.org/download/](http://paperjs.org/download/) The recommended way to install and maintain Paper.js as a dependency in your project is through the [Node.js Package Manager (NPM)](https://www.npmjs.com/) -for browsers, Node.js or Electron, as well as through Bower for browsers. +for browsers, Node.js or Electron. -If NPM or Bower is already installed, simply type one of these -commands in your project folder: +If NPM is already installed, simply type one of these commands in your project +folder: ```sh npm install paper -# Or: -bower install paper ``` Upon execution, you will find a `paper` folder inside the project's -`node_modules` / `bower_components` folder. +`node_modules` folder. For more information on how to install Node.js and NPM, read the chapter [Installing Node.js and NPM](#installing-nodejs-and-npm). -### Which Version to Use? +### Which Version to Use The various distributions come with two different pre-build versions of Paper.js, in minified and normal variants: @@ -68,7 +67,7 @@ rendering to the Canvas on Node.js, as described in the next paragraph. For Linux, see to locate 32-bit and 64-bit Node.js binaries as well as sources, or use NVM, as described in the paragraph above. -### Installing Paper.js for Node.js +### Installing Paper.js Using NPM Paper.js comes in three different versions on NPM: `paper`, `paper-jsdom` and `paper-jsdom-canvas`. Depending on your use case, you need to required a @@ -85,10 +84,17 @@ different one: In order to install `paper-jsdom-canvas`, you need the [Cairo Graphics library](https://cairographics.org/) installed in your system: -##### Installing Cairo and Pango on macOS: +### Installing Native Dependencies -The easiest way to install Cairo is through [Homebrew](https://brew.sh/), by -issuing the command: +Paper.js relies on [Node-Canvas](https://github.com/Automattic/node-canvas) for +rendering, which in turn relies on the native libraries +[Cairo](https://cairographics.org/) and [Pango](https://www.pango.org/). + +#### Installing Native Dependencies on macOS + +Paper.js relies on Node-Canvas for rendering, which in turn relies on Cairo and +Pango. The easiest way to install Cairo is through +[Homebrew](https://brew.sh/), by issuing the command: brew install cairo pango @@ -112,7 +118,7 @@ After adding this line, your commands should work in the expected way: npm install paper npm update -##### Installing Cairo, Pango and all other dependencies on Debian/Ubuntu Linux: +#### Installing Native Dependencies on Debian/Ubuntu Linux sudo apt-get install pkg-config libcairo2-dev libpango1.0-dev libssl-dev libjpeg62-dev libgif-dev @@ -121,24 +127,23 @@ build from c++ sources: sudo apt-get install build-essential -##### After Cairo has been installed: +#### Installing Native Dependencies for Electron + +In order to build Node-Canvas for use of `paper-jsdom-canvas` in Electron, which +is likely to use a different version of V8 than the Node binary installed in +your system, you need to manually specify the location of Electron’s headers. +Follow these steps to do so: + +[Electron — Using Native Node +Modules](https://electron.atom.io/docs/tutorial/using-native-node-modules/) + +#### After Native Dependencies have been installed You should now be able to install the Paper.js module with jsdom and Canvas rendering from NPM: npm install paper-jsdom-canvas -### Installing Paper.js with Node-Canvas for Electron - -[Node-Canvas](https://github.com/Automattic/node-canvas) is a native dependency. -In order to build it for use of `paper-jsdom-canvas` in Electron, which is -likely to use a different version of V8 than the Node binary installed in your -system, you need to manually specify the location of Electron’s headers. Follow -these steps to do so: - -[Electron — Using Native Node -Modules](https://electron.atom.io/docs/tutorial/using-native-node-modules/) - ## Development The main Paper.js source tree is hosted on @@ -163,8 +168,8 @@ run: ### Setting Up For Building As of 2016, Paper.js uses [Gulp.js](https://gulpjs.com/) for building, and has a -couple of dependencies as Bower and NPM modules. Read the chapter [Installing -Node.js, NPM and Bower](#installing-nodejs-npm-and-bower) if you still need to +couple of dependencies as NPM modules. Read the chapter [Installing +Node.js and NPM](#installing-nodejs-and-npm) if you still need to install these. In order to be able to build Paper.js, after checking out the repository, paper diff --git a/bower.json b/bower.json index c1ae758a..87612109 100644 --- a/bower.json +++ b/bower.json @@ -9,8 +9,8 @@ }, "bugs": "https://github.com/paperjs/paper.js/issues", "authors": [ - "Jürg Lehni (http://scratchdisk.com)", - "Jonathan Puckey (http://studiomoniker.com)" + "Jürg Lehni (http://juerglehni.com)", + "Jonathan Puckey (http://puckey.studio)" ], "main": "dist/paper-full.js", "ignore": [ diff --git a/examples/Node.js/Raster.js b/examples/Node.js/Raster.js index e3a28d68..0309ae03 100644 --- a/examples/Node.js/Raster.js +++ b/examples/Node.js/Raster.js @@ -24,7 +24,7 @@ raster.onLoad = function() { var fs = require('fs'); var svg = new paper.XMLSerializer().serializeToString(project.exportSVG()); - fs.writeFile(path.resolve('./out.svg'),svg, function (err) { + fs.writeFile(path.resolve('./out.svg'), svg, function (err) { if (err) throw err; console.log('Saved!'); }); diff --git a/examples/Node.js/SvgExport.js b/examples/Node.js/SvgExport.js index a0ae8365..5d9c236a 100644 --- a/examples/Node.js/SvgExport.js +++ b/examples/Node.js/SvgExport.js @@ -45,7 +45,7 @@ with (paper) { var svg = project.exportSVG({ asString: true }); console.log(svg); - fs.writeFile(path.resolve('./out.svg'),svg, function (err) { + fs.writeFile(path.resolve('./out.svg'), svg, function (err) { if (err) throw err; console.log('Saved!'); }); diff --git a/examples/Paperjs.org/BooleanOperattions.html b/examples/Paperjs.org/BooleanOperattions.html new file mode 100644 index 00000000..930d7078 --- /dev/null +++ b/examples/Paperjs.org/BooleanOperattions.html @@ -0,0 +1,115 @@ + + + + + Path Intersections + + + + + + + + diff --git a/gulp/jsdoc b/gulp/jsdoc index 2533ac8e..1d389681 160000 --- a/gulp/jsdoc +++ b/gulp/jsdoc @@ -1 +1 @@ -Subproject commit 2533ac8e1863262f3c28cd29bc940c6d2ecdf147 +Subproject commit 1d38968146973a4bb10f5495b73c9d5dd46188d7 diff --git a/gulp/tasks/build.js b/gulp/tasks/build.js index ba2397e7..70b51c07 100644 --- a/gulp/tasks/build.js +++ b/gulp/tasks/build.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * diff --git a/gulp/tasks/dist.js b/gulp/tasks/dist.js index 17ba87bd..f8716a9f 100644 --- a/gulp/tasks/dist.js +++ b/gulp/tasks/dist.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * @@ -22,6 +22,8 @@ gulp.task('zip', ['clean:zip', 'dist'], function() { gulp.src([ 'dist/paper-full*.js', 'dist/paper-core*.js', + 'dist/paper.d.ts', + 'dist/paper-core.d.ts', 'dist/node/**/*', 'LICENSE.txt', 'examples/**/*', diff --git a/gulp/tasks/docs.js b/gulp/tasks/docs.js index 01953df9..c8a3e639 100644 --- a/gulp/tasks/docs.js +++ b/gulp/tasks/docs.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * @@ -14,14 +14,15 @@ var gulp = require('gulp'), del = require('del'), rename = require('gulp-rename'), shell = require('gulp-shell'), - options = require('../utils/options.js'); + options = require('../utils/options.js'), + run = require('run-sequence'); var docOptions = { local: 'docs', // Generates the offline docs server: 'serverdocs' // Generates the website templates for the online docs }; -gulp.task('docs', ['docs:local', 'build:full'], function() { +gulp.task('docs', ['docs:local', 'docs:typescript', 'build:full'], function() { return gulp.src('dist/paper-full.js') .pipe(rename({ basename: 'paper' })) .pipe(gulp.dest('dist/docs/assets/js/')); @@ -32,15 +33,58 @@ Object.keys(docOptions).forEach(function(name) { var mode = docOptions[name]; return gulp.src('src') .pipe(shell( - ['java -cp jsrun.jar:lib/* JsRun app/run.js', - ' -c=conf/', name, '.conf ', - ' -D="renderMode:', mode, '" ', - ' -D="version:', options.version, '"'].join(''), + [ + 'java -cp jsrun.jar:lib/* JsRun app/run.js', + ' -c=conf/', name, '.conf ', + ' -D="renderMode:', mode, '" ', + ' -D="version:', options.version, '"' + ].join(''), { cwd: 'gulp/jsdoc' }) - ) + ); }); gulp.task('clean:docs:' + name, function() { - return del([ 'dist/' + docOptions[name] + '/**' ]); + return del(['dist/' + docOptions[name] + '/**']); }); }); + +// The goal of the typescript task is to automatically generate a type +// definition for the library. +gulp.task('docs:typescript', function(callback) { + run( + 'docs:typescript:clean:before', + 'docs:typescript:build', + 'docs:typescript:clean:after', + callback + ); +}); +// First clean eventually existing type definition... +gulp.task('docs:typescript:clean:before', function() { + return del('dist/paper.d.ts'); +}); +// ...then build the definition... +gulp.task('docs:typescript:build', function() { + // First parse JSDoc comments and store parsed data in a temporary file... + return gulp.src('src') + .pipe(shell( + [ + 'java -cp jsrun.jar:lib/* JsRun app/run.js', + ' -c=conf/typescript.conf ', + ' -D="file:../../gulp/typescript/typescript-definition-data.json"', + ' -D="version:', options.version, '"', + ' -D="date:', options.date, '"' + ].join(''), + { cwd: 'gulp/jsdoc' }) + ) + // ...then generate definition from parsed data... + .pipe(shell('node gulp/typescript/typescript-definition-generator.js')) + // ...finally test the definition by compiling a typescript file. + .pipe(shell('node node_modules/typescript/bin/tsc --project gulp/typescript')); +}); +// ...finally remove all unneeded temporary files that were used for building. +gulp.task('docs:typescript:clean:after', function() { + return del([ + 'gulp/typescript/typescript-definition-data.json', + 'gulp/typescript/typescript-definition-test.js' + ]); +}); diff --git a/gulp/tasks/jshint.js b/gulp/tasks/jshint.js index 382f9154..288be598 100644 --- a/gulp/tasks/jshint.js +++ b/gulp/tasks/jshint.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * diff --git a/gulp/tasks/load.js b/gulp/tasks/load.js index 1d301aab..edfad83d 100644 --- a/gulp/tasks/load.js +++ b/gulp/tasks/load.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * diff --git a/gulp/tasks/minify.js b/gulp/tasks/minify.js index b55318bc..0bf063ab 100644 --- a/gulp/tasks/minify.js +++ b/gulp/tasks/minify.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * diff --git a/gulp/tasks/publish.js b/gulp/tasks/publish.js index d1b07c37..d2c3c0c2 100644 --- a/gulp/tasks/publish.js +++ b/gulp/tasks/publish.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * @@ -32,20 +32,21 @@ var packages = ['paper-jsdom', 'paper-jsdom-canvas'], end_with_newline: true }; -gulp.task('publish', function() { +gulp.task('publish', function(callback) { if (options.branch !== 'develop') { throw new Error('Publishing is only allowed on the develop branch.'); } // publish:website comes before publish:release, so paperjs.zip file is gone // before npm publish: - return run( + run( 'publish:json', 'publish:dist', 'publish:packages', 'publish:commit', 'publish:website', 'publish:release', - 'publish:load' + 'publish:load', + callback ); }); @@ -108,11 +109,12 @@ packages.forEach(function(name) { }); }); -gulp.task('publish:website', function() { +gulp.task('publish:website', function(callback) { if (fs.lstatSync(sitePath).isDirectory()) { - return run( + run( 'publish:website:build', - 'publish:website:push' + 'publish:website:push', + callback ); } }); diff --git a/gulp/tasks/test.js b/gulp/tasks/test.js index 1982e46c..181c194d 100644 --- a/gulp/tasks/test.js +++ b/gulp/tasks/test.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * diff --git a/gulp/tasks/watch.js b/gulp/tasks/watch.js index 5864e895..b5673225 100644 --- a/gulp/tasks/watch.js +++ b/gulp/tasks/watch.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * diff --git a/gulp/typescript/tsconfig.json b/gulp/typescript/tsconfig.json new file mode 100644 index 00000000..7fbc4091 --- /dev/null +++ b/gulp/typescript/tsconfig.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "target": "ES5", + "strictNullChecks": true + }, + "files" : [ + "typescript-definition-test.ts" + ] +} diff --git a/gulp/typescript/typescript-definition-generator.js b/gulp/typescript/typescript-definition-generator.js new file mode 100644 index 00000000..d9e94b4e --- /dev/null +++ b/gulp/typescript/typescript-definition-generator.js @@ -0,0 +1,357 @@ +/** + * This script generates a type definition by taking JSDoc roughly parsed data, + * formatting it and passing it to a mustache template. + */ + +const fs = require('fs'); +const mustache = require('mustache'); + +// Retrieve JSDoc data. +const data = JSON.parse(fs.readFileSync(__dirname + '/typescript-definition-data.json', 'utf8')); +const classes = data.classes; + +// Format classes. +classes.forEach(cls => { + // Format class. + // Store name as `className` and not simply `name`, to avoid name conflict + // in static constructors block. + cls.className = cls._name; + // Store closest parent if there is one. + cls.extends = cls.inheritsFrom && cls.inheritsFrom.length > 0 + ? cls.inheritsFrom[0] + : null; + // Store comment using class tag as description. + cls.comment = formatComment(cls.comment, 'class'); + + // Build a filter for deprecated or inherited methods or properties. + const filter = it => !it.deprecated && it.memberOf == cls.alias && !it.isNamespace; + + // Format properties. + cls.properties = cls.properties + .filter(filter) + .map(it => ({ + name: it._name, + type: formatType(it.type, { isProperty: true, isSettableProperty: !it.readOnly }), + static: formatStatic(it.isStatic), + readOnly: formatReadOnly(it.readOnly), + comment: formatComment(it.comment) + })); + + // Format methods. + const methods = cls.methods + .filter(filter) + .map(it => { + const name = formatMethodName(it._name); + const isStaticConstructor = it.isStatic && it.isConstructor; + return { + name: name, + // Constructors don't need return type. + type: !it.isConstructor + ? formatType(getMethodReturnType(it), { isMethodReturnType: true }) + : '', + static: formatStatic(it.isStatic), + // This flag is only used below to filter methods. + isStaticConstructor: isStaticConstructor, + comment: formatComment(it.comment, 'desc', it.isConstructor), + params: it._params + ? it._params + // Filter internal parameters (starting with underscore). + .filter(it => !/^_/.test(it.name)) + .map(it => formatParameter(it, isStaticConstructor && cls)) + .join(', ') + : '' + }; + }) + .sort(sortMethods); + + // Divide methods in 2 parts: static constructors and other. Because static + // constructors need a special syntax in type definition. + cls.methods = []; + cls.staticConstructors = []; + methods.forEach(method => { + if (method.isStaticConstructor) { + // Group static constructors by method name. + let staticConstructors = cls.staticConstructors.find(it => it.name === method.name); + if (!staticConstructors) { + staticConstructors = { + name: method.name, + constructors: [] + }; + cls.staticConstructors.push(staticConstructors); + } + staticConstructors.constructors.push(method); + } else { + cls.methods.push(method); + } + }); + // Store a conveniance flag to check whether class has static constructors. + cls.hasStaticConstructors = cls.staticConstructors.length > 0; +}); + +// PaperScope class needs to be handled slightly differently because it "owns" +// all the other classes as properties. Eg. we can do `new paperScope.Path()`. +// So we add a `classesPointers` property that the template will use. +const paperScopeClass = classes.find(it => it.className === 'PaperScope'); +paperScopeClass.classesPointers = classes.map(it => ({ name: it.className })); + +// Format data trough a mustache template. +// Prepare data for the template. +const context = { + classes: classes, + version: data.version, + date: data.date, + // {{#doc}} blocks are used in template to automatically generate a JSDoc + // comment with a custom indent. + doc: () => formatJSDoc +}; +// Retrieve template content. +const template = fs.readFileSync(__dirname + '/typescript-definition-template.mustache', 'utf8'); +// Render template. +const output = mustache.render(template, context); +// Write output in a file. +fs.writeFileSync(__dirname + '/../../dist/paper.d.ts', output, 'utf8'); + + +// +// METHODS +// + +function formatReadOnly(isReadOnly) { + return isReadOnly ? 'readonly ' : null; +} + +function formatStatic(isStatic) { + return isStatic ? 'static ' : null; +} + +function formatType(type, options) { + return ': ' + parseType(type, options); +} + +function parseType(type, options) { + // Always return a type even if input type is empty. In that case, return + // `void` for method return type and `any` for the rest. + if (!type) { + return options.isMethodReturnType ? 'void' : 'any'; + } + // Prefer `any[]` over `Array` to be more consistent with other types. + if (type === 'Array') { + return 'any[]'; + } + // Handle any type: `*` => `any` + type = type.replace('*', 'any'); + // Check if type is a "rest" type (meaning that an infinite number of + // parameter of this type can be passed). In that case, we need to remove + // `...` prefix and add `[]` as a suffix: + // - `...Type` => `Type[]` + // - `...(TypeA|TypeB)` => `(TypeA|TypeB)[]` + const restPattern = /^\.\.\./; + const isRest = type.match(restPattern); + if (isRest) { + type = type.replace(restPattern, ''); + } + const wrappedPattern = /^\(([^\)]+)\)$/; + const isWrapped = type.match(wrappedPattern); + if (isWrapped) { + type = type.replace(wrappedPattern, '$1'); + } + + + // Handle multiple types possibility by splitting on `|` then re-joining + // back parsed types. + const types = type.split('|'); + + // Hanle nullable type: + // - `?Type` => `Type|null` + // - `?TypeA|TypeB` => `TypeA|TypeB|null` + // - `?TypeA|?TypeB` => `TypeA|TypeB|null` + // If at least one type is nullable, we add null type at the end of the + // list. + const nullablePattern = /^\?/; + let isNullable = false; + for (let i = 0; i < types.length; i++) { + if (types[i].match(nullablePattern)) { + types[i] = types[i].replace(nullablePattern, ''); + isNullable = true; + } + } + if (isNullable) { + types.push('null'); + } + + type = types.map(splittedType => { + // Get type without array suffix `[]` for easier matching. + const singleType = splittedType.replace(/(\[\])+$/, ''); + // Handle eventual type conflict in static constructors block. For + // example, in `Path.Rectangle(rectangle: Rectangle)` method, + // `rectangle` parameter type must be mapped to `paper.Rectangle` as it + // is declared inside a `Path` namespace and would otherwise be wrongly + // assumed as being the type of `Path.Rectangle` class. + if (options.staticConstructorClass && options.staticConstructorClass.methods.find(it => it.isStatic && it.isConstructor && formatMethodName(it._name) === singleType) + ) { + return 'paper.' + splittedType; + } + // Convert primitive types to their lowercase equivalent to suit + // typescript best practices. + if (['Number', 'String', 'Boolean', 'Object'].indexOf(singleType) >= 0) { + splittedType = splittedType.toLowerCase(); + } + // Properties `object` type need to be turned into `any` to avoid + // errors when reading object properties. Eg. if `property` is of type + // `object`, `property.key` access is forbidden. + if (options.isProperty && splittedType === 'object') { + return 'any'; + } + return splittedType; + }).join(' | '); + + // Regroup types. + if (isWrapped) { + type = `(${type})`; + } + if (isRest) { + type += '[]'; + } + + return type; +} + +function formatMethodName(methodName) { + // Overloaded methods were parsed as `method^0`, `method^1`... here, we + // turn them back to `method` as typescript allow overloading. + methodName = methodName.replace(/\^[0-9]+$/, ''); + // Real contructors are called `initialize` in the library. + methodName = methodName.replace(/^initialize$/, 'constructor'); + return methodName; +} + +function formatParameter(param, staticConstructorClass) { + let content = ''; + // Handle rest parameter pattern `...Type`. Parameter name needs to be + // prefixed with `...` as in ES6. E.g. `...parameter: type[]`. + if (param.type.match(/^\.\.\.(.+)$/)) { + content += '...'; + } + content += formatParameterName(param.name); + // Optional parameters are formatted as: `parameter?: type`. + if (param.isOptional) { + content += '?'; + } + content += formatType(param.type, { staticConstructorClass }); + return content; +} + +function formatParameterName(parameterName) { + // Avoid usage of reserved keyword as parameter name. + // E.g. `function` => `callback`. + if (parameterName === 'function') { + return 'callback'; + } + return parameterName; +} + +function formatComment(comment, descriptionTagName = 'desc', skipReturn = false) { + const tags = comment.tags; + let content = ''; + + // Retrieve description tag. + const descriptionTag = tags.find(it => it.title === descriptionTagName); + if (descriptionTag) { + // Don't display group titles. + content += descriptionTag.desc.replace(/\{@grouptitle .+?\}/g, '').trim(); + } + + // Preserve some of the JSDoc tags that can be usefull even in type + // definition. Format their values to make sure that only informations + // that make sense are kept. E.g. method parameters types are already + // provided in the signature... + content += formatCommentTags(tags, 'see'); + content += formatCommentTags(tags, 'option'); + content += formatCommentTags(tags, 'param', it => it.name + ' - ' + it.desc); + + if (!skipReturn) { + content += formatCommentTags(tags, 'return', it => it.desc.trim().replace(/^\{|\}$/g, '').replace(/@([a-zA-Z]+)/, '$1')); + } + + // Make sure links are followable (e.g. by IDEs) by removing parameters. + // {@link Class#method(param)} => {@link Class#method} + content = content.replace(/(\{@link [^\}]+?)\(.*?\)(\})/g, '$1$2'); + + content = content.trim(); + return content; +} + +function formatCommentTags(tags, tagName, formatter) { + let content = ''; + // Default formatter simply outputs description. + formatter = formatter || (it => it.desc); + // Only keep tags that have a description. + tags = tags.filter(it => it.desc && it.title === tagName); + if (tags.length > 0) { + content += '\n'; + // Display tag as it was in original JSDoc, followed by formatted value. + tags.forEach(it => content += '\n@' + tagName + ' ' + formatter(it)); + } + return content; +} + +/** + * This outputs a JSDoc comment indented at the given offset and including the + * parsed comment for current mustache block. + * @param {Number} offset the number of spaces to use for indentation + * @param {Function} render the mustache render method + * @return {string} the formatted JSDoc comment + */ +function formatJSDoc(offset, render) { + // First render current block comment. Use `{{&}}` syntax to make sure + // special characters are not escaped. + let content = render('{{&comment}}'); + if (!content) { + return ''; + } + + // Build indentation. + offset = parseInt(offset); + if (offset > 0) { + offset++; + } + const indentation = new Array(offset).join(' '); + + // Prefix each line with the indentation. + content = content.split('\n') + .map(_ => indentation + ' * ' + _) + .join('\n'); + + // Wrap content in JSDoc delimiters: `/**` and `*/`. + return '/** \n' + content + '\n' + indentation + ' */'; +} + +function getMethodReturnType(method) { + return method.returnType || method.returns.length > 0 && method.returns[0].type; +} + +function sortMethods(methodA, methodB) { + // This places constructors before other methods as it is a best practice. + // This also place constructors with only one object parameter after other + // constructors to avoid type inference errors due to constructors + // overloading order. E.g. if `constructor(object: object)` is defined + // before `constructor(instance: Class)`, calling `constructor(instance)` + // will always be mapped to `contructor(object: object)`, since everything + // is an object in JavaScript. This is problematic because most of Paper.js + // classes have a constructor accepting an object. + const aIsContructor = methodA.name === 'constructor'; + const bIsContructor = methodB.name === 'constructor'; + if (aIsContructor && bIsContructor) { + if (methodA.params === 'object: object') { + return 1; + } + if (methodB.params === 'object: object') { + return -1; + } + } else if (aIsContructor) { + return -1; + } else if (bIsContructor) { + return 1; + } + return 0; +} diff --git a/gulp/typescript/typescript-definition-template.mustache b/gulp/typescript/typescript-definition-template.mustache new file mode 100644 index 00000000..b7e85e11 --- /dev/null +++ b/gulp/typescript/typescript-definition-template.mustache @@ -0,0 +1,65 @@ +/*! + * Paper.js v{{version}} - The Swiss Army Knife of Vector Graphics Scripting. + * http://paperjs.org/ + * + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ + * + * Distributed under the MIT license. See LICENSE file for details. + * + * All rights reserved. + * + * Date: {{date}} + * + * This is an auto-generated type definition. + */ + +declare namespace paper { + {{#classes}} + + {{#doc}}4{{/doc}} + class {{className}} {{#extends}}extends {{extends}}{{/extends}} { + {{#properties}} + {{#doc}}8{{/doc}} + {{static}}{{readOnly}}{{name}}{{type}} + + {{/properties}} + {{#classesPointers}} + {{name}}: typeof {{name}} + {{/classesPointers}} + + {{#methods}} + {{#doc}}8{{/doc}} + {{static}}{{name}}({{params}}){{type}} + + {{/methods}} + } + {{#hasStaticConstructors}} + namespace {{className}} { + {{#staticConstructors}} + + class {{name}} extends {{className}} { + {{#constructors}} + {{#doc}}12{{/doc}} + constructor({{params}}) + + {{/constructors}} + } + {{/staticConstructors}} + } + {{/hasStaticConstructors}} + {{/classes}} +} + + +declare module 'paper/dist/paper-core' +{ + const paperCore: Pick>; + export = paperCore +} + +declare module 'paper' +{ + const paperFull: paper.PaperScope; + export = paperFull +} diff --git a/gulp/typescript/typescript-definition-test.ts b/gulp/typescript/typescript-definition-test.ts new file mode 100644 index 00000000..0d0cead4 --- /dev/null +++ b/gulp/typescript/typescript-definition-test.ts @@ -0,0 +1,1206 @@ +/// + +/** + * This file is used as a way to test auto-generated typescript definition + * validity. For now, this only check that calling methods as they are defined + * in online documentation does not throw error in typescript compilation. + * + * Todo: add more advanced type checking by using either: + * - typescript compiler check: `let result:type = methodCall()` + * - dedicated testing library like: https://github.com/Microsoft/dtslint + */ + +import * as paper from 'paper'; + + +// +// Utility variables +// + +let point = {} as paper.Point; +let size = {} as paper.Size; +let rectangle = {} as paper.Rectangle; +let matrix = {} as paper.Matrix; +let project = {} as paper.Project; +let item = {} as paper.Item; +let layer = {} as paper.Layer; +let group = {} as paper.Group; +let shape = {} as paper.Shape; +let raster = {} as paper.Raster; +let pathItem = {} as paper.PathItem; +let path = {} as paper.Path; +let compoundPath = {} as paper.CompoundPath; +let segment = {} as paper.Segment; +let curve = {} as paper.Curve; +let curveLocation = {} as paper.CurveLocation; +let symbolDefinition = {} as paper.SymbolDefinition; +let symbolItem = {} as paper.SymbolItem; +let style = {} as paper.Style; +let color = {} as paper.Color; +let gradient = {} as paper.Gradient; +let gradientStop = {} as paper.GradientStop; +let textItem = {} as paper.TextItem; +let pointText = {} as paper.PointText; +let view = {} as paper.View; +let event = {} as paper.Event; +let mouseEvent = {} as paper.MouseEvent; +let tool = {} as paper.Tool; +let toolEvent = {} as paper.ToolEvent; +let keyEvent = {} as paper.KeyEvent; +let paperScope = {} as paper.PaperScope; +let callback = {} as () => {}; +let hitResult = {} as paper.HitResult; +let object = {} as object; + + +// +// Classes +// + +// +// Point +// + +new paper.Point(0, 0); +new paper.Point([ 0, 0 ]); +new paper.Point({ x: 0, y: 0 }); +new paper.Point(size); +new paper.Point(point); +point.x; +point.y; +point.length; +point.angle; +point.angleInRadians; +point.quadrant; +point.selected; +point.set(0, 0); +point.set([ 0, 0 ]); +point.set({ x: 0, y: 0 }); +point.set(size); +point.set(point); +point.equals(point); +point.clone(); +point.toString(); +point.getAngle(point); +point.getAngleInRadians(point); +point.getDirectedAngle(point); +point.getDistance(point, true); +point.normalize(); +point.normalize(0); +point.rotate(0, point); +point.transform(matrix); +point.isInside(rectangle); +point.isClose(point, 0); +point.isCollinear(point); +point.isOrthogonal(point); +point.isZero(); +point.isNaN(); +point.isInQuadrant(0); +point.dot(point); +point.cross(point); +point.project(point); +point.round(); +point.ceil(); +point.floor(); +point.abs(); +point.add(0); +point.add(point); +point.subtract(0); +point.subtract(point); +point.multiply(0); +point.multiply(point); +point.divide(0); +point.divide(point); +point.modulo(0); +point.modulo(point); +paper.Point.min(point, point); +paper.Point.max(point, point); +paper.Point.random(); + + +// +// Size +// + + +new paper.Size(0, 0); +new paper.Size([ 0, 0 ]); +new paper.Size({ width: 0, height: 0 }); +new paper.Size(size); +new paper.Size(point); +size.width; +size.height; +size.set(0, 0); +size.set([ 0, 0 ]); +size.set({ x: 0, y: 0 }); +size.set(size); +size.set(point); +size.equals(size); +size.clone(); +size.toString(); +size.isZero(); +size.isNaN(); +size.round(); +size.ceil(); +size.floor(); +size.abs(); +size.add(0); +size.add(size); +size.subtract(0); +size.subtract(size); +size.multiply(0); +size.multiply(size); +size.divide(0); +size.divide(size); +size.modulo(0); +size.modulo(size); +paper.Size.min(size, size); +paper.Size.max(size, size); +paper.Size.random(); + + +// +// Rectangle +// + + +new paper.Rectangle(point, size); +new paper.Rectangle({ point: point, size: size }); +new paper.Rectangle(0, 0, 0, 0); +new paper.Rectangle(point, point); +new paper.Rectangle(rectangle); +rectangle.x; +rectangle.y; +rectangle.width; +rectangle.height; +rectangle.point; +rectangle.size; +rectangle.left; +rectangle.top; +rectangle.right; +rectangle.bottom; +rectangle.center; +rectangle.topLeft; +rectangle.topRight; +rectangle.bottomLeft; +rectangle.bottomRight; +rectangle.leftCenter; +rectangle.topCenter; +rectangle.rightCenter; +rectangle.bottomCenter; +rectangle.area; +rectangle.selected; +rectangle.set(point, size); +rectangle.set({ point: point, size: size }); +rectangle.set(0, 0, 0, 0); +rectangle.set(point, point); +rectangle.set(rectangle); +rectangle.clone(); +rectangle.equals(rectangle); +rectangle.toString(); +rectangle.isEmpty(); +rectangle.contains(point); +rectangle.contains(rectangle); +rectangle.intersects(rectangle); +rectangle.intersects(rectangle, 0); +rectangle.intersect(rectangle); +rectangle.unite(rectangle); +rectangle.include(point); +rectangle.expand(0); +rectangle.expand(0, 0); +rectangle.scale(0); +rectangle.scale(0, 0); + + +// +// Matrix +// + +new paper.Matrix(); +new paper.Matrix(0, 0, 0, 0, 0, 0); +new paper.Matrix([ 0, 0, 0, 0, 0, 0 ]); +new paper.Matrix(matrix); +matrix.a; +matrix.b; +matrix.c; +matrix.d; +matrix.tx; +matrix.ty; +matrix.values; +matrix.translation; +matrix.scaling; +matrix.rotation; +matrix.set(0, 0, 0, 0, 0, 0); +matrix.set([ 0, 0, 0, 0, 0, 0 ]); +matrix.set(matrix); +matrix.clone(); +matrix.equals(matrix); +matrix.toString(); +matrix.reset(); +matrix.apply(); +matrix.apply(true); +matrix.translate(point); +matrix.translate(0, 0); +matrix.scale(0); +matrix.scale(0, point); +matrix.scale(0, 0); +matrix.scale(0, 0, point); +matrix.rotate(0, point); +matrix.rotate(0, 0, 0); +matrix.shear(point); +matrix.shear(point, point); +matrix.shear(0, 0); +matrix.shear(0, 0, point); +matrix.skew(point); +matrix.skew(point, point); +matrix.skew(0, 0); +matrix.skew(0, 0, point); +matrix.append(matrix); +matrix.prepend(matrix); +matrix.appended(matrix); +matrix.prepended(matrix); +matrix.invert(); +matrix.inverted(); +matrix.isIdentity(); +matrix.isInvertible(); +matrix.isSingular(); +matrix.transform(point); +matrix.transform([ 0, 0 ], [ 0, 0 ], 0); +matrix.inverseTransform(point); +matrix.decompose(); +matrix.applyToContext({} as CanvasRenderingContext2D); + + +// +// Project +// + +new paper.Project({} as HTMLCanvasElement); +new paper.Project(''); +new paper.Project(size); +project.view; +project.currentStyle; +project.index; +project.layers; +project.activeLayer; +project.symbolDefinitions; +project.selectedItems; +project.activate(); +project.clear(); +project.isEmpty(); +project.remove(); +project.selectAll(); +project.deselectAll(); +project.addLayer(layer); +project.insertLayer(0, layer); +project.hitTest(point); +project.hitTest(point, {}); +project.hitTestAll(point); +project.hitTestAll(point, {}); +project.getItems({}); +project.getItems(callback); +project.getItem({}); +project.getItem(callback); +project.exportJSON(); +project.exportJSON({}); +project.importJSON(''); +project.exportSVG(); +project.exportSVG({}); +project.importSVG(''); +project.importSVG({} as SVGElement); +project.importSVG('', {}); +project.importSVG('', callback); + + +// +// Item +// + +item.id; +item.className; +item.name; +item.style; +item.locked; +item.visible; +item.blendMode; +item.opacity; +item.selected; +item.clipMask; +item.data; +item.position; +item.pivot; +item.bounds; +item.strokeBounds; +item.handleBounds; +item.rotation; +item.scaling; +item.matrix; +item.globalMatrix; +item.viewMatrix; +item.applyMatrix; +item.project; +item.view; +item.layer; +item.parent; +item.children; +item.firstChild; +item.lastChild; +item.nextSibling; +item.previousSibling; +item.index; +item.strokeColor; +item.strokeWidth; +item.strokeCap; +item.strokeJoin; +item.dashOffset; +item.strokeScaling; +item.dashArray; +item.miterLimit; +item.fillColor; +item.fillColor && item.fillColor.red; +item.fillRule; +item.shadowColor; +item.shadowBlur; +item.shadowOffset; +item.selectedColor; +item.onFrame; +item.onMouseDown; +item.onMouseDrag; +item.onMouseUp; +item.onClick; +item.onDoubleClick; +item.onMouseMove; +item.onMouseEnter; +item.onMouseLeave; +item.set({}); +item.clone(); +item.clone({}); +item.copyContent(item); +item.copyAttributes(item, true); +item.rasterize(); +item.rasterize(0); +item.rasterize(0, true); +item.contains(point); +item.isInside(rectangle); +item.intersects(item); +item.hitTest(point); +item.hitTest(point, {}); +item.hitTestAll(point); +item.hitTestAll(point, {}); +item.matches({}); +item.matches(callback); +item.matches(name, {}); +item.getItems({}); +item.getItems(callback); +item.getItem({}); +item.getItem(callback); +item.exportJSON(); +item.exportJSON({}); +item.importJSON(''); +item.exportSVG(); +item.exportSVG({}); +item.importSVG(''); +item.importSVG({} as SVGElement); +item.importSVG('', {}); +item.importSVG('', callback); +item.addChild(item); +item.insertChild(0, item); +item.addChildren([ item ]); +item.insertChildren(0, [ item ]); +item.insertAbove(item); +item.insertBelow(item); +item.sendToBack(); +item.bringToFront(); +item.addTo(group); +item.copyTo(group); +item.reduce({}); +item.remove(); +item.replaceWith(item); +item.removeChildren(); +item.removeChildren(0); +item.removeChildren(0, 0); +item.reverseChildren(); +item.isEmpty(); +item.hasFill(); +item.hasStroke(); +item.hasShadow(); +item.hasChildren(); +item.isInserted(); +item.isAbove(item); +item.isBelow(item); +item.isParent(item); +item.isChild(item); +item.isDescendant(item); +item.isAncestor(item); +item.isSibling(item); +item.isGroupedWith(item); +item.translate(point); +item.rotate(0); +item.rotate(0, point); +item.scale(0); +item.scale(0, point); +item.scale(0, 0); +item.scale(0, 0, point); +item.shear(point); +item.shear(point, point); +item.shear(0, 0); +item.shear(0, 0, point); +item.skew(point); +item.skew(point, point); +item.skew(0, 0); +item.skew(0, 0, point); +item.transform(matrix); +item.globalToLocal(point); +item.localToGlobal(point); +item.parentToLocal(point); +item.localToParent(point); +item.fitBounds(rectangle); +item.fitBounds(rectangle, true); +item.on('', callback); +item.on({}); +item.off('', callback); +item.off({}); +item.emit('', event); +item.responds(''); +item.removeOn({}); +item.removeOnMove(); +item.removeOnDown(); +item.removeOnDrag(); +item.removeOnUp(); + + +// +// Layer +// + +new paper.Layer([ item ]); +new paper.Layer({}); +layer.activate(); + + +// +// Group +// + +new paper.Group([ item ]); +new paper.Group({}); +group.clipped; + + +// +// Shape +// + +new paper.Shape.Circle(point, 0); +new paper.Shape.Circle({}); +new paper.Shape.Rectangle(rectangle); +new paper.Shape.Rectangle(rectangle, size); +new paper.Shape.Rectangle(point, size); +new paper.Shape.Rectangle(point, point); +new paper.Shape.Rectangle({}); +new paper.Shape.Ellipse(rectangle); +new paper.Shape.Ellipse({}); +shape.type; +shape.size; +shape.radius; +shape.toPath(); +shape.toPath(true); + + +// +// Raster +// + +new paper.Raster(); +new paper.Raster({} as HTMLImageElement); +new paper.Raster({} as HTMLCanvasElement); +new paper.Raster(''); +new paper.Raster('', point); +raster.size; +raster.width; +raster.height; +raster.loaded; +raster.resolution; +raster.image; +raster.canvas; +raster.context; +raster.source; +raster.crossOrigin; +raster.smoothing; +raster.onLoad; +raster.onLoad = () => {}; +raster.onLoad = null; +raster.onError; +raster.getSubCanvas(rectangle); +raster.getSubRaster(rectangle); +raster.toDataURL(); +raster.drawImage({} as HTMLImageElement, point); +raster.getAverageColor(path); +raster.getAverageColor(rectangle); +raster.getAverageColor(point); +raster.getPixel(0, 0); +raster.getPixel(point); +raster.setPixel(0, 0, color); +raster.setPixel(point, color); +raster.createImageData(size); +raster.getImageData(rectangle); +raster.setImageData({} as ImageData, point); + + +// +// HitResult +// + +hitResult.type; +hitResult.name; +hitResult.item; +hitResult.location; +hitResult.color; +hitResult.segment; +hitResult.point; + + +// +// PathItem +// + +pathItem.interiorPoint; +pathItem.clockwise; +pathItem.pathData; +pathItem.unite(path); +pathItem.unite(path, {}); +pathItem.intersect(path); +pathItem.intersect(path, {}); +pathItem.subtract(path); +pathItem.subtract(path, {}); +pathItem.exclude(path); +pathItem.exclude(path, {}); +pathItem.divide(path); +pathItem.divide(path, {}); +pathItem.reorient(); +pathItem.reorient(true); +pathItem.reorient(true, true); +pathItem.getIntersections(path); +pathItem.getIntersections(path, callback); +pathItem.getCrossings(path); +pathItem.getNearestLocation(point); +pathItem.getNearestPoint(point); +pathItem.reverse(); +pathItem.flatten(); +pathItem.flatten(0); +pathItem.smooth(); +pathItem.smooth({}); +pathItem.simplify(); +pathItem.simplify(0); +pathItem.interpolate(path, path, 0); +pathItem.compare(path); +pathItem.moveTo(point); +pathItem.lineTo(point); +pathItem.arcTo(point, point); +pathItem.arcTo(point); +pathItem.arcTo(point, true); +pathItem.curveTo(point, point); +pathItem.curveTo(point, point, 0); +pathItem.cubicCurveTo(point, point, point); +pathItem.quadraticCurveTo(point, point); +pathItem.closePath(); +pathItem.moveBy(point); +pathItem.lineBy(point); +pathItem.arcBy(point, point); +pathItem.arcBy(point); +pathItem.arcBy(point, true); +pathItem.curveBy(point, point); +pathItem.curveBy(point, point, 0); +pathItem.cubicCurveBy(point, point, point); +pathItem.quadraticCurveBy(point, point); +paper.PathItem.create(''); +paper.PathItem.create([ [ 0 ] ]); +paper.PathItem.create({}); + + +// +// Path +// + +new paper.Path(); +new paper.Path([ segment ]); +new paper.Path(object); +new paper.Path(''); +new paper.Path.Line(point, point); +new paper.Path.Line(object); +new paper.Path.Circle(point, 0); +new paper.Path.Circle(object); +new paper.Path.Rectangle(rectangle); +new paper.Path.Rectangle(rectangle, size); +new paper.Path.Rectangle(point, size); +new paper.Path.Rectangle(point, point); +new paper.Path.Rectangle(object); +new paper.Path.Ellipse(rectangle); +new paper.Path.Ellipse(object); +new paper.Path.Arc(point, point, point); +new paper.Path.Arc(object); +new paper.Path.RegularPolygon(point, 0, 0); +new paper.Path.RegularPolygon(object); +new paper.Path.Star(point, 0, 0, 0); +new paper.Path.Star(object); +path.segments; +path.firstSegment; +path.lastSegment; +path.curves; +path.firstCurve; +path.lastCurve; +path.closed; +path.length; +path.area; +path.fullySelected; +path.add(segment); +path.add(point); +path.add([0,0]); +path.add(segment, point, [0,0]); +path.insert(0, segment); +path.addSegments([ segment ]); +path.insertSegments(0, [ segment ]); +path.removeSegment(0); +path.removeSegments(); +path.removeSegments(0); +path.removeSegments(0, 0); +path.hasHandles(); +path.clearHandles(); +path.divideAt(curveLocation); +path.splitAt(curveLocation); +path.join(path); +path.join(path, 0); +path.reduce(object); +path.toShape(); +path.toShape(true); +path.getLocationOf(point); +path.getOffsetOf(point); +path.getLocationAt(0); +path.getPointAt(0); +path.getTangentAt(0); +path.getNormalAt(0); +path.getWeightedTangentAt(0); +path.getWeightedNormalAt(0); +path.getCurvatureAt(0); +path.getOffsetsWithTangent(point); + + +// +// CompoundPath +// + +new paper.CompoundPath(object); +new paper.CompoundPath(''); +compoundPath.closed; +compoundPath.firstSegment; +compoundPath.lastSegment; +compoundPath.curves; +compoundPath.firstCurve; +compoundPath.lastCurve; +compoundPath.area; +compoundPath.length; + + +// +// Segment +// + +new paper.Segment(); +new paper.Segment(point); +new paper.Segment(point, point); +new paper.Segment(point, point, point); +new paper.Segment(object); +segment.point; +segment.handleIn; +segment.handleOut; +segment.selected; +segment.index; +segment.path; +segment.curve; +segment.location; +segment.next; +segment.previous; +segment.hasHandles(); +segment.isSmooth(); +segment.clearHandles(); +segment.smooth(); +segment.smooth(object); +segment.isFirst(); +segment.isLast(); +segment.reverse(); +segment.reversed(); +segment.remove(); +segment.toString(); +segment.transform(matrix); +segment.interpolate(segment, segment, 0); + + +// +// Curve +// + +new paper.Curve(segment, segment); +new paper.Curve(point, point, point, point); +curve.point1; +curve.point2; +curve.handle1; +curve.handle2; +curve.segment1; +curve.segment2; +curve.path; +curve.index; +curve.next; +curve.previous; +curve.selected; +curve.values; +curve.points; +curve.length; +curve.area; +curve.bounds; +curve.strokeBounds; +curve.handleBounds; +curve.clone(); +curve.toString(); +curve.classify(); +curve.remove(); +curve.isFirst(); +curve.isLast(); +curve.getPart(0, 0); +curve.divideAt(curveLocation); +curve.divideAtTime(0); +curve.splitAt(curveLocation); +curve.splitAtTime(0); +curve.reversed(); +curve.clearHandles(); +curve.hasHandles(); +curve.hasLength(); +curve.hasLength(0); +curve.isStraight(); +curve.isLinear(); +curve.isCollinear(curve); +curve.isHorizontal(); +curve.isVertical(); +curve.getLocationAt(0); +curve.getLocationAtTime(0); +curve.getTimeAt(0); +curve.getTimeAt(0, 0); +curve.getTimesWithTangent(point); +curve.getOffsetAtTime(0); +curve.getLocationOf(point); +curve.getOffsetOf(point); +curve.getTimeOf(point); +curve.getNearestLocation(point); +curve.getNearestPoint(point); +curve.getPointAt(curveLocation); +curve.getTangentAt(curveLocation); +curve.getNormalAt(curveLocation); +curve.getWeightedTangentAt(curveLocation); +curve.getWeightedNormalAt(curveLocation); +curve.getCurvatureAt(curveLocation); +curve.getPointAtTime(0); +curve.getTangentAtTime(0); +curve.getNormalAtTime(0); +curve.getWeightedTangentAtTime(0); +curve.getWeightedNormalAtTime(0); +curve.getCurvatureAtTime(0); +curve.getIntersections(curve); + + +// +// CurveLocation +// + +new paper.CurveLocation(curve, 0); +new paper.CurveLocation(curve, 0, point); +curveLocation.segment; +curveLocation.curve; +curveLocation.path; +curveLocation.index; +curveLocation.time; +curveLocation.point; +curveLocation.offset; +curveLocation.curveOffset; +curveLocation.intersection; +curveLocation.tangent; +curveLocation.normal; +curveLocation.curvature; +curveLocation.distance; +curveLocation.equals(curveLocation); +curveLocation.toString(); +curveLocation.isTouching(); +curveLocation.isCrossing(); +curveLocation.hasOverlap(); + + +// +// SymbolDefinition +// + +new paper.SymbolDefinition(item); +new paper.SymbolDefinition(item, true); +symbolDefinition.project; +symbolDefinition.item; +symbolDefinition.place(); +symbolDefinition.place(point); +symbolDefinition.clone(); +symbolDefinition.equals(symbolDefinition); + + +// +// SymbolItem +// + +new paper.SymbolItem(symbolDefinition); +new paper.SymbolItem(item); +new paper.SymbolItem(symbolDefinition, point); +symbolItem.definition; + + +// +// Style +// + +new paper.Style(object); +style.view; +style.strokeColor; +style.strokeWidth; +style.strokeCap; +style.strokeJoin; +style.strokeScaling; +style.dashOffset; +style.dashArray; +style.miterLimit; +style.fillColor; +style.fillRule; +style.shadowColor; +style.shadowBlur; +style.shadowOffset; +style.selectedColor; +style.fontFamily; +style.fontWeight; +style.fontSize; +style.leading; +style.justification; + + +// +// Color +// + +new paper.Color(0, 0, 0); +new paper.Color(0, 0, 0, 0); +new paper.Color(0); +new paper.Color(0, 0); +new paper.Color(object); +new paper.Color(''); +new paper.Color(gradient, point, point); +new paper.Color(gradient, point, point, point); +color.type; +color.components; +color.alpha; +color.red; +color.green; +color.blue; +color.gray; +color.hue; +color.saturation; +color.brightness; +color.lightness; +color.gradient; +color.highlight; +color.set(0, 0, 0); +color.set(0, 0, 0, 0); +color.set(0); +color.set(0, 0); +color.set(object); +color.set(color); +color.set(gradient, point, point); +color.set(gradient, point, point, point); +color.convert(''); +color.hasAlpha(); +color.equals(color); +color.clone(); +color.toString(); +color.toCSS(true); +color.transform(matrix); +color.add(0); +color.add(color); +color.subtract(0); +color.subtract(color); +color.multiply(0); +color.multiply(color); +color.divide(0); +color.divide(color); +paper.Color.random(); + + +// +// Gradient +// + +gradient.stops; +gradient.radial; +gradient.clone(); +gradient.equals(gradient); + + +// +// GradientStop +// + +new paper.GradientStop(); +new paper.GradientStop(color); +new paper.GradientStop(color, 0); +gradientStop.offset; +gradientStop.color; +gradientStop.clone(); + + +// +// TextItem +// + +textItem.content; +textItem.fontFamily; +textItem.fontWeight; +textItem.fontSize; +textItem.leading; +textItem.justification; + + +// +// PointText +// + +new paper.PointText(point); +new paper.PointText(object); +pointText.point; + + +// +// View +// + +view.autoUpdate; +view.element; +view.pixelRatio; +view.resolution; +view.viewSize; +view.bounds; +view.size; +view.center; +view.zoom; +view.rotation; +view.scaling; +view.matrix; +view.onFrame; +view.onResize; +view.onMouseDown; +view.onMouseDrag; +view.onMouseUp; +view.onClick; +view.onDoubleClick; +view.onMouseMove; +view.onMouseEnter; +view.onMouseLeave; +view.remove(); +view.update(); +view.requestUpdate(); +view.play(); +view.pause(); +view.isVisible(); +view.isInserted(); +view.translate(point); +view.rotate(0); +view.rotate(0, point); +view.scale(0); +view.scale(0, point); +view.scale(0, 0); +view.scale(0, 0, point); +view.shear(point); +view.shear(point, point); +view.shear(0, 0); +view.shear(0, 0, point); +view.skew(point); +view.skew(point, point); +view.skew(0, 0); +view.skew(0, 0, point); +view.transform(matrix); +view.projectToView(point); +view.viewToProject(point); +view.getEventPoint(event); +view.on('', callback); +view.on(object); +view.off('', callback); +view.off(object); +view.emit('', event); +view.responds(''); + + +// +// Event +// + +event.timeStamp; +event.modifiers; +event.modifiers.shift; +event.preventDefault(); +event.stopPropagation(); +event.stop(); + + +// +// MouseEvent +// + +mouseEvent.type; +mouseEvent.point; +mouseEvent.target; +mouseEvent.currentTarget; +mouseEvent.delta; +mouseEvent.toString(); + + +// +// Tool +// + +tool.minDistance; +tool.maxDistance; +tool.fixedDistance; +tool.onMouseDown; +tool.onMouseDrag; +tool.onMouseMove; +tool.onMouseUp; +tool.onKeyDown; +tool.onKeyUp; +tool.activate(); +tool.remove(); +tool.on('', callback); +tool.on(object); +tool.off('', callback); +tool.off(object); +tool.emit('', event); +tool.responds(''); + + +// +// ToolEvent +// + +toolEvent.type; +toolEvent.point; +toolEvent.lastPoint; +toolEvent.downPoint; +toolEvent.middlePoint; +toolEvent.delta; +toolEvent.count; +toolEvent.item; +toolEvent.toString(); + + +// +// Key +// + +paper.Key.modifiers; +paper.Key.isDown(''); + + +// +// KeyEvent +// + +keyEvent.type; +keyEvent.character; +keyEvent.key; +keyEvent.toString(); + + +// +// PaperScope +// + +new paper.PaperScope(); +paperScope.version; +paperScope.settings; +paperScope.settings = null; +paperScope.project; +paperScope.projects; +paperScope.view; +paperScope.tool; +paperScope.tools; +paperScope.execute(''); +paperScope.execute('', object); +paperScope.install(object); +paperScope.setup(''); +paperScope.setup({} as HTMLCanvasElement); +paperScope.setup(size); +paperScope.activate(); +paper.PaperScope.get(0); +new paperScope.Color(''); +new paperScope.CompoundPath(''); +new paperScope.Curve(segment, segment); +new paperScope.CurveLocation(curve, 0); +new paperScope.Event(); +new paperScope.Gradient(); +new paperScope.GradientStop(); +new paperScope.Group(); +new paperScope.HitResult(); +new paperScope.Item(); +new paperScope.Key(); +new paperScope.KeyEvent(); +new paperScope.Layer(); +new paperScope.Matrix(); +new paperScope.MouseEvent(); +new paperScope.PaperScript(); +new paperScope.Path(); +new paperScope.PathItem(); +new paperScope.Point(0, 0); +new paperScope.PointText(point); +new paperScope.Project(size); +new paperScope.Raster(); +new paperScope.Rectangle(point, size); +new paperScope.Segment(); +new paperScope.Shape(); +new paperScope.Size(0,0); +new paperScope.Style(object); +new paperScope.SymbolDefinition(item); +new paperScope.SymbolItem(symbolDefinition); +new paperScope.TextItem(); +new paperScope.Tool(); +new paperScope.ToolEvent(); +new paperScope.Tween(object, object, object, 0); +new paperScope.View(); + + +// +// Global PaperScope instance +// + +paper.version; +paper.settings; +paper.project; +paper.projects; +paper.view; +paper.tool; +paper.tools; +paper.execute(''); +paper.execute('', object); +paper.install(object); +paper.setup(''); +paper.setup({} as HTMLCanvasElement); +paper.setup(size); +paper.activate(); + + +// +// PaperScript +// + +paper.PaperScript.compile(''); +paper.PaperScript.compile('', object); +paper.PaperScript.execute('', paperScope); +paper.PaperScript.execute('', paperScope, object); +paper.PaperScript.load(); +paper.PaperScript.load({} as HTMLScriptElement); diff --git a/gulp/utils/error.js b/gulp/utils/error.js index cfd92c58..288a19ef 100644 --- a/gulp/utils/error.js +++ b/gulp/utils/error.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * diff --git a/gulp/utils/options.js b/gulp/utils/options.js index b9e4a414..ae51aacf 100644 --- a/gulp/utils/options.js +++ b/gulp/utils/options.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * @@ -24,9 +24,10 @@ options.date = git('log -1 --pretty=format:%ad'); options.branch = git('rev-parse --abbrev-ref HEAD'); // If a specific branch is requested, quit without errors if we don't match. -if (argv.branch && argv.branch !== options.branch) { - console.log('Branch "' + options.branch + '" does not match "' + - argv.branch + '". There is nothing to do here.'); +var ensureBranch = argv['ensure-branch']; +if (ensureBranch && ensureBranch !== options.branch) { + console.log('Branch "' + options.branch + '" does not match requested "' + + ensureBranch + '". There is nothing to do here.'); process.exit(0); } diff --git a/gulpfile.js b/gulpfile.js index 0e6e6fc3..38d6a963 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * diff --git a/package.json b/package.json index b7951e6a..d60368ed 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@scratch/paper", - "version": "0.11.8", + "version": "0.12.7", "description": "The Swiss Army Knife of Vector Graphics Scripting", "license": "MIT", "homepage": "http://paperjs.org", @@ -9,11 +9,10 @@ "url": "https://github.com/paperjs/paper.js" }, "bugs": "https://github.com/paperjs/paper.js/issues", - "contributors": ["Jürg Lehni (http://scratchdisk.com)", "Jonathan Puckey (http://studiomoniker.com)"], + "contributors": ["Jürg Lehni (http://scratchdisk.com)", "Jonathan Puckey (http://studiomoniker.com)"], "main": "dist/paper-full.js", + "types": "dist/paper.d.ts", "scripts": { - "precommit": "gulp jshint --branch develop", - "prepush": "gulp test --branch develop", "build": "gulp build", "dist": "gulp dist", "zip": "gulp zip", @@ -24,42 +23,13 @@ }, "files": ["AUTHORS.md", "CHANGELOG.md", "dist/", "examples/", "LICENSE.txt", "README.md"], "engines": { - "node": ">=4.0.0" + "node": ">=8.0.0" }, - "devDependencies": { - "acorn": "~0.5.0", - "canvas-prebuilt": "^2.0.0-alpha.14", - "del": "^2.2.1", - "gulp": "^3.9.1", - "gulp-cached": "^1.1.0", - "gulp-git-streamed": "^2.8.1", - "gulp-jshint": "^2.0.0", - "gulp-json-editor": "^2.2.1", - "gulp-prepro": "^2.4.0", - "gulp-qunits": "^2.1.1", - "gulp-rename": "^1.2.2", - "gulp-shell": "^0.5.2", - "gulp-symlink": "^2.1.4", - "gulp-uglify": "^1.5.4", - "gulp-uncomment": "^0.3.0", - "gulp-util": "^3.0.7", - "gulp-webserver": "^0.9.1", - "gulp-whitespace": "^0.1.0", - "gulp-zip": "^3.2.0", - "husky": "^0.11.4", - "jsdom": "^9.4.0", - "jshint": "^2.9.2", - "jshint-summary": "^0.4.0", - "merge-stream": "^1.0.0", - "minimist": "^1.2.0", - "prepro": "^2.4.0", - "qunitjs": "^1.23.0", - "require-dir": "^0.3.0", - "resemblejs": "^2.2.1", - "run-sequence": "^1.2.2", - "source-map-support": "^0.4.0", - "stats.js": "0.16.0", - "straps": "^3.0.1" + "husky": { + "hooks": { + "pre-commit": "gulp jshint --ensure-branch develop", + "pre-push": "gulp test --ensure-branch develop" + } }, "browser": { "canvas": false, @@ -69,5 +39,42 @@ "./dist/node/self.js": false, "./dist/node/extend.js": false }, + "devDependencies": { + "acorn": "~0.5.0", + "canvas": "^2.4.1", + "del": "^4.1.0", + "gulp": "^3.9.1", + "gulp-cached": "^1.1.0", + "gulp-git-streamed": "^2.10.1", + "gulp-jshint": "^2.1.0", + "gulp-json-editor": "^2.5.2", + "gulp-prepro": "^2.4.0", + "gulp-qunits": "^2.1.2", + "gulp-rename": "^1.4.0", + "gulp-shell": "^0.7.0", + "gulp-symlink": "^2.1.4", + "gulp-uglify": "^1.5.4", + "gulp-uncomment": "^0.3.0", + "gulp-util": "^3.0.7", + "gulp-webserver": "^0.9.1", + "gulp-whitespace": "^0.1.0", + "gulp-zip": "^3.2.0", + "husky": "^2.3.0", + "jsdom": "^15.1.1", + "jshint": "^2.10.2", + "jshint-summary": "^0.4.0", + "merge-stream": "^2.0.0", + "minimist": "^1.2.0", + "mustache": "^3.0.1", + "prepro": "^2.4.0", + "qunitjs": "^1.23.0", + "require-dir": "^1.2.0", + "resemblejs": "^3.1.0", + "run-sequence": "^2.2.1", + "source-map-support": "^0.5.12", + "stats.js": "0.17.0", + "straps": "^3.0.1", + "typescript": "^3.1.6" + }, "keywords": ["vector", "graphic", "graphics", "2d", "geometry", "bezier", "curve", "curves", "path", "paths", "canvas", "svg", "paper", "paper.js", "paperjs"] } diff --git a/packages/paper-jsdom b/packages/paper-jsdom index f601084f..33c25749 160000 --- a/packages/paper-jsdom +++ b/packages/paper-jsdom @@ -1 +1 @@ -Subproject commit f601084fc319734d0bf47da700d6b6bff95260ba +Subproject commit 33c25749460be037bf9afdb80700205c3cfd0942 diff --git a/packages/paper-jsdom-canvas b/packages/paper-jsdom-canvas index a07b7d14..317d44cf 160000 --- a/packages/paper-jsdom-canvas +++ b/packages/paper-jsdom-canvas @@ -1 +1 @@ -Subproject commit a07b7d149f02e980dfd837cd595f5000a9d5e052 +Subproject commit 317d44cfa658b2ec242cbb2f54572e2e1b58a3d2 diff --git a/src/anim/Tween.js b/src/anim/Tween.js new file mode 100644 index 00000000..d521d081 --- /dev/null +++ b/src/anim/Tween.js @@ -0,0 +1,398 @@ +/* + * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. + * http://paperjs.org/ + * + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ + * + * Distributed under the MIT license. See LICENSE file for details. + * + * All rights reserved. + */ + +/** + * @name Tween + * + * @class Allows tweening `Object` properties between two states for a given + * duration. To tween properties on Paper.js {@link Item} instances, + * {@link Item#tween(from, to, options)} can be used, which returns created + * tween instance. + * + * @see Item#tween(from, to, options) + * @see Item#tween(to, options) + * @see Item#tween(options) + * @see Item#tweenTo(to, options) + * @see Item#tweenFrom(from, options) + */ +var Tween = Base.extend(Emitter, /** @lends Tween# */{ + _class: 'Tween', + + statics: { + easings: { + // no easing, no acceleration + linear: function(t) { + return t; + }, + + // accelerating from zero velocity + easeInQuad: function(t) { + return t * t; + }, + + // decelerating to zero velocity + easeOutQuad: function(t) { + return t * (2 - t); + }, + + // acceleration until halfway, then deceleration + easeInOutQuad: function(t) { + return t < 0.5 + ? 2 * t * t + : -1 + 2 * (2 - t) * t; + }, + + // accelerating from zero velocity + easeInCubic: function(t) { + return t * t * t; + }, + + // decelerating to zero velocity + easeOutCubic: function(t) { + return --t * t * t + 1; + }, + + // acceleration until halfway, then deceleration + easeInOutCubic: function(t) { + return t < 0.5 + ? 4 * t * t * t + : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1; + }, + + // accelerating from zero velocity + easeInQuart: function(t) { + return t * t * t * t; + }, + + // decelerating to zero velocity + easeOutQuart: function(t) { + return 1 - (--t) * t * t * t; + }, + + // acceleration until halfway, then deceleration + easeInOutQuart: function(t) { + return t < 0.5 + ? 8 * t * t * t * t + : 1 - 8 * (--t) * t * t * t; + }, + + // accelerating from zero velocity + easeInQuint: function(t) { + return t * t * t * t * t; + }, + + // decelerating to zero velocity + easeOutQuint: function(t) { + return 1 + --t * t * t * t * t; + }, + + // acceleration until halfway, then deceleration + easeInOutQuint: function(t) { + return t < 0.5 + ? 16 * t * t * t * t * t + : 1 + 16 * (--t) * t * t * t * t; + } + } + }, + + /** + * Creates a new tween. + * + * @param {Object} object the object to tween the properties on + * @param {Object} from the state at the start of the tweening + * @param {Object} to the state at the end of the tweening + * @param {Number} duration the duration of the tweening + * @param {String|Function} [easing='linear'] the type of the easing + * function or the easing function + * @param {Boolean} [start=true] whether to start tweening automatically + * @return {Tween} the newly created tween + */ + initialize: function Tween(object, from, to, duration, easing, start) { + this.object = object; + var type = typeof easing; + var isFunction = type === 'function'; + this.type = isFunction + ? type + : type === 'string' + ? easing + : 'linear'; + this.easing = isFunction ? easing : Tween.easings[this.type]; + this.duration = duration; + this.running = false; + + this._then = null; + this._startTime = null; + var state = from || to; + this._keys = state ? Object.keys(state) : []; + this._parsedKeys = this._parseKeys(this._keys); + this._from = state && this._getState(from); + this._to = state && this._getState(to); + if (start !== false) { + this.start(); + } + }, + + /** + * Set a function that will be executed when the tween completes. + * @param {Function} function the function to execute when the tween + * completes + * @return {Tween} + * + * @example {@paperscript} + * // Tweens chaining: + * var circle = new Path.Circle({ + * center: view.center, + * radius: 40, + * fillColor: 'blue' + * }); + * // Tween color from blue to red. + * var tween = circle.tweenTo({ fillColor: 'red' }, 2000); + * // When the first tween completes... + * tween.then(function() { + * // ...tween color back to blue. + * circle.tweenTo({ fillColor: 'blue' }, 2000); + * }); + */ + then: function(then) { + this._then = then; + return this; + }, + + /** + * Start tweening. + * @return {Tween} + * + * @example {@paperscript} + * // Manually start tweening. + * var circle = new Path.Circle({ + * center: view.center, + * radius: 40, + * fillColor: 'blue' + * }); + * var tween = circle.tweenTo( + * { fillColor: 'red' }, + * { duration: 2000, start: false } + * ); + * tween.start(); + */ + start: function() { + this._startTime = null; + this.running = true; + return this; + }, + + /** + * Stop tweening. + * @return {Tween} + * + * @example {@paperscript} + * // Stop a tween before it completes. + * var circle = new Path.Circle({ + * center: view.center, + * radius: 40, + * fillColor: 'blue' + * }); + * // Start tweening from blue to red for 2 seconds. + * var tween = circle.tweenTo({ fillColor: 'red' }, 2000); + * // After 1 second... + * setTimeout(function(){ + * // ...stop tweening. + * tween.stop(); + * }, 1000); + */ + stop: function() { + this.running = false; + return this; + }, + + // DOCS: Document Tween#update(progress) + update: function(progress) { + if (this.running) { + if (progress > 1) { + // always finish the animation + progress = 1; + this.running = false; + } + + var factor = this.easing(progress), + keys = this._keys, + getValue = function(value) { + return typeof value === 'function' + ? value(factor, progress) + : value; + }; + for (var i = 0, l = keys && keys.length; i < l; i++) { + var key = keys[i], + from = getValue(this._from[key]), + to = getValue(this._to[key]), + // Some paper objects have math functions (e.g.: Point, + // Color) which can directly be used to do the tweening. + value = (from && to && from.__add && to.__add) + ? to.__subtract(from).__multiply(factor).__add(from) + : ((to - from) * factor) + from; + this._setProperty(this._parsedKeys[key], value); + } + + if (!this.running && this._then) { + // TODO Look into what should be returned. + this._then(this.object); + } + if (this.responds('update')) { + this.emit('update', new Base({ + progress: progress, + factor: factor + })); + } + } + return this; + }, + + /** + * {@grouptitle Event Handlers} + * + * The function to be called when the tween is updated. It receives an + * object as its sole argument, containing the current progress of the + * tweening and the factor calculated by the easing function. + * + * @name Tween#onUpdate + * @property + * @type ?Function + * + * @example {@paperscript} + * // Display tween progression values: + * var circle = new Path.Circle({ + * center: view.center, + * radius: 40, + * fillColor: 'blue' + * }); + * var tween = circle.tweenTo( + * { fillColor: 'red' }, + * { + * duration: 2000, + * easing: 'easeInCubic' + * } + * ); + * var progressText = new PointText(view.center + [60, -10]); + * var factorText = new PointText(view.center + [60, 10]); + * + * // Install event using onUpdate() property: + * tween.onUpdate = function(event) { + * progressText.content = 'progress: ' + event.progress.toFixed(2); + * }; + * + * // Install event using on('update') method: + * tween.on('update', function(event) { + * factorText.content = 'factor: ' + event.factor.toFixed(2); + * }); + */ + _events: { + onUpdate: {} + }, + + _handleFrame: function(time) { + var startTime = this._startTime, + progress = startTime + ? (time - startTime) / this.duration + : 0; + if (!startTime) { + this._startTime = time; + } + this.update(progress); + }, + + _getState: function(state) { + var keys = this._keys, + result = {}; + for (var i = 0, l = keys.length; i < l; i++) { + var key = keys[i], + path = this._parsedKeys[key], + current = this._getProperty(path), + value; + if (state) { + var resolved = this._resolveValue(current, state[key]); + // Temporarily set the resolved value, so we can retrieve the + // coerced value from paper's internal magic. + this._setProperty(path, resolved); + value = this._getProperty(path); + // Clone the value if possible to prevent future changes. + value = value && value.clone ? value.clone() : value; + this._setProperty(path, current); + } else { + // We want to get the current state at the time of the call, so + // we have to clone if possible to prevent future changes. + value = current && current.clone ? current.clone() : current; + } + result[key] = value; + } + return result; + }, + + _resolveValue: function(current, value) { + if (value) { + if (Array.isArray(value) && value.length === 2) { + var operator = value[0]; + return ( + operator && + operator.match && + // We're (unnecessarily) escaping '*/' here to not confuse + // the ol' JSDoc parser... + operator.match(/^[+\-\*\/]=/) + ) + ? this._calculate(current, operator[0], value[1]) + : value; + } else if (typeof value === 'string') { + var match = value.match(/^[+\-*/]=(.*)/); + if (match) { + var parsed = JSON.parse(match[1].replace( + /(['"])?([a-zA-Z0-9_]+)(['"])?:/g, + '"$2": ' + )); + return this._calculate(current, value[0], parsed); + } + } + } + return value; + }, + + _calculate: function(left, operator, right) { + return paper.PaperScript.calculateBinary(left, operator, right); + }, + + _parseKeys: function(keys) { + var parsed = {}; + for (var i = 0, l = keys.length; i < l; i++) { + var key = keys[i], + path = key + // Convert from JS property access notation to JSON pointer: + .replace(/\.([^.]*)/g, '/$1') + // Expand array property access notation ([]) + .replace(/\[['"]?([^'"\]]*)['"]?\]/g, '/$1'); + parsed[key] = path.split('/'); + } + return parsed; + }, + + _getProperty: function(path, offset) { + var obj = this.object; + for (var i = 0, l = path.length - (offset || 0); i < l && obj; i++) { + obj = obj[path[i]]; + } + return obj; + }, + + _setProperty: function(path, value) { + var dest = this._getProperty(path, 1); + if (dest) { + dest[path[path.length - 1]] = value; + } + } +}); diff --git a/src/basic/Line.js b/src/basic/Line.js index fc08eb71..62e66fc2 100644 --- a/src/basic/Line.js +++ b/src/basic/Line.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * @@ -12,8 +12,8 @@ /** * @name Line - * * @class The Line object represents.. + * @private */ var Line = Base.extend(/** @lends Line# */{ _class: 'Line', @@ -141,7 +141,7 @@ var Line = Base.extend(/** @lends Line# */{ } var cross = v1x * v2y - v1y * v2x; // Avoid divisions by 0, and errors when getting too close to 0 - if (!Numerical.isZero(cross)) { + if (!Numerical.isMachineZero(cross)) { var dx = p1x - p2x, dy = p1y - p2y, u1 = (v2x * dy - v2y * dx) / cross, @@ -175,7 +175,7 @@ var Line = Base.extend(/** @lends Line# */{ v2y = y - py, // ccw = v2.cross(v1); ccw = v2x * vy - v2y * vx; - if (!isInfinite && Numerical.isZero(ccw)) { + if (!isInfinite && Numerical.isMachineZero(ccw)) { // If the point is on the infinite line, check if it's on the // finite line too: Project v2 onto v1 and determine ccw based // on which side of the finite line the point lies. Calculate @@ -196,9 +196,13 @@ var Line = Base.extend(/** @lends Line# */{ vy -= py; } // Based on the error analysis by @iconexperience outlined in #799 - return vx === 0 ? vy > 0 ? x - px : px - x - : vy === 0 ? vx < 0 ? y - py : py - y - : ((x-px) * vy - (y-py) * vx) / Math.sqrt(vx * vx + vy * vy); + return vx === 0 ? (vy > 0 ? x - px : px - x) + : vy === 0 ? (vx < 0 ? y - py : py - y) + : ((x - px) * vy - (y - py) * vx) / ( + vy > vx + ? vy * Math.sqrt(1 + (vx * vx) / (vy * vy)) + : vx * Math.sqrt(1 + (vy * vy) / (vx * vx)) + ); }, getDistance: function(px, py, vx, vy, x, y, asVector) { diff --git a/src/basic/Matrix.js b/src/basic/Matrix.js index 32d4d75c..57f61d3e 100644 --- a/src/basic/Matrix.js +++ b/src/basic/Matrix.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * @@ -71,10 +71,11 @@ var Matrix = Base.extend(/** @lends Matrix# */{ * @param {Matrix} matrix the matrix to copy the values from */ initialize: function Matrix(arg, _dontNotify) { - var count = arguments.length, + var args = arguments, + count = args.length, ok = true; if (count >= 6) { // >= 6 to pass on optional _dontNotify argument. - this._set.apply(this, arguments); + this._set.apply(this, args); } else if (count === 1 || count === 2) { // Support both Matrix and Array arguments through #_set(), and pass // on the optional _dontNotify argument: @@ -104,6 +105,8 @@ var Matrix = Base.extend(/** @lends Matrix# */{ * also work for calls of `set()`. * * @function + * @param {...*} values + * @return {Point} */ set: '#initialize', @@ -183,15 +186,14 @@ var Matrix = Base.extend(/** @lends Matrix# */{ * Attempts to apply the matrix to the content of item that it belongs to, * meaning its transformation is baked into the item's content or children. * - * @param {Boolean} recursively controls whether to apply transformations - * recursively on children + * @param {Boolean} [recursively=true] controls whether to apply + * transformations recursively on children * @return {Boolean} {@true if the matrix was applied} */ apply: function(recursively, _setApplyMatrix) { var owner = this._owner; if (owner) { - owner.transform(null, true, Base.pick(recursively, true), - _setApplyMatrix); + owner.transform(null, Base.pick(recursively, true), _setApplyMatrix); // If the matrix was successfully applied, it will be reset now. return this.isIdentity(); } @@ -245,8 +247,9 @@ var Matrix = Base.extend(/** @lends Matrix# */{ * @return {Matrix} this affine transform */ scale: function(/* scale, center */) { - var scale = Point.read(arguments), - center = Point.read(arguments, 0, { readNull: true }); + var args = arguments, + scale = Point.read(args), + center = Point.read(args, 0, { readNull: true }); if (center) this.translate(center); this._a *= scale.x; @@ -326,8 +329,9 @@ var Matrix = Base.extend(/** @lends Matrix# */{ shear: function(/* shear, center */) { // Do not modify point, center, since that would arguments of which // we're reading from! - var shear = Point.read(arguments), - center = Point.read(arguments, 0, { readNull: true }); + var args = arguments, + shear = Point.read(args), + center = Point.read(args, 0, { readNull: true }); if (center) this.translate(center); var a = this._a, @@ -362,8 +366,9 @@ var Matrix = Base.extend(/** @lends Matrix# */{ * @return {Matrix} this affine transform */ skew: function(/* skew, center */) { - var skew = Point.read(arguments), - center = Point.read(arguments, 0, { readNull: true }), + var args = arguments, + skew = Point.read(args), + center = Point.read(args, 0, { readNull: true }), toRadians = Math.PI / 180, shear = new Point(Math.tan(skew.x * toRadians), Math.tan(skew.y * toRadians)); @@ -449,7 +454,7 @@ var Matrix = Base.extend(/** @lends Matrix# */{ /** * Returns a new matrix as the result of prepending the specified matrix * to this matrix. This is the equivalent of multiplying - * `(specified matrix) s* (this matrix)`. + * `(specified matrix) * (this matrix)`. * * @param {Matrix} matrix the matrix to prepend * @return {Matrix} the newly created matrix @@ -498,15 +503,15 @@ var Matrix = Base.extend(/** @lends Matrix# */{ }, /** - * @deprecated use use {@link #append(matrix)} instead. + * @deprecated use {@link #append(matrix)} instead. */ concatenate: '#append', /** - * @deprecated use use {@link #prepend(matrix)} instead. + * @deprecated use {@link #prepend(matrix)} instead. */ preConcatenate: '#prepend', /** - * @deprecated use use {@link #appended(matrix)} instead. + * @deprecated use {@link #appended(matrix)} instead. */ chain: '#appended', @@ -644,6 +649,7 @@ var Matrix = Base.extend(/** @lends Matrix# */{ * Inverse transforms a point and returns the result. * * @param {Point} point the point to be transformed + * @return {Point} */ inverseTransform: function(/* point */) { return this._inverseTransform(Point.read(arguments)); diff --git a/src/basic/Point.js b/src/basic/Point.js index e9b260dd..85974a3e 100644 --- a/src/basic/Point.js +++ b/src/basic/Point.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * @@ -170,6 +170,8 @@ var Point = Base.extend(/** @lends Point# */{ * for calls of `set()`. * * @function + * @param {...*} values + * @return {Point} */ set: '#initialize', @@ -431,11 +433,12 @@ var Point = Base.extend(/** @lends Point# */{ * @return {Number} */ getDistance: function(/* point, squared */) { - var point = Point.read(arguments), + var args = arguments, + point = Point.read(args), x = point.x - this.x, y = point.y - this.y, d = x * x + y * y, - squared = Base.read(arguments); + squared = Base.read(args); return squared ? d : Math.sqrt(d); }, @@ -709,8 +712,9 @@ var Point = Base.extend(/** @lends Point# */{ * @return {Boolean} {@true if it is within the given distance} */ isClose: function(/* point, tolerance */) { - var point = Point.read(arguments), - tolerance = Base.read(arguments); + var args = arguments, + point = Point.read(args), + tolerance = Base.read(args); return this.getDistance(point) <= tolerance; }, @@ -768,7 +772,7 @@ var Point = Base.extend(/** @lends Point# */{ * * @param {Number} quadrant the quadrant to check against * @return {Boolean} {@true if either x or y are not a number} - * @see #getQuadrant() + * @see #quadrant */ isInQuadrant: function(q) { // Map quadrant to x & y coordinate pairs and multiply with coordinates, @@ -935,8 +939,9 @@ var Point = Base.extend(/** @lends Point# */{ * [point1, point2, point3].reduce(Point.min) // {x: 60, y: 5} */ min: function(/* point1, point2 */) { - var point1 = Point.read(arguments), - point2 = Point.read(arguments); + var args = arguments, + point1 = Point.read(args), + point2 = Point.read(args); return new Point( Math.min(point1.x, point2.x), Math.min(point1.y, point2.y) @@ -966,8 +971,9 @@ var Point = Base.extend(/** @lends Point# */{ * [point1, point2, point3].reduce(Point.max) // {x: 250, y: 100} */ max: function(/* point1, point2 */) { - var point1 = Point.read(arguments), - point2 = Point.read(arguments); + var args = arguments, + point1 = Point.read(args), + point2 = Point.read(args); return new Point( Math.max(point1.x, point2.x), Math.max(point1.y, point2.y) diff --git a/src/basic/Rectangle.js b/src/basic/Rectangle.js index 0a2cae29..c65e1fb6 100644 --- a/src/basic/Rectangle.js +++ b/src/basic/Rectangle.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * @@ -73,10 +73,11 @@ var Rectangle = Base.extend(/** @lends Rectangle# */{ * Creates a new rectangle object from the passed rectangle object. * * @name Rectangle#initialize - * @param {Rectangle} rt + * @param {Rectangle} rectangle */ initialize: function Rectangle(arg0, arg1, arg2, arg3) { - var type = typeof arg0, + var args = arguments, + type = typeof arg0, read; if (type === 'number') { // new Rectangle(x, y, width, height) @@ -86,7 +87,7 @@ var Rectangle = Base.extend(/** @lends Rectangle# */{ // new Rectangle(), new Rectangle(null) this._set(0, 0, 0, 0); read = arg0 === null ? 1 : 0; - } else if (arguments.length === 1) { + } else if (args.length === 1) { // This can either be an array, or an object literal. if (Array.isArray(arg0)) { this._set.apply(this, arg0); @@ -98,11 +99,13 @@ var Rectangle = Base.extend(/** @lends Rectangle# */{ arg0.width || 0, arg0.height || 0); read = 1; } else if (arg0.from === undefined && arg0.to === undefined) { - // Use Base.filter() to support whatever property the rectangle - // can take, but handle from/to separately below. + // Use `Base.readSupported()` to read and consume whatever + // property the rectangle can receive, but handle `from` / `to` + // separately below. this._set(0, 0, 0, 0); - Base.filter(this, arg0); - read = 1; + if (Base.readSupported(args, this)) { + read = 1; + } } } if (read === undefined) { @@ -111,17 +114,16 @@ var Rectangle = Base.extend(/** @lends Rectangle# */{ // We're supporting both reading from a normal arguments list and // covering the Rectangle({ from: , to: }) constructor, through // Point.readNamed(). - var frm = Point.readNamed(arguments, 'from'), - next = Base.peek(arguments), + var frm = Point.readNamed(args, 'from'), + next = Base.peek(args), x = frm.x, y = frm.y, width, height; - if (next && next.x !== undefined - || Base.hasNamed(arguments, 'to')) { + if (next && next.x !== undefined || Base.hasNamed(args, 'to')) { // new Rectangle(from, to) // Read above why we can use readNamed() to cover both cases. - var to = Point.readNamed(arguments, 'to'); + var to = Point.readNamed(args, 'to'); width = to.x - x; height = to.y - y; // Check if horizontal or vertical order needs to be reversed. @@ -135,19 +137,19 @@ var Rectangle = Base.extend(/** @lends Rectangle# */{ } } else { // new Rectangle(point, size) - var size = Size.read(arguments); + var size = Size.read(args); width = size.width; height = size.height; } this._set(x, y, width, height); - read = arguments.__index; - // arguments.__filtered wouldn't survive the function call even if a - // previous arguments list was passed through Function#apply(). - // Return it on the object instead, see Base.read() - var filtered = arguments.__filtered; - if (filtered) - this.__filtered = filtered; + read = args.__index; } + // arguments.__filtered wouldn't survive the function call even if a + // previous arguments list was passed through Function#apply(). + // Return it on the object instead, see Base.read() + var filtered = args.__filtered; + if (filtered) + this.__filtered = filtered; if (this.__read) this.__read = read; return this; @@ -159,6 +161,8 @@ var Rectangle = Base.extend(/** @lends Rectangle# */{ * constructors also work for calls of `set()`. * * @function + * @param {...*} values + * @return {Rectangle} */ set: '#initialize', @@ -201,6 +205,7 @@ var Rectangle = Base.extend(/** @lends Rectangle# */{ /** * Returns a copy of the rectangle. + * @return {Rectangle} */ clone: function() { return new Rectangle(this.x, this.y, this.width, this.height); @@ -772,6 +777,8 @@ var Rectangle = Base.extend(/** @lends Rectangle# */{ * Rectangle#contains(point)} returns `false` for that point. * * @param {Point} point + * @return {Rectangle} the smallest rectangle that contains both the + * original rectangle and the specified point */ include: function(/* point */) { var point = Point.read(arguments); @@ -783,17 +790,18 @@ var Rectangle = Base.extend(/** @lends Rectangle# */{ }, /** - * Expands the rectangle by the specified amount in horizontal and - * vertical directions. + * Returns a new rectangle expanded by the specified amount in horizontal + * and vertical directions. * * @name Rectangle#expand * @function * @param {Number|Size|Point} amount the amount to expand the rectangle in * both directions + * @return {Rectangle} the expanded rectangle */ /** - * Expands the rectangle by the specified amounts in horizontal and - * vertical directions. + * Returns a new rectangle expanded by the specified amounts in horizontal + * and vertical directions. * * @name Rectangle#expand * @function @@ -801,6 +809,7 @@ var Rectangle = Base.extend(/** @lends Rectangle# */{ * direction * @param {Number} ver the amount to expand the rectangle in vertical * direction + * @return {Rectangle} the expanded rectangle */ expand: function(/* amount */) { var amount = Size.read(arguments), @@ -811,21 +820,23 @@ var Rectangle = Base.extend(/** @lends Rectangle# */{ }, /** - * Scales the rectangle by the specified amount from its center. + * Returns a new rectangle scaled by the specified amount from its center. * * @name Rectangle#scale * @function * @param {Number} amount + * @return {Rectangle} the scaled rectangle */ /** - * Scales the rectangle in horizontal direction by the specified `hor` - * amount and in vertical direction by the specified `ver` amount from its - * center. + * Returns a new rectangle scaled in horizontal direction by the specified + * `hor` amount and in vertical direction by the specified `ver` amount + * from its center. * * @name Rectangle#scale * @function * @param {Number} hor * @param {Number} ver + * @return {Rectangle} the scaled rectangle */ scale: function(hor, ver) { return this.expand(this.width * hor - this.width, diff --git a/src/basic/Size.js b/src/basic/Size.js index af10e6b5..e30aae7b 100644 --- a/src/basic/Size.js +++ b/src/basic/Size.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * @@ -130,6 +130,8 @@ var Size = Base.extend(/** @lends Size# */{ * for calls of `set()`. * * @function + * @param {...*} values + * @return {Size} */ set: '#initialize', @@ -158,7 +160,7 @@ var Size = Base.extend(/** @lends Size# */{ * Checks whether the width and height of the size are equal to those of the * supplied size. * - * @param {Size} + * @param {Size} size the size to compare to * @return {Boolean} * * @example @@ -176,6 +178,7 @@ var Size = Base.extend(/** @lends Size# */{ /** * Returns a copy of the size. + * @return {Size} */ clone: function() { return new Size(this.width, this.height); diff --git a/src/canvas/BlendMode.js b/src/canvas/BlendMode.js index d3342bd8..32a1dc52 100644 --- a/src/canvas/BlendMode.js +++ b/src/canvas/BlendMode.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * diff --git a/src/canvas/CanvasProvider.js b/src/canvas/CanvasProvider.js index dd9ced59..183fba70 100644 --- a/src/canvas/CanvasProvider.js +++ b/src/canvas/CanvasProvider.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * diff --git a/src/canvas/ProxyContext.js b/src/canvas/ProxyContext.js index b709bbde..636bdea4 100644 --- a/src/canvas/ProxyContext.js +++ b/src/canvas/ProxyContext.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * diff --git a/src/constants.js b/src/constants.js index b360edf0..40eca14e 100644 --- a/src/constants.js +++ b/src/constants.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * diff --git a/src/core/Base.js b/src/core/Base.js index 834c940f..dc9088b9 100644 --- a/src/core/Base.js +++ b/src/core/Base.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * @@ -269,11 +269,11 @@ statics: /** @lends Base */{ }, /** - * Allows using of Base.read() mechanism in combination with reading named - * arguments form a passed property object literal. Calling Base.readNamed() - * can read both from such named properties and normal unnamed arguments - * through Base.read(). In use for example for the various - * Path.Constructors. + * Allows using of `Base.read()` mechanism in combination with reading named + * arguments form a passed property object literal. Calling + * `Base.readNamed()` can read both from such named properties and normal + * unnamed arguments through `Base.read()`. In use for example for + * the various `Path` constructors in `Path.Constructors.js`. * * @param {Array} list the list to read from, either an arguments object or * a normal array @@ -287,24 +287,68 @@ statics: /** @lends Base */{ */ readNamed: function(list, name, start, options, amount) { var value = this.getNamed(list, name), - hasObject = value !== undefined; - if (hasObject) { - // Create a _filtered object that inherits from list[0], and + hasValue = value !== undefined; + if (hasValue) { + // Create a _filtered object that inherits from `source`, and // override all fields that were already read with undefined. var filtered = list.__filtered; if (!filtered) { - filtered = list.__filtered = Base.create(list[0]); - // Point _unfiltered to the original so Base#_set() can - // execute hasOwnProperty on it. - filtered.__unfiltered = list[0]; + var source = this.getSource(list); + filtered = list.__filtered = Base.create(source); + // Point __unfiltered to the original, so `Base.filter()` can + // use it to get all keys to iterate over. + filtered.__unfiltered = source; } // delete wouldn't work since the masked parent's value would // shine through. filtered[name] = undefined; } - var l = hasObject ? [value] : list, - res = this.read(l, start, options, amount); - return res; + return this.read(hasValue ? [value] : list, start, options, amount); + }, + + /** + * If `list[0]` is a source object, calls `Base.readNamed()` for each key in + * it that is supported on `dest`, consuming these values. + * + * @param {Array} list the list to read from, either an arguments object or + * a normal array + * @param {Object} dest the object on which to set the supported properties + * @return {Boolean} {@true if any property was read from the source object} + */ + readSupported: function(list, dest) { + var source = this.getSource(list), + that = this, + read = false; + if (source) { + // If `source` is a filtered object, we get the keys from the the + // original object (it's parent / prototype). See _filtered + // inheritance trick in the argument reading code. + Object.keys(source).forEach(function(key) { + if (key in dest) { + var value = that.readNamed(list, key); + // Due to the _filtered inheritance trick, undefined is used + // to mask already consumed named arguments. + if (value !== undefined) { + dest[key] = value; + } + read = true; + } + }); + } + return read; + }, + + /** + * @return the arguments object if the list provides one at `list[0]` + */ + getSource: function(list) { + var source = list.__source; + if (source === undefined) { + var arg = list.length === 1 && list[0]; + source = list.__source = arg && Base.isPlainObject(arg) + ? arg : null; + } + return source; }, /** @@ -314,12 +358,11 @@ statics: /** @lends Base */{ * provided, it returns the whole arguments object */ getNamed: function(list, name) { - var arg = list[0]; - if (list._hasObject === undefined) - list._hasObject = list.length === 1 && Base.isPlainObject(arg); - if (list._hasObject) + var source = this.getSource(list); + if (source) { // Return the whole arguments object if no name is provided. - return name ? arg[name] : list.__filtered || arg; + return name ? source[name] : list.__filtered || source; + } }, /** @@ -558,8 +601,16 @@ statics: /** @lends Base */{ if (args.length === 1 && obj instanceof Item && (useTarget || !(obj instanceof Layer))) { var arg = args[0]; - if (Base.isPlainObject(arg)) + if (Base.isPlainObject(arg)) { arg.insert = false; + // When using target, make sure the `item.insert()` + // method is not overridden with the `arg.insert` + // property that was just set. Pass an exclude + // object to the call of `obj.set()` below (#1392). + if (useTarget) { + args = args.concat([{ insert: true }]); + } + } } // When reusing an object, initialize it through #set() // instead of the constructor function: diff --git a/src/core/Emitter.js b/src/core/Emitter.js index 0ad43233..ac4b43a8 100644 --- a/src/core/Emitter.js +++ b/src/core/Emitter.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * @@ -68,9 +68,9 @@ var Emitter = { }, once: function(type, func) { - return this.on(type, function() { + return this.on(type, function handler() { func.apply(this, arguments); - this.off(type, func); + this.off(type, handler); }); }, diff --git a/src/core/PaperScope.js b/src/core/PaperScope.js index e60b834b..02f09200 100644 --- a/src/core/PaperScope.js +++ b/src/core/PaperScope.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * @@ -87,7 +87,7 @@ var PaperScope = Base.extend(/** @lends PaperScope# */{ // here: { chrome: true, webkit: false }, Mozilla missing is the // only difference to jQuery.browser user.replace( - /(opera|chrome|safari|webkit|firefox|msie|trident|atom|node)\/?\s*([.\d]+)(?:.*version\/([.\d]+))?(?:.*rv\:v?([.\d]+))?/g, + /(opera|chrome|safari|webkit|firefox|msie|trident|atom|node|jsdom)\/?\s*([.\d]+)(?:.*version\/([.\d]+))?(?:.*rv\:v?([.\d]+))?/g, function(match, n, v1, v2, rv) { // Do not set additional browsers once chrome is detected. if (!agent.chrome) { @@ -95,7 +95,7 @@ var PaperScope = Base.extend(/** @lends PaperScope# */{ /^(node|trident)$/.test(n) ? rv : v1; agent.version = v; agent.versionNumber = parseFloat(v); - n = n === 'trident' ? 'msie' : n; + n = { trident: 'msie', jsdom: 'node' }[n] || n; agent.name = n; agent[n] = true; } @@ -112,6 +112,7 @@ var PaperScope = Base.extend(/** @lends PaperScope# */{ * The version of Paper.js, as a string. * * @type String + * @readonly */ version: /*#=*/__options.version, @@ -200,7 +201,7 @@ var PaperScope = Base.extend(/** @lends PaperScope# */{ * mapping, in case the code that's passed in has already been mingled. * * @param {String} code the PaperScript code - * @param {Object} [option] the compilation options + * @param {Object} [options] the compilation options */ execute: function(code, options) { /*#*/ if (__options.paperScript) { @@ -310,6 +311,7 @@ var PaperScope = Base.extend(/** @lends PaperScope# */{ * Retrieves a PaperScope object with the given scope id. * * @param id + * @return {PaperScope} */ get: function(id) { return this._scopes[id] || null; diff --git a/src/core/PaperScopeItem.js b/src/core/PaperScopeItem.js index f03b8904..fb506c30 100644 --- a/src/core/PaperScopeItem.js +++ b/src/core/PaperScopeItem.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * diff --git a/src/core/PaperScript.js b/src/core/PaperScript.js index 152ded69..ff4a8511 100644 --- a/src/core/PaperScript.js +++ b/src/core/PaperScript.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * @@ -127,7 +127,7 @@ Base.exports.PaperScript = function() { * mapping, in case the code that's passed in has already been mingled. * * @param {String} code the PaperScript code - * @param {Object} [option] the compilation options + * @param {Object} [options] the compilation options * @return {Object} an object holding the compiled PaperScript translated * into JavaScript code along with source-maps and other information. */ @@ -177,7 +177,7 @@ Base.exports.PaperScript = function() { var start = getOffset(node.range[0]), end = getOffset(node.range[1]), insert = 0; - // Sort insertions by their offset, so getOffest() can do its thing + // Sort insertions by their offset, so getOffset() can do its thing for (var i = insertions.length - 1; i >= 0; i--) { if (start > insertions[i][0]) { insert = i + 1; @@ -188,27 +188,8 @@ Base.exports.PaperScript = function() { code = code.substring(0, start) + str + code.substring(end); } - // Recursively walks the AST and replaces the code of certain nodes - function walkAST(node, parent) { - if (!node) - return; - // The easiest way to walk through the whole AST is to simply loop - // over each property of the node and filter out fields we don't - // need to consider... - for (var key in node) { - if (key === 'range' || key === 'loc') - continue; - var value = node[key]; - if (Array.isArray(value)) { - for (var i = 0, l = value.length; i < l; i++) - walkAST(value[i], node); - } else if (value && typeof value === 'object') { - // We cannot use Base.isPlainObject() for these since - // Acorn.js uses its own internal prototypes now. - walkAST(value, node); - } - } - switch (node.type) { + function handleOverloading(node, parent) { + switch (node.type) { case 'UnaryExpression': // -a if (node.operator in unaryOperators && node.argument.type !== 'Literal') { @@ -256,12 +237,19 @@ Base.exports.PaperScript = function() { exp = '__$__(' + arg + ', "' + node.operator[0] + '", 1)', str = arg + ' = ' + exp; - // If this is not a prefixed update expression - // (++a, --a), assign the old value before updating it. - if (!node.prefix - && (parentType === 'AssignmentExpression' - || parentType === 'VariableDeclarator')) { - // Handle special issue #691 where the old value is + if (node.prefix) { + // A prefixed update expression (++a / --a), + // wrap expression in paranthesis. See #1611 + str = '(' + str + ')'; + } else if ( + // A suffixed update expression (a++, a--), + // assign the old value before updating it. + // See #691, #1450 + parentType === 'AssignmentExpression' || + parentType === 'VariableDeclarator' || + parentType === 'BinaryExpression' + ) { + // Handle special case where the old value is // assigned to itself, and the expression is just // executed after, e.g.: `var x = ***; x = x++;` if (getCode(parent.left || parent.id) === arg) @@ -284,6 +272,11 @@ Base.exports.PaperScript = function() { } } break; + } + } + + function handleExports(node) { + switch (node.type) { case 'ExportDefaultDeclaration': // Convert `export default` to `module.exports = ` statements: replaceCode({ @@ -321,9 +314,38 @@ Base.exports.PaperScript = function() { } } + // Recursively walks the AST and replaces the code of certain nodes + function walkAST(node, parent, paperFeatures) { + if (node) { + // The easiest way to walk through the whole AST is to simply + // loop over each property of the node and filter out fields we + // don't need to consider... + for (var key in node) { + if (key !== 'range' && key !== 'loc') { + var value = node[key]; + if (Array.isArray(value)) { + for (var i = 0, l = value.length; i < l; i++) { + walkAST(value[i], node, paperFeatures); + } + } else if (value && typeof value === 'object') { + // Don't use Base.isPlainObject() for these since + // Acorn.js uses its own internal prototypes now. + walkAST(value, node, paperFeatures); + } + } + } + if (paperFeatures.operatorOverloading !== false) { + handleOverloading(node, parent); + } + if (paperFeatures.moduleExports !== false) { + handleExports(node); + } + } + } + // Source-map support: // Encodes a Variable Length Quantity as a Base64 string. - // See: http://www.html5rocks.com/en/tutorials/developertools/sourcemaps + // See: https://www.html5rocks.com/en/tutorials/developertools/sourcemaps/ function encodeVLQ(value) { var res = '', base64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; @@ -339,15 +361,16 @@ Base.exports.PaperScript = function() { } var url = options.url || '', - agent = paper.agent, - version = agent.versionNumber, - offsetCode = false, sourceMaps = options.sourceMaps, + paperFeatures = options.paperFeatures || {}, // Include the original code in the sourceMap if there is no linked // source file so the debugger can still display it correctly. source = options.source || code, - lineBreaks = /\r\n|\n|\r/mg, offset = options.offset || 0, + agent = paper.agent, + version = agent.versionNumber, + offsetCode = false, + lineBreaks = /\r\n|\n|\r/mg, map; // TODO: Verify these browser versions for source map support, and check // other browsers. @@ -397,12 +420,17 @@ Base.exports.PaperScript = function() { sourcesContent: [source] }; } - // Now do the parsing magic - walkAST(parse(code, { - ranges: true, - preserveParens: true, - sourceType: 'module' - })); + if ( + paperFeatures.operatorOverloading !== false || + paperFeatures.moduleExports !== false + ) { + // Now do the parsing magic + walkAST(parse(code, { + ranges: true, + preserveParens: true, + sourceType: 'module' + }), null, paperFeatures); + } if (map) { if (offsetCode) { // Adjust the line offset of the resulting code if required. @@ -441,8 +469,8 @@ Base.exports.PaperScript = function() { * * @param {String} code the PaperScript code * @param {PaperScope} scope the scope for which the code is executed - * @param {Object} [option] the compilation options - * @return the exports defined in the executed code + * @param {Object} [options] the compilation options + * @return {Object} the exports defined in the executed code */ function execute(code, scope, options) { // Set currently active scope. @@ -484,7 +512,7 @@ Base.exports.PaperScript = function() { } } } - expose({ __$__: __$__, $__: $__, paper: scope, view: view, tool: tool }, + expose({ __$__: __$__, $__: $__, paper: scope, tool: tool }, true); expose(scope); // Add a fake `module.exports` object so PaperScripts can export things. @@ -657,7 +685,9 @@ Base.exports.PaperScript = function() { compile: compile, execute: execute, load: load, - parse: parse + parse: parse, + calculateBinary: __$__, + calculateUnary: $__ }; // Pass on `this` as the binding object, so we can reference Acorn both in // development and in the built library. diff --git a/src/docs/global.js b/src/docs/global.js index 4d8c8a82..8bb1a7d4 100644 --- a/src/docs/global.js +++ b/src/docs/global.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * @@ -39,7 +39,7 @@ * * The project for which the PaperScript is executed. * - * Note that when working with mulitple projects, this does not necessarily + * Note that when working with multiple projects, this does not necessarily * reflect the currently active project. For this, use * {@link PaperScope#project} instead. * @@ -57,19 +57,20 @@ /** * The reference to the project's view. * - * Note that when working with mulitple projects, this does not necessarily + * Note that when working with multiple projects, this does not necessarily * reflect the view of the currently active project. For this, use * {@link PaperScope#view} instead. * * @name view * @type View + * @readonly */ /** * The reference to the tool object which is automatically created when global * tool event handlers are defined. * - * Note that when working with mulitple tools, this does not necessarily + * Note that when working with multiple tools, this does not necessarily * reflect the currently active tool. For this, use {@link PaperScope#tool} * instead. * diff --git a/src/dom/DomElement.js b/src/dom/DomElement.js index 035f6cab..70284bb9 100644 --- a/src/dom/DomElement.js +++ b/src/dom/DomElement.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * diff --git a/src/dom/DomEvent.js b/src/dom/DomEvent.js index c6786006..37ed76fb 100644 --- a/src/dom/DomEvent.js +++ b/src/dom/DomEvent.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * diff --git a/src/event/Event.js b/src/event/Event.js index 5beca73c..320c9d18 100644 --- a/src/event/Event.js +++ b/src/event/Event.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * diff --git a/src/event/Key.js b/src/event/Key.js index c8746429..c9e79a73 100644 --- a/src/event/Key.js +++ b/src/event/Key.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * diff --git a/src/event/KeyEvent.js b/src/event/KeyEvent.js index c20af658..5f10a595 100644 --- a/src/event/KeyEvent.js +++ b/src/event/KeyEvent.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * diff --git a/src/event/MouseEvent.js b/src/event/MouseEvent.js index 93859adb..ffdf3e27 100644 --- a/src/event/MouseEvent.js +++ b/src/event/MouseEvent.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * diff --git a/src/export.js b/src/export.js index 0ac7f578..cdbabc46 100644 --- a/src/export.js +++ b/src/export.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * diff --git a/src/init.js b/src/init.js index 3e726c91..c82a3a87 100644 --- a/src/init.js +++ b/src/init.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * diff --git a/src/item/ChangeFlag.js b/src/item/ChangeFlag.js index fd1aaede..b802d9b1 100644 --- a/src/item/ChangeFlag.js +++ b/src/item/ChangeFlag.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * diff --git a/src/item/Group.js b/src/item/Group.js index f9e36070..7f8e146b 100644 --- a/src/item/Group.js +++ b/src/item/Group.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * @@ -171,8 +171,7 @@ var Group = Item.extend(/** @lends Group# */{ _getBounds: function _getBounds(matrix, options) { var clipItem = this._getClipItem(); return clipItem - ? clipItem._getCachedBounds( - matrix && matrix.appended(clipItem._matrix), + ? clipItem._getCachedBounds(clipItem._matrix.prepended(matrix), Base.set({}, options, { stroke: false })) : _getBounds.base.call(this, matrix, options); }, diff --git a/src/item/HitResult.js b/src/item/HitResult.js index 460ee4b0..bdb19048 100644 --- a/src/item/HitResult.js +++ b/src/item/HitResult.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * @@ -75,7 +75,7 @@ var HitResult = Base.extend(/** @lends HitResult# */{ * * @name HitResult#color * @property - * @type Color + * @type ?Color */ /** @@ -105,7 +105,7 @@ var HitResult = Base.extend(/** @lends HitResult# */{ */ getOptions: function(args) { var options = args && Base.read(args); - return Base.set({ + return new Base({ // Type of item, for instanceof check: Group, Layer, Path, // CompoundPath, Shape, Raster, SymbolItem, ... type: null, diff --git a/src/item/Item.js b/src/item/Item.js index 6e2dfc63..d6cd8558 100644 --- a/src/item/Item.js +++ b/src/item/Item.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * @@ -658,8 +658,8 @@ new function() { // Injection scope for various item event handlers /** * Specifies whether the item defines a clip mask. This can only be set on - * paths, compound paths, and text frame objects, and only if the item is - * already contained within a clipping group. + * paths and compound paths, and only if the item is already contained + * within a clipping group. * * @bean * @type Boolean @@ -1075,7 +1075,9 @@ new function() { // Injection scope for various item event handlers options = options || {}; for (var i = 0, l = items.length; i < l; i++) { var item = items[i]; - if (item._visible && !item.isEmpty()) { + // Item is handled if it is visible and not recursively empty. + // This avoid errors with nested empty groups (#1467). + if (item._visible && !item.isEmpty(true)) { // Pass true for noInternal, since even when getting // internal bounds for this item, we need to apply the // matrices to its children. @@ -1120,6 +1122,17 @@ new function() { // Injection scope for various item event handlers * @type Rectangle */ + /** + * The bounding rectangle of the item without any matrix transformations. + * + * Typical use case would be drawing a frame around the object where you + * want to draw something of the same size, position, rotation, and scaling, + * like a selection frame. + * + * @name Item#internalBounds + * @type Rectangle + */ + /** * The rough bounding rectangle of the item that is sure to include all of * the drawing, including stroke width. @@ -1805,11 +1818,15 @@ new function() { // Injection scope for various item event handlers * } * * @param {Point} point the point to check for + * @return {Boolean} */ contains: function(/* point */) { // See CompoundPath#_contains() for the reason for !! - return !!this._contains( - this._matrix._inverseTransform(Point.read(arguments))); + var matrix = this._matrix; + return ( + matrix.isInvertible() && + !!this._contains(matrix._inverseTransform(Point.read(arguments))) + ); }, _contains: function(point) { @@ -1866,16 +1883,18 @@ new function() { // Injection scope for various item event handlers }, new function() { // Injection scope for hit-test functions shared with project function hitTest(/* point, options */) { + var args = arguments; return this._hitTest( - Point.read(arguments), - HitResult.getOptions(arguments)); + Point.read(args), + HitResult.getOptions(args)); } function hitTestAll(/* point, options */) { - var point = Point.read(arguments), - options = HitResult.getOptions(arguments), + var args = arguments, + point = Point.read(args), + options = HitResult.getOptions(args), all = []; - this._hitTest(point, Base.set({ all: all }, options)); + this._hitTest(point, new Base({ all: all }, options)); return all; } @@ -2353,6 +2372,7 @@ new function() { // Injection scope for hit-test functions shared with project * items can have children. * * @param {String} json the JSON data to import from + * @return {Item} */ importJSON: function(json) { // Try importing into `this`. If another item is returned, try adding @@ -2388,7 +2408,8 @@ new function() { // Injection scope for hit-test functions shared with project * kept as a link to their external URL. * * @param {Object} [options] the export options - * @return {SVGElement} the item converted to an SVG node + * @return {SVGElement|String} the item converted to an SVG node or a + * `String` depending on `option.asString` value */ /** @@ -2772,6 +2793,7 @@ new function() { // Injection scope for hit-test functions shared with project * Replaces this item with the provided new item which will takes its place * in the project hierarchy instead. * + * @param {Item} item the item that will replace this item * @return {Boolean} {@true if the item was replaced} */ replaceWith: function(item) { @@ -2840,11 +2862,23 @@ new function() { // Injection scope for hit-test functions shared with project * no children, a {@link TextItem} with no text content and a {@link Path} * with no segments all are considered empty. * - * @return Boolean + * @param {Boolean} [recursively=false] whether an item with children should be + * considered empty if all its descendants are empty + * @return {Boolean} */ - isEmpty: function() { + isEmpty: function(recursively) { var children = this._children; - return !children || !children.length; + var numChildren = children ? children.length : 0; + if (recursively) { + // In recursive check, item is empty if all its children are empty. + for (var i = 0; i < numChildren; i++) { + if (!children[i].isEmpty(recursively)) { + return false; + } + } + return true; + } + return !numChildren; }, /** @@ -2907,7 +2941,7 @@ new function() { // Injection scope for hit-test functions shared with project * defined in such a way, e.g. if one is a descendant of the other. */ _getOrder: function(item) { - // Private method that produces a list of anchestors, starting with the + // Private method that produces a list of ancestors, starting with the // root and ending with the actual element as the last entry. function getList(item) { var list = []; @@ -3057,7 +3091,7 @@ new function() { // Injection scope for hit-test functions shared with project * * @name Item#strokeColor * @property - * @type Color + * @type ?Color * * @example {@paperscript} * // Setting the stroke color of a path: @@ -3194,7 +3228,7 @@ new function() { // Injection scope for hit-test functions shared with project * * @name Item#dashArray * @property - * @type Array + * @type Number[] * @default [] */ @@ -3219,7 +3253,7 @@ new function() { // Injection scope for hit-test functions shared with project * * @name Item#fillColor * @property - * @type Color + * @type ?Color * * @example {@paperscript} * // Setting the fill color of a path to red: @@ -3253,7 +3287,7 @@ new function() { // Injection scope for hit-test functions shared with project * * @property * @name Item#shadowColor - * @type Color + * @type ?Color * * @example {@paperscript} * // Creating a circle with a black shadow: @@ -3299,13 +3333,14 @@ new function() { // Injection scope for hit-test functions shared with project * * @name Item#selectedColor * @property - * @type Color + * @type ?Color */ }, Base.each(['rotate', 'scale', 'shear', 'skew'], function(key) { var rotate = key === 'rotate'; this[key] = function(/* value, center */) { - var value = (rotate ? Base : Point).read(arguments), - center = Point.read(arguments, 0, { readNull: true }); + var args = arguments, + value = (rotate ? Base : Point).read(args), + center = Point.read(args, 0, { readNull: true }); return this.transform(new Matrix()[key](value, center || this.getPosition(true))); }; @@ -3438,7 +3473,7 @@ new function() { // Injection scope for hit-test functions shared with project * * @name Item#shear * @function - * @param {Point} shear the horziontal and vertical shear factors as a point + * @param {Point} shear the horizontal and vertical shear factors as a point * @param {Point} [center={@link Item#position}] * @see Matrix#shear(shear[, center]) */ @@ -3460,7 +3495,7 @@ new function() { // Injection scope for hit-test functions shared with project * * @name Item#skew * @function - * @param {Point} skew the horziontal and vertical skew angles in degrees + * @param {Point} skew the horizontal and vertical skew angles in degrees * @param {Point} [center={@link Item#position}] * @see Matrix#shear(skew[, center]) */ @@ -3485,19 +3520,22 @@ new function() { // Injection scope for hit-test functions shared with project // @param {String[]} flags array of any of the following: 'objects', // 'children', 'fill-gradients', 'fill-patterns', 'stroke-patterns', // 'lines'. Default: ['objects', 'children'] - transform: function(matrix, _applyMatrix, _applyRecursively, - _setApplyMatrix) { + transform: function(matrix, _applyRecursively, _setApplyMatrix) { var _matrix = this._matrix, - // If no matrix is provided, or the matrix is the identity, we might - // still have some work to do in case _applyMatrix is true transformMatrix = matrix && !matrix.isIdentity(), - applyMatrix = (_applyMatrix || this._applyMatrix) + // If no matrix is provided, or the matrix is the identity, we might + // still have some work to do: _setApplyMatrix or _applyRecursively. + applyMatrix = ( + _setApplyMatrix && this._canApplyMatrix || + this._applyMatrix && ( // Don't apply _matrix if the result of concatenating with // matrix would be identity. - && ((!_matrix.isIdentity() || transformMatrix) - // Even if it's an identity matrix, we still need to - // recursively apply the matrix to children. - || _applyMatrix && _applyRecursively && this._children); + transformMatrix || !_matrix.isIdentity() || + // Even if it's an identity matrix, we may still need to + // recursively apply the matrix to children. + _applyRecursively && this._children + ) + ); // Bail out if there is nothing to do. if (!transformMatrix && !applyMatrix) return this; @@ -3529,8 +3567,9 @@ new function() { // Injection scope for hit-test functions shared with project // internal _matrix transformations to the item's content. // Application is not possible on Raster, PointText, SymbolItem, since // the matrix is where the actual transformation state is stored. - if (applyMatrix && (applyMatrix = this._transformContent(_matrix, - _applyRecursively, _setApplyMatrix))) { + + if (applyMatrix && (applyMatrix = this._transformContent( + _matrix, _applyRecursively, _setApplyMatrix))) { // Pivot is provided in the parent's coordinate system, so transform // it along too. var pivot = this._pivot; @@ -3594,9 +3633,9 @@ new function() { // Injection scope for hit-test functions shared with project _transformContent: function(matrix, applyRecursively, setApplyMatrix) { var children = this._children; if (children) { - for (var i = 0, l = children.length; i < l; i++) - children[i].transform(matrix, true, applyRecursively, - setApplyMatrix); + for (var i = 0, l = children.length; i < l; i++) { + children[i].transform(matrix, applyRecursively, setApplyMatrix); + } return true; } }, @@ -3745,7 +3784,7 @@ new function() { // Injection scope for hit-test functions shared with project * * @name Item#onFrame * @property - * @type Function + * @type ?Function * @see View#onFrame * * @example {@paperscript} @@ -3772,7 +3811,7 @@ new function() { // Injection scope for hit-test functions shared with project * * @name Item#onMouseDown * @property - * @type Function + * @type ?Function * @see View#onMouseDown * * @example {@paperscript} @@ -3822,7 +3861,7 @@ new function() { // Injection scope for hit-test functions shared with project * * @name Item#onMouseDrag * @property - * @type Function + * @type ?Function * @see View#onMouseDrag * * @example {@paperscript height=240} @@ -3851,7 +3890,7 @@ new function() { // Injection scope for hit-test functions shared with project * * @name Item#onMouseUp * @property - * @type Function + * @type ?Function * @see View#onMouseUp * * @example {@paperscript} @@ -3881,7 +3920,7 @@ new function() { // Injection scope for hit-test functions shared with project * * @name Item#onClick * @property - * @type Function + * @type ?Function * @see View#onClick * * @example {@paperscript} @@ -3931,7 +3970,7 @@ new function() { // Injection scope for hit-test functions shared with project * * @name Item#onDoubleClick * @property - * @type Function + * @type ?Function * @see View#onDoubleClick * * @example {@paperscript} @@ -3981,7 +4020,7 @@ new function() { // Injection scope for hit-test functions shared with project * * @name Item#onMouseMove * @property - * @type Function + * @type ?Function * @see View#onMouseMove * * @example {@paperscript} @@ -4012,7 +4051,7 @@ new function() { // Injection scope for hit-test functions shared with project * * @name Item#onMouseEnter * @property - * @type Function + * @type ?Function * @see View#onMouseEnter * * @example {@paperscript} @@ -4074,7 +4113,7 @@ new function() { // Injection scope for hit-test functions shared with project * * @name Item#onMouseLeave * @property - * @type Function + * @type ?Function * @see View#onMouseLeave * * @example {@paperscript} @@ -4334,7 +4373,7 @@ new function() { // Injection scope for hit-test functions shared with project // Exclude Raster items since they never draw a stroke and handle // opacity by themselves (they also don't call _setStyles) var blendMode = this._blendMode, - opacity = this._opacity, + opacity = Numerical.clamp(this._opacity, 0, 1), normalBlend = blendMode === 'normal', nativeBlend = BlendMode.nativeModes[blendMode], // Determine if we can draw directly, or if we need to draw into a @@ -4417,8 +4456,10 @@ new function() { // Injection scope for hit-test functions shared with project this._draw(ctx, param, viewMatrix, strokeMatrix); ctx.restore(); matrices.pop(); - if (param.clip && !param.dontFinish) - ctx.clip(); + if (param.clip && !param.dontFinish) { + // Pass fill-rule to handle clipping with compound-paths (#1361). + ctx.clip(this.getFillRule()); + } // If a temporary canvas was created, composite it onto the main canvas: if (!direct) { // Use BlendMode.process even for processing normal blendMode with @@ -4672,4 +4713,176 @@ new function() { // Injection scope for hit-test functions shared with project } return this; } -})); +}), /** @lends Item# */{ + /** + * {@grouptitle Tweening Functions} + * + * Tween item between two states. + * + * @name Item#tween + * + * @option options.duration {Number} the duration of the tweening + * @option [options.easing='linear'] {Function|String} an easing function or the type + * of the easing: {@values 'linear' 'easeInQuad' 'easeOutQuad' + * 'easeInOutQuad' 'easeInCubic' 'easeOutCubic' 'easeInOutCubic' + * 'easeInQuart' 'easeOutQuart' 'easeInOutQuart' 'easeInQuint' + * 'easeOutQuint' 'easeInOutQuint'} + * @option [options.start=true] {Boolean} whether to start tweening automatically + * + * @function + * @param {Object} from the state at the start of the tweening + * @param {Object} to the state at the end of the tweening + * @param {Object|Number} options the options or the duration + * @return {Tween} + * + * @example {@paperscript height=100} + * // Tween fillColor: + * var path = new Path.Circle({ + * radius: view.bounds.height * 0.4, + * center: view.center + * }); + * path.tween( + * { fillColor: 'blue' }, + * { fillColor: 'red' }, + * 3000 + * ); + * @example {@paperscript height=100} + * // Tween rotation: + * var path = new Shape.Rectangle({ + * fillColor: 'red', + * center: [50, view.center.y], + * size: [60, 60] + * }); + * path.tween({ + * rotation: 180, + * 'position.x': view.bounds.width - 50, + * 'fillColor.hue': '+= 90' + * }, { + * easing: 'easeInOutCubic', + * duration: 2000 + * }); + */ + /** + * Tween item to a state. + * + * @name Item#tween + * + * @function + * @param {Object} to the state at the end of the tweening + * @param {Object|Number} options the options or the duration + * @return {Tween} + * + * @example {@paperscript height=200} + * // Tween a nested property with relative values + * var path = new Path.Rectangle({ + * size: [100, 100], + * position: view.center, + * fillColor: 'red', + * }); + * + * var delta = { x: path.bounds.width / 2, y: 0 }; + * + * path.tween({ + * 'segments[1].point': ['+=', delta], + * 'segments[2].point.x': '-= 50' + * }, 3000); + * + * @see Item#tween(from, to, options) + */ + /** + * Tween item. + * + * @name Item#tween + * + * @function + * @param {Object|Number} options the options or the duration + * @return {Tween} + * + * @see Item#tween(from, to, options) + * + * @example {@paperscript height=100} + * // Start an empty tween and just use the update callback: + * var path = new Path.Circle({ + * fillColor: 'blue', + * radius: view.bounds.height * 0.4, + * center: view.center, + * }); + * var pathFrom = path.clone({ insert: false }) + * var pathTo = new Path.Rectangle({ + * position: view.center, + * rectangle: path.bounds, + * insert: false + * }); + * path.tween(2000).onUpdate = function(event) { + * path.interpolate(pathFrom, pathTo, event.factor) + * }; + */ + tween: function(from, to, options) { + if (!options) { + // If there are only two or one arguments, shift arguments to the + // left by one (omit `from`): + options = to; + to = from; + from = null; + if (!options) { + options = to; + to = null; + } + } + var easing = options && options.easing, + start = options && options.start, + duration = options != null && ( + typeof options === 'number' ? options : options.duration + ), + tween = new Tween(this, from, to, duration, easing, start); + function onFrame(event) { + tween._handleFrame(event.time * 1000); + if (!tween.running) { + this.off('frame', onFrame); + } + } + if (duration) { + this.on('frame', onFrame); + } + return tween; + }, + + /** + * + * Tween item to a state. + * + * @function + * @param {Object} to the state at the end of the tweening + * @param {Object|Number} options the options or the duration + * @return {Tween} + * + * @see Item#tween(to, options) + */ + tweenTo: function(to, options) { + return this.tween(null, to, options); + }, + + /** + * + * Tween item from a state to its state before the tweening. + * + * @function + * @param {Object} from the state at the start of the tweening + * @param {Object|Number} options the options or the duration + * @return {Tween} + * + * @see Item#tween(from, to, options) + * + * @example {@paperscript height=100} + * // Tween fillColor from red to the path's initial fillColor: + * var path = new Path.Circle({ + * fillColor: 'blue', + * radius: view.bounds.height * 0.4, + * center: view.center + * }); + * path.tweenFrom({ fillColor: 'red' }, { duration: 1000 }); + */ + tweenFrom: function(from, options) { + return this.tween(from, null, options); + } +}); diff --git a/src/item/ItemSelection.js b/src/item/ItemSelection.js index 94d75180..71340079 100644 --- a/src/item/ItemSelection.js +++ b/src/item/ItemSelection.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * diff --git a/src/item/Layer.js b/src/item/Layer.js index 5650f505..60d6660d 100644 --- a/src/item/Layer.js +++ b/src/item/Layer.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * diff --git a/src/item/Project.js b/src/item/Project.js index 8a7ad5c4..232a900f 100644 --- a/src/item/Project.js +++ b/src/item/Project.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * @@ -133,7 +133,7 @@ var Project = PaperScopeItem.extend(/** @lends Project# */{ /** * Checks whether the project has any content or not. * - * @return Boolean + * @return {Boolean} */ isEmpty: function() { return !this._children.length; @@ -348,7 +348,7 @@ var Project = PaperScopeItem.extend(/** @lends Project# */{ * {@link #layers} list. * * @param {Number} index the index at which to insert the layer - * @param {Item} item the item to be inserted in the project + * @param {Layer} layer the layer to be inserted in the project * @return {Layer} the added layer, or `null` if adding was not possible */ insertLayer: function(index, layer) { @@ -745,6 +745,7 @@ var Project = PaperScopeItem.extend(/** @lends Project# */{ * {@link Project#clear()} to do so. * * @param {String} json the JSON data to import from + * @return {Item} the imported item */ importJSON: function(json) { this.activate(); @@ -781,7 +782,8 @@ var Project = PaperScopeItem.extend(/** @lends Project# */{ * kept as a link to their external URL. * * @param {Object} [options] the export options - * @return {SVGElement} the project converted to an SVG node + * @return {SVGElement|String} the project converted to an SVG node or a + * `String` depending on `option.asString` value */ /** diff --git a/src/item/Raster.js b/src/item/Raster.js index c7a6cd39..23fe1565 100644 --- a/src/item/Raster.js +++ b/src/item/Raster.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * @@ -31,15 +31,19 @@ var Raster = Item.extend(/** @lends Raster# */{ // Prioritize `crossOrigin` over `source`: _prioritize: ['crossOrigin'], _smoothing: false, + // Enforce creation of beans, as bean getters have hidden parameters. + // See #getContext(_change) below. + beans: true, // TODO: Implement type, width, height. // TODO: Have SymbolItem & Raster inherit from a shared class? /** * Creates a new raster item from the passed argument, and places it in the - * active layer. `object` can either be a DOM Image, a Canvas, or a string + * active layer. `source` can either be a DOM Image, a Canvas, or a string * describing the URL to load the image from, or the ID of a DOM element to * get the image from (either a DOM Image or a Canvas). * + * @name Raster#initialize * @param {HTMLImageElement|HTMLCanvasElement|String} [source] the source of * the raster * @param {Point} [position] the center position at which the raster item is @@ -77,22 +81,64 @@ var Raster = Item.extend(/** @lends Raster# */{ * raster.scale(0.5); * raster.rotate(10); */ - initialize: function Raster(object, position) { - // Support two forms of item initialization: Passing one object literal - // describing all the different properties to be set, or an image - // (object) and a point where it should be placed (point). + /** + * Creates a new empty raster of the given size, and places it in the + * active layer. + * + * @name Raster#initialize + * @param {Size} size the size of the raster + * @param {Point} [position] the center position at which the raster item is + * placed + * + * @example {@paperscript height=150} + * // Creating an empty raster and fill it with random pixels: + * var width = 100; + * var height = 100; + * + * // Create an empty raster placed at view center. + * var raster = new Raster(new Size(width, height), view.center); + * + * // For all of its pixels... + * for (var i = 0; i < width; i++) { + * for (var j = 0; j < height; j++) { + * // ...set a random color. + * raster.setPixel(i, j, Color.random()); + * } + * } + */ + initialize: function Raster(source, position) { + // Support three forms of item initialization: + // - One object literal describing all the different properties. + // - An image (Image|Canvas|String) and an optional position (Point). + // - A size (Size) describing the canvas that will be created and an + // optional position (Point). // If _initialize can set properties through object literal, we're done. // Otherwise we need to check the type of object: - if (!this._initialize(object, - position !== undefined && Point.read(arguments, 1))) { - // object can be an image, canvas, URL or DOM-ID: - var image = typeof object === 'string' - ? document.getElementById(object) : object; + if (!this._initialize(source, + position !== undefined && Point.read(arguments))) { + var image, + type = typeof source, + object = type === 'string' + ? document.getElementById(source) + : type === 'object' + ? source + : null; + if (object && object !== Item.NO_INSERT) { + if (object.getContext || object.naturalHeight != null) { + image = object; + } else if (object) { + // See if the arguments describe the raster size: + var size = Size.read(arguments); + if (!size.isZero()) { + image = CanvasProvider.getCanvas(size); + } + } + } if (image) { // #setImage() handles both canvas and image types. this.setImage(image); } else { - this.setSource(object); + this.setSource(source); } } if (!this._size) { @@ -305,7 +351,7 @@ var Raster = Item.extend(/** @lends Raster# */{ * case `null` is returned instead. * * @bean - * @type HTMLCanvasELement + * @type HTMLCanvasElement */ getCanvas: function() { if (!this._canvas) { @@ -330,15 +376,15 @@ var Raster = Item.extend(/** @lends Raster# */{ * The Canvas 2D drawing context of the raster. * * @bean - * @type Context + * @type CanvasRenderingContext2D */ - getContext: function(modify) { + getContext: function(_change) { if (!this._context) this._context = this.getCanvas().getContext('2d'); // Support a hidden parameter that indicates if the context will be used - // to modify the Raster object. We can notify such changes ahead since + // to change the Raster object. We can notify such changes ahead since // they are only used afterwards for redrawing. - if (modify) { + if (_change) { // Also set _image to null since the Raster stops representing it. // NOTE: This should theoretically be in our own _changed() handler // for ChangeFlag.PIXELS, but since it's only happening in one place @@ -386,7 +432,11 @@ var Raster = Item.extend(/** @lends Raster# */{ crossOrigin = this._crossOrigin; if (crossOrigin) image.crossOrigin = crossOrigin; - image.src = src; + // Prevent setting image source to `null`, as this isn't supported by + // browsers, and it would actually throw exceptions in JSDOM. + // TODO: Look into fixing this bug in JSDOM. + if (src) + image.src = src; this.setImage(image); }, @@ -463,7 +513,7 @@ var Raster = Item.extend(/** @lends Raster# */{ * @param {Rectangle} rect the boundaries of the sub image in pixel * coordinates * - * @return {HTMLCanvasELement} the sub image as a Canvas object + * @return {HTMLCanvasElement} the sub image as a Canvas object */ getSubCanvas: function(/* rect */) { var rect = Rectangle.read(arguments), @@ -519,7 +569,7 @@ var Raster = Item.extend(/** @lends Raster# */{ /** * Draws an image on the raster. * - * @param {HTMLImageELement|HTMLCanvasELement} image + * @param {CanvasImageSource} image * @param {Point} point the offset of the image as a point in pixel * coordinates */ @@ -614,8 +664,8 @@ var Raster = Item.extend(/** @lends Raster# */{ * * @name Raster#getPixel * @function - * @param x the x offset of the pixel in pixel coordinates - * @param y the y offset of the pixel in pixel coordinates + * @param {Number} x the x offset of the pixel in pixel coordinates + * @param {Number} y the y offset of the pixel in pixel coordinates * @return {Color} the color of the pixel */ /** @@ -623,7 +673,8 @@ var Raster = Item.extend(/** @lends Raster# */{ * * @name Raster#getPixel * @function - * @param point the offset of the pixel as a point in pixel coordinates + * @param {Point} point the offset of the pixel as a point in pixel + * coordinates * @return {Color} the color of the pixel */ getPixel: function(/* point */) { @@ -639,21 +690,23 @@ var Raster = Item.extend(/** @lends Raster# */{ * * @name Raster#setPixel * @function - * @param x the x offset of the pixel in pixel coordinates - * @param y the y offset of the pixel in pixel coordinates - * @param color the color that the pixel will be set to + * @param {Number} x the x offset of the pixel in pixel coordinates + * @param {Number} y the y offset of the pixel in pixel coordinates + * @param {Color} color the color that the pixel will be set to */ /** * Sets the color of the specified pixel to the specified color. * * @name Raster#setPixel * @function - * @param point the offset of the pixel as a point in pixel coordinates - * @param color the color that the pixel will be set to + * @param {Point} point the offset of the pixel as a point in pixel + * coordinates + * @param {Color} color the color that the pixel will be set to */ setPixel: function(/* point, color */) { - var point = Point.read(arguments), - color = Color.read(arguments), + var args = arguments, + point = Point.read(args), + color = Color.read(args), components = color._convert('rgb'), alpha = color._alpha, ctx = this.getContext(true), @@ -666,6 +719,14 @@ var Raster = Item.extend(/** @lends Raster# */{ ctx.putImageData(imageData, point.x, point.y); }, + /** + * Clears the image, if it is backed by a canvas. + */ + clear: function() { + var size = this._size; + this.getContext(true).clearRect(0, 0, size.width + 1, size.height + 1); + }, + // DOCS: document Raster#createImageData /** * {@grouptitle Image Data} @@ -710,7 +771,7 @@ var Raster = Item.extend(/** @lends Raster# */{ * * @name Raster#onLoad * @property - * @type Function + * @type ?Function * * @example * var url = 'http://assets.paperjs.org/images/marilyn.jpg'; @@ -736,7 +797,7 @@ var Raster = Item.extend(/** @lends Raster# */{ * * @name Raster#onError * @property - * @type Function + * @type ?Function */ _getBounds: function(matrix, options) { @@ -761,10 +822,11 @@ var Raster = Item.extend(/** @lends Raster# */{ _draw: function(ctx, param, viewMatrix) { var element = this.getElement(); - if (element) { + // Only draw if image is not empty (#1320). + if (element && element.width > 0 && element.height > 0) { // Handle opacity for Rasters separately from the rest, since // Rasters never draw a stroke. See Item#draw(). - ctx.globalAlpha = this._opacity; + ctx.globalAlpha = Numerical.clamp(this._opacity, 0, 1); // Call _setStyles() to make sure shadow is drawn (#1437). this._setStyles(ctx, param, viewMatrix); diff --git a/src/item/Shape.js b/src/item/Shape.js index 184362a9..53cec0f7 100644 --- a/src/item/Shape.js +++ b/src/item/Shape.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * @@ -82,7 +82,7 @@ var Shape = Item.extend(/** @lends Shape# */{ setSize: function(/* size */) { var size = Size.read(arguments); if (!this._size) { - // First time, e.g. whean reading from JSON... + // First time, e.g. when reading from JSON... this._size = size.clone(); } else if (!this._size.equals(size)) { var type = this._type, @@ -90,7 +90,7 @@ var Shape = Item.extend(/** @lends Shape# */{ height = size.height; if (type === 'rectangle') { // Shrink radius accordingly - this._radius.set(Size.min(this._radius, size.divide(2))); + this._radius.set(Size.min(this._radius, size.divide(2).abs())); } else if (type === 'circle') { // Use average of width and height as new size, then calculate // radius as a number from that: @@ -130,7 +130,7 @@ var Shape = Item.extend(/** @lends Shape# */{ } else { radius = Size.read(arguments); if (!this._radius) { - // First time, e.g. whean reading from JSON... + // First time, e.g. when reading from JSON... this._radius = radius.clone(); } else { if (this._radius.equals(radius)) @@ -390,10 +390,13 @@ new function() { // Scope for _contains() and _hitTestSelf() code. // Mess with indentation in order to get more line-space below: statics: new function() { function createShape(type, point, size, radius, args) { - var item = new Shape(Base.getNamed(args), point); + // Use `Base.create()` to avoid calling `initialize()` until after the + // internal fields are set here, then call `_initialize()` directly: + var item = Base.create(Shape.prototype); item._type = type; item._size = size; item._radius = radius; + item._initialize(Base.getNamed(args), point); return item; } @@ -427,10 +430,11 @@ statics: new function() { * }); */ Circle: function(/* center, radius */) { - var center = Point.readNamed(arguments, 'center'), - radius = Base.readNamed(arguments, 'radius'); + var args = arguments, + center = Point.readNamed(args, 'center'), + radius = Base.readNamed(args, 'radius'); return createShape('circle', center, new Size(radius * 2), radius, - arguments); + args); }, /** @@ -524,11 +528,12 @@ statics: new function() { * }); */ Rectangle: function(/* rectangle */) { - var rect = Rectangle.readNamed(arguments, 'rectangle'), - radius = Size.min(Size.readNamed(arguments, 'radius'), + var args = arguments, + rect = Rectangle.readNamed(args, 'rectangle'), + radius = Size.min(Size.readNamed(args, 'radius'), rect.getSize(true).divide(2)); return createShape('rectangle', rect.getCenter(true), - rect.getSize(true), radius, arguments); + rect.getSize(true), radius, args); }, /** @@ -567,10 +572,11 @@ statics: new function() { * }); */ Ellipse: function(/* rectangle */) { - var ellipse = Shape._readEllipse(arguments), + var args = arguments, + ellipse = Shape._readEllipse(args), radius = ellipse.radius; return createShape('ellipse', ellipse.center, radius.multiply(2), - radius, arguments); + radius, args); }, // Private method to read ellipse center and radius from arguments list, diff --git a/src/item/SymbolDefinition.js b/src/item/SymbolDefinition.js index 9e79b27d..c65d6bf8 100644 --- a/src/item/SymbolDefinition.js +++ b/src/item/SymbolDefinition.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * @@ -140,7 +140,7 @@ var SymbolDefinition = Base.extend(/** @lends SymbolDefinition# */{ /** * Returns a copy of the symbol. * - * @return {Symbol} + * @return {SymbolDefinition} */ clone: function() { return new SymbolDefinition(this._item.clone(false)); @@ -149,7 +149,7 @@ var SymbolDefinition = Base.extend(/** @lends SymbolDefinition# */{ /** * Checks whether the symbol's definition is equal to the supplied symbol. * - * @param {Symbol} symbol + * @param {SymbolDefinition} symbol * @return {Boolean} {@true if they are equal} */ equals: function(symbol) { diff --git a/src/item/SymbolItem.js b/src/item/SymbolItem.js index 3e78646c..021cabbd 100644 --- a/src/item/SymbolItem.js +++ b/src/item/SymbolItem.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * @@ -31,7 +31,9 @@ var SymbolItem = Item.extend(/** @lends SymbolItem# */{ /** * Creates a new symbol item. * - * @param {Symbol} definition the symbol definition to place + * @name SymbolItem#initialize + * @param {SymbolDefinition|Item} definition the definition to place or an + * item to place as a symbol * @param {Point} [point] the center point of the placed symbol * * @example {@paperscript split=true height=240} @@ -119,7 +121,11 @@ var SymbolItem = Item.extend(/** @lends SymbolItem# */{ }, _hitTestSelf: function(point, options, viewMatrix) { - var res = this._definition._item._hitTest(point, options, viewMatrix); + // We need to call definition item hit test with `options.all = false`, + // as otherwise it would populate the array with its own matches. + // Instead we want only returning one match per symbol-item, see #1680 + var opts = options.extend({ all: false }); + var res = this._definition._item._hitTest(point, opts, viewMatrix); // TODO: When the symbol's definition is a path, should hitResult // contain information like HitResult#curve? if (res) diff --git a/src/load.js b/src/load.js index 59e08c7c..d4c6a471 100644 --- a/src/load.js +++ b/src/load.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * @@ -38,11 +38,10 @@ if (typeof window === 'object') { } else { // Some native javascript classes have name collisions with Paper.js // classes. Store them to be able to use them later in tests. - NativeClasses = { - Event: Event, - MouseEvent: MouseEvent + this.nativeClasses = { + Event: window.Event, + MouseEvent: window.MouseEvent }; - include('options.js'); // Load constants.js, required by the on-the-fly preprocessing: include('constants.js'); diff --git a/src/net/Http.js b/src/net/Http.js index 012f6b92..d043b762 100644 --- a/src/net/Http.js +++ b/src/net/Http.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * diff --git a/src/node/canvas.js b/src/node/canvas.js index fcb96540..ee325d7d 100644 --- a/src/node/canvas.js +++ b/src/node/canvas.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * @@ -18,7 +18,7 @@ module.exports = function(self, requireName) { var Canvas; try { - Canvas = require('canvas'); + Canvas = require('canvas').Canvas; } catch(error) { // Remove `self.window`, so we still have the global `self` reference, // but no `window` object: diff --git a/src/node/extend.js b/src/node/extend.js index d2e1ef66..e3c9af57 100644 --- a/src/node/extend.js +++ b/src/node/extend.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * diff --git a/src/node/self.js b/src/node/self.js index b6e47d8f..4fad478d 100644 --- a/src/node/self.js +++ b/src/node/self.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * @@ -37,15 +37,13 @@ try { if (jsdom) { // Create our document and window objects through jsdom. /* global document:true, window:true */ - var document = jsdom.jsdom('', { + var document = new jsdom.JSDOM('', { // Use the current working directory as the document's origin, so // requests to local files work correctly with CORS. url: 'file://' + process.cwd() + '/', - features: { - FetchExternalResources: ['img', 'script'] - } + resources: 'usable' }); - self = document.defaultView; + self = document.window; require('./canvas.js')(self, requireName); require('./xml.js')(self); } else { diff --git a/src/node/xml.js b/src/node/xml.js index 2a02ae7e..36331d00 100644 --- a/src/node/xml.js +++ b/src/node/xml.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * diff --git a/src/options.js b/src/options.js index 5ec47207..ee000df2 100644 --- a/src/options.js +++ b/src/options.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * @@ -17,7 +17,7 @@ // The paper.js version. // NOTE: Adjust value here before calling `gulp publish`, which then updates and // publishes the various JSON package files automatically. -var version = '0.11.8'; +var version = '0.12.7'; // If this file is loaded in the browser, we're in load.js mode. var load = typeof window === 'object'; diff --git a/src/paper.js b/src/paper.js index f2c6d327..dafcb72d 100644 --- a/src/paper.js +++ b/src/paper.js @@ -2,8 +2,8 @@ * Paper.js v*#=*__options.version - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * @@ -15,15 +15,15 @@ * * Straps.js - Class inheritance library with support for bean-style accessors * - * Copyright (c) 2006 - 2016 Juerg Lehni - * http://scratchdisk.com/ + * Copyright (c) 2006 - 2020 Jürg Lehni + * http://juerglehni.com/ * * Distributed under the MIT license. * *** * * Acorn.js - * http://marijnhaverbeke.nl/acorn/ + * https://marijnhaverbeke.nl/acorn/ * * Acorn is a tiny, fast JavaScript parser written in JavaScript, * created by Marijn Haverbeke and released under an MIT license. @@ -42,6 +42,7 @@ var paper = function(self, undefined) { /*#*/ include('core/PaperScope.js'); /*#*/ include('core/PaperScopeItem.js'); +/*#*/ include('util/CollisionDetection.js'); /*#*/ include('util/Formatter.js'); /*#*/ include('util/Numerical.js'); /*#*/ include('util/UID.js'); @@ -102,6 +103,8 @@ var paper = function(self, undefined) { /*#*/ include('tool/ToolEvent.js'); /*#*/ include('tool/Tool.js'); +/*#*/ include('anim/Tween.js'); + /*#*/ include('net/Http.js'); /*#*/ include('canvas/CanvasProvider.js'); diff --git a/src/path/CompoundPath.js b/src/path/CompoundPath.js index 115af3a9..9ee5c48b 100644 --- a/src/path/CompoundPath.js +++ b/src/path/CompoundPath.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * @@ -161,7 +161,7 @@ var CompoundPath = PathItem.extend(/** @lends CompoundPath# */{ * * @bean * @type Boolean - * @see Path#isClosed() + * @see Path#closed */ isClosed: function() { var children = this._children; @@ -193,7 +193,7 @@ var CompoundPath = PathItem.extend(/** @lends CompoundPath# */{ /** * The last Segment contained within the compound-path, a short-cut to - * calling {@link Path#lastChild} on {@link Item#lastChild}. + * calling {@link Path#lastSegment} on {@link Item#lastChild}. * * @bean * @type Segment diff --git a/src/path/Curve.js b/src/path/Curve.js index 1c5b6714..e0b173c8 100644 --- a/src/path/Curve.js +++ b/src/path/Curve.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * @@ -1476,7 +1476,7 @@ new function() { // Scope for methods that require private functions // 2: normal, 1st derivative // 3: curvature, 1st derivative & 2nd derivative // Prevent tangents and normals of length 0: - // http://stackoverflow.com/questions/10506868/ + // https://stackoverflow.com/questions/10506868/ if (t < tMin) { x = cx; y = cy; @@ -1698,7 +1698,7 @@ new function() { // Scope for methods that require private functions * Peaks are locations sharing some qualities of curvature extrema but * are cheaper to compute. They fulfill their purpose here quite well. * See: - * http://math.stackexchange.com/questions/1954845/bezier-curvature-extrema + * https://math.stackexchange.com/questions/1954845/bezier-curvature-extrema * * @param {Number[]} v the curve values array * @return {Number[]} the roots of all found peaks @@ -1822,9 +1822,10 @@ new function() { // Scope for bezier intersection using fat-line clipping } else { // Apply the result of the clipping to curve 1: v1 = Curve.getPart(v1, tMinClip, tMaxClip); + var uDiff = uMax - uMin; if (tMaxClip - tMinClip > 0.8) { // Subdivide the curve which has converged the least. - if (tMaxNew - tMinNew > uMax - uMin) { + if (tMaxNew - tMinNew > uDiff) { var parts = Curve.subdivide(v1, 0.5), t = (tMinNew + tMaxNew) / 2; calls = addCurveIntersections( @@ -1844,7 +1845,10 @@ new function() { // Scope for bezier intersection using fat-line clipping recursion, calls, u, uMax, tMinNew, tMaxNew); } } else { // Iterate - if (uMax - uMin >= fatLineEpsilon) { + // For some unclear reason we need to check against uDiff === 0 + // here, to prevent a regression from happening, see #1638. + // Maybe @iconexperience could shed some light on this. + if (uDiff === 0 || uDiff >= fatLineEpsilon) { calls = addCurveIntersections( v2, v1, c2, c1, locations, include, !flip, recursion, calls, uMin, uMax, tMinNew, tMaxNew); @@ -2087,7 +2091,7 @@ new function() { // Scope for bezier intersection using fat-line clipping return locations; } - function getLoopIntersection(v1, c1, locations, include) { + function getSelfIntersection(v1, c1, locations, include) { var info = Curve.classify(v1); if (info.type === 'loop') { var roots = info.roots; @@ -2100,50 +2104,50 @@ new function() { // Scope for bezier intersection using fat-line clipping function getIntersections(curves1, curves2, include, matrix1, matrix2, _returnFirst) { - var self = !curves2; + var epsilon = /*#=*/Numerical.GEOMETRIC_EPSILON, + self = !curves2; if (self) curves2 = curves1; var length1 = curves1.length, length2 = curves2.length, - values2 = [], - arrays = [], - locations, - current; - // Cache values for curves2 as we re-iterate them for each in curves1. - for (var i = 0; i < length2; i++) - values2[i] = curves2[i].getValues(matrix2); + values1 = new Array(length1), + values2 = self ? values1 : new Array(length2), + locations = []; + for (var i = 0; i < length1; i++) { - var curve1 = curves1[i], - values1 = self ? values2[i] : curve1.getValues(matrix1), - path1 = curve1.getPath(); - // NOTE: Due to the nature of getCurveIntersections(), we use - // separate location arrays per path1, to make sure the circularity - // checks are not getting confused by locations on separate paths. - // The separate arrays are then flattened in the end. - if (path1 !== current) { - current = path1; - locations = []; - arrays.push(locations); - } - if (self) { - // First check for self-intersections within the same curve. - getLoopIntersection(values1, curve1, locations, include); - } - // Check for intersections with other curves. - // For self-intersection, we can start at i + 1 instead of 0. - for (var j = self ? i + 1 : 0; j < length2; j++) { - // There might be already one location from the above - // self-intersection check: - if (_returnFirst && locations.length) - return locations; - getCurveIntersections(values1, values2[j], curve1, curves2[j], - locations, include); + values1[i] = curves1[i].getValues(matrix1); + } + if (!self) { + for (var i = 0; i < length2; i++) { + values2[i] = curves2[i].getValues(matrix2); } } - // Flatten the list of location arrays to one array and return it. - locations = []; - for (var i = 0, l = arrays.length; i < l; i++) { - Base.push(locations, arrays[i]); + var boundsCollisions = CollisionDetection.findCurveBoundsCollisions( + values1, values2, epsilon); + for (var index1 = 0; index1 < length1; index1++) { + var curve1 = curves1[index1], + v1 = values1[index1]; + if (self) { + // First check for self-intersections within the same curve. + getSelfIntersection(v1, curve1, locations, include); + } + // Check for intersections with potentially intersecting curves. + var collisions1 = boundsCollisions[index1]; + if (collisions1) { + for (var j = 0; j < collisions1.length; j++) { + // There might be already one location from the above + // self-intersection check: + if (_returnFirst && locations.length) + return locations; + var index2 = collisions1[j]; + if (!self || index2 > index1) { + var curve2 = curves2[index2], + v2 = values2[index2]; + getCurveIntersections( + v1, v2, curve1, curve2, locations, include); + } + } + } } return locations; } @@ -2310,7 +2314,7 @@ new function() { // Scope for bezier intersection using fat-line clipping var v1 = this.getValues(), v2 = curve && curve !== this && curve.getValues(); return v2 ? getCurveIntersections(v1, v2, this, curve, []) - : getLoopIntersection(v1, this, []); + : getSelfIntersection(v1, this, []); }, statics: /** @lends Curve */{ diff --git a/src/path/CurveLocation.js b/src/path/CurveLocation.js index cd878d45..ca17fe7b 100644 --- a/src/path/CurveLocation.js +++ b/src/path/CurveLocation.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * @@ -57,13 +57,16 @@ var CurveLocation = Base.extend(/** @lends CurveLocation# */{ this._intersection = this._next = this._previous = null; }, - _setCurve: function(curve) { - var path = curve._path; - // We only store the path to verify versions for cachd values. + _setPath: function(path) { + // We only store the path to verify versions for cached values. // To ensure we use the right path (e.g. after splitting), we shall // always access the path on the result of getCurve(). this._path = path; this._version = path ? path._version : 0; + }, + + _setCurve: function(curve) { + this._setPath(curve._path); this._curve = curve; this._segment = null; // To be determined, see #getSegment() // Also store references to segment1 and segment2, in case path @@ -74,7 +77,14 @@ var CurveLocation = Base.extend(/** @lends CurveLocation# */{ }, _setSegment: function(segment) { - this._setCurve(segment.getCurve()); + var curve = segment.getCurve(); + if (curve) { + this._setCurve(curve); + } else { + this._setPath(segment._path); + this._segment1 = segment; + this._segment2 = null; + } this._segment = segment; this._time = segment === this._segment1 ? 0 : 1; // To avoid issues with imprecision in getCurve() / trySegment() @@ -147,7 +157,7 @@ var CurveLocation = Base.extend(/** @lends CurveLocation# */{ * The path that this locations is situated on. * * @bean - * @type Item + * @type Path */ getPath: function() { var curve = this.getCurve(); @@ -159,7 +169,7 @@ var CurveLocation = Base.extend(/** @lends CurveLocation# */{ * it is part of a {@link Path} item. * * @bean - * @type Index + * @type Number */ getIndex: function() { var curve = this.getCurve(); @@ -284,7 +294,7 @@ var CurveLocation = Base.extend(/** @lends CurveLocation# */{ * @bean * @type Number * @see Curve#getNearestLocation(point) - * @see Path#getNearestLocation(point) + * @see PathItem#getNearestLocation(point) */ getDistance: function() { return this._distance; @@ -424,9 +434,9 @@ var CurveLocation = Base.extend(/** @lends CurveLocation# */{ // both values point to the same curve, and the curve-time is to be // handled accordingly further down. var c2 = this.getCurve(), - c1 = t1 < tMin ? c2.getPrevious() : c2, + c1 = c2 && t1 < tMin ? c2.getPrevious() : c2, c4 = inter.getCurve(), - c3 = t2 < tMin ? c4.getPrevious() : c4; + c3 = c4 && t2 < tMin ? c4.getPrevious() : c4; // If t1 / t2 are at the end, then step to the next curve. if (t1 > tMax) c2 = c2.getNext(); @@ -450,11 +460,12 @@ var CurveLocation = Base.extend(/** @lends CurveLocation# */{ var v = curve.getValues(), roots = Curve.classify(v).roots || Curve.getPeaks(v), count = roots.length, - t = end && count > 1 ? roots[count - 1] - : count > 0 ? roots[0] - : 0.5; - // Then use half of the offset, for extra measure. - offsets.push(Curve.getLength(v, end ? t : 0, end ? 1 : t) / 2); + offset = Curve.getLength(v, + end && count ? roots[count - 1] : 0, + !end && count ? roots[0] : 1); + // When no root was found, the full length was calculated. Use a + // fraction of it. By trial & error, 64 was determined to work well. + offsets.push(count ? offset : offset / 32); } function isInRange(angle, min, max) { @@ -491,7 +502,7 @@ var CurveLocation = Base.extend(/** @lends CurveLocation# */{ // Count how many times curve2 angles appear between the curve1 angles. // If each pair of angles split the other two, then the edges cross. // Use t1Inside to decide which angle pair to check against. - // If t1 is inside the curve, check against a3 & a4, othrwise a1 & a2. + // If t1 is inside the curve, check against a3 & a4, otherwise a1 & a2. return !!(t1Inside ? (isInRange(a1, a3, a4) ^ isInRange(a2, a3, a4)) && (isInRange(a1, a4, a3) ^ isInRange(a2, a4, a3)) diff --git a/src/path/Path.Constructors.js b/src/path/Path.Constructors.js index 3cc23b9d..858d3e06 100644 --- a/src/path/Path.Constructors.js +++ b/src/path/Path.Constructors.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * @@ -79,10 +79,11 @@ Path.inject({ statics: new function() { * }); */ Line: function(/* from, to */) { + var args = arguments; return createPath([ - new Segment(Point.readNamed(arguments, 'from')), - new Segment(Point.readNamed(arguments, 'to')) - ], false, arguments); + new Segment(Point.readNamed(args, 'from')), + new Segment(Point.readNamed(args, 'to')) + ], false, args); }, /** @@ -114,9 +115,10 @@ Path.inject({ statics: new function() { * }); */ Circle: function(/* center, radius */) { - var center = Point.readNamed(arguments, 'center'), - radius = Base.readNamed(arguments, 'radius'); - return createEllipse(center, new Size(radius), arguments); + var args = arguments, + center = Point.readNamed(args, 'center'), + radius = Base.readNamed(args, 'radius'); + return createEllipse(center, new Size(radius), args); }, /** @@ -210,8 +212,9 @@ Path.inject({ statics: new function() { * }); */ Rectangle: function(/* rectangle */) { - var rect = Rectangle.readNamed(arguments, 'rectangle'), - radius = Size.readNamed(arguments, 'radius', 0, + var args = arguments, + rect = Rectangle.readNamed(args, 'rectangle'), + radius = Size.readNamed(args, 'radius', 0, { readNull: true }), bl = rect.getBottomLeft(true), tl = rect.getTopLeft(true), @@ -242,7 +245,7 @@ Path.inject({ statics: new function() { new Segment(br.subtract(rx, 0), [hx, 0]) ]; } - return createPath(segments, true, arguments); + return createPath(segments, true, args); }, /** @@ -286,8 +289,9 @@ Path.inject({ statics: new function() { * }); */ Ellipse: function(/* rectangle */) { - var ellipse = Shape._readEllipse(arguments); - return createEllipse(ellipse.center, ellipse.radius, arguments); + var args = arguments, + ellipse = Shape._readEllipse(args); + return createEllipse(ellipse.center, ellipse.radius, args); }, /** @@ -330,10 +334,11 @@ Path.inject({ statics: new function() { * }); */ Arc: function(/* from, through, to */) { - var from = Point.readNamed(arguments, 'from'), - through = Point.readNamed(arguments, 'through'), - to = Point.readNamed(arguments, 'to'), - props = Base.getNamed(arguments), + var args = arguments, + from = Point.readNamed(args, 'from'), + through = Point.readNamed(args, 'through'), + to = Point.readNamed(args, 'to'), + props = Base.getNamed(args), // See createPath() for an explanation of the following sequence path = new Path(props && props.insert == false && Item.NO_INSERT); @@ -376,9 +381,10 @@ Path.inject({ statics: new function() { * }); */ RegularPolygon: function(/* center, sides, radius */) { - var center = Point.readNamed(arguments, 'center'), - sides = Base.readNamed(arguments, 'sides'), - radius = Base.readNamed(arguments, 'radius'), + var args = arguments, + center = Point.readNamed(args, 'center'), + sides = Base.readNamed(args, 'sides'), + radius = Base.readNamed(args, 'radius'), step = 360 / sides, three = sides % 3 === 0, vector = new Point(0, three ? -radius : radius), @@ -387,7 +393,7 @@ Path.inject({ statics: new function() { for (var i = 0; i < sides; i++) segments[i] = new Segment(center.add( vector.rotate((i + offset) * step))); - return createPath(segments, true, arguments); + return createPath(segments, true, args); }, /** @@ -431,17 +437,18 @@ Path.inject({ statics: new function() { * }); */ Star: function(/* center, points, radius1, radius2 */) { - var center = Point.readNamed(arguments, 'center'), - points = Base.readNamed(arguments, 'points') * 2, - radius1 = Base.readNamed(arguments, 'radius1'), - radius2 = Base.readNamed(arguments, 'radius2'), + var args = arguments, + center = Point.readNamed(args, 'center'), + points = Base.readNamed(args, 'points') * 2, + radius1 = Base.readNamed(args, 'radius1'), + radius2 = Base.readNamed(args, 'radius2'), step = 360 / points, vector = new Point(0, -1), segments = new Array(points); for (var i = 0; i < points; i++) segments[i] = new Segment(center.add(vector.rotate(step * i) .multiply(i % 2 ? radius2 : radius1))); - return createPath(segments, true, arguments); + return createPath(segments, true, args); } }; }}); diff --git a/src/path/Path.js b/src/path/Path.js index e188bee4..19848a9c 100644 --- a/src/path/Path.js +++ b/src/path/Path.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * @@ -75,7 +75,6 @@ var Path = PathItem.extend(/** @lends Path# */{ * Creates a new path item from SVG path-data and places it at the top of * the active layer. * - * @param * @name Path#initialize * @param {String} pathData the SVG path-data that describes the geometry * of this path @@ -99,15 +98,16 @@ var Path = PathItem.extend(/** @lends Path# */{ // check its first entry for object as well. // But first see if segments are directly passed at all. If not, try // _set(arg). - var segments = Array.isArray(arg) + var args = arguments, + segments = Array.isArray(arg) ? typeof arg[0] === 'object' ? arg - : arguments + : args // See if it behaves like a segment or a point, but filter out // rectangles, as accepted by some Path.Constructor constructors. : arg && (arg.size === undefined && (arg.x !== undefined || arg.point !== undefined)) - ? arguments + ? args : null; // Always call setSegments() to initialize a few related variables. if (segments && segments.length > 0) { @@ -484,9 +484,11 @@ var Path = PathItem.extend(/** @lends Path# */{ * Adds one or more segments to the end of the {@link #segments} array of * this path. * - * @param {Segment|Point} segment the segment or point to be added. - * @return {Segment} the added segment. This is not necessarily the same - * object, e.g. if the segment to be added already belongs to another path + * @param {...(Segment|Point|Number[])} segment the segment or point to be + * added. + * @return {Segment|Segment[]} the added segment(s). This is not necessarily + * the same object, e.g. if the segment to be added already belongs to + * another path. * * @example {@paperscript} * // Adding segments to a path using point objects: @@ -547,11 +549,12 @@ var Path = PathItem.extend(/** @lends Path# */{ * path.add(new Point(170, 75)); */ add: function(segment1 /*, segment2, ... */) { - return arguments.length > 1 && typeof segment1 !== 'number' + var args = arguments; + return args.length > 1 && typeof segment1 !== 'number' // addSegments - ? this._add(Segment.readList(arguments)) + ? this._add(Segment.readList(args)) // addSegment - : this._add([ Segment.read(arguments) ])[0]; + : this._add([ Segment.read(args) ])[0]; }, /** @@ -591,11 +594,12 @@ var Path = PathItem.extend(/** @lends Path# */{ * myPath.segments[2].selected = true; */ insert: function(index, segment1 /*, segment2, ... */) { - return arguments.length > 2 && typeof segment1 !== 'number' + var args = arguments; + return args.length > 2 && typeof segment1 !== 'number' // insertSegments - ? this._add(Segment.readList(arguments, 1), index) + ? this._add(Segment.readList(args, 1), index) // insertSegment - : this._add([ Segment.read(arguments, 1) ], index)[0]; + : this._add([ Segment.read(args, 1) ], index)[0]; }, addSegment: function(/* segment */) { @@ -1215,6 +1219,8 @@ var Path = PathItem.extend(/** @lends Path# */{ /** * Reduces the path by removing curves that have a length of 0, * and unnecessary segments between two collinear flat curves. + * + * @return {Path} the reduced path */ reduce: function(options) { var curves = this.getCurves(), @@ -2173,9 +2179,9 @@ new function() { // Scope for drawing // performance. function drawHandles(ctx, segments, matrix, size, isFullySelected) { - if (size === 0) { - return; - } + // Only draw if size is not null or negative. + if (size <= 0) return; + var half = size / 2, coords = new Array(6), pX, pY; @@ -2402,9 +2408,10 @@ new function() { // PostScript-style drawing commands }, cubicCurveTo: function(/* handle1, handle2, to */) { - var handle1 = Point.read(arguments), - handle2 = Point.read(arguments), - to = Point.read(arguments), + var args = arguments, + handle1 = Point.read(args), + handle2 = Point.read(args), + to = Point.read(args), // First modify the current segment: current = getCurrentSegment(this); // Convert to relative values: @@ -2414,8 +2421,9 @@ new function() { // PostScript-style drawing commands }, quadraticCurveTo: function(/* handle, to */) { - var handle = Point.read(arguments), - to = Point.read(arguments), + var args = arguments, + handle = Point.read(args), + to = Point.read(args), current = getCurrentSegment(this)._point; // This is exact: // If we have the three quad points: A E D, @@ -2430,9 +2438,10 @@ new function() { // PostScript-style drawing commands }, curveTo: function(/* through, to, time */) { - var through = Point.read(arguments), - to = Point.read(arguments), - t = Base.pick(Base.read(arguments), 0.5), + var args = arguments, + through = Point.read(args), + to = Point.read(args), + t = Base.pick(Base.read(args), 0.5), t1 = 1 - t, current = getCurrentSegment(this)._point, // handle = (through - (1 - t)^2 * current - t^2 * to) / @@ -2448,15 +2457,16 @@ new function() { // PostScript-style drawing commands arcTo: function(/* to, clockwise | through, to | to, radius, rotation, clockwise, large */) { // Get the start point: - var abs = Math.abs, + var args = arguments, + abs = Math.abs, sqrt = Math.sqrt, current = getCurrentSegment(this), from = current._point, - to = Point.read(arguments), + to = Point.read(args), through, // Peek at next value to see if it's clockwise, with true as the // default value. - peek = Base.peek(arguments), + peek = Base.peek(args), clockwise = Base.pick(peek, true), center, extent, vector, matrix; // We're handling three different approaches to drawing arcs in one @@ -2466,14 +2476,15 @@ new function() { // PostScript-style drawing commands var middle = from.add(to).divide(2), through = middle.add(middle.subtract(from).rotate( clockwise ? -90 : 90)); - } else if (Base.remain(arguments) <= 2) { + } else if (Base.remain(args) <= 2) { // #2: arcTo(through, to) through = to; - to = Point.read(arguments); - } else { + to = Point.read(args); + } else if (!from.equals(to)) { // #3: arcTo(to, radius, rotation, clockwise, large) - // Drawing arcs in SVG style: - var radius = Size.read(arguments), + // Draw arc in SVG style, but only if `from` and `to` are not + // equal (#1613). + var radius = Size.read(args), isZero = Numerical.isZero; // If rx = 0 or ry = 0 then this arc is treated as a // straight line joining the endpoints. @@ -2481,10 +2492,10 @@ new function() { // PostScript-style drawing commands if (isZero(radius.width) || isZero(radius.height)) return this.lineTo(to); // See for an explanation of the following calculations: - // http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes - var rotation = Base.read(arguments), - clockwise = !!Base.read(arguments), - large = !!Base.read(arguments), + // https://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes + var rotation = Base.read(args), + clockwise = !!Base.read(args), + large = !!Base.read(args), middle = from.add(to).divide(2), pt = from.subtract(middle).rotate(-rotation), x = pt.x, @@ -2568,47 +2579,49 @@ new function() { // PostScript-style drawing commands extent += extent < 0 ? 360 : -360; } } - var epsilon = /*#=*/Numerical.GEOMETRIC_EPSILON, - ext = abs(extent), - // Calculate the amount of segments required to approximate over - // `extend` degrees (extend / 90), but prevent ceil() from - // rounding up small imprecisions by subtracting epsilon first. - count = ext >= 360 ? 4 : Math.ceil((ext - epsilon) / 90), - inc = extent / count, - half = inc * Math.PI / 360, - z = 4 / 3 * Math.sin(half) / (1 + Math.cos(half)), - segments = []; - for (var i = 0; i <= count; i++) { - // Explicitly use to point for last segment, since depending - // on values the calculation adds imprecision: - var pt = to, - out = null; - if (i < count) { - out = vector.rotate(90).multiply(z); - if (matrix) { - pt = matrix._transformPoint(vector); - out = matrix._transformPoint(vector.add(out)) - .subtract(pt); + if (extent) { + var epsilon = /*#=*/Numerical.GEOMETRIC_EPSILON, + ext = abs(extent), + // Calculate amount of segments required to approximate over + // `extend` degrees (extend / 90), but prevent ceil() from + // rounding up small imprecisions by subtracting epsilon. + count = ext >= 360 ? 4 : Math.ceil((ext - epsilon) / 90), + inc = extent / count, + half = inc * Math.PI / 360, + z = 4 / 3 * Math.sin(half) / (1 + Math.cos(half)), + segments = []; + for (var i = 0; i <= count; i++) { + // Explicitly use to point for last segment, since depending + // on values the calculation adds imprecision: + var pt = to, + out = null; + if (i < count) { + out = vector.rotate(90).multiply(z); + if (matrix) { + pt = matrix._transformPoint(vector); + out = matrix._transformPoint(vector.add(out)) + .subtract(pt); + } else { + pt = center.add(vector); + } + } + if (!i) { + // Modify startSegment + current.setHandleOut(out); } else { - pt = center.add(vector); + // Add new Segment + var _in = vector.rotate(-90).multiply(z); + if (matrix) { + _in = matrix._transformPoint(vector.add(_in)) + .subtract(pt); + } + segments.push(new Segment(pt, _in, out)); } + vector = vector.rotate(inc); } - if (!i) { - // Modify startSegment - current.setHandleOut(out); - } else { - // Add new Segment - var _in = vector.rotate(-90).multiply(z); - if (matrix) { - _in = matrix._transformPoint(vector.add(_in)) - .subtract(pt); - } - segments.push(new Segment(pt, _in, out)); - } - vector = vector.rotate(inc); + // Add all segments at once at the end for higher performance + this._add(segments); } - // Add all segments at once at the end for higher performance - this._add(segments); }, lineBy: function(/* to */) { @@ -2618,40 +2631,44 @@ new function() { // PostScript-style drawing commands }, curveBy: function(/* through, to, parameter */) { - var through = Point.read(arguments), - to = Point.read(arguments), - parameter = Base.read(arguments), + var args = arguments, + through = Point.read(args), + to = Point.read(args), + parameter = Base.read(args), current = getCurrentSegment(this)._point; this.curveTo(current.add(through), current.add(to), parameter); }, cubicCurveBy: function(/* handle1, handle2, to */) { - var handle1 = Point.read(arguments), - handle2 = Point.read(arguments), - to = Point.read(arguments), + var args = arguments, + handle1 = Point.read(args), + handle2 = Point.read(args), + to = Point.read(args), current = getCurrentSegment(this)._point; this.cubicCurveTo(current.add(handle1), current.add(handle2), current.add(to)); }, quadraticCurveBy: function(/* handle, to */) { - var handle = Point.read(arguments), - to = Point.read(arguments), + var args = arguments, + handle = Point.read(args), + to = Point.read(args), current = getCurrentSegment(this)._point; this.quadraticCurveTo(current.add(handle), current.add(to)); }, // TODO: Implement version for: (to, radius, rotation, clockwise, large) arcBy: function(/* to, clockwise | through, to */) { - var current = getCurrentSegment(this)._point, - point = current.add(Point.read(arguments)), + var args = arguments, + current = getCurrentSegment(this)._point, + point = current.add(Point.read(args)), // Peek at next value to see if it's clockwise, with true as // default value. - clockwise = Base.pick(Base.peek(arguments), true); + clockwise = Base.pick(Base.peek(args), true); if (typeof clockwise === 'boolean') { this.arcTo(point, clockwise); } else { - this.arcTo(point, current.add(Point.read(arguments))); + this.arcTo(point, current.add(Point.read(args))); } }, @@ -2780,15 +2797,18 @@ statics: { } var length = segments.length - (closed ? 0 : 1); - for (var i = 1; i < length; i++) - addJoin(segments[i], join); - if (closed) { - // Go back to the beginning - addJoin(segments[0], join); - } else if (length > 0) { - // Handle caps on open paths - addCap(segments[0], cap); - addCap(segments[segments.length - 1], cap); + if (length > 0) { + for (var i = 1; i < length; i++) { + addJoin(segments[i], join); + } + if (closed) { + // Go back to the beginning + addJoin(segments[0], join); + } else { + // Handle caps on open paths + addCap(segments[0], cap); + addCap(segments[segments.length - 1], cap); + } } return bounds; }, @@ -2846,8 +2866,9 @@ statics: { normal1 = curve1.getNormalAtTime(1).multiply(radius) .transform(strokeMatrix), normal2 = curve2.getNormalAtTime(0).multiply(radius) - .transform(strokeMatrix); - if (normal1.getDirectedAngle(normal2) < 0) { + .transform(strokeMatrix), + angle = normal1.getDirectedAngle(normal2); + if (angle < 0 || angle >= 180) { normal1 = normal1.negate(); normal2 = normal2.negate(); } diff --git a/src/path/PathFitter.js b/src/path/PathFitter.js index b9d10300..8444a811 100644 --- a/src/path/PathFitter.js +++ b/src/path/PathFitter.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * @@ -13,7 +13,7 @@ // An Algorithm for Automatically Fitting Digitized Curves // by Philip J. Schneider // from "Graphics Gems", Academic Press, 1990 -// Modifications and optimizations of original algorithm by Juerg Lehni. +// Modifications and optimizations of original algorithm by Jürg Lehni. /** * @name PathFitter @@ -231,7 +231,7 @@ var PathFitter = Base.extend({ diff = pt.subtract(point), df = pt1.dot(pt1) + diff.dot(pt2); // u = u - f(u) / f'(u) - return Numerical.isZero(df) ? u : u - diff.dot(pt1) / df; + return Numerical.isMachineZero(df) ? u : u - diff.dot(pt1) / df; }, // Evaluate a bezier curve at a particular parameter value diff --git a/src/path/PathFlattener.js b/src/path/PathFlattener.js index 1df208a4..ac28d8f7 100644 --- a/src/path/PathFlattener.js +++ b/src/path/PathFlattener.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * diff --git a/src/path/PathItem.Boolean.js b/src/path/PathItem.Boolean.js index f4f098e4..44867f67 100644 --- a/src/path/PathItem.Boolean.js +++ b/src/path/PathItem.Boolean.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * @@ -23,9 +23,8 @@ * - Boolean operations on self-intersecting Paths items * * @author Harikrishnan Gopalakrishnan - * @author Jan Boesenberg - * @author Juerg Lehni - * http://hkrish.com/playground/paperjs/booleanStudy.html + * @author Jan Boesenberg + * @author Jürg Lehni */ PathItem.inject(new function() { var min = Math.min, @@ -46,6 +45,10 @@ PathItem.inject(new function() { exclude: { '1': true, '-1': true } }; + function getPaths(path) { + return path._children || [path]; + } + /* * Creates a clone of the path that we can modify freely, with its matrix * applied to its geometry. Calls #reduce() to simplify compound paths and @@ -53,12 +56,28 @@ PathItem.inject(new function() { * make sure all paths have correct winding direction. */ function preparePath(path, resolve) { - var res = path.clone(false).reduce({ simplify: true }) - .transform(null, true, true); - return resolve - ? res.resolveCrossings().reorient( - res.getFillRule() === 'nonzero', true) - : res; + var res = path + .clone(false) + .reduce({ simplify: true }) + .transform(null, true, true); + if (resolve) { + // For correct results, close open paths with straight lines: + var paths = getPaths(res); + for (var i = 0, l = paths.length; i < l; i++) { + var path = paths[i]; + if (!path._closed && !path.isEmpty()) { + // Close with epsilon tolerance, to avoid tiny straight + // that would cause issues with intersection detection. + path.closePath(/*#=*/Numerical.EPSILON); + path.getFirstSegment().setHandleIn(0, 0); + path.getLastSegment().setHandleOut(0, 0); + } + } + res = res + .resolveCrossings() + .reorient(res.getFillRule() === 'nonzero', true); + } + return res; } function createResult(paths, simplify, path1, path2, options) { @@ -77,6 +96,17 @@ PathItem.inject(new function() { return result; } + function filterIntersection(inter) { + // TODO: Change isCrossing() to also handle overlaps (hasOverlap()) + // that are actually involved in a crossing! For this we need proper + // overlap range detection / merging first... But as we call + // #resolveCrossings() first in boolean operations, removing all + // self-touching areas in paths, this works for the known use cases. + // The ideal implementation would deal with it in a way outlined in: + // https://github.com/paperjs/paper.js/issues/874#issuecomment-168332391 + return inter.hasOverlap() || inter.isCrossing(); + } + function traceBoolean(path1, path2, operation, options) { // Only support subtract and intersect operations when computing stroke // based boolean operations (options.split = true). @@ -101,15 +131,15 @@ PathItem.inject(new function() { _path2.reverse(); // Split curves at crossings on both paths. Note that for self- // intersection, path2 is null and getIntersections() handles it. - var crossings = divideLocations( - CurveLocation.expand(_path1.getCrossings(_path2))), - paths1 = _path1._children || [_path1], - paths2 = _path2 && (_path2._children || [_path2]), + var crossings = divideLocations(CurveLocation.expand( + _path1.getIntersections(_path2, filterIntersection))), + paths1 = getPaths(_path1), + paths2 = _path2 && getPaths(_path2), segments = [], curves = [], paths; - function collect(paths) { + function collectPaths(paths) { for (var i = 0, l = paths.length; i < l; i++) { var path = paths[i]; Base.push(segments, path._segments); @@ -120,24 +150,51 @@ PathItem.inject(new function() { } } + function getCurves(indices) { + var list = []; + for (var i = 0, l = indices && indices.length; i < l; i++) { + list.push(curves[indices[i]]); + } + return list; + } + if (crossings.length) { // Collect all segments and curves of both involved operands. - collect(paths1); + collectPaths(paths1); if (paths2) - collect(paths2); + collectPaths(paths2); + + var curvesValues = new Array(curves.length); + for (var i = 0, l = curves.length; i < l; i++) { + curvesValues[i] = curves[i].getValues(); + } + var curveCollisions = CollisionDetection.findCurveBoundsCollisions( + curvesValues, curvesValues, 0, true); + var curveCollisionsMap = {}; + for (var i = 0; i < curves.length; i++) { + var curve = curves[i], + id = curve._path._id, + map = curveCollisionsMap[id] = curveCollisionsMap[id] || {}; + map[curve.getIndex()] = { + hor: getCurves(curveCollisions[i].hor), + ver: getCurves(curveCollisions[i].ver) + }; + } + // Propagate the winding contribution. Winding contribution of // curves does not change between two crossings. // First, propagate winding contributions for curve chains starting // in all crossings: for (var i = 0, l = crossings.length; i < l; i++) { - propagateWinding(crossings[i]._segment, _path1, _path2, curves, - operator); + propagateWinding(crossings[i]._segment, _path1, _path2, + curveCollisionsMap, operator); } for (var i = 0, l = segments.length; i < l; i++) { var segment = segments[i], inter = segment._intersection; if (!segment._winding) { - propagateWinding(segment, _path1, _path2, curves, operator); + propagateWinding(segment, _path1, _path2, + curveCollisionsMap, operator); } // See if all encountered segments in a path are overlaps. if (!(inter && inter._overlap)) @@ -155,14 +212,13 @@ PathItem.inject(new function() { return !!operator[w]; }); } - return createResult(paths, true, path1, path2, options); } function splitBoolean(path1, path2, operation) { var _path1 = preparePath(path1), _path2 = preparePath(path2), - crossings = _path1.getCrossings(_path2), + crossings = _path1.getIntersections(_path2, filterIntersection), subtract = operation === 'subtract', divide = operation === 'divide', added = {}, @@ -269,29 +325,39 @@ PathItem.inject(new function() { // Get reference to the first, largest path and insert it // already. first = sorted[0]; + // create lookup containing potentially overlapping path bounds + var collisions = CollisionDetection.findItemBoundsCollisions(sorted, + null, Numerical.GEOMETRIC_EPSILON); if (clockwise == null) clockwise = first.isClockwise(); // Now determine the winding for each path, from large to small. for (var i = 0; i < length; i++) { var path1 = sorted[i], entry1 = lookup[path1._id], - point = path1.getInteriorPoint(), - containerWinding = 0; - for (var j = i - 1; j >= 0; j--) { - var path2 = sorted[j]; - // As we run through the paths from largest to smallest, for - // any current path, all potentially containing paths have - // already been processed and their orientation fixed. - // To achieve correct orientation of contained paths based - // on winding, we have to find one containing path with - // different "insideness" and set opposite orientation. - if (path2.contains(point)) { - var entry2 = lookup[path2._id]; - containerWinding = entry2.winding; - entry1.winding += containerWinding; - entry1.container = entry2.exclude ? entry2.container - : path2; - break; + containerWinding = 0, + indices = collisions[i]; + if (indices) { + var point = null; // interior point, only get it if required. + for (var j = indices.length - 1; j >= 0; j--) { + if (indices[j] < i) { + point = point || path1.getInteriorPoint(); + var path2 = sorted[indices[j]]; + // As we run through the paths from largest to + // smallest, for any current path, all potentially + // containing paths have already been processed and + // their orientation fixed. To achieve correct + // orientation of contained paths based on winding, + // find one containing path with different + // "insideness" and set opposite orientation. + if (path2.contains(point)) { + var entry2 = lookup[path2._id]; + containerWinding = entry2.winding; + entry1.winding += containerWinding; + entry1.container = entry2.exclude + ? entry2.container : path2; + break; + } + } } } // Only keep paths if the "insideness" changes when crossing the @@ -306,8 +372,8 @@ PathItem.inject(new function() { // If the containing path is not excluded, we're done // searching for the orientation defining path. var container = entry1.container; - path1.setClockwise(container ? !container.isClockwise() - : clockwise); + path1.setClockwise( + container ? !container.isClockwise() : clockwise); } } } @@ -452,9 +518,9 @@ PathItem.inject(new function() { * * @param {Point} point the location for which to determine the winding * contribution - * @param {Curve[]} curves the curves that describe the shape against which + * @param {Curve[]} curves The curves that describe the shape against which * to check, as returned by {@link Path#curves} or - * {@link CompoundPath#curves} + * {@link CompoundPath#curves}. * @param {Boolean} [dir=false] the direction in which to determine the * winding contribution, `false`: in x-direction, `true`: in y-direction * @param {Boolean} [closed=false] determines how areas should be closed @@ -468,6 +534,13 @@ PathItem.inject(new function() { * @private */ function getWinding(point, curves, dir, closed, dontFlip) { + // `curves` can either be an array of curves, or an object containing of + // the form `{ hor: [], ver: [] }` (see `curveCollisionsMap`), with each + // key / value pair holding only those curves that can be crossed by a + // horizontal / vertical line through the point to be checked. + var curvesList = Array.isArray(curves) + ? curves + : curves[dir ? 'hor' : 'ver']; // Determine the index of the abscissa and ordinate values in the curve // values arrays, based on the direction: var ia = dir ? 1 : 0, // the abscissa index @@ -573,9 +646,7 @@ PathItem.inject(new function() { onPath = true; } } - // TODO: Determine how to handle quality when curve is crossed - // at starting point. Do we always need to set to 0? - quality = 0; + quality /= 4; } vPrev = v; // If we're on the curve, look at the tangent to decide whether to @@ -615,12 +686,12 @@ PathItem.inject(new function() { } } - for (var i = 0, l = curves.length; i < l; i++) { - var curve = curves[i], + for (var i = 0, l = curvesList.length; i < l; i++) { + var curve = curvesList[i], path = curve._path, v = curve.getValues(), res; - if (!i || curves[i - 1]._path !== path) { + if (!i || curvesList[i - 1]._path !== path) { // We're on a new (sub-)path, so we need to determine values of // the last non-horizontal curve on this path. vPrev = null; @@ -664,7 +735,7 @@ PathItem.inject(new function() { if (res = handleCurve(v)) return res; - if (i + 1 === l || curves[i + 1]._path !== path) { + if (i + 1 === l || curvesList[i + 1]._path !== path) { // We're at the last curve of the current (sub-)path. If a // closing curve was calculated at the beginning of it, handle // it now to treat the path as closed: @@ -705,7 +776,8 @@ PathItem.inject(new function() { }; } - function propagateWinding(segment, path1, path2, curves, operator) { + function propagateWinding(segment, path1, path2, curveCollisionsMap, + operator) { // Here we try to determine the most likely winding number contribution // for the curve-chain starting with this segment. Once we have enough // confidence in the winding contribution, we can propagate it until the @@ -715,10 +787,14 @@ PathItem.inject(new function() { totalLength = 0, winding; do { - var curve = segment.getCurve(), - length = curve.getLength(); - chain.push({ segment: segment, curve: curve, length: length }); - totalLength += length; + var curve = segment.getCurve(); + // We can encounter paths with only one segment, which would not + // have a curve. + if (curve) { + var length = curve.getLength(); + chain.push({ segment: segment, curve: curve, length: length }); + totalLength += length; + } segment = segment.getNext(); } while (segment && !segment._intersection && segment !== start); // Determine winding at three points in the chain. If a winding with @@ -726,7 +802,8 @@ PathItem.inject(new function() { // the best quality. var offsets = [0.5, 0.25, 0.75], winding = { winding: 0, quality: -1 }, - tMin = /*#=*/Numerical.CURVETIME_EPSILON, + // Don't go too close to segments, to avoid special winding cases: + tMin = 1e-3, tMax = 1 - tMin; for (var i = 0; i < offsets.length && winding.quality < 0.5; i++) { var length = totalLength * offsets[i]; @@ -751,9 +828,8 @@ PathItem.inject(new function() { var wind = null; if (operator.subtract && path2) { // Calculate path winding at point depending on operand. - var pathWinding = operand === path1 - ? path2._getWinding(pt, dir, true) - : path1._getWinding(pt, dir, true); + var otherPath = operand === path1 ? path2 : path1, + pathWinding = otherPath._getWinding(pt, dir, true); // Check if curve should be omitted. if (operand === path1 && pathWinding.winding || operand === path2 && !pathWinding.winding) { @@ -767,7 +843,9 @@ PathItem.inject(new function() { } } } - wind = wind || getWinding(pt, curves, dir, true); + wind = wind || getWinding( + pt, curveCollisionsMap[path._id][curve.getIndex()], + dir, true); if (wind.quality > winding.quality) winding = wind; break; @@ -865,8 +943,8 @@ PathItem.inject(new function() { collect(inter); // Find the beginning of the linked intersections and loop all // the way back to start, to collect all valid intersections. - while (inter && inter._prev) - inter = inter._prev; + while (inter && inter._previous) + inter = inter._previous; collect(inter, start); } return crossings; @@ -1125,7 +1203,7 @@ PathItem.inject(new function() { /** * Splits the geometry of this path along the geometry of the specified * path returns the result as a new group item. This is equivalent to - * calling {@link #subtract(path)} and {@link #subtract(path)} and + * calling {@link #subtract(path)} and {@link #intersect(path)} and * putting the results into a new group. * * @option [options.insert=true] {Boolean} whether the resulting item @@ -1156,7 +1234,7 @@ PathItem.inject(new function() { * amount of resulting paths allows so, otherwise a new path / * compound-path is created, replacing the current one. * - * @return {PahtItem} the resulting path item + * @return {PathItem} the resulting path item */ resolveCrossings: function() { var children = this._children, @@ -1168,7 +1246,7 @@ PathItem.inject(new function() { return inter && inter._overlap && inter._path === path; } - // First collect all overlaps and crossings while taking not of the + // First collect all overlaps and crossings while taking note of the // existence of both. var hasOverlaps = false, hasCrossings = false, @@ -1278,7 +1356,7 @@ PathItem.inject(new function() { * @param {Boolean} [clockwise] if provided, the orientation of the root * paths will be set to the orientation specified by `clockwise`, * otherwise the orientation of the largest root child is used. - * @return {PahtItem} a reference to the item itself, reoriented + * @return {PathItem} a reference to the item itself, reoriented */ reorient: function(nonZero, clockwise) { var children = this._children; diff --git a/src/path/PathItem.js b/src/path/PathItem.js index ed9037c1..255e1aaf 100644 --- a/src/path/PathItem.js +++ b/src/path/PathItem.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * @@ -102,8 +102,8 @@ var PathItem = Item.extend(/** @lends PathItem# */{ * * @bean * @type Boolean - * @see Path#getArea() - * @see CompoundPath#getArea() + * @see Path#area + * @see CompoundPath#area */ isClockwise: function() { return this.getArea() >= 0; @@ -345,18 +345,13 @@ var PathItem = Item.extend(/** @lends PathItem# */{ * crossing each other, as opposed to simply touching. * * @param {PathItem} path the other item to find the crossings with + * @return {CurveLocation[]} the locations of all crossings between the + * paths * @see #getIntersections(path) */ getCrossings: function(path) { return this.getIntersections(path, function(inter) { - // TODO: Only return overlaps that are actually crossings! For this - // we need proper overlap range detection / merging first... - // But as we call #resolveCrossings() first in boolean operations, - // removing all self-touching areas in paths, this currently works - // as it should in the known use cases. - // The ideal implementation would deal with it in a way outlined in: - // https://github.com/paperjs/paper.js/issues/874#issuecomment-168332391 - return inter.hasOverlap() || inter.isCrossing(); + return inter.isCrossing(); }); }, @@ -727,16 +722,20 @@ var PathItem = Item.extend(/** @lends PathItem# */{ matched = [], count = 0; ok = true; + var boundsOverlaps = CollisionDetection.findItemBoundsCollisions(paths1, paths2, Numerical.GEOMETRIC_EPSILON); for (var i1 = length1 - 1; i1 >= 0 && ok; i1--) { var path1 = paths1[i1]; ok = false; - for (var i2 = length2 - 1; i2 >= 0 && !ok; i2--) { - if (path1.compare(paths2[i2])) { - if (!matched[i2]) { - matched[i2] = true; - count++; + var pathBoundsOverlaps = boundsOverlaps[i1]; + if (pathBoundsOverlaps) { + for (var i2 = pathBoundsOverlaps.length - 1; i2 >= 0 && !ok; i2--) { + if (path1.compare(paths2[pathBoundsOverlaps[i2]])) { + if (!matched[pathBoundsOverlaps[i2]]) { + matched[pathBoundsOverlaps[i2]] = true; + count++; + } + ok = true; } - ok = true; } } } diff --git a/src/path/Segment.js b/src/path/Segment.js index 298065ec..50164d56 100644 --- a/src/path/Segment.js +++ b/src/path/Segment.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * @@ -234,8 +234,8 @@ var Segment = Base.extend(/** @lends Segment# */{ * Checks if the segment has any curve handles set. * * @return {Boolean} {@true if the segment has handles set} - * @see Segment#getHandleIn() - * @see Segment#getHandleOut() + * @see Segment#handleIn + * @see Segment#handleOut * @see Curve#hasHandles() * @see Path#hasHandles() */ @@ -554,6 +554,9 @@ var Segment = Base.extend(/** @lends Segment# */{ return this._path ? !!this._path.removeSegment(this._index) : false; }, + /** + * @return {Segment} + */ clone: function() { return new Segment(this._point, this._handleIn, this._handleOut); }, diff --git a/src/path/SegmentPoint.js b/src/path/SegmentPoint.js index 2289bda0..b73adfcb 100644 --- a/src/path/SegmentPoint.js +++ b/src/path/SegmentPoint.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * diff --git a/src/path/SegmentSelection.js b/src/path/SegmentSelection.js index 559ef294..ddf81077 100644 --- a/src/path/SegmentSelection.js +++ b/src/path/SegmentSelection.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * diff --git a/src/style/Color.js b/src/style/Color.js index 05f4691f..d97b6fc4 100644 --- a/src/style/Color.js +++ b/src/style/Color.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * @@ -80,7 +80,7 @@ var Color = Base.extend(new function() { } else if (match = string.match(/^(rgb|hsl)a?\((.*)\)$/)) { // RGB / RGBA or HSL / HSLA type = match[1]; - components = match[2].split(/[,\s]+/g); + components = match[2].trim().split(/[,\s]+/g); var isHSL = type === 'hsl'; for (var i = 0, l = Math.min(components.length, 4); i < l; i++) { var component = components[i]; @@ -102,7 +102,7 @@ var Color = Base.extend(new function() { } } else if (i < 3) { // RGB color values to 0..1 - value /= 255; + value /= /%$/.test(component) ? 100 : 255; } components[i] = value; } @@ -691,6 +691,8 @@ var Color = Base.extend(new function() { * constructors also work for calls of `set()`. * * @function + * @param {...*} values + * @return {Color} */ set: '#initialize', @@ -709,8 +711,13 @@ var Color = Base.extend(new function() { */ _changed: function() { this._canvasStyle = null; - if (this._owner) - this._owner._changed(/*#=*/Change.STYLE); + if (this._owner) { + if (this._setter) { + this._owner[this._setter](this); + } else { + this._owner._changed(/*#=*/Change.STYLE); + } + } }, /** @@ -729,7 +736,7 @@ var Color = Base.extend(new function() { }, /** - * Converts the color another type. + * Converts the color to another type. * * @param {String} type the color type to convert to. Possible values: * {@values 'rgb', 'gray', 'hsb', 'hsl'} @@ -1183,9 +1190,35 @@ var Color = Base.extend(new function() { // Export for backward compatibility code below. _types: types, + /** + * Returns a color object with random {@link #red}, {@link #green} + * and {@link #blue} values between `0` and `1`. + * + * @return {Color} the newly created color object + * @static + * + * @example {@paperscript} + * var circle = new Path.Circle(view.center, 50); + * // Set a random color as circle fill color. + * circle.fillColor = Color.random(); + */ random: function() { var random = Math.random; return new Color(random(), random(), random()); + }, + + _setOwner: function(color, owner, setter) { + if (color) { + // Clone color if owner changes: + if (color._owner && owner && color._owner !== owner) { + color = color.clone(); + } + if (!color._owner ^ !owner) { + color._owner = owner || null; + color._setter = setter || null; + } + } + return color; } } }); diff --git a/src/style/Gradient.js b/src/style/Gradient.js index 4fd6b6ea..5a228125 100644 --- a/src/style/Gradient.js +++ b/src/style/Gradient.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * diff --git a/src/style/GradientStop.js b/src/style/GradientStop.js index 96e78464..47ca4569 100644 --- a/src/style/GradientStop.js +++ b/src/style/GradientStop.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * @@ -175,12 +175,10 @@ var GradientStop = Base.extend(/** @lends GradientStop# */{ }, setColor: function(/* color */) { - // Make sure newly set colors are cloned, since they can only have - // one owner. - var color = Color.read(arguments, 0, { clone: true }); - if (color) - color._owner = this; - this._color = color; + // Clear old color owner before setting new one: + Color._setOwner(this._color, null); + this._color = Color._setOwner(Color.read(arguments, 0), this, + 'setColor'); this._changed(); }, diff --git a/src/style/Style.js b/src/style/Style.js index f8e10280..72609b91 100644 --- a/src/style/Style.js +++ b/src/style/Style.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * @@ -162,29 +162,35 @@ var Style = Base.extend(new function() { // raw value is stored, and conversion only happens in the getter. fields[set] = function(value) { var owner = this._owner, - children = owner && owner._children; + children = owner && owner._children, + applyToChildren = children && children.length > 0 + && !(owner instanceof CompoundPath); // Only unify styles on children of Groups, excluding CompoundPaths. - if (children && children.length > 0 - && !(owner instanceof CompoundPath)) { + if (applyToChildren) { for (var i = 0, l = children.length; i < l; i++) children[i]._style[set](value); - } else if (key in this._defaults) { + } + // Always store selectedColor in item _values to make sure that + // group selected bounds and position color is coherent whether it + // has children or not when the value is set. + if ((key === 'selectedColor' || !applyToChildren) + && key in this._defaults) { var old = this._values[key]; if (old !== value) { if (isColor) { // The old value may be a native string or other color // description that wasn't coerced to a color object yet - if (old && old._owner !== undefined) { - old._owner = undefined; + if (old) { + Color._setOwner(old, null); old._canvasStyle = null; } if (value && value.constructor === Color) { - // Clone color if it already has an owner. // NOTE: If value is not a Color, it is only // converted and cloned in the getter further down. - if (value._owner) - value = value.clone(); - value._owner = owner; + value = Color._setOwner(value, owner, + // Only provide a color-setter if the style + // is to be applied to the children: + applyToChildren && set); } } // NOTE: We do not convert the values to Colors in the @@ -201,29 +207,13 @@ var Style = Base.extend(new function() { fields[get] = function(_dontMerge) { var owner = this._owner, children = owner && owner._children, + applyToChildren = children && children.length > 0 + && !(owner instanceof CompoundPath), value; // If the owner has children, walk through all of them and see if // they all have the same style. - // If true is passed for _dontMerge, don't merge children styles - if (key in this._defaults && (!children || !children.length - || _dontMerge || owner instanceof CompoundPath)) { - var value = this._values[key]; - if (value === undefined) { - value = this._defaults[key]; - if (value && value.clone) - value = value.clone(); - } else { - var ctor = isColor ? Color : isPoint ? Point : null; - if (ctor && !(value && value.constructor === ctor)) { - // Convert to a Color / Point, and stored result of the - // conversion. - this._values[key] = value = ctor.read([value], 0, - { readNull: true, clone: true }); - if (value && isColor) - value._owner = owner; - } - } - } else if (children) { + // If true is passed for _dontMerge, don't merge children styles. + if (applyToChildren && !_dontMerge) { for (var i = 0, l = children.length; i < l; i++) { var childValue = children[i]._style[get](); if (!i) { @@ -234,6 +224,30 @@ var Style = Base.extend(new function() { return undefined; } } + } else if (key in this._defaults) { + var value = this._values[key]; + if (value === undefined) { + value = this._defaults[key]; + // Clone defaults if available: + if (value && value.clone) { + value = value.clone(); + } + } else { + var ctor = isColor ? Color : isPoint ? Point : null; + if (ctor && !(value && value.constructor === ctor)) { + // Convert to a Color / Point, and stored result of the + // conversion. + this._values[key] = value = ctor.read([value], 0, + { readNull: true, clone: true }); + } + } + } + if (value && isColor) { + // Color._setOwner() may clone the color if it already has a + // different owner (e.g. resulting from `childValue` above). + // Only provide a color-setter if the style is to be applied to + // the children: + value = Color._setOwner(value, owner, applyToChildren && set); } return value; }; @@ -397,7 +411,7 @@ var Style = Base.extend(new function() { * * @name Style#strokeColor * @property - * @type Color + * @type ?Color * * @example {@paperscript} * // Setting the stroke color of a path: @@ -530,7 +544,7 @@ var Style = Base.extend(new function() { * * @name Style#dashArray * @property - * @type Array + * @type Number[] * @default [] */ @@ -554,7 +568,7 @@ var Style = Base.extend(new function() { * * @name Style#fillColor * @property - * @type Color + * @type ?Color * * @example {@paperscript} * // Setting the fill color of a path to red: @@ -585,7 +599,7 @@ var Style = Base.extend(new function() { * * @property * @name Style#shadowColor - * @type Color + * @type ?Color * * @example {@paperscript} * // Creating a circle with a black shadow: @@ -629,7 +643,7 @@ var Style = Base.extend(new function() { * * @name Style#selectedColor * @property - * @type Color + * @type ?Color */ /** diff --git a/src/svg/SvgElement.js b/src/svg/SvgElement.js index df34143c..c166c229 100644 --- a/src/svg/SvgElement.js +++ b/src/svg/SvgElement.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * diff --git a/src/svg/SvgExport.js b/src/svg/SvgExport.js index 2c8c5b4d..5a890345 100644 --- a/src/svg/SvgExport.js +++ b/src/svg/SvgExport.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * @@ -29,11 +29,16 @@ new function() { // in rotate(). To do so, SVG requries us to inverse transform the // translation point by the matrix itself, since they are provided // in local coordinates. - matrix = matrix._shiftless(); - var point = matrix._inverseTransform(trans); + var point; + if (matrix.isInvertible()) { + matrix = matrix._shiftless(); + point = matrix._inverseTransform(trans); + trans = null; + } else { + point = new Point(); + } attrs[center ? 'cx' : 'x'] = point.x; attrs[center ? 'cy' : 'y'] = point.y; - trans = null; } if (!matrix.isIdentity()) { // See if we can decompose the matrix and can formulate it as a @@ -177,7 +182,7 @@ new function() { definition = item._definition, node = getDefinition(definition, 'symbol'), definitionItem = definition._item, - bounds = definitionItem.getBounds(); + bounds = definitionItem.getStrokeBounds(); if (!node) { node = SvgElement.create('symbol', { viewBox: formatter.rectangle(bounds) @@ -456,7 +461,7 @@ new function() { if (rect) { attrs.width = rect.width; attrs.height = rect.height; - if (rect.x || rect.y) + if (rect.x || rect.x === 0 || rect.y || rect.y === 0) attrs.viewBox = formatter.rectangle(rect); } var node = SvgElement.create('svg', attrs, formatter), diff --git a/src/svg/SvgImport.js b/src/svg/SvgImport.js index a94fc8e2..99066034 100644 --- a/src/svg/SvgImport.js +++ b/src/svg/SvgImport.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * @@ -22,11 +22,12 @@ new function() { var definitions = {}, rootSize; - function getValue(node, name, isString, allowNull, allowPercent) { + function getValue(node, name, isString, allowNull, allowPercent, + defaultValue) { // Interpret value as number. Never return NaN, but 0 instead. // If the value is a sequence of numbers, parseFloat will // return the first occurring number, which is enough for now. - var value = SvgElement.get(node, name), + var value = SvgElement.get(node, name) || defaultValue, res = value == null ? allowNull ? null @@ -43,9 +44,9 @@ new function() { : res; } - function getPoint(node, x, y, allowNull, allowPercent) { - x = getValue(node, x || 'x', false, allowNull, allowPercent); - y = getValue(node, y || 'y', false, allowNull, allowPercent); + function getPoint(node, x, y, allowNull, allowPercent, defaultX, defaultY) { + x = getValue(node, x || 'x', false, allowNull, allowPercent, defaultX); + y = getValue(node, y || 'y', false, allowNull, allowPercent, defaultY); return allowNull && (x == null || y == null) ? null : new Point(x, y); } @@ -168,13 +169,16 @@ new function() { 'userSpaceOnUse'; // Allow percentages in all values if scaleToBounds is true: if (radial) { - origin = getPoint(node, 'cx', 'cy', false, scaleToBounds); + origin = getPoint(node, 'cx', 'cy', false, scaleToBounds, + '50%', '50%'); destination = origin.add( - getValue(node, 'r', false, false, scaleToBounds), 0); + getValue(node, 'r', false, false, scaleToBounds, '50%'), 0); highlight = getPoint(node, 'fx', 'fy', true, scaleToBounds); } else { - origin = getPoint(node, 'x1', 'y1', false, scaleToBounds); - destination = getPoint(node, 'x2', 'y2', false, scaleToBounds); + origin = getPoint(node, 'x1', 'y1', false, scaleToBounds, + '0%', '0%'); + destination = getPoint(node, 'x2', 'y2', false, scaleToBounds, + '100%', '0%'); } var color = applyAttributes( new Color(gradient, origin, destination, highlight), node); @@ -196,23 +200,23 @@ new function() { return importNode(child, options, isRoot); } }, - // http://www.w3.org/TR/SVG/struct.html#Groups + // https://www.w3.org/TR/SVG/struct.html#Groups g: importGroup, - // http://www.w3.org/TR/SVG/struct.html#NewDocument + // https://www.w3.org/TR/SVG/struct.html#NewDocument svg: importGroup, clippath: importGroup, - // http://www.w3.org/TR/SVG/shapes.html#PolygonElement + // https://www.w3.org/TR/SVG/shapes.html#PolygonElement polygon: importPoly, - // http://www.w3.org/TR/SVG/shapes.html#PolylineElement + // https://www.w3.org/TR/SVG/shapes.html#PolylineElement polyline: importPoly, - // http://www.w3.org/TR/SVG/paths.html + // https://www.w3.org/TR/SVG/paths.html path: importPath, - // http://www.w3.org/TR/SVG/pservers.html#LinearGradients + // https://www.w3.org/TR/SVG/pservers.html#LinearGradients lineargradient: importGradient, - // http://www.w3.org/TR/SVG/pservers.html#RadialGradients + // https://www.w3.org/TR/SVG/pservers.html#RadialGradients radialgradient: importGradient, - // http://www.w3.org/TR/SVG/struct.html#ImageElement + // https://www.w3.org/TR/SVG/struct.html#ImageElement image: function (node) { var raster = new Raster(getValue(node, 'href', true)); raster.on('load', function() { @@ -228,17 +232,17 @@ new function() { return raster; }, - // http://www.w3.org/TR/SVG/struct.html#SymbolElement + // https://www.w3.org/TR/SVG/struct.html#SymbolElement symbol: function(node, type, options, isRoot) { return new SymbolDefinition( // Pass true for dontCenter: importGroup(node, type, options, isRoot), true); }, - // http://www.w3.org/TR/SVG/struct.html#DefsElement + // https://www.w3.org/TR/SVG/struct.html#DefsElement defs: importGroup, - // http://www.w3.org/TR/SVG/struct.html#UseElement + // https://www.w3.org/TR/SVG/struct.html#UseElement use: function(node) { // Note the namespaced xlink:href attribute is just called href // as a property on node. @@ -258,14 +262,14 @@ new function() { : null; }, - // http://www.w3.org/TR/SVG/shapes.html#InterfaceSVGCircleElement + // https://www.w3.org/TR/SVG/shapes.html#InterfaceSVGCircleElement circle: function(node) { return new Shape.Circle( getPoint(node, 'cx', 'cy'), getValue(node, 'r')); }, - // http://www.w3.org/TR/SVG/shapes.html#InterfaceSVGEllipseElement + // https://www.w3.org/TR/SVG/shapes.html#InterfaceSVGEllipseElement ellipse: function(node) { // We only use object literal notation where the default one is not // supported (e.g. center / radius fo Shape.Ellipse). @@ -275,7 +279,7 @@ new function() { }); }, - // http://www.w3.org/TR/SVG/shapes.html#RectElement + // https://www.w3.org/TR/SVG/shapes.html#RectElement rect: function(node) { return new Shape.Rectangle(new Rectangle( getPoint(node), @@ -283,7 +287,7 @@ new function() { ), getSize(node, 'rx', 'ry')); }, - // http://www.w3.org/TR/SVG/shapes.html#LineElement + // https://www.w3.org/TR/SVG/shapes.html#LineElement line: function(node) { return new Path.Line( getPoint(node, 'x1', 'y1'), @@ -345,7 +349,11 @@ new function() { text.setContent(lines.join('\n')); return text; } - } + }, + + // https://www.w3.org/TR/SVG/struct.html#SwitchElement + // Conditional attributes are ignored and all children are rendered. + switch: importGroup }; // Attributes and Styles @@ -356,7 +364,7 @@ new function() { function applyTransform(item, value, name, node) { if (item.transform) { - // http://www.w3.org/TR/SVG/types.html#DataTypeTransformList + // https://www.w3.org/TR/SVG/types.html#DataTypeTransformList // Parse SVG transform string. First we split at /)\s*/, to separate // commands var transforms = (node.getAttribute(name) || '').split(/\)\s*/g), @@ -399,8 +407,8 @@ new function() { } function applyOpacity(item, value, name) { - // http://www.w3.org/TR/SVG/painting.html#FillOpacityProperty - // http://www.w3.org/TR/SVG/painting.html#StrokeOpacityProperty + // https://www.w3.org/TR/SVG/painting.html#FillOpacityProperty + // https://www.w3.org/TR/SVG/painting.html#StrokeOpacityProperty var key = name === 'fill-opacity' ? 'getFillColor' : 'getStrokeColor', color = item[key] && item[key](); if (color) @@ -442,7 +450,7 @@ new function() { }, 'clip-path': function(item, value) { - // http://www.w3.org/TR/SVG/masking.html#ClipPathProperty + // https://www.w3.org/TR/SVG/masking.html#ClipPathProperty var clip = getDefinition(value); if (clip) { clip = clip.clone(); @@ -474,20 +482,20 @@ new function() { }, 'stop-color': function(item, value) { - // http://www.w3.org/TR/SVG/pservers.html#StopColorProperty + // https://www.w3.org/TR/SVG/pservers.html#StopColorProperty if (item.setColor) item.setColor(value); }, 'stop-opacity': function(item, value) { - // http://www.w3.org/TR/SVG/pservers.html#StopOpacityProperty + // https://www.w3.org/TR/SVG/pservers.html#StopOpacityProperty // NOTE: It is important that this is applied after stop-color! if (item._color) item._color.setAlpha(parseFloat(value)); }, offset: function(item, value) { - // http://www.w3.org/TR/SVG/pservers.html#StopElementOffsetAttribute + // https://www.w3.org/TR/SVG/pservers.html#StopElementOffsetAttribute if (item.setOffset) { var percent = value.match(/(.*)%$/); item.setOffset(percent ? percent[1] / 100 : parseFloat(value)); @@ -495,7 +503,7 @@ new function() { }, viewBox: function(item, value, name, node, styles) { - // http://www.w3.org/TR/SVG/coords.html#ViewBoxAttribute + // https://www.w3.org/TR/SVG/coords.html#ViewBoxAttribute // TODO: implement preserveAspectRatio attribute // viewBox will be applied both to the group that's created for the // content in SymbolDefinition#item, and the SymbolItem itself. @@ -703,8 +711,12 @@ new function() { function onLoad(svg) { try { - var node = typeof svg === 'object' ? svg : new self.DOMParser() - .parseFromString(svg, 'image/svg+xml'); + var node = typeof svg === 'object' + ? svg + : new self.DOMParser().parseFromString( + svg, + 'image/svg+xml' + ); if (!node.nodeName) { node = null; throw new Error('Unsupported SVG source: ' + source); @@ -734,8 +746,10 @@ new function() { // Have the group not pass on all transformations to its children, // as this is how SVG works too. - // See if it's a string but handle markup separately - if (typeof source === 'string' && !/^.* + */ +var CollisionDetection = /** @lends CollisionDetection */{ + /** + * Finds collisions between axis aligned bounding boxes of items. + * + * This function takes the bounds of all items in the items1 and items2 + * arrays and calls findBoundsCollisions(). + * + * @param {Array} items1 Array of items for which collisions should be + * found. + * @param {Array} [items2] Array of items that the first array should be + * compared with. If not provided, collisions between items within + * the first array will be returned. + * @param {Number} [tolerance] If provided, the tolerance will be added to + * all sides of each bounds when checking for collisions. + * @returns {Array} Array containing for the bounds at the same index in + * items1 an array of the indexes of colliding bounds in items2 + */ + findItemBoundsCollisions: function(items1, items2, tolerance) { + function getBounds(items) { + var bounds = new Array(items.length); + for (var i = 0; i < items.length; i++) { + var rect = items[i].getBounds(); + bounds[i] = [rect.left, rect.top, rect.right, rect.bottom]; + } + return bounds; + } + + var bounds1 = getBounds(items1), + bounds2 = !items2 || items2 === items1 + ? bounds1 + : getBounds(items2); + return this.findBoundsCollisions(bounds1, bounds2, tolerance || 0); + }, + + /** + * Finds collisions between curves bounds. For performance reasons this + * uses broad bounds of the curve, which can be calculated much faster than + * the actual bounds. Broad bounds guarantee to contain the full curve, + * but they are usually larger than the actual bounds of a curve. + * + * This function takes the broad bounds of all curve values in the curves1 + * and curves2 arrays and calls findBoundsCollisions(). + * + * @param {Array} curves1 Array of curve values for which collisions should + * be found. + * @param {Array} [curves2] Array of curve values that the first array + * should be compared with. If not provided, collisions between curve + * bounds within the first arrray will be returned. + * @param {Number} [tolerance] If provided, the tolerance will be added to + * all sides of each bounds when checking for collisions. + * @param {Boolean} [bothAxis] If true, the sweep is performed along both + * axis, and the results include collisions for both: `{ hor, ver }`. + * @returns {Array} Array containing for the bounds at the same index in + * curves1 an array of the indexes of colliding bounds in curves2 + */ + findCurveBoundsCollisions: function(curves1, curves2, tolerance, bothAxis) { + function getBounds(curves) { + var min = Math.min, + max = Math.max, + bounds = new Array(curves.length); + for (var i = 0; i < curves.length; i++) { + var v = curves[i]; + bounds[i] = [ + min(v[0], v[2], v[4], v[6]), + min(v[1], v[3], v[5], v[7]), + max(v[0], v[2], v[4], v[6]), + max(v[1], v[3], v[5], v[7]) + ]; + } + return bounds; + } + + var bounds1 = getBounds(curves1), + bounds2 = !curves2 || curves2 === curves1 + ? bounds1 + : getBounds(curves2); + if (bothAxis) { + var hor = this.findBoundsCollisions( + bounds1, bounds2, tolerance || 0, false, true), + ver = this.findBoundsCollisions( + bounds1, bounds2, tolerance || 0, true, true), + list = []; + for (var i = 0, l = hor.length; i < l; i++) { + list[i] = { hor: hor[i], ver: ver[i] }; + } + return list; + } + return this.findBoundsCollisions(bounds1, bounds2, tolerance || 0); + }, + + /** + * Finds collisions between two sets of bounding rectangles. + * + * The collision detection is implemented as a sweep and prune algorithm + * with sweep either along the x or y axis (primary axis) and immediate + * check on secondary axis for potential pairs. + * + * Each entry in the bounds arrays must be an array of length 4 with + * x0, y0, x1, and y1 as the array elements. + * + * The returned array has the same length as bounds1. Each entry + * contains an array with all indices of overlapping bounds of + * bounds2 (or bounds1 if bounds2 is not provided) sorted + * in ascending order. + * + * If the second bounds array parameter is null, collisions between bounds + * within the first bounds array will be found. In this case the indexed + * returned for each bounds will not contain the bounds' own index. + * + * + * @param {Array} boundsA Array of bounds objects for which collisions + * should be found. + * @param {Array} [boundsB] Array of bounds that the first array should + * be compared with. If not provided, collisions between bounds within + * the first arrray will be returned. + * @param {Number} [tolerance] If provided, the tolerance will be added to + * all sides of each bounds when checking for collisions. + * @param {Boolean} [sweepVertical] If true, the sweep is performed along + * the y-axis. + * @param {Boolean} [onlySweepAxisCollisions] If true, no collision checks + * will be done on the secondary axis. + * @returns {Array} Array containing for the bounds at the same index in + * boundsA an array of the indexes of colliding bounds in boundsB + */ + findBoundsCollisions: function(boundsA, boundsB, tolerance, + sweepVertical, onlySweepAxisCollisions) { + var self = !boundsB || boundsA === boundsB, + allBounds = self ? boundsA : boundsA.concat(boundsB), + lengthA = boundsA.length, + lengthAll = allBounds.length; + + // Binary search utility function. + // For multiple same entries, this returns the rightmost entry. + // https://en.wikipedia.org/wiki/Binary_search_algorithm#Procedure_for_finding_the_rightmost_element + function binarySearch(indices, coord, value) { + var lo = 0, + hi = indices.length; + while (lo < hi) { + var mid = (hi + lo) >>> 1; // Same as Math.floor((hi + lo) / 2) + if (allBounds[indices[mid]][coord] < value) { + lo = mid + 1; + } else { + hi = mid; + } + } + return lo - 1; + } + + // Set coordinates for primary and secondary axis depending on sweep + // direction. By default we sweep in horizontal direction, which + // means x is the primary axis. + var pri0 = sweepVertical ? 1 : 0, + pri1 = pri0 + 2, + sec0 = sweepVertical ? 0 : 1, + sec1 = sec0 + 2; + // Create array with all indices sorted by lower boundary on primary + // axis. + var allIndicesByPri0 = new Array(lengthAll); + for (var i = 0; i < lengthAll; i++) { + allIndicesByPri0[i] = i; + } + allIndicesByPri0.sort(function(i1, i2) { + return allBounds[i1][pri0] - allBounds[i2][pri0]; + }); + // Sweep along primary axis. Indices of active bounds are kept in an + // array sorted by higher boundary on primary axis. + var activeIndicesByPri1 = [], + allCollisions = new Array(lengthA); + for (var i = 0; i < lengthAll; i++) { + var curIndex = allIndicesByPri0[i], + curBounds = allBounds[curIndex], + // The original index in boundsA or boundsB: + origIndex = self ? curIndex : curIndex - lengthA, + isCurrentA = curIndex < lengthA, + isCurrentB = self || !isCurrentA, + curCollisions = isCurrentA ? [] : null; + if (activeIndicesByPri1.length) { + // remove (prune) indices that are no longer active. + var pruneCount = binarySearch(activeIndicesByPri1, pri1, + curBounds[pri0] - tolerance) + 1; + activeIndicesByPri1.splice(0, pruneCount); + // Add collisions for current index. + if (self && onlySweepAxisCollisions) { + // All active indexes can be added, no further checks needed + curCollisions = curCollisions.concat(activeIndicesByPri1); + // Add current index to collisions of all active indexes + for (var j = 0; j < activeIndicesByPri1.length; j++) { + var activeIndex = activeIndicesByPri1[j]; + allCollisions[activeIndex].push(origIndex); + } + } else { + var curSec1 = curBounds[sec1], + curSec0 = curBounds[sec0]; + for (var j = 0; j < activeIndicesByPri1.length; j++) { + var activeIndex = activeIndicesByPri1[j], + activeBounds = allBounds[activeIndex], + isActiveA = activeIndex < lengthA, + isActiveB = self || activeIndex >= lengthA; + + // Check secondary axis bounds if necessary. + if ( + onlySweepAxisCollisions || + ( + isCurrentA && isActiveB || + isCurrentB && isActiveA + ) && ( + curSec1 >= activeBounds[sec0] - tolerance && + curSec0 <= activeBounds[sec1] + tolerance + ) + ) { + // Add current index to collisions of active + // indices and vice versa. + if (isCurrentA && isActiveB) { + curCollisions.push( + self ? activeIndex : activeIndex - lengthA); + } + if (isCurrentB && isActiveA) { + allCollisions[activeIndex].push(origIndex); + } + } + } + } + } + if (isCurrentA) { + if (boundsA === boundsB) { + // If both arrays are the same, add self collision. + curCollisions.push(curIndex); + } + // Add collisions for current index. + allCollisions[curIndex] = curCollisions; + } + // Add current index to active indices. Keep array sorted by + // their higher boundary on the primary axis.s + if (activeIndicesByPri1.length) { + var curPri1 = curBounds[pri1], + index = binarySearch(activeIndicesByPri1, pri1, curPri1); + activeIndicesByPri1.splice(index + 1, 0, curIndex); + } else { + activeIndicesByPri1.push(curIndex); + } + } + // Sort collision indices in ascending order. + for (var i = 0; i < allCollisions.length; i++) { + var collisions = allCollisions[i]; + if (collisions) { + collisions.sort(function(i1, i2) { return i1 - i2; }); + } + } + return allCollisions; + } +}; diff --git a/src/util/Formatter.js b/src/util/Formatter.js index 2b73c5ba..d402b8e4 100644 --- a/src/util/Formatter.js +++ b/src/util/Formatter.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * diff --git a/src/util/Numerical.js b/src/util/Numerical.js index b2b3adfd..6b6a456d 100644 --- a/src/util/Numerical.js +++ b/src/util/Numerical.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * @@ -168,6 +168,10 @@ var Numerical = new function() { return val >= -EPSILON && val <= EPSILON; }, + isMachineZero: function(val) { + return val >= -MACHINE_EPSILON && val <= MACHINE_EPSILON; + }, + /** * Returns a number whose value is clamped by the given range. * diff --git a/src/util/UID.js b/src/util/UID.js index 9aac04c0..1a6dd806 100644 --- a/src/util/UID.js +++ b/src/util/UID.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * diff --git a/src/view/CanvasView.js b/src/view/CanvasView.js index 3afdfa68..cabb1359 100644 --- a/src/view/CanvasView.js +++ b/src/view/CanvasView.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * @@ -49,7 +49,7 @@ var CanvasView = View.extend(/** @lends CanvasView# */{ this._pixelRatio = 1; if (!/^off|false$/.test(PaperScope.getAttribute(canvas, 'hidpi'))) { // Hi-DPI Canvas support based on: - // http://www.html5rocks.com/en/tutorials/canvas/hidpi/ + // https://www.html5rocks.com/en/tutorials/canvas/hidpi/ var deviceRatio = window.devicePixelRatio || 1, backingStoreRatio = DomElement.getPrefixed(ctx, 'backingStorePixelRatio') || 1; @@ -88,6 +88,10 @@ var CanvasView = View.extend(/** @lends CanvasView# */{ } }, + getContext: function() { + return this._context; + }, + /** * Converts the provide size in any of the units allowed in the browser to * pixels. @@ -124,7 +128,7 @@ var CanvasView = View.extend(/** @lends CanvasView# */{ /** * Updates the view if there are changes. Note that when using built-in - * event hanlders for interaction, animation and load events, this method is + * event handlers for interaction, animation and load events, this method is * invoked for you automatically at the end. * * @return {Boolean} {@true if the view was updated} diff --git a/src/view/View.js b/src/view/View.js index 281670f1..beabc959 100644 --- a/src/view/View.js +++ b/src/view/View.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * @@ -45,7 +45,7 @@ var View = Base.extend(Emitter, /** @lends View# */{ // Generate an id for this view / element if it does not have one this._id = element.getAttribute('id'); if (this._id == null) - element.setAttribute('id', this._id = 'view-' + View._id++); + element.setAttribute('id', this._id = 'paper-view-' + View._id++); // Install event handlers DomEvent.add(element, this._viewEvents); // Borrowed from Hammer.js: @@ -480,8 +480,9 @@ var View = Base.extend(Emitter, /** @lends View# */{ }, Base.each(['rotate', 'scale', 'shear', 'skew'], function(key) { var rotate = key === 'rotate'; this[key] = function(/* value, center */) { - var value = (rotate ? Base : Point).read(arguments), - center = Point.read(arguments, 0, { readNull: true }); + var args = arguments, + value = (rotate ? Base : Point).read(args), + center = Point.read(args, 0, { readNull: true }); return this.transform(new Matrix()[key](value, center || this.getCenter(true))); }; @@ -522,7 +523,7 @@ var View = Base.extend(Emitter, /** @lends View# */{ * * @bean * @type Number - * @see #getScaling() + * @see #scaling */ getZoom: function() { var scaling = this._decompose().scaling; @@ -559,7 +560,7 @@ var View = Base.extend(Emitter, /** @lends View# */{ * * @bean * @type Point - * @see #getZoom() + * @see #zoom */ getScaling: function() { var scaling = this._decompose().scaling; @@ -632,7 +633,7 @@ var View = Base.extend(Emitter, /** @lends View# */{ * * @name View#shear * @function - * @param {Point} shear the horziontal and vertical shear factors as a point + * @param {Point} shear the horizontal and vertical shear factors as a point * @param {Point} [center={@link View#center}] * @see Matrix#shear(shear[, center]) */ @@ -654,7 +655,7 @@ var View = Base.extend(Emitter, /** @lends View# */{ * * @name View#skew * @function - * @param {Point} skew the horziontal and vertical skew angles in degrees + * @param {Point} skew the horizontal and vertical skew angles in degrees * @param {Point} [center={@link View#center}] * @see Matrix#shear(skew[, center]) */ @@ -746,7 +747,7 @@ var View = Base.extend(Emitter, /** @lends View# */{ * * @name View#onFrame * @property - * @type Function + * @type ?Function * @see Item#onFrame * * @example {@paperscript} @@ -768,7 +769,7 @@ var View = Base.extend(Emitter, /** @lends View# */{ * * @name View#onResize * @property - * @type Function + * @type ?Function * * @example * // Repositioning items when a view is resized: @@ -793,7 +794,7 @@ var View = Base.extend(Emitter, /** @lends View# */{ * * @name View#onMouseDown * @property - * @type Function + * @type ?Function * @see Item#onMouseDown */ @@ -807,7 +808,7 @@ var View = Base.extend(Emitter, /** @lends View# */{ * * @name View#onMouseDrag * @property - * @type Function + * @type ?Function * @see Item#onMouseDrag */ @@ -818,7 +819,7 @@ var View = Base.extend(Emitter, /** @lends View# */{ * * @name View#onMouseUp * @property - * @type Function + * @type ?Function * @see Item#onMouseUp */ @@ -832,7 +833,7 @@ var View = Base.extend(Emitter, /** @lends View# */{ * * @name View#onClick * @property - * @type Function + * @type ?Function * @see Item#onClick */ @@ -846,7 +847,7 @@ var View = Base.extend(Emitter, /** @lends View# */{ * * @name View#onDoubleClick * @property - * @type Function + * @type ?Function * @see Item#onDoubleClick */ @@ -860,7 +861,7 @@ var View = Base.extend(Emitter, /** @lends View# */{ * * @name View#onMouseMove * @property - * @type Function + * @type ?Function * @see Item#onMouseMove */ @@ -875,7 +876,7 @@ var View = Base.extend(Emitter, /** @lends View# */{ * * @name View#onMouseEnter * @property - * @type Function + * @type ?Function * @see Item#onMouseEnter */ @@ -889,7 +890,7 @@ var View = Base.extend(Emitter, /** @lends View# */{ * * @name View#onMouseLeave * @property - * @type Function + * @type ?Function * @see View#onMouseLeave */ @@ -1496,7 +1497,7 @@ new function() { // Injection scope for event handling on the browser updateFocus: updateFocus, /** - * Clear all events handling state informations. Made for testing + * Clear all events handling state information. Made for testing * purpose, to have a way to start with a fresh state before each * test. * @private diff --git a/test/assets/gradients-3.svg b/test/assets/gradients-3.svg new file mode 100644 index 00000000..854ccf5a --- /dev/null +++ b/test/assets/gradients-3.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/test/assets/gradients-4.svg b/test/assets/gradients-4.svg new file mode 100644 index 00000000..98fc5380 --- /dev/null +++ b/test/assets/gradients-4.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/test/helpers.js b/test/helpers.js index 4ccc68ba..03698eba 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -2,19 +2,21 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * * All rights reserved. */ -var isNode = typeof global === 'object', - isPhantom = typeof window === 'object' && !!window.callPhantom, +// We call our variable `isNodeContext` because resemble.js exposes a global +// `isNode` function which would override it and break node check. +var isNodeContext = typeof global === 'object', + isPhantomContext = typeof window === 'object' && !!window.callPhantom, scope; -if (isNode) { +if (isNodeContext) { scope = global; // Resemble.js needs the Image constructor global. global.Image = paper.window.Image; @@ -35,14 +37,11 @@ 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 - }; -} +// If they have not already been stored in `src/load.js`, do it now: +var nativeClasses = this.nativeClasses || { + Event: this.Event || {}, + MouseEvent: this.MouseEvent || {} +}; // The unit-tests expect the paper classes to be global. paper.install(scope); @@ -107,7 +106,7 @@ var equals = function(actual, expected, message, options) { || type === 'boolean' && 'Boolean' || type === 'undefined' && 'Undefined' || Array.isArray(expected) && 'Array' - || expected instanceof window.Element && 'Element' // handle DOM Elements + || expected instanceof window.Element && 'Element' // DOM Elements || (cls = expected && expected._class) // check _class 2nd last || type === 'object' && 'Object'; // Object as catch-all var comparator = type && comparators[type]; @@ -161,6 +160,81 @@ var compareProperties = function(actual, expected, properties, message, options) } }; +/** + * Compare 2 image data with resemble.js library. + * When comparison fails, expected, actual and compared images are displayed. + * @param {ImageData} imageData1 the expected image data + * @param {ImageData} imageData2 the actual image data + * @param {number} tolerance + * @param {string} diffDetail text displayed when comparison fails + */ +var compareImageData = function(imageData1, imageData2, tolerance, diffDetail) { + /** + * Build an image element from a given image data. + * @param {ImageData} imageData + * @return {HTMLImageElement} + */ + function image(imageData) { + var canvas = document.createElement('canvas'); + canvas.width = imageData.width; + canvas.height = imageData.height; + canvas.getContext('2d').putImageData(imageData, 0, 0); + var image = new Image(); + image.src = canvas.toDataURL(); + canvas.remove(); + return image; + } + + tolerance = (tolerance || 1e-4) * 100; + + var id = QUnit.config.current.testId, + index = QUnit.config.current.assertions.length + 1, + result; + // Compare image-data using resemble.js: + resemble.compare( + imageData1, + imageData2, + { + output: { + errorColor: { red: 255, green: 51, blue: 0 }, + errorType: 'flat', + transparency: 1 + }, + ignore: ['antialiasing'] + }, + // When working with imageData, this call is synchronous: + function (error, data) { + if (error) { + console.error(error); + } else { + result = data; + } + } + ) + // Compare with tolerance in percentage... + var fixed = tolerance < 1 ? ((1 / tolerance) + '').length - 1 : 0, + identical = result ? 100 - result.misMatchPercentage : 0, + ok = Math.abs(100 - identical) <= tolerance, + text = identical.toFixed(fixed) + '% identical', + detail = text; + if (!ok && diffDetail) { + detail += diffDetail; + } + QUnit.push(ok, text, (100).toFixed(fixed) + '% identical'); + if (!ok && result && !isNodeContext) { + // Get the right entry for this unit test and assertion, and + // replace the results with images + var entry = document.getElementById('qunit-test-output-' + id) + .querySelector('li:nth-child(' + (index) + ')'), + bounds = result.diffBounds; + entry.querySelector('.test-expected td').appendChild(image(imageData2)); + entry.querySelector('.test-actual td').appendChild(image(imageData1)); + entry.querySelector('.test-diff td').innerHTML = '
' + detail
+            + '

' + + ''; + } +}; + var comparePixels = function(actual, expected, message, options) { function rasterize(item, group, resolution) { var raster = null; @@ -178,11 +252,6 @@ var comparePixels = function(actual, expected, message, options) { return raster; } - function getImageTag(raster) { - return ''; - } - if (!expected) { return QUnit.strictEqual(actual, expected, message, options); } else if (!actual) { @@ -197,12 +266,12 @@ var comparePixels = function(actual, expected, message, options) { // bounds of both items before rasterizing. var resolution = options.resolution || 72, actualBounds = actual.strokeBounds, - expecedBounds = expected.strokeBounds, + expectedBounds = expected.strokeBounds, bounds = actualBounds.isEmpty() - ? expecedBounds - : expecedBounds.isEmpty() + ? expectedBounds + : expectedBounds.isEmpty() ? actualBounds - : actualBounds.unite(expecedBounds); + : actualBounds.unite(expectedBounds); if (bounds.isEmpty()) { QUnit.equal('empty', 'empty', message); return; @@ -220,53 +289,20 @@ var comparePixels = function(actual, expected, message, options) { expectedRaster = rasterize(expected, group, resolution); if (!actualRaster || !expectedRaster) { QUnit.push(false, null, null, 'Unable to compare rasterized items: ' + - (!actualRaster ? 'actual' : 'expected') + ' item is null', - QUnit.stack(2)); + (!actualRaster ? 'actual' : 'expected') + ' item is null', + QUnit.stack(2)); } else { - // Use resemble.js to compare the two rasterized items. - var id = QUnit.config.current.testId, - index = QUnit.config.current.assertions.length + 1, - result; - if (!resemble._setup) { - resemble._setup = true; - resemble.outputSettings({ - errorColor: { red: 255, green: 51, blue: 0 }, - errorType: 'flat', - transparency: 1 - }); - } - resemble(actualRaster.getImageData()) - .compareTo(expectedRaster.getImageData()) - .ignoreAntialiasing() - // When working with imageData, this call is synchronous: - .onComplete(function(data) { result = data; }); - // Compare with tolerance in percentage... - var tolerance = (options.tolerance || 1e-4) * 100, - fixed = tolerance < 1 ? ((1 / tolerance) + '').length - 1 : 0, - identical = result ? 100 - result.misMatchPercentage : 0, - ok = Math.abs(100 - identical) <= tolerance, - text = identical.toFixed(fixed) + '% identical', - detail = text; - if (!ok && - actual instanceof PathItem && expected instanceof PathItem) { - detail += '\nExpected:\n' + expected.pathData + - '\nActual:\n' + actual.pathData; - } - QUnit.push(ok, text, (100).toFixed(fixed) + '% identical', message); - if (!ok && result && !isNode) { - // Get the right entry for this unit test and assertion, and - // replace the results with images - var entry = document.getElementById('qunit-test-output-' + id) - .querySelector('li:nth-child(' + (index) + ')'), - bounds = result.diffBounds; - entry.querySelector('.test-expected td').innerHTML = - getImageTag(expectedRaster); - entry.querySelector('.test-actual td').innerHTML = - getImageTag(actualRaster); - entry.querySelector('.test-diff td').innerHTML = '
' + detail
-                    + '

' - + ''; - } + // Compare the two rasterized items. + var detail = actual instanceof PathItem && expected instanceof PathItem + ? '\nExpected:\n' + expected.pathData + + '\nActual:\n' + actual.pathData + : ''; + compareImageData( + actualRaster.getImageData(), + expectedRaster.getImageData(), + options.tolerance, + detail + ); } }; @@ -304,6 +340,41 @@ var compareItem = function(actual, expected, message, options, properties) { } }; +/** + * Run each callback in a separated canvas context and compare both outputs. + * This can be used to do selection drawing tests as it is not possible with + * comparePixels() method which relies on the item.rasterize() method which + * ignores selection. + * @param {number} width the width of the canvas + * @param {number} height the height of the canvas + * @param {function} expectedCallback the function producing the expected result + * @param {function} actualCallback the function producing the actual result + * @param {number} tolerance between 0 and 1 + */ +var compareCanvas = function(width, height, expected, actual, tolerance) { + function getImageData(width, height, callback) { + var canvas = document.createElement('canvas'); + canvas.width = width; + canvas.height = height; + var project = new Project(canvas); + var view = project.view; + callback(); + view.update(); + var imageData = view.context.getImageData(0, 0, width, height); + project.remove(); + canvas.remove(); + return imageData; + } + + compareImageData( + getImageData(width, height, expected), + getImageData(width, height, actual), + tolerance + ); + + currentProject.activate(); +}; + // A list of comparator functions, based on `expected` type. See equals() for // an explanation of how the type is determined. var comparators = { @@ -385,7 +456,7 @@ var comparators = { equals(actual.components, expected.components, message + ' (#components)', options); } else { - QUnit.strictEqual(actual, expected, message); + QUnit.push(expected.equals(actual), actual, expected, message); } }, @@ -597,8 +668,8 @@ var MouseEventPolyfill = function(type, params) { ); return mouseEvent; }; -MouseEventPolyfill.prototype = typeof NativeClasses !== 'undefined' - && NativeClasses.Event.prototype || Event.prototype; + +MouseEventPolyfill.prototype = nativeClasses.Event.prototype; var triggerMouseEvent = function(type, point, target) { // Depending on event type, events have to be triggered on different @@ -606,21 +677,16 @@ var triggerMouseEvent = function(type, point, target) { // 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); - + 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; - + // native classes stored in the nativeClasses object instead. // 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, { + // use a polyfill method, see: https://stackoverflow.com/questions/42929639 + var MouseEvent = typeof nativeClasses.MouseEvent === 'function' + ? nativeClasses.MouseEvent + : MouseEventPolyfill; + var event = new MouseEvent(type, { bubbles: true, cancelable: true, composed: true, diff --git a/test/load.js b/test/load.js index ed1cf72e..b4619122 100644 --- a/test/load.js +++ b/test/load.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * diff --git a/test/tests/Color.js b/test/tests/Color.js index 32f0cf9d..7dfabeaa 100644 --- a/test/tests/Color.js +++ b/test/tests/Color.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * @@ -81,6 +81,12 @@ test('Creating Colors', function() { equals(new Color('rgba(255, 0, 0, 0.5)'), new Color(1, 0, 0, 0.5), 'Color from rgba() string'); + equals(new Color('rgba( 255, 0, 0, 0.5 )'), new Color(1, 0, 0, 0.5), + 'Color from rgba() string 2nd test'); + + equals(new Color('rgb(100%, 50%, 0%)'), new Color(1, 0.5, 0), + 'Color from rgb() percenst string'); + equals(new Color('hsl(180deg, 20%, 40%)'), new Color({ hue: 180, saturation: 0.2, lightness: 0.4 }), 'Color from hsl() string'); @@ -299,3 +305,11 @@ test('Gradients with applyMatrix', function() { comparePixels(path, shape); }); + +test('Modifying group.strokeColor for multiple children', function() { + var item = new Group(new Path(), new Path()); + item.strokeColor = 'red'; + var strokeColor = item.strokeColor; + item.strokeColor.hue = 50; + equals(function() { return item.strokeColor !== undefined; }, true); +}); diff --git a/test/tests/CompoundPath.js b/test/tests/CompoundPath.js index adc0224d..3e01d775 100644 --- a/test/tests/CompoundPath.js +++ b/test/tests/CompoundPath.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * diff --git a/test/tests/Curve.js b/test/tests/Curve.js index 6a4b6c4a..c570dbd5 100644 --- a/test/tests/Curve.js +++ b/test/tests/Curve.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * diff --git a/test/tests/CurveLocation.js b/test/tests/CurveLocation.js index fa3b06c2..bcba60da 100644 --- a/test/tests/CurveLocation.js +++ b/test/tests/CurveLocation.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * diff --git a/test/tests/Emitter.js b/test/tests/Emitter.js index 9e55136b..255f6f79 100644 --- a/test/tests/Emitter.js +++ b/test/tests/Emitter.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * diff --git a/test/tests/Group.js b/test/tests/Group.js index 3f542204..f31e172c 100644 --- a/test/tests/Group.js +++ b/test/tests/Group.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * @@ -134,4 +134,93 @@ test('group.addChildren()', function() { group.addChildren(children); equals(group.children.length, 2, 'adding the same item twice should only add it once.'); -}) +}); + +test('group.setSelectedColor() with selected bound and position', function() { + // Working: Set selected color first then add child. + var group1 = new Group(); + group1.bounds.selected = true; + group1.position.selected = true; + group1.selectedColor = 'black'; + group1.addChild(new Path.Circle([50, 50], 40)); + // Failing: Add child first then set selected color. + var group2 = new Group(); + group2.bounds.selected = true; + group2.position.selected = true; + group2.addChild(new Path.Circle([50, 50], 40)); + group2.selectedColor = 'black'; + comparePixels(group1, group2); +}); + +test('Group#isEmpty(recursively)', function() { + var group = new Group(); + equals(true, group.isEmpty()); + equals(true, group.isEmpty(true)); + var group = new Group(new Group()); + equals(false, group.isEmpty()); + equals(true, group.isEmpty(true)); + var group = new Group(new Path()); + equals(false, group.isEmpty()); + equals(true, group.isEmpty(true)); + var group = new Group(new PointText()); + equals(false, group.isEmpty()); + equals(true, group.isEmpty(true)); +}); + +test( + 'group.internalBounds with clip item without clip.applyMatrix = false', + function() { + var point = new Point(100, 100); + var translation = new Point(100, 100); + var item = new Path.Circle({ + center: point, + radius: 50, + fillColor: 'orange' + }); + var clip = new Path.Rectangle({ + from: point.subtract(translation), + to: point.add(translation) + }); + clip.applyMatrix = false; + clip.translate(translation); + var group = new Group(clip, item); + group.clipped = true; + var expected = new Rectangle(point, point.add(translation.multiply(2))); + equals(group.internalBounds, expected); + } +); + +test('group.matrix with parent matrix applied (#1711)', function() { + var child = new Group({ applyMatrix: false }); + var parent = new Group({ applyMatrix: true, children: [child] }); + var scale = 1.1; + var initial = child.scaling.x; + parent.scale(scale); + equals(child.scaling.x, initial * scale); +}); + +test('Nested group.matrix.apply(true, true) with matrices not applied', function() { + var path = new Path({ applyMatrix: false }); + var group = new Group({ applyMatrix: false, children: [path] }); + var parent = new Group({ applyMatrix: false, children: [group] }); + var grandParent = new Group({ applyMatrix: false, children: [parent] }); + equals(function() { + return grandParent.applyMatrix; + }, false); + equals(function() { + return group.applyMatrix; + }, false); + equals(function() { + return path.applyMatrix; + }, false); + grandParent.matrix.apply(true, true); + equals(function() { + return grandParent.applyMatrix; + }, true); + equals(function() { + return group.applyMatrix; + }, true); + equals(function() { + return path.applyMatrix; + }, true); +}); diff --git a/test/tests/HitResult.js b/test/tests/HitResult.js index 5ca489f7..91367464 100644 --- a/test/tests/HitResult.js +++ b/test/tests/HitResult.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * diff --git a/test/tests/Interactions.js b/test/tests/Interactions.js index f606fb78..f364a3ee 100644 --- a/test/tests/Interactions.js +++ b/test/tests/Interactions.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * diff --git a/test/tests/Item.js b/test/tests/Item.js index fa226b93..af6c7f01 100644 --- a/test/tests/Item.js +++ b/test/tests/Item.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * @@ -735,23 +735,54 @@ test('Item#blendMode in a transformed Group', function() { blendMode: 'screen' }); - var raster = layer.rasterize(72); + var raster = layer.rasterize(72, false); equals(raster.getPixel(0, 0), new Color(1, 0, 0, 1), - 'Top left pixel should be red:'); + 'Top left pixel should be red'); equals(raster.getPixel(50, 50), new Color(1, 1, 0, 1), - 'Middle center pixel should be yellow:'); + 'Middle center pixel should be yellow'); - raster.remove(); path2.position = [0, 0]; var group = new Group(path2); group.position = [50, 50]; - var raster = layer.rasterize(72); + var raster = layer.rasterize(72, false); equals(raster.getPixel(0, 0), new Color(1, 0, 0, 1), - 'Top left pixel should be red:'); + 'Top left pixel should be red'); equals(raster.getPixel(50, 50), new Color(1, 1, 0, 1), - 'Middle center pixel should be yellow:'); + 'Middle center pixel should be yellow'); +}); + +test('Item#opacity', function() { + var layer = new Layer(); + var background = new Path.Rectangle({ + size: [100, 100], + fillColor: 'white' + }); + + var circle = new Path.Circle({ + radius: 25, + center: [50, 50], + fillColor: 'red' + }); + + const red = new Color(1, 0, 0, 1) + const white = new Color(1, 1, 1, 1) + + equals(layer.rasterize(72, false).getPixel(50, 50), red, + 'Center pixel should be red'); + circle.opacity = 0; + equals(layer.rasterize(72, false).getPixel(50, 50), white, + 'Center pixel should be white'); + circle.opacity = -1; + equals(layer.rasterize(72, false).getPixel(50, 50), white, + 'Center pixel should be white'); + circle.opacity = 1; + equals(layer.rasterize(72, false).getPixel(50, 50), red, + 'Center pixel should be red'); + circle.opacity = 2; + equals(layer.rasterize(72, false).getPixel(50, 50), red, + 'Center pixel should be red'); }); test('Item#applyMatrix', function() { @@ -945,3 +976,33 @@ test('Children global matrices are cleared after parent transformation', functio group.translate(100, 0); equals(item.localToGlobal(item.getPointAt(0)), new Point(100, 100)); }); + +test('Item#rasterize() with empty bounds', function() { + new Path.Line([0, 0], [100, 0]).rasterize(); + view.update(); + expect(0); +}); + +test('Item#draw() with CompoundPath as clip item', function() { + function createdClippedGroup(invertedOrder) { + var compound = new CompoundPath({ + children: [ + new Path.Circle(new Point(50, 50), 50), + new Path.Circle(new Point(100, 50), 50) + ], + fillRule: 'evenodd' + }); + + var rectangle = new Shape.Rectangle(new Point(0, 0), new Point(150, 50)); + + var group = new Group(); + group.children = invertedOrder + ? [compound, rectangle] + : [rectangle, compound]; + group.fillColor = 'black'; + group.clipped = true; + return group; + }; + + comparePixels(createdClippedGroup(true), createdClippedGroup(false)); +}); diff --git a/test/tests/Item_Bounds.js b/test/tests/Item_Bounds.js index f0b8ae03..b3c52f56 100644 --- a/test/tests/Item_Bounds.js +++ b/test/tests/Item_Bounds.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * @@ -722,7 +722,7 @@ test('path.strokeBounds with applyMatrix disabled', function() { testHitResult(); }); -test('TEST', function() { +test('path.strokeBounds with applyMatrix enabled', function() { var path = new Path.Rectangle({ applyMatrix: false, point: [10, 10], diff --git a/test/tests/Item_Cloning.js b/test/tests/Item_Cloning.js index 762701d0..404d79eb 100644 --- a/test/tests/Item_Cloning.js +++ b/test/tests/Item_Cloning.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * diff --git a/test/tests/Item_Getting.js b/test/tests/Item_Getting.js index 120d0500..79dd2688 100644 --- a/test/tests/Item_Getting.js +++ b/test/tests/Item_Getting.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * diff --git a/test/tests/Item_Order.js b/test/tests/Item_Order.js index b99814ef..77297a52 100644 --- a/test/tests/Item_Order.js +++ b/test/tests/Item_Order.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * diff --git a/test/tests/JSON.js b/test/tests/JSON.js index 1c2fe5af..8ea9ab88 100644 --- a/test/tests/JSON.js +++ b/test/tests/JSON.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * @@ -256,3 +256,10 @@ test('Path#importJSON()', function() { equals(function() { return layer.firstChild === path; }, true); equals(function() { return path.parent === layer; }, true); }); + +test('Item#importJSON() does not override Item#insert()', function() { + var path = new Path(); + equals(typeof path.insert, 'function'); + path.importJSON(path.exportJSON()); + equals(typeof path.insert, 'function'); +}); diff --git a/test/tests/Layer.js b/test/tests/Layer.js index c4d2a188..4f25826f 100644 --- a/test/tests/Layer.js +++ b/test/tests/Layer.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * @@ -139,3 +139,9 @@ test('#remove() with named layers', function(){ equals(removeCount, 2, 'project.layers[name].remove(); should be called twice'); }); + +test('#bounds with nested empty items', function() { + var item = new Path.Rectangle(new Point(10,10), new Size(10)); + new Group(new Group()); + equals(item.bounds, project.activeLayer.bounds); +}); diff --git a/test/tests/Matrix.js b/test/tests/Matrix.js index 6046eaf8..f35483b9 100644 --- a/test/tests/Matrix.js +++ b/test/tests/Matrix.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * diff --git a/test/tests/Numerical.js b/test/tests/Numerical.js index 01a5cb08..d7e74f86 100644 --- a/test/tests/Numerical.js +++ b/test/tests/Numerical.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * diff --git a/test/tests/PaperScript.js b/test/tests/PaperScript.js new file mode 100644 index 00000000..191e9b93 --- /dev/null +++ b/test/tests/PaperScript.js @@ -0,0 +1,48 @@ +/* + * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. + * http://paperjs.org/ + * + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ + * + * Distributed under the MIT license. See LICENSE file for details. + * + * All rights reserved. + */ + +QUnit.module('PaperScript'); + +function executeCode(code, expected) { + try { + equals(PaperScript.execute(code, paper), expected, code); + } catch (err) { + ok(false, err + ''); + } +} + +test('PaperScript with prefix decrement operators', function() { + executeCode( + 'var j = 0; for (var i = 10; i > 0; i--) { j++ }; module.exports = j', + 10 + ); + executeCode( + 'var x = 1; var y = 4 * --x; y; module.exports = x + " " + y', + '0 0' + ); +}); + +test('PaperScript with suffix increment operators', function() { + executeCode( + 'var j = 0; for (var i = 0; i < 10; ++i) { j++ }; module.exports = j', + 10 + ); + // #691 + executeCode( + 'var x = 1; x = x++; module.exports = x', + 1 + ); + executeCode( + 'var x = 1; var y = 4 * x++; y; module.exports = x + " " + y', + '2 4' + ); +}); diff --git a/test/tests/Path.js b/test/tests/Path.js index d985fe7b..d0522c90 100644 --- a/test/tests/Path.js +++ b/test/tests/Path.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * @@ -625,7 +625,7 @@ test('Path#getOffsetsWithTangent()', function() { 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++) { @@ -645,3 +645,21 @@ test('Path#arcTo(through, to) is on through point side (#1477)', function() { path.arcTo(p2, p3); equals(true, path.segments[1].point.x > p1.x); }); + +test('Path#arcTo(to, radius, rotation, clockwise, large) when from and to are equal (#1613)', function(){ + var point = new Point(10,10); + var path = new Path(); + path.moveTo(point); + path.arcTo(point, new Size(10), 0, true, true); + expect(0); +}); + +test('Path#closed with blend mode', function() { + new Path({ + strokeColor: 'black', + blendMode: 'negation', + closed: true + }); + view.update(); + expect(0); +}); diff --git a/test/tests/PathItem.js b/test/tests/PathItem.js index 2ac17864..cc91bd01 100644 --- a/test/tests/PathItem.js +++ b/test/tests/PathItem.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * @@ -165,4 +165,15 @@ test('PathItem#compare()', function() { equals(function() { return circle2.compare(circle); }, true, 'Comparing a circle with additional segments with an identical circle should return true.'); + + var compoundPath1 = PathItem.create('M50,300l0,-150l50,25l0,-75l200,0l0,200z M100,200l50,0l-50,-25z'); + var compoundPath2 = PathItem.create('M50,300l0,-150l50,25l0,-75l200,0l0,200z M100,175l0,25l50,0z'); + var compoundPath3 = PathItem.create('M50,300l0,-150l50,25l0,-75l200,0l0,210z M100,200l50,0l-50,-25z'); + + equals(function() { + return compoundPath1.compare(compoundPath2); + }, true, 'Comparing two compound paths with one child starting at a different point should return true.'); + equals(function() { + return compoundPath1.compare(compoundPath3); + }, false, 'Comparing two compound paths with one child having a different shape should return false.'); }); diff --git a/test/tests/PathItem_Contains.js b/test/tests/PathItem_Contains.js index 30367955..45aa60b4 100644 --- a/test/tests/PathItem_Contains.js +++ b/test/tests/PathItem_Contains.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * @@ -394,4 +394,10 @@ test('Path#contains() with Path#interiorPoint: #854, #1064', function() { } }); - +test('IPathtem#contains() with non-invertible matrices (#1651)', function() { + var path = new Path({ + matrix: new Matrix(0, 0, 0, 0, 0, 0) + }); + equals(path.contains(path.position), false, + 'A path with a non-invertible matrix cannot contain its position'); +}); diff --git a/test/tests/Path_Boolean.js b/test/tests/Path_Boolean.js index ed9061f8..6f9af42f 100644 --- a/test/tests/Path_Boolean.js +++ b/test/tests/Path_Boolean.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * @@ -219,7 +219,6 @@ test('#784', function() { 'M265.13434,453.46566l-0.03434,0.03434c0,0 -49.1,-14.5 -36.6,-36.6c7.48073,-13.22593 39.10093,-1.6319 63.28843,9.81157l16.18604,-16.18604c-8.05354,-21.53223 -15.90287,-47.40397 -10.27447,-54.42553c9.77623,-12.51358 31.40373,30.40618 32.36674,32.33326l0.03326,-0.03326c0,0.1 65,49.8 65,65c0,15.2 -33.8,65 -65,65c-30.62393,0 -63.75273,-62.62185 -64.96566,-64.93434z'); }); - test('#784#issuecomment-144653463', function() { var path1 = new Path({ segments: [ @@ -889,7 +888,7 @@ test('#1221', function() { compareBoolean(function() { return blob.subtract(rect2, { trace: false }); }, 'M534,273c-29.65069,-13.2581 -57.61955,-25.39031 -84,-36.46967M150,138.13156c-71.67127,-11.53613 -105.25987,0.10217 -120,19.86844c-40.5,54.3 31.5,210.2 111,222c3.08303,0.45637 6.07967,0.68158 9,0.69867M409.85616,400c18.87105,20.95032 39.82014,38.41763 69.14384,42c33.8,4.1 83.1,-9.7 150,-90'); compareBoolean(function() { return blob.subtract(rect2, { trace: true }); }, - 'M629,352c-66.9,80.3 -116.2,94.1 -150,90c-29.3237,-3.58237 -50.27279,-21.04968 -69.14384,-42h40.14384v-163.46967c26.38045,11.07937 54.34931,23.21157 84,36.46967M141,380c-79.5,-11.8 -151.5,-167.7 -111,-222c14.74013,-19.76627 48.32873,-31.40457 120,-19.86844v242.56712c-2.92033,-0.01709 -5.91697,-0.24231 -9,-0.69867z'); + 'M629,352c-66.9,80.3 -116.2,94.1 -150,90c-29.3237,-3.58237 -50.27279,-21.04968 -69.14384,-42h40.14384v-163.46967c26.38045,11.07937 54.34931,23.21157 84,36.46967zM141,380c-79.5,-11.8 -151.5,-167.7 -111,-222c14.74013,-19.76627 48.32873,-31.40457 120,-19.86844v242.56712c-2.92033,-0.01709 -5.91697,-0.24231 -9,-0.69867z'); var rect3 = new Path.Rectangle({ point: [150, 100], @@ -899,8 +898,7 @@ test('#1221', function() { compareBoolean(function() { return blob.subtract(rect3, { trace: false }); }, 'M534,273c-29.65069,-13.2581 -57.61955,-25.39031 -84,-36.46967M150,138.13156c-71.67127,-11.53613 -105.25987,0.10217 -120,19.86844c-40.5,54.3 31.5,210.2 111,222c60.8,9 88,-71.9 159,-66c81.6,6.8 99.6,118.3 179,128c33.8,4.1 83.1,-9.7 150,-90'); compareBoolean(function() { return blob.subtract(rect3, { trace: true }); }, - 'M629,352c-66.9,80.3 -116.2,94.1 -150,90c-79.4,-9.7 -97.4,-121.2 -179,-128c-71,-5.9 -98.2,75 -159,66c-79.5,-11.8 -151.5,-167.7 -111,-222c14.74013,-19.76627 48.32873,-31.40457 120,-19.86844v111.86844h300v-13.46967c26.38045,11.07937 54.34931,23.21157 84,36.46967'); - + 'M629,352c-66.9,80.3 -116.2,94.1 -150,90c-79.4,-9.7 -97.4,-121.2 -179,-128c-71,-5.9 -98.2,75 -159,66c-79.5,-11.8 -151.5,-167.7 -111,-222c14.74013,-19.76627 48.32873,-31.40457 120,-19.86844v111.86844h300v-13.46967c26.38045,11.07937 54.34931,23.21157 84,36.46967z'); var rect4 = new Path.Rectangle({ point: [200, 200], @@ -1178,7 +1176,7 @@ test('Isolated edge-cases from @iconexperience\'s boolean-test suite', function( ['M570,290l5.8176,33.58557l-28.17314,-14.439c-14.32289,2.0978 -28.17688,4.12693 -28.17688,4.12693l19.08162,-8.78834l-16.12739,-8.26544l27.9654,2.81326z', 'M538.5492,304.48516l11.83801,-5.45218l9.61279,0.96702l15.8176,23.58557z'], ['', ''], ['M419.20345,384.34607c5.84471,3.31306 8.73763,3.6008 9.66385,3.54578c0.42499,-0.23215 3.1344,-1.90017 6.91252,-9.02761c31.69382,-59.79056 16.74714,-243.86183 15.04159,-262.65257c-3.42808,-8.00965 -2.79156,-12.73288 -0.15048,-1.551l29.19538,-6.89574l-2.9917,-29.85552c34.20913,-3.42795 37.91991,47.22024 32.18962,22.95919c0.38462,1.62842 24.31406,221.98511 -20.27182,306.0966c-7.77973,14.6765 -19.84373,29.87172 -38.60351,37.019c-20.69209,7.88347 -41.8301,3.18373 -60.57335,-7.44083z', 'M450.8214,116.21167c-0.08956,-0.98671 -0.14261,-1.51768 -0.15048,-1.551l29.19538,-6.89574l2.99069,29.84549c-18.44587,1.84838 -28.02391,-12.02551 -32.0356,-21.39876z'], - ['M224.2508,238.43767c2.45124,34.92471 9.86647,54.1339 17.20117,64.07402c4.07985,5.52909 8.23969,8.37799 12.35502,9.97162c7.6805,2.97421 19.93299,3.22543 38.3191,-2.95557c81.13187,-27.27474 190.74601,-146.01269 198.63572,-155.10754c0.92148,-1.70426 1.39237,-2.07227 0.44507,-0.53656l25.35355,15.63944l29.94723,-4.25223c1.89126,13.31958 -6.82114,24.30584 -4.2348,20.11304c-5.40958,8.76963 -129.41512,146.85628 -231.02757,181.01613c-24.82193,8.34458 -52.91092,12.17808 -79.10499,2.03465c-15.35707,-5.9469 -28.53272,-16.15682 -38.96756,-30.29832c-16.97074,-22.99908 -25.95119,-55.26903 -28.7747,-95.49783z', 'M490.76181,154.42019c0.26299,-0.30316 0.41295,-0.48449 0.44507,-0.53656l25.35355,15.63944l-29.45692,4.18261c-1.21879,-8.58358 1.97318,-16.16889 3.6583,-19.28549z'] + ['M224.2508,238.43767c2.45124,34.92471 9.86647,54.1339 17.20117,64.07402c4.07985,5.52909 8.23969,8.37799 12.35502,9.97162c7.6805,2.97421 19.93299,3.22543 38.3191,-2.95557c81.13187,-27.27474 190.74601,-146.01269 198.63572,-155.10754c0.92148,-1.70426 1.39237,-2.07227 0.44507,-0.53656l25.35355,15.63944l29.94723,-4.25223c1.89126,13.31958 -6.82114,24.30584 -4.2348,20.11304c-5.40958,8.76963 -129.41512,146.85628 -231.02757,181.01613c-24.82193,8.34458 -52.91092,12.17808 -79.10499,2.03465c-15.35707,-5.9469 -28.53272,-16.15682 -38.96756,-30.29832c-16.97074,-22.99908 -25.95119,-55.26903 -28.7747,-95.49783z', 'M490.76181,154.42020c0.26299,-0.30316 0.41295,-0.48449 0.44507,-0.53656l25.35355,15.63944l-29.45692,4.18261c-1.21879,-8.58358 1.97318,-16.16889 3.6583,-19.28549z'] ]; for (var i = 0; i < paths.length; i++) { var entry = paths[i], @@ -1202,4 +1200,54 @@ test('#1513', function () { 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); -}); \ No newline at end of file +}); + +test('#1419', function() { + var circle1 = Path.Circle({ + center: [0, 0], + radius: 50 + }); + var circle2 = Path.Circle({ + center: circle1.position.subtract(25), + radius: 50 + }); + var result = 'M-50,0c0,-27.61424 22.38576,-50 50,-50c7.33673,0 14.30439,1.5802 20.58119,4.41881c-7.84546,-17.34804 -25.30368,-29.41881 -45.58119,-29.41881c-27.61424,0 -50,22.38576 -50,50c0,20.27751 12.07077,37.73573 29.41881,45.58119c-2.83861,-6.2768 -4.41881,-13.24446 -4.41881,-20.58119zM50,0c0,27.61424 -22.38576,50 -50,50c-20.27751,0 -37.73573,-12.07077 -45.58119,-29.41881c6.2768,2.83861 13.24446,4.41881 20.58119,4.41881c27.61424,0 50,-22.38576 50,-50c0,-7.33673 -1.5802,-14.30439 -4.41881,-20.58119c17.34804,7.84546 29.41881,25.30368 29.41881,45.58119z'; + compareBoolean(circle1.exclude(circle2), result); +}); + +test('#1351', function() { + var path1 = new CompoundPath(new Path([10, 10], [100, 100]), new Path([150, 25])); + var path2 = new Path([20, 80], [80, 20]); + var result = ''; // empty! + compareBoolean(path1.unite(path2), result); +}); + +test('#1647', function() { + var path1 = PathItem.create("M0,0h60v60h-60z"); + var path2 = PathItem.create("M60,47c-0.89493,-0.00177 -1.72254,0.47497 -2.17,1.25l-12.12,21c-0.44703,0.77427 -0.44656,1.72831 0.00123,2.50214c0.44779,0.77383 1.27472,1.24963 2.16877,1.24786h24.24c0.89405,0.00177 1.72098,-0.47403 2.16877,-1.24786c0.44779,-0.77383 0.44826,-1.72787 0.00123,-2.50214l-12.12,-21c-0.44746,-0.77503 -1.27507,-1.25177 -2.17,-1.25M64.33,47l12.12,21c0.89315,1.54699 0.89316,3.45295 0.00003,4.99995c-0.89313,1.547 -2.54373,2.50001 -4.33003,2.50005h-24.24c-1.78631,-0.00005 -3.4369,-0.95306 -4.33003,-2.50005c-0.89313,-1.547 -0.89312,-3.45296 0.00003,-4.99995l12.12,-21c0.88236,-1.55771 2.53981,-2.51466 4.33,-2.5c1.79019,-0.01466 3.44764,0.94229 4.33,2.5"); + path1.fillColor = path2.fillColor = 'black'; + var result = 'M57.83,48.25l-6.78143,11.75l-2.88143,0l7.50286,-13c0.88236,-1.55771 2.53981,-2.51466 4.33,-2.5v2.5c-0.89493,-0.00177 -1.72254,0.47497 -2.17,1.25z'; + compareBoolean(path1.intersect(path2), result); +}); + +test('#1619', function() { + var path1 = new Path.Rectangle({ + from: [200, 600], + to: [400, 300] + }); + + var path2 = new CompoundPath({ + children: [ + new Path({ + segments: [[420,320],[380,580],[220,580],[220,320]], + closed: true + }), + new Path({ + segments: [[313.36486,413.71682],[243.351,483.70296],[313.33714,553.71682],[383.351,483.73068]], + closed: true + }) + ] + }); + var result = 'M380,580h-160v-260l180,0v130zM313.36486,413.71682l-70.01386,69.98614l69.98614,70.01386l70.01386,-69.98614z'; + compareBoolean(path1.intersect(path2), result); +}); diff --git a/test/tests/Path_Constructors.js b/test/tests/Path_Constructors.js index 220f0ca4..f6dd9c50 100644 --- a/test/tests/Path_Constructors.js +++ b/test/tests/Path_Constructors.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * diff --git a/test/tests/Path_Intersections.js b/test/tests/Path_Intersections.js index 3d079706..2d316479 100644 --- a/test/tests/Path_Intersections.js +++ b/test/tests/Path_Intersections.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * @@ -343,3 +343,68 @@ test('#1284', function() { var path2 = createPath(curve2); testIntersections(path1.getIntersections(path2), expected); }); + +test('#1638', function() { + var circle1 = new Path.Circle({ + center: [100, 100], + radius: 100 + }); + var circle2 = new Path.Circle({ + center: [150, 150], + radius: 100 + }); + testIntersections(circle1.getIntersections(circle2), [ + { point: { x: 191.16238, y: 58.83762 }, index: 1, time: 0.73431, crossing: true }, + { point: { x: 58.83762, y: 191.16238 }, index: 3, time: 0.26569, crossing: true } + ]); +}); + +test('#1263', function() { + var path = new Path({ + segments: [ + [[479,495], [0,0], [-10,4]], + [[437,479], [5,12], [-22,-51]], + [[479,495], [33,-15]] + ] + }); + testIntersections(path.getIntersections(), [ + { point: { x: 479, y: 495 }, index: 0, time: 0, crossing: false } + ]); +}); + +test('#1262', function() { + var c1 = new Curve([561.5500544, 629.1148694240001, 564.581554256, 629.1148694240001, 567.0556160000001, 631.588931168, 567.0556160000001, 634.620431024]); + var c2 = new Curve([561.5500544, 629.1148694240001, 564.581554256, 629.1148694240001, 567.0556160000001, 631.592372144, 567.0556160000001, 634.620431024]); + testIntersections(c1.getIntersections(c2), [ + { point: { x: 561.55005, y: 629.11487 }, time: 0 }, + { point: { x: 567.05562, y: 634.62043 }, time: 1 } + ]); +}); + +test('#1409', function() { + var path1 = new Path({ + segments: [[20, 20], [20, 80], [80, 80], [80, 20]], + closed: true + }); + var path2 = new Path({ + segments: [[80, 20], [80, 80], [140, 80], [140, 20]], + closed: true + }); + testIntersections(path1.getCrossings(path2), []); + + var rect1 = new Path.Rectangle(new Point(100, 100), new Size(100, 100)); + var rect2 = rect1.clone(); + testIntersections(rect1.getCrossings(rect2), []); + + var circ1 = new Path.Circle(new Point(300,300), 40); + var circ2 = circ1.clone(); + testIntersections(circ1.getCrossings(circ2), []); +}); + +test('#1255', function() { + var c1 = new Curve([439.3824078319993,216.13903749801196],[-0.06618902689025684,-11.053567490673387],[-9.709467619869528,5.2831783801853],[455.0432856400729,189.60783460905492]); + var c2 = new Curve([446.1268928788026,138.78016797936476],[-4.688929299417313,19.846766368483937],[-0.19057652660425625,-31.82627994291222],[439.3824078319993,216.13903749801196]); + testIntersections(c1.getIntersections(c2), [ + { point: { x: 439.38241, y: 216.13904 }, time: 0, crossing: false } + ]); +}); diff --git a/test/tests/Point.js b/test/tests/Point.js index 41a5d1e6..e14aa18b 100644 --- a/test/tests/Point.js +++ b/test/tests/Point.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * diff --git a/test/tests/Project.js b/test/tests/Project.js index ca970d01..2626f95e 100644 --- a/test/tests/Project.js +++ b/test/tests/Project.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * diff --git a/test/tests/Raster.js b/test/tests/Raster.js index e7f7b6b6..baa2aad0 100644 --- a/test/tests/Raster.js +++ b/test/tests/Raster.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * @@ -206,3 +206,31 @@ test('Raster#setSmoothing setting does not impact canvas context', function(asse done(); }; }); + +test('new Raster(size[, position])', function() { + // Size only. + var raster = new Raster(new Size(100, 100)); + equals(raster.position, new Point(0, 0)); + equals(raster.bounds, new Rectangle(-50, -50, 100, 100)); + + var raster = new Raster({size:new Size(100, 100)}); + equals(raster.position, new Point(0, 0)); + equals(raster.bounds, new Rectangle(-50, -50, 100, 100)); + + var raster = new Raster({width:100, height:100}); + equals(raster.position, new Point(0, 0)); + equals(raster.bounds, new Rectangle(-50, -50, 100, 100)); + + // Size and position. + var raster = new Raster(new Size(100, 100), new Point(100, 100)); + equals(raster.position, new Point(100, 100)); + equals(raster.bounds, new Rectangle(50, 50, 100, 100)); + + var raster = new Raster({size:new Size(100, 100), position:new Point(100, 100)}); + equals(raster.position, new Point(100, 100)); + equals(raster.bounds, new Rectangle(50, 50, 100, 100)); + + var raster = new Raster({width:100, height:100, position:new Point(100, 100)}); + equals(raster.position, new Point(100, 100)); + equals(raster.bounds, new Rectangle(50, 50, 100, 100)); +}); diff --git a/test/tests/Rectangle.js b/test/tests/Rectangle.js index af17a14d..dcfd2e67 100644 --- a/test/tests/Rectangle.js +++ b/test/tests/Rectangle.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * diff --git a/test/tests/Segment.js b/test/tests/Segment.js index 3a2107af..b283a230 100644 --- a/test/tests/Segment.js +++ b/test/tests/Segment.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * diff --git a/test/tests/Shape.js b/test/tests/Shape.js index cc5b59fd..6b3f442b 100644 --- a/test/tests/Shape.js +++ b/test/tests/Shape.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * @@ -53,3 +53,34 @@ test('shape.toPath().toShape()', function() { equals(shape.toPath().toShape(), shape, name + '.toPath().toShape()'); }); }); + +test('new Shape.Rectangle() with position set before size', function() { + var shape1 = new Shape.Rectangle({ + position: [0, 0], + size: new Size(100, 100) + }); + equals(shape1.bounds.width, 100); +}); + +test('Shape.Rectangle radius works with negative size', function() { + var shape = new Shape.Rectangle({ + center: [50, 50], + size: 50, + fillColor: 'black' + }); + + shape.size = [-25, -25]; + + equals(shape.radius.width, 0); + equals(shape.radius.height, 0); + + shape.radius = [10, 50]; + shape.size = [50, -25]; + + equals(shape.radius.width, 10); + equals(shape.radius.height, 12.5); + + shape.size = [50, 75]; + + equals(shape.radius.height, 12.5); +}); diff --git a/test/tests/Size.js b/test/tests/Size.js index df21a771..68a87da0 100644 --- a/test/tests/Size.js +++ b/test/tests/Size.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * diff --git a/test/tests/Style.js b/test/tests/Style.js index b0d84183..7a780302 100644 --- a/test/tests/Style.js +++ b/test/tests/Style.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * @@ -192,3 +192,30 @@ test('setting Group#fillColor and #strokeColor 2', function() { // The second path still has its strokeColor set to red: equals(secondPath.strokeColor, new Color('red'), 'secondPath.strokeColor'); }); + +test('Color change propagation (#1672)', function(assert) { + // We use this trick to take a snapshot of the current canvas content + // without any kind of side effect that `item.rasterize()` or other + // techniques would have. + function getDataURL() { + view.update(); + return view.context.canvas.toDataURL(); + } + + var item = new Path.Circle({ + center: view.center, + radius: 70, + fillColor: 'red' + }); + var imageDataBefore = getDataURL(); + + // Change style property and check that change was detected. + item.fillColor.hue += 100; + var imageDataAfter = getDataURL(); + + // We are limited to check that both snapshots are different. + equals( + imageDataBefore !== imageDataAfter, true, + 'Canvas content should change after a change of item.fillColor.' + ); +}); diff --git a/test/tests/SvgExport.js b/test/tests/SvgExport.js index a68f6cd3..fb588a00 100644 --- a/test/tests/SvgExport.js +++ b/test/tests/SvgExport.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * @@ -113,7 +113,13 @@ test('Export SVG path at precision 0', function() { equals(path.exportSVG({ precision: 0 }).getAttribute('d'), 'M0,2l1,1'); }); -if (!isNode) { +test('Export SVG viewbox attribute with top left at origin', function() { + var path = new Path.Rectangle(new Point(10, 10), new Size(80)); + var rectangle = new Rectangle(new Point(0, 0), new Size(100)); + equals(project.exportSVG({ bounds: rectangle }).getAttribute('viewBox'), '0,0,100,100'); +}); + +if (!isNodeContext) { // JSDom does not have SVG rendering, so we can't test there. test('Export transformed shapes', function(assert) { var rect = new Shape.Rectangle({ @@ -149,6 +155,17 @@ if (!isNode) { compareSVG(assert.async(), svg, project.activeLayer); }); + test('Export not invertible item.matrix', function(assert) { + var rect = new Shape.Rectangle({ + point: [100, 100], + size: [100, 100], + fillColor: 'red', + matrix: [1, 1, 1, 1, 1, 1] + }); + var svg = project.exportSVG({ bounds: 'content', asString: true }); + compareSVG(assert.async(), svg, project.activeLayer); + }); + test('Export gradients', function(assert) { var bounds = new Rectangle(new Size(300, 600)); var stops = [new Color(1, 1, 0, 0), 'red', 'black']; @@ -212,4 +229,19 @@ if (!isNode) { tolerance: 1e-2 }); }); + + test('Export symbol with stroke', function(assert) { + var item = new Path.Circle({ + center: [0, 0], + radius: 50, + strokeColor: 'blue', + strokeWidth: 10 + }); + + var symbol = new Symbol(item); + symbol.place([50, 50]); + + var svg = project.exportSVG({ bounds: 'content', asString: true }); + compareSVG(assert.async(), svg, project.activeLayer); + }); } diff --git a/test/tests/SvgImport.js b/test/tests/SvgImport.js index a7e256bf..a8c1f145 100644 --- a/test/tests/SvgImport.js +++ b/test/tests/SvgImport.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * @@ -143,6 +143,31 @@ test('Import SVG without insertion', function() { }, true); }); +test('Import SVG switch', function(assert) { + var done = assert.async(); + var svg = ''; + paper.project.importSVG(svg, { + onLoad: function(item) { + equals(item.className, 'Group'); + equals(item.children.length, 1); + equals(item.firstChild.className, 'Group'); + equals(item.firstChild.children.length, 1); + equals(item.firstChild.firstChild, new Path([new Point(0, 0), new Point(10, 10)])); + done(); + } + }); +}); + +test('Import SVG string with leading line-breaks', function() { + var svg = '\n\n \n\n' + var imported = paper.project.importSVG(svg); + equals(imported.children.length, 1); + equals(imported.firstChild, new Shape.Rectangle({ + size: [100, 100], + fillColor: 'red' + })); +}); + function importSVG(assert, url, message, options) { var done = assert.async(); project.importSVG(url, { @@ -166,7 +191,7 @@ function importSVG(assert, url, message, options) { }); } -if (!isNode) { +if (!isNodeContext) { // JSDom does not have SVG rendering, so we can't test there. var svgFiles = { 'butterfly': { tolerance: 1e-2 }, @@ -176,16 +201,18 @@ if (!isNode) { 'symbol': {}, 'symbols': {}, 'blendModes': {}, - 'gradients-1': {} + 'gradients-1': {}, + 'gradients-2': !isPhantomContext && {}, + 'gradients-3': {}, + 'gradients-4': {} }; - // TODO: Investigate why Phantom struggles with this file: - if (!isPhantom) - svgFiles['gradients-2'] = {}; Base.each(svgFiles, function(options, name) { - name += '.svg'; - test('Import ' + name, function(assert) { - importSVG(assert, 'assets/' + name, null, options); - }); + if (options) { + name += '.svg'; + test('Import ' + name, function(assert) { + importSVG(assert, 'assets/' + name, null, options); + }); + } }); test('Import inexistent file', function(assert) { diff --git a/test/tests/SymbolItem.js b/test/tests/SymbolItem.js index 23957210..5a8cd983 100644 --- a/test/tests/SymbolItem.js +++ b/test/tests/SymbolItem.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * @@ -143,3 +143,18 @@ test('SymbolItem#bounds with #applyMatrix = false', function() { equals(function() { return placed.bounds; }, { x: 150, y: 150, width: 100, height: 100 }); }); + +test('SymbolItem#hitTestAll', function() { + var symbol = new SymbolDefinition( + new Path.Circle({ + center: [0, 0], + radius: 10, + fillColor: 'orange' + }) + ); + var symbolItem = symbol.place([50, 50]); + + var hitTestAll = symbolItem.hitTestAll([50, 50]); + equals(hitTestAll.length, 1); + equals(hitTestAll[0].item.id, symbolItem.id); +}); diff --git a/test/tests/TextItem.js b/test/tests/TextItem.js index 4223f249..ebd24889 100644 --- a/test/tests/TextItem.js +++ b/test/tests/TextItem.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * diff --git a/test/tests/load.js b/test/tests/load.js index 17e34a9a..b51fd592 100644 --- a/test/tests/load.js +++ b/test/tests/load.js @@ -2,8 +2,8 @@ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * @@ -63,7 +63,9 @@ /*#*/ include('Numerical.js'); +/*#*/ include('PaperScript.js'); + // There is no need to test interactions in node context. -if (!isNode) { +if (!isNodeContext) { /*#*/ include('Interactions.js'); } diff --git a/travis/deploy-prebuilt.sh b/travis/deploy-prebuilt.sh index 491eabc8..1286b1ed 100755 --- a/travis/deploy-prebuilt.sh +++ b/travis/deploy-prebuilt.sh @@ -3,8 +3,8 @@ # Paper.js - The Swiss Army Knife of Vector Graphics Scripting. # http://paperjs.org/ # -# Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey -# http://scratchdisk.com/ & http://jonathanpuckey.com/ +# Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey +# http://juerglehni.com/ & https://puckey.studio/ # # Distributed under the MIT license. See LICENSE file for details. # diff --git a/travis/install-assets.sh b/travis/install-assets.sh index 9ca3d767..565fd2ed 100755 --- a/travis/install-assets.sh +++ b/travis/install-assets.sh @@ -3,8 +3,8 @@ # Paper.js - The Swiss Army Knife of Vector Graphics Scripting. # http://paperjs.org/ # -# Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey -# http://scratchdisk.com/ & http://jonathanpuckey.com/ +# Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey +# http://juerglehni.com/ & https://puckey.studio/ # # Distributed under the MIT license. See LICENSE file for details. # diff --git a/travis/setup-git.sh b/travis/setup-git.sh index fbc225bf..8e4c72ab 100755 --- a/travis/setup-git.sh +++ b/travis/setup-git.sh @@ -3,8 +3,8 @@ # Paper.js - The Swiss Army Knife of Vector Graphics Scripting. # http://paperjs.org/ # -# Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey -# http://scratchdisk.com/ & http://jonathanpuckey.com/ +# Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey +# http://juerglehni.com/ & https://puckey.studio/ # # Distributed under the MIT license. See LICENSE file for details. #