From 13f88d6b725cba75e198e5c130806c6f9ef6ce4d Mon Sep 17 00:00:00 2001 From: Adam Greenan Date: Wed, 11 Dec 2024 11:00:02 +0000 Subject: [PATCH 1/7] init --- .github/FUNDING.yml | 12 - .github/ISSUE_TEMPLATE.md | 7 - .github/workflows/release.yml | 23 - .github/workflows/test-browser.yml | 24 - .github/workflows/test-node.yml | 24 - .gitignore | 66 - CHANGELOG.md | 1363 ------ LICENSE | 22 - README.md | 205 - eslint.config.mjs | 3 - gulpfile.mjs | 110 - konva-node/demo.js | 77 - konva-node/index.js | 39 - konva-node/package.json | 32 - package.json | 110 - release.sh | 77 - rename-imports.mjs | 47 - resources/doc-includes/ContainerParams.txt | 6 - resources/doc-includes/NodeParams.txt | 20 - resources/doc-includes/ShapeParams.txt | 53 - resources/jsdoc.conf.json | 28 - rollup.config.cjs | 47 - src/Animation.ts | 237 - src/BezierFunctions.ts | 826 ---- src/Canvas.ts | 193 - src/Container.ts | 658 --- src/Context.ts | 989 ---- src/Core.ts | 5 - src/DragAndDrop.ts | 173 - src/Factory.ts | 248 - src/FastLayer.ts | 29 - src/Global.ts | 195 - src/Group.ts | 30 - src/Layer.ts | 544 --- src/Node.ts | 3328 ------------- src/PointerEvents.ts | 67 - src/Shape.ts | 2037 -------- src/Stage.ts | 978 ---- src/Tween.ts | 805 ---- src/Util.ts | 1045 ---- src/Validators.ts | 210 - src/_CoreInternals.ts | 45 - src/_FullInternals.ts | 89 - src/filters/Blur.ts | 390 -- src/filters/Brighten.ts | 45 - src/filters/Contrast.ts | 74 - src/filters/Emboss.ts | 202 - src/filters/Enhance.ts | 150 - src/filters/Grayscale.ts | 25 - src/filters/HSL.ts | 107 - src/filters/HSV.ts | 107 - src/filters/Invert.ts | 23 - src/filters/Kaleidoscope.ts | 272 -- src/filters/Mask.ts | 209 - src/filters/Noise.ts | 43 - src/filters/Pixelate.ts | 121 - src/filters/Posterize.ts | 45 - src/filters/RGB.ts | 81 - src/filters/RGBA.ts | 102 - src/filters/Sepia.ts | 27 - src/filters/Solarize.ts | 48 - src/filters/Threshold.ts | 43 - src/index-node.ts | 27 - src/index-types.d.ts | 181 - src/index.ts | 3 - src/shapes/Arc.ts | 170 - src/shapes/Arrow.ts | 230 - src/shapes/Circle.ts | 75 - src/shapes/Ellipse.ts | 120 - src/shapes/Image.ts | 304 -- src/shapes/Label.ts | 385 -- src/shapes/Line.ts | 357 -- src/shapes/Path.ts | 942 ---- src/shapes/Rect.ts | 78 - src/shapes/RegularPolygon.ts | 129 - src/shapes/Ring.ts | 93 - src/shapes/Sprite.ts | 369 -- src/shapes/Star.ts | 124 - src/shapes/Text.ts | 977 ---- src/shapes/TextPath.ts | 571 --- src/shapes/Transformer.ts | 1874 ------- src/shapes/Wedge.ts | 128 - src/types.ts | 84 - test/assets/bunny.png | Bin 449 -> 0 bytes test/assets/darth-vader.jpg | Bin 117044 -> 0 bytes test/assets/lion.png | Bin 19826 -> 0 bytes test/assets/scorpion-sprite.png | Bin 26550 -> 0 bytes test/assets/tiger.ts | 1313 ----- test/assets/worldMap.ts | 346 -- test/bunnies.html | 200 - test/ifame.html | 14 - test/import-test.cjs | 6 - test/import-test.mjs | 18 - test/manual-tests.html | 70 - test/manual/Blur-test.ts | 279 -- test/manual/Brighten-test.ts | 140 - test/manual/Contrast-test.ts | 100 - test/manual/Emboss-test.ts | 91 - test/manual/Enhance-test.ts | 77 - test/manual/Filter-test.ts | 27 - test/manual/Grayscale-test.ts | 78 - test/manual/HSL-test.ts | 125 - test/manual/HSV-test.ts | 166 - test/manual/Invert-test.ts | 78 - test/manual/Kaleidoscope-test.ts | 127 - test/manual/Manual-test.ts | 416 -- test/manual/Mask-test.ts | 38 - test/manual/Noise-test.ts | 45 - test/manual/Pixelate-test.ts | 65 - test/manual/Posterize-test.ts | 45 - test/manual/RGB-test.ts | 109 - test/manual/RGBA-test.ts | 72 - test/manual/Sepia-test.ts | 78 - test/manual/Solarize-test.ts | 30 - test/manual/Threshold-test.ts | 45 - test/node-global-setup.mjs | 11 - test/package.json | 3 - test/performance/bunnies_native.html | 155 - test/performance/creating_elements.html | 107 - test/performance/jump-shape.html | 166 - test/runner.js | 511 -- test/sandbox.html | 71 - test/text-paths.html | 419 -- test/tsconfig.json | 10 - test/unit-tests.html | 80 - test/unit/Animation-test.ts | 130 - test/unit/Arc-test.ts | 204 - test/unit/Arrow-test.ts | 261 - test/unit/AutoDraw-test.ts | 163 - test/unit/Blob-test.ts | 121 - test/unit/Canvas-test.ts | 42 - test/unit/Circle-test.ts | 320 -- test/unit/Container-test.ts | 2731 ----------- test/unit/Context-test.ts | 118 - test/unit/DragAndDrop-test.ts | 1282 ----- test/unit/DragAndDropEvents-test.ts | 662 --- test/unit/Ellipse-test.ts | 116 - test/unit/Global-test.ts | 22 - test/unit/Group-test.ts | 118 - test/unit/Image-test.ts | 451 -- test/unit/Label-test.ts | 376 -- test/unit/Layer-test.ts | 452 -- test/unit/Line-test.ts | 716 --- test/unit/MouseEvents-test.ts | 2443 ---------- test/unit/Node-cache-test.ts | 1551 ------ test/unit/Node-test.ts | 3845 --------------- test/unit/Path-test.ts | 1669 ------- test/unit/PointerEvents-test.ts | 229 - test/unit/Polygon-test.ts | 25 - test/unit/Rect-test.ts | 237 - test/unit/RegularPolygon-test.ts | 209 - test/unit/Ring-test.ts | 92 - test/unit/Shape-test.ts | 2338 --------- test/unit/Spline-test.ts | 113 - test/unit/Sprite-test.ts | 464 -- test/unit/Stage-test.ts | 1456 ------ test/unit/Star-test.ts | 153 - test/unit/Text-test.ts | 1761 ------- test/unit/TextPath-test.ts | 906 ---- test/unit/TouchEvents-test.ts | 849 ---- test/unit/Transformer-test.ts | 5091 -------------------- test/unit/Tween-test.ts | 410 -- test/unit/Util-test.ts | 119 - test/unit/Wedge-test.ts | 86 - test/unit/imagediff.ts | 332 -- test/unit/test-utils.ts | 392 -- tsconfig.json | 24 - tsconfig.testing.json | 10 - 168 files changed, 63281 deletions(-) delete mode 100644 .github/FUNDING.yml delete mode 100644 .github/ISSUE_TEMPLATE.md delete mode 100644 .github/workflows/release.yml delete mode 100644 .github/workflows/test-browser.yml delete mode 100644 .github/workflows/test-node.yml delete mode 100644 .gitignore delete mode 100644 CHANGELOG.md delete mode 100644 LICENSE delete mode 100644 README.md delete mode 100644 eslint.config.mjs delete mode 100644 gulpfile.mjs delete mode 100644 konva-node/demo.js delete mode 100644 konva-node/index.js delete mode 100644 konva-node/package.json delete mode 100644 package.json delete mode 100755 release.sh delete mode 100644 rename-imports.mjs delete mode 100644 resources/doc-includes/ContainerParams.txt delete mode 100644 resources/doc-includes/NodeParams.txt delete mode 100644 resources/doc-includes/ShapeParams.txt delete mode 100644 resources/jsdoc.conf.json delete mode 100644 rollup.config.cjs delete mode 100644 src/Animation.ts delete mode 100644 src/BezierFunctions.ts delete mode 100644 src/Canvas.ts delete mode 100644 src/Container.ts delete mode 100644 src/Context.ts delete mode 100644 src/Core.ts delete mode 100644 src/DragAndDrop.ts delete mode 100644 src/Factory.ts delete mode 100644 src/FastLayer.ts delete mode 100644 src/Global.ts delete mode 100644 src/Group.ts delete mode 100644 src/Layer.ts delete mode 100644 src/Node.ts delete mode 100644 src/PointerEvents.ts delete mode 100644 src/Shape.ts delete mode 100644 src/Stage.ts delete mode 100644 src/Tween.ts delete mode 100644 src/Util.ts delete mode 100644 src/Validators.ts delete mode 100644 src/_CoreInternals.ts delete mode 100644 src/_FullInternals.ts delete mode 100644 src/filters/Blur.ts delete mode 100644 src/filters/Brighten.ts delete mode 100644 src/filters/Contrast.ts delete mode 100644 src/filters/Emboss.ts delete mode 100644 src/filters/Enhance.ts delete mode 100644 src/filters/Grayscale.ts delete mode 100644 src/filters/HSL.ts delete mode 100644 src/filters/HSV.ts delete mode 100644 src/filters/Invert.ts delete mode 100644 src/filters/Kaleidoscope.ts delete mode 100644 src/filters/Mask.ts delete mode 100644 src/filters/Noise.ts delete mode 100644 src/filters/Pixelate.ts delete mode 100644 src/filters/Posterize.ts delete mode 100644 src/filters/RGB.ts delete mode 100644 src/filters/RGBA.ts delete mode 100644 src/filters/Sepia.ts delete mode 100644 src/filters/Solarize.ts delete mode 100644 src/filters/Threshold.ts delete mode 100644 src/index-node.ts delete mode 100644 src/index-types.d.ts delete mode 100644 src/index.ts delete mode 100644 src/shapes/Arc.ts delete mode 100644 src/shapes/Arrow.ts delete mode 100644 src/shapes/Circle.ts delete mode 100644 src/shapes/Ellipse.ts delete mode 100644 src/shapes/Image.ts delete mode 100644 src/shapes/Label.ts delete mode 100644 src/shapes/Line.ts delete mode 100644 src/shapes/Path.ts delete mode 100644 src/shapes/Rect.ts delete mode 100644 src/shapes/RegularPolygon.ts delete mode 100644 src/shapes/Ring.ts delete mode 100644 src/shapes/Sprite.ts delete mode 100644 src/shapes/Star.ts delete mode 100644 src/shapes/Text.ts delete mode 100644 src/shapes/TextPath.ts delete mode 100644 src/shapes/Transformer.ts delete mode 100644 src/shapes/Wedge.ts delete mode 100644 src/types.ts delete mode 100644 test/assets/bunny.png delete mode 100644 test/assets/darth-vader.jpg delete mode 100644 test/assets/lion.png delete mode 100644 test/assets/scorpion-sprite.png delete mode 100644 test/assets/tiger.ts delete mode 100644 test/assets/worldMap.ts delete mode 100644 test/bunnies.html delete mode 100644 test/ifame.html delete mode 100644 test/import-test.cjs delete mode 100644 test/import-test.mjs delete mode 100644 test/manual-tests.html delete mode 100644 test/manual/Blur-test.ts delete mode 100644 test/manual/Brighten-test.ts delete mode 100644 test/manual/Contrast-test.ts delete mode 100644 test/manual/Emboss-test.ts delete mode 100644 test/manual/Enhance-test.ts delete mode 100644 test/manual/Filter-test.ts delete mode 100644 test/manual/Grayscale-test.ts delete mode 100644 test/manual/HSL-test.ts delete mode 100644 test/manual/HSV-test.ts delete mode 100644 test/manual/Invert-test.ts delete mode 100644 test/manual/Kaleidoscope-test.ts delete mode 100644 test/manual/Manual-test.ts delete mode 100644 test/manual/Mask-test.ts delete mode 100644 test/manual/Noise-test.ts delete mode 100644 test/manual/Pixelate-test.ts delete mode 100644 test/manual/Posterize-test.ts delete mode 100644 test/manual/RGB-test.ts delete mode 100644 test/manual/RGBA-test.ts delete mode 100644 test/manual/Sepia-test.ts delete mode 100644 test/manual/Solarize-test.ts delete mode 100644 test/manual/Threshold-test.ts delete mode 100644 test/node-global-setup.mjs delete mode 100644 test/package.json delete mode 100644 test/performance/bunnies_native.html delete mode 100644 test/performance/creating_elements.html delete mode 100644 test/performance/jump-shape.html delete mode 100644 test/runner.js delete mode 100644 test/sandbox.html delete mode 100644 test/text-paths.html delete mode 100644 test/tsconfig.json delete mode 100644 test/unit-tests.html delete mode 100644 test/unit/Animation-test.ts delete mode 100644 test/unit/Arc-test.ts delete mode 100644 test/unit/Arrow-test.ts delete mode 100644 test/unit/AutoDraw-test.ts delete mode 100644 test/unit/Blob-test.ts delete mode 100644 test/unit/Canvas-test.ts delete mode 100644 test/unit/Circle-test.ts delete mode 100644 test/unit/Container-test.ts delete mode 100644 test/unit/Context-test.ts delete mode 100644 test/unit/DragAndDrop-test.ts delete mode 100644 test/unit/DragAndDropEvents-test.ts delete mode 100644 test/unit/Ellipse-test.ts delete mode 100644 test/unit/Global-test.ts delete mode 100644 test/unit/Group-test.ts delete mode 100644 test/unit/Image-test.ts delete mode 100644 test/unit/Label-test.ts delete mode 100644 test/unit/Layer-test.ts delete mode 100644 test/unit/Line-test.ts delete mode 100644 test/unit/MouseEvents-test.ts delete mode 100644 test/unit/Node-cache-test.ts delete mode 100644 test/unit/Node-test.ts delete mode 100644 test/unit/Path-test.ts delete mode 100644 test/unit/PointerEvents-test.ts delete mode 100644 test/unit/Polygon-test.ts delete mode 100644 test/unit/Rect-test.ts delete mode 100644 test/unit/RegularPolygon-test.ts delete mode 100644 test/unit/Ring-test.ts delete mode 100644 test/unit/Shape-test.ts delete mode 100644 test/unit/Spline-test.ts delete mode 100644 test/unit/Sprite-test.ts delete mode 100644 test/unit/Stage-test.ts delete mode 100644 test/unit/Star-test.ts delete mode 100644 test/unit/Text-test.ts delete mode 100644 test/unit/TextPath-test.ts delete mode 100644 test/unit/TouchEvents-test.ts delete mode 100644 test/unit/Transformer-test.ts delete mode 100644 test/unit/Tween-test.ts delete mode 100644 test/unit/Util-test.ts delete mode 100644 test/unit/Wedge-test.ts delete mode 100644 test/unit/imagediff.ts delete mode 100644 test/unit/test-utils.ts delete mode 100644 tsconfig.json delete mode 100644 tsconfig.testing.json diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index 9438f242d..000000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1,12 +0,0 @@ -# These are supported funding model platforms - -github: [lavrton] -patreon: lavrton -open_collective: konva -ko_fi: # Replace with a single Ko-fi username -tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel -community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry -liberapay: # Replace with a single Liberapay username -issuehunt: # Replace with a single IssueHunt username -otechie: # Replace with a single Otechie username -custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index 6e14b601f..000000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,7 +0,0 @@ -Thank you for submitting an issue! - -Please make sure to check current open and closed issues to see if your question has been asked or answered before. -If you have just a question (not a bug or a feature request) it is better to ask it in [Stackoverflow](http://stackoverflow.com/questions/tagged/konvajs). - -If you have a bug, please, try to create a reproducible example with jsfiddle (or any similar service). -You can use [this JSBIN](https://jsbin.com/necojavuma/edit?js,output) as a template. diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index fe4d6cad5..000000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,23 +0,0 @@ ---- -name: 'tagged-release' - -on: - push: - tags: - - '*' - -jobs: - tagged-release: - name: 'Tagged Release' - runs-on: 'ubuntu-latest' - - steps: - # ... - - name: 'Build & test' - run: | - echo "done!" - - - uses: 'marvinpinto/action-automatic-releases@latest' - with: - repo_token: '${{ secrets.GITHUB_TOKEN }}' - prerelease: false diff --git a/.github/workflows/test-browser.yml b/.github/workflows/test-browser.yml deleted file mode 100644 index bc2eda602..000000000 --- a/.github/workflows/test-browser.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: Test Browser - -on: - push: - branches: [master] - pull_request: - branches: [master] - -jobs: - build: - runs-on: ubuntu-latest - - strategy: - matrix: - node-version: [16.x] - - steps: - - uses: actions/checkout@v2 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 - with: - node-version: ${{ matrix.node-version }} - - run: npm install - - run: npm run test:browser diff --git a/.github/workflows/test-node.yml b/.github/workflows/test-node.yml deleted file mode 100644 index 795887b4d..000000000 --- a/.github/workflows/test-node.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: Test NodeJS - -on: - push: - branches: [master] - pull_request: - branches: [master] - -jobs: - build: - runs-on: ubuntu-latest - - strategy: - matrix: - node-version: [16.x] - - steps: - - uses: actions/checkout@v2 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 - with: - node-version: ${{ matrix.node-version }} - - run: npm install - - run: npm run test:node diff --git a/.gitignore b/.gitignore deleted file mode 100644 index dc6ada4ae..000000000 --- a/.gitignore +++ /dev/null @@ -1,66 +0,0 @@ -dist -es -.parcel-cache -test-build -documentation -analysis -node_modules -bower_components -phantomjs.exe -docs -homedocs -jsdoc-template -api -package-lock.json -lib -src_old -*.zip -*_cache -types -out.png -cmj - -# Numerous always-ignore extensions -*.diff -*.err -*.orig -*.log -*.rej -*.swo -*.swp -*.vi -*~ -*.sass-cache - -# OS or Editor folders -.DS_Store -Thumbs.db -.cache -.project -.settings -.tmproj -*.esproj -nbproject -*.sublime-project -*.sublime-workspace -*.md.html -.vscode - -# Dreamweaver added files -_notes -dwsync.xml - -# Komodo -*.komodoproject -.komodotools - -# Folders to ignore -.hg -.svn -.CVS -intermediate -publish -.idea - -konva.js -konva.min.js \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 0fa23cefd..000000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,1363 +0,0 @@ -# Change Log - -All notable changes to this project will be documented in this file. -This project adheres to [Semantic Versioning](http://semver.org/). - -## 9.3.17 (2024-12-02) (unreleased) - -- Fix `Arrow.getClientRect()` - -## 9.3.16 (2024-10-21) - -- Fix freeze on ios on touch cancel event -- Typescript fixes - -## 9.3.15 (2024-09-09) - -- fix letter spacing for Hindi text -- ts fixes - -### 9.3.14 (2024-07-16) - -- Fix shadow + corner radius for images -- Support `fillRule` for `Konva.Shape` on hit graph - -### 9.3.13 (2024-07-05) - -- Fallback for `Konva.Text.measureSize()` when 2d context doesn't return full data - -### 9.3.12 (2024-06-20) - -- Fix stopped transforming when it was triggered by multi-touch -- Fix svg `path.getPointAtLength()` calculations in some cases -- Fix `shape.getClientRect()` when any of parents is cached - -### 9.3.11 (2024-05-23) - -- Fix chrome clear canvas issue -- Typescript fixes - -### 9.3.9 (2024-05-20) - -- Fix underline and line-through for `Konva.Text` when `Konva._fixTextRendering = true` - -### 9.3.8 (2024-05-15) - -- Fix click events fires on stage -- Temporary `Konva._fixTextRendering = true` flag to fix inconsistent text - -### 9.3.6 (2024-03-04) - -- Fix transformer bug to enable hit graph back - -### 9.3.5 (2024-03-04) - -- `tranformer` event will be triggered AFTER all data of transformer is updated -- Improve performance of transformer - -### 9.3.4 (2024-03-03) - -- Fix clipping with zero size - -### 9.3.3 (2024-02-09) - -- Another fix for exporting buffered shapes - -### 9.3.2 (2024-01-26) - -- Fix large memory usage on node export - -### 9.3.1 (2024-01-17) - -- Fix Pixelate filter work/fix caching size -- Fix node export when large buffer canvas is used - -### 9.3.0 (2023-12-20) - -- New attribute `rotateLineVisible` for `Konva.Transformer` to show/hide rotate line - -### 9.2.3 (2023-10-31) - -- Better `Konva.Transformer` work when it has `flipEnabled = false`. - -### 9.2.2 (2023-09-14) - -- Better RTL support -- Some typescript fixes - -### 9.2.1 (2023-09-14) - -- Fix text rendering when text has both underline and shadow -- Typescript fixes - -### 9.2.0 (2023-05-14) - -- More controls on clipping -- `fillRule` for `Konva.Shape` - -### 9.1.0 (2023-05-14) - -- New `anchorStyleFunc` for `Konva.Transformer` to customize anchor style - -### 9.0.2 (2023-05-14) - -- Better text rendering when it has stroke - -### 9.0.1 (2023-04-17) - -- Better performance for any instance creation -- Little typescript fixes - -### 9.0.0 (2023-04-13) - -- Migrate the npm package from ES back to CommonJS - -### 8.4.4 (2023-04-05) - -- Some fixes for `Konva.TextPath` calculations and rendering. -- Resolve "willReadFrequently" warning in Chrome - -### 8.4.3 (2023-03-23) - -- Typescript fixes -- Better validation for `Konva.Transfomer` `nodes` property - -### 8.4.2 (2023-01-20) - -- Fix justify on text with limited height - -### 8.4.1 (2023-01-19) - -- Typescript fixes for `container.add()` method. Ability to use empty array as argument. E.g. `container.add(...emptyArray)` -- Fix underline for justify text -- Fix gradient display on underline or line-through text - -### 8.4.0 (2023-01-05) - -- Add support for `cornerRadius` for Konva.Image -- Fix cloning of `Konva.Transformer` - -### 8.3.14 (2022-11-09) - -- Automatically release (destroy) used canvas elements. Should fix safari memory issues - -### 8.3.13 (2022-10-03) - -- Typescript fixes -- Better non-passive events usage -- Better 2d context usage to avoid Chrome warnings - -### 8.3.12 (2022-08-29) - -- `ellipsis` fixes for `Konva.Text` -- Allow reset component attributes via overloader - -### 8.3.11 (2022-08-05) - -- Fix `Konva.Label` position when tag attributes are changed -- Fix incorrect ellipsis display for `Konva.Text` -- Fix `click` event trigger on parent containers on touch devices -- Fix incorrect `mouseleave` event trigger when drag is finished - -### 8.3.10 (2022-06-20) - -- Skip `Konva.Transformer` in `container.getClientRect()` calculations - -### 8.3.9 (2022-05-27) - -- Typescript fixes - -### 8.3.8 (2022-05-05) - -- Disable all exports in `package.json` - -### 8.3.7 (2022-05-04) - -- Migrate to CommonJS exports only - -### 8.3.6 (2022-04-27) - -- Better exports definitions. Importing `Konva` should work better in different bundlers and test environments. -- `imageSmoothingEnabled` option for `node.toDataURL()`, `node.toCanvas()` and `node.toImage()` - -## 8.3.5 (2022-03-21) - -- Quick fix for `toCanvas()` and `toDataURL()` size calculation. - -## 8.3.4 (2022-03-13) - -- Fix characters positions calculations on `fontFamily` changes in `TextPath`. -- Remove rounding in `node.getClientRect()` results -- Fix event object on `transformstart` event. - -## 8.3.3 (2022-02-23) - -- Fix `justify` align for text with several paragraphs. - -## 8.3.2 - -- Remove source maps for webpack builds - -## 8.3.1 (2021-12-09) - -- Fix `dbltap` event in Safari -- A bit faster `node.moveToTop()` when node is already on top -- Better client rect calculations for `Konva.Arc` shape. - -## 8.3.0 (2021-11-15) - -- new `transformer.anchorDragBoundFunc` method. - -## 8.2.4 (2021-11-15) - -- Fix not working `Konva.Transformer` when several transformers were used - -## 8.2.2 - -- Fix `Konva.Arrow` rendering when it has two pointers - -## 8.2.1 - -- Fix `package.json` exports. - -## 8.2.0 - -- Restore build in CommonJS. `const Konva = require('konva/cmj').default;` -- Fix arrow rendering when dash is used -- Fix `dbltap` trigger when multi-touch is used - -## 8.1.4 - -- Fix `dblclick` event when `cancelBubble` is used. - -## 8.1.3 - -- Fix `fillPattern` cache invalidation on shapes - -## 8.1.2 - -- Fix memory leak for `Konva.Image` - -## 8.1.1 - -- Fix `Konva.Transformer` dragging draw when `shouldOverdrawWholeArea = true`. -- Fix auto redraw when `container.removeChildren()` or `container.destroyChildren()` are used - -## 8.1.0 - -- New property `useSingleNodeRotation` for `Konva.Transformer`. - -## 8.0.4 - -- Fix fill pattern updates on `fillPatternX` and `fillPatternY` changes. - -## 8.0.2 - -- Fix some transform caches -- Fix cache with hidden shapes - -## 8.0.1 - -- Some typescript fixes - -## 8.0.0 - -This is a very large release! The long term of `Konva` API is to make it simpler and faster. So when possible I am trying to optimize the code and remove unpopular/confusing API methods. - -**BREAKING:** - -- `Konva.Collection` is removed. `container.children` is a simple array now. `container.find()` will returns an array instead of `Konva.Collection()` instance. - `Konva.Collection` was confusing for many users. Also it was slow and worked with a bit of magic. So I decided to get rif of it. Now we are going to use good old arrays. - -```js -// old code: -group.find('Shape').visible(false); - -// new code: -group.find('Shape').forEach((shape) => shape.visible(false)); -``` - -- argument `selector` is removed from `node.getIntersection(pos)` API. I don't think you even knew about it. -- `Konva.Util.extend` is removed. -- All "content" events from `Konva.Stage` are removed. E.g. instead of `contentMousemove` just use `mousemove` event. - -**New features:** - -- All updates on canvas will do automatic redraw with `layer.batchDraw()`. This features is configurable with `Konva.autoDrawEnabled` property. Konva will automatically redraw layer when you change any property, remove or add nodes, do caching. So you don't need to call `layer.draw()` or `layer.batchDraw()` in most of the cases. -- New method `layer.getNativeCanvasElement()` -- new `flipEnabled` property for `Konva.Transformer` -- new `node.isClientRectOnScreen()` method -- Added `Konva.Util.degToRad` and `Konva.Util.radToDeg` -- Added `node.getRelativePointerPosition()` - -**Changes and fixes:** - -- **Full migration to ES modules package (!), commonjs code is removed.** -- **`konva-node` is merged into `konva` npm package. One package works for both environments.** -- Full event system rewrite. Much better `pointer` events support. -- Fix `TextPath` recalculations on `fontSize` change -- Better typescript support. Now every module has its own `*.d.ts` file. -- Removed `Konva.UA`, `Konva._parseUA` (it was used for old browser detection) -- Fixed Arrow head position when an arrow has tension -- `textPath.getKerning()` is removed -- Fix `a` command parsing for `Konva.Path` -- Fix fill pattern for `Konva.Text` when the pattern has an offset or rotation -- `Konva.names` and `Konva.ids` are removed -- `Konva.captureTouchEventsEnabled` is renamed to `Konva.capturePointerEventsEnabled` - -## 7.2.5 - -- Fix transform update on `letterSpacing` change of `Konva.Text` - -## 7.2.4 - -- Fix wrong `mouseleave` trigger for `Konva.Stage` - -## 7.2.3 - -- Fix transformer rotation when parent of a node is rotated too. - -## 7.2.2 - -- Fix wrong size calculations for `Konva.Line` with tension -- Fix `shape.intersects()` behavior when a node is dragged -- Fix ellipsis rendering for `Konva.Text` - -## 7.2.1 - -- Fix correct rendering of `Konva.Label` when heigh of text is changed -- Fix correct `transformstart` and `transformend` events when several nodes are attached with `Konva.Transformer` - -## 7.2.0 - -- New property `fillAfterStrokeEnabled` for `Konva.Shape`. See API docs for more information. -- Fix for `Konva.Transformer` when it may fail to draw. -- Fix rendering of `TextPath` one more time. - -## 7.1.9 - -- Fix autodrawing for `Konva.Transformer` when it is on a different layer -- Fix `Konva.RegularPolygon` size calculations. - -## 7.1.8 - -- Fix incorrect rendering of `TextPath` in some cases. (again) - -## 7.1.7 - -- Fix incorrect rendering of `TextPath` in some cases. - -## 7.1.6 - -- Fix for correct image/dataURL/canvas exports for `Konva.Stage`. - -## 7.1.5 - -- Performance fixes for dragging many nodes with `Konva.Transformer`. -- Documentation updates - -## 7.1.4 - -- Perf fixes -- Change events trigger flow, so adding new events INSIDE event callback will work correctly. -- Fix double `dragend`, `dragstart`, `dragmove` triggers on `Konva.Transformer` - -## 7.1.3 - -- Text rendering fixes - -## 7.1.2 - -- fix ellipses behavior for `Konva.Text`. -- fix scaled fill pattern for text. - -## 7.1.1 - -- fixes for `dragstart` event when `Konva.Transformer` is used. `dragstart` event will have correct native `evt` reference -- Better unicode support in `Konva.Text` and `Konva.TextPath`. Emoji should work better now 👍 - -## 7.1.0 - -- Multi row support for `ellipsis` config for `Konva.Text` -- Better `Konva.Transfomer` behavior when single attached node is programmatically rotated. - -## 7.0.7 - -- fixes for `dragstart` event when `Konva.Transformer` is used. `dragstart` will not bubble from transformer. -- `string` and `fill` properties validation can accept `CanvasGradient` as valid value - -## 7.0.6 - -- Better performance for stage dragging - -## 7.0.5 - -- Fixes for `node.cache()` function. - -## 7.0.4 - -- Add `onUpdate` callbacks to `Konva.Tween` configuration and `node.to()` method. -- Up to 6x faster initializations of objects, like `const shape = new Konva.Shape()`. - -## 7.0.3 - 2020-07-09 - -- Fix wring `dragend` trigger on `draggable` property change inside `click` -- Fix incorrect text rendering with `letterSpacing !== 0` -- Typescript fixes - -## 7.0.2 - 2020-06-30 - -- Fix wrong trigger `dbltap` and `click` on mobile - -## 7.0.1 - 2020-06-29 - -- Fixes for different font families support. -- Fixes for `Konva.Transformer` positions -- Types fixes for better Typescript support - -## 7.0.0 - 2020-06-23 - -- **BREAKING** `inherit` option is removed from `visible` and `listening`. They now just have boolean values `true` or `false`. If you do `group.listening(false);` then whole group and all its children will be removed from the hitGraph (and they will not listen to events). Probably 99% `Konva` applications will be not affected by this _breaking change_. -- **Many performance fixes and code size optimizations. Up to 70% performance boost for many moving nodes.** -- `layer.hitGraphEnabled()` is deprecated. Just use `layer.listening(false)` instead -- Better support for font families with spaces inside (like `Font Awesome 5`). -- Fix wrong `dblclick` and `dbltap` triggers -- Deprecate `Konva.FastLayer`. Use `new Konva.Layer({ listening: false });` instead. -- `dragmove` event will be fired on `Konva.Transformer` too when you drag a node. -- `dragmove` triggers only after ALL positions of dragging nodes are changed - -## 6.0.0 - 2020-05-08 - -- **BREAKING!** `boundBoxFunc` of `Konva.Transformer` works in absolute coordinates of whole transformer. Previously in was working in local coordinates of transforming node. -- Many `Konva.Transformer` fixes. Now it works correctly when you transform several rotated shapes. -- Fix for wrong `mouseleave` and `mouseout` fire on shape remove/destroy. - -## 5.0.3 - 2020-05-01 - -- Fixes for `boundBoxFunc` of `Konva.Transformer`. - -## 5.0.2 - 2020-04-23 - -- Deatach fixes for `Konva.Transformer` - -## 5.0.1 - 2020-04-22 - -- Fixes for `Konva.Transformer` when parent scale is changed -- Fixes for `Konva.Transformer` when parent is draggable -- Performance optimizations - -## 5.0.0 - 2020-04-21 - -- **New `Konva.Transformer` implementation!**. Old API should work. But I marked this release is `major` (breaking) just for smooth updates. Changes: - - Support of transforming multiple nodes at once: `tr.nodes([shape1, shape2])`. - - `tr.node()`, `tr.setNode()`, `tr.attachTo()` methods are deprecated. Use `tr.nodes(array)` instead - - Fixes for center scaling - - Fixes for better `padding` support - - `Transformer` can be placed anywhere in the tree of a stage tree (NOT just inside a parent of attached node). -- Fix `imageSmoothEnabled` resets when stage is resized -- Memory usage optimizations when a node is cached - -## 4.2.2 - 2020-03-26 - -- Fix hit stroke issues - -## 4.2.1 - 2020-03-26 - -- Fix some issues with `mouseenter` and `mouseleave` events. -- Deprecate `hitStrokeEnabled` property -- Fix rounding issues for `getClientRect()` for some shapes - -## 4.2.0 - 2020-03-14 - -- Add `rotationSnapTolerance` property to `Konva.Transformer`. -- Add `getActiveAnchor()` method to `Konva.Transformer` -- Fix hit for non-closed `Konva.Path` -- Some fixes for experimental Offscreen canvas support inside a worker - -## 4.1.6 - 2020-02-25 - -- Events fixes for `Konva.Transformer` -- Now `Konva` will keep `id` in a cloned node -- Better error messages on tainted canvas issues - -## 4.1.5 - 2020-02-16 - -- Fixes for `path.getClientRect()` function calculations - -## 4.1.4 - 2020-02-10 - -- Fix wrong internal caching of absolute attributes -- Fix `Konva.Transformer` behavior on scaled with CSS stage - -## 4.1.3 - 2020-01-30 - -- Fix line with tension calculations -- Add `node.getAbsoluteRotation()` method -- Fix cursor on anchors for rotated parent - -## 4.1.2 - 2020-01-08 - -- Fix possible `NaN` in content calculations - -## 4.1.1 - 2020-01-07 - -- Add ability to use `width = 0` and `height = 0` for `Konva.Image`. -- Fix `cache()` method of `Konva.Arrow()` -- Add `Transform` to `Konva` default exports. So `Konva.Transform` is available now. - -## 4.1.0 - 2019-12-23 - -- Make events work on some CSS transforms -- Fix caching on float dimensions -- Fix `mouseleave` event on stage. -- Increase default anchor size for `Konva.Transformer` on touch devices - -## 4.0.18 - 2019-11-20 - -- Fix `path.getClientRect()` calculations for `Konva.Path` -- Fix wrong fire of `click` and `tap` events on stopped drag events. - -## 4.0.17 - 2019-11-08 - -- Allow hitStrokeWidth usage, even if a shape has not stroke visible -- Better IE11 support - -## 4.0.16 - 2019-10-21 - -- Warn on undefined return value of `dragBoundFunc`. -- Better calculations for `container.getClientRect()` - -## 4.0.15 - 2019-10-15 - -- TS fixes -- Better calculations for `TextPath` with align = right -- Better `textPath.getClientRect()` - -## 4.0.14 - 2019-10-11 - -- TS fixes -- Fix globalCompositeOperation + cached hit detections. -- Fix absolute position calculations for cached parent - -## 4.0.13 - 2019-10-02 - -- Fix `line.getClientRect()` calculations for line with a tension or low number of points - -## 4.0.12 - 2019-09-17 - -- Fix some bugs when `Konva.Transformer` has `padding > 0` - -## 4.0.10 - 2019-09-10 - -- Fix drag position handling -- Fix multiple selector for find() method - -## 4.0.9 - 2019-09-06 - -- Fix `Konva.Transformer` behavior on mirrored nodes -- Fix `stage.getPointerPosition()` logic. - -## 4.0.8 - 2019-09-05 - -- Fix `dragend` event on click -- Revert fillPatternScale for text fix. - -## 4.0.7 - 2019-09-03 - -- Fixed evt object on `dragstart` -- Fixed double tap trigger after dragging - -## 4.0.6 - 2019-08-31 - -- Fix fillPatternScale for text - -## 4.0.5 - 2019-08-17 - -- Fix `dragstart` flow when `node.startDrag()` is called. -- Fix `tap` and `dbltap` double trigger on stage - -## 4.0.4 - 2019-08-12 - -- Add `node.isCached()` method -- Fix nested dragging bug - -## 4.0.3 - 2019-08-08 - -- Slightly changed `mousemove` event flow. It triggers for first `mouseover` event too -- Better `Konva.hitOnDragEnabled` support for mouse inputs - -## 4.0.2 - 2019-08-08 - -- Fixed `node.startDrag()` behavior. We can call it at any time. - -## 4.0.1 - 2019-08-07 - -- Better `Konva.Arrow` + tension drawing -- Typescript fixes - -## 4.0.0 - 2019-08-05 - -Basically the release doesn't have any breaking changes. You may only have issues if you are using something from `Konva.DD` object (which is private and never documented). Otherwise you should be fine. `Konva` has major upgrade about touch events system and drag&drop flow. The API is exactly the same. But the internal refactoring is huge so I decided to make a major version. Please upgrade carefully. Report about any issues you have. - -- Better multi-touch support. Now we can trigger several `touch` events on one or many nodes. -- New drag&drop implementation. You can drag several shapes at once with several pointers. -- HSL colors support - -## 3.4.1 - 2019-07-18 - -- Fix wrong double tap trigger - -## 3.4.0 - 2019-07-12 - -- TS types fixes -- Added support for different values for `cornerRadius` of `Konva.Rect` - -## 3.3.3 - 2019-06-07 - -- Some fixes for better support `konva-node` -- TS types fixes - -## 3.3.2 - 2019-06-03 - -- TS types fixes - -## 3.3.1 - 2019-05-28 - -- Add new property `imageSmoothingEnabled` to the node caching -- Even more ts fixes. Typescript need a lot of attention, you know... - -## 3.3.0 - 2019-05-28 - -- Enable strict mode for ts types -- Add new property `imageSmoothingEnabled` to the layer - -## 3.2.7 - 2019-05-27 - -- Typescript fixes -- Experimental pointer events support. Do `Konva._pointerEventsEnabled = true;` to enable -- Fix some `Konva.Transformer` bugs. - -## 3.2.6 - 2019-05-09 - -- Typescript fixes again - -## 3.2.5 - 2019-04-17 - -- Show a warning when `Konva.Transformer` and attaching node have different parents. -- Typescript fixes - -## 3.2.4 - 2019-04-05 - -- Fix some stage events. `mouseenter` and `mouseleave` should work correctly on empty spaces -- Fix some typescript types -- Better detection of production mode (no extra warnings) - -## 3.2.3 - 2019-03-21 - -- Fix `hasName` method for empty name cases - -## 3.2.2 - 2019-03-19 - -- Remove `dependencies` from npm package - -## 3.2.1 - 2019-03-18 - -- Better `find` and `findOne` lookup. Now we should not care about duplicate ids. -- Better typescript definitions - -## 3.2.0 - 2019-03-10 - -- new property `shape.hitStrokeWidth(10)` -- Better typescript definitions -- Remove `Object.assign` usage (for IE11 support) - -## 3.1.7 - 2019-03-06 - -- Better modules and TS types - -## 3.1.6 - 2019-02-27 - -- Fix commonjs exports -- Fix global injections - -## 3.1.0 - 2019-02-27 - -- Make `Konva` modular: `import Konva from 'konva/lib/Core';`; -- Fix incorrect `Transformer` behavior -- Fix drag&drop for touch devices - -## 3.0.0 - 2019-02-25 - -## Breaking - -Customs builds are temporary removed from npm package. You can not use `import Konva from 'konva/src/Core';`. -This feature will be added back later. - -### Possibly breaking - -That changes are private and internal specific. They should not break most of `Konva` apps. - -- `Konva.Util.addMethods` is removed -- `Konva.Util._removeLastLetter` is removed -- `Konva.Util._getImage` is removed -- `Konv.Util._getRGBAString` is removed -- `Konv.Util._merge` is removed -- Removed polyfill for `requestAnimationFrame`. -- `id` and `name` properties defaults are empty strings, not `undefined` -- internal `_cache` property was updated to use es2015 `Map` instead of `{}`. -- `Konva.Validators` is removed. - -### Added - -- Show a warning when a stage has too many layers -- Show a warning on duplicate ids -- Show a warning on weird class in `Node.create` parsing from JSON -- Show a warning for incorrect value for component setters. -- Show a warning for incorrect value for `zIndex` property. -- Show a warning when user is trying to reuse destroyed shape. -- new publish method `measureSize(string)` for `Konva.Text` -- You can configure what mouse buttons can be used for drag&drop. To enable right button you can use `Konva.dragButtons = [0, 1]`. -- Now you can hide stage `stage.visible(false)`. It will set its container display style to "none". -- New method `stage.setPointersPositions(event)`. Usually you don't need to use it manually. -- New method `layer.toggleHitCanvas()` to show and debug hit areas - -### Changed - -- Full rewrite to Typescript with tons of refactoring and small optimizations. The public API should be 100% the same -- Fixed `patternImage` and `radialGradient` for `Konva.Text` -- `Konva.Util._isObject` is renamed to `Konva.Util._isPlainObject`. -- A bit changed behavior of `removeId` (private method), now it doesn't clear node ref, if object is changed. -- simplified `batchDraw` method (it doesn't use `Konva.Animation`) now. -- Performance improvements for shapes will image patterns, linear and radial fills -- `text.getTextHeight()` is deprecated. Use `text.height()` or `text.fontSize()` instead. -- Private method `stage._setPointerPosition()` is deprecated. Use `stage.setPointersPositions(event)`; - -### Fixed - -- Better mouse support on mobile devices (yes, that is possible to connect mouse to mobile) -- Better implementation of `mouseover` event for stage -- Fixed underline drawing for text with `lineHeight !== 1` -- Fixed some caching behavior when a node has `globalCompositeOperation`. -- Fixed automatic updates for `Konva.Transformer` -- Fixed container change for a stage. -- Fixed warning for `width` and `height` attributes for `Konva.Text` -- Fixed gradient drawing for `Konva.Text` -- Fixed rendering with `strokeWidth = 0` - -## 2.6.0 - 2018-12-14 - -### Changed - -- Performance fixes when cached node has many children -- Better drawing for shape with `strokeScaleEnabled = false` on HDPI devices - -### Added - -- New `ignoreStroke` for `Konva.Transformer`. Good to use when a shape has `strokeScaleEnabled = false` - -### Changed - -- `getKerning` TextPath API is deprecated. Use `kerningFunc` instead. - -## 2.5.1 - 2018-11-08 - -### Changed - -- Use custom functions for `trimRight` and `trimLeft` (for better browsers support) - -## 2.5.0 - 2018-10-24 - -### Added - -- New `anchorCornerRadius` for `Konva.Transformer` - -### Fixed - -- Performance fixes for caching - -### Changed - -- `dragstart` event behavior is a bit changed. It will fire BEFORE actual position of a node is changed. - -## 2.4.2 - 2018-10-12 - -### Fixed - -- Fixed a wrong cache when a shape inside group has `listening = false` - -## 2.4.1 - 2018-10-08 - -### Changed - -- Added some text trim logic to wrap in better - -### Fixed - -- `getClientRect` for complex paths fixes -- `getClientRect` calculation fix for groups -- Update `Konva.Transformer` on `rotateEnabled` change -- Fix click stage event on dragend -- Fix some Transformer cursor behavior - -## 2.4.0 - 2018-09-19 - -### Added - -- Centered resize with ALT key for `Konva.Transformer` -- New `centeredScaling` for `Konva.Transformer` - -### Fixed - -- Tween support for gradient properties -- Add `user-select: none` to the stage container to fix some "selected contend around" issues - -## 2.3.0 - 2018-08-30 - -### Added - -- new methods `path.getLength()` and `path.getPointAtLength(val)` -- `verticalAlign` for `Konva.Text` - -## 2.2.2 - 2018-08-21 - -### Changed - -- Default duration for tweens and `node.to()` methods is now 300ms -- Typescript fixes -- Automatic validations for many attributes - -## 2.2.1 - 2018-08-10 - -### Added - -- New properties for `Konva.Transformer`: `borderStroke`, `borderStrokeWidth`, `borderDash`, `anchorStroke`, `anchorStrokeWidth`, `anchorSize`. - -### Changed - -- Some properties of `Konva.Transformer` are renamed. `lineEnabled` -> `borderEnabled`. `rotateHandlerOffset` -> `rotateAnchorOffset`, `enabledHandlers` -> `enabledAnchors`. - -## 2.1.8 - 2018-08-01 - -### Fixed - -- Some `Konva.Transformer` fixes -- Typescript fixes -- `stage.toDataURL()` fixes when it has hidden layers -- `shape.toDataURL()` automatically adjust position and size of resulted image - -## 2.1.7 - 2018-07-03 - -### Fixed - -- `toObject` fixes - -## 2.1.7 - 2018-07-03 - -### Fixed - -- Some drag&drop fixes - -## 2.1.6 - 2018-06-16 - -### Fixed - -- Removed wrong dep -- Typescript fixes - -## 2.1.5 - 2018-06-15 - -### Fixed - -- Typescript fixes -- add shape as second argument for `sceneFunc` and `hitFunc` - -## 2.1.4 - 2018-06-15 - -### Fixed - -- Fixed `Konva.Text` justify drawing for a text with decoration -- Added methods `data()`,`setData()` and `getData()` methods to `Konva.TextPath` -- Correct cache reset for `Konva.Transformer` - -## 2.1.3 - 2018-05-17 - -### Fixed - -- `Konva.Transformer` automatically track shape changes -- `Konva.Transformer` works with shapes with offset too - -## 2.1.2 - 2018-05-16 - -### Fixed - -- Cursor fixes for `Konva.Transformer` -- Fixed lineHeight behavior for `Konva.Text` -- Some performance optimizations for `Konva.Text` -- Better wrap algorithm for `Konva.Text` -- fixed `Konva.Arrow` with tension != 0 -- Some fixes for `Konva.Transformer` - -## 2.0.3 - 2018-04-21 - -### Added - -- Typescript defs for `Konva.Transformer` -- Typescript defs for `globalCompositeOperation` - -## Changes - -- Fixed flow for `contextmenu` event. Now it will be triggered on shapes too -- `find()` method for Containers can use a function as a parameter - -### Fixed - -- some bugs fixes for `group.getClientRect()` -- `Konva.Arrow` will not draw dash for pointers -- setAttr will trigger change event if new value is the same Object -- better behavior of `dblclick` event when you click fast on different shapes -- `stage.toDataURL` will use `pixelRatio = 1` by default. - -## 2.0.2 - 2018-03-15 - -### Fixed - -- Even more bugs fixes for `Konva.Transformer` - -## 2.0.1 - 2018-03-15 - -### Fixed - -- Several bugs fixes for `Konva.Transformer` - -## 2.0.0 - 2018-03-15 - -### Added - -- new `Konva.Transformer`. It is a special group that allow simple resizing and rotation of a shape. -- Add ability to remove event by callback `node.off('event', callback)`. -- new `Konva.Filters.Contrast`. -- new `Konva.Util.haveIntersection()` to detect simple collusion -- add `Konva.Text.ellipsis` to add '…' to text string if width is fixed and wrap is set to 'none' -- add gradients for strokes - -## Changed - -- stage events are slightly changed. `mousedown`, `click`, `mouseup`, `dblclick`, `touchstart`, `touchend`, `tap`, `dbltap` will be triggered when clicked on empty areas too - -### Fixed - -- Some typescript fixes -- Pixelate filter fixes -- Fixes for path data parsing -- Fixed shadow size calculation - -## Removed - -- Some deprecated methods are removed. If previous version was working without deprecation warnings for you, this one will work fine too. - -## 1.7.6 - 2017-11-01 - -### Fixed - -- Some typescript fixes - -## 1.7.4 - 2017-10-30 - -### Fixed - -- `isBrowser` detection for electron - -## 1.7.3 - 2017-10-19 - -### Changed - -- Changing size of a stage will redraw it in synchronous way - -### Fixed - -- Some fixes special for nodejs - -## 1.7.2 - 2017-10-11 - -### Fixed - -- Fixed `Konva.document is undefined` - -## 1.7.1 - 2017-10-11 - -### Changed - -- Konva for browser env and Konva for nodejs env are separate packages now. You can use `konva-node` for NodeJS env. - -## 1.7.0 - 2017-10-08 - -### Fixed - -- Several typescript fixes - -### Changed - -- Default value for `dragDistance` is changed to 3px. -- Fix rare error throw on drag -- Caching with height = 0 or width = 0 with throw async error. Caching will be ignored. - -## 1.6.8 - 2017-08-19 - -### Changed - -- The `node.getClientRect()` calculation is changed a bit. It is more powerfull and correct. Also it takes parent transform into account. See docs. -- Upgrade nodejs deps - -## 1.6.7 - 2017-07-28 - -### Fixed - -- Fix bug with double trigger wheel in Firefox -- Fix `node.getClientRect()` calculation in a case of Group + invisible child -- Fix dblclick issue https://github.com/konvajs/konva/issues/252 - -## 1.6.3 - 2017-05-24 - -### Fixed - -- Fixed bug with pointer detection. css 3d transformed stage will not work now. - -## 1.6.2 - 2017-05-08 - -### Fixed - -- Fixed bug with automatic shadow for negative scale values - -## 1.6.1 - 2017-04-25 - -### Fixed - -- Fix pointer position detection - -### Changed - -- moved `globalCompositeOperation` property to `Konva.Node` - -## 1.6.0 - 2017-04-21 - -### Added - -- support of globalCompositeOperation for `Konva.Shape` - -### Fixed - -- getAllIntersections now works ok for Text shapes (https://github.com/konvajs/konva/issues/224) - -### Changed - -- Konva a bit changed a way to detect pointer position. Now it should be OK to apply css transform on Konva container. https://github.com/konvajs/konva/pull/215 - -## 1.5.0 - 2017-03-20 - -### Added - -- support for `lineDashOffset` property for `Konva.Shape`. - -## 1.4.0 - 2017-02-07 - -## Added - -- `textDecoration` of `Konva.Text` now supports `line-through` - -## 1.3.0 - 2017-01-10 - -## Added - -- new align value for `Konva.Text` and `Konva.TextPath`: `justify` -- new property for `Konva.Text` and `Konva.TextPath`: `textDecoration`. Right now it sports only '' (no decoration) and 'underline' values. -- new property for `Konva.Text`: `letterSpacing` -- new event `contentContextmenu` for `Konva.Stage` -- `align` support for `Konva.TextPath` -- new method `toCanvas()` for converting a node into canvas element - -### Changed - -- changing a size of `Konva.Stage` will update it in async way (via `batchDraw`). -- `shadowOffset` respect pixel ratio now - -### Fixed - -- Fixed bug when `Konva.Tag` width was not changing its width dynamically -- Fixed "calling remove() for dragging shape will throw an error" -- Fixed wrong opacity level for cached group with opacity -- More consistent shadows on HDPI screens -- Fixed memory leak for nodes with several names - -## 1.2.2 - 2016-09-15 - -### Fixed - -- refresh stage hit and its `dragend` -- `getClientRect` calculations - -## 1.2.0 - 2016-09-15 - -## Added - -- new properties for `Konva.TextPath`: `letterSpacing` and `textBaseline`. - -## 1.1.4 - 2016-09-13 - -### Fixed - -- Prevent throwing an error when text property of `Konva.Text` = undefined or null - -## 1.1.3 - 2016-09-12 - -### Changed - -- Better hit function for `TextPath`. -- Validation of `Shape` filters. - -## 1.1.2 - 2016-09-10 - -### Fixed - -- Fixed "Dragging Group on mobile view throws "missing preventDefault" error" #169 - -## 1.1.1 - 2016-08-30 - -### Fixed - -- Fixed #166 bug of drag&drop - -## 1.1.0 - 2016-08-21 - -## Added - -- new property of `Konva.Shape` - `preventDefault`. - -## 1.0.3 - 2016-08-14 - -### Fixed - -- Fixed some typescript definitions - -## 1.0.2 - 2016-07-08 - -## Changed - -- `Konva.Text` will interpret undefined `width` and `height` as `AUTO` - -## 1.0.1 - 2016-07-05 - -### Changed - -- you can now unset property by `node.x(undefined)` or `node.setAttr('x', null)` - -### Fixed - -- Bug fix for case when `touchend` event throws error - -## 1.0.0 - 2016-07-05 - -### Fixed - -- Bug fix for case when `touchend` event throws error - -## 0.15.0 - 2016-06-18 - -## Added - -- Custom clip function - -## 0.14.0 - 2016-06-17 - -### Fixed - -- fixes in typescript definitions -- fixes for bug with `mouseenter` event on deep nesting case - -## 0.13.9 - 2016-05-14 - -### Changed - -- typescript definition in npm package -- node@5.10.1, canvas@1.3.14, jsdom@8.5.0 support -- `Konva.Path` will be filled when it is not closed -- `Animation.start()` will not not immediate sync draw. This should improve performance a little. -- Warning when node for `Tween` is not in layer yet. -- `removeChildren()` remove only first level children. So it will not remove grandchildren. - -## 0.12.4 - 2016-04-19 - -### Changed - -- `batchDraw` will do not immediate `draw()` - -### Fixed - -- fix incorrect shadow offset on rotation - -## 0.12.3 - 2016-04-07 - -### Fixed - -- `batchDraw` function works less time now -- lighter npm package - -## 0.12.2 - 2016-03-31 - -### Fixed - -- repair `cancelBubble` event property behaviour -- fix wrong `Path` `getClientRect()` calculation -- better HDPI support -- better typescript definitions -- node 0.12 support - -### Changed - -- more universal stage container selector -- `mousewheel` event changed to `wheel` - -## 0.11.1 - 2016-01-16 - -### Fixed - -- correct `Konva.Arrow` drawing. Now it works better. -- Better support for dragging when mouse out of stage -- Better corner radius for `Label` shape -- `contentTap` event for stage - -### Added - -- event delegation. You can use it in this way: `layer.on('click', 'Circle', handler);` -- new `node.findAncestors(selector)` and `node.findAncestor(selector)` functions -- optional selector parameter for `stage.getIntersection` and `layer.getIntersection` -- show warning message if several instances of Konva are added to page. - -### Changed - -- `moveTo` and some other methods return `this` -- `getAbsolutePosition` support optional relative parent argument (useful to find absolute position inside of some of parent nodes) -- `change` event will be not fired if changed value is the same as old value - -## 0.10.0 - 2015-10-27 - -### Added - -- RGBA filter. Thanks to [@codefo](https://github.com/codefo) -- `stroke` and `fill` support for `Konva.Sprite` - -### Fixed - -- Correct calculation in `getClientRect` method of `Konva.Line` and `Konva.Container`. -- Correct `toObject()` behaviour for node with attrs with extended native prototypes -- Fixed bug for caching where buffer canvas is required - -### Changed - -- Dragging works much better. If your pointer is out of stage content dragging will still continue. -- `Konva.Node.create` now works with objects. -- `Konva.Tween` now supports tweening points to state with different length - -## 0.9.5 - 2015-05-28 - -### Fixed - -- `to` will not throw error if no `onFinish` callback -- HDPI support for desktop -- Fix bug when filters are not correct for HDPI -- Fix bug when hit area is not correct for HDPI -- Fix bug for incorrect `getClientRect` calculation -- Repair fill gradient for text - -### Changed - -- context wrapper is more capable with native context. - So you can use `context.fillStyle` property in your `sceneFunc` without accessing native context. -- `toDataURL` now handles pixelRatio. you can pass `config.pixelRatio` argument -- Correct `clone()` for custom nodes -- `FastLayer` can now have transforms -- `stage.toDataURL()` method now works synchronously. So `callback` argument is not required. -- `container.find(selector)` method now has a validation step. So if you forgot to add `#` or `.` you will see a warning message in the console. - -### Added - -- new `Konva.Image.fromURL` method - -### Deprecated - -- `fillRed`, `fillGreen`, `fillBlue`, `fillAlpha` are deprecated. Use `fill` instead. -- `strokeRed`, `strokeGreen`, `strokeBlue`, `strokeAlpha` are deprecated. Use `stroke` instead. -- `shadowRed`, `shadowGreen`, `shadowBlue`, `shadowAlpha` are deprecated. Use `shadow` instead. -- `dashArray` is deprecated. Use `dash` instead. -- `drawFunc` is deprecated. Use `sceneFunc` instead. -- `drawHitFunc` is deprecated. Use `hitFunc` instead. -- `rotateDeg` is deprecated. Use `rotate` instead. - -## 0.9.0 - 2015-02-27 - -### Fixed - -- cache algorithm has A LOT OF updates. - -### Changed - -- `scale` now affects `shadowOffset` -- performance optimization (remove some unnecessary draws) -- more expected drawing when shape has opacity, stroke and shadow -- HDPI for caching. -- Cache should work much better. Now you don't need to pass bounding box {x,y,width,height} to `cache` method for all buildin Konva shapes. (only for your custom `Konva.Shape` instance). -- `Tween` now supports color properties (`fill`, `stroke`, `shadowColor`) - -### Added - -- new methods for working with node's name: `addName`, `removeName`, `hasName`. -- new `perfectDrawEnabled` property for shape. See [http://konvajs.org/docs/performance/Disable_Perfect_Draw.html](http://konvajs.org/docs/performance/Disable_Perfect_Draw.html) -- new `shadowForStrokeEnabled` property for shape. See [http://konvajs.org/docs/performance/All_Performance_Tips.html](http://konvajs.org/docs/performance/All_Performance_Tips.html) -- new `getClientRect` method. -- new `to` method for every node for shorter tweening - -## 0.8.0 - 2015-02-04 - -- Bug Fixes - - browser crashing on pointer events fixed - - optimized `getIntersection` function -- Enhancements - - `container.findOne()` method - - new `strokeHitEnabled` property. Useful for performance optimizations - - typescript definitions. see `/resources/konva.d.ts` - -## Rebranding release 2015-01-28 - -Differences from last official `KineticJS` release - -- Bug Fixes - - - `strokeScaleEnabled = false` is disabled for text as I can not find a way to implement this - - `strokeScaleEnabled = false` for Line now creates a correct hit graph - - working "this-example" as name for nodes - - Konva.Text() with no config will not throw exception - - Konva.Line() with no config will not throw exception - - Correct stage resizing with `FastLayer` - - `batchDraw` method for `FastLayer` - - Correct mouseover/mouseout/mouseenter/mouseleave events for groups - - cache node before adding to layer - - `intersects` function now works for shapes with shadow - -- Enhancements - - `cornerRadius` of Rect is limited by `width/2` and `height/2` - - `black` is default fill for text - - true class extending. Now `rect instanceOf Konva.Shape` will return true - - while dragging you can redraw layer that is not under drag. hit graph will be updated in this case - - now you can move object that is dragging into another layer. - - new `frameOffsets` attribute for `Konva.Sprite` - - much better dragging performance - - `browserify` support - - applying opacity to cached node - - remove all events with `node.off()` - - mouse dragging only with left button - - opacity now affects cached shapes - - Label corner radius - - smart changing `width`, `height`, `radius` attrs for circle, start, ellipse, ring. - - `mousewheel` support. Thanks [@vmichnowicz](https://github.com/vmichnowicz) - - new Arrow plugin - - multiple names: `node.name('foo bar'); container.find('.foo');` (thanks [@mattslocum](https://github.com/mattslocum)) - - `Container.findOne()` diff --git a/LICENSE b/LICENSE deleted file mode 100644 index a25747cf1..000000000 --- a/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -MIT License - -Original work Copyright (C) 2011 - 2013 by Eric Rowell (KineticJS) -Modified work Copyright (C) 2014 - present by Anton Lavrenov (Konva) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md deleted file mode 100644 index 48eb3c938..000000000 --- a/README.md +++ /dev/null @@ -1,205 +0,0 @@ -

- Konva logo -

- -

Konva

- -[![Financial Contributors on Open Collective](https://opencollective.com/konva/all/badge.svg?label=financial+contributors)](https://opencollective.com/konva) -[![npm version](https://badge.fury.io/js/konva.svg)](http://badge.fury.io/js/konva) -[![Build Status](https://github.com/konvajs/konva/actions/workflows/test-browser.yml/badge.svg)](https://github.com/konvajs/konva/actions/workflows/test-browser.ym) -[![Build Status](https://github.com/konvajs/konva/actions/workflows/test-node.yml/badge.svg)](https://github.com/konvajs/konva/actions/workflows/test-node.ym)[![CDNJS version](https://img.shields.io/cdnjs/v/konva.svg)](https://cdnjs.com/libraries/konva) - -Konva is an HTML5 Canvas JavaScript framework that enables high performance animations, transitions, node nesting, layering, filtering, caching, event handling for desktop and mobile applications, and much more. - -You can draw things onto the stage, add event listeners to them, move them, scale them, and rotate them independently from other shapes to support high performance animations, even if your application uses thousands of shapes. Served hot with a side of awesomeness. - -This repository began as a GitHub fork of [ericdrowell/KineticJS](https://github.com/ericdrowell/KineticJS). - -- **Visit:** The [Home Page](http://konvajs.org/) and follow on [Twitter](https://twitter.com/lavrton) -- **Discover:** [Tutorials](http://konvajs.org/docs), [API Documentation](http://konvajs.org/api) -- **Help:** [StackOverflow](http://stackoverflow.com/questions/tagged/konvajs), [Discord Chat](https://discord.gg/8FqZwVT) - -# Quick Look - -```html - -
- -``` - -# Browsers support - -Konva works in all modern mobile and desktop browsers. A browser need to be capable to run javascript code from ES2015 spec. For older browsers you may need polyfills for missing functions. - -At the current moment `Konva` doesn't work in IE11 directly. To make it work you just need to provide some polyfills such as `Array.prototype.find`, `String.prototype.trimLeft`, `String.prototype.trimRight`, `Array.from`. - -# Debugging - -The Chrome inspector simply shows the canvas element. To see the Konva objects and their details, install the konva-dev extension at https://github.com/konvajs/konva-devtool. - -# Loading and installing Konva - -Konva supports UMD loading. So you can use all possible variants to load the framework into your project: - -### Load Konva via classical ` -``` - -### Install with npm: - -```bash -npm install konva --save -``` - -```javascript -// The modern way (e.g. an ES6-style import for webpack, parcel) -import Konva from 'konva'; -``` - -#### Typescript usage - -Add DOM definitions into your `tsconfig.json`: - -``` -{ - "compilerOptions": { - "lib": [ - "es6", - "dom" - ] - } -} -``` - -### 3 Minimal bundle - -```javascript -import Konva from 'konva/lib/Core'; -// Now you have a Konva object with Stage, Layer, FastLayer, Group, Shape and some additional utils function. -// Also core currently already have support for drag&drop and animations. -// BUT there are no shapes (rect, circle, etc), no filters. - -// but you can simply add anything you need: -import { Rect } from 'konva/lib/shapes/Rect'; -// importing a shape will automatically inject it into Konva object - -var rect1 = new Rect(); -// or: -var shape = new Konva.Rect(); - -// for filters you can use this: -import { Blur } from 'konva/lib/filters/Blur'; -``` - -### 4 NodeJS env - -In order to run `konva` in nodejs environment you also need to install `canvas` package manually. Konva will use it for 2d canvas API. - -```bash -npm install konva canvas -``` - -Then you can use the same Konva API and all Konva demos will work just fine. You just don't need to use `container` attribute in your stage. - -```js -import Konva from 'konva'; - -const stage = new Konva.Stage({ - width: 500, - height: 500, -}); -// then all regular Konva code will work -``` - -# Backers - -![https://simpleshow.com](https://avatars.githubusercontent.com/u/99720652?s=200&v=4 'https://simpleshow.com') -![https://www.notably.ai/](https://avatars.githubusercontent.com/u/80046841?s=200&v=4 'https://www.notably.ai/') - -- [myposter GmbH](https://www.myposter.de/) -- [queue.gg](https://queue.gg/) - -# Change log - -See [CHANGELOG.md](https://github.com/konvajs/konva/blob/master/CHANGELOG.md). - -## Building the Konva Framework - -To make a full build run `npm run build`. The command will compile all typescript files, combine then into one bundle and run minifier. - -## Testing - -Konva uses Mocha for testing. - -- If you need run test only one time run `npm run test`. -- While developing it is easy to use `npm start`. Just run it and go to [http://localhost:1234/unit-tests.html](http://localhost:1234/unit-tests.html). The watcher will rebuild the bundle on any change. - -Konva is covered with hundreds of tests and well over a thousand assertions. -Konva uses TDD (test driven development) which means that every new feature or bug fix is accompanied with at least one new test. - -## Generate documentation - -Run `npx gulp api` which will build the documentation files and place them in the `api` folder. - -# Pull Requests - -I'd be happy to review any pull requests that may better the Konva project, -in particular if you have a bug fix, enhancement, or a new shape (see `src/shapes` for examples). Before doing so, please first make sure that all of the tests pass (`npm run test`). - -## Contributors - -### Financial Contributors - -Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/konva/contribute)] - -#### Individuals - - - -#### Organizations - -Support this project with your organization. Your logo will show up here with a link to your website. [[Contribute](https://opencollective.com/konva/contribute)] - - - - - - - - - - - diff --git a/eslint.config.mjs b/eslint.config.mjs deleted file mode 100644 index c6231ea12..000000000 --- a/eslint.config.mjs +++ /dev/null @@ -1,3 +0,0 @@ -import tseslint from 'typescript-eslint'; - -export default tseslint.config(...tseslint.configs.recommended); diff --git a/gulpfile.mjs b/gulpfile.mjs deleted file mode 100644 index 559914c44..000000000 --- a/gulpfile.mjs +++ /dev/null @@ -1,110 +0,0 @@ -import gulp from 'gulp'; -import rename from 'gulp-rename'; -import uglify from 'gulp-uglify-es'; -import replace from 'gulp-replace'; -import jsdoc from 'gulp-jsdoc3'; -import connect from 'gulp-connect'; -import gutil from 'gulp-util'; - -import fs from 'fs'; -var NodeParams = fs - .readFileSync('./resources/doc-includes/NodeParams.txt') - .toString(); -var ContainerParams = fs - .readFileSync('./resources/doc-includes/ContainerParams.txt') - .toString(); -var ShapeParams = fs - .readFileSync('./resources/doc-includes/ShapeParams.txt') - .toString(); - -const conf = JSON.parse(fs.readFileSync('./package.json')); - -function build() { - return gulp - .src(['./konva.js']) - .pipe(replace('@@shapeParams', ShapeParams)) - .pipe(replace('@@nodeParams', NodeParams)) - .pipe(replace('@@containerParams', ContainerParams)) - .pipe(replace('@@version', conf.version)) - .pipe(replace('@@date', new Date().toDateString())); -} - -gulp.task('update-version-lib', function () { - return gulp - .src(['./lib/Global.js']) - .pipe(replace('@@version', conf.version)) - .pipe(rename('Global.js')) - .pipe(gulp.dest('./lib')); -}); - -gulp.task('update-version-cmj', function () { - return gulp - .src(['./cmj/Global.js']) - .pipe(replace('@@version', conf.version)) - .pipe(rename('Global.js')) - .pipe(gulp.dest('./cmj')); -}); - -gulp.task('update-version-es-to-cmj-index', function () { - return gulp - .src(['./lib/index.js']) - .pipe( - replace(`import { Konva } from './_F`, `import { Konva } from '../cmj/_F`) - ) - .pipe(rename('index.js')) - .pipe(gulp.dest('./lib')); -}); - -gulp.task('update-version-es-to-cmj-node', function () { - return gulp - .src(['./lib/index-node.js']) - .pipe( - replace(`import { Konva } from './_F`, `import { Konva } from '../cmj/_F`) - ) - .pipe(rename('index-node.js')) - .pipe(gulp.dest('./lib')); -}); - -// create usual build konva.js and konva.min.js -gulp.task('pre-build', function () { - return build() - .pipe(rename('konva.js')) - .pipe(gulp.dest('./')) - .pipe( - uglify.default({ output: { comments: /^!|@preserve|@license|@cc_on/i } }) - ) - .on('error', function (err) { - gutil.log(gutil.colors.red('[Error]'), err.toString()); - }) - .pipe(rename('konva.min.js')) - .pipe(gulp.dest('./')); -}); - -gulp.task( - 'build', - gulp.parallel([ - 'update-version-lib', - // 'update-version-cmj', - // 'update-version-es-to-cmj-index', - // 'update-version-es-to-cmj-node', - 'pre-build', - ]) -); - -// local server for better development -gulp.task('server', function () { - connect.server(); -}); - -// // generate documentation -gulp.task('api', function () { - return gulp.src('./konva.js').pipe( - jsdoc({ - opts: { - destination: './api', - }, - }) - ); -}); - -gulp.task('default', gulp.parallel(['server'])); diff --git a/konva-node/demo.js b/konva-node/demo.js deleted file mode 100644 index be94f4630..000000000 --- a/konva-node/demo.js +++ /dev/null @@ -1,77 +0,0 @@ -import fs from 'fs'; - -// relative path here -// but you will need just require('konva-node'); -import Konva from '../'; - -// Create stage. Container parameter is not required in NodeJS. -var stage = new Konva.Stage({ - width: 100, - height: 100, -}); - -var layer = new Konva.Layer(); -stage.add(layer); - -var rect = new Konva.Rect({ - width: 100, - height: 100, - x: 50, - y: 50, - fill: 'white', -}); -var text = new Konva.Text({ - text: 'Generated inside node js', - x: 20, - y: 20, - fill: 'black', -}); -layer.add(rect).add(text); -layer.draw(); -stage.setSize({ - width: 200, - height: 200, -}); - -// check tween works -var tween = new Konva.Tween({ - node: rect, - duration: 1, - x: -50, -}); -tween.play(); - -// After tween we want to convert stage to dataURL -setTimeout(function () { - stage.toDataURL({ - callback: function (data) { - // Then add result to stage - var img = new Konva.window.Image(); - img.onload = function () { - var image = new Konva.Image({ - image: img, - x: 10, - y: 50, - }); - layer.add(image); - layer.draw(); - // save stage image as file - stage.toDataURL({ - callback: function (data) { - var base64Data = data.replace(/^data:image\/png;base64,/, ''); - fs.writeFile('./out.png', base64Data, 'base64', function (err) { - err && console.log(err); - console.log('See out.png'); - }); - // now try to create image from url - Konva.Image.fromURL(data, () => { - console.log('image loaded'); - // shoul'd throw - }); - }, - }); - }; - img.src = data; - }, - }); -}, 1050); diff --git a/konva-node/index.js b/konva-node/index.js deleted file mode 100644 index 4b6b3c77b..000000000 --- a/konva-node/index.js +++ /dev/null @@ -1,39 +0,0 @@ -var Konva = require('konva'); -var canvas = require('canvas'); - -// mock window -Konva.window = { - Image: canvas.Image, - devicePixelRatio: 1, -}; -// mock document -Konva.document = { - createElement: function () {}, - documentElement: { - addEventListener: function () {}, - }, -}; - -// make some global injections -global.requestAnimationFrame = (cb) => { - setImmediate(cb); -}; - -// create canvas in Node env -Konva.Util.createCanvasElement = () => { - const node = new canvas.Canvas(); - node.style = {}; - return node; -}; - -// create image in Node env -Konva.Util.createImageElement = () => { - const node = new canvas.Image(); - node.style = {}; - return node; -}; - -// _checkVisibility use dom element, in node we can skip it -Konva.Stage.prototype._checkVisibility = () => {}; - -module.exports = Konva; diff --git a/konva-node/package.json b/konva-node/package.json deleted file mode 100644 index df2a8ad52..000000000 --- a/konva-node/package.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "name": "konva-node", - "version": "0.11.2", - "description": "Konva framework for NodeJS env", - "main": "index.js", - "files": [ - "index.js" - ], - "type": "module", - "typings": "./node_modules/konva/konva.d.ts", - "scripts": {}, - "keywords": [ - "canvas", - "animations", - "graphic", - "html5" - ], - "author": "Anton Lavrenov", - "bugs": { - "url": "https://github.com/konvajs/konva/issues" - }, - "homepage": "http://konvajs.org/", - "repository": { - "type": "git", - "url": "git://github.com/konvajs/konva.git" - }, - "license": "MIT", - "dependencies": { - "canvas": "^2.5.0", - "konva": "^7" - } -} diff --git a/package.json b/package.json deleted file mode 100644 index faa5bcbab..000000000 --- a/package.json +++ /dev/null @@ -1,110 +0,0 @@ -{ - "name": "konva", - "version": "9.3.16", - "description": "HTML5 2d canvas library.", - "author": "Anton Lavrenov", - "files": [ - "README.md", - "konva.js", - "konva.min.js", - "lib", - "cmj" - ], - "main": "./lib/index-node.js", - "browser": "./lib/index.js", - "typings": "./lib/index-types.d.ts", - "scripts": { - "start": "npm run test:watch", - "compile": "npm run clean && npm run tsc && cp ./src/index-types.d.ts ./lib/index-types.d.ts && npm run rollup", - "build": "npm run compile && cp ./src/index-types.d.ts ./lib && gulp build && node ./rename-imports.mjs", - "test:import": "npm run build && node ./test/import-test.cjs &&node ./test/import-test.mjs", - "test": "npm run test:browser && npm run test:node", - "test:build": "parcel build ./test/unit-tests.html --dist-dir ./test-build --target none --public-url ./ --no-source-maps", - "test:browser": "npm run test:build && mocha-headless-chrome -f ./test-build/unit-tests.html -a disable-web-security", - "test:watch": "rm -rf ./.parcel-cache && PARCEL_WORKERS=0 parcel serve ./test/unit-tests.html ./test/manual-tests.html ./test/sandbox.html ./test/text-paths.html ./test/bunnies.html", - "test:node": "ts-mocha -r ./test/node-global-setup.mjs -p ./test/tsconfig.json test/unit/**/*.ts --exit && npm run test:import", - "tsc": "tsc --removeComments", - "rollup": "rollup -c --bundleConfigAsCjs", - "clean": "rm -rf ./lib && rm -rf ./types && rm -rf ./cmj && rm -rf ./test-build", - "watch": "rollup -c -w", - "size": "size-limit" - }, - "targets": { - "none": {} - }, - "funding": [ - { - "type": "patreon", - "url": "https://www.patreon.com/lavrton" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/konva" - }, - { - "type": "github", - "url": "https://github.com/sponsors/lavrton" - } - ], - "size-limit": [ - { - "limit": "45 KB", - "path": "./lib/index.js" - }, - { - "limit": "26 KB", - "path": "./lib/Core.js" - }, - { - "path": "./konva.min.js" - } - ], - "devDependencies": { - "@parcel/transformer-image": "2.13.2", - "@size-limit/preset-big-lib": "^11.1.6", - "@types/mocha": "^10.0.10", - "canvas": "^2.11.2", - "chai": "5.1.2", - "filehound": "^1.17.6", - "gulp": "^5.0.0", - "gulp-concat": "^2.6.1", - "gulp-connect": "^5.7.0", - "gulp-exec": "^5.0.0", - "gulp-jsdoc3": "^3.0.0", - "gulp-rename": "^2.0.0", - "gulp-replace": "^1.1.4", - "gulp-typescript": "^5.0.1", - "gulp-uglify": "^3.0.2", - "gulp-uglify-es": "^3.0.0", - "gulp-util": "^3.0.8", - "mocha": "10.2.0", - "mocha-headless-chrome": "^4.0.0", - "parcel": "2.13.2", - "process": "^0.11.10", - "rollup": "^4.28.1", - "rollup-plugin-typescript2": "^0.36.0", - "size-limit": "^11.1.6", - "ts-mocha": "^10.0.0", - "ts-node": "^10.9.2", - "typescript": "^5.7.2" - }, - "keywords": [ - "canvas", - "animations", - "graphic", - "html5" - ], - "prettier": { - "singleQuote": true - }, - "bugs": { - "url": "https://github.com/konvajs/konva/issues" - }, - "homepage": "http://konvajs.org/", - "readmeFilename": "README.md", - "repository": { - "type": "git", - "url": "git://github.com/konvajs/konva.git" - }, - "license": "MIT" -} diff --git a/release.sh b/release.sh deleted file mode 100755 index 2b4eedeea..000000000 --- a/release.sh +++ /dev/null @@ -1,77 +0,0 @@ -#!/usr/bin/env bash - -set -e -old_version="$(git describe --abbrev=0 --tags)" -new_version=$1 - - -old_cdn="https://unpkg.com/konva@${old_version}/konva.js" -new_cdn="https://unpkg.com/konva@${new_version}/konva.js" - -old_cdn_min="https://unpkg.com/konva@${old_version}/konva.min.js" -new_cdn_min="https://unpkg.com/konva@${new_version}/konva.min.js" - -# make sure new version parameter is passed -if [ -z "$1" ] - then - echo "ERROR - pass new version. usage release.sh 0.11.0" - exit 2 -fi - -# make sure changle log updated -while true; do - read -p "Did you update CHANGELOG.md? " yn - case $yn in - [Yy]* ) break;; - [Nn]* ) exit;; - * ) echo "Please answer yes or no.";; - esac -done - -echo "Old version: ${old_version}" -echo "New version: ${new_version}" - -echo "Pulling" -git pull >/dev/null - -echo "build and test" -npm run build >/dev/null -# npm run test - - -echo "commit change log updates" -git commit -am "update CHANGELOG with new version" --allow-empty >/dev/null - -echo "npm version $1 --no-git-tag-version" -npm version $1 --no-git-tag-version --allow-same-version >/dev/null - -echo "build for $1" -npm run build >/dev/null -git commit -am "build for $1" --allow-empty >/dev/null - -echo "update CDN link in REAME" -perl -i -pe "s|${old_cdn_min}|${new_cdn_min}|g" ./README.md >/dev/null -git commit -am "update cdn link" --allow-empty >/dev/null - -echo "create new git tag" -git tag $1 >/dev/null - -cd ../konva -git push >/dev/null -git push --tags >/dev/null -npm publish - -echo "copy konva.js into konva-site" -cp ./konva.js ../konva-site/ -cd ../konva-site - -echo "replace CDN links" - - -find source themes/hexo3/layout react-demos vue-demos main-demo -name "*.json" -exec perl -i -pe "s|${old_version}|${new_version}|g" {} + >/dev/null -find source themes/hexo3/layout react-demos vue-demos main-demo -name "*.html" -exec perl -i -pe "s|${old_version}|${new_version}|g" {} + >/dev/null - -echo "regenerate site" -./deploy.sh >/dev/null - -echo "DONE!" diff --git a/rename-imports.mjs b/rename-imports.mjs deleted file mode 100644 index db66051d5..000000000 --- a/rename-imports.mjs +++ /dev/null @@ -1,47 +0,0 @@ -import FileHound from 'filehound'; -import fs from 'fs'; - -const files = FileHound.create().paths('./lib').ext(['js', 'ts']).find(); - -files.then((filePaths) => { - filePaths.forEach((filepath) => { - fs.readFile(filepath, 'utf8', (err, text) => { - if (!text.match(/import .* from/g)) { - return; - } - text = text.replace(/(import .* from\s+['"])(.*)(?=['"])/g, '$1$2.js'); - if (text.match(/export .* from/g)) { - text = text.replace(/(export .* from\s+['"])(.*)(?=['"])/g, '$1$2.js'); - } - - if (err) throw err; - - // stupid replacement back - text = text.replace( - "import * as Canvas from 'canvas.js';", - "import * as Canvas from 'canvas';" - ); - - // Handle import("./x/y/z") syntax. - text = text.replace(/(import\s*\(\s*['"])(.*)(?=['"])/g, '$1$2.js'); - - fs.writeFile(filepath, text, function (err) { - if (err) { - throw err; - } - }); - }); - }); -}); - -const indexFiles = ['lib/index.js', 'lib/index-node.js', 'lib/Core.js']; -indexFiles.forEach((filepath) => { - fs.readFile(filepath, 'utf8', (err, text) => { - text = text.replace('exports.default =', 'module.exports ='); - fs.writeFile(filepath, text, function (err) { - if (err) { - throw err; - } - }); - }); -}); diff --git a/resources/doc-includes/ContainerParams.txt b/resources/doc-includes/ContainerParams.txt deleted file mode 100644 index 6b9d72375..000000000 --- a/resources/doc-includes/ContainerParams.txt +++ /dev/null @@ -1,6 +0,0 @@ -* @param {Object} [config.clip] set clip - * @param {Number} [config.clipX] set clip x - * @param {Number} [config.clipY] set clip y - * @param {Number} [config.clipWidth] set clip width - * @param {Number} [config.clipHeight] set clip height - * @param {Function} [config.clipFunc] set clip func diff --git a/resources/doc-includes/NodeParams.txt b/resources/doc-includes/NodeParams.txt deleted file mode 100644 index b3be0fbf6..000000000 --- a/resources/doc-includes/NodeParams.txt +++ /dev/null @@ -1,20 +0,0 @@ -@param {Number} [config.x] - * @param {Number} [config.y] - * @param {Number} [config.width] - * @param {Number} [config.height] - * @param {Boolean} [config.visible] - * @param {Boolean} [config.listening] whether or not the node is listening for events - * @param {String} [config.id] unique id - * @param {String} [config.name] non-unique name - * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 - * @param {Object} [config.scale] set scale - * @param {Number} [config.scaleX] set scale x - * @param {Number} [config.scaleY] set scale y - * @param {Number} [config.rotation] rotation in degrees - * @param {Object} [config.offset] offset from center point and rotation point - * @param {Number} [config.offsetX] set offset x - * @param {Number} [config.offsetY] set offset y - * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop - * the entire stage by dragging any portion of the stage - * @param {Number} [config.dragDistance] - * @param {Function} [config.dragBoundFunc] \ No newline at end of file diff --git a/resources/doc-includes/ShapeParams.txt b/resources/doc-includes/ShapeParams.txt deleted file mode 100644 index dbaee61c1..000000000 --- a/resources/doc-includes/ShapeParams.txt +++ /dev/null @@ -1,53 +0,0 @@ -@param {String} [config.fill] fill color - * @param {Image} [config.fillPatternImage] fill pattern image - * @param {Number} [config.fillPatternX] - * @param {Number} [config.fillPatternY] - * @param {Object} [config.fillPatternOffset] object with x and y component - * @param {Number} [config.fillPatternOffsetX] - * @param {Number} [config.fillPatternOffsetY] - * @param {Object} [config.fillPatternScale] object with x and y component - * @param {Number} [config.fillPatternScaleX] - * @param {Number} [config.fillPatternScaleY] - * @param {Number} [config.fillPatternRotation] - * @param {String} [config.fillPatternRepeat] can be "repeat", "repeat-x", "repeat-y", or "no-repeat". The default is "no-repeat" - * @param {Object} [config.fillLinearGradientStartPoint] object with x and y component - * @param {Number} [config.fillLinearGradientStartPointX] - * @param {Number} [config.fillLinearGradientStartPointY] - * @param {Object} [config.fillLinearGradientEndPoint] object with x and y component - * @param {Number} [config.fillLinearGradientEndPointX] - * @param {Number} [config.fillLinearGradientEndPointY] - * @param {Array} [config.fillLinearGradientColorStops] array of color stops - * @param {Object} [config.fillRadialGradientStartPoint] object with x and y component - * @param {Number} [config.fillRadialGradientStartPointX] - * @param {Number} [config.fillRadialGradientStartPointY] - * @param {Object} [config.fillRadialGradientEndPoint] object with x and y component - * @param {Number} [config.fillRadialGradientEndPointX] - * @param {Number} [config.fillRadialGradientEndPointY] - * @param {Number} [config.fillRadialGradientStartRadius] - * @param {Number} [config.fillRadialGradientEndRadius] - * @param {Array} [config.fillRadialGradientColorStops] array of color stops - * @param {Boolean} [config.fillEnabled] flag which enables or disables the fill. The default value is true - * @param {String} [config.fillPriority] can be color, linear-gradient, radial-graident, or pattern. The default value is color. The fillPriority property makes it really easy to toggle between different fill types. For example, if you want to toggle between a fill color style and a fill pattern style, simply set the fill property and the fillPattern properties, and then use setFillPriority('color') to render the shape with a color fill, or use setFillPriority('pattern') to render the shape with the pattern fill configuration - * @param {String} [config.stroke] stroke color - * @param {Number} [config.strokeWidth] stroke width - * @param {Boolean} [config.fillAfterStrokeEnabled]. Should we draw fill AFTER stroke? Default is false. - * @param {Number} [config.hitStrokeWidth] size of the stroke on hit canvas. The default is "auto" - equals to strokeWidth - * @param {Boolean} [config.strokeHitEnabled] flag which enables or disables stroke hit region. The default is true - * @param {Boolean} [config.perfectDrawEnabled] flag which enables or disables using buffer canvas. The default is true - * @param {Boolean} [config.shadowForStrokeEnabled] flag which enables or disables shadow for stroke. The default is true - * @param {Boolean} [config.strokeScaleEnabled] flag which enables or disables stroke scale. The default is true - * @param {Boolean} [config.strokeEnabled] flag which enables or disables the stroke. The default value is true - * @param {String} [config.lineJoin] can be miter, round, or bevel. The default - * is miter - * @param {String} [config.lineCap] can be butt, round, or square. The default - * is butt - * @param {String} [config.shadowColor] - * @param {Number} [config.shadowBlur] - * @param {Object} [config.shadowOffset] object with x and y component - * @param {Number} [config.shadowOffsetX] - * @param {Number} [config.shadowOffsetY] - * @param {Number} [config.shadowOpacity] shadow opacity. Can be any real number - * between 0 and 1 - * @param {Boolean} [config.shadowEnabled] flag which enables or disables the shadow. The default value is true - * @param {Array} [config.dash] - * @param {Boolean} [config.dashEnabled] flag which enables or disables the dashArray. The default value is true diff --git a/resources/jsdoc.conf.json b/resources/jsdoc.conf.json deleted file mode 100644 index 7cc1fd133..000000000 --- a/resources/jsdoc.conf.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "path" : "ink-docstrap", - "tags" : { - "allowUnknownTags" : true - }, - "plugins" : ["plugins/markdown"], - - "templates" : { - "cleverLinks" : false, - "monospaceLinks" : false, - "dateFormat" : "ddd MMM Do YYYY", - "outputSourceFiles" : true, - "outputSourcePath" : true, - "systemName" : "Konva", - "footer" : "", - "copyright" : "Konva Copyright © 2015 The contributors to the Konva project.", - "navType" : "vertical", - "theme" : "cosmo", - "linenums" : true, - "collapseSymbols" : false, - "inverseNav" : true, - "highlightTutorialCode" : true - }, - "markdown" : { - "parser" : "gfm", - "hardwrap" : true - } -} diff --git a/rollup.config.cjs b/rollup.config.cjs deleted file mode 100644 index 3e7aa5100..000000000 --- a/rollup.config.cjs +++ /dev/null @@ -1,47 +0,0 @@ -// import resolve from 'rollup-plugin-node-resolve'; -import typescript from 'rollup-plugin-typescript2'; - -const pkg = require('./package.json'); - -export default { - input: `src/index.ts`, - output: [ - { - file: 'konva.js', - name: 'Konva', - format: 'umd', - sourcemap: false, - freeze: false, - }, - // { file: pkg.module, format: 'es', sourcemap: true } - ], - // Indicate here external modules you don't wanna include in your bundle (i.e.: 'lodash') - external: [], - watch: { - include: 'src/**', - }, - plugins: [ - // Allow json resolution - // json(), - // Compile TypeScript files - typescript({ - useTsconfigDeclarationDir: true, - abortOnError: false, - removeComments: false, - tsconfigOverride: { - compilerOptions: { - module: 'ES2020', - }, - }, - }), - // // Allow bundling cjs modules (unlike webpack, rollup doesn't understand cjs) - // commonjs(), - // // Allow node_modules resolution, so you can use 'external' to control - // // which external modules to include in the bundle - // // https://github.com/rollup/rollup-plugin-node-resolve#usage - // resolve(), - - // Resolve source maps to the original source - // sourceMaps() - ], -}; diff --git a/src/Animation.ts b/src/Animation.ts deleted file mode 100644 index 2f6bd5589..000000000 --- a/src/Animation.ts +++ /dev/null @@ -1,237 +0,0 @@ -import { glob } from './Global'; -import { Layer } from './Layer'; -import { IFrame, AnimationFn } from './types'; -import { Util } from './Util'; - -const now = (function (): () => number { - if (glob.performance && glob.performance.now) { - return function () { - return glob.performance.now(); - }; - } - - return function () { - return new Date().getTime(); - }; -})(); - -/** - * Animation constructor. - * @constructor - * @memberof Konva - * @param {AnimationFn} func function executed on each animation frame. The function is passed a frame object, which contains - * timeDiff, lastTime, time, and frameRate properties. The timeDiff property is the number of milliseconds that have passed - * since the last animation frame. The time property is the time in milliseconds that elapsed from the moment the animation started - * to the current animation frame. The lastTime property is a `time` value from the previous frame. The frameRate property is the current frame rate in frames / second. - * Return false from function, if you don't need to redraw layer/layers on some frames. - * @param {Konva.Layer|Array} [layers] layer(s) to be redrawn on each animation frame. Can be a layer, an array of layers, or null. - * Not specifying a node will result in no redraw. - * @example - * // move a node to the right at 50 pixels / second - * var velocity = 50; - * - * var anim = new Konva.Animation(function(frame) { - * var dist = velocity * (frame.timeDiff / 1000); - * node.move({x: dist, y: 0}); - * }, layer); - * - * anim.start(); - */ -export class Animation { - func: AnimationFn; - id = Animation.animIdCounter++; - - layers: Layer[]; - - frame: IFrame = { - time: 0, - timeDiff: 0, - lastTime: now(), - frameRate: 0, - }; - - constructor(func: AnimationFn, layers?) { - this.func = func; - this.setLayers(layers); - } - /** - * set layers to be redrawn on each animation frame - * @method - * @name Konva.Animation#setLayers - * @param {Konva.Layer|Array} [layers] layer(s) to be redrawn. Can be a layer, an array of layers, or null. Not specifying a node will result in no redraw. - * @return {Konva.Animation} this - */ - setLayers(layers: null | Layer | Layer[]) { - let lays: Layer[] = []; - // if passing in no layers - if (layers) { - lays = Array.isArray(layers) ? layers : [layers]; - } - this.layers = lays; - return this; - } - /** - * get layers - * @method - * @name Konva.Animation#getLayers - * @return {Array} Array of Konva.Layer - */ - getLayers() { - return this.layers; - } - /** - * add layer. Returns true if the layer was added, and false if it was not - * @method - * @name Konva.Animation#addLayer - * @param {Konva.Layer} layer to add - * @return {Bool} true if layer is added to animation, otherwise false - */ - addLayer(layer: Layer) { - const layers = this.layers; - const len = layers.length; - - // don't add the layer if it already exists - for (let n = 0; n < len; n++) { - if (layers[n]._id === layer._id) { - return false; - } - } - - this.layers.push(layer); - return true; - } - /** - * determine if animation is running or not. returns true or false - * @method - * @name Konva.Animation#isRunning - * @return {Bool} is animation running? - */ - isRunning() { - const a = Animation; - const animations = a.animations; - const len = animations.length; - - for (let n = 0; n < len; n++) { - if (animations[n].id === this.id) { - return true; - } - } - return false; - } - /** - * start animation - * @method - * @name Konva.Animation#start - * @return {Konva.Animation} this - */ - start() { - this.stop(); - this.frame.timeDiff = 0; - this.frame.lastTime = now(); - Animation._addAnimation(this); - return this; - } - /** - * stop animation - * @method - * @name Konva.Animation#stop - * @return {Konva.Animation} this - */ - stop() { - Animation._removeAnimation(this); - return this; - } - _updateFrameObject(time: number) { - this.frame.timeDiff = time - this.frame.lastTime; - this.frame.lastTime = time; - this.frame.time += this.frame.timeDiff; - this.frame.frameRate = 1000 / this.frame.timeDiff; - } - - static animations: Array = []; - static animIdCounter = 0; - static animRunning = false; - - static _addAnimation(anim) { - this.animations.push(anim); - this._handleAnimation(); - } - static _removeAnimation(anim) { - const id = anim.id; - const animations = this.animations; - const len = animations.length; - - for (let n = 0; n < len; n++) { - if (animations[n].id === id) { - this.animations.splice(n, 1); - break; - } - } - } - - static _runFrames() { - const layerHash = {}; - const animations = this.animations; - /* - * loop through all animations and execute animation - * function. if the animation object has specified node, - * we can add the node to the nodes hash to eliminate - * drawing the same node multiple times. The node property - * can be the stage itself or a layer - */ - /* - * WARNING: don't cache animations.length because it could change while - * the for loop is running, causing a JS error - */ - - for (let n = 0; n < animations.length; n++) { - const anim = animations[n]; - const layers = anim.layers; - const func = anim.func; - - anim._updateFrameObject(now()); - const layersLen = layers.length; - - // if animation object has a function, execute it - let needRedraw; - if (func) { - // allow anim bypassing drawing - needRedraw = func.call(anim, anim.frame) !== false; - } else { - needRedraw = true; - } - if (!needRedraw) { - continue; - } - for (let i = 0; i < layersLen; i++) { - const layer = layers[i]; - - if (layer._id !== undefined) { - layerHash[layer._id] = layer; - } - } - } - - for (const key in layerHash) { - if (!layerHash.hasOwnProperty(key)) { - continue; - } - layerHash[key].batchDraw(); - } - } - static _animationLoop() { - const Anim = Animation; - if (Anim.animations.length) { - Anim._runFrames(); - Util.requestAnimFrame(Anim._animationLoop); - } else { - Anim.animRunning = false; - } - } - static _handleAnimation() { - if (!this.animRunning) { - this.animRunning = true; - Util.requestAnimFrame(this._animationLoop); - } - } -} diff --git a/src/BezierFunctions.ts b/src/BezierFunctions.ts deleted file mode 100644 index b17a6492d..000000000 --- a/src/BezierFunctions.ts +++ /dev/null @@ -1,826 +0,0 @@ -// Credits: rveciana/svg-path-properties - -// Legendre-Gauss abscissae (xi values, defined at i=n as the roots of the nth order Legendre polynomial Pn(x)) -export const tValues = [ - [], - [], - [ - -0.5773502691896257645091487805019574556476, - 0.5773502691896257645091487805019574556476, - ], - [ - 0, -0.7745966692414833770358530799564799221665, - 0.7745966692414833770358530799564799221665, - ], - [ - -0.3399810435848562648026657591032446872005, - 0.3399810435848562648026657591032446872005, - -0.8611363115940525752239464888928095050957, - 0.8611363115940525752239464888928095050957, - ], - [ - 0, -0.5384693101056830910363144207002088049672, - 0.5384693101056830910363144207002088049672, - -0.9061798459386639927976268782993929651256, - 0.9061798459386639927976268782993929651256, - ], - [ - 0.6612093864662645136613995950199053470064, - -0.6612093864662645136613995950199053470064, - -0.2386191860831969086305017216807119354186, - 0.2386191860831969086305017216807119354186, - -0.9324695142031520278123015544939946091347, - 0.9324695142031520278123015544939946091347, - ], - [ - 0, 0.4058451513773971669066064120769614633473, - -0.4058451513773971669066064120769614633473, - -0.7415311855993944398638647732807884070741, - 0.7415311855993944398638647732807884070741, - -0.9491079123427585245261896840478512624007, - 0.9491079123427585245261896840478512624007, - ], - [ - -0.1834346424956498049394761423601839806667, - 0.1834346424956498049394761423601839806667, - -0.5255324099163289858177390491892463490419, - 0.5255324099163289858177390491892463490419, - -0.7966664774136267395915539364758304368371, - 0.7966664774136267395915539364758304368371, - -0.9602898564975362316835608685694729904282, - 0.9602898564975362316835608685694729904282, - ], - [ - 0, -0.8360311073266357942994297880697348765441, - 0.8360311073266357942994297880697348765441, - -0.9681602395076260898355762029036728700494, - 0.9681602395076260898355762029036728700494, - -0.3242534234038089290385380146433366085719, - 0.3242534234038089290385380146433366085719, - -0.6133714327005903973087020393414741847857, - 0.6133714327005903973087020393414741847857, - ], - [ - -0.1488743389816312108848260011297199846175, - 0.1488743389816312108848260011297199846175, - -0.4333953941292471907992659431657841622, - 0.4333953941292471907992659431657841622, - -0.6794095682990244062343273651148735757692, - 0.6794095682990244062343273651148735757692, - -0.8650633666889845107320966884234930485275, - 0.8650633666889845107320966884234930485275, - -0.9739065285171717200779640120844520534282, - 0.9739065285171717200779640120844520534282, - ], - [ - 0, -0.2695431559523449723315319854008615246796, - 0.2695431559523449723315319854008615246796, - -0.5190961292068118159257256694586095544802, - 0.5190961292068118159257256694586095544802, - -0.7301520055740493240934162520311534580496, - 0.7301520055740493240934162520311534580496, - -0.8870625997680952990751577693039272666316, - 0.8870625997680952990751577693039272666316, - -0.9782286581460569928039380011228573907714, - 0.9782286581460569928039380011228573907714, - ], - [ - -0.1252334085114689154724413694638531299833, - 0.1252334085114689154724413694638531299833, - -0.3678314989981801937526915366437175612563, - 0.3678314989981801937526915366437175612563, - -0.587317954286617447296702418940534280369, - 0.587317954286617447296702418940534280369, - -0.7699026741943046870368938332128180759849, - 0.7699026741943046870368938332128180759849, - -0.9041172563704748566784658661190961925375, - 0.9041172563704748566784658661190961925375, - -0.9815606342467192506905490901492808229601, - 0.9815606342467192506905490901492808229601, - ], - [ - 0, -0.2304583159551347940655281210979888352115, - 0.2304583159551347940655281210979888352115, - -0.4484927510364468528779128521276398678019, - 0.4484927510364468528779128521276398678019, - -0.6423493394403402206439846069955156500716, - 0.6423493394403402206439846069955156500716, - -0.8015780907333099127942064895828598903056, - 0.8015780907333099127942064895828598903056, - -0.9175983992229779652065478365007195123904, - 0.9175983992229779652065478365007195123904, - -0.9841830547185881494728294488071096110649, - 0.9841830547185881494728294488071096110649, - ], - [ - -0.1080549487073436620662446502198347476119, - 0.1080549487073436620662446502198347476119, - -0.3191123689278897604356718241684754668342, - 0.3191123689278897604356718241684754668342, - -0.5152486363581540919652907185511886623088, - 0.5152486363581540919652907185511886623088, - -0.6872929048116854701480198030193341375384, - 0.6872929048116854701480198030193341375384, - -0.8272013150697649931897947426503949610397, - 0.8272013150697649931897947426503949610397, - -0.928434883663573517336391139377874264477, - 0.928434883663573517336391139377874264477, - -0.986283808696812338841597266704052801676, - 0.986283808696812338841597266704052801676, - ], - [ - 0, -0.2011940939974345223006283033945962078128, - 0.2011940939974345223006283033945962078128, - -0.3941513470775633698972073709810454683627, - 0.3941513470775633698972073709810454683627, - -0.5709721726085388475372267372539106412383, - 0.5709721726085388475372267372539106412383, - -0.7244177313601700474161860546139380096308, - 0.7244177313601700474161860546139380096308, - -0.8482065834104272162006483207742168513662, - 0.8482065834104272162006483207742168513662, - -0.9372733924007059043077589477102094712439, - 0.9372733924007059043077589477102094712439, - -0.9879925180204854284895657185866125811469, - 0.9879925180204854284895657185866125811469, - ], - [ - -0.0950125098376374401853193354249580631303, - 0.0950125098376374401853193354249580631303, - -0.281603550779258913230460501460496106486, - 0.281603550779258913230460501460496106486, - -0.45801677765722738634241944298357757354, - 0.45801677765722738634241944298357757354, - -0.6178762444026437484466717640487910189918, - 0.6178762444026437484466717640487910189918, - -0.7554044083550030338951011948474422683538, - 0.7554044083550030338951011948474422683538, - -0.8656312023878317438804678977123931323873, - 0.8656312023878317438804678977123931323873, - -0.9445750230732325760779884155346083450911, - 0.9445750230732325760779884155346083450911, - -0.9894009349916499325961541734503326274262, - 0.9894009349916499325961541734503326274262, - ], - [ - 0, -0.1784841814958478558506774936540655574754, - 0.1784841814958478558506774936540655574754, - -0.3512317634538763152971855170953460050405, - 0.3512317634538763152971855170953460050405, - -0.5126905370864769678862465686295518745829, - 0.5126905370864769678862465686295518745829, - -0.6576711592166907658503022166430023351478, - 0.6576711592166907658503022166430023351478, - -0.7815140038968014069252300555204760502239, - 0.7815140038968014069252300555204760502239, - -0.8802391537269859021229556944881556926234, - 0.8802391537269859021229556944881556926234, - -0.9506755217687677612227169578958030214433, - 0.9506755217687677612227169578958030214433, - -0.9905754753144173356754340199406652765077, - 0.9905754753144173356754340199406652765077, - ], - [ - -0.0847750130417353012422618529357838117333, - 0.0847750130417353012422618529357838117333, - -0.2518862256915055095889728548779112301628, - 0.2518862256915055095889728548779112301628, - -0.4117511614628426460359317938330516370789, - 0.4117511614628426460359317938330516370789, - -0.5597708310739475346078715485253291369276, - 0.5597708310739475346078715485253291369276, - -0.6916870430603532078748910812888483894522, - 0.6916870430603532078748910812888483894522, - -0.8037049589725231156824174550145907971032, - 0.8037049589725231156824174550145907971032, - -0.8926024664975557392060605911271455154078, - 0.8926024664975557392060605911271455154078, - -0.9558239495713977551811958929297763099728, - 0.9558239495713977551811958929297763099728, - -0.9915651684209309467300160047061507702525, - 0.9915651684209309467300160047061507702525, - ], - [ - 0, -0.1603586456402253758680961157407435495048, - 0.1603586456402253758680961157407435495048, - -0.3165640999636298319901173288498449178922, - 0.3165640999636298319901173288498449178922, - -0.4645707413759609457172671481041023679762, - 0.4645707413759609457172671481041023679762, - -0.6005453046616810234696381649462392798683, - 0.6005453046616810234696381649462392798683, - -0.7209661773352293786170958608237816296571, - 0.7209661773352293786170958608237816296571, - -0.8227146565371428249789224867127139017745, - 0.8227146565371428249789224867127139017745, - -0.9031559036148179016426609285323124878093, - 0.9031559036148179016426609285323124878093, - -0.960208152134830030852778840687651526615, - 0.960208152134830030852778840687651526615, - -0.9924068438435844031890176702532604935893, - 0.9924068438435844031890176702532604935893, - ], - [ - -0.0765265211334973337546404093988382110047, - 0.0765265211334973337546404093988382110047, - -0.227785851141645078080496195368574624743, - 0.227785851141645078080496195368574624743, - -0.3737060887154195606725481770249272373957, - 0.3737060887154195606725481770249272373957, - -0.5108670019508270980043640509552509984254, - 0.5108670019508270980043640509552509984254, - -0.6360536807265150254528366962262859367433, - 0.6360536807265150254528366962262859367433, - -0.7463319064601507926143050703556415903107, - 0.7463319064601507926143050703556415903107, - -0.8391169718222188233945290617015206853296, - 0.8391169718222188233945290617015206853296, - -0.9122344282513259058677524412032981130491, - 0.9122344282513259058677524412032981130491, - -0.963971927277913791267666131197277221912, - 0.963971927277913791267666131197277221912, - -0.9931285991850949247861223884713202782226, - 0.9931285991850949247861223884713202782226, - ], - [ - 0, -0.1455618541608950909370309823386863301163, - 0.1455618541608950909370309823386863301163, - -0.288021316802401096600792516064600319909, - 0.288021316802401096600792516064600319909, - -0.4243421202074387835736688885437880520964, - 0.4243421202074387835736688885437880520964, - -0.551618835887219807059018796724313286622, - 0.551618835887219807059018796724313286622, - -0.667138804197412319305966669990339162597, - 0.667138804197412319305966669990339162597, - -0.7684399634756779086158778513062280348209, - 0.7684399634756779086158778513062280348209, - -0.8533633645833172836472506385875676702761, - 0.8533633645833172836472506385875676702761, - -0.9200993341504008287901871337149688941591, - 0.9200993341504008287901871337149688941591, - -0.9672268385663062943166222149076951614246, - 0.9672268385663062943166222149076951614246, - -0.9937521706203895002602420359379409291933, - 0.9937521706203895002602420359379409291933, - ], - [ - -0.0697392733197222212138417961186280818222, - 0.0697392733197222212138417961186280818222, - -0.2078604266882212854788465339195457342156, - 0.2078604266882212854788465339195457342156, - -0.3419358208920842251581474204273796195591, - 0.3419358208920842251581474204273796195591, - -0.4693558379867570264063307109664063460953, - 0.4693558379867570264063307109664063460953, - -0.5876404035069115929588769276386473488776, - 0.5876404035069115929588769276386473488776, - -0.6944872631866827800506898357622567712673, - 0.6944872631866827800506898357622567712673, - -0.7878168059792081620042779554083515213881, - 0.7878168059792081620042779554083515213881, - -0.8658125777203001365364256370193787290847, - 0.8658125777203001365364256370193787290847, - -0.9269567721871740005206929392590531966353, - 0.9269567721871740005206929392590531966353, - -0.9700604978354287271239509867652687108059, - 0.9700604978354287271239509867652687108059, - -0.994294585482399292073031421161298980393, - 0.994294585482399292073031421161298980393, - ], - [ - 0, -0.1332568242984661109317426822417661370104, - 0.1332568242984661109317426822417661370104, - -0.264135680970344930533869538283309602979, - 0.264135680970344930533869538283309602979, - -0.390301038030290831421488872880605458578, - 0.390301038030290831421488872880605458578, - -0.5095014778460075496897930478668464305448, - 0.5095014778460075496897930478668464305448, - -0.6196098757636461563850973116495956533871, - 0.6196098757636461563850973116495956533871, - -0.7186613631319501944616244837486188483299, - 0.7186613631319501944616244837486188483299, - -0.8048884016188398921511184069967785579414, - 0.8048884016188398921511184069967785579414, - -0.8767523582704416673781568859341456716389, - 0.8767523582704416673781568859341456716389, - -0.9329710868260161023491969890384229782357, - 0.9329710868260161023491969890384229782357, - -0.9725424712181152319560240768207773751816, - 0.9725424712181152319560240768207773751816, - -0.9947693349975521235239257154455743605736, - 0.9947693349975521235239257154455743605736, - ], - [ - -0.0640568928626056260850430826247450385909, - 0.0640568928626056260850430826247450385909, - -0.1911188674736163091586398207570696318404, - 0.1911188674736163091586398207570696318404, - -0.3150426796961633743867932913198102407864, - 0.3150426796961633743867932913198102407864, - -0.4337935076260451384870842319133497124524, - 0.4337935076260451384870842319133497124524, - -0.5454214713888395356583756172183723700107, - 0.5454214713888395356583756172183723700107, - -0.6480936519369755692524957869107476266696, - 0.6480936519369755692524957869107476266696, - -0.7401241915785543642438281030999784255232, - 0.7401241915785543642438281030999784255232, - -0.8200019859739029219539498726697452080761, - 0.8200019859739029219539498726697452080761, - -0.8864155270044010342131543419821967550873, - 0.8864155270044010342131543419821967550873, - -0.9382745520027327585236490017087214496548, - 0.9382745520027327585236490017087214496548, - -0.9747285559713094981983919930081690617411, - 0.9747285559713094981983919930081690617411, - -0.9951872199970213601799974097007368118745, - 0.9951872199970213601799974097007368118745, - ], -]; - -// Legendre-Gauss weights (wi values, defined by a function linked to in the Bezier primer article) -export const cValues = [ - [], - [], - [1.0, 1.0], - [ - 0.8888888888888888888888888888888888888888, - 0.5555555555555555555555555555555555555555, - 0.5555555555555555555555555555555555555555, - ], - [ - 0.6521451548625461426269360507780005927646, - 0.6521451548625461426269360507780005927646, - 0.3478548451374538573730639492219994072353, - 0.3478548451374538573730639492219994072353, - ], - [ - 0.5688888888888888888888888888888888888888, - 0.4786286704993664680412915148356381929122, - 0.4786286704993664680412915148356381929122, - 0.2369268850561890875142640407199173626432, - 0.2369268850561890875142640407199173626432, - ], - [ - 0.3607615730481386075698335138377161116615, - 0.3607615730481386075698335138377161116615, - 0.4679139345726910473898703439895509948116, - 0.4679139345726910473898703439895509948116, - 0.1713244923791703450402961421727328935268, - 0.1713244923791703450402961421727328935268, - ], - [ - 0.4179591836734693877551020408163265306122, - 0.3818300505051189449503697754889751338783, - 0.3818300505051189449503697754889751338783, - 0.2797053914892766679014677714237795824869, - 0.2797053914892766679014677714237795824869, - 0.1294849661688696932706114326790820183285, - 0.1294849661688696932706114326790820183285, - ], - [ - 0.3626837833783619829651504492771956121941, - 0.3626837833783619829651504492771956121941, - 0.3137066458778872873379622019866013132603, - 0.3137066458778872873379622019866013132603, - 0.2223810344533744705443559944262408844301, - 0.2223810344533744705443559944262408844301, - 0.1012285362903762591525313543099621901153, - 0.1012285362903762591525313543099621901153, - ], - [ - 0.3302393550012597631645250692869740488788, - 0.1806481606948574040584720312429128095143, - 0.1806481606948574040584720312429128095143, - 0.0812743883615744119718921581105236506756, - 0.0812743883615744119718921581105236506756, - 0.3123470770400028400686304065844436655987, - 0.3123470770400028400686304065844436655987, - 0.2606106964029354623187428694186328497718, - 0.2606106964029354623187428694186328497718, - ], - [ - 0.295524224714752870173892994651338329421, - 0.295524224714752870173892994651338329421, - 0.2692667193099963550912269215694693528597, - 0.2692667193099963550912269215694693528597, - 0.2190863625159820439955349342281631924587, - 0.2190863625159820439955349342281631924587, - 0.1494513491505805931457763396576973324025, - 0.1494513491505805931457763396576973324025, - 0.0666713443086881375935688098933317928578, - 0.0666713443086881375935688098933317928578, - ], - [ - 0.272925086777900630714483528336342189156, - 0.2628045445102466621806888698905091953727, - 0.2628045445102466621806888698905091953727, - 0.2331937645919904799185237048431751394317, - 0.2331937645919904799185237048431751394317, - 0.1862902109277342514260976414316558916912, - 0.1862902109277342514260976414316558916912, - 0.1255803694649046246346942992239401001976, - 0.1255803694649046246346942992239401001976, - 0.0556685671161736664827537204425485787285, - 0.0556685671161736664827537204425485787285, - ], - [ - 0.2491470458134027850005624360429512108304, - 0.2491470458134027850005624360429512108304, - 0.2334925365383548087608498989248780562594, - 0.2334925365383548087608498989248780562594, - 0.2031674267230659217490644558097983765065, - 0.2031674267230659217490644558097983765065, - 0.160078328543346226334652529543359071872, - 0.160078328543346226334652529543359071872, - 0.1069393259953184309602547181939962242145, - 0.1069393259953184309602547181939962242145, - 0.047175336386511827194615961485017060317, - 0.047175336386511827194615961485017060317, - ], - [ - 0.2325515532308739101945895152688359481566, - 0.2262831802628972384120901860397766184347, - 0.2262831802628972384120901860397766184347, - 0.2078160475368885023125232193060527633865, - 0.2078160475368885023125232193060527633865, - 0.1781459807619457382800466919960979955128, - 0.1781459807619457382800466919960979955128, - 0.1388735102197872384636017768688714676218, - 0.1388735102197872384636017768688714676218, - 0.0921214998377284479144217759537971209236, - 0.0921214998377284479144217759537971209236, - 0.0404840047653158795200215922009860600419, - 0.0404840047653158795200215922009860600419, - ], - [ - 0.2152638534631577901958764433162600352749, - 0.2152638534631577901958764433162600352749, - 0.2051984637212956039659240656612180557103, - 0.2051984637212956039659240656612180557103, - 0.1855383974779378137417165901251570362489, - 0.1855383974779378137417165901251570362489, - 0.1572031671581935345696019386238421566056, - 0.1572031671581935345696019386238421566056, - 0.1215185706879031846894148090724766259566, - 0.1215185706879031846894148090724766259566, - 0.0801580871597602098056332770628543095836, - 0.0801580871597602098056332770628543095836, - 0.0351194603317518630318328761381917806197, - 0.0351194603317518630318328761381917806197, - ], - [ - 0.2025782419255612728806201999675193148386, - 0.1984314853271115764561183264438393248186, - 0.1984314853271115764561183264438393248186, - 0.1861610000155622110268005618664228245062, - 0.1861610000155622110268005618664228245062, - 0.1662692058169939335532008604812088111309, - 0.1662692058169939335532008604812088111309, - 0.1395706779261543144478047945110283225208, - 0.1395706779261543144478047945110283225208, - 0.1071592204671719350118695466858693034155, - 0.1071592204671719350118695466858693034155, - 0.0703660474881081247092674164506673384667, - 0.0703660474881081247092674164506673384667, - 0.0307532419961172683546283935772044177217, - 0.0307532419961172683546283935772044177217, - ], - [ - 0.1894506104550684962853967232082831051469, - 0.1894506104550684962853967232082831051469, - 0.1826034150449235888667636679692199393835, - 0.1826034150449235888667636679692199393835, - 0.1691565193950025381893120790303599622116, - 0.1691565193950025381893120790303599622116, - 0.1495959888165767320815017305474785489704, - 0.1495959888165767320815017305474785489704, - 0.1246289712555338720524762821920164201448, - 0.1246289712555338720524762821920164201448, - 0.0951585116824927848099251076022462263552, - 0.0951585116824927848099251076022462263552, - 0.0622535239386478928628438369943776942749, - 0.0622535239386478928628438369943776942749, - 0.0271524594117540948517805724560181035122, - 0.0271524594117540948517805724560181035122, - ], - [ - 0.1794464703562065254582656442618856214487, - 0.1765627053669926463252709901131972391509, - 0.1765627053669926463252709901131972391509, - 0.1680041021564500445099706637883231550211, - 0.1680041021564500445099706637883231550211, - 0.1540457610768102880814315948019586119404, - 0.1540457610768102880814315948019586119404, - 0.1351363684685254732863199817023501973721, - 0.1351363684685254732863199817023501973721, - 0.1118838471934039710947883856263559267358, - 0.1118838471934039710947883856263559267358, - 0.0850361483171791808835353701910620738504, - 0.0850361483171791808835353701910620738504, - 0.0554595293739872011294401653582446605128, - 0.0554595293739872011294401653582446605128, - 0.0241483028685479319601100262875653246916, - 0.0241483028685479319601100262875653246916, - ], - [ - 0.1691423829631435918406564701349866103341, - 0.1691423829631435918406564701349866103341, - 0.1642764837458327229860537764659275904123, - 0.1642764837458327229860537764659275904123, - 0.1546846751262652449254180038363747721932, - 0.1546846751262652449254180038363747721932, - 0.1406429146706506512047313037519472280955, - 0.1406429146706506512047313037519472280955, - 0.1225552067114784601845191268002015552281, - 0.1225552067114784601845191268002015552281, - 0.1009420441062871655628139849248346070628, - 0.1009420441062871655628139849248346070628, - 0.0764257302548890565291296776166365256053, - 0.0764257302548890565291296776166365256053, - 0.0497145488949697964533349462026386416808, - 0.0497145488949697964533349462026386416808, - 0.0216160135264833103133427102664524693876, - 0.0216160135264833103133427102664524693876, - ], - [ - 0.1610544498487836959791636253209167350399, - 0.1589688433939543476499564394650472016787, - 0.1589688433939543476499564394650472016787, - 0.152766042065859666778855400897662998461, - 0.152766042065859666778855400897662998461, - 0.1426067021736066117757461094419029724756, - 0.1426067021736066117757461094419029724756, - 0.1287539625393362276755157848568771170558, - 0.1287539625393362276755157848568771170558, - 0.1115666455473339947160239016817659974813, - 0.1115666455473339947160239016817659974813, - 0.0914900216224499994644620941238396526609, - 0.0914900216224499994644620941238396526609, - 0.0690445427376412265807082580060130449618, - 0.0690445427376412265807082580060130449618, - 0.0448142267656996003328381574019942119517, - 0.0448142267656996003328381574019942119517, - 0.0194617882297264770363120414644384357529, - 0.0194617882297264770363120414644384357529, - ], - [ - 0.1527533871307258506980843319550975934919, - 0.1527533871307258506980843319550975934919, - 0.1491729864726037467878287370019694366926, - 0.1491729864726037467878287370019694366926, - 0.1420961093183820513292983250671649330345, - 0.1420961093183820513292983250671649330345, - 0.1316886384491766268984944997481631349161, - 0.1316886384491766268984944997481631349161, - 0.118194531961518417312377377711382287005, - 0.118194531961518417312377377711382287005, - 0.1019301198172404350367501354803498761666, - 0.1019301198172404350367501354803498761666, - 0.0832767415767047487247581432220462061001, - 0.0832767415767047487247581432220462061001, - 0.0626720483341090635695065351870416063516, - 0.0626720483341090635695065351870416063516, - 0.040601429800386941331039952274932109879, - 0.040601429800386941331039952274932109879, - 0.0176140071391521183118619623518528163621, - 0.0176140071391521183118619623518528163621, - ], - [ - 0.1460811336496904271919851476833711882448, - 0.1445244039899700590638271665537525436099, - 0.1445244039899700590638271665537525436099, - 0.1398873947910731547221334238675831108927, - 0.1398873947910731547221334238675831108927, - 0.132268938633337461781052574496775604329, - 0.132268938633337461781052574496775604329, - 0.1218314160537285341953671771257335983563, - 0.1218314160537285341953671771257335983563, - 0.1087972991671483776634745780701056420336, - 0.1087972991671483776634745780701056420336, - 0.0934444234560338615532897411139320884835, - 0.0934444234560338615532897411139320884835, - 0.0761001136283793020170516533001831792261, - 0.0761001136283793020170516533001831792261, - 0.0571344254268572082836358264724479574912, - 0.0571344254268572082836358264724479574912, - 0.0369537897708524937999506682993296661889, - 0.0369537897708524937999506682993296661889, - 0.0160172282577743333242246168584710152658, - 0.0160172282577743333242246168584710152658, - ], - [ - 0.1392518728556319933754102483418099578739, - 0.1392518728556319933754102483418099578739, - 0.1365414983460151713525738312315173965863, - 0.1365414983460151713525738312315173965863, - 0.1311735047870623707329649925303074458757, - 0.1311735047870623707329649925303074458757, - 0.1232523768105124242855609861548144719594, - 0.1232523768105124242855609861548144719594, - 0.1129322960805392183934006074217843191142, - 0.1129322960805392183934006074217843191142, - 0.1004141444428809649320788378305362823508, - 0.1004141444428809649320788378305362823508, - 0.0859416062170677274144436813727028661891, - 0.0859416062170677274144436813727028661891, - 0.0697964684245204880949614189302176573987, - 0.0697964684245204880949614189302176573987, - 0.0522933351526832859403120512732112561121, - 0.0522933351526832859403120512732112561121, - 0.0337749015848141547933022468659129013491, - 0.0337749015848141547933022468659129013491, - 0.0146279952982722006849910980471854451902, - 0.0146279952982722006849910980471854451902, - ], - [ - 0.1336545721861061753514571105458443385831, - 0.132462039404696617371642464703316925805, - 0.132462039404696617371642464703316925805, - 0.1289057221880821499785953393997936532597, - 0.1289057221880821499785953393997936532597, - 0.1230490843067295304675784006720096548158, - 0.1230490843067295304675784006720096548158, - 0.1149966402224113649416435129339613014914, - 0.1149966402224113649416435129339613014914, - 0.1048920914645414100740861850147438548584, - 0.1048920914645414100740861850147438548584, - 0.0929157660600351474770186173697646486034, - 0.0929157660600351474770186173697646486034, - 0.0792814117767189549228925247420432269137, - 0.0792814117767189549228925247420432269137, - 0.0642324214085258521271696151589109980391, - 0.0642324214085258521271696151589109980391, - 0.0480376717310846685716410716320339965612, - 0.0480376717310846685716410716320339965612, - 0.0309880058569794443106942196418845053837, - 0.0309880058569794443106942196418845053837, - 0.0134118594871417720813094934586150649766, - 0.0134118594871417720813094934586150649766, - ], - [ - 0.1279381953467521569740561652246953718517, - 0.1279381953467521569740561652246953718517, - 0.1258374563468282961213753825111836887264, - 0.1258374563468282961213753825111836887264, - 0.121670472927803391204463153476262425607, - 0.121670472927803391204463153476262425607, - 0.1155056680537256013533444839067835598622, - 0.1155056680537256013533444839067835598622, - 0.1074442701159656347825773424466062227946, - 0.1074442701159656347825773424466062227946, - 0.0976186521041138882698806644642471544279, - 0.0976186521041138882698806644642471544279, - 0.086190161531953275917185202983742667185, - 0.086190161531953275917185202983742667185, - 0.0733464814110803057340336152531165181193, - 0.0733464814110803057340336152531165181193, - 0.0592985849154367807463677585001085845412, - 0.0592985849154367807463677585001085845412, - 0.0442774388174198061686027482113382288593, - 0.0442774388174198061686027482113382288593, - 0.0285313886289336631813078159518782864491, - 0.0285313886289336631813078159518782864491, - 0.0123412297999871995468056670700372915759, - 0.0123412297999871995468056670700372915759, - ], -]; - -// LUT for binomial coefficient arrays per curve order 'n' -export const binomialCoefficients = [[1], [1, 1], [1, 2, 1], [1, 3, 3, 1]]; - -export const getCubicArcLength = (xs: number[], ys: number[], t: number) => { - let sum: number; - let correctedT: number; - - /*if (xs.length >= tValues.length) { - throw new Error('too high n bezier'); - }*/ - - const n = 20; - - const z = t / 2; - sum = 0; - for (let i = 0; i < n; i++) { - correctedT = z * tValues[n][i] + z; - sum += cValues[n][i] * BFunc(xs, ys, correctedT); - } - return z * sum; -}; - -export const getQuadraticArcLength = ( - xs: number[], - ys: number[], - t: number -) => { - if (t === undefined) { - t = 1; - } - const ax = xs[0] - 2 * xs[1] + xs[2]; - const ay = ys[0] - 2 * ys[1] + ys[2]; - const bx = 2 * xs[1] - 2 * xs[0]; - const by = 2 * ys[1] - 2 * ys[0]; - - const A = 4 * (ax * ax + ay * ay); - const B = 4 * (ax * bx + ay * by); - const C = bx * bx + by * by; - - if (A === 0) { - return ( - t * Math.sqrt(Math.pow(xs[2] - xs[0], 2) + Math.pow(ys[2] - ys[0], 2)) - ); - } - const b = B / (2 * A); - const c = C / A; - const u = t + b; - const k = c - b * b; - - const uuk = u * u + k > 0 ? Math.sqrt(u * u + k) : 0; - const bbk = b * b + k > 0 ? Math.sqrt(b * b + k) : 0; - const term = - b + Math.sqrt(b * b + k) !== 0 - ? k * Math.log(Math.abs((u + uuk) / (b + bbk))) - : 0; - - return (Math.sqrt(A) / 2) * (u * uuk - b * bbk + term); -}; - -function BFunc(xs: number[], ys: number[], t: number) { - const xbase = getDerivative(1, t, xs); - const ybase = getDerivative(1, t, ys); - const combined = xbase * xbase + ybase * ybase; - return Math.sqrt(combined); -} - -/** - * Compute the curve derivative (hodograph) at t. - */ -const getDerivative = (derivative: number, t: number, vs: number[]): number => { - // the derivative of any 't'-less function is zero. - const n = vs.length - 1; - let _vs; - let value; - - if (n === 0) { - return 0; - } - - // direct values? compute! - if (derivative === 0) { - value = 0; - for (let k = 0; k <= n; k++) { - value += - binomialCoefficients[n][k] * - Math.pow(1 - t, n - k) * - Math.pow(t, k) * - vs[k]; - } - return value; - } else { - // Still some derivative? go down one order, then try - // for the lower order curve's. - _vs = new Array(n); - for (let k = 0; k < n; k++) { - _vs[k] = n * (vs[k + 1] - vs[k]); - } - return getDerivative(derivative - 1, t, _vs); - } -}; - -export const t2length = ( - length: number, - totalLength: number, - func: (t: number) => number -): number => { - let error = 1; - let t = length / totalLength; - let step = (length - func(t)) / totalLength; - - let numIterations = 0; - while (error > 0.001) { - const increasedTLength = func(t + step); - const increasedTError = Math.abs(length - increasedTLength) / totalLength; - if (increasedTError < error) { - error = increasedTError; - t += step; - } else { - const decreasedTLength = func(t - step); - const decreasedTError = Math.abs(length - decreasedTLength) / totalLength; - if (decreasedTError < error) { - error = decreasedTError; - t -= step; - } else { - step /= 2; - } - } - - numIterations++; - if (numIterations > 500) { - break; - } - } - - return t; -}; diff --git a/src/Canvas.ts b/src/Canvas.ts deleted file mode 100644 index eb122941d..000000000 --- a/src/Canvas.ts +++ /dev/null @@ -1,193 +0,0 @@ -import { Util } from './Util'; -import { SceneContext, HitContext, Context } from './Context'; -import { Konva } from './Global'; -import { Factory } from './Factory'; -import { getNumberValidator } from './Validators'; - -// calculate pixel ratio -let _pixelRatio; -function getDevicePixelRatio() { - if (_pixelRatio) { - return _pixelRatio; - } - const canvas = Util.createCanvasElement(); - const context = canvas.getContext('2d') as any; - _pixelRatio = (function () { - const devicePixelRatio = Konva._global.devicePixelRatio || 1, - backingStoreRatio = - context.webkitBackingStorePixelRatio || - context.mozBackingStorePixelRatio || - context.msBackingStorePixelRatio || - context.oBackingStorePixelRatio || - context.backingStorePixelRatio || - 1; - return devicePixelRatio / backingStoreRatio; - })(); - Util.releaseCanvas(canvas); - return _pixelRatio; -} - -interface ICanvasConfig { - width?: number; - height?: number; - pixelRatio?: number; - willReadFrequently?: boolean; -} - -/** - * Canvas Renderer constructor. It is a wrapper around native canvas element. - * Usually you don't need to use it manually. - * @constructor - * @abstract - * @memberof Konva - * @param {Object} config - * @param {Number} config.width - * @param {Number} config.height - * @param {Number} config.pixelRatio - */ -export class Canvas { - pixelRatio = 1; - _canvas: HTMLCanvasElement; - context: Context; - width = 0; - height = 0; - - isCache = false; - - constructor(config: ICanvasConfig) { - const conf = config || {}; - - const pixelRatio = - conf.pixelRatio || Konva.pixelRatio || getDevicePixelRatio(); - - this.pixelRatio = pixelRatio; - - this._canvas = Util.createCanvasElement(); - // set inline styles - this._canvas.style.padding = '0'; - this._canvas.style.margin = '0'; - this._canvas.style.border = '0'; - this._canvas.style.background = 'transparent'; - this._canvas.style.position = 'absolute'; - this._canvas.style.top = '0'; - this._canvas.style.left = '0'; - } - - /** - * get canvas context - * @method - * @name Konva.Canvas#getContext - * @returns {CanvasContext} context - */ - getContext() { - return this.context; - } - getPixelRatio() { - return this.pixelRatio; - } - setPixelRatio(pixelRatio) { - const previousRatio = this.pixelRatio; - this.pixelRatio = pixelRatio; - this.setSize( - this.getWidth() / previousRatio, - this.getHeight() / previousRatio - ); - } - setWidth(width) { - // take into account pixel ratio - this.width = this._canvas.width = width * this.pixelRatio; - this._canvas.style.width = width + 'px'; - - const pixelRatio = this.pixelRatio, - _context = this.getContext()._context; - _context.scale(pixelRatio, pixelRatio); - } - setHeight(height) { - // take into account pixel ratio - this.height = this._canvas.height = height * this.pixelRatio; - this._canvas.style.height = height + 'px'; - const pixelRatio = this.pixelRatio, - _context = this.getContext()._context; - _context.scale(pixelRatio, pixelRatio); - } - getWidth() { - return this.width; - } - getHeight() { - return this.height; - } - setSize(width, height) { - this.setWidth(width || 0); - this.setHeight(height || 0); - } - /** - * to data url - * @method - * @name Konva.Canvas#toDataURL - * @param {String} mimeType - * @param {Number} quality between 0 and 1 for jpg mime types - * @returns {String} data url string - */ - toDataURL(mimeType, quality) { - try { - // If this call fails (due to browser bug, like in Firefox 3.6), - // then revert to previous no-parameter image/png behavior - return this._canvas.toDataURL(mimeType, quality); - } catch (e) { - try { - return this._canvas.toDataURL(); - } catch (err: any) { - Util.error( - 'Unable to get data URL. ' + - err.message + - ' For more info read https://konvajs.org/docs/posts/Tainted_Canvas.html.' - ); - return ''; - } - } - } -} - -/** - * get/set pixel ratio. - * KonvaJS automatically handles pixel ratio adustments in order to render crisp drawings - * on all devices. Most desktops, low end tablets, and low end phones, have device pixel ratios - * of 1. Some high end tablets and phones, like iPhones and iPads have a device pixel ratio - * of 2. Some Macbook Pros, and iMacs also have a device pixel ratio of 2. Some high end Android devices have pixel - * ratios of 2 or 3. Some browsers like Firefox allow you to configure the pixel ratio of the viewport. Unless otherwise - * specificed, the pixel ratio will be defaulted to the actual device pixel ratio. You can override the device pixel - * ratio for special situations, or, if you don't want the pixel ratio to be taken into account, you can set it to 1. - * @name Konva.Canvas#pixelRatio - * @method - * @param {Number} pixelRatio - * @returns {Number} - * @example - * // get - * var pixelRatio = layer.getCanvas.pixelRatio(); - * - * // set - * layer.getCanvas().pixelRatio(3); - */ -Factory.addGetterSetter(Canvas, 'pixelRatio', undefined, getNumberValidator()); - -export class SceneCanvas extends Canvas { - constructor( - config: ICanvasConfig = { width: 0, height: 0, willReadFrequently: false } - ) { - super(config); - this.context = new SceneContext(this, { - willReadFrequently: config.willReadFrequently, - }); - this.setSize(config.width, config.height); - } -} - -export class HitCanvas extends Canvas { - hitCanvas = true; - constructor(config: ICanvasConfig = { width: 0, height: 0 }) { - super(config); - - this.context = new HitContext(this); - this.setSize(config.width, config.height); - } -} diff --git a/src/Container.ts b/src/Container.ts deleted file mode 100644 index 73d77a0b3..000000000 --- a/src/Container.ts +++ /dev/null @@ -1,658 +0,0 @@ -import { Factory } from './Factory'; -import { Node, NodeConfig } from './Node'; -import { getNumberValidator } from './Validators'; - -import { GetSet, IRect } from './types'; -import { Shape } from './Shape'; -import { HitCanvas, SceneCanvas } from './Canvas'; -import { SceneContext } from './Context'; - -export type ClipFuncOutput = - | void - | [Path2D | CanvasFillRule] - | [Path2D, CanvasFillRule]; -export interface ContainerConfig extends NodeConfig { - clearBeforeDraw?: boolean; - clipFunc?: (ctx: SceneContext) => ClipFuncOutput; - clipX?: number; - clipY?: number; - clipWidth?: number; - clipHeight?: number; -} - -/** - * Container constructor.  Containers are used to contain nodes or other containers - * @constructor - * @memberof Konva - * @augments Konva.Node - * @abstract - * @param {Object} config - * @@nodeParams - * @@containerParams - */ -export abstract class Container< - ChildType extends Node = Node -> extends Node { - children: Array = []; - - /** - * returns an array of direct descendant nodes - * @method - * @name Konva.Container#getChildren - * @param {Function} [filterFunc] filter function - * @returns {Array} - * @example - * // get all children - * var children = layer.getChildren(); - * - * // get only circles - * var circles = layer.getChildren(function(node){ - * return node.getClassName() === 'Circle'; - * }); - */ - getChildren(filterFunc?: (item: Node) => boolean) { - if (!filterFunc) { - return this.children || []; - } - - const children = this.children || []; - const results: Array = []; - children.forEach(function (child) { - if (filterFunc(child)) { - results.push(child); - } - }); - return results; - } - /** - * determine if node has children - * @method - * @name Konva.Container#hasChildren - * @returns {Boolean} - */ - hasChildren() { - return this.getChildren().length > 0; - } - /** - * remove all children. Children will be still in memory. - * If you want to completely destroy all children please use "destroyChildren" method instead - * @method - * @name Konva.Container#removeChildren - */ - removeChildren() { - this.getChildren().forEach((child) => { - // reset parent to prevent many _setChildrenIndices calls - child.parent = null; - child.index = 0; - child.remove(); - }); - this.children = []; - // because all children were detached from parent, request draw via container - this._requestDraw(); - return this; - } - /** - * destroy all children nodes. - * @method - * @name Konva.Container#destroyChildren - */ - destroyChildren() { - this.getChildren().forEach((child) => { - // reset parent to prevent many _setChildrenIndices calls - child.parent = null; - child.index = 0; - child.destroy(); - }); - this.children = []; - // because all children were detached from parent, request draw via container - this._requestDraw(); - return this; - } - abstract _validateAdd(node: Node): void; - /** - * add a child and children into container - * @name Konva.Container#add - * @method - * @param {...Konva.Node} children - * @returns {Container} - * @example - * layer.add(rect); - * layer.add(shape1, shape2, shape3); - * // empty arrays are accepted, though each individual child must be defined - * layer.add(...shapes); - * // remember to redraw layer if you changed something - * layer.draw(); - */ - add(...children: ChildType[]) { - if (children.length === 0) { - return this; - } - if (children.length > 1) { - for (let i = 0; i < children.length; i++) { - this.add(children[i]); - } - return this; - } - const child = children[0]; - if (child.getParent()) { - child.moveTo(this); - return this; - } - this._validateAdd(child); - child.index = this.getChildren().length; - child.parent = this; - child._clearCaches(); - this.getChildren().push(child); - this._fire('add', { - child: child, - }); - this._requestDraw(); - // chainable - return this; - } - destroy() { - if (this.hasChildren()) { - this.destroyChildren(); - } - super.destroy(); - return this; - } - /** - * return an array of nodes that match the selector. - * You can provide a string with '#' for id selections and '.' for name selections. - * Or a function that will return true/false when a node is passed through. See example below. - * With strings you can also select by type or class name. Pass multiple selectors - * separated by a comma. - * @method - * @name Konva.Container#find - * @param {String | Function} selector - * @returns {Array} - * @example - * - * Passing a string as a selector - * // select node with id foo - * var node = stage.find('#foo'); - * - * // select nodes with name bar inside layer - * var nodes = layer.find('.bar'); - * - * // select all groups inside layer - * var nodes = layer.find('Group'); - * - * // select all rectangles inside layer - * var nodes = layer.find('Rect'); - * - * // select node with an id of foo or a name of bar inside layer - * var nodes = layer.find('#foo, .bar'); - * - * Passing a function as a selector - * - * // get all groups with a function - * var groups = stage.find(node => { - * return node.getType() === 'Group'; - * }); - * - * // get only Nodes with partial opacity - * var alphaNodes = layer.find(node => { - * return node.getType() === 'Node' && node.getAbsoluteOpacity() < 1; - * }); - */ - find(selector): Array { - // protecting _generalFind to prevent user from accidentally adding - // second argument and getting unexpected `findOne` result - return this._generalFind(selector, false); - } - /** - * return a first node from `find` method - * @method - * @name Konva.Container#findOne - * @param {String | Function} selector - * @returns {Konva.Node | Undefined} - * @example - * // select node with id foo - * var node = stage.findOne('#foo'); - * - * // select node with name bar inside layer - * var nodes = layer.findOne('.bar'); - * - * // select the first node to return true in a function - * var node = stage.findOne(node => { - * return node.getType() === 'Shape' - * }) - */ - findOne( - selector: string | Function - ): ChildNode | undefined { - const result = this._generalFind(selector, true); - return result.length > 0 ? result[0] : undefined; - } - _generalFind( - selector: string | Function, - findOne: boolean - ) { - const retArr: Array = []; - - this._descendants((node) => { - const valid = node._isMatch(selector); - if (valid) { - retArr.push(node as unknown as ChildNode); - } - if (valid && findOne) { - return true; - } - return false; - }); - - return retArr; - } - private _descendants(fn: (n: Node) => boolean) { - let shouldStop = false; - const children = this.getChildren(); - for (const child of children) { - shouldStop = fn(child); - if (shouldStop) { - return true; - } - if (!child.hasChildren()) { - continue; - } - shouldStop = (child as unknown as Container)._descendants(fn); - if (shouldStop) { - return true; - } - } - return false; - } - // extenders - toObject() { - const obj = Node.prototype.toObject.call(this); - - obj.children = []; - - this.getChildren().forEach((child) => { - obj.children!.push(child.toObject()); - }); - - return obj; - } - /** - * determine if node is an ancestor - * of descendant - * @method - * @name Konva.Container#isAncestorOf - * @param {Konva.Node} node - */ - isAncestorOf(node: Node) { - let parent = node.getParent(); - while (parent) { - if (parent._id === this._id) { - return true; - } - parent = parent.getParent(); - } - - return false; - } - clone(obj?: any) { - // call super method - const node = Node.prototype.clone.call(this, obj); - - this.getChildren().forEach(function (no) { - node.add(no.clone()); - }); - return node as this; - } - /** - * get all shapes that intersect a point. Note: because this method must clear a temporary - * canvas and redraw every shape inside the container, it should only be used for special situations - * because it performs very poorly. Please use the {@link Konva.Stage#getIntersection} method if at all possible - * because it performs much better - * nodes with listening set to false will not be detected - * @method - * @name Konva.Container#getAllIntersections - * @param {Object} pos - * @param {Number} pos.x - * @param {Number} pos.y - * @returns {Array} array of shapes - */ - getAllIntersections(pos) { - const arr: Shape[] = []; - - this.find('Shape').forEach((shape) => { - if (shape.isVisible() && shape.intersects(pos)) { - arr.push(shape); - } - }); - - return arr; - } - _clearSelfAndDescendantCache(attr?: string) { - super._clearSelfAndDescendantCache(attr); - // skip clearing if node is cached with canvas - // for performance reasons !!! - if (this.isCached()) { - return; - } - this.children?.forEach(function (node) { - node._clearSelfAndDescendantCache(attr); - }); - } - _setChildrenIndices() { - this.children?.forEach(function (child, n) { - child.index = n; - }); - this._requestDraw(); - } - drawScene(can?: SceneCanvas, top?: Node, bufferCanvas?: SceneCanvas) { - const layer = this.getLayer()!, - canvas = can || (layer && layer.getCanvas()), - context = canvas && canvas.getContext(), - cachedCanvas = this._getCanvasCache(), - cachedSceneCanvas = cachedCanvas && cachedCanvas.scene; - - const caching = canvas && canvas.isCache; - if (!this.isVisible() && !caching) { - return this; - } - - if (cachedSceneCanvas) { - context.save(); - const m = this.getAbsoluteTransform(top).getMatrix(); - context.transform(m[0], m[1], m[2], m[3], m[4], m[5]); - this._drawCachedSceneCanvas(context); - context.restore(); - } else { - this._drawChildren('drawScene', canvas, top, bufferCanvas); - } - return this; - } - drawHit(can?: HitCanvas, top?: Node) { - if (!this.shouldDrawHit(top)) { - return this; - } - - const layer = this.getLayer()!, - canvas = can || (layer && layer.hitCanvas), - context = canvas && canvas.getContext(), - cachedCanvas = this._getCanvasCache(), - cachedHitCanvas = cachedCanvas && cachedCanvas.hit; - - if (cachedHitCanvas) { - context.save(); - const m = this.getAbsoluteTransform(top).getMatrix(); - context.transform(m[0], m[1], m[2], m[3], m[4], m[5]); - this._drawCachedHitCanvas(context); - context.restore(); - } else { - this._drawChildren('drawHit', canvas, top); - } - return this; - } - _drawChildren(drawMethod, canvas, top, bufferCanvas?) { - const context = canvas && canvas.getContext(), - clipWidth = this.clipWidth(), - clipHeight = this.clipHeight(), - clipFunc = this.clipFunc(), - hasClip = - (typeof clipWidth === 'number' && typeof clipHeight === 'number') || - clipFunc; - - const selfCache = top === this; - - if (hasClip) { - context.save(); - const transform = this.getAbsoluteTransform(top); - let m = transform.getMatrix(); - context.transform(m[0], m[1], m[2], m[3], m[4], m[5]); - context.beginPath(); - let clipArgs; - if (clipFunc) { - clipArgs = clipFunc.call(this, context, this); - } else { - const clipX = this.clipX(); - const clipY = this.clipY(); - context.rect(clipX || 0, clipY || 0, clipWidth, clipHeight); - } - context.clip.apply(context, clipArgs); - m = transform.copy().invert().getMatrix(); - context.transform(m[0], m[1], m[2], m[3], m[4], m[5]); - } - - const hasComposition = - !selfCache && - this.globalCompositeOperation() !== 'source-over' && - drawMethod === 'drawScene'; - - if (hasComposition) { - context.save(); - context._applyGlobalCompositeOperation(this); - } - - this.children?.forEach(function (child) { - child[drawMethod](canvas, top, bufferCanvas); - }); - if (hasComposition) { - context.restore(); - } - - if (hasClip) { - context.restore(); - } - } - - getClientRect( - config: { - skipTransform?: boolean; - skipShadow?: boolean; - skipStroke?: boolean; - relativeTo?: Container; - } = {} - ): IRect { - const skipTransform = config.skipTransform; - const relativeTo = config.relativeTo; - - let minX, minY, maxX, maxY; - let selfRect = { - x: Infinity, - y: Infinity, - width: 0, - height: 0, - }; - const that = this; - this.children?.forEach(function (child) { - // skip invisible children - if (!child.visible()) { - return; - } - - const rect = child.getClientRect({ - relativeTo: that, - skipShadow: config.skipShadow, - skipStroke: config.skipStroke, - }); - - // skip invisible children (like empty groups) - if (rect.width === 0 && rect.height === 0) { - return; - } - - if (minX === undefined) { - // initial value for first child - minX = rect.x; - minY = rect.y; - maxX = rect.x + rect.width; - maxY = rect.y + rect.height; - } else { - minX = Math.min(minX, rect.x); - minY = Math.min(minY, rect.y); - maxX = Math.max(maxX, rect.x + rect.width); - maxY = Math.max(maxY, rect.y + rect.height); - } - }); - - // if child is group we need to make sure it has visible shapes inside - const shapes = this.find('Shape'); - let hasVisible = false; - for (let i = 0; i < shapes.length; i++) { - const shape = shapes[i]; - if (shape._isVisible(this)) { - hasVisible = true; - break; - } - } - if (hasVisible && minX !== undefined) { - selfRect = { - x: minX, - y: minY, - width: maxX - minX, - height: maxY - minY, - }; - } else { - selfRect = { - x: 0, - y: 0, - width: 0, - height: 0, - }; - } - - if (!skipTransform) { - return this._transformedRect(selfRect, relativeTo); - } - return selfRect; - } - - clip: GetSet; - clipX: GetSet; - clipY: GetSet; - clipWidth: GetSet; - clipHeight: GetSet; - // there was "this" instead of "Container", - // but it breaks react-konva types: https://github.com/konvajs/react-konva/issues/390 - clipFunc: GetSet< - (ctx: CanvasRenderingContext2D, shape: Container) => ClipFuncOutput, - this - >; -} - -// add getters setters -Factory.addComponentsGetterSetter(Container, 'clip', [ - 'x', - 'y', - 'width', - 'height', -]); -/** - * get/set clip - * @method - * @name Konva.Container#clip - * @param {Object} clip - * @param {Number} clip.x - * @param {Number} clip.y - * @param {Number} clip.width - * @param {Number} clip.height - * @returns {Object} - * @example - * // get clip - * var clip = container.clip(); - * - * // set clip - * container.clip({ - * x: 20, - * y: 20, - * width: 20, - * height: 20 - * }); - */ - -Factory.addGetterSetter(Container, 'clipX', undefined, getNumberValidator()); -/** - * get/set clip x - * @name Konva.Container#clipX - * @method - * @param {Number} x - * @returns {Number} - * @example - * // get clip x - * var clipX = container.clipX(); - * - * // set clip x - * container.clipX(10); - */ - -Factory.addGetterSetter(Container, 'clipY', undefined, getNumberValidator()); -/** - * get/set clip y - * @name Konva.Container#clipY - * @method - * @param {Number} y - * @returns {Number} - * @example - * // get clip y - * var clipY = container.clipY(); - * - * // set clip y - * container.clipY(10); - */ - -Factory.addGetterSetter( - Container, - 'clipWidth', - undefined, - getNumberValidator() -); -/** - * get/set clip width - * @name Konva.Container#clipWidth - * @method - * @param {Number} width - * @returns {Number} - * @example - * // get clip width - * var clipWidth = container.clipWidth(); - * - * // set clip width - * container.clipWidth(100); - */ - -Factory.addGetterSetter( - Container, - 'clipHeight', - undefined, - getNumberValidator() -); -/** - * get/set clip height - * @name Konva.Container#clipHeight - * @method - * @param {Number} height - * @returns {Number} - * @example - * // get clip height - * var clipHeight = container.clipHeight(); - * - * // set clip height - * container.clipHeight(100); - */ - -Factory.addGetterSetter(Container, 'clipFunc'); -/** - * get/set clip function - * @name Konva.Container#clipFunc - * @method - * @param {Function} function - * @returns {Function} - * @example - * // get clip function - * var clipFunction = container.clipFunc(); - * - * // set clip function - * container.clipFunc(function(ctx) { - * ctx.rect(0, 0, 100, 100); - * }); - * - * container.clipFunc(function(ctx) { - * // optionally return a clip Path2D and clip-rule or just the clip-rule - * return [new Path2D('M0 0v50h50Z'), 'evenodd'] - * }); - */ diff --git a/src/Context.ts b/src/Context.ts deleted file mode 100644 index aeac2e07b..000000000 --- a/src/Context.ts +++ /dev/null @@ -1,989 +0,0 @@ -import { Util } from './Util'; -import { Konva } from './Global'; -import { Canvas } from './Canvas'; -import { Shape } from './Shape'; -import { IRect } from './types'; -import type { Node } from './Node'; - -function simplifyArray(arr: Array) { - const retArr: Array = [], - len = arr.length, - util = Util; - - for (let n = 0; n < len; n++) { - let val = arr[n]; - if (util._isNumber(val)) { - val = Math.round(val * 1000) / 1000; - } else if (!util._isString(val)) { - val = val + ''; - } - - retArr.push(val); - } - - return retArr; -} - -const COMMA = ',', - OPEN_PAREN = '(', - CLOSE_PAREN = ')', - OPEN_PAREN_BRACKET = '([', - CLOSE_BRACKET_PAREN = '])', - SEMICOLON = ';', - DOUBLE_PAREN = '()', - // EMPTY_STRING = '', - EQUALS = '=', - // SET = 'set', - CONTEXT_METHODS = [ - 'arc', - 'arcTo', - 'beginPath', - 'bezierCurveTo', - 'clearRect', - 'clip', - 'closePath', - 'createLinearGradient', - 'createPattern', - 'createRadialGradient', - 'drawImage', - 'ellipse', - 'fill', - 'fillText', - 'getImageData', - 'createImageData', - 'lineTo', - 'moveTo', - 'putImageData', - 'quadraticCurveTo', - 'rect', - 'roundRect', - 'restore', - 'rotate', - 'save', - 'scale', - 'setLineDash', - 'setTransform', - 'stroke', - 'strokeText', - 'transform', - 'translate', - ]; - -const CONTEXT_PROPERTIES = [ - 'fillStyle', - 'strokeStyle', - 'shadowColor', - 'shadowBlur', - 'shadowOffsetX', - 'shadowOffsetY', - 'letterSpacing', - 'lineCap', - 'lineDashOffset', - 'lineJoin', - 'lineWidth', - 'miterLimit', - 'direction', - 'font', - 'textAlign', - 'textBaseline', - 'globalAlpha', - 'globalCompositeOperation', - 'imageSmoothingEnabled', -] as const; - -const traceArrMax = 100; - -interface ExtendedCanvasRenderingContext2D extends CanvasRenderingContext2D { - letterSpacing: string; -} - -/** - * Konva wrapper around native 2d canvas context. It has almost the same API of 2d context with some additional functions. - * With core Konva shapes you don't need to use this object. But you will use it if you want to create - * a [custom shape](/docs/react/Custom_Shape.html) or a [custom hit regions](/docs/events/Custom_Hit_Region.html). - * For full information about each 2d context API use [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D) - * @constructor - * @memberof Konva - * @example - * const rect = new Konva.Shape({ - * fill: 'red', - * width: 100, - * height: 100, - * sceneFunc: (ctx, shape) => { - * // ctx - is context wrapper - * // shape - is instance of Konva.Shape, so it equals to "rect" variable - * ctx.rect(0, 0, shape.getAttr('width'), shape.getAttr('height')); - * - * // automatically fill shape from props and draw hit region - * ctx.fillStrokeShape(shape); - * } - * }) - */ -export class Context { - canvas: Canvas; - _context: CanvasRenderingContext2D; - traceArr: Array; - - constructor(canvas: Canvas) { - this.canvas = canvas; - - if (Konva.enableTrace) { - this.traceArr = []; - this._enableTrace(); - } - } - - /** - * fill shape - * @method - * @name Konva.Context#fillShape - * @param {Konva.Shape} shape - */ - fillShape(shape: Shape) { - if (shape.fillEnabled()) { - this._fill(shape); - } - } - - _fill(shape: Shape) { - // abstract - } - /** - * stroke shape - * @method - * @name Konva.Context#strokeShape - * @param {Konva.Shape} shape - */ - strokeShape(shape: Shape) { - if (shape.hasStroke()) { - this._stroke(shape); - } - } - - _stroke(shape: Shape) { - // abstract - } - - /** - * fill then stroke - * @method - * @name Konva.Context#fillStrokeShape - * @param {Konva.Shape} shape - */ - fillStrokeShape(shape: Shape) { - if (shape.attrs.fillAfterStrokeEnabled) { - this.strokeShape(shape); - this.fillShape(shape); - } else { - this.fillShape(shape); - this.strokeShape(shape); - } - } - - getTrace(relaxed?: boolean, rounded?: boolean) { - let traceArr = this.traceArr, - len = traceArr.length, - str = '', - n, - trace, - method, - args; - - for (n = 0; n < len; n++) { - trace = traceArr[n]; - method = trace.method; - - // methods - if (method) { - args = trace.args; - str += method; - if (relaxed) { - str += DOUBLE_PAREN; - } else { - if (Util._isArray(args[0])) { - str += OPEN_PAREN_BRACKET + args.join(COMMA) + CLOSE_BRACKET_PAREN; - } else { - if (rounded) { - args = args.map((a) => - typeof a === 'number' ? Math.floor(a) : a - ); - } - str += OPEN_PAREN + args.join(COMMA) + CLOSE_PAREN; - } - } - } else { - // properties - str += trace.property; - if (!relaxed) { - str += EQUALS + trace.val; - } - } - - str += SEMICOLON; - } - - return str; - } - - clearTrace() { - this.traceArr = []; - } - _trace(str) { - let traceArr = this.traceArr, - len; - - traceArr.push(str); - len = traceArr.length; - - if (len >= traceArrMax) { - traceArr.shift(); - } - } - /** - * reset canvas context transform - * @method - * @name Konva.Context#reset - */ - reset() { - const pixelRatio = this.getCanvas().getPixelRatio(); - this.setTransform(1 * pixelRatio, 0, 0, 1 * pixelRatio, 0, 0); - } - /** - * get canvas wrapper - * @method - * @name Konva.Context#getCanvas - * @returns {Konva.Canvas} - */ - getCanvas() { - return this.canvas; - } - /** - * clear canvas - * @method - * @name Konva.Context#clear - * @param {Object} [bounds] - * @param {Number} [bounds.x] - * @param {Number} [bounds.y] - * @param {Number} [bounds.width] - * @param {Number} [bounds.height] - */ - clear(bounds?: IRect) { - const canvas = this.getCanvas(); - - if (bounds) { - this.clearRect( - bounds.x || 0, - bounds.y || 0, - bounds.width || 0, - bounds.height || 0 - ); - } else { - this.clearRect( - 0, - 0, - canvas.getWidth() / canvas.pixelRatio, - canvas.getHeight() / canvas.pixelRatio - ); - } - } - _applyLineCap(shape: Shape) { - const lineCap = shape.attrs.lineCap; - if (lineCap) { - this.setAttr('lineCap', lineCap); - } - } - _applyOpacity(shape: Node) { - const absOpacity = shape.getAbsoluteOpacity(); - if (absOpacity !== 1) { - this.setAttr('globalAlpha', absOpacity); - } - } - _applyLineJoin(shape: Shape) { - const lineJoin = shape.attrs.lineJoin; - if (lineJoin) { - this.setAttr('lineJoin', lineJoin); - } - } - - setAttr(attr: string, val) { - this._context[attr] = val; - } - - /** - * arc function. - * @method - * @name Konva.Context#arc - */ - arc( - x: number, - y: number, - radius: number, - startAngle: number, - endAngle: number, - counterClockwise?: boolean - ) { - this._context.arc(x, y, radius, startAngle, endAngle, counterClockwise); - } - /** - * arcTo function. - * @method - * @name Konva.Context#arcTo - * - */ - arcTo(x1: number, y1: number, x2: number, y2: number, radius: number) { - this._context.arcTo(x1, y1, x2, y2, radius); - } - /** - * beginPath function. - * @method - * @name Konva.Context#beginPath - */ - beginPath() { - this._context.beginPath(); - } - /** - * bezierCurveTo function. - * @method - * @name Konva.Context#bezierCurveTo - */ - - bezierCurveTo( - cp1x: number, - cp1y: number, - cp2x: number, - cp2y: number, - x: number, - y: number - ) { - this._context.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y); - } - /** - * clearRect function. - * @method - * @name Konva.Context#clearRect - */ - clearRect(x: number, y: number, width: number, height: number) { - this._context.clearRect(x, y, width, height); - } - /** - * clip function. - * @method - * @name Konva.Context#clip - */ - clip(fillRule?: CanvasFillRule): void; - clip(path: Path2D, fillRule?: CanvasFillRule): void; - clip(...args: any[]) { - this._context.clip.apply(this._context, args as any); - } - /** - * closePath function. - * @method - * @name Konva.Context#closePath - */ - closePath() { - this._context.closePath(); - } - /** - * createImageData function. - * @method - * @name Konva.Context#createImageData - */ - createImageData(width, height) { - const a = arguments; - if (a.length === 2) { - return this._context.createImageData(width, height); - } else if (a.length === 1) { - return this._context.createImageData(width); - } - } - /** - * createLinearGradient function. - * @method - * @name Konva.Context#createLinearGradient - */ - createLinearGradient(x0: number, y0: number, x1: number, y1: number) { - return this._context.createLinearGradient(x0, y0, x1, y1); - } - /** - * createPattern function. - * @method - * @name Konva.Context#createPattern - */ - createPattern(image: CanvasImageSource, repetition: string | null) { - return this._context.createPattern(image, repetition); - } - /** - * createRadialGradient function. - * @method - * @name Konva.Context#createRadialGradient - */ - createRadialGradient( - x0: number, - y0: number, - r0: number, - x1: number, - y1: number, - r1: number - ) { - return this._context.createRadialGradient(x0, y0, r0, x1, y1, r1); - } - /** - * drawImage function. - * @method - * @name Konva.Context#drawImage - */ - drawImage( - image: CanvasImageSource, - sx: number, - sy: number, - sWidth?: number, - sHeight?: number, - dx?: number, - dy?: number, - dWidth?: number, - dHeight?: number - ) { - // this._context.drawImage(...arguments); - const a = arguments, - _context = this._context; - if (a.length === 3) { - _context.drawImage(image, sx, sy); - } else if (a.length === 5) { - _context.drawImage(image, sx, sy, sWidth as number, sHeight as number); - } else if (a.length === 9) { - _context.drawImage( - image, - sx, - sy, - sWidth as number, - sHeight as number, - dx as number, - dy as number, - dWidth as number, - dHeight as number - ); - } - } - /** - * ellipse function. - * @method - * @name Konva.Context#ellipse - */ - ellipse( - x: number, - y: number, - radiusX: number, - radiusY: number, - rotation: number, - startAngle: number, - endAngle: number, - counterclockwise?: boolean - ) { - this._context.ellipse( - x, - y, - radiusX, - radiusY, - rotation, - startAngle, - endAngle, - counterclockwise - ); - } - /** - * isPointInPath function. - * @method - * @name Konva.Context#isPointInPath - */ - isPointInPath( - x: number, - y: number, - path?: Path2D, - fillRule?: CanvasFillRule - ) { - if (path) { - return this._context.isPointInPath(path, x, y, fillRule); - } - return this._context.isPointInPath(x, y, fillRule); - } - /** - * fill function. - * @method - * @name Konva.Context#fill - */ - fill(fillRule?: CanvasFillRule): void; - fill(path: Path2D, fillRule?: CanvasFillRule): void; - fill(...args: any[]) { - // this._context.fill(); - this._context.fill.apply(this._context, args as any); - } - /** - * fillRect function. - * @method - * @name Konva.Context#fillRect - */ - fillRect(x: number, y: number, width: number, height: number) { - this._context.fillRect(x, y, width, height); - } - /** - * strokeRect function. - * @method - * @name Konva.Context#strokeRect - */ - strokeRect(x: number, y: number, width: number, height: number) { - this._context.strokeRect(x, y, width, height); - } - /** - * fillText function. - * @method - * @name Konva.Context#fillText - */ - fillText(text: string, x: number, y: number, maxWidth?: number) { - if (maxWidth) { - this._context.fillText(text, x, y, maxWidth); - } else { - this._context.fillText(text, x, y); - } - } - /** - * measureText function. - * @method - * @name Konva.Context#measureText - */ - measureText(text: string) { - return this._context.measureText(text); - } - /** - * getImageData function. - * @method - * @name Konva.Context#getImageData - */ - getImageData(sx: number, sy: number, sw: number, sh: number) { - return this._context.getImageData(sx, sy, sw, sh); - } - /** - * lineTo function. - * @method - * @name Konva.Context#lineTo - */ - lineTo(x: number, y: number) { - this._context.lineTo(x, y); - } - /** - * moveTo function. - * @method - * @name Konva.Context#moveTo - */ - moveTo(x: number, y: number) { - this._context.moveTo(x, y); - } - /** - * rect function. - * @method - * @name Konva.Context#rect - */ - rect(x: number, y: number, width: number, height: number) { - this._context.rect(x, y, width, height); - } - /** - * roundRect function. - * @method - * @name Konva.Context#roundRect - */ - roundRect( - x: number, - y: number, - width: number, - height: number, - radii: number | DOMPointInit | (number | DOMPointInit)[] - ) { - this._context.roundRect(x, y, width, height, radii); - } - /** - * putImageData function. - * @method - * @name Konva.Context#putImageData - */ - putImageData(imageData: ImageData, dx: number, dy: number) { - this._context.putImageData(imageData, dx, dy); - } - /** - * quadraticCurveTo function. - * @method - * @name Konva.Context#quadraticCurveTo - */ - quadraticCurveTo(cpx: number, cpy: number, x: number, y: number) { - this._context.quadraticCurveTo(cpx, cpy, x, y); - } - /** - * restore function. - * @method - * @name Konva.Context#restore - */ - restore() { - this._context.restore(); - } - /** - * rotate function. - * @method - * @name Konva.Context#rotate - */ - rotate(angle: number) { - this._context.rotate(angle); - } - /** - * save function. - * @method - * @name Konva.Context#save - */ - save() { - this._context.save(); - } - /** - * scale function. - * @method - * @name Konva.Context#scale - */ - scale(x: number, y: number) { - this._context.scale(x, y); - } - /** - * setLineDash function. - * @method - * @name Konva.Context#setLineDash - */ - setLineDash(segments: number[]) { - // works for Chrome and IE11 - if (this._context.setLineDash) { - this._context.setLineDash(segments); - } else if ('mozDash' in this._context) { - // verified that this works in firefox - (this._context['mozDash']) = segments; - } else if ('webkitLineDash' in this._context) { - // does not currently work for Safari - (this._context['webkitLineDash']) = segments; - } - - // no support for IE9 and IE10 - } - /** - * getLineDash function. - * @method - * @name Konva.Context#getLineDash - */ - getLineDash() { - return this._context.getLineDash(); - } - /** - * setTransform function. - * @method - * @name Konva.Context#setTransform - */ - setTransform( - a: number, - b: number, - c: number, - d: number, - e: number, - f: number - ) { - this._context.setTransform(a, b, c, d, e, f); - } - /** - * stroke function. - * @method - * @name Konva.Context#stroke - */ - stroke(path2d?: Path2D) { - if (path2d) { - this._context.stroke(path2d); - } else { - this._context.stroke(); - } - } - /** - * strokeText function. - * @method - * @name Konva.Context#strokeText - */ - strokeText(text: string, x: number, y: number, maxWidth?: number) { - this._context.strokeText(text, x, y, maxWidth); - } - /** - * transform function. - * @method - * @name Konva.Context#transform - */ - transform(a: number, b: number, c: number, d: number, e: number, f: number) { - this._context.transform(a, b, c, d, e, f); - } - /** - * translate function. - * @method - * @name Konva.Context#translate - */ - translate(x: number, y: number) { - this._context.translate(x, y); - } - _enableTrace() { - let that = this, - len = CONTEXT_METHODS.length, - origSetter = this.setAttr, - n, - args; - - // to prevent creating scope function at each loop - const func = function (methodName) { - let origMethod = that[methodName], - ret; - - that[methodName] = function () { - args = simplifyArray(Array.prototype.slice.call(arguments, 0)); - ret = origMethod.apply(that, arguments); - - that._trace({ - method: methodName, - args: args, - }); - - return ret; - }; - }; - // methods - for (n = 0; n < len; n++) { - func(CONTEXT_METHODS[n]); - } - - // attrs - that.setAttr = function () { - origSetter.apply(that, arguments as any); - const prop = arguments[0]; - let val = arguments[1]; - if ( - prop === 'shadowOffsetX' || - prop === 'shadowOffsetY' || - prop === 'shadowBlur' - ) { - val = val / this.canvas.getPixelRatio(); - } - that._trace({ - property: prop, - val: val, - }); - }; - } - _applyGlobalCompositeOperation(node) { - const op = node.attrs.globalCompositeOperation; - const def = !op || op === 'source-over'; - if (!def) { - this.setAttr('globalCompositeOperation', op); - } - } -} - -// supported context properties -type CanvasContextProps = Pick< - ExtendedCanvasRenderingContext2D, - (typeof CONTEXT_PROPERTIES)[number] ->; - -export interface Context extends CanvasContextProps {} - -CONTEXT_PROPERTIES.forEach(function (prop) { - Object.defineProperty(Context.prototype, prop, { - get() { - return this._context[prop]; - }, - set(val) { - this._context[prop] = val; - }, - }); -}); - -export class SceneContext extends Context { - constructor(canvas: Canvas, { willReadFrequently = false } = {}) { - super(canvas); - this._context = canvas._canvas.getContext('2d', { - willReadFrequently, - }) as CanvasRenderingContext2D; - } - _fillColor(shape: Shape) { - const fill = shape.fill(); - - this.setAttr('fillStyle', fill); - shape._fillFunc(this); - } - _fillPattern(shape: Shape) { - this.setAttr('fillStyle', shape._getFillPattern()); - shape._fillFunc(this); - } - _fillLinearGradient(shape: Shape) { - const grd = shape._getLinearGradient(); - - if (grd) { - this.setAttr('fillStyle', grd); - shape._fillFunc(this); - } - } - _fillRadialGradient(shape: Shape) { - const grd = shape._getRadialGradient(); - if (grd) { - this.setAttr('fillStyle', grd); - shape._fillFunc(this); - } - } - _fill(shape) { - const hasColor = shape.fill(), - fillPriority = shape.getFillPriority(); - - // priority fills - if (hasColor && fillPriority === 'color') { - this._fillColor(shape); - return; - } - - const hasPattern = shape.getFillPatternImage(); - if (hasPattern && fillPriority === 'pattern') { - this._fillPattern(shape); - return; - } - - const hasLinearGradient = shape.getFillLinearGradientColorStops(); - if (hasLinearGradient && fillPriority === 'linear-gradient') { - this._fillLinearGradient(shape); - return; - } - - const hasRadialGradient = shape.getFillRadialGradientColorStops(); - if (hasRadialGradient && fillPriority === 'radial-gradient') { - this._fillRadialGradient(shape); - return; - } - - // now just try and fill with whatever is available - if (hasColor) { - this._fillColor(shape); - } else if (hasPattern) { - this._fillPattern(shape); - } else if (hasLinearGradient) { - this._fillLinearGradient(shape); - } else if (hasRadialGradient) { - this._fillRadialGradient(shape); - } - } - _strokeLinearGradient(shape) { - const start = shape.getStrokeLinearGradientStartPoint(), - end = shape.getStrokeLinearGradientEndPoint(), - colorStops = shape.getStrokeLinearGradientColorStops(), - grd = this.createLinearGradient(start.x, start.y, end.x, end.y); - - if (colorStops) { - // build color stops - for (let n = 0; n < colorStops.length; n += 2) { - grd.addColorStop(colorStops[n] as number, colorStops[n + 1] as string); - } - this.setAttr('strokeStyle', grd); - } - } - _stroke(shape) { - const dash = shape.dash(), - // ignore strokeScaleEnabled for Text - strokeScaleEnabled = shape.getStrokeScaleEnabled(); - - if (shape.hasStroke()) { - if (!strokeScaleEnabled) { - this.save(); - const pixelRatio = this.getCanvas().getPixelRatio(); - this.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0); - } - - this._applyLineCap(shape); - if (dash && shape.dashEnabled()) { - this.setLineDash(dash); - this.setAttr('lineDashOffset', shape.dashOffset()); - } - - this.setAttr('lineWidth', shape.strokeWidth()); - - if (!shape.getShadowForStrokeEnabled()) { - this.setAttr('shadowColor', 'rgba(0,0,0,0)'); - } - - const hasLinearGradient = shape.getStrokeLinearGradientColorStops(); - if (hasLinearGradient) { - this._strokeLinearGradient(shape); - } else { - this.setAttr('strokeStyle', shape.stroke()); - } - - shape._strokeFunc(this); - - if (!strokeScaleEnabled) { - this.restore(); - } - } - } - _applyShadow(shape) { - const color = shape.getShadowRGBA() ?? 'black', - blur = shape.getShadowBlur() ?? 5, - offset = shape.getShadowOffset() ?? { - x: 0, - y: 0, - }, - scale = shape.getAbsoluteScale(), - ratio = this.canvas.getPixelRatio(), - scaleX = scale.x * ratio, - scaleY = scale.y * ratio; - - this.setAttr('shadowColor', color); - this.setAttr( - 'shadowBlur', - blur * Math.min(Math.abs(scaleX), Math.abs(scaleY)) - ); - this.setAttr('shadowOffsetX', offset.x * scaleX); - this.setAttr('shadowOffsetY', offset.y * scaleY); - } -} - -export class HitContext extends Context { - constructor(canvas: Canvas) { - super(canvas); - this._context = canvas._canvas.getContext('2d', { - willReadFrequently: true, - }) as CanvasRenderingContext2D; - } - _fill(shape: Shape) { - this.save(); - this.setAttr('fillStyle', shape.colorKey); - shape._fillFuncHit(this); - this.restore(); - } - strokeShape(shape: Shape) { - if (shape.hasHitStroke()) { - this._stroke(shape); - } - } - _stroke(shape) { - if (shape.hasHitStroke()) { - // ignore strokeScaleEnabled for Text - const strokeScaleEnabled = shape.getStrokeScaleEnabled(); - if (!strokeScaleEnabled) { - this.save(); - const pixelRatio = this.getCanvas().getPixelRatio(); - this.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0); - } - this._applyLineCap(shape); - - const hitStrokeWidth = shape.hitStrokeWidth(); - const strokeWidth = - hitStrokeWidth === 'auto' ? shape.strokeWidth() : hitStrokeWidth; - - this.setAttr('lineWidth', strokeWidth); - this.setAttr('strokeStyle', shape.colorKey); - shape._strokeFuncHit(this); - if (!strokeScaleEnabled) { - this.restore(); - } - } - } -} diff --git a/src/Core.ts b/src/Core.ts deleted file mode 100644 index e6eecb925..000000000 --- a/src/Core.ts +++ /dev/null @@ -1,5 +0,0 @@ -// enter file of limited Konva version with only core functions -export { Konva } from './_CoreInternals'; -import { Konva } from './_CoreInternals'; - -export default Konva; diff --git a/src/DragAndDrop.ts b/src/DragAndDrop.ts deleted file mode 100644 index 4bed333e5..000000000 --- a/src/DragAndDrop.ts +++ /dev/null @@ -1,173 +0,0 @@ -import { Container } from './Container'; -import { Konva } from './Global'; -import { Node } from './Node'; -import { Vector2d } from './types'; -import { Util } from './Util'; - -export const DD = { - get isDragging() { - let flag = false; - DD._dragElements.forEach((elem) => { - if (elem.dragStatus === 'dragging') { - flag = true; - } - }); - return flag; - }, - justDragged: false, - get node() { - // return first dragging node - let node: Node | undefined; - DD._dragElements.forEach((elem) => { - node = elem.node; - }); - return node; - }, - _dragElements: new Map< - number, - { - node: Node; - startPointerPos: Vector2d; - offset: Vector2d; - pointerId?: number; - // when we just put pointer down on a node - // it will create drag element - dragStatus: 'ready' | 'dragging' | 'stopped'; - // dragStarted: boolean; - // isDragging: boolean; - // dragStopped: boolean; - } - >(), - - // methods - _drag(evt) { - const nodesToFireEvents: Array = []; - DD._dragElements.forEach((elem, key) => { - const { node } = elem; - // we need to find pointer relative to that node - const stage = node.getStage()!; - stage.setPointersPositions(evt); - - // it is possible that user call startDrag without any event - // it that case we need to detect first movable pointer and attach it into the node - if (elem.pointerId === undefined) { - elem.pointerId = Util._getFirstPointerId(evt); - } - const pos = stage._changedPointerPositions.find( - (pos) => pos.id === elem.pointerId - ); - - // not related pointer - if (!pos) { - return; - } - if (elem.dragStatus !== 'dragging') { - const dragDistance = node.dragDistance(); - const distance = Math.max( - Math.abs(pos.x - elem.startPointerPos.x), - Math.abs(pos.y - elem.startPointerPos.y) - ); - if (distance < dragDistance) { - return; - } - node.startDrag({ evt }); - // a user can stop dragging inside `dragstart` - if (!node.isDragging()) { - return; - } - } - node._setDragPosition(evt, elem); - nodesToFireEvents.push(node); - }); - // call dragmove only after ALL positions are changed - nodesToFireEvents.forEach((node) => { - node.fire( - 'dragmove', - { - type: 'dragmove', - target: node, - evt: evt, - }, - true - ); - }); - }, - - // dragBefore and dragAfter allows us to set correct order of events - // setup all in dragbefore, and stop dragging only after pointerup triggered. - _endDragBefore(evt?) { - const drawNodes: Array = []; - DD._dragElements.forEach((elem) => { - const { node } = elem; - // we need to find pointer relative to that node - const stage = node.getStage()!; - if (evt) { - stage.setPointersPositions(evt); - } - - const pos = stage._changedPointerPositions.find( - (pos) => pos.id === elem.pointerId - ); - - // that pointer is not related - if (!pos) { - return; - } - - if (elem.dragStatus === 'dragging' || elem.dragStatus === 'stopped') { - // if a node is stopped manually we still need to reset events: - DD.justDragged = true; - Konva._mouseListenClick = false; - Konva._touchListenClick = false; - Konva._pointerListenClick = false; - elem.dragStatus = 'stopped'; - } - - const drawNode = - elem.node.getLayer() || - ((elem.node instanceof Konva['Stage'] && elem.node) as any); - - if (drawNode && drawNodes.indexOf(drawNode) === -1) { - drawNodes.push(drawNode); - } - }); - // draw in a sync way - // because mousemove event may trigger BEFORE batch draw is called - // but as we have not hit canvas updated yet, it will trigger incorrect mouseover/mouseout events - drawNodes.forEach((drawNode) => { - drawNode.draw(); - }); - }, - _endDragAfter(evt) { - DD._dragElements.forEach((elem, key) => { - if (elem.dragStatus === 'stopped') { - elem.node.fire( - 'dragend', - { - type: 'dragend', - target: elem.node, - evt: evt, - }, - true - ); - } - if (elem.dragStatus !== 'dragging') { - DD._dragElements.delete(key); - } - }); - }, -}; - -if (Konva.isBrowser) { - window.addEventListener('mouseup', DD._endDragBefore, true); - window.addEventListener('touchend', DD._endDragBefore, true); - // add touchcancel to fix this: https://github.com/konvajs/konva/issues/1843 - window.addEventListener('touchcancel', DD._endDragBefore, true); - - window.addEventListener('mousemove', DD._drag); - window.addEventListener('touchmove', DD._drag); - - window.addEventListener('mouseup', DD._endDragAfter, false); - window.addEventListener('touchend', DD._endDragAfter, false); - window.addEventListener('touchcancel', DD._endDragAfter, false); -} diff --git a/src/Factory.ts b/src/Factory.ts deleted file mode 100644 index b1af978d2..000000000 --- a/src/Factory.ts +++ /dev/null @@ -1,248 +0,0 @@ -import { Node } from './Node'; -import { GetSet } from './types'; -import { Util } from './Util'; -import { getComponentValidator } from './Validators'; - -const GET = 'get'; -const SET = 'set'; - -/** - * Enforces that a type is a string. - */ -type EnforceString = T extends string ? T : never; - -/** - * Represents a class. - */ -type Constructor = abstract new (...args: any) => any; - -/** - * An attribute of an instance of the provided class. Attributes names be strings. - */ -type Attr = EnforceString>; - -/** - * A function that is called after a setter is called. - */ -type AfterFunc = (this: InstanceType) => void; - -/** - * Extracts the type of a GetSet. - */ -type ExtractGetSet = T extends GetSet ? U : never; - -/** - * Extracts the type of a GetSet class attribute. - */ -type Value> = ExtractGetSet< - InstanceType[U] ->; - -/** - * A function that validates a value. - */ -type ValidatorFunc = (val: ExtractGetSet, attr: string) => T; - -/** - * Extracts the "components" (keys) of a GetSet value. The value must be an object. - */ -type ExtractComponents> = Value< - T, - U -> extends Record - ? EnforceString>[] - : never; - -export const Factory = { - addGetterSetter>( - constructor: T, - attr: U, - def?: Value, - validator?: ValidatorFunc>, - after?: AfterFunc - ): void { - Factory.addGetter(constructor, attr, def); - Factory.addSetter(constructor, attr, validator, after); - Factory.addOverloadedGetterSetter(constructor, attr); - }, - addGetter>( - constructor: T, - attr: U, - def?: Value - ) { - var method = GET + Util._capitalize(attr); - - constructor.prototype[method] = - constructor.prototype[method] || - function (this: Node) { - const val = this.attrs[attr]; - return val === undefined ? def : val; - }; - }, - - addSetter>( - constructor: T, - attr: U, - validator?: ValidatorFunc>, - after?: AfterFunc - ) { - var method = SET + Util._capitalize(attr); - - if (!constructor.prototype[method]) { - Factory.overWriteSetter(constructor, attr, validator, after); - } - }, - - overWriteSetter>( - constructor: T, - attr: U, - validator?: ValidatorFunc>, - after?: AfterFunc - ) { - var method = SET + Util._capitalize(attr); - constructor.prototype[method] = function (val) { - if (validator && val !== undefined && val !== null) { - val = validator.call(this, val, attr); - } - - this._setAttr(attr, val); - - if (after) { - after.call(this); - } - - return this; - }; - }, - - addComponentsGetterSetter>( - constructor: T, - attr: U, - components: ExtractComponents, - validator?: ValidatorFunc>, - after?: AfterFunc - ) { - const len = components.length, - capitalize = Util._capitalize, - getter = GET + capitalize(attr), - setter = SET + capitalize(attr); - - // getter - constructor.prototype[getter] = function () { - const ret: Record = {}; - - for (let n = 0; n < len; n++) { - const component = components[n]; - ret[component] = this.getAttr(attr + capitalize(component)); - } - - return ret; - }; - - const basicValidator = getComponentValidator(components); - - // setter - constructor.prototype[setter] = function (val) { - const oldVal = this.attrs[attr]; - - if (validator) { - val = validator.call(this, val, attr); - } - - if (basicValidator) { - basicValidator.call(this, val, attr); - } - - for (const key in val) { - if (!val.hasOwnProperty(key)) { - continue; - } - this._setAttr(attr + capitalize(key), val[key]); - } - if (!val) { - components.forEach((component) => { - this._setAttr(attr + capitalize(component), undefined); - }); - } - - this._fireChangeEvent(attr, oldVal, val); - - if (after) { - after.call(this); - } - - return this; - }; - - Factory.addOverloadedGetterSetter(constructor, attr); - }, - addOverloadedGetterSetter>( - constructor: T, - attr: U - ) { - var capitalizedAttr = Util._capitalize(attr), - setter = SET + capitalizedAttr, - getter = GET + capitalizedAttr; - - constructor.prototype[attr] = function () { - // setting - if (arguments.length) { - this[setter](arguments[0]); - return this; - } - // getting - return this[getter](); - }; - }, - addDeprecatedGetterSetter>( - constructor: T, - attr: U, - def: Value, - validator: ValidatorFunc> - ) { - Util.error('Adding deprecated ' + attr); - - const method = GET + Util._capitalize(attr); - - const message = - attr + - ' property is deprecated and will be removed soon. Look at Konva change log for more information.'; - constructor.prototype[method] = function () { - Util.error(message); - const val = this.attrs[attr]; - return val === undefined ? def : val; - }; - Factory.addSetter(constructor, attr, validator, function () { - Util.error(message); - }); - Factory.addOverloadedGetterSetter(constructor, attr); - }, - backCompat( - constructor: T, - methods: Record - ) { - Util.each(methods, function (oldMethodName, newMethodName) { - const method = constructor.prototype[newMethodName]; - const oldGetter = GET + Util._capitalize(oldMethodName); - const oldSetter = SET + Util._capitalize(oldMethodName); - - function deprecated(this: Node) { - method.apply(this, arguments); - Util.error( - '"' + - oldMethodName + - '" method is deprecated and will be removed soon. Use ""' + - newMethodName + - '" instead.' - ); - } - - constructor.prototype[oldMethodName] = deprecated; - constructor.prototype[oldGetter] = deprecated; - constructor.prototype[oldSetter] = deprecated; - }); - }, - afterSetFilter(this: Node): void { - this._filterUpToDate = false; - }, -}; diff --git a/src/FastLayer.ts b/src/FastLayer.ts deleted file mode 100644 index a7f599319..000000000 --- a/src/FastLayer.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Util } from './Util'; -import { Layer } from './Layer'; -import { _registerNode } from './Global'; - -/** - * FastLayer constructor. **DEPRECATED!** Please use `Konva.Layer({ listening: false})` instead. Layers are tied to their own canvas element and are used - * to contain shapes only. If you don't need node nesting, mouse and touch interactions, - * or event pub/sub, you should use FastLayer instead of Layer to create your layers. - * It renders about 2x faster than normal layers. - * - * @constructor - * @memberof Konva - * @augments Konva.Layer - * @@containerParams - * @example - * var layer = new Konva.FastLayer(); - */ -export class FastLayer extends Layer { - constructor(attrs) { - super(attrs); - this.listening(false); - Util.warn( - 'Konva.Fast layer is deprecated. Please use "new Konva.Layer({ listening: false })" instead.' - ); - } -} - -FastLayer.prototype.nodeType = 'FastLayer'; -_registerNode(FastLayer); diff --git a/src/Global.ts b/src/Global.ts deleted file mode 100644 index 9996e3981..000000000 --- a/src/Global.ts +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Konva JavaScript Framework v@@version - * http://konvajs.org/ - * Licensed under the MIT - * Date: @@date - * - * Original work Copyright (C) 2011 - 2013 by Eric Rowell (KineticJS) - * Modified work Copyright (C) 2014 - present by Anton Lavrenov (Konva) - * - * @license - */ -const PI_OVER_180 = Math.PI / 180; -/** - * @namespace Konva - */ - -function detectBrowser() { - return ( - typeof window !== 'undefined' && - // browser case - ({}.toString.call(window) === '[object Window]' || - // electron case - {}.toString.call(window) === '[object global]') - ); -} - -declare const WorkerGlobalScope: any; - -export const glob: any = - typeof global !== 'undefined' - ? global - : typeof window !== 'undefined' - ? window - : typeof WorkerGlobalScope !== 'undefined' - ? self - : {}; - -export const Konva = { - _global: glob, - version: '@@version', - isBrowser: detectBrowser(), - isUnminified: /param/.test(function (param: any) {}.toString()), - dblClickWindow: 400, - getAngle(angle: number) { - return Konva.angleDeg ? angle * PI_OVER_180 : angle; - }, - enableTrace: false, - pointerEventsEnabled: true, - /** - * Should Konva automatically update canvas on any changes. Default is true. - * @property autoDrawEnabled - * @default true - * @name autoDrawEnabled - * @memberof Konva - * @example - * Konva.autoDrawEnabled = true; - */ - autoDrawEnabled: true, - /** - * Should we enable hit detection while dragging? For performance reasons, by default it is false. - * But on some rare cases you want to see hit graph and check intersections. Just set it to true. - * @property hitOnDragEnabled - * @default false - * @name hitOnDragEnabled - * @memberof Konva - * @example - * Konva.hitOnDragEnabled = true; - */ - hitOnDragEnabled: false, - /** - * Should we capture touch events and bind them to the touchstart target? That is how it works on DOM elements. - * The case: we touchstart on div1, then touchmove out of that element into another element div2. - * DOM will continue trigger touchmove events on div1 (not div2). Because events are "captured" into initial target. - * By default Konva do not do that and will trigger touchmove on another element, while pointer is moving. - * @property capturePointerEventsEnabled - * @default false - * @name capturePointerEventsEnabled - * @memberof Konva - * @example - * Konva.capturePointerEventsEnabled = true; - */ - capturePointerEventsEnabled: false, - - _mouseListenClick: false, - _touchListenClick: false, - _pointerListenClick: false, - _mouseInDblClickWindow: false, - _touchInDblClickWindow: false, - _pointerInDblClickWindow: false, - _mouseDblClickPointerId: null, - _touchDblClickPointerId: null, - _pointerDblClickPointerId: null, - _fixTextRendering: false, - - /** - * Global pixel ratio configuration. KonvaJS automatically detect pixel ratio of current device. - * But you may override such property, if you want to use your value. Set this value before any components initializations. - * @property pixelRatio - * @default undefined - * @name pixelRatio - * @memberof Konva - * @example - * // before any Konva code: - * Konva.pixelRatio = 1; - */ - pixelRatio: (typeof window !== 'undefined' && window.devicePixelRatio) || 1, - - /** - * Drag distance property. If you start to drag a node you may want to wait until pointer is moved to some distance from start point, - * only then start dragging. Default is 3px. - * @property dragDistance - * @default 0 - * @memberof Konva - * @example - * Konva.dragDistance = 10; - */ - dragDistance: 3, - /** - * Use degree values for angle properties. You may set this property to false if you want to use radian values. - * @property angleDeg - * @default true - * @memberof Konva - * @example - * node.rotation(45); // 45 degrees - * Konva.angleDeg = false; - * node.rotation(Math.PI / 2); // PI/2 radian - */ - angleDeg: true, - /** - * Show different warnings about errors or wrong API usage - * @property showWarnings - * @default true - * @memberof Konva - * @example - * Konva.showWarnings = false; - */ - showWarnings: true, - - /** - * Configure what mouse buttons can be used for drag and drop. - * Default value is [0] - only left mouse button. - * @property dragButtons - * @default true - * @memberof Konva - * @example - * // enable left and right mouse buttons - * Konva.dragButtons = [0, 2]; - */ - dragButtons: [0, 1], - - /** - * returns whether or not drag and drop is currently active - * @method - * @memberof Konva - */ - isDragging() { - return Konva['DD'].isDragging; - }, - isTransforming() { - return Konva['Transformer']?.isTransforming(); - }, - /** - * returns whether or not a drag and drop operation is ready, but may - * not necessarily have started - * @method - * @memberof Konva - */ - isDragReady() { - return !!Konva['DD'].node; - }, - /** - * Should Konva release canvas elements on destroy. Default is true. - * Useful to avoid memory leak issues in Safari on macOS/iOS. - * @property releaseCanvasOnDestroy - * @default true - * @name releaseCanvasOnDestroy - * @memberof Konva - * @example - * Konva.releaseCanvasOnDestroy = true; - */ - releaseCanvasOnDestroy: true, - // user agent - document: glob.document, - // insert Konva into global namespace (window) - // it is required for npm packages - _injectGlobal(Konva) { - glob.Konva = Konva; - }, -}; - -export const _registerNode = (NodeClass: any) => { - Konva[NodeClass.prototype.getClassName()] = NodeClass; -}; - -Konva._injectGlobal(Konva); diff --git a/src/Group.ts b/src/Group.ts deleted file mode 100644 index 77e83ad72..000000000 --- a/src/Group.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Util } from './Util'; -import { Container, ContainerConfig } from './Container'; -import { _registerNode } from './Global'; -import { Node } from './Node'; -import { Shape } from './Shape'; - -export interface GroupConfig extends ContainerConfig {} - -/** - * Group constructor. Groups are used to contain shapes or other groups. - * @constructor - * @memberof Konva - * @augments Konva.Container - * @param {Object} config - * @@nodeParams - * @@containerParams - * @example - * var group = new Konva.Group(); - */ -export class Group extends Container { - _validateAdd(child: Node) { - const type = child.getType(); - if (type !== 'Group' && type !== 'Shape') { - Util.throw('You may only add groups and shapes to groups.'); - } - } -} - -Group.prototype.nodeType = 'Group'; -_registerNode(Group); diff --git a/src/Layer.ts b/src/Layer.ts deleted file mode 100644 index 8e94c1d17..000000000 --- a/src/Layer.ts +++ /dev/null @@ -1,544 +0,0 @@ -import { Util } from './Util'; -import { Container, ContainerConfig } from './Container'; -import { Node } from './Node'; -import { Factory } from './Factory'; -import { SceneCanvas, HitCanvas } from './Canvas'; -import { Stage } from './Stage'; -import { getBooleanValidator } from './Validators'; - -import { GetSet, Vector2d } from './types'; -import { Group } from './Group'; -import { Shape, shapes } from './Shape'; -import { _registerNode } from './Global'; - -export interface LayerConfig extends ContainerConfig { - clearBeforeDraw?: boolean; - hitGraphEnabled?: boolean; - imageSmoothingEnabled?: boolean; -} - -// constants -const HASH = '#', - BEFORE_DRAW = 'beforeDraw', - DRAW = 'draw', - /* - * 2 - 3 - 4 - * | | - * 1 - 0 5 - * | - * 8 - 7 - 6 - */ - INTERSECTION_OFFSETS = [ - { x: 0, y: 0 }, // 0 - { x: -1, y: -1 }, // 2 - { x: 1, y: -1 }, // 4 - { x: 1, y: 1 }, // 6 - { x: -1, y: 1 }, // 8 - ], - INTERSECTION_OFFSETS_LEN = INTERSECTION_OFFSETS.length; - -/** - * Layer constructor. Layers are tied to their own canvas element and are used - * to contain groups or shapes. - * @constructor - * @memberof Konva - * @augments Konva.Container - * @param {Object} config - * @param {Boolean} [config.clearBeforeDraw] set this property to false if you don't want - * to clear the canvas before each layer draw. The default value is true. - * @@nodeParams - * @@containerParams - * @example - * var layer = new Konva.Layer(); - * stage.add(layer); - * // now you can add shapes, groups into the layer - */ - -export class Layer extends Container { - canvas = new SceneCanvas(); - hitCanvas = new HitCanvas({ - pixelRatio: 1, - }); - - _waitingForDraw = false; - - constructor(config?: LayerConfig) { - super(config); - this.on('visibleChange.konva', this._checkVisibility); - this._checkVisibility(); - - this.on('imageSmoothingEnabledChange.konva', this._setSmoothEnabled); - this._setSmoothEnabled(); - } - // for nodejs? - createPNGStream() { - const c = this.canvas._canvas as any; - return c.createPNGStream(); - } - /** - * get layer canvas wrapper - * @method - * @name Konva.Layer#getCanvas - */ - getCanvas() { - return this.canvas; - } - /** - * get native canvas element - * @method - * @name Konva.Layer#getNativeCanvasElement - */ - getNativeCanvasElement() { - return this.canvas._canvas; - } - /** - * get layer hit canvas - * @method - * @name Konva.Layer#getHitCanvas - */ - getHitCanvas() { - return this.hitCanvas; - } - /** - * get layer canvas context - * @method - * @name Konva.Layer#getContext - */ - getContext() { - return this.getCanvas().getContext(); - } - // TODO: deprecate this method - clear(bounds?) { - this.getContext().clear(bounds); - this.getHitCanvas().getContext().clear(bounds); - return this; - } - // extend Node.prototype.setZIndex - setZIndex(index) { - super.setZIndex(index); - const stage = this.getStage(); - if (stage && stage.content) { - stage.content.removeChild(this.getNativeCanvasElement()); - - if (index < stage.children.length - 1) { - stage.content.insertBefore( - this.getNativeCanvasElement(), - stage.children[index + 1].getCanvas()._canvas - ); - } else { - stage.content.appendChild(this.getNativeCanvasElement()); - } - } - return this; - } - moveToTop() { - Node.prototype.moveToTop.call(this); - const stage = this.getStage(); - if (stage && stage.content) { - stage.content.removeChild(this.getNativeCanvasElement()); - stage.content.appendChild(this.getNativeCanvasElement()); - } - return true; - } - moveUp() { - const moved = Node.prototype.moveUp.call(this); - if (!moved) { - return false; - } - const stage = this.getStage(); - if (!stage || !stage.content) { - return false; - } - stage.content.removeChild(this.getNativeCanvasElement()); - - if (this.index < stage.children.length - 1) { - stage.content.insertBefore( - this.getNativeCanvasElement(), - stage.children[this.index + 1].getCanvas()._canvas - ); - } else { - stage.content.appendChild(this.getNativeCanvasElement()); - } - return true; - } - // extend Node.prototype.moveDown - moveDown() { - if (Node.prototype.moveDown.call(this)) { - const stage = this.getStage(); - if (stage) { - const children = stage.children; - if (stage.content) { - stage.content.removeChild(this.getNativeCanvasElement()); - stage.content.insertBefore( - this.getNativeCanvasElement(), - children[this.index + 1].getCanvas()._canvas - ); - } - } - return true; - } - return false; - } - // extend Node.prototype.moveToBottom - moveToBottom() { - if (Node.prototype.moveToBottom.call(this)) { - const stage = this.getStage(); - if (stage) { - const children = stage.children; - if (stage.content) { - stage.content.removeChild(this.getNativeCanvasElement()); - stage.content.insertBefore( - this.getNativeCanvasElement(), - children[1].getCanvas()._canvas - ); - } - } - return true; - } - return false; - } - getLayer() { - return this; - } - remove() { - const _canvas = this.getNativeCanvasElement(); - - Node.prototype.remove.call(this); - - if (_canvas && _canvas.parentNode && Util._isInDocument(_canvas)) { - _canvas.parentNode.removeChild(_canvas); - } - return this; - } - getStage() { - return this.parent as Stage; - } - setSize({ width, height }) { - this.canvas.setSize(width, height); - this.hitCanvas.setSize(width, height); - this._setSmoothEnabled(); - return this; - } - _validateAdd(child) { - const type = child.getType(); - if (type !== 'Group' && type !== 'Shape') { - Util.throw('You may only add groups and shapes to a layer.'); - } - } - _toKonvaCanvas(config) { - config = config || {}; - config.width = config.width || this.getWidth(); - config.height = config.height || this.getHeight(); - config.x = config.x !== undefined ? config.x : this.x(); - config.y = config.y !== undefined ? config.y : this.y(); - - return Node.prototype._toKonvaCanvas.call(this, config); - } - - _checkVisibility() { - const visible = this.visible(); - if (visible) { - this.canvas._canvas.style.display = 'block'; - } else { - this.canvas._canvas.style.display = 'none'; - } - } - - _setSmoothEnabled() { - this.getContext()._context.imageSmoothingEnabled = - this.imageSmoothingEnabled(); - } - /** - * get/set width of layer. getter return width of stage. setter doing nothing. - * if you want change width use `stage.width(value);` - * @name Konva.Layer#width - * @method - * @returns {Number} - * @example - * var width = layer.width(); - */ - getWidth() { - if (this.parent) { - return this.parent.width(); - } - } - setWidth() { - Util.warn( - 'Can not change width of layer. Use "stage.width(value)" function instead.' - ); - } - /** - * get/set height of layer.getter return height of stage. setter doing nothing. - * if you want change height use `stage.height(value);` - * @name Konva.Layer#height - * @method - * @returns {Number} - * @example - * var height = layer.height(); - */ - getHeight() { - if (this.parent) { - return this.parent.height(); - } - } - setHeight() { - Util.warn( - 'Can not change height of layer. Use "stage.height(value)" function instead.' - ); - } - - /** - * batch draw. this function will not do immediate draw - * but it will schedule drawing to next tick (requestAnimFrame) - * @method - * @name Konva.Layer#batchDraw - * @return {Konva.Layer} this - */ - batchDraw() { - if (!this._waitingForDraw) { - this._waitingForDraw = true; - Util.requestAnimFrame(() => { - this.draw(); - this._waitingForDraw = false; - }); - } - return this; - } - - /** - * get visible intersection shape. This is the preferred - * method for determining if a point intersects a shape or not - * also you may pass optional selector parameter to return ancestor of intersected shape - * nodes with listening set to false will not be detected - * @method - * @name Konva.Layer#getIntersection - * @param {Object} pos - * @param {Number} pos.x - * @param {Number} pos.y - * @returns {Konva.Node} - * @example - * var shape = layer.getIntersection({x: 50, y: 50}); - */ - getIntersection(pos: Vector2d) { - if (!this.isListening() || !this.isVisible()) { - return null; - } - // in some cases antialiased area may be bigger than 1px - // it is possible if we will cache node, then scale it a lot - let spiralSearchDistance = 1; - let continueSearch = false; - while (true) { - for (let i = 0; i < INTERSECTION_OFFSETS_LEN; i++) { - const intersectionOffset = INTERSECTION_OFFSETS[i]; - const obj = this._getIntersection({ - x: pos.x + intersectionOffset.x * spiralSearchDistance, - y: pos.y + intersectionOffset.y * spiralSearchDistance, - }); - const shape = obj.shape; - if (shape) { - return shape; - } - // we should continue search if we found antialiased pixel - // that means our node somewhere very close - continueSearch = !!obj.antialiased; - // stop search if found empty pixel - if (!obj.antialiased) { - break; - } - } - // if no shape, and no antialiased pixel, we should end searching - if (continueSearch) { - spiralSearchDistance += 1; - } else { - return null; - } - } - } - _getIntersection(pos: Vector2d): { shape?: Shape; antialiased?: boolean } { - const ratio = this.hitCanvas.pixelRatio; - const p = this.hitCanvas.context.getImageData( - Math.round(pos.x * ratio), - Math.round(pos.y * ratio), - 1, - 1 - ).data; - const p3 = p[3]; - - // fully opaque pixel - if (p3 === 255) { - const colorKey = Util._rgbToHex(p[0], p[1], p[2]); - const shape = shapes[HASH + colorKey]; - if (shape) { - return { - shape: shape, - }; - } - return { - antialiased: true, - }; - } else if (p3 > 0) { - // antialiased pixel - return { - antialiased: true, - }; - } - // empty pixel - return {}; - } - drawScene(can?: SceneCanvas, top?: Node) { - const layer = this.getLayer(), - canvas = can || (layer && layer.getCanvas()); - - this._fire(BEFORE_DRAW, { - node: this, - }); - - if (this.clearBeforeDraw()) { - canvas.getContext().clear(); - } - - Container.prototype.drawScene.call(this, canvas, top); - - this._fire(DRAW, { - node: this, - }); - - return this; - } - drawHit(can?: HitCanvas, top?: Node) { - const layer = this.getLayer(), - canvas = can || (layer && layer.hitCanvas); - - if (layer && layer.clearBeforeDraw()) { - layer.getHitCanvas().getContext().clear(); - } - - Container.prototype.drawHit.call(this, canvas, top); - return this; - } - /** - * enable hit graph. **DEPRECATED!** Use `layer.listening(true)` instead. - * @name Konva.Layer#enableHitGraph - * @method - * @returns {Layer} - */ - enableHitGraph() { - this.hitGraphEnabled(true); - return this; - } - /** - * disable hit graph. **DEPRECATED!** Use `layer.listening(false)` instead. - * @name Konva.Layer#disableHitGraph - * @method - * @returns {Layer} - */ - disableHitGraph() { - this.hitGraphEnabled(false); - return this; - } - - setHitGraphEnabled(val) { - Util.warn( - 'hitGraphEnabled method is deprecated. Please use layer.listening() instead.' - ); - this.listening(val); - } - - getHitGraphEnabled(val) { - Util.warn( - 'hitGraphEnabled method is deprecated. Please use layer.listening() instead.' - ); - return this.listening(); - } - - /** - * Show or hide hit canvas over the stage. May be useful for debugging custom hitFunc - * @name Konva.Layer#toggleHitCanvas - * @method - */ - toggleHitCanvas() { - if (!this.parent || !this.parent['content']) { - return; - } - const parent = this.parent as any; - const added = !!this.hitCanvas._canvas.parentNode; - if (added) { - parent.content.removeChild(this.hitCanvas._canvas); - } else { - parent.content.appendChild(this.hitCanvas._canvas); - } - } - - destroy(): this { - Util.releaseCanvas( - this.getNativeCanvasElement(), - this.getHitCanvas()._canvas - ); - return super.destroy(); - } - - hitGraphEnabled: GetSet; - - clearBeforeDraw: GetSet; - imageSmoothingEnabled: GetSet; -} - -Layer.prototype.nodeType = 'Layer'; -_registerNode(Layer); - -/** - * get/set imageSmoothingEnabled flag - * For more info see https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/imageSmoothingEnabled - * @name Konva.Layer#imageSmoothingEnabled - * @method - * @param {Boolean} imageSmoothingEnabled - * @returns {Boolean} - * @example - * // get imageSmoothingEnabled flag - * var imageSmoothingEnabled = layer.imageSmoothingEnabled(); - * - * layer.imageSmoothingEnabled(false); - * - * layer.imageSmoothingEnabled(true); - */ -Factory.addGetterSetter(Layer, 'imageSmoothingEnabled', true); - -/** - * get/set clearBeforeDraw flag which determines if the layer is cleared or not - * before drawing - * @name Konva.Layer#clearBeforeDraw - * @method - * @param {Boolean} clearBeforeDraw - * @returns {Boolean} - * @example - * // get clearBeforeDraw flag - * var clearBeforeDraw = layer.clearBeforeDraw(); - * - * // disable clear before draw - * layer.clearBeforeDraw(false); - * - * // enable clear before draw - * layer.clearBeforeDraw(true); - */ -Factory.addGetterSetter(Layer, 'clearBeforeDraw', true); - -Factory.addGetterSetter(Layer, 'hitGraphEnabled', true, getBooleanValidator()); -/** - * get/set hitGraphEnabled flag. **DEPRECATED!** Use `layer.listening(false)` instead. - * Disabling the hit graph will greatly increase - * draw performance because the hit graph will not be redrawn each time the layer is - * drawn. This, however, also disables mouse/touch event detection - * @name Konva.Layer#hitGraphEnabled - * @method - * @param {Boolean} enabled - * @returns {Boolean} - * @example - * // get hitGraphEnabled flag - * var hitGraphEnabled = layer.hitGraphEnabled(); - * - * // disable hit graph - * layer.hitGraphEnabled(false); - * - * // enable hit graph - * layer.hitGraphEnabled(true); - */ diff --git a/src/Node.ts b/src/Node.ts deleted file mode 100644 index d31723ff4..000000000 --- a/src/Node.ts +++ /dev/null @@ -1,3328 +0,0 @@ -import { Util, Transform } from './Util'; -import { Factory } from './Factory'; -import { SceneCanvas, HitCanvas, Canvas } from './Canvas'; -import { Konva } from './Global'; -import { Container } from './Container'; -import { GetSet, Vector2d, IRect } from './types'; -import { DD } from './DragAndDrop'; -import { - getNumberValidator, - getStringValidator, - getBooleanValidator, -} from './Validators'; -import { Stage } from './Stage'; -import { Context } from './Context'; -import { Shape } from './Shape'; -import { Layer } from './Layer'; - -export type Filter = (this: Node, imageData: ImageData) => void; - -type globalCompositeOperationType = - | '' - | 'source-over' - | 'source-in' - | 'source-out' - | 'source-atop' - | 'destination-over' - | 'destination-in' - | 'destination-out' - | 'destination-atop' - | 'lighter' - | 'copy' - | 'xor' - | 'multiply' - | 'screen' - | 'overlay' - | 'darken' - | 'lighten' - | 'color-dodge' - | 'color-burn' - | 'hard-light' - | 'soft-light' - | 'difference' - | 'exclusion' - | 'hue' - | 'saturation' - | 'color' - | 'luminosity'; - -export interface NodeConfig { - // allow any custom attribute - [index: string]: any; - x?: number; - y?: number; - width?: number; - height?: number; - visible?: boolean; - listening?: boolean; - id?: string; - name?: string; - opacity?: number; - scale?: Vector2d; - scaleX?: number; - skewX?: number; - skewY?: number; - scaleY?: number; - rotation?: number; - rotationDeg?: number; - offset?: Vector2d; - offsetX?: number; - offsetY?: number; - draggable?: boolean; - dragDistance?: number; - dragBoundFunc?: (this: Node, pos: Vector2d) => Vector2d; - preventDefault?: boolean; - globalCompositeOperation?: globalCompositeOperationType; - filters?: Array; -} - -// CONSTANTS -const ABSOLUTE_OPACITY = 'absoluteOpacity', - ALL_LISTENERS = 'allEventListeners', - ABSOLUTE_TRANSFORM = 'absoluteTransform', - ABSOLUTE_SCALE = 'absoluteScale', - CANVAS = 'canvas', - CHANGE = 'Change', - CHILDREN = 'children', - KONVA = 'konva', - LISTENING = 'listening', - MOUSEENTER = 'mouseenter', - MOUSELEAVE = 'mouseleave', - NAME = 'name', - SET = 'set', - SHAPE = 'Shape', - SPACE = ' ', - STAGE = 'stage', - TRANSFORM = 'transform', - UPPER_STAGE = 'Stage', - VISIBLE = 'visible', - TRANSFORM_CHANGE_STR = [ - 'xChange.konva', - 'yChange.konva', - 'scaleXChange.konva', - 'scaleYChange.konva', - 'skewXChange.konva', - 'skewYChange.konva', - 'rotationChange.konva', - 'offsetXChange.konva', - 'offsetYChange.konva', - 'transformsEnabledChange.konva', - ].join(SPACE); - -let idCounter = 1; - -// create all the events here -type NodeEventMap = GlobalEventHandlersEventMap & { - [index: string]: any; -}; - -export interface KonvaEventObject { - type: string; - target: Shape | Stage; - evt: EventType; - pointerId: number; - currentTarget: This; - cancelBubble: boolean; - child?: Node; -} - -export type KonvaEventListener = ( - this: This, - ev: KonvaEventObject -) => void; - -/** - * Node constructor. Nodes are entities that can be transformed, layered, - * and have bound events. The stage, layers, groups, and shapes all extend Node. - * @constructor - * @memberof Konva - * @param {Object} config - * @@nodeParams - */ -export abstract class Node { - _id = idCounter++; - eventListeners: { - [index: string]: Array<{ name: string; handler: Function }>; - } = {}; - attrs: any = {}; - index = 0; - _allEventListeners: null | Array = null; - parent: Container | null = null; - _cache: Map = new Map(); - _attachedDepsListeners: Map = new Map(); - _lastPos: Vector2d | null = null; - _attrsAffectingSize!: string[]; - _batchingTransformChange = false; - _needClearTransformCache = false; - - _filterUpToDate = false; - _isUnderCache = false; - nodeType!: string; - className!: string; - - _dragEventId: number | null = null; - _shouldFireChangeEvents = false; - - constructor(config?: Config) { - // on initial set attrs wi don't need to fire change events - // because nobody is listening to them yet - this.setAttrs(config); - this._shouldFireChangeEvents = true; - - // all change event listeners are attached to the prototype - } - - hasChildren() { - return false; - } - - _clearCache(attr?: string) { - // if we want to clear transform cache - // we don't really need to remove it from the cache - // but instead mark as "dirty" - // so we don't need to create a new instance next time - if ( - (attr === TRANSFORM || attr === ABSOLUTE_TRANSFORM) && - this._cache.get(attr) - ) { - (this._cache.get(attr) as Transform).dirty = true; - } else if (attr) { - this._cache.delete(attr); - } else { - this._cache.clear(); - } - } - _getCache(attr: string, privateGetter: Function) { - let cache = this._cache.get(attr); - - // for transform the cache can be NOT empty - // but we still need to recalculate it if it is dirty - const isTransform = attr === TRANSFORM || attr === ABSOLUTE_TRANSFORM; - const invalid = cache === undefined || (isTransform && cache.dirty === true); - - // if not cached, we need to set it using the private getter method. - if (invalid) { - cache = privateGetter.call(this); - this._cache.set(attr, cache); - } - - return cache; - } - - _calculate(name: string, deps: Array, getter: Function) { - // if we are trying to calculate function for the first time - // we need to attach listeners for change events - if (!this._attachedDepsListeners.get(name)) { - const depsString = deps.map((dep) => dep + 'Change.konva').join(SPACE); - this.on(depsString, () => { - this._clearCache(name); - }); - this._attachedDepsListeners.set(name, true); - } - // just use cache function - return this._getCache(name, getter); - } - - _getCanvasCache() { - return this._cache.get(CANVAS); - } - /* - * when the logic for a cached result depends on ancestor propagation, use this - * method to clear self and children cache - */ - _clearSelfAndDescendantCache(attr?: string) { - this._clearCache(attr); - // trigger clear cache, so transformer can use it - if (attr === ABSOLUTE_TRANSFORM) { - this.fire('absoluteTransformChange'); - } - } - /** - * clear cached canvas - * @method - * @name Konva.Node#clearCache - * @returns {Konva.Node} - * @example - * node.clearCache(); - */ - clearCache() { - if (this._cache.has(CANVAS)) { - const { scene, filter, hit } = this._cache.get(CANVAS); - Util.releaseCanvas(scene, filter, hit); - this._cache.delete(CANVAS); - } - - this._clearSelfAndDescendantCache(); - this._requestDraw(); - return this; - } - /** - * cache node to improve drawing performance, apply filters, or create more accurate - * hit regions. For all basic shapes size of cache canvas will be automatically detected. - * If you need to cache your custom `Konva.Shape` instance you have to pass shape's bounding box - * properties. Look at [https://konvajs.org/docs/performance/Shape_Caching.html](https://konvajs.org/docs/performance/Shape_Caching.html) for more information. - * @method - * @name Konva.Node#cache - * @param {Object} [config] - * @param {Number} [config.x] - * @param {Number} [config.y] - * @param {Number} [config.width] - * @param {Number} [config.height] - * @param {Number} [config.offset] increase canvas size by `offset` pixel in all directions. - * @param {Boolean} [config.drawBorder] when set to true, a red border will be drawn around the cached - * region for debugging purposes - * @param {Number} [config.pixelRatio] change quality (or pixel ratio) of cached image. pixelRatio = 2 will produce 2x sized cache. - * @param {Boolean} [config.imageSmoothingEnabled] control imageSmoothingEnabled property of created canvas for cache - * @param {Number} [config.hitCanvasPixelRatio] change quality (or pixel ratio) of cached hit canvas. - * @returns {Konva.Node} - * @example - * // cache a shape with the x,y position of the bounding box at the center and - * // the width and height of the bounding box equal to the width and height of - * // the shape obtained from shape.width() and shape.height() - * image.cache(); - * - * // cache a node and define the bounding box position and size - * node.cache({ - * x: -30, - * y: -30, - * width: 100, - * height: 200 - * }); - * - * // cache a node and draw a red border around the bounding box - * // for debugging purposes - * node.cache({ - * x: -30, - * y: -30, - * width: 100, - * height: 200, - * offset : 10, - * drawBorder: true - * }); - */ - cache(config?: { - x?: number; - y?: number; - width?: number; - height?: number; - drawBorder?: boolean; - offset?: number; - pixelRatio?: number; - imageSmoothingEnabled?: boolean; - hitCanvasPixelRatio?: number; - }) { - const conf = config || {}; - let rect = {} as IRect; - - // don't call getClientRect if we have all attributes - // it means call it only if have one undefined - if ( - conf.x === undefined || - conf.y === undefined || - conf.width === undefined || - conf.height === undefined - ) { - rect = this.getClientRect({ - skipTransform: true, - relativeTo: this.getParent() || undefined, - }); - } - let width = Math.ceil(conf.width || rect.width), - height = Math.ceil(conf.height || rect.height), - pixelRatio = conf.pixelRatio, - x = conf.x === undefined ? Math.floor(rect.x) : conf.x, - y = conf.y === undefined ? Math.floor(rect.y) : conf.y, - offset = conf.offset || 0, - drawBorder = conf.drawBorder || false, - hitCanvasPixelRatio = conf.hitCanvasPixelRatio || 1; - - if (!width || !height) { - Util.error( - 'Can not cache the node. Width or height of the node equals 0. Caching is skipped.' - ); - return; - } - - // because using Math.floor on x, y position may shift drawing - // to avoid shift we need to increase size - // but we better to avoid it, for better filters flows - const extraPaddingX = Math.abs(Math.round(rect.x) - x) > 0.5 ? 1 : 0; - const extraPaddingY = Math.abs(Math.round(rect.y) - y) > 0.5 ? 1 : 0; - width += offset * 2 + extraPaddingX; - height += offset * 2 + extraPaddingY; - - x -= offset; - y -= offset; - - // if (Math.floor(x) < x) { - // x = Math.floor(x); - // // width += 1; - // } - // if (Math.floor(y) < y) { - // y = Math.floor(y); - // // height += 1; - // } - - // console.log({ x, y, width, height }, rect); - - const cachedSceneCanvas = new SceneCanvas({ - pixelRatio: pixelRatio, - width: width, - height: height, - }), - cachedFilterCanvas = new SceneCanvas({ - pixelRatio: pixelRatio, - width: 0, - height: 0, - willReadFrequently: true, - }), - cachedHitCanvas = new HitCanvas({ - pixelRatio: hitCanvasPixelRatio, - width: width, - height: height, - }), - sceneContext = cachedSceneCanvas.getContext(), - hitContext = cachedHitCanvas.getContext(); - - cachedHitCanvas.isCache = true; - cachedSceneCanvas.isCache = true; - - this._cache.delete(CANVAS); - this._filterUpToDate = false; - - if (conf.imageSmoothingEnabled === false) { - cachedSceneCanvas.getContext()._context.imageSmoothingEnabled = false; - cachedFilterCanvas.getContext()._context.imageSmoothingEnabled = false; - } - - sceneContext.save(); - hitContext.save(); - - sceneContext.translate(-x, -y); - hitContext.translate(-x, -y); - - // extra flag to skip on getAbsolute opacity calc - this._isUnderCache = true; - this._clearSelfAndDescendantCache(ABSOLUTE_OPACITY); - this._clearSelfAndDescendantCache(ABSOLUTE_SCALE); - - this.drawScene(cachedSceneCanvas, this); - this.drawHit(cachedHitCanvas, this); - this._isUnderCache = false; - - sceneContext.restore(); - hitContext.restore(); - - // this will draw a red border around the cached box for - // debugging purposes - if (drawBorder) { - sceneContext.save(); - sceneContext.beginPath(); - sceneContext.rect(0, 0, width, height); - sceneContext.closePath(); - sceneContext.setAttr('strokeStyle', 'red'); - sceneContext.setAttr('lineWidth', 5); - sceneContext.stroke(); - sceneContext.restore(); - } - - this._cache.set(CANVAS, { - scene: cachedSceneCanvas, - filter: cachedFilterCanvas, - hit: cachedHitCanvas, - x: x, - y: y, - }); - - this._requestDraw(); - - return this; - } - - /** - * determine if node is currently cached - * @method - * @name Konva.Node#isCached - * @returns {Boolean} - */ - isCached() { - return this._cache.has(CANVAS); - } - - abstract drawScene(canvas?: Canvas, top?: Node, bufferCanvas?: Canvas): void; - abstract drawHit(canvas?: Canvas, top?: Node): void; - /** - * Return client rectangle {x, y, width, height} of node. This rectangle also include all styling (strokes, shadows, etc). - * The purpose of the method is similar to getBoundingClientRect API of the DOM. - * @method - * @name Konva.Node#getClientRect - * @param {Object} config - * @param {Boolean} [config.skipTransform] should we apply transform to node for calculating rect? - * @param {Boolean} [config.skipShadow] should we apply shadow to the node for calculating bound box? - * @param {Boolean} [config.skipStroke] should we apply stroke to the node for calculating bound box? - * @param {Object} [config.relativeTo] calculate client rect relative to one of the parents - * @returns {Object} rect with {x, y, width, height} properties - * @example - * var rect = new Konva.Rect({ - * width : 100, - * height : 100, - * x : 50, - * y : 50, - * strokeWidth : 4, - * stroke : 'black', - * offsetX : 50, - * scaleY : 2 - * }); - * - * // get client rect without think off transformations (position, rotation, scale, offset, etc) - * rect.getClientRect({ skipTransform: true}); - * // returns { - * // x : -2, // two pixels for stroke / 2 - * // y : -2, - * // width : 104, // increased by 4 for stroke - * // height : 104 - * //} - * - * // get client rect with transformation applied - * rect.getClientRect(); - * // returns Object {x: -2, y: 46, width: 104, height: 208} - */ - getClientRect(config?: { - skipTransform?: boolean; - skipShadow?: boolean; - skipStroke?: boolean; - relativeTo?: Container; - }): { x: number; y: number; width: number; height: number } { - // abstract method - // redefine in Container and Shape - throw new Error('abstract "getClientRect" method call'); - } - _transformedRect(rect: IRect, top?: Node | null) { - const points = [ - { x: rect.x, y: rect.y }, - { x: rect.x + rect.width, y: rect.y }, - { x: rect.x + rect.width, y: rect.y + rect.height }, - { x: rect.x, y: rect.y + rect.height }, - ]; - let minX: number = Infinity, - minY: number = Infinity, - maxX: number = -Infinity, - maxY: number = -Infinity; - const trans = this.getAbsoluteTransform(top); - points.forEach(function (point) { - const transformed = trans.point(point); - if (minX === undefined) { - minX = maxX = transformed.x; - minY = maxY = transformed.y; - } - minX = Math.min(minX, transformed.x); - minY = Math.min(minY, transformed.y); - maxX = Math.max(maxX, transformed.x); - maxY = Math.max(maxY, transformed.y); - }); - return { - x: minX, - y: minY, - width: maxX - minX, - height: maxY - minY, - }; - } - _drawCachedSceneCanvas(context: Context) { - context.save(); - context._applyOpacity(this); - context._applyGlobalCompositeOperation(this); - - const canvasCache = this._getCanvasCache(); - context.translate(canvasCache.x, canvasCache.y); - - const cacheCanvas = this._getCachedSceneCanvas(); - const ratio = cacheCanvas.pixelRatio; - - context.drawImage( - cacheCanvas._canvas, - 0, - 0, - cacheCanvas.width / ratio, - cacheCanvas.height / ratio - ); - context.restore(); - } - _drawCachedHitCanvas(context: Context) { - const canvasCache = this._getCanvasCache(), - hitCanvas = canvasCache.hit; - context.save(); - context.translate(canvasCache.x, canvasCache.y); - context.drawImage( - hitCanvas._canvas, - 0, - 0, - hitCanvas.width / hitCanvas.pixelRatio, - hitCanvas.height / hitCanvas.pixelRatio - ); - context.restore(); - } - _getCachedSceneCanvas() { - let filters = this.filters(), - cachedCanvas = this._getCanvasCache(), - sceneCanvas = cachedCanvas.scene, - filterCanvas = cachedCanvas.filter, - filterContext = filterCanvas.getContext(), - len, - imageData, - n, - filter; - - if (filters) { - if (!this._filterUpToDate) { - const ratio = sceneCanvas.pixelRatio; - filterCanvas.setSize( - sceneCanvas.width / sceneCanvas.pixelRatio, - sceneCanvas.height / sceneCanvas.pixelRatio - ); - try { - len = filters.length; - filterContext.clear(); - - // copy cached canvas onto filter context - filterContext.drawImage( - sceneCanvas._canvas, - 0, - 0, - sceneCanvas.getWidth() / ratio, - sceneCanvas.getHeight() / ratio - ); - imageData = filterContext.getImageData( - 0, - 0, - filterCanvas.getWidth(), - filterCanvas.getHeight() - ); - - // apply filters to filter context - for (n = 0; n < len; n++) { - filter = filters[n]; - if (typeof filter !== 'function') { - Util.error( - 'Filter should be type of function, but got ' + - typeof filter + - ' instead. Please check correct filters' - ); - continue; - } - filter.call(this, imageData); - filterContext.putImageData(imageData, 0, 0); - } - } catch (e: any) { - Util.error( - 'Unable to apply filter. ' + - e.message + - ' This post my help you https://konvajs.org/docs/posts/Tainted_Canvas.html.' - ); - } - - this._filterUpToDate = true; - } - - return filterCanvas; - } - return sceneCanvas; - } - /** - * bind events to the node. KonvaJS supports mouseover, mousemove, - * mouseout, mouseenter, mouseleave, mousedown, mouseup, wheel, contextmenu, click, dblclick, touchstart, touchmove, - * touchend, tap, dbltap, dragstart, dragmove, and dragend events. - * Pass in a string of events delimited by a space to bind multiple events at once - * such as 'mousedown mouseup mousemove'. Include a namespace to bind an - * event by name such as 'click.foobar'. - * @method - * @name Konva.Node#on - * @param {String} evtStr e.g. 'click', 'mousedown touchstart', 'mousedown.foo touchstart.foo' - * @param {Function} handler The handler function. The first argument of that function is event object. Event object has `target` as main target of the event, `currentTarget` as current node listener and `evt` as native browser event. - * @returns {Konva.Node} - * @example - * // add click listener - * node.on('click', function() { - * console.log('you clicked me!'); - * }); - * - * // get the target node - * node.on('click', function(evt) { - * console.log(evt.target); - * }); - * - * // stop event propagation - * node.on('click', function(evt) { - * evt.cancelBubble = true; - * }); - * - * // bind multiple listeners - * node.on('click touchstart', function() { - * console.log('you clicked/touched me!'); - * }); - * - * // namespace listener - * node.on('click.foo', function() { - * console.log('you clicked/touched me!'); - * }); - * - * // get the event type - * node.on('click tap', function(evt) { - * var eventType = evt.type; - * }); - * - * // get native event object - * node.on('click tap', function(evt) { - * var nativeEvent = evt.evt; - * }); - * - * // for change events, get the old and new val - * node.on('xChange', function(evt) { - * var oldVal = evt.oldVal; - * var newVal = evt.newVal; - * }); - * - * // get event targets - * // with event delegations - * layer.on('click', 'Group', function(evt) { - * var shape = evt.target; - * var group = evt.currentTarget; - * }); - */ - on( - evtStr: K, - handler: KonvaEventListener - ) { - this._cache && this._cache.delete(ALL_LISTENERS); - - if (arguments.length === 3) { - return this._delegate.apply(this, arguments as any); - } - let events = (evtStr as string).split(SPACE), - len = events.length, - n, - event, - parts, - baseEvent, - name; - - /* - * loop through types and attach event listeners to - * each one. eg. 'click mouseover.namespace mouseout' - * will create three event bindings - */ - for (n = 0; n < len; n++) { - event = events[n]; - parts = event.split('.'); - baseEvent = parts[0]; - name = parts[1] || ''; - - // create events array if it doesn't exist - if (!this.eventListeners[baseEvent]) { - this.eventListeners[baseEvent] = []; - } - - this.eventListeners[baseEvent].push({ - name: name, - handler: handler, - }); - } - - return this; - } - /** - * remove event bindings from the node. Pass in a string of - * event types delimmited by a space to remove multiple event - * bindings at once such as 'mousedown mouseup mousemove'. - * include a namespace to remove an event binding by name - * such as 'click.foobar'. If you only give a name like '.foobar', - * all events in that namespace will be removed. - * @method - * @name Konva.Node#off - * @param {String} evtStr e.g. 'click', 'mousedown touchstart', '.foobar' - * @returns {Konva.Node} - * @example - * // remove listener - * node.off('click'); - * - * // remove multiple listeners - * node.off('click touchstart'); - * - * // remove listener by name - * node.off('click.foo'); - */ - off(evtStr?: string, callback?: Function) { - let events = (evtStr || '').split(SPACE), - len = events.length, - n, - t, - event, - parts, - baseEvent, - name; - - this._cache && this._cache.delete(ALL_LISTENERS); - - if (!evtStr) { - // remove all events - for (t in this.eventListeners) { - this._off(t); - } - } - for (n = 0; n < len; n++) { - event = events[n]; - parts = event.split('.'); - baseEvent = parts[0]; - name = parts[1]; - - if (baseEvent) { - if (this.eventListeners[baseEvent]) { - this._off(baseEvent, name, callback); - } - } else { - for (t in this.eventListeners) { - this._off(t, name, callback); - } - } - } - return this; - } - // some event aliases for third party integration like HammerJS - dispatchEvent(evt: any) { - const e = { - target: this, - type: evt.type, - evt: evt, - }; - this.fire(evt.type, e); - return this; - } - addEventListener(type: string, handler: (e: Event) => void) { - // we have to pass native event to handler - this.on(type, function (evt) { - handler.call(this, evt.evt); - }); - return this; - } - removeEventListener(type: string) { - this.off(type); - return this; - } - // like node.on - _delegate(event: string, selector: string, handler: (e: Event) => void) { - const stopNode = this; - this.on(event, function (evt) { - const targets = evt.target.findAncestors(selector, true, stopNode); - for (let i = 0; i < targets.length; i++) { - evt = Util.cloneObject(evt); - evt.currentTarget = targets[i] as any; - handler.call(targets[i], evt as any); - } - }); - } - /** - * remove a node from parent, but don't destroy. You can reuse the node later. - * @method - * @name Konva.Node#remove - * @returns {Konva.Node} - * @example - * node.remove(); - */ - remove() { - if (this.isDragging()) { - this.stopDrag(); - } - // we can have drag element but that is not dragged yet - // so just clear it - DD._dragElements.delete(this._id); - this._remove(); - return this; - } - _clearCaches() { - this._clearSelfAndDescendantCache(ABSOLUTE_TRANSFORM); - this._clearSelfAndDescendantCache(ABSOLUTE_OPACITY); - this._clearSelfAndDescendantCache(ABSOLUTE_SCALE); - this._clearSelfAndDescendantCache(STAGE); - this._clearSelfAndDescendantCache(VISIBLE); - this._clearSelfAndDescendantCache(LISTENING); - } - _remove() { - // every cached attr that is calculated via node tree - // traversal must be cleared when removing a node - this._clearCaches(); - - const parent = this.getParent(); - - if (parent && parent.children) { - parent.children.splice(this.index, 1); - parent._setChildrenIndices(); - this.parent = null; - } - } - /** - * remove and destroy a node. Kill it and delete forever! You should not reuse node after destroy(). - * If the node is a container (Group, Stage or Layer) it will destroy all children too. - * @method - * @name Konva.Node#destroy - * @example - * node.destroy(); - */ - destroy() { - this.remove(); - this.clearCache(); - return this; - } - /** - * get attr - * @method - * @name Konva.Node#getAttr - * @param {String} attr - * @returns {Integer|String|Object|Array} - * @example - * var x = node.getAttr('x'); - */ - getAttr(attr: string) { - const method = 'get' + Util._capitalize(attr); - if (Util._isFunction((this as any)[method])) { - return (this as any)[method](); - } - // otherwise get directly - return this.attrs[attr]; - } - /** - * get ancestors - * @method - * @name Konva.Node#getAncestors - * @returns {Array} - * @example - * shape.getAncestors().forEach(function(node) { - * console.log(node.getId()); - * }) - */ - getAncestors() { - let parent = this.getParent(), - ancestors: Array = []; - - while (parent) { - ancestors.push(parent); - parent = parent.getParent(); - } - - return ancestors; - } - /** - * get attrs object literal - * @method - * @name Konva.Node#getAttrs - * @returns {Object} - */ - getAttrs() { - return (this.attrs || {}) as Config & Record; - } - /** - * set multiple attrs at once using an object literal - * @method - * @name Konva.Node#setAttrs - * @param {Object} config object containing key value pairs - * @returns {Konva.Node} - * @example - * node.setAttrs({ - * x: 5, - * fill: 'red' - * }); - */ - setAttrs(config: any) { - this._batchTransformChanges(() => { - let key, method; - if (!config) { - return this; - } - for (key in config) { - if (key === CHILDREN) { - continue; - } - method = SET + Util._capitalize(key); - // use setter if available - if (Util._isFunction(this[method])) { - this[method](config[key]); - } else { - // otherwise set directly - this._setAttr(key, config[key]); - } - } - }); - - return this; - } - /** - * determine if node is listening for events by taking into account ancestors. - * - * Parent | Self | isListening - * listening | listening | - * ----------+-----------+------------ - * T | T | T - * T | F | F - * F | T | F - * F | F | F - * - * @method - * @name Konva.Node#isListening - * @returns {Boolean} - */ - isListening() { - return this._getCache(LISTENING, this._isListening); - } - _isListening(relativeTo?: Node): boolean { - const listening = this.listening(); - if (!listening) { - return false; - } - const parent = this.getParent(); - if (parent && parent !== relativeTo && this !== relativeTo) { - return parent._isListening(relativeTo); - } else { - return true; - } - } - /** - * determine if node is visible by taking into account ancestors. - * - * Parent | Self | isVisible - * visible | visible | - * ----------+-----------+------------ - * T | T | T - * T | F | F - * F | T | F - * F | F | F - * @method - * @name Konva.Node#isVisible - * @returns {Boolean} - */ - isVisible() { - return this._getCache(VISIBLE, this._isVisible); - } - _isVisible(relativeTo?: Node): boolean { - const visible = this.visible(); - if (!visible) { - return false; - } - const parent = this.getParent(); - if (parent && parent !== relativeTo && this !== relativeTo) { - return parent._isVisible(relativeTo); - } else { - return true; - } - } - shouldDrawHit(top?: Node, skipDragCheck = false) { - if (top) { - return this._isVisible(top) && this._isListening(top); - } - const layer = this.getLayer(); - - let layerUnderDrag = false; - DD._dragElements.forEach((elem) => { - if (elem.dragStatus !== 'dragging') { - return; - } else if (elem.node.nodeType === 'Stage') { - layerUnderDrag = true; - } else if (elem.node.getLayer() === layer) { - layerUnderDrag = true; - } - }); - - const dragSkip = - !skipDragCheck && - !Konva.hitOnDragEnabled && - (layerUnderDrag || Konva.isTransforming()); - return this.isListening() && this.isVisible() && !dragSkip; - } - - /** - * show node. set visible = true - * @method - * @name Konva.Node#show - * @returns {Konva.Node} - */ - show() { - this.visible(true); - return this; - } - /** - * hide node. Hidden nodes are no longer detectable - * @method - * @name Konva.Node#hide - * @returns {Konva.Node} - */ - hide() { - this.visible(false); - return this; - } - getZIndex() { - return this.index || 0; - } - /** - * get absolute z-index which takes into account sibling - * and ancestor indices - * @method - * @name Konva.Node#getAbsoluteZIndex - * @returns {Integer} - */ - getAbsoluteZIndex() { - let depth = this.getDepth(), - that = this, - index = 0, - nodes, - len, - n, - child; - - function addChildren(children) { - nodes = []; - len = children.length; - for (n = 0; n < len; n++) { - child = children[n]; - index++; - - if (child.nodeType !== SHAPE) { - nodes = nodes.concat(child.getChildren().slice()); - } - - if (child._id === that._id) { - n = len; - } - } - - if (nodes.length > 0 && nodes[0].getDepth() <= depth) { - addChildren(nodes); - } - } - const stage = this.getStage(); - if (that.nodeType !== UPPER_STAGE && stage) { - addChildren(stage.getChildren()); - } - - return index; - } - /** - * get node depth in node tree. Returns an integer. - * e.g. Stage depth will always be 0. Layers will always be 1. Groups and Shapes will always - * be >= 2 - * @method - * @name Konva.Node#getDepth - * @returns {Integer} - */ - getDepth() { - let depth = 0, - parent = this.parent; - - while (parent) { - depth++; - parent = parent.parent; - } - return depth; - } - - // sometimes we do several attributes changes - // like node.position(pos) - // for performance reasons, lets batch transform reset - // so it work faster - _batchTransformChanges(func) { - this._batchingTransformChange = true; - func(); - this._batchingTransformChange = false; - if (this._needClearTransformCache) { - this._clearCache(TRANSFORM); - this._clearSelfAndDescendantCache(ABSOLUTE_TRANSFORM); - } - this._needClearTransformCache = false; - } - - setPosition(pos: Vector2d) { - this._batchTransformChanges(() => { - this.x(pos.x); - this.y(pos.y); - }); - return this; - } - getPosition() { - return { - x: this.x(), - y: this.y(), - }; - } - /** - * get position of first pointer (like mouse or first touch) relative to local coordinates of current node - * @method - * @name Konva.Node#getRelativePointerPosition - * @returns {Konva.Node} - * @example - * - * // let's think we have a rectangle at position x = 10, y = 10 - * // now we clicked at x = 15, y = 15 of the stage - * // if you want to know position of the click, related to the rectangle you can use - * rect.getRelativePointerPosition(); - */ - getRelativePointerPosition() { - const stage = this.getStage(); - if (!stage) { - return null; - } - // get pointer (say mouse or touch) position - const pos = stage.getPointerPosition(); - if (!pos) { - return null; - } - const transform = this.getAbsoluteTransform().copy(); - // to detect relative position we need to invert transform - transform.invert(); - // now we can find relative point - return transform.point(pos); - } - /** - * get absolute position of a node. That function can be used to calculate absolute position, but relative to any ancestor - * @method - * @name Konva.Node#getAbsolutePosition - * @param {Object} Ancestor optional ancestor node - * @returns {Konva.Node} - * @example - * - * // returns absolute position relative to top-left corner of canvas - * node.getAbsolutePosition(); - * - * // calculate absolute position of node, inside stage - * // so stage transforms are ignored - * node.getAbsolutePosition(stage) - */ - getAbsolutePosition(top?: Node) { - let haveCachedParent = false; - let parent = this.parent; - while (parent) { - if (parent.isCached()) { - haveCachedParent = true; - break; - } - parent = parent.parent; - } - if (haveCachedParent && !top) { - // make fake top element - // "true" is not a node, but it will just allow skip all caching - top = true as any; - } - const absoluteMatrix = this.getAbsoluteTransform(top).getMatrix(), - absoluteTransform = new Transform(), - offset = this.offset(); - - // clone the matrix array - absoluteTransform.m = absoluteMatrix.slice(); - absoluteTransform.translate(offset.x, offset.y); - - return absoluteTransform.getTranslation(); - } - setAbsolutePosition(pos: Vector2d) { - const { x, y, ...origTrans } = this._clearTransform(); - - // don't clear translation - this.attrs.x = x; - this.attrs.y = y; - - // important, use non cached value - this._clearCache(TRANSFORM); - const it = this._getAbsoluteTransform().copy(); - - it.invert(); - it.translate(pos.x, pos.y); - pos = { - x: this.attrs.x + it.getTranslation().x, - y: this.attrs.y + it.getTranslation().y, - }; - this._setTransform(origTrans); - this.setPosition({ x: pos.x, y: pos.y }); - this._clearCache(TRANSFORM); - this._clearSelfAndDescendantCache(ABSOLUTE_TRANSFORM); - - return this; - } - _setTransform(trans) { - let key; - - for (key in trans) { - this.attrs[key] = trans[key]; - } - // this._clearCache(TRANSFORM); - // this._clearSelfAndDescendantCache(ABSOLUTE_TRANSFORM); - } - _clearTransform() { - const trans = { - x: this.x(), - y: this.y(), - rotation: this.rotation(), - scaleX: this.scaleX(), - scaleY: this.scaleY(), - offsetX: this.offsetX(), - offsetY: this.offsetY(), - skewX: this.skewX(), - skewY: this.skewY(), - }; - - this.attrs.x = 0; - this.attrs.y = 0; - this.attrs.rotation = 0; - this.attrs.scaleX = 1; - this.attrs.scaleY = 1; - this.attrs.offsetX = 0; - this.attrs.offsetY = 0; - this.attrs.skewX = 0; - this.attrs.skewY = 0; - - // return original transform - return trans; - } - /** - * move node by an amount relative to its current position - * @method - * @name Konva.Node#move - * @param {Object} change - * @param {Number} change.x - * @param {Number} change.y - * @returns {Konva.Node} - * @example - * // move node in x direction by 1px and y direction by 2px - * node.move({ - * x: 1, - * y: 2 - * }); - */ - move(change: Vector2d) { - let changeX = change.x, - changeY = change.y, - x = this.x(), - y = this.y(); - - if (changeX !== undefined) { - x += changeX; - } - - if (changeY !== undefined) { - y += changeY; - } - - this.setPosition({ x: x, y: y }); - return this; - } - _eachAncestorReverse(func, top) { - let family: Array = [], - parent = this.getParent(), - len, - n; - - // if top node is defined, and this node is top node, - // there's no need to build a family tree. just execute - // func with this because it will be the only node - if (top && top._id === this._id) { - // func(this); - return; - } - - family.unshift(this); - - while (parent && (!top || parent._id !== top._id)) { - family.unshift(parent); - parent = parent.parent; - } - - len = family.length; - for (n = 0; n < len; n++) { - func(family[n]); - } - } - /** - * rotate node by an amount in degrees relative to its current rotation - * @method - * @name Konva.Node#rotate - * @param {Number} theta - * @returns {Konva.Node} - */ - rotate(theta: number) { - this.rotation(this.rotation() + theta); - return this; - } - /** - * move node to the top of its siblings - * @method - * @name Konva.Node#moveToTop - * @returns {Boolean} - */ - moveToTop() { - if (!this.parent) { - Util.warn('Node has no parent. moveToTop function is ignored.'); - return false; - } - const index = this.index, - len = this.parent.getChildren().length; - if (index < len - 1) { - this.parent.children.splice(index, 1); - this.parent.children.push(this); - this.parent._setChildrenIndices(); - return true; - } - return false; - } - /** - * move node up - * @method - * @name Konva.Node#moveUp - * @returns {Boolean} flag is moved or not - */ - moveUp() { - if (!this.parent) { - Util.warn('Node has no parent. moveUp function is ignored.'); - return false; - } - const index = this.index, - len = this.parent.getChildren().length; - if (index < len - 1) { - this.parent.children.splice(index, 1); - this.parent.children.splice(index + 1, 0, this); - this.parent._setChildrenIndices(); - return true; - } - return false; - } - /** - * move node down - * @method - * @name Konva.Node#moveDown - * @returns {Boolean} - */ - moveDown() { - if (!this.parent) { - Util.warn('Node has no parent. moveDown function is ignored.'); - return false; - } - const index = this.index; - if (index > 0) { - this.parent.children.splice(index, 1); - this.parent.children.splice(index - 1, 0, this); - this.parent._setChildrenIndices(); - return true; - } - return false; - } - /** - * move node to the bottom of its siblings - * @method - * @name Konva.Node#moveToBottom - * @returns {Boolean} - */ - moveToBottom() { - if (!this.parent) { - Util.warn('Node has no parent. moveToBottom function is ignored.'); - return false; - } - const index = this.index; - if (index > 0) { - this.parent.children.splice(index, 1); - this.parent.children.unshift(this); - this.parent._setChildrenIndices(); - return true; - } - return false; - } - setZIndex(zIndex) { - if (!this.parent) { - Util.warn('Node has no parent. zIndex parameter is ignored.'); - return this; - } - if (zIndex < 0 || zIndex >= this.parent.children.length) { - Util.warn( - 'Unexpected value ' + - zIndex + - ' for zIndex property. zIndex is just index of a node in children of its parent. Expected value is from 0 to ' + - (this.parent.children.length - 1) + - '.' - ); - } - const index = this.index; - this.parent.children.splice(index, 1); - this.parent.children.splice(zIndex, 0, this); - this.parent._setChildrenIndices(); - return this; - } - /** - * get absolute opacity - * @method - * @name Konva.Node#getAbsoluteOpacity - * @returns {Number} - */ - getAbsoluteOpacity() { - return this._getCache(ABSOLUTE_OPACITY, this._getAbsoluteOpacity); - } - _getAbsoluteOpacity() { - let absOpacity = this.opacity(); - const parent = this.getParent(); - if (parent && !parent._isUnderCache) { - absOpacity *= parent.getAbsoluteOpacity(); - } - return absOpacity; - } - /** - * move node to another container - * @method - * @name Konva.Node#moveTo - * @param {Container} newContainer - * @returns {Konva.Node} - * @example - * // move node from current layer into layer2 - * node.moveTo(layer2); - */ - moveTo(newContainer: any) { - // do nothing if new container is already parent - if (this.getParent() !== newContainer) { - this._remove(); - newContainer.add(this); - } - return this; - } - /** - * convert Node into an object for serialization. Returns an object. - * @method - * @name Konva.Node#toObject - * @returns {Object} - */ - toObject() { - let attrs = this.getAttrs() as any, - key, - val, - getter, - defaultValue, - nonPlainObject; - - const obj: { - attrs: Config & Record; - className: string; - children?: Array; - } = { - attrs: {} as Config & Record, - className: this.getClassName(), - }; - - for (key in attrs) { - val = attrs[key]; - // if value is object and object is not plain - // like class instance, we should skip it and to not include - nonPlainObject = - Util.isObject(val) && !Util._isPlainObject(val) && !Util._isArray(val); - if (nonPlainObject) { - continue; - } - getter = typeof this[key] === 'function' && this[key]; - // remove attr value so that we can extract the default value from the getter - delete attrs[key]; - defaultValue = getter ? getter.call(this) : null; - // restore attr value - attrs[key] = val; - if (defaultValue !== val) { - (obj.attrs as any)[key] = val; - } - } - - return Util._prepareToStringify(obj) as typeof obj; - } - /** - * convert Node into a JSON string. Returns a JSON string. - * @method - * @name Konva.Node#toJSON - * @returns {String} - */ - toJSON() { - return JSON.stringify(this.toObject()); - } - /** - * get parent container - * @method - * @name Konva.Node#getParent - * @returns {Konva.Node} - */ - getParent() { - return this.parent; - } - /** - * get all ancestors (parent then parent of the parent, etc) of the node - * @method - * @name Konva.Node#findAncestors - * @param {String} selector selector for search - * @param {Boolean} [includeSelf] show we think that node is ancestro itself? - * @param {Konva.Node} [stopNode] optional node where we need to stop searching (one of ancestors) - * @returns {Array} [ancestors] - * @example - * // get one of the parent group - * var parentGroups = node.findAncestors('Group'); - */ - findAncestors( - selector: string | Function, - includeSelf?: boolean, - stopNode?: Node - ) { - const res: Array = []; - - if (includeSelf && this._isMatch(selector)) { - res.push(this); - } - let ancestor = this.parent; - while (ancestor) { - if (ancestor === stopNode) { - return res; - } - if (ancestor._isMatch(selector)) { - res.push(ancestor); - } - ancestor = ancestor.parent; - } - return res; - } - isAncestorOf(node: Node) { - return false; - } - /** - * get ancestor (parent or parent of the parent, etc) of the node that match passed selector - * @method - * @name Konva.Node#findAncestor - * @param {String} selector selector for search - * @param {Boolean} [includeSelf] show we think that node is ancestro itself? - * @param {Konva.Node} [stopNode] optional node where we need to stop searching (one of ancestors) - * @returns {Konva.Node} ancestor - * @example - * // get one of the parent group - * var group = node.findAncestors('.mygroup'); - */ - findAncestor( - selector: string | Function, - includeSelf?: boolean, - stopNode?: Container - ) { - return this.findAncestors(selector, includeSelf, stopNode)[0]; - } - // is current node match passed selector? - _isMatch(selector: string | Function) { - if (!selector) { - return false; - } - if (typeof selector === 'function') { - return selector(this); - } - let selectorArr = selector.replace(/ /g, '').split(','), - len = selectorArr.length, - n, - sel; - - for (n = 0; n < len; n++) { - sel = selectorArr[n]; - if (!Util.isValidSelector(sel)) { - Util.warn( - 'Selector "' + - sel + - '" is invalid. Allowed selectors examples are "#foo", ".bar" or "Group".' - ); - Util.warn( - 'If you have a custom shape with such className, please change it to start with upper letter like "Triangle".' - ); - Util.warn('Konva is awesome, right?'); - } - // id selector - if (sel.charAt(0) === '#') { - if (this.id() === sel.slice(1)) { - return true; - } - } else if (sel.charAt(0) === '.') { - // name selector - if (this.hasName(sel.slice(1))) { - return true; - } - } else if (this.className === sel || this.nodeType === sel) { - return true; - } - } - return false; - } - /** - * get layer ancestor - * @method - * @name Konva.Node#getLayer - * @returns {Konva.Layer} - */ - getLayer(): Layer | null { - const parent = this.getParent(); - return parent ? parent.getLayer() : null; - } - /** - * get stage ancestor - * @method - * @name Konva.Node#getStage - * @returns {Konva.Stage} - */ - getStage(): Stage | null { - return this._getCache(STAGE, this._getStage); - } - - _getStage() { - const parent = this.getParent(); - if (parent) { - return parent.getStage(); - } else { - return null; - } - } - /** - * fire event - * @method - * @name Konva.Node#fire - * @param {String} eventType event type. can be a regular event, like click, mouseover, or mouseout, or it can be a custom event, like myCustomEvent - * @param {Event} [evt] event object - * @param {Boolean} [bubble] setting the value to false, or leaving it undefined, will result in the event - * not bubbling. Setting the value to true will result in the event bubbling. - * @returns {Konva.Node} - * @example - * // manually fire click event - * node.fire('click'); - * - * // fire custom event - * node.fire('foo'); - * - * // fire custom event with custom event object - * node.fire('foo', { - * bar: 10 - * }); - * - * // fire click event that bubbles - * node.fire('click', null, true); - */ - fire(eventType: string, evt: any = {}, bubble?: boolean) { - evt.target = evt.target || this; - // bubble - if (bubble) { - this._fireAndBubble(eventType, evt); - } else { - // no bubble - this._fire(eventType, evt); - } - return this; - } - /** - * get absolute transform of the node which takes into - * account its ancestor transforms - * @method - * @name Konva.Node#getAbsoluteTransform - * @returns {Konva.Transform} - */ - getAbsoluteTransform(top?: Node | null) { - // if using an argument, we can't cache the result. - if (top) { - return this._getAbsoluteTransform(top); - } else { - // if no argument, we can cache the result - return this._getCache( - ABSOLUTE_TRANSFORM, - this._getAbsoluteTransform - ) as Transform; - } - } - _getAbsoluteTransform(top?: Node) { - let at: Transform; - // we we need position relative to an ancestor, we will iterate for all - if (top) { - at = new Transform(); - // start with stage and traverse downwards to self - this._eachAncestorReverse(function (node: Node) { - const transformsEnabled = node.transformsEnabled(); - - if (transformsEnabled === 'all') { - at.multiply(node.getTransform()); - } else if (transformsEnabled === 'position') { - at.translate(node.x() - node.offsetX(), node.y() - node.offsetY()); - } - }, top); - return at; - } else { - // try to use a cached value - at = this._cache.get(ABSOLUTE_TRANSFORM) || new Transform(); - if (this.parent) { - // transform will be cached - this.parent.getAbsoluteTransform().copyInto(at); - } else { - at.reset(); - } - const transformsEnabled = this.transformsEnabled(); - if (transformsEnabled === 'all') { - at.multiply(this.getTransform()); - } else if (transformsEnabled === 'position') { - // use "attrs" directly, because it is a bit faster - const x = this.attrs.x || 0; - const y = this.attrs.y || 0; - const offsetX = this.attrs.offsetX || 0; - const offsetY = this.attrs.offsetY || 0; - - at.translate(x - offsetX, y - offsetY); - } - at.dirty = false; - return at; - } - } - /** - * get absolute scale of the node which takes into - * account its ancestor scales - * @method - * @name Konva.Node#getAbsoluteScale - * @returns {Object} - * @example - * // get absolute scale x - * var scaleX = node.getAbsoluteScale().x; - */ - getAbsoluteScale(top?: Node) { - // do not cache this calculations, - // because it use cache transform - // this is special logic for caching with some shapes with shadow - let parent: Node | null = this; - while (parent) { - if (parent._isUnderCache) { - top = parent; - } - parent = parent.getParent(); - } - - const transform = this.getAbsoluteTransform(top); - const attrs = transform.decompose(); - - return { - x: attrs.scaleX, - y: attrs.scaleY, - }; - } - /** - * get absolute rotation of the node which takes into - * account its ancestor rotations - * @method - * @name Konva.Node#getAbsoluteRotation - * @returns {Number} - * @example - * // get absolute rotation - * var rotation = node.getAbsoluteRotation(); - */ - getAbsoluteRotation() { - // var parent: Node = this; - // var rotation = 0; - - // while (parent) { - // rotation += parent.rotation(); - // parent = parent.getParent(); - // } - // return rotation; - return this.getAbsoluteTransform().decompose().rotation; - } - /** - * get transform of the node - * @method - * @name Konva.Node#getTransform - * @returns {Konva.Transform} - */ - getTransform() { - return this._getCache(TRANSFORM, this._getTransform) as Transform; - } - _getTransform(): Transform { - const m: Transform = this._cache.get(TRANSFORM) || new Transform(); - m.reset(); - - // I was trying to use attributes directly here - // but it doesn't work for Transformer well - // because it overwrite x,y getters - const x = this.x(), - y = this.y(), - rotation = Konva.getAngle(this.rotation()), - scaleX = this.attrs.scaleX ?? 1, - scaleY = this.attrs.scaleY ?? 1, - skewX = this.attrs.skewX || 0, - skewY = this.attrs.skewY || 0, - offsetX = this.attrs.offsetX || 0, - offsetY = this.attrs.offsetY || 0; - - if (x !== 0 || y !== 0) { - m.translate(x, y); - } - if (rotation !== 0) { - m.rotate(rotation); - } - if (skewX !== 0 || skewY !== 0) { - m.skew(skewX, skewY); - } - if (scaleX !== 1 || scaleY !== 1) { - m.scale(scaleX, scaleY); - } - if (offsetX !== 0 || offsetY !== 0) { - m.translate(-1 * offsetX, -1 * offsetY); - } - - m.dirty = false; - - return m; - } - /** - * clone node. Returns a new Node instance with identical attributes. You can also override - * the node properties with an object literal, enabling you to use an existing node as a template - * for another node - * @method - * @name Konva.Node#clone - * @param {Object} obj override attrs - * @returns {Konva.Node} - * @example - * // simple clone - * var clone = node.clone(); - * - * // clone a node and override the x position - * var clone = rect.clone({ - * x: 5 - * }); - */ - clone(obj?: any) { - // instantiate new node - let attrs = Util.cloneObject(this.attrs), - key, - allListeners, - len, - n, - listener; - // apply attr overrides - for (key in obj) { - attrs[key] = obj[key]; - } - - const node = new (this.constructor)(attrs); - // copy over listeners - for (key in this.eventListeners) { - allListeners = this.eventListeners[key]; - len = allListeners.length; - for (n = 0; n < len; n++) { - listener = allListeners[n]; - /* - * don't include konva namespaced listeners because - * these are generated by the constructors - */ - if (listener.name.indexOf(KONVA) < 0) { - // if listeners array doesn't exist, then create it - if (!node.eventListeners[key]) { - node.eventListeners[key] = []; - } - node.eventListeners[key].push(listener); - } - } - } - return node; - } - _toKonvaCanvas(config) { - config = config || {}; - - const box = this.getClientRect(); - - const stage = this.getStage(), - x = config.x !== undefined ? config.x : Math.floor(box.x), - y = config.y !== undefined ? config.y : Math.floor(box.y), - pixelRatio = config.pixelRatio || 1, - canvas = new SceneCanvas({ - width: - config.width || Math.ceil(box.width) || (stage ? stage.width() : 0), - height: - config.height || - Math.ceil(box.height) || - (stage ? stage.height() : 0), - pixelRatio: pixelRatio, - }), - context = canvas.getContext(); - - const bufferCanvas = new SceneCanvas({ - // width and height already multiplied by pixelRatio - // so we need to revert that - // also increase size by x nd y offset to make sure content fits canvas - width: canvas.width / canvas.pixelRatio + Math.abs(x), - height: canvas.height / canvas.pixelRatio + Math.abs(y), - pixelRatio: canvas.pixelRatio, - }); - - if (config.imageSmoothingEnabled === false) { - context._context.imageSmoothingEnabled = false; - } - context.save(); - - if (x || y) { - context.translate(-1 * x, -1 * y); - } - - this.drawScene(canvas, undefined, bufferCanvas); - context.restore(); - - return canvas; - } - /** - * converts node into an canvas element. - * @method - * @name Konva.Node#toCanvas - * @param {Object} config - * @param {Function} config.callback function executed when the composite has completed - * @param {Number} [config.x] x position of canvas section - * @param {Number} [config.y] y position of canvas section - * @param {Number} [config.width] width of canvas section - * @param {Number} [config.height] height of canvas section - * @param {Number} [config.pixelRatio] pixelRatio of output canvas. Default is 1. - * You can use that property to increase quality of the image, for example for super hight quality exports - * or usage on retina (or similar) displays. pixelRatio will be used to multiply the size of exported image. - * If you export to 500x500 size with pixelRatio = 2, then produced image will have size 1000x1000. - * @param {Boolean} [config.imageSmoothingEnabled] set this to false if you want to disable imageSmoothing - * @example - * var canvas = node.toCanvas(); - */ - toCanvas(config?) { - return this._toKonvaCanvas(config)._canvas; - } - /** - * Creates a composite data URL (base64 string). If MIME type is not - * specified, then "image/png" will result. For "image/jpeg", specify a quality - * level as quality (range 0.0 - 1.0) - * @method - * @name Konva.Node#toDataURL - * @param {Object} config - * @param {String} [config.mimeType] can be "image/png" or "image/jpeg". - * "image/png" is the default - * @param {Number} [config.x] x position of canvas section - * @param {Number} [config.y] y position of canvas section - * @param {Number} [config.width] width of canvas section - * @param {Number} [config.height] height of canvas section - * @param {Number} [config.quality] jpeg quality. If using an "image/jpeg" mimeType, - * you can specify the quality from 0 to 1, where 0 is very poor quality and 1 - * is very high quality - * @param {Number} [config.pixelRatio] pixelRatio of output image url. Default is 1. - * You can use that property to increase quality of the image, for example for super hight quality exports - * or usage on retina (or similar) displays. pixelRatio will be used to multiply the size of exported image. - * If you export to 500x500 size with pixelRatio = 2, then produced image will have size 1000x1000. - * @param {Boolean} [config.imageSmoothingEnabled] set this to false if you want to disable imageSmoothing - * @returns {String} - */ - toDataURL(config?: { - x?: number; - y?: number; - width?: number; - height?: number; - pixelRatio?: number; - mimeType?: string; - quality?: number; - callback?: (str: string) => void; - }) { - config = config || {}; - const mimeType = config.mimeType || null, - quality = config.quality || null; - const url = this._toKonvaCanvas(config).toDataURL(mimeType, quality); - if (config.callback) { - config.callback(url); - } - return url; - } - /** - * converts node into an image. Since the toImage - * method is asynchronous, the resulting image can only be retrieved from the config callback - * or the returned Promise. toImage is most commonly used - * to cache complex drawings as an image so that they don't have to constantly be redrawn - * @method - * @name Konva.Node#toImage - * @param {Object} config - * @param {Function} [config.callback] function executed when the composite has completed - * @param {String} [config.mimeType] can be "image/png" or "image/jpeg". - * "image/png" is the default - * @param {Number} [config.x] x position of canvas section - * @param {Number} [config.y] y position of canvas section - * @param {Number} [config.width] width of canvas section - * @param {Number} [config.height] height of canvas section - * @param {Number} [config.quality] jpeg quality. If using an "image/jpeg" mimeType, - * you can specify the quality from 0 to 1, where 0 is very poor quality and 1 - * is very high quality - * @param {Number} [config.pixelRatio] pixelRatio of output image. Default is 1. - * You can use that property to increase quality of the image, for example for super hight quality exports - * or usage on retina (or similar) displays. pixelRatio will be used to multiply the size of exported image. - * If you export to 500x500 size with pixelRatio = 2, then produced image will have size 1000x1000. - * @param {Boolean} [config.imageSmoothingEnabled] set this to false if you want to disable imageSmoothing - * @return {Promise} - * @example - * var image = node.toImage({ - * callback(img) { - * // do stuff with img - * } - * }); - */ - toImage(config?: { - x?: number; - y?: number; - width?: number; - height?: number; - pixelRatio?: number; - mimeType?: string; - quality?: number; - callback?: (img: HTMLImageElement) => void; - }) { - return new Promise((resolve, reject) => { - try { - const callback = config?.callback; - if (callback) delete config.callback; - Util._urlToImage(this.toDataURL(config as any), function (img) { - resolve(img); - callback?.(img); - }); - } catch (err) { - reject(err); - } - }); - } - /** - * Converts node into a blob. Since the toBlob method is asynchronous, - * the resulting blob can only be retrieved from the config callback - * or the returned Promise. - * @method - * @name Konva.Node#toBlob - * @param {Object} config - * @param {Function} [config.callback] function executed when the composite has completed - * @param {Number} [config.x] x position of canvas section - * @param {Number} [config.y] y position of canvas section - * @param {Number} [config.width] width of canvas section - * @param {Number} [config.height] height of canvas section - * @param {Number} [config.pixelRatio] pixelRatio of output canvas. Default is 1. - * You can use that property to increase quality of the image, for example for super hight quality exports - * or usage on retina (or similar) displays. pixelRatio will be used to multiply the size of exported image. - * If you export to 500x500 size with pixelRatio = 2, then produced image will have size 1000x1000. - * @param {Boolean} [config.imageSmoothingEnabled] set this to false if you want to disable imageSmoothing - * @example - * var blob = await node.toBlob({}); - * @returns {Promise} - */ - toBlob(config?: { - x?: number; - y?: number; - width?: number; - height?: number; - pixelRatio?: number; - mimeType?: string; - quality?: number; - callback?: (blob: Blob | null) => void; - }) { - return new Promise((resolve, reject) => { - try { - const callback = config?.callback; - if (callback) delete config.callback; - this.toCanvas(config).toBlob( - (blob) => { - resolve(blob); - callback?.(blob); - }, - config?.mimeType, - config?.quality - ); - } catch (err) { - reject(err); - } - }); - } - setSize(size) { - this.width(size.width); - this.height(size.height); - return this; - } - getSize() { - return { - width: this.width(), - height: this.height(), - }; - } - /** - * get class name, which may return Stage, Layer, Group, or shape class names like Rect, Circle, Text, etc. - * @method - * @name Konva.Node#getClassName - * @returns {String} - */ - getClassName() { - return this.className || this.nodeType; - } - /** - * get the node type, which may return Stage, Layer, Group, or Shape - * @method - * @name Konva.Node#getType - * @returns {String} - */ - getType() { - return this.nodeType; - } - getDragDistance(): number { - // compare with undefined because we need to track 0 value - if (this.attrs.dragDistance !== undefined) { - return this.attrs.dragDistance; - } else if (this.parent) { - return this.parent.getDragDistance(); - } else { - return Konva.dragDistance; - } - } - _off(type, name?, callback?) { - let evtListeners = this.eventListeners[type], - i, - evtName, - handler; - - for (i = 0; i < evtListeners.length; i++) { - evtName = evtListeners[i].name; - handler = evtListeners[i].handler; - - // the following two conditions must be true in order to remove a handler: - // 1) the current event name cannot be konva unless the event name is konva - // this enables developers to force remove a konva specific listener for whatever reason - // 2) an event name is not specified, or if one is specified, it matches the current event name - if ( - (evtName !== 'konva' || name === 'konva') && - (!name || evtName === name) && - (!callback || callback === handler) - ) { - evtListeners.splice(i, 1); - if (evtListeners.length === 0) { - delete this.eventListeners[type]; - break; - } - i--; - } - } - } - _fireChangeEvent(attr, oldVal, newVal) { - this._fire(attr + CHANGE, { - oldVal: oldVal, - newVal: newVal, - }); - } - /** - * add name to node - * @method - * @name Konva.Node#addName - * @param {String} name - * @returns {Konva.Node} - * @example - * node.name('red'); - * node.addName('selected'); - * node.name(); // return 'red selected' - */ - addName(name) { - if (!this.hasName(name)) { - const oldName = this.name(); - const newName = oldName ? oldName + ' ' + name : name; - this.name(newName); - } - return this; - } - /** - * check is node has name - * @method - * @name Konva.Node#hasName - * @param {String} name - * @returns {Boolean} - * @example - * node.name('red'); - * node.hasName('red'); // return true - * node.hasName('selected'); // return false - * node.hasName(''); // return false - */ - hasName(name) { - if (!name) { - return false; - } - const fullName = this.name(); - if (!fullName) { - return false; - } - // if name is '' the "names" will be [''], so I added extra check above - const names = (fullName || '').split(/\s/g); - return names.indexOf(name) !== -1; - } - /** - * remove name from node - * @method - * @name Konva.Node#removeName - * @param {String} name - * @returns {Konva.Node} - * @example - * node.name('red selected'); - * node.removeName('selected'); - * node.hasName('selected'); // return false - * node.name(); // return 'red' - */ - removeName(name) { - const names = (this.name() || '').split(/\s/g); - const index = names.indexOf(name); - if (index !== -1) { - names.splice(index, 1); - this.name(names.join(' ')); - } - return this; - } - /** - * set attr - * @method - * @name Konva.Node#setAttr - * @param {String} attr - * @param {*} val - * @returns {Konva.Node} - * @example - * node.setAttr('x', 5); - */ - setAttr(attr, val) { - const func = this[SET + Util._capitalize(attr)]; - - if (Util._isFunction(func)) { - func.call(this, val); - } else { - // otherwise set directly - this._setAttr(attr, val); - } - return this; - } - _requestDraw() { - if (Konva.autoDrawEnabled) { - const drawNode = this.getLayer() || this.getStage(); - drawNode?.batchDraw(); - } - } - _setAttr(key, val) { - const oldVal = this.attrs[key]; - if (oldVal === val && !Util.isObject(val)) { - return; - } - if (val === undefined || val === null) { - delete this.attrs[key]; - } else { - this.attrs[key] = val; - } - if (this._shouldFireChangeEvents) { - this._fireChangeEvent(key, oldVal, val); - } - this._requestDraw(); - } - _setComponentAttr(key, component, val) { - let oldVal; - if (val !== undefined) { - oldVal = this.attrs[key]; - - if (!oldVal) { - // set value to default value using getAttr - this.attrs[key] = this.getAttr(key); - } - - this.attrs[key][component] = val; - this._fireChangeEvent(key, oldVal, val); - } - } - _fireAndBubble(eventType, evt, compareShape?) { - if (evt && this.nodeType === SHAPE) { - evt.target = this; - } - - const shouldStop = - (eventType === MOUSEENTER || eventType === MOUSELEAVE) && - ((compareShape && - (this === compareShape || - (this.isAncestorOf && this.isAncestorOf(compareShape)))) || - (this.nodeType === 'Stage' && !compareShape)); - - if (!shouldStop) { - this._fire(eventType, evt); - - // simulate event bubbling - const stopBubble = - (eventType === MOUSEENTER || eventType === MOUSELEAVE) && - compareShape && - compareShape.isAncestorOf && - compareShape.isAncestorOf(this) && - !compareShape.isAncestorOf(this.parent); - if ( - ((evt && !evt.cancelBubble) || !evt) && - this.parent && - this.parent.isListening() && - !stopBubble - ) { - if (compareShape && compareShape.parent) { - this._fireAndBubble.call(this.parent, eventType, evt, compareShape); - } else { - this._fireAndBubble.call(this.parent, eventType, evt); - } - } - } - } - - _getProtoListeners(eventType) { - const allListeners = this._cache.get(ALL_LISTENERS) ?? {}; - let events = allListeners?.[eventType]; - if (events === undefined) { - //recalculate cache - events = []; - let obj = Object.getPrototypeOf(this); - while (obj) { - const hierarchyEvents = obj.eventListeners?.[eventType] ?? []; - events.push(...hierarchyEvents); - obj = Object.getPrototypeOf(obj); - } - // update cache - allListeners[eventType] = events; - this._cache.set(ALL_LISTENERS, allListeners); - } - - return events; - } - _fire(eventType, evt) { - evt = evt || {}; - evt.currentTarget = this; - evt.type = eventType; - - const topListeners = this._getProtoListeners(eventType); - if (topListeners) { - for (var i = 0; i < topListeners.length; i++) { - topListeners[i].handler.call(this, evt); - } - } - - // it is important to iterate over self listeners without cache - // because events can be added/removed while firing - const selfListeners = this.eventListeners[eventType]; - if (selfListeners) { - for (var i = 0; i < selfListeners.length; i++) { - selfListeners[i].handler.call(this, evt); - } - } - } - /** - * draw both scene and hit graphs. If the node being drawn is the stage, all of the layers will be cleared and redrawn - * @method - * @name Konva.Node#draw - * @returns {Konva.Node} - */ - draw() { - this.drawScene(); - this.drawHit(); - return this; - } - - // drag & drop - _createDragElement(evt) { - const pointerId = evt ? evt.pointerId : undefined; - const stage = this.getStage(); - const ap = this.getAbsolutePosition(); - if (!stage) { - return; - } - const pos = - stage._getPointerById(pointerId) || - stage._changedPointerPositions[0] || - ap; - DD._dragElements.set(this._id, { - node: this, - startPointerPos: pos, - offset: { - x: pos.x - ap.x, - y: pos.y - ap.y, - }, - dragStatus: 'ready', - pointerId, - }); - } - - /** - * initiate drag and drop. - * @method - * @name Konva.Node#startDrag - */ - startDrag(evt?: any, bubbleEvent = true) { - if (!DD._dragElements.has(this._id)) { - this._createDragElement(evt); - } - - const elem = DD._dragElements.get(this._id)!; - elem.dragStatus = 'dragging'; - this.fire( - 'dragstart', - { - type: 'dragstart', - target: this, - evt: evt && evt.evt, - }, - bubbleEvent - ); - } - - _setDragPosition(evt, elem) { - // const pointers = this.getStage().getPointersPositions(); - // const pos = pointers.find(p => p.id === this._dragEventId); - const pos = this.getStage()!._getPointerById(elem.pointerId); - - if (!pos) { - return; - } - let newNodePos = { - x: pos.x - elem.offset.x, - y: pos.y - elem.offset.y, - }; - - const dbf = this.dragBoundFunc(); - if (dbf !== undefined) { - const bounded = dbf.call(this, newNodePos, evt); - if (!bounded) { - Util.warn( - 'dragBoundFunc did not return any value. That is unexpected behavior. You must return new absolute position from dragBoundFunc.' - ); - } else { - newNodePos = bounded; - } - } - - if ( - !this._lastPos || - this._lastPos.x !== newNodePos.x || - this._lastPos.y !== newNodePos.y - ) { - this.setAbsolutePosition(newNodePos); - this._requestDraw(); - } - - this._lastPos = newNodePos; - } - - /** - * stop drag and drop - * @method - * @name Konva.Node#stopDrag - */ - stopDrag(evt?) { - const elem = DD._dragElements.get(this._id); - if (elem) { - elem.dragStatus = 'stopped'; - } - DD._endDragBefore(evt); - DD._endDragAfter(evt); - } - - setDraggable(draggable) { - this._setAttr('draggable', draggable); - this._dragChange(); - } - - /** - * determine if node is currently in drag and drop mode - * @method - * @name Konva.Node#isDragging - */ - isDragging() { - const elem = DD._dragElements.get(this._id); - return elem ? elem.dragStatus === 'dragging' : false; - } - - _listenDrag() { - this._dragCleanup(); - - this.on('mousedown.konva touchstart.konva', function (evt) { - const shouldCheckButton = evt.evt['button'] !== undefined; - const canDrag = - !shouldCheckButton || Konva.dragButtons.indexOf(evt.evt['button']) >= 0; - if (!canDrag) { - return; - } - if (this.isDragging()) { - return; - } - - let hasDraggingChild = false; - DD._dragElements.forEach((elem) => { - if (this.isAncestorOf(elem.node)) { - hasDraggingChild = true; - } - }); - // nested drag can be started - // in that case we don't need to start new drag - if (!hasDraggingChild) { - this._createDragElement(evt); - } - }); - } - - _dragChange() { - if (this.attrs.draggable) { - this._listenDrag(); - } else { - // remove event listeners - this._dragCleanup(); - - /* - * force drag and drop to end - * if this node is currently in - * drag and drop mode - */ - const stage = this.getStage(); - if (!stage) { - return; - } - const dragElement = DD._dragElements.get(this._id); - const isDragging = dragElement && dragElement.dragStatus === 'dragging'; - const isReady = dragElement && dragElement.dragStatus === 'ready'; - - if (isDragging) { - this.stopDrag(); - } else if (isReady) { - DD._dragElements.delete(this._id); - } - } - } - - _dragCleanup() { - this.off('mousedown.konva'); - this.off('touchstart.konva'); - } - - /** - * determine if node (at least partially) is currently in user-visible area - * @method - * @param {(Number | Object)} margin optional margin in pixels - * @param {Number} margin.x - * @param {Number} margin.y - * @returns {Boolean} - * @name Konva.Node#isClientRectOnScreen - * @example - * // get index - * // default calculations - * var isOnScreen = node.isClientRectOnScreen() - * // increase object size (or screen size) for cases when objects close to the screen still need to be marked as "visible" - * var isOnScreen = node.isClientRectOnScreen({ x: stage.width(), y: stage.height() }) - */ - isClientRectOnScreen( - margin: { x: number; y: number } = { x: 0, y: 0 } - ): boolean { - const stage = this.getStage(); - if (!stage) { - return false; - } - const screenRect = { - x: -margin.x, - y: -margin.y, - width: stage.width() + 2 * margin.x, - height: stage.height() + 2 * margin.y, - }; - return Util.haveIntersection(screenRect, this.getClientRect()); - } - - // @ts-ignore: - preventDefault: GetSet; - - // from filters - blue: GetSet; - brightness: GetSet; - contrast: GetSet; - blurRadius: GetSet; - luminance: GetSet; - green: GetSet; - alpha: GetSet; - hue: GetSet; - kaleidoscopeAngle: GetSet; - kaleidoscopePower: GetSet; - levels: GetSet; - noise: GetSet; - pixelSize: GetSet; - red: GetSet; - saturation: GetSet; - threshold: GetSet; - value: GetSet; - - dragBoundFunc: GetSet< - (this: Node, pos: Vector2d, event: any) => Vector2d, - this - >; - draggable: GetSet; - dragDistance: GetSet; - embossBlend: GetSet; - embossDirection: GetSet; - embossStrength: GetSet; - embossWhiteLevel: GetSet; - enhance: GetSet; - filters: GetSet; - position: GetSet; - absolutePosition: GetSet; - size: GetSet<{ width: number; height: number }, this>; - - id: GetSet; - - listening: GetSet; - name: GetSet; - offset: GetSet; - offsetX: GetSet; - offsetY: GetSet; - opacity: GetSet; - - rotation: GetSet; - zIndex: GetSet; - - scale: GetSet; - scaleX: GetSet; - scaleY: GetSet; - skew: GetSet; - skewX: GetSet; - skewY: GetSet; - - to: (params: AnimTo) => void; - - transformsEnabled: GetSet; - - visible: GetSet; - width: GetSet; - height: GetSet; - - x: GetSet; - y: GetSet; - globalCompositeOperation: GetSet; - - /** - * create node with JSON string or an Object. De-serializtion does not generate custom - * shape drawing functions, images, or event handlers (this would make the - * serialized object huge). If your app uses custom shapes, images, and - * event handlers (it probably does), then you need to select the appropriate - * shapes after loading the stage and set these properties via on(), setSceneFunc(), - * and setImage() methods - * @method - * @memberof Konva.Node - * @param {String|Object} json string or object - * @param {Element} [container] optional container dom element used only if you're - * creating a stage node - */ - static create(data, container?) { - if (Util._isString(data)) { - data = JSON.parse(data); - } - return this._createNode(data, container); - } - - static _createNode(obj, container?) { - let className = Node.prototype.getClassName.call(obj), - children = obj.children, - no, - len, - n; - - // if container was passed in, add it to attrs - if (container) { - obj.attrs.container = container; - } - - if (!Konva[className]) { - Util.warn( - 'Can not find a node with class name "' + - className + - '". Fallback to "Shape".' - ); - className = 'Shape'; - } - - const Class = Konva[className]; - - no = new Class(obj.attrs); - if (children) { - len = children.length; - for (n = 0; n < len; n++) { - no.add(Node._createNode(children[n])); - } - } - - return no; - } -} - -interface AnimTo extends NodeConfig { - onFinish?: Function; - onUpdate?: Function; - duration?: number; -} - -Node.prototype.nodeType = 'Node'; -Node.prototype._attrsAffectingSize = []; - -// attache events listeners once into prototype -// that way we don't spend too much time on making an new instance -Node.prototype.eventListeners = {}; -Node.prototype.on.call(Node.prototype, TRANSFORM_CHANGE_STR, function () { - if (this._batchingTransformChange) { - this._needClearTransformCache = true; - return; - } - this._clearCache(TRANSFORM); - this._clearSelfAndDescendantCache(ABSOLUTE_TRANSFORM); -}); - -Node.prototype.on.call(Node.prototype, 'visibleChange.konva', function () { - this._clearSelfAndDescendantCache(VISIBLE); -}); -Node.prototype.on.call(Node.prototype, 'listeningChange.konva', function () { - this._clearSelfAndDescendantCache(LISTENING); -}); -Node.prototype.on.call(Node.prototype, 'opacityChange.konva', function () { - this._clearSelfAndDescendantCache(ABSOLUTE_OPACITY); -}); - -const addGetterSetter = Factory.addGetterSetter; - -/** - * get/set zIndex relative to the node's siblings who share the same parent. - * Please remember that zIndex is not absolute (like in CSS). It is relative to parent element only. - * @name Konva.Node#zIndex - * @method - * @param {Number} index - * @returns {Number} - * @example - * // get index - * var index = node.zIndex(); - * - * // set index - * node.zIndex(2); - */ -addGetterSetter(Node, 'zIndex'); - -/** - * get/set node absolute position - * @name Konva.Node#absolutePosition - * @method - * @param {Object} pos - * @param {Number} pos.x - * @param {Number} pos.y - * @returns {Object} - * @example - * // get position - * var position = node.absolutePosition(); - * - * // set position - * node.absolutePosition({ - * x: 5, - * y: 10 - * }); - */ -addGetterSetter(Node, 'absolutePosition'); - -addGetterSetter(Node, 'position'); -/** - * get/set node position relative to parent - * @name Konva.Node#position - * @method - * @param {Object} pos - * @param {Number} pos.x - * @param {Number} pos.y - * @returns {Object} - * @example - * // get position - * var position = node.position(); - * - * // set position - * node.position({ - * x: 5, - * y: 10 - * }); - */ - -addGetterSetter(Node, 'x', 0, getNumberValidator()); - -/** - * get/set x position - * @name Konva.Node#x - * @method - * @param {Number} x - * @returns {Object} - * @example - * // get x - * var x = node.x(); - * - * // set x - * node.x(5); - */ - -addGetterSetter(Node, 'y', 0, getNumberValidator()); - -/** - * get/set y position - * @name Konva.Node#y - * @method - * @param {Number} y - * @returns {Integer} - * @example - * // get y - * var y = node.y(); - * - * // set y - * node.y(5); - */ - -addGetterSetter( - Node, - 'globalCompositeOperation', - 'source-over', - getStringValidator() -); - -/** - * get/set globalCompositeOperation of a node. globalCompositeOperation DOESN'T affect hit graph of nodes. So they are still trigger to events as they have default "source-over" globalCompositeOperation. - * @name Konva.Node#globalCompositeOperation - * @method - * @param {String} type - * @returns {String} - * @example - * // get globalCompositeOperation - * var globalCompositeOperation = shape.globalCompositeOperation(); - * - * // set globalCompositeOperation - * shape.globalCompositeOperation('source-in'); - */ -addGetterSetter(Node, 'opacity', 1, getNumberValidator()); - -/** - * get/set opacity. Opacity values range from 0 to 1. - * A node with an opacity of 0 is fully transparent, and a node - * with an opacity of 1 is fully opaque - * @name Konva.Node#opacity - * @method - * @param {Object} opacity - * @returns {Number} - * @example - * // get opacity - * var opacity = node.opacity(); - * - * // set opacity - * node.opacity(0.5); - */ - -addGetterSetter(Node, 'name', '', getStringValidator()); - -/** - * get/set name. - * @name Konva.Node#name - * @method - * @param {String} name - * @returns {String} - * @example - * // get name - * var name = node.name(); - * - * // set name - * node.name('foo'); - * - * // also node may have multiple names (as css classes) - * node.name('foo bar'); - */ - -addGetterSetter(Node, 'id', '', getStringValidator()); - -/** - * get/set id. Id is global for whole page. - * @name Konva.Node#id - * @method - * @param {String} id - * @returns {String} - * @example - * // get id - * var name = node.id(); - * - * // set id - * node.id('foo'); - */ - -addGetterSetter(Node, 'rotation', 0, getNumberValidator()); - -/** - * get/set rotation in degrees - * @name Konva.Node#rotation - * @method - * @param {Number} rotation - * @returns {Number} - * @example - * // get rotation in degrees - * var rotation = node.rotation(); - * - * // set rotation in degrees - * node.rotation(45); - */ - -Factory.addComponentsGetterSetter(Node, 'scale', ['x', 'y']); - -/** - * get/set scale - * @name Konva.Node#scale - * @param {Object} scale - * @param {Number} scale.x - * @param {Number} scale.y - * @method - * @returns {Object} - * @example - * // get scale - * var scale = node.scale(); - * - * // set scale - * shape.scale({ - * x: 2, - * y: 3 - * }); - */ - -addGetterSetter(Node, 'scaleX', 1, getNumberValidator()); - -/** - * get/set scale x - * @name Konva.Node#scaleX - * @param {Number} x - * @method - * @returns {Number} - * @example - * // get scale x - * var scaleX = node.scaleX(); - * - * // set scale x - * node.scaleX(2); - */ - -addGetterSetter(Node, 'scaleY', 1, getNumberValidator()); - -/** - * get/set scale y - * @name Konva.Node#scaleY - * @param {Number} y - * @method - * @returns {Number} - * @example - * // get scale y - * var scaleY = node.scaleY(); - * - * // set scale y - * node.scaleY(2); - */ - -Factory.addComponentsGetterSetter(Node, 'skew', ['x', 'y']); - -/** - * get/set skew - * @name Konva.Node#skew - * @param {Object} skew - * @param {Number} skew.x - * @param {Number} skew.y - * @method - * @returns {Object} - * @example - * // get skew - * var skew = node.skew(); - * - * // set skew - * node.skew({ - * x: 20, - * y: 10 - * }); - */ - -addGetterSetter(Node, 'skewX', 0, getNumberValidator()); - -/** - * get/set skew x - * @name Konva.Node#skewX - * @param {Number} x - * @method - * @returns {Number} - * @example - * // get skew x - * var skewX = node.skewX(); - * - * // set skew x - * node.skewX(3); - */ - -addGetterSetter(Node, 'skewY', 0, getNumberValidator()); - -/** - * get/set skew y - * @name Konva.Node#skewY - * @param {Number} y - * @method - * @returns {Number} - * @example - * // get skew y - * var skewY = node.skewY(); - * - * // set skew y - * node.skewY(3); - */ - -Factory.addComponentsGetterSetter(Node, 'offset', ['x', 'y']); - -/** - * get/set offset. Offsets the default position and rotation point - * @method - * @param {Object} offset - * @param {Number} offset.x - * @param {Number} offset.y - * @returns {Object} - * @example - * // get offset - * var offset = node.offset(); - * - * // set offset - * node.offset({ - * x: 20, - * y: 10 - * }); - */ - -addGetterSetter(Node, 'offsetX', 0, getNumberValidator()); - -/** - * get/set offset x - * @name Konva.Node#offsetX - * @method - * @param {Number} x - * @returns {Number} - * @example - * // get offset x - * var offsetX = node.offsetX(); - * - * // set offset x - * node.offsetX(3); - */ - -addGetterSetter(Node, 'offsetY', 0, getNumberValidator()); - -/** - * get/set offset y - * @name Konva.Node#offsetY - * @method - * @param {Number} y - * @returns {Number} - * @example - * // get offset y - * var offsetY = node.offsetY(); - * - * // set offset y - * node.offsetY(3); - */ - -addGetterSetter(Node, 'dragDistance', undefined, getNumberValidator()); - -/** - * get/set drag distance - * @name Konva.Node#dragDistance - * @method - * @param {Number} distance - * @returns {Number} - * @example - * // get drag distance - * var dragDistance = node.dragDistance(); - * - * // set distance - * // node starts dragging only if pointer moved more then 3 pixels - * node.dragDistance(3); - * // or set globally - * Konva.dragDistance = 3; - */ - -addGetterSetter(Node, 'width', 0, getNumberValidator()); -/** - * get/set width - * @name Konva.Node#width - * @method - * @param {Number} width - * @returns {Number} - * @example - * // get width - * var width = node.width(); - * - * // set width - * node.width(100); - */ - -addGetterSetter(Node, 'height', 0, getNumberValidator()); -/** - * get/set height - * @name Konva.Node#height - * @method - * @param {Number} height - * @returns {Number} - * @example - * // get height - * var height = node.height(); - * - * // set height - * node.height(100); - */ - -addGetterSetter(Node, 'listening', true, getBooleanValidator()); -/** - * get/set listening attr. If you need to determine if a node is listening or not - * by taking into account its parents, use the isListening() method - * nodes with listening set to false will not be detected in hit graph - * so they will be ignored in container.getIntersection() method - * @name Konva.Node#listening - * @method - * @param {Boolean} listening Can be true, or false. The default is true. - * @returns {Boolean} - * @example - * // get listening attr - * var listening = node.listening(); - * - * // stop listening for events, remove node and all its children from hit graph - * node.listening(false); - * - * // listen to events according to the parent - * node.listening(true); - */ - -/** - * get/set preventDefault - * By default all shapes will prevent default behavior - * of a browser on a pointer move or tap. - * that will prevent native scrolling when you are trying to drag&drop a node - * but sometimes you may need to enable default actions - * in that case you can set the property to false - * @name Konva.Node#preventDefault - * @method - * @param {Boolean} preventDefault - * @returns {Boolean} - * @example - * // get preventDefault - * var shouldPrevent = shape.preventDefault(); - * - * // set preventDefault - * shape.preventDefault(false); - */ - -addGetterSetter(Node, 'preventDefault', true, getBooleanValidator()); - -addGetterSetter(Node, 'filters', undefined, function (this: Node, val) { - this._filterUpToDate = false; - return val; -}); -/** - * get/set filters. Filters are applied to cached canvases - * @name Konva.Node#filters - * @method - * @param {Array} filters array of filters - * @returns {Array} - * @example - * // get filters - * var filters = node.filters(); - * - * // set a single filter - * node.cache(); - * node.filters([Konva.Filters.Blur]); - * - * // set multiple filters - * node.cache(); - * node.filters([ - * Konva.Filters.Blur, - * Konva.Filters.Sepia, - * Konva.Filters.Invert - * ]); - */ - -addGetterSetter(Node, 'visible', true, getBooleanValidator()); -/** - * get/set visible attr. Can be true, or false. The default is true. - * If you need to determine if a node is visible or not - * by taking into account its parents, use the isVisible() method - * @name Konva.Node#visible - * @method - * @param {Boolean} visible - * @returns {Boolean} - * @example - * // get visible attr - * var visible = node.visible(); - * - * // make invisible - * node.visible(false); - * - * // make visible (according to the parent) - * node.visible(true); - * - */ - -addGetterSetter(Node, 'transformsEnabled', 'all', getStringValidator()); - -/** - * get/set transforms that are enabled. Can be "all", "none", or "position". The default - * is "all" - * @name Konva.Node#transformsEnabled - * @method - * @param {String} enabled - * @returns {String} - * @example - * // enable position transform only to improve draw performance - * node.transformsEnabled('position'); - * - * // enable all transforms - * node.transformsEnabled('all'); - */ - -/** - * get/set node size - * @name Konva.Node#size - * @method - * @param {Object} size - * @param {Number} size.width - * @param {Number} size.height - * @returns {Object} - * @example - * // get node size - * var size = node.size(); - * var width = size.width; - * var height = size.height; - * - * // set size - * node.size({ - * width: 100, - * height: 200 - * }); - */ -addGetterSetter(Node, 'size'); - -/** - * get/set drag bound function. This is used to override the default - * drag and drop position. - * @name Konva.Node#dragBoundFunc - * @method - * @param {Function} dragBoundFunc - * @returns {Function} - * @example - * // get drag bound function - * var dragBoundFunc = node.dragBoundFunc(); - * - * // create vertical drag and drop - * node.dragBoundFunc(function(pos){ - * // important pos - is absolute position of the node - * // you should return absolute position too - * return { - * x: this.absolutePosition().x, - * y: pos.y - * }; - * }); - */ -addGetterSetter(Node, 'dragBoundFunc'); - -/** - * get/set draggable flag - * @name Konva.Node#draggable - * @method - * @param {Boolean} draggable - * @returns {Boolean} - * @example - * // get draggable flag - * var draggable = node.draggable(); - * - * // enable drag and drop - * node.draggable(true); - * - * // disable drag and drop - * node.draggable(false); - */ -addGetterSetter(Node, 'draggable', false, getBooleanValidator()); - -Factory.backCompat(Node, { - rotateDeg: 'rotate', - setRotationDeg: 'setRotation', - getRotationDeg: 'getRotation', -}); diff --git a/src/PointerEvents.ts b/src/PointerEvents.ts deleted file mode 100644 index 82515948d..000000000 --- a/src/PointerEvents.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { KonvaEventObject } from './Node'; -import { Konva } from './Global'; - -import { Shape } from './Shape'; -import { Stage } from './Stage'; - -const Captures = new Map(); - -// we may use this module for capturing touch events too -// so make sure we don't do something super specific to pointer -const SUPPORT_POINTER_EVENTS = Konva._global['PointerEvent'] !== undefined; - -export interface KonvaPointerEvent extends KonvaEventObject { - pointerId: number; -} - -export function getCapturedShape(pointerId: number) { - return Captures.get(pointerId); -} - -export function createEvent(evt: PointerEvent): KonvaPointerEvent { - return { - evt, - pointerId: evt.pointerId, - } as any; -} - -export function hasPointerCapture(pointerId: number, shape: Shape | Stage) { - return Captures.get(pointerId) === shape; -} - -export function setPointerCapture(pointerId: number, shape: Shape | Stage) { - releaseCapture(pointerId); - - const stage = shape.getStage(); - if (!stage) return; - - Captures.set(pointerId, shape); - - if (SUPPORT_POINTER_EVENTS) { - shape._fire( - 'gotpointercapture', - createEvent(new PointerEvent('gotpointercapture')) - ); - } -} - -export function releaseCapture(pointerId: number, target?: Shape | Stage) { - const shape = Captures.get(pointerId); - - if (!shape) return; - - const stage = shape.getStage(); - - if (stage && stage.content) { - // stage.content.releasePointerCapture(pointerId); - } - - Captures.delete(pointerId); - - if (SUPPORT_POINTER_EVENTS) { - shape._fire( - 'lostpointercapture', - createEvent(new PointerEvent('lostpointercapture')) - ); - } -} diff --git a/src/Shape.ts b/src/Shape.ts deleted file mode 100644 index 70305ec27..000000000 --- a/src/Shape.ts +++ /dev/null @@ -1,2037 +0,0 @@ -import { Konva } from './Global'; -import { Transform, Util } from './Util'; -import { Factory } from './Factory'; -import { Node, NodeConfig } from './Node'; -import { - getNumberValidator, - getNumberOrAutoValidator, - getStringValidator, - getBooleanValidator, - getStringOrGradientValidator, -} from './Validators'; - -import { Context, SceneContext } from './Context'; -import { _registerNode } from './Global'; -import * as PointerEvents from './PointerEvents'; - -import { GetSet, Vector2d } from './types'; -import { HitCanvas, SceneCanvas } from './Canvas'; - -// hack from here https://stackoverflow.com/questions/52667959/what-is-the-purpose-of-bivariancehack-in-typescript-types/52668133#52668133 -export type ShapeConfigHandler = { - bivarianceHack(ctx: Context, shape: TTarget): void; -}['bivarianceHack']; - -export type LineJoin = 'round' | 'bevel' | 'miter'; -export type LineCap = 'butt' | 'round' | 'square'; - -export interface ShapeConfig extends NodeConfig { - fill?: string | CanvasGradient; - fillPatternImage?: HTMLImageElement; - fillPatternX?: number; - fillPatternY?: number; - fillPatternOffset?: Vector2d; - fillPatternOffsetX?: number; - fillPatternOffsetY?: number; - fillPatternScale?: Vector2d; - fillPatternScaleX?: number; - fillPatternScaleY?: number; - fillPatternRotation?: number; - fillPatternRepeat?: string; - fillLinearGradientStartPoint?: Vector2d; - fillLinearGradientStartPointX?: number; - fillLinearGradientStartPointY?: number; - fillLinearGradientEndPoint?: Vector2d; - fillLinearGradientEndPointX?: number; - fillLinearGradientEndPointY?: number; - fillLinearGradientColorStops?: Array; - fillRadialGradientStartPoint?: Vector2d; - fillRadialGradientStartPointX?: number; - fillRadialGradientStartPointY?: number; - fillRadialGradientEndPoint?: Vector2d; - fillRadialGradientEndPointX?: number; - fillRadialGradientEndPointY?: number; - fillRadialGradientStartRadius?: number; - fillRadialGradientEndRadius?: number; - fillRadialGradientColorStops?: Array; - fillEnabled?: boolean; - fillPriority?: string; - fillRule?: CanvasFillRule; - stroke?: string | CanvasGradient; - strokeWidth?: number; - fillAfterStrokeEnabled?: boolean; - hitStrokeWidth?: number | string; - strokeScaleEnabled?: boolean; - strokeHitEnabled?: boolean; - strokeEnabled?: boolean; - lineJoin?: LineJoin; - lineCap?: LineCap; - sceneFunc?: (con: Context, shape: Shape) => void; - hitFunc?: (con: Context, shape: Shape) => void; - shadowColor?: string; - shadowBlur?: number; - shadowOffset?: Vector2d; - shadowOffsetX?: number; - shadowOffsetY?: number; - shadowOpacity?: number; - shadowEnabled?: boolean; - shadowForStrokeEnabled?: boolean; - dash?: number[]; - dashOffset?: number; - dashEnabled?: boolean; - perfectDrawEnabled?: boolean; -} - -export interface ShapeGetClientRectConfig { - skipTransform?: boolean; - skipShadow?: boolean; - skipStroke?: boolean; - relativeTo?: Node; -} - -export type FillFuncOutput = - | void - | [Path2D | CanvasFillRule] - | [Path2D, CanvasFillRule]; - -const HAS_SHADOW = 'hasShadow'; -const SHADOW_RGBA = 'shadowRGBA'; -const patternImage = 'patternImage'; -const linearGradient = 'linearGradient'; -const radialGradient = 'radialGradient'; - -let dummyContext: CanvasRenderingContext2D; -function getDummyContext(): CanvasRenderingContext2D { - if (dummyContext) { - return dummyContext; - } - dummyContext = Util.createCanvasElement().getContext('2d')!; - return dummyContext; -} - -export const shapes: { [key: string]: Shape } = {}; - -// TODO: idea - use only "remove" (or destroy method) -// how? on add, check that every inner shape has reference in konva store with color -// on remove - clear that reference -// the approach is good. But what if we want to cache the shape before we add it into the stage -// what color to use for hit test? - -function _fillFunc(this: Node, context) { - const fillRule = this.attrs.fillRule; - if (fillRule) { - context.fill(fillRule); - } else { - context.fill(); - } -} -function _strokeFunc(context) { - context.stroke(); -} -function _fillFuncHit(this: Node, context) { - const fillRule = this.attrs.fillRule; - if (fillRule) { - context.fill(fillRule); - } else { - context.fill(); - } -} -function _strokeFuncHit(context) { - context.stroke(); -} - -function _clearHasShadowCache(this: Node) { - this._clearCache(HAS_SHADOW); -} - -function _clearGetShadowRGBACache(this: Node) { - this._clearCache(SHADOW_RGBA); -} - -function _clearFillPatternCache(this: Node) { - this._clearCache(patternImage); -} - -function _clearLinearGradientCache(this: Node) { - this._clearCache(linearGradient); -} - -function _clearRadialGradientCache(this: Node) { - this._clearCache(radialGradient); -} - -/** - * Shape constructor. Shapes are primitive objects such as rectangles, - * circles, text, lines, etc. - * @constructor - * @memberof Konva - * @augments Konva.Node - * @param {Object} config - * @@shapeParams - * @@nodeParams - * @example - * var customShape = new Konva.Shape({ - * x: 5, - * y: 10, - * fill: 'red', - * // a Konva.Canvas renderer is passed into the sceneFunc function - * sceneFunc (context, shape) { - * context.beginPath(); - * context.moveTo(200, 50); - * context.lineTo(420, 80); - * context.quadraticCurveTo(300, 100, 260, 170); - * context.closePath(); - * // Konva specific method - * context.fillStrokeShape(shape); - * } - *}); - */ -export class Shape< - Config extends ShapeConfig = ShapeConfig -> extends Node { - _centroid: boolean; - colorKey: string; - - _fillFunc: (ctx: Context) => FillFuncOutput; - _strokeFunc: (ctx: Context) => void; - _fillFuncHit: (ctx: Context) => void; - _strokeFuncHit: (ctx: Context) => void; - - constructor(config?: Config) { - super(config); - // set colorKey - let key: string; - - while (true) { - key = Util.getRandomColor(); - if (key && !(key in shapes)) { - break; - } - } - - this.colorKey = key; - shapes[key] = this; - } - - getContext() { - Util.warn('shape.getContext() method is deprecated. Please do not use it.'); - return this.getLayer()!.getContext(); - } - getCanvas() { - Util.warn('shape.getCanvas() method is deprecated. Please do not use it.'); - return this.getLayer()!.getCanvas(); - } - - getSceneFunc() { - return this.attrs.sceneFunc || this['_sceneFunc']; - } - - getHitFunc() { - return this.attrs.hitFunc || this['_hitFunc']; - } - /** - * returns whether or not a shadow will be rendered - * @method - * @name Konva.Shape#hasShadow - * @returns {Boolean} - */ - hasShadow() { - return this._getCache(HAS_SHADOW, this._hasShadow); - } - _hasShadow() { - return ( - this.shadowEnabled() && - this.shadowOpacity() !== 0 && - !!( - this.shadowColor() || - this.shadowBlur() || - this.shadowOffsetX() || - this.shadowOffsetY() - ) - ); - } - _getFillPattern() { - return this._getCache(patternImage, this.__getFillPattern); - } - __getFillPattern() { - if (this.fillPatternImage()) { - const ctx = getDummyContext(); - const pattern = ctx.createPattern( - this.fillPatternImage(), - this.fillPatternRepeat() || 'repeat' - ); - if (pattern && pattern.setTransform) { - const tr = new Transform(); - - tr.translate(this.fillPatternX(), this.fillPatternY()); - tr.rotate(Konva.getAngle(this.fillPatternRotation())); - tr.scale(this.fillPatternScaleX(), this.fillPatternScaleY()); - tr.translate( - -1 * this.fillPatternOffsetX(), - -1 * this.fillPatternOffsetY() - ); - - const m = tr.getMatrix(); - - const matrix = - typeof DOMMatrix === 'undefined' - ? { - a: m[0], // Horizontal scaling. A value of 1 results in no scaling. - b: m[1], // Vertical skewing. - c: m[2], // Horizontal skewing. - d: m[3], - e: m[4], // Horizontal translation (moving). - f: m[5], // Vertical translation (moving). - } - : new DOMMatrix(m); - - pattern.setTransform(matrix); - } - return pattern; - } - } - _getLinearGradient() { - return this._getCache(linearGradient, this.__getLinearGradient); - } - __getLinearGradient() { - const colorStops = this.fillLinearGradientColorStops(); - if (colorStops) { - const ctx = getDummyContext(); - - const start = this.fillLinearGradientStartPoint(); - const end = this.fillLinearGradientEndPoint(); - const grd = ctx.createLinearGradient(start.x, start.y, end.x, end.y); - - // build color stops - for (let n = 0; n < colorStops.length; n += 2) { - grd.addColorStop(colorStops[n] as number, colorStops[n + 1] as string); - } - return grd; - } - } - - _getRadialGradient() { - return this._getCache(radialGradient, this.__getRadialGradient); - } - __getRadialGradient() { - const colorStops = this.fillRadialGradientColorStops(); - if (colorStops) { - const ctx = getDummyContext(); - - const start = this.fillRadialGradientStartPoint(); - const end = this.fillRadialGradientEndPoint(); - const grd = ctx.createRadialGradient( - start.x, - start.y, - this.fillRadialGradientStartRadius(), - end.x, - end.y, - this.fillRadialGradientEndRadius() - ); - - // build color stops - for (let n = 0; n < colorStops.length; n += 2) { - grd.addColorStop(colorStops[n] as number, colorStops[n + 1] as string); - } - return grd; - } - } - getShadowRGBA() { - return this._getCache(SHADOW_RGBA, this._getShadowRGBA); - } - _getShadowRGBA() { - if (!this.hasShadow()) { - return; - } - const rgba = Util.colorToRGBA(this.shadowColor()); - if (rgba) { - return ( - 'rgba(' + - rgba.r + - ',' + - rgba.g + - ',' + - rgba.b + - ',' + - rgba.a * (this.shadowOpacity() || 1) + - ')' - ); - } - } - /** - * returns whether or not the shape will be filled - * @method - * @name Konva.Shape#hasFill - * @returns {Boolean} - */ - hasFill() { - return this._calculate( - 'hasFill', - [ - 'fillEnabled', - 'fill', - 'fillPatternImage', - 'fillLinearGradientColorStops', - 'fillRadialGradientColorStops', - ], - () => { - return ( - this.fillEnabled() && - !!( - this.fill() || - this.fillPatternImage() || - this.fillLinearGradientColorStops() || - this.fillRadialGradientColorStops() - ) - ); - } - ); - } - /** - * returns whether or not the shape will be stroked - * @method - * @name Konva.Shape#hasStroke - * @returns {Boolean} - */ - hasStroke() { - return this._calculate( - 'hasStroke', - [ - 'strokeEnabled', - 'strokeWidth', - 'stroke', - 'strokeLinearGradientColorStops', - ], - () => { - return ( - this.strokeEnabled() && - this.strokeWidth() && - !!(this.stroke() || this.strokeLinearGradientColorStops()) - // this.getStrokeRadialGradientColorStops() - ); - } - ); - // return ( - // this.strokeEnabled() && - // this.strokeWidth() && - // !!(this.stroke() || this.strokeLinearGradientColorStops()) - // // this.getStrokeRadialGradientColorStops() - // ); - } - hasHitStroke() { - const width = this.hitStrokeWidth(); - - // on auto just check by stroke - if (width === 'auto') { - return this.hasStroke(); - } - - // we should enable hit stroke if stroke is enabled - // and we have some value from width - return this.strokeEnabled() && !!width; - } - /** - * determines if point is in the shape, regardless if other shapes are on top of it. Note: because - * this method clears a temporary canvas and then redraws the shape, it performs very poorly if executed many times - * consecutively. Please use the {@link Konva.Stage#getIntersection} method if at all possible - * because it performs much better - * @method - * @name Konva.Shape#intersects - * @param {Object} point - * @param {Number} point.x - * @param {Number} point.y - * @returns {Boolean} - */ - intersects(point) { - const stage = this.getStage(); - if (!stage) { - return false; - } - const bufferHitCanvas = stage.bufferHitCanvas; - - bufferHitCanvas.getContext().clear(); - this.drawHit(bufferHitCanvas, undefined, true); - const p = bufferHitCanvas.context.getImageData( - Math.round(point.x), - Math.round(point.y), - 1, - 1 - ).data; - return p[3] > 0; - } - - destroy() { - Node.prototype.destroy.call(this); - delete shapes[this.colorKey]; - delete (this as any).colorKey; - return this; - } - // why do we need buffer canvas? - // it give better result when a shape has - // stroke with fill and with some opacity - _useBufferCanvas(forceFill?: boolean): boolean { - // image and sprite still has "fill" as image - // so they use that method with forced fill - // it probably will be simpler, then copy/paste the code - - // force skip buffer canvas - const perfectDrawEnabled = this.attrs.perfectDrawEnabled ?? true; - if (!perfectDrawEnabled) { - return false; - } - const hasFill = forceFill || this.hasFill(); - const hasStroke = this.hasStroke(); - const isTransparent = this.getAbsoluteOpacity() !== 1; - - if (hasFill && hasStroke && isTransparent) { - return true; - } - - const hasShadow = this.hasShadow(); - const strokeForShadow = this.shadowForStrokeEnabled(); - if (hasFill && hasStroke && hasShadow && strokeForShadow) { - return true; - } - return false; - } - setStrokeHitEnabled(val: number) { - Util.warn( - 'strokeHitEnabled property is deprecated. Please use hitStrokeWidth instead.' - ); - if (val) { - this.hitStrokeWidth('auto'); - } else { - this.hitStrokeWidth(0); - } - } - getStrokeHitEnabled() { - if (this.hitStrokeWidth() === 0) { - return false; - } else { - return true; - } - } - /** - * return self rectangle (x, y, width, height) of shape. - * This method are not taken into account transformation and styles. - * @method - * @name Konva.Shape#getSelfRect - * @returns {Object} rect with {x, y, width, height} properties - * @example - * - * rect.getSelfRect(); // return {x:0, y:0, width:rect.width(), height:rect.height()} - * circle.getSelfRect(); // return {x: - circle.width() / 2, y: - circle.height() / 2, width:circle.width(), height:circle.height()} - * - */ - getSelfRect() { - const size = this.size(); - return { - x: this._centroid ? -size.width / 2 : 0, - y: this._centroid ? -size.height / 2 : 0, - width: size.width, - height: size.height, - }; - } - getClientRect(config: ShapeGetClientRectConfig = {}) { - // if we have a cached parent, it will use cached transform matrix - // but we don't want to that - let hasCachedParent = false; - let parent = this.getParent(); - while (parent) { - if (parent.isCached()) { - hasCachedParent = true; - break; - } - parent = parent.getParent(); - } - const skipTransform = config.skipTransform; - - // force relative to stage if we have a cached parent - const relativeTo = - config.relativeTo || (hasCachedParent && this.getStage()) || undefined; - - const fillRect = this.getSelfRect(); - - const applyStroke = !config.skipStroke && this.hasStroke(); - const strokeWidth: number = (applyStroke && this.strokeWidth()) || 0; - - const fillAndStrokeWidth = fillRect.width + strokeWidth; - const fillAndStrokeHeight = fillRect.height + strokeWidth; - - const applyShadow = !config.skipShadow && this.hasShadow(); - const shadowOffsetX = applyShadow ? this.shadowOffsetX() : 0; - const shadowOffsetY = applyShadow ? this.shadowOffsetY() : 0; - - const preWidth = fillAndStrokeWidth + Math.abs(shadowOffsetX); - const preHeight = fillAndStrokeHeight + Math.abs(shadowOffsetY); - - const blurRadius = (applyShadow && this.shadowBlur()) || 0; - - const width = preWidth + blurRadius * 2; - const height = preHeight + blurRadius * 2; - - const rect = { - width: width, - height: height, - x: - -(strokeWidth / 2 + blurRadius) + - Math.min(shadowOffsetX, 0) + - fillRect.x, - y: - -(strokeWidth / 2 + blurRadius) + - Math.min(shadowOffsetY, 0) + - fillRect.y, - }; - if (!skipTransform) { - return this._transformedRect(rect, relativeTo); - } - return rect; - } - drawScene(can?: SceneCanvas, top?: Node, bufferCanvas?: SceneCanvas) { - // basically there are 3 drawing modes - // 1 - simple drawing when nothing is cached. - // 2 - when we are caching current - // 3 - when node is cached and we need to draw it into layer - - const layer = this.getLayer(); - let canvas = can || layer!.getCanvas(), - context = canvas.getContext() as SceneContext, - cachedCanvas = this._getCanvasCache(), - drawFunc = this.getSceneFunc(), - hasShadow = this.hasShadow(), - stage, - bufferContext; - - const skipBuffer = canvas.isCache; - const cachingSelf = top === this; - - if (!this.isVisible() && !cachingSelf) { - return this; - } - // if node is cached we just need to draw from cache - if (cachedCanvas) { - context.save(); - - const m = this.getAbsoluteTransform(top).getMatrix(); - context.transform(m[0], m[1], m[2], m[3], m[4], m[5]); - this._drawCachedSceneCanvas(context); - context.restore(); - return this; - } - - if (!drawFunc) { - return this; - } - - context.save(); - // if buffer canvas is needed - if (this._useBufferCanvas() && !skipBuffer) { - stage = this.getStage(); - const bc = bufferCanvas || stage.bufferCanvas; - bufferContext = bc.getContext(); - bufferContext.clear(); - bufferContext.save(); - bufferContext._applyLineJoin(this); - // layer might be undefined if we are using cache before adding to layer - var o = this.getAbsoluteTransform(top).getMatrix(); - bufferContext.transform(o[0], o[1], o[2], o[3], o[4], o[5]); - - drawFunc.call(this, bufferContext, this); - bufferContext.restore(); - - const ratio = bc.pixelRatio; - - if (hasShadow) { - context._applyShadow(this); - } - context._applyOpacity(this); - context._applyGlobalCompositeOperation(this); - context.drawImage(bc._canvas, 0, 0, bc.width / ratio, bc.height / ratio); - } else { - context._applyLineJoin(this); - - if (!cachingSelf) { - var o = this.getAbsoluteTransform(top).getMatrix(); - context.transform(o[0], o[1], o[2], o[3], o[4], o[5]); - context._applyOpacity(this); - context._applyGlobalCompositeOperation(this); - } - - if (hasShadow) { - context._applyShadow(this); - } - - drawFunc.call(this, context, this); - } - context.restore(); - return this; - } - drawHit(can?: HitCanvas, top?: Node, skipDragCheck = false) { - if (!this.shouldDrawHit(top, skipDragCheck)) { - return this; - } - - const layer = this.getLayer(), - canvas = can || layer!.hitCanvas, - context = canvas && canvas.getContext(), - drawFunc = this.hitFunc() || this.sceneFunc(), - cachedCanvas = this._getCanvasCache(), - cachedHitCanvas = cachedCanvas && cachedCanvas.hit; - - if (!this.colorKey) { - Util.warn( - 'Looks like your canvas has a destroyed shape in it. Do not reuse shape after you destroyed it. If you want to reuse shape you should call remove() instead of destroy()' - ); - } - - if (cachedHitCanvas) { - context.save(); - - const m = this.getAbsoluteTransform(top).getMatrix(); - context.transform(m[0], m[1], m[2], m[3], m[4], m[5]); - - this._drawCachedHitCanvas(context); - context.restore(); - return this; - } - if (!drawFunc) { - return this; - } - context.save(); - context._applyLineJoin(this); - - const selfCache = this === top; - if (!selfCache) { - const o = this.getAbsoluteTransform(top).getMatrix(); - context.transform(o[0], o[1], o[2], o[3], o[4], o[5]); - } - drawFunc.call(this, context, this); - context.restore(); - return this; - } - /** - * draw hit graph using the cached scene canvas - * @method - * @name Konva.Shape#drawHitFromCache - * @param {Integer} alphaThreshold alpha channel threshold that determines whether or not - * a pixel should be drawn onto the hit graph. Must be a value between 0 and 255. - * The default is 0 - * @returns {Konva.Shape} - * @example - * shape.cache(); - * shape.drawHitFromCache(); - */ - drawHitFromCache(alphaThreshold = 0) { - const cachedCanvas = this._getCanvasCache(), - sceneCanvas = this._getCachedSceneCanvas(), - hitCanvas = cachedCanvas.hit, - hitContext = hitCanvas.getContext(), - hitWidth = hitCanvas.getWidth(), - hitHeight = hitCanvas.getHeight(); - - hitContext.clear(); - hitContext.drawImage(sceneCanvas._canvas, 0, 0, hitWidth, hitHeight); - - try { - const hitImageData = hitContext.getImageData(0, 0, hitWidth, hitHeight); - const hitData = hitImageData.data; - const len = hitData.length; - const rgbColorKey = Util._hexToRgb(this.colorKey); - - // replace non transparent pixels with color key - for (let i = 0; i < len; i += 4) { - const alpha = hitData[i + 3]; - if (alpha > alphaThreshold) { - hitData[i] = rgbColorKey.r; - hitData[i + 1] = rgbColorKey.g; - hitData[i + 2] = rgbColorKey.b; - hitData[i + 3] = 255; - } else { - hitData[i + 3] = 0; - } - } - hitContext.putImageData(hitImageData, 0, 0); - } catch (e: any) { - Util.error( - 'Unable to draw hit graph from cached scene canvas. ' + e.message - ); - } - - return this; - } - - hasPointerCapture(pointerId: number): boolean { - return PointerEvents.hasPointerCapture(pointerId, this); - } - - setPointerCapture(pointerId: number) { - PointerEvents.setPointerCapture(pointerId, this); - } - - releaseCapture(pointerId: number) { - PointerEvents.releaseCapture(pointerId, this); - } - - draggable: GetSet; - embossBlend: GetSet; - - dash: GetSet; - dashEnabled: GetSet; - dashOffset: GetSet; - fill: GetSet; - fillEnabled: GetSet; - fillLinearGradientColorStops: GetSet, this>; - fillLinearGradientStartPoint: GetSet; - fillLinearGradientStartPointX: GetSet; - fillLinearGradientStartPointY: GetSet; - fillLinearGradientEndPoint: GetSet; - fillLinearGradientEndPointX: GetSet; - fillLinearGradientEndPointY: GetSet; - fillLinearRadialStartPoint: GetSet; - fillLinearRadialStartPointX: GetSet; - fillLinearRadialStartPointY: GetSet; - fillLinearRadialEndPoint: GetSet; - fillLinearRadialEndPointX: GetSet; - fillLinearRadialEndPointY: GetSet; - fillPatternImage: GetSet; - fillRadialGradientStartRadius: GetSet; - fillRadialGradientEndRadius: GetSet; - fillRadialGradientColorStops: GetSet, this>; - fillRadialGradientStartPoint: GetSet; - fillRadialGradientStartPointX: GetSet; - fillRadialGradientStartPointY: GetSet; - fillRadialGradientEndPoint: GetSet; - fillRadialGradientEndPointX: GetSet; - fillRadialGradientEndPointY: GetSet; - fillPatternOffset: GetSet; - fillPatternOffsetX: GetSet; - fillPatternOffsetY: GetSet; - fillPatternRepeat: GetSet; - fillPatternRotation: GetSet; - fillPatternScale: GetSet; - fillPatternScaleX: GetSet; - fillPatternScaleY: GetSet; - fillPatternX: GetSet; - fillPatternY: GetSet; - fillPriority: GetSet; - hitFunc: GetSet, this>; - lineCap: GetSet; - lineJoin: GetSet; - perfectDrawEnabled: GetSet; - sceneFunc: GetSet, this>; - shadowColor: GetSet; - shadowEnabled: GetSet; - shadowForStrokeEnabled: GetSet; - shadowOffset: GetSet; - shadowOffsetX: GetSet; - shadowOffsetY: GetSet; - shadowOpacity: GetSet; - shadowBlur: GetSet; - stroke: GetSet; - strokeEnabled: GetSet; - fillAfterStrokeEnabled: GetSet; - strokeScaleEnabled: GetSet; - strokeHitEnabled: GetSet; - strokeWidth: GetSet; - hitStrokeWidth: GetSet; - strokeLinearGradientStartPoint: GetSet; - strokeLinearGradientEndPoint: GetSet; - strokeLinearGradientColorStops: GetSet, this>; - strokeLinearGradientStartPointX: GetSet; - strokeLinearGradientStartPointY: GetSet; - strokeLinearGradientEndPointX: GetSet; - strokeLinearGradientEndPointY: GetSet; - fillRule: GetSet; -} - -Shape.prototype._fillFunc = _fillFunc; -Shape.prototype._strokeFunc = _strokeFunc; -Shape.prototype._fillFuncHit = _fillFuncHit; -Shape.prototype._strokeFuncHit = _strokeFuncHit; - -Shape.prototype._centroid = false; -Shape.prototype.nodeType = 'Shape'; -_registerNode(Shape); - -Shape.prototype.eventListeners = {}; -Shape.prototype.on.call( - Shape.prototype, - 'shadowColorChange.konva shadowBlurChange.konva shadowOffsetChange.konva shadowOpacityChange.konva shadowEnabledChange.konva', - _clearHasShadowCache -); - -Shape.prototype.on.call( - Shape.prototype, - 'shadowColorChange.konva shadowOpacityChange.konva shadowEnabledChange.konva', - _clearGetShadowRGBACache -); - -Shape.prototype.on.call( - Shape.prototype, - 'fillPriorityChange.konva fillPatternImageChange.konva fillPatternRepeatChange.konva fillPatternScaleXChange.konva fillPatternScaleYChange.konva fillPatternOffsetXChange.konva fillPatternOffsetYChange.konva fillPatternXChange.konva fillPatternYChange.konva fillPatternRotationChange.konva', - _clearFillPatternCache -); - -Shape.prototype.on.call( - Shape.prototype, - 'fillPriorityChange.konva fillLinearGradientColorStopsChange.konva fillLinearGradientStartPointXChange.konva fillLinearGradientStartPointYChange.konva fillLinearGradientEndPointXChange.konva fillLinearGradientEndPointYChange.konva', - _clearLinearGradientCache -); - -Shape.prototype.on.call( - Shape.prototype, - 'fillPriorityChange.konva fillRadialGradientColorStopsChange.konva fillRadialGradientStartPointXChange.konva fillRadialGradientStartPointYChange.konva fillRadialGradientEndPointXChange.konva fillRadialGradientEndPointYChange.konva fillRadialGradientStartRadiusChange.konva fillRadialGradientEndRadiusChange.konva', - _clearRadialGradientCache -); - -// add getters and setters -Factory.addGetterSetter( - Shape, - 'stroke', - undefined, - getStringOrGradientValidator() -); - -/** - * get/set stroke color - * @name Konva.Shape#stroke - * @method - * @param {String} color - * @returns {String} - * @example - * // get stroke color - * var stroke = shape.stroke(); - * - * // set stroke color with color string - * shape.stroke('green'); - * - * // set stroke color with hex - * shape.stroke('#00ff00'); - * - * // set stroke color with rgb - * shape.stroke('rgb(0,255,0)'); - * - * // set stroke color with rgba and make it 50% opaque - * shape.stroke('rgba(0,255,0,0.5'); - */ - -Factory.addGetterSetter(Shape, 'strokeWidth', 2, getNumberValidator()); - -/** - * get/set stroke width - * @name Konva.Shape#strokeWidth - * @method - * @param {Number} strokeWidth - * @returns {Number} - * @example - * // get stroke width - * var strokeWidth = shape.strokeWidth(); - * - * // set stroke width - * shape.strokeWidth(10); - */ - -Factory.addGetterSetter(Shape, 'fillAfterStrokeEnabled', false); - -/** - * get/set fillAfterStrokeEnabled property. By default Konva is drawing filling first, then stroke on top of the fill. - * In rare situations you may want a different behavior. When you have a stroke first then fill on top of it. - * Especially useful for Text objects. - * Default is false. - * @name Konva.Shape#fillAfterStrokeEnabled - * @method - * @param {Boolean} fillAfterStrokeEnabled - * @returns {Boolean} - * @example - * // get stroke width - * var fillAfterStrokeEnabled = shape.fillAfterStrokeEnabled(); - * - * // set stroke width - * shape.fillAfterStrokeEnabled(true); - */ - -Factory.addGetterSetter( - Shape, - 'hitStrokeWidth', - 'auto', - getNumberOrAutoValidator() -); - -/** - * get/set stroke width for hit detection. Default value is "auto", it means it will be equals to strokeWidth - * @name Konva.Shape#hitStrokeWidth - * @method - * @param {Number} hitStrokeWidth - * @returns {Number} - * @example - * // get stroke width - * var hitStrokeWidth = shape.hitStrokeWidth(); - * - * // set hit stroke width - * shape.hitStrokeWidth(20); - * // set hit stroke width always equals to scene stroke width - * shape.hitStrokeWidth('auto'); - */ - -Factory.addGetterSetter(Shape, 'strokeHitEnabled', true, getBooleanValidator()); - -/** - * **deprecated, use hitStrokeWidth instead!** get/set strokeHitEnabled property. Useful for performance optimization. - * You may set `shape.strokeHitEnabled(false)`. In this case stroke will be no draw on hit canvas, so hit area - * of shape will be decreased (by lineWidth / 2). Remember that non closed line with `strokeHitEnabled = false` - * will be not drawn on hit canvas, that is mean line will no trigger pointer events (like mouseover) - * Default value is true. - * @name Konva.Shape#strokeHitEnabled - * @method - * @param {Boolean} strokeHitEnabled - * @returns {Boolean} - * @example - * // get strokeHitEnabled - * var strokeHitEnabled = shape.strokeHitEnabled(); - * - * // set strokeHitEnabled - * shape.strokeHitEnabled(); - */ - -Factory.addGetterSetter( - Shape, - 'perfectDrawEnabled', - true, - getBooleanValidator() -); - -/** - * get/set perfectDrawEnabled. If a shape has fill, stroke and opacity you may set `perfectDrawEnabled` to false to improve performance. - * See http://konvajs.org/docs/performance/Disable_Perfect_Draw.html for more information. - * Default value is true - * @name Konva.Shape#perfectDrawEnabled - * @method - * @param {Boolean} perfectDrawEnabled - * @returns {Boolean} - * @example - * // get perfectDrawEnabled - * var perfectDrawEnabled = shape.perfectDrawEnabled(); - * - * // set perfectDrawEnabled - * shape.perfectDrawEnabled(); - */ - -Factory.addGetterSetter( - Shape, - 'shadowForStrokeEnabled', - true, - getBooleanValidator() -); - -/** - * get/set shadowForStrokeEnabled. Useful for performance optimization. - * You may set `shape.shadowForStrokeEnabled(false)`. In this case stroke will no effect shadow. - * Remember if you set `shadowForStrokeEnabled = false` for non closed line - that line will have no shadow!. - * Default value is true - * @name Konva.Shape#shadowForStrokeEnabled - * @method - * @param {Boolean} shadowForStrokeEnabled - * @returns {Boolean} - * @example - * // get shadowForStrokeEnabled - * var shadowForStrokeEnabled = shape.shadowForStrokeEnabled(); - * - * // set shadowForStrokeEnabled - * shape.shadowForStrokeEnabled(); - */ - -Factory.addGetterSetter(Shape, 'lineJoin'); - -/** - * get/set line join. Can be miter, round, or bevel. The - * default is miter - * @name Konva.Shape#lineJoin - * @method - * @param {String} lineJoin - * @returns {String} - * @example - * // get line join - * var lineJoin = shape.lineJoin(); - * - * // set line join - * shape.lineJoin('round'); - */ - -Factory.addGetterSetter(Shape, 'lineCap'); - -/** - * get/set line cap. Can be butt, round, or square - * @name Konva.Shape#lineCap - * @method - * @param {String} lineCap - * @returns {String} - * @example - * // get line cap - * var lineCap = shape.lineCap(); - * - * // set line cap - * shape.lineCap('round'); - */ - -Factory.addGetterSetter(Shape, 'sceneFunc'); - -/** - * get/set scene draw function. That function is used to draw the shape on a canvas. - * Also that function will be used to draw hit area of the shape, in case if hitFunc is not defined. - * @name Konva.Shape#sceneFunc - * @method - * @param {Function} drawFunc drawing function - * @returns {Function} - * @example - * // get scene draw function - * var sceneFunc = shape.sceneFunc(); - * - * // set scene draw function - * shape.sceneFunc(function(context, shape) { - * context.beginPath(); - * context.rect(0, 0, shape.width(), shape.height()); - * context.closePath(); - * // important Konva method that fill and stroke shape from its properties - * // like stroke and fill - * context.fillStrokeShape(shape); - * }); - */ - -Factory.addGetterSetter(Shape, 'hitFunc'); - -/** - * get/set hit draw function. That function is used to draw custom hit area of a shape. - * @name Konva.Shape#hitFunc - * @method - * @param {Function} drawFunc drawing function - * @returns {Function} - * @example - * // get hit draw function - * var hitFunc = shape.hitFunc(); - * - * // set hit draw function - * shape.hitFunc(function(context) { - * context.beginPath(); - * context.rect(0, 0, shape.width(), shape.height()); - * context.closePath(); - * // important Konva method that fill and stroke shape from its properties - * context.fillStrokeShape(shape); - * }); - */ - -Factory.addGetterSetter(Shape, 'dash'); - -/** - * get/set dash array for stroke. - * @name Konva.Shape#dash - * @method - * @param {Array} dash - * @returns {Array} - * @example - * // apply dashed stroke that is 10px long and 5 pixels apart - * line.dash([10, 5]); - * // apply dashed stroke that is made up of alternating dashed - * // lines that are 10px long and 20px apart, and dots that have - * // a radius of 5px and are 20px apart - * line.dash([10, 20, 0.001, 20]); - */ - -Factory.addGetterSetter(Shape, 'dashOffset', 0, getNumberValidator()); - -/** - * get/set dash offset for stroke. - * @name Konva.Shape#dash - * @method - * @param {Number} dash offset - * @returns {Number} - * @example - * // apply dashed stroke that is 10px long and 5 pixels apart with an offset of 5px - * line.dash([10, 5]); - * line.dashOffset(5); - */ - -Factory.addGetterSetter(Shape, 'shadowColor', undefined, getStringValidator()); - -/** - * get/set shadow color - * @name Konva.Shape#shadowColor - * @method - * @param {String} color - * @returns {String} - * @example - * // get shadow color - * var shadow = shape.shadowColor(); - * - * // set shadow color with color string - * shape.shadowColor('green'); - * - * // set shadow color with hex - * shape.shadowColor('#00ff00'); - * - * // set shadow color with rgb - * shape.shadowColor('rgb(0,255,0)'); - * - * // set shadow color with rgba and make it 50% opaque - * shape.shadowColor('rgba(0,255,0,0.5'); - */ - -Factory.addGetterSetter(Shape, 'shadowBlur', 0, getNumberValidator()); - -/** - * get/set shadow blur - * @name Konva.Shape#shadowBlur - * @method - * @param {Number} blur - * @returns {Number} - * @example - * // get shadow blur - * var shadowBlur = shape.shadowBlur(); - * - * // set shadow blur - * shape.shadowBlur(10); - */ - -Factory.addGetterSetter(Shape, 'shadowOpacity', 1, getNumberValidator()); - -/** - * get/set shadow opacity. must be a value between 0 and 1 - * @name Konva.Shape#shadowOpacity - * @method - * @param {Number} opacity - * @returns {Number} - * @example - * // get shadow opacity - * var shadowOpacity = shape.shadowOpacity(); - * - * // set shadow opacity - * shape.shadowOpacity(0.5); - */ - -Factory.addComponentsGetterSetter(Shape, 'shadowOffset', ['x', 'y']); - -/** - * get/set shadow offset - * @name Konva.Shape#shadowOffset - * @method - * @param {Object} offset - * @param {Number} offset.x - * @param {Number} offset.y - * @returns {Object} - * @example - * // get shadow offset - * var shadowOffset = shape.shadowOffset(); - * - * // set shadow offset - * shape.shadowOffset({ - * x: 20, - * y: 10 - * }); - */ - -Factory.addGetterSetter(Shape, 'shadowOffsetX', 0, getNumberValidator()); - -/** - * get/set shadow offset x - * @name Konva.Shape#shadowOffsetX - * @method - * @param {Number} x - * @returns {Number} - * @example - * // get shadow offset x - * var shadowOffsetX = shape.shadowOffsetX(); - * - * // set shadow offset x - * shape.shadowOffsetX(5); - */ - -Factory.addGetterSetter(Shape, 'shadowOffsetY', 0, getNumberValidator()); - -/** - * get/set shadow offset y - * @name Konva.Shape#shadowOffsetY - * @method - * @param {Number} y - * @returns {Number} - * @example - * // get shadow offset y - * var shadowOffsetY = shape.shadowOffsetY(); - * - * // set shadow offset y - * shape.shadowOffsetY(5); - */ - -Factory.addGetterSetter(Shape, 'fillPatternImage'); - -/** - * get/set fill pattern image - * @name Konva.Shape#fillPatternImage - * @method - * @param {Image} image object - * @returns {Image} - * @example - * // get fill pattern image - * var fillPatternImage = shape.fillPatternImage(); - * - * // set fill pattern image - * var imageObj = new Image(); - * imageObj.onload = function() { - * shape.fillPatternImage(imageObj); - * }; - * imageObj.src = 'path/to/image/jpg'; - */ - -Factory.addGetterSetter( - Shape, - 'fill', - undefined, - getStringOrGradientValidator() -); - -/** - * get/set fill color - * @name Konva.Shape#fill - * @method - * @param {String} color - * @returns {String} - * @example - * // get fill color - * var fill = shape.fill(); - * - * // set fill color with color string - * shape.fill('green'); - * - * // set fill color with hex - * shape.fill('#00ff00'); - * - * // set fill color with rgb - * shape.fill('rgb(0,255,0)'); - * - * // set fill color with rgba and make it 50% opaque - * shape.fill('rgba(0,255,0,0.5'); - * - * // shape without fill - * shape.fill(null); - */ - -Factory.addGetterSetter(Shape, 'fillPatternX', 0, getNumberValidator()); - -/** - * get/set fill pattern x - * @name Konva.Shape#fillPatternX - * @method - * @param {Number} x - * @returns {Number} - * @example - * // get fill pattern x - * var fillPatternX = shape.fillPatternX(); - * // set fill pattern x - * shape.fillPatternX(20); - */ - -Factory.addGetterSetter(Shape, 'fillPatternY', 0, getNumberValidator()); - -/** - * get/set fill pattern y - * @name Konva.Shape#fillPatternY - * @method - * @param {Number} y - * @returns {Number} - * @example - * // get fill pattern y - * var fillPatternY = shape.fillPatternY(); - * // set fill pattern y - * shape.fillPatternY(20); - */ - -Factory.addGetterSetter(Shape, 'fillLinearGradientColorStops'); - -/** - * get/set fill linear gradient color stops - * @name Konva.Shape#fillLinearGradientColorStops - * @method - * @param {Array} colorStops - * @returns {Array} colorStops - * @example - * // get fill linear gradient color stops - * var colorStops = shape.fillLinearGradientColorStops(); - * - * // create a linear gradient that starts with red, changes to blue - * // halfway through, and then changes to green - * shape.fillLinearGradientColorStops(0, 'red', 0.5, 'blue', 1, 'green'); - */ - -Factory.addGetterSetter(Shape, 'strokeLinearGradientColorStops'); - -/** - * get/set stroke linear gradient color stops - * @name Konva.Shape#strokeLinearGradientColorStops - * @method - * @param {Array} colorStops - * @returns {Array} colorStops - * @example - * // get stroke linear gradient color stops - * var colorStops = shape.strokeLinearGradientColorStops(); - * - * // create a linear gradient that starts with red, changes to blue - * // halfway through, and then changes to green - * shape.strokeLinearGradientColorStops([0, 'red', 0.5, 'blue', 1, 'green']); - */ - -Factory.addGetterSetter(Shape, 'fillRadialGradientStartRadius', 0); - -/** - * get/set fill radial gradient start radius - * @name Konva.Shape#fillRadialGradientStartRadius - * @method - * @param {Number} radius - * @returns {Number} - * @example - * // get radial gradient start radius - * var startRadius = shape.fillRadialGradientStartRadius(); - * - * // set radial gradient start radius - * shape.fillRadialGradientStartRadius(0); - */ - -Factory.addGetterSetter(Shape, 'fillRadialGradientEndRadius', 0); - -/** - * get/set fill radial gradient end radius - * @name Konva.Shape#fillRadialGradientEndRadius - * @method - * @param {Number} radius - * @returns {Number} - * @example - * // get radial gradient end radius - * var endRadius = shape.fillRadialGradientEndRadius(); - * - * // set radial gradient end radius - * shape.fillRadialGradientEndRadius(100); - */ - -Factory.addGetterSetter(Shape, 'fillRadialGradientColorStops'); - -/** - * get/set fill radial gradient color stops - * @name Konva.Shape#fillRadialGradientColorStops - * @method - * @param {Number} colorStops - * @returns {Array} - * @example - * // get fill radial gradient color stops - * var colorStops = shape.fillRadialGradientColorStops(); - * - * // create a radial gradient that starts with red, changes to blue - * // halfway through, and then changes to green - * shape.fillRadialGradientColorStops(0, 'red', 0.5, 'blue', 1, 'green'); - */ - -Factory.addGetterSetter(Shape, 'fillPatternRepeat', 'repeat'); - -/** - * get/set fill pattern repeat. Can be 'repeat', 'repeat-x', 'repeat-y', or 'no-repeat'. The default is 'repeat' - * @name Konva.Shape#fillPatternRepeat - * @method - * @param {String} repeat - * @returns {String} - * @example - * // get fill pattern repeat - * var repeat = shape.fillPatternRepeat(); - * - * // repeat pattern in x direction only - * shape.fillPatternRepeat('repeat-x'); - * - * // do not repeat the pattern - * shape.fillPatternRepeat('no-repeat'); - */ - -Factory.addGetterSetter(Shape, 'fillEnabled', true); - -/** - * get/set fill enabled flag - * @name Konva.Shape#fillEnabled - * @method - * @param {Boolean} enabled - * @returns {Boolean} - * @example - * // get fill enabled flag - * var fillEnabled = shape.fillEnabled(); - * - * // disable fill - * shape.fillEnabled(false); - * - * // enable fill - * shape.fillEnabled(true); - */ - -Factory.addGetterSetter(Shape, 'strokeEnabled', true); - -/** - * get/set stroke enabled flag - * @name Konva.Shape#strokeEnabled - * @method - * @param {Boolean} enabled - * @returns {Boolean} - * @example - * // get stroke enabled flag - * var strokeEnabled = shape.strokeEnabled(); - * - * // disable stroke - * shape.strokeEnabled(false); - * - * // enable stroke - * shape.strokeEnabled(true); - */ - -Factory.addGetterSetter(Shape, 'shadowEnabled', true); - -/** - * get/set shadow enabled flag - * @name Konva.Shape#shadowEnabled - * @method - * @param {Boolean} enabled - * @returns {Boolean} - * @example - * // get shadow enabled flag - * var shadowEnabled = shape.shadowEnabled(); - * - * // disable shadow - * shape.shadowEnabled(false); - * - * // enable shadow - * shape.shadowEnabled(true); - */ - -Factory.addGetterSetter(Shape, 'dashEnabled', true); - -/** - * get/set dash enabled flag - * @name Konva.Shape#dashEnabled - * @method - * @param {Boolean} enabled - * @returns {Boolean} - * @example - * // get dash enabled flag - * var dashEnabled = shape.dashEnabled(); - * - * // disable dash - * shape.dashEnabled(false); - * - * // enable dash - * shape.dashEnabled(true); - */ - -Factory.addGetterSetter(Shape, 'strokeScaleEnabled', true); - -/** - * get/set strokeScale enabled flag - * @name Konva.Shape#strokeScaleEnabled - * @method - * @param {Boolean} enabled - * @returns {Boolean} - * @example - * // get stroke scale enabled flag - * var strokeScaleEnabled = shape.strokeScaleEnabled(); - * - * // disable stroke scale - * shape.strokeScaleEnabled(false); - * - * // enable stroke scale - * shape.strokeScaleEnabled(true); - */ - -Factory.addGetterSetter(Shape, 'fillPriority', 'color'); - -/** - * get/set fill priority. can be color, pattern, linear-gradient, or radial-gradient. The default is color. - * This is handy if you want to toggle between different fill types. - * @name Konva.Shape#fillPriority - * @method - * @param {String} priority - * @returns {String} - * @example - * // get fill priority - * var fillPriority = shape.fillPriority(); - * - * // set fill priority - * shape.fillPriority('linear-gradient'); - */ - -Factory.addComponentsGetterSetter(Shape, 'fillPatternOffset', ['x', 'y']); - -/** - * get/set fill pattern offset - * @name Konva.Shape#fillPatternOffset - * @method - * @param {Object} offset - * @param {Number} offset.x - * @param {Number} offset.y - * @returns {Object} - * @example - * // get fill pattern offset - * var patternOffset = shape.fillPatternOffset(); - * - * // set fill pattern offset - * shape.fillPatternOffset({ - * x: 20, - * y: 10 - * }); - */ - -Factory.addGetterSetter(Shape, 'fillPatternOffsetX', 0, getNumberValidator()); - -/** - * get/set fill pattern offset x - * @name Konva.Shape#fillPatternOffsetX - * @method - * @param {Number} x - * @returns {Number} - * @example - * // get fill pattern offset x - * var patternOffsetX = shape.fillPatternOffsetX(); - * - * // set fill pattern offset x - * shape.fillPatternOffsetX(20); - */ - -Factory.addGetterSetter(Shape, 'fillPatternOffsetY', 0, getNumberValidator()); - -/** - * get/set fill pattern offset y - * @name Konva.Shape#fillPatternOffsetY - * @method - * @param {Number} y - * @returns {Number} - * @example - * // get fill pattern offset y - * var patternOffsetY = shape.fillPatternOffsetY(); - * - * // set fill pattern offset y - * shape.fillPatternOffsetY(10); - */ - -Factory.addComponentsGetterSetter(Shape, 'fillPatternScale', ['x', 'y']); - -/** - * get/set fill pattern scale - * @name Konva.Shape#fillPatternScale - * @method - * @param {Object} scale - * @param {Number} scale.x - * @param {Number} scale.y - * @returns {Object} - * @example - * // get fill pattern scale - * var patternScale = shape.fillPatternScale(); - * - * // set fill pattern scale - * shape.fillPatternScale({ - * x: 2, - * y: 2 - * }); - */ - -Factory.addGetterSetter(Shape, 'fillPatternScaleX', 1, getNumberValidator()); - -/** - * get/set fill pattern scale x - * @name Konva.Shape#fillPatternScaleX - * @method - * @param {Number} x - * @returns {Number} - * @example - * // get fill pattern scale x - * var patternScaleX = shape.fillPatternScaleX(); - * - * // set fill pattern scale x - * shape.fillPatternScaleX(2); - */ - -Factory.addGetterSetter(Shape, 'fillPatternScaleY', 1, getNumberValidator()); - -/** - * get/set fill pattern scale y - * @name Konva.Shape#fillPatternScaleY - * @method - * @param {Number} y - * @returns {Number} - * @example - * // get fill pattern scale y - * var patternScaleY = shape.fillPatternScaleY(); - * - * // set fill pattern scale y - * shape.fillPatternScaleY(2); - */ - -Factory.addComponentsGetterSetter(Shape, 'fillLinearGradientStartPoint', [ - 'x', - 'y', -]); - -/** - * get/set fill linear gradient start point - * @name Konva.Shape#fillLinearGradientStartPoint - * @method - * @param {Object} startPoint - * @param {Number} startPoint.x - * @param {Number} startPoint.y - * @returns {Object} - * @example - * // get fill linear gradient start point - * var startPoint = shape.fillLinearGradientStartPoint(); - * - * // set fill linear gradient start point - * shape.fillLinearGradientStartPoint({ - * x: 20, - * y: 10 - * }); - */ - -Factory.addComponentsGetterSetter(Shape, 'strokeLinearGradientStartPoint', [ - 'x', - 'y', -]); - -/** - * get/set stroke linear gradient start point - * @name Konva.Shape#strokeLinearGradientStartPoint - * @method - * @param {Object} startPoint - * @param {Number} startPoint.x - * @param {Number} startPoint.y - * @returns {Object} - * @example - * // get stroke linear gradient start point - * var startPoint = shape.strokeLinearGradientStartPoint(); - * - * // set stroke linear gradient start point - * shape.strokeLinearGradientStartPoint({ - * x: 20, - * y: 10 - * }); - */ - -Factory.addGetterSetter(Shape, 'fillLinearGradientStartPointX', 0); - -/** - * get/set fill linear gradient start point x - * @name Konva.Shape#fillLinearGradientStartPointX - * @method - * @param {Number} x - * @returns {Number} - * @example - * // get fill linear gradient start point x - * var startPointX = shape.fillLinearGradientStartPointX(); - * - * // set fill linear gradient start point x - * shape.fillLinearGradientStartPointX(20); - */ - -Factory.addGetterSetter(Shape, 'strokeLinearGradientStartPointX', 0); - -/** - * get/set stroke linear gradient start point x - * @name Konva.Shape#linearLinearGradientStartPointX - * @method - * @param {Number} x - * @returns {Number} - * @example - * // get stroke linear gradient start point x - * var startPointX = shape.strokeLinearGradientStartPointX(); - * - * // set stroke linear gradient start point x - * shape.strokeLinearGradientStartPointX(20); - */ - -Factory.addGetterSetter(Shape, 'fillLinearGradientStartPointY', 0); - -/** - * get/set fill linear gradient start point y - * @name Konva.Shape#fillLinearGradientStartPointY - * @method - * @param {Number} y - * @returns {Number} - * @example - * // get fill linear gradient start point y - * var startPointY = shape.fillLinearGradientStartPointY(); - * - * // set fill linear gradient start point y - * shape.fillLinearGradientStartPointY(20); - */ - -Factory.addGetterSetter(Shape, 'strokeLinearGradientStartPointY', 0); -/** - * get/set stroke linear gradient start point y - * @name Konva.Shape#strokeLinearGradientStartPointY - * @method - * @param {Number} y - * @returns {Number} - * @example - * // get stroke linear gradient start point y - * var startPointY = shape.strokeLinearGradientStartPointY(); - * - * // set stroke linear gradient start point y - * shape.strokeLinearGradientStartPointY(20); - */ - -Factory.addComponentsGetterSetter(Shape, 'fillLinearGradientEndPoint', [ - 'x', - 'y', -]); - -/** - * get/set fill linear gradient end point - * @name Konva.Shape#fillLinearGradientEndPoint - * @method - * @param {Object} endPoint - * @param {Number} endPoint.x - * @param {Number} endPoint.y - * @returns {Object} - * @example - * // get fill linear gradient end point - * var endPoint = shape.fillLinearGradientEndPoint(); - * - * // set fill linear gradient end point - * shape.fillLinearGradientEndPoint({ - * x: 20, - * y: 10 - * }); - */ - -Factory.addComponentsGetterSetter(Shape, 'strokeLinearGradientEndPoint', [ - 'x', - 'y', -]); - -/** - * get/set stroke linear gradient end point - * @name Konva.Shape#strokeLinearGradientEndPoint - * @method - * @param {Object} endPoint - * @param {Number} endPoint.x - * @param {Number} endPoint.y - * @returns {Object} - * @example - * // get stroke linear gradient end point - * var endPoint = shape.strokeLinearGradientEndPoint(); - * - * // set stroke linear gradient end point - * shape.strokeLinearGradientEndPoint({ - * x: 20, - * y: 10 - * }); - */ - -Factory.addGetterSetter(Shape, 'fillLinearGradientEndPointX', 0); -/** - * get/set fill linear gradient end point x - * @name Konva.Shape#fillLinearGradientEndPointX - * @method - * @param {Number} x - * @returns {Number} - * @example - * // get fill linear gradient end point x - * var endPointX = shape.fillLinearGradientEndPointX(); - * - * // set fill linear gradient end point x - * shape.fillLinearGradientEndPointX(20); - */ - -Factory.addGetterSetter(Shape, 'strokeLinearGradientEndPointX', 0); -/** - * get/set fill linear gradient end point x - * @name Konva.Shape#strokeLinearGradientEndPointX - * @method - * @param {Number} x - * @returns {Number} - * @example - * // get stroke linear gradient end point x - * var endPointX = shape.strokeLinearGradientEndPointX(); - * - * // set stroke linear gradient end point x - * shape.strokeLinearGradientEndPointX(20); - */ - -Factory.addGetterSetter(Shape, 'fillLinearGradientEndPointY', 0); -/** - * get/set fill linear gradient end point y - * @name Konva.Shape#fillLinearGradientEndPointY - * @method - * @param {Number} y - * @returns {Number} - * @example - * // get fill linear gradient end point y - * var endPointY = shape.fillLinearGradientEndPointY(); - * - * // set fill linear gradient end point y - * shape.fillLinearGradientEndPointY(20); - */ - -Factory.addGetterSetter(Shape, 'strokeLinearGradientEndPointY', 0); -/** - * get/set stroke linear gradient end point y - * @name Konva.Shape#strokeLinearGradientEndPointY - * @method - * @param {Number} y - * @returns {Number} - * @example - * // get stroke linear gradient end point y - * var endPointY = shape.strokeLinearGradientEndPointY(); - * - * // set stroke linear gradient end point y - * shape.strokeLinearGradientEndPointY(20); - */ - -Factory.addComponentsGetterSetter(Shape, 'fillRadialGradientStartPoint', [ - 'x', - 'y', -]); - -/** - * get/set fill radial gradient start point - * @name Konva.Shape#fillRadialGradientStartPoint - * @method - * @param {Object} startPoint - * @param {Number} startPoint.x - * @param {Number} startPoint.y - * @returns {Object} - * @example - * // get fill radial gradient start point - * var startPoint = shape.fillRadialGradientStartPoint(); - * - * // set fill radial gradient start point - * shape.fillRadialGradientStartPoint({ - * x: 20, - * y: 10 - * }); - */ - -Factory.addGetterSetter(Shape, 'fillRadialGradientStartPointX', 0); -/** - * get/set fill radial gradient start point x - * @name Konva.Shape#fillRadialGradientStartPointX - * @method - * @param {Number} x - * @returns {Number} - * @example - * // get fill radial gradient start point x - * var startPointX = shape.fillRadialGradientStartPointX(); - * - * // set fill radial gradient start point x - * shape.fillRadialGradientStartPointX(20); - */ - -Factory.addGetterSetter(Shape, 'fillRadialGradientStartPointY', 0); -/** - * get/set fill radial gradient start point y - * @name Konva.Shape#fillRadialGradientStartPointY - * @method - * @param {Number} y - * @returns {Number} - * @example - * // get fill radial gradient start point y - * var startPointY = shape.fillRadialGradientStartPointY(); - * - * // set fill radial gradient start point y - * shape.fillRadialGradientStartPointY(20); - */ - -Factory.addComponentsGetterSetter(Shape, 'fillRadialGradientEndPoint', [ - 'x', - 'y', -]); - -/** - * get/set fill radial gradient end point - * @name Konva.Shape#fillRadialGradientEndPoint - * @method - * @param {Object} endPoint - * @param {Number} endPoint.x - * @param {Number} endPoint.y - * @returns {Object} - * @example - * // get fill radial gradient end point - * var endPoint = shape.fillRadialGradientEndPoint(); - * - * // set fill radial gradient end point - * shape.fillRadialGradientEndPoint({ - * x: 20, - * y: 10 - * }); - */ - -Factory.addGetterSetter(Shape, 'fillRadialGradientEndPointX', 0); -/** - * get/set fill radial gradient end point x - * @name Konva.Shape#fillRadialGradientEndPointX - * @method - * @param {Number} x - * @returns {Number} - * @example - * // get fill radial gradient end point x - * var endPointX = shape.fillRadialGradientEndPointX(); - * - * // set fill radial gradient end point x - * shape.fillRadialGradientEndPointX(20); - */ - -Factory.addGetterSetter(Shape, 'fillRadialGradientEndPointY', 0); -/** - * get/set fill radial gradient end point y - * @name Konva.Shape#fillRadialGradientEndPointY - * @method - * @param {Number} y - * @returns {Number} - * @example - * // get fill radial gradient end point y - * var endPointY = shape.fillRadialGradientEndPointY(); - * - * // set fill radial gradient end point y - * shape.fillRadialGradientEndPointY(20); - */ - -Factory.addGetterSetter(Shape, 'fillPatternRotation', 0); - -/** - * get/set fill pattern rotation in degrees - * @name Konva.Shape#fillPatternRotation - * @method - * @param {Number} rotation - * @returns {Konva.Shape} - * @example - * // get fill pattern rotation - * var patternRotation = shape.fillPatternRotation(); - * - * // set fill pattern rotation - * shape.fillPatternRotation(20); - */ - -Factory.addGetterSetter(Shape, 'fillRule', undefined, getStringValidator()); - -/** - * get/set fill rule - * @name Konva.Shape#fillRule - * @method - * @param {CanvasFillRule} rotation - * @returns {Konva.Shape} - * @example - * // get fill rule - * var fillRule = shape.fillRule(); - * - * // set fill rule - * shape.fillRule('evenodd'); - */ - -Factory.backCompat(Shape, { - dashArray: 'dash', - getDashArray: 'getDash', - setDashArray: 'getDash', - - drawFunc: 'sceneFunc', - getDrawFunc: 'getSceneFunc', - setDrawFunc: 'setSceneFunc', - - drawHitFunc: 'hitFunc', - getDrawHitFunc: 'getHitFunc', - setDrawHitFunc: 'setHitFunc', -}); diff --git a/src/Stage.ts b/src/Stage.ts deleted file mode 100644 index 495f2b682..000000000 --- a/src/Stage.ts +++ /dev/null @@ -1,978 +0,0 @@ -import { Util } from './Util'; -import { Factory } from './Factory'; -import { Container, ContainerConfig } from './Container'; -import { Konva } from './Global'; -import { SceneCanvas, HitCanvas } from './Canvas'; -import { GetSet, Vector2d } from './types'; -import { Shape } from './Shape'; -import { Layer } from './Layer'; -import { DD } from './DragAndDrop'; -import { _registerNode } from './Global'; -import * as PointerEvents from './PointerEvents'; - -export interface StageConfig extends ContainerConfig { - container?: HTMLDivElement | string; -} - -// CONSTANTS -const STAGE = 'Stage', - STRING = 'string', - PX = 'px', - MOUSEOUT = 'mouseout', - MOUSELEAVE = 'mouseleave', - MOUSEOVER = 'mouseover', - MOUSEENTER = 'mouseenter', - MOUSEMOVE = 'mousemove', - MOUSEDOWN = 'mousedown', - MOUSEUP = 'mouseup', - POINTERMOVE = 'pointermove', - POINTERDOWN = 'pointerdown', - POINTERUP = 'pointerup', - POINTERCANCEL = 'pointercancel', - LOSTPOINTERCAPTURE = 'lostpointercapture', - POINTEROUT = 'pointerout', - POINTERLEAVE = 'pointerleave', - POINTEROVER = 'pointerover', - POINTERENTER = 'pointerenter', - CONTEXTMENU = 'contextmenu', - TOUCHSTART = 'touchstart', - TOUCHEND = 'touchend', - TOUCHMOVE = 'touchmove', - TOUCHCANCEL = 'touchcancel', - WHEEL = 'wheel', - MAX_LAYERS_NUMBER = 5, - EVENTS = [ - [MOUSEENTER, '_pointerenter'], - [MOUSEDOWN, '_pointerdown'], - [MOUSEMOVE, '_pointermove'], - [MOUSEUP, '_pointerup'], - [MOUSELEAVE, '_pointerleave'], - [TOUCHSTART, '_pointerdown'], - [TOUCHMOVE, '_pointermove'], - [TOUCHEND, '_pointerup'], - [TOUCHCANCEL, '_pointercancel'], - [MOUSEOVER, '_pointerover'], - [WHEEL, '_wheel'], - [CONTEXTMENU, '_contextmenu'], - [POINTERDOWN, '_pointerdown'], - [POINTERMOVE, '_pointermove'], - [POINTERUP, '_pointerup'], - [POINTERCANCEL, '_pointercancel'], - [LOSTPOINTERCAPTURE, '_lostpointercapture'], - ]; - -const EVENTS_MAP = { - mouse: { - [POINTEROUT]: MOUSEOUT, - [POINTERLEAVE]: MOUSELEAVE, - [POINTEROVER]: MOUSEOVER, - [POINTERENTER]: MOUSEENTER, - [POINTERMOVE]: MOUSEMOVE, - [POINTERDOWN]: MOUSEDOWN, - [POINTERUP]: MOUSEUP, - [POINTERCANCEL]: 'mousecancel', - pointerclick: 'click', - pointerdblclick: 'dblclick', - }, - touch: { - [POINTEROUT]: 'touchout', - [POINTERLEAVE]: 'touchleave', - [POINTEROVER]: 'touchover', - [POINTERENTER]: 'touchenter', - [POINTERMOVE]: TOUCHMOVE, - [POINTERDOWN]: TOUCHSTART, - [POINTERUP]: TOUCHEND, - [POINTERCANCEL]: TOUCHCANCEL, - pointerclick: 'tap', - pointerdblclick: 'dbltap', - }, - pointer: { - [POINTEROUT]: POINTEROUT, - [POINTERLEAVE]: POINTERLEAVE, - [POINTEROVER]: POINTEROVER, - [POINTERENTER]: POINTERENTER, - [POINTERMOVE]: POINTERMOVE, - [POINTERDOWN]: POINTERDOWN, - [POINTERUP]: POINTERUP, - [POINTERCANCEL]: POINTERCANCEL, - pointerclick: 'pointerclick', - pointerdblclick: 'pointerdblclick', - }, -}; - -const getEventType = (type) => { - if (type.indexOf('pointer') >= 0) { - return 'pointer'; - } - if (type.indexOf('touch') >= 0) { - return 'touch'; - } - return 'mouse'; -}; - -const getEventsMap = (eventType: string) => { - const type = getEventType(eventType); - if (type === 'pointer') { - return Konva.pointerEventsEnabled && EVENTS_MAP.pointer; - } - if (type === 'touch') { - return EVENTS_MAP.touch; - } - if (type === 'mouse') { - return EVENTS_MAP.mouse; - } -}; - -function checkNoClip(attrs: any = {}) { - if (attrs.clipFunc || attrs.clipWidth || attrs.clipHeight) { - Util.warn( - 'Stage does not support clipping. Please use clip for Layers or Groups.' - ); - } - return attrs; -} - -const NO_POINTERS_MESSAGE = `Pointer position is missing and not registered by the stage. Looks like it is outside of the stage container. You can set it manually from event: stage.setPointersPositions(event);`; - -export const stages: Stage[] = []; - -/** - * Stage constructor. A stage is used to contain multiple layers - * @constructor - * @memberof Konva - * @augments Konva.Container - * @param {Object} config - * @param {String|Element} config.container Container selector or DOM element - * @@nodeParams - * @example - * var stage = new Konva.Stage({ - * width: 500, - * height: 800, - * container: 'containerId' // or "#containerId" or ".containerClass" - * }); - */ - -export class Stage extends Container { - content: HTMLDivElement; - pointerPos: Vector2d | null; - _pointerPositions: (Vector2d & { id?: number })[] = []; - _changedPointerPositions: (Vector2d & { id: number })[] = []; - - bufferCanvas: SceneCanvas; - bufferHitCanvas: HitCanvas; - _mouseTargetShape: Shape; - _touchTargetShape: Shape; - _pointerTargetShape: Shape; - _mouseClickStartShape: Shape; - _touchClickStartShape: Shape; - _pointerClickStartShape: Shape; - _mouseClickEndShape: Shape; - _touchClickEndShape: Shape; - _pointerClickEndShape: Shape; - - _mouseDblTimeout: any; - _touchDblTimeout: any; - _pointerDblTimeout: any; - - constructor(config: StageConfig) { - super(checkNoClip(config)); - this._buildDOM(); - this._bindContentEvents(); - stages.push(this); - this.on('widthChange.konva heightChange.konva', this._resizeDOM); - this.on('visibleChange.konva', this._checkVisibility); - this.on( - 'clipWidthChange.konva clipHeightChange.konva clipFuncChange.konva', - () => { - checkNoClip(this.attrs); - } - ); - this._checkVisibility(); - } - - _validateAdd(child) { - const isLayer = child.getType() === 'Layer'; - const isFastLayer = child.getType() === 'FastLayer'; - const valid = isLayer || isFastLayer; - if (!valid) { - Util.throw('You may only add layers to the stage.'); - } - } - - _checkVisibility() { - if (!this.content) { - return; - } - const style = this.visible() ? '' : 'none'; - this.content.style.display = style; - } - /** - * set container dom element which contains the stage wrapper div element - * @method - * @name Konva.Stage#setContainer - * @param {DomElement} container can pass in a dom element or id string - */ - setContainer(container) { - if (typeof container === STRING) { - if (container.charAt(0) === '.') { - const className = container.slice(1); - container = document.getElementsByClassName(className)[0]; - } else { - var id; - if (container.charAt(0) !== '#') { - id = container; - } else { - id = container.slice(1); - } - container = document.getElementById(id); - } - if (!container) { - throw 'Can not find container in document with id ' + id; - } - } - this._setAttr('container', container); - if (this.content) { - if (this.content.parentElement) { - this.content.parentElement.removeChild(this.content); - } - container.appendChild(this.content); - } - return this; - } - shouldDrawHit() { - return true; - } - - /** - * clear all layers - * @method - * @name Konva.Stage#clear - */ - clear() { - const layers = this.children, - len = layers.length; - - for (let n = 0; n < len; n++) { - layers[n].clear(); - } - return this; - } - clone(obj?) { - if (!obj) { - obj = {}; - } - obj.container = - typeof document !== 'undefined' && document.createElement('div'); - return Container.prototype.clone.call(this, obj) as this; - } - - destroy() { - super.destroy(); - - const content = this.content; - if (content && Util._isInDocument(content)) { - this.container().removeChild(content); - } - const index = stages.indexOf(this); - if (index > -1) { - stages.splice(index, 1); - } - - Util.releaseCanvas(this.bufferCanvas._canvas, this.bufferHitCanvas._canvas); - - return this; - } - /** - * returns ABSOLUTE pointer position which can be a touch position or mouse position - * pointer position doesn't include any transforms (such as scale) of the stage - * it is just a plain position of pointer relative to top-left corner of the canvas - * @method - * @name Konva.Stage#getPointerPosition - * @returns {Vector2d|null} - */ - getPointerPosition(): Vector2d | null { - const pos = this._pointerPositions[0] || this._changedPointerPositions[0]; - if (!pos) { - Util.warn(NO_POINTERS_MESSAGE); - return null; - } - return { - x: pos.x, - y: pos.y, - }; - } - _getPointerById(id?: number) { - return this._pointerPositions.find((p) => p.id === id); - } - getPointersPositions() { - return this._pointerPositions; - } - getStage() { - return this; - } - getContent() { - return this.content; - } - _toKonvaCanvas(config) { - config = config || {}; - - config.x = config.x || 0; - config.y = config.y || 0; - config.width = config.width || this.width(); - config.height = config.height || this.height(); - - const canvas = new SceneCanvas({ - width: config.width, - height: config.height, - pixelRatio: config.pixelRatio || 1, - }); - const _context = canvas.getContext()._context; - const layers = this.children; - - if (config.x || config.y) { - _context.translate(-1 * config.x, -1 * config.y); - } - - layers.forEach(function (layer) { - if (!layer.isVisible()) { - return; - } - const layerCanvas = layer._toKonvaCanvas(config); - _context.drawImage( - layerCanvas._canvas, - config.x, - config.y, - layerCanvas.getWidth() / layerCanvas.getPixelRatio(), - layerCanvas.getHeight() / layerCanvas.getPixelRatio() - ); - }); - return canvas; - } - - /** - * get visible intersection shape. This is the preferred - * method for determining if a point intersects a shape or not - * nodes with listening set to false will not be detected - * @method - * @name Konva.Stage#getIntersection - * @param {Object} pos - * @param {Number} pos.x - * @param {Number} pos.y - * @returns {Konva.Node} - * @example - * var shape = stage.getIntersection({x: 50, y: 50}); - */ - getIntersection(pos: Vector2d) { - if (!pos) { - return null; - } - const layers = this.children, - len = layers.length, - end = len - 1; - - for (let n = end; n >= 0; n--) { - const shape = layers[n].getIntersection(pos); - if (shape) { - return shape; - } - } - - return null; - } - _resizeDOM() { - const width = this.width(); - const height = this.height(); - if (this.content) { - // set content dimensions - this.content.style.width = width + PX; - this.content.style.height = height + PX; - } - - this.bufferCanvas.setSize(width, height); - this.bufferHitCanvas.setSize(width, height); - - // set layer dimensions - this.children.forEach((layer) => { - layer.setSize({ width, height }); - layer.draw(); - }); - } - add(layer: Layer, ...rest) { - if (arguments.length > 1) { - for (let i = 0; i < arguments.length; i++) { - this.add(arguments[i]); - } - return this; - } - super.add(layer); - - const length = this.children.length; - if (length > MAX_LAYERS_NUMBER) { - Util.warn( - 'The stage has ' + - length + - ' layers. Recommended maximum number of layers is 3-5. Adding more layers into the stage may drop the performance. Rethink your tree structure, you can use Konva.Group.' - ); - } - layer.setSize({ width: this.width(), height: this.height() }); - - // draw layer and append canvas to container - layer.draw(); - - if (Konva.isBrowser) { - this.content.appendChild(layer.canvas._canvas); - } - - // chainable - return this; - } - getParent() { - return null; - } - getLayer() { - return null; - } - - hasPointerCapture(pointerId: number): boolean { - return PointerEvents.hasPointerCapture(pointerId, this); - } - - setPointerCapture(pointerId: number) { - PointerEvents.setPointerCapture(pointerId, this); - } - - releaseCapture(pointerId: number) { - PointerEvents.releaseCapture(pointerId, this); - } - - /** - * returns an array of layers - * @method - * @name Konva.Stage#getLayers - */ - getLayers() { - return this.children; - } - _bindContentEvents() { - if (!Konva.isBrowser) { - return; - } - EVENTS.forEach(([event, methodName]) => { - this.content.addEventListener( - event, - (evt) => { - this[methodName](evt); - }, - { passive: false } - ); - }); - } - _pointerenter(evt: PointerEvent) { - this.setPointersPositions(evt); - const events = getEventsMap(evt.type); - if (events) { - this._fire(events.pointerenter, { - evt: evt, - target: this, - currentTarget: this, - }); - } - } - _pointerover(evt) { - this.setPointersPositions(evt); - const events = getEventsMap(evt.type); - if (events) { - this._fire(events.pointerover, { - evt: evt, - target: this, - currentTarget: this, - }); - } - } - _getTargetShape(evenType) { - let shape: Shape | null = this[evenType + 'targetShape']; - if (shape && !shape.getStage()) { - shape = null; - } - return shape; - } - _pointerleave(evt) { - const events = getEventsMap(evt.type); - const eventType = getEventType(evt.type); - - if (!events) { - return; - } - this.setPointersPositions(evt); - - const targetShape = this._getTargetShape(eventType); - const eventsEnabled = - !(Konva.isDragging() || Konva.isTransforming()) || Konva.hitOnDragEnabled; - if (targetShape && eventsEnabled) { - targetShape._fireAndBubble(events.pointerout, { evt: evt }); - targetShape._fireAndBubble(events.pointerleave, { evt: evt }); - this._fire(events.pointerleave, { - evt: evt, - target: this, - currentTarget: this, - }); - this[eventType + 'targetShape'] = null; - } else if (eventsEnabled) { - this._fire(events.pointerleave, { - evt: evt, - target: this, - currentTarget: this, - }); - this._fire(events.pointerout, { - evt: evt, - target: this, - currentTarget: this, - }); - } - this.pointerPos = null; - this._pointerPositions = []; - } - _pointerdown(evt: TouchEvent | MouseEvent | PointerEvent) { - const events = getEventsMap(evt.type); - const eventType = getEventType(evt.type); - - if (!events) { - return; - } - this.setPointersPositions(evt); - - let triggeredOnShape = false; - this._changedPointerPositions.forEach((pos) => { - const shape = this.getIntersection(pos); - DD.justDragged = false; - // probably we are staring a click - Konva['_' + eventType + 'ListenClick'] = true; - - // no shape detected? do nothing - if (!shape || !shape.isListening()) { - this[eventType + 'ClickStartShape'] = undefined; - return; - } - - if (Konva.capturePointerEventsEnabled) { - shape.setPointerCapture(pos.id); - } - - // save where we started the click - this[eventType + 'ClickStartShape'] = shape; - - shape._fireAndBubble(events.pointerdown, { - evt: evt, - pointerId: pos.id, - }); - triggeredOnShape = true; - - // TODO: test in iframe - // only call preventDefault if the shape is listening for events - const isTouch = evt.type.indexOf('touch') >= 0; - if (shape.preventDefault() && evt.cancelable && isTouch) { - evt.preventDefault(); - } - }); - - // trigger down on stage if not already - if (!triggeredOnShape) { - this._fire(events.pointerdown, { - evt: evt, - target: this, - currentTarget: this, - pointerId: this._pointerPositions[0].id, - }); - } - } - _pointermove(evt: TouchEvent | MouseEvent | PointerEvent) { - const events = getEventsMap(evt.type); - const eventType = getEventType(evt.type); - if (!events) { - return; - } - if (Konva.isDragging() && DD.node!.preventDefault() && evt.cancelable) { - evt.preventDefault(); - } - this.setPointersPositions(evt); - - const eventsEnabled = - !(Konva.isDragging() || Konva.isTransforming()) || Konva.hitOnDragEnabled; - if (!eventsEnabled) { - return; - } - - const processedShapesIds = {}; - let triggeredOnShape = false; - const targetShape = this._getTargetShape(eventType); - this._changedPointerPositions.forEach((pos) => { - const shape = (PointerEvents.getCapturedShape(pos.id) || - this.getIntersection(pos)) as Shape; - const pointerId = pos.id; - const event = { evt: evt, pointerId }; - - const differentTarget = targetShape !== shape; - - if (differentTarget && targetShape) { - targetShape._fireAndBubble(events.pointerout, { ...event }, shape); - targetShape._fireAndBubble(events.pointerleave, { ...event }, shape); - } - - if (shape) { - if (processedShapesIds[shape._id]) { - return; - } - processedShapesIds[shape._id] = true; - } - - if (shape && shape.isListening()) { - triggeredOnShape = true; - if (differentTarget) { - shape._fireAndBubble(events.pointerover, { ...event }, targetShape); - shape._fireAndBubble(events.pointerenter, { ...event }, targetShape); - this[eventType + 'targetShape'] = shape; - } - shape._fireAndBubble(events.pointermove, { ...event }); - } else { - if (targetShape) { - this._fire(events.pointerover, { - evt: evt, - target: this, - currentTarget: this, - pointerId, - }); - this[eventType + 'targetShape'] = null; - } - } - }); - - if (!triggeredOnShape) { - this._fire(events.pointermove, { - evt: evt, - target: this, - currentTarget: this, - pointerId: this._changedPointerPositions[0].id, - }); - } - } - _pointerup(evt) { - const events = getEventsMap(evt.type); - const eventType = getEventType(evt.type); - - if (!events) { - return; - } - this.setPointersPositions(evt); - const clickStartShape = this[eventType + 'ClickStartShape']; - const clickEndShape = this[eventType + 'ClickEndShape']; - const processedShapesIds = {}; - let triggeredOnShape = false; - this._changedPointerPositions.forEach((pos) => { - const shape = (PointerEvents.getCapturedShape(pos.id) || - this.getIntersection(pos)) as Shape; - - if (shape) { - shape.releaseCapture(pos.id); - if (processedShapesIds[shape._id]) { - return; - } - processedShapesIds[shape._id] = true; - } - - const pointerId = pos.id; - const event = { evt: evt, pointerId }; - - let fireDblClick = false; - if (Konva['_' + eventType + 'InDblClickWindow']) { - fireDblClick = true; - clearTimeout(this[eventType + 'DblTimeout']); - } else if (!DD.justDragged) { - // don't set inDblClickWindow after dragging - Konva['_' + eventType + 'InDblClickWindow'] = true; - clearTimeout(this[eventType + 'DblTimeout']); - } - - this[eventType + 'DblTimeout'] = setTimeout(function () { - Konva['_' + eventType + 'InDblClickWindow'] = false; - }, Konva.dblClickWindow); - - if (shape && shape.isListening()) { - triggeredOnShape = true; - this[eventType + 'ClickEndShape'] = shape; - shape._fireAndBubble(events.pointerup, { ...event }); - - // detect if click or double click occurred - if ( - Konva['_' + eventType + 'ListenClick'] && - clickStartShape && - clickStartShape === shape - ) { - shape._fireAndBubble(events.pointerclick, { ...event }); - - if (fireDblClick && clickEndShape && clickEndShape === shape) { - shape._fireAndBubble(events.pointerdblclick, { ...event }); - } - } - } else { - this[eventType + 'ClickEndShape'] = null; - - if (Konva['_' + eventType + 'ListenClick']) { - this._fire(events.pointerclick, { - evt: evt, - target: this, - currentTarget: this, - pointerId, - }); - } - - if (fireDblClick) { - this._fire(events.pointerdblclick, { - evt: evt, - target: this, - currentTarget: this, - pointerId, - }); - } - } - }); - - if (!triggeredOnShape) { - this._fire(events.pointerup, { - evt: evt, - target: this, - currentTarget: this, - pointerId: this._changedPointerPositions[0].id, - }); - } - - Konva['_' + eventType + 'ListenClick'] = false; - - // always call preventDefault for desktop events because some browsers - // try to drag and drop the canvas element - // TODO: are we sure we need to prevent default at all? - // do not call this function on mobile because it prevent "click" event on all parent containers - // but apps may listen to it. - if (evt.cancelable && eventType !== 'touch' && eventType !== 'pointer') { - evt.preventDefault(); - } - } - _contextmenu(evt) { - this.setPointersPositions(evt); - const shape = this.getIntersection(this.getPointerPosition()!); - - if (shape && shape.isListening()) { - shape._fireAndBubble(CONTEXTMENU, { evt: evt }); - } else { - this._fire(CONTEXTMENU, { - evt: evt, - target: this, - currentTarget: this, - }); - } - } - - _wheel(evt) { - this.setPointersPositions(evt); - const shape = this.getIntersection(this.getPointerPosition()!); - - if (shape && shape.isListening()) { - shape._fireAndBubble(WHEEL, { evt: evt }); - } else { - this._fire(WHEEL, { - evt: evt, - target: this, - currentTarget: this, - }); - } - } - - _pointercancel(evt: PointerEvent) { - this.setPointersPositions(evt); - const shape = - PointerEvents.getCapturedShape(evt.pointerId) || - this.getIntersection(this.getPointerPosition()!); - - if (shape) { - shape._fireAndBubble(POINTERUP, PointerEvents.createEvent(evt)); - } - - PointerEvents.releaseCapture(evt.pointerId); - } - - _lostpointercapture(evt: PointerEvent) { - PointerEvents.releaseCapture(evt.pointerId); - } - - /** - * manually register pointers positions (mouse/touch) in the stage. - * So you can use stage.getPointerPosition(). Usually you don't need to use that method - * because all internal events are automatically registered. It may be useful if event - * is triggered outside of the stage, but you still want to use Konva methods to get pointers position. - * @method - * @name Konva.Stage#setPointersPositions - * @param {Object} event Event object - * @example - * - * window.addEventListener('mousemove', (e) => { - * stage.setPointersPositions(e); - * }); - */ - setPointersPositions(evt) { - const contentPosition = this._getContentPosition(); - let x: number | null = null, - y: number | null = null; - evt = evt ? evt : window.event; - - // touch events - if (evt.touches !== undefined) { - // touchlist has not support for map method - // so we have to iterate - this._pointerPositions = []; - this._changedPointerPositions = []; - Array.prototype.forEach.call(evt.touches, (touch: any) => { - this._pointerPositions.push({ - id: touch.identifier, - x: (touch.clientX - contentPosition.left) / contentPosition.scaleX, - y: (touch.clientY - contentPosition.top) / contentPosition.scaleY, - }); - }); - - Array.prototype.forEach.call( - evt.changedTouches || evt.touches, - (touch: any) => { - this._changedPointerPositions.push({ - id: touch.identifier, - x: (touch.clientX - contentPosition.left) / contentPosition.scaleX, - y: (touch.clientY - contentPosition.top) / contentPosition.scaleY, - }); - } - ); - } else { - // mouse events - x = (evt.clientX - contentPosition.left) / contentPosition.scaleX; - y = (evt.clientY - contentPosition.top) / contentPosition.scaleY; - this.pointerPos = { - x: x, - y: y, - }; - this._pointerPositions = [{ x, y, id: Util._getFirstPointerId(evt) }]; - this._changedPointerPositions = [ - { x, y, id: Util._getFirstPointerId(evt) }, - ]; - } - } - _setPointerPosition(evt) { - Util.warn( - 'Method _setPointerPosition is deprecated. Use "stage.setPointersPositions(event)" instead.' - ); - this.setPointersPositions(evt); - } - _getContentPosition() { - if (!this.content || !this.content.getBoundingClientRect) { - return { - top: 0, - left: 0, - scaleX: 1, - scaleY: 1, - }; - } - - const rect = this.content.getBoundingClientRect(); - - return { - top: rect.top, - left: rect.left, - // sometimes clientWidth can be equals to 0 - // i saw it in react-konva test, looks like it is because of hidden testing element - scaleX: rect.width / this.content.clientWidth || 1, - scaleY: rect.height / this.content.clientHeight || 1, - }; - } - _buildDOM() { - this.bufferCanvas = new SceneCanvas({ - width: this.width(), - height: this.height(), - }); - this.bufferHitCanvas = new HitCanvas({ - pixelRatio: 1, - width: this.width(), - height: this.height(), - }); - - if (!Konva.isBrowser) { - return; - } - const container = this.container(); - if (!container) { - throw 'Stage has no container. A container is required.'; - } - // clear content inside container - container.innerHTML = ''; - - // content - this.content = document.createElement('div'); - this.content.style.position = 'relative'; - this.content.style.userSelect = 'none'; - this.content.className = 'konvajs-content'; - - this.content.setAttribute('role', 'presentation'); - - container.appendChild(this.content); - - this._resizeDOM(); - } - // currently cache function is now working for stage, because stage has no its own canvas element - cache() { - Util.warn( - 'Cache function is not allowed for stage. You may use cache only for layers, groups and shapes.' - ); - return this; - } - clearCache() { - return this; - } - /** - * batch draw - * @method - * @name Konva.Stage#batchDraw - * @return {Konva.Stage} this - */ - batchDraw() { - this.getChildren().forEach(function (layer) { - layer.batchDraw(); - }); - return this; - } - - container: GetSet; -} - -Stage.prototype.nodeType = STAGE; -_registerNode(Stage); - -/** - * get/set container DOM element - * @method - * @name Konva.Stage#container - * @returns {DomElement} container - * @example - * // get container - * var container = stage.container(); - * // set container - * var container = document.createElement('div'); - * body.appendChild(container); - * stage.container(container); - */ -Factory.addGetterSetter(Stage, 'container'); - -// chrome is clearing canvas in inactive browser window, causing layer content to be erased -// so let's redraw layers as soon as window becomes active -// TODO: any other way to solve this issue? -// TODO: should we remove it if chrome fixes the issue? -if (Konva.isBrowser) { - document.addEventListener('visibilitychange', () => { - stages.forEach((stage) => { - stage.batchDraw(); - }); - }); -} diff --git a/src/Tween.ts b/src/Tween.ts deleted file mode 100644 index bb2d82788..000000000 --- a/src/Tween.ts +++ /dev/null @@ -1,805 +0,0 @@ -import { Util } from './Util'; -import { Animation } from './Animation'; -import { Node, NodeConfig } from './Node'; -import { Konva } from './Global'; -import { Line } from './shapes/Line'; - -const blacklist = { - node: 1, - duration: 1, - easing: 1, - onFinish: 1, - yoyo: 1, - }, - PAUSED = 1, - PLAYING = 2, - REVERSING = 3, - colorAttrs = ['fill', 'stroke', 'shadowColor']; -let idCounter = 0; - -class TweenEngine { - prop: string; - propFunc: Function; - begin: number; - _pos: number; - duration: number; - prevPos: number; - yoyo: boolean; - _time: number; - _position: number; - _startTime: number; - _finish: number; - func: Function; - _change: number; - state: number; - - onPlay: Function; - onReverse: Function; - onPause: Function; - onReset: Function; - onFinish: Function; - onUpdate: Function; - - constructor(prop, propFunc, func, begin, finish, duration, yoyo) { - this.prop = prop; - this.propFunc = propFunc; - this.begin = begin; - this._pos = begin; - this.duration = duration; - this._change = 0; - this.prevPos = 0; - this.yoyo = yoyo; - this._time = 0; - this._position = 0; - this._startTime = 0; - this._finish = 0; - this.func = func; - this._change = finish - this.begin; - this.pause(); - } - fire(str) { - const handler = this[str]; - if (handler) { - handler(); - } - } - setTime(t) { - if (t > this.duration) { - if (this.yoyo) { - this._time = this.duration; - this.reverse(); - } else { - this.finish(); - } - } else if (t < 0) { - if (this.yoyo) { - this._time = 0; - this.play(); - } else { - this.reset(); - } - } else { - this._time = t; - this.update(); - } - } - getTime() { - return this._time; - } - setPosition(p) { - this.prevPos = this._pos; - this.propFunc(p); - this._pos = p; - } - getPosition(t) { - if (t === undefined) { - t = this._time; - } - return this.func(t, this.begin, this._change, this.duration); - } - play() { - this.state = PLAYING; - this._startTime = this.getTimer() - this._time; - this.onEnterFrame(); - this.fire('onPlay'); - } - reverse() { - this.state = REVERSING; - this._time = this.duration - this._time; - this._startTime = this.getTimer() - this._time; - this.onEnterFrame(); - this.fire('onReverse'); - } - seek(t) { - this.pause(); - this._time = t; - this.update(); - this.fire('onSeek'); - } - reset() { - this.pause(); - this._time = 0; - this.update(); - this.fire('onReset'); - } - finish() { - this.pause(); - this._time = this.duration; - this.update(); - this.fire('onFinish'); - } - update() { - this.setPosition(this.getPosition(this._time)); - this.fire('onUpdate'); - } - onEnterFrame() { - const t = this.getTimer() - this._startTime; - if (this.state === PLAYING) { - this.setTime(t); - } else if (this.state === REVERSING) { - this.setTime(this.duration - t); - } - } - pause() { - this.state = PAUSED; - this.fire('onPause'); - } - getTimer() { - return new Date().getTime(); - } -} - -export interface TweenConfig extends NodeConfig { - onFinish?: Function; - onUpdate?: Function; - duration?: number; - node: Node; -} - -/** - * Tween constructor. Tweens enable you to animate a node between the current state and a new state. - * You can play, pause, reverse, seek, reset, and finish tweens. By default, tweens are animated using - * a linear easing. For more tweening options, check out {@link Konva.Easings} - * @constructor - * @memberof Konva - * @example - * // instantiate new tween which fully rotates a node in 1 second - * var tween = new Konva.Tween({ - * // list of tween specific properties - * node: node, - * duration: 1, - * easing: Konva.Easings.EaseInOut, - * onUpdate: () => console.log('node attrs updated') - * onFinish: () => console.log('finished'), - * // set new values for any attributes of a passed node - * rotation: 360, - * fill: 'red' - * }); - * - * // play tween - * tween.play(); - * - * // pause tween - * tween.pause(); - */ -export class Tween { - static attrs = {}; - static tweens = {}; - - node: Node; - anim: Animation; - tween: TweenEngine; - _id: number; - onFinish: Function | undefined; - onReset: Function | undefined; - onUpdate: Function | undefined; - - constructor(config: TweenConfig) { - const that = this, - node = config.node as any, - nodeId = node._id, - easing = config.easing || Easings.Linear, - yoyo = !!config.yoyo; - let duration, - key; - - if (typeof config.duration === 'undefined') { - duration = 0.3; - } else if (config.duration === 0) { - // zero is bad value for duration - duration = 0.001; - } else { - duration = config.duration; - } - this.node = node; - this._id = idCounter++; - - const layers = - node.getLayer() || - (node instanceof Konva['Stage'] ? node.getLayers() : null); - if (!layers) { - Util.error( - 'Tween constructor have `node` that is not in a layer. Please add node into layer first.' - ); - } - this.anim = new Animation(function () { - that.tween.onEnterFrame(); - }, layers); - - this.tween = new TweenEngine( - key, - function (i) { - that._tweenFunc(i); - }, - easing, - 0, - 1, - duration * 1000, - yoyo - ); - - this._addListeners(); - - // init attrs map - if (!Tween.attrs[nodeId]) { - Tween.attrs[nodeId] = {}; - } - if (!Tween.attrs[nodeId][this._id]) { - Tween.attrs[nodeId][this._id] = {}; - } - // init tweens map - if (!Tween.tweens[nodeId]) { - Tween.tweens[nodeId] = {}; - } - - for (key in config) { - if (blacklist[key] === undefined) { - this._addAttr(key, config[key]); - } - } - - this.reset(); - - // callbacks - this.onFinish = config.onFinish; - this.onReset = config.onReset; - this.onUpdate = config.onUpdate; - } - _addAttr(key, end) { - const node = this.node, - nodeId = node._id; - let diff, - len, - trueEnd, - trueStart, - endRGBA; - - // remove conflict from tween map if it exists - const tweenId = Tween.tweens[nodeId][key]; - - if (tweenId) { - delete Tween.attrs[nodeId][tweenId][key]; - } - - // add to tween map - let start = node.getAttr(key); - - if (Util._isArray(end)) { - diff = []; - len = Math.max(end.length, start.length); - - if (key === 'points' && end.length !== start.length) { - // before tweening points we need to make sure that start.length === end.length - // Util._prepareArrayForTween thinking that end.length > start.length - if (end.length > start.length) { - // so in this case we will increase number of starting points - trueStart = start; - start = Util._prepareArrayForTween( - start, - end, - (node as Line).closed() - ); - } else { - // in this case we will increase number of eding points - trueEnd = end; - end = Util._prepareArrayForTween(end, start, (node as Line).closed()); - } - } - - if (key.indexOf('fill') === 0) { - for (let n = 0; n < len; n++) { - if (n % 2 === 0) { - diff.push(end[n] - start[n]); - } else { - const startRGBA = Util.colorToRGBA(start[n])!; - endRGBA = Util.colorToRGBA(end[n]); - start[n] = startRGBA; - diff.push({ - r: endRGBA.r - startRGBA.r, - g: endRGBA.g - startRGBA.g, - b: endRGBA.b - startRGBA.b, - a: endRGBA.a - startRGBA.a, - }); - } - } - } else { - for (let n = 0; n < len; n++) { - diff.push(end[n] - start[n]); - } - } - } else if (colorAttrs.indexOf(key) !== -1) { - start = Util.colorToRGBA(start); - endRGBA = Util.colorToRGBA(end); - diff = { - r: endRGBA.r - start.r, - g: endRGBA.g - start.g, - b: endRGBA.b - start.b, - a: endRGBA.a - start.a, - }; - } else { - diff = end - start; - } - - Tween.attrs[nodeId][this._id][key] = { - start: start, - diff: diff, - end: end, - trueEnd: trueEnd, - trueStart: trueStart, - }; - Tween.tweens[nodeId][key] = this._id; - } - _tweenFunc(i) { - const node = this.node, - attrs = Tween.attrs[node._id][this._id]; - let key, - attr, - start, - diff, - newVal, - n, - len, - end; - - for (key in attrs) { - attr = attrs[key]; - start = attr.start; - diff = attr.diff; - end = attr.end; - - if (Util._isArray(start)) { - newVal = []; - len = Math.max(start.length, end.length); - if (key.indexOf('fill') === 0) { - for (n = 0; n < len; n++) { - if (n % 2 === 0) { - newVal.push((start[n] || 0) + diff[n] * i); - } else { - newVal.push( - 'rgba(' + - Math.round(start[n].r + diff[n].r * i) + - ',' + - Math.round(start[n].g + diff[n].g * i) + - ',' + - Math.round(start[n].b + diff[n].b * i) + - ',' + - (start[n].a + diff[n].a * i) + - ')' - ); - } - } - } else { - for (n = 0; n < len; n++) { - newVal.push((start[n] || 0) + diff[n] * i); - } - } - } else if (colorAttrs.indexOf(key) !== -1) { - newVal = - 'rgba(' + - Math.round(start.r + diff.r * i) + - ',' + - Math.round(start.g + diff.g * i) + - ',' + - Math.round(start.b + diff.b * i) + - ',' + - (start.a + diff.a * i) + - ')'; - } else { - newVal = start + diff * i; - } - - node.setAttr(key, newVal); - } - } - _addListeners() { - // start listeners - this.tween.onPlay = () => { - this.anim.start(); - }; - this.tween.onReverse = () => { - this.anim.start(); - }; - - // stop listeners - this.tween.onPause = () => { - this.anim.stop(); - }; - this.tween.onFinish = () => { - const node = this.node as Node; - - // after tweening points of line we need to set original end - const attrs = Tween.attrs[node._id][this._id]; - if (attrs.points && attrs.points.trueEnd) { - node.setAttr('points', attrs.points.trueEnd); - } - - if (this.onFinish) { - this.onFinish.call(this); - } - }; - this.tween.onReset = () => { - const node = this.node as any; - // after tweening points of line we need to set original start - const attrs = Tween.attrs[node._id][this._id]; - if (attrs.points && attrs.points.trueStart) { - node.points(attrs.points.trueStart); - } - - if (this.onReset) { - this.onReset(); - } - }; - this.tween.onUpdate = () => { - if (this.onUpdate) { - this.onUpdate.call(this); - } - }; - } - /** - * play - * @method - * @name Konva.Tween#play - * @returns {Tween} - */ - play() { - this.tween.play(); - return this; - } - /** - * reverse - * @method - * @name Konva.Tween#reverse - * @returns {Tween} - */ - reverse() { - this.tween.reverse(); - return this; - } - /** - * reset - * @method - * @name Konva.Tween#reset - * @returns {Tween} - */ - reset() { - this.tween.reset(); - return this; - } - /** - * seek - * @method - * @name Konva.Tween#seek( - * @param {Integer} t time in seconds between 0 and the duration - * @returns {Tween} - */ - seek(t) { - this.tween.seek(t * 1000); - return this; - } - /** - * pause - * @method - * @name Konva.Tween#pause - * @returns {Tween} - */ - pause() { - this.tween.pause(); - return this; - } - /** - * finish - * @method - * @name Konva.Tween#finish - * @returns {Tween} - */ - finish() { - this.tween.finish(); - return this; - } - /** - * destroy - * @method - * @name Konva.Tween#destroy - */ - destroy() { - const nodeId = this.node._id, - thisId = this._id, - attrs = Tween.tweens[nodeId]; - - this.pause(); - - for (const key in attrs) { - delete Tween.tweens[nodeId][key]; - } - - delete Tween.attrs[nodeId][thisId]; - } -} - -/** - * Tween node properties. Shorter usage of {@link Konva.Tween} object. - * - * @method Konva.Node#to - * @param {Object} [params] tween params - * @example - * - * circle.to({ - * x : 50, - * duration : 0.5, - * onUpdate: () => console.log('props updated'), - * onFinish: () => console.log('finished'), - * }); - */ -Node.prototype.to = function (params) { - const onFinish = params.onFinish; - params.node = this; - params.onFinish = function () { - this.destroy(); - if (onFinish) { - onFinish(); - } - }; - const tween = new Tween(params as any); - tween.play(); -}; - -/* - * These eases were ported from an Adobe Flash tweening library to JavaScript - * by Xaric - */ - -/** - * @namespace Easings - * @memberof Konva - */ -export const Easings = { - /** - * back ease in - * @function - * @memberof Konva.Easings - */ - BackEaseIn(t, b, c, d) { - const s = 1.70158; - return c * (t /= d) * t * ((s + 1) * t - s) + b; - }, - /** - * back ease out - * @function - * @memberof Konva.Easings - */ - BackEaseOut(t, b, c, d) { - const s = 1.70158; - return c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b; - }, - /** - * back ease in out - * @function - * @memberof Konva.Easings - */ - BackEaseInOut(t, b, c, d) { - let s = 1.70158; - if ((t /= d / 2) < 1) { - return (c / 2) * (t * t * (((s *= 1.525) + 1) * t - s)) + b; - } - return (c / 2) * ((t -= 2) * t * (((s *= 1.525) + 1) * t + s) + 2) + b; - }, - /** - * elastic ease in - * @function - * @memberof Konva.Easings - */ - ElasticEaseIn(t, b, c, d, a, p) { - // added s = 0 - let s = 0; - if (t === 0) { - return b; - } - if ((t /= d) === 1) { - return b + c; - } - if (!p) { - p = d * 0.3; - } - if (!a || a < Math.abs(c)) { - a = c; - s = p / 4; - } else { - s = (p / (2 * Math.PI)) * Math.asin(c / a); - } - return ( - -( - a * - Math.pow(2, 10 * (t -= 1)) * - Math.sin(((t * d - s) * (2 * Math.PI)) / p) - ) + b - ); - }, - /** - * elastic ease out - * @function - * @memberof Konva.Easings - */ - ElasticEaseOut(t, b, c, d, a, p) { - // added s = 0 - let s = 0; - if (t === 0) { - return b; - } - if ((t /= d) === 1) { - return b + c; - } - if (!p) { - p = d * 0.3; - } - if (!a || a < Math.abs(c)) { - a = c; - s = p / 4; - } else { - s = (p / (2 * Math.PI)) * Math.asin(c / a); - } - return ( - a * Math.pow(2, -10 * t) * Math.sin(((t * d - s) * (2 * Math.PI)) / p) + - c + - b - ); - }, - /** - * elastic ease in out - * @function - * @memberof Konva.Easings - */ - ElasticEaseInOut(t, b, c, d, a, p) { - // added s = 0 - let s = 0; - if (t === 0) { - return b; - } - if ((t /= d / 2) === 2) { - return b + c; - } - if (!p) { - p = d * (0.3 * 1.5); - } - if (!a || a < Math.abs(c)) { - a = c; - s = p / 4; - } else { - s = (p / (2 * Math.PI)) * Math.asin(c / a); - } - if (t < 1) { - return ( - -0.5 * - (a * - Math.pow(2, 10 * (t -= 1)) * - Math.sin(((t * d - s) * (2 * Math.PI)) / p)) + - b - ); - } - return ( - a * - Math.pow(2, -10 * (t -= 1)) * - Math.sin(((t * d - s) * (2 * Math.PI)) / p) * - 0.5 + - c + - b - ); - }, - /** - * bounce ease out - * @function - * @memberof Konva.Easings - */ - BounceEaseOut(t, b, c, d) { - if ((t /= d) < 1 / 2.75) { - return c * (7.5625 * t * t) + b; - } else if (t < 2 / 2.75) { - return c * (7.5625 * (t -= 1.5 / 2.75) * t + 0.75) + b; - } else if (t < 2.5 / 2.75) { - return c * (7.5625 * (t -= 2.25 / 2.75) * t + 0.9375) + b; - } else { - return c * (7.5625 * (t -= 2.625 / 2.75) * t + 0.984375) + b; - } - }, - /** - * bounce ease in - * @function - * @memberof Konva.Easings - */ - BounceEaseIn(t, b, c, d) { - return c - Easings.BounceEaseOut(d - t, 0, c, d) + b; - }, - /** - * bounce ease in out - * @function - * @memberof Konva.Easings - */ - BounceEaseInOut(t, b, c, d) { - if (t < d / 2) { - return Easings.BounceEaseIn(t * 2, 0, c, d) * 0.5 + b; - } else { - return Easings.BounceEaseOut(t * 2 - d, 0, c, d) * 0.5 + c * 0.5 + b; - } - }, - /** - * ease in - * @function - * @memberof Konva.Easings - */ - EaseIn(t, b, c, d) { - return c * (t /= d) * t + b; - }, - /** - * ease out - * @function - * @memberof Konva.Easings - */ - EaseOut(t, b, c, d) { - return -c * (t /= d) * (t - 2) + b; - }, - /** - * ease in out - * @function - * @memberof Konva.Easings - */ - EaseInOut(t, b, c, d) { - if ((t /= d / 2) < 1) { - return (c / 2) * t * t + b; - } - return (-c / 2) * (--t * (t - 2) - 1) + b; - }, - /** - * strong ease in - * @function - * @memberof Konva.Easings - */ - StrongEaseIn(t, b, c, d) { - return c * (t /= d) * t * t * t * t + b; - }, - /** - * strong ease out - * @function - * @memberof Konva.Easings - */ - StrongEaseOut(t, b, c, d) { - return c * ((t = t / d - 1) * t * t * t * t + 1) + b; - }, - /** - * strong ease in out - * @function - * @memberof Konva.Easings - */ - StrongEaseInOut(t, b, c, d) { - if ((t /= d / 2) < 1) { - return (c / 2) * t * t * t * t * t + b; - } - return (c / 2) * ((t -= 2) * t * t * t * t + 2) + b; - }, - /** - * linear - * @function - * @memberof Konva.Easings - */ - Linear(t, b, c, d) { - return (c * t) / d + b; - }, -}; diff --git a/src/Util.ts b/src/Util.ts deleted file mode 100644 index ae5075a5e..000000000 --- a/src/Util.ts +++ /dev/null @@ -1,1045 +0,0 @@ -import { Konva } from './Global'; -import { Context } from './Context'; -import { IRect, RGB, Vector2d } from './types'; - -/* - * Last updated November 2011 - * By Simon Sarris - * www.simonsarris.com - * sarris@acm.org - * - * Free to use and distribute at will - * So long as you are nice to people, etc - */ -/* - * The usage of this class was inspired by some of the work done by a forked - * project, KineticJS-Ext by Wappworks, which is based on Simon's Transform - * class. Modified by Eric Rowell - */ - -/** - * Transform constructor. - * In most of the cases you don't need to use it in your app. Because it is for internal usage in Konva core. - * But there is a documentation for that class in case you still want - * to make some manual calculations. - * @constructor - * @param {Array} [m] Optional six-element matrix - * @memberof Konva - */ -export class Transform { - m: Array; - dirty = false; - constructor(m = [1, 0, 0, 1, 0, 0]) { - this.m = (m && m.slice()) || [1, 0, 0, 1, 0, 0]; - } - reset() { - this.m[0] = 1; - this.m[1] = 0; - this.m[2] = 0; - this.m[3] = 1; - this.m[4] = 0; - this.m[5] = 0; - } - /** - * Copy Konva.Transform object - * @method - * @name Konva.Transform#copy - * @returns {Konva.Transform} - * @example - * const tr = shape.getTransform().copy() - */ - copy() { - return new Transform(this.m); - } - copyInto(tr: Transform) { - tr.m[0] = this.m[0]; - tr.m[1] = this.m[1]; - tr.m[2] = this.m[2]; - tr.m[3] = this.m[3]; - tr.m[4] = this.m[4]; - tr.m[5] = this.m[5]; - } - /** - * Transform point - * @method - * @name Konva.Transform#point - * @param {Object} point 2D point(x, y) - * @returns {Object} 2D point(x, y) - */ - point(point: Vector2d) { - const m = this.m; - return { - x: m[0] * point.x + m[2] * point.y + m[4], - y: m[1] * point.x + m[3] * point.y + m[5], - }; - } - /** - * Apply translation - * @method - * @name Konva.Transform#translate - * @param {Number} x - * @param {Number} y - * @returns {Konva.Transform} - */ - translate(x: number, y: number) { - this.m[4] += this.m[0] * x + this.m[2] * y; - this.m[5] += this.m[1] * x + this.m[3] * y; - return this; - } - /** - * Apply scale - * @method - * @name Konva.Transform#scale - * @param {Number} sx - * @param {Number} sy - * @returns {Konva.Transform} - */ - scale(sx: number, sy: number) { - this.m[0] *= sx; - this.m[1] *= sx; - this.m[2] *= sy; - this.m[3] *= sy; - return this; - } - /** - * Apply rotation - * @method - * @name Konva.Transform#rotate - * @param {Number} rad Angle in radians - * @returns {Konva.Transform} - */ - rotate(rad: number) { - const c = Math.cos(rad); - const s = Math.sin(rad); - const m11 = this.m[0] * c + this.m[2] * s; - const m12 = this.m[1] * c + this.m[3] * s; - const m21 = this.m[0] * -s + this.m[2] * c; - const m22 = this.m[1] * -s + this.m[3] * c; - this.m[0] = m11; - this.m[1] = m12; - this.m[2] = m21; - this.m[3] = m22; - return this; - } - /** - * Returns the translation - * @method - * @name Konva.Transform#getTranslation - * @returns {Object} 2D point(x, y) - */ - getTranslation() { - return { - x: this.m[4], - y: this.m[5], - }; - } - /** - * Apply skew - * @method - * @name Konva.Transform#skew - * @param {Number} sx - * @param {Number} sy - * @returns {Konva.Transform} - */ - skew(sx: number, sy: number) { - const m11 = this.m[0] + this.m[2] * sy; - const m12 = this.m[1] + this.m[3] * sy; - const m21 = this.m[2] + this.m[0] * sx; - const m22 = this.m[3] + this.m[1] * sx; - this.m[0] = m11; - this.m[1] = m12; - this.m[2] = m21; - this.m[3] = m22; - return this; - } - /** - * Transform multiplication - * @method - * @name Konva.Transform#multiply - * @param {Konva.Transform} matrix - * @returns {Konva.Transform} - */ - multiply(matrix: Transform) { - const m11 = this.m[0] * matrix.m[0] + this.m[2] * matrix.m[1]; - const m12 = this.m[1] * matrix.m[0] + this.m[3] * matrix.m[1]; - - const m21 = this.m[0] * matrix.m[2] + this.m[2] * matrix.m[3]; - const m22 = this.m[1] * matrix.m[2] + this.m[3] * matrix.m[3]; - - const dx = this.m[0] * matrix.m[4] + this.m[2] * matrix.m[5] + this.m[4]; - const dy = this.m[1] * matrix.m[4] + this.m[3] * matrix.m[5] + this.m[5]; - - this.m[0] = m11; - this.m[1] = m12; - this.m[2] = m21; - this.m[3] = m22; - this.m[4] = dx; - this.m[5] = dy; - return this; - } - /** - * Invert the matrix - * @method - * @name Konva.Transform#invert - * @returns {Konva.Transform} - */ - invert() { - const d = 1 / (this.m[0] * this.m[3] - this.m[1] * this.m[2]); - const m0 = this.m[3] * d; - const m1 = -this.m[1] * d; - const m2 = -this.m[2] * d; - const m3 = this.m[0] * d; - const m4 = d * (this.m[2] * this.m[5] - this.m[3] * this.m[4]); - const m5 = d * (this.m[1] * this.m[4] - this.m[0] * this.m[5]); - this.m[0] = m0; - this.m[1] = m1; - this.m[2] = m2; - this.m[3] = m3; - this.m[4] = m4; - this.m[5] = m5; - return this; - } - /** - * return matrix - * @method - * @name Konva.Transform#getMatrix - */ - getMatrix() { - return this.m; - } - /** - * convert transformation matrix back into node's attributes - * @method - * @name Konva.Transform#decompose - * @returns {Konva.Transform} - */ - decompose() { - const a = this.m[0]; - const b = this.m[1]; - const c = this.m[2]; - const d = this.m[3]; - const e = this.m[4]; - const f = this.m[5]; - - const delta = a * d - b * c; - - const result = { - x: e, - y: f, - rotation: 0, - scaleX: 0, - scaleY: 0, - skewX: 0, - skewY: 0, - }; - - // Apply the QR-like decomposition. - if (a != 0 || b != 0) { - const r = Math.sqrt(a * a + b * b); - result.rotation = b > 0 ? Math.acos(a / r) : -Math.acos(a / r); - result.scaleX = r; - result.scaleY = delta / r; - result.skewX = (a * c + b * d) / delta; - result.skewY = 0; - } else if (c != 0 || d != 0) { - const s = Math.sqrt(c * c + d * d); - result.rotation = - Math.PI / 2 - (d > 0 ? Math.acos(-c / s) : -Math.acos(c / s)); - result.scaleX = delta / s; - result.scaleY = s; - result.skewX = 0; - result.skewY = (a * c + b * d) / delta; - } else { - // a = b = c = d = 0 - } - - result.rotation = Util._getRotation(result.rotation); - - return result; - } -} - -// CONSTANTS -const OBJECT_ARRAY = '[object Array]', - OBJECT_NUMBER = '[object Number]', - OBJECT_STRING = '[object String]', - OBJECT_BOOLEAN = '[object Boolean]', - PI_OVER_DEG180 = Math.PI / 180, - DEG180_OVER_PI = 180 / Math.PI, - HASH = '#', - EMPTY_STRING = '', - ZERO = '0', - KONVA_WARNING = 'Konva warning: ', - KONVA_ERROR = 'Konva error: ', - RGB_PAREN = 'rgb(', - COLORS = { - aliceblue: [240, 248, 255], - antiquewhite: [250, 235, 215], - aqua: [0, 255, 255], - aquamarine: [127, 255, 212], - azure: [240, 255, 255], - beige: [245, 245, 220], - bisque: [255, 228, 196], - black: [0, 0, 0], - blanchedalmond: [255, 235, 205], - blue: [0, 0, 255], - blueviolet: [138, 43, 226], - brown: [165, 42, 42], - burlywood: [222, 184, 135], - cadetblue: [95, 158, 160], - chartreuse: [127, 255, 0], - chocolate: [210, 105, 30], - coral: [255, 127, 80], - cornflowerblue: [100, 149, 237], - cornsilk: [255, 248, 220], - crimson: [220, 20, 60], - cyan: [0, 255, 255], - darkblue: [0, 0, 139], - darkcyan: [0, 139, 139], - darkgoldenrod: [184, 132, 11], - darkgray: [169, 169, 169], - darkgreen: [0, 100, 0], - darkgrey: [169, 169, 169], - darkkhaki: [189, 183, 107], - darkmagenta: [139, 0, 139], - darkolivegreen: [85, 107, 47], - darkorange: [255, 140, 0], - darkorchid: [153, 50, 204], - darkred: [139, 0, 0], - darksalmon: [233, 150, 122], - darkseagreen: [143, 188, 143], - darkslateblue: [72, 61, 139], - darkslategray: [47, 79, 79], - darkslategrey: [47, 79, 79], - darkturquoise: [0, 206, 209], - darkviolet: [148, 0, 211], - deeppink: [255, 20, 147], - deepskyblue: [0, 191, 255], - dimgray: [105, 105, 105], - dimgrey: [105, 105, 105], - dodgerblue: [30, 144, 255], - firebrick: [178, 34, 34], - floralwhite: [255, 255, 240], - forestgreen: [34, 139, 34], - fuchsia: [255, 0, 255], - gainsboro: [220, 220, 220], - ghostwhite: [248, 248, 255], - gold: [255, 215, 0], - goldenrod: [218, 165, 32], - gray: [128, 128, 128], - green: [0, 128, 0], - greenyellow: [173, 255, 47], - grey: [128, 128, 128], - honeydew: [240, 255, 240], - hotpink: [255, 105, 180], - indianred: [205, 92, 92], - indigo: [75, 0, 130], - ivory: [255, 255, 240], - khaki: [240, 230, 140], - lavender: [230, 230, 250], - lavenderblush: [255, 240, 245], - lawngreen: [124, 252, 0], - lemonchiffon: [255, 250, 205], - lightblue: [173, 216, 230], - lightcoral: [240, 128, 128], - lightcyan: [224, 255, 255], - lightgoldenrodyellow: [250, 250, 210], - lightgray: [211, 211, 211], - lightgreen: [144, 238, 144], - lightgrey: [211, 211, 211], - lightpink: [255, 182, 193], - lightsalmon: [255, 160, 122], - lightseagreen: [32, 178, 170], - lightskyblue: [135, 206, 250], - lightslategray: [119, 136, 153], - lightslategrey: [119, 136, 153], - lightsteelblue: [176, 196, 222], - lightyellow: [255, 255, 224], - lime: [0, 255, 0], - limegreen: [50, 205, 50], - linen: [250, 240, 230], - magenta: [255, 0, 255], - maroon: [128, 0, 0], - mediumaquamarine: [102, 205, 170], - mediumblue: [0, 0, 205], - mediumorchid: [186, 85, 211], - mediumpurple: [147, 112, 219], - mediumseagreen: [60, 179, 113], - mediumslateblue: [123, 104, 238], - mediumspringgreen: [0, 250, 154], - mediumturquoise: [72, 209, 204], - mediumvioletred: [199, 21, 133], - midnightblue: [25, 25, 112], - mintcream: [245, 255, 250], - mistyrose: [255, 228, 225], - moccasin: [255, 228, 181], - navajowhite: [255, 222, 173], - navy: [0, 0, 128], - oldlace: [253, 245, 230], - olive: [128, 128, 0], - olivedrab: [107, 142, 35], - orange: [255, 165, 0], - orangered: [255, 69, 0], - orchid: [218, 112, 214], - palegoldenrod: [238, 232, 170], - palegreen: [152, 251, 152], - paleturquoise: [175, 238, 238], - palevioletred: [219, 112, 147], - papayawhip: [255, 239, 213], - peachpuff: [255, 218, 185], - peru: [205, 133, 63], - pink: [255, 192, 203], - plum: [221, 160, 203], - powderblue: [176, 224, 230], - purple: [128, 0, 128], - rebeccapurple: [102, 51, 153], - red: [255, 0, 0], - rosybrown: [188, 143, 143], - royalblue: [65, 105, 225], - saddlebrown: [139, 69, 19], - salmon: [250, 128, 114], - sandybrown: [244, 164, 96], - seagreen: [46, 139, 87], - seashell: [255, 245, 238], - sienna: [160, 82, 45], - silver: [192, 192, 192], - skyblue: [135, 206, 235], - slateblue: [106, 90, 205], - slategray: [119, 128, 144], - slategrey: [119, 128, 144], - snow: [255, 255, 250], - springgreen: [0, 255, 127], - steelblue: [70, 130, 180], - tan: [210, 180, 140], - teal: [0, 128, 128], - thistle: [216, 191, 216], - transparent: [255, 255, 255, 0], - tomato: [255, 99, 71], - turquoise: [64, 224, 208], - violet: [238, 130, 238], - wheat: [245, 222, 179], - white: [255, 255, 255], - whitesmoke: [245, 245, 245], - yellow: [255, 255, 0], - yellowgreen: [154, 205, 5], - }, - RGB_REGEX = /rgb\((\d{1,3}),(\d{1,3}),(\d{1,3})\)/; - let animQueue: Array = []; - -const req = - (typeof requestAnimationFrame !== 'undefined' && requestAnimationFrame) || - function (f) { - setTimeout(f, 60); - }; -/** - * @namespace Util - * @memberof Konva - */ -export const Util = { - /* - * cherry-picked utilities from underscore.js - */ - _isElement(obj: any): obj is Element { - return !!(obj && obj.nodeType == 1); - }, - _isFunction(obj: any) { - return !!(obj && obj.constructor && obj.call && obj.apply); - }, - _isPlainObject(obj: any) { - return !!obj && obj.constructor === Object; - }, - _isArray(obj: any): obj is Array { - return Object.prototype.toString.call(obj) === OBJECT_ARRAY; - }, - _isNumber(obj: any): obj is number { - return ( - Object.prototype.toString.call(obj) === OBJECT_NUMBER && - !isNaN(obj) && - isFinite(obj) - ); - }, - _isString(obj: any): obj is string { - return Object.prototype.toString.call(obj) === OBJECT_STRING; - }, - _isBoolean(obj: any): obj is boolean { - return Object.prototype.toString.call(obj) === OBJECT_BOOLEAN; - }, - // arrays are objects too - isObject(val: any): val is object { - return val instanceof Object; - }, - isValidSelector(selector: any) { - if (typeof selector !== 'string') { - return false; - } - const firstChar = selector[0]; - return ( - firstChar === '#' || - firstChar === '.' || - firstChar === firstChar.toUpperCase() - ); - }, - _sign(number: number) { - if (number === 0) { - // that is not what sign usually returns - // but that is what we need - return 1; - } - if (number > 0) { - return 1; - } else { - return -1; - } - }, - - requestAnimFrame(callback: Function) { - animQueue.push(callback); - if (animQueue.length === 1) { - req(function () { - const queue = animQueue; - animQueue = []; - queue.forEach(function (cb) { - cb(); - }); - }); - } - }, - createCanvasElement() { - const canvas = document.createElement('canvas'); - // on some environments canvas.style is readonly - try { - (canvas).style = canvas.style || {}; - } catch (e) {} - return canvas; - }, - createImageElement() { - return document.createElement('img'); - }, - _isInDocument(el: any) { - while ((el = el.parentNode)) { - if (el == document) { - return true; - } - } - return false; - }, - - /* - * arg can be an image object or image data - */ - _urlToImage(url: string, callback: Function) { - // if arg is a string, then it's a data url - const imageObj = Util.createImageElement(); - imageObj.onload = function () { - callback(imageObj); - }; - imageObj.src = url; - }, - _rgbToHex(r: number, g: number, b: number) { - return ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1); - }, - _hexToRgb(hex: string): RGB { - hex = hex.replace(HASH, EMPTY_STRING); - const bigint = parseInt(hex, 16); - return { - r: (bigint >> 16) & 255, - g: (bigint >> 8) & 255, - b: bigint & 255, - }; - }, - /** - * return random hex color - * @method - * @memberof Konva.Util - * @example - * shape.fill(Konva.Util.getRandomColor()); - */ - getRandomColor() { - let randColor = ((Math.random() * 0xffffff) << 0).toString(16); - while (randColor.length < 6) { - randColor = ZERO + randColor; - } - return HASH + randColor; - }, - - /** - * get RGB components of a color - * @method - * @memberof Konva.Util - * @param {String} color - * @example - * // each of the following examples return {r:0, g:0, b:255} - * var rgb = Konva.Util.getRGB('blue'); - * var rgb = Konva.Util.getRGB('#0000ff'); - * var rgb = Konva.Util.getRGB('rgb(0,0,255)'); - */ - getRGB(color: string): RGB { - let rgb; - // color string - if (color in COLORS) { - rgb = COLORS[color as keyof typeof COLORS]; - return { - r: rgb[0], - g: rgb[1], - b: rgb[2], - }; - } else if (color[0] === HASH) { - // hex - return this._hexToRgb(color.substring(1)); - } else if (color.substr(0, 4) === RGB_PAREN) { - // rgb string - rgb = RGB_REGEX.exec(color.replace(/ /g, '')) as RegExpExecArray; - return { - r: parseInt(rgb[1], 10), - g: parseInt(rgb[2], 10), - b: parseInt(rgb[3], 10), - }; - } else { - // default - return { - r: 0, - g: 0, - b: 0, - }; - } - }, - // convert any color string to RGBA object - // from https://github.com/component/color-parser - colorToRGBA(str: string) { - str = str || 'black'; - return ( - Util._namedColorToRBA(str) || - Util._hex3ColorToRGBA(str) || - Util._hex4ColorToRGBA(str) || - Util._hex6ColorToRGBA(str) || - Util._hex8ColorToRGBA(str) || - Util._rgbColorToRGBA(str) || - Util._rgbaColorToRGBA(str) || - Util._hslColorToRGBA(str) - ); - }, - // Parse named css color. Like "green" - _namedColorToRBA(str: string) { - const c = COLORS[str.toLowerCase() as keyof typeof COLORS]; - if (!c) { - return null; - } - return { - r: c[0], - g: c[1], - b: c[2], - a: 1, - }; - }, - // Parse rgb(n, n, n) - _rgbColorToRGBA(str: string) { - if (str.indexOf('rgb(') === 0) { - str = str.match(/rgb\(([^)]+)\)/)![1]; - const parts = str.split(/ *, */).map(Number); - return { - r: parts[0], - g: parts[1], - b: parts[2], - a: 1, - }; - } - }, - // Parse rgba(n, n, n, n) - _rgbaColorToRGBA(str: string) { - if (str.indexOf('rgba(') === 0) { - str = str.match(/rgba\(([^)]+)\)/)![1]!; - const parts = str.split(/ *, */).map((n, index) => { - if (n.slice(-1) === '%') { - return index === 3 ? parseInt(n) / 100 : (parseInt(n) / 100) * 255; - } - return Number(n); - }); - return { - r: parts[0], - g: parts[1], - b: parts[2], - a: parts[3], - }; - } - }, - // Parse #nnnnnnnn - _hex8ColorToRGBA(str: string) { - if (str[0] === '#' && str.length === 9) { - return { - r: parseInt(str.slice(1, 3), 16), - g: parseInt(str.slice(3, 5), 16), - b: parseInt(str.slice(5, 7), 16), - a: parseInt(str.slice(7, 9), 16) / 0xff, - }; - } - }, - // Parse #nnnnnn - _hex6ColorToRGBA(str: string) { - if (str[0] === '#' && str.length === 7) { - return { - r: parseInt(str.slice(1, 3), 16), - g: parseInt(str.slice(3, 5), 16), - b: parseInt(str.slice(5, 7), 16), - a: 1, - }; - } - }, - // Parse #nnnn - _hex4ColorToRGBA(str: string) { - if (str[0] === '#' && str.length === 5) { - return { - r: parseInt(str[1] + str[1], 16), - g: parseInt(str[2] + str[2], 16), - b: parseInt(str[3] + str[3], 16), - a: parseInt(str[4] + str[4], 16) / 0xff, - }; - } - }, - // Parse #nnn - _hex3ColorToRGBA(str: string) { - if (str[0] === '#' && str.length === 4) { - return { - r: parseInt(str[1] + str[1], 16), - g: parseInt(str[2] + str[2], 16), - b: parseInt(str[3] + str[3], 16), - a: 1, - }; - } - }, - // Code adapted from https://github.com/Qix-/color-convert/blob/master/conversions.js#L244 - _hslColorToRGBA(str: string) { - // Check hsl() format - if (/hsl\((\d+),\s*([\d.]+)%,\s*([\d.]+)%\)/g.test(str)) { - // Extract h, s, l - const [_, ...hsl] = /hsl\((\d+),\s*([\d.]+)%,\s*([\d.]+)%\)/g.exec(str)!; - - const h = Number(hsl[0]) / 360; - const s = Number(hsl[1]) / 100; - const l = Number(hsl[2]) / 100; - - let t2; - let t3; - let val; - - if (s === 0) { - val = l * 255; - return { - r: Math.round(val), - g: Math.round(val), - b: Math.round(val), - a: 1, - }; - } - - if (l < 0.5) { - t2 = l * (1 + s); - } else { - t2 = l + s - l * s; - } - - const t1 = 2 * l - t2; - - const rgb = [0, 0, 0]; - for (let i = 0; i < 3; i++) { - t3 = h + (1 / 3) * -(i - 1); - if (t3 < 0) { - t3++; - } - - if (t3 > 1) { - t3--; - } - - if (6 * t3 < 1) { - val = t1 + (t2 - t1) * 6 * t3; - } else if (2 * t3 < 1) { - val = t2; - } else if (3 * t3 < 2) { - val = t1 + (t2 - t1) * (2 / 3 - t3) * 6; - } else { - val = t1; - } - - rgb[i] = val * 255; - } - - return { - r: Math.round(rgb[0]), - g: Math.round(rgb[1]), - b: Math.round(rgb[2]), - a: 1, - }; - } - }, - /** - * check intersection of two client rectangles - * @method - * @memberof Konva.Util - * @param {Object} r1 - { x, y, width, height } client rectangle - * @param {Object} r2 - { x, y, width, height } client rectangle - * @example - * const overlapping = Konva.Util.haveIntersection(shape1.getClientRect(), shape2.getClientRect()); - */ - haveIntersection(r1: IRect, r2: IRect) { - return !( - r2.x > r1.x + r1.width || - r2.x + r2.width < r1.x || - r2.y > r1.y + r1.height || - r2.y + r2.height < r1.y - ); - }, - cloneObject(obj: Any): Any { - const retObj: any = {}; - for (const key in obj) { - if (this._isPlainObject(obj[key])) { - retObj[key] = this.cloneObject(obj[key]); - } else if (this._isArray(obj[key])) { - retObj[key] = this.cloneArray(obj[key] as Array); - } else { - retObj[key] = obj[key]; - } - } - return retObj; - }, - cloneArray(arr: Array) { - return arr.slice(0); - }, - degToRad(deg: number) { - return deg * PI_OVER_DEG180; - }, - radToDeg(rad: number) { - return rad * DEG180_OVER_PI; - }, - _degToRad(deg: number) { - Util.warn( - 'Util._degToRad is removed. Please use public Util.degToRad instead.' - ); - return Util.degToRad(deg); - }, - _radToDeg(rad: number) { - Util.warn( - 'Util._radToDeg is removed. Please use public Util.radToDeg instead.' - ); - return Util.radToDeg(rad); - }, - _getRotation(radians: number) { - return Konva.angleDeg ? Util.radToDeg(radians) : radians; - }, - _capitalize(str: string) { - return str.charAt(0).toUpperCase() + str.slice(1); - }, - throw(str: string) { - throw new Error(KONVA_ERROR + str); - }, - error(str: string) { - console.error(KONVA_ERROR + str); - }, - warn(str: string) { - if (!Konva.showWarnings) { - return; - } - console.warn(KONVA_WARNING + str); - }, - each(obj: object, func: Function) { - for (const key in obj) { - func(key, obj[key as keyof typeof obj]); - } - }, - _inRange(val: number, left: number, right: number) { - return left <= val && val < right; - }, - _getProjectionToSegment(x1, y1, x2, y2, x3, y3) { - let x, y, dist; - - const pd2 = (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2); - if (pd2 == 0) { - x = x1; - y = y1; - dist = (x3 - x2) * (x3 - x2) + (y3 - y2) * (y3 - y2); - } else { - const u = ((x3 - x1) * (x2 - x1) + (y3 - y1) * (y2 - y1)) / pd2; - if (u < 0) { - x = x1; - y = y1; - dist = (x1 - x3) * (x1 - x3) + (y1 - y3) * (y1 - y3); - } else if (u > 1.0) { - x = x2; - y = y2; - dist = (x2 - x3) * (x2 - x3) + (y2 - y3) * (y2 - y3); - } else { - x = x1 + u * (x2 - x1); - y = y1 + u * (y2 - y1); - dist = (x - x3) * (x - x3) + (y - y3) * (y - y3); - } - } - return [x, y, dist]; - }, - // line as array of points. - // line might be closed - _getProjectionToLine(pt: Vector2d, line: Array, isClosed: boolean) { - const pc = Util.cloneObject(pt); - let dist = Number.MAX_VALUE; - line.forEach(function (p1, i) { - if (!isClosed && i === line.length - 1) { - return; - } - const p2 = line[(i + 1) % line.length]; - const proj = Util._getProjectionToSegment( - p1.x, - p1.y, - p2.x, - p2.y, - pt.x, - pt.y - ); - const px = proj[0], - py = proj[1], - pdist = proj[2]; - if (pdist < dist) { - pc.x = px; - pc.y = py; - dist = pdist; - } - }); - return pc; - }, - _prepareArrayForTween(startArray, endArray, isClosed) { - const start: Vector2d[] = [], - end: Vector2d[] = []; - if (startArray.length > endArray.length) { - const temp = endArray; - endArray = startArray; - startArray = temp; - } - for (let n = 0; n < startArray.length; n += 2) { - start.push({ - x: startArray[n], - y: startArray[n + 1], - }); - } - for (let n = 0; n < endArray.length; n += 2) { - end.push({ - x: endArray[n], - y: endArray[n + 1], - }); - } - - const newStart: number[] = []; - end.forEach(function (point) { - const pr = Util._getProjectionToLine(point, start, isClosed); - newStart.push(pr.x); - newStart.push(pr.y); - }); - return newStart; - }, - _prepareToStringify(obj: any): T | null { - let desc; - - obj.visitedByCircularReferenceRemoval = true; - - for (const key in obj) { - if ( - !(obj.hasOwnProperty(key) && obj[key] && typeof obj[key] == 'object') - ) { - continue; - } - desc = Object.getOwnPropertyDescriptor(obj, key); - if ( - obj[key].visitedByCircularReferenceRemoval || - Util._isElement(obj[key]) - ) { - if (desc.configurable) { - delete obj[key]; - } else { - return null; - } - } else if (Util._prepareToStringify(obj[key]) === null) { - if (desc.configurable) { - delete obj[key]; - } else { - return null; - } - } - } - - delete obj.visitedByCircularReferenceRemoval; - - return obj; - }, - // very simplified version of Object.assign - _assign(target: T, source: U) { - for (const key in source) { - (target)[key] = source[key]; - } - return target as T & U; - }, - _getFirstPointerId(evt) { - if (!evt.touches) { - // try to use pointer id or fake id - return evt.pointerId || 999; - } else { - return evt.changedTouches[0].identifier; - } - }, - releaseCanvas(...canvases: HTMLCanvasElement[]) { - if (!Konva.releaseCanvasOnDestroy) return; - - canvases.forEach((c) => { - c.width = 0; - c.height = 0; - }); - }, - drawRoundedRectPath( - context: Context, - width: number, - height: number, - cornerRadius: number | number[] - ) { - let topLeft = 0; - let topRight = 0; - let bottomLeft = 0; - let bottomRight = 0; - if (typeof cornerRadius === 'number') { - topLeft = - topRight = - bottomLeft = - bottomRight = - Math.min(cornerRadius, width / 2, height / 2); - } else { - topLeft = Math.min(cornerRadius[0] || 0, width / 2, height / 2); - topRight = Math.min(cornerRadius[1] || 0, width / 2, height / 2); - bottomRight = Math.min(cornerRadius[2] || 0, width / 2, height / 2); - bottomLeft = Math.min(cornerRadius[3] || 0, width / 2, height / 2); - } - context.moveTo(topLeft, 0); - context.lineTo(width - topRight, 0); - context.arc( - width - topRight, - topRight, - topRight, - (Math.PI * 3) / 2, - 0, - false - ); - context.lineTo(width, height - bottomRight); - context.arc( - width - bottomRight, - height - bottomRight, - bottomRight, - 0, - Math.PI / 2, - false - ); - context.lineTo(bottomLeft, height); - context.arc( - bottomLeft, - height - bottomLeft, - bottomLeft, - Math.PI / 2, - Math.PI, - false - ); - context.lineTo(0, topLeft); - context.arc(topLeft, topLeft, topLeft, Math.PI, (Math.PI * 3) / 2, false); - }, -}; diff --git a/src/Validators.ts b/src/Validators.ts deleted file mode 100644 index a8534b3b9..000000000 --- a/src/Validators.ts +++ /dev/null @@ -1,210 +0,0 @@ -import { Konva } from './Global'; -import { Util } from './Util'; - -function _formatValue(val: any) { - if (Util._isString(val)) { - return '"' + val + '"'; - } - if (Object.prototype.toString.call(val) === '[object Number]') { - return val; - } - if (Util._isBoolean(val)) { - return val; - } - return Object.prototype.toString.call(val); -} - -export function RGBComponent(val: number) { - if (val > 255) { - return 255; - } else if (val < 0) { - return 0; - } - return Math.round(val); -} -export function alphaComponent(val: number) { - if (val > 1) { - return 1; - } else if (val < 0.0001) { - // chrome does not honor alpha values of 0 - return 0.0001; - } - - return val; -} - -export function getNumberValidator() { - if (Konva.isUnminified) { - return function (val: T, attr: string): T { - if (!Util._isNumber(val)) { - Util.warn( - _formatValue(val) + - ' is a not valid value for "' + - attr + - '" attribute. The value should be a number.' - ); - } - return val; - }; - } -} - -export function getNumberOrArrayOfNumbersValidator(noOfElements: number) { - if (Konva.isUnminified) { - return function (val: T, attr: string): T { - let isNumber = Util._isNumber(val); - let isValidArray = Util._isArray(val) && val.length == noOfElements; - if (!isNumber && !isValidArray) { - Util.warn( - _formatValue(val) + - ' is a not valid value for "' + - attr + - '" attribute. The value should be a number or Array(' + - noOfElements + - ')' - ); - } - return val; - }; - } -} - -export function getNumberOrAutoValidator() { - if (Konva.isUnminified) { - return function (val: T, attr: string): T { - var isNumber = Util._isNumber(val); - var isAuto = val === 'auto'; - - if (!(isNumber || isAuto)) { - Util.warn( - _formatValue(val) + - ' is a not valid value for "' + - attr + - '" attribute. The value should be a number or "auto".' - ); - } - return val; - }; - } -} - -export function getStringValidator() { - if (Konva.isUnminified) { - return function (val: T, attr: string): T { - if (!Util._isString(val)) { - Util.warn( - _formatValue(val) + - ' is a not valid value for "' + - attr + - '" attribute. The value should be a string.' - ); - } - return val; - }; - } -} - -export function getStringOrGradientValidator() { - if (Konva.isUnminified) { - return function (val: T, attr: string): T { - const isString = Util._isString(val); - const isGradient = - Object.prototype.toString.call(val) === '[object CanvasGradient]' || - (val && val['addColorStop']); - if (!(isString || isGradient)) { - Util.warn( - _formatValue(val) + - ' is a not valid value for "' + - attr + - '" attribute. The value should be a string or a native gradient.' - ); - } - return val; - }; - } -} - -export function getFunctionValidator() { - if (Konva.isUnminified) { - return function (val: T, attr: string): T { - if (!Util._isFunction(val)) { - Util.warn( - _formatValue(val) + - ' is a not valid value for "' + - attr + - '" attribute. The value should be a function.' - ); - } - return val; - }; - } -} -export function getNumberArrayValidator() { - if (Konva.isUnminified) { - return function (val: T, attr: string): T { - // Retrieve TypedArray constructor as found in MDN (if TypedArray is available) - // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray#description - const TypedArray = Int8Array ? Object.getPrototypeOf(Int8Array) : null; - if (TypedArray && val instanceof TypedArray) { - return val; - } - if (!Util._isArray(val)) { - Util.warn( - _formatValue(val) + - ' is a not valid value for "' + - attr + - '" attribute. The value should be a array of numbers.' - ); - } else { - val.forEach(function (item: any) { - if (!Util._isNumber(item)) { - Util.warn( - '"' + - attr + - '" attribute has non numeric element ' + - item + - '. Make sure that all elements are numbers.' - ); - } - }); - } - return val; - }; - } -} -export function getBooleanValidator() { - if (Konva.isUnminified) { - return function (val: T, attr: string): T { - var isBool = val === true || val === false; - if (!isBool) { - Util.warn( - _formatValue(val) + - ' is a not valid value for "' + - attr + - '" attribute. The value should be a boolean.' - ); - } - return val; - }; - } -} -export function getComponentValidator(components: string[]) { - if (Konva.isUnminified) { - return function (val: T, attr: string): T { - // ignore validation on undefined value, because it will reset to defalt - if (val === undefined || val === null) { - return val; - } - if (!Util.isObject(val)) { - Util.warn( - _formatValue(val) + - ' is a not valid value for "' + - attr + - '" attribute. The value should be an object with properties ' + - components - ); - } - return val; - }; - } -} diff --git a/src/_CoreInternals.ts b/src/_CoreInternals.ts deleted file mode 100644 index 9744e3a06..000000000 --- a/src/_CoreInternals.ts +++ /dev/null @@ -1,45 +0,0 @@ -// what is core parts of Konva? -import { Konva as Global } from './Global'; - -import { Util, Transform } from './Util'; -import { Node } from './Node'; -import { Container } from './Container'; - -import { Stage, stages } from './Stage'; - -import { Layer } from './Layer'; -import { FastLayer } from './FastLayer'; - -import { Group } from './Group'; - -import { DD } from './DragAndDrop'; - -import { Shape, shapes } from './Shape'; - -import { Animation } from './Animation'; -import { Tween, Easings } from './Tween'; - -import { Context } from './Context'; -import { Canvas } from './Canvas'; - -export const Konva = Util._assign(Global, { - Util, - Transform, - Node, - Container, - Stage, - stages, - Layer, - FastLayer, - Group, - DD, - Shape, - shapes, - Animation, - Tween, - Easings, - Context, - Canvas, -}); - -export default Konva; diff --git a/src/_FullInternals.ts b/src/_FullInternals.ts deleted file mode 100644 index d257f4cd8..000000000 --- a/src/_FullInternals.ts +++ /dev/null @@ -1,89 +0,0 @@ -// we need to import core of the Konva and then extend it with all additional objects - -import { Konva as Core } from './_CoreInternals'; - -// shapes -import { Arc } from './shapes/Arc'; -import { Arrow } from './shapes/Arrow'; -import { Circle } from './shapes/Circle'; -import { Ellipse } from './shapes/Ellipse'; -import { Image } from './shapes/Image'; -import { Label, Tag } from './shapes/Label'; -import { Line } from './shapes/Line'; -import { Path } from './shapes/Path'; -import { Rect } from './shapes/Rect'; -import { RegularPolygon } from './shapes/RegularPolygon'; -import { Ring } from './shapes/Ring'; -import { Sprite } from './shapes/Sprite'; -import { Star } from './shapes/Star'; -import { Text } from './shapes/Text'; -import { TextPath } from './shapes/TextPath'; -import { Transformer } from './shapes/Transformer'; -import { Wedge } from './shapes/Wedge'; - -// filters -import { Blur } from './filters/Blur'; -import { Brighten } from './filters/Brighten'; -import { Contrast } from './filters/Contrast'; -import { Emboss } from './filters/Emboss'; -import { Enhance } from './filters/Enhance'; -import { Grayscale } from './filters/Grayscale'; -import { HSL } from './filters/HSL'; -import { HSV } from './filters/HSV'; -import { Invert } from './filters/Invert'; -import { Kaleidoscope } from './filters/Kaleidoscope'; -import { Mask } from './filters/Mask'; -import { Noise } from './filters/Noise'; -import { Pixelate } from './filters/Pixelate'; -import { Posterize } from './filters/Posterize'; -import { RGB } from './filters/RGB'; -import { RGBA } from './filters/RGBA'; -import { Sepia } from './filters/Sepia'; -import { Solarize } from './filters/Solarize'; -import { Threshold } from './filters/Threshold'; - -export const Konva = Core.Util._assign(Core, { - Arc, - Arrow, - Circle, - Ellipse, - Image, - Label, - Tag, - Line, - Path, - Rect, - RegularPolygon, - Ring, - Sprite, - Star, - Text, - TextPath, - Transformer, - Wedge, - /** - * @namespace Filters - * @memberof Konva - */ - Filters: { - Blur, - Brighten, - Contrast, - Emboss, - Enhance, - Grayscale, - HSL, - HSV, - Invert, - Kaleidoscope, - Mask, - Noise, - Pixelate, - Posterize, - RGB, - RGBA, - Sepia, - Solarize, - Threshold, - }, -}); diff --git a/src/filters/Blur.ts b/src/filters/Blur.ts deleted file mode 100644 index 4998e8e7e..000000000 --- a/src/filters/Blur.ts +++ /dev/null @@ -1,390 +0,0 @@ -import { Factory } from '../Factory'; -import { Node, Filter } from '../Node'; -import { getNumberValidator } from '../Validators'; -/* - the Gauss filter - master repo: https://github.com/pavelpower/kineticjsGaussFilter -*/ -/* - - StackBlur - a fast almost Gaussian Blur For Canvas - - Version: 0.5 - Author: Mario Klingemann - Contact: mario@quasimondo.com - Website: http://www.quasimondo.com/StackBlurForCanvas - Twitter: @quasimondo - - In case you find this class useful - especially in commercial projects - - I am not totally unhappy for a small donation to my PayPal account - mario@quasimondo.de - - Or support me on flattr: - https://flattr.com/thing/72791/StackBlur-a-fast-almost-Gaussian-Blur-Effect-for-CanvasJavascript - - Copyright (c) 2010 Mario Klingemann - - Permission is hereby granted, free of charge, to any person - obtaining a copy of this software and associated documentation - files (the "Software"), to deal in the Software without - restriction, including without limitation the rights to use, - copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following - conditions: - - The above copyright notice and this permission notice shall be - included in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - OTHER DEALINGS IN THE SOFTWARE. - */ - -function BlurStack(this: any) { - this.r = 0; - this.g = 0; - this.b = 0; - this.a = 0; - this.next = null; -} - -const mul_table = [ - 512, 512, 456, 512, 328, 456, 335, 512, 405, 328, 271, 456, 388, 335, 292, - 512, 454, 405, 364, 328, 298, 271, 496, 456, 420, 388, 360, 335, 312, 292, - 273, 512, 482, 454, 428, 405, 383, 364, 345, 328, 312, 298, 284, 271, 259, - 496, 475, 456, 437, 420, 404, 388, 374, 360, 347, 335, 323, 312, 302, 292, - 282, 273, 265, 512, 497, 482, 468, 454, 441, 428, 417, 405, 394, 383, 373, - 364, 354, 345, 337, 328, 320, 312, 305, 298, 291, 284, 278, 271, 265, 259, - 507, 496, 485, 475, 465, 456, 446, 437, 428, 420, 412, 404, 396, 388, 381, - 374, 367, 360, 354, 347, 341, 335, 329, 323, 318, 312, 307, 302, 297, 292, - 287, 282, 278, 273, 269, 265, 261, 512, 505, 497, 489, 482, 475, 468, 461, - 454, 447, 441, 435, 428, 422, 417, 411, 405, 399, 394, 389, 383, 378, 373, - 368, 364, 359, 354, 350, 345, 341, 337, 332, 328, 324, 320, 316, 312, 309, - 305, 301, 298, 294, 291, 287, 284, 281, 278, 274, 271, 268, 265, 262, 259, - 257, 507, 501, 496, 491, 485, 480, 475, 470, 465, 460, 456, 451, 446, 442, - 437, 433, 428, 424, 420, 416, 412, 408, 404, 400, 396, 392, 388, 385, 381, - 377, 374, 370, 367, 363, 360, 357, 354, 350, 347, 344, 341, 338, 335, 332, - 329, 326, 323, 320, 318, 315, 312, 310, 307, 304, 302, 299, 297, 294, 292, - 289, 287, 285, 282, 280, 278, 275, 273, 271, 269, 267, 265, 263, 261, 259, -]; - -const shg_table = [ - 9, 11, 12, 13, 13, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 17, 17, 17, 17, 17, - 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 18, 19, 19, 19, 19, 19, 19, 19, 19, - 19, 19, 19, 19, 19, 19, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, - 20, 20, 20, 20, 20, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, - 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23, 23, 23, 23, 23, 23, 23, - 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, - 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, - 23, 23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, - 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, - 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, - 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, - 24, 24, 24, 24, 24, 24, 24, -]; - -function filterGaussBlurRGBA(imageData, radius) { - const pixels = imageData.data, - width = imageData.width, - height = imageData.height; - - let x, - y, - i, - p, - yp, - yi, - yw, - r_sum, - g_sum, - b_sum, - a_sum, - r_out_sum, - g_out_sum, - b_out_sum, - a_out_sum, - r_in_sum, - g_in_sum, - b_in_sum, - a_in_sum, - pr, - pg, - pb, - pa, - rbs; - - const div = radius + radius + 1, - widthMinus1 = width - 1, - heightMinus1 = height - 1, - radiusPlus1 = radius + 1, - sumFactor = (radiusPlus1 * (radiusPlus1 + 1)) / 2, - stackStart = new BlurStack(), - mul_sum = mul_table[radius], - shg_sum = shg_table[radius]; - - let stackEnd = null, - stack = stackStart, - stackIn: any = null, - stackOut: any = null; - - for (i = 1; i < div; i++) { - stack = stack.next = new BlurStack(); - if (i === radiusPlus1) { - stackEnd = stack; - } - } - - stack.next = stackStart; - - yw = yi = 0; - - for (y = 0; y < height; y++) { - r_in_sum = - g_in_sum = - b_in_sum = - a_in_sum = - r_sum = - g_sum = - b_sum = - a_sum = - 0; - - r_out_sum = radiusPlus1 * (pr = pixels[yi]); - g_out_sum = radiusPlus1 * (pg = pixels[yi + 1]); - b_out_sum = radiusPlus1 * (pb = pixels[yi + 2]); - a_out_sum = radiusPlus1 * (pa = pixels[yi + 3]); - - r_sum += sumFactor * pr; - g_sum += sumFactor * pg; - b_sum += sumFactor * pb; - a_sum += sumFactor * pa; - - stack = stackStart; - - for (i = 0; i < radiusPlus1; i++) { - stack.r = pr; - stack.g = pg; - stack.b = pb; - stack.a = pa; - stack = stack.next; - } - - for (i = 1; i < radiusPlus1; i++) { - p = yi + ((widthMinus1 < i ? widthMinus1 : i) << 2); - r_sum += (stack.r = pr = pixels[p]) * (rbs = radiusPlus1 - i); - g_sum += (stack.g = pg = pixels[p + 1]) * rbs; - b_sum += (stack.b = pb = pixels[p + 2]) * rbs; - a_sum += (stack.a = pa = pixels[p + 3]) * rbs; - - r_in_sum += pr; - g_in_sum += pg; - b_in_sum += pb; - a_in_sum += pa; - - stack = stack.next; - } - - stackIn = stackStart; - stackOut = stackEnd; - for (x = 0; x < width; x++) { - pixels[yi + 3] = pa = (a_sum * mul_sum) >> shg_sum; - if (pa !== 0) { - pa = 255 / pa; - pixels[yi] = ((r_sum * mul_sum) >> shg_sum) * pa; - pixels[yi + 1] = ((g_sum * mul_sum) >> shg_sum) * pa; - pixels[yi + 2] = ((b_sum * mul_sum) >> shg_sum) * pa; - } else { - pixels[yi] = pixels[yi + 1] = pixels[yi + 2] = 0; - } - - r_sum -= r_out_sum; - g_sum -= g_out_sum; - b_sum -= b_out_sum; - a_sum -= a_out_sum; - - r_out_sum -= stackIn.r; - g_out_sum -= stackIn.g; - b_out_sum -= stackIn.b; - a_out_sum -= stackIn.a; - - p = (yw + ((p = x + radius + 1) < widthMinus1 ? p : widthMinus1)) << 2; - - r_in_sum += stackIn.r = pixels[p]; - g_in_sum += stackIn.g = pixels[p + 1]; - b_in_sum += stackIn.b = pixels[p + 2]; - a_in_sum += stackIn.a = pixels[p + 3]; - - r_sum += r_in_sum; - g_sum += g_in_sum; - b_sum += b_in_sum; - a_sum += a_in_sum; - - stackIn = stackIn.next; - - r_out_sum += pr = stackOut.r; - g_out_sum += pg = stackOut.g; - b_out_sum += pb = stackOut.b; - a_out_sum += pa = stackOut.a; - - r_in_sum -= pr; - g_in_sum -= pg; - b_in_sum -= pb; - a_in_sum -= pa; - - stackOut = stackOut.next; - - yi += 4; - } - yw += width; - } - - for (x = 0; x < width; x++) { - g_in_sum = - b_in_sum = - a_in_sum = - r_in_sum = - g_sum = - b_sum = - a_sum = - r_sum = - 0; - - yi = x << 2; - r_out_sum = radiusPlus1 * (pr = pixels[yi]); - g_out_sum = radiusPlus1 * (pg = pixels[yi + 1]); - b_out_sum = radiusPlus1 * (pb = pixels[yi + 2]); - a_out_sum = radiusPlus1 * (pa = pixels[yi + 3]); - - r_sum += sumFactor * pr; - g_sum += sumFactor * pg; - b_sum += sumFactor * pb; - a_sum += sumFactor * pa; - - stack = stackStart; - - for (i = 0; i < radiusPlus1; i++) { - stack.r = pr; - stack.g = pg; - stack.b = pb; - stack.a = pa; - stack = stack.next; - } - - yp = width; - - for (i = 1; i <= radius; i++) { - yi = (yp + x) << 2; - - r_sum += (stack.r = pr = pixels[yi]) * (rbs = radiusPlus1 - i); - g_sum += (stack.g = pg = pixels[yi + 1]) * rbs; - b_sum += (stack.b = pb = pixels[yi + 2]) * rbs; - a_sum += (stack.a = pa = pixels[yi + 3]) * rbs; - - r_in_sum += pr; - g_in_sum += pg; - b_in_sum += pb; - a_in_sum += pa; - - stack = stack.next; - - if (i < heightMinus1) { - yp += width; - } - } - - yi = x; - stackIn = stackStart; - stackOut = stackEnd; - for (y = 0; y < height; y++) { - p = yi << 2; - pixels[p + 3] = pa = (a_sum * mul_sum) >> shg_sum; - if (pa > 0) { - pa = 255 / pa; - pixels[p] = ((r_sum * mul_sum) >> shg_sum) * pa; - pixels[p + 1] = ((g_sum * mul_sum) >> shg_sum) * pa; - pixels[p + 2] = ((b_sum * mul_sum) >> shg_sum) * pa; - } else { - pixels[p] = pixels[p + 1] = pixels[p + 2] = 0; - } - - r_sum -= r_out_sum; - g_sum -= g_out_sum; - b_sum -= b_out_sum; - a_sum -= a_out_sum; - - r_out_sum -= stackIn.r; - g_out_sum -= stackIn.g; - b_out_sum -= stackIn.b; - a_out_sum -= stackIn.a; - - p = - (x + - ((p = y + radiusPlus1) < heightMinus1 ? p : heightMinus1) * width) << - 2; - - r_sum += r_in_sum += stackIn.r = pixels[p]; - g_sum += g_in_sum += stackIn.g = pixels[p + 1]; - b_sum += b_in_sum += stackIn.b = pixels[p + 2]; - a_sum += a_in_sum += stackIn.a = pixels[p + 3]; - - stackIn = stackIn.next; - - r_out_sum += pr = stackOut.r; - g_out_sum += pg = stackOut.g; - b_out_sum += pb = stackOut.b; - a_out_sum += pa = stackOut.a; - - r_in_sum -= pr; - g_in_sum -= pg; - b_in_sum -= pb; - a_in_sum -= pa; - - stackOut = stackOut.next; - - yi += width; - } - } -} - -/** - * Blur Filter - * @function - * @name Blur - * @memberof Konva.Filters - * @param {Object} imageData - * @example - * node.cache(); - * node.filters([Konva.Filters.Blur]); - * node.blurRadius(10); - */ -export const Blur: Filter = function Blur(imageData) { - const radius = Math.round(this.blurRadius()); - - if (radius > 0) { - filterGaussBlurRGBA(imageData, radius); - } -}; - -Factory.addGetterSetter( - Node, - 'blurRadius', - 0, - getNumberValidator(), - Factory.afterSetFilter -); - -/** - * get/set blur radius. Use with {@link Konva.Filters.Blur} filter - * @name Konva.Node#blurRadius - * @method - * @param {Integer} radius - * @returns {Integer} - */ diff --git a/src/filters/Brighten.ts b/src/filters/Brighten.ts deleted file mode 100644 index d7d981b7a..000000000 --- a/src/filters/Brighten.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { Factory } from '../Factory'; -import { Node, Filter } from '../Node'; -import { getNumberValidator } from '../Validators'; - -/** - * Brighten Filter. - * @function - * @memberof Konva.Filters - * @param {Object} imageData - * @example - * node.cache(); - * node.filters([Konva.Filters.Brighten]); - * node.brightness(0.8); - */ -export const Brighten: Filter = function (imageData) { - const brightness = this.brightness() * 255, - data = imageData.data, - len = data.length; - - for (let i = 0; i < len; i += 4) { - // red - data[i] += brightness; - // green - data[i + 1] += brightness; - // blue - data[i + 2] += brightness; - } -}; - -Factory.addGetterSetter( - Node, - 'brightness', - 0, - getNumberValidator(), - Factory.afterSetFilter -); -/** - * get/set filter brightness. The brightness is a number between -1 and 1.  Positive values - * brighten the pixels and negative values darken them. Use with {@link Konva.Filters.Brighten} filter. - * @name Konva.Node#brightness - * @method - - * @param {Number} brightness value between -1 and 1 - * @returns {Number} - */ diff --git a/src/filters/Contrast.ts b/src/filters/Contrast.ts deleted file mode 100644 index 80ec38b40..000000000 --- a/src/filters/Contrast.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { Factory } from '../Factory'; -import { Node, Filter } from '../Node'; -import { getNumberValidator } from '../Validators'; -/** - * Contrast Filter. - * @function - * @memberof Konva.Filters - * @param {Object} imageData - * @example - * node.cache(); - * node.filters([Konva.Filters.Contrast]); - * node.contrast(10); - */ - -export const Contrast: Filter = function (imageData) { - const adjust = Math.pow((this.contrast() + 100) / 100, 2); - - const data = imageData.data, - nPixels = data.length; - let red = 150, - green = 150, - blue = 150; - - for (let i = 0; i < nPixels; i += 4) { - red = data[i]; - green = data[i + 1]; - blue = data[i + 2]; - - //Red channel - red /= 255; - red -= 0.5; - red *= adjust; - red += 0.5; - red *= 255; - - //Green channel - green /= 255; - green -= 0.5; - green *= adjust; - green += 0.5; - green *= 255; - - //Blue channel - blue /= 255; - blue -= 0.5; - blue *= adjust; - blue += 0.5; - blue *= 255; - - red = red < 0 ? 0 : red > 255 ? 255 : red; - green = green < 0 ? 0 : green > 255 ? 255 : green; - blue = blue < 0 ? 0 : blue > 255 ? 255 : blue; - - data[i] = red; - data[i + 1] = green; - data[i + 2] = blue; - } -}; - -/** - * get/set filter contrast. The contrast is a number between -100 and 100. - * Use with {@link Konva.Filters.Contrast} filter. - * @name Konva.Node#contrast - * @method - * @param {Number} contrast value between -100 and 100 - * @returns {Number} - */ -Factory.addGetterSetter( - Node, - 'contrast', - 0, - getNumberValidator(), - Factory.afterSetFilter -); diff --git a/src/filters/Emboss.ts b/src/filters/Emboss.ts deleted file mode 100644 index a040cc8f4..000000000 --- a/src/filters/Emboss.ts +++ /dev/null @@ -1,202 +0,0 @@ -import { Factory } from '../Factory'; -import { Node, Filter } from '../Node'; -import { Util } from '../Util'; -import { getNumberValidator } from '../Validators'; -/** - * Emboss Filter. - * Pixastic Lib - Emboss filter - v0.1.0 - * Copyright (c) 2008 Jacob Seidelin, jseidelin@nihilogic.dk, http://blog.nihilogic.dk/ - * License: [http://www.pixastic.com/lib/license.txt] - * @function - * @memberof Konva.Filters - * @param {Object} imageData - * @example - * node.cache(); - * node.filters([Konva.Filters.Emboss]); - * node.embossStrength(0.8); - * node.embossWhiteLevel(0.3); - * node.embossDirection('right'); - * node.embossBlend(true); - */ -export const Emboss: Filter = function (imageData) { - // pixastic strength is between 0 and 10. I want it between 0 and 1 - // pixastic greyLevel is between 0 and 255. I want it between 0 and 1. Also, - // a max value of greyLevel yields a white emboss, and the min value yields a black - // emboss. Therefore, I changed greyLevel to whiteLevel - const strength = this.embossStrength() * 10, - greyLevel = this.embossWhiteLevel() * 255, - direction = this.embossDirection(), - blend = this.embossBlend(), - data = imageData.data, - w = imageData.width, - h = imageData.height, - w4 = w * 4; - let dirY = 0, - dirX = 0, - y = h; - - switch (direction) { - case 'top-left': - dirY = -1; - dirX = -1; - break; - case 'top': - dirY = -1; - dirX = 0; - break; - case 'top-right': - dirY = -1; - dirX = 1; - break; - case 'right': - dirY = 0; - dirX = 1; - break; - case 'bottom-right': - dirY = 1; - dirX = 1; - break; - case 'bottom': - dirY = 1; - dirX = 0; - break; - case 'bottom-left': - dirY = 1; - dirX = -1; - break; - case 'left': - dirY = 0; - dirX = -1; - break; - default: - Util.error('Unknown emboss direction: ' + direction); - } - - do { - const offsetY = (y - 1) * w4; - - let otherY = dirY; - if (y + otherY < 1) { - otherY = 0; - } - if (y + otherY > h) { - otherY = 0; - } - - const offsetYOther = (y - 1 + otherY) * w * 4; - - let x = w; - do { - const offset = offsetY + (x - 1) * 4; - - let otherX = dirX; - if (x + otherX < 1) { - otherX = 0; - } - if (x + otherX > w) { - otherX = 0; - } - - const offsetOther = offsetYOther + (x - 1 + otherX) * 4; - - const dR = data[offset] - data[offsetOther]; - const dG = data[offset + 1] - data[offsetOther + 1]; - const dB = data[offset + 2] - data[offsetOther + 2]; - - let dif = dR; - const absDif = dif > 0 ? dif : -dif; - - const absG = dG > 0 ? dG : -dG; - const absB = dB > 0 ? dB : -dB; - - if (absG > absDif) { - dif = dG; - } - if (absB > absDif) { - dif = dB; - } - - dif *= strength; - - if (blend) { - const r = data[offset] + dif; - const g = data[offset + 1] + dif; - const b = data[offset + 2] + dif; - - data[offset] = r > 255 ? 255 : r < 0 ? 0 : r; - data[offset + 1] = g > 255 ? 255 : g < 0 ? 0 : g; - data[offset + 2] = b > 255 ? 255 : b < 0 ? 0 : b; - } else { - let grey = greyLevel - dif; - if (grey < 0) { - grey = 0; - } else if (grey > 255) { - grey = 255; - } - - data[offset] = data[offset + 1] = data[offset + 2] = grey; - } - } while (--x); - } while (--y); -}; - -Factory.addGetterSetter( - Node, - 'embossStrength', - 0.5, - getNumberValidator(), - Factory.afterSetFilter -); -/** - * get/set emboss strength. Use with {@link Konva.Filters.Emboss} filter. - * @name Konva.Node#embossStrength - * @method - * @param {Number} level between 0 and 1. Default is 0.5 - * @returns {Number} - */ - -Factory.addGetterSetter( - Node, - 'embossWhiteLevel', - 0.5, - getNumberValidator(), - Factory.afterSetFilter -); -/** - * get/set emboss white level. Use with {@link Konva.Filters.Emboss} filter. - * @name Konva.Node#embossWhiteLevel - * @method - * @param {Number} embossWhiteLevel between 0 and 1. Default is 0.5 - * @returns {Number} - */ - -Factory.addGetterSetter( - Node, - 'embossDirection', - 'top-left', - undefined, - Factory.afterSetFilter -); -/** - * get/set emboss direction. Use with {@link Konva.Filters.Emboss} filter. - * @name Konva.Node#embossDirection - * @method - * @param {String} embossDirection can be top-left, top, top-right, right, bottom-right, bottom, bottom-left or left - * The default is top-left - * @returns {String} - */ - -Factory.addGetterSetter( - Node, - 'embossBlend', - false, - undefined, - Factory.afterSetFilter -); -/** - * get/set emboss blend. Use with {@link Konva.Filters.Emboss} filter. - * @name Konva.Node#embossBlend - * @method - * @param {Boolean} embossBlend - * @returns {Boolean} - */ diff --git a/src/filters/Enhance.ts b/src/filters/Enhance.ts deleted file mode 100644 index d92d68344..000000000 --- a/src/filters/Enhance.ts +++ /dev/null @@ -1,150 +0,0 @@ -import { Factory } from '../Factory'; -import { Node, Filter } from '../Node'; -import { getNumberValidator } from '../Validators'; - -function remap(fromValue: number, fromMin: number, fromMax: number, toMin: number, toMax: number) { - // Compute the range of the data - const fromRange = fromMax - fromMin, - toRange = toMax - toMin; - - // If either range is 0, then the value can only be mapped to 1 value - if (fromRange === 0) { - return toMin + toRange / 2; - } - if (toRange === 0) { - return toMin; - } - - // (1) untranslate, (2) unscale, (3) rescale, (4) retranslate - let toValue = (fromValue - fromMin) / fromRange; - toValue = toRange * toValue + toMin; - - return toValue; -} - -/** - * Enhance Filter. Adjusts the colors so that they span the widest - * possible range (ie 0-255). Performs w*h pixel reads and w*h pixel - * writes. - * @function - * @name Enhance - * @memberof Konva.Filters - * @param {Object} imageData - * @author ippo615 - * @example - * node.cache(); - * node.filters([Konva.Filters.Enhance]); - * node.enhance(0.4); - */ -export const Enhance: Filter = function (imageData) { - const data = imageData.data, - nSubPixels = data.length; - let rMin = data[0], - rMax = rMin, - r, - gMin = data[1], - gMax = gMin, - g, - bMin = data[2], - bMax = bMin, - b; - - // If we are not enhancing anything - don't do any computation - const enhanceAmount = this.enhance(); - if (enhanceAmount === 0) { - return; - } - - // 1st Pass - find the min and max for each channel: - for (let i = 0; i < nSubPixels; i += 4) { - r = data[i + 0]; - if (r < rMin) { - rMin = r; - } else if (r > rMax) { - rMax = r; - } - g = data[i + 1]; - if (g < gMin) { - gMin = g; - } else if (g > gMax) { - gMax = g; - } - b = data[i + 2]; - if (b < bMin) { - bMin = b; - } else if (b > bMax) { - bMax = b; - } - //a = data[i + 3]; - //if (a < aMin) { aMin = a; } else - //if (a > aMax) { aMax = a; } - } - - // If there is only 1 level - don't remap - if (rMax === rMin) { - rMax = 255; - rMin = 0; - } - if (gMax === gMin) { - gMax = 255; - gMin = 0; - } - if (bMax === bMin) { - bMax = 255; - bMin = 0; - } - - let rMid, - rGoalMax, - rGoalMin, - gMid, - gGoalMax, - gGoalMin, - bMid, - bGoalMax, - bGoalMin; - - // If the enhancement is positive - stretch the histogram - if (enhanceAmount > 0) { - rGoalMax = rMax + enhanceAmount * (255 - rMax); - rGoalMin = rMin - enhanceAmount * (rMin - 0); - gGoalMax = gMax + enhanceAmount * (255 - gMax); - gGoalMin = gMin - enhanceAmount * (gMin - 0); - bGoalMax = bMax + enhanceAmount * (255 - bMax); - bGoalMin = bMin - enhanceAmount * (bMin - 0); - // If the enhancement is negative - compress the histogram - } else { - rMid = (rMax + rMin) * 0.5; - rGoalMax = rMax + enhanceAmount * (rMax - rMid); - rGoalMin = rMin + enhanceAmount * (rMin - rMid); - gMid = (gMax + gMin) * 0.5; - gGoalMax = gMax + enhanceAmount * (gMax - gMid); - gGoalMin = gMin + enhanceAmount * (gMin - gMid); - bMid = (bMax + bMin) * 0.5; - bGoalMax = bMax + enhanceAmount * (bMax - bMid); - bGoalMin = bMin + enhanceAmount * (bMin - bMid); - } - - // Pass 2 - remap everything, except the alpha - for (let i = 0; i < nSubPixels; i += 4) { - data[i + 0] = remap(data[i + 0], rMin, rMax, rGoalMin, rGoalMax); - data[i + 1] = remap(data[i + 1], gMin, gMax, gGoalMin, gGoalMax); - data[i + 2] = remap(data[i + 2], bMin, bMax, bGoalMin, bGoalMax); - //data[i + 3] = remap(data[i + 3], aMin, aMax, aGoalMin, aGoalMax); - } -}; - -/** - * get/set enhance. Use with {@link Konva.Filters.Enhance} filter. -1 to 1 values - * @name Konva.Node#enhance - * @method - * @param {Float} amount - * @returns {Float} - */ -Factory.addGetterSetter( - Node, - 'enhance', - 0, - getNumberValidator(), - Factory.afterSetFilter -); diff --git a/src/filters/Grayscale.ts b/src/filters/Grayscale.ts deleted file mode 100644 index 4457a95fb..000000000 --- a/src/filters/Grayscale.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Filter } from '../Node'; - -/** - * Grayscale Filter - * @function - * @memberof Konva.Filters - * @param {Object} imageData - * @example - * node.cache(); - * node.filters([Konva.Filters.Grayscale]); - */ -export const Grayscale: Filter = function (imageData) { - const data = imageData.data, - len = data.length; - - for (let i = 0; i < len; i += 4) { - const brightness = 0.34 * data[i] + 0.5 * data[i + 1] + 0.16 * data[i + 2]; - // red - data[i] = brightness; - // green - data[i + 1] = brightness; - // blue - data[i + 2] = brightness; - } -}; diff --git a/src/filters/HSL.ts b/src/filters/HSL.ts deleted file mode 100644 index 47763ad2d..000000000 --- a/src/filters/HSL.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { Factory } from '../Factory'; -import { Node, Filter } from '../Node'; -import { getNumberValidator } from '../Validators'; - -Factory.addGetterSetter( - Node, - 'hue', - 0, - getNumberValidator(), - Factory.afterSetFilter -); -/** - * get/set hsv hue in degrees. Use with {@link Konva.Filters.HSV} or {@link Konva.Filters.HSL} filter. - * @name Konva.Node#hue - * @method - * @param {Number} hue value between 0 and 359 - * @returns {Number} - */ - -Factory.addGetterSetter( - Node, - 'saturation', - 0, - getNumberValidator(), - Factory.afterSetFilter -); -/** - * get/set hsv saturation. Use with {@link Konva.Filters.HSV} or {@link Konva.Filters.HSL} filter. - * @name Konva.Node#saturation - * @method - * @param {Number} saturation 0 is no change, -1.0 halves the saturation, 1.0 doubles, etc.. - * @returns {Number} - */ - -Factory.addGetterSetter( - Node, - 'luminance', - 0, - getNumberValidator(), - Factory.afterSetFilter -); -/** - * get/set hsl luminance. Use with {@link Konva.Filters.HSL} filter. - * @name Konva.Node#luminance - * @method - * @param {Number} value from -1 to 1 - * @returns {Number} - */ - -/** - * HSL Filter. Adjusts the hue, saturation and luminance (or lightness) - * @function - * @memberof Konva.Filters - * @param {Object} imageData - * @author ippo615 - * @example - * image.filters([Konva.Filters.HSL]); - * image.luminance(0.2); - */ - -export const HSL: Filter = function (imageData) { - const data = imageData.data, - nPixels = data.length, - v = 1, - s = Math.pow(2, this.saturation()), - h = Math.abs(this.hue() + 360) % 360, - l = this.luminance() * 127; - - // Basis for the technique used: - // http://beesbuzz.biz/code/hsv_color_transforms.php - // V is the value multiplier (1 for none, 2 for double, 0.5 for half) - // S is the saturation multiplier (1 for none, 2 for double, 0.5 for half) - // H is the hue shift in degrees (0 to 360) - // vsu = V*S*cos(H*PI/180); - // vsw = V*S*sin(H*PI/180); - //[ .299V+.701vsu+.168vsw .587V-.587vsu+.330vsw .114V-.114vsu-.497vsw ] [R] - //[ .299V-.299vsu-.328vsw .587V+.413vsu+.035vsw .114V-.114vsu+.292vsw ]*[G] - //[ .299V-.300vsu+1.25vsw .587V-.588vsu-1.05vsw .114V+.886vsu-.203vsw ] [B] - - // Precompute the values in the matrix: - const vsu = v * s * Math.cos((h * Math.PI) / 180), - vsw = v * s * Math.sin((h * Math.PI) / 180); - // (result spot)(source spot) - const rr = 0.299 * v + 0.701 * vsu + 0.167 * vsw, - rg = 0.587 * v - 0.587 * vsu + 0.33 * vsw, - rb = 0.114 * v - 0.114 * vsu - 0.497 * vsw; - const gr = 0.299 * v - 0.299 * vsu - 0.328 * vsw, - gg = 0.587 * v + 0.413 * vsu + 0.035 * vsw, - gb = 0.114 * v - 0.114 * vsu + 0.293 * vsw; - const br = 0.299 * v - 0.3 * vsu + 1.25 * vsw, - bg = 0.587 * v - 0.586 * vsu - 1.05 * vsw, - bb = 0.114 * v + 0.886 * vsu - 0.2 * vsw; - - let r: number, g: number, b: number, a: number; - - for (let i = 0; i < nPixels; i += 4) { - r = data[i + 0]; - g = data[i + 1]; - b = data[i + 2]; - a = data[i + 3]; - - data[i + 0] = rr * r + rg * g + rb * b + l; - data[i + 1] = gr * r + gg * g + gb * b + l; - data[i + 2] = br * r + bg * g + bb * b + l; - data[i + 3] = a; // alpha - } -}; diff --git a/src/filters/HSV.ts b/src/filters/HSV.ts deleted file mode 100644 index 855f66445..000000000 --- a/src/filters/HSV.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { Factory } from '../Factory'; -import { Node, Filter } from '../Node'; -import { getNumberValidator } from '../Validators'; - -/** - * HSV Filter. Adjusts the hue, saturation and value - * @function - * @name HSV - * @memberof Konva.Filters - * @param {Object} imageData - * @author ippo615 - * @example - * image.filters([Konva.Filters.HSV]); - * image.value(200); - */ - -export const HSV: Filter = function (imageData) { - const data = imageData.data, - nPixels = data.length, - v = Math.pow(2, this.value()), - s = Math.pow(2, this.saturation()), - h = Math.abs(this.hue() + 360) % 360; - - // Basis for the technique used: - // http://beesbuzz.biz/code/hsv_color_transforms.php - // V is the value multiplier (1 for none, 2 for double, 0.5 for half) - // S is the saturation multiplier (1 for none, 2 for double, 0.5 for half) - // H is the hue shift in degrees (0 to 360) - // vsu = V*S*cos(H*PI/180); - // vsw = V*S*sin(H*PI/180); - //[ .299V+.701vsu+.168vsw .587V-.587vsu+.330vsw .114V-.114vsu-.497vsw ] [R] - //[ .299V-.299vsu-.328vsw .587V+.413vsu+.035vsw .114V-.114vsu+.292vsw ]*[G] - //[ .299V-.300vsu+1.25vsw .587V-.588vsu-1.05vsw .114V+.886vsu-.203vsw ] [B] - - // Precompute the values in the matrix: - const vsu = v * s * Math.cos((h * Math.PI) / 180), - vsw = v * s * Math.sin((h * Math.PI) / 180); - // (result spot)(source spot) - const rr = 0.299 * v + 0.701 * vsu + 0.167 * vsw, - rg = 0.587 * v - 0.587 * vsu + 0.33 * vsw, - rb = 0.114 * v - 0.114 * vsu - 0.497 * vsw; - const gr = 0.299 * v - 0.299 * vsu - 0.328 * vsw, - gg = 0.587 * v + 0.413 * vsu + 0.035 * vsw, - gb = 0.114 * v - 0.114 * vsu + 0.293 * vsw; - const br = 0.299 * v - 0.3 * vsu + 1.25 * vsw, - bg = 0.587 * v - 0.586 * vsu - 1.05 * vsw, - bb = 0.114 * v + 0.886 * vsu - 0.2 * vsw; - - let r, g, b, a; - - for (let i = 0; i < nPixels; i += 4) { - r = data[i + 0]; - g = data[i + 1]; - b = data[i + 2]; - a = data[i + 3]; - - data[i + 0] = rr * r + rg * g + rb * b; - data[i + 1] = gr * r + gg * g + gb * b; - data[i + 2] = br * r + bg * g + bb * b; - data[i + 3] = a; // alpha - } -}; - -Factory.addGetterSetter( - Node, - 'hue', - 0, - getNumberValidator(), - Factory.afterSetFilter -); -/** - * get/set hsv hue in degrees. Use with {@link Konva.Filters.HSV} or {@link Konva.Filters.HSL} filter. - * @name Konva.Node#hue - * @method - * @param {Number} hue value between 0 and 359 - * @returns {Number} - */ - -Factory.addGetterSetter( - Node, - 'saturation', - 0, - getNumberValidator(), - Factory.afterSetFilter -); -/** - * get/set hsv saturation. Use with {@link Konva.Filters.HSV} or {@link Konva.Filters.HSL} filter. - * @name Konva.Node#saturation - * @method - * @param {Number} saturation 0 is no change, -1.0 halves the saturation, 1.0 doubles, etc.. - * @returns {Number} - */ - -Factory.addGetterSetter( - Node, - 'value', - 0, - getNumberValidator(), - Factory.afterSetFilter -); -/** - * get/set hsv value. Use with {@link Konva.Filters.HSV} filter. - * @name Konva.Node#value - * @method - * @param {Number} value 0 is no change, -1.0 halves the value, 1.0 doubles, etc.. - * @returns {Number} - */ diff --git a/src/filters/Invert.ts b/src/filters/Invert.ts deleted file mode 100644 index 766c9506c..000000000 --- a/src/filters/Invert.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Filter } from '../Node'; -/** - * Invert Filter - * @function - * @memberof Konva.Filters - * @param {Object} imageData - * @example - * node.cache(); - * node.filters([Konva.Filters.Invert]); - */ -export const Invert: Filter = function (imageData) { - const data = imageData.data, - len = data.length; - - for (let i = 0; i < len; i += 4) { - // red - data[i] = 255 - data[i]; - // green - data[i + 1] = 255 - data[i + 1]; - // blue - data[i + 2] = 255 - data[i + 2]; - } -}; diff --git a/src/filters/Kaleidoscope.ts b/src/filters/Kaleidoscope.ts deleted file mode 100644 index bdca220c1..000000000 --- a/src/filters/Kaleidoscope.ts +++ /dev/null @@ -1,272 +0,0 @@ -import { Factory } from '../Factory'; -import { Filter, Node } from '../Node'; -import { Util } from '../Util'; -import { getNumberValidator } from '../Validators'; - -/* - * ToPolar Filter. Converts image data to polar coordinates. Performs - * w*h*4 pixel reads and w*h pixel writes. The r axis is placed along - * what would be the y axis and the theta axis along the x axis. - * @function - * @author ippo615 - * @memberof Konva.Filters - * @param {ImageData} src, the source image data (what will be transformed) - * @param {ImageData} dst, the destination image data (where it will be saved) - * @param {Object} opt - * @param {Number} [opt.polarCenterX] horizontal location for the center of the circle, - * default is in the middle - * @param {Number} [opt.polarCenterY] vertical location for the center of the circle, - * default is in the middle - */ - -const ToPolar = function (src, dst, opt) { - const srcPixels = src.data, - dstPixels = dst.data, - xSize = src.width, - ySize = src.height, - xMid = opt.polarCenterX || xSize / 2, - yMid = opt.polarCenterY || ySize / 2; - - // Find the largest radius - let rMax = Math.sqrt(xMid * xMid + yMid * yMid); - let x = xSize - xMid; - let y = ySize - yMid; - const rad = Math.sqrt(x * x + y * y); - rMax = rad > rMax ? rad : rMax; - - // We'll be uisng y as the radius, and x as the angle (theta=t) - const rSize = ySize, - tSize = xSize; - - // We want to cover all angles (0-360) and we need to convert to - // radians (*PI/180) - const conversion = ((360 / tSize) * Math.PI) / 180; - - // var x1, x2, x1i, x2i, y1, y2, y1i, y2i, scale; - - for (let theta = 0; theta < tSize; theta += 1) { - const sin = Math.sin(theta * conversion); - const cos = Math.cos(theta * conversion); - for (let radius = 0; radius < rSize; radius += 1) { - x = Math.floor(xMid + ((rMax * radius) / rSize) * cos); - y = Math.floor(yMid + ((rMax * radius) / rSize) * sin); - let i = (y * xSize + x) * 4; - const r = srcPixels[i + 0]; - const g = srcPixels[i + 1]; - const b = srcPixels[i + 2]; - const a = srcPixels[i + 3]; - - // Store it - //i = (theta * xSize + radius) * 4; - i = (theta + radius * xSize) * 4; - dstPixels[i + 0] = r; - dstPixels[i + 1] = g; - dstPixels[i + 2] = b; - dstPixels[i + 3] = a; - } - } -}; - -/* - * FromPolar Filter. Converts image data from polar coordinates back to rectangular. - * Performs w*h*4 pixel reads and w*h pixel writes. - * @function - * @author ippo615 - * @memberof Konva.Filters - * @param {ImageData} src, the source image data (what will be transformed) - * @param {ImageData} dst, the destination image data (where it will be saved) - * @param {Object} opt - * @param {Number} [opt.polarCenterX] horizontal location for the center of the circle, - * default is in the middle - * @param {Number} [opt.polarCenterY] vertical location for the center of the circle, - * default is in the middle - * @param {Number} [opt.polarRotation] amount to rotate the image counterclockwis, - * 0 is no rotation, 360 degrees is a full rotation - */ - -const FromPolar = function (src, dst, opt) { - const srcPixels = src.data, - dstPixels = dst.data, - xSize = src.width, - ySize = src.height, - xMid = opt.polarCenterX || xSize / 2, - yMid = opt.polarCenterY || ySize / 2; - - // Find the largest radius - let rMax = Math.sqrt(xMid * xMid + yMid * yMid); - let x = xSize - xMid; - let y = ySize - yMid; - const rad = Math.sqrt(x * x + y * y); - rMax = rad > rMax ? rad : rMax; - - // We'll be uisng x as the radius, and y as the angle (theta=t) - const rSize = ySize, - tSize = xSize, - phaseShift = opt.polarRotation || 0; - - // We need to convert to degrees and we need to make sure - // it's between (0-360) - // var conversion = tSize/360*180/Math.PI; - //var conversion = tSize/360*180/Math.PI; - - let x1, y1; - - for (x = 0; x < xSize; x += 1) { - for (y = 0; y < ySize; y += 1) { - const dx = x - xMid; - const dy = y - yMid; - const radius = (Math.sqrt(dx * dx + dy * dy) * rSize) / rMax; - let theta = ((Math.atan2(dy, dx) * 180) / Math.PI + 360 + phaseShift) % 360; - theta = (theta * tSize) / 360; - x1 = Math.floor(theta); - y1 = Math.floor(radius); - let i = (y1 * xSize + x1) * 4; - const r = srcPixels[i + 0]; - const g = srcPixels[i + 1]; - const b = srcPixels[i + 2]; - const a = srcPixels[i + 3]; - - // Store it - i = (y * xSize + x) * 4; - dstPixels[i + 0] = r; - dstPixels[i + 1] = g; - dstPixels[i + 2] = b; - dstPixels[i + 3] = a; - } - } -}; - -//Konva.Filters.ToPolar = Util._FilterWrapDoubleBuffer(ToPolar); -//Konva.Filters.FromPolar = Util._FilterWrapDoubleBuffer(FromPolar); - -// create a temporary canvas for working - shared between multiple calls - -/* - * Kaleidoscope Filter. - * @function - * @name Kaleidoscope - * @author ippo615 - * @memberof Konva.Filters - * @example - * node.cache(); - * node.filters([Konva.Filters.Kaleidoscope]); - * node.kaleidoscopePower(3); - * node.kaleidoscopeAngle(45); - */ -export const Kaleidoscope: Filter = function (imageData) { - const xSize = imageData.width, - ySize = imageData.height; - - let x, y, xoff, i, r, g, b, a, srcPos, dstPos; - let power = Math.round(this.kaleidoscopePower()); - const angle = Math.round(this.kaleidoscopeAngle()); - const offset = Math.floor((xSize * (angle % 360)) / 360); - - if (power < 1) { - return; - } - - // Work with our shared buffer canvas - const tempCanvas = Util.createCanvasElement(); - tempCanvas.width = xSize; - tempCanvas.height = ySize; - const scratchData = tempCanvas - .getContext('2d')! - .getImageData(0, 0, xSize, ySize); - Util.releaseCanvas(tempCanvas); - // Convert thhe original to polar coordinates - ToPolar(imageData, scratchData, { - polarCenterX: xSize / 2, - polarCenterY: ySize / 2, - }); - - // Determine how big each section will be, if it's too small - // make it bigger - let minSectionSize = xSize / Math.pow(2, power); - while (minSectionSize <= 8) { - minSectionSize = minSectionSize * 2; - power -= 1; - } - minSectionSize = Math.ceil(minSectionSize); - let sectionSize = minSectionSize; - - // Copy the offset region to 0 - // Depending on the size of filter and location of the offset we may need - // to copy the section backwards to prevent it from rewriting itself - let xStart = 0, - xEnd = sectionSize, - xDelta = 1; - if (offset + minSectionSize > xSize) { - xStart = sectionSize; - xEnd = 0; - xDelta = -1; - } - for (y = 0; y < ySize; y += 1) { - for (x = xStart; x !== xEnd; x += xDelta) { - xoff = Math.round(x + offset) % xSize; - srcPos = (xSize * y + xoff) * 4; - r = scratchData.data[srcPos + 0]; - g = scratchData.data[srcPos + 1]; - b = scratchData.data[srcPos + 2]; - a = scratchData.data[srcPos + 3]; - dstPos = (xSize * y + x) * 4; - scratchData.data[dstPos + 0] = r; - scratchData.data[dstPos + 1] = g; - scratchData.data[dstPos + 2] = b; - scratchData.data[dstPos + 3] = a; - } - } - - // Perform the actual effect - for (y = 0; y < ySize; y += 1) { - sectionSize = Math.floor(minSectionSize); - for (i = 0; i < power; i += 1) { - for (x = 0; x < sectionSize + 1; x += 1) { - srcPos = (xSize * y + x) * 4; - r = scratchData.data[srcPos + 0]; - g = scratchData.data[srcPos + 1]; - b = scratchData.data[srcPos + 2]; - a = scratchData.data[srcPos + 3]; - dstPos = (xSize * y + sectionSize * 2 - x - 1) * 4; - scratchData.data[dstPos + 0] = r; - scratchData.data[dstPos + 1] = g; - scratchData.data[dstPos + 2] = b; - scratchData.data[dstPos + 3] = a; - } - sectionSize *= 2; - } - } - - // Convert back from polar coordinates - FromPolar(scratchData, imageData, { polarRotation: 0 }); -}; - -/** - * get/set kaleidoscope power. Use with {@link Konva.Filters.Kaleidoscope} filter. - * @name Konva.Node#kaleidoscopePower - * @method - * @param {Integer} power of kaleidoscope - * @returns {Integer} - */ -Factory.addGetterSetter( - Node, - 'kaleidoscopePower', - 2, - getNumberValidator(), - Factory.afterSetFilter -); - -/** - * get/set kaleidoscope angle. Use with {@link Konva.Filters.Kaleidoscope} filter. - * @name Konva.Node#kaleidoscopeAngle - * @method - * @param {Integer} degrees - * @returns {Integer} - */ -Factory.addGetterSetter( - Node, - 'kaleidoscopeAngle', - 0, - getNumberValidator(), - Factory.afterSetFilter -); diff --git a/src/filters/Mask.ts b/src/filters/Mask.ts deleted file mode 100644 index 0668c0d58..000000000 --- a/src/filters/Mask.ts +++ /dev/null @@ -1,209 +0,0 @@ -import { Factory } from '../Factory'; -import { Filter, Node } from '../Node'; -import { getNumberValidator } from '../Validators'; - -function pixelAt(idata, x, y) { - let idx = (y * idata.width + x) * 4; - const d: Array = []; - d.push( - idata.data[idx++], - idata.data[idx++], - idata.data[idx++], - idata.data[idx++] - ); - return d; -} - -function rgbDistance(p1, p2) { - return Math.sqrt( - Math.pow(p1[0] - p2[0], 2) + - Math.pow(p1[1] - p2[1], 2) + - Math.pow(p1[2] - p2[2], 2) - ); -} - -function rgbMean(pTab) { - const m = [0, 0, 0]; - - for (let i = 0; i < pTab.length; i++) { - m[0] += pTab[i][0]; - m[1] += pTab[i][1]; - m[2] += pTab[i][2]; - } - - m[0] /= pTab.length; - m[1] /= pTab.length; - m[2] /= pTab.length; - - return m; -} - -function backgroundMask(idata, threshold) { - const rgbv_no = pixelAt(idata, 0, 0); - const rgbv_ne = pixelAt(idata, idata.width - 1, 0); - const rgbv_so = pixelAt(idata, 0, idata.height - 1); - const rgbv_se = pixelAt(idata, idata.width - 1, idata.height - 1); - - const thres = threshold || 10; - if ( - rgbDistance(rgbv_no, rgbv_ne) < thres && - rgbDistance(rgbv_ne, rgbv_se) < thres && - rgbDistance(rgbv_se, rgbv_so) < thres && - rgbDistance(rgbv_so, rgbv_no) < thres - ) { - // Mean color - const mean = rgbMean([rgbv_ne, rgbv_no, rgbv_se, rgbv_so]); - - // Mask based on color distance - const mask: Array = []; - for (let i = 0; i < idata.width * idata.height; i++) { - const d = rgbDistance(mean, [ - idata.data[i * 4], - idata.data[i * 4 + 1], - idata.data[i * 4 + 2], - ]); - mask[i] = d < thres ? 0 : 255; - } - - return mask; - } -} - -function applyMask(idata, mask) { - for (let i = 0; i < idata.width * idata.height; i++) { - idata.data[4 * i + 3] = mask[i]; - } -} - -function erodeMask(mask, sw, sh) { - const weights = [1, 1, 1, 1, 0, 1, 1, 1, 1]; - const side = Math.round(Math.sqrt(weights.length)); - const halfSide = Math.floor(side / 2); - - const maskResult: Array = []; - for (let y = 0; y < sh; y++) { - for (let x = 0; x < sw; x++) { - const so = y * sw + x; - let a = 0; - for (let cy = 0; cy < side; cy++) { - for (let cx = 0; cx < side; cx++) { - const scy = y + cy - halfSide; - const scx = x + cx - halfSide; - - if (scy >= 0 && scy < sh && scx >= 0 && scx < sw) { - const srcOff = scy * sw + scx; - const wt = weights[cy * side + cx]; - - a += mask[srcOff] * wt; - } - } - } - - maskResult[so] = a === 255 * 8 ? 255 : 0; - } - } - - return maskResult; -} - -function dilateMask(mask, sw, sh) { - const weights = [1, 1, 1, 1, 1, 1, 1, 1, 1]; - const side = Math.round(Math.sqrt(weights.length)); - const halfSide = Math.floor(side / 2); - - const maskResult: Array = []; - for (let y = 0; y < sh; y++) { - for (let x = 0; x < sw; x++) { - const so = y * sw + x; - let a = 0; - for (let cy = 0; cy < side; cy++) { - for (let cx = 0; cx < side; cx++) { - const scy = y + cy - halfSide; - const scx = x + cx - halfSide; - - if (scy >= 0 && scy < sh && scx >= 0 && scx < sw) { - const srcOff = scy * sw + scx; - const wt = weights[cy * side + cx]; - - a += mask[srcOff] * wt; - } - } - } - - maskResult[so] = a >= 255 * 4 ? 255 : 0; - } - } - - return maskResult; -} - -function smoothEdgeMask(mask, sw, sh) { - const weights = [1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9]; - const side = Math.round(Math.sqrt(weights.length)); - const halfSide = Math.floor(side / 2); - - const maskResult: Array = []; - for (let y = 0; y < sh; y++) { - for (let x = 0; x < sw; x++) { - const so = y * sw + x; - let a = 0; - for (let cy = 0; cy < side; cy++) { - for (let cx = 0; cx < side; cx++) { - const scy = y + cy - halfSide; - const scx = x + cx - halfSide; - - if (scy >= 0 && scy < sh && scx >= 0 && scx < sw) { - const srcOff = scy * sw + scx; - const wt = weights[cy * side + cx]; - - a += mask[srcOff] * wt; - } - } - } - - maskResult[so] = a; - } - } - - return maskResult; -} - -/** - * Mask Filter - * @function - * @name Mask - * @memberof Konva.Filters - * @param {Object} imageData - * @example - * node.cache(); - * node.filters([Konva.Filters.Mask]); - * node.threshold(200); - */ -export const Mask: Filter = function (imageData) { - // Detect pixels close to the background color - const threshold = this.threshold(); - let mask = backgroundMask(imageData, threshold); - if (mask) { - // Erode - mask = erodeMask(mask, imageData.width, imageData.height); - - // Dilate - mask = dilateMask(mask, imageData.width, imageData.height); - - // Gradient - mask = smoothEdgeMask(mask, imageData.width, imageData.height); - - // Apply mask - applyMask(imageData, mask); - } - - return imageData; -}; - -Factory.addGetterSetter( - Node, - 'threshold', - 0, - getNumberValidator(), - Factory.afterSetFilter -); diff --git a/src/filters/Noise.ts b/src/filters/Noise.ts deleted file mode 100644 index 77a9d7101..000000000 --- a/src/filters/Noise.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { Factory } from '../Factory'; -import { Filter, Node } from '../Node'; -import { getNumberValidator } from '../Validators'; - -/** - * Noise Filter. Randomly adds or substracts to the color channels - * @function - * @name Noise - * @memberof Konva.Filters - * @param {Object} imageData - * @author ippo615 - * @example - * node.cache(); - * node.filters([Konva.Filters.Noise]); - * node.noise(0.8); - */ -export const Noise: Filter = function (imageData) { - const amount = this.noise() * 255, - data = imageData.data, - nPixels = data.length, - half = amount / 2; - - for (let i = 0; i < nPixels; i += 4) { - data[i + 0] += half - 2 * half * Math.random(); - data[i + 1] += half - 2 * half * Math.random(); - data[i + 2] += half - 2 * half * Math.random(); - } -}; - -Factory.addGetterSetter( - Node, - 'noise', - 0.2, - getNumberValidator(), - Factory.afterSetFilter -); -/** - * get/set noise amount. Must be a value between 0 and 1. Use with {@link Konva.Filters.Noise} filter. - * @name Konva.Node#noise - * @method - * @param {Number} noise - * @returns {Number} - */ diff --git a/src/filters/Pixelate.ts b/src/filters/Pixelate.ts deleted file mode 100644 index 31dc27279..000000000 --- a/src/filters/Pixelate.ts +++ /dev/null @@ -1,121 +0,0 @@ - -import { Factory } from '../Factory'; -import { Util } from '../Util'; -import { Node, Filter } from '../Node'; -import { getNumberValidator } from '../Validators'; - -/** - * Pixelate Filter. Averages groups of pixels and redraws - * them as larger pixels - * @function - * @name Pixelate - * @memberof Konva.Filters - * @param {Object} imageData - * @author ippo615 - * @example - * node.cache(); - * node.filters([Konva.Filters.Pixelate]); - * node.pixelSize(10); - */ - -export const Pixelate: Filter = function (imageData) { - let pixelSize = Math.ceil(this.pixelSize()), - width = imageData.width, - height = imageData.height, - x, - y, - i, - //pixelsPerBin = pixelSize * pixelSize, - red, - green, - blue, - alpha, - nBinsX = Math.ceil(width / pixelSize), - nBinsY = Math.ceil(height / pixelSize), - xBinStart, - xBinEnd, - yBinStart, - yBinEnd, - xBin, - yBin, - pixelsInBin, - data = imageData.data; - - if (pixelSize <= 0) { - Util.error('pixelSize value can not be <= 0'); - return; - } - - for (xBin = 0; xBin < nBinsX; xBin += 1) { - for (yBin = 0; yBin < nBinsY; yBin += 1) { - // Initialize the color accumlators to 0 - red = 0; - green = 0; - blue = 0; - alpha = 0; - - // Determine which pixels are included in this bin - xBinStart = xBin * pixelSize; - xBinEnd = xBinStart + pixelSize; - yBinStart = yBin * pixelSize; - yBinEnd = yBinStart + pixelSize; - - // Add all of the pixels to this bin! - pixelsInBin = 0; - for (x = xBinStart; x < xBinEnd; x += 1) { - if (x >= width) { - continue; - } - for (y = yBinStart; y < yBinEnd; y += 1) { - if (y >= height) { - continue; - } - i = (width * y + x) * 4; - red += data[i + 0]; - green += data[i + 1]; - blue += data[i + 2]; - alpha += data[i + 3]; - pixelsInBin += 1; - } - } - - // Make sure the channels are between 0-255 - red = red / pixelsInBin; - green = green / pixelsInBin; - blue = blue / pixelsInBin; - alpha = alpha / pixelsInBin; - - // Draw this bin - for (x = xBinStart; x < xBinEnd; x += 1) { - if (x >= width) { - continue; - } - for (y = yBinStart; y < yBinEnd; y += 1) { - if (y >= height) { - continue; - } - i = (width * y + x) * 4; - data[i + 0] = red; - data[i + 1] = green; - data[i + 2] = blue; - data[i + 3] = alpha; - } - } - } - } -}; - -Factory.addGetterSetter( - Node, - 'pixelSize', - 8, - getNumberValidator(), - Factory.afterSetFilter -); -/** - * get/set pixel size. Use with {@link Konva.Filters.Pixelate} filter. - * @name Konva.Node#pixelSize - * @method - * @param {Integer} pixelSize - * @returns {Integer} - */ diff --git a/src/filters/Posterize.ts b/src/filters/Posterize.ts deleted file mode 100644 index 6fb89b919..000000000 --- a/src/filters/Posterize.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { Factory } from '../Factory'; -import { Filter, Node } from '../Node'; -import { getNumberValidator } from '../Validators'; - -/** - * Posterize Filter. Adjusts the channels so that there are no more - * than n different values for that channel. This is also applied - * to the alpha channel. - * @function - * @name Posterize - * @author ippo615 - * @memberof Konva.Filters - * @param {Object} imageData - * @example - * node.cache(); - * node.filters([Konva.Filters.Posterize]); - * node.levels(0.8); // between 0 and 1 - */ -export const Posterize: Filter = function (imageData) { - // level must be between 1 and 255 - const levels = Math.round(this.levels() * 254) + 1, - data = imageData.data, - len = data.length, - scale = 255 / levels; - - for (let i = 0; i < len; i += 1) { - data[i] = Math.floor(data[i] / scale) * scale; - } -}; - -Factory.addGetterSetter( - Node, - 'levels', - 0.5, - getNumberValidator(), - Factory.afterSetFilter -); - -/** - * get/set levels. Must be a number between 0 and 1. Use with {@link Konva.Filters.Posterize} filter. - * @name Konva.Node#levels - * @method - * @param {Number} level between 0 and 1 - * @returns {Number} - */ diff --git a/src/filters/RGB.ts b/src/filters/RGB.ts deleted file mode 100644 index e5ea879c1..000000000 --- a/src/filters/RGB.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { Factory } from '../Factory'; -import { Node, Filter } from '../Node'; -import { RGBComponent } from '../Validators'; - -/** - * RGB Filter - * @function - * @name RGB - * @memberof Konva.Filters - * @param {Object} imageData - * @author ippo615 - * @example - * node.cache(); - * node.filters([Konva.Filters.RGB]); - * node.blue(120); - * node.green(200); - */ -export const RGB: Filter = function (imageData) { - const data = imageData.data, - nPixels = data.length, - red = this.red(), - green = this.green(), - blue = this.blue(); - - for (let i = 0; i < nPixels; i += 4) { - const brightness = - (0.34 * data[i] + 0.5 * data[i + 1] + 0.16 * data[i + 2]) / 255; - data[i] = brightness * red; // r - data[i + 1] = brightness * green; // g - data[i + 2] = brightness * blue; // b - data[i + 3] = data[i + 3]; // alpha - } -}; - -Factory.addGetterSetter(Node, 'red', 0, function (this: Node, val) { - this._filterUpToDate = false; - if (val > 255) { - return 255; - } else if (val < 0) { - return 0; - } else { - return Math.round(val); - } -}); -/** - * get/set filter red value. Use with {@link Konva.Filters.RGB} filter. - * @name red - * @method - * @memberof Konva.Node.prototype - * @param {Integer} red value between 0 and 255 - * @returns {Integer} - */ - -Factory.addGetterSetter(Node, 'green', 0, function (this: Node, val) { - this._filterUpToDate = false; - if (val > 255) { - return 255; - } else if (val < 0) { - return 0; - } else { - return Math.round(val); - } -}); -/** - * get/set filter green value. Use with {@link Konva.Filters.RGB} filter. - * @name green - * @method - * @memberof Konva.Node.prototype - * @param {Integer} green value between 0 and 255 - * @returns {Integer} - */ - -Factory.addGetterSetter(Node, 'blue', 0, RGBComponent, Factory.afterSetFilter); -/** - * get/set filter blue value. Use with {@link Konva.Filters.RGB} filter. - * @name blue - * @method - * @memberof Konva.Node.prototype - * @param {Integer} blue value between 0 and 255 - * @returns {Integer} - */ diff --git a/src/filters/RGBA.ts b/src/filters/RGBA.ts deleted file mode 100644 index dcd140c07..000000000 --- a/src/filters/RGBA.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { Factory } from '../Factory'; -import { Node, Filter } from '../Node'; -import { RGBComponent } from '../Validators'; - -/** - * RGBA Filter - * @function - * @name RGBA - * @memberof Konva.Filters - * @param {Object} imageData - * @author codefo - * @example - * node.cache(); - * node.filters([Konva.Filters.RGBA]); - * node.blue(120); - * node.green(200); - * node.alpha(0.3); - */ - -export const RGBA: Filter = function (imageData) { - const data = imageData.data, - nPixels = data.length, - red = this.red(), - green = this.green(), - blue = this.blue(), - alpha = this.alpha(); - - for (let i = 0; i < nPixels; i += 4) { - const ia = 1 - alpha; - - data[i] = red * alpha + data[i] * ia; // r - data[i + 1] = green * alpha + data[i + 1] * ia; // g - data[i + 2] = blue * alpha + data[i + 2] * ia; // b - } -}; - -Factory.addGetterSetter(Node, 'red', 0, function (this: Node, val: number) { - this._filterUpToDate = false; - if (val > 255) { - return 255; - } else if (val < 0) { - return 0; - } else { - return Math.round(val); - } -}); -/** - * get/set filter red value. Use with {@link Konva.Filters.RGBA} filter. - * @name red - * @method - * @memberof Konva.Node.prototype - * @param {Integer} red value between 0 and 255 - * @returns {Integer} - */ - -Factory.addGetterSetter(Node, 'green', 0, function (this: Node, val) { - this._filterUpToDate = false; - if (val > 255) { - return 255; - } else if (val < 0) { - return 0; - } else { - return Math.round(val); - } -}); -/** - * get/set filter green value. Use with {@link Konva.Filters.RGBA} filter. - * @name green - * @method - * @memberof Konva.Node.prototype - * @param {Integer} green value between 0 and 255 - * @returns {Integer} - */ - -Factory.addGetterSetter(Node, 'blue', 0, RGBComponent, Factory.afterSetFilter); -/** - * get/set filter blue value. Use with {@link Konva.Filters.RGBA} filter. - * @name blue - * @method - * @memberof Konva.Node.prototype - * @param {Integer} blue value between 0 and 255 - * @returns {Integer} - */ - -Factory.addGetterSetter(Node, 'alpha', 1, function (this: Node, val) { - this._filterUpToDate = false; - if (val > 1) { - return 1; - } else if (val < 0) { - return 0; - } else { - return val; - } -}); -/** - * get/set filter alpha value. Use with {@link Konva.Filters.RGBA} filter. - * @name alpha - * @method - * @memberof Konva.Node.prototype - * @param {Float} alpha value between 0 and 1 - * @returns {Float} - */ diff --git a/src/filters/Sepia.ts b/src/filters/Sepia.ts deleted file mode 100644 index 3719ffe3c..000000000 --- a/src/filters/Sepia.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Filter } from '../Node'; - -// based on https://stackoverflow.com/questions/1061093/how-is-a-sepia-tone-created - -/** - * @function - * @name Sepia - * @memberof Konva.Filters - * @param {Object} imageData - * @example - * node.cache(); - * node.filters([Konva.Filters.Sepia]); - */ -export const Sepia: Filter = function (imageData) { - const data = imageData.data, - nPixels = data.length; - - for (let i = 0; i < nPixels; i += 4) { - const r = data[i + 0]; - const g = data[i + 1]; - const b = data[i + 2]; - - data[i + 0] = Math.min(255, r * 0.393 + g * 0.769 + b * 0.189); - data[i + 1] = Math.min(255, r * 0.349 + g * 0.686 + b * 0.168); - data[i + 2] = Math.min(255, r * 0.272 + g * 0.534 + b * 0.131); - } -}; diff --git a/src/filters/Solarize.ts b/src/filters/Solarize.ts deleted file mode 100644 index c576b6f71..000000000 --- a/src/filters/Solarize.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { Filter } from '../Node'; -/** - * Solarize Filter - * Pixastic Lib - Solarize filter - v0.1.0 - * Copyright (c) 2008 Jacob Seidelin, jseidelin@nihilogic.dk, http://blog.nihilogic.dk/ - * License: [http://www.pixastic.com/lib/license.txt] - * @function - * @name Solarize - * @memberof Konva.Filters - * @param {Object} imageData - * @example - * node.cache(); - * node.filters([Konva.Filters.Solarize]); - */ - -export const Solarize: Filter = function (imageData) { - const data = imageData.data, - w = imageData.width, - h = imageData.height, - w4 = w * 4; - - let y = h; - - do { - const offsetY = (y - 1) * w4; - let x = w; - do { - const offset = offsetY + (x - 1) * 4; - let r = data[offset]; - let g = data[offset + 1]; - let b = data[offset + 2]; - - if (r > 127) { - r = 255 - r; - } - if (g > 127) { - g = 255 - g; - } - if (b > 127) { - b = 255 - b; - } - - data[offset] = r; - data[offset + 1] = g; - data[offset + 2] = b; - } while (--x); - } while (--y); -}; diff --git a/src/filters/Threshold.ts b/src/filters/Threshold.ts deleted file mode 100644 index 1cdb8bb02..000000000 --- a/src/filters/Threshold.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { Factory } from '../Factory'; -import { Node, Filter } from '../Node'; -import { getNumberValidator } from '../Validators'; -/** - * Threshold Filter. Pushes any value above the mid point to - * the max and any value below the mid point to the min. - * This affects the alpha channel. - * @function - * @name Threshold - * @memberof Konva.Filters - * @param {Object} imageData - * @author ippo615 - * @example - * node.cache(); - * node.filters([Konva.Filters.Threshold]); - * node.threshold(0.1); - */ - -export const Threshold: Filter = function (imageData) { - const level = this.threshold() * 255, - data = imageData.data, - len = data.length; - - for (let i = 0; i < len; i += 1) { - data[i] = data[i] < level ? 0 : 255; - } -}; - -Factory.addGetterSetter( - Node, - 'threshold', - 0.5, - getNumberValidator(), - Factory.afterSetFilter -); -/** - * get/set threshold. Must be a value between 0 and 1. Use with {@link Konva.Filters.Threshold} or {@link Konva.Filters.Mask} filter. - * @name threshold - * @method - * @memberof Konva.Node.prototype - * @param {Number} threshold - * @returns {Number} - */ diff --git a/src/index-node.ts b/src/index-node.ts deleted file mode 100644 index 31a28e6d6..000000000 --- a/src/index-node.ts +++ /dev/null @@ -1,27 +0,0 @@ -// main entry for umd build for rollup -import { Konva } from './_FullInternals'; -import * as Canvas from 'canvas'; - -const canvas = Canvas['default'] || Canvas; - -global.DOMMatrix = canvas.DOMMatrix; - -const isNode = typeof global.document === 'undefined'; - -if (isNode) { - Konva.Util['createCanvasElement'] = () => { - const node = canvas.createCanvas(300, 300) as any; - if (!node['style']) { - node['style'] = {}; - } - return node; - }; - - // create image in Node env - Konva.Util.createImageElement = () => { - const node = new canvas.Image() as any; - return node; - }; -} - -export default Konva; diff --git a/src/index-types.d.ts b/src/index-types.d.ts deleted file mode 100644 index 77ae5a1cb..000000000 --- a/src/index-types.d.ts +++ /dev/null @@ -1,181 +0,0 @@ -// the purpose of that file is very stupid -// I was not able to generate correct typescript declarations from the main source code -// because right now Konva is an object. Typescript can not define types from object like this: -// const stage : Konva.Stage = new Konva.Stage(); -// we can convert Konva to namespace -// but I was not able to find a way to extend it -// so here we just need to define full API of Konva manually - -// filters -import { Blur } from './filters/Blur'; -import { Brighten } from './filters/Brighten'; -import { Contrast } from './filters/Contrast'; -import { Emboss } from './filters/Emboss'; -import { Enhance } from './filters/Enhance'; -import { Grayscale } from './filters/Grayscale'; -import { HSL } from './filters/HSL'; -import { HSV } from './filters/HSV'; -import { Invert } from './filters/Invert'; -import { Kaleidoscope } from './filters/Kaleidoscope'; -import { Mask } from './filters/Mask'; -import { Noise } from './filters/Noise'; -import { Pixelate } from './filters/Pixelate'; -import { Posterize } from './filters/Posterize'; -import { RGB } from './filters/RGB'; -import { RGBA } from './filters/RGBA'; -import { Sepia } from './filters/Sepia'; -import { Solarize } from './filters/Solarize'; -import { Threshold } from './filters/Threshold'; - -declare namespace Konva { - export let enableTrace: number; - export let pixelRatio: number; - export let autoDrawEnabled: boolean; - export let dragDistance: number; - export let angleDeg: boolean; - export let showWarnings: boolean; - export let capturePointerEventsEnabled: boolean; - export let dragButtons: Array; - export let hitOnDragEnabled: boolean; - export const isDragging: () => boolean; - export const isDragReady: () => boolean; - export const getAngle: (angle: number) => number; - - export type Vector2d = import('./types').Vector2d; - - export const Node: typeof import('./Node').Node; - export type Node = import('./Node').Node; - export type NodeConfig = import('./Node').NodeConfig; - - export type KonvaEventObject = - import('./Node').KonvaEventObject; - - export type KonvaPointerEvent = import('./PointerEvents').KonvaPointerEvent; - - export type KonvaEventListener = - import('./Node').KonvaEventListener; - - export const Container: typeof import('./Container').Container; - export type Container = import('./Container').Container; - export type ContainerConfig = import('./Container').ContainerConfig; - - export const Transform: typeof import('./Util').Transform; - export type Transform = import('./Util').Transform; - - export const Util: typeof import('./Util').Util; - - export const Context: typeof import('./Context').Context; - export type Context = import('./Context').Context; - - export const Stage: typeof import('./Stage').Stage; - export type Stage = import('./Stage').Stage; - export type StageConfig = import('./Stage').StageConfig; - export const stages: typeof import('./Stage').stages; - - export const Layer: typeof import('./Layer').Layer; - export type Layer = import('./Layer').Layer; - export type LayerConfig = import('./Layer').LayerConfig; - - export const FastLayer: typeof import('./FastLayer').FastLayer; - export type FastLayer = import('./FastLayer').FastLayer; - - export const Group: typeof import('./Group').Group; - export type Group = import('./Group').Group; - export type GroupConfig = import('./Group').GroupConfig; - - export const DD: typeof import('./DragAndDrop').DD; - - export const Shape: typeof import('./Shape').Shape; - export type Shape = import('./Shape').Shape; - export type ShapeConfig = import('./Shape').ShapeConfig; - export const shapes: typeof import('./Shape').shapes; - - export const Animation: typeof import('./Animation').Animation; - export type Animation = import('./Animation').Animation; - - export const Tween: typeof import('./Tween').Tween; - export type Tween = import('./Tween').Tween; - export type TweenConfig = import('./Tween').TweenConfig; - export const Easings: typeof import('./Tween').Easings; - - export const Arc: typeof import('./shapes/Arc').Arc; - export type Arc = import('./shapes/Arc').Arc; - export type ArcConfig = import('./shapes/Arc').ArcConfig; - export const Arrow: typeof import('./shapes/Arrow').Arrow; - export type Arrow = import('./shapes/Arrow').Arrow; - export type ArrowConfig = import('./shapes/Arrow').ArrowConfig; - export const Circle: typeof import('./shapes/Circle').Circle; - export type Circle = import('./shapes/Circle').Circle; - export type CircleConfig = import('./shapes/Circle').CircleConfig; - export const Ellipse: typeof import('./shapes/Ellipse').Ellipse; - export type Ellipse = import('./shapes/Ellipse').Ellipse; - export type EllipseConfig = import('./shapes/Ellipse').EllipseConfig; - export const Image: typeof import('./shapes/Image').Image; - export type Image = import('./shapes/Image').Image; - export type ImageConfig = import('./shapes/Image').ImageConfig; - export const Label: typeof import('./shapes/Label').Label; - export type Label = import('./shapes/Label').Label; - export type LabelConfig = import('./shapes/Label').LabelConfig; - export const Tag: typeof import('./shapes/Label').Tag; - export type Tag = import('./shapes/Label').Tag; - export type TagConfig = import('./shapes/Label').TagConfig; - export const Line: typeof import('./shapes/Line').Line; - export type Line = import('./shapes/Line').Line; - export type LineConfig = import('./shapes/Line').LineConfig; - export const Path: typeof import('./shapes/Path').Path; - export type Path = import('./shapes/Path').Path; - export type PathConfig = import('./shapes/Path').PathConfig; - export const Rect: typeof import('./shapes/Rect').Rect; - export type Rect = import('./shapes/Rect').Rect; - export type RectConfig = import('./shapes/Rect').RectConfig; - export const RegularPolygon: typeof import('./shapes/RegularPolygon').RegularPolygon; - export type RegularPolygon = import('./shapes/RegularPolygon').RegularPolygon; - export type RegularPolygonConfig = - import('./shapes/RegularPolygon').RegularPolygonConfig; - export const Ring: typeof import('./shapes/Ring').Ring; - export type Ring = import('./shapes/Ring').Ring; - export type RingConfig = import('./shapes/Ring').RingConfig; - export const Sprite: typeof import('./shapes/Sprite').Sprite; - export type Sprite = import('./shapes/Sprite').Sprite; - export type SpriteConfig = import('./shapes/Sprite').SpriteConfig; - export const Star: typeof import('./shapes/Star').Star; - export type Star = import('./shapes/Star').Star; - export type StarConfig = import('./shapes/Star').StarConfig; - export const Text: typeof import('./shapes/Text').Text; - export type Text = import('./shapes/Text').Text; - export type TextConfig = import('./shapes/Text').TextConfig; - export const TextPath: typeof import('./shapes/TextPath').TextPath; - export type TextPath = import('./shapes/TextPath').TextPath; - export type TextPathConfig = import('./shapes/TextPath').TextPathConfig; - export const Transformer: typeof import('./shapes/Transformer').Transformer; - export type Transformer = import('./shapes/Transformer').Transformer; - export type TransformerConfig = - import('./shapes/Transformer').TransformerConfig; - export const Wedge: typeof import('./shapes/Wedge').Wedge; - export type Wedge = import('./shapes/Wedge').Wedge; - export type WedgeConfig = import('./shapes/Wedge').WedgeConfig; - - export const Filters: { - Blur: typeof Blur; - Brighten: typeof Brighten; - Contrast: typeof Contrast; - Emboss: typeof Emboss; - Enhance: typeof Enhance; - Grayscale: typeof Grayscale; - HSL: typeof HSL; - HSV: typeof HSV; - Invert: typeof Invert; - Kaleidoscope: typeof Kaleidoscope; - Mask: typeof Mask; - Noise: typeof Noise; - Pixelate: typeof Pixelate; - Posterize: typeof Posterize; - RGB: typeof RGB; - RGBA: typeof RGBA; - Sepia: typeof Sepia; - Solarize: typeof Solarize; - Threshold: typeof Threshold; - }; -} - -export default Konva; diff --git a/src/index.ts b/src/index.ts deleted file mode 100644 index 75ffb0645..000000000 --- a/src/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { Konva } from './_FullInternals'; - -export default Konva; diff --git a/src/shapes/Arc.ts b/src/shapes/Arc.ts deleted file mode 100644 index 63b97c5f7..000000000 --- a/src/shapes/Arc.ts +++ /dev/null @@ -1,170 +0,0 @@ -import { Factory } from '../Factory'; -import { Shape, ShapeConfig } from '../Shape'; -import { Konva } from '../Global'; -import { GetSet } from '../types'; -import { getNumberValidator, getBooleanValidator } from '../Validators'; -import { _registerNode } from '../Global'; -import { Context } from '../Context'; - -export interface ArcConfig extends ShapeConfig { - angle: number; - innerRadius: number; - outerRadius: number; - clockwise?: boolean; -} - -/** - * Arc constructor - * @constructor - * @memberof Konva - * @augments Konva.Shape - * @param {Object} config - * @param {Number} config.angle in degrees - * @param {Number} config.innerRadius - * @param {Number} config.outerRadius - * @param {Boolean} [config.clockwise] - * @@shapeParams - * @@nodeParams - * @example - * // draw a Arc that's pointing downwards - * var arc = new Konva.Arc({ - * innerRadius: 40, - * outerRadius: 80, - * fill: 'red', - * stroke: 'black' - * strokeWidth: 5, - * angle: 60, - * rotationDeg: -120 - * }); - */ -export class Arc extends Shape { - _sceneFunc(context: Context) { - const angle = Konva.getAngle(this.angle()), - clockwise = this.clockwise(); - - context.beginPath(); - context.arc(0, 0, this.outerRadius(), 0, angle, clockwise); - context.arc(0, 0, this.innerRadius(), angle, 0, !clockwise); - context.closePath(); - context.fillStrokeShape(this); - } - getWidth() { - return this.outerRadius() * 2; - } - getHeight() { - return this.outerRadius() * 2; - } - setWidth(width: number) { - this.outerRadius(width / 2); - } - setHeight(height: number) { - this.outerRadius(height / 2); - } - - getSelfRect() { - const innerRadius = this.innerRadius(); - const outerRadius = this.outerRadius(); - const clockwise = this.clockwise(); - const angle = Konva.getAngle(clockwise ? 360 - this.angle() : this.angle()); - - const boundLeftRatio = Math.cos(Math.min(angle, Math.PI)); - const boundRightRatio = 1; - const boundTopRatio = Math.sin( - Math.min(Math.max(Math.PI, angle), (3 * Math.PI) / 2) - ); - const boundBottomRatio = Math.sin(Math.min(angle, Math.PI / 2)); - const boundLeft = - boundLeftRatio * (boundLeftRatio > 0 ? innerRadius : outerRadius); - const boundRight = - boundRightRatio * (boundRightRatio > 0 ? outerRadius : innerRadius); - const boundTop = - boundTopRatio * (boundTopRatio > 0 ? innerRadius : outerRadius); - const boundBottom = - boundBottomRatio * (boundBottomRatio > 0 ? outerRadius : innerRadius); - - return { - x: boundLeft, - y: clockwise ? -1 * boundBottom : boundTop, - width: boundRight - boundLeft, - height: boundBottom - boundTop, - }; - } - - innerRadius: GetSet; - outerRadius: GetSet; - angle: GetSet; - clockwise: GetSet; -} - -Arc.prototype._centroid = true; -Arc.prototype.className = 'Arc'; -Arc.prototype._attrsAffectingSize = ['innerRadius', 'outerRadius']; -_registerNode(Arc); - -// add getters setters -Factory.addGetterSetter(Arc, 'innerRadius', 0, getNumberValidator()); - -/** - * get/set innerRadius - * @name Konva.Arc#innerRadius - * @method - * @param {Number} innerRadius - * @returns {Number} - * @example - * // get inner radius - * var innerRadius = arc.innerRadius(); - * - * // set inner radius - * arc.innerRadius(20); - */ - -Factory.addGetterSetter(Arc, 'outerRadius', 0, getNumberValidator()); - -/** - * get/set outerRadius - * @name Konva.Arc#outerRadius - * @method - * @param {Number} outerRadius - * @returns {Number} - * @example - * // get outer radius - * var outerRadius = arc.outerRadius(); - * - * // set outer radius - * arc.outerRadius(20); - */ - -Factory.addGetterSetter(Arc, 'angle', 0, getNumberValidator()); - -/** - * get/set angle in degrees - * @name Konva.Arc#angle - * @method - * @param {Number} angle - * @returns {Number} - * @example - * // get angle - * var angle = arc.angle(); - * - * // set angle - * arc.angle(20); - */ - -Factory.addGetterSetter(Arc, 'clockwise', false, getBooleanValidator()); - -/** - * get/set clockwise flag - * @name Konva.Arc#clockwise - * @method - * @param {Boolean} clockwise - * @returns {Boolean} - * @example - * // get clockwise flag - * var clockwise = arc.clockwise(); - * - * // draw arc counter-clockwise - * arc.clockwise(false); - * - * // draw arc clockwise - * arc.clockwise(true); - */ diff --git a/src/shapes/Arrow.ts b/src/shapes/Arrow.ts deleted file mode 100644 index 2503743ff..000000000 --- a/src/shapes/Arrow.ts +++ /dev/null @@ -1,230 +0,0 @@ -import { Factory } from '../Factory'; -import { Line, LineConfig } from './Line'; -import { GetSet } from '../types'; -import { getNumberValidator } from '../Validators'; -import { _registerNode } from '../Global'; -import { Path } from './Path'; -import { Context } from '../Context'; - -export interface ArrowConfig extends LineConfig { - points: number[]; - tension?: number; - closed?: boolean; - pointerLength?: number; - pointerWidth?: number; - pointerAtBeginning?: boolean; - pointerAtEnding?: boolean; -} - -/** - * Arrow constructor - * @constructor - * @memberof Konva - * @augments Konva.Line - * @param {Object} config - * @param {Array} config.points Flat array of points coordinates. You should define them as [x1, y1, x2, y2, x3, y3]. - * @param {Number} [config.tension] Higher values will result in a more curvy line. A value of 0 will result in no interpolation. - * The default is 0 - * @param {Number} config.pointerLength Arrow pointer length. Default value is 10. - * @param {Number} config.pointerWidth Arrow pointer width. Default value is 10. - * @param {Boolean} config.pointerAtBeginning Do we need to draw pointer on beginning position?. Default false. - * @param {Boolean} config.pointerAtEnding Do we need to draw pointer on ending position?. Default true. - * @@shapeParams - * @@nodeParams - * @example - * var line = new Konva.Line({ - * points: [73, 70, 340, 23, 450, 60, 500, 20], - * stroke: 'red', - * tension: 1, - * pointerLength : 10, - * pointerWidth : 12 - * }); - */ -export class Arrow extends Line { - _sceneFunc(ctx: Context) { - super._sceneFunc(ctx); - const PI2 = Math.PI * 2; - const points = this.points(); - - let tp = points; - const fromTension = this.tension() !== 0 && points.length > 4; - if (fromTension) { - tp = this.getTensionPoints(); - } - const length = this.pointerLength(); - - const n = points.length; - - let dx, dy; - if (fromTension) { - const lp = [ - tp[tp.length - 4], - tp[tp.length - 3], - tp[tp.length - 2], - tp[tp.length - 1], - points[n - 2], - points[n - 1], - ]; - const lastLength = Path.calcLength( - tp[tp.length - 4], - tp[tp.length - 3], - 'C', - lp - ); - const previous = Path.getPointOnQuadraticBezier( - Math.min(1, 1 - length / lastLength), - lp[0], - lp[1], - lp[2], - lp[3], - lp[4], - lp[5] - ); - - dx = points[n - 2] - previous.x; - dy = points[n - 1] - previous.y; - } else { - dx = points[n - 2] - points[n - 4]; - dy = points[n - 1] - points[n - 3]; - } - - const radians = (Math.atan2(dy, dx) + PI2) % PI2; - - const width = this.pointerWidth(); - - if (this.pointerAtEnding()) { - ctx.save(); - ctx.beginPath(); - ctx.translate(points[n - 2], points[n - 1]); - ctx.rotate(radians); - ctx.moveTo(0, 0); - ctx.lineTo(-length, width / 2); - ctx.lineTo(-length, -width / 2); - ctx.closePath(); - ctx.restore(); - this.__fillStroke(ctx); - } - - if (this.pointerAtBeginning()) { - ctx.save(); - ctx.beginPath(); - ctx.translate(points[0], points[1]); - if (fromTension) { - dx = (tp[0] + tp[2]) / 2 - points[0]; - dy = (tp[1] + tp[3]) / 2 - points[1]; - } else { - dx = points[2] - points[0]; - dy = points[3] - points[1]; - } - - ctx.rotate((Math.atan2(-dy, -dx) + PI2) % PI2); - ctx.moveTo(0, 0); - ctx.lineTo(-length, width / 2); - ctx.lineTo(-length, -width / 2); - ctx.closePath(); - ctx.restore(); - this.__fillStroke(ctx); - } - } - - __fillStroke(ctx: Context) { - // here is a tricky part - // we need to disable dash for arrow pointers - const isDashEnabled = this.dashEnabled(); - if (isDashEnabled) { - // manually disable dash for head - // it is better not to use setter here, - // because it will trigger attr change event - this.attrs.dashEnabled = false; - ctx.setLineDash([]); - } - - ctx.fillStrokeShape(this); - - // restore old value - if (isDashEnabled) { - this.attrs.dashEnabled = true; - } - } - - getSelfRect() { - const lineRect = super.getSelfRect(); - const offset = this.pointerWidth() / 2; - return { - x: lineRect.x, - y: lineRect.y - offset, - width: lineRect.width, - height: lineRect.height + offset * 2, - }; - } - - pointerLength: GetSet; - pointerWidth: GetSet; - pointerAtEnding: GetSet; - pointerAtBeginning: GetSet; -} - -Arrow.prototype.className = 'Arrow'; -_registerNode(Arrow); - -/** - * get/set pointerLength - * @name Konva.Arrow#pointerLength - * @method - * @param {Number} Length of pointer of arrow. The default is 10. - * @returns {Number} - * @example - * // get length - * var pointerLength = line.pointerLength(); - * - * // set length - * line.pointerLength(15); - */ - -Factory.addGetterSetter(Arrow, 'pointerLength', 10, getNumberValidator()); -/** - * get/set pointerWidth - * @name Konva.Arrow#pointerWidth - * @method - * @param {Number} Width of pointer of arrow. - * The default is 10. - * @returns {Number} - * @example - * // get width - * var pointerWidth = line.pointerWidth(); - * - * // set width - * line.pointerWidth(15); - */ - -Factory.addGetterSetter(Arrow, 'pointerWidth', 10, getNumberValidator()); -/** - * get/set pointerAtBeginning - * @name Konva.Arrow#pointerAtBeginning - * @method - * @param {Number} Should pointer displayed at beginning of arrow. The default is false. - * @returns {Boolean} - * @example - * // get value - * var pointerAtBeginning = line.pointerAtBeginning(); - * - * // set value - * line.pointerAtBeginning(true); - */ - -Factory.addGetterSetter(Arrow, 'pointerAtBeginning', false); -/** - * get/set pointerAtEnding - * @name Konva.Arrow#pointerAtEnding - * @method - * @param {Number} Should pointer displayed at ending of arrow. The default is true. - * @returns {Boolean} - * @example - * // get value - * var pointerAtEnding = line.pointerAtEnding(); - * - * // set value - * line.pointerAtEnding(false); - */ - -Factory.addGetterSetter(Arrow, 'pointerAtEnding', true); diff --git a/src/shapes/Circle.ts b/src/shapes/Circle.ts deleted file mode 100644 index 67b47ec5a..000000000 --- a/src/shapes/Circle.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { Factory } from '../Factory'; -import { Shape, ShapeConfig } from '../Shape'; -import { GetSet } from '../types'; -import { getNumberValidator } from '../Validators'; -import { _registerNode } from '../Global'; -import { Context } from '../Context'; - -export interface CircleConfig extends ShapeConfig { - radius?: number; -} - -/** - * Circle constructor - * @constructor - * @memberof Konva - * @augments Konva.Shape - * @param {Object} config - * @param {Number} config.radius - * @@shapeParams - * @@nodeParams - * @example - * // create circle - * var circle = new Konva.Circle({ - * radius: 40, - * fill: 'red', - * stroke: 'black', - * strokeWidth: 5 - * }); - */ -export class Circle extends Shape { - _sceneFunc(context: Context) { - context.beginPath(); - context.arc(0, 0, this.attrs.radius || 0, 0, Math.PI * 2, false); - context.closePath(); - context.fillStrokeShape(this); - } - getWidth() { - return this.radius() * 2; - } - getHeight() { - return this.radius() * 2; - } - setWidth(width: number) { - if (this.radius() !== width / 2) { - this.radius(width / 2); - } - } - setHeight(height: number) { - if (this.radius() !== height / 2) { - this.radius(height / 2); - } - } - - radius: GetSet; -} - -Circle.prototype._centroid = true; -Circle.prototype.className = 'Circle'; -Circle.prototype._attrsAffectingSize = ['radius']; -_registerNode(Circle); - -/** - * get/set radius - * @name Konva.Circle#radius - * @method - * @param {Number} radius - * @returns {Number} - * @example - * // get radius - * var radius = circle.radius(); - * - * // set radius - * circle.radius(10); - */ -Factory.addGetterSetter(Circle, 'radius', 0, getNumberValidator()); diff --git a/src/shapes/Ellipse.ts b/src/shapes/Ellipse.ts deleted file mode 100644 index 2c115ae6c..000000000 --- a/src/shapes/Ellipse.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { Factory } from '../Factory'; -import { Shape, ShapeConfig } from '../Shape'; -import { getNumberValidator } from '../Validators'; -import { _registerNode } from '../Global'; -import { Context } from '../Context'; - -import { GetSet, Vector2d } from '../types'; - -export interface EllipseConfig extends ShapeConfig { - radiusX: number; - radiusY: number; -} - -/** - * Ellipse constructor - * @constructor - * @memberof Konva - * @augments Konva.Shape - * @param {Object} config - * @param {Object} config.radius defines x and y radius - * @@shapeParams - * @@nodeParams - * @example - * var ellipse = new Konva.Ellipse({ - * radius : { - * x : 50, - * y : 50 - * }, - * fill: 'red' - * }); - */ -export class Ellipse extends Shape { - _sceneFunc(context: Context) { - const rx = this.radiusX(), - ry = this.radiusY(); - - context.beginPath(); - context.save(); - if (rx !== ry) { - context.scale(1, ry / rx); - } - context.arc(0, 0, rx, 0, Math.PI * 2, false); - context.restore(); - context.closePath(); - context.fillStrokeShape(this); - } - getWidth() { - return this.radiusX() * 2; - } - getHeight() { - return this.radiusY() * 2; - } - setWidth(width: number) { - this.radiusX(width / 2); - } - setHeight(height: number) { - this.radiusY(height / 2); - } - - radius: GetSet; - radiusX: GetSet; - radiusY: GetSet; -} - -Ellipse.prototype.className = 'Ellipse'; -Ellipse.prototype._centroid = true; -Ellipse.prototype._attrsAffectingSize = ['radiusX', 'radiusY']; -_registerNode(Ellipse); - -// add getters setters -Factory.addComponentsGetterSetter(Ellipse, 'radius', ['x', 'y']); - -/** - * get/set radius - * @name Konva.Ellipse#radius - * @method - * @param {Object} radius - * @param {Number} radius.x - * @param {Number} radius.y - * @returns {Object} - * @example - * // get radius - * var radius = ellipse.radius(); - * - * // set radius - * ellipse.radius({ - * x: 200, - * y: 100 - * }); - */ - -Factory.addGetterSetter(Ellipse, 'radiusX', 0, getNumberValidator()); -/** - * get/set radius x - * @name Konva.Ellipse#radiusX - * @method - * @param {Number} x - * @returns {Number} - * @example - * // get radius x - * var radiusX = ellipse.radiusX(); - * - * // set radius x - * ellipse.radiusX(200); - */ - -Factory.addGetterSetter(Ellipse, 'radiusY', 0, getNumberValidator()); -/** - * get/set radius y - * @name Konva.Ellipse#radiusY - * @method - * @param {Number} y - * @returns {Number} - * @example - * // get radius y - * var radiusY = ellipse.radiusY(); - * - * // set radius y - * ellipse.radiusY(200); - */ diff --git a/src/shapes/Image.ts b/src/shapes/Image.ts deleted file mode 100644 index ae5095308..000000000 --- a/src/shapes/Image.ts +++ /dev/null @@ -1,304 +0,0 @@ -import { Util } from '../Util'; -import { Factory } from '../Factory'; -import { Shape, ShapeConfig } from '../Shape'; -import { _registerNode } from '../Global'; -import { - getNumberOrArrayOfNumbersValidator, - getNumberValidator, -} from '../Validators'; - -import { GetSet, IRect } from '../types'; -import { Context } from '../Context'; - -export interface ImageConfig extends ShapeConfig { - image: CanvasImageSource | undefined; - crop?: IRect; - cornerRadius?: number | number[]; -} - -/** - * Image constructor - * @constructor - * @memberof Konva - * @augments Konva.Shape - * @param {Object} config - * @param {Image} config.image - * @param {Object} [config.crop] - * @@shapeParams - * @@nodeParams - * @example - * var imageObj = new Image(); - * imageObj.onload = function() { - * var image = new Konva.Image({ - * x: 200, - * y: 50, - * image: imageObj, - * width: 100, - * height: 100 - * }); - * }; - * imageObj.src = '/path/to/image.jpg' - */ -export class Image extends Shape { - constructor(attrs: ImageConfig) { - super(attrs); - this.on('imageChange.konva', () => { - this._setImageLoad(); - }); - - this._setImageLoad(); - } - _setImageLoad() { - const image = this.image() as any; - // check is image is already loaded - if (image && image.complete) { - return; - } - // check is video is already loaded - if (image && image.readyState === 4) { - return; - } - if (image && image['addEventListener']) { - image['addEventListener']('load', () => { - this._requestDraw(); - }); - } - } - _useBufferCanvas() { - const hasCornerRadius = !!this.cornerRadius(); - const hasShadow = this.hasShadow(); - if (hasCornerRadius && hasShadow) { - return true; - } - return super._useBufferCanvas(true); - } - _sceneFunc(context: Context) { - const width = this.getWidth(); - const height = this.getHeight(); - const cornerRadius = this.cornerRadius(); - const image = this.attrs.image; - let params; - - if (image) { - const cropWidth = this.attrs.cropWidth; - const cropHeight = this.attrs.cropHeight; - if (cropWidth && cropHeight) { - params = [ - image, - this.cropX(), - this.cropY(), - cropWidth, - cropHeight, - 0, - 0, - width, - height, - ]; - } else { - params = [image, 0, 0, width, height]; - } - } - - if (this.hasFill() || this.hasStroke() || cornerRadius) { - context.beginPath(); - cornerRadius - ? Util.drawRoundedRectPath(context, width, height, cornerRadius) - : context.rect(0, 0, width, height); - context.closePath(); - context.fillStrokeShape(this); - } - - if (image) { - if (cornerRadius) { - context.clip(); - } - context.drawImage.apply(context, params); - } - // If you need to draw later, you need to execute save/restore - } - _hitFunc(context: Context) { - const width = this.width(), - height = this.height(), - cornerRadius = this.cornerRadius(); - - context.beginPath(); - if (!cornerRadius) { - context.rect(0, 0, width, height); - } else { - Util.drawRoundedRectPath(context, width, height, cornerRadius); - } - context.closePath(); - context.fillStrokeShape(this); - } - getWidth() { - return this.attrs.width ?? (this.image() as any)?.width; - } - getHeight() { - return this.attrs.height ?? (this.image() as any)?.height; - } - - /** - * load image from given url and create `Konva.Image` instance - * @method - * @memberof Konva.Image - * @param {String} url image source - * @param {Function} callback with Konva.Image instance as first argument - * @param {Function} onError optional error handler - * @example - * Konva.Image.fromURL(imageURL, function(image){ - * // image is Konva.Image instance - * layer.add(image); - * layer.draw(); - * }); - */ - static fromURL( - url: string, - callback: (img: Image) => void, - onError: OnErrorEventHandler = null - ) { - const img = Util.createImageElement(); - img.onload = function () { - const image = new Image({ - image: img, - }); - callback(image); - }; - img.onerror = onError; - img.crossOrigin = 'Anonymous'; - img.src = url; - } - - image: GetSet; - crop: GetSet; - cropX: GetSet; - cropY: GetSet; - cropWidth: GetSet; - cropHeight: GetSet; - cornerRadius: GetSet; -} - -Image.prototype.className = 'Image'; -_registerNode(Image); - -/** - * get/set corner radius - * @method - * @name Konva.Image#cornerRadius - * @param {Number} cornerRadius - * @returns {Number} - * @example - * // get corner radius - * var cornerRadius = image.cornerRadius(); - * - * // set corner radius - * image.cornerRadius(10); - * - * // set different corner radius values - * // top-left, top-right, bottom-right, bottom-left - * image.cornerRadius([0, 10, 20, 30]); - */ -Factory.addGetterSetter( - Image, - 'cornerRadius', - 0, - getNumberOrArrayOfNumbersValidator(4) -); - -/** - * get/set image source. It can be image, canvas or video element - * @name Konva.Image#image - * @method - * @param {Object} image source - * @returns {Object} - * @example - * // get value - * var image = shape.image(); - * - * // set value - * shape.image(img); - */ -Factory.addGetterSetter(Image, 'image'); - -Factory.addComponentsGetterSetter(Image, 'crop', ['x', 'y', 'width', 'height']); -/** - * get/set crop - * @method - * @name Konva.Image#crop - * @param {Object} crop - * @param {Number} crop.x - * @param {Number} crop.y - * @param {Number} crop.width - * @param {Number} crop.height - * @returns {Object} - * @example - * // get crop - * var crop = image.crop(); - * - * // set crop - * image.crop({ - * x: 20, - * y: 20, - * width: 20, - * height: 20 - * }); - */ - -Factory.addGetterSetter(Image, 'cropX', 0, getNumberValidator()); -/** - * get/set crop x - * @method - * @name Konva.Image#cropX - * @param {Number} x - * @returns {Number} - * @example - * // get crop x - * var cropX = image.cropX(); - * - * // set crop x - * image.cropX(20); - */ - -Factory.addGetterSetter(Image, 'cropY', 0, getNumberValidator()); -/** - * get/set crop y - * @name Konva.Image#cropY - * @method - * @param {Number} y - * @returns {Number} - * @example - * // get crop y - * var cropY = image.cropY(); - * - * // set crop y - * image.cropY(20); - */ - -Factory.addGetterSetter(Image, 'cropWidth', 0, getNumberValidator()); -/** - * get/set crop width - * @name Konva.Image#cropWidth - * @method - * @param {Number} width - * @returns {Number} - * @example - * // get crop width - * var cropWidth = image.cropWidth(); - * - * // set crop width - * image.cropWidth(20); - */ - -Factory.addGetterSetter(Image, 'cropHeight', 0, getNumberValidator()); -/** - * get/set crop height - * @name Konva.Image#cropHeight - * @method - * @param {Number} height - * @returns {Number} - * @example - * // get crop height - * var cropHeight = image.cropHeight(); - * - * // set crop height - * image.cropHeight(20); - */ diff --git a/src/shapes/Label.ts b/src/shapes/Label.ts deleted file mode 100644 index be15df8c2..000000000 --- a/src/shapes/Label.ts +++ /dev/null @@ -1,385 +0,0 @@ -import { Factory } from '../Factory'; -import { Shape, ShapeConfig } from '../Shape'; -import { Group } from '../Group'; -import { Context } from '../Context'; -import { ContainerConfig } from '../Container'; -import { - getNumberOrArrayOfNumbersValidator, - getNumberValidator, -} from '../Validators'; -import { _registerNode } from '../Global'; - -import { GetSet } from '../types'; -import { Text } from './Text'; - -export interface LabelConfig extends ContainerConfig {} - -// constants -const ATTR_CHANGE_LIST = [ - 'fontFamily', - 'fontSize', - 'fontStyle', - 'padding', - 'lineHeight', - 'text', - 'width', - 'height', - 'pointerDirection', - 'pointerWidth', - 'pointerHeight', - ], - CHANGE_KONVA = 'Change.konva', - NONE = 'none', - UP = 'up', - RIGHT = 'right', - DOWN = 'down', - LEFT = 'left', - // cached variables - attrChangeListLen = ATTR_CHANGE_LIST.length; - -/** - * Label constructor.  Labels are groups that contain a Text and Tag shape - * @constructor - * @memberof Konva - * @param {Object} config - * @@nodeParams - * @example - * // create label - * var label = new Konva.Label({ - * x: 100, - * y: 100, - * draggable: true - * }); - * - * // add a tag to the label - * label.add(new Konva.Tag({ - * fill: '#bbb', - * stroke: '#333', - * shadowColor: 'black', - * shadowBlur: 10, - * shadowOffset: [10, 10], - * shadowOpacity: 0.2, - * lineJoin: 'round', - * pointerDirection: 'up', - * pointerWidth: 20, - * pointerHeight: 20, - * cornerRadius: 5 - * })); - * - * // add text to the label - * label.add(new Konva.Text({ - * text: 'Hello World!', - * fontSize: 50, - * lineHeight: 1.2, - * padding: 10, - * fill: 'green' - * })); - */ -export class Label extends Group { - constructor(config?: LabelConfig) { - super(config); - this.on('add.konva', function (evt) { - this._addListeners(evt.child); - this._sync(); - }); - } - - /** - * get Text shape for the label. You need to access the Text shape in order to update - * the text properties - * @name Konva.Label#getText - * @method - * @example - * label.getText().fill('red') - */ - getText() { - return this.find('Text')[0]; - } - /** - * get Tag shape for the label. You need to access the Tag shape in order to update - * the pointer properties and the corner radius - * @name Konva.Label#getTag - * @method - */ - getTag() { - return this.find('Tag')[0] as Tag; - } - _addListeners(text) { - let that = this, - n; - const func = function () { - that._sync(); - }; - - // update text data for certain attr changes - for (n = 0; n < attrChangeListLen; n++) { - text.on(ATTR_CHANGE_LIST[n] + CHANGE_KONVA, func); - } - } - getWidth() { - return this.getText().width(); - } - getHeight() { - return this.getText().height(); - } - _sync() { - let text = this.getText(), - tag = this.getTag(), - width, - height, - pointerDirection, - pointerWidth, - x, - y, - pointerHeight; - - if (text && tag) { - width = text.width(); - height = text.height(); - pointerDirection = tag.pointerDirection(); - pointerWidth = tag.pointerWidth(); - pointerHeight = tag.pointerHeight(); - x = 0; - y = 0; - - switch (pointerDirection) { - case UP: - x = width / 2; - y = -1 * pointerHeight; - break; - case RIGHT: - x = width + pointerWidth; - y = height / 2; - break; - case DOWN: - x = width / 2; - y = height + pointerHeight; - break; - case LEFT: - x = -1 * pointerWidth; - y = height / 2; - break; - } - - tag.setAttrs({ - x: -1 * x, - y: -1 * y, - width: width, - height: height, - }); - - text.setAttrs({ - x: -1 * x, - y: -1 * y, - }); - } - } -} - -Label.prototype.className = 'Label'; -_registerNode(Label); - -export interface TagConfig extends ShapeConfig { - pointerDirection?: string; - pointerWidth?: number; - pointerHeight?: number; - cornerRadius?: number | Array; -} - -/** - * Tag constructor.  A Tag can be configured - * to have a pointer element that points up, right, down, or left - * @constructor - * @memberof Konva - * @param {Object} config - * @param {String} [config.pointerDirection] can be up, right, down, left, or none; the default - * is none. When a pointer is present, the positioning of the label is relative to the tip of the pointer. - * @param {Number} [config.pointerWidth] - * @param {Number} [config.pointerHeight] - * @param {Number} [config.cornerRadius] - */ -export class Tag extends Shape { - _sceneFunc(context: Context) { - const width = this.width(), - height = this.height(), - pointerDirection = this.pointerDirection(), - pointerWidth = this.pointerWidth(), - pointerHeight = this.pointerHeight(), - cornerRadius = this.cornerRadius(); - - let topLeft = 0; - let topRight = 0; - let bottomLeft = 0; - let bottomRight = 0; - - if (typeof cornerRadius === 'number') { - topLeft = - topRight = - bottomLeft = - bottomRight = - Math.min(cornerRadius, width / 2, height / 2); - } else { - topLeft = Math.min(cornerRadius[0] || 0, width / 2, height / 2); - topRight = Math.min(cornerRadius[1] || 0, width / 2, height / 2); - bottomRight = Math.min(cornerRadius[2] || 0, width / 2, height / 2); - bottomLeft = Math.min(cornerRadius[3] || 0, width / 2, height / 2); - } - - context.beginPath(); - context.moveTo(topLeft, 0); - - if (pointerDirection === UP) { - context.lineTo((width - pointerWidth) / 2, 0); - context.lineTo(width / 2, -1 * pointerHeight); - context.lineTo((width + pointerWidth) / 2, 0); - } - - context.lineTo(width - topRight, 0); - context.arc( - width - topRight, - topRight, - topRight, - (Math.PI * 3) / 2, - 0, - false - ); - - if (pointerDirection === RIGHT) { - context.lineTo(width, (height - pointerHeight) / 2); - context.lineTo(width + pointerWidth, height / 2); - context.lineTo(width, (height + pointerHeight) / 2); - } - - context.lineTo(width, height - bottomRight); - context.arc( - width - bottomRight, - height - bottomRight, - bottomRight, - 0, - Math.PI / 2, - false - ); - - if (pointerDirection === DOWN) { - context.lineTo((width + pointerWidth) / 2, height); - context.lineTo(width / 2, height + pointerHeight); - context.lineTo((width - pointerWidth) / 2, height); - } - - context.lineTo(bottomLeft, height); - context.arc( - bottomLeft, - height - bottomLeft, - bottomLeft, - Math.PI / 2, - Math.PI, - false - ); - - if (pointerDirection === LEFT) { - context.lineTo(0, (height + pointerHeight) / 2); - context.lineTo(-1 * pointerWidth, height / 2); - context.lineTo(0, (height - pointerHeight) / 2); - } - - context.lineTo(0, topLeft); - context.arc(topLeft, topLeft, topLeft, Math.PI, (Math.PI * 3) / 2, false); - - context.closePath(); - context.fillStrokeShape(this); - } - getSelfRect() { - let x = 0, - y = 0, - pointerWidth = this.pointerWidth(), - pointerHeight = this.pointerHeight(), - direction = this.pointerDirection(), - width = this.width(), - height = this.height(); - - if (direction === UP) { - y -= pointerHeight; - height += pointerHeight; - } else if (direction === DOWN) { - height += pointerHeight; - } else if (direction === LEFT) { - // ARGH!!! I have no idea why should I used magic 1.5!!!!!!!!! - x -= pointerWidth * 1.5; - width += pointerWidth; - } else if (direction === RIGHT) { - width += pointerWidth * 1.5; - } - return { - x: x, - y: y, - width: width, - height: height, - }; - } - - pointerDirection: GetSet< - 'left' | 'up' | 'right' | 'down' | typeof NONE, - this - >; - pointerWidth: GetSet; - pointerHeight: GetSet; - cornerRadius: GetSet; -} - -Tag.prototype.className = 'Tag'; -_registerNode(Tag); - -/** - * get/set pointer direction - * @name Konva.Tag#pointerDirection - * @method - * @param {String} pointerDirection can be up, right, down, left, or none. The default is none. - * @returns {String} - * @example - * tag.pointerDirection('right'); - */ -Factory.addGetterSetter(Tag, 'pointerDirection', NONE); - -/** - * get/set pointer width - * @name Konva.Tag#pointerWidth - * @method - * @param {Number} pointerWidth - * @returns {Number} - * @example - * tag.pointerWidth(20); - */ -Factory.addGetterSetter(Tag, 'pointerWidth', 0, getNumberValidator()); - -/** - * get/set pointer height - * @method - * @name Konva.Tag#pointerHeight - * @param {Number} pointerHeight - * @returns {Number} - * @example - * tag.pointerHeight(20); - */ - -Factory.addGetterSetter(Tag, 'pointerHeight', 0, getNumberValidator()); - -/** - * get/set cornerRadius - * @name Konva.Tag#cornerRadius - * @method - * @param {Number} cornerRadius - * @returns {Number} - * @example - * tag.cornerRadius(20); - * - * // set different corner radius values - * // top-left, top-right, bottom-right, bottom-left - * tag.cornerRadius([0, 10, 20, 30]); - */ - -Factory.addGetterSetter( - Tag, - 'cornerRadius', - 0, - getNumberOrArrayOfNumbersValidator(4) -); diff --git a/src/shapes/Line.ts b/src/shapes/Line.ts deleted file mode 100644 index 664fc3d50..000000000 --- a/src/shapes/Line.ts +++ /dev/null @@ -1,357 +0,0 @@ -import { Factory } from '../Factory'; -import { _registerNode } from '../Global'; -import { Shape, ShapeConfig } from '../Shape'; -import { getNumberArrayValidator, getNumberValidator } from '../Validators'; - -import { Context } from '../Context'; -import { GetSet } from '../types'; - -function getControlPoints(x0, y0, x1, y1, x2, y2, t) { - const d01 = Math.sqrt(Math.pow(x1 - x0, 2) + Math.pow(y1 - y0, 2)), - d12 = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)), - fa = (t * d01) / (d01 + d12), - fb = (t * d12) / (d01 + d12), - p1x = x1 - fa * (x2 - x0), - p1y = y1 - fa * (y2 - y0), - p2x = x1 + fb * (x2 - x0), - p2y = y1 + fb * (y2 - y0); - - return [p1x, p1y, p2x, p2y]; -} - -function expandPoints(p, tension) { - const len = p.length, - allPoints: Array = []; - - for (let n = 2; n < len - 2; n += 2) { - const cp = getControlPoints( - p[n - 2], - p[n - 1], - p[n], - p[n + 1], - p[n + 2], - p[n + 3], - tension - ); - if (isNaN(cp[0])) { - continue; - } - allPoints.push(cp[0]); - allPoints.push(cp[1]); - allPoints.push(p[n]); - allPoints.push(p[n + 1]); - allPoints.push(cp[2]); - allPoints.push(cp[3]); - } - - return allPoints; -} - -export interface LineConfig extends ShapeConfig { - points?: - | number[] - | Int8Array - | Uint8Array - | Uint8ClampedArray - | Int16Array - | Uint16Array - | Int32Array - | Uint32Array - | Float32Array - | Float64Array; - tension?: number; - closed?: boolean; - bezier?: boolean; -} - -/** - * Line constructor.  Lines are defined by an array of points and - * a tension - * @constructor - * @memberof Konva - * @augments Konva.Shape - * @param {Object} config - * @param {Array} config.points Flat array of points coordinates. You should define them as [x1, y1, x2, y2, x3, y3]. - * @param {Number} [config.tension] Higher values will result in a more curvy line. A value of 0 will result in no interpolation. - * The default is 0 - * @param {Boolean} [config.closed] defines whether or not the line shape is closed, creating a polygon or blob - * @param {Boolean} [config.bezier] if no tension is provided but bezier=true, we draw the line as a bezier using the passed points - * @@shapeParams - * @@nodeParams - * @example - * var line = new Konva.Line({ - * x: 100, - * y: 50, - * points: [73, 70, 340, 23, 450, 60, 500, 20], - * stroke: 'red', - * tension: 1 - * }); - */ - -export class Line< - Config extends LineConfig = LineConfig -> extends Shape { - constructor(config?: Config) { - super(config); - this.on( - 'pointsChange.konva tensionChange.konva closedChange.konva bezierChange.konva', - function () { - this._clearCache('tensionPoints'); - } - ); - } - - _sceneFunc(context: Context) { - let points = this.points(), - length = points.length, - tension = this.tension(), - closed = this.closed(), - bezier = this.bezier(), - tp, - len, - n; - - if (!length) { - return; - } - - context.beginPath(); - context.moveTo(points[0], points[1]); - - // tension - if (tension !== 0 && length > 4) { - tp = this.getTensionPoints(); - len = tp.length; - n = closed ? 0 : 4; - - if (!closed) { - context.quadraticCurveTo(tp[0], tp[1], tp[2], tp[3]); - } - - while (n < len - 2) { - context.bezierCurveTo( - tp[n++], - tp[n++], - tp[n++], - tp[n++], - tp[n++], - tp[n++] - ); - } - - if (!closed) { - context.quadraticCurveTo( - tp[len - 2], - tp[len - 1], - points[length - 2], - points[length - 1] - ); - } - } else if (bezier) { - // no tension but bezier - n = 2; - - while (n < length) { - context.bezierCurveTo( - points[n++], - points[n++], - points[n++], - points[n++], - points[n++], - points[n++] - ); - } - } else { - // no tension - for (n = 2; n < length; n += 2) { - context.lineTo(points[n], points[n + 1]); - } - } - - // closed e.g. polygons and blobs - if (closed) { - context.closePath(); - context.fillStrokeShape(this); - } else { - // open e.g. lines and splines - context.strokeShape(this); - } - } - getTensionPoints() { - return this._getCache('tensionPoints', this._getTensionPoints); - } - _getTensionPoints() { - if (this.closed()) { - return this._getTensionPointsClosed(); - } else { - return expandPoints(this.points(), this.tension()); - } - } - _getTensionPointsClosed() { - const p = this.points(), - len = p.length, - tension = this.tension(), - firstControlPoints = getControlPoints( - p[len - 2], - p[len - 1], - p[0], - p[1], - p[2], - p[3], - tension - ), - lastControlPoints = getControlPoints( - p[len - 4], - p[len - 3], - p[len - 2], - p[len - 1], - p[0], - p[1], - tension - ), - middle = expandPoints(p, tension), - tp = [firstControlPoints[2], firstControlPoints[3]] - .concat(middle) - .concat([ - lastControlPoints[0], - lastControlPoints[1], - p[len - 2], - p[len - 1], - lastControlPoints[2], - lastControlPoints[3], - firstControlPoints[0], - firstControlPoints[1], - p[0], - p[1], - ]); - - return tp; - } - getWidth() { - return this.getSelfRect().width; - } - getHeight() { - return this.getSelfRect().height; - } - // overload size detection - getSelfRect() { - let points = this.points(); - if (points.length < 4) { - return { - x: points[0] || 0, - y: points[1] || 0, - width: 0, - height: 0, - }; - } - if (this.tension() !== 0) { - points = [ - points[0], - points[1], - ...this._getTensionPoints(), - points[points.length - 2], - points[points.length - 1], - ]; - } else { - points = this.points(); - } - let minX = points[0]; - let maxX = points[0]; - let minY = points[1]; - let maxY = points[1]; - let x, y; - for (let i = 0; i < points.length / 2; i++) { - x = points[i * 2]; - y = points[i * 2 + 1]; - minX = Math.min(minX, x); - maxX = Math.max(maxX, x); - minY = Math.min(minY, y); - maxY = Math.max(maxY, y); - } - return { - x: minX, - y: minY, - width: maxX - minX, - height: maxY - minY, - }; - } - - closed: GetSet; - bezier: GetSet; - tension: GetSet; - points: GetSet; -} - -Line.prototype.className = 'Line'; -Line.prototype._attrsAffectingSize = ['points', 'bezier', 'tension']; -_registerNode(Line); - -// add getters setters -Factory.addGetterSetter(Line, 'closed', false); - -/** - * get/set closed flag. The default is false - * @name Konva.Line#closed - * @method - * @param {Boolean} closed - * @returns {Boolean} - * @example - * // get closed flag - * var closed = line.closed(); - * - * // close the shape - * line.closed(true); - * - * // open the shape - * line.closed(false); - */ - -Factory.addGetterSetter(Line, 'bezier', false); - -/** - * get/set bezier flag. The default is false - * @name Konva.Line#bezier - * @method - * @param {Boolean} bezier - * @returns {Boolean} - * @example - * // get whether the line is a bezier - * var isBezier = line.bezier(); - * - * // set whether the line is a bezier - * line.bezier(true); - */ - -Factory.addGetterSetter(Line, 'tension', 0, getNumberValidator()); - -/** - * get/set tension - * @name Konva.Line#tension - * @method - * @param {Number} tension Higher values will result in a more curvy line. A value of 0 will result in no interpolation. The default is 0 - * @returns {Number} - * @example - * // get tension - * var tension = line.tension(); - * - * // set tension - * line.tension(3); - */ - -Factory.addGetterSetter(Line, 'points', [], getNumberArrayValidator()); -/** - * get/set points array. Points is a flat array [x1, y1, x2, y2]. It is flat for performance reasons. - * @name Konva.Line#points - * @method - * @param {Array} points - * @returns {Array} - * @example - * // get points - * var points = line.points(); - * - * // set points - * line.points([10, 20, 30, 40, 50, 60]); - * - * // push a new point - * line.points(line.points().concat([70, 80])); - */ diff --git a/src/shapes/Path.ts b/src/shapes/Path.ts deleted file mode 100644 index e9e634932..000000000 --- a/src/shapes/Path.ts +++ /dev/null @@ -1,942 +0,0 @@ -import { Factory } from '../Factory'; -import { Shape, ShapeConfig } from '../Shape'; -import { _registerNode } from '../Global'; - -import { GetSet, PathSegment } from '../types'; -import { - getCubicArcLength, - getQuadraticArcLength, - t2length, -} from '../BezierFunctions'; - -export interface PathConfig extends ShapeConfig { - data?: string; -} -/** - * Path constructor. - * @author Jason Follas - * @constructor - * @memberof Konva - * @augments Konva.Shape - * @param {Object} config - * @param {String} config.data SVG data string - * @@shapeParams - * @@nodeParams - * @example - * var path = new Konva.Path({ - * x: 240, - * y: 40, - * data: 'M12.582,9.551C3.251,16.237,0.921,29.021,7.08,38.564l-2.36,1.689l4.893,2.262l4.893,2.262l-0.568-5.36l-0.567-5.359l-2.365,1.694c-4.657-7.375-2.83-17.185,4.352-22.33c7.451-5.338,17.817-3.625,23.156,3.824c5.337,7.449,3.625,17.813-3.821,23.152l2.857,3.988c9.617-6.893,11.827-20.277,4.935-29.896C35.591,4.87,22.204,2.658,12.582,9.551z', - * fill: 'green', - * scaleX: 2, - * scaleY: 2 - * }); - */ -export class Path extends Shape { - dataArray: PathSegment[] = []; - pathLength = 0; - - constructor(config?: PathConfig) { - super(config); - this._readDataAttribute(); - - this.on('dataChange.konva', function () { - this._readDataAttribute(); - }); - } - - _readDataAttribute() { - this.dataArray = Path.parsePathData(this.data()); - this.pathLength = Path.getPathLength(this.dataArray); - } - - _sceneFunc(context) { - const ca = this.dataArray; - - // context position - context.beginPath(); - let isClosed = false; - for (let n = 0; n < ca.length; n++) { - const c = ca[n].command; - const p = ca[n].points; - switch (c) { - case 'L': - context.lineTo(p[0], p[1]); - break; - case 'M': - context.moveTo(p[0], p[1]); - break; - case 'C': - context.bezierCurveTo(p[0], p[1], p[2], p[3], p[4], p[5]); - break; - case 'Q': - context.quadraticCurveTo(p[0], p[1], p[2], p[3]); - break; - case 'A': - var cx = p[0], - cy = p[1], - rx = p[2], - ry = p[3], - theta = p[4], - dTheta = p[5], - psi = p[6], - fs = p[7]; - - var r = rx > ry ? rx : ry; - var scaleX = rx > ry ? 1 : rx / ry; - var scaleY = rx > ry ? ry / rx : 1; - - context.translate(cx, cy); - context.rotate(psi); - context.scale(scaleX, scaleY); - context.arc(0, 0, r, theta, theta + dTheta, 1 - fs); - context.scale(1 / scaleX, 1 / scaleY); - context.rotate(-psi); - context.translate(-cx, -cy); - - break; - case 'z': - isClosed = true; - context.closePath(); - break; - } - } - - if (!isClosed && !this.hasFill()) { - context.strokeShape(this); - } else { - context.fillStrokeShape(this); - } - } - getSelfRect() { - let points: Array = []; - this.dataArray.forEach(function (data) { - if (data.command === 'A') { - // Approximates by breaking curve into line segments - const start = data.points[4]; - // 4 = theta - const dTheta = data.points[5]; - // 5 = dTheta - const end = data.points[4] + dTheta; - let inc = Math.PI / 180.0; - // 1 degree resolution - if (Math.abs(start - end) < inc) { - inc = Math.abs(start - end); - } - if (dTheta < 0) { - // clockwise - for (let t = start - inc; t > end; t -= inc) { - const point = Path.getPointOnEllipticalArc( - data.points[0], - data.points[1], - data.points[2], - data.points[3], - t, - 0 - ); - points.push(point.x, point.y); - } - } else { - // counter-clockwise - for (let t = start + inc; t < end; t += inc) { - const point = Path.getPointOnEllipticalArc( - data.points[0], - data.points[1], - data.points[2], - data.points[3], - t, - 0 - ); - points.push(point.x, point.y); - } - } - } else if (data.command === 'C') { - // Approximates by breaking curve into 100 line segments - for (let t = 0.0; t <= 1; t += 0.01) { - const point = Path.getPointOnCubicBezier( - t, - data.start.x, - data.start.y, - data.points[0], - data.points[1], - data.points[2], - data.points[3], - data.points[4], - data.points[5] - ); - points.push(point.x, point.y); - } - } else { - // TODO: how can we calculate bezier curves better? - points = points.concat(data.points); - } - }); - let minX = points[0]; - let maxX = points[0]; - let minY = points[1]; - let maxY = points[1]; - let x, y; - for (let i = 0; i < points.length / 2; i++) { - x = points[i * 2]; - y = points[i * 2 + 1]; - - // skip bad values - if (!isNaN(x)) { - minX = Math.min(minX, x); - maxX = Math.max(maxX, x); - } - if (!isNaN(y)) { - minY = Math.min(minY, y); - maxY = Math.max(maxY, y); - } - } - return { - x: minX, - y: minY, - width: maxX - minX, - height: maxY - minY, - }; - } - /** - * Return length of the path. - * @method - * @name Konva.Path#getLength - * @returns {Number} length - * @example - * var length = path.getLength(); - */ - getLength() { - return this.pathLength; - } - /** - * Get point on path at specific length of the path - * @method - * @name Konva.Path#getPointAtLength - * @param {Number} length length - * @returns {Object} point {x,y} point - * @example - * var point = path.getPointAtLength(10); - */ - getPointAtLength(length) { - return Path.getPointAtLengthOfDataArray(length, this.dataArray); - } - - data: GetSet; - - static getLineLength(x1, y1, x2, y2) { - return Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)); - } - - static getPathLength(dataArray: PathSegment[]) { - let pathLength = 0; - for (let i = 0; i < dataArray.length; ++i) { - pathLength += dataArray[i].pathLength; - } - return pathLength; - } - - static getPointAtLengthOfDataArray(length: number, dataArray: PathSegment[]) { - let points: number[], - i = 0, - ii = dataArray.length; - - if (!ii) { - return null; - } - - while (i < ii && length > dataArray[i].pathLength) { - length -= dataArray[i].pathLength; - ++i; - } - - if (i === ii) { - points = dataArray[i - 1].points.slice(-2); - return { - x: points[0], - y: points[1], - }; - } - - if (length < 0.01) { - points = dataArray[i].points.slice(0, 2); - return { - x: points[0], - y: points[1], - }; - } - - const cp = dataArray[i]; - const p = cp.points; - switch (cp.command) { - case 'L': - return Path.getPointOnLine(length, cp.start.x, cp.start.y, p[0], p[1]); - case 'C': - return Path.getPointOnCubicBezier( - t2length(length, Path.getPathLength(dataArray), (i) => { - return getCubicArcLength( - [cp.start.x, p[0], p[2], p[4]], - [cp.start.y, p[1], p[3], p[5]], - i - ); - }), - cp.start.x, - cp.start.y, - p[0], - p[1], - p[2], - p[3], - p[4], - p[5] - ); - case 'Q': - return Path.getPointOnQuadraticBezier( - t2length(length, Path.getPathLength(dataArray), (i) => { - return getQuadraticArcLength( - [cp.start.x, p[0], p[2]], - [cp.start.y, p[1], p[3]], - i - ); - }), - cp.start.x, - cp.start.y, - p[0], - p[1], - p[2], - p[3] - ); - case 'A': - var cx = p[0], - cy = p[1], - rx = p[2], - ry = p[3], - theta = p[4], - dTheta = p[5], - psi = p[6]; - theta += (dTheta * length) / cp.pathLength; - return Path.getPointOnEllipticalArc(cx, cy, rx, ry, theta, psi); - } - - return null; - } - - static getPointOnLine( - dist: number, - P1x: number, - P1y: number, - P2x: number, - P2y: number, - fromX?: number, - fromY?: number - ) { - fromX = fromX ?? P1x; - fromY = fromY ?? P1y; - - const len = this.getLineLength(P1x, P1y, P2x, P2y); - if (len < 1e-10) { - return { x: P1x, y: P1y }; - } - - if (P2x === P1x) { - // Vertical line - return { x: fromX, y: fromY + (P2y > P1y ? dist : -dist) }; - } - - const m = (P2y - P1y) / (P2x - P1x); - const run = Math.sqrt((dist * dist) / (1 + m * m)) * (P2x < P1x ? -1 : 1); - const rise = m * run; - - if (Math.abs(fromY - P1y - m * (fromX - P1x)) < 1e-10) { - return { x: fromX + run, y: fromY + rise }; - } - - const u = - ((fromX - P1x) * (P2x - P1x) + (fromY - P1y) * (P2y - P1y)) / (len * len); - const ix = P1x + u * (P2x - P1x); - const iy = P1y + u * (P2y - P1y); - const pRise = this.getLineLength(fromX, fromY, ix, iy); - const pRun = Math.sqrt(dist * dist - pRise * pRise); - const adjustedRun = - Math.sqrt((pRun * pRun) / (1 + m * m)) * (P2x < P1x ? -1 : 1); - const adjustedRise = m * adjustedRun; - - return { x: ix + adjustedRun, y: iy + adjustedRise }; - } - - static getPointOnCubicBezier(pct, P1x, P1y, P2x, P2y, P3x, P3y, P4x, P4y) { - function CB1(t) { - return t * t * t; - } - function CB2(t) { - return 3 * t * t * (1 - t); - } - function CB3(t) { - return 3 * t * (1 - t) * (1 - t); - } - function CB4(t) { - return (1 - t) * (1 - t) * (1 - t); - } - const x = P4x * CB1(pct) + P3x * CB2(pct) + P2x * CB3(pct) + P1x * CB4(pct); - const y = P4y * CB1(pct) + P3y * CB2(pct) + P2y * CB3(pct) + P1y * CB4(pct); - - return { - x: x, - y: y, - }; - } - static getPointOnQuadraticBezier(pct, P1x, P1y, P2x, P2y, P3x, P3y) { - function QB1(t) { - return t * t; - } - function QB2(t) { - return 2 * t * (1 - t); - } - function QB3(t) { - return (1 - t) * (1 - t); - } - const x = P3x * QB1(pct) + P2x * QB2(pct) + P1x * QB3(pct); - const y = P3y * QB1(pct) + P2y * QB2(pct) + P1y * QB3(pct); - - return { - x: x, - y: y, - }; - } - static getPointOnEllipticalArc( - cx: number, - cy: number, - rx: number, - ry: number, - theta: number, - psi: number - ) { - const cosPsi = Math.cos(psi), - sinPsi = Math.sin(psi); - const pt = { - x: rx * Math.cos(theta), - y: ry * Math.sin(theta), - }; - return { - x: cx + (pt.x * cosPsi - pt.y * sinPsi), - y: cy + (pt.x * sinPsi + pt.y * cosPsi), - }; - } - /* - * get parsed data array from the data - * string. V, v, H, h, and l data are converted to - * L data for the purpose of high performance Path - * rendering - */ - static parsePathData(data): PathSegment[] { - // Path Data Segment must begin with a moveTo - //m (x y)+ Relative moveTo (subsequent points are treated as lineTo) - //M (x y)+ Absolute moveTo (subsequent points are treated as lineTo) - //l (x y)+ Relative lineTo - //L (x y)+ Absolute LineTo - //h (x)+ Relative horizontal lineTo - //H (x)+ Absolute horizontal lineTo - //v (y)+ Relative vertical lineTo - //V (y)+ Absolute vertical lineTo - //z (closepath) - //Z (closepath) - //c (x1 y1 x2 y2 x y)+ Relative Bezier curve - //C (x1 y1 x2 y2 x y)+ Absolute Bezier curve - //q (x1 y1 x y)+ Relative Quadratic Bezier - //Q (x1 y1 x y)+ Absolute Quadratic Bezier - //t (x y)+ Shorthand/Smooth Relative Quadratic Bezier - //T (x y)+ Shorthand/Smooth Absolute Quadratic Bezier - //s (x2 y2 x y)+ Shorthand/Smooth Relative Bezier curve - //S (x2 y2 x y)+ Shorthand/Smooth Absolute Bezier curve - //a (rx ry x-axis-rotation large-arc-flag sweep-flag x y)+ Relative Elliptical Arc - //A (rx ry x-axis-rotation large-arc-flag sweep-flag x y)+ Absolute Elliptical Arc - - // return early if data is not defined - if (!data) { - return []; - } - - // command string - let cs = data; - - // command chars - const cc = [ - 'm', - 'M', - 'l', - 'L', - 'v', - 'V', - 'h', - 'H', - 'z', - 'Z', - 'c', - 'C', - 'q', - 'Q', - 't', - 'T', - 's', - 'S', - 'a', - 'A', - ]; - // convert white spaces to commas - cs = cs.replace(new RegExp(' ', 'g'), ','); - // create pipes so that we can split the data - for (var n = 0; n < cc.length; n++) { - cs = cs.replace(new RegExp(cc[n], 'g'), '|' + cc[n]); - } - // create array - const arr = cs.split('|'); - const ca: PathSegment[] = []; - const coords: string[] = []; - // init context point - let cpx = 0; - let cpy = 0; - - const re = /([-+]?((\d+\.\d+)|((\d+)|(\.\d+)))(?:e[-+]?\d+)?)/gi; - let match; - for (n = 1; n < arr.length; n++) { - let str = arr[n]; - let c = str.charAt(0); - str = str.slice(1); - - coords.length = 0; - while ((match = re.exec(str))) { - coords.push(match[0]); - } - - // while ((match = re.exec(str))) { - // coords.push(match[0]); - // } - const p: number[] = []; - - for (let j = 0, jlen = coords.length; j < jlen; j++) { - // extra case for merged flags - if (coords[j] === '00') { - p.push(0, 0); - continue; - } - const parsed = parseFloat(coords[j]); - if (!isNaN(parsed)) { - p.push(parsed); - } else { - p.push(0); - } - } - - while (p.length > 0) { - if (isNaN(p[0])) { - // case for a trailing comma before next command - break; - } - - let cmd: string = ''; - let points: number[] = []; - const startX = cpx, - startY = cpy; - // Move var from within the switch to up here (jshint) - var prevCmd, ctlPtx, ctlPty; // Ss, Tt - var rx, ry, psi, fa, fs, x1, y1; // Aa - - // convert l, H, h, V, and v to L - switch (c) { - // Note: Keep the lineTo's above the moveTo's in this switch - case 'l': - cpx += p.shift()!; - cpy += p.shift()!; - cmd = 'L'; - points.push(cpx, cpy); - break; - case 'L': - cpx = p.shift()!; - cpy = p.shift()!; - points.push(cpx, cpy); - break; - // Note: lineTo handlers need to be above this point - case 'm': - var dx = p.shift()!; - var dy = p.shift()!; - cpx += dx; - cpy += dy; - cmd = 'M'; - // After closing the path move the current position - // to the the first point of the path (if any). - if (ca.length > 2 && ca[ca.length - 1].command === 'z') { - for (let idx = ca.length - 2; idx >= 0; idx--) { - if (ca[idx].command === 'M') { - cpx = ca[idx].points[0] + dx; - cpy = ca[idx].points[1] + dy; - break; - } - } - } - points.push(cpx, cpy); - c = 'l'; - // subsequent points are treated as relative lineTo - break; - case 'M': - cpx = p.shift()!; - cpy = p.shift()!; - cmd = 'M'; - points.push(cpx, cpy); - c = 'L'; - // subsequent points are treated as absolute lineTo - break; - - case 'h': - cpx += p.shift()!; - cmd = 'L'; - points.push(cpx, cpy); - break; - case 'H': - cpx = p.shift()!; - cmd = 'L'; - points.push(cpx, cpy); - break; - case 'v': - cpy += p.shift()!; - cmd = 'L'; - points.push(cpx, cpy); - break; - case 'V': - cpy = p.shift()!; - cmd = 'L'; - points.push(cpx, cpy); - break; - case 'C': - points.push(p.shift()!, p.shift()!, p.shift()!, p.shift()!); - cpx = p.shift()!; - cpy = p.shift()!; - points.push(cpx, cpy); - break; - case 'c': - points.push( - cpx + p.shift()!, - cpy + p.shift()!, - cpx + p.shift()!, - cpy + p.shift()! - ); - cpx += p.shift()!; - cpy += p.shift()!; - cmd = 'C'; - points.push(cpx, cpy); - break; - case 'S': - ctlPtx = cpx; - ctlPty = cpy; - prevCmd = ca[ca.length - 1]; - if (prevCmd.command === 'C') { - ctlPtx = cpx + (cpx - prevCmd.points[2]); - ctlPty = cpy + (cpy - prevCmd.points[3]); - } - points.push(ctlPtx, ctlPty, p.shift()!, p.shift()!); - cpx = p.shift()!; - cpy = p.shift()!; - cmd = 'C'; - points.push(cpx, cpy); - break; - case 's': - ctlPtx = cpx; - ctlPty = cpy; - prevCmd = ca[ca.length - 1]; - if (prevCmd.command === 'C') { - ctlPtx = cpx + (cpx - prevCmd.points[2]); - ctlPty = cpy + (cpy - prevCmd.points[3]); - } - points.push(ctlPtx, ctlPty, cpx + p.shift()!, cpy + p.shift()!); - cpx += p.shift()!; - cpy += p.shift()!; - cmd = 'C'; - points.push(cpx, cpy); - break; - case 'Q': - points.push(p.shift()!, p.shift()!); - cpx = p.shift()!; - cpy = p.shift()!; - points.push(cpx, cpy); - break; - case 'q': - points.push(cpx + p.shift()!, cpy + p.shift()!); - cpx += p.shift()!; - cpy += p.shift()!; - cmd = 'Q'; - points.push(cpx, cpy); - break; - case 'T': - ctlPtx = cpx; - ctlPty = cpy; - prevCmd = ca[ca.length - 1]; - if (prevCmd.command === 'Q') { - ctlPtx = cpx + (cpx - prevCmd.points[0]); - ctlPty = cpy + (cpy - prevCmd.points[1]); - } - cpx = p.shift()!; - cpy = p.shift()!; - cmd = 'Q'; - points.push(ctlPtx, ctlPty, cpx, cpy); - break; - case 't': - ctlPtx = cpx; - ctlPty = cpy; - prevCmd = ca[ca.length - 1]; - if (prevCmd.command === 'Q') { - ctlPtx = cpx + (cpx - prevCmd.points[0]); - ctlPty = cpy + (cpy - prevCmd.points[1]); - } - cpx += p.shift()!; - cpy += p.shift()!; - cmd = 'Q'; - points.push(ctlPtx, ctlPty, cpx, cpy); - break; - case 'A': - rx = p.shift()!; - ry = p.shift()!; - psi = p.shift()!; - fa = p.shift()!; - fs = p.shift()!; - x1 = cpx; - y1 = cpy; - cpx = p.shift()!; - cpy = p.shift()!; - cmd = 'A'; - points = this.convertEndpointToCenterParameterization( - x1, - y1, - cpx, - cpy, - fa, - fs, - rx, - ry, - psi - ); - break; - case 'a': - rx = p.shift(); - ry = p.shift(); - psi = p.shift(); - fa = p.shift(); - fs = p.shift(); - x1 = cpx; - y1 = cpy; - cpx += p.shift()!; - cpy += p.shift()!; - cmd = 'A'; - points = this.convertEndpointToCenterParameterization( - x1, - y1, - cpx, - cpy, - fa, - fs, - rx, - ry, - psi - ); - break; - } - - ca.push({ - command: cmd || c, - points: points, - start: { - x: startX, - y: startY, - }, - pathLength: this.calcLength(startX, startY, cmd || c, points), - }); - } - - if (c === 'z' || c === 'Z') { - ca.push({ - command: 'z', - points: [], - start: undefined as any, - pathLength: 0, - }); - } - } - - return ca; - } - static calcLength(x, y, cmd, points) { - let len, p1, p2, t; - const path = Path; - - switch (cmd) { - case 'L': - return path.getLineLength(x, y, points[0], points[1]); - case 'C': - return getCubicArcLength( - [x, points[0], points[2], points[4]], - [y, points[1], points[3], points[5]], - 1 - ); - case 'Q': - return getQuadraticArcLength( - [x, points[0], points[2]], - [y, points[1], points[3]], - 1 - ); - case 'A': - // Approximates by breaking curve into line segments - len = 0.0; - var start = points[4]; - // 4 = theta - var dTheta = points[5]; - // 5 = dTheta - var end = points[4] + dTheta; - var inc = Math.PI / 180.0; - // 1 degree resolution - if (Math.abs(start - end) < inc) { - inc = Math.abs(start - end); - } - // Note: for purpose of calculating arc length, not going to worry about rotating X-axis by angle psi - p1 = path.getPointOnEllipticalArc( - points[0], - points[1], - points[2], - points[3], - start, - 0 - ); - if (dTheta < 0) { - // clockwise - for (t = start - inc; t > end; t -= inc) { - p2 = path.getPointOnEllipticalArc( - points[0], - points[1], - points[2], - points[3], - t, - 0 - ); - len += path.getLineLength(p1.x, p1.y, p2.x, p2.y); - p1 = p2; - } - } else { - // counter-clockwise - for (t = start + inc; t < end; t += inc) { - p2 = path.getPointOnEllipticalArc( - points[0], - points[1], - points[2], - points[3], - t, - 0 - ); - len += path.getLineLength(p1.x, p1.y, p2.x, p2.y); - p1 = p2; - } - } - p2 = path.getPointOnEllipticalArc( - points[0], - points[1], - points[2], - points[3], - end, - 0 - ); - len += path.getLineLength(p1.x, p1.y, p2.x, p2.y); - - return len; - } - - return 0; - } - static convertEndpointToCenterParameterization( - x1, - y1, - x2, - y2, - fa, - fs, - rx, - ry, - psiDeg - ) { - // Derived from: http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes - const psi = psiDeg * (Math.PI / 180.0); - const xp = - (Math.cos(psi) * (x1 - x2)) / 2.0 + (Math.sin(psi) * (y1 - y2)) / 2.0; - const yp = - (-1 * Math.sin(psi) * (x1 - x2)) / 2.0 + - (Math.cos(psi) * (y1 - y2)) / 2.0; - - const lambda = (xp * xp) / (rx * rx) + (yp * yp) / (ry * ry); - - if (lambda > 1) { - rx *= Math.sqrt(lambda); - ry *= Math.sqrt(lambda); - } - - let f = Math.sqrt( - (rx * rx * (ry * ry) - rx * rx * (yp * yp) - ry * ry * (xp * xp)) / - (rx * rx * (yp * yp) + ry * ry * (xp * xp)) - ); - - if (fa === fs) { - f *= -1; - } - if (isNaN(f)) { - f = 0; - } - - const cxp = (f * rx * yp) / ry; - const cyp = (f * -ry * xp) / rx; - - const cx = (x1 + x2) / 2.0 + Math.cos(psi) * cxp - Math.sin(psi) * cyp; - const cy = (y1 + y2) / 2.0 + Math.sin(psi) * cxp + Math.cos(psi) * cyp; - - const vMag = function (v) { - return Math.sqrt(v[0] * v[0] + v[1] * v[1]); - }; - const vRatio = function (u, v) { - return (u[0] * v[0] + u[1] * v[1]) / (vMag(u) * vMag(v)); - }; - const vAngle = function (u, v) { - return (u[0] * v[1] < u[1] * v[0] ? -1 : 1) * Math.acos(vRatio(u, v)); - }; - const theta = vAngle([1, 0], [(xp - cxp) / rx, (yp - cyp) / ry]); - const u = [(xp - cxp) / rx, (yp - cyp) / ry]; - const v = [(-1 * xp - cxp) / rx, (-1 * yp - cyp) / ry]; - let dTheta = vAngle(u, v); - - if (vRatio(u, v) <= -1) { - dTheta = Math.PI; - } - if (vRatio(u, v) >= 1) { - dTheta = 0; - } - if (fs === 0 && dTheta > 0) { - dTheta = dTheta - 2 * Math.PI; - } - if (fs === 1 && dTheta < 0) { - dTheta = dTheta + 2 * Math.PI; - } - return [cx, cy, rx, ry, theta, dTheta, psi, fs]; - } -} - -Path.prototype.className = 'Path'; -Path.prototype._attrsAffectingSize = ['data']; -_registerNode(Path); - -/** - * get/set SVG path data string. This method - * also automatically parses the data string - * into a data array. Currently supported SVG data: - * M, m, L, l, H, h, V, v, Q, q, T, t, C, c, S, s, A, a, Z, z - * @name Konva.Path#data - * @method - * @param {String} data svg path string - * @returns {String} - * @example - * // get data - * var data = path.data(); - * - * // set data - * path.data('M200,100h100v50z'); - */ -Factory.addGetterSetter(Path, 'data'); diff --git a/src/shapes/Rect.ts b/src/shapes/Rect.ts deleted file mode 100644 index ec8ad79de..000000000 --- a/src/shapes/Rect.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { Factory } from '../Factory'; -import { Shape, ShapeConfig } from '../Shape'; -import { _registerNode } from '../Global'; - -import { Util } from '../Util'; -import { GetSet } from '../types'; -import { Context } from '../Context'; -import { getNumberOrArrayOfNumbersValidator } from '../Validators'; - -export interface RectConfig extends ShapeConfig { - cornerRadius?: number | number[]; -} - -/** - * Rect constructor - * @constructor - * @memberof Konva - * @augments Konva.Shape - * @param {Object} config - * @param {Number} [config.cornerRadius] - * @@shapeParams - * @@nodeParams - * @example - * var rect = new Konva.Rect({ - * width: 100, - * height: 50, - * fill: 'red', - * stroke: 'black', - * strokeWidth: 5 - * }); - */ -export class Rect extends Shape { - _sceneFunc(context: Context) { - const cornerRadius = this.cornerRadius(), - width = this.width(), - height = this.height(); - - context.beginPath(); - - if (!cornerRadius) { - // simple rect - don't bother doing all that complicated maths stuff. - context.rect(0, 0, width, height); - } else { - Util.drawRoundedRectPath(context, width, height, cornerRadius); - } - context.closePath(); - context.fillStrokeShape(this); - } - - cornerRadius: GetSet; -} - -Rect.prototype.className = 'Rect'; -_registerNode(Rect); - -/** - * get/set corner radius - * @method - * @name Konva.Rect#cornerRadius - * @param {Number} cornerRadius - * @returns {Number} - * @example - * // get corner radius - * var cornerRadius = rect.cornerRadius(); - * - * // set corner radius - * rect.cornerRadius(10); - * - * // set different corner radius values - * // top-left, top-right, bottom-right, bottom-left - * rect.cornerRadius([0, 10, 20, 30]); - */ -Factory.addGetterSetter( - Rect, - 'cornerRadius', - 0, - getNumberOrArrayOfNumbersValidator(4) -); diff --git a/src/shapes/RegularPolygon.ts b/src/shapes/RegularPolygon.ts deleted file mode 100644 index 6ade36cfa..000000000 --- a/src/shapes/RegularPolygon.ts +++ /dev/null @@ -1,129 +0,0 @@ -import { Factory } from '../Factory'; -import { Shape, ShapeConfig } from '../Shape'; -import { GetSet, Vector2d } from '../types'; -import { getNumberValidator } from '../Validators'; -import { _registerNode } from '../Global'; -import { Context } from '../Context'; - -export interface RegularPolygonConfig extends ShapeConfig { - sides: number; - radius: number; -} -/** - * RegularPolygon constructor. Examples include triangles, squares, pentagons, hexagons, etc. - * @constructor - * @memberof Konva - * @augments Konva.Shape - * @param {Object} config - * @param {Number} config.sides - * @param {Number} config.radius - * @@shapeParams - * @@nodeParams - * @example - * var hexagon = new Konva.RegularPolygon({ - * x: 100, - * y: 200, - * sides: 6, - * radius: 70, - * fill: 'red', - * stroke: 'black', - * strokeWidth: 4 - * }); - */ -export class RegularPolygon extends Shape { - _sceneFunc(context: Context) { - const points = this._getPoints(); - - context.beginPath(); - context.moveTo(points[0].x, points[0].y); - - for (let n = 1; n < points.length; n++) { - context.lineTo(points[n].x, points[n].y); - } - - context.closePath(); - context.fillStrokeShape(this); - } - _getPoints() { - const sides = this.attrs.sides as number; - const radius = this.attrs.radius || 0; - const points: Vector2d[] = []; - for (let n = 0; n < sides; n++) { - points.push({ - x: radius * Math.sin((n * 2 * Math.PI) / sides), - y: -1 * radius * Math.cos((n * 2 * Math.PI) / sides), - }); - } - return points; - } - getSelfRect() { - const points = this._getPoints(); - - let minX = points[0].x; - let maxX = points[0].y; - let minY = points[0].x; - let maxY = points[0].y; - points.forEach((point) => { - minX = Math.min(minX, point.x); - maxX = Math.max(maxX, point.x); - minY = Math.min(minY, point.y); - maxY = Math.max(maxY, point.y); - }); - return { - x: minX, - y: minY, - width: maxX - minX, - height: maxY - minY, - }; - } - getWidth() { - return this.radius() * 2; - } - getHeight() { - return this.radius() * 2; - } - setWidth(width: number) { - this.radius(width / 2); - } - setHeight(height: number) { - this.radius(height / 2); - } - - radius: GetSet; - sides: GetSet; -} - -RegularPolygon.prototype.className = 'RegularPolygon'; -RegularPolygon.prototype._centroid = true; -RegularPolygon.prototype._attrsAffectingSize = ['radius']; -_registerNode(RegularPolygon); - -/** - * get/set radius - * @method - * @name Konva.RegularPolygon#radius - * @param {Number} radius - * @returns {Number} - * @example - * // get radius - * var radius = shape.radius(); - * - * // set radius - * shape.radius(10); - */ -Factory.addGetterSetter(RegularPolygon, 'radius', 0, getNumberValidator()); - -/** - * get/set sides - * @method - * @name Konva.RegularPolygon#sides - * @param {Number} sides - * @returns {Number} - * @example - * // get sides - * var sides = shape.sides(); - * - * // set sides - * shape.sides(10); - */ -Factory.addGetterSetter(RegularPolygon, 'sides', 0, getNumberValidator()); diff --git a/src/shapes/Ring.ts b/src/shapes/Ring.ts deleted file mode 100644 index f22a47696..000000000 --- a/src/shapes/Ring.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { Factory } from '../Factory'; -import { Shape, ShapeConfig } from '../Shape'; -import { GetSet } from '../types'; -import { getNumberValidator } from '../Validators'; -import { _registerNode } from '../Global'; -import { Context } from '../Context'; - -export interface RingConfig extends ShapeConfig { - innerRadius: number; - outerRadius: number; -} - -const PIx2 = Math.PI * 2; -/** - * Ring constructor - * @constructor - * @augments Konva.Shape - * @memberof Konva - * @param {Object} config - * @param {Number} config.innerRadius - * @param {Number} config.outerRadius - * @@shapeParams - * @@nodeParams - * @example - * var ring = new Konva.Ring({ - * innerRadius: 40, - * outerRadius: 80, - * fill: 'red', - * stroke: 'black', - * strokeWidth: 5 - * }); - */ -export class Ring extends Shape { - _sceneFunc(context: Context) { - context.beginPath(); - context.arc(0, 0, this.innerRadius(), 0, PIx2, false); - context.moveTo(this.outerRadius(), 0); - context.arc(0, 0, this.outerRadius(), PIx2, 0, true); - context.closePath(); - context.fillStrokeShape(this); - } - getWidth() { - return this.outerRadius() * 2; - } - getHeight() { - return this.outerRadius() * 2; - } - setWidth(width: number) { - this.outerRadius(width / 2); - } - setHeight(height: number) { - this.outerRadius(height / 2); - } - - outerRadius: GetSet; - innerRadius: GetSet; -} - -Ring.prototype.className = 'Ring'; -Ring.prototype._centroid = true; -Ring.prototype._attrsAffectingSize = ['innerRadius', 'outerRadius']; -_registerNode(Ring); - -/** - * get/set innerRadius - * @method - * @name Konva.Ring#innerRadius - * @param {Number} innerRadius - * @returns {Number} - * @example - * // get inner radius - * var innerRadius = ring.innerRadius(); - * - * // set inner radius - * ring.innerRadius(20); - */ - -Factory.addGetterSetter(Ring, 'innerRadius', 0, getNumberValidator()); - -/** - * get/set outerRadius - * @name Konva.Ring#outerRadius - * @method - * @param {Number} outerRadius - * @returns {Number} - * @example - * // get outer radius - * var outerRadius = ring.outerRadius(); - * - * // set outer radius - * ring.outerRadius(20); - */ -Factory.addGetterSetter(Ring, 'outerRadius', 0, getNumberValidator()); diff --git a/src/shapes/Sprite.ts b/src/shapes/Sprite.ts deleted file mode 100644 index a4ac4918d..000000000 --- a/src/shapes/Sprite.ts +++ /dev/null @@ -1,369 +0,0 @@ -import { Factory } from '../Factory'; -import { Context } from '../Context'; -import { Shape, ShapeConfig } from '../Shape'; -import { Animation } from '../Animation'; -import { getNumberValidator } from '../Validators'; -import { _registerNode } from '../Global'; - -import { GetSet } from '../types'; - -export interface SpriteConfig extends ShapeConfig { - animation: string; - animations: any; - frameIndex?: number; - image: HTMLImageElement; - frameRate?: number; -} - -/** - * Sprite constructor - * @constructor - * @memberof Konva - * @augments Konva.Shape - * @param {Object} config - * @param {String} config.animation animation key - * @param {Object} config.animations animation map - * @param {Integer} [config.frameIndex] animation frame index - * @param {Image} config.image image object - * @param {Integer} [config.frameRate] animation frame rate - * @@shapeParams - * @@nodeParams - * @example - * var imageObj = new Image(); - * imageObj.onload = function() { - * var sprite = new Konva.Sprite({ - * x: 200, - * y: 100, - * image: imageObj, - * animation: 'standing', - * animations: { - * standing: [ - * // x, y, width, height (6 frames) - * 0, 0, 49, 109, - * 52, 0, 49, 109, - * 105, 0, 49, 109, - * 158, 0, 49, 109, - * 210, 0, 49, 109, - * 262, 0, 49, 109 - * ], - * kicking: [ - * // x, y, width, height (6 frames) - * 0, 109, 45, 98, - * 45, 109, 45, 98, - * 95, 109, 63, 98, - * 156, 109, 70, 98, - * 229, 109, 60, 98, - * 287, 109, 41, 98 - * ] - * }, - * frameRate: 7, - * frameIndex: 0 - * }); - * }; - * imageObj.src = '/path/to/image.jpg' - */ -export class Sprite extends Shape { - _updated = true; - anim: Animation; - interval: any; - constructor(config: SpriteConfig) { - super(config); - this.anim = new Animation(() => { - // if we don't need to redraw layer we should return false - const updated = this._updated; - this._updated = false; - return updated; - }); - this.on('animationChange.konva', function () { - // reset index when animation changes - this.frameIndex(0); - }); - this.on('frameIndexChange.konva', function () { - this._updated = true; - }); - // smooth change for frameRate - this.on('frameRateChange.konva', function () { - if (!this.anim.isRunning()) { - return; - } - clearInterval(this.interval); - this._setInterval(); - }); - } - - _sceneFunc(context: Context) { - const anim = this.animation(), - index = this.frameIndex(), - ix4 = index * 4, - set = this.animations()[anim], - offsets = this.frameOffsets(), - x = set[ix4 + 0], - y = set[ix4 + 1], - width = set[ix4 + 2], - height = set[ix4 + 3], - image = this.image(); - - if (this.hasFill() || this.hasStroke()) { - context.beginPath(); - context.rect(0, 0, width, height); - context.closePath(); - context.fillStrokeShape(this); - } - - if (image) { - if (offsets) { - const offset = offsets[anim], - ix2 = index * 2; - context.drawImage( - image, - x, - y, - width, - height, - offset[ix2 + 0], - offset[ix2 + 1], - width, - height - ); - } else { - context.drawImage(image, x, y, width, height, 0, 0, width, height); - } - } - } - _hitFunc(context: Context) { - const anim = this.animation(), - index = this.frameIndex(), - ix4 = index * 4, - set = this.animations()[anim], - offsets = this.frameOffsets(), - width = set[ix4 + 2], - height = set[ix4 + 3]; - - context.beginPath(); - if (offsets) { - const offset = offsets[anim]; - const ix2 = index * 2; - context.rect(offset[ix2 + 0], offset[ix2 + 1], width, height); - } else { - context.rect(0, 0, width, height); - } - context.closePath(); - context.fillShape(this); - } - - _useBufferCanvas() { - return super._useBufferCanvas(true); - } - - _setInterval() { - const that = this; - this.interval = setInterval(function () { - that._updateIndex(); - }, 1000 / this.frameRate()); - } - /** - * start sprite animation - * @method - * @name Konva.Sprite#start - */ - start() { - if (this.isRunning()) { - return; - } - const layer = this.getLayer(); - - /* - * animation object has no executable function because - * the updates are done with a fixed FPS with the setInterval - * below. The anim object only needs the layer reference for - * redraw - */ - this.anim.setLayers(layer); - this._setInterval(); - this.anim.start(); - } - /** - * stop sprite animation - * @method - * @name Konva.Sprite#stop - */ - stop() { - this.anim.stop(); - clearInterval(this.interval); - } - /** - * determine if animation of sprite is running or not. returns true or false - * @method - * @name Konva.Sprite#isRunning - * @returns {Boolean} - */ - isRunning() { - return this.anim.isRunning(); - } - _updateIndex() { - const index = this.frameIndex(), - animation = this.animation(), - animations = this.animations(), - anim = animations[animation], - len = anim.length / 4; - - if (index < len - 1) { - this.frameIndex(index + 1); - } else { - this.frameIndex(0); - } - } - - frameIndex: GetSet; - animation: GetSet; - image: GetSet; - animations: GetSet; - frameOffsets: GetSet; - frameRate: GetSet; -} - -Sprite.prototype.className = 'Sprite'; -_registerNode(Sprite); - -// add getters setters -Factory.addGetterSetter(Sprite, 'animation'); - -/** - * get/set animation key - * @name Konva.Sprite#animation - * @method - * @param {String} anim animation key - * @returns {String} - * @example - * // get animation key - * var animation = sprite.animation(); - * - * // set animation key - * sprite.animation('kicking'); - */ - -Factory.addGetterSetter(Sprite, 'animations'); - -/** - * get/set animations map - * @name Konva.Sprite#animations - * @method - * @param {Object} animations - * @returns {Object} - * @example - * // get animations map - * var animations = sprite.animations(); - * - * // set animations map - * sprite.animations({ - * standing: [ - * // x, y, width, height (6 frames) - * 0, 0, 49, 109, - * 52, 0, 49, 109, - * 105, 0, 49, 109, - * 158, 0, 49, 109, - * 210, 0, 49, 109, - * 262, 0, 49, 109 - * ], - * kicking: [ - * // x, y, width, height (6 frames) - * 0, 109, 45, 98, - * 45, 109, 45, 98, - * 95, 109, 63, 98, - * 156, 109, 70, 98, - * 229, 109, 60, 98, - * 287, 109, 41, 98 - * ] - * }); - */ - -Factory.addGetterSetter(Sprite, 'frameOffsets'); - -/** - * get/set offsets map - * @name Konva.Sprite#offsets - * @method - * @param {Object} offsets - * @returns {Object} - * @example - * // get offsets map - * var offsets = sprite.offsets(); - * - * // set offsets map - * sprite.offsets({ - * standing: [ - * // x, y (6 frames) - * 0, 0, - * 0, 0, - * 5, 0, - * 0, 0, - * 0, 3, - * 2, 0 - * ], - * kicking: [ - * // x, y (6 frames) - * 0, 5, - * 5, 0, - * 10, 0, - * 0, 0, - * 2, 1, - * 0, 0 - * ] - * }); - */ - -Factory.addGetterSetter(Sprite, 'image'); - -/** - * get/set image - * @name Konva.Sprite#image - * @method - * @param {Image} image - * @returns {Image} - * @example - * // get image - * var image = sprite.image(); - * - * // set image - * sprite.image(imageObj); - */ - -Factory.addGetterSetter(Sprite, 'frameIndex', 0, getNumberValidator()); - -/** - * set/set animation frame index - * @name Konva.Sprite#frameIndex - * @method - * @param {Integer} frameIndex - * @returns {Integer} - * @example - * // get animation frame index - * var frameIndex = sprite.frameIndex(); - * - * // set animation frame index - * sprite.frameIndex(3); - */ - -Factory.addGetterSetter(Sprite, 'frameRate', 17, getNumberValidator()); - -/** - * get/set frame rate in frames per second. Increase this number to make the sprite - * animation run faster, and decrease the number to make the sprite animation run slower - * The default is 17 frames per second - * @name Konva.Sprite#frameRate - * @method - * @param {Integer} frameRate - * @returns {Integer} - * @example - * // get frame rate - * var frameRate = sprite.frameRate(); - * - * // set frame rate to 2 frames per second - * sprite.frameRate(2); - */ - -Factory.backCompat(Sprite, { - index: 'frameIndex', - getIndex: 'getFrameIndex', - setIndex: 'setFrameIndex', -}); diff --git a/src/shapes/Star.ts b/src/shapes/Star.ts deleted file mode 100644 index 8dee6d542..000000000 --- a/src/shapes/Star.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { Factory } from '../Factory'; -import { Context } from '../Context'; -import { Shape, ShapeConfig } from '../Shape'; -import { getNumberValidator } from '../Validators'; -import { _registerNode } from '../Global'; - -import { GetSet } from '../types'; - -export interface StarConfig extends ShapeConfig { - numPoints: number; - innerRadius: number; - outerRadius: number; -} - -/** - * Star constructor - * @constructor - * @memberof Konva - * @augments Konva.Shape - * @param {Object} config - * @param {Integer} config.numPoints - * @param {Number} config.innerRadius - * @param {Number} config.outerRadius - * @@shapeParams - * @@nodeParams - * @example - * var star = new Konva.Star({ - * x: 100, - * y: 200, - * numPoints: 5, - * innerRadius: 70, - * outerRadius: 70, - * fill: 'red', - * stroke: 'black', - * strokeWidth: 4 - * }); - */ -export class Star extends Shape { - _sceneFunc(context: Context) { - const innerRadius = this.innerRadius(), - outerRadius = this.outerRadius(), - numPoints = this.numPoints(); - - context.beginPath(); - context.moveTo(0, 0 - outerRadius); - - for (let n = 1; n < numPoints * 2; n++) { - const radius = n % 2 === 0 ? outerRadius : innerRadius; - const x = radius * Math.sin((n * Math.PI) / numPoints); - const y = -1 * radius * Math.cos((n * Math.PI) / numPoints); - context.lineTo(x, y); - } - context.closePath(); - - context.fillStrokeShape(this); - } - getWidth() { - return this.outerRadius() * 2; - } - getHeight() { - return this.outerRadius() * 2; - } - setWidth(width: number) { - this.outerRadius(width / 2); - } - setHeight(height: number) { - this.outerRadius(height / 2); - } - - outerRadius: GetSet; - innerRadius: GetSet; - numPoints: GetSet; -} - -Star.prototype.className = 'Star'; -Star.prototype._centroid = true; -Star.prototype._attrsAffectingSize = ['innerRadius', 'outerRadius']; -_registerNode(Star); - -/** - * get/set number of points - * @name Konva.Star#numPoints - * @method - * @param {Number} numPoints - * @returns {Number} - * @example - * // get inner radius - * var numPoints = star.numPoints(); - * - * // set inner radius - * star.numPoints(20); - */ -Factory.addGetterSetter(Star, 'numPoints', 5, getNumberValidator()); - -/** - * get/set innerRadius - * @name Konva.Star#innerRadius - * @method - * @param {Number} innerRadius - * @returns {Number} - * @example - * // get inner radius - * var innerRadius = star.innerRadius(); - * - * // set inner radius - * star.innerRadius(20); - */ -Factory.addGetterSetter(Star, 'innerRadius', 0, getNumberValidator()); - -/** - * get/set outerRadius - * @name Konva.Star#outerRadius - * @method - * @param {Number} outerRadius - * @returns {Number} - * @example - * // get inner radius - * var outerRadius = star.outerRadius(); - * - * // set inner radius - * star.outerRadius(20); - */ - -Factory.addGetterSetter(Star, 'outerRadius', 0, getNumberValidator()); diff --git a/src/shapes/Text.ts b/src/shapes/Text.ts deleted file mode 100644 index dbd3b9485..000000000 --- a/src/shapes/Text.ts +++ /dev/null @@ -1,977 +0,0 @@ -import { Util } from '../Util'; -import { Context } from '../Context'; -import { Factory } from '../Factory'; -import { Shape, ShapeConfig } from '../Shape'; -import { Konva } from '../Global'; -import { - getNumberValidator, - getStringValidator, - getNumberOrAutoValidator, - getBooleanValidator, -} from '../Validators'; -import { _registerNode } from '../Global'; - -import { GetSet } from '../types'; - -export function stringToArray(string: string): string[] { - // Use Unicode-aware splitting - return [...string].reduce((acc, char, index, array) => { - // Handle emoji sequences (including ZWJ sequences) - if ( - /\p{Emoji_Modifier_Base}\p{Emoji_Modifier}?(?:\u200D\p{Emoji_Presentation})+/u.test( - char - ) - ) { - acc.push(char); - } - // Handle regional indicator symbols (flags) - else if ( - /\p{Regional_Indicator}{2}/u.test(char + (array[index + 1] || '')) - ) { - acc.push(char + array[index + 1]); - } - // Handle Indic scripts and other combining characters - else if (index > 0 && /\p{Mn}|\p{Me}|\p{Mc}/u.test(char)) { - acc[acc.length - 1] += char; - } - // Handle other characters - else { - acc.push(char); - } - return acc; - }, [] as string[]); -} - -export interface TextConfig extends ShapeConfig { - direction?: string; - text?: string; - fontFamily?: string; - fontSize?: number; - fontStyle?: string; - fontVariant?: string; - textDecoration?: string; - align?: string; - verticalAlign?: string; - padding?: number; - lineHeight?: number; - letterSpacing?: number; - wrap?: string; - ellipsis?: boolean; -} - -// constants -const AUTO = 'auto', - //CANVAS = 'canvas', - CENTER = 'center', - INHERIT = 'inherit', - JUSTIFY = 'justify', - CHANGE_KONVA = 'Change.konva', - CONTEXT_2D = '2d', - DASH = '-', - LEFT = 'left', - TEXT = 'text', - TEXT_UPPER = 'Text', - TOP = 'top', - BOTTOM = 'bottom', - MIDDLE = 'middle', - NORMAL = 'normal', - PX_SPACE = 'px ', - SPACE = ' ', - RIGHT = 'right', - RTL = 'rtl', - WORD = 'word', - CHAR = 'char', - NONE = 'none', - ELLIPSIS = '…', - ATTR_CHANGE_LIST = [ - 'direction', - 'fontFamily', - 'fontSize', - 'fontStyle', - 'fontVariant', - 'padding', - 'align', - 'verticalAlign', - 'lineHeight', - 'text', - 'width', - 'height', - 'wrap', - 'ellipsis', - 'letterSpacing', - ], - // cached variables - attrChangeListLen = ATTR_CHANGE_LIST.length; - -function normalizeFontFamily(fontFamily: string) { - return fontFamily - .split(',') - .map((family) => { - family = family.trim(); - const hasSpace = family.indexOf(' ') >= 0; - const hasQuotes = family.indexOf('"') >= 0 || family.indexOf("'") >= 0; - if (hasSpace && !hasQuotes) { - family = `"${family}"`; - } - return family; - }) - .join(', '); -} - -let dummyContext: CanvasRenderingContext2D; -function getDummyContext() { - if (dummyContext) { - return dummyContext; - } - dummyContext = Util.createCanvasElement().getContext( - CONTEXT_2D - ) as CanvasRenderingContext2D; - return dummyContext; -} - -function _fillFunc(this: Text, context: Context) { - context.fillText(this._partialText, this._partialTextX, this._partialTextY); -} -function _strokeFunc(this: Text, context: Context) { - context.setAttr('miterLimit', 2); - context.strokeText(this._partialText, this._partialTextX, this._partialTextY); -} - -function checkDefaultFill(config?: TextConfig) { - config = config || {}; - - // set default color to black - if ( - !config.fillLinearGradientColorStops && - !config.fillRadialGradientColorStops && - !config.fillPatternImage - ) { - config.fill = config.fill || 'black'; - } - return config; -} - -/** - * Text constructor - * @constructor - * @memberof Konva - * @augments Konva.Shape - * @param {Object} config - * @param {String} [config.direction] default is inherit - * @param {String} [config.fontFamily] default is Arial - * @param {Number} [config.fontSize] in pixels. Default is 12 - * @param {String} [config.fontStyle] can be 'normal', 'italic', or 'bold', '500' or even 'italic bold'. 'normal' is the default. - * @param {String} [config.fontVariant] can be normal or small-caps. Default is normal - * @param {String} [config.textDecoration] can be line-through, underline or empty string. Default is empty string. - * @param {String} config.text - * @param {String} [config.align] can be left, center, or right - * @param {String} [config.verticalAlign] can be top, middle or bottom - * @param {Number} [config.padding] - * @param {Number} [config.lineHeight] default is 1 - * @param {String} [config.wrap] can be "word", "char", or "none". Default is word - * @param {Boolean} [config.ellipsis] can be true or false. Default is false. if Konva.Text config is set to wrap="none" and ellipsis=true, then it will add "..." to the end - * @@shapeParams - * @@nodeParams - * @example - * var text = new Konva.Text({ - * x: 10, - * y: 15, - * text: 'Simple Text', - * fontSize: 30, - * fontFamily: 'Calibri', - * fill: 'green' - * }); - */ -export class Text extends Shape { - textArr: Array<{ text: string; width: number; lastInParagraph: boolean }>; - _partialText: string; - _partialTextX = 0; - _partialTextY = 0; - - textWidth: number; - textHeight: number; - constructor(config?: TextConfig) { - super(checkDefaultFill(config)); - // update text data for certain attr changes - for (let n = 0; n < attrChangeListLen; n++) { - this.on(ATTR_CHANGE_LIST[n] + CHANGE_KONVA, this._setTextData); - } - this._setTextData(); - } - - _sceneFunc(context: Context) { - const textArr = this.textArr, - textArrLen = textArr.length; - - if (!this.text()) { - return; - } - - let padding = this.padding(), - fontSize = this.fontSize(), - lineHeightPx = this.lineHeight() * fontSize, - verticalAlign = this.verticalAlign(), - direction = this.direction(), - alignY = 0, - align = this.align(), - totalWidth = this.getWidth(), - letterSpacing = this.letterSpacing(), - fill = this.fill(), - textDecoration = this.textDecoration(), - shouldUnderline = textDecoration.indexOf('underline') !== -1, - shouldLineThrough = textDecoration.indexOf('line-through') !== -1, - n; - - direction = direction === INHERIT ? context.direction : direction; - - let translateY = lineHeightPx / 2; - let baseline = MIDDLE; - if (Konva._fixTextRendering) { - const metrics = this.measureSize('M'); // Use a sample character to get the ascent - - baseline = 'alphabetic'; - translateY = - (metrics.fontBoundingBoxAscent - metrics.fontBoundingBoxDescent) / 2 + - lineHeightPx / 2; - } - - var lineTranslateX = 0; - var lineTranslateY = 0; - - if (direction === RTL) { - context.setAttr('direction', direction); - } - - context.setAttr('font', this._getContextFont()); - - context.setAttr('textBaseline', baseline); - - context.setAttr('textAlign', LEFT); - - // handle vertical alignment - if (verticalAlign === MIDDLE) { - alignY = (this.getHeight() - textArrLen * lineHeightPx - padding * 2) / 2; - } else if (verticalAlign === BOTTOM) { - alignY = this.getHeight() - textArrLen * lineHeightPx - padding * 2; - } - - context.translate(padding, alignY + padding); - - // draw text lines - for (n = 0; n < textArrLen; n++) { - var lineTranslateX = 0; - var lineTranslateY = 0; - var obj = textArr[n], - text = obj.text, - width = obj.width, - lastLine = obj.lastInParagraph, - spacesNumber, - oneWord, - lineWidth; - - // horizontal alignment - context.save(); - if (align === RIGHT) { - lineTranslateX += totalWidth - width - padding * 2; - } else if (align === CENTER) { - lineTranslateX += (totalWidth - width - padding * 2) / 2; - } - - if (shouldUnderline) { - context.save(); - context.beginPath(); - - const yOffset = Konva._fixTextRendering - ? Math.round(fontSize / 4) - : Math.round(fontSize / 2); - const x = lineTranslateX; - const y = translateY + lineTranslateY + yOffset; - context.moveTo(x, y); - spacesNumber = text.split(' ').length - 1; - oneWord = spacesNumber === 0; - lineWidth = - align === JUSTIFY && !lastLine ? totalWidth - padding * 2 : width; - context.lineTo(x + Math.round(lineWidth), y); - - // I have no idea what is real ratio - // just /15 looks good enough - context.lineWidth = fontSize / 15; - - const gradient = this._getLinearGradient(); - context.strokeStyle = gradient || fill; - context.stroke(); - context.restore(); - } - if (shouldLineThrough) { - context.save(); - context.beginPath(); - const yOffset = Konva._fixTextRendering ? -Math.round(fontSize / 4) : 0; - context.moveTo(lineTranslateX, translateY + lineTranslateY + yOffset); - spacesNumber = text.split(' ').length - 1; - oneWord = spacesNumber === 0; - lineWidth = - align === JUSTIFY && !lastLine ? totalWidth - padding * 2 : width; - context.lineTo( - lineTranslateX + Math.round(lineWidth), - translateY + lineTranslateY + yOffset - ); - context.lineWidth = fontSize / 15; - const gradient = this._getLinearGradient(); - context.strokeStyle = gradient || fill; - context.stroke(); - context.restore(); - } - // As `letterSpacing` isn't supported on Safari, we use this polyfill. - // The exception is for RTL text, which we rely on native as it cannot - // be supported otherwise. - if (direction !== RTL && (letterSpacing !== 0 || align === JUSTIFY)) { - // var words = text.split(' '); - spacesNumber = text.split(' ').length - 1; - const array = stringToArray(text); - for (let li = 0; li < array.length; li++) { - const letter = array[li]; - // skip justify for the last line - if (letter === ' ' && !lastLine && align === JUSTIFY) { - lineTranslateX += (totalWidth - padding * 2 - width) / spacesNumber; - // context.translate( - // Math.floor((totalWidth - padding * 2 - width) / spacesNumber), - // 0 - // ); - } - this._partialTextX = lineTranslateX; - this._partialTextY = translateY + lineTranslateY; - this._partialText = letter; - context.fillStrokeShape(this); - lineTranslateX += this.measureSize(letter).width + letterSpacing; - } - } else { - if (letterSpacing !== 0) { - context.setAttr('letterSpacing', `${letterSpacing}px`); - } - this._partialTextX = lineTranslateX; - this._partialTextY = translateY + lineTranslateY; - this._partialText = text; - - context.fillStrokeShape(this); - } - context.restore(); - if (textArrLen > 1) { - translateY += lineHeightPx; - } - } - } - _hitFunc(context: Context) { - const width = this.getWidth(), - height = this.getHeight(); - - context.beginPath(); - context.rect(0, 0, width, height); - context.closePath(); - context.fillStrokeShape(this); - } - setText(text: string) { - const str = Util._isString(text) - ? text - : text === null || text === undefined - ? '' - : text + ''; - this._setAttr(TEXT, str); - return this; - } - getWidth() { - const isAuto = this.attrs.width === AUTO || this.attrs.width === undefined; - return isAuto ? this.getTextWidth() + this.padding() * 2 : this.attrs.width; - } - getHeight() { - const isAuto = this.attrs.height === AUTO || this.attrs.height === undefined; - return isAuto - ? this.fontSize() * this.textArr.length * this.lineHeight() + - this.padding() * 2 - : this.attrs.height; - } - /** - * get pure text width without padding - * @method - * @name Konva.Text#getTextWidth - * @returns {Number} - */ - getTextWidth() { - return this.textWidth; - } - getTextHeight() { - Util.warn( - 'text.getTextHeight() method is deprecated. Use text.height() - for full height and text.fontSize() - for one line height.' - ); - return this.textHeight; - } - - /** - * measure string with the font of current text shape. - * That method can't handle multiline text. - * @method - * @name Konva.Text#measureSize - * @param {String} text text to measure - * @returns {Object} { width , height } of measured text - */ - measureSize(text: string) { - let _context = getDummyContext(), - fontSize = this.fontSize(), - metrics: TextMetrics; - - _context.save(); - _context.font = this._getContextFont(); - - metrics = _context.measureText(text); - _context.restore(); - - // Scale the fallback values based on the provided fontSize compared to the sample size (100 in your new case) - const scaleFactor = fontSize / 100; - - // Note, fallback values are from chrome browser with 100px font size and font-family "Arial" - return { - actualBoundingBoxAscent: - metrics.actualBoundingBoxAscent ?? 71.58203125 * scaleFactor, - actualBoundingBoxDescent: metrics.actualBoundingBoxDescent ?? 0, // Remains zero as there is no descent in the provided metrics - actualBoundingBoxLeft: - metrics.actualBoundingBoxLeft ?? -7.421875 * scaleFactor, - actualBoundingBoxRight: - metrics.actualBoundingBoxRight ?? 75.732421875 * scaleFactor, - alphabeticBaseline: metrics.alphabeticBaseline ?? 0, // Remains zero as it's typically relative to the baseline itself - emHeightAscent: metrics.emHeightAscent ?? 100 * scaleFactor, - emHeightDescent: metrics.emHeightDescent ?? -20 * scaleFactor, - fontBoundingBoxAscent: metrics.fontBoundingBoxAscent ?? 91 * scaleFactor, - fontBoundingBoxDescent: - metrics.fontBoundingBoxDescent ?? 21 * scaleFactor, - hangingBaseline: - metrics.hangingBaseline ?? 72.80000305175781 * scaleFactor, - ideographicBaseline: metrics.ideographicBaseline ?? -21 * scaleFactor, - width: metrics.width, - height: fontSize, // Typically set to the font size - }; - } - _getContextFont() { - return ( - this.fontStyle() + - SPACE + - this.fontVariant() + - SPACE + - (this.fontSize() + PX_SPACE) + - // wrap font family into " so font families with spaces works ok - normalizeFontFamily(this.fontFamily()) - ); - } - _addTextLine(line: string) { - const align = this.align(); - if (align === JUSTIFY) { - line = line.trim(); - } - const width = this._getTextWidth(line); - return this.textArr.push({ - text: line, - width: width, - lastInParagraph: false, - }); - } - _getTextWidth(text: string) { - const letterSpacing = this.letterSpacing(); - const length = text.length; - return ( - getDummyContext().measureText(text).width + - (length ? letterSpacing * (length - 1) : 0) - ); - } - _setTextData() { - let lines = this.text().split('\n'), - fontSize = +this.fontSize(), - textWidth = 0, - lineHeightPx = this.lineHeight() * fontSize, - width = this.attrs.width, - height = this.attrs.height, - fixedWidth = width !== AUTO && width !== undefined, - fixedHeight = height !== AUTO && height !== undefined, - padding = this.padding(), - maxWidth = width - padding * 2, - maxHeightPx = height - padding * 2, - currentHeightPx = 0, - wrap = this.wrap(), - // align = this.align(), - shouldWrap = wrap !== NONE, - wrapAtWord = wrap !== CHAR && shouldWrap, - shouldAddEllipsis = this.ellipsis(); - - this.textArr = []; - getDummyContext().font = this._getContextFont(); - const additionalWidth = shouldAddEllipsis ? this._getTextWidth(ELLIPSIS) : 0; - for (let i = 0, max = lines.length; i < max; ++i) { - let line = lines[i]; - - let lineWidth = this._getTextWidth(line); - if (fixedWidth && lineWidth > maxWidth) { - /* - * if width is fixed and line does not fit entirely - * break the line into multiple fitting lines - */ - while (line.length > 0) { - /* - * use binary search to find the longest substring that - * that would fit in the specified width - */ - let low = 0, - high = line.length, - match = '', - matchWidth = 0; - while (low < high) { - const mid = (low + high) >>> 1, - substr = line.slice(0, mid + 1), - substrWidth = this._getTextWidth(substr) + additionalWidth; - if (substrWidth <= maxWidth) { - low = mid + 1; - match = substr; - matchWidth = substrWidth; - } else { - high = mid; - } - } - /* - * 'low' is now the index of the substring end - * 'match' is the substring - * 'matchWidth' is the substring width in px - */ - if (match) { - // a fitting substring was found - if (wrapAtWord) { - // try to find a space or dash where wrapping could be done - var wrapIndex; - const nextChar = line[match.length]; - const nextIsSpaceOrDash = nextChar === SPACE || nextChar === DASH; - if (nextIsSpaceOrDash && matchWidth <= maxWidth) { - wrapIndex = match.length; - } else { - wrapIndex = - Math.max(match.lastIndexOf(SPACE), match.lastIndexOf(DASH)) + - 1; - } - if (wrapIndex > 0) { - // re-cut the substring found at the space/dash position - low = wrapIndex; - match = match.slice(0, low); - matchWidth = this._getTextWidth(match); - } - } - // if (align === 'right') { - match = match.trimRight(); - // } - this._addTextLine(match); - textWidth = Math.max(textWidth, matchWidth); - currentHeightPx += lineHeightPx; - - const shouldHandleEllipsis = - this._shouldHandleEllipsis(currentHeightPx); - if (shouldHandleEllipsis) { - this._tryToAddEllipsisToLastLine(); - /* - * stop wrapping if wrapping is disabled or if adding - * one more line would overflow the fixed height - */ - break; - } - line = line.slice(low); - line = line.trimLeft(); - if (line.length > 0) { - // Check if the remaining text would fit on one line - lineWidth = this._getTextWidth(line); - if (lineWidth <= maxWidth) { - // if it does, add the line and break out of the loop - this._addTextLine(line); - currentHeightPx += lineHeightPx; - textWidth = Math.max(textWidth, lineWidth); - break; - } - } - } else { - // not even one character could fit in the element, abort - break; - } - } - } else { - // element width is automatically adjusted to max line width - this._addTextLine(line); - currentHeightPx += lineHeightPx; - textWidth = Math.max(textWidth, lineWidth); - if (this._shouldHandleEllipsis(currentHeightPx) && i < max - 1) { - this._tryToAddEllipsisToLastLine(); - } - } - // if element height is fixed, abort if adding one more line would overflow - if (this.textArr[this.textArr.length - 1]) { - this.textArr[this.textArr.length - 1].lastInParagraph = true; - } - if (fixedHeight && currentHeightPx + lineHeightPx > maxHeightPx) { - break; - } - } - this.textHeight = fontSize; - // var maxTextWidth = 0; - // for(var j = 0; j < this.textArr.length; j++) { - // maxTextWidth = Math.max(maxTextWidth, this.textArr[j].width); - // } - this.textWidth = textWidth; - } - - /** - * whether to handle ellipsis, there are two cases: - * 1. the current line is the last line - * 2. wrap is NONE - * @param {Number} currentHeightPx - * @returns - */ - _shouldHandleEllipsis(currentHeightPx: number): boolean { - const fontSize = +this.fontSize(), - lineHeightPx = this.lineHeight() * fontSize, - height = this.attrs.height, - fixedHeight = height !== AUTO && height !== undefined, - padding = this.padding(), - maxHeightPx = height - padding * 2, - wrap = this.wrap(), - shouldWrap = wrap !== NONE; - - return ( - !shouldWrap || - (fixedHeight && currentHeightPx + lineHeightPx > maxHeightPx) - ); - } - - _tryToAddEllipsisToLastLine(): void { - const width = this.attrs.width, - fixedWidth = width !== AUTO && width !== undefined, - padding = this.padding(), - maxWidth = width - padding * 2, - shouldAddEllipsis = this.ellipsis(); - - const lastLine = this.textArr[this.textArr.length - 1]; - if (!lastLine || !shouldAddEllipsis) { - return; - } - - if (fixedWidth) { - const haveSpace = this._getTextWidth(lastLine.text + ELLIPSIS) < maxWidth; - if (!haveSpace) { - lastLine.text = lastLine.text.slice(0, lastLine.text.length - 3); - } - } - - this.textArr.splice(this.textArr.length - 1, 1); - this._addTextLine(lastLine.text + ELLIPSIS); - } - - // for text we can't disable stroke scaling - // if we do, the result will be unexpected - getStrokeScaleEnabled() { - return true; - } - - _useBufferCanvas() { - const hasLine = - this.textDecoration().indexOf('underline') !== -1 || - this.textDecoration().indexOf('line-through') !== -1; - const hasShadow = this.hasShadow(); - if (hasLine && hasShadow) { - return true; - } - return super._useBufferCanvas(); - } - - direction: GetSet; - fontFamily: GetSet; - fontSize: GetSet; - fontStyle: GetSet; - fontVariant: GetSet; - align: GetSet; - letterSpacing: GetSet; - verticalAlign: GetSet; - padding: GetSet; - lineHeight: GetSet; - textDecoration: GetSet; - text: GetSet; - wrap: GetSet; - ellipsis: GetSet; -} - -Text.prototype._fillFunc = _fillFunc; -Text.prototype._strokeFunc = _strokeFunc; -Text.prototype.className = TEXT_UPPER; -Text.prototype._attrsAffectingSize = [ - 'text', - 'fontSize', - 'padding', - 'wrap', - 'lineHeight', - 'letterSpacing', -]; -_registerNode(Text); - -/** - * get/set width of text area, which includes padding. - * @name Konva.Text#width - * @method - * @param {Number} width - * @returns {Number} - * @example - * // get width - * var width = text.width(); - * - * // set width - * text.width(20); - * - * // set to auto - * text.width('auto'); - * text.width() // will return calculated width, and not "auto" - */ -Factory.overWriteSetter(Text, 'width', getNumberOrAutoValidator()); - -/** - * get/set the height of the text area, which takes into account multi-line text, line heights, and padding. - * @name Konva.Text#height - * @method - * @param {Number} height - * @returns {Number} - * @example - * // get height - * var height = text.height(); - * - * // set height - * text.height(20); - * - * // set to auto - * text.height('auto'); - * text.height() // will return calculated height, and not "auto" - */ - -Factory.overWriteSetter(Text, 'height', getNumberOrAutoValidator()); - -/** - * get/set direction - * @name Konva.Text#direction - * @method - * @param {String} direction - * @returns {String} - * @example - * // get direction - * var direction = text.direction(); - * - * // set direction - * text.direction('rtl'); - */ -Factory.addGetterSetter(Text, 'direction', INHERIT); - -/** - * get/set font family - * @name Konva.Text#fontFamily - * @method - * @param {String} fontFamily - * @returns {String} - * @example - * // get font family - * var fontFamily = text.fontFamily(); - * - * // set font family - * text.fontFamily('Arial'); - */ -Factory.addGetterSetter(Text, 'fontFamily', 'Arial'); - -/** - * get/set font size in pixels - * @name Konva.Text#fontSize - * @method - * @param {Number} fontSize - * @returns {Number} - * @example - * // get font size - * var fontSize = text.fontSize(); - * - * // set font size to 22px - * text.fontSize(22); - */ -Factory.addGetterSetter(Text, 'fontSize', 12, getNumberValidator()); - -/** - * get/set font style. Can be 'normal', 'italic', or 'bold', '500' or even 'italic bold'. 'normal' is the default. - * @name Konva.Text#fontStyle - * @method - * @param {String} fontStyle - * @returns {String} - * @example - * // get font style - * var fontStyle = text.fontStyle(); - * - * // set font style - * text.fontStyle('bold'); - */ - -Factory.addGetterSetter(Text, 'fontStyle', NORMAL); - -/** - * get/set font variant. Can be 'normal' or 'small-caps'. 'normal' is the default. - * @name Konva.Text#fontVariant - * @method - * @param {String} fontVariant - * @returns {String} - * @example - * // get font variant - * var fontVariant = text.fontVariant(); - * - * // set font variant - * text.fontVariant('small-caps'); - */ - -Factory.addGetterSetter(Text, 'fontVariant', NORMAL); - -/** - * get/set padding - * @name Konva.Text#padding - * @method - * @param {Number} padding - * @returns {Number} - * @example - * // get padding - * var padding = text.padding(); - * - * // set padding to 10 pixels - * text.padding(10); - */ - -Factory.addGetterSetter(Text, 'padding', 0, getNumberValidator()); - -/** - * get/set horizontal align of text. Can be 'left', 'center', 'right' or 'justify' - * @name Konva.Text#align - * @method - * @param {String} align - * @returns {String} - * @example - * // get text align - * var align = text.align(); - * - * // center text - * text.align('center'); - * - * // align text to right - * text.align('right'); - */ - -Factory.addGetterSetter(Text, 'align', LEFT); - -/** - * get/set vertical align of text. Can be 'top', 'middle', 'bottom'. - * @name Konva.Text#verticalAlign - * @method - * @param {String} verticalAlign - * @returns {String} - * @example - * // get text vertical align - * var verticalAlign = text.verticalAlign(); - * - * // center text - * text.verticalAlign('middle'); - */ - -Factory.addGetterSetter(Text, 'verticalAlign', TOP); - -/** - * get/set line height. The default is 1. - * @name Konva.Text#lineHeight - * @method - * @param {Number} lineHeight - * @returns {Number} - * @example - * // get line height - * var lineHeight = text.lineHeight(); - * - * // set the line height - * text.lineHeight(2); - */ - -Factory.addGetterSetter(Text, 'lineHeight', 1, getNumberValidator()); - -/** - * get/set wrap. Can be "word", "char", or "none". Default is "word". - * In "word" wrapping any word still can be wrapped if it can't be placed in the required width - * without breaks. - * @name Konva.Text#wrap - * @method - * @param {String} wrap - * @returns {String} - * @example - * // get wrap - * var wrap = text.wrap(); - * - * // set wrap - * text.wrap('word'); - */ - -Factory.addGetterSetter(Text, 'wrap', WORD); - -/** - * get/set ellipsis. Can be true or false. Default is false. If ellipses is true, - * Konva will add "..." at the end of the text if it doesn't have enough space to write characters. - * That is possible only when you limit both width and height of the text - * @name Konva.Text#ellipsis - * @method - * @param {Boolean} ellipsis - * @returns {Boolean} - * @example - * // get ellipsis param, returns true or false - * var ellipsis = text.ellipsis(); - * - * // set ellipsis - * text.ellipsis(true); - */ - -Factory.addGetterSetter(Text, 'ellipsis', false, getBooleanValidator()); - -/** - * set letter spacing property. Default value is 0. - * @name Konva.Text#letterSpacing - * @method - * @param {Number} letterSpacing - */ - -Factory.addGetterSetter(Text, 'letterSpacing', 0, getNumberValidator()); - -/** - * get/set text - * @name Konva.Text#text - * @method - * @param {String} text - * @returns {String} - * @example - * // get text - * var text = text.text(); - * - * // set text - * text.text('Hello world!'); - */ - -Factory.addGetterSetter(Text, 'text', '', getStringValidator()); - -/** - * get/set text decoration of a text. Possible values are 'underline', 'line-through' or combination of these values separated by space - * @name Konva.Text#textDecoration - * @method - * @param {String} textDecoration - * @returns {String} - * @example - * // get text decoration - * var textDecoration = text.textDecoration(); - * - * // underline text - * text.textDecoration('underline'); - * - * // strike text - * text.textDecoration('line-through'); - * - * // underline and strike text - * text.textDecoration('underline line-through'); - */ - -Factory.addGetterSetter(Text, 'textDecoration', ''); diff --git a/src/shapes/TextPath.ts b/src/shapes/TextPath.ts deleted file mode 100644 index 1ae774627..000000000 --- a/src/shapes/TextPath.ts +++ /dev/null @@ -1,571 +0,0 @@ -import { Util } from '../Util'; -import { Factory } from '../Factory'; -import { Context } from '../Context'; -import { Shape, ShapeConfig } from '../Shape'; -import { Path } from './Path'; -import { Text, stringToArray } from './Text'; -import { getNumberValidator } from '../Validators'; -import { _registerNode } from '../Global'; - -import { GetSet, PathSegment, Vector2d } from '../types'; - -export interface TextPathConfig extends ShapeConfig { - text?: string; - data?: string; - fontFamily?: string; - fontSize?: number; - fontStyle?: string; - letterSpacing?: number; -} - -const EMPTY_STRING = '', - NORMAL = 'normal'; - -function _fillFunc(this: TextPath, context) { - context.fillText(this.partialText, 0, 0); -} -function _strokeFunc(this: TextPath, context) { - context.strokeText(this.partialText, 0, 0); -} - -/** - * Path constructor. - * @author Jason Follas - * @constructor - * @memberof Konva - * @augments Konva.Shape - * @param {Object} config - * @param {String} [config.fontFamily] default is Arial - * @param {Number} [config.fontSize] default is 12 - * @param {String} [config.fontStyle] Can be 'normal', 'italic', or 'bold', '500' or even 'italic bold'. 'normal' is the default. - * @param {String} [config.fontVariant] can be normal or small-caps. Default is normal - * @param {String} [config.textBaseline] Can be 'top', 'bottom', 'middle', 'alphabetic', 'hanging'. Default is middle - * @param {String} config.text - * @param {String} config.data SVG data string - * @param {Function} config.kerningFunc a getter for kerning values for the specified characters - * @@shapeParams - * @@nodeParams - * @example - * var kerningPairs = { - * 'A': { - * ' ': -0.05517578125, - * 'T': -0.07421875, - * 'V': -0.07421875 - * } - * 'V': { - * ',': -0.091796875, - * ":": -0.037109375, - * ";": -0.037109375, - * "A": -0.07421875 - * } - * } - * var textpath = new Konva.TextPath({ - * x: 100, - * y: 50, - * fill: '#333', - * fontSize: '24', - * fontFamily: 'Arial', - * text: 'All the world\'s a stage, and all the men and women merely players.', - * data: 'M10,10 C0,0 10,150 100,100 S300,150 400,50', - * kerningFunc(leftChar, rightChar) { - * return kerningPairs.hasOwnProperty(leftChar) ? pairs[leftChar][rightChar] || 0 : 0 - * } - * }); - */ -export class TextPath extends Shape { - dummyCanvas = Util.createCanvasElement(); - dataArray: PathSegment[] = []; - glyphInfo: Array<{ - transposeX: number; - transposeY: number; - text: string; - rotation: number; - p0: Vector2d; - p1: Vector2d; - }>; - partialText: string; - pathLength: number; - textWidth: number; - textHeight: number; - - constructor(config?: TextPathConfig) { - // call super constructor - super(config); - - this._readDataAttribute(); - - this.on('dataChange.konva', function () { - this._readDataAttribute(); - this._setTextData(); - }); - - // update text data for certain attr changes - this.on( - 'textChange.konva alignChange.konva letterSpacingChange.konva kerningFuncChange.konva fontSizeChange.konva fontFamilyChange.konva', - this._setTextData - ); - - this._setTextData(); - } - - _getTextPathLength() { - return Path.getPathLength(this.dataArray); - } - _getPointAtLength(length: number) { - // if path is not defined yet, do nothing - if (!this.attrs.data) { - return null; - } - - const totalLength = this.pathLength; - // -1px for rounding of the last symbol - if (length - 1 > totalLength) { - return null; - } - - return Path.getPointAtLengthOfDataArray(length, this.dataArray); - } - - _readDataAttribute() { - this.dataArray = Path.parsePathData(this.attrs.data); - this.pathLength = this._getTextPathLength(); - } - - _sceneFunc(context: Context) { - context.setAttr('font', this._getContextFont()); - context.setAttr('textBaseline', this.textBaseline()); - context.setAttr('textAlign', 'left'); - context.save(); - - const textDecoration = this.textDecoration(); - const fill = this.fill(); - const fontSize = this.fontSize(); - - const glyphInfo = this.glyphInfo; - if (textDecoration === 'underline') { - context.beginPath(); - } - for (let i = 0; i < glyphInfo.length; i++) { - context.save(); - - const p0 = glyphInfo[i].p0; - - context.translate(p0.x, p0.y); - context.rotate(glyphInfo[i].rotation); - this.partialText = glyphInfo[i].text; - - context.fillStrokeShape(this); - if (textDecoration === 'underline') { - if (i === 0) { - context.moveTo(0, fontSize / 2 + 1); - } - - context.lineTo(fontSize, fontSize / 2 + 1); - } - context.restore(); - - //// To assist with debugging visually, uncomment following - // - // if (i % 2) context.strokeStyle = 'cyan'; - // else context.strokeStyle = 'green'; - // var p1 = glyphInfo[i].p1; - // context.moveTo(p0.x, p0.y); - // context.lineTo(p1.x, p1.y); - // context.stroke(); - } - if (textDecoration === 'underline') { - context.strokeStyle = fill; - context.lineWidth = fontSize / 20; - context.stroke(); - } - - context.restore(); - } - _hitFunc(context: Context) { - context.beginPath(); - - const glyphInfo = this.glyphInfo; - if (glyphInfo.length >= 1) { - const p0 = glyphInfo[0].p0; - context.moveTo(p0.x, p0.y); - } - for (let i = 0; i < glyphInfo.length; i++) { - const p1 = glyphInfo[i].p1; - context.lineTo(p1.x, p1.y); - } - context.setAttr('lineWidth', this.fontSize()); - context.setAttr('strokeStyle', this.colorKey); - context.stroke(); - } - /** - * get text width in pixels - * @method - * @name Konva.TextPath#getTextWidth - */ - getTextWidth() { - return this.textWidth; - } - getTextHeight() { - Util.warn( - 'text.getTextHeight() method is deprecated. Use text.height() - for full height and text.fontSize() - for one line height.' - ); - return this.textHeight; - } - setText(text: string) { - return Text.prototype.setText.call(this, text); - } - - _getContextFont() { - return Text.prototype._getContextFont.call(this); - } - - _getTextSize(text: string) { - const dummyCanvas = this.dummyCanvas; - const _context = dummyCanvas.getContext('2d')!; - - _context.save(); - - _context.font = this._getContextFont(); - const metrics = _context.measureText(text); - - _context.restore(); - - return { - width: metrics.width, - height: parseInt(`${this.fontSize()}`, 10), - }; - } - _setTextData() { - const { width, height } = this._getTextSize(this.attrs.text); - this.textWidth = width; - this.textHeight = height; - this.glyphInfo = []; - - if (!this.attrs.data) { - return null; - } - - const letterSpacing = this.letterSpacing(); - const align = this.align(); - const kerningFunc = this.kerningFunc(); - - // defines the width of the text on a straight line - const textWidth = Math.max( - this.textWidth + ((this.attrs.text || '').length - 1) * letterSpacing, - 0 - ); - - let offset = 0; - if (align === 'center') { - offset = Math.max(0, this.pathLength / 2 - textWidth / 2); - } - if (align === 'right') { - offset = Math.max(0, this.pathLength - textWidth); - } - - const charArr = stringToArray(this.text()); - - // Algorithm for calculating glyph positions: - // 1. Get the begging point of the glyph on the path using the offsetToGlyph, - // 2. Get the ending point of the glyph on the path using the offsetToGlyph plus glyph width, - // 3. Calculate the rotation, width, and midpoint of the glyph using the start and end points, - // 4. Add glyph width to the offsetToGlyph and repeat - let offsetToGlyph = offset; - for (let i = 0; i < charArr.length; i++) { - const charStartPoint = this._getPointAtLength(offsetToGlyph); - if (!charStartPoint) return; - - let glyphWidth = this._getTextSize(charArr[i]).width + letterSpacing; - if (charArr[i] === ' ' && align === 'justify') { - const numberOfSpaces = this.text().split(' ').length - 1; - glyphWidth += (this.pathLength - textWidth) / numberOfSpaces; - } - - const charEndPoint = this._getPointAtLength(offsetToGlyph + glyphWidth); - if (!charEndPoint) return; - - const width = Path.getLineLength( - charStartPoint.x, - charStartPoint.y, - charEndPoint.x, - charEndPoint.y - ); - - let kern = 0; - if (kerningFunc) { - try { - // getKerning is a user provided getter. Make sure it never breaks our logic - kern = kerningFunc(charArr[i - 1], charArr[i]) * this.fontSize(); - } catch (e) { - kern = 0; - } - } - - charStartPoint.x += kern; - charEndPoint.x += kern; - this.textWidth += kern; - - const midpoint = Path.getPointOnLine( - kern + width / 2.0, - charStartPoint.x, - charStartPoint.y, - charEndPoint.x, - charEndPoint.y - ); - - const rotation = Math.atan2( - charEndPoint.y - charStartPoint.y, - charEndPoint.x - charStartPoint.x - ); - this.glyphInfo.push({ - transposeX: midpoint.x, - transposeY: midpoint.y, - text: charArr[i], - rotation: rotation, - p0: charStartPoint, - p1: charEndPoint, - }); - - offsetToGlyph += glyphWidth; - } - } - getSelfRect() { - if (!this.glyphInfo.length) { - return { - x: 0, - y: 0, - width: 0, - height: 0, - }; - } - const points: number[] = []; - - this.glyphInfo.forEach(function (info) { - points.push(info.p0.x); - points.push(info.p0.y); - points.push(info.p1.x); - points.push(info.p1.y); - }); - let minX = points[0] || 0; - let maxX = points[0] || 0; - let minY = points[1] || 0; - let maxY = points[1] || 0; - let x, y; - for (let i = 0; i < points.length / 2; i++) { - x = points[i * 2]; - y = points[i * 2 + 1]; - minX = Math.min(minX, x); - maxX = Math.max(maxX, x); - minY = Math.min(minY, y); - maxY = Math.max(maxY, y); - } - const fontSize = this.fontSize(); - return { - x: minX - fontSize / 2, - y: minY - fontSize / 2, - width: maxX - minX + fontSize, - height: maxY - minY + fontSize, - }; - } - destroy(): this { - Util.releaseCanvas(this.dummyCanvas); - return super.destroy(); - } - - fontFamily: GetSet; - fontSize: GetSet; - fontStyle: GetSet; - fontVariant: GetSet; - align: GetSet; - letterSpacing: GetSet; - text: GetSet; - data: GetSet; - - kerningFunc: GetSet<(leftChar: string, rightChar: string) => number, this>; - textBaseline: GetSet; - textDecoration: GetSet; -} - -TextPath.prototype._fillFunc = _fillFunc; -TextPath.prototype._strokeFunc = _strokeFunc; -TextPath.prototype._fillFuncHit = _fillFunc; -TextPath.prototype._strokeFuncHit = _strokeFunc; -TextPath.prototype.className = 'TextPath'; -TextPath.prototype._attrsAffectingSize = ['text', 'fontSize', 'data']; -_registerNode(TextPath); - -/** - * get/set SVG path data string. This method - * also automatically parses the data string - * into a data array. Currently supported SVG data: - * M, m, L, l, H, h, V, v, Q, q, T, t, C, c, S, s, A, a, Z, z - * @name Konva.TextPath#data - * @method - * @param {String} data svg path string - * @returns {String} - * @example - * // get data - * var data = shape.data(); - * - * // set data - * shape.data('M200,100h100v50z'); - */ -Factory.addGetterSetter(TextPath, 'data'); - -/** - * get/set font family - * @name Konva.TextPath#fontFamily - * @method - * @param {String} fontFamily - * @returns {String} - * @example - * // get font family - * var fontFamily = shape.fontFamily(); - * - * // set font family - * shape.fontFamily('Arial'); - */ -Factory.addGetterSetter(TextPath, 'fontFamily', 'Arial'); - -/** - * get/set font size in pixels - * @name Konva.TextPath#fontSize - * @method - * @param {Number} fontSize - * @returns {Number} - * @example - * // get font size - * var fontSize = shape.fontSize(); - * - * // set font size to 22px - * shape.fontSize(22); - */ - -Factory.addGetterSetter(TextPath, 'fontSize', 12, getNumberValidator()); - -/** - * get/set font style. Can be 'normal', 'italic', or 'bold', '500' or even 'italic bold'. 'normal' is the default. - * @name Konva.TextPath#fontStyle - * @method - * @param {String} fontStyle - * @returns {String} - * @example - * // get font style - * var fontStyle = shape.fontStyle(); - * - * // set font style - * shape.fontStyle('bold'); - */ - -Factory.addGetterSetter(TextPath, 'fontStyle', NORMAL); - -/** - * get/set horizontal align of text. Can be 'left', 'center', 'right' or 'justify' - * @name Konva.TextPath#align - * @method - * @param {String} align - * @returns {String} - * @example - * // get text align - * var align = text.align(); - * - * // center text - * text.align('center'); - * - * // align text to right - * text.align('right'); - */ -Factory.addGetterSetter(TextPath, 'align', 'left'); - -/** - * get/set letter spacing. The default is 0. - * @name Konva.TextPath#letterSpacing - * @method - * @param {Number} letterSpacing - * @returns {Number} - * @example - * // get letter spacing value - * var letterSpacing = shape.letterSpacing(); - * - * // set the letter spacing value - * shape.letterSpacing(2); - */ - -Factory.addGetterSetter(TextPath, 'letterSpacing', 0, getNumberValidator()); - -/** - * get/set text baseline. The default is 'middle'. Can be 'top', 'bottom', 'middle', 'alphabetic', 'hanging' - * @name Konva.TextPath#textBaseline - * @method - * @param {String} textBaseline - * @returns {String} - * @example - * // get current text baseline - * var textBaseline = shape.textBaseline(); - * - * // set new text baseline - * shape.textBaseline('top'); - */ -Factory.addGetterSetter(TextPath, 'textBaseline', 'middle'); - -/** - * get/set font variant. Can be 'normal' or 'small-caps'. 'normal' is the default. - * @name Konva.TextPath#fontVariant - * @method - * @param {String} fontVariant - * @returns {String} - * @example - * // get font variant - * var fontVariant = shape.fontVariant(); - * - * // set font variant - * shape.fontVariant('small-caps'); - */ -Factory.addGetterSetter(TextPath, 'fontVariant', NORMAL); - -/** - * get/set text - * @name Konva.TextPath#getText - * @method - * @param {String} text - * @returns {String} - * @example - * // get text - * var text = text.text(); - * - * // set text - * text.text('Hello world!'); - */ -Factory.addGetterSetter(TextPath, 'text', EMPTY_STRING); - -/** - * get/set text decoration of a text. Can be '' or 'underline'. - * @name Konva.TextPath#textDecoration - * @method - * @param {String} textDecoration - * @returns {String} - * @example - * // get text decoration - * var textDecoration = shape.textDecoration(); - * - * // underline text - * shape.textDecoration('underline'); - */ -Factory.addGetterSetter(TextPath, 'textDecoration', ''); - -/** - * get/set kerning function. - * @name Konva.TextPath#kerningFunc - * @method - * @param {String} kerningFunc - * @returns {String} - * @example - * // get text decoration - * var kerningFunc = text.kerningFunc(); - * - * // center text - * text.kerningFunc(function(leftChar, rightChar) { - * return 1; - * }); - */ -Factory.addGetterSetter(TextPath, 'kerningFunc', undefined); diff --git a/src/shapes/Transformer.ts b/src/shapes/Transformer.ts deleted file mode 100644 index 3c254a515..000000000 --- a/src/shapes/Transformer.ts +++ /dev/null @@ -1,1874 +0,0 @@ -import { Util, Transform } from '../Util'; -import { Factory } from '../Factory'; -import { Node } from '../Node'; -import { Shape } from '../Shape'; -import { Rect } from './Rect'; -import { Group } from '../Group'; -import { ContainerConfig } from '../Container'; -import { Konva } from '../Global'; -import { getBooleanValidator, getNumberValidator } from '../Validators'; -import { _registerNode } from '../Global'; - -import { GetSet, IRect, Vector2d } from '../types'; - -export interface Box extends IRect { - rotation: number; -} - -export interface TransformerConfig extends ContainerConfig { - resizeEnabled?: boolean; - rotateEnabled?: boolean; - rotateLineVisible?: boolean; - rotationSnaps?: Array; - rotationSnapTolerance?: number; - rotateAnchorOffset?: number; - rotateAnchorCursor?: string; - borderEnabled?: boolean; - borderStroke?: string; - borderStrokeWidth?: number; - borderDash?: Array; - anchorFill?: string; - anchorStroke?: string; - anchorStrokeWidth?: number; - anchorSize?: number; - anchorCornerRadius?: number; - keepRatio?: boolean; - shiftBehavior?: string; - centeredScaling?: boolean; - enabledAnchors?: Array; - flipEnabled?: boolean; - node?: Rect; - ignoreStroke?: boolean; - boundBoxFunc?: (oldBox: Box, newBox: Box) => Box; - useSingleNodeRotation?: boolean; - shouldOverdrawWholeArea?: boolean; - anchorDragBoundFunc?: ( - oldPos: Vector2d, - newPos: Vector2d, - evt: any - ) => Vector2d; - anchorStyleFunc?: (anchor: Rect) => void; -} - -const EVENTS_NAME = 'tr-konva'; - -const ATTR_CHANGE_LIST = [ - 'resizeEnabledChange', - 'rotateAnchorOffsetChange', - 'rotateEnabledChange', - 'enabledAnchorsChange', - 'anchorSizeChange', - 'borderEnabledChange', - 'borderStrokeChange', - 'borderStrokeWidthChange', - 'borderDashChange', - 'anchorStrokeChange', - 'anchorStrokeWidthChange', - 'anchorFillChange', - 'anchorCornerRadiusChange', - 'ignoreStrokeChange', - 'anchorStyleFuncChange', -] - .map((e) => e + `.${EVENTS_NAME}`) - .join(' '); - -const NODES_RECT = 'nodesRect'; - -const TRANSFORM_CHANGE_STR = [ - 'widthChange', - 'heightChange', - 'scaleXChange', - 'scaleYChange', - 'skewXChange', - 'skewYChange', - 'rotationChange', - 'offsetXChange', - 'offsetYChange', - 'transformsEnabledChange', - 'strokeWidthChange', -]; - -const ANGLES = { - 'top-left': -45, - 'top-center': 0, - 'top-right': 45, - 'middle-right': -90, - 'middle-left': 90, - 'bottom-left': -135, - 'bottom-center': 180, - 'bottom-right': 135, -}; - -const TOUCH_DEVICE = 'ontouchstart' in Konva._global; - -function getCursor(anchorName, rad, rotateCursor) { - if (anchorName === 'rotater') { - return rotateCursor; - } - - rad += Util.degToRad(ANGLES[anchorName] || 0); - const angle = ((Util.radToDeg(rad) % 360) + 360) % 360; - - if (Util._inRange(angle, 315 + 22.5, 360) || Util._inRange(angle, 0, 22.5)) { - // TOP - return 'ns-resize'; - } else if (Util._inRange(angle, 45 - 22.5, 45 + 22.5)) { - // TOP - RIGHT - return 'nesw-resize'; - } else if (Util._inRange(angle, 90 - 22.5, 90 + 22.5)) { - // RIGHT - return 'ew-resize'; - } else if (Util._inRange(angle, 135 - 22.5, 135 + 22.5)) { - // BOTTOM - RIGHT - return 'nwse-resize'; - } else if (Util._inRange(angle, 180 - 22.5, 180 + 22.5)) { - // BOTTOM - return 'ns-resize'; - } else if (Util._inRange(angle, 225 - 22.5, 225 + 22.5)) { - // BOTTOM - LEFT - return 'nesw-resize'; - } else if (Util._inRange(angle, 270 - 22.5, 270 + 22.5)) { - // RIGHT - return 'ew-resize'; - } else if (Util._inRange(angle, 315 - 22.5, 315 + 22.5)) { - // BOTTOM - RIGHT - return 'nwse-resize'; - } else { - // how can we can there? - Util.error('Transformer has unknown angle for cursor detection: ' + angle); - return 'pointer'; - } -} - -const ANCHORS_NAMES = [ - 'top-left', - 'top-center', - 'top-right', - 'middle-right', - 'middle-left', - 'bottom-left', - 'bottom-center', - 'bottom-right', -]; - -const MAX_SAFE_INTEGER = 100000000; - -function getCenter(shape: Box) { - return { - x: - shape.x + - (shape.width / 2) * Math.cos(shape.rotation) + - (shape.height / 2) * Math.sin(-shape.rotation), - y: - shape.y + - (shape.height / 2) * Math.cos(shape.rotation) + - (shape.width / 2) * Math.sin(shape.rotation), - }; -} - -function rotateAroundPoint(shape: Box, angleRad: number, point: Vector2d) { - const x = - point.x + - (shape.x - point.x) * Math.cos(angleRad) - - (shape.y - point.y) * Math.sin(angleRad); - const y = - point.y + - (shape.x - point.x) * Math.sin(angleRad) + - (shape.y - point.y) * Math.cos(angleRad); - return { - ...shape, - rotation: shape.rotation + angleRad, - x, - y, - }; -} - -function rotateAroundCenter(shape: Box, deltaRad: number) { - const center = getCenter(shape); - return rotateAroundPoint(shape, deltaRad, center); -} - -function getSnap(snaps: Array, newRotationRad: number, tol: number) { - let snapped = newRotationRad; - for (let i = 0; i < snaps.length; i++) { - const angle = Konva.getAngle(snaps[i]); - - const absDiff = Math.abs(angle - newRotationRad) % (Math.PI * 2); - const dif = Math.min(absDiff, Math.PI * 2 - absDiff); - - if (dif < tol) { - snapped = angle; - } - } - return snapped; -} - -let activeTransformersCount = 0; -/** - * Transformer constructor. Transformer is a special type of group that allow you transform Konva - * primitives and shapes. Transforming tool is not changing `width` and `height` properties of nodes - * when you resize them. Instead it changes `scaleX` and `scaleY` properties. - * @constructor - * @memberof Konva - * @param {Object} config - * @param {Boolean} [config.resizeEnabled] Default is true - * @param {Boolean} [config.rotateEnabled] Default is true - * @param {Boolean} [config.rotateLineVisible] Default is true - * @param {Array} [config.rotationSnaps] Array of angles for rotation snaps. Default is [] - * @param {Number} [config.rotationSnapTolerance] Snapping tolerance. If closer than this it will snap. Default is 5 - * @param {Number} [config.rotateAnchorOffset] Default is 50 - * @param {String} [config.rotateAnchorCursor] Default is crosshair - * @param {Number} [config.padding] Default is 0 - * @param {Boolean} [config.borderEnabled] Should we draw border? Default is true - * @param {String} [config.borderStroke] Border stroke color - * @param {Number} [config.borderStrokeWidth] Border stroke size - * @param {Array} [config.borderDash] Array for border dash. - * @param {String} [config.anchorFill] Anchor fill color - * @param {String} [config.anchorStroke] Anchor stroke color - * @param {String} [config.anchorCornerRadius] Anchor corner radius - * @param {Number} [config.anchorStrokeWidth] Anchor stroke size - * @param {Number} [config.anchorSize] Default is 10 - * @param {Boolean} [config.keepRatio] Should we keep ratio when we are moving edges? Default is true - * @param {String} [config.shiftBehavior] How does transformer react on shift key press when we are moving edges? Default is 'default' - * @param {Boolean} [config.centeredScaling] Should we resize relative to node's center? Default is false - * @param {Array} [config.enabledAnchors] Array of names of enabled handles - * @param {Boolean} [config.flipEnabled] Can we flip/mirror shape on transform?. True by default - * @param {Function} [config.boundBoxFunc] Bounding box function - * @param {Function} [config.ignoreStroke] Should we ignore stroke size? Default is false - * @param {Boolean} [config.useSingleNodeRotation] When just one node attached, should we use its rotation for transformer? - * @param {Boolean} [config.shouldOverdrawWholeArea] Should we fill whole transformer area with fake transparent shape to enable dragging from empty spaces? - * @example - * var transformer = new Konva.Transformer({ - * nodes: [rectangle], - * rotateAnchorOffset: 60, - * enabledAnchors: ['top-left', 'top-right', 'bottom-left', 'bottom-right'] - * }); - * layer.add(transformer); - */ -export class Transformer extends Group { - _nodes: Array; - _movingAnchorName: string | null = null; - _transforming = false; - _anchorDragOffset: Vector2d; - sin: number; - cos: number; - _cursorChange: boolean; - - static isTransforming = () => { - return activeTransformersCount > 0; - }; - - constructor(config?: TransformerConfig) { - // call super constructor - super(config); - this._createElements(); - - // bindings - this._handleMouseMove = this._handleMouseMove.bind(this); - this._handleMouseUp = this._handleMouseUp.bind(this); - this.update = this.update.bind(this); - - // update transformer data for certain attr changes - this.on(ATTR_CHANGE_LIST, this.update); - - if (this.getNode()) { - this.update(); - } - } - /** - * alias to `tr.nodes([shape])`/ This method is deprecated and will be removed soon. - * @method - * @name Konva.Transformer#attachTo - * @returns {Konva.Transformer} - * @example - * transformer.attachTo(shape); - */ - attachTo(node: Node) { - this.setNode(node); - return this; - } - setNode(node: Node) { - Util.warn( - 'tr.setNode(shape), tr.node(shape) and tr.attachTo(shape) methods are deprecated. Please use tr.nodes(nodesArray) instead.' - ); - return this.setNodes([node]); - } - getNode() { - return this._nodes && this._nodes[0]; - } - - _getEventNamespace() { - return EVENTS_NAME + this._id; - } - - setNodes(nodes: Array = []) { - if (this._nodes && this._nodes.length) { - this.detach(); - } - - const filteredNodes = nodes.filter((node) => { - // check if ancestor of the transformer - if (node.isAncestorOf(this)) { - Util.error( - 'Konva.Transformer cannot be an a child of the node you are trying to attach' - ); - return false; - } - - return true; - }); - - this._nodes = nodes = filteredNodes; - if (nodes.length === 1 && this.useSingleNodeRotation()) { - this.rotation(nodes[0].getAbsoluteRotation()); - } else { - this.rotation(0); - } - this._nodes.forEach((node) => { - const onChange = () => { - if (this.nodes().length === 1 && this.useSingleNodeRotation()) { - this.rotation(this.nodes()[0].getAbsoluteRotation()); - } - - this._resetTransformCache(); - if (!this._transforming && !this.isDragging()) { - this.update(); - } - }; - const additionalEvents = node._attrsAffectingSize - .map((prop) => prop + 'Change.' + this._getEventNamespace()) - .join(' '); - node.on(additionalEvents, onChange); - node.on( - TRANSFORM_CHANGE_STR.map( - (e) => e + `.${this._getEventNamespace()}` - ).join(' '), - onChange - ); - node.on(`absoluteTransformChange.${this._getEventNamespace()}`, onChange); - this._proxyDrag(node); - }); - this._resetTransformCache(); - // we may need it if we set node in initial props - // so elements are not defined yet - const elementsCreated = !!this.findOne('.top-left'); - if (elementsCreated) { - this.update(); - } - return this; - } - - _proxyDrag(node: Node) { - let lastPos; - node.on(`dragstart.${this._getEventNamespace()}`, (e) => { - lastPos = node.getAbsolutePosition(); - // actual dragging of Transformer doesn't make sense - // but we need to make sure it also has all drag events - if (!this.isDragging() && node !== this.findOne('.back')) { - this.startDrag(e, false); - } - }); - node.on(`dragmove.${this._getEventNamespace()}`, (e) => { - if (!lastPos) { - return; - } - const abs = node.getAbsolutePosition(); - const dx = abs.x - lastPos.x; - const dy = abs.y - lastPos.y; - this.nodes().forEach((otherNode) => { - if (otherNode === node) { - return; - } - if (otherNode.isDragging()) { - return; - } - const otherAbs = otherNode.getAbsolutePosition(); - otherNode.setAbsolutePosition({ - x: otherAbs.x + dx, - y: otherAbs.y + dy, - }); - otherNode.startDrag(e); - }); - lastPos = null; - }); - } - - getNodes() { - return this._nodes || []; - } - /** - * return the name of current active anchor - * @method - * @name Konva.Transformer#getActiveAnchor - * @returns {String | Null} - * @example - * transformer.getActiveAnchor(); - */ - getActiveAnchor() { - return this._movingAnchorName; - } - /** - * detach transformer from an attached node - * @method - * @name Konva.Transformer#detach - * @returns {Konva.Transformer} - * @example - * transformer.detach(); - */ - detach() { - // remove events - if (this._nodes) { - this._nodes.forEach((node) => { - node.off('.' + this._getEventNamespace()); - }); - } - this._nodes = []; - this._resetTransformCache(); - } - /** - * bind events to the Transformer. You can use events: `transform`, `transformstart`, `transformend`, `dragstart`, `dragmove`, `dragend` - * @method - * @name Konva.Transformer#on - * @param {String} evtStr e.g. 'transform' - * @param {Function} handler The handler function. The first argument of that function is event object. Event object has `target` as main target of the event, `currentTarget` as current node listener and `evt` as native browser event. - * @returns {Konva.Transformer} - * @example - * // add click listener - * tr.on('transformstart', function() { - * console.log('transform started'); - * }); - */ - _resetTransformCache() { - this._clearCache(NODES_RECT); - this._clearCache('transform'); - this._clearSelfAndDescendantCache('absoluteTransform'); - } - _getNodeRect() { - return this._getCache(NODES_RECT, this.__getNodeRect); - } - - // return absolute rotated bounding rectangle - __getNodeShape(node: Node, rot = this.rotation(), relative?: Node) { - const rect = node.getClientRect({ - skipTransform: true, - skipShadow: true, - skipStroke: this.ignoreStroke(), - }); - - const absScale = node.getAbsoluteScale(relative); - const absPos = node.getAbsolutePosition(relative); - - const dx = rect.x * absScale.x - node.offsetX() * absScale.x; - const dy = rect.y * absScale.y - node.offsetY() * absScale.y; - - const rotation = - (Konva.getAngle(node.getAbsoluteRotation()) + Math.PI * 2) % - (Math.PI * 2); - - const box = { - x: absPos.x + dx * Math.cos(rotation) + dy * Math.sin(-rotation), - y: absPos.y + dy * Math.cos(rotation) + dx * Math.sin(rotation), - width: rect.width * absScale.x, - height: rect.height * absScale.y, - rotation: rotation, - }; - return rotateAroundPoint(box, -Konva.getAngle(rot), { - x: 0, - y: 0, - }); - } - // returns box + rotation of all shapes - __getNodeRect() { - const node = this.getNode(); - if (!node) { - return { - x: -MAX_SAFE_INTEGER, - y: -MAX_SAFE_INTEGER, - width: 0, - height: 0, - rotation: 0, - }; - } - - const totalPoints: Vector2d[] = []; - this.nodes().map((node) => { - const box = node.getClientRect({ - skipTransform: true, - skipShadow: true, - skipStroke: this.ignoreStroke(), - }); - const points = [ - { x: box.x, y: box.y }, - { x: box.x + box.width, y: box.y }, - { x: box.x + box.width, y: box.y + box.height }, - { x: box.x, y: box.y + box.height }, - ]; - const trans = node.getAbsoluteTransform(); - points.forEach(function (point) { - const transformed = trans.point(point); - totalPoints.push(transformed); - }); - }); - - const tr = new Transform(); - tr.rotate(-Konva.getAngle(this.rotation())); - - let minX: number = Infinity, - minY: number = Infinity, - maxX: number = -Infinity, - maxY: number = -Infinity; - totalPoints.forEach(function (point) { - const transformed = tr.point(point); - if (minX === undefined) { - minX = maxX = transformed.x; - minY = maxY = transformed.y; - } - minX = Math.min(minX, transformed.x); - minY = Math.min(minY, transformed.y); - maxX = Math.max(maxX, transformed.x); - maxY = Math.max(maxY, transformed.y); - }); - - tr.invert(); - const p = tr.point({ x: minX, y: minY }); - return { - x: p.x, - y: p.y, - width: maxX - minX, - height: maxY - minY, - rotation: Konva.getAngle(this.rotation()), - }; - // const shapes = this.nodes().map(node => { - // return this.__getNodeShape(node); - // }); - - // const box = getShapesRect(shapes); - // return rotateAroundPoint(box, Konva.getAngle(this.rotation()), { - // x: 0, - // y: 0 - // }); - } - getX() { - return this._getNodeRect().x; - } - getY() { - return this._getNodeRect().y; - } - getWidth() { - return this._getNodeRect().width; - } - getHeight() { - return this._getNodeRect().height; - } - _createElements() { - this._createBack(); - - ANCHORS_NAMES.forEach((name) => { - this._createAnchor(name); - }); - - this._createAnchor('rotater'); - } - _createAnchor(name) { - const anchor = new Rect({ - stroke: 'rgb(0, 161, 255)', - fill: 'white', - strokeWidth: 1, - name: name + ' _anchor', - dragDistance: 0, - // make it draggable, - // so activating the anchor will not start drag&drop of any parent - draggable: true, - hitStrokeWidth: TOUCH_DEVICE ? 10 : 'auto', - }); - const self = this; - anchor.on('mousedown touchstart', function (e) { - self._handleMouseDown(e); - }); - anchor.on('dragstart', (e) => { - anchor.stopDrag(); - e.cancelBubble = true; - }); - anchor.on('dragend', (e) => { - e.cancelBubble = true; - }); - - // add hover styling - anchor.on('mouseenter', () => { - const rad = Konva.getAngle(this.rotation()); - const rotateCursor = this.rotateAnchorCursor(); - const cursor = getCursor(name, rad, rotateCursor); - anchor.getStage()!.content && - (anchor.getStage()!.content.style.cursor = cursor); - this._cursorChange = true; - }); - anchor.on('mouseout', () => { - anchor.getStage()!.content && - (anchor.getStage()!.content.style.cursor = ''); - this._cursorChange = false; - }); - this.add(anchor); - } - _createBack() { - const back = new Shape({ - name: 'back', - width: 0, - height: 0, - draggable: true, - sceneFunc(ctx, shape) { - const tr = shape.getParent() as Transformer; - const padding = tr.padding(); - ctx.beginPath(); - ctx.rect( - -padding, - -padding, - shape.width() + padding * 2, - shape.height() + padding * 2 - ); - ctx.moveTo(shape.width() / 2, -padding); - if (tr.rotateEnabled() && tr.rotateLineVisible()) { - ctx.lineTo( - shape.width() / 2, - -tr.rotateAnchorOffset() * Util._sign(shape.height()) - padding - ); - } - - ctx.fillStrokeShape(shape); - }, - hitFunc: (ctx, shape) => { - if (!this.shouldOverdrawWholeArea()) { - return; - } - const padding = this.padding(); - ctx.beginPath(); - ctx.rect( - -padding, - -padding, - shape.width() + padding * 2, - shape.height() + padding * 2 - ); - ctx.fillStrokeShape(shape); - }, - }); - this.add(back); - this._proxyDrag(back); - // do not bubble drag from the back shape - // because we already "drag" whole transformer - // so we don't want to trigger drag twice on transformer - back.on('dragstart', (e) => { - e.cancelBubble = true; - }); - back.on('dragmove', (e) => { - e.cancelBubble = true; - }); - back.on('dragend', (e) => { - e.cancelBubble = true; - }); - // force self update when we drag with shouldOverDrawWholeArea setting - this.on('dragmove', (e) => { - this.update(); - }); - } - _handleMouseDown(e) { - // do nothing if we already transforming - // that is possible to trigger with multitouch - if (this._transforming) { - return; - } - this._movingAnchorName = e.target.name().split(' ')[0]; - - const attrs = this._getNodeRect(); - const width = attrs.width; - const height = attrs.height; - - const hypotenuse = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2)); - this.sin = Math.abs(height / hypotenuse); - this.cos = Math.abs(width / hypotenuse); - - if (typeof window !== 'undefined') { - window.addEventListener('mousemove', this._handleMouseMove); - window.addEventListener('touchmove', this._handleMouseMove); - window.addEventListener('mouseup', this._handleMouseUp, true); - window.addEventListener('touchend', this._handleMouseUp, true); - } - - this._transforming = true; - const ap = e.target.getAbsolutePosition(); - const pos = e.target.getStage().getPointerPosition(); - this._anchorDragOffset = { - x: pos.x - ap.x, - y: pos.y - ap.y, - }; - activeTransformersCount++; - this._fire('transformstart', { evt: e.evt, target: this.getNode() }); - this._nodes.forEach((target) => { - target._fire('transformstart', { evt: e.evt, target }); - }); - } - _handleMouseMove(e) { - let x, y, newHypotenuse; - const anchorNode = this.findOne('.' + this._movingAnchorName)!; - const stage = anchorNode.getStage()!; - - stage.setPointersPositions(e); - - const pp = stage.getPointerPosition()!; - let newNodePos = { - x: pp.x - this._anchorDragOffset.x, - y: pp.y - this._anchorDragOffset.y, - }; - const oldAbs = anchorNode.getAbsolutePosition(); - - if (this.anchorDragBoundFunc()) { - newNodePos = this.anchorDragBoundFunc()(oldAbs, newNodePos, e); - } - anchorNode.setAbsolutePosition(newNodePos); - const newAbs = anchorNode.getAbsolutePosition(); - - // console.log(oldAbs, newNodePos, newAbs); - - if (oldAbs.x === newAbs.x && oldAbs.y === newAbs.y) { - return; - } - - // rotater is working very differently, so do it first - if (this._movingAnchorName === 'rotater') { - const attrs = this._getNodeRect(); - x = anchorNode.x() - attrs.width / 2; - y = -anchorNode.y() + attrs.height / 2; - - // hor angle is changed? - let delta = Math.atan2(-y, x) + Math.PI / 2; - - if (attrs.height < 0) { - delta -= Math.PI; - } - - const oldRotation = Konva.getAngle(this.rotation()); - const newRotation = oldRotation + delta; - - const tol = Konva.getAngle(this.rotationSnapTolerance()); - const snappedRot = getSnap(this.rotationSnaps(), newRotation, tol); - - const diff = snappedRot - attrs.rotation; - - const shape = rotateAroundCenter(attrs, diff); - this._fitNodesInto(shape, e); - return; - } - - const shiftBehavior = this.shiftBehavior(); - - let keepProportion: boolean; - if (shiftBehavior === 'inverted') { - keepProportion = this.keepRatio() && !e.shiftKey; - } else if (shiftBehavior === 'none') { - keepProportion = this.keepRatio(); - } else { - keepProportion = this.keepRatio() || e.shiftKey; - } - - var centeredScaling = this.centeredScaling() || e.altKey; - - if (this._movingAnchorName === 'top-left') { - if (keepProportion) { - var comparePoint = centeredScaling - ? { - x: this.width() / 2, - y: this.height() / 2, - } - : { - x: this.findOne('.bottom-right')!.x(), - y: this.findOne('.bottom-right')!.y(), - }; - newHypotenuse = Math.sqrt( - Math.pow(comparePoint.x - anchorNode.x(), 2) + - Math.pow(comparePoint.y - anchorNode.y(), 2) - ); - - var reverseX = this.findOne('.top-left')!.x() > comparePoint.x ? -1 : 1; - - var reverseY = this.findOne('.top-left')!.y() > comparePoint.y ? -1 : 1; - - x = newHypotenuse * this.cos * reverseX; - y = newHypotenuse * this.sin * reverseY; - - this.findOne('.top-left')!.x(comparePoint.x - x); - this.findOne('.top-left')!.y(comparePoint.y - y); - } - } else if (this._movingAnchorName === 'top-center') { - this.findOne('.top-left')!.y(anchorNode.y()); - } else if (this._movingAnchorName === 'top-right') { - if (keepProportion) { - var comparePoint = centeredScaling - ? { - x: this.width() / 2, - y: this.height() / 2, - } - : { - x: this.findOne('.bottom-left')!.x(), - y: this.findOne('.bottom-left')!.y(), - }; - - newHypotenuse = Math.sqrt( - Math.pow(anchorNode.x() - comparePoint.x, 2) + - Math.pow(comparePoint.y - anchorNode.y(), 2) - ); - - var reverseX = - this.findOne('.top-right')!.x() < comparePoint.x ? -1 : 1; - - var reverseY = - this.findOne('.top-right')!.y() > comparePoint.y ? -1 : 1; - - x = newHypotenuse * this.cos * reverseX; - y = newHypotenuse * this.sin * reverseY; - - this.findOne('.top-right')!.x(comparePoint.x + x); - this.findOne('.top-right')!.y(comparePoint.y - y); - } - var pos = anchorNode.position(); - this.findOne('.top-left')!.y(pos.y); - this.findOne('.bottom-right')!.x(pos.x); - } else if (this._movingAnchorName === 'middle-left') { - this.findOne('.top-left')!.x(anchorNode.x()); - } else if (this._movingAnchorName === 'middle-right') { - this.findOne('.bottom-right')!.x(anchorNode.x()); - } else if (this._movingAnchorName === 'bottom-left') { - if (keepProportion) { - var comparePoint = centeredScaling - ? { - x: this.width() / 2, - y: this.height() / 2, - } - : { - x: this.findOne('.top-right')!.x(), - y: this.findOne('.top-right')!.y(), - }; - - newHypotenuse = Math.sqrt( - Math.pow(comparePoint.x - anchorNode.x(), 2) + - Math.pow(anchorNode.y() - comparePoint.y, 2) - ); - - var reverseX = comparePoint.x < anchorNode.x() ? -1 : 1; - - var reverseY = anchorNode.y() < comparePoint.y ? -1 : 1; - - x = newHypotenuse * this.cos * reverseX; - y = newHypotenuse * this.sin * reverseY; - - anchorNode.x(comparePoint.x - x); - anchorNode.y(comparePoint.y + y); - } - - pos = anchorNode.position(); - - this.findOne('.top-left')!.x(pos.x); - this.findOne('.bottom-right')!.y(pos.y); - } else if (this._movingAnchorName === 'bottom-center') { - this.findOne('.bottom-right')!.y(anchorNode.y()); - } else if (this._movingAnchorName === 'bottom-right') { - if (keepProportion) { - var comparePoint = centeredScaling - ? { - x: this.width() / 2, - y: this.height() / 2, - } - : { - x: this.findOne('.top-left')!.x(), - y: this.findOne('.top-left')!.y(), - }; - - newHypotenuse = Math.sqrt( - Math.pow(anchorNode.x() - comparePoint.x, 2) + - Math.pow(anchorNode.y() - comparePoint.y, 2) - ); - - var reverseX = - this.findOne('.bottom-right')!.x() < comparePoint.x ? -1 : 1; - - var reverseY = - this.findOne('.bottom-right')!.y() < comparePoint.y ? -1 : 1; - - x = newHypotenuse * this.cos * reverseX; - y = newHypotenuse * this.sin * reverseY; - - this.findOne('.bottom-right')!.x(comparePoint.x + x); - this.findOne('.bottom-right')!.y(comparePoint.y + y); - } - } else { - console.error( - new Error( - 'Wrong position argument of selection resizer: ' + - this._movingAnchorName - ) - ); - } - - var centeredScaling = this.centeredScaling() || e.altKey; - if (centeredScaling) { - const topLeft = this.findOne('.top-left')!; - const bottomRight = this.findOne('.bottom-right')!; - const topOffsetX = topLeft.x(); - const topOffsetY = topLeft.y(); - - const bottomOffsetX = this.getWidth() - bottomRight.x(); - const bottomOffsetY = this.getHeight() - bottomRight.y(); - - bottomRight.move({ - x: -topOffsetX, - y: -topOffsetY, - }); - - topLeft.move({ - x: bottomOffsetX, - y: bottomOffsetY, - }); - } - - const absPos = this.findOne('.top-left')!.getAbsolutePosition(); - - x = absPos.x; - y = absPos.y; - - const width = - this.findOne('.bottom-right')!.x() - this.findOne('.top-left')!.x(); - - const height = - this.findOne('.bottom-right')!.y() - this.findOne('.top-left')!.y(); - - this._fitNodesInto( - { - x: x, - y: y, - width: width, - height: height, - rotation: Konva.getAngle(this.rotation()), - }, - e - ); - } - _handleMouseUp(e) { - this._removeEvents(e); - } - getAbsoluteTransform() { - return this.getTransform(); - } - _removeEvents(e?) { - if (this._transforming) { - this._transforming = false; - if (typeof window !== 'undefined') { - window.removeEventListener('mousemove', this._handleMouseMove); - window.removeEventListener('touchmove', this._handleMouseMove); - window.removeEventListener('mouseup', this._handleMouseUp, true); - window.removeEventListener('touchend', this._handleMouseUp, true); - } - const node = this.getNode(); - activeTransformersCount--; - this._fire('transformend', { evt: e, target: node }); - // redraw layer to restore hit graph - this.getLayer()?.batchDraw(); - - if (node) { - this._nodes.forEach((target) => { - target._fire('transformend', { evt: e, target }); - // redraw layer to restore hit graph - target.getLayer()?.batchDraw(); - }); - } - this._movingAnchorName = null; - } - } - _fitNodesInto(newAttrs, evt?) { - const oldAttrs = this._getNodeRect(); - - const minSize = 1; - - if (Util._inRange(newAttrs.width, -this.padding() * 2 - minSize, minSize)) { - this.update(); - return; - } - if ( - Util._inRange(newAttrs.height, -this.padding() * 2 - minSize, minSize) - ) { - this.update(); - return; - } - - const t = new Transform(); - t.rotate(Konva.getAngle(this.rotation())); - if ( - this._movingAnchorName && - newAttrs.width < 0 && - this._movingAnchorName.indexOf('left') >= 0 - ) { - const offset = t.point({ - x: -this.padding() * 2, - y: 0, - }); - newAttrs.x += offset.x; - newAttrs.y += offset.y; - newAttrs.width += this.padding() * 2; - this._movingAnchorName = this._movingAnchorName.replace('left', 'right'); - this._anchorDragOffset.x -= offset.x; - this._anchorDragOffset.y -= offset.y; - } else if ( - this._movingAnchorName && - newAttrs.width < 0 && - this._movingAnchorName.indexOf('right') >= 0 - ) { - const offset = t.point({ - x: this.padding() * 2, - y: 0, - }); - this._movingAnchorName = this._movingAnchorName.replace('right', 'left'); - this._anchorDragOffset.x -= offset.x; - this._anchorDragOffset.y -= offset.y; - newAttrs.width += this.padding() * 2; - } - if ( - this._movingAnchorName && - newAttrs.height < 0 && - this._movingAnchorName.indexOf('top') >= 0 - ) { - const offset = t.point({ - x: 0, - y: -this.padding() * 2, - }); - newAttrs.x += offset.x; - newAttrs.y += offset.y; - this._movingAnchorName = this._movingAnchorName.replace('top', 'bottom'); - this._anchorDragOffset.x -= offset.x; - this._anchorDragOffset.y -= offset.y; - newAttrs.height += this.padding() * 2; - } else if ( - this._movingAnchorName && - newAttrs.height < 0 && - this._movingAnchorName.indexOf('bottom') >= 0 - ) { - const offset = t.point({ - x: 0, - y: this.padding() * 2, - }); - this._movingAnchorName = this._movingAnchorName.replace('bottom', 'top'); - this._anchorDragOffset.x -= offset.x; - this._anchorDragOffset.y -= offset.y; - newAttrs.height += this.padding() * 2; - } - - if (this.boundBoxFunc()) { - const bounded = this.boundBoxFunc()(oldAttrs, newAttrs); - if (bounded) { - newAttrs = bounded; - } else { - Util.warn( - 'boundBoxFunc returned falsy. You should return new bound rect from it!' - ); - } - } - - // base size value doesn't really matter - // we just need to think about bounding boxes as transforms - // but how? - // the idea is that we have a transformed rectangle with the size of "baseSize" - const baseSize = 10000000; - const oldTr = new Transform(); - oldTr.translate(oldAttrs.x, oldAttrs.y); - oldTr.rotate(oldAttrs.rotation); - oldTr.scale(oldAttrs.width / baseSize, oldAttrs.height / baseSize); - - const newTr = new Transform(); - const newScaleX = newAttrs.width / baseSize; - const newScaleY = newAttrs.height / baseSize; - - if (this.flipEnabled() === false) { - newTr.translate(newAttrs.x, newAttrs.y); - newTr.rotate(newAttrs.rotation); - newTr.translate( - newAttrs.width < 0 ? newAttrs.width : 0, - newAttrs.height < 0 ? newAttrs.height : 0 - ); - newTr.scale(Math.abs(newScaleX), Math.abs(newScaleY)); - } else { - newTr.translate(newAttrs.x, newAttrs.y); - newTr.rotate(newAttrs.rotation); - newTr.scale(newScaleX, newScaleY); - } - - // now lets think we had [old transform] and n ow we have [new transform] - // Now, the questions is: how can we transform "parent" to go from [old transform] into [new transform] - // in equation it will be: - // [delta transform] * [old transform] = [new transform] - // that means that - // [delta transform] = [new transform] * [old transform inverted] - const delta = newTr.multiply(oldTr.invert()); - - this._nodes.forEach((node) => { - // for each node we have the same [delta transform] - // the equations is - // [delta transform] * [parent transform] * [old local transform] = [parent transform] * [new local transform] - // and we need to find [new local transform] - // [new local] = [parent inverted] * [delta] * [parent] * [old local] - const parentTransform = node.getParent()!.getAbsoluteTransform(); - const localTransform = node.getTransform().copy(); - // skip offset: - localTransform.translate(node.offsetX(), node.offsetY()); - - const newLocalTransform = new Transform(); - newLocalTransform - .multiply(parentTransform.copy().invert()) - .multiply(delta) - .multiply(parentTransform) - .multiply(localTransform); - - const attrs = newLocalTransform.decompose(); - node.setAttrs(attrs); - node.getLayer()?.batchDraw(); - }); - this.rotation(Util._getRotation(newAttrs.rotation)); - // trigger transform event AFTER we update rotation - this._nodes.forEach((node) => { - this._fire('transform', { evt: evt, target: node }); - node._fire('transform', { evt: evt, target: node }); - }); - this._resetTransformCache(); - this.update(); - this.getLayer()!.batchDraw(); - } - /** - * force update of Konva.Transformer. - * Use it when you updated attached Konva.Group and now you need to reset transformer size - * @method - * @name Konva.Transformer#forceUpdate - */ - forceUpdate() { - this._resetTransformCache(); - this.update(); - } - - _batchChangeChild(selector: string, attrs: any) { - const anchor = this.findOne(selector)!; - anchor.setAttrs(attrs); - } - - update() { - const attrs = this._getNodeRect(); - this.rotation(Util._getRotation(attrs.rotation)); - const width = attrs.width; - const height = attrs.height; - - const enabledAnchors = this.enabledAnchors(); - const resizeEnabled = this.resizeEnabled(); - const padding = this.padding(); - - const anchorSize = this.anchorSize(); - const anchors = this.find('._anchor'); - anchors.forEach((node) => { - node.setAttrs({ - width: anchorSize, - height: anchorSize, - offsetX: anchorSize / 2, - offsetY: anchorSize / 2, - stroke: this.anchorStroke(), - strokeWidth: this.anchorStrokeWidth(), - fill: this.anchorFill(), - cornerRadius: this.anchorCornerRadius(), - }); - }); - - this._batchChangeChild('.top-left', { - x: 0, - y: 0, - offsetX: anchorSize / 2 + padding, - offsetY: anchorSize / 2 + padding, - visible: resizeEnabled && enabledAnchors.indexOf('top-left') >= 0, - }); - this._batchChangeChild('.top-center', { - x: width / 2, - y: 0, - offsetY: anchorSize / 2 + padding, - visible: resizeEnabled && enabledAnchors.indexOf('top-center') >= 0, - }); - this._batchChangeChild('.top-right', { - x: width, - y: 0, - offsetX: anchorSize / 2 - padding, - offsetY: anchorSize / 2 + padding, - visible: resizeEnabled && enabledAnchors.indexOf('top-right') >= 0, - }); - this._batchChangeChild('.middle-left', { - x: 0, - y: height / 2, - offsetX: anchorSize / 2 + padding, - visible: resizeEnabled && enabledAnchors.indexOf('middle-left') >= 0, - }); - this._batchChangeChild('.middle-right', { - x: width, - y: height / 2, - offsetX: anchorSize / 2 - padding, - visible: resizeEnabled && enabledAnchors.indexOf('middle-right') >= 0, - }); - this._batchChangeChild('.bottom-left', { - x: 0, - y: height, - offsetX: anchorSize / 2 + padding, - offsetY: anchorSize / 2 - padding, - visible: resizeEnabled && enabledAnchors.indexOf('bottom-left') >= 0, - }); - this._batchChangeChild('.bottom-center', { - x: width / 2, - y: height, - offsetY: anchorSize / 2 - padding, - visible: resizeEnabled && enabledAnchors.indexOf('bottom-center') >= 0, - }); - this._batchChangeChild('.bottom-right', { - x: width, - y: height, - offsetX: anchorSize / 2 - padding, - offsetY: anchorSize / 2 - padding, - visible: resizeEnabled && enabledAnchors.indexOf('bottom-right') >= 0, - }); - - this._batchChangeChild('.rotater', { - x: width / 2, - y: -this.rotateAnchorOffset() * Util._sign(height) - padding, - visible: this.rotateEnabled(), - }); - - this._batchChangeChild('.back', { - width: width, - height: height, - visible: this.borderEnabled(), - stroke: this.borderStroke(), - strokeWidth: this.borderStrokeWidth(), - dash: this.borderDash(), - x: 0, - y: 0, - }); - - const styleFunc = this.anchorStyleFunc(); - if (styleFunc) { - anchors.forEach((node) => { - styleFunc(node); - }); - } - this.getLayer()?.batchDraw(); - } - /** - * determine if transformer is in active transform - * @method - * @name Konva.Transformer#isTransforming - * @returns {Boolean} - */ - isTransforming() { - return this._transforming; - } - /** - * Stop active transform action - * @method - * @name Konva.Transformer#stopTransform - * @returns {Boolean} - */ - stopTransform() { - if (this._transforming) { - this._removeEvents(); - const anchorNode = this.findOne('.' + this._movingAnchorName); - if (anchorNode) { - anchorNode.stopDrag(); - } - } - } - destroy() { - if (this.getStage() && this._cursorChange) { - this.getStage()!.content && (this.getStage()!.content.style.cursor = ''); - } - Group.prototype.destroy.call(this); - this.detach(); - this._removeEvents(); - return this; - } - // do not work as a container - // we will recreate inner nodes manually - toObject() { - return Node.prototype.toObject.call(this); - } - - // overwrite clone to NOT use method from Container - clone(obj?: any) { - const node = Node.prototype.clone.call(this, obj); - return node as this; - } - getClientRect() { - if (this.nodes().length > 0) { - return super.getClientRect(); - } else { - // if we are detached return zero size - // so it will be skipped in calculations - return { x: 0, y: 0, width: 0, height: 0 }; - } - } - - nodes: GetSet; - enabledAnchors: GetSet; - rotationSnaps: GetSet; - anchorSize: GetSet; - resizeEnabled: GetSet; - rotateEnabled: GetSet; - rotateLineVisible: GetSet; - rotateAnchorOffset: GetSet; - rotationSnapTolerance: GetSet; - rotateAnchorCursor: GetSet; - padding: GetSet; - borderEnabled: GetSet; - borderStroke: GetSet; - borderStrokeWidth: GetSet; - borderDash: GetSet; - anchorFill: GetSet; - anchorStroke: GetSet; - anchorCornerRadius: GetSet; - anchorStrokeWidth: GetSet; - keepRatio: GetSet; - shiftBehavior: GetSet; - centeredScaling: GetSet; - flipEnabled: GetSet; - ignoreStroke: GetSet; - boundBoxFunc: GetSet<(oldBox: Box, newBox: Box) => Box, this>; - anchorDragBoundFunc: GetSet< - (oldPos: Vector2d, newPos: Vector2d, e: MouseEvent) => Vector2d, - this - >; - anchorStyleFunc: GetSet void), this>; - shouldOverdrawWholeArea: GetSet; - useSingleNodeRotation: GetSet; -} - -function validateAnchors(val) { - if (!(val instanceof Array)) { - Util.warn('enabledAnchors value should be an array'); - } - if (val instanceof Array) { - val.forEach(function (name) { - if (ANCHORS_NAMES.indexOf(name) === -1) { - Util.warn( - 'Unknown anchor name: ' + - name + - '. Available names are: ' + - ANCHORS_NAMES.join(', ') - ); - } - }); - } - return val || []; -} - -Transformer.prototype.className = 'Transformer'; -_registerNode(Transformer); - -/** - * get/set enabled handlers - * @name Konva.Transformer#enabledAnchors - * @method - * @param {Array} array - * @returns {Array} - * @example - * // get list of handlers - * var enabledAnchors = transformer.enabledAnchors(); - * - * // set handlers - * transformer.enabledAnchors(['top-left', 'top-center', 'top-right', 'middle-right', 'middle-left', 'bottom-left', 'bottom-center', 'bottom-right']); - */ -Factory.addGetterSetter( - Transformer, - 'enabledAnchors', - ANCHORS_NAMES, - validateAnchors -); - -/** - * get/set flip enabled - * @name Konva.Transformer#flipEnabled - * @method - * @param {Boolean} flag - * @returns {Boolean} - * @example - * // get flip enabled property - * var flipEnabled = transformer.flipEnabled(); - * - * // set flip enabled property - * transformer.flipEnabled(false); - */ -Factory.addGetterSetter( - Transformer, - 'flipEnabled', - true, - getBooleanValidator() -); - -/** - * get/set resize ability. If false it will automatically hide resizing handlers - * @name Konva.Transformer#resizeEnabled - * @method - * @param {Boolean} enabled - * @returns {Boolean} - * @example - * // get - * var resizeEnabled = transformer.resizeEnabled(); - * - * // set - * transformer.resizeEnabled(false); - */ -Factory.addGetterSetter(Transformer, 'resizeEnabled', true); -/** - * get/set anchor size. Default is 10 - * @name Konva.Transformer#anchorSize - * @method - * @param {Number} size - * @returns {Number} - * @example - * // get - * var anchorSize = transformer.anchorSize(); - * - * // set - * transformer.anchorSize(20) - */ -Factory.addGetterSetter(Transformer, 'anchorSize', 10, getNumberValidator()); - -/** - * get/set ability to rotate. - * @name Konva.Transformer#rotateEnabled - * @method - * @param {Boolean} enabled - * @returns {Boolean} - * @example - * // get - * var rotateEnabled = transformer.rotateEnabled(); - * - * // set - * transformer.rotateEnabled(false); - */ -Factory.addGetterSetter(Transformer, 'rotateEnabled', true); - -/** - * get/set visibility of a little line that connects transformer and rotate anchor. - * @name Konva.Transformer#rotateLineVisible - * @method - * @param {Boolean} enabled - * @returns {Boolean} - * @example - * // get - * var rotateLineVisible = transformer.rotateLineVisible(); - * - * // set - * transformer.rotateLineVisible(false); - */ -Factory.addGetterSetter(Transformer, 'rotateLineVisible', true); - -/** - * get/set rotation snaps angles. - * @name Konva.Transformer#rotationSnaps - * @method - * @param {Array} array - * @returns {Array} - * @example - * // get - * var rotationSnaps = transformer.rotationSnaps(); - * - * // set - * transformer.rotationSnaps([0, 90, 180, 270]); - */ -Factory.addGetterSetter(Transformer, 'rotationSnaps', []); - -/** - * get/set distance for rotation handler - * @name Konva.Transformer#rotateAnchorOffset - * @method - * @param {Number} offset - * @returns {Number} - * @example - * // get - * var rotateAnchorOffset = transformer.rotateAnchorOffset(); - * - * // set - * transformer.rotateAnchorOffset(100); - */ -Factory.addGetterSetter( - Transformer, - 'rotateAnchorOffset', - 50, - getNumberValidator() -); - -/** - * get/set rotation anchor cursor - * @name Konva.Transformer#rotateAnchorCursor - * @method - * @param {String} cursorName - * @returns {String} - * @example - * // get - * var currentRotationAnchorCursor = transformer.rotateAnchorCursor(); - * - * // set - * transformer.rotateAnchorCursor('grab'); - */ -Factory.addGetterSetter(Transformer, 'rotateAnchorCursor', 'crosshair'); - -/** - * get/set distance for rotation tolerance - * @name Konva.Transformer#rotationSnapTolerance - * @method - * @param {Number} tolerance - * @returns {Number} - * @example - * // get - * var rotationSnapTolerance = transformer.rotationSnapTolerance(); - * - * // set - * transformer.rotationSnapTolerance(100); - */ -Factory.addGetterSetter( - Transformer, - 'rotationSnapTolerance', - 5, - getNumberValidator() -); - -/** - * get/set visibility of border - * @name Konva.Transformer#borderEnabled - * @method - * @param {Boolean} enabled - * @returns {Boolean} - * @example - * // get - * var borderEnabled = transformer.borderEnabled(); - * - * // set - * transformer.borderEnabled(false); - */ -Factory.addGetterSetter(Transformer, 'borderEnabled', true); - -/** - * get/set anchor stroke color - * @name Konva.Transformer#anchorStroke - * @method - * @param {String} strokeColor - * @returns {String} - * @example - * // get - * var anchorStroke = transformer.anchorStroke(); - * - * // set - * transformer.anchorStroke('red'); - */ -Factory.addGetterSetter(Transformer, 'anchorStroke', 'rgb(0, 161, 255)'); - -/** - * get/set anchor stroke width - * @name Konva.Transformer#anchorStrokeWidth - * @method - * @param {Number} anchorStrokeWidth - * @returns {Number} - * @example - * // get - * var anchorStrokeWidth = transformer.anchorStrokeWidth(); - * - * // set - * transformer.anchorStrokeWidth(3); - */ -Factory.addGetterSetter( - Transformer, - 'anchorStrokeWidth', - 1, - getNumberValidator() -); - -/** - * get/set anchor fill color - * @name Konva.Transformer#anchorFill - * @method - * @param {String} anchorFill - * @returns {String} - * @example - * // get - * var anchorFill = transformer.anchorFill(); - * - * // set - * transformer.anchorFill('red'); - */ -Factory.addGetterSetter(Transformer, 'anchorFill', 'white'); - -/** - * get/set anchor corner radius - * @name Konva.Transformer#anchorCornerRadius - * @method - * @param {Number} radius - * @returns {Number} - * @example - * // get - * var anchorCornerRadius = transformer.anchorCornerRadius(); - * - * // set - * transformer.anchorCornerRadius(3); - */ -Factory.addGetterSetter( - Transformer, - 'anchorCornerRadius', - 0, - getNumberValidator() -); - -/** - * get/set border stroke color - * @name Konva.Transformer#borderStroke - * @method - * @param {Boolean} enabled - * @returns {Boolean} - * @example - * // get - * var borderStroke = transformer.borderStroke(); - * - * // set - * transformer.borderStroke('red'); - */ -Factory.addGetterSetter(Transformer, 'borderStroke', 'rgb(0, 161, 255)'); - -/** - * get/set border stroke width - * @name Konva.Transformer#borderStrokeWidth - * @method - * @param {Number} strokeWidth - * @returns {Number} - * @example - * // get - * var borderStrokeWidth = transformer.borderStrokeWidth(); - * - * // set - * transformer.borderStrokeWidth(3); - */ -Factory.addGetterSetter( - Transformer, - 'borderStrokeWidth', - 1, - getNumberValidator() -); - -/** - * get/set border dash array - * @name Konva.Transformer#borderDash - * @method - * @param {Array} dash array - * @returns {Array} - * @example - * // get - * var borderDash = transformer.borderDash(); - * - * // set - * transformer.borderDash([2, 2]); - */ -Factory.addGetterSetter(Transformer, 'borderDash'); - -/** - * get/set should we keep ratio while resize anchors at corners - * @name Konva.Transformer#keepRatio - * @method - * @param {Boolean} keepRatio - * @returns {Boolean} - * @example - * // get - * var keepRatio = transformer.keepRatio(); - * - * // set - * transformer.keepRatio(false); - */ -Factory.addGetterSetter(Transformer, 'keepRatio', true); - -/** - * get/set how to react on skift key while resizing anchors at corners - * @name Konva.Transformer#shiftBehavior - * @method - * @param {String} shiftBehavior - * @returns {String} - * @example - * // get - * var shiftBehavior = transformer.shiftBehavior(); - * - * // set - * transformer.shiftBehavior('none'); - */ -Factory.addGetterSetter(Transformer, 'shiftBehavior', 'default'); - -/** - * get/set should we resize relative to node's center? - * @name Konva.Transformer#centeredScaling - * @method - * @param {Boolean} centeredScaling - * @returns {Boolean} - * @example - * // get - * var centeredScaling = transformer.centeredScaling(); - * - * // set - * transformer.centeredScaling(true); - */ -Factory.addGetterSetter(Transformer, 'centeredScaling', false); - -/** - * get/set should we think about stroke while resize? Good to use when a shape has strokeScaleEnabled = false - * default is false - * @name Konva.Transformer#ignoreStroke - * @method - * @param {Boolean} ignoreStroke - * @returns {Boolean} - * @example - * // get - * var ignoreStroke = transformer.ignoreStroke(); - * - * // set - * transformer.ignoreStroke(true); - */ -Factory.addGetterSetter(Transformer, 'ignoreStroke', false); - -/** - * get/set padding - * @name Konva.Transformer#padding - * @method - * @param {Number} padding - * @returns {Number} - * @example - * // get - * var padding = transformer.padding(); - * - * // set - * transformer.padding(10); - */ -Factory.addGetterSetter(Transformer, 'padding', 0, getNumberValidator()); - -/** - * get/set attached nodes of the Transformer. Transformer will adapt to their size and listen to their events - * @method - * @name Konva.Transformer#nodes - * @returns {Konva.Node} - * @example - * // get - * const nodes = transformer.nodes(); - * - * // set - * transformer.nodes([rect, circle]); - * - * // push new item: - * const oldNodes = transformer.nodes(); - * const newNodes = oldNodes.concat([newShape]); - * // it is important to set new array instance (and concat method above will create it) - * transformer.nodes(newNodes); - */ - -Factory.addGetterSetter(Transformer, 'nodes'); -// @ts-ignore -// deprecated -Factory.addGetterSetter(Transformer, 'node'); - -/** - * get/set bounding box function. **IMPORTANT!** boundBondFunc operates in absolute coordinates. - * @name Konva.Transformer#boundBoxFunc - * @method - * @param {Function} func - * @returns {Function} - * @example - * // get - * var boundBoxFunc = transformer.boundBoxFunc(); - * - * // set - * transformer.boundBoxFunc(function(oldBox, newBox) { - * // width and height of the boxes are corresponding to total absolute width and height of all nodes combined - * // so it includes scale of the node. - * if (newBox.width > 200) { - * return oldBox; - * } - * return newBox; - * }); - */ -Factory.addGetterSetter(Transformer, 'boundBoxFunc'); - -/** - * get/set dragging func for transformer anchors - * @name Konva.Transformer#anchorDragBoundFunc - * @method - * @param {Function} func - * @returns {Function} - * @example - * // get - * var anchorDragBoundFunc = transformer.anchorDragBoundFunc(); - * - * // set - * transformer.anchorDragBoundFunc(function(oldAbsPos, newAbsPos, event) { - * return { - * x: 0, - * y: newAbsolutePosition.y - * } - * }); - */ -Factory.addGetterSetter(Transformer, 'anchorDragBoundFunc'); - -/** - * get/set styling function for transformer anchors to overwrite default styles - * @name Konva.Transformer#anchorStyleFunc - * @method - * @param {Function} func - * @returns {Function} - * @example - * // get - * var anchorStyleFunc = transformer.anchorStyleFunc(); - * - * // set - * transformer.anchorStyleFunc(function(anchor) { - * // anchor is a simple Konva.Rect instance - * // it will be executed AFTER all attributes are set, like 'anchorStrokeWidth' or 'anchorFill' - * if (anchor.hasName('.rotater')) { - * // make rotater anchor filled black and looks like a circle - * anchor.fill('black'); - * anchor.cornerRadius(anchor.width() / 2); - * } - * }); - */ -Factory.addGetterSetter(Transformer, 'anchorStyleFunc'); - -/** - * using this setting you can drag transformer group by dragging empty space between attached nodes - * shouldOverdrawWholeArea = true may temporary disable all events on attached nodes - * @name Konva.Transformer#shouldOverdrawWholeArea - * @method - * @param {Boolean} shouldOverdrawWholeArea - * @returns {Boolean} - * @example - * // get - * var shouldOverdrawWholeArea = transformer.shouldOverdrawWholeArea(); - * - * // set - * transformer.shouldOverdrawWholeArea(true); - */ -Factory.addGetterSetter(Transformer, 'shouldOverdrawWholeArea', false); - -/** - * If you have just one attached node to Transformer it will set its initial rotation to the rotation of that node. - * In some cases you may need to set a different rotation. - * @name Konva.Transformer#useSingleNodeRotation - * @method - * @param {Boolean} useSingleNodeRotation - * @returns {Boolean} - * @example - * // set flag to false - * transformer.useSingleNodeRotation(false); - * // attach a shape - * transformer.nodes([shape]); - * transformer.rotation(45); - * transformer.update(); - */ -Factory.addGetterSetter(Transformer, 'useSingleNodeRotation', true); - -Factory.backCompat(Transformer, { - lineEnabled: 'borderEnabled', - rotateHandlerOffset: 'rotateAnchorOffset', - enabledHandlers: 'enabledAnchors', -}); diff --git a/src/shapes/Wedge.ts b/src/shapes/Wedge.ts deleted file mode 100644 index 1a312961a..000000000 --- a/src/shapes/Wedge.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { Factory } from '../Factory'; -import { Context } from '../Context'; -import { Shape, ShapeConfig } from '../Shape'; -import { Konva } from '../Global'; -import { getNumberValidator } from '../Validators'; -import { _registerNode } from '../Global'; - -import { GetSet } from '../types'; - -export interface WedgeConfig extends ShapeConfig { - angle: number; - radius: number; - clockwise?: boolean; -} - -/** - * Wedge constructor - * @constructor - * @memberof Konva - * @augments Konva.Shape - * @param {Object} config - * @param {Number} config.angle in degrees - * @param {Number} config.radius - * @param {Boolean} [config.clockwise] - * @@shapeParams - * @@nodeParams - * @example - * // draw a wedge that's pointing downwards - * var wedge = new Konva.Wedge({ - * radius: 40, - * fill: 'red', - * stroke: 'black' - * strokeWidth: 5, - * angleDeg: 60, - * rotationDeg: -120 - * }); - */ -export class Wedge extends Shape { - _sceneFunc(context: Context) { - context.beginPath(); - context.arc( - 0, - 0, - this.radius(), - 0, - Konva.getAngle(this.angle()), - this.clockwise() - ); - context.lineTo(0, 0); - context.closePath(); - context.fillStrokeShape(this); - } - getWidth() { - return this.radius() * 2; - } - getHeight() { - return this.radius() * 2; - } - setWidth(width: number) { - this.radius(width / 2); - } - setHeight(height: number) { - this.radius(height / 2); - } - - radius: GetSet; - angle: GetSet; - clockwise: GetSet; -} - -Wedge.prototype.className = 'Wedge'; -Wedge.prototype._centroid = true; -Wedge.prototype._attrsAffectingSize = ['radius']; -_registerNode(Wedge); - -/** - * get/set radius - * @name Konva.Wedge#radius - * @method - * @param {Number} radius - * @returns {Number} - * @example - * // get radius - * var radius = wedge.radius(); - * - * // set radius - * wedge.radius(10); - */ -Factory.addGetterSetter(Wedge, 'radius', 0, getNumberValidator()); - -/** - * get/set angle in degrees - * @name Konva.Wedge#angle - * @method - * @param {Number} angle - * @returns {Number} - * @example - * // get angle - * var angle = wedge.angle(); - * - * // set angle - * wedge.angle(20); - */ -Factory.addGetterSetter(Wedge, 'angle', 0, getNumberValidator()); - -/** - * get/set clockwise flag - * @name Konva.Wedge#clockwise - * @method - * @param {Number} clockwise - * @returns {Number} - * @example - * // get clockwise flag - * var clockwise = wedge.clockwise(); - * - * // draw wedge counter-clockwise - * wedge.clockwise(false); - * - * // draw wedge clockwise - * wedge.clockwise(true); - */ -Factory.addGetterSetter(Wedge, 'clockwise', false); - -Factory.backCompat(Wedge, { - angleDeg: 'angle', - getAngleDeg: 'getAngle', - setAngleDeg: 'setAngle', -}); diff --git a/src/types.ts b/src/types.ts deleted file mode 100644 index 6ce992fed..000000000 --- a/src/types.ts +++ /dev/null @@ -1,84 +0,0 @@ -export interface GetSet { - (): Type; - (v: Type): This; -} - -export interface Vector2d { - x: number; - y: number; -} - -export interface PathSegment { - command: - | 'm' - | 'M' - | 'l' - | 'L' - | 'v' - | 'V' - | 'h' - | 'H' - | 'z' - | 'Z' - | 'c' - | 'C' - | 'q' - | 'Q' - | 't' - | 'T' - | 's' - | 'S' - | 'a' - | 'A'; - start: Vector2d; - points: number[]; - pathLength: number; -} - -export interface IRect { - x: number; - y: number; - width: number; - height: number; -} - -export interface IFrame { - time: number; - timeDiff: number; - lastTime: number; - frameRate: number; -} - -export type AnimationFn = (frame?: IFrame) => boolean | void; - -export enum KonvaNodeEvent { - mouseover = 'mouseover', - mouseout = 'mouseout', - mousemove = 'mousemove', - mouseleave = 'mouseleave', - mouseenter = 'mouseenter', - mousedown = 'mousedown', - mouseup = 'mouseup', - wheel = 'wheel', - contextmenu = 'contextmenu', - click = 'click', - dblclick = 'dblclick', - touchstart = 'touchstart', - touchmove = 'touchmove', - touchend = 'touchend', - tap = 'tap', - dbltap = 'dbltap', - dragstart = 'dragstart', - dragmove = 'dragmove', - dragend = 'dragend', -} - -export interface RGB { - r: number; - g: number; - b: number; -} - -export interface RGBA extends RGB { - a: number; -} diff --git a/test/assets/bunny.png b/test/assets/bunny.png deleted file mode 100644 index 79c31675083b7ffc272a6370bc189360bde484d0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 449 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�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�+*_l`ULa(Dv(Sf&_(SrKeo!6V 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 F{{y?tYry~j diff --git a/test/assets/lion.png b/test/assets/lion.png deleted file mode 100644 index 6bab65392592f20364c33aa4990087011fa4a302..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19826 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 diff --git a/test/assets/scorpion-sprite.png b/test/assets/scorpion-sprite.png deleted file mode 100644 index dd19e647a535b153d6a085c5355e1a479b5f14fc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26550 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 V#VgD7Z=e_*q9CIxT_b50`hT5z_1ORb diff --git a/test/assets/tiger.ts b/test/assets/tiger.ts deleted file mode 100644 index 754f7078e..000000000 --- a/test/assets/tiger.ts +++ /dev/null @@ -1,1313 +0,0 @@ -export default [ - { - fill: '#ffffff', - stroke: '#000000', - strokeWidth: 0.172, - data: - 'M-122.304 84.285C-122.304 84.285 -122.203 86.179 -123.027 86.16C-123.851 86.141 -140.305 38.066 -160.833 40.309C-160.833 40.309 -143.05 32.956 -122.304 84.285z', - }, - { - fill: '#ffffff', - stroke: '#000000', - strokeWidth: 0.172, - data: - 'M-118.774 81.262C-118.774 81.262 -119.323 83.078 -120.092 82.779C-120.86 82.481 -119.977 31.675 -140.043 26.801C-140.043 26.801 -120.82 25.937 -118.774 81.262z', - }, - { - fill: '#ffffff', - stroke: '#000000', - strokeWidth: 0.172, - data: - 'M-91.284 123.59C-91.284 123.59 -89.648 124.55 -90.118 125.227C-90.589 125.904 -139.763 113.102 -149.218 131.459C-149.218 131.459 -145.539 112.572 -91.284 123.59z', - }, - { - fill: '#ffffff', - stroke: '#000000', - strokeWidth: 0.172, - data: - 'M-94.093 133.801C-94.093 133.801 -92.237 134.197 -92.471 134.988C-92.704 135.779 -143.407 139.121 -146.597 159.522C-146.597 159.522 -149.055 140.437 -94.093 133.801z', - }, - { - fill: '#ffffff', - stroke: '#000000', - strokeWidth: 0.172, - data: - 'M-98.304 128.276C-98.304 128.276 -96.526 128.939 -96.872 129.687C-97.218 130.435 -147.866 126.346 -153.998 146.064C-153.998 146.064 -153.646 126.825 -98.304 128.276z', - }, - { - fill: '#ffffff', - stroke: '#000000', - strokeWidth: 0.172, - data: - 'M-109.009 110.072C-109.009 110.072 -107.701 111.446 -108.34 111.967C-108.979 112.488 -152.722 86.634 -166.869 101.676C-166.869 101.676 -158.128 84.533 -109.009 110.072z', - }, - { - fill: '#ffffff', - stroke: '#000000', - strokeWidth: 0.172, - data: - 'M-116.554 114.263C-116.554 114.263 -115.098 115.48 -115.674 116.071C-116.25 116.661 -162.638 95.922 -174.992 112.469C-174.992 112.469 -168.247 94.447 -116.554 114.263z', - }, - { - fill: '#ffffff', - stroke: '#000000', - strokeWidth: 0.172, - data: - 'M-119.154 118.335C-119.154 118.335 -117.546 119.343 -118.036 120.006C-118.526 120.669 -167.308 106.446 -177.291 124.522C-177.291 124.522 -173.066 105.749 -119.154 118.335z', - }, - { - fill: '#ffffff', - stroke: '#000000', - strokeWidth: 0.172, - data: - 'M-108.42 118.949C-108.42 118.949 -107.298 120.48 -107.999 120.915C-108.7 121.35 -148.769 90.102 -164.727 103.207C-164.727 103.207 -153.862 87.326 -108.42 118.949z', - }, - { - fill: '#ffffff', - stroke: '#000000', - strokeWidth: 0.172, - data: - 'M-128.2 90C-128.2 90 -127.6 91.8 -128.4 92C-129.2 92.2 -157.8 50.2 -177.001 57.8C-177.001 57.8 -161.8 46 -128.2 90z', - }, - { - fill: '#ffffff', - stroke: '#000000', - strokeWidth: 0.172, - data: - 'M-127.505 96.979C-127.505 96.979 -126.53 98.608 -127.269 98.975C-128.007 99.343 -164.992 64.499 -182.101 76.061C-182.101 76.061 -169.804 61.261 -127.505 96.979z', - }, - { - fill: '#ffffff', - stroke: '#000000', - strokeWidth: 0.172, - data: - 'M-127.62 101.349C-127.62 101.349 -126.498 102.88 -127.199 103.315C-127.9 103.749 -167.969 72.502 -183.927 85.607C-183.927 85.607 -173.062 69.726 -127.62 101.349z', - }, - { - fill: '#ffffff', - stroke: '#000000', - data: - 'M-129.83 103.065C-129.327 109.113 -128.339 115.682 -126.6 118.801C-126.6 118.801 -130.2 131.201 -121.4 144.401C-121.4 144.401 -121.8 151.601 -120.2 154.801C-120.2 154.801 -116.2 163.201 -111.4 164.001C-107.516 164.648 -98.793 167.717 -88.932 169.121C-88.932 169.121 -71.8 183.201 -75 196.001C-75 196.001 -75.4 212.401 -79 214.001C-79 214.001 -67.4 202.801 -77 219.601L-81.4 238.401C-81.4 238.401 -55.8 216.801 -71.4 235.201L-81.4 261.201C-81.4 261.201 -61.8 242.801 -69 251.201L-72.2 260.001C-72.2 260.001 -29 232.801 -59.8 262.401C-59.8 262.401 -51.8 258.801 -47.4 261.601C-47.4 261.601 -40.6 260.401 -41.4 262.001C-41.4 262.001 -62.2 272.401 -65.8 290.801C-65.8 290.801 -57.4 280.801 -60.6 291.601L-60.2 303.201C-60.2 303.201 -56.2 281.601 -56.6 319.201C-56.6 319.201 -37.4 301.201 -49 322.001L-49 338.801C-49 338.801 -33.8 322.401 -40.2 335.201C-40.2 335.201 -30.2 326.401 -34.2 341.601C-34.2 341.601 -35 352.001 -30.6 340.801C-30.6 340.801 -14.6 310.201 -20.6 336.401C-20.6 336.401 -21.4 355.601 -16.6 340.801C-16.6 340.801 -16.2 351.201 -7 358.401C-7 358.401 -8.2 307.601 4.6 343.601L8.6 360.001C8.6 360.001 11.4 350.801 11 345.601C11 345.601 25.8 329.201 19 353.601C19 353.601 34.2 330.801 31 344.001C31 344.001 23.4 360.001 25 364.801C25 364.801 41.8 330.001 43 328.401C43 328.401 41 370.802 51.8 334.801C51.8 334.801 57.4 346.801 54.6 351.201C54.6 351.201 62.6 343.201 61.8 340.001C61.8 340.001 66.4 331.801 69.2 345.401C69.2 345.401 71 354.801 72.6 351.601C72.6 351.601 76.6 375.602 77.8 352.801C77.8 352.801 79.4 339.201 72.2 327.601C72.2 327.601 73 324.401 70.2 320.401C70.2 320.401 83.8 342.001 76.6 313.201C76.6 313.201 87.801 321.201 89.001 321.201C89.001 321.201 75.4 298.001 84.2 302.801C84.2 302.801 79 292.401 97.001 304.401C97.001 304.401 81 288.401 98.601 298.001C98.601 298.001 106.601 304.401 99.001 294.401C99.001 294.401 84.6 278.401 106.601 296.401C106.601 296.401 118.201 312.801 119.001 315.601C119.001 315.601 109.001 286.401 104.601 283.601C104.601 283.601 113.001 247.201 154.201 262.801C154.201 262.801 161.001 280.001 165.401 261.601C165.401 261.601 178.201 255.201 189.401 282.801C189.401 282.801 193.401 269.201 192.601 266.401C192.601 266.401 199.401 267.601 198.601 266.401C198.601 266.401 211.801 270.801 213.001 270.001C213.001 270.001 219.801 276.801 220.201 273.201C220.201 273.201 229.401 276.001 227.401 272.401C227.401 272.401 236.201 288.001 236.601 291.601L239.001 277.601L241.001 280.401C241.001 280.401 242.601 272.801 241.801 271.601C241.001 270.401 261.801 278.401 266.601 299.201L268.601 307.601C268.601 307.601 274.601 292.801 273.001 288.801C273.001 288.801 278.201 289.601 278.601 294.001C278.601 294.001 282.601 270.801 277.801 264.801C277.801 264.801 282.201 264.001 283.401 267.601L283.401 260.401C283.401 260.401 290.601 261.201 290.601 258.801C290.601 258.801 295.001 254.801 297.001 259.601C297.001 259.601 284.601 224.401 303.001 243.601C303.001 243.601 310.201 254.401 306.601 235.601C303.001 216.801 299.001 215.201 303.801 214.801C303.801 214.801 304.601 211.201 302.601 209.601C300.601 208.001 303.801 209.601 303.801 209.601C303.801 209.601 308.601 213.601 303.401 191.601C303.401 191.601 309.801 193.201 297.801 164.001C297.801 164.001 300.601 161.601 296.601 153.201C296.601 153.201 304.601 157.601 307.401 156.001C307.401 156.001 307.001 154.401 303.801 150.401C303.801 150.401 282.201 95.6 302.601 117.601C302.601 117.601 314.451 131.151 308.051 108.351C308.051 108.351 298.94 84.341 299.717 80.045L-129.83 103.065z', - }, - { - fill: '#cc7226', - stroke: '#000000', - data: - 'M299.717 80.245C300.345 80.426 302.551 81.55 303.801 83.2C303.801 83.2 310.601 94 305.401 75.6C305.401 75.6 296.201 46.8 305.001 58C305.001 58 311.001 65.2 307.801 51.6C303.936 35.173 301.401 28.8 301.401 28.8C301.401 28.8 313.001 33.6 286.201 -6L295.001 -2.4C295.001 -2.4 275.401 -42 253.801 -47.2L245.801 -53.2C245.801 -53.2 284.201 -91.2 271.401 -128C271.401 -128 264.601 -133.2 255.001 -124C255.001 -124 248.601 -119.2 242.601 -120.8C242.601 -120.8 211.801 -119.6 209.801 -119.6C207.801 -119.6 173.001 -156.8 107.401 -139.2C107.401 -139.2 102.201 -137.2 97.801 -138.4C97.801 -138.4 79.4 -154.4 30.6 -131.6C30.6 -131.6 20.6 -129.6 19 -129.6C17.4 -129.6 14.6 -129.6 6.6 -123.2C-1.4 -116.8 -1.8 -116 -3.8 -114.4C-3.8 -114.4 -20.2 -103.2 -25 -102.4C-25 -102.4 -36.6 -96 -41 -86L-44.6 -84.8C-44.6 -84.8 -46.2 -77.6 -46.6 -76.4C-46.6 -76.4 -51.4 -72.8 -52.2 -67.2C-52.2 -67.2 -61 -61.2 -60.6 -56.8C-60.6 -56.8 -62.2 -51.6 -63 -46.8C-63 -46.8 -70.2 -42 -69.4 -39.2C-69.4 -39.2 -77 -25.2 -75.8 -18.4C-75.8 -18.4 -82.2 -18.8 -85 -16.4C-85 -16.4 -85.8 -11.6 -87.4 -11.2C-87.4 -11.2 -90.2 -10 -87.8 -6C-87.8 -6 -89.4 -3.2 -89.8 -1.6C-89.8 -1.6 -89 1.2 -93.4 6.8C-93.4 6.8 -99.8 25.6 -97.8 30.8C-97.8 30.8 -97.4 35.6 -100.2 37.2C-100.2 37.2 -103.8 36.8 -95.4 48.8C-95.4 48.8 -94.6 50 -97.8 52.4C-97.8 52.4 -115 56 -117.4 72.4C-117.4 72.4 -131 87.2 -131 92.4C-131 94.705 -130.729 97.852 -130.03 102.465C-130.03 102.465 -130.6 110.801 -103 111.601C-75.4 112.401 299.717 80.245 299.717 80.245z', - }, - { - fill: '#cc7226', - data: - 'M-115.6 102.6C-140.6 63.2 -126.2 119.601 -126.2 119.601C-117.4 154.001 12.2 116.401 12.2 116.401C12.2 116.401 181.001 86 192.201 82C203.401 78 298.601 84.4 298.601 84.4L293.001 67.6C228.201 21.2 209.001 44.4 195.401 40.4C181.801 36.4 184.201 46 181.001 46.8C177.801 47.6 138.601 22.8 132.201 23.6C125.801 24.4 100.459 0.649 115.401 32.4C131.401 66.4 57 71.6 40.2 60.4C23.4 49.2 47.4 78.8 47.4 78.8C65.8 98.8 31.4 82 31.4 82C-3 69.2 -27 94.8 -30.2 95.6C-33.4 96.4 -38.2 99.6 -39 93.2C-39.8 86.8 -47.31 70.099 -79 96.4C-99 113.001 -112.8 91 -112.8 91L-115.6 102.6z', - }, - { - fill: '#e87f3a', - data: - 'M133.51 25.346C127.11 26.146 101.743 2.407 116.71 34.146C133.31 69.346 58.31 73.346 41.51 62.146C24.709 50.946 48.71 80.546 48.71 80.546C67.11 100.546 32.709 83.746 32.709 83.746C-1.691 70.946 -25.691 96.546 -28.891 97.346C-32.091 98.146 -36.891 101.346 -37.691 94.946C-38.491 88.546 -45.87 72.012 -77.691 98.146C-98.927 115.492 -112.418 94.037 -112.418 94.037L-115.618 104.146C-140.618 64.346 -125.546 122.655 -125.546 122.655C-116.745 157.056 13.509 118.146 13.509 118.146C13.509 118.146 182.31 87.746 193.51 83.746C204.71 79.746 299.038 86.073 299.038 86.073L293.51 68.764C228.71 22.364 210.31 46.146 196.71 42.146C183.11 38.146 185.51 47.746 182.31 48.546C179.11 49.346 139.91 24.546 133.51 25.346z', - }, - { - fill: '#ea8c4d', - data: - 'M134.819 27.091C128.419 27.891 103.685 3.862 118.019 35.891C134.219 72.092 59.619 75.092 42.819 63.892C26.019 52.692 50.019 82.292 50.019 82.292C68.419 102.292 34.019 85.492 34.019 85.492C-0.381 72.692 -24.382 98.292 -27.582 99.092C-30.782 99.892 -35.582 103.092 -36.382 96.692C-37.182 90.292 -44.43 73.925 -76.382 99.892C-98.855 117.983 -112.036 97.074 -112.036 97.074L-115.636 105.692C-139.436 66.692 -124.891 125.71 -124.891 125.71C-116.091 160.11 14.819 119.892 14.819 119.892C14.819 119.892 183.619 89.492 194.819 85.492C206.019 81.492 299.474 87.746 299.474 87.746L294.02 69.928C229.219 23.528 211.619 47.891 198.019 43.891C184.419 39.891 186.819 49.491 183.619 50.292C180.419 51.092 141.219 26.291 134.819 27.091z', - }, - { - fill: '#ec9961', - data: - 'M136.128 28.837C129.728 29.637 104.999 5.605 119.328 37.637C136.128 75.193 60.394 76.482 44.128 65.637C27.328 54.437 51.328 84.037 51.328 84.037C69.728 104.037 35.328 87.237 35.328 87.237C0.928 74.437 -23.072 100.037 -26.272 100.837C-29.472 101.637 -34.272 104.837 -35.072 98.437C-35.872 92.037 -42.989 75.839 -75.073 101.637C-98.782 120.474 -111.655 100.11 -111.655 100.11L-115.655 107.237C-137.455 70.437 -124.236 128.765 -124.236 128.765C-115.436 163.165 16.128 121.637 16.128 121.637C16.128 121.637 184.928 91.237 196.129 87.237C207.329 83.237 299.911 89.419 299.911 89.419L294.529 71.092C229.729 24.691 212.929 49.637 199.329 45.637C185.728 41.637 188.128 51.237 184.928 52.037C181.728 52.837 142.528 28.037 136.128 28.837z', - }, - { - fill: '#eea575', - data: - 'M137.438 30.583C131.037 31.383 106.814 7.129 120.637 39.383C137.438 78.583 62.237 78.583 45.437 67.383C28.637 56.183 52.637 85.783 52.637 85.783C71.037 105.783 36.637 88.983 36.637 88.983C2.237 76.183 -21.763 101.783 -24.963 102.583C-28.163 103.383 -32.963 106.583 -33.763 100.183C-34.563 93.783 -41.548 77.752 -73.763 103.383C-98.709 122.965 -111.273 103.146 -111.273 103.146L-115.673 108.783C-135.473 73.982 -123.582 131.819 -123.582 131.819C-114.782 166.22 17.437 123.383 17.437 123.383C17.437 123.383 186.238 92.983 197.438 88.983C208.638 84.983 300.347 91.092 300.347 91.092L295.038 72.255C230.238 25.855 214.238 51.383 200.638 47.383C187.038 43.383 189.438 52.983 186.238 53.783C183.038 54.583 143.838 29.783 137.438 30.583z', - }, - { - fill: '#f1b288', - data: - 'M138.747 32.328C132.347 33.128 106.383 9.677 121.947 41.128C141.147 79.928 63.546 80.328 46.746 69.128C29.946 57.928 53.946 87.528 53.946 87.528C72.346 107.528 37.946 90.728 37.946 90.728C3.546 77.928 -20.454 103.528 -23.654 104.328C-26.854 105.128 -31.654 108.328 -32.454 101.928C-33.254 95.528 -40.108 79.665 -72.454 105.128C-98.636 125.456 -110.891 106.183 -110.891 106.183L-115.691 110.328C-133.691 77.128 -122.927 134.874 -122.927 134.874C-114.127 169.274 18.746 125.128 18.746 125.128C18.746 125.128 187.547 94.728 198.747 90.728C209.947 86.728 300.783 92.764 300.783 92.764L295.547 73.419C230.747 27.019 215.547 53.128 201.947 49.128C188.347 45.128 190.747 54.728 187.547 55.528C184.347 56.328 145.147 31.528 138.747 32.328z', - }, - { - fill: '#f3bf9c', - data: - 'M140.056 34.073C133.655 34.873 107.313 11.613 123.255 42.873C143.656 82.874 64.855 82.074 48.055 70.874C31.255 59.674 55.255 89.274 55.255 89.274C73.655 109.274 39.255 92.474 39.255 92.474C4.855 79.674 -19.145 105.274 -22.345 106.074C-25.545 106.874 -30.345 110.074 -31.145 103.674C-31.945 97.274 -38.668 81.578 -71.145 106.874C-98.564 127.947 -110.509 109.219 -110.509 109.219L-115.709 111.874C-131.709 81.674 -122.273 137.929 -122.273 137.929C-113.473 172.329 20.055 126.874 20.055 126.874C20.055 126.874 188.856 96.474 200.056 92.474C211.256 88.474 301.22 94.437 301.22 94.437L296.056 74.583C231.256 28.183 216.856 54.874 203.256 50.874C189.656 46.873 192.056 56.474 188.856 57.274C185.656 58.074 146.456 33.273 140.056 34.073z', - }, - { - fill: '#f5ccb0', - data: - 'M141.365 35.819C134.965 36.619 107.523 13.944 124.565 44.619C146.565 84.219 66.164 83.819 49.364 72.619C32.564 61.419 56.564 91.019 56.564 91.019C74.964 111.019 40.564 94.219 40.564 94.219C6.164 81.419 -17.836 107.019 -21.036 107.819C-24.236 108.619 -29.036 111.819 -29.836 105.419C-30.636 99.019 -37.227 83.492 -69.836 108.619C-98.491 130.438 -110.127 112.256 -110.127 112.256L-115.727 113.419C-130.128 85.019 -121.618 140.983 -121.618 140.983C-112.818 175.384 21.364 128.619 21.364 128.619C21.364 128.619 190.165 98.219 201.365 94.219C212.565 90.219 301.656 96.11 301.656 96.11L296.565 75.746C231.765 29.346 218.165 56.619 204.565 52.619C190.965 48.619 193.365 58.219 190.165 59.019C186.965 59.819 147.765 35.019 141.365 35.819z', - }, - { - fill: '#f8d8c4', - data: - 'M142.674 37.565C136.274 38.365 108.832 15.689 125.874 46.365C147.874 85.965 67.474 85.565 50.674 74.365C33.874 63.165 57.874 92.765 57.874 92.765C76.274 112.765 41.874 95.965 41.874 95.965C7.473 83.165 -16.527 108.765 -19.727 109.565C-22.927 110.365 -27.727 113.565 -28.527 107.165C-29.327 100.765 -35.786 85.405 -68.527 110.365C-98.418 132.929 -109.745 115.293 -109.745 115.293L-115.745 114.965C-129.346 88.564 -120.963 144.038 -120.963 144.038C-112.163 178.438 22.673 130.365 22.673 130.365C22.673 130.365 191.474 99.965 202.674 95.965C213.874 91.965 302.093 97.783 302.093 97.783L297.075 76.91C232.274 30.51 219.474 58.365 205.874 54.365C192.274 50.365 194.674 59.965 191.474 60.765C188.274 61.565 149.074 36.765 142.674 37.565z', - }, - { - fill: '#fae5d7', - data: - 'M143.983 39.31C137.583 40.11 110.529 17.223 127.183 48.11C149.183 88.91 68.783 87.31 51.983 76.11C35.183 64.91 59.183 94.51 59.183 94.51C77.583 114.51 43.183 97.71 43.183 97.71C8.783 84.91 -15.217 110.51 -18.417 111.31C-21.618 112.11 -26.418 115.31 -27.218 108.91C-28.018 102.51 -34.346 87.318 -67.218 112.11C-98.345 135.42 -109.363 118.329 -109.363 118.329L-115.764 116.51C-128.764 92.51 -120.309 147.093 -120.309 147.093C-111.509 181.493 23.983 132.11 23.983 132.11C23.983 132.11 192.783 101.71 203.983 97.71C215.183 93.71 302.529 99.456 302.529 99.456L297.583 78.074C232.783 31.673 220.783 60.11 207.183 56.11C193.583 52.11 195.983 61.71 192.783 62.51C189.583 63.31 150.383 38.51 143.983 39.31z', - }, - { - fill: '#fcf2eb', - data: - 'M145.292 41.055C138.892 41.855 112.917 18.411 128.492 49.855C149.692 92.656 70.092 89.056 53.292 77.856C36.492 66.656 60.492 96.256 60.492 96.256C78.892 116.256 44.492 99.456 44.492 99.456C10.092 86.656 -13.908 112.256 -17.108 113.056C-20.308 113.856 -25.108 117.056 -25.908 110.656C-26.708 104.256 -32.905 89.232 -65.908 113.856C-98.273 137.911 -108.982 121.365 -108.982 121.365L-115.782 118.056C-128.582 94.856 -119.654 150.147 -119.654 150.147C-110.854 184.547 25.292 133.856 25.292 133.856C25.292 133.856 194.093 103.456 205.293 99.456C216.493 95.456 302.965 101.128 302.965 101.128L298.093 79.237C233.292 32.837 222.093 61.856 208.493 57.856C194.893 53.855 197.293 63.456 194.093 64.256C190.892 65.056 151.692 40.255 145.292 41.055z', - }, - { - fill: '#ffffff', - data: - 'M-115.8 119.601C-128.6 97.6 -119 153.201 -119 153.201C-110.2 187.601 26.6 135.601 26.6 135.601C26.6 135.601 195.401 105.2 206.601 101.2C217.801 97.2 303.401 102.8 303.401 102.8L298.601 80.4C233.801 34 223.401 63.6 209.801 59.6C196.201 55.6 198.601 65.2 195.401 66C192.201 66.8 153.001 42 146.601 42.8C140.201 43.6 114.981 19.793 129.801 51.6C152.028 99.307 69.041 89.227 54.6 79.6C37.8 68.4 61.8 98 61.8 98C80.2 118.001 45.8 101.2 45.8 101.2C11.4 88.4 -12.6 114.001 -15.8 114.801C-19 115.601 -23.8 118.801 -24.6 112.401C-25.4 106 -31.465 91.144 -64.6 115.601C-98.2 140.401 -108.6 124.401 -108.6 124.401L-115.8 119.601z', - }, - { - fill: '#000000', - data: - 'M-74.2 149.601C-74.2 149.601 -81.4 161.201 -60.6 174.401C-60.6 174.401 -59.2 175.801 -77.2 171.601C-77.2 171.601 -83.4 169.601 -85 159.201C-85 159.201 -89.8 154.801 -94.6 149.201C-99.4 143.601 -74.2 149.601 -74.2 149.601z', - }, - { - fill: '#cccccc', - data: - 'M65.8 102C65.8 102 83.498 128.821 82.9 133.601C81.6 144.001 81.4 153.601 84.6 157.601C87.801 161.601 96.601 194.801 96.601 194.801C96.601 194.801 96.201 196.001 108.601 158.001C108.601 158.001 120.201 142.001 100.201 123.601C100.201 123.601 65 94.8 65.8 102z', - }, - { - fill: '#000000', - data: - 'M-54.2 176.401C-54.2 176.401 -43 183.601 -57.4 214.801L-51 212.401C-51 212.401 -51.8 223.601 -55 226.001L-47.8 222.801C-47.8 222.801 -43 230.801 -47 235.601C-47 235.601 -30.2 243.601 -31 250.001C-31 250.001 -24.6 242.001 -28.6 235.601C-32.6 229.201 -39.8 233.201 -39 214.801L-47.8 218.001C-47.8 218.001 -42.2 209.201 -42.2 202.801L-50.2 205.201C-50.2 205.201 -34.731 178.623 -45.4 177.201C-51.4 176.401 -54.2 176.401 -54.2 176.401z', - }, - { - fill: '#cccccc', - data: - 'M-21.8 193.201C-21.8 193.201 -19 188.801 -21.8 189.601C-24.6 190.401 -55.8 205.201 -61.8 214.801C-61.8 214.801 -27.4 190.401 -21.8 193.201z', - }, - { - fill: '#cccccc', - data: - 'M-11.4 201.201C-11.4 201.201 -8.6 196.801 -11.4 197.601C-14.2 198.401 -45.4 213.201 -51.4 222.801C-51.4 222.801 -17 198.401 -11.4 201.201z', - }, - { - fill: '#cccccc', - data: - 'M1.8 186.001C1.8 186.001 4.6 181.601 1.8 182.401C-1 183.201 -32.2 198.001 -38.2 207.601C-38.2 207.601 -3.8 183.201 1.8 186.001z', - }, - { - fill: '#cccccc', - data: - 'M-21.4 229.601C-21.4 229.601 -21.4 223.601 -24.2 224.401C-27 225.201 -63 242.801 -69 252.401C-69 252.401 -27 226.801 -21.4 229.601z', - }, - { - fill: '#cccccc', - data: - 'M-20.2 218.801C-20.2 218.801 -19 214.001 -21.8 214.801C-23.8 214.801 -50.2 226.401 -56.2 236.001C-56.2 236.001 -26.6 214.401 -20.2 218.801z', - }, - { - fill: '#cccccc', - data: - 'M-34.6 266.401L-44.6 274.001C-44.6 274.001 -34.2 266.401 -30.6 267.601C-30.6 267.601 -37.4 278.801 -38.2 284.001C-38.2 284.001 -27.8 271.201 -22.2 271.601C-22.2 271.601 -14.6 272.001 -14.6 282.801C-14.6 282.801 -9 272.401 -5.8 272.801C-5.8 272.801 -4.6 279.201 -5.8 286.001C-5.8 286.001 -1.8 278.401 2.2 280.001C2.2 280.001 8.6 278.001 7.8 289.601C7.8 289.601 7.8 300.001 7 302.801C7 302.801 12.6 276.401 15 276.001C15 276.001 23 274.801 27.8 283.601C27.8 283.601 23.8 276.001 28.6 278.001C28.6 278.001 39.4 279.601 42.6 286.401C42.6 286.401 35.8 274.401 41.4 277.601C41.4 277.601 48.2 277.601 49.4 284.001C49.4 284.001 57.8 305.201 59.8 306.801C59.8 306.801 52.2 285.201 53.8 285.201C53.8 285.201 51.8 273.201 57 288.001C57 288.001 53.8 274.001 59.4 274.801C65 275.601 69.4 285.601 77.8 283.201C77.8 283.201 87.401 288.801 89.401 219.601L-34.6 266.401z', - }, - { - fill: '#000000', - data: - 'M-29.8 173.601C-29.8 173.601 -15 167.601 25 173.601C25 173.601 32.2 174.001 39 165.201C45.8 156.401 72.6 149.201 79 151.201L88.601 157.601L89.401 158.801C89.401 158.801 101.801 169.201 102.201 176.801C102.601 184.401 87.801 232.401 78.2 248.401C68.6 264.401 59 276.801 39.8 274.401C39.8 274.401 19 270.401 -6.6 274.401C-6.6 274.401 -35.8 272.801 -38.6 264.801C-41.4 256.801 -27.4 241.601 -27.4 241.601C-27.4 241.601 -23 233.201 -24.2 218.801C-25.4 204.401 -25 176.401 -29.8 173.601z', - }, - { - fill: '#e5668c', - data: - 'M-7.8 175.601C0.6 194.001 -29 259.201 -29 259.201C-31 260.801 -16.34 266.846 -6.2 264.401C4.746 261.763 45 266.001 45 266.001C68.6 250.401 81.4 206.001 81.4 206.001C81.4 206.001 91.801 182.001 74.2 178.801C56.6 175.601 -7.8 175.601 -7.8 175.601z', - }, - { - fill: '#b23259', - data: - 'M-9.831 206.497C-6.505 193.707 -4.921 181.906 -7.8 175.601C-7.8 175.601 54.6 182.001 65.8 161.201C70.041 153.326 84.801 184.001 84.4 193.601C84.4 193.601 21.4 208.001 6.6 196.801L-9.831 206.497z', - }, - { - fill: '#a5264c', - data: - 'M-5.4 222.801C-5.4 222.801 -3.4 230.001 -5.8 234.001C-5.8 234.001 -7.4 234.801 -8.6 235.201C-8.6 235.201 -7.4 238.801 -1.4 240.401C-1.4 240.401 0.6 244.801 3 245.201C5.4 245.601 10.2 251.201 14.2 250.001C18.2 248.801 29.4 244.801 29.4 244.801C29.4 244.801 35 241.601 43.8 245.201C43.8 245.201 46.175 244.399 46.6 240.401C47.1 235.701 50.2 232.001 52.2 230.001C54.2 228.001 63.8 215.201 62.6 214.801C61.4 214.401 -5.4 222.801 -5.4 222.801z', - }, - { - fill: '#ff727f', - stroke: '#000000', - data: - 'M-9.8 174.401C-9.8 174.401 -12.6 196.801 -9.4 205.201C-6.2 213.601 -7 215.601 -7.8 219.601C-8.6 223.601 -4.2 233.601 1.4 239.601L13.4 241.201C13.4 241.201 28.6 237.601 37.8 240.401C37.8 240.401 46.794 241.744 50.2 226.801C50.2 226.801 55 220.401 62.2 217.601C69.4 214.801 76.6 173.201 72.6 165.201C68.6 157.201 54.2 152.801 38.2 168.401C22.2 184.001 20.2 167.201 -9.8 174.401z', - }, - { - fill: '#ffffcc', - stroke: '#000000', - strokeWidth: 0.5, - data: - 'M-8.2 249.201C-8.2 249.201 -9 247.201 -13.4 246.801C-13.4 246.801 -35.8 243.201 -44.2 230.801C-44.2 230.801 -51 225.201 -46.6 236.801C-46.6 236.801 -36.2 257.201 -29.4 260.001C-29.4 260.001 -13 264.001 -8.2 249.201z', - }, - { - fill: '#cc3f4c', - data: - 'M71.742 185.229C72.401 177.323 74.354 168.709 72.6 165.201C66.154 152.307 49.181 157.695 38.2 168.401C22.2 184.001 20.2 167.201 -9.8 174.401C-9.8 174.401 -11.545 188.364 -10.705 198.376C-10.705 198.376 26.6 186.801 27.4 192.401C27.4 192.401 29 189.201 38.2 189.201C47.4 189.201 70.142 188.029 71.742 185.229z', - }, - { - stroke: '#a51926', - strokeWidth: 2, - data: - 'M28.6 175.201C28.6 175.201 33.4 180.001 29.8 189.601C29.8 189.601 15.4 205.601 17.4 219.601', - }, - { - fill: '#ffffcc', - stroke: '#000000', - strokeWidth: 0.5, - data: - 'M-19.4 260.001C-19.4 260.001 -23.8 247.201 -15 254.001C-15 254.001 -10.2 256.001 -11.4 257.601C-12.6 259.201 -18.2 263.201 -19.4 260.001z', - }, - { - fill: '#ffffcc', - stroke: '#000000', - strokeWidth: 0.5, - data: - 'M-14.36 261.201C-14.36 261.201 -17.88 250.961 -10.84 256.401C-10.84 256.401 -6.419 258.849 -7.96 259.281C-12.52 260.561 -7.96 263.121 -14.36 261.201z', - }, - { - fill: '#ffffcc', - stroke: '#000000', - strokeWidth: 0.5, - data: - 'M-9.56 261.201C-9.56 261.201 -13.08 250.961 -6.04 256.401C-6.04 256.401 -1.665 258.711 -3.16 259.281C-6.52 260.561 -3.16 263.121 -9.56 261.201z', - }, - { - fill: '#ffffcc', - stroke: '#000000', - strokeWidth: 0.5, - data: - 'M-2.96 261.401C-2.96 261.401 -6.48 251.161 0.56 256.601C0.56 256.601 4.943 258.933 3.441 259.481C0.48 260.561 3.441 263.321 -2.96 261.401z', - }, - { - fill: '#ffffcc', - stroke: '#000000', - strokeWidth: 0.5, - data: - 'M3.52 261.321C3.52 261.321 0 251.081 7.041 256.521C7.041 256.521 10.881 258.121 9.921 259.401C8.961 260.681 9.921 263.241 3.52 261.321z', - }, - { - fill: '#ffffcc', - stroke: '#000000', - strokeWidth: 0.5, - data: - 'M10.2 262.001C10.2 262.001 5.4 249.601 14.6 256.001C14.6 256.001 19.4 258.001 18.2 259.601C17 261.201 18.2 264.401 10.2 262.001z', - }, - { - stroke: '#a5264c', - strokeWidth: 2, - data: - 'M-18.2 244.801C-18.2 244.801 -5 242.001 1 245.201C1 245.201 7 246.401 8.2 246.001C9.4 245.601 12.6 245.201 12.6 245.201', - }, - { - stroke: '#a5264c', - strokeWidth: 2, - data: - 'M15.8 253.601C15.8 253.601 27.8 240.001 39.8 244.401C46.816 246.974 45.8 243.601 46.6 240.801C47.4 238.001 47.6 233.801 52.6 230.801', - }, - { - fill: '#ffffcc', - stroke: '#000000', - strokeWidth: 0.5, - data: - 'M33 237.601C33 237.601 29 226.801 26.2 239.601C23.4 252.401 20.2 256.001 18.6 258.801C18.6 258.801 18.6 264.001 27 263.601C27 263.601 37.8 263.201 38.2 260.401C38.6 257.601 37 246.001 33 237.601z', - }, - { - stroke: '#a5264c', - strokeWidth: 2, - data: 'M47 244.801C47 244.801 50.6 242.401 53 243.601', - }, - { - stroke: '#a5264c', - strokeWidth: 2, - data: 'M53.5 228.401C53.5 228.401 56.4 223.501 61.2 222.701', - }, - { - fill: '#b2b2b2', - data: - 'M-25.8 265.201C-25.8 265.201 -7.8 268.401 -3.4 266.801C-3.4 266.801 5.4 266.801 -3 268.801C-3 268.801 -15.8 268.801 -23.8 267.601C-23.8 267.601 -35.4 262.001 -25.8 265.201z', - }, - { - fill: '#ffffcc', - stroke: '#000000;', - strokeWidth: 0.5, - data: - 'M-11.8 172.001C-11.8 172.001 5.8 172.001 7.8 172.801C7.8 172.801 15 203.601 11.4 211.201C11.4 211.201 10.2 214.001 7.4 208.401C7.4 208.401 -11 175.601 -14.2 173.601C-17.4 171.601 -13 172.001 -11.8 172.001z', - }, - { - fill: '#ffffcc', - stroke: '#000000;', - strokeWidth: 0.5, - data: - 'M-88.9 169.301C-88.9 169.301 -80 171.001 -67.4 173.601C-67.4 173.601 -62.6 196.001 -59.4 200.801C-56.2 205.601 -59.8 205.601 -63.4 202.801C-67 200.001 -81.8 186.001 -83.8 181.601C-85.8 177.201 -88.9 169.301 -88.9 169.301z', - }, - { - fill: '#ffffcc', - stroke: '#000000;', - strokeWidth: 0.5, - data: - 'M-67.039 173.818C-67.039 173.818 -61.239 175.366 -60.23 177.581C-59.222 179.795 -61.432 183.092 -61.432 183.092C-61.432 183.092 -62.432 186.397 -63.634 184.235C-64.836 182.072 -67.708 174.412 -67.039 173.818z', - }, - { - fill: '#000000', - data: - 'M-67 173.601C-67 173.601 -63.4 178.801 -59.8 178.801C-56.2 178.801 -55.818 178.388 -53 179.001C-48.4 180.001 -48.8 178.001 -42.2 179.201C-39.56 179.681 -37 178.801 -34.2 180.001C-31.4 181.201 -28.2 180.401 -27 178.401C-25.8 176.401 -21 172.201 -21 172.201C-21 172.201 -33.8 174.001 -36.6 174.801C-36.6 174.801 -59 176.001 -67 173.601z', - }, - { - fill: '#ffffcc', - stroke: '#000000;', - strokeWidth: 0.5, - data: - 'M-22.4 173.801C-22.4 173.801 -28.85 177.301 -29.25 179.701C-29.65 182.101 -24 185.801 -24 185.801C-24 185.801 -21.25 190.401 -20.65 188.001C-20.05 185.601 -21.6 174.201 -22.4 173.801z', - }, - { - fill: '#ffffcc', - stroke: '#000000;', - strokeWidth: 0.5, - data: - 'M-59.885 179.265C-59.885 179.265 -52.878 190.453 -52.661 179.242C-52.661 179.242 -52.104 177.984 -53.864 177.962C-59.939 177.886 -58.418 173.784 -59.885 179.265z', - }, - { - fill: '#ffffcc', - stroke: '#000000;', - strokeWidth: 0.5, - data: - 'M-52.707 179.514C-52.707 179.514 -44.786 190.701 -45.422 179.421C-45.422 179.421 -45.415 179.089 -47.168 178.936C-51.915 178.522 -51.57 174.004 -52.707 179.514z', - }, - { - fill: '#ffffcc', - stroke: '#000000;', - strokeWidth: 0.5, - data: - 'M-45.494 179.522C-45.494 179.522 -37.534 190.15 -38.203 180.484C-38.203 180.484 -38.084 179.251 -39.738 178.95C-43.63 178.244 -43.841 174.995 -45.494 179.522z', - }, - { - fill: '#ffffcc', - stroke: '#000000;', - strokeWidth: 0.5, - data: - 'M-38.618 179.602C-38.618 179.602 -30.718 191.163 -30.37 181.382C-30.37 181.382 -28.726 180.004 -30.472 179.782C-36.29 179.042 -35.492 174.588 -38.618 179.602z', - }, - { - fill: '#e5e5b2', - data: - 'M-74.792 183.132L-82.45 181.601C-85.05 176.601 -87.15 170.451 -87.15 170.451C-87.15 170.451 -80.8 171.451 -68.3 174.251C-68.3 174.251 -67.424 177.569 -65.952 183.364L-74.792 183.132z', - }, - { - fill: '#e5e5b2', - data: - 'M-9.724 178.47C-11.39 175.964 -12.707 174.206 -13.357 173.8C-16.37 171.917 -12.227 172.294 -11.098 172.294C-11.098 172.294 5.473 172.294 7.356 173.047C7.356 173.047 7.88 175.289 8.564 178.68C8.564 178.68 -1.524 176.67 -9.724 178.47z', - }, - { - fill: '#cc7226', - data: - 'M43.88 40.321C71.601 44.281 97.121 8.641 98.881 -1.04C100.641 -10.72 90.521 -22.6 90.521 -22.6C91.841 -25.68 87.001 -39.76 81.721 -49C76.441 -58.24 60.54 -57.266 43 -58.24C27.16 -59.12 8.68 -35.8 7.36 -34.04C6.04 -32.28 12.2 6.001 13.52 11.721C14.84 17.441 12.2 43.841 12.2 43.841C46.44 34.741 16.16 36.361 43.88 40.321z', - }, - { - fill: '#ea8e51', - data: - 'M8.088 -33.392C6.792 -31.664 12.84 5.921 14.136 11.537C15.432 17.153 12.84 43.073 12.84 43.073C45.512 34.193 16.728 35.729 43.944 39.617C71.161 43.505 96.217 8.513 97.945 -0.992C99.673 -10.496 89.737 -22.16 89.737 -22.16C91.033 -25.184 86.281 -39.008 81.097 -48.08C75.913 -57.152 60.302 -56.195 43.08 -57.152C27.528 -58.016 9.384 -35.12 8.088 -33.392z', - }, - { - fill: '#efaa7c', - data: - 'M8.816 -32.744C7.544 -31.048 13.48 5.841 14.752 11.353C16.024 16.865 13.48 42.305 13.48 42.305C44.884 33.145 17.296 35.097 44.008 38.913C70.721 42.729 95.313 8.385 97.009 -0.944C98.705 -10.272 88.953 -21.72 88.953 -21.72C90.225 -24.688 85.561 -38.256 80.473 -47.16C75.385 -56.064 60.063 -55.125 43.16 -56.064C27.896 -56.912 10.088 -34.44 8.816 -32.744z', - }, - { - fill: '#f4c6a8', - data: - 'M9.544 -32.096C8.296 -30.432 14.12 5.761 15.368 11.169C16.616 16.577 14.12 41.537 14.12 41.537C43.556 32.497 17.864 34.465 44.072 38.209C70.281 41.953 94.409 8.257 96.073 -0.895C97.737 -10.048 88.169 -21.28 88.169 -21.28C89.417 -24.192 84.841 -37.504 79.849 -46.24C74.857 -54.976 59.824 -54.055 43.24 -54.976C28.264 -55.808 10.792 -33.76 9.544 -32.096z', - }, - { - fill: '#f9e2d3', - data: - 'M10.272 -31.448C9.048 -29.816 14.76 5.681 15.984 10.985C17.208 16.289 14.76 40.769 14.76 40.769C42.628 31.849 18.432 33.833 44.136 37.505C69.841 41.177 93.505 8.129 95.137 -0.848C96.769 -9.824 87.385 -20.84 87.385 -20.84C88.609 -23.696 84.121 -36.752 79.225 -45.32C74.329 -53.888 59.585 -52.985 43.32 -53.888C28.632 -54.704 11.496 -33.08 10.272 -31.448z', - }, - { - fill: '#ffffff', - data: - 'M44.2 36.8C69.4 40.4 92.601 8 94.201 -0.8C95.801 -9.6 86.601 -20.4 86.601 -20.4C87.801 -23.2 83.4 -36 78.6 -44.4C73.8 -52.8 59.346 -51.914 43.4 -52.8C29 -53.6 12.2 -32.4 11 -30.8C9.8 -29.2 15.4 5.6 16.6 10.8C17.8 16 15.4 40 15.4 40C40.9 31.4 19 33.2 44.2 36.8z', - }, - { - fill: '#cccccc', - data: - 'M90.601 2.8C90.601 2.8 62.8 10.4 51.2 8.8C51.2 8.8 35.4 2.2 26.6 24C26.6 24 23 31.2 21 33.2C19 35.2 90.601 2.8 90.601 2.8z', - }, - { - fill: '#000000', - data: - 'M94.401 0.6C94.401 0.6 65.4 12.8 55.4 12.4C55.4 12.4 39 7.8 30.6 22.4C30.6 22.4 22.2 31.6 19 33.2C19 33.2 18.6 34.8 25 30.8L35.4 36C35.4 36 50.2 45.6 59.8 29.6C59.8 29.6 63.8 18.4 63.8 16.4C63.8 14.4 85 8.8 86.601 8.4C88.201 8 94.801 3.8 94.401 0.6z', - }, - { - fill: '#99cc32', - data: - 'M47 36.514C40.128 36.514 31.755 32.649 31.755 26.4C31.755 20.152 40.128 13.887 47 13.887C53.874 13.887 59.446 18.952 59.446 25.2C59.446 31.449 53.874 36.514 47 36.514z', - }, - { - fill: '#659900', - data: - 'M43.377 19.83C38.531 20.552 33.442 22.055 33.514 21.839C35.054 17.22 41.415 13.887 47 13.887C51.296 13.887 55.084 15.865 57.32 18.875C57.32 18.875 52.004 18.545 43.377 19.83z', - }, - { - fill: '#ffffff', - data: 'M55.4 19.6C55.4 19.6 51 16.4 51 18.6C51 18.6 54.6 23 55.4 19.6z', - }, - { - fill: '#000000', - data: - 'M45.4 27.726C42.901 27.726 40.875 25.7 40.875 23.2C40.875 20.701 42.901 18.675 45.4 18.675C47.9 18.675 49.926 20.701 49.926 23.2C49.926 25.7 47.9 27.726 45.4 27.726z', - }, - { - fill: '#cc7226', - data: - 'M-58.6 14.4C-58.6 14.4 -61.8 -6.8 -59.4 -11.2C-59.4 -11.2 -48.6 -21.2 -49 -24.8C-49 -24.8 -49.4 -42.8 -50.6 -43.6C-51.8 -44.4 -59.4 -50.4 -65.4 -44C-65.4 -44 -75.8 -26 -75 -19.6L-75 -17.6C-75 -17.6 -82.6 -18 -84.2 -16C-84.2 -16 -85.4 -10.8 -86.6 -10.4C-86.6 -10.4 -89.4 -8 -87.4 -5.2C-87.4 -5.2 -89.4 -2.8 -89 1.2L-81.4 5.2C-81.4 5.2 -79.4 19.6 -68.6 24.8C-63.764 27.129 -60.6 20.4 -58.6 14.4z', - }, - { - fill: '#ffffff', - data: - 'M-59.6 12.56C-59.6 12.56 -62.48 -6.52 -60.32 -10.48C-60.32 -10.48 -50.6 -19.48 -50.96 -22.72C-50.96 -22.72 -51.32 -38.92 -52.4 -39.64C-53.48 -40.36 -60.32 -45.76 -65.72 -40C-65.72 -40 -75.08 -23.8 -74.36 -18.04L-74.36 -16.24C-74.36 -16.24 -81.2 -16.6 -82.64 -14.8C-82.64 -14.8 -83.72 -10.12 -84.8 -9.76C-84.8 -9.76 -87.32 -7.6 -85.52 -5.08C-85.52 -5.08 -87.32 -2.92 -86.96 0.68L-80.12 4.28C-80.12 4.28 -78.32 17.24 -68.6 21.92C-64.248 24.015 -61.4 17.96 -59.6 12.56z', - }, - { - fill: '#eb955c', - data: - 'M-51.05 -42.61C-52.14 -43.47 -59.63 -49.24 -65.48 -43C-65.48 -43 -75.62 -25.45 -74.84 -19.21L-74.84 -17.26C-74.84 -17.26 -82.25 -17.65 -83.81 -15.7C-83.81 -15.7 -84.98 -10.63 -86.15 -10.24C-86.15 -10.24 -88.88 -7.9 -86.93 -5.17C-86.93 -5.17 -88.88 -2.83 -88.49 1.07L-81.08 4.97C-81.08 4.97 -79.13 19.01 -68.6 24.08C-63.886 26.35 -60.8 19.79 -58.85 13.94C-58.85 13.94 -61.97 -6.73 -59.63 -11.02C-59.63 -11.02 -49.1 -20.77 -49.49 -24.28C-49.49 -24.28 -49.88 -41.83 -51.05 -42.61z', - }, - { - fill: '#f2b892', - data: - 'M-51.5 -41.62C-52.48 -42.54 -59.86 -48.08 -65.56 -42C-65.56 -42 -75.44 -24.9 -74.68 -18.82L-74.68 -16.92C-74.68 -16.92 -81.9 -17.3 -83.42 -15.4C-83.42 -15.4 -84.56 -10.46 -85.7 -10.08C-85.7 -10.08 -88.36 -7.8 -86.46 -5.14C-86.46 -5.14 -88.36 -2.86 -87.98 0.94L-80.76 4.74C-80.76 4.74 -78.86 18.42 -68.6 23.36C-64.006 25.572 -61 19.18 -59.1 13.48C-59.1 13.48 -62.14 -6.66 -59.86 -10.84C-59.86 -10.84 -49.6 -20.34 -49.98 -23.76C-49.98 -23.76 -50.36 -40.86 -51.5 -41.62z', - }, - { - fill: '#f8dcc8', - data: - 'M-51.95 -40.63C-52.82 -41.61 -60.09 -46.92 -65.64 -41C-65.64 -41 -75.26 -24.35 -74.52 -18.43L-74.52 -16.58C-74.52 -16.58 -81.55 -16.95 -83.03 -15.1C-83.03 -15.1 -84.14 -10.29 -85.25 -9.92C-85.25 -9.92 -87.84 -7.7 -85.99 -5.11C-85.99 -5.11 -87.84 -2.89 -87.47 0.81L-80.44 4.51C-80.44 4.51 -78.59 17.83 -68.6 22.64C-64.127 24.794 -61.2 18.57 -59.35 13.02C-59.35 13.02 -62.31 -6.59 -60.09 -10.66C-60.09 -10.66 -50.1 -19.91 -50.47 -23.24C-50.47 -23.24 -50.84 -39.89 -51.95 -40.63z', - }, - { - fill: '#ffffff', - data: - 'M-59.6 12.46C-59.6 12.46 -62.48 -6.52 -60.32 -10.48C-60.32 -10.48 -50.6 -19.48 -50.96 -22.72C-50.96 -22.72 -51.32 -38.92 -52.4 -39.64C-53.16 -40.68 -60.32 -45.76 -65.72 -40C-65.72 -40 -75.08 -23.8 -74.36 -18.04L-74.36 -16.24C-74.36 -16.24 -81.2 -16.6 -82.64 -14.8C-82.64 -14.8 -83.72 -10.12 -84.8 -9.76C-84.8 -9.76 -87.32 -7.6 -85.52 -5.08C-85.52 -5.08 -87.32 -2.92 -86.96 0.68L-80.12 4.28C-80.12 4.28 -78.32 17.24 -68.6 21.92C-64.248 24.015 -61.4 17.86 -59.6 12.46z', - }, - { - fill: '#cccccc', - data: - 'M-62.7 6.2C-62.7 6.2 -84.3 -4 -85.2 -4.8C-85.2 -4.8 -76.1 3.4 -75.3 3.4C-74.5 3.4 -62.7 6.2 -62.7 6.2z', - }, - { - fill: '#000000', - data: - 'M-79.8 0C-79.8 0 -61.4 3.6 -61.4 8C-61.4 10.912 -61.643 24.331 -67 22.8C-75.4 20.4 -71.8 6 -79.8 0z', - }, - { - fill: '#99cc32', - data: - 'M-71.4 3.8C-71.4 3.8 -62.422 5.274 -61.4 8C-60.8 9.6 -60.137 17.908 -65.6 19C-70.152 19.911 -72.382 9.69 -71.4 3.8z', - }, - { - fill: '#000000', - data: - 'M14.595 46.349C14.098 44.607 15.409 44.738 17.2 44.2C19.2 43.6 31.4 39.8 32.2 37.2C33 34.6 46.2 39 46.2 39C48 39.8 52.4 42.4 52.4 42.4C57.2 43.6 63.8 44 63.8 44C66.2 45 69.6 47.8 69.6 47.8C84.2 58 96.601 50.8 96.601 50.8C116.601 44.2 110.601 27 110.601 27C107.601 18 110.801 14.6 110.801 14.6C111.001 10.8 118.201 17.2 118.201 17.2C120.801 21.4 121.601 26.4 121.601 26.4C129.601 37.6 126.201 19.8 126.201 19.8C126.401 18.8 123.601 15.2 123.601 14C123.601 12.8 121.801 9.4 121.801 9.4C118.801 6 121.201 -1 121.201 -1C123.001 -14.8 120.801 -13 120.801 -13C119.601 -14.8 110.401 -4.8 110.401 -4.8C108.201 -1.4 102.201 0.2 102.201 0.2C99.401 2 96.001 0.6 96.001 0.6C93.401 0.2 87.801 7.2 87.801 7.2C90.601 7 93.001 11.4 95.401 11.6C97.801 11.8 99.601 9.2 101.201 8.6C102.801 8 105.601 13.8 105.601 13.8C106.001 16.4 100.401 21.2 100.401 21.2C100.001 25.8 98.401 24.2 98.401 24.2C95.401 23.6 94.201 27.4 93.201 32C92.201 36.6 88.001 37 88.001 37C86.401 44.4 85.2 41.4 85.2 41.4C85 35.8 79 41.6 79 41.6C77.8 43.6 73.2 41.4 73.2 41.4C66.4 39.4 68.8 37.4 68.8 37.4C70.6 35.2 81.8 37.4 81.8 37.4C84 35.8 76 31.8 76 31.8C75.4 30 76.4 25.6 76.4 25.6C77.6 22.4 84.4 16.8 84.4 16.8C93.801 15.6 91.001 14 91.001 14C84.801 8.8 79 16.4 79 16.4C76.8 22.6 59.4 37.6 59.4 37.6C54.6 41 57.2 34.2 53.2 37.6C49.2 41 28.6 32 28.6 32C17.038 30.807 14.306 46.549 10.777 43.429C10.777 43.429 16.195 51.949 14.595 46.349z', - }, - { - fill: '#000000', - data: - 'M209.401 -120C209.401 -120 183.801 -112 181.001 -93.2C181.001 -93.2 178.601 -70.4 199.001 -52.8C199.001 -52.8 199.401 -46.4 201.401 -43.2C201.401 -43.2 199.801 -38.4 218.601 -46L245.801 -54.4C245.801 -54.4 252.201 -56.8 257.401 -65.6C262.601 -74.4 277.801 -93.2 274.201 -118.4C274.201 -118.4 275.401 -129.6 269.401 -130C269.401 -130 261.001 -131.6 253.801 -124C253.801 -124 247.001 -120.8 244.601 -121.2L209.401 -120z', - }, - { - fill: '#000000', - data: - 'M264.022 -120.99C264.022 -120.99 266.122 -129.92 261.282 -125.08C261.282 -125.08 254.242 -119.36 246.761 -119.36C246.761 -119.36 232.241 -117.16 227.841 -103.96C227.841 -103.96 223.881 -77.12 231.801 -71.4C231.801 -71.4 236.641 -63.92 243.681 -70.52C250.722 -77.12 266.222 -107.35 264.022 -120.99z', - }, - { - fill: '#323232', - data: - 'M263.648 -120.632C263.648 -120.632 265.738 -129.376 260.986 -124.624C260.986 -124.624 254.074 -119.008 246.729 -119.008C246.729 -119.008 232.473 -116.848 228.153 -103.888C228.153 -103.888 224.265 -77.536 232.041 -71.92C232.041 -71.92 236.793 -64.576 243.705 -71.056C250.618 -77.536 265.808 -107.24 263.648 -120.632z', - }, - { - fill: '#666666', - data: - 'M263.274 -120.274C263.274 -120.274 265.354 -128.832 260.69 -124.168C260.69 -124.168 253.906 -118.656 246.697 -118.656C246.697 -118.656 232.705 -116.536 228.465 -103.816C228.465 -103.816 224.649 -77.952 232.281 -72.44C232.281 -72.44 236.945 -65.232 243.729 -71.592C250.514 -77.952 265.394 -107.13 263.274 -120.274z', - }, - { - fill: '#999999', - data: - 'M262.9 -119.916C262.9 -119.916 264.97 -128.288 260.394 -123.712C260.394 -123.712 253.738 -118.304 246.665 -118.304C246.665 -118.304 232.937 -116.224 228.777 -103.744C228.777 -103.744 225.033 -78.368 232.521 -72.96C232.521 -72.96 237.097 -65.888 243.753 -72.128C250.41 -78.368 264.98 -107.02 262.9 -119.916z', - }, - { - fill: '#cccccc', - data: - 'M262.526 -119.558C262.526 -119.558 264.586 -127.744 260.098 -123.256C260.098 -123.256 253.569 -117.952 246.633 -117.952C246.633 -117.952 233.169 -115.912 229.089 -103.672C229.089 -103.672 225.417 -78.784 232.761 -73.48C232.761 -73.48 237.249 -66.544 243.777 -72.664C250.305 -78.784 264.566 -106.91 262.526 -119.558z', - }, - { - fill: '#ffffff', - data: - 'M262.151 -119.2C262.151 -119.2 264.201 -127.2 259.801 -122.8C259.801 -122.8 253.401 -117.6 246.601 -117.6C246.601 -117.6 233.401 -115.6 229.401 -103.6C229.401 -103.6 225.801 -79.2 233.001 -74C233.001 -74 237.401 -67.2 243.801 -73.2C250.201 -79.2 264.151 -106.8 262.151 -119.2z', - }, - { - fill: '#992600', - data: - 'M50.6 84C50.6 84 30.2 64.8 22.2 64C22.2 64 -12.2 60 -27 78C-27 78 -9.4 57.6 18.2 63.2C18.2 63.2 -3.4 58.8 -15.8 62C-15.8 62 -32.6 62 -42.2 76L-45 80.8C-45 80.8 -41 66 -22.6 60C-22.6 60 0.2 55.2 11 60C11 60 -10.6 53.2 -20.6 55.2C-20.6 55.2 -51 52.8 -63.8 79.2C-63.8 79.2 -59.8 64.8 -45 57.6C-45 57.6 -31.4 48.8 -11 51.6C-11 51.6 3.4 54.8 8.6 57.2C13.8 59.6 12.6 56.8 4.2 52C4.2 52 -1.4 42 -15.4 42.4C-15.4 42.4 -58.2 46 -68.6 58C-68.6 58 -55 46.8 -44.6 44C-44.6 44 -22.2 36 -13.8 36.8C-13.8 36.8 11 37.8 18.6 33.8C18.6 33.8 7.4 38.8 10.6 42C13.8 45.2 20.6 52.8 20.6 54C20.6 55.2 44.8 77.3 48.4 81.7L50.6 84z', - }, - { - fill: '#cccccc', - data: - 'M189 278C189 278 173.5 241.5 161 232C161 232 187 248 190.5 266C190.5 266 190.5 276 189 278z', - }, - { - fill: '#cccccc', - data: - 'M236 285.5C236 285.5 209.5 230.5 191 206.5C191 206.5 234.5 244 239.5 270.5L240 276L237 273.5C237 273.5 236.5 282.5 236 285.5z', - }, - { - fill: '#cccccc', - data: - 'M292.5 237C292.5 237 230 177.5 228.5 175C228.5 175 289 241 292 248.5C292 248.5 290 239.5 292.5 237z', - }, - { - fill: '#cccccc', - data: - 'M104 280.5C104 280.5 123.5 228.5 142.5 251C142.5 251 157.5 261 157 264C157 264 153 257.5 135 258C135 258 116 255 104 280.5z', - }, - { - fill: '#cccccc', - data: - 'M294.5 153C294.5 153 249.5 124.5 242 123C230.193 120.639 291.5 152 296.5 162.5C296.5 162.5 298.5 160 294.5 153z', - }, - { - fill: '#000000', - data: - 'M143.801 259.601C143.801 259.601 164.201 257.601 171.001 250.801L175.401 254.401L193.001 216.001L196.601 221.201C196.601 221.201 211.001 206.401 210.201 198.401C209.401 190.401 223.001 204.401 223.001 204.401C223.001 204.401 222.201 192.801 229.401 199.601C229.401 199.601 227.001 184.001 235.401 192.001C235.401 192.001 224.864 161.844 247.401 187.601C253.001 194.001 248.601 187.201 248.601 187.201C248.601 187.201 222.601 139.201 244.201 153.601C244.201 153.601 246.201 130.801 245.001 126.401C243.801 122.001 241.801 99.6 237.001 94.4C232.201 89.2 237.401 87.6 243.001 92.8C243.001 92.8 231.801 68.8 245.001 80.8C245.001 80.8 241.401 65.6 237.001 62.8C237.001 62.8 231.401 45.6 246.601 56.4C246.601 56.4 242.201 44 239.001 40.8C239.001 40.8 227.401 13.2 234.601 18L239.001 21.6C239.001 21.6 232.201 7.6 238.601 12C245.001 16.4 245.001 16 245.001 16C245.001 16 223.801 -17.2 244.201 0.4C244.201 0.4 236.042 -13.518 232.601 -20.4C232.601 -20.4 213.801 -40.8 228.201 -34.4L233.001 -32.8C233.001 -32.8 224.201 -42.8 216.201 -44.4C208.201 -46 218.601 -52.4 225.001 -50.4C231.401 -48.4 247.001 -40.8 247.001 -40.8C247.001 -40.8 259.801 -22 263.801 -21.6C263.801 -21.6 243.801 -29.2 249.801 -21.2C249.801 -21.2 264.201 -7.2 257.001 -7.6C257.001 -7.6 251.001 -0.4 255.801 8.4C255.801 8.4 237.342 -9.991 252.201 15.6L259.001 32C259.001 32 234.601 7.2 245.801 29.2C245.801 29.2 263.001 52.8 265.001 53.2C267.001 53.6 271.401 62.4 271.401 62.4L267.001 60.4L272.201 69.2C272.201 69.2 261.001 57.2 267.001 70.4L272.601 84.8C272.601 84.8 252.201 62.8 265.801 92.4C265.801 92.4 249.401 87.2 258.201 104.4C258.201 104.4 256.601 120.401 257.001 125.601C257.401 130.801 258.601 159.201 254.201 167.201C249.801 175.201 260.201 194.401 262.201 198.401C264.201 202.401 267.801 213.201 259.001 204.001C250.201 194.801 254.601 200.401 256.601 209.201C258.601 218.001 264.601 233.601 263.801 239.201C263.801 239.201 262.601 240.401 259.401 236.801C259.401 236.801 244.601 214.001 246.201 228.401C246.201 228.401 245.001 236.401 241.801 245.201C241.801 245.201 238.601 256.001 238.601 247.201C238.601 247.201 235.401 230.401 232.601 238.001C229.801 245.601 226.201 251.601 223.401 254.001C220.601 256.401 215.401 233.601 214.201 244.001C214.201 244.001 202.201 231.601 197.401 248.001L185.801 264.401C185.801 264.401 185.401 252.001 184.201 258.001C184.201 258.001 154.201 264.001 143.801 259.601z', - }, - { - fill: '#000000', - data: - 'M109.401 -97.2C109.401 -97.2 97.801 -105.2 93.801 -104.8C89.801 -104.4 121.401 -113.6 162.601 -86C162.601 -86 167.401 -83.2 171.001 -83.6C171.001 -83.6 174.201 -81.2 171.401 -77.6C171.401 -77.6 162.601 -68 173.801 -56.8C173.801 -56.8 192.201 -50 186.601 -58.8C186.601 -58.8 197.401 -54.8 199.801 -50.8C202.201 -46.8 201.001 -50.8 201.001 -50.8C201.001 -50.8 194.601 -58 188.601 -63.2C188.601 -63.2 183.401 -65.2 180.601 -73.6C177.801 -82 175.401 -92 179.801 -95.2C179.801 -95.2 175.801 -90.8 176.601 -94.8C177.401 -98.8 181.001 -102.4 182.601 -102.8C184.201 -103.2 200.601 -119 207.401 -119.4C207.401 -119.4 198.201 -118 195.201 -119C192.201 -120 165.601 -131.4 159.601 -132.6C159.601 -132.6 142.801 -139.2 154.801 -137.2C154.801 -137.2 190.601 -133.4 208.801 -120.2C208.801 -120.2 201.601 -128.6 183.201 -135.6C183.201 -135.6 161.001 -148.2 125.801 -143.2C125.801 -143.2 108.001 -140 100.201 -138.2C100.201 -138.2 97.601 -138.8 97.001 -139.2C96.401 -139.6 84.6 -148.6 57 -141.6C57 -141.6 40 -137 31.4 -132.2C31.4 -132.2 16.2 -131 12.6 -127.8C12.6 -127.8 -6 -113.2 -8 -112.4C-10 -111.6 -21.4 -104 -22.2 -103.6C-22.2 -103.6 2.4 -110.2 4.8 -112.6C7.2 -115 24.6 -117.6 27 -116.2C29.4 -114.8 37.8 -115.4 28.2 -114.8C28.2 -114.8 103.801 -100 104.601 -98C105.401 -96 109.401 -97.2 109.401 -97.2z', - }, - { - fill: '#cc7226', - data: - 'M180.801 -106.4C180.801 -106.4 170.601 -113.8 168.601 -113.8C166.601 -113.8 154.201 -124 150.001 -123.6C145.801 -123.2 133.601 -133.2 106.201 -125C106.201 -125 105.601 -127 109.201 -127.8C109.201 -127.8 115.601 -130 116.001 -130.6C116.001 -130.6 136.201 -134.8 143.401 -131.2C143.401 -131.2 152.601 -128.6 158.801 -122.4C158.801 -122.4 170.001 -119.2 173.201 -120.2C173.201 -120.2 182.001 -118 182.401 -116.2C182.401 -116.2 188.201 -113.2 186.401 -110.6C186.401 -110.6 186.801 -109 180.801 -106.4z', - }, - { - fill: '#cc7226', - data: - 'M168.33 -108.509C169.137 -107.877 170.156 -107.779 170.761 -106.97C170.995 -106.656 170.706 -106.33 170.391 -106.233C169.348 -105.916 168.292 -106.486 167.15 -105.898C166.748 -105.691 166.106 -105.873 165.553 -106.022C163.921 -106.463 162.092 -106.488 160.401 -105.8C158.416 -106.929 156.056 -106.345 153.975 -107.346C153.917 -107.373 153.695 -107.027 153.621 -107.054C150.575 -108.199 146.832 -107.916 144.401 -110.2C141.973 -110.612 139.616 -111.074 137.188 -111.754C135.37 -112.263 133.961 -113.252 132.341 -114.084C130.964 -114.792 129.507 -115.314 127.973 -115.686C126.11 -116.138 124.279 -116.026 122.386 -116.546C122.293 -116.571 122.101 -116.227 122.019 -116.254C121.695 -116.362 121.405 -116.945 121.234 -116.892C119.553 -116.37 118.065 -117.342 116.401 -117C115.223 -118.224 113.495 -117.979 111.949 -118.421C108.985 -119.269 105.831 -117.999 102.801 -119C106.914 -120.842 111.601 -119.61 115.663 -121.679C117.991 -122.865 120.653 -121.763 123.223 -122.523C123.71 -122.667 124.401 -122.869 124.801 -122.2C124.935 -122.335 125.117 -122.574 125.175 -122.546C127.625 -121.389 129.94 -120.115 132.422 -119.049C132.763 -118.903 133.295 -119.135 133.547 -118.933C135.067 -117.717 137.01 -117.82 138.401 -116.6C140.099 -117.102 141.892 -116.722 143.621 -117.346C143.698 -117.373 143.932 -117.032 143.965 -117.054C145.095 -117.802 146.25 -117.531 147.142 -117.227C147.48 -117.112 148.143 -116.865 148.448 -116.791C149.574 -116.515 150.43 -116.035 151.609 -115.852C151.723 -115.834 151.908 -116.174 151.98 -116.146C153.103 -115.708 154.145 -115.764 154.801 -114.6C154.936 -114.735 155.101 -114.973 155.183 -114.946C156.21 -114.608 156.859 -113.853 157.96 -113.612C158.445 -113.506 159.057 -112.88 159.633 -112.704C162.025 -111.973 163.868 -110.444 166.062 -109.549C166.821 -109.239 167.697 -109.005 168.33 -108.509z', - }, - { - fill: '#cc7226', - data: - 'M91.696 -122.739C89.178 -124.464 86.81 -125.57 84.368 -127.356C84.187 -127.489 83.827 -127.319 83.625 -127.441C82.618 -128.05 81.73 -128.631 80.748 -129.327C80.209 -129.709 79.388 -129.698 78.88 -129.956C76.336 -131.248 73.707 -131.806 71.2 -133C71.882 -133.638 73.004 -133.394 73.6 -134.2C73.795 -133.92 74.033 -133.636 74.386 -133.827C76.064 -134.731 77.914 -134.884 79.59 -134.794C81.294 -134.702 83.014 -134.397 84.789 -134.125C85.096 -134.078 85.295 -133.555 85.618 -133.458C87.846 -132.795 90.235 -133.32 92.354 -132.482C93.945 -131.853 95.515 -131.03 96.754 -129.755C97.006 -129.495 96.681 -129.194 96.401 -129C96.789 -129.109 97.062 -128.903 97.173 -128.59C97.257 -128.351 97.257 -128.049 97.173 -127.81C97.061 -127.498 96.782 -127.397 96.408 -127.346C95.001 -127.156 96.773 -128.536 96.073 -128.088C94.8 -127.274 95.546 -125.868 94.801 -124.6C94.521 -124.794 94.291 -125.012 94.401 -125.4C94.635 -124.878 94.033 -124.588 93.865 -124.272C93.48 -123.547 92.581 -122.132 91.696 -122.739z', - }, - { - fill: '#cc7226', - data: - 'M59.198 -115.391C56.044 -116.185 52.994 -116.07 49.978 -117.346C49.911 -117.374 49.688 -117.027 49.624 -117.054C48.258 -117.648 47.34 -118.614 46.264 -119.66C45.351 -120.548 43.693 -120.161 42.419 -120.648C42.095 -120.772 41.892 -121.284 41.591 -121.323C40.372 -121.48 39.445 -122.429 38.4 -123C40.736 -123.795 43.147 -123.764 45.609 -124.148C45.722 -124.166 45.867 -123.845 46 -123.845C46.136 -123.845 46.266 -124.066 46.4 -124.2C46.595 -123.92 46.897 -123.594 47.154 -123.848C47.702 -124.388 48.258 -124.198 48.798 -124.158C48.942 -124.148 49.067 -123.845 49.2 -123.845C49.336 -123.845 49.467 -124.156 49.6 -124.156C49.736 -124.155 49.867 -123.845 50 -123.845C50.136 -123.845 50.266 -124.066 50.4 -124.2C51.092 -123.418 51.977 -123.972 52.799 -123.793C53.837 -123.566 54.104 -122.418 55.178 -122.12C59.893 -120.816 64.03 -118.671 68.393 -116.584C68.7 -116.437 68.91 -116.189 68.8 -115.8C69.067 -115.8 69.38 -115.888 69.57 -115.756C70.628 -115.024 71.669 -114.476 72.366 -113.378C72.582 -113.039 72.253 -112.632 72.02 -112.684C67.591 -113.679 63.585 -114.287 59.198 -115.391z', - }, - { - fill: '#cc7226', - data: - 'M45.338 -71.179C43.746 -72.398 43.162 -74.429 42.034 -76.221C41.82 -76.561 42.094 -76.875 42.411 -76.964C42.971 -77.123 43.514 -76.645 43.923 -76.443C45.668 -75.581 47.203 -74.339 49.2 -74.2C51.19 -71.966 55.45 -71.581 55.457 -68.2C55.458 -67.341 54.03 -68.259 53.6 -67.4C51.149 -68.403 48.76 -68.3 46.38 -69.767C45.763 -70.148 46.093 -70.601 45.338 -71.179z', - }, - { - fill: '#cc7226', - data: - 'M17.8 -123.756C17.935 -123.755 24.966 -123.522 24.949 -123.408C24.904 -123.099 17.174 -122.05 16.81 -122.22C16.646 -122.296 9.134 -119.866 9 -120C9.268 -120.135 17.534 -123.756 17.8 -123.756z', - }, - { - fill: '#000000', - data: - 'M33.2 -114C33.2 -114 18.4 -112.2 14 -111C9.6 -109.8 -9 -102.2 -12 -100.2C-12 -100.2 -25.4 -94.8 -42.4 -74.8C-42.4 -74.8 -34.8 -78.2 -32.6 -81C-32.6 -81 -19 -93.6 -19.2 -91C-19.2 -91 -7 -99.6 -7.6 -97.4C-7.6 -97.4 16.8 -108.6 14.8 -105.4C14.8 -105.4 36.4 -110 35.4 -108C35.4 -108 54.2 -103.6 51.4 -103.4C51.4 -103.4 45.6 -102.2 52 -98.6C52 -98.6 48.6 -94.2 43.2 -98.2C37.8 -102.2 40.8 -100 35.8 -99C35.8 -99 33.2 -98.2 28.6 -102.2C28.6 -102.2 23 -106.8 14.2 -103.2C14.2 -103.2 -16.4 -90.6 -18.4 -90C-18.4 -90 -22 -87.2 -24.4 -83.6C-24.4 -83.6 -30.2 -79.2 -33.2 -77.8C-33.2 -77.8 -46 -66.2 -47.2 -64.8C-47.2 -64.8 -50.6 -59.6 -51.4 -59.2C-51.4 -59.2 -45 -63 -43 -65C-43 -65 -29 -75 -23.6 -75.8C-23.6 -75.8 -19.2 -78.8 -18.4 -80.2C-18.4 -80.2 -4 -89.4 0.2 -89.4C0.2 -89.4 9.4 -84.2 11.8 -91.2C11.8 -91.2 17.6 -93 23.2 -91.8C23.2 -91.8 26.4 -94.4 25.6 -96.6C25.6 -96.6 27.2 -98.4 28.2 -94.6C28.2 -94.6 31.6 -91 36.4 -93C36.4 -93 40.4 -93.2 38.4 -90.8C38.4 -90.8 34 -87 22.2 -86.8C22.2 -86.8 9.8 -86.2 -6.6 -78.6C-6.6 -78.6 -36.4 -68.2 -45.6 -57.8C-45.6 -57.8 -52 -49 -57.4 -47.8C-57.4 -47.8 -63.2 -47 -69.2 -39.6C-69.2 -39.6 -59.4 -45.4 -50.4 -45.4C-50.4 -45.4 -46.4 -47.8 -50.2 -44.2C-50.2 -44.2 -53.8 -36.6 -52.2 -31.2C-52.2 -31.2 -52.8 -26 -53.6 -24.4C-53.6 -24.4 -61.4 -11.6 -61.4 -9.2C-61.4 -6.8 -60.2 3 -59.8 3.6C-59.4 4.2 -60.8 2 -57 4.4C-53.2 6.8 -50.4 8.4 -49.6 11.2C-48.8 14 -51.6 5.8 -51.8 4C-52 2.2 -56.2 -5 -55.4 -7.4C-55.4 -7.4 -54.4 -6.4 -53.6 -5C-53.6 -5 -54.2 -5.6 -53.6 -9.2C-53.6 -9.2 -52.8 -14.4 -51.4 -17.6C-50 -20.8 -48 -24.6 -47.6 -25.4C-47.2 -26.2 -47.2 -32 -45.8 -29.4L-42.4 -26.8C-42.4 -26.8 -45.2 -29.4 -43 -31.6C-43 -31.6 -44 -37.2 -42.2 -39.8C-42.2 -39.8 -35.2 -48.2 -33.6 -49.2C-32 -50.2 -33.4 -49.8 -33.4 -49.8C-33.4 -49.8 -27.4 -54 -33.2 -52.4C-33.2 -52.4 -37.2 -50.8 -40.2 -50.8C-40.2 -50.8 -47.8 -48.8 -43.8 -53C-39.8 -57.2 -29.8 -62.6 -26 -62.4L-25.2 -60.8L-14 -63.2L-15.2 -62.4C-15.2 -62.4 -15.4 -62.6 -11.2 -63C-7 -63.4 -1.2 -62 0.2 -63.8C1.6 -65.6 5 -66.6 4.6 -65.2C4.2 -63.8 4 -61.8 4 -61.8C4 -61.8 9 -67.6 8.4 -65.4C7.8 -63.2 -0.4 -58 -1.8 -51.8L8.6 -60L12.2 -63C12.2 -63 15.8 -60.8 16 -62.4C16.2 -64 20.8 -69.8 22 -69.6C23.2 -69.4 25.2 -72.2 25 -69.6C24.8 -67 32.4 -61.6 32.4 -61.6C32.4 -61.6 35.6 -63.4 37 -62C38.4 -60.6 42.6 -81.8 42.6 -81.8L67.6 -92.4L111.201 -95.8L94.201 -102.6L33.2 -114z', - }, - { - stroke: '#4c0000', - strokeWidth: 2, - data: 'M51.4 85C51.4 85 36.4 68.2 28 65.6C28 65.6 14.6 58.8 -10 66.6', - }, - { - stroke: '#4c0000', - strokeWidth: 2, - data: - 'M24.8 64.2C24.8 64.2 -0.4 56.2 -15.8 60.4C-15.8 60.4 -34.2 62.4 -42.6 76.2', - }, - { - stroke: '#4c0000', - strokeWidth: 2, - data: - 'M21.2 63C21.2 63 4.2 55.8 -10.6 53.6C-10.6 53.6 -27.2 51 -43.8 58.2C-43.8 58.2 -56 64.2 -61.4 74.4', - }, - { - stroke: '#4c0000', - strokeWidth: 2, - data: - 'M22.2 63.4C22.2 63.4 6.8 52.4 5.8 51C5.8 51 -1.2 40 -14.2 39.6C-14.2 39.6 -35.6 40.4 -52.8 48.4', - }, - { - fill: '#000000', - data: - 'M20.895 54.407C22.437 55.87 49.4 84.8 49.4 84.8C84.6 121.401 56.6 87.2 56.6 87.2C49 82.4 39.8 63.6 39.8 63.6C38.6 60.8 53.8 70.8 53.8 70.8C57.8 71.6 71.4 90.8 71.4 90.8C64.6 88.4 69.4 95.6 69.4 95.6C72.2 97.6 92.601 113.201 92.601 113.201C96.201 117.201 100.201 118.801 100.201 118.801C114.201 113.601 107.801 126.801 107.801 126.801C110.201 133.601 115.801 122.001 115.801 122.001C127.001 105.2 110.601 107.601 110.601 107.601C80.6 110.401 73.8 94.4 73.8 94.4C71.4 92 80.2 94.4 80.2 94.4C88.601 96.4 73 82 73 82C75.4 82 84.6 88.8 84.6 88.8C95.001 98 97.001 96 97.001 96C115.001 87.2 125.401 94.8 125.401 94.8C127.401 96.4 121.801 103.2 123.401 108.401C125.001 113.601 129.801 126.001 129.801 126.001C127.401 127.601 127.801 138.401 127.801 138.401C144.601 161.601 135.001 159.601 135.001 159.601C119.401 159.201 134.201 166.801 134.201 166.801C137.401 168.801 146.201 176.001 146.201 176.001C143.401 174.801 141.801 180.001 141.801 180.001C146.601 184.001 143.801 188.801 143.801 188.801C137.801 190.001 136.601 194.001 136.601 194.001C143.401 202.001 133.401 202.401 133.401 202.401C137.001 206.801 132.201 218.801 132.201 218.801C127.401 218.801 121.001 224.401 121.001 224.401C123.401 229.201 113.001 234.801 113.001 234.801C104.601 236.401 107.401 243.201 107.401 243.201C99.401 249.201 97.001 265.201 97.001 265.201C96.201 275.601 93.801 278.801 99.001 276.801C104.201 274.801 103.401 262.401 103.401 262.401C98.601 246.801 141.401 230.801 141.401 230.801C145.401 229.201 146.201 224.001 146.201 224.001C148.201 224.401 157.001 232.001 157.001 232.001C164.601 243.201 165.001 234.001 165.001 234.001C166.201 230.401 164.601 224.401 164.601 224.401C170.601 202.801 156.601 196.401 156.601 196.401C146.601 162.801 160.601 171.201 160.601 171.201C163.401 176.801 174.201 182.001 174.201 182.001L177.801 179.601C176.201 174.801 184.601 168.801 184.601 168.801C187.401 175.201 193.401 167.201 193.401 167.201C197.001 142.801 209.401 157.201 209.401 157.201C213.401 158.401 214.601 151.601 214.601 151.601C218.201 141.201 214.601 127.601 214.601 127.601C218.201 127.201 227.801 133.201 227.801 133.201C230.601 129.601 221.401 112.801 225.401 115.201C229.401 117.601 233.801 119.201 233.801 119.201C234.601 117.201 224.601 104.801 224.601 104.801C220.201 102 215.001 81.6 215.001 81.6C222.201 85.2 212.201 70 212.201 70C212.201 66.8 218.201 55.6 218.201 55.6C217.401 48.8 218.201 49.2 218.201 49.2C221.001 50.4 229.001 52 222.201 45.6C215.401 39.2 223.001 34.4 223.001 34.4C227.401 31.6 213.801 32 213.801 32C208.601 27.6 209.001 23.6 209.001 23.6C217.001 25.6 202.601 11.2 200.201 7.6C197.801 4 207.401 -1.2 207.401 -1.2C220.601 -4.8 209.001 -8 209.001 -8C189.401 -7.6 200.201 -18.4 200.201 -18.4C206.201 -18 204.601 -20.4 204.601 -20.4C199.401 -21.6 189.801 -28 189.801 -28C185.801 -31.6 189.401 -30.8 189.401 -30.8C206.201 -29.6 177.401 -40.8 177.401 -40.8C185.401 -40.8 167.401 -51.2 167.401 -51.2C165.401 -52.8 162.201 -60.4 162.201 -60.4C156.201 -65.6 151.401 -72.4 151.401 -72.4C151.001 -76.8 146.201 -81.6 146.201 -81.6C134.601 -95.2 129.001 -94.8 129.001 -94.8C114.201 -98.4 109.001 -97.6 109.001 -97.6L56.2 -93.2C29.8 -80.4 37.6 -59.4 37.6 -59.4C44 -51 53.2 -54.8 53.2 -54.8C57.8 -61 69.4 -58.8 69.4 -58.8C89.801 -55.6 87.201 -59.2 87.201 -59.2C84.801 -63.8 68.6 -70 68.4 -70.6C68.2 -71.2 59.4 -74.6 59.4 -74.6C56.4 -75.8 52 -85 52 -85C48.8 -88.4 64.6 -82.6 64.6 -82.6C63.4 -81.6 70.8 -77.6 70.8 -77.6C88.201 -78.6 98.801 -67.8 98.801 -67.8C109.601 -51.2 109.801 -59.4 109.801 -59.4C112.601 -68.8 100.801 -90 100.801 -90C101.201 -92 109.401 -85.4 109.401 -85.4C110.801 -87.4 111.601 -81.6 111.601 -81.6C111.801 -79.2 115.601 -71.2 115.601 -71.2C118.401 -58.2 122.001 -65.6 122.001 -65.6L126.601 -56.2C128.001 -53.6 122.001 -46 122.001 -46C121.801 -43.2 122.601 -43.4 117.001 -35.8C111.401 -28.2 114.801 -23.8 114.801 -23.8C113.401 -17.2 122.201 -17.6 122.201 -17.6C124.801 -15.4 128.201 -15.4 128.201 -15.4C130.001 -13.4 132.401 -14 132.401 -14C134.001 -17.8 140.201 -15.8 140.201 -15.8C141.601 -18.2 149.801 -18.6 149.801 -18.6C150.801 -21.2 151.201 -22.8 154.601 -23.4C158.001 -24 133.401 -67 133.401 -67C139.801 -67.8 131.601 -80.2 131.601 -80.2C129.401 -86.8 140.801 -72.2 143.001 -70.8C145.201 -69.4 146.201 -67.2 144.601 -67.4C143.001 -67.6 141.201 -65.4 142.601 -65.2C144.001 -65 157.001 -50 160.401 -39.8C163.801 -29.6 169.801 -25.6 176.001 -19.6C182.201 -13.6 181.401 10.6 181.401 10.6C181.001 19.4 187.001 30 187.001 30C189.001 33.8 184.801 52 184.801 52C182.801 54.2 184.201 55 184.201 55C185.201 56.2 192.001 69.4 192.001 69.4C190.201 69.2 193.801 72.8 193.801 72.8C199.001 78.8 192.601 75.8 192.601 75.8C186.601 74.2 193.601 84 193.601 84C194.801 85.8 185.801 81.2 185.801 81.2C176.601 80.6 188.201 87.8 188.201 87.8C196.801 95 185.401 90.6 185.401 90.6C180.801 88.8 184.001 95.6 184.001 95.6C187.201 97.2 204.401 104.2 204.401 104.2C204.801 108.001 201.801 113.001 201.801 113.001C202.201 117.001 200.001 120.401 200.001 120.401C198.801 128.601 198.201 129.401 198.201 129.401C194.001 129.601 186.601 143.401 186.601 143.401C184.801 146.001 174.601 158.001 174.601 158.001C172.601 165.001 154.601 157.801 154.601 157.801C148.001 161.201 150.001 157.801 150.001 157.801C149.601 155.601 154.401 149.601 154.401 149.601C161.401 147.001 158.801 136.201 158.801 136.201C162.801 134.801 151.601 132.001 151.801 130.801C152.001 129.601 157.801 128.201 157.801 128.201C165.801 126.201 161.401 123.801 161.401 123.801C160.801 119.801 163.801 114.201 163.801 114.201C175.401 113.401 163.801 97.2 163.801 97.2C153.001 89.6 152.001 83.8 152.001 83.8C164.601 75.6 156.401 63.2 156.601 59.6C156.801 56 158.001 34.4 158.001 34.4C156.001 28.2 153.001 14.6 153.001 14.6C155.201 9.4 162.601 -3.2 162.601 -3.2C165.401 -7.4 174.201 -12.2 172.001 -15.2C169.801 -18.2 162.001 -16.4 162.001 -16.4C154.201 -17.8 154.801 -12.6 154.801 -12.6C153.201 -11.6 152.401 -6.6 152.401 -6.6C151.68 1.333 142.801 7.6 142.801 7.6C131.601 13.8 140.801 17.8 140.801 17.8C146.801 24.4 137.001 24.6 137.001 24.6C126.001 22.8 134.201 33 134.201 33C145.001 45.8 142.001 48.6 142.001 48.6C131.801 49.6 144.401 58.8 144.401 58.8C144.401 58.8 143.601 56.8 143.801 58.6C144.001 60.4 147.001 64.6 147.801 66.6C148.601 68.6 144.601 68.8 144.601 68.8C145.201 78.4 129.801 74.2 129.801 74.2C129.801 74.2 129.801 74.2 128.201 74.4C126.601 74.6 115.401 73.8 109.601 71.6C103.801 69.4 97.001 69.4 97.001 69.4C97.001 69.4 93.001 71.2 85.4 71C77.8 70.8 69.8 73.6 69.8 73.6C65.4 73.2 74 68.8 74.2 69C74.4 69.2 80 63.6 72 64.2C50.203 65.835 39.4 55.6 39.4 55.6C37.4 54.2 34.8 51.4 34.8 51.4C24.8 49.4 36.2 63.8 36.2 63.8C37.4 65.2 36 66.2 36 66.2C35.2 64.6 27.4 59.2 27.4 59.2C24.589 58.227 23.226 56.893 20.895 54.407z', - }, - { - fill: '#4c0000', - data: - 'M-3 42.8C-3 42.8 8.6 48.4 11.2 51.2C13.8 54 27.8 65.4 27.8 65.4C27.8 65.4 22.4 63.4 19.8 61.6C17.2 59.8 6.4 51.6 6.4 51.6C6.4 51.6 2.6 45.6 -3 42.8z', - }, - { - fill: '#99cc32', - data: - 'M-61.009 11.603C-60.672 11.455 -61.196 8.743 -61.4 8.2C-62.422 5.474 -71.4 4 -71.4 4C-71.627 5.365 -71.682 6.961 -71.576 8.599C-71.576 8.599 -66.708 14.118 -61.009 11.603z', - }, - { - fill: '#659900', - data: - 'M-61.009 11.403C-61.458 11.561 -61.024 8.669 -61.2 8.2C-62.222 5.474 -71.4 3.9 -71.4 3.9C-71.627 5.265 -71.682 6.861 -71.576 8.499C-71.576 8.499 -67.308 13.618 -61.009 11.403z', - }, - { - fill: '#000000', - data: - 'M-65.4 11.546C-66.025 11.546 -66.531 10.406 -66.531 9C-66.531 7.595 -66.025 6.455 -65.4 6.455C-64.775 6.455 -64.268 7.595 -64.268 9C-64.268 10.406 -64.775 11.546 -65.4 11.546z', - }, - { fill: '#000000', data: 'M-65.4 9z' }, - { - fill: '#000000', - data: - 'M-111 109.601C-111 109.601 -116.6 119.601 -91.8 113.601C-91.8 113.601 -77.8 112.401 -75.4 110.001C-74.2 110.801 -65.834 113.734 -63 114.401C-56.2 116.001 -47.8 106 -47.8 106C-47.8 106 -43.2 95.5 -40.4 95.5C-37.6 95.5 -40.8 97.1 -40.8 97.1C-40.8 97.1 -47.4 107.201 -47 108.801C-47 108.801 -52.2 128.801 -68.2 129.601C-68.2 129.601 -84.35 130.551 -83 136.401C-83 136.401 -74.2 134.001 -71.8 136.401C-71.8 136.401 -61 136.001 -69 142.401L-75.8 154.001C-75.8 154.001 -75.66 157.919 -85.8 154.401C-95.6 151.001 -105.9 138.101 -105.9 138.101C-105.9 138.101 -121.85 123.551 -111 109.601z', - }, - { - fill: '#e59999', - data: - 'M-112.2 113.601C-112.2 113.601 -114.2 123.201 -77.4 112.801C-77.4 112.801 -73 112.801 -70.6 113.601C-68.2 114.401 -56.2 117.201 -54.2 116.001C-54.2 116.001 -61.4 129.601 -73 128.001C-73 128.001 -86.2 129.601 -85.8 134.401C-85.8 134.401 -81.8 141.601 -77 144.001C-77 144.001 -74.2 146.401 -74.6 149.601C-75 152.801 -77.8 154.401 -79.8 155.201C-81.8 156.001 -85 152.801 -86.6 152.801C-88.2 152.801 -96.6 146.401 -101 141.601C-105.4 136.801 -113.8 124.801 -113.4 122.001C-113 119.201 -112.2 113.601 -112.2 113.601z', - }, - { - fill: '#b26565', - data: - 'M-109 131.051C-106.4 135.001 -103.2 139.201 -101 141.601C-96.6 146.401 -88.2 152.801 -86.6 152.801C-85 152.801 -81.8 156.001 -79.8 155.201C-77.8 154.401 -75 152.801 -74.6 149.601C-74.2 146.401 -77 144.001 -77 144.001C-80.066 142.468 -82.806 138.976 -84.385 136.653C-84.385 136.653 -84.2 139.201 -89.4 138.401C-94.6 137.601 -99.8 134.801 -101.4 131.601C-103 128.401 -105.4 126.001 -103.8 129.601C-102.2 133.201 -99.8 136.801 -98.2 137.201C-96.6 137.601 -97 138.801 -99.4 138.401C-101.8 138.001 -104.6 137.601 -109 132.401z', - }, - { - fill: '#992600', - data: - 'M-111.6 110.001C-111.6 110.001 -109.8 96.4 -108.6 92.4C-108.6 92.4 -109.4 85.6 -107 81.4C-104.6 77.2 -102.6 71 -99.6 65.6C-96.6 60.2 -96.4 56.2 -92.4 54.6C-88.4 53 -82.4 44.4 -79.6 43.4C-76.8 42.4 -77 43.2 -77 43.2C-77 43.2 -70.2 28.4 -56.6 32.4C-56.6 32.4 -72.8 29.6 -57 20.2C-57 20.2 -61.8 21.3 -58.5 14.3C-56.299 9.632 -56.8 16.4 -67.8 28.2C-67.8 28.2 -72.8 36.8 -78 39.8C-83.2 42.8 -95.2 49.8 -96.4 53.6C-97.6 57.4 -100.8 63.2 -102.8 64.8C-104.8 66.4 -107.6 70.6 -108 74C-108 74 -109.2 78 -110.6 79.2C-112 80.4 -112.2 83.6 -112.2 85.6C-112.2 87.6 -114.2 90.4 -114 92.8C-114 92.8 -113.2 111.801 -113.6 113.801L-111.6 110.001z', - }, - { - fill: '#ffffff', - data: - 'M-120.2 114.601C-120.2 114.601 -122.2 113.201 -126.6 119.201C-126.6 119.201 -119.3 152.201 -119.3 153.601C-119.3 153.601 -118.2 151.501 -119.5 144.301C-120.8 137.101 -121.7 124.401 -121.7 124.401L-120.2 114.601z', - }, - { - fill: '#992600', - data: - 'M-98.6 54C-98.6 54 -116.2 57.2 -115.8 86.4L-116.6 111.201C-116.6 111.201 -117.8 85.6 -119 84C-120.2 82.4 -116.2 71.2 -119.4 77.2C-119.4 77.2 -133.4 91.2 -125.4 112.401C-125.4 112.401 -123.9 115.701 -126.9 111.101C-126.9 111.101 -131.5 98.5 -130.4 92.1C-130.4 92.1 -130.2 89.9 -128.3 87.1C-128.3 87.1 -119.7 75.4 -117 73.1C-117 73.1 -115.2 58.7 -99.8 53.5C-99.8 53.5 -94.1 51.2 -98.6 54z', - }, - { - fill: '#000000', - data: - 'M40.8 -12.2C41.46 -12.554 41.451 -13.524 42.031 -13.697C43.18 -14.041 43.344 -15.108 43.862 -15.892C44.735 -17.211 44.928 -18.744 45.51 -20.235C45.782 -20.935 45.809 -21.89 45.496 -22.55C44.322 -25.031 43.62 -27.48 42.178 -29.906C41.91 -30.356 41.648 -31.15 41.447 -31.748C40.984 -33.132 39.727 -34.123 38.867 -35.443C38.579 -35.884 39.104 -36.809 38.388 -36.893C37.491 -36.998 36.042 -37.578 35.809 -36.552C35.221 -33.965 36.232 -31.442 37.2 -29C36.418 -28.308 36.752 -27.387 36.904 -26.62C37.614 -23.014 36.416 -19.662 35.655 -16.188C35.632 -16.084 35.974 -15.886 35.946 -15.824C34.724 -13.138 33.272 -10.693 31.453 -8.312C30.695 -7.32 29.823 -6.404 29.326 -5.341C28.958 -4.554 28.55 -3.588 28.8 -2.6C25.365 0.18 23.115 4.025 20.504 7.871C20.042 8.551 20.333 9.76 20.884 10.029C21.697 10.427 22.653 9.403 23.123 8.557C23.512 7.859 23.865 7.209 24.356 6.566C24.489 6.391 24.31 5.972 24.445 5.851C27.078 3.504 28.747 0.568 31.2 -1.8C33.15 -2.129 34.687 -3.127 36.435 -4.14C36.743 -4.319 37.267 -4.07 37.557 -4.265C39.31 -5.442 39.308 -7.478 39.414 -9.388C39.464 -10.272 39.66 -11.589 40.8 -12.2z', - }, - { - fill: '#000000', - data: - 'M31.959 -16.666C32.083 -16.743 31.928 -17.166 32.037 -17.382C32.199 -17.706 32.602 -17.894 32.764 -18.218C32.873 -18.434 32.71 -18.814 32.846 -18.956C35.179 -21.403 35.436 -24.427 34.4 -27.4C35.424 -28.02 35.485 -29.282 35.06 -30.129C34.207 -31.829 34.014 -33.755 33.039 -35.298C32.237 -36.567 30.659 -37.811 29.288 -36.508C28.867 -36.108 28.546 -35.321 28.824 -34.609C28.888 -34.446 29.173 -34.3 29.146 -34.218C29.039 -33.894 28.493 -33.67 28.487 -33.398C28.457 -31.902 27.503 -30.391 28.133 -29.062C28.905 -27.433 29.724 -25.576 30.4 -23.8C29.166 -21.684 30.199 -19.235 28.446 -17.358C28.31 -17.212 28.319 -16.826 28.441 -16.624C28.733 -16.138 29.139 -15.732 29.625 -15.44C29.827 -15.319 30.175 -15.317 30.375 -15.441C30.953 -15.803 31.351 -16.29 31.959 -16.666z', - }, - { - fill: '#000000', - data: - 'M94.771 -26.977C96.16 -25.185 96.45 -22.39 94.401 -21C94.951 -17.691 98.302 -19.67 100.401 -20.2C100.292 -20.588 100.519 -20.932 100.802 -20.937C101.859 -20.952 102.539 -21.984 103.601 -21.8C104.035 -23.357 105.673 -24.059 106.317 -25.439C108.043 -29.134 107.452 -33.407 104.868 -36.653C104.666 -36.907 104.883 -37.424 104.759 -37.786C104.003 -39.997 101.935 -40.312 100.001 -41C98.824 -44.875 98.163 -48.906 96.401 -52.6C94.787 -52.85 94.089 -54.589 92.752 -55.309C91.419 -56.028 90.851 -54.449 90.892 -53.403C90.899 -53.198 91.351 -52.974 91.181 -52.609C91.105 -52.445 90.845 -52.334 90.845 -52.2C90.846 -52.065 91.067 -51.934 91.201 -51.8C90.283 -50.98 88.86 -50.503 88.565 -49.358C87.611 -45.648 90.184 -42.523 91.852 -39.322C92.443 -38.187 91.707 -36.916 90.947 -35.708C90.509 -35.013 90.617 -33.886 90.893 -33.03C91.645 -30.699 93.236 -28.96 94.771 -26.977z', - }, - { - fill: '#000000', - data: - 'M57.611 -8.591C56.124 -6.74 52.712 -4.171 55.629 -2.243C55.823 -2.114 56.193 -2.11 56.366 -2.244C58.387 -3.809 60.39 -4.712 62.826 -5.294C62.95 -5.323 63.224 -4.856 63.593 -5.017C65.206 -5.72 67.216 -5.662 68.4 -7C72.167 -6.776 75.732 -7.892 79.123 -9.2C80.284 -9.648 81.554 -10.207 82.755 -10.709C84.131 -11.285 85.335 -12.213 86.447 -13.354C86.58 -13.49 86.934 -13.4 87.201 -13.4C87.161 -14.263 88.123 -14.39 88.37 -15.012C88.462 -15.244 88.312 -15.64 88.445 -15.742C90.583 -17.372 91.503 -19.39 90.334 -21.767C90.049 -22.345 89.8 -22.963 89.234 -23.439C88.149 -24.35 87.047 -23.496 86 -23.8C85.841 -23.172 85.112 -23.344 84.726 -23.146C83.867 -22.707 82.534 -23.292 81.675 -22.854C80.313 -22.159 79.072 -21.99 77.65 -21.613C77.338 -21.531 76.56 -21.627 76.4 -21C76.266 -21.134 76.118 -21.368 76.012 -21.346C74.104 -20.95 72.844 -20.736 71.543 -19.044C71.44 -18.911 70.998 -19.09 70.839 -18.955C69.882 -18.147 69.477 -16.913 68.376 -16.241C68.175 -16.118 67.823 -16.286 67.629 -16.157C66.983 -15.726 66.616 -15.085 65.974 -14.638C65.645 -14.409 65.245 -14.734 65.277 -14.99C65.522 -16.937 66.175 -18.724 65.6 -20.6C67.677 -23.12 70.194 -25.069 72 -27.8C72.015 -29.966 72.707 -32.112 72.594 -34.189C72.584 -34.382 72.296 -35.115 72.17 -35.462C71.858 -36.316 72.764 -37.382 71.92 -38.106C70.516 -39.309 69.224 -38.433 68.4 -37C66.562 -36.61 64.496 -35.917 62.918 -37.151C61.911 -37.938 61.333 -38.844 60.534 -39.9C59.549 -41.202 59.884 -42.638 59.954 -44.202C59.96 -44.33 59.645 -44.466 59.645 -44.6C59.646 -44.735 59.866 -44.866 60 -45C59.294 -45.626 59.019 -46.684 58 -47C58.305 -48.092 57.629 -48.976 56.758 -49.278C54.763 -49.969 53.086 -48.057 51.194 -47.984C50.68 -47.965 50.213 -49.003 49.564 -49.328C49.132 -49.544 48.428 -49.577 48.066 -49.311C47.378 -48.807 46.789 -48.693 46.031 -48.488C44.414 -48.052 43.136 -46.958 41.656 -46.103C40.171 -45.246 39.216 -43.809 38.136 -42.489C37.195 -41.337 37.059 -38.923 38.479 -38.423C40.322 -37.773 41.626 -40.476 43.592 -40.15C43.904 -40.099 44.11 -39.788 44 -39.4C44.389 -39.291 44.607 -39.52 44.8 -39.8C45.658 -38.781 46.822 -38.444 47.76 -37.571C48.73 -36.667 50.476 -37.085 51.491 -36.088C53.02 -34.586 52.461 -31.905 54.4 -30.6C53.814 -29.287 53.207 -28.01 52.872 -26.583C52.59 -25.377 53.584 -24.18 54.795 -24.271C56.053 -24.365 56.315 -25.124 56.8 -26.2C57.067 -25.933 57.536 -25.636 57.495 -25.42C57.038 -23.033 56.011 -21.04 55.553 -18.609C55.494 -18.292 55.189 -18.09 54.8 -18.2C54.332 -14.051 50.28 -11.657 47.735 -8.492C47.332 -7.99 47.328 -6.741 47.737 -6.338C49.14 -4.951 51.1 -6.497 52.8 -7C53.013 -8.206 53.872 -9.148 55.204 -9.092C55.46 -9.082 55.695 -9.624 56.019 -9.754C56.367 -9.892 56.869 -9.668 57.155 -9.866C58.884 -11.061 60.292 -12.167 62.03 -13.356C62.222 -13.487 62.566 -13.328 62.782 -13.436C63.107 -13.598 63.294 -13.985 63.617 -14.17C63.965 -14.37 64.207 -14.08 64.4 -13.8C63.754 -13.451 63.75 -12.494 63.168 -12.292C62.393 -12.024 61.832 -11.511 61.158 -11.064C60.866 -10.871 60.207 -11.119 60.103 -10.94C59.505 -9.912 58.321 -9.474 57.611 -8.591z', - }, - { - fill: '#000000', - data: - 'M2.2 -58C2.2 -58 -7.038 -60.872 -18.2 -35.2C-18.2 -35.2 -20.6 -30 -23 -28C-25.4 -26 -36.6 -22.4 -38.6 -18.4L-49 -2.4C-49 -2.4 -34.2 -18.4 -31 -20.8C-31 -20.8 -23 -29.2 -26.2 -22.4C-26.2 -22.4 -40.2 -11.6 -39 -2.4C-39 -2.4 -44.6 12 -45.4 14C-45.4 14 -29.4 -18 -27 -19.2C-24.6 -20.4 -23.4 -20.4 -24.6 -16.8C-25.8 -13.2 -26.2 3.2 -29 5.2C-29 5.2 -21 -15.2 -21.8 -18.4C-21.8 -18.4 -18.6 -22 -16.2 -16.8L-17.4 -0.8L-13 11.2C-13 11.2 -15.4 0 -13.8 -15.6C-13.8 -15.6 -15.8 -26 -11.8 -20.4C-7.8 -14.8 1.8 -8.8 1.8 -4C1.8 -4 -3.4 -21.6 -12.6 -26.4L-16.6 -20.4L-17.8 -22.4C-17.8 -22.4 -21.4 -23.2 -17 -30C-12.6 -36.8 -13 -37.6 -13 -37.6C-13 -37.6 -6.6 -30.4 -5 -30.4C-5 -30.4 8.2 -38 9.4 -13.6C9.4 -13.6 16.2 -28 7 -34.8C7 -34.8 -7.8 -36.8 -6.6 -42L0.6 -54.4C4.2 -59.6 2.6 -56.8 2.6 -56.8z', - }, - { - fill: '#000000', - data: - 'M-17.8 -41.6C-17.8 -41.6 -30.6 -41.6 -33.8 -36.4L-41 -26.8C-41 -26.8 -23.8 -36.8 -19.8 -38C-15.8 -39.2 -17.8 -41.6 -17.8 -41.6z', - }, - { - fill: '#000000', - data: - 'M-57.8 -35.2C-57.8 -35.2 -59.8 -34 -60.2 -31.2C-60.6 -28.4 -63 -28 -62.2 -25.2C-61.4 -22.4 -59.4 -20 -59.4 -24C-59.4 -28 -57.8 -30 -57 -31.2C-56.2 -32.4 -54.6 -36.8 -57.8 -35.2z', - }, - { - fill: '#000000', - data: - 'M-66.6 26C-66.6 26 -75 22 -78.2 18.4C-81.4 14.8 -80.948 19.966 -85.8 19.6C-91.647 19.159 -90.6 3.2 -90.6 3.2L-94.6 10.8C-94.6 10.8 -95.8 25.2 -87.8 22.8C-83.893 21.628 -82.6 23.2 -84.2 24C-85.8 24.8 -78.6 25.2 -81.4 26.8C-84.2 28.4 -69.8 23.2 -72.2 33.6L-66.6 26z', - }, - { - fill: '#000000', - data: - 'M-79.2 40.4C-79.2 40.4 -94.6 44.8 -98.2 35.2C-98.2 35.2 -103 37.6 -100.8 40.6C-98.6 43.6 -97.4 44 -97.4 44C-97.4 44 -92 45.2 -92.6 46C-93.2 46.8 -95.6 50.2 -95.6 50.2C-95.6 50.2 -85.4 44.2 -79.2 40.4z', - }, - { - fill: '#ffffff', - data: - 'M149.201 118.601C148.774 120.735 147.103 121.536 145.201 122.201C143.284 121.243 140.686 118.137 138.801 120.201C138.327 119.721 137.548 119.661 137.204 118.999C136.739 118.101 137.011 117.055 136.669 116.257C136.124 114.985 135.415 113.619 135.601 112.201C137.407 111.489 138.002 109.583 137.528 107.82C137.459 107.563 137.03 107.366 137.23 107.017C137.416 106.694 137.734 106.467 138.001 106.2C137.866 106.335 137.721 106.568 137.61 106.548C137 106.442 137.124 105.805 137.254 105.418C137.839 103.672 139.853 103.408 141.201 104.6C141.457 104.035 141.966 104.229 142.401 104.2C142.351 103.621 142.759 103.094 142.957 102.674C143.475 101.576 145.104 102.682 145.901 102.07C146.977 101.245 148.04 100.546 149.118 101.149C150.927 102.162 152.636 103.374 153.835 105.115C154.41 105.949 154.65 107.23 154.592 108.188C154.554 108.835 153.173 108.483 152.83 109.412C152.185 111.16 154.016 111.679 154.772 113.017C154.97 113.366 154.706 113.67 154.391 113.768C153.98 113.896 153.196 113.707 153.334 114.16C154.306 117.353 151.55 118.031 149.201 118.601z', - }, - { - fill: '#ffffff', - data: - 'M139.6 138.201C139.593 136.463 137.992 134.707 139.201 133.001C139.336 133.135 139.467 133.356 139.601 133.356C139.736 133.356 139.867 133.135 140.001 133.001C141.496 135.217 145.148 136.145 145.006 138.991C144.984 139.438 143.897 140.356 144.801 141.001C142.988 142.349 142.933 144.719 142.001 146.601C140.763 146.315 139.551 145.952 138.401 145.401C138.753 143.915 138.636 142.231 139.456 140.911C139.89 140.213 139.603 139.134 139.6 138.201z', - }, - { - fill: '#cccccc', - data: - 'M-26.6 129.201C-26.6 129.201 -43.458 139.337 -29.4 124.001C-20.6 114.401 -10.6 108.801 -10.6 108.801C-10.6 108.801 -0.2 104.4 3.4 103.2C7 102 22.2 96.8 25.4 96.4C28.6 96 38.2 92 45 96C51.8 100 59.8 104.4 59.8 104.4C59.8 104.4 43.4 96 39.8 98.4C36.2 100.8 29 100.4 23 103.6C23 103.6 8.2 108.001 5 110.001C1.8 112.001 -8.6 123.601 -10.2 122.801C-11.8 122.001 -9.8 121.601 -8.6 118.801C-7.4 116.001 -9.4 114.401 -17.4 120.801C-25.4 127.201 -26.6 129.201 -26.6 129.201z', - }, - { - fill: '#000000', - data: - 'M-19.195 123.234C-19.195 123.234 -17.785 110.194 -9.307 111.859C-9.307 111.859 -1.081 107.689 1.641 105.721C1.641 105.721 9.78 104.019 11.09 103.402C29.569 94.702 44.288 99.221 44.835 98.101C45.381 96.982 65.006 104.099 68.615 108.185C69.006 108.628 58.384 102.588 48.686 100.697C40.413 99.083 18.811 100.944 7.905 106.48C4.932 107.989 -4.013 113.773 -6.544 113.662C-9.075 113.55 -19.195 123.234 -19.195 123.234z', - }, - { - fill: '#cccccc', - data: - 'M-23 148.801C-23 148.801 -38.2 146.401 -21.4 144.801C-21.4 144.801 -3.4 142.801 0.6 137.601C0.6 137.601 14.2 128.401 17 128.001C19.8 127.601 49.8 120.401 50.2 118.001C50.6 115.601 56.2 115.601 57.8 116.401C59.4 117.201 58.6 118.401 55.8 119.201C53 120.001 21.8 136.401 15.4 137.601C9 138.801 -2.6 146.401 -7.4 147.601C-12.2 148.801 -23 148.801 -23 148.801z', - }, - { - fill: '#000000', - data: - 'M-3.48 141.403C-3.48 141.403 -12.062 140.574 -3.461 139.755C-3.461 139.755 5.355 136.331 7.403 133.668C7.403 133.668 14.367 128.957 15.8 128.753C17.234 128.548 31.194 124.861 31.399 123.633C31.604 122.404 65.67 109.823 70.09 113.013C73.001 115.114 63.1 113.437 53.466 117.847C52.111 118.467 18.258 133.054 14.981 133.668C11.704 134.283 5.765 138.174 3.307 138.788C0.85 139.403 -3.48 141.403 -3.48 141.403z', - }, - { - fill: '#000000', - data: - 'M-11.4 143.601C-11.4 143.601 -6.2 143.201 -7.4 144.801C-8.6 146.401 -11 145.601 -11 145.601L-11.4 143.601z', - }, - { - fill: '#000000', - data: - 'M-18.6 145.201C-18.6 145.201 -13.4 144.801 -14.6 146.401C-15.8 148.001 -18.2 147.201 -18.2 147.201L-18.6 145.201z', - }, - { - fill: '#000000', - data: - 'M-29 146.801C-29 146.801 -23.8 146.401 -25 148.001C-26.2 149.601 -28.6 148.801 -28.6 148.801L-29 146.801z', - }, - { - fill: '#000000', - data: - 'M-36.6 147.601C-36.6 147.601 -31.4 147.201 -32.6 148.801C-33.8 150.401 -36.2 149.601 -36.2 149.601L-36.6 147.601z', - }, - { - fill: '#000000', - data: - 'M1.8 108.001C1.8 108.001 6.2 108.001 5 109.601C3.8 111.201 0.6 110.801 0.6 110.801L1.8 108.001z', - }, - { - fill: '#000000', - data: - 'M-8.2 113.601C-8.2 113.601 -1.694 111.46 -4.2 114.801C-5.4 116.401 -7.8 115.601 -7.8 115.601L-8.2 113.601z', - }, - { - fill: '#000000', - data: - 'M-19.4 118.401C-19.4 118.401 -14.2 118.001 -15.4 119.601C-16.6 121.201 -19 120.401 -19 120.401L-19.4 118.401z', - }, - { - fill: '#000000', - data: - 'M-27 124.401C-27 124.401 -21.8 124.001 -23 125.601C-24.2 127.201 -26.6 126.401 -26.6 126.401L-27 124.401z', - }, - { - fill: '#000000', - data: - 'M-33.8 129.201C-33.8 129.201 -28.6 128.801 -29.8 130.401C-31 132.001 -33.4 131.201 -33.4 131.201L-33.8 129.201z', - }, - { - fill: '#000000', - data: - 'M5.282 135.598C5.282 135.598 12.203 135.066 10.606 137.195C9.009 139.325 5.814 138.26 5.814 138.26L5.282 135.598z', - }, - { - fill: '#000000', - data: - 'M15.682 130.798C15.682 130.798 22.603 130.266 21.006 132.395C19.409 134.525 16.214 133.46 16.214 133.46L15.682 130.798z', - }, - { - fill: '#000000', - data: - 'M26.482 126.398C26.482 126.398 33.403 125.866 31.806 127.995C30.209 130.125 27.014 129.06 27.014 129.06L26.482 126.398z', - }, - { - fill: '#000000', - data: - 'M36.882 121.598C36.882 121.598 43.803 121.066 42.206 123.195C40.609 125.325 37.414 124.26 37.414 124.26L36.882 121.598z', - }, - { - fill: '#000000', - data: - 'M9.282 103.598C9.282 103.598 16.203 103.066 14.606 105.195C13.009 107.325 9.014 107.06 9.014 107.06L9.282 103.598z', - }, - { - fill: '#000000', - data: - 'M19.282 100.398C19.282 100.398 26.203 99.866 24.606 101.995C23.009 104.125 18.614 103.86 18.614 103.86L19.282 100.398z', - }, - { - fill: '#000000', - data: - 'M-3.4 140.401C-3.4 140.401 1.8 140.001 0.6 141.601C-0.6 143.201 -3 142.401 -3 142.401L-3.4 140.401z', - }, - { - fill: '#992600', - data: - 'M-76.6 41.2C-76.6 41.2 -81 50 -81.4 53.2C-81.4 53.2 -80.6 44.4 -79.4 42.4C-78.2 40.4 -76.6 41.2 -76.6 41.2z', - }, - { - fill: '#992600', - data: - 'M-95 55.2C-95 55.2 -98.2 69.6 -97.8 72.4C-97.8 72.4 -99 60.8 -98.6 59.6C-98.2 58.4 -95 55.2 -95 55.2z', - }, - { - fill: '#cccccc', - data: - 'M-74.2 -19.4L-74.4 -16.2L-76.6 -16C-76.6 -16 -62.4 -3.4 -61.8 4.2C-61.8 4.2 -61 -4 -74.2 -19.4z', - }, - { - fill: '#000000', - data: - 'M-70.216 -18.135C-70.647 -18.551 -70.428 -19.296 -70.836 -19.556C-71.645 -20.072 -69.538 -20.129 -69.766 -20.845C-70.149 -22.051 -69.962 -22.072 -70.084 -23.348C-70.141 -23.946 -69.553 -25.486 -69.168 -25.926C-67.722 -27.578 -69.046 -30.51 -67.406 -32.061C-67.102 -32.35 -66.726 -32.902 -66.441 -33.32C-65.782 -34.283 -64.598 -34.771 -63.648 -35.599C-63.33 -35.875 -63.531 -36.702 -62.962 -36.61C-62.248 -36.495 -61.007 -36.625 -61.052 -35.784C-61.165 -33.664 -62.494 -31.944 -63.774 -30.276C-63.323 -29.572 -63.781 -28.937 -64.065 -28.38C-65.4 -25.76 -65.211 -22.919 -65.385 -20.079C-65.39 -19.994 -65.697 -19.916 -65.689 -19.863C-65.336 -17.528 -64.752 -15.329 -63.873 -13.1C-63.507 -12.17 -63.036 -11.275 -62.886 -10.348C-62.775 -9.662 -62.672 -8.829 -63.08 -8.124C-61.045 -5.234 -62.354 -2.583 -61.185 0.948C-60.978 1.573 -59.286 3.487 -59.749 3.326C-62.262 2.455 -62.374 2.057 -62.551 1.304C-62.697 0.681 -63.027 -0.696 -63.264 -1.298C-63.328 -1.462 -63.499 -3.346 -63.577 -3.468C-65.09 -5.85 -63.732 -5.674 -65.102 -8.032C-66.53 -8.712 -67.496 -9.816 -68.619 -10.978C-68.817 -11.182 -67.674 -11.906 -67.855 -12.119C-68.947 -13.408 -70.1 -14.175 -69.764 -15.668C-69.609 -16.358 -69.472 -17.415 -70.216 -18.135z', - }, - { - fill: '#000000', - data: - 'M-73.8 -16.4C-73.8 -16.4 -73.4 -9.6 -71 -8C-68.6 -6.4 -69.8 -7.2 -73 -8.4C-76.2 -9.6 -75 -10.4 -75 -10.4C-75 -10.4 -77.8 -10 -75.4 -8C-73 -6 -69.4 -3.6 -71 -3.6C-72.6 -3.6 -80.2 -7.6 -80.2 -10.4C-80.2 -13.2 -81.2 -17.3 -81.2 -17.3C-81.2 -17.3 -80.1 -18.1 -75.3 -18C-75.3 -18 -73.9 -17.3 -73.8 -16.4z', - }, - { - fill: '#ffffff', - stroke: '#000000', - strokeWidth: 0.1, - data: - 'M-74.6 2.2C-74.6 2.2 -83.12 -0.591 -101.6 2.8C-101.6 2.8 -92.569 0.722 -73.8 3C-63.5 4.25 -74.6 2.2 -74.6 2.2z', - }, - { - fill: '#ffffff', - stroke: '#000000', - strokeWidth: 0.1, - data: - 'M-72.502 2.129C-72.502 2.129 -80.748 -1.389 -99.453 0.392C-99.453 0.392 -90.275 -0.897 -71.774 2.995C-61.62 5.131 -72.502 2.129 -72.502 2.129z', - }, - { - fill: '#ffffff', - stroke: '#000000', - strokeWidth: 0.1, - data: - 'M-70.714 2.222C-70.714 2.222 -78.676 -1.899 -97.461 -1.514C-97.461 -1.514 -88.213 -2.118 -70.052 3.14C-60.086 6.025 -70.714 2.222 -70.714 2.222z', - }, - { - fill: '#ffffff', - stroke: '#000000', - strokeWidth: 0.1, - data: - 'M-69.444 2.445C-69.444 2.445 -76.268 -1.862 -93.142 -2.96C-93.142 -2.96 -84.803 -2.79 -68.922 3.319C-60.206 6.672 -69.444 2.445 -69.444 2.445z', - }, - { - fill: '#ffffff', - stroke: '#000000', - strokeWidth: 0.1, - data: - 'M45.84 12.961C45.84 12.961 44.91 13.605 45.124 12.424C45.339 11.243 73.547 -1.927 77.161 -1.677C77.161 -1.677 46.913 11.529 45.84 12.961z', - }, - { - fill: '#ffffff', - stroke: '#000000', - strokeWidth: 0.1, - data: - 'M42.446 13.6C42.446 13.6 41.57 14.315 41.691 13.121C41.812 11.927 68.899 -3.418 72.521 -3.452C72.521 -3.452 43.404 12.089 42.446 13.6z', - }, - { - fill: '#ffffff', - stroke: '#000000', - strokeWidth: 0.1, - data: - 'M39.16 14.975C39.16 14.975 38.332 15.747 38.374 14.547C38.416 13.348 58.233 -2.149 68.045 -4.023C68.045 -4.023 50.015 4.104 39.16 14.975z', - }, - { - fill: '#ffffff', - stroke: '#000000', - strokeWidth: 0.1, - data: - 'M36.284 16.838C36.284 16.838 35.539 17.532 35.577 16.453C35.615 15.373 53.449 1.426 62.28 -0.26C62.28 -0.26 46.054 7.054 36.284 16.838z', - }, - { - fill: '#cccccc', - data: - 'M4.6 164.801C4.6 164.801 -10.6 162.401 6.2 160.801C6.2 160.801 24.2 158.801 28.2 153.601C28.2 153.601 41.8 144.401 44.6 144.001C47.4 143.601 63.8 140.001 64.2 137.601C64.6 135.201 70.6 132.801 72.2 133.601C73.8 134.401 73.8 143.601 71 144.401C68.2 145.201 49.4 152.401 43 153.601C36.6 154.801 25 162.401 20.2 163.601C15.4 164.801 4.6 164.801 4.6 164.801z', - }, - { - fill: '#000000', - data: - 'M77.6 127.401C77.6 127.401 74.6 129.001 73.4 131.601C73.4 131.601 67 142.201 52.8 145.401C52.8 145.401 29.8 154.401 22 156.401C22 156.401 8.6 161.401 1.2 160.601C1.2 160.601 -5.8 160.801 0.4 162.401C0.4 162.401 20.6 160.401 24 158.601C24 158.601 39.6 153.401 42.6 150.801C45.6 148.201 63.8 143.201 66 141.201C68.2 139.201 78 130.801 77.6 127.401z', - }, - { - fill: '#000000', - data: - 'M18.882 158.911C18.882 158.911 24.111 158.685 22.958 160.234C21.805 161.784 19.357 160.91 19.357 160.91L18.882 158.911z', - }, - { - fill: '#000000', - data: - 'M11.68 160.263C11.68 160.263 16.908 160.037 15.756 161.586C14.603 163.136 12.155 162.263 12.155 162.263L11.68 160.263z', - }, - { - fill: '#000000', - data: - 'M1.251 161.511C1.251 161.511 6.48 161.284 5.327 162.834C4.174 164.383 1.726 163.51 1.726 163.51L1.251 161.511z', - }, - { - fill: '#000000', - data: - 'M-6.383 162.055C-6.383 162.055 -1.154 161.829 -2.307 163.378C-3.46 164.928 -5.908 164.054 -5.908 164.054L-6.383 162.055z', - }, - { - fill: '#000000', - data: - 'M35.415 151.513C35.415 151.513 42.375 151.212 40.84 153.274C39.306 155.336 36.047 154.174 36.047 154.174L35.415 151.513z', - }, - { - fill: '#000000', - data: - 'M45.73 147.088C45.73 147.088 51.689 143.787 51.155 148.849C50.885 151.405 46.362 149.749 46.362 149.749L45.73 147.088z', - }, - { - fill: '#000000', - data: - 'M54.862 144.274C54.862 144.274 62.021 140.573 60.287 146.035C59.509 148.485 55.493 146.935 55.493 146.935L54.862 144.274z', - }, - { - fill: '#000000', - data: - 'M64.376 139.449C64.376 139.449 68.735 134.548 69.801 141.21C70.207 143.748 65.008 142.11 65.008 142.11L64.376 139.449z', - }, - { - fill: '#000000', - data: - 'M26.834 155.997C26.834 155.997 32.062 155.77 30.91 157.32C29.757 158.869 27.308 157.996 27.308 157.996L26.834 155.997z', - }, - { - fill: '#ffffff', - stroke: '#000000', - strokeWidth: 0.1, - data: - 'M62.434 34.603C62.434 34.603 61.708 35.268 61.707 34.197C61.707 33.127 79.191 19.863 88.034 18.479C88.034 18.479 71.935 25.208 62.434 34.603z', - }, - { - fill: '#000000', - data: - 'M65.4 98.4C65.4 98.4 87.401 120.801 96.601 124.401C96.601 124.401 105.801 135.601 101.801 161.601C101.801 161.601 98.601 169.201 95.401 148.401C95.401 148.401 98.601 123.201 87.401 139.201C87.401 139.201 79 129.301 85.4 129.601C85.4 129.601 88.601 131.601 89.001 130.001C89.401 128.401 81.4 114.801 64.2 100.4C47 86 65.4 98.4 65.4 98.4z', - }, - { - fill: '#ffffff', - stroke: '#000000', - strokeWidth: 0.1, - data: - 'M7 137.201C7 137.201 6.8 135.401 8.6 136.201C10.4 137.001 104.601 143.201 136.201 167.201C136.201 167.201 91.001 144.001 7 137.201z', - }, - { - fill: '#ffffff', - stroke: '#000000', - strokeWidth: 0.1, - data: - 'M17.4 132.801C17.4 132.801 17.2 131.001 19 131.801C20.8 132.601 157.401 131.601 181.001 164.001C181.001 164.001 159.001 138.801 17.4 132.801z', - }, - { - fill: '#ffffff', - stroke: '#000000', - strokeWidth: 0.1, - data: - 'M29 128.801C29 128.801 28.8 127.001 30.6 127.801C32.4 128.601 205.801 115.601 229.401 148.001C229.401 148.001 219.801 122.401 29 128.801z', - }, - { - fill: '#ffffff', - stroke: '#000000', - strokeWidth: 0.1, - data: - 'M39 124.001C39 124.001 38.8 122.201 40.6 123.001C42.4 123.801 164.601 85.2 188.201 117.601C188.201 117.601 174.801 93 39 124.001z', - }, - { - fill: '#ffffff', - stroke: '#000000', - strokeWidth: 0.1, - data: - 'M-19 146.801C-19 146.801 -19.2 145.001 -17.4 145.801C-15.6 146.601 2.2 148.801 4.2 187.601C4.2 187.601 -3 145.601 -19 146.801z', - }, - { - fill: '#ffffff', - stroke: '#000000', - strokeWidth: 0.1, - data: - 'M-27.8 148.401C-27.8 148.401 -28 146.601 -26.2 147.401C-24.4 148.201 -10.2 143.601 -13 182.401C-13 182.401 -11.8 147.201 -27.8 148.401z', - }, - { - fill: '#ffffff', - stroke: '#000000', - strokeWidth: 0.1, - data: - 'M-35.8 148.801C-35.8 148.801 -36 147.001 -34.2 147.801C-32.4 148.601 -17 149.201 -29.4 171.601C-29.4 171.601 -19.8 147.601 -35.8 148.801z', - }, - { - fill: '#ffffff', - stroke: '#000000', - strokeWidth: 0.1, - data: - 'M11.526 104.465C11.526 104.465 11.082 106.464 12.631 105.247C28.699 92.622 61.141 33.72 116.826 28.086C116.826 28.086 78.518 15.976 11.526 104.465z', - }, - { - fill: '#ffffff', - stroke: '#000000', - strokeWidth: 0.1, - data: - 'M22.726 102.665C22.726 102.665 21.363 101.472 23.231 100.847C25.099 100.222 137.541 27.72 176.826 35.686C176.826 35.686 149.719 28.176 22.726 102.665z', - }, - { - fill: '#ffffff', - stroke: '#000000', - strokeWidth: 0.1, - data: - 'M1.885 108.767C1.885 108.767 1.376 110.366 3.087 109.39C12.062 104.27 15.677 47.059 59.254 45.804C59.254 45.804 26.843 31.09 1.885 108.767z', - }, - { - fill: '#ffffff', - stroke: '#000000', - strokeWidth: 0.1, - data: - 'M-18.038 119.793C-18.038 119.793 -19.115 121.079 -17.162 120.825C-6.916 119.493 14.489 78.222 58.928 83.301C58.928 83.301 26.962 68.955 -18.038 119.793z', - }, - { - fill: '#ffffff', - stroke: '#000000', - strokeWidth: 0.1, - data: - 'M-6.8 113.667C-6.8 113.667 -7.611 115.136 -5.742 114.511C4.057 111.237 17.141 66.625 61.729 63.078C61.729 63.078 27.603 55.135 -6.8 113.667z', - }, - { - fill: '#ffffff', - stroke: '#000000', - strokeWidth: 0.1, - data: - 'M-25.078 124.912C-25.078 124.912 -25.951 125.954 -24.369 125.748C-16.07 124.669 1.268 91.24 37.264 95.354C37.264 95.354 11.371 83.734 -25.078 124.912z', - }, - { - fill: '#ffffff', - stroke: '#000000', - strokeWidth: 0.1, - data: - 'M-32.677 130.821C-32.677 130.821 -33.682 131.866 -32.091 131.748C-27.923 131.439 2.715 98.36 21.183 113.862C21.183 113.862 9.168 95.139 -32.677 130.821z', - }, - { - fill: '#ffffff', - stroke: '#000000', - strokeWidth: 0.1, - data: - 'M36.855 98.898C36.855 98.898 35.654 97.543 37.586 97.158C39.518 96.774 160.221 39.061 198.184 51.927C198.184 51.927 172.243 41.053 36.855 98.898z', - }, - { - fill: '#ffffff', - stroke: '#000000', - strokeWidth: 0.1, - data: - 'M3.4 163.201C3.4 163.201 3.2 161.401 5 162.201C6.8 163.001 22.2 163.601 9.8 186.001C9.8 186.001 19.4 162.001 3.4 163.201z', - }, - { - fill: '#ffffff', - stroke: '#000000', - strokeWidth: 0.1, - data: - 'M13.8 161.601C13.8 161.601 13.6 159.801 15.4 160.601C17.2 161.401 35 163.601 37 202.401C37 202.401 29.8 160.401 13.8 161.601z', - }, - { - fill: '#ffffff', - stroke: '#000000', - strokeWidth: 0.1, - data: - 'M20.6 160.001C20.6 160.001 20.4 158.201 22.2 159.001C24 159.801 48.6 163.201 72.2 195.601C72.2 195.601 36.6 158.801 20.6 160.001z', - }, - { - fill: '#ffffff', - stroke: '#000000', - strokeWidth: 0.1, - data: - 'M28.225 157.972C28.225 157.972 27.788 156.214 29.678 156.768C31.568 157.322 52.002 155.423 90.099 189.599C90.099 189.599 43.924 154.656 28.225 157.972z', - }, - { - fill: '#ffffff', - stroke: '#000000', - strokeWidth: 0.1, - data: - 'M38.625 153.572C38.625 153.572 38.188 151.814 40.078 152.368C41.968 152.922 76.802 157.423 128.499 192.399C128.499 192.399 54.324 150.256 38.625 153.572z', - }, - { - fill: '#ffffff', - stroke: '#000000', - strokeWidth: 0.1, - data: - 'M-1.8 142.001C-1.8 142.001 -2 140.201 -0.2 141.001C1.6 141.801 55 144.401 85.4 171.201C85.4 171.201 50.499 146.426 -1.8 142.001z', - }, - { - fill: '#ffffff', - stroke: '#000000', - strokeWidth: 0.1, - data: - 'M-11.8 146.001C-11.8 146.001 -12 144.201 -10.2 145.001C-8.4 145.801 16.2 149.201 39.8 181.601C39.8 181.601 4.2 144.801 -11.8 146.001z', - }, - { - fill: '#ffffff', - stroke: '#000000', - strokeWidth: 0.1, - data: - 'M49.503 148.962C49.503 148.962 48.938 147.241 50.864 147.655C52.79 148.068 87.86 150.004 141.981 181.098C141.981 181.098 64.317 146.704 49.503 148.962z', - }, - { - fill: '#ffffff', - stroke: '#000000', - strokeWidth: 0.1, - data: - 'M57.903 146.562C57.903 146.562 57.338 144.841 59.264 145.255C61.19 145.668 96.26 147.604 150.381 178.698C150.381 178.698 73.317 143.904 57.903 146.562z', - }, - { - fill: '#ffffff', - stroke: '#000000', - strokeWidth: 0.1, - data: - 'M67.503 141.562C67.503 141.562 66.938 139.841 68.864 140.255C70.79 140.668 113.86 145.004 203.582 179.298C203.582 179.298 82.917 138.904 67.503 141.562z', - }, - { - fill: '#000000', - data: - 'M-43.8 148.401C-43.8 148.401 -38.6 148.001 -39.8 149.601C-41 151.201 -43.4 150.401 -43.4 150.401L-43.8 148.401z', - }, - { - fill: '#000000', - data: - 'M-13 162.401C-13 162.401 -7.8 162.001 -9 163.601C-10.2 165.201 -12.6 164.401 -12.6 164.401L-13 162.401z', - }, - { - fill: '#000000', - data: - 'M-21.8 162.001C-21.8 162.001 -16.6 161.601 -17.8 163.201C-19 164.801 -21.4 164.001 -21.4 164.001L-21.8 162.001z', - }, - { - fill: '#000000', - data: - 'M-117.169 150.182C-117.169 150.182 -112.124 151.505 -113.782 152.624C-115.439 153.744 -117.446 152.202 -117.446 152.202L-117.169 150.182z', - }, - { - fill: '#000000', - data: - 'M-115.169 140.582C-115.169 140.582 -110.124 141.905 -111.782 143.024C-113.439 144.144 -115.446 142.602 -115.446 142.602L-115.169 140.582z', - }, - { - fill: '#000000', - data: - 'M-122.369 136.182C-122.369 136.182 -117.324 137.505 -118.982 138.624C-120.639 139.744 -122.646 138.202 -122.646 138.202L-122.369 136.182z', - }, - { - fill: '#cccccc', - data: - 'M-42.6 211.201C-42.6 211.201 -44.2 211.201 -48.2 213.201C-50.2 213.201 -61.4 216.801 -67 226.801C-67 226.801 -54.6 217.201 -42.6 211.201z', - }, - { - fill: '#cccccc', - data: - 'M45.116 303.847C45.257 304.105 45.312 304.525 45.604 304.542C46.262 304.582 47.495 304.883 47.37 304.247C46.522 299.941 45.648 295.004 41.515 293.197C40.876 292.918 39.434 293.331 39.36 294.215C39.233 295.739 39.116 297.088 39.425 298.554C39.725 299.975 41.883 299.985 42.8 298.601C43.736 300.273 44.168 302.116 45.116 303.847z', - }, - { - fill: '#cccccc', - data: - 'M34.038 308.581C34.786 309.994 34.659 311.853 36.074 312.416C36.814 312.71 38.664 311.735 38.246 310.661C37.444 308.6 37.056 306.361 35.667 304.55C35.467 304.288 35.707 303.755 35.547 303.427C34.953 302.207 33.808 301.472 32.4 301.801C31.285 304.004 32.433 306.133 33.955 307.842C34.091 307.994 33.925 308.37 34.038 308.581z', - }, - { - fill: '#cccccc', - data: - 'M-5.564 303.391C-5.672 303.014 -5.71 302.551 -5.545 302.23C-5.014 301.197 -4.221 300.075 -4.558 299.053C-4.906 297.997 -6.022 298.179 -6.672 298.748C-7.807 299.742 -7.856 301.568 -8.547 302.927C-8.743 303.313 -8.692 303.886 -9.133 304.277C-9.607 304.698 -10.047 306.222 -9.951 306.793C-9.898 307.106 -10.081 317.014 -9.859 316.751C-9.24 316.018 -6.19 306.284 -6.121 305.392C-6.064 304.661 -5.332 304.196 -5.564 303.391z', - }, - { - fill: '#cccccc', - data: - 'M-31.202 296.599C-28.568 294.1 -25.778 291.139 -26.22 287.427C-26.336 286.451 -28.111 286.978 -28.298 287.824C-29.1 291.449 -31.139 294.11 -33.707 296.502C-35.903 298.549 -37.765 304.893 -38 305.401C-34.303 300.145 -32.046 297.399 -31.202 296.599z', - }, - { - fill: '#cccccc', - data: - 'M-44.776 290.635C-44.253 290.265 -44.555 289.774 -44.338 289.442C-43.385 287.984 -42.084 286.738 -42.066 285C-42.063 284.723 -42.441 284.414 -42.776 284.638C-43.053 284.822 -43.395 284.952 -43.503 285.082C-45.533 287.531 -46.933 290.202 -48.376 293.014C-48.559 293.371 -49.703 297.862 -49.39 297.973C-49.151 298.058 -47.431 293.877 -47.221 293.763C-45.958 293.077 -45.946 291.462 -44.776 290.635z', - }, - { - fill: '#cccccc', - data: - 'M-28.043 310.179C-27.599 309.31 -26.023 308.108 -26.136 307.219C-26.254 306.291 -25.786 304.848 -26.698 305.536C-27.955 306.484 -31.404 307.833 -31.674 313.641C-31.7 314.212 -28.726 311.519 -28.043 310.179z', - }, - { - fill: '#cccccc', - data: - 'M-13.6 293.001C-13.2 292.333 -12.492 292.806 -12.033 292.543C-11.385 292.171 -10.774 291.613 -10.482 290.964C-9.512 288.815 -7.743 286.995 -7.6 284.601C-9.091 283.196 -9.77 285.236 -10.4 286.201C-11.723 284.554 -12.722 286.428 -14.022 286.947C-14.092 286.975 -14.305 286.628 -14.38 286.655C-15.557 287.095 -16.237 288.176 -17.235 288.957C-17.406 289.091 -17.811 288.911 -17.958 289.047C-18.61 289.65 -19.583 289.975 -19.863 290.657C-20.973 293.364 -24.113 295.459 -26 303.001C-25.619 303.91 -21.488 296.359 -21.001 295.661C-20.165 294.465 -20.047 297.322 -18.771 296.656C-18.72 296.629 -18.534 296.867 -18.4 297.001C-18.206 296.721 -17.988 296.492 -17.6 296.601C-17.6 296.201 -17.734 295.645 -17.533 295.486C-16.296 294.509 -16.38 293.441 -15.6 292.201C-15.142 292.99 -14.081 292.271 -13.6 293.001z', - }, - { - fill: '#cccccc', - data: - 'M46.2 347.401C46.2 347.401 53.6 327.001 49.2 315.801C49.2 315.801 60.6 337.401 56 348.601C56 348.601 55.6 338.201 51.6 333.201C51.6 333.201 47.6 346.001 46.2 347.401z', - }, - { - fill: '#cccccc', - data: - 'M31.4 344.801C31.4 344.801 36.8 336.001 28.8 317.601C28.8 317.601 28 338.001 21.2 349.001C21.2 349.001 35.4 328.801 31.4 344.801z', - }, - { - fill: '#cccccc', - data: - 'M21.4 342.801C21.4 342.801 21.2 322.801 21.6 319.801C21.6 319.801 17.8 336.401 7.6 346.001C7.6 346.001 22 334.001 21.4 342.801z', - }, - { - fill: '#cccccc', - data: - 'M11.8 310.801C11.8 310.801 17.8 324.401 7.8 342.801C7.8 342.801 14.2 330.601 9.4 323.601C9.4 323.601 12 320.201 11.8 310.801z', - }, - { - fill: '#cccccc', - data: - 'M-7.4 342.401C-7.4 342.401 -8.4 326.801 -6.6 324.601C-6.6 324.601 -6.4 318.201 -6.8 317.201C-6.8 317.201 -2.8 311.001 -2.6 318.401C-2.6 318.401 -1.2 326.201 1.6 330.801C1.6 330.801 5.2 336.201 5 342.601C5 342.601 -5 312.401 -7.4 342.401z', - }, - { - fill: '#cccccc', - data: - 'M-11 314.801C-11 314.801 -17.6 325.601 -19.4 344.601C-19.4 344.601 -20.8 338.401 -17 324.001C-17 324.001 -12.8 308.601 -11 314.801z', - }, - { - fill: '#cccccc', - data: - 'M-32.8 334.601C-32.8 334.601 -27.8 329.201 -26.4 324.201C-26.4 324.201 -22.8 308.401 -29.2 317.001C-29.2 317.001 -29 325.001 -37.2 332.401C-37.2 332.401 -32.4 330.001 -32.8 334.601z', - }, - { - fill: '#cccccc', - data: - 'M-38.6 329.601C-38.6 329.601 -35.2 312.201 -34.4 311.401C-34.4 311.401 -32.6 308.001 -35.4 311.201C-35.4 311.201 -44.2 330.401 -48.2 337.001C-48.2 337.001 -40.2 327.801 -38.6 329.601z', - }, - { - fill: '#cccccc', - data: - 'M-44.4 313.001C-44.4 313.001 -32.8 290.601 -54.6 316.401C-54.6 316.401 -43.6 306.601 -44.4 313.001z', - }, - { - fill: '#cccccc', - data: - 'M-59.8 298.401C-59.8 298.401 -55 279.601 -52.4 279.801C-52.4 279.801 -44.2 270.801 -50.8 281.401C-50.8 281.401 -56.8 291.001 -56.2 300.801C-56.2 300.801 -56.8 291.201 -59.8 298.401z', - }, - { - fill: '#cccccc', - data: - 'M270.5 287C270.5 287 258.5 277 256 273.5C256 273.5 269.5 292 269.5 299C269.5 299 272 291.5 270.5 287z', - }, - { - fill: '#cccccc', - data: - 'M276 265C276 265 255 250 251.5 242.5C251.5 242.5 278 272 278 276.5C278 276.5 278.5 267.5 276 265z', - }, - { - fill: '#cccccc', - data: - 'M293 111C293 111 281 103 279.5 105C279.5 105 290 111.5 292.5 120C292.5 120 291 111 293 111z', - }, - { - fill: '#cccccc', - data: 'M301.5 191.5L284 179.5C284 179.5 303 196.5 303.5 200.5L301.5 191.5z', - }, - { stroke: '#000000', data: 'M-89.25 169L-67.25 173.75' }, - { stroke: '#000000', data: 'M-39 331C-39 331 -39.5 327.5 -48.5 338' }, - { stroke: '#000000', data: 'M-33.5 336C-33.5 336 -31.5 329.5 -38 334' }, - { stroke: '#000000', data: 'M20.5 344.5C20.5 344.5 22 333.5 10.5 346.5' }, -]; diff --git a/test/assets/worldMap.ts b/test/assets/worldMap.ts deleted file mode 100644 index 2efc4ce89..000000000 --- a/test/assets/worldMap.ts +++ /dev/null @@ -1,346 +0,0 @@ -export default { - shapes: { - AE: - 'M604.196,161.643l0.514-0.129l0,0.772l2.188-0.386l2.189,0l1.672,0.129l1.803-1.802l2.058-1.802l1.674-1.673l0.518,0.900l0.385,2.189l-1.417,0l-0.258,1.802l0.517,0.386l-1.159,0.515l-0.129,1.029l-0.773,1.159l0,1.030l-0.514,0.644l-8.110-1.416l-1.031-2.704l0.127,0.643z', - AF: - 'M630.069,130.876l2.832,1.030l2.059-0.257l0.517-1.288l2.058-0.386l1.546-0.772l0.515-2.188l2.317-0.516l0.387-1.030l1.285,0.774l0.902,0.128l1.416,0l2.059,0.515l0.773,0.385l2.059-0.900l0.901,0.515l0.773-1.287l1.674,0.128l0.386-0.387l0.256-1.157l1.160-0.903l1.543,0.645l-0.384,0.772l0.901,0.129l-0.259,2.317l1.030,0.900l0.904-0.643l1.285-0.257l1.674-1.159l1.802,0.129l2.832,0l0.387,0.773l-1.545,0.385l-1.416,0.516l-3.090,0.256l-2.833,0.517l-1.545,1.287l0.645,1.029l0.257,1.416l-1.287,1.159l0.129,1.029l-0.773,0.902l-2.575,0l1.030,1.673l-1.673,0.772l-1.158,1.545l0.129,1.674l-1.031,0.772l-1.029-0.257l-2.061,0.386l-0.257,0.644l-2.058,0l-1.417,1.544l-0.129,2.317l-3.476,1.159l-1.931-0.257l-0.514,0.643l-1.674-0.386l-2.704,0.386l-4.504-1.415l2.445-2.447l-0.129-1.673l-2.060-0.515l-0.256-1.674l-0.902-2.188l1.158-1.416l-1.158-0.386l0.773-1.930l-1.029,3.477z', - AL: - 'M520.651,114.27l-0.257,0.900l0.385,1.160l1.029,0.643l0,0.644l-0.901,0.386l-0.128,0.901l-1.288,1.287l-0.386-0.128l-0.127-0.644l-1.417-0.900l-0.259-1.288l0.259-1.803l0.256-0.901l-0.384-0.386l-0.258-0.901l1.287-1.288l0.129,0.516l0.771-0.258l0.516,0.773l0.643,0.257l-0.130-1.030z', - AM: - 'M582.697,116.33l3.605,-0.515l0.642,0.772l1.032,0.386l-0.516,0.773l1.416,0.900l-0.772,0.902l1.159,0.643l1.158,0.516l0.129,1.801l-1.029,0.129l-1.032,-1.544l0,-0.515l-1.287,0.129l-0.771,-0.772l-0.516,0l-1.029,-0.773l-2.059,-0.643l0.256,-1.288l0.386,0.901z', - AO: - 'M497.994,242.615l-0.643-2.060l1.030-1.159l0.900-0.515l0.902,1.031l-0.902,0.516l-0.514,0.642l0,1.159l0.773-0.386zM496.836,273.64l-0.257-1.804l0.385-2.317l0.900-2.445l0.130-1.158l0.901-2.447l0.643-1.157l1.545-1.674l0.902-1.288l0.257-1.931l-0.129-1.544l-0.771-0.902l-0.775-1.673l-0.642-1.674l0.129-0.515l0.772-1.029l-0.772-2.704l-0.516-1.802l-1.414-1.674l0.257-0.515l1.157-0.387l0.774,0.131l0.900-0.389l7.982,0.131l0.643,1.930l0.771,1.674l0.645,0.773l1.031,1.415l1.801-0.128l0.900-0.387l1.418,0.387l0.514-0.772l0.644-1.545l1.673-0.128l0.128-0.388l1.417,0l-0.258,0.902l3.219,0l0.128,1.672l0.514,1.031l-0.385,1.673l0.129,1.674l0.900,1.030l-0.129,3.091l0.645-0.131l1.158,0l1.674-0.385l1.287,0.128l0.257,0.902l-0.257,1.286l0.387,1.287l-0.387,0.903l0.257,1.028l-5.536-0.127l-0.128,8.625l1.804,2.187l1.673,1.674l-4.892,1.158l-6.566-0.385l-1.801-1.287l-10.944,0.128l-0.384,0.128l-1.674-1.159l-1.672-0.128l-1.674,0.515l1.288-0.516z', - AR: - 'M319.448,295.781l1.288,1.544v2.188l-2.319,1.416l-1.801,1.158l-2.961,2.576l-3.605,3.732l-0.771,2.188l-0.645,2.702v2.705l-0.643,0.643l-0.129,1.674l-0.257,1.418l3.475,2.316l-0.387,1.802l1.675,1.287l-0.129,1.288l-2.574,3.475l-3.991,1.418l-5.406,0.512l-2.961-0.256l0.514,1.674l-0.514,1.931l0.514,1.415l-1.673,0.902l-2.703,0.385l-2.575-1.027l-1.029,0.77l0.386,2.705l1.801,0.771l1.417-0.9l0.901,1.416l-2.575,0.901l-2.188,1.673l-0.386,2.705l-0.643,1.414h-2.448l-2.188,1.416l-0.772,1.932l2.704,2.06l2.574,0.517l-0.901,2.444l-3.218,1.545l-1.803,3.09l-2.445,1.03l-1.031,1.287l0.774,2.832l1.802,1.543l-1.03-0.127l-2.574-0.387l-6.436-0.386l-1.16-1.545v-2.06l-1.801,0.129l-0.902-0.902l-0.258-2.831l2.06-1.288l0.901-1.674l-0.386-1.288l1.546-2.315l0.9-3.605l-0.257-1.545l1.158-0.516l-0.258-1.029l-1.287-0.514l0.901-1.158l-1.157-1.03l-0.645-3.089l1.03-0.516l-0.385-3.348l0.513-2.703l0.773-2.447l1.673-1.029l-0.9-2.574v-2.445l2.06-1.803v-2.189l1.415-2.701l0.129-2.447l-0.772-0.514l-1.287-4.637l1.672-2.83l-0.257-2.575l1.03-2.446l1.802-2.574l1.802-1.673l-0.772-1.03l0.515-0.9v-4.379l2.96-1.414l0.902-2.704l-0.386-0.772l2.316-2.447l3.477,0.645l1.544,2.061l1.03-2.188l3.089,0.127l0.515,0.516l4.892,4.377l2.188,0.387l3.348,2.059l2.703,1.03l0.386,1.157l-2.574,4.121l2.702,0.771l2.961,0.387l2.189-0.387l2.446-2.059l0.386-2.445L319.448,295.781zM282.761,371.99l3.475,1.674l3.733,0.643l-1.159,1.416l-2.574,0.131l-1.416-1.031h-1.546h-2.96l0.129-5.924l0.901,1.16l-1.417-1.931L282.761,371.99z', - AT: - 'M510.996,97.278l-0.257,1.158l-1.545,0l0.643,0.643l-0.900,1.674l-0.515,0.515l-2.446,0l-1.289,0.644l-2.315-0.258l-3.734-0.644l-0.644-0.900l-2.703,0.386l-0.258,0.514l-1.672-0.386l-1.416,0l-1.160-0.514l0.385-0.644l-0.128-0.515l0.903-0.128l1.285,0.772l0.387-0.772l2.446,0.128l1.931-0.515l1.287,0.128l0.773,0.515l0.258-0.386l-0.387-1.802l1.030-0.386l0.901-1.158l2.058,0.772l1.417-1.030l1.030-0.258l2.061,0.901l1.286-0.129l1.158,0.516l-0.127,0.256l-0.257-0.903z', - AU: - 'M863.067,336.975l1.674,0.129l0.129,3.218l-0.900,0.901l-0.258,2.188l-0.900-0.772l-1.934,1.931l-0.514-0.129l-1.672-0.129l-1.675-2.316l-0.385-1.803l-1.545-2.318l0.127-1.287l1.674,0.259l2.576,0.901l1.545-0.258l-2.058,0.515zM805.011,313.803l-2.832,1.288l-2.317,0.643l-0.513,1.416l-1.034,1.159l-2.185,0l-1.803,0.256l-2.318-0.513l-1.930,0.386l-1.930,0.127l-1.546,1.417l-0.772-0.128l-1.416,0.772l-1.287,0.772l-1.932-0.128l-1.800,0l-2.834-1.674l-1.416-0.514l0-1.545l1.289-0.387l0.515-0.515l-0.131-1.029l0.387-1.932l-0.256-1.545l-1.547-2.702l-0.386-1.546l0.129-1.545l-1.030-1.801l-0.127-0.773l-1.160-1.030l-0.387-2.058l-1.545-2.189l-0.384-1.160l1.287,1.160l-1.029-2.447l1.416,0.774l0.771,1.030l0-1.417l-1.416-2.061l-0.258-0.900l-0.644-0.773l0.386-1.545l0.516-0.644l0.387-1.415l-0.258-1.546l1.029-1.930l0.258,2.060l1.158-1.932l2.188-0.900l1.287-1.160l2.060-0.901l1.159-0.257l0.773,0.387l2.188-1.029l1.544-0.258l0.516-0.644l0.643-0.257l1.545,0.128l2.832-0.901l1.418-1.160l0.640-1.414l1.676-1.416l0.129-1.030l0-1.417l1.930-2.318l1.158,2.318l1.031-0.514l-0.902-1.287l0.902-1.287l1.156,0.516l0.260-2.061l1.545-1.289l0.643-1.028l1.289-0.516l0.127-0.773l1.158,0.386l0-0.643l1.158-0.387l1.416-0.385l1.930,1.157l1.547,1.675l1.671,0l1.676,0.258l-0.515-1.545l1.287-2.060l1.158-0.772l-0.385-0.643l1.158-1.545l1.672-1.031l1.289,0.385l2.317-0.514l-0.129-1.416l-1.932-0.900l1.418-0.388l1.801,0.775l1.416,1.029l2.316,0.772l0.774-0.387l1.674,0.902l1.544-0.773l1.030,0.258l0.644-0.516l1.158,1.289l-0.644,1.416l-1.029,1.157l-0.903,0l0.260,1.160l-0.773,1.286l-0.901,1.289l0.127,0.772l2.190,1.545l2.058,0.900l1.418,0.902l1.930,1.544l0.771,0l1.418,0.773l0.387,0.772l2.574,0.900l1.801-0.900l0.516-1.416l0.513-1.289l0.387-1.415l0.772-2.188l-0.385-1.286l0.127-0.775l-0.256-1.542l0.387-2.062l0.513-0.514l-0.386-0.901l0.644-1.417l0.516-1.414l0-0.772l1.029-1.032l0.772,1.288l0.130,1.674l0.641,0.385l0.131,1.029l1.029,1.417l0.258,1.544l-0.129,1.031l0.902,2.061l1.801-1.031l0.903,1.158l1.285,1.031l-0.256,1.158l0.515,2.317l0.387,1.416l0.641,0.257l0.773,2.319l-0.256,1.414l0.901,1.805l2.961,1.414l1.800,1.288l1.803,1.159l-0.258,0.642l1.545,1.674l1.030,2.961l1.031-0.642l1.158,1.286l0.643-0.516l0.386,2.961l1.932,1.544l1.287,1.030l2.061,2.189l0.771,2.189l0.129,1.545l-0.260,1.674l1.289,2.316l-0.129,2.317l-0.515,1.287l-0.645,2.447l0,1.545l-0.513,1.930l-1.159,2.446l-2.058,1.288l-0.903,2.060l-0.900,1.415l-0.902,2.317l-1.030,1.288l-0.642,2.060l-0.387,1.802l0.129,0.900l-1.545,0.902l-2.961,0.128l-2.445,1.031l-1.287,1.030l-1.674,1.157l-2.188-1.157l-1.675-0.515l0.517-1.287l-1.547,0.516l-2.316,1.929l-2.316-0.773l-1.547-0.385l-1.545-0.258l-2.572-0.772l-1.803-1.674l-0.516-2.060l-0.644-1.288l-1.287-1.157l-2.575-0.258l0.903-1.287l-0.645-2.060l-1.287,1.931l-2.445,0.387l1.416-1.416l0.386-1.545l1.030-1.288l-0.258-2.059l-2.188,2.316l-1.673,0.902l-1.032,2.189l-2.058-1.159l0.129-1.416l-1.674-1.932l-1.545-1.029l0.516-0.643l-3.348-1.675l-1.932,0l-2.574-1.286l-4.893,0.256l-3.474,0.902l-3.090,0.902l2.574,0.130z', - AZ: - 'M590.292,114.27l0.643,0l1.931,1.673l1.158,0.129l0.516-0.644l1.545-1.030l1.416,1.417l1.417,1.802l1.286,0.129l0.774,0.773l-2.190,0.257l-0.514,2.059l-0.386,0.901l-1.031,0.644l0,1.416l-0.643,0.129l-1.674-1.417l0.902-1.415l-0.773-0.773l-1.030,0.258l-3.089,1.930l-0.129-1.801l-1.158-0.516l-1.159-0.643l0.772-0.902l-1.416-0.900l0.516-0.773l-1.032-0.386l-0.642-0.772l0.129,0l0.644-0.387l1.930,0.772l1.545,0.130l0.258-0.258l-1.287-1.545l-0.771,0.257zM589.521,122.637l-1.804-0.386l-1.415-1.288l-0.387-1.028l0.516,0l0.771,0.772l1.287-0.129l0,0.515l-1.032-1.544z', - BA: - 'M516.403,106.159l1.030,0l-0.645,1.159l1.289,1.030l-0.389,1.287l-1.158,0.387l-0.900,0.515l-0.387,1.545l-2.445-1.030l-1.031-1.159l-0.901-0.514l-1.286-1.031l-0.643-0.901l-1.290-1.158l0.516-1.159l1.031,0.643l0.643-0.643l1.159,0l2.316,0.386l1.931,0l-1.160-0.643z', - BD: - 'M714.901,167.564l-0.13,1.932l-0.899-0.387l0.127,2.189l-0.771-1.417l-0.129-1.415l-0.514-1.287l-1.031-1.545l-2.575-0.129l0.259,1.159l-0.771,1.544l-1.158-0.644l-0.389,0.516l-0.772-0.258l-1.028-0.258l-0.516-2.188l-0.9-2.059l0.514-1.674L702.544,161l0.514-1.031l1.803-1.03l-2.061-1.415l1.031-1.803l2.061,1.159l1.285,0.128l0.26,1.931l2.574,0.386l2.574-0.128l1.545,0.515l-1.289,2.317l-1.158,0.129l-0.9,1.545l1.545,1.416l0.387-1.802h0.771L714.901,167.564z', - BE: - 'M474.179,88.652l1.932,0.258l2.574-0.643l1.673,1.158l1.416,0.644l-0.258,1.930l-0.644,0l-0.385,1.544l-2.318-1.286l-1.416,0.257l-1.801-1.287l-1.288-1.029l-1.287,0l-0.385-1.031l-2.187,0.515z', - BF: - 'M457.573,201.035l-1.802,-0.773l-1.287,0.129l-0.902,0.644l-1.286,-0.515l-0.387,-0.902l-1.287,-0.643l-0.128,-1.545l0.771,-1.159l-0.128,-0.900l2.189,-2.189l0.385,-1.802l0.773,-0.644l1.287,0.257l1.159,-0.514l0.257,-0.645l2.189,-1.285l0.514,-0.774l2.446,-1.158l1.545,-0.387l0.644,0.516l1.673,0l-0.129,1.287l0.258,1.287l1.545,1.673l0.128,1.417l3.091,0.515l0,1.930l-0.645,0.774l-1.287,0.257l-0.515,1.159l-1.030,0.256l-2.317,0l-1.288,-0.256l-0.770,0.514l-1.289,-0.258l-4.634,0.129l-0.129,1.545l-0.386,-2.060z', - BG: - 'M526.314,107.833l0.773,1.030l1.031-0.129l2.059,0.386l3.990,0.130l1.287-0.644l3.219-0.644l1.930,1.030l1.544,0.258l-1.416,1.158l-0.900,1.931l0.772,1.416l-2.317-0.257l-2.705,0.772l0,1.417l-2.445,0.256l-1.930-1.029l-2.187,0.773l-1.932-0.130l-0.258-1.674l-1.287-0.900l0.385-0.387l-0.256-0.386l0.515-0.772l1.030-0.901l-1.415-1.158l-0.259-0.902l-0.772,0.644z', - BI: - 'M544.208,239.14l-0.130-3.347l-0.643-1.159l1.673,0.258l0.773-1.545l1.415,0.128l0.131,1.030l0.642,0.643l0,0.903l-0.642,0.513l-1.030,1.416l-1.031,1.032l1.158-0.128z', - BJ: - 'M472.505,210.174l-2.188,0.258l-0.773-1.803l0.131-6.307l-0.516-0.515l-0.129-1.287l-0.900-0.902l-0.775-0.900l0.259-1.417l1.030-0.256l0.515-1.159l1.287-0.257l0.645-0.774l0.901-0.773l0.901-0.127l2.059,1.674l-0.129,0.771l0.643,1.673l-0.514,1.031l0.258,0.773l-1.288,1.672l-0.901,0.773l-0.386,1.674l0,1.802l0.130-4.376z', - BN: - 'M772.829,214.809l1.16-1.029l2.314-1.416l-0.127,1.287l-0.26,1.674h-1.285l-0.516,0.902L772.829,214.809z', - BO: - 'M295.89,286.383l-3.089-0.127l-1.030,2.187l-1.544-2.060l-3.477-0.644l-2.316,2.447l-1.932,0.386l-1.028-3.733l-1.417-2.960l0.773-2.576l-1.417-1.157l-0.387-1.933l-1.286-1.932l1.673-2.830l-1.158-2.318l0.643-0.901l-0.515-1.029l1.159-1.287l0-2.317l0.128-1.931l0.644-0.901l-2.445-4.248l2.060,0.127l1.415,0l0.515-0.771l2.446-1.160l1.416-1.029l3.476-0.386l-0.258,1.930l0.387,1.159l-0.258,1.802l2.960,2.317l2.962,0.515l1.030,1.030l1.801,0.515l1.159,0.772l1.673,0l1.545,0.773l0.128,1.544l0.516,0.773l0.128,1.158l-0.772,0l1.031,3.219l5.148,0.131l-0.386,1.542l0.258,1.030l1.416,0.771l0.643,1.676l-0.386,2.061l-0.772,1.158l0.257,1.544l-0.901,0.643l0-0.902l-2.575-1.285l-2.446-0.130l-4.634,0.772l-1.416,2.447l0,1.414l-1.030,3.219l0.515,0.515z', - BR: - 'M310.05,308.396l3.605-3.732l2.961-2.576l1.801-1.158l2.319-1.416v-2.188l-1.288-1.544l-1.416,0.516l0.515-1.546l0.386-1.545v-1.544l-0.9-0.516l-1.031,0.516l-1.028-0.129l-0.259-1.031l-0.256-2.443l-0.516-0.902l-1.802-0.643l-1.159,0.514l-2.831-0.514l0.128-3.736l-0.772-1.414l0.901-0.643l-0.257-1.545l0.771-1.158l0.386-2.061l-0.643-1.676l-1.416-0.771l-0.258-1.029l0.386-1.543l-5.148-0.131l-1.031-3.219h0.772l-0.128-1.158l-0.516-0.772l-0.128-1.544l-1.545-0.773h-1.673l-1.159-0.771l-1.801-0.516l-1.03-1.029l-2.962-0.516l-2.96-2.316l0.258-1.803l-0.387-1.158l0.258-1.931l-3.476,0.386l-1.416,1.029l-2.446,1.16l-0.515,0.771h-1.415l-2.06-0.127l-1.416,0.383l-1.287-0.256l0.256-4.119l-2.317,1.545h-2.317l-1.03-1.416l-1.801-0.129l0.644-1.158l-1.546-1.674l-1.158-2.445l0.772-0.516v-1.158l1.545-0.773l-0.257-1.416l0.772-0.9l0.129-1.289l3.089-1.801l2.188-0.516l0.386-0.514l2.446,0.129l1.159-7.338l0.129-1.159l-0.515-1.544l-1.159-1.03v-1.931l1.545-0.387l0.515,0.258l0.129-1.029l-1.545-0.258l-0.129-1.674h5.278l0.9-0.902l0.773,0.902l0.515,1.545l0.516-0.387l1.544,1.416l2.06-0.129l0.515-0.771l1.93-0.645l1.159-0.515l0.257-1.159l1.931-0.771l-0.128-0.514l-2.188-0.26l-0.387-1.672v-1.805l-1.158-0.643l0.514-0.257l2.06,0.257l2.059,0.773l0.774-0.643l1.93-0.516l3.09-0.902l0.9-1.029l-0.257-0.772l1.287-0.129l0.644,0.644l-0.257,1.158l0.9,0.387l0.644,1.287l-0.772,0.902l-0.515,2.316l0.773,1.287l0.128,1.287l1.674,1.287l1.288,0.129l0.386-0.516l0.771-0.128l1.288-0.515l0.901-0.645l1.416,0.26l0.643-0.131l1.546,0.131l0.258-0.518l-0.517-0.514l0.259-0.773l1.158,0.26l1.159-0.26l1.545,0.516l1.287,0.516l0.771-0.645l0.644,0.129l0.387,0.771l1.287-0.256l1.03-1.031l0.771-1.93l1.545-2.446l1.029-0.128l0.646,1.415l1.544,4.763l1.416,0.387v1.931l-1.932,2.188l0.773,0.772l4.763,0.388l0.128,2.701l2.06-1.674l3.348,0.902l4.505,1.674l1.288,1.545l-0.387,1.545l3.09-0.9l5.277,1.414h3.991l3.99,2.189l3.476,2.961l2.06,0.771l2.317,0.129l0.9,0.901l0.901,3.476l0.516,1.545l-1.159,4.504l-1.287,1.676l-3.861,3.863l-1.674,2.959l-2.06,2.316l-0.643,0.129l-0.773,1.932l0.257,5.02l-0.772,4.25l-0.256,1.672l-0.902,1.158l-0.515,3.605l-2.703,3.475l-0.388,2.833l-2.187,1.158l-0.645,1.546h-2.96l-4.249,1.027l-1.931,1.289l-2.96,0.772l-3.219,2.06l-2.188,2.703l-0.386,2.061l0.386,1.416l-0.515,2.703l-0.645,1.416l-1.803,1.416l-2.96,4.764l-2.446,2.189l-1.802,1.156l-1.287,2.574l-1.673,1.545l-0.771-1.545l1.157-1.286l-1.545-1.804l-2.188-1.414l-2.702-1.805l-1.03,0.129l-2.704-2.059L310.05,308.396z', - BT: - 'M712.198,152.117l1.158,0.901l-0.257,1.674l-2.188,0l-2.189,-0.129l-1.672,0.386l-2.447,-1.029l-0.129,-0.516l1.804,-1.931l1.414,-0.773l1.930,0.645l1.416,0.128l-1.160,-0.644z', - BW: - 'M534.296,276.857l0.516,0.516l0.900,1.544l3.089,2.962l1.158,0.256l0,1.030l0.772,1.674l2.061,0.385l1.673,1.290l-3.734,1.929l-2.445,2.059l-0.901,1.804l-0.773,1.030l-1.545,0.128l-0.386,1.287l-0.258,0.901l-1.801,0.645l-2.188,-0.129l-1.288,-0.773l-1.159,-0.387l-1.287,0.644l-0.642,1.286l-1.287,0.775l-1.290,1.287l-1.929,0.256l-0.645,-0.901l0.258,-1.673l-1.544,-2.575l-0.772,-0.386l0,-7.852l2.574,-0.130l0.129,-9.654l2.060,0l4.119,-1.030l1.029,1.158l1.674,-1.028l0.901,0l1.416,-0.645l0.515,0.259l-1.030,-2.058z', - BY: - 'M528.503,81.701l2.574,0l2.961,-0.901l0.643,-1.545l2.189,-0.901l-0.258,-1.159l1.674,-0.514l2.831,-1.031l2.833,0.644l0.387,0.772l1.416,-0.385l2.703,0.643l0.258,1.287l-0.645,0.644l1.672,1.802l1.160,0.515l-0.129,0.515l1.803,0.387l0.772,0.772l-1.030,0.643l-2.187,-0.128l-0.516,0.257l0.644,0.901l0.643,1.674l-2.318,0.129l-0.900,0.643l-0.128,1.416l-1.031,-0.258l-2.446,0.129l-0.772,-0.643l-1.030,0.386l-0.900,-0.386l-2.189,0l-2.959,-0.644l-2.706,-0.258l-2.187,0.129l-1.417,0.644l-1.286,0.129l-0.129,-1.159l-0.772,-1.287l1.672,-0.516l0,-1.029l-0.771,-1.029l0.129,1.288z', - BZ: - 'M225.09,179.022l0,-0.387l0.257,-0.129l0.515,0.258l1.030,-1.544l0.515,-0.130l0,0.387l0.515,0.128l-0.129,0.645l-0.386,1.159l0.258,0.513l-0.258,0.902l0.128,0.258l-0.256,1.416l-0.644,0.643l-0.387,0.129l-0.643,0.901l-0.772,0l0.257,-3.089l0,2.060z', - CA: - 'M212.989,24.93l-1.416,1.159l-3.862-0.257l-3.347-0.644l1.417-1.288l3.99-0.772l2.317,1.03l-0.901-0.772L212.989,24.93zM212.474,18.107l-1.287,0.13l-5.02-0.13l-0.772-0.772h5.535l1.802,0.515l0.258-0.257L212.474,18.107zM204.622,14.761l3.218,0.901l-0.772,1.03l-3.991,0.515l-2.188-0.644l-1.159-0.901l-0.257-1.159l3.604,0.129l-1.545-0.129L204.622,14.761zM227.793,26.604l-4.377-0.387l-7.208-0.9l-0.901-1.417l-0.258-1.287l-2.703-1.287l-5.664-0.257l-3.09-0.901l1.03-1.031l5.535,0.13l2.962,0.901h5.406l2.317,0.901l-0.643,1.029l3.089,0.515l1.673,0.643l3.605,0.13l3.99,0.257L236.804,23l5.535-0.129L246.716,23l2.832,1.029l0.644,1.159l-1.674,0.644l-3.991,0.644l-3.475-0.387l-7.724,0.387l5.535-0.128L227.793,26.604zM165.489,16.434l3.862,0.386l-0.902,0.901l-5.02,0.772l-3.991-0.9l2.188-0.901L165.489,16.434zM166.261,14.632l3.604,0.644l-3.347,0.515h-4.505l0.128-0.387l2.704-0.901L166.261,14.632zM205.137,40.636l2.703,1.158l-1.673,0.902l-3.605-1.031l-2.188,0.516l-3.09-0.387l1.803-1.673l1.931-1.159l2.059,0.643l-2.06-1.031L205.137,40.636zM315.458,88.781l-1.417,1.673l-1.802,2.317l1.802-0.9l1.802,0.643l-1.029,0.902l2.446,0.772l1.287-0.772l2.574,0.901l-0.772,1.93l1.932-0.386l0.257,1.417l0.9,1.673l-1.157,2.317l-1.288,0.129l-1.673-0.515l0.515-2.189l-0.771-0.386l-3.09,2.317h-1.545l1.801-1.287l-2.573-0.644l-2.832,0.13l-5.278-0.13l-0.386-0.772l1.674-0.901l-1.159-0.773l2.317-1.673l2.702-4.248l1.675-1.545l2.316-0.901l1.288,0.129l0.516-0.772L315.458,88.781zM239.25,51.578l2.96,0.901l3.09,0.901l0.258,1.287l1.93-0.257l1.931,0.9l-2.316,0.903l-4.249-0.774l-1.544-1.158l-2.575,1.416l-3.861,1.416l-0.902-1.544l-3.733,0.257l2.317-1.416l0.386-2.06l0.901-2.445l1.931,0.257l0.515,1.158l1.417-0.514l-1.544-0.772L239.25,51.578zM218.525,6.393l7.08-0.643l5.278-0.386l5.921-0.13l3.604-1.415l11.199-0.773l9.656,0.129l7.723-0.386l18.924,0.514l10.555,1.802L291.9,6.264l-6.437,0.515l-2.445,0.644h5.792L278.126,9.74l-10.169,2.704l-9.913,0.9l3.734,0.258l-1.931,0.515l2.317,1.287l-6.694,1.674l-1.287,1.159l-3.863,0.772l0.387,0.643l3.604,0.258v0.644l-6.049,1.158l-7.081-0.643l-7.981,0.386l-9.012-0.515l-0.385-1.288l5.02-0.643l-1.158-0.902l2.187-0.9l6.437,0.9l-7.981-2.316l2.188-1.03l4.763-0.644l0.773-0.901l-3.862-1.03l-1.159-1.416l7.338,0.129l6.437-0.644l-15.577-0.128l-4.762-1.031l-5.407-1.802l0.515,0.901L218.525,6.393zM253.024,32.01l2.574-1.03l5.922,1.417l3.734,1.287l0.385,1.158l5.02-0.643l2.833,1.674l6.437,1.158l2.317,1.03l2.574,2.575l-4.891,1.158l6.307,1.803l4.248,0.643l3.862,2.446l4.248,0.128l-0.773,1.932l-4.763,3.089l-3.347-1.158l-4.248-2.575l-3.476,0.386l-0.257,1.545l2.832,1.545l3.605,1.287l1.159,0.644l1.673,2.704l-0.902,1.93l-3.347-0.772l-6.821-2.061l3.862,2.318l2.702,1.545l0.516,1.03l-7.339-1.159l-5.793-1.545l-3.218-1.286l0.903-0.774l-3.991-1.415l-3.992-1.287l0.129,0.772l-7.853,0.386l-2.188-0.901l1.675-1.931l5.149-0.129l5.535-0.257l-0.901-1.031l0.901-1.287l3.475-2.702l-0.772-1.159l-1.03-0.901l-4.12-1.288l-5.406-0.901l1.674-0.772l-2.832-1.674l-2.317-0.129l-2.189-0.9l-1.416,0.772l-4.891,0.385l-9.784-0.643l-5.664-0.772l-4.377-0.386l-2.317-0.901l2.832-1.287h-3.862l-0.772-2.704l2.059-2.446l2.704-1.03l6.951-0.772l-1.931,1.802l2.188,1.674l2.447-2.189l6.823-1.159l4.633,2.832l-0.386,1.675l-5.278,0.774L253.024,32.01zM210.672,27.248l5.536,0.128l5.148,0.645l-3.989,2.445l-3.219,0.514l-2.833,1.932l-3.088-0.128l-1.675-2.318v-1.287l1.417-1.158L210.672,27.248zM206.552,9.869l1.931-0.901l2.704-0.128l-1.159-0.644l6.308-0.129l3.348,1.416l8.753,1.673l5.664,2.06l-3.733,0.772l-5.021,2.06l-4.763,0.258l-5.535-0.386l-2.961-1.031l0.129-1.03l2.059-0.772l-4.891,0.129l-2.961-0.902l-1.673-1.287L206.552,9.869zM194.71,31.109l-2.832-2.574l2.961-0.514l3.218,0.643l4.119-0.258l0.515,1.03l-1.544,0.901l3.604,1.803l-0.644,1.416l-3.862,1.415l-2.574-0.257l-1.803-1.03l-5.535-1.544l-1.673-1.16L194.71,31.109zM178.233,30.08l3.089,1.158l1.674,2.574l0.772,1.932l4.634,1.287l4.764,1.287l-0.258,1.159l-4.377,0.257l1.673,1.03l-0.9,1.03h-6.436l-1.804-0.644l-4.376-0.386l-5.278,1.545l-6.565,0.644l-3.604,0.128l-2.704-2.059l-6.05-0.386l-4.505-1.674l2.96-0.772l4.119-0.386l3.863,0.129l3.475-0.516l-5.149-0.644l-5.793,0.258l-3.862-0.129l-1.416-0.901l6.308-1.159l-4.249,0.129l-4.634-0.772l2.189-2.059l1.932-1.031l7.208-1.673l2.703,0.515l-1.287,1.287l5.922-0.772l3.861,1.287l2.961-1.287l2.446,0.901l2.189,2.574l1.416-1.157l-1.932-2.704l2.446-0.387L178.233,30.08zM174.757,22.613l2.446-0.385l2.832,0.128l0.385,1.287l-1.543,1.287l-9.141,0.387l-6.822,1.159l-4.12,0.128l-0.257-0.901l5.535-1.159l-12.228,0.257l-3.734-0.514l3.734-2.575l2.445-0.772l7.596,0.901l4.891,1.673l4.634,0.129l-3.862-2.574l2.446-1.03l1.803,0.643l0.9,1.287l-2.06-0.644L174.757,22.613zM134.336,21.969l4.506-2.059l5.535-1.803l4.12,0.13l3.732-0.387l-0.385,2.06l-2.06,0.901l-2.575,0.129l-5.02,1.158l-4.248,0.386l3.605,0.515L134.336,21.969zM137.812,26.476l3.862,0.514l6.823,0.129l2.703,0.772l2.832,1.158l-3.347,0.644l-6.694,1.674L140,33.427l-0.643,1.287l-5.664,1.287l-1.802-1.03l-5.922-1.544l0.129-0.902l2.188-2.317l2.06-1.159l-1.673-2.188L137.812,26.476zM107.69,81.443l2.574-0.256l-0.773,3.088l2.318,2.188h-1.03l-1.674-1.287l-0.9-1.287l-1.416-0.772l-0.516-1.158l0.13-0.902l-1.287-0.386L107.69,81.443zM199.73,20.682l1.288,0.901V23l-1.416,1.801l-3.218,0.387l-2.961-0.387l0.129-1.545l-4.507,0.13l-0.128-2.06l2.961,0.129l3.99-0.901l-3.862-0.128L199.73,20.682zM181.064,13.344l5.279,0.387l7.337,0.901l2.06,1.288l1.03,1.158l-4.377-0.258l-4.506-0.9l-5.922-0.129l2.576-0.773l-3.348-0.644l0.129,1.03L181.064,13.344zM127.385,92.386l1.288,1.287l2.702,1.158l1.16,1.416l-1.417,0.387l-4.376-1.159l-0.773-1.029l-2.446-0.903l-0.515-0.772l-2.703-0.514l-1.03-1.416l0.129-0.643l2.832,0.643l1.673,0.386l2.575,0.257l-0.901-0.902L127.385,92.386zM315.071,83.502l0.129,2.961l-1.932,1.031l-1.932,0.901l-4.376,1.03l-3.476,2.188l-4.505,0.386l-5.793-0.515h-3.99l-2.832,0.129l-2.318,1.93l-3.346,1.288l-3.863,3.476l-3.089,2.575l2.189-0.515l4.376-3.476l5.664-2.317l3.991-0.257l2.445,1.286l-2.573,1.932l0.772,2.832l0.901,2.06l3.476,1.287l4.504-0.387l2.704-2.96l0.258,1.931l1.673,1.029l-3.347,1.674l-5.921,1.674l-2.703,1.029l-2.961,1.931l-2.06-0.128l-0.128-2.317l4.633-2.189h-4.247l-2.961,0.387l-1.803-1.545v-3.605l-1.157-0.772l-1.804,0.386l-0.9-0.644l-2.06,1.932l-0.901,2.187l-0.902,1.159l-1.158,0.515h-0.901l-0.258,0.772h-4.891h-4.12l-1.287,0.516l-2.703,1.801l-0.387,0.258l-0.256,0.258l-0.387,0.386l-0.257,0.515h-0.643h-0.516h-0.901l-0.772-0.128h-0.902h-0.643l-0.772,0.128h-0.258l-0.515,0.257l-0.386,0.129l0.257,0.386v0.129l0.387,0.772v0.258v0.128l-0.258,0.13l-0.386,0.128l-0.772,0.258l-0.902,0.257l-0.643,0.257l-0.643,0.258l-0.644,0.129h-0.128h-0.387l-0.9,0.128l-0.645,0.129l-0.644,0.258l-0.643,0.385l-0.644,0.258l-0.644,0.257l-0.643,0.258h-0.644l-0.514-0.129l-0.387-0.257l-0.257-0.257v-0.13v-0.257l0.644-0.9l1.286-1.546v-0.128v-0.129l0.259-0.515l0.385-0.515l0.129-0.258l-0.258-0.771l-0.129-0.515v-0.386l-0.127-0.515l-0.13-0.515l-0.129-0.515l-0.128-0.386l-0.13-0.515v-0.257l-0.128-0.387l-0.515-0.386l-0.514-0.128l-0.644-0.258l-0.643-0.257l-0.516-0.257l0.386-0.515v-0.129h-0.128l-0.258-0.258h-0.128l-0.258,0.128l-0.386-0.128l-0.258-0.129h-0.128l-0.129-0.257h-0.129v-0.258v-0.128v-0.129v-0.129h-0.257l-0.258,0.258h-0.772l0.128-0.258h-0.257l-0.386-0.257l-0.128-0.387l-0.13-0.386l-0.514-0.257l-0.515-0.129l-0.515-0.258l-0.515-0.257l-0.515-0.128l-0.515-0.258l-0.515-0.258l-0.514-0.128l-0.258-0.128l-0.387-0.13l-0.643-0.257l-0.772-0.386l-0.772-0.258l-0.773-0.257l-0.386-0.257h-0.258l-0.386-0.258l-0.644-0.129l-0.643,0.129l-0.772,0.258l-0.387,0.128l-0.386,0.129l-0.258,0.129h-0.515h-0.385l-3.219-0.773l-2.188,0.387l-2.703-0.773l-2.704-0.515l-1.93-0.129l-0.772-0.514l-0.516-1.417h-0.901v1.03h-5.536h-9.139h-9.397h-32.182h-2.704H133.95l-5.149-2.574l-1.931-1.287l-4.891-1.03l-1.545-2.446l0.385-1.673l-3.474-1.031l-0.387-2.188l-3.348-2.061v-1.287l1.417-1.287v-1.802l-4.634-1.673l-2.703-3.09l-1.674-1.93l-2.446-1.159l-1.802-1.159l-1.545-1.417l-2.703,0.902l-2.575,1.545L92.5,66.51l-1.802-1.157l-2.704-0.774H85.42V49.133V39.22l5.019,0.644l4.249,1.286l2.832,0.258l2.317-1.158l3.347-0.901l3.99,0.385l3.992-1.157l4.376-0.644l1.931,1.029l1.931-0.644l0.643-1.158l1.803,0.257l4.634,2.447l3.604-1.931l0.387,2.059l3.218-0.387l1.029-0.772l3.219,0.129l4.12,1.159l6.307,0.901l3.733,0.515l2.704-0.129l3.604,1.288l-3.734,1.415l4.763,0.515l7.338-0.257l2.317-0.515l2.832,1.544l2.96-1.287l-2.832-1.158l1.803-0.901l3.218-0.129l2.189-0.258l2.188,0.644l2.703,1.417l2.961-0.258l4.763,1.287l4.248-0.386h3.862l-0.258-1.673l2.446-0.515l4.12,0.9v2.576l1.673-2.06h2.188l1.288-2.704l-2.962-1.673l-3.088-1.03l0.128-2.961l3.218-2.06l3.605,0.515l2.703,1.158l3.604,3.091l-2.317,1.287l5.02,0.514v2.832l3.605-2.189l3.218,1.804l-0.9,1.93l2.702,1.802l2.704-1.931l2.06-2.317l0.129-2.96l3.861,0.257l3.862,0.387l3.733,1.287l0.128,1.416l-2.059,1.416l1.931,1.416l-0.386,1.286l-5.277,1.932l-3.734,0.386l-2.704-0.772l-0.901,1.287l-2.574,2.317l-0.773,1.159l-3.089,1.802l-3.862,0.257l-2.188,1.031l-0.13,1.802l-3.089,0.386l-3.347,2.188l-2.961,2.961l-1.028,2.188l-0.13,3.09l3.991,0.386l1.159,2.576l1.287,2.059l3.733-0.515l5.02,1.159l2.704,1.029l1.93,1.288l3.347,0.643l2.832,1.158l4.507,0.129l2.959,0.258l-0.514,2.446l0.901,2.702l1.931,2.961l3.991,2.576l2.059-0.902l1.545-2.703l-1.416-4.247l-1.931-1.545l4.247-1.159l3.09-1.931l1.545-1.931l-0.257-1.803l-1.802-2.188l-3.348-2.06l3.219-2.832l-1.158-2.445l-0.902-4.249l1.931-0.514l4.506,0.643l2.832,0.257l2.188-0.644l2.575,0.902l3.347,1.545l0.772,1.029l4.763,0.259v2.187l0.901,3.476l2.446,0.386l1.931,1.545l3.862-1.416l2.574-2.961l1.802-1.287l2.06,2.446l3.605,3.347l2.96,3.218l-1.159,1.802l3.604,1.417l2.446,1.545l4.25,0.772l1.802,0.772l1.03,2.317l2.06,0.387l-1.158-1.028L315.071,83.502z', - CD: - 'M500.183,239.912l-0.902,-1.031l-0.900,0.515l-1.030,1.159l-2.189,-2.832l2.059,-1.544l-1.029,-1.804l0.901,-0.643l1.802,-0.257l0.256,-1.287l1.416,1.287l2.319,0.129l0.900,-1.288l0.258,-1.802l-0.258,-2.059l-1.286,-1.545l1.157,-3.219l-0.642,-0.515l-2.060,0.258l-0.643,-1.415l0.127,-1.160l3.475,0.129l2.062,0.644l2.187,0.643l0.259,-1.416l1.415,-2.446l1.545,-1.544l1.803,0.514l1.800,0.130l-0.257,1.673l-0.770,1.416l-0.517,1.673l-0.386,2.448l0.257,1.414l-0.514,1.030l0,0.901l-0.385,0.901l-1.805,1.287l-1.156,1.417l-1.160,2.575l0,2.190l-0.645,0.898l-1.543,1.288l-1.544,1.804l-1.032,-0.516l-0.128,-0.772l-1.544,0l-0.901,1.030l0.772,0.258z', - CF: - 'M506.361,206.957l2.318,-0.129l0.384,-0.773l0.517,0.129l0.642,0.515l3.349,-1.029l1.157,-1.031l1.416,-0.901l-0.256,-0.900l0.772,-0.259l2.574,0.130l2.574,-1.287l1.932,-2.962l1.417,-1.030l1.672,-0.514l0.258,1.157l1.545,1.674l0,1.159l-0.387,1.159l0.129,0.773l1.029,0.771l2.059,1.159l1.419,1.159l0,0.901l1.800,1.287l1.159,1.287l0.643,1.544l2.059,1.031l0.389,0.901l-0.903,0.257l-1.674,0l-2.058,-0.257l-0.901,0.129l-0.514,0.643l-0.775,0.129l-1.158,-0.514l-2.961,1.287l-1.287,-0.258l-0.258,0.258l-0.900,1.544l-1.930,-0.514l-2.060,-0.258l-1.674,-1.030l-2.190,-0.900l-1.415,0.900l-1.030,1.417l-0.258,1.802l-1.800,-0.130l-1.803,-0.514l-1.545,1.544l-1.415,2.446l-0.387,-0.772l-0.129,-1.287l-1.157,-0.773l-1.031,-1.416l-0.257,-1.029l-1.288,-1.416l0.258,-0.772l-0.258,-1.160l0.258,-2.060l0.643,-0.515l-1.287,2.702z', - CG: - 'M548.327,217.513l-0.258,3.217l1.159,0.258l-0.901,1.031l-1.031,0.643l-1.029,1.416l-0.514,1.287l-0.131,2.189l-0.643,1.028l0,2.061l-0.901,0.643l0,1.674l-0.386,0.128l-0.257,1.546l0.643,1.159l0.130,3.347l0.514,2.445l-0.257,1.415l0.514,1.546l1.545,1.546l1.545,3.346l-1.030,-0.258l-3.733,0.386l-0.643,0.387l-0.771,1.673l0.642,1.288l-0.514,3.088l-0.387,2.705l0.772,0.514l1.932,1.031l0.642,-0.516l0.258,2.961l-2.058,0l-1.159,-1.545l-0.903,-1.158l-2.058,-0.387l-0.644,-1.416l-1.674,0.901l-2.187,-0.385l-0.903,-1.158l-1.672,-0.258l-1.289,0l-0.128,-0.772l-0.901,-0.130l-1.287,-0.128l-1.674,0.385l-1.158,0l-0.645,0.131l0.129,-3.091l-0.900,-1.030l-0.129,-1.674l0.385,-1.673l-0.514,-1.031l-0.128,-1.672l-3.219,0l0.258,-0.902l-1.417,0l-0.128,0.388l-1.673,0.128l-0.644,1.545l-0.514,0.772l-1.418,-0.387l-0.900,0.387l-1.801,0.128l-1.031,-1.415l-0.645,-0.773l-0.771,-1.674l-0.643,-1.930l-7.982,-0.131l-0.900,0.389l-0.774,-0.131l-1.157,0.387l-0.387,-0.772l0.773,-0.386l0,-1.159l0.514,-0.642l0.902,-0.516l0.772,0.258l0.901,-1.030l1.544,0l0.128,0.772l1.032,0.516l1.544,-1.804l1.543,-1.288l0.645,-0.898l0,-2.190l1.160,-2.575l1.156,-1.417l1.805,-1.287l0.385,-0.901l0,-0.901l0.514,-1.030l-0.257,-1.414l0.386,-2.448l0.517,-1.673l0.770,-1.416l0.257,-1.673l0.258,-1.802l1.030,-1.417l1.415,-0.900l2.190,0.900l1.674,1.030l2.060,0.258l1.930,0.514l0.900,-1.544l0.258,-0.258l1.287,0.258l2.961,-1.287l1.158,0.514l0.775,-0.129l0.514,-0.643l0.901,-0.129l2.058,0.257l1.674,0l0.903,-0.257l1.672,2.188l1.158,0.387l0.773,-0.515l1.287,0.257l1.416,-0.643l0.644,1.159l-2.446,-1.802z', - CH: - 'M491.042,98.951l0.128,0.515l-0.385,0.644l1.160,0.514l1.416,0l-0.258,1.159l-1.158,0.386l-1.932,-0.257l-0.643,1.030l-1.288,0.129l-0.387,-0.516l-1.543,1.031l-1.287,0.128l-1.160,-0.643l-0.901,-1.159l-1.288,0.386l0,-1.158l1.932,-1.545l-0.130,-0.644l1.288,0.257l0.772,-0.515l2.317,0l0.515,-0.514l-2.832,-0.772z', - CI: - 'M457.573,213.521l-1.287,0l-1.802,-0.514l-1.802,0l-3.219,0.514l-1.802,0.773l-2.703,1.030l-0.516,-0.129l0.259,-2.188l0.257,-0.387l-0.129,-1.030l-1.159,-1.158l-0.772,-0.129l-0.901,-0.772l0.644,-1.158l-0.258,-1.287l0.129,-0.773l0.386,0l0.129,-1.159l-0.129,-0.644l0.258,-0.257l1.030,-0.387l-0.772,-2.187l-0.516,-1.031l0.129,-0.901l0.515,-0.257l0.387,-0.258l0.772,0.386l2.059,0l0.514,-0.772l0.516,0.129l0.772,-0.385l0.387,1.157l0.643,-0.257l1.030,-0.515l1.287,0.643l0.387,0.902l1.286,0.515l0.902,-0.644l1.287,-0.129l1.802,0.773l0.772,3.861l-1.158,2.190l-0.644,3.088l1.159,2.317l0.129,-1.030z', - CL: - 'M266.669,369.286l-3.347-1.544l-0.772-1.676l0.644-1.543l-1.288-1.803l-0.386-4.634l1.158-2.573l2.832-2.062l-3.99-0.772l2.445-2.445l1.03-4.506l2.962,1.031l1.416-5.666l-1.802-0.642l-0.902,3.345l-1.674-0.386l0.902-3.862l0.901-5.02l1.159-1.801l-0.773-2.576l-0.129-3.09l1.03-0.129l1.673-4.248l1.932-4.377l1.158-3.99l-0.643-3.99l0.772-2.316l-0.387-3.348l1.674-3.218l0.386-5.278l0.901-5.535l0.902-6.051l-0.259-4.378l-0.513-3.862l1.415-0.644l0.644-1.417l1.286,1.932l0.387,1.934l1.417,1.156l-0.773,2.576l1.417,2.96l1.028,3.733l1.932-0.387l0.386,0.772l-0.902,2.704l-2.96,1.415v4.378l-0.515,0.9l0.772,1.031l-1.802,1.672l-1.802,2.574l-1.03,2.446l0.257,2.575l-1.672,2.831l1.287,4.636l0.772,0.514l-0.129,2.447l-1.415,2.702v2.188l-2.06,1.803v2.445l0.9,2.574l-1.673,1.03l-0.773,2.446l-0.513,2.703l0.385,3.348l-1.03,0.516l0.645,3.09l1.157,1.029l-0.901,1.158l1.287,0.514l0.258,1.03l-1.158,0.515l0.257,1.545l-0.9,3.605l-1.546,2.316l0.386,1.287l-0.901,1.674l-2.06,1.289l0.258,2.83l0.902,0.902l1.801-0.129v2.061l1.16,1.545l6.436,0.385l2.574,0.387h-2.446l-1.288,0.643l-2.444,1.029l-0.387,2.447l-1.159,0.129l-3.09-0.902L266.669,369.286zM283.274,374.822h1.546l-0.902,1.156l-2.316,0.774h-1.288l-1.544-0.256l-1.932-0.774l-2.831-0.386l-3.476-1.545l-2.704-1.416l-3.732-3.09l2.188,0.646l3.862,1.801l3.476,0.901l1.416-1.159l0.901-1.932l2.445-1.029l1.931,0.258l0.129,0.127l-0.129,5.924H283.274z', - CM: - 'M500.439,220.859l-0.256,-0.129l-1.674,0.387l-1.673,-0.387l-1.288,0.129l-4.378,0l0.387,-2.188l-1.029,-1.802l-1.158,-0.387l-0.516,-1.287l-0.772,-0.386l0,-0.643l0.772,-1.932l1.289,-2.575l0.771,-0.128l1.544,-1.545l1.029,0l1.546,1.030l1.803,-0.901l0.257,-1.029l0.644,-1.159l0.387,-1.288l1.414,-1.159l0.645,-1.931l0.513,-0.514l0.387,-1.417l0.773,-1.673l2.188,-2.189l0.129,-0.901l0.387,-0.386l-1.160,-1.158l0.128,-0.773l0.774,-0.257l1.029,1.801l0.258,1.804l-0.128,1.802l1.415,2.446l-1.415,-0.129l-0.774,0.257l-1.287,-0.257l-0.514,1.287l1.545,1.546l1.158,0.385l0.387,1.160l0.900,1.930l-0.515,0.644l-1.287,2.702l-0.643,0.515l-0.258,2.060l0.258,1.160l-0.258,0.772l1.288,1.416l0.257,1.029l1.031,1.416l1.157,0.773l0.129,1.287l0.387,0.772l-0.259,1.416l-2.187,-0.643l-2.062,-0.644l3.475,0.129z', - CN: - 'M760.085,177.992l-2.188-0.902v-2.317l1.288-1.158l2.961-0.773h1.544l0.645,1.031l-1.289,1.287l-0.514,1.545L760.085,177.992zM712.198,152.117l-1.16-0.644l-1.416-0.128l-1.93-0.645l-1.414,0.773l-1.805,1.931l-0.258-2.059l-1.543,0.514l-3.221-0.257l-2.959-0.644l-2.189-1.158l-2.188-0.515l-0.9-1.288l-1.545-0.386l-2.703-1.802l-2.061-0.772l-1.158,0.643l-3.732-1.93l-2.704-1.674l-0.772-2.96l1.932,0.385l0.129-1.416l-1.029-1.416l0.256-2.189l-2.961-3.089l-4.375-1.159l-0.773-2.059l-2.059-1.287l-0.388-0.773l-0.515-1.416l0.129-1.158l-1.674-0.515l-0.772,0.256l-0.772-2.573l0.772-0.516l-0.386-0.643l2.574-1.288l1.93-0.514l2.834,0.257l1.029-1.673l3.476-0.258l0.901-1.158l4.248-1.416l0.387-0.644l-0.26-1.545l1.931-0.643l-2.444-4.635l5.278-1.159l1.415-0.514l1.932-4.892l5.408,0.901l1.416-1.288l0.127-2.704l2.316-0.128l2.061-1.801l1.029-0.258l0.645,1.802l2.317,1.545l3.862,0.901l1.803,2.188l-1.031,3.219l1.031,1.158l3.217,0.387l3.605,0.385l3.217,1.674l1.673,0.386l1.159,2.446l1.672,1.545h2.962l5.536,0.644l3.605-0.386l2.701,0.386l3.861,1.673h3.348l1.159,0.773l3.091-1.416l4.375-0.902l4.121-0.128l3.088-1.03l1.932-1.416l1.931-0.902l-0.515-0.9l-0.774-1.03l1.416-1.674l1.416,0.257l2.832,0.516l2.704-1.417l4.119-1.029l1.932-1.803l1.932-0.772l3.861-0.386l2.189,0.258l0.258-0.902l-2.447-1.931l-2.189-0.772l-2.059,0.901l-2.701-0.386l-1.42,0.386l-0.771-1.158l1.932-2.704l1.286-1.931l3.22,0.9l3.861-1.672v-1.159l2.447-2.832l1.414-0.901v-1.416l-1.545-0.644l2.316-1.417l3.35-0.513h3.475l4.119,0.772l2.316,1.03l1.674,2.703l1.031,1.158l0.9,1.674l1.029,2.574l4.635,0.902l3.219,1.93l1.158,2.447h3.99l2.447-1.03l4.375-0.774l-1.414,2.448l-1.031,1.029l-0.9,2.832l-1.803,2.704l-3.346-0.516l-2.318,0.901l0.771,2.317l-0.385,3.219l-1.416,0.129v1.288l-1.675-1.546l-1.028,1.546l-4.248,1.157l0.387,1.417l-2.319-0.13l-1.286-0.9l-1.803,1.93l-2.961,1.546l-2.189,1.673l-3.732,0.772l-2.059,1.288l-2.832,0.772l1.416-1.288l-0.513-1.028l2.058-1.803l-1.418-1.417l-2.314,0.902l-3.09,1.931l-1.674,1.673l-2.576,0.129l-1.414,1.287l1.414,1.802l2.189,0.387l0.129,1.287l2.061,0.773l3.088-1.931l2.447,1.029l1.672,0.129l0.388,1.416l-3.733,0.772l-1.287,1.416l-2.574,1.288l-1.418,1.931l2.834,1.417l1.158,2.702l1.545,2.446l1.93,2.06l-0.129,2.059l-1.674,0.773l0.645,1.416l1.545,0.773l-0.387,2.187l-0.643,2.189l-1.545,0.258l-1.933,2.832l-2.188,3.604l-2.443,3.219l-3.734,2.446l-3.732,2.317l-3.09,0.258l-1.674,1.157l-0.9-0.772l-1.545,1.287l-3.733,1.416l-2.831,0.386l-0.9,2.833l-1.547,0.129l-0.643-1.931l0.643-1.031l-3.605-0.9l-1.284,0.387l-2.704-0.645l-1.289-1.029l0.387-1.545l-2.445-0.515l-1.287-1.03l-2.316,1.416l-2.576,0.257h-2.187l-1.416,0.644l-1.416,0.386l0.386,3.089h-1.418l-0.256-0.643l-0.128-1.158l-1.931,0.773l-1.16-0.387l-2.059-1.03l0.771-2.317l-1.674-0.515l-0.645-2.446l-2.832,0.386l0.387-3.089l2.445-2.318l0.131-2.188v-2.06l-1.289-0.644l-0.9-1.545l-1.545,0.13l-2.832-0.386l0.9-1.159l-1.285-1.674l-1.934,1.158l-2.314-0.643l-3.092,1.674l-2.445,2.059L712.198,152.117z', - CO: - 'M262.164,227.425l-1.159,-0.644l-1.287,-0.901l-0.772,0.386l-2.318,-0.386l-0.643,-1.157l-0.515,0.127l-2.704,-1.544l-0.386,-0.902l1.031,-0.129l-0.130,-1.416l0.644,-1.029l1.417,-0.129l1.029,-1.674l1.030,-1.416l-0.901,-0.644l0.515,-1.545l-0.644,-2.445l0.515,-0.772l-0.386,-2.318l-1.030,-1.416l0.258,-1.287l0.900,0.257l0.515,-0.901l-0.643,-1.544l0.386,-0.387l1.416,0.129l1.931,-1.931l1.158,-0.258l0,-0.901l0.515,-2.317l1.545,-1.158l1.674,-0.128l0.257,-0.516l2.059,0.257l2.189,-1.415l1.029,-0.644l1.288,-1.288l0.901,0.258l0.773,0.644l-0.516,0.901l-1.802,0.514l-0.644,1.289l-1.029,0.771l-0.772,1.030l-0.387,1.931l-0.772,1.545l1.415,0.129l0.387,1.287l0.644,0.645l0.128,1.028l-0.257,1.030l0,0.516l0.772,0.257l0.644,0.901l3.475,-0.258l1.546,0.387l1.802,2.317l1.158,-0.258l1.931,0.129l1.545,-0.386l0.902,0.515l-0.517,1.416l-0.513,0.901l-0.259,1.931l0.516,1.802l0.773,0.772l0.127,0.644l-1.416,1.287l1.031,0.644l0.772,0.901l0.773,2.703l-0.516,0.387l-0.515,-1.545l-0.773,-0.902l-0.900,0.902l-5.278,0l0.129,1.674l1.545,0.258l-0.129,1.029l-0.515,-0.258l-1.545,0.387l0,1.931l1.159,1.030l0.515,1.544l-0.129,1.159l-1.159,7.338l-1.416,-1.417l-0.772,0l1.802,-2.704l-2.060,-1.287l-1.673,0.259l-1.030,-0.516l-1.416,0.644l-2.060,-0.257l-1.544,-2.832l-1.288,-0.644l-0.772,-1.287l-1.802,-1.288l0.772,-0.258z', - CR: - 'M241.695,204.768l-1.415,-0.515l-0.515,-0.644l0.257,-0.386l-0.128,-0.644l-0.644,-0.643l-1.159,-0.514l-0.901,-0.387l-0.128,-0.773l-0.773,-0.515l0.257,0.901l-0.643,0.644l-0.515,-0.772l-0.901,-0.258l-0.386,-0.644l0,-0.772l0.386,-0.901l-0.772,-0.257l0.644,-0.643l0.386,-0.259l1.801,0.644l0.644,-0.257l0.773,0.128l0.515,0.644l0.772,0.128l0.644,-0.514l0.644,1.416l1.029,1.030l1.287,1.157l-1.029,0.260l0,1.157l0.514,0.387l-0.385,0.257l0.128,0.515l-0.257,0.515l0.130,-0.515z', - CU: - 'M243.626,164.475l2.318,0.257l2.059,0l2.576,0.902l1.028,1.030l2.576,-0.387l0.900,0.644l2.318,1.673l1.673,1.287l0.901,-0.128l1.545,0.644l-0.129,0.772l1.931,0l2.060,1.159l-0.257,0.644l-1.803,0.385l-1.802,0.129l-1.931,-0.257l-3.861,0.257l1.801,-1.544l-1.029,-0.644l-1.802,-0.258l-0.902,-0.772l-0.643,-1.415l-1.546,0l-2.445,-0.645l-0.772,-0.644l-3.604,-0.385l-0.902,-0.515l1.030,-0.644l-2.704,-0.128l-1.930,1.416l-1.030,0l-0.386,0.643l-1.417,0.257l-1.158,-0.257l1.417,-0.772l0.643,-1.030l1.159,-0.515l1.415,-0.515l2.059,-0.257l-0.644,0.387z', - CY: - 'M556.694,132.549l0.129,0.259l-2.704,1.028l-1.417,-0.385l-0.514,-1.030l1.159,-0.129l0.258,0.129l0.127,0l0.130,0l0.257,0l0.257,-0.129l0.260,-0.128l0.127,0.128l0.258,0l0.128,0l0.128,0l0.130,0.129l0,0.258l0.129,-0.130l0.257,0.130l0.128,0l0.131,-0.130l0.128,0l0.128,0l0.129,-0.128l0.128,0l-0.129,-0.128z', - CZ: - 'M510.866,96.119l-1.158,-0.516l-1.286,0.129l-2.061,-0.901l-1.030,0.258l-1.417,1.030l-2.058,-0.772l-1.544,-1.159l-1.288,-0.645l-0.386,-1.157l-0.387,-0.773l1.932,-0.643l1.029,-0.644l1.932,-0.515l0.642,-0.516l0.645,0.259l1.287,-0.259l1.287,0.903l1.932,0.256l-0.129,0.645l1.414,0.644l0.517,-0.773l1.802,0.386l0.257,0.772l1.930,0.129l1.289,1.416l-0.774,0l-0.385,0.515l-0.644,0l-0.256,0.643l-0.517,0.129l0,0.257l-0.900,0.258l-1.288,0l0.387,-0.644z', - DE: - 'M491.945,78.87l0.127,1.028l2.703,0.644l-0.128,0.901l2.831,-0.514l1.417,-0.644l3.090,1.029l1.287,0.901l0.642,1.287l-0.770,0.773l1.029,0.901l0.644,1.417l-0.257,1.030l1.158,1.672l-1.287,0.259l-0.645,-0.259l-0.642,0.516l-1.932,0.515l-1.029,0.644l-1.932,0.643l0.387,0.773l0.386,1.157l1.288,0.645l1.544,1.159l-0.901,1.158l-1.030,0.386l0.387,1.802l-0.258,0.386l-0.773,-0.515l-1.287,-0.128l-1.931,0.515l-2.446,-0.128l-0.387,0.772l-1.285,-0.772l-0.903,0.128l-2.832,-0.772l-0.515,0.514l-2.317,0l0.257,-1.931l1.416,-1.802l-3.861,-0.514l-1.287,-0.773l0.129,-1.159l-0.516,-0.515l0.258,-1.930l-0.386,-2.833l1.544,0l0.773,-0.901l0.644,-2.574l-0.515,-0.902l0.515,-0.515l2.317,-0.129l0.385,0.516l1.933,-1.288l-0.645,-1.029l-0.129,-1.544l2.060,0.385l-1.675,0.385z', - DJ: - 'M581.28,192.797l0.645,0.771l-0.129,1.159l-1.545,0.644l1.158,0.772l-0.900,1.416l-0.645,-0.514l-0.642,0.256l-1.545,-0.128l0,-0.773l-0.257,-0.771l0.901,-1.288l1.030,-1.159l1.158,0.257l-0.771,0.642z', - DK: - 'M488.21,78.87l-1.159,-1.417l0,-2.832l0.387,-0.644l0.772,-0.901l2.447,-0.130l0.900,-0.772l2.188,-0.771l-0.128,1.415l-0.772,0.902l0.385,0.772l1.417,0.386l-0.644,1.029l-0.773,-0.257l-2.060,1.932l0.775,1.288l-1.675,0.385l2.060,0.385zM498.509,75.779l0.900,1.416l-1.545,2.188l-2.831,-1.544l-0.386,-1.158l-3.862,0.902z', - DO: - 'M272.075,173.873l0.259,-0.516l2.187,0l1.545,0.772l0.772,-0.128l0.387,1.030l1.545,-0.129l-0.129,0.901l1.288,0l1.286,1.030l-1.030,1.159l-1.287,-0.644l-1.287,0.129l-0.773,-0.129l-0.514,0.515l-1.030,0.129l-0.387,-0.644l-0.900,0.386l-1.159,1.803l-0.643,-0.387l-0.130,-0.772l0,-0.773l-0.643,-0.772l0.643,-0.515l0.259,-1.029l0.259,1.416z', - DZ: - 'M497.608,163.703l-9.269,5.150l-7.852,5.276l-3.734,1.288l-2.961,0.257l-0.128,-1.801l-1.159,-0.387l-1.672,-0.772l-0.645,-1.288l-9.139,-5.792l-9.140,-5.922l-10.040,-6.566l0,-0.514l0,-3.347l4.377,-1.931l2.703,-0.514l2.188,-0.644l1.030,-1.417l3.090,-1.029l0.128,-2.061l1.545,-0.128l1.287,-1.030l3.476,-0.515l0.515,-1.030l-0.772,-0.514l-0.902,-2.832l-0.128,-1.674l-1.030,-1.674l2.574,-1.545l2.962,-0.515l1.673,-1.029l2.574,-0.902l4.633,-0.385l4.377,-0.258l1.416,0.385l2.575,-1.028l2.833,0l1.029,0.643l1.930,-0.258l-0.642,1.416l0.514,2.575l-0.642,2.189l-1.674,1.545l0.257,2.059l2.187,1.545l0,0.643l1.674,1.159l1.159,4.763l0.903,2.446l0.126,1.158l-0.513,2.318l0.256,1.158l-0.387,1.546l0.259,1.673l-1.030,1.030l1.546,2.059l0.127,1.159l0.902,1.415l1.286,-0.385l2.060,1.158l-1.288,-1.674z', - EC: - 'M248.905,236.179l1.415,-2.060l-0.514,-1.159l-1.031,1.288l-1.672,-1.160l0.515,-0.772l-0.387,-2.445l0.901,-0.516l0.515,-1.673l1.030,-1.674l-0.258,-1.158l1.545,-0.514l1.802,-1.030l2.704,1.544l0.515,-0.127l0.643,1.157l2.318,0.386l0.772,-0.386l1.287,0.901l1.159,0.644l0.386,2.059l-0.772,1.674l-2.961,2.832l-3.219,1.030l-1.673,2.446l-0.514,1.802l-1.545,1.030l-1.159,-1.286l-1.030,-0.388l-1.159,0.257l0,-1.029l0.773,-0.643l0.386,1.030z', - EE: - 'M530.69,71.273l0.387-1.544l-1.029,0.257l-1.674-0.9l-0.256-1.545l3.344-0.773l3.478-0.386l2.833,0.515l2.831-0.129l0.386,0.515l-1.931,1.544l0.9,2.446l-1.158,0.901h-2.317l-2.316-1.028l-1.158-0.387L530.69,71.273z', - EG: - 'M559.269,147.483l-0.773,1.158l-0.514,1.931l-0.771,1.417l-0.645,0.514l-0.901-0.901l-1.159-1.158l-1.93-3.862l-0.258,0.258l1.158,2.831l1.546,2.703l2.059,4.119l1.03,1.545l0.902,1.545l2.316,2.961l-0.517,0.386l0.13,1.802l3.089,2.447l0.259,0.514h-10.299h-10.557h-10.812v-9.912v-9.526l-0.901-2.189l0.772-1.673l-0.388-1.159l0.903-1.287h3.604l2.574,0.644l2.705,0.773l1.287,0.514l2.059-0.901l1.029-0.772l2.447-0.258l1.93,0.386l0.643,1.287l0.646-0.9l2.187,0.644l2.061,0.128l1.415-0.644L559.269,147.483z', - EH: - 'M441.482,153.92l0,-1.417l0.387,0l0,0.129l0,0.514l0,4.120l-8.883,-0.129l0.129,6.823l-2.574,0.257l-0.644,1.417l0.515,3.862l-10.557,0l-0.643,0.901l0.129,-1.159l0.129,0l6.050,-0.129l0.257,-1.029l1.159,-1.159l0.901,-3.733l3.733,-2.961l1.287,-3.347l0.773,-0.257l0.900,-2.060l2.319,-0.257l0.900,0.257l1.288,0l0.901,-0.515l-1.544,0.128z', - ER: - 'M579.351,193.182l-0.901,-0.901l-1.160,-1.545l-1.158,-0.902l-0.773,-0.900l-2.317,-1.030l-1.801,-0.129l-0.644,-0.514l-1.674,0.643l-1.544,-1.287l-0.900,2.059l-3.091,-0.514l-0.258,-1.160l1.160,-3.861l0.258,-1.802l0.770,-0.901l2.061,-0.386l1.288,-1.546l1.543,3.090l0.773,2.446l1.545,1.288l3.604,2.574l1.545,1.545l1.415,1.544l0.903,0.902l1.285,0.902l-0.771,0.642l1.158,0.257z', - ES: - 'M440.838,114.141l0.129,-1.931l-1.029,-1.158l3.861,-1.932l3.219,0.515l3.604,0l2.960,0.387l2.189,-0.129l4.377,0.129l1.029,1.030l5.021,1.158l0.901,-0.514l3.089,1.158l3.090,-0.258l0.129,1.545l-2.574,1.802l-3.478,0.516l-0.127,0.900l-1.672,1.545l-1.031,2.189l1.031,1.544l-1.547,1.159l-0.642,1.803l-2.061,0.514l-1.802,2.060l-3.476,0l-2.574,0l-1.673,0.901l-1.031,1.030l-1.287,-0.129l-1.030,-1.030l-0.772,-1.545l-2.446,-0.385l-0.257,-0.902l1.030,-1.030l0.258,-0.644l-0.902,-0.900l0.772,-1.674l-1.030,-1.674l1.160,-0.256l0,-1.159l0.514,-0.387l0,-2.189l1.287,-0.643l-0.773,-1.416l-1.545,-0.128l-0.514,0.385l-1.545,0l-0.643,-1.287l-1.158,0.387l1.031,-0.643z', - ET: - 'M579.351,193.182l-1.030,1.159l-0.901,1.288l0.257,0.771l0,0.773l1.545,0.128l0.642,-0.256l0.645,0.514l-0.645,0.901l1.032,1.545l1.029,1.287l1.029,0.901l8.754,3.218l2.316,0l-7.722,8.110l-3.475,0.129l-2.318,1.932l-1.803,0l-1.029,0.644l-1.030,0.256l-1.931,-1.158l-2.445,1.287l-1.030,1.159l-1.031,-0.387l-0.900,0.258l-1.159,-0.385l-0.772,-0.130l-3.089,-2.574l-2.318,0l-0.129,-0.644l-0.772,-1.288l-1.159,-0.515l-1.158,-2.832l-1.286,-0.644l-0.388,-1.158l-1.416,-1.287l-1.673,-0.129l0.901,-1.545l1.416,-0.127l0.386,-0.774l0,-2.447l0.774,-2.831l1.286,-0.772l0.259,-1.030l1.158,-2.060l1.672,-1.415l1.158,-2.575l0.387,-2.317l3.091,0.514l0.900,-2.059l1.544,1.287l1.674,-0.643l0.644,0.514l1.801,0.129l2.317,1.030l0.773,0.900l1.158,0.902l1.160,1.545l-0.901,-0.901z', - FI: - 'M542.276,40.893l-0.384,1.932l4.119,1.801l-2.448,2.060l3.089,2.960l-1.801,2.318l2.445,2.060l-1.157,1.802l3.991,1.802l-1.030,1.416l-2.448,1.545l-5.792,3.347l-4.890,0.257l-4.764,1.030l-4.377,0.515l-1.545,-1.416l-2.574,-0.901l0.514,-2.704l-1.286,-2.445l1.286,-1.545l2.447,-1.673l6.180,-2.961l1.800,-0.515l-0.256,-1.159l-3.734,-1.286l-0.901,-1.031l-0.128,-4.120l-4.250,-1.801l-3.475,-1.417l1.545,-0.643l2.961,1.416l3.606,-0.129l2.832,0.644l2.572,-1.159l1.289,-2.060l4.247,-0.900l3.476,1.157l1.159,-1.803z', - FJ: - 'M946.097,274.154l0.773,-0.514l0.901,0.772l-0.516,1.416l-1.672,0.385l-1.418,-0.256l-0.256,-1.289l1.029,-0.900l-1.159,-0.386zM950.089,271.579l-1.160,0.773l-1.545,0.644l-0.385,-1.287l1.031,-1.030l0.899,-0.130l1.160,-0.256l-0.001,0l0.515,-0.129l-0.387,1.287l-0.128,0.128l-0.001,0z', - FK: - 'M302.584,365.296l-0.129,1.159l-1.03,1.416l2.188-1.031l1.158-1.286L302.584,365.296zM307.733,365.037l1.159,0.388l-0.902,1.415l-2.188,0.772l-0.257-0.9l1.288-1.416L307.733,365.037z', - FR: - 'M481.903,93.673l1.287,0.773l3.861,0.514l-1.416,1.802l-0.257,1.931l-0.772,0.515l-1.288,-0.257l0.130,0.644l-1.932,1.545l0,1.158l1.288,-0.386l0.901,1.159l-0.128,0.772l0.772,1.029l-0.901,0.774l0.642,2.058l1.418,0.386l-0.258,1.160l-2.446,1.544l-5.277,-0.772l-3.992,0.901l-0.257,1.673l-3.090,0.258l-3.089,-1.158l-0.901,0.514l-5.021,-1.158l-1.029,-1.030l1.416,-1.674l0.515,-5.277l-2.832,-2.833l-2.060,-1.415l-3.991,-1.031l-0.386,-1.931l3.604,-0.644l4.506,0.773l-0.901,-3.090l2.575,1.159l6.306,-2.060l0.775,-2.317l2.317,-0.515l0.385,1.031l1.287,0l1.288,1.029l1.801,1.287l1.416,-0.257l2.318,1.286l0.643,0.259l-0.773,0.129zM488.854,112.082l1.674,-1.030l0.514,2.317l-0.899,2.188l-1.289,-0.643l-0.644,-1.803l-0.644,1.029z', - GA: - 'M495.162,237.723l-2.833-2.703l-1.801-2.316l-1.544-2.704V229.1l0.642-0.902l0.644-1.932l0.516-2.06l0.903-0.128h3.987l-0.128-3.219l1.288-0.129l1.673,0.387l1.674-0.387l0.257,0.129l-0.127,1.16l0.643,1.414l2.06-0.258l0.643,0.516l-1.157,3.219l1.286,1.545l0.258,2.059l-0.258,1.803l-0.9,1.287l-2.318-0.129l-1.416-1.287l-0.256,1.287l-1.803,0.258l-0.9,0.643l1.028,1.805L495.162,237.723z', - GB: - 'M444.829,78.483l2.317-0.129l2.831,1.673l-1.415,1.803l-2.061-0.516h-1.673l0.515-1.416L444.829,78.483zM453.84,69.214l3.347-0.257l-2.961,2.96l2.832-0.386h2.832l-0.643,2.189l-2.446,2.446l2.832,0.256l2.575,3.348l1.801,0.515l1.674,3.089l0.773,1.03l3.347,0.515l-0.387,1.674L468,87.365l1.159,1.416l-2.446,1.417h-3.604l-4.634,0.772l-1.158-0.516l-1.804,1.159l-2.573-0.257l-1.803,1.03l-1.415-0.515l3.86-2.832l2.446-0.644l-4.247-0.386l-0.772-1.03l2.831-0.901l-1.416-1.416l0.516-1.803l3.99,0.258l0.387-1.545l-1.804-1.674l-3.346-0.515l-0.646-0.772l1.031-1.158l-0.9-0.772l-1.416,1.286l-0.259-2.573l-1.286-1.417l0.9-2.704l2.189-2.187L453.84,69.214z', - GE: - 'M577.161,115.042l0.387-1.159l-0.643-1.801l-1.546-1.03l-1.544-0.258l-0.9-0.772l0.256-0.387l2.318,0.516l3.989,0.386l3.604,1.287l0.517,0.515l1.672-0.387l2.445,0.516l0.772,1.158l1.803,0.644l-0.771,0.257l1.287,1.545l-0.258,0.258l-1.545-0.13l-1.93-0.772l-0.645,0.387l-3.733,0.515l-2.702-1.416L577.161,115.042z', - GF: - 'M319.834,211.463l0.902,0.256l2.058,0.645l2.833,2.316l0.386,1.159l-1.545,2.446l-0.771,1.93l-1.03,1.031l-1.287,0.256l-0.387-0.771l-0.644-0.129l-0.771,0.645l-1.287-0.516l0.772-1.158l0.257-1.159l0.386-1.157l-1.029-1.674l-0.259-1.803L319.834,211.463z', - GH: - 'M468.13,210.946l-4.249,1.674l-1.545,0.901l-2.446,0.773l-2.317,-0.773l0.129,-1.030l-1.159,-2.317l0.644,-3.088l1.158,-2.190l-0.772,-3.861l-0.386,-2.060l0.129,-1.545l4.634,-0.129l1.289,0.258l0.770,-0.514l1.288,0.256l-0.258,0.772l1.159,1.417l0,1.932l0.258,2.187l0.643,1.030l-0.514,2.318l0.128,1.416l0.773,1.673l-0.644,-0.900z', - GL: - 'M339.272,4.333l9.011,-1.544l9.525,0.128l3.348,-1.029l9.526,-0.258l21.497,0.386l16.864,2.060l-4.892,1.029l-10.298,0.129l-14.546,0.258l1.287,0.515l9.654,-0.257l8.110,0.901l5.149,-0.773l2.317,0.901l-2.961,1.545l6.824,-1.030l13.130,-1.030l7.981,0.515l1.545,1.159l-10.942,1.931l-1.546,0.644l-8.625,0.514l6.180,0.129l-3.089,1.931l-2.189,1.802l0.129,2.961l3.218,1.674l-4.249,0.128l-4.376,0.902l4.893,1.415l0.643,2.318l-2.832,0.257l3.476,2.317l-5.923,0.129l3.091,1.159l-0.902,0.900l-3.733,0.387l-3.862,0l3.476,1.931l0,1.158l-5.407,-1.158l-1.287,0.773l3.604,0.644l3.476,1.673l1.030,2.188l-4.763,0.515l-2.060,-1.031l-3.347,-1.544l0.901,1.803l-3.090,1.416l7.081,0.129l3.733,0.128l-7.208,2.316l-7.338,2.189l-7.852,0.902l-2.962,0l-2.831,1.030l-3.734,2.832l-5.793,1.931l-1.930,0.128l-3.604,0.644l-3.862,0.644l-2.317,1.673l0,1.802l-1.288,1.802l-4.505,2.189l1.158,2.060l-1.287,2.188l-1.287,2.703l-3.863,0.129l-3.989,-2.188l-5.278,0l-2.704,-1.545l-1.802,-2.574l-4.635,-3.347l-1.415,-1.803l-0.258,-2.316l-3.732,-2.576l0.900,-1.930l-1.802,-1.031l2.703,-3.088l3.991,-1.031l1.159,-1.158l0.515,-2.059l-3.476,-0.259l-6.179,-1.416l2.189,0l6.049,0l-4.634,-1.801l-2.446,-0.902l-4.892,-0.258l2.960,-2.445l-1.544,-1.030l-2.188,-1.931l-3.218,-2.832l-3.475,-1.030l0.128,-1.159l-7.338,-1.545l-5.664,-0.257l-7.208,0.129l-6.565,0.257l-3.090,-0.901l-4.763,-1.673l7.081,-0.901l5.405,-0.130l-11.457,-0.643l-6.050,-1.158l0.387,-1.030l10.169,-1.288l9.784,-1.287l1.030,-1.030l-7.210,-0.901l2.318,-1.029l9.397,-1.931l3.862,-0.258l-1.159,-1.287l6.437,-0.644l8.238,-0.387l8.368,-0.128l2.832,0.901l7.209,-1.545l6.436,1.030l3.347,1.159l6.050,0l-6.436,-1.545l-0.386,1.159z', - GM: - 'M419.855,191.51l0.387,-1.160l2.961,-0.129l0.515,-0.643l0.901,0l1.030,0.643l0.901,0l0.900,-0.387l0.516,0.773l-1.159,0.644l-1.158,-0.128l-1.159,-0.516l-1.030,0.644l-0.514,0l-0.644,0.386l2.447,0.127z', - GN: - 'M442.512,206.313l-0.772,-0.129l-0.515,1.158l-0.772,-0.128l-0.515,-0.515l0.128,-1.029l-1.158,-1.674l-0.644,0.257l-0.643,0.130l-0.644,0.127l0,-1.030l-0.387,-0.642l0,-0.773l-0.515,-1.159l-0.772,-1.029l-2.188,0l-0.644,0.514l-0.772,0.129l-0.386,0.515l-0.387,0.772l-1.415,1.159l-1.159,-1.544l-1.030,-1.031l-0.644,-0.386l-0.772,-0.515l-0.257,-1.159l-0.386,-0.643l-0.773,-0.515l1.159,-1.287l0.901,0.128l0.644,-0.515l0.643,0l0.386,-0.386l-0.257,-0.901l0.257,-0.257l0.129,-0.901l1.287,0l1.931,0.643l0.643,0l0.130,-0.258l1.544,0.129l0.387,-0.129l0.128,1.030l0.387,0l0.772,-0.387l0.386,0.130l0.772,0.643l1.159,0.258l0.772,-0.644l0.773,-0.387l0.643,-0.385l0.515,0.129l0.644,0.643l0.386,0.644l1.030,1.158l-0.516,0.645l-0.128,0.900l0.644,-0.257l0.257,0.386l-0.128,0.773l0.772,0.772l-0.515,0.257l-0.129,0.901l0.516,1.031l0.772,2.187l-1.030,0.387l-0.258,0.257l0.129,0.644l-0.129,1.159l0.386,0z', - GQ: - 'M490.785,224.206l-0.515,-0.387l0.900,-2.960l4.378,0l0.128,3.219l-3.988,0l0.903,-0.128z', - GR: - 'M536.099,131.906l-0.387,0.773l-3.861,0.257l0,-0.515l-3.219,-0.515l0.387,-1.159l1.543,0.902l2.060,-0.129l2.059,0.257l-0.127,0.387l-1.545,0.258zM521.808,116.973l1.804,-0.258l1.029,-0.643l1.417,0.128l0.515,-0.513l0.514,-0.130l1.932,0.130l2.187,-0.773l1.930,1.029l2.445,-0.256l0,-1.417l1.289,0.772l-0.771,1.673l-0.645,0.258l-1.674,0l-1.416,-0.258l-3.218,0.644l1.802,1.545l-1.287,0.387l-1.543,0l-1.418,-1.417l-0.514,0.645l0.643,1.672l1.289,1.159l-1.031,0.644l1.545,1.286l1.286,0.774l0.130,1.545l-2.575,-0.773l0.772,1.417l-1.672,0.256l1.028,2.317l-1.800,0.129l-2.189,-1.287l-1.030,-2.059l-0.516,-1.803l-1.030,-1.288l-1.287,-1.545l-0.258,-0.772l1.288,-1.287l0.128,-0.901l0.901,-0.386l0,0.644z', - GT: - 'M222.516,189.963l-1.417,-0.514l-1.673,0l-1.159,-0.515l-1.544,-1.159l0.128,-0.773l0.257,-0.643l-0.385,-0.514l1.416,-2.188l3.347,0l0.128,-0.903l-0.385,-0.128l-0.387,-0.644l-1.030,-0.643l-0.901,-0.901l1.158,0l0,-1.416l2.575,0l2.446,0l0,2.060l-0.257,3.089l0.772,0l0.901,0.515l0.258,-0.386l0.771,0.257l-1.158,1.030l-1.287,0.772l-0.257,0.516l0.257,0.514l-0.515,0.773l-0.644,0.129l0.129,0.258l-0.515,0.385l-0.901,0.644l0.128,-0.385z', - GW: - 'M424.49,197.173l-1.416,-1.030l-1.159,-0.257l-0.643,-0.773l0,-0.386l-0.772,-0.515l-0.258,-0.644l1.545,-0.386l0.901,0.129l0.644,-0.386l5.020,0.129l-0.129,0.901l-0.257,0.257l0.257,0.901l-0.386,0.386l-0.643,0l-0.644,0.515l-0.901,-0.128l1.159,-1.287z', - GY: - 'M304.257,204.383l1.804,1.028l1.672,1.803l0,1.415l1.030,0l1.417,1.289l1.157,1.028l-0.514,2.319l-1.545,0.772l0.129,0.643l-0.514,1.416l1.157,1.931l0.902,0l0.385,1.545l1.545,2.317l-0.643,0.130l-1.416,-0.259l-0.901,0.644l-1.288,0.515l-0.772,0.128l-0.386,0.515l-1.287,-0.128l-1.674,-1.288l-0.128,-1.287l-0.773,-1.287l0.515,-2.316l0.772,-0.902l-0.644,-1.288l-0.900,-0.386l0.257,-1.159l-0.644,-0.643l-1.287,0.129l-1.930,-2.061l0.772,-0.772l0,-1.287l1.673,-0.385l0.644,-0.516l-0.902,-1.029l0.130,-0.902l-2.187,1.672z', - HN: - 'M229.981,192.023l-0.385,-0.900l-0.902,-0.258l0.258,-1.031l-0.386,-0.256l-0.515,-0.258l-1.287,0.386l0,-0.386l-0.902,-0.386l-0.515,-0.643l-0.772,-0.129l0.515,-0.773l-0.257,-0.514l0.257,-0.516l1.287,-0.772l1.158,-1.030l0.258,0.129l0.644,-0.386l0.772,-0.129l0.257,0.258l0.386,-0.129l1.288,0.257l1.288,-0.128l0.772,-0.258l0.386,-0.258l0.773,0.129l0.643,0.129l0.772,0l0.515,-0.258l1.287,0.387l0.387,0l0.772,0.515l0.773,0.643l1.030,0.387l0.643,0.772l-0.901,-0.128l-0.386,0.386l-0.902,0.386l-0.643,0l-0.643,0.385l-0.516,-0.127l-0.514,-0.517l-0.258,0.130l-0.258,0.643l-0.257,0l-0.129,0.516l-0.900,0.771l-0.515,0.258l-0.258,0.386l-0.773,-0.515l-0.643,0.643l-0.515,0l-0.643,0.129l0,1.288l-0.387,0l-0.257,0.644l0.902,-0.128z', - HR: - 'M516.017,103.327l0.643,1.031l0.773,0.772l-1.030,1.029l-1.160,-0.643l-1.931,0l-2.316,-0.386l-1.159,0l-0.643,0.643l-1.031,-0.643l-0.516,1.159l1.290,1.158l0.643,0.901l1.286,1.031l0.901,0.514l1.031,1.159l2.445,1.030l-0.258,0.514l-2.572,-1.030l-1.547,-1.029l-2.444,-0.773l-2.318,-1.931l0.514,-0.257l-1.157,-1.159l-0.130,-0.901l-1.674,-0.386l-0.898,1.159l-0.774,-0.901l0.128,-0.902l0.129,-0.128l1.802,0.128l0.516,-0.386l0.901,0.386l1.030,0l0,-0.772l0.901,-0.257l0.255,-1.030l2.190,-0.773l0.902,0.386l1.930,1.159l2.316,0.515l-1.032,0.387zM502.372,101.654l2.315,0.258l1.289,-0.644l2.446,0l0.515,-0.515l0.385,0l0.515,0.901l-2.190,0.773l-0.255,1.030l-0.901,0.257l0,0.772l-1.030,0l-0.901,-0.386l-0.516,0.386l-1.802,-0.128l0.517,-0.258l-0.646,-1.029l-0.259,1.417z', - HT: - 'M268.085,173.357l1.673,0.129l2.317,0.387l0.259,1.416l-0.259,1.029l-0.643,0.515l0.643,0.772l0,0.773l-1.802,-0.515l-1.287,0.257l-1.673,-0.257l-1.159,0.515l-1.545,-0.773l0.258,-0.900l2.446,0.385l2.060,0.258l1.029,-0.643l-1.288,-1.159l0,-1.030l-1.673,-0.387l-0.644,0.772z', - HU: - 'M508.937,100.753l0.900,-1.674l-0.643,-0.643l1.545,0l0.257,-1.158l1.288,0.772l1.028,0.257l2.318,-0.257l0.129,-0.644l1.158,0l1.287,-0.515l0.258,0.258l1.287,-0.387l0.645,-0.643l0.900,-0.129l2.832,0.772l0.645,-0.257l1.415,0.773l0.256,0.643l-1.671,0.643l-1.290,1.803l-1.673,1.802l-2.059,0.515l-1.672,-0.129l-2.060,0.772l-1.032,0.387l-2.316,-0.515l-1.930,-1.159l-0.902,-0.386l-0.515,-0.901l0.385,0z', - ID: - 'M801.921,250.982l0.258,0.515v0.772l-1.674,2.061l-2.317,0.516l-0.386-0.258l0.258-0.902l1.158-1.674L801.921,250.982zM826.767,245.576l-0.258-2.059l0.516-0.902l0.516-1.03l0.643,0.901v1.285L826.767,245.576zM845.175,242.742v8.755l-2.447-2.188l-2.701-0.514l-0.645,0.771l-3.475,0.129l1.155-2.189l1.677-0.771l-0.645-2.963l-1.287-2.316l-5.279-2.188l-2.188-0.256l-3.992-2.447l-0.898,1.287l-1.031,0.258l-0.516-1.03v-1.157l-2.059-1.288l2.832-1.03h1.932l-0.26-0.644h-3.859l-1.16-1.674l-2.314-0.515l-1.16-1.287l3.605-0.644l1.414-0.901l4.248,1.16l0.516,1.027l0.771,4.248l2.705,1.676l2.316-2.833l3.091-1.674h2.315l2.318,0.901l2.059,1.029l2.832,0.516L845.175,242.742zM761.116,223.434l1.801,1.416l1.803-0.514l1.672,0.257l1.546-1.417l1.288-0.257l2.574,0.772l2.189-0.516l1.414-3.861l1.031-0.901l0.9-3.089h3.09l2.316,0.515l-1.545,2.446l2.059,2.574l-0.514,1.16l3.09,2.573l-3.217,0.257l-0.902,1.803l0.129,2.447l-2.575,1.93l-0.13,2.574l-1.029,4.119l-0.387-0.9l-3.088,1.158l-1.03-1.543l-1.931-0.258l-1.287-0.773l-3.219,0.9l-1.029-1.287l-1.801,0.129l-2.188-0.256l-0.388-3.606l-1.416-0.772l-1.287-2.316l-0.26-2.317l0.26-2.573l1.546-1.675L761.116,223.434zM813.765,234.505l2.961,0.772l0.902,2.059l-2.19-1.029l-2.317-0.256l-1.545,0.129h-1.801l0.643-1.546L813.765,234.505zM807.069,237.209l-1.93-0.516l-0.516-1.158l2.705-0.129l0.643,0.9L807.069,237.209zM809.903,221.117l0.129,1.416l1.674,0.258l0.256,1.158l-0.256,2.316l-1.289-0.258l-0.514,1.674l1.159,1.418l-0.774,0.256l-1.029-1.674l-0.771-3.476l0.514-2.06L809.903,221.117zM796.386,224.593l3.09-0.13l2.703-1.93l0.387,0.643l-2.061,2.704l-2.059,0.515l-2.574-0.644l-4.506,0.257l-2.316,0.387l-0.387,1.932l2.315,2.445l1.546-1.158l5.021-1.031l-0.258,1.289l-1.158-0.387l-1.16,1.545l-2.445,1.029l2.574,3.477l-0.514,0.902l2.445,3.217v1.674l-1.416,0.771l-1.029-0.901l1.287-2.187l-2.703,1.028l-0.645-0.772l0.385-1.031l-1.931-1.543l0.132-2.574l-1.805,0.773l0.257,3.088l0.13,3.861l-1.801,0.387l-1.16-0.773l0.773-2.443l-0.389-2.574l-1.156-0.131l-0.771-1.802l1.158-1.802l0.385-2.061l1.288-4.119l0.515-1.029l2.317-2.061l2.188,0.772L796.386,224.593zM789.306,254.588l-3.604-1.804l2.574-0.644l1.416,0.902l0.902,0.771l-0.131,0.773H789.306zM792.138,249.953l1.803-0.129l2.316-1.029l-0.385,1.544l-3.992,0.644l-3.604-0.258v-1.029l2.188-0.516L792.138,249.953zM783.771,249.566l1.673-0.258l0.645,1.158l-3.09,0.516l-1.803,0.387h-1.545l1.029-1.674h1.416l0.773-0.9L783.771,249.566zM757.511,244.287l0.386,0.902l5.149,0.258l0.514-1.031l5.021,1.288l1.029,1.674l3.99,0.515l3.35,1.674l-3.092,1.031l-2.962-1.16l-2.444,0.129l-2.832-0.258l-2.445-0.514l-3.219-0.902l-1.932-0.387l-1.158,0.387l-4.891-1.158l-0.387-1.158l-2.574-0.129l1.93-2.574l3.219,0.127l2.189,1.031l-1.157-0.256L757.511,244.287zM746.438,229.871l0.388,1.932l0.903,1.415l2.058,0.257l1.289,1.803l-0.645,3.347l-0.129,4.118h-2.961l-2.316-2.188l-3.477-2.188l-1.158-1.674l-2.059-2.188l-1.289-2.06l-2.062-3.733l-2.313-2.188l-0.775-2.317l-1.027-2.187l-2.447-1.674l-1.416-2.318l-2.06-1.416l-2.705-3.09l-0.256-1.287l1.675,0.129l4.247,0.515l2.317,2.575l2.058,1.803l1.548,1.157l2.571,2.962h2.706l2.188,1.801l1.674,2.318l2.06,1.158l-1.157,2.188l1.545,1.03h-1.027H746.438z', - IE: - 'M448.562,81.83l0.387,1.931l-2.061,2.445l-4.764,1.544l-3.732-0.385l2.188-2.832l-1.415-2.703l3.604-2.06l2.06-1.287l0.515,1.415l-0.515,1.416h1.673L448.562,81.83z', - IL: - 'M561.458,138.857l-0.516,0.902l-0.900,-0.387l-0.645,1.803l0.774,0.258l-0.774,0.385l-0.128,0.644l1.287,-0.257l0.130,1.029l-1.417,4.249l-1.674,-4.635l0.773,-0.901l-0.258,-0.129l0.772,-1.287l0.515,-1.931l0.385,-0.773l0.130,0l0.900,0l0.259,-0.515l0.643,0l0,1.160l0.256,-0.385z', - IN: - 'M674.866,131.391l2.961,3.089l-0.256,2.189l1.03,1.416l-0.13,1.416l-1.932-0.385l0.773,2.96l2.703,1.674l3.732,1.93l-1.672,1.16l-1.16,2.573l2.703,1.031l2.447,1.287l3.604,1.545l3.604,0.386l1.674,1.287l2.059,0.257l3.22,0.644h2.188l0.385-1.158l-0.385-1.674l0.258-1.159l1.543-0.514l0.259,2.059l0.129,0.516l2.446,1.029l1.673-0.386l2.188,0.129h2.188l0.257-1.674l-1.158-0.901l2.188-0.258l2.444-2.059l3.092-1.674l2.314,0.643l1.934-1.158l1.285,1.674l-0.899,1.159l2.832,0.386l0.258,1.029l-1.03,0.515l0.256,1.674l-1.93-0.515l-3.475,1.802l0.127,1.545l-1.545,2.317l-0.127,1.287l-1.159,2.189l-2.188-0.515v2.704l-0.642,0.9l0.255,1.159l-1.287,0.643l-1.416-4.247h-0.771l-0.387,1.802l-1.545-1.416l0.901-1.545l1.157-0.129l1.289-2.317l-1.545-0.515l-2.573,0.128l-2.574-0.386l-0.26-1.931l-1.285-0.128l-2.062-1.159l-1.03,1.803l2.06,1.415l-1.802,1.03L702.544,161l1.673,0.643l-0.515,1.674l0.901,2.059l0.515,2.188l-0.387,1.03l-1.931-0.128l-3.218,0.643l0.129,1.931l-1.416,1.674l-3.86,1.802l-3.092,3.218l-2.06,1.675l-2.574,1.673l-0.129,1.287l-1.287,0.644l-2.446,1.029l-1.287,0.129l-0.772,2.059l0.646,3.476l0.127,2.189l-1.159,2.574v4.635l-1.414,0.128l-1.289,2.06l0.903,0.901l-2.448,0.772l-0.9,1.802l-1.157,0.772l-2.576-2.574l-1.159-3.734l-1.027-2.703l-1.03-1.287l-1.416-2.575l-0.646-3.347l-0.513-1.674l-2.448-3.733l-1.029-5.278l-0.899-3.346v-3.347l-0.517-2.446l-3.86,1.544l-1.932-0.257l-3.476-3.347l1.287-0.901l-0.772-1.159l-3.218-2.188l1.801-1.802h5.922l-0.514-2.317l-1.545-1.417l-0.258-2.059l-1.802-1.159l2.961-2.832l3.218,0.129l2.704-2.833l1.802-2.702l2.575-2.704v-1.931l2.187-1.545l-2.059-1.287l-1.031-1.802l-0.899-2.447l1.286-1.157l4.121,0.643l2.961-0.386L674.866,131.391z', - IQ: - 'M585.658,126.628l0.128,0l1.803,3.476l1.802,0.772l0.130,1.545l-1.289,0.902l-0.643,2.187l1.802,2.575l3.347,1.416l1.415,2.060l-0.514,1.931l0.901,0l0,1.416l1.545,1.416l-1.674,-0.128l-1.803,-0.258l-1.930,2.703l-5.020,-0.258l-7.596,-5.406l-3.990,-1.931l-3.218,-0.773l-1.158,-3.218l6.051,-2.832l1.029,-3.218l-0.258,-1.931l1.417,-0.773l1.416,-1.673l1.158,-0.385l3.091,0.385l0.899,0.643l1.287,-0.385l0.128,0.258z', - IR: - 'M610.502,126.756l2.317,-0.513l1.932,-1.546l1.803,0.129l1.157,-0.515l1.932,0.257l2.961,1.288l2.188,0.387l3.088,2.317l2.060,0.128l0.129,2.188l-1.029,3.477l-0.773,1.930l1.158,0.386l-1.158,1.416l0.902,2.188l0.256,1.674l2.060,0.515l0.129,1.673l-2.445,2.447l1.414,1.415l1.031,1.674l2.574,1.159l0.128,2.446l1.288,0.386l0.259,1.287l-3.992,1.288l-1.030,3.218l-5.020,-0.902l-2.961,-0.515l-2.961,-0.386l-1.160,-3.346l-1.285,-0.515l-2.058,0.515l-2.706,1.287l-3.345,-0.901l-2.705,-2.060l-2.575,-0.773l-1.800,-2.446l-2.061,-3.604l-1.416,0.387l-1.674,-0.902l-1.029,1.030l-1.545,-1.416l0,-1.416l-0.901,0l0.514,-1.931l-1.415,-2.060l-3.347,-1.416l-1.802,-2.575l0.643,-2.187l1.289,-0.902l-0.130,-1.545l-1.802,-0.772l-1.803,-3.476l-0.128,0l-1.288,-1.931l0.516,-0.901l-0.773,-3.089l1.802,-0.772l0.387,1.028l1.415,1.288l1.804,0.386l1.029,-0.129l3.089,-1.930l1.030,-0.258l0.773,0.773l-0.902,1.415l1.674,1.417l0.643,-0.129l0.901,1.931l2.575,0.516l1.803,1.415l3.862,0.385l4.247,-0.643l-0.257,0.644z', - IS: - 'M426.163,47.974l-0.644,1.672l3.09,1.932l-3.604,2.059l-7.723,1.802l-2.318,0.515l-3.475-0.385l-7.596-0.902l2.703-1.158l-5.922-1.287l4.763-0.516l-0.128-0.9l-5.663-0.644l1.93-1.674l3.991-0.386l4.248,1.803l4.118-1.417l3.349,0.645l4.376-1.417L426.163,47.974z', - IT: - 'M493.361,100.624l1.672,0.386l0.258,-0.514l2.703,-0.386l0.644,0.900l3.734,0.644l-0.259,1.417l0.646,1.029l-2.063,-0.386l-2.315,1.030l0.257,1.287l-0.387,0.772l0.900,1.417l2.577,1.287l1.287,2.317l2.961,2.189l2.187,-0.130l0.645,0.644l-0.773,0.515l2.445,1.030l1.933,0.772l2.315,1.416l0.257,0.516l-0.513,0.900l-1.417,-1.157l-2.316,-0.516l-1.159,1.803l1.931,0.901l-0.387,1.416l-1.030,0.257l-1.544,2.317l-1.029,0.129l0,-0.772l0.514,-1.417l0.644,-0.643l-1.158,-1.545l-0.772,-1.417l-1.160,-0.256l-0.772,-1.159l-1.673,-0.515l-1.159,-1.030l-2.060,-0.257l-2.061,-1.159l-2.444,-1.802l-1.933,-1.545l-0.772,-2.703l-1.286,-0.258l-2.189,-0.901l-1.288,0.386l-1.545,1.287l-1.157,0.130l0.258,-1.160l-1.418,-0.386l-0.642,-2.058l0.901,-0.774l-0.772,-1.029l0.128,-0.772l1.160,0.643l1.287,-0.128l1.543,-1.031l0.387,0.516l1.288,-0.129l0.643,-1.030l1.932,0.257l1.158,-0.386l-0.258,1.159zM504.944,124.183l2.061,-0.258l-0.901,2.188l0.387,0.773l-0.644,1.415l-2.061,-1.030l-1.286,-0.256l-3.733,-1.416l0.384,-1.288l3.091,0.257l-2.702,0.385zM488.726,116.844l1.287,-0.901l1.675,1.931l-0.387,3.605l-1.288,-0.258l-1.029,0.902l-1.032,-0.644l-0.128,-3.219l-0.642,-1.545l-1.544,-0.129z', - JM: - 'M256.242,177.22l1.802,0.128l1.416,0.644l0.515,0.772l-1.931,0.129l-0.772,0.386l-1.544,-0.386l-1.545,-1.030l0.385,-0.643l1.030,-0.130l-0.644,-0.130z', - JO: - 'M560.942,139.759l0.516,-0.902l2.960,1.031l5.278,-2.833l1.158,3.218l-0.514,0.516l-5.407,1.287l2.703,2.703l-0.901,0.515l-0.515,0.902l-2.060,0.386l-0.643,0.901l-1.160,0.900l-2.960,-0.514l-0.128,-0.386l1.417,-4.249l-0.130,-1.029l0.386,-0.902l0,1.544z', - JP: - 'M847.491,121.479l-2.574,2.704l0.129,2.703l-1.031,2.188l0.387,1.287l-1.287,1.931l-3.477,1.288l-4.762,0.128l-3.861,3.09l-1.801-1.03l-0.129-1.932l-4.635,0.517l-3.22,1.287h-3.089l2.703,2.059l-1.803,4.506l-1.801,1.159l-1.287-1.031l0.643-2.445l-1.672-0.772l-1.031-1.804l2.445-0.9l1.416-1.674l2.705-1.415l2.06-1.803l5.276-0.773l2.961,0.516l2.832-4.764l1.803,1.288l3.861-2.704l1.545-1.029l1.674-3.347l-0.387-2.961l1.158-1.803l2.832-0.386l1.416,3.734v-2.188V121.479zM854.829,108.606l1.93-1.159l0.516,2.961l-3.99,0.772l-2.316,2.703l-4.25-1.931l-1.414,2.962l-3.09,0.128l-0.387-2.703l1.416-2.06l2.832-0.128l0.773-3.734l0.771-2.188l3.219,2.832l2.06,0.901l-1.93-0.644L854.829,108.606zM821.874,136.798l1.416-1.545l1.545,0.257l1.16-1.157l1.93,0.643l0.388,0.9l-1.546,1.674l-1.158-0.901l-1.287,0.643l-0.773,1.545l-1.801-0.772L821.874,136.798z', - KE: - 'M561.972,214.552l2.318,0l3.089,2.574l0.772,0.130l1.159,0.385l0.900,-0.258l1.031,0.387l1.030,-1.159l2.445,-1.287l1.931,1.158l1.030,-0.256l-2.188,2.960l-0.130,10.169l1.931,2.189l-1.931,1.030l-0.514,1.416l-1.030,0.258l-0.515,1.545l-0.902,1.158l-0.513,1.673l-1.031,1.157l-4.119,-2.445l-0.256,-2.059l-10.042,-5.793l0,-2.832l0,-0.772l1.931,-1.674l1.029,-1.931l-0.771,-1.930l-1.031,-2.704l-1.287,-1.930l1.416,-1.159l2.188,-2.447l1.159,0.515l0.772,1.288l-0.129,-0.644z', - KG: - 'M656.46,113.111l0.514,-1.159l1.801,-0.386l4.378,0.902l0.514,-1.545l1.545,-0.644l3.732,1.159l1.030,-0.258l4.505,0l3.993,0.386l1.287,0.902l1.674,0.386l-0.387,0.644l-4.248,1.416l-0.901,1.158l-3.476,0.258l-1.029,1.673l-2.834,-0.257l-1.930,0.514l-2.574,1.288l0.386,0.643l-0.773,0.516l-5.020,0.514l-3.347,-0.901l-2.961,0.129l0.257,-1.545l2.961,0.515l1.030,-0.900l2.060,0.257l3.346,-1.932l-3.090,-1.416l-1.929,0.772l-2.061,-1.030l2.317,-1.801l0.770,0.258z', - KH: - 'M743.995,198.331l-1.031,-1.415l-1.416,-2.834l-0.643,-3.217l1.801,-2.189l3.475,-0.514l2.447,0.387l2.316,1.029l1.160,-1.803l2.446,0.901l0.644,1.803l-0.386,3.218l-4.506,2.059l1.160,1.674l-2.834,0.258l-2.316,1.030l2.317,0.387z', - KP: - 'M817.112,112.726l0.385,0.514l-1.029-0.129l-1.158,0.902l-0.773,0.901l0.131,1.93l-1.418,0.644l-0.516,0.386l-1.027,0.772l-1.803,0.516l-1.158,0.773v1.158l-0.387,0.257l1.156,0.386l1.418,1.159l-0.385,0.772l-1.033,0.129l-1.93,0.129l-1.029,1.158h-1.284l-0.132,0.257l-1.286-0.514l-0.386,0.386l-0.773,0.257l-0.129-0.514l-0.645-0.258l-0.771-0.386l0.771-1.159l0.645-0.385l-0.256-0.387l0.641-1.545l-0.127-0.386l-1.545-0.258l-1.289-0.772l2.189-1.673l2.961-1.546l1.803-1.93l1.286,0.9l2.319,0.13l-0.387-1.417l4.248-1.157l1.028-1.546L817.112,112.726z', - KR: - 'M810.933,122.895l2.447,3.218l0.642,1.803l0,3.089l-1.029,1.416l-2.445,0.515l-2.190,1.159l-2.445,0.258l-0.258,-1.545l0.514,-1.932l-1.158,-2.833l1.931,-0.514l-1.802,-2.189l0.131,-0.257l1.285,0l1.029,-1.158l1.930,-0.129l1.033,-0.129l-0.385,0.772z', - KW: - 'M594.411,146.196l0.645,1.158l-0.257,0.643l0.9,2.06l-1.93,0.129l-0.645-1.288l-2.447-0.257l1.931-2.703L594.411,146.196z', - KZ: - 'M656.46,113.111l-1.547,0.515l-3.603,1.802l-1.160,1.931l-1.030,0.129l-0.771,-1.288l-3.347,-0.128l-0.644,-2.189l-1.287,0l0.258,-2.703l-3.219,-2.060l-4.633,0.259l-3.219,0.385l-2.574,-2.446l-2.189,-1.029l-4.120,-1.931l-0.515,-0.129l-6.951,1.544l0.130,9.914l-1.419,0.128l-1.930,-2.060l-1.800,-0.772l-3.090,0.515l-1.160,0.900l-0.127,-0.643l0.642,-1.159l-0.515,-0.900l-3.089,-0.902l-1.286,-2.446l-1.416,-0.644l-0.130,-0.901l2.702,0.258l0,-1.931l2.320,-0.514l2.316,0.385l0.515,-2.574l-0.387,-1.674l-2.704,0.129l-2.316,-0.644l-3.090,1.159l-2.574,0.643l-1.416,-0.514l0.387,-1.416l-1.803,-1.803l-1.931,0.129l-2.317,-1.802l1.545,-2.060l-0.772,-0.515l2.186,-2.960l2.705,1.544l0.387,-1.931l5.535,-2.962l4.248,-0.127l5.922,1.931l3.088,1.029l2.961,-1.158l4.250,0l3.474,1.416l0.773,-0.772l3.732,0l0.644,-1.159l-4.376,-1.931l2.702,-1.288l-0.515,-0.772l2.575,-0.644l-1.929,-1.931l1.158,-0.901l10.039,-0.901l1.418,-0.644l6.693,-1.028l2.446,-1.160l4.763,0.644l0.901,2.833l2.832,-0.645l3.474,0.901l-0.258,1.416l2.577,-0.128l6.822,-2.575l-1.029,0.901l3.474,2.060l5.922,6.822l1.545,-1.416l3.605,1.546l3.860,-0.644l1.545,0.514l1.289,1.545l1.930,0.515l1.158,1.159l3.478,-0.387l1.414,1.675l-2.060,1.801l-2.317,0.128l-0.127,2.704l-1.416,1.288l-5.408,-0.901l-1.931,4.892l-1.415,0.514l-5.279,1.159l2.445,4.635l-1.931,0.643l0.260,1.545l-1.674,-0.386l-1.287,-0.902l-3.993,-0.386l-4.505,0l-1.030,0.258l-3.732,-1.159l-1.545,0.644l-0.514,1.545l-4.378,-0.902l-1.801,0.386l0.514,-1.159z', - LA: - 'M748.628,188.549l0.902,-1.288l0.127,-2.189l-2.187,-2.446l-0.129,-2.574l-2.059,-2.189l-2.060,-0.258l-0.516,1.030l-1.545,0l-0.900,-0.385l-2.832,1.544l0,-2.446l0.642,-2.832l-1.800,-0.128l-0.129,-1.546l-1.161,-0.900l0.516,-0.902l2.318,-1.802l0.256,0.643l1.418,0l-0.386,-3.089l1.416,-0.386l1.544,2.188l1.159,2.446l3.347,0l1.028,2.317l-1.672,0.772l-0.774,0.902l3.219,1.674l2.188,3.217l1.673,2.318l2.061,1.931l0.645,1.803l-0.387,2.702l-2.446,-0.901l-1.160,1.803l2.316,1.029z', - LB: - 'M561.714,137.312l-0.643,0l-0.259,0.515l-0.900,0l0.900,-2.187l1.289,-1.932l0.128,0l1.159,0.128l0.515,1.031l-1.546,1.029l-0.514,1.416l0.129,0z', - LK: - 'M685.552,206.699l-0.387,2.832l-1.158,0.771l-2.317,0.643l-1.288-2.187l-0.514-3.862l1.285-4.377l1.805,1.545l1.285,1.802L685.552,206.699z', - LR: - 'M444.442,215.195l-0.643,0l-2.832,-1.287l-2.446,-2.060l-2.317,-1.416l-1.802,-1.673l0.644,-0.902l0.129,-0.771l1.287,-1.546l1.159,-1.157l0.643,-0.130l0.644,-0.257l1.158,1.674l-0.128,1.029l0.515,0.515l0.772,0.128l0.515,-1.158l0.772,0.129l-0.129,0.773l0.258,1.287l-0.644,1.158l0.901,0.772l0.772,0.129l1.159,1.158l0.129,1.030l-0.257,0.387l0.259,-2.188z', - LS: - 'M543.306,304.922l0.902,0.900l-0.773,1.287l-0.515,0.902l-1.417,0.385l-0.514,0.901l-1.030,0.258l-1.931,-2.059l1.416,-1.803l1.416,-1.029l1.287,-0.516l-1.159,-0.774z', - LT: - 'M526.442,80.67l-0.128,-0.772l0.259,-0.643l-1.289,-0.515l-2.702,-0.386l-0.644,-2.317l3.088,-0.773l4.506,0.130l2.705,-0.259l0.385,0.515l1.416,0.257l2.574,1.288l0.258,1.159l-2.189,0.901l-0.643,1.545l-2.961,0.901l-2.574,0l-0.644,-0.772l1.417,0.259z', - LU: - 'M481.516,91.999l0.516,0.515l-0.129,1.159l-0.773,0.129l-0.643,-0.259l0.385,-1.544l-0.644,0z', - LV: - 'M521.938,76.037l0.128,-2.060l1.288,-1.674l2.573,-0.900l2.060,2.059l2.190,-0.128l0.513,-2.061l2.319,-0.514l1.158,0.387l2.316,1.028l2.318,0l1.286,0.644l0.129,1.287l0.901,1.545l-2.831,1.031l-1.674,0.514l-2.574,-1.288l-1.416,-0.257l-0.385,-0.515l-2.705,0.259l-4.506,-0.130l3.088,-0.773z', - LY: - 'M505.204,165.376l-1.932,1.03l-1.416-1.544l-4.248-1.159l-1.288-1.674l-2.061-1.158l-1.286,0.385l-0.901-1.415l-0.127-1.159l-1.546-2.059l1.029-1.03l-0.259-1.673l0.387-1.546l-0.256-1.158l0.514-2.318l-0.127-1.158l-0.902-2.446l1.287-0.643l0.257-1.031l-0.257-1.158l1.803-1.029l0.9-0.902l1.287-0.772l0.13-2.06l3.217,0.901l1.03-0.257l2.319,0.514l3.603,1.159l1.159,2.446l2.446,0.515l3.86,1.158l2.833,1.288l1.286-0.644l1.288-1.287l-0.644-2.059l0.9-1.288l1.932-1.288l1.801-0.385l3.734,0.514l0.901,1.287h1.028l0.773,0.516l2.703,0.257l0.645,0.901l-0.902,1.287l0.387,1.159l-0.772,1.673l0.901,2.189v9.526v9.912v5.408h-2.832v1.415l-11.069-5.407l-10.814-5.149L505.204,165.376z', - MA: - 'M461.436,138.472l0.772,0.514l-0.515,1.030l-3.476,0.515l-1.287,1.030l-1.545,0.128l-0.128,2.061l-3.090,1.029l-1.030,1.417l-2.188,0.644l-2.703,0.514l-4.377,1.931l0,3.218l-0.387,0l0,1.417l-1.544,0.128l-0.901,0.515l-1.288,0l-0.900,-0.257l-2.319,0.257l-0.900,2.060l-0.773,0.257l-1.287,3.347l-3.733,2.961l-0.901,3.733l-1.159,1.159l-0.257,1.029l-6.050,0.129l-0.129,0l0.129,-1.158l1.030,-0.772l0.901,-1.416l-0.129,-0.902l0.901,-1.930l1.545,-1.674l0.901,-0.515l0.644,-1.546l0.128,-1.415l0.901,-1.673l1.802,-1.031l1.803,-2.703l1.287,-1.030l2.574,-0.386l2.060,-1.802l1.416,-0.644l2.189,-2.317l-0.644,-3.347l1.031,-2.317l0.384,-1.416l1.675,-1.803l2.703,-1.287l2.059,-1.029l1.802,-2.833l0.773,-1.673l2.059,0l1.545,1.158l2.575,-0.128l2.832,0.515l1.159,0.128l1.030,1.674l0.128,1.674l-0.902,-2.832z', - MD: - 'M536.998,97.02l0.644,-0.386l1.675,-0.259l2.059,0.903l1.029,0.128l1.287,0.644l-0.257,0.901l1.030,0.515l0.386,1.030l0.902,0.772l-0.131,0.386l0.517,0.258l-0.773,0.257l-1.545,-0.129l-0.258,-0.386l-0.513,0.258l0.129,0.386l-0.774,0.901l-0.385,0.901l-0.773,0.386l-0.387,-1.287l0.257,-1.159l-0.128,-1.158l-1.545,-1.674l-0.902,-1.029l-0.770,-0.901l0.774,0.258z', - MG: - 'M598.66,260.508l0.772,1.160l0.643,1.801l0.385,3.219l0.775,1.287l-0.257,1.287l-0.518,0.773l-0.898,-1.545l-0.516,0.772l0.516,2.060l-0.258,1.158l-0.774,0.516l-0.129,2.316l-1.028,3.219l-1.417,3.604l-1.545,5.149l-1.031,3.734l-1.285,3.089l-2.188,0.644l-2.318,1.157l-1.544,-0.641l-2.189,-1.031l-0.773,-1.417l-0.129,-2.317l-0.900,-2.188l-0.258,-1.931l0.387,-1.930l1.287,-0.515l0,-0.901l1.288,-1.932l0.257,-1.802l-0.645,-1.285l-0.514,-1.676l-0.128,-2.446l0.900,-1.544l0.387,-1.673l1.287,-0.130l1.544,-0.514l0.901,-0.516l1.289,0l1.544,-1.544l2.189,-1.674l0.771,-1.415l-0.387,-1.159l1.159,0.386l1.545,-1.931l0,-1.544l0.901,-1.288l-0.902,-1.158z', - MK: - 'M520.651,114.27l0.385,0l0.129,-0.515l1.545,-0.515l1.544,-0.257l1.288,0l1.287,0.900l0.258,1.674l-0.514,0.130l-0.515,0.513l-1.417,-0.128l-1.029,0.643l-1.804,0.258l-1.029,-0.643l-0.385,-1.160l-0.257,0.900z', - ML: - 'M432.471,187.646l0.902,-0.514l0.385,-1.674l0.902,0l1.930,0.772l1.416,-0.514l1.160,0.129l0.385,-0.644l10.814,0l0.514,-1.931l-0.385,-0.257l-1.288,-11.587l-1.416,-11.714l4.119,0l9.140,5.922l9.139,5.792l0.645,1.288l1.672,0.772l1.159,0.387l0.128,1.801l2.961,-0.257l0,6.179l-1.543,1.802l-0.130,1.674l-2.445,0.386l-3.735,0.258l-0.900,0.901l-1.802,0.129l-1.673,0l-0.644,-0.516l-1.545,0.387l-2.446,1.158l-0.514,0.774l-2.189,1.285l-0.257,0.645l-1.159,0.514l-1.287,-0.257l-0.773,0.644l-0.385,1.802l-2.189,2.189l0.128,0.900l-0.771,1.159l0.128,1.545l-1.030,0.515l-0.643,0.257l-0.387,-1.157l-0.772,0.385l-0.516,-0.129l-0.514,0.772l-2.059,0l-0.772,-0.386l-0.387,0.258l-0.772,-0.772l0.128,-0.773l-0.257,-0.386l-0.644,0.257l0.128,-0.900l0.516,-0.645l-1.030,-1.158l-0.386,-0.644l-0.644,-0.643l-0.515,-0.129l-0.643,0.385l-0.773,0.387l-0.772,0.644l-1.159,-0.258l-0.772,-0.643l-0.386,-0.130l-0.772,0.387l-0.387,0l-0.128,-1.030l0.128,-0.772l-0.257,-1.030l-1.030,-0.772l-0.515,-1.545l0.129,1.674z', - MM: - 'M733.437,172.585l-1.672,1.159l-1.803,0.129l-1.287,2.960l-1.158,0.515l1.287,2.317l1.802,1.931l1.030,1.802l-0.901,2.318l-1.029,0.514l0.643,1.416l1.802,2.060l0.387,1.545l-0.129,1.287l1.158,2.447l-1.545,2.445l-1.287,2.832l-0.257,-2.059l0.773,-2.060l-0.902,-1.544l0.257,-2.962l-1.158,-1.416l-0.773,-3.219l-0.516,-3.345l-1.158,-2.318l-1.803,1.415l-3.088,1.932l-1.416,-0.257l-1.673,-0.644l0.902,-3.347l-0.647,-2.575l-2.058,-3.090l0.386,-0.900l-1.671,-0.387l-1.934,-2.188l-0.127,-2.189l0.900,0.387l0.129,-1.932l1.287,-0.643l-0.255,-1.159l0.642,-0.900l0,-2.704l2.188,0.515l1.160,-2.189l0.127,-1.287l1.545,-2.317l-0.127,-1.545l3.474,-1.802l1.930,0.515l-0.256,-1.674l1.030,-0.515l-0.258,-1.029l1.545,-0.130l0.900,1.545l1.289,0.644l0,2.060l-0.131,2.188l-2.445,2.318l-0.387,3.089l2.832,-0.386l0.645,2.446l1.674,0.515l-0.772,2.317l2.059,1.030l1.160,0.387l1.930,-0.773l0.128,1.158l-2.318,1.802l-0.516,0.902l1.544,-0.643z', - MN: - 'M701.642,94.188l2.832-0.515l5.148-2.317l4.121-1.287l2.316,0.901h2.832l1.803,1.287l2.702,0.129l3.862,0.644l2.574-1.931l-1.029-1.545l2.703-2.832l3.09,1.158l2.445,0.257l3.09,0.773l0.516,1.931l3.861,1.158l2.574-0.515l3.348-0.257l2.705,0.257l2.701,1.287l1.674,1.417h2.445l3.348,0.386l2.574-0.644l3.477-0.387l3.99-1.93l1.545,0.258l1.414,0.9l3.219-0.128l-1.287,1.931l-1.932,2.704l0.771,1.158l1.42-0.386l2.701,0.386l2.059-0.901l2.189,0.772l2.448,1.931l-0.259,0.902l-2.189-0.258l-3.861,0.386l-1.932,0.772l-1.932,1.803l-4.119,1.029l-2.703,1.417l-2.832-0.516l-1.416-0.257l-1.416,1.674l0.773,1.03l0.516,0.9l-1.932,0.902l-1.932,1.416l-3.088,1.03l-4.121,0.128l-4.375,0.902l-3.09,1.416l-1.16-0.773h-3.347l-3.862-1.673l-2.701-0.386l-3.604,0.386l-5.536-0.644h-2.962l-1.673-1.545l-1.158-2.446l-1.673-0.386l-3.218-1.674l-3.605-0.385l-3.216-0.387l-1.032-1.158l1.032-3.219l-1.804-2.188l-3.862-0.901l-2.317-1.545L701.642,94.188z', - MR: - 'M432.471,187.646l-1.802,-1.930l-1.674,-1.931l-1.801,-0.772l-1.288,-0.773l-1.416,0l-1.287,0.643l-1.416,-0.257l-0.901,0.901l-0.258,-1.416l0.773,-1.416l0.386,-2.445l-0.386,-2.704l-0.258,-1.417l0.258,-1.287l-0.773,-1.287l-1.416,-1.158l0.643,-0.901l10.557,0l-0.515,-3.862l0.644,-1.417l2.574,-0.257l-0.129,-6.823l8.883,0.129l0,-4.120l10.040,6.566l-4.119,0l1.416,11.714l1.288,11.587l0.385,0.257l-0.514,1.931l-10.814,0l-0.385,0.644l-1.160,-0.129l-1.416,0.514l-1.930,-0.772l-0.902,0l-0.385,1.674l0.902,-0.514z', - MW: - 'M558.368,258.062l-0.773,1.932l0.773,3.605l0.901,-0.130l1.030,0.902l1.030,1.930l0.258,3.476l-1.160,0.645l-0.772,1.801l-1.802,-1.674l-0.258,-1.931l0.645,-1.158l-0.130,-1.159l-1.159,-0.644l-0.643,0.259l-1.545,-1.289l-1.416,-0.770l0.772,-2.447l0.773,-0.902l-0.516,-2.317l0.645,-2.189l0.386,-0.644l-0.644,-2.315l-1.287,-1.160l2.704,0.514l1.415,1.933l-0.773,-3.732z', - - MX: - 'M203.592,157.266l-1.030,2.446l-0.515,1.931l-0.257,3.605l-0.257,1.287l0.514,1.416l0.773,1.287l0.644,2.188l1.802,1.931l0.515,1.545l1.158,1.416l2.832,0.643l1.029,1.159l2.447,-0.772l2.060,-0.258l1.930,-0.513l1.803,-0.388l1.672,-1.158l0.644,-1.545l0.258,-2.317l0.386,-0.772l1.803,-0.644l2.961,-0.644l2.316,0l1.674,-0.129l0.644,0.516l-0.129,1.287l-1.417,1.674l-0.643,1.544l0.515,0.515l-0.386,1.158l-0.772,2.060l-0.644,-0.644l-0.515,0l-0.515,0.130l-1.030,1.544l-0.515,-0.258l-0.257,0.129l0,0.387l-2.446,0l-2.575,0l0,1.416l-1.158,0l0.901,0.901l1.030,0.643l0.387,0.644l0.385,0.128l-0.128,0.903l-3.347,0l-1.416,2.188l0.385,0.514l-0.257,0.643l-0.128,0.773l-2.961,-2.832l-1.416,-0.901l-2.189,-0.772l-1.544,0.257l-2.189,1.030l-1.287,0.258l-1.930,-0.773l-2.060,-0.515l-2.446,-1.158l-2.061,-0.387l-3.088,-1.287l-2.189,-1.286l-0.644,-0.645l-1.545,-0.258l-2.702,-0.772l-1.159,-1.287l-2.961,-1.545l-1.288,-1.673l-0.644,-1.287l0.902,-0.258l-0.258,-0.772l0.644,-0.772l0,-0.902l-0.901,-1.158l-0.257,-1.159l-0.902,-1.287l-2.445,-2.704l-2.703,-2.059l-1.288,-1.674l-2.317,-1.159l-0.515,-0.643l0.386,-1.674l-1.287,-0.643l-1.673,-1.287l-0.644,-1.802l-1.416,-0.258l-1.545,-1.416l-1.287,-1.288l-0.129,-0.901l-1.416,-2.060l-1.029,-2.059l0.128,-1.030l-1.931,-1.030l-0.901,0.129l-1.544,-0.773l-0.515,1.159l0.515,1.288l0.257,1.930l0.901,1.160l1.931,1.801l0.515,0.644l0.386,0.257l0.386,0.902l0.515,0l0.515,1.673l0.773,0.644l0.643,1.030l1.673,1.415l0.902,2.446l0.772,1.159l0.773,1.287l0.128,1.416l1.287,0.129l1.030,1.158l1.029,1.288l-0.128,0.386l-1.029,1.030l-0.516,0l-0.772,-1.673l-1.673,-1.546l-1.931,-1.286l-1.416,-0.644l0.129,-1.931l-0.515,-1.545l-1.288,-0.773l-1.802,-1.287l-0.386,0.386l-0.644,-0.643l-1.673,-0.643l-1.545,-1.675l0.129,-0.128l1.158,0.128l1.030,-1.029l0,-1.159l-2.059,-1.931l-1.545,-0.772l-1.031,-1.674l-0.900,-1.802l-1.287,-2.189l-1.159,-2.317l3.090,-0.256l3.475,-0.259l-0.258,0.515l3.992,1.288l6.178,1.931l5.407,0l2.060,0l0.129,-1.158l4.633,0l0.902,0.900l1.416,0.901l1.545,1.159l0.900,1.416l0.772,1.545l1.288,0.772l2.316,0.772l1.674,-2.058l2.189,-0.130l1.930,1.159l1.288,1.802l1.030,1.545l1.545,1.545l0.515,1.931l0.772,1.287l2.188,0.773l1.931,0.643l-1.030,0.129z', - - MY: - 'M740.39,210.174l0.642,0.258l1.545,1.673l1.160,1.803l0.129,1.803l-0.258,1.287l0.258,0.900l0.129,1.545l1.029,0.772l1.030,2.318l0,0.901l-1.932,0.257l-2.574,-2.059l-3.217,-2.060l-0.260,-1.416l-1.543,-1.802l-0.386,-2.188l-1.032,-1.546l0.387,-1.931l-0.643,-1.158l0.516,-0.385l2.188,1.157l0.129,1.287l1.802,-0.257l-0.901,1.159zM760.601,221.632l2.058,0.901l2.061,-0.514l0.513,-2.318l1.159,-0.515l3.218,-0.515l1.932,-2.189l1.287,-1.673l1.287,1.417l0.516,-0.902l1.285,0l0.260,-1.674l0.127,-1.287l2.060,-1.931l1.287,-2.059l1.159,0l1.287,1.286l0.129,1.159l1.802,0.772l2.319,0.773l-0.258,1.158l-1.803,0.129l0.514,1.288l-2.059,0.901l-2.316,-0.515l-3.090,0l-0.900,3.089l-1.032,0.901l-1.414,3.862l-2.189,0.515l-2.574,-0.772l-1.289,0.257l-1.545,1.417l-1.672,-0.257l-1.803,0.514l-1.801,-1.416l0.515,1.802z', - MZ: - 'M558.368,258.062l1.931,-0.256l3.347,0.771l0.644,-0.386l1.930,0l0.902,-0.900l1.672,0.128l2.961,-1.030l2.060,-1.674l0.516,1.287l-0.129,2.705l0.257,2.316l0.128,4.248l0.516,1.289l-0.772,1.930l-1.160,1.803l-1.673,1.673l-2.446,1.030l-3.090,1.416l-2.961,2.830l-1.029,0.517l-1.930,1.930l-1.160,0.643l-0.128,1.803l1.288,2.060l0.514,1.674l-0.129,1.415l0.644,-0.770l-0.129,2.573l-0.386,1.288l0.643,0.514l-0.387,1.030l-1.157,1.030l-2.187,0.901l-3.349,1.417l-1.159,1.030l0.259,1.158l0.643,0.127l-0.130,1.418l-2.058,0l-0.259,-1.158l-0.385,-1.289l-0.258,-0.901l0.514,-3.090l-0.771,-1.801l-1.287,-3.863l2.832,-3.089l0.773,-1.930l0.386,-0.258l0.257,-1.545l-0.385,-0.773l0.128,-2.061l0.513,-1.801l0,-3.475l-1.415,-0.774l-1.287,-0.254l-0.515,-0.645l-1.287,-0.645l-2.317,0.129l-0.129,-1.029l-0.258,-1.932l8.239,-2.189l1.545,1.289l0.643,-0.259l1.159,0.644l0.130,1.159l-0.645,1.158l0.258,1.931l1.802,1.674l0.772,-1.801l1.160,-0.645l-0.258,-3.476l-1.030,-1.930l-1.030,-0.902l-0.901,0.130l-0.773,-3.605l-0.773,1.932z', - NA: - 'M509.322,304.019l-2.059,-2.059l-1.030,-2.060l-0.644,-2.575l-0.645,-1.930l-0.900,-4.120l0,-3.216l-0.387,-1.545l-1.029,-1.032l-1.416,-2.317l-1.414,-3.218l-0.647,-1.674l-2.187,-2.575l-0.128,-2.058l1.288,-0.516l1.674,-0.515l1.672,0.128l1.674,1.159l0.384,-0.128l10.944,-0.128l1.801,1.287l6.566,0.385l4.892,-1.158l2.187,-0.644l1.803,0.258l1.030,0.513l0,0.259l-1.416,0.645l-0.901,0l-1.674,1.028l-1.029,-1.158l-4.119,1.030l-2.060,0l-0.129,9.654l-2.574,0.130l0,7.852l0,9.912l-2.446,1.416l-1.418,0.129l-1.673,-0.514l-1.288,-0.129l-0.383,-1.158l-1.033,-0.773l1.286,-1.415z', - NC: - 'M911.856,283.809l2.188,1.673l1.416,1.159l-1.029,0.643l-1.545,-0.643l-1.932,-1.287l-1.672,-1.416l-1.803,-1.932l-0.386,-0.901l1.158,0.129l1.545,0.901l1.158,0.902l-0.902,-0.772z', - NE: - 'M471.091,194.855l0,-1.930l-3.091,-0.515l-0.128,-1.417l-1.545,-1.673l-0.258,-1.287l0.129,-1.287l1.802,-0.129l0.900,-0.901l3.735,-0.258l2.445,-0.386l0.130,-1.674l1.543,-1.802l0,-6.179l3.734,-1.288l7.852,-5.276l9.269,-5.150l4.248,1.159l1.416,1.544l1.932,-1.030l0.643,4.249l1.029,0.643l0.129,0.901l1.030,0.901l-0.514,1.159l-1.030,5.406l-0.130,3.605l-3.475,2.446l-1.158,3.605l1.158,1.029l0,1.673l1.674,0.130l-0.258,1.158l-0.774,0.257l-0.128,0.773l-0.514,0.128l-1.803,-2.960l-0.644,-0.129l-2.058,1.545l-2.061,-0.772l-1.545,-0.258l-0.772,0.386l-1.545,0l-1.544,1.159l-1.416,0l-3.219,-1.417l-1.286,0.644l-1.416,0l-1.030,-1.030l-2.704,-1.029l-2.832,0.385l-0.772,0.516l-0.259,1.544l-0.770,1.159l-0.258,2.447l-2.059,-1.674l-0.901,0.127l0.901,-0.773z', - NG: - 'M488.082,214.166l-2.704,0.900l-1.029,-0.128l-1.031,0.644l-2.188,-0.129l-1.415,-1.674l-0.902,-1.931l-1.931,-1.802l-2.059,0.128l-2.318,0l0.130,-4.376l0,-1.802l0.386,-1.674l0.901,-0.773l1.288,-1.672l-0.258,-0.773l0.514,-1.031l-0.643,-1.673l0.129,-0.771l0.258,-2.447l0.770,-1.159l0.259,-1.544l0.772,-0.516l2.832,-0.385l2.704,1.029l1.030,1.030l1.416,0l1.286,-0.644l3.219,1.417l1.416,0l1.544,-1.159l1.545,0l0.772,-0.386l1.545,0.258l2.061,0.772l2.058,-1.545l0.644,0.129l1.803,2.960l0.514,-0.128l1.160,1.158l-0.387,0.386l-0.129,0.901l-2.188,2.189l-0.773,1.673l-0.387,1.417l-0.513,0.514l-0.645,1.931l-1.414,1.159l-0.387,1.288l-0.644,1.159l-0.257,1.029l-1.803,0.901l-1.546,-1.030l-1.029,0l-1.544,1.545l-0.771,0.128l-1.289,2.575l0.772,-1.932z', - NI: - 'M234.359,197.045l-0.902,-0.774l-1.287,-1.158l-0.643,-0.901l-1.159,-0.773l-1.288,-1.287l0.258,-0.386l0.514,0.386l0.129,-0.129l0.902,-0.128l0.257,-0.644l0.387,0l0,-1.288l0.643,-0.129l0.515,0l0.643,-0.643l0.773,0.515l0.258,-0.386l0.515,-0.258l0.900,-0.771l0.129,-0.516l0.257,0l0.258,-0.643l0.258,-0.130l0.514,0.517l0.516,0.127l0.643,-0.385l0.643,0l0.902,-0.386l0.386,-0.386l0.901,0.128l-0.129,0.258l-0.129,0.514l0.258,1.030l-0.643,0.901l-0.258,1.159l-0.129,1.158l0.129,0.644l0.128,1.287l-0.514,0.258l-0.129,1.159l0.129,0.644l-0.516,0.771l0.130,0.645l0.386,0.514l-0.644,0.514l-0.772,-0.128l-0.515,-0.644l-0.773,-0.128l-0.644,0.257l-1.801,-0.644l0.386,-0.259z', - NL: - 'M481.646,82.859h2.188l0.515,0.902l-0.644,2.574l-0.773,0.901h-1.544l0.387,2.833l-1.416-0.644l-1.674-1.158l-2.573,0.643l-1.933-0.258l1.417-0.772l2.317-3.991L481.646,82.859z', - NO: - 'M494.905,68.442l-1.802,-1.674l-5.279,3.090l-3.603,0.643l-3.734,-1.415l-0.902,-2.833l-0.900,-6.179l2.445,-1.802l7.080,-2.189l5.407,-2.832l4.892,-3.733l6.435,-5.278l4.508,-1.931l7.465,-3.476l5.922,-1.158l4.377,0.129l4.119,-2.188l4.893,0.128l4.889,-0.515l8.368,1.931l-3.474,0.773l2.961,1.672l-4.507,1.031l-2.189,0.257l1.159,-1.803l-3.476,-1.157l-4.247,0.900l-1.289,2.060l-2.572,1.159l-2.832,-0.644l-3.606,0.129l-2.961,-1.416l-1.545,0.643l-1.673,0.129l-0.513,1.803l-5.022,-0.387l-0.644,1.417l-2.702,0l-1.674,1.931l-2.703,2.960l-4.248,3.862l1.031,0.901l-0.903,1.030l-2.705,0l-1.800,2.446l0.127,3.605l1.803,1.415l-0.900,3.090l-2.318,1.931l1.158,-1.545z', - NP: - 'M702.673,151.859l-0.258,1.159l0.385,1.674l-0.385,1.158l-2.189,0l-3.219,-0.644l-2.059,-0.257l-1.674,-1.287l-3.603,-0.386l-3.604,-1.545l-2.447,-1.287l-2.703,-1.031l1.160,-2.573l1.672,-1.160l1.158,-0.643l2.061,0.772l2.703,1.802l1.545,0.386l0.900,1.288l2.188,0.515l2.189,1.158l2.959,0.644l-3.221,-0.257z', - NZ: - 'M941.72,334.914l-1.030,1.417l-1.287,1.931l-2.058,1.030l-0.514,-0.772l-1.160,-0.386l1.545,-2.189l-0.774,-1.416l-2.961,-1.159l0.131,-0.901l1.930,-1.030l0.387,-2.059l-0.129,-1.674l-1.029,-1.803l0,-0.514l-1.290,-1.158l-2.058,-2.317l-1.158,-1.932l1.027,-0.256l1.418,1.544l2.187,0.773l0.774,2.315l1.930,2.834l0,-1.803l1.289,0.773l0.384,1.931l2.190,0.901l1.803,0.257l1.545,-1.030l1.285,0.258l-0.645,2.446l-0.771,1.544l-2.059,0l-0.771,0.773l0.255,1.158l0.386,-0.514zM922.282,344.312l2.319,-1.416l1.671,-1.416l1.161,-1.931l1.029,-0.772l0.387,-1.416l1.929,-1.287l0.514,1.158l0.645,1.030l1.933,-1.030l0.770,1.160l0,1.157l-1.028,1.160l-1.802,2.059l-1.289,1.029l1.029,1.288l-2.188,0l-2.316,1.030l-0.645,1.803l-1.545,2.703l-2.060,1.286l-1.414,0.773l-2.445,-0.128l-1.805,-0.901l-2.830,-0.130l-0.516,-1.030l1.416,-1.930l3.477,-2.704l1.672,-0.515l-1.931,1.030z', - OM: - 'M617.197,159.841l1.157,1.802l1.545,1.030l1.932,0.387l1.674,0.385l1.158,1.545l0.772,0.902l0.902,0.385l0,0.644l-1.031,1.545l-0.387,0.772l-1.158,0.902l-1.029,1.802l-1.157,-0.130l-0.517,0.645l-0.514,1.416l0.385,1.673l-0.257,0.387l-1.286,0l-1.675,1.028l-0.257,1.289l-0.642,0.514l-1.675,0l-1.031,0.773l0,1.029l-1.287,0.773l-1.416,-0.257l-1.802,0.900l-1.286,0.129l-0.900,-1.930l-2.061,-4.378l7.981,-2.702l1.802,-5.408l-1.159,-1.931l0,-1.030l0.773,-1.159l0.129,-1.029l1.159,-0.515l-0.517,-0.386l0.258,-1.802l-1.417,0zM616.294,156.752l0.773,-0.902l0.387,0.257l-0.257,1.159l-0.385,0.386l0.518,0.900z', - PA: - 'M255.47,207.471l-0.902,-0.772l-0.643,-1.416l0.643,-0.773l-0.643,-0.127l-0.514,-0.903l-1.288,-0.771l-1.159,0.257l-0.644,0.900l-1.029,0.644l-0.644,0.129l-0.257,0.515l1.287,1.545l-0.643,0.258l-0.387,0.385l-1.287,0.129l-0.515,-1.544l-0.387,0.386l-0.772,-0.129l-0.643,-1.030l-1.030,-0.258l-0.773,-0.257l-1.158,0l0,0.644l-0.387,-0.515l0.130,-0.515l0.257,-0.515l-0.128,-0.515l0.385,-0.257l-0.514,-0.387l0,-1.157l1.029,-0.260l1.030,1.031l-0.129,0.516l1.159,0.129l0.129,-0.129l0.772,0.643l1.416,-0.258l1.031,-0.643l1.673,-0.515l0.900,-0.901l1.545,0.257l-0.129,0.257l1.545,0l1.159,0.516l0.900,0.773l1.031,0.771l-0.386,0.387l0.643,1.544l-0.515,0.901l-0.900,-0.257l0.258,-1.287z', - PE: - 'M277.74,274.281l-0.644,1.417l-1.415,0.644l-2.704-1.543l-0.258-1.031l-5.278-2.705l-4.891-2.959l-2.059-1.674l-1.159-2.188l0.515-0.773l-2.318-3.605l-2.703-4.891l-2.446-5.406l-1.158-1.289l-0.902-1.93l-2.058-1.802l-1.932-1.028l0.901-1.16l-1.287-2.576l0.772-1.93l2.189-1.672l0.386,1.029l-0.773,0.643v1.029l1.159-0.257l1.03,0.388l1.159,1.286l1.545-1.03l0.514-1.802l1.673-2.446l3.219-1.029l2.961-2.832l0.772-1.674l-0.386-2.06l0.772-0.258l1.802,1.288l0.772,1.287l1.288,0.644l1.544,2.832l2.06,0.257l1.416-0.644l1.03,0.517l1.673-0.26l2.06,1.287l-1.802,2.704h0.772l1.416,1.417l-2.446-0.129l-0.386,0.514l-2.188,0.516l-3.089,1.802l-0.129,1.288l-0.772,0.9l0.257,1.416l-1.545,0.773v1.158l-0.772,0.516l1.158,2.445l1.546,1.674l-0.644,1.158l1.801,0.129l1.03,1.416h2.317l2.317-1.545l-0.256,4.119l1.287,0.256l1.416-0.383l2.445,4.248l-0.644,0.9l-0.128,1.932v2.316l-1.159,1.287l0.515,1.029l-0.643,0.9l1.158,2.318L277.74,274.281z', - PG: - 'M845.175,242.742l-0.129-8.752l4.635,1.803l5.02,1.543l1.932,1.417l1.416,1.417l0.385,1.544l4.505,1.673l0.646,1.416l-2.445,0.258l0.643,1.803l2.316,1.802l1.803,2.832l1.545-0.128l-0.129,1.287l2.059,0.387l-0.771,0.514l2.831,1.158l-0.258,0.773l-1.803,0.129l-0.641-0.645l-2.32-0.258l-2.701-0.385l-2.061-1.803l-1.545-1.416l-1.414-2.446l-3.479-1.159l-2.314,0.771l-1.673,0.902l0.386,2.06l-2.189,0.901l-1.416-0.515l-2.832-0.129V242.742zM876.454,236.822l1.031,0.9l0.258,1.418l-0.771,0.641l-0.518-1.545l-0.643-1.027l-1.288-0.902l-1.545-1.158l-1.931-0.773l0.773-0.643l1.416,0.772l1.029,0.515l1.031,0.643L876.454,236.822zM872.851,242.742l-1.545,0.645l-1.286,0.645h-1.546l-2.188-0.772l-1.545-0.772l0.256-0.901l2.447,0.388l1.416-0.131l0.387-1.287l0.385-0.127l0.261,1.414l1.543-0.128l0.772-0.901l1.543-1.031l-0.256-1.545l1.543-0.127l0.517,0.515v1.416l-0.901,1.674l-1.416,0.259L872.851,242.742zM882.118,241.328l0.775,0.645l1.414,1.674l1.158,0.899l-0.258,0.771l-0.771,0.26l-1.159-1.03l-1.287-1.673l-0.516-2.061l0.387-0.258L882.118,241.328z', - PH: - 'M790.722,192.797l-1.416,-2.061l2.318,0l1.031,1.030l-0.775,2.316l1.158,1.285zM795.485,200.134l0.645,-0.773l0.256,-1.673l1.545,-0.129l-0.385,1.802l1.930,-2.703l-0.258,2.574l-0.903,0.902l-0.900,1.802l-0.900,0.773l-1.545,-1.932l-0.515,0.643zM805.655,204.253l0.258,1.802l0.256,1.545l-1.029,2.446l-0.901,-2.704l-1.289,1.287l0.903,2.060l-0.774,1.288l-3.217,-1.545l-0.771,-2.059l0.898,-1.287l-1.801,-1.159l-0.773,1.030l-1.285,-0.129l-2.061,1.545l-0.386,-0.773l1.031,-2.317l1.672,-0.773l1.545,-0.901l0.902,1.159l2.061,-0.772l0.384,-1.158l1.930,-0.129l-0.129,-2.061l2.192,1.288l0.255,1.416l-0.129,-0.901zM784.415,201.936l-3.477,2.447l1.288,-1.804l1.929,-1.673l1.676,-1.931l1.285,-2.575l0.518,2.190l-1.803,1.415l1.416,-1.931zM794.841,177.863l-0.514,1.159l0.901,1.931l-0.643,2.188l-1.545,0.901l-0.516,2.188l0.645,2.189l1.416,0.257l1.158,-0.257l3.348,1.415l-0.258,1.417l0.900,0.772l-0.257,1.159l-2.061,-1.287l-1.029,-1.416l-0.643,1.031l-1.803,-1.676l-2.445,0.387l-1.287,-0.515l0.127,-1.157l0.775,-0.645l-0.775,-0.643l-0.256,0.901l-1.416,-1.545l-0.387,-1.159l-0.127,-2.575l1.157,0.902l0.257,-4.248l0.901,-2.447l1.545,0l1.674,0.773l0.902,-0.643l-0.256,-0.643zM793.94,196.271l-0.386,-1.286l1.674,0.771l1.673,0l0,1.160l-1.287,1.157l-1.674,0.773l-0.128,-1.287l-0.128,1.288zM803.337,194.212l0.773,2.961l-2.060,-0.644l0,0.901l0.644,1.674l-1.287,0.514l-0.129,-1.802l-0.773,-0.128l-0.385,-1.674l1.545,0.257l0,-1.029l-1.676,-2.060l2.576,0l-0.772,-1.030z', - PK: - 'M667.659,126.886l2.059,1.287l0.773,2.059l4.375,1.159l-2.572,2.189l-2.961,0.386l-4.121,-0.643l-1.287,1.157l0.900,2.447l1.031,1.802l2.059,1.287l-2.187,1.545l0,1.931l-2.575,2.704l-1.802,2.702l-2.704,2.833l-3.218,-0.129l-2.961,2.832l1.802,1.159l0.258,2.059l1.545,1.417l0.514,2.317l-5.922,0l-1.801,1.802l-1.931,-0.772l-0.774,-1.932l-2.187,-2.059l-4.891,0.514l-4.377,0.130l-3.863,0.386l1.030,-3.218l3.992,-1.288l-0.259,-1.287l-1.288,-0.386l-0.128,-2.446l-2.574,-1.159l-1.031,-1.674l-1.414,-1.415l4.504,1.415l2.704,-0.386l1.674,0.386l0.514,-0.643l1.931,0.257l3.476,-1.159l0.129,-2.317l1.417,-1.544l2.058,0l0.257,-0.644l2.061,-0.386l1.029,0.257l1.031,-0.772l-0.129,-1.674l1.158,-1.545l1.673,-0.772l-1.030,-1.673l2.575,0l0.773,-0.902l-0.129,-1.029l1.287,-1.159l-0.257,-1.416l-0.645,-1.029l1.545,-1.287l2.833,-0.517l3.090,-0.256l1.416,-0.516l-1.545,0.385z', - PL: - 'M505.718,89.295l-1.158,-1.672l0.257,-1.030l-0.644,-1.417l-1.029,-0.901l0.770,-0.773l-0.642,-1.287l1.802,-0.901l4.248,-1.158l3.347,-0.901l2.703,0.387l0.258,0.643l2.574,0.129l3.348,0.256l4.890,0l1.417,0.259l0.644,0.772l0.129,1.288l0.771,1.029l0,1.029l-1.672,0.516l0.772,1.287l0.129,1.159l1.286,2.317l-0.257,0.773l-1.287,0.385l-2.447,2.189l0.646,1.287l-0.515,-0.257l-2.577,-1.030l-1.929,0.386l-1.289,-0.257l-1.672,0.644l-1.289,-1.030l-1.158,0.386l-0.127,-0.129l-1.289,-1.416l-1.930,-0.129l-0.257,-0.772l-1.802,-0.386l-0.517,0.773l-1.414,-0.644l0.129,-0.645l-1.932,-0.256l1.287,0.903z', - PR: - 'M286.622,177.09l1.416,0.258l0.516,0.515l-0.644,0.643h-2.06l-1.545,0.129l-0.258-1.158l0.387-0.387H286.622z', - PS: - 'M560.942,139.759l0,1.544l-0.386,0.902l-1.287,0.257l0.128,-0.644l0.774,-0.385l-0.774,-0.258l0.645,-1.803l-0.900,-0.387z', - PT: - 'M440.838,114.141l1.031,-0.643l1.158,-0.387l0.643,1.287l1.545,0l0.514,-0.385l1.545,0.128l0.773,1.416l-1.287,0.643l0,2.189l-0.514,0.387l0,1.159l-1.160,0.256l1.030,1.674l-0.772,1.674l0.902,0.900l-0.258,0.644l-1.030,1.030l0.257,0.902l-1.158,0.772l-1.416,-0.387l-1.416,0.258l0.386,-2.059l-0.129,-1.674l-1.288,-0.258l-0.643,-1.030l0.259,-1.802l1.028,-0.900l0.259,-1.159l0.514,-1.545l0,-1.159l-0.644,-1.030l0.129,0.901z', - PY: - 'M296.405,286.898l1.03-3.219v-1.414l1.416-2.447l4.634-0.772l2.446,0.13l2.575,1.285v0.902l0.772,1.414l-0.128,3.736l2.831,0.514l1.159-0.514l1.802,0.643l0.516,0.902l0.256,2.443l0.259,1.031l1.028,0.129l1.031-0.516l0.9,0.516v1.544l-0.386,1.545l-0.515,1.546l-0.386,2.445l-2.446,2.059l-2.189,0.387l-2.961-0.387l-2.702-0.771l2.574-4.121l-0.386-1.157l-2.703-1.03l-3.348-2.059l-2.188-0.387L296.405,286.898z', - QA: - 'M602.136,160.227l-0.257,-1.931l0.770,-1.416l0.772,-0.257l0.775,0.901l0,1.545l-0.517,1.544l-0.772,0.258l0.771,0.644z', - RO: - 'M526.442,97.921l1.159,-0.515l1.674,0.258l1.673,0l1.289,0.772l0.899,-0.515l1.931,-0.257l0.773,-0.644l1.158,0l0.774,0.258l0.770,0.901l0.902,1.029l1.545,1.674l0.128,1.158l-0.257,1.159l0.387,1.287l1.287,0.386l1.287,-0.386l1.158,0.515l0,0.645l-1.287,0.643l-0.772,-0.258l-0.773,3.219l-1.544,-0.258l-1.930,-1.030l-3.219,0.644l-1.287,0.644l-3.990,-0.130l-2.059,-0.386l-1.031,0.129l-0.773,-1.030l-0.513,-0.515l0.641,-0.386l-0.771,-0.386l-0.774,0.644l-1.543,-0.773l-0.257,-1.158l-1.674,-0.643l-0.258,-0.902l-1.416,-1.030l2.059,-0.515l1.673,-1.802l1.290,-1.803l-1.671,0.643z', - RS: - 'M519.749,102.684l1.416,1.030l0.258,0.902l1.674,0.643l0.257,1.158l1.543,0.773l0.774,-0.644l0.771,0.386l-0.641,0.386l0.513,0.515l-0.772,0.644l0.259,0.902l1.415,1.158l-1.030,0.901l-0.515,0.772l0.256,0.386l-0.385,0.387l-1.288,0l-1.544,0.257l-1.545,0.515l-0.129,0.515l-0.385,0l-0.130,-1.030l-0.643,-0.257l-0.516,-0.773l-0.771,0.258l-0.129,-0.516l-1.287,1.288l0.258,0.901l-0.516,-0.128l-0.773,-0.902l-1.159,-0.515l0.258,-0.514l0.387,-1.545l0.900,-0.515l1.158,-0.387l0.389,-1.287l-1.289,-1.030l0.645,-1.159l-1.030,0l1.030,-1.029l-0.773,-0.772l-0.643,-1.031l2.060,-0.772l-1.672,-0.129z', - RU: - 'M950.089,36.129l-0.258,0l-0.516,-1.801l0.774,-0.772l0.127,-0.129l6.308,1.158l6.435,-1.544zM586.045,9.869l5.276,-0.515l4.121,0l0.514,0.773l1.545,-0.644l2.574,-0.515l3.990,0.644l-1.028,0.386l-3.605,0.385l-2.447,0.130l-0.384,0.514l-3.221,0.386l-2.830,-0.643l1.545,-0.772l6.050,0.129zM950.089,51.964l-3.992,1.802l2.574,3.219l-0.641,2.188l-5.539,-0.773l-7.336,1.674l-6.177,2.703l-4.764,2.703l-3.990,-1.673l-7.725,1.803l-6.693,0.128l-4.377,4.506l3.088,0.772l0,4.634l-3.475,1.545l0.645,1.803l-4.506,1.544l-1.159,3.219l-4.250,1.158l-0.513,1.931l-4.119,3.089l-1.674,-6.178l-1.545,-5.922l1.545,-4.249l2.060,-1.157l0.127,-1.287l3.864,-0.773l5.148,-3.219l4.506,-2.832l5.019,-2.060l2.061,-3.732l-3.219,0.128l-1.672,2.317l-6.695,2.446l-2.187,-3.089l-7.081,0.901l-6.693,4.247l1.803,1.288l-6.309,1.416l-10.041,-1.416l-11.715,0l-6.564,1.159l-8.369,5.278l-9.781,5.665l3.861,0.643l0.771,2.317l3.092,0.385l1.672,-1.545l2.961,0.387l3.475,2.060l0.515,3.089l-1.543,2.189l-0.902,2.575l-1.031,5.535l-4.120,3.862l-0.900,1.802l-3.603,3.219l-3.735,3.089l-1.674,1.545l-3.603,1.674l-1.674,0l-1.674,-1.288l-3.601,1.931l-0.518,0.901l-0.385,-0.514l0,-1.288l1.416,-0.129l0.385,-3.219l-0.771,-2.317l2.318,-0.901l3.346,0.516l1.802,-2.704l0.901,-2.832l1.031,-1.029l1.414,-2.448l-4.375,0.774l-2.447,1.030l-3.990,0l-1.159,-2.447l-3.218,-1.930l-4.635,-0.902l-1.029,-2.574l-0.901,-1.674l-1.031,-1.158l-1.674,-2.703l-2.316,-1.030l-4.119,-0.772l-3.475,0l-3.350,0.513l-2.316,1.417l1.545,0.644l0,1.416l-1.414,0.901l-2.447,2.832l0,1.159l-3.862,1.672l-3.219,-0.900l-3.218,0.128l-1.414,-0.900l-1.545,-0.258l-3.991,1.930l-3.476,0.387l-2.574,0.644l-3.348,-0.386l-2.445,0l-1.674,-1.417l-2.701,-1.287l-2.705,-0.257l-3.348,0.257l-2.574,0.515l-3.862,-1.158l-0.515,-1.931l-3.090,-0.773l-2.445,-0.257l-3.090,-1.158l-2.703,2.832l1.029,1.545l-2.574,1.931l-3.862,-0.644l-2.703,-0.129l-1.802,-1.287l-2.832,0l-2.317,-0.901l-4.121,1.287l-5.148,2.317l-2.832,0.515l-1.030,0.258l-1.414,-1.675l-3.478,0.387l-1.158,-1.159l-1.930,-0.515l-1.289,-1.545l-1.545,-0.514l-3.860,0.644l-3.605,-1.546l-1.545,1.416l-5.922,-6.822l-3.474,-2.060l1.029,-0.901l-6.822,2.575l-2.577,0.128l0.258,-1.416l-3.474,-0.901l-2.832,0.645l-0.901,-2.833l-4.763,-0.644l-2.446,1.160l-6.693,1.028l-1.418,0.644l-10.039,0.901l-1.158,0.901l1.929,1.931l-2.575,0.644l0.515,0.772l-2.702,1.288l4.376,1.931l-0.644,1.159l-3.732,0l-0.773,0.772l-3.474,-1.416l-4.250,0l-2.961,1.158l-3.088,-1.029l-5.922,-1.931l-4.248,0.127l-5.535,2.962l-0.387,1.931l-2.705,-1.544l-2.186,2.960l0.772,0.515l-1.545,2.060l2.317,1.802l1.931,-0.129l1.803,1.803l-0.387,1.416l1.416,0.514l-1.287,1.546l-2.575,0.386l-2.704,2.831l2.445,2.576l-0.255,1.801l2.960,3.218l-1.545,1.030l-0.516,0.644l-1.158,-0.129l-1.931,-1.673l-0.643,0l-1.803,-0.644l-0.772,-1.158l-2.446,-0.516l-1.671,0.387l-0.517,-0.515l-3.604,-1.287l-3.990,-0.386l-2.318,-0.516l-0.256,0.387l-3.477,-2.318l-3.089,-1.029l-2.318,-1.545l1.931,-0.514l2.317,-2.189l-1.544,-1.030l3.991,-1.159l-0.129,-0.643l-2.446,0.515l0.128,-1.159l1.417,-0.772l2.575,-0.258l0.384,-0.901l-0.513,-1.417l1.029,-1.415l0,-0.772l-3.990,-0.902l-1.545,0l-1.674,-1.287l-2.059,0.386l-3.476,-0.901l0.129,-0.514l-1.030,-1.159l-2.058,-0.129l-0.258,-0.901l0.643,-0.515l-1.673,-1.544l-2.833,0.256l-0.772,-0.128l-0.773,0.644l-0.901,-0.129l-0.643,-1.674l-0.644,-0.901l0.516,-0.257l2.187,0.128l1.030,-0.643l-0.772,-0.772l-1.803,-0.387l0.129,-0.515l-1.160,-0.515l-1.672,-1.802l0.645,-0.644l-0.258,-1.287l-2.703,-0.643l-1.416,0.385l-0.387,-0.772l-2.833,-0.644l-0.901,-1.545l-0.129,-1.287l-1.286,-0.644l1.158,-0.901l-0.900,-2.446l1.930,-1.544l-0.386,-0.515l3.089,-1.545l-2.832,-1.287l5.792,-3.347l2.448,-1.545l1.030,-1.416l-3.991,-1.802l1.157,-1.802l-2.445,-2.060l1.801,-2.318l-3.089,-2.960l2.448,-2.060l-4.119,-1.801l0.384,-1.932l2.189,-0.257l4.507,-1.031l2.830,-0.900l4.378,1.545l7.466,0.643l10.169,3.089l2.059,1.288l0.129,1.802l-7.336,2.061l-12.102,-2.061l-1.929,0.386l4.504,3.219l0.772,2.060l2.961,1.544l3.218,-2.703l7.596,1.287l0,-2.960l7.465,-1.803l3.992,-0.901l-2.190,-1.674l-0.643,-3.218l7.466,0.772l-1.801,3.348l4.632,-0.129l7.210,-2.703l9.783,-2.318l2.060,1.417l9.397,-1.546l6.695,0.902l0.643,-3.219l7.853,0.772l10.684,2.832l1.673,-1.801l-3.991,-4.507l4.505,-2.702l2.190,-3.090l8.369,0.386l0.769,4.763l0.260,5.536l1.672,1.674l-0.516,1.802l-4.119,2.832l2.832,0.386l5.151,-2.961l1.029,-3.991l-2.832,-1.159l-1.029,-5.664l3.345,-3.346l2.190,1.802l0.644,2.060l1.672,-1.288l3.477,-0.901l5.535,-0.128l5.019,1.544l-2.445,-2.575l-0.256,-2.574l4.760,-0.514l6.437,0.128l5.793,-0.387l-2.189,-1.415l3.219,-1.674l3.090,-0.129l5.150,-1.288l0.385,-0.128l1.029,0l1.418,0l1.545,-0.128l1.416,-0.129l1.027,0l0.389,0l0.900,-0.773l7.080,-0.257l2.190,0.643l6.049,-1.415l4.890,0l0.774,-1.159l2.574,-1.159l6.309,-1.030l4.632,0.772l-3.603,0.644l6.051,0.515l0.771,1.288l2.447,-0.644l9.782,-0.257l5.023,1.673l-2.318,3.089l-7.082,1.546l1.031,1.544l6.180,-0.257l2.961,1.030l11.968,0l2.705,1.544l10.299,0.129l0.387,-1.673l16.603,1.673l0.518,4.892l4.246,1.030l8.111,-1.545l15.834,-0.515l1.930,-3.476l23.170,1.802l2.320,1.545l7.078,2.059l14.416,-0.385l6.438,3.733l10.170,-0.128l9.269,-0.259l6.178,2.447l0.774,-3.219l13.257,0.515l8.496,1.159l3.735,1.158l6.564,2.059l7.209,2.448l8.110,1.029l5.277,2.575l-6.178,1.416l-0.386,2.703l-4.506,0.129l-5.278,-2.317l-5.150,-0.644l-3.475,-1.674l-1.802,2.961l0.385,-0.129zM518.204,80.414l0.645,-1.288l3.733,-0.772l2.702,0.386l1.289,0.515l-0.259,0.643l0.128,0.772l-4.890,0l3.348,0.256zM861.522,24.158l5.666,0.515l-0.128,1.416l-7.725,-1.416l-2.187,0.515zM836.034,22.871l5.279,-0.387l10.426,0.772l1.803,2.189l-9.527,-0.128l-3.989,1.030l-5.021,-1.931l-1.029,1.545zM742.835,13.473l0.516,0.772l5.019,2.575l-14.287,0.387l3.604,-3.090l-5.148,0.644zM718.763,9.226l10.556,0.386l5.922,3.346l-7.853,1.674l-11.328,-1.030l-0.127,-2.446l-2.830,1.930zM609.345,28.277l6.435,-2.317l-0.643,-1.287l6.050,-1.417l8.882,-1.673l8.882,-0.514l4.634,-1.030l5.279,-0.387l1.801,1.159l-1.801,0.772l-9.526,1.417l-8.239,1.287l-8.367,2.445l-3.993,2.704l-4.246,2.574l0.644,2.189l5.149,2.317l-1.672,0.129l-8.756,-0.257l-0.771,-1.287l-4.891,-0.644l-0.386,-1.545l2.830,-0.515l-0.127,-1.417l5.277,-2.316l2.445,0.387zM850.194,82.344l0.901,2.575l0,2.575l1.158,2.832l2.705,4.763l-3.990,-0.901l-1.674,3.862l2.574,2.703l0,1.931l-2.058,-1.674l-1.803,2.189l-0.516,-2.317l0.258,-2.575l-0.258,-2.960l0.645,-2.061l0.127,-3.604l-1.545,-2.575l0.131,-3.733l2.574,-1.287l-1.160,-1.158l1.289,-0.387l-0.642,-1.802z', - RW: - 'M547.169,229.999l1.028,1.545l-0.128,1.544l-0.773,0.387l-1.415,-0.128l-0.773,1.545l-1.673,-0.258l0.257,-1.546l0.386,-0.128l0,-1.674l0.901,-0.643l0.643,0.256l-1.547,0.900z', - SA: - 'M580.509,182.883l-0.387,-1.157l-0.771,-0.773l-0.259,-1.031l-1.415,-1.029l-1.416,-2.188l-0.772,-2.189l-1.802,-1.931l-1.289,-0.386l-1.672,-2.574l-0.386,-1.932l0.128,-1.544l-1.545,-2.961l-1.287,-1.030l-1.416,-0.644l-0.902,-1.545l0.130,-0.514l-0.771,-1.417l-0.774,-0.643l-1.030,-2.060l-1.674,-2.059l-1.287,-1.931l-1.417,0l0.387,-1.417l0.131,-0.901l0.384,-1.158l2.960,0.514l1.160,-0.900l0.643,-0.901l2.060,-0.386l0.515,-0.902l0.901,-0.515l-2.703,-2.703l5.407,-1.287l0.514,-0.516l3.218,0.773l3.990,1.931l7.596,5.406l5.020,0.258l2.447,0.257l0.644,1.288l1.930,-0.129l1.030,2.317l1.288,0.644l0.513,0.902l1.803,1.158l0.127,1.159l-0.256,0.900l0.387,0.901l0.772,0.644l0.386,0.901l0.387,0.644l0.771,0.644l0.772,-0.258l0.517,1.030l0.127,0.643l1.031,2.704l8.110,1.416l0.514,-0.644l1.159,1.931l-1.802,5.408l-7.981,2.702l-7.853,1.030l-2.445,1.159l-1.931,2.832l-1.287,0.515l-0.644,-0.901l-1.031,0.128l-2.574,-0.257l-0.514,-0.257l-3.090,0l-0.773,0.257l-1.158,-0.644l-0.645,1.288l0.258,1.158l1.158,-0.772z', - SB: - 'M901.944,255.23l0.772,0.903l-1.930,0l-1.031,-1.674l1.674,0.643l-0.515,-0.128zM900.786,252.784l-0.387,0.516l-2.060,-2.318l-0.514,-1.544l0.901,0l1.029,2.059l-1.031,-1.287zM898.597,253.557l-1.159,0.129l-1.544,-0.386l-0.643,-0.386l0.256,-1.031l1.674,0.387l0.900,0.644l-0.516,-0.643zM895.251,248.537l0.643,0.901l0.128,0.515l-2.060,-1.158l-1.543,-0.902l-1.031,-0.901l0.384,-0.258l1.290,0.644l-2.189,-1.159zM888.556,245.834l1.031,0.900l-0.516,0.129l-1.160,-0.515l-1.158,-1.159l0.129,-0.386l-1.674,-1.031z', - SD: - 'M556.308,215.711l-1.416,1.028l-1.545,0l-2.189,0.644l-1.802,-0.644l-1.029,0.774l-2.446,-1.802l-0.644,-1.159l-1.416,0.643l-1.287,-0.257l-0.773,0.515l-1.158,-0.387l-1.672,-2.188l-0.389,-0.901l-2.059,-1.031l-0.643,-1.544l-1.159,-1.287l-1.800,-1.287l0,-0.901l-1.419,-1.159l-2.059,-1.159l-1.029,-0.771l-0.129,-0.773l0.387,-1.159l0,-1.159l-1.545,-1.674l-0.258,-1.157l0,-0.645l-0.902,-0.772l-0.126,-1.544l-0.517,-1.030l-0.902,0.129l0.259,-1.031l0.643,-1.030l-0.258,-1.159l0.901,-0.772l-0.643,-0.643l0.774,-1.673l1.158,-2.060l2.316,0.257l0.258,-10.427l0,-1.415l2.832,0l0,-5.408l10.813,0l10.556,0l10.299,0l1.158,2.704l-0.643,0.386l0.385,2.832l1.030,3.218l1.029,0.644l1.418,1.029l-1.288,1.546l-2.061,0.386l-0.770,0.901l-0.258,1.802l-1.160,3.861l0.258,1.160l-0.387,2.317l-1.158,2.575l-1.672,1.415l-1.158,2.060l-0.259,1.030l-1.286,0.772l-0.774,2.831l0,2.447l-0.386,0.774l-1.416,0.127l-0.901,1.545l1.673,0.129l1.416,1.287l0.388,1.158l1.286,0.644l1.158,2.832l-2.188,2.447l1.416,-1.159z', - SE: - 'M525.026,49.905l-2.703,1.930l0.516,1.674l-4.377,2.060l-5.150,2.317l-1.930,3.861l1.930,1.932l2.576,1.415l-2.576,3.090l-2.704,0.643l-1.028,4.507l-1.545,2.445l-3.348,-0.257l-1.415,2.188l-3.218,0.129l-0.773,-2.575l-2.317,-3.090l-2.059,-3.732l1.158,-1.545l2.318,-1.931l0.900,-3.090l-1.803,-1.415l-0.127,-3.605l1.800,-2.446l2.705,0l0.903,-1.030l-1.031,-0.901l4.248,-3.862l2.703,-2.960l1.674,-1.931l2.702,0l0.644,-1.417l5.022,0.387l0.513,-1.803l1.673,-0.129l3.475,1.417l4.250,1.801l0.128,4.120l0.901,1.031l4.635,-0.772z', - SJ: - 'M539.059,11.285l-3.991,1.416l-7.852,0.387l-7.854-0.515l-0.514-0.644l-3.863-0.128l-2.961-1.159l8.369-0.772l3.86,0.644l2.704-0.772l6.822,0.643L539.059,11.285zM505.976,12.314h-3.733l-1.546-0.901l-7.335,1.031l2.059,2.06l5.276,2.317l3.99,0.772l-2.314,0.9l5.791,1.675l3.219-0.129l1.287-2.189l2.316-0.515l1.545-2.06l6.693-1.031l-8.881-1.931l-3.347-1.03l-3.991,0.128L505.976,12.314zM531.851,17.207l-3.863-0.515l-1.158-1.03l-5.535,0.515l1.674,0.901l-1.932,0.643l4.765,0.645L531.851,17.207z', - SK: - 'M516.017,93.673l0.127,0.129l1.158,-0.386l1.289,1.030l1.672,-0.644l1.289,0.257l1.929,-0.386l2.577,1.030l-0.774,0.772l-0.513,1.030l-0.645,0.257l-2.832,-0.772l-0.900,0.129l-0.645,0.643l-1.287,0.387l-0.258,-0.258l-1.287,0.515l-1.158,0l-0.129,0.644l-2.318,0.257l-1.028,-0.257l-1.288,-0.772l-0.257,-0.903l0.127,-0.256l0.387,-0.644l1.288,0l0.900,-0.258l0,-0.257l0.517,-0.129l0.256,-0.643l0.644,0l0.385,-0.515l-0.774,0z', - SL: - 'M434.402,208.759l-0.772,-0.257l-1.931,-1.031l-1.287,-1.544l-0.515,-0.902l-0.386,-2.059l1.415,-1.159l0.387,-0.772l0.386,-0.515l0.772,-0.129l0.644,-0.514l2.188,0l0.772,1.029l0.515,1.159l0,0.773l0.387,0.642l0,1.030l0.644,-0.127l-1.159,1.157l-1.287,1.546l-0.129,0.771l0.644,-0.902z', - SN: - 'M420.242,190.35l-1.159,-2.059l-1.287,-1.030l1.159,-0.515l1.287,-1.803l0.644,-1.416l0.901,-0.901l1.416,0.257l1.287,-0.643l1.416,0l1.288,0.773l1.801,0.772l1.674,1.931l1.802,1.930l0.129,1.674l0.515,1.545l1.030,0.772l0.257,1.030l-0.128,0.772l-0.387,0.129l-1.544,-0.129l-0.130,0.258l-0.643,0l-1.931,-0.643l-1.287,0l-5.020,-0.129l-0.644,0.386l-0.901,-0.129l-1.545,0.386l-0.387,-2.058l2.447,0.127l0.644,-0.386l0.514,0l1.030,-0.644l1.159,0.516l1.158,0.128l1.159,-0.644l-0.516,-0.773l-0.900,0.387l-0.901,0l-1.030,-0.643l-0.901,0l-0.515,0.643l2.961,-0.129z', - SO: - 'M591.708,205.411l-8.754-3.218l-1.029-0.901l-1.028-1.287l-1.032-1.545l0.645-0.901l0.9-1.416l0.902,0.515l0.516,1.03l1.286,1.158h1.288l2.575-0.643l2.959-0.387l2.318-0.771l1.286-0.259l1.029-0.515h1.545l0.902-0.128l1.158-0.387l1.416-0.257l1.288-0.9h1.028l-0.13-0.772l0.26,1.544l-0.258,1.545v1.415l-0.644,0.901l-0.772,2.961l-1.288,2.961l-1.674,3.475l-2.316,3.991l-2.189,2.962l-3.218,3.732l-2.702,2.188l-3.991,2.576l-2.575,2.059l-2.959,3.348l-0.645,1.414l-0.516,0.646l-1.931-2.188l0.13-10.17l2.188-2.959l1.029-0.645h1.803l2.318-1.932l3.475-0.129l7.722-8.11H591.708z', - SR: - 'M311.337,210.946l3.219,0.517l0.257,-0.517l2.188,-0.257l2.833,0.774l-1.417,2.316l0.259,1.803l1.029,1.674l-0.386,1.157l-0.257,1.159l-0.772,1.158l-1.545,-0.515l-1.159,0.259l-1.158,-0.259l-0.259,0.773l0.516,0.514l-0.257,0.517l-1.546,-0.130l-1.545,-2.317l-0.385,-1.545l-0.902,0l-1.157,-1.931l0.514,-1.416l-0.129,-0.643l1.545,-0.772l-0.514,2.319z', - SV: - 'M228.694,190.865l-0.257,0.645l-1.545,0l-1.030,-0.259l-1.029,-0.515l-1.545,-0.129l-0.772,-0.644l0.128,-0.385l0.901,-0.644l0.515,-0.385l-0.129,-0.258l0.644,-0.129l0.772,0.129l0.515,0.643l0.902,0.386l0,0.386l1.287,-0.386l0.515,0.258l0.386,0.256l0.258,-1.031z', - SY: - 'M569.696,137.055l-5.278,2.833l-2.96-1.031l0.256-0.385v-1.16l0.644-1.416l1.546-1.029l-0.516-1.031l-1.159-0.128l-0.257-2.059l0.645-1.159l0.771-0.643l0.644-0.515l0.129-1.545l0.901,0.514l2.96-0.772l1.416,0.514h2.317l3.09-1.028l1.416,0.128l3.09-0.515l-1.416,1.673l-1.417,0.773l0.259,1.931l-1.029,3.218L569.696,137.055z', - SZ: - 'M551.674,299l-0.644,1.158l-1.545,0.386l-1.545,-1.544l-0.128,-0.902l0.771,-1.030l0.258,-0.771l0.773,-0.129l1.416,0.385l0.385,1.289l-0.259,-1.158z', - TD: - 'M504.302,192.281l0.258,-1.158l-1.674,-0.130l0,-1.673l-1.158,-1.029l1.158,-3.605l3.475,-2.446l0.130,-3.605l1.030,-5.406l0.514,-1.159l-1.030,-0.901l-0.129,-0.901l-1.029,-0.643l-0.643,-4.249l2.702,-1.416l10.815,5.149l11.069,5.407l-0.258,10.427l-2.316,-0.257l-1.158,2.060l-0.774,1.673l0.643,0.643l-0.901,0.772l0.258,1.159l-0.643,1.030l-0.259,1.031l0.902,-0.129l0.517,1.030l0.126,1.544l0.902,0.772l0,0.645l-1.672,0.514l-1.417,1.030l-1.932,2.962l-2.574,1.287l-2.574,-0.130l-0.772,0.259l0.256,0.900l-1.416,0.901l-1.157,1.031l-3.349,1.029l-0.642,-0.515l-0.517,-0.129l-0.384,0.773l-2.318,0.129l0.515,-0.644l-0.900,-1.930l-0.387,-1.160l-1.158,-0.385l-1.545,-1.546l0.514,-1.287l1.287,0.257l0.774,-0.257l1.415,0.129l-1.415,-2.446l0.128,-1.802l-0.258,-1.804l1.029,1.801z', - TG: - 'M470.317,210.432l-2.187,0.514l-0.644,-0.900l-0.773,-1.673l-0.128,-1.416l0.514,-2.318l-0.643,-1.030l-0.258,-2.187l0,-1.932l-1.159,-1.417l0.258,-0.772l2.317,0l-0.259,1.417l0.775,0.900l0.900,0.902l0.129,1.287l0.516,0.515l-0.131,6.307l-0.773,-1.803z', - TH: - 'M741.548,194.082l-2.445-1.157h-2.188l0.385-2.06h-2.446l-0.128,2.961l-1.545,3.99l-0.902,2.318l0.26,1.931l1.801,0.129l1.031,2.445l0.514,2.318l1.416,1.545l1.674,0.257l1.416,1.415l-0.9,1.159l-1.802,0.257l-0.13-1.287l-2.188-1.156l-0.516,0.385l-1.029-1.029l-0.515-1.288l-1.419-1.544l-1.285-1.287l-0.516,1.544l-0.514-1.417l0.385-1.672l0.773-2.576l1.287-2.832l1.545-2.445l-1.158-2.447l0.129-1.287l-0.387-1.545l-1.803-2.06l-0.643-1.416l1.029-0.514l0.9-2.318l-1.03-1.802l-1.802-1.931l-1.287-2.317l1.158-0.515l1.287-2.96l1.803-0.129l1.672-1.159l1.544-0.643l1.161,0.9l0.13,1.546l1.8,0.128l-0.643,2.832v2.446l2.832-1.544l0.9,0.385h1.545l0.516-1.03l2.061,0.258l2.059,2.189l0.129,2.574l2.188,2.446l-0.127,2.189l-0.902,1.288l-2.447-0.387l-3.475,0.514l-1.801,2.189L741.548,194.082z', - TJ: - 'M656.587,118.519l-1.030,0.900l-2.961,-0.515l-0.257,1.545l2.961,-0.129l3.347,0.901l5.020,-0.514l0.773,2.573l0.772,-0.256l1.674,0.515l-0.129,1.158l0.515,1.416l-2.832,0l-1.802,-0.129l-1.674,1.159l-1.285,0.257l-0.904,0.643l-1.030,-0.900l0.259,-2.317l-0.901,-0.129l0.384,-0.772l-1.543,-0.645l-1.160,0.903l-0.256,1.157l-0.386,0.387l-1.674,-0.128l-0.773,1.287l-0.901,-0.515l-2.059,0.900l-0.773,-0.385l1.547,-2.703l-0.645,-2.060l-1.931,-0.644l0.643,-1.159l2.318,0.129l1.285,-1.417l0.775,-1.801l3.603,-0.644l-0.514,1.287l0.386,0.772l-1.158,0.127z', - TL: - 'M801.921,250.982l0.258,-0.643l2.316,-0.644l1.934,-0.129l0.771,-0.257l1.029,0.257l-0.900,0.773l-2.834,1.158l-2.316,0.773l0,-0.773l0.258,0.515z', - TM: - 'M630.069,130.876l-0.129,-2.188l-2.060,-0.128l-3.088,-2.317l-2.188,-0.387l-2.961,-1.288l-1.932,-0.257l-1.157,0.515l-1.803,-0.129l-1.932,1.546l-2.317,0.513l-0.515,-1.930l0.387,-2.832l-2.059,-0.901l0.644,-1.802l-1.804,-0.129l0.645,-2.318l2.574,0.644l2.316,-0.773l-1.931,-1.673l-0.772,-1.544l-2.187,0.643l-0.260,2.059l-0.900,-1.802l1.160,-0.900l3.090,-0.515l1.800,0.772l1.930,2.060l1.419,-0.128l2.959,0l-0.384,-1.417l2.315,-0.901l2.188,-1.545l3.735,1.416l0.256,2.188l1.030,0.515l2.832,-0.128l1.029,0.515l1.289,2.702l3.089,1.931l1.674,1.288l2.832,1.288l3.604,1.158l0,1.674l-0.902,-0.128l-1.285,-0.774l-0.387,1.030l-2.317,0.516l-0.515,2.188l-1.546,0.772l-2.058,0.386l-0.517,1.288l-2.059,0.257l2.832,1.030z', - TN: - 'M490.785,145.294l-1.159,-4.763l-1.674,-1.159l0,-0.643l-2.187,-1.545l-0.257,-2.059l1.674,-1.545l0.642,-2.189l-0.514,-2.575l0.642,-1.416l2.962,-1.029l1.801,0.257l0,1.415l2.190,-1.028l0.257,0.514l-1.417,1.416l0,1.159l0.902,0.643l-0.258,2.446l-1.801,1.287l0.515,1.416l1.415,0.129l0.644,1.287l1.030,0.387l-0.129,2.060l-1.288,0.772l-0.900,0.902l-1.803,1.029l0.257,1.158l-0.257,1.031l1.287,-0.643z', - TR: - 'M535.712,120.707l2.961-2.575l4.247-0.129l1.03-2.06l5.149,0.387l3.218-1.803l3.219-0.772h4.376l4.765,1.932l3.86,1.028l3.089-0.515l2.317,0.259l3.218-1.417l2.834-0.128l2.702,1.416l0.386,0.901l-0.256,1.288l2.059,0.643l1.029,0.773l-1.802,0.772l0.772,3.089l-0.516,0.901l1.416,2.189l-1.287,0.385l-0.899-0.643l-3.091-0.385l-1.157,0.385l-3.091,0.515l-1.416-0.128l-3.09,1.028h-2.317l-1.416-0.514l-2.96,0.772l-0.901-0.514l-0.129,1.545l-0.644,0.515l-0.771,0.643l-1.029-1.287l1.029-0.902l-1.674,0.129l-2.188-0.514l-1.803,1.544l-4.118,0.257l-2.189-1.416l-2.961-0.128l-0.644,1.159l-1.802,0.256l-2.574-1.415h-2.961l-1.545-2.574l-2.059-1.545l1.286-2.06L535.712,120.707zM535.581,114.27l2.705-0.772l2.317,0.257l0.386,1.03l2.316,0.902l-0.514,0.643l-3.219,0.257l-1.031,0.772l-2.313,1.417l-0.774-1.159v-0.644l0.645-0.258l0.771-1.673L535.581,114.27z', - TW: - 'M793.296,161.256l-1.672,4.378l-1.16,2.188l-1.414-2.317l-0.26-1.932l1.545-2.702l2.189-2.06l1.288,0.772L793.296,161.256z', - TZ: - 'M551.03,229.742l5.407,-0.258l10.042,5.793l0.256,2.059l4.119,2.445l-1.289,3.092l0.131,1.543l1.802,1.031l0,0.645l-0.644,1.673l0.129,0.772l-0.129,1.287l0.901,1.673l1.160,2.704l0.900,0.514l-2.060,1.674l-2.961,1.030l-1.672,-0.128l-0.902,0.900l-1.930,0l-0.644,0.386l-3.347,-0.771l-1.931,0.256l-0.773,-3.732l-1.415,-1.933l-2.704,-0.514l-1.543,-0.901l-1.675,-0.384l-1.030,-0.387l-1.159,-0.773l-1.545,-3.346l-1.545,-1.546l-0.514,-1.546l0.257,-1.415l-0.514,-2.445l1.158,-0.128l1.031,-1.032l1.030,-1.416l0.642,-0.513l0,-0.903l-0.642,-0.643l-0.131,-1.030l0.773,-0.387l0.128,-1.544l-1.028,-1.545l0.900,-0.257l-2.961,0z', - UA: - 'M550.901,86.593l0.901,0.129l0.773,-0.644l0.772,0.128l2.833,-0.256l1.673,1.544l-0.643,0.515l0.258,0.901l2.058,0.129l1.030,1.159l-0.129,0.514l3.476,0.901l2.059,-0.386l1.674,1.287l1.545,0l3.990,0.902l0,0.772l-1.029,1.415l0.513,1.417l-0.384,0.901l-2.575,0.258l-1.417,0.772l-0.128,1.159l-2.060,0.128l-1.801,0.902l-2.576,0.128l-2.317,1.030l0.129,1.674l1.416,0.644l2.704,-0.130l-0.516,0.902l-2.959,0.515l-3.606,1.544l-1.544,-0.515l0.643,-1.286l-2.962,-0.774l0.389,-0.514l2.573,-0.901l-0.772,-0.645l-4.120,-0.643l-0.257,-1.029l-2.446,0.386l-1.030,1.416l-2.060,2.059l-1.158,-0.515l-1.287,0.386l-1.287,-0.386l0.773,-0.386l0.385,-0.901l0.774,-0.901l-0.129,-0.386l0.513,-0.258l0.258,0.386l1.545,0.129l0.773,-0.257l-0.517,-0.258l0.131,-0.386l-0.902,-0.772l-0.386,-1.030l-1.030,-0.515l0.257,-0.901l-1.287,-0.644l-1.029,-0.128l-2.059,-0.903l-1.675,0.259l-0.644,0.386l-1.158,0l-0.773,0.644l-1.931,0.257l-0.899,0.515l-1.289,-0.772l-1.673,0l-1.674,-0.258l-1.159,0.515l-0.256,-0.643l-1.415,-0.773l0.513,-1.030l0.774,-0.772l0.515,0.257l-0.646,-1.287l2.447,-2.189l1.287,-0.385l0.257,-0.773l-1.286,-2.317l1.286,-0.129l1.417,-0.644l2.187,-0.129l2.706,0.258l2.959,0.644l2.189,0l0.900,0.386l1.030,-0.386l0.772,0.643l2.446,-0.129l1.031,0.258l0.128,-1.416l0.900,-0.643l-2.318,0.129z', - UG: - 'M551.03,229.742h-2.961l-0.9,0.257l-1.547,0.899l-0.644-0.256v-2.061l0.644-1.027l0.131-2.189l0.514-1.287l1.029-1.416l1.031-0.643l0.9-1.031l-1.158-0.258l0.258-3.217l1.028-0.775l1.803,0.645l2.188-0.645h1.545l1.416-1.027l1.287,1.93l1.031,2.705l0.771,1.93l-1.028,1.931l-1.932,1.674v0.772v2.832L551.03,229.742z', - - US: - 'M284.434,106.546l-2.704,0.772l-2.575,0.644l-3.089,1.673l-1.287,1.417l-0.258,0.386l-0.127,1.545l0.9,1.415h1.159l-0.259-0.9l0.773,0.515l-0.257,0.772l-1.803,0.515l-1.286-0.13l-1.931,0.517l-1.159,0.128l-1.673,0.128l-2.06,0.644l3.733-0.387h0.128l0.773,0.515l-3.733,0.773h-1.802l0.129-0.257l0.128-0.644l-0.9,1.416h0.643l-0.515,2.06l-1.931,2.06l-0.257-0.773l-0.516-0.129l-0.643-0.643h-0.129h-0.128l0.514,1.416l0.773,1.416l0.129,0.257l-1.03,0.901l-1.545,2.188l-0.258-0.128l1.03-1.802l-1.416-1.287l-0.128-2.06l-0.387,0.901v2.446l-1.673-0.901l1.802,1.544l0.515,1.417l0.772,1.674l0.387,2.703l-1.803,1.93l-2.574,1.03l-2.318,1.417l-0.901,0.128l-1.158,1.931l-2.317,1.673l-2.832,1.288l-1.158,2.06l-0.516,1.415l0.387,2.061l1.03,2.187l1.159,2.061v1.029l1.157,2.703l0.129,2.447l-0.514,2.316l-1.159,0.516l-1.287-0.386l-0.386-1.159l-1.031-0.644l-1.545-2.317l-1.287-1.931l-0.257-1.287l0.515-1.674l-0.643-1.544l-1.803-1.545l-1.416-1.03l-3.089,1.158l-0.644-0.772l-2.574-1.287l-2.962,0.386l-2.445-0.258l-1.674,0.515h-1.544l-0.258,1.16l0.772,1.543l-3.605,0.13l-2.316-0.516l-1.545-0.514l-2.059-0.387l-2.318-0.128l-2.317,0.643l-2.446,1.931l-2.702,1.158l-1.417,1.289l-0.644,1.287v1.802l0.129,1.287l0.515,0.901l-1.03,0.129l-1.931-0.643l-2.188-0.773l-0.772-1.287l-0.515-1.931l-1.545-1.545l-1.03-1.545l-1.288-1.802l-1.93-1.159l-2.189,0.13l-1.674,2.058l-2.316-0.772l-1.288-0.772l-0.772-1.545l-0.9-1.416l-1.545-1.159l-1.416-0.901l-0.902-0.9h-4.633l-0.129,1.158h-2.06h-5.407l-6.178-1.931l-3.992-1.288l0.258-0.515l-3.475,0.259l-3.09,0.256l-0.258-1.029l-1.159-1.416l-2.831-1.545l-1.158-0.129l-1.16-0.9l-2.059-0.13l-0.772-0.515L140,132.292l-2.702-2.704l-2.189-3.732l0.128-0.644l-1.287-0.901l-2.059-2.317l-0.386-2.188l-1.417-1.417l0.644-2.189l-0.129-2.317l-0.901-1.544l0.901-2.96l0.129-2.962l0.514-4.119l-0.771-2.188l-0.387-2.575l3.734,0.515l1.158,2.06l0.644-0.773l-0.387-2.188l-1.287-2.189h15.962h2.704h32.182h18.536h5.536v-1.03h0.901l0.516,1.417l0.772,0.514l1.93,0.129l2.704,0.515l2.703,0.773l2.188-0.387l3.219,0.773h0.385h0.515l0.258-0.129l0.386-0.129l0.387-0.128l0.772-0.258l0.643-0.129l0.644,0.129l0.386,0.258h0.258l0.386,0.257l0.773,0.257l0.772,0.258l0.772,0.386l0.643,0.257l0.387,0.13l0.258,0.128l0.514,0.128l0.515,0.258l0.515,0.258l0.515,0.128l0.515,0.257l0.515,0.258l0.515,0.129l0.514,0.257l0.13,0.386l0.128,0.387l0.386,0.257h0.257h0.902h0.257v0.129v0.129v0.128v0.258h0.129l0.129,0.257h0.128l0.258,0.129l0.386,0.128l0.258-0.128h0.128l0.258,0.258h0.128v0.129l-0.386,0.515l0.516,0.257l0.643,0.257l0.644,0.258l0.514,0.128l0.515,0.386l0.128,0.387v0.257l0.13,0.515l0.128,0.386l0.129,0.515l0.13,0.515l0.127,0.515v0.386l0.129,0.515l0.258,0.772l-0.129,0.258l-0.385,0.515l-0.259,0.515v0.129v0.128l-0.514,0.516l-0.772,1.03l-0.387,0.385l-0.257,0.515v0.257v0.13l0.257,0.257l0.387,0.257l0.514,0.129h0.644l0.643-0.258l0.644-0.257l0.644-0.258l0.643-0.385l0.644-0.258l0.645-0.129l0.9-0.128h0.387h0.128l0.644-0.129l0.643-0.258l0.643-0.257l0.902-0.257l0.772-0.258l0.386-0.128l0.258-0.13v-0.128v-0.258l-0.387-0.772v-0.129l-0.257-0.386l0.386-0.129l0.515-0.257h0.258l0.772-0.128h0.643h0.902l0.772,0.128h0.901h0.516h0.643l0.257-0.515l0.387-0.386l0.256-0.258l0.387-0.258l2.703-1.801l1.287-0.516h4.12h4.891l0.258-0.772h0.901l1.158-0.515l0.902-1.159l0.901-2.187l2.06-1.932l0.9,0.644l1.804-0.386l1.157,0.772v3.605l1.803,1.545v-1.158V106.546zM16.808,64.322l2.059,0.257l0.258,1.031l-1.545,0.386l-1.802-0.516l-1.673-0.772l-2.703,0.386L16.808,64.322zM52.465,70.759l1.803,0.257l1.157,0.774l-2.317,1.286l-2.703,1.029l-1.416-0.643l-0.385-1.288l2.445-0.901l-1.416,0.514L52.465,70.759zM85.42,39.22v9.913v15.446h2.574l2.704,0.774L92.5,66.51l2.445,1.803l2.575-1.545l2.703-0.902l1.545,1.417l1.802,1.159l2.446,1.159l1.674,1.93l2.703,3.09l4.634,1.673v1.802l-1.417,1.287l-1.544-1.029l-2.316-0.901l-0.773-2.318l-3.476-2.189l-1.415-2.573l-2.576-0.129l-4.376-0.129l-3.09-0.773l-5.535-2.703l-2.702-0.514l-4.636-1.031l-3.733,0.259l-5.278-1.288l-3.217-1.159l-2.962,0.643l0.515,1.804l-1.544,0.257l-3.09,0.515l-2.318,0.9l-2.961,0.644l-0.385-1.673l1.159-2.575l2.831-0.9l-0.771-0.645l-3.347,1.545l-1.802,1.802l-3.991,1.931l2.059,1.288l-2.574,1.931l-2.961,1.03l-2.704,0.9l-0.643,1.159l-4.119,1.416l-0.901,1.288l-3.09,1.158l-1.931-0.258l-2.445,0.773l-2.832,0.901l-2.189,0.902l-4.634,0.772l-0.387-0.516l2.962-1.158l2.574-0.901l2.832-1.417l3.347-0.385l1.416-1.03l3.734-1.673l0.514-0.516l2.059-0.901l0.386-2.059l1.418-1.545l-3.091,0.773l-0.901-0.516l-1.415,1.031l-1.803-1.417l-0.644,1.03l-1.029-1.417l-2.704,1.16h-1.673l-0.257-1.674l0.514-0.901l-1.673-1.029l-3.604,0.513l-2.189-1.287l-1.931-0.643v-1.545l-2.059-1.03l1.029-1.673l2.188-1.416l1.03-1.416l2.189-0.129l1.802,0.386l2.189-1.287l1.93,0.257l2.059-0.901l-0.513-1.158l-1.546-0.515l2.059-1.03h-1.673l-2.832,0.515l-0.772,0.643l-2.188-0.514l-3.863,0.257l-3.861-0.643l-1.158-1.159l-3.476-1.545l3.862-1.03l6.05-1.416h2.188l-0.386,1.416l5.665-0.129l-2.189-1.673l-3.347-1.031l-1.931-1.286l-2.574-1.158l-3.605-0.901l1.417-1.417l4.762-0.129l3.475-1.158l0.644-1.288l2.703-1.287l2.704-0.386l5.021-1.159l2.574,0.128l4.119-1.415l3.99,0.643l2.06,1.159l1.159-0.515l4.505,0.128l-0.128,0.644l4.119,0.516l2.703-0.258l5.664,0.773l5.278,0.257l2.06,0.386l3.604-0.514l3.991,0.9l-2.961-0.387L85.42,39.22zM2.647,55.182l1.673,0.515l1.674-0.258l2.189,0.644l2.574,0.386l-0.128,0.258l-2.061,0.644l-2.059-0.644l-1.03-0.514l-2.446,0.128L2.39,56.213l-0.257,1.031L2.647,55.182zM45.256,175.546v-0.773l-0.385-1.029l0.643-0.643l-0.258-0.516l0.129-0.128v-0.129l1.803,0.773l0.256,0.385v0.258l0.258,0.129l0.129,0.128l0.385,0.387l-0.643,0.514l-0.772,0.129l-0.515,0.515l-0.258,0.387L45.256,175.546zM43.067,170.01l-0.385,0.258l-1.158-0.128l0.128-0.387L43.067,170.01zM44.999,170.912v0.257l-0.258,0.129l-0.9,0.128l-0.13-0.514h-0.386l-0.258-0.387l0.13-0.128l0.257-0.129l0.257,0.385l0.516-0.128L44.999,170.912zM39.335,169.496l-0.515-0.643l0.386-0.13l0.515-0.257l0.386,0.643h0.257l0.258,0.516h-0.515l-0.257-0.129h-0.129H39.335zM34.829,167.564l0.129-0.256l0.386-0.259l0.643,0.13l0.13,0.129l-0.13,0.514l-0.256,0.258l-0.516-0.129L34.829,167.564z', - - UY: - 'M310.05,308.396l1.674,-0.257l2.704,2.059l1.030,-0.130l2.702,1.805l2.189,1.414l1.545,1.804l-1.158,1.286l0.772,1.545l-1.159,1.674l-3.089,1.545l-2.060,-0.515l-1.416,0.257l-2.447,-1.157l-1.801,0.128l-1.674,-1.545l0.129,-1.674l0.643,-0.643l0,-2.705l0.644,-2.702l-0.772,2.189z', - UZ: - 'M644.487,126.371l0,-1.674l-3.604,-1.158l-2.832,-1.288l-1.674,-1.288l-3.089,-1.931l-1.289,-2.702l-1.029,-0.515l-2.832,0.128l-1.030,-0.515l-0.256,-2.188l-3.735,-1.416l-2.188,1.545l-2.315,0.901l0.384,1.417l-2.959,0l-0.130,-9.914l6.951,-1.544l0.515,0.129l0.644,0.386l1.159,0.515l2.317,1.030l2.189,1.029l2.574,2.446l3.219,-0.385l4.633,-0.259l3.219,2.060l-0.258,2.703l1.287,0l0.644,2.189l3.347,0.128l0.771,1.288l1.030,-0.129l1.160,-1.931l3.603,-1.802l1.547,-0.515l0.770,0.258l-2.317,1.801l2.061,1.030l1.929,-0.772l3.090,1.416l-3.346,1.932l-2.060,-0.257l-1.158,0.127l-0.386,-0.772l0.514,-1.287l-3.603,0.644l-0.775,1.801l-1.285,1.417l-2.318,-0.129l-0.643,1.159l1.931,0.644l0.645,2.060l-1.547,2.703l-2.059,-0.515l1.416,0z', - VE: - 'M273.105,195.242l-0.128,0.644l-1.545,0.257l0.902,1.287l-0.129,1.416l-1.160,1.545l1.030,2.188l1.159,-0.257l0.643,-1.931l-0.900,-0.901l-0.129,-2.060l3.346,-1.159l-0.385,-1.158l1.029,-0.901l0.902,1.931l1.930,0l1.804,1.545l0,0.902l2.444,0l2.962,-0.259l1.544,1.159l2.060,0.387l1.416,-0.902l0.128,-0.644l3.218,-0.128l3.348,-0.130l-2.317,0.773l0.900,1.288l2.189,0.257l2.060,1.288l0.386,2.187l1.417,-0.128l1.028,0.645l-2.187,1.672l-0.130,0.902l0.902,1.029l-0.644,0.516l-1.673,0.385l0,1.287l-0.772,0.772l1.930,2.061l0.257,0.772l-0.900,1.030l-3.090,0.902l-1.930,0.515l-0.774,0.643l-2.059,-0.773l-2.060,-0.257l-0.514,0.257l1.158,0.643l0,1.804l0.387,1.672l2.188,0.259l0.128,0.514l-1.931,0.772l-0.257,1.159l-1.159,0.515l-1.930,0.644l-0.515,0.772l-2.060,0.129l-1.544,-1.417l-0.773,-2.703l-0.772,-0.901l-1.031,-0.644l1.416,-1.287l-0.127,-0.644l-0.773,-0.772l-0.516,-1.802l0.259,-1.931l0.513,-0.901l0.517,-1.416l-0.902,-0.515l-1.545,0.386l-1.931,-0.129l-1.158,0.258l-1.802,-2.317l-1.546,-0.387l-3.475,0.258l-0.644,-0.901l-0.772,-0.257l0,-0.516l0.257,-1.030l-0.128,-1.028l-0.644,-0.645l-0.387,-1.287l-1.415,-0.129l0.772,-1.545l0.387,-1.931l0.772,-1.030l1.029,-0.771l0.644,-1.289l-1.802,0.514z', - VN: - 'M756.353,168.853l-3.606,2.316l-2.316,2.575l-0.514,1.930l2.059,2.832l2.445,3.476l2.445,1.675l1.674,2.188l1.287,5.020l-0.385,4.634l-2.318,1.802l-3.090,1.803l-2.187,2.188l-3.348,2.574l-0.902,-1.801l0.644,-1.803l-1.929,-1.544l2.316,-1.030l2.834,-0.258l-1.160,-1.674l4.506,-2.059l0.386,-3.218l-0.644,-1.803l0.387,-2.702l-0.645,-1.803l-2.061,-1.931l-1.673,-2.318l-2.188,-3.217l-3.219,-1.674l0.774,-0.902l1.672,-0.772l-1.028,-2.317l-3.347,0l-1.159,-2.446l-1.544,-2.188l1.416,-0.644l2.187,0l2.576,-0.257l2.317,-1.416l1.287,1.030l2.445,0.515l-0.387,1.545l1.289,1.029l-2.704,-0.645z', - VU: - 'M915.718,269.777l1.674,1.545l-0.901,0.387l-0.902,-1.160l-0.129,0.772zM914.56,269.133l-0.387,-0.643l0,-2.060l1.287,0.773l0.387,2.189l-0.774,-0.387l0.513,-0.128z', - YE: - 'M608.315,182.111l-1.931,0.772l-0.516,1.159l-0.127,0.901l-2.704,1.159l-4.249,1.287l-2.445,1.802l-1.158,0.258l-0.774,-0.258l-1.673,1.159l-1.673,0.515l-2.189,0.128l-0.772,0.130l-0.515,0.772l-0.772,0.128l-0.386,0.774l-1.287,-0.130l-0.903,0.387l-1.930,-0.129l-0.644,-1.545l0.129,-1.546l-0.516,-0.772l-0.513,-1.930l-0.772,-1.158l0.514,-0.129l-0.258,-1.159l0.388,-0.515l-0.130,-1.288l1.158,-0.772l-0.258,-1.158l0.645,-1.288l1.158,0.644l0.773,-0.257l3.090,0l0.514,0.257l2.574,0.257l1.031,-0.128l0.644,0.901l1.287,-0.515l1.931,-2.832l2.445,-1.159l7.853,-1.030l2.061,4.378l-0.900,-1.930z', - ZA: - 'M550.13,305.822l-0.516,0.387l-1.158,1.287l-0.773,1.416l-1.544,1.93l-2.96,2.832l-1.932,1.545l-2.061,1.287l-2.832,1.031l-1.287,0.128l-0.387,0.772l-1.672-0.386l-1.288,0.514l-2.961-0.514l-1.544,0.257l-1.158-0.128l-2.834,1.028l-2.316,0.517l-1.545,1.028h-1.285l-1.16-0.9l-0.9-0.128l-1.158-1.16l-0.131,0.388l-0.385-0.772v-1.546l-0.771-1.801l0.771-0.516v-2.061l-1.802-2.445l-1.288-2.316l-1.931-3.478l1.286-1.415l1.032,0.773l0.384,1.158l1.288,0.129l1.673,0.514l1.418-0.129l2.445-1.416v-9.912l0.772,0.387l1.544,2.574l-0.258,1.674l0.645,0.9l1.93-0.256l1.289-1.287l1.287-0.774l0.643-1.286l1.287-0.645l1.158,0.387l1.288,0.773l2.188,0.129l1.801-0.645l0.258-0.901l0.387-1.287l1.545-0.128l0.772-1.03l0.901-1.804l2.445-2.059l3.733-1.93h1.157l1.289,0.514l0.9-0.387l1.416,0.258l1.287,3.862l0.771,1.802l-0.514,3.09l0.258,0.9l-1.416-0.385l-0.773,0.129l-0.258,0.771l-0.771,1.029L547.94,299l1.545,1.544l1.545-0.386l0.644-1.158h2.059l-0.772,1.93l-0.258,2.318l-0.77,1.157L550.13,305.822zM543.306,304.922l-1.158-0.773l-1.287,0.516l-1.416,1.029l-1.416,1.803l1.931,2.059l1.03-0.258l0.514-0.9l1.417-0.386l0.515-0.901l0.773-1.287L543.306,304.922z', - ZM: - 'M553.476,251.883l1.287,1.160l0.644,2.315l-0.386,0.644l-0.645,2.189l0.516,2.317l-0.773,0.902l-0.772,2.447l1.416,0.770l-8.239,2.189l0.258,1.932l-2.060,0.385l-1.543,1.031l-0.259,1.028l-1.028,0.130l-2.319,2.188l-1.545,1.802l-0.902,0l-0.772,-0.257l-3.088,-0.256l-0.515,-0.259l0,-0.259l-1.030,-0.513l-1.803,-0.258l-2.187,0.644l-1.673,-1.674l-1.804,-2.187l0.128,-8.625l5.536,0.127l-0.257,-1.028l0.387,-0.903l-0.387,-1.287l0.257,-1.286l-0.257,-0.902l0.901,0.130l0.128,0.772l1.289,0l1.672,0.258l0.903,1.158l2.187,0.385l1.674,-0.901l0.644,1.416l2.058,0.387l0.903,1.158l1.159,1.545l2.058,0l-0.258,-2.961l-0.642,0.516l-1.932,-1.031l-0.772,-0.514l0.387,-2.705l0.514,-3.088l-0.642,-1.288l0.771,-1.673l0.643,-0.387l3.733,-0.386l1.030,0.258l1.159,0.773l1.030,0.387l1.675,0.384l-1.543,-0.901z', - ZW: - 'M549.228,286.898l-1.416,-0.257l-0.901,0.386l-1.289,-0.513l-1.157,0l-1.673,-1.290l-2.061,-0.385l-0.772,-1.674l0,-1.030l-1.158,-0.256l-3.089,-2.962l-0.900,-1.544l-0.516,-0.516l-1.030,-2.058l3.088,0.256l0.772,0.257l0.902,0l1.545,-1.802l2.319,-2.188l1.028,-0.130l0.259,-1.028l1.543,-1.031l2.060,-0.385l0.129,1.029l2.317,-0.129l1.287,0.645l0.515,0.645l1.287,0.254l1.415,0.774l0,3.475l-0.513,1.801l-0.128,2.061l0.385,0.773l-0.257,1.545l-0.386,0.258l-0.773,1.930l2.832,-3.089z', - }, -}; diff --git a/test/bunnies.html b/test/bunnies.html deleted file mode 100644 index f40aba225..000000000 --- a/test/bunnies.html +++ /dev/null @@ -1,200 +0,0 @@ - - - - - - -
    - - - - diff --git a/test/ifame.html b/test/ifame.html deleted file mode 100644 index 386c0d7c5..000000000 --- a/test/ifame.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - KonvaJS Sandbox - - - - - - - - - \ No newline at end of file diff --git a/test/import-test.cjs b/test/import-test.cjs deleted file mode 100644 index c060a3cde..000000000 --- a/test/import-test.cjs +++ /dev/null @@ -1,6 +0,0 @@ -// try to import only core -const Konva = require('../'); - -// just do a simple action -const stage = new Konva.Stage(); -stage.toDataURL(); diff --git a/test/import-test.mjs b/test/import-test.mjs deleted file mode 100644 index cebddda14..000000000 --- a/test/import-test.mjs +++ /dev/null @@ -1,18 +0,0 @@ -function equal(val1, val2, message) { - if (val1 !== val2) { - throw new Error('Not passed: ' + message); - } -} - -// try to import only core -import Konva from '../lib/Core.js'; -import { Rect } from '../lib/shapes/Rect.js'; -import '../lib/index-node.js'; - -equal(Rect !== undefined, true, 'Rect is defined'); - -equal(Konva.Rect, Rect, 'Rect is injected'); - -// // just do a simple action -const stage = new Konva.Stage(); -stage.toDataURL(); diff --git a/test/manual-tests.html b/test/manual-tests.html deleted file mode 100644 index a4c09abfa..000000000 --- a/test/manual-tests.html +++ /dev/null @@ -1,70 +0,0 @@ - - -
    - - - - - - - - - - -
    - - - - - diff --git a/test/manual/Blur-test.ts b/test/manual/Blur-test.ts deleted file mode 100644 index 9bafa8e3f..000000000 --- a/test/manual/Blur-test.ts +++ /dev/null @@ -1,279 +0,0 @@ -import { assert } from 'chai'; - -import { addStage, Konva, loadImage } from '../unit/test-utils'; - -describe('Blur', function () { - // ====================================================== - it('basic blur', function (done) { - var stage = addStage(); - - loadImage('darth-vader.jpg', (imageObj) => { - var layer = new Konva.Layer(); - const darth = new Konva.Image({ - x: 10, - y: 10, - image: imageObj, - draggable: true, - }); - - layer.add(darth); - stage.add(layer); - - darth.cache(); - darth.filters([Konva.Filters.Blur]); - darth.blurRadius(10); - - assert.equal(darth.blurRadius(), 10); - assert.equal(darth._filterUpToDate, false); - - layer.draw(); - - assert.equal(darth._filterUpToDate, true); - - darth.blurRadius(20); - - assert.equal(darth.blurRadius(), 20); - assert.equal(darth._filterUpToDate, false); - - layer.draw(); - - assert.equal(darth._filterUpToDate, true); - - done(); - }); - }); - - it('blur group', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var group = new Konva.Group({ - x: 100, - y: 100, - draggable: true, - }); - var top = new Konva.Circle({ - x: 0, - y: -70, - radius: 30, - fill: 'blue', - stroke: 'black', - strokeWidth: 4, - }); - var right = new Konva.Circle({ - x: 70, - y: 0, - radius: 30, - fill: 'blue', - stroke: 'black', - strokeWidth: 4, - }); - var bottom = new Konva.Circle({ - x: 0, - y: 70, - radius: 30, - fill: 'blue', - stroke: 'black', - strokeWidth: 4, - }); - var left = new Konva.Circle({ - x: -70, - y: 0, - radius: 30, - fill: 'blue', - stroke: 'black', - strokeWidth: 4, - }); - - group.add(top).add(right).add(bottom).add(left); - layer.add(group); - stage.add(layer); - - group.cache(); - - group.offset(); - - group.filters([Konva.Filters.Blur]); - group.blurRadius(20); - - layer.draw(); - }); - - // ====================================================== - it('tween blur', function (done) { - var stage = addStage(); - - loadImage('darth-vader.jpg', (imageObj) => { - var layer = new Konva.Layer(); - const darth = new Konva.Image({ - x: 10, - y: 10, - image: imageObj, - draggable: true, - }); - - layer.add(darth); - stage.add(layer); - - darth.cache(); - darth.filters([Konva.Filters.Blur]); - darth.blurRadius(100); - layer.draw(); - - var tween = new Konva.Tween({ - node: darth, - duration: 2.0, - blurRadius: 0, - easing: Konva.Easings.EaseInOut, - }); - - darth.on('mouseover', function () { - tween.play(); - }); - - darth.on('mouseout', function () { - tween.reverse(); - }); - - done(); - }); - }); - - // ====================================================== - it('crop blur', function (done) { - var stage = addStage(); - - loadImage('darth-vader.jpg', (imageObj) => { - var layer = new Konva.Layer(); - const darth = new Konva.Image({ - x: 10, - y: 10, - image: imageObj, - crop: { x: 128, y: 48, width: 256, height: 128 }, - draggable: true, - }); - - layer.add(darth); - stage.add(layer); - - darth.cache(); - darth.filters([Konva.Filters.Blur]); - darth.blurRadius(10); - layer.draw(); - - done(); - }); - }); - - // ====================================================== - it('crop tween blur', function (done) { - var stage = addStage(); - - loadImage('darth-vader.jpg', (imageObj) => { - var layer = new Konva.Layer(); - const darth = new Konva.Image({ - x: 10, - y: 10, - image: imageObj, - crop: { x: 128, y: 48, width: 256, height: 128 }, - draggable: true, - }); - - layer.add(darth); - stage.add(layer); - - darth.cache(); - darth.filters([Konva.Filters.Blur]); - darth.blurRadius(100); - layer.draw(); - - var tween = new Konva.Tween({ - node: darth, - duration: 2.0, - blurRadius: 0, - easing: Konva.Easings.EaseInOut, - }); - - darth.on('mouseover', function () { - tween.play(); - }); - - darth.on('mouseout', function () { - tween.reverse(); - }); - - done(); - }); - }); - - // ====================================================== - it('transparency', function (done) { - var stage = addStage(); - - loadImage('darth-vader.jpg', (imageObj) => { - var layer = new Konva.Layer(); - const darth = new Konva.Image({ - x: 10, - y: 10, - image: imageObj, - draggable: true, - }); - - layer.add(darth); - stage.add(layer); - - darth.cache(); - darth.filters([Konva.Filters.Blur]); - darth.blurRadius(100); - layer.draw(); - - var tween = new Konva.Tween({ - node: darth, - duration: 1, - blurRadius: 0, - easing: Konva.Easings.EaseInOut, - }); - - darth.on('mouseover', function () { - tween.play(); - }); - - darth.on('mouseout', function () { - tween.reverse(); - }); - - done(); - }); - }); - - // ====================================================== - it('blur hit region', function (done) { - var stage = addStage(); - - loadImage('lion.png', (imageObj) => { - var layer = new Konva.Layer(); - const darth = new Konva.Image({ - x: 10, - y: 10, - image: imageObj, - draggable: true, - }); - - //console.log(darth.hasStroke()) - - layer.add(darth); - stage.add(layer); - - darth.cache(); - darth.filters([Konva.Filters.Blur]); - darth.blurRadius(20); - darth.drawHitFromCache(100); - layer.draw(); - - //console.log(darth._getCanvasCache().hit.getContext().getTrace()); - - //assert.equal(darth._getCanvasCache().hit.getContext().getTrace(true), 'save();translate();beginPath();rect();closePath();save();fillStyle;fill();restore();restore();clearRect();getImageData();putImageData();'); - - done(); - }); - }); -}); diff --git a/test/manual/Brighten-test.ts b/test/manual/Brighten-test.ts deleted file mode 100644 index 06112211d..000000000 --- a/test/manual/Brighten-test.ts +++ /dev/null @@ -1,140 +0,0 @@ -import { assert } from 'chai'; - -import { addStage, Konva, loadImage } from '../unit/test-utils'; - -describe('Brighten', function () { - // ====================================================== - it('basic', function (done) { - var stage = addStage(); - - loadImage('darth-vader.jpg', (imageObj) => { - var layer = new Konva.Layer(); - const darth = new Konva.Image({ - x: 10, - y: 10, - image: imageObj, - draggable: true, - }); - - layer.add(darth); - stage.add(layer); - - darth.cache(); - darth.filters([Konva.Filters.Brighten]); - darth.brightness(0.3); - layer.draw(); - - assert.equal(darth.brightness(), 0.3); - - done(); - }); - }); - - // ====================================================== - it('tween', function (done) { - var stage = addStage(); - - loadImage('darth-vader.jpg', (imageObj) => { - var layer = new Konva.Layer(); - const darth = new Konva.Image({ - x: 10, - y: 10, - image: imageObj, - draggable: true, - }); - - layer.add(darth); - stage.add(layer); - - darth.cache(); - darth.filters([Konva.Filters.Brighten]); - darth.brightness(0.3); - layer.draw(); - - var tween = new Konva.Tween({ - node: darth, - duration: 2.0, - brightness: 0, - easing: Konva.Easings.EaseInOut, - }); - - darth.on('mouseover', function () { - tween.play(); - }); - - darth.on('mouseout', function () { - tween.reverse(); - }); - - done(); - }); - }); - - // ====================================================== - it('crop', function (done) { - var stage = addStage(); - - loadImage('darth-vader.jpg', (imageObj) => { - var layer = new Konva.Layer(); - const darth = new Konva.Image({ - x: 10, - y: 10, - image: imageObj, - crop: { x: 128, y: 48, width: 256, height: 128 }, - draggable: true, - }); - - layer.add(darth); - stage.add(layer); - - darth.cache(); - darth.filters([Konva.Filters.Brighten]); - darth.brightness(-0.3); - layer.draw(); - - assert.equal(darth.brightness(), -0.3); - - done(); - }); - }); - - // ====================================================== - it('tween transparency', function (done) { - var stage = addStage(); - - loadImage('lion.png', (imageObj) => { - var layer = new Konva.Layer(); - const darth = new Konva.Image({ - x: 10, - y: 10, - image: imageObj, - draggable: true, - }); - - layer.add(darth); - stage.add(layer); - - darth.cache(); - darth.filters([Konva.Filters.Brighten]); - darth.brightness(0.3); - layer.draw(); - - var tween = new Konva.Tween({ - node: darth, - duration: 2.0, - brightness: -0.3, - easing: Konva.Easings.EaseInOut, - }); - - darth.on('mouseover', function () { - tween.play(); - }); - - darth.on('mouseout', function () { - tween.reverse(); - }); - - done(); - }); - }); -}); diff --git a/test/manual/Contrast-test.ts b/test/manual/Contrast-test.ts deleted file mode 100644 index 0528af45c..000000000 --- a/test/manual/Contrast-test.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { assert } from 'chai'; - -import { addStage, Konva, loadImage } from '../unit/test-utils'; - -describe('Filter Contrast', function () { - // ====================================================== - it('basic', function (done) { - var stage = addStage(); - - loadImage('darth-vader.jpg', (imageObj) => { - var layer = new Konva.Layer(); - var darth = new Konva.Image({ - x: 10, - y: 10, - image: imageObj, - draggable: true, - }); - - layer.add(darth); - stage.add(layer); - - darth.cache(); - darth.filters([Konva.Filters.Contrast]); - darth.contrast(40); - layer.draw(); - - assert.equal(darth.contrast(), 40); - - done(); - }); - }); - - // ====================================================== - it('tween', function (done) { - var stage = addStage(); - - loadImage('darth-vader.jpg', (imageObj) => { - var layer = new Konva.Layer(); - var darth = new Konva.Image({ - x: 10, - y: 10, - image: imageObj, - draggable: true, - }); - - layer.add(darth); - stage.add(layer); - - darth.cache(); - darth.filters([Konva.Filters.Contrast]); - darth.contrast(40); - layer.draw(); - - var tween = new Konva.Tween({ - node: darth, - duration: 2.0, - contrast: 0, - easing: Konva.Easings.EaseInOut, - }); - - darth.on('mouseover', function () { - tween.play(); - }); - - darth.on('mouseout', function () { - tween.reverse(); - }); - - done(); - }); - }); - - // ====================================================== - it('crop', function (done) { - var stage = addStage(); - - loadImage('darth-vader.jpg', (imageObj) => { - var layer = new Konva.Layer(); - var darth = new Konva.Image({ - x: 10, - y: 10, - image: imageObj, - crop: { x: 128, y: 48, width: 256, height: 128 }, - draggable: true, - }); - - layer.add(darth); - stage.add(layer); - - darth.cache(); - darth.filters([Konva.Filters.Contrast]); - darth.contrast(-40); - layer.draw(); - - assert.equal(darth.contrast(), -40); - - done(); - }); - }); -}); diff --git a/test/manual/Emboss-test.ts b/test/manual/Emboss-test.ts deleted file mode 100644 index 841e44fed..000000000 --- a/test/manual/Emboss-test.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { assert } from 'chai'; - -import { addStage, Konva, loadImage } from '../unit/test-utils'; - -describe('Emboss', function () { - // ====================================================== - it('basic emboss', function (done) { - var stage = addStage(); - - loadImage('darth-vader.jpg', (imageObj) => { - var layer = new Konva.Layer(); - const darth = new Konva.Image({ - x: 10, - y: 10, - image: imageObj, - draggable: true, - }); - - layer.add(darth); - stage.add(layer); - darth.cache(); - darth.filters([Konva.Filters.Emboss]); - darth.embossStrength(0.5); - darth.embossWhiteLevel(0.8); - darth.embossDirection('top-right'); - - layer.draw(); - - var tween = new Konva.Tween({ - node: darth, - duration: 0.6, - embossStrength: 10, - easing: Konva.Easings.EaseInOut, - }); - - darth.on('mouseover', function () { - tween.play(); - }); - - darth.on('mouseout', function () { - tween.reverse(); - }); - - done(); - }); - //imageObj.src = 'assets/lion.png'; - }); - - // ====================================================== - it('blended emboss', function (done) { - var stage = addStage(); - - loadImage('darth-vader.jpg', (imageObj) => { - var layer = new Konva.Layer(); - const darth = new Konva.Image({ - x: 10, - y: 10, - image: imageObj, - draggable: true, - }); - - layer.add(darth); - stage.add(layer); - darth.cache(); - darth.filters([Konva.Filters.Emboss]); - darth.embossStrength(0.5); - darth.embossWhiteLevel(0.2); - darth.embossBlend(true); - - layer.draw(); - - var tween = new Konva.Tween({ - node: darth, - duration: 0.6, - embossStrength: 10, - easing: Konva.Easings.EaseInOut, - }); - - darth.on('mouseover', function () { - tween.play(); - }); - - darth.on('mouseout', function () { - tween.reverse(); - }); - - done(); - }); - //imageObj.src = 'assets/lion.png'; - }); -}); diff --git a/test/manual/Enhance-test.ts b/test/manual/Enhance-test.ts deleted file mode 100644 index 0f93f2b18..000000000 --- a/test/manual/Enhance-test.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { assert } from 'chai'; - -import { addStage, Konva, loadImage } from '../unit/test-utils'; - -describe('Enhance', function () { - // ====================================================== - it('on image', function (done) { - var stage = addStage(); - - loadImage('scorpion-sprite.png', (imageObj) => { - var layer = new Konva.Layer(); - var filt = new Konva.Image({ - x: 10, - y: 10, - image: imageObj, - draggable: true, - }); - var orig = new Konva.Image({ - x: 200, - y: 10, - image: imageObj, - draggable: true, - }); - - layer.add(filt); - layer.add(orig); - stage.add(layer); - - filt.cache(); - filt.enhance(1.0); - filt.filters([Konva.Filters.Enhance]); - layer.draw(); - - done(); - }); - }); - - // ====================================================== - it('tween enhance', function (done) { - var stage = addStage(); - - loadImage('scorpion-sprite.png', (imageObj) => { - var layer = new Konva.Layer(); - const darth = new Konva.Image({ - x: 10, - y: 10, - image: imageObj, - draggable: true, - }); - - layer.add(darth); - stage.add(layer); - - darth.cache(); - darth.filters([Konva.Filters.Enhance]); - darth.enhance(-1); - layer.draw(); - - var tween = new Konva.Tween({ - node: darth, - duration: 2.0, - enhance: 1.0, - easing: Konva.Easings.EaseInOut, - }); - - darth.on('mouseover', function () { - tween.play(); - }); - - darth.on('mouseout', function () { - tween.reverse(); - }); - - done(); - }); - }); -}); diff --git a/test/manual/Filter-test.ts b/test/manual/Filter-test.ts deleted file mode 100644 index a12af132c..000000000 --- a/test/manual/Filter-test.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { addStage, Konva, cloneAndCompareLayer } from '../unit/test-utils'; - -describe('Filter', function () { - it('pixelRaio check', function () { - Konva.pixelRatio = 2; - var stage = addStage(); - var layer = new Konva.Layer(); - - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - fill: 'red', - stroke: 'green', - radius: 15, - }); - - layer.add(circle); - stage.add(layer); - circle.cache(); - circle.filters([Konva.Filters.Blur]); - circle.blurRadius(0); - layer.draw(); - - cloneAndCompareLayer(layer, 150); - Konva.pixelRatio = 1; - }); -}); diff --git a/test/manual/Grayscale-test.ts b/test/manual/Grayscale-test.ts deleted file mode 100644 index 0b04baa09..000000000 --- a/test/manual/Grayscale-test.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { assert } from 'chai'; - -import { addStage, Konva, loadImage } from '../unit/test-utils'; - -describe('Grayscale', function () { - // ====================================================== - it('basic', function (done) { - var stage = addStage(); - - loadImage('darth-vader.jpg', (imageObj) => { - var layer = new Konva.Layer(); - const darth = new Konva.Image({ - x: 10, - y: 10, - image: imageObj, - draggable: true, - }); - - layer.add(darth); - stage.add(layer); - - darth.cache(); - darth.filters([Konva.Filters.Grayscale]); - layer.draw(); - - done(); - }); - }); - - // ====================================================== - it('crop', function (done) { - var stage = addStage(); - - loadImage('darth-vader.jpg', (imageObj) => { - var layer = new Konva.Layer(); - const darth = new Konva.Image({ - x: 10, - y: 10, - image: imageObj, - crop: { x: 128, y: 48, width: 256, height: 128 }, - draggable: true, - }); - - layer.add(darth); - stage.add(layer); - - darth.cache(); - darth.filters([Konva.Filters.Grayscale]); - layer.draw(); - - done(); - }); - }); - - // ====================================================== - it('with transparency', function (done) { - var stage = addStage(); - - loadImage('lion.png', (imageObj) => { - var layer = new Konva.Layer(); - const darth = new Konva.Image({ - x: 10, - y: 10, - image: imageObj, - draggable: true, - }); - - layer.add(darth); - stage.add(layer); - - darth.cache(); - darth.filters([Konva.Filters.Grayscale]); - layer.draw(); - - done(); - }); - }); -}); diff --git a/test/manual/HSL-test.ts b/test/manual/HSL-test.ts deleted file mode 100644 index ea4fc3406..000000000 --- a/test/manual/HSL-test.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { assert } from 'chai'; - -import { addStage, Konva, loadImage } from '../unit/test-utils'; - -describe('HSL', function () { - // ====================================================== - it('hue shift tween transparancy', function (done) { - var stage = addStage(); - - loadImage('lion.png', (imageObj) => { - var layer = new Konva.Layer(); - const darth = new Konva.Image({ - x: 10, - y: 10, - image: imageObj, - draggable: true, - }); - - layer.add(darth); - stage.add(layer); - - darth.cache(); - darth.filters([Konva.Filters.HSL]); - darth.hue(360); - layer.draw(); - - var tween = new Konva.Tween({ - node: darth, - duration: 1.0, - hue: 0, - easing: Konva.Easings.EaseInOut, - }); - - darth.on('mouseover', function () { - tween.play(); - }); - - darth.on('mouseout', function () { - tween.reverse(); - }); - - done(); - }); - }); - - // ====================================================== - it('HSL luminance tween transparancy', function (done) { - var stage = addStage(); - - loadImage('darth-vader.jpg', (imageObj) => { - var layer = new Konva.Layer(); - const darth = new Konva.Image({ - x: 10, - y: 10, - image: imageObj, - draggable: true, - }); - - layer.add(darth); - stage.add(layer); - - darth.cache(); - darth.filters([Konva.Filters.HSL]); - darth.luminance(1.0); - layer.draw(); - - var tween = new Konva.Tween({ - node: darth, - duration: 1.0, - luminance: -1.0, - easing: Konva.Easings.EaseInOut, - }); - - darth.on('mouseover', function () { - tween.play(); - }); - - darth.on('mouseout', function () { - tween.reverse(); - }); - - done(); - }); - }); - - // ====================================================== - it('HSL saturation tween transparancy', function (done) { - var stage = addStage(); - - loadImage('lion.png', (imageObj) => { - var layer = new Konva.Layer(); - const darth = new Konva.Image({ - x: 10, - y: 10, - image: imageObj, - draggable: true, - }); - - layer.add(darth); - stage.add(layer); - - darth.cache(); - darth.filters([Konva.Filters.HSL]); - darth.saturation(1.0); - layer.draw(); - - var tween = new Konva.Tween({ - node: darth, - duration: 1.0, - saturation: -1.0, - easing: Konva.Easings.EaseInOut, - }); - - darth.on('mouseover', function () { - tween.play(); - }); - - darth.on('mouseout', function () { - tween.reverse(); - }); - - done(); - }); - }); -}); diff --git a/test/manual/HSV-test.ts b/test/manual/HSV-test.ts deleted file mode 100644 index 695266178..000000000 --- a/test/manual/HSV-test.ts +++ /dev/null @@ -1,166 +0,0 @@ -import { assert } from 'chai'; - -import { addStage, Konva, loadImage } from '../unit/test-utils'; - -describe('HSV', function () { - // ====================================================== - it('hue shift tween transparancy', function (done) { - var stage = addStage(); - - loadImage('darth-vader.jpg', (imageObj) => { - var layer = new Konva.Layer(); - const darth = new Konva.Image({ - x: 10, - y: 10, - image: imageObj, - draggable: true, - }); - - layer.add(darth); - stage.add(layer); - - darth.cache(); - darth.filters([Konva.Filters.HSV]); - darth.hue(360); - layer.draw(); - - var tween = new Konva.Tween({ - node: darth, - duration: 1.0, - hue: 0, - easing: Konva.Easings.EaseInOut, - }); - - darth.on('mouseover', function () { - tween.play(); - }); - - darth.on('mouseout', function () { - tween.reverse(); - }); - - done(); - }); - }); - - // ====================================================== - it('saturate image', function (done) { - var stage = addStage(); - - loadImage('darth-vader.jpg', (imageObj) => { - var layer = new Konva.Layer(); - const darth = new Konva.Image({ - x: 10, - y: 10, - image: imageObj, - draggable: true, - }); - - layer.add(darth); - stage.add(layer); - - darth.cache(); - darth.filters([Konva.Filters.HSV]); - - darth.saturation(1.0); - layer.draw(); - - var tween = new Konva.Tween({ - node: darth, - duration: 1.0, - saturation: -1.0, - easing: Konva.Easings.EaseInOut, - }); - - darth.on('mouseover', function () { - tween.play(); - }); - - darth.on('mouseout', function () { - tween.reverse(); - }); - - done(); - }); - }); - - // ====================================================== - it('saturation tween transparancy', function (done) { - var stage = addStage(); - - loadImage('darth-vader.jpg', (imageObj) => { - var layer = new Konva.Layer(); - const darth = new Konva.Image({ - x: 10, - y: 10, - image: imageObj, - draggable: true, - }); - - layer.add(darth); - stage.add(layer); - - darth.cache(); - darth.filters([Konva.Filters.HSV]); - darth.saturation(1.0); - layer.draw(); - - var tween = new Konva.Tween({ - node: darth, - duration: 1.0, - saturation: -1, - easing: Konva.Easings.EaseInOut, - }); - - darth.on('mouseover', function () { - tween.play(); - }); - - darth.on('mouseout', function () { - tween.reverse(); - }); - - done(); - }); - }); - - // ====================================================== - it('value tween transparancy', function (done) { - var stage = addStage(); - - loadImage('darth-vader.jpg', (imageObj) => { - var layer = new Konva.Layer(); - const darth = new Konva.Image({ - x: 10, - y: 10, - image: imageObj, - draggable: true, - }); - - layer.add(darth); - stage.add(layer); - - darth.cache(); - darth.filters([Konva.Filters.HSV]); - darth.value(1.0); - layer.draw(); - - var tween = new Konva.Tween({ - node: darth, - duration: 1.0, - value: -1.0, - easing: Konva.Easings.EaseInOut, - }); - - darth.on('mouseover', function () { - tween.play(); - }); - - darth.on('mouseout', function () { - tween.reverse(); - }); - - done(); - }); - }); -}); diff --git a/test/manual/Invert-test.ts b/test/manual/Invert-test.ts deleted file mode 100644 index 800cebf9d..000000000 --- a/test/manual/Invert-test.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { assert } from 'chai'; - -import { addStage, Konva, loadImage } from '../unit/test-utils'; - -describe('Invert', function () { - // ====================================================== - it('basic', function (done) { - var stage = addStage(); - - loadImage('darth-vader.jpg', (imageObj) => { - var layer = new Konva.Layer(); - const darth = new Konva.Image({ - x: 10, - y: 10, - image: imageObj, - draggable: true, - }); - - layer.add(darth); - stage.add(layer); - - darth.cache(); - darth.filters([Konva.Filters.Invert]); - layer.draw(); - - done(); - }); - }); - - // ====================================================== - it('crop', function (done) { - var stage = addStage(); - - loadImage('darth-vader.jpg', (imageObj) => { - var layer = new Konva.Layer(); - const darth = new Konva.Image({ - x: 10, - y: 10, - image: imageObj, - crop: { x: 128, y: 48, width: 256, height: 128 }, - draggable: true, - }); - - layer.add(darth); - stage.add(layer); - - darth.cache(); - darth.filters([Konva.Filters.Invert]); - layer.draw(); - - done(); - }); - }); - - // ====================================================== - it('transparancy', function (done) { - var stage = addStage(); - - loadImage('darth-vader.jpg', (imageObj) => { - var layer = new Konva.Layer(); - const darth = new Konva.Image({ - x: 10, - y: 10, - image: imageObj, - draggable: true, - }); - - layer.add(darth); - stage.add(layer); - - darth.cache(); - darth.filters([Konva.Filters.Invert]); - layer.draw(); - - done(); - }); - }); -}); diff --git a/test/manual/Kaleidoscope-test.ts b/test/manual/Kaleidoscope-test.ts deleted file mode 100644 index ef400ac1f..000000000 --- a/test/manual/Kaleidoscope-test.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { assert } from 'chai'; - -import { addStage, Konva, loadImage } from '../unit/test-utils'; - -describe('Kaleidoscope', function () { - // ====================================================== - it('basic', function (done) { - var stage = addStage(); - - loadImage('darth-vader.jpg', (imageObj) => { - var layer = new Konva.Layer(); - const darth = new Konva.Image({ - x: 10, - y: 10, - image: imageObj, - draggable: true, - }); - - layer.add(darth); - stage.add(layer); - - darth.cache(); - darth.filters([Konva.Filters.Kaleidoscope]); - darth.kaleidoscopePower(2); - - assert.equal(darth.kaleidoscopePower(), 2); - assert.equal(darth._filterUpToDate, false); - - layer.draw(); - - assert.equal(darth._filterUpToDate, true); - - darth.kaleidoscopePower(3); - - assert.equal(darth.kaleidoscopePower(), 3); - assert.equal(darth._filterUpToDate, false); - - layer.draw(); - - assert.equal(darth._filterUpToDate, true); - - done(); - }); - }); - - // ====================================================== - it('tween angle', function (done) { - var stage = addStage(); - - loadImage('darth-vader.jpg', (imageObj) => { - var layer = new Konva.Layer(); - const darth = new Konva.Image({ - x: 10, - y: 10, - image: imageObj, - draggable: true, - }); - - layer.add(darth); - stage.add(layer); - - darth.cache(); - darth.filters([Konva.Filters.Kaleidoscope]); - darth.kaleidoscopePower(3); - darth.kaleidoscopeAngle(0); - layer.draw(); - - var tween = new Konva.Tween({ - node: darth, - duration: 10.0, - kaleidoscopeAngle: 720, - easing: Konva.Easings.EaseInOut, - }); - - darth.on('mouseover', function () { - tween.play(); - }); - - darth.on('mouseout', function () { - tween.reverse(); - }); - - done(); - }); - }); - - // ====================================================== - it('tween power', function (done) { - var stage = addStage(); - - loadImage('darth-vader.jpg', (imageObj) => { - var layer = new Konva.Layer(); - const darth = new Konva.Image({ - x: 10, - y: 10, - image: imageObj, - draggable: true, - }); - - layer.add(darth); - stage.add(layer); - - darth.cache(); - darth.filters([Konva.Filters.Kaleidoscope]); - darth.kaleidoscopePower(0); - darth.kaleidoscopeAngle(0); - layer.draw(); - - var tween = new Konva.Tween({ - node: darth, - duration: 2.0, - kaleidoscopePower: 8, - easing: Konva.Easings.EaseInOut, - }); - - darth.on('mouseover', function () { - tween.play(); - }); - - darth.on('mouseout', function () { - tween.reverse(); - }); - - done(); - }); - }); -}); diff --git a/test/manual/Manual-test.ts b/test/manual/Manual-test.ts deleted file mode 100644 index 1a506a04b..000000000 --- a/test/manual/Manual-test.ts +++ /dev/null @@ -1,416 +0,0 @@ -import { assert } from 'chai'; - -import { addStage, Konva, loadImage } from '../unit/test-utils'; - -describe('Manual', function () { - // ====================================================== - it('oscillation animation', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var hexagon = new Konva.RegularPolygon({ - x: stage.width() / 2, - y: stage.height() / 2, - sides: 6, - radius: 70, - fill: 'red', - stroke: 'black', - strokeWidth: 4, - }); - - // var hexagon = new Konva.Rect({ - // x: stage.width()/2, - // y: stage.height()/2, - // width: 100, - // height: 50, - // fill: 'red', - // stroke: 'black', - // strokeWidth: 4 - // }); - - layer.add(hexagon); - stage.add(layer); - - var amplitude = 150; - var period = 2000; - // in ms - var centerX = stage.width() / 2; - - var anim = new Konva.Animation(function (frame) { - hexagon.x( - amplitude * Math.sin((new Date().getTime() * 2 * Math.PI) / period) + - centerX - ); - }, layer); - - anim.start(); - }); - - // ====================================================== - it('rotation animation', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var rect; - - for (var n = 0; n < 100; n++) { - rect = new Konva.Rect({ - x: Math.random() * 400, - y: Math.random() * 400, - width: 100, - height: 50, - fill: 'red', - stroke: 'black', - strokeWidth: 4, - }); - - layer.add(rect); - } - - stage.add(layer); - - var velocity = 360; // 1 rev per second - - var anim = new Konva.Animation(function (frame) { - layer - .find('Rect') - .forEach((rect) => rect.rotate((velocity * frame.timeDiff) / 1000)); - }, layer); - - anim.start(); - }); - - // ====================================================== - it('tween node', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var rect = new Konva.Rect({ - x: 100, - y: 100, - width: 100, - height: 50, - fill: 'green', - stroke: 'black', - strokeWidth: 2, - opacity: 0.2, - }); - - layer.add(rect); - stage.add(layer); - - var tween = new Konva.Tween({ - node: rect, - duration: 1, - x: 400, - y: 30, - rotation: 90, - opacity: 1, - strokeWidth: 6, - scaleX: 1.5, - }); - - tween.play(); - }); - - // ====================================================== - it('tween spline', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var spline = new Konva.Line({ - points: [73, 160, 340, 23, 500, 109, 300, 109], - stroke: 'blue', - strokeWidth: 10, - lineCap: 'round', - lineJoin: 'round', - draggable: true, - tension: 1, - }); - - layer.add(spline); - stage.add(layer); - - var tween = new Konva.Tween({ - node: spline, - duration: 1, - //x: 100, - - points: [200, 160, 200, 23, 500, 109, 100, 10], - easing: Konva.Easings.BackEaseOut, - yoyo: false, - }); - - // stage.getContent().addEventListener('mouseover', function() { - // tween.play(); - // }); - - // stage.getContent().addEventListener('mouseout', function() { - // tween.reverse(); - // }); - - tween.play(); - }); - - // ====================================================== - it('blur and tween spline', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var spline = new Konva.Line({ - points: [73, 160, 340, 23, 500, 109, 300, 109], - stroke: 'blue', - strokeWidth: 10, - lineCap: 'round', - lineJoin: 'round', - draggable: true, - tension: 1, - }); - - layer.add(spline); - stage.add(layer); - - spline.cache({ - width: stage.width(), - height: stage.height(), - }); - - spline.filters([Konva.Filters.Blur]).blurRadius(40); - layer.draw(); - - layer.on('beforeDraw', function () { - spline.cache({ - width: stage.width(), - height: stage.height(), - }); - }); - - var tween = new Konva.Tween({ - node: spline, - duration: 2, - //x: 100, - - points: [200, 160, 200, 23, 500, 109, 100, 10], - blurRadius: 0, - easing: Konva.Easings.BackEaseOut, - yoyo: false, - }); - - // stage.getContent().addEventListener('mouseover', function() { - // tween.play(); - // }); - - // stage.getContent().addEventListener('mouseout', function() { - // tween.reverse(); - // }); - - tween.play(); - }); - - it('Make sure that all texts are inside rectangles.', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var text = new Konva.Text({ - fontSize: 50, - y: 5, - x: 25, - fill: 'black', - text: 'text', - }); - var params = text.getSelfRect(); - var rect = new Konva.Rect({ - x: text.x() + params.x, - y: text.y() + params.y, - width: params.width, - height: params.height, - stroke: 'black', - }); - layer.add(rect, text); - - text = new Konva.Text({ - fontSize: 40, - y: 40, - x: 150, - fill: 'black', - text: 'Hello\nWorld! How Are you?', - align: 'center', - }); - params = text.getSelfRect(); - rect = new Konva.Rect({ - x: text.x() + params.x, - y: text.y() + params.y, - width: params.width, - height: params.height, - stroke: 'black', - }); - layer.add(rect, text); - - stage.add(layer); - }); - - it('change hit graph ratio', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 50, - stroke: 'black', - fill: 'red', - strokeWidth: 5, - draggable: true, - }); - - var text = new Konva.Text({ - text: 'click on circle to decrease hit grpah retion', - }); - - layer.add(circle, text); - stage.add(layer); - showHit(layer); - - circle.on('mouseenter', function () { - document.body.style.cursor = 'pointer'; - }); - - circle.on('mouseleave', function () { - document.body.style.cursor = 'default'; - }); - - circle.on('click', function () { - var ratio = layer.getHitCanvas().getPixelRatio() * 0.8; - console.log('new ratio', ratio); - layer.getHitCanvas().setPixelRatio(ratio); - layer.draw(); - }); - }); - - it('tween color', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - - var circle = new Konva.Circle({ - x: 100, - y: stage.height() / 2, - radius: 70, - fill: 'red', - stroke: 'blue', - strokeWidth: 4, - shadowOffx: 10, - shadowOffsetY: 10, - shadowColor: 'black', - }); - - var text = new Konva.Text({ - text: 'click on circle to start tween', - }); - - layer.add(circle, text); - stage.add(layer); - - circle.on('click', function () { - var tween = new Konva.Tween({ - node: circle, - duration: 1, - fill: Konva.Util.getRandomColor(), - stroke: Konva.Util.getRandomColor(), - shadowColor: Konva.Util.getRandomColor(), - }); - tween.play(); - }); - }); - - // ====================================================== - it('create image hit region with pixelRatio, look at hit, test hit with mouseover', function (done) { - var imageObj = new Image(); - - Konva.pixelRatio = 2; - var stage = addStage(); - var layer = new Konva.Layer(); - - imageObj.onload = function () { - var lion = new Konva.Image({ - x: 200, - y: 40, - image: imageObj, - draggable: true, - }); - - layer.add(lion); - - stage.add(layer); - - lion.cache(); - lion.drawHitFromCache(); - layer.draw(); - - lion.on('mouseenter', function () { - document.body.style.cursor = 'pointer'; - }); - - lion.on('mouseleave', function () { - document.body.style.cursor = 'default'; - }); - - Konva.pixelRatio = undefined; - done(); - }; - imageObj.src = 'assets/lion.png'; - - showHit(layer); - }); - - // ====================================================== - it('image hit region with alpha threshold, mouseover circle', function (done) { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var group = new Konva.Group(); - - var circle = new Konva.Circle({ - x: 50, - y: 50, - fill: 'red', - radius: 40, - }); - var rect = new Konva.Rect({ - width: 100, - height: 100, - fill: 'green', - opacity: 0.5, - }); - group.add(rect, circle); - - group.toImage({ - width: 100, - height: 100, - callback: function (img) { - var image = new Konva.Image({ - image: img, - }); - image.cache(); - image.drawHitFromCache(200); - layer.add(image); - layer.draw(); - var shape = layer.getIntersection({ - x: 5, - y: 5, - }); - - assert.equal(!!shape, false, 'shape should not be detected'); - - image.on('mouseenter', function () { - document.body.style.cursor = 'pointer'; - }); - - image.on('mouseleave', function () { - document.body.style.cursor = 'default'; - }); - done(); - }, - }); - - showHit(layer); - }); -}); diff --git a/test/manual/Mask-test.ts b/test/manual/Mask-test.ts deleted file mode 100644 index 870c4f31f..000000000 --- a/test/manual/Mask-test.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { addStage, Konva, loadImage } from '../unit/test-utils'; - -describe('Mask', function () { - // ====================================================== - it('basic', function (done) { - var stage = addStage(); - - loadImage('darth-vader.jpg', (imageObj) => { - var layer = new Konva.Layer({ - throttle: 999, - }); - var bamoon = new Konva.Image({ - x: 0, - y: 0, - image: imageObj, - draggable: true, - }), - filtered = new Konva.Image({ - x: 300, - y: 0, - image: imageObj, - draggable: true, - }); - - layer.add(bamoon); - layer.add(filtered); - stage.add(layer); - - filtered.cache(); - filtered.filters([Konva.Filters.Mask]); - filtered.threshold(10); - - layer.draw(); - - done(); - }); - }); -}); diff --git a/test/manual/Noise-test.ts b/test/manual/Noise-test.ts deleted file mode 100644 index c910a640d..000000000 --- a/test/manual/Noise-test.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { assert } from 'chai'; - -import { addStage, Konva, loadImage } from '../unit/test-utils'; - -describe('Noise', function () { - // ====================================================== - it('noise tween', function (done) { - var stage = addStage(); - - loadImage('darth-vader.jpg', (imageObj) => { - var layer = new Konva.Layer(); - const darth = new Konva.Image({ - x: 10, - y: 10, - image: imageObj, - draggable: true, - }); - - layer.add(darth); - stage.add(layer); - - darth.cache(); - darth.filters([Konva.Filters.Noise]); - darth.noise(1); - layer.draw(); - - var tween = new Konva.Tween({ - node: darth, - duration: 5.0, - noise: 0, - easing: Konva.Easings.EaseInOut, - }); - - darth.on('mouseover', function () { - tween.play(); - }); - - darth.on('mouseout', function () { - tween.reverse(); - }); - - done(); - }); - }); -}); diff --git a/test/manual/Pixelate-test.ts b/test/manual/Pixelate-test.ts deleted file mode 100644 index a8a9dc416..000000000 --- a/test/manual/Pixelate-test.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { addStage, Konva, loadImage } from '../unit/test-utils'; -import { cloneAndCompareLayer } from '../unit/test-utils'; - -describe('Pixelate', function () { - // ====================================================== - it('tween pixelate', function (done) { - var stage = addStage(); - - loadImage('darth-vader.jpg', (imageObj) => { - var layer = new Konva.Layer(); - const lion = new Konva.Image({ - x: 10, - y: 10, - image: imageObj, - draggable: true, - }); - - layer.add(lion); - stage.add(layer); - - lion.cache(); - lion.filters([Konva.Filters.Pixelate]); - lion.pixelSize(16); - layer.draw(); - - var tween = new Konva.Tween({ - node: lion, - duration: 3.0, - pixelSize: 1, - easing: Konva.Easings.EaseInOut, - }); - - lion.on('mouseover', function () { - tween.play(); - }); - - lion.on('mouseout', function () { - tween.reverse(); - }); - - done(); - }); - }); - - it('make sure we have no extra transparent pixels', function (done) { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - Konva.Image.fromURL( - 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGUAAABmCAYAAADS6F9hAAAAAXNSR0IArs4c6QAAAXJJREFUeF7t1cEJADAMw8B2/6Fd6BT3UCYQEiZ3205HGbhFoXp8mKJ4TYoCNilKUUQDIFM/pSigARCppRQFNAAitZSigAZApJZSFNAAiNRSigIaAJFaSlFAAyBSSykKaABEailFAQ2ASC2lKKABEKmlFAU0ACK1lKKABkCkllIU0ACI1FKKAhoAkVpKUUADIFJLKQpoAERqKUUBDYBILaUooAEQqaUUBTQAIrWUooAGQKSWUhTQAIjUUooCGgCRWkpRQAMgUkspCmgARGopRQENgEgtpSigARCppRQFNAAitZSigAZApJZSFNAAiNRSigIaAJFaSlFAAyBSSykKaABEailFAQ2ASC2lKKABEKmlFAU0ACK1lKKABkCkllIU0ACI1FKKAhoAkVpKUUADIFJLKQpoAERqKUUBDYBILaUooAEQqaUUBTQAIrWUooAGQKSWUhTQAIjUUooCGgCRWkpRQAMgUkspCmgARGopRQENgEgPgGOW3jCsp3sAAAAASUVORK5CYII=', - function (image) { - layer.add(image); - - image.cache(); - image.filters([Konva.Filters.Pixelate]); - image.pixelSize(4); - layer.draw(); - cloneAndCompareLayer(layer); - - done(); - } - ); - }); -}); diff --git a/test/manual/Posterize-test.ts b/test/manual/Posterize-test.ts deleted file mode 100644 index e83f14d49..000000000 --- a/test/manual/Posterize-test.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { assert } from 'chai'; - -import { addStage, Konva, loadImage } from '../unit/test-utils'; - -describe('Posterize', function () { - // ====================================================== - it('on image tween', function (done) { - var stage = addStage(); - - loadImage('darth-vader.jpg', (imageObj) => { - var layer = new Konva.Layer(); - const darth = new Konva.Image({ - x: 10, - y: 10, - image: imageObj, - draggable: true, - }); - - layer.add(darth); - stage.add(layer); - - darth.cache(); - darth.filters([Konva.Filters.Posterize]); - darth.levels(0.2); - layer.draw(); - - var tween = new Konva.Tween({ - node: darth, - duration: 1.0, - levels: 0, - easing: Konva.Easings.Linear, - }); - - darth.on('mouseover', function () { - tween.play(); - }); - - darth.on('mouseout', function () { - tween.reverse(); - }); - - done(); - }); - }); -}); diff --git a/test/manual/RGB-test.ts b/test/manual/RGB-test.ts deleted file mode 100644 index 2fef7e740..000000000 --- a/test/manual/RGB-test.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { assert } from 'chai'; - -import { addStage, Konva, loadImage } from '../unit/test-utils'; - -describe('RGB', function () { - // ====================================================== - it('colorize basic', function (done) { - var stage = addStage(); - - loadImage('darth-vader.jpg', (imageObj) => { - var layer = new Konva.Layer(); - const darth = new Konva.Image({ - x: 10, - y: 10, - image: imageObj, - draggable: true, - }); - - layer.add(darth); - stage.add(layer); - - darth.cache(); - darth.filters([Konva.Filters.RGB]); - darth.red(255).green(0).blue(128); - layer.draw(); - - // Assert fails even though '[255,0,128] = [255,0,128]' - // assert.deepEqual(darth.getFilterColorizeColor(), [255,0,128]); - - done(); - }); - }); - - // ====================================================== - it('colorize crop', function (done) { - var stage = addStage(); - - loadImage('darth-vader.jpg', (imageObj) => { - var layer = new Konva.Layer(); - const darth = new Konva.Image({ - x: 10, - y: 10, - image: imageObj, - crop: { x: 128, y: 48, width: 256, height: 128 }, - draggable: true, - }); - - layer.add(darth); - stage.add(layer); - - darth.cache(); - darth.filters([Konva.Filters.RGB]); - darth.red(0).green(255).blue(0); - layer.draw(); - - // assert.deepEqual(darth.getFilterColorizeColor(), [0,255,0]); - - done(); - }); - }); - - // ====================================================== - it('colorize transparancy', function (done) { - loadImage('lion.png', (imageObj) => { - var stage = addStage(); - var layer = new Konva.Layer(); - - var colors = [ - [255, 0, 0], - [255, 128, 0], - [255, 255, 0], - [0, 255, 0], - [0, 255, 128], - [0, 255, 255], - [0, 0, 255], - [128, 0, 255], - [255, 0, 255], - [0, 0, 0], - [128, 128, 128], - [255, 255, 255], - ]; - var i, - l = colors.length; - var nAdded = 0; - for (i = 0; i < l; i += 1) { - const color = colors[i]; - const x = -64 + (i / l) * stage.width(); - var darth = new Konva.Image({ - x: x, - y: 32, - image: imageObj, - draggable: true, - }); - layer.add(darth); - - darth.cache(); - darth.filters([Konva.Filters.RGB]); - darth.red(color[0]).green(color[1]).blue(color[2]); - - nAdded += 1; - if (nAdded >= l) { - stage.add(layer); - layer.draw(); - done(); - } - } - }); - }); -}); diff --git a/test/manual/RGBA-test.ts b/test/manual/RGBA-test.ts deleted file mode 100644 index 37c671336..000000000 --- a/test/manual/RGBA-test.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { assert } from 'chai'; - -import { addStage, Konva, loadImage } from '../unit/test-utils'; - -describe('RGBA', function () { - // ====================================================== - it.skip('colorize basic', function (done) { - var data = [ - { - color: '#2a6511', - filter: [242, 193, 168, 0.33], - result: [108, 131, 67, 255], - }, - { - color: '#e4d526', - filter: [175, 98, 37, 0.79], - result: [186, 122, 37, 255], - }, - ]; - - var stage = new Konva.Stage({ - container: 'konva-container', - width: data.length, - height: 1, - }); - - var layer = new Konva.Layer({ - id: 'layer', - }); - - for (var i = 0; i < data.length; i += 1) { - var d = data[i]; - - var rect = new Konva.Rect({ - x: i, - y: 0, - width: 1, - height: 1, - fill: d.color, - }); - - rect.cache(); - - rect.red(d.filter[0]); - rect.green(d.filter[1]); - rect.blue(d.filter[2]); - rect.alpha(d.filter[3]); - - rect.filters([Konva.Filters.RGBA]); - layer.add(rect); - } - - stage.add(layer); - layer.batchDraw(); - - var context = layer.getCanvas().getContext(); - - var imageDataToArray = function (x) { - var imageData = context.getImageData(x, 0, 1, 1).data; - - return [imageData['0'], imageData['1'], imageData['2'], imageData['3']]; - }; - - var a0 = imageDataToArray(0); - var a1 = imageDataToArray(1); - - assert.deepEqual(a0, data[0].result); - assert.deepEqual(a1, data[1].result); - - done(); - }); -}); diff --git a/test/manual/Sepia-test.ts b/test/manual/Sepia-test.ts deleted file mode 100644 index e22a132a8..000000000 --- a/test/manual/Sepia-test.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { assert } from 'chai'; - -import { addStage, Konva, loadImage } from '../unit/test-utils'; - -describe('Filter Sepia', function () { - // ====================================================== - it('basic', function (done) { - var stage = addStage(); - - loadImage('darth-vader.jpg', (imageObj) => { - var layer = new Konva.Layer(); - const darth = new Konva.Image({ - x: 10, - y: 10, - image: imageObj, - draggable: true, - }); - - layer.add(darth); - stage.add(layer); - - darth.cache(); - darth.filters([Konva.Filters.Sepia]); - layer.draw(); - - done(); - }); - }); - - // ====================================================== - it('crop', function (done) { - var stage = addStage(); - - loadImage('darth-vader.jpg', (imageObj) => { - var layer = new Konva.Layer(); - const darth = new Konva.Image({ - x: 10, - y: 10, - image: imageObj, - crop: { x: 128, y: 48, width: 256, height: 128 }, - draggable: true, - }); - - layer.add(darth); - stage.add(layer); - - darth.cache(); - darth.filters([Konva.Filters.Sepia]); - layer.draw(); - - done(); - }); - }); - - // ====================================================== - it('with transparency', function (done) { - var stage = addStage(); - - loadImage('darth-vader.jpg', (imageObj) => { - var layer = new Konva.Layer(); - const darth = new Konva.Image({ - x: 10, - y: 10, - image: imageObj, - draggable: true, - }); - - layer.add(darth); - stage.add(layer); - - darth.cache(); - darth.filters([Konva.Filters.Sepia]); - layer.draw(); - - done(); - }); - }); -}); diff --git a/test/manual/Solarize-test.ts b/test/manual/Solarize-test.ts deleted file mode 100644 index a32faec00..000000000 --- a/test/manual/Solarize-test.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { assert } from 'chai'; - -import { addStage, Konva, loadImage } from '../unit/test-utils'; - -describe('Solarize', function () { - // ====================================================== - it('solarize', function (done) { - var stage = addStage(); - - loadImage('darth-vader.jpg', (imageObj) => { - var layer = new Konva.Layer(); - const darth = new Konva.Image({ - x: 10, - y: 10, - image: imageObj, - draggable: true, - }); - - layer.add(darth); - stage.add(layer); - darth.cache(); - darth.filters([Konva.Filters.Solarize]); - - layer.draw(); - - done(); - }); - //imageObj.src = 'assets/lion.png'; - }); -}); diff --git a/test/manual/Threshold-test.ts b/test/manual/Threshold-test.ts deleted file mode 100644 index df1d0701f..000000000 --- a/test/manual/Threshold-test.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { assert } from 'chai'; - -import { addStage, Konva, loadImage } from '../unit/test-utils'; - -describe('Threshold', function () { - // ====================================================== - it('image tween', function (done) { - var stage = addStage(); - - loadImage('darth-vader.jpg', (imageObj) => { - var layer = new Konva.Layer(); - const darth = new Konva.Image({ - x: 10, - y: 10, - image: imageObj, - draggable: true, - }); - - layer.add(darth); - stage.add(layer); - - darth.cache(); - darth.filters([Konva.Filters.Threshold]); - darth.threshold(1); - layer.draw(); - - var tween = new Konva.Tween({ - node: darth, - duration: 5.0, - threshold: 0, - easing: Konva.Easings.EaseInOut, - }); - - darth.on('mouseover', function () { - tween.play(); - }); - - darth.on('mouseout', function () { - tween.reverse(); - }); - - done(); - }); - }); -}); diff --git a/test/node-global-setup.mjs b/test/node-global-setup.mjs deleted file mode 100644 index b54de7247..000000000 --- a/test/node-global-setup.mjs +++ /dev/null @@ -1,11 +0,0 @@ -export function mochaGlobalSetup() { - globalThis.Path2D ??= class Path2D { - constructor(path) { - this.path = path - } - - get [Symbol.toStringTag]() { - return `Path2D`; - } - } -} diff --git a/test/package.json b/test/package.json deleted file mode 100644 index 5bbefffba..000000000 --- a/test/package.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "type": "commonjs" -} diff --git a/test/performance/bunnies_native.html b/test/performance/bunnies_native.html deleted file mode 100644 index 35f63484d..000000000 --- a/test/performance/bunnies_native.html +++ /dev/null @@ -1,155 +0,0 @@ - - - - - - - - - - - diff --git a/test/performance/creating_elements.html b/test/performance/creating_elements.html deleted file mode 100644 index 847171bf4..000000000 --- a/test/performance/creating_elements.html +++ /dev/null @@ -1,107 +0,0 @@ - - - - - - -
    - - - - - - diff --git a/test/performance/jump-shape.html b/test/performance/jump-shape.html deleted file mode 100644 index e9b25223d..000000000 --- a/test/performance/jump-shape.html +++ /dev/null @@ -1,166 +0,0 @@ - - - - - - -
    - - - - - - diff --git a/test/runner.js b/test/runner.js deleted file mode 100644 index c432fe1e6..000000000 --- a/test/runner.js +++ /dev/null @@ -1,511 +0,0 @@ -mocha.ui('tdd'); -mocha.setup('bdd'); -var assert = chai.assert, - konvaContainer = document.getElementById('konva-container'), - origAssertEqual = assert.equal, - origAssert = assert, - origNotEqual = assert.notEqual, - origDeepEqual = assert.deepEqual, - assertionCount = 0, - assertions = document.createElement('em'); - -window.requestAnimFrame = (function (callback) { - return ( - window.requestAnimationFrame || - window.webkitRequestAnimationFrame || - window.mozRequestAnimationFrame || - window.oRequestAnimationFrame || - window.msRequestAnimationFrame || - function (callback) { - window.setTimeout(callback, 1000 / 30); - } - ); -})(); - -function init() { - // assert extenders so that we can count assertions - assert = function () { - origAssert.apply(this, arguments); - assertions.innerHTML = ++assertionCount; - }; - assert.equal = function () { - origAssertEqual.apply(this, arguments); - assertions.innerHTML = ++assertionCount; - }; - assert.notEqual = function () { - origNotEqual.apply(this, arguments); - assertions.innerHTML = ++assertionCount; - }; - - assert.deepEqual = function () { - origDeepEqual.apply(this, arguments); - assertions.innerHTML = ++assertionCount; - }; - - window.onload = function () { - var mochaStats = document.getElementById('mocha-stats'); - - if (mochaStats) { - var li = document.createElement('li'); - var anchor = document.createElement('a'); - - anchor.href = '#'; - anchor.innerHTML = 'assertions:'; - assertions.innerHTML = 0; - - li.appendChild(anchor); - li.appendChild(assertions); - mochaStats.appendChild(li); - } - }; - - //addStats(); -} - -Konva.enableTrace = true; -Konva.showWarnings = true; -//Konva.pixelRatio = 2; -window.isPhantomJS = /PhantomJS/.test(window.navigator.userAgent); - -function addStats() { - stats = new Stats(); - stats.setMode(0); - stats.domElement.style.position = 'fixed'; - stats.domElement.style.left = '0px'; - stats.domElement.style.top = '0px'; - document.getElementsByTagName('body')[0].appendChild(stats.domElement); - - function animate(lastTime) { - stats.begin(); - - requestAnimFrame(function () { - stats.end(); - animate(lastTime); - }); - } - - animate(); -} - -function addStage(attrs) { - var container = document.createElement('div'); - const props = Konva.Util._assign( - { - container: container, - width: 578, - height: 200, - }, - attrs - ); - - var stage = new Konva.Stage(props); - - konvaContainer.appendChild(container); - return stage; -} - -function createCanvas() { - var canvas = document.createElement('canvas'); - var ratio = Konva.pixelRatio || window.devicePixelRatio; - canvas.width = 578 * ratio; - canvas.height = 200 * ratio; - canvas.getContext('2d').scale(ratio, ratio); - canvas.ratio = ratio; - return canvas; -} - -function get(element, content) { - element = document.createElement(element); - if (element && content) { - element.innerHTML = content; - } - return element; -} -function compareCanvases(canvas1, canvas2, tol) { - // don't test in PhantomJS as it use old chrome engine - // it it has opacity + shadow bug - var equal = imagediff.equal(canvas1, canvas2, tol); - if (!equal) { - var div = get('div'), - b = get('div', '
    Expected:
    '), - c = get('div', '
    Diff:
    '), - diff = imagediff.diff(canvas1, canvas2), - diffCanvas = get('canvas'), - context; - - diffCanvas.height = diff.height; - diffCanvas.width = diff.width; - - div.style.overflow = 'hidden'; - b.style.float = 'left'; - c.style.float = 'left'; - - canvas2.style.position = ''; - canvas2.style.display = ''; - - context = diffCanvas.getContext('2d'); - context.putImageData(diff, 0, 0); - - b.appendChild(canvas2); - c.appendChild(diffCanvas); - - var base64 = diffCanvas.toDataURL(); - console.error('Diff image:'); - console.error(base64); - - div.appendChild(b); - div.appendChild(c); - konvaContainer.appendChild(div); - } - assert.equal( - equal, - true, - 'Result from Konva is different with canvas result' - ); -} - -function compareLayerAndCanvas(layer, canvas, tol) { - compareCanvases(layer.getCanvas()._canvas, canvas, tol); -} - -function compareLayers(layer1, layer2, tol) { - compareLayerAndCanvas(layer1, layer2.getCanvas()._canvas, tol); -} - -function cloneAndCompareLayer(layer, tol) { - var layer2 = layer.clone(); - layer.getStage().add(layer2); - layer2.hide(); - compareLayers(layer, layer2, tol); -} - -function cloneAndCompareLayerWithHit(layer, tol) { - var layer2 = layer.clone(); - layer.getStage().add(layer2); - layer2.hide(); - compareLayers(layer, layer2, tol); - compareCanvases( - layer.getHitCanvas()._canvas, - layer2.getHitCanvas()._canvas, - tol - ); -} - -function compareSceneAndHit(layer) { - compareLayerAndCanvas(layer, layer.getHitCanvas()._canvas, 254); -} - -function addContainer() { - var container = document.createElement('div'); - - konvaContainer.appendChild(container); - - return container; -} - -function showCanvas(canvas) { - canvas.style.position = 'relative'; - - konvaContainer.appendChild(canvas); -} -function showHit(layer) { - var canvas = layer.hitCanvas._canvas; - canvas.style.position = 'relative'; - - konvaContainer.appendChild(canvas); -} - -beforeEach(function () { - var title = document.createElement('h2'), - test = this.currentTest; - - title.innerHTML = test.parent.title + ' - ' + test.title; - title.className = 'konva-title'; - konvaContainer.appendChild(title); - - // resets - Konva.inDblClickWindow = false; - Konva.DD && (Konva.DD.isDragging = false); - Konva.DD && (Konva.DD.node = undefined); - - if ( - !( - this.currentTest.body.indexOf('assert') !== -1 || - this.currentTest.body.toLowerCase().indexOf('compare') !== -1 - ) - ) { - console.error(this.currentTest.title); - } -}); - -Konva.UA.mobile = false; - -afterEach(function () { - var isFailed = this.currentTest.state == 'failed'; - var isManual = this.currentTest.parent.title === 'Manual'; - - Konva.stages.forEach(function (stage) { - clearTimeout(stage.dblTimeout); - }); - - if (!isFailed && !isManual) { - Konva.stages.forEach(function (stage) { - stage.destroy(); - }); - if (Konva.DD._dragElements.size) { - throw 'Why drag elements are not cleaned?'; - } - } -}); - -Konva.Stage.prototype.simulateMouseDown = function (pos) { - var top = this.content.getBoundingClientRect().top; - - this._mousedown({ - clientX: pos.x, - clientY: pos.y + top, - button: pos.button || 0, - }); -}; - -Konva.Stage.prototype.simulateMouseMove = function (pos) { - var top = this.content.getBoundingClientRect().top; - - var evt = { - clientX: pos.x, - clientY: pos.y + top, - button: pos.button || 0, - }; - - this._mousemove(evt); - Konva.DD._drag(evt); -}; - -Konva.Stage.prototype.simulateMouseUp = function (pos) { - var top = this.content.getBoundingClientRect().top; - - var evt = { - clientX: pos.x, - clientY: pos.y + top, - button: pos.button || 0, - }; - - Konva.DD._endDragBefore(evt); - this._mouseup(evt); - Konva.DD._endDragAfter(evt); -}; - -Konva.Stage.prototype.simulateTouchStart = function (pos, changed) { - var top = this.content.getBoundingClientRect().top; - - var touches; - var changedTouches; - if (Array.isArray(pos)) { - touches = pos.map(function (touch) { - return { - identifier: touch.id, - clientX: touch.x, - clientY: touch.y + top, - }; - }); - changedTouches = (changed || pos).map(function (touch) { - return { - identifier: touch.id, - clientX: touch.x, - clientY: touch.y + top, - }; - }); - } else { - changedTouches = touches = [ - { - clientX: pos.x, - clientY: pos.y + top, - id: 0, - }, - ]; - } - var evt = { - touches: touches, - changedTouches: changedTouches, - }; - - this._touchstart(evt); -}; - -Konva.Stage.prototype.simulateTouchMove = function (pos, changed) { - var top = this.content.getBoundingClientRect().top; - - var touches; - var changedTouches; - if (Array.isArray(pos)) { - touches = pos.map(function (touch) { - return { - identifier: touch.id, - clientX: touch.x, - clientY: touch.y + top, - }; - }); - changedTouches = (changed || pos).map(function (touch) { - return { - identifier: touch.id, - clientX: touch.x, - clientY: touch.y + top, - }; - }); - } else { - changedTouches = touches = [ - { - clientX: pos.x, - clientY: pos.y + top, - id: 0, - }, - ]; - } - var evt = { - touches: touches, - changedTouches: changedTouches, - }; - - this._touchmove(evt); - Konva.DD._drag(evt); -}; - -Konva.Stage.prototype.simulateTouchEnd = function (pos, changed) { - var top = this.content.getBoundingClientRect().top; - - var touches; - var changedTouches; - if (Array.isArray(pos)) { - touches = pos.map(function (touch) { - return { - identifier: touch.id, - clientX: touch.x, - clientY: touch.y + top, - }; - }); - changedTouches = (changed || pos).map(function (touch) { - return { - identifier: touch.id, - clientX: touch.x, - clientY: touch.y + top, - }; - }); - } else { - changedTouches = touches = [ - { - clientX: pos.x, - clientY: pos.y + top, - id: 0, - }, - ]; - } - var evt = { - touches: touches, - changedTouches: changedTouches, - }; - - Konva.DD._endDragBefore(evt); - this._touchend(evt); - Konva.DD._endDragAfter(evt); -}; - -Konva.Stage.prototype.simulatePointerDown = function (pos) { - var top = this.content.getBoundingClientRect().top; - - this._mousedown({ - clientX: pos.x, - clientY: pos.y + top, - button: pos.button || 0, - pointerId: pos.pointerId || 1, - }); -}; - -Konva.Stage.prototype.simulatePointerMove = function (pos) { - var top = this.content.getBoundingClientRect().top; - - var evt = { - clientX: pos.x, - clientY: pos.y + top, - button: pos.button || 0, - pointerId: pos.pointerId || 1, - }; - - this._mousemove(evt); - Konva.DD._drag(evt); -}; - -Konva.Stage.prototype.simulatePointerUp = function (pos) { - var top = this.content.getBoundingClientRect().top; - - var evt = { - clientX: pos.x, - clientY: pos.y + top, - button: pos.button || 0, - pointerId: pos.pointerId || 1, - }; - - Konva.DD._endDragBefore(evt); - this._mouseup(evt); - Konva.DD._endDragAfter(evt); -}; - -init(); - -// polyfills -if (!Array.prototype.find) { - Object.defineProperty(Array.prototype, 'find', { - value: function (predicate) { - // 1. Let O be ? ToObject(this value). - if (this == null) { - throw TypeError('"this" is null or not defined'); - } - - var o = Object(this); - - // 2. Let len be ? ToLength(? Get(O, "length")). - var len = o.length >>> 0; - - // 3. If IsCallable(predicate) is false, throw a TypeError exception. - if (typeof predicate !== 'function') { - throw TypeError('predicate must be a function'); - } - - // 4. If thisArg was supplied, let T be thisArg; else let T be undefined. - var thisArg = arguments[1]; - - // 5. Let k be 0. - var k = 0; - - // 6. Repeat, while k < len - while (k < len) { - // a. Let Pk be ! ToString(k). - // b. Let kValue be ? Get(O, Pk). - // c. Let testResult be ToBoolean(? Call(predicate, T, « kValue, k, O »)). - // d. If testResult is true, return kValue. - var kValue = o[k]; - if (predicate.call(thisArg, kValue, k, o)) { - return kValue; - } - // e. Increase k by 1. - k++; - } - - // 7. Return undefined. - return undefined; - }, - configurable: true, - writable: true, - }); -} - -String.prototype.trimRight = - String.prototype.trimRight || - function polyfill() { - return this.replace(/[\s\xa0]+$/, ''); - }; - -String.prototype.trimLeft = - String.prototype.trimLeft || - function polyfill() { - return this.replace(/^\s+/, ''); - }; diff --git a/test/sandbox.html b/test/sandbox.html deleted file mode 100644 index 2b17ce672..000000000 --- a/test/sandbox.html +++ /dev/null @@ -1,71 +0,0 @@ - - - - - KonvaJS Sandbox - - - - - - - - - - - -
    - - - - diff --git a/test/text-paths.html b/test/text-paths.html deleted file mode 100644 index f329445f9..000000000 --- a/test/text-paths.html +++ /dev/null @@ -1,419 +0,0 @@ - - - - - KonvaJS text paths - - - - - - - -
    - - - - diff --git a/test/tsconfig.json b/test/tsconfig.json deleted file mode 100644 index ce747036f..000000000 --- a/test/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2015", - "noEmitOnError": true, - "moduleResolution": "node", - "lib": ["ES2015", "dom"], - "module": "CommonJS" - }, - "include": ["../src/**/*.ts"] -} diff --git a/test/unit-tests.html b/test/unit-tests.html deleted file mode 100644 index 1262ea64f..000000000 --- a/test/unit-tests.html +++ /dev/null @@ -1,80 +0,0 @@ - - -
    - - - - - - - - -
    - - - - - diff --git a/test/unit/Animation-test.ts b/test/unit/Animation-test.ts deleted file mode 100644 index 856632e20..000000000 --- a/test/unit/Animation-test.ts +++ /dev/null @@ -1,130 +0,0 @@ -import { assert } from 'chai'; - -import { addStage, Konva } from './test-utils'; - -describe('Animation', function () { - // ====================================================== - it('test start and stop', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var rect = new Konva.Rect({ - x: 200, - y: 100, - width: 100, - height: 50, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - }); - - layer.add(rect); - stage.add(layer); - - var amplitude = 150; - var period = 1000; - // in ms - var centerX = stage.width() / 2 - 100 / 2; - - var anim = new Konva.Animation(function (frame) { - rect.x( - amplitude * Math.sin((frame.time * 2 * Math.PI) / period) + centerX - ); - }, layer); - var a = Konva.Animation.animations; - var startLen = a.length; - - assert.equal(a.length, startLen, '1should be no animations running'); - - anim.start(); - assert.equal(a.length, startLen + 1, '2should be 1 animation running'); - - anim.stop(); - assert.equal(a.length, startLen, '3should be no animations running'); - - anim.start(); - assert.equal(a.length, startLen + 1, '4should be 1 animation running'); - - anim.start(); - assert.equal(a.length, startLen + 1, '5should be 1 animation runningg'); - - anim.stop(); - assert.equal(a.length, startLen, '6should be no animations running'); - - anim.stop(); - assert.equal(a.length, startLen, '7should be no animations running'); - }); - - // ====================================================== - it('layer batch draw', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var rect = new Konva.Rect({ - x: 200, - y: 100, - width: 100, - height: 50, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - }); - - layer.add(rect); - stage.add(layer); - - var draws = 0; - - layer.on('draw', function () { - //console.log('draw') - draws++; - }); - - layer.draw(); - layer.draw(); - layer.draw(); - - assert.equal(draws, 3, 'draw count should be 3'); - - layer.batchDraw(); - layer.batchDraw(); - layer.batchDraw(); - - assert.notEqual(draws, 6, 'should not be 6 draws'); - }); - - // ====================================================== - it('stage batch draw', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var rect = new Konva.Rect({ - x: 200, - y: 100, - width: 100, - height: 50, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - }); - - layer.add(rect); - stage.add(layer); - - var draws = 0; - - layer.on('draw', function () { - //console.log('draw') - draws++; - }); - - stage.draw(); - stage.draw(); - stage.draw(); - - assert.equal(draws, 3, 'draw count should be 3'); - - stage.batchDraw(); - stage.batchDraw(); - stage.batchDraw(); - - assert.notEqual(draws, 6, 'should not be 6 draws'); - }); -}); diff --git a/test/unit/Arc-test.ts b/test/unit/Arc-test.ts deleted file mode 100644 index 6fe18537f..000000000 --- a/test/unit/Arc-test.ts +++ /dev/null @@ -1,204 +0,0 @@ -import { assert } from 'chai'; - -import { - addStage, - Konva, - createCanvas, - compareLayerAndCanvas, - assertAlmostDeepEqual, -} from './test-utils'; - -describe('Arc', function () { - // ====================================================== - it('add arc', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var arc = new Konva.Arc({ - x: 100, - y: 100, - innerRadius: 50, - outerRadius: 80, - angle: 90, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myArc', - draggable: true, - }); - - layer.add(arc); - stage.add(layer); - - assert.equal(arc.getClassName(), 'Arc'); - - var trace = layer.getContext().getTrace(); - //console.log(trace); - assert.equal( - trace, - 'clearRect(0,0,578,200);save();transform(1,0,0,1,100,100);beginPath();arc(0,0,80,0,1.571,false);arc(0,0,50,1.571,0,true);closePath();fillStyle=green;fill();lineWidth=4;strokeStyle=black;stroke();restore();' - ); - }); - - // ====================================================== - it('attrs sync', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var arc = new Konva.Arc({ - x: 100, - y: 100, - innerRadius: 50, - outerRadius: 80, - angle: 90, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myArc', - draggable: true, - }); - - layer.add(arc); - stage.add(layer); - assert.equal(arc.getWidth(), 160); - assert.equal(arc.getHeight(), 160); - - arc.setWidth(100); - assert.equal(arc.outerRadius(), 50); - assert.equal(arc.getHeight(), 100); - - arc.setHeight(120); - assert.equal(arc.outerRadius(), 60); - assert.equal(arc.getHeight(), 120); - }); - - it('getSelfRect', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var arc = new Konva.Arc({ - x: 100, - y: 100, - innerRadius: 50, - outerRadius: 80, - angle: 90, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myArc', - draggable: true, - }); - - layer.add(arc); - stage.add(layer); - - assertAlmostDeepEqual(arc.getSelfRect(), { - x: 0, - y: 0, - width: 80, - height: 80, - }); - }); - - it('getSelfRect on clockwise', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var arc = new Konva.Arc({ - x: 100, - y: 100, - innerRadius: 50, - outerRadius: 80, - angle: 90, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myArc', - draggable: true, - clockwise: true, - }); - - layer.add(arc); - stage.add(layer); - - assertAlmostDeepEqual(arc.getSelfRect(), { - x: -80, - y: -80, - width: 160, - height: 160, - }); - }); - - it('getSelfRect on quarter clockwise arc bounds to the visible part', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var arc = new Konva.Arc({ - x: 100, - y: 100, - innerRadius: 50, - outerRadius: 80, - angle: 270, - strokeWidth: 4, - clockwise: true, - }); - - layer.add(arc); - stage.add(layer); - - assertAlmostDeepEqual(arc.getSelfRect(), { - x: 0, - y: -80, - width: 80, - height: 80, - }); - }); - - it('getSelfRect on small angle arc should bounds to inner radius', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var arc = new Konva.Arc({ - x: 100, - y: 100, - innerRadius: 50, - outerRadius: 80, - angle: 60, - strokeWidth: 4, - }); - - layer.add(arc); - stage.add(layer); - - assertAlmostDeepEqual(arc.getSelfRect(), { - x: 25, - y: 0, - width: 55, - height: 69.282032302755, - }); - }); - - it('cache', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var arc = new Konva.Arc({ - x: 100, - y: 100, - innerRadius: 50, - outerRadius: 80, - angle: 90, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - }); - - layer.add(arc); - stage.add(layer); - - var canvas = createCanvas(); - var context = canvas.getContext('2d'); - context.beginPath(); - context.arc(100, 100, 80, 0, Math.PI / 2, false); - context.arc(100, 100, 50, Math.PI / 2, 0, true); - context.closePath(); - context.fillStyle = 'green'; - context.fill(); - context.lineWidth = 4; - context.stroke(); - compareLayerAndCanvas(layer, canvas, 10); - }); -}); diff --git a/test/unit/Arrow-test.ts b/test/unit/Arrow-test.ts deleted file mode 100644 index ace482a61..000000000 --- a/test/unit/Arrow-test.ts +++ /dev/null @@ -1,261 +0,0 @@ -import { assert } from 'chai'; - -import { addStage, Konva, cloneAndCompareLayer } from './test-utils'; - -describe('Arrow', function () { - // ====================================================== - it('add arrow', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var arrow = new Konva.Arrow({ - points: [73, 160, 340, 23], - stroke: 'blue', - fill: 'blue', - strokeWidth: 1, - draggable: true, - tension: 0, - }); - - layer.add(arrow); - stage.add(layer); - - arrow.points([1, 2, 3, 4]); - assert.equal(arrow.points()[0], 1); - - arrow.points([5, 6, 7, 8]); - assert.equal(arrow.points()[0], 5); - arrow.points([73, 160, 340, 23, 50, 100, 80, 50]); - arrow.tension(0); - - arrow.pointerLength(15); - assert.equal(arrow.pointerLength(), 15); - - arrow.pointerWidth(15); - assert.equal(arrow.pointerWidth(), 15); - - assert.equal(arrow.getClassName(), 'Arrow'); - - layer.draw(); - }); - - it('do not draw dash for head', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var arrow = new Konva.Arrow({ - points: [50, 50, 100, 100], - stroke: 'red', - fill: 'blue', - strokeWidth: 5, - pointerWidth: 20, - pointerLength: 20, - dash: [5, 5], - }); - - layer.add(arrow); - stage.add(layer); - - var trace = layer.getContext().getTrace(); - - // console.log(trace); - - assert.equal( - trace, - 'clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);beginPath();moveTo(50,50);lineTo(100,100);setLineDash(5,5);lineDashOffset=0;lineWidth=5;strokeStyle=red;stroke();save();beginPath();translate(100,100);rotate(0.785);moveTo(0,0);lineTo(-20,10);lineTo(-20,-10);closePath();restore();setLineDash();fillStyle=blue;fill();lineWidth=5;strokeStyle=red;stroke();restore();' - ); - }); - - it('pointer on both directions', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var arrow = new Konva.Arrow({ - points: [50, 50, 100, 100], - stroke: 'red', - strokeWidth: 5, - pointerWidth: 20, - pointerLength: 20, - pointerAtBeginning: true, - pointerAtEnding: true, - opacity: 0.5, - }); - - layer.add(arrow); - stage.add(layer); - - var trace = layer.getContext().getTrace(); - - // console.log(trace); - - assert.equal( - trace, - 'clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);globalAlpha=0.5;beginPath();moveTo(50,50);lineTo(100,100);lineWidth=5;strokeStyle=red;stroke();save();beginPath();translate(100,100);rotate(0.785);moveTo(0,0);lineTo(-20,10);lineTo(-20,-10);closePath();restore();setLineDash();lineWidth=5;strokeStyle=red;stroke();save();beginPath();translate(50,50);rotate(3.927);moveTo(0,0);lineTo(-20,10);lineTo(-20,-10);closePath();restore();setLineDash();lineWidth=5;strokeStyle=red;stroke();restore();' - ); - }); - - it('dash checks', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - stage.add(layer); - - var width = stage.width(); - var height = stage.height(); - - // regular line - var arrow = new Konva.Arrow({ - x: stage.width() / 4, - y: stage.height() / 4, - points: [0, 0, width / 2, height / 2], - pointerLength: 20, - pointerWidth: 20, - fill: 'red', - stroke: 'red', - strokeWidth: 4, - dash: [10, 5], - }); - layer.add(arrow); - - // arrow with no end (like a simple line) - var arrowNoEnd = new Konva.Arrow({ - x: stage.width() / 4 + 50, - y: stage.height() / 4, - points: [0, 0, width / 2, height / 2], - pointerLength: 20, - pointerWidth: 20, - pointerAtEnding: false, - fill: 'blue', - stroke: 'blue', - strokeWidth: 4, - dash: [10, 5], - }); - layer.add(arrowNoEnd); - - var arrowStartButNoEnd = new Konva.Arrow({ - x: stage.width() / 4 + 100, - y: stage.height() / 4, - points: [0, 0, width / 2, height / 2], - pointerLength: 20, - pointerWidth: 20, - pointerAtEnding: false, - pointerAtBeginning: true, - fill: 'green', - stroke: 'green', - strokeWidth: 4, - dash: [10, 5], - }); - layer.add(arrowStartButNoEnd); - layer.draw(); - - var trace = layer.getContext().getTrace(); - - assert.equal( - trace, - 'clearRect(0,0,578,200);clearRect(0,0,578,200);save();transform(1,0,0,1,144.5,50);beginPath();moveTo(0,0);lineTo(289,100);setLineDash(10,5);lineDashOffset=0;lineWidth=4;strokeStyle=red;stroke();save();beginPath();translate(289,100);rotate(0.333);moveTo(0,0);lineTo(-20,10);lineTo(-20,-10);closePath();restore();setLineDash();fillStyle=red;fill();lineWidth=4;strokeStyle=red;stroke();restore();save();transform(1,0,0,1,194.5,50);beginPath();moveTo(0,0);lineTo(289,100);setLineDash(10,5);lineDashOffset=0;lineWidth=4;strokeStyle=blue;stroke();restore();save();transform(1,0,0,1,244.5,50);beginPath();moveTo(0,0);lineTo(289,100);setLineDash(10,5);lineDashOffset=0;lineWidth=4;strokeStyle=green;stroke();save();beginPath();translate(0,0);rotate(3.475);moveTo(0,0);lineTo(-20,10);lineTo(-20,-10);closePath();restore();setLineDash();fillStyle=green;fill();lineWidth=4;strokeStyle=green;stroke();restore();' - ); - }); - - it('direction with tension', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var arrow = new Konva.Arrow({ - points: [50, 50, 100, 50, 100, 100], - stroke: 'red', - fill: 'red', - tension: 1, - pointerAtBeginning: true, - }); - - layer.add(arrow); - stage.add(layer); - - var trace = layer.getContext().getTrace(false, true); - - assert.equal( - trace, - 'clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);beginPath();moveTo(50,50);quadraticCurveTo(75,25,100,50);quadraticCurveTo(125,75,100,100);lineWidth=2;strokeStyle=red;stroke();save();beginPath();translate(100,100);rotate(2);moveTo(0,0);lineTo(-10,5);lineTo(-10,-5);closePath();restore();setLineDash();fillStyle=red;fill();lineWidth=2;strokeStyle=red;stroke();save();beginPath();translate(50,50);rotate(2);moveTo(0,0);lineTo(-10,5);lineTo(-10,-5);closePath();restore();setLineDash();fillStyle=red;fill();lineWidth=2;strokeStyle=red;stroke();restore();' - ); - }); - - it('direction with tension 2', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var arrow = new Konva.Arrow({ - points: [ - 79.57486136783733, 63.27171903881701, 87.33826247689463, - 80.73937153419593, 124.99075785582254, 82.29205175600738, - 141.68207024029573, 107.52310536044362, 165.74861367837337, - 104.80591497227356, - ], - stroke: 'red', - fill: 'red', - tension: 1, - pointerWidth: 10, - pointerAtBeginning: true, - }); - - layer.add(arrow); - stage.add(layer); - - var trace = layer.getContext().getTrace(false, true); - - assert.equal( - trace, - 'clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);beginPath();moveTo(79,63);quadraticCurveTo(72,74,87,80);bezierCurveTo(117,93,94,67,124,82);bezierCurveTo(149,94,119,95,141,107);quadraticCurveTo(159,117,165,104);lineWidth=2;strokeStyle=red;stroke();save();beginPath();translate(165,104);rotate(5);moveTo(0,0);lineTo(-10,5);lineTo(-10,-5);closePath();restore();setLineDash();fillStyle=red;fill();lineWidth=2;strokeStyle=red;stroke();save();beginPath();translate(79,63);rotate(4);moveTo(0,0);lineTo(-10,5);lineTo(-10,-5);closePath();restore();setLineDash();fillStyle=red;fill();lineWidth=2;strokeStyle=red;stroke();restore();' - ); - }); - - it('test cache', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var arrow = new Konva.Arrow({ - points: [50, 50, 150, 50], - stroke: 'blue', - fill: 'blue', - // large stroke width will not work :( - strokeWidth: 1, - draggable: true, - tension: 0, - }); - layer.add(arrow); - - stage.add(layer); - arrow.cache(); - layer.draw(); - - cloneAndCompareLayer(layer, 255, 50); - }); - - it('getClientRect', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var arrow = new Konva.Arrow({ - points: [50, 50, 150, 50], - stroke: 'blue', - fill: 'blue', - // large stroke width will not work :( - strokeWidth: 1, - draggable: true, - tension: 0, - pointerLength: 10, - pointerWidth: 20, - }); - layer.add(arrow); - - - stage.add(layer); - - var rect = arrow.getClientRect({ skipStroke: true }); - layer.add(new Konva.Rect({...rect, stroke: 'red' })); - - assert.equal(rect.x, 50); - assert.equal(rect.y, 40); - assert.equal(rect.width, 100); - assert.equal(rect.height, 20); - }); -}); diff --git a/test/unit/AutoDraw-test.ts b/test/unit/AutoDraw-test.ts deleted file mode 100644 index 42e24a4ca..000000000 --- a/test/unit/AutoDraw-test.ts +++ /dev/null @@ -1,163 +0,0 @@ -import { assert } from 'chai'; -import { addStage, isNode, Konva } from './test-utils'; - -describe('AutoDraw', function () { - // ====================================================== - it('schedule draw on shape add/change/remove', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - let callCount = 0; - layer.batchDraw = function () { - callCount += 1; - Konva.Layer.prototype.batchDraw.call(this); - return layer; - }; - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - }); - layer.add(circle); - assert.equal(callCount, 1); - circle.radius(50); - assert.equal(callCount, 2); - circle.destroy(); - assert.equal(callCount, 3); - }); - - // ====================================================== - it('schedule draw on order change', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - }); - layer.add(circle); - - var circle2 = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - }); - layer.add(circle2); - - let callCount = 0; - layer.batchDraw = function () { - callCount += 1; - Konva.Layer.prototype.batchDraw.call(this); - return layer; - }; - - circle.moveToTop(); - assert.equal(callCount, 1); - }); - - // ====================================================== - it('schedules draw when calling removeChildren/destroyChildren', () => { - var stage = addStage(); - var layer = new Konva.Layer(); - var group1 = new Konva.Group(); - var group2 = new Konva.Group(); - - stage.add(layer); - layer.add(group1); - group1.add(new Konva.Circle()); - layer.add(group2); - group2.add(new Konva.Circle()); - - let callCount = 0; - layer.batchDraw = function () { - callCount += 1; - Konva.Layer.prototype.batchDraw.call(this); - return layer; - }; - - group1.destroyChildren(); - assert.equal(callCount, 1); - group2.removeChildren(); - assert.equal(callCount, 2); - }); - - // ====================================================== - it('schedule draw on cache', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - }); - layer.add(circle); - - let callCount = 0; - layer.batchDraw = function () { - callCount += 1; - Konva.Layer.prototype.batchDraw.call(this); - return layer; - }; - - circle.cache(); - assert.equal(callCount, 1); - - circle.clearCache(); - assert.equal(callCount, 2); - }); - - // ====================================================== - it('redraw for images', function (done) { - // don't test on node, because of specific url access - if (isNode) { - return done(); - } - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - const { src } = document.getElementById( - 'darth-vader.jpg' - ) as HTMLImageElement; - - const img = new Image(); - img.src = src + '?'; // change url to reset cache - const image = new Konva.Image({ - image: img, - }); - layer.add(image); - - let callCount = 0; - layer.batchDraw = function () { - callCount += 1; - Konva.Layer.prototype.batchDraw.call(this); - return layer; - }; - - img.onload = () => { - assert.equal(callCount, 1); - done(); - }; - }); -}); diff --git a/test/unit/Blob-test.ts b/test/unit/Blob-test.ts deleted file mode 100644 index aa4315afb..000000000 --- a/test/unit/Blob-test.ts +++ /dev/null @@ -1,121 +0,0 @@ -import { assert } from 'chai'; -import { Line } from '../../src/shapes/Line'; - -import { addStage, Konva, cloneAndCompareLayer } from './test-utils'; - -describe('Blob', function () { - // ====================================================== - it('add blob', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var blob = new Konva.Line({ - points: [73, 140, 340, 23, 500, 109, 300, 170], - stroke: 'blue', - strokeWidth: 10, - draggable: true, - fill: '#aaf', - tension: 0.8, - closed: true, - }); - - layer.add(blob); - stage.add(layer); - - assert.equal(blob.tension(), 0.8); - - assert.equal(blob.getClassName(), 'Line'); - - //console.log(blob1.getPoints()) - - // test setter - blob.tension(1.5); - assert.equal(blob.tension(), 1.5); - - var trace = layer.getContext().getTrace(); - assert.equal( - trace, - 'clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);beginPath();moveTo(73,140);bezierCurveTo(90.922,74.135,129.542,38.279,340,23);bezierCurveTo(471.142,13.479,514.876,54.33,500,109);bezierCurveTo(482.876,171.93,463.05,158.163,300,170);bezierCurveTo(121.45,182.963,58.922,191.735,73,140);closePath();fillStyle=#aaf;fill();lineWidth=10;strokeStyle=blue;stroke();restore();' - ); - }); - - // ====================================================== - it('define tension first', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var blob = new Konva.Line({ - tension: 0.8, - points: [73, 140, 340, 23, 500, 109, 300, 170], - stroke: 'blue', - strokeWidth: 10, - draggable: true, - fill: '#aaf', - closed: true, - }); - - layer.add(blob); - stage.add(layer); - - assert.equal(stage.findOne('Line').points().length, 8); - }); - - // ====================================================== - it('check for konva event handlers', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var blob = new Konva.Line({ - points: [73, 140, 340, 23, 500, 109, 300, 170], - stroke: 'blue', - strokeWidth: 10, - draggable: true, - fill: '#aaf', - tension: 0.8, - closed: true, - }); - - layer.add(blob); - - stage.add(layer); - - assert.equal(blob.eventListeners.pointsChange[0].name, 'konva'); - assert.equal(blob.eventListeners.tensionChange[0].name, 'konva'); - - // removing handlers should not remove konva specific handlers - blob.off('pointsChange'); - blob.off('tensionChange'); - - assert.equal(blob.eventListeners.pointsChange[0].name, 'konva'); - assert.equal(blob.eventListeners.tensionChange[0].name, 'konva'); - - // you can force remove an event by adding the name - blob.off('pointsChange.konva'); - blob.off('tensionChange.konva'); - - assert.equal(blob.eventListeners.pointsChange, undefined); - assert.equal(blob.eventListeners.tensionChange, undefined); - }); - - it('cache', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var blob = new Konva.Line({ - x: 50, - y: 50, - points: [-25, 50, 250, -30, 150, 50, 250, 110], - stroke: 'black', - strokeWidth: 10, - draggable: true, - fill: '#aaf', - tension: 0.3, - closed: true, - }); - - blob.cache(); - layer.add(blob); - stage.add(layer); - - cloneAndCompareLayer(layer, 150); - }); -}); diff --git a/test/unit/Canvas-test.ts b/test/unit/Canvas-test.ts deleted file mode 100644 index 05fa37f92..000000000 --- a/test/unit/Canvas-test.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { assert } from 'chai'; -import { addStage, Konva } from './test-utils'; - -describe('Canvas', function () { - // ====================================================== - it('pixel ratio', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - - var circle = new Konva.Circle({ - x: 100, - y: 70, - radius: 70, - fill: 'green', - stroke: 'blue', - strokeWidth: 4, - draggable: true, - }); - - layer.add(circle); - stage.add(layer); - - stage.width(578 / 2); - stage.height(100); - - stage.draw(); - assert.equal(layer.getCanvas().getPixelRatio(), Konva.pixelRatio); - - layer.getCanvas().setPixelRatio(1); - assert.equal(layer.getCanvas().getPixelRatio(), 1); - assert.equal(layer.getCanvas().width, 289); - assert.equal(layer.getCanvas().height, 100); - - layer.getCanvas().setPixelRatio(2); - assert.equal(layer.getCanvas().getPixelRatio(), 2); - assert.equal(layer.getCanvas().width, 578); - assert.equal(layer.getCanvas().height, 200); - - layer.draw(); - }); -}); diff --git a/test/unit/Circle-test.ts b/test/unit/Circle-test.ts deleted file mode 100644 index 11c1c2660..000000000 --- a/test/unit/Circle-test.ts +++ /dev/null @@ -1,320 +0,0 @@ -import { assert } from 'chai'; - -import { - addStage, - Konva, - createCanvas, - compareLayerAndCanvas, - loadImage, -} from './test-utils'; - -describe('Circle', function () { - // ====================================================== - - it('add circle to stage', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var group = new Konva.Group(); - var circle = new Konva.Circle({ - x: 100, - y: 100, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - draggable: true, - }); - - stage.add(layer); - layer.add(group); - group.add(circle); - layer.draw(); - - var attrs = circle.getAttrs(); - - assert.equal(attrs.x, 100); - assert.equal(attrs.y, 100); - assert.equal(attrs.radius, 70); - assert.equal(attrs.fill, 'green'); - assert.equal(attrs.stroke, 'black'); - assert.equal(attrs.strokeWidth, 4); - assert.equal(attrs.name, 'myCircle'); - assert.equal(attrs.draggable, true); - assert.equal(circle.getClassName(), 'Circle'); - - var trace = layer.getContext().getTrace(); - // console.log(trace); - // console.log( - // 'clearRect(0,0,578,200);clearRect(0,0,578,200);save();transform(1,0,0,1,100,100);beginPath();arc(0,0,70,0,6.283,false);closePath();fillStyle=green;fill();lineWidth=4;strokeStyle=black;stroke();restore();' - // ); - assert.equal( - trace, - 'clearRect(0,0,578,200);clearRect(0,0,578,200);save();transform(1,0,0,1,100,100);beginPath();arc(0,0,70,0,6.283,false);closePath();fillStyle=green;fill();lineWidth=4;strokeStyle=black;stroke();restore();' - ); - }); - - it('clone', function () { - var circle = new Konva.Circle(); - var clone = circle.clone(); - assert.equal(clone instanceof Konva.Circle, true); - assert.equal(clone.className, 'Circle'); - }); - - // ====================================================== - it('add circle with pattern fill', function (done) { - loadImage('darth-vader.jpg', (imageObj) => { - var stage = addStage(); - var layer = new Konva.Layer(); - var group = new Konva.Group(); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fillPatternImage: imageObj, - fillPatternOffset: { x: -5, y: -5 }, - fillPatternScale: { x: 0.7, y: 0.7 }, - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - draggable: true, - }); - - group.add(circle); - layer.add(group); - stage.add(layer); - - assert.equal(circle.fillPatternOffset().x, -5); - assert.equal(circle.fillPatternOffset().y, -5); - - circle.fillPatternOffset({ x: 1, y: 2 }); - assert.equal(circle.fillPatternOffset().x, 1); - assert.equal(circle.fillPatternOffset().y, 2); - - circle.fillPatternOffset({ - x: 3, - y: 4, - }); - assert.equal(circle.fillPatternOffset().x, 3); - assert.equal(circle.fillPatternOffset().y, 4); - - done(); - }); - }); - - // ====================================================== - it('add circle with radial gradient fill', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var group = new Konva.Group(); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fillRadialGradientStartPoint: { x: -20, y: -20 }, - fillRadialGradientStartRadius: 0, - fillRadialGradientEndPoint: { x: -60, y: -60 }, - fillRadialGradientEndRadius: 130, - fillRadialGradientColorStops: [0, 'red', 0.2, 'yellow', 1, 'blue'], - name: 'myCircle', - draggable: true, - scale: { - x: 0.5, - y: 0.5, - }, - }); - - group.add(circle); - layer.add(group); - stage.add(layer); - - assert.equal(circle.fillRadialGradientStartPoint().x, -20); - assert.equal(circle.fillRadialGradientStartPoint().y, -20); - assert.equal(circle.fillRadialGradientStartRadius(), 0); - assert.equal(circle.fillRadialGradientEndPoint().x, -60); - assert.equal(circle.fillRadialGradientEndPoint().y, -60); - assert.equal(circle.fillRadialGradientEndRadius(), 130); - assert.equal(circle.fillRadialGradientColorStops().length, 6); - }); - - // ====================================================== - it('add shape with linear gradient fill', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var group = new Konva.Group(); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fillLinearGradientStartPoint: { x: -35, y: -35 }, - fillLinearGradientEndPoint: { x: 35, y: 35 }, - fillLinearGradientColorStops: [0, 'red', 1, 'blue'], - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - draggable: true, - }); - - group.add(circle); - layer.add(group); - stage.add(layer); - - var canvas = createCanvas(); - var ctx = canvas.getContext('2d'); - - var start = { x: -35, y: -35 }; - var end = { x: 35, y: 35 }; - var colorStops = [0, 'red', 1, 'blue']; - var grd = ctx.createLinearGradient(start.x, start.y, end.x, end.y); - - // build color stops - for (var n = 0; n < colorStops.length; n += 2) { - grd.addColorStop(colorStops[n] as number, colorStops[n + 1] as string); - } - ctx.beginPath(); - ctx.translate(circle.x(), circle.y()); - ctx.arc(0, 0, 70, 0, Math.PI * 2, false); - ctx.closePath(); - - ctx.fillStyle = grd; - ctx.lineWidth = 4; - - ctx.fill(); - ctx.stroke(); - - compareLayerAndCanvas(layer, canvas, 200); - }); - - // ====================================================== - it('set opacity after instantiation', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var group = new Konva.Group(); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'red', - }); - - group.add(circle); - layer.add(group); - stage.add(layer); - - circle.opacity(0.5); - layer.draw(); - - circle.opacity(1); - layer.draw(); - - var trace = layer.getContext().getTrace(); - assert.equal( - trace, - 'clearRect(0,0,578,200);save();transform(1,0,0,1,289,100);beginPath();arc(0,0,70,0,6.283,false);closePath();fillStyle=red;fill();restore();clearRect(0,0,578,200);save();transform(1,0,0,1,289,100);globalAlpha=0.5;beginPath();arc(0,0,70,0,6.283,false);closePath();fillStyle=red;fill();restore();clearRect(0,0,578,200);save();transform(1,0,0,1,289,100);beginPath();arc(0,0,70,0,6.283,false);closePath();fillStyle=red;fill();restore();' - ); - }); - - // ====================================================== - it('attrs sync', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - }); - - layer.add(circle); - stage.add(layer); - - assert.equal(circle.getWidth(), 140); - assert.equal(circle.getHeight(), 140); - - circle.setWidth(100); - assert.equal(circle.radius(), 50); - assert.equal(circle.getHeight(), 100); - - circle.setHeight(120); - assert.equal(circle.radius(), 60); - assert.equal(circle.getHeight(), 120); - }); - - // ====================================================== - it('set fill after instantiation', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - }); - layer.add(circle); - - circle.fill('blue'); - - stage.add(layer); - - var trace = layer.getContext().getTrace(); - assert.equal( - trace, - 'clearRect(0,0,578,200);save();transform(1,0,0,1,289,100);beginPath();arc(0,0,70,0,6.283,false);closePath();fillStyle=blue;fill();lineWidth=4;strokeStyle=black;stroke();restore();' - ); - }); - - it('getSelfRect', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: 100, - y: 100, - radius: 50, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - draggable: true, - }); - - layer.add(circle); - stage.add(layer); - - assert.deepEqual(circle.getSelfRect(), { - x: -50, - y: -50, - width: 100, - height: 100, - }); - }); - - it('cache', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: 100, - y: 100, - radius: 50, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - }); - - layer.add(circle); - stage.add(layer); - - var canvas = createCanvas(); - var context = canvas.getContext('2d'); - context.beginPath(); - context.arc(100, 100, 50, 0, Math.PI * 2, false); - context.closePath(); - context.fillStyle = 'green'; - context.fill(); - context.lineWidth = 4; - context.stroke(); - compareLayerAndCanvas(layer, canvas, 100); - }); -}); diff --git a/test/unit/Container-test.ts b/test/unit/Container-test.ts deleted file mode 100644 index 045dfdd7a..000000000 --- a/test/unit/Container-test.ts +++ /dev/null @@ -1,2731 +0,0 @@ -import { assert } from 'chai'; -import { Shape } from '../../src/Shape.js'; -import { addStage, Konva, compareLayers, isNode } from './test-utils'; - -describe('Container', function () { - // ====================================================== - it('clip', function () { - var stage = addStage(); - var layer = new Konva.Layer({ - clip: { x: 0, y: 0, width: stage.width() / 2, height: 100 }, - }); - var group = new Konva.Group(); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - draggable: true, - }); - - stage.add(layer); - layer.add(group); - group.add(circle); - layer.draw(); - - var trace = layer.getContext().getTrace(); - assert.equal( - trace, - 'clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);beginPath();rect(0,0,289,100);clip();transform(1,0,0,1,0,0);restore();clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);beginPath();rect(0,0,289,100);clip();transform(1,0,0,1,0,0);save();transform(1,0,0,1,289,100);beginPath();arc(0,0,70,0,6.283,false);closePath();fillStyle=green;fill();lineWidth=4;strokeStyle=black;stroke();restore();restore();' - ); - }); - - // ====================================================== - it('clip function', function () { - var stage = addStage(); - - // cliped by circle is the same as draw circle - var layer = new Konva.Layer(); - stage.add(layer); - var circle = new Konva.Circle({ - fill: 'black', - x: 50, - y: 50, - radius: 40, - }); - layer.add(circle); - - layer.draw(); - - var clipedLayer = new Konva.Layer({ - clipFunc: function (ctx) { - ctx.arc(50, 50, 40, 0, Math.PI * 2, false); - }, - }); - stage.add(clipedLayer); - var rect = new Konva.Rect({ - x: 10, - y: 10, - fill: 'black', - width: 200, - height: 200, - }); - clipedLayer.add(rect); - stage.draw(); - - compareLayers(layer, clipedLayer, 150); - }); - - // ====================================================== - it('clip function', function () { - if (isNode) { - // how to use Path2D in nodejs env? - return; - } - var stage = addStage(); - - // cliped by circle is the same as draw circle - var layer = new Konva.Layer(); - stage.add(layer); - var circle = new Konva.Circle({ - fill: 'black', - x: 50, - y: 50, - radius: 40, - }); - layer.add(circle); - - layer.draw(); - - var clipedLayer = new Konva.Layer({ - clipFunc: function (ctx) { - const path2D = new Path2D(); - path2D.arc(50, 50, 40, 0, Math.PI * 2, false); - ctx.fill(path2D); - }, - }); - stage.add(clipedLayer); - var rect = new Konva.Rect({ - x: 10, - y: 10, - fill: 'black', - width: 200, - height: 200, - }); - clipedLayer.add(rect); - stage.draw(); - - compareLayers(layer, clipedLayer, 150); - }); - - // ====================================================== - it('adder validation', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var group = new Konva.Group(); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - }); - - stage.add(layer); - layer.add(group); - group.add(circle); - layer.draw(); - - // disassemble the tree - circle.remove(); - group.remove(); - layer.remove(); - - // =================================== - var errorThrown = false; - try { - stage.add(stage as any); - } catch (err) { - errorThrown = true; - } - assert.equal( - errorThrown, - true, - 'error should have been thrown when adding stage to stage' - ); - stage.remove(); - - // =================================== - var errorThrown = false; - try { - stage.add(group as any); - } catch (err) { - errorThrown = true; - } - assert.equal( - errorThrown, - true, - 'error should have been thrown when adding group to stage' - ); - group.remove(); - - // =================================== - var errorThrown = false; - try { - stage.add(circle as any); - } catch (err) { - errorThrown = true; - } - assert.equal( - errorThrown, - true, - 'error should have been thrown when adding shape to stage' - ); - circle.remove(); - - // =================================== - var errorThrown = false; - try { - layer.add(stage as any); - } catch (err) { - errorThrown = true; - } - assert.equal( - errorThrown, - true, - 'error should have been thrown when adding stage to layer' - ); - stage.remove(); - - // =================================== - var errorThrown = false; - try { - layer.add(layer); - } catch (err) { - errorThrown = true; - } - assert.equal( - errorThrown, - true, - 'error should have been thrown when adding layer to layer' - ); - layer.remove(); - - // =================================== - var errorThrown = false; - try { - group.add(stage as any); - } catch (err) { - errorThrown = true; - } - assert.equal( - errorThrown, - true, - 'error should have been thrown when adding stage to group' - ); - stage.remove(); - - // =================================== - var errorThrown = false; - try { - group.add(layer); - } catch (err) { - errorThrown = true; - } - assert.equal( - errorThrown, - true, - 'error should have been thrown when adding layer to group' - ); - layer.remove(); - }); - - // ====================================================== - it('add layer then group then shape', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var group = new Konva.Group(); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - }); - - // these should all pass because they are valid - stage.add(layer); - layer.add(group); - group.add(circle); - layer.draw(); - - var trace = layer.getContext().getTrace(); - assert.equal( - trace, - 'clearRect(0,0,578,200);clearRect(0,0,578,200);save();transform(1,0,0,1,289,100);beginPath();arc(0,0,70,0,6.283,false);closePath();fillStyle=green;fill();lineWidth=4;strokeStyle=black;stroke();restore();' - ); - }); - - // ====================================================== - it('add shape then stage then layer', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var group = new Konva.Group(); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - }); - - group.add(circle); - stage.add(layer); - layer.add(group); - - layer.draw(); - - var trace = layer.getContext().getTrace(); - assert.equal( - trace, - 'clearRect(0,0,578,200);clearRect(0,0,578,200);save();transform(1,0,0,1,289,100);beginPath();arc(0,0,70,0,6.283,false);closePath();fillStyle=green;fill();lineWidth=4;strokeStyle=black;stroke();restore();' - ); - }); - - // ====================================================== - it('select shape by id and name', function () { - var stage = addStage(); - var layer = new Konva.Layer({ - id: 'myLayer', - }); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - id: 'myCircle', - }); - - var rect = new Konva.Rect({ - x: 300, - y: 100, - width: 100, - height: 50, - fill: 'purple', - stroke: 'black', - strokeWidth: 4, - name: 'myRect', - }); - - layer.add(circle); - layer.add(rect); - stage.add(layer); - - var node; - node = stage.find('#myCircle')[0]; - assert.equal(node.className, 'Circle', 'className should be Circle'); - node = layer.find('.myRect')[0]; - assert.equal(node.className, 'Rect', 'className should be rect'); - node = layer.find('#myLayer')[0]; - assert.equal(node, undefined, 'node should be undefined'); - node = stage.find('#myLayer')[0]; - assert.equal(node.nodeType, 'Layer', 'node type should be Layer'); - }); - - // ====================================================== - it('select shape by name with "-" char', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var rect = new Konva.Rect({ - x: 300, - y: 100, - width: 100, - height: 50, - fill: 'purple', - stroke: 'black', - strokeWidth: 4, - name: 'bounding-box', - }); - - layer.add(rect); - stage.add(layer); - - var node = stage.find('.bounding-box')[0]; - assert.equal(node, rect); - }); - - // ====================================================== - it('select should return elements in their order', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - // create circle before any nodes - var circle = new Konva.Circle({ - radius: 50, - fill: 'red', - name: 'node', - }); - - var group = new Konva.Group({ - name: 'node', - }); - layer.add(group); - - var rect = new Konva.Rect({ - x: 300, - y: 100, - width: 100, - height: 50, - fill: 'purple', - stroke: 'black', - strokeWidth: 4, - name: 'node', - }); - group.add(rect); - group.add(circle); - - // move circle - circle.moveToBottom(); - - assert.equal(layer.findOne('.node'), group, 'group here'); - - var nodes = layer.find('.node'); - assert.equal(nodes[0], group, 'group first'); - assert.equal(nodes[1], circle, 'then circle'); - assert.equal(nodes[2], rect, 'then rect'); - - assert.equal(layer.findOne('Shape'), circle, 'circle is first'); - }); - - // ====================================================== - it('select shape with findOne', function () { - var stage = addStage(); - var layer = new Konva.Layer({ - id: 'myLayer', - }); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - id: 'myCircle', - }); - - var rect = new Konva.Rect({ - x: 300, - y: 100, - width: 100, - height: 50, - fill: 'purple', - stroke: 'black', - strokeWidth: 4, - name: 'myRect', - }); - - layer.add(circle); - layer.add(rect); - stage.add(layer); - - var node; - node = stage.findOne('#myCircle'); - assert.equal(node, circle); - node = layer.findOne('.myRect'); - assert.equal(node, rect); - node = layer.findOne('#myLayer'); - assert.equal(node, undefined, 'node should be undefined'); - node = stage.findOne('#myLayer'); - assert.equal(node, layer, 'node type should be Layer'); - node = stage.findOne(function (node) { - return node.getType() === 'Shape'; - }); - assert.equal(node, circle, 'findOne should work with functions'); - }); - - // ====================================================== - it('select shapes with multiple selectors', function () { - var stage = addStage(); - var layer = new Konva.Layer({ - id: 'myLayer', - }); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - id: 'myCircle', - }); - - var rect = new Konva.Rect({ - x: 300, - y: 100, - width: 100, - height: 50, - fill: 'purple', - stroke: 'black', - strokeWidth: 4, - name: 'myRect', - }); - - layer.add(circle); - layer.add(rect); - stage.add(layer); - - assert.equal( - layer.find('#myCircle, .myRect').length, - 2, - 'should be 2 items in the array' - ); - assert.equal( - layer.find('#myCircle, .myRect')[0]._id, - circle._id, - 'circle id is wrong' - ); - assert.equal( - layer.find('#myCircle, .myRect')[1]._id, - rect._id, - 'rect id is wrong' - ); - - assert.equal( - layer.find('#myCircle, Circle, .myRect, Rect').length, - 2, - 'should be 2 items in the array' - ); - assert.equal( - layer.find('#myCircle, Circle, .myRect, Rect')[0]._id, - circle._id, - 'circle id is wrong' - ); - assert.equal( - layer.find('#myCircle, Circle, .myRect, Rect')[1]._id, - rect._id, - 'rect id is wrong' - ); - }); - - // ====================================================== - it('select shape by function', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var rect = new Konva.Rect({ - x: 300, - y: 100, - width: 100, - height: 50, - fill: 'purple', - stroke: 'black', - strokeWidth: 4, - name: 'myRect', - }); - - layer.add(rect); - stage.add(layer); - - var fn = function (node) { - return node.nodeType === 'Shape'; - }; - - var noOp = function (node) { - return false; - }; - - assert.equal(stage.find(fn)[0], rect); - assert.equal(stage.find(noOp).length, 0); - }); - - // ====================================================== - it('select shape with duplicate id', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect1 = new Konva.Rect({ - x: 300, - y: 100, - width: 100, - height: 50, - fill: 'purple', - stroke: 'black', - strokeWidth: 4, - id: 'myRect', - }); - layer.add(rect1); - - var rect2 = new Konva.Rect({ - x: 300, - y: 100, - width: 100, - height: 50, - fill: 'purple', - stroke: 'black', - strokeWidth: 4, - id: 'myRect', - }); - layer.add(rect2); - stage.draw(); - - assert.equal(stage.find('#myRect').length, 2); - assert.equal(stage.find('#myRect')[0], rect1); - assert.equal(stage.find('#myRect')[1], rect2); - }); - - // ====================================================== - it('set x on an array of nodes', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myShape', - }); - - var rect = new Konva.Rect({ - x: 300, - y: 100, - width: 100, - height: 50, - fill: 'purple', - stroke: 'black', - strokeWidth: 4, - name: 'myShape', - }); - - layer.add(circle); - layer.add(rect); - stage.add(layer); - - var shapes = layer.find('.myShape'); - - assert.equal(shapes.length, 2, 'shapes array should have 2 elements'); - - shapes.forEach(function (node) { - node.x(200); - }); - - layer.draw(); - - shapes.forEach(function (node) { - assert.equal(node.x(), 200, 'shape x should be 200'); - }); - }); - - // ====================================================== - it('set fill on array by Shape-selector', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myShape', - }); - - var rect = new Konva.Rect({ - x: 300, - y: 100, - width: 100, - height: 50, - fill: 'purple', - stroke: 'black', - strokeWidth: 4, - name: 'myShape', - }); - - layer.add(circle); - layer.add(rect); - stage.add(layer); - - var shapes = layer.find('Shape'); - - assert.equal(shapes.length, 2, 'shapes array should have 2 elements'); - - shapes.forEach(function (node) { - node.fill('gray'); - }); - - layer.draw(); - - shapes.forEach(function (node) { - assert.equal(node.fill(), 'gray', 'shape x should be 200'); - }); - }); - - // ====================================================== - it('add listener to an array of nodes', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myShape', - }); - - var rect = new Konva.Rect({ - x: 300, - y: 100, - width: 100, - height: 50, - fill: 'purple', - stroke: 'black', - strokeWidth: 4, - name: 'myShape', - }); - - layer.add(circle); - layer.add(rect); - stage.add(layer); - - var shapes = layer.find('.myShape'); - - assert.equal(shapes.length, 2, 'shapes array should have 2 elements'); - var a = 0; - shapes.forEach((shape) => - shape.on('mouseover', function () { - a++; - }) - ); - circle.fire('mouseover'); - assert.equal(a, 1, 'listener should have fired for circle'); - rect.fire('mouseover'); - assert.equal(a, 2, 'listener should have fired for rect'); - }); - - // ====================================================== - it('remove all children from layer', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var group = new Konva.Group(); - var circle1 = new Konva.Circle({ - x: 100, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - }); - - var circle2 = new Konva.Circle({ - x: 300, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - }); - - group.add(circle1); - group.add(circle2); - layer.add(group); - stage.add(layer); - - assert.equal(layer.children.length, 1, 'layer should have 1 children'); - assert.equal(group.children.length, 2, 'group should have 2 children'); - - layer.removeChildren(); - layer.draw(); - - assert.equal(layer.children.length, 0, 'layer should have 0 children'); - assert.equal( - group.children.length, - 2, - 'group still should have 2 children' - ); - }); - - // ====================================================== - it('add group', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var group = new Konva.Group(); - - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - }); - - group.add(circle); - layer.add(group); - stage.add(layer); - - var trace = layer.getContext().getTrace(); - assert.equal( - trace, - 'clearRect(0,0,578,200);save();transform(1,0,0,1,289,100);beginPath();arc(0,0,70,0,6.283,false);closePath();fillStyle=green;fill();lineWidth=4;strokeStyle=black;stroke();restore();' - ); - }); - - // ====================================================== - it('create two groups, move first group', function () { - var stage = addStage(); - var greenLayer = new Konva.Layer(); - var blueLayer = new Konva.Layer(); - var greenGroup = new Konva.Group(); - var blueGroup = new Konva.Group(); - - var greencircle = new Konva.Circle({ - x: stage.width() / 2 - 100, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - draggable: true, - }); - - var bluecircle = new Konva.Circle({ - x: stage.width() / 2 + 100, - y: stage.height() / 2, - radius: 70, - fill: 'blue', - stroke: 'black', - strokeWidth: 4, - }); - - greenGroup.add(greencircle); - blueGroup.add(bluecircle); - greenLayer.add(greenGroup); - blueLayer.add(blueGroup); - stage.add(greenLayer); - stage.add(blueLayer); - - blueLayer.removeChildren(); - var blueGroup2 = new Konva.Group(); - var bluecircle2 = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'blue', - stroke: 'black', - strokeWidth: 4, - }); - blueGroup2.add(bluecircle2); - blueLayer.add(blueGroup2); - blueLayer.draw(); - blueGroup2.position({ x: 100, y: 0 }); - blueLayer.draw(); - - var trace = blueLayer.getContext().getTrace(); - assert.equal( - trace, - 'clearRect(0,0,578,200);save();transform(1,0,0,1,389,100);beginPath();arc(0,0,70,0,6.283,false);closePath();fillStyle=blue;fill();lineWidth=4;strokeStyle=black;stroke();restore();clearRect(0,0,578,200);save();transform(1,0,0,1,289,100);beginPath();arc(0,0,70,0,6.283,false);closePath();fillStyle=blue;fill();lineWidth=4;strokeStyle=black;stroke();restore();clearRect(0,0,578,200);save();transform(1,0,0,1,389,100);beginPath();arc(0,0,70,0,6.283,false);closePath();fillStyle=blue;fill();lineWidth=4;strokeStyle=black;stroke();restore();' - ); - }); - - // ====================================================== - it('node type selector', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var fooLayer = new Konva.Layer(); - var group = new Konva.Group(); - - var blue = new Konva.Rect({ - x: 200, - y: 100, - width: 100, - height: 50, - fill: 'blue', - }); - - var red = new Konva.Rect({ - x: 250, - y: 100, - width: 100, - height: 50, - fill: 'red', - }); - - group.add(red); - layer.add(blue); - layer.add(group); - stage.add(layer); - stage.add(fooLayer); - - assert.equal(stage.find('Shape').length, 2, 'stage should have 2 shapes'); - assert.equal(layer.find('Shape').length, 2, 'layer should have 2 shapes'); - assert.equal(group.find('Shape').length, 1, 'layer should have 2 shapes'); - - assert.equal(stage.find('Layer').length, 2, 'stage should have 2 layers'); - assert.equal(stage.find('Group').length, 1, 'stage should have 1 group'); - - assert.equal(layer.find('Group').length, 1, 'layer should have 1 group'); - assert.equal(layer.find('Shape').length, 2, 'layer should have 2 shapes'); - assert.equal(layer.find('Layer').length, 0, 'layer should have 0 layers'); - - assert.equal( - layer.find('Group, Rect').length, - 3, - 'layer should have 3 [group or rects]' - ); - - assert.equal( - fooLayer.find('Group').length, - 0, - 'layer should have 0 groups' - ); - assert.equal( - fooLayer.find('Shape').length, - 0, - 'layer should have 0 shapes' - ); - - assert.equal(group.find('Shape').length, 1, 'group should have 1 shape'); - assert.equal(group.find('Layer').length, 0, 'group should have 0 layers'); - assert.equal(group.find('Group').length, 0, 'group should have 0 groups'); - }); - - // ====================================================== - it('node and shape type selector', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var layer2 = new Konva.Layer(); - var fooLayer = new Konva.Layer(); - var group = new Konva.Group(); - - var blue = new Konva.Rect({ - x: 100, - y: 50, - width: 100, - height: 50, - fill: 'blue', - }); - - var red = new Konva.Rect({ - x: 150, - y: 75, - width: 100, - height: 50, - fill: 'red', - }); - - var green = new Konva.Rect({ - x: 200, - y: 100, - width: 100, - height: 50, - fill: 'green', - }); - - var blueCircle = new Konva.Circle({ - x: 350, - y: 75, - radius: 40, - fill: 'blue', - }); - - var redCircle = new Konva.Circle({ - x: 400, - y: 125, - radius: 40, - fill: 'red', - }); - - var textpath = new Konva.TextPath({ - y: 35, - stroke: 'black', - strokeWidth: 1, - fill: 'orange', - fontSize: 18, - fontFamily: 'Arial', - text: "The quick brown fox jumped over the lazy dog's back", - data: 'M 10,10 300,150 550,150', - }); - - var path = new Konva.Path({ - x: 200, - y: -75, - data: 'M200,100h100v50z', - fill: '#ccc', - stroke: '#333', - strokeWidth: 2, - shadowColor: 'black', - shadowBlur: 2, - shadowOffset: { x: 10, y: 10 }, - shadowOpacity: 0.5, - }); - - var poly = new Konva.RegularPolygon({ - x: stage.width() / 2, - y: stage.height() / 2, - sides: 5, - radius: 50, - fill: 'green', - stroke: 'blue', - strokeWidth: 5, - name: 'foobar', - }); - - group.add(red); - group.add(redCircle); - layer.add(blue); - layer.add(green); - layer.add(blueCircle); - layer.add(group); - layer2.add(textpath); - layer2.add(path); - layer2.add(poly); - stage.add(layer); - stage.add(layer2); - stage.add(fooLayer); - - assert.equal(stage.find('Shape').length, 8, 'stage should have 5 shapes'); - assert.equal(stage.find('Layer').length, 3, 'stage should have 2 layers'); - assert.equal(stage.find('Group').length, 1, 'stage should have 1 group'); - assert.equal(stage.find('Rect').length, 3, 'stage should have 3 rects'); - assert.equal(stage.find('Circle').length, 2, 'stage should have 2 circles'); - assert.equal( - stage.find('RegularPolygon').length, - 1, - 'stage should have 1 regular polygon' - ); - assert.equal( - stage.find('TextPath').length, - 1, - 'stage should have 1 text path' - ); - assert.equal(stage.find('Path').length, 1, 'stage should have 1 path'); - - assert.equal(layer.find('Shape').length, 5, 'layer should have 5 shapes'); - assert.equal(layer.find('Layer').length, 0, 'layer should have 0 layers'); - assert.equal(layer.find('Group').length, 1, 'layer should have 1 group'); - assert.equal(layer.find('Rect').length, 3, 'layer should have 3 rects'); - assert.equal(layer.find('Circle').length, 2, 'layer should have 2 circles'); - assert.equal( - layer.find('RegularPolygon').length, - 0, - 'layer should have 0 regular polygon' - ); - assert.equal( - layer.find('TextPath').length, - 0, - 'layer should have 0 text path' - ); - assert.equal(layer.find('Path').length, 0, 'layer should have 0 path'); - - assert.equal(layer2.find('Shape').length, 3, 'layer2 should have 3 shapes'); - assert.equal(layer2.find('Layer').length, 0, 'layer2 should have 0 layers'); - assert.equal(layer2.find('Group').length, 0, 'layer2 should have 0 group'); - assert.equal( - layer2.find('RegularPolygon').length, - 1, - 'layer2 should have 1 regular polygon' - ); - assert.equal( - layer2.find('TextPath').length, - 1, - 'layer2 should have 1 text path' - ); - assert.equal(layer2.find('Path').length, 1, 'layer2 should have 1 path'); - - assert.equal( - fooLayer.find('Shape').length, - 0, - 'layer should have 0 shapes' - ); - assert.equal( - fooLayer.find('Group').length, - 0, - 'layer should have 0 groups' - ); - assert.equal(fooLayer.find('Rect').length, 0, 'layer should have 0 rects'); - assert.equal( - fooLayer.find('Circle').length, - 0, - 'layer should have 0 circles' - ); - - assert.equal(group.find('Shape').length, 2, 'group should have 2 shape'); - assert.equal(group.find('Layer').length, 0, 'group should have 0 layers'); - assert.equal(group.find('Group').length, 0, 'group should have 0 groups'); - assert.equal(group.find('Rect').length, 1, 'group should have 1 rects'); - assert.equal(group.find('Circle').length, 1, 'gropu should have 1 circles'); - }); - - // ====================================================== - it('test find() selector by adding shapes with multiple names', function () { - var stage = addStage(); - var layer = new Konva.Layer({ - name: 'layerName', - id: 'layerId', - }); - var group = new Konva.Group({ - name: 'groupName', - id: 'groupId', - }); - var rect = new Konva.Rect({ - x: 200, - y: 20, - width: 100, - height: 50, - fill: 'red', - name: 'red rectangle', - id: 'rectId', - }); - var circle = new Konva.Circle({ - x: 50, - y: 50, - radius: 20, - fill: 'red', - name: 'red circle', - id: 'circleId', - }); - - group.add(rect); - group.add(circle); - layer.add(group); - stage.add(layer); - - assert.equal( - stage.find('.rectangle')[0], - rect, - 'problem with shape name selector' - ); - assert.equal( - layer.find('.rectangle')[0], - rect, - 'problem with shape name selector' - ); - assert.equal( - group.find('.rectangle')[0], - rect, - 'problem with shape name selector' - ); - - assert.equal( - stage.find('.circle')[0], - circle, - 'problem with shape name selector' - ); - assert.equal( - layer.find('.circle')[0], - circle, - 'problem with shape name selector' - ); - assert.equal( - group.find('.circle')[0], - circle, - 'problem with shape name selector' - ); - - assert.equal( - stage.find('.red')[0], - rect, - 'problem with shape name selector' - ); - assert.equal( - stage.find('.red')[1], - circle, - 'problem with shape name selector' - ); - assert.equal( - layer.find('.red')[0], - rect, - 'problem with shape name selector' - ); - assert.equal( - layer.find('.red')[1], - circle, - 'problem with shape name selector' - ); - assert.equal( - group.find('.red')[0], - rect, - 'problem with shape name selector' - ); - assert.equal( - group.find('.red')[1], - circle, - 'problem with shape name selector' - ); - - assert.equal( - stage.find('.groupName')[0], - group, - 'problem with group name selector' - ); - assert.equal( - layer.find('.groupName')[0], - group, - 'problem with group name selector' - ); - - assert.equal( - stage.find('.layerName')[0], - layer, - 'problem with layer name selector' - ); - }); - - // ====================================================== - it('test find() selector by adding shape, then group, then layer', function () { - var stage = addStage(); - var layer = new Konva.Layer({ - name: 'layerName', - id: 'layerId', - }); - var group = new Konva.Group({ - name: 'groupName', - id: 'groupId', - }); - var rect = new Konva.Rect({ - x: 200, - y: 20, - width: 100, - height: 50, - fill: 'red', - stroke: 'black', - strokeWidth: 4, - name: 'rectName', - id: 'rectId', - }); - - group.add(rect); - layer.add(group); - stage.add(layer); - - assert.equal( - stage.find('.rectName')[0].attrs.id, - 'rectId', - 'problem with shape name selector' - ); - assert.equal( - stage.find('#rectId')[0].attrs.id, - 'rectId', - 'problem with shape id selector' - ); - assert.equal( - layer.find('.rectName')[0].attrs.id, - 'rectId', - 'problem with shape name selector' - ); - assert.equal( - layer.find('#rectId')[0].attrs.id, - 'rectId', - 'problem with shape id selector' - ); - assert.equal( - group.find('.rectName')[0].attrs.id, - 'rectId', - 'problem with shape name selector' - ); - assert.equal( - group.find('#rectId')[0].attrs.id, - 'rectId', - 'problem with shape id selector' - ); - - assert.equal( - stage.find('.groupName')[0].attrs.id, - 'groupId', - 'problem with group name selector' - ); - assert.equal( - stage.find('#groupId')[0].attrs.id, - 'groupId', - 'problem with group id selector' - ); - assert.equal( - layer.find('.groupName')[0].attrs.id, - 'groupId', - 'problem with group name selector' - ); - assert.equal( - layer.find('#groupId')[0].attrs.id, - 'groupId', - 'problem with group id selector' - ); - - assert.equal( - stage.find('.layerName')[0].attrs.id, - 'layerId', - 'problem with layer name selector' - ); - assert.equal( - stage.find('#layerId')[0].attrs.id, - 'layerId', - 'problem with layer id selector' - ); - }); - - // ====================================================== - it('test find() selector by adding group, then shape, then layer', function () { - var stage = addStage(); - var layer = new Konva.Layer({ - name: 'layerName', - id: 'layerId', - }); - var group = new Konva.Group({ - name: 'groupName', - id: 'groupId', - }); - var rect = new Konva.Rect({ - x: 200, - y: 20, - width: 100, - height: 50, - fill: 'red', - stroke: 'black', - strokeWidth: 4, - name: 'rectName', - id: 'rectId', - }); - - layer.add(group); - group.add(rect); - stage.add(layer); - - assert.equal( - stage.find('.rectName')[0].attrs.id, - 'rectId', - 'problem with shape name selector' - ); - assert.equal( - stage.find('#rectId')[0].attrs.id, - 'rectId', - 'problem with shape id selector' - ); - assert.equal( - layer.find('.rectName')[0].attrs.id, - 'rectId', - 'problem with shape name selector' - ); - assert.equal( - layer.find('#rectId')[0].attrs.id, - 'rectId', - 'problem with shape id selector' - ); - assert.equal( - group.find('.rectName')[0].attrs.id, - 'rectId', - 'problem with shape name selector' - ); - assert.equal( - group.find('#rectId')[0].attrs.id, - 'rectId', - 'problem with shape id selector' - ); - - assert.equal( - stage.find('.groupName')[0].attrs.id, - 'groupId', - 'problem with group name selector' - ); - assert.equal( - stage.find('#groupId')[0].attrs.id, - 'groupId', - 'problem with group id selector' - ); - assert.equal( - layer.find('.groupName')[0].attrs.id, - 'groupId', - 'problem with group name selector' - ); - assert.equal( - layer.find('#groupId')[0].attrs.id, - 'groupId', - 'problem with group id selector' - ); - - assert.equal( - stage.find('.layerName')[0].attrs.id, - 'layerId', - 'problem with layer name selector' - ); - assert.equal( - stage.find('#layerId')[0].attrs.id, - 'layerId', - 'problem with layer id selector' - ); - }); - - // ====================================================== - it('test find() selector by adding group, then layer, then shape', function () { - var stage = addStage(); - var layer = new Konva.Layer({ - name: 'layerName', - id: 'layerId', - }); - var group = new Konva.Group({ - name: 'groupName', - id: 'groupId', - }); - var rect = new Konva.Rect({ - x: 200, - y: 20, - width: 100, - height: 50, - fill: 'red', - stroke: 'black', - strokeWidth: 4, - name: 'rectName', - id: 'rectId', - }); - - layer.add(group); - stage.add(layer); - group.add(rect); - - assert.equal( - stage.find('.rectName')[0].attrs.id, - 'rectId', - 'problem with shape name selector' - ); - assert.equal( - stage.find('#rectId')[0].attrs.id, - 'rectId', - 'problem with shape id selector' - ); - assert.equal( - layer.find('.rectName')[0].attrs.id, - 'rectId', - 'problem with shape name selector' - ); - assert.equal( - layer.find('#rectId')[0].attrs.id, - 'rectId', - 'problem with shape id selector' - ); - assert.equal( - group.find('.rectName')[0].attrs.id, - 'rectId', - 'problem with shape name selector' - ); - assert.equal( - group.find('#rectId')[0].attrs.id, - 'rectId', - 'problem with shape id selector' - ); - - assert.equal( - stage.find('.groupName')[0].attrs.id, - 'groupId', - 'problem with group name selector' - ); - assert.equal( - stage.find('#groupId')[0].attrs.id, - 'groupId', - 'problem with group id selector' - ); - assert.equal( - layer.find('.groupName')[0].attrs.id, - 'groupId', - 'problem with group name selector' - ); - assert.equal( - layer.find('#groupId')[0].attrs.id, - 'groupId', - 'problem with group id selector' - ); - - assert.equal( - stage.find('.layerName')[0].attrs.id, - 'layerId', - 'problem with layer name selector' - ); - assert.equal( - stage.find('#layerId')[0].attrs.id, - 'layerId', - 'problem with layer id selector' - ); - }); - - // ====================================================== - it('test find() selector by adding layer, then group, then shape', function () { - var stage = addStage(); - var layer = new Konva.Layer({ - name: 'layerName', - id: 'layerId', - }); - var group = new Konva.Group({ - name: 'groupName', - id: 'groupId', - }); - var rect = new Konva.Rect({ - x: 200, - y: 20, - width: 100, - height: 50, - fill: 'red', - stroke: 'black', - strokeWidth: 4, - name: 'rectName', - id: 'rectId', - }); - - stage.add(layer); - layer.add(group); - group.add(rect); - - assert.equal( - stage.find('.rectName')[0].attrs.id, - 'rectId', - 'problem with shape name selector' - ); - assert.equal( - stage.find('#rectId')[0].attrs.id, - 'rectId', - 'problem with shape id selector' - ); - assert.equal( - layer.find('.rectName')[0].attrs.id, - 'rectId', - 'problem with shape name selector' - ); - assert.equal( - layer.find('#rectId')[0].attrs.id, - 'rectId', - 'problem with shape id selector' - ); - assert.equal( - group.find('.rectName')[0].attrs.id, - 'rectId', - 'problem with shape name selector' - ); - assert.equal( - group.find('#rectId')[0].attrs.id, - 'rectId', - 'problem with shape id selector' - ); - - assert.equal( - stage.find('.groupName')[0].attrs.id, - 'groupId', - 'problem with group name selector' - ); - assert.equal( - stage.find('#groupId')[0].attrs.id, - 'groupId', - 'problem with group id selector' - ); - assert.equal( - layer.find('.groupName')[0].attrs.id, - 'groupId', - 'problem with group name selector' - ); - assert.equal( - layer.find('#groupId')[0].attrs.id, - 'groupId', - 'problem with group id selector' - ); - - assert.equal( - stage.find('.layerName')[0].attrs.id, - 'layerId', - 'problem with layer name selector' - ); - assert.equal( - stage.find('#layerId')[0].attrs.id, - 'layerId', - 'problem with layer id selector' - ); - - layer.draw(); - }); - - it('warn on invalid selector', function () { - var stage = addStage(); - var layer = new Konva.Layer({ - name: 'layerName', - id: 'layerId', - }); - var group = new Konva.Group({ - name: 'groupName', - id: 'groupId', - }); - var rect = new Konva.Rect({ - x: 200, - y: 20, - width: 100, - height: 50, - fill: 'red', - stroke: 'black', - strokeWidth: 4, - name: 'rectName', - id: 'rectId', - }); - - stage.add(layer); - layer.add(group); - group.add(rect); - layer.draw(); - - var counter = 0; - var oldWarn = Konva.Util.warn; - Konva.Util.warn = function () { - counter += 1; - }; - - // forgot dot - group.find('rectName'); - assert.equal(counter > 0, true); - Konva.Util.warn = oldWarn; - }); - - // ====================================================== - it('add layer then shape', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - }); - - stage.add(layer); - layer.add(circle); - layer.draw(); - - assert(layer.getChildren().length === 1); - }); - - // ====================================================== - it('move blue layer on top of green layer with setZIndex', function () { - var stage = addStage(); - var blueLayer = new Konva.Layer(); - var greenLayer = new Konva.Layer(); - - var bluecircle = new Konva.Circle({ - x: 200, - y: stage.height() / 2, - radius: 70, - fill: 'blue', - stroke: 'black', - strokeWidth: 4, - }); - - var greencircle = new Konva.Circle({ - x: 280, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - }); - - blueLayer.add(bluecircle); - greenLayer.add(greencircle); - - stage.add(blueLayer); - stage.add(greenLayer); - - blueLayer.setZIndex(1); - - //console.log(greenLayer.getZIndex()); - - assert.equal( - greenLayer.getZIndex(), - 0, - 'green layer should have z index of 0' - ); - assert.equal( - blueLayer.getZIndex(), - 1, - 'blue layer should have z index of 1' - ); - }); - - // ====================================================== - it('move blue layer on top of green layer with moveToTop', function () { - var stage = addStage(); - var blueLayer = new Konva.Layer(); - var greenLayer = new Konva.Layer(); - - var bluecircle = new Konva.Circle({ - x: 200, - y: stage.height() / 2, - radius: 70, - fill: 'blue', - stroke: 'black', - strokeWidth: 4, - }); - - var greencircle = new Konva.Circle({ - x: 280, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - }); - - blueLayer.add(bluecircle); - greenLayer.add(greencircle); - - stage.add(blueLayer); - stage.add(greenLayer); - - blueLayer.moveToTop(); - - assert(blueLayer.zIndex() === 1); - assert(greenLayer.zIndex() === 0); - }); - - // ====================================================== - it('move green layer below blue layer with moveToBottom', function () { - var stage = addStage(); - var blueLayer = new Konva.Layer(); - var greenLayer = new Konva.Layer(); - - var bluecircle = new Konva.Circle({ - x: 200, - y: stage.height() / 2, - radius: 70, - fill: 'blue', - stroke: 'black', - strokeWidth: 4, - }); - - var greencircle = new Konva.Circle({ - x: 280, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - }); - - blueLayer.add(bluecircle); - greenLayer.add(greencircle); - - stage.add(blueLayer); - stage.add(greenLayer); - - greenLayer.moveToBottom(); - - assert(blueLayer.zIndex() === 1); - assert(greenLayer.zIndex() === 0); - }); - - // ====================================================== - it('move green layer below blue layer with moveDown', function () { - var stage = addStage(); - var blueLayer = new Konva.Layer(); - var greenLayer = new Konva.Layer(); - - var bluecircle = new Konva.Circle({ - x: 200, - y: stage.height() / 2, - radius: 70, - fill: 'blue', - stroke: 'black', - strokeWidth: 4, - }); - - var greencircle = new Konva.Circle({ - x: 280, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - }); - - blueLayer.add(bluecircle); - greenLayer.add(greencircle); - - stage.add(blueLayer); - stage.add(greenLayer); - greenLayer.moveDown(); - - assert(blueLayer.zIndex() === 1); - assert(greenLayer.zIndex() === 0); - }); - - // ====================================================== - it('move blue layer above green layer with moveUp', function () { - var stage = addStage(); - var blueLayer = new Konva.Layer(); - var greenLayer = new Konva.Layer(); - - var bluecircle = new Konva.Circle({ - x: 200, - y: stage.height() / 2, - radius: 70, - fill: 'blue', - stroke: 'black', - strokeWidth: 4, - }); - - var greencircle = new Konva.Circle({ - x: 280, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - }); - - blueLayer.add(bluecircle); - greenLayer.add(greencircle); - - stage.add(blueLayer); - stage.add(greenLayer); - blueLayer.moveUp(); - - assert(blueLayer.zIndex() === 1); - assert(greenLayer.zIndex() === 0); - }); - - // ====================================================== - it('move blue circle on top of green circle with moveToTop', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var bluecircle = new Konva.Circle({ - x: 200, - y: stage.height() / 2, - radius: 70, - fill: 'blue', - stroke: 'black', - strokeWidth: 4, - }); - - var greencircle = new Konva.Circle({ - x: 280, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - }); - - layer.add(bluecircle); - layer.add(greencircle); - stage.add(layer); - - assert.equal( - bluecircle.getZIndex(), - 0, - 'blue circle should have zindex 0 before relayering' - ); - assert.equal( - greencircle.getZIndex(), - 1, - 'green circle should have zindex 1 before relayering' - ); - - bluecircle.moveToTop(); - - assert.equal( - bluecircle.getZIndex(), - 1, - 'blue circle should have zindex 1 after relayering' - ); - assert.equal( - greencircle.getZIndex(), - 0, - 'green circle should have zindex 0 after relayering' - ); - - layer.draw(); - }); - - // ====================================================== - it('move green circle below blue circle with moveDown', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var bluecircle = new Konva.Circle({ - x: 200, - y: stage.height() / 2, - radius: 70, - fill: 'blue', - stroke: 'black', - strokeWidth: 4, - }); - - var greencircle = new Konva.Circle({ - x: 280, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - }); - - layer.add(bluecircle); - layer.add(greencircle); - stage.add(layer); - - assert.equal( - bluecircle.getZIndex(), - 0, - 'blue circle should have zindex 0 before relayering' - ); - assert.equal( - greencircle.getZIndex(), - 1, - 'green circle should have zindex 1 before relayering' - ); - - greencircle.moveDown(); - - assert.equal( - bluecircle.getZIndex(), - 1, - 'blue circle should have zindex 1 after relayering' - ); - assert.equal( - greencircle.getZIndex(), - 0, - 'green circle should have zindex 0 after relayering' - ); - - layer.draw(); - }); - - // ====================================================== - it('layer layer when only one layer', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var bluecircle = new Konva.Circle({ - x: 200, - y: stage.height() / 2, - radius: 70, - fill: 'blue', - stroke: 'black', - strokeWidth: 4, - }); - - layer.add(bluecircle); - stage.add(layer); - - assert.equal(layer.getZIndex(), 0, 'layer should have zindex of 0'); - - layer.moveDown(); - assert.equal(layer.getZIndex(), 0, 'layer should have zindex of 0'); - - layer.moveToBottom(); - assert.equal(layer.getZIndex(), 0, 'layer should have zindex of 0'); - - layer.moveUp(); - assert.equal(layer.getZIndex(), 0, 'layer should have zindex of 0'); - - layer.moveToTop(); - assert.equal(layer.getZIndex(), 0, 'layer should have zindex of 0'); - }); - - // ====================================================== - it('move blue group on top of green group with moveToTop', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var greenGroup = new Konva.Group(); - var blueGroup = new Konva.Group(); - - var bluecircle = new Konva.Circle({ - x: 200, - y: stage.height() / 2, - radius: 70, - fill: 'blue', - stroke: 'black', - strokeWidth: 4, - }); - - var greencircle = new Konva.Circle({ - x: 280, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - }); - - blueGroup.add(bluecircle); - greenGroup.add(greencircle); - - layer.add(blueGroup); - layer.add(greenGroup); - stage.add(layer); - - assert.equal( - blueGroup.getZIndex(), - 0, - 'blue group should have zindex 0 before relayering' - ); - assert.equal( - greenGroup.getZIndex(), - 1, - 'green group should have zindex 1 before relayering' - ); - - blueGroup.moveToTop(); - - assert.equal( - blueGroup.getZIndex(), - 1, - 'blue group should have zindex 1 after relayering' - ); - assert.equal( - greenGroup.getZIndex(), - 0, - 'green group should have zindex 0 after relayering' - ); - - layer.draw(); - }); - - // ====================================================== - it('move blue group on top of green group with moveUp', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var greenGroup = new Konva.Group(); - var blueGroup = new Konva.Group(); - - var bluecircle = new Konva.Circle({ - x: 200, - y: stage.height() / 2, - radius: 70, - fill: 'blue', - stroke: 'black', - strokeWidth: 4, - }); - - var greencircle = new Konva.Circle({ - x: 280, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - }); - - blueGroup.add(bluecircle); - greenGroup.add(greencircle); - - layer.add(blueGroup); - layer.add(greenGroup); - stage.add(layer); - - assert.equal( - blueGroup.getZIndex(), - 0, - 'blue group should have zindex 0 before relayering' - ); - assert.equal( - greenGroup.getZIndex(), - 1, - 'green group should have zindex 1 before relayering' - ); - - blueGroup.moveUp(); - - assert.equal( - blueGroup.getZIndex(), - 1, - 'blue group should have zindex 1 after relayering' - ); - assert.equal( - greenGroup.getZIndex(), - 0, - 'green group should have zindex 0 after relayering' - ); - - layer.draw(); - }); - - // ====================================================== - it('add and moveTo should work same way (depend on parent)', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var greenGroup = new Konva.Group(); - var blueGroup = new Konva.Group(); - - var bluecircle = new Konva.Circle({ - x: 200, - y: stage.height() / 2, - radius: 70, - fill: 'blue', - stroke: 'black', - strokeWidth: 4, - }); - - bluecircle.moveTo(blueGroup); - - layer.add(blueGroup); - layer.add(greenGroup); - stage.add(layer); - - assert.equal( - blueGroup.getChildren().length, - 1, - 'blue group should have only one children' - ); - blueGroup.add(bluecircle); - assert.equal( - blueGroup.getChildren().length, - 1, - 'blue group should have only one children after adding node twice' - ); - - greenGroup.add(bluecircle); - assert.equal( - blueGroup.getChildren().length, - 0, - 'blue group should not have children' - ); - assert.equal( - greenGroup.getChildren().length, - 1, - 'green group should have only one children' - ); - - layer.draw(); - }); - - // ====================================================== - it('getChildren may use filter function', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var group = new Konva.Group(); - - var circle1 = new Konva.Circle({ - x: 200, - y: stage.height() / 2, - radius: 70, - fill: 'blue', - stroke: 'black', - strokeWidth: 4, - }); - var circle2 = circle1.clone(); - group.add(circle1).add(circle2); - - var rect = new Konva.Rect({ - name: 'test', - }); - group.add(rect); - - var circles = group.getChildren(function (node) { - return node.getClassName() === 'Circle'; - }); - assert.equal(circles.length, 2, 'group has two circle children'); - assert.equal(circles.indexOf(circle1) > -1, true); - assert.equal(circles.indexOf(circle2) > -1, true); - - var testName = group.getChildren(function (node) { - return node.name() === 'test'; - }); - - assert.equal(testName.length, 1, 'group has one children with test name'); - - layer.add(group); - - layer.draw(); - }); - - it('add multiple nodes to container', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle1 = new Konva.Circle({ - x: 0, - y: 0, - radius: 10, - fill: 'red', - }); - var circle2 = new Konva.Circle({ - x: 0, - y: 0, - radius: 10, - fill: 'white', - }); - var circle3 = new Konva.Circle({ - x: 0, - y: 0, - radius: 10, - fill: 'blue', - }); - layer.add(circle1, circle2, circle3); - assert.equal( - layer.getChildren().length, - 3, - 'layer has exactly three children' - ); - }); - it('getClientRect - adding a zero bounds shape should result in zero bounds', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - var grp = new Konva.Group(); - var zeroRect = new Konva.Rect({ x: 0, y: 0, width: 0, height: 0 }); - grp.add(zeroRect); - var bounds = grp.getClientRect(); - assert.deepEqual(bounds, { - x: 0, - y: 0, - width: 0, - height: 0, - }); - }); - it('getClientRect - test empty case', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - var group = new Konva.Group({ - x: 10, - y: 10, - }); - group.add(new Konva.Group()); - assert.deepEqual(group.getClientRect(), { - x: 10, - y: 10, - width: 0, - height: 0, - }); - }); - - it('get client rect with deep nested hidden shape', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - var group = new Konva.Group({ - draggable: true, - x: 100, - y: 40, - }); - - var rect = new Konva.Rect({ - height: 100, - width: 100, - fill: 'red', - }); - group.add(rect); - layer.add(group); - - var subGroup = new Konva.Group(); - group.add(subGroup); - - subGroup.add( - new Konva.Rect({ - visible: false, - }) - ); - - stage.add(layer); - stage.draw(); - - var clientRect = group.getClientRect(); - - assert.deepEqual(clientRect, { - x: 100, - y: 40, - width: 100, - height: 100, - }); - }); - - it('getClientRect - test group with invisible child', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - var group = new Konva.Group({ - x: 10, - y: 10, - }); - layer.add(group); - layer.draw(); - group.add( - new Konva.Rect({ - x: 0, - y: 0, - width: 50, - height: 50, - }) - ); - group.add( - new Konva.Rect({ - x: 400, - y: 400, - width: 50, - height: 50, - visible: false, - }) - ); - assert.deepEqual(group.getClientRect(), { - x: 10, - y: 10, - width: 50, - height: 50, - }); - }); - - it('getClientRect - test group with invisible child inside invisible parent', function () { - var stage = addStage(); - var layer = new Konva.Layer({ - visible: false, - }); - stage.add(layer); - var group = new Konva.Group({ - x: 10, - y: 10, - }); - layer.add(group); - layer.draw(); - group.add( - new Konva.Rect({ - x: 0, - y: 0, - width: 50, - height: 50, - }) - ); - group.add( - new Konva.Rect({ - x: 400, - y: 400, - width: 50, - height: 50, - visible: false, - }) - ); - assert.deepEqual(group.getClientRect(), { - x: 10, - y: 10, - width: 50, - height: 50, - }); - }); - - it('getClientRect - test group with visible child inside invisible parent', function () { - var stage = addStage(); - var layer = new Konva.Layer({ - visible: false, - }); - stage.add(layer); - var group = new Konva.Group({ - x: 10, - y: 10, - }); - layer.add(group); - - var subGroup = new Konva.Group({ - visible: false, - }); - group.add(subGroup); - subGroup.add( - new Konva.Rect({ - x: 0, - y: 0, - width: 50, - height: 50, - visible: true, - }) - ); - subGroup.add( - new Konva.Rect({ - x: 400, - y: 400, - width: 50, - height: 50, - visible: true, - }) - ); - layer.draw(); - assert.deepEqual(group.getClientRect(), { - x: 10, - y: 10, - width: 0, - height: 0, - }); - }); - - it('get client rect with deep nested hidden shape 2', function () { - var layer = new Konva.Layer(); - var group = new Konva.Group({ - visible: false, - x: 100, - y: 40, - }); - - var rect = new Konva.Rect({ - height: 100, - width: 100, - fill: 'red', - }); - group.add(rect); - layer.add(group); - - var clientRect = layer.getClientRect(); - - assert.deepEqual(clientRect, { - x: 0, - y: 0, - width: 0, - height: 0, - }); - }); - - it('getClientRect - test layer', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var group = new Konva.Group(); - - var rect = new Konva.Rect({ - x: 50, - y: 100, - width: 200, - height: 75, - fill: 'red', - }); - - group.add(rect); - layer.add(group); - stage.add(layer); - - assert.deepEqual(layer.getClientRect(), { - x: 50, - y: 100, - width: 200, - height: 75, - }); - }); - - it('getClientRect - nested group with a hidden shapes', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - stage.add(layer); - - var group1 = new Konva.Group(); - layer.add(group1); - - var rect = new Konva.Rect({ - x: 50, - y: 100, - width: 200, - height: 75, - fill: 'red', - }); - group1.add(rect); - - var group2 = new Konva.Group(); - layer.add(group2); - - var rect2 = new Konva.Rect({ - x: 0, - y: 0, - width: 200, - height: 75, - fill: 'red', - visible: false, - }); - group1.add(rect2); - - assert.deepEqual(layer.getClientRect(), { - x: 50, - y: 100, - width: 200, - height: 75, - }); - }); - - it('clip-cache', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var group = new Konva.Group(); - var circle = new Konva.Circle({ - x: 50, - y: 50, - radius: 30, - fill: 'red', - }); - - group.add(circle); - layer.add(group.clip({ x: 25, y: 25, width: 50, height: 50 })); - stage.add(layer); - - layer.cache(); - stage.draw(); - - var data = layer.getContext().getImageData(24, 50, 1, 1).data; - var isTransparent = data[3] == 0; - assert.equal( - isTransparent, - true, - 'tested pixel (24,50) should be transparent: ' + - data[0] + - '_' + - data[1] + - '_' + - data[2] + - '_' + - data[3] - ); - - data = layer.getContext().getImageData(50, 24, 1, 1).data; - isTransparent = data[3] == 0; - assert.equal( - isTransparent, - true, - 'tested pixel (50,24) should be transparent: ' + - data[0] + - '_' + - data[1] + - '_' + - data[2] + - '_' + - data[3] - ); - - data = layer.getHitCanvas().getContext().getImageData(76, 50, 1, 1).data; - isTransparent = data[3] == 0; - - assert.equal( - isTransparent, - true, - 'tested pixel (76,50) should be transparent: ' + - data[0] + - '_' + - data[1] + - '_' + - data[2] + - '_' + - data[3] - ); - - data = layer.getHitCanvas().getContext().getImageData(50, 76, 1, 1).data; - isTransparent = data[3] == 0; - assert.equal( - isTransparent, - true, - 'tested pixel (50,76) should be transparent: ' + - data[0] + - '_' + - data[1] + - '_' + - data[2] + - '_' + - data[3] - ); - - var ratio = layer.getCanvas().getPixelRatio(); - data = layer.getContext().getImageData(26 * ratio, 50 * ratio, 1, 1).data; - var isRed = - data[0] == 255 && data[1] == 0 && data[2] == 0 && data[3] == 255; - assert.equal( - isRed, - true, - 'tested pixel (26,50) should be red: ' + - data[0] + - '_' + - data[1] + - '_' + - data[2] + - '_' + - data[3] - ); - - data = layer.getContext().getImageData(50 * ratio, 26 * ratio, 1, 1).data; - isRed = data[0] == 255 && data[1] == 0 && data[2] == 0 && data[3] == 255; - assert.equal( - isRed, - true, - 'tested pixel (50,26) should be red: ' + - data[0] + - '_' + - data[1] + - '_' + - data[2] + - '_' + - data[3] - ); - - data = layer.getContext().getImageData(74 * ratio, 50 * ratio, 1, 1).data; - isRed = data[0] == 255 && data[1] == 0 && data[2] == 0 && data[3] == 255; - assert.equal( - isRed, - true, - 'tested pixel (74,50) should be red: ' + - data[0] + - '_' + - data[1] + - '_' + - data[2] + - '_' + - data[3] - ); - - data = layer.getContext().getImageData(50 * ratio, 74 * ratio, 1, 1).data; - isRed = data[0] == 255 && data[1] == 0 && data[2] == 0 && data[3] == 255; - assert.equal( - isRed, - true, - 'tested pixel (50,74) should be red: ' + - data[0] + - '_' + - data[1] + - '_' + - data[2] + - '_' + - data[3] - ); - }); - - it('clip-cache-scale', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var group = new Konva.Group(); - var circle = new Konva.Circle({ - x: 50, - y: 50, - radius: 30, - fill: 'red', - }); - - group.add(circle); - layer.add(group.clip({ x: 25, y: 25, width: 50, height: 50 })); - stage.add(layer); - - layer.cache(); - stage.scale({ x: 2, y: 2 }); - stage.draw(); - - var data = layer.getHitCanvas().getContext().getImageData(48, 100, 1, 1) - .data; - var isTransparent = data[3] == 0; - assert.equal( - isTransparent, - true, - 'tested pixel (48,100) should be transparent: ' + - data[0] + - '_' + - data[1] + - '_' + - data[2] + - '_' + - data[3] - ); - - data = layer.getHitCanvas().getContext().getImageData(100, 48, 1, 1).data; - isTransparent = data[3] == 0; - assert.equal( - isTransparent, - true, - 'tested pixel (100,48) should be transparent: ' + - data[0] + - '_' + - data[1] + - '_' + - data[2] + - '_' + - data[3] - ); - - data = layer.getHitCanvas().getContext().getImageData(152, 100, 1, 1).data; - isTransparent = data[3] == 0; - assert.equal( - isTransparent, - true, - 'tested pixel (152,100) should be transparent: ' + - data[0] + - '_' + - data[1] + - '_' + - data[2] + - '_' + - data[3] - ); - - data = layer.getHitCanvas().getContext().getImageData(100, 152, 1, 1).data; - isTransparent = data[3] == 0; - assert.equal( - isTransparent, - true, - 'tested pixel (100,152) should be transparent: ' + - data[0] + - '_' + - data[1] + - '_' + - data[2] + - '_' + - data[3] - ); - - var ratio = layer.getCanvas().getPixelRatio(); - data = layer.getContext().getImageData(52 * ratio, 100 * ratio, 1, 1).data; - var isRed = - data[0] == 255 && data[1] == 0 && data[2] == 0 && data[3] == 255; - assert.equal( - isRed, - true, - 'tested pixel (52,100) should be red: ' + - data[0] + - '_' + - data[1] + - '_' + - data[2] + - '_' + - data[3] - ); - - data = layer.getContext().getImageData(100 * ratio, 52 * ratio, 1, 1).data; - isRed = data[0] == 255 && data[1] == 0 && data[2] == 0 && data[3] == 255; - assert.equal( - isRed, - true, - 'tested pixel (100,52) should be red: ' + - data[0] + - '_' + - data[1] + - '_' + - data[2] + - '_' + - data[3] - ); - - data = layer.getContext().getImageData(148 * ratio, 100 * ratio, 1, 1).data; - isRed = data[0] == 255 && data[1] == 0 && data[2] == 0 && data[3] == 255; - assert.equal( - isRed, - true, - 'tested pixel (148,100) should be red: ' + - data[0] + - '_' + - data[1] + - '_' + - data[2] + - '_' + - data[3] - ); - - data = layer.getContext().getImageData(100 * ratio, 148 * ratio, 1, 1).data; - isRed = data[0] == 255 && data[1] == 0 && data[2] == 0 && data[3] == 255; - assert.equal( - isRed, - true, - 'tested pixel (100,148) should be red: ' + - data[0] + - '_' + - data[1] + - '_' + - data[2] + - '_' + - data[3] - ); - }); -}); diff --git a/test/unit/Context-test.ts b/test/unit/Context-test.ts deleted file mode 100644 index 7cf6fc5d8..000000000 --- a/test/unit/Context-test.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { assert } from 'chai'; -import { addStage, Konva } from './test-utils'; - -describe('Context', function () { - // ====================================================== - var contextMethods = [ - 'clearRect', - 'fillRect', - 'strokeRect', - 'fillText', - 'strokeText', - 'measureText', - 'createLinearGradient', - 'createRadialGradient', - 'createPattern', - 'beginPath', - 'closePath', - 'moveTo', - 'lineTo', - 'bezierCurveTo', - 'quadraticCurveTo', - 'arc', - 'arcTo', - 'rect', - 'roundRect', - 'ellipse', - 'fill', - 'stroke', - 'clip', - 'isPointInPath', - 'scale', - 'rotate', - 'translate', - 'transform', - 'setTransform', - 'drawImage', - 'createImageData', - 'getImageData', - 'putImageData', - 'save', - 'restore', - ]; - - var contextProperties = [ - 'fillStyle', - 'strokeStyle', - 'shadowColor', - 'shadowBlur', - 'shadowOffsetX', - 'shadowOffsetY', - 'lineCap', - 'lineDashOffset', - 'lineJoin', - 'lineWidth', - 'miterLimit', - 'font', - 'textAlign', - 'textBaseline', - 'globalAlpha', - 'globalCompositeOperation', - ] as const; - - it('context wrapper should work like native context', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - - var circle = new Konva.Circle({ - x: 100, - y: 70, - radius: 70, - fill: 'green', - stroke: 'blue', - strokeWidth: 4, - draggable: true, - }); - - layer.add(circle); - stage.add(layer); - - var context = layer.getContext(); - var nativeContext = context._context; - - contextMethods.forEach(function (method) { - assert.equal( - typeof nativeContext[method], - 'function', - 'native context has no method ' + method - ); - assert.equal( - typeof context[method], - 'function', - 'context wrapper has no method ' + method - ); - }); - - contextProperties.forEach(function (prop) { - assert.equal( - nativeContext[prop] !== undefined, - true, - 'native context has no property ' + prop - ); - assert.equal( - context[prop] !== undefined, - true, - 'context wrapper has no property ' + prop - ); - }); - - // test get - nativeContext.fillStyle = '#ff0000'; - assert.equal(context.fillStyle, '#ff0000'); - - // test set - context.globalAlpha = 0.5; - assert.equal(context.globalAlpha, 0.5); - }); -}); diff --git a/test/unit/DragAndDrop-test.ts b/test/unit/DragAndDrop-test.ts deleted file mode 100644 index c734fcec9..000000000 --- a/test/unit/DragAndDrop-test.ts +++ /dev/null @@ -1,1282 +0,0 @@ -import { assert } from 'chai'; -import { - addStage, - Konva, - simulateMouseDown, - simulateMouseMove, - simulateMouseUp, - simulateTouchStart, - simulateTouchEnd, - simulateTouchMove, -} from './test-utils'; - -describe('DragAndDrop', function () { - // ====================================================== - it('test drag and drop properties and methods', function (done) { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - }); - - stage.add(layer); - layer.add(circle); - - setTimeout(function () { - layer.draw(); - - // test defaults - assert.equal(circle.draggable(), false); - - //change properties - circle.setDraggable(true); - - //circle.on('click', function(){}); - - layer.draw(); - - // test new properties - assert.equal(circle.draggable(), true); - - done(); - }, 50); - }); - - // ====================================================== - it('multiple drag and drop sets with setDraggable()', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: 380, - y: stage.height() / 2, - radius: 70, - strokeWidth: 4, - fill: 'red', - stroke: 'black', - }); - - circle.setDraggable(true); - assert.equal(circle.draggable(), true); - circle.setDraggable(true); - assert.equal(circle.draggable(), true); - circle.setDraggable(false); - assert.equal(!circle.draggable(), true); - - layer.add(circle); - stage.add(layer); - }); - - // ====================================================== - it('right click is not for dragging', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - draggable: true, - }); - - layer.add(circle); - stage.add(layer); - - simulateMouseDown(stage, { - x: 291, - y: 112, - }); - - simulateMouseMove(stage, { - x: 311, - y: 112, - }); - - assert(circle.isDragging(), 'dragging is ok'); - - simulateMouseUp(stage, { - x: 291, - y: 112, - }); - - assert(!circle.isDragging(), 'drag stopped'); - - simulateMouseDown(stage, { - x: 291, - y: 112, - button: 2, - }); - - simulateMouseMove(stage, { - x: 311, - y: 112, - button: 2, - }); - - assert(circle.isDragging() === false, 'no dragging with right click'); - - Konva.dragButtons = [0, 2]; - simulateMouseUp(stage, { - x: 291, - y: 112, - button: 2, - }); - - // simulate buttons change - simulateMouseDown(stage, { - x: 291, - y: 112, - button: 2, - }); - - simulateMouseMove(stage, { - x: 311, - y: 112, - button: 2, - }); - - assert(circle.isDragging() === true, 'now dragging with right click'); - - simulateMouseUp(stage, { - x: 291, - y: 112, - button: 2, - }); - Konva.dragButtons = [0]; - }); - - // ====================================================== - it('changing draggable on mousedown should take effect', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - }); - - layer.add(circle); - stage.add(layer); - - circle.on('mousedown', () => { - circle.draggable(true); - }); - - simulateMouseDown(stage, { - x: circle.x(), - y: circle.y(), - }); - - simulateMouseMove(stage, { - x: circle.x() + 10, - y: circle.y() + 10, - }); - - assert.equal(circle.isDragging(), true); - - simulateMouseUp(stage, { - x: circle.x() + 10, - y: circle.y() + 10, - }); - }); - - // ====================================================== - it('while dragging do not draw hit', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - stage.add(layer); - - var dragLayer = new Konva.Layer(); - stage.add(dragLayer); - - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - draggable: true, - }); - - dragLayer.add(circle); - dragLayer.draw(); - - var rect = new Konva.Rect({ - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - width: 50, - height: 50, - draggable: true, - }); - layer.add(rect); - layer.draw(); - - var shape = layer.getIntersection({ - x: 2, - y: 2, - }); - - assert.equal(shape, rect, 'rect is detected'); - - simulateMouseDown(stage, { - x: stage.width() / 2, - y: stage.height() / 2, - }); - - simulateMouseMove(stage, { - x: stage.width() / 2 + 5, - y: stage.height() / 2, - }); - - // redraw layer. hit must be not touched for not dragging layer - layer.draw(); - shape = layer.getIntersection({ - x: 2, - y: 2, - }); - assert.equal(shape, rect, 'rect is still detected'); - - assert(circle.isDragging(), 'dragging is ok'); - - dragLayer.draw(); - shape = dragLayer.getIntersection({ - x: stage.width() / 2, - y: stage.height() / 2, - }); - // as dragLayer under dragging we should not able to detect intersect - assert.equal(!!shape, false, 'circle is not detected'); - - simulateMouseUp(stage, { - x: 291, - y: 112, - }); - }); - - // ====================================================== - it('it is possible to change layer while dragging', function () { - var stage = addStage(); - - var startDragLayer = new Konva.Layer(); - stage.add(startDragLayer); - - var endDragLayer = new Konva.Layer(); - stage.add(endDragLayer); - - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - draggable: true, - }); - - startDragLayer.add(circle); - startDragLayer.draw(); - - var rect = new Konva.Rect({ - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - width: 50, - height: 50, - draggable: true, - }); - endDragLayer.add(rect); - endDragLayer.draw(); - - simulateMouseDown(stage, { - x: stage.width() / 2, - y: stage.height() / 2, - }); - - simulateMouseMove(stage, { - x: stage.width() / 2 + 5, - y: stage.height() / 2, - }); - - // change layer while dragging circle - circle.moveTo(endDragLayer); - // move rectange for test hit update - rect.moveTo(startDragLayer); - startDragLayer.draw(); - - var shape = startDragLayer.getIntersection({ - x: 2, - y: 2, - }); - assert.equal(shape, rect, 'rect is detected'); - - assert(circle.isDragging(), 'dragging is ok'); - - endDragLayer.draw(); - shape = endDragLayer.getIntersection({ - x: stage.width() / 2, - y: stage.height() / 2, - }); - // as endDragLayer under dragging we should not able to detect intersect - assert.equal(!!shape, false, 'circle is not detected'); - - simulateMouseUp(stage, { - x: 291, - y: 112, - }); - }); - - // ====================================================== - it('removing parent of draggable node should not throw error', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - var circle = new Konva.Circle({ - x: 380, - y: stage.height() / 2, - radius: 70, - strokeWidth: 4, - fill: 'red', - stroke: 'black', - draggable: true, - }); - - layer.add(circle); - simulateMouseMove(stage, { - x: stage.width() / 2 + 5, - y: stage.height() / 2, - }); - - circle.startDrag(); - try { - layer.destroy(); - assert.equal(true, true, 'no error, that is very good'); - } catch (e) { - assert.equal(true, false, 'error happened'); - } - }); - - it('update hit on stage drag end', function (done) { - var stage = addStage(); - - stage.draggable(true); - - var layer = new Konva.Layer(); - stage.add(layer); - - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - }); - - layer.add(circle); - layer.draw(); - - simulateMouseDown(stage, { - x: stage.width() / 2, - y: stage.height() / 2, - }); - - simulateMouseMove(stage, { - x: stage.width() / 2 - 50, - y: stage.height() / 2, - }); - - setTimeout(function () { - assert.equal(stage.isDragging(), true); - simulateMouseUp(stage, { - x: stage.width() / 2 - 50, - y: stage.height() / 2, - }); - setTimeout(function () { - var shape = layer.getIntersection({ - x: stage.width() / 2 + 5, - y: stage.height() / 2, - }); - - assert.equal(shape, circle); - done(); - }, 100); - }, 50); - }); - - it('removing shape while drag and drop should no throw error', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - draggable: true, - }); - - layer.add(circle); - stage.add(layer); - - simulateMouseDown(stage, { - x: 291, - y: 112, - }); - - circle.remove(); - - simulateMouseMove(stage, { - x: 311, - y: 112, - }); - - simulateMouseUp(stage, { - x: 291, - y: 112, - button: 2, - }); - - assert(Konva.isDragging() === false); - }); - - it('destroying shape while drag and drop should no throw error', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - draggable: true, - }); - - layer.add(circle); - stage.add(layer); - - simulateMouseDown(stage, { - x: 291, - y: 112, - }); - - circle.destroy(); - - simulateMouseMove(stage, { - x: 311, - y: 112, - }); - - simulateMouseUp(stage, { - x: 291, - y: 112, - }); - - assert(Konva.isDragging() === false); - }); - - it('drag start should trigger before movement', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var circle = new Konva.Circle({ - x: 70, - y: 70, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - draggable: true, - }); - - layer.add(circle); - stage.add(layer); - - circle.on('dragstart', function () { - assert.equal(circle.x(), 70); - assert.equal(circle.y(), 70); - }); - - simulateMouseDown(stage, { - x: 70, - y: 70, - }); - - simulateMouseMove(stage, { - x: 100, - y: 100, - }); - - simulateMouseUp(stage, { - x: 100, - y: 100, - }); - }); - - it('drag with touch', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var circle = new Konva.Circle({ - x: 70, - y: 70, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - draggable: true, - }); - - layer.add(circle); - stage.add(layer); - - circle.on('dragstart', function () { - assert.equal(circle.x(), 70); - assert.equal(circle.y(), 70); - }); - - simulateTouchStart(stage, { - x: 70, - y: 70, - }); - - simulateTouchMove(stage, { - x: 100, - y: 100, - }); - - simulateTouchEnd(stage, { - x: 100, - y: 100, - }); - assert.equal(circle.x(), 100); - assert.equal(circle.y(), 100); - }); - - it('drag with multi-touch (second finger on empty space)', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var circle = new Konva.Circle({ - x: 70, - y: 70, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - draggable: true, - }); - - layer.add(circle); - stage.add(layer); - - circle.on('dragstart', function () { - assert.equal(circle.x(), 70); - assert.equal(circle.y(), 70); - }); - - simulateTouchStart(stage, [ - { - x: 70, - y: 70, - id: 0, - }, - { - x: 270, - y: 270, - id: 1, - }, - ]); - - simulateTouchMove(stage, [ - { - x: 100, - y: 100, - id: 0, - }, - { - x: 270, - y: 270, - id: 1, - }, - ]); - - simulateTouchEnd(stage, [ - { - x: 100, - y: 100, - id: 0, - }, - { - x: 270, - y: 270, - id: 1, - }, - ]); - assert.equal(circle.x(), 100); - assert.equal(circle.y(), 100); - }); - - it('drag with multi-touch (two shapes)', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var circle1 = new Konva.Circle({ - x: 70, - y: 70, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - draggable: true, - }); - layer.add(circle1); - - var circle2 = new Konva.Circle({ - x: 270, - y: 70, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - draggable: true, - }); - layer.add(circle2); - layer.draw(); - - var dragstart1 = 0; - var dragmove1 = 0; - circle1.on('dragstart', function () { - dragstart1 += 1; - }); - circle1.on('dragmove', function () { - dragmove1 += 1; - }); - - var dragstart2 = 0; - var dragmove2 = 0; - circle2.on('dragstart', function () { - dragstart2 += 1; - }); - - circle2.on('dragmove', function () { - dragmove2 += 1; - }); - - simulateTouchStart(stage, [ - { - x: 70, - y: 70, - id: 0, - }, - { - x: 270, - y: 70, - id: 1, - }, - ]); - - // move one finger - simulateTouchMove( - stage, - [ - { - x: 100, - y: 100, - id: 0, - }, - { - x: 270, - y: 70, - id: 1, - }, - ], - [ - { - x: 100, - y: 100, - id: 0, - }, - ] - ); - - assert.equal(dragstart1, 1); - assert.equal(circle1.isDragging(), true); - assert.equal(dragmove1, 1); - assert.equal(dragmove2, 0); - assert.equal(circle1.x(), 100); - assert.equal(circle1.y(), 100); - - // move second finger - simulateTouchMove(stage, [ - { - x: 100, - y: 100, - id: 0, - }, - { - x: 290, - y: 70, - id: 1, - }, - ]); - - assert.equal(dragstart2, 1); - assert.equal(circle2.isDragging(), true); - assert.equal(dragmove2, 1); - assert.equal(circle2.x(), 290); - assert.equal(circle2.y(), 70); - - // remove first finger - simulateTouchEnd( - stage, - [ - { - x: 290, - y: 70, - id: 1, - }, - ], - [ - { - x: 100, - y: 100, - id: 0, - }, - ] - ); - assert.equal(circle1.isDragging(), false); - assert.equal(circle2.isDragging(), true); - assert.equal(Konva.DD.isDragging, true); - // remove first finger - simulateTouchEnd( - stage, - [], - [ - { - x: 290, - y: 70, - id: 1, - }, - ] - ); - assert.equal(circle2.isDragging(), false); - assert.equal(Konva.DD.isDragging, false); - }); - - it('drag with multi-touch (same shape)', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var circle1 = new Konva.Circle({ - x: 70, - y: 70, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - draggable: true, - }); - layer.add(circle1); - layer.draw(); - - var dragstart1 = 0; - var dragmove1 = 0; - circle1.on('dragstart', function () { - dragstart1 += 1; - }); - circle1.on('dragmove', function () { - dragmove1 += 1; - }); - - simulateTouchStart(stage, [ - { - x: 70, - y: 70, - id: 0, - }, - ]); - // move one finger - simulateTouchMove(stage, [ - { - x: 75, - y: 75, - id: 0, - }, - ]); - - simulateTouchStart( - stage, - [ - { - x: 75, - y: 75, - id: 0, - }, - { - x: 80, - y: 80, - id: 1, - }, - ], - [ - { - x: 80, - y: 80, - id: 1, - }, - ] - ); - - simulateTouchMove( - stage, - [ - { - x: 75, - y: 75, - id: 0, - }, - { - x: 85, - y: 85, - id: 1, - }, - ], - [ - { - x: 85, - y: 85, - id: 1, - }, - ] - ); - - assert.equal(dragstart1, 1); - assert.equal(circle1.isDragging(), true); - assert.equal(dragmove1, 1); - assert.equal(circle1.x(), 75); - assert.equal(circle1.y(), 75); - - // remove first finger - simulateTouchEnd( - stage, - [], - [ - { - x: 75, - y: 75, - id: 0, - }, - { - x: 85, - y: 85, - id: 1, - }, - ] - ); - }); - - it('can stop drag on dragstart without changing position later', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var circle = new Konva.Circle({ - x: 70, - y: 70, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - draggable: true, - }); - - layer.add(circle); - stage.add(layer); - - circle.on('dragstart', function () { - circle.stopDrag(); - }); - circle.on('dragmove', function () { - assert.equal(false, true, 'dragmove called!'); - }); - - simulateMouseDown(stage, { - x: 70, - y: 70, - }); - - simulateMouseMove(stage, { - x: 100, - y: 100, - }); - - simulateMouseUp(stage, { - x: 100, - y: 100, - }); - - assert.equal(circle.x(), 70); - assert.equal(circle.y(), 70); - }); - - it('can force drag at any time (when pointer already registered)', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var circle = new Konva.Circle({ - x: 70, - y: 70, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - draggable: true, - }); - - layer.add(circle); - stage.add(layer); - - simulateMouseMove(stage, { x: 70, y: 70 }); - circle.startDrag(); - assert.equal(circle.isDragging(), true); - simulateMouseMove(stage, { x: 80, y: 80 }); - simulateMouseUp(stage, { x: 80, y: 80 }); - assert.equal(circle.x(), 80); - assert.equal(circle.y(), 80); - }); - - it('can force drag at any time (when pointer not registered)', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var circle = new Konva.Circle({ - x: 70, - y: 70, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - draggable: true, - }); - - layer.add(circle); - stage.add(layer); - - circle.startDrag(); - assert.equal(circle.isDragging(), true); - // let us think that offset will be equal 0 if not registered pointers - simulateMouseMove(stage, { x: 80, y: 80 }); - simulateMouseUp(stage, { x: 80, y: 80 }); - - assert.equal(circle.x(), 80); - assert.equal(circle.y(), 80); - }); - - it('calling startDrag show still fire event when required', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var circle = new Konva.Circle({ - x: 70, - y: 70, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - draggable: true, - }); - - var dragstart = 0; - circle.on('dragstart', function () { - dragstart += 1; - }); - layer.add(circle); - stage.add(layer); - - // register pointer - simulateMouseMove(stage, { x: 70, y: 80 }); - circle.startDrag(); - assert.equal(dragstart, 1); - assert.equal(circle.isDragging(), true); - // moving by one pixel should move circle too - simulateMouseMove(stage, { x: 70, y: 81 }); - simulateMouseUp(stage, { x: 70, y: 81 }); - - assert.equal(circle.x(), 70); - assert.equal(circle.y(), 71); - }); - - it('make sure we have event object', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var circle = new Konva.Circle({ - x: 70, - y: 70, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - draggable: true, - }); - - circle.on('dragstart', function (e) { - assert.equal(e.evt === undefined, false); - }); - circle.on('dragmove', function (e) { - assert.equal(e.evt === undefined, false); - }); - circle.on('dragend', function (e) { - assert.equal(e.evt === undefined, false); - }); - layer.add(circle); - stage.add(layer); - - // register pointer - simulateMouseDown(stage, { x: 70, y: 80 }); - // moving by one pixel should move circle too - simulateMouseMove(stage, { x: 80, y: 80 }); - simulateMouseUp(stage, { x: 70, y: 80 }); - }); - - it('try nested dragging', function () { - var stage = addStage(); - var layer = new Konva.Layer({ - draggable: true, - }); - - var circle = new Konva.Circle({ - x: 70, - y: 70, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - draggable: true, - }); - - layer.add(circle); - stage.add(layer); - - layer.add(circle.clone({ x: 30, fill: 'red', draggable: false })); - - simulateMouseDown(stage, { x: 70, y: 70 }); - simulateMouseMove(stage, { x: 80, y: 80 }); - - assert.equal(circle.x(), 80); - assert.equal(circle.y(), 80); - assert.equal(layer.x(), 0); - assert.equal(layer.y(), 0); - // layer is not dragging, because drag is registered on circle - assert.equal(layer.isDragging(), false); - simulateMouseUp(stage, { x: 80, y: 80 }); - }); - - it('warn on bad dragBoundFunc', function () { - var stage = addStage(); - var layer = new Konva.Layer({ - draggable: true, - }); - - var circle = new Konva.Circle({ - x: 70, - y: 70, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - draggable: true, - dragBoundFunc: function () {} as any, - }); - - layer.add(circle); - stage.add(layer); - - var counter = 0; - var oldWarn = Konva.Util.warn; - Konva.Util.warn = function () { - counter += 1; - }; - simulateMouseDown(stage, { x: 70, y: 70 }); - simulateMouseMove(stage, { x: 80, y: 80 }); - simulateMouseUp(stage, { x: 80, y: 80 }); - assert.equal(counter > 0, true); - Konva.Util.warn = oldWarn; - }); - - it('deletage drag', function () { - var stage = addStage(); - stage.draggable(true); - var layer = new Konva.Layer(); - - var circle = new Konva.Circle({ - x: 70, - y: 70, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - }); - - layer.add(circle); - stage.add(layer); - - stage.on('dragstart', function (e) { - if (e.target === stage) { - stage.stopDrag(); - circle.startDrag(); - } - }); - - simulateMouseDown(stage, { x: 5, y: 5 }); - simulateMouseMove(stage, { x: 10, y: 10 }); - - assert.equal(circle.isDragging(), true); - assert.equal(stage.isDragging(), false); - - simulateMouseUp(stage, { x: 10, y: 10 }); - - assert.equal(circle.x(), 70); - assert.equal(circle.y(), 70); - }); - - it('disable drag on click', function () { - var stage = addStage(); - stage.draggable(true); - var layer = new Konva.Layer(); - stage.add(layer); - - var circle = new Konva.Circle({ - x: 70, - y: 70, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - draggable: true, - }); - layer.add(circle); - layer.draw(); - - circle.on('click', function () { - circle.draggable(false); - circle.draggable(true); - }); - - var dragstart = 0; - var dragend = 0; - - stage.on('dragstart', function (e) { - dragstart += 1; - }); - stage.on('dragend', function (e) { - dragend += 1; - }); - - simulateMouseDown(stage, { x: 70, y: 75 }); - simulateMouseUp(stage, { x: 70, y: 70 }); - - // drag events should not be called - assert.equal(dragstart, 0); - assert.equal(dragend, 0); - }); - - it('mouseleave should not occur after drag and drop', function (done) { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: 40, - y: 40, - radius: 20, - strokeWidth: 4, - fill: 'green', - stroke: 'black', - draggable: true, - }); - layer.add(circle); - stage.add(layer); - var mouseleave = false; - var dragend = false; - var mouseenter = true; - circle.on('mouseenter', function () { - mouseenter = true; - }); - circle.on('mouseleave', function () { - mouseleave = true; - }); - circle.on('dragend', function () { - dragend = true; - }); - simulateMouseMove(stage, { - x: 40, - y: 40, - }); - assert(mouseenter, 'mouseenter event should have been fired'); - simulateMouseDown(stage, { - x: 40, - y: 40, - }); - setTimeout(function () { - simulateMouseMove(stage, { - x: 100, - y: 100, - }); - setTimeout(() => { - simulateMouseUp(stage, { - x: 100, - y: 100, - }); - simulateMouseMove(stage, { - x: 102, - y: 102, - }); - assert(!mouseleave, 'mouseleave event should not have been fired'); - assert(dragend, 'dragend event should have been fired'); - done(); - }, 70); - }, 70); - }); -}); diff --git a/test/unit/DragAndDropEvents-test.ts b/test/unit/DragAndDropEvents-test.ts deleted file mode 100644 index 2eb0474d6..000000000 --- a/test/unit/DragAndDropEvents-test.ts +++ /dev/null @@ -1,662 +0,0 @@ -import { assert } from 'chai'; - -import { - addStage, - Konva, - simulateMouseDown, - simulateMouseMove, - simulateMouseUp, - simulatePointerDown, - simulatePointerMove, - simulatePointerUp, -} from './test-utils'; - -describe('DragAndDropEvents', function () { - // ====================================================== - it('test dragstart, dragmove, dragend', function (done) { - var stage = addStage(); - - var layer = new Konva.Layer(); - - var greenCircle = new Konva.Circle({ - x: 40, - y: 40, - radius: 20, - strokeWidth: 4, - fill: 'green', - stroke: 'black', - opacity: 0.5, - }); - - var circle = new Konva.Circle({ - x: 380, - y: stage.height() / 2, - radius: 70, - strokeWidth: 4, - fill: 'red', - stroke: 'black', - opacity: 0.5, - }); - - circle.setDraggable(true); - - layer.add(circle); - layer.add(greenCircle); - stage.add(layer); - - var dragStart = false; - var dragMove = false; - var dragEnd = false; - var events = []; - - circle.on('dragstart', function () { - dragStart = true; - }); - - circle.on('dragmove', function () { - dragMove = true; - }); - - circle.on('dragend', function () { - dragEnd = true; - events.push('dragend'); - }); - - circle.on('mouseup', function () { - events.push('mouseup'); - }); - - assert(!Konva.isDragging(), ' isDragging() should be false 1'); - assert(!Konva.isDragReady(), ' isDragReady()) should be false 2'); - - /* - * simulate drag and drop - */ - simulateMouseDown(stage, { - x: 380, - y: 98, - }); - assert(!dragStart, 'dragstart event should not have been triggered 3'); - //assert.equal(!dragMove, 'dragmove event should not have been triggered'); - assert(!dragEnd, 'dragend event should not have been triggered 4'); - - assert(!Konva.isDragging(), ' isDragging() should be false 5'); - assert(Konva.isDragReady(), ' isDragReady()) should be true 6'); - - setTimeout(function () { - simulateMouseMove(stage, { - x: 385, - y: 98, - }); - - assert(Konva.isDragging(), ' isDragging() should be true 7'); - assert(Konva.isDragReady(), ' isDragReady()) should be true 8'); - - assert(dragStart, 'dragstart event was not triggered 9'); - //assert.equal(dragMove, 'dragmove event was not triggered'); - assert(!dragEnd, 'dragend event should not have been triggered 10'); - - simulateMouseUp(stage, { - x: 385, - y: 98, - }); - - assert(dragStart, 'dragstart event was not triggered 11'); - assert(dragMove, 'dragmove event was not triggered 12'); - assert(dragEnd, 'dragend event was not triggered 13'); - - assert.equal( - events.toString(), - 'mouseup,dragend', - 'mouseup should occur before dragend 14' - ); - - assert(!Konva.isDragging(), ' isDragging() should be false 15'); - assert(!Konva.isDragReady(), ' isDragReady()) should be false 16'); - - //console.log(greenCircle.getPosition()); - //console.log(circle.getPosition()); - - assert.equal(greenCircle.x(), 40, 'green circle x should be 40'); - assert.equal(greenCircle.y(), 40, 'green circle y should be 40'); - assert.equal(circle.x(), 385, 'circle x should be 100'); - assert.equal(circle.y(), 100, 'circle y should be 100'); - - done(); - }, 20); - }); - - // ====================================================== - it('destroy shape while dragging', function (done) { - var stage = addStage(); - var layer = new Konva.Layer(); - - var greenCircle = new Konva.Circle({ - x: 40, - y: 40, - radius: 20, - strokeWidth: 4, - fill: 'green', - stroke: 'black', - opacity: 0.5, - }); - - var circle = new Konva.Circle({ - x: 380, - y: stage.height() / 2, - radius: 70, - strokeWidth: 4, - fill: 'red', - stroke: 'black', - opacity: 0.5, - }); - - circle.setDraggable(true); - - layer.add(circle); - layer.add(greenCircle); - stage.add(layer); - - var dragEnd = false; - - circle.on('dragend', function () { - dragEnd = true; - }); - - assert(!Konva.isDragging(), ' isDragging() should be false'); - assert(!Konva.isDragReady(), ' isDragReady()) should be false'); - - simulateMouseDown(stage, { - x: 380, - y: 98, - }); - - assert(!circle.isDragging(), 'circle should not be dragging'); - - setTimeout(function () { - simulateMouseMove(stage, { - x: 100, - y: 98, - }); - - assert(circle.isDragging(), 'circle should be dragging'); - assert(!dragEnd, 'dragEnd should not have fired yet'); - - // at this point, we are in drag and drop mode - - // removing or destroying the circle should trigger dragend - circle.destroy(); - layer.draw(); - - assert( - !circle.isDragging(), - 'destroying circle should stop drag and drop' - ); - assert(dragEnd, 'dragEnd should have fired'); - done(); - }, 20); - }); - - // ====================================================== - it('click should not occur after drag and drop', function (done) { - var stage = addStage(); - var layer = new Konva.Layer(); - - var circle = new Konva.Circle({ - x: 40, - y: 40, - radius: 20, - strokeWidth: 4, - fill: 'green', - stroke: 'black', - draggable: true, - }); - - layer.add(circle); - stage.add(layer); - - var clicked = false; - - circle.on('click', function () { - //console.log('click'); - clicked = true; - }); - - simulateMouseDown(stage, { - x: 40, - y: 40, - }); - - setTimeout(function () { - simulateMouseMove(stage, { - x: 100, - y: 100, - }); - - simulateMouseUp(stage, { - x: 100, - y: 100, - }); - - assert(!clicked, 'click event should not have been fired'); - - done(); - }, 20); - }); - - // TODO: how to solve it? - // hint: every shape has pointerId that indicates which pointer is dragging it - // but "pointer" event and mouse event has different pointerId - // so we need to find a way to match them - // should we save several pointers per shape? - // doesn't sound good - // switch to pointer only event handling? - it.skip('click should not occur after drag and drop', function (done) { - var stage = addStage(); - var layer = new Konva.Layer(); - - var circle = new Konva.Circle({ - x: 40, - y: 40, - radius: 20, - strokeWidth: 4, - fill: 'green', - stroke: 'black', - draggable: true, - }); - - layer.add(circle); - stage.add(layer); - - var clicked = false; - - stage.on('pointerclick', function () { - clicked = true; - }); - - simulatePointerDown(stage, { - x: 40, - y: 40, - }); - - setTimeout(function () { - simulatePointerMove(stage, { - x: 100, - y: 100, - }); - - simulatePointerUp(stage, { - x: 100, - y: 100, - }); - - assert(!clicked, 'click event should not have been fired'); - - done(); - }, 20); - }); - - // ====================================================== - it('drag and drop distance', function (done) { - var stage = addStage(); - var layer = new Konva.Layer(); - - var circle = new Konva.Circle({ - x: 40, - y: 40, - radius: 20, - strokeWidth: 4, - fill: 'green', - stroke: 'black', - draggable: true, - }); - - layer.add(circle); - stage.add(layer); - circle.dragDistance(4); - - simulateMouseDown(stage, { - x: 40, - y: 40, - }); - - setTimeout(function () { - simulateMouseMove(stage, { - x: 40, - y: 42, - }); - assert(!circle.isDragging(), 'still not dragging'); - simulateMouseMove(stage, { - x: 40, - y: 45, - }); - assert(circle.isDragging(), 'now circle is dragging'); - simulateMouseUp(stage, { - x: 41, - y: 45, - }); - - done(); - }, 20); - }); - - // ====================================================== - it('cancel drag and drop by setting draggable to false', function (done) { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: 380, - y: 100, - radius: 70, - strokeWidth: 4, - fill: 'red', - stroke: 'black', - draggable: true, - }); - - var dragStart = false; - var dragMove = false; - var dragEnd = false; - - circle.on('dragstart', function () { - dragStart = true; - }); - - circle.on('dragmove', function () { - dragMove = true; - }); - - circle.on('dragend', function () { - dragEnd = true; - }); - - circle.on('mousedown', function () { - circle.setDraggable(false); - }); - - layer.add(circle); - stage.add(layer); - - /* - * simulate drag and drop - */ - simulateMouseDown(stage, { - x: 380, - y: 100, - }); - - setTimeout(function () { - simulateMouseMove(stage, { - x: 100, - y: 100, - }); - - simulateMouseUp(stage, { - x: 100, - y: 100, - }); - - assert.equal(circle.getPosition().x, 380, 'circle x should be 380'); - assert.equal(circle.getPosition().y, 100, 'circle y should be 100'); - done(); - }, 20); - }); - - // ====================================================== - it('drag and drop layer', function (done) { - var stage = addStage(); - var layer = new Konva.Layer({ - sceneFunc: function () { - var context = this.getContext(); - context.beginPath(); - context.moveTo(200, 50); - context.lineTo(420, 80); - context.quadraticCurveTo(300, 100, 260, 170); - context.closePath(); - context.fillStyle = 'blue'; - context.fill(context); - }, - draggable: true, - }); - - var circle1 = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'red', - }); - - var circle2 = new Konva.Circle({ - x: 400, - y: stage.height() / 2, - radius: 70, - fill: 'green', - }); - - layer.add(circle1); - layer.add(circle2); - - stage.add(layer); - - /* - * simulate drag and drop - */ - simulateMouseDown(stage, { - x: 399, - y: 96, - }); - - setTimeout(function () { - simulateMouseMove(stage, { - x: 210, - y: 109, - }); - - simulateMouseUp(stage, { - x: 210, - y: 109, - }); - - //console.log(layer.getPosition()) - - assert.equal(layer.x(), -189, 'layer x should be -189'); - assert.equal(layer.y(), 13, 'layer y should be 13'); - - done(); - }, 20); - }); - - // ====================================================== - it('drag and drop stage', function (done) { - var stage = addStage({ draggable: true }); - - //stage.setDraggable(true); - - var layer = new Konva.Layer(); - - var circle = new Konva.Circle({ - x: 100, - y: 100, - radius: 70, - fill: 'red', - }); - - layer.add(circle); - stage.add(layer); - - assert.equal(stage.x(), 0); - assert.equal(stage.y(), 0); - - /* - * simulate drag and drop - */ - simulateMouseDown(stage, { - x: 0, - y: 100, - }); - - setTimeout(function () { - simulateMouseMove(stage, { - x: 300, - y: 110, - }); - - simulateMouseUp(stage, { - x: 300, - y: 110, - }); - - assert.equal(stage.x(), 300); - assert.equal(stage.y(), 10); - - done(); - }, 20); - }); - - it('click should not start drag&drop', function () { - var stage = addStage(); - var layer = new Konva.Layer({ - draggable: true, - }); - - var circle = new Konva.Circle({ - x: 70, - y: 70, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - draggable: true, - }); - - layer.add(circle); - stage.add(layer); - - var dragstart = 0; - circle.on('dragstart', function () { - dragstart += 1; - }); - - var dragmove = 0; - circle.on('dragmove', function () { - dragmove += 1; - }); - - var dragend = 0; - circle.on('dragend', function () { - dragend += 1; - }); - - var click = 0; - circle.on('click', function () { - click += 1; - }); - simulateMouseDown(stage, { x: 70, y: 70 }); - simulateMouseUp(stage, { x: 70, y: 70 }); - - assert.equal(click, 1, 'click triggered'); - assert.equal(dragstart, 0, 'dragstart not triggered'); - assert.equal(dragmove, 0, 'dragmove not triggered'); - assert.equal(dragend, 0, 'dragend not triggered'); - }); - - it('drag&drop should not fire click', function () { - var stage = addStage(); - var layer = new Konva.Layer({ - draggable: true, - }); - - var circle = new Konva.Circle({ - x: 70, - y: 70, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - draggable: true, - }); - - layer.add(circle); - stage.add(layer); - - var dragstart = 0; - circle.on('dragstart', function () { - dragstart += 1; - }); - - var dragmove = 0; - circle.on('dragmove', function () { - dragmove += 1; - }); - - var dragend = 0; - circle.on('dragend', function () { - dragend += 1; - }); - - var click = 0; - circle.on('click', function () { - click += 1; - }); - simulateMouseDown(stage, { x: 70, y: 70 }); - simulateMouseMove(stage, { x: 80, y: 80 }); - simulateMouseUp(stage, { x: 80, y: 80 }); - - assert.equal(click, 0, 'click triggered'); - assert.equal(dragstart, 1, 'dragstart not triggered'); - assert.equal(dragmove, 1, 'dragmove not triggered'); - assert.equal(dragend, 1, 'dragend not triggered'); - }); - - it('drag events should not trigger on a click even if we stop drag on dragstart', function () { - var stage = addStage(); - var layer = new Konva.Layer({ - draggable: true, - }); - - var circle = new Konva.Circle({ - x: 70, - y: 70, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - draggable: true, - }); - - layer.add(circle); - stage.add(layer); - - var dragstart = 0; - circle.on('dragstart', function () { - circle.stopDrag(); - dragstart += 1; - }); - - var dragmove = 0; - circle.on('dragmove', function () { - dragmove += 1; - }); - - var dragend = 0; - circle.on('dragend', function () { - dragend += 1; - }); - - var click = 0; - circle.on('click', function () { - click += 1; - }); - simulateMouseDown(stage, { x: 70, y: 70 }); - simulateMouseMove(stage, { x: 75, y: 75 }); - simulateMouseUp(stage, { x: 75, y: 75 }); - - assert.equal(click, 0, 'click triggered'); - assert.equal(dragstart, 1, 'dragstart triggered'); - assert.equal(dragmove, 0, 'dragmove not triggered'); - assert.equal(dragend, 1, 'dragend triggered'); - }); -}); diff --git a/test/unit/Ellipse-test.ts b/test/unit/Ellipse-test.ts deleted file mode 100644 index 2411dc50e..000000000 --- a/test/unit/Ellipse-test.ts +++ /dev/null @@ -1,116 +0,0 @@ -import { assert } from 'chai'; - -import { - addStage, - Konva, - createCanvas, - compareLayerAndCanvas, -} from './test-utils'; - -describe('Ellipse', function () { - // ====================================================== - it('add ellipse', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var ellipse = new Konva.Ellipse({ - x: stage.width() / 2, - y: stage.height() / 2, - radiusX: 70, - radiusY: 35, - fill: 'green', - stroke: 'black', - strokeWidth: 8, - }); - layer.add(ellipse); - stage.add(layer); - assert.equal(ellipse.getClassName(), 'Ellipse'); - - var trace = layer.getContext().getTrace(); - assert.equal( - trace, - 'clearRect(0,0,578,200);save();transform(1,0,0,1,289,100);beginPath();save();scale(1,0.5);arc(0,0,70,0,6.283,false);restore();closePath();fillStyle=green;fill();lineWidth=8;strokeStyle=black;stroke();restore();' - ); - }); - - // ====================================================== - it('attrs sync', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var ellipse = new Konva.Ellipse({ - x: stage.width() / 2, - y: stage.height() / 2, - radiusX: 70, - radiusY: 35, - fill: 'green', - stroke: 'black', - strokeWidth: 8, - }); - layer.add(ellipse); - stage.add(layer); - - assert.equal(ellipse.getWidth(), 140); - assert.equal(ellipse.getHeight(), 70); - - ellipse.setWidth(100); - assert.equal(ellipse.radiusX(), 50); - assert.equal(ellipse.radiusY(), 35); - - ellipse.setHeight(120); - assert.equal(ellipse.radiusX(), 50); - assert.equal(ellipse.radiusY(), 60); - }); - - it('getSelfRect', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var ellipse = new Konva.Ellipse({ - x: stage.width() / 2, - y: stage.height() / 2, - radiusX: 70, - radiusY: 35, - fill: 'green', - stroke: 'black', - strokeWidth: 8, - }); - layer.add(ellipse); - stage.add(layer); - - assert.deepEqual(ellipse.getSelfRect(), { - x: -70, - y: -35, - width: 140, - height: 70, - }); - }); - - it('cache', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var ellipse = new Konva.Ellipse({ - x: stage.width() / 2, - y: stage.height() / 2, - radiusX: 70, - radiusY: 35, - fill: 'green', - stroke: 'black', - strokeWidth: 8, - }); - ellipse.cache(); - layer.add(ellipse); - stage.add(layer); - - var canvas = createCanvas(); - var context = canvas.getContext('2d'); - context.save(); - context.beginPath(); - context.scale(1, 0.5); - context.arc(stage.width() / 2, stage.height(), 70, 0, Math.PI * 2, false); - context.closePath(); - context.restore(); - context.fillStyle = 'green'; - context.fill(); - context.lineWidth = 8; - context.stroke(); - compareLayerAndCanvas(layer, canvas, 150); - }); -}); diff --git a/test/unit/Global-test.ts b/test/unit/Global-test.ts deleted file mode 100644 index 551542e40..000000000 --- a/test/unit/Global-test.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { assert } from 'chai'; -import { Konva } from './test-utils'; - -describe('Global', function () { - // ====================================================== - it('test Konva version number', function () { - assert.equal(!!Konva.version, true); - }); - - // ====================================================== - it('getAngle()', function () { - // test that default angleDeg is true - assert.equal(Konva.angleDeg, true); - assert.equal(Konva.getAngle(180), Math.PI); - - Konva.angleDeg = false; - assert.equal(Konva.getAngle(1), 1); - - // set angleDeg back to true for future tests - Konva.angleDeg = true; - }); -}); diff --git a/test/unit/Group-test.ts b/test/unit/Group-test.ts deleted file mode 100644 index c2ec769ff..000000000 --- a/test/unit/Group-test.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { addStage, cloneAndCompareLayer, Konva } from './test-utils'; -import { assert } from 'chai'; - -describe('Group', function () { - // ====================================================== - it('cache group with text', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - var group = new Konva.Group({ - draggable: true, - x: 50, - y: 40, - }); - var text = new Konva.Text({ - text: 'some text', - fontSize: 20, - fill: 'black', - y: 50, - }); - - var rect = new Konva.Rect({ - height: 100, - width: 100, - stroke: 'black', - strokeWidth: 10, - // cornerRadius: 1, - }); - group.add(text); - group.add(rect); - layer.add(group); - - stage.add(layer); - - group - .cache({ - x: -15, - y: -15, - width: 150, - height: 150, - }) - .offsetX(5) - .offsetY(5); - - layer.draw(); - - cloneAndCompareLayer(layer, 200); - }); - - it('clip group with a Path2D', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - - var path = new Konva.Group({ - width: 100, - height: 100, - clipFunc: () => [new Path2D('M0 0v50h50Z')], - }); - - layer.add(path); - stage.add(layer); - - const trace = layer.getContext().getTrace(); - - assert.equal( - trace, - 'clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);beginPath();clip([object Path2D]);transform(1,0,0,1,0,0);restore();' - ); - }); - - it('clip group with by zero size', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - - var group = new Konva.Group({ - width: 100, - height: 100, - clipWidth: 0, - clipHeight: 0, - }); - - layer.add(group); - stage.add(layer); - - const trace = layer.getContext().getTrace(); - - console.log(trace); - - assert.equal( - trace, - 'clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);beginPath();rect(0,0,0,0);clip();transform(1,0,0,1,0,0);restore();' - ); - }); - - it('clip group with a Path2D and clipRule', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - - var path = new Konva.Group({ - width: 100, - height: 100, - clipFunc: () => [new Path2D('M0 0v50h50Z'), 'evenodd'], - }); - - layer.add(path); - stage.add(layer); - - const trace = layer.getContext().getTrace(); - - assert.equal( - trace, - 'clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);beginPath();clip([object Path2D],evenodd);transform(1,0,0,1,0,0);restore();' - ); - }); -}); diff --git a/test/unit/Image-test.ts b/test/unit/Image-test.ts deleted file mode 100644 index be9c95168..000000000 --- a/test/unit/Image-test.ts +++ /dev/null @@ -1,451 +0,0 @@ -import { assert } from 'chai'; - -import { - addStage, - Konva, - createCanvas, - compareLayerAndCanvas, - loadImage, - isNode, - isBrowser, -} from './test-utils'; - -describe('Image', function () { - // ====================================================== - it('add image', function (done) { - loadImage('darth-vader.jpg', (imageObj) => { - var stage = addStage(); - - var layer = new Konva.Layer(); - var darth = new Konva.Image({ - x: 200, - y: 60, - image: imageObj, - width: 100, - height: 100, - offset: { x: 50, y: 30 }, - crop: { x: 135, y: 7, width: 167, height: 134 }, - draggable: true, - }); - - layer.add(darth); - stage.add(layer); - - darth.width(200); - layer.draw(); - - darth.width(100); - layer.draw(); - - assert.equal(darth.x(), 200); - assert.equal(darth.y(), 60); - assert.equal(darth.getWidth(), 100); - assert.equal(darth.getHeight(), 100); - assert.equal(darth.offset().x, 50); - assert.equal(darth.offset().y, 30); - - var crop = darth.crop(); - - assert.equal(crop.x, 135); - assert.equal(crop.y, 7); - assert.equal(crop.width, 167); - assert.equal(crop.height, 134); - - darth.crop({ - x: 8, - y: 9, - width: 10, - height: 11, - }); - crop = darth.crop(); - assert.equal(crop.x, 8); - assert.equal(crop.y, 9); - assert.equal(crop.width, 10); - assert.equal(crop.height, 11); - - darth.cropX(12); - crop = darth.crop(); - assert.equal(crop.x, 12); - assert.equal(crop.y, 9); - assert.equal(crop.width, 10); - assert.equal(crop.height, 11); - - darth.cropY(13); - crop = darth.crop(); - assert.equal(crop.x, 12); - assert.equal(crop.y, 13); - assert.equal(crop.width, 10); - assert.equal(crop.height, 11); - - darth.cropWidth(14); - crop = darth.crop(); - assert.equal(crop.x, 12); - assert.equal(crop.y, 13); - assert.equal(crop.width, 14); - assert.equal(crop.height, 11); - - darth.cropHeight(15); - crop = darth.crop(); - assert.equal(crop.x, 12); - assert.equal(crop.y, 13); - assert.equal(crop.width, 14); - assert.equal(crop.height, 15); - - darth.setAttrs({ - x: 200, - y: 60, - image: imageObj, - width: 100, - height: 100, - offsetX: 50, - offsetY: 30, - crop: { x: 135, y: 7, width: 167, height: 134 }, - draggable: true, - }); - - assert.equal(darth.getClassName(), 'Image'); - - var trace = layer.getContext().getTrace(); - - if (isBrowser) { - assert.equal( - trace, - 'clearRect(0,0,578,200);save();transform(1,0,0,1,150,30);drawImage([object HTMLImageElement],135,7,167,134,0,0,100,100);restore();clearRect(0,0,578,200);save();transform(1,0,0,1,150,30);drawImage([object HTMLImageElement],135,7,167,134,0,0,200,100);restore();clearRect(0,0,578,200);save();transform(1,0,0,1,150,30);drawImage([object HTMLImageElement],135,7,167,134,0,0,100,100);restore();' - ); - } else { - assert.equal( - trace, - 'clearRect(0,0,578,200);save();transform(1,0,0,1,150,30);drawImage([object Object],135,7,167,134,0,0,100,100);restore();clearRect(0,0,578,200);save();transform(1,0,0,1,150,30);drawImage([object Object],135,7,167,134,0,0,200,100);restore();clearRect(0,0,578,200);save();transform(1,0,0,1,150,30);drawImage([object Object],135,7,167,134,0,0,100,100);restore();' - ); - } - - done(); - }); - }); - - // ====================================================== - it('try image will fill pattern', function (done) { - loadImage('darth-vader.jpg', (imageObj) => { - loadImage('lion.png', (lion) => { - var stage = addStage(); - - var layer = new Konva.Layer(); - var darth = new Konva.Image({ - x: 20, - y: 20, - image: lion, - draggable: true, - fillPatternImage: imageObj, - fillPatternRepeat: 'no-repeat', - fillPatternX: 50, - }); - - layer.add(darth); - stage.add(layer); - - assert.equal( - layer.getContext().getTrace(true), - 'clearRect();save();transform();beginPath();rect();closePath();fillStyle;fill();drawImage();restore();' - ); - - done(); - }); - }); - }); - - // ====================================================== - it('crop and scale image', function (done) { - loadImage('darth-vader.jpg', (imageObj) => { - var stage = addStage(); - var layer = new Konva.Layer(); - var darth = new Konva.Image({ - x: 200, - y: 75, - image: imageObj, - width: 107, - height: 75, - crop: { x: 186, y: 211, width: 106, height: 74 }, - draggable: true, - scale: { x: 0.5, y: 0.5 }, - cornerRadius: 15, - }); - - layer.add(darth); - stage.add(layer); - - assert.equal(darth.crop().x, 186); - assert.equal(darth.crop().y, 211); - assert.equal(darth.crop().width, 106); - assert.equal(darth.crop().height, 74); - - assert.equal(darth.cropX(), 186); - assert.equal(darth.cropY(), 211); - assert.equal(darth.cropWidth(), 106); - assert.equal(darth.cropHeight(), 74); - - darth.crop({ x: 1, y: 2, width: 3, height: 4 }); - - assert.equal(darth.crop().x, 1); - assert.equal(darth.crop().y, 2); - assert.equal(darth.crop().width, 3); - assert.equal(darth.crop().height, 4); - - assert.equal(darth.cropX(), 1); - assert.equal(darth.cropY(), 2); - assert.equal(darth.cropWidth(), 3); - assert.equal(darth.cropHeight(), 4); - - darth.cropX(5); - darth.cropY(6); - darth.cropWidth(7); - darth.cropHeight(8); - - assert.equal(darth.crop().x, 5); - assert.equal(darth.crop().y, 6); - assert.equal(darth.crop().width, 7); - assert.equal(darth.crop().height, 8); - - assert.equal(darth.cropX(), 5); - assert.equal(darth.cropY(), 6); - assert.equal(darth.cropWidth(), 7); - assert.equal(darth.cropHeight(), 8); - assert.equal(darth.cornerRadius(), 15); - - done(); - }); - }); - - // ====================================================== - it('image with opacity and shadow', function (done) { - loadImage('darth-vader.jpg', (imageObj) => { - var stage = addStage(); - - var layer = new Konva.Layer(); - var darth = new Konva.Image({ - x: 200, - y: 60, - image: imageObj, - width: 100, - height: 100, - offset: { x: 50, y: 30 }, - draggable: true, - opacity: 0.5, - shadowColor: 'black', - shadowBlur: 10, - shadowOpacity: 0.1, - shadowOffset: { x: 20, y: 20 }, - }); - - layer.add(darth); - stage.add(layer); - - var trace = layer.getContext().getTrace(); - - if (isBrowser) { - assert.equal( - trace, - 'clearRect(0,0,578,200);save();transform(1,0,0,1,150,30);globalAlpha=0.5;shadowColor=rgba(0,0,0,0.1);shadowBlur=10;shadowOffsetX=20;shadowOffsetY=20;drawImage([object HTMLImageElement],0,0,100,100);restore();' - ); - } else { - assert.equal( - trace, - 'clearRect(0,0,578,200);save();transform(1,0,0,1,150,30);globalAlpha=0.5;shadowColor=rgba(0,0,0,0.1);shadowBlur=10;shadowOffsetX=20;shadowOffsetY=20;drawImage([object Object],0,0,100,100);restore();' - ); - } - - done(); - }); - }); - - // ====================================================== - it('image with stroke, opacity and shadow', function (done) { - loadImage('darth-vader.jpg', (imageObj) => { - var stage = addStage(); - - var layer = new Konva.Layer(); - var darth = new Konva.Image({ - x: 200, - y: 60, - image: imageObj, - width: 100, - height: 100, - offset: { x: 50, y: 30 }, - draggable: true, - opacity: 0.5, - shadowColor: 'black', - shadowBlur: 10, - shadowOpacity: 0.5, - shadowOffset: { x: 20, y: 20 }, - stroke: 'red', - strokeWidth: 20, - }); - - layer.add(darth); - stage.add(layer); - - var trace = layer.getContext().getTrace(); - - if (isBrowser) { - assert.equal( - trace, - 'clearRect(0,0,578,200);save();shadowColor=rgba(0,0,0,0.5);shadowBlur=10;shadowOffsetX=20;shadowOffsetY=20;globalAlpha=0.5;drawImage([object HTMLCanvasElement],0,0,578,200);restore();' - ); - } else { - assert.equal( - trace, - 'clearRect(0,0,578,200);save();shadowColor=rgba(0,0,0,0.5);shadowBlur=10;shadowOffsetX=20;shadowOffsetY=20;globalAlpha=0.5;drawImage([object Object],0,0,578,200);restore();' - ); - } - - done(); - }); - }); - - // ====================================================== - it('image caching', function (done) { - loadImage('darth-vader.jpg', (imageObj) => { - var stage = addStage(); - var layer = new Konva.Layer(); - var darth = new Konva.Image({ - x: 200, - y: 60, - image: imageObj, - width: 100, - height: 100, - draggable: true, - }); - - darth.cache(); - layer.add(darth); - stage.add(layer); - - assert.deepEqual(darth.getSelfRect(), { - x: 0, - y: 0, - width: 100, - height: 100, - }); - - var canvas = createCanvas(); - var context = canvas.getContext('2d'); - context.drawImage(imageObj, 200, 60, 100, 100); - compareLayerAndCanvas(layer, canvas, 10); - done(); - }); - }); - - it('image loader', function (done) { - if (isNode) { - done(); - return; - } - loadImage('darth-vader.jpg', (img) => { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - var src = img.src; - Konva.Image.fromURL(src, function (image) { - layer.add(image); - layer.draw(); - assert.equal(image instanceof Konva.Image, true); - var nativeImg = image.image(); - assert.equal(nativeImg instanceof Image, true); - assert.equal(nativeImg.src.indexOf(src) !== -1, true); - assert.equal(nativeImg.complete, true); - done(); - }); - }); - }); - - it('check loading failure', function (done) { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - var src = 'non-existent.jpg'; - Konva.Image.fromURL(src, null, function (e) { - done(); - }); - }); - - it('check zero values', function (done) { - loadImage('darth-vader.jpg', (imageObj) => { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - const image = new Konva.Image({ image: imageObj }); - layer.add(image); - - image.width(0); - image.height(0); - layer.draw(); - - assert.equal(image.width(), 0); - assert.equal(image.height(), 0); - done(); - }); - }); - - it('corner radius', function (done) { - loadImage('darth-vader.jpg', (imageObj) => { - var stage = addStage(); - - var layer = new Konva.Layer(); - var darth = new Konva.Image({ - x: 20, - y: 20, - image: imageObj, - cornerRadius: 10, - draggable: true, - stroke: 'red', - strokeWidth: 100, - strokeEnabled: false, - }); - - layer.add(darth); - stage.add(layer); - - assert.equal( - layer.getContext().getTrace(true), - 'clearRect();save();transform();beginPath();moveTo();lineTo();arc();lineTo();arc();lineTo();arc();lineTo();arc();closePath();clip();drawImage();restore();' - ); - - done(); - }); - }); - - it('corner radius with shadow', function (done) { - // that will trigger buffer canvas - loadImage('darth-vader.jpg', (imageObj) => { - var stage = addStage(); - - var layer = new Konva.Layer(); - var darth = new Konva.Image({ - x: 20, - y: 20, - image: imageObj, - cornerRadius: 10, - draggable: true, - stroke: 'red', - strokeWidth: 100, - strokeEnabled: false, - shadowColor: 'black', - shadowBlur: 10, - shadowOffsetX: 10, - shadowOffsetY: 10, - scaleX: 0.5, - scaleY: 0.5, - }); - - layer.add(darth); - stage.add(layer); - - assert.equal( - layer.getContext().getTrace(true), - 'clearRect();save();shadowColor;shadowBlur;shadowOffsetX;shadowOffsetY;drawImage();restore();' - ); - - done(); - }); - }); -}); diff --git a/test/unit/Label-test.ts b/test/unit/Label-test.ts deleted file mode 100644 index 1f3e31620..000000000 --- a/test/unit/Label-test.ts +++ /dev/null @@ -1,376 +0,0 @@ -import { assert } from 'chai'; - -import { addStage, Konva, cloneAndCompareLayer, isBrowser } from './test-utils'; - -describe('Label', function () { - // ====================================================== - it('add label', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var label = new Konva.Label({ - x: 100, - y: 100, - draggable: true, - }); - - // add a tag to the label - label.add( - new Konva.Tag({ - fill: '#bbb', - shadowColor: 'black', - shadowBlur: 10, - shadowOffset: { x: 10, y: 10 }, - shadowOpacity: 0.2, - lineJoin: 'round', - pointerDirection: 'up', - pointerWidth: 20, - pointerHeight: 20, - cornerRadius: 5, - }) - ); - - // add text to the label - label.add( - new Konva.Text({ - text: '', - fontSize: 50, - //fontFamily: 'Calibri', - //fontStyle: 'normal', - lineHeight: 1.2, - //padding: 10, - fill: 'green', - }) - ); - - layer.add(label); - stage.add(layer); - - label.getText().fontSize(100); - - label.getText().fontSize(50); - - label.getText().text('Hello big world'); - - layer.draw(); - - assert.equal(label.getType(), 'Group'); - assert.equal(label.getClassName(), 'Label'); - - // use relaxed trace because text can be a slightly different size in different browsers, - // resulting in slightly different tag dimensions - var trace = layer.getContext().getTrace(true); - assert.equal( - trace, - 'clearRect();save();lineJoin;transform();shadowColor;shadowBlur;shadowOffsetX;shadowOffsetY;beginPath();moveTo();lineTo();lineTo();lineTo();lineTo();arc();lineTo();arc();lineTo();arc();lineTo();arc();closePath();fillStyle;fill();restore();save();transform();restore();clearRect();save();lineJoin;transform();shadowColor;shadowBlur;shadowOffsetX;shadowOffsetY;beginPath();moveTo();lineTo();lineTo();lineTo();lineTo();arc();lineTo();arc();lineTo();arc();lineTo();arc();closePath();fillStyle;fill();restore();save();transform();font;textBaseline;textAlign;translate();save();fillStyle;fillText();restore();restore();' - ); - }); - - // ====================================================== - it('create label from json', function () { - var stage = addStage(); - - var json = - '{"attrs":{"x":100,"y":100,"draggable":true},"className":"Label","children":[{"attrs":{"fill":"#bbb","shadowColor":"black","shadowBlur":10,"shadowOffsetX":10,"shadowOffsetY":10,"shadowOpacity":0.2,"lineJoin":"round","pointerDirection":"up","pointerWidth":20,"pointerHeight":20,"cornerRadius":5,"x":-151.5,"y":20,"width":303,"height":60},"className":"Tag"},{"attrs":{"text":"Hello big world","fontSize":50,"lineHeight":1.2,"fill":"green","width":"auto","height":"auto","x":-151.5,"y":20},"className":"Text"}]}'; - var layer = new Konva.Layer(); - - var label = Konva.Node.create(json); - - layer.add(label); - stage.add(layer); - - var trace = layer.getContext().getTrace(false, true); - - if (isBrowser) { - assert.equal( - trace, - 'clearRect(0,0,578,200);save();lineJoin=round;transform(1,0,0,1,-64,120);shadowColor=rgba(0,0,0,0.2);shadowBlur=10;shadowOffsetX=10;shadowOffsetY=10;beginPath();moveTo(5,0);lineTo(153,0);lineTo(163,-20);lineTo(173,0);lineTo(322,0);arc(322,5,5,4,0,false);lineTo(327,55);arc(322,55,5,0,1,false);lineTo(5,60);arc(5,55,5,1,3,false);lineTo(0,5);arc(5,5,5,3,4,false);closePath();fillStyle=#bbb;fill();restore();save();transform(1,0,0,1,-64,120);font=normal normal 50px Arial;textBaseline=middle;textAlign=left;translate(0,0);save();fillStyle=green;fillText(Hello big world,0,30);restore();restore();' - ); - } else { - assert.equal( - trace, - 'clearRect(0,0,578,200);save();lineJoin=round;transform(1,0,0,1,-64,120);shadowColor=rgba(0,0,0,0.2);shadowBlur=10;shadowOffsetX=10;shadowOffsetY=10;beginPath();moveTo(5,0);lineTo(153,0);lineTo(163,-20);lineTo(173,0);lineTo(322,0);arc(322,5,5,4,0,false);lineTo(327,55);arc(322,55,5,0,1,false);lineTo(5,60);arc(5,55,5,1,3,false);lineTo(0,5);arc(5,5,5,3,4,false);closePath();fillStyle=#bbb;fill();restore();save();transform(1,0,0,1,-64,120);font=normal normal 50px Arial;textBaseline=middle;textAlign=left;translate(0,0);save();fillStyle=green;fillText(Hello big world,0,30);restore();restore();' - ); - } - }); - - it('find label class', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var label = new Konva.Label({ - x: 100, - y: 100, - }); - - // add a tag to the label - label.add( - new Konva.Tag({ - fill: '#bbb', - }) - ); - - // add text to the label - label.add( - new Konva.Text({ - text: 'Test Label', - fill: 'green', - }) - ); - - layer.add(label); - stage.add(layer); - - assert.equal(stage.find('Label')[0], label); - }); - - // caching doesn't give exactly the same result. WHY? - it('cache label', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - // tooltip - var tooltip = new Konva.Label({ - x: 170, - y: 75, - opacity: 0.75, - }); - tooltip.add( - new Konva.Tag({ - fill: 'black', - pointerDirection: 'down', - pointerWidth: 10, - pointerHeight: 10, - lineJoin: 'round', - shadowColor: 'black', - shadowBlur: 10, - shadowOffsetX: 10, - shadowOpacity: 0.5, - }) - ); - tooltip.add( - new Konva.Text({ - text: 'Tooltip pointing down', - fontFamily: 'Calibri', - fontSize: 18, - padding: 5, - fill: 'white', - }) - ); - - var tooltipUp = new Konva.Label({ - x: 170, - y: 75, - opacity: 0.75, - }); - tooltipUp.add( - new Konva.Tag({ - fill: 'black', - pointerDirection: 'up', - pointerWidth: 10, - pointerHeight: 10, - lineJoin: 'round', - shadowColor: 'black', - shadowBlur: 10, - shadowOffsetX: 10, - shadowOpacity: 0.5, - }) - ); - tooltipUp.add( - new Konva.Text({ - text: 'Tooltip pointing up', - fontFamily: 'Calibri', - fontSize: 18, - padding: 5, - fill: 'white', - }) - ); - // label with left pointer - var labelLeft = new Konva.Label({ - x: 20, - y: 130, - opacity: 0.75, - }); - labelLeft.add( - new Konva.Tag({ - fill: 'green', - pointerDirection: 'left', - pointerWidth: 30, - pointerHeight: 28, - lineJoin: 'round', - }) - ); - labelLeft.add( - new Konva.Text({ - text: 'Label pointing left', - fontFamily: 'Calibri', - fontSize: 18, - padding: 5, - fill: 'white', - }) - ); - // label with left pointer - var labelRight = new Konva.Label({ - x: 160, - y: 170, - offsetX: 20, - opacity: 0.75, - }); - labelRight.add( - new Konva.Tag({ - fill: 'green', - pointerDirection: 'right', - pointerWidth: 20, - pointerHeight: 28, - lineJoin: 'round', - }) - ); - labelRight.add( - new Konva.Text({ - text: 'Label right', - fontFamily: 'Calibri', - fontSize: 18, - padding: 5, - fill: 'white', - }) - ); - // simple label - var simpleLabel = new Konva.Label({ - x: 180, - y: 150, - opacity: 0.75, - }); - simpleLabel.add( - new Konva.Tag({ - fill: 'yellow', - }) - ); - simpleLabel.add( - new Konva.Text({ - text: 'Simple label', - fontFamily: 'Calibri', - fontSize: 18, - padding: 5, - fill: 'black', - }) - ); - // add the labels to layer - layer.add(tooltip, tooltipUp, labelLeft, labelRight, simpleLabel); - layer.children.forEach((child) => child.cache()); - - stage.add(layer); - - cloneAndCompareLayer(layer, 250, 100); - }); - - it('tag should list text size changes', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var label = new Konva.Label(); - - var tag = new Konva.Tag({ - stroke: 'black', - }); - - label.add(tag); - - var text = new Konva.Text({ - text: 'hello hello hello hello hello hello hello hello', - }); - label.add(text); - - layer.add(label); - layer.draw(); - - text.width(200); - - layer.draw(); - assert.equal(tag.width(), text.width()); - - text.height(200); - assert.equal(tag.height(), text.height()); - }); - - it('tag cornerRadius', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var tag = new Konva.Tag({ - x: 50, - y: 50, - width: 100, - height: 100, - fill: 'black', - cornerRadius: [0, 10, 20, 30], - }); - layer.add(tag); - stage.add(layer); - layer.draw(); - - assert.equal(tag.cornerRadius()[0], 0); - assert.equal(tag.cornerRadius()[1], 10); - assert.equal(tag.cornerRadius()[2], 20); - assert.equal(tag.cornerRadius()[3], 30); - - var trace = layer.getContext().getTrace(); - assert.equal( - trace, - 'clearRect(0,0,578,200);save();transform(1,0,0,1,50,50);beginPath();moveTo(0,0);lineTo(90,0);arc(90,10,10,4.712,0,false);lineTo(100,80);arc(80,80,20,0,1.571,false);lineTo(30,100);arc(30,70,30,1.571,3.142,false);lineTo(0,0);arc(0,0,0,3.142,4.712,false);closePath();fillStyle=black;fill();restore();clearRect(0,0,578,200);save();transform(1,0,0,1,50,50);beginPath();moveTo(0,0);lineTo(90,0);arc(90,10,10,4.712,0,false);lineTo(100,80);arc(80,80,20,0,1.571,false);lineTo(30,100);arc(30,70,30,1.571,3.142,false);lineTo(0,0);arc(0,0,0,3.142,4.712,false);closePath();fillStyle=black;fill();restore();' - ); - }); - - it('react to pointer properties', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var label = new Konva.Label({ - x: 100, - y: 100, - draggable: true, - }); - - var counter = 0; - var oldSync = label._sync; - label._sync = () => { - oldSync.call(label); - counter += 1; - }; - - const tag = new Konva.Tag({ - fill: '#bbb', - shadowColor: 'black', - shadowBlur: 10, - shadowOffset: { x: 10, y: 10 }, - shadowOpacity: 0.2, - lineJoin: 'round', - pointerDirection: 'up', - pointerWidth: 20, - pointerHeight: 20, - cornerRadius: 5, - }); - // add a tag to the label - label.add(tag); - - // add text to the label - label.add( - new Konva.Text({ - text: 'hello', - fontSize: 50, - lineHeight: 1.2, - fill: 'green', - }) - ); - layer.add(label); - - assert.equal(counter, 4); - tag.pointerDirection('bottom'); - assert.equal(counter, 5); - tag.pointerWidth(30); - assert.equal(counter, 6); - tag.pointerHeight(40); - assert.equal(counter, 7); - }); -}); diff --git a/test/unit/Layer-test.ts b/test/unit/Layer-test.ts deleted file mode 100644 index b0891d560..000000000 --- a/test/unit/Layer-test.ts +++ /dev/null @@ -1,452 +0,0 @@ -import { assert } from 'chai'; - -import { - addStage, - simulateMouseDown, - simulateMouseMove, - simulateMouseUp, - showHit, - Konva, - loadImage, - isNode, -} from './test-utils'; - -describe('Layer', function () { - // ====================================================== - it('width and height', function () { - Konva.showWarnings = false; - var stage = addStage(); - - var layer = new Konva.Layer(); - assert.equal( - layer.width(), - undefined, - 'while layer is not on stage width is undefined' - ); - assert.equal( - layer.height(), - undefined, - 'while layer is not on stage height is undefined' - ); - - layer.width(10); - assert.equal( - layer.width(), - undefined, - 'while layer is not on stage changing width doing nothing' - ); - layer.height(10); - assert.equal( - layer.height(), - undefined, - 'while layer is not on stage changing height doing nothing' - ); - stage.add(layer); - - assert.equal( - layer.width(), - stage.width(), - 'while layer is on stage width is stage`s width' - ); - assert.equal( - layer.height(), - stage.height(), - 'while layer is on stage height is stage`s height' - ); - - layer.width(10); - assert.equal( - layer.width(), - stage.width(), - 'while layer is on stage changing width doing nothing' - ); - layer.height(10); - assert.equal( - layer.height(), - stage.height(), - 'while layer is on stage changing height doing nothing' - ); - Konva.showWarnings = true; - }); - - // ====================================================== - it('test canvas inline styles', function () { - if (isNode) { - return; - } - var stage = addStage(); - - var layer = new Konva.Layer(); - - var circle = new Konva.Circle({ - x: 100, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - }); - - layer.add(circle); - stage.add(layer); - - var style = layer.getCanvas()._canvas.style; - - assert.equal( - style.position, - 'absolute', - 'canvas position style should be absolute' - ); - assert.equal( - style.border.indexOf('0px'), - 0, - 'canvas border style should be 0px' - ); - assert.equal(style.margin, '0px', 'canvas margin style should be 0px'); - assert.equal(style.padding, '0px', 'canvas padding style should be 0px'); - assert.equal( - style.backgroundColor, - 'transparent', - 'canvas backgroundColor style should be transparent' - ); - assert.equal(style.top, '0px', 'canvas top should be 0px'); - assert.equal(style.left, '0px', 'canvas left should be 0px'); - }); - - it('test clear()', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: 100, - y: 100, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - draggable: true, - }); - - circle.colorKey = '#000000'; - - circle.on('mouseover', function () { - console.log('mouseover'); - }); - - layer.add(circle); - stage.add(layer); - - layer.clear(); - - var trace = layer.getContext().getTrace(); - //console.log(trace); - assert.equal( - trace, - 'clearRect(0,0,578,200);save();transform(1,0,0,1,100,100);beginPath();arc(0,0,70,0,6.283,false);closePath();fillStyle=green;fill();lineWidth=4;strokeStyle=black;stroke();restore();clearRect(0,0,578,200);' - ); - - var hitTrace = layer.getHitCanvas().getContext().getTrace(); - //console.log(hitTrace); - assert.equal( - hitTrace, - 'clearRect(0,0,578,200);save();transform(1,0,0,1,100,100);beginPath();arc(0,0,70,0,6.283,false);closePath();save();fillStyle=#000000;fill();restore();lineWidth=4;strokeStyle=#000000;stroke();restore();clearRect(0,0,578,200);' - ); - - showHit(layer); - }); - - it('test clear() with bounds', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: 100, - y: 100, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - draggable: true, - }); - - circle.colorKey = '#000000'; - - circle.on('mouseover', function () { - console.log('mouseover'); - }); - - layer.add(circle); - stage.add(layer); - - layer.clear({ x: 100, y: 100, width: 100, height: 100 }); - - var trace = layer.getContext().getTrace(); - //console.log(trace); - assert.equal( - trace, - 'clearRect(0,0,578,200);save();transform(1,0,0,1,100,100);beginPath();arc(0,0,70,0,6.283,false);closePath();fillStyle=green;fill();lineWidth=4;strokeStyle=black;stroke();restore();clearRect(100,100,100,100);' - ); - - var hitTrace = layer.getHitCanvas().getContext().getTrace(); - //console.log(hitTrace); - assert.equal( - hitTrace, - 'clearRect(0,0,578,200);save();transform(1,0,0,1,100,100);beginPath();arc(0,0,70,0,6.283,false);closePath();save();fillStyle=#000000;fill();restore();lineWidth=4;strokeStyle=#000000;stroke();restore();clearRect(100,100,100,100);' - ); - - showHit(layer); - }); - - // ====================================================== - it('layer getIntersection()', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var redCircle = new Konva.Circle({ - x: 380, - y: stage.height() / 2, - radius: 70, - strokeWidth: 4, - fill: 'red', - stroke: 'black', - id: 'redCircle', - }); - - var greenCircle = new Konva.Circle({ - x: 300, - y: stage.height() / 2, - radius: 70, - strokeWidth: 4, - fill: 'green', - stroke: 'black', - id: 'greenCircle', - }); - - layer.add(redCircle); - layer.add(greenCircle); - stage.add(layer); - - assert.equal( - layer.getIntersection({ x: 300, y: 100 }).id(), - 'greenCircle', - 'shape should be greenCircle' - ); - assert.equal( - layer.getIntersection({ x: 380, y: 100 }).id(), - 'redCircle', - 'shape should be redCircle' - ); - assert.equal( - layer.getIntersection({ x: 100, y: 100 }), - null, - 'shape should be null' - ); - }); - - // ====================================================== - it('set layer visibility', function () { - var stage = addStage(); - var layer = new Konva.Layer({ - visible: false, - }); - - stage.add(layer); - - assert(layer.getNativeCanvasElement().style.display === 'none'); - }); - - // ====================================================== - it('set clearBeforeDraw to false, and test toDataURL for stage, layer, group, and shape', function () { - var stage = addStage(); - - var layer = new Konva.Layer({ - clearBeforeDraw: false, - throttle: 999, - }); - - var group = new Konva.Group(); - - var circle = new Konva.Circle({ - x: 100, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - }); - - group.add(circle); - layer.add(group); - stage.add(layer); - - for (var n = 0; n < 20; n++) { - circle.move({ x: 10, y: 0 }); - layer.draw(); - } - - var trace = layer.getContext().getTrace(); - //console.log(trace); - assert.equal( - trace, - 'save();transform(1,0,0,1,220,100);beginPath();arc(0,0,70,0,6.283,false);closePath();fillStyle=green;fill();lineWidth=4;strokeStyle=black;stroke();restore();save();transform(1,0,0,1,230,100);beginPath();arc(0,0,70,0,6.283,false);closePath();fillStyle=green;fill();lineWidth=4;strokeStyle=black;stroke();restore();save();transform(1,0,0,1,240,100);beginPath();arc(0,0,70,0,6.283,false);closePath();fillStyle=green;fill();lineWidth=4;strokeStyle=black;stroke();restore();save();transform(1,0,0,1,250,100);beginPath();arc(0,0,70,0,6.283,false);closePath();fillStyle=green;fill();lineWidth=4;strokeStyle=black;stroke();restore();save();transform(1,0,0,1,260,100);beginPath();arc(0,0,70,0,6.283,false);closePath();fillStyle=green;fill();lineWidth=4;strokeStyle=black;stroke();restore();save();transform(1,0,0,1,270,100);beginPath();arc(0,0,70,0,6.283,false);closePath();fillStyle=green;fill();lineWidth=4;strokeStyle=black;stroke();restore();save();transform(1,0,0,1,280,100);beginPath();arc(0,0,70,0,6.283,false);closePath();fillStyle=green;fill();lineWidth=4;strokeStyle=black;stroke();restore();save();transform(1,0,0,1,290,100);beginPath();arc(0,0,70,0,6.283,false);closePath();fillStyle=green;fill();lineWidth=4;strokeStyle=black;stroke();restore();save();transform(1,0,0,1,300,100);beginPath();arc(0,0,70,0,6.283,false);closePath();fillStyle=green;fill();lineWidth=4;strokeStyle=black;stroke();restore();' - ); - }); - - // ====================================================== - it('save layer as png', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var Circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'violet', - stroke: 'black', - strokeWidth: 4, - }); - - layer.add(Circle); - stage.add(layer); - - var dataUrl = layer.toDataURL(); - assert(dataUrl.length > 30); - }); - - // ====================================================== - it('save layer as low quality jpg', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'violet', - stroke: 'black', - strokeWidth: 4, - }); - - layer.add(circle); - stage.add(layer); - - var dataUrl = layer.toDataURL({ - mimeType: 'image/jpeg', - quality: 0.2, - }); - - assert( - dataUrl.length < - layer.toDataURL({ - mimeType: 'image/jpeg', - }).length - ); - }); - - // ====================================================== - it('hit graph enable disable', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'red', - stroke: 'black', - strokeWidth: 4, - }); - - layer.add(circle); - stage.add(layer); - - assert.equal(layer.listening(), true); - assert.equal(layer.shouldDrawHit(), true); - - layer.listening(false); - - assert.equal(layer.listening(), false); - assert.equal(layer.shouldDrawHit(), false); - - layer.listening(true); - - assert.equal(layer.listening(), true); - assert.equal(layer.shouldDrawHit(), true); - }); - - // ====================================================== - it('should not draw hit on stage drag', function () { - var stage = addStage(); - stage.draggable(true); - var layer = new Konva.Layer(); - - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'red', - stroke: 'black', - strokeWidth: 4, - }); - - layer.add(circle); - stage.add(layer); - - simulateMouseDown(stage, { - x: circle.x(), - y: circle.y(), - }); - - simulateMouseMove(stage, { - x: circle.x() + 10, - y: circle.y() + 10, - }); - assert.equal(stage.isDragging(), true, 'dragging of stage is ok'); - assert.equal(layer.shouldDrawHit(), false); - - simulateMouseUp(stage, { - x: 291, - y: 112, - }); - }); - - it('get/set layer size', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - assert.deepEqual(layer.size(), stage.size()); - assert.equal(layer.width(), stage.width()); - assert.equal(layer.height(), stage.height()); - }); - - it('get/set imageSmoothingEnabled', function (done) { - loadImage('darth-vader.jpg', (imageObj) => { - var stage = addStage(); - - var layer = new Konva.Layer({ - imageSmoothingEnabled: false, - }); - var darth = new Konva.Image({ - image: imageObj, - scaleX: 5, - scaleY: 5, - }); - - layer.add(darth); - stage.add(layer); - - assert.equal(layer.getContext()['imageSmoothingEnabled'], false); - - layer.imageSmoothingEnabled(true); - - assert.equal(layer.getContext()['imageSmoothingEnabled'], true); - - layer.imageSmoothingEnabled(false); - // change size - stage.width(stage.width() + 1); - assert.equal(layer.getContext()['imageSmoothingEnabled'], false); - - done(); - }); - }); -}); diff --git a/test/unit/Line-test.ts b/test/unit/Line-test.ts deleted file mode 100644 index 2efa2816b..000000000 --- a/test/unit/Line-test.ts +++ /dev/null @@ -1,716 +0,0 @@ -import { assert } from 'chai'; - -import { - addStage, - Konva, - createCanvas, - compareLayerAndCanvas, - compareLayers, -} from './test-utils'; - -describe('Line', function () { - // ====================================================== - it('add line', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var line = new Konva.Line({ - stroke: 'blue', - strokeWidth: 20, - lineCap: 'round', - lineJoin: 'round', - draggable: true, - tension: 0, - }); - - layer.add(line); - stage.add(layer); - - line.points([1, 2, 3, 4]); - assert.equal(line.points()[0], 1); - - line.points([5, 6, 7, 8]); - assert.equal(line.points()[0], 5); - - line.points([73, 160, 340, 23, 340, 80]); - assert.equal(line.points()[0], 73); - - assert.equal(line.getClassName(), 'Line'); - - layer.draw(); - }); - - // ====================================================== - it('test default ponts array for two lines', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var line = new Konva.Line({ - stroke: 'blue', - strokeWidth: 20, - lineCap: 'round', - lineJoin: 'round', - draggable: true, - }); - - var redLine = new Konva.Line({ - x: 50, - stroke: 'red', - strokeWidth: 20, - lineCap: 'round', - lineJoin: 'round', - draggable: true, - }); - - line.points([0, 1, 2, 3]); - redLine.points([4, 5, 6, 7]); - - layer.add(line).add(redLine); - stage.add(layer); - - assert.equal(line.points()[0], 0); - assert.equal(redLine.points()[0], 4); - }); - - // ====================================================== - it('add dashed line', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - /* - var points = [{ - x: 73, - y: 160 - }, { - x: 340, - y: 23 - }, { - x: 500, - y: 109 - }, { - x: 500, - y: 180 - }]; - */ - - var line = new Konva.Line({ - points: [73, 160, 340, 23, 500, 109, 500, 180], - stroke: 'blue', - - strokeWidth: 10, - lineCap: 'round', - lineJoin: 'round', - draggable: true, - dash: [30, 10, 0, 10, 10, 20], - shadowColor: '#aaa', - shadowBlur: 10, - shadowOffset: { - x: 20, - y: 20, - }, - //opacity: 0.2 - }); - - layer.add(line); - stage.add(layer); - - assert.equal(line.dash().length, 6); - line.dash([10, 10]); - assert.equal(line.dash().length, 2); - - assert.equal(line.points().length, 8); - }); - - // ====================================================== - it('add line with shadow', function () { - const oldRatio = Konva.pixelRatio; - Konva.pixelRatio = 1; - var stage = addStage(); - var layer = new Konva.Layer(); - - var line = new Konva.Line({ - points: [73, 160, 340, 23], - stroke: 'blue', - strokeWidth: 20, - lineCap: 'round', - lineJoin: 'round', - shadowColor: 'black', - shadowBlur: 20, - shadowOffset: { - x: 10, - y: 10, - }, - shadowOpacity: 0.5, - draggable: true, - }); - - layer.add(line); - stage.add(layer); - - var canvas = createCanvas(); - var context = canvas.getContext('2d'); - - context.save(); - context.lineJoin = 'round'; - context.lineCap = 'round'; - context.lineWidth = 20; - context.strokeStyle = 'blue'; - - context.shadowColor = 'rgba(0,0,0,0.5)'; - context.shadowBlur = 20; - context.shadowOffsetX = 10; - context.shadowOffsetY = 10; - context.moveTo(73, 160); - context.lineTo(340, 23); - - context.stroke(); - // context.fill(); - context.restore(); - - Konva.pixelRatio = oldRatio; - - compareLayerAndCanvas(layer, canvas, 50); - - var trace = layer.getContext().getTrace(); - - assert.equal( - trace, - 'clearRect(0,0,578,200);save();lineJoin=round;transform(1,0,0,1,0,0);shadowColor=rgba(0,0,0,0.5);shadowBlur=20;shadowOffsetX=10;shadowOffsetY=10;beginPath();moveTo(73,160);lineTo(340,23);lineCap=round;lineWidth=20;strokeStyle=blue;stroke();restore();' - ); - }); - - it('line hit test with strokeScaleEnabled = false', function () { - var stage = addStage(); - var scale = 0.1; - var layer = new Konva.Layer(); - - var group = new Konva.Group({ - scale: { - x: scale, - y: scale, - }, - }); - - var line1 = new Konva.Line({ - points: [0, 0, 300, 0], - stroke: 'red', - strokeScaleEnabled: false, - strokeWidth: 10, - y: 0, - }); - group.add(line1); - - var line2 = new Konva.Line({ - points: [0, 0, 300, 0], - stroke: 'green', - strokeWidth: 40 / scale, - y: 60 / scale, - }); - group.add(line2); - - layer.add(group); - stage.add(layer); - - var shape = layer.getIntersection({ - x: 10, - y: 60, - }); - assert.equal(shape, line2, 'second line detected'); - - shape = layer.getIntersection({ - x: 10, - y: 4, - }); - assert.equal(shape, line1, 'first line detected'); - }); - - it('line get size', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var line = new Konva.Line({ - points: [73, 160, 340, 23, 500, 109, 500, 180], - stroke: 'blue', - - strokeWidth: 10, - }); - - layer.add(line); - stage.add(layer); - - assert.deepEqual(line.size(), { - width: 500 - 73, - height: 180 - 23, - }); - }); - - it('getSelfRect', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var blob = new Konva.Line({ - x: 50, - y: 50, - points: [-25, 50, 250, -30, 150, 50, 250, 110], - stroke: 'blue', - strokeWidth: 10, - draggable: true, - fill: '#aaf', - closed: true, - }); - - layer.add(blob); - stage.add(layer); - - assert.deepEqual(blob.getSelfRect(), { - x: -25, - y: -30, - width: 275, - height: 140, - }); - }); - - it('getClientRect', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var poly = new Konva.Line({ - x: 0, - y: 0, - points: [-100, 0, +100, 0, +100, 100, -100, 100], - closed: true, - fill: '#0f0', - }); - - stage.position({ - x: 150, - y: 50, - }); - - layer.add(poly); - stage.add(layer); - - var rect = layer.getClientRect({ relativeTo: stage as any }); - assert.deepEqual(rect, { - x: -100, - y: 0, - width: 200, - height: 100, - }); - }); - - it('getClientRect with tension', function () { - var stage = addStage(); - stage.draggable(true); - var layer = new Konva.Layer(); - - var line = new Konva.Line({ - x: 0, - y: 0, - points: [75, 75, 100, 200, 300, 140], - tension: 0.5, - stroke: '#0f0', - }); - layer.add(line); - - var client = line.getClientRect(); - var rect = new Konva.Rect(Konva.Util._assign({ stroke: 'red' }, client)); - layer.add(rect); - - stage.add(layer); - - assert.equal(Math.round(client.x), 56, 'check x'); - assert.equal(Math.round(client.y), 74, 'check y'); - assert.equal(Math.round(client.width), 245, 'check width'); - assert.equal(Math.round(client.height), 147, 'check height'); - }); - - it('getClientRect with tension 2', function () { - var stage = addStage(); - stage.draggable(true); - var layer = new Konva.Layer(); - - var line = new Konva.Line({ - x: 0, - y: 0, - points: [ - 494.39880507841673, - 795.3696788648244, - 494.49880507841675, - 795.4696788648245, - 494.39880507841673, - 796.8633308439133, - 489.9178491411501, - 798.3569828230022, - 480.95593726661684, - 802.8379387602688, - 467.513069454817, - 810.3061986557132, - 451.0828976848394, - 820.7617625093353, - 433.15907393577294, - 832.7109783420462, - 415.2352501867065, - 846.1538461538461, - 398.8050784167289, - 859.596713965646, - 383.8685586258402, - 871.545929798357, - 374.90664675130694, - 880.5078416728902, - 371.9193427931292, - 883.4951456310679, - 371.9193427931292, - 883.4951456310679, - 371.9193427931292, - 883.4951456310679, - 376.40029873039583, - 882.0014936519791, - 395.8177744585511, - 876.0268857356235, - 443.6146377893951, - 856.6094100074682, - 507.84167289021656, - 838.6855862584017, - 551.1575802837939, - 825.2427184466019, - 624.3465272591486, - 807.3188946975355, - 696.0418222554144, - 789.395070948469, - 758.7752053771471, - 777.445855115758, - 802.0911127707244, - 772.9648991784914, - 820.0149365197909, - 771.4712471994025, - 821.5085884988797, - 771.4712471994025, - 820.0149365197909, - 775.9522031366691, - 799.1038088125466, - 790.8887229275579, - 743.8386855862584, - 825.2427184466019, - 652.7259148618372, - 871.545929798357, - 542.1956684092606, - 926.8110530246452, - 455.563853622106, - 977.5952203136669, - 412.24794622852875, - 1010.455563853622, - 397.31142643764, - 1026.8857356235997, - 397.31142643764, - 1032.8603435399552, - 400.29873039581776, - 1038.8349514563106, - 415.2352501867065, - 1043.3159073935774, - 463.0321135175504, - 1043.3159073935774, - 563.1067961165048, - 1040.3286034353996, - 696.0418222554144, - 1032.8603435399552, - 787.1545929798357, - 1026.8857356235997, - 921.5832710978342, - 1017.9238237490664, - 1018.6706497386109, - 1013.4428678117998, - 1069.4548170276325, - 1013.4428678117998, - 1076.923076923077, - 1013.4428678117998, - 1075.4294249439881, - 1014.9365197908887, - 1051.530993278566, - 1026.8857356235997, - 979.8356982823002, - 1053.7714712471993, - 888.722927557879, - 1079.1635548917102, - 761.7625093353248, - 1116.504854368932, - 672.1433905899925, - 1150.858849887976, - 628.8274831964152, - 1171.7699775952203, - 615.3846153846154, - 1180.7318894697535, - 615.3846153846154, - 1182.2255414488425, - 618.3719193427931, - 1183.7191934279313, - 633.3084391336819, - 1182.2255414488425, - 687.0799103808812, - 1171.7699775952203, - 775.2053771471248, - 1150.858849887976, - 902.1657953696788, - 1116.504854368932, - 990.2912621359224, - 1091.1127707244211, - 1082.8976848394325, - 1062.7333831217327, - 1133.681852128454, - 1046.303211351755, - 1144.1374159820762, - 1041.8222554144884, - 1144.1374159820762, - 1041.8222554144884, - 1141.1501120238984, - 1041.8222554144884, - 1117.2516803584765, - 1043.3159073935774, - 1082.8976848394325, - 1046.303211351755, - 1008.2150858849888, - 1062.7333831217327, - 917.1023151605675, - 1092.6064227035101, - 861.8371919342793, - 1117.9985063480208, - 814.0403286034353, - 1152.352501867065, - 794.62285287528, - 1176.250933532487, - 790.1418969380135, - 1189.6938013442868, - 793.1292008961912, - 1198.65571321882, - 802.0911127707244, - 1206.1239731142643, - 831.9641523525019, - 1216.5795369678865, - 903.6594473487677, - 1225.5414488424196, - 1014.1896938013442, - 1228.5287528005974, - 1148.6183719193427, - 1228.5287528005974, - 1272.591486183719, - 1225.5414488424196, - 1314.4137415982075, - 1225.5414488424196, - 1326.3629574309186, - 1225.5414488424196, - 1326.3629574309186, - 1225.5414488424196, - 1314.4137415982075, - 1228.5287528005974, - 1272.591486183719, - 1237.4906646751306, - 1197.9088872292755, - 1247.9462285287527, - 1105.3024645257656, - 1270.3510082150858, - 1048.5436893203882, - 1286.7811799850635, - 1024.6452576549664, - 1295.7430918595967, - 1006.7214339058999, - 1306.1986557132188, - 1000.7468259895444, - 1313.6669156086632, - 1000.7468259895444, - 1315.160567587752, - 1003.7341299477222, - 1316.6542195668408, - 1015.6833457804331, - 1319.6415235250186, - 1050.0373412994772, - 1321.1351755041076, - 1103.8088125466766, - 1321.1351755041076, - 1169.529499626587, - 1316.6542195668408, - 1220.3136669156086, - 1310.6796116504854, - 1248.6930545182972, - 1307.6923076923076, - 1253.1740104555638, - 1307.6923076923076, - 1253.1740104555638, - 1307.6923076923076, - 1253.1740104555638, - 1307.6923076923076, - 1248.6930545182972, - 1309.1859596713964, - 1229.275578790142, - 1312.1732636295742, - 1199.4025392083645, - 1319.6415235250186, - 1172.5168035847648, - 1330.0970873786407, - 1154.5929798356983, - 1342.0463032113516, - 1144.1374159820762, - 1353.9955190440626, - 1139.6564600448096, - 1361.463778939507, - 1138.1628080657206, - 1364.4510828976847, - 1138.1628080657206, - 1365.9447348767737, - 1138.1628080657206, - 1365.9447348767737, - ], - tension: 0.5, - stroke: '#0f0', - }); - layer.add(line); - - var client = line.getClientRect(); - var rect = new Konva.Rect(Konva.Util._assign({ stroke: 'red' }, client)); - layer.add(rect); - - stage.add(layer); - - assert.equal(Math.round(client.x), 371, 'check x'); - assert.equal(Math.round(client.y), 770, 'check y'); - assert.equal(Math.round(client.width), 956, 'check width'); - assert.equal(Math.round(client.height), 597, 'check height'); - }); - - it('getClientRect with low number of points', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var line = new Konva.Line({ - x: 0, - y: 0, - points: [], - tension: 0.5, - stroke: '#0f0', - }); - layer.add(line); - layer.draw(); - - var client = line.getClientRect(); - - assert.equal(client.x, -1, 'check x'); - assert.equal(client.y, -1, 'check y'); - assert.equal(client.width, 2, 'check width'); - assert.equal(client.height, 2, 'check height'); - - line.points([10, 10]); - client = line.getClientRect(); - - assert.equal(client.x, 9, 'check x'); - assert.equal(client.y, 9, 'check y'); - assert.equal(client.width, 2, 'check width'); - assert.equal(client.height, 2, 'check height'); - }); - - it('line caching', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var blob = new Konva.Line({ - x: 50, - y: 50, - points: [-25, 50, 250, -30, 150, 50, 250, 110], - stroke: 'black', - strokeWidth: 10, - draggable: true, - closed: true, - }); - - layer.add(blob); - var layer2 = layer.clone(); - blob.cache({ - offset: 30, - }); - stage.add(layer); - stage.add(layer2); - layer2.hide(); - compareLayers(layer, layer2, 150); - }); - - it('updating points with old mutable array should trigger recalculations', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var points = [-25, 50, 250, -30, 150, 50]; - var blob = new Konva.Line({ - x: 50, - y: 50, - points: points, - stroke: 'blue', - strokeWidth: 10, - draggable: true, - closed: true, - tension: 1, - }); - - var tensionPoints = blob.getTensionPoints(); - points.push(250, 100); - blob.points(points); - - layer.add(blob); - stage.add(layer); - - assert.equal( - tensionPoints === blob.getTensionPoints(), - false, - 'calculated points should change' - ); - }); - - it('hit test for scaled line', function () { - var stage = addStage(); - var scale = 42; - stage.scaleX(scale); - stage.scaleY(scale); - var layer = new Konva.Layer(); - stage.add(layer); - - var points = [1, 1, 7, 2, 8, 7, 2, 6]; - var line = new Konva.Line({ - points: points.map(function (v) { - return (v * 20) / scale; - }), - closed: true, - fill: 'green', - draggable: true, - }); - layer.add(line); - layer.draw(); - - assert.equal(line.hasHitStroke(), false); - assert.equal(layer.getIntersection({ x: 1, y: 1 }), null); - - layer.toggleHitCanvas(); - }); - - it('getClientRect with scaling', function () { - var stage = addStage(); - var scale = 42; - stage.scaleX(scale); - stage.scaleY(scale); - var layer = new Konva.Layer(); - stage.add(layer); - - var points = [1, 1, 7, 2, 8, 7, 2, 6]; - var line = new Konva.Line({ - points: points.map(function (v) { - return (v * 20) / scale; - }), - closed: true, - fill: 'green', - draggable: true, - }); - layer.add(line); - layer.draw(); - - var client = line.getClientRect(); - - assert.equal(client.x, 20, 'check x'); - assert.equal(client.y, 20, 'check y'); - assert.equal(client.width, 140, 'check width'); - assert.equal(client.height, 120, 'check height'); - }); -}); diff --git a/test/unit/MouseEvents-test.ts b/test/unit/MouseEvents-test.ts deleted file mode 100644 index ed08d1441..000000000 --- a/test/unit/MouseEvents-test.ts +++ /dev/null @@ -1,2443 +0,0 @@ -import { assert } from 'chai'; - -import { - addStage, - Konva, - simulateMouseDown, - simulateMouseMove, - simulateMouseUp, - simulateTouchStart, - simulateTouchEnd, - isNode, -} from './test-utils'; - -describe('MouseEvents', function () { - // ====================================================== - it('remove shape with onclick', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - draggable: true, - }); - - layer.add(circle); - stage.add(layer); - - function remove() { - circle.remove(); - layer.draw(); - } - - circle.on('click', function () { - setTimeout(remove, 0); - }); - - simulateMouseDown(stage, { - x: 291, - y: 112, - }); - - simulateMouseUp(stage, { - x: 291, - y: 112, - }); - Konva.DD._endDragAfter({ dragEndNode: circle }); - }); - - // ====================================================== - it('test listening true/false with clicks', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - }); - - var clickCount = 0; - - circle.on('click', function () { - clickCount++; - }); - - layer.add(circle); - stage.add(layer); - - // ----------------------------------- - simulateMouseDown(stage, { - x: 291, - y: 112, - }); - simulateMouseUp(stage, { - x: 291, - y: 112, - }); - assert.equal(clickCount, 1, 'should be 1 click'); - - // ----------------------------------- - circle.listening(false); - simulateMouseDown(stage, { - x: 291, - y: 112, - }); - simulateMouseUp(stage, { - x: 291, - y: 112, - }); - assert.equal( - clickCount, - 1, - 'should be 1 click even though another click occurred' - ); - - // ----------------------------------- - circle.listening(true); - simulateMouseDown(stage, { - x: 291, - y: 112, - }); - simulateMouseUp(stage, { - x: 291, - y: 112, - }); - assert.equal(clickCount, 2, 'should be 2 clicks'); - }); - - // ====================================================== - it('click mapping', function () { - var stage = addStage(); - var layer = new Konva.Layer({ - sceneFunc: function () { - var context = this.getContext(); - context.beginPath(); - context.moveTo(200, 50); - context.lineTo(420, 80); - context.quadraticCurveTo(300, 100, 260, 170); - context.closePath(); - context.fillStyle = 'blue'; - context.fill(context); - }, - }); - - var redCircle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'red', - }); - - var greenCircle = new Konva.Circle({ - x: 400, - y: stage.height() / 2, - radius: 70, - fill: 'green', - }); - - var redClicks = 0; - var greenClicks = 0; - - redCircle.on('click', function () { - //console.log('clicked redCircle'); - redClicks++; - }); - - greenCircle.on('click', function () { - //console.log('clicked greenCircle'); - greenClicks++; - }); - - layer.add(redCircle); - layer.add(greenCircle); - - stage.add(layer); - - // mousedown and mouseup on red circle - simulateMouseDown(stage, { - x: 284, - y: 113, - }); - - simulateMouseUp(stage, { - x: 284, - y: 113, - }); - - assert.equal(redClicks, 1, 'red circle should have 1 click'); - assert.equal(greenClicks, 0, 'green circle should have 0 clicks'); - - // mousedown and mouseup on green circle - simulateMouseDown(stage, { - x: 397, - y: 108, - }); - - simulateMouseUp(stage, { - x: 397, - y: 108, - }); - - assert.equal(redClicks, 1, 'red circle should have 1 click'); - assert.equal(greenClicks, 1, 'green circle should have 1 click'); - - // mousedown red circle and mouseup on green circle - simulateMouseDown(stage, { - x: 284, - y: 113, - }); - - simulateMouseUp(stage, { - x: 397, - y: 108, - }); - - assert.equal(redClicks, 1, 'red circle should still have 1 click'); - assert.equal(greenClicks, 1, 'green circle should still have 1 click'); - }); - - // ====================================================== - it('text events', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var text = new Konva.Text({ - x: 290, - y: 111, - fontFamily: 'Calibri', - fontSize: 30, - fill: 'red', - text: 'Testing 123', - draggable: true, - }); - - var click = false; - - text.on('click', function () { - //console.log('text click'); - click = true; - }); - - layer.add(text); - stage.add(layer); - - simulateMouseDown(stage, { - x: 300, - y: 120, - }); - - simulateMouseUp(stage, { - x: 300, - y: 120, - }); - - assert.equal( - click, - true, - 'click event should have been fired when mousing down and then up on text' - ); - }); - - // ====================================================== - it('modify fill stroke and stroke width on hover with circle', function (done) { - var stage = addStage(); - var layer = new Konva.Layer({ - throttle: 999, - }); - var circle = new Konva.Circle({ - x: 380, - y: stage.height() / 2, - radius: 70, - strokeWidth: 4, - fill: 'red', - stroke: 'black', - }); - - circle.on('mouseover', function () { - this.fill('yellow'); - this.stroke('purple'); - this.strokeWidth(20); - //console.log('mouseover') - layer.draw(); - }); - - circle.on('mouseout', function () { - this.fill('red'); - this.stroke('black'); - this.strokeWidth(4); - //console.log('mouseout') - layer.draw(); - }); - - layer.add(circle); - stage.add(layer); - - assert.equal(circle.fill(), 'red', 'circle fill should be red'); - assert.equal(circle.stroke(), 'black', 'circle stroke should be black'); - - setTimeout(function () { - simulateMouseMove(stage, { - x: 377, - y: 101, - }); - - assert.equal(circle.fill(), 'yellow', 'circle fill should be yellow'); - assert.equal(circle.stroke(), 'purple', 'circle stroke should be purple'); - - setTimeout(function () { - // move mouse back out of circle - simulateMouseMove(stage, { - x: 157, - y: 138, - }); - - assert.equal(circle.fill(), 'red', 'circle fill should be red'); - assert.equal(circle.stroke(), 'black', 'circle stroke should be black'); - done(); - }, 20); - }, 20); - }); - - it.skip('mouseleave and mouseenter', function () { - var stage = addStage(); - var layer = new Konva.Layer({ - throttle: 999, - }); - var circle = new Konva.Circle({ - x: 100, - y: 100, - radius: 70, - strokeWidth: 4, - fill: 'red', - stroke: 'black', - }); - layer.add(circle); - stage.add(layer); - - var mouseenter = 0; - circle.on('mouseenter', () => { - mouseenter += 1; - }); - - var mouseleave = 0; - circle.on('mouseleave', () => { - mouseleave += 1; - }); - - simulateMouseMove(stage, { - x: 10, - y: 10, - }); - simulateMouseMove(stage, { - x: 100, - y: 100, - }); - simulateMouseMove(stage, { - x: 100, - y: 100, - }); - assert.equal(mouseenter, 1); - assert.equal(mouseleave, 0); - simulateMouseMove(stage, { - x: 10, - y: 10, - }); - assert.equal(mouseenter, 1); - assert.equal(mouseleave, 1); - }); - - // ====================================================== - it('mousedown mouseup mouseover mouseout mousemove click dblclick', function (done) { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'red', - stroke: 'black', - strokeWidth: 4, - }); - - // desktop events - var mousedown = false; - var mouseup = false; - var click = false; - var dblclick = false; - var mouseover = false; - var mouseout = false; - var mousemove = false; - - circle.on('mousedown', function () { - mousedown = true; - //log('mousedown'); - }); - - circle.on('mouseup', function () { - mouseup = true; - //log('mouseup'); - }); - - circle.on('mouseover', function () { - mouseover = true; - //log('mouseover'); - }); - - circle.on('mouseout', function () { - mouseout = true; - //log('mouseout'); - }); - - circle.on('mousemove', function () { - mousemove = true; - //log('mousemove'); - }); - - circle.on('click', function () { - click = true; - //log('click'); - }); - - circle.on('dblclick', function () { - dblclick = true; - //log('dblclick'); - }); - - layer.add(circle); - stage.add(layer); - - setTimeout(function () { - // move mouse to center of circle to trigger mouseover - simulateMouseMove(stage, { - x: 290, - y: 100, - }); - - assert(mouseover, '1) mouseover should be true'); - assert(mousemove, '1) mousemove should be true'); - assert(!mousedown, '1) mousedown should be false'); - assert(!mouseup, '1) mouseup should be false'); - assert(!click, '1) click should be false'); - assert(!dblclick, '1) dblclick should be false'); - assert(!mouseout, '1) mouseout should be false'); - - setTimeout(function () { - // move mouse again inside circle to trigger mousemove - simulateMouseMove(stage, { - x: 290, - y: 100, - }); - - assert(mouseover, '2) mouseover should be true'); - assert(mousemove, '2) mousemove should be true'); - assert(!mousedown, '2) mousedown should be false'); - assert(!mouseup, '2) mouseup should be false'); - assert(!click, '2) click should be false'); - assert(!dblclick, '2) dblclick should be false'); - assert(!mouseout, '2) mouseout should be false'); - - // mousedown inside circle - simulateMouseDown(stage, { - x: 290, - y: 100, - }); - - assert(mouseover, '3) mouseover should be true'); - assert(mousemove, '3) mousemove should be true'); - assert(mousedown, '3) mousedown should be true'); - assert(!mouseup, '3) mouseup should be false'); - assert(!click, '3) click should be false'); - assert(!dblclick, '3) dblclick should be false'); - assert(!mouseout, '3) mouseout should be false'); - - // mouseup inside circle - simulateMouseUp(stage, { - x: 290, - y: 100, - }); - - assert(mouseover, '4) mouseover should be true'); - assert(mousemove, '4) mousemove should be true'); - assert(mousedown, '4) mousedown should be true'); - assert(mouseup, '4) mouseup should be true'); - assert(click, '4) click should be true'); - assert(!dblclick, '4) dblclick should be false'); - assert(!mouseout, '4) mouseout should be false'); - - // mousedown inside circle - simulateMouseDown(stage, { - x: 290, - y: 100, - }); - - assert(mouseover, '5) mouseover should be true'); - assert(mousemove, '5) mousemove should be true'); - assert(mousedown, '5) mousedown should be true'); - assert(mouseup, '5) mouseup should be true'); - assert(click, '5) click should be true'); - assert(!dblclick, '5) dblclick should be false'); - assert(!mouseout, '5) mouseout should be false'); - - // mouseup inside circle to trigger double click - simulateMouseUp(stage, { - x: 290, - y: 100, - }); - - assert(mouseover, '6) mouseover should be true'); - assert(mousemove, '6) mousemove should be true'); - assert(mousedown, '6) mousedown should be true'); - assert(mouseup, '6) mouseup should be true'); - assert(click, '6) click should be true'); - assert(dblclick, '6) dblclick should be true'); - assert(!mouseout, '6) mouseout should be false'); - - setTimeout(function () { - // move mouse outside of circle to trigger mouseout - simulateMouseMove(stage, { - x: 0, - y: 100, - }); - - assert(mouseover, '7) mouseover should be true'); - assert(mousemove, '7) mousemove should be true'); - assert(mousedown, '7) mousedown should be true'); - assert(mouseup, '7) mouseup should be true'); - assert(click, '7) click should be true'); - assert(dblclick, '7) dblclick should be true'); - assert(mouseout, '7) mouseout should be true'); - done(); - }, 20); - }, 20); - }, 20); - }); - - // ====================================================== - it('test group mousedown events', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var group = new Konva.Group(); - - var redCircle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 80, - strokeWidth: 4, - fill: 'red', - stroke: 'black', - name: 'red', - }); - - var greenCircle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 40, - strokeWidth: 4, - fill: 'green', - stroke: 'black', - name: 'green', - }); - - group.add(redCircle); - group.add(greenCircle); - - layer.add(group); - stage.add(layer); - - var groupMousedowns = 0; - var greenCircleMousedowns = 0; - - group.on('mousedown', function () { - groupMousedowns++; - }); - - greenCircle.on('mousedown', function () { - greenCircleMousedowns++; - }); - - simulateMouseDown(stage, { - x: 285, - y: 100, - }); - - assert.equal(groupMousedowns, 1, 'groupMousedowns should be 1'); - assert.equal(greenCircleMousedowns, 1, 'greenCircleMousedowns should be 1'); - - simulateMouseDown(stage, { - x: 332, - y: 139, - }); - - assert.equal(groupMousedowns, 2, 'groupMousedowns should be 2'); - assert.equal(greenCircleMousedowns, 1, 'greenCircleMousedowns should be 1'); - - simulateMouseDown(stage, { - x: 285, - y: 92, - }); - - assert.equal(groupMousedowns, 3, 'groupMousedowns should be 3'); - assert.equal(greenCircleMousedowns, 2, 'greenCircleMousedowns should be 2'); - - simulateMouseDown(stage, { - x: 221, - y: 146, - }); - - //assert.equal(groupMousedowns, 4, 'groupMousedowns should be 4'); - assert.equal(greenCircleMousedowns, 2, 'greenCircleMousedowns should be 2'); - }); - - // ====================================================== - it('test mousedown events with antialiasing', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var group = new Konva.Group(); - - var greenCircle = new Konva.Circle({ - x: 50, - y: 50, - radius: 50, - fill: 'green', - name: 'green', - }); - - var groupMousedowns = 0; - group.add(greenCircle); - layer.add(group); - - group.cache(); - group.scale({ - x: 5, - y: 5, - }); - group.on('mousedown', function () { - groupMousedowns++; - }); - - stage.add(layer); - layer.draw(); - - simulateMouseDown(stage, { - x: 135, - y: 30, - }); - - assert.equal(groupMousedowns, 1, 'groupMousedowns should be 1'); - }); - it('test mousemove events with antialiasing', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var group = new Konva.Group({ - name: 'group', - }); - var rect1 = new Konva.Rect({ - x: 0, - y: 0, - width: 100, - height: 100, - fill: 'red', - }); - - var rect2 = new Konva.Rect({ - x: 50, - y: 0, - width: 70, - height: 70, - rotation: 45, - fill: 'green', - }); - group.add(rect1).add(rect2); - layer.add(group); - group.scaleX(5); - group.scaleY(5); - var mouseenterCount = 0; - group.on('mouseenter', function () { - mouseenterCount++; - }); - - stage.add(layer); - - // move mouse slowly - for (var i = 99; i < 129; i++) { - simulateMouseMove(stage, { - x: i, - y: 135, - }); - } - assert.equal(mouseenterCount, 1, 'mouseenterCount should be 1'); - }); - - // ====================================================== - it('group mouseenter events', function (done) { - var stage = addStage(); - var layer = new Konva.Layer(); - var group = new Konva.Group({ - name: 'group', - }); - - var redMouseenters = 0; - var redMouseleaves = 0; - var greenMouseenters = 0; - var greenMouseleaves = 0; - var groupMouseenters = 0; - var groupMouseleaves = 0; - - var redCircle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 80, - strokeWidth: 4, - fill: 'red', - stroke: 'black', - name: 'red', - }); - - var greenCircle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 40, - strokeWidth: 4, - fill: 'green', - stroke: 'black', - name: 'green', - }); - - group.on('mouseenter', function () { - groupMouseenters++; - //console.log('group over') - }); - - group.on('mouseleave', function () { - groupMouseleaves++; - //console.log('group out') - }); - - redCircle.on('mouseenter', function () { - redMouseenters++; - //console.log('red over') - }); - - redCircle.on('mouseleave', function () { - redMouseleaves++; - //console.log('red out') - }); - - greenCircle.on('mouseenter', function () { - greenMouseenters++; - //console.log('green over') - }); - - greenCircle.on('mouseleave', function () { - greenMouseleaves++; - //console.log('green out') - }); - - group.add(redCircle); - group.add(greenCircle); - - layer.add(group); - stage.add(layer); - - setTimeout(function () { - // move mouse outside of circles - simulateMouseMove(stage, { - x: 177, - y: 146, - }); - - assert.equal(redMouseenters, 0, 'redMouseenters should be 0'); - assert.equal(redMouseleaves, 0, 'redMouseleaves should be 0'); - assert.equal(greenMouseenters, 0, 'greenMouseenters should be 0'); - assert.equal(greenMouseleaves, 0, 'greenMouseleaves should be 0'); - assert.equal(groupMouseenters, 0, 'groupMouseenters should be 0'); - assert.equal(groupMouseleaves, 0, 'groupMouseleaves should be 0'); - - setTimeout(function () { - // move mouse inside of red circle - simulateMouseMove(stage, { - x: 236, - y: 145, - }); - - //console.log('groupMouseenters=' + groupMouseenters); - - assert.equal(redMouseenters, 1, 'redMouseenters should be 1'); - assert.equal(redMouseleaves, 0, 'redMouseleaves should be 0'); - assert.equal(greenMouseenters, 0, 'greenMouseenters should be 0'); - assert.equal(greenMouseleaves, 0, 'greenMouseleaves should be 0'); - assert.equal(groupMouseenters, 1, 'groupMouseenters should be 1'); - assert.equal(groupMouseleaves, 0, 'groupMouseleaves should be 0'); - - setTimeout(function () { - // move mouse inside of green circle - simulateMouseMove(stage, { - x: 284, - y: 118, - }); - - assert.equal(redMouseenters, 1, 'redMouseenters should be 1'); - assert.equal(redMouseleaves, 1, 'redMouseleaves should be 1'); - assert.equal(greenMouseenters, 1, 'greenMouseenters should be 1'); - assert.equal(greenMouseleaves, 0, 'greenMouseleaves should be 0'); - assert.equal(groupMouseenters, 1, 'groupMouseenters should be 1'); - assert.equal(groupMouseleaves, 0, 'groupMouseleaves should be 0'); - - setTimeout(function () { - // move mouse back to red circle - - simulateMouseMove(stage, { - x: 345, - y: 105, - }); - - assert.equal(redMouseenters, 2, 'redMouseenters should be 2'); - assert.equal(redMouseleaves, 1, 'redMouseleaves should be 1'); - assert.equal(greenMouseenters, 1, 'greenMouseenters should be 1'); - assert.equal(greenMouseleaves, 1, 'greenMouseleaves should be 1'); - assert.equal(groupMouseenters, 1, 'groupMouseenters should be 1'); - assert.equal(groupMouseleaves, 0, 'groupMouseleaves should be 0'); - - setTimeout(function () { - // move mouse outside of circles - simulateMouseMove(stage, { - x: 177, - y: 146, - }); - - assert.equal(redMouseenters, 2, 'redMouseenters should be 2'); - assert.equal(redMouseleaves, 2, 'redMouseleaves should be 2'); - assert.equal(greenMouseenters, 1, 'greenMouseenters should be 1'); - assert.equal(greenMouseleaves, 1, 'greenMouseleaves should be 1'); - assert.equal(groupMouseenters, 1, 'groupMouseenters should be 1'); - assert.equal(groupMouseleaves, 1, 'groupMouseleaves should be 1'); - - //document.body.appendChild(layer.bufferCanvas.element) - - //layer.bufferCanvas.element.style.marginTop = '220px'; - - done(); - }, 20); - }, 20); - }, 20); - }, 20); - }, 20); - }); - - // ====================================================== - it('test mouseleave with multiple groups', function () { - var stage = addStage(); - var layer = new Konva.Layer({ - id: 'layer', - }); - - var rect1 = new Konva.Rect({ - x: 0, - y: 0, - width: 100, - height: 100, - fill: 'red', - id: 'redRect', - }); - - var rect2 = new Konva.Rect({ - x: 50, - y: 0, - width: 70, - height: 70, - rotation: 45, - fill: 'green', - id: 'greenRect', - }); - - var group = new Konva.Group({ - id: 'group1', - }); - var group2 = new Konva.Group({ - id: 'group2', - }); - group.add(rect1); - group2.add(rect2); - group.add(group2); - layer.add(group); - stage.add(layer); - layer.draw(); - - var groupMouseenter = 0; - var groupMouseleave = 0; - var groupMouseover = 0; - var groupMouseout = 0; - - var group2Mouseleave = 0; - var group2Mouseenter = 0; - var group2Mouseover = 0; - var group2Mouseout = 0; - - group.on('mouseenter', function () { - groupMouseenter += 1; - }); - group.on('mouseleave', function () { - groupMouseleave += 1; - }); - group.on('mouseover', function () { - groupMouseover += 1; - }); - group.on('mouseout', function () { - groupMouseout += 1; - }); - - group2.on('mouseenter', function () { - group2Mouseenter += 1; - }); - group2.on('mouseleave', function () { - group2Mouseleave += 1; - }); - group2.on('mouseover', function () { - group2Mouseover += 1; - }); - group2.on('mouseout', function () { - group2Mouseout += 1; - }); - - simulateMouseMove(stage, { - x: 10, - y: 10, - }); - assert.equal(groupMouseenter, 1, 'move1 : group mouseenter should trigger'); - assert.equal( - group2Mouseenter, - 0, - 'move1 : group2 mouseenter should not trigger' - ); - - assert.equal( - groupMouseleave, - 0, - 'move1 : group mouseleave should not trigger' - ); - assert.equal( - group2Mouseleave, - 0, - 'move1 : group2 mouseleave should not trigger' - ); - - assert.equal(groupMouseover, 1, 'move1 : group mouseover should trigger'); - assert.equal( - group2Mouseover, - 0, - 'move1 : group2 mouseover should not trigger' - ); - - assert.equal(groupMouseout, 0, 'move1 : group mouseout should not trigger'); - assert.equal( - group2Mouseout, - 0, - 'move1 : group2 mouseout should not trigger' - ); - - simulateMouseMove(stage, { - x: 50, - y: 50, - }); - assert.equal( - groupMouseenter, - 1, - 'move2 : group mouseenter should not trigger' - ); - assert.equal( - group2Mouseenter, - 1, - 'move2 : group2 mouseenter should trigger' - ); - - assert.equal( - groupMouseleave, - 0, - 'move2 : group mouseleave should not trigger' - ); - assert.equal( - group2Mouseleave, - 0, - 'move2 : group2 mouseleave should not trigger' - ); - - assert.equal(groupMouseover, 2, 'move2 : group mouseover should trigger'); - assert.equal(group2Mouseover, 1, 'move2 : group2 mouseover should trigger'); - - assert.equal(groupMouseout, 1, 'move2 : group mouseout should trigger'); - assert.equal( - group2Mouseout, - 0, - 'move2 : group2 mouseout should not trigger' - ); - - simulateMouseMove(stage, { - x: 10, - y: 10, - }); - assert.equal( - groupMouseenter, - 1, - 'move3 : group mouseenter should not trigger' - ); - assert.equal( - group2Mouseenter, - 1, - 'move3 : group2 mouseenter should not trigger' - ); - - assert.equal( - groupMouseleave, - 0, - 'move3 : group mouseleave should not trigger' - ); - assert.equal( - group2Mouseleave, - 1, - 'move3 : group2 mouseleave should trigger' - ); - - assert.equal(groupMouseover, 3, 'move3 : group mouseover should trigger'); - assert.equal(group2Mouseover, 1, 'move3 : group2 mouseover should trigger'); - - assert.equal(groupMouseout, 2, 'move3 : group mouseout should trigger'); - assert.equal(group2Mouseout, 1, 'move3 : group2 mouseout should trigger'); - - simulateMouseMove(stage, { - x: 50, - y: 50, - }); - - assert.equal(groupMouseenter, 1, 'move4 : mouseenter should not trigger'); - assert.equal( - group2Mouseenter, - 2, - 'move4 : group2 mouseenter should trigger' - ); - - assert.equal( - groupMouseleave, - 0, - 'move4 : group mouseleave should not trigger' - ); - assert.equal( - group2Mouseleave, - 1, - 'move4 : group2 mouseleave should not trigger' - ); - - assert.equal(groupMouseover, 4, 'move1 : group mouseover should trigger'); - assert.equal(group2Mouseover, 2, 'move1 : group2 mouseover should trigger'); - - assert.equal(groupMouseout, 3, 'move4 : group mouseout should trigger'); - assert.equal( - group2Mouseout, - 1, - 'move4 : group2 mouseout should not trigger' - ); - }); - - // ====================================================== - it('test mouseleave with multiple groups 2', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var group1 = new Konva.Group({ name: 'group1' }); - layer.add(group1); - - var bigRect = new Konva.Rect({ - x: 0, - y: 0, - width: 200, - height: 200, - fill: 'green', - }); - group1.add(bigRect); - - var group21 = new Konva.Group({ name: 'group21' }); - layer.add(group21); - - var group22 = new Konva.Group({ name: 'group22' }); - group21.add(group22); - - var smallShape = new Konva.Rect({ - x: 50, - y: 50, - width: 100, - height: 100, - fill: 'red', - }); - group22.add(smallShape); - stage.draw(); - - var group1Mouseenter = 0; - var group1Mouseleave = 0; - var group1Mouseover = 0; - var group1Mouseout = 0; - - var group21Mouseenter = 0; - var group21Mouseleave = 0; - var group21Mouseover = 0; - var group21Mouseout = 0; - - var group22Mouseenter = 0; - var group22Mouseleave = 0; - var group22Mouseover = 0; - var group22Mouseout = 0; - - group1.on('mouseenter', function () { - group1Mouseenter += 1; - }); - group1.on('mouseleave', function () { - group1Mouseleave += 1; - }); - group1.on('mouseover', function () { - group1Mouseover += 1; - }); - group1.on('mouseout', function () { - group1Mouseout += 1; - }); - - group21.on('mouseenter', function () { - group21Mouseenter += 1; - }); - group21.on('mouseleave', function () { - group21Mouseleave += 1; - }); - group21.on('mouseover', function () { - group21Mouseover += 1; - }); - group21.on('mouseout', function () { - group21Mouseout += 1; - }); - - group22.on('mouseenter', function () { - group22Mouseenter += 1; - }); - group22.on('mouseleave', function () { - group22Mouseleave += 1; - }); - group22.on('mouseover', function () { - group22Mouseover += 1; - }); - group22.on('mouseout', function () { - group22Mouseout += 1; - }); - - simulateMouseMove(stage, { - x: 10, - y: 10, - }); - - assert.equal( - group1Mouseenter, - 1, - 'move1 : group1 mouseenter should trigger' - ); - - simulateMouseMove(stage, { - x: 60, - y: 60, - }); - assert.equal( - group21Mouseenter, - 1, - 'move2 : group21 mouseenter should trigger' - ); - assert.equal( - group22Mouseenter, - 1, - 'move2 : group22 mouseenter should trigger' - ); - assert.equal( - group1Mouseleave, - 1, - 'move2 : group1 mouseleave should trigger' - ); - - simulateMouseMove(stage, { - x: 10, - y: 10, - }); - - assert.equal( - group21Mouseleave, - 1, - 'move3 : group21 mouseleave should trigger' - ); - assert.equal( - group22Mouseleave, - 1, - 'move3 : group22 mouseleave should trigger' - ); - assert.equal( - group1Mouseenter, - 2, - 'move3 : group1 mouseenter should trigger' - ); - }); - - // ====================================================== - it('test mouseleave and mouseenter on deep nesting', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - // top group - var group = new Konva.Group({ - x: 0, - y: 0, - width: stage.width(), - height: stage.height(), - name: 'top-group', - }); - layer.add(group); - - // circle inside top group - var circle = new Konva.Circle({ - x: 50, - y: 50, - radius: 50, - fill: 'green', - }); - group.add(circle); - - // two level nesting - var group2 = new Konva.Group({ - x: 0, - y: 0, - name: 'group-2', - }); - group.add(group2); - - var group3 = new Konva.Group({ - x: 0, - y: 0, - name: 'group-3', - }); - group2.add(group3); - - // circle inside deep group - var circle2 = new Konva.Circle({ - x: 50, - y: 50, - radius: 20, - fill: 'white', - }); - group3.add(circle2); - - layer.draw(); - - var mouseenter = 0; - var mouseleave = 0; - group.on('mouseenter', function () { - mouseenter += 1; - }); - group.on('mouseleave', function () { - mouseleave += 1; - }); - // move to big circle - simulateMouseMove(stage, { - x: 20, - y: 20, - }); - assert.equal(mouseenter, 1, 'first enter big circle'); - assert.equal(mouseleave, 0, 'no leave on first move'); - - // move to small inner circle - simulateMouseMove(stage, { - x: 50, - y: 50, - }); - assert.equal(mouseenter, 1, 'enter small circle'); - assert.equal(mouseleave, 0, 'no leave on second move'); - - // move to big circle - simulateMouseMove(stage, { - x: 20, - y: 20, - }); - assert.equal(mouseenter, 1, 'second enter big circle'); - assert.equal(mouseleave, 0, 'no leave on third move'); - - // move out of group - simulateMouseMove(stage, { - x: 0, - y: 0, - }); - assert.equal(mouseenter, 1, 'mouseenter = 1 at the end'); - assert.equal(mouseleave, 1, 'first mouseleave'); - }); - - // ====================================================== - it('test dblclick to a wrong target', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var leftRect = new Konva.Rect({ - x: 0, - y: 0, - width: 100, - height: 100, - fill: 'red', - }); - layer.add(leftRect); - - var rightRect = new Konva.Rect({ - x: 100, - y: 0, - width: 100, - height: 100, - fill: 'blue', - }); - layer.add(rightRect); - - stage.draw(); - - var leftRectSingleClick = 0; - var rightRectSingleClick = 0; - var rightRectDblClick = 0; - - leftRect.on('click', function () { - leftRectSingleClick++; - }); - rightRect.on('click', function () { - rightRectSingleClick++; - }); - rightRect.on('dblclick', function () { - rightRectDblClick++; - }); - - simulateMouseDown(stage, { - x: 50, - y: 50, - }); - simulateMouseUp(stage, { - x: 50, - y: 50, - }); - assert.equal(leftRectSingleClick, 1, 'leftRect trigger a click'); - - simulateMouseDown(stage, { - x: 150, - y: 50, - }); - simulateMouseUp(stage, { - x: 150, - y: 50, - }); - assert.equal(rightRectSingleClick, 1, 'rightRect trigger a click'); - assert.equal(rightRectDblClick, 0, 'rightRect dblClick should not trigger'); - }); - - // ====================================================== - it('test mouseleave + mouseenter with deep nesting', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var leftRect = new Konva.Rect({ - x: 0, - y: 0, - width: 100, - height: 100, - fill: 'green', - }); - layer.add(leftRect); - - var rightGrandParentGroup = new Konva.Group({ - name: 'rightGrandParentGroup', - }); - layer.add(rightGrandParentGroup); - - var rightParentGroup = new Konva.Group({ name: 'rightParentGroup' }); - rightGrandParentGroup.add(rightParentGroup); - - var rightRect = new Konva.Rect({ - x: 100, - y: 0, - width: 100, - height: 100, - fill: 'red', - }); - rightParentGroup.add(rightRect); - stage.draw(); - - var leftRectMouseenter = 0; - var leftRectMouseleave = 0; - var leftRectMouseover = 0; - var leftRectMouseout = 0; - - var rightGrandParentGroupMouseenter = 0; - var rightGrandParentGroupMouseleave = 0; - var rightGrandParentGroupMouseover = 0; - var rightGrandParentGroupMouseout = 0; - - var rightParentGroupMouseenter = 0; - var rightParentGroupMouseleave = 0; - var rightParentGroupMouseover = 0; - var rightParentGroupMouseout = 0; - - var rightRectMouseenter = 0; - var rightRectMouseleave = 0; - var rightRectMouseover = 0; - var rightRectMouseout = 0; - - leftRect.on('mouseenter', function () { - leftRectMouseenter += 1; - }); - leftRect.on('mouseleave', function () { - leftRectMouseleave += 1; - }); - leftRect.on('mouseover', function () { - leftRectMouseover += 1; - }); - leftRect.on('mouseout', function () { - leftRectMouseout += 1; - }); - - rightGrandParentGroup.on('mouseenter', function () { - rightGrandParentGroupMouseenter += 1; - }); - rightGrandParentGroup.on('mouseleave', function () { - rightGrandParentGroupMouseleave += 1; - }); - rightGrandParentGroup.on('mouseover', function () { - rightGrandParentGroupMouseover += 1; - }); - rightGrandParentGroup.on('mouseout', function () { - rightGrandParentGroupMouseout += 1; - }); - - rightParentGroup.on('mouseenter', function () { - rightParentGroupMouseenter += 1; - }); - rightParentGroup.on('mouseleave', function () { - rightParentGroupMouseleave += 1; - }); - rightParentGroup.on('mouseover', function () { - rightParentGroupMouseover += 1; - }); - rightParentGroup.on('mouseout', function () { - rightParentGroupMouseout += 1; - }); - - rightRect.on('mouseenter', function () { - rightRectMouseenter += 1; - }); - rightRect.on('mouseleave', function () { - rightRectMouseleave += 1; - }); - rightRect.on('mouseover', function () { - rightRectMouseover += 1; - }); - rightRect.on('mouseout', function () { - rightRectMouseout += 1; - }); - - simulateMouseMove(stage, { - x: 50, - y: 50, - }); - - assert.equal( - leftRectMouseenter, - 1, - 'move1 : leftRectMouseenter mouseenter should trigger' - ); - - simulateMouseMove(stage, { - x: 150, - y: 50, - }); - - assert.equal( - leftRectMouseleave, - 1, - 'move2 : leftRectMouseleave mouseleave should trigger' - ); - assert.equal( - rightRectMouseenter, - 1, - 'move2 : rightRect mouseenter should trigger' - ); - assert.equal( - rightParentGroupMouseenter, - 1, - 'move2 : rightParentGroup mouseenter should trigger' - ); - assert.equal( - rightGrandParentGroupMouseenter, - 1, - 'move2 : rightGrandParentGroup mouseenter should trigger' - ); - - simulateMouseMove(stage, { - x: 50, - y: 50, - }); - - assert.equal( - rightRectMouseleave, - 1, - 'move3 : rightRect mouseleave should trigger' - ); - assert.equal( - rightParentGroupMouseleave, - 1, - 'move3 : rightParentGroup mouseleave should trigger' - ); - assert.equal( - rightGrandParentGroupMouseleave, - 1, - 'move3 : rightGrandParentGroup mouseleave should trigger' - ); - - assert.equal( - leftRectMouseenter, - 2, - 'move3 : leftRectMouseenter mouseenter should trigger' - ); - }); - - // ====================================================== - it('test event bubbling', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: 380, - y: stage.height() / 2, - radius: 70, - strokeWidth: 4, - fill: 'red', - stroke: 'black', - id: 'myCircle', - }); - - var group1 = new Konva.Group(); - var group2 = new Konva.Group(); - - /* - * stage - * | - * layer - * | - * group2 - * | - * group1 - * | - * circle - */ - - group1.add(circle); - group2.add(group1); - layer.add(group2); - stage.add(layer); - - // events array - var e = []; - - circle.on('click', function () { - e.push('circle'); - }); - group1.on('click', function () { - e.push('group1'); - }); - group2.on('click', function () { - e.push('group2'); - }); - layer.on('click', function (evt) { - //console.log(evt) - assert.equal(evt.target.id(), 'myCircle'); - assert.equal(evt.type, 'click'); - e.push('layer'); - }); - stage.on('click', function (evt) { - e.push('stage'); - }); - // click on circle - simulateMouseDown(stage, { - x: 374, - y: 114, - }); - simulateMouseUp(stage, { - x: 374, - y: 114, - }); - Konva.DD._endDragAfter({ dragEndNode: circle }); - - assert.equal( - e.toString(), - 'circle,group1,group2,layer,stage', - 'problem with event bubbling' - ); - }); - - // ====================================================== - it('test custom circle hit function', function (done) { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: 380, - y: stage.height() / 2, - radius: 70, - strokeWidth: 4, - fill: 'red', - stroke: 'black', - hitFunc: function (context) { - var _context = context._context; - - _context.beginPath(); - _context.arc(0, 0, this.getRadius() + 100, 0, Math.PI * 2, true); - _context.closePath(); - context.fillStrokeShape(this); - }, - }); - - circle.setDraggable(true); - - layer.add(circle); - stage.add(layer); - - var mouseovers = 0; - var mouseouts = 0; - - circle.on('mouseover', function () { - mouseovers++; - }); - - circle.on('mouseout', function () { - mouseouts++; - }); - - setTimeout(function () { - // move mouse far outside circle - simulateMouseMove(stage, { - x: 113, - y: 112, - }); - - setTimeout(function () { - assert.equal(mouseovers, 0, '1) mouseovers should be 0'); - assert.equal(mouseouts, 0, '1) mouseouts should be 0'); - - simulateMouseMove(stage, { - x: 286, - y: 118, - }); - - assert.equal(mouseovers, 1, '2) mouseovers should be 1'); - assert.equal(mouseouts, 0, '2)mouseouts should be 0'); - - setTimeout(function () { - simulateMouseMove(stage, { - x: 113, - y: 112, - }); - - assert.equal(mouseovers, 1, '3) mouseovers should be 1'); - assert.equal(mouseouts, 1, '3) mouseouts should be 1'); - - // set drawBufferFunc with setter - - circle.hitFunc(function (context) { - var _context = context._context; - _context.beginPath(); - _context.arc(0, 0, this.getRadius() - 50, 0, Math.PI * 2, true); - _context.closePath(); - context.fillStrokeShape(this); - }); - - layer.getHitCanvas().getContext().clear(); - layer.drawHit(); - - setTimeout(function () { - // move mouse far outside circle - simulateMouseMove(stage, { - x: 113, - y: 112, - }); - - assert.equal(mouseovers, 1, '4) mouseovers should be 1'); - assert.equal(mouseouts, 1, '4) mouseouts should be 1'); - - setTimeout(function () { - simulateMouseMove(stage, { - x: 286, - y: 118, - }); - - assert.equal(mouseovers, 1, '5) mouseovers should be 1'); - assert.equal(mouseouts, 1, '5) mouseouts should be 1'); - - setTimeout(function () { - simulateMouseMove(stage, { - x: 321, - y: 112, - }); - - assert.equal(mouseovers, 1, '6) mouseovers should be 1'); - assert.equal(mouseouts, 1, '6) mouseouts should be 1'); - - setTimeout(function () { - // move to center of circle - simulateMouseMove(stage, { - x: 375, - y: 112, - }); - - assert.equal(mouseovers, 2, '7) mouseovers should be 2'); - assert.equal(mouseouts, 1, '7) mouseouts should be 1'); - - done(); - }, 20); - }, 20); - }, 20); - }, 20); - }, 20); - }, 20); - }, 20); - }); - - it('change ratio for hit graph', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 50, - stroke: 'black', - fill: 'red', - strokeWidth: 5, - draggable: true, - }); - - layer.add(circle); - stage.add(layer); - - layer.getHitCanvas().setPixelRatio(0.5); - - layer.draw(); - var shape = layer.getIntersection({ - x: stage.width() / 2 - 55, - y: stage.height() / 2 - 55, - }); - assert.equal(!!shape, false, 'no shape here'); - shape = layer.getIntersection({ - x: stage.width() / 2 + 55, - y: stage.height() / 2 + 55, - }); - assert.equal(!!shape, false, 'no shape here'); - shape = layer.getIntersection({ - x: stage.width() / 2, - y: stage.height() / 2, - }); - assert.equal(shape, circle); - }); - - it('double click after click should trigger event', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var bigRect = new Konva.Rect({ - x: 0, - y: 0, - width: 200, - height: 200, - fill: 'yellow', - }); - layer.add(bigRect); - - var smallShape = new Konva.Circle({ - x: 100, - y: 100, - width: 100, - fill: 'red', - }); - layer.add(smallShape); - layer.draw(); - - var bigClicks = 0; - var smallClicks = 0; - var smallDblClicks = 0; - - bigRect.on('click', function () { - bigClicks += 1; - }); - - smallShape.on('click', function () { - smallClicks += 1; - }); - - smallShape.on('dblclick', function () { - smallDblClicks += 1; - }); - - simulateMouseDown(stage, { - x: 10, - y: 10, - }); - simulateMouseUp(stage, { - x: 10, - y: 10, - }); - - assert.equal(bigClicks, 1, 'single click on big rect'); - assert.equal(smallClicks, 0, 'no click on small rect'); - assert.equal(smallDblClicks, 0, 'no dblclick on small rect'); - - simulateMouseDown(stage, { - x: 100, - y: 100, - }); - simulateMouseUp(stage, { - x: 100, - y: 100, - }); - - assert.equal(bigClicks, 1, 'single click on big rect'); - assert.equal(smallClicks, 1, 'single click on small rect'); - assert.equal(smallDblClicks, 0, 'no dblclick on small rect'); - - simulateMouseDown(stage, { - x: 100, - y: 100, - }); - simulateMouseUp(stage, { - x: 100, - y: 100, - }); - - assert.equal(bigClicks, 1, 'single click on big rect'); - assert.equal(smallClicks, 2, 'second click on small rect'); - assert.equal(smallDblClicks, 1, 'single dblclick on small rect'); - }); - - it('click on stage and second click on shape should not trigger double click (check after dblclick)', function (done) { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var bigRect = new Konva.Rect({ - x: 50, - y: 50, - width: 200, - height: 200, - fill: 'yellow', - }); - layer.add(bigRect); - - layer.draw(); - - var bigClicks = 0; - var bigDblClicks = 0; - - // make dblclick - simulateMouseDown(stage, { - x: 100, - y: 100, - }); - simulateMouseUp(stage, { - x: 100, - y: 100, - }); - simulateMouseDown(stage, { - x: 100, - y: 100, - }); - simulateMouseUp(stage, { - x: 100, - y: 100, - }); - - bigRect.on('click', function () { - bigClicks += 1; - }); - - bigRect.on('dblclick', function () { - bigDblClicks += 1; - }); - - simulateMouseDown(stage, { - x: 10, - y: 10, - }); - simulateMouseUp(stage, { - x: 10, - y: 10, - }); - - assert.equal(bigClicks, 0); - assert.equal(bigDblClicks, 0); - - simulateMouseDown(stage, { - x: 100, - y: 100, - }); - simulateMouseUp(stage, { - x: 100, - y: 100, - }); - - assert.equal(bigClicks, 1); - assert.equal(bigDblClicks, 0); - - done(); - }); - - it('click and dblclick with cancel bubble on container', function (done) { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var bigRect = new Konva.Rect({ - x: 50, - y: 50, - width: 200, - height: 200, - fill: 'yellow', - }); - layer.add(bigRect); - - layer.draw(); - - var clicks = 0; - var dblclicks = 0; - - layer.on('click', (e) => { - e.cancelBubble = true; - clicks += 1; - }); - - layer.on('dblclick', (e) => { - e.cancelBubble = true; - dblclicks += 1; - }); - - // make dblclick - simulateMouseDown(stage, { - x: 100, - y: 100, - }); - simulateMouseUp(stage, { - x: 100, - y: 100, - }); - simulateMouseDown(stage, { - x: 100, - y: 100, - }); - simulateMouseUp(stage, { - x: 100, - y: 100, - }); - - assert.equal(clicks, 2); - assert.equal(dblclicks, 1); - - done(); - }); - - it('double click after drag should trigger event', function (done) { - // skip this test for NodeJS because it fails sometimes - // TODO: WHY?!?!?! - if (!Konva.isBrowser) { - return done(); - } - - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var bigRect = new Konva.Rect({ - x: 0, - y: 0, - width: 200, - height: 200, - fill: 'yellow', - draggable: true, - }); - layer.add(bigRect); - - var smallShape = new Konva.Circle({ - x: 100, - y: 100, - width: 100, - fill: 'red', - }); - layer.add(smallShape); - layer.draw(); - - var bigClicks = 0; - var smallClicks = 0; - var smallDblClicks = 0; - - bigRect.on('click', function () { - bigClicks += 1; - }); - - smallShape.on('click', function () { - smallClicks += 1; - }); - - smallShape.on('dblclick', function () { - smallDblClicks += 1; - }); - - simulateMouseDown(stage, { - x: 10, - y: 10, - }); - simulateMouseMove(stage, { - x: 15, - y: 15, - }); - simulateMouseUp(stage, { - x: 15, - y: 15, - }); - - assert.equal(bigClicks, 0, 'single click on big rect (1)'); - assert.equal(smallClicks, 0, 'no click on small rect'); - assert.equal(smallDblClicks, 0, 'no dblclick on small rect'); - - setTimeout(function () { - simulateMouseDown(stage, { - x: 100, - y: 100, - }); - simulateMouseUp(stage, { - x: 100, - y: 100, - }); - - assert.equal(bigClicks, 0, 'single click on big rect (2)'); - assert.equal(smallClicks, 1, 'single click on small rect'); - assert.equal(smallDblClicks, 0, 'no dblclick on small rect'); - - setTimeout(function () { - simulateMouseDown(stage, { - x: 100, - y: 100, - }); - simulateMouseUp(stage, { - x: 100, - y: 100, - }); - - assert.equal(bigClicks, 0, 'single click on big rect (3)'); - assert.equal(smallClicks, 2, 'second click on small rect'); - assert.equal(smallDblClicks, 1, 'single dblclick on small rect'); - - done(); - }, 5); - }); - }); - - it('test mouseenter on empty stage', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var mouseenterCount = 0; - stage.on('mouseenter', function () { - mouseenterCount += 1; - }); - - var top = (stage.content && stage.content.getBoundingClientRect().top) || 0; - var evt = { - clientX: 10, - clientY: 10 + top, - button: 0, - type: 'mouseenter', - }; - - stage._pointerenter(evt); - - assert.equal(mouseenterCount, 1, 'mouseenterCount should be 1'); - }); - - it('test mouseleave on empty stage', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var mouseleave = 0; - stage.on('mouseleave', function () { - mouseleave += 1; - }); - - var top = (stage.content && stage.content.getBoundingClientRect().top) || 0; - var evt = { - clientX: 0, - clientY: 0 + top, - button: 0, - type: 'mouseleave', - }; - - stage._pointerleave(evt); - - assert.equal(mouseleave, 1, 'mouseleave should be 1'); - }); - - it('test mouseleave from the shape', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var circle = new Konva.Circle({ - fill: 'red', - radius: 100, - x: 200, - y: 0, - }); - layer.add(circle); - layer.draw(); - - var mouseleave = 0; - stage.on('mouseleave', function () { - mouseleave += 1; - }); - - var mouseout = 0; - stage.on('mouseout', function () { - mouseout += 1; - }); - - var circleMouseleave = 0; - circle.on('mouseleave', function () { - circleMouseleave += 1; - }); - - var circleMouseout = 0; - circle.on('mouseout', function () { - circleMouseout += 1; - }); - - var layerMouseleave = 0; - layer.on('mouseleave', function () { - layerMouseleave += 1; - }); - - var layerMouseout = 0; - layer.on('mouseout', function () { - layerMouseout += 1; - }); - - // move into a circle - simulateMouseMove(stage, { x: 200, y: 5 }); - - var top = (stage.content && stage.content.getBoundingClientRect().top) || 0; - var evt = { - clientX: 200, - clientY: -5 + top, - button: 0, - type: 'mouseleave', - }; - - stage._pointerleave(evt); - - assert.equal(circleMouseleave, 1, 'circleMouseleave should be 1'); - assert.equal(circleMouseout, 1, 'circleMouseout should be 1'); - assert.equal(layerMouseleave, 1, 'layerMouseleave should be 1'); - assert.equal(layerMouseout, 1, 'layerMouseout should be 1'); - assert.equal(mouseleave, 1, 'mouseleave should be 1'); - assert.equal(mouseout, 1, 'mouseout should be 1'); - }); - - it('should not trigger mouseenter on stage when we go to the shape from empty space', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - width: 50, - height: 50, - fill: 'red', - }); - layer.add(rect); - - layer.draw(); - - var mouseenter = 0; - stage.on('mouseenter', function () { - debugger; - mouseenter += 1; - }); - - simulateMouseMove(stage, { - x: 100, - y: 100, - }); - - simulateMouseMove(stage, { - x: 25, - y: 25, - }); - - assert.equal(mouseenter, 0, 'mouseenter should be 1'); - }); - - it('should not trigger mouseleave after shape destroy', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - width: 50, - height: 50, - fill: 'red', - }); - layer.add(rect); - - layer.draw(); - - var mouseout = 0; - var mouseleave = 0; - stage.on('mouseout', function () { - mouseout += 1; - }); - - rect.on('mouseout', function () { - mouseout += 1; - }); - - stage.on('mouseleave', function () { - mouseleave += 1; - }); - - rect.on('mouseleave', function () { - mouseleave += 1; - }); - - simulateMouseMove(stage, { - x: 10, - y: 10, - }); - - rect.destroy(); - layer.draw(); - simulateMouseMove(stage, { - x: 20, - y: 20, - }); - assert.equal(mouseout, 0); - assert.equal(mouseleave, 0); - }); - - it('should not trigger mouseenter on stage twice when we go to the shape directly', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - width: 50, - height: 50, - fill: 'red', - }); - layer.add(rect); - - layer.draw(); - - var mouseenter = 0; - stage.on('mouseenter', function () { - mouseenter += 1; - }); - - var top = (stage.content && stage.content.getBoundingClientRect().top) || 0; - var evt = { - clientX: 10, - clientY: 10 + top, - button: 0, - type: 'mouseenter', - }; - - stage._pointerenter(evt); - - simulateMouseMove(stage, { - x: 10, - y: 10, - }); - - stage._pointerleave({ - ...evt, - type: 'mouseleave', - }); - - assert.equal(mouseenter, 1, 'mouseenter should be 1'); - }); - - it('should trigger mouse events if we set Konva.hitOnDragEnabled = true', function () { - Konva.hitOnDragEnabled = true; - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - width: 50, - height: 50, - fill: 'red', - draggable: true, - }); - layer.add(rect); - - layer.draw(); - - var mousemove = 0; - rect.on('mousemove', function () { - mousemove += 1; - }); - - simulateMouseDown(stage, { - x: 10, - y: 10, - }); - - simulateMouseMove(stage, { - x: 20, - y: 20, - }); - simulateMouseMove(stage, { - x: 30, - y: 30, - }); - simulateMouseUp(stage, { - x: 30, - y: 30, - }); - - assert.equal(mousemove, 2, 'mousemove should be 2'); - Konva.hitOnDragEnabled = false; - }); - - it('test scaled with CSS stage', function () { - if (isNode) { - return; - } - var stage = addStage(); - - stage.container().style.transform = 'scale(0.5)'; - stage.container().style.transformOrigin = 'left top'; - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - width: 50, - height: 50, - fill: 'red', - draggable: true, - }); - layer.add(rect); - - layer.draw(); - - var clicks = 0; - rect.on('click', function () { - clicks += 1; - }); - - simulateMouseDown(stage, { - x: 40, - y: 40, - }); - - simulateMouseUp(stage, { - x: 40, - y: 40, - }); - - // should not register this click this click, because the stage is scaled - assert.equal(clicks, 0, 'clicks not triggered'); - assert.deepEqual(stage.getPointerPosition(), { x: 80, y: 80 }); - - // try touch too - simulateTouchStart(stage, { - x: 30, - y: 30, - }); - simulateTouchEnd(stage, { - x: 30, - y: 30, - }); - assert.deepEqual(stage.getPointerPosition(), { x: 60, y: 60 }); - }); - - it('mousedown on empty then mouseup on shape', function () { - if (isNode) { - return; - } - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - stage.on('mousedown mousemove mouseup click', function (e) { - console.log('state', e.type); - }); - - var rect = new Konva.Rect({ - width: 50, - height: 50, - fill: 'red', - draggable: true, - }); - layer.add(rect); - - layer.draw(); - - var clicks = 0; - rect.on('click', function () { - console.log('click'); - clicks += 1; - if (clicks === 2) { - debugger; - } - }); - - simulateMouseDown(stage, { - x: 40, - y: 40, - }); - - simulateMouseUp(stage, { - x: 40, - y: 40, - }); - - // trigger click - assert.equal(clicks, 1, 'clicks not triggered'); - - // mousedown outside - simulateMouseDown(stage, { - x: 60, - y: 6, - }); - // move into rect - simulateMouseMove(stage, { - x: 40, - y: 40, - }); - // mouseup inside rect - simulateMouseUp(stage, { - x: 40, - y: 40, - }); - // it shouldn't trigger the click event!!! - assert.equal(clicks, 1, 'clicks not triggered'); - }); -}); diff --git a/test/unit/Node-cache-test.ts b/test/unit/Node-cache-test.ts deleted file mode 100644 index 687a1c5ad..000000000 --- a/test/unit/Node-cache-test.ts +++ /dev/null @@ -1,1551 +0,0 @@ -import { assert } from 'chai'; -import { - addStage, - Konva, - compareLayerAndCanvas, - cloneAndCompareLayer, - compareCanvases, - createCanvas, - loadImage, - getPixelRatio, -} from './test-utils'; - -describe('Caching', function () { - it('cache simple rectangle', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - - var rect = new Konva.Rect({ - x: 100, - y: 50, - width: 100, - height: 50, - fill: 'green', - draggable: true, - }); - rect.cache(); - - layer.add(rect); - stage.add(layer); - - var canvas = createCanvas(); - var context = canvas.getContext('2d'); - context.beginPath(); - context.rect(100, 50, 100, 50); - context.closePath(); - context.fillStyle = 'green'; - context.fill(); - - compareLayerAndCanvas(layer, canvas, 10); - cloneAndCompareLayer(layer); - }); - - it('cache simple rectangle with transform', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - - var rect = new Konva.Rect({ - x: 100, - y: 50, - width: 100, - height: 50, - rotation: 45, - scaleY: 2, - fill: 'green', - }); - rect.cache(); - - layer.add(rect); - stage.add(layer); - - var canvas = createCanvas(); - var context = canvas.getContext('2d'); - context.translate(100, 50); - context.rotate(Math.PI / 4); - context.beginPath(); - context.rect(0, 0, 100, 100); - context.closePath(); - context.fillStyle = 'green'; - context.fill(); - - compareLayerAndCanvas(layer, canvas, 200, 100); - cloneAndCompareLayer(layer, 150, 100); - }); - - it('cache rectangle with fill and stroke', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - - var rect = new Konva.Rect({ - x: 100, - y: 50, - width: 100, - height: 50, - fill: 'green', - stroke: 'black', - strokeWidth: 20, - }); - rect.cache(); - - layer.add(rect); - stage.add(layer); - - var canvas = createCanvas(); - var context = canvas.getContext('2d'); - context.beginPath(); - context.rect(100, 50, 100, 50); - context.closePath(); - context.fillStyle = 'green'; - context.fill(); - context.lineWidth = 20; - context.stroke(); - compareLayerAndCanvas(layer, canvas, 50); - cloneAndCompareLayer(layer, 50); - }); - - it('cache rectangle with fill and opacity', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - - var rect = new Konva.Rect({ - x: 100, - y: 50, - width: 100, - height: 50, - fill: 'green', - opacity: 0.5, - }); - rect.cache(); - rect.opacity(0.3); - - layer.add(rect); - stage.add(layer); - - var canvas = createCanvas(); - var context = canvas.getContext('2d'); - context.globalAlpha = 0.3; - context.beginPath(); - context.rect(100, 50, 100, 50); - context.closePath(); - context.fillStyle = 'green'; - context.fill(); - compareLayerAndCanvas(layer, canvas, 5); - }); - - it('cache rectangle with fill, stroke opacity', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - - var rect = new Konva.Rect({ - x: 100, - y: 50, - width: 100, - height: 50, - fill: 'green', - opacity: 0.5, - stroke: 'black', - strokeWidth: 10, - }); - rect.cache(); - rect.opacity(0.3); - - layer.add(rect); - stage.add(layer); - - cloneAndCompareLayer(layer, 100); - }); - - // skip, because opacity rendering of cached shape is different - // nothing we can do here - it('cache rectangle with fill, shadow and opacity', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - - var rect = new Konva.Rect({ - x: 10, - y: 10, - width: 100, - height: 50, - fill: 'green', - opacity: 0.5, - shadowBlur: 10, - shadowColor: 'black', - draggable: true, - }); - // rect.cache(); - // rect.opacity(0.3); - - layer.add(rect.clone({ y: 50, x: 50, shadowEnabled: false })); - layer.add(rect); - stage.add(layer); - - cloneAndCompareLayer(layer, 10); - }); - - it('cache rectangle with fill and simple shadow', function () { - Konva.pixelRatio = 1; - var stage = addStage(); - - var layer = new Konva.Layer(); - - var rect = new Konva.Rect({ - x: 100, - y: 50, - width: 100, - height: 50, - fill: 'green', - shadowColor: 'black', - shadowBlur: 10, - draggable: true, - }); - rect.cache(); - - layer.add(rect); - stage.add(layer); - - var canvas = createCanvas(); - var context = canvas.getContext('2d'); - context.beginPath(); - context.rect(100, 50, 100, 50); - context.closePath(); - context.fillStyle = 'green'; - context.shadowColor = 'black'; - context.shadowBlur = 10; - context.fill(); - - compareLayerAndCanvas(layer, canvas, 100); - Konva.pixelRatio = getPixelRatio(); - }); - - it('cache rectangle with fill and shadow with offset', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - - var rect = new Konva.Rect({ - x: 100, - y: 50, - width: 50, - height: 25, - fill: 'green', - shadowOffsetX: 10, - shadowOffsetY: 10, - shadowColor: 'black', - shadowBlur: 10, - }); - rect.cache(); - - layer.add(rect); - stage.add(layer); - - var canvas = createCanvas(); - var context = canvas.getContext('2d'); - - context.translate(100, 50); - context.beginPath(); - context.rect(0, 0, 50, 25); - context.closePath(); - context.fillStyle = 'green'; - context.shadowColor = 'black'; - context.shadowBlur = 10 * Konva.pixelRatio; - context.shadowOffsetX = 10 * Konva.pixelRatio; - context.shadowOffsetY = 10 * Konva.pixelRatio; - context.fill(); - compareLayerAndCanvas(layer, canvas, 50); - }); - - it('cache rectangle with fill and shadow with negative offset', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - - var rect = new Konva.Rect({ - x: 100, - y: 50, - width: 50, - height: 25, - fill: 'green', - shadowOffsetX: -10, - shadowOffsetY: -10, - shadowColor: 'black', - shadowBlur: 10, - }); - rect.cache(); - - layer.add(rect); - stage.add(layer); - - var canvas = createCanvas(); - var context = canvas.getContext('2d'); - - context.translate(100, 50); - context.beginPath(); - context.rect(0, 0, 50, 25); - context.closePath(); - context.fillStyle = 'green'; - context.shadowColor = 'black'; - context.shadowBlur = 10 * Konva.pixelRatio; - context.shadowOffsetX = -10 * Konva.pixelRatio; - context.shadowOffsetY = -10 * Konva.pixelRatio; - context.fill(); - compareLayerAndCanvas(layer, canvas, 50); - }); - - it('cache rectangle with fill and shadow with negative offset and scale', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - - var rect = new Konva.Rect({ - x: 100, - y: 50, - width: 50, - height: 25, - fill: 'green', - shadowOffsetX: -10, - shadowOffsetY: -10, - shadowColor: 'black', - shadowBlur: 10, - scaleX: 2, - scaleY: 2, - }); - rect.cache(); - - layer.add(rect); - stage.add(layer); - - cloneAndCompareLayer(layer, 200); - }); - - it('cache rectangle with fill and shadow and some transform', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - - var rect = new Konva.Rect({ - x: 100, - y: 50, - width: 50, - height: 25, - fill: 'green', - shadowOffsetX: -10, - shadowOffsetY: -10, - shadowColor: 'black', - shadowBlur: 10, - offsetX: 50, - offsetY: 25, - }); - rect.cache(); - - layer.add(rect); - stage.add(layer); - - var canvas = createCanvas(); - var context = canvas.getContext('2d'); - - context.translate(50, 25); - context.beginPath(); - context.rect(0, 0, 50, 25); - context.closePath(); - context.fillStyle = 'green'; - context.shadowColor = 'black'; - context.shadowBlur = 10 * Konva.pixelRatio; - context.shadowOffsetX = -10 * Konva.pixelRatio; - context.shadowOffsetY = -10 * Konva.pixelRatio; - context.fill(); - compareLayerAndCanvas(layer, canvas, 50); - }); - - // CACHING CONTAINERS - it('cache group with simple rectangle', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - - var group = new Konva.Group({ - x: 100, - y: 50, - }); - - var rect = new Konva.Rect({ - width: 100, - height: 50, - fill: 'green', - }); - group.add(rect); - group.cache(); - - layer.add(group); - stage.add(layer); - - var canvas = createCanvas(); - var context = canvas.getContext('2d'); - context.beginPath(); - context.rect(100, 50, 100, 50); - context.closePath(); - context.fillStyle = 'green'; - context.fill(); - compareLayerAndCanvas(layer, canvas, 10); - cloneAndCompareLayer(layer); - }); - - it('cache group with simple rectangle with transform', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - - var group = new Konva.Group({ - x: 50, - y: 25, - }); - - var rect = new Konva.Rect({ - x: 50, - y: 25, - width: 100, - height: 50, - fill: 'green', - rotation: 45, - }); - group.add(rect); - group.cache(); - - layer.add(group); - stage.add(layer); - cloneAndCompareLayer(layer, 200); - }); - - it('cache group with several shape with transform', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - - var group = new Konva.Group({ - x: 50, - y: 25, - }); - - var rect = new Konva.Rect({ - x: 50, - y: 25, - width: 100, - height: 50, - fill: 'green', - shadowOffsetX: 10, - shadowOffsetY: 10, - shadowBlur: 10, - }); - group.add(rect); - - var circle = new Konva.Circle({ - x: 250, - y: 50, - radius: 25, - fill: 'red', - // rotation on circle should not have any effects - stroke: 'black', - rotation: 45, - scaleX: 2, - scaleY: 2, - }); - group.add(circle); - - group.cache(); - - layer.add(group); - stage.add(layer); - - var canvas = createCanvas(); - var context = canvas.getContext('2d'); - - // draw rect - context.save(); - context.beginPath(); - context.rect(100, 50, 100, 50); - context.closePath(); - context.fillStyle = 'green'; - context.shadowColor = 'black'; - context.shadowBlur = 10 * Konva.pixelRatio; - context.shadowOffsetX = 10 * Konva.pixelRatio; - context.shadowOffsetY = 10 * Konva.pixelRatio; - context.fill(); - context.restore(); - - // circle - context.save(); - context.beginPath(); - context.arc(300, 75, 50, 0, Math.PI * 2); - context.closePath(); - context.fillStyle = 'red'; - context.lineWidth = 4; - context.fill(); - context.stroke(); - context.restore(); - - compareLayerAndCanvas(layer, canvas, 210, 20); - - // recache - group.cache(); - layer.draw(); - compareLayerAndCanvas(layer, canvas, 210, 20); - }); - - it('cache group with rectangle and text', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - - var button = new Konva.Group({ - width: 100, - height: 50, - draggable: true, - }); - - var face = new Konva.Rect({ - fill: 'red', - x: 0, - y: 0, - width: 100, - height: 50, - }); - - var text = new Konva.Text({ - text: 'Wrong button', - x: 15, - y: 20, - }); - - button.add(face); - button.add(text); - - button.cache(); - - layer.add(button); - stage.add(layer); - - cloneAndCompareLayer(layer, 100); - }); - - it('cache layer with several shape with transform', function () { - var stage = addStage(); - - var layer = new Konva.Layer({ - draggable: true, - }); - - var group = new Konva.Group({ - x: 50, - y: 25, - }); - - var rect = new Konva.Rect({ - x: 50, - y: 25, - width: 100, - height: 50, - fill: 'green', - shadowOffsetX: 10, - shadowOffsetY: 10, - shadowBlur: 10, - }); - group.add(rect); - - var circle = new Konva.Circle({ - x: 250, - y: 50, - radius: 25, - fill: 'red', - // rotation on circle should not have any effects - rotation: 45, - stroke: 'black', - scaleX: 2, - scaleY: 2, - }); - group.add(circle); - - group.cache(); - - layer.add(group); - stage.add(layer); - - var canvas = createCanvas(); - var context = canvas.getContext('2d'); - - // draw rect - context.save(); - context.beginPath(); - context.rect(100, 50, 100, 50); - context.closePath(); - context.fillStyle = 'green'; - context.shadowColor = 'black'; - context.shadowBlur = 10 * Konva.pixelRatio; - context.shadowOffsetX = 10 * Konva.pixelRatio; - context.shadowOffsetY = 10 * Konva.pixelRatio; - context.fill(); - context.restore(); - - // circle - context.save(); - context.beginPath(); - context.arc(300, 75, 50, 0, Math.PI * 2); - context.closePath(); - context.fillStyle = 'red'; - context.lineWidth = 4; - context.fill(); - context.stroke(); - context.restore(); - - compareLayerAndCanvas(layer, canvas, 150); - - // recache - group.cache(); - layer.draw(); - compareLayerAndCanvas(layer, canvas, 150); - }); - - it('cache shape that is larger than stage', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var group = new Konva.Group(); - var circle = new Konva.Circle({ - x: 74, - y: 74, - radius: 300, - fill: 'red', - stroke: 'black', - strokeWidth: 4, - scaleX: 1 / 2, - scaleY: 1 / 2, - }); - - group.add(circle); - layer.add(group); - stage.add(layer); - - assert.equal(circle._getCanvasCache(), undefined); - - var canvas = createCanvas(); - var context = canvas.getContext('2d'); - // circle - context.save(); - context.beginPath(); - context.arc(74, 74, 150, 0, Math.PI * 2); - context.closePath(); - context.fillStyle = 'red'; - context.lineWidth = 2; - context.fill(); - context.stroke(); - context.restore(); - - compareLayerAndCanvas(layer, canvas, 150); - }); - - it('cache shape that is larger than stage but need buffer canvas', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var group = new Konva.Group(); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 400, - fill: 'red', - stroke: 'black', - strokeWidth: 50, - opacity: 0.5, - scaleX: 1 / 5, - scaleY: 1 / 5, - }); - - group.add(circle); - layer.add(group); - stage.add(layer); - circle.cache(); - layer.draw(); - - cloneAndCompareLayer(layer, 200); - }); - - it('cache nested groups', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var groupOuter = new Konva.Group({ - x: 50, - y: 10, - }); - - var groupInner = new Konva.Group({ - x: 10, - y: 10, - draggable: true, - }); - var rect = new Konva.Rect({ - width: 50, - height: 50, - stroke: 'grey', - strokeWidth: 3, - fill: 'yellow', - }); - - var text = new Konva.Text({ - x: 18, - y: 15, - text: 'A', - fill: 'black', - fontSize: 24, - }); - - groupInner.add(rect); - groupInner.add(text); - - groupOuter.add(groupInner); - - layer.add(groupOuter); - stage.add(layer); - - groupInner.cache(); - - layer.draw(); - cloneAndCompareLayer(layer, 150); - - groupInner.clearCache(); - groupOuter.cache(); - layer.draw(); - cloneAndCompareLayer(layer, 150); - - groupOuter.clearCache(); - groupInner.clearCache(); - rect.cache(); - layer.draw(); - cloneAndCompareLayer(layer, 150); - }); - - it('test group with circle + buffer canvas usage', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var group = new Konva.Group({ - x: 100, - y: 100, - draggable: true, - }); - layer.add(group); - - var circle = new Konva.Circle({ - radius: 10, - // fill: 'white', - fillRadialGradientStartRadius: 0, - fillRadialGradientEndRadius: 10, - fillRadialGradientColorStops: [0, 'red', 0.5, 'yellow', 1, 'black'], - opacity: 0.4, - hitStrokeWidth: 0, - stroke: 'rgba(0,0,0,0)', - }); - group.add(circle); - group.cache(); - stage.draw(); - - cloneAndCompareLayer(layer, 200); - }); - - it('test group with opacity', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var group = new Konva.Group({ - x: 100, - y: 100, - draggable: true, - }); - layer.add(group); - - var circle = new Konva.Circle({ - radius: 10, - fillRadialGradientStartRadius: 0, - fillRadialGradientEndRadius: 10, - fillRadialGradientColorStops: [0, 'red', 0.5, 'yellow', 1, 'black'], - opacity: 0.4, - hitStrokeWidth: 0, - stroke: 'rgba(0,0,0,0)', - }); - group.add(circle); - group.cache(); - stage.draw(); - - cloneAndCompareLayer(layer, 210); - }); - - it('test group with opacity', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var group = new Konva.Group({ - x: 100, - y: 100, - draggable: true, - }); - layer.add(group); - - var circle = new Konva.Circle({ - radius: 10, - fillRadialGradientStartRadius: 0, - fillRadialGradientEndRadius: 10, - fillRadialGradientColorStops: [0, 'red', 0.5, 'yellow', 1, 'black'], - opacity: 0.4, - hitStrokeWidth: 0, - stroke: 'rgba(0,0,0,0)', - }); - group.add(circle); - group.cache(); - stage.draw(); - - cloneAndCompareLayer(layer, 100); - }); - - it('test rect with float dimensions', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var group = new Konva.Group({ - x: 10, - y: 10, - draggable: true, - }); - layer.add(group); - - var circle = new Konva.Circle({ - radius: 52.2, - fill: 'red', - }); - group.add(circle); - group.cache(); - - const canvas = group._cache.get('canvas').scene; - console.log(canvas.width / 2); - assert.equal(canvas.width, 106 * canvas.pixelRatio); - }); - - it('cache group with rectangle with fill and opacity', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - - var group = new Konva.Group({ - opacity: 0.5, - }); - - var rect = new Konva.Rect({ - x: 100, - y: 50, - width: 100, - height: 50, - fill: 'green', - }); - - group.add(rect); - layer.add(group); - stage.add(layer); - - group.cache(); - layer.draw(); - - var canvas = createCanvas(); - var context = canvas.getContext('2d'); - context.globalAlpha = 0.5; - context.beginPath(); - context.rect(100, 50, 100, 50); - context.closePath(); - context.fillStyle = 'green'; - context.fill(); - compareLayerAndCanvas(layer, canvas, 5); - }); - - it('even if parent is not visible cache should be created', function () { - var stage = addStage(); - - var layer = new Konva.Layer({ - visible: false, - }); - - var rect = new Konva.Rect({ - x: 100, - y: 50, - width: 100, - height: 100, - fill: 'green', - }); - - layer.add(rect); - stage.add(layer); - - rect.cache(); - layer.visible(true); - layer.draw(); - - var canvas = createCanvas(); - var context = canvas.getContext('2d'); - context.beginPath(); - context.rect(100, 50, 100, 100); - context.closePath(); - context.fillStyle = 'green'; - context.fill(); - compareLayerAndCanvas(layer, canvas, 5); - assert.equal(stage.getIntersection({ x: 150, y: 100 }), rect); - }); - - it('check cache for invisible shape', function () { - var stage = addStage(); - - var layer = new Konva.Layer({ - // visible: false, - }); - - var group = new Konva.Group(); - layer.add(group); - - group.add( - new Konva.Rect({ - x: 50, - y: 50, - width: 100, - height: 100, - fill: 'red', - }) - ); - var rect = new Konva.Rect({ - x: 0, - y: 0, - width: 100, - height: 100, - fill: 'green', - visible: false, - }); - group.add(rect); - - stage.add(layer); - - group.cache(); - layer.draw(); - cloneAndCompareLayer(layer); - }); - - it('even if parent is not listening cache and hit should be created', function () { - var stage = addStage(); - - var layer = new Konva.Layer({ - listening: false, - }); - - var rect = new Konva.Rect({ - x: 100, - y: 50, - width: 100, - height: 100, - fill: 'green', - }); - - layer.add(rect); - stage.add(layer); - - rect.cache(); - layer.listening(true); - layer.draw(); - - var canvas = createCanvas(); - var context = canvas.getContext('2d'); - context.beginPath(); - context.rect(100, 50, 100, 100); - context.closePath(); - context.fillStyle = 'green'; - context.fill(); - compareLayerAndCanvas(layer, canvas, 5); - assert.equal(stage.getIntersection({ x: 150, y: 100 }), rect); - }); - - // hard to fix - it.skip('even if parent is not visible cache should be created - test for group', function () { - var stage = addStage(); - - var layer = new Konva.Layer({ - visible: false, - }); - - var group = new Konva.Group({ - opacity: 0.5, - }); - - var rect = new Konva.Rect({ - x: 100, - y: 50, - width: 100, - height: 100, - fill: 'green', - }); - - group.add(rect); - layer.add(group); - stage.add(layer); - - group.cache(); - layer.visible(true); - layer.draw(); - - var canvas = createCanvas(); - var context = canvas.getContext('2d'); - context.globalAlpha = 0.5; - context.beginPath(); - context.rect(100, 50, 100, 100); - context.closePath(); - context.fillStyle = 'green'; - context.fill(); - compareLayerAndCanvas(layer, canvas, 5); - assert.equal(stage.getIntersection({ x: 150, y: 100 }), rect); - }); - - it('listening false on a shape should not create hit area', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - stage.add(layer); - - var bigCircle = new Konva.Circle({ - x: 100, - y: 100, - radius: 100, - fill: 'green', - }); - - layer.add(bigCircle); - - var smallCircle = new Konva.Circle({ - x: 100, - y: 100, - radius: 50, - fill: 'red', - listening: false, - }); - - layer.add(smallCircle); - smallCircle.cache(); - layer.draw(); - - var shape = stage.getIntersection({ x: 100, y: 100 }); - assert.equal(shape, bigCircle); - }); - - it('listening false on a shape inside group should not create hit area', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - stage.add(layer); - - var group = new Konva.Group(); - layer.add(group); - - var bigCircle = new Konva.Circle({ - x: 100, - y: 100, - radius: 100, - fill: 'green', - }); - - group.add(bigCircle); - - var smallCircle = new Konva.Circle({ - x: 100, - y: 100, - radius: 50, - fill: 'red', - listening: false, - }); - - group.add(smallCircle); - group.cache(); - - layer.draw(); - var shape = stage.getIntersection({ x: 100, y: 100 }); - assert.equal(shape, bigCircle); - }); - it('listening false on a group inside a group should not create hit area', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - stage.add(layer); - - var group = new Konva.Group(); - layer.add(group); - - var bigCircle = new Konva.Circle({ - x: 100, - y: 100, - radius: 100, - fill: 'green', - }); - group.add(bigCircle); - - var innerGroup = new Konva.Group({ - listening: false, - }); - group.add(innerGroup); - - var smallCircle = new Konva.Circle({ - x: 100, - y: 100, - radius: 50, - fill: 'red', - }); - - innerGroup.add(smallCircle); - group.cache(); - - layer.draw(); - var shape = stage.getIntersection({ x: 100, y: 100 }); - assert.equal(shape, bigCircle); - }); - - // for performance reasons - it('do no call client rect calculation, if we do not need it', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - stage.add(layer); - - var group = new Konva.Group(); - layer.add(group); - - var bigCircle = new Konva.Circle({ - x: 100, - y: 100, - radius: 100, - fill: 'green', - }); - group.add(bigCircle); - - layer.draw(); - - var called = false; - group.getClientRect = function () { - called = true; - } as any; - group.cache({ - x: 0, - y: 0, - width: 100, - height: 100, - }); - - assert.equal(called, false); - }); - - // for performance reasons - it('caching should skip clearing internal caching for perf boos', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - stage.add(layer); - - var bigCircle = new Konva.Circle({ - x: 100, - y: 100, - radius: 100, - fill: 'green', - }); - layer.add(bigCircle); - - layer.cache(); - - var callCount = 0; - bigCircle._clearSelfAndDescendantCache = function () { - callCount += 1; - }; - - layer.x(10); - assert.equal(callCount, 0); - layer.clearCache(); - // make sure all cleared for children - assert.equal(callCount, 1); - }); - - it('caching group with clip', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - stage.add(layer); - - var width = 100; - var height = 100; - var verts = [ - { x: width * 0.2, y: 0 }, - { x: width, y: 0 }, - { x: width * 0.8, y: height }, - { x: 0, y: height }, - ]; - - var clipFunc = (ctx) => { - for (let i = 0; i < verts.length; i++) { - const vertex = verts[i]; - if (i === 0) { - ctx.moveTo(vertex.x, vertex.y); - } else { - ctx.lineTo(vertex.x, vertex.y); - } - } - ctx.closePath(); - }; - - var group1 = new Konva.Group({ - clipFunc: clipFunc, - x: 50, - y: 50, - listening: false, - }); - layer.add(group1); - var rect1 = new Konva.Rect({ - fill: 'green', - width: 100, - height: 100, - }); - group1.add(rect1); - - layer.draw(); - group1.cache(); - - layer.draw(); - - // var group2 = group1.clone({ - // x: height - 20, - // y: 50, - // }); - // group2.findOne('Rect').fill('red'); - // layer.add(group2); - // group2.cache(); - - // var group3 = group1.clone({ - // x: width + 20, - // }); - // layer.add(group3); - // group3.findOne('Rect').x(150); - // group2.cache(); - - layer.draw(); - - cloneAndCompareLayer(layer, 10); - }); - - it('check caching with global composite operation', function () { - var stage = addStage(); - - const layer = new Konva.Layer(); - stage.add(layer); - - function getColor(pos) { - var ratio = layer.canvas.pixelRatio; - var p = layer.canvas.context.getImageData( - Math.round(pos.x * ratio), - Math.round(pos.y * ratio), - 1, - 1 - ).data; - return Konva.Util._rgbToHex(p[0], p[1], p[2]); - } - - const bg = new Konva.Rect({ - x: 0, - y: 0, - width: stage.width(), - height: stage.height(), - fill: 'lightgray', - }); - layer.add(bg); - - const group = new Konva.Group(); - layer.add(group); - - const rect = new Konva.Rect({ - x: 10, - y: 0, - width: 200, - height: 100, - fill: 'blue', - draggable: true, - }); - group.add(rect); - - const maskgroup = new Konva.Group({}); - group.add(maskgroup); - - const mask = new Konva.Rect({ - x: 50, - y: 0, - width: 100, - height: 100, - fill: 'black', - }); - maskgroup.add(mask); - - maskgroup.cache(); - var canvasBefore = maskgroup._cache.get('canvas').scene._canvas; - - maskgroup.globalCompositeOperation('destination-in'); - maskgroup.cache(); - var canvasAfter = maskgroup._cache.get('canvas').scene._canvas; - - compareCanvases(canvasBefore, canvasAfter); - - maskgroup.clearCache(); - - layer.draw(); - // no caches - mask group clipped all drawing - assert.equal(getColor({ x: 5, y: 20 }), '000000'); - assert.equal(getColor({ x: 55, y: 20 }), '0000ff'); - - // cache inner mask group - same result - maskgroup.cache(); - layer.draw(); - - assert.equal(getColor({ x: 5, y: 20 }), '000000'); - assert.equal(getColor({ x: 55, y: 20 }), '0000ff'); - - // cache group - // background will be visible now, because globalCompositeOperation - // will work inside cached parent only - group.cache(); - layer.draw(); - - assert.equal(getColor({ x: 5, y: 20 }), 'd3d3d3'); - assert.equal(getColor({ x: 55, y: 20 }), '0000ff'); - }); - - it('recache should update internal caching', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - stage.add(layer); - - var bigCircle = new Konva.Circle({ - x: 100, - y: 100, - radius: 100, - fill: 'red', - draggable: true, - }); - layer.add(bigCircle); - - bigCircle.cache(); - - layer.draw(); - - var d = layer.getContext().getImageData(100, 100, 1, 1).data; - assert.equal(d[0], 255, 'see red'); - - bigCircle.fill('blue'); - bigCircle.cache(); - - layer.draw(); - d = layer.getContext().getImageData(100, 100, 1, 1).data; - assert.equal(d[0], 0, 'no red'); - assert.equal(d[2], 255, 'see blue'); - }); - - it('recache with filters', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - stage.add(layer); - - var bigCircle = new Konva.Circle({ - x: 100, - y: 100, - radius: 100, - fill: 'red', - draggable: true, - }); - layer.add(bigCircle); - - bigCircle.filters([Konva.Filters.Blur]); - bigCircle.blurRadius(10); - bigCircle.cache(); - - layer.draw(); - bigCircle.cache(); - - layer.draw(); - - var d = layer.getContext().getImageData(100, 100, 1, 1).data; - assert.equal(d[0], 255, 'see red'); - }); - - it('check image smooth', function () { - var stage = addStage(); - - var layer = new Konva.Layer({ - imageSmoothingEnabled: false, - }); - stage.add(layer); - - var bigCircle = new Konva.Circle({ - x: 100, - y: 100, - radius: 10, - fill: 'red', - draggable: true, - scaleX: 10, - scaleY: 10, - }); - layer.add(bigCircle); - - bigCircle.cache({ - imageSmoothingEnabled: false, - }); - - layer.draw(); - assert.equal( - bigCircle._cache.get('canvas').scene.getContext()._context - .imageSmoothingEnabled, - false - ); - }); - - it('getAbsolutePosition for cached container', function () { - var stage = addStage(); - - var layer = new Konva.Layer({}); - stage.add(layer); - - var circle = new Konva.Circle({ - x: 100, - y: 100, - radius: 10, - fill: 'red', - draggable: true, - scaleX: 10, - scaleY: 10, - }); - layer.add(circle); - // initial calculations - circle.getAbsolutePosition(); - // - - layer.cache(); - layer.draw(); - layer.position({ - x: 10, - y: 10, - }); - assert.equal(circle.getAbsolutePosition().x, 110); - assert.equal(circle.getAbsolutePosition().y, 110); - }); - - it('cached node should not have filter canvas until we have a filter', function () { - var stage = addStage(); - - var layer = new Konva.Layer({}); - stage.add(layer); - - var circle = new Konva.Circle({ - x: 100, - y: 100, - radius: 10, - fill: 'red', - draggable: true, - scaleX: 10, - scaleY: 10, - }); - layer.add(circle); - circle.cache(); - - assert.equal(circle._cache.get('canvas').filter.width, 0); - circle.filters([Konva.Filters.Blur]); - layer.draw(); - assert.equal( - circle._cache.get('canvas').filter.width, - 20 * circle._cache.get('canvas').filter.pixelRatio - ); - circle.filters([]); - // TODO: should we clear cache canvas? - // assert.equal(circle._cache.get('canvas').filter.width, 0); - }); - - it('hit from cache + global composite', function (done) { - // blend mode should NOT effect hit detection. - var stage = addStage(); - - var layer = new Konva.Layer({}); - stage.add(layer); - - loadImage('lion.png', (img) => { - const lion = new Konva.Image({ image: img }); - lion.name('lion'); - lion.cache(); - lion.drawHitFromCache(); - layer.add(lion); - - const lion2 = new Konva.Image({ image: img }); - - lion2.position({ - x: 50, - y: 50, - }); - lion2.name('lion2'); - lion2.globalCompositeOperation('overlay'); - lion2.cache(); - lion2.drawHitFromCache(); - layer.add(lion2); - layer.draw(); - // layer.toggleHitCanvas(); - - var shape = layer.getIntersection({ x: 106, y: 78 }); - assert.equal(shape, lion2); - done(); - }); - }); - - it('hit from cache with custom pixelRatio', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - - var rect = new Konva.Rect({ - x: 100, - y: 50, - width: 100, - height: 100, - fill: 'green', - }); - - layer.add(rect); - stage.add(layer); - - rect.cache({ - hitCanvasPixelRatio: 0.2, - }); - layer.draw(); - - var hitCanvas = rect._cache.get('canvas').hit; - assert.equal(hitCanvas._canvas.width, rect.width() * 0.2); - assert.equal(hitCanvas._canvas.height, rect.height() * 0.2); - assert.equal(hitCanvas.pixelRatio, 0.2); - - var canvas = createCanvas(); - var context = canvas.getContext('2d'); - context.beginPath(); - context.rect(100, 50, 100, 100); - context.closePath(); - context.fillStyle = 'green'; - context.fill(); - - compareLayerAndCanvas(layer, canvas, 5); - - assert.equal(stage.getIntersection({ x: 150, y: 100 }), rect); - }); -}); diff --git a/test/unit/Node-test.ts b/test/unit/Node-test.ts deleted file mode 100644 index 599254b65..000000000 --- a/test/unit/Node-test.ts +++ /dev/null @@ -1,3845 +0,0 @@ -import { assert } from 'chai'; -import { Shape } from '../../src/Shape'; - -import { - addStage, - simulateMouseDown, - simulateMouseUp, - showHit, - compareLayerAndCanvas, - compareLayers, - addContainer, - loadImage, - Konva, - isBrowser, - simulateMouseMove, -} from './test-utils'; - -describe('Node', function () { - // ====================================================== - it('getType and getClassName', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var group = new Konva.Group(); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - }); - - stage.add(layer.add(group.add(circle))); - - //console.log(stage.getType()); - - assert.equal(stage.getType(), 'Stage'); - assert.equal(layer.getType(), 'Layer'); - assert.equal(group.getType(), 'Group'); - assert.equal(circle.getType(), 'Shape'); - - assert.equal(stage.getClassName(), 'Stage'); - assert.equal(layer.getClassName(), 'Layer'); - assert.equal(group.getClassName(), 'Group'); - assert.equal(circle.getClassName(), 'Circle'); - }); - // ====================================================== - it('get layer', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - }); - assert.equal(circle.getLayer(), null); - - stage.add(layer.add(circle)); - assert.equal(circle.getLayer(), layer); - }); - // ====================================================== - it('setAttr', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - }); - - stage.add(layer.add(circle)); - - circle.setAttr('fill', 'red'); - layer.draw(); - - assert.equal(circle.fill(), 'red'); - - circle.setAttr('position', { x: 5, y: 6 }); - - assert.equal(circle.x(), 5); - assert.equal(circle.y(), 6); - - circle.setAttr('foobar', 12); - - assert.equal(circle.getAttr('foobar'), 12); - }); - - // ====================================================== - it('unset attr', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - }); - - stage.add(layer.add(circle)); - - circle.setAttr('x', undefined); - assert.equal(circle.x(), 0); - - circle.y(null); - assert.equal(circle.y(), 0); - }); - - // ====================================================== - it('set shape and layer opacity to 0.5', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - }); - - circle.opacity(0.5); - layer.opacity(0.5); - layer.add(circle); - stage.add(layer); - - assert.equal(circle.getAbsoluteOpacity(), 0.25); - assert.equal(layer.getAbsoluteOpacity(), 0.5); - }); - // ====================================================== - it('transform cache', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - }); - - assert.equal(circle._cache.get('transform'), undefined); - - layer.add(circle); - stage.add(layer); - - // transform cache - assert.notEqual(circle._cache.get('transform'), undefined); - circle.x(100); - assert.equal(circle._cache.get('transform').dirty, true); - layer.draw(); - assert.equal(circle._cache.get('transform').dirty, false); - }); - - // ====================================================== - it('visible cache', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - }); - layer.add(circle); - stage.add(layer); - - // visible cache - assert.equal(circle._cache.get('visible'), true); - circle.hide(); - assert.equal(circle._cache.get('visible'), undefined); - stage.draw(); - assert.equal(circle._cache.get('visible'), false); - circle.show(); - assert.equal(circle._cache.get('visible'), undefined); - layer.draw(); - assert.equal(circle._cache.get('visible'), true); - }); - - // ====================================================== - it('shadow cache', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - }); - layer.add(circle); - stage.add(layer); - - // shadow cache - assert.equal(circle._cache.get('hasShadow'), false); - circle.shadowColor('red'); - circle.shadowOffsetX(10); - assert.equal(circle._cache.get('hasShadow'), undefined); - layer.draw(); - assert.equal(circle._cache.get('hasShadow'), true); - layer.draw(); - assert.equal(circle._cache.get('hasShadow'), true); - }); - - // ====================================================== - it('has shadow', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var rect = new Konva.Rect({ - x: 10, - y: stage.height() / 3, - width: 100, - height: 100, - fill: 'red', - stroke: 'black', - strokeWidth: 4, - draggable: true, - }); - layer.add(rect); - stage.add(layer); - rect.shadowEnabled(true); - rect.shadowColor('grey'); - assert.equal(rect.hasShadow(), true); - rect.shadowEnabled(false); - assert.equal(rect.hasShadow(), false); - }); - - // ====================================================== - it('opacity cache', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - }); - layer.add(circle); - stage.add(layer); - - // opacity cache - assert.equal(circle._cache.get('absoluteOpacity'), 1); - circle.opacity(0.5); - assert.equal(circle._cache.get('absoluteOpacity'), undefined); - layer.draw(); - assert.equal(circle._cache.get('absoluteOpacity'), 0.5); - }); - - // ====================================================== - it('listening cache', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - }); - layer.add(circle); - stage.add(layer); - - // listening cache - - // prime the cache - circle.isListening(); - - assert.equal(circle._cache.get('listening'), true); - circle.listening(false); - assert.equal(circle._cache.get('listening'), undefined); - circle.isListening(); - assert.equal(circle._cache.get('listening'), false); - }); - - // ====================================================== - it('stage cache', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - }); - layer.add(circle); - stage.add(layer); - - // stage cache - var st = circle.getStage(); - assert.equal(circle._cache.get('stage')._id, stage._id); - }); - - // ====================================================== - it('toDataURL + HDPI', function (done) { - // this.timeout(5000); - var oldRatio = Konva.pixelRatio; - Konva.pixelRatio = 2; - - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var circle = new Konva.Circle({ - fill: 'green', - x: stage.width() / 2, - y: stage.height() / 2, - radius: 50, - }); - layer.add(circle); - - var layer2 = new Konva.Layer(); - stage.add(layer2); - - stage.draw(); - stage.toDataURL({ - pixelRatio: 2, - callback: function (url) { - loadImage(url, (img) => { - var image = new Konva.Image({ - image: img, - scaleX: 0.5, - scaleY: 0.5, - }); - assert.equal( - image.width(), - stage.width() * 2, - 'image has double size' - ); - layer2.add(image); - layer2.draw(); - compareLayers(layer, layer2, 150); - Konva.pixelRatio = oldRatio; - done(); - }); - }, - }); - }); - - // ====================================================== - it('toDataURL with moved layer', function () { - var stage = addStage(); - var layer = new Konva.Layer({ - x: 50, - y: 50, - }); - stage.add(layer); - - var circle = new Konva.Circle({ - fill: 'green', - x: 50, - y: 50, - radius: 50, - }); - layer.add(circle); - - stage.draw(); - var stageExport = stage.toCanvas({ - pixelRatio: layer.getCanvas().getPixelRatio(), - }); - compareLayerAndCanvas(layer, stageExport); - }); - - // ====================================================== - it('toDataURL with moved layer and moved export', function () { - var stage = addStage(); - var layer = new Konva.Layer({}); - stage.add(layer); - - var circle = new Konva.Circle({ - fill: 'green', - x: 50, - y: 50, - radius: 50, - }); - layer.add(circle); - - stage.draw(); - var stageExport = stage.toCanvas({ - x: 50, - y: 50, - pixelRatio: layer.getCanvas().getPixelRatio(), - }); - layer.x(-50); - layer.y(-50); - layer.draw(); - compareLayerAndCanvas(layer, stageExport); - }); - - // ====================================================== - it('toDataURL of moved shape', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var circle = new Konva.Circle({ - fill: 'green', - radius: 50, - }); - layer.add(circle); - - var oldURL = circle.toDataURL(); - - circle.x(100); - circle.y(100); - - var newURL = circle.toDataURL(); - assert.equal(oldURL, newURL); - }); - - // ====================================================== - it('toDataURL of transformer shape', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var text = new Konva.Text({ - fill: 'green', - text: 'hello, test', - rotation: 45, - }); - layer.add(text); - - var oldURL = text.toDataURL(); - - text.x(100); - text.y(100); - - var newURL = text.toDataURL(); - assert.equal(oldURL, newURL); - }); - - // ====================================================== - it('export with smooth disabled', function (done) { - loadImage('lion.png', (imageObj) => { - var stage = addStage(); - var layer = new Konva.Layer({ - imageSmoothingEnabled: false, - }); - stage.add(layer); - - var image = new Konva.Image({ - x: -50, - y: -50, - image: imageObj, - scaleX: 5, - scaleY: 5, - }); - layer.add(image); - - const canvas = layer.toCanvas({ - imageSmoothingEnabled: false, - pixelRatio: layer.getCanvas().getPixelRatio(), - }); - layer.draw(); - compareLayerAndCanvas(layer, canvas); - - // just check clone without crashes - done(); - }); - }); - - // ====================================================== - it("listen and don't listen", function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var rect = new Konva.Rect({ - x: 50, - y: 50, - width: 200, - height: 50, - fill: 'blue', - }); - - var rect2 = new Konva.Rect({ - x: 200, - y: 100, - width: 200, - height: 50, - fill: 'red', - listening: false, - }); - - layer.add(rect).add(rect2); - stage.add(layer); - - assert.equal(rect.listening(), true); - - assert.equal(rect.isListening(), true); - rect.listening(false); - assert.equal(rect.listening(), false); - - assert.equal(rect2.listening(), false); - rect2.listening(true); - assert.equal(rect2.listening(), true); - }); - - // ====================================================== - it("listen and don't listen with one shape", function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var rect = new Konva.Rect({ - x: 50, - y: 50, - width: 200, - height: 50, - fill: 'blue', - }); - - layer.add(rect); - stage.add(layer); - - rect.listening(false); - layer.drawHit(); - - showHit(layer); - - assert.equal(layer.getIntersection({ x: 60, y: 60 }), null); - }); - - // ====================================================== - it('test offset attr change', function () { - /* - * the premise of this test to make sure that only - * root level attributes trigger an attr change event. - * for this test, we have two offset properties. one - * is in the root level, and the other is inside the shadow - * object - */ - var stage = addStage(); - var layer = new Konva.Layer(); - var rect = new Konva.Rect({ - x: 50, - y: 50, - width: 200, - height: 50, - fill: 'blue', - offset: { x: 10, y: 10 }, - shadowColor: 'black', - shadowOffset: { x: 20, y: 20 }, - }); - - layer.add(rect); - stage.add(layer); - - var offsetChange = false; - var shadowOffsetChange = false; - - rect.on('offsetChange', function (val) { - offsetChange = true; - }); - - rect.offset({ x: 1, y: 2 }); - - assert.equal(offsetChange, true); - }); - - // ====================================================== - it('simple clone', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var rect = new Konva.Rect({ - x: 0, - y: 0, - width: 100, - height: 100, - stroke: 'red', - }); - - var clone = rect.clone({ - stroke: 'green', - }); - - layer.add(clone); - stage.add(layer); - - assert.equal(rect.stroke(), 'red'); - assert.equal(clone.stroke(), 'green'); - }); - - // ====================================================== - it('clone - check reference', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var line = new Konva.Line({ - x: 0, - y: 0, - stroke: 'red', - points: [0, 0, 10, 10], - }); - - var clone = line.clone({ - stroke: 'green', - }); - - layer.add(clone); - stage.add(layer); - - assert.equal(line.points() === clone.points(), false); - assert.equal(JSON.stringify(clone.points()), '[0,0,10,10]'); - }); - - // ====================================================== - it('complex clone', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var rect = new Konva.Rect({ - x: 50, - y: 50, - width: 200, - height: 50, - fill: 'blue', - offsetX: 10, - offsetY: 10, - shadowColor: 'black', - shadowOffsetX: 20, - shadowOffsetY: 20, - draggable: true, - name: 'myRect', - id: 'myRect', - }); - - var clicks = []; - - rect.on('click', function () { - clicks.push(this.name()); - }); - var clone = rect.clone({ - x: 300, - fill: 'red', - name: 'rectClone', - }); - - assert.equal(clone.x(), 300); - assert.equal(clone.y(), 50); - assert.equal(clone.width(), 200); - assert.equal(clone.height(), 50); - assert.equal(clone.fill(), 'red'); - - assert.equal(rect.shadowColor(), 'black'); - assert.equal(clone.shadowColor(), 'black'); - - assert.equal(clone.id() === 'myRect', true, 'clone id'); - - clone.shadowColor('green'); - - /* - * Make sure that when we change a clone object attr that the rect object - * attr isn't updated by reference - */ - - assert.equal(rect.shadowColor(), 'black'); - assert.equal(clone.shadowColor(), 'green'); - - layer.add(rect); - layer.add(clone); - stage.add(layer); - - // make sure private ids are different - assert(rect._id !== clone._id, 'rect and clone ids should be different'); - - // test user event binding cloning - assert.equal(clicks.length, 0); - rect.fire('click'); - assert.equal(clicks.toString(), 'myRect'); - clone.fire('click'); - assert.equal(clicks.toString(), 'myRect,rectClone'); - }); - - // ====================================================== - it('clone a group', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var group = new Konva.Group({ - x: 50, - draggable: true, - name: 'myGroup', - }); - - var rect = new Konva.Rect({ - x: 0, - y: 50, - width: 200, - height: 50, - fill: 'red', - offsetX: 10, - offsetY: 10, - shadowColor: 'black', - shadowOffset: { x: 20, y: 20 }, - name: 'myRect', - myAttr: 'group rect', - }); - var text = new Konva.Text({ - x: 0, - y: 110, - text: 'Some awesome text!', - fontSize: 14, - fontFamily: 'Calibri', - fill: 'blue', - name: 'myText', - }); - group.add(rect); - group.add(text); - - var clicks = []; - var taps = []; - - group.on('click', function () { - clicks.push(this.name()); - }); - rect.on('tap', function () { - taps.push(this.attrs.myAttr); - }); - var clone = group.clone({ - x: 300, - name: 'groupClone', - }); - - showHit(layer); - - layer.add(group); - layer.add(clone); - stage.add(layer); - - assert.equal(clone.x(), 300); - assert.equal(clone.y(), 0); - assert.equal(clone.draggable(), true); - // test alias - assert.equal(clone.draggable(), true); - assert.equal(clone.name(), 'groupClone'); - - assert.equal(group.getChildren().length, 2); - assert.equal(clone.getChildren().length, 2); - assert.equal(group.find('.myText')[0].fill(), 'blue'); - assert.equal(clone.find('.myText')[0].fill(), 'blue'); - clone.find('.myText')[0].fill('black'); - assert.equal(group.find('.myRect')[0].attrs.myAttr, 'group rect'); - assert.equal(clone.find('.myRect')[0].attrs.myAttr, 'group rect'); - clone.find('.myRect')[0].setAttrs({ - myAttr: 'clone rect', - }); - - // Make sure that when we change a clone object attr that the rect object - // attr isn't updated by reference - - assert.equal(group.find('.myText')[0].fill(), 'blue'); - assert.equal(clone.find('.myText')[0].fill(), 'black'); - - assert.equal(group.find('.myRect')[0].attrs.myAttr, 'group rect'); - assert.equal(clone.find('.myRect')[0].attrs.myAttr, 'clone rect'); - - // make sure private ids are different - assert.notEqual(group._id, clone._id); - - // make sure childrens private ids are different - assert.notEqual(group.find('.myRect')[0]._id, clone.find('.myRect')[0]._id); - assert.notEqual(group.find('.myText')[0]._id, clone.find('.myText')[0]._id); - - // test user event binding cloning - assert.equal(clicks.length, 0); - group.fire('click'); - assert.equal(clicks.toString(), 'myGroup'); - clone.fire('click'); - assert.equal(clicks.toString(), 'myGroup,groupClone'); - - // test user event binding cloning on children - assert.equal(taps.length, 0); - group.find('.myRect')[0].fire('tap'); - assert.equal(taps.toString(), 'group rect'); - clone.find('.myRect')[0].fire('tap'); - assert.equal(taps.toString(), 'group rect,clone rect'); - - stage.draw(); - }); - - // ====================================================== - it('test on attr change', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var rect = new Konva.Rect({ - x: 50, - y: 50, - width: 200, - height: 50, - fill: 'blue', - shadowOffset: { x: 10, y: 10 }, - }); - - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 35, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - }); - - layer.add(circle); - layer.add(rect); - stage.add(layer); - - var widthChanged = 0; - var shadowChanged = 0; - var radiusChanged = 0; - - rect.on('widthChange', function (evt) { - widthChanged++; - assert.equal(evt['oldVal'], 200); - assert.equal(evt['newVal'], 210); - }); - - rect.on('shadowOffsetChange', function () { - shadowChanged++; - }); - - circle.on('radiusChange', function () { - radiusChanged++; - }); - - circle.radius(70); - - rect.setSize({ width: 210, height: 210 }); - rect.shadowOffset({ - x: 20, - y: 0, - }); - - assert.equal(widthChanged, 1); - assert.equal(shadowChanged, 1); - assert.equal(radiusChanged, 1); - }); - - // ====================================================== - it('test on attr change for same value', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var rect = new Konva.Rect({ - x: 50, - y: 50, - width: 200, - height: 50, - fill: 'blue', - shadowOffset: { x: 10, y: 10 }, - }); - - layer.add(rect); - stage.add(layer); - - var widthChanged = 0; - - rect.on('widthChange', function (evt) { - widthChanged++; - }); - - rect.width(210); - rect.width(210); - - assert.equal(widthChanged, 1, 'should trigger only once'); - }); - - // ====================================================== - it('set shape, layer and stage opacity to 0.5', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - }); - - circle.opacity(0.5); - layer.opacity(0.5); - stage.opacity(0.5); - layer.add(circle); - stage.add(layer); - - assert.equal(circle.getAbsoluteOpacity(), 0.125); - assert.equal(layer.getAbsoluteOpacity(), 0.25); - assert.equal(stage.getAbsoluteOpacity(), 0.5); - }); - - // ====================================================== - it('hide show layer', function () { - var stage = addStage(); - - var layer1 = new Konva.Layer(); - var layer2 = new Konva.Layer(); - - var circle1 = new Konva.Circle({ - x: 100, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - }); - var circle2 = new Konva.Circle({ - x: 150, - y: stage.height() / 2, - radius: 70, - fill: 'red', - stroke: 'black', - strokeWidth: 4, - }); - - circle1.on('mousemove', function () { - console.log('mousemove circle1'); - }); - - circle2.on('mousemove', function () { - console.log('mousemove circle2'); - }); - - layer1.add(circle1); - layer2.add(circle2); - stage.add(layer1).add(layer2); - - assert.equal(layer2.isVisible(), true); - layer2.hide(); - assert.equal(layer2.isVisible(), false); - assert.equal(layer2.canvas._canvas.style.display, 'none'); - - layer2.show(); - assert.equal(layer2.isVisible(), true); - assert.equal(layer2.canvas._canvas.style.display, 'block'); - }); - - // ====================================================== - it('rotation in degrees', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var rect = new Konva.Rect({ - x: 200, - y: 100, - width: 100, - height: 50, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - rotation: 10, - }); - - assert.equal(rect.rotation(), 10); - rect.rotation(20); - assert.equal(rect.rotation(), 20); - rect.rotate(20); - assert.equal(rect.rotation(), 40); - - layer.add(rect); - stage.add(layer); - }); - - // ====================================================== - it('skew', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var rect = new Konva.Rect({ - x: 200, - y: 100, - width: 100, - height: 50, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - skewX: 1, - }); - - layer.add(rect); - stage.add(layer); - - assert.equal(rect.skewX(), 1); - assert.equal(rect.skewY(), 0); - - /* - rect.transitionTo({ - duration: 4, - skewY: -2, - easing: 'ease-in-out' - - - }) - */ - }); - - // ====================================================== - it('init with position, scale, rotation, then change scale', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var rect = new Konva.Rect({ - x: 200, - y: 100, - width: 100, - height: 50, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - scale: { - x: 0.5, - y: 0.5, - }, - rotation: 20, - }); - - assert.equal(rect.getPosition().x, 200); - assert.equal(rect.getPosition().y, 100); - assert.equal(rect.scale().x, 0.5); - assert.equal(rect.scale().y, 0.5); - assert.equal(rect.rotation(), 20); - - rect.scale({ x: 2, y: 0.3 }); - assert.equal(rect.scale().x, 2); - assert.equal(rect.scale().y, 0.3); - - layer.add(rect); - stage.add(layer); - }); - - // ====================================================== - it('clone sprite', function (done) { - loadImage('scorpion-sprite.png', (imageObj) => { - var stage = addStage(); - var layer = new Konva.Layer(); - - var sprite = new Konva.Sprite({ - x: 200, - y: 50, - image: imageObj, - animation: 'standing', - animations: { - standing: [ - 0, 0, 49, 109, 52, 0, 49, 109, 105, 0, 49, 109, 158, 0, 49, 109, - 210, 0, 49, 109, 262, 0, 49, 109, - ], - kicking: [ - 0, 109, 45, 98, 45, 109, 45, 98, 95, 109, 63, 98, 156, 109, 70, 98, - 229, 109, 60, 98, 287, 109, 41, 98, - ], - }, - frameRate: 10, - draggable: true, - shadowColor: 'black', - shadowBlur: 3, - shadowOffset: { x: 3, y: 1 }, - shadowOpacity: 0.3, - }); - - var clone = sprite.clone(); - layer.add(clone); - stage.add(layer); - clone.start(); - - assert.equal(clone.image(), sprite.image()); - - // just check clone without crashes - done(); - }); - }); - - // ====================================================== - it('hide group', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - var group = new Konva.Group(); - - var circle1 = new Konva.Circle({ - x: 100, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - }); - var circle2 = new Konva.Circle({ - x: 150, - y: stage.height() / 2, - radius: 70, - fill: 'red', - stroke: 'black', - strokeWidth: 4, - }); - - circle1.on('mousemove', function () { - console.log('mousemove circle1'); - }); - - circle2.on('mousemove', function () { - console.log('mousemove circle2'); - }); - - group.add(circle2); - layer.add(circle1).add(group); - stage.add(layer); - - assert.equal(group.isVisible(), true); - assert.equal(circle2.isVisible(), true); - - group.hide(); - layer.draw(); - - assert.equal(!group.isVisible(), true); - assert.equal(!circle2.isVisible(), true); - }); - - // ====================================================== - it('add shape with custom attr pointing to self', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - offset: { - x: 0, - y: 0, - }, - scale: { - x: 2, - y: 2, - }, - }); - layer.add(circle); - stage.add(layer); - - /* - * add custom attr that points to self. The setAttrs method should - * not inifinitely recurse causing a stack overflow - */ - circle.setAttrs({ - self: circle, - }); - - /* - * serialize the stage. The json should succeed because objects that have - * methods, such as self, are not serialized, and will therefore avoid - * circular json errors. - */ - // console.log(stage.children); - // return; - var json = stage.toJSON(); - - // make sure children are ok after json - assert.equal(stage.children[0], layer); - }); - - // ====================================================== - it('set offset offset after instantiation', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var rect = new Konva.Rect({ - x: 200, - y: 100, - width: 100, - height: 50, - stroke: 'blue', - offset: { - x: 40, - y: 20, - }, - }); - - layer.add(rect); - stage.add(layer); - - assert.equal(rect.offsetX(), 40); - assert.equal(rect.offsetY(), 20); - - assert.equal(rect.offset().x, 40); - assert.equal(rect.offset().y, 20); - - rect.offset({ x: 80, y: 40 }); - - assert.equal(rect.offsetX(), 80); - assert.equal(rect.offsetY(), 40); - - assert.equal(rect.offset().x, 80); - assert.equal(rect.offset().y, 40); - }); - - // ====================================================== - it('test name methods', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - }); - - layer.add(circle); - stage.add(layer); - assert.equal(circle.name(), ''); - - circle.addName('foo'); - assert.equal(circle.name(), 'foo'); - circle.addName('myCircle'); - assert.equal(circle.name(), 'foo myCircle'); - - // add existing name - circle.addName('foo'); - assert.equal(circle.name(), 'foo myCircle'); - - // check hasName - assert.equal(circle.hasName('myCircle'), true); - assert.equal(circle.hasName('foo'), true); - assert.equal(circle.hasName('boo'), false); - // should return false for empty name - assert.equal(layer.hasName(''), false); - assert.equal(stage.findOne('.foo'), circle); - - // removing name - circle.removeName('foo'); - assert.equal(circle.name(), 'myCircle'); - assert.equal(layer.find('.foo').length, 0); - }); - - // ====================================================== - it('test setting shadow offset', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var rect = new Konva.Rect({ - x: 0, - y: 0, - width: 100, - height: 50, - fill: 'red', - shadowColor: 'blue', - shadowBlur: 12, - }); - - layer.add(rect); - stage.add(layer); - - rect.shadowOffset({ x: 1, y: 2 }); - assert.equal(rect.shadowOffset().x, 1); - assert.equal(rect.shadowOffset().y, 2); - // make sure we still have the other properties - assert.equal(rect.shadowColor(), 'blue'); - assert.equal(rect.shadowBlur(), 12); - - rect.shadowOffset({ - x: 3, - y: 4, - }); - assert.equal(rect.shadowOffset().x, 3); - assert.equal(rect.shadowOffset().y, 4); - - // test partial setting - rect.shadowOffsetX(5); - assert.equal(rect.shadowOffset().x, 5); - assert.equal(rect.shadowOffset().y, 4); - - // test partial setting - rect.shadowOffsetY(6); - assert.equal(rect.shadowOffset().x, 5); - assert.equal(rect.shadowOffset().y, 6); - }); - - // ====================================================== - it('test offset', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var rect = new Konva.Rect({ - x: 0, - y: 0, - width: 100, - height: 50, - fill: 'red', - }); - - layer.add(rect); - stage.add(layer); - - rect.offset({ x: 1, y: 2 }); - assert.equal(rect.offset().x, 1); - assert.equal(rect.offset().y, 2); - - rect.offset({ x: 3, y: 4 }); - assert.equal(rect.offset().x, 3); - assert.equal(rect.offset().y, 4); - - rect.offset({ - x: 5, - y: 6, - }); - assert.equal(rect.offset().x, 5); - assert.equal(rect.offset().y, 6); - - rect.offsetX(7); - assert.equal(rect.offset().x, 7); - assert.equal(rect.offset().y, 6); - - rect.offsetY(8); - assert.equal(rect.offset().x, 7); - assert.equal(rect.offset().y, 8); - }); - - // ====================================================== - it('test setPosition and move', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var rect = new Konva.Rect({ - x: 0, - y: 0, - width: 100, - height: 50, - fill: 'red', - }); - - layer.add(rect); - stage.add(layer); - - rect.setPosition({ x: 1, y: 2 }); - assert.equal(rect.getPosition().x, 1); - assert.equal(rect.getPosition().y, 2); - - rect.setPosition({ x: 3, y: 4 }); - assert.equal(rect.getPosition().x, 3); - assert.equal(rect.getPosition().y, 4); - - rect.setPosition({ - x: 5, - y: 6, - }); - assert.equal(rect.getPosition().x, 5); - assert.equal(rect.getPosition().y, 6); - - rect.x(7); - assert.equal(rect.getPosition().x, 7); - assert.equal(rect.getPosition().y, 6); - - rect.y(8); - assert.equal(rect.getPosition().x, 7); - assert.equal(rect.getPosition().y, 8); - - rect.move({ x: 10, y: 10 }); - assert.equal(rect.getPosition().x, 17); - assert.equal(rect.getPosition().y, 18); - }); - - // ====================================================== - it('test setScale', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var rect = new Konva.Rect({ - x: 200, - y: 20, - width: 100, - height: 50, - fill: 'red', - stroke: 'black', - strokeWidth: 4, - }); - - layer.add(rect); - stage.add(layer); - - rect.scale({ x: 2, y: 3 }); - assert.equal(rect.scale().x, 2); - assert.equal(rect.scale().y, 3); - - rect.scale({ x: 4, y: 4 }); - assert.equal(rect.scale().x, 4); - assert.equal(rect.scale().y, 4); - - rect.scale({ x: 5, y: 6 }); - assert.equal(rect.scale().x, 5); - assert.equal(rect.scale().y, 6); - - rect.scale({ x: 7, y: 8 }); - assert.equal(rect.scale().x, 7); - assert.equal(rect.scale().y, 8); - - rect.scale({ - x: 9, - y: 10, - }); - assert.equal(rect.scale().x, 9); - assert.equal(rect.scale().y, 10); - - rect.scaleX(11); - assert.equal(rect.scale().x, 11); - assert.equal(rect.scale().y, 10); - - rect.scaleY(12); - assert.equal(rect.scale().x, 11); - assert.equal(rect.scale().y, 12); - }); - - // ====================================================== - it('test config scale', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var rect1 = new Konva.Rect({ - x: 200, - y: 20, - width: 100, - height: 50, - fill: 'red', - scale: { - x: 2, - y: 3, - }, - }); - - var rect2 = new Konva.Rect({ - x: 200, - y: 20, - width: 100, - height: 50, - fill: 'red', - scale: { x: 2, y: 2 }, - }); - - var rect3 = new Konva.Rect({ - x: 200, - y: 20, - width: 100, - height: 50, - fill: 'red', - scale: { x: 2, y: 3 }, - }); - - var rect4 = new Konva.Rect({ - x: 200, - y: 20, - width: 100, - height: 50, - fill: 'red', - scaleX: 2, - }); - - var rect5 = new Konva.Rect({ - x: 200, - y: 20, - width: 100, - height: 50, - fill: 'red', - scaleY: 2, - }); - - layer.add(rect1).add(rect2).add(rect3).add(rect4).add(rect5); - stage.add(layer); - - assert.equal(rect1.scale().x, 2); - assert.equal(rect1.scale().y, 3); - - assert.equal(rect2.scale().x, 2); - assert.equal(rect2.scale().y, 2); - - assert.equal(rect3.scale().x, 2); - assert.equal(rect3.scale().y, 3); - - assert.equal(rect4.scale().x, 2); - assert.equal(rect4.scale().y, 1); - - //assert.equal(rect5.scale().x, 1); - assert.equal(rect5.scale().y, 2); - }); - - // ====================================================== - it('test config position', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var rect1 = new Konva.Rect({ - x: 1, - y: 2, - width: 100, - height: 50, - fill: 'red', - }); - - var rect2 = new Konva.Rect({ - x: 3, - width: 100, - height: 50, - fill: 'red', - }); - - var rect3 = new Konva.Rect({ - y: 4, - width: 100, - height: 50, - fill: 'red', - }); - - var rect4 = new Konva.Rect({ - width: 100, - height: 50, - fill: 'red', - }); - - layer.add(rect1).add(rect2).add(rect3).add(rect4); - stage.add(layer); - - assert.equal(rect1.getPosition().x, 1); - assert.equal(rect1.getPosition().y, 2); - - assert.equal(rect2.getPosition().x, 3); - assert.equal(rect2.getPosition().y, 0); - - assert.equal(rect3.getPosition().x, 0); - assert.equal(rect3.getPosition().y, 4); - - assert.equal(rect4.getPosition().x, 0); - assert.equal(rect4.getPosition().y, 0); - }); - - // ====================================================== - it('test getPosition and getAbsolutePosition for shape inside transformed stage', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var rect = new Konva.Rect({ - x: 200, - y: 20, - width: 100, - height: 50, - fill: 'red', - stroke: 'black', - strokeWidth: 4, - draggable: true, - }); - - layer.add(rect); - stage.add(layer); - - stage.rotate(180 / 3); - stage.scale({ x: 0.5, y: 0.5 }); - - stage.draw(); - - assert.equal(rect.getPosition().x, 200); - assert.equal(rect.getPosition().y, 20); - - assert.equal(Math.round(rect.getAbsolutePosition().x), 41); - assert.equal(Math.round(rect.getAbsolutePosition().y), 92); - }); - - // ====================================================== - it('test consecutive getAbsolutePositions()s when shape has offset', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var rect = new Konva.Rect({ - x: 200, - y: 20, - width: 100, - height: 50, - fill: 'red', - stroke: 'black', - strokeWidth: 4, - draggable: true, - offsetX: 30, - offsetY: 30, - //rotationDeg: 60 - //rotationDeg: Math.PI / 3 - }); - - layer.add(rect); - stage.add(layer); - - assert( - rect.getAbsolutePosition().x === 200 && - rect.getAbsolutePosition().y === 20, - 'absolute position should be 200, 20' - ); - assert( - rect.getAbsolutePosition().x === 200 && - rect.getAbsolutePosition().y === 20, - 'absolute position should be 200, 20' - ); - assert( - rect.getAbsolutePosition().x === 200 && - rect.getAbsolutePosition().y === 20, - 'absolute position should be 200, 20' - ); - assert( - rect.getAbsolutePosition().x === 200 && - rect.getAbsolutePosition().y === 20, - 'absolute position should be 200, 20' - ); - assert( - rect.getAbsolutePosition().x === 200 && - rect.getAbsolutePosition().y === 20, - 'absolute position should be 200, 20' - ); - }); - - // ====================================================== - it('test getPosition and getAbsolutePosition for transformed parent with offset offset', function () { - var side = 100; - var diagonal = Math.sqrt(side * side * 2); - - var stage = addStage(); - var layer = new Konva.Layer({ - name: 'layerName', - }); - var group = new Konva.Group({ - name: 'groupName', - id: 'groupId', - rotation: 45, - offset: { x: side / 2, y: side / 2 }, - x: diagonal / 2, - y: diagonal / 2, - }); - var rect = new Konva.Rect({ - x: 0, - y: 0, - width: side, - height: side, - fill: 'red', - name: 'rectName', - }); - var marker = new Konva.Rect({ - x: side, - y: 0, - width: 1, - height: 1, - fill: 'blue', - stroke: 'blue', - strokeWidth: 4, - name: 'markerName', - id: 'markerId', - }); - - group.add(rect); - group.add(marker); - layer.add(group); - stage.add(layer); - - assert.equal( - Math.round(marker.getAbsolutePosition().x), - Math.round(diagonal), - 'marker absolute position x should be about ' + - Math.round(diagonal) + - ' but is about ' + - Math.round(marker.getAbsolutePosition().x) - ); - assert.equal( - Math.round(marker.getAbsolutePosition().y), - Math.round(diagonal / 2), - 'marker absolute position y should be about ' + - Math.round(diagonal / 2) + - ' but is about ' + - Math.round(marker.getAbsolutePosition().y) - ); - }); - - // ====================================================== - it('test relative getAbsolutePosition for transformed parent ', function () { - var stage = addStage(); - var layer = new Konva.Layer({ - name: 'layerName', - x: 100, - y: 100, - }); - var group = new Konva.Group({ - name: 'groupName', - x: 100, - y: 100, - }); - var rect = new Konva.Rect({ - x: 50, - y: 60, - width: 50, - height: 50, - fill: 'red', - name: 'rectName', - }); - - group.add(rect); - layer.add(group); - stage.add(layer); - - assert.equal(rect.getAbsolutePosition(layer).x, 150); - assert.equal(rect.getAbsolutePosition(layer).y, 160); - }); - - // ====================================================== - it( - 'results of getAbsoluteTransform limited to position and offset transformations are the same' + - " when used with transformsEnabled = 'all' and transformsEnabled = 'position'", - function () { - var stage = addStage(); - var layer1 = new Konva.Layer({ - name: 'layerName', - x: 90, - y: 110, - offsetX: 50, - offsetY: 50, - transformsEnabled: 'all', - }); - var group1 = new Konva.Group({ - name: 'groupName', - x: 30, - y: 30, - offsetX: -60, - offsetY: -80, - transformsEnabled: 'all', - }); - var rect1 = new Konva.Rect({ - x: -50, - y: -60, - offsetX: 50, - offsetY: 50, - width: 50, - height: 50, - fill: 'red', - name: 'rectName', - id: 'rectId1', - transformsEnabled: 'all', - }); - - var layer2 = layer1.clone({ transformsEnabled: 'position' }); - var group2 = group1.clone({ transformsEnabled: 'position' }); - var rect2 = rect1.clone({ transformsEnabled: 'position' }); - - group1.add(rect1); - layer1.add(group1); - stage.add(layer1); - - group2.add(rect2); - layer2.add(group2); - stage.add(layer2); - - assert.equal(layer1.transformsEnabled(), 'all'); - assert.equal(group1.transformsEnabled(), 'all'); - assert.equal(rect1.transformsEnabled(), 'all'); - - assert.equal(layer2.transformsEnabled(), 'position'); - assert.equal(group2.transformsEnabled(), 'position'); - assert.equal(rect2.transformsEnabled(), 'position'); - - assert.deepEqual( - rect2.getAbsoluteTransform(), - rect1.getAbsoluteTransform() - ); - } - ); - - // ====================================================== - it('test dragDistance', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var rect1 = new Konva.Rect({ - x: 1, - y: 2, - width: 100, - height: 50, - fill: 'red', - }); - - var group = new Konva.Group({ - dragDistance: 2, - }); - - var rect2 = new Konva.Rect({ - x: 3, - width: 100, - height: 50, - fill: 'red', - }); - group.add(rect2); - - layer.add(rect1).add(group); - stage.add(layer); - - assert.equal(rect1.dragDistance(), Konva.dragDistance); - assert.equal(group.dragDistance(), 2); - assert.equal(rect2.dragDistance(), 2); - }); - - // ====================================================== - it('translate, rotate, scale shape', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Rect({ - x: 100, - y: 100, - rotation: 20, - width: 100, - height: 50, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - scale: { - x: 2, - y: 1, - }, - offset: { - x: 50, - y: 25, - }, - }); - - layer.add(circle); - stage.add(layer); - - var trace = layer.getContext().getTrace(); - assert.equal( - trace, - 'clearRect(0,0,578,200);save();transform(1.879,0.684,-0.342,0.94,14.581,42.306);beginPath();rect(0,0,100,50);closePath();fillStyle=green;fill();lineWidth=4;strokeStyle=black;stroke();restore();' - ); - }); - - // ====================================================== - it('test isListening', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var rect = new Konva.Rect({ - x: 100, - y: 100, - rotation: 20, - width: 100, - height: 50, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - }); - - layer.add(rect); - stage.add(layer); - - assert.equal(rect.isListening(), true); - - rect.listening(false); - assert.equal(rect.isListening(), false); - - rect.listening(true); - assert.equal(rect.isListening(), true); - - layer.listening(false); - - assert.equal(rect.isListening(), false); - - layer.listening(true); - assert.equal(rect.isListening(), true); - - stage.listening(false); - assert.equal(rect.isListening(), false); - }); - - // ====================================================== - it('test fire event', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - }); - - stage.add(layer); - layer.add(circle); - layer.draw(); - - var clicks = []; - - circle.on('click', function () { - clicks.push('circle'); - - /* - var evt = window.event; - var rightClick = evt.which ? evt.which == 3 : evt.button == 2; - console.log(rightClick); - */ - }); - var foo; - circle.on('customEvent', function (evt) { - foo = evt['foo']; - }); - - layer.on('click', function () { - clicks.push('layer'); - }); - // fire event with bubbling - circle.fire('click', undefined, true); - - //console.log(clicks); - - assert.equal(clicks.toString(), 'circle,layer'); - - // no bubble - circle.fire('click'); - - assert.equal(clicks.toString(), 'circle,layer,circle'); - - // test custom event - circle.fire('customEvent', { - foo: 'bar', - }); - - assert.equal(foo, 'bar'); - - // test fireing custom event that doesn't exist. script should not fail - circle.fire('kaboom'); - }); - - // ====================================================== - it('add remove event', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - }); - - /* - * test regular on and off - */ - assert.equal(circle.eventListeners['click'], undefined); - - circle.on('click', function () {}); - assert.equal(circle.eventListeners['click'].length, 1); - - circle.on('click', function () {}); - assert.equal(circle.eventListeners['click'].length, 2); - - circle.off('click'); - assert.equal(circle.eventListeners['click'], undefined); - - /* - * test name spacing - */ - circle.on('click.foo', function () {}); - assert.equal(circle.eventListeners['click'].length, 1); - - circle.on('click.foo', function () {}); - assert.equal(circle.eventListeners['click'].length, 2); - circle.on('click.bar', function () {}); - assert.equal(circle.eventListeners['click'].length, 3); - - circle.off('click.foo'); - assert.equal(circle.eventListeners['click'].length, 1); - - circle.off('click.bar'); - assert.equal(circle.eventListeners['click'], undefined); - - /* - * test remove all events in name space - */ - circle.on('click.foo', function () {}); - circle.on('click.foo', function () {}); - circle.on('touch.foo', function () {}); - circle.on('click.bar', function () {}); - circle.on('touch.bar', function () {}); - assert.equal(circle.eventListeners['click'].length, 3); - assert.equal(circle.eventListeners['touch'].length, 2); - circle.off('.foo'); - assert.equal(circle.eventListeners['click'].length, 1); - assert.equal(circle.eventListeners['touch'].length, 1); - - circle.off('.bar'); - assert.equal(circle.eventListeners['click'], undefined); - assert.equal(circle.eventListeners['touch'], undefined); - - // test remove all events - circle.on('click.konva', function () {}); - circle.on('click', function () {}); - circle.on('boo', function () {}); - assert.equal(circle.eventListeners['click'].length, 2); - assert.equal(circle.eventListeners['boo'].length, 1); - circle.off(); - assert.equal(circle.eventListeners['boo'], undefined); - // should not remove konva listeners - assert.equal(circle.eventListeners['click'].length, 1); - stage.add(layer); - layer.add(circle); - layer.draw(); - }); - - // ====================================================== - it('remove event with with callback', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - }); - stage.add(layer); - layer.add(circle); - layer.draw(); - - var event1 = 0; - var event2 = 0; - - var callback1 = function () { - event1 += 1; - }; - var callback2 = function () { - event2 += 1; - }; - - circle.on('event', callback1); - circle.on('event', callback2); - - circle.fire('event'); - - assert.equal(event1, 1, 'event1 triggered once'); - assert.equal(event2, 1, 'event2 triggered once'); - - circle.off('event', callback1); - circle.fire('event'); - - assert.equal(event1, 1, 'event1 still triggered once'); - assert.equal(event2, 2, 'event2 triggered twice'); - }); - - // ====================================================== - it('simulate event bubble', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - }); - - stage.add(layer); - layer.add(circle); - layer.draw(); - - var clicks = []; - - circle.on('click', function () { - clicks.push('circle'); - }); - - layer.on('click', function () { - clicks.push('layer'); - }); - - circle.fire('click', undefined, true); - - assert.equal(clicks[0], 'circle'); - assert.equal(clicks[1], 'layer'); - }); - - // ====================================================== - it('simulate cancel event bubble', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - }); - - stage.add(layer); - layer.add(circle); - layer.draw(); - - var clicks = []; - - circle.on('click', function (e) { - e.cancelBubble = true; - clicks.push('circle'); - }); - - layer.on('click', function () { - clicks.push('layer'); - }); - - circle.fire('click', {}, true); - - assert.equal(clicks[0], 'circle'); - assert.equal(clicks.length, 1); - }); - - // TODO: should we remove deligation? - it.skip('simple event delegation', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - }); - - stage.add(layer); - layer.add(circle); - layer.draw(); - - var fired = false; - // layer.on('click', 'Circle', function (e) { - // assert.equal(this, circle); - // assert.equal(e.currentTarget, circle); - // fired = true; - // }); - circle.fire('click', undefined, true); - assert.equal(fired, true); - }); - - it.skip('complex event delegation', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - }); - var group = new Konva.Group({ - name: 'group1 group2', - }); - group.add(circle); - layer.add(group); - - layer.draw(); - - var fired = false; - // layer.on('click', '.group1', function (e) { - // assert.equal(this, group); - // assert.equal(e.currentTarget, group); - // fired = true; - // }); - circle.fire('click', undefined, true); - assert.equal(fired, true); - }); - // ====================================================== - it('move shape, group, and layer, and then get absolute position', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var group = new Konva.Group(); - - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - }); - - group.add(circle); - layer.add(group); - stage.add(layer); - - circle.setPosition({ x: 100, y: 0 }); - group.setPosition({ x: 100, y: 0 }); - layer.setPosition({ x: 100, y: 0 }); - - // test relative positions - assert.equal(circle.getPosition().x, 100); - assert.equal(group.getPosition().x, 100); - assert.equal(layer.getPosition().x, 100); - - // test absolute positions - assert.equal(circle.getAbsolutePosition().x, 300); - assert.equal(group.getAbsolutePosition().x, 200); - assert.equal(layer.getAbsolutePosition().x, 100); - - layer.draw(); - }); - - // ====================================================== - it('scale layer, rotate group, position shape, and then get absolute position', function () { - var stage = addStage(); - var layer = new Konva.Layer({ - scale: { - x: 2, - y: 2, - }, - }); - var group = new Konva.Group({ - x: 100, - rotation: 90, - }); - - var rect = new Konva.Rect({ - x: 50, - y: 10, - width: 100, - height: 50, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - draggable: true, - }); - - group.add(rect); - layer.add(group); - stage.add(layer); - - // test absolute positions - assert.equal(rect.getAbsolutePosition().x, 180); - assert.equal(rect.getAbsolutePosition().y, 100); - - layer.draw(); - }); - - // ====================================================== - it('hide show circle', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - }); - - layer.add(circle); - stage.add(layer); - - assert.equal(circle.isVisible(), true); - - circle.hide(); - layer.draw(); - - assert.equal(circle.isVisible(), false); - - circle.show(); - layer.draw(); - - assert.equal(circle.isVisible(), true); - }); - - // ====================================================== - it('set shape opacity to 0.5', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 20, - draggable: true, - }); - - circle.opacity(0.5); - layer.add(circle); - stage.add(layer); - - var sceneTrace = layer.getContext().getTrace(); - //console.log(sceneTrace); - - var bufferTrace = stage.bufferCanvas.getContext().getTrace(); - - if (isBrowser) { - assert.equal( - sceneTrace, - 'clearRect(0,0,578,200);save();globalAlpha=0.5;drawImage([object HTMLCanvasElement],0,0,578,200);restore();' - ); - } else { - assert.equal( - sceneTrace, - 'clearRect(0,0,578,200);save();globalAlpha=0.5;drawImage([object Object],0,0,578,200);restore();' - ); - } - - assert.equal( - bufferTrace, - 'clearRect(0,0,578,200);save();transform(1,0,0,1,289,100);beginPath();arc(0,0,70,0,6.283,false);closePath();fillStyle=green;fill();lineWidth=20;strokeStyle=black;stroke();restore();' - ); - }); - - it('set shape opacity to 0.5 then back to 1', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - }); - - circle.opacity(0.5); - layer.add(circle); - stage.add(layer); - - assert.equal(circle.getAbsoluteOpacity(), 0.5); - - circle.opacity(1); - layer.draw(); - - assert.equal(circle.getAbsoluteOpacity(), 1); - }); - - // ====================================================== - it('get absolute z index', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var group1 = new Konva.Group(); - var group2 = new Konva.Group(); - var group3 = new Konva.Group(); - var group4 = new Konva.Group(); - - var shape1 = new Konva.Circle({ - x: 150, - y: stage.height() / 2, - radius: 40, - fill: 'green', - }); - - var shape2 = new Konva.Circle({ - x: 250, - y: stage.height() / 2, - radius: 40, - fill: 'green', - }); - - /* - * Stage(0) - * | - * Layer(1) - * | - * +-----+-----+ - * | | - * G1(2) G2(3) - * | | - * + +---+---+ - * | | | - * S1(4) G3(5) G4(6) - * | - * + - * | - * S2(7) - */ - - group1.add(shape1); - group2.add(group3); - group2.add(group4); - group3.add(shape2); - layer.add(group1); - layer.add(group2); - stage.add(layer); - - assert.equal(stage.getAbsoluteZIndex(), 0); - //console.log(layer.getAbsoluteZIndex()); - assert.equal(layer.getAbsoluteZIndex(), 1); - assert.equal(group1.getAbsoluteZIndex(), 2); - assert.equal(group2.getAbsoluteZIndex(), 3); - assert.equal(shape1.getAbsoluteZIndex(), 4); - assert.equal(group3.getAbsoluteZIndex(), 5); - assert.equal(group4.getAbsoluteZIndex(), 6); - assert.equal(shape2.getAbsoluteZIndex(), 7); - - const tempLayer = new Konva.Layer(); - assert.equal(tempLayer.getAbsoluteZIndex(), 0); - }); - - // ====================================================== - it('JPEG toDataURL() Not Hiding Lower Layers with Black', function (done) { - var stage = addStage(); - - var layer1 = new Konva.Layer(); - var layer2 = new Konva.Layer(); - - layer1.add( - new Konva.Rect({ - x: 10, - y: 10, - width: 25, - height: 15, - fill: 'red', - }) - ); - layer2.add( - new Konva.Rect({ - x: 50, - y: 50, - width: 15, - height: 25, - fill: 'green', - }) - ); - - stage.add(layer1); - stage.add(layer2); - - stage.toDataURL({ - height: 100, - width: 100, - mimeType: 'image/jpeg', - quality: 0.8, - callback: function (url) { - loadImage(url, (imageObj) => { - layer2.add( - new Konva.Image({ - x: 200, - y: 10, - image: imageObj, - }) - ); - layer2.draw(); - done(); - }); - }, - }); - }); - - // ====================================================== - it('serialize stage', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var group = new Konva.Group(); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - draggable: true, - }); - - stage.add(layer); - layer.add(group); - group.add(circle); - layer.draw(); - - var expectedJson = - '{"attrs":{"width":578,"height":200},"className":"Stage","children":[{"attrs":{},"className":"Layer","children":[{"attrs":{},"className":"Group","children":[{"attrs":{"x":289,"y":100,"radius":70,"fill":"green","stroke":"black","strokeWidth":4,"name":"myCircle","draggable":true},"className":"Circle"}]}]}]}'; - - assert.equal(stage.toJSON(), expectedJson); - }); - - // ====================================================== - it('serialize shape', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var group = new Konva.Group(); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - draggable: true, - dash: [5, 5], - }); - - stage.add(layer); - layer.add(group); - group.add(circle); - layer.draw(); - - var expectedJson = - '{"attrs":{"x":289,"y":100,"radius":70,"fill":"green","stroke":"black","strokeWidth":4,"name":"myCircle","draggable":true,"dash":[5,5]},"className":"Circle"}'; - - assert.equal(circle.toJSON(), expectedJson); - }); - - // ====================================================== - it('serialize shape with custom attributes', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var group = new Konva.Group(); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - draggable: true, - }); - - stage.add(layer); - layer.add(group); - group.add(circle); - layer.draw(); - - circle.setAttr('customAttr', 3); - circle.setAttr('customAttrObj', { - x: 1, - y: 5, - size: { - width: 10, - height: 20, - }, - }); - - var expectedJson = - '{"attrs":{"x":289,"y":100,"radius":70,"fill":"green","stroke":"black","strokeWidth":4,"name":"myCircle","draggable":true,"customAttr":3,"customAttrObj":{"x":1,"y":5,"size":{"width":10,"height":20}}},"className":"Circle"}'; - - assert.equal(circle.toJSON(), expectedJson); - }); - - // ====================================================== - it('load stage using json', function () { - var container = addContainer(); - var json = - '{"attrs":{"width":578,"height":200},"className":"Stage","children":[{"attrs":{},"className":"Layer","children":[{"attrs":{},"className":"Group","children":[{"attrs":{"x":289,"y":100,"radius":70,"fill":"green","stroke":"black","strokeWidth":4,"name":"myCircle","draggable":true},"className":"Circle"}]}]}]}'; - var stage = Konva.Node.create(json, container); - - assert.equal(stage.toJSON(), json); - }); - - // ====================================================== - it('create node using object', function () { - var node = new Konva.Circle({ - id: 'test', - radius: 10, - }); - var clone = Konva.Node.create(node.toObject()); - - assert.deepEqual(node.toObject(), clone.toObject()); - }); - - it('make sure we can create non existing node type', function () { - var json = - '{"attrs":{},"className":"Layer","children":[{"attrs":{},"className":"Group","children":[{"attrs":{"x":289,"y":100,"radius":70,"fill":"green","stroke":"black","strokeWidth":4,"name":"myCircle","draggable":true},"className":"WeirdShape"}]}]}'; - var layer = Konva.Node.create(json); - - assert.deepEqual(layer.find('Shape').length, 1); - }); - - // ====================================================== - it('serialize stage with custom shape', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var group = new Konva.Group(); - - var drawTriangle = function (context) { - context.beginPath(); - context.moveTo(200, 50); - context.lineTo(420, 80); - context.quadraticCurveTo(300, 100, 260, 170); - context.closePath(); - context.fillStrokeShape(this); - }; - var triangle = new Konva.Shape({ - sceneFunc: drawTriangle, - fill: '#00D2FF', - stroke: 'black', - strokeWidth: 4, - id: 'myTriangle', - }); - - group.add(triangle); - layer.add(group); - stage.add(layer); - - assert.equal(triangle.id(), 'myTriangle'); - - var expectedJson = - '{"attrs":{"width":578,"height":200},"className":"Stage","children":[{"attrs":{},"className":"Layer","children":[{"attrs":{},"className":"Group","children":[{"attrs":{"fill":"#00D2FF","stroke":"black","strokeWidth":4,"id":"myTriangle"},"className":"Shape"}]}]}]}'; - - assert.equal(stage.toJSON(), expectedJson); - - layer.draw(); - }); - - // ====================================================== - it('load stage with custom shape using json', function () { - var container = addContainer(); - - var drawTriangle = function (context) { - context.beginPath(); - context.moveTo(200, 50); - context.lineTo(420, 80); - context.quadraticCurveTo(300, 100, 260, 170); - context.closePath(); - context.fillStrokeShape(this); - }; - var json = - '{"attrs":{"width":578,"height":200},"className":"Stage","children":[{"attrs":{},"className":"Layer","children":[{"attrs":{},"className":"Group","children":[{"attrs":{"fill":"#00D2FF","stroke":"black","strokeWidth":4,"id":"myTriangle1","customAttrObj":{"x":1,"y":5,"size":{"width":10,"height":20}}},"className":"Shape"}]}]}]}'; - - var stage = Konva.Node.create(json, container); - - stage.find('#myTriangle1').forEach(function (node) { - node.sceneFunc(drawTriangle); - }); - - stage.draw(); - - assert.equal(stage.toJSON(), json); - }); - - // ====================================================== - it('serialize stage with an image', function (done) { - loadImage('darth-vader.jpg', (imageObj) => { - var stage = addStage(); - var layer = new Konva.Layer(); - const darth = new Konva.Image({ - x: 200, - y: 60, - image: imageObj, - offset: { - x: 50, - y: imageObj.height / 2, - }, - id: 'darth', - }); - - layer.add(darth); - stage.add(layer); - var json = - '{"attrs":{"width":578,"height":200},"className":"Stage","children":[{"attrs":{},"className":"Layer","children":[{"attrs":{"x":200,"y":60,"offsetX":50,"offsetY":150,"id":"darth"},"className":"Image"}]}]}'; - - assert.equal(stage.toJSON(), json); - - done(); - }); - }); - - // ====================================================== - it('load stage with an image', function (done) { - var container = addContainer(); - loadImage('darth-vader.jpg', (imageObj) => { - var json = - '{"attrs":{"width":578,"height":200},"className":"Stage","children":[{"attrs":{},"className":"Layer","children":[{"attrs":{"x":200,"y":60,"offsetX":50,"offsetY":150,"id":"darth1"},"className":"Image"}]}]}'; - var stage = Konva.Node.create(json, container); - - assert.equal(stage.toJSON(), json); - stage.find('#darth1').forEach(function (node) { - node.setImage(imageObj); - }); - stage.draw(); - - done(); - }); - }); - - // ====================================================== - it('remove shape', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - }); - - layer.add(circle); - stage.add(layer); - - assert.equal(layer.children.length, 1); - - circle.remove(); - - assert.equal(layer.children.length, 0); - - layer.draw(); - - assert.equal(circle.getParent(), undefined); - }); - - // ====================================================== - it('destroy shape', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - }); - - layer.add(circle); - stage.add(layer); - - assert.equal(layer.children.length, 1); - - circle.destroy(); - - assert.equal(layer.children.length, 0); - - layer.draw(); - - assert.equal(circle.getParent(), undefined); - }); - - // ====================================================== - it('destroy shape without adding its parent to stage', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - id: 'myCircle', - }); - - layer.add(circle); - - var node = stage.find('#myCircle')[0]; - - assert.equal(node, undefined); - - circle.destroy(); - }); - - // ====================================================== - it('destroy layer with shape', function () { - var stage = addStage(); - var layer = new Konva.Layer({ - name: 'myLayer', - }); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - }); - - layer.add(circle); - stage.add(layer); - - assert.equal(stage.children.length, 1); - assert(stage.find('.myLayer')[0] !== undefined); - assert(stage.find('.myCircle')[0] !== undefined); - - layer.destroy(); - - assert.equal(stage.children.length, 0); - assert.equal(stage.find('.myLayer')[0], undefined); - assert.equal(stage.find('.myCircle')[0], undefined); - - stage.draw(); - }); - - // ====================================================== - it('destroy stage with layer and shape', function () { - var stage = addStage(); - var layer = new Konva.Layer({ - name: 'myLayer', - }); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - }); - - layer.add(circle); - stage.add(layer); - - stage.destroy(); - - assert.equal(layer.getParent(), undefined); - assert.equal(circle.getParent(), undefined); - assert.equal(stage.children.length, 0); - assert.equal(layer.children.length, 0); - }); - - // ====================================================== - it('destroy group with shape', function () { - var stage = addStage(); - var layer = new Konva.Layer({ - name: 'myLayer', - }); - var group = new Konva.Group({ - name: 'myGroup', - }); - - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - }); - - group.add(circle); - layer.add(group); - stage.add(layer); - - assert.equal(layer.getChildren().length, 1); - assert(stage.find('.myGroup')[0] !== undefined); - assert(stage.find('.myCircle')[0] !== undefined); - - group.destroy(); - - assert.equal(layer.children.length, 0); - assert.equal(stage.find('.myGroup')[0], undefined); - assert.equal(stage.find('.myCircle')[0], undefined); - - stage.draw(); - }); - - // ====================================================== - it('destroy layer with no shapes', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - layer.destroy(); - - assert.equal(stage.children.length, 0); - }); - - // ====================================================== - it('destroy shape multiple times', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var shape1 = new Konva.Circle({ - x: 150, - y: 100, - radius: 50, - fill: 'green', - name: 'myCircle', - }); - - var shape2 = new Konva.Circle({ - x: 250, - y: 100, - radius: 50, - fill: 'green', - name: 'myCircle', - }); - - layer.add(shape1); - layer.add(shape2); - stage.add(layer); - - assert.equal(layer.getChildren().length, 2); - - shape1.destroy(); - shape1.destroy(); - - assert.equal(layer.getChildren().length, 1); - - layer.draw(); - }); - - // ====================================================== - it('remove layer multiple times', function () { - var stage = addStage(); - var layer1 = new Konva.Layer(); - var layer2 = new Konva.Layer(); - - var shape1 = new Konva.Circle({ - x: 150, - y: 100, - radius: 50, - fill: 'green', - name: 'myCircle', - }); - - var shape2 = new Konva.Circle({ - x: 250, - y: 100, - radius: 50, - fill: 'green', - name: 'myCircle', - }); - - layer1.add(shape1); - layer2.add(shape2); - stage.add(layer1); - stage.add(layer2); - - assert.equal(stage.getChildren().length, 2); - - layer1.remove(); - layer1.remove(); - - assert.equal(stage.getChildren().length, 1); - - stage.draw(); - }); - - // ====================================================== - it('destroy shape by id or name', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - id: 'myCircle2', - }); - - var rect = new Konva.Rect({ - x: 300, - y: 100, - width: 100, - height: 50, - fill: 'purple', - stroke: 'black', - strokeWidth: 4, - name: 'myRect2', - }); - - var circleColorKey = circle.colorKey; - var rectColorKey = rect.colorKey; - - layer.add(circle); - layer.add(rect); - stage.add(layer); - - assert.equal(Konva.shapes[circleColorKey]._id, circle._id); - assert.equal(Konva.shapes[rectColorKey]._id, rect._id); - - circle.destroy(); - - assert.equal(Konva.shapes[circleColorKey], undefined); - assert.equal(Konva.shapes[rectColorKey]._id, rect._id); - - rect.destroy(); - - assert.equal(Konva.shapes[circleColorKey], undefined); - assert.equal(Konva.shapes[rectColorKey], undefined); - }); - // ====================================================== - it('hide stage', function () { - var stage = addStage({ - visible: false, - }); - var layer = new Konva.Layer(); - var group = new Konva.Group(); - - var rect = new Konva.Rect({ - x: 200, - y: 100, - width: 100, - height: 50, - fill: 'red', - stroke: 'black', - strokeWidth: 4, - draggable: true, - rotation: 60, - scale: { - x: 2, - y: 1, - }, - visible: false, - }); - - group.add(rect); - layer.add(group); - stage.add(layer); - - if (isBrowser) { - assert.equal(stage.content.style.display, 'none'); - } - - stage.show(); - stage.draw(); - if (isBrowser) { - assert.equal(stage.content.style.display, ''); - } - - stage.hide(); - stage.draw(); - if (isBrowser) { - assert.equal(stage.content.style.display, 'none'); - } - }); - - // ====================================================== - it('listening, & shouldDrawHit', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - - var rect = new Konva.Rect({ - x: 100, - y: 50, - width: 100, - height: 50, - fill: 'green', - stroke: 'blue', - }); - - layer.add(rect); - stage.add(layer); - - assert.equal(rect.isListening(), true); - assert.equal(rect.shouldDrawHit(), true); - - rect.listening(false); - - assert.equal(rect.isListening(), false); - assert.equal(rect.shouldDrawHit(), false); - }); - - // ====================================================== - it('group, listening, & shouldDrawHit', function () { - var stage = addStage(); - - var layer = new Konva.Layer({ - listening: false, - }); - stage.add(layer); - - var group = new Konva.Group({ - listening: false, - }); - layer.add(group); - - var rect = new Konva.Rect({ - x: 100, - y: 50, - width: 100, - height: 50, - fill: 'green', - stroke: 'blue', - listening: true, - }); - group.add(rect); - layer.draw(); - - showHit(layer); - - assert.equal(rect.isListening(), false); - assert.equal(rect.shouldDrawHit(), false); - - assert.equal(group.isListening(), false); - assert.equal(group.shouldDrawHit(), false, 'hit graph for group'); - - assert.equal(layer.isListening(), false); - assert.equal(layer.shouldDrawHit(), false, 'hit graph for layer'); - - var layerClick = 0; - var groupClick = 0; - var rectClick = 0; - - rect.on('click', function () { - rectClick += 1; - }); - group.on('click', function () { - groupClick += 1; - }); - layer.on('click', function () { - layerClick += 1; - }); - showHit(layer); - - simulateMouseDown(stage, { - x: 150, - y: 75, - }); - simulateMouseUp(stage, { - x: 150, - y: 75, - }); - - assert.equal(rectClick, 0, 'click on rectangle'); - assert.equal(groupClick, 0, 'no click on group'); - assert.equal(layerClick, 0, 'no click on layer'); - }); - - // ====================================================== - it('isVisible', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var group = new Konva.Group(); - var circle = new Konva.Circle({ - x: 100, - y: 100, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - draggable: true, - }); - - group.add(circle); - layer.add(group); - stage.add(layer); - - assert.equal(stage.isVisible(), true); - assert.equal(layer.isVisible(), true); - assert.equal(circle.isVisible(), true); - - stage.visible(false); - - assert.equal(stage.isVisible(), false); - assert.equal(layer.isVisible(), false); - assert.equal(circle.isVisible(), false); - - stage.visible(true); - layer.visible(false); - - assert.equal(stage.isVisible(), true); - assert.equal(layer.isVisible(), false); - assert.equal(circle.isVisible(), false); - - layer.visible(true); - circle.visible(false); - - assert.equal(stage.isVisible(), true); - assert.equal(layer.isVisible(), true); - assert.equal(circle.isVisible(), false); - - circle.visible(true); - stage.visible(true); - - assert.equal(stage.isVisible(), true); - assert.equal(layer.isVisible(), true); - assert.equal(circle.isVisible(), true); - - stage.visible(true); - layer.visible(true); - - assert.equal(stage.isVisible(), true); - assert.equal(layer.isVisible(), true); - assert.equal(circle.isVisible(), true); - - layer.visible(true); - circle.visible(true); - - assert.equal(stage.isVisible(), true); - assert.equal(layer.isVisible(), true); - assert.equal(circle.isVisible(), true); - }); - - it('overloaders', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var group = new Konva.Group(); - var circle = new Konva.Circle({ - x: 100, - y: 100, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - draggable: true, - }); - - group.add(circle); - layer.add(group); - stage.add(layer); - - circle.x(1); - assert.equal(circle.x(), 1); - - circle.y(2); - assert.equal(circle.y(), 2); - - circle.opacity(0.5); - assert.equal(circle.opacity(), 0.5); - - circle.name('foo'); - assert.equal(circle.name(), 'foo'); - - circle.id('bar'); - assert.equal(circle.id(), 'bar'); - - circle.rotation(2); - assert.equal(circle.rotation(), 2); - - circle.scale({ x: 2, y: 2 }); - assert.equal(circle.scale().x, 2); - assert.equal(circle.scale().y, 2); - - circle.scaleX(5); - assert.equal(circle.scaleX(), 5); - - circle.scaleY(8); - assert.equal(circle.scaleY(), 8); - - circle.skew({ x: 2, y: 2 }); - assert.equal(circle.skew().x, 2); - assert.equal(circle.skew().y, 2); - - circle.skewX(5); - assert.equal(circle.skewX(), 5); - - circle.skewY(8); - assert.equal(circle.skewY(), 8); - - circle.offset({ x: 2, y: 2 }); - assert.equal(circle.offset().x, 2); - assert.equal(circle.offset().y, 2); - - circle.offsetX(5); - assert.equal(circle.offsetX(), 5); - - circle.offsetY(8); - assert.equal(circle.offsetY(), 8); - - circle.width(23); - assert.equal(circle.width(), 23); - - circle.height(11); - assert.equal(circle.height(), 11); - - circle.listening(false); - assert.equal(circle.listening(), false); - - circle.visible(false); - assert.equal(circle.visible(), false); - - // circle.transformsEnabled(false); - // assert.equal(circle.transformsEnabled(), false); - - circle.position({ x: 6, y: 8 }); - assert.equal(circle.position().x, 6); - assert.equal(circle.position().y, 8); - - // because the height was set to 11, the width - // is also 11 because the node is a circle - assert.equal(circle.size().width, 11); - assert.equal(circle.size().height, 11); - }); - - it('overloaders reset', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - radius: 70, - fill: 'green', - }); - - layer.add(circle); - stage.add(layer); - - circle.scale({ x: 2, y: 2 }); - - circle.scale(undefined); - - assert.equal(circle.scaleX(), 1); - assert.equal(circle.scaleY(), 1); - }); - - it('cache shape', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var group = new Konva.Group(); - var circle = new Konva.Circle({ - x: 74, - y: 74, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - draggable: true, - }); - - group.add(circle); - layer.add(group); - stage.add(layer); - - assert.equal(circle._getCanvasCache(), undefined); - - circle - .cache({ - x: -74, - y: -74, - width: 148, - height: 148, - }) - .offset({ - x: 74, - y: 74, - }); - - assert.notEqual(circle._getCanvasCache().scene, undefined); - assert.notEqual(circle._getCanvasCache().hit, undefined); - - layer.draw(); - - //document.body.appendChild(circle._getCanvasCache().scene._canvas); - // document.body.appendChild(circle._getCanvasCache().hit._canvas); - - showHit(layer); - - //assert.equal(layer.getContext().getTrace(), 'clearRect(0,0,578,200);save();transform(1,0,0,1,74,74);beginPath();arc(0,0,70,0,6.283,false);closePath();fillStyle=green;fill();lineWidth=4;strokeStyle=black;stroke();restore();clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);drawImage([object HTMLCanvasElement],0,0);restore();'); - - //assert.equal(circle._getCanvasCache().scene.getContext().getTrace(), 'save();translate(74,74);beginPath();arc(0,0,70,0,6.283,false);closePath();fillStyle=green;fill();lineWidth=4;strokeStyle=black;stroke();restore();'); - }); - - it('cache shape before adding to layer', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var group = new Konva.Group({ - x: 0, - y: 0, - }); - var rect = new Konva.Rect({ - x: 35, - y: 35, - width: 50, - height: 50, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - offsetX: 25, - offsetY: 25, - rotation: 45, - draggable: true, - }); - group.add(rect); - - assert.equal(rect._getCanvasCache(), undefined); - group.cache({ - x: 0, - y: 0, - width: 148, - height: 148, - }); - stage.add(layer); - - assert(group._getCanvasCache().scene); - assert(group._getCanvasCache().hit); - - layer.add(group); - layer.draw(); - showHit(layer); - var shape = stage.getIntersection({ - x: 5, - y: 5, - }); - assert(!shape, 'no shape (rotate applied)'); - shape = stage.getIntersection({ - x: 35, - y: 35, - }); - assert.equal(shape, rect, 'rect found'); - }); - - it('stage.toObject() when stage contains an image', function (done) { - loadImage('darth-vader.jpg', (imageObj) => { - var stage = addStage(); - - var layer = new Konva.Layer(); - const darth = new Konva.Image({ - x: 200, - y: 60, - image: imageObj, - width: 100, - height: 100, - offset: { x: 50, y: 30 }, - crop: { x: 135, y: 7, width: 167, height: 134 }, - draggable: true, - }); - - layer.add(darth); - stage.add(layer); - - assert.equal(stage.toObject().className, 'Stage'); - - done(); - }); - }); - - it('toObject with extended prototypes', function () { - var node = new Konva.Circle({ - id: 'foo', - radius: 10, - }); - Number.prototype['customFunc'] = function () {}; - assert.equal(node.toObject().attrs.radius, 10); - delete Number.prototype['customFunc']; - }); - - it('toObject with property in attrs and instanse', function () { - var node = new Konva.Circle({ - id: 'foo1', - radius: 10, - filled: true, - }); - node['filled'] = true; - assert.equal(node.toObject().attrs.filled, true); - }); - - it('test findAncestor', function () { - var stage = addStage(); - stage.setAttrs({ - name: 'stage', - id: 'stage', - }); - var layer = new Konva.Layer({ - id: 'layer', - name: 'layer', - }); - stage.add(layer); - - var group = new Konva.Group({ - id: 'group', - name: 'group', - }); - layer.add(group); - - var rect = new Konva.Rect({ - x: 35, - y: 35, - width: 50, - height: 50, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'rect', - }); - group.add(rect); - - stage.draw(); - - assert.equal(!!rect.findAncestor('.null'), false); - - assert.notEqual( - rect.findAncestor('.rect'), - rect, - 'do not find self in ancestors' - ); - - assert.equal(rect.findAncestor('.stage'), stage, 'find stage by name'); - assert.equal(rect.findAncestor('#stage'), stage, 'find stage by id'); - assert.equal(rect.findAncestor('Stage'), stage, 'find stage by node type'); - - assert.equal(rect.findAncestor('.layer'), layer); - assert.equal(rect.findAncestor('#layer'), layer); - assert.equal(rect.findAncestor('Layer'), layer); - - assert.equal(rect.findAncestor('.group'), group); - assert.equal(rect.findAncestor('#group'), group); - assert.equal(rect.findAncestor('Group'), group); - - assert.equal(rect.findAncestor(), null, 'return null if no selector'); - }); - - it('moveToTop() properly changes z-indices of the node and its siblings', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect1 = new Konva.Rect(); - var rect2 = new Konva.Rect(); - var rect3 = new Konva.Rect(); - var rect4 = new Konva.Rect(); - layer.add(rect1, rect2, rect3, rect4); - - assert.equal(rect1.getZIndex(), 0); - assert.equal(rect2.getZIndex(), 1); - assert.equal(rect3.getZIndex(), 2); - assert.equal(rect4.getZIndex(), 3); - - rect2.moveToTop(); - - assert.equal(rect1.getZIndex(), 0); - assert.equal(rect3.getZIndex(), 1); - assert.equal(rect4.getZIndex(), 2); - assert.equal(rect2.getZIndex(), 3); - - rect1.moveToTop(); - - assert.equal(rect3.getZIndex(), 0); - assert.equal(rect4.getZIndex(), 1); - assert.equal(rect2.getZIndex(), 2); - assert.equal(rect1.getZIndex(), 3); - }); - - it('moveToBottom() properly changes z-indices of the node and its siblings', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect1 = new Konva.Rect(); - var rect2 = new Konva.Rect(); - var rect3 = new Konva.Rect(); - var rect4 = new Konva.Rect(); - layer.add(rect1, rect2, rect3, rect4); - - assert.equal(rect1.getZIndex(), 0); - assert.equal(rect2.getZIndex(), 1); - assert.equal(rect3.getZIndex(), 2); - assert.equal(rect4.getZIndex(), 3); - - rect3.moveToBottom(); - - assert.equal(rect3.getZIndex(), 0); - assert.equal(rect1.getZIndex(), 1); - assert.equal(rect2.getZIndex(), 2); - assert.equal(rect4.getZIndex(), 3); - - rect4.moveToBottom(); - - assert.equal(rect4.getZIndex(), 0); - assert.equal(rect3.getZIndex(), 1); - assert.equal(rect1.getZIndex(), 2); - assert.equal(rect2.getZIndex(), 3); - }); - - it('moveUp() properly changes z-indices of the node and its siblings', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect1 = new Konva.Rect(); - var rect2 = new Konva.Rect(); - var rect3 = new Konva.Rect(); - var rect4 = new Konva.Rect(); - layer.add(rect1, rect2, rect3, rect4); - - assert.equal(rect1.getZIndex(), 0); - assert.equal(rect2.getZIndex(), 1); - assert.equal(rect3.getZIndex(), 2); - assert.equal(rect4.getZIndex(), 3); - - rect1.moveUp(); - - assert.equal(rect2.getZIndex(), 0); - assert.equal(rect1.getZIndex(), 1); - assert.equal(rect3.getZIndex(), 2); - assert.equal(rect4.getZIndex(), 3); - - rect3.moveUp(); - - assert.equal(rect2.getZIndex(), 0); - assert.equal(rect1.getZIndex(), 1); - assert.equal(rect4.getZIndex(), 2); - assert.equal(rect3.getZIndex(), 3); - }); - - it('moveDown() properly changes z-indices of the node and its siblings', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect1 = new Konva.Rect(); - var rect2 = new Konva.Rect(); - var rect3 = new Konva.Rect(); - var rect4 = new Konva.Rect(); - layer.add(rect1, rect2, rect3, rect4); - - assert.equal(rect1.getZIndex(), 0); - assert.equal(rect2.getZIndex(), 1); - assert.equal(rect3.getZIndex(), 2); - assert.equal(rect4.getZIndex(), 3); - - rect4.moveDown(); - - assert.equal(rect1.getZIndex(), 0); - assert.equal(rect2.getZIndex(), 1); - assert.equal(rect4.getZIndex(), 2); - assert.equal(rect3.getZIndex(), 3); - - rect2.moveDown(); - - assert.equal(rect2.getZIndex(), 0); - assert.equal(rect1.getZIndex(), 1); - assert.equal(rect4.getZIndex(), 2); - assert.equal(rect3.getZIndex(), 3); - }); - - it('setZIndex() properly changes z-indices of the node and its siblings', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect1 = new Konva.Rect(); - var rect2 = new Konva.Rect(); - var rect3 = new Konva.Rect(); - var rect4 = new Konva.Rect(); - layer.add(rect1, rect2, rect3, rect4); - - assert.equal(rect1.getZIndex(), 0); - assert.equal(rect2.getZIndex(), 1); - assert.equal(rect3.getZIndex(), 2); - assert.equal(rect4.getZIndex(), 3); - - rect1.setZIndex(2); - - assert.equal(rect2.getZIndex(), 0); - assert.equal(rect3.getZIndex(), 1); - assert.equal(rect1.getZIndex(), 2); - assert.equal(rect4.getZIndex(), 3); - - rect2.setZIndex(3); - - assert.equal(rect3.getZIndex(), 0); - assert.equal(rect1.getZIndex(), 1); - assert.equal(rect4.getZIndex(), 2); - assert.equal(rect2.getZIndex(), 3); - - rect2.setZIndex(1); - - assert.equal(rect3.getZIndex(), 0); - assert.equal(rect2.getZIndex(), 1); - assert.equal(rect1.getZIndex(), 2); - assert.equal(rect4.getZIndex(), 3); - - rect4.setZIndex(0); - - assert.equal(rect4.getZIndex(), 0); - assert.equal(rect3.getZIndex(), 1); - assert.equal(rect2.getZIndex(), 2); - assert.equal(rect1.getZIndex(), 3); - }); - - it('remove() removes the node and properly changes z-indices of its siblings', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect1 = new Konva.Rect(); - var rect2 = new Konva.Rect(); - var rect3 = new Konva.Rect(); - var rect4 = new Konva.Rect(); - layer.add(rect1, rect2, rect3, rect4); - - assert.equal(layer.getChildren().length, 4); - assert.equal(rect1.getZIndex(), 0); - assert.equal(rect2.getZIndex(), 1); - assert.equal(rect3.getZIndex(), 2); - assert.equal(rect4.getZIndex(), 3); - - rect4.remove(); - - assert.equal(layer.getChildren().length, 3); - assert.equal(rect1.getZIndex(), 0); - assert.equal(rect2.getZIndex(), 1); - assert.equal(rect3.getZIndex(), 2); - - rect2.remove(); - - assert.equal(layer.getChildren().length, 2); - assert.equal(rect1.getZIndex(), 0); - assert.equal(rect3.getZIndex(), 1); - - rect1.remove(); - - assert.equal(layer.getChildren().length, 1); - assert.equal(rect3.getZIndex(), 0); - }); - - it('show warning when we are trying to use non-objects for component setters', function () { - if (!Konva.isUnminified) { - return; - } - var stage = addStage(); - var callCount = 0; - var oldWarn = Konva.Util.warn; - Konva.Util.warn = function () { - callCount += 1; - }; - - stage.scale(1 as any); - assert.equal(callCount, 1); - Konva.Util.warn = oldWarn; - }); - - it('show warning for unexpected zIndexes', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - stage.add(layer); - - var shape = new Konva.Circle({ - radius: 50, - fill: 'red', - }); - layer.add(shape); - - var callCount = 0; - var oldWarn = Konva.Util.warn; - Konva.Util.warn = function () { - callCount += 1; - }; - - shape.zIndex(-1); - shape.zIndex(0); - shape.zIndex(10); - - assert.equal(callCount, 2); - Konva.Util.warn = oldWarn; - }); - - it('check transform caching', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - - var rect = new Konva.Rect({ - x: 50, - y: 50, - width: 150, - height: 50, - stroke: 'black', - strokeWidth: 4, - }); - - var text00 = new Konva.Text({ - text: 'Sample text', - x: 50, - y: 50, - fontSize: 20, - }); - - layer.add(rect); - layer.add(text00); - - stage.add(layer); - - stage.x(+40); - stage.y(+40); - - stage.draw(); - - layer.removeChildren(); - text00.getClientRect({}); // Commenting this line or putting it after line 41 puts text in rectangle - - layer.add(text00); - layer.add(rect); - - stage.draw(); - - assert.equal(text00.getClientRect().x, 90); - assert.equal(text00.getClientRect().y, 90); - }); - - // ====================================================== - it('isClientRectOnScreen() method', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 30, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - }); - - assert.equal( - circle.isClientRectOnScreen(), - false, - 'not visible when not on stage' - ); - - layer.add(circle); - stage.add(layer); - - assert.equal(circle.isClientRectOnScreen(), true); - - circle.x(-circle.radius() - circle.strokeWidth() / 2 - 1); // Move circle 1px outside of visible area - assert.equal(circle.isClientRectOnScreen(), false); - assert.equal(circle.isClientRectOnScreen({ x: 10, y: 0 }), true); - assert.equal(circle.isClientRectOnScreen({ x: 0, y: 10 }), false); - - stage.x(10); - assert.equal(circle.isClientRectOnScreen(), true); - }); - - // ====================================================== - it('getRelativePointerPosition() method', function () { - var stage = addStage(); - var layer = new Konva.Layer({ - scaleX: 2, - }); - stage.add(layer); - var circle = new Konva.Circle({ - x: 100, - y: 100, - radius: 30, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - }); - layer.add(circle); - - simulateMouseMove(stage, { - x: 100, - y: 100, - }); - - assert.equal(circle.getRelativePointerPosition().x, -50); - assert.equal(circle.getRelativePointerPosition().y, 0); - }); -}); diff --git a/test/unit/Path-test.ts b/test/unit/Path-test.ts deleted file mode 100644 index 4b3501f67..000000000 --- a/test/unit/Path-test.ts +++ /dev/null @@ -1,1669 +0,0 @@ -import { assert } from 'chai'; - -import worldMap from '../assets/worldMap'; -import tiger from '../assets/tiger'; - -import { - addStage, - Konva, - createCanvas, - compareLayerAndCanvas, - cloneAndCompareLayer, - isNode, - assertAlmostDeepEqual, - isBrowser, -} from './test-utils'; - -describe('Path', function () { - // ====================================================== - it('add simple path', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - - var path = new Konva.Path({ - data: 'M200,100h100v50z', - fill: '#ccc', - stroke: '#333', - strokeWidth: 2, - shadowColor: 'black', - shadowBlur: 2, - shadowOffset: { x: 10, y: 10 }, - shadowOpacity: 0.5, - draggable: true, - }); - - path.on('mouseover', function () { - this.fill('red'); - layer.draw(); - }); - - path.on('mouseout', function () { - this.fill('#ccc'); - layer.draw(); - }); - - layer.add(path); - - stage.add(layer); - - assert.equal(path.data(), 'M200,100h100v50z'); - assert.equal(path.dataArray.length, 4); - - path.data('M200'); - - assert.equal(path.data(), 'M200'); - assert.equal(path.dataArray.length, 1); - - path.data('M200,100h100v50z'); - - assert.equal(path.getClassName(), 'Path'); - }); - - // ====================================================== - it('add path with line cap and line join', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var path = new Konva.Path({ - data: 'M200,100h100v50', - stroke: '#333', - strokeWidth: 20, - draggable: true, - lineCap: 'round', - lineJoin: 'round', - }); - - layer.add(path); - - stage.add(layer); - - var trace = layer.getContext().getTrace(); - - assert.equal( - trace, - 'clearRect(0,0,578,200);save();lineJoin=round;transform(1,0,0,1,0,0);beginPath();moveTo(200,100);lineTo(300,100);lineTo(300,150);lineCap=round;lineWidth=20;strokeStyle=#333;stroke();restore();' - ); - }); - - //======================================================= - it('add path with double closed path and releative moveto', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var path = new Konva.Path({ - data: 'm 4.114181,28.970898 8.838835,50.205 22.980968,-48.4372 z m -4.59619304,13.7887 0,18.385 c 14.10943004,-12.211 24.57748204,-6.2149 35.00178204,0 l 2.304443,-8.6004 -13.264598,0 c 2.227131,-5.4642 7.171257,-11.834 -6.858423,-11.8792 -3.920218,12.899 -9.493066,14.6427 -17.18320404,2.0946 z', - stroke: '#000', - strokeWidth: 1, - lineCap: 'round', - lineJoin: 'round', - }); - - layer.add(path); - - stage.add(layer); - - var trace = layer.getContext().getTrace(); - - assert.equal( - trace, - 'clearRect(0,0,578,200);save();lineJoin=round;transform(1,0,0,1,0,0);beginPath();moveTo(4.114,28.971);lineTo(12.953,79.176);lineTo(35.934,30.739);closePath();moveTo(-0.482,42.76);lineTo(-0.482,61.145);bezierCurveTo(13.627,48.934,24.095,54.93,34.52,61.145);lineTo(36.824,52.544);lineTo(23.56,52.544);bezierCurveTo(25.787,47.08,30.731,40.71,16.701,40.665);bezierCurveTo(12.781,53.564,7.208,55.308,-0.482,42.76);closePath();lineCap=round;lineWidth=1;strokeStyle=#000;stroke();restore();' - ); - }); - - //======================================================= - it('complex path made of many different closed and open paths (Sopwith Camel)', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var path = new Konva.Path({ - data: 'm 15.749277,58.447629 8.495831,-0.05348 m 0.319898,-15.826548 -0.202438,17.295748 0.942206,0.941911 1.345933,-1.816987 0.20211,-11.642611 z m 77.458374,28.680768 c 0,5.308829 -4.303525,9.612686 -9.612485,9.612686 -5.30873,0 -9.612194,-4.303857 -9.612194,-9.612686 0,-5.308829 4.303464,-9.61226 9.612194,-9.61226 5.30896,0 9.612485,4.303431 9.612485,9.61226 z m -3.520874,0 c 0,3.364079 -2.72763,6.091348 -6.091611,6.091348 -3.364243,0 -6.091119,-2.727269 -6.091119,-6.091348 0,-3.363719 2.726876,-6.090791 6.091119,-6.090791 3.363981,0 6.091611,2.727072 6.091611,6.090791 z m -3.997576,0 c 0,1.156718 -0.937743,2.093937 -2.094035,2.093937 -1.156062,0 -2.093871,-0.937219 -2.093871,-2.093937 0,-1.156357 0.937809,-2.093773 2.093871,-2.093773 1.156292,0 2.094035,0.937416 2.094035,2.093773 z m 45.77821,4.283023 c -0.24607,1.90039 5.06492,3.680204 7.61403,5.520093 0.50662,0.514199 0.27889,0.975967 -0.0984,1.427532 l 3.9019,-1.141987 c -0.59258,-0.121397 -1.85951,0.01969 -1.71294,-0.380038 -0.85894,-1.950525 -3.68693,-2.761261 -5.61518,-4.092495 -1.06971,-1.03496 0.0997,-1.60766 0.76126,-2.284203 z M 43.206396,42.60133 55.578964,74.008743 58.71987,73.910313 47.203939,44.40726 c -1.109013,-0.737406 -1.174108,-2.1004 -3.997543,-1.808752 z m -18.654022,-0.570632 12.467721,31.692335 3.140643,0.09843 -12.467656,-31.692927 z m 2.285318,42.353106 -2.636648,-0.06431 0.163066,0.734584 3.709372,9.956142 2.357927,-1.168202 z m 19.411934,0.566268 -6.370726,9.901284 2.090163,1.615665 7.13671,-11.417403 0.303821,-0.4347 -2.942667,-0.02953 z m -12.091915,8.286013 c -5.729323,0 -10.367941,4.560169 -10.367941,10.184405 0,5.62429 4.638618,10.18489 10.367941,10.18489 5.729424,0 10.37654,-4.5606 10.37654,-10.18489 0,-5.624236 -4.647083,-10.184405 -10.37654,-10.184405 z m 0,2.473319 c 4.310029,0 7.811352,3.453552 7.811352,7.711086 0,4.25776 -3.50129,7.71167 -7.811352,7.71167 -4.310157,0 -7.803016,-3.45391 -7.803016,-7.71167 0,-4.257534 3.492859,-7.711086 7.803016,-7.711086 z m 3.528526,-21.795876 c -1.29032,-0.0066 -2.97525,0.03839 -3.402437,1.45155 l -0.01969,7.494437 c 0.586775,0.761915 1.42432,0.688978 2.236565,0.71411 l 26.529545,-0.14502 8.636784,0.761324 0,-7.518487 C 71.56989,75.908478 71.09444,75.467051 70.239377,75.338961 61.126027,73.734287 49.244756,73.929146 37.690371,73.911166 z M 20.959576,41.269176 c -0.0098,0.603377 0.575258,0.881409 0.575258,0.881409 L 58.95771,42.33629 c -4.893946,-0.985482 -16.592629,-2.859625 -32.835015,-2.783473 -1.570354,0.107617 -5.151439,1.109571 -5.163119,1.712718 z m 3.353022,14.276273 c -2.79955,0.01312 -5.595489,0.02953 -8.382964,0.05545 l 0,9.9e-5 0.0033,1.447677 -1.173484,0.01312 0.0066,1.244485 1.184048,0.05807 c -1.34298,0.220812 -2.956414,1.305807 -3.054779,3.476618 0.0098,3.269061 0.01312,6.538943 0.01312,9.808103 l -1.21197,0.0033 -0.01969,-2.361569 -4.6851755,0.0033 0,5.901969 4.6323185,0.0066 -0.02953,-1.7556 1.308596,-0.02297 0.0098,9.180447 c -0.0066,1.315781 2.739048,3.634336 4.542583,3.634336 l 4.811756,-2.995032 c 1.616583,-0.107617 1.758126,0.482078 1.884346,1.076924 l 35.667571,0.318914 6.909664,-0.81031 m 4.994738,-0.585889 85.216614,-9.991675 c 4.93952,-0.487623 14.9162,-22.255511 -3.75098,-25.556727 -5.12814,-0.887479 -15.53194,4.839613 -21.44018,9.104984 -2.31314,1.954593 -1.74166,4.084194 0.0263,5.982879 l -72.209399,-8.111923 -2.12281,-0.0012 c -0.966453,1.390128 -3.158262,3.260465 -4.554559,4.053123 M 49.36027,58.361483 c -1.699757,-1.038536 -2.965602,-2.804438 -4.533856,-2.875275 -3.903936,0.0011 -7.904399,0.0066 -11.882849,0.01312 m -3.081192,0.0066 c -1.043195,0.0033 -2.082715,0.0066 -3.116396,0.0098', - stroke: '#000', - strokeWidth: 1, - lineCap: 'round', - lineJoin: 'round', - }); - - layer.add(path); - - stage.add(layer); - - var trace = layer.getContext().getTrace(); - - assert.equal( - trace, - 'bezierCurveTo(140.037,77.432,145.348,79.212,147.897,81.051);bezierCurveTo(148.404,81.566,148.176,82.027,147.799,82.479);lineTo(151.701,81.337);bezierCurveTo(151.108,81.216,149.841,81.357,149.988,80.957);bezierCurveTo(149.129,79.006,146.301,78.196,144.373,76.864);bezierCurveTo(143.303,75.83,144.472,75.257,145.134,74.58);closePath();moveTo(43.206,42.601);lineTo(55.579,74.009);lineTo(58.72,73.91);lineTo(47.204,44.407);bezierCurveTo(46.095,43.67,46.03,42.307,43.206,42.599);closePath();moveTo(24.552,42.031);lineTo(37.02,73.723);lineTo(40.161,73.821);lineTo(27.693,42.129);closePath();moveTo(26.838,84.384);lineTo(24.201,84.319);lineTo(24.364,85.054);lineTo(28.073,95.01);lineTo(30.431,93.842);closePath();moveTo(46.25,84.95);lineTo(39.879,94.851);lineTo(41.969,96.467);lineTo(49.106,85.05);lineTo(49.41,84.615);lineTo(46.467,84.585);closePath();moveTo(34.158,93.236);bezierCurveTo(28.428,93.236,23.79,97.796,23.79,103.42);bezierCurveTo(23.79,109.045,28.428,113.605,34.158,113.605);bezierCurveTo(39.887,113.605,44.534,109.045,44.534,103.42);bezierCurveTo(44.534,97.796,39.887,93.236,34.158,93.236);closePath();moveTo(34.158,95.709);bezierCurveTo(38.468,95.709,41.969,99.163,41.969,103.42);bezierCurveTo(41.969,107.678,38.468,111.132,34.158,111.132);bezierCurveTo(29.848,111.132,26.355,107.678,26.355,103.42);bezierCurveTo(26.355,99.163,29.848,95.709,34.158,95.709);closePath();moveTo(37.686,73.914);bezierCurveTo(36.396,73.907,34.711,73.952,34.284,75.365);lineTo(34.264,82.86);bezierCurveTo(34.851,83.621,35.688,83.548,36.501,83.574);lineTo(63.03,83.429);lineTo(71.667,84.19);lineTo(71.667,76.671);bezierCurveTo(71.57,75.908,71.094,75.467,70.239,75.339);bezierCurveTo(61.126,73.734,49.245,73.929,37.69,73.911);closePath();moveTo(20.96,41.269);bezierCurveTo(20.95,41.873,21.535,42.151,21.535,42.151);lineTo(58.958,42.336);bezierCurveTo(54.064,41.351,42.365,39.477,26.123,39.553);bezierCurveTo(24.552,39.66,20.971,40.662,20.96,41.266);closePath();moveTo(24.313,55.545);bezierCurveTo(21.513,55.559,18.717,55.575,15.93,55.601);lineTo(15.93,55.601);lineTo(15.933,57.049);lineTo(14.759,57.062);lineTo(14.766,58.306);lineTo(15.95,58.364);bezierCurveTo(14.607,58.585,12.994,59.67,12.895,61.841);bezierCurveTo(12.905,65.11,12.908,68.38,12.908,71.649);lineTo(11.696,71.652);lineTo(11.677,69.291);lineTo(6.992,69.294);lineTo(6.992,75.196);lineTo(11.624,75.203);lineTo(11.594,73.447);lineTo(12.903,73.424);lineTo(12.913,82.605);bezierCurveTo(12.906,83.92,15.652,86.239,17.455,86.239);lineTo(22.267,83.244);bezierCurveTo(23.884,83.136,24.025,83.726,24.151,84.321);lineTo(59.819,84.64);lineTo(66.729,83.829);moveTo(71.723,83.243);lineTo(156.94,73.252);bezierCurveTo(161.88,72.764,171.856,50.996,153.189,47.695);bezierCurveTo(148.061,46.808,137.657,52.535,131.749,56.8);bezierCurveTo(129.436,58.755,130.007,60.884,131.775,62.783);lineTo(59.566,54.671);lineTo(57.443,54.67);bezierCurveTo(56.477,56.06,54.285,57.93,52.888,58.723);moveTo(49.36,58.361);bezierCurveTo(47.661,57.323,46.395,55.557,44.826,55.486);bezierCurveTo(40.922,55.487,36.922,55.493,32.944,55.499);moveTo(29.862,55.506);bezierCurveTo(28.819,55.509,27.78,55.513,26.746,55.516);lineCap=round;lineWidth=1;strokeStyle=#000;stroke();restore();' - ); - }); - - //======================================================= - it('complex path made of many different closed and open paths (Sopwith Camel) cached', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var path = new Konva.Path({ - data: 'm 15.749277,58.447629 8.495831,-0.05348 m 0.319898,-15.826548 -0.202438,17.295748 0.942206,0.941911 1.345933,-1.816987 0.20211,-11.642611 z m 77.458374,28.680768 c 0,5.308829 -4.303525,9.612686 -9.612485,9.612686 -5.30873,0 -9.612194,-4.303857 -9.612194,-9.612686 0,-5.308829 4.303464,-9.61226 9.612194,-9.61226 5.30896,0 9.612485,4.303431 9.612485,9.61226 z m -3.520874,0 c 0,3.364079 -2.72763,6.091348 -6.091611,6.091348 -3.364243,0 -6.091119,-2.727269 -6.091119,-6.091348 0,-3.363719 2.726876,-6.090791 6.091119,-6.090791 3.363981,0 6.091611,2.727072 6.091611,6.090791 z m -3.997576,0 c 0,1.156718 -0.937743,2.093937 -2.094035,2.093937 -1.156062,0 -2.093871,-0.937219 -2.093871,-2.093937 0,-1.156357 0.937809,-2.093773 2.093871,-2.093773 1.156292,0 2.094035,0.937416 2.094035,2.093773 z m 45.77821,4.283023 c -0.24607,1.90039 5.06492,3.680204 7.61403,5.520093 0.50662,0.514199 0.27889,0.975967 -0.0984,1.427532 l 3.9019,-1.141987 c -0.59258,-0.121397 -1.85951,0.01969 -1.71294,-0.380038 -0.85894,-1.950525 -3.68693,-2.761261 -5.61518,-4.092495 -1.06971,-1.03496 0.0997,-1.60766 0.76126,-2.284203 z M 43.206396,42.60133 55.578964,74.008743 58.71987,73.910313 47.203939,44.40726 c -1.109013,-0.737406 -1.174108,-2.1004 -3.997543,-1.808752 z m -18.654022,-0.570632 12.467721,31.692335 3.140643,0.09843 -12.467656,-31.692927 z m 2.285318,42.353106 -2.636648,-0.06431 0.163066,0.734584 3.709372,9.956142 2.357927,-1.168202 z m 19.411934,0.566268 -6.370726,9.901284 2.090163,1.615665 7.13671,-11.417403 0.303821,-0.4347 -2.942667,-0.02953 z m -12.091915,8.286013 c -5.729323,0 -10.367941,4.560169 -10.367941,10.184405 0,5.62429 4.638618,10.18489 10.367941,10.18489 5.729424,0 10.37654,-4.5606 10.37654,-10.18489 0,-5.624236 -4.647083,-10.184405 -10.37654,-10.184405 z m 0,2.473319 c 4.310029,0 7.811352,3.453552 7.811352,7.711086 0,4.25776 -3.50129,7.71167 -7.811352,7.71167 -4.310157,0 -7.803016,-3.45391 -7.803016,-7.71167 0,-4.257534 3.492859,-7.711086 7.803016,-7.711086 z m 3.528526,-21.795876 c -1.29032,-0.0066 -2.97525,0.03839 -3.402437,1.45155 l -0.01969,7.494437 c 0.586775,0.761915 1.42432,0.688978 2.236565,0.71411 l 26.529545,-0.14502 8.636784,0.761324 0,-7.518487 C 71.56989,75.908478 71.09444,75.467051 70.239377,75.338961 61.126027,73.734287 49.244756,73.929146 37.690371,73.911166 z M 20.959576,41.269176 c -0.0098,0.603377 0.575258,0.881409 0.575258,0.881409 L 58.95771,42.33629 c -4.893946,-0.985482 -16.592629,-2.859625 -32.835015,-2.783473 -1.570354,0.107617 -5.151439,1.109571 -5.163119,1.712718 z m 3.353022,14.276273 c -2.79955,0.01312 -5.595489,0.02953 -8.382964,0.05545 l 0,9.9e-5 0.0033,1.447677 -1.173484,0.01312 0.0066,1.244485 1.184048,0.05807 c -1.34298,0.220812 -2.956414,1.305807 -3.054779,3.476618 0.0098,3.269061 0.01312,6.538943 0.01312,9.808103 l -1.21197,0.0033 -0.01969,-2.361569 -4.6851755,0.0033 0,5.901969 4.6323185,0.0066 -0.02953,-1.7556 1.308596,-0.02297 0.0098,9.180447 c -0.0066,1.315781 2.739048,3.634336 4.542583,3.634336 l 4.811756,-2.995032 c 1.616583,-0.107617 1.758126,0.482078 1.884346,1.076924 l 35.667571,0.318914 6.909664,-0.81031 m 4.994738,-0.585889 85.216614,-9.991675 c 4.93952,-0.487623 14.9162,-22.255511 -3.75098,-25.556727 -5.12814,-0.887479 -15.53194,4.839613 -21.44018,9.104984 -2.31314,1.954593 -1.74166,4.084194 0.0263,5.982879 l -72.209399,-8.111923 -2.12281,-0.0012 c -0.966453,1.390128 -3.158262,3.260465 -4.554559,4.053123 M 49.36027,58.361483 c -1.699757,-1.038536 -2.965602,-2.804438 -4.533856,-2.875275 -3.903936,0.0011 -7.904399,0.0066 -11.882849,0.01312 m -3.081192,0.0066 c -1.043195,0.0033 -2.082715,0.0066 -3.116396,0.0098', - stroke: '#000', - strokeWidth: 1, - lineCap: 'round', - lineJoin: 'round', - draggable: true, - }); - - layer.add(path); - - stage.add(layer); - - path.cache(); - layer.draw(); - // layer.draw(); - cloneAndCompareLayer(layer, 230); - }); - - // ====================================================== - it('moveTo with implied lineTos and trailing comma', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var path = new Konva.Path({ - data: 'm200,100,100,0,0,50,-100,0z', - fill: '#fcc', - // stroke: '#333', - // strokeWidth: 2, - shadowColor: 'maroon', - shadowBlur: 2, - shadowOffset: { x: 10, y: 10 }, - shadowOpacity: 1, - draggable: true, - }); - - path.on('mouseover', function () { - this.fill('red'); - layer.draw(); - }); - - path.on('mouseout', function () { - this.fill('#ccc'); - layer.draw(); - }); - - layer.add(path); - - stage.add(layer); - - assert.equal(path.data(), 'm200,100,100,0,0,50,-100,0z'); - assert.equal(path.dataArray.length, 5); - - assert.equal(path.dataArray[1].command, 'L'); - - var canvas = createCanvas(); - var context = canvas.getContext('2d'); - // stroke - context.beginPath(); - context.moveTo(200, 100); - context.lineTo(300, 100); - context.lineTo(300, 150); - context.lineTo(200, 150); - context.closePath(); - context.fillStyle = '#fcc'; - context.shadowColor = 'maroon'; - context.shadowBlur = 2 * Konva.pixelRatio; - context.shadowOffsetX = 10 * Konva.pixelRatio; - context.shadowOffsetY = 10 * Konva.pixelRatio; - context.fill(); - // context.stroke(); - compareLayerAndCanvas(layer, canvas, 20); - }); - - // ====================================================== - it('add map path', function () { - var stage = addStage(); - var mapLayer = new Konva.Layer(); - - for (var key in worldMap.shapes) { - var c = worldMap.shapes[key]; - - var path = new Konva.Path({ - data: c, - fill: '#ccc', - stroke: '#999', - strokeWidth: 1, - }); - - if (key === 'US') { - assert.equal(path.dataArray[0].command, 'M'); - } - - path.on('mouseover', function () { - this.fill('red'); - mapLayer.drawScene(); - }); - - path.on('mouseout', function () { - this.fill('#ccc'); - mapLayer.drawScene(); - }); - - mapLayer.add(path); - } - - stage.add(mapLayer); - - //document.body.appendChild(mapLayer.bufferCanvas.element); - }); - - // ====================================================== - it('curved arrow path', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var c = - 'M12.582,9.551C3.251,16.237,0.921,29.021,7.08,38.564l-2.36,1.689l4.893,2.262l4.893,2.262l-0.568-5.36l-0.567-5.359l-2.365,1.694c-4.657-7.375-2.83-17.185,4.352-22.33c7.451-5.338,17.817-3.625,23.156,3.824c5.337,7.449,3.625,17.813-3.821,23.152l2.857,3.988c9.617-6.893,11.827-20.277,4.935-29.896C35.591,4.87,22.204,2.658,12.582,9.551z'; - - var path = new Konva.Path({ - data: c, - fill: '#ccc', - stroke: '#999', - strokeWidth: 1, - }); - - path.on('mouseover', function () { - this.fill('red'); - layer.draw(); - }); - - path.on('mouseout', function () { - this.fill('#ccc'); - layer.draw(); - }); - - layer.add(path); - stage.add(layer); - - var trace = layer.getContext().getTrace(); - - assert.equal( - trace, - 'clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);beginPath();moveTo(12.582,9.551);bezierCurveTo(3.251,16.237,0.921,29.021,7.08,38.564);lineTo(4.72,40.253);lineTo(9.613,42.515);lineTo(14.506,44.777);lineTo(13.938,39.417);lineTo(13.371,34.058);lineTo(11.006,35.752);bezierCurveTo(6.349,28.377,8.176,18.567,15.358,13.422);bezierCurveTo(22.809,8.084,33.175,9.797,38.514,17.246);bezierCurveTo(43.851,24.695,42.139,35.059,34.693,40.398);lineTo(37.55,44.386);bezierCurveTo(47.167,37.493,49.377,24.109,42.485,14.49);bezierCurveTo(35.591,4.87,22.204,2.658,12.582,9.551);closePath();fillStyle=#ccc;fill();lineWidth=1;strokeStyle=#999;stroke();restore();' - ); - }); - - // ====================================================== - it('Quadradic Curve test from SVG w3c spec', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var c = 'M200,300 Q400,50 600,300 T1000,300'; - - var path = new Konva.Path({ - data: c, - stroke: 'red', - strokeWidth: 5, - }); - - layer.add(path); - - layer.add( - new Konva.Circle({ - x: 200, - y: 300, - radius: 10, - fill: 'black', - }) - ); - - layer.add( - new Konva.Circle({ - x: 600, - y: 300, - radius: 10, - fill: 'black', - }) - ); - - layer.add( - new Konva.Circle({ - x: 1000, - y: 300, - radius: 10, - fill: 'black', - }) - ); - - layer.add( - new Konva.Circle({ - x: 400, - y: 50, - radius: 10, - fill: '#888', - }) - ); - - layer.add( - new Konva.Circle({ - x: 800, - y: 550, - radius: 10, - fill: '#888', - }) - ); - - layer.add( - new Konva.Path({ - data: 'M200,300 L400,50L600,300L800,550L1000,300', - stroke: '#888', - strokeWidth: 2, - }) - ); - - stage.add(layer); - - var trace = layer.getContext().getTrace(); - assert.equal( - trace, - 'clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);beginPath();moveTo(200,300);quadraticCurveTo(400,50,600,300);quadraticCurveTo(800,550,1000,300);lineWidth=5;strokeStyle=red;stroke();restore();save();transform(1,0,0,1,200,300);beginPath();arc(0,0,10,0,6.283,false);closePath();fillStyle=black;fill();restore();save();transform(1,0,0,1,600,300);beginPath();arc(0,0,10,0,6.283,false);closePath();fillStyle=black;fill();restore();save();transform(1,0,0,1,1000,300);beginPath();arc(0,0,10,0,6.283,false);closePath();fillStyle=black;fill();restore();save();transform(1,0,0,1,400,50);beginPath();arc(0,0,10,0,6.283,false);closePath();fillStyle=#888;fill();restore();save();transform(1,0,0,1,800,550);beginPath();arc(0,0,10,0,6.283,false);closePath();fillStyle=#888;fill();restore();save();transform(1,0,0,1,0,0);beginPath();moveTo(200,300);lineTo(400,50);lineTo(600,300);lineTo(800,550);lineTo(1000,300);lineWidth=2;strokeStyle=#888;stroke();restore();' - ); - }); - - // ====================================================== - it('Cubic Bezier Curve test from SVG w3c spec using data', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var c = 'M100,200 C100,100 250,100 250,200 S400,300 400,200'; - - var path = new Konva.Path({ - stroke: 'red', - strokeWidth: 5, - }); - - path.data(c); - - layer.add(path); - - layer.add( - new Konva.Circle({ - x: 100, - y: 200, - radius: 10, - stroke: '#888', - }) - ); - - layer.add( - new Konva.Circle({ - x: 250, - y: 200, - radius: 10, - stroke: '#888', - }) - ); - - layer.add( - new Konva.Circle({ - x: 400, - y: 200, - radius: 10, - stroke: '#888', - }) - ); - - layer.add( - new Konva.Circle({ - x: 100, - y: 100, - radius: 10, - fill: '#888', - }) - ); - - layer.add( - new Konva.Circle({ - x: 250, - y: 100, - radius: 10, - fill: '#888', - }) - ); - - layer.add( - new Konva.Circle({ - x: 400, - y: 300, - radius: 10, - fill: '#888', - }) - ); - - layer.add( - new Konva.Circle({ - x: 250, - y: 300, - radius: 10, - stroke: 'blue', - }) - ); - - stage.add(layer); - - var trace = layer.getContext().getTrace(); - assert.equal( - trace, - 'clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);beginPath();moveTo(100,200);bezierCurveTo(100,100,250,100,250,200);bezierCurveTo(250,300,400,300,400,200);lineWidth=5;strokeStyle=red;stroke();restore();save();transform(1,0,0,1,100,200);beginPath();arc(0,0,10,0,6.283,false);closePath();lineWidth=2;strokeStyle=#888;stroke();restore();save();transform(1,0,0,1,250,200);beginPath();arc(0,0,10,0,6.283,false);closePath();lineWidth=2;strokeStyle=#888;stroke();restore();save();transform(1,0,0,1,400,200);beginPath();arc(0,0,10,0,6.283,false);closePath();lineWidth=2;strokeStyle=#888;stroke();restore();save();transform(1,0,0,1,100,100);beginPath();arc(0,0,10,0,6.283,false);closePath();fillStyle=#888;fill();restore();save();transform(1,0,0,1,250,100);beginPath();arc(0,0,10,0,6.283,false);closePath();fillStyle=#888;fill();restore();save();transform(1,0,0,1,400,300);beginPath();arc(0,0,10,0,6.283,false);closePath();fillStyle=#888;fill();restore();save();transform(1,0,0,1,250,300);beginPath();arc(0,0,10,0,6.283,false);closePath();lineWidth=2;strokeStyle=blue;stroke();restore();' - ); - }); - - // ====================================================== - it('path arc', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var c = - 'M100,350 l 50,-25 a25,25 -30 0,1 50,-25 l 50,-25 a25,50 -30 0,1 50,-25 l 50,-25 a25,75 -30 0,1 50,-25 l 50,-25 a25,100 -30 0,1 50,-25 l 50,-25'; - - var path = new Konva.Path({ - data: c, - fill: 'none', - stroke: '#999', - strokeWidth: 1, - }); - - path.on('mouseover', function () { - this.fill('red'); - layer.draw(); - }); - - path.on('mouseout', function () { - this.fill('none'); - layer.draw(); - }); - - layer.add(path); - stage.add(layer); - - var trace = layer.getContext().getTrace(); - assert.equal( - trace, - 'clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);beginPath();moveTo(100,350);lineTo(150,325);translate(175,312.5);rotate(-0.524);scale(1,1);arc(0,0,27.951,-3.082,0.06,0);scale(1,1);rotate(0.524);translate(-175,-312.5);lineTo(250,275);translate(275,262.5);rotate(-0.524);scale(0.5,1);arc(0,0,55.826,-3.112,0.03,0);scale(2,1);rotate(0.524);translate(-275,-262.5);lineTo(350,225);translate(375,212.5);rotate(-0.524);scale(0.333,1);arc(0,0,83.719,-3.122,0.02,0);scale(3,1);rotate(0.524);translate(-375,-212.5);lineTo(450,175);translate(475,162.5);rotate(-0.524);scale(0.25,1);arc(0,0,111.615,-3.127,0.015,0);scale(4,1);rotate(0.524);translate(-475,-162.5);lineTo(550,125);fillStyle=none;fill();lineWidth=1;strokeStyle=#999;stroke();restore();' - ); - }); - - // ====================================================== - it('Tiger (RAWR!)', function () { - this.timeout(5000); - var stage = addStage(); - var layer = new Konva.Layer(); - var group = new Konva.Group(); - - for (var i = 0; i < tiger.length; i++) { - var path = new Konva.Path(tiger[i]); - group.add(path); - } - - group.setDraggable(true); - layer.add(group); - stage.add(layer); - - var trace = layer.getContext().getTrace(); - assert.equal( - trace, - 'moveTo(-44.4,313.001);bezierCurveTo(-44.4,313.001,-32.8,290.601,-54.6,316.401);bezierCurveTo(-54.6,316.401,-43.6,306.601,-44.4,313.001);closePath();fillStyle=#cccccc;fill();restore();save();transform(1,0,0,1,0,0);beginPath();moveTo(-59.8,298.401);bezierCurveTo(-59.8,298.401,-55,279.601,-52.4,279.801);bezierCurveTo(-52.4,279.801,-44.2,270.801,-50.8,281.401);bezierCurveTo(-50.8,281.401,-56.8,291.001,-56.2,300.801);bezierCurveTo(-56.2,300.801,-56.8,291.201,-59.8,298.401);closePath();fillStyle=#cccccc;fill();restore();save();transform(1,0,0,1,0,0);beginPath();moveTo(270.5,287);bezierCurveTo(270.5,287,258.5,277,256,273.5);bezierCurveTo(256,273.5,269.5,292,269.5,299);bezierCurveTo(269.5,299,272,291.5,270.5,287);closePath();fillStyle=#cccccc;fill();restore();save();transform(1,0,0,1,0,0);beginPath();moveTo(276,265);bezierCurveTo(276,265,255,250,251.5,242.5);bezierCurveTo(251.5,242.5,278,272,278,276.5);bezierCurveTo(278,276.5,278.5,267.5,276,265);closePath();fillStyle=#cccccc;fill();restore();save();transform(1,0,0,1,0,0);beginPath();moveTo(293,111);bezierCurveTo(293,111,281,103,279.5,105);bezierCurveTo(279.5,105,290,111.5,292.5,120);bezierCurveTo(292.5,120,291,111,293,111);closePath();fillStyle=#cccccc;fill();restore();save();transform(1,0,0,1,0,0);beginPath();moveTo(301.5,191.5);lineTo(284,179.5);bezierCurveTo(284,179.5,303,196.5,303.5,200.5);lineTo(301.5,191.5);closePath();fillStyle=#cccccc;fill();restore();save();transform(1,0,0,1,0,0);beginPath();moveTo(-89.25,169);lineTo(-67.25,173.75);lineWidth=2;strokeStyle=#000000;stroke();restore();save();transform(1,0,0,1,0,0);beginPath();moveTo(-39,331);bezierCurveTo(-39,331,-39.5,327.5,-48.5,338);lineWidth=2;strokeStyle=#000000;stroke();restore();save();transform(1,0,0,1,0,0);beginPath();moveTo(-33.5,336);bezierCurveTo(-33.5,336,-31.5,329.5,-38,334);lineWidth=2;strokeStyle=#000000;stroke();restore();save();transform(1,0,0,1,0,0);beginPath();moveTo(20.5,344.5);bezierCurveTo(20.5,344.5,22,333.5,10.5,346.5);lineWidth=2;strokeStyle=#000000;stroke();restore();' - ); - }); - - // ====================================================== - it('Tiger (RAWR!) cached', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var group = new Konva.Group(); - - for (var i = 0; i < tiger.length; i++) { - var path = new Konva.Path(tiger[i]); - group.add(path); - } - - group.setDraggable(true); - layer.add(group); - stage.add(layer); - group.cache(); - layer.draw(); - - cloneAndCompareLayer(layer, 200); - }); - - // ====================================================== - it('Able to determine point on line some distance from another point on line', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var c = 'M10,10 210,160'; - // i.e., from a 3-4-5 triangle - - var path = new Konva.Path({ - stroke: 'red', - strokeWidth: 3, - }); - - path.data(c); - layer.add(path); - - layer.add( - new Konva.Circle({ - x: 10, - y: 10, - radius: 10, - fill: 'black', - }) - ); - - var p1 = Konva.Path.getPointOnLine(125, 10, 10, 210, 160); - // should be 1/2 way, or (110,85) - assert.equal(Math.round(p1.x), 110); - assert.equal(Math.round(p1.y), 85); - - layer.add( - new Konva.Circle({ - x: p1.x, - y: p1.y, - radius: 10, - fill: 'blue', - }) - ); - - stage.add(layer); - }); - - // ====================================================== - it('Able to determine points on Cubic Bezier Curve', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var c = 'M100,200 C100,100 250,100 250,200 S400,300 400,200'; - - var path = new Konva.Path({ - stroke: 'red', - strokeWidth: 3, - }); - - path.data(c); - - layer.add(path); - c = 'M 100 200'; - - for (let t = 0.25; t <= 1; t += 0.25) { - var p1 = Konva.Path.getPointOnCubicBezier( - t, - 100, - 200, - 100, - 100, - 250, - 100, - 250, - 200 - ); - c += ' ' + p1.x.toString() + ' ' + p1.y.toString(); - } - - for (let t = 0.25; t <= 1; t += 0.25) { - var p1 = Konva.Path.getPointOnCubicBezier( - t, - 250, - 200, - 250, - 300, - 400, - 300, - 400, - 200 - ); - c += ' ' + p1.x.toString() + ' ' + p1.y.toString(); - } - - var testPath = new Konva.Path({ - stroke: 'black', - strokewidth: 2, - data: c, - }); - - layer.add(testPath); - stage.add(layer); - - assert.equal( - c, - 'M 100 200 123.4375 143.75 175 125 226.5625 143.75 250 200 273.4375 256.25 325 275 376.5625 256.25 400 200' - ); - }); - - // ====================================================== - it('Able to determine points on Quadratic Curve', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var c = 'M200,300 Q400,50 600,300 T1000,300'; - - var path = new Konva.Path({ - stroke: 'red', - strokeWidth: 3, - }); - - path.data(c); - - layer.add(path); - c = 'M 200 300'; - - for (let t = 0.333; t <= 1; t += 0.333) { - var p1 = Konva.Path.getPointOnQuadraticBezier( - t, - 200, - 300, - 400, - 50, - 600, - 300 - ); - c += ' ' + p1.x.toString() + ' ' + p1.y.toString(); - } - - for (let t = 0.333; t <= 1; t += 0.333) { - var p1 = Konva.Path.getPointOnQuadraticBezier( - t, - 600, - 300, - 800, - 550, - 1000, - 300 - ); - c += ' ' + p1.x.toString() + ' ' + p1.y.toString(); - } - - var testPath = new Konva.Path({ - stroke: 'black', - strokewidth: 2, - data: c, - }); - - layer.add(testPath); - stage.add(layer); - - assert.equal( - c, - 'M 200 300 333.20000000000005 188.9445 466.40000000000003 188.77800000000002 599.6 299.50050000000005 733.2 411.05550000000005 866.4 411.222 999.6 300.49949999999995' - ); - }); - - // ====================================================== - it('Able to determine points on Elliptical Arc with clockwise stroke', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var c = 'M 50,100 A 100 50 0 1 1 150 150'; - - var path = new Konva.Path({ - stroke: 'red', - strokeWidth: 3, - }); - - path.data(c); - - layer.add(path); - - var centerParamPoints = Konva.Path.convertEndpointToCenterParameterization( - 50, - 100, - 150, - 150, - 1, - 1, - 100, - 50, - 0 - ); - - var start = centerParamPoints[4]; - // 4 = theta - var dTheta = centerParamPoints[5]; - // 5 = dTheta - var end = centerParamPoints[4] + dTheta; - var inc = Math.PI / 6.0; - // 30 degree resolution - - var p1 = Konva.Path.getPointOnEllipticalArc( - centerParamPoints[0], - centerParamPoints[1], - centerParamPoints[2], - centerParamPoints[3], - start, - 0 - ); - c = 'M ' + p1.x.toFixed(2) + ' ' + p1.y.toFixed(2); - - if ( - dTheta < 0 // clockwise - ) { - for (let t = start - inc; t > end; t -= inc) { - p1 = Konva.Path.getPointOnEllipticalArc( - centerParamPoints[0], - centerParamPoints[1], - centerParamPoints[2], - centerParamPoints[3], - t, - 0 - ); - c += ' ' + p1.x.toFixed(2) + ' ' + p1.y.toFixed(2); - } - } else { - // counter-clockwise - for (let t = start + inc; t < end; t += inc) { - p1 = Konva.Path.getPointOnEllipticalArc( - centerParamPoints[0], - centerParamPoints[1], - centerParamPoints[2], - centerParamPoints[3], - t, - 0 - ); - c += ' ' + p1.x.toFixed(2) + ' ' + p1.y.toFixed(2); - } - } - p1 = Konva.Path.getPointOnEllipticalArc( - centerParamPoints[0], - centerParamPoints[1], - centerParamPoints[2], - centerParamPoints[3], - end, - 0 - ); - c += ' ' + p1.x.toFixed(2) + ' ' + p1.y.toFixed(2); - - var testpath = new Konva.Path({ - stroke: 'black', - strokeWidth: 2, - data: c, - }); - - layer.add(testpath); - stage.add(layer); - - assert.equal( - c, - 'M 50.00 100.00 63.40 75.00 100.00 56.70 150.00 50.00 200.00 56.70 236.60 75.00 250.00 100.00 236.60 125.00 200.00 143.30 150.00 150.00' - ); - }); - - // ====================================================== - it('Able to determine points on Elliptical Arc with counter-clockwise stroke', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var c = 'M 250,100 A 100 50 0 1 0 150 150'; - - var path = new Konva.Path({ - stroke: 'red', - strokeWidth: 3, - }); - - path.data(c); - - layer.add(path); - - var centerParamPoints = Konva.Path.convertEndpointToCenterParameterization( - 250, - 100, - 150, - 150, - 1, - 0, - 100, - 50, - 0 - ); - - var start = centerParamPoints[4]; - // 4 = theta - var dTheta = centerParamPoints[5]; - // 5 = dTheta - var end = centerParamPoints[4] + dTheta; - var inc = Math.PI / 6.0; - // 30 degree resolution - - var p1 = Konva.Path.getPointOnEllipticalArc( - centerParamPoints[0], - centerParamPoints[1], - centerParamPoints[2], - centerParamPoints[3], - start, - 0 - ); - c = 'M ' + p1.x.toString() + ' ' + p1.y.toString(); - - if ( - dTheta < 0 // clockwise - ) { - for (let t = start - inc; t > end; t -= inc) { - p1 = Konva.Path.getPointOnEllipticalArc( - centerParamPoints[0], - centerParamPoints[1], - centerParamPoints[2], - centerParamPoints[3], - t, - 0 - ); - c += ' ' + p1.x.toString() + ' ' + p1.y.toString(); - } - } else { - // counter-clockwise - for (let t = start + inc; t < end; t += inc) { - p1 = Konva.Path.getPointOnEllipticalArc( - centerParamPoints[0], - centerParamPoints[1], - centerParamPoints[2], - centerParamPoints[3], - t, - 0 - ); - c += ' ' + p1.x.toString() + ' ' + p1.y.toString(); - } - } - p1 = Konva.Path.getPointOnEllipticalArc( - centerParamPoints[0], - centerParamPoints[1], - centerParamPoints[2], - centerParamPoints[3], - end, - 0 - ); - c += ' ' + p1.x.toString() + ' ' + p1.y.toString(); - - var testpath = new Konva.Path({ - stroke: 'black', - strokeWidth: 2, - data: c, - }); - - layer.add(testpath); - stage.add(layer); - - assert.equal( - c, - 'M 250 100 236.60254037844388 75 200 56.69872981077807 150 50 100.00000000000003 56.69872981077806 63.39745962155615 74.99999999999999 50 99.99999999999997 63.397459621556095 124.99999999999997 99.99999999999996 143.30127018922192 149.99999999999997 150' - ); - }); - - // ====================================================== - it('Able to determine points on Elliptical Arc when rotated', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var c = 'M 250,100 A 100 50 30 1 0 150 150'; - - var path = new Konva.Path({ - stroke: 'red', - strokeWidth: 3, - }); - - path.data(c); - - layer.add(path); - - var centerParamPoints = Konva.Path.convertEndpointToCenterParameterization( - 250, - 100, - 150, - 150, - 1, - 0, - 100, - 50, - 30 - ); - - var start = centerParamPoints[4]; - // 4 = theta - var dTheta = centerParamPoints[5]; - // 5 = dTheta - var end = centerParamPoints[4] + dTheta; - var inc = Math.PI / 6.0; - // 30 degree resolution - var psi = centerParamPoints[6]; - // 6 = psi radians - - var p1 = Konva.Path.getPointOnEllipticalArc( - centerParamPoints[0], - centerParamPoints[1], - centerParamPoints[2], - centerParamPoints[3], - start, - psi - ); - c = 'M ' + p1.x.toFixed(2) + ' ' + p1.y.toFixed(2); - - if ( - dTheta < 0 // clockwise - ) { - for (let t = start - inc; t > end; t -= inc) { - p1 = Konva.Path.getPointOnEllipticalArc( - centerParamPoints[0], - centerParamPoints[1], - centerParamPoints[2], - centerParamPoints[3], - t, - psi - ); - c += ' ' + p1.x.toFixed(2) + ' ' + p1.y.toFixed(2); - } - } else { - // counter-clockwise - for (let t = start + inc; t < end; t += inc) { - p1 = Konva.Path.getPointOnEllipticalArc( - centerParamPoints[0], - centerParamPoints[1], - centerParamPoints[2], - centerParamPoints[3], - t, - psi - ); - c += ' ' + p1.x.toFixed(2) + ' ' + p1.y.toFixed(2); - } - } - p1 = Konva.Path.getPointOnEllipticalArc( - centerParamPoints[0], - centerParamPoints[1], - centerParamPoints[2], - centerParamPoints[3], - end, - psi - ); - c += ' ' + p1.x.toFixed(2) + ' ' + p1.y.toFixed(2); - - var testpath = new Konva.Path({ - stroke: 'black', - strokeWidth: 2, - data: c, - }); - - layer.add(testpath); - stage.add(layer); - - assert.equal( - c, - 'M 250.00 100.00 209.63 69.47 162.97 50.77 122.52 48.92 99.13 64.41 99.05 93.09 122.32 127.28 150.00 150.00' - ); - }); - - // ====================================================== - it('getPointOnLine for different directions', function () { - var origo = { - x: 0, - y: 0, - }; - - var p, point; - - //point up left - p = { - x: -10, - y: -10, - }; - point = Konva.Path.getPointOnLine(10, origo.x, origo.y, p.x, p.y); - assert(point.x < 0 && point.y < 0, 'The new point should be up left'); - - //point up right - p = { - x: 10, - y: -10, - }; - point = Konva.Path.getPointOnLine(10, origo.x, origo.y, p.x, p.y); - assert(point.x > 0 && point.y < 0, 'The new point should be up right'); - - //point down right - p = { - x: 10, - y: 10, - }; - point = Konva.Path.getPointOnLine(10, origo.x, origo.y, p.x, p.y); - assert(point.x > 0 && point.y > 0, 'The new point should be down right'); - - //point down left - p = { - x: -10, - y: 10, - }; - point = Konva.Path.getPointOnLine(10, origo.x, origo.y, p.x, p.y); - assert(point.x < 0 && point.y > 0, 'The new point should be down left'); - - //point left - p = { - x: -10, - y: 0, - }; - point = Konva.Path.getPointOnLine(10, origo.x, origo.y, p.x, p.y); - assert(point.x == -10 && point.y == 0, 'The new point should be left'); - - //point up - p = { - x: 0, - y: -10, - }; - point = Konva.Path.getPointOnLine(10, origo.x, origo.y, p.x, p.y); - assert( - Math.abs(point.x) < 0.0000001 && point.y == -10, - 'The new point should be up' - ); - - //point right - p = { - x: 10, - y: 0, - }; - point = Konva.Path.getPointOnLine(10, origo.x, origo.y, p.x, p.y); - assert(point.x == 10 && point.y == 0, 'The new point should be right'); - - //point down - p = { - x: 0, - y: 10, - }; - point = Konva.Path.getPointOnLine(10, origo.x, origo.y, p.x, p.y); - assert( - Math.abs(point.x) < 0.0000001 && point.y == 10, - 'The new point should be down' - ); - }); - - // ====================================================== - it('get path length', function () { - var path = new Konva.Path({ data: 'M 10,10 L 20,10 L 20,20' }); - assert.equal(path.getLength(), 20); - }); - - // ====================================================== - - it('get point at path', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - const data = - 'M 300,10 L 250,100 A 100 40 30 1 0 150 150 C 160,100, 290,100, 300,150'; - var path = new Konva.Path({ - stroke: 'red', - strokeWidth: 3, - data, - }); - layer.add(path); - if (isBrowser) { - const SVGPath = document.createElementNS( - 'http://www.w3.org/2000/svg', - 'path' - ) as SVGPathElement; - SVGPath.setAttribute('d', data); - for (var i = 0; i < path.getLength(); i += 1) { - var p = path.getPointAtLength(i); - var circle = new Konva.Circle({ - x: p.x, - y: p.y, - radius: 2, - fill: 'black', - stroke: 'black', - }); - layer.add(circle); - const position = SVGPath.getPointAtLength(i); - assert( - Math.abs(p.x / position.x) >= 0.8, - 'error should be smaller than 10%' - ); - assert( - Math.abs(p.y / position.y) >= 0.8, - 'error should be smaller than 10%' - ); - } - } else { - var points = []; - for (var i = 0; i < path.getLength(); i += 20) { - var p = path.getPointAtLength(i); - points.push(p); - var circle = new Konva.Circle({ - x: p.x, - y: p.y, - radius: 2, - fill: 'black', - stroke: 'black', - }); - layer.add(circle); - } - - assert.deepEqual(points, [ - { x: 300, y: 10 }, - { x: 290.28714137642737, y: 27.483145522430753 }, - { x: 280.57428275285474, y: 44.96629104486151 }, - { x: 270.86142412928206, y: 62.44943656729226 }, - { x: 261.1485655057094, y: 79.93258208972301 }, - { x: 251.4357068821368, y: 97.41572761215377 }, - { x: 230.89220826660141, y: 87.23996356219386 }, - { x: 207.0639321224534, y: 74.08466390481559 }, - { x: 182.87529785963875, y: 63.52674972743341 }, - { x: 159.56025996483157, y: 56.104820499018956 }, - { x: 138.30820744216845, y: 52.197497135977514 }, - { x: 120.20328854394192, y: 52.00410710518156 }, - { x: 106.16910423342256, y: 55.53451596967142 }, - { x: 96.92159177720502, y: 62.60862410865827 }, - { x: 92.93250205472883, y: 72.86555428606191 }, - { x: 94.40533374670959, y: 85.78206137467119 }, - { x: 101.26495209131289, y: 100.69922508568548 }, - { x: 113.1614217949117, y: 116.85606400569954 }, - { x: 129.4878585660311, y: 133.42835616090537 }, - { x: 149.41138859764925, y: 149.5706857234721 }, - { x: 159.43138712714935, y: 133.06025615594774 }, - { x: 175.3017710206886, y: 122.31378864213205 }, - { x: 194.92856277944335, y: 115.73314636675508 }, - { x: 214.84499816899648, y: 112.85265466076682 }, - { x: 234.86585690487928, y: 112.83275701234302 }, - { x: 254.65745479392615, y: 115.6401774356189 }, - { x: 273.58108654098885, y: 121.79846344304384 }, - { x: 289.93157588171135, y: 132.43782950384232 }, - { x: 299.87435436448743, y: 149.4028482225714 }, - ]); - } - - stage.add(layer); - }); - - it('get point at path with float attrs', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - const data = - 'M419.0000314094981 342.88624187900973 L419.00003140949804 577.0038889378335 L465.014001082264 577.0038889378336 Z'; - var path = new Konva.Path({ - stroke: 'red', - strokeWidth: 3, - data, - }); - layer.add(path); - if (isBrowser) { - const SVGPath = document.createElementNS( - 'http://www.w3.org/2000/svg', - 'path' - ) as SVGPathElement; - SVGPath.setAttribute('d', data); - for (var i = 0; i < path.getLength(); i += 1) { - var p = path.getPointAtLength(i); - var circle = new Konva.Circle({ - x: p.x, - y: p.y, - radius: 2, - fill: 'black', - stroke: 'black', - }); - layer.add(circle); - const position = SVGPath.getPointAtLength(i); - assert( - Math.abs(p.x - position.x) <= 1, - 'error for x should be smaller than 10% for i = ' + i - ); - assert( - Math.abs(p.y - position.y) <= 1, - 'error for y should be smaller than 10% for i = ' + i - ); - } - } - }); - - it('get point at path - bezier', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - const data = - 'M100,250 q150,-150 300,0 M 117.12814070351759 108.66938206658291 C 79.18719346733668 277.73956799623113 75.85761180904522 379.96743797110554 82.84673366834171 395.7761659861809 S 148.83130025125627 280.47708118718595 177.12060301507537 244.36661824748745 S 326.1725898241206 61.02036887562815 325.67336683417085 85.815110709799 S 174.998726758794 435.7304316896985 172.8354271356784 457.1970202575377 S 273.65633103015074 310.01551271984926 307.1042713567839 270.07767352386935 S 466.09929459798997 92.08432302135678 459.9422110552764 114.3829499057789 S 266.23512060301505 435.5226006595478 254.2537688442211 461.4821961369347 S 328.1430565326633 368.1639210113065 357.09798994974875 337.2120956344221 S 486.31961118090453 207.61623570979899 502.79396984924625 195.8012916143216 S 511.48859170854274 200.85065719221106 498.50879396984925 235.79626648869348 S 379.73086055276383 489.4401119660804 391.37939698492465 495.76360317211055 S 573.2022663316583 313.03941849874377 598.4962311557789 290.0751609610553 S 608.3285672110553 288.6610529208543 608.4949748743719 298.64551271984925 S 604.9168530150754 352.64801334799 599.9246231155779 375.778678548995 S 540.6820665829146 508.5077162374372 565.643216080402 497.19199513190955 S 690.3761155778894 408.77881799623117 814.1834170854271 278.6480252826633'; - var path = new Konva.Path({ - stroke: 'red', - strokeWidth: 3, - data, - }); - layer.add(path); - if (isBrowser) { - const SVGPath = document.createElementNS( - 'http://www.w3.org/2000/svg', - 'path' - ) as SVGPathElement; - SVGPath.setAttribute('d', data); - for (var i = 0; i < path.getLength(); i += 10) { - var p = path.getPointAtLength(i); - var circle = new Konva.Circle({ - x: p.x, - y: p.y, - radius: 2, - fill: 'black', - stroke: 'black', - }); - layer.add(circle); - const position = SVGPath.getPointAtLength(i); - assert( - Math.abs(p.x / position.x) >= 0.8, - 'error should be smaller than 10%' - ); - assert( - Math.abs(p.y / position.y) >= 0.8, - 'error should be smaller than 10%' - ); - } - } else { - var points = []; - for (var i = 0; i < path.getLength(); i += 500) { - var p = path.getPointAtLength(i); - points.push(p); - var circle = new Konva.Circle({ - x: p.x, - y: p.y, - radius: 2, - fill: 'black', - stroke: 'black', - }); - layer.add(circle); - } - - assert.deepEqual(points, [ - { x: 100, y: 250 }, - { x: 88.80979830887104, y: 261.9310198815103 }, - { x: 296.17215373535686, y: 105.30891997028526 }, - { x: 207.5911710830848, y: 414.96086124898176 }, - { x: 410.01622229664224, y: 202.72024124427364 }, - { x: 374.86125434742394, y: 318.78396882819396 }, - { x: 392.21257855027216, y: 483.8201732191269 }, - { x: 572.3287288437606, y: 447.38305323763467 }, - ]); - } - stage.add(layer); - }); - - // ====================================================== - it('Borneo Map (has scientific notation: -10e-4)', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var borneo = new Konva.Path({ - data: 'm 136.68513,236.08861 c -0.63689,-0.67792 -0.75417,-1.28099 -1.03556,-5.32352 -0.26489,-3.80589 -0.4465,-4.81397 -1.09951,-6.1026 -0.51169,-1.00981 -0.98721,-1.54361 -1.375,-1.54361 -0.8911,0 -3.48931,-1.22828 -3.80975,-1.80103 -0.16294,-0.29089 -0.87295,-0.56825 -1.68693,-0.65886 -1.13423,-0.12629 -1.91094,0.0661 -4.02248,0.99633 -4.0367,1.77835 -5.46464,1.87106 -6.79674,0.44127 -0.51948,-0.55765 -0.64763,-1.12674 -0.64763,-2.87683 l 0,-2.18167 -0.87832,0.20996 c -0.48312,0.11549 -1.12041,0.33383 -1.41635,0.4852 -1.52799,0.78172 -4.61534,-0.0398 -5.55846,-1.47906 -0.30603,-0.46718 -1.06518,-1.19501 -1.68667,-1.61739 -1.27136,-0.86387 -1.62607,-0.6501 -1.63439,0.98494 -0.007,1.00822 -0.76687,2.38672 -1.31885,2.38672 -0.17579,0 -1.27182,0.66553 -2.4356,1.47895 -4.016775,2.8076 -6.006455,3.29182 -7.693525,1.87231 -0.52348,-0.44054 -1.43004,-1.00203 -2.01445,-1.24775 -1.35902,-0.57143 -2.10139,-0.21496 -5.36296,2.57523 -2.00259,1.71315 -2.55857,2.02869 -3.57441,2.02869 -0.66172,0 -1.31931,-0.17966 -1.46135,-0.39925 -0.27734,-0.42865 -0.75823,-5.15099 -0.87007,-8.54399 -0.0708,-2.14922 -0.41754,-3.83281 -0.78935,-3.83281 -0.1176,0 -0.45993,0.28746 -0.76078,0.63881 -0.66657,0.77849 -3.4572,0.87321 -4.70537,0.15969 -1.29782,-0.7419 -2.38029,-0.55672 -5.01545,0.85797 -2.16783,1.16385 -2.75945,1.33971 -4.5666,1.35746 -1.66762,0.0163 -2.276,-0.12217 -3.09174,-0.70405 -0.61985,-0.44211 -1.09397,-0.5977 -1.21663,-0.39925 -0.32993,0.53385 -2.25686,0.37294 -2.80642,-0.23436 -0.27856,-0.30774 -0.65658,-0.95453 -0.8401,-1.43731 -0.42448,-1.11632 -0.91809,-1.10316 -3.01531,0.0804 -0.93379,0.52702 -2.13107,0.9582 -2.66054,0.9582 -1.46554,0 -1.97734,-0.82307 -2.19476,-3.52955 -0.10515,-1.30865 -0.4137,-2.90864 -0.68575,-3.55553 -0.37975,-0.90312 -0.41736,-1.39768 -0.16196,-2.13038 0.35544,-1.01957 -0.24711,-3.50377 -1.40121,-5.77657 -0.48023,-0.94578 -0.50724,-1.33822 -0.19445,-2.82926 0.40575,-1.93441 -0.0409,-3.36568 -1.16059,-3.72114 -0.3255,-0.10331 -0.93466,-0.55279 -1.35374,-0.99885 -1.12569,-1.19829 -1.03821,-2.92553 0.22088,-4.35957 0.85079,-0.96896 1.01308,-1.45348 1.2082,-3.60666 l 0.22545,-2.48734 -1.16949,-1.19763 c -0.64324,-0.65869 -1.26203,-1.64897 -1.37517,-2.20061 -0.13388,-0.6528 -0.56813,-1.23242 -1.24372,-1.66009 l -1.03807,-0.65709 0,1.0782 c 0,0.59301 -0.21786,1.38922 -0.48413,1.76937 -0.68007,0.97099 -4.56312,2.96438 -5.77445,2.96438 -1.55729,0 -1.88611,-0.67097 -1.88611,-3.84837 0,-3.52819 0.41663,-4.13666 2.83284,-4.13666 1.49279,0 1.57631,-0.0396 1.09598,-0.51996 -0.4316,-0.43155 -0.69566,-0.4587 -1.55343,-0.15971 -0.56839,0.19815 -1.3354,0.35443 -1.70442,0.34729 -0.86278,-0.0167 -2.61563,-1.51607 -3.02205,-2.58498 -0.3513,-0.92403 -0.12267,-3.38466 0.34119,-3.67132 0.16474,-0.1018 -0.39367,-0.50661 -1.24085,-0.89959 -2.032471,-0.94281 -2.321421,-1.35146 -2.487701,-3.51839 -0.0772,-1.00533 -0.30119,-2.31552 -0.4979,-2.91152 -0.48076,-1.45668 -0.16499,-2.30832 0.90163,-2.43139 0.843711,-0.0974 0.860511,-0.14171 0.748911,-1.97594 -0.0696,-1.14269 0.0236,-1.96143 0.23793,-2.09396 0.47223,-0.29188 -2.501621,-3.97433 -3.330171,-4.12358 -0.34456,-0.062 -0.75956,-0.23921 -0.92229,-0.39365 -0.3459,-0.32835 -0.78945,-2.83658 -0.98794,-5.58637 -0.0769,-1.06517 -0.35848,-2.55647 -0.62576,-3.31402 -0.71739,-2.03331 -0.61465,-2.55112 0.76687,-3.86532 l 1.25273,-1.19173 -0.46915,-1.36178 c -0.53343,-1.54826 -0.33638,-2.99085 0.48923,-3.5815 0.65547,-0.46898 1.32731,-2.61652 1.52388,-4.87126 0.13191,-1.51252 0.2658,-1.7153 2.531131,-3.83281 2.21127,-2.06705 2.41106,-2.36144 2.64687,-3.89989 0.31881,-2.07979 0.74608,-2.60075 2.34208,-2.85597 0.69615,-0.11132 1.66359,-0.53718 2.14988,-0.94636 1.89204,-1.59201 4.16695,-1.77416 4.16695,-0.33363 0,0.40454 -0.23171,1.4157 -0.51499,2.24703 -0.28322,0.83134 -0.45486,1.57164 -0.38139,1.64512 0.0735,0.0735 1.32057,0.92807 2.77127,1.89909 2.57827,1.72574 2.68847,1.7655 4.89522,1.7655 1.74495,0 2.50706,-0.15424 3.35669,-0.67937 0.91121,-0.56315 1.2344,-0.61779 1.88934,-0.3194 0.43449,0.19798 1.19684,0.35997 1.69411,0.35997 1.03354,0 1.51204,0.45563 1.67033,1.59058 0.10938,0.78459 0.54215,1.02641 2.56344,1.43244 0.47079,0.0946 1.07249,0.38843 1.33713,0.65302 0.29826,0.29829 0.55659,0.35879 0.67998,0.15922 0.3007,-0.48659 2.51019,-0.38548 3.21433,0.1471 0.90129,0.6817 0.99638,0.6211 1.2201,-0.77786 0.1114,-0.69691 0.4878,-1.53284 0.83642,-1.85761 0.34861,-0.32477 0.76943,-1.29968 0.93532,-2.16645 0.36198,-1.89196 1.67658,-4.95214 2.37708,-5.53353 0.45941,-0.38127 0.45882,-0.50661 -0.007,-1.40586 -0.92929,-1.79695 -1.07762,-2.78281 -0.59325,-3.94207 0.32267,-0.77223 0.71393,-1.13742 1.3562,-1.26589 l 0.90282,-0.18055 -0.12723,-3.168 -0.1273,-3.168021 1.13626,0 c 0.6249,0 1.22425,0.14254 1.33189,0.31676 0.11034,0.17851 0.92013,-0.22348 1.85538,-0.92103 2.57554,-1.920815 3.6054,-2.317745 6.74013,-2.597735 2.80648,-0.25066 4.59942,-0.61535 8.65387,-1.76019 1.05398,-0.29761 2.49129,-0.66582 3.19396,-0.81822 2.5583,-0.55486 5.16562,-1.18239 7.665675,-1.84504 2.13376,-0.56557 2.7297,-0.87493 3.61346,-1.87587 1.968,-2.22882 6.60136,-8.28119 7.54474,-9.85529 0.55323,-0.92329 1.87182,-2.29956 3.218,-3.35904 2.58733,-2.03622 6.23997,-6.36804 7.37413,-8.74536 0.64823,-1.35877 0.73216,-1.8923 0.56253,-3.57654 -0.2316,-2.3005 -0.44696,-2.16353 3.91929,-2.49301 3.85817,-0.29115 6.65679,-1.49266 9.77494,-4.19656 2.99721,-2.5991 5.77546,-4.25711 7.14234,-4.26265 1.34414,-0.005 2.18866,0.95864 1.93792,2.21228 l -0.19117,0.956 1.02783,-0.62674 c 0.66237,-0.40384 1.60221,-0.62716 2.64269,-0.62793 1.73168,-10e-4 3.01752,-0.70122 4.31246,-2.34742 0.89476,-1.13744 0.70339,-1.77317 -0.78398,-2.60556 -0.68465,-0.38314 -1.52661,-1.0834 -1.87097,-1.55613 -0.54929,-0.75408 -0.57635,-0.97959 -0.22059,-1.83856 0.52649,-1.27114 3.93115,-4.11017 4.92904,-4.11017 0.41859,0 1.13672,0.14279 1.59566,0.3173 1.3868,0.52725 2.80354,-0.28364 3.6531,-2.09077 0.39579,-0.84216 1.25891,-2.18904 1.91795,-2.99304 1.48075,-1.80638 2.89866,-4.72745 2.89866,-5.97158 0,-0.75538 0.58238,-1.50827 3.06391,-3.96067 2.7523,-2.72002 6.3045,-6.98689 7.09162,-8.51845 0.1634,-0.318 0.3954,-1.22055 0.51562,-2.00566 0.25722,-1.68064 1.72382,-4.16066 2.46108,-4.16147 0.9766,-10e-4 2.12459,1.22566 2.31255,2.47132 0.0998,0.66067 0.27255,1.72385 0.384,2.36261 0.1155,0.66184 0.0472,1.45181 -0.15868,1.83656 -0.24595,0.45955 -0.25349,0.67517 -0.0229,0.67517 0.51299,0 2.24002,-2.8963 2.24002,-3.75665 0,-0.8354 0.53999,-2.02246 1.08581,-2.38694 0.19334,-0.12906 0.94292,-0.23686 1.66584,-0.23955 1.77381,-0.007 2.99753,0.95517 2.99753,2.35583 0,0.57021 0.21732,1.76868 0.48299,2.66324 l 0.48306,1.6265 0.92969,-0.92972 c 1.22287,-1.22287 2.47045,-1.24866 2.92225,-0.0604 0.22007,0.57891 0.22505,1.10057 0.0151,1.56166 -0.27458,0.60266 -0.20454,0.71514 0.53993,0.86809 1.18369,0.24315 3.55993,2.06175 3.91536,2.99648 0.59574,1.567 0.35077,3.19938 -0.65144,4.34081 -0.94122,1.07196 -0.94371,1.08593 -0.60505,3.28498 0.18712,1.21464 0.38753,2.25901 0.44545,2.32083 0.2451,0.26166 3.313,-0.9897 3.8317,-1.56289 1.62004,-1.79007 4.61934,0.34098 4.61934,3.28202 0,0.59127 -0.10771,1.21358 -0.23953,1.38292 -0.13176,0.16934 0.1309,-0.10749 0.58362,-0.61518 l 0.82309,-0.92308 2.45525,0.57882 c 3.13892,0.74002 4.67982,1.61224 5.4805,3.10222 0.49583,0.92281 0.83285,1.18897 1.50604,1.18964 0.49596,0.001 1.31643,0.39216 1.91637,0.91477 0.57707,0.50266 1.55223,1.17153 2.16717,1.48639 0.61481,0.31487 1.27608,0.78847 1.46955,1.05246 0.39952,0.54529 2.27154,0.59949 2.79024,0.0808 0.66827,-0.66817 2.3398,-0.37712 3.37202,0.58712 0.87138,0.81397 0.99174,1.13441 0.98984,2.63507 -0.007,3.14067 -1.18875,4.18009 -7.03587,6.17196 -3.71253,1.26471 -4.57971,1.44538 -6.93747,1.44538 -2.24861,0 -2.8547,-0.11412 -3.66279,-0.68954 -0.94626,-0.67382 -0.99252,-0.67652 -2.02854,-0.11858 -0.5831,0.31401 -1.383,0.91461 -1.77767,1.33464 l -0.71741,0.76372 1.56061,1.58439 c 1.40266,1.42413 1.61342,1.53657 2.08298,1.11159 0.76662,-0.69377 2.74012,-0.60035 3.50647,0.16598 0.78732,0.78729 0.81648,1.55502 0.0799,2.09925 -0.83901,0.61987 -0.0838,1.18313 1.57667,1.17578 1.61709,-0.007 2.17621,0.35138 2.17621,1.3954 0,0.59148 -0.17166,0.7594 -0.7769,0.7594 -0.48332,0 -0.84989,0.22977 -0.96998,0.60798 -0.26508,0.83534 -2.11417,1.6503 -4.4471,1.96007 -1.90366,0.25276 -5.24254,1.10817 -7.59191,1.94503 -1.09649,0.39058 -1.18265,0.52074 -1.37769,2.08163 -0.25454,2.03716 -0.67941,2.42422 -2.5359,2.31005 -0.79407,-0.0488 -1.53022,-0.002 -1.6359,0.10335 -0.10561,0.10567 0.32091,0.60142 0.94784,1.10167 0.62693,0.50027 1.13993,1.14348 1.13993,1.4294 0,0.28592 0.21555,0.69878 0.47906,0.91747 1.02219,0.84833 0.30092,2.43799 -1.55295,3.4227 -0.52676,0.27977 -0.48306,0.33828 0.3819,0.51126 1.25557,0.25111 1.75716,1.19504 1.48651,2.79737 -0.15363,0.90893 -0.36794,1.2537 -0.77945,1.2537 -1.42926,0 -3.3719,-2.70726 -2.60535,-3.63084 0.50081,-0.60337 -1.57909,-0.86467 -4.87669,-0.61268 -2.37814,0.18174 -2.45709,0.21144 -1.43732,0.54105 0.67928,0.21956 1.25642,0.70374 1.55806,1.30695 0.41505,0.8301 0.62988,0.94551 1.607,0.86325 0.85566,-0.072 1.30196,0.0903 1.84916,0.67285 0.87917,0.9358 1.26172,2.8927 0.69828,3.57163 -0.45639,0.54984 -2.57856,0.65234 -3.08199,0.14886 -0.23101,-0.23099 -0.45619,-0.1844 -0.73549,0.15214 -0.34547,0.41624 -0.19184,0.54147 1.0828,0.88237 2.06555,0.55246 2.84678,1.34484 2.63181,2.66945 -0.12598,0.77608 -0.0111,1.1894 0.4446,1.60189 0.33781,0.30575 0.61514,0.85703 0.61626,1.22506 0,0.40883 0.37665,0.8823 0.9648,1.21704 0.60282,0.34303 1.20761,1.11895 1.61742,2.075045 0.37403,0.87256 1.58191,2.485991 2.81788,3.764031 2.72839,2.82133 3.02053,3.36933 2.75178,5.16167 -0.1765,1.17708 -0.43169,1.57351 -1.52084,2.36249 -0.71977,0.52142 -1.65712,1.46074 -2.08292,2.08735 -0.66074,0.97241 -0.72193,1.26543 -0.41747,2.00042 0.19615,0.47362 1.00666,1.25369 1.80099,1.7335 0.79426,0.47981 1.6716,1.26687 1.94966,1.74904 0.56868,0.98649 2.52869,2.54597 4.42534,3.52103 0.69619,0.35796 1.69715,1.10835 2.22417,1.66754 0.52702,0.55918 1.52124,1.30625 2.2095,1.66012 1.53401,0.78869 4.33814,2.85596 4.33814,3.19814 0,0.64314 2.36392,2.78408 3.29157,2.98114 3.11842,0.66236 2.71293,3.44603 -0.88801,6.09705 l -1.28558,0.94651 -5.32705,-0.0434 c -4.41945,-0.036 -5.46766,-0.13568 -6.15336,-0.58491 -1.12014,-0.734 -3.69123,-1.21344 -3.69123,-0.68833 0,0.88679 -1.22942,1.53613 -2.56839,1.35654 -1.12847,-0.15136 -1.45376,-0.0446 -2.40271,0.78858 -0.60361,0.52999 -1.09747,1.11694 -1.09747,1.30432 0,0.61061 -2.01766,4.84486 -2.64971,5.56065 -0.83547,0.94619 -1.93367,5.6836 -1.50374,6.48688 0.50015,0.93456 0.37973,2.29694 -0.31815,3.59909 -0.77894,1.45317 -0.79106,1.89641 -0.10398,3.81328 0.46,1.28334 0.67568,1.5151 1.48658,1.597 1.48403,0.14992 1.74197,0.90287 0.92798,2.70938 -0.38137,0.84625 -0.78522,2.35688 -0.89764,3.35694 -0.11931,1.06047 -0.42298,2.01508 -0.72888,2.29042 -0.68334,0.61527 -3.70237,1.79849 -4.6086,1.8063 -0.72042,0.007 -3.41815,2.85544 -5.35745,5.65834 -1.05175,1.52015 -2.85327,2.4565 -4.21281,2.18961 -0.75535,-0.14829 -0.87832,-0.0687 -0.87832,0.56857 0,0.91256 -0.75207,1.60008 -2.29008,2.09359 -1.4381,0.46144 -1.7214,0.80341 -1.96204,2.3682 -0.23809,1.54838 -0.68406,2.08325 -2.35507,2.82408 l -1.33701,0.5928 0.77815,0.77808 c 0.69428,0.6944 0.77808,1.05197 0.77808,3.32499 0,1.85231 -0.13241,2.67923 -0.48529,3.03212 -0.43398,0.43402 -0.35818,0.52049 0.71872,0.81954 0.66212,0.18388 1.51875,0.33512 1.9036,0.3361 0.38485,0.001 0.78136,0.13367 0.88094,0.29487 0.25866,0.41856 -0.38281,4.69924 -0.97325,6.49419 l -0.49911,1.51716 -1.65116,-0.001 -1.65116,-10e-4 0.0983,3.6244 0.0984,3.6244 -1.14753,1.00754 c -0.63119,0.55415 -1.34035,1.00754 -1.57601,1.00754 -0.28893,0 -0.47605,0.57495 -0.57491,1.76696 -0.11787,1.42104 -0.33794,1.96816 -1.1244,2.79476 -1.13233,1.19012 -2.96046,4.69205 -2.96046,5.671 0,1.11194 -0.56115,1.80916 -1.6279,2.02253 -0.55663,0.11131 -1.67566,0.67436 -2.48682,1.25124 -1.22006,0.86773 -6.20079,3.10238 -6.91473,3.10238 -0.11119,0 -1.23238,0.43908 -2.49148,0.97576 -1.25917,0.53667 -2.86172,1.21939 -3.56125,1.51716 -0.69952,0.29776 -3.03704,1.4397 -5.19451,2.53764 -2.15747,1.09794 -4.25494,1.99626 -4.66121,1.99626 -0.4062,0 -1.06176,-0.34404 -1.4569,-0.76453 z', - fill: 'blue', - }); - layer.add(borneo); - stage.add(layer); - - var trace = layer.getContext().getTrace(); - assert.equal( - trace, - 'bezierCurveTo(209.761,60.371,209.972,60.483,210.442,60.058);bezierCurveTo(211.208,59.365,213.182,59.458,213.948,60.224);bezierCurveTo(214.736,61.012,214.765,61.779,214.028,62.324);bezierCurveTo(213.189,62.943,213.944,63.507,215.605,63.499);bezierCurveTo(217.222,63.492,217.781,63.851,217.781,64.895);bezierCurveTo(217.781,65.486,217.609,65.654,217.004,65.654);bezierCurveTo(216.521,65.654,216.154,65.884,216.034,66.262);bezierCurveTo(215.769,67.097,213.92,67.912,211.587,68.222);bezierCurveTo(209.683,68.475,206.345,69.33,203.995,70.167);bezierCurveTo(202.899,70.558,202.813,70.688,202.617,72.249);bezierCurveTo(202.363,74.286,201.938,74.673,200.082,74.559);bezierCurveTo(199.287,74.51,198.551,74.557,198.446,74.662);bezierCurveTo(198.34,74.768,198.767,75.264,199.394,75.764);bezierCurveTo(200.02,76.264,200.533,76.907,200.533,77.193);bezierCurveTo(200.533,77.479,200.749,77.892,201.012,78.111);bezierCurveTo(202.035,78.959,201.313,80.549,199.46,81.533);bezierCurveTo(198.933,81.813,198.976,81.872,199.841,82.045);bezierCurveTo(201.097,82.296,201.599,83.24,201.328,84.842);bezierCurveTo(201.174,85.751,200.96,86.096,200.549,86.096);bezierCurveTo(199.119,86.096,197.177,83.389,197.943,82.465);bezierCurveTo(198.444,81.862,196.364,81.6,193.066,81.852);bezierCurveTo(190.688,82.034,190.609,82.064,191.629,82.393);bezierCurveTo(192.308,82.613,192.886,83.097,193.187,83.7);bezierCurveTo(193.602,84.53,193.817,84.646,194.794,84.564);bezierCurveTo(195.65,84.492,196.096,84.654,196.643,85.236);bezierCurveTo(197.523,86.172,197.905,88.129,197.342,88.808);bezierCurveTo(196.885,89.358,194.763,89.46,194.26,88.957);bezierCurveTo(194.029,88.726,193.803,88.772,193.524,89.109);bezierCurveTo(193.179,89.525,193.332,89.65,194.607,89.991);bezierCurveTo(196.673,90.544,197.454,91.336,197.239,92.661);bezierCurveTo(197.113,93.437,197.228,93.85,197.683,94.263);bezierCurveTo(198.021,94.568,198.299,95.12,198.3,95.488);bezierCurveTo(198.3,95.897,198.676,96.37,199.264,96.705);bezierCurveTo(199.867,97.048,200.472,97.824,200.882,98.78);bezierCurveTo(201.256,99.652,202.464,101.266,203.7,102.544);bezierCurveTo(206.428,105.365,206.72,105.913,206.452,107.706);bezierCurveTo(206.275,108.883,206.02,109.279,204.931,110.068);bezierCurveTo(204.211,110.589,203.274,111.529,202.848,112.155);bezierCurveTo(202.187,113.128,202.126,113.421,202.43,114.156);bezierCurveTo(202.626,114.629,203.437,115.409,204.231,115.889);bezierCurveTo(205.026,116.369,205.903,117.156,206.181,117.638);bezierCurveTo(206.75,118.625,208.71,120.184,210.606,121.159);bezierCurveTo(211.302,121.517,212.303,122.268,212.83,122.827);bezierCurveTo(213.357,123.386,214.352,124.133,215.04,124.487);bezierCurveTo(216.574,125.276,219.378,127.343,219.378,127.685);bezierCurveTo(219.378,128.328,221.742,130.469,222.67,130.666);bezierCurveTo(225.788,131.329,225.383,134.112,221.782,136.763);lineTo(220.496,137.71);lineTo(215.169,137.666);bezierCurveTo(210.75,137.63,209.701,137.531,209.016,137.082);bezierCurveTo(207.896,136.348,205.324,135.868,205.324,136.393);bezierCurveTo(205.324,137.28,204.095,137.929,202.756,137.75);bezierCurveTo(201.628,137.598,201.302,137.705,200.353,138.538);bezierCurveTo(199.75,139.068,199.256,139.655,199.256,139.843);bezierCurveTo(199.256,140.453,197.238,144.688,196.606,145.403);bezierCurveTo(195.771,146.35,194.672,151.087,195.102,151.89);bezierCurveTo(195.603,152.825,195.482,154.187,194.784,155.489);bezierCurveTo(194.005,156.942,193.993,157.386,194.68,159.303);bezierCurveTo(195.14,160.586,195.356,160.818,196.167,160.9);bezierCurveTo(197.651,161.049,197.909,161.802,197.095,163.609);bezierCurveTo(196.713,164.455,196.31,165.966,196.197,166.966);bezierCurveTo(196.078,168.026,195.774,168.981,195.468,169.256);bezierCurveTo(194.785,169.872,191.766,171.055,190.86,171.063);bezierCurveTo(190.139,171.07,187.442,173.918,185.502,176.721);bezierCurveTo(184.451,178.241,182.649,179.177,181.289,178.911);bezierCurveTo(180.534,178.762,180.411,178.842,180.411,179.479);bezierCurveTo(180.411,180.392,179.659,181.079,178.121,181.573);bezierCurveTo(176.683,182.034,176.4,182.376,176.159,183.941);bezierCurveTo(175.921,185.489,175.475,186.024,173.804,186.765);lineTo(172.467,187.358);lineTo(173.245,188.136);bezierCurveTo(173.939,188.83,174.023,189.188,174.023,191.461);bezierCurveTo(174.023,193.313,173.891,194.14,173.538,194.493);bezierCurveTo(173.104,194.927,173.18,195.013,174.257,195.313);bezierCurveTo(174.919,195.496,175.775,195.648,176.16,195.649);bezierCurveTo(176.545,195.65,176.942,195.782,177.041,195.944);bezierCurveTo(177.3,196.362,176.658,200.643,176.068,202.438);lineTo(175.569,203.955);lineTo(173.918,203.954);lineTo(172.266,203.953);lineTo(172.365,207.577);lineTo(172.463,211.202);lineTo(171.316,212.209);bezierCurveTo(170.684,212.763,169.975,213.217,169.74,213.217);bezierCurveTo(169.451,213.217,169.264,213.792,169.165,214.984);bezierCurveTo(169.047,216.405,168.827,216.952,168.04,217.778);bezierCurveTo(166.908,218.969,165.08,222.471,165.08,223.449);bezierCurveTo(165.08,224.561,164.519,225.259,163.452,225.472);bezierCurveTo(162.895,225.583,161.776,226.146,160.965,226.723);bezierCurveTo(159.745,227.591,154.764,229.826,154.05,229.826);bezierCurveTo(153.939,229.826,152.818,230.265,151.559,230.801);bezierCurveTo(150.3,231.338,148.697,232.021,147.998,232.319);bezierCurveTo(147.298,232.616,144.961,233.758,142.803,234.856);bezierCurveTo(140.646,235.954,138.548,236.852,138.142,236.852);bezierCurveTo(137.736,236.852,137.08,236.508,136.685,236.088);closePath();fillStyle=blue;fill();restore();' - ); - }); - - // ====================================================== - it('Stroke and fill when no closed', function () { - // https://github.com/konvajs/konva/issues/150 - - var stage = addStage(); - var layer = new Konva.Layer(); - - var path = new Konva.Path({ - data: 'M 50 0 C 50 150 170 170 200 170', - stroke: 'black', - fill: '#ff0000', - }); - - // override color key so that we can test the context trace - path.colorKey = 'black'; - - path.on('mouseover', function () { - this.stroke('#f00'); - layer.draw(); - }); - - path.on('mouseout', function () { - this.stroke('#000'); - layer.draw(); - }); - - layer.add(path); - stage.add(layer); - - var trace = layer.getContext().getTrace(); - - //console.log(trace); - - var hitTrace = layer.hitCanvas.getContext().getTrace(); - //console.log(hitTrace); - - assert.equal( - trace, - 'clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);beginPath();moveTo(50,0);bezierCurveTo(50,150,170,170,200,170);fillStyle=#ff0000;fill();lineWidth=2;strokeStyle=black;stroke();restore();' - ); - assert.equal( - hitTrace, - 'clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);beginPath();moveTo(50,0);bezierCurveTo(50,150,170,170,200,170);save();fillStyle=black;fill();restore();lineWidth=2;strokeStyle=black;stroke();restore();' - ); - }); - - // ====================================================== - // do we need to fill hit, when it is not closed? - it('Stroke when no closed', function () { - // https://github.com/konvajs/konva/issues/867 - - var stage = addStage(); - var layer = new Konva.Layer(); - - var path = new Konva.Path({ - data: 'M 0 0 L 100 100 L 100 0', - stroke: 'black', - }); - - // override color key so that we can test the context trace - path.colorKey = 'black'; - - path.on('mouseover', function () { - this.stroke('#f00'); - layer.draw(); - }); - - path.on('mouseout', function () { - this.stroke('#000'); - layer.draw(); - }); - - layer.add(path); - stage.add(layer); - - var trace = layer.getContext().getTrace(); - - var hitTrace = layer.hitCanvas.getContext().getTrace(); - - assert.equal( - trace, - 'clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);beginPath();moveTo(0,0);lineTo(100,100);lineTo(100,0);lineWidth=2;strokeStyle=black;stroke();restore();' - ); - assert.equal( - hitTrace, - 'clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);beginPath();moveTo(0,0);lineTo(100,100);lineTo(100,0);lineWidth=2;strokeStyle=black;stroke();restore();' - ); - }); - - // ====================================================== - it('draw path with no space in numbers', function () { - // https://github.com/konvajs/konva/issues/329 - - var stage = addStage(); - var layer = new Konva.Layer(); - - var path = new Konva.Path({ - data: 'M10.5.5l10 10', - stroke: 'black', - }); - layer.add(path); - - stage.add(layer); - - var trace = layer.getContext().getTrace(); - - assert.equal( - trace, - 'clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);beginPath();moveTo(10.5,0.5);lineTo(20.5,10.5);lineWidth=2;strokeStyle=black;stroke();restore();' - ); - }); - - it('getClientRect', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var path = new Konva.Path({ - data: 'M61.55,184.55 60.55,280.55 164.55,284.55 151.55,192.55 Z', - fill: 'black', - stroke: 'red', - }); - layer.add(path); - var rect = path.getClientRect(); - assertAlmostDeepEqual(rect, { - x: 59.55, - y: 183.55, - width: 106, - height: 102, - }); - }); - - it('getClientRect of complex path', function () { - // TODO: it is failing on Node - if (isNode) { - return; - } - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var path = new Konva.Path({ - data: 'M9.9,104.71l2.19-1.27a2,2,0,0,1,1.94,0l.5.29a.5.5,0,0,1,.21.67s0,0,0,0a.5.5,0,0,1-.19.19l-2.2,1.27a1.92,1.92,0,0,1-1.94,0l-.5-.29a.51.51,0,0,1-.21-.68l0,0A.52.52,0,0,1,9.9,104.71Zm4.85-1.9.5.29a1.92,1.92,0,0,0,1.94,0l2.19-1.27a.51.51,0,0,0,.21-.68l0,0a.44.44,0,0,0-.19-.19l-.5-.29a1.92,1.92,0,0,0-1.94,0l-2.2,1.27a.5.5,0,0,0-.21.67s0,0,0,0a.5.5,0,0,0,.19.19Zm4.86-2.8.5.29a1.92,1.92,0,0,0,1.94,0L24.25,99a.51.51,0,0,0,.21-.68l0,0a.44.44,0,0,0-.19-.19l-.5-.29a1.92,1.92,0,0,0-1.94,0l-2.19,1.27a.5.5,0,0,0-.21.67s0,0,0,0a.5.5,0,0,0,.19.19Zm4.85-2.8.5.29a2,2,0,0,0,2,0l2.21-1.27a.52.52,0,0,0,.21-.68s0,0,0,0a.57.57,0,0,0-.19-.19l-.5-.29a1.92,1.92,0,0,0-1.94,0L24.51,96.3a.5.5,0,0,0-.25.66s0,0,0,0a.49.49,0,0,0,.18.2Zm4.86-2.8.5.29a2,2,0,0,0,1.94,0L34,93.43a.51.51,0,0,0,.21-.68l0,0a.44.44,0,0,0-.19-.19l-.5-.29a1.92,1.92,0,0,0-1.94,0L29.32,93.5a.52.52,0,0,0-.18.72A.47.47,0,0,0,29.32,94.41Zm4.85-2.81.5.29a1.92,1.92,0,0,0,1.94,0l2.2-1.26A.52.52,0,0,0,39,90s0,0,0,0a.57.57,0,0,0-.19-.19l-.5-.29a1.92,1.92,0,0,0-1.94,0l-2.2,1.26a.5.5,0,0,0-.22.67l0,0A.5.5,0,0,0,34.17,91.6ZM39,88.8l.5.29a1.92,1.92,0,0,0,1.94,0l2.21-1.26a.53.53,0,0,0,.18-.73.77.77,0,0,0-.18-.18l-.5-.29a2,2,0,0,0-1.94,0L39,87.9a.5.5,0,0,0-.23.67l0,0a.52.52,0,0,0,.19.2ZM43.88,86l.5.29a1.92,1.92,0,0,0,1.94,0L48.52,85a.52.52,0,0,0,.21-.68l0,0a.57.57,0,0,0-.19-.19l-.5-.29a2,2,0,0,0-2,0L43.83,85.1a.49.49,0,0,0-.17.69h0a.44.44,0,0,0,.2.18Zm4.85-2.8.5.29a1.92,1.92,0,0,0,1.94,0l2.19-1.27a.52.52,0,0,0,.21-.68l0,0a.57.57,0,0,0-.19-.19l-.5-.29a1.92,1.92,0,0,0-1.94,0L48.73,82.3a.5.5,0,0,0-.2.68l0,0a.47.47,0,0,0,.18.19Zm4.86-2.8.5.29a1.92,1.92,0,0,0,1.94,0l2.2-1.27a.52.52,0,0,0,.21-.68s0,0,0,0a.57.57,0,0,0-.19-.19l-.5-.29a1.92,1.92,0,0,0-1.94,0L53.6,79.5a.5.5,0,0,0-.21.68l0,0a.58.58,0,0,0,.19.19Zm4.85-2.8.5.29a2,2,0,0,0,2,0l2.19-1.27a.52.52,0,0,0,.21-.68s0,0,0,0a.57.57,0,0,0-.19-.19l-.5-.29a1.92,1.92,0,0,0-1.94,0l-2.2,1.27a.49.49,0,0,0-.25.66.08.08,0,0,0,0,0,.49.49,0,0,0,.18.2Zm4.86-2.8.5.29a2,2,0,0,0,1.94,0l2.2-1.27a.52.52,0,0,0,.21-.68s0,0,0,0a.57.57,0,0,0-.19-.19l-.5-.29a1.92,1.92,0,0,0-1.94,0l-2.19,1.26a.53.53,0,0,0-.19.73h0a.52.52,0,0,0,.18.18ZM68.15,72l.5.29a1.92,1.92,0,0,0,1.94,0L72.79,71a.52.52,0,0,0,.21-.68l0,0a.57.57,0,0,0-.19-.19l-.5-.29a1.92,1.92,0,0,0-1.94,0l-2.2,1.26a.5.5,0,0,0-.22.67l0,0A.5.5,0,0,0,68.15,72ZM73,69.19l.5.29a1.92,1.92,0,0,0,1.94,0l2.19-1.26a.53.53,0,0,0,.18-.73.7.7,0,0,0-.15-.19l-.5-.29a2,2,0,0,0-1.94,0L73,68.3a.49.49,0,0,0-.22.67s0,0,0,0a.5.5,0,0,0,.19.19ZM12.94,107.37l2.2,1.27a2,2,0,0,0,1.94,0l2.2-1.27a.51.51,0,0,0,.21-.68l0,0a.44.44,0,0,0-.19-.19l-2.2-1.27a1.92,1.92,0,0,0-1.94,0l-2.2,1.27a.5.5,0,0,0-.21.67s0,0,0,0A.5.5,0,0,0,12.94,107.37Zm4.86-2.8L20,105.83a1.92,1.92,0,0,0,1.94,0l2.2-1.26a.51.51,0,0,0,.21-.68l0,0a.44.44,0,0,0-.19-.19l-2.2-1.27a1.92,1.92,0,0,0-1.94,0l-2.19,1.27a.5.5,0,0,0-.17.69s0,0,0,0a.54.54,0,0,0,.15.15Zm4.85-2.8,2.2,1.26a1.92,1.92,0,0,0,1.94,0L29,101.77a.54.54,0,0,0,.19-.73.66.66,0,0,0-.19-.18L26.79,99.6a1.92,1.92,0,0,0-1.94,0l-2.18,1.26a.52.52,0,0,0-.19.72.58.58,0,0,0,.19.19ZM27.51,99l2.19,1.27a1.92,1.92,0,0,0,1.94,0L33.84,99a.51.51,0,0,0,.21-.68l0,0a.44.44,0,0,0-.19-.19L31.67,96.8a1.92,1.92,0,0,0-1.94,0l-2.19,1.26a.5.5,0,0,0-.22.67l0,0a.5.5,0,0,0,.19.19Zm4.85-2.8,2.2,1.27a1.92,1.92,0,0,0,1.94,0l2.19-1.27a.51.51,0,0,0,.21-.68l0,0a.44.44,0,0,0-.19-.19L36.5,94a2,2,0,0,0-1.94,0l-2.2,1.27a.5.5,0,0,0-.21.67s0,0,0,0A.5.5,0,0,0,32.36,96.16Zm4.86-2.8,2.19,1.27a1.92,1.92,0,0,0,1.94,0l2.2-1.27a.51.51,0,0,0,.21-.68l0,0a.44.44,0,0,0-.19-.19l-2.2-1.27a2,2,0,0,0-1.94,0l-2.19,1.27a.5.5,0,0,0-.21.67l0,0A.5.5,0,0,0,37.22,93.36Zm4.85-2.8,2.2,1.27a2,2,0,0,0,1.94,0l2.19-1.27a.51.51,0,0,0,.21-.68l0,0a.44.44,0,0,0-.19-.19l-2.19-1.27a1.92,1.92,0,0,0-1.94,0l-2.2,1.27a.5.5,0,0,0-.2.68.48.48,0,0,0,.2.2Zm4.85-2.8L49.12,89a2,2,0,0,0,1.94,0l2.2-1.27a.51.51,0,0,0,.21-.68l0,0a.44.44,0,0,0-.19-.19l-2.2-1.27a1.92,1.92,0,0,0-1.94,0l-2.2,1.27a.5.5,0,0,0-.21.67s0,0,0,0A.5.5,0,0,0,46.92,87.76ZM51.78,85,54,86.22a1.94,1.94,0,0,0,2,0L58.16,85a.51.51,0,0,0,.21-.68l0,0a.44.44,0,0,0-.19-.19L56,82.8a1.94,1.94,0,0,0-2,0l-2.19,1.27a.5.5,0,0,0-.21.67s0,0,0,0a.5.5,0,0,0,.19.19Zm4.85-2.8,2.2,1.26a1.92,1.92,0,0,0,1.94,0L63,82.16a.51.51,0,0,0,.21-.68l0,0a.62.62,0,0,0-.18-.18L60.77,80a1.92,1.92,0,0,0-1.94,0l-2.2,1.26a.52.52,0,0,0-.18.72A.47.47,0,0,0,56.63,82.16Zm4.86-2.81,2.19,1.27a1.92,1.92,0,0,0,1.94,0l2.2-1.27a.51.51,0,0,0,.21-.68l0,0a.44.44,0,0,0-.19-.19l-2.2-1.26a1.92,1.92,0,0,0-1.94,0l-2.19,1.26a.5.5,0,0,0-.21.67s0,0,0,0A.5.5,0,0,0,61.49,79.35Zm4.85-2.8,2.2,1.27a1.92,1.92,0,0,0,1.94,0l2.19-1.27a.52.52,0,0,0,.21-.68l0,0a.57.57,0,0,0-.19-.19l-2.19-1.27a2,2,0,0,0-1.94,0l-2.2,1.27a.5.5,0,0,0-.2.68l0,0a.62.62,0,0,0,.18.18Zm4.91-2.83L73.45,75a2,2,0,0,0,1.94,0l7-4a.51.51,0,0,0,.21-.68l0,0a.44.44,0,0,0-.19-.19L80.2,68.8a2,2,0,0,0-1.94,0l-7,4a.49.49,0,0,0-.22.67s0,0,0,0a.5.5,0,0,0,.19.19ZM17.83,110.19l2.2,1.27a2,2,0,0,0,1.94,0l3.82-2.21a.51.51,0,0,0,.21-.68l0,0a.44.44,0,0,0-.19-.19l-2.19-1.27a1.92,1.92,0,0,0-1.94,0l-3.83,2.22a.48.48,0,0,0-.17.68v0a.5.5,0,0,0,.19.19Zm6.5-3.74,2.19,1.26a1.92,1.92,0,0,0,1.94,0l2.21-1.26a.52.52,0,0,0,.21-.68l0,0a.77.77,0,0,0-.18-.18l-2.22-1.24a1.92,1.92,0,0,0-1.94,0l-2.19,1.26a.52.52,0,0,0-.18.72.47.47,0,0,0,.18.19Zm4.85-2.8,2.2,1.26a1.92,1.92,0,0,0,1.94,0l2.19-1.26a.51.51,0,0,0,.21-.68l0,0a.62.62,0,0,0-.18-.18l-2.19-1.26a1.92,1.92,0,0,0-1.94,0l-2.21,1.26a.52.52,0,0,0-.19.72.58.58,0,0,0,.19.19ZM34,100.84l2.2,1.27a1.92,1.92,0,0,0,1.94,0l2.2-1.27a.51.51,0,0,0,.21-.68l0,0a.44.44,0,0,0-.19-.19l-2.2-1.27a2,2,0,0,0-1.94,0L34,99.94a.5.5,0,0,0-.23.67l0,0a.52.52,0,0,0,.19.2ZM38.89,98l2.18,1.26a1.94,1.94,0,0,0,2,0L45.26,98a.51.51,0,0,0,.21-.68l0,0a.44.44,0,0,0-.19-.19l-2.19-1.27a2,2,0,0,0-2,0l-2.19,1.27a.5.5,0,0,0-.21.67s0,0,0,0a.5.5,0,0,0,.19.19Zm4.85-2.8,2.2,1.27a2,2,0,0,0,1.94,0l2.2-1.27a.51.51,0,0,0,.21-.68l0,0a.44.44,0,0,0-.19-.19l-2.2-1.27a2,2,0,0,0-1.94,0l-2.2,1.27a.5.5,0,0,0-.21.67s0,0,0,0A.5.5,0,0,0,43.74,95.24Zm4.86-2.8,2.19,1.27a2,2,0,0,0,1.94,0l2.2-1.27a.51.51,0,0,0,.21-.68l0,0a.44.44,0,0,0-.19-.19l-2.2-1.27a1.92,1.92,0,0,0-1.94,0L48.6,91.54a.5.5,0,0,0-.21.67s0,0,0,0A.5.5,0,0,0,48.6,92.44Zm4.85-2.8,2.2,1.27a2,2,0,0,0,1.94,0l2.19-1.27A.51.51,0,0,0,60,89l0,0a.44.44,0,0,0-.19-.19l-2.19-1.27a1.92,1.92,0,0,0-1.94,0l-2.2,1.27a.5.5,0,0,0-.21.67l0,0A.5.5,0,0,0,53.45,89.64Zm4.86-2.8L60.5,88.1a1.92,1.92,0,0,0,1.94,0l2.2-1.26a.54.54,0,0,0,.17-.74.46.46,0,0,0-.17-.17l-2.2-1.26a1.92,1.92,0,0,0-1.94,0l-2.19,1.26a.52.52,0,0,0-.18.72.47.47,0,0,0,.18.19ZM63.16,84l2.2,1.26a1.92,1.92,0,0,0,1.94,0L69.49,84a.54.54,0,0,0,.17-.74.46.46,0,0,0-.17-.17L67.3,81.87a1.92,1.92,0,0,0-1.94,0l-2.2,1.26a.52.52,0,0,0-.18.72.47.47,0,0,0,.18.19ZM68,81.23l2.19,1.27a1.92,1.92,0,0,0,1.94,0l2.2-1.27a.52.52,0,0,0,.19-.71.44.44,0,0,0-.19-.19l-2.2-1.26a1.92,1.92,0,0,0-1.94,0L68,80.33a.5.5,0,0,0-.22.67l0,0a.41.41,0,0,0,.18.19Zm4.85-2.8,2.19,1.27a1.94,1.94,0,0,0,2,0l2.19-1.27a.51.51,0,0,0,.21-.68l0,0a.44.44,0,0,0-.19-.19l-2.19-1.27a2,2,0,0,0-2,0l-2.19,1.27a.5.5,0,0,0-.21.67s0,0,0,0A.5.5,0,0,0,72.87,78.43Zm4.91-2.83L80,76.87a2,2,0,0,0,1.94,0l5.36-3.1a.51.51,0,0,0,.21-.68l0,0a.44.44,0,0,0-.19-.19l-2.2-1.27a2,2,0,0,0-1.94,0l-5.36,3.1a.5.5,0,0,0-.21.67s0,0,0,0A.5.5,0,0,0,77.78,75.6ZM22.78,113,25,114.3a1.92,1.92,0,0,0,1.94,0l3.83-2.2a.54.54,0,0,0,.17-.74.46.46,0,0,0-.17-.17l-2.2-1.26a1.92,1.92,0,0,0-1.94,0l-3.83,2.2a.52.52,0,0,0-.19.72h0a.55.55,0,0,0,.18.19Zm6.5-3.75,2.2,1.27a1.92,1.92,0,0,0,1.94,0l2.17-1.26a.51.51,0,0,0,.21-.68l0,0a.44.44,0,0,0-.19-.19l-2.19-1.26a1.92,1.92,0,0,0-1.94,0l-2.2,1.26a.5.5,0,0,0-.22.67l0,0a.41.41,0,0,0,.18.19Zm4.85-2.8,2.2,1.27a1.92,1.92,0,0,0,1.94,0l2.2-1.27a.51.51,0,0,0,.2-.68h0a.57.57,0,0,0-.19-.19l-2.23-1.31a2,2,0,0,0-1.94,0l-2.2,1.27a.5.5,0,0,0-.29.65l0,0a.53.53,0,0,0,.26.27Zm4.86-2.8L41.17,105a2,2,0,0,0,1.95,0l2.19-1.27a.52.52,0,0,0,.21-.68l0,0a.57.57,0,0,0-.19-.19l-2.2-1.25a2,2,0,0,0-2,0L39,102.8a.5.5,0,0,0-.15.69s0,0,0,0a.44.44,0,0,0,.17.16Zm4.85-2.8,2.2,1.27a2,2,0,0,0,1.94,0l2.19-1.27a.52.52,0,0,0,.19-.71.44.44,0,0,0-.19-.19L48,98.72a1.92,1.92,0,0,0-1.94,0L43.84,100a.5.5,0,0,0-.22.67l0,0a.41.41,0,0,0,.18.19Zm4.86-2.8,2.19,1.27a2,2,0,0,0,1.94,0L55,98.09a.52.52,0,0,0,.19-.71.44.44,0,0,0-.19-.19l-2.2-1.27a1.92,1.92,0,0,0-1.94,0l-2.22,1.27a.49.49,0,0,0-.22.67s0,0,0,0a.44.44,0,0,0,.19.19Zm4.85-2.8,2.2,1.26a1.92,1.92,0,0,0,1.94,0l2.19-1.26a.52.52,0,0,0,.19-.71.58.58,0,0,0-.19-.19l-2.21-1.27a1.92,1.92,0,0,0-1.94,0l-2.2,1.27a.49.49,0,0,0-.22.67s0,0,0,0a.44.44,0,0,0,.19.19Zm4.86-2.8,2.19,1.26a1.92,1.92,0,0,0,1.94,0l2.2-1.26a.54.54,0,0,0,.17-.74.46.46,0,0,0-.17-.17l-2.2-1.26a1.92,1.92,0,0,0-1.94,0l-2.19,1.26a.53.53,0,0,0-.26.7.57.57,0,0,0,.25.26Zm4.85-2.81L65.46,91a1.92,1.92,0,0,0,1.94,0l2.19-1.27a.52.52,0,0,0,.19-.71.58.58,0,0,0-.19-.19L67.4,87.52a1.92,1.92,0,0,0-1.94,0l-2.2,1.26a.5.5,0,0,0-.22.67l0,0a.41.41,0,0,0,.18.19Zm4.86-2.8,2.19,1.27a1.92,1.92,0,0,0,1.94,0l2.2-1.27a.52.52,0,0,0,.19-.71.58.58,0,0,0-.19-.19l-2.2-1.27a2,2,0,0,0-1.94,0L68.12,86a.5.5,0,0,0-.22.67l0,0a.41.41,0,0,0,.18.19ZM73,84.08l2.2,1.27a2,2,0,0,0,2,0l2.19-1.27a.53.53,0,0,0,.19-.71.69.69,0,0,0-.19-.19l-2.19-1.27a2,2,0,0,0-2,0L73,83.18a.49.49,0,0,0-.22.67s0,0,0,0a.44.44,0,0,0,.19.19Zm4.85-2.8L80,82.54a2,2,0,0,0,1.94,0l2.2-1.27a.51.51,0,0,0,.21-.68l0,0a.44.44,0,0,0-.19-.19L82,79.1a1.92,1.92,0,0,0-1.94,0l-2.2,1.27a.5.5,0,0,0-.23.67l0,0a.58.58,0,0,0,.2.21Zm4.92-2.83,2.19,1.26a1.92,1.92,0,0,0,1.94,0l5.36-3.09a.52.52,0,0,0,.19-.71.58.58,0,0,0-.19-.19L90,74.45a1.92,1.92,0,0,0-1.94,0l-5.36,3.09a.53.53,0,0,0-.18.73.52.52,0,0,0,.18.18Zm-55,37.43,2.19,1.27a1.94,1.94,0,0,0,1.95,0l7.29-4.21a.55.55,0,0,0,.17-.74.46.46,0,0,0-.17-.17L37,110.77a1.92,1.92,0,0,0-1.94,0L27.73,115a.5.5,0,0,0-.23.67l0,0a.52.52,0,0,0,.19.2Zm10-5.75,2.2,1.27a1.92,1.92,0,0,0,1.94,0l2.19-1.27a.52.52,0,0,0,.19-.71.58.58,0,0,0-.19-.19L41.88,108a1.92,1.92,0,0,0-1.94,0l-2.2,1.26a.5.5,0,0,0-.26.66l0,0a.46.46,0,0,0,.18.21Zm4.86-2.8,2.19,1.27a1.92,1.92,0,0,0,1.94,0l2.2-1.27a.52.52,0,0,0,.19-.71.58.58,0,0,0-.19-.19l-2.2-1.27a2,2,0,0,0-1.94,0l-2.19,1.27a.5.5,0,0,0-.26.66l0,0a.46.46,0,0,0,.18.21Zm4.85-2.8,2.2,1.27a1.92,1.92,0,0,0,1.94,0l2.19-1.27a.52.52,0,0,0,.19-.71.58.58,0,0,0-.19-.19l-2.19-1.27a2,2,0,0,0-1.94,0l-2.2,1.27a.5.5,0,0,0-.3.64l0,0a.45.45,0,0,0,.19.22Zm4.86-2.8L54.5,103a2,2,0,0,0,1.94,0l2.2-1.27a.52.52,0,0,0,.19-.71.58.58,0,0,0-.19-.19l-2.2-1.27a1.92,1.92,0,0,0-1.94,0l-2.19,1.27a.5.5,0,0,0-.26.66l0,0a.46.46,0,0,0,.18.21Zm4.85-2.8,2.19,1.27a2,2,0,0,0,2,0l2.19-1.27a.52.52,0,0,0,.19-.71.58.58,0,0,0-.19-.19l-2.19-1.27a1.94,1.94,0,0,0-2,0L57.17,98a.49.49,0,0,0-.26.66s0,0,0,0a.41.41,0,0,0,.18.21ZM62,96.13l2.2,1.26a1.92,1.92,0,0,0,1.94,0l2.2-1.26a.52.52,0,0,0,.19-.71.58.58,0,0,0-.19-.19L66.15,94a1.92,1.92,0,0,0-1.94,0L62,95.23a.5.5,0,0,0-.26.66l0,0a.46.46,0,0,0,.18.21Zm4.86-2.8,2.19,1.26a1.92,1.92,0,0,0,1.94,0l2.2-1.26a.54.54,0,0,0,.17-.74.46.46,0,0,0-.17-.17L71,91.16a1.92,1.92,0,0,0-1.94,0l-2.19,1.26a.52.52,0,0,0-.23.71.55.55,0,0,0,.17.2Zm4.85-2.81,2.2,1.27a1.92,1.92,0,0,0,1.94,0L78,90.54a.51.51,0,0,0,.21-.68l0,0a.44.44,0,0,0-.19-.19l-2.19-1.26a1.92,1.92,0,0,0-1.94,0l-2.2,1.26a.51.51,0,0,0-.24.67l0,0a.49.49,0,0,0,.18.2Zm4.86-2.8L78.77,89a1.92,1.92,0,0,0,1.94,0l2.2-1.27A.52.52,0,0,0,83.1,87a.58.58,0,0,0-.19-.19l-2.2-1.27a2,2,0,0,0-1.94,0L76.56,86.8a.5.5,0,0,0-.26.66l0,0a.46.46,0,0,0,.18.21Zm4.85-2.8,2.2,1.27a1.92,1.92,0,0,0,1.94,0l2.19-1.27a.52.52,0,0,0,.19-.71.58.58,0,0,0-.19-.19l-2.19-1.27a2,2,0,0,0-1.94,0L81.43,84a.5.5,0,0,0-.26.66l0,0a.46.46,0,0,0,.18.21Zm4.84-2.79,2.2,1.26a1.92,1.92,0,0,0,1.94,0l6.81-3.92a.54.54,0,0,0,.17-.74.46.46,0,0,0-.17-.17L95,77.3a1.92,1.92,0,0,0-1.94,0l-6.81,3.93a.5.5,0,0,0-.26.66l0,0a.46.46,0,0,0,.18.21Zm-53.58,36.6,2.2,1.27a2,2,0,0,0,1.94,0l3.84-2.2a.53.53,0,0,0,.19-.71.69.69,0,0,0-.19-.19l-2.2-1.27a1.92,1.92,0,0,0-1.94,0l-3.83,2.21a.49.49,0,0,0-.26.66s0,0,0,0a.41.41,0,0,0,.18.21Zm6.5-3.74,2.19,1.27a2,2,0,0,0,1.94,0l2.2-1.27a.52.52,0,0,0,.19-.71.58.58,0,0,0-.19-.19l-2.21-1.29a1.92,1.92,0,0,0-1.94,0l-2.2,1.29a.49.49,0,0,0-.26.66s0,0,0,0a.41.41,0,0,0,.18.21Zm4.85-2.8,2.2,1.26a1.92,1.92,0,0,0,1.94,0l2.19-1.26a.52.52,0,0,0,.19-.71.58.58,0,0,0-.19-.19L48.17,110a1.92,1.92,0,0,0-1.94,0L44,111.3a.51.51,0,0,0-.24.67l0,0a.49.49,0,0,0,.18.2Zm4.86-2.8,2.19,1.26a1.92,1.92,0,0,0,1.94,0l2.2-1.26a.54.54,0,0,0,.17-.74.46.46,0,0,0-.17-.17L53,107.22a1.92,1.92,0,0,0-1.94,0l-2.19,1.26a.52.52,0,0,0-.23.71.55.55,0,0,0,.17.2Zm4.85-2.81,2.2,1.27a1.92,1.92,0,0,0,1.94,0L79.48,95.39a.52.52,0,0,0,.19-.71.58.58,0,0,0-.19-.19l-2.19-1.27a1.94,1.94,0,0,0-2,0L53.7,105.68a.5.5,0,0,0-.17.69l0,0a.44.44,0,0,0,.13.14ZM78,92.54l2.2,1.26a1.92,1.92,0,0,0,1.94,0l2.2-1.26a.51.51,0,0,0,.21-.68l0,0a.44.44,0,0,0-.19-.19l-2.2-1.27a1.92,1.92,0,0,0-1.94,0L78,91.64a.5.5,0,0,0-.21.67l0,0a.47.47,0,0,0,.18.19Zm4.86-2.8L85.06,91A1.92,1.92,0,0,0,87,91l2.2-1.26a.52.52,0,0,0,0-.91L87,87.57a1.92,1.92,0,0,0-1.94,0l-2.19,1.26a.53.53,0,0,0-.05,1Zm4.85-2.81,2.2,1.27a1.92,1.92,0,0,0,1.94,0l2.19-1.27a.51.51,0,0,0,.21-.68l0,0a.44.44,0,0,0-.19-.19l-2.19-1.26a1.92,1.92,0,0,0-1.94,0L87.72,86a.51.51,0,0,0-.34.62.54.54,0,0,0,.29.32Zm4.91-2.83,2.2,1.27a2,2,0,0,0,1.94,0l5.36-3.1a.51.51,0,0,0,.21-.68l0,0a.44.44,0,0,0-.19-.19L99.94,80.1a2,2,0,0,0-1.94,0l-5.37,3.1a.51.51,0,0,0-.34.62.54.54,0,0,0,.29.32ZM59,109.65l12.15,7a1.92,1.92,0,0,0,1.94,0l21.57-12.46a.52.52,0,0,0,.21-.68l0,0a.54.54,0,0,0-.19-.18l-12.15-7a2,2,0,0,0-1.94,0L59,108.75a.52.52,0,0,0-.19.72.49.49,0,0,0,.2.18Z', - fill: 'black', - stroke: 'red', - }); - layer.add(path); - var rect = path.getClientRect(); - - var back = new Konva.Rect({ - x: rect.x, - y: rect.y, - width: rect.width, - height: rect.height, - stroke: 'red', - }); - layer.add(back); - layer.draw(); - - assertAlmostDeepEqual(rect, { - x: 8.6440882161882, - y: 65.75902834, - width: 94.74182356762, - height: 55.4919433, - }); - }); - - it('getClientRect of another complex path', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var path = new Konva.Path({ - x: 50, - y: 50, - data: 'M0,29 C71,-71,142,128,213,29 L213,207 C142,307,71,108,0,207 L0,29 Z', - fill: 'black', - stroke: 'red', - scaleY: 0.3, - }); - layer.add(path); - var rect = path.getClientRect(); - - var back = new Konva.Rect({ - x: rect.x, - y: rect.y, - width: rect.width, - height: rect.height, - stroke: 'red', - }); - layer.add(back); - layer.draw(); - - assertAlmostDeepEqual(rect, { - x: 49, - y: 49.7086649, - width: 215, - height: 71.3826701999, - }); - }); - - it('getClientRect of one more path', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var path = new Konva.Path({ - x: 50, - y: 50, - data: 'M25.21,2.36C22.11,6.1,19,10.17,22.1,15.52a2.14,2.14,0,0,1,.22.69c.18,1.09-.52,1.31-1.31,1.11C19.88,17,19.29,16,18.55,15.21a12.71,12.71,0,0,0-7.82-4.28c-3.24-.42-7.9,1.26-9,3.68-2.24,5-2.64,10.23.66,14.94a26,26,0,0,0,11.57,9c6.17,2.56,12.6,4.45,18.67,7.28,1.33.62,1.67-.14,2.11-1.12,3.84-8.44,5.64-17.32,6-28.25.53-3.82-1.37-8.64-4.3-13.12C33.91-.58,28.2-1.24,25.21,2.36Z', - fill: 'black', - stroke: 'red', - }); - layer.add(path); - var rect = path.getClientRect(); - - var back = new Konva.Rect({ - x: rect.x, - y: rect.y, - width: rect.width, - height: rect.height, - stroke: 'red', - }); - layer.add(back); - layer.draw(); - - assertAlmostDeepEqual(rect, { - x: 48.981379, - y: 48.996825, - width: 42.84717526, - height: 48.057550000000006, - }); - }); - - it('getClientRect for arc', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var path = new Konva.Path({ - data: 'M -12274.95703125 17975.16015625 C -12271.4072265625 17975.16015625 -12268.017578125 17974.345703125 -12264.8837890625 17972.740234375 C -12261.892578125 17971.208984375 -12259.24609375 17968.97265625 -12257.2314453125 17966.2734375 L -12256.775390625 17965.662109375 L -12256.0654296875 17965.939453125 C -12253.494140625 17966.947265625 -12250.7783203125 17967.45703125 -12247.9921875 17967.45703125 C -12245.01171875 17967.45703125 -12242.1201171875 17966.873046875 -12239.396484375 17965.720703125 C -12236.765625 17964.607421875 -12234.4013671875 17963.013671875 -12232.3701171875 17960.982421875 C -12230.3388671875 17958.953125 -12228.7431640625 17956.587890625 -12227.62890625 17953.95703125 C -12226.4755859375 17951.232421875 -12225.890625 17948.337890625 -12225.890625 17945.35546875 C -12225.890625 17941.30859375 -12226.99609375 17937.349609375 -12229.0888671875 17933.90625 C -12231.12109375 17930.5625 -12234.01171875 17927.802734375 -12237.447265625 17925.927734375 L -12237.849609375 17925.708984375 L -12237.9462890625 17925.26171875 C -12238.99609375 17920.408203125 -12241.708984375 17915.994140625 -12245.5830078125 17912.83203125 C -12247.5146484375 17911.2578125 -12249.6748046875 17910.029296875 -12252.0068359375 17909.18359375 C -12254.41796875 17908.306640625 -12256.9541015625 17907.86328125 -12259.54296875 17907.86328125 C -12263.171875 17907.86328125 -12266.7568359375 17908.75390625 -12269.9111328125 17910.44140625 L -12270.556640625 17910.78515625 L -12271.0810546875 17910.275390625 C -12275.2353515625 17906.2265625 -12280.7138671875 17903.99609375 -12286.5078125 17903.99609375 C -12288.9462890625 17903.99609375 -12291.34375 17904.390625 -12293.630859375 17905.171875 C -12295.84375 17905.92578125 -12297.916015625 17907.0234375 -12299.791015625 17908.4375 C -12301.646484375 17909.8359375 -12303.2646484375 17911.509765625 -12304.599609375 17913.41015625 C -12305.9541015625 17915.3359375 -12306.984375 17917.44921875 -12307.6640625 17919.69140625 L -12307.8193359375 17920.203125 L -12308.3310546875 17920.359375 C -12310.5712890625 17921.0390625 -12312.6826171875 17922.068359375 -12314.6044921875 17923.421875 C -12316.501953125 17924.755859375 -12318.1708984375 17926.37109375 -12319.56640625 17928.224609375 C -12322.466796875 17932.078125 -12324 17936.671875 -12324 17941.51171875 C -12324 17944.498046875 -12323.416015625 17947.392578125 -12322.2646484375 17950.1171875 C -12321.15234375 17952.75 -12319.55859375 17955.11328125 -12317.529296875 17957.142578125 C -12315.5 17959.171875 -12313.1376953125 17960.765625 -12310.505859375 17961.876953125 C -12307.7822265625 17963.029296875 -12304.8876953125 17963.61328125 -12301.90234375 17963.61328125 C -12299.7900390625 17963.61328125 -12297.71875 17963.322265625 -12295.744140625 17962.74609375 L -12294.95703125 17962.517578125 L -12294.578125 17963.24609375 C -12292.7373046875 17966.78125 -12289.9716796875 17969.759765625 -12286.580078125 17971.861328125 C -12283.095703125 17974.01953125 -12279.076171875 17975.16015625 -12274.95703125 17975.16015625 M -12274.95703125 17976.16015625 C -12283.8671875 17976.16015625 -12291.609375 17971.11328125 -12295.46484375 17963.70703125 C -12297.50390625 17964.30078125 -12299.66796875 17964.61328125 -12301.90234375 17964.61328125 C -12314.6640625 17964.61328125 -12325 17954.27734375 -12325 17941.51171875 C -12325 17931.08203125 -12318.1015625 17922.27734375 -12308.62109375 17919.40234375 C -12305.74609375 17909.91015625 -12296.92578125 17902.99609375 -12286.5078125 17902.99609375 C -12280.234375 17902.99609375 -12274.54296875 17905.50390625 -12270.3828125 17909.55859375 C -12267.15234375 17907.83203125 -12263.46484375 17906.86328125 -12259.54296875 17906.86328125 C -12248.48046875 17906.86328125 -12239.21875 17914.65234375 -12236.96875 17925.05078125 C -12229.78125 17928.97265625 -12224.890625 17936.58984375 -12224.890625 17945.35546875 C -12224.890625 17958.11328125 -12235.25 17968.45703125 -12247.9921875 17968.45703125 C -12250.96875 17968.45703125 -12253.81640625 17967.89453125 -12256.4296875 17966.87109375 C -12260.640625 17972.51171875 -12267.37109375 17976.16015625 -12274.95703125 17976.16015625 Z', - fill: 'black', - stroke: 'blue', - strokeWidth: 10, - }); - layer.add(path); - var rect = path.getClientRect(); - - var scale = stage.height() / rect.height / 2; - - path.x(-rect.x * scale); - path.y(-rect.y * scale); - path.scaleX(scale); - path.scaleY(scale); - - rect = path.getClientRect(); - - var back = new Konva.Rect({ - x: rect.x, - y: rect.y, - width: rect.width, - height: rect.height, - stroke: 'red', - }); - layer.add(back); - layer.draw(); - - assertAlmostDeepEqual(rect, { - x: 0, - y: 0, - width: 132.4001878816343, - height: 100, - }); - }); - - it('getClientRect on scaled', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var path = new Konva.Path({ - x: -100, - y: -190, - data: 'M10 10 h10 v10 h-10 z', - fill: 'yellow', - stroke: 'blue', - strokeWidth: 0.1, - scaleX: 20, - scaleY: 20, - }); - layer.add(path); - var rect = path.getClientRect(); - - var back = new Konva.Rect({ - x: rect.x, - y: rect.y, - width: rect.width, - height: rect.height, - stroke: 'red', - }); - layer.add(back); - layer.draw(); - - assertAlmostDeepEqual(rect, { - height: 201.99999999999994, - width: 201.99999999999994, - x: 99, - y: 9, - }); - }); - - it('check arc parsing', function () { - var stage = addStage(); - var layer1 = new Konva.Layer(); - stage.add(layer1); - - const weirdPath = new Konva.Path({ - x: 40, - y: 40, - scale: { x: 5, y: 5 }, - data: - 'M16 5.095c0-2.255-1.88-4.083-4.2-4.083-1.682 0-3.13.964-3.8 2.352' + - 'a4.206 4.206 0 00-3.8-2.352' + // Merged arc command flags (00) - 'C1.88 1.012 0 2.84 0 5.095c0 .066.007.13.01.194H.004c.001.047.01.096.014.143l.013.142c.07.8.321 1.663.824 2.573C2.073 10.354 4.232 12.018 8 15c3.767-2.982 5.926-4.647 7.144-6.854.501-.905.752-1.766.823-2.562.007-.055.012-.11.016-.164.003-.043.012-.088.013-.13h-.006c.003-.066.01-.13.01-.195z', - fill: 'red', - }); - layer1.add(weirdPath); - layer1.draw(); - - const layer2 = new Konva.Layer(); - stage.add(layer2); - - const normalPath = new Konva.Path({ - x: 40, - y: 40, - scale: { x: 5, y: 5 }, - data: - 'M16 5.095c0-2.255-1.88-4.083-4.2-4.083-1.682 0-3.13.964-3.8 2.352' + - 'a4.206 4.206 0 0 0-3.8-2.352' + // Spaced arc command flags (0 0) - 'C1.88 1.012 0 2.84 0 5.095c0 .066.007.13.01.194H.004c.001.047.01.096.014.143l.013.142c.07.8.321 1.663.824 2.573C2.073 10.354 4.232 12.018 8 15c3.767-2.982 5.926-4.647 7.144-6.854.501-.905.752-1.766.823-2.562.007-.055.012-.11.016-.164.003-.043.012-.088.013-.13h-.006c.003-.066.01-.13.01-.195z', - fill: 'red', - }); - layer2.add(normalPath); - layer2.draw(); - - var trace1 = layer1.getContext().getTrace(); - var trace2 = layer2.getContext().getTrace(); - - assert.equal(trace1, trace2); - }); - - it('draw path with fillRule', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - - var path = new Konva.Path({ - data: 'M200,100h100v50z', - fill: '#ccc', - fillRule: 'evenodd', - }); - - layer.add(path); - stage.add(layer); - - const trace = layer.getContext().getTrace(); - - assert.equal( - trace, - 'clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);beginPath();moveTo(200,100);lineTo(300,100);lineTo(300,150);closePath();fillStyle=#ccc;fill(evenodd);restore();' - ); - }); -}); diff --git a/test/unit/PointerEvents-test.ts b/test/unit/PointerEvents-test.ts deleted file mode 100644 index f2d36095b..000000000 --- a/test/unit/PointerEvents-test.ts +++ /dev/null @@ -1,229 +0,0 @@ -import { assert } from 'chai'; - -import { - addStage, - Konva, - simulatePointerDown, - simulatePointerMove, - simulatePointerUp, -} from './test-utils'; - -describe.skip('PointerEvents', function () { - // ====================================================== - it('pointerdown pointerup pointermove', function (done) { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'red', - stroke: 'black', - strokeWidth: 4, - }); - - // mobile events - var pointerdown = false; - var pointerup = false; - var pointermove = false; - - /* - * mobile - */ - circle.on('pointerdown', function () { - pointerdown = true; - }); - - circle.on('pointerup', function () { - pointerup = true; - }); - - circle.on('pointermove', function () { - pointermove = true; - }); - - layer.add(circle); - stage.add(layer); - - // touchstart circle - simulatePointerDown(stage, { - x: 289, - y: 100, - }); - - assert(pointerdown, '1) pointerdown should be true'); - assert(!pointermove, '1) pointermove should be false'); - assert(!pointerup, '1) pointerup should be false'); - - // pointerup circle - simulatePointerUp(stage, { - x: 289, - y: 100, - }); - - assert(pointerdown, '2) pointerdown should be true'); - assert(!pointermove, '2) pointermove should be false'); - assert(pointerup, '2) pointerup should be true'); - - // pointerdown circle - simulatePointerDown(stage, { - x: 289, - y: 100, - }); - - assert(pointerdown, '3) pointerdown should be true'); - assert(!pointermove, '3) pointermove should be false'); - assert(pointerup, '3) pointerup should be true'); - - // pointerup circle to triger dbltap - simulatePointerUp(stage, { - x: 289, - y: 100, - }); - // end drag is tied to document mouseup and pointerup event - // which can't be simulated. call _endDrag manually - //Konva.DD._endDrag(); - - assert(pointerdown, '4) pointerdown should be true'); - assert(!pointermove, '4) pointermove should be false'); - assert(pointerup, '4) pointerup should be true'); - - setTimeout(function () { - // pointermove circle - simulatePointerMove(stage, { - x: 290, - y: 100, - }); - - assert(pointerdown, '5) pointerdown should be true'); - assert(pointermove, '5) pointermove should be true'); - assert(pointerup, '5) pointerup should be true'); - - done(); - }, 17); - }); - - // ====================================================== - it.skip('pointer capture', function (done) { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'red', - stroke: 'black', - strokeWidth: 4, - }); - - var circle2 = new Konva.Circle({ - x: stage.width() / 2, - y: 20, - radius: 20, - fill: 'red', - stroke: 'black', - strokeWidth: 4, - }); - - // mobile events - var downCount = 0; - var otherDownCount = 0; - - var pointerup = false; - var pointermove = false; - - circle2.on('pointerdown', function () { - otherDownCount++; - }); - - circle.on('pointerdown', function (event) { - downCount++; - this.setPointerCapture(event['pointerId']); - }); - - circle.on('pointerup', function (evt) { - assert( - this.hasPointerCapture(evt['pointerId']), - 'circle released capture' - ); - pointerup = true; - }); - - circle.on('pointermove', function (evt) { - assert(this.hasPointerCapture(evt['pointerId']), 'circle has capture'); - pointermove = true; - }); - - layer.add(circle); - layer.add(circle2); - stage.add(layer); - - // on circle 2 to confirm it works - simulatePointerDown(stage, { - x: 289, - y: 10, - pointerId: 0, - preventDefault: function () {}, - }); - - assert.equal(otherDownCount, 1, '6) otherDownCount should be 1'); - assert(downCount === 0, '6) downCount should be 0'); - assert(!pointermove, '6) pointermove should be false'); - assert(!pointerup, '6) pointerup should be false'); - - // on circle with capture - simulatePointerDown(stage, { - x: 289, - y: 100, - pointerId: 1, - preventDefault: function () {}, - }); - - assert.equal(otherDownCount, 1, '7) otherDownCount should be 1'); - assert(downCount === 1, '7) downCount should be 1'); - assert(!pointermove, '7) pointermove should be false'); - assert(!pointerup, '7) pointerup should be true'); - - // second pointerdown - simulatePointerDown(stage, { - x: 289, - y: 10, - pointerId: 2, - preventDefault: function () {}, - }); - - assert.equal(otherDownCount, 1, '8) otherDownCount should be 1'); - assert(downCount === 2, '8) pointerdown should be 2'); - assert(!pointermove, '8) pointermove should be false'); - assert(!pointerup, '8) pointerup should be true'); - - setTimeout(function () { - // pointermove over circle 2 - simulatePointerMove(stage, { - x: 290, - y: 10, - pointerId: 1, - }); - - assert(otherDownCount === 1, '9) otherDownCount should be 1'); - assert(pointermove, '9) pointermove should be true'); - - simulatePointerUp(stage, { - pointerId: 1, - preventDefault: function () {}, - }); - - simulatePointerDown(stage, { - x: 289, - y: 10, - pointerId: 1, - preventDefault: function () {}, - }); - - assert(otherDownCount === 2, '10) otherDownCount should be 1'); - assert(pointerup, '10) pointerup should be true'); - - done(); - }, 17); - }); -}); diff --git a/test/unit/Polygon-test.ts b/test/unit/Polygon-test.ts deleted file mode 100644 index d1b6e5769..000000000 --- a/test/unit/Polygon-test.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { assert } from 'chai'; - -import { addStage, Konva } from './test-utils'; - -describe('Polygon', function () { - it('add polygon', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - var points = [73, 192, 73, 160, 340, 23, 500, 109, 499, 139, 342, 93]; - - var poly = new Konva.Line({ - points: points, - fill: 'green', - stroke: 'blue', - strokeWidth: 5, - closed: true, - }); - - layer.add(poly); - stage.add(layer); - - assert.equal(poly.getClassName(), 'Line'); - }); -}); diff --git a/test/unit/Rect-test.ts b/test/unit/Rect-test.ts deleted file mode 100644 index 308a61888..000000000 --- a/test/unit/Rect-test.ts +++ /dev/null @@ -1,237 +0,0 @@ -import { assert } from 'chai'; - -import { - addStage, - Konva, - createCanvas, - compareLayerAndCanvas, -} from './test-utils'; - -describe('Rect', function () { - // ====================================================== - it('add rect to stage', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - - var rect = new Konva.Rect({ - x: 100, - y: 50, - width: 100, - height: 50, - fill: 'green', - stroke: 'blue', - }); - - layer.add(rect); - stage.add(layer); - - assert.equal(rect.x(), 100); - assert.equal(rect.y(), 50); - - var trace = layer.getContext().getTrace(); - //console.log(trace); - assert.equal( - trace, - 'clearRect(0,0,578,200);save();transform(1,0,0,1,100,50);beginPath();rect(0,0,100,50);closePath();fillStyle=green;fill();lineWidth=2;strokeStyle=blue;stroke();restore();' - ); - - var relaxedTrace = layer.getContext().getTrace(true); - //console.log(relaxedTrace); - assert.equal( - relaxedTrace, - 'clearRect();save();transform();beginPath();rect();closePath();fillStyle;fill();lineWidth;strokeStyle;stroke();restore();' - ); - }); - - // ====================================================== - it('add rect with shadow, corner radius, and opacity', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - - var rect = new Konva.Rect({ - x: 100, - y: 50, - width: 100, - height: 50, - fill: 'green', - stroke: 'blue', - shadowColor: 'red', - shadowBlur: 10, - shadowOffset: { x: 5, y: 5 }, - shadowOpacity: 0.5, - opacity: 0.4, - cornerRadius: 5, - }); - - layer.add(rect); - stage.add(layer); - - assert.equal(rect.shadowColor(), 'red'); - assert.equal(rect.shadowBlur(), 10); - assert.equal(rect.shadowOffsetX(), 5); - assert.equal(rect.shadowOffsetY(), 5); - assert.equal(rect.shadowOpacity(), 0.5); - assert.equal(rect.opacity(), 0.4); - assert.equal(rect.cornerRadius(), 5); - }); - - // ====================================================== - it('draw rect', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var rect = new Konva.Rect({ - x: 200, - y: 90, - width: 100, - height: 50, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - scale: { - x: 2, - y: 2, - }, - cornerRadius: 15, - draggable: true, - }); - - layer.add(rect); - stage.add(layer); - - assert.equal(rect.getClassName(), 'Rect'); - }); - - // ====================================================== - it('add fill stroke rect', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var rect = new Konva.Rect({ - x: 200, - y: 100, - width: 100, - height: 50, - fill: 'blue', - stroke: 'green', - strokeWidth: 4, - }); - - layer.add(rect); - stage.add(layer); - - var canvas = createCanvas(); - var context = canvas.getContext('2d'); - context.beginPath(); - context.rect(200, 100, 100, 50); - context.fillStyle = 'blue'; - context.fill(); - context.lineWidth = 4; - context.strokeStyle = 'green'; - context.stroke(); - - compareLayerAndCanvas(layer, canvas); - }); - - // ====================================================== - it('add stroke rect', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var rect = new Konva.Rect({ - x: 200, - y: 100, - width: 100, - height: 50, - stroke: 'green', - strokeWidth: 4, - }); - - layer.add(rect); - stage.add(layer); - - var canvas = createCanvas(); - var context = canvas.getContext('2d'); - context.beginPath(); - context.rect(200, 100, 100, 50); - context.lineWidth = 4; - context.strokeStyle = 'green'; - context.stroke(); - - compareLayerAndCanvas(layer, canvas); - }); - - // ====================================================== - it('use default stroke width (stroke width should be 2)', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var rect = new Konva.Rect({ - x: 200, - y: 100, - width: 100, - height: 50, - stroke: 'blue', - }); - - layer.add(rect); - stage.add(layer); - - var canvas = createCanvas(); - var context = canvas.getContext('2d'); - context.beginPath(); - context.rect(200, 100, 100, 50); - context.lineWidth = 2; - context.strokeStyle = 'blue'; - context.stroke(); - compareLayerAndCanvas(layer, canvas); - }); - - // ====================================================== - it('limit corner radius', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var rect = new Konva.Rect({ - x: 50, - y: 50, - width: 100, - height: 100, - fill: 'black', - cornerRadius: 100, - }); - - layer.add(rect); - stage.add(layer); - - // as corner radius is much bigger we should have circe in the result - var canvas = createCanvas(); - var context = canvas.getContext('2d'); - context.beginPath(); - context.arc(100, 100, 50, 0, Math.PI * 2); - context.fillStyle = 'black'; - context.fill(); - compareLayerAndCanvas(layer, canvas, 100); - }); - - // ====================================================== - it('array for corner radius', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var rect = new Konva.Rect({ - x: 50, - y: 50, - width: 100, - height: 100, - fill: 'black', - cornerRadius: [0, 10, 20, 30], - }); - - layer.add(rect); - stage.add(layer); - layer.draw(); - - var trace = layer.getContext().getTrace(); - assert.equal( - trace, - 'clearRect(0,0,578,200);save();transform(1,0,0,1,50,50);beginPath();moveTo(0,0);lineTo(90,0);arc(90,10,10,4.712,0,false);lineTo(100,80);arc(80,80,20,0,1.571,false);lineTo(30,100);arc(30,70,30,1.571,3.142,false);lineTo(0,0);arc(0,0,0,3.142,4.712,false);closePath();fillStyle=black;fill();restore();clearRect(0,0,578,200);save();transform(1,0,0,1,50,50);beginPath();moveTo(0,0);lineTo(90,0);arc(90,10,10,4.712,0,false);lineTo(100,80);arc(80,80,20,0,1.571,false);lineTo(30,100);arc(30,70,30,1.571,3.142,false);lineTo(0,0);arc(0,0,0,3.142,4.712,false);closePath();fillStyle=black;fill();restore();' - ); - }); -}); diff --git a/test/unit/RegularPolygon-test.ts b/test/unit/RegularPolygon-test.ts deleted file mode 100644 index 8e996f805..000000000 --- a/test/unit/RegularPolygon-test.ts +++ /dev/null @@ -1,209 +0,0 @@ -import { assert } from 'chai'; - -import { - addStage, - Konva, - cloneAndCompareLayer, - assertAlmostEqual, -} from './test-utils'; - -describe('RegularPolygon', function () { - // ====================================================== - it('add regular polygon triangle', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - - var poly = new Konva.RegularPolygon({ - x: 200, - y: 100, - sides: 3, - radius: 50, - fill: 'green', - stroke: 'blue', - strokeWidth: 5, - name: 'foobar', - center: { - x: 0, - y: -50, - }, - }); - - layer.add(poly); - stage.add(layer); - - assert.equal(poly.getClassName(), 'RegularPolygon'); - }); - - // ====================================================== - it('add regular polygon square', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var poly = new Konva.RegularPolygon({ - x: 200, - y: 100, - sides: 4, - radius: 50, - fill: 'green', - stroke: 'blue', - strokeWidth: 5, - name: 'foobar', - }); - - layer.add(poly); - stage.add(layer); - - var trace = layer.getContext().getTrace(); - - assert.equal( - trace, - 'clearRect(0,0,578,200);save();transform(1,0,0,1,200,100);beginPath();moveTo(0,-50);lineTo(50,0);lineTo(0,50);lineTo(-50,0);closePath();fillStyle=green;fill();lineWidth=5;strokeStyle=blue;stroke();restore();' - ); - }); - - // ====================================================== - it('add regular polygon pentagon', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var poly = new Konva.RegularPolygon({ - x: 200, - y: 100, - sides: 5, - radius: 50, - fill: 'green', - stroke: 'blue', - strokeWidth: 5, - name: 'foobar', - }); - - layer.add(poly); - stage.add(layer); - - var trace = layer.getContext().getTrace(); - - assert.equal( - trace, - 'clearRect(0,0,578,200);save();transform(1,0,0,1,200,100);beginPath();moveTo(0,-50);lineTo(47.553,-15.451);lineTo(29.389,40.451);lineTo(-29.389,40.451);lineTo(-47.553,-15.451);closePath();fillStyle=green;fill();lineWidth=5;strokeStyle=blue;stroke();restore();' - ); - }); - - // ====================================================== - it('add regular polygon octogon', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var poly = new Konva.RegularPolygon({ - x: 200, - y: 100, - sides: 8, - radius: 50, - fill: 'green', - stroke: 'blue', - strokeWidth: 5, - name: 'foobar', - }); - - layer.add(poly); - stage.add(layer); - - var trace = layer.getContext().getTrace(); - - assert.equal( - trace, - 'clearRect(0,0,578,200);save();transform(1,0,0,1,200,100);beginPath();moveTo(0,-50);lineTo(35.355,-35.355);lineTo(50,0);lineTo(35.355,35.355);lineTo(0,50);lineTo(-35.355,35.355);lineTo(-50,0);lineTo(-35.355,-35.355);closePath();fillStyle=green;fill();lineWidth=5;strokeStyle=blue;stroke();restore();' - ); - }); - - // ====================================================== - it('attr sync', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var poly = new Konva.RegularPolygon({ - x: 200, - y: 100, - sides: 5, - radius: 50, - fill: 'green', - stroke: 'blue', - strokeWidth: 5, - name: 'foobar', - }); - - layer.add(poly); - stage.add(layer); - - assert.equal(poly.getWidth(), 100); - assert.equal(poly.getHeight(), 100); - - poly.setWidth(120); - assert.equal(poly.radius(), 60); - assert.equal(poly.getHeight(), 120); - - poly.setHeight(140); - assert.equal(poly.radius(), 70); - assert.equal(poly.getHeight(), 140); - }); - - it('polygon cache', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var poly = new Konva.RegularPolygon({ - x: 200, - y: 100, - sides: 5, - radius: 50, - fill: 'green', - stroke: 'black', - strokeWidth: 5, - name: 'foobar', - }); - poly.cache(); - layer.add(poly); - stage.add(layer); - - assert.deepEqual(poly.getSelfRect(), { - x: -47.55282581475768, - y: -50, - height: 90.45084971874738, - width: 95.10565162951536, - }); - - cloneAndCompareLayer(layer, 254); - }); - - it('triangle - bounding box', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - stage.add(layer); - - var poly = new Konva.RegularPolygon({ - x: 200, - y: 100, - sides: 3, - radius: 50, - fill: 'green', - stroke: 'blue', - strokeWidth: 5, - name: 'foobar', - }); - - layer.add(poly); - - var tr = new Konva.Transformer({ - nodes: [poly], - }); - layer.add(tr); - - layer.draw(); - - var box = poly.getClientRect(); - - assertAlmostEqual(box.width, 91.60254037844388); - assertAlmostEqual(box.height, 80.00000000000003); - }); -}); diff --git a/test/unit/Ring-test.ts b/test/unit/Ring-test.ts deleted file mode 100644 index 7abe4f756..000000000 --- a/test/unit/Ring-test.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { assert } from 'chai'; - -import { addStage, Konva, compareLayers } from './test-utils'; - -describe('Ring', function () { - // ====================================================== - it('add ring', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var ring = new Konva.Ring({ - x: stage.width() / 2, - y: stage.height() / 2, - innerRadius: 50, - outerRadius: 90, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - draggable: true, - }); - layer.add(ring); - stage.add(layer); - assert.equal(ring.getClassName(), 'Ring'); - - var trace = layer.getContext().getTrace(); - assert.equal( - trace, - 'clearRect(0,0,578,200);save();transform(1,0,0,1,289,100);beginPath();arc(0,0,50,0,6.283,false);moveTo(90,0);arc(0,0,90,6.283,0,true);closePath();fillStyle=green;fill();lineWidth=4;strokeStyle=black;stroke();restore();' - ); - }); - - // ====================================================== - it('ring attrs sync', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var ring = new Konva.Ring({ - name: 'ring', - x: 30, - y: 50, - innerRadius: 15, - outerRadius: 30, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - draggable: true, - }); - layer.add(ring); - stage.add(layer); - - assert(ring.width(), 60); - assert(ring.height(), 60); - - ring.height(100); - assert(ring.width(), 100); - assert(ring.outerRadius(), 50); - - ring.width(120); - assert(ring.height(), 120); - assert(ring.outerRadius(), 60); - }); - - it('ring cache', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var ring = new Konva.Ring({ - name: 'ring', - x: 30, - y: 50, - innerRadius: 15, - outerRadius: 30, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - draggable: true, - }); - - layer.add(ring); - stage.add(layer); - - assert.deepEqual(ring.getSelfRect(), { - x: -30, - y: -30, - width: 60, - height: 60, - }); - - var layer2 = layer.clone(); - stage.add(layer2); - layer2.hide(); - - compareLayers(layer, layer2); - }); -}); diff --git a/test/unit/Shape-test.ts b/test/unit/Shape-test.ts deleted file mode 100644 index 26758dda7..000000000 --- a/test/unit/Shape-test.ts +++ /dev/null @@ -1,2338 +0,0 @@ -import { assert } from 'chai'; - -import { - addStage, - simulateMouseDown, - simulateMouseMove, - simulateMouseUp, - createCanvas, - isBrowser, - isNode, - compareLayerAndCanvas, - compareLayers, - loadImage, - Konva, - compareCanvases, -} from './test-utils'; - -describe('Shape', function () { - // ====================================================== - it('test intersects()', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var rect = new Konva.Rect({ - x: 200, - y: 100, - width: 100, - height: 50, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - }); - - layer.add(rect); - stage.add(layer); - - assert.equal( - rect.intersects({ - x: 201, - y: 101, - }), - true, - '(201,101) should intersect the shape' - ); - - assert.equal( - rect.intersects({ - x: 197, - y: 97, - }), - false, - '(197, 97) should not intersect the shape' - ); - - assert.equal( - rect.intersects({ - x: 250, - y: 125, - }), - true, - '(250, 125) should intersect the shape' - ); - - assert.equal( - rect.intersects({ - x: 300, - y: 150, - }), - true, - '(300, 150) should intersect the shape' - ); - - assert.equal( - rect.intersects({ - x: 303, - y: 153, - }), - false, - '(303, 153) should not intersect the shape' - ); - }); - - // ====================================================== - it('test hasShadow() method', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var shape = new Konva.Shape({ - sceneFunc: function (context) { - context.beginPath(); - context.moveTo(0, 0); - context.lineTo(100, 0); - context.lineTo(100, 100); - context.closePath(); - context.fillStrokeShape(this); - }, - x: 10, - y: 10, - fill: 'green', - stroke: 'blue', - strokeWidth: 5, - shadowColor: 'black', - shadowOffsetX: 10, - shadowOpacity: 0, - }); - - layer.add(shape); - stage.add(layer); - - assert.equal( - shape.hasShadow(), - false, - 'shape should not have a shadow because opacity is 0' - ); - - shape.shadowOpacity(0.5); - - assert.equal( - shape.hasShadow(), - true, - 'shape should have a shadow because opacity is nonzero' - ); - - shape.shadowEnabled(false); - - assert.equal( - shape.hasShadow(), - false, - 'shape should not have a shadow because it is not enabled' - ); - }); - - // ====================================================== - it('custom shape with fill, stroke, and strokeWidth', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var shape = new Konva.Shape({ - sceneFunc: function (context) { - context.beginPath(); - context.moveTo(0, 0); - context.lineTo(100, 0); - context.lineTo(100, 100); - context.closePath(); - context.fillStrokeShape(this); - }, - x: 200, - y: 100, - fill: 'green', - stroke: 'blue', - strokeWidth: 5, - }); - - layer.add(shape); - stage.add(layer); - - var trace = layer.getContext().getTrace(); - - assert.equal( - trace, - 'clearRect(0,0,578,200);save();transform(1,0,0,1,200,100);beginPath();moveTo(0,0);lineTo(100,0);lineTo(100,100);closePath();fillStyle=green;fill();lineWidth=5;strokeStyle=blue;stroke();restore();' - ); - }); - - // ====================================================== - it('add star with translated, scaled, rotated fill', function (done) { - loadImage('darth-vader.jpg', (imageObj) => { - var stage = addStage(); - var layer = new Konva.Layer(); - - var star = new Konva.Star({ - x: 200, - y: 100, - numPoints: 5, - innerRadius: 40, - outerRadius: 70, - - fillPatternImage: imageObj, - fillPatternX: -20, - fillPatternY: -30, - fillPatternScale: { x: 0.5, y: 0.5 }, - fillPatternOffset: { x: 219, y: 150 }, - fillPatternRotation: 90, - fillPatternRepeat: 'no-repeat', - - stroke: 'blue', - strokeWidth: 5, - draggable: true, - }); - - layer.add(star); - stage.add(layer); - - /* - var anim = new Konva.Animation(function() { - star.attrs.fill.rotation += 0.02; - }, layer); - anim.start(); - */ - - assert.equal(star.fillPatternX(), -20, 'star fill x should be -20'); - assert.equal(star.fillPatternY(), -30, 'star fill y should be -30'); - assert.equal( - star.fillPatternScale().x, - 0.5, - 'star fill scale x should be 0.5' - ); - assert.equal( - star.fillPatternScale().y, - 0.5, - 'star fill scale y should be 0.5' - ); - assert.equal( - star.fillPatternOffset().x, - 219, - 'star fill offset x should be 219' - ); - assert.equal( - star.fillPatternOffset().y, - 150, - 'star fill offset y should be 150' - ); - assert.equal( - star.fillPatternRotation(), - 90, - 'star fill rotation should be 90' - ); - - star.fillPatternRotation(180); - - assert.equal( - star.fillPatternRotation(), - 180, - 'star fill rotation should be 180' - ); - - star.fillPatternScale({ x: 1, y: 1 }); - - assert.equal( - star.fillPatternScale().x, - 1, - 'star fill scale x should be 1' - ); - assert.equal( - star.fillPatternScale().y, - 1, - 'star fill scale y should be 1' - ); - - star.fillPatternOffset({ x: 100, y: 120 }); - - assert.equal( - star.fillPatternOffset().x, - 100, - 'star fill offset x should be 100' - ); - assert.equal( - star.fillPatternOffset().y, - 120, - 'star fill offset y should be 120' - ); - - done(); - }); - }); - - // ====================================================== - it('test size setters and getters', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 50, - fill: 'red', - }); - - var ellipse = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 50, - fill: 'yellow', - }); - - layer.add(ellipse); - layer.add(circle); - stage.add(layer); - - // circle tests - assert.equal(circle.width(), 100, 'circle width should be 100'); - assert.equal(circle.height(), 100, 'circle height should be 100'); - assert.equal(circle.getSize().width, 100, 'circle width should be 100'); - assert.equal(circle.getSize().height, 100, 'circle height should be 100'); - assert.equal(circle.radius(), 50, 'circle radius should be 50'); - - circle.setWidth(200); - - assert.equal(circle.width(), 200, 'circle width should be 200'); - assert.equal(circle.height(), 200, 'circle height should be 200'); - assert.equal(circle.getSize().width, 200, 'circle width should be 200'); - assert.equal(circle.getSize().height, 200, 'circle height should be 200'); - assert.equal(circle.radius(), 100, 'circle radius should be 100'); - }); - - // ====================================================== - it('set image fill to color then image then linear gradient then back to image', function (done) { - loadImage('darth-vader.jpg', (imageObj) => { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: 200, - y: 60, - radius: 50, - fill: 'blue', - }); - - layer.add(circle); - stage.add(layer); - - assert.equal(circle.fill(), 'blue', 'circle fill should be blue'); - - circle.fill(null); - circle.fillPatternImage(imageObj); - circle.fillPatternRepeat('no-repeat'); - circle.fillPatternOffset({ x: -200, y: -70 }); - - assert.notEqual( - circle.fillPatternImage(), - undefined, - 'circle fill image should be defined' - ); - assert.equal( - circle.fillPatternRepeat(), - 'no-repeat', - 'circle fill repeat should be no-repeat' - ); - assert.equal( - circle.fillPatternOffset().x, - -200, - 'circle fill offset x should be -200' - ); - assert.equal( - circle.fillPatternOffset().y, - -70, - 'circle fill offset y should be -70' - ); - - circle.fillPatternImage(null); - circle.fillLinearGradientStartPoint({ x: -35, y: -35 }); - circle.fillLinearGradientEndPoint({ x: 35, y: 35 }); - circle.fillLinearGradientColorStops([0, 'red', 1, 'blue']); - - circle.fillLinearGradientStartPoint({ x: 0, y: 0 }); - circle.fillPatternImage(imageObj); - circle.fillPatternRepeat('repeat'); - circle.fillPatternOffset({ x: 0, y: 0 }); - - layer.draw(); - - done(); - }); - }); - - it('stroke gradient', function () { - var stage = addStage(); - var layer = new Konva.Layer({ - scaleY: 1.5, - }); - - var shape = new Konva.Rect({ - x: 10, - y: 10, - width: 100, - height: 100, - fillLinearGradientColorStops: [0, 'yellow', 0.5, 'red', 1, 'white'], - fillLinearGradientStartPoint: { - x: 0, - y: 0, - }, - scaleX: 3, - fillLinearGradientEndPoint: { - x: 100, - y: 100, - }, - strokeLinearGradientColorStops: [0, 'red', 0.5, 'blue', 1, 'green'], - strokeLinearGradientStartPoint: { - x: 0, - y: 0, - }, - strokeLinearGradientEndPoint: { - x: 100, - y: 100, - }, - }); - layer.add(shape); - stage.add(layer); - - if (isNode) { - assert.equal( - layer.getContext().getTrace(true), - 'clearRect();save();transform();beginPath();rect();closePath();fillStyle;fill();lineWidth;createLinearGradient();strokeStyle;stroke();restore();' - ); - } else { - assert.equal( - layer.getContext().getTrace(), - 'clearRect(0,0,578,200);save();transform(3,0,0,1.5,10,15);beginPath();rect(0,0,100,100);closePath();fillStyle=[object CanvasGradient];fill();lineWidth=2;createLinearGradient(0,0,100,100);strokeStyle=[object CanvasGradient];stroke();restore();' - ); - } - }); - - // ====================================================== - it('test enablers and disablers', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - shadowColor: 'black', - shadowBlur: 10, - shadowOffset: { x: 10, y: 10 }, - dash: [10, 10], - scaleX: 3, - }); - layer.add(circle); - stage.add(layer); - - assert.equal(circle.strokeScaleEnabled(), true); - assert.equal(circle.fillEnabled(), true, 'fillEnabled should be true'); - assert.equal(circle.strokeEnabled(), true, 'strokeEnabled should be true'); - assert.equal(circle.shadowEnabled(), true, 'shadowEnabled should be true'); - assert.equal(circle.dashEnabled(), true, 'dashEnabled should be true'); - - circle.strokeScaleEnabled(false); - assert.equal(circle.strokeScaleEnabled(), false); - - layer.draw(); - // var trace = layer.getContext().getTrace(); - - circle.fillEnabled(false); - assert.equal(circle.fillEnabled(), false, 'fillEnabled should be false'); - - circle.strokeEnabled(false); - assert.equal( - circle.strokeEnabled(), - false, - 'strokeEnabled should be false' - ); - - circle.shadowEnabled(false); - assert.equal( - circle.shadowEnabled(), - false, - 'shadowEnabled should be false' - ); - - circle.dashEnabled(false); - assert.equal(circle.dashEnabled(), false, 'dashEnabled should be false'); - - // re-enable - - circle.dashEnabled(true); - assert.equal(circle.dashEnabled(), true, 'dashEnabled should be true'); - - circle.shadowEnabled(true); - assert.equal(circle.shadowEnabled(), true, 'shadowEnabled should be true'); - - circle.strokeEnabled(true); - assert.equal(circle.strokeEnabled(), true, 'strokeEnabled should be true'); - - circle.fillEnabled(true); - assert.equal(circle.fillEnabled(), true, 'fillEnabled should be true'); - }); - - // ====================================================== - it('fill with shadow and opacity', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - - var rect = new Konva.Rect({ - x: 100, - y: 50, - width: 100, - height: 50, - fill: 'green', - opacity: 0.5, - shadowColor: 'black', - shadowBlur: 10, - shadowOffset: { x: 10, y: 10 }, - shadowOpacity: 0.5, - }); - - layer.add(rect); - stage.add(layer); - - assert.equal(rect.x(), 100); - assert.equal(rect.y(), 50); - - var canvas = createCanvas(); - var context = canvas.getContext('2d'); - context.globalAlpha = 0.5; - // rect - context.beginPath(); - context.rect(100, 50, 100, 50); - context.closePath(); - - context.fillStyle = 'green'; - context.shadowColor = 'rgba(0,0,0,0.5)'; - context.shadowBlur = 10 * Konva.pixelRatio; - context.shadowOffsetX = 10 * Konva.pixelRatio; - context.shadowOffsetY = 10 * Konva.pixelRatio; - context.fill(); - - compareLayerAndCanvas(layer, canvas, 30); - - var trace = layer.getContext().getTrace(); - - assert.equal( - trace, - 'clearRect(0,0,578,200);save();transform(1,0,0,1,100,50);globalAlpha=0.5;shadowColor=rgba(0,0,0,0.5);shadowBlur=10;shadowOffsetX=10;shadowOffsetY=10;beginPath();rect(0,0,100,50);closePath();fillStyle=green;fill();restore();' - ); - }); - - // ====================================================== - it('draw fill after stroke', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - - var rect = new Konva.Rect({ - x: 100, - y: 50, - width: 100, - height: 50, - fill: 'green', - stroke: 'red', - strokeWidth: 10, - fillAfterStrokeEnabled: true, - }); - - layer.add(rect); - stage.add(layer); - - var trace = layer.getContext().getTrace(); - - assert.equal( - trace, - 'clearRect(0,0,578,200);save();transform(1,0,0,1,100,50);beginPath();rect(0,0,100,50);closePath();lineWidth=10;strokeStyle=red;stroke();fillStyle=green;fill();restore();' - ); - }); - - // ====================================================== - it('test strokeWidth = 0', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - - var rect = new Konva.Rect({ - x: 100, - y: 50, - width: 100, - height: 50, - fill: 'green', - strokeWidth: 0, - stroke: 'black', - }); - - layer.add(rect); - stage.add(layer); - - var canvas = createCanvas(); - var context = canvas.getContext('2d'); - context.beginPath(); - context.rect(100, 50, 100, 50); - context.closePath(); - - context.fillStyle = 'green'; - context.fill(); - var trace = layer.getContext().getTrace(); - - compareLayerAndCanvas(layer, canvas, 30); - - assert.equal( - trace, - 'clearRect(0,0,578,200);save();transform(1,0,0,1,100,50);beginPath();rect(0,0,100,50);closePath();fillStyle=green;fill();restore();' - ); - }); - - // ====================================================== - it('stroke with shadow and opacity', function () { - Konva.pixelRatio = 1; - var stage = addStage(); - - var layer = new Konva.Layer(); - - var rect = new Konva.Rect({ - x: 100, - y: 50, - width: 100, - height: 50, - stroke: 'red', - strokeWidth: 20, - opacity: 0.5, - shadowColor: 'black', - shadowBlur: 10, - shadowOffset: { x: 10, y: 10 }, - shadowOpacity: 0.5, - }); - - layer.add(rect); - stage.add(layer); - - assert.equal(rect.x(), 100); - assert.equal(rect.y(), 50); - - var canvas = createCanvas(); - var context = canvas.getContext('2d'); - context.globalAlpha = 0.5; - // rect - context.beginPath(); - context.rect(100, 50, 100, 50); - context.closePath(); - - context.strokeStyle = 'red'; - context.lineWidth = 20; - - context.shadowColor = 'rgba(0,0,0,0.5)'; - context.shadowBlur = 10 * Konva.pixelRatio; - context.shadowOffsetX = 10 * Konva.pixelRatio; - context.shadowOffsetY = 10 * Konva.pixelRatio; - context.stroke(); - - compareLayerAndCanvas(layer, canvas, 10); - - var trace = layer.getContext().getTrace(); - //console.log(trace); - assert.equal( - trace, - 'clearRect(0,0,578,200);save();transform(1,0,0,1,100,50);globalAlpha=0.5;shadowColor=rgba(0,0,0,0.5);shadowBlur=10;shadowOffsetX=10;shadowOffsetY=10;beginPath();rect(0,0,100,50);closePath();lineWidth=20;strokeStyle=red;stroke();restore();' - ); - }); - - // ====================================================== - it('fill and stroke with opacity', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - - var rect = new Konva.Rect({ - x: 100, - y: 50, - width: 100, - height: 50, - fill: 'green', - stroke: 'black', - strokeWidth: 10, - opacity: 0.5, - }); - - layer.add(rect); - - stage.add(layer); - - var canvas = createCanvas(); - var context = canvas.getContext('2d'); - context.globalAlpha = 0.5; - // stroke - context.beginPath(); - context.moveTo(100, 50); - context.lineTo(200, 50); - context.lineTo(200, 100); - context.lineTo(100, 100); - context.closePath(); - context.lineWidth = 10; - context.strokeStyle = 'black'; - context.stroke(); - - // rect - context.fillStyle = 'green'; - context.fillRect(105, 55, 90, 40); - - compareLayerAndCanvas(layer, canvas, 10); - }); - - // ====================================================== - it('fill and stroke with shadow', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - - var rect = new Konva.Rect({ - x: 100, - y: 50, - width: 100, - height: 50, - fill: 'green', - stroke: 'black', - strokeWidth: 10, - shadowColor: 'grey', - shadowBlur: 10, - shadowOffset: { - x: 20, - y: 20, - }, - }); - - layer.add(rect); - stage.add(layer); - - var canvas = createCanvas(); - var context = canvas.getContext('2d'); - context.beginPath(); - context.rect(100, 50, 100, 50); - context.closePath(); - context.fillStyle = 'green'; - - context.lineWidth = 10; - context.fill(); - context.stroke(); - - var c2 = createCanvas(); - var ctx2 = c2.getContext('2d'); - ctx2.shadowColor = 'grey'; - ctx2.shadowBlur = 10 * Konva.pixelRatio; - ctx2.shadowOffsetX = 20 * Konva.pixelRatio; - ctx2.shadowOffsetY = 20 * Konva.pixelRatio; - - ctx2.drawImage(canvas, 0, 0, canvas.width / 2, canvas.height / 2); - - // compareLayerAndCanvas(layer, c2, 50); - - if (isBrowser) { - var trace = layer.getContext().getTrace(); - assert.equal( - trace, - 'clearRect(0,0,578,200);save();shadowColor=rgba(128,128,128,1);shadowBlur=10;shadowOffsetX=20;shadowOffsetY=20;drawImage([object HTMLCanvasElement],0,0,578,200);restore();' - ); - } else { - var trace = layer.getContext().getTrace(true); - assert.equal( - trace, - 'clearRect();save();shadowColor;shadowBlur;shadowOffsetX;shadowOffsetY;drawImage();restore();' - ); - } - }); - - // ====================================================== - // hard to emulate the same drawing - it('fill and stroke with shadow and opacity', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var rect = new Konva.Rect({ - x: 100, - y: 50, - width: 100, - height: 50, - fill: 'green', - stroke: 'black', - strokeWidth: 10, - shadowColor: 'grey', - opacity: 0.5, - shadowBlur: 5, - shadowOffset: { - x: 20, - y: 20, - }, - }); - - layer.add(rect); - stage.add(layer); - - var canvas = createCanvas(); - var context = canvas.getContext('2d'); - context.globalAlpha = 0.3; - - // draw shadow - context.save(); - context.beginPath(); - context.rect(95, 45, 110, 60); - context.closePath(); - context.shadowColor = 'grey'; - context.shadowBlur = 5 * Konva.pixelRatio; - context.shadowOffsetX = 20 * Konva.pixelRatio; - context.shadowOffsetY = 20 * Konva.pixelRatio; - context.fillStyle = 'black'; - context.fill(); - context.restore(); - - // draw "stroke" - context.save(); - context.beginPath(); - context.moveTo(100, 50); - context.lineTo(200, 50); - context.lineTo(200, 100); - context.lineTo(100, 100); - context.closePath(); - context.lineWidth = 10; - context.strokeStyle = 'black'; - context.stroke(); - context.restore(); - - context.save(); - context.beginPath(); - context.fillStyle = 'green'; - context.rect(105, 55, 90, 40); - context.closePath(); - context.fill(); - context.restore(); - - compareLayerAndCanvas(layer, canvas, 260); - - if (isBrowser) { - var trace = layer.getContext().getTrace(); - assert.equal( - trace, - 'clearRect(0,0,578,200);save();shadowColor=rgba(128,128,128,1);shadowBlur=5;shadowOffsetX=20;shadowOffsetY=20;globalAlpha=0.5;drawImage([object HTMLCanvasElement],0,0,578,200);restore();' - ); - } else { - var trace = layer.getContext().getTrace(true); - assert.equal( - trace, - 'clearRect();save();shadowColor;shadowBlur;shadowOffsetX;shadowOffsetY;globalAlpha;drawImage();restore();' - ); - } - }); - - // ====================================================== - it('text with fill and stroke with shadow', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - - var text = new Konva.Text({ - x: 50, - y: 50, - text: 'Test TEXT', - fontSize: 50, - fill: 'green', - stroke: 'black', - strokeWidth: 2, - shadowColor: 'black', - shadowBlur: 2, - shadowOffset: { - x: 20, - y: 20, - }, - }); - - layer.add(text); - stage.add(layer); - - var canvas = createCanvas(); - var context = canvas.getContext('2d'); - - context.save(); - context.shadowColor = 'black'; - context.shadowBlur = 2 * Konva.pixelRatio; - context.shadowOffsetX = 20 * Konva.pixelRatio; - context.shadowOffsetY = 20 * Konva.pixelRatio; - context.font = 'normal 50px Arial'; - context.textBaseline = 'middle'; - - context.fillStyle = 'green'; - context.fillText('Test TEXT', 50, 75); - - context.lineWidth = 2; - context.strokeStyle = 'black'; - context.strokeText('Test TEXT', 50, 75); - - context.stroke(); - context.fill(); - context.restore(); - - // draw text again to remove shadow under stroke - context.font = 'normal 50px Arial'; - context.textBaseline = 'middle'; - context.fillText('Test TEXT', 50, 75); - context.fillStyle = 'green'; - context.fillText('Test TEXT', 50, 75); - - context.lineWidth = 2; - context.strokeStyle = 'black'; - context.strokeText('Test TEXT', 50, 75); - - compareLayerAndCanvas(layer, canvas, 254, 10); - }); - - // ====================================================== - it('shape intersect with shadow', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - - var rect = new Konva.Rect({ - fill: '#ff0000', - x: 50, - y: 50, - width: 200, - height: 200, - draggable: true, - shadowColor: '#000', // if all shadow properties removed, works fine - }); - layer.add(rect); - stage.add(layer); - - //error here - assert.equal(rect.intersects({ x: 52, y: 52 }), true); - assert.equal(rect.intersects({ x: 45, y: 45 }), false); - }); - - // ====================================================== - it('shape intersect while dragging', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - - var rect = new Konva.Rect({ - fill: '#ff0000', - x: 50, - y: 50, - width: 200, - height: 200, - draggable: true, - shadowColor: '#000', // if all shadow properties removed, works fine - }); - layer.add(rect); - stage.add(layer); - - simulateMouseDown(stage, { x: 55, y: 55 }); - simulateMouseMove(stage, { x: 65, y: 65 }); - - //error here - assert.equal(rect.intersects({ x: 65, y: 65 }), true); - simulateMouseUp(stage, { x: 65, y: 65 }); - }); - - // ====================================================== - it('overloaded getters and setters', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - - var rect = new Konva.Rect({ - x: 100, - y: 50, - width: 100, - height: 50, - fill: 'green', - stroke: 'red', - strokeWidth: 20, - draggable: true, - }); - - layer.add(rect); - stage.add(layer); - - rect.stroke('blue'); - assert.equal(rect.stroke(), 'blue'); - - rect.lineJoin('bevel'); - assert.equal(rect.lineJoin(), 'bevel'); - - rect.lineCap('square'); - assert.equal(rect.lineCap(), 'square'); - - rect.strokeWidth(8); - assert.equal(rect.strokeWidth(), 8); - - const f = () => {}; - rect.sceneFunc(f); - assert.equal(rect.sceneFunc(), f); - - rect.hitFunc(f); - assert.equal(rect.hitFunc(), f); - - rect.dash([1]); - assert.equal(rect.dash()[0], 1); - - // NOTE: skipping the rest because it would take hours to test all possible methods. - // This should hopefully be enough to test Factor overloaded methods - }); - - // ====================================================== - it('create image hit region', function (done) { - loadImage('lion.png', (imageObj) => { - var stage = addStage(); - var layer = new Konva.Layer(); - var lion = new Konva.Image({ - x: 0, - y: 0, - image: imageObj, - draggable: true, - shadowColor: 'black', - shadowBlur: 10, - shadowOffsetX: 20, - shadowOpacity: 0.2, - }); - - // override color key with black - // lion.colorKey = '#000000'; - // Konva.shapes['#000000'] = lion; - - layer.add(lion); - - stage.add(layer); - - assert.equal(layer.getIntersection({ x: 10, y: 10 }), lion); - - lion.cache(); - - lion.drawHitFromCache(); - - layer.draw(); - - assert.equal(layer.getIntersection({ x: 10, y: 10 }), null); - assert.equal(layer.getIntersection({ x: 50, y: 50 }), lion); - done(); - }); - }); - - it('test defaults', function () { - var shape = new Konva.Shape(); - - assert.equal(shape.strokeWidth(), 2); - assert.equal(shape.shadowOffsetX(), 0); - assert.equal(shape.shadowOffsetY(), 0); - assert.equal(shape.fillPatternX(), 0); - assert.equal(shape.fillPatternY(), 0); - assert.equal(shape.fillRadialGradientStartRadius(), 0); - assert.equal(shape.fillRadialGradientEndRadius(), 0); - assert.equal(shape.fillPatternRepeat(), 'repeat'); - assert.equal(shape.fillEnabled(), true); - assert.equal(shape.strokeEnabled(), true); - assert.equal(shape.shadowEnabled(), true); - assert.equal(shape.dashEnabled(), true); - assert.equal(shape.dashOffset(), 0); - assert.equal(shape.strokeScaleEnabled(), true); - assert.equal(shape.fillPriority(), 'color'); - assert.equal(shape.fillPatternOffsetX(), 0); - assert.equal(shape.fillPatternOffsetY(), 0); - assert.equal(shape.fillPatternScaleX(), 1); - assert.equal(shape.fillPatternScaleY(), 1); - assert.equal(shape.fillLinearGradientStartPointX(), 0); - assert.equal(shape.fillLinearGradientStartPointY(), 0); - assert.equal(shape.fillLinearGradientEndPointX(), 0); - assert.equal(shape.fillLinearGradientEndPointY(), 0); - assert.equal(shape.fillRadialGradientStartPointX(), 0); - assert.equal(shape.fillRadialGradientStartPointY(), 0); - assert.equal(shape.fillRadialGradientEndPointX(), 0); - assert.equal(shape.fillRadialGradientEndPointY(), 0); - assert.equal(shape.fillPatternRotation(), 0); - }); - - // ====================================================== - it('hit graph when shape cached before adding to Layer', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var rect = new Konva.Rect({ - x: 290, - y: 111, - width: 50, - height: 50, - fill: 'black', - }); - rect.cache(); - - var click = false; - - rect.on('click', function () { - click = true; - }); - - layer.add(rect); - stage.add(layer); - - simulateMouseDown(stage, { - x: 300, - y: 120, - }); - - simulateMouseUp(stage, { - x: 300, - y: 120, - }); - - assert.equal( - click, - true, - 'click event should have been fired when mousing down and then up on rect' - ); - }); - - it('class inherence', function () { - var rect = new Konva.Rect(); - assert.equal(rect instanceof Konva.Rect, true); - assert.equal(rect instanceof Konva.Shape, true); - assert.equal(rect instanceof Konva.Node, true); - }); - - it('disable stroke for hit', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - - var rect = new Konva.Rect({ - x: 100, - y: 50, - width: 100, - height: 50, - stroke: 'red', - strokeWidth: 20, - draggable: true, - }); - // default value - assert.equal(rect.hitStrokeWidth(), 'auto'); - - rect.hitStrokeWidth(0); - assert.equal(rect.hitStrokeWidth(), 0); - - layer.add(rect); - stage.add(layer); - - assert.equal(rect.y(), 50); - - var trace = layer.getHitCanvas().getContext().getTrace(true); - assert.equal( - trace, - 'clearRect();save();transform();beginPath();rect();closePath();save();fillStyle;fill();restore();restore();' - ); - }); - - it('hitStrokeWidth', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - - var rect = new Konva.Rect({ - x: 10, - y: 10, - width: 100, - height: 100, - stroke: 'red', - strokeWidth: 2, - }); - // default value - layer.add(rect); - stage.add(layer); - - // default value is auto - assert.equal(rect.hitStrokeWidth(), 'auto'); - - // try to hit test near edge - assert.equal(stage.getIntersection({ x: 5, y: 5 }), null); - - rect.hitStrokeWidth(20); - layer.draw(); - // no we should hit the rect - assert.equal(stage.getIntersection({ x: 5, y: 5 }), rect); - - rect.strokeHitEnabled(false); - - assert.equal(rect.hitStrokeWidth(), 0); - - rect.strokeHitEnabled(true); - - assert.equal(rect.hitStrokeWidth(), 'auto'); - - rect.hitStrokeWidth(0); - - assert.equal(rect.strokeHitEnabled(), false); - - // var trace = layer - // .getHitCanvas() - // .getContext() - // .getTrace(true); - // assert.equal( - // trace, - // 'clearRect();save();transform();beginPath();rect();closePath();save();fillStyle;fill();restore();restore();' - // ); - }); - - it('enable hitStrokeWidth even if we have no stroke on scene', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - - var rect = new Konva.Rect({ - x: 10, - y: 10, - width: 100, - height: 100, - }); - // default value - layer.add(rect); - stage.add(layer); - - // try to hit test near edge - assert.equal(stage.getIntersection({ x: 5, y: 5 }), null); - - rect.hitStrokeWidth(20); - layer.draw(); - // no we should hit the rect - assert.equal(stage.getIntersection({ x: 5, y: 5 }), rect); - }); - - it('cache shadow color rgba', function () { - var circle = new Konva.Circle({ - fill: 'green', - radius: 50, - }); - // no shadow on start - assert.equal(circle.hasShadow(), false); - assert.equal(circle.getShadowRGBA(), undefined); - - // set shadow - circle.shadowColor('black'); - assert.equal(circle.hasShadow(), true); - assert.equal(circle.getShadowRGBA(), 'rgba(0,0,0,1)'); - - // set another shadow property - circle.shadowOpacity(0.2); - assert.equal(circle.getShadowRGBA(), 'rgba(0,0,0,0.2)'); - - circle.shadowColor('rgba(10,10,10,0.5)'); - assert.equal(circle.getShadowRGBA(), 'rgba(10,10,10,0.1)'); - - // reset shadow - circle.shadowColor(null); - assert.equal(circle.getShadowRGBA(), undefined); - - // illegal color - circle.shadowColor('#0000f'); - assert.equal(circle.hasShadow(), true); - assert.equal(circle.getShadowRGBA(), undefined); - }); - - it('scale should also effect shadow offset', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - - var rect = new Konva.Rect({ - x: 100, - y: 100, - width: 100, - height: 100, - scaleX: 0.5, - scaleY: 0.5, - fill: 'green', - shadowColor: 'black', - shadowBlur: 0, - shadowOffset: { x: 10, y: 10 }, - }); - - layer.add(rect); - stage.add(layer); - - var canvas = createCanvas(); - var context = canvas.getContext('2d'); - // rect - context.beginPath(); - context.rect(100, 100, 50, 50); - context.closePath(); - - context.fillStyle = 'green'; - context.shadowColor = 'rgba(0,0,0,1)'; - context.shadowBlur = 0; - context.shadowOffsetX = 5 * Konva.pixelRatio; - context.shadowOffsetY = 5 * Konva.pixelRatio; - context.fill(); - - compareLayerAndCanvas(layer, canvas, 10); - - var trace = layer.getContext().getTrace(); - - assert.equal( - trace, - 'clearRect(0,0,578,200);save();transform(0.5,0,0,0.5,100,100);shadowColor=rgba(0,0,0,1);shadowBlur=0;shadowOffsetX=5;shadowOffsetY=5;beginPath();rect(0,0,100,100);closePath();fillStyle=green;fill();restore();' - ); - }); - - // TODO: restore it! - it.skip('scale should also effect shadow offset - negative scale', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - - var rect = new Konva.Rect({ - x: 100, - y: 100, - width: 100, - height: 100, - scaleX: -0.5, - scaleY: 0.5, - fill: 'green', - shadowColor: 'black', - shadowBlur: 10, - shadowOffset: { x: 10, y: 10 }, - }); - - layer.add(rect); - stage.add(layer); - - var canvas = createCanvas(); - var context = canvas.getContext('2d'); - // rect - context.beginPath(); - context.rect(50, 100, 50, 50); - context.closePath(); - - context.fillStyle = 'green'; - context.shadowColor = 'rgba(0,0,0,1)'; - context.shadowBlur = 10; - context.shadowOffsetX = -5 * Konva.pixelRatio; - context.shadowOffsetY = 5 * Konva.pixelRatio; - context.fill(); - - compareLayerAndCanvas(layer, canvas, 150); - - // var trace = layer.getContext().getTrace(); - - // assert.equal( - // trace, - // 'clearRect(0,0,578,200);save();transform(-0.5,0,0,0.5,100,100);save();shadowColor=rgba(0,0,0,1);shadowBlur=10;shadowOffsetX=-5;shadowOffsetY=5;beginPath();rect(0,0,100,100);closePath();fillStyle=green;fill();restore();restore();' - // ); - }); - - it('scale of parent container should also effect shadow offset', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - var group = new Konva.Group({ - x: 100, - y: 100, - scaleX: 0.5, - scaleY: 0.5, - }); - var rect = new Konva.Rect({ - width: 200, - height: 200, - scaleX: 0.5, - scaleY: 0.5, - fill: 'green', - shadowColor: 'black', - shadowBlur: 0, - shadowOffset: { x: 20, y: 20 }, - }); - - group.add(rect); - layer.add(group); - stage.add(layer); - - var canvas = createCanvas(); - var context = canvas.getContext('2d'); - // rect - context.beginPath(); - context.rect(100, 100, 50, 50); - context.closePath(); - - context.fillStyle = 'green'; - context.shadowColor = 'rgba(0,0,0,1)'; - context.shadowBlur = 0; - context.shadowOffsetX = 5 * Konva.pixelRatio; - context.shadowOffsetY = 5 * Konva.pixelRatio; - context.fill(); - - compareLayerAndCanvas(layer, canvas, 10); - - var trace = layer.getContext().getTrace(); - - assert.equal( - trace, - 'clearRect(0,0,578,200);save();transform(0.25,0,0,0.25,100,100);shadowColor=rgba(0,0,0,1);shadowBlur=0;shadowOffsetX=5;shadowOffsetY=5;beginPath();rect(0,0,200,200);closePath();fillStyle=green;fill();restore();' - ); - }); - - it('optional disable buffer canvas', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - - var rect = new Konva.Rect({ - x: 100, - y: 50, - width: 100, - height: 50, - fill: 'green', - stroke: 'black', - strokeWidth: 10, - opacity: 0.5, - perfectDrawEnabled: false, - }); - - layer.add(rect); - - stage.add(layer); - - var canvas = createCanvas(); - var context = canvas.getContext('2d'); - context.globalAlpha = 0.5; - // stroke - context.beginPath(); - context.rect(100, 50, 100, 50); - context.closePath(); - context.lineWidth = 10; - context.strokeStyle = 'black'; - context.fillStyle = 'green'; - context.fill(); - context.stroke(); - - compareLayerAndCanvas(layer, canvas, 10); - - var trace = layer.getContext().getTrace(); - - assert.equal( - trace, - 'clearRect(0,0,578,200);save();transform(1,0,0,1,100,50);globalAlpha=0.5;beginPath();rect(0,0,100,50);closePath();fillStyle=green;fill();lineWidth=10;strokeStyle=black;stroke();restore();' - ); - }); - - it('check lineJoin in buffer canvas', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - - var rect = new Konva.Rect({ - x: 100, - y: 50, - width: 100, - height: 50, - fill: 'green', - stroke: 'black', - strokeWidth: 10, - opacity: 0.5, - lineJoin: 'round', - }); - - layer.add(rect); - - stage.add(layer); - - var canvas = createCanvas(); - var context = canvas.getContext('2d'); - - // stroke - context.beginPath(); - context.rect(100, 50, 100, 50); - context.closePath(); - context.lineWidth = 10; - context.strokeStyle = 'black'; - context.fillStyle = 'green'; - context.lineJoin = 'round'; - context.fill(); - context.stroke(); - - var canvas2 = createCanvas(); - var context2 = canvas2.getContext('2d'); - context2.globalAlpha = 0.5; - context2.drawImage(canvas, 0, 0, canvas.width / 2, canvas.height / 2); - - compareLayerAndCanvas(layer, canvas2, 150); - - if (isBrowser) { - var trace = layer.getContext().getTrace(); - - assert.equal( - trace, - 'clearRect(0,0,578,200);save();globalAlpha=0.5;drawImage([object HTMLCanvasElement],0,0,578,200);restore();' - ); - } else { - var trace = layer.getContext().getTrace(true); - - assert.equal( - trace, - 'clearRect();save();globalAlpha;drawImage();restore();' - ); - } - }); - - it('export when buffer canvas is used should handle scaling correctly', async function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - stage.add(layer); - - var group = new Konva.Group(); - layer.add(group); - - var text = new Konva.Text({ - text: 'hello', - fontSize: 300, - fill: 'green', - shadowColor: 'black', - }); - group.add(text); - - const canvas1 = group.toCanvas({ - x: group.x(), - y: group.y(), - width: text.width(), - height: text.height(), - }); - text.stroke('transparent'); - const canvas2 = group.toCanvas({ - x: group.x(), - y: group.y(), - width: text.width(), - height: text.height(), - }); - - compareCanvases(canvas2, canvas1, 255, 10); - }); - - it('export when buffer canvas is used should handle scaling correctly another time', async function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - stage.add(layer); - - var group = new Konva.Group({ - x: 400, - }); - layer.add(group); - - var text = new Konva.Text({ - text: 'hello', - fontSize: 300, - fill: 'green', - shadowColor: 'black', - }); - group.add(text); - - const canvas1 = group.toCanvas({ - x: group.x(), - y: group.y(), - width: text.width(), - height: text.height(), - }); - text.stroke('transparent'); - const canvas2 = group.toCanvas({ - x: group.x(), - y: group.y(), - width: text.width(), - height: text.height(), - }); - - compareCanvases(canvas2, canvas1, 240, 110); - }); - - // ====================================================== - it('optional disable shadow for stroke', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - - var rect = new Konva.Rect({ - x: 100, - y: 50, - width: 100, - height: 50, - fill: 'green', - stroke: 'black', - strokeWidth: 10, - shadowColor: 'grey', - shadowBlur: 10, - shadowOffset: { - x: 20, - y: 20, - }, - shadowForStrokeEnabled: false, - }); - - layer.add(rect); - stage.add(layer); - - var canvas = createCanvas(); - var context = canvas.getContext('2d'); - context.beginPath(); - context.rect(100, 50, 100, 50); - context.closePath(); - context.fillStyle = 'green'; - context.shadowColor = 'grey'; - context.shadowBlur = 10 * Konva.pixelRatio; - context.shadowOffsetX = 20 * Konva.pixelRatio; - context.shadowOffsetY = 20 * Konva.pixelRatio; - context.lineWidth = 10; - context.fill(); - - context.shadowColor = 'rgba(0,0,0,0)'; - context.stroke(); - - compareLayerAndCanvas(layer, canvas, 10); - - var trace = layer.getContext().getTrace(); - assert.equal( - trace, - 'clearRect(0,0,578,200);save();transform(1,0,0,1,100,50);shadowColor=rgba(128,128,128,1);shadowBlur=10;shadowOffsetX=20;shadowOffsetY=20;beginPath();rect(0,0,100,50);closePath();fillStyle=green;fill();lineWidth=10;shadowColor=rgba(0,0,0,0);strokeStyle=black;stroke();restore();' - ); - }); - - it('clone custom shape', function () { - var className = 'myCustomName'; - class CustomShape extends Konva.Shape { - foo() {} - } - CustomShape.prototype.className = className; - // var CustomShape = function () { - // CustomShape.super.apply(this, arguments); - // this.className = className; - // }; - - // CustomShape.prototype.foo = function () {}; - - // Konva.Util.extend(CustomShape, Konva.Shape); - - var myShape = new CustomShape({ - fill: 'grey', - }); - - var clone = myShape.clone(); - assert.equal(clone instanceof CustomShape, true); - assert.equal(clone instanceof Konva.Shape, true); - assert.equal(clone.className, className); - assert.equal(clone.fill(), 'grey'); - assert.equal(clone.foo, CustomShape.prototype.foo); - }); - - it('getClientRect should skip disabled attributes', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var shape = new Konva.Rect({ - x: 200, - y: 100, - width: 100, - height: 100, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - strokeEnabled: false, - shadowOffsetX: 10, - shadowEnabled: false, - }); - - layer.add(shape); - stage.add(layer); - - var rect = shape.getClientRect(); - - assert.equal(rect.width, 100, 'should not effect width'); - assert.equal(rect.height, 100, 'should not effect width'); - }); - - it('getClientRect should not use cached values', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var shape = new Konva.Rect({ - x: 100, - y: 100, - width: 100, - height: 100, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - strokeEnabled: false, - shadowOffsetX: 10, - shadowEnabled: false, - }); - - layer.add(shape); - stage.add(layer); - - layer.cache(); - - layer.scaleX(2); - - const rect = shape.getClientRect(); - - assert.equal(rect.x, 200); - }); - - it('getClientRect for shape in transformed parent', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var group = new Konva.Group({ - x: 110, - y: 0, - rotation: 90, - }); - layer.add(group); - - var shape = new Konva.Rect({ - x: 0, - y: 0, - width: 100, - height: 100, - fill: 'green', - }); - group.add(shape); - - var relativeRect = shape.getClientRect({ relativeTo: group }); - - assert.equal(relativeRect.x, 0); - assert.equal(relativeRect.y, 0); - assert.equal(relativeRect.width, 100); - assert.equal(relativeRect.height, 100); - - var absRect = shape.getClientRect(); - - assert.equal(absRect.x, 10); - assert.equal(absRect.y, 0); - assert.equal(absRect.width, 100); - assert.equal(absRect.height, 100); - }); - - it('getClientRect with skew', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var shape = new Konva.Rect({ - x: 0, - y: 0, - width: 200, - height: 100, - skewX: 0.5, - scaleX: 2, - fill: 'green', - }); - layer.add(shape); - - var back = new Konva.Rect({ - stroke: 'red', - }); - back.setAttrs(shape.getClientRect()); - layer.add(back); - layer.draw(); - - var absRect = shape.getClientRect(); - - assert.equal(absRect.x, 0); - assert.equal(absRect.y, 0); - assert.equal(absRect.width, 450); - assert.equal(absRect.height, 100); - }); - - it('decompose transform', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var shape = new Konva.Rect({ - x: 0, - y: 0, - width: 200, - height: 100, - skewX: 0.5, - scaleX: 2, - scaleY: 2, - fill: 'green', - }); - layer.add(shape); - layer.draw(); - - assert.equal(shape.getTransform().decompose().scaleX, 2); - assert.equal(shape.getTransform().decompose().scaleY, 2); - assert.equal(shape.getTransform().decompose().skewX, 0.5); - - shape.skewX(2); - shape.scaleX(0.5); - - assert.equal(shape.getTransform().decompose().skewX, 2); - assert.equal(shape.getTransform().decompose().scaleX, 0.5); - assert.equal(shape.getTransform().decompose().scaleY, 2); - }); - - it('shadow should respect pixel ratio', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - layer.getCanvas().setPixelRatio(2); - var shape = new Konva.Rect({ - width: 100, - height: 100, - fill: 'black', - shadowColor: 'green', - shadowOffsetX: 20, - shadowOffsetY: 20, - shadowBlur: 0, - }); - - layer.add(shape); - stage.add(layer); - var data = layer.getContext().getImageData(15 * 2, (100 + 5) * 2, 1, 1); - assert.equal(data.data[3], 0, 'pixel should be empty, no shadow here'); - }); - - it('text shadow blur should take scale into account', function () { - var stage = addStage(); - var layer1 = new Konva.Layer(); - stage.add(layer1); - - var rect1 = new Konva.Rect({ - x: 10, - y: 10, - scaleX: 0.5, - scaleY: 0.5, - width: 80, - height: 80, - fill: 'black', - shadowColor: 'black', - shadowOffsetX: 0, - shadowOffsetY: 50, - shadowBlur: 10, - }); - layer1.add(rect1); - stage.add(layer1); - - var layer2 = new Konva.Layer(); - stage.add(layer2); - - var rect2 = new Konva.Rect({ - x: 10, - y: 10, - fill: 'black', - width: 40, - height: 40, - shadowColor: 'black', - shadowOffsetX: 0, - shadowOffsetY: 25, - shadowBlur: 5, - }); - layer2.add(rect2); - stage.add(layer2); - - compareLayers(layer1, layer2, 30); - }); - - // ====================================================== - it('sceneFunc and hitFunc should have shape as second argument', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var shape = new Konva.Shape({ - sceneFunc: function (context, shape) { - assert.equal(this, shape); - context.beginPath(); - context.moveTo(0, 0); - context.lineTo(100, 0); - context.lineTo(100, 100); - context.closePath(); - context.fillStrokeShape(shape); - }, - x: 200, - y: 100, - fill: 'green', - stroke: 'blue', - strokeWidth: 5, - }); - layer.add(shape); - - var rect = new Konva.Rect({ - hitFunc: function (ctx, shape) { - assert.equal(this, shape); - }, - }); - layer.add(rect); - stage.add(layer); - }); - - // ====================================================== - it('cache fill pattern', function (done) { - loadImage('darth-vader.jpg', (imageObj) => { - var stage = addStage(); - var layer = new Konva.Layer(); - - var star = new Konva.Star({ - x: 200, - y: 100, - numPoints: 5, - innerRadius: 40, - outerRadius: 70, - - fillPatternImage: imageObj, - fillPatternX: -20, - fillPatternY: -30, - fillPatternScale: { x: 0.5, y: 0.5 }, - fillPatternOffset: { x: 219, y: 150 }, - fillPatternRotation: 90, - fillPatternRepeat: 'no-repeat', - - stroke: 'blue', - strokeWidth: 5, - draggable: true, - }); - - layer.add(star); - stage.add(layer); - - var ctx = layer.getContext(); - var oldCreate = ctx.createPattern; - - var callCount = 0; - ctx.createPattern = function () { - callCount += 1; - return oldCreate.apply(this, arguments); - }; - - layer.draw(); - layer.draw(); - assert.equal(callCount, 0); - done(); - }); - }); - - it('recache fill pattern on changes', function (done) { - loadImage('darth-vader.jpg', (imageObj) => { - var stage = addStage(); - var layer = new Konva.Layer(); - - var star = new Konva.Star({ - x: 200, - y: 100, - numPoints: 5, - innerRadius: 40, - outerRadius: 70, - - fillPatternImage: imageObj, - fillPatternX: -20, - fillPatternY: -30, - fillPatternScale: { x: 0.5, y: 0.5 }, - fillPatternOffset: { x: 219, y: 150 }, - fillPatternRotation: 90, - fillPatternRepeat: 'no-repeat', - - stroke: 'blue', - strokeWidth: 5, - draggable: true, - }); - - layer.add(star); - stage.add(layer); - - var pattern1 = star._getFillPattern(); - - star.fillPatternImage(Konva.Util.createCanvasElement()); - - var pattern2 = star._getFillPattern(); - - assert.notEqual(pattern1, pattern2); - - star.fillPatternRepeat('repeat'); - - var pattern3 = star._getFillPattern(); - - assert.notEqual(pattern2, pattern3); - - star.fillPatternX(10); - - var pattern4 = star._getFillPattern(); - - assert.notEqual(pattern4, pattern3); - - star.fillPatternOffsetX(10); - - var pattern5 = star._getFillPattern(); - - assert.notEqual(pattern4, pattern5); - - done(); - }); - }); - - // ====================================================== - it('cache linear gradient', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var star = new Konva.Star({ - x: 200, - y: 100, - numPoints: 5, - innerRadius: 40, - outerRadius: 70, - - fillLinearGradientStartPoint: { x: -50, y: -50 }, - fillLinearGradientEndPoint: { x: 50, y: 50 }, - fillLinearGradientColorStops: [0, 'red', 1, 'yellow'], - - stroke: 'blue', - strokeWidth: 5, - draggable: true, - }); - - layer.add(star); - stage.add(layer); - - var ctx = layer.getContext(); - var oldCreate = ctx.createLinearGradient; - - var callCount = 0; - ctx.createLinearGradient = function () { - callCount += 1; - return oldCreate.apply(this, arguments); - }; - - layer.draw(); - layer.draw(); - assert.equal(callCount, 0); - }); - - it('recache linear gradient on changes', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var star = new Konva.Star({ - x: 200, - y: 100, - numPoints: 5, - innerRadius: 40, - outerRadius: 70, - - fillLinearGradientStartPoint: { x: -50, y: -50 }, - fillLinearGradientEndPoint: { x: 50, y: 50 }, - fillLinearGradientColorStops: [0, 'red', 1, 'yellow'], - - stroke: 'blue', - strokeWidth: 5, - draggable: true, - }); - - layer.add(star); - stage.add(layer); - - var gradient1 = star._getLinearGradient(); - - star.fillLinearGradientStartPointX(-10); - - var gradient2 = star._getLinearGradient(); - - assert.notEqual(gradient1, gradient2); - - star.fillLinearGradientStartPointY(-10); - - var gradient3 = star._getLinearGradient(); - - assert.notEqual(gradient2, gradient3); - - star.fillLinearGradientEndPointX(100); - - var gradient4 = star._getLinearGradient(); - - assert.notEqual(gradient3, gradient4); - - star.fillLinearGradientEndPointY(100); - - var gradient5 = star._getLinearGradient(); - - assert.notEqual(gradient4, gradient5); - - star.fillLinearGradientColorStops([0, 'red', 1, 'green']); - - var gradient6 = star._getLinearGradient(); - - assert.notEqual(gradient5, gradient6); - - layer.draw(); - }); - - // ====================================================== - it('cache radial gradient', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var star = new Konva.Star({ - x: 200, - y: 100, - numPoints: 5, - innerRadius: 40, - outerRadius: 70, - - fillRadialGradientStartPoint: { x: 0, y: 0 }, - fillRadialGradientStartRadius: 0, - fillRadialGradientEndPoint: { x: 0, y: 0 }, - fillRadialGradientEndRadius: 70, - fillRadialGradientColorStops: [0, 'red', 0.5, 'yellow', 1, 'blue'], - - stroke: 'blue', - strokeWidth: 5, - draggable: true, - }); - - layer.add(star); - stage.add(layer); - - var ctx = layer.getContext(); - var oldCreate = ctx.createRadialGradient; - - var callCount = 0; - ctx.createRadialGradient = function () { - callCount += 1; - return oldCreate.apply(this, arguments); - }; - - layer.draw(); - layer.draw(); - assert.equal(callCount, 0); - }); - - it('recache linear gradient on changes', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var star = new Konva.Star({ - x: 200, - y: 100, - numPoints: 5, - innerRadius: 40, - outerRadius: 70, - - fillRadialGradientStartPoint: { x: 0, y: 0 }, - fillRadialGradientStartRadius: 0, - fillRadialGradientEndPoint: { x: 0, y: 0 }, - fillRadialGradientEndRadius: 70, - fillRadialGradientColorStops: [0, 'red', 0.5, 'yellow', 1, 'blue'], - - stroke: 'blue', - strokeWidth: 5, - draggable: true, - }); - - layer.add(star); - stage.add(layer); - - var gradient1 = star._getRadialGradient(); - - star.fillRadialGradientStartPointX(-10); - - var gradient2 = star._getRadialGradient(); - - assert.notEqual(gradient1, gradient2); - - star.fillRadialGradientStartPointY(-10); - - var gradient3 = star._getRadialGradient(); - - assert.notEqual(gradient2, gradient3); - - star.fillRadialGradientEndPointX(100); - - var gradient4 = star._getRadialGradient(); - - assert.notEqual(gradient3, gradient4); - - star.fillRadialGradientEndPointY(100); - - var gradient5 = star._getRadialGradient(); - - assert.notEqual(gradient4, gradient5); - - star.fillRadialGradientColorStops([0, 'red', 1, 'green']); - - var gradient6 = star._getRadialGradient(); - - assert.notEqual(gradient5, gradient6); - - star.fillRadialGradientStartRadius(10); - - var gradient7 = star._getRadialGradient(); - - assert.notEqual(gradient6, gradient7); - - star.fillRadialGradientEndRadius(200); - - var gradient8 = star._getRadialGradient(); - - assert.notEqual(gradient7, gradient8); - - layer.draw(); - }); - - it('try to add destroyed shape', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var star = new Konva.Star({ - x: 200, - y: 100, - numPoints: 5, - innerRadius: 40, - outerRadius: 70, - - stroke: 'blue', - strokeWidth: 5, - draggable: true, - }); - - star.destroy(); - - var callCount = 0; - var oldWarn = Konva.Util.warn; - Konva.Util.warn = function () { - callCount += 1; - }; - - layer.add(star); - - layer.draw(); - - assert.equal(callCount, 1); - Konva.Util.warn = oldWarn; - }); - - it('hasFill getter', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var shape = new Konva.Shape({ - stroke: 'black', - strokeWidth: 4, - sceneFunc: function (context) { - context.beginPath(); - context.moveTo(20, 50); - context.quadraticCurveTo(550, 0, 500, 500); - context.fillStrokeShape(shape); - }, - fill: 'red', - fillEnabled: false, - }); - - layer.add(shape); - assert.equal(shape.hasFill(), false); - }); - - it('test hit of non filled shape', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var line = new Konva.Shape({ - sceneFunc: function (context) { - context.beginPath(); - context.moveTo(20, 50); - context.quadraticCurveTo(550, 0, 500, 500); - - context.fillStrokeShape(line); - }, - }); - - layer.add(line); - layer.draw(); - - // we still should register shape here - // like for a non filled rectangle (with just stroke), - // we need fill it for full events - var shape = layer.getIntersection({ x: 50, y: 70 }); - assert.equal(shape, line); - }); - - it('validation on stroke should accept gradients', function () { - var callCount = 0; - var oldWarn = Konva.Util.warn; - Konva.Util.warn = function () { - callCount += 1; - }; - - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var canvas = createCanvas(); - var ctx = canvas.getContext('2d'); - - var gradient = ctx.createLinearGradient(0, 75, 100, 75); - gradient.addColorStop(0.0, 'rgba(255,255,255,1)'); - gradient.addColorStop(1 / 6, 'rgba(255,255,255,0.8)'); - gradient.addColorStop(2 / 6, 'rgba(255,255,255,0.6)'); - gradient.addColorStop(3 / 6, 'rgba(255,255,255,0.4)'); - gradient.addColorStop(4 / 6, 'rgba(255,255,255,0.3)'); - gradient.addColorStop(5 / 6, 'rgba(255,255,255,0.2)'); - gradient.addColorStop(1.0, 'rgba(255,255,255, 0)'); - - var star = new Konva.Star({ - x: 200, - y: 100, - numPoints: 5, - innerRadius: 40, - outerRadius: 70, - - stroke: gradient, - strokeWidth: 5, - draggable: true, - }); - layer.add(star); - - layer.draw(); - - assert.equal(callCount, 0); - Konva.Util.warn = oldWarn; - }); - - it('fill rule on hit graph', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - stage.add(layer); - - var mask = new Konva.Shape({ - sceneFunc: function (ctx, shape) { - ctx.beginPath(); - ctx.rect(0, 0, 500, 500); - ctx.rect(100, 100, 100, 100); - ctx.closePath(); - ctx.fillShape(shape); - }, - draggable: true, - fill: 'red', - fillRule: 'evenodd', - }); - - layer.add(mask); - layer.draw(); - const trace = layer.getContext().getTrace(); - - assert.equal( - trace, - 'clearRect(0,0,578,200);clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);beginPath();rect(0,0,500,500);rect(100,100,100,100);closePath();fillStyle=red;fill(evenodd);restore();' - ); - - const hitShape = layer.getIntersection({ x: 150, y: 150 }); - assert.equal(hitShape, null); - }); -}); diff --git a/test/unit/Spline-test.ts b/test/unit/Spline-test.ts deleted file mode 100644 index 8ec014200..000000000 --- a/test/unit/Spline-test.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { assert } from 'chai'; - -import { addStage, Konva } from './test-utils'; - -describe('Spline', function () { - // ====================================================== - it('add splines', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var line1 = new Konva.Line({ - points: [73, 160, 340, 23, 500, 109, 300, 109], - stroke: 'blue', - strokeWidth: 10, - lineCap: 'round', - lineJoin: 'round', - draggable: true, - tension: 1, - }); - - var line2 = new Konva.Line({ - points: [73, 160, 340, 23, 500, 109], - stroke: 'red', - strokeWidth: 10, - lineCap: 'round', - lineJoin: 'round', - draggable: true, - tension: 1, - }); - - var line3 = new Konva.Line({ - points: [73, 160, 340, 23], - stroke: 'green', - strokeWidth: 10, - lineCap: 'round', - lineJoin: 'round', - draggable: true, - tension: 1, - }); - - layer.add(line1); - layer.add(line2); - layer.add(line3); - stage.add(layer); - - assert.equal(line1.getClassName(), 'Line'); - - var trace = layer.getContext().getTrace(); - - //console.log(trace); - assert.equal( - trace, - 'clearRect(0,0,578,200);save();lineJoin=round;transform(1,0,0,1,0,0);beginPath();moveTo(73,160);quadraticCurveTo(74.006,54.77,340,23);bezierCurveTo(501.006,3.77,519.038,68.068,500,109);quadraticCurveTo(479.038,154.068,300,109);lineCap=round;lineWidth=10;strokeStyle=blue;stroke();restore();save();lineJoin=round;transform(1,0,0,1,0,0);beginPath();moveTo(73,160);quadraticCurveTo(74.006,54.77,340,23);quadraticCurveTo(501.006,3.77,500,109);lineCap=round;lineWidth=10;strokeStyle=red;stroke();restore();save();lineJoin=round;transform(1,0,0,1,0,0);beginPath();moveTo(73,160);lineTo(340,23);lineCap=round;lineWidth=10;strokeStyle=green;stroke();restore();' - ); - }); - - // ====================================================== - it('update spline points', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var spline = new Konva.Line({ - points: [73, 160, 340, 23, 500, 109, 300, 109], - stroke: 'blue', - strokeWidth: 10, - lineCap: 'round', - lineJoin: 'round', - draggable: true, - tension: 1, - }); - - layer.add(spline); - stage.add(layer); - - assert.equal(spline.getTensionPoints().length, 12); - - spline.points([73, 160, 340, 23, 500, 109]); - - assert.equal(spline.getTensionPoints().length, 6); - - layer.draw(); - }); - - // ====================================================== - it('add point to spline points', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var spline = new Konva.Line({ - points: [73, 160, 340, 23, 500, 109, 300, 109], - stroke: 'blue', - strokeWidth: 10, - lineCap: 'round', - lineJoin: 'round', - draggable: true, - tension: 1, - }); - - layer.add(spline); - stage.add(layer); - - assert.equal(spline.points().length, 8); - - var points = spline.points(); - points.push(300); - points.push(200); - spline.clearCache(); - - assert.equal(spline.points().length, 10); - - layer.draw(); - }); -}); diff --git a/test/unit/Sprite-test.ts b/test/unit/Sprite-test.ts deleted file mode 100644 index 9e8f3cfc1..000000000 --- a/test/unit/Sprite-test.ts +++ /dev/null @@ -1,464 +0,0 @@ -import { assert } from 'chai'; - -import { addStage, Konva, loadImage } from './test-utils'; - -describe('Sprite', function () { - // ====================================================== - it('add sprite', function (done) { - loadImage('scorpion-sprite.png', (imageObj) => { - var stage = addStage(); - var layer = new Konva.Layer(); - - var sprite = new Konva.Sprite({ - x: 200, - y: 50, - image: imageObj, - animation: 'standing', - animations: { - standing: [ - 0, - 0, - 49, - 109, - 52, - 0, - 49, - 109, - 105, - 0, - 49, - 109, - 158, - 0, - 49, - 109, - 210, - 0, - 49, - 109, - 262, - 0, - 49, - 109, - ], - kicking: [ - 0, - 109, - 45, - 98, - 45, - 109, - 45, - 98, - 95, - 109, - 63, - 98, - 156, - 109, - 70, - 98, - 229, - 109, - 60, - 98, - 287, - 109, - 41, - 98, - ], - }, - frameRate: 10, - draggable: true, - shadowColor: 'black', - shadowBlur: 3, - shadowOffset: { x: 3, y: 1 }, - shadowOpacity: 0.3, - }); - - layer.add(sprite); - stage.add(layer); - - assert.equal(sprite.getClassName(), 'Sprite'); - assert.equal(sprite.frameIndex(), 0); - - var trace = layer.hitCanvas.getContext().getTrace(); - - assert.equal(trace.indexOf(sprite.colorKey) >= 0, true); - - sprite.start(); - - // kick once - setTimeout(function () { - sprite.animation('kicking'); - sprite.on('indexChange', function (evt) { - if (evt['newVal'] === 0 && this.animation() === 'kicking') { - sprite.animation('standing'); - } - }); - }, 2000); - setTimeout(function () { - sprite.stop(); - }, 3000); - - done(); - }); - }); - - // ====================================================== - it('don`t update layer too many times', function (done) { - loadImage('scorpion-sprite.png', (imageObj) => { - var stage = addStage(); - var layer = new Konva.Layer(); - - var sprite = new Konva.Sprite({ - x: 200, - y: 50, - image: imageObj, - animation: 'standing', - animations: { - standing: [ - 0, - 0, - 49, - 109, - 52, - 0, - 49, - 109, - 105, - 0, - 49, - 109, - 158, - 0, - 49, - 109, - 210, - 0, - 49, - 109, - 262, - 0, - 49, - 109, - ], - }, - frameRate: 5, - draggable: true, - shadowColor: 'black', - shadowBlur: 3, - shadowOffset: { x: 3, y: 1 }, - shadowOpacity: 0.3, - }); - - layer.add(sprite); - stage.add(layer); - - var oldDraw = layer.draw; - var updateCount = 0; - layer.draw = function () { - updateCount++; - oldDraw.call(layer); - return layer; - }; - - sprite.start(); - setTimeout(function () { - sprite.stop(); - assert.equal(updateCount < 7, true); - done(); - }, 1000); - }); - }); - - // ====================================================== - it('don`t update layer too many times 2', function (done) { - loadImage('scorpion-sprite.png', (imageObj) => { - var stage = addStage(); - var layer = new Konva.Layer(); - - var sprite = new Konva.Sprite({ - x: 200, - y: 50, - image: imageObj, - animation: 'standing', - animations: { - standing: [ - 0, - 0, - 49, - 109, - 52, - 0, - 49, - 109, - 105, - 0, - 49, - 109, - 158, - 0, - 49, - 109, - 210, - 0, - 49, - 109, - 262, - 0, - 49, - 109, - ], - }, - frameRate: 5, - }); - - var sprite2 = new Konva.Sprite({ - x: 200, - y: 50, - image: imageObj, - animation: 'standing', - animations: { - standing: [ - 0, - 0, - 49, - 109, - 52, - 0, - 49, - 109, - 105, - 0, - 49, - 109, - 158, - 0, - 49, - 109, - 210, - 0, - 49, - 109, - 262, - 0, - 49, - 109, - ], - }, - frameRate: 20, - }); - - layer.add(sprite).add(sprite2); - stage.add(layer); - - var oldDraw = layer.draw; - var updateCount = 0; - layer.draw = function () { - updateCount++; - oldDraw.call(layer); - return layer; - }; - - sprite.start(); - sprite2.start(); - setTimeout(function () { - sprite.stop(); - sprite2.stop(); - assert.equal(updateCount > 15, true); - assert.equal(updateCount < 27, true); - done(); - }, 1000); - }); - }); - - it('check is sprite running', function (done) { - loadImage('scorpion-sprite.png', (imageObj) => { - var stage = addStage(); - var layer = new Konva.Layer(); - - var sprite = new Konva.Sprite({ - x: 200, - y: 50, - image: imageObj, - animation: 'standing', - animations: { - standing: [ - 0, - 0, - 49, - 109, - 52, - 0, - 49, - 109, - 105, - 0, - 49, - 109, - 158, - 0, - 49, - 109, - 210, - 0, - 49, - 109, - 262, - 0, - 49, - 109, - ], - }, - frameRate: 50, - draggable: true, - shadowColor: 'black', - shadowBlur: 3, - shadowOffset: { x: 3, y: 1 }, - shadowOpacity: 0.3, - }); - - layer.add(sprite); - stage.add(layer); - assert.equal(sprite.isRunning(), false); - sprite.start(); - assert.equal(sprite.isRunning(), true); - sprite.stop(); - done(); - }); - }); - - it('start do nothing if animation is already running', function (done) { - loadImage('scorpion-sprite.png', (imageObj) => { - var stage = addStage(); - var layer = new Konva.Layer(); - - var sprite = new Konva.Sprite({ - x: 200, - y: 50, - image: imageObj, - animation: 'standing', - animations: { - standing: [ - 0, - 0, - 49, - 109, - 52, - 0, - 49, - 109, - 105, - 0, - 49, - 109, - 158, - 0, - 49, - 109, - 210, - 0, - 49, - 109, - 262, - 0, - 49, - 109, - ], - }, - frameRate: 50, - draggable: true, - shadowColor: 'black', - shadowBlur: 3, - shadowOffset: { x: 3, y: 1 }, - shadowOpacity: 0.3, - }); - - layer.add(sprite); - stage.add(layer); - - var counter = 0; - sprite.on('frameIndexChange.konva', function (event) { - counter += 1; - }); - - sprite.start(); - sprite.start(); - sprite.stop(); - - setTimeout(function () { - assert.equal(counter, 0); - done(); - }, 200); - }); - }); - - // need fix. - it.skip('can change frame rate on fly', function (done) { - loadImage('scorpion-sprite.png', (imageObj) => { - var stage = addStage(); - var layer = new Konva.Layer(); - - var sprite = new Konva.Sprite({ - x: 200, - y: 50, - image: imageObj, - animation: 'standing', - animations: { - standing: [ - 0, - 0, - 49, - 109, - 52, - 0, - 49, - 109, - 105, - 0, - 49, - 109, - 158, - 0, - 49, - 109, - 210, - 0, - 49, - 109, - 262, - 0, - 49, - 109, - ], - }, - frameRate: 50, - draggable: true, - shadowColor: 'black', - shadowBlur: 3, - shadowOffset: { x: 3, y: 1 }, - shadowOpacity: 0.3, - }); - - layer.add(sprite); - stage.add(layer); - assert.equal(sprite.frameRate(), 50); - setTimeout(function () { - sprite.frameRate(100); - assert.equal(sprite.frameRate(), 100); - assert.equal(sprite.anim.isRunning(), false, '1'); - }, 23); - - setTimeout(function () { - sprite.start(); - sprite.frameRate(52); - assert.equal(sprite.anim.isRunning(), true); - // for this moment should tick more than 2 times - // make sure that sprite is not restating after set frame rate - assert.equal(sprite.frameIndex() > 2, true, '2'); - done(); - }, 68); - }); - }); -}); diff --git a/test/unit/Stage-test.ts b/test/unit/Stage-test.ts deleted file mode 100644 index 76f236781..000000000 --- a/test/unit/Stage-test.ts +++ /dev/null @@ -1,1456 +0,0 @@ -import { assert } from 'chai'; - -import { - addStage, - simulateMouseDown, - simulateMouseMove, - simulateMouseUp, - simulateTouchStart, - simulateTouchMove, - simulateTouchEnd, - compareCanvases, - createCanvas, - showHit, - getContainer, - isNode, - isBrowser, - Konva, -} from './test-utils'; - -describe('Stage', function () { - // ====================================================== - it('instantiate stage with id', function () { - if (isNode) { - return; - } - var container = Konva.document.createElement('div'); - container.id = 'container'; - getContainer().appendChild(container); - - var stage = new Konva.Stage({ - container: 'container', - width: 578, - height: 200, - }); - - assert.equal(stage.getContent().className, 'konvajs-content'); - assert.equal(stage.getContent().getAttribute('role'), 'presentation'); - }); - - // ====================================================== - it('test stage buffer canvas and hit buffer canvas', function () { - if (isNode) { - return; - } - var container = Konva.document.createElement('div'); - container.id = 'container'; - - getContainer().appendChild(container); - - // simulate pixelRatio = 2 - Konva.pixelRatio = 2; - - var stage = new Konva.Stage({ - container: 'container', - width: 578, - height: 200, - }); - - assert.equal(stage.bufferCanvas.getPixelRatio(), 2); - assert.equal(stage.bufferHitCanvas.getPixelRatio(), 1); - - // reset - Konva.pixelRatio = 1; - }); - - // ====================================================== - it('instantiate stage with dom element', function () { - if (isNode) { - return; - } - var container = Konva.document.createElement('div'); - - getContainer().appendChild(container); - - var stage = new Konva.Stage({ - container: container, - width: 578, - height: 200, - }); - - assert.equal(stage.container(), container); - }); - - // ====================================================== - it('stage instantiation should clear container', function () { - if (isNode) { - return; - } - var container = Konva.document.createElement('div'); - var dummy = Konva.document.createElement('p'); - - container.appendChild(dummy); - getContainer().appendChild(container); - - var stage = new Konva.Stage({ - container: container, - width: 578, - height: 200, - }); - - assert.equal( - container.getElementsByTagName('p').length, - 0, - 'container should have no p tags' - ); - }); - - // ====================================================== - it('test stage cloning', function () { - if (isNode) { - return; - } - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var stageClone = stage.clone(); - assert.notEqual( - stage.container(), - stageClone.container(), - 'clone should be in different container' - ); - - assert.equal( - stage.container().childNodes[0].childNodes.length, - 1, - 'container should not have changes' - ); - }); - - // ====================================================== - it('set stage size', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - }); - - assert.equal(stage.getSize().width, 578); - assert.equal(stage.getSize().height, 200); - stage.setSize({ width: 1, height: 2 }); - assert.equal(stage.getSize().width, 1); - assert.equal(stage.getSize().height, 2); - stage.setSize({ width: 3, height: 3 }); - assert.equal(stage.getSize().width, 3); - assert.equal(stage.getSize().height, 3); - stage.setSize({ - width: 4, - height: 5, - }); - assert.equal(stage.getSize().width, 4); - assert.equal(stage.getSize().height, 5); - stage.width(6); - assert.equal(stage.getSize().width, 6); - assert.equal(stage.getSize().height, 5); - stage.height(7); - assert.equal(stage.getSize().width, 6); - assert.equal(stage.getSize().height, 7); - stage.setSize({ width: 8, height: 9 }); - assert.equal(stage.getSize().width, 8); - assert.equal(stage.getSize().height, 9); - stage.setSize({ width: 10, height: 11 }); - assert.equal(stage.getSize().width, 10); - assert.equal(stage.getSize().height, 11); - - layer.add(circle); - stage.add(layer); - - stage.setSize({ width: 333, height: 155 }); - - assert.equal(stage.getSize().width, 333); - assert.equal(stage.getSize().height, 155); - if (isBrowser) { - assert.equal(stage.getContent().style.width, '333px'); - assert.equal(stage.getContent().style.height, '155px'); - } - - assert.equal( - layer.getCanvas()._canvas.width, - 333 * layer.getCanvas().getPixelRatio() - ); - assert.equal( - layer.getCanvas()._canvas.height, - 155 * layer.getCanvas().getPixelRatio() - ); - }); - - // ====================================================== - it('get stage DOM', function () { - if (isNode) { - return; - } - var stage = addStage(); - - assert.equal(stage.getContent().className, 'konvajs-content'); - }); - - it('try to move stage ', function () { - if (isNode) { - return; - } - var stage = addStage(); - var container = document.createElement('div'); - var wrap = stage.container().parentNode; - wrap.appendChild(container); - - stage.container(container); - - assert.equal(stage.container(), container); - - assert.equal(stage.content, container.children[0]); - }); - - it('clone stage ', function () { - if (isNode) { - return; - } - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var shape = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - strokeWidth: 4, - fill: 'red', - stroke: 'black', - }); - layer.add(shape); - layer.draw(); - - var container = document.createElement('div'); - var wrap = stage.container().parentNode; - wrap.appendChild(container); - - var clone = stage.clone(); - clone.container(container); - - assert.equal(clone.container(), container); - - assert.equal(clone.content, container.children[0]); - }); - - it('dangling stage ', function () { - if (isNode) { - return; - } - var stage = addStage(); - var container = stage.container(); - var parent = stage.content.parentElement; - - parent.removeChild(stage.content); - - stage.setContainer(container); - - assert.equal(stage.container(), container); - }); - - // ====================================================== - it('stage getIntersection()', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var redCircle = new Konva.Circle({ - x: 380, - y: stage.height() / 2, - radius: 70, - strokeWidth: 4, - fill: 'red', - stroke: 'black', - }); - - var greenCircle = new Konva.Circle({ - x: 300, - y: stage.height() / 2, - radius: 70, - strokeWidth: 4, - fill: 'green', - stroke: 'black', - }); - - layer.add(redCircle); - layer.add(greenCircle); - stage.add(layer); - - assert.equal( - stage.getIntersection({ x: 300, y: 100 }), - greenCircle, - 'shape should be greenCircle' - ); - assert.equal( - stage.getIntersection({ x: 380, y: 100 }), - redCircle, - 'shape should be redCircle' - ); - assert.equal( - stage.getIntersection({ x: 100, y: 100 }), - null, - 'shape should be null' - ); - }); - - // ====================================================== - it('stage getIntersection() edge detection', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var redCircle = new Konva.Circle({ - x: 380, - y: stage.height() / 2, - radius: 70, - strokeWidth: 4, - fill: 'red', - stroke: 'black', - }); - - var greenCircle = new Konva.Circle({ - x: 300, - y: stage.height() / 2, - radius: 70, - strokeWidth: 4, - fill: 'green', - stroke: 'black', - }); - - stage.on('contentMousemove', function () { - var pos = stage.getPointerPosition(); - var shape = stage.getIntersection(pos); - if (!shape) { - //console.log(pos); - } - }); - - layer.add(redCircle); - layer.add(greenCircle); - stage.add(layer); - - assert.equal( - stage.getIntersection({ x: 370, y: 93 }), - greenCircle, - 'shape should be greenCircle' - ); - assert.equal( - stage.getIntersection({ x: 371, y: 93 }), - greenCircle, - 'shape should be greenCircle' - ); - assert.equal( - stage.getIntersection({ x: 372, y: 93 }), - redCircle, - 'shape should be redCircle' - ); - }); - - // ====================================================== - it('test getAllIntersections', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var redCircle = new Konva.Circle({ - x: 380, - y: stage.height() / 2, - radius: 70, - strokeWidth: 4, - fill: 'red', - stroke: 'black', - id: 'redCircle', - }); - - var greenCircle = new Konva.Circle({ - x: 300, - y: stage.height() / 2, - radius: 70, - strokeWidth: 4, - fill: 'green', - stroke: 'black', - id: 'greenCircle', - }); - - layer.add(redCircle); - layer.add(greenCircle); - stage.add(layer); - - // test individual shapes - assert.equal( - stage.getAllIntersections({ x: 266, y: 114 }).length, - 1, - '17) getAllIntersections should return one shape' - ); - assert.equal( - stage.getAllIntersections({ x: 266, y: 114 })[0].getId(), - 'greenCircle', - '19) first intersection should be greenCircle' - ); - - assert.equal( - stage.getAllIntersections({ x: 414, y: 115 }).length, - 1, - '18) getAllIntersections should return one shape' - ); - assert.equal( - stage.getAllIntersections({ x: 414, y: 115 })[0].getId(), - 'redCircle', - '20) first intersection should be redCircle' - ); - - assert.equal( - stage.getAllIntersections({ x: 350, y: 118 }).length, - 2, - '1) getAllIntersections should return two shapes' - ); - assert.equal( - stage.getAllIntersections({ x: 350, y: 118 })[0].getId(), - 'redCircle', - '2) first intersection should be redCircle' - ); - assert.equal( - stage.getAllIntersections({ x: 350, y: 118 })[1].getId(), - 'greenCircle', - '3) second intersection should be greenCircle' - ); - - // hide green circle. make sure only red circle is in result set - greenCircle.hide(); - layer.draw(); - - assert.equal( - stage.getAllIntersections({ x: 350, y: 118 }).length, - 1, - '4) getAllIntersections should return one shape' - ); - assert.equal( - stage.getAllIntersections({ x: 350, y: 118 })[0].getId(), - 'redCircle', - '5) first intersection should be redCircle' - ); - - // show green circle again. make sure both circles are in result set - greenCircle.show(); - layer.draw(); - - assert.equal( - stage.getAllIntersections({ x: 350, y: 118 }).length, - 2, - '6) getAllIntersections should return two shapes' - ); - assert.equal( - stage.getAllIntersections({ x: 350, y: 118 })[0].getId(), - 'redCircle', - '7) first intersection should be redCircle' - ); - assert.equal( - stage.getAllIntersections({ x: 350, y: 118 })[1].getId(), - 'greenCircle', - '8) second intersection should be greenCircle' - ); - - // hide red circle. make sure only green circle is in result set - redCircle.hide(); - layer.draw(); - - assert.equal( - stage.getAllIntersections({ x: 350, y: 118 }).length, - 1, - '9) getAllIntersections should return one shape' - ); - assert.equal( - stage.getAllIntersections({ x: 350, y: 118 })[0].getId(), - 'greenCircle', - '10) first intersection should be greenCircle' - ); - - // show red circle again. make sure both circles are in result set - redCircle.show(); - layer.draw(); - - assert.equal( - stage.getAllIntersections({ x: 350, y: 118 }).length, - 2, - '11) getAllIntersections should return two shapes' - ); - assert.equal( - stage.getAllIntersections({ x: 350, y: 118 })[0].getId(), - 'redCircle', - '12) first intersection should be redCircle' - ); - assert.equal( - stage.getAllIntersections({ x: 350, y: 118 })[1].getId(), - 'greenCircle', - '13) second intersection should be greenCircle' - ); - - // test from layer - assert.equal( - layer.getAllIntersections({ x: 350, y: 118 }).length, - 2, - '14) getAllIntersections should return two shapes' - ); - assert.equal( - layer.getAllIntersections({ x: 350, y: 118 })[0].getId(), - 'redCircle', - '15) first intersection should be redCircle' - ); - assert.equal( - layer.getAllIntersections({ x: 350, y: 118 })[1].getId(), - 'greenCircle', - '16) second intersection should be greenCircle' - ); - - // now hide layer and but force visible for shape. - - layer.hide(); - redCircle.visible(true); - assert.equal(stage.getAllIntersections(redCircle.position()).length, 0); - }); - - // ====================================================== - it('test getAllIntersections for text', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var text = new Konva.Text({ - x: 0, - y: 0, - text: 'Hello world', - fontSize: 50, - name: 'intersectText', - }); - - layer.add(text); - stage.add(layer); - - // test individual shapes - assert.equal( - stage.getAllIntersections({ x: 10, y: 10 }).length, - 1, - '17) getAllIntersections should return one shape' - ); - }); - - // ====================================================== - it('Should not throw on clip for stage', function () { - // no asserts, because we check throw - var stage = addStage({ - clipFunc: function () {}, - }); - var layer = new Konva.Layer(); - - var text = new Konva.Text({ - x: 0, - y: 0, - text: 'Hello world', - fontSize: 50, - name: 'intersectText', - }); - - layer.add(text); - stage.add(layer); - }); - - // ====================================================== - it('scale stage after add layer', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - }); - - layer.add(circle); - stage.add(layer); - - stage.scale({ x: 0.5, y: 0.5 }); - - assert.equal(stage.scale().x, 0.5, 'stage scale x should be 0.5'); - assert.equal(stage.scale().y, 0.5, 'stage scale y should be 0.5'); - stage.draw(); - }); - - // ====================================================== - it('scale stage before add shape', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - }); - - stage.scale({ x: 0.5, y: 0.5 }); - - assert.equal(stage.scale().x, 0.5, 'stage scale x should be 0.5'); - assert.equal(stage.scale().y, 0.5, 'stage scale y should be 0.5'); - - layer.add(circle); - stage.add(layer); - }); - - // ====================================================== - // TODO: restore it, remove should deatach from DOM - it.skip('remove stage', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - }); - - layer.add(circle); - stage.add(layer); - - // remove should have no effect, and should cause no JS error - stage.remove(); - - assert.equal(stage.content.parentNode, undefined); - }); - - // ====================================================== - it('destroy stage', function () { - var stage = addStage({ - width: 578, - height: 200, - id: 'stageFalconId', - name: 'stageFalconName', - }); - - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - id: 'circleFalconId', - name: 'circleFalconName', - }); - - layer.add(circle); - stage.add(layer); - - stage.destroy(); - - assert.equal( - Konva.stages.indexOf(stage) === -1, - true, - 'stage should not be in stages array' - ); - }); - - // ====================================================== - it('scale stage with no shapes', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - - stage.add(layer); - stage.scaleX(0.5); - - stage.draw(); - - assert.equal(stage.scaleX(), 0.5); - }); - - // ====================================================== - it('test stage.getStage()', function () { - var stage = addStage(); - - assert.notEqual(stage.getStage(), undefined); - - //console.log(stage.getStage()); - }); - - it('add multiple layers to stage', function () { - var stage = addStage(); - var layer1 = new Konva.Layer(); - var layer2 = new Konva.Layer(); - var layer3 = new Konva.Layer(); - stage.add(layer1, layer2, layer3); - assert.equal(stage.getLayers().length, 3, 'stage has exactly three layers'); - }); - // ====================================================== - it('test drag and click', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var rect = new Konva.Rect({ - x: 50, - y: 50, - width: 50, - height: 50, - fill: 'red', - draggable: true, - }); - - layer.add(rect); - stage.add(layer); - - rect.on('dblclick', function () { - assert(false, 'double click fired'); - }); - - var y = 60; - - // simulate dragging - simulateMouseDown(stage, { - x: 60, - y: y, - }); - - simulateMouseMove(stage, { - x: 61, - y: y, - }); - - simulateMouseMove(stage, { - x: 62, - y: y, - }); - - simulateMouseMove(stage, { - x: 63, - y: y, - }); - - simulateMouseMove(stage, { - x: 64, - y: y, - }); - - simulateMouseUp(stage, { - x: 65, - y: y, - }); - - assert.equal(Konva.isDragging(), false); - assert.equal(Konva.DD.node, undefined); - // simulate click - simulateMouseDown(stage, { - x: 66, - y: y, - }); - - simulateMouseUp(stage, { - x: 66, - y: y, - }); - assert.equal(Konva.isDragging(), false); - assert.equal(Konva.DD.node, undefined); - }); - - // ====================================================== - it('do not trigger stage click after dragend', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var rect = new Konva.Rect({ - x: 0, - y: 0, - width: 50, - height: 50, - fill: 'red', - draggable: true, - }); - - layer.add(rect); - stage.add(layer); - - var clicks = 0; - - stage.on('click', function () { - clicks += 1; - }); - - // simulate dragging - simulateMouseDown(stage, { - x: 25, - y: 25, - }); - - simulateMouseMove(stage, { - x: 100, - y: 100, - }); - - // move rect out of mouse - rect.x(-30); - rect.y(-30); - - simulateMouseUp(stage, { - x: 100, - y: 100, - }); - - assert.equal(clicks, 0); - }); - - it('can listen click on empty areas', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var dblicks = 0; - var clicks = 0; - var mousedowns = 0; - var mouseups = 0; - var mousemoves = 0; - - stage.on('mousedown', function (e) { - mousedowns += 1; - assert.equal(e.target, stage); - assert.equal(e.currentTarget, stage); - }); - - stage.on('mousemove', function (e) { - mousemoves += 1; - assert.equal(e.target, stage); - assert.equal(e.currentTarget, stage); - }); - - stage.on('mouseup', function (e) { - mouseups += 1; - assert.equal(e.target, stage); - assert.equal(e.currentTarget, stage); - }); - - stage.on('click', function (e) { - clicks += 1; - assert.equal(e.target, stage); - assert.equal(e.currentTarget, stage); - }); - - stage.on('dblclick', function (e) { - dblicks += 1; - assert.equal(e.target, stage); - assert.equal(e.currentTarget, stage); - }); - - // simulate dragging - simulateMouseDown(stage, { - x: 60, - y: 10, - }); - - simulateMouseMove(stage, { - x: 60, - y: 10, - }); - - simulateMouseUp(stage, { - x: 65, - y: 10, - }); - - assert.equal(mousedowns, 1, 'first mousedown registered'); - assert.equal(mouseups, 1, 'first mouseup registered'); - assert.equal(clicks, 1, 'first click registered'); - assert.equal(mousemoves, 1, 'first mousemove registered'); - assert.equal(dblicks, 0, 'no dbclicks registered'); - - simulateMouseDown(stage, { - x: 60, - y: 10, - }); - - simulateMouseUp(stage, { - x: 65, - y: 10, - }); - - assert.equal(mousedowns, 2, 'second mousedown registered'); - assert.equal(mouseups, 2, 'seconds mouseup registered'); - assert.equal(clicks, 2, 'seconds click registered'); - assert.equal(dblicks, 1, 'first dbclick registered'); - }); - - it('can listen taps on empty areas', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var dbltaps = 0; - var taps = 0; - var touchstarts = 0; - var touchends = 0; - var touchmoves = 0; - - stage.on('touchstart', function (e) { - touchstarts += 1; - assert.equal(e.target, stage); - assert.equal(e.currentTarget, stage); - }); - - stage.on('touchend', function (e) { - touchends += 1; - assert.equal(e.target, stage); - assert.equal(e.currentTarget, stage); - }); - - stage.on('touchmove', function (e) { - touchmoves += 1; - assert.equal(e.target, stage); - assert.equal(e.currentTarget, stage); - }); - - stage.on('tap', function (e) { - taps += 1; - assert.equal(e.target, stage); - assert.equal(e.currentTarget, stage); - }); - - stage.on('dbltap', function (e) { - dbltaps += 1; - assert.equal(e.target, stage); - assert.equal(e.currentTarget, stage); - }); - - // simulate dragging - simulateTouchStart(stage, [{ x: 100, y: 100, id: 1 }]); - - simulateTouchMove(stage, [{ x: 100, y: 100, id: 1 }]); - - simulateTouchEnd(stage, [], [{ x: 100, y: 100, id: 1 }]); - - assert.equal(touchstarts, 1, 'first touchstart registered'); - assert.equal(touchends, 1, 'first touchends registered'); - assert.equal(taps, 1, 'first tap registered'); - assert.equal(touchmoves, 1, 'first touchmove registered'); - assert.equal(dbltaps, 0, 'no dbltap registered'); - - simulateTouchStart(stage, [{ x: 100, y: 100, id: 1 }]); - - simulateTouchEnd(stage, [], [{ x: 100, y: 100, id: 1 }]); - - assert.equal(touchstarts, 2, 'first touchstart registered'); - assert.equal(touchends, 2, 'first touchends registered'); - assert.equal(taps, 2, 'first tap registered'); - assert.equal(dbltaps, 1, 'dbltap registered'); - }); - - it('pass context and wheel events to shape', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - x: 50, - y: 50, - width: 100, - height: 100, - fill: 'red', - }); - layer.add(rect); - layer.draw(); - - var contextmenus = 0; - var wheels = 0; - - // test on empty - stage.on('contextmenu', function (e) { - contextmenus += 1; - assert.equal(e.target, stage); - assert.equal(e.currentTarget, stage); - }); - - stage.on('wheel', function (e) { - wheels += 1; - assert.equal(e.target, stage); - assert.equal(e.currentTarget, stage); - }); - - var top = stage.content ? stage.content.getBoundingClientRect().top : 0; - stage._contextmenu({ - clientX: 0, - clientY: top + 0, - }); - stage._wheel({ - clientX: 0, - clientY: top + 0, - }); - - assert.equal(contextmenus, 1, 'first contextment registered'); - assert.equal(wheels, 1, 'first wheel registered'); - - stage.off('contextmenu'); - stage.off('wheel'); - - // test on shape - stage.on('contextmenu', function (e) { - contextmenus += 1; - assert.equal(e.target, rect); - assert.equal(e.currentTarget, stage); - }); - - stage.on('wheel', function (e) { - wheels += 1; - assert.equal(e.target, rect); - assert.equal(e.currentTarget, stage); - }); - stage._contextmenu({ - clientX: 60, - clientY: top + 60, - }); - stage._wheel({ - clientX: 60, - clientY: top + 60, - }); - - assert.equal(contextmenus, 2, 'second contextment registered'); - assert.equal(wheels, 2, 'second wheel registered'); - }); - - it('make sure it does not trigger too many events', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - var rect = new Konva.Rect({ - width: stage.width(), - height: stage.height(), - }); - layer.add(rect); - layer.draw(); - - var dblicks = 0; - var clicks = 0; - var mousedowns = 0; - var mouseups = 0; - - stage.on('mousedown', function (e) { - mousedowns += 1; - assert.equal(e.target, rect); - assert.equal(e.currentTarget, stage); - }); - - stage.on('mouseup', function (e) { - mouseups += 1; - assert.equal(e.target, rect); - assert.equal(e.currentTarget, stage); - }); - - stage.on('click', function (e) { - clicks += 1; - assert.equal(e.target, rect); - assert.equal(e.currentTarget, stage); - }); - - stage.on('dblclick', function (e) { - dblicks += 1; - assert.equal(e.target, rect); - assert.equal(e.currentTarget, stage); - }); - - // simulate dragging - simulateMouseDown(stage, { - x: 60, - y: 10, - }); - - simulateMouseUp(stage, { - x: 65, - y: 10, - }); - - assert.equal(mousedowns, 1, 'first mousedown registered'); - assert.equal(mouseups, 1, 'first mouseup registered'); - assert.equal(clicks, 1, 'first click registered'); - assert.equal(dblicks, 0, 'no dbclicks registered'); - - simulateMouseDown(stage, { - x: 60, - y: 10, - }); - - simulateMouseUp(stage, { - x: 65, - y: 10, - }); - - assert.equal(mousedowns, 2, 'second mousedown registered'); - assert.equal(mouseups, 2, 'seconds mouseup registered'); - assert.equal(clicks, 2, 'seconds click registered'); - assert.equal(dblicks, 1, 'first dbclick registered'); - }); - - it('test mouseover event on stage', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - var rect1 = new Konva.Rect({ - x: 50, - y: 50, - width: 50, - height: 50, - fill: 'red', - }); - layer.add(rect1); - - var rect2 = new Konva.Rect({ - x: 100, - y: 100, - width: 50, - height: 50, - fill: 'red', - }); - layer.add(rect2); - layer.draw(); - - var mouseover = 0; - - stage.on('mouseover', function (e) { - mouseover += 1; - - if (mouseover === 1) { - assert.equal(e.target, stage); - assert.equal(e.currentTarget, stage); - } - if (mouseover === 2) { - assert.equal(e.target, rect1); - } - if (mouseover === 3) { - assert.equal(e.target, rect2); - } - }); - - stage._pointerover({ - clientX: 0, - clientY: 0, - type: 'mouseover', - }); - - assert.equal(mouseover, 1, 'initial over'); - simulateMouseMove(stage, { - x: 10, - y: 10, - }); - - assert.equal(mouseover, 1, 'moved inside stage - no new over events'); - - simulateMouseMove(stage, { - x: 60, - y: 60, - }); - - assert.equal(mouseover, 2, 'moved into inner shape, trigger new mouseover'); - - simulateMouseMove(stage, { - x: 110, - y: 110, - }); - - assert.equal( - mouseover, - 3, - 'moved into second shape, trigger new mouseover' - ); - - simulateMouseMove(stage, { - x: 10, - y: 10, - }); - - assert.equal( - mouseover, - 4, - 'moved to empty space shape, trigger new mouseover' - ); - }); - - it('toCanvas in sync way', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - fill: 'black', - radius: 50, - }); - layer.add(circle); - stage.add(layer); - - compareCanvases(stage.toCanvas(), layer.toCanvas(), 200); - }); - - it('listen to mouseleave event on container', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - fill: 'black', - radius: 50, - }); - layer.add(circle); - stage.add(layer); - - var count = 0; - stage.on('mouseleave', function () { - count += 1; - }); - stage.on('mouseout', function () { - count += 1; - }); - stage._pointerleave({ type: 'mouseleave' }); - assert.equal(count, 2); - }); - - it('toDataURL with hidden layer', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - fill: 'red', - radius: 50, - }); - layer.add(circle); - stage.add(layer); - - var stageDataUrl = stage.toDataURL(); - layer.visible(false); - assert.equal(stage.toDataURL() === stageDataUrl, false); - }); - - it('toDataURL works as toCanvas', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - fill: 'red', - radius: 50, - }); - layer.add(circle); - stage.add(layer); - - assert.equal(stage.toDataURL(), stage.toCanvas().toDataURL()); - }); - - it('toDataURL should no relate on stage size', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - fill: 'red', - radius: stage.height() * 0.6, - }); - layer.add(circle); - stage.add(layer); - - compareCanvases(stage.toCanvas(circle.getClientRect()), circle.toCanvas()); - }); - - it('toCanvas with large size', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var radius = stage.height() / 2 + 10; - var circle = new Konva.Circle({ - x: stage.height() / 2, - y: stage.height() / 2, - fill: 'black', - radius: radius, - }); - layer.add(circle); - stage.add(layer); - - var stageCanvas = stage.toCanvas({ - x: -10, - y: -10, - width: stage.height() + 20, - height: stage.height() + 20, - }); - - var canvas = createCanvas(); - canvas.width = radius * 2; - canvas.height = radius * 2; - var context = canvas.getContext('2d'); - context.beginPath(); - context.arc(radius, radius, radius, 0, 2 * Math.PI); - context.fillStyle = 'black'; - context.fill(); - compareCanvases(stageCanvas, canvas, 100); - }); - - it('toImage with large size', async function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var radius = stage.height() / 2 + 10; - var circle = new Konva.Circle({ - x: stage.height() / 2, - y: stage.height() / 2, - fill: 'black', - radius: radius, - }); - layer.add(circle); - stage.add(layer); - - if (isBrowser) { - try { - const img = await stage.toImage({ - x: -10, - y: -10, - width: stage.height() + 20, - height: stage.height() + 20, - callback: (img) => - assert.isTrue(img instanceof Image, 'not an image'), - }); - assert.isTrue(img instanceof Image, 'not an image'); - } catch (e) { - console.error(e); - assert.fail('error creating image'); - } - } - }); - - it('toBlob with large size', async function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var radius = stage.height() / 2 + 10; - var circle = new Konva.Circle({ - x: stage.height() / 2, - y: stage.height() / 2, - fill: 'black', - radius: radius, - }); - layer.add(circle); - stage.add(layer); - - if (isBrowser) { - try { - const blob = await stage.toBlob({ - x: -10, - y: -10, - width: stage.height() + 20, - height: stage.height() + 20, - callback: (blob) => - assert.isTrue( - blob instanceof Blob && blob.size > 0, - 'blob is empty' - ), - }); - assert.isTrue(blob instanceof Blob && blob.size > 0, 'blob is empty'); - } catch (e) { - console.error(e); - assert.fail('error creating blob'); - } - } - }); - - it('toBlob with mimeType option using', async function () { - const stage = addStage(); - const layer = new Konva.Layer(); - - stage.add(layer); - - if (isBrowser) { - try { - const blob = await stage.toBlob({ - mimeType: 'image/jpeg', - quality: 0.5, - }); - assert.isTrue(blob instanceof Blob && blob.type === 'image/jpeg', "can't change type of blob"); - } catch (e) { - console.error(e); - assert.fail('error creating blob'); - } - } - }); - - it('check hit graph with stage listening property', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - showHit(layer); - var circle = new Konva.Circle({ - fill: 'green', - radius: 50, - }); - layer.add(circle); - - var pos = { - x: stage.width() / 2, - y: stage.height() / 2, - }; - circle.position(pos); - stage.draw(); - - // try to detect circle via hit graph - assert.equal(stage.getIntersection(pos), circle, 'has circle'); - - // disable hit graph - stage.listening(false); - stage.draw(); - assert.equal(!!stage.getIntersection(pos), false, 'no circle'); - - // enable it again - stage.listening(true); - stage.draw(); - assert.equal(stage.getIntersection(pos), circle, 'circle again'); - }); - - it('toDataURL should use pixelRatio 1 by default', function (done) { - var stage = addStage(); - - var url = stage.toDataURL(); - var image = Konva.Util.createImageElement(); - image.onload = function () { - assert.equal(image.width, stage.width()); - assert.equal(image.height, stage.height()); - done(); - }; - image.src = url; - }); - - it('show a warning if the stage has too many layers', function () { - var stage = addStage(); - var oldWarn = Konva.Util.warn; - var called = false; - Konva.Util.warn = function () { - called = true; - }; - - // let say 5 is max number - stage.add(new Konva.Layer()); - stage.add(new Konva.Layer()); - stage.add(new Konva.Layer()); - stage.add(new Konva.Layer()); - stage.add(new Konva.Layer()); - stage.add(new Konva.Layer()); - stage.add(new Konva.Layer()); - - Konva.Util.warn = oldWarn; - assert.equal(called, true); - }); -}); diff --git a/test/unit/Star-test.ts b/test/unit/Star-test.ts deleted file mode 100644 index 52cc3fd9c..000000000 --- a/test/unit/Star-test.ts +++ /dev/null @@ -1,153 +0,0 @@ -import { assert } from 'chai'; - -import { addStage, Konva, cloneAndCompareLayer } from './test-utils'; - -describe('Star', function () { - // ====================================================== - it('add five point star', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - - var star = new Konva.Star({ - x: 200, - y: 100, - numPoints: 5, - innerRadius: 40, - outerRadius: 70, - fill: 'green', - stroke: 'blue', - strokeWidth: 5, - name: 'foobar', - center: { - x: 0, - y: -70, - }, - scale: { - x: 0.5, - y: 0.5, - }, - }); - - layer.add(star); - stage.add(layer); - - assert.equal(star.getClassName(), 'Star'); - }); - - // ====================================================== - it('add star with line join and shadow', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var rect = new Konva.Rect({ - x: 250, - y: 75, - width: 100, - height: 100, - fill: 'red', - }); - - var star = new Konva.Star({ - x: 200, - y: 100, - numPoints: 5, - innerRadius: 40, - outerRadius: 70, - fill: 'green', - stroke: 'blue', - strokeWidth: 5, - lineJoin: 'round', - shadowColor: 'black', - shadowBlur: 10, - shadowOffset: { x: 20, y: 20 }, - shadowOpacity: 0.5, - draggable: true, - }); - - layer.add(rect); - layer.add(star); - - stage.add(layer); - - assert.equal(star.lineJoin(), 'round'); - star.lineJoin('bevel'); - assert.equal(star.lineJoin(), 'bevel'); - - star.lineJoin('round'); - }); - - // ====================================================== - it('attr sync', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var star = new Konva.Star({ - x: 200, - y: 100, - numPoints: 5, - innerRadius: 30, - outerRadius: 50, - fill: 'green', - stroke: 'blue', - strokeWidth: 5, - lineJoin: 'round', - shadowColor: 'black', - shadowBlur: 10, - shadowOffset: { x: 20, y: 20 }, - shadowOpacity: 0.5, - draggable: true, - }); - - layer.add(star); - - stage.add(layer); - - assert.equal(star.getWidth(), 100); - assert.equal(star.getHeight(), 100); - - star.setWidth(120); - assert.equal(star.outerRadius(), 60); - assert.equal(star.getHeight(), 120); - - star.setHeight(140); - assert.equal(star.outerRadius(), 70); - assert.equal(star.getHeight(), 140); - }); - - // ====================================================== - it('star cache', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var star = new Konva.Star({ - x: 200, - y: 100, - numPoints: 5, - innerRadius: 30, - outerRadius: 50, - fill: 'green', - stroke: 'black', - strokeWidth: 5, - lineJoin: 'round', - shadowColor: 'black', - shadowBlur: 10, - shadowOffset: { x: 20, y: 20 }, - shadowOpacity: 0.5, - draggable: true, - }); - - layer.add(star); - - stage.add(layer); - star.cache(); - - assert.deepEqual(star.getSelfRect(), { - x: -50, - y: -50, - height: 100, - width: 100, - }); - cloneAndCompareLayer(layer, 100); - }); -}); diff --git a/test/unit/Text-test.ts b/test/unit/Text-test.ts deleted file mode 100644 index 6f5ae0016..000000000 --- a/test/unit/Text-test.ts +++ /dev/null @@ -1,1761 +0,0 @@ -import { assert } from 'chai'; - -import { - addStage, - Konva, - createCanvas, - compareLayerAndCanvas, - compareLayers, - loadImage, - isBrowser, - isNode, - compareCanvases, -} from './test-utils'; - -describe('Text', function () { - // ====================================================== - it('text with empty config is allowed', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - stage.add(layer); - var text = new Konva.Text(); - - layer.add(text); - layer.draw(); - - var trace = - 'clearRect(0,0,578,200);clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);restore();'; - - assert.equal(layer.getContext().getTrace(), trace); - }); - - it('check text with FALSY values', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - stage.add(layer); - var text = new Konva.Text(); - - layer.add(text); - layer.draw(); - - text.text(0 as any); - assert.equal(text.text(), '0'); - - text.text(true as any); - assert.equal(text.text(), 'true'); - - text.text(false as any); - assert.equal(text.text(), 'false'); - - text.text(undefined); - assert.equal(text.text(), ''); - }); - - // ====================================================== - it('text with undefined text property should not throw an error', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - stage.add(layer); - var text = new Konva.Text({ text: undefined }); - - layer.add(text); - layer.draw(); - - assert.equal(text.getWidth(), 0); - }); - - it('add text with shadows', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var text = new Konva.Text({ - x: 40, - y: 40, - text: 'Hello World!', - fontSize: 50, - fontFamily: 'Arial', - fontStyle: 'normal', - fill: '#888', - stroke: '#333', - align: 'right', - shadowForStrokeEnabled: false, - lineHeight: 1.2, - width: 400, - height: 100, - padding: 10, - shadowColor: 'red', - shadowBlur: 1, - shadowOffset: { x: 10, y: 10 }, - shadowOpacity: 0.2, - }); - - var group = new Konva.Group({ - draggable: true, - }); - - group.add(text); - layer.add(group); - stage.add(layer); - - assert.equal( - layer.getContext().getTrace(false, true), - 'clearRect(0,0,578,200);save();transform(1,0,0,1,40,40);shadowColor=rgba(255,0,0,0.2);shadowBlur=1;shadowOffsetX=10;shadowOffsetY=10;font=normal normal 50px Arial;textBaseline=middle;textAlign=left;translate(10,10);save();fillStyle=#888;fillText(Hello World!,108,30);lineWidth=2;shadowColor=rgba(0,0,0,0);strokeStyle=#333;miterLimit=2;strokeText(Hello World!,108,30);restore();restore();' - ); - - assert.equal(text.getClassName(), 'Text', 'getClassName should be Text'); - }); - - it('text with fill and shadow', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var text = new Konva.Text({ - x: 10, - y: 10, - text: 'Hello World!', - fontSize: 50, - fill: 'black', - shadowColor: 'darkgrey', - shadowOffsetX: 0, - shadowOffsetY: 50, - shadowBlur: 0, - }); - - layer.add(text); - stage.add(layer); - - var canvas = createCanvas(); - var context = canvas.getContext('2d'); - context.textBaseline = 'middle'; - context.font = 'normal normal 50px Arial'; - context.fillStyle = 'darkgrey'; - context.fillText('Hello World!', 10, 10 + 50 + 25); - context.fillStyle = 'black'; - context.fillText('Hello World!', 10, 10 + 25); - - compareLayerAndCanvas(layer, canvas, 254, 200); - }); - - it('check emoji with letterSpacing', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var text = new Konva.Text({ - x: 10, - y: 10, - text: '😬', - fontSize: 50, - letterSpacing: 1, - }); - - layer.add(text); - stage.add(layer); - - var canvas = createCanvas(); - var context = canvas.getContext('2d'); - context.textBaseline = 'middle'; - context.font = 'normal normal 50px Arial'; - context.fillStyle = 'darkgrey'; - context.fillText('😬', 10, 10 + 25); - - compareLayerAndCanvas(layer, canvas, 254); - }); - - it('check hindi with letterSpacing', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var text = new Konva.Text({ - x: 10, - y: 10, - text: 'आपकी दौड़ के लिए परफेक्ट जूते!', - fontSize: 50, - letterSpacing: 10, - }); - - layer.add(text); - stage.add(layer); - - var canvas = createCanvas(); - var context = canvas.getContext('2d'); - context.textBaseline = 'middle'; - context.letterSpacing = '10px'; - context.font = 'normal normal 50px Arial'; - context.fillStyle = 'darkgrey'; - context.fillText('आपकी दौड़ के लिए परफेक्ट जूते!', 10, 10 + 25); - - if (isBrowser) { - compareLayerAndCanvas(layer, canvas, 254, 200); - } - }); - - it('text cache with fill and shadow', function () { - // TODO: on node-canvas it doesn't work - // text scaling is not correct - if (isNode) { - return; - } - var stage = addStage(); - var layer1 = new Konva.Layer(); - layer1.getCanvas().setPixelRatio(1); - stage.add(layer1); - - var text1 = new Konva.Text({ - x: 10, - y: 10, - text: 'some text', - fontSize: 50, - fill: 'black', - shadowColor: 'black', - shadowOffsetX: 0, - shadowOffsetY: 50, - opacity: 1, - shadowBlur: 10, - draggable: true, - }); - layer1.add(text1); - - var layer2 = new Konva.Layer(); - layer2.getCanvas().setPixelRatio(1); - - layer2.add(text1.clone().cache({ pixelRatio: 3 })); - stage.add(layer1, layer2); - - compareLayers(layer1, layer2, 250, 100); - }); - - it('text cache with fill and shadow and some scale', function () { - var stage = addStage(); - var layer1 = new Konva.Layer(); - stage.add(layer1); - - var text1 = new Konva.Text({ - x: 10, - y: 10, - text: 'some text', - fontSize: 50, - fill: 'black', - shadowColor: 'black', - shadowOffsetX: 0, - shadowOffsetY: 50, - opacity: 1, - shadowBlur: 10, - draggable: true, - }); - layer1.add(text1); - - var layer2 = new Konva.Layer({ - scaleX: 0.5, - scaleY: 0.5, - }); - stage.add(layer2); - - var group = new Konva.Group(); - layer2.add(group); - - var text2 = text1.clone(); - group.add(text2); - - text2.cache(); - group.scale({ x: 2, y: 2 }); - - stage.draw(); - - compareLayers(layer1, layer2, 200); - }); - - // ====================================================== - it('add text with letter spacing', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - stage.add(layer); - var text = new Konva.Text({ - text: 'hello', - }); - layer.add(text); - layer.draw(); - - var oldWidth = text.width(); - text.letterSpacing(10); - - assert.equal(text.width(), oldWidth + 40); - layer.draw(); - }); - // ====================================================== - it('text getters and setters', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var text = new Konva.Text({ - x: stage.width() / 2, - y: stage.height() / 2, - text: 'Hello World!', - fontSize: 50, - fontFamily: 'Calibri', - fontStyle: 'normal', - fontVariant: 'normal', - fill: '#888', - stroke: '#333', - align: 'right', - lineHeight: 1.2, - width: 400, - height: 100, - padding: 10, - shadowColor: 'black', - shadowBlur: 1, - shadowOffset: { x: 10, y: 10 }, - shadowOpacity: 0.2, - draggable: true, - }); - - // center text box - text.offsetX(text.getWidth() / 2); - - layer.add(text); - stage.add(layer); - - /* - * test getters and setters - */ - - assert.equal(text.x(), stage.width() / 2); - assert.equal(text.y(), stage.height() / 2); - assert.equal(text.text(), 'Hello World!'); - assert.equal(text.fontSize(), 50); - assert.equal(text.fontFamily(), 'Calibri'); - assert.equal(text.fontStyle(), 'normal'); - assert.equal(text.fontVariant(), 'normal'); - assert.equal(text.fill(), '#888'); - assert.equal(text.stroke(), '#333'); - assert.equal(text.align(), 'right'); - assert.equal(text.lineHeight(), 1.2); - assert.equal(text.getWidth(), 400); - assert.equal(text.height(), 100); - assert.equal(text.padding(), 10); - assert.equal(text.shadowColor(), 'black'); - assert.equal(text.draggable(), true); - assert.equal(text.getWidth(), 400); - assert.equal(text.height(), 100); - assert(text.getTextWidth() > 0, 'text width should be greater than 0'); - assert(text.fontSize() > 0, 'text height should be greater than 0'); - - text.x(1); - text.y(2); - text.text('bye world!'); - text.fontSize(10); - text.fontFamily('Arial'); - text.fontStyle('bold'); - text.fontVariant('small-caps'); - text.fill('green'); - text.stroke('yellow'); - text.align('left'); - text.width(300); - text.height(75); - text.padding(20); - text.shadowColor('green'); - text.setDraggable(false); - - assert.equal(text.x(), 1); - assert.equal(text.y(), 2); - assert.equal(text.text(), 'bye world!'); - assert.equal(text.fontSize(), 10); - assert.equal(text.fontFamily(), 'Arial'); - assert.equal(text.fontStyle(), 'bold'); - assert.equal(text.fontVariant(), 'small-caps'); - assert.equal(text.fill(), 'green'); - assert.equal(text.stroke(), 'yellow'); - assert.equal(text.align(), 'left'); - assert.equal(text.getWidth(), 300); - assert.equal(text.height(), 75); - assert.equal(text.padding(), 20); - assert.equal(text.shadowColor(), 'green'); - assert.equal(text.draggable(), false); - - // test set text to integer - text.text(5 as any); - - //document.body.appendChild(layer.bufferCanvas.element) - - //layer.setListening(false); - layer.drawHit(); - }); - - // ====================================================== - it('reset text auto width', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var text = new Konva.Text({ - text: 'Hello World!', - fontSize: 50, - draggable: true, - width: 10, - }); - - assert.equal(text.width(), 10); - text.setAttr('width', undefined); - assert.equal(text.width() > 100, true); - - layer.add(text); - stage.add(layer); - }); - - // ====================================================== - it('text multi line', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var rect = new Konva.Rect({ - x: 10, - y: 10, - width: 380, - height: 300, - fill: 'red', - }); - - var text = new Konva.Text({ - x: 10, - y: 10, - text: "HEADING\n\nAll the world's a stage, merely players. They have their exits and their entrances; And one man in his time plays many parts.", - //text: 'HEADING\n\nThis is a really cool paragraph. \n And this is a footer.', - fontSize: 14, - fontFamily: 'Calibri', - fontStyle: 'normal', - fill: '#555', - //width: 20, - width: 380, - //width: 200, - padding: 10, - lineHeight: 20, - align: 'center', - draggable: true, - wrap: 'WORD', - }); - - rect.height(text.height()); - // center text box - //text.setOffset(text.getBoxWidth() / 2, text.getBoxHeight() / 2); - - layer.add(rect).add(text); - stage.add(layer); - - assert.equal(text.lineHeight(), 20); - }); - - // ====================================================== - it('text single line with ellipsis', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var rect = new Konva.Rect({ - x: 10, - y: 10, - width: 380, - height: 300, - fill: 'red', - }); - - var text = new Konva.Text({ - x: 10, - y: 10, - text: "HEADING\n\nAll the world's a stage, merely players. They have their exits and their entrances; And one man in his time plays many parts.", - fontSize: 14, - fontFamily: 'Calibri', - fontStyle: 'normal', - fill: '#555', - width: 100, - padding: 0, - lineHeight: 20, - align: 'center', - wrap: 'none', - ellipsis: true, - }); - - layer.add(rect).add(text); - stage.add(layer); - - assert.equal(text.textArr.length, 3); - assert.equal(text.textArr[2].text.slice(-1), '…'); - }); - - // ====================================================== - it('text single line with ellipsis when there is no need in them', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var rect = new Konva.Rect({ - x: 10, - y: 10, - width: 380, - height: 300, - fill: 'red', - }); - - var text = new Konva.Text({ - width: 497, - height: 49, - text: 'Body text', - fill: 'black', - fontSize: 40, - shadowColor: 'black', - shadowOpacity: 1, - lineHeight: 1.2, - letterSpacing: 0, - ellipsis: true, - }); - - layer.add(rect).add(text); - stage.add(layer); - - assert.equal(text.textArr.length, 1); - assert.equal(text.textArr[0].text.slice(-1), 't'); - }); - - // ====================================================== - it('multiline with ellipsis', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var text = new Konva.Text({ - x: 10, - y: 10, - text: "HEADING\n\nAll the world's a stage, merely players. They have theirrrrrrr exits and theirrrrr entrances; And one man in his time plays many parts.", - fontSize: 14, - fontFamily: 'Arial', - fontStyle: 'normal', - width: 100, - padding: 0, - align: 'center', - height: 100, - ellipsis: true, - }); - - layer.add(text); - stage.add(layer); - - assert.equal(text.textArr.length, 7); - assert.equal(text.textArr[6].text.slice(-1), '…'); - - if (isBrowser) { - assert.equal( - layer.getContext().getTrace(false, true), - "clearRect(0,0,578,200);save();transform(1,0,0,1,10,10);font=normal normal 14px Arial;textBaseline=middle;textAlign=left;translate(0,0);save();fillStyle=black;fillText(HEADING,18,7);restore();save();fillStyle=black;fillText(,50,21);restore();save();fillStyle=black;fillText(All the world's,7,35);restore();save();fillStyle=black;fillText(a stage,,25,49);restore();save();fillStyle=black;fillText(merely,28,63);restore();save();fillStyle=black;fillText(players. They,7,77);restore();save();fillStyle=black;fillText(have…,27,91);restore();restore();" - ); - } - }); - - // ====================================================== - it('multiline with ellipsis and lineWidth less than maxWidth', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var text = new Konva.Text({ - x: 10, - y: 10, - text: "HEADING\nAll the\n world's a stage, merely players. They have theirrrrrrr exits and theirrrrr entrances; And one man in his time plays many parts.", - fontSize: 14, - fontFamily: 'Arial', - fontStyle: 'normal', - width: 100, - padding: 0, - align: 'center', - height: 30, - ellipsis: true, - }); - - layer.add(text); - stage.add(layer); - - assert.equal(text.textArr.length, 2); - assert.equal(text.textArr[1].text.slice(-1), '…'); - - if (isBrowser) { - assert.equal( - layer.getContext().getTrace(false, true), - 'clearRect(0,0,578,200);save();transform(1,0,0,1,10,10);font=normal normal 14px Arial;textBaseline=middle;textAlign=left;translate(0,0);save();fillStyle=black;fillText(HEADING,18,7);restore();save();fillStyle=black;fillText(All the…,23,21);restore();restore();' - ); - } - }); - - // ====================================================== - it('make sure we respect false for ellipsis', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var text = new Konva.Text({ - x: 10, - y: 10, - text: 'Hello foo bar', - wrap: 'word', - ellipsis: false, - width: 60, - height: 20, - }); - - layer.add(text); - stage.add(layer); - - assert.equal(text.textArr.length, 1); - assert.equal(text.textArr[0].text, 'Hello foo'); - }); - - // ====================================================== - it('wrap none check', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var text = new Konva.Text({ - x: 10, - y: 10, - text: 'Hello foo bar', - wrap: 'none', - ellipsis: false, - width: 60, - height: 20, - }); - - layer.add( - new Konva.Rect({ - ...text.getClientRect(), - fill: 'rgba(0, 0, 0, 0.4)', - }) - ); - - layer.add(text); - stage.add(layer); - - assert.equal(text.textArr.length, 1); - assert.equal(text.textArr[0].text, 'Hello foo b'); - - var trace = - 'clearRect(0,0,578,200);save();transform(1,0,0,1,10,10);beginPath();rect(0,0,60,20);closePath();fillStyle=rgba(0, 0, 0, 0.4);fill();restore();save();transform(1,0,0,1,10,10);font=normal normal 12px Arial;textBaseline=middle;textAlign=left;translate(0,0);save();fillStyle=black;fillText(Hello foo b,0,6);restore();restore();'; - - assert.equal(layer.getContext().getTrace(), trace); - }); - - // ====================================================== - it('text multi line with justify align', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var rect = new Konva.Rect({ - x: 10, - y: 10, - width: 380, - height: 300, - fill: 'yellow', - }); - - var text = new Konva.Text({ - x: 10, - y: 10, - text: "HEADING\n\n All the world's a stage, merely players. They have their exits and their entrances; And one man in his time plays many parts.", - fontSize: 14, - fontFamily: 'Arial', - fontStyle: 'normal', - fill: '#555', - width: 380, - align: 'justify', - letterSpacing: 5, - draggable: true, - }); - - rect.height(text.height()); - layer.add(rect).add(text); - - stage.add(layer); - - var trace = - 'fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();restore();save();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();restore();restore();'; - - assert.equal(layer.getContext().getTrace(true), trace); - }); - - it('text justify should not justify just one line', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var text = new Konva.Text({ - x: 10, - y: 10, - rotation: 0, - width: 500, - height: 58, - text: 'YOU ARE INVITED!', - fontSize: 30, - align: 'justify', - draggable: true, - }); - - layer.add(text); - - stage.add(layer); - - if (Konva.isBrowser) { - var trace = - 'clearRect(0,0,578,200);save();transform(1,0,0,1,10,10);font=normal normal 30px Arial;textBaseline=middle;textAlign=left;translate(0,0);save();fillStyle=black;fillText(Y,0,15);fillStyle=black;fillText(O,20,15);fillStyle=black;fillText(U,43,15);fillStyle=black;fillText( ,65,15);fillStyle=black;fillText(A,73,15);fillStyle=black;fillText(R,93,15);fillStyle=black;fillText(E,115,15);fillStyle=black;fillText( ,135,15);fillStyle=black;fillText(I,143,15);fillStyle=black;fillText(N,151,15);fillStyle=black;fillText(V,173,15);fillStyle=black;fillText(I,193,15);fillStyle=black;fillText(T,201,15);fillStyle=black;fillText(E,220,15);fillStyle=black;fillText(D,240,15);fillStyle=black;fillText(!,261,15);restore();restore();'; - - assert.equal(layer.getContext().getTrace(false, true), trace); - } - }); - - it('text multi line with justify align and several paragraphs', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var rect = new Konva.Rect({ - x: 10, - y: 10, - width: 380, - height: 300, - fill: 'yellow', - }); - - var text = new Konva.Text({ - x: 10, - y: 10, - text: "HEADING\n\n All the world's a stage, merely players. They have their exits and their entrances;\nAnd one man in his time plays many parts.", - fontSize: 14, - fontFamily: 'Arial', - fontStyle: 'normal', - fill: '#555', - width: 380, - align: 'justify', - letterSpacing: 5, - draggable: true, - }); - - rect.height(text.height()); - layer.add(rect).add(text); - - stage.add(layer); - - var trace = - 'fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();restore();save();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();restore();save();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();restore();restore();'; - - assert.equal(layer.getContext().getTrace(true), trace); - }); - - // ====================================================== - it('text multi line with justify align and decoration', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var rect = new Konva.Rect({ - x: 10, - y: 10, - width: 380, - height: 300, - fill: 'yellow', - }); - - var text = new Konva.Text({ - x: 10, - y: 10, - text: "HEADING\n\n All the world's a stage, merely players. They have their exits and their entrances; And one man in his time plays many parts.", - fontSize: 14, - fontFamily: 'Arial', - fontStyle: 'normal', - fill: '#555', - width: 380, - align: 'justify', - letterSpacing: 5, - textDecoration: 'underline line-through', - padding: 20, - draggable: true, - }); - - rect.height(text.height()); - - layer.add(rect).add(text); - - stage.add(layer); - - if (isNode) { - return; - } - - var trace = - 'fillText(;,106.482,77);fillStyle=#555;fillText( ,117.549,77);fillStyle=#555;fillText(A,126.438,77);fillStyle=#555;fillText(n,140.776,77);fillStyle=#555;fillText(d,153.563,77);fillStyle=#555;fillText( ,168.525,77);fillStyle=#555;fillText(o,177.415,77);fillStyle=#555;fillText(n,190.201,77);fillStyle=#555;fillText(e,202.987,77);fillStyle=#555;fillText( ,217.95,77);fillStyle=#555;fillText(m,226.84,77);fillStyle=#555;fillText(a,243.502,77);fillStyle=#555;fillText(n,256.288,77);fillStyle=#555;fillText( ,271.251,77);fillStyle=#555;fillText(i,280.141,77);fillStyle=#555;fillText(n,288.251,77);fillStyle=#555;fillText( ,303.214,77);fillStyle=#555;fillText(h,312.104,77);fillStyle=#555;fillText(i,324.89,77);fillStyle=#555;fillText(s,333,77);restore();save();save();beginPath();moveTo(0,98);lineTo(245,98);stroke();restore();save();beginPath();moveTo(0,91);lineTo(245,91);stroke();restore();fillStyle=#555;fillText(t,0,91);fillStyle=#555;fillText(i,8.89,91);fillStyle=#555;fillText(m,17,91);fillStyle=#555;fillText(e,33.662,91);fillStyle=#555;fillText( ,46.448,91);fillStyle=#555;fillText(p,55.338,91);fillStyle=#555;fillText(l,68.124,91);fillStyle=#555;fillText(a,76.234,91);fillStyle=#555;fillText(y,89.021,91);fillStyle=#555;fillText(s,101.021,91);fillStyle=#555;fillText( ,113.021,91);fillStyle=#555;fillText(m,121.91,91);fillStyle=#555;fillText(a,138.572,91);fillStyle=#555;fillText(n,151.358,91);fillStyle=#555;fillText(y,164.145,91);fillStyle=#555;fillText( ,176.145,91);fillStyle=#555;fillText(p,185.034,91);fillStyle=#555;fillText(a,197.82,91);fillStyle=#555;fillText(r,210.606,91);fillStyle=#555;fillText(t,220.269,91);fillStyle=#555;fillText(s,229.158,91);fillStyle=#555;fillText(.,241.158,91);restore();restore();'; - - assert.equal(layer.getContext().getTrace(), trace); - }); - - // ====================================================== - it('text multi line with shadows', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var text = new Konva.Text({ - x: 10, - y: 10, - //stroke: '#555', - //strokeWidth: 5, - text: "HEADING\n\nAll the world's a stage, and all the men and women merely players. They have their exits and their entrances; And one man in his time plays many parts.", - //text: 'HEADING\n\nThis is a really cool paragraph. \n And this is a footer.', - fontSize: 16, - fontFamily: 'Calibri', - fontStyle: 'normal', - fill: '#555', - //width: 20, - width: 380, - //width: 200, - padding: 20, - align: 'center', - shadowColor: 'red', - shadowBlur: 1, - shadowOffset: { x: 10, y: 10 }, - shadowOpacity: 0.5, - draggable: true, - }); - - layer.add(text); - stage.add(layer); - - if (isBrowser) { - assert.equal( - layer.getContext().getTrace(false, true), - "clearRect(0,0,578,200);save();transform(1,0,0,1,10,10);shadowColor=rgba(255,0,0,0.5);shadowBlur=1;shadowOffsetX=10;shadowOffsetY=10;font=normal normal 16px Calibri;textBaseline=middle;textAlign=left;translate(20,20);save();fillStyle=#555;fillText(HEADING,133,8);restore();save();fillStyle=#555;fillText(,170,24);restore();save();fillStyle=#555;fillText(All the world's a stage, and all the men and women,6,40);restore();save();fillStyle=#555;fillText(merely players. They have their exits and their,21,56);restore();save();fillStyle=#555;fillText(entrances; And one man in his time plays many,18,72);restore();save();fillStyle=#555;fillText(parts.,152,88);restore();restore();" - ); - } else { - // use relax, because in GitHub Actions calculations are too different - assert.equal( - layer.getContext().getTrace(true, true), - 'clearRect();save();transform();shadowColor;shadowBlur;shadowOffsetX;shadowOffsetY;font;textBaseline;textAlign;translate();save();fillStyle;fillText();restore();save();fillStyle;fillText();restore();save();fillStyle;fillText();restore();save();fillStyle;fillText();restore();save();fillStyle;fillText();restore();save();fillStyle;fillText();restore();restore();' - ); - } - }); - - // ====================================================== - it('text multi line with underline and spacing', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var text = new Konva.Text({ - x: 10, - y: 10, - text: 'hello\nworld', - fontSize: 80, - fill: 'red', - letterSpacing: 5, - textDecoration: 'underline', - draggable: true, - }); - - layer.add(text); - stage.add(layer); - - assert.equal( - layer.getContext().getTrace(true), - 'clearRect();save();transform();font;textBaseline;textAlign;translate();save();save();beginPath();moveTo();lineTo();stroke();restore();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();restore();save();save();beginPath();moveTo();lineTo();stroke();restore();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();restore();restore();' - ); - }); - - // ====================================================== - it('test text with crazy font families', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var text = new Konva.Text({ - text: 'hello', - fontFamily: 'Arial', - }); - layer.add(text); - layer.draw(); - - text.fontFamily('Font Awesome'); - layer.draw(); - text.fontFamily('Font Awesome, Arial'); - layer.draw(); - text.fontFamily('"Font Awesome", Arial'); - layer.draw(); - - var trace = - 'clearRect(0,0,578,200);clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);font=normal normal 12px Arial;textBaseline=middle;textAlign=left;translate(0,0);save();fillStyle=black;fillText(hello,0,6);restore();restore();clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);font=normal normal 12px "Font Awesome";textBaseline=middle;textAlign=left;translate(0,0);save();fillStyle=black;fillText(hello,0,6);restore();restore();clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);font=normal normal 12px "Font Awesome", Arial;textBaseline=middle;textAlign=left;translate(0,0);save();fillStyle=black;fillText(hello,0,6);restore();restore();clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);font=normal normal 12px "Font Awesome", Arial;textBaseline=middle;textAlign=left;translate(0,0);save();fillStyle=black;fillText(hello,0,6);restore();restore();'; - - assert.equal(layer.getContext().getTrace(), trace); - }); - - // ====================================================== - it('text with underline and large line height', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var text = new Konva.Text({ - fontFamily: 'Arial', - text: 'text', - fontSize: 80, - lineHeight: 2, - textDecoration: 'underline', - }); - - layer.add(text); - stage.add(layer); - - var canvas = createCanvas(); - var context = canvas.getContext('2d'); - context.translate(0, 80); - context.lineWidth = 2; - context.font = '80px Arial'; - context.textBaseline = 'middle'; - context.fillText('text', 0, 0); - context.beginPath(); - context.moveTo(0, 40); - context.lineTo(text.width(), 40); - context.lineWidth = 80 / 15; - context.stroke(); - compareLayerAndCanvas(layer, canvas, 50); - }); - - it('text multi line with strike', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var text = new Konva.Text({ - x: 10, - y: 10, - text: 'hello\nworld', - fontSize: 80, - fill: 'red', - textDecoration: 'line-through', - }); - - layer.add(text); - stage.add(layer); - - var trace = - 'clearRect();save();transform();font;textBaseline;textAlign;translate();save();save();beginPath();moveTo();lineTo();stroke();restore();fillStyle;fillText();restore();save();save();beginPath();moveTo();lineTo();stroke();restore();fillStyle;fillText();restore();restore();'; - assert.equal(layer.getContext().getTrace(true), trace); - }); - - it('text multi line with underline and strike', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var text = new Konva.Text({ - x: 10, - y: 10, - text: 'hello\nworld', - fontSize: 80, - fill: 'red', - textDecoration: 'underline line-through', - }); - - layer.add(text); - stage.add(layer); - - var trace = - 'clearRect();save();transform();font;textBaseline;textAlign;translate();save();save();beginPath();moveTo();lineTo();stroke();restore();save();beginPath();moveTo();lineTo();stroke();restore();fillStyle;fillText();restore();save();save();beginPath();moveTo();lineTo();stroke();restore();save();beginPath();moveTo();lineTo();stroke();restore();fillStyle;fillText();restore();restore();'; - - assert.equal(layer.getContext().getTrace(true), trace); - }); - - it('text multi line with underline and strike and gradient', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var text = new Konva.Text({ - x: 10, - y: 10, - text: 'hello\nworld', - fontSize: 80, - // fill: 'red', - fillPriority: 'linear-gradient', - fillLinearGradientStartPoint: { x: 0, y: 0 }, - fillLinearGradientEndPoint: { x: 100, y: 0 }, - fillLinearGradientColorStops: [0, 'red', 1, 'yellow'], - fillAfterStrokeEnabled: true, - textDecoration: 'underline line-through', - }); - - layer.add(text); - stage.add(layer); - - if (isNode) { - return; - } - var trace = - 'clearRect(0,0,578,200);save();transform(1,0,0,1,10,10);font=normal normal 80px Arial;textBaseline=middle;textAlign=left;translate(0,0);save();save();beginPath();moveTo(0,80);lineTo(169,80);stroke();restore();save();beginPath();moveTo(0,40);lineTo(169,40);stroke();restore();fillStyle=[object CanvasGradient];fillText(hello,0,40);restore();save();save();beginPath();moveTo(0,160);lineTo(191,160);stroke();restore();save();beginPath();moveTo(0,120);lineTo(191,120);stroke();restore();fillStyle=[object CanvasGradient];fillText(world,0,120);restore();restore();'; - - assert.equal(layer.getContext().getTrace(), trace); - }); - - it('text multi line with underline and strike and gradient vertical', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var text = new Konva.Text({ - x: 10, - y: 10, - text: 'hello\nworld', - fontSize: 80, - // fill: 'red', - fillPriority: 'linear-gradient', - fillLinearGradientStartPoint: { x: 0, y: 0 }, - fillLinearGradientEndPoint: { x: 0, y: 160 }, - fillLinearGradientColorStops: [0, 'red', 1, 'yellow'], - fillAfterStrokeEnabled: true, - textDecoration: 'underline line-through', - }); - - layer.add(text); - stage.add(layer); - - if (isNode) { - return; - } - - var trace = - 'clearRect(0,0,578,200);save();transform(1,0,0,1,10,10);font=normal normal 80px Arial;textBaseline=middle;textAlign=left;translate(0,0);save();save();beginPath();moveTo(0,80);lineTo(169,80);stroke();restore();save();beginPath();moveTo(0,40);lineTo(169,40);stroke();restore();fillStyle=[object CanvasGradient];fillText(hello,0,40);restore();save();save();beginPath();moveTo(0,160);lineTo(191,160);stroke();restore();save();beginPath();moveTo(0,120);lineTo(191,120);stroke();restore();fillStyle=[object CanvasGradient];fillText(world,0,120);restore();restore();'; - - assert.equal(layer.getContext().getTrace(), trace); - }); - - it('text with underline and shadow', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var text = new Konva.Text({ - text: 'Test', - fill: 'black', - fontSize: 40, - textDecoration: 'underline', - shadowEnabled: true, - shadowColor: 'red', - shadowOffsetX: 15, - shadowOffsetY: 15, - }); - - layer.add(text); - stage.add(layer); - - var trace = - 'clearRect();save();shadowColor;shadowBlur;shadowOffsetX;shadowOffsetY;drawImage();restore();'; - - assert.equal(layer.getContext().getTrace(true), trace); - - // now check result visually - // text with red shadow is the same as red text with back text on top - const group = new Konva.Group({}); - layer.add(group); - group.add(text.clone({ shadowEnabled: false, x: 15, y: 15, fill: 'red' })); - group.add(text.clone({ shadowEnabled: false })); - const groupCanvas = group.toCanvas(); - - compareCanvases(groupCanvas, text.toCanvas(), 200); - }); - - it('text with line-through and shadow', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var text = new Konva.Text({ - text: 'Test', - fill: 'black', - fontSize: 40, - textDecoration: 'line-through', - shadowEnabled: true, - shadowColor: 'red', - shadowOffsetX: 5, - shadowOffsetY: 5, - }); - - layer.add(text); - stage.add(layer); - - var trace = - 'clearRect();save();shadowColor;shadowBlur;shadowOffsetX;shadowOffsetY;drawImage();restore();'; - - assert.equal(layer.getContext().getTrace(true), trace); - - // now check result visually - // text with red shadow is the same as red text with back text on top - const group = new Konva.Group({}); - layer.add(group); - group.add(text.clone({ shadowEnabled: false, x: 5, y: 5, fill: 'red' })); - group.add(text.clone({ shadowEnabled: false })); - const groupCanvas = group.toCanvas(); - - compareCanvases(groupCanvas, text.toCanvas(), 200, 50); - }); - - // ====================================================== - it('change font size should update text data', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var text = new Konva.Text({ - x: 10, - y: 10, - text: 'Some awesome text', - fontSize: 16, - fontFamily: 'Calibri', - fontStyle: 'normal', - fill: '#555', - align: 'center', - padding: 5, - draggable: true, - }); - - var width = text.getWidth(); - var height = text.height(); - - layer.add(text); - stage.add(layer); - - text.fontSize(30); - layer.draw(); - - assert(text.getWidth() > width, 'width should have increased'); - assert(text.height() > height, 'height should have increased'); - }); - - it('text vertical align', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var rect = new Konva.Rect({ - x: 10, - y: 10, - width: 200, - height: 100, - stroke: 'black', - }); - layer.add(rect); - - var text = new Konva.Text({ - x: rect.x(), - y: rect.y(), - width: rect.width(), - height: rect.height(), - text: 'Some awesome text', - fontSize: 16, - fill: '#555', - align: 'center', - padding: 10, - draggable: true, - }); - - assert.equal(text.verticalAlign(), 'top'); - - text.verticalAlign('middle'); - - layer.add(text); - stage.add(layer); - - if (isBrowser) { - assert.equal( - layer.getContext().getTrace(false, true), - 'clearRect(0,0,578,200);save();transform(1,0,0,1,10,10);beginPath();rect(0,0,200,100);closePath();lineWidth=2;strokeStyle=black;stroke();restore();save();transform(1,0,0,1,10,10);font=normal normal 16px Arial;textBaseline=middle;textAlign=left;translate(10,42);save();fillStyle=#555;fillText(Some awesome text,17,8);restore();restore();' - ); - } else { - assert.equal( - layer.getContext().getTrace(false, true), - 'clearRect(0,0,578,200);save();transform(1,0,0,1,10,10);beginPath();rect(0,0,200,100);closePath();lineWidth=2;strokeStyle=black;stroke();restore();save();transform(1,0,0,1,10,10);font=normal normal 16px Arial;textBaseline=middle;textAlign=left;translate(10,42);save();fillStyle=#555;fillText(Some awesome text,17,8);restore();restore();' - ); - } - }); - - it('get text width', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - var text = new Konva.Text({ - text: 'hello asd fasdf asdf asd fasdf asdfasd fa sds helloo', - fill: 'black', - width: 100, - }); - - layer.add(text); - layer.draw(); - assert.equal(text.getTextWidth() > 0 && text.getTextWidth() < 100, true); - }); - - it('get text width of long text with spacing (check it visually!)', function () { - var stage = addStage(); - stage.draggable(true); - var layer = new Konva.Layer(); - stage.add(layer); - - var textProps = { - x: 10, - y: 10, - fontSize: 19, - text: 'very long text, very long text, very long text, very long text, very long text, very long text, very long text, very long text, very long text, very long text, very long text, very long text, very long text, very long text.', - draggable: true, - }; - - var text1 = new Konva.Text(textProps); - layer.add(text1); - var box1 = new Konva.Rect( - Object.assign(text1.getClientRect(), { stroke: 'black' }) - ); - layer.add(box1); - - // demo2 - var text2 = new Konva.Text( - Object.assign(textProps, { letterSpacing: 4, y: 50 }) - ); - layer.add(text2); - var box2 = new Konva.Rect( - Object.assign(text2.getClientRect(), { stroke: 'black' }) - ); - layer.add(box2); - - // demo3 - - var text3 = new Konva.Text( - Object.assign(textProps, { - text: 'gregrg44g4g4g4g4g4g4g4regrg44g4g4g4g4g4g4g4regrg44g4g4g4g4g4g4g4regrg44g4g4g4g4g4g4g4regrg44g4g4g4g4g4g4g4regrg44g4g4g4g4g4g4g4regrg4g4g4', - letterSpacing: 4, - fontSize: 20, - y: 100, - }) - ); - layer.add(text3); - var box3 = new Konva.Rect( - Object.assign(text3.getClientRect(), { stroke: 'black' }) - ); - layer.add(box3); - - // demo4 - var text4 = new Konva.Text( - Object.assign(textProps, { - text: 'gregrg44g4g4g4g4g4g4g4regrg44g4g4g4g4g4g4g4regrg44g4g4g4g4g4g4g4regrg44g4g4g4g4g4g4g4regrg44g4g4g4g4g4g4g4regrg44g4g4g4g4g4g4g4regrg4g4g4', - letterSpacing: 4, - fontSize: 19, - y: 150, - }) - ); - layer.add(text4); - var box4 = new Konva.Rect( - Object.assign(text4.getClientRect(), { stroke: 'black' }) - ); - layer.add(box4); - - layer.draw(); - - // on nodejs the length is very different - // so we need to adjust offset - const diff = isBrowser ? 4 : 50; - assert.equal(Math.abs(Math.round(text1.width()) - 1725) < diff, true); - assert.equal(Math.abs(Math.round(text2.width()) - 2613) < diff, true); - assert.equal(Math.abs(Math.round(text3.width()) - 2005) < diff, true); - assert.equal(Math.abs(Math.round(text4.width()) - 1932) < diff, true); - }); - - it('default text color should be black', function () { - var text = new Konva.Text(); - assert.equal(text.fill(), 'black'); - }); - - it('text with stoke and strokeScaleEnabled', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var text = new Konva.Text({ - fontSize: 50, - y: 50, - x: 50, - fill: 'black', - text: 'text', - stroke: 'red', - strokeScaleEnabled: false, - strokeWidth: 2, - scaleX: 2, - }); - layer.add(text); - stage.add(layer); - - var canvas = createCanvas(); - var context = canvas.getContext('2d'); - context.translate(50, 50); - context.lineWidth = 2; - context.font = '50px Arial'; - context.strokeStyle = 'red'; - context.scale(2, 1); - context.textBaseline = 'middle'; - context.fillText('text', 0, 25); - context.miterLimit = 2; - context.strokeText('text', 0, 25); - compareLayerAndCanvas(layer, canvas); - }); - - it('text getSelfRect', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var text = new Konva.Text({ - fontSize: 50, - y: 50, - x: 50, - fill: 'black', - text: 'text', - }); - - layer.add(text); - stage.add(layer); - - var rect = text.getSelfRect(); - - assert.deepEqual(rect, { - x: 0, - y: 0, - width: text.width(), - height: 50, - }); - }); - - it('linear gradient', function () { - // Konva.pixelRatio = 1; - var stage = addStage(); - var layer = new Konva.Layer(); - - var text = new Konva.Text({ - fontSize: 50, - fillLinearGradientStartPoint: { x: 0, y: 0 }, - fillLinearGradientEndPoint: { x: 300, y: 0 }, - fillLinearGradientColorStops: [0, 'black', 1, 'red'], - text: 'Text with gradient!!', - draggable: true, - }); - layer.add(text); - stage.add(layer); - - var canvas = createCanvas(); - var ctx = canvas.getContext('2d'); - - ctx.fillStyle = 'green'; - ctx.font = 'normal 50px Arial'; - ctx.textBaseline = 'middle'; - - var start = { x: 0, y: 0 }; - var end = { x: 300, y: 0 }; - var colorStops = [0, 'black', 1, 'red']; - var grd = ctx.createLinearGradient(start.x, start.y, end.x, end.y); - - // build color stops - for (var n = 0; n < colorStops.length; n += 2) { - grd.addColorStop(colorStops[n] as number, colorStops[n + 1] as string); - } - ctx.fillStyle = grd; - - ctx.fillText(text.text(), text.x(), text.y() + text.fontSize() / 2); - - compareLayerAndCanvas(layer, canvas, 200); - }); - - it('linear gradient multiline', function () { - const oldRatio = Konva.pixelRatio; - Konva.pixelRatio = 1; - var stage = addStage(); - var layer = new Konva.Layer(); - - var text = new Konva.Text({ - fontSize: 50, - fillLinearGradientStartPoint: { x: 0, y: 0 }, - fillLinearGradientEndPoint: { x: 0, y: 100 }, - fillLinearGradientColorStops: [0, 'yellow', 1, 'red'], - text: 'Text with gradient!!\nText with gradient!!', - draggable: true, - }); - layer.add(text); - stage.add(layer); - - var canvas = createCanvas(); - var ctx = canvas.getContext('2d'); - - ctx.fillStyle = 'green'; - ctx.font = 'normal 50px Arial'; - ctx.textBaseline = 'middle'; - - var start = { x: 0, y: 0 }; - var end = { x: 0, y: 100 }; - var colorStops = [0, 'yellow', 1, 'red']; - var grd = ctx.createLinearGradient(start.x, start.y, end.x, end.y); - - // build color stops - for (var n = 0; n < colorStops.length; n += 2) { - grd.addColorStop(colorStops[n] as number, colorStops[n + 1] as string); - } - ctx.fillStyle = grd; - - ctx.fillText( - 'Text with gradient!!', - text.x(), - text.y() + text.fontSize() / 2 - ); - ctx.fillText( - 'Text with gradient!!', - text.x(), - text.y() + text.fontSize() / 2 + text.fontSize() - ); - - compareLayerAndCanvas(layer, canvas, 200); - - var data = layer.getContext().getImageData(25, 41, 1, 1).data; - Konva.pixelRatio = oldRatio; - }); - - it('radial gradient', function () { - const oldRatio = Konva.pixelRatio; - Konva.pixelRatio = 1; - var stage = addStage(); - var layer = new Konva.Layer(); - - var text = new Konva.Text({ - fontSize: 50, - y: 0, - x: 0, - fillRadialGradientStartPoint: { x: 100, y: 0 }, - fillRadialGradientStartRadius: 0, - fillRadialGradientEndRadius: 100, - fillRadialGradientEndPoint: { x: 100, y: 0 }, - fillRadialGradientColorStops: [0, 'yellow', 1, 'red'], - text: 'Text with gradient!!', - draggable: true, - }); - layer.add(text); - stage.add(layer); - - var canvas = createCanvas(); - var ctx = canvas.getContext('2d'); - - ctx.fillStyle = 'green'; - ctx.font = 'normal 50px Arial'; - ctx.textBaseline = 'middle'; - - var start = { x: 100, y: 0 }; - var end = { x: 100, y: 0 }; - var colorStops = [0, 'yellow', 1, 'red']; - var grd = ctx.createRadialGradient(start.x, start.y, 0, end.x, end.y, 100); - - // build color stops - for (var n = 0; n < colorStops.length; n += 2) { - grd.addColorStop(colorStops[n] as number, colorStops[n + 1] as string); - } - ctx.fillStyle = grd; - - ctx.translate(0, 25); - - ctx.fillText(text.text(), 0, 0); - - Konva.pixelRatio = oldRatio; - - compareLayerAndCanvas(layer, canvas, 100, 30); - }); - - it('text should be centered in line height', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - layer.add( - new Konva.Rect({ - stroke: 'black', - width: 100, - height: 40 * 3, - }) - ); - - var text = new Konva.Text({ - fontSize: 40, - text: 'Some good text', - lineHeight: 3, - draggable: true, - }); - layer.add(text); - stage.add(layer); - - // this text should look like it is positioned in y = 40 - - var trace = - 'clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);beginPath();rect(0,0,100,120);closePath();lineWidth=2;strokeStyle=black;stroke();restore();save();transform(1,0,0,1,0,0);font=normal normal 40px Arial;textBaseline=middle;textAlign=left;translate(0,0);save();fillStyle=black;fillText(Some good text,0,60);restore();restore();'; - - assert.equal(layer.getContext().getTrace(), trace); - }); - - it('check wrapping', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var text = new Konva.Text({ - fontSize: 40, - text: 'Hello, this is some good text', - width: 185, - draggable: true, - }); - layer.add(text); - stage.add(layer); - - var lines = text.textArr; - - // first line should fit "Hello, this" - // I faced this issue in large app - // we should draw as much text in one line, as possible - // so Konva.Text + textarea editing works better - assert.equal(lines[0].text, 'Hello, this'); - }); - - it('check trip when go to new line', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var text = new Konva.Text({ - text: 'Hello, this is some good text', - fontSize: 30, - }); - layer.add(text); - stage.add(layer); - - text.width(245); - - var lines = text.textArr; - - // remove all trimming spaces - // it also looks better in many cases - // it will work as text in div - assert.equal(lines[0].text, 'Hello, this is some'); - assert.equal(lines[1].text, 'good text'); - - text.width(261); - var lines = text.textArr; - - assert.equal(lines[0].text, 'Hello, this is some'); - assert.equal(lines[1].text, 'good text'); - layer.draw(); - }); - - it('image gradient for text', function (done) { - const oldRatio = Konva.pixelRatio; - Konva.pixelRatio = 1; - loadImage('darth-vader.jpg', (imageObj) => { - var stage = addStage(); - var layer = new Konva.Layer(); - - var text = new Konva.Text({ - text: 'Hello, this is some good text', - fontSize: 30, - fillPatternImage: imageObj, - }); - layer.add(text); - stage.add(layer); - - var canvas = createCanvas(); - var ctx = canvas.getContext('2d'); - - ctx.fillStyle = 'green'; - ctx.font = 'normal normal 30px Arial'; - ctx.textBaseline = 'middle'; - - var grd = ctx.createPattern(imageObj, 'repeat'); - ctx.fillStyle = grd; - - ctx.fillText(text.text(), 0, 15); - - compareLayerAndCanvas(layer, canvas, 200); - Konva.pixelRatio = oldRatio; - done(); - }); - }); - - it('image gradient for text with offset', function (done) { - if (isNode) { - // skip in NodeJS because it has not transform API on gradients - return done(); - } - const oldRatio = Konva.pixelRatio; - Konva.pixelRatio = 1; - loadImage('darth-vader.jpg', (imageObj) => { - var stage = addStage(); - var layer = new Konva.Layer(); - - var text = new Konva.Text({ - text: 'Hello, this is some good text', - fontSize: 30, - fillPatternImage: imageObj, - fillPatternOffsetX: 50, - fillPatternRotation: 0, - }); - layer.add(text); - stage.add(layer); - - var canvas = createCanvas(); - var ctx = canvas.getContext('2d'); - - ctx.fillStyle = 'green'; - ctx.font = 'normal normal 30px Arial'; - ctx.textBaseline = 'middle'; - - var grd = ctx.createPattern(imageObj, 'repeat'); - grd.setTransform({ - a: 1, - b: 0, - c: 0, - d: 1, - e: -50, - f: 0, - }); - ctx.fillStyle = grd; - - ctx.fillText(text.text(), 0, 15); - - compareLayerAndCanvas(layer, canvas, 200); - Konva.pixelRatio = oldRatio; - done(); - }); - }); - - it('image gradient for text with scale', function (done) { - const oldRatio = Konva.pixelRatio; - Konva.pixelRatio = 1; - loadImage('darth-vader.jpg', (imageObj) => { - var stage = addStage(); - var layer = new Konva.Layer(); - - var text = new Konva.Text({ - text: 'Hello, this is some good text', - fontSize: 30, - fillPatternImage: imageObj, - fillPatternScaleX: 0.5, - fillPatternScaleY: 0.5, - }); - layer.add(text); - stage.add(layer); - - var canvas = createCanvas(); - var ctx = canvas.getContext('2d'); - - ctx.fillStyle = 'green'; - ctx.font = 'normal normal 30px Arial'; - ctx.textBaseline = 'middle'; - - var grd = ctx.createPattern(imageObj, 'repeat'); - const matrix = - typeof DOMMatrix === 'undefined' - ? { - a: 0.5, // Horizontal scaling. A value of 1 results in no scaling. - b: 0, // Vertical skewing. - c: 0, // Horizontal skewing. - d: 0.5, - e: 0, // Horizontal translation (moving). - f: 0, // Vertical translation (moving). - } - : new DOMMatrix([0.5, 0, 0, 0.5, 0, 0]); - - grd.setTransform(matrix); - - ctx.fillStyle = grd; - - ctx.fillText(text.text(), 0, 15); - - compareLayerAndCanvas(layer, canvas, 200); - Konva.pixelRatio = oldRatio; - done(); - }); - }); - - it('stripe bad stroke', () => { - var stage = addStage(); - var layer = new Konva.Layer(); - - stage.add(layer); - var text = new Konva.Text({ - text: 'HELLO WORLD', - fontFamily: 'Arial', - fontSize: 80, - stroke: 'red', - strokeWidth: 20, - fillAfterStrokeEnabled: true, - draggable: true, - }); - - layer.add(text); - layer.draw(); - - var trace = - 'clearRect(0,0,578,200);clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);font=normal normal 80px Arial;textBaseline=middle;textAlign=left;translate(0,0);save();lineWidth=20;strokeStyle=red;miterLimit=2;strokeText(HELLO WORLD,0,40);fillStyle=black;fillText(HELLO WORLD,0,40);restore();restore();'; - - assert.equal(layer.getContext().getTrace(), trace); - }); - - it('sets ltr text direction', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - stage.add(layer); - var text = new Konva.Text({ - text: 'ltr text', - direction: 'ltr', - }); - - layer.add(text); - layer.draw(); - - var trace = - 'clearRect(0,0,578,200);clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);font=normal normal 12px Arial;textBaseline=middle;textAlign=left;translate(0,0);save();fillStyle=black;fillText(ltr text,0,6);restore();restore();'; - - assert.equal(layer.getContext().getTrace(), trace); - }); - - it('sets rtl text direction', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - stage.add(layer); - var text = new Konva.Text({ - text: 'rtl text', - direction: 'rtl', - }); - - layer.add(text); - layer.draw(); - - var trace = - 'clearRect(0,0,578,200);clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);direction=rtl;font=normal normal 12px Arial;textBaseline=middle;textAlign=left;translate(0,0);save();fillStyle=black;fillText(rtl text,0,6);restore();restore();'; - - assert.equal(layer.getContext().getTrace(), trace); - }); - - it('sets rtl text direction with letterSpacing', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - stage.add(layer); - var text = new Konva.Text({ - text: 'rtl text', - direction: 'rtl', - letterSpacing: 2, - }); - - layer.add(text); - layer.draw(); - - var trace = - 'clearRect(0,0,578,200);clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);direction=rtl;font=normal normal 12px Arial;textBaseline=middle;textAlign=left;translate(0,0);save();letterSpacing=2px;fillStyle=black;fillText(rtl text,0,6);restore();restore();'; - - assert.equal(layer.getContext().getTrace(), trace); - }); - - it('try fixed render', () => { - Konva._fixTextRendering = true; - var stage = addStage(); - var layer = new Konva.Layer(); - - stage.add(layer); - var text = new Konva.Text({ text: 'hello', fontSize: 100 }); - - layer.add(text); - layer.draw(); - Konva._fixTextRendering = false; - - const trace = - 'clearRect(0,0,578,200);clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);font=normal normal 100px Arial;textBaseline=alphabetic;textAlign=left;translate(0,0);save();fillStyle=black;fillText(hello,0,85);restore();restore();'; - - assert.equal(layer.getContext().getTrace(), trace); - }); -}); diff --git a/test/unit/TextPath-test.ts b/test/unit/TextPath-test.ts deleted file mode 100644 index e03a40950..000000000 --- a/test/unit/TextPath-test.ts +++ /dev/null @@ -1,906 +0,0 @@ -import { assert } from 'chai'; - -import { addStage, Konva, cloneAndCompareLayer, isBrowser } from './test-utils'; - -describe('TextPath', function () { - // ====================================================== - it('Render Text Along Line', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var c = 'M 10,10 300,150'; - - var path = new Konva.Path({ - stroke: 'red', - strokeWidth: 1, - data: c, - }); - - layer.add(path); - - var textpath = new Konva.TextPath({ - fill: 'orange', - fontSize: 24, - fontFamily: 'Arial', - text: "The quick brown fox jumped over the lazy dog's back", - data: c, - }); - textpath.on('mouseover', function () { - this.fill('blue'); - layer.drawScene(); - }); - textpath.on('mouseout', function () { - this.fill('orange'); - layer.drawScene(); - }); - - layer.add(textpath); - stage.add(layer); - - assert.equal( - textpath.getClassName(), - 'TextPath', - 'getClassName should be TextPath' - ); - - var trace = layer.getContext().getTrace(true); - //console.log(trace); - assert.equal( - trace, - 'restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();restore();restore();' - ); - }); - - it('Draw more characters then there are path', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var c = 'M 10,10 60,10'; - - var path = new Konva.Path({ - stroke: 'red', - strokeWidth: 1, - data: c, - }); - - layer.add(path); - - var textpath = new Konva.TextPath({ - fill: 'orange', - fontSize: 24, - fontFamily: 'Arial', - text: "The quick brown fox jumped over the lazy dog's back", - data: c, - }); - layer.add(textpath); - - layer.draw(); - - var trace = layer.getContext().getTrace(true); - assert.equal( - trace, - 'clearRect();clearRect();save();transform();beginPath();moveTo();lineTo();lineWidth;strokeStyle;stroke();restore();save();transform();font;textBaseline;textAlign;save();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();restore();restore();' - ); - }); - - // ====================================================== - it('Find Next Segment when Arc is in Path', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var c = 'M10,10 C0,0 10,150 100,100 S300,150 40,130'; - var path = new Konva.Path({ - x: 0, - y: 50, - stroke: 'green', - strokeWidth: 1, - data: c, - }); - - layer.add(path); - - var textpath = new Konva.TextPath({ - x: 0, - y: 50, - fill: '#333', - fontSize: 50, - fontFamily: 'Arial', - text: "All mhe world's a smage, and all mhe men and women merely players.", - data: c, - }); - - layer.add(textpath); - stage.add(layer); - - var trace = layer.getContext().getTrace(); - assert.equal(trace.indexOf('NaN') === -1, true, 'No NaNs'); - }); - - // ====================================================== - it('Check getter and setter', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var c = 'M 50 50 l 250 0'; - var path = new Konva.TextPath({ - text: 'some text', - stroke: 'red', - strokeWidth: 1, - }); - - layer.add(path); - stage.add(layer); - - assert.equal(path.data(), undefined); - path.data(c); - assert.equal(path.data(), c); - - layer.draw(); - }); - - // ====================================================== - it('Render Text Along Vertical Line', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - // Top Down - var c = 'M 50,10 50,150'; - - var path = new Konva.Path({ - stroke: 'red', - strokeWidth: 1, - data: c, - }); - - layer.add(path); - - var textpath = new Konva.TextPath({ - stroke: 'black', - strokeWidth: 1, - fill: 'orange', - fontSize: 18, - fontFamily: 'Arial', - text: "The quick brown fox jumped over the lazy dog's back", - data: c, - }); - - layer.add(textpath); - - // Bottom up - c = 'M 150,150 150,10'; - - path = new Konva.Path({ - stroke: 'red', - strokeWidth: 1, - data: c, - }); - - layer.add(path); - - textpath = new Konva.TextPath({ - stroke: 'black', - strokeWidth: 1, - fill: 'orange', - fontSize: 18, - fontFamily: 'Arial', - text: "The quick brown fox jumped over the lazy dog's back", - data: c, - }); - - layer.add(textpath); - stage.add(layer); - - assert.equal( - layer.getContext().getTrace(true), - 'rotate();fillStyle;fillText();lineWidth;strokeStyle;strokeText();restore();save();translate();rotate();fillStyle;fillText();lineWidth;strokeStyle;strokeText();restore();save();translate();rotate();fillStyle;fillText();lineWidth;strokeStyle;strokeText();restore();save();translate();rotate();fillStyle;fillText();lineWidth;strokeStyle;strokeText();restore();save();translate();rotate();fillStyle;fillText();lineWidth;strokeStyle;strokeText();restore();save();translate();rotate();fillStyle;fillText();lineWidth;strokeStyle;strokeText();restore();save();translate();rotate();fillStyle;fillText();lineWidth;strokeStyle;strokeText();restore();save();translate();rotate();fillStyle;fillText();lineWidth;strokeStyle;strokeText();restore();save();translate();rotate();fillStyle;fillText();lineWidth;strokeStyle;strokeText();restore();save();translate();rotate();fillStyle;fillText();lineWidth;strokeStyle;strokeText();restore();save();translate();rotate();fillStyle;fillText();lineWidth;strokeStyle;strokeText();restore();restore();restore();' - ); - }); - - // ====================================================== - it('Render Text Along two connected Bezier', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var c = 'M10,10 C0,0 10,150 100,100 S300,150 400,50'; - var path = new Konva.Path({ - stroke: 'red', - strokeWidth: 1, - data: c, - }); - - layer.add(path); - - var textpath = new Konva.TextPath({ - stroke: 'black', - strokeWidth: 1, - fill: 'orange', - fontSize: 8, - fontFamily: 'Arial', - text: "All the world's a stage, and all the men and women merely players. They have their exits and their entrances; And one man in his time plays many parts.", - data: c, - }); - - layer.add(textpath); - stage.add(layer); - - assert.equal( - layer.getContext().getTrace(true), - 'rotate();fillStyle;fillText();lineWidth;strokeStyle;strokeText();restore();save();translate();rotate();fillStyle;fillText();lineWidth;strokeStyle;strokeText();restore();save();translate();rotate();fillStyle;fillText();lineWidth;strokeStyle;strokeText();restore();save();translate();rotate();fillStyle;fillText();lineWidth;strokeStyle;strokeText();restore();save();translate();rotate();fillStyle;fillText();lineWidth;strokeStyle;strokeText();restore();save();translate();rotate();fillStyle;fillText();lineWidth;strokeStyle;strokeText();restore();save();translate();rotate();fillStyle;fillText();lineWidth;strokeStyle;strokeText();restore();save();translate();rotate();fillStyle;fillText();lineWidth;strokeStyle;strokeText();restore();save();translate();rotate();fillStyle;fillText();lineWidth;strokeStyle;strokeText();restore();save();translate();rotate();fillStyle;fillText();lineWidth;strokeStyle;strokeText();restore();save();translate();rotate();fillStyle;fillText();lineWidth;strokeStyle;strokeText();restore();restore();restore();' - ); - }); - - // ====================================================== - it('Render Text Along Elliptical Arc', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var c = 'M 250,100 A 100 50 30 1 0 150 150'; - var path = new Konva.Path({ - stroke: 'red', - strokeWidth: 1, - data: c, - }); - - layer.add(path); - - var textpath = new Konva.TextPath({ - fill: 'black', - fontSize: 10, - text: "All the world's a stage, and all the men and women merely players. They have their exits and their entrances; And one man in his time plays many parts.", - data: c, - }); - - layer.add(textpath); - stage.add(layer); - - assert.equal( - layer.getContext().getTrace(true), - 'restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();restore();restore();' - ); - }); - - // ====================================================== - it('Render Text Along complex path', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var c = - 'M 955.92249,-42.126952 L 955.92249,-42.126952 L 955.92249,-42.126952 L 961.93262,212.9279 C 961.72797,213.3372 961.22315,215.2234 960.80572,215.5574 C 957.45077,218.2413 956.9054,218.3026 953.66869,216.6843 C 952.62164,216.1607 951.67338,214.3658 949.91236,214.8061 C 947.3405,215.4491 948.09281,215.8744 946.53166,217.4355 C 945.22315,218.744 943.52659,219.8744 943.52659,222.3188 C 943.52659,225.6087 944.62943,224.7909 946.15603,226.8264 C 947.55965,228.6979 948.18154,229.6696 948.78546,232.0852 C 949.37174,234.4304 951.2918,235.2197 952.16616,236.9685 C 953.11809,238.8723 956.44837,240.9001 955.17122,242.6029 C 955.17122,242.8772 955.27602,243.9657 955.17122,244.1055 C 954.37248,245.1705 952.25782,247.1195 951.79052,248.9887 C 951.25154,251.1447 951.97226,252.3937 951.41489,254.6232 C 950.9178,256.6116 949.53672,257.6472 949.53672,259.8821 C 949.53672,261.2894 949.87203,263.5578 950.66362,265.1409 C 951.32462,266.4629 953.24159,268.3158 953.66869,270.0242 C 954.03114,271.474 954.12634,273.8281 953.66869,275.6587 C 953.20033,277.5321 952.16616,278.7427 952.16616,280.9175 C 952.16616,281.7694 952.66216,286.9313 952.16616,287.3033 C 950.55129,287.3033 950.38215,287.5144 949.16109,288.4302 C 947.74898,289.4893 945.57047,291.4095 944.65349,292.9378 C 943.57061,294.7426 942.86906,296.6011 942.3997,298.9479 C 941.97063,301.0933 941.32659,303.0261 940.1459,304.2068 C 938.60102,305.7517 939.019,307.4128 939.019,309.8413 C 939.019,311.6467 939.44296,314.3005 938.26773,315.4758 C 937.15545,316.5881 934.88703,318.5361 934.88703,320.7346 C 934.88703,322.7058 934.79432,324.8714 935.26267,326.7448 C 935.72373,328.589 935.6383,330.6902 935.6383,332.7549 C 935.6383,334.5937 936.08895,337.1125 935.26267,338.765 C 933.38787,342.5146 935.26267,342.5858 935.26267,345.5264 C 935.61053,346.9179 935.6383,348.2383 935.6383,350.034 C 935.6383,351.5752 934.96036,354.5783 932.63323,353.4147 C 932.09123,353.1437 928.92886,348.8032 927.75,351.1609 C 926.64231,353.3763 926.87972,354.3829 928.12564,356.0442 C 929.10471,357.3496 930.01787,360.3569 928.12564,361.303 C 926.67006,362.0308 924.24963,362.5828 924.74494,365.0593 C 925.21304,367.3998 926.19847,367.8684 926.6231,369.567 C 926.7781,370.1869 927.80544,374.5783 926.24747,375.2014 C 924.2456,376.0022 920.63857,376.64 919.86171,378.5821 C 918.7844,381.2754 918.89909,381.8572 921.36424,383.0897 C 922.93947,383.8774 923.65296,384.6272 925.12057,386.0948 C 925.4026,386.3768 928.41848,391.3951 926.99874,392.1049 C 926.6231,392.2301 926.22599,392.3035 925.87184,392.4806 C 924.02717,393.4029 922.07311,394.7556 920.61297,395.4856 C 918.19436,396.6949 919.66034,398.0609 920.23734,400.3689 C 920.66358,402.0738 920.9143,404.1809 919.48607,405.2521 C 918.27148,406.163 916.40598,407.9567 914.60284,407.5059 C 912.7458,407.0416 911.06841,406.8699 909.71961,407.8815 C 908.08698,409.106 906.39997,410.6424 905.96328,412.3891 C 905.46424,414.3853 903.5041,416.8116 901.83132,417.648 C 900.14443,418.4914 897.73682,419.2163 895.82119,420.6531 C 894.39644,421.7216 891.99114,423.3808 890.93796,424.785 C 889.59804,426.5716 888.40557,428.0687 886.80599,429.6682 C 885.18365,431.2906 883.35936,432.8052 882.29839,434.9271 C 881.56876,436.3864 879.95545,436.9836 879.29333,438.3078 C 878.57656,439.7413 877.73542,441.3406 876.28826,442.0641 C 874.75553,442.8305 873.35007,443.456 871.40503,443.9423 C 867.75936,444.8537 869.30342,446.1864 868.7756,448.8255 C 868.7756,449.4008 868.88599,450.1518 868.7756,450.7037 C 868.4147,452.5082 867.97176,454.46 866.14617,454.46 C 863.87643,454.46 863.13519,452.5202 860.51167,452.9575 C 858.30041,453.326 855.7288,453.4708 853.75028,454.46 C 851.66578,455.5023 850.88183,456.6114 849.24268,457.8407 C 848.34172,458.5165 844.59521,461.2214 842.85692,461.2214 C 841.06194,461.2214 838.75283,461.625 837.59805,460.4702 C 836.02546,458.8976 834.59299,457.0331 834.59299,454.8357 C 834.59299,452.5753 834.44046,450.9268 833.09045,449.5768 C 831.22582,447.7122 830.88608,448.6344 829.33412,450.7037 C 827.57516,453.049 826.50225,455.876 824.07526,457.0895 C 820.97109,458.6416 819.33963,458.3772 818.44076,461.9727 C 817.87317,464.2431 816.93823,466.7246 816.93823,469.1097 C 816.93823,470.8675 817.70296,474.7173 816.93823,476.2468 C 816.14706,477.8291 812.41394,478.9791 810.9281,476.998 C 808.83845,474.2118 810.41749,473.2417 805.66924,473.2417 C 801.6473,473.2417 799.28347,473.0146 799.28347,477.3737 C 799.28347,479.1155 799.58784,484.5107 796.65404,484.5107 C 796.27841,484.5107 795.90277,484.5107 795.52714,484.5107 C 793.06311,484.5107 790.57051,484.2819 789.51701,486.3889 C 789.24153,486.9398 789.17021,490.492 788.39011,491.2721 C 785.76325,493.8989 789.66759,493.7526 790.26828,496.1553 C 790.57092,497.3659 791.29959,501.1341 790.26828,502.1654 C 788.37505,504.0587 788.1443,505.2726 787.63885,507.7999 C 787.12622,510.3631 787.28641,510.4294 784.25815,510.4294 C 779.52049,510.4294 778.62062,512.1783 781.25308,515.6882 C 782.04773,516.7478 784.15693,519.0183 785.76068,519.8202 C 787.2339,520.5568 788.2453,521.5264 787.63885,523.9522 C 787.29534,525.3262 785.38505,526.8783 785.38505,528.8354 C 785.38505,532.3304 785.96541,532.0452 787.63885,533.7186 C 789.35939,535.4392 791.26358,536.4988 790.64391,538.9775 C 790.07532,541.2518 787.846,540.5966 785.38505,540.1044 C 784.8577,539.9989 777.87238,538.1167 777.87238,538.2262 C 777.87238,538.3043 777.87238,541.4667 777.87238,543.1095 C 777.87238,545.7389 776.11001,547.6978 773.74042,549.1196 C 769.72179,551.5308 769.56137,548.92 765.85212,547.9927 C 764.43987,547.6396 762.84706,547.0925 762.84706,544.9876 C 762.84706,542.5025 764.72522,540.5566 764.72522,538.9775 C 764.72522,537.481 764.49962,535.4457 763.97396,533.343 C 763.53464,531.5857 763.96677,529.2128 760.96889,529.9623 C 759.74476,530.2683 755.76059,530.9158 755.3344,529.211 C 754.79258,527.0438 753.83472,525.0819 752.32933,523.5765 C 751.7239,522.9711 748.78535,518.481 747.07047,520.1958 C 745.42956,521.8367 745.1923,522.8794 745.1923,525.4547 C 745.1923,529.5231 743.80555,527.5927 741.43597,529.9623 C 739.21241,532.1859 738.84328,532.0691 738.05527,535.2212 C 737.62578,536.9391 737.33255,538.9489 736.17711,540.1044 C 735.37222,540.9093 731.5352,542.6268 730.91824,543.8607 C 729.89113,545.9149 730.31425,546.7847 731.29388,548.744 C 731.93347,550.0231 732.94949,551.8879 732.42078,554.0028 C 731.86797,556.214 729.92914,558.5699 727.16191,559.2617 C 726.16187,559.7617 724.82639,560.5029 723.78121,560.7642 C 721.91594,561.2305 719.64925,561.351 719.64925,564.1449 C 719.64925,566.832 719.04019,568.7236 721.15178,569.7794 C 722.21289,570.31 724.72561,571.2926 725.28375,572.4088 C 726.18968,574.2207 726.03501,576.214 726.03501,578.419 C 726.03501,580.9518 724.90811,582.9761 724.90811,585.1804 C 724.90811,587.587 724.17206,589.3326 725.28375,590.8149 C 726.38582,592.2843 727.68532,592.9085 728.28881,595.3225 C 728.47077,596.0503 729.29883,599.6882 728.66444,600.957 C 728.20299,601.8799 726.62388,604.7133 724.90811,604.7133 C 722.23081,604.7133 719.55156,603.2108 717.77108,603.2108 C 712.9722,603.2108 711.01958,602.0443 709.88279,606.5915 C 709.52114,608.038 708.85871,610.3121 708.38026,612.2259 C 707.78279,614.6158 706.87772,616.6877 706.87772,619.363 C 706.87772,621.8398 706.7087,624.1711 706.12646,626.5 C 705.78303,627.8737 704.58011,630.6495 702.74576,631.3832 C 700.14612,632.4231 699.90837,632.6269 696.73563,633.2614 C 695.19072,633.5704 692.38471,634.0127 690.34987,634.0127 C 687.92024,634.0127 684.24023,633.3112 682.08594,634.3883 C 680.51621,635.1732 677.63742,637.5327 677.20271,639.2715 C 676.32889,642.7668 669.65019,641.1298 666.68498,639.6472 C 665.51347,639.0614 662.57658,637.112 662.17738,635.5152 C 661.57521,633.1065 663.16351,629.2235 662.17738,627.2513 C 661.26634,625.4292 659.87344,623.4448 658.42105,621.9924 C 657.38134,620.9527 655.38855,620.0777 654.28908,618.6117 C 653.089,617.0116 651.62053,616.0553 650.15712,614.1041 C 648.34003,611.6813 647.12666,612.2259 643.77136,612.2259 C 639.94754,612.2259 634.27092,612.8011 630.99983,610.3478 C 628.83169,608.7217 627.09631,607.7996 625.74097,605.0889 C 624.63961,602.8862 624.51407,601.3082 623.8628,598.7032 C 623.8628,597.1031 624.2465,594.9791 623.8628,593.4443 C 623.39918,591.5898 621.23337,589.3243 621.23337,587.4342 C 621.23337,587.1837 621.29411,586.9258 621.23337,586.6829 C 620.53685,583.8968 622.36027,582.4393 622.36027,580.6728 C 622.36027,578.1478 621.87342,577.1809 620.10647,575.4139 C 619.11396,574.4214 614.71345,572.543 612.96944,574.287 C 611.60526,575.6512 609.17921,577.309 606.95931,578.419 C 604.01326,579.892 598.66588,576.9755 597.19285,579.9215 C 596.40756,581.4921 595.76926,583.6587 595.31468,585.9316 C 594.88705,588.0698 594.09657,589.556 591.55835,590.0636 C 590.26591,590.3221 585.80562,591.0513 585.17259,592.3174 C 584.45323,593.7561 582.33804,595.3917 581.79189,597.5763 C 581.21425,599.8868 580.53762,600.7708 578.78683,602.0839 C 576.60544,603.7199 574.24457,604.0233 571.27416,602.8351 C 569.56134,602.15 566.96195,601.3583 564.51277,601.7082 C 562.15094,602.0456 560.7219,604.7047 559.2539,604.3377 C 556.608,603.6762 556.41629,603.5592 554.74631,601.3326 C 553.7801,600.0443 552.83677,595.5353 551.36561,594.9468 C 549.22437,594.0903 546.63624,594.001 543.85294,593.4443 C 541.39906,592.9535 538.87331,593.0687 536.34028,593.0687 C 532.49916,593.0687 532.93906,592.2969 530.70579,590.0636 C 529.57858,588.9364 527.94151,588.118 525.82255,587.0585 C 523.85495,586.0747 523.02163,585.6928 520.56369,586.3073 C 518.15725,586.9089 517.4765,588.4877 515.68046,588.9367 C 514.53264,589.2237 511.38458,588.643 510.04596,589.3123 C 508.49749,590.0866 507.19267,590.5834 506.66527,592.693 C 506.20828,594.521 505.99947,595.9598 504.7871,597.5763 C 503.10137,599.8239 501.43481,599.4686 499.1526,598.3275 C 496.74377,597.1231 496.63249,597.7484 493.89374,597.2006 C 491.45635,596.7131 490.45647,596.313 488.63487,594.9468 C 486.20245,593.1225 485.84728,591.7342 484.87854,589.3123 C 484.34805,587.9861 483.82138,584.0535 482.24911,584.0535 C 479.1858,584.0535 478.32694,584.2633 476.23898,582.1753 C 475.01433,580.9507 474.104,579.7043 472.85828,578.0433 C 471.87387,576.7308 471.15841,575.0383 468.72632,575.0383 C 465.62648,575.0383 465.0931,574.4101 463.09182,572.4088 C 461.80618,571.1232 459.77548,570.155 457.45733,570.155 C 454.22738,570.155 453.13567,570.2034 450.69593,572.0332 C 449.01793,573.2917 445.74427,574.287 443.5589,574.287 C 441.14907,574.287 438.88122,574.5776 436.7975,573.5357 C 435.27776,572.7759 434.01441,571.5961 432.28991,570.9063 C 429.9965,569.989 427.79078,568.6525 425.15288,568.6525 C 423.40022,568.6525 419.8328,569.7488 418.39148,569.0281 C 418.14106,568.9029 417.89064,568.7777 417.64021,568.6525 C 415.49479,567.5798 416.55622,567.2358 415.38641,564.8962 C 414.77237,563.6681 414.63515,562.1788 414.63515,560.0129 C 414.63515,558.3145 415.04465,556.0165 414.63515,554.3784 C 414.06491,552.0975 414.24886,549.8602 412.38135,547.9927 C 411.40995,547.0213 409.24156,545.0938 408.62502,543.8607 C 408.07318,542.757 407.08617,540.8193 405.99559,539.7288 C 404.23882,537.972 404.86869,537.4962 404.86869,535.2212 C 404.86869,532.3223 402.92378,530.8222 402.23926,528.0841 C 402.03511,527.2676 400.20775,523.9522 399.23419,523.9522 C 397.40724,523.9522 395.17436,524.3278 393.59969,524.3278 C 392.1471,524.3278 388.62445,524.895 387.9652,523.5765 C 387.16017,521.9665 386.46266,520.8647 386.46266,518.3177 C 386.46266,517.2392 387.06995,513.4929 386.46266,512.6832 C 385.44124,511.3213 383.94518,508.9268 382.3307,508.9268 C 380.0442,508.9268 378.68472,509.6505 377.07184,510.0537 C 374.43842,510.7121 375.12089,510.9506 374.06677,513.0588 C 372.99551,515.2013 371.43568,515.6866 369.55917,513.8101 C 367.11608,511.367 367.54854,511.9833 366.17847,513.8101 C 364.4331,516.1372 362.02692,517.942 359.04145,517.942 C 356.27733,517.942 354.79253,517.3325 353.78258,515.3126 C 352.71976,513.187 352.20547,512.3075 349.65062,512.3075 C 347.43943,512.3075 345.67638,511.8115 345.14302,509.6781 C 344.69437,507.8835 343.8574,505.0515 342.51359,504.0436 C 341.49931,503.2829 339.32282,500.99 337.25472,502.5411 C 336.12724,503.3867 330.59067,511.5766 329.49596,511.5766 L 339.92116,9.4291543 L 531.3294,9.5579943 C 531.53498,9.8775343 531.74056,10.197084 531.94614,10.516624 C 532.70213,11.691684 530.89998,12.894794 530.62953,14.247024 C 530.42067,15.291354 532.94855,14.371684 533.70163,15.124764 C 533.96143,15.384574 533.06188,17.795104 533.26276,18.196854 C 533.6241,18.919554 537.09651,16.118584 537.43203,15.783074 C 538.52925,14.685844 541.26067,15.533334 542.2596,15.783074 C 544.36225,16.308734 544.53484,13.969904 545.77057,16.441374 C 546.72008,18.340404 548.8757,18.577754 550.81758,18.855164 C 551.5334,18.957424 552.36959,15.108804 552.7925,14.685894 C 553.70371,13.774684 554.04733,13.026284 554.76742,14.466454 C 555.55609,16.043794 556.96728,16.885754 558.27838,18.196854 C 559.14892,19.067394 560.36843,19.874104 561.35048,20.610644 C 562.42985,21.420174 563.12715,21.998014 564.20314,22.805004 C 565.9662,24.127294 567.78898,25.511804 570.12789,26.096534 C 572.7652,26.755854 576.55367,27.553934 578.90531,28.729754 C 580.9132,29.733704 583.43718,29.459644 585.48837,30.485234 C 586.49144,30.986774 588.94826,31.133324 590.09651,31.362974 C 591.42028,32.024864 591.77294,34.338314 592.07143,35.532254 C 592.3559,36.670124 593.11993,38.320014 593.82691,39.262654 C 594.69143,40.415344 596.17315,41.423224 597.11844,41.895874 C 598.26675,42.470034 600.11464,43.649294 601.28771,44.529104 C 602.4452,45.397214 603.546,45.151114 603.04319,47.162324 C 602.73764,48.384554 601.38101,48.605074 600.62941,49.356674 C 599.50817,50.477904 599.93932,51.519254 600.84884,52.428774 C 601.81016,53.390084 603.26382,53.305314 604.14037,52.428774 C 604.62824,51.940894 608.18038,52.428774 608.96795,52.428774 C 611.1468,52.428774 610.66216,51.127474 612.47891,50.673284 C 612.63759,50.633624 612.77149,50.526994 612.91778,50.453854 C 614.68717,49.569154 616.9206,51.445064 617.9648,49.356674 C 618.52936,48.227544 619.56541,48.220674 619.93972,46.723454 C 620.25133,45.477014 620.37729,44.531694 621.03689,43.212484 C 621.76915,41.747964 621.9135,40.434484 622.79237,39.262654 C 623.77356,37.954414 624.27391,36.972204 625.64503,36.629424 C 627.98413,36.044654 628.95445,36.884634 629.81431,38.604344 C 630.5868,40.149334 629.04661,41.566394 628.05882,42.554184 C 627.03053,43.582464 626.94563,46.049134 627.83939,46.942884 C 628.71859,47.822094 631.7203,46.960114 632.66697,46.723454 C 635.14429,46.104124 638.40825,46.723454 641.00551,46.723454 C 642.99376,46.723454 643.25279,47.744904 644.29704,49.137244 C 645.27121,50.436134 645.05681,51.584644 643.63873,52.648204 C 642.199,53.728004 640.62809,54.372964 639.25003,55.061994 C 637.13418,56.119914 635.43133,55.127564 633.54471,54.184254 C 631.95211,53.387954 630.44161,53.389994 628.71713,53.964814 C 626.84122,54.590124 627.42091,55.720304 625.20616,55.720304 C 623.21044,55.720304 622.67528,55.410144 621.25633,54.842564 C 619.91862,54.307474 619.00883,54.278974 617.9648,55.061994 C 617.10854,55.704184 616.39298,55.720304 614.8927,55.720304 C 613.05499,55.720304 612.78965,55.409564 611.82061,56.378604 C 611.11873,57.080484 611.94664,57.914654 609.40682,57.914654 C 607.90864,57.914654 607.56008,59.135134 606.55416,59.889574 C 605.2063,60.900474 602.08634,60.328444 600.40997,60.328444 C 598.82692,60.328444 597.23216,60.282954 596.02126,60.767314 C 592.93299,62.002624 597.05347,63.219724 597.77675,63.400534 C 599.71594,63.885334 600.39327,64.211484 600.84884,66.033764 C 601.33813,67.990904 602.14535,68.474354 603.48206,66.692064 C 604.91144,64.786234 602.91352,64.497714 606.77359,64.497714 C 607.59464,64.497714 608.63043,67.232284 608.96795,67.569814 C 610.45793,69.059794 611.16665,70.095494 613.13722,71.080774 C 614.46498,71.744654 616.30615,67.595574 616.64819,66.911504 C 617.28296,65.641964 617.99069,64.704204 619.28141,64.058844 C 621.30547,63.046814 622.75619,64.278284 624.76729,64.278284 C 626.50942,64.278284 627.61995,65.003454 627.61995,62.742234 C 627.61995,61.212584 627.63406,61.199134 628.93656,60.547884 C 628.93656,59.039954 631.8995,61.398604 633.10584,62.303364 C 634.22905,63.145774 635.25806,64.560214 636.6168,65.375454 C 638.02819,66.222284 639.45789,65.179164 639.90833,64.278284 C 640.50672,63.081494 642.69629,63.368184 643.63873,63.839414 C 644.9694,64.504744 646.71554,64.500074 648.02744,65.156024 C 649.65658,65.970594 651.25018,66.091894 652.63558,67.130944 C 654.5709,68.582434 655.72441,69.284754 658.12146,69.764164 C 660.76933,70.293734 662.17378,70.473704 664.26565,71.519644 C 666.22906,72.501344 668.08427,73.121854 669.75154,74.372304 C 670.99777,75.306984 673.61008,75.688914 675.23742,75.688914 C 678.09495,75.688914 679.5978,74.715624 682.03992,73.494564 C 683.61178,72.708634 685.09563,72.194334 686.20919,71.080774 C 687.25214,70.037824 688.09533,68.975204 689.28128,67.789244 C 690.81968,66.250844 691.90496,66.472634 694.10886,66.472634 C 695.98476,66.472634 697.61589,67.130944 699.37531,67.130944 C 700.88236,67.130944 702.30921,68.008684 703.98345,68.008684 C 705.78815,68.008684 706.82154,67.443974 708.15272,66.911504 C 709.49084,66.376254 710.32631,65.391024 711.22482,64.717154 C 712.93357,63.435584 713.93405,62.155634 715.83296,61.206184 C 717.44839,60.398474 719.60451,59.255264 721.09941,58.134094 C 722.32027,57.218444 724.55866,55.842944 725.92699,55.500864 C 727.42616,55.126074 729.09302,54.102794 730.53513,53.525944 C 732.4374,52.765044 734.47148,52.545224 736.02101,51.770464 C 736.81463,51.373654 738.38579,51.112164 739.31254,51.112164 C 739.58229,50.977294 739.8977,50.965874 740.19028,50.892724 C 741.93619,50.456234 744.97275,50.145724 746.55391,51.331594 C 747.77567,52.247914 749.08929,52.550364 750.06487,53.525944 C 751.05366,54.514734 751.10636,54.963084 752.6981,55.281434 C 753.97746,55.537304 755.20688,54.403694 756.64793,54.403694 C 757.60799,54.403694 759.65763,56.143574 760.59777,56.378604 C 762.10547,56.755534 763.41059,56.817474 764.98648,56.817474 C 766.46659,56.817474 768.85254,54.943624 770.47236,54.403694 C 772.25575,53.809224 773.23113,53.525944 775.29994,53.525944 C 777.348,53.525944 779.39606,53.525944 781.44413,53.525944 C 783.12504,53.525944 784.01926,53.375894 785.17453,53.087074 C 786.13177,52.847764 786.81429,52.867644 787.80775,52.867644 C 789.68721,52.397784 790.54366,51.799654 792.41589,51.331594 C 793.72507,51.004304 794.52824,48.862394 795.04912,47.820634 C 795.74654,46.425784 796.31421,45.768114 797.24347,44.529104 C 798.0814,43.411864 799.90954,42.318324 801.19331,41.676444 C 802.47959,41.033304 803.007,40.301614 804.04597,39.262654 C 804.9791,38.329524 805.42163,37.448114 806.24032,36.629424 C 807.32555,35.544194 808.33509,33.723304 809.09298,32.460154 C 809.72369,31.408974 811.13754,30.635024 812.16508,29.607494 C 812.75994,29.012634 816.59236,28.500674 817.43152,28.290884 C 818.9728,27.905564 820.03772,26.864014 820.94249,25.657664 C 821.81326,24.496634 822.20664,23.673144 822.47854,22.585564 C 822.70979,21.660554 823.16846,20.484194 823.35628,19.732904 C 823.39176,19.590984 823.35628,19.440324 823.35628,19.294034 C 824.72829,14.181234 833.5556,11.720324 838.16552,9.4153643 C 840.3455,8.3253643 841.62867,5.2222343 843.25846,3.0491743 C 844.34873,1.5954943 847.99376,1.4409443 850.04906,0.92711429 C 853.15105,0.15161429 855.95039,-0.84630571 858.11289,-2.4681757 C 860.2827,-4.0955457 863.83523,-5.3512957 866.17672,-6.2878857 C 868.93603,-7.3916157 871.61677,-9.3068957 873.81614,-10.956426 C 875.97519,-12.575706 878.16034,-13.552932 880.60673,-14.776132 C 882.92916,-15.937342 883.77331,-17.477632 886.5485,-18.171422 C 890.51751,-19.163682 894.57232,-17.476362 898.43204,-19.020252 C 901.2465,-20.146032 904.60721,-21.731172 907.3447,-22.415552 C 909.30842,-22.906482 911.47245,-25.328252 913.28647,-26.235262 C 916.00359,-27.593822 917.08159,-29.412202 919.65265,-30.054972 C 921.32298,-30.472552 924.26602,-31.730552 926.44325,-32.601442 C 928.89479,-33.582062 931.86421,-33.402072 933.65826,-34.299092 C 936.16619,-35.553052 937.08458,-36.322802 939.17561,-36.845562 C 941.67817,-37.471202 944.13749,-38.007702 946.81503,-38.543212 C 948.94134,-38.968472 950.98649,-40.592612 952.33239,-41.938512 C 953.1616,-42.767712 955.07166,-42.233042 955.92249,-42.126952 z '; - - var textpath = new Konva.TextPath({ - y: 50, - fill: 'black', - fontSize: 24, - text: Array(4).join( - "All the world's a stage, and all the men and women merely players. They have their exits and their entrances; And one man in his time plays many parts." - ), - data: c, - }); - - layer.add(textpath); - stage.add(layer); - - assert.equal( - layer.getContext().getTrace(true), - 'restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();restore();restore();' - ); - }); - - // ====================================================== - it('Render Text Along complex path cached', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var c = 'M10,10 C0,0 10,150 100,100 S300,150 400,50'; - - var textpath = new Konva.TextPath({ - stroke: 'black', - strokeWidth: 1, - fill: 'orange', - fontSize: 10, - fontFamily: 'Arial', - text: "All the world's a stage, and all the men and women merely players. They have their exits and their entrances; And one man in his time plays many parts.", - data: c, - draggable: true, - }); - - textpath.cache(); - - layer.add(textpath); - stage.add(layer); - - cloneAndCompareLayer(layer, 200, 10); - }); - - it('Text path with letter spacing', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var c = 'M10,10 C0,0 10,150 100,100 S300,150 400,50'; - - var textpath = new Konva.TextPath({ - stroke: 'black', - strokeWidth: 1, - fill: 'orange', - fontSize: 10, - fontFamily: 'Arial', - letterSpacing: 5, - text: "All the world's a stage, and all the men and women merely players.", - data: c, - }); - - textpath.cache(); - - layer.add(textpath); - stage.add(layer); - cloneAndCompareLayer(layer, 200, 30); - }); - - it('Text path with align', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var c = 'M10,10 300, 10'; - - var textpath = new Konva.TextPath({ - fill: 'black', - fontSize: 10, - fontFamily: 'Arial', - letterSpacing: 5, - text: "All the world's a stage.", - align: 'center', - data: c, - }); - - layer.add(textpath); - stage.add(layer); - - var trace = - 'restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();restore();restore();'; - - assert.equal(layer.getContext().getTrace(true), trace); - }); - - it('Text path with emoji', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var c = 'M10,10 300, 10'; - - var textpath = new Konva.TextPath({ - fill: 'black', - fontSize: 10, - fontFamily: 'Arial', - letterSpacing: 5, - text: '😬', - align: 'center', - data: c, - }); - - layer.add(textpath); - stage.add(layer); - - assert.equal( - layer.getContext().getTrace(true), - 'clearRect();save();transform();font;textBaseline;textAlign;save();save();translate();rotate();fillStyle;fillText();restore();restore();restore();' - ); - }); - - it('Text path with center align - arc', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var textpath = new Konva.TextPath({ - fill: '#333', - fontSize: 20, - text: 'Hello World', - align: 'right', - data: 'M 50 200 a 100 100 0 0 1 200 0', - }); - layer.add(textpath); - - var path = new Konva.Path({ - stroke: '#000', - data: 'M 50 200 a 100 100 0 0 1 200 0', - }); - layer.add(path); - stage.add(layer); - - assert.equal( - layer.getContext().getTrace(true), - 'clearRect();save();transform();font;textBaseline;textAlign;save();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();restore();restore();save();transform();beginPath();moveTo();translate();rotate();scale();arc();scale();rotate();translate();lineWidth;strokeStyle;stroke();restore();' - ); - }); - - it('Text path with align right', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var c = 'M10,10 300, 10'; - - var path = new Konva.Path({ - stroke: 'red', - data: c, - }); - - layer.add(path); - - var textpath = new Konva.TextPath({ - fill: 'black', - fontSize: 10, - fontFamily: 'Arial', - text: "All the world's a stage.", - align: 'right', - data: c, - }); - - layer.add(textpath); - stage.add(layer); - - assert.equal( - layer.getContext().getTrace(true), - 'restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();restore();restore();' - ); - }); - - it('Text path with justify align', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var c = 'M10,10 C0,0 10,150 100,100 S300,150 400,50'; - - var textpath = new Konva.TextPath({ - stroke: 'black', - strokeWidth: 1, - fill: 'orange', - fontSize: 10, - fontFamily: 'Arial', - letterSpacing: 5, - text: 'All the worlds a stage.', - align: 'justify', - data: c, - }); - - layer.add(textpath); - stage.add(layer); - - var trace = - 'rotate();fillStyle;fillText();lineWidth;strokeStyle;strokeText();restore();save();translate();rotate();fillStyle;fillText();lineWidth;strokeStyle;strokeText();restore();save();translate();rotate();fillStyle;fillText();lineWidth;strokeStyle;strokeText();restore();save();translate();rotate();fillStyle;fillText();lineWidth;strokeStyle;strokeText();restore();save();translate();rotate();fillStyle;fillText();lineWidth;strokeStyle;strokeText();restore();save();translate();rotate();fillStyle;fillText();lineWidth;strokeStyle;strokeText();restore();save();translate();rotate();fillStyle;fillText();lineWidth;strokeStyle;strokeText();restore();save();translate();rotate();fillStyle;fillText();lineWidth;strokeStyle;strokeText();restore();save();translate();rotate();fillStyle;fillText();lineWidth;strokeStyle;strokeText();restore();save();translate();rotate();fillStyle;fillText();lineWidth;strokeStyle;strokeText();restore();save();translate();rotate();fillStyle;fillText();lineWidth;strokeStyle;strokeText();restore();restore();restore();'; - - assert.equal(layer.getContext().getTrace(true), trace); - }); - - it('Text path with underline', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var c = 'M10,10 C0,0 10,150 100,100 S300,150 400,50'; - - var textpath = new Konva.TextPath({ - fill: 'orange', - fontSize: 10, - fontFamily: 'Arial', - letterSpacing: 5, - text: 'All the worlds a stage.', - textDecoration: 'underline', - data: c, - draggable: true, - }); - - layer.add(textpath); - stage.add(layer); - - var trace = - 'rotate();fillStyle;fillText();lineTo();restore();save();translate();rotate();fillStyle;fillText();lineTo();restore();save();translate();rotate();fillStyle;fillText();lineTo();restore();save();translate();rotate();fillStyle;fillText();lineTo();restore();save();translate();rotate();fillStyle;fillText();lineTo();restore();save();translate();rotate();fillStyle;fillText();lineTo();restore();save();translate();rotate();fillStyle;fillText();lineTo();restore();save();translate();rotate();fillStyle;fillText();lineTo();restore();save();translate();rotate();fillStyle;fillText();lineTo();restore();save();translate();rotate();fillStyle;fillText();lineTo();restore();save();translate();rotate();fillStyle;fillText();lineTo();restore();save();translate();rotate();fillStyle;fillText();lineTo();restore();save();translate();rotate();fillStyle;fillText();lineTo();restore();save();translate();rotate();fillStyle;fillText();lineTo();restore();stroke();restore();restore();'; - - assert.equal(layer.getContext().getTrace(true), trace); - }); - - it('Text with baseline', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - - var c = 'M 10,10 300,10'; - - var path = new Konva.Path({ - stroke: 'red', - strokeWidth: 1, - data: c, - }); - - layer.add(path); - - var textpath = new Konva.TextPath({ - fill: 'orange', - fontSize: 24, - fontFamily: 'Arial', - text: "The quick brown fox jumped over the lazy dog's back", - data: c, - textBaseline: 'top', - }); - textpath.on('mouseover', function () { - this.fill('blue'); - layer.drawScene(); - }); - textpath.on('mouseout', function () { - this.fill('orange'); - layer.drawScene(); - }); - - layer.add(textpath); - stage.add(layer); - - assert.equal( - layer.getContext().getTrace(true), - 'restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();restore();restore();' - ); - }); - - it('Text with kerning', function () { - var stage = addStage(); - - // simulate lack of kerning support - stage.content && (stage.container().style.fontKerning = 'none'); - - var layer = new Konva.Layer(); - var pairs = { - A: { - V: -0.07421875, - }, - V: { - A: -0.07421875, - }, - }; - - const kernedText = new Konva.TextPath({ - x: 0, - y: 30, - fill: 'black', - text: 'AV', - fontSize: 60, - data: 'M0,0 L200,0', - kerningFunc: function (leftChar, rightChar) { - return pairs.hasOwnProperty(leftChar) - ? pairs[leftChar][rightChar] || 0 - : 0; - }, - }); - - const unkernedText = new Konva.TextPath({ - x: 0, - y: 90, - fill: 'black', - text: 'AV', - fontSize: 60, - data: 'M0,0 L200,0', - }); - - layer.add(kernedText); - layer.add(unkernedText); - stage.add(layer); - - assert( - kernedText.getTextWidth() < unkernedText.getTextWidth(), - 'kerned text lenght must be less then unkerned text length' - ); - }); - - it('Text with invalid kerning getter should not fail (fallback to unkerned)', function () { - var stage = addStage(); - - // simulate lack of kerning support - stage.content && (stage.container().style.fontKerning = 'none'); - - var layer = new Konva.Layer(); - - const kernedText = new Konva.TextPath({ - x: 0, - y: 30, - fill: 'black', - text: 'AV', - fontSize: 60, - data: 'M0,0 L200,0', - kerningFunc: function (leftChar, rightChar) { - // getter that fails - throw new Error('something went wrong'); - }, - }); - - const unkernedText = new Konva.TextPath({ - x: 0, - y: 90, - fill: 'black', - text: 'AV', - fontSize: 60, - data: 'M0,0 L200,0', - }); - - layer.add(kernedText); - layer.add(unkernedText); - stage.add(layer); - - assert.equal( - kernedText.getTextWidth(), - unkernedText.getTextWidth(), - 'should gracefully fallback to unkerned text' - ); - }); - - it('can set kerning after initialization', function () { - var stage = addStage(); - - // simulate lack of kerning support - stage.content && (stage.container().style.fontKerning = 'none'); - - var layer = new Konva.Layer(); - stage.add(layer); - - const kernedText = new Konva.TextPath({ - x: 0, - y: 30, - fill: 'black', - text: 'AV', - fontSize: 60, - data: 'M0,0 L200,0', - }); - layer.add(kernedText); - layer.draw(); - - var called = false; - kernedText.kerningFunc(function () { - called = true; - return 1; - }); - - layer.draw(); - assert.equal(called, true); - }); - - it.skip('linear gradient for path', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - stage.add(layer); - - const text = new Konva.TextPath({ - x: 0, - y: 30, - fontSize: 20, - data: 'M0,0 L200,0', - fillLinearGradientStartPoint: { x: 0, y: 0 }, - fillLinearGradientEndPoint: { x: 200, y: 0 }, - fillLinearGradientColorStops: [0, 'yellow', 1, 'red'], - text: 'Text with gradient!!', - }); - layer.add(text); - layer.draw(); - }); - - it('visual check for text path', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - stage.add(layer); - - layer.add( - new Konva.TextPath({ - fill: '#333', - fontSize: 20, - x: 80, - y: 300, - fontFamily: 'Calibri', - text: 'Hello World', - align: 'center', - textBaseline: 'bottom', - data: 'M -80.34441853748636 -247.27469423673992 A 260 260 0 0 1 80.34441853748628 -247.27469423673995', - }) - ); - - layer.add( - new Konva.TextPath({ - fill: '#333', - fontSize: 20, - x: 80, - y: 350, - fontFamily: 'Calibri', - text: 'Hello World', - align: 'center', - // textBaseline: 'bottom', - data: 'M -80.34441853748636 -247.27469423673992 A 260 260 0 0 1 80.34441853748628 -247.27469423673995', - }) - ); - - layer.add( - new Konva.Text({ - text: 'Hello world', - }) - ); - - layer.add( - new Konva.TextPath({ - fill: '#333', - text: 'Hello world', - y: 20, - data: 'M 0 0 L100 0', - }) - ); - layer.draw(); - - assert.equal( - layer.getContext().getTrace(true), - 'save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();restore();restore();save();transform();font;textBaseline;textAlign;translate();save();fillStyle;fillText();restore();restore();save();transform();font;textBaseline;textAlign;save();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();restore();restore();' - ); - }); - - it('client rect calculations', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - stage.add(layer); - - var textpath = new Konva.TextPath({ - x: 100, - y: 150, - fill: '#333', - fontSize: 16, - fontFamily: 'Arial', - align: 'right', - text: 'test_path', - data: 'M 0,10 L 300 10', - }); - layer.add(textpath); - layer.draw(); - - var rect = textpath.getClientRect(); - - assert.equal(rect.height, 16, 'check height'); - - textpath.text(''); - rect = textpath.getClientRect(); - assert.equal(rect.height, 0, 'check height'); - }); - - it('dashes on line', function () { - var stage = addStage(); - stage.draggable(true); - - var layer = new Konva.Layer(); - stage.add(layer); - - var textpath = new Konva.TextPath({ - fill: '#333', - fontSize: 16, - scaleX: 0.8, - scaleY: 0.8, - text: '_______________', - data: 'M 0,10 L 300 10', - y: 5, - }); - layer.add(textpath); - var path = new Konva.Path({ - stroke: 'red', - scaleX: 0.8, - scaleY: 0.8, - data: 'M 0,10 L 300 10', - }); - layer.add(path); - layer.draw(); - - var rect = textpath.getClientRect(); - - // just different results in different envs - assert.equal(Math.round(rect.height), 13, 'check height'); - }); - - it('check bad calculations', function () { - var stage = addStage(); - stage.draggable(true); - - var layer = new Konva.Layer(); - stage.add(layer); - - var textpath = new Konva.TextPath({ - fill: '#333', - fontSize: 16, - scaleX: 0.8, - scaleY: 0.8, - text: '___________________________________________________________________________________________________________________________________________________________________________________________________________________', - data: 'M 109.98618090452261 138.6656132223618 C 135.94577638190955 48.80547503140701 149.91187876884422 79.79800957914573 151.40954773869348 117.23973382537689 S 123.00811620603017 419.616741991206 122.84170854271358 460.0538041771357 S 134.33883542713568 469.8304329459799 149.98115577889448 464.33898005653265 S 245.4620163316583 411.5856081972362 257.1105527638191 412.91686950376885 S 239.31850251256282 474.434854428392 249.96859296482413 475.76611573492465 S 338.21036306532665 425.67526648869347 348.5276381909548 424.3440051821608 S 337.3640408291457 461.1772344535176 338.5288944723618 464.33898005653265 S 346.8778454773869 466.79295744346734 358.52638190954775 451.4834524183417', - }); - layer.add(textpath); - - layer.draw(); - - var trace = layer.getContext().getTrace(true); - assert.equal( - trace, - 'restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();restore();restore();' - ); - - var path = new Konva.Path({ - stroke: 'red', - scaleX: 0.8, - scaleY: 0.8, - data: 'M 109.98618090452261 138.6656132223618 C 135.94577638190955 48.80547503140701 149.91187876884422 79.79800957914573 151.40954773869348 117.23973382537689 S 123.00811620603017 419.616741991206 122.84170854271358 460.0538041771357 S 134.33883542713568 469.8304329459799 149.98115577889448 464.33898005653265 S 245.4620163316583 411.5856081972362 257.1105527638191 412.91686950376885 S 239.31850251256282 474.434854428392 249.96859296482413 475.76611573492465 S 338.21036306532665 425.67526648869347 348.5276381909548 424.3440051821608 S 337.3640408291457 461.1772344535176 338.5288944723618 464.33898005653265 S 346.8778454773869 466.79295744346734 358.52638190954775 451.4834524183417', - }); - layer.add(path); - layer.draw(); - - var rect = textpath.getClientRect(); - - // just different results in different envs - assert.equal(Math.round(rect.height), 330, 'check height'); - - textpath.text(''); - rect = textpath.getClientRect(); - assert.equal(rect.height, 0, 'check height'); - }); - - it('check bad calculations 2', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - stage.add(layer); - - var textpath = new Konva.TextPath({ - x: 0, - y: 0, - fill: '#333', - fontSize: 10, - // strokeWidth: 100, - stroke: 'black', - scaleX: 0.4, - scaleY: 0.4, - text: '....................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................', - data: 'M 117.12814070351759 108.66938206658291 C 79.18719346733668 277.73956799623113 75.85761180904522 379.96743797110554 82.84673366834171 395.7761659861809 S 148.83130025125627 280.47708118718595 177.12060301507537 244.36661824748745 S 326.1725898241206 61.02036887562815 325.67336683417085 85.815110709799 S 174.998726758794 435.7304316896985 172.8354271356784 457.1970202575377 S 273.65633103015074 310.01551271984926 307.1042713567839 270.07767352386935 S 466.09929459798997 92.08432302135678 459.9422110552764 114.3829499057789 S 266.23512060301505 435.5226006595478 254.2537688442211 461.4821961369347 S 328.1430565326633 368.1639210113065 357.09798994974875 337.2120956344221 S 486.31961118090453 207.61623570979899 502.79396984924625 195.8012916143216 S 511.48859170854274 200.85065719221106 498.50879396984925 235.79626648869348 S 379.73086055276383 489.4401119660804 391.37939698492465 495.76360317211055 S 573.2022663316583 313.03941849874377 598.4962311557789 290.0751609610553 S 608.3285672110553 288.6610529208543 608.4949748743719 298.64551271984925 S 604.9168530150754 352.64801334799 599.9246231155779 375.778678548995 S 540.6820665829146 508.5077162374372 565.643216080402 497.19199513190955 S 690.3761155778894 408.77881799623117 814.1834170854271 278.6480252826633', - }); - var path = new Konva.Path({ - x: 0, - y: 0, - stroke: 'red', - scaleX: 0.4, - scaleY: 0.4, - data: 'M 117.12814070351759 108.66938206658291 C 79.18719346733668 277.73956799623113 75.85761180904522 379.96743797110554 82.84673366834171 395.7761659861809 S 148.83130025125627 280.47708118718595 177.12060301507537 244.36661824748745 S 326.1725898241206 61.02036887562815 325.67336683417085 85.815110709799 S 174.998726758794 435.7304316896985 172.8354271356784 457.1970202575377 S 273.65633103015074 310.01551271984926 307.1042713567839 270.07767352386935 S 466.09929459798997 92.08432302135678 459.9422110552764 114.3829499057789 S 266.23512060301505 435.5226006595478 254.2537688442211 461.4821961369347 S 328.1430565326633 368.1639210113065 357.09798994974875 337.2120956344221 S 486.31961118090453 207.61623570979899 502.79396984924625 195.8012916143216 S 511.48859170854274 200.85065719221106 498.50879396984925 235.79626648869348 S 379.73086055276383 489.4401119660804 391.37939698492465 495.76360317211055 S 573.2022663316583 313.03941849874377 598.4962311557789 290.0751609610553 S 608.3285672110553 288.6610529208543 608.4949748743719 298.64551271984925 S 604.9168530150754 352.64801334799 599.9246231155779 375.778678548995 S 540.6820665829146 508.5077162374372 565.643216080402 497.19199513190955 S 690.3761155778894 408.77881799623117 814.1834170854271 278.6480252826633', - }); - layer.add(path); - // emulate different size function: - // I found the app with custom font - // we calculations were not correct - // so I just coppied text size from that app - textpath._getTextSize = () => { - return { height: 10, width: 5.9399871826171875 }; - }; - layer.add(textpath); - - layer.draw(); - - var rect = textpath.getClientRect(); - assert.equal(Math.round(rect.width), 299); - assert.equal(Math.round(rect.height), 171); - }); - - it.skip('check vertical text path', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - stage.add(layer); - - var textpath = new Konva.TextPath({ - x: -280, - y: -190, - fill: 'black', - fontSize: 10, - fontFamily: 'Arial', - align: 'right', - text: '&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&', - data: 'M 283 383 L 283 187', - }); - layer.add(textpath); - layer.draw(); - - var rect = textpath.getClientRect(); - - assert.equal(rect.height, 200, 'check height'); - }); -}); diff --git a/test/unit/TouchEvents-test.ts b/test/unit/TouchEvents-test.ts deleted file mode 100644 index 5d8fa7701..000000000 --- a/test/unit/TouchEvents-test.ts +++ /dev/null @@ -1,849 +0,0 @@ -import { assert } from 'chai'; - -import { - addStage, - Konva, - simulateTouchStart, - simulateTouchEnd, - simulateTouchMove, -} from './test-utils'; - -describe('TouchEvents', function () { - // ====================================================== - it('touchstart touchend touchmove tap dbltap', function (done) { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - fill: 'red', - stroke: 'black', - strokeWidth: 4, - }); - - // mobile events - var touchstart = false; - var touchend = false; - var tap = false; - var touchmove = false; - var dbltap = false; - - /* - * mobile - */ - circle.on('touchstart', function () { - touchstart = true; - //log('touchstart'); - //alert('touchstart') - }); - - circle.on('touchend', function () { - touchend = true; - //alert('touchend') - //log('touchend'); - }); - - circle.on('touchmove', function () { - touchmove = true; - //log('touchmove'); - }); - - circle.on('tap', function (evt) { - tap = true; - //log('tap'); - }); - - circle.on('dbltap', function () { - dbltap = true; - //log('dbltap'); - }); - - layer.add(circle); - stage.add(layer); - - // reset inDoubleClickWindow - Konva._touchInDblClickWindow = false; - - // touchstart circle - simulateTouchStart(stage, [{ x: 289, y: 100, id: 0 }]); - - assert(touchstart, '8) touchstart should be true'); - assert(!touchmove, '8) touchmove should be false'); - assert(!touchend, '8) touchend should be false'); - assert(!tap, '8) tap should be false'); - assert(!dbltap, '8) dbltap should be false'); - - // touchend circle - simulateTouchEnd(stage, [], [{ x: 289, y: 100, id: 0 }]); - // end drag is tied to document mouseup and touchend event - // which can't be simulated. call _endDrag manually - //Konva.DD._endDrag(); - - assert(touchstart, '9) touchstart should be true'); - assert(!touchmove, '9) touchmove should be false'); - assert(touchend, '9) touchend should be true'); - assert(tap, '9) tap should be true'); - assert(!dbltap, '9) dbltap should be false'); - - // touchstart circle - simulateTouchStart(stage, [{ x: 289, y: 100, id: 0 }]); - - assert(touchstart, '10) touchstart should be true'); - assert(!touchmove, '10) touchmove should be false'); - assert(touchend, '10) touchend should be true'); - assert(tap, '10) tap should be true'); - assert(!dbltap, '10) dbltap should be false'); - - // touchend circle to triger dbltap - simulateTouchEnd(stage, [], [{ x: 289, y: 100, id: 0 }]); - // end drag is tied to document mouseup and touchend event - // which can't be simulated. call _endDrag manually - //Konva.DD._endDrag(); - - assert(touchstart, '11) touchstart should be true'); - assert(!touchmove, '11) touchmove should be false'); - assert(touchend, '11) touchend should be true'); - assert(tap, '11) tap should be true'); - assert(dbltap, '11) dbltap should be true'); - - setTimeout(function () { - // touchmove circle - simulateTouchMove(stage, [], [{ x: 289, y: 100, id: 0 }]); - - assert(touchstart, '12) touchstart should be true'); - assert(touchmove, '12) touchmove should be true'); - assert(touchend, '12) touchend should be true'); - assert(tap, '12) tap should be true'); - assert(dbltap, '12) dbltap should be true'); - - done(); - }, 17); - }); - - it('tap on stage and second tap on shape should not trigger double tap (check after dbltap)', function (done) { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var bigRect = new Konva.Rect({ - x: 50, - y: 50, - width: 200, - height: 200, - fill: 'yellow', - }); - layer.add(bigRect); - - layer.draw(); - - var bigClicks = 0; - var bigDblClicks = 0; - - // make dblclick - simulateTouchStart(stage, { - x: 100, - y: 100, - }); - simulateTouchEnd(stage, { - x: 100, - y: 100, - }); - simulateTouchStart(stage, { - x: 100, - y: 100, - }); - simulateTouchEnd(stage, { - x: 100, - y: 100, - }); - - bigRect.on('tap', function () { - bigClicks += 1; - }); - - bigRect.on('dbltap', function () { - bigDblClicks += 1; - }); - - simulateTouchStart(stage, { - x: 10, - y: 10, - }); - simulateTouchEnd(stage, { - x: 10, - y: 10, - }); - - assert.equal(bigClicks, 0); - assert.equal(bigDblClicks, 0); - - simulateTouchStart(stage, { - x: 100, - y: 100, - }); - simulateTouchEnd(stage, { - x: 100, - y: 100, - }); - - assert.equal(bigClicks, 1); - assert.equal(bigDblClicks, 0); - - done(); - }); - - // test for https://github.com/konvajs/konva/issues/156 - it('touchstart out of shape, then touch end inside shape', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: 100, - y: 100, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - }); - - layer.add(circle); - stage.add(layer); - - var circleTouchend = 0; - - circle.on('touchend', function () { - circleTouchend++; - }); - - simulateTouchStart(stage, [{ x: 1, y: 1, id: 0 }]); - simulateTouchEnd(stage, [], [{ x: 100, y: 100, id: 0 }]); - - assert.equal(circleTouchend, 1); - }); - - it('tap on one shape, then fast tap on another shape should no trigger double tap', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var circle1 = new Konva.Circle({ - x: 100, - y: 100, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - }); - - layer.add(circle1); - - var circle2 = new Konva.Circle({ - x: 200, - y: 100, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - }); - - layer.add(circle2); - - layer.draw(); - - var circle1Tap = 0; - var circle2Tap = 0; - var circle2DoubleTap = 0; - - circle1.on('tap', function () { - circle1Tap++; - }); - circle2.on('tap', function () { - circle2Tap++; - }); - circle2.on('dbltap', function () { - circle2DoubleTap++; - }); - - simulateTouchStart(stage, { x: 100, y: 100 }); - simulateTouchEnd(stage, { x: 100, y: 100 }); - - assert.equal(circle1Tap, 1, 'should trigger tap on first circle'); - assert.equal(circle2Tap, 0, 'should NOT trigger tap on second circle'); - assert.equal( - circle2DoubleTap, - 0, - 'should NOT trigger dbltap on second circle' - ); - - simulateTouchStart(stage, { x: 200, y: 100 }); - simulateTouchEnd(stage, { x: 200, y: 100 }); - - assert.equal(circle1Tap, 1, 'should trigger tap on first circle'); - assert.equal(circle2Tap, 1, 'should trigger tap on second circle'); - assert.equal( - circle2DoubleTap, - 0, - 'should NOT trigger dbltap on second circle' - ); - }); - - it('multitouch - register all touches', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var circle1 = new Konva.Circle({ - x: 100, - y: 100, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle1', - draggable: true, - }); - layer.add(circle1); - - var circle2 = new Konva.Circle({ - x: 100, - y: 200, - radius: 80, - fill: 'red', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle2', - draggable: true, - }); - - layer.add(circle2); - layer.draw(); - - var touchStart = 0; - var touchMove = 0; - var touchEnd = 0; - var touchEnd2 = 0; - - circle1.on('touchstart', function () { - touchStart++; - }); - circle1.on('touchmove', function () { - touchMove++; - }); - circle1.on('touchend', function () { - touchEnd++; - }); - - circle2.on('touchend', function () { - touchEnd2++; - }); - - var stageTouchStart = 0; - var stageTouchMove = 0; - var stageTouchEnd = 0; - var stageTap = 0; - var stageEventStack: string[] = []; - stage.on('touchstart', function () { - stageTouchStart++; - stageEventStack.push('touchstart'); - }); - stage.on('touchmove', function () { - stageTouchMove++; - }); - stage.on('touchend', function () { - stageTouchEnd++; - stageEventStack.push('touchend'); - }); - stage.on('tap', function () { - stageTap++; - stageEventStack.push('tap'); - }); - - // start with one touch - simulateTouchStart( - stage, - [{ x: 100, y: 100, id: 0 }], - [{ x: 100, y: 100, id: 0 }] - ); - - assert.equal(stageTouchStart, 1, 'trigger first touch start on stage'); - assert.equal(touchStart, 1, 'trigger first touch start on circle'); - - // make second touch - simulateTouchStart( - stage, - [ - { x: 100, y: 100, id: 0 }, - { x: 210, y: 100, id: 1 }, - ], - [{ x: 210, y: 100, id: 1 }] - ); - - assert.equal( - stageTouchStart, - 2, - 'should trigger the second touch on stage' - ); - assert.equal( - touchStart, - 1, - 'should not trigger the second touch start (it is outside)' - ); - - // now try to make two touches at the same time - simulateTouchStart( - stage, - [ - { x: 100, y: 100, id: 0 }, - { x: 210, y: 100, id: 1 }, - ], - [ - { x: 100, y: 100, id: 0 }, - { x: 210, y: 100, id: 1 }, - ] - ); - - assert.equal(stageTouchStart, 3, 'should trigger one more touch'); - assert.equal( - touchStart, - 2, - 'should trigger the second touch start on the circle' - ); - - // check variables - assert.deepEqual(stage.getPointerPosition(), { x: 100, y: 100 }); - assert.deepEqual(stage.getPointersPositions(), [ - { x: 100, y: 100, id: 0 }, - { x: 210, y: 100, id: 1 }, - ]); - - // move one finger - simulateTouchMove( - stage, - [ - { x: 100, y: 100, id: 0 }, - { x: 220, y: 100, id: 1 }, - ], - [{ x: 220, y: 100, id: 1 }] - ); - assert.equal(touchMove, 0, 'should not trigger touch move on circle'); - assert.equal(stageTouchMove, 1, 'should trigger touch move on stage'); - - // move two fingers - simulateTouchMove( - stage, - [ - { x: 100, y: 100, id: 0 }, - { x: 220, y: 100, id: 1 }, - ], - [ - { x: 100, y: 100, id: 0 }, - { x: 220, y: 100, id: 1 }, - ] - ); - assert.equal(touchMove, 1, 'should trigger touch move on circle'); - assert.equal( - stageTouchMove, - 2, - 'should trigger two more touchmoves on stage' - ); - - simulateTouchEnd( - stage, - [], - [ - { x: 100, y: 100, id: 0 }, - { x: 220, y: 100, id: 1 }, - ] - ); - assert.equal(touchEnd, 1); - assert.equal(stageTouchEnd, 1); - assert.equal(stageTap, 1, 'one tap should be fired'); - - assert.equal( - stageEventStack.join(' '), - 'touchstart touchstart touchstart touchend tap', - 'should fire tap after touchend' - ); - - // try two touch ends on both shapes - simulateTouchEnd( - stage, - [], - [ - { x: 100, y: 100, id: 0 }, - { x: 100, y: 170, id: 1 }, - ] - ); - - assert.equal(touchEnd, 2); - assert.equal(touchEnd2, 1); - assert.equal(stageTouchEnd, 3); - assert.equal(stageTap, 1, 'still one tap should be fired'); - // Don't need to check event stack here, the pointers moved so no tap is fired - }); - - it.skip('letting go of two fingers quickly should not fire dbltap', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var touchend = 0; - var dbltap = 0; - - stage.on('dbltap', function (e) { - dbltap += 1; - }); - - stage.on('touchend', function (e) { - touchend += 1; - }); - - simulateTouchStart( - stage, - [{ x: 100, y: 100, id: 0 }], - [{ x: 100, y: 100, id: 0 }] - ); - - simulateTouchStart( - stage, - [{ x: 110, y: 110, id: 1 }], - [{ x: 110, y: 110, id: 1 }] - ); - - assert.equal( - touchend, - 0, - '1) no touchend triggered after holding down two fingers' - ); - assert.equal( - dbltap, - 0, - '1) no dbltap triggered after holding down two fingers' - ); - - simulateTouchEnd( - stage, - [{ x: 110, y: 110, id: 1 }], - [{ x: 100, y: 100, id: 0 }] - ); - simulateTouchEnd(stage, [], [{ x: 110, y: 110, id: 1 }]); - - assert.equal( - touchend, - 2, - '2) touchend triggered twice after letting go two fingers' - ); - assert.equal( - dbltap, - 0, - '2) no dbltap triggered after letting go two fingers' - ); - }); - - it('can capture touch events', function () { - Konva.capturePointerEventsEnabled = true; - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var circle1 = new Konva.Circle({ - x: 100, - y: 100, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle1', - }); - layer.add(circle1); - - layer.draw(); - - var touchStart = 0; - var touchMove = 0; - var touchEnd = 0; - - circle1.on('touchstart', function (e) { - touchStart++; - }); - circle1.on('touchmove', function () { - touchMove++; - }); - circle1.on('touchend', function () { - touchEnd++; - }); - - simulateTouchStart( - stage, - [{ x: 100, y: 100, id: 0 }], - [{ x: 100, y: 100, id: 0 }] - ); - - // go out of circle - simulateTouchMove( - stage, - [{ x: 180, y: 100, id: 0 }], - [{ x: 180, y: 100, id: 0 }] - ); - assert.equal(touchMove, 1, 'first touchmove'); - - // add another finger - simulateTouchStart( - stage, - [ - { x: 180, y: 100, id: 0 }, - { x: 100, y: 100, id: 1 }, - ], - [{ x: 100, y: 100, id: 1 }] - ); - - // move all out - simulateTouchMove( - stage, - [ - { x: 185, y: 100, id: 0 }, - { x: 190, y: 100, id: 1 }, - ], - [ - { x: 185, y: 100, id: 0 }, - { x: 190, y: 100, id: 1 }, - ] - ); - // should trigger just one more touchmove - assert.equal(touchMove, 2, 'second touchmove'); - - // remove fingers - simulateTouchEnd( - stage, - [], - [ - { x: 185, y: 100, id: 0 }, - { x: 190, y: 100, id: 1 }, - ] - ); - - assert.equal(touchEnd, 1, 'first touchend'); - - // should release captures on touchend - assert.equal(circle1.hasPointerCapture(0), false); - assert.equal(circle1.hasPointerCapture(1), false); - - Konva.capturePointerEventsEnabled = false; - }); - - it('tap and double tap should trigger just once on stage', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var circle1 = new Konva.Circle({ - x: 100, - y: 100, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle1', - }); - layer.add(circle1); - layer.draw(); - - var tap = 0; - var dbltap = 0; - - stage.on('tap', function (e) { - assert.equal(e.target, circle1); - tap += 1; - }); - - stage.on('dbltap', function (e) { - assert.equal(e.target, circle1); - dbltap += 1; - }); - - simulateTouchStart( - stage, - [{ x: 100, y: 100, id: 0 }], - [{ x: 100, y: 100, id: 0 }] - ); - - simulateTouchEnd(stage, [], [{ x: 100, y: 100, id: 0 }]); - - assert.equal(tap, 1, 'tap triggered'); - assert.equal(dbltap, 0, 'no dbltap triggered'); - - simulateTouchStart( - stage, - [{ x: 100, y: 100, id: 0 }], - [{ x: 100, y: 100, id: 0 }] - ); - - simulateTouchEnd(stage, [], [{ x: 100, y: 100, id: 0 }]); - assert.equal(tap, 2, 'tap triggered'); - assert.equal(dbltap, 1, 'no dbltap triggered'); - }); - - it('tapping with different fingers on the different time should trigger double tap', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var circle1 = new Konva.Circle({ - x: 100, - y: 100, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle1', - }); - layer.add(circle1); - layer.draw(); - - var tap = 0; - var dbltap = 0; - - stage.on('tap', function (e) { - assert.equal(e.target, circle1); - tap += 1; - }); - - stage.on('dbltap', function (e) { - assert.equal(e.target, circle1); - dbltap += 1; - }); - - simulateTouchStart( - stage, - [{ x: 100, y: 100, id: 0 }], - [{ x: 100, y: 100, id: 0 }] - ); - - simulateTouchEnd(stage, [], [{ x: 100, y: 100, id: 0 }]); - - assert.equal(tap, 1, 'tap triggered'); - assert.equal(dbltap, 0, 'no dbltap triggered'); - - simulateTouchStart( - stage, - [{ x: 100, y: 100, id: 1 }], - [{ x: 100, y: 100, id: 1 }] - ); - - simulateTouchEnd(stage, [], [{ x: 100, y: 100, id: 1 }]); - assert.equal(tap, 2, 'tap triggered'); - assert.equal(dbltap, 1, 'dbltap triggered'); - }); - - it('drag and second tap should not trigger dbltap', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var circle1 = new Konva.Circle({ - x: 100, - y: 100, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle1', - draggable: true, - }); - layer.add(circle1); - layer.draw(); - - var tap = 0; - var dbltap = 0; - var dragmove = 0; - - stage.on('tap', function (e) { - assert.equal(e.target, circle1); - tap += 1; - }); - - stage.on('dbltap', function (e) { - dbltap += 1; - }); - - stage.on('dragmove', function (e) { - dragmove += 1; - }); - - simulateTouchStart( - stage, - [{ x: 100, y: 100, id: 0 }], - [{ x: 100, y: 100, id: 0 }] - ); - - simulateTouchMove( - stage, - [{ x: 150, y: 150, id: 0 }], - [{ x: 150, y: 150, id: 0 }] - ); - - simulateTouchEnd(stage, [], [{ x: 150, y: 150, id: 0 }]); - - assert.equal(tap, 0, 'no tap triggered'); - assert.equal(dbltap, 0, 'no dbltap triggered'); - assert.equal(dragmove, 1, 'dragmove triggered'); - - simulateTouchStart( - stage, - [{ x: 150, y: 150, id: 0 }], - [{ x: 150, y: 150, id: 0 }] - ); - - simulateTouchEnd(stage, [], [{ x: 150, y: 150, id: 0 }]); - - assert.equal(tap, 1, 'tap triggered'); - assert.equal(dbltap, 0, 'no dbltap triggered'); - }); - - it('tap should give pointer position', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var circle1 = new Konva.Circle({ - x: 100, - y: 100, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle1', - draggable: true, - }); - layer.add(circle1); - layer.draw(); - - var tap = 0; - var click = 0; - - stage.on('tap', function (e) { - assert.equal(e.target, circle1); - assert.equal(stage.getPointerPosition().x, 100); - assert.equal(stage.getPointerPosition().y, 100); - tap += 1; - }); - - stage.on('click', function (e) { - click += 1; - }); - - simulateTouchStart( - stage, - [{ x: 100, y: 100, id: 0 }], - [{ x: 100, y: 100, id: 0 }] - ); - - simulateTouchEnd(stage, [], [{ x: 100, y: 100, id: 0 }]); - - assert.equal(tap, 1, 'tap triggered'); - assert.equal(click, 0, 'no click triggered'); - }); -}); diff --git a/test/unit/Transformer-test.ts b/test/unit/Transformer-test.ts deleted file mode 100644 index af6782df1..000000000 --- a/test/unit/Transformer-test.ts +++ /dev/null @@ -1,5091 +0,0 @@ -import { assert } from 'chai'; -import { Transformer } from '../../src/shapes/Transformer'; - -import { - addStage, - isNode, - Konva, - simulateMouseDown as sd, - simulateMouseMove as sm, - simulateMouseUp as su, - assertAlmostEqual, -} from './test-utils'; - -function simulateMouseDown(tr, pos) { - sd(tr.getStage(), pos); -} - -function simulateMouseMove(tr, pos) { - const stage = tr.getStage(); - var top = (stage.content && stage.content.getBoundingClientRect().top) || 0; - tr._handleMouseMove({ - ...pos, - clientX: pos.x, - clientY: pos.y + top, - }); - sm(stage, pos); -} - -function simulateMouseUp(tr: Transformer, pos = { x: 0, y: 0 }) { - const stage = tr.getStage(); - var top = (stage.content && stage.content.getBoundingClientRect().top) || 0; - tr._handleMouseUp({ - clientX: pos.x, - clientY: pos.y + top, - }); - su(tr.getStage(), pos || { x: 1, y: 1 }); -} - -describe('Transformer', function () { - // ====================================================== - it('init transformer on simple rectangle', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - x: 100, - y: 60, - draggable: true, - width: 100, - height: 100, - fill: 'yellow', - }); - layer.add(rect); - - var tr = new Konva.Transformer({ - nodes: [rect], - }); - layer.add(tr); - - layer.draw(); - assert.equal(tr.getClassName(), 'Transformer'); - - assert.equal(tr.x(), rect.x()); - assert.equal(tr.y(), rect.y()); - assert.equal(tr.width(), rect.width()); - assert.equal(tr.height(), rect.height()); - - // manual check of correct position of node - var handler = tr.findOne('.bottom-right'); - var pos = handler.getAbsolutePosition(); - assert.equal(pos.x, rect.x() + rect.width()); - assert.equal(pos.y, rect.y() + rect.height()); - }); - - it('can attach transformer into several nodes', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect1 = new Konva.Rect({ - x: 10, - y: 10, - draggable: true, - width: 100, - height: 50, - fill: 'yellow', - }); - layer.add(rect1); - - var rect2 = new Konva.Rect({ - x: 110, - y: 60, - draggable: true, - width: 100, - height: 50, - fill: 'red', - }); - - layer.add(rect2); - - var tr = new Konva.Transformer({ - nodes: [rect1, rect2], - }); - layer.add(tr); - - layer.draw(); - assert.equal(tr.x(), rect1.x()); - assert.equal(tr.y(), rect1.y()); - assert.equal(tr.width(), rect1.width() + rect2.width()); - assert.equal(tr.height(), rect1.height() + rect2.height()); - assert.equal(tr.rotation(), 0); - }); - - it('try set/get node', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - x: 100, - y: 60, - draggable: true, - width: 100, - height: 100, - fill: 'yellow', - }); - layer.add(rect); - - var circle = new Konva.Circle({ - x: 10, - y: 60, - radius: 100, - fill: 'red', - }); - layer.add(circle); - - var tr = new Konva.Transformer({ - nodes: [rect], - }); - layer.add(tr); - - layer.draw(); - assert.equal(tr.nodes()[0], rect); - - tr.nodes([circle]); - assert.equal(tr.nodes()[0], circle); - layer.draw(); - }); - - it('try to fit simple rectangle', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - x: 100, - y: 60, - draggable: true, - width: 100, - height: 100, - fill: 'yellow', - }); - layer.add(rect); - - var tr = new Konva.Transformer(); - layer.add(tr); - tr.nodes([rect]); - - layer.draw(); - - tr._fitNodesInto({ - x: 120, - y: 60, - width: 50, - height: 50, - rotation: Konva.getAngle(45), - }); - - assert.equal(tr.x(), rect.x()); - assert.equal(Math.round(tr.y()), rect.y()); - assert.equal(tr.width(), 50); - assert.equal(tr.height(), 50); - assert.equal(tr.rotation(), rect.rotation()); - }); - - it('try to fit simple rotated rectangle', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - x: 50, - y: 50, - draggable: true, - width: 100, - height: 150, - fill: 'yellow', - rotation: 45, - }); - layer.add(rect); - - var tr = new Konva.Transformer(); - layer.add(tr); - tr.nodes([rect]); - - layer.draw(); - - tr._fitNodesInto({ - x: 50, - y: 50, - width: 100, - height: 150, - rotation: Konva.getAngle(45), - }); - - assertAlmostEqual(rect.x(), 50); - assertAlmostEqual(rect.y(), 50); - assertAlmostEqual(tr.width(), 100); - assertAlmostEqual(tr.height(), 150); - assertAlmostEqual(tr.rotation(), rect.rotation()); - }); - - it('transformer should follow rotation on single node', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - x: 100, - y: 60, - draggable: true, - width: 100, - height: 100, - fill: 'yellow', - }); - layer.add(rect); - - var tr = new Konva.Transformer({ - nodes: [rect], - }); - layer.add(tr); - - layer.draw(); - - rect.rotation(45); - layer.draw(); - - assert.equal(tr.rotation(), 45); - }); - - it('try to fit simple rotated rectangle in group', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var group = new Konva.Group({ - rotation: 45, - x: 50, - y: 50, - }); - layer.add(group); - - var rect = new Konva.Rect({ - draggable: true, - width: 100, - height: 150, - fill: 'yellow', - }); - group.add(rect); - - var tr = new Konva.Transformer(); - layer.add(tr); - tr.nodes([rect]); - - layer.draw(); - - tr._fitNodesInto({ - x: 50, - y: 50, - width: 100, - height: 150, - rotation: 0, - }); - - assertAlmostEqual(rect.x(), 0); - assertAlmostEqual(rect.y(), 0); - assertAlmostEqual(tr.width(), 100); - assertAlmostEqual(tr.height(), 150); - assertAlmostEqual(rect.rotation(), -45); - }); - - it('transformer should follow rotation on single node inside group', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var group = new Konva.Group({ - rotation: 45, - }); - layer.add(group); - - var rect = new Konva.Rect({ - x: 100, - y: 60, - draggable: true, - width: 100, - height: 100, - fill: 'yellow', - }); - group.add(rect); - - var tr = new Konva.Transformer({ - nodes: [rect], - }); - group.add(tr); - - layer.draw(); - - rect.rotation(45); - layer.draw(); - - assertAlmostEqual(tr.rotation(), 90); - }); - - it('try to fit simple rotated rectangle - 2', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - x: 50, - y: 50, - draggable: true, - width: 100, - height: 200, - fill: 'yellow', - rotation: 45, - }); - layer.add(rect); - - var tr = new Konva.Transformer(); - layer.add(tr); - tr.nodes([rect]); - - layer.draw(); - - tr._fitNodesInto({ - x: 40, - y: 40, - width: 100, - height: 100, - rotation: 0, - }); - - assertAlmostEqual(rect.x(), 40); - assertAlmostEqual(rect.y(), 40); - assertAlmostEqual(rect.width(), 100); - assertAlmostEqual(rect.height(), 200); - assertAlmostEqual(rect.scaleY(), 0.5); - assertAlmostEqual(rect.rotation(), 0); - }); - - it('rotate around center', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - x: 50, - y: 50, - draggable: true, - width: 100, - height: 200, - fill: 'yellow', - rotation: 45, - }); - layer.add(rect); - - var tr = new Konva.Transformer(); - layer.add(tr); - tr.nodes([rect]); - - layer.draw(); - - tr._fitNodesInto({ - x: 40, - y: 40, - width: 100, - height: 100, - rotation: 0, - }); - - assertAlmostEqual(rect.x(), 40); - assertAlmostEqual(rect.y(), 40); - assertAlmostEqual(rect.width(), 100); - assertAlmostEqual(rect.height(), 200); - assertAlmostEqual(rect.scaleY(), 0.5); - assertAlmostEqual(rect.rotation(), 0); - }); - - it('change transform of parent', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - x: 50, - y: 50, - draggable: true, - width: 100, - height: 100, - fill: 'yellow', - }); - layer.add(rect); - - var tr = new Konva.Transformer({ - nodes: [rect], - }); - layer.add(tr); - - layer.draw(); - - assert.equal(tr.x(), 50, 'first x'); - - stage.scaleX(2); - stage.scaleY(2); - - // check attrs - assert.equal(tr.x(), 100, 'second x'); - assert.equal(tr.width(), 200); - // check visual - var pos = tr.findOne('.top-right').getAbsolutePosition(); - assert.equal(pos.x, 300); - - stage.draw(); - }); - - it('rotated inside scaled (in one direction) parent', function () { - var stage = addStage(); - stage.scaleX(2); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - x: 50, - y: 50, - draggable: true, - width: 100, - height: 100, - fill: 'yellow', - }); - layer.add(rect); - - var tr = new Konva.Transformer({ - nodes: [rect], - }); - layer.add(tr); - - layer.draw(); - tr._fitNodesInto({ - x: 100, - y: 0, - width: 50, - height: 50, - rotation: Konva.getAngle(45), - }); - - assert.equal(rect.x(), 50); - assert.equal(rect.skewX(), 0.75); - assert.equal(rect.skewY(), 0); - }); - - it('try to fit rectangle with skew', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - x: 100, - y: 60, - draggable: true, - width: 100, - height: 100, - fill: 'yellow', - skewX: 0.5, - scaleX: 2, - }); - layer.add(rect); - - var tr = new Konva.Transformer(); - layer.add(tr); - tr.nodes([rect]); - - layer.draw(); - - tr._fitNodesInto({ - x: 120, - y: 60, - width: 50, - height: 50, - rotation: Konva.getAngle(45), - }); - - assert.equal(tr.x(), rect.x()); - assert.equal(Math.round(tr.y()), rect.y()); - assert.equal(tr.width(), 50); - assert.equal(tr.height(), 50); - assert.equal(tr.rotation(), rect.rotation()); - assertAlmostEqual(rect.skewX(), 0.2); - }); - - it('try to resize in draggable stage', function () { - var stage = addStage(); - stage.draggable(true); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - x: 50, - y: 50, - draggable: true, - width: 100, - height: 100, - fill: 'yellow', - }); - layer.add(rect); - - var tr = new Konva.Transformer({ - nodes: [rect], - }); - layer.add(tr); - layer.draw(); - - var dragstart = 0; - var dragmove = 0; - var dragend = 0; - stage.on('dragstart', function () { - dragstart += 1; - }); - stage.on('dragmove', function () { - dragmove += 1; - }); - stage.on('dragend', function () { - dragend += 1; - }); - - simulateMouseDown(tr, { - x: 50, - y: 50, - }); - simulateMouseMove(tr, { - x: 60, - y: 60, - }); - assert.equal(stage.isDragging(), false); - assert.equal(dragstart, 0); - simulateMouseUp(tr, { x: 60, y: 60 }); - assert.equal(dragmove, 0); - assert.equal(dragend, 0); - - simulateMouseDown(tr, { - x: 40, - y: 40, - }); - sm(stage, { - x: 45, - y: 45, - }); - sm(stage, { - x: 50, - y: 50, - }); - assert.equal(stage.isDragging(), true); - assert.equal(stage.x(), 10); - assert.equal(stage.y(), 10); - su(stage, { - x: 45, - y: 45, - }); - }); - - it('try to fit simple rectangle into negative scale', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - x: 0, - y: 0, - draggable: true, - width: 100, - height: 100, - fill: 'yellow', - }); - layer.add(rect); - - var tr = new Konva.Transformer(); - layer.add(tr); - tr.nodes([rect]); - - layer.draw(); - - var box = { - x: 100, - y: 0, - width: -100, - height: 100, - rotation: 0, - }; - - tr._fitNodesInto(box); - - assertAlmostEqual(rect.x(), 100); - assertAlmostEqual(rect.y(), 0); - assertAlmostEqual(rect.width(), 100); - assertAlmostEqual(rect.scaleX(), 1); - assertAlmostEqual(rect.height(), 100); - assertAlmostEqual(rect.scaleY(), -1); - assertAlmostEqual(rect.rotation(), -180); - - layer.draw(); - }); - it('try to fit rectangle with ignoreStroke = false', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - x: 20, - y: 20, - width: 100, - height: 100, - fill: 'green', - stroke: 'rgba(0,0,0,0.5)', - strokeWidth: 40, - name: 'myCircle', - draggable: true, - strokeScaleEnabled: false, - }); - layer.add(rect); - - var tr = new Konva.Transformer({ - ignoreStroke: true, - }); - layer.add(tr); - tr.nodes([rect]); - - layer.draw(); - - tr._fitNodesInto({ - x: 20, - y: 20, - width: 200, - height: 200, - rotation: 0, - }); - - assertAlmostEqual(rect.x(), 20); - assertAlmostEqual(rect.y(), 20); - assertAlmostEqual(rect.width(), 100); - assertAlmostEqual(rect.height(), 100); - assertAlmostEqual(rect.scaleX(), 2); - }); - - it('listen shape changes', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - draggable: true, - fill: 'yellow', - }); - layer.add(rect); - - var tr = new Konva.Transformer(); - layer.add(tr); - tr.nodes([rect]); - - layer.draw(); - assert.equal(tr.getClassName(), 'Transformer'); - - rect.setAttrs({ - x: 50, - y: 50, - width: 100, - height: 100, - }); - layer.draw(); - assert.equal(tr.x(), rect.x()); - assert.equal(tr.y(), rect.y()); - assert.equal(tr.width(), rect.width()); - assert.equal(tr.height(), rect.height()); - assert.equal(tr.findOne('.back').width(), rect.width()); - }); - - it('add transformer for transformed rect', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - x: 150, - y: 60, - draggable: true, - width: 100, - height: 100, - fill: 'yellow', - rotation: 90, - scaleY: 1.5, - }); - layer.add(rect); - - var tr = new Konva.Transformer(); - layer.add(tr); - tr.nodes([rect]); - - layer.draw(); - assert.equal(tr.getClassName(), 'Transformer'); - - layer.draw(); - - assert.equal(tr.x(), rect.x()); - assert.equal(tr.y(), rect.y()); - assert.equal(tr.width(), rect.width() * rect.scaleX()); - assert.equal(tr.height(), rect.height() * rect.scaleY()); - assert.equal(tr.rotation(), rect.rotation()); - }); - - it('try to fit a transformed rect', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - x: 150, - y: 60, - draggable: true, - width: 150, - height: 100, - fill: 'yellow', - rotation: 90, - scaleY: 1.5, - }); - layer.add(rect); - - var tr = new Konva.Transformer(); - layer.add(tr); - tr.nodes([rect]); - - layer.draw(); - - tr._fitNodesInto({ - x: 100, - y: 70, - width: 100, - height: 100, - rotation: 0, - }); - - assertAlmostEqual(rect.x(), 100); - assertAlmostEqual(rect.y(), 70); - assertAlmostEqual(rect.width() * rect.scaleX(), 100); - assertAlmostEqual(rect.height() * rect.scaleY(), 100); - assertAlmostEqual(rect.rotation(), rect.rotation()); - }); - - it('add transformer for transformed rect with offset', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - x: 100, - y: 100, - draggable: true, - width: 100, - height: 100, - scaleX: 2, - scaleY: 2, - fill: 'yellow', - offsetX: 50, - offsetY: 50, - }); - layer.add(rect); - - var tr = new Konva.Transformer(); - layer.add(tr); - tr.nodes([rect]); - - layer.draw(); - assert.equal(tr.getClassName(), 'Transformer'); - - assert.equal(tr.x(), 0); - assert.equal(tr.y(), 0); - assert.equal(tr.width(), rect.width() * rect.scaleX()); - assert.equal(tr.height(), rect.height() * rect.scaleY()); - assert.equal(tr.rotation(), rect.rotation()); - }); - - it('fit rect with offset', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - x: 50, - y: 50, - draggable: true, - width: 100, - height: 100, - fill: 'yellow', - offsetX: 50, - offsetY: 50, - }); - layer.add(rect); - - var tr = new Konva.Transformer(); - layer.add(tr); - tr.nodes([rect]); - - tr._fitNodesInto({ - x: 0, - y: 0, - width: 200, - height: 100, - rotation: 0, - }); - layer.draw(); - - assertAlmostEqual(rect.x(), 100); - assertAlmostEqual(rect.y(), 50); - assertAlmostEqual(rect.width() * rect.scaleX(), 200); - assertAlmostEqual(rect.height() * rect.scaleY(), 100); - assertAlmostEqual(rect.rotation(), rect.rotation()); - - assertAlmostEqual(tr.x(), 0); - assertAlmostEqual(tr.y(), 0); - assertAlmostEqual(tr.width(), 200); - assertAlmostEqual(tr.height(), 100); - assertAlmostEqual(rect.rotation(), rect.rotation()); - }); - - it('add transformer for circle', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var circle = new Konva.Circle({ - x: 40, - y: 40, - draggable: true, - radius: 40, - fill: 'yellow', - }); - layer.add(circle); - - var tr = new Konva.Transformer(); - layer.add(tr); - tr.nodes([circle]); - - layer.draw(); - assert.equal(tr.getClassName(), 'Transformer'); - - assert.equal(tr.x(), 0); - assert.equal(tr.y(), 0); - assert.equal(tr.width(), circle.width() * circle.scaleX()); - assert.equal(tr.height(), circle.height() * circle.scaleY()); - assert.equal(tr.rotation(), circle.rotation()); - }); - - it('fit a circle', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var circle = new Konva.Circle({ - x: 40, - y: 40, - draggable: true, - radius: 40, - fill: 'yellow', - }); - layer.add(circle); - - var tr = new Konva.Transformer(); - layer.add(tr); - tr.nodes([circle]); - - tr._fitNodesInto({ - x: 40, - y: 40, - width: 160, - height: 80, - rotation: 0, - }); - layer.draw(); - - assert.equal(circle.x(), 120); - assert.equal(circle.y(), 80); - assert.equal(circle.width() * circle.scaleX(), 160); - assert.equal(circle.height() * circle.scaleY(), 80); - - assert.equal(tr.x(), 40); - assert.equal(tr.y(), 40); - assert.equal(tr.width(), 160); - assert.equal(tr.height(), 80); - }); - - it('fit a rotated circle', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var circle = new Konva.Circle({ - x: 40, - y: 40, - draggable: true, - radius: 40, - fill: 'yellow', - }); - layer.add(circle); - - var tr = new Konva.Transformer(); - layer.add(tr); - tr.nodes([circle]); - - tr._fitNodesInto({ - x: 80, - y: 0, - width: 80, - height: 80, - rotation: Konva.getAngle(90), - }); - layer.draw(); - - assertAlmostEqual(circle.x(), 40); - assertAlmostEqual(circle.y(), 40); - assertAlmostEqual(circle.width() * circle.scaleX(), 80); - assertAlmostEqual(circle.height() * circle.scaleY(), 80); - assertAlmostEqual(circle.rotation(), 90); - - assertAlmostEqual(tr.x(), 80); - assertAlmostEqual(tr.y(), 0); - assertAlmostEqual(tr.width(), 80); - assertAlmostEqual(tr.height(), 80); - }); - - it('add transformer for transformed circle', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var circle = new Konva.Circle({ - x: 100, - y: 100, - draggable: true, - radius: 40, - fill: 'yellow', - scaleX: 1.5, - }); - layer.add(circle); - - var tr = new Konva.Transformer(); - layer.add(tr); - tr.nodes([circle]); - - layer.draw(); - assert.equal(tr.getClassName(), 'Transformer'); - - assert.equal(tr.x(), 40); - assert.equal(tr.y(), 60); - assert.equal(tr.width(), 120); - assert.equal(tr.height(), 80); - assert.equal(tr.rotation(), 0); - }); - - it('add transformer for rotated circle', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var circle = new Konva.Circle({ - x: 100, - y: 100, - draggable: true, - radius: 40, - fill: 'yellow', - scaleX: 1.5, - rotation: 90, - }); - layer.add(circle); - - var tr = new Konva.Transformer(); - layer.add(tr); - tr.nodes([circle]); - - layer.draw(); - - assert.equal(tr.x(), 140); - assert.equal(tr.y(), 40); - assert.equal(tr.width(), 120); - assert.equal(tr.height(), 80); - assert.equal(tr.rotation(), circle.rotation()); - }); - - it('add transformer to group', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var group = new Konva.Group({ - x: 50, - y: 50, - draggable: true, - }); - layer.add(group); - - var shape1 = new Konva.Rect({ - radius: 100, - fill: 'red', - x: 0, - y: 0, - width: 100, - height: 100, - }); - - group.add(shape1); - - var shape2 = new Konva.Rect({ - radius: 100, - fill: 'yellow', - x: 50, - y: 50, - width: 100, - height: 100, - }); - group.add(shape2); - - var tr = new Konva.Transformer(); - layer.add(tr); - tr.nodes([group]); - - layer.draw(); - - assert.equal(tr.x(), group.x()); - assert.equal(tr.y(), group.y()); - assert.equal(tr.width(), 150); - assert.equal(tr.height(), 150); - assert.equal(tr.rotation(), 0); - }); - - it('rotated fit group', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var group = new Konva.Group({ - x: 100, - y: 100, - draggable: true, - }); - layer.add(group); - - var shape1 = new Konva.Rect({ - fill: 'red', - x: -50, - y: -50, - width: 50, - height: 50, - }); - - group.add(shape1); - - var shape2 = new Konva.Rect({ - fill: 'yellow', - x: 0, - y: 0, - width: 50, - height: 50, - }); - group.add(shape2); - - var tr = new Konva.Transformer(); - layer.add(tr); - tr.nodes([group]); - - tr._fitNodesInto({ - x: 100, - y: 0, - width: 100, - height: 100, - rotation: Konva.getAngle(90), - }); - layer.draw(); - - var rect = group.getClientRect(); - - assertAlmostEqual(group.x(), 50); - assertAlmostEqual(group.y(), 50); - assertAlmostEqual(rect.width, 100); - assertAlmostEqual(rect.height, 100); - assertAlmostEqual(group.rotation(), 90); - - assertAlmostEqual(tr.x(), 100); - assertAlmostEqual(tr.y(), 0); - assertAlmostEqual(tr.width(), 100); - assertAlmostEqual(tr.height(), 100); - }); - - it('add transformer to another group', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var group = new Konva.Group({ - x: 100, - y: 100, - draggable: true, - }); - layer.add(group); - - var shape1 = new Konva.Rect({ - fill: 'red', - x: -50, - y: -50, - width: 50, - height: 50, - }); - - group.add(shape1); - - var shape2 = new Konva.Rect({ - fill: 'yellow', - x: 0, - y: 0, - width: 50, - height: 50, - }); - group.add(shape2); - - var tr = new Konva.Transformer(); - layer.add(tr); - tr.nodes([group]); - - layer.draw(); - - assert.equal(tr.x(), 50); - assert.equal(tr.y(), 50); - assert.equal(tr.width(), 100); - assert.equal(tr.height(), 100); - assert.equal(tr.rotation(), 0); - }); - - it('fit group', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var group = new Konva.Group({ - x: 100, - y: 100, - draggable: true, - }); - layer.add(group); - - var shape1 = new Konva.Rect({ - fill: 'red', - x: -50, - y: -50, - width: 50, - height: 50, - }); - - group.add(shape1); - - var shape2 = new Konva.Rect({ - fill: 'yellow', - x: 0, - y: 0, - width: 50, - height: 50, - }); - group.add(shape2); - - var tr = new Konva.Transformer(); - layer.add(tr); - tr.nodes([group]); - - tr._fitNodesInto({ - x: 0, - y: 0, - width: 200, - height: 100, - rotation: 0, - }); - layer.draw(); - - var rect = group.getClientRect(); - - assertAlmostEqual(group.x(), 100); - assertAlmostEqual(group.y(), 50); - assertAlmostEqual(rect.width, 200); - assertAlmostEqual(rect.height, 100); - - assertAlmostEqual(tr.x(), 0); - assertAlmostEqual(tr.y(), 0); - assertAlmostEqual(tr.width(), 200); - assertAlmostEqual(tr.height(), 100); - }); - - it('toJSON should not save attached node and children', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - x: 100, - y: 60, - draggable: true, - width: 100, - height: 100, - fill: 'yellow', - }); - layer.add(rect); - - var tr = new Konva.Transformer(); - layer.add(tr); - tr.nodes([rect]); - - layer.draw(); - - var json = tr.toJSON(); - var object = JSON.parse(json); - - assert.equal(object.attrs.node, undefined); - assert.equal(object.children, undefined); - }); - - it('make sure we can work without inner node', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var tr = new Konva.Transformer(); - layer.add(tr); - layer.draw(); - - // just check not throw - assert.equal(1, 1); - }); - - it('reset attrs on node set', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - x: 100, - y: 60, - draggable: true, - width: 100, - height: 100, - fill: 'yellow', - }); - layer.add(rect); - - var tr = new Konva.Transformer(); - layer.add(tr); - layer.draw(); - - assert.equal(tr.getWidth(), 0); - - tr.nodes([rect]); - assert.equal(tr.getWidth(), 100); - }); - - it('can destroy without attached node', function () { - var tr = new Konva.Transformer(); - tr.destroy(); - // just check not throw - assert.equal(1, 1); - }); - - it('can destroy with attached node while resize', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - x: 100, - y: 60, - draggable: true, - width: 100, - height: 100, - fill: 'yellow', - }); - layer.add(rect); - - var tr = new Konva.Transformer({ - nodes: [rect], - }); - layer.add(tr); - - layer.draw(); - - simulateMouseDown(tr, { - x: 100, - y: 60, - }); - - assert.equal(tr.isTransforming(), true); - - tr.destroy(); - - assert.equal(tr.isTransforming(), false); - - assert.equal(tr.getNode(), undefined); - - su(stage, { - x: 100, - y: 60, - }); - }); - - it('can add padding', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - x: 30, - y: 30, - draggable: true, - width: 100, - height: 100, - fill: 'yellow', - }); - layer.add(rect); - - var tr = new Konva.Transformer({ - nodes: [rect], - padding: 20, - }); - layer.add(tr); - layer.draw(); - - simulateMouseDown(tr, { - x: 10, - y: 80, - }); - - simulateMouseMove(tr, { - x: 60, - y: 80, - }); - - simulateMouseUp(tr, { - x: 200, - y: 150, - }); - - assertAlmostEqual(rect.x(), 80); - assertAlmostEqual(rect.y(), 30); - assertAlmostEqual(rect.width(), 100); - assertAlmostEqual(rect.scaleX(), 0.5); - assertAlmostEqual(rect.height(), 100); - assertAlmostEqual(rect.scaleY(), 1); - }); - - it('keep ratio should allow negative scaling', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - x: 150, - y: 10, - draggable: true, - width: 50, - height: 50, - fill: 'yellow', - }); - layer.add(rect); - - var tr = new Konva.Transformer({ - nodes: [rect], - }); - layer.add(tr); - layer.draw(); - - var anchor = tr.findOne('.top-right'); - var pos = anchor.getAbsolutePosition(); - - simulateMouseDown(tr, { - x: pos.x, - y: pos.y, - }); - simulateMouseMove(tr, { - x: pos.x - 100, - y: pos.y + 100, - }); - simulateMouseUp(tr, { - x: pos.x - 100, - y: pos.y + 100, - }); - - assertAlmostEqual(rect.scaleX(), 1); - assertAlmostEqual(rect.scaleY(), 1); - assertAlmostEqual(rect.rotation(), -180); - }); - - it('slightly move for cache check (top-left anchor)', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - x: 20, - y: 20, - draggable: true, - width: 100, - height: 100, - fill: 'yellow', - }); - layer.add(rect); - - var tr = new Konva.Transformer({ - nodes: [rect], - }); - layer.add(tr); - - layer.draw(); - - var anchor = tr.findOne('.top-left'); - assertAlmostEqual(anchor.getAbsolutePosition().x, 20); - - simulateMouseDown(tr, { - x: 20, - y: 20, - }); - - simulateMouseMove(tr, { - x: 20, - y: 20, - }); - - simulateMouseUp(tr); - layer.draw(); - - assertAlmostEqual(rect.x(), 20); - assertAlmostEqual(rect.y(), 20); - }); - - it('rotation snaps', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - x: 50, - y: 50, - width: 100, - height: 100, - fill: 'yellow', - }); - layer.add(rect); - - var tr = new Konva.Transformer({ - nodes: [rect], - rotationSnaps: [0, 90, 180, 270], - rotationSnapTolerance: 45, - }); - layer.add(tr); - - layer.draw(); - - simulateMouseDown(tr, { - x: 100, - y: 0, - }); - - // move to almost 45 deg - simulateMouseMove(tr, { - x: 199, - y: 0, - }); - assert.equal(rect.rotation(), 0); - - // move to more than 45 deg - simulateMouseMove(tr, { - x: 200, - y: 2, - }); - assert.equal(rect.rotation(), 90); - - simulateMouseMove(tr, { - x: 200, - y: 199, - }); - assert.equal(rect.rotation(), 90); - - simulateMouseMove(tr, { - x: 199, - y: 200, - }); - assert.equal(rect.rotation(), 180); - - simulateMouseMove(tr, { - x: 1, - y: 200, - }); - assert.equal(rect.rotation(), 180); - - simulateMouseMove(tr, { - x: 0, - y: 199, - }); - assertAlmostEqual(rect.rotation(), -90); - - simulateMouseMove(tr, { - x: 0, - y: 50, - }); - assertAlmostEqual(rect.rotation(), -90); - simulateMouseMove(tr, { - x: 0, - y: 45, - }); - assertAlmostEqual(rect.rotation(), -90); - - simulateMouseMove(tr, { - x: 0, - y: 1, - }); - assertAlmostEqual(rect.rotation(), -90); - - simulateMouseMove(tr, { - x: 1, - y: 0, - }); - assert.equal(rect.rotation(), 0); - - simulateMouseUp(tr); - }); - - it('switch scaling with padding - x', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - x: 10, - y: 10, - draggable: true, - width: 100, - height: 100, - fill: 'yellow', - }); - layer.add(rect); - - var tr = new Konva.Transformer({ - nodes: [rect], - padding: 10, - }); - layer.add(tr); - - layer.draw(); - - simulateMouseDown(tr, { - x: 0, - y: 60, - }); - - simulateMouseMove(tr, { - x: 125, - y: 60, - }); - - assertAlmostEqual(rect.x(), 115); - assertAlmostEqual(rect.y(), 10); - assertAlmostEqual(rect.width(), 100); - assertAlmostEqual(rect.scaleX(), 0.05); - assertAlmostEqual(rect.scaleY(), -1); - assertAlmostEqual(rect.height(), 100); - assertAlmostEqual(rect.rotation(), -180); - - simulateMouseMove(tr, { - x: 125, - y: 60, - }); - - assertAlmostEqual(rect.x(), 115); - assertAlmostEqual(rect.y(), 10); - assertAlmostEqual(rect.width(), 100); - assertAlmostEqual(rect.scaleX(), 0.05); - assertAlmostEqual(rect.height(), 100); - assertAlmostEqual(rect.scaleY(), -1); - - // switch again - simulateMouseMove(tr, { - x: 90, - y: 60, - }); - - assertAlmostEqual(rect.x(), 100); - assertAlmostEqual(rect.y(), 10); - assertAlmostEqual(rect.width(), 100); - assertAlmostEqual(rect.scaleY(), 1); - assertAlmostEqual(rect.scaleX(), 0.1); - assertAlmostEqual(rect.height(), 100); - - simulateMouseUp(tr); - }); - - it('switch scaling with padding - y', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - x: 10, - y: 10, - draggable: true, - width: 100, - height: 100, - fill: 'yellow', - }); - layer.add(rect); - - var tr = new Konva.Transformer({ - nodes: [rect], - padding: 10, - }); - layer.add(tr); - - layer.draw(); - - simulateMouseDown(tr, { - x: 60, - y: 0, - }); - - simulateMouseMove(tr, { - x: 60, - y: 125, - }); - - assertAlmostEqual(rect.x(), 10); - assertAlmostEqual(rect.y(), 115); - assertAlmostEqual(rect.width(), 100); - assertAlmostEqual(rect.scaleY(), -0.05); - assertAlmostEqual(rect.height(), 100); - assertAlmostEqual(rect.rotation(), 0); - - simulateMouseMove(tr, { - x: 60, - y: 125, - }); - - assertAlmostEqual(rect.x(), 10); - assertAlmostEqual(rect.y(), 115); - assertAlmostEqual(rect.width(), 100); - assertAlmostEqual(rect.scaleY(), -0.05); - assertAlmostEqual(rect.height(), 100); - assertAlmostEqual(rect.rotation(), 0); - - // switch again - simulateMouseMove(tr, { - x: 60, - y: 90, - }); - - assertAlmostEqual(rect.x(), 10); - assertAlmostEqual(rect.y(), 100); - assertAlmostEqual(rect.width(), 100); - assertAlmostEqual(rect.scaleX(), 1); - assertAlmostEqual(rect.scaleY(), 0.1); - assertAlmostEqual(rect.height(), 100); - assertAlmostEqual(rect.rotation(), 0); - - simulateMouseUp(tr); - }); - - it('switch horizontal scaling with (top-left anchor)', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - x: 0, - y: 0, - draggable: true, - width: 100, - height: 100, - fill: 'yellow', - }); - layer.add(rect); - - var tr = new Konva.Transformer({ - nodes: [rect], - }); - layer.add(tr); - - layer.draw(); - - simulateMouseDown(tr, { - x: 0, - y: 0, - }); - - simulateMouseMove(tr, { - x: 150, - y: 50, - }); - layer.draw(); - - assertAlmostEqual(rect.x(), 150); - assertAlmostEqual(rect.y(), 50); - assertAlmostEqual(rect.width(), 100); - assertAlmostEqual(rect.scaleX(), 0.5); - assertAlmostEqual(rect.scaleY(), -0.5); - assertAlmostEqual(rect.height(), 100); - assertAlmostEqual(rect.rotation(), -180); - - simulateMouseMove(tr, { - x: 98, - y: 2.859375, - }); - simulateMouseMove(tr, { - x: 98, - y: 2.859375, - }); - simulateMouseMove(tr, { - x: 98, - y: 2.859375, - }); - simulateMouseMove(tr, { - x: 100, - y: 2.859375, - }); - layer.draw(); - simulateMouseMove(tr, { - x: 101, - y: 2.859375, - }); - layer.draw(); - simulateMouseMove(tr, { - x: 101, - y: 2.859375, - }); - layer.draw(); - simulateMouseMove(tr, { - x: 101, - y: 2.859375, - }); - layer.draw(); - simulateMouseMove(tr, { - x: 102, - y: 2.859375, - }); - layer.draw(); - // switch again - simulateMouseMove(tr, { - x: 0, - y: 0, - }); - - assertAlmostEqual(rect.x(), 0); - assertAlmostEqual(rect.y(), 0); - assertAlmostEqual(rect.width(), 100); - assertAlmostEqual(rect.scaleY(), 1); - assertAlmostEqual(rect.scaleX(), 1); - assertAlmostEqual(rect.height(), 100); - - simulateMouseUp(tr); - }); - - it('switch vertical scaling with (top-left anchor)', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - x: 0, - y: 0, - draggable: true, - width: 100, - height: 100, - fill: 'yellow', - }); - layer.add(rect); - - var tr = new Konva.Transformer({ - nodes: [rect], - }); - layer.add(tr); - - layer.draw(); - - simulateMouseDown(tr, { - x: 0, - y: 0, - }); - - simulateMouseMove(tr, { - x: 0, - y: 200, - }); - layer.draw(); - - assertAlmostEqual(rect.x(), 0); - assertAlmostEqual(rect.y(), 200); - assertAlmostEqual(rect.width(), 100); - assertAlmostEqual(rect.scaleX(), 1); - assertAlmostEqual(rect.height(), 100); - assertAlmostEqual(rect.rotation(), 0); - - simulateMouseMove(tr, { - x: 0, - y: 0, - }); - layer.draw(); - simulateMouseUp(tr); - - assertAlmostEqual(rect.x(), 0); - assertAlmostEqual(rect.y(), 0); - assertAlmostEqual(rect.width(), 100); - assertAlmostEqual(rect.scaleX(), 1); - assertAlmostEqual(rect.height(), 100); - assertAlmostEqual(rect.rotation(), 0); - assertAlmostEqual(rect.scaleY(), 1); - }); - - it('switch scaling with padding for rotated - x', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - x: 110, - y: 10, - draggable: true, - width: 100, - height: 100, - fill: 'yellow', - rotation: 90, - }); - layer.add(rect); - - var tr = new Konva.Transformer({ - nodes: [rect], - padding: 10, - }); - layer.add(tr); - - layer.draw(); - - simulateMouseDown(tr, { - x: 60, - y: 0, - }); - - simulateMouseMove(tr, { - x: 60, - y: 125, - }); - - assertAlmostEqual(rect.x(), 110); - assertAlmostEqual(rect.y(), 115); - assertAlmostEqual(rect.width(), 100); - assertAlmostEqual(rect.scaleX(), 0.05); - assertAlmostEqual(rect.scaleY(), -1); - assertAlmostEqual(rect.height(), 100); - assertAlmostEqual(rect.rotation(), -90); - - simulateMouseMove(tr, { - x: 60, - y: 125, - }); - - assertAlmostEqual(rect.x(), 110); - assertAlmostEqual(rect.y(), 115); - assertAlmostEqual(rect.width(), 100); - assertAlmostEqual(rect.scaleX(), 0.05); - assertAlmostEqual(rect.height(), 100); - assertAlmostEqual(rect.scaleY(), -1); - - layer.draw(); - - // switch again - simulateMouseMove(tr, { - x: 60, - y: 90, - }); - - assertAlmostEqual(rect.x(), 110); - assertAlmostEqual(rect.y(), 100); - assertAlmostEqual(rect.width(), 100); - assertAlmostEqual(rect.scaleX(), 0.1); - assertAlmostEqual(rect.scaleY(), 1); - - assertAlmostEqual(rect.height(), 100); - - simulateMouseUp(tr); - }); - - it('switch scaling with padding for rotated - y', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - x: 110, - y: 10, - draggable: true, - width: 100, - height: 100, - fill: 'yellow', - rotation: 90, - }); - layer.add(rect); - - var tr = new Konva.Transformer({ - nodes: [rect], - padding: 10, - }); - layer.add(tr); - - layer.draw(); - - simulateMouseDown(tr, { - x: 0, - y: 60, - }); - - simulateMouseMove(tr, { - x: 125, - y: 60, - }); - - assertAlmostEqual(rect.x(), 110); - assertAlmostEqual(rect.y(), 10); - assertAlmostEqual(rect.width(), 100); - assertAlmostEqual(rect.scaleX(), 1); - assertAlmostEqual(rect.scaleY(), -0.05); - assertAlmostEqual(rect.height(), 100); - assertAlmostEqual(rect.rotation(), 90); - - simulateMouseMove(tr, { - x: 125, - y: 60, - }); - - assertAlmostEqual(rect.x(), 110); - assertAlmostEqual(rect.y(), 10); - assertAlmostEqual(rect.width(), 100); - assertAlmostEqual(rect.scaleX(), 1); - assertAlmostEqual(rect.scaleY(), -0.05); - assertAlmostEqual(rect.height(), 100); - assertAlmostEqual(rect.rotation(), 90); - - // switch again - simulateMouseMove(tr, { - x: 90, - y: 60, - }); - - assertAlmostEqual(rect.x(), 110); - assertAlmostEqual(rect.y() - 120 < 0.001, true); - assertAlmostEqual(rect.width(), 100); - assertAlmostEqual(rect.scaleX(), 1); - assertAlmostEqual(rect.scaleY(), 0.1); - assertAlmostEqual(rect.height(), 100); - - simulateMouseUp(tr); - }); - - it('transformer should automatically track attr changes of a node', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - x: 100, - y: 60, - draggable: true, - width: 100, - height: 100, - fill: 'yellow', - }); - layer.add(rect); - - var tr = new Konva.Transformer({ - nodes: [rect], - }); - layer.add(tr); - - layer.draw(); - - assert.equal(tr.x(), 100); - assert.equal(tr.y(), 60); - assert.equal(tr.width(), 100); - assert.equal(rect.height(), 100); - assert.equal(rect.rotation(), 0); - - rect.x(0); - assert.equal(tr.x(), 0); - - rect.y(0); - assert.equal(tr.y(), 0); - - rect.width(50); - assert.equal(tr.width(), 50); - - rect.height(50); - assert.equal(tr.height(), 50); - - rect.scaleX(2); - assert.equal(tr.width(), 100); - - rect.scaleY(2); - assert.equal(tr.height(), 100); - - // manual check position - var back = tr.findOne('.back'); - assert.equal(back.getAbsolutePosition().x, 0); - - layer.batchDraw(); - }); - - it('on detach should remove all listeners', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - x: 100, - y: 60, - draggable: true, - width: 100, - height: 100, - fill: 'yellow', - }); - layer.add(rect); - - var tr = new Konva.Transformer({ - nodes: [rect], - }); - layer.add(tr); - - layer.draw(); - - tr.detach(); - - rect.width(200); - assert.equal(tr.width(), 0); - layer.draw(); - - var called = false; - // clear cache is called on each update - // make sure we don't call it - tr._clearCache = function () { - called = true; - }; - rect.width(50); - assert.equal(called, false, 'don not call clear cache'); - }); - - it('check transformer with drag&drop', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - x: 0, - y: 0, - width: 100, - height: 100, - fill: 'green', - draggable: true, - }); - - layer.add(rect); - - var tr = new Konva.Transformer({ - nodes: [rect], - }); - layer.add(tr); - - layer.draw(); - - simulateMouseDown(tr, { - x: 20, - y: 20, - }); - - sm(stage, { - x: 30, - y: 30, - }); - - su(stage, { - x: 30, - y: 30, - }); - - assert.equal(rect.x(), 10); - assert.equal(rect.y(), 10); - - assert.equal(tr.x(), 10); - assert.equal(tr.y(), 10); - }); - - it('check transformer with drag&drop and scaled shape', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - x: 0, - y: 0, - width: 100, - height: 100, - fill: 'green', - draggable: true, - scaleX: 2, - }); - - layer.add(rect); - - var tr = new Konva.Transformer({ - nodes: [rect], - }); - layer.add(tr); - - layer.draw(); - - simulateMouseDown(tr, { - x: 20, - y: 20, - }); - - sm(stage, { - x: 30, - y: 30, - }); - - layer.draw(); - - assert.equal(rect.x(), 10); - assert.equal(rect.y(), 10); - - assert.equal(tr.x(), 10); - assert.equal(tr.y(), 10); - - assert.equal(tr.width(), 200); - - su(stage, { - x: 30, - y: 30, - }); - }); - - it('try rotate scaled rect', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - x: 50, - y: 150, - draggable: true, - width: 100, - height: 100, - fill: 'yellow', - scaleY: -1, - }); - layer.add(rect); - - var tr = new Konva.Transformer({ - nodes: [rect], - }); - layer.add(tr); - layer.draw(); - - var rotater = tr.findOne('.rotater'); - var pos = rotater.getAbsolutePosition(); - - simulateMouseDown(tr, { - x: pos.x, - y: pos.y, - }); - simulateMouseMove(tr, { - x: pos.x + 100, - y: pos.y + 100, - }); - simulateMouseUp(tr, { - x: pos.x + 100, - y: pos.y + 100, - }); - - assert.equal(rect.rotation(), 90); - }); - - it('check correct cursor on scaled shape', function () { - if (isNode) { - return; - } - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - x: 50, - y: 100, - draggable: true, - width: 100, - height: 100, - fill: 'yellow', - scaleY: -1, - }); - layer.add(rect); - - var tr = new Konva.Transformer({ - nodes: [rect], - }); - layer.add(tr); - layer.draw(); - - sm(stage, { - x: 50, - y: 1, - }); - assert.equal(stage.content.style.cursor, 'nwse-resize'); - }); - - it('check correct cursor off on Transformer destroy', function () { - if (isNode) { - return; - } - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - x: 0, - y: 0, - draggable: true, - width: 100, - height: 100, - fill: 'yellow', - }); - layer.add(rect); - - var tr = new Konva.Transformer({ - nodes: [rect], - }); - layer.add(tr); - layer.draw(); - - sm(stage, { - x: 100, - y: 100, - }); - simulateMouseDown(tr, { - x: 100, - y: 100, - }); - - assert.equal(stage.content.style.cursor, 'nwse-resize'); - - var target = stage.getIntersection({ - x: 100, - y: 100, - }); - simulateMouseMove(tr, { - x: 120, - y: 100, - }); - - // here is duplicate, because transformer is listening window events - simulateMouseUp(tr, { - x: 120, - y: 100, - }); - su(stage, { - x: 120, - y: 100, - }); - - tr.destroy(); - sm(stage, { - x: 140, - y: 100, - }); - assert.equal(stage.content.style.cursor, ''); - }); - - it('check correct cursor on scaled parent', function () { - if (isNode) { - return; - } - var stage = addStage(); - var layer = new Konva.Layer({ - y: 100, - scaleY: -1, - }); - stage.add(layer); - - var rect = new Konva.Rect({ - x: 50, - y: 0, - draggable: true, - width: 100, - height: 100, - fill: 'yellow', - }); - layer.add(rect); - - var tr = new Konva.Transformer({ - nodes: [rect], - }); - layer.add(tr); - layer.draw(); - - sm(stage, { - x: 50, - y: 1, - }); - assert.equal(stage.content.style.cursor, 'nwse-resize'); - }); - - it('check default cursor transformer', function () { - if (isNode) { - return; - } - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - x: 50, - y: 50, - draggable: true, - width: 100, - height: 100, - fill: 'yellow', - }); - layer.add(rect); - - var tr = new Konva.Transformer({ - nodes: [rect], - }); - layer.add(tr); - layer.draw(); - - sm(stage, { - x: 100, - y: 0, - }); - assert.equal(stage.content.style.cursor, 'crosshair'); - }); - - it('using custom cursor on configured transformer should show custom cursor instead of crosshair', function () { - if (isNode) { - return; - } - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - x: 50, - y: 50, - draggable: true, - width: 100, - height: 100, - fill: 'yellow', - }); - layer.add(rect); - - var tr = new Konva.Transformer({ - nodes: [rect], - rotateAnchorCursor: 'grab', - }); - layer.add(tr); - layer.draw(); - - sm(stage, { - x: 100, - y: 0, - }); - assert.equal(stage.content.style.cursor, 'grab'); - }); - - it('changing parent transform should recalculate transformer attrs', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - x: 0, - y: 0, - draggable: true, - width: 100, - height: 100, - fill: 'yellow', - }); - layer.add(rect); - - var tr = new Konva.Transformer({ - nodes: [rect], - }); - layer.add(tr); - layer.draw(); - - layer.scaleX(2); - - layer.draw(); - - assert.equal(tr.width(), 200); - }); - - it('check fit and correct cursor on rotated parent', function () { - if (isNode) { - return; - } - var stage = addStage(); - var layer = new Konva.Layer({ - x: 100, - y: -50, - rotation: 90, - }); - stage.add(layer); - - var rect = new Konva.Rect({ - x: 50, - y: 0, - draggable: true, - width: 100, - height: 100, - fill: 'yellow', - }); - layer.add(rect); - - var tr = new Konva.Transformer({ - nodes: [rect], - }); - layer.add(tr); - layer.draw(); - - var box = { - x: 100, - y: 0, - width: 100, - height: 100, - rotation: Konva.getAngle(90), - }; - tr._fitNodesInto(box); - - assert.equal(Math.round(tr.x()), Math.round(box.x)); - assert.equal(Math.round(tr.y()), Math.round(box.y)); - assert.equal(Math.round(tr.width()), Math.round(box.width)); - assert.equal(Math.round(tr.height()), Math.round(box.height)); - - sm(stage, { - x: 50, - y: 1, - }); - assert.equal(stage.content.style.cursor, 'ns-resize'); - }); - - it('check drag with transformer', function () { - var stage = addStage(); - stage.draggable(true); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - x: 0, - y: 0, - draggable: true, - width: 100, - height: 100, - fill: 'yellow', - }); - layer.add(rect); - - var tr = new Konva.Transformer({ - nodes: [rect], - }); - layer.add(tr); - layer.draw(); - - simulateMouseDown(tr, { - x: 50, - y: 50, - }); - - sm(stage, { - x: 55, - y: 50, - }); - sm(stage, { - x: 60, - y: 50, - }); - - su(stage, { - x: 60, - y: 50, - }); - - assert.equal(rect.x(), 10); - assert.equal(rect.y(), 0); - }); - - it('stopTransform method', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - x: 50, - y: 50, - draggable: true, - width: 100, - height: 100, - fill: 'yellow', - }); - layer.add(rect); - - var tr = new Konva.Transformer({ - nodes: [rect], - }); - layer.add(tr); - layer.draw(); - - simulateMouseDown(tr, { - x: 50, - y: 50, - }); - - simulateMouseMove(tr, { - x: 60, - y: 60, - }); - - assert.equal(tr.isTransforming(), true); - assert.equal(rect.x(), 60); - - var transformend = 0; - rect.on('transformend', function () { - transformend += 1; - }); - - tr.stopTransform(); - - assert.equal(transformend, 1); - - assert.equal(tr.isTransforming(), false); - assert.equal(rect.x(), 60); - - // here is duplicate, because transformer is listening window events - su(stage, { - x: 100, - y: 100, - }); - }); - - it('transform events check', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - x: 50, - y: 50, - draggable: true, - width: 100, - height: 100, - fill: 'yellow', - }); - layer.add(rect); - - var tr = new Konva.Transformer({ - nodes: [rect], - }); - layer.add(tr); - layer.draw(); - - var callCount = 0; - rect.on('transformstart', function (e) { - callCount += 1; - assert.equal(e.target, rect); - assert.equal(tr.getActiveAnchor(), 'top-left'); - assert.equal(typeof e.evt.clientX === 'number', true); - }); - - rect.on('transform', function (e) { - callCount += 1; - assert.equal(e.target, rect); - assert.equal(tr.getActiveAnchor(), 'top-left'); - }); - - rect.on('transformend', function (e) { - callCount += 1; - assert.equal(e.target, rect); - assert.equal(tr.getActiveAnchor(), 'top-left'); - }); - - tr.on('transformstart', function (e) { - callCount += 1; - assert.equal(e.target, rect); - }); - - tr.on('transform', function (e) { - callCount += 1; - assert.equal(e.target, rect); - }); - - tr.on('transformend', function (e) { - callCount += 1; - assert.equal(e.target, rect); - }); - - simulateMouseDown(tr, { - x: 50, - y: 50, - }); - - simulateMouseMove(tr, { - x: 60, - y: 60, - }); - - simulateMouseUp(tr, { - x: 60, - y: 60, - }); - - assert.equal(callCount, 6); - assert.equal(tr.getActiveAnchor(), null); - }); - - it('on force update should clear transform', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var group = new Konva.Group({ - x: 50, - y: 50, - }); - layer.add(group); - - var tr = new Konva.Transformer(); - layer.add(tr); - tr.nodes([group]); - - layer.draw(); - - assert.equal(tr._cache.get('transform').m[4], 50); - - var rect = new Konva.Rect({ - x: 50, - y: 50, - width: 100, - height: 100, - fill: 'yellow', - }); - group.add(rect); - - tr.forceUpdate(); - layer.draw(); - - assert.equal(tr._cache.get('transform').m[4], 100); - - // tr._fitNodesInto({ - // x: 100, - // y: 70, - // width: 100, - // height: 100 - // }); - - // assert.equal(rect.x(), 100); - // assert.equal(rect.y(), 70); - // assert.equal(rect.width() * rect.scaleX(), 100); - // assert.equal(rect.height() * rect.scaleY(), 100); - // assert.equal(rect.rotation(), rect.rotation()); - }); - - it('test cache reset on attach', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - x: 20, - y: 20, - draggable: true, - width: 150, - height: 100, - fill: 'yellow', - }); - layer.add(rect); - - var tr = new Konva.Transformer(); - layer.add(tr); - - // make draw to set all caches - layer.draw(); - // then attach - tr.nodes([rect]); - - layer.draw(); - - var shape = layer.getIntersection({ - x: 20, - y: 20, - }); - assert.equal(shape.name(), 'top-left _anchor'); - }); - - it('check rotator size on scaled transformer', function () { - var stage = addStage(); - var layer = new Konva.Layer({ - scaleX: 10, - scaleY: 10, - }); - stage.add(layer); - - var rect = new Konva.Rect({ - x: 5, - y: 16, - draggable: true, - width: 10, - height: 10, - fill: 'yellow', - }); - layer.add(rect); - - var tr = new Konva.Transformer({ - nodes: [rect], - }); - layer.add(tr); - layer.draw(); - - var rotater = tr.findOne('.rotater'); - var pos = rotater.getAbsolutePosition(); - - // pos.x === (x * scaleX - (height)) - assert.equal(pos.x, 100); - - // pos.y === (y * scaleY - (height * scaleY / 2)) - assert.equal(pos.y, 110); - }); - - var tests = [ - { - name: 'top-left', - startPos: { - x: 0, - y: 0, - }, - endPos: { - x: 25, - y: 25, - }, - expectedWidth: 50, - expectedHeight: 50, - }, - { - name: 'top-center', - startPos: { - x: 50, - y: 0, - }, - endPos: { - x: 50, - y: 25, - }, - expectedWidth: 100, - expectedHeight: 50, - }, - { - name: 'top-right', - startPos: { - x: 100, - y: 0, - }, - endPos: { - x: 75, - y: 25, - }, - expectedWidth: 50, - expectedHeight: 50, - }, - { - name: 'middle-left', - startPos: { - x: 0, - y: 50, - }, - endPos: { - x: 25, - y: 50, - }, - expectedWidth: 50, - expectedHeight: 100, - }, - { - name: 'middle-right', - startPos: { - x: 100, - y: 50, - }, - endPos: { - x: 75, - y: 50, - }, - expectedWidth: 50, - expectedHeight: 100, - }, - { - name: 'bottom-left', - startPos: { - x: 0, - y: 100, - }, - endPos: { - x: 25, - y: 75, - }, - expectedWidth: 50, - expectedHeight: 50, - }, - { - name: 'bottom-center', - startPos: { - x: 50, - y: 100, - }, - endPos: { - x: 50, - y: 75, - }, - expectedWidth: 100, - expectedHeight: 50, - }, - { - name: 'bottom-right', - startPos: { - x: 100, - y: 100, - }, - endPos: { - x: 75, - y: 75, - }, - expectedWidth: 50, - expectedHeight: 50, - }, - // { - // name: 'top-left-reverse', - // startPos: { - // x: 0, - // y: 0 - // }, - // endPos: { - // x: 100, - // y: 100 - // }, - // expectedWidth: 100, - // expectedHeight: 100 - // } - ]; - - it('if alt is pressed should transform around center', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - draggable: true, - fill: 'yellow', - }); - layer.add(rect); - - var tr = new Konva.Transformer({ - nodes: [rect], - }); - layer.add(tr); - - tests.forEach(function (test) { - rect.setAttrs({ - x: 0, - y: 0, - width: 100, - height: 100, - scaleX: 1, - scaleY: 1, - }); - tr.update(); - - layer.draw(); - - simulateMouseDown(tr, test.startPos); - - var target = stage.getIntersection(test.startPos); - simulateMouseMove(tr, { - target: target, - x: test.endPos.x, - y: test.endPos.y, - altKey: true, - }); - - // here is duplicate, because transformer is listening window events - simulateMouseUp(tr, { - x: test.endPos.x, - y: test.endPos.y, - }); - su(stage, { - x: test.endPos.x, - y: test.endPos.y, - }); - layer.draw(); - - assertAlmostEqual(rect.width() * rect.scaleX(), test.expectedWidth); - assertAlmostEqual(rect.height() * rect.scaleY(), test.expectedHeight); - }); - }); - - it('centered scaling - no keep ratio', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - draggable: true, - fill: 'yellow', - }); - layer.add(rect); - - var tr = new Konva.Transformer({ - nodes: [rect], - centeredScaling: true, - keepRatio: false, - }); - layer.add(tr); - - tests.forEach(function (test) { - rect.setAttrs({ - x: 0, - y: 0, - width: 100, - height: 100, - scaleX: 1, - scaleY: 1, - }); - tr.update(); - - layer.draw(); - - simulateMouseDown(tr, test.startPos); - - var target = stage.getIntersection(test.startPos); - simulateMouseMove(tr, { - target: target, - x: test.endPos.x, - y: test.endPos.y, - }); - - // here is duplicate, because transformer is listening window events - simulateMouseUp(tr, { - x: test.endPos.x, - y: test.endPos.y, - }); - su(stage, { - x: test.endPos.x, - y: test.endPos.y, - }); - layer.draw(); - - assertAlmostEqual(rect.width() * rect.scaleX(), test.expectedWidth); - assertAlmostEqual(rect.height() * rect.scaleY(), test.expectedHeight); - }); - }); - - it('centered scaling', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - draggable: true, - fill: 'yellow', - }); - layer.add(rect); - - var tr = new Konva.Transformer({ - nodes: [rect], - centeredScaling: true, - }); - layer.add(tr); - - tests.forEach(function (test) { - rect.setAttrs({ - x: 0, - y: 0, - width: 100, - height: 100, - scaleX: 1, - scaleY: 1, - }); - tr.update(); - - layer.draw(); - - simulateMouseDown(tr, test.startPos); - - var target = stage.getIntersection(test.startPos); - simulateMouseMove(tr, { - target: target, - x: test.endPos.x, - y: test.endPos.y, - }); - - // here is duplicate, because transformer is listening window events - simulateMouseUp(tr, { - x: test.endPos.x, - y: test.endPos.y, - }); - su(stage, { - x: test.endPos.x, - y: test.endPos.y, - }); - layer.draw(); - - assertAlmostEqual(rect.width() * rect.scaleX(), test.expectedWidth); - assertAlmostEqual(rect.height() * rect.scaleY(), test.expectedHeight); - }); - }); - - it('centered scaling on flip + keep ratio', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - draggable: true, - fill: 'yellow', - }); - layer.add(rect); - - var tr = new Konva.Transformer({ - nodes: [rect], - centeredScaling: true, - keepRatio: true, - }); - layer.add(tr); - - // try to move mouse from edge corners into different directions - var tl = { x: 0, y: 0 }; - var trr = { x: 200, y: 0 }; - var bl = { x: 0, y: 100 }; - var br = { x: 200, y: 100 }; - - var tests = [ - [tl, trr], - [tl, bl], - [tl, br], - [trr, tl], - [trr, bl], - [trr, br], - - [bl, tl], - [bl, trr], - [bl, br], - - [br, tl], - [br, trr], - [br, bl], - ]; - tests.forEach((test) => { - var start = test[0]; - var end = test[1]; - rect.setAttrs({ - x: 0, - y: 0, - width: 200, - height: 100, - scaleX: 1, - scaleY: 1, - rotation: 0, - }); - layer.draw(); - - // move from start to end - simulateMouseDown(tr, start); - simulateMouseMove(tr, end); - var box = rect.getClientRect(); - assertAlmostEqual(box.x, 0); - assertAlmostEqual(box.y, 0); - assertAlmostEqual(box.width, 200); - assertAlmostEqual(box.height, 100); - - // make extra move on end - simulateMouseMove(tr, end); - var box = rect.getClientRect(); - assertAlmostEqual(box.x, 0); - assertAlmostEqual(box.y, 0); - assertAlmostEqual(box.width, 200); - assertAlmostEqual(box.height, 100); - - // move back - simulateMouseMove(tr, start); - simulateMouseUp(tr); - assertAlmostEqual(box.x, 0); - assertAlmostEqual(box.y, 0); - assertAlmostEqual(box.width, 200); - assertAlmostEqual(box.height, 100); - }); - }); - - it('transform scaled (in one direction) node', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - draggable: true, - x: 150, - y: 50, - width: 100, - height: 100, - scaleX: -1, - fillLinearGradientStartPoint: { x: 0, y: 0 }, - fillLinearGradientEndPoint: { x: 100, y: 100 }, - fillLinearGradientColorStops: [0, 'red', 0.8, 'yellow'], - }); - layer.add(rect); - - var tr = new Konva.Transformer({ - nodes: [rect], - }); - layer.add(tr); - - layer.draw(); - - simulateMouseDown(tr, { - x: 150, - y: 150, - }); - - var target = stage.getIntersection({ - x: 150, - y: 150, - }); - simulateMouseMove(tr, { - x: 100, - y: 100, - }); - - // here is duplicate, because transformer is listening window events - simulateMouseUp(tr, { - x: 100, - y: 100, - }); - su(stage, { - x: 100, - y: 100, - }); - layer.draw(); - - assert.equal(rect.width() * rect.scaleX() - 50 < 1, true, ' width check'); - assert.equal(rect.height() * rect.scaleY() + 50 < 1, true, ' height check'); - }); - - it('transformer should ignore shadow', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - x: 50, - y: 50, - draggable: true, - width: 100, - height: 100, - fill: 'yellow', - shadowBlur: 10, - }); - layer.add(rect); - - var tr = new Konva.Transformer({ - nodes: [rect], - }); - layer.add(tr); - layer.draw(); - - assert.equal(tr.x(), 50); - assert.equal(tr.y(), 50); - - assert.equal(tr.width(), 100); - assert.equal(tr.height(), 100); - - tr._fitNodesInto({ - x: 50, - y: 50, - width: 100, - height: 100, - rotation: 0, - }); - - assert.equal(rect.x(), 50); - assert.equal(rect.y(), 50); - - assert.equal(rect.width(), 100); - assert.equal(rect.height(), 100); - }); - - it.skip('transformer should skip scale on stroke if strokeScaleEnabled = false', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - x: 50, - y: 50, - draggable: true, - width: 10, - height: 10, - scaleX: 10, - scaleY: 10, - fill: 'yellow', - strokeWidth: 10, - stroke: 'red', - strokeScaleEnabled: false, - }); - layer.add(rect); - - var tr = new Konva.Transformer({ - nodes: [rect], - ignoreStroke: true, - }); - layer.add(tr); - layer.draw(); - - assert.equal(tr.x(), 50); - assert.equal(tr.y(), 50); - - assert.equal(tr.width(), 100); - assert.equal(tr.height(), 100); - - tr._fitNodesInto({ - x: 50, - y: 50, - width: 100, - height: 100, - rotation: 0, - }); - - assert.equal(rect.x(), 50); - assert.equal(rect.y(), 50); - - assert.equal(rect.width(), 100); - assert.equal(rect.height(), 100); - }); - - it.skip('check calculations when the size = 0', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - x: 50, - y: 50, - draggable: true, - // can we fit from empty width? - width: 0, - height: 100, - fill: 'yellow', - }); - layer.add(rect); - - var tr = new Konva.Transformer({ - nodes: [rect], - }); - layer.add(tr); - layer.draw(); - - tr._fitNodesInto({ - x: 50, - y: 50, - width: 100, - height: 100, - rotation: 0, - }); - layer.draw(); - assert.equal(rect.scaleX(), 1); - }); - - it('attrs change - arc', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var shape = new Konva.Arc({ - x: stage.width() / 2, - y: stage.height() / 2, - innerRadius: 40, - outerRadius: 70, - angle: 60, - fill: 'yellow', - stroke: 'black', - strokeWidth: 4, - }); - layer.add(shape); - - var tr = new Konva.Transformer({ - nodes: [shape], - }); - layer.add(tr); - - layer.draw(); - - shape.outerRadius(100); - - layer.draw(); - var rect = Konva.Util._assign({}, tr._getNodeRect()); - delete rect.rotation; - assert.deepEqual(shape.getClientRect(), rect); - layer.draw(); - }); - - it('attrs change - line', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var shape = new Konva.Arrow({ - x: stage.width() / 4, - y: stage.height() / 4, - points: [0, 0, stage.width() / 2, stage.height() / 2], - pointerLength: 20, - pointerWidth: 20, - fill: 'black', - stroke: 'black', - strokeWidth: 4, - draggable: true, - }); - layer.add(shape); - - var tr = new Konva.Transformer({ - nodes: [shape], - }); - layer.add(tr); - - layer.draw(); - - shape.points([10, 10, 100, 10]); - var rect = Konva.Util._assign({}, tr._getNodeRect()); - delete rect.rotation; - layer.draw(); - assert.deepEqual(shape.getClientRect(), rect); - - shape.strokeWidth(10); - var rect = Konva.Util._assign({}, tr._getNodeRect()); - delete rect.rotation; - layer.draw(); - assert.deepEqual(shape.getClientRect(), rect); - }); - - it('attrs change - circle', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var shape = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 40, - fill: 'yellow', - stroke: 'black', - strokeWidth: 4, - }); - layer.add(shape); - - var tr = new Konva.Transformer({ - nodes: [shape], - }); - layer.add(tr); - - shape.radius(100); - layer.draw(); - var rect = Konva.Util._assign({}, tr._getNodeRect()); - delete rect.rotation; - assert.deepEqual(shape.getClientRect(), rect); - }); - - it('attrs change - ellipse', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var shape = new Konva.Ellipse({ - x: stage.width() / 2, - y: stage.height() / 2, - radiusX: 100, - radiusY: 50, - fill: 'yellow', - stroke: 'black', - strokeWidth: 4, - }); - layer.add(shape); - - var tr = new Konva.Transformer({ - nodes: [shape], - }); - layer.add(tr); - - shape.radiusX(120); - - layer.draw(); - var rect = Konva.Util._assign({}, tr._getNodeRect()); - delete rect.rotation; - assert.deepEqual(shape.getClientRect(), rect); - - shape.radiusY(100); - var rect = Konva.Util._assign({}, tr._getNodeRect()); - delete rect.rotation; - assert.deepEqual(shape.getClientRect(), rect); - - layer.draw(); - }); - - it('attrs change - rect', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var shape = new Konva.Rect({ - x: stage.width() / 2, - y: stage.height() / 2, - width: 100, - height: 100, - fill: 'yellow', - stroke: 'black', - strokeWidth: 4, - }); - layer.add(shape); - - var tr = new Konva.Transformer({ - nodes: [shape], - }); - layer.add(tr); - - shape.width(120); - - layer.draw(); - var rect = Konva.Util._assign({}, tr._getNodeRect()); - delete rect.rotation; - assert.deepEqual(shape.getClientRect(), rect); - - shape.height(110); - var rect = Konva.Util._assign({}, tr._getNodeRect()); - delete rect.rotation; - assert.deepEqual(shape.getClientRect(), rect); - - layer.draw(); - }); - - it('attrs change - path', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var shape = new Konva.Path({ - x: 50, - y: 40, - data: 'M12.582,9.551C3.251,16.237,0.921,29.021,7.08,38.564l-2.36,1.689l4.893,2.262l4.893,2.262l-0.568-5.36l-0.567-5.359l-2.365,1.694c-4.657-7.375-2.83-17.185,4.352-22.33c7.451-5.338,17.817-3.625,23.156,3.824c5.337,7.449,3.625,17.813-3.821,23.152l2.857,3.988c9.617-6.893,11.827-20.277,4.935-29.896C35.591,4.87,22.204,2.658,12.582,9.551z', - fill: 'green', - }); - layer.add(shape); - - var tr = new Konva.Transformer({ - nodes: [shape], - }); - layer.add(tr); - - shape.data('M200,100h100v50z'); - layer.draw(); - - var rect = Konva.Util._assign({}, tr._getNodeRect()); - delete rect.rotation; - assert.deepEqual(shape.getClientRect(), rect); - }); - - it('attrs change - regular polygon', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var shape = new Konva.RegularPolygon({ - x: 100, - y: 150, - sides: 6, - radius: 70, - fill: 'red', - stroke: 'black', - strokeWidth: 4, - }); - layer.add(shape); - - var tr = new Konva.Transformer({ - nodes: [shape], - }); - layer.add(tr); - - shape.radius(100); - layer.draw(); - - var rect = Konva.Util._assign({}, tr._getNodeRect()); - delete rect.rotation; - assert.deepEqual(shape.getClientRect(), rect); - }); - - it('attrs change - ring', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var shape = new Konva.Ring({ - x: stage.width() / 2, - y: stage.height() / 2, - innerRadius: 40, - outerRadius: 70, - fill: 'yellow', - stroke: 'black', - strokeWidth: 4, - }); - layer.add(shape); - - var tr = new Konva.Transformer({ - nodes: [shape], - }); - layer.add(tr); - - shape.outerRadius(100); - - layer.draw(); - var rect = Konva.Util._assign({}, tr._getNodeRect()); - delete rect.rotation; - assert.deepEqual(shape.getClientRect(), rect); - - shape.innerRadius(200); - var rect = Konva.Util._assign({}, tr._getNodeRect()); - delete rect.rotation; - assert.deepEqual(shape.getClientRect(), rect); - - layer.draw(); - }); - - it('attrs change - star', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var shape = new Konva.Star({ - x: stage.width() / 2, - y: stage.height() / 2, - numPoints: 6, - innerRadius: 40, - outerRadius: 70, - fill: 'yellow', - stroke: 'black', - strokeWidth: 4, - }); - layer.add(shape); - - var tr = new Konva.Transformer({ - nodes: [shape], - }); - layer.add(tr); - - shape.outerRadius(100); - - layer.draw(); - var rect = Konva.Util._assign({}, tr._getNodeRect()); - delete rect.rotation; - assert.deepEqual(shape.getClientRect(), rect); - - shape.innerRadius(200); - var rect = Konva.Util._assign({}, tr._getNodeRect()); - delete rect.rotation; - assert.deepEqual(shape.getClientRect(), rect); - - layer.draw(); - }); - - it('attrs change - wedge', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var shape = new Konva.Wedge({ - x: stage.width() / 2, - y: stage.height() / 2, - radius: 70, - angle: 60, - fill: 'red', - stroke: 'black', - strokeWidth: 4, - }); - layer.add(shape); - - var tr = new Konva.Transformer({ - nodes: [shape], - }); - layer.add(tr); - - shape.radius(100); - layer.draw(); - var rect = Konva.Util._assign({}, tr._getNodeRect()); - delete rect.rotation; - assert.deepEqual(shape.getClientRect(), rect); - }); - - it('attrs change - text', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var shape = new Konva.Text({ - x: stage.width() / 2, - y: 15, - text: 'Simple Text', - fontSize: 60, - fontFamily: 'Arial', - fill: 'green', - }); - layer.add(shape); - - var tr = new Konva.Transformer({ - nodes: [shape], - }); - layer.add(tr); - - shape.text('Simple'); - layer.draw(); - var rect = Konva.Util._assign({}, tr._getNodeRect()); - delete rect.rotation; - assert.deepEqual(shape.getClientRect(), rect, 'change text'); - - shape.fontSize(30); - layer.draw(); - var rect = Konva.Util._assign({}, tr._getNodeRect()); - delete rect.rotation; - assert.deepEqual(shape.getClientRect(), rect, 'change font size'); - - shape.padding(10); - layer.draw(); - var rect = Konva.Util._assign({}, tr._getNodeRect()); - delete rect.rotation; - assert.deepEqual(shape.getClientRect(), rect, 'change padding'); - - shape.lineHeight(2); - layer.draw(); - var rect = Konva.Util._assign({}, tr._getNodeRect()); - delete rect.rotation; - assert.deepEqual(shape.getClientRect(), rect, 'change line height'); - - shape.width(30); - layer.draw(); - var rect = Konva.Util._assign({}, tr._getNodeRect()); - delete rect.rotation; - assert.deepEqual(shape.getClientRect(), rect), 'change width'; - - shape.height(30); - layer.draw(); - var rect = Konva.Util._assign({}, tr._getNodeRect()); - delete rect.rotation; - assert.deepEqual(shape.getClientRect(), rect, 'change height'); - }); - - it('attrs change - text path', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var shape = new Konva.TextPath({ - x: 0, - y: 50, - fill: '#333', - fontSize: 16, - fontFamily: 'Arial', - text: "All the world's a stage, and all the men and women merely players.", - data: 'M10,10 C0,0 10,150 100,100 S300,150 400,50', - }); - layer.add(shape); - - var tr = new Konva.Transformer({ - nodes: [shape], - }); - layer.add(tr); - - shape.text('Simple'); - layer.draw(); - var rect = Konva.Util._assign({}, tr._getNodeRect()); - delete rect.rotation; - assert.deepEqual(shape.getClientRect(), rect, 'change text'); - - shape.fontSize(30); - layer.draw(); - var rect = Konva.Util._assign({}, tr._getNodeRect()); - delete rect.rotation; - assert.deepEqual(shape.getClientRect(), rect, 'change font size'); - - shape.data('M10,10 C0,0 10,150 100,100 S300,150 400,50'); - layer.draw(); - var rect = Konva.Util._assign({}, tr._getNodeRect()); - delete rect.rotation; - assert.deepEqual(shape.getClientRect(), rect), 'change data'; - }); - - it('make sure transformer events are not cloned', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect1 = new Konva.Rect({ - x: stage.width() / 5, - y: stage.height() / 5, - width: 50, - height: 50, - fill: 'green', - draggable: true, - }); - - layer.add(rect1); - - var tr1 = new Konva.Transformer({ - nodes: [rect1], - }); - layer.add(tr1); - - var rect2 = rect1.clone({ - fill: 'red', - x: stage.width() / 3, - y: stage.height() / 3, - }); - layer.add(rect2); - - tr1.destroy(); - - var tr2 = new Konva.Transformer({ - nodes: [rect2], - }); - layer.add(tr2); - - // should not throw error - rect2.width(100); - - assertAlmostEqual(tr2.width(), 100); - - stage.draw(); - }); - - it('try to move anchor on scaled with css stage', function () { - if (isNode) { - return; - } - var stage = addStage(); - stage.container().style.transform = 'scale(0.5)'; - stage.container().style.transformOrigin = 'top left'; - - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - x: 0, - y: 0, - draggable: true, - width: 100, - height: 100, - fill: 'yellow', - }); - layer.add(rect); - - var tr = new Konva.Transformer({ - nodes: [rect], - keepRatio: false, - }); - layer.add(tr); - layer.draw(); - - sm(stage, { - x: 50, - y: 50, - }); - simulateMouseDown(tr, { - x: 50, - y: 50, - }); - - var target = stage.getIntersection({ - x: 50, - y: 50, - }); - simulateMouseMove(tr, { - x: 100, - y: 50, - }); - - // here is duplicate, because transformer is listening window events - simulateMouseUp(tr, { - x: 100, - y: 50, - }); - su(stage, { - x: 100, - y: 50, - }); - - assertAlmostEqual(rect.width() * rect.scaleX(), 200); - }); - - it('rotate several nodes', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect1 = new Konva.Rect({ - x: 50, - y: 50, - draggable: true, - width: 50, - height: 50, - fill: 'yellow', - }); - layer.add(rect1); - - var rect2 = new Konva.Rect({ - x: 100, - y: 100, - draggable: true, - width: 50, - height: 50, - fill: 'red', - }); - - layer.add(rect2); - - var tr = new Konva.Transformer({ - nodes: [rect1, rect2], - }); - layer.add(tr); - layer.draw(); - - tr._fitNodesInto({ - x: 100, - y: 0, - width: 100, - height: 100, - rotation: Konva.getAngle(90), - }); - - layer.draw(); - - assertAlmostEqual(rect1.x(), 100); - assertAlmostEqual(rect1.y(), 0); - assertAlmostEqual(rect1.width() + rect2.width(), 100); - assertAlmostEqual(rect1.height() + rect2.width(), 100); - assertAlmostEqual(rect1.rotation(), 90); - - assertAlmostEqual(rect2.x(), 50); - assertAlmostEqual(rect2.y(), 50); - assertAlmostEqual(rect2.width() + rect2.width(), 100); - assertAlmostEqual(rect2.height() + rect2.width(), 100); - assertAlmostEqual(tr.rotation(), 90); - - tr._fitNodesInto({ - x: 100, - y: 100, - width: 100, - height: 100, - rotation: Konva.getAngle(180), - }); - - assertAlmostEqual(tr.x(), rect1.x()); - assertAlmostEqual(tr.y(), rect1.y()); - assertAlmostEqual(tr.width(), rect1.width() + rect2.width()); - assertAlmostEqual(tr.height(), rect1.height() + rect2.width()); - assertAlmostEqual(tr.rotation(), 180); - }); - - it('events on several nodes', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect1 = new Konva.Rect({ - x: 100, - y: 60, - draggable: true, - width: 100, - height: 100, - fill: 'yellow', - }); - layer.add(rect1); - var rect2 = new Konva.Rect({ - x: 100, - y: 60, - draggable: true, - width: 100, - height: 100, - fill: 'yellow', - }); - layer.add(rect2); - - var transformstart = 0; - var transform = 0; - var transformend = 0; - - rect1.on('transformstart', function () { - transformstart += 1; - }); - rect1.on('transform', function () { - transform += 1; - }); - rect1.on('transformend', function () { - transformend += 1; - }); - - rect2.on('transformstart', function () { - transformstart += 1; - }); - rect2.on('transform', function () { - transform += 1; - }); - rect2.on('transformend', function () { - transformend += 1; - }); - - var tr = new Konva.Transformer({ - nodes: [rect1, rect2], - }); - layer.add(tr); - - layer.draw(); - - simulateMouseDown(tr, { - x: 100, - y: 60, - }); - - simulateMouseMove(tr, { - x: 105, - y: 60, - }); - - simulateMouseUp(tr, { - x: 105, - y: 60, - }); - - assert.equal(transformstart, 2); - assert.equal(transform, 2); - assert.equal(transformend, 2); - }); - - it('transform several rotated nodes', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect1 = new Konva.Rect({ - x: 50, - y: 50, - draggable: true, - width: 50, - height: 50, - fill: 'blue', - rotation: 45, - }); - layer.add(rect1); - - var rect2 = new Konva.Rect({ - x: 100, - y: 100, - draggable: true, - width: 50, - height: 50, - fill: 'red', - rotation: 120, - }); - - layer.add(rect2); - - var tr = new Konva.Transformer({ - nodes: [rect1, rect2], - }); - layer.add(tr); - layer.draw(); - - tr._fitNodesInto({ - x: 100, - y: 0, - width: 100, - height: 100, - rotation: Konva.getAngle(90), - }); - - layer.draw(); - - assertAlmostEqual(rect1.x(), 100); - assertAlmostEqual(rect1.y(), 41.421356237309496); - assertAlmostEqual(rect1.width() + rect2.width(), 100); - assertAlmostEqual(rect1.height() + rect2.width(), 100); - assertAlmostEqual(rect1.rotation(), 132.45339125826706); - - assertAlmostEqual(rect2.x(), 46.41016151377549); - assertAlmostEqual(rect2.y(), 100); - assertAlmostEqual(rect2.width() + rect2.width(), 100); - assertAlmostEqual(rect2.height() + rect2.width(), 100); - assertAlmostEqual(tr.rotation(), 90); - - tr._fitNodesInto({ - x: 100, - y: 100, - width: 100, - height: 100, - rotation: Konva.getAngle(180), - }); - - assertAlmostEqual(tr.x(), 100); - assertAlmostEqual(tr.y(), 100); - }); - - it('drag several nodes', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect1 = new Konva.Rect({ - x: 50, - y: 50, - draggable: true, - width: 50, - height: 50, - fill: 'yellow', - }); - layer.add(rect1); - - var rect2 = new Konva.Rect({ - x: 100, - y: 100, - draggable: true, - width: 50, - height: 50, - fill: 'red', - }); - - layer.add(rect2); - - var dragstart = 0; - var dragmove = 0; - var dragend = 0; - - rect2.on('dragstart', () => { - dragstart += 1; - }); - rect2.on('dragmove', () => { - dragmove += 1; - }); - rect2.on('dragend', () => { - dragend += 1; - }); - - var tr = new Konva.Transformer({ - nodes: [rect1, rect2], - }); - - // make sure drag also triggers on the transformer. - tr.on('dragstart', (e) => { - assert.equal(!!e.evt, true); - dragstart += 1; - }); - tr.on('dragmove', () => { - dragmove += 1; - }); - tr.on('dragend', () => { - dragend += 1; - }); - - // also drag should bubble to stage - // two times for two rects - stage.on('dragstart', (e) => { - assert.equal(!!e.evt, true); - dragstart += 1; - }); - - layer.add(tr); - layer.draw(); - - simulateMouseDown(tr, { - x: 75, - y: 75, - }); - sm(stage, { - x: 80, - y: 80, - }); - - sm(stage, { - x: 85, - y: 85, - }); - - su(stage, { - x: 80, - y: 80, - }); - - // proxy drag to other nodes - assert.equal(rect2.x(), 110); - assert.equal(rect2.y(), 110); - assert.equal(dragstart, 4, 'dragstart'); - assert.equal(dragmove, 3, 'dragmove'); - assert.equal(dragend, 2, 'dragend'); - }); - - it('reattach from several and drag one', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect1 = new Konva.Rect({ - x: 50, - y: 50, - draggable: true, - width: 50, - height: 50, - fill: 'yellow', - }); - layer.add(rect1); - - var rect2 = new Konva.Rect({ - x: 100, - y: 100, - draggable: true, - width: 50, - height: 50, - fill: 'red', - }); - - layer.add(rect2); - - var tr = new Konva.Transformer({ - nodes: [rect1, rect2], - }); - layer.add(tr); - layer.draw(); - - tr.nodes([rect1]); - - // now drag just the first - simulateMouseDown(tr, { - x: 125, - y: 125, - }); - sm(stage, { - x: 130, - y: 130, - }); - - su(stage, { - x: 130, - y: 130, - }); - - // no changes on the second - assert.equal(rect1.x(), 50); - assert.equal(rect1.y(), 50); - }); - - it('transformer should not hide shapes', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect1 = new Konva.Rect({ - x: 50, - y: 50, - draggable: true, - width: 50, - height: 50, - fill: 'yellow', - }); - layer.add(rect1); - - var click = 0; - - rect1.on('click', () => { - click += 1; - }); - - var tr = new Konva.Transformer({ - nodes: [rect1], - }); - layer.add(tr); - layer.draw(); - - simulateMouseDown(tr, { - x: 75, - y: 75, - }); - sm(stage, { - x: 75, - y: 75, - }); - - su(stage, { - x: 75, - y: 75, - }); - - // proxy drag to other nodes - assert.equal(click, 1); - }); - - it('drag several nodes by transformer back', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect1 = new Konva.Rect({ - x: 50, - y: 50, - draggable: true, - width: 50, - height: 50, - fill: 'yellow', - }); - layer.add(rect1); - - var rect2 = new Konva.Rect({ - x: 100, - y: 100, - draggable: true, - width: 50, - height: 50, - fill: 'red', - }); - - layer.add(rect2); - - var dragstart = 0; - var dragmove = 0; - var dragend = 0; - - rect1.on('dragstart', () => { - dragstart += 1; - }); - rect1.on('dragmove', () => { - dragmove += 1; - }); - rect1.on('dragend', () => { - dragend += 1; - }); - rect2.on('dragstart', () => { - dragstart += 1; - }); - rect2.on('dragmove', () => { - dragmove += 1; - }); - rect2.on('dragend', () => { - dragend += 1; - }); - - var tr = new Konva.Transformer({ - nodes: [rect1, rect2], - shouldOverdrawWholeArea: true, - }); - - tr.on('dragstart', () => { - dragstart += 1; - }); - tr.on('dragmove', () => { - dragmove += 1; - }); - tr.on('dragend', () => { - dragend += 1; - }); - layer.add(tr); - layer.draw(); - - simulateMouseDown(tr, { - x: 110, - y: 90, - }); - - // move mouse twice - // because first move will jus trigger start dragging - sm(stage, { - x: 120, - y: 90, - }); - sm(stage, { - x: 120, - y: 90, - }); - - su(stage, { - x: 120, - y: 90, - }); - - // proxy drag to other nodes - assert.equal(rect1.x(), 60); - assert.equal(rect1.y(), 50); - assert.equal(rect2.x(), 110); - assert.equal(rect2.y(), 100); - assert.equal(dragstart, 3, 'dragstart'); - assert.equal(dragmove, 3, 'dragmove'); - assert.equal(dragend, 3, 'dragend'); - }); - - it('reattach to several nodes', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect1 = new Konva.Rect({ - x: 50, - y: 50, - draggable: true, - width: 50, - height: 50, - fill: 'yellow', - }); - layer.add(rect1); - - var rect2 = new Konva.Rect({ - x: 100, - y: 100, - draggable: true, - width: 50, - height: 50, - fill: 'red', - }); - - layer.add(rect2); - - var tr = new Konva.Transformer({ - nodes: [rect1, rect2], - }); - layer.add(tr); - layer.draw(); - - tr._fitNodesInto({ - x: 100, - y: 0, - width: 100, - height: 100, - rotation: Konva.getAngle(90), - }); - - assertAlmostEqual(tr.x(), rect1.x()); - assertAlmostEqual(tr.y(), rect1.y()); - assertAlmostEqual(tr.width(), rect1.width() + rect2.width()); - assertAlmostEqual(tr.height(), rect1.height() + rect2.width()); - assertAlmostEqual(tr.rotation(), 90); - layer.draw(); - - tr.nodes([rect1, rect2]); - - assertAlmostEqual(tr.x(), 0); - assertAlmostEqual(tr.y(), 0); - assertAlmostEqual(tr.width(), rect1.width() + rect2.width()); - assertAlmostEqual(tr.height(), rect1.height() + rect2.width()); - assertAlmostEqual(tr.rotation(), 0); - }); - - it('rotate several nodes inside different parents', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect1 = new Konva.Rect({ - x: 0, - y: 0, - draggable: true, - width: 50, - height: 50, - fill: 'yellow', - }); - layer.add(rect1); - - var group = new Konva.Group({ - x: 50, - scaleX: 2, - }); - - layer.add(group); - - var rect2 = new Konva.Rect({ - x: 0, - y: 50, - draggable: true, - width: 25, - height: 50, - fill: 'red', - }); - group.add(rect2); - - var tr = new Konva.Transformer({ - nodes: [rect1, rect2], - }); - layer.add(tr); - layer.draw(); - - assert.equal(tr.x(), 0); - assert.equal(tr.y(), 0); - assert.equal(tr.width(), 100); - assert.equal(tr.height(), 100); - assert.equal(tr.rotation(), 0); - - // fit into the same area - const box = { - x: 0, - y: 0, - width: 100, - height: 100, - rotation: 0, - }; - - tr._fitNodesInto(box); - - var newBox = tr._getNodeRect(); - - assertAlmostEqual(box.x, newBox.x); - assertAlmostEqual(box.y, newBox.y); - assertAlmostEqual(box.width, newBox.width); - assertAlmostEqual(box.height, newBox.height); - assertAlmostEqual(box.rotation, newBox.rotation); - - assertAlmostEqual(rect1.x(), 0); - assertAlmostEqual(rect1.y(), 0); - assertAlmostEqual(rect1.width(), 50); - assertAlmostEqual(rect1.height(), 50); - assertAlmostEqual(rect1.rotation(), 0); - - assertAlmostEqual(rect2.x(), 0); - assertAlmostEqual(rect2.y(), 50); - assertAlmostEqual(rect2.width(), 25); - assertAlmostEqual(rect2.height(), 50); - assertAlmostEqual(rect2.rotation(), 0); - }); - - it('can attach transformer into several nodes and fit into negative scale', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect1 = new Konva.Rect({ - x: 0, - y: 0, - draggable: true, - width: 50, - height: 50, - fill: 'yellow', - }); - layer.add(rect1); - - var rect2 = new Konva.Rect({ - x: 50, - y: 50, - draggable: true, - width: 50, - height: 50, - fill: 'red', - }); - - layer.add(rect2); - - var tr = new Konva.Transformer({ - nodes: [rect1, rect2], - }); - layer.add(tr); - - tr._fitNodesInto({ - x: 100, - y: 0, - width: 0, - height: 100, - rotation: 0, - }); - - tr._fitNodesInto({ - x: 100, - y: 0, - width: -100, - height: 100, - rotation: 0, - }); - - layer.draw(); - assertAlmostEqual(Math.round(tr.x()), 0); - assertAlmostEqual(Math.round(tr.y()), 0); - assertAlmostEqual(tr.width(), rect1.width() + rect2.width()); - assertAlmostEqual(tr.height(), rect1.height() + rect2.height()); - assertAlmostEqual(tr.rotation(), 0); - }); - - it('boundBoxFox should work in absolute coordinates', function () { - var stage = addStage(); - var layer = new Konva.Layer({ - x: 10, - y: 10, - scaleX: 2, - scaleY: 2, - }); - stage.add(layer); - - var rect1 = new Konva.Rect({ - x: 0, - y: 0, - draggable: true, - width: 50, - height: 50, - fill: 'yellow', - }); - layer.add(rect1); - - var rect2 = new Konva.Rect({ - x: 50, - y: 50, - draggable: true, - width: 50, - height: 50, - fill: 'red', - }); - - layer.add(rect2); - - var callCount = 0; - var tr = new Konva.Transformer({ - nodes: [rect1, rect2], - boundBoxFunc: function (oldBox, newBox) { - callCount += 1; - assert.deepEqual(oldBox, { - x: 10, - y: 10, - width: 200, - height: 200, - rotation: 0, - }); - assert.deepEqual(newBox, { - x: 10, - y: 10, - width: 300, - height: 200, - rotation: 0, - }); - return newBox; - }, - }); - layer.add(tr); - - tr._fitNodesInto({ - x: 10, - y: 10, - width: 300, - height: 200, - rotation: 0, - }); - assert.equal(callCount, 1); - }); - - // TODO: move to manual tests - it.skip('performance check - drag several nodes', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - for (var i = 0; i < 500; i++) { - var shape = new Konva.Circle({ - x: 100, - y: 100, - radius: 50, - fill: 'red', - draggable: true, - }); - layer.add(shape); - } - var shapes = layer.find('Circle'); - var tr = new Konva.Transformer({ - nodes: shapes, - }); - layer.add(tr); - layer.draw(); - - throw 1; - }); - - // we don't support height = 0 - it.skip('try to transform zero size shape', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var shape = new Konva.Line({ - x: stage.width() / 4, - y: stage.height() / 4, - points: [0, 0, 200, 0], - fill: 'black', - stroke: 'black', - strokeWidth: 4, - draggable: true, - }); - layer.add(shape); - - var tr = new Konva.Transformer({ - nodes: [shape], - enabledAnchors: ['middle-left', 'middle-right'], - ignoreStroke: true, - }); - layer.add(tr); - - layer.draw(); - - simulateMouseDown(tr, { - x: stage.width() / 2, - y: stage.height() / 2, - }); - simulateMouseDown(tr, { - x: stage.width() / 2 + 100, - y: stage.height() / 2, - }); - simulateMouseUp(tr); - assert.equal(shape.scaleX(), 0.5); - }); - - it('check transform cache', function () { - var stage = addStage({ scaleX: 0.5, scaleY: 0.5 }); - var layer = new Konva.Layer(); - stage.add(layer); - - var textNode = new Konva.Text({ - text: 'Some text here', - x: 300, - y: 100, - fontSize: 20, - draggable: true, - width: 200, - }); - - var tr = new Konva.Transformer({ - nodes: [textNode], - enabledAnchors: [ - 'top-left', - 'top-right', - 'bottom-left', - 'bottom-right', - 'middle-left', - 'middle-right', - ], - boundBoxFunc: function (oldBox, newBox) { - if (newBox.width < 5 || newBox.height < 5 || newBox.width > 1000) { - return oldBox; - } - return newBox; - }, - }); - - layer.add(tr); - layer.add(textNode); - - assert.equal(textNode.getClientRect().width, 100); - }); - - // ====================================================== - it('init transformer on simple rectangle', function () { - var stage = addStage(); - stage.rotation(45); - - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - x: 100, - y: 60, - draggable: true, - width: 100, - height: 100, - fill: 'yellow', - rotation: 45, - }); - layer.add(rect); - - var tr = new Konva.Transformer({ - useSingleNodeRotation: false, - nodes: [rect], - }); - layer.add(tr); - - layer.draw(); - assert.equal(tr.getClassName(), 'Transformer'); - - assert.equal(tr.rotation(), 0); - }); - - it('use several transformers on a single node', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - x: 100, - y: 60, - draggable: true, - width: 100, - height: 100, - fill: 'yellow', - }); - layer.add(rect); - - var tr1 = new Konva.Transformer({ - nodes: [rect], - }); - layer.add(tr1); - - var tr2 = new Konva.Transformer({ - nodes: [rect], - }); - layer.add(tr2); - - // detach tr1 - tr1.nodes([]); - - // update rect - rect.setAttrs({ x: 0, y: 0, width: 50, height: 50 }); - - // it should update second transformer - assert.equal(tr2.x(), rect.x()); - assert.equal(tr2.y(), rect.y()); - assert.equal(tr2.width(), rect.width()); - assert.equal(tr2.height(), rect.height()); - }); - it('detached transformer should not affect client rect', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - x: 100, - y: 60, - draggable: true, - width: 100, - height: 100, - fill: 'yellow', - }); - layer.add(rect); - - var tr = new Konva.Transformer({ - nodes: [], - }); - layer.add(tr); - - const layerClientRect = layer.getClientRect(); - const rectClientRect = rect.getClientRect(); - - // the client rect should not be affected by the transformer - assert.deepEqual(layerClientRect, rectClientRect); - }); - it('attached transformer should affect client rect', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - x: 100, - y: 60, - draggable: true, - width: 100, - height: 100, - fill: 'yellow', - }); - layer.add(rect); - - var tr = new Konva.Transformer({ - nodes: [rect], - }); - layer.add(tr); - - const layerClientRect = layer.getClientRect(); - const rectClientRect = rect.getClientRect(); - const trClientRect = tr.getClientRect(); - - // the client rect should be affecte by the transformer - assert.notDeepEqual(layerClientRect, rectClientRect); - assert.deepEqual(layerClientRect, trClientRect); - }); - - it('cloning of transformer should double create child elements', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - x: 100, - y: 60, - draggable: true, - width: 100, - height: 100, - fill: 'yellow', - }); - layer.add(rect); - - var tr = new Konva.Transformer({ - nodes: [rect], - }); - layer.add(tr); - - const clone = tr.clone(); - assert.equal(clone.getChildren().length, tr.getChildren().length); - assert.equal(clone.nodes().length, 0); - }); - - it('should filter parent of the transformer', function () { - const stage = addStage(); - - const layer = new Konva.Layer(); - stage.add(layer); - - const tr = new Konva.Transformer(); - layer.add(tr); - - tr.nodes([layer]); - assert.equal(tr.nodes().length, 0); - }); - - it('anchorStyleFunc', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - x: 100, - y: 60, - draggable: true, - width: 100, - height: 100, - fill: 'yellow', - }); - layer.add(rect); - - var tr = new Konva.Transformer({ - nodes: [rect], - }); - layer.add(tr); - // manual check of correct position of node - var handler = tr.findOne('.bottom-right'); - assert.equal(handler.fill(), 'white'); - - tr.anchorStyleFunc((anchor) => { - if (anchor.hasName('bottom-right')) { - anchor.fill('red'); - } - }); - assert.equal(handler.fill(), 'red'); - tr.anchorStyleFunc(null); - assert.equal(handler.fill(), 'white'); - }); - - it('flip rectangle', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - draggable: true, - x: 150, - y: 50, - width: 100, - height: 100, - fillLinearGradientStartPoint: { x: -50, y: -50 }, - fillLinearGradientEndPoint: { x: 50, y: 50 }, - fillLinearGradientColorStops: [0, 'red', 1, 'yellow'], - }); - layer.add(rect); - - var tr = new Konva.Transformer({ - nodes: [rect], - flipEnabled: false, - }); - layer.add(tr); - - layer.draw(); - - simulateMouseDown(tr, { - x: 150, - y: 50, - }); - simulateMouseMove(tr, { - x: 250, - y: 50, - }); - simulateMouseMove(tr, { - x: 350, - y: 50, - }); - - simulateMouseUp(tr, { - x: 350, - y: 50, - }); - - layer.draw(); - - assertAlmostEqual(rect.x(), 250); - assertAlmostEqual(rect.y(), 50); - assertAlmostEqual(rect.scaleX(), 1); - assertAlmostEqual(rect.scaleY(), 1); - }); - - it('should be able to prevent rotation in transform event', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - x: 55, - y: 55, - draggable: true, - width: 100, - height: 100, - fill: 'yellow', - }); - layer.add(rect); - - var tr = new Konva.Transformer({ - nodes: [rect], - }); - layer.add(tr); - layer.draw(); - - tr.on('transform', function (e) { - tr.rotation(0); - }); - - simulateMouseDown(tr, { - x: 100, - y: 2, - }); - simulateMouseMove(tr, { - x: 110, - y: 2, - }); - assert.equal(tr.rotation(), 0); - simulateMouseUp(tr, { x: 110, y: 2 }); - }); - - it('skip render on hit graph while transforming', function (done) { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - x: 55, - y: 55, - draggable: true, - width: 100, - height: 100, - fill: 'yellow', - }); - layer.add(rect); - - var tr = new Konva.Transformer({ - nodes: [rect], - }); - layer.add(tr); - layer.draw(); - - simulateMouseDown(tr, { - x: 100, - y: 2, - }); - simulateMouseMove(tr, { - x: 110, - y: 2, - }); - let shape = layer.getIntersection({ - x: 100, - y: 100, - }); - assert.equal(shape, null); - simulateMouseUp(tr, { x: 110, y: 2 }); - layer.draw(); - shape = layer.getIntersection({ - x: 100, - y: 100, - }); - assert.equal(shape, rect); - // reset position - rect.setAttrs({ - x: 50, - y: 50, - draggable: true, - width: 100, - height: 100, - }); - - tr.nodes([rect]); - layer.draw(); - // now check if graph is visible back when we moved a bit - simulateMouseDown(tr, { - x: 100, - y: 2, - }); - simulateMouseMove(tr, { - x: 110, - y: 2, - }); - setTimeout(() => { - shape = layer.getIntersection({ - x: 100, - y: 100, - }); - assert.equal(shape, null); - simulateMouseUp(tr, { x: 110, y: 2 }); - setTimeout(() => { - shape = layer.getIntersection({ - x: 100, - y: 100, - }); - assert.equal(shape, rect); - done(); - }, 100); - }, 100); - }); - - it('enable hit graph back on transformer destroy', function (done) { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var rect = new Konva.Rect({ - x: 55, - y: 55, - draggable: true, - width: 100, - height: 100, - fill: 'yellow', - }); - layer.add(rect); - - var tr = new Konva.Transformer({ - nodes: [rect], - }); - layer.add(tr); - layer.draw(); - - // now check if graph is visible back when we moved a bit - simulateMouseDown(tr, { - x: 100, - y: 2, - }); - simulateMouseMove(tr, { - x: 110, - y: 2, - }); - setTimeout(() => { - tr.destroy(); - setTimeout(() => { - var shape = layer.getIntersection({ - x: 100, - y: 100, - }); - assert.equal(shape, rect); - done(); - }, 100); - }, 100); - }); -}); diff --git a/test/unit/Tween-test.ts b/test/unit/Tween-test.ts deleted file mode 100644 index 838f1e131..000000000 --- a/test/unit/Tween-test.ts +++ /dev/null @@ -1,410 +0,0 @@ -import { assert } from 'chai'; - -import { addStage, Konva } from './test-utils'; - -describe('Tween', function () { - // ====================================================== - it('tween node', function (done) { - var stage = addStage(); - - var layer = new Konva.Layer(); - - var circle = new Konva.Circle({ - x: 100, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'blue', - strokeWidth: 4, - }); - - layer.add(circle); - stage.add(layer); - - var finishCount = 0; - var onFinish = function () { - assert(++finishCount <= 1, 'finishCount should not exceed 1'); - done(); - }; - - var tweens = 0; - var attrs = 0; - - for (var key in Konva.Tween.tweens) { - tweens++; - } - for (var key in Konva.Tween.attrs) { - attrs++; - } - - assert.equal(tweens, 0); - assert.equal(attrs, 0); - - var tween = new Konva.Tween({ - node: circle, - duration: 0.2, - x: 200, - y: 100, - onFinish: onFinish, - }).play(); - - var tweens = 0; - var attrs = 0; - for (var key in Konva.Tween.tweens) { - tweens++; - } - for (var key in Konva.Tween.attrs[circle._id][tween._id]) { - attrs++; - } - - assert.equal(tweens, 1); - assert.equal(attrs, 2); - - assert.notEqual(Konva.Tween.attrs[circle._id][tween._id].x, undefined); - assert.notEqual(Konva.Tween.attrs[circle._id][tween._id].y, undefined); - }); - - // ====================================================== - it('destroy tween while tweening', function () { - var stage = addStage(); - - var layer = new Konva.Layer(); - - var circle = new Konva.Circle({ - x: 100, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'blue', - strokeWidth: 4, - }); - - layer.add(circle); - stage.add(layer); - - var tween = new Konva.Tween({ - node: circle, - duration: 0.2, - x: 200, - y: 100, - }).play(); - - // start/diff object = attrs.nodeId.tweenId.attr - // tweenId = tweens.nodeId.attr - - assert.notEqual(tween._id, undefined); - assert.equal(Konva.Tween.tweens[circle._id].x, tween._id); - assert.notEqual(Konva.Tween.attrs[circle._id][tween._id], undefined); - - tween.destroy(); - - assert.equal(Konva.Tween.tweens[circle._id].x, undefined); - assert.equal(Konva.Tween.attrs[circle._id][tween._id], undefined); - }); - - // ====================================================== - it('zero duration', function (done) { - var stage = addStage(); - - var layer = new Konva.Layer(); - - var circle = new Konva.Circle({ - x: 100, - y: stage.height() / 2, - radius: 70, - fill: 'green', - stroke: 'blue', - strokeWidth: 4, - }); - - layer.add(circle); - stage.add(layer); - - var tween = new Konva.Tween({ - node: circle, - duration: 0, - x: 200, - y: 100, - }); - tween.play(); - - setTimeout(function () { - assert.equal(circle.x(), 200); - assert.equal(circle.y(), 100); - done(); - }, 60); - }); - - it('color tweening', function (done) { - var stage = addStage(); - - var layer = new Konva.Layer(); - - var circle = new Konva.Circle({ - x: 100, - y: stage.height() / 2, - radius: 70, - fill: 'red', - stroke: 'blue', - strokeWidth: 4, - }); - - layer.add(circle); - stage.add(layer); - - var duration = 0.1; - var c = Konva.Util.colorToRGBA('rgba(0,255,0,0.5)'); - var endFill = 'rgba(' + c.r + ',' + c.g + ',' + c.b + ',' + c.a + ')'; - var midFill = 'rgba(128,128,0,0.75)'; - - var tween = new Konva.Tween({ - node: circle, - duration: duration, - fill: endFill, - onFinish: function () { - assert.equal(endFill, circle.fill()); - done(); - }, - }); - - tween.seek(duration * 0.5); - assert.equal(midFill, circle.fill()); - - tween.seek(0); - tween.play(); - }); - - it('gradient tweening', function (done) { - var stage = addStage(); - - var layer = new Konva.Layer(); - - var circle = new Konva.Circle({ - x: 100, - y: stage.height() / 2, - radius: 70, - fillLinearGradientStartPoint: { x: -50, y: -50 }, - fillLinearGradientEndPoint: { x: 50, y: 50 }, - fillLinearGradientColorStops: [0, 'red', 0.5, 'blue'], - }); - - layer.add(circle); - stage.add(layer); - - var duration = 0.1; - var endFill = [0.5, 'red', 1, 'black']; - - var tween = new Konva.Tween({ - node: circle, - duration: duration, - fillLinearGradientColorStops: endFill, - onFinish: function () { - assert.deepEqual( - [0.5, 'rgba(255,0,0,1)', 1, 'rgba(0,0,0,1)'], - circle.fillLinearGradientColorStops() - ); - done(); - }, - }); - - tween.seek(duration * 0.5); - assert.deepEqual( - [0.25, 'rgba(255,0,0,1)', 0.75, 'rgba(0,0,128,1)'], - circle.fillLinearGradientColorStops() - ); - - tween.seek(0); - tween.play(); - }); - - it('to method', function (done) { - var stage = addStage(); - - var layer = new Konva.Layer(); - - var circle = new Konva.Circle({ - radius: 70, - fill: 'red', - stroke: 'blue', - strokeWidth: 4, - }); - - layer.add(circle); - stage.add(layer); - - circle.to({ - x: stage.width() / 2, - y: stage.height() / 2, - duration: 0.1, - onFinish: function () { - assert.equal(circle.x(), stage.width() / 2); - assert.equal(Object.keys(Konva.Tween.attrs[circle._id]).length, 0); - done(); - }, - }); - }); - - it('to method simple usage', function (done) { - var stage = addStage(); - - stage.to({ - x: 10, - duration: 0.001, - onFinish: () => { - assert(stage.x() === 10); - done(); - }, - }); - }); - - it('tween to call update callback', function (done) { - var stage = addStage(); - var updateCount = 0; - - stage.to({ - x: 10, - duration: 0.1, - onUpdate: function () { - updateCount++; - }, - onFinish: function () { - assert(updateCount > 2); - done(); - }, - }); - }); - - it('prepare array closed', function () { - var start = [0, 0, 10, 0, 10, 10]; - var end = [0, 0, 10, 0, 10, 10, 0, 10]; - var newStart = Konva.Util._prepareArrayForTween(start, end, true); - assert.deepEqual(newStart, [0, 0, 10, 0, 10, 10, 5, 5]); - }); - - it('prepare array - opened', function () { - var start = [0, 0, 10, 0, 10, 10, 0, 10]; - var end = [0, 0, 10, 0, 7, 9]; - end = Konva.Util._prepareArrayForTween(start, end, false); - assert.deepEqual(end, [0, 0, 10, 0, 7, 9, 7, 9]); - }); - - it('tween array with bigger size', function (done) { - var stage = addStage(); - - var layer = new Konva.Layer(); - stage.add(layer); - - var line = new Konva.Line({ - stroke: 'black', - points: [100, 100, 200, 100, 200, 200], - closed: true, - }); - layer.add(line); - - line.to({ - points: [100, 100, 200, 100, 200, 200, 100, 200], - duration: 0.1, - onFinish: function () { - assert.deepEqual(line.points(), [ - 100, - 100, - 200, - 100, - 200, - 200, - 100, - 200, - ]); - done(); - }, - }); - }); - - it('tween array to lower size', function (done) { - var stage = addStage(); - - var layer = new Konva.Layer(); - stage.add(layer); - - var line = new Konva.Line({ - stroke: 'black', - points: [100, 100, 200, 100, 200, 200, 100, 200], - closed: true, - }); - layer.add(line); - - line.to({ - points: [100, 100, 200, 100, 200, 200], - duration: 0.1, - onFinish: function () { - assert.deepEqual(line.points(), [100, 100, 200, 100, 200, 200]); - done(); - }, - }); - }); - - it('tween array to lower size and go back', function (done) { - var stage = addStage(); - - var layer = new Konva.Layer(); - stage.add(layer); - - var line = new Konva.Line({ - stroke: 'black', - points: [100, 100, 200, 100, 200, 200, 100, 200], - closed: true, - }); - layer.add(line); - - var tween = new Konva.Tween({ - node: line, - points: [100, 100, 200, 100, 200, 200], - duration: 0.01, - onFinish: function () { - tween.reverse(); - }, - onReset: function () { - assert.deepEqual(line.points(), [ - 100, - 100, - 200, - 100, - 200, - 200, - 100, - 200, - ]); - done(); - }, - }); - tween.play(); - }); - - it('tween array to bigger size and go back', function (done) { - var stage = addStage(); - - var layer = new Konva.Layer(); - stage.add(layer); - - var line = new Konva.Line({ - stroke: 'black', - points: [100, 100, 200, 100, 200, 200], - closed: true, - }); - layer.add(line); - - var tween = new Konva.Tween({ - node: line, - points: [100, 100, 200, 100, 200, 200, 100, 200], - duration: 0.01, - onFinish: function () { - tween.reverse(); - }, - onReset: function () { - assert.deepEqual(line.points(), [100, 100, 200, 100, 200, 200]); - done(); - }, - }); - tween.play(); - }); -}); diff --git a/test/unit/Util-test.ts b/test/unit/Util-test.ts deleted file mode 100644 index be49bebc8..000000000 --- a/test/unit/Util-test.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { assert } from 'chai'; -import { Konva } from './test-utils'; - -describe('Util', function () { - it('test _prepareToStringify', function () { - var o: any = { - a: 1, - b: 'string1', - }; - o.c = { - d: 'string2', - e: o, - f: global.document ? global.document.createElement('p') : { nodeType: 1 }, - }; - o.g = o; - - assert.deepEqual(Konva.Util._prepareToStringify(o), { - a: 1, - b: 'string1', - c: { - d: 'string2', - }, - }); - }); - - it('colorToRGBA() - from HSL to RGBA conversion', function () { - assert.deepEqual(Konva.Util.colorToRGBA('hsl(0, 0%, 0%)'), { - r: 0, - g: 0, - b: 0, - a: 1, - }); - - assert.deepEqual(Konva.Util.colorToRGBA('hsl(96, 48%, 59%)'), { - r: 140, - g: 201, - b: 100, - a: 1, - }); - - assert.deepEqual(Konva.Util.colorToRGBA('hsl(200, 100%, 70%)'), { - r: 102, - g: 204, - b: 255, - a: 1, - }); - }); - - it('colorToRGBA() - from color string with percentage to RGBA conversion!', function () { - assert.deepEqual(Konva.Util.colorToRGBA('rgba(50, 100, 150, 0.5)'), { - r: 50, - g: 100, - b: 150, - a: 0.5, - }); - - assert.deepEqual(Konva.Util.colorToRGBA('rgba(50, 100, 150, 50%)'), { - r: 50, - g: 100, - b: 150, - a: 0.5, - }); - - assert.deepEqual(Konva.Util.colorToRGBA('rgba(25%, 50%, 100%, 0.5)'), { - r: 63.75, - g: 127.5, - b: 255, - a: 0.5, - }); - - assert.deepEqual(Konva.Util.colorToRGBA('rgba(0%, 50%, 100%, 100%)'), { - r: 0, - g: 127.5, - b: 255, - a: 1, - }); - }); - - it('colorToRGBA() - from hex color string with percentage to RGBA conversion!', function () { - assert.deepEqual(Konva.Util.colorToRGBA('#F00'), { - r: 255, - g: 0, - b: 0, - a: 1, - }); - - assert.deepEqual(Konva.Util.colorToRGBA('#F00F'), { - r: 255, - g: 0, - b: 0, - a: 1, - }); - - assert.deepEqual(Konva.Util.colorToRGBA('#F00C'), { - r: 255, - g: 0, - b: 0, - a: 0.8, - }); - - assert.deepEqual(Konva.Util.colorToRGBA('#FF0000FF'), { - r: 255, - g: 0, - b: 0, - a: 1, - }); - - assert.deepEqual(Konva.Util.colorToRGBA('#FF0000CC'), { - r: 255, - g: 0, - b: 0, - a: 0.8, - }); - }); - - it('make sure Transform is exported', () => { - assert.equal(!!Konva.Transform, true); - }); -}); diff --git a/test/unit/Wedge-test.ts b/test/unit/Wedge-test.ts deleted file mode 100644 index 0b0c231d4..000000000 --- a/test/unit/Wedge-test.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { assert } from 'chai'; - -import { addStage, Konva } from './test-utils'; - -describe('Wedge', function () { - // ====================================================== - it('add wedge', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var wedge = new Konva.Wedge({ - x: 100, - y: 100, - radius: 70, - angle: 180 * 0.4, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle', - draggable: true, - }); - - layer.add(wedge); - stage.add(layer); - - assert.equal(wedge.getClassName(), 'Wedge'); - - var trace = layer.getContext().getTrace(); - //console.log(trace); - assert.equal( - trace, - 'clearRect(0,0,578,200);save();transform(1,0,0,1,100,100);beginPath();arc(0,0,70,0,1.257,false);lineTo(0,0);closePath();fillStyle=green;fill();lineWidth=4;strokeStyle=black;stroke();restore();' - ); - }); - - it('attrs sync', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var wedge = new Konva.Wedge({ - x: stage.width() / 2, - y: stage.height() / 2, - angle: 180 * 0.4, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - }); - - layer.add(wedge); - stage.add(layer); - - assert.equal(wedge.getWidth(), 140); - assert.equal(wedge.getHeight(), 140); - - wedge.setWidth(100); - assert.equal(wedge.radius(), 50); - assert.equal(wedge.getHeight(), 100); - - wedge.setHeight(120); - assert.equal(wedge.radius(), 60); - assert.equal(wedge.getHeight(), 120); - }); - - it('getSelfRect', function () { - var stage = addStage(); - var layer = new Konva.Layer(); - var wedge = new Konva.Wedge({ - x: stage.width() / 2, - y: stage.height() / 2, - angle: 180 * 0.4, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - }); - - layer.add(wedge); - stage.add(layer); - - assert.deepEqual(wedge.getSelfRect(), { - x: -70, - y: -70, - width: 140, - height: 140, - }); - }); -}); diff --git a/test/unit/imagediff.ts b/test/unit/imagediff.ts deleted file mode 100644 index bb1e563f5..000000000 --- a/test/unit/imagediff.ts +++ /dev/null @@ -1,332 +0,0 @@ -import { createCanvas, Canvas } from 'canvas'; - -var TYPE_ARRAY = /\[object Array\]/i, - TYPE_CANVAS = /\[object (Canvas|HTMLCanvasElement)\]/i, - TYPE_NODE_CANVAS = /\[object (Canvas|HTMLCanvasElement)\]/i, - TYPE_CONTEXT = /\[object CanvasRenderingContext2D\]/i, - TYPE_IMAGE = /\[object (Image|HTMLImageElement)\]/i, - TYPE_IMAGE_DATA = /\[object ImageData\]/i, - UNDEFINED = 'undefined', - canvas = getCanvas(), - context = canvas.getContext('2d'); - -// Creation -function getCanvas(width?, height?) { - return createCanvas(width, height); -} -function getImageData(width, height) { - canvas.width = width; - canvas.height = height; - context.clearRect(0, 0, width, height); - return context.createImageData(width, height); -} - -// Type Checking -function isImage(object) { - return isType(object, TYPE_IMAGE); -} -function isCanvas(object) { - return isType(object, TYPE_CANVAS) || object instanceof Canvas; -} -function isContext(object) { - return isType(object, TYPE_CONTEXT); -} -function isImageData(object) { - return !!( - object && - isType(object, TYPE_IMAGE_DATA) && - typeof object.width !== UNDEFINED && - typeof object.height !== UNDEFINED && - typeof object.data !== UNDEFINED - ); -} -function isImageType(object) { - return ( - isImage(object) || - isCanvas(object) || - isContext(object) || - isImageData(object) - ); -} -function isType(object, type) { - return ( - typeof object === 'object' && - !!Object.prototype.toString.apply(object).match(type) - ); -} - -// Type Conversion -function copyImageData(imageData) { - var height = imageData.height, - width = imageData.width, - data = imageData.data, - newImageData, - newData, - i; - - canvas.width = width; - canvas.height = height; - newImageData = context.getImageData(0, 0, width, height); - newData = newImageData.data; - - for (i = imageData.data.length; i--; ) { - newData[i] = data[i]; - } - - return newImageData; -} -function toImageData(object) { - if (isImage(object)) { - return toImageDataFromImage(object); - } - if (isCanvas(object)) { - return toImageDataFromCanvas(object); - } - if (isContext(object)) { - return toImageDataFromContext(object); - } - if (isImageData(object)) { - return object; - } -} -function toImageDataFromImage(image) { - var height = image.height, - width = image.width; - canvas.width = width; - canvas.height = height; - context.clearRect(0, 0, width, height); - context.drawImage(image, 0, 0); - return context.getImageData(0, 0, width, height); -} -function toImageDataFromCanvas(canvas) { - var height = canvas.height, - width = canvas.width, - context = canvas.getContext('2d'); - if (!width || !height) { - console.trace(width, height); - } - - return context.getImageData(0, 0, width, height); -} -function toImageDataFromContext(context) { - var canvas = context.canvas, - height = canvas.height, - width = canvas.width; - return context.getImageData(0, 0, width, height); -} -function toCanvas(object) { - var data = toImageData(object), - canvas = getCanvas(data.width, data.height), - context = canvas.getContext('2d'); - - context.putImageData(data, 0, 0); - return canvas; -} - -// ImageData Equality Operators -function equalWidth(a, b) { - return a.width === b.width; -} -function equalHeight(a, b) { - return a.height === b.height; -} -function equalDimensions(a, b) { - return equalHeight(a, b) && equalWidth(a, b); -} - -export function equal(a, b, tolerance, secondTol) { - var aData = a.data, - bData = b.data, - length = aData.length, - i; - - tolerance = tolerance || 0; - - var count = 0; - if (!equalDimensions(a, b)) return false; - for (i = length; i--; ) { - const d = Math.abs(aData[i] - bData[i]); - if (aData[i] !== bData[i] && d > tolerance) { - if (!secondTol) { - console.log('Diff', d); - return false; - } else { - count += 1; - } - if (count > secondTol) { - console.log('Diff', d, count); - return false; - } - } - } - - return true; -} - -// Diff -function diff(a, b, options) { - return (equalDimensions(a, b) ? diffEqual : diffUnequal)(a, b, options); -} -function diffEqual(a, b, options) { - var height = a.height, - width = a.width, - c = getImageData(width, height), // c = a - b - aData = a.data, - bData = b.data, - cData = c.data, - length = cData.length, - row, - column, - i, - j, - k, - v; - - for (i = 0; i < length; i += 4) { - cData[i] = Math.abs(aData[i] - bData[i]); - cData[i + 1] = Math.abs(aData[i + 1] - bData[i + 1]); - cData[i + 2] = Math.abs(aData[i + 2] - bData[i + 2]); - cData[i + 3] = Math.abs(255 - Math.abs(aData[i + 3] - bData[i + 3])); - } - - return c; -} -function diffUnequal(a, b, options) { - var height = Math.max(a.height, b.height), - width = Math.max(a.width, b.width), - c = getImageData(width, height), // c = a - b - aData = a.data, - bData = b.data, - cData = c.data, - align = options && options.align, - rowOffset, - columnOffset, - row, - column, - i, - j, - k, - v; - - for (i = cData.length - 1; i > 0; i = i - 4) { - cData[i] = 255; - } - - // Add First Image - offsets(a); - for (row = a.height; row--; ) { - for (column = a.width; column--; ) { - i = 4 * ((row + rowOffset) * width + (column + columnOffset)); - j = 4 * (row * a.width + column); - cData[i + 0] = aData[j + 0]; // r - cData[i + 1] = aData[j + 1]; // g - cData[i + 2] = aData[j + 2]; // b - // cData[i+3] = aData[j+3]; // a - } - } - - // Subtract Second Image - offsets(b); - for (row = b.height; row--; ) { - for (column = b.width; column--; ) { - i = 4 * ((row + rowOffset) * width + (column + columnOffset)); - j = 4 * (row * b.width + column); - cData[i + 0] = Math.abs(cData[i + 0] - bData[j + 0]); // r - cData[i + 1] = Math.abs(cData[i + 1] - bData[j + 1]); // g - cData[i + 2] = Math.abs(cData[i + 2] - bData[j + 2]); // b - } - } - - // Helpers - function offsets(imageData) { - if (align === 'top') { - rowOffset = 0; - columnOffset = 0; - } else { - rowOffset = Math.floor((height - imageData.height) / 2); - columnOffset = Math.floor((width - imageData.width) / 2); - } - } - - return c; -} - -// Validation -function checkType(...args) { - var i; - for (i = 0; i < args.length; i++) { - if (!isImageType(args[i])) { - throw { - name: 'ImageTypeError', - message: 'Submitted object was not an image.', - }; - } - } -} - -// function formatImageDiffEqualHtmlReport(actual, expected) { -// var div = get('div', 'Expected to be equal.'), -// a = get('div', '
    Actual:
    '), -// b = get('div', '
    Expected:
    '), -// c = get('div', '
    Diff:
    '), -// diff = imagediff.diff(actual, expected), -// canvas = getCanvas(), -// context; - -// canvas.height = diff.height; -// canvas.width = diff.width; - -// div.style.overflow = 'hidden'; -// a.style.float = 'left'; -// b.style.float = 'left'; -// c.style.float = 'left'; - -// context = canvas.getContext('2d'); -// context.putImageData(diff, 0, 0); - -// a.appendChild(toCanvas(actual)); -// b.appendChild(toCanvas(expected)); -// c.appendChild(canvas); - -// div.appendChild(a); -// div.appendChild(b); -// div.appendChild(c); - -// return div.innerHTML; -// } - -// function formatImageDiffEqualTextReport(actual, expected) { -// return 'Expected to be equal.'; -// } - -export const imagediff = { - createCanvas: getCanvas, - createImageData: getImageData, - - isImage: isImage, - isCanvas: isCanvas, - isContext: isContext, - isImageData: isImageData, - isImageType: isImageType, - - toImageData: function (object) { - checkType(object); - if (isImageData(object)) { - return copyImageData(object); - } - return toImageData(object); - }, - - equal: function (a, b, tolerance, secondTol) { - checkType(a, b); - a = toImageData(a); - b = toImageData(b); - return equal(a, b, tolerance, secondTol); - }, - diff: function (a, b, options?) { - checkType(a, b); - a = toImageData(a); - b = toImageData(b); - return diff(a, b, options); - }, -}; diff --git a/test/unit/test-utils.ts b/test/unit/test-utils.ts deleted file mode 100644 index 3738df2e3..000000000 --- a/test/unit/test-utils.ts +++ /dev/null @@ -1,392 +0,0 @@ -import { assert } from 'chai'; -import KonvaModule from '../../src/index'; -import '../../src/index-node'; - -export const Konva = KonvaModule; - -import * as canvas from 'canvas'; - -Konva.enableTrace = true; -Konva.showWarnings = true; - -import { imagediff } from './imagediff'; -import { Layer } from '../../src/Layer'; -import { Stage } from '../../src/Stage'; - -// reset some data -beforeEach(function () { - Konva._mouseInDblClickWindow = false; - Konva._touchInDblClickWindow = false; - Konva._pointerInDblClickWindow = false; -}); - -// clear after test -afterEach(function () { - var isFailed = this.currentTest.state == 'failed'; - var isManual = this.currentTest.parent.title === 'Manual'; - - Konva.stages.forEach(function (stage) { - clearTimeout(stage._mouseDblTimeout); - clearTimeout(stage._touchDblTimeout); - clearTimeout(stage._pointerDblTimeout); - }); - - if (!isFailed && !isManual) { - Konva.stages.forEach(function (stage) { - stage.destroy(); - }); - if (Konva.DD._dragElements.size) { - throw 'Why drag elements are not cleaned?'; - } - } -}); - -export const isNode = typeof global.document === 'undefined'; -export const isBrowser = !isNode; - -export function getContainer() { - return document.getElementById('konva-container'); -} - -export function addContainer() { - if (isNode) { - return; - } - var container = document.createElement('div'); - - getContainer().appendChild(container); - return container; -} - -export function addStage(attrs?) { - var container = - (!isNode && global.document.createElement('div')) || undefined; - - var stage = new Konva.Stage({ - container: container, - width: 578, - height: 200, - ...attrs, - }); - - if (!isNode) { - getContainer().appendChild(container); - } - - return stage; -} - -export function loadImage(url, callback) { - const isBase64 = url.indexOf('base64') >= 0; - if (isNode && !isBase64) { - url = './test/assets/' + url; - } else if (!isBase64) { - url = (document.getElementById(url) as HTMLImageElement).src; - } - - return canvas - .loadImage(url) - .then(callback) - .catch((e) => { - console.error(e); - }); -} - -export function getPixelRatio() { - return (typeof window !== 'undefined' && window.devicePixelRatio) || 1; -} - -function get(element, content?) { - element = document.createElement(element); - if (element && content) { - element.innerHTML = content; - } - return element; -} - -export function compareCanvases(canvas1, canvas2, tol?, secondTol?) { - // don't test in PhantomJS as it use old chrome engine - // it it has opacity + shadow bug - var equal = imagediff.equal(canvas1, canvas2, tol, secondTol); - if (!equal) { - const diff = imagediff.diff(canvas1, canvas2); - const diffCanvas = createCanvas(); - - const context = diffCanvas.getContext('2d'); - context.putImageData(diff, 0, 0); - - var base64 = diffCanvas.toDataURL(); - console.error('Diff image:'); - console.error(base64); - - if (isBrowser) { - var div = get('div'), - b = get('div', '
    Expected:
    '), - c = get('div', '
    Diff:
    '); - div.style.overflow = 'hidden'; - b.style.float = 'left'; - c.style.float = 'left'; - canvas2.style.position = ''; - canvas2.style.display = ''; - b.appendChild(canvas2); - c.appendChild(diffCanvas); - div.appendChild(b); - div.appendChild(c); - getContainer().appendChild(div); - } - } - assert.equal( - equal, - true, - 'Result from Konva is different with canvas result' - ); -} - -export function compareLayerAndCanvas(layer: Layer, canvas, tol?, secondTol?) { - compareCanvases(layer.getNativeCanvasElement(), canvas, tol, secondTol); -} - -export function cloneAndCompareLayer(layer: Layer, tol?, secondTol?) { - var layer2 = layer.clone(); - layer.getStage().add(layer2); - layer2.hide(); - compareLayers(layer, layer2, tol, secondTol); -} - -export function compareLayers(layer1: Layer, layer2: Layer, tol?, secondTol?) { - compareLayerAndCanvas( - layer1, - layer2.getNativeCanvasElement(), - tol, - secondTol - ); -} - -export function createCanvas() { - var node = canvas.createCanvas(300, 300); - node.width = 578 * Konva.pixelRatio; - node.height = 200 * Konva.pixelRatio; - node.getContext('2d').scale(Konva.pixelRatio, Konva.pixelRatio); - return node; -} - -export function showHit(layer) { - if (isNode) { - return; - } - var canvas = layer.hitCanvas._canvas; - canvas.style.position = 'relative'; - - getContainer().appendChild(canvas); -} - -export function simulateMouseDown(stage, pos) { - simulatePointerDown(stage, pos); - var top = isNode ? 0 : stage.content.getBoundingClientRect().top; - - stage._pointerdown({ - clientX: pos.x, - clientY: pos.y + top, - button: pos.button || 0, - type: 'mousedown', - }); -} - -export function simulateMouseMove(stage, pos) { - simulatePointerMove(stage, pos); - var top = isNode ? 0 : stage.content.getBoundingClientRect().top; - var evt = { - clientX: pos.x, - clientY: pos.y + top, - button: pos.button || 0, - type: 'mousemove', - }; - - Konva.DD._drag(evt); - stage._pointermove(evt); -} - -export function simulateMouseUp(stage, pos) { - simulatePointerUp(stage, pos); - var top = isNode ? 0 : stage.content.getBoundingClientRect().top; - var evt = { - clientX: pos.x, - clientY: pos.y + top, - button: pos.button || 0, - type: 'mouseup', - }; - - Konva.DD._endDragBefore(evt); - stage._pointerup(evt); - Konva.DD._endDragAfter(evt); -} - -export function simulateTouchStart(stage, pos, changed?) { - var top = isNode ? 0 : stage.content.getBoundingClientRect().top; - var touches; - var changedTouches; - if (Array.isArray(pos)) { - touches = pos.map(function (touch) { - return { - identifier: touch.id, - clientX: touch.x, - clientY: touch.y + top, - }; - }); - changedTouches = (changed || pos).map(function (touch) { - return { - identifier: touch.id, - clientX: touch.x, - clientY: touch.y + top, - }; - }); - } else { - changedTouches = touches = [ - { - clientX: pos.x, - clientY: pos.y + top, - id: 0, - }, - ]; - } - var evt = { - touches: touches, - changedTouches: changedTouches, - type: 'touchstart', - }; - - stage._pointerdown(evt); -} - -export function simulateTouchMove(stage, pos, changed?) { - var top = isNode ? 0 : stage.content.getBoundingClientRect().top; - var touches; - var changedTouches; - if (Array.isArray(pos)) { - touches = pos.map(function (touch) { - return { - identifier: touch.id, - clientX: touch.x, - clientY: touch.y + top, - }; - }); - changedTouches = (changed || pos).map(function (touch) { - return { - identifier: touch.id, - clientX: touch.x, - clientY: touch.y + top, - }; - }); - } else { - changedTouches = touches = [ - { - clientX: pos.x, - clientY: pos.y + top, - id: 0, - }, - ]; - } - var evt = { - touches: touches, - changedTouches: changedTouches, - type: 'touchmove', - }; - - stage._pointermove(evt); - Konva.DD._drag(evt); -} - -export function simulateTouchEnd(stage, pos, changed?) { - var top = isNode ? 0 : stage.content.getBoundingClientRect().top; - var touches; - var changedTouches; - if (Array.isArray(pos)) { - touches = pos.map(function (touch) { - return { - identifier: touch.id, - clientX: touch.x, - clientY: touch.y + top, - }; - }); - changedTouches = (changed || pos).map(function (touch) { - return { - identifier: touch.id, - clientX: touch.x, - clientY: touch.y + top, - }; - }); - } else { - changedTouches = touches = [ - { - clientX: pos.x, - clientY: pos.y + top, - id: 0, - }, - ]; - } - var evt = { - touches: touches, - changedTouches: changedTouches, - type: 'touchend', - }; - - Konva.DD._endDragBefore(evt); - stage._pointerup(evt); - Konva.DD._endDragAfter(evt); -} - -export function simulatePointerDown(stage: Stage, pos) { - var top = isNode ? 0 : stage.content.getBoundingClientRect().top; - stage._pointerdown({ - clientX: pos.x, - clientY: pos.y + top, - button: pos.button || 0, - pointerId: pos.pointerId || 1, - type: 'pointerdown', - } as any); -} - -export function simulatePointerMove(stage: Stage, pos) { - var top = isNode ? 0 : stage.content.getBoundingClientRect().top; - var evt = { - clientX: pos.x, - clientY: pos.y + top, - button: pos.button || 0, - pointerId: pos.pointerId || 1, - type: 'pointermove', - }; - - stage._pointermove(evt as any); - // Konva.DD._drag(evt); -} - -export function simulatePointerUp(stage: Stage, pos) { - var top = isNode ? 0 : stage.content.getBoundingClientRect().top; - var evt = { - clientX: pos.x, - clientY: pos.y + top, - button: pos.button || 0, - pointerId: pos.pointerId || 1, - type: 'pointerup', - }; - - // Konva.DD._endDragBefore(evt); - stage._pointerup(evt as any); - // Konva.DD._endDragAfter(evt); -} - -function isClose(a, b) { - return Math.abs(a - b) < 0.000001; -} - -export const assertAlmostEqual = function (val1, val2) { - if (!isClose(val1, val2)) { - throw new Error('Expected ' + val1 + ' to be almost equal to ' + val2); - } -}; - -export const assertAlmostDeepEqual = function (obj1, obj2) { - for (var key1 in obj1) { - assertAlmostEqual(obj1[key1], obj2[key1]); - } -}; diff --git a/tsconfig.json b/tsconfig.json deleted file mode 100644 index 0312b5cc1..000000000 --- a/tsconfig.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "compilerOptions": { - "outDir": "lib", - "module": "CommonJS", - "target": "ES2018", - // "sourceMap": true, - "noEmitOnError": true, - "lib": ["ES2019", "dom"], - "moduleResolution": "node", - "declaration": true, - "removeComments": false, - - "strict": true, - "noImplicitAny": false, - "noImplicitThis": false, - "useUnknownInCatchVariables": false, - "skipLibCheck": true, - // probably we would never enable this one - // because it's too strict, konva generates many functions on the runtime - "strictPropertyInitialization": false, - - }, - "include": ["./src/**/*.ts"], -} diff --git a/tsconfig.testing.json b/tsconfig.testing.json deleted file mode 100644 index 0177d39c7..000000000 --- a/tsconfig.testing.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "compilerOptions": { - "outDir": "lib", - "target": "ES2015", - "noEmitOnError": true, - "moduleResolution": "node", - "lib": ["ES2015", "dom"] - }, - "include": ["./src/**/*.ts"] -} From 09445e0880956fc75d35fd6a64fd598476bd5d59 Mon Sep 17 00:00:00 2001 From: Adam Greenan Date: Wed, 11 Dec 2024 11:00:09 +0000 Subject: [PATCH 2/7] init --- .github/FUNDING.yml | 12 + .github/ISSUE_TEMPLATE.md | 7 + .github/workflows/release.yml | 23 + .github/workflows/test-browser.yml | 24 + .github/workflows/test-node.yml | 24 + .gitignore | 66 + CHANGELOG.md | 1363 ++++++ LICENSE | 22 + README.md | 205 + eslint.config.mjs | 3 + gulpfile.mjs | 110 + konva-node/demo.js | 77 + konva-node/index.js | 39 + konva-node/package.json | 32 + package.json | 110 + release.sh | 77 + rename-imports.mjs | 47 + resources/doc-includes/ContainerParams.txt | 6 + resources/doc-includes/NodeParams.txt | 20 + resources/doc-includes/ShapeParams.txt | 53 + resources/jsdoc.conf.json | 28 + rollup.config.cjs | 47 + src/Animation.ts | 237 + src/BezierFunctions.ts | 826 ++++ src/Canvas.ts | 193 + src/Container.ts | 658 +++ src/Context.ts | 989 ++++ src/Core.ts | 5 + src/DragAndDrop.ts | 173 + src/Factory.ts | 248 + src/FastLayer.ts | 29 + src/Global.ts | 195 + src/Group.ts | 30 + src/Layer.ts | 544 +++ src/Node.ts | 3328 +++++++++++++ src/PointerEvents.ts | 67 + src/Shape.ts | 2037 ++++++++ src/Stage.ts | 978 ++++ src/Tween.ts | 805 ++++ src/Util.ts | 1045 ++++ src/Validators.ts | 210 + src/_CoreInternals.ts | 45 + src/_FullInternals.ts | 89 + src/filters/Blur.ts | 390 ++ src/filters/Brighten.ts | 45 + src/filters/Contrast.ts | 74 + src/filters/Emboss.ts | 202 + src/filters/Enhance.ts | 150 + src/filters/Grayscale.ts | 25 + src/filters/HSL.ts | 107 + src/filters/HSV.ts | 107 + src/filters/Invert.ts | 23 + src/filters/Kaleidoscope.ts | 272 ++ src/filters/Mask.ts | 209 + src/filters/Noise.ts | 43 + src/filters/Pixelate.ts | 121 + src/filters/Posterize.ts | 45 + src/filters/RGB.ts | 81 + src/filters/RGBA.ts | 102 + src/filters/Sepia.ts | 27 + src/filters/Solarize.ts | 48 + src/filters/Threshold.ts | 43 + src/index-node.ts | 27 + src/index-types.d.ts | 181 + src/index.ts | 3 + src/shapes/Arc.ts | 170 + src/shapes/Arrow.ts | 230 + src/shapes/Circle.ts | 75 + src/shapes/Ellipse.ts | 120 + src/shapes/Image.ts | 304 ++ src/shapes/Label.ts | 385 ++ src/shapes/Line.ts | 357 ++ src/shapes/Path.ts | 942 ++++ src/shapes/Rect.ts | 78 + src/shapes/RegularPolygon.ts | 129 + src/shapes/Ring.ts | 93 + src/shapes/Sprite.ts | 369 ++ src/shapes/Star.ts | 124 + src/shapes/Text.ts | 977 ++++ src/shapes/TextPath.ts | 571 +++ src/shapes/Transformer.ts | 1874 +++++++ src/shapes/Wedge.ts | 128 + src/types.ts | 84 + test/assets/bunny.png | Bin 0 -> 449 bytes test/assets/darth-vader.jpg | Bin 0 -> 117044 bytes test/assets/lion.png | Bin 0 -> 19826 bytes test/assets/scorpion-sprite.png | Bin 0 -> 26550 bytes test/assets/tiger.ts | 1313 +++++ test/assets/worldMap.ts | 346 ++ test/bunnies.html | 200 + test/ifame.html | 14 + test/import-test.cjs | 6 + test/import-test.mjs | 18 + test/manual-tests.html | 70 + test/manual/Blur-test.ts | 279 ++ test/manual/Brighten-test.ts | 140 + test/manual/Contrast-test.ts | 100 + test/manual/Emboss-test.ts | 91 + test/manual/Enhance-test.ts | 77 + test/manual/Filter-test.ts | 27 + test/manual/Grayscale-test.ts | 78 + test/manual/HSL-test.ts | 125 + test/manual/HSV-test.ts | 166 + test/manual/Invert-test.ts | 78 + test/manual/Kaleidoscope-test.ts | 127 + test/manual/Manual-test.ts | 416 ++ test/manual/Mask-test.ts | 38 + test/manual/Noise-test.ts | 45 + test/manual/Pixelate-test.ts | 65 + test/manual/Posterize-test.ts | 45 + test/manual/RGB-test.ts | 109 + test/manual/RGBA-test.ts | 72 + test/manual/Sepia-test.ts | 78 + test/manual/Solarize-test.ts | 30 + test/manual/Threshold-test.ts | 45 + test/node-global-setup.mjs | 11 + test/package.json | 3 + test/performance/bunnies_native.html | 155 + test/performance/creating_elements.html | 107 + test/performance/jump-shape.html | 166 + test/runner.js | 511 ++ test/sandbox.html | 71 + test/text-paths.html | 419 ++ test/tsconfig.json | 10 + test/unit-tests.html | 80 + test/unit/Animation-test.ts | 130 + test/unit/Arc-test.ts | 204 + test/unit/Arrow-test.ts | 261 + test/unit/AutoDraw-test.ts | 163 + test/unit/Blob-test.ts | 121 + test/unit/Canvas-test.ts | 42 + test/unit/Circle-test.ts | 320 ++ test/unit/Container-test.ts | 2731 +++++++++++ test/unit/Context-test.ts | 118 + test/unit/DragAndDrop-test.ts | 1282 +++++ test/unit/DragAndDropEvents-test.ts | 662 +++ test/unit/Ellipse-test.ts | 116 + test/unit/Global-test.ts | 22 + test/unit/Group-test.ts | 118 + test/unit/Image-test.ts | 451 ++ test/unit/Label-test.ts | 376 ++ test/unit/Layer-test.ts | 452 ++ test/unit/Line-test.ts | 716 +++ test/unit/MouseEvents-test.ts | 2443 ++++++++++ test/unit/Node-cache-test.ts | 1551 ++++++ test/unit/Node-test.ts | 3845 +++++++++++++++ test/unit/Path-test.ts | 1669 +++++++ test/unit/PointerEvents-test.ts | 229 + test/unit/Polygon-test.ts | 25 + test/unit/Rect-test.ts | 237 + test/unit/RegularPolygon-test.ts | 209 + test/unit/Ring-test.ts | 92 + test/unit/Shape-test.ts | 2338 +++++++++ test/unit/Spline-test.ts | 113 + test/unit/Sprite-test.ts | 464 ++ test/unit/Stage-test.ts | 1456 ++++++ test/unit/Star-test.ts | 153 + test/unit/Text-test.ts | 1761 +++++++ test/unit/TextPath-test.ts | 906 ++++ test/unit/TouchEvents-test.ts | 849 ++++ test/unit/Transformer-test.ts | 5091 ++++++++++++++++++++ test/unit/Tween-test.ts | 410 ++ test/unit/Util-test.ts | 119 + test/unit/Wedge-test.ts | 86 + test/unit/imagediff.ts | 332 ++ test/unit/test-utils.ts | 392 ++ tsconfig.json | 24 + tsconfig.testing.json | 10 + 168 files changed, 63281 insertions(+) create mode 100644 .github/FUNDING.yml create mode 100644 .github/ISSUE_TEMPLATE.md create mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/test-browser.yml create mode 100644 .github/workflows/test-node.yml create mode 100644 .gitignore create mode 100644 CHANGELOG.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 eslint.config.mjs create mode 100644 gulpfile.mjs create mode 100644 konva-node/demo.js create mode 100644 konva-node/index.js create mode 100644 konva-node/package.json create mode 100644 package.json create mode 100755 release.sh create mode 100644 rename-imports.mjs create mode 100644 resources/doc-includes/ContainerParams.txt create mode 100644 resources/doc-includes/NodeParams.txt create mode 100644 resources/doc-includes/ShapeParams.txt create mode 100644 resources/jsdoc.conf.json create mode 100644 rollup.config.cjs create mode 100644 src/Animation.ts create mode 100644 src/BezierFunctions.ts create mode 100644 src/Canvas.ts create mode 100644 src/Container.ts create mode 100644 src/Context.ts create mode 100644 src/Core.ts create mode 100644 src/DragAndDrop.ts create mode 100644 src/Factory.ts create mode 100644 src/FastLayer.ts create mode 100644 src/Global.ts create mode 100644 src/Group.ts create mode 100644 src/Layer.ts create mode 100644 src/Node.ts create mode 100644 src/PointerEvents.ts create mode 100644 src/Shape.ts create mode 100644 src/Stage.ts create mode 100644 src/Tween.ts create mode 100644 src/Util.ts create mode 100644 src/Validators.ts create mode 100644 src/_CoreInternals.ts create mode 100644 src/_FullInternals.ts create mode 100644 src/filters/Blur.ts create mode 100644 src/filters/Brighten.ts create mode 100644 src/filters/Contrast.ts create mode 100644 src/filters/Emboss.ts create mode 100644 src/filters/Enhance.ts create mode 100644 src/filters/Grayscale.ts create mode 100644 src/filters/HSL.ts create mode 100644 src/filters/HSV.ts create mode 100644 src/filters/Invert.ts create mode 100644 src/filters/Kaleidoscope.ts create mode 100644 src/filters/Mask.ts create mode 100644 src/filters/Noise.ts create mode 100644 src/filters/Pixelate.ts create mode 100644 src/filters/Posterize.ts create mode 100644 src/filters/RGB.ts create mode 100644 src/filters/RGBA.ts create mode 100644 src/filters/Sepia.ts create mode 100644 src/filters/Solarize.ts create mode 100644 src/filters/Threshold.ts create mode 100644 src/index-node.ts create mode 100644 src/index-types.d.ts create mode 100644 src/index.ts create mode 100644 src/shapes/Arc.ts create mode 100644 src/shapes/Arrow.ts create mode 100644 src/shapes/Circle.ts create mode 100644 src/shapes/Ellipse.ts create mode 100644 src/shapes/Image.ts create mode 100644 src/shapes/Label.ts create mode 100644 src/shapes/Line.ts create mode 100644 src/shapes/Path.ts create mode 100644 src/shapes/Rect.ts create mode 100644 src/shapes/RegularPolygon.ts create mode 100644 src/shapes/Ring.ts create mode 100644 src/shapes/Sprite.ts create mode 100644 src/shapes/Star.ts create mode 100644 src/shapes/Text.ts create mode 100644 src/shapes/TextPath.ts create mode 100644 src/shapes/Transformer.ts create mode 100644 src/shapes/Wedge.ts create mode 100644 src/types.ts create mode 100644 test/assets/bunny.png create mode 100644 test/assets/darth-vader.jpg create mode 100644 test/assets/lion.png create mode 100644 test/assets/scorpion-sprite.png create mode 100644 test/assets/tiger.ts create mode 100644 test/assets/worldMap.ts create mode 100644 test/bunnies.html create mode 100644 test/ifame.html create mode 100644 test/import-test.cjs create mode 100644 test/import-test.mjs create mode 100644 test/manual-tests.html create mode 100644 test/manual/Blur-test.ts create mode 100644 test/manual/Brighten-test.ts create mode 100644 test/manual/Contrast-test.ts create mode 100644 test/manual/Emboss-test.ts create mode 100644 test/manual/Enhance-test.ts create mode 100644 test/manual/Filter-test.ts create mode 100644 test/manual/Grayscale-test.ts create mode 100644 test/manual/HSL-test.ts create mode 100644 test/manual/HSV-test.ts create mode 100644 test/manual/Invert-test.ts create mode 100644 test/manual/Kaleidoscope-test.ts create mode 100644 test/manual/Manual-test.ts create mode 100644 test/manual/Mask-test.ts create mode 100644 test/manual/Noise-test.ts create mode 100644 test/manual/Pixelate-test.ts create mode 100644 test/manual/Posterize-test.ts create mode 100644 test/manual/RGB-test.ts create mode 100644 test/manual/RGBA-test.ts create mode 100644 test/manual/Sepia-test.ts create mode 100644 test/manual/Solarize-test.ts create mode 100644 test/manual/Threshold-test.ts create mode 100644 test/node-global-setup.mjs create mode 100644 test/package.json create mode 100644 test/performance/bunnies_native.html create mode 100644 test/performance/creating_elements.html create mode 100644 test/performance/jump-shape.html create mode 100644 test/runner.js create mode 100644 test/sandbox.html create mode 100644 test/text-paths.html create mode 100644 test/tsconfig.json create mode 100644 test/unit-tests.html create mode 100644 test/unit/Animation-test.ts create mode 100644 test/unit/Arc-test.ts create mode 100644 test/unit/Arrow-test.ts create mode 100644 test/unit/AutoDraw-test.ts create mode 100644 test/unit/Blob-test.ts create mode 100644 test/unit/Canvas-test.ts create mode 100644 test/unit/Circle-test.ts create mode 100644 test/unit/Container-test.ts create mode 100644 test/unit/Context-test.ts create mode 100644 test/unit/DragAndDrop-test.ts create mode 100644 test/unit/DragAndDropEvents-test.ts create mode 100644 test/unit/Ellipse-test.ts create mode 100644 test/unit/Global-test.ts create mode 100644 test/unit/Group-test.ts create mode 100644 test/unit/Image-test.ts create mode 100644 test/unit/Label-test.ts create mode 100644 test/unit/Layer-test.ts create mode 100644 test/unit/Line-test.ts create mode 100644 test/unit/MouseEvents-test.ts create mode 100644 test/unit/Node-cache-test.ts create mode 100644 test/unit/Node-test.ts create mode 100644 test/unit/Path-test.ts create mode 100644 test/unit/PointerEvents-test.ts create mode 100644 test/unit/Polygon-test.ts create mode 100644 test/unit/Rect-test.ts create mode 100644 test/unit/RegularPolygon-test.ts create mode 100644 test/unit/Ring-test.ts create mode 100644 test/unit/Shape-test.ts create mode 100644 test/unit/Spline-test.ts create mode 100644 test/unit/Sprite-test.ts create mode 100644 test/unit/Stage-test.ts create mode 100644 test/unit/Star-test.ts create mode 100644 test/unit/Text-test.ts create mode 100644 test/unit/TextPath-test.ts create mode 100644 test/unit/TouchEvents-test.ts create mode 100644 test/unit/Transformer-test.ts create mode 100644 test/unit/Tween-test.ts create mode 100644 test/unit/Util-test.ts create mode 100644 test/unit/Wedge-test.ts create mode 100644 test/unit/imagediff.ts create mode 100644 test/unit/test-utils.ts create mode 100644 tsconfig.json create mode 100644 tsconfig.testing.json diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..9438f242d --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +github: [lavrton] +patreon: lavrton +open_collective: konva +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 000000000..6e14b601f --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,7 @@ +Thank you for submitting an issue! + +Please make sure to check current open and closed issues to see if your question has been asked or answered before. +If you have just a question (not a bug or a feature request) it is better to ask it in [Stackoverflow](http://stackoverflow.com/questions/tagged/konvajs). + +If you have a bug, please, try to create a reproducible example with jsfiddle (or any similar service). +You can use [this JSBIN](https://jsbin.com/necojavuma/edit?js,output) as a template. diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..fe4d6cad5 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,23 @@ +--- +name: 'tagged-release' + +on: + push: + tags: + - '*' + +jobs: + tagged-release: + name: 'Tagged Release' + runs-on: 'ubuntu-latest' + + steps: + # ... + - name: 'Build & test' + run: | + echo "done!" + + - uses: 'marvinpinto/action-automatic-releases@latest' + with: + repo_token: '${{ secrets.GITHUB_TOKEN }}' + prerelease: false diff --git a/.github/workflows/test-browser.yml b/.github/workflows/test-browser.yml new file mode 100644 index 000000000..bc2eda602 --- /dev/null +++ b/.github/workflows/test-browser.yml @@ -0,0 +1,24 @@ +name: Test Browser + +on: + push: + branches: [master] + pull_request: + branches: [master] + +jobs: + build: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [16.x] + + steps: + - uses: actions/checkout@v2 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - run: npm install + - run: npm run test:browser diff --git a/.github/workflows/test-node.yml b/.github/workflows/test-node.yml new file mode 100644 index 000000000..795887b4d --- /dev/null +++ b/.github/workflows/test-node.yml @@ -0,0 +1,24 @@ +name: Test NodeJS + +on: + push: + branches: [master] + pull_request: + branches: [master] + +jobs: + build: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [16.x] + + steps: + - uses: actions/checkout@v2 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - run: npm install + - run: npm run test:node diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..dc6ada4ae --- /dev/null +++ b/.gitignore @@ -0,0 +1,66 @@ +dist +es +.parcel-cache +test-build +documentation +analysis +node_modules +bower_components +phantomjs.exe +docs +homedocs +jsdoc-template +api +package-lock.json +lib +src_old +*.zip +*_cache +types +out.png +cmj + +# Numerous always-ignore extensions +*.diff +*.err +*.orig +*.log +*.rej +*.swo +*.swp +*.vi +*~ +*.sass-cache + +# OS or Editor folders +.DS_Store +Thumbs.db +.cache +.project +.settings +.tmproj +*.esproj +nbproject +*.sublime-project +*.sublime-workspace +*.md.html +.vscode + +# Dreamweaver added files +_notes +dwsync.xml + +# Komodo +*.komodoproject +.komodotools + +# Folders to ignore +.hg +.svn +.CVS +intermediate +publish +.idea + +konva.js +konva.min.js \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..0fa23cefd --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,1363 @@ +# Change Log + +All notable changes to this project will be documented in this file. +This project adheres to [Semantic Versioning](http://semver.org/). + +## 9.3.17 (2024-12-02) (unreleased) + +- Fix `Arrow.getClientRect()` + +## 9.3.16 (2024-10-21) + +- Fix freeze on ios on touch cancel event +- Typescript fixes + +## 9.3.15 (2024-09-09) + +- fix letter spacing for Hindi text +- ts fixes + +### 9.3.14 (2024-07-16) + +- Fix shadow + corner radius for images +- Support `fillRule` for `Konva.Shape` on hit graph + +### 9.3.13 (2024-07-05) + +- Fallback for `Konva.Text.measureSize()` when 2d context doesn't return full data + +### 9.3.12 (2024-06-20) + +- Fix stopped transforming when it was triggered by multi-touch +- Fix svg `path.getPointAtLength()` calculations in some cases +- Fix `shape.getClientRect()` when any of parents is cached + +### 9.3.11 (2024-05-23) + +- Fix chrome clear canvas issue +- Typescript fixes + +### 9.3.9 (2024-05-20) + +- Fix underline and line-through for `Konva.Text` when `Konva._fixTextRendering = true` + +### 9.3.8 (2024-05-15) + +- Fix click events fires on stage +- Temporary `Konva._fixTextRendering = true` flag to fix inconsistent text + +### 9.3.6 (2024-03-04) + +- Fix transformer bug to enable hit graph back + +### 9.3.5 (2024-03-04) + +- `tranformer` event will be triggered AFTER all data of transformer is updated +- Improve performance of transformer + +### 9.3.4 (2024-03-03) + +- Fix clipping with zero size + +### 9.3.3 (2024-02-09) + +- Another fix for exporting buffered shapes + +### 9.3.2 (2024-01-26) + +- Fix large memory usage on node export + +### 9.3.1 (2024-01-17) + +- Fix Pixelate filter work/fix caching size +- Fix node export when large buffer canvas is used + +### 9.3.0 (2023-12-20) + +- New attribute `rotateLineVisible` for `Konva.Transformer` to show/hide rotate line + +### 9.2.3 (2023-10-31) + +- Better `Konva.Transformer` work when it has `flipEnabled = false`. + +### 9.2.2 (2023-09-14) + +- Better RTL support +- Some typescript fixes + +### 9.2.1 (2023-09-14) + +- Fix text rendering when text has both underline and shadow +- Typescript fixes + +### 9.2.0 (2023-05-14) + +- More controls on clipping +- `fillRule` for `Konva.Shape` + +### 9.1.0 (2023-05-14) + +- New `anchorStyleFunc` for `Konva.Transformer` to customize anchor style + +### 9.0.2 (2023-05-14) + +- Better text rendering when it has stroke + +### 9.0.1 (2023-04-17) + +- Better performance for any instance creation +- Little typescript fixes + +### 9.0.0 (2023-04-13) + +- Migrate the npm package from ES back to CommonJS + +### 8.4.4 (2023-04-05) + +- Some fixes for `Konva.TextPath` calculations and rendering. +- Resolve "willReadFrequently" warning in Chrome + +### 8.4.3 (2023-03-23) + +- Typescript fixes +- Better validation for `Konva.Transfomer` `nodes` property + +### 8.4.2 (2023-01-20) + +- Fix justify on text with limited height + +### 8.4.1 (2023-01-19) + +- Typescript fixes for `container.add()` method. Ability to use empty array as argument. E.g. `container.add(...emptyArray)` +- Fix underline for justify text +- Fix gradient display on underline or line-through text + +### 8.4.0 (2023-01-05) + +- Add support for `cornerRadius` for Konva.Image +- Fix cloning of `Konva.Transformer` + +### 8.3.14 (2022-11-09) + +- Automatically release (destroy) used canvas elements. Should fix safari memory issues + +### 8.3.13 (2022-10-03) + +- Typescript fixes +- Better non-passive events usage +- Better 2d context usage to avoid Chrome warnings + +### 8.3.12 (2022-08-29) + +- `ellipsis` fixes for `Konva.Text` +- Allow reset component attributes via overloader + +### 8.3.11 (2022-08-05) + +- Fix `Konva.Label` position when tag attributes are changed +- Fix incorrect ellipsis display for `Konva.Text` +- Fix `click` event trigger on parent containers on touch devices +- Fix incorrect `mouseleave` event trigger when drag is finished + +### 8.3.10 (2022-06-20) + +- Skip `Konva.Transformer` in `container.getClientRect()` calculations + +### 8.3.9 (2022-05-27) + +- Typescript fixes + +### 8.3.8 (2022-05-05) + +- Disable all exports in `package.json` + +### 8.3.7 (2022-05-04) + +- Migrate to CommonJS exports only + +### 8.3.6 (2022-04-27) + +- Better exports definitions. Importing `Konva` should work better in different bundlers and test environments. +- `imageSmoothingEnabled` option for `node.toDataURL()`, `node.toCanvas()` and `node.toImage()` + +## 8.3.5 (2022-03-21) + +- Quick fix for `toCanvas()` and `toDataURL()` size calculation. + +## 8.3.4 (2022-03-13) + +- Fix characters positions calculations on `fontFamily` changes in `TextPath`. +- Remove rounding in `node.getClientRect()` results +- Fix event object on `transformstart` event. + +## 8.3.3 (2022-02-23) + +- Fix `justify` align for text with several paragraphs. + +## 8.3.2 + +- Remove source maps for webpack builds + +## 8.3.1 (2021-12-09) + +- Fix `dbltap` event in Safari +- A bit faster `node.moveToTop()` when node is already on top +- Better client rect calculations for `Konva.Arc` shape. + +## 8.3.0 (2021-11-15) + +- new `transformer.anchorDragBoundFunc` method. + +## 8.2.4 (2021-11-15) + +- Fix not working `Konva.Transformer` when several transformers were used + +## 8.2.2 + +- Fix `Konva.Arrow` rendering when it has two pointers + +## 8.2.1 + +- Fix `package.json` exports. + +## 8.2.0 + +- Restore build in CommonJS. `const Konva = require('konva/cmj').default;` +- Fix arrow rendering when dash is used +- Fix `dbltap` trigger when multi-touch is used + +## 8.1.4 + +- Fix `dblclick` event when `cancelBubble` is used. + +## 8.1.3 + +- Fix `fillPattern` cache invalidation on shapes + +## 8.1.2 + +- Fix memory leak for `Konva.Image` + +## 8.1.1 + +- Fix `Konva.Transformer` dragging draw when `shouldOverdrawWholeArea = true`. +- Fix auto redraw when `container.removeChildren()` or `container.destroyChildren()` are used + +## 8.1.0 + +- New property `useSingleNodeRotation` for `Konva.Transformer`. + +## 8.0.4 + +- Fix fill pattern updates on `fillPatternX` and `fillPatternY` changes. + +## 8.0.2 + +- Fix some transform caches +- Fix cache with hidden shapes + +## 8.0.1 + +- Some typescript fixes + +## 8.0.0 + +This is a very large release! The long term of `Konva` API is to make it simpler and faster. So when possible I am trying to optimize the code and remove unpopular/confusing API methods. + +**BREAKING:** + +- `Konva.Collection` is removed. `container.children` is a simple array now. `container.find()` will returns an array instead of `Konva.Collection()` instance. + `Konva.Collection` was confusing for many users. Also it was slow and worked with a bit of magic. So I decided to get rif of it. Now we are going to use good old arrays. + +```js +// old code: +group.find('Shape').visible(false); + +// new code: +group.find('Shape').forEach((shape) => shape.visible(false)); +``` + +- argument `selector` is removed from `node.getIntersection(pos)` API. I don't think you even knew about it. +- `Konva.Util.extend` is removed. +- All "content" events from `Konva.Stage` are removed. E.g. instead of `contentMousemove` just use `mousemove` event. + +**New features:** + +- All updates on canvas will do automatic redraw with `layer.batchDraw()`. This features is configurable with `Konva.autoDrawEnabled` property. Konva will automatically redraw layer when you change any property, remove or add nodes, do caching. So you don't need to call `layer.draw()` or `layer.batchDraw()` in most of the cases. +- New method `layer.getNativeCanvasElement()` +- new `flipEnabled` property for `Konva.Transformer` +- new `node.isClientRectOnScreen()` method +- Added `Konva.Util.degToRad` and `Konva.Util.radToDeg` +- Added `node.getRelativePointerPosition()` + +**Changes and fixes:** + +- **Full migration to ES modules package (!), commonjs code is removed.** +- **`konva-node` is merged into `konva` npm package. One package works for both environments.** +- Full event system rewrite. Much better `pointer` events support. +- Fix `TextPath` recalculations on `fontSize` change +- Better typescript support. Now every module has its own `*.d.ts` file. +- Removed `Konva.UA`, `Konva._parseUA` (it was used for old browser detection) +- Fixed Arrow head position when an arrow has tension +- `textPath.getKerning()` is removed +- Fix `a` command parsing for `Konva.Path` +- Fix fill pattern for `Konva.Text` when the pattern has an offset or rotation +- `Konva.names` and `Konva.ids` are removed +- `Konva.captureTouchEventsEnabled` is renamed to `Konva.capturePointerEventsEnabled` + +## 7.2.5 + +- Fix transform update on `letterSpacing` change of `Konva.Text` + +## 7.2.4 + +- Fix wrong `mouseleave` trigger for `Konva.Stage` + +## 7.2.3 + +- Fix transformer rotation when parent of a node is rotated too. + +## 7.2.2 + +- Fix wrong size calculations for `Konva.Line` with tension +- Fix `shape.intersects()` behavior when a node is dragged +- Fix ellipsis rendering for `Konva.Text` + +## 7.2.1 + +- Fix correct rendering of `Konva.Label` when heigh of text is changed +- Fix correct `transformstart` and `transformend` events when several nodes are attached with `Konva.Transformer` + +## 7.2.0 + +- New property `fillAfterStrokeEnabled` for `Konva.Shape`. See API docs for more information. +- Fix for `Konva.Transformer` when it may fail to draw. +- Fix rendering of `TextPath` one more time. + +## 7.1.9 + +- Fix autodrawing for `Konva.Transformer` when it is on a different layer +- Fix `Konva.RegularPolygon` size calculations. + +## 7.1.8 + +- Fix incorrect rendering of `TextPath` in some cases. (again) + +## 7.1.7 + +- Fix incorrect rendering of `TextPath` in some cases. + +## 7.1.6 + +- Fix for correct image/dataURL/canvas exports for `Konva.Stage`. + +## 7.1.5 + +- Performance fixes for dragging many nodes with `Konva.Transformer`. +- Documentation updates + +## 7.1.4 + +- Perf fixes +- Change events trigger flow, so adding new events INSIDE event callback will work correctly. +- Fix double `dragend`, `dragstart`, `dragmove` triggers on `Konva.Transformer` + +## 7.1.3 + +- Text rendering fixes + +## 7.1.2 + +- fix ellipses behavior for `Konva.Text`. +- fix scaled fill pattern for text. + +## 7.1.1 + +- fixes for `dragstart` event when `Konva.Transformer` is used. `dragstart` event will have correct native `evt` reference +- Better unicode support in `Konva.Text` and `Konva.TextPath`. Emoji should work better now 👍 + +## 7.1.0 + +- Multi row support for `ellipsis` config for `Konva.Text` +- Better `Konva.Transfomer` behavior when single attached node is programmatically rotated. + +## 7.0.7 + +- fixes for `dragstart` event when `Konva.Transformer` is used. `dragstart` will not bubble from transformer. +- `string` and `fill` properties validation can accept `CanvasGradient` as valid value + +## 7.0.6 + +- Better performance for stage dragging + +## 7.0.5 + +- Fixes for `node.cache()` function. + +## 7.0.4 + +- Add `onUpdate` callbacks to `Konva.Tween` configuration and `node.to()` method. +- Up to 6x faster initializations of objects, like `const shape = new Konva.Shape()`. + +## 7.0.3 - 2020-07-09 + +- Fix wring `dragend` trigger on `draggable` property change inside `click` +- Fix incorrect text rendering with `letterSpacing !== 0` +- Typescript fixes + +## 7.0.2 - 2020-06-30 + +- Fix wrong trigger `dbltap` and `click` on mobile + +## 7.0.1 - 2020-06-29 + +- Fixes for different font families support. +- Fixes for `Konva.Transformer` positions +- Types fixes for better Typescript support + +## 7.0.0 - 2020-06-23 + +- **BREAKING** `inherit` option is removed from `visible` and `listening`. They now just have boolean values `true` or `false`. If you do `group.listening(false);` then whole group and all its children will be removed from the hitGraph (and they will not listen to events). Probably 99% `Konva` applications will be not affected by this _breaking change_. +- **Many performance fixes and code size optimizations. Up to 70% performance boost for many moving nodes.** +- `layer.hitGraphEnabled()` is deprecated. Just use `layer.listening(false)` instead +- Better support for font families with spaces inside (like `Font Awesome 5`). +- Fix wrong `dblclick` and `dbltap` triggers +- Deprecate `Konva.FastLayer`. Use `new Konva.Layer({ listening: false });` instead. +- `dragmove` event will be fired on `Konva.Transformer` too when you drag a node. +- `dragmove` triggers only after ALL positions of dragging nodes are changed + +## 6.0.0 - 2020-05-08 + +- **BREAKING!** `boundBoxFunc` of `Konva.Transformer` works in absolute coordinates of whole transformer. Previously in was working in local coordinates of transforming node. +- Many `Konva.Transformer` fixes. Now it works correctly when you transform several rotated shapes. +- Fix for wrong `mouseleave` and `mouseout` fire on shape remove/destroy. + +## 5.0.3 - 2020-05-01 + +- Fixes for `boundBoxFunc` of `Konva.Transformer`. + +## 5.0.2 - 2020-04-23 + +- Deatach fixes for `Konva.Transformer` + +## 5.0.1 - 2020-04-22 + +- Fixes for `Konva.Transformer` when parent scale is changed +- Fixes for `Konva.Transformer` when parent is draggable +- Performance optimizations + +## 5.0.0 - 2020-04-21 + +- **New `Konva.Transformer` implementation!**. Old API should work. But I marked this release is `major` (breaking) just for smooth updates. Changes: + - Support of transforming multiple nodes at once: `tr.nodes([shape1, shape2])`. + - `tr.node()`, `tr.setNode()`, `tr.attachTo()` methods are deprecated. Use `tr.nodes(array)` instead + - Fixes for center scaling + - Fixes for better `padding` support + - `Transformer` can be placed anywhere in the tree of a stage tree (NOT just inside a parent of attached node). +- Fix `imageSmoothEnabled` resets when stage is resized +- Memory usage optimizations when a node is cached + +## 4.2.2 - 2020-03-26 + +- Fix hit stroke issues + +## 4.2.1 - 2020-03-26 + +- Fix some issues with `mouseenter` and `mouseleave` events. +- Deprecate `hitStrokeEnabled` property +- Fix rounding issues for `getClientRect()` for some shapes + +## 4.2.0 - 2020-03-14 + +- Add `rotationSnapTolerance` property to `Konva.Transformer`. +- Add `getActiveAnchor()` method to `Konva.Transformer` +- Fix hit for non-closed `Konva.Path` +- Some fixes for experimental Offscreen canvas support inside a worker + +## 4.1.6 - 2020-02-25 + +- Events fixes for `Konva.Transformer` +- Now `Konva` will keep `id` in a cloned node +- Better error messages on tainted canvas issues + +## 4.1.5 - 2020-02-16 + +- Fixes for `path.getClientRect()` function calculations + +## 4.1.4 - 2020-02-10 + +- Fix wrong internal caching of absolute attributes +- Fix `Konva.Transformer` behavior on scaled with CSS stage + +## 4.1.3 - 2020-01-30 + +- Fix line with tension calculations +- Add `node.getAbsoluteRotation()` method +- Fix cursor on anchors for rotated parent + +## 4.1.2 - 2020-01-08 + +- Fix possible `NaN` in content calculations + +## 4.1.1 - 2020-01-07 + +- Add ability to use `width = 0` and `height = 0` for `Konva.Image`. +- Fix `cache()` method of `Konva.Arrow()` +- Add `Transform` to `Konva` default exports. So `Konva.Transform` is available now. + +## 4.1.0 - 2019-12-23 + +- Make events work on some CSS transforms +- Fix caching on float dimensions +- Fix `mouseleave` event on stage. +- Increase default anchor size for `Konva.Transformer` on touch devices + +## 4.0.18 - 2019-11-20 + +- Fix `path.getClientRect()` calculations for `Konva.Path` +- Fix wrong fire of `click` and `tap` events on stopped drag events. + +## 4.0.17 - 2019-11-08 + +- Allow hitStrokeWidth usage, even if a shape has not stroke visible +- Better IE11 support + +## 4.0.16 - 2019-10-21 + +- Warn on undefined return value of `dragBoundFunc`. +- Better calculations for `container.getClientRect()` + +## 4.0.15 - 2019-10-15 + +- TS fixes +- Better calculations for `TextPath` with align = right +- Better `textPath.getClientRect()` + +## 4.0.14 - 2019-10-11 + +- TS fixes +- Fix globalCompositeOperation + cached hit detections. +- Fix absolute position calculations for cached parent + +## 4.0.13 - 2019-10-02 + +- Fix `line.getClientRect()` calculations for line with a tension or low number of points + +## 4.0.12 - 2019-09-17 + +- Fix some bugs when `Konva.Transformer` has `padding > 0` + +## 4.0.10 - 2019-09-10 + +- Fix drag position handling +- Fix multiple selector for find() method + +## 4.0.9 - 2019-09-06 + +- Fix `Konva.Transformer` behavior on mirrored nodes +- Fix `stage.getPointerPosition()` logic. + +## 4.0.8 - 2019-09-05 + +- Fix `dragend` event on click +- Revert fillPatternScale for text fix. + +## 4.0.7 - 2019-09-03 + +- Fixed evt object on `dragstart` +- Fixed double tap trigger after dragging + +## 4.0.6 - 2019-08-31 + +- Fix fillPatternScale for text + +## 4.0.5 - 2019-08-17 + +- Fix `dragstart` flow when `node.startDrag()` is called. +- Fix `tap` and `dbltap` double trigger on stage + +## 4.0.4 - 2019-08-12 + +- Add `node.isCached()` method +- Fix nested dragging bug + +## 4.0.3 - 2019-08-08 + +- Slightly changed `mousemove` event flow. It triggers for first `mouseover` event too +- Better `Konva.hitOnDragEnabled` support for mouse inputs + +## 4.0.2 - 2019-08-08 + +- Fixed `node.startDrag()` behavior. We can call it at any time. + +## 4.0.1 - 2019-08-07 + +- Better `Konva.Arrow` + tension drawing +- Typescript fixes + +## 4.0.0 - 2019-08-05 + +Basically the release doesn't have any breaking changes. You may only have issues if you are using something from `Konva.DD` object (which is private and never documented). Otherwise you should be fine. `Konva` has major upgrade about touch events system and drag&drop flow. The API is exactly the same. But the internal refactoring is huge so I decided to make a major version. Please upgrade carefully. Report about any issues you have. + +- Better multi-touch support. Now we can trigger several `touch` events on one or many nodes. +- New drag&drop implementation. You can drag several shapes at once with several pointers. +- HSL colors support + +## 3.4.1 - 2019-07-18 + +- Fix wrong double tap trigger + +## 3.4.0 - 2019-07-12 + +- TS types fixes +- Added support for different values for `cornerRadius` of `Konva.Rect` + +## 3.3.3 - 2019-06-07 + +- Some fixes for better support `konva-node` +- TS types fixes + +## 3.3.2 - 2019-06-03 + +- TS types fixes + +## 3.3.1 - 2019-05-28 + +- Add new property `imageSmoothingEnabled` to the node caching +- Even more ts fixes. Typescript need a lot of attention, you know... + +## 3.3.0 - 2019-05-28 + +- Enable strict mode for ts types +- Add new property `imageSmoothingEnabled` to the layer + +## 3.2.7 - 2019-05-27 + +- Typescript fixes +- Experimental pointer events support. Do `Konva._pointerEventsEnabled = true;` to enable +- Fix some `Konva.Transformer` bugs. + +## 3.2.6 - 2019-05-09 + +- Typescript fixes again + +## 3.2.5 - 2019-04-17 + +- Show a warning when `Konva.Transformer` and attaching node have different parents. +- Typescript fixes + +## 3.2.4 - 2019-04-05 + +- Fix some stage events. `mouseenter` and `mouseleave` should work correctly on empty spaces +- Fix some typescript types +- Better detection of production mode (no extra warnings) + +## 3.2.3 - 2019-03-21 + +- Fix `hasName` method for empty name cases + +## 3.2.2 - 2019-03-19 + +- Remove `dependencies` from npm package + +## 3.2.1 - 2019-03-18 + +- Better `find` and `findOne` lookup. Now we should not care about duplicate ids. +- Better typescript definitions + +## 3.2.0 - 2019-03-10 + +- new property `shape.hitStrokeWidth(10)` +- Better typescript definitions +- Remove `Object.assign` usage (for IE11 support) + +## 3.1.7 - 2019-03-06 + +- Better modules and TS types + +## 3.1.6 - 2019-02-27 + +- Fix commonjs exports +- Fix global injections + +## 3.1.0 - 2019-02-27 + +- Make `Konva` modular: `import Konva from 'konva/lib/Core';`; +- Fix incorrect `Transformer` behavior +- Fix drag&drop for touch devices + +## 3.0.0 - 2019-02-25 + +## Breaking + +Customs builds are temporary removed from npm package. You can not use `import Konva from 'konva/src/Core';`. +This feature will be added back later. + +### Possibly breaking + +That changes are private and internal specific. They should not break most of `Konva` apps. + +- `Konva.Util.addMethods` is removed +- `Konva.Util._removeLastLetter` is removed +- `Konva.Util._getImage` is removed +- `Konv.Util._getRGBAString` is removed +- `Konv.Util._merge` is removed +- Removed polyfill for `requestAnimationFrame`. +- `id` and `name` properties defaults are empty strings, not `undefined` +- internal `_cache` property was updated to use es2015 `Map` instead of `{}`. +- `Konva.Validators` is removed. + +### Added + +- Show a warning when a stage has too many layers +- Show a warning on duplicate ids +- Show a warning on weird class in `Node.create` parsing from JSON +- Show a warning for incorrect value for component setters. +- Show a warning for incorrect value for `zIndex` property. +- Show a warning when user is trying to reuse destroyed shape. +- new publish method `measureSize(string)` for `Konva.Text` +- You can configure what mouse buttons can be used for drag&drop. To enable right button you can use `Konva.dragButtons = [0, 1]`. +- Now you can hide stage `stage.visible(false)`. It will set its container display style to "none". +- New method `stage.setPointersPositions(event)`. Usually you don't need to use it manually. +- New method `layer.toggleHitCanvas()` to show and debug hit areas + +### Changed + +- Full rewrite to Typescript with tons of refactoring and small optimizations. The public API should be 100% the same +- Fixed `patternImage` and `radialGradient` for `Konva.Text` +- `Konva.Util._isObject` is renamed to `Konva.Util._isPlainObject`. +- A bit changed behavior of `removeId` (private method), now it doesn't clear node ref, if object is changed. +- simplified `batchDraw` method (it doesn't use `Konva.Animation`) now. +- Performance improvements for shapes will image patterns, linear and radial fills +- `text.getTextHeight()` is deprecated. Use `text.height()` or `text.fontSize()` instead. +- Private method `stage._setPointerPosition()` is deprecated. Use `stage.setPointersPositions(event)`; + +### Fixed + +- Better mouse support on mobile devices (yes, that is possible to connect mouse to mobile) +- Better implementation of `mouseover` event for stage +- Fixed underline drawing for text with `lineHeight !== 1` +- Fixed some caching behavior when a node has `globalCompositeOperation`. +- Fixed automatic updates for `Konva.Transformer` +- Fixed container change for a stage. +- Fixed warning for `width` and `height` attributes for `Konva.Text` +- Fixed gradient drawing for `Konva.Text` +- Fixed rendering with `strokeWidth = 0` + +## 2.6.0 - 2018-12-14 + +### Changed + +- Performance fixes when cached node has many children +- Better drawing for shape with `strokeScaleEnabled = false` on HDPI devices + +### Added + +- New `ignoreStroke` for `Konva.Transformer`. Good to use when a shape has `strokeScaleEnabled = false` + +### Changed + +- `getKerning` TextPath API is deprecated. Use `kerningFunc` instead. + +## 2.5.1 - 2018-11-08 + +### Changed + +- Use custom functions for `trimRight` and `trimLeft` (for better browsers support) + +## 2.5.0 - 2018-10-24 + +### Added + +- New `anchorCornerRadius` for `Konva.Transformer` + +### Fixed + +- Performance fixes for caching + +### Changed + +- `dragstart` event behavior is a bit changed. It will fire BEFORE actual position of a node is changed. + +## 2.4.2 - 2018-10-12 + +### Fixed + +- Fixed a wrong cache when a shape inside group has `listening = false` + +## 2.4.1 - 2018-10-08 + +### Changed + +- Added some text trim logic to wrap in better + +### Fixed + +- `getClientRect` for complex paths fixes +- `getClientRect` calculation fix for groups +- Update `Konva.Transformer` on `rotateEnabled` change +- Fix click stage event on dragend +- Fix some Transformer cursor behavior + +## 2.4.0 - 2018-09-19 + +### Added + +- Centered resize with ALT key for `Konva.Transformer` +- New `centeredScaling` for `Konva.Transformer` + +### Fixed + +- Tween support for gradient properties +- Add `user-select: none` to the stage container to fix some "selected contend around" issues + +## 2.3.0 - 2018-08-30 + +### Added + +- new methods `path.getLength()` and `path.getPointAtLength(val)` +- `verticalAlign` for `Konva.Text` + +## 2.2.2 - 2018-08-21 + +### Changed + +- Default duration for tweens and `node.to()` methods is now 300ms +- Typescript fixes +- Automatic validations for many attributes + +## 2.2.1 - 2018-08-10 + +### Added + +- New properties for `Konva.Transformer`: `borderStroke`, `borderStrokeWidth`, `borderDash`, `anchorStroke`, `anchorStrokeWidth`, `anchorSize`. + +### Changed + +- Some properties of `Konva.Transformer` are renamed. `lineEnabled` -> `borderEnabled`. `rotateHandlerOffset` -> `rotateAnchorOffset`, `enabledHandlers` -> `enabledAnchors`. + +## 2.1.8 - 2018-08-01 + +### Fixed + +- Some `Konva.Transformer` fixes +- Typescript fixes +- `stage.toDataURL()` fixes when it has hidden layers +- `shape.toDataURL()` automatically adjust position and size of resulted image + +## 2.1.7 - 2018-07-03 + +### Fixed + +- `toObject` fixes + +## 2.1.7 - 2018-07-03 + +### Fixed + +- Some drag&drop fixes + +## 2.1.6 - 2018-06-16 + +### Fixed + +- Removed wrong dep +- Typescript fixes + +## 2.1.5 - 2018-06-15 + +### Fixed + +- Typescript fixes +- add shape as second argument for `sceneFunc` and `hitFunc` + +## 2.1.4 - 2018-06-15 + +### Fixed + +- Fixed `Konva.Text` justify drawing for a text with decoration +- Added methods `data()`,`setData()` and `getData()` methods to `Konva.TextPath` +- Correct cache reset for `Konva.Transformer` + +## 2.1.3 - 2018-05-17 + +### Fixed + +- `Konva.Transformer` automatically track shape changes +- `Konva.Transformer` works with shapes with offset too + +## 2.1.2 - 2018-05-16 + +### Fixed + +- Cursor fixes for `Konva.Transformer` +- Fixed lineHeight behavior for `Konva.Text` +- Some performance optimizations for `Konva.Text` +- Better wrap algorithm for `Konva.Text` +- fixed `Konva.Arrow` with tension != 0 +- Some fixes for `Konva.Transformer` + +## 2.0.3 - 2018-04-21 + +### Added + +- Typescript defs for `Konva.Transformer` +- Typescript defs for `globalCompositeOperation` + +## Changes + +- Fixed flow for `contextmenu` event. Now it will be triggered on shapes too +- `find()` method for Containers can use a function as a parameter + +### Fixed + +- some bugs fixes for `group.getClientRect()` +- `Konva.Arrow` will not draw dash for pointers +- setAttr will trigger change event if new value is the same Object +- better behavior of `dblclick` event when you click fast on different shapes +- `stage.toDataURL` will use `pixelRatio = 1` by default. + +## 2.0.2 - 2018-03-15 + +### Fixed + +- Even more bugs fixes for `Konva.Transformer` + +## 2.0.1 - 2018-03-15 + +### Fixed + +- Several bugs fixes for `Konva.Transformer` + +## 2.0.0 - 2018-03-15 + +### Added + +- new `Konva.Transformer`. It is a special group that allow simple resizing and rotation of a shape. +- Add ability to remove event by callback `node.off('event', callback)`. +- new `Konva.Filters.Contrast`. +- new `Konva.Util.haveIntersection()` to detect simple collusion +- add `Konva.Text.ellipsis` to add '…' to text string if width is fixed and wrap is set to 'none' +- add gradients for strokes + +## Changed + +- stage events are slightly changed. `mousedown`, `click`, `mouseup`, `dblclick`, `touchstart`, `touchend`, `tap`, `dbltap` will be triggered when clicked on empty areas too + +### Fixed + +- Some typescript fixes +- Pixelate filter fixes +- Fixes for path data parsing +- Fixed shadow size calculation + +## Removed + +- Some deprecated methods are removed. If previous version was working without deprecation warnings for you, this one will work fine too. + +## 1.7.6 - 2017-11-01 + +### Fixed + +- Some typescript fixes + +## 1.7.4 - 2017-10-30 + +### Fixed + +- `isBrowser` detection for electron + +## 1.7.3 - 2017-10-19 + +### Changed + +- Changing size of a stage will redraw it in synchronous way + +### Fixed + +- Some fixes special for nodejs + +## 1.7.2 - 2017-10-11 + +### Fixed + +- Fixed `Konva.document is undefined` + +## 1.7.1 - 2017-10-11 + +### Changed + +- Konva for browser env and Konva for nodejs env are separate packages now. You can use `konva-node` for NodeJS env. + +## 1.7.0 - 2017-10-08 + +### Fixed + +- Several typescript fixes + +### Changed + +- Default value for `dragDistance` is changed to 3px. +- Fix rare error throw on drag +- Caching with height = 0 or width = 0 with throw async error. Caching will be ignored. + +## 1.6.8 - 2017-08-19 + +### Changed + +- The `node.getClientRect()` calculation is changed a bit. It is more powerfull and correct. Also it takes parent transform into account. See docs. +- Upgrade nodejs deps + +## 1.6.7 - 2017-07-28 + +### Fixed + +- Fix bug with double trigger wheel in Firefox +- Fix `node.getClientRect()` calculation in a case of Group + invisible child +- Fix dblclick issue https://github.com/konvajs/konva/issues/252 + +## 1.6.3 - 2017-05-24 + +### Fixed + +- Fixed bug with pointer detection. css 3d transformed stage will not work now. + +## 1.6.2 - 2017-05-08 + +### Fixed + +- Fixed bug with automatic shadow for negative scale values + +## 1.6.1 - 2017-04-25 + +### Fixed + +- Fix pointer position detection + +### Changed + +- moved `globalCompositeOperation` property to `Konva.Node` + +## 1.6.0 - 2017-04-21 + +### Added + +- support of globalCompositeOperation for `Konva.Shape` + +### Fixed + +- getAllIntersections now works ok for Text shapes (https://github.com/konvajs/konva/issues/224) + +### Changed + +- Konva a bit changed a way to detect pointer position. Now it should be OK to apply css transform on Konva container. https://github.com/konvajs/konva/pull/215 + +## 1.5.0 - 2017-03-20 + +### Added + +- support for `lineDashOffset` property for `Konva.Shape`. + +## 1.4.0 - 2017-02-07 + +## Added + +- `textDecoration` of `Konva.Text` now supports `line-through` + +## 1.3.0 - 2017-01-10 + +## Added + +- new align value for `Konva.Text` and `Konva.TextPath`: `justify` +- new property for `Konva.Text` and `Konva.TextPath`: `textDecoration`. Right now it sports only '' (no decoration) and 'underline' values. +- new property for `Konva.Text`: `letterSpacing` +- new event `contentContextmenu` for `Konva.Stage` +- `align` support for `Konva.TextPath` +- new method `toCanvas()` for converting a node into canvas element + +### Changed + +- changing a size of `Konva.Stage` will update it in async way (via `batchDraw`). +- `shadowOffset` respect pixel ratio now + +### Fixed + +- Fixed bug when `Konva.Tag` width was not changing its width dynamically +- Fixed "calling remove() for dragging shape will throw an error" +- Fixed wrong opacity level for cached group with opacity +- More consistent shadows on HDPI screens +- Fixed memory leak for nodes with several names + +## 1.2.2 - 2016-09-15 + +### Fixed + +- refresh stage hit and its `dragend` +- `getClientRect` calculations + +## 1.2.0 - 2016-09-15 + +## Added + +- new properties for `Konva.TextPath`: `letterSpacing` and `textBaseline`. + +## 1.1.4 - 2016-09-13 + +### Fixed + +- Prevent throwing an error when text property of `Konva.Text` = undefined or null + +## 1.1.3 - 2016-09-12 + +### Changed + +- Better hit function for `TextPath`. +- Validation of `Shape` filters. + +## 1.1.2 - 2016-09-10 + +### Fixed + +- Fixed "Dragging Group on mobile view throws "missing preventDefault" error" #169 + +## 1.1.1 - 2016-08-30 + +### Fixed + +- Fixed #166 bug of drag&drop + +## 1.1.0 - 2016-08-21 + +## Added + +- new property of `Konva.Shape` - `preventDefault`. + +## 1.0.3 - 2016-08-14 + +### Fixed + +- Fixed some typescript definitions + +## 1.0.2 - 2016-07-08 + +## Changed + +- `Konva.Text` will interpret undefined `width` and `height` as `AUTO` + +## 1.0.1 - 2016-07-05 + +### Changed + +- you can now unset property by `node.x(undefined)` or `node.setAttr('x', null)` + +### Fixed + +- Bug fix for case when `touchend` event throws error + +## 1.0.0 - 2016-07-05 + +### Fixed + +- Bug fix for case when `touchend` event throws error + +## 0.15.0 - 2016-06-18 + +## Added + +- Custom clip function + +## 0.14.0 - 2016-06-17 + +### Fixed + +- fixes in typescript definitions +- fixes for bug with `mouseenter` event on deep nesting case + +## 0.13.9 - 2016-05-14 + +### Changed + +- typescript definition in npm package +- node@5.10.1, canvas@1.3.14, jsdom@8.5.0 support +- `Konva.Path` will be filled when it is not closed +- `Animation.start()` will not not immediate sync draw. This should improve performance a little. +- Warning when node for `Tween` is not in layer yet. +- `removeChildren()` remove only first level children. So it will not remove grandchildren. + +## 0.12.4 - 2016-04-19 + +### Changed + +- `batchDraw` will do not immediate `draw()` + +### Fixed + +- fix incorrect shadow offset on rotation + +## 0.12.3 - 2016-04-07 + +### Fixed + +- `batchDraw` function works less time now +- lighter npm package + +## 0.12.2 - 2016-03-31 + +### Fixed + +- repair `cancelBubble` event property behaviour +- fix wrong `Path` `getClientRect()` calculation +- better HDPI support +- better typescript definitions +- node 0.12 support + +### Changed + +- more universal stage container selector +- `mousewheel` event changed to `wheel` + +## 0.11.1 - 2016-01-16 + +### Fixed + +- correct `Konva.Arrow` drawing. Now it works better. +- Better support for dragging when mouse out of stage +- Better corner radius for `Label` shape +- `contentTap` event for stage + +### Added + +- event delegation. You can use it in this way: `layer.on('click', 'Circle', handler);` +- new `node.findAncestors(selector)` and `node.findAncestor(selector)` functions +- optional selector parameter for `stage.getIntersection` and `layer.getIntersection` +- show warning message if several instances of Konva are added to page. + +### Changed + +- `moveTo` and some other methods return `this` +- `getAbsolutePosition` support optional relative parent argument (useful to find absolute position inside of some of parent nodes) +- `change` event will be not fired if changed value is the same as old value + +## 0.10.0 - 2015-10-27 + +### Added + +- RGBA filter. Thanks to [@codefo](https://github.com/codefo) +- `stroke` and `fill` support for `Konva.Sprite` + +### Fixed + +- Correct calculation in `getClientRect` method of `Konva.Line` and `Konva.Container`. +- Correct `toObject()` behaviour for node with attrs with extended native prototypes +- Fixed bug for caching where buffer canvas is required + +### Changed + +- Dragging works much better. If your pointer is out of stage content dragging will still continue. +- `Konva.Node.create` now works with objects. +- `Konva.Tween` now supports tweening points to state with different length + +## 0.9.5 - 2015-05-28 + +### Fixed + +- `to` will not throw error if no `onFinish` callback +- HDPI support for desktop +- Fix bug when filters are not correct for HDPI +- Fix bug when hit area is not correct for HDPI +- Fix bug for incorrect `getClientRect` calculation +- Repair fill gradient for text + +### Changed + +- context wrapper is more capable with native context. + So you can use `context.fillStyle` property in your `sceneFunc` without accessing native context. +- `toDataURL` now handles pixelRatio. you can pass `config.pixelRatio` argument +- Correct `clone()` for custom nodes +- `FastLayer` can now have transforms +- `stage.toDataURL()` method now works synchronously. So `callback` argument is not required. +- `container.find(selector)` method now has a validation step. So if you forgot to add `#` or `.` you will see a warning message in the console. + +### Added + +- new `Konva.Image.fromURL` method + +### Deprecated + +- `fillRed`, `fillGreen`, `fillBlue`, `fillAlpha` are deprecated. Use `fill` instead. +- `strokeRed`, `strokeGreen`, `strokeBlue`, `strokeAlpha` are deprecated. Use `stroke` instead. +- `shadowRed`, `shadowGreen`, `shadowBlue`, `shadowAlpha` are deprecated. Use `shadow` instead. +- `dashArray` is deprecated. Use `dash` instead. +- `drawFunc` is deprecated. Use `sceneFunc` instead. +- `drawHitFunc` is deprecated. Use `hitFunc` instead. +- `rotateDeg` is deprecated. Use `rotate` instead. + +## 0.9.0 - 2015-02-27 + +### Fixed + +- cache algorithm has A LOT OF updates. + +### Changed + +- `scale` now affects `shadowOffset` +- performance optimization (remove some unnecessary draws) +- more expected drawing when shape has opacity, stroke and shadow +- HDPI for caching. +- Cache should work much better. Now you don't need to pass bounding box {x,y,width,height} to `cache` method for all buildin Konva shapes. (only for your custom `Konva.Shape` instance). +- `Tween` now supports color properties (`fill`, `stroke`, `shadowColor`) + +### Added + +- new methods for working with node's name: `addName`, `removeName`, `hasName`. +- new `perfectDrawEnabled` property for shape. See [http://konvajs.org/docs/performance/Disable_Perfect_Draw.html](http://konvajs.org/docs/performance/Disable_Perfect_Draw.html) +- new `shadowForStrokeEnabled` property for shape. See [http://konvajs.org/docs/performance/All_Performance_Tips.html](http://konvajs.org/docs/performance/All_Performance_Tips.html) +- new `getClientRect` method. +- new `to` method for every node for shorter tweening + +## 0.8.0 - 2015-02-04 + +- Bug Fixes + - browser crashing on pointer events fixed + - optimized `getIntersection` function +- Enhancements + - `container.findOne()` method + - new `strokeHitEnabled` property. Useful for performance optimizations + - typescript definitions. see `/resources/konva.d.ts` + +## Rebranding release 2015-01-28 + +Differences from last official `KineticJS` release + +- Bug Fixes + + - `strokeScaleEnabled = false` is disabled for text as I can not find a way to implement this + - `strokeScaleEnabled = false` for Line now creates a correct hit graph + - working "this-example" as name for nodes + - Konva.Text() with no config will not throw exception + - Konva.Line() with no config will not throw exception + - Correct stage resizing with `FastLayer` + - `batchDraw` method for `FastLayer` + - Correct mouseover/mouseout/mouseenter/mouseleave events for groups + - cache node before adding to layer + - `intersects` function now works for shapes with shadow + +- Enhancements + - `cornerRadius` of Rect is limited by `width/2` and `height/2` + - `black` is default fill for text + - true class extending. Now `rect instanceOf Konva.Shape` will return true + - while dragging you can redraw layer that is not under drag. hit graph will be updated in this case + - now you can move object that is dragging into another layer. + - new `frameOffsets` attribute for `Konva.Sprite` + - much better dragging performance + - `browserify` support + - applying opacity to cached node + - remove all events with `node.off()` + - mouse dragging only with left button + - opacity now affects cached shapes + - Label corner radius + - smart changing `width`, `height`, `radius` attrs for circle, start, ellipse, ring. + - `mousewheel` support. Thanks [@vmichnowicz](https://github.com/vmichnowicz) + - new Arrow plugin + - multiple names: `node.name('foo bar'); container.find('.foo');` (thanks [@mattslocum](https://github.com/mattslocum)) + - `Container.findOne()` diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..a25747cf1 --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Original work Copyright (C) 2011 - 2013 by Eric Rowell (KineticJS) +Modified work Copyright (C) 2014 - present by Anton Lavrenov (Konva) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 000000000..48eb3c938 --- /dev/null +++ b/README.md @@ -0,0 +1,205 @@ +

    + Konva logo +

    + +

    Konva

    + +[![Financial Contributors on Open Collective](https://opencollective.com/konva/all/badge.svg?label=financial+contributors)](https://opencollective.com/konva) +[![npm version](https://badge.fury.io/js/konva.svg)](http://badge.fury.io/js/konva) +[![Build Status](https://github.com/konvajs/konva/actions/workflows/test-browser.yml/badge.svg)](https://github.com/konvajs/konva/actions/workflows/test-browser.ym) +[![Build Status](https://github.com/konvajs/konva/actions/workflows/test-node.yml/badge.svg)](https://github.com/konvajs/konva/actions/workflows/test-node.ym)[![CDNJS version](https://img.shields.io/cdnjs/v/konva.svg)](https://cdnjs.com/libraries/konva) + +Konva is an HTML5 Canvas JavaScript framework that enables high performance animations, transitions, node nesting, layering, filtering, caching, event handling for desktop and mobile applications, and much more. + +You can draw things onto the stage, add event listeners to them, move them, scale them, and rotate them independently from other shapes to support high performance animations, even if your application uses thousands of shapes. Served hot with a side of awesomeness. + +This repository began as a GitHub fork of [ericdrowell/KineticJS](https://github.com/ericdrowell/KineticJS). + +- **Visit:** The [Home Page](http://konvajs.org/) and follow on [Twitter](https://twitter.com/lavrton) +- **Discover:** [Tutorials](http://konvajs.org/docs), [API Documentation](http://konvajs.org/api) +- **Help:** [StackOverflow](http://stackoverflow.com/questions/tagged/konvajs), [Discord Chat](https://discord.gg/8FqZwVT) + +# Quick Look + +```html + +
    + +``` + +# Browsers support + +Konva works in all modern mobile and desktop browsers. A browser need to be capable to run javascript code from ES2015 spec. For older browsers you may need polyfills for missing functions. + +At the current moment `Konva` doesn't work in IE11 directly. To make it work you just need to provide some polyfills such as `Array.prototype.find`, `String.prototype.trimLeft`, `String.prototype.trimRight`, `Array.from`. + +# Debugging + +The Chrome inspector simply shows the canvas element. To see the Konva objects and their details, install the konva-dev extension at https://github.com/konvajs/konva-devtool. + +# Loading and installing Konva + +Konva supports UMD loading. So you can use all possible variants to load the framework into your project: + +### Load Konva via classical ` +``` + +### Install with npm: + +```bash +npm install konva --save +``` + +```javascript +// The modern way (e.g. an ES6-style import for webpack, parcel) +import Konva from 'konva'; +``` + +#### Typescript usage + +Add DOM definitions into your `tsconfig.json`: + +``` +{ + "compilerOptions": { + "lib": [ + "es6", + "dom" + ] + } +} +``` + +### 3 Minimal bundle + +```javascript +import Konva from 'konva/lib/Core'; +// Now you have a Konva object with Stage, Layer, FastLayer, Group, Shape and some additional utils function. +// Also core currently already have support for drag&drop and animations. +// BUT there are no shapes (rect, circle, etc), no filters. + +// but you can simply add anything you need: +import { Rect } from 'konva/lib/shapes/Rect'; +// importing a shape will automatically inject it into Konva object + +var rect1 = new Rect(); +// or: +var shape = new Konva.Rect(); + +// for filters you can use this: +import { Blur } from 'konva/lib/filters/Blur'; +``` + +### 4 NodeJS env + +In order to run `konva` in nodejs environment you also need to install `canvas` package manually. Konva will use it for 2d canvas API. + +```bash +npm install konva canvas +``` + +Then you can use the same Konva API and all Konva demos will work just fine. You just don't need to use `container` attribute in your stage. + +```js +import Konva from 'konva'; + +const stage = new Konva.Stage({ + width: 500, + height: 500, +}); +// then all regular Konva code will work +``` + +# Backers + +![https://simpleshow.com](https://avatars.githubusercontent.com/u/99720652?s=200&v=4 'https://simpleshow.com') +![https://www.notably.ai/](https://avatars.githubusercontent.com/u/80046841?s=200&v=4 'https://www.notably.ai/') + +- [myposter GmbH](https://www.myposter.de/) +- [queue.gg](https://queue.gg/) + +# Change log + +See [CHANGELOG.md](https://github.com/konvajs/konva/blob/master/CHANGELOG.md). + +## Building the Konva Framework + +To make a full build run `npm run build`. The command will compile all typescript files, combine then into one bundle and run minifier. + +## Testing + +Konva uses Mocha for testing. + +- If you need run test only one time run `npm run test`. +- While developing it is easy to use `npm start`. Just run it and go to [http://localhost:1234/unit-tests.html](http://localhost:1234/unit-tests.html). The watcher will rebuild the bundle on any change. + +Konva is covered with hundreds of tests and well over a thousand assertions. +Konva uses TDD (test driven development) which means that every new feature or bug fix is accompanied with at least one new test. + +## Generate documentation + +Run `npx gulp api` which will build the documentation files and place them in the `api` folder. + +# Pull Requests + +I'd be happy to review any pull requests that may better the Konva project, +in particular if you have a bug fix, enhancement, or a new shape (see `src/shapes` for examples). Before doing so, please first make sure that all of the tests pass (`npm run test`). + +## Contributors + +### Financial Contributors + +Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/konva/contribute)] + +#### Individuals + + + +#### Organizations + +Support this project with your organization. Your logo will show up here with a link to your website. [[Contribute](https://opencollective.com/konva/contribute)] + + + + + + + + + + + diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 000000000..c6231ea12 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,3 @@ +import tseslint from 'typescript-eslint'; + +export default tseslint.config(...tseslint.configs.recommended); diff --git a/gulpfile.mjs b/gulpfile.mjs new file mode 100644 index 000000000..559914c44 --- /dev/null +++ b/gulpfile.mjs @@ -0,0 +1,110 @@ +import gulp from 'gulp'; +import rename from 'gulp-rename'; +import uglify from 'gulp-uglify-es'; +import replace from 'gulp-replace'; +import jsdoc from 'gulp-jsdoc3'; +import connect from 'gulp-connect'; +import gutil from 'gulp-util'; + +import fs from 'fs'; +var NodeParams = fs + .readFileSync('./resources/doc-includes/NodeParams.txt') + .toString(); +var ContainerParams = fs + .readFileSync('./resources/doc-includes/ContainerParams.txt') + .toString(); +var ShapeParams = fs + .readFileSync('./resources/doc-includes/ShapeParams.txt') + .toString(); + +const conf = JSON.parse(fs.readFileSync('./package.json')); + +function build() { + return gulp + .src(['./konva.js']) + .pipe(replace('@@shapeParams', ShapeParams)) + .pipe(replace('@@nodeParams', NodeParams)) + .pipe(replace('@@containerParams', ContainerParams)) + .pipe(replace('@@version', conf.version)) + .pipe(replace('@@date', new Date().toDateString())); +} + +gulp.task('update-version-lib', function () { + return gulp + .src(['./lib/Global.js']) + .pipe(replace('@@version', conf.version)) + .pipe(rename('Global.js')) + .pipe(gulp.dest('./lib')); +}); + +gulp.task('update-version-cmj', function () { + return gulp + .src(['./cmj/Global.js']) + .pipe(replace('@@version', conf.version)) + .pipe(rename('Global.js')) + .pipe(gulp.dest('./cmj')); +}); + +gulp.task('update-version-es-to-cmj-index', function () { + return gulp + .src(['./lib/index.js']) + .pipe( + replace(`import { Konva } from './_F`, `import { Konva } from '../cmj/_F`) + ) + .pipe(rename('index.js')) + .pipe(gulp.dest('./lib')); +}); + +gulp.task('update-version-es-to-cmj-node', function () { + return gulp + .src(['./lib/index-node.js']) + .pipe( + replace(`import { Konva } from './_F`, `import { Konva } from '../cmj/_F`) + ) + .pipe(rename('index-node.js')) + .pipe(gulp.dest('./lib')); +}); + +// create usual build konva.js and konva.min.js +gulp.task('pre-build', function () { + return build() + .pipe(rename('konva.js')) + .pipe(gulp.dest('./')) + .pipe( + uglify.default({ output: { comments: /^!|@preserve|@license|@cc_on/i } }) + ) + .on('error', function (err) { + gutil.log(gutil.colors.red('[Error]'), err.toString()); + }) + .pipe(rename('konva.min.js')) + .pipe(gulp.dest('./')); +}); + +gulp.task( + 'build', + gulp.parallel([ + 'update-version-lib', + // 'update-version-cmj', + // 'update-version-es-to-cmj-index', + // 'update-version-es-to-cmj-node', + 'pre-build', + ]) +); + +// local server for better development +gulp.task('server', function () { + connect.server(); +}); + +// // generate documentation +gulp.task('api', function () { + return gulp.src('./konva.js').pipe( + jsdoc({ + opts: { + destination: './api', + }, + }) + ); +}); + +gulp.task('default', gulp.parallel(['server'])); diff --git a/konva-node/demo.js b/konva-node/demo.js new file mode 100644 index 000000000..be94f4630 --- /dev/null +++ b/konva-node/demo.js @@ -0,0 +1,77 @@ +import fs from 'fs'; + +// relative path here +// but you will need just require('konva-node'); +import Konva from '../'; + +// Create stage. Container parameter is not required in NodeJS. +var stage = new Konva.Stage({ + width: 100, + height: 100, +}); + +var layer = new Konva.Layer(); +stage.add(layer); + +var rect = new Konva.Rect({ + width: 100, + height: 100, + x: 50, + y: 50, + fill: 'white', +}); +var text = new Konva.Text({ + text: 'Generated inside node js', + x: 20, + y: 20, + fill: 'black', +}); +layer.add(rect).add(text); +layer.draw(); +stage.setSize({ + width: 200, + height: 200, +}); + +// check tween works +var tween = new Konva.Tween({ + node: rect, + duration: 1, + x: -50, +}); +tween.play(); + +// After tween we want to convert stage to dataURL +setTimeout(function () { + stage.toDataURL({ + callback: function (data) { + // Then add result to stage + var img = new Konva.window.Image(); + img.onload = function () { + var image = new Konva.Image({ + image: img, + x: 10, + y: 50, + }); + layer.add(image); + layer.draw(); + // save stage image as file + stage.toDataURL({ + callback: function (data) { + var base64Data = data.replace(/^data:image\/png;base64,/, ''); + fs.writeFile('./out.png', base64Data, 'base64', function (err) { + err && console.log(err); + console.log('See out.png'); + }); + // now try to create image from url + Konva.Image.fromURL(data, () => { + console.log('image loaded'); + // shoul'd throw + }); + }, + }); + }; + img.src = data; + }, + }); +}, 1050); diff --git a/konva-node/index.js b/konva-node/index.js new file mode 100644 index 000000000..4b6b3c77b --- /dev/null +++ b/konva-node/index.js @@ -0,0 +1,39 @@ +var Konva = require('konva'); +var canvas = require('canvas'); + +// mock window +Konva.window = { + Image: canvas.Image, + devicePixelRatio: 1, +}; +// mock document +Konva.document = { + createElement: function () {}, + documentElement: { + addEventListener: function () {}, + }, +}; + +// make some global injections +global.requestAnimationFrame = (cb) => { + setImmediate(cb); +}; + +// create canvas in Node env +Konva.Util.createCanvasElement = () => { + const node = new canvas.Canvas(); + node.style = {}; + return node; +}; + +// create image in Node env +Konva.Util.createImageElement = () => { + const node = new canvas.Image(); + node.style = {}; + return node; +}; + +// _checkVisibility use dom element, in node we can skip it +Konva.Stage.prototype._checkVisibility = () => {}; + +module.exports = Konva; diff --git a/konva-node/package.json b/konva-node/package.json new file mode 100644 index 000000000..df2a8ad52 --- /dev/null +++ b/konva-node/package.json @@ -0,0 +1,32 @@ +{ + "name": "konva-node", + "version": "0.11.2", + "description": "Konva framework for NodeJS env", + "main": "index.js", + "files": [ + "index.js" + ], + "type": "module", + "typings": "./node_modules/konva/konva.d.ts", + "scripts": {}, + "keywords": [ + "canvas", + "animations", + "graphic", + "html5" + ], + "author": "Anton Lavrenov", + "bugs": { + "url": "https://github.com/konvajs/konva/issues" + }, + "homepage": "http://konvajs.org/", + "repository": { + "type": "git", + "url": "git://github.com/konvajs/konva.git" + }, + "license": "MIT", + "dependencies": { + "canvas": "^2.5.0", + "konva": "^7" + } +} diff --git a/package.json b/package.json new file mode 100644 index 000000000..faa5bcbab --- /dev/null +++ b/package.json @@ -0,0 +1,110 @@ +{ + "name": "konva", + "version": "9.3.16", + "description": "HTML5 2d canvas library.", + "author": "Anton Lavrenov", + "files": [ + "README.md", + "konva.js", + "konva.min.js", + "lib", + "cmj" + ], + "main": "./lib/index-node.js", + "browser": "./lib/index.js", + "typings": "./lib/index-types.d.ts", + "scripts": { + "start": "npm run test:watch", + "compile": "npm run clean && npm run tsc && cp ./src/index-types.d.ts ./lib/index-types.d.ts && npm run rollup", + "build": "npm run compile && cp ./src/index-types.d.ts ./lib && gulp build && node ./rename-imports.mjs", + "test:import": "npm run build && node ./test/import-test.cjs &&node ./test/import-test.mjs", + "test": "npm run test:browser && npm run test:node", + "test:build": "parcel build ./test/unit-tests.html --dist-dir ./test-build --target none --public-url ./ --no-source-maps", + "test:browser": "npm run test:build && mocha-headless-chrome -f ./test-build/unit-tests.html -a disable-web-security", + "test:watch": "rm -rf ./.parcel-cache && PARCEL_WORKERS=0 parcel serve ./test/unit-tests.html ./test/manual-tests.html ./test/sandbox.html ./test/text-paths.html ./test/bunnies.html", + "test:node": "ts-mocha -r ./test/node-global-setup.mjs -p ./test/tsconfig.json test/unit/**/*.ts --exit && npm run test:import", + "tsc": "tsc --removeComments", + "rollup": "rollup -c --bundleConfigAsCjs", + "clean": "rm -rf ./lib && rm -rf ./types && rm -rf ./cmj && rm -rf ./test-build", + "watch": "rollup -c -w", + "size": "size-limit" + }, + "targets": { + "none": {} + }, + "funding": [ + { + "type": "patreon", + "url": "https://www.patreon.com/lavrton" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/konva" + }, + { + "type": "github", + "url": "https://github.com/sponsors/lavrton" + } + ], + "size-limit": [ + { + "limit": "45 KB", + "path": "./lib/index.js" + }, + { + "limit": "26 KB", + "path": "./lib/Core.js" + }, + { + "path": "./konva.min.js" + } + ], + "devDependencies": { + "@parcel/transformer-image": "2.13.2", + "@size-limit/preset-big-lib": "^11.1.6", + "@types/mocha": "^10.0.10", + "canvas": "^2.11.2", + "chai": "5.1.2", + "filehound": "^1.17.6", + "gulp": "^5.0.0", + "gulp-concat": "^2.6.1", + "gulp-connect": "^5.7.0", + "gulp-exec": "^5.0.0", + "gulp-jsdoc3": "^3.0.0", + "gulp-rename": "^2.0.0", + "gulp-replace": "^1.1.4", + "gulp-typescript": "^5.0.1", + "gulp-uglify": "^3.0.2", + "gulp-uglify-es": "^3.0.0", + "gulp-util": "^3.0.8", + "mocha": "10.2.0", + "mocha-headless-chrome": "^4.0.0", + "parcel": "2.13.2", + "process": "^0.11.10", + "rollup": "^4.28.1", + "rollup-plugin-typescript2": "^0.36.0", + "size-limit": "^11.1.6", + "ts-mocha": "^10.0.0", + "ts-node": "^10.9.2", + "typescript": "^5.7.2" + }, + "keywords": [ + "canvas", + "animations", + "graphic", + "html5" + ], + "prettier": { + "singleQuote": true + }, + "bugs": { + "url": "https://github.com/konvajs/konva/issues" + }, + "homepage": "http://konvajs.org/", + "readmeFilename": "README.md", + "repository": { + "type": "git", + "url": "git://github.com/konvajs/konva.git" + }, + "license": "MIT" +} diff --git a/release.sh b/release.sh new file mode 100755 index 000000000..2b4eedeea --- /dev/null +++ b/release.sh @@ -0,0 +1,77 @@ +#!/usr/bin/env bash + +set -e +old_version="$(git describe --abbrev=0 --tags)" +new_version=$1 + + +old_cdn="https://unpkg.com/konva@${old_version}/konva.js" +new_cdn="https://unpkg.com/konva@${new_version}/konva.js" + +old_cdn_min="https://unpkg.com/konva@${old_version}/konva.min.js" +new_cdn_min="https://unpkg.com/konva@${new_version}/konva.min.js" + +# make sure new version parameter is passed +if [ -z "$1" ] + then + echo "ERROR - pass new version. usage release.sh 0.11.0" + exit 2 +fi + +# make sure changle log updated +while true; do + read -p "Did you update CHANGELOG.md? " yn + case $yn in + [Yy]* ) break;; + [Nn]* ) exit;; + * ) echo "Please answer yes or no.";; + esac +done + +echo "Old version: ${old_version}" +echo "New version: ${new_version}" + +echo "Pulling" +git pull >/dev/null + +echo "build and test" +npm run build >/dev/null +# npm run test + + +echo "commit change log updates" +git commit -am "update CHANGELOG with new version" --allow-empty >/dev/null + +echo "npm version $1 --no-git-tag-version" +npm version $1 --no-git-tag-version --allow-same-version >/dev/null + +echo "build for $1" +npm run build >/dev/null +git commit -am "build for $1" --allow-empty >/dev/null + +echo "update CDN link in REAME" +perl -i -pe "s|${old_cdn_min}|${new_cdn_min}|g" ./README.md >/dev/null +git commit -am "update cdn link" --allow-empty >/dev/null + +echo "create new git tag" +git tag $1 >/dev/null + +cd ../konva +git push >/dev/null +git push --tags >/dev/null +npm publish + +echo "copy konva.js into konva-site" +cp ./konva.js ../konva-site/ +cd ../konva-site + +echo "replace CDN links" + + +find source themes/hexo3/layout react-demos vue-demos main-demo -name "*.json" -exec perl -i -pe "s|${old_version}|${new_version}|g" {} + >/dev/null +find source themes/hexo3/layout react-demos vue-demos main-demo -name "*.html" -exec perl -i -pe "s|${old_version}|${new_version}|g" {} + >/dev/null + +echo "regenerate site" +./deploy.sh >/dev/null + +echo "DONE!" diff --git a/rename-imports.mjs b/rename-imports.mjs new file mode 100644 index 000000000..db66051d5 --- /dev/null +++ b/rename-imports.mjs @@ -0,0 +1,47 @@ +import FileHound from 'filehound'; +import fs from 'fs'; + +const files = FileHound.create().paths('./lib').ext(['js', 'ts']).find(); + +files.then((filePaths) => { + filePaths.forEach((filepath) => { + fs.readFile(filepath, 'utf8', (err, text) => { + if (!text.match(/import .* from/g)) { + return; + } + text = text.replace(/(import .* from\s+['"])(.*)(?=['"])/g, '$1$2.js'); + if (text.match(/export .* from/g)) { + text = text.replace(/(export .* from\s+['"])(.*)(?=['"])/g, '$1$2.js'); + } + + if (err) throw err; + + // stupid replacement back + text = text.replace( + "import * as Canvas from 'canvas.js';", + "import * as Canvas from 'canvas';" + ); + + // Handle import("./x/y/z") syntax. + text = text.replace(/(import\s*\(\s*['"])(.*)(?=['"])/g, '$1$2.js'); + + fs.writeFile(filepath, text, function (err) { + if (err) { + throw err; + } + }); + }); + }); +}); + +const indexFiles = ['lib/index.js', 'lib/index-node.js', 'lib/Core.js']; +indexFiles.forEach((filepath) => { + fs.readFile(filepath, 'utf8', (err, text) => { + text = text.replace('exports.default =', 'module.exports ='); + fs.writeFile(filepath, text, function (err) { + if (err) { + throw err; + } + }); + }); +}); diff --git a/resources/doc-includes/ContainerParams.txt b/resources/doc-includes/ContainerParams.txt new file mode 100644 index 000000000..6b9d72375 --- /dev/null +++ b/resources/doc-includes/ContainerParams.txt @@ -0,0 +1,6 @@ +* @param {Object} [config.clip] set clip + * @param {Number} [config.clipX] set clip x + * @param {Number} [config.clipY] set clip y + * @param {Number} [config.clipWidth] set clip width + * @param {Number} [config.clipHeight] set clip height + * @param {Function} [config.clipFunc] set clip func diff --git a/resources/doc-includes/NodeParams.txt b/resources/doc-includes/NodeParams.txt new file mode 100644 index 000000000..b3be0fbf6 --- /dev/null +++ b/resources/doc-includes/NodeParams.txt @@ -0,0 +1,20 @@ +@param {Number} [config.x] + * @param {Number} [config.y] + * @param {Number} [config.width] + * @param {Number} [config.height] + * @param {Boolean} [config.visible] + * @param {Boolean} [config.listening] whether or not the node is listening for events + * @param {String} [config.id] unique id + * @param {String} [config.name] non-unique name + * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 + * @param {Object} [config.scale] set scale + * @param {Number} [config.scaleX] set scale x + * @param {Number} [config.scaleY] set scale y + * @param {Number} [config.rotation] rotation in degrees + * @param {Object} [config.offset] offset from center point and rotation point + * @param {Number} [config.offsetX] set offset x + * @param {Number} [config.offsetY] set offset y + * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop + * the entire stage by dragging any portion of the stage + * @param {Number} [config.dragDistance] + * @param {Function} [config.dragBoundFunc] \ No newline at end of file diff --git a/resources/doc-includes/ShapeParams.txt b/resources/doc-includes/ShapeParams.txt new file mode 100644 index 000000000..dbaee61c1 --- /dev/null +++ b/resources/doc-includes/ShapeParams.txt @@ -0,0 +1,53 @@ +@param {String} [config.fill] fill color + * @param {Image} [config.fillPatternImage] fill pattern image + * @param {Number} [config.fillPatternX] + * @param {Number} [config.fillPatternY] + * @param {Object} [config.fillPatternOffset] object with x and y component + * @param {Number} [config.fillPatternOffsetX] + * @param {Number} [config.fillPatternOffsetY] + * @param {Object} [config.fillPatternScale] object with x and y component + * @param {Number} [config.fillPatternScaleX] + * @param {Number} [config.fillPatternScaleY] + * @param {Number} [config.fillPatternRotation] + * @param {String} [config.fillPatternRepeat] can be "repeat", "repeat-x", "repeat-y", or "no-repeat". The default is "no-repeat" + * @param {Object} [config.fillLinearGradientStartPoint] object with x and y component + * @param {Number} [config.fillLinearGradientStartPointX] + * @param {Number} [config.fillLinearGradientStartPointY] + * @param {Object} [config.fillLinearGradientEndPoint] object with x and y component + * @param {Number} [config.fillLinearGradientEndPointX] + * @param {Number} [config.fillLinearGradientEndPointY] + * @param {Array} [config.fillLinearGradientColorStops] array of color stops + * @param {Object} [config.fillRadialGradientStartPoint] object with x and y component + * @param {Number} [config.fillRadialGradientStartPointX] + * @param {Number} [config.fillRadialGradientStartPointY] + * @param {Object} [config.fillRadialGradientEndPoint] object with x and y component + * @param {Number} [config.fillRadialGradientEndPointX] + * @param {Number} [config.fillRadialGradientEndPointY] + * @param {Number} [config.fillRadialGradientStartRadius] + * @param {Number} [config.fillRadialGradientEndRadius] + * @param {Array} [config.fillRadialGradientColorStops] array of color stops + * @param {Boolean} [config.fillEnabled] flag which enables or disables the fill. The default value is true + * @param {String} [config.fillPriority] can be color, linear-gradient, radial-graident, or pattern. The default value is color. The fillPriority property makes it really easy to toggle between different fill types. For example, if you want to toggle between a fill color style and a fill pattern style, simply set the fill property and the fillPattern properties, and then use setFillPriority('color') to render the shape with a color fill, or use setFillPriority('pattern') to render the shape with the pattern fill configuration + * @param {String} [config.stroke] stroke color + * @param {Number} [config.strokeWidth] stroke width + * @param {Boolean} [config.fillAfterStrokeEnabled]. Should we draw fill AFTER stroke? Default is false. + * @param {Number} [config.hitStrokeWidth] size of the stroke on hit canvas. The default is "auto" - equals to strokeWidth + * @param {Boolean} [config.strokeHitEnabled] flag which enables or disables stroke hit region. The default is true + * @param {Boolean} [config.perfectDrawEnabled] flag which enables or disables using buffer canvas. The default is true + * @param {Boolean} [config.shadowForStrokeEnabled] flag which enables or disables shadow for stroke. The default is true + * @param {Boolean} [config.strokeScaleEnabled] flag which enables or disables stroke scale. The default is true + * @param {Boolean} [config.strokeEnabled] flag which enables or disables the stroke. The default value is true + * @param {String} [config.lineJoin] can be miter, round, or bevel. The default + * is miter + * @param {String} [config.lineCap] can be butt, round, or square. The default + * is butt + * @param {String} [config.shadowColor] + * @param {Number} [config.shadowBlur] + * @param {Object} [config.shadowOffset] object with x and y component + * @param {Number} [config.shadowOffsetX] + * @param {Number} [config.shadowOffsetY] + * @param {Number} [config.shadowOpacity] shadow opacity. Can be any real number + * between 0 and 1 + * @param {Boolean} [config.shadowEnabled] flag which enables or disables the shadow. The default value is true + * @param {Array} [config.dash] + * @param {Boolean} [config.dashEnabled] flag which enables or disables the dashArray. The default value is true diff --git a/resources/jsdoc.conf.json b/resources/jsdoc.conf.json new file mode 100644 index 000000000..7cc1fd133 --- /dev/null +++ b/resources/jsdoc.conf.json @@ -0,0 +1,28 @@ +{ + "path" : "ink-docstrap", + "tags" : { + "allowUnknownTags" : true + }, + "plugins" : ["plugins/markdown"], + + "templates" : { + "cleverLinks" : false, + "monospaceLinks" : false, + "dateFormat" : "ddd MMM Do YYYY", + "outputSourceFiles" : true, + "outputSourcePath" : true, + "systemName" : "Konva", + "footer" : "", + "copyright" : "Konva Copyright © 2015 The contributors to the Konva project.", + "navType" : "vertical", + "theme" : "cosmo", + "linenums" : true, + "collapseSymbols" : false, + "inverseNav" : true, + "highlightTutorialCode" : true + }, + "markdown" : { + "parser" : "gfm", + "hardwrap" : true + } +} diff --git a/rollup.config.cjs b/rollup.config.cjs new file mode 100644 index 000000000..3e7aa5100 --- /dev/null +++ b/rollup.config.cjs @@ -0,0 +1,47 @@ +// import resolve from 'rollup-plugin-node-resolve'; +import typescript from 'rollup-plugin-typescript2'; + +const pkg = require('./package.json'); + +export default { + input: `src/index.ts`, + output: [ + { + file: 'konva.js', + name: 'Konva', + format: 'umd', + sourcemap: false, + freeze: false, + }, + // { file: pkg.module, format: 'es', sourcemap: true } + ], + // Indicate here external modules you don't wanna include in your bundle (i.e.: 'lodash') + external: [], + watch: { + include: 'src/**', + }, + plugins: [ + // Allow json resolution + // json(), + // Compile TypeScript files + typescript({ + useTsconfigDeclarationDir: true, + abortOnError: false, + removeComments: false, + tsconfigOverride: { + compilerOptions: { + module: 'ES2020', + }, + }, + }), + // // Allow bundling cjs modules (unlike webpack, rollup doesn't understand cjs) + // commonjs(), + // // Allow node_modules resolution, so you can use 'external' to control + // // which external modules to include in the bundle + // // https://github.com/rollup/rollup-plugin-node-resolve#usage + // resolve(), + + // Resolve source maps to the original source + // sourceMaps() + ], +}; diff --git a/src/Animation.ts b/src/Animation.ts new file mode 100644 index 000000000..2f6bd5589 --- /dev/null +++ b/src/Animation.ts @@ -0,0 +1,237 @@ +import { glob } from './Global'; +import { Layer } from './Layer'; +import { IFrame, AnimationFn } from './types'; +import { Util } from './Util'; + +const now = (function (): () => number { + if (glob.performance && glob.performance.now) { + return function () { + return glob.performance.now(); + }; + } + + return function () { + return new Date().getTime(); + }; +})(); + +/** + * Animation constructor. + * @constructor + * @memberof Konva + * @param {AnimationFn} func function executed on each animation frame. The function is passed a frame object, which contains + * timeDiff, lastTime, time, and frameRate properties. The timeDiff property is the number of milliseconds that have passed + * since the last animation frame. The time property is the time in milliseconds that elapsed from the moment the animation started + * to the current animation frame. The lastTime property is a `time` value from the previous frame. The frameRate property is the current frame rate in frames / second. + * Return false from function, if you don't need to redraw layer/layers on some frames. + * @param {Konva.Layer|Array} [layers] layer(s) to be redrawn on each animation frame. Can be a layer, an array of layers, or null. + * Not specifying a node will result in no redraw. + * @example + * // move a node to the right at 50 pixels / second + * var velocity = 50; + * + * var anim = new Konva.Animation(function(frame) { + * var dist = velocity * (frame.timeDiff / 1000); + * node.move({x: dist, y: 0}); + * }, layer); + * + * anim.start(); + */ +export class Animation { + func: AnimationFn; + id = Animation.animIdCounter++; + + layers: Layer[]; + + frame: IFrame = { + time: 0, + timeDiff: 0, + lastTime: now(), + frameRate: 0, + }; + + constructor(func: AnimationFn, layers?) { + this.func = func; + this.setLayers(layers); + } + /** + * set layers to be redrawn on each animation frame + * @method + * @name Konva.Animation#setLayers + * @param {Konva.Layer|Array} [layers] layer(s) to be redrawn. Can be a layer, an array of layers, or null. Not specifying a node will result in no redraw. + * @return {Konva.Animation} this + */ + setLayers(layers: null | Layer | Layer[]) { + let lays: Layer[] = []; + // if passing in no layers + if (layers) { + lays = Array.isArray(layers) ? layers : [layers]; + } + this.layers = lays; + return this; + } + /** + * get layers + * @method + * @name Konva.Animation#getLayers + * @return {Array} Array of Konva.Layer + */ + getLayers() { + return this.layers; + } + /** + * add layer. Returns true if the layer was added, and false if it was not + * @method + * @name Konva.Animation#addLayer + * @param {Konva.Layer} layer to add + * @return {Bool} true if layer is added to animation, otherwise false + */ + addLayer(layer: Layer) { + const layers = this.layers; + const len = layers.length; + + // don't add the layer if it already exists + for (let n = 0; n < len; n++) { + if (layers[n]._id === layer._id) { + return false; + } + } + + this.layers.push(layer); + return true; + } + /** + * determine if animation is running or not. returns true or false + * @method + * @name Konva.Animation#isRunning + * @return {Bool} is animation running? + */ + isRunning() { + const a = Animation; + const animations = a.animations; + const len = animations.length; + + for (let n = 0; n < len; n++) { + if (animations[n].id === this.id) { + return true; + } + } + return false; + } + /** + * start animation + * @method + * @name Konva.Animation#start + * @return {Konva.Animation} this + */ + start() { + this.stop(); + this.frame.timeDiff = 0; + this.frame.lastTime = now(); + Animation._addAnimation(this); + return this; + } + /** + * stop animation + * @method + * @name Konva.Animation#stop + * @return {Konva.Animation} this + */ + stop() { + Animation._removeAnimation(this); + return this; + } + _updateFrameObject(time: number) { + this.frame.timeDiff = time - this.frame.lastTime; + this.frame.lastTime = time; + this.frame.time += this.frame.timeDiff; + this.frame.frameRate = 1000 / this.frame.timeDiff; + } + + static animations: Array = []; + static animIdCounter = 0; + static animRunning = false; + + static _addAnimation(anim) { + this.animations.push(anim); + this._handleAnimation(); + } + static _removeAnimation(anim) { + const id = anim.id; + const animations = this.animations; + const len = animations.length; + + for (let n = 0; n < len; n++) { + if (animations[n].id === id) { + this.animations.splice(n, 1); + break; + } + } + } + + static _runFrames() { + const layerHash = {}; + const animations = this.animations; + /* + * loop through all animations and execute animation + * function. if the animation object has specified node, + * we can add the node to the nodes hash to eliminate + * drawing the same node multiple times. The node property + * can be the stage itself or a layer + */ + /* + * WARNING: don't cache animations.length because it could change while + * the for loop is running, causing a JS error + */ + + for (let n = 0; n < animations.length; n++) { + const anim = animations[n]; + const layers = anim.layers; + const func = anim.func; + + anim._updateFrameObject(now()); + const layersLen = layers.length; + + // if animation object has a function, execute it + let needRedraw; + if (func) { + // allow anim bypassing drawing + needRedraw = func.call(anim, anim.frame) !== false; + } else { + needRedraw = true; + } + if (!needRedraw) { + continue; + } + for (let i = 0; i < layersLen; i++) { + const layer = layers[i]; + + if (layer._id !== undefined) { + layerHash[layer._id] = layer; + } + } + } + + for (const key in layerHash) { + if (!layerHash.hasOwnProperty(key)) { + continue; + } + layerHash[key].batchDraw(); + } + } + static _animationLoop() { + const Anim = Animation; + if (Anim.animations.length) { + Anim._runFrames(); + Util.requestAnimFrame(Anim._animationLoop); + } else { + Anim.animRunning = false; + } + } + static _handleAnimation() { + if (!this.animRunning) { + this.animRunning = true; + Util.requestAnimFrame(this._animationLoop); + } + } +} diff --git a/src/BezierFunctions.ts b/src/BezierFunctions.ts new file mode 100644 index 000000000..b17a6492d --- /dev/null +++ b/src/BezierFunctions.ts @@ -0,0 +1,826 @@ +// Credits: rveciana/svg-path-properties + +// Legendre-Gauss abscissae (xi values, defined at i=n as the roots of the nth order Legendre polynomial Pn(x)) +export const tValues = [ + [], + [], + [ + -0.5773502691896257645091487805019574556476, + 0.5773502691896257645091487805019574556476, + ], + [ + 0, -0.7745966692414833770358530799564799221665, + 0.7745966692414833770358530799564799221665, + ], + [ + -0.3399810435848562648026657591032446872005, + 0.3399810435848562648026657591032446872005, + -0.8611363115940525752239464888928095050957, + 0.8611363115940525752239464888928095050957, + ], + [ + 0, -0.5384693101056830910363144207002088049672, + 0.5384693101056830910363144207002088049672, + -0.9061798459386639927976268782993929651256, + 0.9061798459386639927976268782993929651256, + ], + [ + 0.6612093864662645136613995950199053470064, + -0.6612093864662645136613995950199053470064, + -0.2386191860831969086305017216807119354186, + 0.2386191860831969086305017216807119354186, + -0.9324695142031520278123015544939946091347, + 0.9324695142031520278123015544939946091347, + ], + [ + 0, 0.4058451513773971669066064120769614633473, + -0.4058451513773971669066064120769614633473, + -0.7415311855993944398638647732807884070741, + 0.7415311855993944398638647732807884070741, + -0.9491079123427585245261896840478512624007, + 0.9491079123427585245261896840478512624007, + ], + [ + -0.1834346424956498049394761423601839806667, + 0.1834346424956498049394761423601839806667, + -0.5255324099163289858177390491892463490419, + 0.5255324099163289858177390491892463490419, + -0.7966664774136267395915539364758304368371, + 0.7966664774136267395915539364758304368371, + -0.9602898564975362316835608685694729904282, + 0.9602898564975362316835608685694729904282, + ], + [ + 0, -0.8360311073266357942994297880697348765441, + 0.8360311073266357942994297880697348765441, + -0.9681602395076260898355762029036728700494, + 0.9681602395076260898355762029036728700494, + -0.3242534234038089290385380146433366085719, + 0.3242534234038089290385380146433366085719, + -0.6133714327005903973087020393414741847857, + 0.6133714327005903973087020393414741847857, + ], + [ + -0.1488743389816312108848260011297199846175, + 0.1488743389816312108848260011297199846175, + -0.4333953941292471907992659431657841622, + 0.4333953941292471907992659431657841622, + -0.6794095682990244062343273651148735757692, + 0.6794095682990244062343273651148735757692, + -0.8650633666889845107320966884234930485275, + 0.8650633666889845107320966884234930485275, + -0.9739065285171717200779640120844520534282, + 0.9739065285171717200779640120844520534282, + ], + [ + 0, -0.2695431559523449723315319854008615246796, + 0.2695431559523449723315319854008615246796, + -0.5190961292068118159257256694586095544802, + 0.5190961292068118159257256694586095544802, + -0.7301520055740493240934162520311534580496, + 0.7301520055740493240934162520311534580496, + -0.8870625997680952990751577693039272666316, + 0.8870625997680952990751577693039272666316, + -0.9782286581460569928039380011228573907714, + 0.9782286581460569928039380011228573907714, + ], + [ + -0.1252334085114689154724413694638531299833, + 0.1252334085114689154724413694638531299833, + -0.3678314989981801937526915366437175612563, + 0.3678314989981801937526915366437175612563, + -0.587317954286617447296702418940534280369, + 0.587317954286617447296702418940534280369, + -0.7699026741943046870368938332128180759849, + 0.7699026741943046870368938332128180759849, + -0.9041172563704748566784658661190961925375, + 0.9041172563704748566784658661190961925375, + -0.9815606342467192506905490901492808229601, + 0.9815606342467192506905490901492808229601, + ], + [ + 0, -0.2304583159551347940655281210979888352115, + 0.2304583159551347940655281210979888352115, + -0.4484927510364468528779128521276398678019, + 0.4484927510364468528779128521276398678019, + -0.6423493394403402206439846069955156500716, + 0.6423493394403402206439846069955156500716, + -0.8015780907333099127942064895828598903056, + 0.8015780907333099127942064895828598903056, + -0.9175983992229779652065478365007195123904, + 0.9175983992229779652065478365007195123904, + -0.9841830547185881494728294488071096110649, + 0.9841830547185881494728294488071096110649, + ], + [ + -0.1080549487073436620662446502198347476119, + 0.1080549487073436620662446502198347476119, + -0.3191123689278897604356718241684754668342, + 0.3191123689278897604356718241684754668342, + -0.5152486363581540919652907185511886623088, + 0.5152486363581540919652907185511886623088, + -0.6872929048116854701480198030193341375384, + 0.6872929048116854701480198030193341375384, + -0.8272013150697649931897947426503949610397, + 0.8272013150697649931897947426503949610397, + -0.928434883663573517336391139377874264477, + 0.928434883663573517336391139377874264477, + -0.986283808696812338841597266704052801676, + 0.986283808696812338841597266704052801676, + ], + [ + 0, -0.2011940939974345223006283033945962078128, + 0.2011940939974345223006283033945962078128, + -0.3941513470775633698972073709810454683627, + 0.3941513470775633698972073709810454683627, + -0.5709721726085388475372267372539106412383, + 0.5709721726085388475372267372539106412383, + -0.7244177313601700474161860546139380096308, + 0.7244177313601700474161860546139380096308, + -0.8482065834104272162006483207742168513662, + 0.8482065834104272162006483207742168513662, + -0.9372733924007059043077589477102094712439, + 0.9372733924007059043077589477102094712439, + -0.9879925180204854284895657185866125811469, + 0.9879925180204854284895657185866125811469, + ], + [ + -0.0950125098376374401853193354249580631303, + 0.0950125098376374401853193354249580631303, + -0.281603550779258913230460501460496106486, + 0.281603550779258913230460501460496106486, + -0.45801677765722738634241944298357757354, + 0.45801677765722738634241944298357757354, + -0.6178762444026437484466717640487910189918, + 0.6178762444026437484466717640487910189918, + -0.7554044083550030338951011948474422683538, + 0.7554044083550030338951011948474422683538, + -0.8656312023878317438804678977123931323873, + 0.8656312023878317438804678977123931323873, + -0.9445750230732325760779884155346083450911, + 0.9445750230732325760779884155346083450911, + -0.9894009349916499325961541734503326274262, + 0.9894009349916499325961541734503326274262, + ], + [ + 0, -0.1784841814958478558506774936540655574754, + 0.1784841814958478558506774936540655574754, + -0.3512317634538763152971855170953460050405, + 0.3512317634538763152971855170953460050405, + -0.5126905370864769678862465686295518745829, + 0.5126905370864769678862465686295518745829, + -0.6576711592166907658503022166430023351478, + 0.6576711592166907658503022166430023351478, + -0.7815140038968014069252300555204760502239, + 0.7815140038968014069252300555204760502239, + -0.8802391537269859021229556944881556926234, + 0.8802391537269859021229556944881556926234, + -0.9506755217687677612227169578958030214433, + 0.9506755217687677612227169578958030214433, + -0.9905754753144173356754340199406652765077, + 0.9905754753144173356754340199406652765077, + ], + [ + -0.0847750130417353012422618529357838117333, + 0.0847750130417353012422618529357838117333, + -0.2518862256915055095889728548779112301628, + 0.2518862256915055095889728548779112301628, + -0.4117511614628426460359317938330516370789, + 0.4117511614628426460359317938330516370789, + -0.5597708310739475346078715485253291369276, + 0.5597708310739475346078715485253291369276, + -0.6916870430603532078748910812888483894522, + 0.6916870430603532078748910812888483894522, + -0.8037049589725231156824174550145907971032, + 0.8037049589725231156824174550145907971032, + -0.8926024664975557392060605911271455154078, + 0.8926024664975557392060605911271455154078, + -0.9558239495713977551811958929297763099728, + 0.9558239495713977551811958929297763099728, + -0.9915651684209309467300160047061507702525, + 0.9915651684209309467300160047061507702525, + ], + [ + 0, -0.1603586456402253758680961157407435495048, + 0.1603586456402253758680961157407435495048, + -0.3165640999636298319901173288498449178922, + 0.3165640999636298319901173288498449178922, + -0.4645707413759609457172671481041023679762, + 0.4645707413759609457172671481041023679762, + -0.6005453046616810234696381649462392798683, + 0.6005453046616810234696381649462392798683, + -0.7209661773352293786170958608237816296571, + 0.7209661773352293786170958608237816296571, + -0.8227146565371428249789224867127139017745, + 0.8227146565371428249789224867127139017745, + -0.9031559036148179016426609285323124878093, + 0.9031559036148179016426609285323124878093, + -0.960208152134830030852778840687651526615, + 0.960208152134830030852778840687651526615, + -0.9924068438435844031890176702532604935893, + 0.9924068438435844031890176702532604935893, + ], + [ + -0.0765265211334973337546404093988382110047, + 0.0765265211334973337546404093988382110047, + -0.227785851141645078080496195368574624743, + 0.227785851141645078080496195368574624743, + -0.3737060887154195606725481770249272373957, + 0.3737060887154195606725481770249272373957, + -0.5108670019508270980043640509552509984254, + 0.5108670019508270980043640509552509984254, + -0.6360536807265150254528366962262859367433, + 0.6360536807265150254528366962262859367433, + -0.7463319064601507926143050703556415903107, + 0.7463319064601507926143050703556415903107, + -0.8391169718222188233945290617015206853296, + 0.8391169718222188233945290617015206853296, + -0.9122344282513259058677524412032981130491, + 0.9122344282513259058677524412032981130491, + -0.963971927277913791267666131197277221912, + 0.963971927277913791267666131197277221912, + -0.9931285991850949247861223884713202782226, + 0.9931285991850949247861223884713202782226, + ], + [ + 0, -0.1455618541608950909370309823386863301163, + 0.1455618541608950909370309823386863301163, + -0.288021316802401096600792516064600319909, + 0.288021316802401096600792516064600319909, + -0.4243421202074387835736688885437880520964, + 0.4243421202074387835736688885437880520964, + -0.551618835887219807059018796724313286622, + 0.551618835887219807059018796724313286622, + -0.667138804197412319305966669990339162597, + 0.667138804197412319305966669990339162597, + -0.7684399634756779086158778513062280348209, + 0.7684399634756779086158778513062280348209, + -0.8533633645833172836472506385875676702761, + 0.8533633645833172836472506385875676702761, + -0.9200993341504008287901871337149688941591, + 0.9200993341504008287901871337149688941591, + -0.9672268385663062943166222149076951614246, + 0.9672268385663062943166222149076951614246, + -0.9937521706203895002602420359379409291933, + 0.9937521706203895002602420359379409291933, + ], + [ + -0.0697392733197222212138417961186280818222, + 0.0697392733197222212138417961186280818222, + -0.2078604266882212854788465339195457342156, + 0.2078604266882212854788465339195457342156, + -0.3419358208920842251581474204273796195591, + 0.3419358208920842251581474204273796195591, + -0.4693558379867570264063307109664063460953, + 0.4693558379867570264063307109664063460953, + -0.5876404035069115929588769276386473488776, + 0.5876404035069115929588769276386473488776, + -0.6944872631866827800506898357622567712673, + 0.6944872631866827800506898357622567712673, + -0.7878168059792081620042779554083515213881, + 0.7878168059792081620042779554083515213881, + -0.8658125777203001365364256370193787290847, + 0.8658125777203001365364256370193787290847, + -0.9269567721871740005206929392590531966353, + 0.9269567721871740005206929392590531966353, + -0.9700604978354287271239509867652687108059, + 0.9700604978354287271239509867652687108059, + -0.994294585482399292073031421161298980393, + 0.994294585482399292073031421161298980393, + ], + [ + 0, -0.1332568242984661109317426822417661370104, + 0.1332568242984661109317426822417661370104, + -0.264135680970344930533869538283309602979, + 0.264135680970344930533869538283309602979, + -0.390301038030290831421488872880605458578, + 0.390301038030290831421488872880605458578, + -0.5095014778460075496897930478668464305448, + 0.5095014778460075496897930478668464305448, + -0.6196098757636461563850973116495956533871, + 0.6196098757636461563850973116495956533871, + -0.7186613631319501944616244837486188483299, + 0.7186613631319501944616244837486188483299, + -0.8048884016188398921511184069967785579414, + 0.8048884016188398921511184069967785579414, + -0.8767523582704416673781568859341456716389, + 0.8767523582704416673781568859341456716389, + -0.9329710868260161023491969890384229782357, + 0.9329710868260161023491969890384229782357, + -0.9725424712181152319560240768207773751816, + 0.9725424712181152319560240768207773751816, + -0.9947693349975521235239257154455743605736, + 0.9947693349975521235239257154455743605736, + ], + [ + -0.0640568928626056260850430826247450385909, + 0.0640568928626056260850430826247450385909, + -0.1911188674736163091586398207570696318404, + 0.1911188674736163091586398207570696318404, + -0.3150426796961633743867932913198102407864, + 0.3150426796961633743867932913198102407864, + -0.4337935076260451384870842319133497124524, + 0.4337935076260451384870842319133497124524, + -0.5454214713888395356583756172183723700107, + 0.5454214713888395356583756172183723700107, + -0.6480936519369755692524957869107476266696, + 0.6480936519369755692524957869107476266696, + -0.7401241915785543642438281030999784255232, + 0.7401241915785543642438281030999784255232, + -0.8200019859739029219539498726697452080761, + 0.8200019859739029219539498726697452080761, + -0.8864155270044010342131543419821967550873, + 0.8864155270044010342131543419821967550873, + -0.9382745520027327585236490017087214496548, + 0.9382745520027327585236490017087214496548, + -0.9747285559713094981983919930081690617411, + 0.9747285559713094981983919930081690617411, + -0.9951872199970213601799974097007368118745, + 0.9951872199970213601799974097007368118745, + ], +]; + +// Legendre-Gauss weights (wi values, defined by a function linked to in the Bezier primer article) +export const cValues = [ + [], + [], + [1.0, 1.0], + [ + 0.8888888888888888888888888888888888888888, + 0.5555555555555555555555555555555555555555, + 0.5555555555555555555555555555555555555555, + ], + [ + 0.6521451548625461426269360507780005927646, + 0.6521451548625461426269360507780005927646, + 0.3478548451374538573730639492219994072353, + 0.3478548451374538573730639492219994072353, + ], + [ + 0.5688888888888888888888888888888888888888, + 0.4786286704993664680412915148356381929122, + 0.4786286704993664680412915148356381929122, + 0.2369268850561890875142640407199173626432, + 0.2369268850561890875142640407199173626432, + ], + [ + 0.3607615730481386075698335138377161116615, + 0.3607615730481386075698335138377161116615, + 0.4679139345726910473898703439895509948116, + 0.4679139345726910473898703439895509948116, + 0.1713244923791703450402961421727328935268, + 0.1713244923791703450402961421727328935268, + ], + [ + 0.4179591836734693877551020408163265306122, + 0.3818300505051189449503697754889751338783, + 0.3818300505051189449503697754889751338783, + 0.2797053914892766679014677714237795824869, + 0.2797053914892766679014677714237795824869, + 0.1294849661688696932706114326790820183285, + 0.1294849661688696932706114326790820183285, + ], + [ + 0.3626837833783619829651504492771956121941, + 0.3626837833783619829651504492771956121941, + 0.3137066458778872873379622019866013132603, + 0.3137066458778872873379622019866013132603, + 0.2223810344533744705443559944262408844301, + 0.2223810344533744705443559944262408844301, + 0.1012285362903762591525313543099621901153, + 0.1012285362903762591525313543099621901153, + ], + [ + 0.3302393550012597631645250692869740488788, + 0.1806481606948574040584720312429128095143, + 0.1806481606948574040584720312429128095143, + 0.0812743883615744119718921581105236506756, + 0.0812743883615744119718921581105236506756, + 0.3123470770400028400686304065844436655987, + 0.3123470770400028400686304065844436655987, + 0.2606106964029354623187428694186328497718, + 0.2606106964029354623187428694186328497718, + ], + [ + 0.295524224714752870173892994651338329421, + 0.295524224714752870173892994651338329421, + 0.2692667193099963550912269215694693528597, + 0.2692667193099963550912269215694693528597, + 0.2190863625159820439955349342281631924587, + 0.2190863625159820439955349342281631924587, + 0.1494513491505805931457763396576973324025, + 0.1494513491505805931457763396576973324025, + 0.0666713443086881375935688098933317928578, + 0.0666713443086881375935688098933317928578, + ], + [ + 0.272925086777900630714483528336342189156, + 0.2628045445102466621806888698905091953727, + 0.2628045445102466621806888698905091953727, + 0.2331937645919904799185237048431751394317, + 0.2331937645919904799185237048431751394317, + 0.1862902109277342514260976414316558916912, + 0.1862902109277342514260976414316558916912, + 0.1255803694649046246346942992239401001976, + 0.1255803694649046246346942992239401001976, + 0.0556685671161736664827537204425485787285, + 0.0556685671161736664827537204425485787285, + ], + [ + 0.2491470458134027850005624360429512108304, + 0.2491470458134027850005624360429512108304, + 0.2334925365383548087608498989248780562594, + 0.2334925365383548087608498989248780562594, + 0.2031674267230659217490644558097983765065, + 0.2031674267230659217490644558097983765065, + 0.160078328543346226334652529543359071872, + 0.160078328543346226334652529543359071872, + 0.1069393259953184309602547181939962242145, + 0.1069393259953184309602547181939962242145, + 0.047175336386511827194615961485017060317, + 0.047175336386511827194615961485017060317, + ], + [ + 0.2325515532308739101945895152688359481566, + 0.2262831802628972384120901860397766184347, + 0.2262831802628972384120901860397766184347, + 0.2078160475368885023125232193060527633865, + 0.2078160475368885023125232193060527633865, + 0.1781459807619457382800466919960979955128, + 0.1781459807619457382800466919960979955128, + 0.1388735102197872384636017768688714676218, + 0.1388735102197872384636017768688714676218, + 0.0921214998377284479144217759537971209236, + 0.0921214998377284479144217759537971209236, + 0.0404840047653158795200215922009860600419, + 0.0404840047653158795200215922009860600419, + ], + [ + 0.2152638534631577901958764433162600352749, + 0.2152638534631577901958764433162600352749, + 0.2051984637212956039659240656612180557103, + 0.2051984637212956039659240656612180557103, + 0.1855383974779378137417165901251570362489, + 0.1855383974779378137417165901251570362489, + 0.1572031671581935345696019386238421566056, + 0.1572031671581935345696019386238421566056, + 0.1215185706879031846894148090724766259566, + 0.1215185706879031846894148090724766259566, + 0.0801580871597602098056332770628543095836, + 0.0801580871597602098056332770628543095836, + 0.0351194603317518630318328761381917806197, + 0.0351194603317518630318328761381917806197, + ], + [ + 0.2025782419255612728806201999675193148386, + 0.1984314853271115764561183264438393248186, + 0.1984314853271115764561183264438393248186, + 0.1861610000155622110268005618664228245062, + 0.1861610000155622110268005618664228245062, + 0.1662692058169939335532008604812088111309, + 0.1662692058169939335532008604812088111309, + 0.1395706779261543144478047945110283225208, + 0.1395706779261543144478047945110283225208, + 0.1071592204671719350118695466858693034155, + 0.1071592204671719350118695466858693034155, + 0.0703660474881081247092674164506673384667, + 0.0703660474881081247092674164506673384667, + 0.0307532419961172683546283935772044177217, + 0.0307532419961172683546283935772044177217, + ], + [ + 0.1894506104550684962853967232082831051469, + 0.1894506104550684962853967232082831051469, + 0.1826034150449235888667636679692199393835, + 0.1826034150449235888667636679692199393835, + 0.1691565193950025381893120790303599622116, + 0.1691565193950025381893120790303599622116, + 0.1495959888165767320815017305474785489704, + 0.1495959888165767320815017305474785489704, + 0.1246289712555338720524762821920164201448, + 0.1246289712555338720524762821920164201448, + 0.0951585116824927848099251076022462263552, + 0.0951585116824927848099251076022462263552, + 0.0622535239386478928628438369943776942749, + 0.0622535239386478928628438369943776942749, + 0.0271524594117540948517805724560181035122, + 0.0271524594117540948517805724560181035122, + ], + [ + 0.1794464703562065254582656442618856214487, + 0.1765627053669926463252709901131972391509, + 0.1765627053669926463252709901131972391509, + 0.1680041021564500445099706637883231550211, + 0.1680041021564500445099706637883231550211, + 0.1540457610768102880814315948019586119404, + 0.1540457610768102880814315948019586119404, + 0.1351363684685254732863199817023501973721, + 0.1351363684685254732863199817023501973721, + 0.1118838471934039710947883856263559267358, + 0.1118838471934039710947883856263559267358, + 0.0850361483171791808835353701910620738504, + 0.0850361483171791808835353701910620738504, + 0.0554595293739872011294401653582446605128, + 0.0554595293739872011294401653582446605128, + 0.0241483028685479319601100262875653246916, + 0.0241483028685479319601100262875653246916, + ], + [ + 0.1691423829631435918406564701349866103341, + 0.1691423829631435918406564701349866103341, + 0.1642764837458327229860537764659275904123, + 0.1642764837458327229860537764659275904123, + 0.1546846751262652449254180038363747721932, + 0.1546846751262652449254180038363747721932, + 0.1406429146706506512047313037519472280955, + 0.1406429146706506512047313037519472280955, + 0.1225552067114784601845191268002015552281, + 0.1225552067114784601845191268002015552281, + 0.1009420441062871655628139849248346070628, + 0.1009420441062871655628139849248346070628, + 0.0764257302548890565291296776166365256053, + 0.0764257302548890565291296776166365256053, + 0.0497145488949697964533349462026386416808, + 0.0497145488949697964533349462026386416808, + 0.0216160135264833103133427102664524693876, + 0.0216160135264833103133427102664524693876, + ], + [ + 0.1610544498487836959791636253209167350399, + 0.1589688433939543476499564394650472016787, + 0.1589688433939543476499564394650472016787, + 0.152766042065859666778855400897662998461, + 0.152766042065859666778855400897662998461, + 0.1426067021736066117757461094419029724756, + 0.1426067021736066117757461094419029724756, + 0.1287539625393362276755157848568771170558, + 0.1287539625393362276755157848568771170558, + 0.1115666455473339947160239016817659974813, + 0.1115666455473339947160239016817659974813, + 0.0914900216224499994644620941238396526609, + 0.0914900216224499994644620941238396526609, + 0.0690445427376412265807082580060130449618, + 0.0690445427376412265807082580060130449618, + 0.0448142267656996003328381574019942119517, + 0.0448142267656996003328381574019942119517, + 0.0194617882297264770363120414644384357529, + 0.0194617882297264770363120414644384357529, + ], + [ + 0.1527533871307258506980843319550975934919, + 0.1527533871307258506980843319550975934919, + 0.1491729864726037467878287370019694366926, + 0.1491729864726037467878287370019694366926, + 0.1420961093183820513292983250671649330345, + 0.1420961093183820513292983250671649330345, + 0.1316886384491766268984944997481631349161, + 0.1316886384491766268984944997481631349161, + 0.118194531961518417312377377711382287005, + 0.118194531961518417312377377711382287005, + 0.1019301198172404350367501354803498761666, + 0.1019301198172404350367501354803498761666, + 0.0832767415767047487247581432220462061001, + 0.0832767415767047487247581432220462061001, + 0.0626720483341090635695065351870416063516, + 0.0626720483341090635695065351870416063516, + 0.040601429800386941331039952274932109879, + 0.040601429800386941331039952274932109879, + 0.0176140071391521183118619623518528163621, + 0.0176140071391521183118619623518528163621, + ], + [ + 0.1460811336496904271919851476833711882448, + 0.1445244039899700590638271665537525436099, + 0.1445244039899700590638271665537525436099, + 0.1398873947910731547221334238675831108927, + 0.1398873947910731547221334238675831108927, + 0.132268938633337461781052574496775604329, + 0.132268938633337461781052574496775604329, + 0.1218314160537285341953671771257335983563, + 0.1218314160537285341953671771257335983563, + 0.1087972991671483776634745780701056420336, + 0.1087972991671483776634745780701056420336, + 0.0934444234560338615532897411139320884835, + 0.0934444234560338615532897411139320884835, + 0.0761001136283793020170516533001831792261, + 0.0761001136283793020170516533001831792261, + 0.0571344254268572082836358264724479574912, + 0.0571344254268572082836358264724479574912, + 0.0369537897708524937999506682993296661889, + 0.0369537897708524937999506682993296661889, + 0.0160172282577743333242246168584710152658, + 0.0160172282577743333242246168584710152658, + ], + [ + 0.1392518728556319933754102483418099578739, + 0.1392518728556319933754102483418099578739, + 0.1365414983460151713525738312315173965863, + 0.1365414983460151713525738312315173965863, + 0.1311735047870623707329649925303074458757, + 0.1311735047870623707329649925303074458757, + 0.1232523768105124242855609861548144719594, + 0.1232523768105124242855609861548144719594, + 0.1129322960805392183934006074217843191142, + 0.1129322960805392183934006074217843191142, + 0.1004141444428809649320788378305362823508, + 0.1004141444428809649320788378305362823508, + 0.0859416062170677274144436813727028661891, + 0.0859416062170677274144436813727028661891, + 0.0697964684245204880949614189302176573987, + 0.0697964684245204880949614189302176573987, + 0.0522933351526832859403120512732112561121, + 0.0522933351526832859403120512732112561121, + 0.0337749015848141547933022468659129013491, + 0.0337749015848141547933022468659129013491, + 0.0146279952982722006849910980471854451902, + 0.0146279952982722006849910980471854451902, + ], + [ + 0.1336545721861061753514571105458443385831, + 0.132462039404696617371642464703316925805, + 0.132462039404696617371642464703316925805, + 0.1289057221880821499785953393997936532597, + 0.1289057221880821499785953393997936532597, + 0.1230490843067295304675784006720096548158, + 0.1230490843067295304675784006720096548158, + 0.1149966402224113649416435129339613014914, + 0.1149966402224113649416435129339613014914, + 0.1048920914645414100740861850147438548584, + 0.1048920914645414100740861850147438548584, + 0.0929157660600351474770186173697646486034, + 0.0929157660600351474770186173697646486034, + 0.0792814117767189549228925247420432269137, + 0.0792814117767189549228925247420432269137, + 0.0642324214085258521271696151589109980391, + 0.0642324214085258521271696151589109980391, + 0.0480376717310846685716410716320339965612, + 0.0480376717310846685716410716320339965612, + 0.0309880058569794443106942196418845053837, + 0.0309880058569794443106942196418845053837, + 0.0134118594871417720813094934586150649766, + 0.0134118594871417720813094934586150649766, + ], + [ + 0.1279381953467521569740561652246953718517, + 0.1279381953467521569740561652246953718517, + 0.1258374563468282961213753825111836887264, + 0.1258374563468282961213753825111836887264, + 0.121670472927803391204463153476262425607, + 0.121670472927803391204463153476262425607, + 0.1155056680537256013533444839067835598622, + 0.1155056680537256013533444839067835598622, + 0.1074442701159656347825773424466062227946, + 0.1074442701159656347825773424466062227946, + 0.0976186521041138882698806644642471544279, + 0.0976186521041138882698806644642471544279, + 0.086190161531953275917185202983742667185, + 0.086190161531953275917185202983742667185, + 0.0733464814110803057340336152531165181193, + 0.0733464814110803057340336152531165181193, + 0.0592985849154367807463677585001085845412, + 0.0592985849154367807463677585001085845412, + 0.0442774388174198061686027482113382288593, + 0.0442774388174198061686027482113382288593, + 0.0285313886289336631813078159518782864491, + 0.0285313886289336631813078159518782864491, + 0.0123412297999871995468056670700372915759, + 0.0123412297999871995468056670700372915759, + ], +]; + +// LUT for binomial coefficient arrays per curve order 'n' +export const binomialCoefficients = [[1], [1, 1], [1, 2, 1], [1, 3, 3, 1]]; + +export const getCubicArcLength = (xs: number[], ys: number[], t: number) => { + let sum: number; + let correctedT: number; + + /*if (xs.length >= tValues.length) { + throw new Error('too high n bezier'); + }*/ + + const n = 20; + + const z = t / 2; + sum = 0; + for (let i = 0; i < n; i++) { + correctedT = z * tValues[n][i] + z; + sum += cValues[n][i] * BFunc(xs, ys, correctedT); + } + return z * sum; +}; + +export const getQuadraticArcLength = ( + xs: number[], + ys: number[], + t: number +) => { + if (t === undefined) { + t = 1; + } + const ax = xs[0] - 2 * xs[1] + xs[2]; + const ay = ys[0] - 2 * ys[1] + ys[2]; + const bx = 2 * xs[1] - 2 * xs[0]; + const by = 2 * ys[1] - 2 * ys[0]; + + const A = 4 * (ax * ax + ay * ay); + const B = 4 * (ax * bx + ay * by); + const C = bx * bx + by * by; + + if (A === 0) { + return ( + t * Math.sqrt(Math.pow(xs[2] - xs[0], 2) + Math.pow(ys[2] - ys[0], 2)) + ); + } + const b = B / (2 * A); + const c = C / A; + const u = t + b; + const k = c - b * b; + + const uuk = u * u + k > 0 ? Math.sqrt(u * u + k) : 0; + const bbk = b * b + k > 0 ? Math.sqrt(b * b + k) : 0; + const term = + b + Math.sqrt(b * b + k) !== 0 + ? k * Math.log(Math.abs((u + uuk) / (b + bbk))) + : 0; + + return (Math.sqrt(A) / 2) * (u * uuk - b * bbk + term); +}; + +function BFunc(xs: number[], ys: number[], t: number) { + const xbase = getDerivative(1, t, xs); + const ybase = getDerivative(1, t, ys); + const combined = xbase * xbase + ybase * ybase; + return Math.sqrt(combined); +} + +/** + * Compute the curve derivative (hodograph) at t. + */ +const getDerivative = (derivative: number, t: number, vs: number[]): number => { + // the derivative of any 't'-less function is zero. + const n = vs.length - 1; + let _vs; + let value; + + if (n === 0) { + return 0; + } + + // direct values? compute! + if (derivative === 0) { + value = 0; + for (let k = 0; k <= n; k++) { + value += + binomialCoefficients[n][k] * + Math.pow(1 - t, n - k) * + Math.pow(t, k) * + vs[k]; + } + return value; + } else { + // Still some derivative? go down one order, then try + // for the lower order curve's. + _vs = new Array(n); + for (let k = 0; k < n; k++) { + _vs[k] = n * (vs[k + 1] - vs[k]); + } + return getDerivative(derivative - 1, t, _vs); + } +}; + +export const t2length = ( + length: number, + totalLength: number, + func: (t: number) => number +): number => { + let error = 1; + let t = length / totalLength; + let step = (length - func(t)) / totalLength; + + let numIterations = 0; + while (error > 0.001) { + const increasedTLength = func(t + step); + const increasedTError = Math.abs(length - increasedTLength) / totalLength; + if (increasedTError < error) { + error = increasedTError; + t += step; + } else { + const decreasedTLength = func(t - step); + const decreasedTError = Math.abs(length - decreasedTLength) / totalLength; + if (decreasedTError < error) { + error = decreasedTError; + t -= step; + } else { + step /= 2; + } + } + + numIterations++; + if (numIterations > 500) { + break; + } + } + + return t; +}; diff --git a/src/Canvas.ts b/src/Canvas.ts new file mode 100644 index 000000000..eb122941d --- /dev/null +++ b/src/Canvas.ts @@ -0,0 +1,193 @@ +import { Util } from './Util'; +import { SceneContext, HitContext, Context } from './Context'; +import { Konva } from './Global'; +import { Factory } from './Factory'; +import { getNumberValidator } from './Validators'; + +// calculate pixel ratio +let _pixelRatio; +function getDevicePixelRatio() { + if (_pixelRatio) { + return _pixelRatio; + } + const canvas = Util.createCanvasElement(); + const context = canvas.getContext('2d') as any; + _pixelRatio = (function () { + const devicePixelRatio = Konva._global.devicePixelRatio || 1, + backingStoreRatio = + context.webkitBackingStorePixelRatio || + context.mozBackingStorePixelRatio || + context.msBackingStorePixelRatio || + context.oBackingStorePixelRatio || + context.backingStorePixelRatio || + 1; + return devicePixelRatio / backingStoreRatio; + })(); + Util.releaseCanvas(canvas); + return _pixelRatio; +} + +interface ICanvasConfig { + width?: number; + height?: number; + pixelRatio?: number; + willReadFrequently?: boolean; +} + +/** + * Canvas Renderer constructor. It is a wrapper around native canvas element. + * Usually you don't need to use it manually. + * @constructor + * @abstract + * @memberof Konva + * @param {Object} config + * @param {Number} config.width + * @param {Number} config.height + * @param {Number} config.pixelRatio + */ +export class Canvas { + pixelRatio = 1; + _canvas: HTMLCanvasElement; + context: Context; + width = 0; + height = 0; + + isCache = false; + + constructor(config: ICanvasConfig) { + const conf = config || {}; + + const pixelRatio = + conf.pixelRatio || Konva.pixelRatio || getDevicePixelRatio(); + + this.pixelRatio = pixelRatio; + + this._canvas = Util.createCanvasElement(); + // set inline styles + this._canvas.style.padding = '0'; + this._canvas.style.margin = '0'; + this._canvas.style.border = '0'; + this._canvas.style.background = 'transparent'; + this._canvas.style.position = 'absolute'; + this._canvas.style.top = '0'; + this._canvas.style.left = '0'; + } + + /** + * get canvas context + * @method + * @name Konva.Canvas#getContext + * @returns {CanvasContext} context + */ + getContext() { + return this.context; + } + getPixelRatio() { + return this.pixelRatio; + } + setPixelRatio(pixelRatio) { + const previousRatio = this.pixelRatio; + this.pixelRatio = pixelRatio; + this.setSize( + this.getWidth() / previousRatio, + this.getHeight() / previousRatio + ); + } + setWidth(width) { + // take into account pixel ratio + this.width = this._canvas.width = width * this.pixelRatio; + this._canvas.style.width = width + 'px'; + + const pixelRatio = this.pixelRatio, + _context = this.getContext()._context; + _context.scale(pixelRatio, pixelRatio); + } + setHeight(height) { + // take into account pixel ratio + this.height = this._canvas.height = height * this.pixelRatio; + this._canvas.style.height = height + 'px'; + const pixelRatio = this.pixelRatio, + _context = this.getContext()._context; + _context.scale(pixelRatio, pixelRatio); + } + getWidth() { + return this.width; + } + getHeight() { + return this.height; + } + setSize(width, height) { + this.setWidth(width || 0); + this.setHeight(height || 0); + } + /** + * to data url + * @method + * @name Konva.Canvas#toDataURL + * @param {String} mimeType + * @param {Number} quality between 0 and 1 for jpg mime types + * @returns {String} data url string + */ + toDataURL(mimeType, quality) { + try { + // If this call fails (due to browser bug, like in Firefox 3.6), + // then revert to previous no-parameter image/png behavior + return this._canvas.toDataURL(mimeType, quality); + } catch (e) { + try { + return this._canvas.toDataURL(); + } catch (err: any) { + Util.error( + 'Unable to get data URL. ' + + err.message + + ' For more info read https://konvajs.org/docs/posts/Tainted_Canvas.html.' + ); + return ''; + } + } + } +} + +/** + * get/set pixel ratio. + * KonvaJS automatically handles pixel ratio adustments in order to render crisp drawings + * on all devices. Most desktops, low end tablets, and low end phones, have device pixel ratios + * of 1. Some high end tablets and phones, like iPhones and iPads have a device pixel ratio + * of 2. Some Macbook Pros, and iMacs also have a device pixel ratio of 2. Some high end Android devices have pixel + * ratios of 2 or 3. Some browsers like Firefox allow you to configure the pixel ratio of the viewport. Unless otherwise + * specificed, the pixel ratio will be defaulted to the actual device pixel ratio. You can override the device pixel + * ratio for special situations, or, if you don't want the pixel ratio to be taken into account, you can set it to 1. + * @name Konva.Canvas#pixelRatio + * @method + * @param {Number} pixelRatio + * @returns {Number} + * @example + * // get + * var pixelRatio = layer.getCanvas.pixelRatio(); + * + * // set + * layer.getCanvas().pixelRatio(3); + */ +Factory.addGetterSetter(Canvas, 'pixelRatio', undefined, getNumberValidator()); + +export class SceneCanvas extends Canvas { + constructor( + config: ICanvasConfig = { width: 0, height: 0, willReadFrequently: false } + ) { + super(config); + this.context = new SceneContext(this, { + willReadFrequently: config.willReadFrequently, + }); + this.setSize(config.width, config.height); + } +} + +export class HitCanvas extends Canvas { + hitCanvas = true; + constructor(config: ICanvasConfig = { width: 0, height: 0 }) { + super(config); + + this.context = new HitContext(this); + this.setSize(config.width, config.height); + } +} diff --git a/src/Container.ts b/src/Container.ts new file mode 100644 index 000000000..73d77a0b3 --- /dev/null +++ b/src/Container.ts @@ -0,0 +1,658 @@ +import { Factory } from './Factory'; +import { Node, NodeConfig } from './Node'; +import { getNumberValidator } from './Validators'; + +import { GetSet, IRect } from './types'; +import { Shape } from './Shape'; +import { HitCanvas, SceneCanvas } from './Canvas'; +import { SceneContext } from './Context'; + +export type ClipFuncOutput = + | void + | [Path2D | CanvasFillRule] + | [Path2D, CanvasFillRule]; +export interface ContainerConfig extends NodeConfig { + clearBeforeDraw?: boolean; + clipFunc?: (ctx: SceneContext) => ClipFuncOutput; + clipX?: number; + clipY?: number; + clipWidth?: number; + clipHeight?: number; +} + +/** + * Container constructor.  Containers are used to contain nodes or other containers + * @constructor + * @memberof Konva + * @augments Konva.Node + * @abstract + * @param {Object} config + * @@nodeParams + * @@containerParams + */ +export abstract class Container< + ChildType extends Node = Node +> extends Node { + children: Array = []; + + /** + * returns an array of direct descendant nodes + * @method + * @name Konva.Container#getChildren + * @param {Function} [filterFunc] filter function + * @returns {Array} + * @example + * // get all children + * var children = layer.getChildren(); + * + * // get only circles + * var circles = layer.getChildren(function(node){ + * return node.getClassName() === 'Circle'; + * }); + */ + getChildren(filterFunc?: (item: Node) => boolean) { + if (!filterFunc) { + return this.children || []; + } + + const children = this.children || []; + const results: Array = []; + children.forEach(function (child) { + if (filterFunc(child)) { + results.push(child); + } + }); + return results; + } + /** + * determine if node has children + * @method + * @name Konva.Container#hasChildren + * @returns {Boolean} + */ + hasChildren() { + return this.getChildren().length > 0; + } + /** + * remove all children. Children will be still in memory. + * If you want to completely destroy all children please use "destroyChildren" method instead + * @method + * @name Konva.Container#removeChildren + */ + removeChildren() { + this.getChildren().forEach((child) => { + // reset parent to prevent many _setChildrenIndices calls + child.parent = null; + child.index = 0; + child.remove(); + }); + this.children = []; + // because all children were detached from parent, request draw via container + this._requestDraw(); + return this; + } + /** + * destroy all children nodes. + * @method + * @name Konva.Container#destroyChildren + */ + destroyChildren() { + this.getChildren().forEach((child) => { + // reset parent to prevent many _setChildrenIndices calls + child.parent = null; + child.index = 0; + child.destroy(); + }); + this.children = []; + // because all children were detached from parent, request draw via container + this._requestDraw(); + return this; + } + abstract _validateAdd(node: Node): void; + /** + * add a child and children into container + * @name Konva.Container#add + * @method + * @param {...Konva.Node} children + * @returns {Container} + * @example + * layer.add(rect); + * layer.add(shape1, shape2, shape3); + * // empty arrays are accepted, though each individual child must be defined + * layer.add(...shapes); + * // remember to redraw layer if you changed something + * layer.draw(); + */ + add(...children: ChildType[]) { + if (children.length === 0) { + return this; + } + if (children.length > 1) { + for (let i = 0; i < children.length; i++) { + this.add(children[i]); + } + return this; + } + const child = children[0]; + if (child.getParent()) { + child.moveTo(this); + return this; + } + this._validateAdd(child); + child.index = this.getChildren().length; + child.parent = this; + child._clearCaches(); + this.getChildren().push(child); + this._fire('add', { + child: child, + }); + this._requestDraw(); + // chainable + return this; + } + destroy() { + if (this.hasChildren()) { + this.destroyChildren(); + } + super.destroy(); + return this; + } + /** + * return an array of nodes that match the selector. + * You can provide a string with '#' for id selections and '.' for name selections. + * Or a function that will return true/false when a node is passed through. See example below. + * With strings you can also select by type or class name. Pass multiple selectors + * separated by a comma. + * @method + * @name Konva.Container#find + * @param {String | Function} selector + * @returns {Array} + * @example + * + * Passing a string as a selector + * // select node with id foo + * var node = stage.find('#foo'); + * + * // select nodes with name bar inside layer + * var nodes = layer.find('.bar'); + * + * // select all groups inside layer + * var nodes = layer.find('Group'); + * + * // select all rectangles inside layer + * var nodes = layer.find('Rect'); + * + * // select node with an id of foo or a name of bar inside layer + * var nodes = layer.find('#foo, .bar'); + * + * Passing a function as a selector + * + * // get all groups with a function + * var groups = stage.find(node => { + * return node.getType() === 'Group'; + * }); + * + * // get only Nodes with partial opacity + * var alphaNodes = layer.find(node => { + * return node.getType() === 'Node' && node.getAbsoluteOpacity() < 1; + * }); + */ + find(selector): Array { + // protecting _generalFind to prevent user from accidentally adding + // second argument and getting unexpected `findOne` result + return this._generalFind(selector, false); + } + /** + * return a first node from `find` method + * @method + * @name Konva.Container#findOne + * @param {String | Function} selector + * @returns {Konva.Node | Undefined} + * @example + * // select node with id foo + * var node = stage.findOne('#foo'); + * + * // select node with name bar inside layer + * var nodes = layer.findOne('.bar'); + * + * // select the first node to return true in a function + * var node = stage.findOne(node => { + * return node.getType() === 'Shape' + * }) + */ + findOne( + selector: string | Function + ): ChildNode | undefined { + const result = this._generalFind(selector, true); + return result.length > 0 ? result[0] : undefined; + } + _generalFind( + selector: string | Function, + findOne: boolean + ) { + const retArr: Array = []; + + this._descendants((node) => { + const valid = node._isMatch(selector); + if (valid) { + retArr.push(node as unknown as ChildNode); + } + if (valid && findOne) { + return true; + } + return false; + }); + + return retArr; + } + private _descendants(fn: (n: Node) => boolean) { + let shouldStop = false; + const children = this.getChildren(); + for (const child of children) { + shouldStop = fn(child); + if (shouldStop) { + return true; + } + if (!child.hasChildren()) { + continue; + } + shouldStop = (child as unknown as Container)._descendants(fn); + if (shouldStop) { + return true; + } + } + return false; + } + // extenders + toObject() { + const obj = Node.prototype.toObject.call(this); + + obj.children = []; + + this.getChildren().forEach((child) => { + obj.children!.push(child.toObject()); + }); + + return obj; + } + /** + * determine if node is an ancestor + * of descendant + * @method + * @name Konva.Container#isAncestorOf + * @param {Konva.Node} node + */ + isAncestorOf(node: Node) { + let parent = node.getParent(); + while (parent) { + if (parent._id === this._id) { + return true; + } + parent = parent.getParent(); + } + + return false; + } + clone(obj?: any) { + // call super method + const node = Node.prototype.clone.call(this, obj); + + this.getChildren().forEach(function (no) { + node.add(no.clone()); + }); + return node as this; + } + /** + * get all shapes that intersect a point. Note: because this method must clear a temporary + * canvas and redraw every shape inside the container, it should only be used for special situations + * because it performs very poorly. Please use the {@link Konva.Stage#getIntersection} method if at all possible + * because it performs much better + * nodes with listening set to false will not be detected + * @method + * @name Konva.Container#getAllIntersections + * @param {Object} pos + * @param {Number} pos.x + * @param {Number} pos.y + * @returns {Array} array of shapes + */ + getAllIntersections(pos) { + const arr: Shape[] = []; + + this.find('Shape').forEach((shape) => { + if (shape.isVisible() && shape.intersects(pos)) { + arr.push(shape); + } + }); + + return arr; + } + _clearSelfAndDescendantCache(attr?: string) { + super._clearSelfAndDescendantCache(attr); + // skip clearing if node is cached with canvas + // for performance reasons !!! + if (this.isCached()) { + return; + } + this.children?.forEach(function (node) { + node._clearSelfAndDescendantCache(attr); + }); + } + _setChildrenIndices() { + this.children?.forEach(function (child, n) { + child.index = n; + }); + this._requestDraw(); + } + drawScene(can?: SceneCanvas, top?: Node, bufferCanvas?: SceneCanvas) { + const layer = this.getLayer()!, + canvas = can || (layer && layer.getCanvas()), + context = canvas && canvas.getContext(), + cachedCanvas = this._getCanvasCache(), + cachedSceneCanvas = cachedCanvas && cachedCanvas.scene; + + const caching = canvas && canvas.isCache; + if (!this.isVisible() && !caching) { + return this; + } + + if (cachedSceneCanvas) { + context.save(); + const m = this.getAbsoluteTransform(top).getMatrix(); + context.transform(m[0], m[1], m[2], m[3], m[4], m[5]); + this._drawCachedSceneCanvas(context); + context.restore(); + } else { + this._drawChildren('drawScene', canvas, top, bufferCanvas); + } + return this; + } + drawHit(can?: HitCanvas, top?: Node) { + if (!this.shouldDrawHit(top)) { + return this; + } + + const layer = this.getLayer()!, + canvas = can || (layer && layer.hitCanvas), + context = canvas && canvas.getContext(), + cachedCanvas = this._getCanvasCache(), + cachedHitCanvas = cachedCanvas && cachedCanvas.hit; + + if (cachedHitCanvas) { + context.save(); + const m = this.getAbsoluteTransform(top).getMatrix(); + context.transform(m[0], m[1], m[2], m[3], m[4], m[5]); + this._drawCachedHitCanvas(context); + context.restore(); + } else { + this._drawChildren('drawHit', canvas, top); + } + return this; + } + _drawChildren(drawMethod, canvas, top, bufferCanvas?) { + const context = canvas && canvas.getContext(), + clipWidth = this.clipWidth(), + clipHeight = this.clipHeight(), + clipFunc = this.clipFunc(), + hasClip = + (typeof clipWidth === 'number' && typeof clipHeight === 'number') || + clipFunc; + + const selfCache = top === this; + + if (hasClip) { + context.save(); + const transform = this.getAbsoluteTransform(top); + let m = transform.getMatrix(); + context.transform(m[0], m[1], m[2], m[3], m[4], m[5]); + context.beginPath(); + let clipArgs; + if (clipFunc) { + clipArgs = clipFunc.call(this, context, this); + } else { + const clipX = this.clipX(); + const clipY = this.clipY(); + context.rect(clipX || 0, clipY || 0, clipWidth, clipHeight); + } + context.clip.apply(context, clipArgs); + m = transform.copy().invert().getMatrix(); + context.transform(m[0], m[1], m[2], m[3], m[4], m[5]); + } + + const hasComposition = + !selfCache && + this.globalCompositeOperation() !== 'source-over' && + drawMethod === 'drawScene'; + + if (hasComposition) { + context.save(); + context._applyGlobalCompositeOperation(this); + } + + this.children?.forEach(function (child) { + child[drawMethod](canvas, top, bufferCanvas); + }); + if (hasComposition) { + context.restore(); + } + + if (hasClip) { + context.restore(); + } + } + + getClientRect( + config: { + skipTransform?: boolean; + skipShadow?: boolean; + skipStroke?: boolean; + relativeTo?: Container; + } = {} + ): IRect { + const skipTransform = config.skipTransform; + const relativeTo = config.relativeTo; + + let minX, minY, maxX, maxY; + let selfRect = { + x: Infinity, + y: Infinity, + width: 0, + height: 0, + }; + const that = this; + this.children?.forEach(function (child) { + // skip invisible children + if (!child.visible()) { + return; + } + + const rect = child.getClientRect({ + relativeTo: that, + skipShadow: config.skipShadow, + skipStroke: config.skipStroke, + }); + + // skip invisible children (like empty groups) + if (rect.width === 0 && rect.height === 0) { + return; + } + + if (minX === undefined) { + // initial value for first child + minX = rect.x; + minY = rect.y; + maxX = rect.x + rect.width; + maxY = rect.y + rect.height; + } else { + minX = Math.min(minX, rect.x); + minY = Math.min(minY, rect.y); + maxX = Math.max(maxX, rect.x + rect.width); + maxY = Math.max(maxY, rect.y + rect.height); + } + }); + + // if child is group we need to make sure it has visible shapes inside + const shapes = this.find('Shape'); + let hasVisible = false; + for (let i = 0; i < shapes.length; i++) { + const shape = shapes[i]; + if (shape._isVisible(this)) { + hasVisible = true; + break; + } + } + if (hasVisible && minX !== undefined) { + selfRect = { + x: minX, + y: minY, + width: maxX - minX, + height: maxY - minY, + }; + } else { + selfRect = { + x: 0, + y: 0, + width: 0, + height: 0, + }; + } + + if (!skipTransform) { + return this._transformedRect(selfRect, relativeTo); + } + return selfRect; + } + + clip: GetSet; + clipX: GetSet; + clipY: GetSet; + clipWidth: GetSet; + clipHeight: GetSet; + // there was "this" instead of "Container", + // but it breaks react-konva types: https://github.com/konvajs/react-konva/issues/390 + clipFunc: GetSet< + (ctx: CanvasRenderingContext2D, shape: Container) => ClipFuncOutput, + this + >; +} + +// add getters setters +Factory.addComponentsGetterSetter(Container, 'clip', [ + 'x', + 'y', + 'width', + 'height', +]); +/** + * get/set clip + * @method + * @name Konva.Container#clip + * @param {Object} clip + * @param {Number} clip.x + * @param {Number} clip.y + * @param {Number} clip.width + * @param {Number} clip.height + * @returns {Object} + * @example + * // get clip + * var clip = container.clip(); + * + * // set clip + * container.clip({ + * x: 20, + * y: 20, + * width: 20, + * height: 20 + * }); + */ + +Factory.addGetterSetter(Container, 'clipX', undefined, getNumberValidator()); +/** + * get/set clip x + * @name Konva.Container#clipX + * @method + * @param {Number} x + * @returns {Number} + * @example + * // get clip x + * var clipX = container.clipX(); + * + * // set clip x + * container.clipX(10); + */ + +Factory.addGetterSetter(Container, 'clipY', undefined, getNumberValidator()); +/** + * get/set clip y + * @name Konva.Container#clipY + * @method + * @param {Number} y + * @returns {Number} + * @example + * // get clip y + * var clipY = container.clipY(); + * + * // set clip y + * container.clipY(10); + */ + +Factory.addGetterSetter( + Container, + 'clipWidth', + undefined, + getNumberValidator() +); +/** + * get/set clip width + * @name Konva.Container#clipWidth + * @method + * @param {Number} width + * @returns {Number} + * @example + * // get clip width + * var clipWidth = container.clipWidth(); + * + * // set clip width + * container.clipWidth(100); + */ + +Factory.addGetterSetter( + Container, + 'clipHeight', + undefined, + getNumberValidator() +); +/** + * get/set clip height + * @name Konva.Container#clipHeight + * @method + * @param {Number} height + * @returns {Number} + * @example + * // get clip height + * var clipHeight = container.clipHeight(); + * + * // set clip height + * container.clipHeight(100); + */ + +Factory.addGetterSetter(Container, 'clipFunc'); +/** + * get/set clip function + * @name Konva.Container#clipFunc + * @method + * @param {Function} function + * @returns {Function} + * @example + * // get clip function + * var clipFunction = container.clipFunc(); + * + * // set clip function + * container.clipFunc(function(ctx) { + * ctx.rect(0, 0, 100, 100); + * }); + * + * container.clipFunc(function(ctx) { + * // optionally return a clip Path2D and clip-rule or just the clip-rule + * return [new Path2D('M0 0v50h50Z'), 'evenodd'] + * }); + */ diff --git a/src/Context.ts b/src/Context.ts new file mode 100644 index 000000000..aeac2e07b --- /dev/null +++ b/src/Context.ts @@ -0,0 +1,989 @@ +import { Util } from './Util'; +import { Konva } from './Global'; +import { Canvas } from './Canvas'; +import { Shape } from './Shape'; +import { IRect } from './types'; +import type { Node } from './Node'; + +function simplifyArray(arr: Array) { + const retArr: Array = [], + len = arr.length, + util = Util; + + for (let n = 0; n < len; n++) { + let val = arr[n]; + if (util._isNumber(val)) { + val = Math.round(val * 1000) / 1000; + } else if (!util._isString(val)) { + val = val + ''; + } + + retArr.push(val); + } + + return retArr; +} + +const COMMA = ',', + OPEN_PAREN = '(', + CLOSE_PAREN = ')', + OPEN_PAREN_BRACKET = '([', + CLOSE_BRACKET_PAREN = '])', + SEMICOLON = ';', + DOUBLE_PAREN = '()', + // EMPTY_STRING = '', + EQUALS = '=', + // SET = 'set', + CONTEXT_METHODS = [ + 'arc', + 'arcTo', + 'beginPath', + 'bezierCurveTo', + 'clearRect', + 'clip', + 'closePath', + 'createLinearGradient', + 'createPattern', + 'createRadialGradient', + 'drawImage', + 'ellipse', + 'fill', + 'fillText', + 'getImageData', + 'createImageData', + 'lineTo', + 'moveTo', + 'putImageData', + 'quadraticCurveTo', + 'rect', + 'roundRect', + 'restore', + 'rotate', + 'save', + 'scale', + 'setLineDash', + 'setTransform', + 'stroke', + 'strokeText', + 'transform', + 'translate', + ]; + +const CONTEXT_PROPERTIES = [ + 'fillStyle', + 'strokeStyle', + 'shadowColor', + 'shadowBlur', + 'shadowOffsetX', + 'shadowOffsetY', + 'letterSpacing', + 'lineCap', + 'lineDashOffset', + 'lineJoin', + 'lineWidth', + 'miterLimit', + 'direction', + 'font', + 'textAlign', + 'textBaseline', + 'globalAlpha', + 'globalCompositeOperation', + 'imageSmoothingEnabled', +] as const; + +const traceArrMax = 100; + +interface ExtendedCanvasRenderingContext2D extends CanvasRenderingContext2D { + letterSpacing: string; +} + +/** + * Konva wrapper around native 2d canvas context. It has almost the same API of 2d context with some additional functions. + * With core Konva shapes you don't need to use this object. But you will use it if you want to create + * a [custom shape](/docs/react/Custom_Shape.html) or a [custom hit regions](/docs/events/Custom_Hit_Region.html). + * For full information about each 2d context API use [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D) + * @constructor + * @memberof Konva + * @example + * const rect = new Konva.Shape({ + * fill: 'red', + * width: 100, + * height: 100, + * sceneFunc: (ctx, shape) => { + * // ctx - is context wrapper + * // shape - is instance of Konva.Shape, so it equals to "rect" variable + * ctx.rect(0, 0, shape.getAttr('width'), shape.getAttr('height')); + * + * // automatically fill shape from props and draw hit region + * ctx.fillStrokeShape(shape); + * } + * }) + */ +export class Context { + canvas: Canvas; + _context: CanvasRenderingContext2D; + traceArr: Array; + + constructor(canvas: Canvas) { + this.canvas = canvas; + + if (Konva.enableTrace) { + this.traceArr = []; + this._enableTrace(); + } + } + + /** + * fill shape + * @method + * @name Konva.Context#fillShape + * @param {Konva.Shape} shape + */ + fillShape(shape: Shape) { + if (shape.fillEnabled()) { + this._fill(shape); + } + } + + _fill(shape: Shape) { + // abstract + } + /** + * stroke shape + * @method + * @name Konva.Context#strokeShape + * @param {Konva.Shape} shape + */ + strokeShape(shape: Shape) { + if (shape.hasStroke()) { + this._stroke(shape); + } + } + + _stroke(shape: Shape) { + // abstract + } + + /** + * fill then stroke + * @method + * @name Konva.Context#fillStrokeShape + * @param {Konva.Shape} shape + */ + fillStrokeShape(shape: Shape) { + if (shape.attrs.fillAfterStrokeEnabled) { + this.strokeShape(shape); + this.fillShape(shape); + } else { + this.fillShape(shape); + this.strokeShape(shape); + } + } + + getTrace(relaxed?: boolean, rounded?: boolean) { + let traceArr = this.traceArr, + len = traceArr.length, + str = '', + n, + trace, + method, + args; + + for (n = 0; n < len; n++) { + trace = traceArr[n]; + method = trace.method; + + // methods + if (method) { + args = trace.args; + str += method; + if (relaxed) { + str += DOUBLE_PAREN; + } else { + if (Util._isArray(args[0])) { + str += OPEN_PAREN_BRACKET + args.join(COMMA) + CLOSE_BRACKET_PAREN; + } else { + if (rounded) { + args = args.map((a) => + typeof a === 'number' ? Math.floor(a) : a + ); + } + str += OPEN_PAREN + args.join(COMMA) + CLOSE_PAREN; + } + } + } else { + // properties + str += trace.property; + if (!relaxed) { + str += EQUALS + trace.val; + } + } + + str += SEMICOLON; + } + + return str; + } + + clearTrace() { + this.traceArr = []; + } + _trace(str) { + let traceArr = this.traceArr, + len; + + traceArr.push(str); + len = traceArr.length; + + if (len >= traceArrMax) { + traceArr.shift(); + } + } + /** + * reset canvas context transform + * @method + * @name Konva.Context#reset + */ + reset() { + const pixelRatio = this.getCanvas().getPixelRatio(); + this.setTransform(1 * pixelRatio, 0, 0, 1 * pixelRatio, 0, 0); + } + /** + * get canvas wrapper + * @method + * @name Konva.Context#getCanvas + * @returns {Konva.Canvas} + */ + getCanvas() { + return this.canvas; + } + /** + * clear canvas + * @method + * @name Konva.Context#clear + * @param {Object} [bounds] + * @param {Number} [bounds.x] + * @param {Number} [bounds.y] + * @param {Number} [bounds.width] + * @param {Number} [bounds.height] + */ + clear(bounds?: IRect) { + const canvas = this.getCanvas(); + + if (bounds) { + this.clearRect( + bounds.x || 0, + bounds.y || 0, + bounds.width || 0, + bounds.height || 0 + ); + } else { + this.clearRect( + 0, + 0, + canvas.getWidth() / canvas.pixelRatio, + canvas.getHeight() / canvas.pixelRatio + ); + } + } + _applyLineCap(shape: Shape) { + const lineCap = shape.attrs.lineCap; + if (lineCap) { + this.setAttr('lineCap', lineCap); + } + } + _applyOpacity(shape: Node) { + const absOpacity = shape.getAbsoluteOpacity(); + if (absOpacity !== 1) { + this.setAttr('globalAlpha', absOpacity); + } + } + _applyLineJoin(shape: Shape) { + const lineJoin = shape.attrs.lineJoin; + if (lineJoin) { + this.setAttr('lineJoin', lineJoin); + } + } + + setAttr(attr: string, val) { + this._context[attr] = val; + } + + /** + * arc function. + * @method + * @name Konva.Context#arc + */ + arc( + x: number, + y: number, + radius: number, + startAngle: number, + endAngle: number, + counterClockwise?: boolean + ) { + this._context.arc(x, y, radius, startAngle, endAngle, counterClockwise); + } + /** + * arcTo function. + * @method + * @name Konva.Context#arcTo + * + */ + arcTo(x1: number, y1: number, x2: number, y2: number, radius: number) { + this._context.arcTo(x1, y1, x2, y2, radius); + } + /** + * beginPath function. + * @method + * @name Konva.Context#beginPath + */ + beginPath() { + this._context.beginPath(); + } + /** + * bezierCurveTo function. + * @method + * @name Konva.Context#bezierCurveTo + */ + + bezierCurveTo( + cp1x: number, + cp1y: number, + cp2x: number, + cp2y: number, + x: number, + y: number + ) { + this._context.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y); + } + /** + * clearRect function. + * @method + * @name Konva.Context#clearRect + */ + clearRect(x: number, y: number, width: number, height: number) { + this._context.clearRect(x, y, width, height); + } + /** + * clip function. + * @method + * @name Konva.Context#clip + */ + clip(fillRule?: CanvasFillRule): void; + clip(path: Path2D, fillRule?: CanvasFillRule): void; + clip(...args: any[]) { + this._context.clip.apply(this._context, args as any); + } + /** + * closePath function. + * @method + * @name Konva.Context#closePath + */ + closePath() { + this._context.closePath(); + } + /** + * createImageData function. + * @method + * @name Konva.Context#createImageData + */ + createImageData(width, height) { + const a = arguments; + if (a.length === 2) { + return this._context.createImageData(width, height); + } else if (a.length === 1) { + return this._context.createImageData(width); + } + } + /** + * createLinearGradient function. + * @method + * @name Konva.Context#createLinearGradient + */ + createLinearGradient(x0: number, y0: number, x1: number, y1: number) { + return this._context.createLinearGradient(x0, y0, x1, y1); + } + /** + * createPattern function. + * @method + * @name Konva.Context#createPattern + */ + createPattern(image: CanvasImageSource, repetition: string | null) { + return this._context.createPattern(image, repetition); + } + /** + * createRadialGradient function. + * @method + * @name Konva.Context#createRadialGradient + */ + createRadialGradient( + x0: number, + y0: number, + r0: number, + x1: number, + y1: number, + r1: number + ) { + return this._context.createRadialGradient(x0, y0, r0, x1, y1, r1); + } + /** + * drawImage function. + * @method + * @name Konva.Context#drawImage + */ + drawImage( + image: CanvasImageSource, + sx: number, + sy: number, + sWidth?: number, + sHeight?: number, + dx?: number, + dy?: number, + dWidth?: number, + dHeight?: number + ) { + // this._context.drawImage(...arguments); + const a = arguments, + _context = this._context; + if (a.length === 3) { + _context.drawImage(image, sx, sy); + } else if (a.length === 5) { + _context.drawImage(image, sx, sy, sWidth as number, sHeight as number); + } else if (a.length === 9) { + _context.drawImage( + image, + sx, + sy, + sWidth as number, + sHeight as number, + dx as number, + dy as number, + dWidth as number, + dHeight as number + ); + } + } + /** + * ellipse function. + * @method + * @name Konva.Context#ellipse + */ + ellipse( + x: number, + y: number, + radiusX: number, + radiusY: number, + rotation: number, + startAngle: number, + endAngle: number, + counterclockwise?: boolean + ) { + this._context.ellipse( + x, + y, + radiusX, + radiusY, + rotation, + startAngle, + endAngle, + counterclockwise + ); + } + /** + * isPointInPath function. + * @method + * @name Konva.Context#isPointInPath + */ + isPointInPath( + x: number, + y: number, + path?: Path2D, + fillRule?: CanvasFillRule + ) { + if (path) { + return this._context.isPointInPath(path, x, y, fillRule); + } + return this._context.isPointInPath(x, y, fillRule); + } + /** + * fill function. + * @method + * @name Konva.Context#fill + */ + fill(fillRule?: CanvasFillRule): void; + fill(path: Path2D, fillRule?: CanvasFillRule): void; + fill(...args: any[]) { + // this._context.fill(); + this._context.fill.apply(this._context, args as any); + } + /** + * fillRect function. + * @method + * @name Konva.Context#fillRect + */ + fillRect(x: number, y: number, width: number, height: number) { + this._context.fillRect(x, y, width, height); + } + /** + * strokeRect function. + * @method + * @name Konva.Context#strokeRect + */ + strokeRect(x: number, y: number, width: number, height: number) { + this._context.strokeRect(x, y, width, height); + } + /** + * fillText function. + * @method + * @name Konva.Context#fillText + */ + fillText(text: string, x: number, y: number, maxWidth?: number) { + if (maxWidth) { + this._context.fillText(text, x, y, maxWidth); + } else { + this._context.fillText(text, x, y); + } + } + /** + * measureText function. + * @method + * @name Konva.Context#measureText + */ + measureText(text: string) { + return this._context.measureText(text); + } + /** + * getImageData function. + * @method + * @name Konva.Context#getImageData + */ + getImageData(sx: number, sy: number, sw: number, sh: number) { + return this._context.getImageData(sx, sy, sw, sh); + } + /** + * lineTo function. + * @method + * @name Konva.Context#lineTo + */ + lineTo(x: number, y: number) { + this._context.lineTo(x, y); + } + /** + * moveTo function. + * @method + * @name Konva.Context#moveTo + */ + moveTo(x: number, y: number) { + this._context.moveTo(x, y); + } + /** + * rect function. + * @method + * @name Konva.Context#rect + */ + rect(x: number, y: number, width: number, height: number) { + this._context.rect(x, y, width, height); + } + /** + * roundRect function. + * @method + * @name Konva.Context#roundRect + */ + roundRect( + x: number, + y: number, + width: number, + height: number, + radii: number | DOMPointInit | (number | DOMPointInit)[] + ) { + this._context.roundRect(x, y, width, height, radii); + } + /** + * putImageData function. + * @method + * @name Konva.Context#putImageData + */ + putImageData(imageData: ImageData, dx: number, dy: number) { + this._context.putImageData(imageData, dx, dy); + } + /** + * quadraticCurveTo function. + * @method + * @name Konva.Context#quadraticCurveTo + */ + quadraticCurveTo(cpx: number, cpy: number, x: number, y: number) { + this._context.quadraticCurveTo(cpx, cpy, x, y); + } + /** + * restore function. + * @method + * @name Konva.Context#restore + */ + restore() { + this._context.restore(); + } + /** + * rotate function. + * @method + * @name Konva.Context#rotate + */ + rotate(angle: number) { + this._context.rotate(angle); + } + /** + * save function. + * @method + * @name Konva.Context#save + */ + save() { + this._context.save(); + } + /** + * scale function. + * @method + * @name Konva.Context#scale + */ + scale(x: number, y: number) { + this._context.scale(x, y); + } + /** + * setLineDash function. + * @method + * @name Konva.Context#setLineDash + */ + setLineDash(segments: number[]) { + // works for Chrome and IE11 + if (this._context.setLineDash) { + this._context.setLineDash(segments); + } else if ('mozDash' in this._context) { + // verified that this works in firefox + (this._context['mozDash']) = segments; + } else if ('webkitLineDash' in this._context) { + // does not currently work for Safari + (this._context['webkitLineDash']) = segments; + } + + // no support for IE9 and IE10 + } + /** + * getLineDash function. + * @method + * @name Konva.Context#getLineDash + */ + getLineDash() { + return this._context.getLineDash(); + } + /** + * setTransform function. + * @method + * @name Konva.Context#setTransform + */ + setTransform( + a: number, + b: number, + c: number, + d: number, + e: number, + f: number + ) { + this._context.setTransform(a, b, c, d, e, f); + } + /** + * stroke function. + * @method + * @name Konva.Context#stroke + */ + stroke(path2d?: Path2D) { + if (path2d) { + this._context.stroke(path2d); + } else { + this._context.stroke(); + } + } + /** + * strokeText function. + * @method + * @name Konva.Context#strokeText + */ + strokeText(text: string, x: number, y: number, maxWidth?: number) { + this._context.strokeText(text, x, y, maxWidth); + } + /** + * transform function. + * @method + * @name Konva.Context#transform + */ + transform(a: number, b: number, c: number, d: number, e: number, f: number) { + this._context.transform(a, b, c, d, e, f); + } + /** + * translate function. + * @method + * @name Konva.Context#translate + */ + translate(x: number, y: number) { + this._context.translate(x, y); + } + _enableTrace() { + let that = this, + len = CONTEXT_METHODS.length, + origSetter = this.setAttr, + n, + args; + + // to prevent creating scope function at each loop + const func = function (methodName) { + let origMethod = that[methodName], + ret; + + that[methodName] = function () { + args = simplifyArray(Array.prototype.slice.call(arguments, 0)); + ret = origMethod.apply(that, arguments); + + that._trace({ + method: methodName, + args: args, + }); + + return ret; + }; + }; + // methods + for (n = 0; n < len; n++) { + func(CONTEXT_METHODS[n]); + } + + // attrs + that.setAttr = function () { + origSetter.apply(that, arguments as any); + const prop = arguments[0]; + let val = arguments[1]; + if ( + prop === 'shadowOffsetX' || + prop === 'shadowOffsetY' || + prop === 'shadowBlur' + ) { + val = val / this.canvas.getPixelRatio(); + } + that._trace({ + property: prop, + val: val, + }); + }; + } + _applyGlobalCompositeOperation(node) { + const op = node.attrs.globalCompositeOperation; + const def = !op || op === 'source-over'; + if (!def) { + this.setAttr('globalCompositeOperation', op); + } + } +} + +// supported context properties +type CanvasContextProps = Pick< + ExtendedCanvasRenderingContext2D, + (typeof CONTEXT_PROPERTIES)[number] +>; + +export interface Context extends CanvasContextProps {} + +CONTEXT_PROPERTIES.forEach(function (prop) { + Object.defineProperty(Context.prototype, prop, { + get() { + return this._context[prop]; + }, + set(val) { + this._context[prop] = val; + }, + }); +}); + +export class SceneContext extends Context { + constructor(canvas: Canvas, { willReadFrequently = false } = {}) { + super(canvas); + this._context = canvas._canvas.getContext('2d', { + willReadFrequently, + }) as CanvasRenderingContext2D; + } + _fillColor(shape: Shape) { + const fill = shape.fill(); + + this.setAttr('fillStyle', fill); + shape._fillFunc(this); + } + _fillPattern(shape: Shape) { + this.setAttr('fillStyle', shape._getFillPattern()); + shape._fillFunc(this); + } + _fillLinearGradient(shape: Shape) { + const grd = shape._getLinearGradient(); + + if (grd) { + this.setAttr('fillStyle', grd); + shape._fillFunc(this); + } + } + _fillRadialGradient(shape: Shape) { + const grd = shape._getRadialGradient(); + if (grd) { + this.setAttr('fillStyle', grd); + shape._fillFunc(this); + } + } + _fill(shape) { + const hasColor = shape.fill(), + fillPriority = shape.getFillPriority(); + + // priority fills + if (hasColor && fillPriority === 'color') { + this._fillColor(shape); + return; + } + + const hasPattern = shape.getFillPatternImage(); + if (hasPattern && fillPriority === 'pattern') { + this._fillPattern(shape); + return; + } + + const hasLinearGradient = shape.getFillLinearGradientColorStops(); + if (hasLinearGradient && fillPriority === 'linear-gradient') { + this._fillLinearGradient(shape); + return; + } + + const hasRadialGradient = shape.getFillRadialGradientColorStops(); + if (hasRadialGradient && fillPriority === 'radial-gradient') { + this._fillRadialGradient(shape); + return; + } + + // now just try and fill with whatever is available + if (hasColor) { + this._fillColor(shape); + } else if (hasPattern) { + this._fillPattern(shape); + } else if (hasLinearGradient) { + this._fillLinearGradient(shape); + } else if (hasRadialGradient) { + this._fillRadialGradient(shape); + } + } + _strokeLinearGradient(shape) { + const start = shape.getStrokeLinearGradientStartPoint(), + end = shape.getStrokeLinearGradientEndPoint(), + colorStops = shape.getStrokeLinearGradientColorStops(), + grd = this.createLinearGradient(start.x, start.y, end.x, end.y); + + if (colorStops) { + // build color stops + for (let n = 0; n < colorStops.length; n += 2) { + grd.addColorStop(colorStops[n] as number, colorStops[n + 1] as string); + } + this.setAttr('strokeStyle', grd); + } + } + _stroke(shape) { + const dash = shape.dash(), + // ignore strokeScaleEnabled for Text + strokeScaleEnabled = shape.getStrokeScaleEnabled(); + + if (shape.hasStroke()) { + if (!strokeScaleEnabled) { + this.save(); + const pixelRatio = this.getCanvas().getPixelRatio(); + this.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0); + } + + this._applyLineCap(shape); + if (dash && shape.dashEnabled()) { + this.setLineDash(dash); + this.setAttr('lineDashOffset', shape.dashOffset()); + } + + this.setAttr('lineWidth', shape.strokeWidth()); + + if (!shape.getShadowForStrokeEnabled()) { + this.setAttr('shadowColor', 'rgba(0,0,0,0)'); + } + + const hasLinearGradient = shape.getStrokeLinearGradientColorStops(); + if (hasLinearGradient) { + this._strokeLinearGradient(shape); + } else { + this.setAttr('strokeStyle', shape.stroke()); + } + + shape._strokeFunc(this); + + if (!strokeScaleEnabled) { + this.restore(); + } + } + } + _applyShadow(shape) { + const color = shape.getShadowRGBA() ?? 'black', + blur = shape.getShadowBlur() ?? 5, + offset = shape.getShadowOffset() ?? { + x: 0, + y: 0, + }, + scale = shape.getAbsoluteScale(), + ratio = this.canvas.getPixelRatio(), + scaleX = scale.x * ratio, + scaleY = scale.y * ratio; + + this.setAttr('shadowColor', color); + this.setAttr( + 'shadowBlur', + blur * Math.min(Math.abs(scaleX), Math.abs(scaleY)) + ); + this.setAttr('shadowOffsetX', offset.x * scaleX); + this.setAttr('shadowOffsetY', offset.y * scaleY); + } +} + +export class HitContext extends Context { + constructor(canvas: Canvas) { + super(canvas); + this._context = canvas._canvas.getContext('2d', { + willReadFrequently: true, + }) as CanvasRenderingContext2D; + } + _fill(shape: Shape) { + this.save(); + this.setAttr('fillStyle', shape.colorKey); + shape._fillFuncHit(this); + this.restore(); + } + strokeShape(shape: Shape) { + if (shape.hasHitStroke()) { + this._stroke(shape); + } + } + _stroke(shape) { + if (shape.hasHitStroke()) { + // ignore strokeScaleEnabled for Text + const strokeScaleEnabled = shape.getStrokeScaleEnabled(); + if (!strokeScaleEnabled) { + this.save(); + const pixelRatio = this.getCanvas().getPixelRatio(); + this.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0); + } + this._applyLineCap(shape); + + const hitStrokeWidth = shape.hitStrokeWidth(); + const strokeWidth = + hitStrokeWidth === 'auto' ? shape.strokeWidth() : hitStrokeWidth; + + this.setAttr('lineWidth', strokeWidth); + this.setAttr('strokeStyle', shape.colorKey); + shape._strokeFuncHit(this); + if (!strokeScaleEnabled) { + this.restore(); + } + } + } +} diff --git a/src/Core.ts b/src/Core.ts new file mode 100644 index 000000000..e6eecb925 --- /dev/null +++ b/src/Core.ts @@ -0,0 +1,5 @@ +// enter file of limited Konva version with only core functions +export { Konva } from './_CoreInternals'; +import { Konva } from './_CoreInternals'; + +export default Konva; diff --git a/src/DragAndDrop.ts b/src/DragAndDrop.ts new file mode 100644 index 000000000..4bed333e5 --- /dev/null +++ b/src/DragAndDrop.ts @@ -0,0 +1,173 @@ +import { Container } from './Container'; +import { Konva } from './Global'; +import { Node } from './Node'; +import { Vector2d } from './types'; +import { Util } from './Util'; + +export const DD = { + get isDragging() { + let flag = false; + DD._dragElements.forEach((elem) => { + if (elem.dragStatus === 'dragging') { + flag = true; + } + }); + return flag; + }, + justDragged: false, + get node() { + // return first dragging node + let node: Node | undefined; + DD._dragElements.forEach((elem) => { + node = elem.node; + }); + return node; + }, + _dragElements: new Map< + number, + { + node: Node; + startPointerPos: Vector2d; + offset: Vector2d; + pointerId?: number; + // when we just put pointer down on a node + // it will create drag element + dragStatus: 'ready' | 'dragging' | 'stopped'; + // dragStarted: boolean; + // isDragging: boolean; + // dragStopped: boolean; + } + >(), + + // methods + _drag(evt) { + const nodesToFireEvents: Array = []; + DD._dragElements.forEach((elem, key) => { + const { node } = elem; + // we need to find pointer relative to that node + const stage = node.getStage()!; + stage.setPointersPositions(evt); + + // it is possible that user call startDrag without any event + // it that case we need to detect first movable pointer and attach it into the node + if (elem.pointerId === undefined) { + elem.pointerId = Util._getFirstPointerId(evt); + } + const pos = stage._changedPointerPositions.find( + (pos) => pos.id === elem.pointerId + ); + + // not related pointer + if (!pos) { + return; + } + if (elem.dragStatus !== 'dragging') { + const dragDistance = node.dragDistance(); + const distance = Math.max( + Math.abs(pos.x - elem.startPointerPos.x), + Math.abs(pos.y - elem.startPointerPos.y) + ); + if (distance < dragDistance) { + return; + } + node.startDrag({ evt }); + // a user can stop dragging inside `dragstart` + if (!node.isDragging()) { + return; + } + } + node._setDragPosition(evt, elem); + nodesToFireEvents.push(node); + }); + // call dragmove only after ALL positions are changed + nodesToFireEvents.forEach((node) => { + node.fire( + 'dragmove', + { + type: 'dragmove', + target: node, + evt: evt, + }, + true + ); + }); + }, + + // dragBefore and dragAfter allows us to set correct order of events + // setup all in dragbefore, and stop dragging only after pointerup triggered. + _endDragBefore(evt?) { + const drawNodes: Array = []; + DD._dragElements.forEach((elem) => { + const { node } = elem; + // we need to find pointer relative to that node + const stage = node.getStage()!; + if (evt) { + stage.setPointersPositions(evt); + } + + const pos = stage._changedPointerPositions.find( + (pos) => pos.id === elem.pointerId + ); + + // that pointer is not related + if (!pos) { + return; + } + + if (elem.dragStatus === 'dragging' || elem.dragStatus === 'stopped') { + // if a node is stopped manually we still need to reset events: + DD.justDragged = true; + Konva._mouseListenClick = false; + Konva._touchListenClick = false; + Konva._pointerListenClick = false; + elem.dragStatus = 'stopped'; + } + + const drawNode = + elem.node.getLayer() || + ((elem.node instanceof Konva['Stage'] && elem.node) as any); + + if (drawNode && drawNodes.indexOf(drawNode) === -1) { + drawNodes.push(drawNode); + } + }); + // draw in a sync way + // because mousemove event may trigger BEFORE batch draw is called + // but as we have not hit canvas updated yet, it will trigger incorrect mouseover/mouseout events + drawNodes.forEach((drawNode) => { + drawNode.draw(); + }); + }, + _endDragAfter(evt) { + DD._dragElements.forEach((elem, key) => { + if (elem.dragStatus === 'stopped') { + elem.node.fire( + 'dragend', + { + type: 'dragend', + target: elem.node, + evt: evt, + }, + true + ); + } + if (elem.dragStatus !== 'dragging') { + DD._dragElements.delete(key); + } + }); + }, +}; + +if (Konva.isBrowser) { + window.addEventListener('mouseup', DD._endDragBefore, true); + window.addEventListener('touchend', DD._endDragBefore, true); + // add touchcancel to fix this: https://github.com/konvajs/konva/issues/1843 + window.addEventListener('touchcancel', DD._endDragBefore, true); + + window.addEventListener('mousemove', DD._drag); + window.addEventListener('touchmove', DD._drag); + + window.addEventListener('mouseup', DD._endDragAfter, false); + window.addEventListener('touchend', DD._endDragAfter, false); + window.addEventListener('touchcancel', DD._endDragAfter, false); +} diff --git a/src/Factory.ts b/src/Factory.ts new file mode 100644 index 000000000..b1af978d2 --- /dev/null +++ b/src/Factory.ts @@ -0,0 +1,248 @@ +import { Node } from './Node'; +import { GetSet } from './types'; +import { Util } from './Util'; +import { getComponentValidator } from './Validators'; + +const GET = 'get'; +const SET = 'set'; + +/** + * Enforces that a type is a string. + */ +type EnforceString = T extends string ? T : never; + +/** + * Represents a class. + */ +type Constructor = abstract new (...args: any) => any; + +/** + * An attribute of an instance of the provided class. Attributes names be strings. + */ +type Attr = EnforceString>; + +/** + * A function that is called after a setter is called. + */ +type AfterFunc = (this: InstanceType) => void; + +/** + * Extracts the type of a GetSet. + */ +type ExtractGetSet = T extends GetSet ? U : never; + +/** + * Extracts the type of a GetSet class attribute. + */ +type Value> = ExtractGetSet< + InstanceType[U] +>; + +/** + * A function that validates a value. + */ +type ValidatorFunc = (val: ExtractGetSet, attr: string) => T; + +/** + * Extracts the "components" (keys) of a GetSet value. The value must be an object. + */ +type ExtractComponents> = Value< + T, + U +> extends Record + ? EnforceString>[] + : never; + +export const Factory = { + addGetterSetter>( + constructor: T, + attr: U, + def?: Value, + validator?: ValidatorFunc>, + after?: AfterFunc + ): void { + Factory.addGetter(constructor, attr, def); + Factory.addSetter(constructor, attr, validator, after); + Factory.addOverloadedGetterSetter(constructor, attr); + }, + addGetter>( + constructor: T, + attr: U, + def?: Value + ) { + var method = GET + Util._capitalize(attr); + + constructor.prototype[method] = + constructor.prototype[method] || + function (this: Node) { + const val = this.attrs[attr]; + return val === undefined ? def : val; + }; + }, + + addSetter>( + constructor: T, + attr: U, + validator?: ValidatorFunc>, + after?: AfterFunc + ) { + var method = SET + Util._capitalize(attr); + + if (!constructor.prototype[method]) { + Factory.overWriteSetter(constructor, attr, validator, after); + } + }, + + overWriteSetter>( + constructor: T, + attr: U, + validator?: ValidatorFunc>, + after?: AfterFunc + ) { + var method = SET + Util._capitalize(attr); + constructor.prototype[method] = function (val) { + if (validator && val !== undefined && val !== null) { + val = validator.call(this, val, attr); + } + + this._setAttr(attr, val); + + if (after) { + after.call(this); + } + + return this; + }; + }, + + addComponentsGetterSetter>( + constructor: T, + attr: U, + components: ExtractComponents, + validator?: ValidatorFunc>, + after?: AfterFunc + ) { + const len = components.length, + capitalize = Util._capitalize, + getter = GET + capitalize(attr), + setter = SET + capitalize(attr); + + // getter + constructor.prototype[getter] = function () { + const ret: Record = {}; + + for (let n = 0; n < len; n++) { + const component = components[n]; + ret[component] = this.getAttr(attr + capitalize(component)); + } + + return ret; + }; + + const basicValidator = getComponentValidator(components); + + // setter + constructor.prototype[setter] = function (val) { + const oldVal = this.attrs[attr]; + + if (validator) { + val = validator.call(this, val, attr); + } + + if (basicValidator) { + basicValidator.call(this, val, attr); + } + + for (const key in val) { + if (!val.hasOwnProperty(key)) { + continue; + } + this._setAttr(attr + capitalize(key), val[key]); + } + if (!val) { + components.forEach((component) => { + this._setAttr(attr + capitalize(component), undefined); + }); + } + + this._fireChangeEvent(attr, oldVal, val); + + if (after) { + after.call(this); + } + + return this; + }; + + Factory.addOverloadedGetterSetter(constructor, attr); + }, + addOverloadedGetterSetter>( + constructor: T, + attr: U + ) { + var capitalizedAttr = Util._capitalize(attr), + setter = SET + capitalizedAttr, + getter = GET + capitalizedAttr; + + constructor.prototype[attr] = function () { + // setting + if (arguments.length) { + this[setter](arguments[0]); + return this; + } + // getting + return this[getter](); + }; + }, + addDeprecatedGetterSetter>( + constructor: T, + attr: U, + def: Value, + validator: ValidatorFunc> + ) { + Util.error('Adding deprecated ' + attr); + + const method = GET + Util._capitalize(attr); + + const message = + attr + + ' property is deprecated and will be removed soon. Look at Konva change log for more information.'; + constructor.prototype[method] = function () { + Util.error(message); + const val = this.attrs[attr]; + return val === undefined ? def : val; + }; + Factory.addSetter(constructor, attr, validator, function () { + Util.error(message); + }); + Factory.addOverloadedGetterSetter(constructor, attr); + }, + backCompat( + constructor: T, + methods: Record + ) { + Util.each(methods, function (oldMethodName, newMethodName) { + const method = constructor.prototype[newMethodName]; + const oldGetter = GET + Util._capitalize(oldMethodName); + const oldSetter = SET + Util._capitalize(oldMethodName); + + function deprecated(this: Node) { + method.apply(this, arguments); + Util.error( + '"' + + oldMethodName + + '" method is deprecated and will be removed soon. Use ""' + + newMethodName + + '" instead.' + ); + } + + constructor.prototype[oldMethodName] = deprecated; + constructor.prototype[oldGetter] = deprecated; + constructor.prototype[oldSetter] = deprecated; + }); + }, + afterSetFilter(this: Node): void { + this._filterUpToDate = false; + }, +}; diff --git a/src/FastLayer.ts b/src/FastLayer.ts new file mode 100644 index 000000000..a7f599319 --- /dev/null +++ b/src/FastLayer.ts @@ -0,0 +1,29 @@ +import { Util } from './Util'; +import { Layer } from './Layer'; +import { _registerNode } from './Global'; + +/** + * FastLayer constructor. **DEPRECATED!** Please use `Konva.Layer({ listening: false})` instead. Layers are tied to their own canvas element and are used + * to contain shapes only. If you don't need node nesting, mouse and touch interactions, + * or event pub/sub, you should use FastLayer instead of Layer to create your layers. + * It renders about 2x faster than normal layers. + * + * @constructor + * @memberof Konva + * @augments Konva.Layer + * @@containerParams + * @example + * var layer = new Konva.FastLayer(); + */ +export class FastLayer extends Layer { + constructor(attrs) { + super(attrs); + this.listening(false); + Util.warn( + 'Konva.Fast layer is deprecated. Please use "new Konva.Layer({ listening: false })" instead.' + ); + } +} + +FastLayer.prototype.nodeType = 'FastLayer'; +_registerNode(FastLayer); diff --git a/src/Global.ts b/src/Global.ts new file mode 100644 index 000000000..9996e3981 --- /dev/null +++ b/src/Global.ts @@ -0,0 +1,195 @@ +/* + * Konva JavaScript Framework v@@version + * http://konvajs.org/ + * Licensed under the MIT + * Date: @@date + * + * Original work Copyright (C) 2011 - 2013 by Eric Rowell (KineticJS) + * Modified work Copyright (C) 2014 - present by Anton Lavrenov (Konva) + * + * @license + */ +const PI_OVER_180 = Math.PI / 180; +/** + * @namespace Konva + */ + +function detectBrowser() { + return ( + typeof window !== 'undefined' && + // browser case + ({}.toString.call(window) === '[object Window]' || + // electron case + {}.toString.call(window) === '[object global]') + ); +} + +declare const WorkerGlobalScope: any; + +export const glob: any = + typeof global !== 'undefined' + ? global + : typeof window !== 'undefined' + ? window + : typeof WorkerGlobalScope !== 'undefined' + ? self + : {}; + +export const Konva = { + _global: glob, + version: '@@version', + isBrowser: detectBrowser(), + isUnminified: /param/.test(function (param: any) {}.toString()), + dblClickWindow: 400, + getAngle(angle: number) { + return Konva.angleDeg ? angle * PI_OVER_180 : angle; + }, + enableTrace: false, + pointerEventsEnabled: true, + /** + * Should Konva automatically update canvas on any changes. Default is true. + * @property autoDrawEnabled + * @default true + * @name autoDrawEnabled + * @memberof Konva + * @example + * Konva.autoDrawEnabled = true; + */ + autoDrawEnabled: true, + /** + * Should we enable hit detection while dragging? For performance reasons, by default it is false. + * But on some rare cases you want to see hit graph and check intersections. Just set it to true. + * @property hitOnDragEnabled + * @default false + * @name hitOnDragEnabled + * @memberof Konva + * @example + * Konva.hitOnDragEnabled = true; + */ + hitOnDragEnabled: false, + /** + * Should we capture touch events and bind them to the touchstart target? That is how it works on DOM elements. + * The case: we touchstart on div1, then touchmove out of that element into another element div2. + * DOM will continue trigger touchmove events on div1 (not div2). Because events are "captured" into initial target. + * By default Konva do not do that and will trigger touchmove on another element, while pointer is moving. + * @property capturePointerEventsEnabled + * @default false + * @name capturePointerEventsEnabled + * @memberof Konva + * @example + * Konva.capturePointerEventsEnabled = true; + */ + capturePointerEventsEnabled: false, + + _mouseListenClick: false, + _touchListenClick: false, + _pointerListenClick: false, + _mouseInDblClickWindow: false, + _touchInDblClickWindow: false, + _pointerInDblClickWindow: false, + _mouseDblClickPointerId: null, + _touchDblClickPointerId: null, + _pointerDblClickPointerId: null, + _fixTextRendering: false, + + /** + * Global pixel ratio configuration. KonvaJS automatically detect pixel ratio of current device. + * But you may override such property, if you want to use your value. Set this value before any components initializations. + * @property pixelRatio + * @default undefined + * @name pixelRatio + * @memberof Konva + * @example + * // before any Konva code: + * Konva.pixelRatio = 1; + */ + pixelRatio: (typeof window !== 'undefined' && window.devicePixelRatio) || 1, + + /** + * Drag distance property. If you start to drag a node you may want to wait until pointer is moved to some distance from start point, + * only then start dragging. Default is 3px. + * @property dragDistance + * @default 0 + * @memberof Konva + * @example + * Konva.dragDistance = 10; + */ + dragDistance: 3, + /** + * Use degree values for angle properties. You may set this property to false if you want to use radian values. + * @property angleDeg + * @default true + * @memberof Konva + * @example + * node.rotation(45); // 45 degrees + * Konva.angleDeg = false; + * node.rotation(Math.PI / 2); // PI/2 radian + */ + angleDeg: true, + /** + * Show different warnings about errors or wrong API usage + * @property showWarnings + * @default true + * @memberof Konva + * @example + * Konva.showWarnings = false; + */ + showWarnings: true, + + /** + * Configure what mouse buttons can be used for drag and drop. + * Default value is [0] - only left mouse button. + * @property dragButtons + * @default true + * @memberof Konva + * @example + * // enable left and right mouse buttons + * Konva.dragButtons = [0, 2]; + */ + dragButtons: [0, 1], + + /** + * returns whether or not drag and drop is currently active + * @method + * @memberof Konva + */ + isDragging() { + return Konva['DD'].isDragging; + }, + isTransforming() { + return Konva['Transformer']?.isTransforming(); + }, + /** + * returns whether or not a drag and drop operation is ready, but may + * not necessarily have started + * @method + * @memberof Konva + */ + isDragReady() { + return !!Konva['DD'].node; + }, + /** + * Should Konva release canvas elements on destroy. Default is true. + * Useful to avoid memory leak issues in Safari on macOS/iOS. + * @property releaseCanvasOnDestroy + * @default true + * @name releaseCanvasOnDestroy + * @memberof Konva + * @example + * Konva.releaseCanvasOnDestroy = true; + */ + releaseCanvasOnDestroy: true, + // user agent + document: glob.document, + // insert Konva into global namespace (window) + // it is required for npm packages + _injectGlobal(Konva) { + glob.Konva = Konva; + }, +}; + +export const _registerNode = (NodeClass: any) => { + Konva[NodeClass.prototype.getClassName()] = NodeClass; +}; + +Konva._injectGlobal(Konva); diff --git a/src/Group.ts b/src/Group.ts new file mode 100644 index 000000000..77e83ad72 --- /dev/null +++ b/src/Group.ts @@ -0,0 +1,30 @@ +import { Util } from './Util'; +import { Container, ContainerConfig } from './Container'; +import { _registerNode } from './Global'; +import { Node } from './Node'; +import { Shape } from './Shape'; + +export interface GroupConfig extends ContainerConfig {} + +/** + * Group constructor. Groups are used to contain shapes or other groups. + * @constructor + * @memberof Konva + * @augments Konva.Container + * @param {Object} config + * @@nodeParams + * @@containerParams + * @example + * var group = new Konva.Group(); + */ +export class Group extends Container { + _validateAdd(child: Node) { + const type = child.getType(); + if (type !== 'Group' && type !== 'Shape') { + Util.throw('You may only add groups and shapes to groups.'); + } + } +} + +Group.prototype.nodeType = 'Group'; +_registerNode(Group); diff --git a/src/Layer.ts b/src/Layer.ts new file mode 100644 index 000000000..8e94c1d17 --- /dev/null +++ b/src/Layer.ts @@ -0,0 +1,544 @@ +import { Util } from './Util'; +import { Container, ContainerConfig } from './Container'; +import { Node } from './Node'; +import { Factory } from './Factory'; +import { SceneCanvas, HitCanvas } from './Canvas'; +import { Stage } from './Stage'; +import { getBooleanValidator } from './Validators'; + +import { GetSet, Vector2d } from './types'; +import { Group } from './Group'; +import { Shape, shapes } from './Shape'; +import { _registerNode } from './Global'; + +export interface LayerConfig extends ContainerConfig { + clearBeforeDraw?: boolean; + hitGraphEnabled?: boolean; + imageSmoothingEnabled?: boolean; +} + +// constants +const HASH = '#', + BEFORE_DRAW = 'beforeDraw', + DRAW = 'draw', + /* + * 2 - 3 - 4 + * | | + * 1 - 0 5 + * | + * 8 - 7 - 6 + */ + INTERSECTION_OFFSETS = [ + { x: 0, y: 0 }, // 0 + { x: -1, y: -1 }, // 2 + { x: 1, y: -1 }, // 4 + { x: 1, y: 1 }, // 6 + { x: -1, y: 1 }, // 8 + ], + INTERSECTION_OFFSETS_LEN = INTERSECTION_OFFSETS.length; + +/** + * Layer constructor. Layers are tied to their own canvas element and are used + * to contain groups or shapes. + * @constructor + * @memberof Konva + * @augments Konva.Container + * @param {Object} config + * @param {Boolean} [config.clearBeforeDraw] set this property to false if you don't want + * to clear the canvas before each layer draw. The default value is true. + * @@nodeParams + * @@containerParams + * @example + * var layer = new Konva.Layer(); + * stage.add(layer); + * // now you can add shapes, groups into the layer + */ + +export class Layer extends Container { + canvas = new SceneCanvas(); + hitCanvas = new HitCanvas({ + pixelRatio: 1, + }); + + _waitingForDraw = false; + + constructor(config?: LayerConfig) { + super(config); + this.on('visibleChange.konva', this._checkVisibility); + this._checkVisibility(); + + this.on('imageSmoothingEnabledChange.konva', this._setSmoothEnabled); + this._setSmoothEnabled(); + } + // for nodejs? + createPNGStream() { + const c = this.canvas._canvas as any; + return c.createPNGStream(); + } + /** + * get layer canvas wrapper + * @method + * @name Konva.Layer#getCanvas + */ + getCanvas() { + return this.canvas; + } + /** + * get native canvas element + * @method + * @name Konva.Layer#getNativeCanvasElement + */ + getNativeCanvasElement() { + return this.canvas._canvas; + } + /** + * get layer hit canvas + * @method + * @name Konva.Layer#getHitCanvas + */ + getHitCanvas() { + return this.hitCanvas; + } + /** + * get layer canvas context + * @method + * @name Konva.Layer#getContext + */ + getContext() { + return this.getCanvas().getContext(); + } + // TODO: deprecate this method + clear(bounds?) { + this.getContext().clear(bounds); + this.getHitCanvas().getContext().clear(bounds); + return this; + } + // extend Node.prototype.setZIndex + setZIndex(index) { + super.setZIndex(index); + const stage = this.getStage(); + if (stage && stage.content) { + stage.content.removeChild(this.getNativeCanvasElement()); + + if (index < stage.children.length - 1) { + stage.content.insertBefore( + this.getNativeCanvasElement(), + stage.children[index + 1].getCanvas()._canvas + ); + } else { + stage.content.appendChild(this.getNativeCanvasElement()); + } + } + return this; + } + moveToTop() { + Node.prototype.moveToTop.call(this); + const stage = this.getStage(); + if (stage && stage.content) { + stage.content.removeChild(this.getNativeCanvasElement()); + stage.content.appendChild(this.getNativeCanvasElement()); + } + return true; + } + moveUp() { + const moved = Node.prototype.moveUp.call(this); + if (!moved) { + return false; + } + const stage = this.getStage(); + if (!stage || !stage.content) { + return false; + } + stage.content.removeChild(this.getNativeCanvasElement()); + + if (this.index < stage.children.length - 1) { + stage.content.insertBefore( + this.getNativeCanvasElement(), + stage.children[this.index + 1].getCanvas()._canvas + ); + } else { + stage.content.appendChild(this.getNativeCanvasElement()); + } + return true; + } + // extend Node.prototype.moveDown + moveDown() { + if (Node.prototype.moveDown.call(this)) { + const stage = this.getStage(); + if (stage) { + const children = stage.children; + if (stage.content) { + stage.content.removeChild(this.getNativeCanvasElement()); + stage.content.insertBefore( + this.getNativeCanvasElement(), + children[this.index + 1].getCanvas()._canvas + ); + } + } + return true; + } + return false; + } + // extend Node.prototype.moveToBottom + moveToBottom() { + if (Node.prototype.moveToBottom.call(this)) { + const stage = this.getStage(); + if (stage) { + const children = stage.children; + if (stage.content) { + stage.content.removeChild(this.getNativeCanvasElement()); + stage.content.insertBefore( + this.getNativeCanvasElement(), + children[1].getCanvas()._canvas + ); + } + } + return true; + } + return false; + } + getLayer() { + return this; + } + remove() { + const _canvas = this.getNativeCanvasElement(); + + Node.prototype.remove.call(this); + + if (_canvas && _canvas.parentNode && Util._isInDocument(_canvas)) { + _canvas.parentNode.removeChild(_canvas); + } + return this; + } + getStage() { + return this.parent as Stage; + } + setSize({ width, height }) { + this.canvas.setSize(width, height); + this.hitCanvas.setSize(width, height); + this._setSmoothEnabled(); + return this; + } + _validateAdd(child) { + const type = child.getType(); + if (type !== 'Group' && type !== 'Shape') { + Util.throw('You may only add groups and shapes to a layer.'); + } + } + _toKonvaCanvas(config) { + config = config || {}; + config.width = config.width || this.getWidth(); + config.height = config.height || this.getHeight(); + config.x = config.x !== undefined ? config.x : this.x(); + config.y = config.y !== undefined ? config.y : this.y(); + + return Node.prototype._toKonvaCanvas.call(this, config); + } + + _checkVisibility() { + const visible = this.visible(); + if (visible) { + this.canvas._canvas.style.display = 'block'; + } else { + this.canvas._canvas.style.display = 'none'; + } + } + + _setSmoothEnabled() { + this.getContext()._context.imageSmoothingEnabled = + this.imageSmoothingEnabled(); + } + /** + * get/set width of layer. getter return width of stage. setter doing nothing. + * if you want change width use `stage.width(value);` + * @name Konva.Layer#width + * @method + * @returns {Number} + * @example + * var width = layer.width(); + */ + getWidth() { + if (this.parent) { + return this.parent.width(); + } + } + setWidth() { + Util.warn( + 'Can not change width of layer. Use "stage.width(value)" function instead.' + ); + } + /** + * get/set height of layer.getter return height of stage. setter doing nothing. + * if you want change height use `stage.height(value);` + * @name Konva.Layer#height + * @method + * @returns {Number} + * @example + * var height = layer.height(); + */ + getHeight() { + if (this.parent) { + return this.parent.height(); + } + } + setHeight() { + Util.warn( + 'Can not change height of layer. Use "stage.height(value)" function instead.' + ); + } + + /** + * batch draw. this function will not do immediate draw + * but it will schedule drawing to next tick (requestAnimFrame) + * @method + * @name Konva.Layer#batchDraw + * @return {Konva.Layer} this + */ + batchDraw() { + if (!this._waitingForDraw) { + this._waitingForDraw = true; + Util.requestAnimFrame(() => { + this.draw(); + this._waitingForDraw = false; + }); + } + return this; + } + + /** + * get visible intersection shape. This is the preferred + * method for determining if a point intersects a shape or not + * also you may pass optional selector parameter to return ancestor of intersected shape + * nodes with listening set to false will not be detected + * @method + * @name Konva.Layer#getIntersection + * @param {Object} pos + * @param {Number} pos.x + * @param {Number} pos.y + * @returns {Konva.Node} + * @example + * var shape = layer.getIntersection({x: 50, y: 50}); + */ + getIntersection(pos: Vector2d) { + if (!this.isListening() || !this.isVisible()) { + return null; + } + // in some cases antialiased area may be bigger than 1px + // it is possible if we will cache node, then scale it a lot + let spiralSearchDistance = 1; + let continueSearch = false; + while (true) { + for (let i = 0; i < INTERSECTION_OFFSETS_LEN; i++) { + const intersectionOffset = INTERSECTION_OFFSETS[i]; + const obj = this._getIntersection({ + x: pos.x + intersectionOffset.x * spiralSearchDistance, + y: pos.y + intersectionOffset.y * spiralSearchDistance, + }); + const shape = obj.shape; + if (shape) { + return shape; + } + // we should continue search if we found antialiased pixel + // that means our node somewhere very close + continueSearch = !!obj.antialiased; + // stop search if found empty pixel + if (!obj.antialiased) { + break; + } + } + // if no shape, and no antialiased pixel, we should end searching + if (continueSearch) { + spiralSearchDistance += 1; + } else { + return null; + } + } + } + _getIntersection(pos: Vector2d): { shape?: Shape; antialiased?: boolean } { + const ratio = this.hitCanvas.pixelRatio; + const p = this.hitCanvas.context.getImageData( + Math.round(pos.x * ratio), + Math.round(pos.y * ratio), + 1, + 1 + ).data; + const p3 = p[3]; + + // fully opaque pixel + if (p3 === 255) { + const colorKey = Util._rgbToHex(p[0], p[1], p[2]); + const shape = shapes[HASH + colorKey]; + if (shape) { + return { + shape: shape, + }; + } + return { + antialiased: true, + }; + } else if (p3 > 0) { + // antialiased pixel + return { + antialiased: true, + }; + } + // empty pixel + return {}; + } + drawScene(can?: SceneCanvas, top?: Node) { + const layer = this.getLayer(), + canvas = can || (layer && layer.getCanvas()); + + this._fire(BEFORE_DRAW, { + node: this, + }); + + if (this.clearBeforeDraw()) { + canvas.getContext().clear(); + } + + Container.prototype.drawScene.call(this, canvas, top); + + this._fire(DRAW, { + node: this, + }); + + return this; + } + drawHit(can?: HitCanvas, top?: Node) { + const layer = this.getLayer(), + canvas = can || (layer && layer.hitCanvas); + + if (layer && layer.clearBeforeDraw()) { + layer.getHitCanvas().getContext().clear(); + } + + Container.prototype.drawHit.call(this, canvas, top); + return this; + } + /** + * enable hit graph. **DEPRECATED!** Use `layer.listening(true)` instead. + * @name Konva.Layer#enableHitGraph + * @method + * @returns {Layer} + */ + enableHitGraph() { + this.hitGraphEnabled(true); + return this; + } + /** + * disable hit graph. **DEPRECATED!** Use `layer.listening(false)` instead. + * @name Konva.Layer#disableHitGraph + * @method + * @returns {Layer} + */ + disableHitGraph() { + this.hitGraphEnabled(false); + return this; + } + + setHitGraphEnabled(val) { + Util.warn( + 'hitGraphEnabled method is deprecated. Please use layer.listening() instead.' + ); + this.listening(val); + } + + getHitGraphEnabled(val) { + Util.warn( + 'hitGraphEnabled method is deprecated. Please use layer.listening() instead.' + ); + return this.listening(); + } + + /** + * Show or hide hit canvas over the stage. May be useful for debugging custom hitFunc + * @name Konva.Layer#toggleHitCanvas + * @method + */ + toggleHitCanvas() { + if (!this.parent || !this.parent['content']) { + return; + } + const parent = this.parent as any; + const added = !!this.hitCanvas._canvas.parentNode; + if (added) { + parent.content.removeChild(this.hitCanvas._canvas); + } else { + parent.content.appendChild(this.hitCanvas._canvas); + } + } + + destroy(): this { + Util.releaseCanvas( + this.getNativeCanvasElement(), + this.getHitCanvas()._canvas + ); + return super.destroy(); + } + + hitGraphEnabled: GetSet; + + clearBeforeDraw: GetSet; + imageSmoothingEnabled: GetSet; +} + +Layer.prototype.nodeType = 'Layer'; +_registerNode(Layer); + +/** + * get/set imageSmoothingEnabled flag + * For more info see https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/imageSmoothingEnabled + * @name Konva.Layer#imageSmoothingEnabled + * @method + * @param {Boolean} imageSmoothingEnabled + * @returns {Boolean} + * @example + * // get imageSmoothingEnabled flag + * var imageSmoothingEnabled = layer.imageSmoothingEnabled(); + * + * layer.imageSmoothingEnabled(false); + * + * layer.imageSmoothingEnabled(true); + */ +Factory.addGetterSetter(Layer, 'imageSmoothingEnabled', true); + +/** + * get/set clearBeforeDraw flag which determines if the layer is cleared or not + * before drawing + * @name Konva.Layer#clearBeforeDraw + * @method + * @param {Boolean} clearBeforeDraw + * @returns {Boolean} + * @example + * // get clearBeforeDraw flag + * var clearBeforeDraw = layer.clearBeforeDraw(); + * + * // disable clear before draw + * layer.clearBeforeDraw(false); + * + * // enable clear before draw + * layer.clearBeforeDraw(true); + */ +Factory.addGetterSetter(Layer, 'clearBeforeDraw', true); + +Factory.addGetterSetter(Layer, 'hitGraphEnabled', true, getBooleanValidator()); +/** + * get/set hitGraphEnabled flag. **DEPRECATED!** Use `layer.listening(false)` instead. + * Disabling the hit graph will greatly increase + * draw performance because the hit graph will not be redrawn each time the layer is + * drawn. This, however, also disables mouse/touch event detection + * @name Konva.Layer#hitGraphEnabled + * @method + * @param {Boolean} enabled + * @returns {Boolean} + * @example + * // get hitGraphEnabled flag + * var hitGraphEnabled = layer.hitGraphEnabled(); + * + * // disable hit graph + * layer.hitGraphEnabled(false); + * + * // enable hit graph + * layer.hitGraphEnabled(true); + */ diff --git a/src/Node.ts b/src/Node.ts new file mode 100644 index 000000000..d31723ff4 --- /dev/null +++ b/src/Node.ts @@ -0,0 +1,3328 @@ +import { Util, Transform } from './Util'; +import { Factory } from './Factory'; +import { SceneCanvas, HitCanvas, Canvas } from './Canvas'; +import { Konva } from './Global'; +import { Container } from './Container'; +import { GetSet, Vector2d, IRect } from './types'; +import { DD } from './DragAndDrop'; +import { + getNumberValidator, + getStringValidator, + getBooleanValidator, +} from './Validators'; +import { Stage } from './Stage'; +import { Context } from './Context'; +import { Shape } from './Shape'; +import { Layer } from './Layer'; + +export type Filter = (this: Node, imageData: ImageData) => void; + +type globalCompositeOperationType = + | '' + | 'source-over' + | 'source-in' + | 'source-out' + | 'source-atop' + | 'destination-over' + | 'destination-in' + | 'destination-out' + | 'destination-atop' + | 'lighter' + | 'copy' + | 'xor' + | 'multiply' + | 'screen' + | 'overlay' + | 'darken' + | 'lighten' + | 'color-dodge' + | 'color-burn' + | 'hard-light' + | 'soft-light' + | 'difference' + | 'exclusion' + | 'hue' + | 'saturation' + | 'color' + | 'luminosity'; + +export interface NodeConfig { + // allow any custom attribute + [index: string]: any; + x?: number; + y?: number; + width?: number; + height?: number; + visible?: boolean; + listening?: boolean; + id?: string; + name?: string; + opacity?: number; + scale?: Vector2d; + scaleX?: number; + skewX?: number; + skewY?: number; + scaleY?: number; + rotation?: number; + rotationDeg?: number; + offset?: Vector2d; + offsetX?: number; + offsetY?: number; + draggable?: boolean; + dragDistance?: number; + dragBoundFunc?: (this: Node, pos: Vector2d) => Vector2d; + preventDefault?: boolean; + globalCompositeOperation?: globalCompositeOperationType; + filters?: Array; +} + +// CONSTANTS +const ABSOLUTE_OPACITY = 'absoluteOpacity', + ALL_LISTENERS = 'allEventListeners', + ABSOLUTE_TRANSFORM = 'absoluteTransform', + ABSOLUTE_SCALE = 'absoluteScale', + CANVAS = 'canvas', + CHANGE = 'Change', + CHILDREN = 'children', + KONVA = 'konva', + LISTENING = 'listening', + MOUSEENTER = 'mouseenter', + MOUSELEAVE = 'mouseleave', + NAME = 'name', + SET = 'set', + SHAPE = 'Shape', + SPACE = ' ', + STAGE = 'stage', + TRANSFORM = 'transform', + UPPER_STAGE = 'Stage', + VISIBLE = 'visible', + TRANSFORM_CHANGE_STR = [ + 'xChange.konva', + 'yChange.konva', + 'scaleXChange.konva', + 'scaleYChange.konva', + 'skewXChange.konva', + 'skewYChange.konva', + 'rotationChange.konva', + 'offsetXChange.konva', + 'offsetYChange.konva', + 'transformsEnabledChange.konva', + ].join(SPACE); + +let idCounter = 1; + +// create all the events here +type NodeEventMap = GlobalEventHandlersEventMap & { + [index: string]: any; +}; + +export interface KonvaEventObject { + type: string; + target: Shape | Stage; + evt: EventType; + pointerId: number; + currentTarget: This; + cancelBubble: boolean; + child?: Node; +} + +export type KonvaEventListener = ( + this: This, + ev: KonvaEventObject +) => void; + +/** + * Node constructor. Nodes are entities that can be transformed, layered, + * and have bound events. The stage, layers, groups, and shapes all extend Node. + * @constructor + * @memberof Konva + * @param {Object} config + * @@nodeParams + */ +export abstract class Node { + _id = idCounter++; + eventListeners: { + [index: string]: Array<{ name: string; handler: Function }>; + } = {}; + attrs: any = {}; + index = 0; + _allEventListeners: null | Array = null; + parent: Container | null = null; + _cache: Map = new Map(); + _attachedDepsListeners: Map = new Map(); + _lastPos: Vector2d | null = null; + _attrsAffectingSize!: string[]; + _batchingTransformChange = false; + _needClearTransformCache = false; + + _filterUpToDate = false; + _isUnderCache = false; + nodeType!: string; + className!: string; + + _dragEventId: number | null = null; + _shouldFireChangeEvents = false; + + constructor(config?: Config) { + // on initial set attrs wi don't need to fire change events + // because nobody is listening to them yet + this.setAttrs(config); + this._shouldFireChangeEvents = true; + + // all change event listeners are attached to the prototype + } + + hasChildren() { + return false; + } + + _clearCache(attr?: string) { + // if we want to clear transform cache + // we don't really need to remove it from the cache + // but instead mark as "dirty" + // so we don't need to create a new instance next time + if ( + (attr === TRANSFORM || attr === ABSOLUTE_TRANSFORM) && + this._cache.get(attr) + ) { + (this._cache.get(attr) as Transform).dirty = true; + } else if (attr) { + this._cache.delete(attr); + } else { + this._cache.clear(); + } + } + _getCache(attr: string, privateGetter: Function) { + let cache = this._cache.get(attr); + + // for transform the cache can be NOT empty + // but we still need to recalculate it if it is dirty + const isTransform = attr === TRANSFORM || attr === ABSOLUTE_TRANSFORM; + const invalid = cache === undefined || (isTransform && cache.dirty === true); + + // if not cached, we need to set it using the private getter method. + if (invalid) { + cache = privateGetter.call(this); + this._cache.set(attr, cache); + } + + return cache; + } + + _calculate(name: string, deps: Array, getter: Function) { + // if we are trying to calculate function for the first time + // we need to attach listeners for change events + if (!this._attachedDepsListeners.get(name)) { + const depsString = deps.map((dep) => dep + 'Change.konva').join(SPACE); + this.on(depsString, () => { + this._clearCache(name); + }); + this._attachedDepsListeners.set(name, true); + } + // just use cache function + return this._getCache(name, getter); + } + + _getCanvasCache() { + return this._cache.get(CANVAS); + } + /* + * when the logic for a cached result depends on ancestor propagation, use this + * method to clear self and children cache + */ + _clearSelfAndDescendantCache(attr?: string) { + this._clearCache(attr); + // trigger clear cache, so transformer can use it + if (attr === ABSOLUTE_TRANSFORM) { + this.fire('absoluteTransformChange'); + } + } + /** + * clear cached canvas + * @method + * @name Konva.Node#clearCache + * @returns {Konva.Node} + * @example + * node.clearCache(); + */ + clearCache() { + if (this._cache.has(CANVAS)) { + const { scene, filter, hit } = this._cache.get(CANVAS); + Util.releaseCanvas(scene, filter, hit); + this._cache.delete(CANVAS); + } + + this._clearSelfAndDescendantCache(); + this._requestDraw(); + return this; + } + /** + * cache node to improve drawing performance, apply filters, or create more accurate + * hit regions. For all basic shapes size of cache canvas will be automatically detected. + * If you need to cache your custom `Konva.Shape` instance you have to pass shape's bounding box + * properties. Look at [https://konvajs.org/docs/performance/Shape_Caching.html](https://konvajs.org/docs/performance/Shape_Caching.html) for more information. + * @method + * @name Konva.Node#cache + * @param {Object} [config] + * @param {Number} [config.x] + * @param {Number} [config.y] + * @param {Number} [config.width] + * @param {Number} [config.height] + * @param {Number} [config.offset] increase canvas size by `offset` pixel in all directions. + * @param {Boolean} [config.drawBorder] when set to true, a red border will be drawn around the cached + * region for debugging purposes + * @param {Number} [config.pixelRatio] change quality (or pixel ratio) of cached image. pixelRatio = 2 will produce 2x sized cache. + * @param {Boolean} [config.imageSmoothingEnabled] control imageSmoothingEnabled property of created canvas for cache + * @param {Number} [config.hitCanvasPixelRatio] change quality (or pixel ratio) of cached hit canvas. + * @returns {Konva.Node} + * @example + * // cache a shape with the x,y position of the bounding box at the center and + * // the width and height of the bounding box equal to the width and height of + * // the shape obtained from shape.width() and shape.height() + * image.cache(); + * + * // cache a node and define the bounding box position and size + * node.cache({ + * x: -30, + * y: -30, + * width: 100, + * height: 200 + * }); + * + * // cache a node and draw a red border around the bounding box + * // for debugging purposes + * node.cache({ + * x: -30, + * y: -30, + * width: 100, + * height: 200, + * offset : 10, + * drawBorder: true + * }); + */ + cache(config?: { + x?: number; + y?: number; + width?: number; + height?: number; + drawBorder?: boolean; + offset?: number; + pixelRatio?: number; + imageSmoothingEnabled?: boolean; + hitCanvasPixelRatio?: number; + }) { + const conf = config || {}; + let rect = {} as IRect; + + // don't call getClientRect if we have all attributes + // it means call it only if have one undefined + if ( + conf.x === undefined || + conf.y === undefined || + conf.width === undefined || + conf.height === undefined + ) { + rect = this.getClientRect({ + skipTransform: true, + relativeTo: this.getParent() || undefined, + }); + } + let width = Math.ceil(conf.width || rect.width), + height = Math.ceil(conf.height || rect.height), + pixelRatio = conf.pixelRatio, + x = conf.x === undefined ? Math.floor(rect.x) : conf.x, + y = conf.y === undefined ? Math.floor(rect.y) : conf.y, + offset = conf.offset || 0, + drawBorder = conf.drawBorder || false, + hitCanvasPixelRatio = conf.hitCanvasPixelRatio || 1; + + if (!width || !height) { + Util.error( + 'Can not cache the node. Width or height of the node equals 0. Caching is skipped.' + ); + return; + } + + // because using Math.floor on x, y position may shift drawing + // to avoid shift we need to increase size + // but we better to avoid it, for better filters flows + const extraPaddingX = Math.abs(Math.round(rect.x) - x) > 0.5 ? 1 : 0; + const extraPaddingY = Math.abs(Math.round(rect.y) - y) > 0.5 ? 1 : 0; + width += offset * 2 + extraPaddingX; + height += offset * 2 + extraPaddingY; + + x -= offset; + y -= offset; + + // if (Math.floor(x) < x) { + // x = Math.floor(x); + // // width += 1; + // } + // if (Math.floor(y) < y) { + // y = Math.floor(y); + // // height += 1; + // } + + // console.log({ x, y, width, height }, rect); + + const cachedSceneCanvas = new SceneCanvas({ + pixelRatio: pixelRatio, + width: width, + height: height, + }), + cachedFilterCanvas = new SceneCanvas({ + pixelRatio: pixelRatio, + width: 0, + height: 0, + willReadFrequently: true, + }), + cachedHitCanvas = new HitCanvas({ + pixelRatio: hitCanvasPixelRatio, + width: width, + height: height, + }), + sceneContext = cachedSceneCanvas.getContext(), + hitContext = cachedHitCanvas.getContext(); + + cachedHitCanvas.isCache = true; + cachedSceneCanvas.isCache = true; + + this._cache.delete(CANVAS); + this._filterUpToDate = false; + + if (conf.imageSmoothingEnabled === false) { + cachedSceneCanvas.getContext()._context.imageSmoothingEnabled = false; + cachedFilterCanvas.getContext()._context.imageSmoothingEnabled = false; + } + + sceneContext.save(); + hitContext.save(); + + sceneContext.translate(-x, -y); + hitContext.translate(-x, -y); + + // extra flag to skip on getAbsolute opacity calc + this._isUnderCache = true; + this._clearSelfAndDescendantCache(ABSOLUTE_OPACITY); + this._clearSelfAndDescendantCache(ABSOLUTE_SCALE); + + this.drawScene(cachedSceneCanvas, this); + this.drawHit(cachedHitCanvas, this); + this._isUnderCache = false; + + sceneContext.restore(); + hitContext.restore(); + + // this will draw a red border around the cached box for + // debugging purposes + if (drawBorder) { + sceneContext.save(); + sceneContext.beginPath(); + sceneContext.rect(0, 0, width, height); + sceneContext.closePath(); + sceneContext.setAttr('strokeStyle', 'red'); + sceneContext.setAttr('lineWidth', 5); + sceneContext.stroke(); + sceneContext.restore(); + } + + this._cache.set(CANVAS, { + scene: cachedSceneCanvas, + filter: cachedFilterCanvas, + hit: cachedHitCanvas, + x: x, + y: y, + }); + + this._requestDraw(); + + return this; + } + + /** + * determine if node is currently cached + * @method + * @name Konva.Node#isCached + * @returns {Boolean} + */ + isCached() { + return this._cache.has(CANVAS); + } + + abstract drawScene(canvas?: Canvas, top?: Node, bufferCanvas?: Canvas): void; + abstract drawHit(canvas?: Canvas, top?: Node): void; + /** + * Return client rectangle {x, y, width, height} of node. This rectangle also include all styling (strokes, shadows, etc). + * The purpose of the method is similar to getBoundingClientRect API of the DOM. + * @method + * @name Konva.Node#getClientRect + * @param {Object} config + * @param {Boolean} [config.skipTransform] should we apply transform to node for calculating rect? + * @param {Boolean} [config.skipShadow] should we apply shadow to the node for calculating bound box? + * @param {Boolean} [config.skipStroke] should we apply stroke to the node for calculating bound box? + * @param {Object} [config.relativeTo] calculate client rect relative to one of the parents + * @returns {Object} rect with {x, y, width, height} properties + * @example + * var rect = new Konva.Rect({ + * width : 100, + * height : 100, + * x : 50, + * y : 50, + * strokeWidth : 4, + * stroke : 'black', + * offsetX : 50, + * scaleY : 2 + * }); + * + * // get client rect without think off transformations (position, rotation, scale, offset, etc) + * rect.getClientRect({ skipTransform: true}); + * // returns { + * // x : -2, // two pixels for stroke / 2 + * // y : -2, + * // width : 104, // increased by 4 for stroke + * // height : 104 + * //} + * + * // get client rect with transformation applied + * rect.getClientRect(); + * // returns Object {x: -2, y: 46, width: 104, height: 208} + */ + getClientRect(config?: { + skipTransform?: boolean; + skipShadow?: boolean; + skipStroke?: boolean; + relativeTo?: Container; + }): { x: number; y: number; width: number; height: number } { + // abstract method + // redefine in Container and Shape + throw new Error('abstract "getClientRect" method call'); + } + _transformedRect(rect: IRect, top?: Node | null) { + const points = [ + { x: rect.x, y: rect.y }, + { x: rect.x + rect.width, y: rect.y }, + { x: rect.x + rect.width, y: rect.y + rect.height }, + { x: rect.x, y: rect.y + rect.height }, + ]; + let minX: number = Infinity, + minY: number = Infinity, + maxX: number = -Infinity, + maxY: number = -Infinity; + const trans = this.getAbsoluteTransform(top); + points.forEach(function (point) { + const transformed = trans.point(point); + if (minX === undefined) { + minX = maxX = transformed.x; + minY = maxY = transformed.y; + } + minX = Math.min(minX, transformed.x); + minY = Math.min(minY, transformed.y); + maxX = Math.max(maxX, transformed.x); + maxY = Math.max(maxY, transformed.y); + }); + return { + x: minX, + y: minY, + width: maxX - minX, + height: maxY - minY, + }; + } + _drawCachedSceneCanvas(context: Context) { + context.save(); + context._applyOpacity(this); + context._applyGlobalCompositeOperation(this); + + const canvasCache = this._getCanvasCache(); + context.translate(canvasCache.x, canvasCache.y); + + const cacheCanvas = this._getCachedSceneCanvas(); + const ratio = cacheCanvas.pixelRatio; + + context.drawImage( + cacheCanvas._canvas, + 0, + 0, + cacheCanvas.width / ratio, + cacheCanvas.height / ratio + ); + context.restore(); + } + _drawCachedHitCanvas(context: Context) { + const canvasCache = this._getCanvasCache(), + hitCanvas = canvasCache.hit; + context.save(); + context.translate(canvasCache.x, canvasCache.y); + context.drawImage( + hitCanvas._canvas, + 0, + 0, + hitCanvas.width / hitCanvas.pixelRatio, + hitCanvas.height / hitCanvas.pixelRatio + ); + context.restore(); + } + _getCachedSceneCanvas() { + let filters = this.filters(), + cachedCanvas = this._getCanvasCache(), + sceneCanvas = cachedCanvas.scene, + filterCanvas = cachedCanvas.filter, + filterContext = filterCanvas.getContext(), + len, + imageData, + n, + filter; + + if (filters) { + if (!this._filterUpToDate) { + const ratio = sceneCanvas.pixelRatio; + filterCanvas.setSize( + sceneCanvas.width / sceneCanvas.pixelRatio, + sceneCanvas.height / sceneCanvas.pixelRatio + ); + try { + len = filters.length; + filterContext.clear(); + + // copy cached canvas onto filter context + filterContext.drawImage( + sceneCanvas._canvas, + 0, + 0, + sceneCanvas.getWidth() / ratio, + sceneCanvas.getHeight() / ratio + ); + imageData = filterContext.getImageData( + 0, + 0, + filterCanvas.getWidth(), + filterCanvas.getHeight() + ); + + // apply filters to filter context + for (n = 0; n < len; n++) { + filter = filters[n]; + if (typeof filter !== 'function') { + Util.error( + 'Filter should be type of function, but got ' + + typeof filter + + ' instead. Please check correct filters' + ); + continue; + } + filter.call(this, imageData); + filterContext.putImageData(imageData, 0, 0); + } + } catch (e: any) { + Util.error( + 'Unable to apply filter. ' + + e.message + + ' This post my help you https://konvajs.org/docs/posts/Tainted_Canvas.html.' + ); + } + + this._filterUpToDate = true; + } + + return filterCanvas; + } + return sceneCanvas; + } + /** + * bind events to the node. KonvaJS supports mouseover, mousemove, + * mouseout, mouseenter, mouseleave, mousedown, mouseup, wheel, contextmenu, click, dblclick, touchstart, touchmove, + * touchend, tap, dbltap, dragstart, dragmove, and dragend events. + * Pass in a string of events delimited by a space to bind multiple events at once + * such as 'mousedown mouseup mousemove'. Include a namespace to bind an + * event by name such as 'click.foobar'. + * @method + * @name Konva.Node#on + * @param {String} evtStr e.g. 'click', 'mousedown touchstart', 'mousedown.foo touchstart.foo' + * @param {Function} handler The handler function. The first argument of that function is event object. Event object has `target` as main target of the event, `currentTarget` as current node listener and `evt` as native browser event. + * @returns {Konva.Node} + * @example + * // add click listener + * node.on('click', function() { + * console.log('you clicked me!'); + * }); + * + * // get the target node + * node.on('click', function(evt) { + * console.log(evt.target); + * }); + * + * // stop event propagation + * node.on('click', function(evt) { + * evt.cancelBubble = true; + * }); + * + * // bind multiple listeners + * node.on('click touchstart', function() { + * console.log('you clicked/touched me!'); + * }); + * + * // namespace listener + * node.on('click.foo', function() { + * console.log('you clicked/touched me!'); + * }); + * + * // get the event type + * node.on('click tap', function(evt) { + * var eventType = evt.type; + * }); + * + * // get native event object + * node.on('click tap', function(evt) { + * var nativeEvent = evt.evt; + * }); + * + * // for change events, get the old and new val + * node.on('xChange', function(evt) { + * var oldVal = evt.oldVal; + * var newVal = evt.newVal; + * }); + * + * // get event targets + * // with event delegations + * layer.on('click', 'Group', function(evt) { + * var shape = evt.target; + * var group = evt.currentTarget; + * }); + */ + on( + evtStr: K, + handler: KonvaEventListener + ) { + this._cache && this._cache.delete(ALL_LISTENERS); + + if (arguments.length === 3) { + return this._delegate.apply(this, arguments as any); + } + let events = (evtStr as string).split(SPACE), + len = events.length, + n, + event, + parts, + baseEvent, + name; + + /* + * loop through types and attach event listeners to + * each one. eg. 'click mouseover.namespace mouseout' + * will create three event bindings + */ + for (n = 0; n < len; n++) { + event = events[n]; + parts = event.split('.'); + baseEvent = parts[0]; + name = parts[1] || ''; + + // create events array if it doesn't exist + if (!this.eventListeners[baseEvent]) { + this.eventListeners[baseEvent] = []; + } + + this.eventListeners[baseEvent].push({ + name: name, + handler: handler, + }); + } + + return this; + } + /** + * remove event bindings from the node. Pass in a string of + * event types delimmited by a space to remove multiple event + * bindings at once such as 'mousedown mouseup mousemove'. + * include a namespace to remove an event binding by name + * such as 'click.foobar'. If you only give a name like '.foobar', + * all events in that namespace will be removed. + * @method + * @name Konva.Node#off + * @param {String} evtStr e.g. 'click', 'mousedown touchstart', '.foobar' + * @returns {Konva.Node} + * @example + * // remove listener + * node.off('click'); + * + * // remove multiple listeners + * node.off('click touchstart'); + * + * // remove listener by name + * node.off('click.foo'); + */ + off(evtStr?: string, callback?: Function) { + let events = (evtStr || '').split(SPACE), + len = events.length, + n, + t, + event, + parts, + baseEvent, + name; + + this._cache && this._cache.delete(ALL_LISTENERS); + + if (!evtStr) { + // remove all events + for (t in this.eventListeners) { + this._off(t); + } + } + for (n = 0; n < len; n++) { + event = events[n]; + parts = event.split('.'); + baseEvent = parts[0]; + name = parts[1]; + + if (baseEvent) { + if (this.eventListeners[baseEvent]) { + this._off(baseEvent, name, callback); + } + } else { + for (t in this.eventListeners) { + this._off(t, name, callback); + } + } + } + return this; + } + // some event aliases for third party integration like HammerJS + dispatchEvent(evt: any) { + const e = { + target: this, + type: evt.type, + evt: evt, + }; + this.fire(evt.type, e); + return this; + } + addEventListener(type: string, handler: (e: Event) => void) { + // we have to pass native event to handler + this.on(type, function (evt) { + handler.call(this, evt.evt); + }); + return this; + } + removeEventListener(type: string) { + this.off(type); + return this; + } + // like node.on + _delegate(event: string, selector: string, handler: (e: Event) => void) { + const stopNode = this; + this.on(event, function (evt) { + const targets = evt.target.findAncestors(selector, true, stopNode); + for (let i = 0; i < targets.length; i++) { + evt = Util.cloneObject(evt); + evt.currentTarget = targets[i] as any; + handler.call(targets[i], evt as any); + } + }); + } + /** + * remove a node from parent, but don't destroy. You can reuse the node later. + * @method + * @name Konva.Node#remove + * @returns {Konva.Node} + * @example + * node.remove(); + */ + remove() { + if (this.isDragging()) { + this.stopDrag(); + } + // we can have drag element but that is not dragged yet + // so just clear it + DD._dragElements.delete(this._id); + this._remove(); + return this; + } + _clearCaches() { + this._clearSelfAndDescendantCache(ABSOLUTE_TRANSFORM); + this._clearSelfAndDescendantCache(ABSOLUTE_OPACITY); + this._clearSelfAndDescendantCache(ABSOLUTE_SCALE); + this._clearSelfAndDescendantCache(STAGE); + this._clearSelfAndDescendantCache(VISIBLE); + this._clearSelfAndDescendantCache(LISTENING); + } + _remove() { + // every cached attr that is calculated via node tree + // traversal must be cleared when removing a node + this._clearCaches(); + + const parent = this.getParent(); + + if (parent && parent.children) { + parent.children.splice(this.index, 1); + parent._setChildrenIndices(); + this.parent = null; + } + } + /** + * remove and destroy a node. Kill it and delete forever! You should not reuse node after destroy(). + * If the node is a container (Group, Stage or Layer) it will destroy all children too. + * @method + * @name Konva.Node#destroy + * @example + * node.destroy(); + */ + destroy() { + this.remove(); + this.clearCache(); + return this; + } + /** + * get attr + * @method + * @name Konva.Node#getAttr + * @param {String} attr + * @returns {Integer|String|Object|Array} + * @example + * var x = node.getAttr('x'); + */ + getAttr(attr: string) { + const method = 'get' + Util._capitalize(attr); + if (Util._isFunction((this as any)[method])) { + return (this as any)[method](); + } + // otherwise get directly + return this.attrs[attr]; + } + /** + * get ancestors + * @method + * @name Konva.Node#getAncestors + * @returns {Array} + * @example + * shape.getAncestors().forEach(function(node) { + * console.log(node.getId()); + * }) + */ + getAncestors() { + let parent = this.getParent(), + ancestors: Array = []; + + while (parent) { + ancestors.push(parent); + parent = parent.getParent(); + } + + return ancestors; + } + /** + * get attrs object literal + * @method + * @name Konva.Node#getAttrs + * @returns {Object} + */ + getAttrs() { + return (this.attrs || {}) as Config & Record; + } + /** + * set multiple attrs at once using an object literal + * @method + * @name Konva.Node#setAttrs + * @param {Object} config object containing key value pairs + * @returns {Konva.Node} + * @example + * node.setAttrs({ + * x: 5, + * fill: 'red' + * }); + */ + setAttrs(config: any) { + this._batchTransformChanges(() => { + let key, method; + if (!config) { + return this; + } + for (key in config) { + if (key === CHILDREN) { + continue; + } + method = SET + Util._capitalize(key); + // use setter if available + if (Util._isFunction(this[method])) { + this[method](config[key]); + } else { + // otherwise set directly + this._setAttr(key, config[key]); + } + } + }); + + return this; + } + /** + * determine if node is listening for events by taking into account ancestors. + * + * Parent | Self | isListening + * listening | listening | + * ----------+-----------+------------ + * T | T | T + * T | F | F + * F | T | F + * F | F | F + * + * @method + * @name Konva.Node#isListening + * @returns {Boolean} + */ + isListening() { + return this._getCache(LISTENING, this._isListening); + } + _isListening(relativeTo?: Node): boolean { + const listening = this.listening(); + if (!listening) { + return false; + } + const parent = this.getParent(); + if (parent && parent !== relativeTo && this !== relativeTo) { + return parent._isListening(relativeTo); + } else { + return true; + } + } + /** + * determine if node is visible by taking into account ancestors. + * + * Parent | Self | isVisible + * visible | visible | + * ----------+-----------+------------ + * T | T | T + * T | F | F + * F | T | F + * F | F | F + * @method + * @name Konva.Node#isVisible + * @returns {Boolean} + */ + isVisible() { + return this._getCache(VISIBLE, this._isVisible); + } + _isVisible(relativeTo?: Node): boolean { + const visible = this.visible(); + if (!visible) { + return false; + } + const parent = this.getParent(); + if (parent && parent !== relativeTo && this !== relativeTo) { + return parent._isVisible(relativeTo); + } else { + return true; + } + } + shouldDrawHit(top?: Node, skipDragCheck = false) { + if (top) { + return this._isVisible(top) && this._isListening(top); + } + const layer = this.getLayer(); + + let layerUnderDrag = false; + DD._dragElements.forEach((elem) => { + if (elem.dragStatus !== 'dragging') { + return; + } else if (elem.node.nodeType === 'Stage') { + layerUnderDrag = true; + } else if (elem.node.getLayer() === layer) { + layerUnderDrag = true; + } + }); + + const dragSkip = + !skipDragCheck && + !Konva.hitOnDragEnabled && + (layerUnderDrag || Konva.isTransforming()); + return this.isListening() && this.isVisible() && !dragSkip; + } + + /** + * show node. set visible = true + * @method + * @name Konva.Node#show + * @returns {Konva.Node} + */ + show() { + this.visible(true); + return this; + } + /** + * hide node. Hidden nodes are no longer detectable + * @method + * @name Konva.Node#hide + * @returns {Konva.Node} + */ + hide() { + this.visible(false); + return this; + } + getZIndex() { + return this.index || 0; + } + /** + * get absolute z-index which takes into account sibling + * and ancestor indices + * @method + * @name Konva.Node#getAbsoluteZIndex + * @returns {Integer} + */ + getAbsoluteZIndex() { + let depth = this.getDepth(), + that = this, + index = 0, + nodes, + len, + n, + child; + + function addChildren(children) { + nodes = []; + len = children.length; + for (n = 0; n < len; n++) { + child = children[n]; + index++; + + if (child.nodeType !== SHAPE) { + nodes = nodes.concat(child.getChildren().slice()); + } + + if (child._id === that._id) { + n = len; + } + } + + if (nodes.length > 0 && nodes[0].getDepth() <= depth) { + addChildren(nodes); + } + } + const stage = this.getStage(); + if (that.nodeType !== UPPER_STAGE && stage) { + addChildren(stage.getChildren()); + } + + return index; + } + /** + * get node depth in node tree. Returns an integer. + * e.g. Stage depth will always be 0. Layers will always be 1. Groups and Shapes will always + * be >= 2 + * @method + * @name Konva.Node#getDepth + * @returns {Integer} + */ + getDepth() { + let depth = 0, + parent = this.parent; + + while (parent) { + depth++; + parent = parent.parent; + } + return depth; + } + + // sometimes we do several attributes changes + // like node.position(pos) + // for performance reasons, lets batch transform reset + // so it work faster + _batchTransformChanges(func) { + this._batchingTransformChange = true; + func(); + this._batchingTransformChange = false; + if (this._needClearTransformCache) { + this._clearCache(TRANSFORM); + this._clearSelfAndDescendantCache(ABSOLUTE_TRANSFORM); + } + this._needClearTransformCache = false; + } + + setPosition(pos: Vector2d) { + this._batchTransformChanges(() => { + this.x(pos.x); + this.y(pos.y); + }); + return this; + } + getPosition() { + return { + x: this.x(), + y: this.y(), + }; + } + /** + * get position of first pointer (like mouse or first touch) relative to local coordinates of current node + * @method + * @name Konva.Node#getRelativePointerPosition + * @returns {Konva.Node} + * @example + * + * // let's think we have a rectangle at position x = 10, y = 10 + * // now we clicked at x = 15, y = 15 of the stage + * // if you want to know position of the click, related to the rectangle you can use + * rect.getRelativePointerPosition(); + */ + getRelativePointerPosition() { + const stage = this.getStage(); + if (!stage) { + return null; + } + // get pointer (say mouse or touch) position + const pos = stage.getPointerPosition(); + if (!pos) { + return null; + } + const transform = this.getAbsoluteTransform().copy(); + // to detect relative position we need to invert transform + transform.invert(); + // now we can find relative point + return transform.point(pos); + } + /** + * get absolute position of a node. That function can be used to calculate absolute position, but relative to any ancestor + * @method + * @name Konva.Node#getAbsolutePosition + * @param {Object} Ancestor optional ancestor node + * @returns {Konva.Node} + * @example + * + * // returns absolute position relative to top-left corner of canvas + * node.getAbsolutePosition(); + * + * // calculate absolute position of node, inside stage + * // so stage transforms are ignored + * node.getAbsolutePosition(stage) + */ + getAbsolutePosition(top?: Node) { + let haveCachedParent = false; + let parent = this.parent; + while (parent) { + if (parent.isCached()) { + haveCachedParent = true; + break; + } + parent = parent.parent; + } + if (haveCachedParent && !top) { + // make fake top element + // "true" is not a node, but it will just allow skip all caching + top = true as any; + } + const absoluteMatrix = this.getAbsoluteTransform(top).getMatrix(), + absoluteTransform = new Transform(), + offset = this.offset(); + + // clone the matrix array + absoluteTransform.m = absoluteMatrix.slice(); + absoluteTransform.translate(offset.x, offset.y); + + return absoluteTransform.getTranslation(); + } + setAbsolutePosition(pos: Vector2d) { + const { x, y, ...origTrans } = this._clearTransform(); + + // don't clear translation + this.attrs.x = x; + this.attrs.y = y; + + // important, use non cached value + this._clearCache(TRANSFORM); + const it = this._getAbsoluteTransform().copy(); + + it.invert(); + it.translate(pos.x, pos.y); + pos = { + x: this.attrs.x + it.getTranslation().x, + y: this.attrs.y + it.getTranslation().y, + }; + this._setTransform(origTrans); + this.setPosition({ x: pos.x, y: pos.y }); + this._clearCache(TRANSFORM); + this._clearSelfAndDescendantCache(ABSOLUTE_TRANSFORM); + + return this; + } + _setTransform(trans) { + let key; + + for (key in trans) { + this.attrs[key] = trans[key]; + } + // this._clearCache(TRANSFORM); + // this._clearSelfAndDescendantCache(ABSOLUTE_TRANSFORM); + } + _clearTransform() { + const trans = { + x: this.x(), + y: this.y(), + rotation: this.rotation(), + scaleX: this.scaleX(), + scaleY: this.scaleY(), + offsetX: this.offsetX(), + offsetY: this.offsetY(), + skewX: this.skewX(), + skewY: this.skewY(), + }; + + this.attrs.x = 0; + this.attrs.y = 0; + this.attrs.rotation = 0; + this.attrs.scaleX = 1; + this.attrs.scaleY = 1; + this.attrs.offsetX = 0; + this.attrs.offsetY = 0; + this.attrs.skewX = 0; + this.attrs.skewY = 0; + + // return original transform + return trans; + } + /** + * move node by an amount relative to its current position + * @method + * @name Konva.Node#move + * @param {Object} change + * @param {Number} change.x + * @param {Number} change.y + * @returns {Konva.Node} + * @example + * // move node in x direction by 1px and y direction by 2px + * node.move({ + * x: 1, + * y: 2 + * }); + */ + move(change: Vector2d) { + let changeX = change.x, + changeY = change.y, + x = this.x(), + y = this.y(); + + if (changeX !== undefined) { + x += changeX; + } + + if (changeY !== undefined) { + y += changeY; + } + + this.setPosition({ x: x, y: y }); + return this; + } + _eachAncestorReverse(func, top) { + let family: Array = [], + parent = this.getParent(), + len, + n; + + // if top node is defined, and this node is top node, + // there's no need to build a family tree. just execute + // func with this because it will be the only node + if (top && top._id === this._id) { + // func(this); + return; + } + + family.unshift(this); + + while (parent && (!top || parent._id !== top._id)) { + family.unshift(parent); + parent = parent.parent; + } + + len = family.length; + for (n = 0; n < len; n++) { + func(family[n]); + } + } + /** + * rotate node by an amount in degrees relative to its current rotation + * @method + * @name Konva.Node#rotate + * @param {Number} theta + * @returns {Konva.Node} + */ + rotate(theta: number) { + this.rotation(this.rotation() + theta); + return this; + } + /** + * move node to the top of its siblings + * @method + * @name Konva.Node#moveToTop + * @returns {Boolean} + */ + moveToTop() { + if (!this.parent) { + Util.warn('Node has no parent. moveToTop function is ignored.'); + return false; + } + const index = this.index, + len = this.parent.getChildren().length; + if (index < len - 1) { + this.parent.children.splice(index, 1); + this.parent.children.push(this); + this.parent._setChildrenIndices(); + return true; + } + return false; + } + /** + * move node up + * @method + * @name Konva.Node#moveUp + * @returns {Boolean} flag is moved or not + */ + moveUp() { + if (!this.parent) { + Util.warn('Node has no parent. moveUp function is ignored.'); + return false; + } + const index = this.index, + len = this.parent.getChildren().length; + if (index < len - 1) { + this.parent.children.splice(index, 1); + this.parent.children.splice(index + 1, 0, this); + this.parent._setChildrenIndices(); + return true; + } + return false; + } + /** + * move node down + * @method + * @name Konva.Node#moveDown + * @returns {Boolean} + */ + moveDown() { + if (!this.parent) { + Util.warn('Node has no parent. moveDown function is ignored.'); + return false; + } + const index = this.index; + if (index > 0) { + this.parent.children.splice(index, 1); + this.parent.children.splice(index - 1, 0, this); + this.parent._setChildrenIndices(); + return true; + } + return false; + } + /** + * move node to the bottom of its siblings + * @method + * @name Konva.Node#moveToBottom + * @returns {Boolean} + */ + moveToBottom() { + if (!this.parent) { + Util.warn('Node has no parent. moveToBottom function is ignored.'); + return false; + } + const index = this.index; + if (index > 0) { + this.parent.children.splice(index, 1); + this.parent.children.unshift(this); + this.parent._setChildrenIndices(); + return true; + } + return false; + } + setZIndex(zIndex) { + if (!this.parent) { + Util.warn('Node has no parent. zIndex parameter is ignored.'); + return this; + } + if (zIndex < 0 || zIndex >= this.parent.children.length) { + Util.warn( + 'Unexpected value ' + + zIndex + + ' for zIndex property. zIndex is just index of a node in children of its parent. Expected value is from 0 to ' + + (this.parent.children.length - 1) + + '.' + ); + } + const index = this.index; + this.parent.children.splice(index, 1); + this.parent.children.splice(zIndex, 0, this); + this.parent._setChildrenIndices(); + return this; + } + /** + * get absolute opacity + * @method + * @name Konva.Node#getAbsoluteOpacity + * @returns {Number} + */ + getAbsoluteOpacity() { + return this._getCache(ABSOLUTE_OPACITY, this._getAbsoluteOpacity); + } + _getAbsoluteOpacity() { + let absOpacity = this.opacity(); + const parent = this.getParent(); + if (parent && !parent._isUnderCache) { + absOpacity *= parent.getAbsoluteOpacity(); + } + return absOpacity; + } + /** + * move node to another container + * @method + * @name Konva.Node#moveTo + * @param {Container} newContainer + * @returns {Konva.Node} + * @example + * // move node from current layer into layer2 + * node.moveTo(layer2); + */ + moveTo(newContainer: any) { + // do nothing if new container is already parent + if (this.getParent() !== newContainer) { + this._remove(); + newContainer.add(this); + } + return this; + } + /** + * convert Node into an object for serialization. Returns an object. + * @method + * @name Konva.Node#toObject + * @returns {Object} + */ + toObject() { + let attrs = this.getAttrs() as any, + key, + val, + getter, + defaultValue, + nonPlainObject; + + const obj: { + attrs: Config & Record; + className: string; + children?: Array; + } = { + attrs: {} as Config & Record, + className: this.getClassName(), + }; + + for (key in attrs) { + val = attrs[key]; + // if value is object and object is not plain + // like class instance, we should skip it and to not include + nonPlainObject = + Util.isObject(val) && !Util._isPlainObject(val) && !Util._isArray(val); + if (nonPlainObject) { + continue; + } + getter = typeof this[key] === 'function' && this[key]; + // remove attr value so that we can extract the default value from the getter + delete attrs[key]; + defaultValue = getter ? getter.call(this) : null; + // restore attr value + attrs[key] = val; + if (defaultValue !== val) { + (obj.attrs as any)[key] = val; + } + } + + return Util._prepareToStringify(obj) as typeof obj; + } + /** + * convert Node into a JSON string. Returns a JSON string. + * @method + * @name Konva.Node#toJSON + * @returns {String} + */ + toJSON() { + return JSON.stringify(this.toObject()); + } + /** + * get parent container + * @method + * @name Konva.Node#getParent + * @returns {Konva.Node} + */ + getParent() { + return this.parent; + } + /** + * get all ancestors (parent then parent of the parent, etc) of the node + * @method + * @name Konva.Node#findAncestors + * @param {String} selector selector for search + * @param {Boolean} [includeSelf] show we think that node is ancestro itself? + * @param {Konva.Node} [stopNode] optional node where we need to stop searching (one of ancestors) + * @returns {Array} [ancestors] + * @example + * // get one of the parent group + * var parentGroups = node.findAncestors('Group'); + */ + findAncestors( + selector: string | Function, + includeSelf?: boolean, + stopNode?: Node + ) { + const res: Array = []; + + if (includeSelf && this._isMatch(selector)) { + res.push(this); + } + let ancestor = this.parent; + while (ancestor) { + if (ancestor === stopNode) { + return res; + } + if (ancestor._isMatch(selector)) { + res.push(ancestor); + } + ancestor = ancestor.parent; + } + return res; + } + isAncestorOf(node: Node) { + return false; + } + /** + * get ancestor (parent or parent of the parent, etc) of the node that match passed selector + * @method + * @name Konva.Node#findAncestor + * @param {String} selector selector for search + * @param {Boolean} [includeSelf] show we think that node is ancestro itself? + * @param {Konva.Node} [stopNode] optional node where we need to stop searching (one of ancestors) + * @returns {Konva.Node} ancestor + * @example + * // get one of the parent group + * var group = node.findAncestors('.mygroup'); + */ + findAncestor( + selector: string | Function, + includeSelf?: boolean, + stopNode?: Container + ) { + return this.findAncestors(selector, includeSelf, stopNode)[0]; + } + // is current node match passed selector? + _isMatch(selector: string | Function) { + if (!selector) { + return false; + } + if (typeof selector === 'function') { + return selector(this); + } + let selectorArr = selector.replace(/ /g, '').split(','), + len = selectorArr.length, + n, + sel; + + for (n = 0; n < len; n++) { + sel = selectorArr[n]; + if (!Util.isValidSelector(sel)) { + Util.warn( + 'Selector "' + + sel + + '" is invalid. Allowed selectors examples are "#foo", ".bar" or "Group".' + ); + Util.warn( + 'If you have a custom shape with such className, please change it to start with upper letter like "Triangle".' + ); + Util.warn('Konva is awesome, right?'); + } + // id selector + if (sel.charAt(0) === '#') { + if (this.id() === sel.slice(1)) { + return true; + } + } else if (sel.charAt(0) === '.') { + // name selector + if (this.hasName(sel.slice(1))) { + return true; + } + } else if (this.className === sel || this.nodeType === sel) { + return true; + } + } + return false; + } + /** + * get layer ancestor + * @method + * @name Konva.Node#getLayer + * @returns {Konva.Layer} + */ + getLayer(): Layer | null { + const parent = this.getParent(); + return parent ? parent.getLayer() : null; + } + /** + * get stage ancestor + * @method + * @name Konva.Node#getStage + * @returns {Konva.Stage} + */ + getStage(): Stage | null { + return this._getCache(STAGE, this._getStage); + } + + _getStage() { + const parent = this.getParent(); + if (parent) { + return parent.getStage(); + } else { + return null; + } + } + /** + * fire event + * @method + * @name Konva.Node#fire + * @param {String} eventType event type. can be a regular event, like click, mouseover, or mouseout, or it can be a custom event, like myCustomEvent + * @param {Event} [evt] event object + * @param {Boolean} [bubble] setting the value to false, or leaving it undefined, will result in the event + * not bubbling. Setting the value to true will result in the event bubbling. + * @returns {Konva.Node} + * @example + * // manually fire click event + * node.fire('click'); + * + * // fire custom event + * node.fire('foo'); + * + * // fire custom event with custom event object + * node.fire('foo', { + * bar: 10 + * }); + * + * // fire click event that bubbles + * node.fire('click', null, true); + */ + fire(eventType: string, evt: any = {}, bubble?: boolean) { + evt.target = evt.target || this; + // bubble + if (bubble) { + this._fireAndBubble(eventType, evt); + } else { + // no bubble + this._fire(eventType, evt); + } + return this; + } + /** + * get absolute transform of the node which takes into + * account its ancestor transforms + * @method + * @name Konva.Node#getAbsoluteTransform + * @returns {Konva.Transform} + */ + getAbsoluteTransform(top?: Node | null) { + // if using an argument, we can't cache the result. + if (top) { + return this._getAbsoluteTransform(top); + } else { + // if no argument, we can cache the result + return this._getCache( + ABSOLUTE_TRANSFORM, + this._getAbsoluteTransform + ) as Transform; + } + } + _getAbsoluteTransform(top?: Node) { + let at: Transform; + // we we need position relative to an ancestor, we will iterate for all + if (top) { + at = new Transform(); + // start with stage and traverse downwards to self + this._eachAncestorReverse(function (node: Node) { + const transformsEnabled = node.transformsEnabled(); + + if (transformsEnabled === 'all') { + at.multiply(node.getTransform()); + } else if (transformsEnabled === 'position') { + at.translate(node.x() - node.offsetX(), node.y() - node.offsetY()); + } + }, top); + return at; + } else { + // try to use a cached value + at = this._cache.get(ABSOLUTE_TRANSFORM) || new Transform(); + if (this.parent) { + // transform will be cached + this.parent.getAbsoluteTransform().copyInto(at); + } else { + at.reset(); + } + const transformsEnabled = this.transformsEnabled(); + if (transformsEnabled === 'all') { + at.multiply(this.getTransform()); + } else if (transformsEnabled === 'position') { + // use "attrs" directly, because it is a bit faster + const x = this.attrs.x || 0; + const y = this.attrs.y || 0; + const offsetX = this.attrs.offsetX || 0; + const offsetY = this.attrs.offsetY || 0; + + at.translate(x - offsetX, y - offsetY); + } + at.dirty = false; + return at; + } + } + /** + * get absolute scale of the node which takes into + * account its ancestor scales + * @method + * @name Konva.Node#getAbsoluteScale + * @returns {Object} + * @example + * // get absolute scale x + * var scaleX = node.getAbsoluteScale().x; + */ + getAbsoluteScale(top?: Node) { + // do not cache this calculations, + // because it use cache transform + // this is special logic for caching with some shapes with shadow + let parent: Node | null = this; + while (parent) { + if (parent._isUnderCache) { + top = parent; + } + parent = parent.getParent(); + } + + const transform = this.getAbsoluteTransform(top); + const attrs = transform.decompose(); + + return { + x: attrs.scaleX, + y: attrs.scaleY, + }; + } + /** + * get absolute rotation of the node which takes into + * account its ancestor rotations + * @method + * @name Konva.Node#getAbsoluteRotation + * @returns {Number} + * @example + * // get absolute rotation + * var rotation = node.getAbsoluteRotation(); + */ + getAbsoluteRotation() { + // var parent: Node = this; + // var rotation = 0; + + // while (parent) { + // rotation += parent.rotation(); + // parent = parent.getParent(); + // } + // return rotation; + return this.getAbsoluteTransform().decompose().rotation; + } + /** + * get transform of the node + * @method + * @name Konva.Node#getTransform + * @returns {Konva.Transform} + */ + getTransform() { + return this._getCache(TRANSFORM, this._getTransform) as Transform; + } + _getTransform(): Transform { + const m: Transform = this._cache.get(TRANSFORM) || new Transform(); + m.reset(); + + // I was trying to use attributes directly here + // but it doesn't work for Transformer well + // because it overwrite x,y getters + const x = this.x(), + y = this.y(), + rotation = Konva.getAngle(this.rotation()), + scaleX = this.attrs.scaleX ?? 1, + scaleY = this.attrs.scaleY ?? 1, + skewX = this.attrs.skewX || 0, + skewY = this.attrs.skewY || 0, + offsetX = this.attrs.offsetX || 0, + offsetY = this.attrs.offsetY || 0; + + if (x !== 0 || y !== 0) { + m.translate(x, y); + } + if (rotation !== 0) { + m.rotate(rotation); + } + if (skewX !== 0 || skewY !== 0) { + m.skew(skewX, skewY); + } + if (scaleX !== 1 || scaleY !== 1) { + m.scale(scaleX, scaleY); + } + if (offsetX !== 0 || offsetY !== 0) { + m.translate(-1 * offsetX, -1 * offsetY); + } + + m.dirty = false; + + return m; + } + /** + * clone node. Returns a new Node instance with identical attributes. You can also override + * the node properties with an object literal, enabling you to use an existing node as a template + * for another node + * @method + * @name Konva.Node#clone + * @param {Object} obj override attrs + * @returns {Konva.Node} + * @example + * // simple clone + * var clone = node.clone(); + * + * // clone a node and override the x position + * var clone = rect.clone({ + * x: 5 + * }); + */ + clone(obj?: any) { + // instantiate new node + let attrs = Util.cloneObject(this.attrs), + key, + allListeners, + len, + n, + listener; + // apply attr overrides + for (key in obj) { + attrs[key] = obj[key]; + } + + const node = new (this.constructor)(attrs); + // copy over listeners + for (key in this.eventListeners) { + allListeners = this.eventListeners[key]; + len = allListeners.length; + for (n = 0; n < len; n++) { + listener = allListeners[n]; + /* + * don't include konva namespaced listeners because + * these are generated by the constructors + */ + if (listener.name.indexOf(KONVA) < 0) { + // if listeners array doesn't exist, then create it + if (!node.eventListeners[key]) { + node.eventListeners[key] = []; + } + node.eventListeners[key].push(listener); + } + } + } + return node; + } + _toKonvaCanvas(config) { + config = config || {}; + + const box = this.getClientRect(); + + const stage = this.getStage(), + x = config.x !== undefined ? config.x : Math.floor(box.x), + y = config.y !== undefined ? config.y : Math.floor(box.y), + pixelRatio = config.pixelRatio || 1, + canvas = new SceneCanvas({ + width: + config.width || Math.ceil(box.width) || (stage ? stage.width() : 0), + height: + config.height || + Math.ceil(box.height) || + (stage ? stage.height() : 0), + pixelRatio: pixelRatio, + }), + context = canvas.getContext(); + + const bufferCanvas = new SceneCanvas({ + // width and height already multiplied by pixelRatio + // so we need to revert that + // also increase size by x nd y offset to make sure content fits canvas + width: canvas.width / canvas.pixelRatio + Math.abs(x), + height: canvas.height / canvas.pixelRatio + Math.abs(y), + pixelRatio: canvas.pixelRatio, + }); + + if (config.imageSmoothingEnabled === false) { + context._context.imageSmoothingEnabled = false; + } + context.save(); + + if (x || y) { + context.translate(-1 * x, -1 * y); + } + + this.drawScene(canvas, undefined, bufferCanvas); + context.restore(); + + return canvas; + } + /** + * converts node into an canvas element. + * @method + * @name Konva.Node#toCanvas + * @param {Object} config + * @param {Function} config.callback function executed when the composite has completed + * @param {Number} [config.x] x position of canvas section + * @param {Number} [config.y] y position of canvas section + * @param {Number} [config.width] width of canvas section + * @param {Number} [config.height] height of canvas section + * @param {Number} [config.pixelRatio] pixelRatio of output canvas. Default is 1. + * You can use that property to increase quality of the image, for example for super hight quality exports + * or usage on retina (or similar) displays. pixelRatio will be used to multiply the size of exported image. + * If you export to 500x500 size with pixelRatio = 2, then produced image will have size 1000x1000. + * @param {Boolean} [config.imageSmoothingEnabled] set this to false if you want to disable imageSmoothing + * @example + * var canvas = node.toCanvas(); + */ + toCanvas(config?) { + return this._toKonvaCanvas(config)._canvas; + } + /** + * Creates a composite data URL (base64 string). If MIME type is not + * specified, then "image/png" will result. For "image/jpeg", specify a quality + * level as quality (range 0.0 - 1.0) + * @method + * @name Konva.Node#toDataURL + * @param {Object} config + * @param {String} [config.mimeType] can be "image/png" or "image/jpeg". + * "image/png" is the default + * @param {Number} [config.x] x position of canvas section + * @param {Number} [config.y] y position of canvas section + * @param {Number} [config.width] width of canvas section + * @param {Number} [config.height] height of canvas section + * @param {Number} [config.quality] jpeg quality. If using an "image/jpeg" mimeType, + * you can specify the quality from 0 to 1, where 0 is very poor quality and 1 + * is very high quality + * @param {Number} [config.pixelRatio] pixelRatio of output image url. Default is 1. + * You can use that property to increase quality of the image, for example for super hight quality exports + * or usage on retina (or similar) displays. pixelRatio will be used to multiply the size of exported image. + * If you export to 500x500 size with pixelRatio = 2, then produced image will have size 1000x1000. + * @param {Boolean} [config.imageSmoothingEnabled] set this to false if you want to disable imageSmoothing + * @returns {String} + */ + toDataURL(config?: { + x?: number; + y?: number; + width?: number; + height?: number; + pixelRatio?: number; + mimeType?: string; + quality?: number; + callback?: (str: string) => void; + }) { + config = config || {}; + const mimeType = config.mimeType || null, + quality = config.quality || null; + const url = this._toKonvaCanvas(config).toDataURL(mimeType, quality); + if (config.callback) { + config.callback(url); + } + return url; + } + /** + * converts node into an image. Since the toImage + * method is asynchronous, the resulting image can only be retrieved from the config callback + * or the returned Promise. toImage is most commonly used + * to cache complex drawings as an image so that they don't have to constantly be redrawn + * @method + * @name Konva.Node#toImage + * @param {Object} config + * @param {Function} [config.callback] function executed when the composite has completed + * @param {String} [config.mimeType] can be "image/png" or "image/jpeg". + * "image/png" is the default + * @param {Number} [config.x] x position of canvas section + * @param {Number} [config.y] y position of canvas section + * @param {Number} [config.width] width of canvas section + * @param {Number} [config.height] height of canvas section + * @param {Number} [config.quality] jpeg quality. If using an "image/jpeg" mimeType, + * you can specify the quality from 0 to 1, where 0 is very poor quality and 1 + * is very high quality + * @param {Number} [config.pixelRatio] pixelRatio of output image. Default is 1. + * You can use that property to increase quality of the image, for example for super hight quality exports + * or usage on retina (or similar) displays. pixelRatio will be used to multiply the size of exported image. + * If you export to 500x500 size with pixelRatio = 2, then produced image will have size 1000x1000. + * @param {Boolean} [config.imageSmoothingEnabled] set this to false if you want to disable imageSmoothing + * @return {Promise} + * @example + * var image = node.toImage({ + * callback(img) { + * // do stuff with img + * } + * }); + */ + toImage(config?: { + x?: number; + y?: number; + width?: number; + height?: number; + pixelRatio?: number; + mimeType?: string; + quality?: number; + callback?: (img: HTMLImageElement) => void; + }) { + return new Promise((resolve, reject) => { + try { + const callback = config?.callback; + if (callback) delete config.callback; + Util._urlToImage(this.toDataURL(config as any), function (img) { + resolve(img); + callback?.(img); + }); + } catch (err) { + reject(err); + } + }); + } + /** + * Converts node into a blob. Since the toBlob method is asynchronous, + * the resulting blob can only be retrieved from the config callback + * or the returned Promise. + * @method + * @name Konva.Node#toBlob + * @param {Object} config + * @param {Function} [config.callback] function executed when the composite has completed + * @param {Number} [config.x] x position of canvas section + * @param {Number} [config.y] y position of canvas section + * @param {Number} [config.width] width of canvas section + * @param {Number} [config.height] height of canvas section + * @param {Number} [config.pixelRatio] pixelRatio of output canvas. Default is 1. + * You can use that property to increase quality of the image, for example for super hight quality exports + * or usage on retina (or similar) displays. pixelRatio will be used to multiply the size of exported image. + * If you export to 500x500 size with pixelRatio = 2, then produced image will have size 1000x1000. + * @param {Boolean} [config.imageSmoothingEnabled] set this to false if you want to disable imageSmoothing + * @example + * var blob = await node.toBlob({}); + * @returns {Promise} + */ + toBlob(config?: { + x?: number; + y?: number; + width?: number; + height?: number; + pixelRatio?: number; + mimeType?: string; + quality?: number; + callback?: (blob: Blob | null) => void; + }) { + return new Promise((resolve, reject) => { + try { + const callback = config?.callback; + if (callback) delete config.callback; + this.toCanvas(config).toBlob( + (blob) => { + resolve(blob); + callback?.(blob); + }, + config?.mimeType, + config?.quality + ); + } catch (err) { + reject(err); + } + }); + } + setSize(size) { + this.width(size.width); + this.height(size.height); + return this; + } + getSize() { + return { + width: this.width(), + height: this.height(), + }; + } + /** + * get class name, which may return Stage, Layer, Group, or shape class names like Rect, Circle, Text, etc. + * @method + * @name Konva.Node#getClassName + * @returns {String} + */ + getClassName() { + return this.className || this.nodeType; + } + /** + * get the node type, which may return Stage, Layer, Group, or Shape + * @method + * @name Konva.Node#getType + * @returns {String} + */ + getType() { + return this.nodeType; + } + getDragDistance(): number { + // compare with undefined because we need to track 0 value + if (this.attrs.dragDistance !== undefined) { + return this.attrs.dragDistance; + } else if (this.parent) { + return this.parent.getDragDistance(); + } else { + return Konva.dragDistance; + } + } + _off(type, name?, callback?) { + let evtListeners = this.eventListeners[type], + i, + evtName, + handler; + + for (i = 0; i < evtListeners.length; i++) { + evtName = evtListeners[i].name; + handler = evtListeners[i].handler; + + // the following two conditions must be true in order to remove a handler: + // 1) the current event name cannot be konva unless the event name is konva + // this enables developers to force remove a konva specific listener for whatever reason + // 2) an event name is not specified, or if one is specified, it matches the current event name + if ( + (evtName !== 'konva' || name === 'konva') && + (!name || evtName === name) && + (!callback || callback === handler) + ) { + evtListeners.splice(i, 1); + if (evtListeners.length === 0) { + delete this.eventListeners[type]; + break; + } + i--; + } + } + } + _fireChangeEvent(attr, oldVal, newVal) { + this._fire(attr + CHANGE, { + oldVal: oldVal, + newVal: newVal, + }); + } + /** + * add name to node + * @method + * @name Konva.Node#addName + * @param {String} name + * @returns {Konva.Node} + * @example + * node.name('red'); + * node.addName('selected'); + * node.name(); // return 'red selected' + */ + addName(name) { + if (!this.hasName(name)) { + const oldName = this.name(); + const newName = oldName ? oldName + ' ' + name : name; + this.name(newName); + } + return this; + } + /** + * check is node has name + * @method + * @name Konva.Node#hasName + * @param {String} name + * @returns {Boolean} + * @example + * node.name('red'); + * node.hasName('red'); // return true + * node.hasName('selected'); // return false + * node.hasName(''); // return false + */ + hasName(name) { + if (!name) { + return false; + } + const fullName = this.name(); + if (!fullName) { + return false; + } + // if name is '' the "names" will be [''], so I added extra check above + const names = (fullName || '').split(/\s/g); + return names.indexOf(name) !== -1; + } + /** + * remove name from node + * @method + * @name Konva.Node#removeName + * @param {String} name + * @returns {Konva.Node} + * @example + * node.name('red selected'); + * node.removeName('selected'); + * node.hasName('selected'); // return false + * node.name(); // return 'red' + */ + removeName(name) { + const names = (this.name() || '').split(/\s/g); + const index = names.indexOf(name); + if (index !== -1) { + names.splice(index, 1); + this.name(names.join(' ')); + } + return this; + } + /** + * set attr + * @method + * @name Konva.Node#setAttr + * @param {String} attr + * @param {*} val + * @returns {Konva.Node} + * @example + * node.setAttr('x', 5); + */ + setAttr(attr, val) { + const func = this[SET + Util._capitalize(attr)]; + + if (Util._isFunction(func)) { + func.call(this, val); + } else { + // otherwise set directly + this._setAttr(attr, val); + } + return this; + } + _requestDraw() { + if (Konva.autoDrawEnabled) { + const drawNode = this.getLayer() || this.getStage(); + drawNode?.batchDraw(); + } + } + _setAttr(key, val) { + const oldVal = this.attrs[key]; + if (oldVal === val && !Util.isObject(val)) { + return; + } + if (val === undefined || val === null) { + delete this.attrs[key]; + } else { + this.attrs[key] = val; + } + if (this._shouldFireChangeEvents) { + this._fireChangeEvent(key, oldVal, val); + } + this._requestDraw(); + } + _setComponentAttr(key, component, val) { + let oldVal; + if (val !== undefined) { + oldVal = this.attrs[key]; + + if (!oldVal) { + // set value to default value using getAttr + this.attrs[key] = this.getAttr(key); + } + + this.attrs[key][component] = val; + this._fireChangeEvent(key, oldVal, val); + } + } + _fireAndBubble(eventType, evt, compareShape?) { + if (evt && this.nodeType === SHAPE) { + evt.target = this; + } + + const shouldStop = + (eventType === MOUSEENTER || eventType === MOUSELEAVE) && + ((compareShape && + (this === compareShape || + (this.isAncestorOf && this.isAncestorOf(compareShape)))) || + (this.nodeType === 'Stage' && !compareShape)); + + if (!shouldStop) { + this._fire(eventType, evt); + + // simulate event bubbling + const stopBubble = + (eventType === MOUSEENTER || eventType === MOUSELEAVE) && + compareShape && + compareShape.isAncestorOf && + compareShape.isAncestorOf(this) && + !compareShape.isAncestorOf(this.parent); + if ( + ((evt && !evt.cancelBubble) || !evt) && + this.parent && + this.parent.isListening() && + !stopBubble + ) { + if (compareShape && compareShape.parent) { + this._fireAndBubble.call(this.parent, eventType, evt, compareShape); + } else { + this._fireAndBubble.call(this.parent, eventType, evt); + } + } + } + } + + _getProtoListeners(eventType) { + const allListeners = this._cache.get(ALL_LISTENERS) ?? {}; + let events = allListeners?.[eventType]; + if (events === undefined) { + //recalculate cache + events = []; + let obj = Object.getPrototypeOf(this); + while (obj) { + const hierarchyEvents = obj.eventListeners?.[eventType] ?? []; + events.push(...hierarchyEvents); + obj = Object.getPrototypeOf(obj); + } + // update cache + allListeners[eventType] = events; + this._cache.set(ALL_LISTENERS, allListeners); + } + + return events; + } + _fire(eventType, evt) { + evt = evt || {}; + evt.currentTarget = this; + evt.type = eventType; + + const topListeners = this._getProtoListeners(eventType); + if (topListeners) { + for (var i = 0; i < topListeners.length; i++) { + topListeners[i].handler.call(this, evt); + } + } + + // it is important to iterate over self listeners without cache + // because events can be added/removed while firing + const selfListeners = this.eventListeners[eventType]; + if (selfListeners) { + for (var i = 0; i < selfListeners.length; i++) { + selfListeners[i].handler.call(this, evt); + } + } + } + /** + * draw both scene and hit graphs. If the node being drawn is the stage, all of the layers will be cleared and redrawn + * @method + * @name Konva.Node#draw + * @returns {Konva.Node} + */ + draw() { + this.drawScene(); + this.drawHit(); + return this; + } + + // drag & drop + _createDragElement(evt) { + const pointerId = evt ? evt.pointerId : undefined; + const stage = this.getStage(); + const ap = this.getAbsolutePosition(); + if (!stage) { + return; + } + const pos = + stage._getPointerById(pointerId) || + stage._changedPointerPositions[0] || + ap; + DD._dragElements.set(this._id, { + node: this, + startPointerPos: pos, + offset: { + x: pos.x - ap.x, + y: pos.y - ap.y, + }, + dragStatus: 'ready', + pointerId, + }); + } + + /** + * initiate drag and drop. + * @method + * @name Konva.Node#startDrag + */ + startDrag(evt?: any, bubbleEvent = true) { + if (!DD._dragElements.has(this._id)) { + this._createDragElement(evt); + } + + const elem = DD._dragElements.get(this._id)!; + elem.dragStatus = 'dragging'; + this.fire( + 'dragstart', + { + type: 'dragstart', + target: this, + evt: evt && evt.evt, + }, + bubbleEvent + ); + } + + _setDragPosition(evt, elem) { + // const pointers = this.getStage().getPointersPositions(); + // const pos = pointers.find(p => p.id === this._dragEventId); + const pos = this.getStage()!._getPointerById(elem.pointerId); + + if (!pos) { + return; + } + let newNodePos = { + x: pos.x - elem.offset.x, + y: pos.y - elem.offset.y, + }; + + const dbf = this.dragBoundFunc(); + if (dbf !== undefined) { + const bounded = dbf.call(this, newNodePos, evt); + if (!bounded) { + Util.warn( + 'dragBoundFunc did not return any value. That is unexpected behavior. You must return new absolute position from dragBoundFunc.' + ); + } else { + newNodePos = bounded; + } + } + + if ( + !this._lastPos || + this._lastPos.x !== newNodePos.x || + this._lastPos.y !== newNodePos.y + ) { + this.setAbsolutePosition(newNodePos); + this._requestDraw(); + } + + this._lastPos = newNodePos; + } + + /** + * stop drag and drop + * @method + * @name Konva.Node#stopDrag + */ + stopDrag(evt?) { + const elem = DD._dragElements.get(this._id); + if (elem) { + elem.dragStatus = 'stopped'; + } + DD._endDragBefore(evt); + DD._endDragAfter(evt); + } + + setDraggable(draggable) { + this._setAttr('draggable', draggable); + this._dragChange(); + } + + /** + * determine if node is currently in drag and drop mode + * @method + * @name Konva.Node#isDragging + */ + isDragging() { + const elem = DD._dragElements.get(this._id); + return elem ? elem.dragStatus === 'dragging' : false; + } + + _listenDrag() { + this._dragCleanup(); + + this.on('mousedown.konva touchstart.konva', function (evt) { + const shouldCheckButton = evt.evt['button'] !== undefined; + const canDrag = + !shouldCheckButton || Konva.dragButtons.indexOf(evt.evt['button']) >= 0; + if (!canDrag) { + return; + } + if (this.isDragging()) { + return; + } + + let hasDraggingChild = false; + DD._dragElements.forEach((elem) => { + if (this.isAncestorOf(elem.node)) { + hasDraggingChild = true; + } + }); + // nested drag can be started + // in that case we don't need to start new drag + if (!hasDraggingChild) { + this._createDragElement(evt); + } + }); + } + + _dragChange() { + if (this.attrs.draggable) { + this._listenDrag(); + } else { + // remove event listeners + this._dragCleanup(); + + /* + * force drag and drop to end + * if this node is currently in + * drag and drop mode + */ + const stage = this.getStage(); + if (!stage) { + return; + } + const dragElement = DD._dragElements.get(this._id); + const isDragging = dragElement && dragElement.dragStatus === 'dragging'; + const isReady = dragElement && dragElement.dragStatus === 'ready'; + + if (isDragging) { + this.stopDrag(); + } else if (isReady) { + DD._dragElements.delete(this._id); + } + } + } + + _dragCleanup() { + this.off('mousedown.konva'); + this.off('touchstart.konva'); + } + + /** + * determine if node (at least partially) is currently in user-visible area + * @method + * @param {(Number | Object)} margin optional margin in pixels + * @param {Number} margin.x + * @param {Number} margin.y + * @returns {Boolean} + * @name Konva.Node#isClientRectOnScreen + * @example + * // get index + * // default calculations + * var isOnScreen = node.isClientRectOnScreen() + * // increase object size (or screen size) for cases when objects close to the screen still need to be marked as "visible" + * var isOnScreen = node.isClientRectOnScreen({ x: stage.width(), y: stage.height() }) + */ + isClientRectOnScreen( + margin: { x: number; y: number } = { x: 0, y: 0 } + ): boolean { + const stage = this.getStage(); + if (!stage) { + return false; + } + const screenRect = { + x: -margin.x, + y: -margin.y, + width: stage.width() + 2 * margin.x, + height: stage.height() + 2 * margin.y, + }; + return Util.haveIntersection(screenRect, this.getClientRect()); + } + + // @ts-ignore: + preventDefault: GetSet; + + // from filters + blue: GetSet; + brightness: GetSet; + contrast: GetSet; + blurRadius: GetSet; + luminance: GetSet; + green: GetSet; + alpha: GetSet; + hue: GetSet; + kaleidoscopeAngle: GetSet; + kaleidoscopePower: GetSet; + levels: GetSet; + noise: GetSet; + pixelSize: GetSet; + red: GetSet; + saturation: GetSet; + threshold: GetSet; + value: GetSet; + + dragBoundFunc: GetSet< + (this: Node, pos: Vector2d, event: any) => Vector2d, + this + >; + draggable: GetSet; + dragDistance: GetSet; + embossBlend: GetSet; + embossDirection: GetSet; + embossStrength: GetSet; + embossWhiteLevel: GetSet; + enhance: GetSet; + filters: GetSet; + position: GetSet; + absolutePosition: GetSet; + size: GetSet<{ width: number; height: number }, this>; + + id: GetSet; + + listening: GetSet; + name: GetSet; + offset: GetSet; + offsetX: GetSet; + offsetY: GetSet; + opacity: GetSet; + + rotation: GetSet; + zIndex: GetSet; + + scale: GetSet; + scaleX: GetSet; + scaleY: GetSet; + skew: GetSet; + skewX: GetSet; + skewY: GetSet; + + to: (params: AnimTo) => void; + + transformsEnabled: GetSet; + + visible: GetSet; + width: GetSet; + height: GetSet; + + x: GetSet; + y: GetSet; + globalCompositeOperation: GetSet; + + /** + * create node with JSON string or an Object. De-serializtion does not generate custom + * shape drawing functions, images, or event handlers (this would make the + * serialized object huge). If your app uses custom shapes, images, and + * event handlers (it probably does), then you need to select the appropriate + * shapes after loading the stage and set these properties via on(), setSceneFunc(), + * and setImage() methods + * @method + * @memberof Konva.Node + * @param {String|Object} json string or object + * @param {Element} [container] optional container dom element used only if you're + * creating a stage node + */ + static create(data, container?) { + if (Util._isString(data)) { + data = JSON.parse(data); + } + return this._createNode(data, container); + } + + static _createNode(obj, container?) { + let className = Node.prototype.getClassName.call(obj), + children = obj.children, + no, + len, + n; + + // if container was passed in, add it to attrs + if (container) { + obj.attrs.container = container; + } + + if (!Konva[className]) { + Util.warn( + 'Can not find a node with class name "' + + className + + '". Fallback to "Shape".' + ); + className = 'Shape'; + } + + const Class = Konva[className]; + + no = new Class(obj.attrs); + if (children) { + len = children.length; + for (n = 0; n < len; n++) { + no.add(Node._createNode(children[n])); + } + } + + return no; + } +} + +interface AnimTo extends NodeConfig { + onFinish?: Function; + onUpdate?: Function; + duration?: number; +} + +Node.prototype.nodeType = 'Node'; +Node.prototype._attrsAffectingSize = []; + +// attache events listeners once into prototype +// that way we don't spend too much time on making an new instance +Node.prototype.eventListeners = {}; +Node.prototype.on.call(Node.prototype, TRANSFORM_CHANGE_STR, function () { + if (this._batchingTransformChange) { + this._needClearTransformCache = true; + return; + } + this._clearCache(TRANSFORM); + this._clearSelfAndDescendantCache(ABSOLUTE_TRANSFORM); +}); + +Node.prototype.on.call(Node.prototype, 'visibleChange.konva', function () { + this._clearSelfAndDescendantCache(VISIBLE); +}); +Node.prototype.on.call(Node.prototype, 'listeningChange.konva', function () { + this._clearSelfAndDescendantCache(LISTENING); +}); +Node.prototype.on.call(Node.prototype, 'opacityChange.konva', function () { + this._clearSelfAndDescendantCache(ABSOLUTE_OPACITY); +}); + +const addGetterSetter = Factory.addGetterSetter; + +/** + * get/set zIndex relative to the node's siblings who share the same parent. + * Please remember that zIndex is not absolute (like in CSS). It is relative to parent element only. + * @name Konva.Node#zIndex + * @method + * @param {Number} index + * @returns {Number} + * @example + * // get index + * var index = node.zIndex(); + * + * // set index + * node.zIndex(2); + */ +addGetterSetter(Node, 'zIndex'); + +/** + * get/set node absolute position + * @name Konva.Node#absolutePosition + * @method + * @param {Object} pos + * @param {Number} pos.x + * @param {Number} pos.y + * @returns {Object} + * @example + * // get position + * var position = node.absolutePosition(); + * + * // set position + * node.absolutePosition({ + * x: 5, + * y: 10 + * }); + */ +addGetterSetter(Node, 'absolutePosition'); + +addGetterSetter(Node, 'position'); +/** + * get/set node position relative to parent + * @name Konva.Node#position + * @method + * @param {Object} pos + * @param {Number} pos.x + * @param {Number} pos.y + * @returns {Object} + * @example + * // get position + * var position = node.position(); + * + * // set position + * node.position({ + * x: 5, + * y: 10 + * }); + */ + +addGetterSetter(Node, 'x', 0, getNumberValidator()); + +/** + * get/set x position + * @name Konva.Node#x + * @method + * @param {Number} x + * @returns {Object} + * @example + * // get x + * var x = node.x(); + * + * // set x + * node.x(5); + */ + +addGetterSetter(Node, 'y', 0, getNumberValidator()); + +/** + * get/set y position + * @name Konva.Node#y + * @method + * @param {Number} y + * @returns {Integer} + * @example + * // get y + * var y = node.y(); + * + * // set y + * node.y(5); + */ + +addGetterSetter( + Node, + 'globalCompositeOperation', + 'source-over', + getStringValidator() +); + +/** + * get/set globalCompositeOperation of a node. globalCompositeOperation DOESN'T affect hit graph of nodes. So they are still trigger to events as they have default "source-over" globalCompositeOperation. + * @name Konva.Node#globalCompositeOperation + * @method + * @param {String} type + * @returns {String} + * @example + * // get globalCompositeOperation + * var globalCompositeOperation = shape.globalCompositeOperation(); + * + * // set globalCompositeOperation + * shape.globalCompositeOperation('source-in'); + */ +addGetterSetter(Node, 'opacity', 1, getNumberValidator()); + +/** + * get/set opacity. Opacity values range from 0 to 1. + * A node with an opacity of 0 is fully transparent, and a node + * with an opacity of 1 is fully opaque + * @name Konva.Node#opacity + * @method + * @param {Object} opacity + * @returns {Number} + * @example + * // get opacity + * var opacity = node.opacity(); + * + * // set opacity + * node.opacity(0.5); + */ + +addGetterSetter(Node, 'name', '', getStringValidator()); + +/** + * get/set name. + * @name Konva.Node#name + * @method + * @param {String} name + * @returns {String} + * @example + * // get name + * var name = node.name(); + * + * // set name + * node.name('foo'); + * + * // also node may have multiple names (as css classes) + * node.name('foo bar'); + */ + +addGetterSetter(Node, 'id', '', getStringValidator()); + +/** + * get/set id. Id is global for whole page. + * @name Konva.Node#id + * @method + * @param {String} id + * @returns {String} + * @example + * // get id + * var name = node.id(); + * + * // set id + * node.id('foo'); + */ + +addGetterSetter(Node, 'rotation', 0, getNumberValidator()); + +/** + * get/set rotation in degrees + * @name Konva.Node#rotation + * @method + * @param {Number} rotation + * @returns {Number} + * @example + * // get rotation in degrees + * var rotation = node.rotation(); + * + * // set rotation in degrees + * node.rotation(45); + */ + +Factory.addComponentsGetterSetter(Node, 'scale', ['x', 'y']); + +/** + * get/set scale + * @name Konva.Node#scale + * @param {Object} scale + * @param {Number} scale.x + * @param {Number} scale.y + * @method + * @returns {Object} + * @example + * // get scale + * var scale = node.scale(); + * + * // set scale + * shape.scale({ + * x: 2, + * y: 3 + * }); + */ + +addGetterSetter(Node, 'scaleX', 1, getNumberValidator()); + +/** + * get/set scale x + * @name Konva.Node#scaleX + * @param {Number} x + * @method + * @returns {Number} + * @example + * // get scale x + * var scaleX = node.scaleX(); + * + * // set scale x + * node.scaleX(2); + */ + +addGetterSetter(Node, 'scaleY', 1, getNumberValidator()); + +/** + * get/set scale y + * @name Konva.Node#scaleY + * @param {Number} y + * @method + * @returns {Number} + * @example + * // get scale y + * var scaleY = node.scaleY(); + * + * // set scale y + * node.scaleY(2); + */ + +Factory.addComponentsGetterSetter(Node, 'skew', ['x', 'y']); + +/** + * get/set skew + * @name Konva.Node#skew + * @param {Object} skew + * @param {Number} skew.x + * @param {Number} skew.y + * @method + * @returns {Object} + * @example + * // get skew + * var skew = node.skew(); + * + * // set skew + * node.skew({ + * x: 20, + * y: 10 + * }); + */ + +addGetterSetter(Node, 'skewX', 0, getNumberValidator()); + +/** + * get/set skew x + * @name Konva.Node#skewX + * @param {Number} x + * @method + * @returns {Number} + * @example + * // get skew x + * var skewX = node.skewX(); + * + * // set skew x + * node.skewX(3); + */ + +addGetterSetter(Node, 'skewY', 0, getNumberValidator()); + +/** + * get/set skew y + * @name Konva.Node#skewY + * @param {Number} y + * @method + * @returns {Number} + * @example + * // get skew y + * var skewY = node.skewY(); + * + * // set skew y + * node.skewY(3); + */ + +Factory.addComponentsGetterSetter(Node, 'offset', ['x', 'y']); + +/** + * get/set offset. Offsets the default position and rotation point + * @method + * @param {Object} offset + * @param {Number} offset.x + * @param {Number} offset.y + * @returns {Object} + * @example + * // get offset + * var offset = node.offset(); + * + * // set offset + * node.offset({ + * x: 20, + * y: 10 + * }); + */ + +addGetterSetter(Node, 'offsetX', 0, getNumberValidator()); + +/** + * get/set offset x + * @name Konva.Node#offsetX + * @method + * @param {Number} x + * @returns {Number} + * @example + * // get offset x + * var offsetX = node.offsetX(); + * + * // set offset x + * node.offsetX(3); + */ + +addGetterSetter(Node, 'offsetY', 0, getNumberValidator()); + +/** + * get/set offset y + * @name Konva.Node#offsetY + * @method + * @param {Number} y + * @returns {Number} + * @example + * // get offset y + * var offsetY = node.offsetY(); + * + * // set offset y + * node.offsetY(3); + */ + +addGetterSetter(Node, 'dragDistance', undefined, getNumberValidator()); + +/** + * get/set drag distance + * @name Konva.Node#dragDistance + * @method + * @param {Number} distance + * @returns {Number} + * @example + * // get drag distance + * var dragDistance = node.dragDistance(); + * + * // set distance + * // node starts dragging only if pointer moved more then 3 pixels + * node.dragDistance(3); + * // or set globally + * Konva.dragDistance = 3; + */ + +addGetterSetter(Node, 'width', 0, getNumberValidator()); +/** + * get/set width + * @name Konva.Node#width + * @method + * @param {Number} width + * @returns {Number} + * @example + * // get width + * var width = node.width(); + * + * // set width + * node.width(100); + */ + +addGetterSetter(Node, 'height', 0, getNumberValidator()); +/** + * get/set height + * @name Konva.Node#height + * @method + * @param {Number} height + * @returns {Number} + * @example + * // get height + * var height = node.height(); + * + * // set height + * node.height(100); + */ + +addGetterSetter(Node, 'listening', true, getBooleanValidator()); +/** + * get/set listening attr. If you need to determine if a node is listening or not + * by taking into account its parents, use the isListening() method + * nodes with listening set to false will not be detected in hit graph + * so they will be ignored in container.getIntersection() method + * @name Konva.Node#listening + * @method + * @param {Boolean} listening Can be true, or false. The default is true. + * @returns {Boolean} + * @example + * // get listening attr + * var listening = node.listening(); + * + * // stop listening for events, remove node and all its children from hit graph + * node.listening(false); + * + * // listen to events according to the parent + * node.listening(true); + */ + +/** + * get/set preventDefault + * By default all shapes will prevent default behavior + * of a browser on a pointer move or tap. + * that will prevent native scrolling when you are trying to drag&drop a node + * but sometimes you may need to enable default actions + * in that case you can set the property to false + * @name Konva.Node#preventDefault + * @method + * @param {Boolean} preventDefault + * @returns {Boolean} + * @example + * // get preventDefault + * var shouldPrevent = shape.preventDefault(); + * + * // set preventDefault + * shape.preventDefault(false); + */ + +addGetterSetter(Node, 'preventDefault', true, getBooleanValidator()); + +addGetterSetter(Node, 'filters', undefined, function (this: Node, val) { + this._filterUpToDate = false; + return val; +}); +/** + * get/set filters. Filters are applied to cached canvases + * @name Konva.Node#filters + * @method + * @param {Array} filters array of filters + * @returns {Array} + * @example + * // get filters + * var filters = node.filters(); + * + * // set a single filter + * node.cache(); + * node.filters([Konva.Filters.Blur]); + * + * // set multiple filters + * node.cache(); + * node.filters([ + * Konva.Filters.Blur, + * Konva.Filters.Sepia, + * Konva.Filters.Invert + * ]); + */ + +addGetterSetter(Node, 'visible', true, getBooleanValidator()); +/** + * get/set visible attr. Can be true, or false. The default is true. + * If you need to determine if a node is visible or not + * by taking into account its parents, use the isVisible() method + * @name Konva.Node#visible + * @method + * @param {Boolean} visible + * @returns {Boolean} + * @example + * // get visible attr + * var visible = node.visible(); + * + * // make invisible + * node.visible(false); + * + * // make visible (according to the parent) + * node.visible(true); + * + */ + +addGetterSetter(Node, 'transformsEnabled', 'all', getStringValidator()); + +/** + * get/set transforms that are enabled. Can be "all", "none", or "position". The default + * is "all" + * @name Konva.Node#transformsEnabled + * @method + * @param {String} enabled + * @returns {String} + * @example + * // enable position transform only to improve draw performance + * node.transformsEnabled('position'); + * + * // enable all transforms + * node.transformsEnabled('all'); + */ + +/** + * get/set node size + * @name Konva.Node#size + * @method + * @param {Object} size + * @param {Number} size.width + * @param {Number} size.height + * @returns {Object} + * @example + * // get node size + * var size = node.size(); + * var width = size.width; + * var height = size.height; + * + * // set size + * node.size({ + * width: 100, + * height: 200 + * }); + */ +addGetterSetter(Node, 'size'); + +/** + * get/set drag bound function. This is used to override the default + * drag and drop position. + * @name Konva.Node#dragBoundFunc + * @method + * @param {Function} dragBoundFunc + * @returns {Function} + * @example + * // get drag bound function + * var dragBoundFunc = node.dragBoundFunc(); + * + * // create vertical drag and drop + * node.dragBoundFunc(function(pos){ + * // important pos - is absolute position of the node + * // you should return absolute position too + * return { + * x: this.absolutePosition().x, + * y: pos.y + * }; + * }); + */ +addGetterSetter(Node, 'dragBoundFunc'); + +/** + * get/set draggable flag + * @name Konva.Node#draggable + * @method + * @param {Boolean} draggable + * @returns {Boolean} + * @example + * // get draggable flag + * var draggable = node.draggable(); + * + * // enable drag and drop + * node.draggable(true); + * + * // disable drag and drop + * node.draggable(false); + */ +addGetterSetter(Node, 'draggable', false, getBooleanValidator()); + +Factory.backCompat(Node, { + rotateDeg: 'rotate', + setRotationDeg: 'setRotation', + getRotationDeg: 'getRotation', +}); diff --git a/src/PointerEvents.ts b/src/PointerEvents.ts new file mode 100644 index 000000000..82515948d --- /dev/null +++ b/src/PointerEvents.ts @@ -0,0 +1,67 @@ +import { KonvaEventObject } from './Node'; +import { Konva } from './Global'; + +import { Shape } from './Shape'; +import { Stage } from './Stage'; + +const Captures = new Map(); + +// we may use this module for capturing touch events too +// so make sure we don't do something super specific to pointer +const SUPPORT_POINTER_EVENTS = Konva._global['PointerEvent'] !== undefined; + +export interface KonvaPointerEvent extends KonvaEventObject { + pointerId: number; +} + +export function getCapturedShape(pointerId: number) { + return Captures.get(pointerId); +} + +export function createEvent(evt: PointerEvent): KonvaPointerEvent { + return { + evt, + pointerId: evt.pointerId, + } as any; +} + +export function hasPointerCapture(pointerId: number, shape: Shape | Stage) { + return Captures.get(pointerId) === shape; +} + +export function setPointerCapture(pointerId: number, shape: Shape | Stage) { + releaseCapture(pointerId); + + const stage = shape.getStage(); + if (!stage) return; + + Captures.set(pointerId, shape); + + if (SUPPORT_POINTER_EVENTS) { + shape._fire( + 'gotpointercapture', + createEvent(new PointerEvent('gotpointercapture')) + ); + } +} + +export function releaseCapture(pointerId: number, target?: Shape | Stage) { + const shape = Captures.get(pointerId); + + if (!shape) return; + + const stage = shape.getStage(); + + if (stage && stage.content) { + // stage.content.releasePointerCapture(pointerId); + } + + Captures.delete(pointerId); + + if (SUPPORT_POINTER_EVENTS) { + shape._fire( + 'lostpointercapture', + createEvent(new PointerEvent('lostpointercapture')) + ); + } +} diff --git a/src/Shape.ts b/src/Shape.ts new file mode 100644 index 000000000..70305ec27 --- /dev/null +++ b/src/Shape.ts @@ -0,0 +1,2037 @@ +import { Konva } from './Global'; +import { Transform, Util } from './Util'; +import { Factory } from './Factory'; +import { Node, NodeConfig } from './Node'; +import { + getNumberValidator, + getNumberOrAutoValidator, + getStringValidator, + getBooleanValidator, + getStringOrGradientValidator, +} from './Validators'; + +import { Context, SceneContext } from './Context'; +import { _registerNode } from './Global'; +import * as PointerEvents from './PointerEvents'; + +import { GetSet, Vector2d } from './types'; +import { HitCanvas, SceneCanvas } from './Canvas'; + +// hack from here https://stackoverflow.com/questions/52667959/what-is-the-purpose-of-bivariancehack-in-typescript-types/52668133#52668133 +export type ShapeConfigHandler = { + bivarianceHack(ctx: Context, shape: TTarget): void; +}['bivarianceHack']; + +export type LineJoin = 'round' | 'bevel' | 'miter'; +export type LineCap = 'butt' | 'round' | 'square'; + +export interface ShapeConfig extends NodeConfig { + fill?: string | CanvasGradient; + fillPatternImage?: HTMLImageElement; + fillPatternX?: number; + fillPatternY?: number; + fillPatternOffset?: Vector2d; + fillPatternOffsetX?: number; + fillPatternOffsetY?: number; + fillPatternScale?: Vector2d; + fillPatternScaleX?: number; + fillPatternScaleY?: number; + fillPatternRotation?: number; + fillPatternRepeat?: string; + fillLinearGradientStartPoint?: Vector2d; + fillLinearGradientStartPointX?: number; + fillLinearGradientStartPointY?: number; + fillLinearGradientEndPoint?: Vector2d; + fillLinearGradientEndPointX?: number; + fillLinearGradientEndPointY?: number; + fillLinearGradientColorStops?: Array; + fillRadialGradientStartPoint?: Vector2d; + fillRadialGradientStartPointX?: number; + fillRadialGradientStartPointY?: number; + fillRadialGradientEndPoint?: Vector2d; + fillRadialGradientEndPointX?: number; + fillRadialGradientEndPointY?: number; + fillRadialGradientStartRadius?: number; + fillRadialGradientEndRadius?: number; + fillRadialGradientColorStops?: Array; + fillEnabled?: boolean; + fillPriority?: string; + fillRule?: CanvasFillRule; + stroke?: string | CanvasGradient; + strokeWidth?: number; + fillAfterStrokeEnabled?: boolean; + hitStrokeWidth?: number | string; + strokeScaleEnabled?: boolean; + strokeHitEnabled?: boolean; + strokeEnabled?: boolean; + lineJoin?: LineJoin; + lineCap?: LineCap; + sceneFunc?: (con: Context, shape: Shape) => void; + hitFunc?: (con: Context, shape: Shape) => void; + shadowColor?: string; + shadowBlur?: number; + shadowOffset?: Vector2d; + shadowOffsetX?: number; + shadowOffsetY?: number; + shadowOpacity?: number; + shadowEnabled?: boolean; + shadowForStrokeEnabled?: boolean; + dash?: number[]; + dashOffset?: number; + dashEnabled?: boolean; + perfectDrawEnabled?: boolean; +} + +export interface ShapeGetClientRectConfig { + skipTransform?: boolean; + skipShadow?: boolean; + skipStroke?: boolean; + relativeTo?: Node; +} + +export type FillFuncOutput = + | void + | [Path2D | CanvasFillRule] + | [Path2D, CanvasFillRule]; + +const HAS_SHADOW = 'hasShadow'; +const SHADOW_RGBA = 'shadowRGBA'; +const patternImage = 'patternImage'; +const linearGradient = 'linearGradient'; +const radialGradient = 'radialGradient'; + +let dummyContext: CanvasRenderingContext2D; +function getDummyContext(): CanvasRenderingContext2D { + if (dummyContext) { + return dummyContext; + } + dummyContext = Util.createCanvasElement().getContext('2d')!; + return dummyContext; +} + +export const shapes: { [key: string]: Shape } = {}; + +// TODO: idea - use only "remove" (or destroy method) +// how? on add, check that every inner shape has reference in konva store with color +// on remove - clear that reference +// the approach is good. But what if we want to cache the shape before we add it into the stage +// what color to use for hit test? + +function _fillFunc(this: Node, context) { + const fillRule = this.attrs.fillRule; + if (fillRule) { + context.fill(fillRule); + } else { + context.fill(); + } +} +function _strokeFunc(context) { + context.stroke(); +} +function _fillFuncHit(this: Node, context) { + const fillRule = this.attrs.fillRule; + if (fillRule) { + context.fill(fillRule); + } else { + context.fill(); + } +} +function _strokeFuncHit(context) { + context.stroke(); +} + +function _clearHasShadowCache(this: Node) { + this._clearCache(HAS_SHADOW); +} + +function _clearGetShadowRGBACache(this: Node) { + this._clearCache(SHADOW_RGBA); +} + +function _clearFillPatternCache(this: Node) { + this._clearCache(patternImage); +} + +function _clearLinearGradientCache(this: Node) { + this._clearCache(linearGradient); +} + +function _clearRadialGradientCache(this: Node) { + this._clearCache(radialGradient); +} + +/** + * Shape constructor. Shapes are primitive objects such as rectangles, + * circles, text, lines, etc. + * @constructor + * @memberof Konva + * @augments Konva.Node + * @param {Object} config + * @@shapeParams + * @@nodeParams + * @example + * var customShape = new Konva.Shape({ + * x: 5, + * y: 10, + * fill: 'red', + * // a Konva.Canvas renderer is passed into the sceneFunc function + * sceneFunc (context, shape) { + * context.beginPath(); + * context.moveTo(200, 50); + * context.lineTo(420, 80); + * context.quadraticCurveTo(300, 100, 260, 170); + * context.closePath(); + * // Konva specific method + * context.fillStrokeShape(shape); + * } + *}); + */ +export class Shape< + Config extends ShapeConfig = ShapeConfig +> extends Node { + _centroid: boolean; + colorKey: string; + + _fillFunc: (ctx: Context) => FillFuncOutput; + _strokeFunc: (ctx: Context) => void; + _fillFuncHit: (ctx: Context) => void; + _strokeFuncHit: (ctx: Context) => void; + + constructor(config?: Config) { + super(config); + // set colorKey + let key: string; + + while (true) { + key = Util.getRandomColor(); + if (key && !(key in shapes)) { + break; + } + } + + this.colorKey = key; + shapes[key] = this; + } + + getContext() { + Util.warn('shape.getContext() method is deprecated. Please do not use it.'); + return this.getLayer()!.getContext(); + } + getCanvas() { + Util.warn('shape.getCanvas() method is deprecated. Please do not use it.'); + return this.getLayer()!.getCanvas(); + } + + getSceneFunc() { + return this.attrs.sceneFunc || this['_sceneFunc']; + } + + getHitFunc() { + return this.attrs.hitFunc || this['_hitFunc']; + } + /** + * returns whether or not a shadow will be rendered + * @method + * @name Konva.Shape#hasShadow + * @returns {Boolean} + */ + hasShadow() { + return this._getCache(HAS_SHADOW, this._hasShadow); + } + _hasShadow() { + return ( + this.shadowEnabled() && + this.shadowOpacity() !== 0 && + !!( + this.shadowColor() || + this.shadowBlur() || + this.shadowOffsetX() || + this.shadowOffsetY() + ) + ); + } + _getFillPattern() { + return this._getCache(patternImage, this.__getFillPattern); + } + __getFillPattern() { + if (this.fillPatternImage()) { + const ctx = getDummyContext(); + const pattern = ctx.createPattern( + this.fillPatternImage(), + this.fillPatternRepeat() || 'repeat' + ); + if (pattern && pattern.setTransform) { + const tr = new Transform(); + + tr.translate(this.fillPatternX(), this.fillPatternY()); + tr.rotate(Konva.getAngle(this.fillPatternRotation())); + tr.scale(this.fillPatternScaleX(), this.fillPatternScaleY()); + tr.translate( + -1 * this.fillPatternOffsetX(), + -1 * this.fillPatternOffsetY() + ); + + const m = tr.getMatrix(); + + const matrix = + typeof DOMMatrix === 'undefined' + ? { + a: m[0], // Horizontal scaling. A value of 1 results in no scaling. + b: m[1], // Vertical skewing. + c: m[2], // Horizontal skewing. + d: m[3], + e: m[4], // Horizontal translation (moving). + f: m[5], // Vertical translation (moving). + } + : new DOMMatrix(m); + + pattern.setTransform(matrix); + } + return pattern; + } + } + _getLinearGradient() { + return this._getCache(linearGradient, this.__getLinearGradient); + } + __getLinearGradient() { + const colorStops = this.fillLinearGradientColorStops(); + if (colorStops) { + const ctx = getDummyContext(); + + const start = this.fillLinearGradientStartPoint(); + const end = this.fillLinearGradientEndPoint(); + const grd = ctx.createLinearGradient(start.x, start.y, end.x, end.y); + + // build color stops + for (let n = 0; n < colorStops.length; n += 2) { + grd.addColorStop(colorStops[n] as number, colorStops[n + 1] as string); + } + return grd; + } + } + + _getRadialGradient() { + return this._getCache(radialGradient, this.__getRadialGradient); + } + __getRadialGradient() { + const colorStops = this.fillRadialGradientColorStops(); + if (colorStops) { + const ctx = getDummyContext(); + + const start = this.fillRadialGradientStartPoint(); + const end = this.fillRadialGradientEndPoint(); + const grd = ctx.createRadialGradient( + start.x, + start.y, + this.fillRadialGradientStartRadius(), + end.x, + end.y, + this.fillRadialGradientEndRadius() + ); + + // build color stops + for (let n = 0; n < colorStops.length; n += 2) { + grd.addColorStop(colorStops[n] as number, colorStops[n + 1] as string); + } + return grd; + } + } + getShadowRGBA() { + return this._getCache(SHADOW_RGBA, this._getShadowRGBA); + } + _getShadowRGBA() { + if (!this.hasShadow()) { + return; + } + const rgba = Util.colorToRGBA(this.shadowColor()); + if (rgba) { + return ( + 'rgba(' + + rgba.r + + ',' + + rgba.g + + ',' + + rgba.b + + ',' + + rgba.a * (this.shadowOpacity() || 1) + + ')' + ); + } + } + /** + * returns whether or not the shape will be filled + * @method + * @name Konva.Shape#hasFill + * @returns {Boolean} + */ + hasFill() { + return this._calculate( + 'hasFill', + [ + 'fillEnabled', + 'fill', + 'fillPatternImage', + 'fillLinearGradientColorStops', + 'fillRadialGradientColorStops', + ], + () => { + return ( + this.fillEnabled() && + !!( + this.fill() || + this.fillPatternImage() || + this.fillLinearGradientColorStops() || + this.fillRadialGradientColorStops() + ) + ); + } + ); + } + /** + * returns whether or not the shape will be stroked + * @method + * @name Konva.Shape#hasStroke + * @returns {Boolean} + */ + hasStroke() { + return this._calculate( + 'hasStroke', + [ + 'strokeEnabled', + 'strokeWidth', + 'stroke', + 'strokeLinearGradientColorStops', + ], + () => { + return ( + this.strokeEnabled() && + this.strokeWidth() && + !!(this.stroke() || this.strokeLinearGradientColorStops()) + // this.getStrokeRadialGradientColorStops() + ); + } + ); + // return ( + // this.strokeEnabled() && + // this.strokeWidth() && + // !!(this.stroke() || this.strokeLinearGradientColorStops()) + // // this.getStrokeRadialGradientColorStops() + // ); + } + hasHitStroke() { + const width = this.hitStrokeWidth(); + + // on auto just check by stroke + if (width === 'auto') { + return this.hasStroke(); + } + + // we should enable hit stroke if stroke is enabled + // and we have some value from width + return this.strokeEnabled() && !!width; + } + /** + * determines if point is in the shape, regardless if other shapes are on top of it. Note: because + * this method clears a temporary canvas and then redraws the shape, it performs very poorly if executed many times + * consecutively. Please use the {@link Konva.Stage#getIntersection} method if at all possible + * because it performs much better + * @method + * @name Konva.Shape#intersects + * @param {Object} point + * @param {Number} point.x + * @param {Number} point.y + * @returns {Boolean} + */ + intersects(point) { + const stage = this.getStage(); + if (!stage) { + return false; + } + const bufferHitCanvas = stage.bufferHitCanvas; + + bufferHitCanvas.getContext().clear(); + this.drawHit(bufferHitCanvas, undefined, true); + const p = bufferHitCanvas.context.getImageData( + Math.round(point.x), + Math.round(point.y), + 1, + 1 + ).data; + return p[3] > 0; + } + + destroy() { + Node.prototype.destroy.call(this); + delete shapes[this.colorKey]; + delete (this as any).colorKey; + return this; + } + // why do we need buffer canvas? + // it give better result when a shape has + // stroke with fill and with some opacity + _useBufferCanvas(forceFill?: boolean): boolean { + // image and sprite still has "fill" as image + // so they use that method with forced fill + // it probably will be simpler, then copy/paste the code + + // force skip buffer canvas + const perfectDrawEnabled = this.attrs.perfectDrawEnabled ?? true; + if (!perfectDrawEnabled) { + return false; + } + const hasFill = forceFill || this.hasFill(); + const hasStroke = this.hasStroke(); + const isTransparent = this.getAbsoluteOpacity() !== 1; + + if (hasFill && hasStroke && isTransparent) { + return true; + } + + const hasShadow = this.hasShadow(); + const strokeForShadow = this.shadowForStrokeEnabled(); + if (hasFill && hasStroke && hasShadow && strokeForShadow) { + return true; + } + return false; + } + setStrokeHitEnabled(val: number) { + Util.warn( + 'strokeHitEnabled property is deprecated. Please use hitStrokeWidth instead.' + ); + if (val) { + this.hitStrokeWidth('auto'); + } else { + this.hitStrokeWidth(0); + } + } + getStrokeHitEnabled() { + if (this.hitStrokeWidth() === 0) { + return false; + } else { + return true; + } + } + /** + * return self rectangle (x, y, width, height) of shape. + * This method are not taken into account transformation and styles. + * @method + * @name Konva.Shape#getSelfRect + * @returns {Object} rect with {x, y, width, height} properties + * @example + * + * rect.getSelfRect(); // return {x:0, y:0, width:rect.width(), height:rect.height()} + * circle.getSelfRect(); // return {x: - circle.width() / 2, y: - circle.height() / 2, width:circle.width(), height:circle.height()} + * + */ + getSelfRect() { + const size = this.size(); + return { + x: this._centroid ? -size.width / 2 : 0, + y: this._centroid ? -size.height / 2 : 0, + width: size.width, + height: size.height, + }; + } + getClientRect(config: ShapeGetClientRectConfig = {}) { + // if we have a cached parent, it will use cached transform matrix + // but we don't want to that + let hasCachedParent = false; + let parent = this.getParent(); + while (parent) { + if (parent.isCached()) { + hasCachedParent = true; + break; + } + parent = parent.getParent(); + } + const skipTransform = config.skipTransform; + + // force relative to stage if we have a cached parent + const relativeTo = + config.relativeTo || (hasCachedParent && this.getStage()) || undefined; + + const fillRect = this.getSelfRect(); + + const applyStroke = !config.skipStroke && this.hasStroke(); + const strokeWidth: number = (applyStroke && this.strokeWidth()) || 0; + + const fillAndStrokeWidth = fillRect.width + strokeWidth; + const fillAndStrokeHeight = fillRect.height + strokeWidth; + + const applyShadow = !config.skipShadow && this.hasShadow(); + const shadowOffsetX = applyShadow ? this.shadowOffsetX() : 0; + const shadowOffsetY = applyShadow ? this.shadowOffsetY() : 0; + + const preWidth = fillAndStrokeWidth + Math.abs(shadowOffsetX); + const preHeight = fillAndStrokeHeight + Math.abs(shadowOffsetY); + + const blurRadius = (applyShadow && this.shadowBlur()) || 0; + + const width = preWidth + blurRadius * 2; + const height = preHeight + blurRadius * 2; + + const rect = { + width: width, + height: height, + x: + -(strokeWidth / 2 + blurRadius) + + Math.min(shadowOffsetX, 0) + + fillRect.x, + y: + -(strokeWidth / 2 + blurRadius) + + Math.min(shadowOffsetY, 0) + + fillRect.y, + }; + if (!skipTransform) { + return this._transformedRect(rect, relativeTo); + } + return rect; + } + drawScene(can?: SceneCanvas, top?: Node, bufferCanvas?: SceneCanvas) { + // basically there are 3 drawing modes + // 1 - simple drawing when nothing is cached. + // 2 - when we are caching current + // 3 - when node is cached and we need to draw it into layer + + const layer = this.getLayer(); + let canvas = can || layer!.getCanvas(), + context = canvas.getContext() as SceneContext, + cachedCanvas = this._getCanvasCache(), + drawFunc = this.getSceneFunc(), + hasShadow = this.hasShadow(), + stage, + bufferContext; + + const skipBuffer = canvas.isCache; + const cachingSelf = top === this; + + if (!this.isVisible() && !cachingSelf) { + return this; + } + // if node is cached we just need to draw from cache + if (cachedCanvas) { + context.save(); + + const m = this.getAbsoluteTransform(top).getMatrix(); + context.transform(m[0], m[1], m[2], m[3], m[4], m[5]); + this._drawCachedSceneCanvas(context); + context.restore(); + return this; + } + + if (!drawFunc) { + return this; + } + + context.save(); + // if buffer canvas is needed + if (this._useBufferCanvas() && !skipBuffer) { + stage = this.getStage(); + const bc = bufferCanvas || stage.bufferCanvas; + bufferContext = bc.getContext(); + bufferContext.clear(); + bufferContext.save(); + bufferContext._applyLineJoin(this); + // layer might be undefined if we are using cache before adding to layer + var o = this.getAbsoluteTransform(top).getMatrix(); + bufferContext.transform(o[0], o[1], o[2], o[3], o[4], o[5]); + + drawFunc.call(this, bufferContext, this); + bufferContext.restore(); + + const ratio = bc.pixelRatio; + + if (hasShadow) { + context._applyShadow(this); + } + context._applyOpacity(this); + context._applyGlobalCompositeOperation(this); + context.drawImage(bc._canvas, 0, 0, bc.width / ratio, bc.height / ratio); + } else { + context._applyLineJoin(this); + + if (!cachingSelf) { + var o = this.getAbsoluteTransform(top).getMatrix(); + context.transform(o[0], o[1], o[2], o[3], o[4], o[5]); + context._applyOpacity(this); + context._applyGlobalCompositeOperation(this); + } + + if (hasShadow) { + context._applyShadow(this); + } + + drawFunc.call(this, context, this); + } + context.restore(); + return this; + } + drawHit(can?: HitCanvas, top?: Node, skipDragCheck = false) { + if (!this.shouldDrawHit(top, skipDragCheck)) { + return this; + } + + const layer = this.getLayer(), + canvas = can || layer!.hitCanvas, + context = canvas && canvas.getContext(), + drawFunc = this.hitFunc() || this.sceneFunc(), + cachedCanvas = this._getCanvasCache(), + cachedHitCanvas = cachedCanvas && cachedCanvas.hit; + + if (!this.colorKey) { + Util.warn( + 'Looks like your canvas has a destroyed shape in it. Do not reuse shape after you destroyed it. If you want to reuse shape you should call remove() instead of destroy()' + ); + } + + if (cachedHitCanvas) { + context.save(); + + const m = this.getAbsoluteTransform(top).getMatrix(); + context.transform(m[0], m[1], m[2], m[3], m[4], m[5]); + + this._drawCachedHitCanvas(context); + context.restore(); + return this; + } + if (!drawFunc) { + return this; + } + context.save(); + context._applyLineJoin(this); + + const selfCache = this === top; + if (!selfCache) { + const o = this.getAbsoluteTransform(top).getMatrix(); + context.transform(o[0], o[1], o[2], o[3], o[4], o[5]); + } + drawFunc.call(this, context, this); + context.restore(); + return this; + } + /** + * draw hit graph using the cached scene canvas + * @method + * @name Konva.Shape#drawHitFromCache + * @param {Integer} alphaThreshold alpha channel threshold that determines whether or not + * a pixel should be drawn onto the hit graph. Must be a value between 0 and 255. + * The default is 0 + * @returns {Konva.Shape} + * @example + * shape.cache(); + * shape.drawHitFromCache(); + */ + drawHitFromCache(alphaThreshold = 0) { + const cachedCanvas = this._getCanvasCache(), + sceneCanvas = this._getCachedSceneCanvas(), + hitCanvas = cachedCanvas.hit, + hitContext = hitCanvas.getContext(), + hitWidth = hitCanvas.getWidth(), + hitHeight = hitCanvas.getHeight(); + + hitContext.clear(); + hitContext.drawImage(sceneCanvas._canvas, 0, 0, hitWidth, hitHeight); + + try { + const hitImageData = hitContext.getImageData(0, 0, hitWidth, hitHeight); + const hitData = hitImageData.data; + const len = hitData.length; + const rgbColorKey = Util._hexToRgb(this.colorKey); + + // replace non transparent pixels with color key + for (let i = 0; i < len; i += 4) { + const alpha = hitData[i + 3]; + if (alpha > alphaThreshold) { + hitData[i] = rgbColorKey.r; + hitData[i + 1] = rgbColorKey.g; + hitData[i + 2] = rgbColorKey.b; + hitData[i + 3] = 255; + } else { + hitData[i + 3] = 0; + } + } + hitContext.putImageData(hitImageData, 0, 0); + } catch (e: any) { + Util.error( + 'Unable to draw hit graph from cached scene canvas. ' + e.message + ); + } + + return this; + } + + hasPointerCapture(pointerId: number): boolean { + return PointerEvents.hasPointerCapture(pointerId, this); + } + + setPointerCapture(pointerId: number) { + PointerEvents.setPointerCapture(pointerId, this); + } + + releaseCapture(pointerId: number) { + PointerEvents.releaseCapture(pointerId, this); + } + + draggable: GetSet; + embossBlend: GetSet; + + dash: GetSet; + dashEnabled: GetSet; + dashOffset: GetSet; + fill: GetSet; + fillEnabled: GetSet; + fillLinearGradientColorStops: GetSet, this>; + fillLinearGradientStartPoint: GetSet; + fillLinearGradientStartPointX: GetSet; + fillLinearGradientStartPointY: GetSet; + fillLinearGradientEndPoint: GetSet; + fillLinearGradientEndPointX: GetSet; + fillLinearGradientEndPointY: GetSet; + fillLinearRadialStartPoint: GetSet; + fillLinearRadialStartPointX: GetSet; + fillLinearRadialStartPointY: GetSet; + fillLinearRadialEndPoint: GetSet; + fillLinearRadialEndPointX: GetSet; + fillLinearRadialEndPointY: GetSet; + fillPatternImage: GetSet; + fillRadialGradientStartRadius: GetSet; + fillRadialGradientEndRadius: GetSet; + fillRadialGradientColorStops: GetSet, this>; + fillRadialGradientStartPoint: GetSet; + fillRadialGradientStartPointX: GetSet; + fillRadialGradientStartPointY: GetSet; + fillRadialGradientEndPoint: GetSet; + fillRadialGradientEndPointX: GetSet; + fillRadialGradientEndPointY: GetSet; + fillPatternOffset: GetSet; + fillPatternOffsetX: GetSet; + fillPatternOffsetY: GetSet; + fillPatternRepeat: GetSet; + fillPatternRotation: GetSet; + fillPatternScale: GetSet; + fillPatternScaleX: GetSet; + fillPatternScaleY: GetSet; + fillPatternX: GetSet; + fillPatternY: GetSet; + fillPriority: GetSet; + hitFunc: GetSet, this>; + lineCap: GetSet; + lineJoin: GetSet; + perfectDrawEnabled: GetSet; + sceneFunc: GetSet, this>; + shadowColor: GetSet; + shadowEnabled: GetSet; + shadowForStrokeEnabled: GetSet; + shadowOffset: GetSet; + shadowOffsetX: GetSet; + shadowOffsetY: GetSet; + shadowOpacity: GetSet; + shadowBlur: GetSet; + stroke: GetSet; + strokeEnabled: GetSet; + fillAfterStrokeEnabled: GetSet; + strokeScaleEnabled: GetSet; + strokeHitEnabled: GetSet; + strokeWidth: GetSet; + hitStrokeWidth: GetSet; + strokeLinearGradientStartPoint: GetSet; + strokeLinearGradientEndPoint: GetSet; + strokeLinearGradientColorStops: GetSet, this>; + strokeLinearGradientStartPointX: GetSet; + strokeLinearGradientStartPointY: GetSet; + strokeLinearGradientEndPointX: GetSet; + strokeLinearGradientEndPointY: GetSet; + fillRule: GetSet; +} + +Shape.prototype._fillFunc = _fillFunc; +Shape.prototype._strokeFunc = _strokeFunc; +Shape.prototype._fillFuncHit = _fillFuncHit; +Shape.prototype._strokeFuncHit = _strokeFuncHit; + +Shape.prototype._centroid = false; +Shape.prototype.nodeType = 'Shape'; +_registerNode(Shape); + +Shape.prototype.eventListeners = {}; +Shape.prototype.on.call( + Shape.prototype, + 'shadowColorChange.konva shadowBlurChange.konva shadowOffsetChange.konva shadowOpacityChange.konva shadowEnabledChange.konva', + _clearHasShadowCache +); + +Shape.prototype.on.call( + Shape.prototype, + 'shadowColorChange.konva shadowOpacityChange.konva shadowEnabledChange.konva', + _clearGetShadowRGBACache +); + +Shape.prototype.on.call( + Shape.prototype, + 'fillPriorityChange.konva fillPatternImageChange.konva fillPatternRepeatChange.konva fillPatternScaleXChange.konva fillPatternScaleYChange.konva fillPatternOffsetXChange.konva fillPatternOffsetYChange.konva fillPatternXChange.konva fillPatternYChange.konva fillPatternRotationChange.konva', + _clearFillPatternCache +); + +Shape.prototype.on.call( + Shape.prototype, + 'fillPriorityChange.konva fillLinearGradientColorStopsChange.konva fillLinearGradientStartPointXChange.konva fillLinearGradientStartPointYChange.konva fillLinearGradientEndPointXChange.konva fillLinearGradientEndPointYChange.konva', + _clearLinearGradientCache +); + +Shape.prototype.on.call( + Shape.prototype, + 'fillPriorityChange.konva fillRadialGradientColorStopsChange.konva fillRadialGradientStartPointXChange.konva fillRadialGradientStartPointYChange.konva fillRadialGradientEndPointXChange.konva fillRadialGradientEndPointYChange.konva fillRadialGradientStartRadiusChange.konva fillRadialGradientEndRadiusChange.konva', + _clearRadialGradientCache +); + +// add getters and setters +Factory.addGetterSetter( + Shape, + 'stroke', + undefined, + getStringOrGradientValidator() +); + +/** + * get/set stroke color + * @name Konva.Shape#stroke + * @method + * @param {String} color + * @returns {String} + * @example + * // get stroke color + * var stroke = shape.stroke(); + * + * // set stroke color with color string + * shape.stroke('green'); + * + * // set stroke color with hex + * shape.stroke('#00ff00'); + * + * // set stroke color with rgb + * shape.stroke('rgb(0,255,0)'); + * + * // set stroke color with rgba and make it 50% opaque + * shape.stroke('rgba(0,255,0,0.5'); + */ + +Factory.addGetterSetter(Shape, 'strokeWidth', 2, getNumberValidator()); + +/** + * get/set stroke width + * @name Konva.Shape#strokeWidth + * @method + * @param {Number} strokeWidth + * @returns {Number} + * @example + * // get stroke width + * var strokeWidth = shape.strokeWidth(); + * + * // set stroke width + * shape.strokeWidth(10); + */ + +Factory.addGetterSetter(Shape, 'fillAfterStrokeEnabled', false); + +/** + * get/set fillAfterStrokeEnabled property. By default Konva is drawing filling first, then stroke on top of the fill. + * In rare situations you may want a different behavior. When you have a stroke first then fill on top of it. + * Especially useful for Text objects. + * Default is false. + * @name Konva.Shape#fillAfterStrokeEnabled + * @method + * @param {Boolean} fillAfterStrokeEnabled + * @returns {Boolean} + * @example + * // get stroke width + * var fillAfterStrokeEnabled = shape.fillAfterStrokeEnabled(); + * + * // set stroke width + * shape.fillAfterStrokeEnabled(true); + */ + +Factory.addGetterSetter( + Shape, + 'hitStrokeWidth', + 'auto', + getNumberOrAutoValidator() +); + +/** + * get/set stroke width for hit detection. Default value is "auto", it means it will be equals to strokeWidth + * @name Konva.Shape#hitStrokeWidth + * @method + * @param {Number} hitStrokeWidth + * @returns {Number} + * @example + * // get stroke width + * var hitStrokeWidth = shape.hitStrokeWidth(); + * + * // set hit stroke width + * shape.hitStrokeWidth(20); + * // set hit stroke width always equals to scene stroke width + * shape.hitStrokeWidth('auto'); + */ + +Factory.addGetterSetter(Shape, 'strokeHitEnabled', true, getBooleanValidator()); + +/** + * **deprecated, use hitStrokeWidth instead!** get/set strokeHitEnabled property. Useful for performance optimization. + * You may set `shape.strokeHitEnabled(false)`. In this case stroke will be no draw on hit canvas, so hit area + * of shape will be decreased (by lineWidth / 2). Remember that non closed line with `strokeHitEnabled = false` + * will be not drawn on hit canvas, that is mean line will no trigger pointer events (like mouseover) + * Default value is true. + * @name Konva.Shape#strokeHitEnabled + * @method + * @param {Boolean} strokeHitEnabled + * @returns {Boolean} + * @example + * // get strokeHitEnabled + * var strokeHitEnabled = shape.strokeHitEnabled(); + * + * // set strokeHitEnabled + * shape.strokeHitEnabled(); + */ + +Factory.addGetterSetter( + Shape, + 'perfectDrawEnabled', + true, + getBooleanValidator() +); + +/** + * get/set perfectDrawEnabled. If a shape has fill, stroke and opacity you may set `perfectDrawEnabled` to false to improve performance. + * See http://konvajs.org/docs/performance/Disable_Perfect_Draw.html for more information. + * Default value is true + * @name Konva.Shape#perfectDrawEnabled + * @method + * @param {Boolean} perfectDrawEnabled + * @returns {Boolean} + * @example + * // get perfectDrawEnabled + * var perfectDrawEnabled = shape.perfectDrawEnabled(); + * + * // set perfectDrawEnabled + * shape.perfectDrawEnabled(); + */ + +Factory.addGetterSetter( + Shape, + 'shadowForStrokeEnabled', + true, + getBooleanValidator() +); + +/** + * get/set shadowForStrokeEnabled. Useful for performance optimization. + * You may set `shape.shadowForStrokeEnabled(false)`. In this case stroke will no effect shadow. + * Remember if you set `shadowForStrokeEnabled = false` for non closed line - that line will have no shadow!. + * Default value is true + * @name Konva.Shape#shadowForStrokeEnabled + * @method + * @param {Boolean} shadowForStrokeEnabled + * @returns {Boolean} + * @example + * // get shadowForStrokeEnabled + * var shadowForStrokeEnabled = shape.shadowForStrokeEnabled(); + * + * // set shadowForStrokeEnabled + * shape.shadowForStrokeEnabled(); + */ + +Factory.addGetterSetter(Shape, 'lineJoin'); + +/** + * get/set line join. Can be miter, round, or bevel. The + * default is miter + * @name Konva.Shape#lineJoin + * @method + * @param {String} lineJoin + * @returns {String} + * @example + * // get line join + * var lineJoin = shape.lineJoin(); + * + * // set line join + * shape.lineJoin('round'); + */ + +Factory.addGetterSetter(Shape, 'lineCap'); + +/** + * get/set line cap. Can be butt, round, or square + * @name Konva.Shape#lineCap + * @method + * @param {String} lineCap + * @returns {String} + * @example + * // get line cap + * var lineCap = shape.lineCap(); + * + * // set line cap + * shape.lineCap('round'); + */ + +Factory.addGetterSetter(Shape, 'sceneFunc'); + +/** + * get/set scene draw function. That function is used to draw the shape on a canvas. + * Also that function will be used to draw hit area of the shape, in case if hitFunc is not defined. + * @name Konva.Shape#sceneFunc + * @method + * @param {Function} drawFunc drawing function + * @returns {Function} + * @example + * // get scene draw function + * var sceneFunc = shape.sceneFunc(); + * + * // set scene draw function + * shape.sceneFunc(function(context, shape) { + * context.beginPath(); + * context.rect(0, 0, shape.width(), shape.height()); + * context.closePath(); + * // important Konva method that fill and stroke shape from its properties + * // like stroke and fill + * context.fillStrokeShape(shape); + * }); + */ + +Factory.addGetterSetter(Shape, 'hitFunc'); + +/** + * get/set hit draw function. That function is used to draw custom hit area of a shape. + * @name Konva.Shape#hitFunc + * @method + * @param {Function} drawFunc drawing function + * @returns {Function} + * @example + * // get hit draw function + * var hitFunc = shape.hitFunc(); + * + * // set hit draw function + * shape.hitFunc(function(context) { + * context.beginPath(); + * context.rect(0, 0, shape.width(), shape.height()); + * context.closePath(); + * // important Konva method that fill and stroke shape from its properties + * context.fillStrokeShape(shape); + * }); + */ + +Factory.addGetterSetter(Shape, 'dash'); + +/** + * get/set dash array for stroke. + * @name Konva.Shape#dash + * @method + * @param {Array} dash + * @returns {Array} + * @example + * // apply dashed stroke that is 10px long and 5 pixels apart + * line.dash([10, 5]); + * // apply dashed stroke that is made up of alternating dashed + * // lines that are 10px long and 20px apart, and dots that have + * // a radius of 5px and are 20px apart + * line.dash([10, 20, 0.001, 20]); + */ + +Factory.addGetterSetter(Shape, 'dashOffset', 0, getNumberValidator()); + +/** + * get/set dash offset for stroke. + * @name Konva.Shape#dash + * @method + * @param {Number} dash offset + * @returns {Number} + * @example + * // apply dashed stroke that is 10px long and 5 pixels apart with an offset of 5px + * line.dash([10, 5]); + * line.dashOffset(5); + */ + +Factory.addGetterSetter(Shape, 'shadowColor', undefined, getStringValidator()); + +/** + * get/set shadow color + * @name Konva.Shape#shadowColor + * @method + * @param {String} color + * @returns {String} + * @example + * // get shadow color + * var shadow = shape.shadowColor(); + * + * // set shadow color with color string + * shape.shadowColor('green'); + * + * // set shadow color with hex + * shape.shadowColor('#00ff00'); + * + * // set shadow color with rgb + * shape.shadowColor('rgb(0,255,0)'); + * + * // set shadow color with rgba and make it 50% opaque + * shape.shadowColor('rgba(0,255,0,0.5'); + */ + +Factory.addGetterSetter(Shape, 'shadowBlur', 0, getNumberValidator()); + +/** + * get/set shadow blur + * @name Konva.Shape#shadowBlur + * @method + * @param {Number} blur + * @returns {Number} + * @example + * // get shadow blur + * var shadowBlur = shape.shadowBlur(); + * + * // set shadow blur + * shape.shadowBlur(10); + */ + +Factory.addGetterSetter(Shape, 'shadowOpacity', 1, getNumberValidator()); + +/** + * get/set shadow opacity. must be a value between 0 and 1 + * @name Konva.Shape#shadowOpacity + * @method + * @param {Number} opacity + * @returns {Number} + * @example + * // get shadow opacity + * var shadowOpacity = shape.shadowOpacity(); + * + * // set shadow opacity + * shape.shadowOpacity(0.5); + */ + +Factory.addComponentsGetterSetter(Shape, 'shadowOffset', ['x', 'y']); + +/** + * get/set shadow offset + * @name Konva.Shape#shadowOffset + * @method + * @param {Object} offset + * @param {Number} offset.x + * @param {Number} offset.y + * @returns {Object} + * @example + * // get shadow offset + * var shadowOffset = shape.shadowOffset(); + * + * // set shadow offset + * shape.shadowOffset({ + * x: 20, + * y: 10 + * }); + */ + +Factory.addGetterSetter(Shape, 'shadowOffsetX', 0, getNumberValidator()); + +/** + * get/set shadow offset x + * @name Konva.Shape#shadowOffsetX + * @method + * @param {Number} x + * @returns {Number} + * @example + * // get shadow offset x + * var shadowOffsetX = shape.shadowOffsetX(); + * + * // set shadow offset x + * shape.shadowOffsetX(5); + */ + +Factory.addGetterSetter(Shape, 'shadowOffsetY', 0, getNumberValidator()); + +/** + * get/set shadow offset y + * @name Konva.Shape#shadowOffsetY + * @method + * @param {Number} y + * @returns {Number} + * @example + * // get shadow offset y + * var shadowOffsetY = shape.shadowOffsetY(); + * + * // set shadow offset y + * shape.shadowOffsetY(5); + */ + +Factory.addGetterSetter(Shape, 'fillPatternImage'); + +/** + * get/set fill pattern image + * @name Konva.Shape#fillPatternImage + * @method + * @param {Image} image object + * @returns {Image} + * @example + * // get fill pattern image + * var fillPatternImage = shape.fillPatternImage(); + * + * // set fill pattern image + * var imageObj = new Image(); + * imageObj.onload = function() { + * shape.fillPatternImage(imageObj); + * }; + * imageObj.src = 'path/to/image/jpg'; + */ + +Factory.addGetterSetter( + Shape, + 'fill', + undefined, + getStringOrGradientValidator() +); + +/** + * get/set fill color + * @name Konva.Shape#fill + * @method + * @param {String} color + * @returns {String} + * @example + * // get fill color + * var fill = shape.fill(); + * + * // set fill color with color string + * shape.fill('green'); + * + * // set fill color with hex + * shape.fill('#00ff00'); + * + * // set fill color with rgb + * shape.fill('rgb(0,255,0)'); + * + * // set fill color with rgba and make it 50% opaque + * shape.fill('rgba(0,255,0,0.5'); + * + * // shape without fill + * shape.fill(null); + */ + +Factory.addGetterSetter(Shape, 'fillPatternX', 0, getNumberValidator()); + +/** + * get/set fill pattern x + * @name Konva.Shape#fillPatternX + * @method + * @param {Number} x + * @returns {Number} + * @example + * // get fill pattern x + * var fillPatternX = shape.fillPatternX(); + * // set fill pattern x + * shape.fillPatternX(20); + */ + +Factory.addGetterSetter(Shape, 'fillPatternY', 0, getNumberValidator()); + +/** + * get/set fill pattern y + * @name Konva.Shape#fillPatternY + * @method + * @param {Number} y + * @returns {Number} + * @example + * // get fill pattern y + * var fillPatternY = shape.fillPatternY(); + * // set fill pattern y + * shape.fillPatternY(20); + */ + +Factory.addGetterSetter(Shape, 'fillLinearGradientColorStops'); + +/** + * get/set fill linear gradient color stops + * @name Konva.Shape#fillLinearGradientColorStops + * @method + * @param {Array} colorStops + * @returns {Array} colorStops + * @example + * // get fill linear gradient color stops + * var colorStops = shape.fillLinearGradientColorStops(); + * + * // create a linear gradient that starts with red, changes to blue + * // halfway through, and then changes to green + * shape.fillLinearGradientColorStops(0, 'red', 0.5, 'blue', 1, 'green'); + */ + +Factory.addGetterSetter(Shape, 'strokeLinearGradientColorStops'); + +/** + * get/set stroke linear gradient color stops + * @name Konva.Shape#strokeLinearGradientColorStops + * @method + * @param {Array} colorStops + * @returns {Array} colorStops + * @example + * // get stroke linear gradient color stops + * var colorStops = shape.strokeLinearGradientColorStops(); + * + * // create a linear gradient that starts with red, changes to blue + * // halfway through, and then changes to green + * shape.strokeLinearGradientColorStops([0, 'red', 0.5, 'blue', 1, 'green']); + */ + +Factory.addGetterSetter(Shape, 'fillRadialGradientStartRadius', 0); + +/** + * get/set fill radial gradient start radius + * @name Konva.Shape#fillRadialGradientStartRadius + * @method + * @param {Number} radius + * @returns {Number} + * @example + * // get radial gradient start radius + * var startRadius = shape.fillRadialGradientStartRadius(); + * + * // set radial gradient start radius + * shape.fillRadialGradientStartRadius(0); + */ + +Factory.addGetterSetter(Shape, 'fillRadialGradientEndRadius', 0); + +/** + * get/set fill radial gradient end radius + * @name Konva.Shape#fillRadialGradientEndRadius + * @method + * @param {Number} radius + * @returns {Number} + * @example + * // get radial gradient end radius + * var endRadius = shape.fillRadialGradientEndRadius(); + * + * // set radial gradient end radius + * shape.fillRadialGradientEndRadius(100); + */ + +Factory.addGetterSetter(Shape, 'fillRadialGradientColorStops'); + +/** + * get/set fill radial gradient color stops + * @name Konva.Shape#fillRadialGradientColorStops + * @method + * @param {Number} colorStops + * @returns {Array} + * @example + * // get fill radial gradient color stops + * var colorStops = shape.fillRadialGradientColorStops(); + * + * // create a radial gradient that starts with red, changes to blue + * // halfway through, and then changes to green + * shape.fillRadialGradientColorStops(0, 'red', 0.5, 'blue', 1, 'green'); + */ + +Factory.addGetterSetter(Shape, 'fillPatternRepeat', 'repeat'); + +/** + * get/set fill pattern repeat. Can be 'repeat', 'repeat-x', 'repeat-y', or 'no-repeat'. The default is 'repeat' + * @name Konva.Shape#fillPatternRepeat + * @method + * @param {String} repeat + * @returns {String} + * @example + * // get fill pattern repeat + * var repeat = shape.fillPatternRepeat(); + * + * // repeat pattern in x direction only + * shape.fillPatternRepeat('repeat-x'); + * + * // do not repeat the pattern + * shape.fillPatternRepeat('no-repeat'); + */ + +Factory.addGetterSetter(Shape, 'fillEnabled', true); + +/** + * get/set fill enabled flag + * @name Konva.Shape#fillEnabled + * @method + * @param {Boolean} enabled + * @returns {Boolean} + * @example + * // get fill enabled flag + * var fillEnabled = shape.fillEnabled(); + * + * // disable fill + * shape.fillEnabled(false); + * + * // enable fill + * shape.fillEnabled(true); + */ + +Factory.addGetterSetter(Shape, 'strokeEnabled', true); + +/** + * get/set stroke enabled flag + * @name Konva.Shape#strokeEnabled + * @method + * @param {Boolean} enabled + * @returns {Boolean} + * @example + * // get stroke enabled flag + * var strokeEnabled = shape.strokeEnabled(); + * + * // disable stroke + * shape.strokeEnabled(false); + * + * // enable stroke + * shape.strokeEnabled(true); + */ + +Factory.addGetterSetter(Shape, 'shadowEnabled', true); + +/** + * get/set shadow enabled flag + * @name Konva.Shape#shadowEnabled + * @method + * @param {Boolean} enabled + * @returns {Boolean} + * @example + * // get shadow enabled flag + * var shadowEnabled = shape.shadowEnabled(); + * + * // disable shadow + * shape.shadowEnabled(false); + * + * // enable shadow + * shape.shadowEnabled(true); + */ + +Factory.addGetterSetter(Shape, 'dashEnabled', true); + +/** + * get/set dash enabled flag + * @name Konva.Shape#dashEnabled + * @method + * @param {Boolean} enabled + * @returns {Boolean} + * @example + * // get dash enabled flag + * var dashEnabled = shape.dashEnabled(); + * + * // disable dash + * shape.dashEnabled(false); + * + * // enable dash + * shape.dashEnabled(true); + */ + +Factory.addGetterSetter(Shape, 'strokeScaleEnabled', true); + +/** + * get/set strokeScale enabled flag + * @name Konva.Shape#strokeScaleEnabled + * @method + * @param {Boolean} enabled + * @returns {Boolean} + * @example + * // get stroke scale enabled flag + * var strokeScaleEnabled = shape.strokeScaleEnabled(); + * + * // disable stroke scale + * shape.strokeScaleEnabled(false); + * + * // enable stroke scale + * shape.strokeScaleEnabled(true); + */ + +Factory.addGetterSetter(Shape, 'fillPriority', 'color'); + +/** + * get/set fill priority. can be color, pattern, linear-gradient, or radial-gradient. The default is color. + * This is handy if you want to toggle between different fill types. + * @name Konva.Shape#fillPriority + * @method + * @param {String} priority + * @returns {String} + * @example + * // get fill priority + * var fillPriority = shape.fillPriority(); + * + * // set fill priority + * shape.fillPriority('linear-gradient'); + */ + +Factory.addComponentsGetterSetter(Shape, 'fillPatternOffset', ['x', 'y']); + +/** + * get/set fill pattern offset + * @name Konva.Shape#fillPatternOffset + * @method + * @param {Object} offset + * @param {Number} offset.x + * @param {Number} offset.y + * @returns {Object} + * @example + * // get fill pattern offset + * var patternOffset = shape.fillPatternOffset(); + * + * // set fill pattern offset + * shape.fillPatternOffset({ + * x: 20, + * y: 10 + * }); + */ + +Factory.addGetterSetter(Shape, 'fillPatternOffsetX', 0, getNumberValidator()); + +/** + * get/set fill pattern offset x + * @name Konva.Shape#fillPatternOffsetX + * @method + * @param {Number} x + * @returns {Number} + * @example + * // get fill pattern offset x + * var patternOffsetX = shape.fillPatternOffsetX(); + * + * // set fill pattern offset x + * shape.fillPatternOffsetX(20); + */ + +Factory.addGetterSetter(Shape, 'fillPatternOffsetY', 0, getNumberValidator()); + +/** + * get/set fill pattern offset y + * @name Konva.Shape#fillPatternOffsetY + * @method + * @param {Number} y + * @returns {Number} + * @example + * // get fill pattern offset y + * var patternOffsetY = shape.fillPatternOffsetY(); + * + * // set fill pattern offset y + * shape.fillPatternOffsetY(10); + */ + +Factory.addComponentsGetterSetter(Shape, 'fillPatternScale', ['x', 'y']); + +/** + * get/set fill pattern scale + * @name Konva.Shape#fillPatternScale + * @method + * @param {Object} scale + * @param {Number} scale.x + * @param {Number} scale.y + * @returns {Object} + * @example + * // get fill pattern scale + * var patternScale = shape.fillPatternScale(); + * + * // set fill pattern scale + * shape.fillPatternScale({ + * x: 2, + * y: 2 + * }); + */ + +Factory.addGetterSetter(Shape, 'fillPatternScaleX', 1, getNumberValidator()); + +/** + * get/set fill pattern scale x + * @name Konva.Shape#fillPatternScaleX + * @method + * @param {Number} x + * @returns {Number} + * @example + * // get fill pattern scale x + * var patternScaleX = shape.fillPatternScaleX(); + * + * // set fill pattern scale x + * shape.fillPatternScaleX(2); + */ + +Factory.addGetterSetter(Shape, 'fillPatternScaleY', 1, getNumberValidator()); + +/** + * get/set fill pattern scale y + * @name Konva.Shape#fillPatternScaleY + * @method + * @param {Number} y + * @returns {Number} + * @example + * // get fill pattern scale y + * var patternScaleY = shape.fillPatternScaleY(); + * + * // set fill pattern scale y + * shape.fillPatternScaleY(2); + */ + +Factory.addComponentsGetterSetter(Shape, 'fillLinearGradientStartPoint', [ + 'x', + 'y', +]); + +/** + * get/set fill linear gradient start point + * @name Konva.Shape#fillLinearGradientStartPoint + * @method + * @param {Object} startPoint + * @param {Number} startPoint.x + * @param {Number} startPoint.y + * @returns {Object} + * @example + * // get fill linear gradient start point + * var startPoint = shape.fillLinearGradientStartPoint(); + * + * // set fill linear gradient start point + * shape.fillLinearGradientStartPoint({ + * x: 20, + * y: 10 + * }); + */ + +Factory.addComponentsGetterSetter(Shape, 'strokeLinearGradientStartPoint', [ + 'x', + 'y', +]); + +/** + * get/set stroke linear gradient start point + * @name Konva.Shape#strokeLinearGradientStartPoint + * @method + * @param {Object} startPoint + * @param {Number} startPoint.x + * @param {Number} startPoint.y + * @returns {Object} + * @example + * // get stroke linear gradient start point + * var startPoint = shape.strokeLinearGradientStartPoint(); + * + * // set stroke linear gradient start point + * shape.strokeLinearGradientStartPoint({ + * x: 20, + * y: 10 + * }); + */ + +Factory.addGetterSetter(Shape, 'fillLinearGradientStartPointX', 0); + +/** + * get/set fill linear gradient start point x + * @name Konva.Shape#fillLinearGradientStartPointX + * @method + * @param {Number} x + * @returns {Number} + * @example + * // get fill linear gradient start point x + * var startPointX = shape.fillLinearGradientStartPointX(); + * + * // set fill linear gradient start point x + * shape.fillLinearGradientStartPointX(20); + */ + +Factory.addGetterSetter(Shape, 'strokeLinearGradientStartPointX', 0); + +/** + * get/set stroke linear gradient start point x + * @name Konva.Shape#linearLinearGradientStartPointX + * @method + * @param {Number} x + * @returns {Number} + * @example + * // get stroke linear gradient start point x + * var startPointX = shape.strokeLinearGradientStartPointX(); + * + * // set stroke linear gradient start point x + * shape.strokeLinearGradientStartPointX(20); + */ + +Factory.addGetterSetter(Shape, 'fillLinearGradientStartPointY', 0); + +/** + * get/set fill linear gradient start point y + * @name Konva.Shape#fillLinearGradientStartPointY + * @method + * @param {Number} y + * @returns {Number} + * @example + * // get fill linear gradient start point y + * var startPointY = shape.fillLinearGradientStartPointY(); + * + * // set fill linear gradient start point y + * shape.fillLinearGradientStartPointY(20); + */ + +Factory.addGetterSetter(Shape, 'strokeLinearGradientStartPointY', 0); +/** + * get/set stroke linear gradient start point y + * @name Konva.Shape#strokeLinearGradientStartPointY + * @method + * @param {Number} y + * @returns {Number} + * @example + * // get stroke linear gradient start point y + * var startPointY = shape.strokeLinearGradientStartPointY(); + * + * // set stroke linear gradient start point y + * shape.strokeLinearGradientStartPointY(20); + */ + +Factory.addComponentsGetterSetter(Shape, 'fillLinearGradientEndPoint', [ + 'x', + 'y', +]); + +/** + * get/set fill linear gradient end point + * @name Konva.Shape#fillLinearGradientEndPoint + * @method + * @param {Object} endPoint + * @param {Number} endPoint.x + * @param {Number} endPoint.y + * @returns {Object} + * @example + * // get fill linear gradient end point + * var endPoint = shape.fillLinearGradientEndPoint(); + * + * // set fill linear gradient end point + * shape.fillLinearGradientEndPoint({ + * x: 20, + * y: 10 + * }); + */ + +Factory.addComponentsGetterSetter(Shape, 'strokeLinearGradientEndPoint', [ + 'x', + 'y', +]); + +/** + * get/set stroke linear gradient end point + * @name Konva.Shape#strokeLinearGradientEndPoint + * @method + * @param {Object} endPoint + * @param {Number} endPoint.x + * @param {Number} endPoint.y + * @returns {Object} + * @example + * // get stroke linear gradient end point + * var endPoint = shape.strokeLinearGradientEndPoint(); + * + * // set stroke linear gradient end point + * shape.strokeLinearGradientEndPoint({ + * x: 20, + * y: 10 + * }); + */ + +Factory.addGetterSetter(Shape, 'fillLinearGradientEndPointX', 0); +/** + * get/set fill linear gradient end point x + * @name Konva.Shape#fillLinearGradientEndPointX + * @method + * @param {Number} x + * @returns {Number} + * @example + * // get fill linear gradient end point x + * var endPointX = shape.fillLinearGradientEndPointX(); + * + * // set fill linear gradient end point x + * shape.fillLinearGradientEndPointX(20); + */ + +Factory.addGetterSetter(Shape, 'strokeLinearGradientEndPointX', 0); +/** + * get/set fill linear gradient end point x + * @name Konva.Shape#strokeLinearGradientEndPointX + * @method + * @param {Number} x + * @returns {Number} + * @example + * // get stroke linear gradient end point x + * var endPointX = shape.strokeLinearGradientEndPointX(); + * + * // set stroke linear gradient end point x + * shape.strokeLinearGradientEndPointX(20); + */ + +Factory.addGetterSetter(Shape, 'fillLinearGradientEndPointY', 0); +/** + * get/set fill linear gradient end point y + * @name Konva.Shape#fillLinearGradientEndPointY + * @method + * @param {Number} y + * @returns {Number} + * @example + * // get fill linear gradient end point y + * var endPointY = shape.fillLinearGradientEndPointY(); + * + * // set fill linear gradient end point y + * shape.fillLinearGradientEndPointY(20); + */ + +Factory.addGetterSetter(Shape, 'strokeLinearGradientEndPointY', 0); +/** + * get/set stroke linear gradient end point y + * @name Konva.Shape#strokeLinearGradientEndPointY + * @method + * @param {Number} y + * @returns {Number} + * @example + * // get stroke linear gradient end point y + * var endPointY = shape.strokeLinearGradientEndPointY(); + * + * // set stroke linear gradient end point y + * shape.strokeLinearGradientEndPointY(20); + */ + +Factory.addComponentsGetterSetter(Shape, 'fillRadialGradientStartPoint', [ + 'x', + 'y', +]); + +/** + * get/set fill radial gradient start point + * @name Konva.Shape#fillRadialGradientStartPoint + * @method + * @param {Object} startPoint + * @param {Number} startPoint.x + * @param {Number} startPoint.y + * @returns {Object} + * @example + * // get fill radial gradient start point + * var startPoint = shape.fillRadialGradientStartPoint(); + * + * // set fill radial gradient start point + * shape.fillRadialGradientStartPoint({ + * x: 20, + * y: 10 + * }); + */ + +Factory.addGetterSetter(Shape, 'fillRadialGradientStartPointX', 0); +/** + * get/set fill radial gradient start point x + * @name Konva.Shape#fillRadialGradientStartPointX + * @method + * @param {Number} x + * @returns {Number} + * @example + * // get fill radial gradient start point x + * var startPointX = shape.fillRadialGradientStartPointX(); + * + * // set fill radial gradient start point x + * shape.fillRadialGradientStartPointX(20); + */ + +Factory.addGetterSetter(Shape, 'fillRadialGradientStartPointY', 0); +/** + * get/set fill radial gradient start point y + * @name Konva.Shape#fillRadialGradientStartPointY + * @method + * @param {Number} y + * @returns {Number} + * @example + * // get fill radial gradient start point y + * var startPointY = shape.fillRadialGradientStartPointY(); + * + * // set fill radial gradient start point y + * shape.fillRadialGradientStartPointY(20); + */ + +Factory.addComponentsGetterSetter(Shape, 'fillRadialGradientEndPoint', [ + 'x', + 'y', +]); + +/** + * get/set fill radial gradient end point + * @name Konva.Shape#fillRadialGradientEndPoint + * @method + * @param {Object} endPoint + * @param {Number} endPoint.x + * @param {Number} endPoint.y + * @returns {Object} + * @example + * // get fill radial gradient end point + * var endPoint = shape.fillRadialGradientEndPoint(); + * + * // set fill radial gradient end point + * shape.fillRadialGradientEndPoint({ + * x: 20, + * y: 10 + * }); + */ + +Factory.addGetterSetter(Shape, 'fillRadialGradientEndPointX', 0); +/** + * get/set fill radial gradient end point x + * @name Konva.Shape#fillRadialGradientEndPointX + * @method + * @param {Number} x + * @returns {Number} + * @example + * // get fill radial gradient end point x + * var endPointX = shape.fillRadialGradientEndPointX(); + * + * // set fill radial gradient end point x + * shape.fillRadialGradientEndPointX(20); + */ + +Factory.addGetterSetter(Shape, 'fillRadialGradientEndPointY', 0); +/** + * get/set fill radial gradient end point y + * @name Konva.Shape#fillRadialGradientEndPointY + * @method + * @param {Number} y + * @returns {Number} + * @example + * // get fill radial gradient end point y + * var endPointY = shape.fillRadialGradientEndPointY(); + * + * // set fill radial gradient end point y + * shape.fillRadialGradientEndPointY(20); + */ + +Factory.addGetterSetter(Shape, 'fillPatternRotation', 0); + +/** + * get/set fill pattern rotation in degrees + * @name Konva.Shape#fillPatternRotation + * @method + * @param {Number} rotation + * @returns {Konva.Shape} + * @example + * // get fill pattern rotation + * var patternRotation = shape.fillPatternRotation(); + * + * // set fill pattern rotation + * shape.fillPatternRotation(20); + */ + +Factory.addGetterSetter(Shape, 'fillRule', undefined, getStringValidator()); + +/** + * get/set fill rule + * @name Konva.Shape#fillRule + * @method + * @param {CanvasFillRule} rotation + * @returns {Konva.Shape} + * @example + * // get fill rule + * var fillRule = shape.fillRule(); + * + * // set fill rule + * shape.fillRule('evenodd'); + */ + +Factory.backCompat(Shape, { + dashArray: 'dash', + getDashArray: 'getDash', + setDashArray: 'getDash', + + drawFunc: 'sceneFunc', + getDrawFunc: 'getSceneFunc', + setDrawFunc: 'setSceneFunc', + + drawHitFunc: 'hitFunc', + getDrawHitFunc: 'getHitFunc', + setDrawHitFunc: 'setHitFunc', +}); diff --git a/src/Stage.ts b/src/Stage.ts new file mode 100644 index 000000000..495f2b682 --- /dev/null +++ b/src/Stage.ts @@ -0,0 +1,978 @@ +import { Util } from './Util'; +import { Factory } from './Factory'; +import { Container, ContainerConfig } from './Container'; +import { Konva } from './Global'; +import { SceneCanvas, HitCanvas } from './Canvas'; +import { GetSet, Vector2d } from './types'; +import { Shape } from './Shape'; +import { Layer } from './Layer'; +import { DD } from './DragAndDrop'; +import { _registerNode } from './Global'; +import * as PointerEvents from './PointerEvents'; + +export interface StageConfig extends ContainerConfig { + container?: HTMLDivElement | string; +} + +// CONSTANTS +const STAGE = 'Stage', + STRING = 'string', + PX = 'px', + MOUSEOUT = 'mouseout', + MOUSELEAVE = 'mouseleave', + MOUSEOVER = 'mouseover', + MOUSEENTER = 'mouseenter', + MOUSEMOVE = 'mousemove', + MOUSEDOWN = 'mousedown', + MOUSEUP = 'mouseup', + POINTERMOVE = 'pointermove', + POINTERDOWN = 'pointerdown', + POINTERUP = 'pointerup', + POINTERCANCEL = 'pointercancel', + LOSTPOINTERCAPTURE = 'lostpointercapture', + POINTEROUT = 'pointerout', + POINTERLEAVE = 'pointerleave', + POINTEROVER = 'pointerover', + POINTERENTER = 'pointerenter', + CONTEXTMENU = 'contextmenu', + TOUCHSTART = 'touchstart', + TOUCHEND = 'touchend', + TOUCHMOVE = 'touchmove', + TOUCHCANCEL = 'touchcancel', + WHEEL = 'wheel', + MAX_LAYERS_NUMBER = 5, + EVENTS = [ + [MOUSEENTER, '_pointerenter'], + [MOUSEDOWN, '_pointerdown'], + [MOUSEMOVE, '_pointermove'], + [MOUSEUP, '_pointerup'], + [MOUSELEAVE, '_pointerleave'], + [TOUCHSTART, '_pointerdown'], + [TOUCHMOVE, '_pointermove'], + [TOUCHEND, '_pointerup'], + [TOUCHCANCEL, '_pointercancel'], + [MOUSEOVER, '_pointerover'], + [WHEEL, '_wheel'], + [CONTEXTMENU, '_contextmenu'], + [POINTERDOWN, '_pointerdown'], + [POINTERMOVE, '_pointermove'], + [POINTERUP, '_pointerup'], + [POINTERCANCEL, '_pointercancel'], + [LOSTPOINTERCAPTURE, '_lostpointercapture'], + ]; + +const EVENTS_MAP = { + mouse: { + [POINTEROUT]: MOUSEOUT, + [POINTERLEAVE]: MOUSELEAVE, + [POINTEROVER]: MOUSEOVER, + [POINTERENTER]: MOUSEENTER, + [POINTERMOVE]: MOUSEMOVE, + [POINTERDOWN]: MOUSEDOWN, + [POINTERUP]: MOUSEUP, + [POINTERCANCEL]: 'mousecancel', + pointerclick: 'click', + pointerdblclick: 'dblclick', + }, + touch: { + [POINTEROUT]: 'touchout', + [POINTERLEAVE]: 'touchleave', + [POINTEROVER]: 'touchover', + [POINTERENTER]: 'touchenter', + [POINTERMOVE]: TOUCHMOVE, + [POINTERDOWN]: TOUCHSTART, + [POINTERUP]: TOUCHEND, + [POINTERCANCEL]: TOUCHCANCEL, + pointerclick: 'tap', + pointerdblclick: 'dbltap', + }, + pointer: { + [POINTEROUT]: POINTEROUT, + [POINTERLEAVE]: POINTERLEAVE, + [POINTEROVER]: POINTEROVER, + [POINTERENTER]: POINTERENTER, + [POINTERMOVE]: POINTERMOVE, + [POINTERDOWN]: POINTERDOWN, + [POINTERUP]: POINTERUP, + [POINTERCANCEL]: POINTERCANCEL, + pointerclick: 'pointerclick', + pointerdblclick: 'pointerdblclick', + }, +}; + +const getEventType = (type) => { + if (type.indexOf('pointer') >= 0) { + return 'pointer'; + } + if (type.indexOf('touch') >= 0) { + return 'touch'; + } + return 'mouse'; +}; + +const getEventsMap = (eventType: string) => { + const type = getEventType(eventType); + if (type === 'pointer') { + return Konva.pointerEventsEnabled && EVENTS_MAP.pointer; + } + if (type === 'touch') { + return EVENTS_MAP.touch; + } + if (type === 'mouse') { + return EVENTS_MAP.mouse; + } +}; + +function checkNoClip(attrs: any = {}) { + if (attrs.clipFunc || attrs.clipWidth || attrs.clipHeight) { + Util.warn( + 'Stage does not support clipping. Please use clip for Layers or Groups.' + ); + } + return attrs; +} + +const NO_POINTERS_MESSAGE = `Pointer position is missing and not registered by the stage. Looks like it is outside of the stage container. You can set it manually from event: stage.setPointersPositions(event);`; + +export const stages: Stage[] = []; + +/** + * Stage constructor. A stage is used to contain multiple layers + * @constructor + * @memberof Konva + * @augments Konva.Container + * @param {Object} config + * @param {String|Element} config.container Container selector or DOM element + * @@nodeParams + * @example + * var stage = new Konva.Stage({ + * width: 500, + * height: 800, + * container: 'containerId' // or "#containerId" or ".containerClass" + * }); + */ + +export class Stage extends Container { + content: HTMLDivElement; + pointerPos: Vector2d | null; + _pointerPositions: (Vector2d & { id?: number })[] = []; + _changedPointerPositions: (Vector2d & { id: number })[] = []; + + bufferCanvas: SceneCanvas; + bufferHitCanvas: HitCanvas; + _mouseTargetShape: Shape; + _touchTargetShape: Shape; + _pointerTargetShape: Shape; + _mouseClickStartShape: Shape; + _touchClickStartShape: Shape; + _pointerClickStartShape: Shape; + _mouseClickEndShape: Shape; + _touchClickEndShape: Shape; + _pointerClickEndShape: Shape; + + _mouseDblTimeout: any; + _touchDblTimeout: any; + _pointerDblTimeout: any; + + constructor(config: StageConfig) { + super(checkNoClip(config)); + this._buildDOM(); + this._bindContentEvents(); + stages.push(this); + this.on('widthChange.konva heightChange.konva', this._resizeDOM); + this.on('visibleChange.konva', this._checkVisibility); + this.on( + 'clipWidthChange.konva clipHeightChange.konva clipFuncChange.konva', + () => { + checkNoClip(this.attrs); + } + ); + this._checkVisibility(); + } + + _validateAdd(child) { + const isLayer = child.getType() === 'Layer'; + const isFastLayer = child.getType() === 'FastLayer'; + const valid = isLayer || isFastLayer; + if (!valid) { + Util.throw('You may only add layers to the stage.'); + } + } + + _checkVisibility() { + if (!this.content) { + return; + } + const style = this.visible() ? '' : 'none'; + this.content.style.display = style; + } + /** + * set container dom element which contains the stage wrapper div element + * @method + * @name Konva.Stage#setContainer + * @param {DomElement} container can pass in a dom element or id string + */ + setContainer(container) { + if (typeof container === STRING) { + if (container.charAt(0) === '.') { + const className = container.slice(1); + container = document.getElementsByClassName(className)[0]; + } else { + var id; + if (container.charAt(0) !== '#') { + id = container; + } else { + id = container.slice(1); + } + container = document.getElementById(id); + } + if (!container) { + throw 'Can not find container in document with id ' + id; + } + } + this._setAttr('container', container); + if (this.content) { + if (this.content.parentElement) { + this.content.parentElement.removeChild(this.content); + } + container.appendChild(this.content); + } + return this; + } + shouldDrawHit() { + return true; + } + + /** + * clear all layers + * @method + * @name Konva.Stage#clear + */ + clear() { + const layers = this.children, + len = layers.length; + + for (let n = 0; n < len; n++) { + layers[n].clear(); + } + return this; + } + clone(obj?) { + if (!obj) { + obj = {}; + } + obj.container = + typeof document !== 'undefined' && document.createElement('div'); + return Container.prototype.clone.call(this, obj) as this; + } + + destroy() { + super.destroy(); + + const content = this.content; + if (content && Util._isInDocument(content)) { + this.container().removeChild(content); + } + const index = stages.indexOf(this); + if (index > -1) { + stages.splice(index, 1); + } + + Util.releaseCanvas(this.bufferCanvas._canvas, this.bufferHitCanvas._canvas); + + return this; + } + /** + * returns ABSOLUTE pointer position which can be a touch position or mouse position + * pointer position doesn't include any transforms (such as scale) of the stage + * it is just a plain position of pointer relative to top-left corner of the canvas + * @method + * @name Konva.Stage#getPointerPosition + * @returns {Vector2d|null} + */ + getPointerPosition(): Vector2d | null { + const pos = this._pointerPositions[0] || this._changedPointerPositions[0]; + if (!pos) { + Util.warn(NO_POINTERS_MESSAGE); + return null; + } + return { + x: pos.x, + y: pos.y, + }; + } + _getPointerById(id?: number) { + return this._pointerPositions.find((p) => p.id === id); + } + getPointersPositions() { + return this._pointerPositions; + } + getStage() { + return this; + } + getContent() { + return this.content; + } + _toKonvaCanvas(config) { + config = config || {}; + + config.x = config.x || 0; + config.y = config.y || 0; + config.width = config.width || this.width(); + config.height = config.height || this.height(); + + const canvas = new SceneCanvas({ + width: config.width, + height: config.height, + pixelRatio: config.pixelRatio || 1, + }); + const _context = canvas.getContext()._context; + const layers = this.children; + + if (config.x || config.y) { + _context.translate(-1 * config.x, -1 * config.y); + } + + layers.forEach(function (layer) { + if (!layer.isVisible()) { + return; + } + const layerCanvas = layer._toKonvaCanvas(config); + _context.drawImage( + layerCanvas._canvas, + config.x, + config.y, + layerCanvas.getWidth() / layerCanvas.getPixelRatio(), + layerCanvas.getHeight() / layerCanvas.getPixelRatio() + ); + }); + return canvas; + } + + /** + * get visible intersection shape. This is the preferred + * method for determining if a point intersects a shape or not + * nodes with listening set to false will not be detected + * @method + * @name Konva.Stage#getIntersection + * @param {Object} pos + * @param {Number} pos.x + * @param {Number} pos.y + * @returns {Konva.Node} + * @example + * var shape = stage.getIntersection({x: 50, y: 50}); + */ + getIntersection(pos: Vector2d) { + if (!pos) { + return null; + } + const layers = this.children, + len = layers.length, + end = len - 1; + + for (let n = end; n >= 0; n--) { + const shape = layers[n].getIntersection(pos); + if (shape) { + return shape; + } + } + + return null; + } + _resizeDOM() { + const width = this.width(); + const height = this.height(); + if (this.content) { + // set content dimensions + this.content.style.width = width + PX; + this.content.style.height = height + PX; + } + + this.bufferCanvas.setSize(width, height); + this.bufferHitCanvas.setSize(width, height); + + // set layer dimensions + this.children.forEach((layer) => { + layer.setSize({ width, height }); + layer.draw(); + }); + } + add(layer: Layer, ...rest) { + if (arguments.length > 1) { + for (let i = 0; i < arguments.length; i++) { + this.add(arguments[i]); + } + return this; + } + super.add(layer); + + const length = this.children.length; + if (length > MAX_LAYERS_NUMBER) { + Util.warn( + 'The stage has ' + + length + + ' layers. Recommended maximum number of layers is 3-5. Adding more layers into the stage may drop the performance. Rethink your tree structure, you can use Konva.Group.' + ); + } + layer.setSize({ width: this.width(), height: this.height() }); + + // draw layer and append canvas to container + layer.draw(); + + if (Konva.isBrowser) { + this.content.appendChild(layer.canvas._canvas); + } + + // chainable + return this; + } + getParent() { + return null; + } + getLayer() { + return null; + } + + hasPointerCapture(pointerId: number): boolean { + return PointerEvents.hasPointerCapture(pointerId, this); + } + + setPointerCapture(pointerId: number) { + PointerEvents.setPointerCapture(pointerId, this); + } + + releaseCapture(pointerId: number) { + PointerEvents.releaseCapture(pointerId, this); + } + + /** + * returns an array of layers + * @method + * @name Konva.Stage#getLayers + */ + getLayers() { + return this.children; + } + _bindContentEvents() { + if (!Konva.isBrowser) { + return; + } + EVENTS.forEach(([event, methodName]) => { + this.content.addEventListener( + event, + (evt) => { + this[methodName](evt); + }, + { passive: false } + ); + }); + } + _pointerenter(evt: PointerEvent) { + this.setPointersPositions(evt); + const events = getEventsMap(evt.type); + if (events) { + this._fire(events.pointerenter, { + evt: evt, + target: this, + currentTarget: this, + }); + } + } + _pointerover(evt) { + this.setPointersPositions(evt); + const events = getEventsMap(evt.type); + if (events) { + this._fire(events.pointerover, { + evt: evt, + target: this, + currentTarget: this, + }); + } + } + _getTargetShape(evenType) { + let shape: Shape | null = this[evenType + 'targetShape']; + if (shape && !shape.getStage()) { + shape = null; + } + return shape; + } + _pointerleave(evt) { + const events = getEventsMap(evt.type); + const eventType = getEventType(evt.type); + + if (!events) { + return; + } + this.setPointersPositions(evt); + + const targetShape = this._getTargetShape(eventType); + const eventsEnabled = + !(Konva.isDragging() || Konva.isTransforming()) || Konva.hitOnDragEnabled; + if (targetShape && eventsEnabled) { + targetShape._fireAndBubble(events.pointerout, { evt: evt }); + targetShape._fireAndBubble(events.pointerleave, { evt: evt }); + this._fire(events.pointerleave, { + evt: evt, + target: this, + currentTarget: this, + }); + this[eventType + 'targetShape'] = null; + } else if (eventsEnabled) { + this._fire(events.pointerleave, { + evt: evt, + target: this, + currentTarget: this, + }); + this._fire(events.pointerout, { + evt: evt, + target: this, + currentTarget: this, + }); + } + this.pointerPos = null; + this._pointerPositions = []; + } + _pointerdown(evt: TouchEvent | MouseEvent | PointerEvent) { + const events = getEventsMap(evt.type); + const eventType = getEventType(evt.type); + + if (!events) { + return; + } + this.setPointersPositions(evt); + + let triggeredOnShape = false; + this._changedPointerPositions.forEach((pos) => { + const shape = this.getIntersection(pos); + DD.justDragged = false; + // probably we are staring a click + Konva['_' + eventType + 'ListenClick'] = true; + + // no shape detected? do nothing + if (!shape || !shape.isListening()) { + this[eventType + 'ClickStartShape'] = undefined; + return; + } + + if (Konva.capturePointerEventsEnabled) { + shape.setPointerCapture(pos.id); + } + + // save where we started the click + this[eventType + 'ClickStartShape'] = shape; + + shape._fireAndBubble(events.pointerdown, { + evt: evt, + pointerId: pos.id, + }); + triggeredOnShape = true; + + // TODO: test in iframe + // only call preventDefault if the shape is listening for events + const isTouch = evt.type.indexOf('touch') >= 0; + if (shape.preventDefault() && evt.cancelable && isTouch) { + evt.preventDefault(); + } + }); + + // trigger down on stage if not already + if (!triggeredOnShape) { + this._fire(events.pointerdown, { + evt: evt, + target: this, + currentTarget: this, + pointerId: this._pointerPositions[0].id, + }); + } + } + _pointermove(evt: TouchEvent | MouseEvent | PointerEvent) { + const events = getEventsMap(evt.type); + const eventType = getEventType(evt.type); + if (!events) { + return; + } + if (Konva.isDragging() && DD.node!.preventDefault() && evt.cancelable) { + evt.preventDefault(); + } + this.setPointersPositions(evt); + + const eventsEnabled = + !(Konva.isDragging() || Konva.isTransforming()) || Konva.hitOnDragEnabled; + if (!eventsEnabled) { + return; + } + + const processedShapesIds = {}; + let triggeredOnShape = false; + const targetShape = this._getTargetShape(eventType); + this._changedPointerPositions.forEach((pos) => { + const shape = (PointerEvents.getCapturedShape(pos.id) || + this.getIntersection(pos)) as Shape; + const pointerId = pos.id; + const event = { evt: evt, pointerId }; + + const differentTarget = targetShape !== shape; + + if (differentTarget && targetShape) { + targetShape._fireAndBubble(events.pointerout, { ...event }, shape); + targetShape._fireAndBubble(events.pointerleave, { ...event }, shape); + } + + if (shape) { + if (processedShapesIds[shape._id]) { + return; + } + processedShapesIds[shape._id] = true; + } + + if (shape && shape.isListening()) { + triggeredOnShape = true; + if (differentTarget) { + shape._fireAndBubble(events.pointerover, { ...event }, targetShape); + shape._fireAndBubble(events.pointerenter, { ...event }, targetShape); + this[eventType + 'targetShape'] = shape; + } + shape._fireAndBubble(events.pointermove, { ...event }); + } else { + if (targetShape) { + this._fire(events.pointerover, { + evt: evt, + target: this, + currentTarget: this, + pointerId, + }); + this[eventType + 'targetShape'] = null; + } + } + }); + + if (!triggeredOnShape) { + this._fire(events.pointermove, { + evt: evt, + target: this, + currentTarget: this, + pointerId: this._changedPointerPositions[0].id, + }); + } + } + _pointerup(evt) { + const events = getEventsMap(evt.type); + const eventType = getEventType(evt.type); + + if (!events) { + return; + } + this.setPointersPositions(evt); + const clickStartShape = this[eventType + 'ClickStartShape']; + const clickEndShape = this[eventType + 'ClickEndShape']; + const processedShapesIds = {}; + let triggeredOnShape = false; + this._changedPointerPositions.forEach((pos) => { + const shape = (PointerEvents.getCapturedShape(pos.id) || + this.getIntersection(pos)) as Shape; + + if (shape) { + shape.releaseCapture(pos.id); + if (processedShapesIds[shape._id]) { + return; + } + processedShapesIds[shape._id] = true; + } + + const pointerId = pos.id; + const event = { evt: evt, pointerId }; + + let fireDblClick = false; + if (Konva['_' + eventType + 'InDblClickWindow']) { + fireDblClick = true; + clearTimeout(this[eventType + 'DblTimeout']); + } else if (!DD.justDragged) { + // don't set inDblClickWindow after dragging + Konva['_' + eventType + 'InDblClickWindow'] = true; + clearTimeout(this[eventType + 'DblTimeout']); + } + + this[eventType + 'DblTimeout'] = setTimeout(function () { + Konva['_' + eventType + 'InDblClickWindow'] = false; + }, Konva.dblClickWindow); + + if (shape && shape.isListening()) { + triggeredOnShape = true; + this[eventType + 'ClickEndShape'] = shape; + shape._fireAndBubble(events.pointerup, { ...event }); + + // detect if click or double click occurred + if ( + Konva['_' + eventType + 'ListenClick'] && + clickStartShape && + clickStartShape === shape + ) { + shape._fireAndBubble(events.pointerclick, { ...event }); + + if (fireDblClick && clickEndShape && clickEndShape === shape) { + shape._fireAndBubble(events.pointerdblclick, { ...event }); + } + } + } else { + this[eventType + 'ClickEndShape'] = null; + + if (Konva['_' + eventType + 'ListenClick']) { + this._fire(events.pointerclick, { + evt: evt, + target: this, + currentTarget: this, + pointerId, + }); + } + + if (fireDblClick) { + this._fire(events.pointerdblclick, { + evt: evt, + target: this, + currentTarget: this, + pointerId, + }); + } + } + }); + + if (!triggeredOnShape) { + this._fire(events.pointerup, { + evt: evt, + target: this, + currentTarget: this, + pointerId: this._changedPointerPositions[0].id, + }); + } + + Konva['_' + eventType + 'ListenClick'] = false; + + // always call preventDefault for desktop events because some browsers + // try to drag and drop the canvas element + // TODO: are we sure we need to prevent default at all? + // do not call this function on mobile because it prevent "click" event on all parent containers + // but apps may listen to it. + if (evt.cancelable && eventType !== 'touch' && eventType !== 'pointer') { + evt.preventDefault(); + } + } + _contextmenu(evt) { + this.setPointersPositions(evt); + const shape = this.getIntersection(this.getPointerPosition()!); + + if (shape && shape.isListening()) { + shape._fireAndBubble(CONTEXTMENU, { evt: evt }); + } else { + this._fire(CONTEXTMENU, { + evt: evt, + target: this, + currentTarget: this, + }); + } + } + + _wheel(evt) { + this.setPointersPositions(evt); + const shape = this.getIntersection(this.getPointerPosition()!); + + if (shape && shape.isListening()) { + shape._fireAndBubble(WHEEL, { evt: evt }); + } else { + this._fire(WHEEL, { + evt: evt, + target: this, + currentTarget: this, + }); + } + } + + _pointercancel(evt: PointerEvent) { + this.setPointersPositions(evt); + const shape = + PointerEvents.getCapturedShape(evt.pointerId) || + this.getIntersection(this.getPointerPosition()!); + + if (shape) { + shape._fireAndBubble(POINTERUP, PointerEvents.createEvent(evt)); + } + + PointerEvents.releaseCapture(evt.pointerId); + } + + _lostpointercapture(evt: PointerEvent) { + PointerEvents.releaseCapture(evt.pointerId); + } + + /** + * manually register pointers positions (mouse/touch) in the stage. + * So you can use stage.getPointerPosition(). Usually you don't need to use that method + * because all internal events are automatically registered. It may be useful if event + * is triggered outside of the stage, but you still want to use Konva methods to get pointers position. + * @method + * @name Konva.Stage#setPointersPositions + * @param {Object} event Event object + * @example + * + * window.addEventListener('mousemove', (e) => { + * stage.setPointersPositions(e); + * }); + */ + setPointersPositions(evt) { + const contentPosition = this._getContentPosition(); + let x: number | null = null, + y: number | null = null; + evt = evt ? evt : window.event; + + // touch events + if (evt.touches !== undefined) { + // touchlist has not support for map method + // so we have to iterate + this._pointerPositions = []; + this._changedPointerPositions = []; + Array.prototype.forEach.call(evt.touches, (touch: any) => { + this._pointerPositions.push({ + id: touch.identifier, + x: (touch.clientX - contentPosition.left) / contentPosition.scaleX, + y: (touch.clientY - contentPosition.top) / contentPosition.scaleY, + }); + }); + + Array.prototype.forEach.call( + evt.changedTouches || evt.touches, + (touch: any) => { + this._changedPointerPositions.push({ + id: touch.identifier, + x: (touch.clientX - contentPosition.left) / contentPosition.scaleX, + y: (touch.clientY - contentPosition.top) / contentPosition.scaleY, + }); + } + ); + } else { + // mouse events + x = (evt.clientX - contentPosition.left) / contentPosition.scaleX; + y = (evt.clientY - contentPosition.top) / contentPosition.scaleY; + this.pointerPos = { + x: x, + y: y, + }; + this._pointerPositions = [{ x, y, id: Util._getFirstPointerId(evt) }]; + this._changedPointerPositions = [ + { x, y, id: Util._getFirstPointerId(evt) }, + ]; + } + } + _setPointerPosition(evt) { + Util.warn( + 'Method _setPointerPosition is deprecated. Use "stage.setPointersPositions(event)" instead.' + ); + this.setPointersPositions(evt); + } + _getContentPosition() { + if (!this.content || !this.content.getBoundingClientRect) { + return { + top: 0, + left: 0, + scaleX: 1, + scaleY: 1, + }; + } + + const rect = this.content.getBoundingClientRect(); + + return { + top: rect.top, + left: rect.left, + // sometimes clientWidth can be equals to 0 + // i saw it in react-konva test, looks like it is because of hidden testing element + scaleX: rect.width / this.content.clientWidth || 1, + scaleY: rect.height / this.content.clientHeight || 1, + }; + } + _buildDOM() { + this.bufferCanvas = new SceneCanvas({ + width: this.width(), + height: this.height(), + }); + this.bufferHitCanvas = new HitCanvas({ + pixelRatio: 1, + width: this.width(), + height: this.height(), + }); + + if (!Konva.isBrowser) { + return; + } + const container = this.container(); + if (!container) { + throw 'Stage has no container. A container is required.'; + } + // clear content inside container + container.innerHTML = ''; + + // content + this.content = document.createElement('div'); + this.content.style.position = 'relative'; + this.content.style.userSelect = 'none'; + this.content.className = 'konvajs-content'; + + this.content.setAttribute('role', 'presentation'); + + container.appendChild(this.content); + + this._resizeDOM(); + } + // currently cache function is now working for stage, because stage has no its own canvas element + cache() { + Util.warn( + 'Cache function is not allowed for stage. You may use cache only for layers, groups and shapes.' + ); + return this; + } + clearCache() { + return this; + } + /** + * batch draw + * @method + * @name Konva.Stage#batchDraw + * @return {Konva.Stage} this + */ + batchDraw() { + this.getChildren().forEach(function (layer) { + layer.batchDraw(); + }); + return this; + } + + container: GetSet; +} + +Stage.prototype.nodeType = STAGE; +_registerNode(Stage); + +/** + * get/set container DOM element + * @method + * @name Konva.Stage#container + * @returns {DomElement} container + * @example + * // get container + * var container = stage.container(); + * // set container + * var container = document.createElement('div'); + * body.appendChild(container); + * stage.container(container); + */ +Factory.addGetterSetter(Stage, 'container'); + +// chrome is clearing canvas in inactive browser window, causing layer content to be erased +// so let's redraw layers as soon as window becomes active +// TODO: any other way to solve this issue? +// TODO: should we remove it if chrome fixes the issue? +if (Konva.isBrowser) { + document.addEventListener('visibilitychange', () => { + stages.forEach((stage) => { + stage.batchDraw(); + }); + }); +} diff --git a/src/Tween.ts b/src/Tween.ts new file mode 100644 index 000000000..bb2d82788 --- /dev/null +++ b/src/Tween.ts @@ -0,0 +1,805 @@ +import { Util } from './Util'; +import { Animation } from './Animation'; +import { Node, NodeConfig } from './Node'; +import { Konva } from './Global'; +import { Line } from './shapes/Line'; + +const blacklist = { + node: 1, + duration: 1, + easing: 1, + onFinish: 1, + yoyo: 1, + }, + PAUSED = 1, + PLAYING = 2, + REVERSING = 3, + colorAttrs = ['fill', 'stroke', 'shadowColor']; +let idCounter = 0; + +class TweenEngine { + prop: string; + propFunc: Function; + begin: number; + _pos: number; + duration: number; + prevPos: number; + yoyo: boolean; + _time: number; + _position: number; + _startTime: number; + _finish: number; + func: Function; + _change: number; + state: number; + + onPlay: Function; + onReverse: Function; + onPause: Function; + onReset: Function; + onFinish: Function; + onUpdate: Function; + + constructor(prop, propFunc, func, begin, finish, duration, yoyo) { + this.prop = prop; + this.propFunc = propFunc; + this.begin = begin; + this._pos = begin; + this.duration = duration; + this._change = 0; + this.prevPos = 0; + this.yoyo = yoyo; + this._time = 0; + this._position = 0; + this._startTime = 0; + this._finish = 0; + this.func = func; + this._change = finish - this.begin; + this.pause(); + } + fire(str) { + const handler = this[str]; + if (handler) { + handler(); + } + } + setTime(t) { + if (t > this.duration) { + if (this.yoyo) { + this._time = this.duration; + this.reverse(); + } else { + this.finish(); + } + } else if (t < 0) { + if (this.yoyo) { + this._time = 0; + this.play(); + } else { + this.reset(); + } + } else { + this._time = t; + this.update(); + } + } + getTime() { + return this._time; + } + setPosition(p) { + this.prevPos = this._pos; + this.propFunc(p); + this._pos = p; + } + getPosition(t) { + if (t === undefined) { + t = this._time; + } + return this.func(t, this.begin, this._change, this.duration); + } + play() { + this.state = PLAYING; + this._startTime = this.getTimer() - this._time; + this.onEnterFrame(); + this.fire('onPlay'); + } + reverse() { + this.state = REVERSING; + this._time = this.duration - this._time; + this._startTime = this.getTimer() - this._time; + this.onEnterFrame(); + this.fire('onReverse'); + } + seek(t) { + this.pause(); + this._time = t; + this.update(); + this.fire('onSeek'); + } + reset() { + this.pause(); + this._time = 0; + this.update(); + this.fire('onReset'); + } + finish() { + this.pause(); + this._time = this.duration; + this.update(); + this.fire('onFinish'); + } + update() { + this.setPosition(this.getPosition(this._time)); + this.fire('onUpdate'); + } + onEnterFrame() { + const t = this.getTimer() - this._startTime; + if (this.state === PLAYING) { + this.setTime(t); + } else if (this.state === REVERSING) { + this.setTime(this.duration - t); + } + } + pause() { + this.state = PAUSED; + this.fire('onPause'); + } + getTimer() { + return new Date().getTime(); + } +} + +export interface TweenConfig extends NodeConfig { + onFinish?: Function; + onUpdate?: Function; + duration?: number; + node: Node; +} + +/** + * Tween constructor. Tweens enable you to animate a node between the current state and a new state. + * You can play, pause, reverse, seek, reset, and finish tweens. By default, tweens are animated using + * a linear easing. For more tweening options, check out {@link Konva.Easings} + * @constructor + * @memberof Konva + * @example + * // instantiate new tween which fully rotates a node in 1 second + * var tween = new Konva.Tween({ + * // list of tween specific properties + * node: node, + * duration: 1, + * easing: Konva.Easings.EaseInOut, + * onUpdate: () => console.log('node attrs updated') + * onFinish: () => console.log('finished'), + * // set new values for any attributes of a passed node + * rotation: 360, + * fill: 'red' + * }); + * + * // play tween + * tween.play(); + * + * // pause tween + * tween.pause(); + */ +export class Tween { + static attrs = {}; + static tweens = {}; + + node: Node; + anim: Animation; + tween: TweenEngine; + _id: number; + onFinish: Function | undefined; + onReset: Function | undefined; + onUpdate: Function | undefined; + + constructor(config: TweenConfig) { + const that = this, + node = config.node as any, + nodeId = node._id, + easing = config.easing || Easings.Linear, + yoyo = !!config.yoyo; + let duration, + key; + + if (typeof config.duration === 'undefined') { + duration = 0.3; + } else if (config.duration === 0) { + // zero is bad value for duration + duration = 0.001; + } else { + duration = config.duration; + } + this.node = node; + this._id = idCounter++; + + const layers = + node.getLayer() || + (node instanceof Konva['Stage'] ? node.getLayers() : null); + if (!layers) { + Util.error( + 'Tween constructor have `node` that is not in a layer. Please add node into layer first.' + ); + } + this.anim = new Animation(function () { + that.tween.onEnterFrame(); + }, layers); + + this.tween = new TweenEngine( + key, + function (i) { + that._tweenFunc(i); + }, + easing, + 0, + 1, + duration * 1000, + yoyo + ); + + this._addListeners(); + + // init attrs map + if (!Tween.attrs[nodeId]) { + Tween.attrs[nodeId] = {}; + } + if (!Tween.attrs[nodeId][this._id]) { + Tween.attrs[nodeId][this._id] = {}; + } + // init tweens map + if (!Tween.tweens[nodeId]) { + Tween.tweens[nodeId] = {}; + } + + for (key in config) { + if (blacklist[key] === undefined) { + this._addAttr(key, config[key]); + } + } + + this.reset(); + + // callbacks + this.onFinish = config.onFinish; + this.onReset = config.onReset; + this.onUpdate = config.onUpdate; + } + _addAttr(key, end) { + const node = this.node, + nodeId = node._id; + let diff, + len, + trueEnd, + trueStart, + endRGBA; + + // remove conflict from tween map if it exists + const tweenId = Tween.tweens[nodeId][key]; + + if (tweenId) { + delete Tween.attrs[nodeId][tweenId][key]; + } + + // add to tween map + let start = node.getAttr(key); + + if (Util._isArray(end)) { + diff = []; + len = Math.max(end.length, start.length); + + if (key === 'points' && end.length !== start.length) { + // before tweening points we need to make sure that start.length === end.length + // Util._prepareArrayForTween thinking that end.length > start.length + if (end.length > start.length) { + // so in this case we will increase number of starting points + trueStart = start; + start = Util._prepareArrayForTween( + start, + end, + (node as Line).closed() + ); + } else { + // in this case we will increase number of eding points + trueEnd = end; + end = Util._prepareArrayForTween(end, start, (node as Line).closed()); + } + } + + if (key.indexOf('fill') === 0) { + for (let n = 0; n < len; n++) { + if (n % 2 === 0) { + diff.push(end[n] - start[n]); + } else { + const startRGBA = Util.colorToRGBA(start[n])!; + endRGBA = Util.colorToRGBA(end[n]); + start[n] = startRGBA; + diff.push({ + r: endRGBA.r - startRGBA.r, + g: endRGBA.g - startRGBA.g, + b: endRGBA.b - startRGBA.b, + a: endRGBA.a - startRGBA.a, + }); + } + } + } else { + for (let n = 0; n < len; n++) { + diff.push(end[n] - start[n]); + } + } + } else if (colorAttrs.indexOf(key) !== -1) { + start = Util.colorToRGBA(start); + endRGBA = Util.colorToRGBA(end); + diff = { + r: endRGBA.r - start.r, + g: endRGBA.g - start.g, + b: endRGBA.b - start.b, + a: endRGBA.a - start.a, + }; + } else { + diff = end - start; + } + + Tween.attrs[nodeId][this._id][key] = { + start: start, + diff: diff, + end: end, + trueEnd: trueEnd, + trueStart: trueStart, + }; + Tween.tweens[nodeId][key] = this._id; + } + _tweenFunc(i) { + const node = this.node, + attrs = Tween.attrs[node._id][this._id]; + let key, + attr, + start, + diff, + newVal, + n, + len, + end; + + for (key in attrs) { + attr = attrs[key]; + start = attr.start; + diff = attr.diff; + end = attr.end; + + if (Util._isArray(start)) { + newVal = []; + len = Math.max(start.length, end.length); + if (key.indexOf('fill') === 0) { + for (n = 0; n < len; n++) { + if (n % 2 === 0) { + newVal.push((start[n] || 0) + diff[n] * i); + } else { + newVal.push( + 'rgba(' + + Math.round(start[n].r + diff[n].r * i) + + ',' + + Math.round(start[n].g + diff[n].g * i) + + ',' + + Math.round(start[n].b + diff[n].b * i) + + ',' + + (start[n].a + diff[n].a * i) + + ')' + ); + } + } + } else { + for (n = 0; n < len; n++) { + newVal.push((start[n] || 0) + diff[n] * i); + } + } + } else if (colorAttrs.indexOf(key) !== -1) { + newVal = + 'rgba(' + + Math.round(start.r + diff.r * i) + + ',' + + Math.round(start.g + diff.g * i) + + ',' + + Math.round(start.b + diff.b * i) + + ',' + + (start.a + diff.a * i) + + ')'; + } else { + newVal = start + diff * i; + } + + node.setAttr(key, newVal); + } + } + _addListeners() { + // start listeners + this.tween.onPlay = () => { + this.anim.start(); + }; + this.tween.onReverse = () => { + this.anim.start(); + }; + + // stop listeners + this.tween.onPause = () => { + this.anim.stop(); + }; + this.tween.onFinish = () => { + const node = this.node as Node; + + // after tweening points of line we need to set original end + const attrs = Tween.attrs[node._id][this._id]; + if (attrs.points && attrs.points.trueEnd) { + node.setAttr('points', attrs.points.trueEnd); + } + + if (this.onFinish) { + this.onFinish.call(this); + } + }; + this.tween.onReset = () => { + const node = this.node as any; + // after tweening points of line we need to set original start + const attrs = Tween.attrs[node._id][this._id]; + if (attrs.points && attrs.points.trueStart) { + node.points(attrs.points.trueStart); + } + + if (this.onReset) { + this.onReset(); + } + }; + this.tween.onUpdate = () => { + if (this.onUpdate) { + this.onUpdate.call(this); + } + }; + } + /** + * play + * @method + * @name Konva.Tween#play + * @returns {Tween} + */ + play() { + this.tween.play(); + return this; + } + /** + * reverse + * @method + * @name Konva.Tween#reverse + * @returns {Tween} + */ + reverse() { + this.tween.reverse(); + return this; + } + /** + * reset + * @method + * @name Konva.Tween#reset + * @returns {Tween} + */ + reset() { + this.tween.reset(); + return this; + } + /** + * seek + * @method + * @name Konva.Tween#seek( + * @param {Integer} t time in seconds between 0 and the duration + * @returns {Tween} + */ + seek(t) { + this.tween.seek(t * 1000); + return this; + } + /** + * pause + * @method + * @name Konva.Tween#pause + * @returns {Tween} + */ + pause() { + this.tween.pause(); + return this; + } + /** + * finish + * @method + * @name Konva.Tween#finish + * @returns {Tween} + */ + finish() { + this.tween.finish(); + return this; + } + /** + * destroy + * @method + * @name Konva.Tween#destroy + */ + destroy() { + const nodeId = this.node._id, + thisId = this._id, + attrs = Tween.tweens[nodeId]; + + this.pause(); + + for (const key in attrs) { + delete Tween.tweens[nodeId][key]; + } + + delete Tween.attrs[nodeId][thisId]; + } +} + +/** + * Tween node properties. Shorter usage of {@link Konva.Tween} object. + * + * @method Konva.Node#to + * @param {Object} [params] tween params + * @example + * + * circle.to({ + * x : 50, + * duration : 0.5, + * onUpdate: () => console.log('props updated'), + * onFinish: () => console.log('finished'), + * }); + */ +Node.prototype.to = function (params) { + const onFinish = params.onFinish; + params.node = this; + params.onFinish = function () { + this.destroy(); + if (onFinish) { + onFinish(); + } + }; + const tween = new Tween(params as any); + tween.play(); +}; + +/* + * These eases were ported from an Adobe Flash tweening library to JavaScript + * by Xaric + */ + +/** + * @namespace Easings + * @memberof Konva + */ +export const Easings = { + /** + * back ease in + * @function + * @memberof Konva.Easings + */ + BackEaseIn(t, b, c, d) { + const s = 1.70158; + return c * (t /= d) * t * ((s + 1) * t - s) + b; + }, + /** + * back ease out + * @function + * @memberof Konva.Easings + */ + BackEaseOut(t, b, c, d) { + const s = 1.70158; + return c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b; + }, + /** + * back ease in out + * @function + * @memberof Konva.Easings + */ + BackEaseInOut(t, b, c, d) { + let s = 1.70158; + if ((t /= d / 2) < 1) { + return (c / 2) * (t * t * (((s *= 1.525) + 1) * t - s)) + b; + } + return (c / 2) * ((t -= 2) * t * (((s *= 1.525) + 1) * t + s) + 2) + b; + }, + /** + * elastic ease in + * @function + * @memberof Konva.Easings + */ + ElasticEaseIn(t, b, c, d, a, p) { + // added s = 0 + let s = 0; + if (t === 0) { + return b; + } + if ((t /= d) === 1) { + return b + c; + } + if (!p) { + p = d * 0.3; + } + if (!a || a < Math.abs(c)) { + a = c; + s = p / 4; + } else { + s = (p / (2 * Math.PI)) * Math.asin(c / a); + } + return ( + -( + a * + Math.pow(2, 10 * (t -= 1)) * + Math.sin(((t * d - s) * (2 * Math.PI)) / p) + ) + b + ); + }, + /** + * elastic ease out + * @function + * @memberof Konva.Easings + */ + ElasticEaseOut(t, b, c, d, a, p) { + // added s = 0 + let s = 0; + if (t === 0) { + return b; + } + if ((t /= d) === 1) { + return b + c; + } + if (!p) { + p = d * 0.3; + } + if (!a || a < Math.abs(c)) { + a = c; + s = p / 4; + } else { + s = (p / (2 * Math.PI)) * Math.asin(c / a); + } + return ( + a * Math.pow(2, -10 * t) * Math.sin(((t * d - s) * (2 * Math.PI)) / p) + + c + + b + ); + }, + /** + * elastic ease in out + * @function + * @memberof Konva.Easings + */ + ElasticEaseInOut(t, b, c, d, a, p) { + // added s = 0 + let s = 0; + if (t === 0) { + return b; + } + if ((t /= d / 2) === 2) { + return b + c; + } + if (!p) { + p = d * (0.3 * 1.5); + } + if (!a || a < Math.abs(c)) { + a = c; + s = p / 4; + } else { + s = (p / (2 * Math.PI)) * Math.asin(c / a); + } + if (t < 1) { + return ( + -0.5 * + (a * + Math.pow(2, 10 * (t -= 1)) * + Math.sin(((t * d - s) * (2 * Math.PI)) / p)) + + b + ); + } + return ( + a * + Math.pow(2, -10 * (t -= 1)) * + Math.sin(((t * d - s) * (2 * Math.PI)) / p) * + 0.5 + + c + + b + ); + }, + /** + * bounce ease out + * @function + * @memberof Konva.Easings + */ + BounceEaseOut(t, b, c, d) { + if ((t /= d) < 1 / 2.75) { + return c * (7.5625 * t * t) + b; + } else if (t < 2 / 2.75) { + return c * (7.5625 * (t -= 1.5 / 2.75) * t + 0.75) + b; + } else if (t < 2.5 / 2.75) { + return c * (7.5625 * (t -= 2.25 / 2.75) * t + 0.9375) + b; + } else { + return c * (7.5625 * (t -= 2.625 / 2.75) * t + 0.984375) + b; + } + }, + /** + * bounce ease in + * @function + * @memberof Konva.Easings + */ + BounceEaseIn(t, b, c, d) { + return c - Easings.BounceEaseOut(d - t, 0, c, d) + b; + }, + /** + * bounce ease in out + * @function + * @memberof Konva.Easings + */ + BounceEaseInOut(t, b, c, d) { + if (t < d / 2) { + return Easings.BounceEaseIn(t * 2, 0, c, d) * 0.5 + b; + } else { + return Easings.BounceEaseOut(t * 2 - d, 0, c, d) * 0.5 + c * 0.5 + b; + } + }, + /** + * ease in + * @function + * @memberof Konva.Easings + */ + EaseIn(t, b, c, d) { + return c * (t /= d) * t + b; + }, + /** + * ease out + * @function + * @memberof Konva.Easings + */ + EaseOut(t, b, c, d) { + return -c * (t /= d) * (t - 2) + b; + }, + /** + * ease in out + * @function + * @memberof Konva.Easings + */ + EaseInOut(t, b, c, d) { + if ((t /= d / 2) < 1) { + return (c / 2) * t * t + b; + } + return (-c / 2) * (--t * (t - 2) - 1) + b; + }, + /** + * strong ease in + * @function + * @memberof Konva.Easings + */ + StrongEaseIn(t, b, c, d) { + return c * (t /= d) * t * t * t * t + b; + }, + /** + * strong ease out + * @function + * @memberof Konva.Easings + */ + StrongEaseOut(t, b, c, d) { + return c * ((t = t / d - 1) * t * t * t * t + 1) + b; + }, + /** + * strong ease in out + * @function + * @memberof Konva.Easings + */ + StrongEaseInOut(t, b, c, d) { + if ((t /= d / 2) < 1) { + return (c / 2) * t * t * t * t * t + b; + } + return (c / 2) * ((t -= 2) * t * t * t * t + 2) + b; + }, + /** + * linear + * @function + * @memberof Konva.Easings + */ + Linear(t, b, c, d) { + return (c * t) / d + b; + }, +}; diff --git a/src/Util.ts b/src/Util.ts new file mode 100644 index 000000000..ae5075a5e --- /dev/null +++ b/src/Util.ts @@ -0,0 +1,1045 @@ +import { Konva } from './Global'; +import { Context } from './Context'; +import { IRect, RGB, Vector2d } from './types'; + +/* + * Last updated November 2011 + * By Simon Sarris + * www.simonsarris.com + * sarris@acm.org + * + * Free to use and distribute at will + * So long as you are nice to people, etc + */ +/* + * The usage of this class was inspired by some of the work done by a forked + * project, KineticJS-Ext by Wappworks, which is based on Simon's Transform + * class. Modified by Eric Rowell + */ + +/** + * Transform constructor. + * In most of the cases you don't need to use it in your app. Because it is for internal usage in Konva core. + * But there is a documentation for that class in case you still want + * to make some manual calculations. + * @constructor + * @param {Array} [m] Optional six-element matrix + * @memberof Konva + */ +export class Transform { + m: Array; + dirty = false; + constructor(m = [1, 0, 0, 1, 0, 0]) { + this.m = (m && m.slice()) || [1, 0, 0, 1, 0, 0]; + } + reset() { + this.m[0] = 1; + this.m[1] = 0; + this.m[2] = 0; + this.m[3] = 1; + this.m[4] = 0; + this.m[5] = 0; + } + /** + * Copy Konva.Transform object + * @method + * @name Konva.Transform#copy + * @returns {Konva.Transform} + * @example + * const tr = shape.getTransform().copy() + */ + copy() { + return new Transform(this.m); + } + copyInto(tr: Transform) { + tr.m[0] = this.m[0]; + tr.m[1] = this.m[1]; + tr.m[2] = this.m[2]; + tr.m[3] = this.m[3]; + tr.m[4] = this.m[4]; + tr.m[5] = this.m[5]; + } + /** + * Transform point + * @method + * @name Konva.Transform#point + * @param {Object} point 2D point(x, y) + * @returns {Object} 2D point(x, y) + */ + point(point: Vector2d) { + const m = this.m; + return { + x: m[0] * point.x + m[2] * point.y + m[4], + y: m[1] * point.x + m[3] * point.y + m[5], + }; + } + /** + * Apply translation + * @method + * @name Konva.Transform#translate + * @param {Number} x + * @param {Number} y + * @returns {Konva.Transform} + */ + translate(x: number, y: number) { + this.m[4] += this.m[0] * x + this.m[2] * y; + this.m[5] += this.m[1] * x + this.m[3] * y; + return this; + } + /** + * Apply scale + * @method + * @name Konva.Transform#scale + * @param {Number} sx + * @param {Number} sy + * @returns {Konva.Transform} + */ + scale(sx: number, sy: number) { + this.m[0] *= sx; + this.m[1] *= sx; + this.m[2] *= sy; + this.m[3] *= sy; + return this; + } + /** + * Apply rotation + * @method + * @name Konva.Transform#rotate + * @param {Number} rad Angle in radians + * @returns {Konva.Transform} + */ + rotate(rad: number) { + const c = Math.cos(rad); + const s = Math.sin(rad); + const m11 = this.m[0] * c + this.m[2] * s; + const m12 = this.m[1] * c + this.m[3] * s; + const m21 = this.m[0] * -s + this.m[2] * c; + const m22 = this.m[1] * -s + this.m[3] * c; + this.m[0] = m11; + this.m[1] = m12; + this.m[2] = m21; + this.m[3] = m22; + return this; + } + /** + * Returns the translation + * @method + * @name Konva.Transform#getTranslation + * @returns {Object} 2D point(x, y) + */ + getTranslation() { + return { + x: this.m[4], + y: this.m[5], + }; + } + /** + * Apply skew + * @method + * @name Konva.Transform#skew + * @param {Number} sx + * @param {Number} sy + * @returns {Konva.Transform} + */ + skew(sx: number, sy: number) { + const m11 = this.m[0] + this.m[2] * sy; + const m12 = this.m[1] + this.m[3] * sy; + const m21 = this.m[2] + this.m[0] * sx; + const m22 = this.m[3] + this.m[1] * sx; + this.m[0] = m11; + this.m[1] = m12; + this.m[2] = m21; + this.m[3] = m22; + return this; + } + /** + * Transform multiplication + * @method + * @name Konva.Transform#multiply + * @param {Konva.Transform} matrix + * @returns {Konva.Transform} + */ + multiply(matrix: Transform) { + const m11 = this.m[0] * matrix.m[0] + this.m[2] * matrix.m[1]; + const m12 = this.m[1] * matrix.m[0] + this.m[3] * matrix.m[1]; + + const m21 = this.m[0] * matrix.m[2] + this.m[2] * matrix.m[3]; + const m22 = this.m[1] * matrix.m[2] + this.m[3] * matrix.m[3]; + + const dx = this.m[0] * matrix.m[4] + this.m[2] * matrix.m[5] + this.m[4]; + const dy = this.m[1] * matrix.m[4] + this.m[3] * matrix.m[5] + this.m[5]; + + this.m[0] = m11; + this.m[1] = m12; + this.m[2] = m21; + this.m[3] = m22; + this.m[4] = dx; + this.m[5] = dy; + return this; + } + /** + * Invert the matrix + * @method + * @name Konva.Transform#invert + * @returns {Konva.Transform} + */ + invert() { + const d = 1 / (this.m[0] * this.m[3] - this.m[1] * this.m[2]); + const m0 = this.m[3] * d; + const m1 = -this.m[1] * d; + const m2 = -this.m[2] * d; + const m3 = this.m[0] * d; + const m4 = d * (this.m[2] * this.m[5] - this.m[3] * this.m[4]); + const m5 = d * (this.m[1] * this.m[4] - this.m[0] * this.m[5]); + this.m[0] = m0; + this.m[1] = m1; + this.m[2] = m2; + this.m[3] = m3; + this.m[4] = m4; + this.m[5] = m5; + return this; + } + /** + * return matrix + * @method + * @name Konva.Transform#getMatrix + */ + getMatrix() { + return this.m; + } + /** + * convert transformation matrix back into node's attributes + * @method + * @name Konva.Transform#decompose + * @returns {Konva.Transform} + */ + decompose() { + const a = this.m[0]; + const b = this.m[1]; + const c = this.m[2]; + const d = this.m[3]; + const e = this.m[4]; + const f = this.m[5]; + + const delta = a * d - b * c; + + const result = { + x: e, + y: f, + rotation: 0, + scaleX: 0, + scaleY: 0, + skewX: 0, + skewY: 0, + }; + + // Apply the QR-like decomposition. + if (a != 0 || b != 0) { + const r = Math.sqrt(a * a + b * b); + result.rotation = b > 0 ? Math.acos(a / r) : -Math.acos(a / r); + result.scaleX = r; + result.scaleY = delta / r; + result.skewX = (a * c + b * d) / delta; + result.skewY = 0; + } else if (c != 0 || d != 0) { + const s = Math.sqrt(c * c + d * d); + result.rotation = + Math.PI / 2 - (d > 0 ? Math.acos(-c / s) : -Math.acos(c / s)); + result.scaleX = delta / s; + result.scaleY = s; + result.skewX = 0; + result.skewY = (a * c + b * d) / delta; + } else { + // a = b = c = d = 0 + } + + result.rotation = Util._getRotation(result.rotation); + + return result; + } +} + +// CONSTANTS +const OBJECT_ARRAY = '[object Array]', + OBJECT_NUMBER = '[object Number]', + OBJECT_STRING = '[object String]', + OBJECT_BOOLEAN = '[object Boolean]', + PI_OVER_DEG180 = Math.PI / 180, + DEG180_OVER_PI = 180 / Math.PI, + HASH = '#', + EMPTY_STRING = '', + ZERO = '0', + KONVA_WARNING = 'Konva warning: ', + KONVA_ERROR = 'Konva error: ', + RGB_PAREN = 'rgb(', + COLORS = { + aliceblue: [240, 248, 255], + antiquewhite: [250, 235, 215], + aqua: [0, 255, 255], + aquamarine: [127, 255, 212], + azure: [240, 255, 255], + beige: [245, 245, 220], + bisque: [255, 228, 196], + black: [0, 0, 0], + blanchedalmond: [255, 235, 205], + blue: [0, 0, 255], + blueviolet: [138, 43, 226], + brown: [165, 42, 42], + burlywood: [222, 184, 135], + cadetblue: [95, 158, 160], + chartreuse: [127, 255, 0], + chocolate: [210, 105, 30], + coral: [255, 127, 80], + cornflowerblue: [100, 149, 237], + cornsilk: [255, 248, 220], + crimson: [220, 20, 60], + cyan: [0, 255, 255], + darkblue: [0, 0, 139], + darkcyan: [0, 139, 139], + darkgoldenrod: [184, 132, 11], + darkgray: [169, 169, 169], + darkgreen: [0, 100, 0], + darkgrey: [169, 169, 169], + darkkhaki: [189, 183, 107], + darkmagenta: [139, 0, 139], + darkolivegreen: [85, 107, 47], + darkorange: [255, 140, 0], + darkorchid: [153, 50, 204], + darkred: [139, 0, 0], + darksalmon: [233, 150, 122], + darkseagreen: [143, 188, 143], + darkslateblue: [72, 61, 139], + darkslategray: [47, 79, 79], + darkslategrey: [47, 79, 79], + darkturquoise: [0, 206, 209], + darkviolet: [148, 0, 211], + deeppink: [255, 20, 147], + deepskyblue: [0, 191, 255], + dimgray: [105, 105, 105], + dimgrey: [105, 105, 105], + dodgerblue: [30, 144, 255], + firebrick: [178, 34, 34], + floralwhite: [255, 255, 240], + forestgreen: [34, 139, 34], + fuchsia: [255, 0, 255], + gainsboro: [220, 220, 220], + ghostwhite: [248, 248, 255], + gold: [255, 215, 0], + goldenrod: [218, 165, 32], + gray: [128, 128, 128], + green: [0, 128, 0], + greenyellow: [173, 255, 47], + grey: [128, 128, 128], + honeydew: [240, 255, 240], + hotpink: [255, 105, 180], + indianred: [205, 92, 92], + indigo: [75, 0, 130], + ivory: [255, 255, 240], + khaki: [240, 230, 140], + lavender: [230, 230, 250], + lavenderblush: [255, 240, 245], + lawngreen: [124, 252, 0], + lemonchiffon: [255, 250, 205], + lightblue: [173, 216, 230], + lightcoral: [240, 128, 128], + lightcyan: [224, 255, 255], + lightgoldenrodyellow: [250, 250, 210], + lightgray: [211, 211, 211], + lightgreen: [144, 238, 144], + lightgrey: [211, 211, 211], + lightpink: [255, 182, 193], + lightsalmon: [255, 160, 122], + lightseagreen: [32, 178, 170], + lightskyblue: [135, 206, 250], + lightslategray: [119, 136, 153], + lightslategrey: [119, 136, 153], + lightsteelblue: [176, 196, 222], + lightyellow: [255, 255, 224], + lime: [0, 255, 0], + limegreen: [50, 205, 50], + linen: [250, 240, 230], + magenta: [255, 0, 255], + maroon: [128, 0, 0], + mediumaquamarine: [102, 205, 170], + mediumblue: [0, 0, 205], + mediumorchid: [186, 85, 211], + mediumpurple: [147, 112, 219], + mediumseagreen: [60, 179, 113], + mediumslateblue: [123, 104, 238], + mediumspringgreen: [0, 250, 154], + mediumturquoise: [72, 209, 204], + mediumvioletred: [199, 21, 133], + midnightblue: [25, 25, 112], + mintcream: [245, 255, 250], + mistyrose: [255, 228, 225], + moccasin: [255, 228, 181], + navajowhite: [255, 222, 173], + navy: [0, 0, 128], + oldlace: [253, 245, 230], + olive: [128, 128, 0], + olivedrab: [107, 142, 35], + orange: [255, 165, 0], + orangered: [255, 69, 0], + orchid: [218, 112, 214], + palegoldenrod: [238, 232, 170], + palegreen: [152, 251, 152], + paleturquoise: [175, 238, 238], + palevioletred: [219, 112, 147], + papayawhip: [255, 239, 213], + peachpuff: [255, 218, 185], + peru: [205, 133, 63], + pink: [255, 192, 203], + plum: [221, 160, 203], + powderblue: [176, 224, 230], + purple: [128, 0, 128], + rebeccapurple: [102, 51, 153], + red: [255, 0, 0], + rosybrown: [188, 143, 143], + royalblue: [65, 105, 225], + saddlebrown: [139, 69, 19], + salmon: [250, 128, 114], + sandybrown: [244, 164, 96], + seagreen: [46, 139, 87], + seashell: [255, 245, 238], + sienna: [160, 82, 45], + silver: [192, 192, 192], + skyblue: [135, 206, 235], + slateblue: [106, 90, 205], + slategray: [119, 128, 144], + slategrey: [119, 128, 144], + snow: [255, 255, 250], + springgreen: [0, 255, 127], + steelblue: [70, 130, 180], + tan: [210, 180, 140], + teal: [0, 128, 128], + thistle: [216, 191, 216], + transparent: [255, 255, 255, 0], + tomato: [255, 99, 71], + turquoise: [64, 224, 208], + violet: [238, 130, 238], + wheat: [245, 222, 179], + white: [255, 255, 255], + whitesmoke: [245, 245, 245], + yellow: [255, 255, 0], + yellowgreen: [154, 205, 5], + }, + RGB_REGEX = /rgb\((\d{1,3}),(\d{1,3}),(\d{1,3})\)/; + let animQueue: Array = []; + +const req = + (typeof requestAnimationFrame !== 'undefined' && requestAnimationFrame) || + function (f) { + setTimeout(f, 60); + }; +/** + * @namespace Util + * @memberof Konva + */ +export const Util = { + /* + * cherry-picked utilities from underscore.js + */ + _isElement(obj: any): obj is Element { + return !!(obj && obj.nodeType == 1); + }, + _isFunction(obj: any) { + return !!(obj && obj.constructor && obj.call && obj.apply); + }, + _isPlainObject(obj: any) { + return !!obj && obj.constructor === Object; + }, + _isArray(obj: any): obj is Array { + return Object.prototype.toString.call(obj) === OBJECT_ARRAY; + }, + _isNumber(obj: any): obj is number { + return ( + Object.prototype.toString.call(obj) === OBJECT_NUMBER && + !isNaN(obj) && + isFinite(obj) + ); + }, + _isString(obj: any): obj is string { + return Object.prototype.toString.call(obj) === OBJECT_STRING; + }, + _isBoolean(obj: any): obj is boolean { + return Object.prototype.toString.call(obj) === OBJECT_BOOLEAN; + }, + // arrays are objects too + isObject(val: any): val is object { + return val instanceof Object; + }, + isValidSelector(selector: any) { + if (typeof selector !== 'string') { + return false; + } + const firstChar = selector[0]; + return ( + firstChar === '#' || + firstChar === '.' || + firstChar === firstChar.toUpperCase() + ); + }, + _sign(number: number) { + if (number === 0) { + // that is not what sign usually returns + // but that is what we need + return 1; + } + if (number > 0) { + return 1; + } else { + return -1; + } + }, + + requestAnimFrame(callback: Function) { + animQueue.push(callback); + if (animQueue.length === 1) { + req(function () { + const queue = animQueue; + animQueue = []; + queue.forEach(function (cb) { + cb(); + }); + }); + } + }, + createCanvasElement() { + const canvas = document.createElement('canvas'); + // on some environments canvas.style is readonly + try { + (canvas).style = canvas.style || {}; + } catch (e) {} + return canvas; + }, + createImageElement() { + return document.createElement('img'); + }, + _isInDocument(el: any) { + while ((el = el.parentNode)) { + if (el == document) { + return true; + } + } + return false; + }, + + /* + * arg can be an image object or image data + */ + _urlToImage(url: string, callback: Function) { + // if arg is a string, then it's a data url + const imageObj = Util.createImageElement(); + imageObj.onload = function () { + callback(imageObj); + }; + imageObj.src = url; + }, + _rgbToHex(r: number, g: number, b: number) { + return ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1); + }, + _hexToRgb(hex: string): RGB { + hex = hex.replace(HASH, EMPTY_STRING); + const bigint = parseInt(hex, 16); + return { + r: (bigint >> 16) & 255, + g: (bigint >> 8) & 255, + b: bigint & 255, + }; + }, + /** + * return random hex color + * @method + * @memberof Konva.Util + * @example + * shape.fill(Konva.Util.getRandomColor()); + */ + getRandomColor() { + let randColor = ((Math.random() * 0xffffff) << 0).toString(16); + while (randColor.length < 6) { + randColor = ZERO + randColor; + } + return HASH + randColor; + }, + + /** + * get RGB components of a color + * @method + * @memberof Konva.Util + * @param {String} color + * @example + * // each of the following examples return {r:0, g:0, b:255} + * var rgb = Konva.Util.getRGB('blue'); + * var rgb = Konva.Util.getRGB('#0000ff'); + * var rgb = Konva.Util.getRGB('rgb(0,0,255)'); + */ + getRGB(color: string): RGB { + let rgb; + // color string + if (color in COLORS) { + rgb = COLORS[color as keyof typeof COLORS]; + return { + r: rgb[0], + g: rgb[1], + b: rgb[2], + }; + } else if (color[0] === HASH) { + // hex + return this._hexToRgb(color.substring(1)); + } else if (color.substr(0, 4) === RGB_PAREN) { + // rgb string + rgb = RGB_REGEX.exec(color.replace(/ /g, '')) as RegExpExecArray; + return { + r: parseInt(rgb[1], 10), + g: parseInt(rgb[2], 10), + b: parseInt(rgb[3], 10), + }; + } else { + // default + return { + r: 0, + g: 0, + b: 0, + }; + } + }, + // convert any color string to RGBA object + // from https://github.com/component/color-parser + colorToRGBA(str: string) { + str = str || 'black'; + return ( + Util._namedColorToRBA(str) || + Util._hex3ColorToRGBA(str) || + Util._hex4ColorToRGBA(str) || + Util._hex6ColorToRGBA(str) || + Util._hex8ColorToRGBA(str) || + Util._rgbColorToRGBA(str) || + Util._rgbaColorToRGBA(str) || + Util._hslColorToRGBA(str) + ); + }, + // Parse named css color. Like "green" + _namedColorToRBA(str: string) { + const c = COLORS[str.toLowerCase() as keyof typeof COLORS]; + if (!c) { + return null; + } + return { + r: c[0], + g: c[1], + b: c[2], + a: 1, + }; + }, + // Parse rgb(n, n, n) + _rgbColorToRGBA(str: string) { + if (str.indexOf('rgb(') === 0) { + str = str.match(/rgb\(([^)]+)\)/)![1]; + const parts = str.split(/ *, */).map(Number); + return { + r: parts[0], + g: parts[1], + b: parts[2], + a: 1, + }; + } + }, + // Parse rgba(n, n, n, n) + _rgbaColorToRGBA(str: string) { + if (str.indexOf('rgba(') === 0) { + str = str.match(/rgba\(([^)]+)\)/)![1]!; + const parts = str.split(/ *, */).map((n, index) => { + if (n.slice(-1) === '%') { + return index === 3 ? parseInt(n) / 100 : (parseInt(n) / 100) * 255; + } + return Number(n); + }); + return { + r: parts[0], + g: parts[1], + b: parts[2], + a: parts[3], + }; + } + }, + // Parse #nnnnnnnn + _hex8ColorToRGBA(str: string) { + if (str[0] === '#' && str.length === 9) { + return { + r: parseInt(str.slice(1, 3), 16), + g: parseInt(str.slice(3, 5), 16), + b: parseInt(str.slice(5, 7), 16), + a: parseInt(str.slice(7, 9), 16) / 0xff, + }; + } + }, + // Parse #nnnnnn + _hex6ColorToRGBA(str: string) { + if (str[0] === '#' && str.length === 7) { + return { + r: parseInt(str.slice(1, 3), 16), + g: parseInt(str.slice(3, 5), 16), + b: parseInt(str.slice(5, 7), 16), + a: 1, + }; + } + }, + // Parse #nnnn + _hex4ColorToRGBA(str: string) { + if (str[0] === '#' && str.length === 5) { + return { + r: parseInt(str[1] + str[1], 16), + g: parseInt(str[2] + str[2], 16), + b: parseInt(str[3] + str[3], 16), + a: parseInt(str[4] + str[4], 16) / 0xff, + }; + } + }, + // Parse #nnn + _hex3ColorToRGBA(str: string) { + if (str[0] === '#' && str.length === 4) { + return { + r: parseInt(str[1] + str[1], 16), + g: parseInt(str[2] + str[2], 16), + b: parseInt(str[3] + str[3], 16), + a: 1, + }; + } + }, + // Code adapted from https://github.com/Qix-/color-convert/blob/master/conversions.js#L244 + _hslColorToRGBA(str: string) { + // Check hsl() format + if (/hsl\((\d+),\s*([\d.]+)%,\s*([\d.]+)%\)/g.test(str)) { + // Extract h, s, l + const [_, ...hsl] = /hsl\((\d+),\s*([\d.]+)%,\s*([\d.]+)%\)/g.exec(str)!; + + const h = Number(hsl[0]) / 360; + const s = Number(hsl[1]) / 100; + const l = Number(hsl[2]) / 100; + + let t2; + let t3; + let val; + + if (s === 0) { + val = l * 255; + return { + r: Math.round(val), + g: Math.round(val), + b: Math.round(val), + a: 1, + }; + } + + if (l < 0.5) { + t2 = l * (1 + s); + } else { + t2 = l + s - l * s; + } + + const t1 = 2 * l - t2; + + const rgb = [0, 0, 0]; + for (let i = 0; i < 3; i++) { + t3 = h + (1 / 3) * -(i - 1); + if (t3 < 0) { + t3++; + } + + if (t3 > 1) { + t3--; + } + + if (6 * t3 < 1) { + val = t1 + (t2 - t1) * 6 * t3; + } else if (2 * t3 < 1) { + val = t2; + } else if (3 * t3 < 2) { + val = t1 + (t2 - t1) * (2 / 3 - t3) * 6; + } else { + val = t1; + } + + rgb[i] = val * 255; + } + + return { + r: Math.round(rgb[0]), + g: Math.round(rgb[1]), + b: Math.round(rgb[2]), + a: 1, + }; + } + }, + /** + * check intersection of two client rectangles + * @method + * @memberof Konva.Util + * @param {Object} r1 - { x, y, width, height } client rectangle + * @param {Object} r2 - { x, y, width, height } client rectangle + * @example + * const overlapping = Konva.Util.haveIntersection(shape1.getClientRect(), shape2.getClientRect()); + */ + haveIntersection(r1: IRect, r2: IRect) { + return !( + r2.x > r1.x + r1.width || + r2.x + r2.width < r1.x || + r2.y > r1.y + r1.height || + r2.y + r2.height < r1.y + ); + }, + cloneObject(obj: Any): Any { + const retObj: any = {}; + for (const key in obj) { + if (this._isPlainObject(obj[key])) { + retObj[key] = this.cloneObject(obj[key]); + } else if (this._isArray(obj[key])) { + retObj[key] = this.cloneArray(obj[key] as Array); + } else { + retObj[key] = obj[key]; + } + } + return retObj; + }, + cloneArray(arr: Array) { + return arr.slice(0); + }, + degToRad(deg: number) { + return deg * PI_OVER_DEG180; + }, + radToDeg(rad: number) { + return rad * DEG180_OVER_PI; + }, + _degToRad(deg: number) { + Util.warn( + 'Util._degToRad is removed. Please use public Util.degToRad instead.' + ); + return Util.degToRad(deg); + }, + _radToDeg(rad: number) { + Util.warn( + 'Util._radToDeg is removed. Please use public Util.radToDeg instead.' + ); + return Util.radToDeg(rad); + }, + _getRotation(radians: number) { + return Konva.angleDeg ? Util.radToDeg(radians) : radians; + }, + _capitalize(str: string) { + return str.charAt(0).toUpperCase() + str.slice(1); + }, + throw(str: string) { + throw new Error(KONVA_ERROR + str); + }, + error(str: string) { + console.error(KONVA_ERROR + str); + }, + warn(str: string) { + if (!Konva.showWarnings) { + return; + } + console.warn(KONVA_WARNING + str); + }, + each(obj: object, func: Function) { + for (const key in obj) { + func(key, obj[key as keyof typeof obj]); + } + }, + _inRange(val: number, left: number, right: number) { + return left <= val && val < right; + }, + _getProjectionToSegment(x1, y1, x2, y2, x3, y3) { + let x, y, dist; + + const pd2 = (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2); + if (pd2 == 0) { + x = x1; + y = y1; + dist = (x3 - x2) * (x3 - x2) + (y3 - y2) * (y3 - y2); + } else { + const u = ((x3 - x1) * (x2 - x1) + (y3 - y1) * (y2 - y1)) / pd2; + if (u < 0) { + x = x1; + y = y1; + dist = (x1 - x3) * (x1 - x3) + (y1 - y3) * (y1 - y3); + } else if (u > 1.0) { + x = x2; + y = y2; + dist = (x2 - x3) * (x2 - x3) + (y2 - y3) * (y2 - y3); + } else { + x = x1 + u * (x2 - x1); + y = y1 + u * (y2 - y1); + dist = (x - x3) * (x - x3) + (y - y3) * (y - y3); + } + } + return [x, y, dist]; + }, + // line as array of points. + // line might be closed + _getProjectionToLine(pt: Vector2d, line: Array, isClosed: boolean) { + const pc = Util.cloneObject(pt); + let dist = Number.MAX_VALUE; + line.forEach(function (p1, i) { + if (!isClosed && i === line.length - 1) { + return; + } + const p2 = line[(i + 1) % line.length]; + const proj = Util._getProjectionToSegment( + p1.x, + p1.y, + p2.x, + p2.y, + pt.x, + pt.y + ); + const px = proj[0], + py = proj[1], + pdist = proj[2]; + if (pdist < dist) { + pc.x = px; + pc.y = py; + dist = pdist; + } + }); + return pc; + }, + _prepareArrayForTween(startArray, endArray, isClosed) { + const start: Vector2d[] = [], + end: Vector2d[] = []; + if (startArray.length > endArray.length) { + const temp = endArray; + endArray = startArray; + startArray = temp; + } + for (let n = 0; n < startArray.length; n += 2) { + start.push({ + x: startArray[n], + y: startArray[n + 1], + }); + } + for (let n = 0; n < endArray.length; n += 2) { + end.push({ + x: endArray[n], + y: endArray[n + 1], + }); + } + + const newStart: number[] = []; + end.forEach(function (point) { + const pr = Util._getProjectionToLine(point, start, isClosed); + newStart.push(pr.x); + newStart.push(pr.y); + }); + return newStart; + }, + _prepareToStringify(obj: any): T | null { + let desc; + + obj.visitedByCircularReferenceRemoval = true; + + for (const key in obj) { + if ( + !(obj.hasOwnProperty(key) && obj[key] && typeof obj[key] == 'object') + ) { + continue; + } + desc = Object.getOwnPropertyDescriptor(obj, key); + if ( + obj[key].visitedByCircularReferenceRemoval || + Util._isElement(obj[key]) + ) { + if (desc.configurable) { + delete obj[key]; + } else { + return null; + } + } else if (Util._prepareToStringify(obj[key]) === null) { + if (desc.configurable) { + delete obj[key]; + } else { + return null; + } + } + } + + delete obj.visitedByCircularReferenceRemoval; + + return obj; + }, + // very simplified version of Object.assign + _assign(target: T, source: U) { + for (const key in source) { + (target)[key] = source[key]; + } + return target as T & U; + }, + _getFirstPointerId(evt) { + if (!evt.touches) { + // try to use pointer id or fake id + return evt.pointerId || 999; + } else { + return evt.changedTouches[0].identifier; + } + }, + releaseCanvas(...canvases: HTMLCanvasElement[]) { + if (!Konva.releaseCanvasOnDestroy) return; + + canvases.forEach((c) => { + c.width = 0; + c.height = 0; + }); + }, + drawRoundedRectPath( + context: Context, + width: number, + height: number, + cornerRadius: number | number[] + ) { + let topLeft = 0; + let topRight = 0; + let bottomLeft = 0; + let bottomRight = 0; + if (typeof cornerRadius === 'number') { + topLeft = + topRight = + bottomLeft = + bottomRight = + Math.min(cornerRadius, width / 2, height / 2); + } else { + topLeft = Math.min(cornerRadius[0] || 0, width / 2, height / 2); + topRight = Math.min(cornerRadius[1] || 0, width / 2, height / 2); + bottomRight = Math.min(cornerRadius[2] || 0, width / 2, height / 2); + bottomLeft = Math.min(cornerRadius[3] || 0, width / 2, height / 2); + } + context.moveTo(topLeft, 0); + context.lineTo(width - topRight, 0); + context.arc( + width - topRight, + topRight, + topRight, + (Math.PI * 3) / 2, + 0, + false + ); + context.lineTo(width, height - bottomRight); + context.arc( + width - bottomRight, + height - bottomRight, + bottomRight, + 0, + Math.PI / 2, + false + ); + context.lineTo(bottomLeft, height); + context.arc( + bottomLeft, + height - bottomLeft, + bottomLeft, + Math.PI / 2, + Math.PI, + false + ); + context.lineTo(0, topLeft); + context.arc(topLeft, topLeft, topLeft, Math.PI, (Math.PI * 3) / 2, false); + }, +}; diff --git a/src/Validators.ts b/src/Validators.ts new file mode 100644 index 000000000..a8534b3b9 --- /dev/null +++ b/src/Validators.ts @@ -0,0 +1,210 @@ +import { Konva } from './Global'; +import { Util } from './Util'; + +function _formatValue(val: any) { + if (Util._isString(val)) { + return '"' + val + '"'; + } + if (Object.prototype.toString.call(val) === '[object Number]') { + return val; + } + if (Util._isBoolean(val)) { + return val; + } + return Object.prototype.toString.call(val); +} + +export function RGBComponent(val: number) { + if (val > 255) { + return 255; + } else if (val < 0) { + return 0; + } + return Math.round(val); +} +export function alphaComponent(val: number) { + if (val > 1) { + return 1; + } else if (val < 0.0001) { + // chrome does not honor alpha values of 0 + return 0.0001; + } + + return val; +} + +export function getNumberValidator() { + if (Konva.isUnminified) { + return function (val: T, attr: string): T { + if (!Util._isNumber(val)) { + Util.warn( + _formatValue(val) + + ' is a not valid value for "' + + attr + + '" attribute. The value should be a number.' + ); + } + return val; + }; + } +} + +export function getNumberOrArrayOfNumbersValidator(noOfElements: number) { + if (Konva.isUnminified) { + return function (val: T, attr: string): T { + let isNumber = Util._isNumber(val); + let isValidArray = Util._isArray(val) && val.length == noOfElements; + if (!isNumber && !isValidArray) { + Util.warn( + _formatValue(val) + + ' is a not valid value for "' + + attr + + '" attribute. The value should be a number or Array(' + + noOfElements + + ')' + ); + } + return val; + }; + } +} + +export function getNumberOrAutoValidator() { + if (Konva.isUnminified) { + return function (val: T, attr: string): T { + var isNumber = Util._isNumber(val); + var isAuto = val === 'auto'; + + if (!(isNumber || isAuto)) { + Util.warn( + _formatValue(val) + + ' is a not valid value for "' + + attr + + '" attribute. The value should be a number or "auto".' + ); + } + return val; + }; + } +} + +export function getStringValidator() { + if (Konva.isUnminified) { + return function (val: T, attr: string): T { + if (!Util._isString(val)) { + Util.warn( + _formatValue(val) + + ' is a not valid value for "' + + attr + + '" attribute. The value should be a string.' + ); + } + return val; + }; + } +} + +export function getStringOrGradientValidator() { + if (Konva.isUnminified) { + return function (val: T, attr: string): T { + const isString = Util._isString(val); + const isGradient = + Object.prototype.toString.call(val) === '[object CanvasGradient]' || + (val && val['addColorStop']); + if (!(isString || isGradient)) { + Util.warn( + _formatValue(val) + + ' is a not valid value for "' + + attr + + '" attribute. The value should be a string or a native gradient.' + ); + } + return val; + }; + } +} + +export function getFunctionValidator() { + if (Konva.isUnminified) { + return function (val: T, attr: string): T { + if (!Util._isFunction(val)) { + Util.warn( + _formatValue(val) + + ' is a not valid value for "' + + attr + + '" attribute. The value should be a function.' + ); + } + return val; + }; + } +} +export function getNumberArrayValidator() { + if (Konva.isUnminified) { + return function (val: T, attr: string): T { + // Retrieve TypedArray constructor as found in MDN (if TypedArray is available) + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray#description + const TypedArray = Int8Array ? Object.getPrototypeOf(Int8Array) : null; + if (TypedArray && val instanceof TypedArray) { + return val; + } + if (!Util._isArray(val)) { + Util.warn( + _formatValue(val) + + ' is a not valid value for "' + + attr + + '" attribute. The value should be a array of numbers.' + ); + } else { + val.forEach(function (item: any) { + if (!Util._isNumber(item)) { + Util.warn( + '"' + + attr + + '" attribute has non numeric element ' + + item + + '. Make sure that all elements are numbers.' + ); + } + }); + } + return val; + }; + } +} +export function getBooleanValidator() { + if (Konva.isUnminified) { + return function (val: T, attr: string): T { + var isBool = val === true || val === false; + if (!isBool) { + Util.warn( + _formatValue(val) + + ' is a not valid value for "' + + attr + + '" attribute. The value should be a boolean.' + ); + } + return val; + }; + } +} +export function getComponentValidator(components: string[]) { + if (Konva.isUnminified) { + return function (val: T, attr: string): T { + // ignore validation on undefined value, because it will reset to defalt + if (val === undefined || val === null) { + return val; + } + if (!Util.isObject(val)) { + Util.warn( + _formatValue(val) + + ' is a not valid value for "' + + attr + + '" attribute. The value should be an object with properties ' + + components + ); + } + return val; + }; + } +} diff --git a/src/_CoreInternals.ts b/src/_CoreInternals.ts new file mode 100644 index 000000000..9744e3a06 --- /dev/null +++ b/src/_CoreInternals.ts @@ -0,0 +1,45 @@ +// what is core parts of Konva? +import { Konva as Global } from './Global'; + +import { Util, Transform } from './Util'; +import { Node } from './Node'; +import { Container } from './Container'; + +import { Stage, stages } from './Stage'; + +import { Layer } from './Layer'; +import { FastLayer } from './FastLayer'; + +import { Group } from './Group'; + +import { DD } from './DragAndDrop'; + +import { Shape, shapes } from './Shape'; + +import { Animation } from './Animation'; +import { Tween, Easings } from './Tween'; + +import { Context } from './Context'; +import { Canvas } from './Canvas'; + +export const Konva = Util._assign(Global, { + Util, + Transform, + Node, + Container, + Stage, + stages, + Layer, + FastLayer, + Group, + DD, + Shape, + shapes, + Animation, + Tween, + Easings, + Context, + Canvas, +}); + +export default Konva; diff --git a/src/_FullInternals.ts b/src/_FullInternals.ts new file mode 100644 index 000000000..d257f4cd8 --- /dev/null +++ b/src/_FullInternals.ts @@ -0,0 +1,89 @@ +// we need to import core of the Konva and then extend it with all additional objects + +import { Konva as Core } from './_CoreInternals'; + +// shapes +import { Arc } from './shapes/Arc'; +import { Arrow } from './shapes/Arrow'; +import { Circle } from './shapes/Circle'; +import { Ellipse } from './shapes/Ellipse'; +import { Image } from './shapes/Image'; +import { Label, Tag } from './shapes/Label'; +import { Line } from './shapes/Line'; +import { Path } from './shapes/Path'; +import { Rect } from './shapes/Rect'; +import { RegularPolygon } from './shapes/RegularPolygon'; +import { Ring } from './shapes/Ring'; +import { Sprite } from './shapes/Sprite'; +import { Star } from './shapes/Star'; +import { Text } from './shapes/Text'; +import { TextPath } from './shapes/TextPath'; +import { Transformer } from './shapes/Transformer'; +import { Wedge } from './shapes/Wedge'; + +// filters +import { Blur } from './filters/Blur'; +import { Brighten } from './filters/Brighten'; +import { Contrast } from './filters/Contrast'; +import { Emboss } from './filters/Emboss'; +import { Enhance } from './filters/Enhance'; +import { Grayscale } from './filters/Grayscale'; +import { HSL } from './filters/HSL'; +import { HSV } from './filters/HSV'; +import { Invert } from './filters/Invert'; +import { Kaleidoscope } from './filters/Kaleidoscope'; +import { Mask } from './filters/Mask'; +import { Noise } from './filters/Noise'; +import { Pixelate } from './filters/Pixelate'; +import { Posterize } from './filters/Posterize'; +import { RGB } from './filters/RGB'; +import { RGBA } from './filters/RGBA'; +import { Sepia } from './filters/Sepia'; +import { Solarize } from './filters/Solarize'; +import { Threshold } from './filters/Threshold'; + +export const Konva = Core.Util._assign(Core, { + Arc, + Arrow, + Circle, + Ellipse, + Image, + Label, + Tag, + Line, + Path, + Rect, + RegularPolygon, + Ring, + Sprite, + Star, + Text, + TextPath, + Transformer, + Wedge, + /** + * @namespace Filters + * @memberof Konva + */ + Filters: { + Blur, + Brighten, + Contrast, + Emboss, + Enhance, + Grayscale, + HSL, + HSV, + Invert, + Kaleidoscope, + Mask, + Noise, + Pixelate, + Posterize, + RGB, + RGBA, + Sepia, + Solarize, + Threshold, + }, +}); diff --git a/src/filters/Blur.ts b/src/filters/Blur.ts new file mode 100644 index 000000000..4998e8e7e --- /dev/null +++ b/src/filters/Blur.ts @@ -0,0 +1,390 @@ +import { Factory } from '../Factory'; +import { Node, Filter } from '../Node'; +import { getNumberValidator } from '../Validators'; +/* + the Gauss filter + master repo: https://github.com/pavelpower/kineticjsGaussFilter +*/ +/* + + StackBlur - a fast almost Gaussian Blur For Canvas + + Version: 0.5 + Author: Mario Klingemann + Contact: mario@quasimondo.com + Website: http://www.quasimondo.com/StackBlurForCanvas + Twitter: @quasimondo + + In case you find this class useful - especially in commercial projects - + I am not totally unhappy for a small donation to my PayPal account + mario@quasimondo.de + + Or support me on flattr: + https://flattr.com/thing/72791/StackBlur-a-fast-almost-Gaussian-Blur-Effect-for-CanvasJavascript + + Copyright (c) 2010 Mario Klingemann + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + */ + +function BlurStack(this: any) { + this.r = 0; + this.g = 0; + this.b = 0; + this.a = 0; + this.next = null; +} + +const mul_table = [ + 512, 512, 456, 512, 328, 456, 335, 512, 405, 328, 271, 456, 388, 335, 292, + 512, 454, 405, 364, 328, 298, 271, 496, 456, 420, 388, 360, 335, 312, 292, + 273, 512, 482, 454, 428, 405, 383, 364, 345, 328, 312, 298, 284, 271, 259, + 496, 475, 456, 437, 420, 404, 388, 374, 360, 347, 335, 323, 312, 302, 292, + 282, 273, 265, 512, 497, 482, 468, 454, 441, 428, 417, 405, 394, 383, 373, + 364, 354, 345, 337, 328, 320, 312, 305, 298, 291, 284, 278, 271, 265, 259, + 507, 496, 485, 475, 465, 456, 446, 437, 428, 420, 412, 404, 396, 388, 381, + 374, 367, 360, 354, 347, 341, 335, 329, 323, 318, 312, 307, 302, 297, 292, + 287, 282, 278, 273, 269, 265, 261, 512, 505, 497, 489, 482, 475, 468, 461, + 454, 447, 441, 435, 428, 422, 417, 411, 405, 399, 394, 389, 383, 378, 373, + 368, 364, 359, 354, 350, 345, 341, 337, 332, 328, 324, 320, 316, 312, 309, + 305, 301, 298, 294, 291, 287, 284, 281, 278, 274, 271, 268, 265, 262, 259, + 257, 507, 501, 496, 491, 485, 480, 475, 470, 465, 460, 456, 451, 446, 442, + 437, 433, 428, 424, 420, 416, 412, 408, 404, 400, 396, 392, 388, 385, 381, + 377, 374, 370, 367, 363, 360, 357, 354, 350, 347, 344, 341, 338, 335, 332, + 329, 326, 323, 320, 318, 315, 312, 310, 307, 304, 302, 299, 297, 294, 292, + 289, 287, 285, 282, 280, 278, 275, 273, 271, 269, 267, 265, 263, 261, 259, +]; + +const shg_table = [ + 9, 11, 12, 13, 13, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 17, 17, 17, 17, 17, + 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 18, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, +]; + +function filterGaussBlurRGBA(imageData, radius) { + const pixels = imageData.data, + width = imageData.width, + height = imageData.height; + + let x, + y, + i, + p, + yp, + yi, + yw, + r_sum, + g_sum, + b_sum, + a_sum, + r_out_sum, + g_out_sum, + b_out_sum, + a_out_sum, + r_in_sum, + g_in_sum, + b_in_sum, + a_in_sum, + pr, + pg, + pb, + pa, + rbs; + + const div = radius + radius + 1, + widthMinus1 = width - 1, + heightMinus1 = height - 1, + radiusPlus1 = radius + 1, + sumFactor = (radiusPlus1 * (radiusPlus1 + 1)) / 2, + stackStart = new BlurStack(), + mul_sum = mul_table[radius], + shg_sum = shg_table[radius]; + + let stackEnd = null, + stack = stackStart, + stackIn: any = null, + stackOut: any = null; + + for (i = 1; i < div; i++) { + stack = stack.next = new BlurStack(); + if (i === radiusPlus1) { + stackEnd = stack; + } + } + + stack.next = stackStart; + + yw = yi = 0; + + for (y = 0; y < height; y++) { + r_in_sum = + g_in_sum = + b_in_sum = + a_in_sum = + r_sum = + g_sum = + b_sum = + a_sum = + 0; + + r_out_sum = radiusPlus1 * (pr = pixels[yi]); + g_out_sum = radiusPlus1 * (pg = pixels[yi + 1]); + b_out_sum = radiusPlus1 * (pb = pixels[yi + 2]); + a_out_sum = radiusPlus1 * (pa = pixels[yi + 3]); + + r_sum += sumFactor * pr; + g_sum += sumFactor * pg; + b_sum += sumFactor * pb; + a_sum += sumFactor * pa; + + stack = stackStart; + + for (i = 0; i < radiusPlus1; i++) { + stack.r = pr; + stack.g = pg; + stack.b = pb; + stack.a = pa; + stack = stack.next; + } + + for (i = 1; i < radiusPlus1; i++) { + p = yi + ((widthMinus1 < i ? widthMinus1 : i) << 2); + r_sum += (stack.r = pr = pixels[p]) * (rbs = radiusPlus1 - i); + g_sum += (stack.g = pg = pixels[p + 1]) * rbs; + b_sum += (stack.b = pb = pixels[p + 2]) * rbs; + a_sum += (stack.a = pa = pixels[p + 3]) * rbs; + + r_in_sum += pr; + g_in_sum += pg; + b_in_sum += pb; + a_in_sum += pa; + + stack = stack.next; + } + + stackIn = stackStart; + stackOut = stackEnd; + for (x = 0; x < width; x++) { + pixels[yi + 3] = pa = (a_sum * mul_sum) >> shg_sum; + if (pa !== 0) { + pa = 255 / pa; + pixels[yi] = ((r_sum * mul_sum) >> shg_sum) * pa; + pixels[yi + 1] = ((g_sum * mul_sum) >> shg_sum) * pa; + pixels[yi + 2] = ((b_sum * mul_sum) >> shg_sum) * pa; + } else { + pixels[yi] = pixels[yi + 1] = pixels[yi + 2] = 0; + } + + r_sum -= r_out_sum; + g_sum -= g_out_sum; + b_sum -= b_out_sum; + a_sum -= a_out_sum; + + r_out_sum -= stackIn.r; + g_out_sum -= stackIn.g; + b_out_sum -= stackIn.b; + a_out_sum -= stackIn.a; + + p = (yw + ((p = x + radius + 1) < widthMinus1 ? p : widthMinus1)) << 2; + + r_in_sum += stackIn.r = pixels[p]; + g_in_sum += stackIn.g = pixels[p + 1]; + b_in_sum += stackIn.b = pixels[p + 2]; + a_in_sum += stackIn.a = pixels[p + 3]; + + r_sum += r_in_sum; + g_sum += g_in_sum; + b_sum += b_in_sum; + a_sum += a_in_sum; + + stackIn = stackIn.next; + + r_out_sum += pr = stackOut.r; + g_out_sum += pg = stackOut.g; + b_out_sum += pb = stackOut.b; + a_out_sum += pa = stackOut.a; + + r_in_sum -= pr; + g_in_sum -= pg; + b_in_sum -= pb; + a_in_sum -= pa; + + stackOut = stackOut.next; + + yi += 4; + } + yw += width; + } + + for (x = 0; x < width; x++) { + g_in_sum = + b_in_sum = + a_in_sum = + r_in_sum = + g_sum = + b_sum = + a_sum = + r_sum = + 0; + + yi = x << 2; + r_out_sum = radiusPlus1 * (pr = pixels[yi]); + g_out_sum = radiusPlus1 * (pg = pixels[yi + 1]); + b_out_sum = radiusPlus1 * (pb = pixels[yi + 2]); + a_out_sum = radiusPlus1 * (pa = pixels[yi + 3]); + + r_sum += sumFactor * pr; + g_sum += sumFactor * pg; + b_sum += sumFactor * pb; + a_sum += sumFactor * pa; + + stack = stackStart; + + for (i = 0; i < radiusPlus1; i++) { + stack.r = pr; + stack.g = pg; + stack.b = pb; + stack.a = pa; + stack = stack.next; + } + + yp = width; + + for (i = 1; i <= radius; i++) { + yi = (yp + x) << 2; + + r_sum += (stack.r = pr = pixels[yi]) * (rbs = radiusPlus1 - i); + g_sum += (stack.g = pg = pixels[yi + 1]) * rbs; + b_sum += (stack.b = pb = pixels[yi + 2]) * rbs; + a_sum += (stack.a = pa = pixels[yi + 3]) * rbs; + + r_in_sum += pr; + g_in_sum += pg; + b_in_sum += pb; + a_in_sum += pa; + + stack = stack.next; + + if (i < heightMinus1) { + yp += width; + } + } + + yi = x; + stackIn = stackStart; + stackOut = stackEnd; + for (y = 0; y < height; y++) { + p = yi << 2; + pixels[p + 3] = pa = (a_sum * mul_sum) >> shg_sum; + if (pa > 0) { + pa = 255 / pa; + pixels[p] = ((r_sum * mul_sum) >> shg_sum) * pa; + pixels[p + 1] = ((g_sum * mul_sum) >> shg_sum) * pa; + pixels[p + 2] = ((b_sum * mul_sum) >> shg_sum) * pa; + } else { + pixels[p] = pixels[p + 1] = pixels[p + 2] = 0; + } + + r_sum -= r_out_sum; + g_sum -= g_out_sum; + b_sum -= b_out_sum; + a_sum -= a_out_sum; + + r_out_sum -= stackIn.r; + g_out_sum -= stackIn.g; + b_out_sum -= stackIn.b; + a_out_sum -= stackIn.a; + + p = + (x + + ((p = y + radiusPlus1) < heightMinus1 ? p : heightMinus1) * width) << + 2; + + r_sum += r_in_sum += stackIn.r = pixels[p]; + g_sum += g_in_sum += stackIn.g = pixels[p + 1]; + b_sum += b_in_sum += stackIn.b = pixels[p + 2]; + a_sum += a_in_sum += stackIn.a = pixels[p + 3]; + + stackIn = stackIn.next; + + r_out_sum += pr = stackOut.r; + g_out_sum += pg = stackOut.g; + b_out_sum += pb = stackOut.b; + a_out_sum += pa = stackOut.a; + + r_in_sum -= pr; + g_in_sum -= pg; + b_in_sum -= pb; + a_in_sum -= pa; + + stackOut = stackOut.next; + + yi += width; + } + } +} + +/** + * Blur Filter + * @function + * @name Blur + * @memberof Konva.Filters + * @param {Object} imageData + * @example + * node.cache(); + * node.filters([Konva.Filters.Blur]); + * node.blurRadius(10); + */ +export const Blur: Filter = function Blur(imageData) { + const radius = Math.round(this.blurRadius()); + + if (radius > 0) { + filterGaussBlurRGBA(imageData, radius); + } +}; + +Factory.addGetterSetter( + Node, + 'blurRadius', + 0, + getNumberValidator(), + Factory.afterSetFilter +); + +/** + * get/set blur radius. Use with {@link Konva.Filters.Blur} filter + * @name Konva.Node#blurRadius + * @method + * @param {Integer} radius + * @returns {Integer} + */ diff --git a/src/filters/Brighten.ts b/src/filters/Brighten.ts new file mode 100644 index 000000000..d7d981b7a --- /dev/null +++ b/src/filters/Brighten.ts @@ -0,0 +1,45 @@ +import { Factory } from '../Factory'; +import { Node, Filter } from '../Node'; +import { getNumberValidator } from '../Validators'; + +/** + * Brighten Filter. + * @function + * @memberof Konva.Filters + * @param {Object} imageData + * @example + * node.cache(); + * node.filters([Konva.Filters.Brighten]); + * node.brightness(0.8); + */ +export const Brighten: Filter = function (imageData) { + const brightness = this.brightness() * 255, + data = imageData.data, + len = data.length; + + for (let i = 0; i < len; i += 4) { + // red + data[i] += brightness; + // green + data[i + 1] += brightness; + // blue + data[i + 2] += brightness; + } +}; + +Factory.addGetterSetter( + Node, + 'brightness', + 0, + getNumberValidator(), + Factory.afterSetFilter +); +/** + * get/set filter brightness. The brightness is a number between -1 and 1.  Positive values + * brighten the pixels and negative values darken them. Use with {@link Konva.Filters.Brighten} filter. + * @name Konva.Node#brightness + * @method + + * @param {Number} brightness value between -1 and 1 + * @returns {Number} + */ diff --git a/src/filters/Contrast.ts b/src/filters/Contrast.ts new file mode 100644 index 000000000..80ec38b40 --- /dev/null +++ b/src/filters/Contrast.ts @@ -0,0 +1,74 @@ +import { Factory } from '../Factory'; +import { Node, Filter } from '../Node'; +import { getNumberValidator } from '../Validators'; +/** + * Contrast Filter. + * @function + * @memberof Konva.Filters + * @param {Object} imageData + * @example + * node.cache(); + * node.filters([Konva.Filters.Contrast]); + * node.contrast(10); + */ + +export const Contrast: Filter = function (imageData) { + const adjust = Math.pow((this.contrast() + 100) / 100, 2); + + const data = imageData.data, + nPixels = data.length; + let red = 150, + green = 150, + blue = 150; + + for (let i = 0; i < nPixels; i += 4) { + red = data[i]; + green = data[i + 1]; + blue = data[i + 2]; + + //Red channel + red /= 255; + red -= 0.5; + red *= adjust; + red += 0.5; + red *= 255; + + //Green channel + green /= 255; + green -= 0.5; + green *= adjust; + green += 0.5; + green *= 255; + + //Blue channel + blue /= 255; + blue -= 0.5; + blue *= adjust; + blue += 0.5; + blue *= 255; + + red = red < 0 ? 0 : red > 255 ? 255 : red; + green = green < 0 ? 0 : green > 255 ? 255 : green; + blue = blue < 0 ? 0 : blue > 255 ? 255 : blue; + + data[i] = red; + data[i + 1] = green; + data[i + 2] = blue; + } +}; + +/** + * get/set filter contrast. The contrast is a number between -100 and 100. + * Use with {@link Konva.Filters.Contrast} filter. + * @name Konva.Node#contrast + * @method + * @param {Number} contrast value between -100 and 100 + * @returns {Number} + */ +Factory.addGetterSetter( + Node, + 'contrast', + 0, + getNumberValidator(), + Factory.afterSetFilter +); diff --git a/src/filters/Emboss.ts b/src/filters/Emboss.ts new file mode 100644 index 000000000..a040cc8f4 --- /dev/null +++ b/src/filters/Emboss.ts @@ -0,0 +1,202 @@ +import { Factory } from '../Factory'; +import { Node, Filter } from '../Node'; +import { Util } from '../Util'; +import { getNumberValidator } from '../Validators'; +/** + * Emboss Filter. + * Pixastic Lib - Emboss filter - v0.1.0 + * Copyright (c) 2008 Jacob Seidelin, jseidelin@nihilogic.dk, http://blog.nihilogic.dk/ + * License: [http://www.pixastic.com/lib/license.txt] + * @function + * @memberof Konva.Filters + * @param {Object} imageData + * @example + * node.cache(); + * node.filters([Konva.Filters.Emboss]); + * node.embossStrength(0.8); + * node.embossWhiteLevel(0.3); + * node.embossDirection('right'); + * node.embossBlend(true); + */ +export const Emboss: Filter = function (imageData) { + // pixastic strength is between 0 and 10. I want it between 0 and 1 + // pixastic greyLevel is between 0 and 255. I want it between 0 and 1. Also, + // a max value of greyLevel yields a white emboss, and the min value yields a black + // emboss. Therefore, I changed greyLevel to whiteLevel + const strength = this.embossStrength() * 10, + greyLevel = this.embossWhiteLevel() * 255, + direction = this.embossDirection(), + blend = this.embossBlend(), + data = imageData.data, + w = imageData.width, + h = imageData.height, + w4 = w * 4; + let dirY = 0, + dirX = 0, + y = h; + + switch (direction) { + case 'top-left': + dirY = -1; + dirX = -1; + break; + case 'top': + dirY = -1; + dirX = 0; + break; + case 'top-right': + dirY = -1; + dirX = 1; + break; + case 'right': + dirY = 0; + dirX = 1; + break; + case 'bottom-right': + dirY = 1; + dirX = 1; + break; + case 'bottom': + dirY = 1; + dirX = 0; + break; + case 'bottom-left': + dirY = 1; + dirX = -1; + break; + case 'left': + dirY = 0; + dirX = -1; + break; + default: + Util.error('Unknown emboss direction: ' + direction); + } + + do { + const offsetY = (y - 1) * w4; + + let otherY = dirY; + if (y + otherY < 1) { + otherY = 0; + } + if (y + otherY > h) { + otherY = 0; + } + + const offsetYOther = (y - 1 + otherY) * w * 4; + + let x = w; + do { + const offset = offsetY + (x - 1) * 4; + + let otherX = dirX; + if (x + otherX < 1) { + otherX = 0; + } + if (x + otherX > w) { + otherX = 0; + } + + const offsetOther = offsetYOther + (x - 1 + otherX) * 4; + + const dR = data[offset] - data[offsetOther]; + const dG = data[offset + 1] - data[offsetOther + 1]; + const dB = data[offset + 2] - data[offsetOther + 2]; + + let dif = dR; + const absDif = dif > 0 ? dif : -dif; + + const absG = dG > 0 ? dG : -dG; + const absB = dB > 0 ? dB : -dB; + + if (absG > absDif) { + dif = dG; + } + if (absB > absDif) { + dif = dB; + } + + dif *= strength; + + if (blend) { + const r = data[offset] + dif; + const g = data[offset + 1] + dif; + const b = data[offset + 2] + dif; + + data[offset] = r > 255 ? 255 : r < 0 ? 0 : r; + data[offset + 1] = g > 255 ? 255 : g < 0 ? 0 : g; + data[offset + 2] = b > 255 ? 255 : b < 0 ? 0 : b; + } else { + let grey = greyLevel - dif; + if (grey < 0) { + grey = 0; + } else if (grey > 255) { + grey = 255; + } + + data[offset] = data[offset + 1] = data[offset + 2] = grey; + } + } while (--x); + } while (--y); +}; + +Factory.addGetterSetter( + Node, + 'embossStrength', + 0.5, + getNumberValidator(), + Factory.afterSetFilter +); +/** + * get/set emboss strength. Use with {@link Konva.Filters.Emboss} filter. + * @name Konva.Node#embossStrength + * @method + * @param {Number} level between 0 and 1. Default is 0.5 + * @returns {Number} + */ + +Factory.addGetterSetter( + Node, + 'embossWhiteLevel', + 0.5, + getNumberValidator(), + Factory.afterSetFilter +); +/** + * get/set emboss white level. Use with {@link Konva.Filters.Emboss} filter. + * @name Konva.Node#embossWhiteLevel + * @method + * @param {Number} embossWhiteLevel between 0 and 1. Default is 0.5 + * @returns {Number} + */ + +Factory.addGetterSetter( + Node, + 'embossDirection', + 'top-left', + undefined, + Factory.afterSetFilter +); +/** + * get/set emboss direction. Use with {@link Konva.Filters.Emboss} filter. + * @name Konva.Node#embossDirection + * @method + * @param {String} embossDirection can be top-left, top, top-right, right, bottom-right, bottom, bottom-left or left + * The default is top-left + * @returns {String} + */ + +Factory.addGetterSetter( + Node, + 'embossBlend', + false, + undefined, + Factory.afterSetFilter +); +/** + * get/set emboss blend. Use with {@link Konva.Filters.Emboss} filter. + * @name Konva.Node#embossBlend + * @method + * @param {Boolean} embossBlend + * @returns {Boolean} + */ diff --git a/src/filters/Enhance.ts b/src/filters/Enhance.ts new file mode 100644 index 000000000..d92d68344 --- /dev/null +++ b/src/filters/Enhance.ts @@ -0,0 +1,150 @@ +import { Factory } from '../Factory'; +import { Node, Filter } from '../Node'; +import { getNumberValidator } from '../Validators'; + +function remap(fromValue: number, fromMin: number, fromMax: number, toMin: number, toMax: number) { + // Compute the range of the data + const fromRange = fromMax - fromMin, + toRange = toMax - toMin; + + // If either range is 0, then the value can only be mapped to 1 value + if (fromRange === 0) { + return toMin + toRange / 2; + } + if (toRange === 0) { + return toMin; + } + + // (1) untranslate, (2) unscale, (3) rescale, (4) retranslate + let toValue = (fromValue - fromMin) / fromRange; + toValue = toRange * toValue + toMin; + + return toValue; +} + +/** + * Enhance Filter. Adjusts the colors so that they span the widest + * possible range (ie 0-255). Performs w*h pixel reads and w*h pixel + * writes. + * @function + * @name Enhance + * @memberof Konva.Filters + * @param {Object} imageData + * @author ippo615 + * @example + * node.cache(); + * node.filters([Konva.Filters.Enhance]); + * node.enhance(0.4); + */ +export const Enhance: Filter = function (imageData) { + const data = imageData.data, + nSubPixels = data.length; + let rMin = data[0], + rMax = rMin, + r, + gMin = data[1], + gMax = gMin, + g, + bMin = data[2], + bMax = bMin, + b; + + // If we are not enhancing anything - don't do any computation + const enhanceAmount = this.enhance(); + if (enhanceAmount === 0) { + return; + } + + // 1st Pass - find the min and max for each channel: + for (let i = 0; i < nSubPixels; i += 4) { + r = data[i + 0]; + if (r < rMin) { + rMin = r; + } else if (r > rMax) { + rMax = r; + } + g = data[i + 1]; + if (g < gMin) { + gMin = g; + } else if (g > gMax) { + gMax = g; + } + b = data[i + 2]; + if (b < bMin) { + bMin = b; + } else if (b > bMax) { + bMax = b; + } + //a = data[i + 3]; + //if (a < aMin) { aMin = a; } else + //if (a > aMax) { aMax = a; } + } + + // If there is only 1 level - don't remap + if (rMax === rMin) { + rMax = 255; + rMin = 0; + } + if (gMax === gMin) { + gMax = 255; + gMin = 0; + } + if (bMax === bMin) { + bMax = 255; + bMin = 0; + } + + let rMid, + rGoalMax, + rGoalMin, + gMid, + gGoalMax, + gGoalMin, + bMid, + bGoalMax, + bGoalMin; + + // If the enhancement is positive - stretch the histogram + if (enhanceAmount > 0) { + rGoalMax = rMax + enhanceAmount * (255 - rMax); + rGoalMin = rMin - enhanceAmount * (rMin - 0); + gGoalMax = gMax + enhanceAmount * (255 - gMax); + gGoalMin = gMin - enhanceAmount * (gMin - 0); + bGoalMax = bMax + enhanceAmount * (255 - bMax); + bGoalMin = bMin - enhanceAmount * (bMin - 0); + // If the enhancement is negative - compress the histogram + } else { + rMid = (rMax + rMin) * 0.5; + rGoalMax = rMax + enhanceAmount * (rMax - rMid); + rGoalMin = rMin + enhanceAmount * (rMin - rMid); + gMid = (gMax + gMin) * 0.5; + gGoalMax = gMax + enhanceAmount * (gMax - gMid); + gGoalMin = gMin + enhanceAmount * (gMin - gMid); + bMid = (bMax + bMin) * 0.5; + bGoalMax = bMax + enhanceAmount * (bMax - bMid); + bGoalMin = bMin + enhanceAmount * (bMin - bMid); + } + + // Pass 2 - remap everything, except the alpha + for (let i = 0; i < nSubPixels; i += 4) { + data[i + 0] = remap(data[i + 0], rMin, rMax, rGoalMin, rGoalMax); + data[i + 1] = remap(data[i + 1], gMin, gMax, gGoalMin, gGoalMax); + data[i + 2] = remap(data[i + 2], bMin, bMax, bGoalMin, bGoalMax); + //data[i + 3] = remap(data[i + 3], aMin, aMax, aGoalMin, aGoalMax); + } +}; + +/** + * get/set enhance. Use with {@link Konva.Filters.Enhance} filter. -1 to 1 values + * @name Konva.Node#enhance + * @method + * @param {Float} amount + * @returns {Float} + */ +Factory.addGetterSetter( + Node, + 'enhance', + 0, + getNumberValidator(), + Factory.afterSetFilter +); diff --git a/src/filters/Grayscale.ts b/src/filters/Grayscale.ts new file mode 100644 index 000000000..4457a95fb --- /dev/null +++ b/src/filters/Grayscale.ts @@ -0,0 +1,25 @@ +import { Filter } from '../Node'; + +/** + * Grayscale Filter + * @function + * @memberof Konva.Filters + * @param {Object} imageData + * @example + * node.cache(); + * node.filters([Konva.Filters.Grayscale]); + */ +export const Grayscale: Filter = function (imageData) { + const data = imageData.data, + len = data.length; + + for (let i = 0; i < len; i += 4) { + const brightness = 0.34 * data[i] + 0.5 * data[i + 1] + 0.16 * data[i + 2]; + // red + data[i] = brightness; + // green + data[i + 1] = brightness; + // blue + data[i + 2] = brightness; + } +}; diff --git a/src/filters/HSL.ts b/src/filters/HSL.ts new file mode 100644 index 000000000..47763ad2d --- /dev/null +++ b/src/filters/HSL.ts @@ -0,0 +1,107 @@ +import { Factory } from '../Factory'; +import { Node, Filter } from '../Node'; +import { getNumberValidator } from '../Validators'; + +Factory.addGetterSetter( + Node, + 'hue', + 0, + getNumberValidator(), + Factory.afterSetFilter +); +/** + * get/set hsv hue in degrees. Use with {@link Konva.Filters.HSV} or {@link Konva.Filters.HSL} filter. + * @name Konva.Node#hue + * @method + * @param {Number} hue value between 0 and 359 + * @returns {Number} + */ + +Factory.addGetterSetter( + Node, + 'saturation', + 0, + getNumberValidator(), + Factory.afterSetFilter +); +/** + * get/set hsv saturation. Use with {@link Konva.Filters.HSV} or {@link Konva.Filters.HSL} filter. + * @name Konva.Node#saturation + * @method + * @param {Number} saturation 0 is no change, -1.0 halves the saturation, 1.0 doubles, etc.. + * @returns {Number} + */ + +Factory.addGetterSetter( + Node, + 'luminance', + 0, + getNumberValidator(), + Factory.afterSetFilter +); +/** + * get/set hsl luminance. Use with {@link Konva.Filters.HSL} filter. + * @name Konva.Node#luminance + * @method + * @param {Number} value from -1 to 1 + * @returns {Number} + */ + +/** + * HSL Filter. Adjusts the hue, saturation and luminance (or lightness) + * @function + * @memberof Konva.Filters + * @param {Object} imageData + * @author ippo615 + * @example + * image.filters([Konva.Filters.HSL]); + * image.luminance(0.2); + */ + +export const HSL: Filter = function (imageData) { + const data = imageData.data, + nPixels = data.length, + v = 1, + s = Math.pow(2, this.saturation()), + h = Math.abs(this.hue() + 360) % 360, + l = this.luminance() * 127; + + // Basis for the technique used: + // http://beesbuzz.biz/code/hsv_color_transforms.php + // V is the value multiplier (1 for none, 2 for double, 0.5 for half) + // S is the saturation multiplier (1 for none, 2 for double, 0.5 for half) + // H is the hue shift in degrees (0 to 360) + // vsu = V*S*cos(H*PI/180); + // vsw = V*S*sin(H*PI/180); + //[ .299V+.701vsu+.168vsw .587V-.587vsu+.330vsw .114V-.114vsu-.497vsw ] [R] + //[ .299V-.299vsu-.328vsw .587V+.413vsu+.035vsw .114V-.114vsu+.292vsw ]*[G] + //[ .299V-.300vsu+1.25vsw .587V-.588vsu-1.05vsw .114V+.886vsu-.203vsw ] [B] + + // Precompute the values in the matrix: + const vsu = v * s * Math.cos((h * Math.PI) / 180), + vsw = v * s * Math.sin((h * Math.PI) / 180); + // (result spot)(source spot) + const rr = 0.299 * v + 0.701 * vsu + 0.167 * vsw, + rg = 0.587 * v - 0.587 * vsu + 0.33 * vsw, + rb = 0.114 * v - 0.114 * vsu - 0.497 * vsw; + const gr = 0.299 * v - 0.299 * vsu - 0.328 * vsw, + gg = 0.587 * v + 0.413 * vsu + 0.035 * vsw, + gb = 0.114 * v - 0.114 * vsu + 0.293 * vsw; + const br = 0.299 * v - 0.3 * vsu + 1.25 * vsw, + bg = 0.587 * v - 0.586 * vsu - 1.05 * vsw, + bb = 0.114 * v + 0.886 * vsu - 0.2 * vsw; + + let r: number, g: number, b: number, a: number; + + for (let i = 0; i < nPixels; i += 4) { + r = data[i + 0]; + g = data[i + 1]; + b = data[i + 2]; + a = data[i + 3]; + + data[i + 0] = rr * r + rg * g + rb * b + l; + data[i + 1] = gr * r + gg * g + gb * b + l; + data[i + 2] = br * r + bg * g + bb * b + l; + data[i + 3] = a; // alpha + } +}; diff --git a/src/filters/HSV.ts b/src/filters/HSV.ts new file mode 100644 index 000000000..855f66445 --- /dev/null +++ b/src/filters/HSV.ts @@ -0,0 +1,107 @@ +import { Factory } from '../Factory'; +import { Node, Filter } from '../Node'; +import { getNumberValidator } from '../Validators'; + +/** + * HSV Filter. Adjusts the hue, saturation and value + * @function + * @name HSV + * @memberof Konva.Filters + * @param {Object} imageData + * @author ippo615 + * @example + * image.filters([Konva.Filters.HSV]); + * image.value(200); + */ + +export const HSV: Filter = function (imageData) { + const data = imageData.data, + nPixels = data.length, + v = Math.pow(2, this.value()), + s = Math.pow(2, this.saturation()), + h = Math.abs(this.hue() + 360) % 360; + + // Basis for the technique used: + // http://beesbuzz.biz/code/hsv_color_transforms.php + // V is the value multiplier (1 for none, 2 for double, 0.5 for half) + // S is the saturation multiplier (1 for none, 2 for double, 0.5 for half) + // H is the hue shift in degrees (0 to 360) + // vsu = V*S*cos(H*PI/180); + // vsw = V*S*sin(H*PI/180); + //[ .299V+.701vsu+.168vsw .587V-.587vsu+.330vsw .114V-.114vsu-.497vsw ] [R] + //[ .299V-.299vsu-.328vsw .587V+.413vsu+.035vsw .114V-.114vsu+.292vsw ]*[G] + //[ .299V-.300vsu+1.25vsw .587V-.588vsu-1.05vsw .114V+.886vsu-.203vsw ] [B] + + // Precompute the values in the matrix: + const vsu = v * s * Math.cos((h * Math.PI) / 180), + vsw = v * s * Math.sin((h * Math.PI) / 180); + // (result spot)(source spot) + const rr = 0.299 * v + 0.701 * vsu + 0.167 * vsw, + rg = 0.587 * v - 0.587 * vsu + 0.33 * vsw, + rb = 0.114 * v - 0.114 * vsu - 0.497 * vsw; + const gr = 0.299 * v - 0.299 * vsu - 0.328 * vsw, + gg = 0.587 * v + 0.413 * vsu + 0.035 * vsw, + gb = 0.114 * v - 0.114 * vsu + 0.293 * vsw; + const br = 0.299 * v - 0.3 * vsu + 1.25 * vsw, + bg = 0.587 * v - 0.586 * vsu - 1.05 * vsw, + bb = 0.114 * v + 0.886 * vsu - 0.2 * vsw; + + let r, g, b, a; + + for (let i = 0; i < nPixels; i += 4) { + r = data[i + 0]; + g = data[i + 1]; + b = data[i + 2]; + a = data[i + 3]; + + data[i + 0] = rr * r + rg * g + rb * b; + data[i + 1] = gr * r + gg * g + gb * b; + data[i + 2] = br * r + bg * g + bb * b; + data[i + 3] = a; // alpha + } +}; + +Factory.addGetterSetter( + Node, + 'hue', + 0, + getNumberValidator(), + Factory.afterSetFilter +); +/** + * get/set hsv hue in degrees. Use with {@link Konva.Filters.HSV} or {@link Konva.Filters.HSL} filter. + * @name Konva.Node#hue + * @method + * @param {Number} hue value between 0 and 359 + * @returns {Number} + */ + +Factory.addGetterSetter( + Node, + 'saturation', + 0, + getNumberValidator(), + Factory.afterSetFilter +); +/** + * get/set hsv saturation. Use with {@link Konva.Filters.HSV} or {@link Konva.Filters.HSL} filter. + * @name Konva.Node#saturation + * @method + * @param {Number} saturation 0 is no change, -1.0 halves the saturation, 1.0 doubles, etc.. + * @returns {Number} + */ + +Factory.addGetterSetter( + Node, + 'value', + 0, + getNumberValidator(), + Factory.afterSetFilter +); +/** + * get/set hsv value. Use with {@link Konva.Filters.HSV} filter. + * @name Konva.Node#value + * @method + * @param {Number} value 0 is no change, -1.0 halves the value, 1.0 doubles, etc.. + * @returns {Number} + */ diff --git a/src/filters/Invert.ts b/src/filters/Invert.ts new file mode 100644 index 000000000..766c9506c --- /dev/null +++ b/src/filters/Invert.ts @@ -0,0 +1,23 @@ +import { Filter } from '../Node'; +/** + * Invert Filter + * @function + * @memberof Konva.Filters + * @param {Object} imageData + * @example + * node.cache(); + * node.filters([Konva.Filters.Invert]); + */ +export const Invert: Filter = function (imageData) { + const data = imageData.data, + len = data.length; + + for (let i = 0; i < len; i += 4) { + // red + data[i] = 255 - data[i]; + // green + data[i + 1] = 255 - data[i + 1]; + // blue + data[i + 2] = 255 - data[i + 2]; + } +}; diff --git a/src/filters/Kaleidoscope.ts b/src/filters/Kaleidoscope.ts new file mode 100644 index 000000000..bdca220c1 --- /dev/null +++ b/src/filters/Kaleidoscope.ts @@ -0,0 +1,272 @@ +import { Factory } from '../Factory'; +import { Filter, Node } from '../Node'; +import { Util } from '../Util'; +import { getNumberValidator } from '../Validators'; + +/* + * ToPolar Filter. Converts image data to polar coordinates. Performs + * w*h*4 pixel reads and w*h pixel writes. The r axis is placed along + * what would be the y axis and the theta axis along the x axis. + * @function + * @author ippo615 + * @memberof Konva.Filters + * @param {ImageData} src, the source image data (what will be transformed) + * @param {ImageData} dst, the destination image data (where it will be saved) + * @param {Object} opt + * @param {Number} [opt.polarCenterX] horizontal location for the center of the circle, + * default is in the middle + * @param {Number} [opt.polarCenterY] vertical location for the center of the circle, + * default is in the middle + */ + +const ToPolar = function (src, dst, opt) { + const srcPixels = src.data, + dstPixels = dst.data, + xSize = src.width, + ySize = src.height, + xMid = opt.polarCenterX || xSize / 2, + yMid = opt.polarCenterY || ySize / 2; + + // Find the largest radius + let rMax = Math.sqrt(xMid * xMid + yMid * yMid); + let x = xSize - xMid; + let y = ySize - yMid; + const rad = Math.sqrt(x * x + y * y); + rMax = rad > rMax ? rad : rMax; + + // We'll be uisng y as the radius, and x as the angle (theta=t) + const rSize = ySize, + tSize = xSize; + + // We want to cover all angles (0-360) and we need to convert to + // radians (*PI/180) + const conversion = ((360 / tSize) * Math.PI) / 180; + + // var x1, x2, x1i, x2i, y1, y2, y1i, y2i, scale; + + for (let theta = 0; theta < tSize; theta += 1) { + const sin = Math.sin(theta * conversion); + const cos = Math.cos(theta * conversion); + for (let radius = 0; radius < rSize; radius += 1) { + x = Math.floor(xMid + ((rMax * radius) / rSize) * cos); + y = Math.floor(yMid + ((rMax * radius) / rSize) * sin); + let i = (y * xSize + x) * 4; + const r = srcPixels[i + 0]; + const g = srcPixels[i + 1]; + const b = srcPixels[i + 2]; + const a = srcPixels[i + 3]; + + // Store it + //i = (theta * xSize + radius) * 4; + i = (theta + radius * xSize) * 4; + dstPixels[i + 0] = r; + dstPixels[i + 1] = g; + dstPixels[i + 2] = b; + dstPixels[i + 3] = a; + } + } +}; + +/* + * FromPolar Filter. Converts image data from polar coordinates back to rectangular. + * Performs w*h*4 pixel reads and w*h pixel writes. + * @function + * @author ippo615 + * @memberof Konva.Filters + * @param {ImageData} src, the source image data (what will be transformed) + * @param {ImageData} dst, the destination image data (where it will be saved) + * @param {Object} opt + * @param {Number} [opt.polarCenterX] horizontal location for the center of the circle, + * default is in the middle + * @param {Number} [opt.polarCenterY] vertical location for the center of the circle, + * default is in the middle + * @param {Number} [opt.polarRotation] amount to rotate the image counterclockwis, + * 0 is no rotation, 360 degrees is a full rotation + */ + +const FromPolar = function (src, dst, opt) { + const srcPixels = src.data, + dstPixels = dst.data, + xSize = src.width, + ySize = src.height, + xMid = opt.polarCenterX || xSize / 2, + yMid = opt.polarCenterY || ySize / 2; + + // Find the largest radius + let rMax = Math.sqrt(xMid * xMid + yMid * yMid); + let x = xSize - xMid; + let y = ySize - yMid; + const rad = Math.sqrt(x * x + y * y); + rMax = rad > rMax ? rad : rMax; + + // We'll be uisng x as the radius, and y as the angle (theta=t) + const rSize = ySize, + tSize = xSize, + phaseShift = opt.polarRotation || 0; + + // We need to convert to degrees and we need to make sure + // it's between (0-360) + // var conversion = tSize/360*180/Math.PI; + //var conversion = tSize/360*180/Math.PI; + + let x1, y1; + + for (x = 0; x < xSize; x += 1) { + for (y = 0; y < ySize; y += 1) { + const dx = x - xMid; + const dy = y - yMid; + const radius = (Math.sqrt(dx * dx + dy * dy) * rSize) / rMax; + let theta = ((Math.atan2(dy, dx) * 180) / Math.PI + 360 + phaseShift) % 360; + theta = (theta * tSize) / 360; + x1 = Math.floor(theta); + y1 = Math.floor(radius); + let i = (y1 * xSize + x1) * 4; + const r = srcPixels[i + 0]; + const g = srcPixels[i + 1]; + const b = srcPixels[i + 2]; + const a = srcPixels[i + 3]; + + // Store it + i = (y * xSize + x) * 4; + dstPixels[i + 0] = r; + dstPixels[i + 1] = g; + dstPixels[i + 2] = b; + dstPixels[i + 3] = a; + } + } +}; + +//Konva.Filters.ToPolar = Util._FilterWrapDoubleBuffer(ToPolar); +//Konva.Filters.FromPolar = Util._FilterWrapDoubleBuffer(FromPolar); + +// create a temporary canvas for working - shared between multiple calls + +/* + * Kaleidoscope Filter. + * @function + * @name Kaleidoscope + * @author ippo615 + * @memberof Konva.Filters + * @example + * node.cache(); + * node.filters([Konva.Filters.Kaleidoscope]); + * node.kaleidoscopePower(3); + * node.kaleidoscopeAngle(45); + */ +export const Kaleidoscope: Filter = function (imageData) { + const xSize = imageData.width, + ySize = imageData.height; + + let x, y, xoff, i, r, g, b, a, srcPos, dstPos; + let power = Math.round(this.kaleidoscopePower()); + const angle = Math.round(this.kaleidoscopeAngle()); + const offset = Math.floor((xSize * (angle % 360)) / 360); + + if (power < 1) { + return; + } + + // Work with our shared buffer canvas + const tempCanvas = Util.createCanvasElement(); + tempCanvas.width = xSize; + tempCanvas.height = ySize; + const scratchData = tempCanvas + .getContext('2d')! + .getImageData(0, 0, xSize, ySize); + Util.releaseCanvas(tempCanvas); + // Convert thhe original to polar coordinates + ToPolar(imageData, scratchData, { + polarCenterX: xSize / 2, + polarCenterY: ySize / 2, + }); + + // Determine how big each section will be, if it's too small + // make it bigger + let minSectionSize = xSize / Math.pow(2, power); + while (minSectionSize <= 8) { + minSectionSize = minSectionSize * 2; + power -= 1; + } + minSectionSize = Math.ceil(minSectionSize); + let sectionSize = minSectionSize; + + // Copy the offset region to 0 + // Depending on the size of filter and location of the offset we may need + // to copy the section backwards to prevent it from rewriting itself + let xStart = 0, + xEnd = sectionSize, + xDelta = 1; + if (offset + minSectionSize > xSize) { + xStart = sectionSize; + xEnd = 0; + xDelta = -1; + } + for (y = 0; y < ySize; y += 1) { + for (x = xStart; x !== xEnd; x += xDelta) { + xoff = Math.round(x + offset) % xSize; + srcPos = (xSize * y + xoff) * 4; + r = scratchData.data[srcPos + 0]; + g = scratchData.data[srcPos + 1]; + b = scratchData.data[srcPos + 2]; + a = scratchData.data[srcPos + 3]; + dstPos = (xSize * y + x) * 4; + scratchData.data[dstPos + 0] = r; + scratchData.data[dstPos + 1] = g; + scratchData.data[dstPos + 2] = b; + scratchData.data[dstPos + 3] = a; + } + } + + // Perform the actual effect + for (y = 0; y < ySize; y += 1) { + sectionSize = Math.floor(minSectionSize); + for (i = 0; i < power; i += 1) { + for (x = 0; x < sectionSize + 1; x += 1) { + srcPos = (xSize * y + x) * 4; + r = scratchData.data[srcPos + 0]; + g = scratchData.data[srcPos + 1]; + b = scratchData.data[srcPos + 2]; + a = scratchData.data[srcPos + 3]; + dstPos = (xSize * y + sectionSize * 2 - x - 1) * 4; + scratchData.data[dstPos + 0] = r; + scratchData.data[dstPos + 1] = g; + scratchData.data[dstPos + 2] = b; + scratchData.data[dstPos + 3] = a; + } + sectionSize *= 2; + } + } + + // Convert back from polar coordinates + FromPolar(scratchData, imageData, { polarRotation: 0 }); +}; + +/** + * get/set kaleidoscope power. Use with {@link Konva.Filters.Kaleidoscope} filter. + * @name Konva.Node#kaleidoscopePower + * @method + * @param {Integer} power of kaleidoscope + * @returns {Integer} + */ +Factory.addGetterSetter( + Node, + 'kaleidoscopePower', + 2, + getNumberValidator(), + Factory.afterSetFilter +); + +/** + * get/set kaleidoscope angle. Use with {@link Konva.Filters.Kaleidoscope} filter. + * @name Konva.Node#kaleidoscopeAngle + * @method + * @param {Integer} degrees + * @returns {Integer} + */ +Factory.addGetterSetter( + Node, + 'kaleidoscopeAngle', + 0, + getNumberValidator(), + Factory.afterSetFilter +); diff --git a/src/filters/Mask.ts b/src/filters/Mask.ts new file mode 100644 index 000000000..0668c0d58 --- /dev/null +++ b/src/filters/Mask.ts @@ -0,0 +1,209 @@ +import { Factory } from '../Factory'; +import { Filter, Node } from '../Node'; +import { getNumberValidator } from '../Validators'; + +function pixelAt(idata, x, y) { + let idx = (y * idata.width + x) * 4; + const d: Array = []; + d.push( + idata.data[idx++], + idata.data[idx++], + idata.data[idx++], + idata.data[idx++] + ); + return d; +} + +function rgbDistance(p1, p2) { + return Math.sqrt( + Math.pow(p1[0] - p2[0], 2) + + Math.pow(p1[1] - p2[1], 2) + + Math.pow(p1[2] - p2[2], 2) + ); +} + +function rgbMean(pTab) { + const m = [0, 0, 0]; + + for (let i = 0; i < pTab.length; i++) { + m[0] += pTab[i][0]; + m[1] += pTab[i][1]; + m[2] += pTab[i][2]; + } + + m[0] /= pTab.length; + m[1] /= pTab.length; + m[2] /= pTab.length; + + return m; +} + +function backgroundMask(idata, threshold) { + const rgbv_no = pixelAt(idata, 0, 0); + const rgbv_ne = pixelAt(idata, idata.width - 1, 0); + const rgbv_so = pixelAt(idata, 0, idata.height - 1); + const rgbv_se = pixelAt(idata, idata.width - 1, idata.height - 1); + + const thres = threshold || 10; + if ( + rgbDistance(rgbv_no, rgbv_ne) < thres && + rgbDistance(rgbv_ne, rgbv_se) < thres && + rgbDistance(rgbv_se, rgbv_so) < thres && + rgbDistance(rgbv_so, rgbv_no) < thres + ) { + // Mean color + const mean = rgbMean([rgbv_ne, rgbv_no, rgbv_se, rgbv_so]); + + // Mask based on color distance + const mask: Array = []; + for (let i = 0; i < idata.width * idata.height; i++) { + const d = rgbDistance(mean, [ + idata.data[i * 4], + idata.data[i * 4 + 1], + idata.data[i * 4 + 2], + ]); + mask[i] = d < thres ? 0 : 255; + } + + return mask; + } +} + +function applyMask(idata, mask) { + for (let i = 0; i < idata.width * idata.height; i++) { + idata.data[4 * i + 3] = mask[i]; + } +} + +function erodeMask(mask, sw, sh) { + const weights = [1, 1, 1, 1, 0, 1, 1, 1, 1]; + const side = Math.round(Math.sqrt(weights.length)); + const halfSide = Math.floor(side / 2); + + const maskResult: Array = []; + for (let y = 0; y < sh; y++) { + for (let x = 0; x < sw; x++) { + const so = y * sw + x; + let a = 0; + for (let cy = 0; cy < side; cy++) { + for (let cx = 0; cx < side; cx++) { + const scy = y + cy - halfSide; + const scx = x + cx - halfSide; + + if (scy >= 0 && scy < sh && scx >= 0 && scx < sw) { + const srcOff = scy * sw + scx; + const wt = weights[cy * side + cx]; + + a += mask[srcOff] * wt; + } + } + } + + maskResult[so] = a === 255 * 8 ? 255 : 0; + } + } + + return maskResult; +} + +function dilateMask(mask, sw, sh) { + const weights = [1, 1, 1, 1, 1, 1, 1, 1, 1]; + const side = Math.round(Math.sqrt(weights.length)); + const halfSide = Math.floor(side / 2); + + const maskResult: Array = []; + for (let y = 0; y < sh; y++) { + for (let x = 0; x < sw; x++) { + const so = y * sw + x; + let a = 0; + for (let cy = 0; cy < side; cy++) { + for (let cx = 0; cx < side; cx++) { + const scy = y + cy - halfSide; + const scx = x + cx - halfSide; + + if (scy >= 0 && scy < sh && scx >= 0 && scx < sw) { + const srcOff = scy * sw + scx; + const wt = weights[cy * side + cx]; + + a += mask[srcOff] * wt; + } + } + } + + maskResult[so] = a >= 255 * 4 ? 255 : 0; + } + } + + return maskResult; +} + +function smoothEdgeMask(mask, sw, sh) { + const weights = [1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9]; + const side = Math.round(Math.sqrt(weights.length)); + const halfSide = Math.floor(side / 2); + + const maskResult: Array = []; + for (let y = 0; y < sh; y++) { + for (let x = 0; x < sw; x++) { + const so = y * sw + x; + let a = 0; + for (let cy = 0; cy < side; cy++) { + for (let cx = 0; cx < side; cx++) { + const scy = y + cy - halfSide; + const scx = x + cx - halfSide; + + if (scy >= 0 && scy < sh && scx >= 0 && scx < sw) { + const srcOff = scy * sw + scx; + const wt = weights[cy * side + cx]; + + a += mask[srcOff] * wt; + } + } + } + + maskResult[so] = a; + } + } + + return maskResult; +} + +/** + * Mask Filter + * @function + * @name Mask + * @memberof Konva.Filters + * @param {Object} imageData + * @example + * node.cache(); + * node.filters([Konva.Filters.Mask]); + * node.threshold(200); + */ +export const Mask: Filter = function (imageData) { + // Detect pixels close to the background color + const threshold = this.threshold(); + let mask = backgroundMask(imageData, threshold); + if (mask) { + // Erode + mask = erodeMask(mask, imageData.width, imageData.height); + + // Dilate + mask = dilateMask(mask, imageData.width, imageData.height); + + // Gradient + mask = smoothEdgeMask(mask, imageData.width, imageData.height); + + // Apply mask + applyMask(imageData, mask); + } + + return imageData; +}; + +Factory.addGetterSetter( + Node, + 'threshold', + 0, + getNumberValidator(), + Factory.afterSetFilter +); diff --git a/src/filters/Noise.ts b/src/filters/Noise.ts new file mode 100644 index 000000000..77a9d7101 --- /dev/null +++ b/src/filters/Noise.ts @@ -0,0 +1,43 @@ +import { Factory } from '../Factory'; +import { Filter, Node } from '../Node'; +import { getNumberValidator } from '../Validators'; + +/** + * Noise Filter. Randomly adds or substracts to the color channels + * @function + * @name Noise + * @memberof Konva.Filters + * @param {Object} imageData + * @author ippo615 + * @example + * node.cache(); + * node.filters([Konva.Filters.Noise]); + * node.noise(0.8); + */ +export const Noise: Filter = function (imageData) { + const amount = this.noise() * 255, + data = imageData.data, + nPixels = data.length, + half = amount / 2; + + for (let i = 0; i < nPixels; i += 4) { + data[i + 0] += half - 2 * half * Math.random(); + data[i + 1] += half - 2 * half * Math.random(); + data[i + 2] += half - 2 * half * Math.random(); + } +}; + +Factory.addGetterSetter( + Node, + 'noise', + 0.2, + getNumberValidator(), + Factory.afterSetFilter +); +/** + * get/set noise amount. Must be a value between 0 and 1. Use with {@link Konva.Filters.Noise} filter. + * @name Konva.Node#noise + * @method + * @param {Number} noise + * @returns {Number} + */ diff --git a/src/filters/Pixelate.ts b/src/filters/Pixelate.ts new file mode 100644 index 000000000..31dc27279 --- /dev/null +++ b/src/filters/Pixelate.ts @@ -0,0 +1,121 @@ + +import { Factory } from '../Factory'; +import { Util } from '../Util'; +import { Node, Filter } from '../Node'; +import { getNumberValidator } from '../Validators'; + +/** + * Pixelate Filter. Averages groups of pixels and redraws + * them as larger pixels + * @function + * @name Pixelate + * @memberof Konva.Filters + * @param {Object} imageData + * @author ippo615 + * @example + * node.cache(); + * node.filters([Konva.Filters.Pixelate]); + * node.pixelSize(10); + */ + +export const Pixelate: Filter = function (imageData) { + let pixelSize = Math.ceil(this.pixelSize()), + width = imageData.width, + height = imageData.height, + x, + y, + i, + //pixelsPerBin = pixelSize * pixelSize, + red, + green, + blue, + alpha, + nBinsX = Math.ceil(width / pixelSize), + nBinsY = Math.ceil(height / pixelSize), + xBinStart, + xBinEnd, + yBinStart, + yBinEnd, + xBin, + yBin, + pixelsInBin, + data = imageData.data; + + if (pixelSize <= 0) { + Util.error('pixelSize value can not be <= 0'); + return; + } + + for (xBin = 0; xBin < nBinsX; xBin += 1) { + for (yBin = 0; yBin < nBinsY; yBin += 1) { + // Initialize the color accumlators to 0 + red = 0; + green = 0; + blue = 0; + alpha = 0; + + // Determine which pixels are included in this bin + xBinStart = xBin * pixelSize; + xBinEnd = xBinStart + pixelSize; + yBinStart = yBin * pixelSize; + yBinEnd = yBinStart + pixelSize; + + // Add all of the pixels to this bin! + pixelsInBin = 0; + for (x = xBinStart; x < xBinEnd; x += 1) { + if (x >= width) { + continue; + } + for (y = yBinStart; y < yBinEnd; y += 1) { + if (y >= height) { + continue; + } + i = (width * y + x) * 4; + red += data[i + 0]; + green += data[i + 1]; + blue += data[i + 2]; + alpha += data[i + 3]; + pixelsInBin += 1; + } + } + + // Make sure the channels are between 0-255 + red = red / pixelsInBin; + green = green / pixelsInBin; + blue = blue / pixelsInBin; + alpha = alpha / pixelsInBin; + + // Draw this bin + for (x = xBinStart; x < xBinEnd; x += 1) { + if (x >= width) { + continue; + } + for (y = yBinStart; y < yBinEnd; y += 1) { + if (y >= height) { + continue; + } + i = (width * y + x) * 4; + data[i + 0] = red; + data[i + 1] = green; + data[i + 2] = blue; + data[i + 3] = alpha; + } + } + } + } +}; + +Factory.addGetterSetter( + Node, + 'pixelSize', + 8, + getNumberValidator(), + Factory.afterSetFilter +); +/** + * get/set pixel size. Use with {@link Konva.Filters.Pixelate} filter. + * @name Konva.Node#pixelSize + * @method + * @param {Integer} pixelSize + * @returns {Integer} + */ diff --git a/src/filters/Posterize.ts b/src/filters/Posterize.ts new file mode 100644 index 000000000..6fb89b919 --- /dev/null +++ b/src/filters/Posterize.ts @@ -0,0 +1,45 @@ +import { Factory } from '../Factory'; +import { Filter, Node } from '../Node'; +import { getNumberValidator } from '../Validators'; + +/** + * Posterize Filter. Adjusts the channels so that there are no more + * than n different values for that channel. This is also applied + * to the alpha channel. + * @function + * @name Posterize + * @author ippo615 + * @memberof Konva.Filters + * @param {Object} imageData + * @example + * node.cache(); + * node.filters([Konva.Filters.Posterize]); + * node.levels(0.8); // between 0 and 1 + */ +export const Posterize: Filter = function (imageData) { + // level must be between 1 and 255 + const levels = Math.round(this.levels() * 254) + 1, + data = imageData.data, + len = data.length, + scale = 255 / levels; + + for (let i = 0; i < len; i += 1) { + data[i] = Math.floor(data[i] / scale) * scale; + } +}; + +Factory.addGetterSetter( + Node, + 'levels', + 0.5, + getNumberValidator(), + Factory.afterSetFilter +); + +/** + * get/set levels. Must be a number between 0 and 1. Use with {@link Konva.Filters.Posterize} filter. + * @name Konva.Node#levels + * @method + * @param {Number} level between 0 and 1 + * @returns {Number} + */ diff --git a/src/filters/RGB.ts b/src/filters/RGB.ts new file mode 100644 index 000000000..e5ea879c1 --- /dev/null +++ b/src/filters/RGB.ts @@ -0,0 +1,81 @@ +import { Factory } from '../Factory'; +import { Node, Filter } from '../Node'; +import { RGBComponent } from '../Validators'; + +/** + * RGB Filter + * @function + * @name RGB + * @memberof Konva.Filters + * @param {Object} imageData + * @author ippo615 + * @example + * node.cache(); + * node.filters([Konva.Filters.RGB]); + * node.blue(120); + * node.green(200); + */ +export const RGB: Filter = function (imageData) { + const data = imageData.data, + nPixels = data.length, + red = this.red(), + green = this.green(), + blue = this.blue(); + + for (let i = 0; i < nPixels; i += 4) { + const brightness = + (0.34 * data[i] + 0.5 * data[i + 1] + 0.16 * data[i + 2]) / 255; + data[i] = brightness * red; // r + data[i + 1] = brightness * green; // g + data[i + 2] = brightness * blue; // b + data[i + 3] = data[i + 3]; // alpha + } +}; + +Factory.addGetterSetter(Node, 'red', 0, function (this: Node, val) { + this._filterUpToDate = false; + if (val > 255) { + return 255; + } else if (val < 0) { + return 0; + } else { + return Math.round(val); + } +}); +/** + * get/set filter red value. Use with {@link Konva.Filters.RGB} filter. + * @name red + * @method + * @memberof Konva.Node.prototype + * @param {Integer} red value between 0 and 255 + * @returns {Integer} + */ + +Factory.addGetterSetter(Node, 'green', 0, function (this: Node, val) { + this._filterUpToDate = false; + if (val > 255) { + return 255; + } else if (val < 0) { + return 0; + } else { + return Math.round(val); + } +}); +/** + * get/set filter green value. Use with {@link Konva.Filters.RGB} filter. + * @name green + * @method + * @memberof Konva.Node.prototype + * @param {Integer} green value between 0 and 255 + * @returns {Integer} + */ + +Factory.addGetterSetter(Node, 'blue', 0, RGBComponent, Factory.afterSetFilter); +/** + * get/set filter blue value. Use with {@link Konva.Filters.RGB} filter. + * @name blue + * @method + * @memberof Konva.Node.prototype + * @param {Integer} blue value between 0 and 255 + * @returns {Integer} + */ diff --git a/src/filters/RGBA.ts b/src/filters/RGBA.ts new file mode 100644 index 000000000..dcd140c07 --- /dev/null +++ b/src/filters/RGBA.ts @@ -0,0 +1,102 @@ +import { Factory } from '../Factory'; +import { Node, Filter } from '../Node'; +import { RGBComponent } from '../Validators'; + +/** + * RGBA Filter + * @function + * @name RGBA + * @memberof Konva.Filters + * @param {Object} imageData + * @author codefo + * @example + * node.cache(); + * node.filters([Konva.Filters.RGBA]); + * node.blue(120); + * node.green(200); + * node.alpha(0.3); + */ + +export const RGBA: Filter = function (imageData) { + const data = imageData.data, + nPixels = data.length, + red = this.red(), + green = this.green(), + blue = this.blue(), + alpha = this.alpha(); + + for (let i = 0; i < nPixels; i += 4) { + const ia = 1 - alpha; + + data[i] = red * alpha + data[i] * ia; // r + data[i + 1] = green * alpha + data[i + 1] * ia; // g + data[i + 2] = blue * alpha + data[i + 2] * ia; // b + } +}; + +Factory.addGetterSetter(Node, 'red', 0, function (this: Node, val: number) { + this._filterUpToDate = false; + if (val > 255) { + return 255; + } else if (val < 0) { + return 0; + } else { + return Math.round(val); + } +}); +/** + * get/set filter red value. Use with {@link Konva.Filters.RGBA} filter. + * @name red + * @method + * @memberof Konva.Node.prototype + * @param {Integer} red value between 0 and 255 + * @returns {Integer} + */ + +Factory.addGetterSetter(Node, 'green', 0, function (this: Node, val) { + this._filterUpToDate = false; + if (val > 255) { + return 255; + } else if (val < 0) { + return 0; + } else { + return Math.round(val); + } +}); +/** + * get/set filter green value. Use with {@link Konva.Filters.RGBA} filter. + * @name green + * @method + * @memberof Konva.Node.prototype + * @param {Integer} green value between 0 and 255 + * @returns {Integer} + */ + +Factory.addGetterSetter(Node, 'blue', 0, RGBComponent, Factory.afterSetFilter); +/** + * get/set filter blue value. Use with {@link Konva.Filters.RGBA} filter. + * @name blue + * @method + * @memberof Konva.Node.prototype + * @param {Integer} blue value between 0 and 255 + * @returns {Integer} + */ + +Factory.addGetterSetter(Node, 'alpha', 1, function (this: Node, val) { + this._filterUpToDate = false; + if (val > 1) { + return 1; + } else if (val < 0) { + return 0; + } else { + return val; + } +}); +/** + * get/set filter alpha value. Use with {@link Konva.Filters.RGBA} filter. + * @name alpha + * @method + * @memberof Konva.Node.prototype + * @param {Float} alpha value between 0 and 1 + * @returns {Float} + */ diff --git a/src/filters/Sepia.ts b/src/filters/Sepia.ts new file mode 100644 index 000000000..3719ffe3c --- /dev/null +++ b/src/filters/Sepia.ts @@ -0,0 +1,27 @@ +import { Filter } from '../Node'; + +// based on https://stackoverflow.com/questions/1061093/how-is-a-sepia-tone-created + +/** + * @function + * @name Sepia + * @memberof Konva.Filters + * @param {Object} imageData + * @example + * node.cache(); + * node.filters([Konva.Filters.Sepia]); + */ +export const Sepia: Filter = function (imageData) { + const data = imageData.data, + nPixels = data.length; + + for (let i = 0; i < nPixels; i += 4) { + const r = data[i + 0]; + const g = data[i + 1]; + const b = data[i + 2]; + + data[i + 0] = Math.min(255, r * 0.393 + g * 0.769 + b * 0.189); + data[i + 1] = Math.min(255, r * 0.349 + g * 0.686 + b * 0.168); + data[i + 2] = Math.min(255, r * 0.272 + g * 0.534 + b * 0.131); + } +}; diff --git a/src/filters/Solarize.ts b/src/filters/Solarize.ts new file mode 100644 index 000000000..c576b6f71 --- /dev/null +++ b/src/filters/Solarize.ts @@ -0,0 +1,48 @@ +import { Filter } from '../Node'; +/** + * Solarize Filter + * Pixastic Lib - Solarize filter - v0.1.0 + * Copyright (c) 2008 Jacob Seidelin, jseidelin@nihilogic.dk, http://blog.nihilogic.dk/ + * License: [http://www.pixastic.com/lib/license.txt] + * @function + * @name Solarize + * @memberof Konva.Filters + * @param {Object} imageData + * @example + * node.cache(); + * node.filters([Konva.Filters.Solarize]); + */ + +export const Solarize: Filter = function (imageData) { + const data = imageData.data, + w = imageData.width, + h = imageData.height, + w4 = w * 4; + + let y = h; + + do { + const offsetY = (y - 1) * w4; + let x = w; + do { + const offset = offsetY + (x - 1) * 4; + let r = data[offset]; + let g = data[offset + 1]; + let b = data[offset + 2]; + + if (r > 127) { + r = 255 - r; + } + if (g > 127) { + g = 255 - g; + } + if (b > 127) { + b = 255 - b; + } + + data[offset] = r; + data[offset + 1] = g; + data[offset + 2] = b; + } while (--x); + } while (--y); +}; diff --git a/src/filters/Threshold.ts b/src/filters/Threshold.ts new file mode 100644 index 000000000..1cdb8bb02 --- /dev/null +++ b/src/filters/Threshold.ts @@ -0,0 +1,43 @@ +import { Factory } from '../Factory'; +import { Node, Filter } from '../Node'; +import { getNumberValidator } from '../Validators'; +/** + * Threshold Filter. Pushes any value above the mid point to + * the max and any value below the mid point to the min. + * This affects the alpha channel. + * @function + * @name Threshold + * @memberof Konva.Filters + * @param {Object} imageData + * @author ippo615 + * @example + * node.cache(); + * node.filters([Konva.Filters.Threshold]); + * node.threshold(0.1); + */ + +export const Threshold: Filter = function (imageData) { + const level = this.threshold() * 255, + data = imageData.data, + len = data.length; + + for (let i = 0; i < len; i += 1) { + data[i] = data[i] < level ? 0 : 255; + } +}; + +Factory.addGetterSetter( + Node, + 'threshold', + 0.5, + getNumberValidator(), + Factory.afterSetFilter +); +/** + * get/set threshold. Must be a value between 0 and 1. Use with {@link Konva.Filters.Threshold} or {@link Konva.Filters.Mask} filter. + * @name threshold + * @method + * @memberof Konva.Node.prototype + * @param {Number} threshold + * @returns {Number} + */ diff --git a/src/index-node.ts b/src/index-node.ts new file mode 100644 index 000000000..31a28e6d6 --- /dev/null +++ b/src/index-node.ts @@ -0,0 +1,27 @@ +// main entry for umd build for rollup +import { Konva } from './_FullInternals'; +import * as Canvas from 'canvas'; + +const canvas = Canvas['default'] || Canvas; + +global.DOMMatrix = canvas.DOMMatrix; + +const isNode = typeof global.document === 'undefined'; + +if (isNode) { + Konva.Util['createCanvasElement'] = () => { + const node = canvas.createCanvas(300, 300) as any; + if (!node['style']) { + node['style'] = {}; + } + return node; + }; + + // create image in Node env + Konva.Util.createImageElement = () => { + const node = new canvas.Image() as any; + return node; + }; +} + +export default Konva; diff --git a/src/index-types.d.ts b/src/index-types.d.ts new file mode 100644 index 000000000..77ae5a1cb --- /dev/null +++ b/src/index-types.d.ts @@ -0,0 +1,181 @@ +// the purpose of that file is very stupid +// I was not able to generate correct typescript declarations from the main source code +// because right now Konva is an object. Typescript can not define types from object like this: +// const stage : Konva.Stage = new Konva.Stage(); +// we can convert Konva to namespace +// but I was not able to find a way to extend it +// so here we just need to define full API of Konva manually + +// filters +import { Blur } from './filters/Blur'; +import { Brighten } from './filters/Brighten'; +import { Contrast } from './filters/Contrast'; +import { Emboss } from './filters/Emboss'; +import { Enhance } from './filters/Enhance'; +import { Grayscale } from './filters/Grayscale'; +import { HSL } from './filters/HSL'; +import { HSV } from './filters/HSV'; +import { Invert } from './filters/Invert'; +import { Kaleidoscope } from './filters/Kaleidoscope'; +import { Mask } from './filters/Mask'; +import { Noise } from './filters/Noise'; +import { Pixelate } from './filters/Pixelate'; +import { Posterize } from './filters/Posterize'; +import { RGB } from './filters/RGB'; +import { RGBA } from './filters/RGBA'; +import { Sepia } from './filters/Sepia'; +import { Solarize } from './filters/Solarize'; +import { Threshold } from './filters/Threshold'; + +declare namespace Konva { + export let enableTrace: number; + export let pixelRatio: number; + export let autoDrawEnabled: boolean; + export let dragDistance: number; + export let angleDeg: boolean; + export let showWarnings: boolean; + export let capturePointerEventsEnabled: boolean; + export let dragButtons: Array; + export let hitOnDragEnabled: boolean; + export const isDragging: () => boolean; + export const isDragReady: () => boolean; + export const getAngle: (angle: number) => number; + + export type Vector2d = import('./types').Vector2d; + + export const Node: typeof import('./Node').Node; + export type Node = import('./Node').Node; + export type NodeConfig = import('./Node').NodeConfig; + + export type KonvaEventObject = + import('./Node').KonvaEventObject; + + export type KonvaPointerEvent = import('./PointerEvents').KonvaPointerEvent; + + export type KonvaEventListener = + import('./Node').KonvaEventListener; + + export const Container: typeof import('./Container').Container; + export type Container = import('./Container').Container; + export type ContainerConfig = import('./Container').ContainerConfig; + + export const Transform: typeof import('./Util').Transform; + export type Transform = import('./Util').Transform; + + export const Util: typeof import('./Util').Util; + + export const Context: typeof import('./Context').Context; + export type Context = import('./Context').Context; + + export const Stage: typeof import('./Stage').Stage; + export type Stage = import('./Stage').Stage; + export type StageConfig = import('./Stage').StageConfig; + export const stages: typeof import('./Stage').stages; + + export const Layer: typeof import('./Layer').Layer; + export type Layer = import('./Layer').Layer; + export type LayerConfig = import('./Layer').LayerConfig; + + export const FastLayer: typeof import('./FastLayer').FastLayer; + export type FastLayer = import('./FastLayer').FastLayer; + + export const Group: typeof import('./Group').Group; + export type Group = import('./Group').Group; + export type GroupConfig = import('./Group').GroupConfig; + + export const DD: typeof import('./DragAndDrop').DD; + + export const Shape: typeof import('./Shape').Shape; + export type Shape = import('./Shape').Shape; + export type ShapeConfig = import('./Shape').ShapeConfig; + export const shapes: typeof import('./Shape').shapes; + + export const Animation: typeof import('./Animation').Animation; + export type Animation = import('./Animation').Animation; + + export const Tween: typeof import('./Tween').Tween; + export type Tween = import('./Tween').Tween; + export type TweenConfig = import('./Tween').TweenConfig; + export const Easings: typeof import('./Tween').Easings; + + export const Arc: typeof import('./shapes/Arc').Arc; + export type Arc = import('./shapes/Arc').Arc; + export type ArcConfig = import('./shapes/Arc').ArcConfig; + export const Arrow: typeof import('./shapes/Arrow').Arrow; + export type Arrow = import('./shapes/Arrow').Arrow; + export type ArrowConfig = import('./shapes/Arrow').ArrowConfig; + export const Circle: typeof import('./shapes/Circle').Circle; + export type Circle = import('./shapes/Circle').Circle; + export type CircleConfig = import('./shapes/Circle').CircleConfig; + export const Ellipse: typeof import('./shapes/Ellipse').Ellipse; + export type Ellipse = import('./shapes/Ellipse').Ellipse; + export type EllipseConfig = import('./shapes/Ellipse').EllipseConfig; + export const Image: typeof import('./shapes/Image').Image; + export type Image = import('./shapes/Image').Image; + export type ImageConfig = import('./shapes/Image').ImageConfig; + export const Label: typeof import('./shapes/Label').Label; + export type Label = import('./shapes/Label').Label; + export type LabelConfig = import('./shapes/Label').LabelConfig; + export const Tag: typeof import('./shapes/Label').Tag; + export type Tag = import('./shapes/Label').Tag; + export type TagConfig = import('./shapes/Label').TagConfig; + export const Line: typeof import('./shapes/Line').Line; + export type Line = import('./shapes/Line').Line; + export type LineConfig = import('./shapes/Line').LineConfig; + export const Path: typeof import('./shapes/Path').Path; + export type Path = import('./shapes/Path').Path; + export type PathConfig = import('./shapes/Path').PathConfig; + export const Rect: typeof import('./shapes/Rect').Rect; + export type Rect = import('./shapes/Rect').Rect; + export type RectConfig = import('./shapes/Rect').RectConfig; + export const RegularPolygon: typeof import('./shapes/RegularPolygon').RegularPolygon; + export type RegularPolygon = import('./shapes/RegularPolygon').RegularPolygon; + export type RegularPolygonConfig = + import('./shapes/RegularPolygon').RegularPolygonConfig; + export const Ring: typeof import('./shapes/Ring').Ring; + export type Ring = import('./shapes/Ring').Ring; + export type RingConfig = import('./shapes/Ring').RingConfig; + export const Sprite: typeof import('./shapes/Sprite').Sprite; + export type Sprite = import('./shapes/Sprite').Sprite; + export type SpriteConfig = import('./shapes/Sprite').SpriteConfig; + export const Star: typeof import('./shapes/Star').Star; + export type Star = import('./shapes/Star').Star; + export type StarConfig = import('./shapes/Star').StarConfig; + export const Text: typeof import('./shapes/Text').Text; + export type Text = import('./shapes/Text').Text; + export type TextConfig = import('./shapes/Text').TextConfig; + export const TextPath: typeof import('./shapes/TextPath').TextPath; + export type TextPath = import('./shapes/TextPath').TextPath; + export type TextPathConfig = import('./shapes/TextPath').TextPathConfig; + export const Transformer: typeof import('./shapes/Transformer').Transformer; + export type Transformer = import('./shapes/Transformer').Transformer; + export type TransformerConfig = + import('./shapes/Transformer').TransformerConfig; + export const Wedge: typeof import('./shapes/Wedge').Wedge; + export type Wedge = import('./shapes/Wedge').Wedge; + export type WedgeConfig = import('./shapes/Wedge').WedgeConfig; + + export const Filters: { + Blur: typeof Blur; + Brighten: typeof Brighten; + Contrast: typeof Contrast; + Emboss: typeof Emboss; + Enhance: typeof Enhance; + Grayscale: typeof Grayscale; + HSL: typeof HSL; + HSV: typeof HSV; + Invert: typeof Invert; + Kaleidoscope: typeof Kaleidoscope; + Mask: typeof Mask; + Noise: typeof Noise; + Pixelate: typeof Pixelate; + Posterize: typeof Posterize; + RGB: typeof RGB; + RGBA: typeof RGBA; + Sepia: typeof Sepia; + Solarize: typeof Solarize; + Threshold: typeof Threshold; + }; +} + +export default Konva; diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 000000000..75ffb0645 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,3 @@ +import { Konva } from './_FullInternals'; + +export default Konva; diff --git a/src/shapes/Arc.ts b/src/shapes/Arc.ts new file mode 100644 index 000000000..63b97c5f7 --- /dev/null +++ b/src/shapes/Arc.ts @@ -0,0 +1,170 @@ +import { Factory } from '../Factory'; +import { Shape, ShapeConfig } from '../Shape'; +import { Konva } from '../Global'; +import { GetSet } from '../types'; +import { getNumberValidator, getBooleanValidator } from '../Validators'; +import { _registerNode } from '../Global'; +import { Context } from '../Context'; + +export interface ArcConfig extends ShapeConfig { + angle: number; + innerRadius: number; + outerRadius: number; + clockwise?: boolean; +} + +/** + * Arc constructor + * @constructor + * @memberof Konva + * @augments Konva.Shape + * @param {Object} config + * @param {Number} config.angle in degrees + * @param {Number} config.innerRadius + * @param {Number} config.outerRadius + * @param {Boolean} [config.clockwise] + * @@shapeParams + * @@nodeParams + * @example + * // draw a Arc that's pointing downwards + * var arc = new Konva.Arc({ + * innerRadius: 40, + * outerRadius: 80, + * fill: 'red', + * stroke: 'black' + * strokeWidth: 5, + * angle: 60, + * rotationDeg: -120 + * }); + */ +export class Arc extends Shape { + _sceneFunc(context: Context) { + const angle = Konva.getAngle(this.angle()), + clockwise = this.clockwise(); + + context.beginPath(); + context.arc(0, 0, this.outerRadius(), 0, angle, clockwise); + context.arc(0, 0, this.innerRadius(), angle, 0, !clockwise); + context.closePath(); + context.fillStrokeShape(this); + } + getWidth() { + return this.outerRadius() * 2; + } + getHeight() { + return this.outerRadius() * 2; + } + setWidth(width: number) { + this.outerRadius(width / 2); + } + setHeight(height: number) { + this.outerRadius(height / 2); + } + + getSelfRect() { + const innerRadius = this.innerRadius(); + const outerRadius = this.outerRadius(); + const clockwise = this.clockwise(); + const angle = Konva.getAngle(clockwise ? 360 - this.angle() : this.angle()); + + const boundLeftRatio = Math.cos(Math.min(angle, Math.PI)); + const boundRightRatio = 1; + const boundTopRatio = Math.sin( + Math.min(Math.max(Math.PI, angle), (3 * Math.PI) / 2) + ); + const boundBottomRatio = Math.sin(Math.min(angle, Math.PI / 2)); + const boundLeft = + boundLeftRatio * (boundLeftRatio > 0 ? innerRadius : outerRadius); + const boundRight = + boundRightRatio * (boundRightRatio > 0 ? outerRadius : innerRadius); + const boundTop = + boundTopRatio * (boundTopRatio > 0 ? innerRadius : outerRadius); + const boundBottom = + boundBottomRatio * (boundBottomRatio > 0 ? outerRadius : innerRadius); + + return { + x: boundLeft, + y: clockwise ? -1 * boundBottom : boundTop, + width: boundRight - boundLeft, + height: boundBottom - boundTop, + }; + } + + innerRadius: GetSet; + outerRadius: GetSet; + angle: GetSet; + clockwise: GetSet; +} + +Arc.prototype._centroid = true; +Arc.prototype.className = 'Arc'; +Arc.prototype._attrsAffectingSize = ['innerRadius', 'outerRadius']; +_registerNode(Arc); + +// add getters setters +Factory.addGetterSetter(Arc, 'innerRadius', 0, getNumberValidator()); + +/** + * get/set innerRadius + * @name Konva.Arc#innerRadius + * @method + * @param {Number} innerRadius + * @returns {Number} + * @example + * // get inner radius + * var innerRadius = arc.innerRadius(); + * + * // set inner radius + * arc.innerRadius(20); + */ + +Factory.addGetterSetter(Arc, 'outerRadius', 0, getNumberValidator()); + +/** + * get/set outerRadius + * @name Konva.Arc#outerRadius + * @method + * @param {Number} outerRadius + * @returns {Number} + * @example + * // get outer radius + * var outerRadius = arc.outerRadius(); + * + * // set outer radius + * arc.outerRadius(20); + */ + +Factory.addGetterSetter(Arc, 'angle', 0, getNumberValidator()); + +/** + * get/set angle in degrees + * @name Konva.Arc#angle + * @method + * @param {Number} angle + * @returns {Number} + * @example + * // get angle + * var angle = arc.angle(); + * + * // set angle + * arc.angle(20); + */ + +Factory.addGetterSetter(Arc, 'clockwise', false, getBooleanValidator()); + +/** + * get/set clockwise flag + * @name Konva.Arc#clockwise + * @method + * @param {Boolean} clockwise + * @returns {Boolean} + * @example + * // get clockwise flag + * var clockwise = arc.clockwise(); + * + * // draw arc counter-clockwise + * arc.clockwise(false); + * + * // draw arc clockwise + * arc.clockwise(true); + */ diff --git a/src/shapes/Arrow.ts b/src/shapes/Arrow.ts new file mode 100644 index 000000000..2503743ff --- /dev/null +++ b/src/shapes/Arrow.ts @@ -0,0 +1,230 @@ +import { Factory } from '../Factory'; +import { Line, LineConfig } from './Line'; +import { GetSet } from '../types'; +import { getNumberValidator } from '../Validators'; +import { _registerNode } from '../Global'; +import { Path } from './Path'; +import { Context } from '../Context'; + +export interface ArrowConfig extends LineConfig { + points: number[]; + tension?: number; + closed?: boolean; + pointerLength?: number; + pointerWidth?: number; + pointerAtBeginning?: boolean; + pointerAtEnding?: boolean; +} + +/** + * Arrow constructor + * @constructor + * @memberof Konva + * @augments Konva.Line + * @param {Object} config + * @param {Array} config.points Flat array of points coordinates. You should define them as [x1, y1, x2, y2, x3, y3]. + * @param {Number} [config.tension] Higher values will result in a more curvy line. A value of 0 will result in no interpolation. + * The default is 0 + * @param {Number} config.pointerLength Arrow pointer length. Default value is 10. + * @param {Number} config.pointerWidth Arrow pointer width. Default value is 10. + * @param {Boolean} config.pointerAtBeginning Do we need to draw pointer on beginning position?. Default false. + * @param {Boolean} config.pointerAtEnding Do we need to draw pointer on ending position?. Default true. + * @@shapeParams + * @@nodeParams + * @example + * var line = new Konva.Line({ + * points: [73, 70, 340, 23, 450, 60, 500, 20], + * stroke: 'red', + * tension: 1, + * pointerLength : 10, + * pointerWidth : 12 + * }); + */ +export class Arrow extends Line { + _sceneFunc(ctx: Context) { + super._sceneFunc(ctx); + const PI2 = Math.PI * 2; + const points = this.points(); + + let tp = points; + const fromTension = this.tension() !== 0 && points.length > 4; + if (fromTension) { + tp = this.getTensionPoints(); + } + const length = this.pointerLength(); + + const n = points.length; + + let dx, dy; + if (fromTension) { + const lp = [ + tp[tp.length - 4], + tp[tp.length - 3], + tp[tp.length - 2], + tp[tp.length - 1], + points[n - 2], + points[n - 1], + ]; + const lastLength = Path.calcLength( + tp[tp.length - 4], + tp[tp.length - 3], + 'C', + lp + ); + const previous = Path.getPointOnQuadraticBezier( + Math.min(1, 1 - length / lastLength), + lp[0], + lp[1], + lp[2], + lp[3], + lp[4], + lp[5] + ); + + dx = points[n - 2] - previous.x; + dy = points[n - 1] - previous.y; + } else { + dx = points[n - 2] - points[n - 4]; + dy = points[n - 1] - points[n - 3]; + } + + const radians = (Math.atan2(dy, dx) + PI2) % PI2; + + const width = this.pointerWidth(); + + if (this.pointerAtEnding()) { + ctx.save(); + ctx.beginPath(); + ctx.translate(points[n - 2], points[n - 1]); + ctx.rotate(radians); + ctx.moveTo(0, 0); + ctx.lineTo(-length, width / 2); + ctx.lineTo(-length, -width / 2); + ctx.closePath(); + ctx.restore(); + this.__fillStroke(ctx); + } + + if (this.pointerAtBeginning()) { + ctx.save(); + ctx.beginPath(); + ctx.translate(points[0], points[1]); + if (fromTension) { + dx = (tp[0] + tp[2]) / 2 - points[0]; + dy = (tp[1] + tp[3]) / 2 - points[1]; + } else { + dx = points[2] - points[0]; + dy = points[3] - points[1]; + } + + ctx.rotate((Math.atan2(-dy, -dx) + PI2) % PI2); + ctx.moveTo(0, 0); + ctx.lineTo(-length, width / 2); + ctx.lineTo(-length, -width / 2); + ctx.closePath(); + ctx.restore(); + this.__fillStroke(ctx); + } + } + + __fillStroke(ctx: Context) { + // here is a tricky part + // we need to disable dash for arrow pointers + const isDashEnabled = this.dashEnabled(); + if (isDashEnabled) { + // manually disable dash for head + // it is better not to use setter here, + // because it will trigger attr change event + this.attrs.dashEnabled = false; + ctx.setLineDash([]); + } + + ctx.fillStrokeShape(this); + + // restore old value + if (isDashEnabled) { + this.attrs.dashEnabled = true; + } + } + + getSelfRect() { + const lineRect = super.getSelfRect(); + const offset = this.pointerWidth() / 2; + return { + x: lineRect.x, + y: lineRect.y - offset, + width: lineRect.width, + height: lineRect.height + offset * 2, + }; + } + + pointerLength: GetSet; + pointerWidth: GetSet; + pointerAtEnding: GetSet; + pointerAtBeginning: GetSet; +} + +Arrow.prototype.className = 'Arrow'; +_registerNode(Arrow); + +/** + * get/set pointerLength + * @name Konva.Arrow#pointerLength + * @method + * @param {Number} Length of pointer of arrow. The default is 10. + * @returns {Number} + * @example + * // get length + * var pointerLength = line.pointerLength(); + * + * // set length + * line.pointerLength(15); + */ + +Factory.addGetterSetter(Arrow, 'pointerLength', 10, getNumberValidator()); +/** + * get/set pointerWidth + * @name Konva.Arrow#pointerWidth + * @method + * @param {Number} Width of pointer of arrow. + * The default is 10. + * @returns {Number} + * @example + * // get width + * var pointerWidth = line.pointerWidth(); + * + * // set width + * line.pointerWidth(15); + */ + +Factory.addGetterSetter(Arrow, 'pointerWidth', 10, getNumberValidator()); +/** + * get/set pointerAtBeginning + * @name Konva.Arrow#pointerAtBeginning + * @method + * @param {Number} Should pointer displayed at beginning of arrow. The default is false. + * @returns {Boolean} + * @example + * // get value + * var pointerAtBeginning = line.pointerAtBeginning(); + * + * // set value + * line.pointerAtBeginning(true); + */ + +Factory.addGetterSetter(Arrow, 'pointerAtBeginning', false); +/** + * get/set pointerAtEnding + * @name Konva.Arrow#pointerAtEnding + * @method + * @param {Number} Should pointer displayed at ending of arrow. The default is true. + * @returns {Boolean} + * @example + * // get value + * var pointerAtEnding = line.pointerAtEnding(); + * + * // set value + * line.pointerAtEnding(false); + */ + +Factory.addGetterSetter(Arrow, 'pointerAtEnding', true); diff --git a/src/shapes/Circle.ts b/src/shapes/Circle.ts new file mode 100644 index 000000000..67b47ec5a --- /dev/null +++ b/src/shapes/Circle.ts @@ -0,0 +1,75 @@ +import { Factory } from '../Factory'; +import { Shape, ShapeConfig } from '../Shape'; +import { GetSet } from '../types'; +import { getNumberValidator } from '../Validators'; +import { _registerNode } from '../Global'; +import { Context } from '../Context'; + +export interface CircleConfig extends ShapeConfig { + radius?: number; +} + +/** + * Circle constructor + * @constructor + * @memberof Konva + * @augments Konva.Shape + * @param {Object} config + * @param {Number} config.radius + * @@shapeParams + * @@nodeParams + * @example + * // create circle + * var circle = new Konva.Circle({ + * radius: 40, + * fill: 'red', + * stroke: 'black', + * strokeWidth: 5 + * }); + */ +export class Circle extends Shape { + _sceneFunc(context: Context) { + context.beginPath(); + context.arc(0, 0, this.attrs.radius || 0, 0, Math.PI * 2, false); + context.closePath(); + context.fillStrokeShape(this); + } + getWidth() { + return this.radius() * 2; + } + getHeight() { + return this.radius() * 2; + } + setWidth(width: number) { + if (this.radius() !== width / 2) { + this.radius(width / 2); + } + } + setHeight(height: number) { + if (this.radius() !== height / 2) { + this.radius(height / 2); + } + } + + radius: GetSet; +} + +Circle.prototype._centroid = true; +Circle.prototype.className = 'Circle'; +Circle.prototype._attrsAffectingSize = ['radius']; +_registerNode(Circle); + +/** + * get/set radius + * @name Konva.Circle#radius + * @method + * @param {Number} radius + * @returns {Number} + * @example + * // get radius + * var radius = circle.radius(); + * + * // set radius + * circle.radius(10); + */ +Factory.addGetterSetter(Circle, 'radius', 0, getNumberValidator()); diff --git a/src/shapes/Ellipse.ts b/src/shapes/Ellipse.ts new file mode 100644 index 000000000..2c115ae6c --- /dev/null +++ b/src/shapes/Ellipse.ts @@ -0,0 +1,120 @@ +import { Factory } from '../Factory'; +import { Shape, ShapeConfig } from '../Shape'; +import { getNumberValidator } from '../Validators'; +import { _registerNode } from '../Global'; +import { Context } from '../Context'; + +import { GetSet, Vector2d } from '../types'; + +export interface EllipseConfig extends ShapeConfig { + radiusX: number; + radiusY: number; +} + +/** + * Ellipse constructor + * @constructor + * @memberof Konva + * @augments Konva.Shape + * @param {Object} config + * @param {Object} config.radius defines x and y radius + * @@shapeParams + * @@nodeParams + * @example + * var ellipse = new Konva.Ellipse({ + * radius : { + * x : 50, + * y : 50 + * }, + * fill: 'red' + * }); + */ +export class Ellipse extends Shape { + _sceneFunc(context: Context) { + const rx = this.radiusX(), + ry = this.radiusY(); + + context.beginPath(); + context.save(); + if (rx !== ry) { + context.scale(1, ry / rx); + } + context.arc(0, 0, rx, 0, Math.PI * 2, false); + context.restore(); + context.closePath(); + context.fillStrokeShape(this); + } + getWidth() { + return this.radiusX() * 2; + } + getHeight() { + return this.radiusY() * 2; + } + setWidth(width: number) { + this.radiusX(width / 2); + } + setHeight(height: number) { + this.radiusY(height / 2); + } + + radius: GetSet; + radiusX: GetSet; + radiusY: GetSet; +} + +Ellipse.prototype.className = 'Ellipse'; +Ellipse.prototype._centroid = true; +Ellipse.prototype._attrsAffectingSize = ['radiusX', 'radiusY']; +_registerNode(Ellipse); + +// add getters setters +Factory.addComponentsGetterSetter(Ellipse, 'radius', ['x', 'y']); + +/** + * get/set radius + * @name Konva.Ellipse#radius + * @method + * @param {Object} radius + * @param {Number} radius.x + * @param {Number} radius.y + * @returns {Object} + * @example + * // get radius + * var radius = ellipse.radius(); + * + * // set radius + * ellipse.radius({ + * x: 200, + * y: 100 + * }); + */ + +Factory.addGetterSetter(Ellipse, 'radiusX', 0, getNumberValidator()); +/** + * get/set radius x + * @name Konva.Ellipse#radiusX + * @method + * @param {Number} x + * @returns {Number} + * @example + * // get radius x + * var radiusX = ellipse.radiusX(); + * + * // set radius x + * ellipse.radiusX(200); + */ + +Factory.addGetterSetter(Ellipse, 'radiusY', 0, getNumberValidator()); +/** + * get/set radius y + * @name Konva.Ellipse#radiusY + * @method + * @param {Number} y + * @returns {Number} + * @example + * // get radius y + * var radiusY = ellipse.radiusY(); + * + * // set radius y + * ellipse.radiusY(200); + */ diff --git a/src/shapes/Image.ts b/src/shapes/Image.ts new file mode 100644 index 000000000..ae5095308 --- /dev/null +++ b/src/shapes/Image.ts @@ -0,0 +1,304 @@ +import { Util } from '../Util'; +import { Factory } from '../Factory'; +import { Shape, ShapeConfig } from '../Shape'; +import { _registerNode } from '../Global'; +import { + getNumberOrArrayOfNumbersValidator, + getNumberValidator, +} from '../Validators'; + +import { GetSet, IRect } from '../types'; +import { Context } from '../Context'; + +export interface ImageConfig extends ShapeConfig { + image: CanvasImageSource | undefined; + crop?: IRect; + cornerRadius?: number | number[]; +} + +/** + * Image constructor + * @constructor + * @memberof Konva + * @augments Konva.Shape + * @param {Object} config + * @param {Image} config.image + * @param {Object} [config.crop] + * @@shapeParams + * @@nodeParams + * @example + * var imageObj = new Image(); + * imageObj.onload = function() { + * var image = new Konva.Image({ + * x: 200, + * y: 50, + * image: imageObj, + * width: 100, + * height: 100 + * }); + * }; + * imageObj.src = '/path/to/image.jpg' + */ +export class Image extends Shape { + constructor(attrs: ImageConfig) { + super(attrs); + this.on('imageChange.konva', () => { + this._setImageLoad(); + }); + + this._setImageLoad(); + } + _setImageLoad() { + const image = this.image() as any; + // check is image is already loaded + if (image && image.complete) { + return; + } + // check is video is already loaded + if (image && image.readyState === 4) { + return; + } + if (image && image['addEventListener']) { + image['addEventListener']('load', () => { + this._requestDraw(); + }); + } + } + _useBufferCanvas() { + const hasCornerRadius = !!this.cornerRadius(); + const hasShadow = this.hasShadow(); + if (hasCornerRadius && hasShadow) { + return true; + } + return super._useBufferCanvas(true); + } + _sceneFunc(context: Context) { + const width = this.getWidth(); + const height = this.getHeight(); + const cornerRadius = this.cornerRadius(); + const image = this.attrs.image; + let params; + + if (image) { + const cropWidth = this.attrs.cropWidth; + const cropHeight = this.attrs.cropHeight; + if (cropWidth && cropHeight) { + params = [ + image, + this.cropX(), + this.cropY(), + cropWidth, + cropHeight, + 0, + 0, + width, + height, + ]; + } else { + params = [image, 0, 0, width, height]; + } + } + + if (this.hasFill() || this.hasStroke() || cornerRadius) { + context.beginPath(); + cornerRadius + ? Util.drawRoundedRectPath(context, width, height, cornerRadius) + : context.rect(0, 0, width, height); + context.closePath(); + context.fillStrokeShape(this); + } + + if (image) { + if (cornerRadius) { + context.clip(); + } + context.drawImage.apply(context, params); + } + // If you need to draw later, you need to execute save/restore + } + _hitFunc(context: Context) { + const width = this.width(), + height = this.height(), + cornerRadius = this.cornerRadius(); + + context.beginPath(); + if (!cornerRadius) { + context.rect(0, 0, width, height); + } else { + Util.drawRoundedRectPath(context, width, height, cornerRadius); + } + context.closePath(); + context.fillStrokeShape(this); + } + getWidth() { + return this.attrs.width ?? (this.image() as any)?.width; + } + getHeight() { + return this.attrs.height ?? (this.image() as any)?.height; + } + + /** + * load image from given url and create `Konva.Image` instance + * @method + * @memberof Konva.Image + * @param {String} url image source + * @param {Function} callback with Konva.Image instance as first argument + * @param {Function} onError optional error handler + * @example + * Konva.Image.fromURL(imageURL, function(image){ + * // image is Konva.Image instance + * layer.add(image); + * layer.draw(); + * }); + */ + static fromURL( + url: string, + callback: (img: Image) => void, + onError: OnErrorEventHandler = null + ) { + const img = Util.createImageElement(); + img.onload = function () { + const image = new Image({ + image: img, + }); + callback(image); + }; + img.onerror = onError; + img.crossOrigin = 'Anonymous'; + img.src = url; + } + + image: GetSet; + crop: GetSet; + cropX: GetSet; + cropY: GetSet; + cropWidth: GetSet; + cropHeight: GetSet; + cornerRadius: GetSet; +} + +Image.prototype.className = 'Image'; +_registerNode(Image); + +/** + * get/set corner radius + * @method + * @name Konva.Image#cornerRadius + * @param {Number} cornerRadius + * @returns {Number} + * @example + * // get corner radius + * var cornerRadius = image.cornerRadius(); + * + * // set corner radius + * image.cornerRadius(10); + * + * // set different corner radius values + * // top-left, top-right, bottom-right, bottom-left + * image.cornerRadius([0, 10, 20, 30]); + */ +Factory.addGetterSetter( + Image, + 'cornerRadius', + 0, + getNumberOrArrayOfNumbersValidator(4) +); + +/** + * get/set image source. It can be image, canvas or video element + * @name Konva.Image#image + * @method + * @param {Object} image source + * @returns {Object} + * @example + * // get value + * var image = shape.image(); + * + * // set value + * shape.image(img); + */ +Factory.addGetterSetter(Image, 'image'); + +Factory.addComponentsGetterSetter(Image, 'crop', ['x', 'y', 'width', 'height']); +/** + * get/set crop + * @method + * @name Konva.Image#crop + * @param {Object} crop + * @param {Number} crop.x + * @param {Number} crop.y + * @param {Number} crop.width + * @param {Number} crop.height + * @returns {Object} + * @example + * // get crop + * var crop = image.crop(); + * + * // set crop + * image.crop({ + * x: 20, + * y: 20, + * width: 20, + * height: 20 + * }); + */ + +Factory.addGetterSetter(Image, 'cropX', 0, getNumberValidator()); +/** + * get/set crop x + * @method + * @name Konva.Image#cropX + * @param {Number} x + * @returns {Number} + * @example + * // get crop x + * var cropX = image.cropX(); + * + * // set crop x + * image.cropX(20); + */ + +Factory.addGetterSetter(Image, 'cropY', 0, getNumberValidator()); +/** + * get/set crop y + * @name Konva.Image#cropY + * @method + * @param {Number} y + * @returns {Number} + * @example + * // get crop y + * var cropY = image.cropY(); + * + * // set crop y + * image.cropY(20); + */ + +Factory.addGetterSetter(Image, 'cropWidth', 0, getNumberValidator()); +/** + * get/set crop width + * @name Konva.Image#cropWidth + * @method + * @param {Number} width + * @returns {Number} + * @example + * // get crop width + * var cropWidth = image.cropWidth(); + * + * // set crop width + * image.cropWidth(20); + */ + +Factory.addGetterSetter(Image, 'cropHeight', 0, getNumberValidator()); +/** + * get/set crop height + * @name Konva.Image#cropHeight + * @method + * @param {Number} height + * @returns {Number} + * @example + * // get crop height + * var cropHeight = image.cropHeight(); + * + * // set crop height + * image.cropHeight(20); + */ diff --git a/src/shapes/Label.ts b/src/shapes/Label.ts new file mode 100644 index 000000000..be15df8c2 --- /dev/null +++ b/src/shapes/Label.ts @@ -0,0 +1,385 @@ +import { Factory } from '../Factory'; +import { Shape, ShapeConfig } from '../Shape'; +import { Group } from '../Group'; +import { Context } from '../Context'; +import { ContainerConfig } from '../Container'; +import { + getNumberOrArrayOfNumbersValidator, + getNumberValidator, +} from '../Validators'; +import { _registerNode } from '../Global'; + +import { GetSet } from '../types'; +import { Text } from './Text'; + +export interface LabelConfig extends ContainerConfig {} + +// constants +const ATTR_CHANGE_LIST = [ + 'fontFamily', + 'fontSize', + 'fontStyle', + 'padding', + 'lineHeight', + 'text', + 'width', + 'height', + 'pointerDirection', + 'pointerWidth', + 'pointerHeight', + ], + CHANGE_KONVA = 'Change.konva', + NONE = 'none', + UP = 'up', + RIGHT = 'right', + DOWN = 'down', + LEFT = 'left', + // cached variables + attrChangeListLen = ATTR_CHANGE_LIST.length; + +/** + * Label constructor.  Labels are groups that contain a Text and Tag shape + * @constructor + * @memberof Konva + * @param {Object} config + * @@nodeParams + * @example + * // create label + * var label = new Konva.Label({ + * x: 100, + * y: 100, + * draggable: true + * }); + * + * // add a tag to the label + * label.add(new Konva.Tag({ + * fill: '#bbb', + * stroke: '#333', + * shadowColor: 'black', + * shadowBlur: 10, + * shadowOffset: [10, 10], + * shadowOpacity: 0.2, + * lineJoin: 'round', + * pointerDirection: 'up', + * pointerWidth: 20, + * pointerHeight: 20, + * cornerRadius: 5 + * })); + * + * // add text to the label + * label.add(new Konva.Text({ + * text: 'Hello World!', + * fontSize: 50, + * lineHeight: 1.2, + * padding: 10, + * fill: 'green' + * })); + */ +export class Label extends Group { + constructor(config?: LabelConfig) { + super(config); + this.on('add.konva', function (evt) { + this._addListeners(evt.child); + this._sync(); + }); + } + + /** + * get Text shape for the label. You need to access the Text shape in order to update + * the text properties + * @name Konva.Label#getText + * @method + * @example + * label.getText().fill('red') + */ + getText() { + return this.find('Text')[0]; + } + /** + * get Tag shape for the label. You need to access the Tag shape in order to update + * the pointer properties and the corner radius + * @name Konva.Label#getTag + * @method + */ + getTag() { + return this.find('Tag')[0] as Tag; + } + _addListeners(text) { + let that = this, + n; + const func = function () { + that._sync(); + }; + + // update text data for certain attr changes + for (n = 0; n < attrChangeListLen; n++) { + text.on(ATTR_CHANGE_LIST[n] + CHANGE_KONVA, func); + } + } + getWidth() { + return this.getText().width(); + } + getHeight() { + return this.getText().height(); + } + _sync() { + let text = this.getText(), + tag = this.getTag(), + width, + height, + pointerDirection, + pointerWidth, + x, + y, + pointerHeight; + + if (text && tag) { + width = text.width(); + height = text.height(); + pointerDirection = tag.pointerDirection(); + pointerWidth = tag.pointerWidth(); + pointerHeight = tag.pointerHeight(); + x = 0; + y = 0; + + switch (pointerDirection) { + case UP: + x = width / 2; + y = -1 * pointerHeight; + break; + case RIGHT: + x = width + pointerWidth; + y = height / 2; + break; + case DOWN: + x = width / 2; + y = height + pointerHeight; + break; + case LEFT: + x = -1 * pointerWidth; + y = height / 2; + break; + } + + tag.setAttrs({ + x: -1 * x, + y: -1 * y, + width: width, + height: height, + }); + + text.setAttrs({ + x: -1 * x, + y: -1 * y, + }); + } + } +} + +Label.prototype.className = 'Label'; +_registerNode(Label); + +export interface TagConfig extends ShapeConfig { + pointerDirection?: string; + pointerWidth?: number; + pointerHeight?: number; + cornerRadius?: number | Array; +} + +/** + * Tag constructor.  A Tag can be configured + * to have a pointer element that points up, right, down, or left + * @constructor + * @memberof Konva + * @param {Object} config + * @param {String} [config.pointerDirection] can be up, right, down, left, or none; the default + * is none. When a pointer is present, the positioning of the label is relative to the tip of the pointer. + * @param {Number} [config.pointerWidth] + * @param {Number} [config.pointerHeight] + * @param {Number} [config.cornerRadius] + */ +export class Tag extends Shape { + _sceneFunc(context: Context) { + const width = this.width(), + height = this.height(), + pointerDirection = this.pointerDirection(), + pointerWidth = this.pointerWidth(), + pointerHeight = this.pointerHeight(), + cornerRadius = this.cornerRadius(); + + let topLeft = 0; + let topRight = 0; + let bottomLeft = 0; + let bottomRight = 0; + + if (typeof cornerRadius === 'number') { + topLeft = + topRight = + bottomLeft = + bottomRight = + Math.min(cornerRadius, width / 2, height / 2); + } else { + topLeft = Math.min(cornerRadius[0] || 0, width / 2, height / 2); + topRight = Math.min(cornerRadius[1] || 0, width / 2, height / 2); + bottomRight = Math.min(cornerRadius[2] || 0, width / 2, height / 2); + bottomLeft = Math.min(cornerRadius[3] || 0, width / 2, height / 2); + } + + context.beginPath(); + context.moveTo(topLeft, 0); + + if (pointerDirection === UP) { + context.lineTo((width - pointerWidth) / 2, 0); + context.lineTo(width / 2, -1 * pointerHeight); + context.lineTo((width + pointerWidth) / 2, 0); + } + + context.lineTo(width - topRight, 0); + context.arc( + width - topRight, + topRight, + topRight, + (Math.PI * 3) / 2, + 0, + false + ); + + if (pointerDirection === RIGHT) { + context.lineTo(width, (height - pointerHeight) / 2); + context.lineTo(width + pointerWidth, height / 2); + context.lineTo(width, (height + pointerHeight) / 2); + } + + context.lineTo(width, height - bottomRight); + context.arc( + width - bottomRight, + height - bottomRight, + bottomRight, + 0, + Math.PI / 2, + false + ); + + if (pointerDirection === DOWN) { + context.lineTo((width + pointerWidth) / 2, height); + context.lineTo(width / 2, height + pointerHeight); + context.lineTo((width - pointerWidth) / 2, height); + } + + context.lineTo(bottomLeft, height); + context.arc( + bottomLeft, + height - bottomLeft, + bottomLeft, + Math.PI / 2, + Math.PI, + false + ); + + if (pointerDirection === LEFT) { + context.lineTo(0, (height + pointerHeight) / 2); + context.lineTo(-1 * pointerWidth, height / 2); + context.lineTo(0, (height - pointerHeight) / 2); + } + + context.lineTo(0, topLeft); + context.arc(topLeft, topLeft, topLeft, Math.PI, (Math.PI * 3) / 2, false); + + context.closePath(); + context.fillStrokeShape(this); + } + getSelfRect() { + let x = 0, + y = 0, + pointerWidth = this.pointerWidth(), + pointerHeight = this.pointerHeight(), + direction = this.pointerDirection(), + width = this.width(), + height = this.height(); + + if (direction === UP) { + y -= pointerHeight; + height += pointerHeight; + } else if (direction === DOWN) { + height += pointerHeight; + } else if (direction === LEFT) { + // ARGH!!! I have no idea why should I used magic 1.5!!!!!!!!! + x -= pointerWidth * 1.5; + width += pointerWidth; + } else if (direction === RIGHT) { + width += pointerWidth * 1.5; + } + return { + x: x, + y: y, + width: width, + height: height, + }; + } + + pointerDirection: GetSet< + 'left' | 'up' | 'right' | 'down' | typeof NONE, + this + >; + pointerWidth: GetSet; + pointerHeight: GetSet; + cornerRadius: GetSet; +} + +Tag.prototype.className = 'Tag'; +_registerNode(Tag); + +/** + * get/set pointer direction + * @name Konva.Tag#pointerDirection + * @method + * @param {String} pointerDirection can be up, right, down, left, or none. The default is none. + * @returns {String} + * @example + * tag.pointerDirection('right'); + */ +Factory.addGetterSetter(Tag, 'pointerDirection', NONE); + +/** + * get/set pointer width + * @name Konva.Tag#pointerWidth + * @method + * @param {Number} pointerWidth + * @returns {Number} + * @example + * tag.pointerWidth(20); + */ +Factory.addGetterSetter(Tag, 'pointerWidth', 0, getNumberValidator()); + +/** + * get/set pointer height + * @method + * @name Konva.Tag#pointerHeight + * @param {Number} pointerHeight + * @returns {Number} + * @example + * tag.pointerHeight(20); + */ + +Factory.addGetterSetter(Tag, 'pointerHeight', 0, getNumberValidator()); + +/** + * get/set cornerRadius + * @name Konva.Tag#cornerRadius + * @method + * @param {Number} cornerRadius + * @returns {Number} + * @example + * tag.cornerRadius(20); + * + * // set different corner radius values + * // top-left, top-right, bottom-right, bottom-left + * tag.cornerRadius([0, 10, 20, 30]); + */ + +Factory.addGetterSetter( + Tag, + 'cornerRadius', + 0, + getNumberOrArrayOfNumbersValidator(4) +); diff --git a/src/shapes/Line.ts b/src/shapes/Line.ts new file mode 100644 index 000000000..664fc3d50 --- /dev/null +++ b/src/shapes/Line.ts @@ -0,0 +1,357 @@ +import { Factory } from '../Factory'; +import { _registerNode } from '../Global'; +import { Shape, ShapeConfig } from '../Shape'; +import { getNumberArrayValidator, getNumberValidator } from '../Validators'; + +import { Context } from '../Context'; +import { GetSet } from '../types'; + +function getControlPoints(x0, y0, x1, y1, x2, y2, t) { + const d01 = Math.sqrt(Math.pow(x1 - x0, 2) + Math.pow(y1 - y0, 2)), + d12 = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)), + fa = (t * d01) / (d01 + d12), + fb = (t * d12) / (d01 + d12), + p1x = x1 - fa * (x2 - x0), + p1y = y1 - fa * (y2 - y0), + p2x = x1 + fb * (x2 - x0), + p2y = y1 + fb * (y2 - y0); + + return [p1x, p1y, p2x, p2y]; +} + +function expandPoints(p, tension) { + const len = p.length, + allPoints: Array = []; + + for (let n = 2; n < len - 2; n += 2) { + const cp = getControlPoints( + p[n - 2], + p[n - 1], + p[n], + p[n + 1], + p[n + 2], + p[n + 3], + tension + ); + if (isNaN(cp[0])) { + continue; + } + allPoints.push(cp[0]); + allPoints.push(cp[1]); + allPoints.push(p[n]); + allPoints.push(p[n + 1]); + allPoints.push(cp[2]); + allPoints.push(cp[3]); + } + + return allPoints; +} + +export interface LineConfig extends ShapeConfig { + points?: + | number[] + | Int8Array + | Uint8Array + | Uint8ClampedArray + | Int16Array + | Uint16Array + | Int32Array + | Uint32Array + | Float32Array + | Float64Array; + tension?: number; + closed?: boolean; + bezier?: boolean; +} + +/** + * Line constructor.  Lines are defined by an array of points and + * a tension + * @constructor + * @memberof Konva + * @augments Konva.Shape + * @param {Object} config + * @param {Array} config.points Flat array of points coordinates. You should define them as [x1, y1, x2, y2, x3, y3]. + * @param {Number} [config.tension] Higher values will result in a more curvy line. A value of 0 will result in no interpolation. + * The default is 0 + * @param {Boolean} [config.closed] defines whether or not the line shape is closed, creating a polygon or blob + * @param {Boolean} [config.bezier] if no tension is provided but bezier=true, we draw the line as a bezier using the passed points + * @@shapeParams + * @@nodeParams + * @example + * var line = new Konva.Line({ + * x: 100, + * y: 50, + * points: [73, 70, 340, 23, 450, 60, 500, 20], + * stroke: 'red', + * tension: 1 + * }); + */ + +export class Line< + Config extends LineConfig = LineConfig +> extends Shape { + constructor(config?: Config) { + super(config); + this.on( + 'pointsChange.konva tensionChange.konva closedChange.konva bezierChange.konva', + function () { + this._clearCache('tensionPoints'); + } + ); + } + + _sceneFunc(context: Context) { + let points = this.points(), + length = points.length, + tension = this.tension(), + closed = this.closed(), + bezier = this.bezier(), + tp, + len, + n; + + if (!length) { + return; + } + + context.beginPath(); + context.moveTo(points[0], points[1]); + + // tension + if (tension !== 0 && length > 4) { + tp = this.getTensionPoints(); + len = tp.length; + n = closed ? 0 : 4; + + if (!closed) { + context.quadraticCurveTo(tp[0], tp[1], tp[2], tp[3]); + } + + while (n < len - 2) { + context.bezierCurveTo( + tp[n++], + tp[n++], + tp[n++], + tp[n++], + tp[n++], + tp[n++] + ); + } + + if (!closed) { + context.quadraticCurveTo( + tp[len - 2], + tp[len - 1], + points[length - 2], + points[length - 1] + ); + } + } else if (bezier) { + // no tension but bezier + n = 2; + + while (n < length) { + context.bezierCurveTo( + points[n++], + points[n++], + points[n++], + points[n++], + points[n++], + points[n++] + ); + } + } else { + // no tension + for (n = 2; n < length; n += 2) { + context.lineTo(points[n], points[n + 1]); + } + } + + // closed e.g. polygons and blobs + if (closed) { + context.closePath(); + context.fillStrokeShape(this); + } else { + // open e.g. lines and splines + context.strokeShape(this); + } + } + getTensionPoints() { + return this._getCache('tensionPoints', this._getTensionPoints); + } + _getTensionPoints() { + if (this.closed()) { + return this._getTensionPointsClosed(); + } else { + return expandPoints(this.points(), this.tension()); + } + } + _getTensionPointsClosed() { + const p = this.points(), + len = p.length, + tension = this.tension(), + firstControlPoints = getControlPoints( + p[len - 2], + p[len - 1], + p[0], + p[1], + p[2], + p[3], + tension + ), + lastControlPoints = getControlPoints( + p[len - 4], + p[len - 3], + p[len - 2], + p[len - 1], + p[0], + p[1], + tension + ), + middle = expandPoints(p, tension), + tp = [firstControlPoints[2], firstControlPoints[3]] + .concat(middle) + .concat([ + lastControlPoints[0], + lastControlPoints[1], + p[len - 2], + p[len - 1], + lastControlPoints[2], + lastControlPoints[3], + firstControlPoints[0], + firstControlPoints[1], + p[0], + p[1], + ]); + + return tp; + } + getWidth() { + return this.getSelfRect().width; + } + getHeight() { + return this.getSelfRect().height; + } + // overload size detection + getSelfRect() { + let points = this.points(); + if (points.length < 4) { + return { + x: points[0] || 0, + y: points[1] || 0, + width: 0, + height: 0, + }; + } + if (this.tension() !== 0) { + points = [ + points[0], + points[1], + ...this._getTensionPoints(), + points[points.length - 2], + points[points.length - 1], + ]; + } else { + points = this.points(); + } + let minX = points[0]; + let maxX = points[0]; + let minY = points[1]; + let maxY = points[1]; + let x, y; + for (let i = 0; i < points.length / 2; i++) { + x = points[i * 2]; + y = points[i * 2 + 1]; + minX = Math.min(minX, x); + maxX = Math.max(maxX, x); + minY = Math.min(minY, y); + maxY = Math.max(maxY, y); + } + return { + x: minX, + y: minY, + width: maxX - minX, + height: maxY - minY, + }; + } + + closed: GetSet; + bezier: GetSet; + tension: GetSet; + points: GetSet; +} + +Line.prototype.className = 'Line'; +Line.prototype._attrsAffectingSize = ['points', 'bezier', 'tension']; +_registerNode(Line); + +// add getters setters +Factory.addGetterSetter(Line, 'closed', false); + +/** + * get/set closed flag. The default is false + * @name Konva.Line#closed + * @method + * @param {Boolean} closed + * @returns {Boolean} + * @example + * // get closed flag + * var closed = line.closed(); + * + * // close the shape + * line.closed(true); + * + * // open the shape + * line.closed(false); + */ + +Factory.addGetterSetter(Line, 'bezier', false); + +/** + * get/set bezier flag. The default is false + * @name Konva.Line#bezier + * @method + * @param {Boolean} bezier + * @returns {Boolean} + * @example + * // get whether the line is a bezier + * var isBezier = line.bezier(); + * + * // set whether the line is a bezier + * line.bezier(true); + */ + +Factory.addGetterSetter(Line, 'tension', 0, getNumberValidator()); + +/** + * get/set tension + * @name Konva.Line#tension + * @method + * @param {Number} tension Higher values will result in a more curvy line. A value of 0 will result in no interpolation. The default is 0 + * @returns {Number} + * @example + * // get tension + * var tension = line.tension(); + * + * // set tension + * line.tension(3); + */ + +Factory.addGetterSetter(Line, 'points', [], getNumberArrayValidator()); +/** + * get/set points array. Points is a flat array [x1, y1, x2, y2]. It is flat for performance reasons. + * @name Konva.Line#points + * @method + * @param {Array} points + * @returns {Array} + * @example + * // get points + * var points = line.points(); + * + * // set points + * line.points([10, 20, 30, 40, 50, 60]); + * + * // push a new point + * line.points(line.points().concat([70, 80])); + */ diff --git a/src/shapes/Path.ts b/src/shapes/Path.ts new file mode 100644 index 000000000..e9e634932 --- /dev/null +++ b/src/shapes/Path.ts @@ -0,0 +1,942 @@ +import { Factory } from '../Factory'; +import { Shape, ShapeConfig } from '../Shape'; +import { _registerNode } from '../Global'; + +import { GetSet, PathSegment } from '../types'; +import { + getCubicArcLength, + getQuadraticArcLength, + t2length, +} from '../BezierFunctions'; + +export interface PathConfig extends ShapeConfig { + data?: string; +} +/** + * Path constructor. + * @author Jason Follas + * @constructor + * @memberof Konva + * @augments Konva.Shape + * @param {Object} config + * @param {String} config.data SVG data string + * @@shapeParams + * @@nodeParams + * @example + * var path = new Konva.Path({ + * x: 240, + * y: 40, + * data: 'M12.582,9.551C3.251,16.237,0.921,29.021,7.08,38.564l-2.36,1.689l4.893,2.262l4.893,2.262l-0.568-5.36l-0.567-5.359l-2.365,1.694c-4.657-7.375-2.83-17.185,4.352-22.33c7.451-5.338,17.817-3.625,23.156,3.824c5.337,7.449,3.625,17.813-3.821,23.152l2.857,3.988c9.617-6.893,11.827-20.277,4.935-29.896C35.591,4.87,22.204,2.658,12.582,9.551z', + * fill: 'green', + * scaleX: 2, + * scaleY: 2 + * }); + */ +export class Path extends Shape { + dataArray: PathSegment[] = []; + pathLength = 0; + + constructor(config?: PathConfig) { + super(config); + this._readDataAttribute(); + + this.on('dataChange.konva', function () { + this._readDataAttribute(); + }); + } + + _readDataAttribute() { + this.dataArray = Path.parsePathData(this.data()); + this.pathLength = Path.getPathLength(this.dataArray); + } + + _sceneFunc(context) { + const ca = this.dataArray; + + // context position + context.beginPath(); + let isClosed = false; + for (let n = 0; n < ca.length; n++) { + const c = ca[n].command; + const p = ca[n].points; + switch (c) { + case 'L': + context.lineTo(p[0], p[1]); + break; + case 'M': + context.moveTo(p[0], p[1]); + break; + case 'C': + context.bezierCurveTo(p[0], p[1], p[2], p[3], p[4], p[5]); + break; + case 'Q': + context.quadraticCurveTo(p[0], p[1], p[2], p[3]); + break; + case 'A': + var cx = p[0], + cy = p[1], + rx = p[2], + ry = p[3], + theta = p[4], + dTheta = p[5], + psi = p[6], + fs = p[7]; + + var r = rx > ry ? rx : ry; + var scaleX = rx > ry ? 1 : rx / ry; + var scaleY = rx > ry ? ry / rx : 1; + + context.translate(cx, cy); + context.rotate(psi); + context.scale(scaleX, scaleY); + context.arc(0, 0, r, theta, theta + dTheta, 1 - fs); + context.scale(1 / scaleX, 1 / scaleY); + context.rotate(-psi); + context.translate(-cx, -cy); + + break; + case 'z': + isClosed = true; + context.closePath(); + break; + } + } + + if (!isClosed && !this.hasFill()) { + context.strokeShape(this); + } else { + context.fillStrokeShape(this); + } + } + getSelfRect() { + let points: Array = []; + this.dataArray.forEach(function (data) { + if (data.command === 'A') { + // Approximates by breaking curve into line segments + const start = data.points[4]; + // 4 = theta + const dTheta = data.points[5]; + // 5 = dTheta + const end = data.points[4] + dTheta; + let inc = Math.PI / 180.0; + // 1 degree resolution + if (Math.abs(start - end) < inc) { + inc = Math.abs(start - end); + } + if (dTheta < 0) { + // clockwise + for (let t = start - inc; t > end; t -= inc) { + const point = Path.getPointOnEllipticalArc( + data.points[0], + data.points[1], + data.points[2], + data.points[3], + t, + 0 + ); + points.push(point.x, point.y); + } + } else { + // counter-clockwise + for (let t = start + inc; t < end; t += inc) { + const point = Path.getPointOnEllipticalArc( + data.points[0], + data.points[1], + data.points[2], + data.points[3], + t, + 0 + ); + points.push(point.x, point.y); + } + } + } else if (data.command === 'C') { + // Approximates by breaking curve into 100 line segments + for (let t = 0.0; t <= 1; t += 0.01) { + const point = Path.getPointOnCubicBezier( + t, + data.start.x, + data.start.y, + data.points[0], + data.points[1], + data.points[2], + data.points[3], + data.points[4], + data.points[5] + ); + points.push(point.x, point.y); + } + } else { + // TODO: how can we calculate bezier curves better? + points = points.concat(data.points); + } + }); + let minX = points[0]; + let maxX = points[0]; + let minY = points[1]; + let maxY = points[1]; + let x, y; + for (let i = 0; i < points.length / 2; i++) { + x = points[i * 2]; + y = points[i * 2 + 1]; + + // skip bad values + if (!isNaN(x)) { + minX = Math.min(minX, x); + maxX = Math.max(maxX, x); + } + if (!isNaN(y)) { + minY = Math.min(minY, y); + maxY = Math.max(maxY, y); + } + } + return { + x: minX, + y: minY, + width: maxX - minX, + height: maxY - minY, + }; + } + /** + * Return length of the path. + * @method + * @name Konva.Path#getLength + * @returns {Number} length + * @example + * var length = path.getLength(); + */ + getLength() { + return this.pathLength; + } + /** + * Get point on path at specific length of the path + * @method + * @name Konva.Path#getPointAtLength + * @param {Number} length length + * @returns {Object} point {x,y} point + * @example + * var point = path.getPointAtLength(10); + */ + getPointAtLength(length) { + return Path.getPointAtLengthOfDataArray(length, this.dataArray); + } + + data: GetSet; + + static getLineLength(x1, y1, x2, y2) { + return Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)); + } + + static getPathLength(dataArray: PathSegment[]) { + let pathLength = 0; + for (let i = 0; i < dataArray.length; ++i) { + pathLength += dataArray[i].pathLength; + } + return pathLength; + } + + static getPointAtLengthOfDataArray(length: number, dataArray: PathSegment[]) { + let points: number[], + i = 0, + ii = dataArray.length; + + if (!ii) { + return null; + } + + while (i < ii && length > dataArray[i].pathLength) { + length -= dataArray[i].pathLength; + ++i; + } + + if (i === ii) { + points = dataArray[i - 1].points.slice(-2); + return { + x: points[0], + y: points[1], + }; + } + + if (length < 0.01) { + points = dataArray[i].points.slice(0, 2); + return { + x: points[0], + y: points[1], + }; + } + + const cp = dataArray[i]; + const p = cp.points; + switch (cp.command) { + case 'L': + return Path.getPointOnLine(length, cp.start.x, cp.start.y, p[0], p[1]); + case 'C': + return Path.getPointOnCubicBezier( + t2length(length, Path.getPathLength(dataArray), (i) => { + return getCubicArcLength( + [cp.start.x, p[0], p[2], p[4]], + [cp.start.y, p[1], p[3], p[5]], + i + ); + }), + cp.start.x, + cp.start.y, + p[0], + p[1], + p[2], + p[3], + p[4], + p[5] + ); + case 'Q': + return Path.getPointOnQuadraticBezier( + t2length(length, Path.getPathLength(dataArray), (i) => { + return getQuadraticArcLength( + [cp.start.x, p[0], p[2]], + [cp.start.y, p[1], p[3]], + i + ); + }), + cp.start.x, + cp.start.y, + p[0], + p[1], + p[2], + p[3] + ); + case 'A': + var cx = p[0], + cy = p[1], + rx = p[2], + ry = p[3], + theta = p[4], + dTheta = p[5], + psi = p[6]; + theta += (dTheta * length) / cp.pathLength; + return Path.getPointOnEllipticalArc(cx, cy, rx, ry, theta, psi); + } + + return null; + } + + static getPointOnLine( + dist: number, + P1x: number, + P1y: number, + P2x: number, + P2y: number, + fromX?: number, + fromY?: number + ) { + fromX = fromX ?? P1x; + fromY = fromY ?? P1y; + + const len = this.getLineLength(P1x, P1y, P2x, P2y); + if (len < 1e-10) { + return { x: P1x, y: P1y }; + } + + if (P2x === P1x) { + // Vertical line + return { x: fromX, y: fromY + (P2y > P1y ? dist : -dist) }; + } + + const m = (P2y - P1y) / (P2x - P1x); + const run = Math.sqrt((dist * dist) / (1 + m * m)) * (P2x < P1x ? -1 : 1); + const rise = m * run; + + if (Math.abs(fromY - P1y - m * (fromX - P1x)) < 1e-10) { + return { x: fromX + run, y: fromY + rise }; + } + + const u = + ((fromX - P1x) * (P2x - P1x) + (fromY - P1y) * (P2y - P1y)) / (len * len); + const ix = P1x + u * (P2x - P1x); + const iy = P1y + u * (P2y - P1y); + const pRise = this.getLineLength(fromX, fromY, ix, iy); + const pRun = Math.sqrt(dist * dist - pRise * pRise); + const adjustedRun = + Math.sqrt((pRun * pRun) / (1 + m * m)) * (P2x < P1x ? -1 : 1); + const adjustedRise = m * adjustedRun; + + return { x: ix + adjustedRun, y: iy + adjustedRise }; + } + + static getPointOnCubicBezier(pct, P1x, P1y, P2x, P2y, P3x, P3y, P4x, P4y) { + function CB1(t) { + return t * t * t; + } + function CB2(t) { + return 3 * t * t * (1 - t); + } + function CB3(t) { + return 3 * t * (1 - t) * (1 - t); + } + function CB4(t) { + return (1 - t) * (1 - t) * (1 - t); + } + const x = P4x * CB1(pct) + P3x * CB2(pct) + P2x * CB3(pct) + P1x * CB4(pct); + const y = P4y * CB1(pct) + P3y * CB2(pct) + P2y * CB3(pct) + P1y * CB4(pct); + + return { + x: x, + y: y, + }; + } + static getPointOnQuadraticBezier(pct, P1x, P1y, P2x, P2y, P3x, P3y) { + function QB1(t) { + return t * t; + } + function QB2(t) { + return 2 * t * (1 - t); + } + function QB3(t) { + return (1 - t) * (1 - t); + } + const x = P3x * QB1(pct) + P2x * QB2(pct) + P1x * QB3(pct); + const y = P3y * QB1(pct) + P2y * QB2(pct) + P1y * QB3(pct); + + return { + x: x, + y: y, + }; + } + static getPointOnEllipticalArc( + cx: number, + cy: number, + rx: number, + ry: number, + theta: number, + psi: number + ) { + const cosPsi = Math.cos(psi), + sinPsi = Math.sin(psi); + const pt = { + x: rx * Math.cos(theta), + y: ry * Math.sin(theta), + }; + return { + x: cx + (pt.x * cosPsi - pt.y * sinPsi), + y: cy + (pt.x * sinPsi + pt.y * cosPsi), + }; + } + /* + * get parsed data array from the data + * string. V, v, H, h, and l data are converted to + * L data for the purpose of high performance Path + * rendering + */ + static parsePathData(data): PathSegment[] { + // Path Data Segment must begin with a moveTo + //m (x y)+ Relative moveTo (subsequent points are treated as lineTo) + //M (x y)+ Absolute moveTo (subsequent points are treated as lineTo) + //l (x y)+ Relative lineTo + //L (x y)+ Absolute LineTo + //h (x)+ Relative horizontal lineTo + //H (x)+ Absolute horizontal lineTo + //v (y)+ Relative vertical lineTo + //V (y)+ Absolute vertical lineTo + //z (closepath) + //Z (closepath) + //c (x1 y1 x2 y2 x y)+ Relative Bezier curve + //C (x1 y1 x2 y2 x y)+ Absolute Bezier curve + //q (x1 y1 x y)+ Relative Quadratic Bezier + //Q (x1 y1 x y)+ Absolute Quadratic Bezier + //t (x y)+ Shorthand/Smooth Relative Quadratic Bezier + //T (x y)+ Shorthand/Smooth Absolute Quadratic Bezier + //s (x2 y2 x y)+ Shorthand/Smooth Relative Bezier curve + //S (x2 y2 x y)+ Shorthand/Smooth Absolute Bezier curve + //a (rx ry x-axis-rotation large-arc-flag sweep-flag x y)+ Relative Elliptical Arc + //A (rx ry x-axis-rotation large-arc-flag sweep-flag x y)+ Absolute Elliptical Arc + + // return early if data is not defined + if (!data) { + return []; + } + + // command string + let cs = data; + + // command chars + const cc = [ + 'm', + 'M', + 'l', + 'L', + 'v', + 'V', + 'h', + 'H', + 'z', + 'Z', + 'c', + 'C', + 'q', + 'Q', + 't', + 'T', + 's', + 'S', + 'a', + 'A', + ]; + // convert white spaces to commas + cs = cs.replace(new RegExp(' ', 'g'), ','); + // create pipes so that we can split the data + for (var n = 0; n < cc.length; n++) { + cs = cs.replace(new RegExp(cc[n], 'g'), '|' + cc[n]); + } + // create array + const arr = cs.split('|'); + const ca: PathSegment[] = []; + const coords: string[] = []; + // init context point + let cpx = 0; + let cpy = 0; + + const re = /([-+]?((\d+\.\d+)|((\d+)|(\.\d+)))(?:e[-+]?\d+)?)/gi; + let match; + for (n = 1; n < arr.length; n++) { + let str = arr[n]; + let c = str.charAt(0); + str = str.slice(1); + + coords.length = 0; + while ((match = re.exec(str))) { + coords.push(match[0]); + } + + // while ((match = re.exec(str))) { + // coords.push(match[0]); + // } + const p: number[] = []; + + for (let j = 0, jlen = coords.length; j < jlen; j++) { + // extra case for merged flags + if (coords[j] === '00') { + p.push(0, 0); + continue; + } + const parsed = parseFloat(coords[j]); + if (!isNaN(parsed)) { + p.push(parsed); + } else { + p.push(0); + } + } + + while (p.length > 0) { + if (isNaN(p[0])) { + // case for a trailing comma before next command + break; + } + + let cmd: string = ''; + let points: number[] = []; + const startX = cpx, + startY = cpy; + // Move var from within the switch to up here (jshint) + var prevCmd, ctlPtx, ctlPty; // Ss, Tt + var rx, ry, psi, fa, fs, x1, y1; // Aa + + // convert l, H, h, V, and v to L + switch (c) { + // Note: Keep the lineTo's above the moveTo's in this switch + case 'l': + cpx += p.shift()!; + cpy += p.shift()!; + cmd = 'L'; + points.push(cpx, cpy); + break; + case 'L': + cpx = p.shift()!; + cpy = p.shift()!; + points.push(cpx, cpy); + break; + // Note: lineTo handlers need to be above this point + case 'm': + var dx = p.shift()!; + var dy = p.shift()!; + cpx += dx; + cpy += dy; + cmd = 'M'; + // After closing the path move the current position + // to the the first point of the path (if any). + if (ca.length > 2 && ca[ca.length - 1].command === 'z') { + for (let idx = ca.length - 2; idx >= 0; idx--) { + if (ca[idx].command === 'M') { + cpx = ca[idx].points[0] + dx; + cpy = ca[idx].points[1] + dy; + break; + } + } + } + points.push(cpx, cpy); + c = 'l'; + // subsequent points are treated as relative lineTo + break; + case 'M': + cpx = p.shift()!; + cpy = p.shift()!; + cmd = 'M'; + points.push(cpx, cpy); + c = 'L'; + // subsequent points are treated as absolute lineTo + break; + + case 'h': + cpx += p.shift()!; + cmd = 'L'; + points.push(cpx, cpy); + break; + case 'H': + cpx = p.shift()!; + cmd = 'L'; + points.push(cpx, cpy); + break; + case 'v': + cpy += p.shift()!; + cmd = 'L'; + points.push(cpx, cpy); + break; + case 'V': + cpy = p.shift()!; + cmd = 'L'; + points.push(cpx, cpy); + break; + case 'C': + points.push(p.shift()!, p.shift()!, p.shift()!, p.shift()!); + cpx = p.shift()!; + cpy = p.shift()!; + points.push(cpx, cpy); + break; + case 'c': + points.push( + cpx + p.shift()!, + cpy + p.shift()!, + cpx + p.shift()!, + cpy + p.shift()! + ); + cpx += p.shift()!; + cpy += p.shift()!; + cmd = 'C'; + points.push(cpx, cpy); + break; + case 'S': + ctlPtx = cpx; + ctlPty = cpy; + prevCmd = ca[ca.length - 1]; + if (prevCmd.command === 'C') { + ctlPtx = cpx + (cpx - prevCmd.points[2]); + ctlPty = cpy + (cpy - prevCmd.points[3]); + } + points.push(ctlPtx, ctlPty, p.shift()!, p.shift()!); + cpx = p.shift()!; + cpy = p.shift()!; + cmd = 'C'; + points.push(cpx, cpy); + break; + case 's': + ctlPtx = cpx; + ctlPty = cpy; + prevCmd = ca[ca.length - 1]; + if (prevCmd.command === 'C') { + ctlPtx = cpx + (cpx - prevCmd.points[2]); + ctlPty = cpy + (cpy - prevCmd.points[3]); + } + points.push(ctlPtx, ctlPty, cpx + p.shift()!, cpy + p.shift()!); + cpx += p.shift()!; + cpy += p.shift()!; + cmd = 'C'; + points.push(cpx, cpy); + break; + case 'Q': + points.push(p.shift()!, p.shift()!); + cpx = p.shift()!; + cpy = p.shift()!; + points.push(cpx, cpy); + break; + case 'q': + points.push(cpx + p.shift()!, cpy + p.shift()!); + cpx += p.shift()!; + cpy += p.shift()!; + cmd = 'Q'; + points.push(cpx, cpy); + break; + case 'T': + ctlPtx = cpx; + ctlPty = cpy; + prevCmd = ca[ca.length - 1]; + if (prevCmd.command === 'Q') { + ctlPtx = cpx + (cpx - prevCmd.points[0]); + ctlPty = cpy + (cpy - prevCmd.points[1]); + } + cpx = p.shift()!; + cpy = p.shift()!; + cmd = 'Q'; + points.push(ctlPtx, ctlPty, cpx, cpy); + break; + case 't': + ctlPtx = cpx; + ctlPty = cpy; + prevCmd = ca[ca.length - 1]; + if (prevCmd.command === 'Q') { + ctlPtx = cpx + (cpx - prevCmd.points[0]); + ctlPty = cpy + (cpy - prevCmd.points[1]); + } + cpx += p.shift()!; + cpy += p.shift()!; + cmd = 'Q'; + points.push(ctlPtx, ctlPty, cpx, cpy); + break; + case 'A': + rx = p.shift()!; + ry = p.shift()!; + psi = p.shift()!; + fa = p.shift()!; + fs = p.shift()!; + x1 = cpx; + y1 = cpy; + cpx = p.shift()!; + cpy = p.shift()!; + cmd = 'A'; + points = this.convertEndpointToCenterParameterization( + x1, + y1, + cpx, + cpy, + fa, + fs, + rx, + ry, + psi + ); + break; + case 'a': + rx = p.shift(); + ry = p.shift(); + psi = p.shift(); + fa = p.shift(); + fs = p.shift(); + x1 = cpx; + y1 = cpy; + cpx += p.shift()!; + cpy += p.shift()!; + cmd = 'A'; + points = this.convertEndpointToCenterParameterization( + x1, + y1, + cpx, + cpy, + fa, + fs, + rx, + ry, + psi + ); + break; + } + + ca.push({ + command: cmd || c, + points: points, + start: { + x: startX, + y: startY, + }, + pathLength: this.calcLength(startX, startY, cmd || c, points), + }); + } + + if (c === 'z' || c === 'Z') { + ca.push({ + command: 'z', + points: [], + start: undefined as any, + pathLength: 0, + }); + } + } + + return ca; + } + static calcLength(x, y, cmd, points) { + let len, p1, p2, t; + const path = Path; + + switch (cmd) { + case 'L': + return path.getLineLength(x, y, points[0], points[1]); + case 'C': + return getCubicArcLength( + [x, points[0], points[2], points[4]], + [y, points[1], points[3], points[5]], + 1 + ); + case 'Q': + return getQuadraticArcLength( + [x, points[0], points[2]], + [y, points[1], points[3]], + 1 + ); + case 'A': + // Approximates by breaking curve into line segments + len = 0.0; + var start = points[4]; + // 4 = theta + var dTheta = points[5]; + // 5 = dTheta + var end = points[4] + dTheta; + var inc = Math.PI / 180.0; + // 1 degree resolution + if (Math.abs(start - end) < inc) { + inc = Math.abs(start - end); + } + // Note: for purpose of calculating arc length, not going to worry about rotating X-axis by angle psi + p1 = path.getPointOnEllipticalArc( + points[0], + points[1], + points[2], + points[3], + start, + 0 + ); + if (dTheta < 0) { + // clockwise + for (t = start - inc; t > end; t -= inc) { + p2 = path.getPointOnEllipticalArc( + points[0], + points[1], + points[2], + points[3], + t, + 0 + ); + len += path.getLineLength(p1.x, p1.y, p2.x, p2.y); + p1 = p2; + } + } else { + // counter-clockwise + for (t = start + inc; t < end; t += inc) { + p2 = path.getPointOnEllipticalArc( + points[0], + points[1], + points[2], + points[3], + t, + 0 + ); + len += path.getLineLength(p1.x, p1.y, p2.x, p2.y); + p1 = p2; + } + } + p2 = path.getPointOnEllipticalArc( + points[0], + points[1], + points[2], + points[3], + end, + 0 + ); + len += path.getLineLength(p1.x, p1.y, p2.x, p2.y); + + return len; + } + + return 0; + } + static convertEndpointToCenterParameterization( + x1, + y1, + x2, + y2, + fa, + fs, + rx, + ry, + psiDeg + ) { + // Derived from: http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes + const psi = psiDeg * (Math.PI / 180.0); + const xp = + (Math.cos(psi) * (x1 - x2)) / 2.0 + (Math.sin(psi) * (y1 - y2)) / 2.0; + const yp = + (-1 * Math.sin(psi) * (x1 - x2)) / 2.0 + + (Math.cos(psi) * (y1 - y2)) / 2.0; + + const lambda = (xp * xp) / (rx * rx) + (yp * yp) / (ry * ry); + + if (lambda > 1) { + rx *= Math.sqrt(lambda); + ry *= Math.sqrt(lambda); + } + + let f = Math.sqrt( + (rx * rx * (ry * ry) - rx * rx * (yp * yp) - ry * ry * (xp * xp)) / + (rx * rx * (yp * yp) + ry * ry * (xp * xp)) + ); + + if (fa === fs) { + f *= -1; + } + if (isNaN(f)) { + f = 0; + } + + const cxp = (f * rx * yp) / ry; + const cyp = (f * -ry * xp) / rx; + + const cx = (x1 + x2) / 2.0 + Math.cos(psi) * cxp - Math.sin(psi) * cyp; + const cy = (y1 + y2) / 2.0 + Math.sin(psi) * cxp + Math.cos(psi) * cyp; + + const vMag = function (v) { + return Math.sqrt(v[0] * v[0] + v[1] * v[1]); + }; + const vRatio = function (u, v) { + return (u[0] * v[0] + u[1] * v[1]) / (vMag(u) * vMag(v)); + }; + const vAngle = function (u, v) { + return (u[0] * v[1] < u[1] * v[0] ? -1 : 1) * Math.acos(vRatio(u, v)); + }; + const theta = vAngle([1, 0], [(xp - cxp) / rx, (yp - cyp) / ry]); + const u = [(xp - cxp) / rx, (yp - cyp) / ry]; + const v = [(-1 * xp - cxp) / rx, (-1 * yp - cyp) / ry]; + let dTheta = vAngle(u, v); + + if (vRatio(u, v) <= -1) { + dTheta = Math.PI; + } + if (vRatio(u, v) >= 1) { + dTheta = 0; + } + if (fs === 0 && dTheta > 0) { + dTheta = dTheta - 2 * Math.PI; + } + if (fs === 1 && dTheta < 0) { + dTheta = dTheta + 2 * Math.PI; + } + return [cx, cy, rx, ry, theta, dTheta, psi, fs]; + } +} + +Path.prototype.className = 'Path'; +Path.prototype._attrsAffectingSize = ['data']; +_registerNode(Path); + +/** + * get/set SVG path data string. This method + * also automatically parses the data string + * into a data array. Currently supported SVG data: + * M, m, L, l, H, h, V, v, Q, q, T, t, C, c, S, s, A, a, Z, z + * @name Konva.Path#data + * @method + * @param {String} data svg path string + * @returns {String} + * @example + * // get data + * var data = path.data(); + * + * // set data + * path.data('M200,100h100v50z'); + */ +Factory.addGetterSetter(Path, 'data'); diff --git a/src/shapes/Rect.ts b/src/shapes/Rect.ts new file mode 100644 index 000000000..ec8ad79de --- /dev/null +++ b/src/shapes/Rect.ts @@ -0,0 +1,78 @@ +import { Factory } from '../Factory'; +import { Shape, ShapeConfig } from '../Shape'; +import { _registerNode } from '../Global'; + +import { Util } from '../Util'; +import { GetSet } from '../types'; +import { Context } from '../Context'; +import { getNumberOrArrayOfNumbersValidator } from '../Validators'; + +export interface RectConfig extends ShapeConfig { + cornerRadius?: number | number[]; +} + +/** + * Rect constructor + * @constructor + * @memberof Konva + * @augments Konva.Shape + * @param {Object} config + * @param {Number} [config.cornerRadius] + * @@shapeParams + * @@nodeParams + * @example + * var rect = new Konva.Rect({ + * width: 100, + * height: 50, + * fill: 'red', + * stroke: 'black', + * strokeWidth: 5 + * }); + */ +export class Rect extends Shape { + _sceneFunc(context: Context) { + const cornerRadius = this.cornerRadius(), + width = this.width(), + height = this.height(); + + context.beginPath(); + + if (!cornerRadius) { + // simple rect - don't bother doing all that complicated maths stuff. + context.rect(0, 0, width, height); + } else { + Util.drawRoundedRectPath(context, width, height, cornerRadius); + } + context.closePath(); + context.fillStrokeShape(this); + } + + cornerRadius: GetSet; +} + +Rect.prototype.className = 'Rect'; +_registerNode(Rect); + +/** + * get/set corner radius + * @method + * @name Konva.Rect#cornerRadius + * @param {Number} cornerRadius + * @returns {Number} + * @example + * // get corner radius + * var cornerRadius = rect.cornerRadius(); + * + * // set corner radius + * rect.cornerRadius(10); + * + * // set different corner radius values + * // top-left, top-right, bottom-right, bottom-left + * rect.cornerRadius([0, 10, 20, 30]); + */ +Factory.addGetterSetter( + Rect, + 'cornerRadius', + 0, + getNumberOrArrayOfNumbersValidator(4) +); diff --git a/src/shapes/RegularPolygon.ts b/src/shapes/RegularPolygon.ts new file mode 100644 index 000000000..6ade36cfa --- /dev/null +++ b/src/shapes/RegularPolygon.ts @@ -0,0 +1,129 @@ +import { Factory } from '../Factory'; +import { Shape, ShapeConfig } from '../Shape'; +import { GetSet, Vector2d } from '../types'; +import { getNumberValidator } from '../Validators'; +import { _registerNode } from '../Global'; +import { Context } from '../Context'; + +export interface RegularPolygonConfig extends ShapeConfig { + sides: number; + radius: number; +} +/** + * RegularPolygon constructor. Examples include triangles, squares, pentagons, hexagons, etc. + * @constructor + * @memberof Konva + * @augments Konva.Shape + * @param {Object} config + * @param {Number} config.sides + * @param {Number} config.radius + * @@shapeParams + * @@nodeParams + * @example + * var hexagon = new Konva.RegularPolygon({ + * x: 100, + * y: 200, + * sides: 6, + * radius: 70, + * fill: 'red', + * stroke: 'black', + * strokeWidth: 4 + * }); + */ +export class RegularPolygon extends Shape { + _sceneFunc(context: Context) { + const points = this._getPoints(); + + context.beginPath(); + context.moveTo(points[0].x, points[0].y); + + for (let n = 1; n < points.length; n++) { + context.lineTo(points[n].x, points[n].y); + } + + context.closePath(); + context.fillStrokeShape(this); + } + _getPoints() { + const sides = this.attrs.sides as number; + const radius = this.attrs.radius || 0; + const points: Vector2d[] = []; + for (let n = 0; n < sides; n++) { + points.push({ + x: radius * Math.sin((n * 2 * Math.PI) / sides), + y: -1 * radius * Math.cos((n * 2 * Math.PI) / sides), + }); + } + return points; + } + getSelfRect() { + const points = this._getPoints(); + + let minX = points[0].x; + let maxX = points[0].y; + let minY = points[0].x; + let maxY = points[0].y; + points.forEach((point) => { + minX = Math.min(minX, point.x); + maxX = Math.max(maxX, point.x); + minY = Math.min(minY, point.y); + maxY = Math.max(maxY, point.y); + }); + return { + x: minX, + y: minY, + width: maxX - minX, + height: maxY - minY, + }; + } + getWidth() { + return this.radius() * 2; + } + getHeight() { + return this.radius() * 2; + } + setWidth(width: number) { + this.radius(width / 2); + } + setHeight(height: number) { + this.radius(height / 2); + } + + radius: GetSet; + sides: GetSet; +} + +RegularPolygon.prototype.className = 'RegularPolygon'; +RegularPolygon.prototype._centroid = true; +RegularPolygon.prototype._attrsAffectingSize = ['radius']; +_registerNode(RegularPolygon); + +/** + * get/set radius + * @method + * @name Konva.RegularPolygon#radius + * @param {Number} radius + * @returns {Number} + * @example + * // get radius + * var radius = shape.radius(); + * + * // set radius + * shape.radius(10); + */ +Factory.addGetterSetter(RegularPolygon, 'radius', 0, getNumberValidator()); + +/** + * get/set sides + * @method + * @name Konva.RegularPolygon#sides + * @param {Number} sides + * @returns {Number} + * @example + * // get sides + * var sides = shape.sides(); + * + * // set sides + * shape.sides(10); + */ +Factory.addGetterSetter(RegularPolygon, 'sides', 0, getNumberValidator()); diff --git a/src/shapes/Ring.ts b/src/shapes/Ring.ts new file mode 100644 index 000000000..f22a47696 --- /dev/null +++ b/src/shapes/Ring.ts @@ -0,0 +1,93 @@ +import { Factory } from '../Factory'; +import { Shape, ShapeConfig } from '../Shape'; +import { GetSet } from '../types'; +import { getNumberValidator } from '../Validators'; +import { _registerNode } from '../Global'; +import { Context } from '../Context'; + +export interface RingConfig extends ShapeConfig { + innerRadius: number; + outerRadius: number; +} + +const PIx2 = Math.PI * 2; +/** + * Ring constructor + * @constructor + * @augments Konva.Shape + * @memberof Konva + * @param {Object} config + * @param {Number} config.innerRadius + * @param {Number} config.outerRadius + * @@shapeParams + * @@nodeParams + * @example + * var ring = new Konva.Ring({ + * innerRadius: 40, + * outerRadius: 80, + * fill: 'red', + * stroke: 'black', + * strokeWidth: 5 + * }); + */ +export class Ring extends Shape { + _sceneFunc(context: Context) { + context.beginPath(); + context.arc(0, 0, this.innerRadius(), 0, PIx2, false); + context.moveTo(this.outerRadius(), 0); + context.arc(0, 0, this.outerRadius(), PIx2, 0, true); + context.closePath(); + context.fillStrokeShape(this); + } + getWidth() { + return this.outerRadius() * 2; + } + getHeight() { + return this.outerRadius() * 2; + } + setWidth(width: number) { + this.outerRadius(width / 2); + } + setHeight(height: number) { + this.outerRadius(height / 2); + } + + outerRadius: GetSet; + innerRadius: GetSet; +} + +Ring.prototype.className = 'Ring'; +Ring.prototype._centroid = true; +Ring.prototype._attrsAffectingSize = ['innerRadius', 'outerRadius']; +_registerNode(Ring); + +/** + * get/set innerRadius + * @method + * @name Konva.Ring#innerRadius + * @param {Number} innerRadius + * @returns {Number} + * @example + * // get inner radius + * var innerRadius = ring.innerRadius(); + * + * // set inner radius + * ring.innerRadius(20); + */ + +Factory.addGetterSetter(Ring, 'innerRadius', 0, getNumberValidator()); + +/** + * get/set outerRadius + * @name Konva.Ring#outerRadius + * @method + * @param {Number} outerRadius + * @returns {Number} + * @example + * // get outer radius + * var outerRadius = ring.outerRadius(); + * + * // set outer radius + * ring.outerRadius(20); + */ +Factory.addGetterSetter(Ring, 'outerRadius', 0, getNumberValidator()); diff --git a/src/shapes/Sprite.ts b/src/shapes/Sprite.ts new file mode 100644 index 000000000..a4ac4918d --- /dev/null +++ b/src/shapes/Sprite.ts @@ -0,0 +1,369 @@ +import { Factory } from '../Factory'; +import { Context } from '../Context'; +import { Shape, ShapeConfig } from '../Shape'; +import { Animation } from '../Animation'; +import { getNumberValidator } from '../Validators'; +import { _registerNode } from '../Global'; + +import { GetSet } from '../types'; + +export interface SpriteConfig extends ShapeConfig { + animation: string; + animations: any; + frameIndex?: number; + image: HTMLImageElement; + frameRate?: number; +} + +/** + * Sprite constructor + * @constructor + * @memberof Konva + * @augments Konva.Shape + * @param {Object} config + * @param {String} config.animation animation key + * @param {Object} config.animations animation map + * @param {Integer} [config.frameIndex] animation frame index + * @param {Image} config.image image object + * @param {Integer} [config.frameRate] animation frame rate + * @@shapeParams + * @@nodeParams + * @example + * var imageObj = new Image(); + * imageObj.onload = function() { + * var sprite = new Konva.Sprite({ + * x: 200, + * y: 100, + * image: imageObj, + * animation: 'standing', + * animations: { + * standing: [ + * // x, y, width, height (6 frames) + * 0, 0, 49, 109, + * 52, 0, 49, 109, + * 105, 0, 49, 109, + * 158, 0, 49, 109, + * 210, 0, 49, 109, + * 262, 0, 49, 109 + * ], + * kicking: [ + * // x, y, width, height (6 frames) + * 0, 109, 45, 98, + * 45, 109, 45, 98, + * 95, 109, 63, 98, + * 156, 109, 70, 98, + * 229, 109, 60, 98, + * 287, 109, 41, 98 + * ] + * }, + * frameRate: 7, + * frameIndex: 0 + * }); + * }; + * imageObj.src = '/path/to/image.jpg' + */ +export class Sprite extends Shape { + _updated = true; + anim: Animation; + interval: any; + constructor(config: SpriteConfig) { + super(config); + this.anim = new Animation(() => { + // if we don't need to redraw layer we should return false + const updated = this._updated; + this._updated = false; + return updated; + }); + this.on('animationChange.konva', function () { + // reset index when animation changes + this.frameIndex(0); + }); + this.on('frameIndexChange.konva', function () { + this._updated = true; + }); + // smooth change for frameRate + this.on('frameRateChange.konva', function () { + if (!this.anim.isRunning()) { + return; + } + clearInterval(this.interval); + this._setInterval(); + }); + } + + _sceneFunc(context: Context) { + const anim = this.animation(), + index = this.frameIndex(), + ix4 = index * 4, + set = this.animations()[anim], + offsets = this.frameOffsets(), + x = set[ix4 + 0], + y = set[ix4 + 1], + width = set[ix4 + 2], + height = set[ix4 + 3], + image = this.image(); + + if (this.hasFill() || this.hasStroke()) { + context.beginPath(); + context.rect(0, 0, width, height); + context.closePath(); + context.fillStrokeShape(this); + } + + if (image) { + if (offsets) { + const offset = offsets[anim], + ix2 = index * 2; + context.drawImage( + image, + x, + y, + width, + height, + offset[ix2 + 0], + offset[ix2 + 1], + width, + height + ); + } else { + context.drawImage(image, x, y, width, height, 0, 0, width, height); + } + } + } + _hitFunc(context: Context) { + const anim = this.animation(), + index = this.frameIndex(), + ix4 = index * 4, + set = this.animations()[anim], + offsets = this.frameOffsets(), + width = set[ix4 + 2], + height = set[ix4 + 3]; + + context.beginPath(); + if (offsets) { + const offset = offsets[anim]; + const ix2 = index * 2; + context.rect(offset[ix2 + 0], offset[ix2 + 1], width, height); + } else { + context.rect(0, 0, width, height); + } + context.closePath(); + context.fillShape(this); + } + + _useBufferCanvas() { + return super._useBufferCanvas(true); + } + + _setInterval() { + const that = this; + this.interval = setInterval(function () { + that._updateIndex(); + }, 1000 / this.frameRate()); + } + /** + * start sprite animation + * @method + * @name Konva.Sprite#start + */ + start() { + if (this.isRunning()) { + return; + } + const layer = this.getLayer(); + + /* + * animation object has no executable function because + * the updates are done with a fixed FPS with the setInterval + * below. The anim object only needs the layer reference for + * redraw + */ + this.anim.setLayers(layer); + this._setInterval(); + this.anim.start(); + } + /** + * stop sprite animation + * @method + * @name Konva.Sprite#stop + */ + stop() { + this.anim.stop(); + clearInterval(this.interval); + } + /** + * determine if animation of sprite is running or not. returns true or false + * @method + * @name Konva.Sprite#isRunning + * @returns {Boolean} + */ + isRunning() { + return this.anim.isRunning(); + } + _updateIndex() { + const index = this.frameIndex(), + animation = this.animation(), + animations = this.animations(), + anim = animations[animation], + len = anim.length / 4; + + if (index < len - 1) { + this.frameIndex(index + 1); + } else { + this.frameIndex(0); + } + } + + frameIndex: GetSet; + animation: GetSet; + image: GetSet; + animations: GetSet; + frameOffsets: GetSet; + frameRate: GetSet; +} + +Sprite.prototype.className = 'Sprite'; +_registerNode(Sprite); + +// add getters setters +Factory.addGetterSetter(Sprite, 'animation'); + +/** + * get/set animation key + * @name Konva.Sprite#animation + * @method + * @param {String} anim animation key + * @returns {String} + * @example + * // get animation key + * var animation = sprite.animation(); + * + * // set animation key + * sprite.animation('kicking'); + */ + +Factory.addGetterSetter(Sprite, 'animations'); + +/** + * get/set animations map + * @name Konva.Sprite#animations + * @method + * @param {Object} animations + * @returns {Object} + * @example + * // get animations map + * var animations = sprite.animations(); + * + * // set animations map + * sprite.animations({ + * standing: [ + * // x, y, width, height (6 frames) + * 0, 0, 49, 109, + * 52, 0, 49, 109, + * 105, 0, 49, 109, + * 158, 0, 49, 109, + * 210, 0, 49, 109, + * 262, 0, 49, 109 + * ], + * kicking: [ + * // x, y, width, height (6 frames) + * 0, 109, 45, 98, + * 45, 109, 45, 98, + * 95, 109, 63, 98, + * 156, 109, 70, 98, + * 229, 109, 60, 98, + * 287, 109, 41, 98 + * ] + * }); + */ + +Factory.addGetterSetter(Sprite, 'frameOffsets'); + +/** + * get/set offsets map + * @name Konva.Sprite#offsets + * @method + * @param {Object} offsets + * @returns {Object} + * @example + * // get offsets map + * var offsets = sprite.offsets(); + * + * // set offsets map + * sprite.offsets({ + * standing: [ + * // x, y (6 frames) + * 0, 0, + * 0, 0, + * 5, 0, + * 0, 0, + * 0, 3, + * 2, 0 + * ], + * kicking: [ + * // x, y (6 frames) + * 0, 5, + * 5, 0, + * 10, 0, + * 0, 0, + * 2, 1, + * 0, 0 + * ] + * }); + */ + +Factory.addGetterSetter(Sprite, 'image'); + +/** + * get/set image + * @name Konva.Sprite#image + * @method + * @param {Image} image + * @returns {Image} + * @example + * // get image + * var image = sprite.image(); + * + * // set image + * sprite.image(imageObj); + */ + +Factory.addGetterSetter(Sprite, 'frameIndex', 0, getNumberValidator()); + +/** + * set/set animation frame index + * @name Konva.Sprite#frameIndex + * @method + * @param {Integer} frameIndex + * @returns {Integer} + * @example + * // get animation frame index + * var frameIndex = sprite.frameIndex(); + * + * // set animation frame index + * sprite.frameIndex(3); + */ + +Factory.addGetterSetter(Sprite, 'frameRate', 17, getNumberValidator()); + +/** + * get/set frame rate in frames per second. Increase this number to make the sprite + * animation run faster, and decrease the number to make the sprite animation run slower + * The default is 17 frames per second + * @name Konva.Sprite#frameRate + * @method + * @param {Integer} frameRate + * @returns {Integer} + * @example + * // get frame rate + * var frameRate = sprite.frameRate(); + * + * // set frame rate to 2 frames per second + * sprite.frameRate(2); + */ + +Factory.backCompat(Sprite, { + index: 'frameIndex', + getIndex: 'getFrameIndex', + setIndex: 'setFrameIndex', +}); diff --git a/src/shapes/Star.ts b/src/shapes/Star.ts new file mode 100644 index 000000000..8dee6d542 --- /dev/null +++ b/src/shapes/Star.ts @@ -0,0 +1,124 @@ +import { Factory } from '../Factory'; +import { Context } from '../Context'; +import { Shape, ShapeConfig } from '../Shape'; +import { getNumberValidator } from '../Validators'; +import { _registerNode } from '../Global'; + +import { GetSet } from '../types'; + +export interface StarConfig extends ShapeConfig { + numPoints: number; + innerRadius: number; + outerRadius: number; +} + +/** + * Star constructor + * @constructor + * @memberof Konva + * @augments Konva.Shape + * @param {Object} config + * @param {Integer} config.numPoints + * @param {Number} config.innerRadius + * @param {Number} config.outerRadius + * @@shapeParams + * @@nodeParams + * @example + * var star = new Konva.Star({ + * x: 100, + * y: 200, + * numPoints: 5, + * innerRadius: 70, + * outerRadius: 70, + * fill: 'red', + * stroke: 'black', + * strokeWidth: 4 + * }); + */ +export class Star extends Shape { + _sceneFunc(context: Context) { + const innerRadius = this.innerRadius(), + outerRadius = this.outerRadius(), + numPoints = this.numPoints(); + + context.beginPath(); + context.moveTo(0, 0 - outerRadius); + + for (let n = 1; n < numPoints * 2; n++) { + const radius = n % 2 === 0 ? outerRadius : innerRadius; + const x = radius * Math.sin((n * Math.PI) / numPoints); + const y = -1 * radius * Math.cos((n * Math.PI) / numPoints); + context.lineTo(x, y); + } + context.closePath(); + + context.fillStrokeShape(this); + } + getWidth() { + return this.outerRadius() * 2; + } + getHeight() { + return this.outerRadius() * 2; + } + setWidth(width: number) { + this.outerRadius(width / 2); + } + setHeight(height: number) { + this.outerRadius(height / 2); + } + + outerRadius: GetSet; + innerRadius: GetSet; + numPoints: GetSet; +} + +Star.prototype.className = 'Star'; +Star.prototype._centroid = true; +Star.prototype._attrsAffectingSize = ['innerRadius', 'outerRadius']; +_registerNode(Star); + +/** + * get/set number of points + * @name Konva.Star#numPoints + * @method + * @param {Number} numPoints + * @returns {Number} + * @example + * // get inner radius + * var numPoints = star.numPoints(); + * + * // set inner radius + * star.numPoints(20); + */ +Factory.addGetterSetter(Star, 'numPoints', 5, getNumberValidator()); + +/** + * get/set innerRadius + * @name Konva.Star#innerRadius + * @method + * @param {Number} innerRadius + * @returns {Number} + * @example + * // get inner radius + * var innerRadius = star.innerRadius(); + * + * // set inner radius + * star.innerRadius(20); + */ +Factory.addGetterSetter(Star, 'innerRadius', 0, getNumberValidator()); + +/** + * get/set outerRadius + * @name Konva.Star#outerRadius + * @method + * @param {Number} outerRadius + * @returns {Number} + * @example + * // get inner radius + * var outerRadius = star.outerRadius(); + * + * // set inner radius + * star.outerRadius(20); + */ + +Factory.addGetterSetter(Star, 'outerRadius', 0, getNumberValidator()); diff --git a/src/shapes/Text.ts b/src/shapes/Text.ts new file mode 100644 index 000000000..dbd3b9485 --- /dev/null +++ b/src/shapes/Text.ts @@ -0,0 +1,977 @@ +import { Util } from '../Util'; +import { Context } from '../Context'; +import { Factory } from '../Factory'; +import { Shape, ShapeConfig } from '../Shape'; +import { Konva } from '../Global'; +import { + getNumberValidator, + getStringValidator, + getNumberOrAutoValidator, + getBooleanValidator, +} from '../Validators'; +import { _registerNode } from '../Global'; + +import { GetSet } from '../types'; + +export function stringToArray(string: string): string[] { + // Use Unicode-aware splitting + return [...string].reduce((acc, char, index, array) => { + // Handle emoji sequences (including ZWJ sequences) + if ( + /\p{Emoji_Modifier_Base}\p{Emoji_Modifier}?(?:\u200D\p{Emoji_Presentation})+/u.test( + char + ) + ) { + acc.push(char); + } + // Handle regional indicator symbols (flags) + else if ( + /\p{Regional_Indicator}{2}/u.test(char + (array[index + 1] || '')) + ) { + acc.push(char + array[index + 1]); + } + // Handle Indic scripts and other combining characters + else if (index > 0 && /\p{Mn}|\p{Me}|\p{Mc}/u.test(char)) { + acc[acc.length - 1] += char; + } + // Handle other characters + else { + acc.push(char); + } + return acc; + }, [] as string[]); +} + +export interface TextConfig extends ShapeConfig { + direction?: string; + text?: string; + fontFamily?: string; + fontSize?: number; + fontStyle?: string; + fontVariant?: string; + textDecoration?: string; + align?: string; + verticalAlign?: string; + padding?: number; + lineHeight?: number; + letterSpacing?: number; + wrap?: string; + ellipsis?: boolean; +} + +// constants +const AUTO = 'auto', + //CANVAS = 'canvas', + CENTER = 'center', + INHERIT = 'inherit', + JUSTIFY = 'justify', + CHANGE_KONVA = 'Change.konva', + CONTEXT_2D = '2d', + DASH = '-', + LEFT = 'left', + TEXT = 'text', + TEXT_UPPER = 'Text', + TOP = 'top', + BOTTOM = 'bottom', + MIDDLE = 'middle', + NORMAL = 'normal', + PX_SPACE = 'px ', + SPACE = ' ', + RIGHT = 'right', + RTL = 'rtl', + WORD = 'word', + CHAR = 'char', + NONE = 'none', + ELLIPSIS = '…', + ATTR_CHANGE_LIST = [ + 'direction', + 'fontFamily', + 'fontSize', + 'fontStyle', + 'fontVariant', + 'padding', + 'align', + 'verticalAlign', + 'lineHeight', + 'text', + 'width', + 'height', + 'wrap', + 'ellipsis', + 'letterSpacing', + ], + // cached variables + attrChangeListLen = ATTR_CHANGE_LIST.length; + +function normalizeFontFamily(fontFamily: string) { + return fontFamily + .split(',') + .map((family) => { + family = family.trim(); + const hasSpace = family.indexOf(' ') >= 0; + const hasQuotes = family.indexOf('"') >= 0 || family.indexOf("'") >= 0; + if (hasSpace && !hasQuotes) { + family = `"${family}"`; + } + return family; + }) + .join(', '); +} + +let dummyContext: CanvasRenderingContext2D; +function getDummyContext() { + if (dummyContext) { + return dummyContext; + } + dummyContext = Util.createCanvasElement().getContext( + CONTEXT_2D + ) as CanvasRenderingContext2D; + return dummyContext; +} + +function _fillFunc(this: Text, context: Context) { + context.fillText(this._partialText, this._partialTextX, this._partialTextY); +} +function _strokeFunc(this: Text, context: Context) { + context.setAttr('miterLimit', 2); + context.strokeText(this._partialText, this._partialTextX, this._partialTextY); +} + +function checkDefaultFill(config?: TextConfig) { + config = config || {}; + + // set default color to black + if ( + !config.fillLinearGradientColorStops && + !config.fillRadialGradientColorStops && + !config.fillPatternImage + ) { + config.fill = config.fill || 'black'; + } + return config; +} + +/** + * Text constructor + * @constructor + * @memberof Konva + * @augments Konva.Shape + * @param {Object} config + * @param {String} [config.direction] default is inherit + * @param {String} [config.fontFamily] default is Arial + * @param {Number} [config.fontSize] in pixels. Default is 12 + * @param {String} [config.fontStyle] can be 'normal', 'italic', or 'bold', '500' or even 'italic bold'. 'normal' is the default. + * @param {String} [config.fontVariant] can be normal or small-caps. Default is normal + * @param {String} [config.textDecoration] can be line-through, underline or empty string. Default is empty string. + * @param {String} config.text + * @param {String} [config.align] can be left, center, or right + * @param {String} [config.verticalAlign] can be top, middle or bottom + * @param {Number} [config.padding] + * @param {Number} [config.lineHeight] default is 1 + * @param {String} [config.wrap] can be "word", "char", or "none". Default is word + * @param {Boolean} [config.ellipsis] can be true or false. Default is false. if Konva.Text config is set to wrap="none" and ellipsis=true, then it will add "..." to the end + * @@shapeParams + * @@nodeParams + * @example + * var text = new Konva.Text({ + * x: 10, + * y: 15, + * text: 'Simple Text', + * fontSize: 30, + * fontFamily: 'Calibri', + * fill: 'green' + * }); + */ +export class Text extends Shape { + textArr: Array<{ text: string; width: number; lastInParagraph: boolean }>; + _partialText: string; + _partialTextX = 0; + _partialTextY = 0; + + textWidth: number; + textHeight: number; + constructor(config?: TextConfig) { + super(checkDefaultFill(config)); + // update text data for certain attr changes + for (let n = 0; n < attrChangeListLen; n++) { + this.on(ATTR_CHANGE_LIST[n] + CHANGE_KONVA, this._setTextData); + } + this._setTextData(); + } + + _sceneFunc(context: Context) { + const textArr = this.textArr, + textArrLen = textArr.length; + + if (!this.text()) { + return; + } + + let padding = this.padding(), + fontSize = this.fontSize(), + lineHeightPx = this.lineHeight() * fontSize, + verticalAlign = this.verticalAlign(), + direction = this.direction(), + alignY = 0, + align = this.align(), + totalWidth = this.getWidth(), + letterSpacing = this.letterSpacing(), + fill = this.fill(), + textDecoration = this.textDecoration(), + shouldUnderline = textDecoration.indexOf('underline') !== -1, + shouldLineThrough = textDecoration.indexOf('line-through') !== -1, + n; + + direction = direction === INHERIT ? context.direction : direction; + + let translateY = lineHeightPx / 2; + let baseline = MIDDLE; + if (Konva._fixTextRendering) { + const metrics = this.measureSize('M'); // Use a sample character to get the ascent + + baseline = 'alphabetic'; + translateY = + (metrics.fontBoundingBoxAscent - metrics.fontBoundingBoxDescent) / 2 + + lineHeightPx / 2; + } + + var lineTranslateX = 0; + var lineTranslateY = 0; + + if (direction === RTL) { + context.setAttr('direction', direction); + } + + context.setAttr('font', this._getContextFont()); + + context.setAttr('textBaseline', baseline); + + context.setAttr('textAlign', LEFT); + + // handle vertical alignment + if (verticalAlign === MIDDLE) { + alignY = (this.getHeight() - textArrLen * lineHeightPx - padding * 2) / 2; + } else if (verticalAlign === BOTTOM) { + alignY = this.getHeight() - textArrLen * lineHeightPx - padding * 2; + } + + context.translate(padding, alignY + padding); + + // draw text lines + for (n = 0; n < textArrLen; n++) { + var lineTranslateX = 0; + var lineTranslateY = 0; + var obj = textArr[n], + text = obj.text, + width = obj.width, + lastLine = obj.lastInParagraph, + spacesNumber, + oneWord, + lineWidth; + + // horizontal alignment + context.save(); + if (align === RIGHT) { + lineTranslateX += totalWidth - width - padding * 2; + } else if (align === CENTER) { + lineTranslateX += (totalWidth - width - padding * 2) / 2; + } + + if (shouldUnderline) { + context.save(); + context.beginPath(); + + const yOffset = Konva._fixTextRendering + ? Math.round(fontSize / 4) + : Math.round(fontSize / 2); + const x = lineTranslateX; + const y = translateY + lineTranslateY + yOffset; + context.moveTo(x, y); + spacesNumber = text.split(' ').length - 1; + oneWord = spacesNumber === 0; + lineWidth = + align === JUSTIFY && !lastLine ? totalWidth - padding * 2 : width; + context.lineTo(x + Math.round(lineWidth), y); + + // I have no idea what is real ratio + // just /15 looks good enough + context.lineWidth = fontSize / 15; + + const gradient = this._getLinearGradient(); + context.strokeStyle = gradient || fill; + context.stroke(); + context.restore(); + } + if (shouldLineThrough) { + context.save(); + context.beginPath(); + const yOffset = Konva._fixTextRendering ? -Math.round(fontSize / 4) : 0; + context.moveTo(lineTranslateX, translateY + lineTranslateY + yOffset); + spacesNumber = text.split(' ').length - 1; + oneWord = spacesNumber === 0; + lineWidth = + align === JUSTIFY && !lastLine ? totalWidth - padding * 2 : width; + context.lineTo( + lineTranslateX + Math.round(lineWidth), + translateY + lineTranslateY + yOffset + ); + context.lineWidth = fontSize / 15; + const gradient = this._getLinearGradient(); + context.strokeStyle = gradient || fill; + context.stroke(); + context.restore(); + } + // As `letterSpacing` isn't supported on Safari, we use this polyfill. + // The exception is for RTL text, which we rely on native as it cannot + // be supported otherwise. + if (direction !== RTL && (letterSpacing !== 0 || align === JUSTIFY)) { + // var words = text.split(' '); + spacesNumber = text.split(' ').length - 1; + const array = stringToArray(text); + for (let li = 0; li < array.length; li++) { + const letter = array[li]; + // skip justify for the last line + if (letter === ' ' && !lastLine && align === JUSTIFY) { + lineTranslateX += (totalWidth - padding * 2 - width) / spacesNumber; + // context.translate( + // Math.floor((totalWidth - padding * 2 - width) / spacesNumber), + // 0 + // ); + } + this._partialTextX = lineTranslateX; + this._partialTextY = translateY + lineTranslateY; + this._partialText = letter; + context.fillStrokeShape(this); + lineTranslateX += this.measureSize(letter).width + letterSpacing; + } + } else { + if (letterSpacing !== 0) { + context.setAttr('letterSpacing', `${letterSpacing}px`); + } + this._partialTextX = lineTranslateX; + this._partialTextY = translateY + lineTranslateY; + this._partialText = text; + + context.fillStrokeShape(this); + } + context.restore(); + if (textArrLen > 1) { + translateY += lineHeightPx; + } + } + } + _hitFunc(context: Context) { + const width = this.getWidth(), + height = this.getHeight(); + + context.beginPath(); + context.rect(0, 0, width, height); + context.closePath(); + context.fillStrokeShape(this); + } + setText(text: string) { + const str = Util._isString(text) + ? text + : text === null || text === undefined + ? '' + : text + ''; + this._setAttr(TEXT, str); + return this; + } + getWidth() { + const isAuto = this.attrs.width === AUTO || this.attrs.width === undefined; + return isAuto ? this.getTextWidth() + this.padding() * 2 : this.attrs.width; + } + getHeight() { + const isAuto = this.attrs.height === AUTO || this.attrs.height === undefined; + return isAuto + ? this.fontSize() * this.textArr.length * this.lineHeight() + + this.padding() * 2 + : this.attrs.height; + } + /** + * get pure text width without padding + * @method + * @name Konva.Text#getTextWidth + * @returns {Number} + */ + getTextWidth() { + return this.textWidth; + } + getTextHeight() { + Util.warn( + 'text.getTextHeight() method is deprecated. Use text.height() - for full height and text.fontSize() - for one line height.' + ); + return this.textHeight; + } + + /** + * measure string with the font of current text shape. + * That method can't handle multiline text. + * @method + * @name Konva.Text#measureSize + * @param {String} text text to measure + * @returns {Object} { width , height } of measured text + */ + measureSize(text: string) { + let _context = getDummyContext(), + fontSize = this.fontSize(), + metrics: TextMetrics; + + _context.save(); + _context.font = this._getContextFont(); + + metrics = _context.measureText(text); + _context.restore(); + + // Scale the fallback values based on the provided fontSize compared to the sample size (100 in your new case) + const scaleFactor = fontSize / 100; + + // Note, fallback values are from chrome browser with 100px font size and font-family "Arial" + return { + actualBoundingBoxAscent: + metrics.actualBoundingBoxAscent ?? 71.58203125 * scaleFactor, + actualBoundingBoxDescent: metrics.actualBoundingBoxDescent ?? 0, // Remains zero as there is no descent in the provided metrics + actualBoundingBoxLeft: + metrics.actualBoundingBoxLeft ?? -7.421875 * scaleFactor, + actualBoundingBoxRight: + metrics.actualBoundingBoxRight ?? 75.732421875 * scaleFactor, + alphabeticBaseline: metrics.alphabeticBaseline ?? 0, // Remains zero as it's typically relative to the baseline itself + emHeightAscent: metrics.emHeightAscent ?? 100 * scaleFactor, + emHeightDescent: metrics.emHeightDescent ?? -20 * scaleFactor, + fontBoundingBoxAscent: metrics.fontBoundingBoxAscent ?? 91 * scaleFactor, + fontBoundingBoxDescent: + metrics.fontBoundingBoxDescent ?? 21 * scaleFactor, + hangingBaseline: + metrics.hangingBaseline ?? 72.80000305175781 * scaleFactor, + ideographicBaseline: metrics.ideographicBaseline ?? -21 * scaleFactor, + width: metrics.width, + height: fontSize, // Typically set to the font size + }; + } + _getContextFont() { + return ( + this.fontStyle() + + SPACE + + this.fontVariant() + + SPACE + + (this.fontSize() + PX_SPACE) + + // wrap font family into " so font families with spaces works ok + normalizeFontFamily(this.fontFamily()) + ); + } + _addTextLine(line: string) { + const align = this.align(); + if (align === JUSTIFY) { + line = line.trim(); + } + const width = this._getTextWidth(line); + return this.textArr.push({ + text: line, + width: width, + lastInParagraph: false, + }); + } + _getTextWidth(text: string) { + const letterSpacing = this.letterSpacing(); + const length = text.length; + return ( + getDummyContext().measureText(text).width + + (length ? letterSpacing * (length - 1) : 0) + ); + } + _setTextData() { + let lines = this.text().split('\n'), + fontSize = +this.fontSize(), + textWidth = 0, + lineHeightPx = this.lineHeight() * fontSize, + width = this.attrs.width, + height = this.attrs.height, + fixedWidth = width !== AUTO && width !== undefined, + fixedHeight = height !== AUTO && height !== undefined, + padding = this.padding(), + maxWidth = width - padding * 2, + maxHeightPx = height - padding * 2, + currentHeightPx = 0, + wrap = this.wrap(), + // align = this.align(), + shouldWrap = wrap !== NONE, + wrapAtWord = wrap !== CHAR && shouldWrap, + shouldAddEllipsis = this.ellipsis(); + + this.textArr = []; + getDummyContext().font = this._getContextFont(); + const additionalWidth = shouldAddEllipsis ? this._getTextWidth(ELLIPSIS) : 0; + for (let i = 0, max = lines.length; i < max; ++i) { + let line = lines[i]; + + let lineWidth = this._getTextWidth(line); + if (fixedWidth && lineWidth > maxWidth) { + /* + * if width is fixed and line does not fit entirely + * break the line into multiple fitting lines + */ + while (line.length > 0) { + /* + * use binary search to find the longest substring that + * that would fit in the specified width + */ + let low = 0, + high = line.length, + match = '', + matchWidth = 0; + while (low < high) { + const mid = (low + high) >>> 1, + substr = line.slice(0, mid + 1), + substrWidth = this._getTextWidth(substr) + additionalWidth; + if (substrWidth <= maxWidth) { + low = mid + 1; + match = substr; + matchWidth = substrWidth; + } else { + high = mid; + } + } + /* + * 'low' is now the index of the substring end + * 'match' is the substring + * 'matchWidth' is the substring width in px + */ + if (match) { + // a fitting substring was found + if (wrapAtWord) { + // try to find a space or dash where wrapping could be done + var wrapIndex; + const nextChar = line[match.length]; + const nextIsSpaceOrDash = nextChar === SPACE || nextChar === DASH; + if (nextIsSpaceOrDash && matchWidth <= maxWidth) { + wrapIndex = match.length; + } else { + wrapIndex = + Math.max(match.lastIndexOf(SPACE), match.lastIndexOf(DASH)) + + 1; + } + if (wrapIndex > 0) { + // re-cut the substring found at the space/dash position + low = wrapIndex; + match = match.slice(0, low); + matchWidth = this._getTextWidth(match); + } + } + // if (align === 'right') { + match = match.trimRight(); + // } + this._addTextLine(match); + textWidth = Math.max(textWidth, matchWidth); + currentHeightPx += lineHeightPx; + + const shouldHandleEllipsis = + this._shouldHandleEllipsis(currentHeightPx); + if (shouldHandleEllipsis) { + this._tryToAddEllipsisToLastLine(); + /* + * stop wrapping if wrapping is disabled or if adding + * one more line would overflow the fixed height + */ + break; + } + line = line.slice(low); + line = line.trimLeft(); + if (line.length > 0) { + // Check if the remaining text would fit on one line + lineWidth = this._getTextWidth(line); + if (lineWidth <= maxWidth) { + // if it does, add the line and break out of the loop + this._addTextLine(line); + currentHeightPx += lineHeightPx; + textWidth = Math.max(textWidth, lineWidth); + break; + } + } + } else { + // not even one character could fit in the element, abort + break; + } + } + } else { + // element width is automatically adjusted to max line width + this._addTextLine(line); + currentHeightPx += lineHeightPx; + textWidth = Math.max(textWidth, lineWidth); + if (this._shouldHandleEllipsis(currentHeightPx) && i < max - 1) { + this._tryToAddEllipsisToLastLine(); + } + } + // if element height is fixed, abort if adding one more line would overflow + if (this.textArr[this.textArr.length - 1]) { + this.textArr[this.textArr.length - 1].lastInParagraph = true; + } + if (fixedHeight && currentHeightPx + lineHeightPx > maxHeightPx) { + break; + } + } + this.textHeight = fontSize; + // var maxTextWidth = 0; + // for(var j = 0; j < this.textArr.length; j++) { + // maxTextWidth = Math.max(maxTextWidth, this.textArr[j].width); + // } + this.textWidth = textWidth; + } + + /** + * whether to handle ellipsis, there are two cases: + * 1. the current line is the last line + * 2. wrap is NONE + * @param {Number} currentHeightPx + * @returns + */ + _shouldHandleEllipsis(currentHeightPx: number): boolean { + const fontSize = +this.fontSize(), + lineHeightPx = this.lineHeight() * fontSize, + height = this.attrs.height, + fixedHeight = height !== AUTO && height !== undefined, + padding = this.padding(), + maxHeightPx = height - padding * 2, + wrap = this.wrap(), + shouldWrap = wrap !== NONE; + + return ( + !shouldWrap || + (fixedHeight && currentHeightPx + lineHeightPx > maxHeightPx) + ); + } + + _tryToAddEllipsisToLastLine(): void { + const width = this.attrs.width, + fixedWidth = width !== AUTO && width !== undefined, + padding = this.padding(), + maxWidth = width - padding * 2, + shouldAddEllipsis = this.ellipsis(); + + const lastLine = this.textArr[this.textArr.length - 1]; + if (!lastLine || !shouldAddEllipsis) { + return; + } + + if (fixedWidth) { + const haveSpace = this._getTextWidth(lastLine.text + ELLIPSIS) < maxWidth; + if (!haveSpace) { + lastLine.text = lastLine.text.slice(0, lastLine.text.length - 3); + } + } + + this.textArr.splice(this.textArr.length - 1, 1); + this._addTextLine(lastLine.text + ELLIPSIS); + } + + // for text we can't disable stroke scaling + // if we do, the result will be unexpected + getStrokeScaleEnabled() { + return true; + } + + _useBufferCanvas() { + const hasLine = + this.textDecoration().indexOf('underline') !== -1 || + this.textDecoration().indexOf('line-through') !== -1; + const hasShadow = this.hasShadow(); + if (hasLine && hasShadow) { + return true; + } + return super._useBufferCanvas(); + } + + direction: GetSet; + fontFamily: GetSet; + fontSize: GetSet; + fontStyle: GetSet; + fontVariant: GetSet; + align: GetSet; + letterSpacing: GetSet; + verticalAlign: GetSet; + padding: GetSet; + lineHeight: GetSet; + textDecoration: GetSet; + text: GetSet; + wrap: GetSet; + ellipsis: GetSet; +} + +Text.prototype._fillFunc = _fillFunc; +Text.prototype._strokeFunc = _strokeFunc; +Text.prototype.className = TEXT_UPPER; +Text.prototype._attrsAffectingSize = [ + 'text', + 'fontSize', + 'padding', + 'wrap', + 'lineHeight', + 'letterSpacing', +]; +_registerNode(Text); + +/** + * get/set width of text area, which includes padding. + * @name Konva.Text#width + * @method + * @param {Number} width + * @returns {Number} + * @example + * // get width + * var width = text.width(); + * + * // set width + * text.width(20); + * + * // set to auto + * text.width('auto'); + * text.width() // will return calculated width, and not "auto" + */ +Factory.overWriteSetter(Text, 'width', getNumberOrAutoValidator()); + +/** + * get/set the height of the text area, which takes into account multi-line text, line heights, and padding. + * @name Konva.Text#height + * @method + * @param {Number} height + * @returns {Number} + * @example + * // get height + * var height = text.height(); + * + * // set height + * text.height(20); + * + * // set to auto + * text.height('auto'); + * text.height() // will return calculated height, and not "auto" + */ + +Factory.overWriteSetter(Text, 'height', getNumberOrAutoValidator()); + +/** + * get/set direction + * @name Konva.Text#direction + * @method + * @param {String} direction + * @returns {String} + * @example + * // get direction + * var direction = text.direction(); + * + * // set direction + * text.direction('rtl'); + */ +Factory.addGetterSetter(Text, 'direction', INHERIT); + +/** + * get/set font family + * @name Konva.Text#fontFamily + * @method + * @param {String} fontFamily + * @returns {String} + * @example + * // get font family + * var fontFamily = text.fontFamily(); + * + * // set font family + * text.fontFamily('Arial'); + */ +Factory.addGetterSetter(Text, 'fontFamily', 'Arial'); + +/** + * get/set font size in pixels + * @name Konva.Text#fontSize + * @method + * @param {Number} fontSize + * @returns {Number} + * @example + * // get font size + * var fontSize = text.fontSize(); + * + * // set font size to 22px + * text.fontSize(22); + */ +Factory.addGetterSetter(Text, 'fontSize', 12, getNumberValidator()); + +/** + * get/set font style. Can be 'normal', 'italic', or 'bold', '500' or even 'italic bold'. 'normal' is the default. + * @name Konva.Text#fontStyle + * @method + * @param {String} fontStyle + * @returns {String} + * @example + * // get font style + * var fontStyle = text.fontStyle(); + * + * // set font style + * text.fontStyle('bold'); + */ + +Factory.addGetterSetter(Text, 'fontStyle', NORMAL); + +/** + * get/set font variant. Can be 'normal' or 'small-caps'. 'normal' is the default. + * @name Konva.Text#fontVariant + * @method + * @param {String} fontVariant + * @returns {String} + * @example + * // get font variant + * var fontVariant = text.fontVariant(); + * + * // set font variant + * text.fontVariant('small-caps'); + */ + +Factory.addGetterSetter(Text, 'fontVariant', NORMAL); + +/** + * get/set padding + * @name Konva.Text#padding + * @method + * @param {Number} padding + * @returns {Number} + * @example + * // get padding + * var padding = text.padding(); + * + * // set padding to 10 pixels + * text.padding(10); + */ + +Factory.addGetterSetter(Text, 'padding', 0, getNumberValidator()); + +/** + * get/set horizontal align of text. Can be 'left', 'center', 'right' or 'justify' + * @name Konva.Text#align + * @method + * @param {String} align + * @returns {String} + * @example + * // get text align + * var align = text.align(); + * + * // center text + * text.align('center'); + * + * // align text to right + * text.align('right'); + */ + +Factory.addGetterSetter(Text, 'align', LEFT); + +/** + * get/set vertical align of text. Can be 'top', 'middle', 'bottom'. + * @name Konva.Text#verticalAlign + * @method + * @param {String} verticalAlign + * @returns {String} + * @example + * // get text vertical align + * var verticalAlign = text.verticalAlign(); + * + * // center text + * text.verticalAlign('middle'); + */ + +Factory.addGetterSetter(Text, 'verticalAlign', TOP); + +/** + * get/set line height. The default is 1. + * @name Konva.Text#lineHeight + * @method + * @param {Number} lineHeight + * @returns {Number} + * @example + * // get line height + * var lineHeight = text.lineHeight(); + * + * // set the line height + * text.lineHeight(2); + */ + +Factory.addGetterSetter(Text, 'lineHeight', 1, getNumberValidator()); + +/** + * get/set wrap. Can be "word", "char", or "none". Default is "word". + * In "word" wrapping any word still can be wrapped if it can't be placed in the required width + * without breaks. + * @name Konva.Text#wrap + * @method + * @param {String} wrap + * @returns {String} + * @example + * // get wrap + * var wrap = text.wrap(); + * + * // set wrap + * text.wrap('word'); + */ + +Factory.addGetterSetter(Text, 'wrap', WORD); + +/** + * get/set ellipsis. Can be true or false. Default is false. If ellipses is true, + * Konva will add "..." at the end of the text if it doesn't have enough space to write characters. + * That is possible only when you limit both width and height of the text + * @name Konva.Text#ellipsis + * @method + * @param {Boolean} ellipsis + * @returns {Boolean} + * @example + * // get ellipsis param, returns true or false + * var ellipsis = text.ellipsis(); + * + * // set ellipsis + * text.ellipsis(true); + */ + +Factory.addGetterSetter(Text, 'ellipsis', false, getBooleanValidator()); + +/** + * set letter spacing property. Default value is 0. + * @name Konva.Text#letterSpacing + * @method + * @param {Number} letterSpacing + */ + +Factory.addGetterSetter(Text, 'letterSpacing', 0, getNumberValidator()); + +/** + * get/set text + * @name Konva.Text#text + * @method + * @param {String} text + * @returns {String} + * @example + * // get text + * var text = text.text(); + * + * // set text + * text.text('Hello world!'); + */ + +Factory.addGetterSetter(Text, 'text', '', getStringValidator()); + +/** + * get/set text decoration of a text. Possible values are 'underline', 'line-through' or combination of these values separated by space + * @name Konva.Text#textDecoration + * @method + * @param {String} textDecoration + * @returns {String} + * @example + * // get text decoration + * var textDecoration = text.textDecoration(); + * + * // underline text + * text.textDecoration('underline'); + * + * // strike text + * text.textDecoration('line-through'); + * + * // underline and strike text + * text.textDecoration('underline line-through'); + */ + +Factory.addGetterSetter(Text, 'textDecoration', ''); diff --git a/src/shapes/TextPath.ts b/src/shapes/TextPath.ts new file mode 100644 index 000000000..1ae774627 --- /dev/null +++ b/src/shapes/TextPath.ts @@ -0,0 +1,571 @@ +import { Util } from '../Util'; +import { Factory } from '../Factory'; +import { Context } from '../Context'; +import { Shape, ShapeConfig } from '../Shape'; +import { Path } from './Path'; +import { Text, stringToArray } from './Text'; +import { getNumberValidator } from '../Validators'; +import { _registerNode } from '../Global'; + +import { GetSet, PathSegment, Vector2d } from '../types'; + +export interface TextPathConfig extends ShapeConfig { + text?: string; + data?: string; + fontFamily?: string; + fontSize?: number; + fontStyle?: string; + letterSpacing?: number; +} + +const EMPTY_STRING = '', + NORMAL = 'normal'; + +function _fillFunc(this: TextPath, context) { + context.fillText(this.partialText, 0, 0); +} +function _strokeFunc(this: TextPath, context) { + context.strokeText(this.partialText, 0, 0); +} + +/** + * Path constructor. + * @author Jason Follas + * @constructor + * @memberof Konva + * @augments Konva.Shape + * @param {Object} config + * @param {String} [config.fontFamily] default is Arial + * @param {Number} [config.fontSize] default is 12 + * @param {String} [config.fontStyle] Can be 'normal', 'italic', or 'bold', '500' or even 'italic bold'. 'normal' is the default. + * @param {String} [config.fontVariant] can be normal or small-caps. Default is normal + * @param {String} [config.textBaseline] Can be 'top', 'bottom', 'middle', 'alphabetic', 'hanging'. Default is middle + * @param {String} config.text + * @param {String} config.data SVG data string + * @param {Function} config.kerningFunc a getter for kerning values for the specified characters + * @@shapeParams + * @@nodeParams + * @example + * var kerningPairs = { + * 'A': { + * ' ': -0.05517578125, + * 'T': -0.07421875, + * 'V': -0.07421875 + * } + * 'V': { + * ',': -0.091796875, + * ":": -0.037109375, + * ";": -0.037109375, + * "A": -0.07421875 + * } + * } + * var textpath = new Konva.TextPath({ + * x: 100, + * y: 50, + * fill: '#333', + * fontSize: '24', + * fontFamily: 'Arial', + * text: 'All the world\'s a stage, and all the men and women merely players.', + * data: 'M10,10 C0,0 10,150 100,100 S300,150 400,50', + * kerningFunc(leftChar, rightChar) { + * return kerningPairs.hasOwnProperty(leftChar) ? pairs[leftChar][rightChar] || 0 : 0 + * } + * }); + */ +export class TextPath extends Shape { + dummyCanvas = Util.createCanvasElement(); + dataArray: PathSegment[] = []; + glyphInfo: Array<{ + transposeX: number; + transposeY: number; + text: string; + rotation: number; + p0: Vector2d; + p1: Vector2d; + }>; + partialText: string; + pathLength: number; + textWidth: number; + textHeight: number; + + constructor(config?: TextPathConfig) { + // call super constructor + super(config); + + this._readDataAttribute(); + + this.on('dataChange.konva', function () { + this._readDataAttribute(); + this._setTextData(); + }); + + // update text data for certain attr changes + this.on( + 'textChange.konva alignChange.konva letterSpacingChange.konva kerningFuncChange.konva fontSizeChange.konva fontFamilyChange.konva', + this._setTextData + ); + + this._setTextData(); + } + + _getTextPathLength() { + return Path.getPathLength(this.dataArray); + } + _getPointAtLength(length: number) { + // if path is not defined yet, do nothing + if (!this.attrs.data) { + return null; + } + + const totalLength = this.pathLength; + // -1px for rounding of the last symbol + if (length - 1 > totalLength) { + return null; + } + + return Path.getPointAtLengthOfDataArray(length, this.dataArray); + } + + _readDataAttribute() { + this.dataArray = Path.parsePathData(this.attrs.data); + this.pathLength = this._getTextPathLength(); + } + + _sceneFunc(context: Context) { + context.setAttr('font', this._getContextFont()); + context.setAttr('textBaseline', this.textBaseline()); + context.setAttr('textAlign', 'left'); + context.save(); + + const textDecoration = this.textDecoration(); + const fill = this.fill(); + const fontSize = this.fontSize(); + + const glyphInfo = this.glyphInfo; + if (textDecoration === 'underline') { + context.beginPath(); + } + for (let i = 0; i < glyphInfo.length; i++) { + context.save(); + + const p0 = glyphInfo[i].p0; + + context.translate(p0.x, p0.y); + context.rotate(glyphInfo[i].rotation); + this.partialText = glyphInfo[i].text; + + context.fillStrokeShape(this); + if (textDecoration === 'underline') { + if (i === 0) { + context.moveTo(0, fontSize / 2 + 1); + } + + context.lineTo(fontSize, fontSize / 2 + 1); + } + context.restore(); + + //// To assist with debugging visually, uncomment following + // + // if (i % 2) context.strokeStyle = 'cyan'; + // else context.strokeStyle = 'green'; + // var p1 = glyphInfo[i].p1; + // context.moveTo(p0.x, p0.y); + // context.lineTo(p1.x, p1.y); + // context.stroke(); + } + if (textDecoration === 'underline') { + context.strokeStyle = fill; + context.lineWidth = fontSize / 20; + context.stroke(); + } + + context.restore(); + } + _hitFunc(context: Context) { + context.beginPath(); + + const glyphInfo = this.glyphInfo; + if (glyphInfo.length >= 1) { + const p0 = glyphInfo[0].p0; + context.moveTo(p0.x, p0.y); + } + for (let i = 0; i < glyphInfo.length; i++) { + const p1 = glyphInfo[i].p1; + context.lineTo(p1.x, p1.y); + } + context.setAttr('lineWidth', this.fontSize()); + context.setAttr('strokeStyle', this.colorKey); + context.stroke(); + } + /** + * get text width in pixels + * @method + * @name Konva.TextPath#getTextWidth + */ + getTextWidth() { + return this.textWidth; + } + getTextHeight() { + Util.warn( + 'text.getTextHeight() method is deprecated. Use text.height() - for full height and text.fontSize() - for one line height.' + ); + return this.textHeight; + } + setText(text: string) { + return Text.prototype.setText.call(this, text); + } + + _getContextFont() { + return Text.prototype._getContextFont.call(this); + } + + _getTextSize(text: string) { + const dummyCanvas = this.dummyCanvas; + const _context = dummyCanvas.getContext('2d')!; + + _context.save(); + + _context.font = this._getContextFont(); + const metrics = _context.measureText(text); + + _context.restore(); + + return { + width: metrics.width, + height: parseInt(`${this.fontSize()}`, 10), + }; + } + _setTextData() { + const { width, height } = this._getTextSize(this.attrs.text); + this.textWidth = width; + this.textHeight = height; + this.glyphInfo = []; + + if (!this.attrs.data) { + return null; + } + + const letterSpacing = this.letterSpacing(); + const align = this.align(); + const kerningFunc = this.kerningFunc(); + + // defines the width of the text on a straight line + const textWidth = Math.max( + this.textWidth + ((this.attrs.text || '').length - 1) * letterSpacing, + 0 + ); + + let offset = 0; + if (align === 'center') { + offset = Math.max(0, this.pathLength / 2 - textWidth / 2); + } + if (align === 'right') { + offset = Math.max(0, this.pathLength - textWidth); + } + + const charArr = stringToArray(this.text()); + + // Algorithm for calculating glyph positions: + // 1. Get the begging point of the glyph on the path using the offsetToGlyph, + // 2. Get the ending point of the glyph on the path using the offsetToGlyph plus glyph width, + // 3. Calculate the rotation, width, and midpoint of the glyph using the start and end points, + // 4. Add glyph width to the offsetToGlyph and repeat + let offsetToGlyph = offset; + for (let i = 0; i < charArr.length; i++) { + const charStartPoint = this._getPointAtLength(offsetToGlyph); + if (!charStartPoint) return; + + let glyphWidth = this._getTextSize(charArr[i]).width + letterSpacing; + if (charArr[i] === ' ' && align === 'justify') { + const numberOfSpaces = this.text().split(' ').length - 1; + glyphWidth += (this.pathLength - textWidth) / numberOfSpaces; + } + + const charEndPoint = this._getPointAtLength(offsetToGlyph + glyphWidth); + if (!charEndPoint) return; + + const width = Path.getLineLength( + charStartPoint.x, + charStartPoint.y, + charEndPoint.x, + charEndPoint.y + ); + + let kern = 0; + if (kerningFunc) { + try { + // getKerning is a user provided getter. Make sure it never breaks our logic + kern = kerningFunc(charArr[i - 1], charArr[i]) * this.fontSize(); + } catch (e) { + kern = 0; + } + } + + charStartPoint.x += kern; + charEndPoint.x += kern; + this.textWidth += kern; + + const midpoint = Path.getPointOnLine( + kern + width / 2.0, + charStartPoint.x, + charStartPoint.y, + charEndPoint.x, + charEndPoint.y + ); + + const rotation = Math.atan2( + charEndPoint.y - charStartPoint.y, + charEndPoint.x - charStartPoint.x + ); + this.glyphInfo.push({ + transposeX: midpoint.x, + transposeY: midpoint.y, + text: charArr[i], + rotation: rotation, + p0: charStartPoint, + p1: charEndPoint, + }); + + offsetToGlyph += glyphWidth; + } + } + getSelfRect() { + if (!this.glyphInfo.length) { + return { + x: 0, + y: 0, + width: 0, + height: 0, + }; + } + const points: number[] = []; + + this.glyphInfo.forEach(function (info) { + points.push(info.p0.x); + points.push(info.p0.y); + points.push(info.p1.x); + points.push(info.p1.y); + }); + let minX = points[0] || 0; + let maxX = points[0] || 0; + let minY = points[1] || 0; + let maxY = points[1] || 0; + let x, y; + for (let i = 0; i < points.length / 2; i++) { + x = points[i * 2]; + y = points[i * 2 + 1]; + minX = Math.min(minX, x); + maxX = Math.max(maxX, x); + minY = Math.min(minY, y); + maxY = Math.max(maxY, y); + } + const fontSize = this.fontSize(); + return { + x: minX - fontSize / 2, + y: minY - fontSize / 2, + width: maxX - minX + fontSize, + height: maxY - minY + fontSize, + }; + } + destroy(): this { + Util.releaseCanvas(this.dummyCanvas); + return super.destroy(); + } + + fontFamily: GetSet; + fontSize: GetSet; + fontStyle: GetSet; + fontVariant: GetSet; + align: GetSet; + letterSpacing: GetSet; + text: GetSet; + data: GetSet; + + kerningFunc: GetSet<(leftChar: string, rightChar: string) => number, this>; + textBaseline: GetSet; + textDecoration: GetSet; +} + +TextPath.prototype._fillFunc = _fillFunc; +TextPath.prototype._strokeFunc = _strokeFunc; +TextPath.prototype._fillFuncHit = _fillFunc; +TextPath.prototype._strokeFuncHit = _strokeFunc; +TextPath.prototype.className = 'TextPath'; +TextPath.prototype._attrsAffectingSize = ['text', 'fontSize', 'data']; +_registerNode(TextPath); + +/** + * get/set SVG path data string. This method + * also automatically parses the data string + * into a data array. Currently supported SVG data: + * M, m, L, l, H, h, V, v, Q, q, T, t, C, c, S, s, A, a, Z, z + * @name Konva.TextPath#data + * @method + * @param {String} data svg path string + * @returns {String} + * @example + * // get data + * var data = shape.data(); + * + * // set data + * shape.data('M200,100h100v50z'); + */ +Factory.addGetterSetter(TextPath, 'data'); + +/** + * get/set font family + * @name Konva.TextPath#fontFamily + * @method + * @param {String} fontFamily + * @returns {String} + * @example + * // get font family + * var fontFamily = shape.fontFamily(); + * + * // set font family + * shape.fontFamily('Arial'); + */ +Factory.addGetterSetter(TextPath, 'fontFamily', 'Arial'); + +/** + * get/set font size in pixels + * @name Konva.TextPath#fontSize + * @method + * @param {Number} fontSize + * @returns {Number} + * @example + * // get font size + * var fontSize = shape.fontSize(); + * + * // set font size to 22px + * shape.fontSize(22); + */ + +Factory.addGetterSetter(TextPath, 'fontSize', 12, getNumberValidator()); + +/** + * get/set font style. Can be 'normal', 'italic', or 'bold', '500' or even 'italic bold'. 'normal' is the default. + * @name Konva.TextPath#fontStyle + * @method + * @param {String} fontStyle + * @returns {String} + * @example + * // get font style + * var fontStyle = shape.fontStyle(); + * + * // set font style + * shape.fontStyle('bold'); + */ + +Factory.addGetterSetter(TextPath, 'fontStyle', NORMAL); + +/** + * get/set horizontal align of text. Can be 'left', 'center', 'right' or 'justify' + * @name Konva.TextPath#align + * @method + * @param {String} align + * @returns {String} + * @example + * // get text align + * var align = text.align(); + * + * // center text + * text.align('center'); + * + * // align text to right + * text.align('right'); + */ +Factory.addGetterSetter(TextPath, 'align', 'left'); + +/** + * get/set letter spacing. The default is 0. + * @name Konva.TextPath#letterSpacing + * @method + * @param {Number} letterSpacing + * @returns {Number} + * @example + * // get letter spacing value + * var letterSpacing = shape.letterSpacing(); + * + * // set the letter spacing value + * shape.letterSpacing(2); + */ + +Factory.addGetterSetter(TextPath, 'letterSpacing', 0, getNumberValidator()); + +/** + * get/set text baseline. The default is 'middle'. Can be 'top', 'bottom', 'middle', 'alphabetic', 'hanging' + * @name Konva.TextPath#textBaseline + * @method + * @param {String} textBaseline + * @returns {String} + * @example + * // get current text baseline + * var textBaseline = shape.textBaseline(); + * + * // set new text baseline + * shape.textBaseline('top'); + */ +Factory.addGetterSetter(TextPath, 'textBaseline', 'middle'); + +/** + * get/set font variant. Can be 'normal' or 'small-caps'. 'normal' is the default. + * @name Konva.TextPath#fontVariant + * @method + * @param {String} fontVariant + * @returns {String} + * @example + * // get font variant + * var fontVariant = shape.fontVariant(); + * + * // set font variant + * shape.fontVariant('small-caps'); + */ +Factory.addGetterSetter(TextPath, 'fontVariant', NORMAL); + +/** + * get/set text + * @name Konva.TextPath#getText + * @method + * @param {String} text + * @returns {String} + * @example + * // get text + * var text = text.text(); + * + * // set text + * text.text('Hello world!'); + */ +Factory.addGetterSetter(TextPath, 'text', EMPTY_STRING); + +/** + * get/set text decoration of a text. Can be '' or 'underline'. + * @name Konva.TextPath#textDecoration + * @method + * @param {String} textDecoration + * @returns {String} + * @example + * // get text decoration + * var textDecoration = shape.textDecoration(); + * + * // underline text + * shape.textDecoration('underline'); + */ +Factory.addGetterSetter(TextPath, 'textDecoration', ''); + +/** + * get/set kerning function. + * @name Konva.TextPath#kerningFunc + * @method + * @param {String} kerningFunc + * @returns {String} + * @example + * // get text decoration + * var kerningFunc = text.kerningFunc(); + * + * // center text + * text.kerningFunc(function(leftChar, rightChar) { + * return 1; + * }); + */ +Factory.addGetterSetter(TextPath, 'kerningFunc', undefined); diff --git a/src/shapes/Transformer.ts b/src/shapes/Transformer.ts new file mode 100644 index 000000000..3c254a515 --- /dev/null +++ b/src/shapes/Transformer.ts @@ -0,0 +1,1874 @@ +import { Util, Transform } from '../Util'; +import { Factory } from '../Factory'; +import { Node } from '../Node'; +import { Shape } from '../Shape'; +import { Rect } from './Rect'; +import { Group } from '../Group'; +import { ContainerConfig } from '../Container'; +import { Konva } from '../Global'; +import { getBooleanValidator, getNumberValidator } from '../Validators'; +import { _registerNode } from '../Global'; + +import { GetSet, IRect, Vector2d } from '../types'; + +export interface Box extends IRect { + rotation: number; +} + +export interface TransformerConfig extends ContainerConfig { + resizeEnabled?: boolean; + rotateEnabled?: boolean; + rotateLineVisible?: boolean; + rotationSnaps?: Array; + rotationSnapTolerance?: number; + rotateAnchorOffset?: number; + rotateAnchorCursor?: string; + borderEnabled?: boolean; + borderStroke?: string; + borderStrokeWidth?: number; + borderDash?: Array; + anchorFill?: string; + anchorStroke?: string; + anchorStrokeWidth?: number; + anchorSize?: number; + anchorCornerRadius?: number; + keepRatio?: boolean; + shiftBehavior?: string; + centeredScaling?: boolean; + enabledAnchors?: Array; + flipEnabled?: boolean; + node?: Rect; + ignoreStroke?: boolean; + boundBoxFunc?: (oldBox: Box, newBox: Box) => Box; + useSingleNodeRotation?: boolean; + shouldOverdrawWholeArea?: boolean; + anchorDragBoundFunc?: ( + oldPos: Vector2d, + newPos: Vector2d, + evt: any + ) => Vector2d; + anchorStyleFunc?: (anchor: Rect) => void; +} + +const EVENTS_NAME = 'tr-konva'; + +const ATTR_CHANGE_LIST = [ + 'resizeEnabledChange', + 'rotateAnchorOffsetChange', + 'rotateEnabledChange', + 'enabledAnchorsChange', + 'anchorSizeChange', + 'borderEnabledChange', + 'borderStrokeChange', + 'borderStrokeWidthChange', + 'borderDashChange', + 'anchorStrokeChange', + 'anchorStrokeWidthChange', + 'anchorFillChange', + 'anchorCornerRadiusChange', + 'ignoreStrokeChange', + 'anchorStyleFuncChange', +] + .map((e) => e + `.${EVENTS_NAME}`) + .join(' '); + +const NODES_RECT = 'nodesRect'; + +const TRANSFORM_CHANGE_STR = [ + 'widthChange', + 'heightChange', + 'scaleXChange', + 'scaleYChange', + 'skewXChange', + 'skewYChange', + 'rotationChange', + 'offsetXChange', + 'offsetYChange', + 'transformsEnabledChange', + 'strokeWidthChange', +]; + +const ANGLES = { + 'top-left': -45, + 'top-center': 0, + 'top-right': 45, + 'middle-right': -90, + 'middle-left': 90, + 'bottom-left': -135, + 'bottom-center': 180, + 'bottom-right': 135, +}; + +const TOUCH_DEVICE = 'ontouchstart' in Konva._global; + +function getCursor(anchorName, rad, rotateCursor) { + if (anchorName === 'rotater') { + return rotateCursor; + } + + rad += Util.degToRad(ANGLES[anchorName] || 0); + const angle = ((Util.radToDeg(rad) % 360) + 360) % 360; + + if (Util._inRange(angle, 315 + 22.5, 360) || Util._inRange(angle, 0, 22.5)) { + // TOP + return 'ns-resize'; + } else if (Util._inRange(angle, 45 - 22.5, 45 + 22.5)) { + // TOP - RIGHT + return 'nesw-resize'; + } else if (Util._inRange(angle, 90 - 22.5, 90 + 22.5)) { + // RIGHT + return 'ew-resize'; + } else if (Util._inRange(angle, 135 - 22.5, 135 + 22.5)) { + // BOTTOM - RIGHT + return 'nwse-resize'; + } else if (Util._inRange(angle, 180 - 22.5, 180 + 22.5)) { + // BOTTOM + return 'ns-resize'; + } else if (Util._inRange(angle, 225 - 22.5, 225 + 22.5)) { + // BOTTOM - LEFT + return 'nesw-resize'; + } else if (Util._inRange(angle, 270 - 22.5, 270 + 22.5)) { + // RIGHT + return 'ew-resize'; + } else if (Util._inRange(angle, 315 - 22.5, 315 + 22.5)) { + // BOTTOM - RIGHT + return 'nwse-resize'; + } else { + // how can we can there? + Util.error('Transformer has unknown angle for cursor detection: ' + angle); + return 'pointer'; + } +} + +const ANCHORS_NAMES = [ + 'top-left', + 'top-center', + 'top-right', + 'middle-right', + 'middle-left', + 'bottom-left', + 'bottom-center', + 'bottom-right', +]; + +const MAX_SAFE_INTEGER = 100000000; + +function getCenter(shape: Box) { + return { + x: + shape.x + + (shape.width / 2) * Math.cos(shape.rotation) + + (shape.height / 2) * Math.sin(-shape.rotation), + y: + shape.y + + (shape.height / 2) * Math.cos(shape.rotation) + + (shape.width / 2) * Math.sin(shape.rotation), + }; +} + +function rotateAroundPoint(shape: Box, angleRad: number, point: Vector2d) { + const x = + point.x + + (shape.x - point.x) * Math.cos(angleRad) - + (shape.y - point.y) * Math.sin(angleRad); + const y = + point.y + + (shape.x - point.x) * Math.sin(angleRad) + + (shape.y - point.y) * Math.cos(angleRad); + return { + ...shape, + rotation: shape.rotation + angleRad, + x, + y, + }; +} + +function rotateAroundCenter(shape: Box, deltaRad: number) { + const center = getCenter(shape); + return rotateAroundPoint(shape, deltaRad, center); +} + +function getSnap(snaps: Array, newRotationRad: number, tol: number) { + let snapped = newRotationRad; + for (let i = 0; i < snaps.length; i++) { + const angle = Konva.getAngle(snaps[i]); + + const absDiff = Math.abs(angle - newRotationRad) % (Math.PI * 2); + const dif = Math.min(absDiff, Math.PI * 2 - absDiff); + + if (dif < tol) { + snapped = angle; + } + } + return snapped; +} + +let activeTransformersCount = 0; +/** + * Transformer constructor. Transformer is a special type of group that allow you transform Konva + * primitives and shapes. Transforming tool is not changing `width` and `height` properties of nodes + * when you resize them. Instead it changes `scaleX` and `scaleY` properties. + * @constructor + * @memberof Konva + * @param {Object} config + * @param {Boolean} [config.resizeEnabled] Default is true + * @param {Boolean} [config.rotateEnabled] Default is true + * @param {Boolean} [config.rotateLineVisible] Default is true + * @param {Array} [config.rotationSnaps] Array of angles for rotation snaps. Default is [] + * @param {Number} [config.rotationSnapTolerance] Snapping tolerance. If closer than this it will snap. Default is 5 + * @param {Number} [config.rotateAnchorOffset] Default is 50 + * @param {String} [config.rotateAnchorCursor] Default is crosshair + * @param {Number} [config.padding] Default is 0 + * @param {Boolean} [config.borderEnabled] Should we draw border? Default is true + * @param {String} [config.borderStroke] Border stroke color + * @param {Number} [config.borderStrokeWidth] Border stroke size + * @param {Array} [config.borderDash] Array for border dash. + * @param {String} [config.anchorFill] Anchor fill color + * @param {String} [config.anchorStroke] Anchor stroke color + * @param {String} [config.anchorCornerRadius] Anchor corner radius + * @param {Number} [config.anchorStrokeWidth] Anchor stroke size + * @param {Number} [config.anchorSize] Default is 10 + * @param {Boolean} [config.keepRatio] Should we keep ratio when we are moving edges? Default is true + * @param {String} [config.shiftBehavior] How does transformer react on shift key press when we are moving edges? Default is 'default' + * @param {Boolean} [config.centeredScaling] Should we resize relative to node's center? Default is false + * @param {Array} [config.enabledAnchors] Array of names of enabled handles + * @param {Boolean} [config.flipEnabled] Can we flip/mirror shape on transform?. True by default + * @param {Function} [config.boundBoxFunc] Bounding box function + * @param {Function} [config.ignoreStroke] Should we ignore stroke size? Default is false + * @param {Boolean} [config.useSingleNodeRotation] When just one node attached, should we use its rotation for transformer? + * @param {Boolean} [config.shouldOverdrawWholeArea] Should we fill whole transformer area with fake transparent shape to enable dragging from empty spaces? + * @example + * var transformer = new Konva.Transformer({ + * nodes: [rectangle], + * rotateAnchorOffset: 60, + * enabledAnchors: ['top-left', 'top-right', 'bottom-left', 'bottom-right'] + * }); + * layer.add(transformer); + */ +export class Transformer extends Group { + _nodes: Array; + _movingAnchorName: string | null = null; + _transforming = false; + _anchorDragOffset: Vector2d; + sin: number; + cos: number; + _cursorChange: boolean; + + static isTransforming = () => { + return activeTransformersCount > 0; + }; + + constructor(config?: TransformerConfig) { + // call super constructor + super(config); + this._createElements(); + + // bindings + this._handleMouseMove = this._handleMouseMove.bind(this); + this._handleMouseUp = this._handleMouseUp.bind(this); + this.update = this.update.bind(this); + + // update transformer data for certain attr changes + this.on(ATTR_CHANGE_LIST, this.update); + + if (this.getNode()) { + this.update(); + } + } + /** + * alias to `tr.nodes([shape])`/ This method is deprecated and will be removed soon. + * @method + * @name Konva.Transformer#attachTo + * @returns {Konva.Transformer} + * @example + * transformer.attachTo(shape); + */ + attachTo(node: Node) { + this.setNode(node); + return this; + } + setNode(node: Node) { + Util.warn( + 'tr.setNode(shape), tr.node(shape) and tr.attachTo(shape) methods are deprecated. Please use tr.nodes(nodesArray) instead.' + ); + return this.setNodes([node]); + } + getNode() { + return this._nodes && this._nodes[0]; + } + + _getEventNamespace() { + return EVENTS_NAME + this._id; + } + + setNodes(nodes: Array = []) { + if (this._nodes && this._nodes.length) { + this.detach(); + } + + const filteredNodes = nodes.filter((node) => { + // check if ancestor of the transformer + if (node.isAncestorOf(this)) { + Util.error( + 'Konva.Transformer cannot be an a child of the node you are trying to attach' + ); + return false; + } + + return true; + }); + + this._nodes = nodes = filteredNodes; + if (nodes.length === 1 && this.useSingleNodeRotation()) { + this.rotation(nodes[0].getAbsoluteRotation()); + } else { + this.rotation(0); + } + this._nodes.forEach((node) => { + const onChange = () => { + if (this.nodes().length === 1 && this.useSingleNodeRotation()) { + this.rotation(this.nodes()[0].getAbsoluteRotation()); + } + + this._resetTransformCache(); + if (!this._transforming && !this.isDragging()) { + this.update(); + } + }; + const additionalEvents = node._attrsAffectingSize + .map((prop) => prop + 'Change.' + this._getEventNamespace()) + .join(' '); + node.on(additionalEvents, onChange); + node.on( + TRANSFORM_CHANGE_STR.map( + (e) => e + `.${this._getEventNamespace()}` + ).join(' '), + onChange + ); + node.on(`absoluteTransformChange.${this._getEventNamespace()}`, onChange); + this._proxyDrag(node); + }); + this._resetTransformCache(); + // we may need it if we set node in initial props + // so elements are not defined yet + const elementsCreated = !!this.findOne('.top-left'); + if (elementsCreated) { + this.update(); + } + return this; + } + + _proxyDrag(node: Node) { + let lastPos; + node.on(`dragstart.${this._getEventNamespace()}`, (e) => { + lastPos = node.getAbsolutePosition(); + // actual dragging of Transformer doesn't make sense + // but we need to make sure it also has all drag events + if (!this.isDragging() && node !== this.findOne('.back')) { + this.startDrag(e, false); + } + }); + node.on(`dragmove.${this._getEventNamespace()}`, (e) => { + if (!lastPos) { + return; + } + const abs = node.getAbsolutePosition(); + const dx = abs.x - lastPos.x; + const dy = abs.y - lastPos.y; + this.nodes().forEach((otherNode) => { + if (otherNode === node) { + return; + } + if (otherNode.isDragging()) { + return; + } + const otherAbs = otherNode.getAbsolutePosition(); + otherNode.setAbsolutePosition({ + x: otherAbs.x + dx, + y: otherAbs.y + dy, + }); + otherNode.startDrag(e); + }); + lastPos = null; + }); + } + + getNodes() { + return this._nodes || []; + } + /** + * return the name of current active anchor + * @method + * @name Konva.Transformer#getActiveAnchor + * @returns {String | Null} + * @example + * transformer.getActiveAnchor(); + */ + getActiveAnchor() { + return this._movingAnchorName; + } + /** + * detach transformer from an attached node + * @method + * @name Konva.Transformer#detach + * @returns {Konva.Transformer} + * @example + * transformer.detach(); + */ + detach() { + // remove events + if (this._nodes) { + this._nodes.forEach((node) => { + node.off('.' + this._getEventNamespace()); + }); + } + this._nodes = []; + this._resetTransformCache(); + } + /** + * bind events to the Transformer. You can use events: `transform`, `transformstart`, `transformend`, `dragstart`, `dragmove`, `dragend` + * @method + * @name Konva.Transformer#on + * @param {String} evtStr e.g. 'transform' + * @param {Function} handler The handler function. The first argument of that function is event object. Event object has `target` as main target of the event, `currentTarget` as current node listener and `evt` as native browser event. + * @returns {Konva.Transformer} + * @example + * // add click listener + * tr.on('transformstart', function() { + * console.log('transform started'); + * }); + */ + _resetTransformCache() { + this._clearCache(NODES_RECT); + this._clearCache('transform'); + this._clearSelfAndDescendantCache('absoluteTransform'); + } + _getNodeRect() { + return this._getCache(NODES_RECT, this.__getNodeRect); + } + + // return absolute rotated bounding rectangle + __getNodeShape(node: Node, rot = this.rotation(), relative?: Node) { + const rect = node.getClientRect({ + skipTransform: true, + skipShadow: true, + skipStroke: this.ignoreStroke(), + }); + + const absScale = node.getAbsoluteScale(relative); + const absPos = node.getAbsolutePosition(relative); + + const dx = rect.x * absScale.x - node.offsetX() * absScale.x; + const dy = rect.y * absScale.y - node.offsetY() * absScale.y; + + const rotation = + (Konva.getAngle(node.getAbsoluteRotation()) + Math.PI * 2) % + (Math.PI * 2); + + const box = { + x: absPos.x + dx * Math.cos(rotation) + dy * Math.sin(-rotation), + y: absPos.y + dy * Math.cos(rotation) + dx * Math.sin(rotation), + width: rect.width * absScale.x, + height: rect.height * absScale.y, + rotation: rotation, + }; + return rotateAroundPoint(box, -Konva.getAngle(rot), { + x: 0, + y: 0, + }); + } + // returns box + rotation of all shapes + __getNodeRect() { + const node = this.getNode(); + if (!node) { + return { + x: -MAX_SAFE_INTEGER, + y: -MAX_SAFE_INTEGER, + width: 0, + height: 0, + rotation: 0, + }; + } + + const totalPoints: Vector2d[] = []; + this.nodes().map((node) => { + const box = node.getClientRect({ + skipTransform: true, + skipShadow: true, + skipStroke: this.ignoreStroke(), + }); + const points = [ + { x: box.x, y: box.y }, + { x: box.x + box.width, y: box.y }, + { x: box.x + box.width, y: box.y + box.height }, + { x: box.x, y: box.y + box.height }, + ]; + const trans = node.getAbsoluteTransform(); + points.forEach(function (point) { + const transformed = trans.point(point); + totalPoints.push(transformed); + }); + }); + + const tr = new Transform(); + tr.rotate(-Konva.getAngle(this.rotation())); + + let minX: number = Infinity, + minY: number = Infinity, + maxX: number = -Infinity, + maxY: number = -Infinity; + totalPoints.forEach(function (point) { + const transformed = tr.point(point); + if (minX === undefined) { + minX = maxX = transformed.x; + minY = maxY = transformed.y; + } + minX = Math.min(minX, transformed.x); + minY = Math.min(minY, transformed.y); + maxX = Math.max(maxX, transformed.x); + maxY = Math.max(maxY, transformed.y); + }); + + tr.invert(); + const p = tr.point({ x: minX, y: minY }); + return { + x: p.x, + y: p.y, + width: maxX - minX, + height: maxY - minY, + rotation: Konva.getAngle(this.rotation()), + }; + // const shapes = this.nodes().map(node => { + // return this.__getNodeShape(node); + // }); + + // const box = getShapesRect(shapes); + // return rotateAroundPoint(box, Konva.getAngle(this.rotation()), { + // x: 0, + // y: 0 + // }); + } + getX() { + return this._getNodeRect().x; + } + getY() { + return this._getNodeRect().y; + } + getWidth() { + return this._getNodeRect().width; + } + getHeight() { + return this._getNodeRect().height; + } + _createElements() { + this._createBack(); + + ANCHORS_NAMES.forEach((name) => { + this._createAnchor(name); + }); + + this._createAnchor('rotater'); + } + _createAnchor(name) { + const anchor = new Rect({ + stroke: 'rgb(0, 161, 255)', + fill: 'white', + strokeWidth: 1, + name: name + ' _anchor', + dragDistance: 0, + // make it draggable, + // so activating the anchor will not start drag&drop of any parent + draggable: true, + hitStrokeWidth: TOUCH_DEVICE ? 10 : 'auto', + }); + const self = this; + anchor.on('mousedown touchstart', function (e) { + self._handleMouseDown(e); + }); + anchor.on('dragstart', (e) => { + anchor.stopDrag(); + e.cancelBubble = true; + }); + anchor.on('dragend', (e) => { + e.cancelBubble = true; + }); + + // add hover styling + anchor.on('mouseenter', () => { + const rad = Konva.getAngle(this.rotation()); + const rotateCursor = this.rotateAnchorCursor(); + const cursor = getCursor(name, rad, rotateCursor); + anchor.getStage()!.content && + (anchor.getStage()!.content.style.cursor = cursor); + this._cursorChange = true; + }); + anchor.on('mouseout', () => { + anchor.getStage()!.content && + (anchor.getStage()!.content.style.cursor = ''); + this._cursorChange = false; + }); + this.add(anchor); + } + _createBack() { + const back = new Shape({ + name: 'back', + width: 0, + height: 0, + draggable: true, + sceneFunc(ctx, shape) { + const tr = shape.getParent() as Transformer; + const padding = tr.padding(); + ctx.beginPath(); + ctx.rect( + -padding, + -padding, + shape.width() + padding * 2, + shape.height() + padding * 2 + ); + ctx.moveTo(shape.width() / 2, -padding); + if (tr.rotateEnabled() && tr.rotateLineVisible()) { + ctx.lineTo( + shape.width() / 2, + -tr.rotateAnchorOffset() * Util._sign(shape.height()) - padding + ); + } + + ctx.fillStrokeShape(shape); + }, + hitFunc: (ctx, shape) => { + if (!this.shouldOverdrawWholeArea()) { + return; + } + const padding = this.padding(); + ctx.beginPath(); + ctx.rect( + -padding, + -padding, + shape.width() + padding * 2, + shape.height() + padding * 2 + ); + ctx.fillStrokeShape(shape); + }, + }); + this.add(back); + this._proxyDrag(back); + // do not bubble drag from the back shape + // because we already "drag" whole transformer + // so we don't want to trigger drag twice on transformer + back.on('dragstart', (e) => { + e.cancelBubble = true; + }); + back.on('dragmove', (e) => { + e.cancelBubble = true; + }); + back.on('dragend', (e) => { + e.cancelBubble = true; + }); + // force self update when we drag with shouldOverDrawWholeArea setting + this.on('dragmove', (e) => { + this.update(); + }); + } + _handleMouseDown(e) { + // do nothing if we already transforming + // that is possible to trigger with multitouch + if (this._transforming) { + return; + } + this._movingAnchorName = e.target.name().split(' ')[0]; + + const attrs = this._getNodeRect(); + const width = attrs.width; + const height = attrs.height; + + const hypotenuse = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2)); + this.sin = Math.abs(height / hypotenuse); + this.cos = Math.abs(width / hypotenuse); + + if (typeof window !== 'undefined') { + window.addEventListener('mousemove', this._handleMouseMove); + window.addEventListener('touchmove', this._handleMouseMove); + window.addEventListener('mouseup', this._handleMouseUp, true); + window.addEventListener('touchend', this._handleMouseUp, true); + } + + this._transforming = true; + const ap = e.target.getAbsolutePosition(); + const pos = e.target.getStage().getPointerPosition(); + this._anchorDragOffset = { + x: pos.x - ap.x, + y: pos.y - ap.y, + }; + activeTransformersCount++; + this._fire('transformstart', { evt: e.evt, target: this.getNode() }); + this._nodes.forEach((target) => { + target._fire('transformstart', { evt: e.evt, target }); + }); + } + _handleMouseMove(e) { + let x, y, newHypotenuse; + const anchorNode = this.findOne('.' + this._movingAnchorName)!; + const stage = anchorNode.getStage()!; + + stage.setPointersPositions(e); + + const pp = stage.getPointerPosition()!; + let newNodePos = { + x: pp.x - this._anchorDragOffset.x, + y: pp.y - this._anchorDragOffset.y, + }; + const oldAbs = anchorNode.getAbsolutePosition(); + + if (this.anchorDragBoundFunc()) { + newNodePos = this.anchorDragBoundFunc()(oldAbs, newNodePos, e); + } + anchorNode.setAbsolutePosition(newNodePos); + const newAbs = anchorNode.getAbsolutePosition(); + + // console.log(oldAbs, newNodePos, newAbs); + + if (oldAbs.x === newAbs.x && oldAbs.y === newAbs.y) { + return; + } + + // rotater is working very differently, so do it first + if (this._movingAnchorName === 'rotater') { + const attrs = this._getNodeRect(); + x = anchorNode.x() - attrs.width / 2; + y = -anchorNode.y() + attrs.height / 2; + + // hor angle is changed? + let delta = Math.atan2(-y, x) + Math.PI / 2; + + if (attrs.height < 0) { + delta -= Math.PI; + } + + const oldRotation = Konva.getAngle(this.rotation()); + const newRotation = oldRotation + delta; + + const tol = Konva.getAngle(this.rotationSnapTolerance()); + const snappedRot = getSnap(this.rotationSnaps(), newRotation, tol); + + const diff = snappedRot - attrs.rotation; + + const shape = rotateAroundCenter(attrs, diff); + this._fitNodesInto(shape, e); + return; + } + + const shiftBehavior = this.shiftBehavior(); + + let keepProportion: boolean; + if (shiftBehavior === 'inverted') { + keepProportion = this.keepRatio() && !e.shiftKey; + } else if (shiftBehavior === 'none') { + keepProportion = this.keepRatio(); + } else { + keepProportion = this.keepRatio() || e.shiftKey; + } + + var centeredScaling = this.centeredScaling() || e.altKey; + + if (this._movingAnchorName === 'top-left') { + if (keepProportion) { + var comparePoint = centeredScaling + ? { + x: this.width() / 2, + y: this.height() / 2, + } + : { + x: this.findOne('.bottom-right')!.x(), + y: this.findOne('.bottom-right')!.y(), + }; + newHypotenuse = Math.sqrt( + Math.pow(comparePoint.x - anchorNode.x(), 2) + + Math.pow(comparePoint.y - anchorNode.y(), 2) + ); + + var reverseX = this.findOne('.top-left')!.x() > comparePoint.x ? -1 : 1; + + var reverseY = this.findOne('.top-left')!.y() > comparePoint.y ? -1 : 1; + + x = newHypotenuse * this.cos * reverseX; + y = newHypotenuse * this.sin * reverseY; + + this.findOne('.top-left')!.x(comparePoint.x - x); + this.findOne('.top-left')!.y(comparePoint.y - y); + } + } else if (this._movingAnchorName === 'top-center') { + this.findOne('.top-left')!.y(anchorNode.y()); + } else if (this._movingAnchorName === 'top-right') { + if (keepProportion) { + var comparePoint = centeredScaling + ? { + x: this.width() / 2, + y: this.height() / 2, + } + : { + x: this.findOne('.bottom-left')!.x(), + y: this.findOne('.bottom-left')!.y(), + }; + + newHypotenuse = Math.sqrt( + Math.pow(anchorNode.x() - comparePoint.x, 2) + + Math.pow(comparePoint.y - anchorNode.y(), 2) + ); + + var reverseX = + this.findOne('.top-right')!.x() < comparePoint.x ? -1 : 1; + + var reverseY = + this.findOne('.top-right')!.y() > comparePoint.y ? -1 : 1; + + x = newHypotenuse * this.cos * reverseX; + y = newHypotenuse * this.sin * reverseY; + + this.findOne('.top-right')!.x(comparePoint.x + x); + this.findOne('.top-right')!.y(comparePoint.y - y); + } + var pos = anchorNode.position(); + this.findOne('.top-left')!.y(pos.y); + this.findOne('.bottom-right')!.x(pos.x); + } else if (this._movingAnchorName === 'middle-left') { + this.findOne('.top-left')!.x(anchorNode.x()); + } else if (this._movingAnchorName === 'middle-right') { + this.findOne('.bottom-right')!.x(anchorNode.x()); + } else if (this._movingAnchorName === 'bottom-left') { + if (keepProportion) { + var comparePoint = centeredScaling + ? { + x: this.width() / 2, + y: this.height() / 2, + } + : { + x: this.findOne('.top-right')!.x(), + y: this.findOne('.top-right')!.y(), + }; + + newHypotenuse = Math.sqrt( + Math.pow(comparePoint.x - anchorNode.x(), 2) + + Math.pow(anchorNode.y() - comparePoint.y, 2) + ); + + var reverseX = comparePoint.x < anchorNode.x() ? -1 : 1; + + var reverseY = anchorNode.y() < comparePoint.y ? -1 : 1; + + x = newHypotenuse * this.cos * reverseX; + y = newHypotenuse * this.sin * reverseY; + + anchorNode.x(comparePoint.x - x); + anchorNode.y(comparePoint.y + y); + } + + pos = anchorNode.position(); + + this.findOne('.top-left')!.x(pos.x); + this.findOne('.bottom-right')!.y(pos.y); + } else if (this._movingAnchorName === 'bottom-center') { + this.findOne('.bottom-right')!.y(anchorNode.y()); + } else if (this._movingAnchorName === 'bottom-right') { + if (keepProportion) { + var comparePoint = centeredScaling + ? { + x: this.width() / 2, + y: this.height() / 2, + } + : { + x: this.findOne('.top-left')!.x(), + y: this.findOne('.top-left')!.y(), + }; + + newHypotenuse = Math.sqrt( + Math.pow(anchorNode.x() - comparePoint.x, 2) + + Math.pow(anchorNode.y() - comparePoint.y, 2) + ); + + var reverseX = + this.findOne('.bottom-right')!.x() < comparePoint.x ? -1 : 1; + + var reverseY = + this.findOne('.bottom-right')!.y() < comparePoint.y ? -1 : 1; + + x = newHypotenuse * this.cos * reverseX; + y = newHypotenuse * this.sin * reverseY; + + this.findOne('.bottom-right')!.x(comparePoint.x + x); + this.findOne('.bottom-right')!.y(comparePoint.y + y); + } + } else { + console.error( + new Error( + 'Wrong position argument of selection resizer: ' + + this._movingAnchorName + ) + ); + } + + var centeredScaling = this.centeredScaling() || e.altKey; + if (centeredScaling) { + const topLeft = this.findOne('.top-left')!; + const bottomRight = this.findOne('.bottom-right')!; + const topOffsetX = topLeft.x(); + const topOffsetY = topLeft.y(); + + const bottomOffsetX = this.getWidth() - bottomRight.x(); + const bottomOffsetY = this.getHeight() - bottomRight.y(); + + bottomRight.move({ + x: -topOffsetX, + y: -topOffsetY, + }); + + topLeft.move({ + x: bottomOffsetX, + y: bottomOffsetY, + }); + } + + const absPos = this.findOne('.top-left')!.getAbsolutePosition(); + + x = absPos.x; + y = absPos.y; + + const width = + this.findOne('.bottom-right')!.x() - this.findOne('.top-left')!.x(); + + const height = + this.findOne('.bottom-right')!.y() - this.findOne('.top-left')!.y(); + + this._fitNodesInto( + { + x: x, + y: y, + width: width, + height: height, + rotation: Konva.getAngle(this.rotation()), + }, + e + ); + } + _handleMouseUp(e) { + this._removeEvents(e); + } + getAbsoluteTransform() { + return this.getTransform(); + } + _removeEvents(e?) { + if (this._transforming) { + this._transforming = false; + if (typeof window !== 'undefined') { + window.removeEventListener('mousemove', this._handleMouseMove); + window.removeEventListener('touchmove', this._handleMouseMove); + window.removeEventListener('mouseup', this._handleMouseUp, true); + window.removeEventListener('touchend', this._handleMouseUp, true); + } + const node = this.getNode(); + activeTransformersCount--; + this._fire('transformend', { evt: e, target: node }); + // redraw layer to restore hit graph + this.getLayer()?.batchDraw(); + + if (node) { + this._nodes.forEach((target) => { + target._fire('transformend', { evt: e, target }); + // redraw layer to restore hit graph + target.getLayer()?.batchDraw(); + }); + } + this._movingAnchorName = null; + } + } + _fitNodesInto(newAttrs, evt?) { + const oldAttrs = this._getNodeRect(); + + const minSize = 1; + + if (Util._inRange(newAttrs.width, -this.padding() * 2 - minSize, minSize)) { + this.update(); + return; + } + if ( + Util._inRange(newAttrs.height, -this.padding() * 2 - minSize, minSize) + ) { + this.update(); + return; + } + + const t = new Transform(); + t.rotate(Konva.getAngle(this.rotation())); + if ( + this._movingAnchorName && + newAttrs.width < 0 && + this._movingAnchorName.indexOf('left') >= 0 + ) { + const offset = t.point({ + x: -this.padding() * 2, + y: 0, + }); + newAttrs.x += offset.x; + newAttrs.y += offset.y; + newAttrs.width += this.padding() * 2; + this._movingAnchorName = this._movingAnchorName.replace('left', 'right'); + this._anchorDragOffset.x -= offset.x; + this._anchorDragOffset.y -= offset.y; + } else if ( + this._movingAnchorName && + newAttrs.width < 0 && + this._movingAnchorName.indexOf('right') >= 0 + ) { + const offset = t.point({ + x: this.padding() * 2, + y: 0, + }); + this._movingAnchorName = this._movingAnchorName.replace('right', 'left'); + this._anchorDragOffset.x -= offset.x; + this._anchorDragOffset.y -= offset.y; + newAttrs.width += this.padding() * 2; + } + if ( + this._movingAnchorName && + newAttrs.height < 0 && + this._movingAnchorName.indexOf('top') >= 0 + ) { + const offset = t.point({ + x: 0, + y: -this.padding() * 2, + }); + newAttrs.x += offset.x; + newAttrs.y += offset.y; + this._movingAnchorName = this._movingAnchorName.replace('top', 'bottom'); + this._anchorDragOffset.x -= offset.x; + this._anchorDragOffset.y -= offset.y; + newAttrs.height += this.padding() * 2; + } else if ( + this._movingAnchorName && + newAttrs.height < 0 && + this._movingAnchorName.indexOf('bottom') >= 0 + ) { + const offset = t.point({ + x: 0, + y: this.padding() * 2, + }); + this._movingAnchorName = this._movingAnchorName.replace('bottom', 'top'); + this._anchorDragOffset.x -= offset.x; + this._anchorDragOffset.y -= offset.y; + newAttrs.height += this.padding() * 2; + } + + if (this.boundBoxFunc()) { + const bounded = this.boundBoxFunc()(oldAttrs, newAttrs); + if (bounded) { + newAttrs = bounded; + } else { + Util.warn( + 'boundBoxFunc returned falsy. You should return new bound rect from it!' + ); + } + } + + // base size value doesn't really matter + // we just need to think about bounding boxes as transforms + // but how? + // the idea is that we have a transformed rectangle with the size of "baseSize" + const baseSize = 10000000; + const oldTr = new Transform(); + oldTr.translate(oldAttrs.x, oldAttrs.y); + oldTr.rotate(oldAttrs.rotation); + oldTr.scale(oldAttrs.width / baseSize, oldAttrs.height / baseSize); + + const newTr = new Transform(); + const newScaleX = newAttrs.width / baseSize; + const newScaleY = newAttrs.height / baseSize; + + if (this.flipEnabled() === false) { + newTr.translate(newAttrs.x, newAttrs.y); + newTr.rotate(newAttrs.rotation); + newTr.translate( + newAttrs.width < 0 ? newAttrs.width : 0, + newAttrs.height < 0 ? newAttrs.height : 0 + ); + newTr.scale(Math.abs(newScaleX), Math.abs(newScaleY)); + } else { + newTr.translate(newAttrs.x, newAttrs.y); + newTr.rotate(newAttrs.rotation); + newTr.scale(newScaleX, newScaleY); + } + + // now lets think we had [old transform] and n ow we have [new transform] + // Now, the questions is: how can we transform "parent" to go from [old transform] into [new transform] + // in equation it will be: + // [delta transform] * [old transform] = [new transform] + // that means that + // [delta transform] = [new transform] * [old transform inverted] + const delta = newTr.multiply(oldTr.invert()); + + this._nodes.forEach((node) => { + // for each node we have the same [delta transform] + // the equations is + // [delta transform] * [parent transform] * [old local transform] = [parent transform] * [new local transform] + // and we need to find [new local transform] + // [new local] = [parent inverted] * [delta] * [parent] * [old local] + const parentTransform = node.getParent()!.getAbsoluteTransform(); + const localTransform = node.getTransform().copy(); + // skip offset: + localTransform.translate(node.offsetX(), node.offsetY()); + + const newLocalTransform = new Transform(); + newLocalTransform + .multiply(parentTransform.copy().invert()) + .multiply(delta) + .multiply(parentTransform) + .multiply(localTransform); + + const attrs = newLocalTransform.decompose(); + node.setAttrs(attrs); + node.getLayer()?.batchDraw(); + }); + this.rotation(Util._getRotation(newAttrs.rotation)); + // trigger transform event AFTER we update rotation + this._nodes.forEach((node) => { + this._fire('transform', { evt: evt, target: node }); + node._fire('transform', { evt: evt, target: node }); + }); + this._resetTransformCache(); + this.update(); + this.getLayer()!.batchDraw(); + } + /** + * force update of Konva.Transformer. + * Use it when you updated attached Konva.Group and now you need to reset transformer size + * @method + * @name Konva.Transformer#forceUpdate + */ + forceUpdate() { + this._resetTransformCache(); + this.update(); + } + + _batchChangeChild(selector: string, attrs: any) { + const anchor = this.findOne(selector)!; + anchor.setAttrs(attrs); + } + + update() { + const attrs = this._getNodeRect(); + this.rotation(Util._getRotation(attrs.rotation)); + const width = attrs.width; + const height = attrs.height; + + const enabledAnchors = this.enabledAnchors(); + const resizeEnabled = this.resizeEnabled(); + const padding = this.padding(); + + const anchorSize = this.anchorSize(); + const anchors = this.find('._anchor'); + anchors.forEach((node) => { + node.setAttrs({ + width: anchorSize, + height: anchorSize, + offsetX: anchorSize / 2, + offsetY: anchorSize / 2, + stroke: this.anchorStroke(), + strokeWidth: this.anchorStrokeWidth(), + fill: this.anchorFill(), + cornerRadius: this.anchorCornerRadius(), + }); + }); + + this._batchChangeChild('.top-left', { + x: 0, + y: 0, + offsetX: anchorSize / 2 + padding, + offsetY: anchorSize / 2 + padding, + visible: resizeEnabled && enabledAnchors.indexOf('top-left') >= 0, + }); + this._batchChangeChild('.top-center', { + x: width / 2, + y: 0, + offsetY: anchorSize / 2 + padding, + visible: resizeEnabled && enabledAnchors.indexOf('top-center') >= 0, + }); + this._batchChangeChild('.top-right', { + x: width, + y: 0, + offsetX: anchorSize / 2 - padding, + offsetY: anchorSize / 2 + padding, + visible: resizeEnabled && enabledAnchors.indexOf('top-right') >= 0, + }); + this._batchChangeChild('.middle-left', { + x: 0, + y: height / 2, + offsetX: anchorSize / 2 + padding, + visible: resizeEnabled && enabledAnchors.indexOf('middle-left') >= 0, + }); + this._batchChangeChild('.middle-right', { + x: width, + y: height / 2, + offsetX: anchorSize / 2 - padding, + visible: resizeEnabled && enabledAnchors.indexOf('middle-right') >= 0, + }); + this._batchChangeChild('.bottom-left', { + x: 0, + y: height, + offsetX: anchorSize / 2 + padding, + offsetY: anchorSize / 2 - padding, + visible: resizeEnabled && enabledAnchors.indexOf('bottom-left') >= 0, + }); + this._batchChangeChild('.bottom-center', { + x: width / 2, + y: height, + offsetY: anchorSize / 2 - padding, + visible: resizeEnabled && enabledAnchors.indexOf('bottom-center') >= 0, + }); + this._batchChangeChild('.bottom-right', { + x: width, + y: height, + offsetX: anchorSize / 2 - padding, + offsetY: anchorSize / 2 - padding, + visible: resizeEnabled && enabledAnchors.indexOf('bottom-right') >= 0, + }); + + this._batchChangeChild('.rotater', { + x: width / 2, + y: -this.rotateAnchorOffset() * Util._sign(height) - padding, + visible: this.rotateEnabled(), + }); + + this._batchChangeChild('.back', { + width: width, + height: height, + visible: this.borderEnabled(), + stroke: this.borderStroke(), + strokeWidth: this.borderStrokeWidth(), + dash: this.borderDash(), + x: 0, + y: 0, + }); + + const styleFunc = this.anchorStyleFunc(); + if (styleFunc) { + anchors.forEach((node) => { + styleFunc(node); + }); + } + this.getLayer()?.batchDraw(); + } + /** + * determine if transformer is in active transform + * @method + * @name Konva.Transformer#isTransforming + * @returns {Boolean} + */ + isTransforming() { + return this._transforming; + } + /** + * Stop active transform action + * @method + * @name Konva.Transformer#stopTransform + * @returns {Boolean} + */ + stopTransform() { + if (this._transforming) { + this._removeEvents(); + const anchorNode = this.findOne('.' + this._movingAnchorName); + if (anchorNode) { + anchorNode.stopDrag(); + } + } + } + destroy() { + if (this.getStage() && this._cursorChange) { + this.getStage()!.content && (this.getStage()!.content.style.cursor = ''); + } + Group.prototype.destroy.call(this); + this.detach(); + this._removeEvents(); + return this; + } + // do not work as a container + // we will recreate inner nodes manually + toObject() { + return Node.prototype.toObject.call(this); + } + + // overwrite clone to NOT use method from Container + clone(obj?: any) { + const node = Node.prototype.clone.call(this, obj); + return node as this; + } + getClientRect() { + if (this.nodes().length > 0) { + return super.getClientRect(); + } else { + // if we are detached return zero size + // so it will be skipped in calculations + return { x: 0, y: 0, width: 0, height: 0 }; + } + } + + nodes: GetSet; + enabledAnchors: GetSet; + rotationSnaps: GetSet; + anchorSize: GetSet; + resizeEnabled: GetSet; + rotateEnabled: GetSet; + rotateLineVisible: GetSet; + rotateAnchorOffset: GetSet; + rotationSnapTolerance: GetSet; + rotateAnchorCursor: GetSet; + padding: GetSet; + borderEnabled: GetSet; + borderStroke: GetSet; + borderStrokeWidth: GetSet; + borderDash: GetSet; + anchorFill: GetSet; + anchorStroke: GetSet; + anchorCornerRadius: GetSet; + anchorStrokeWidth: GetSet; + keepRatio: GetSet; + shiftBehavior: GetSet; + centeredScaling: GetSet; + flipEnabled: GetSet; + ignoreStroke: GetSet; + boundBoxFunc: GetSet<(oldBox: Box, newBox: Box) => Box, this>; + anchorDragBoundFunc: GetSet< + (oldPos: Vector2d, newPos: Vector2d, e: MouseEvent) => Vector2d, + this + >; + anchorStyleFunc: GetSet void), this>; + shouldOverdrawWholeArea: GetSet; + useSingleNodeRotation: GetSet; +} + +function validateAnchors(val) { + if (!(val instanceof Array)) { + Util.warn('enabledAnchors value should be an array'); + } + if (val instanceof Array) { + val.forEach(function (name) { + if (ANCHORS_NAMES.indexOf(name) === -1) { + Util.warn( + 'Unknown anchor name: ' + + name + + '. Available names are: ' + + ANCHORS_NAMES.join(', ') + ); + } + }); + } + return val || []; +} + +Transformer.prototype.className = 'Transformer'; +_registerNode(Transformer); + +/** + * get/set enabled handlers + * @name Konva.Transformer#enabledAnchors + * @method + * @param {Array} array + * @returns {Array} + * @example + * // get list of handlers + * var enabledAnchors = transformer.enabledAnchors(); + * + * // set handlers + * transformer.enabledAnchors(['top-left', 'top-center', 'top-right', 'middle-right', 'middle-left', 'bottom-left', 'bottom-center', 'bottom-right']); + */ +Factory.addGetterSetter( + Transformer, + 'enabledAnchors', + ANCHORS_NAMES, + validateAnchors +); + +/** + * get/set flip enabled + * @name Konva.Transformer#flipEnabled + * @method + * @param {Boolean} flag + * @returns {Boolean} + * @example + * // get flip enabled property + * var flipEnabled = transformer.flipEnabled(); + * + * // set flip enabled property + * transformer.flipEnabled(false); + */ +Factory.addGetterSetter( + Transformer, + 'flipEnabled', + true, + getBooleanValidator() +); + +/** + * get/set resize ability. If false it will automatically hide resizing handlers + * @name Konva.Transformer#resizeEnabled + * @method + * @param {Boolean} enabled + * @returns {Boolean} + * @example + * // get + * var resizeEnabled = transformer.resizeEnabled(); + * + * // set + * transformer.resizeEnabled(false); + */ +Factory.addGetterSetter(Transformer, 'resizeEnabled', true); +/** + * get/set anchor size. Default is 10 + * @name Konva.Transformer#anchorSize + * @method + * @param {Number} size + * @returns {Number} + * @example + * // get + * var anchorSize = transformer.anchorSize(); + * + * // set + * transformer.anchorSize(20) + */ +Factory.addGetterSetter(Transformer, 'anchorSize', 10, getNumberValidator()); + +/** + * get/set ability to rotate. + * @name Konva.Transformer#rotateEnabled + * @method + * @param {Boolean} enabled + * @returns {Boolean} + * @example + * // get + * var rotateEnabled = transformer.rotateEnabled(); + * + * // set + * transformer.rotateEnabled(false); + */ +Factory.addGetterSetter(Transformer, 'rotateEnabled', true); + +/** + * get/set visibility of a little line that connects transformer and rotate anchor. + * @name Konva.Transformer#rotateLineVisible + * @method + * @param {Boolean} enabled + * @returns {Boolean} + * @example + * // get + * var rotateLineVisible = transformer.rotateLineVisible(); + * + * // set + * transformer.rotateLineVisible(false); + */ +Factory.addGetterSetter(Transformer, 'rotateLineVisible', true); + +/** + * get/set rotation snaps angles. + * @name Konva.Transformer#rotationSnaps + * @method + * @param {Array} array + * @returns {Array} + * @example + * // get + * var rotationSnaps = transformer.rotationSnaps(); + * + * // set + * transformer.rotationSnaps([0, 90, 180, 270]); + */ +Factory.addGetterSetter(Transformer, 'rotationSnaps', []); + +/** + * get/set distance for rotation handler + * @name Konva.Transformer#rotateAnchorOffset + * @method + * @param {Number} offset + * @returns {Number} + * @example + * // get + * var rotateAnchorOffset = transformer.rotateAnchorOffset(); + * + * // set + * transformer.rotateAnchorOffset(100); + */ +Factory.addGetterSetter( + Transformer, + 'rotateAnchorOffset', + 50, + getNumberValidator() +); + +/** + * get/set rotation anchor cursor + * @name Konva.Transformer#rotateAnchorCursor + * @method + * @param {String} cursorName + * @returns {String} + * @example + * // get + * var currentRotationAnchorCursor = transformer.rotateAnchorCursor(); + * + * // set + * transformer.rotateAnchorCursor('grab'); + */ +Factory.addGetterSetter(Transformer, 'rotateAnchorCursor', 'crosshair'); + +/** + * get/set distance for rotation tolerance + * @name Konva.Transformer#rotationSnapTolerance + * @method + * @param {Number} tolerance + * @returns {Number} + * @example + * // get + * var rotationSnapTolerance = transformer.rotationSnapTolerance(); + * + * // set + * transformer.rotationSnapTolerance(100); + */ +Factory.addGetterSetter( + Transformer, + 'rotationSnapTolerance', + 5, + getNumberValidator() +); + +/** + * get/set visibility of border + * @name Konva.Transformer#borderEnabled + * @method + * @param {Boolean} enabled + * @returns {Boolean} + * @example + * // get + * var borderEnabled = transformer.borderEnabled(); + * + * // set + * transformer.borderEnabled(false); + */ +Factory.addGetterSetter(Transformer, 'borderEnabled', true); + +/** + * get/set anchor stroke color + * @name Konva.Transformer#anchorStroke + * @method + * @param {String} strokeColor + * @returns {String} + * @example + * // get + * var anchorStroke = transformer.anchorStroke(); + * + * // set + * transformer.anchorStroke('red'); + */ +Factory.addGetterSetter(Transformer, 'anchorStroke', 'rgb(0, 161, 255)'); + +/** + * get/set anchor stroke width + * @name Konva.Transformer#anchorStrokeWidth + * @method + * @param {Number} anchorStrokeWidth + * @returns {Number} + * @example + * // get + * var anchorStrokeWidth = transformer.anchorStrokeWidth(); + * + * // set + * transformer.anchorStrokeWidth(3); + */ +Factory.addGetterSetter( + Transformer, + 'anchorStrokeWidth', + 1, + getNumberValidator() +); + +/** + * get/set anchor fill color + * @name Konva.Transformer#anchorFill + * @method + * @param {String} anchorFill + * @returns {String} + * @example + * // get + * var anchorFill = transformer.anchorFill(); + * + * // set + * transformer.anchorFill('red'); + */ +Factory.addGetterSetter(Transformer, 'anchorFill', 'white'); + +/** + * get/set anchor corner radius + * @name Konva.Transformer#anchorCornerRadius + * @method + * @param {Number} radius + * @returns {Number} + * @example + * // get + * var anchorCornerRadius = transformer.anchorCornerRadius(); + * + * // set + * transformer.anchorCornerRadius(3); + */ +Factory.addGetterSetter( + Transformer, + 'anchorCornerRadius', + 0, + getNumberValidator() +); + +/** + * get/set border stroke color + * @name Konva.Transformer#borderStroke + * @method + * @param {Boolean} enabled + * @returns {Boolean} + * @example + * // get + * var borderStroke = transformer.borderStroke(); + * + * // set + * transformer.borderStroke('red'); + */ +Factory.addGetterSetter(Transformer, 'borderStroke', 'rgb(0, 161, 255)'); + +/** + * get/set border stroke width + * @name Konva.Transformer#borderStrokeWidth + * @method + * @param {Number} strokeWidth + * @returns {Number} + * @example + * // get + * var borderStrokeWidth = transformer.borderStrokeWidth(); + * + * // set + * transformer.borderStrokeWidth(3); + */ +Factory.addGetterSetter( + Transformer, + 'borderStrokeWidth', + 1, + getNumberValidator() +); + +/** + * get/set border dash array + * @name Konva.Transformer#borderDash + * @method + * @param {Array} dash array + * @returns {Array} + * @example + * // get + * var borderDash = transformer.borderDash(); + * + * // set + * transformer.borderDash([2, 2]); + */ +Factory.addGetterSetter(Transformer, 'borderDash'); + +/** + * get/set should we keep ratio while resize anchors at corners + * @name Konva.Transformer#keepRatio + * @method + * @param {Boolean} keepRatio + * @returns {Boolean} + * @example + * // get + * var keepRatio = transformer.keepRatio(); + * + * // set + * transformer.keepRatio(false); + */ +Factory.addGetterSetter(Transformer, 'keepRatio', true); + +/** + * get/set how to react on skift key while resizing anchors at corners + * @name Konva.Transformer#shiftBehavior + * @method + * @param {String} shiftBehavior + * @returns {String} + * @example + * // get + * var shiftBehavior = transformer.shiftBehavior(); + * + * // set + * transformer.shiftBehavior('none'); + */ +Factory.addGetterSetter(Transformer, 'shiftBehavior', 'default'); + +/** + * get/set should we resize relative to node's center? + * @name Konva.Transformer#centeredScaling + * @method + * @param {Boolean} centeredScaling + * @returns {Boolean} + * @example + * // get + * var centeredScaling = transformer.centeredScaling(); + * + * // set + * transformer.centeredScaling(true); + */ +Factory.addGetterSetter(Transformer, 'centeredScaling', false); + +/** + * get/set should we think about stroke while resize? Good to use when a shape has strokeScaleEnabled = false + * default is false + * @name Konva.Transformer#ignoreStroke + * @method + * @param {Boolean} ignoreStroke + * @returns {Boolean} + * @example + * // get + * var ignoreStroke = transformer.ignoreStroke(); + * + * // set + * transformer.ignoreStroke(true); + */ +Factory.addGetterSetter(Transformer, 'ignoreStroke', false); + +/** + * get/set padding + * @name Konva.Transformer#padding + * @method + * @param {Number} padding + * @returns {Number} + * @example + * // get + * var padding = transformer.padding(); + * + * // set + * transformer.padding(10); + */ +Factory.addGetterSetter(Transformer, 'padding', 0, getNumberValidator()); + +/** + * get/set attached nodes of the Transformer. Transformer will adapt to their size and listen to their events + * @method + * @name Konva.Transformer#nodes + * @returns {Konva.Node} + * @example + * // get + * const nodes = transformer.nodes(); + * + * // set + * transformer.nodes([rect, circle]); + * + * // push new item: + * const oldNodes = transformer.nodes(); + * const newNodes = oldNodes.concat([newShape]); + * // it is important to set new array instance (and concat method above will create it) + * transformer.nodes(newNodes); + */ + +Factory.addGetterSetter(Transformer, 'nodes'); +// @ts-ignore +// deprecated +Factory.addGetterSetter(Transformer, 'node'); + +/** + * get/set bounding box function. **IMPORTANT!** boundBondFunc operates in absolute coordinates. + * @name Konva.Transformer#boundBoxFunc + * @method + * @param {Function} func + * @returns {Function} + * @example + * // get + * var boundBoxFunc = transformer.boundBoxFunc(); + * + * // set + * transformer.boundBoxFunc(function(oldBox, newBox) { + * // width and height of the boxes are corresponding to total absolute width and height of all nodes combined + * // so it includes scale of the node. + * if (newBox.width > 200) { + * return oldBox; + * } + * return newBox; + * }); + */ +Factory.addGetterSetter(Transformer, 'boundBoxFunc'); + +/** + * get/set dragging func for transformer anchors + * @name Konva.Transformer#anchorDragBoundFunc + * @method + * @param {Function} func + * @returns {Function} + * @example + * // get + * var anchorDragBoundFunc = transformer.anchorDragBoundFunc(); + * + * // set + * transformer.anchorDragBoundFunc(function(oldAbsPos, newAbsPos, event) { + * return { + * x: 0, + * y: newAbsolutePosition.y + * } + * }); + */ +Factory.addGetterSetter(Transformer, 'anchorDragBoundFunc'); + +/** + * get/set styling function for transformer anchors to overwrite default styles + * @name Konva.Transformer#anchorStyleFunc + * @method + * @param {Function} func + * @returns {Function} + * @example + * // get + * var anchorStyleFunc = transformer.anchorStyleFunc(); + * + * // set + * transformer.anchorStyleFunc(function(anchor) { + * // anchor is a simple Konva.Rect instance + * // it will be executed AFTER all attributes are set, like 'anchorStrokeWidth' or 'anchorFill' + * if (anchor.hasName('.rotater')) { + * // make rotater anchor filled black and looks like a circle + * anchor.fill('black'); + * anchor.cornerRadius(anchor.width() / 2); + * } + * }); + */ +Factory.addGetterSetter(Transformer, 'anchorStyleFunc'); + +/** + * using this setting you can drag transformer group by dragging empty space between attached nodes + * shouldOverdrawWholeArea = true may temporary disable all events on attached nodes + * @name Konva.Transformer#shouldOverdrawWholeArea + * @method + * @param {Boolean} shouldOverdrawWholeArea + * @returns {Boolean} + * @example + * // get + * var shouldOverdrawWholeArea = transformer.shouldOverdrawWholeArea(); + * + * // set + * transformer.shouldOverdrawWholeArea(true); + */ +Factory.addGetterSetter(Transformer, 'shouldOverdrawWholeArea', false); + +/** + * If you have just one attached node to Transformer it will set its initial rotation to the rotation of that node. + * In some cases you may need to set a different rotation. + * @name Konva.Transformer#useSingleNodeRotation + * @method + * @param {Boolean} useSingleNodeRotation + * @returns {Boolean} + * @example + * // set flag to false + * transformer.useSingleNodeRotation(false); + * // attach a shape + * transformer.nodes([shape]); + * transformer.rotation(45); + * transformer.update(); + */ +Factory.addGetterSetter(Transformer, 'useSingleNodeRotation', true); + +Factory.backCompat(Transformer, { + lineEnabled: 'borderEnabled', + rotateHandlerOffset: 'rotateAnchorOffset', + enabledHandlers: 'enabledAnchors', +}); diff --git a/src/shapes/Wedge.ts b/src/shapes/Wedge.ts new file mode 100644 index 000000000..1a312961a --- /dev/null +++ b/src/shapes/Wedge.ts @@ -0,0 +1,128 @@ +import { Factory } from '../Factory'; +import { Context } from '../Context'; +import { Shape, ShapeConfig } from '../Shape'; +import { Konva } from '../Global'; +import { getNumberValidator } from '../Validators'; +import { _registerNode } from '../Global'; + +import { GetSet } from '../types'; + +export interface WedgeConfig extends ShapeConfig { + angle: number; + radius: number; + clockwise?: boolean; +} + +/** + * Wedge constructor + * @constructor + * @memberof Konva + * @augments Konva.Shape + * @param {Object} config + * @param {Number} config.angle in degrees + * @param {Number} config.radius + * @param {Boolean} [config.clockwise] + * @@shapeParams + * @@nodeParams + * @example + * // draw a wedge that's pointing downwards + * var wedge = new Konva.Wedge({ + * radius: 40, + * fill: 'red', + * stroke: 'black' + * strokeWidth: 5, + * angleDeg: 60, + * rotationDeg: -120 + * }); + */ +export class Wedge extends Shape { + _sceneFunc(context: Context) { + context.beginPath(); + context.arc( + 0, + 0, + this.radius(), + 0, + Konva.getAngle(this.angle()), + this.clockwise() + ); + context.lineTo(0, 0); + context.closePath(); + context.fillStrokeShape(this); + } + getWidth() { + return this.radius() * 2; + } + getHeight() { + return this.radius() * 2; + } + setWidth(width: number) { + this.radius(width / 2); + } + setHeight(height: number) { + this.radius(height / 2); + } + + radius: GetSet; + angle: GetSet; + clockwise: GetSet; +} + +Wedge.prototype.className = 'Wedge'; +Wedge.prototype._centroid = true; +Wedge.prototype._attrsAffectingSize = ['radius']; +_registerNode(Wedge); + +/** + * get/set radius + * @name Konva.Wedge#radius + * @method + * @param {Number} radius + * @returns {Number} + * @example + * // get radius + * var radius = wedge.radius(); + * + * // set radius + * wedge.radius(10); + */ +Factory.addGetterSetter(Wedge, 'radius', 0, getNumberValidator()); + +/** + * get/set angle in degrees + * @name Konva.Wedge#angle + * @method + * @param {Number} angle + * @returns {Number} + * @example + * // get angle + * var angle = wedge.angle(); + * + * // set angle + * wedge.angle(20); + */ +Factory.addGetterSetter(Wedge, 'angle', 0, getNumberValidator()); + +/** + * get/set clockwise flag + * @name Konva.Wedge#clockwise + * @method + * @param {Number} clockwise + * @returns {Number} + * @example + * // get clockwise flag + * var clockwise = wedge.clockwise(); + * + * // draw wedge counter-clockwise + * wedge.clockwise(false); + * + * // draw wedge clockwise + * wedge.clockwise(true); + */ +Factory.addGetterSetter(Wedge, 'clockwise', false); + +Factory.backCompat(Wedge, { + angleDeg: 'angle', + getAngleDeg: 'getAngle', + setAngleDeg: 'setAngle', +}); diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 000000000..6ce992fed --- /dev/null +++ b/src/types.ts @@ -0,0 +1,84 @@ +export interface GetSet { + (): Type; + (v: Type): This; +} + +export interface Vector2d { + x: number; + y: number; +} + +export interface PathSegment { + command: + | 'm' + | 'M' + | 'l' + | 'L' + | 'v' + | 'V' + | 'h' + | 'H' + | 'z' + | 'Z' + | 'c' + | 'C' + | 'q' + | 'Q' + | 't' + | 'T' + | 's' + | 'S' + | 'a' + | 'A'; + start: Vector2d; + points: number[]; + pathLength: number; +} + +export interface IRect { + x: number; + y: number; + width: number; + height: number; +} + +export interface IFrame { + time: number; + timeDiff: number; + lastTime: number; + frameRate: number; +} + +export type AnimationFn = (frame?: IFrame) => boolean | void; + +export enum KonvaNodeEvent { + mouseover = 'mouseover', + mouseout = 'mouseout', + mousemove = 'mousemove', + mouseleave = 'mouseleave', + mouseenter = 'mouseenter', + mousedown = 'mousedown', + mouseup = 'mouseup', + wheel = 'wheel', + contextmenu = 'contextmenu', + click = 'click', + dblclick = 'dblclick', + touchstart = 'touchstart', + touchmove = 'touchmove', + touchend = 'touchend', + tap = 'tap', + dbltap = 'dbltap', + dragstart = 'dragstart', + dragmove = 'dragmove', + dragend = 'dragend', +} + +export interface RGB { + r: number; + g: number; + b: number; +} + +export interface RGBA extends RGB { + a: number; +} diff --git a/test/assets/bunny.png b/test/assets/bunny.png new file mode 100644 index 0000000000000000000000000000000000000000..79c31675083b7ffc272a6370bc189360bde484d0 GIT binary patch literal 449 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�=C_xubX%3C)D%KYqjz_lbBx`cTB>cTkPDv6<2t6-p^WVg2JXOLSH3<5O4L=NIuQh{9Hy>RG}a0{{U7i(kZQ@jY0naSLgo#71M#8sl+OUDkFOf z#H$&OLbuaU6>hErTTwb%-&4=G{7`^-pD&k5B!L-)Na5$ynDVIJDD5FJ=vOKV@=q>| zPp{SW;@}Psx;(n;MWY`-@O4c(YC4@wlhE=Br1F@YA0%qZjE{A&Evs2TE=9Np+v{;6 zs;`hAZhF3pVxW|%KjHJ~M@gAU5&W+rc*3$JgAF>!EQ+U5Z8x>g)BU%%#QNwYAb*pp z>nlS_(}zZT325H$cn5~t|HvKKDJtkFk6=#S#?q5X;FY#034sYeXfcriJOH2_vnq6SbC9HsIU<-S*z-$kyldepR472raM6f>A+X#=R1n&dvT} z8^<>;+QoDy@Ct-u5v!=%vnD#7S+x280Ey&kKGa0IZde-+t-aN3l665V)kl>&aPF5T zijXJE<^H49%6vOs#OpnSPnPZYcI8I(jw-1#d2CvwRCw9Zo_HzRR7lpPj9|wmpGo?Q z`!}#K+BhImhac+s^u+5u*Y?jzY30NA{{X7JUeCvU-HqHhY(C`0$Y!9-MNL}^E1fMA z@e82?`vT+wK7-kYVEl!i2zqe_qclig?4PZ4fvZ*8Y3G|?` z4jWfLLC3O7W_E^^pdMWqNfNoLfl7auqNly^vrD+?j0p20%DhI#!I&LrN)i^zdFTC) zvx|9CLWXYcklZ83Dvdp-&!#QM98gn0wP1!uXu|~sw6Hw8dTnbH_24h!{@*863bq01 zwgq^OmKzH!jg45M0>NG=h+o_C2D>pv7dnNBa|f|IKDJn{VpyP}l`!oaRiuUnOa z{23Jff6JnUQIXBVnsugDmY2$7Kvb0)NeEbv(gm&bxc>kTevpQVl0lkr>X9QX$aLTy zALi&u#0|+K{^-#Q>IMP|fDWN=BU}dbgJOTgTiAI`CXuCTfN}YBb}Cc`;m4x`wURjS zwQ$PJDhyHy6u;;!qe~u6qjB}*dlT$4M9Eb&s~&^Po)$+$sjG(d8bOzVt+>U>xz~|T)Oe9rcFaRGsf2;H8@-0YNSx7ZJ zdTz{RX|c35HI*`?aMQ%{OC*vis-i8)V#I|Ze<#}#J3Afuh2V=1|)`YUsRvia#uM%mC zP?*2f!=jgkhNi1C473tS_lQzRNM#S^FRgTdFUk7-2RwT*Xh{vJKTe;w{f?0mk}{B3 zO+B1?ENd_5>G||Eu`@Ng84f)@ z&WBC)hlajAIz4O@Fg}nM{Z}NZ0_XZ)+cvEtDz~3XCXF5<2*LjVSNgxn&@&#?jtNUL zpt%Yi%7p{y4ZeWe{lBwMsG3JA3DePM(?=Pj8lShN1#a6|aP>7Yi1>ln6jA|1QpByS z3leYj{CjRGS#A+r_SfwlZQ;`tjw7Zz)vx)M_s$=rb32D~?iz)ZCzh83idnQ(W%D5( zT6#KhaKaKssbscVS*?oYDkCCMzpmFnlypq8S%06+jq>tW$6 zWh$&S9ZX1s$;YO|6~FZM$7|GZ`Sqd3o_!{3YGnP_1YE9?-a-mrQV7-AK+p>tg#dBS zlCVHO+lIt5Xo@508i~RDAE^OX#jmJFZuUy9;A_unlu_ zk4q{jEP$=SHshbizbc0KR~p!AjfjMpg_yAbpkmtX$Y1GiZUSRP1W=50;sQV%M^m?sGOsCwmZhJ~UyZgxCW$r8 zdOB5xvQq0{O-%$b%2a)1fVb42Zb+*nY)Grn{{VxnNX9FWpXh8q)&5?9nF_y_SC+=X zn5iU}V738CW&ne5Zm0VDDNxFw5kPveHEa%>wMq$7W_>47x|K~rz%mal!Ym!zPmpATJC zB)B@-1q%S40)#~z#1adD2r2_`d6_oJE+Vy#&h+Y50EVr2@ajL^trltN9BL)wj*JiU zmiYJAy|dCATjvK`;$y<@-{Lr2S872?PPFvb{nl$;#rCID=I3^iB$wCBB=c=MN5J-GXMglnvRk_mil`#)t(sWyeK&%sL41i6t1vl3eTW| z>{%N21P{_!9ssxa+up+;BT9d(`Sra3&{Bl|0F(UvJH=XAU5f{eqFn(=)4^NF8s}T8 zDC6sMZU?tRsUUROjlD6_nhcCn_)D|FAan{Ffhi=Vf{^|(<~dU%?sI&at4 zqkZusw7~#-V z8t~~&yM=#?Nb7BVo!dKKdF?zd>!qU4WieZeA4ihMB@FYnF19L|(b9F5HO3$Vh5+4I z5Nt=Xxm}@MO&QP2p(3uOjq14`xgRd3ZZnqLn97xkgwjP(M^Q^fjHhwprnFXw%NmAf zEXS87Id}O`Sg3cs)sdN_R!=L^>FA7#p9`JtK^=kkgCqd)PmMQsM`0p zscZ;8Q~f==QmNazeO3sv4)a@%qL5&~Oyy+GVazVG_o;{h{9?%<& zIxl?%hyyk2hf`~8d_;q5o1=ze7%J*g*S4YuCf`x*;iQ}c(*~74Z$<{9uAvqaFVr_YMNR&!*p--1}}q zs@hcJ*Q>)6l18Ub9*}$Hf8BVXki1N#bplF-(JstwCTH7}rg zf&j`b_#}_Vu*A!)4TAxw`Jd&}r6wpeRWqyT;hBm8sUQrj-hx322L}HD zTl;##SAmrV0RDY3cWo2K%^LzoOa8mvJ8F(9no8DzCymJP%HWk^4ajfo`uO^NJ&H`} zFa#ejUV;V}Bn&kuQcJD9KiU9u^}W5YW2-A_H3zHy zm;ps}5uU&Q)jYU2s7!NdYXchET__aXF(9Zbpj_CG>&LN?U0JU|_V|u#(3|mlu#aAL z?LKay$cQCWZmdPYalpOog6Z_J`u%;R-7Ssfn?wZ>-Q?@tG+^NFXRZ(LhtAa?Ul7a_Ek!C}5Mlu~B0&ah&`1>%k*=+44SwfbO zet*lP_dBGw3E_|j+0}UG)oI>pza!d4EXY9e`6Mn&xqnCju5LcQ#9_{50)TmROk*e+ zt5M`STXguOIHHTB#aB{TQCD%n@!k{$J3w3wDU~w3$X3M6sP%mTWO%q z0}(^z*1_9jEk{SS@$%zzG))XZvMO*qcUCqwL8m)Dj1qXA10fVRqO@+NAkFOrskg!@Q&#zmE(p6el zJv7RtWYnQuL@g6~8GsB|K}BW`oT$@qKmz{&UwZLC00CAX>aR*>wss|z#-r0~#LX9x zUCT(UfVzfYTnnzYvuhf8HUj+pKAr-hkCEw-W(gI1eVs&}4H7zOVx;Ou*W((&TNhnG z64ulPtBy*(QDx?OC>(kNP|YU)rrW_bK+P|;T(@m)-bqNau9kU8l=l1lPR#}3I5IQzAl=PWwZX-VoQ9?5K*rlAt$QByt3er?3 zH*1-q)NU>bzZbW&MIEDtnsk&zgHWbAk39l2MK~zJ6KL%0sU&Y5y;{mO!(2ZDSJ(Y^ zt|Jc>RQG+IEw_pwAQ1r zvEX`g<$;Vpoq8{Oj~zpe$nGpvEOSX6JzG>@q>Nq{!^@S6VKwr!r~&gv3zPxVvs=#6*w1_~-}u-;VDq`$;*nSH-H+nDlOV!1?V zIt#iQoMn7Yu20ba0Ee+ZHK-HpxA3hsf(<-~70o&(+m5I%VsxS42cz5$AD~ zQaO~0d=_gC>m+Mo8q9bc`&qtQ17&k$?XECEIrxG900ljIBZ_$Ay0f~}Ri2~N)lE9q zN3i-9sV9>+vZec~N|*&~n5uxWPfQ#cd=!$K$5jH8ek{wt_0Uu@%%ce`#9Ll%`f~z$iA6Kg$jD2_43B9Qmd5M)0+fD7xS$ywd31)U2j$kupUFq$JX^k8{{Y1e?^)!U2&*?Y zSUpUv8KQ^x^SgEzEL@<38i-?7AZe1`&EZAdAkc!Z7EU<+T{65z-}*B*5BALi>} z8jXh*32z-1x^Pr9rZE2itDYp%F5jQ{*S)MNiEEdFSH#qG^s4G+O^e=%1aSSW z2V!7p>LQc{5S+#%(<(8&{L@ppAbv-?B8a4s1_Nj9KA-IMX`~(qYE_hDtAfN+VfQx5 z%HVQHvs6@3wAlXu(n`)EEEY97g2=YH{{VaTNq0zO4-!>;{{YL{PfUEe--znSke<&@gG^6oW3r?{ zS5>N^Z>w4LfPd8}3`k$?1YyS z6Q}h3dOHdT%NltOm+M!K7|OI-h~7qqK*s20{{U2Z(p4{D0Ve!=W&K(`!`?K*B|Hz-=U?M9H<2cSz?km4#1&`P|V5z49#z)_#*!R>w8ZiB67ckuUb$Q_0*2GH28x*_s^_x z^>uYEP=4K2!G9|ULKW|0bs&p>y?X%e=fJ*m9|9{w{;%cGw#EEQ1(4>Af^*Q^HpWIh zz!r)IdUcgsSx~X6elMrePaon>wCQrC1!xUFZ%)Wgj?f6lK;O;^sc~H&pF}aGOt{*J zl`Y905Ns?|t2VAb*W=mOEG&}l0oMeIbWgrArr#Y-bJxz-`HTFY&sSwKzaMtCYINH) zHT1b%{RJIJNb-58M!HN~rZab@$CeUF8-Kg)aq0Fhzm=nrpi-?*KTnrI_?Rp`hvP~f zszz#%sTPSAS8)8P(yGnJmD#lU29fn2>OlVhSp^FOE-6ZI>uLs(sL&1v&!n|{x`ANe zxo;xbjm)xnH7bWyC3Ns_a4%u+%xF$BdVNw5Lm4~IP_zOY$kLfnL(|xmaUs%JMzMxX zK|tgEhqi%Sp(dRuWdJsc2B()Fmr#p_6BZJHj7GLGvZHC#?im!bl8RE6xPM1&);&awHQCWku zOESk{rB0V9@;GLYdRbncK&(ggJQMWx&`?;i=kx1jRPhx(LMn7zDPYrjY|p6yZ46|2 zVtE2;updhRFa3S*LGxM<^YyAGa5{xo8DOV~NOG$wzm3ANFhp$EUIo6S6K}`A8Uiby z^?$4F>cb#;bqy6RLNR#_(ZEZ8n*=E!D7q;pkQU&cZ|yXQ+J}NDE1&X{)qfUvbpHT= zrH3P*#p9|_)l<`;{KZKLgsj4IrQF&-$s;3q>S?Kh=(y za&*}mN1|CNnkRtAEK$cDn2QD)oGz7(^ox(FQg8J>+ICSuvF5nyvK?xu+ySparsTrY zQDtx$iDXegYepL>96*!yStWTXrf{r6h$-r%h}`qxA3^QwRfyk1AT%x7N865@m9@bz zQCFpYR2j!XW?vCehsjrCDijBztf-_iD*kC?DCbSDrO;{|au2lkb|%W^;tEs-$Jlt+ z%d5dUPXtm2B!4gUe}k-4m9wPoq!~1*3dhR&H4Q1_E?C=vVQzT#z;d9~NCe~j-CV{B zh6Ggq0IHo`*Ye8G(bsnsUeC$+4llcUi*szLRn!$ZEQT6rqJ@&g4LKT9WA)T|_Gn%s z0+gi;Kc7WEX;xR@E9ff4M6?33 zC7u}+HlMeUdSzCPuHd?rioT^I@WTB-^Xa`SDMbw3p;6vS9a6CR> z^=_XFyU!!m9mm!U*fse{wl4d^@2-p7wN&*##3`!wPibQ17}9BjvrQH=H8GW=3RxTQ z7+c(}Wh}J9UlQR~SCHw` zk@FlnKfzFE zbUGE0Y~ht`qfj0;lUp-9iUBeq{PX@k-I0w}gFQBAXemnm-kj(v9TrV7t4iE1l868v zlPG;jZ`^;#9NXKml0mIF^-{$vjP*91-Wr#L$}XR{p+U1Xm6|mwT!m7=@oqn_y(!Yo zMI3sm0g{C0k48_SdyhNPeY4j6t+px+7FT2LJVsg>p>~JPwDk)0R8==NcxvgHqGhlJ zSl<53EN+_4<~LeUtq-BE1NQWpqgg>m^Wp1`AGfcd&tH6o>yG8>O{v+NMy8)5iQafh zOsza|c-iP{Srsxk>g)3jUNW^_ITZ3VTyR15b#n2IC;+L?`TD)Gs?rihtmEb9)85;_ zRdz*o+podovH1PPw6`Q#DkrU!4?R4pW0sUaj2=j)kL&&;{5_+8)$mFmhbQKKeLF&} zGBB#wt3271IShRivF-`dyCBppQ%5Bi`8-sU#KOxHf*H|;#mT?4bHcNb6wGuTY6BbEyuEt;i0AZh$Sv&|muB*^=O@`E-QgUr6uh zSlW0wDXAld2*6n-8nlz6hav9kX$!6Y05=}p(u!GY+y1Zhe7f{wR7HvI=yey)_DY^uQ5Pvu8Xm_TM?)rJPQkpkI%Am+}X!R z3P}L@`gAK(cEuY}nAa?_%_8!O!8JJGrh?++srujj1a#^?lfxYstk!2K zU7e||rKGB!kr||Fg$2?erqZjuxpq=)Z*VyNp32{Ckbxv|oj>aT09W$qdE6mTh|B_? zPxJo(E}ZClB_hJ+u-u=zj+P+}+fw-J=%)t%0IQQ3Yu@%g+(}tPw#GcL6dB?OJze1j zSgw3QL0|9@pZKqzU;oz_T6w4ruQcIZSs9~|RoMuzQd!g!q*#xq9>$DRlv;8AeGoVx zf^*QJvCR!EGFQ|}tYKo|%b%eaKsg_{zn(AtKGVv+B$`Q}r&}W=aTKpg*5CVmm7|(? z+=g;ltSmz))2~q0*Z%+kZ*(^Arl@~Dn;CfrPPm-@8z|4>mL)1!fpE^qnu^9O*8`Dj zo_?41+?M1J)I2|*PG~gvPere`>zHH=D9p$KF|r>`&A5?0Hn-3N`Szd&@QWeg*1!Ry zPs3dQ04|#LKc68(qB#&rJ2V=IE8{H6niR&`izyfS00p`BhB+QdNowR%qYnza7}Nb& z>t(NkTL!CY^*$(1Rse=UdA?^gF`3-6T%BadbaGCcH~1g$_5|M_#Jyl^RmVbG1Zb_I zgo-Kk{{TPwzRr)eH5DZ|ckv65F4p}gixN)+fO-1+EJX~YmBl(<5kUn;Px`;r{$8n@ z`OJ3pH%NS5>^{k=u7V1D1`bH^)RGlf$nI>F=yvux7});n&ybNBg{&h+KaXPWS#JsD zbtDB|gPg=QDnq8K)&5^+UoXt=QzN)@IX%ako_g%AYb9Bcud9+zG_@5~vb{|5U5}+_ znqoQP-2VWNvmY6sT%1cadMHa+{5X|7m-+tytLM^2xs`mRBQ}!h7$6`~Di0>DM@tB~ zv0qim_Wel%)24z_QyB!P^5Ook+tUw;T5V*T1`DL7rAAv6Ae0NK%G>ew*Mh>Qz!2MTkkHp|$ycQ|pY_YPaBhf29K{;nUdwEe=1`{JMcK@)nhV zj>a`m4xtaI3!;Olg2XWXgZ+)D%t0cw>qS+CTz1o_4x-LxWd+ofG5Eu>NBU$UH2_5F z{E$f}>wk5q$Sudqt1;7$!_<10)f>*>MEvRq&83BiXHUb1QT5`${{V-*D=?yvKpZ}Q z&JfKVCll5t2v0h1$6gtZ#fFJ8|Z+k4&H9c5R{k?TDmEj@bQqrTm zamQ<@jg^~FlS`LlV&7gz7aV)hB+&l=sP&`3=cY7Q6%3+T7$B5uWV^@?l2r-U$X`e{ zBIJ*3yR>v=9=PjFMJRY4mU}8QKlh6{50V6emP!gRL0=(?85*HswcFEUs^;I1@%FE6 zE@7T)gI_`U`E;7|;f{uwJtp?SO8U8_ke64J$s;H#VvuX~5X!??x%v}f{_>&+jz(`3 zw{`_TzZ`D^lD~ugj+Xk;>JD3pz6| z?fvOSCx8$2_MY4r*s`?+0qMn&wUMTn=*7>DnFoxENl!fSN{tk8vntMgU8nCT?LOpYnrU-f@JoU-dCLXw&Zp^asvrwJ^O5~5vUim=iyXOTxG zh#%@lwKnDCR@5k?s?2;o)H;d&T?2c=4zun2xzNiYjSI*biy7#Qm2@wmms*EGEJW$I zsC{qkJkUo*l|>{2RedSeqSYz@%ahPWyrupnE*Cx_sWN7;^ikGG%w3x)j9=k(zO!Uz zf44_&ZVR7gTgWm!joOdXmJ~l}r%3EjLT@0IfHAjk%wnI@t^k@U5_l`KuD#K~7b%D?LQ^!JVk0MeD?(b@Qc*r#Rh zuCm>sr;4Lw@9b4nhwzxZjYJd8<6>AWPGFQ1^s^iLQQ*X*Os2lS=IP~;WI+~ybH=~q z>dm{yrf4@W${y_NJYZ3d?p>w2>Nc}F1r<3h&9^_j`*%Vqu`jwYj)+Gkh~xUm{ILu- z+fCh~4NhZmPqcRa&V!rdeH8Y$r-*GN`j1YieJHDE!q5tc-YV_2mdS10iadHg-KUaz zx;SM)V6sP4wTM9m#BucY4~8eXv$B!U>$fFIuK`+97~|*CnWu)|$1#u5l~kvwCWPj{ z<@-7^9dWqna65A|_jNMGnZe7qx4zbrR+skkyI(y^RW&Sa8%N_xjCE7D=^>1M$2Qky z&uECqOV4jEwW+BJPI&&`K7;oVnXew+AYM74$EAF_wojB5y9=|sR~y@V1{J2+J@eO_ zN3qh9K@p1=xpElv!*6_Q=(p}s?wnlr76cgqx6|0(Fqbk(t;1VMNfEq=BZuYDRgswz z>JBr;!&eVHQ}XDV=e9;-f|SQaO;;GFuGaNPZyczFOhifK)v8F%y+*>`)81Rl1dy}> zvFiN#RS*)fSrn*F@3i?3^7Kr*<8S14hhs@yEi0`)=A)t9wCoBJ=At#Hu1Pe6k5Q?m zV`~;tcs!qRZ!p_QwQrNWh;+yUV}Kk#pF&G%R_59Si0Y{0&{w$5FLC0-Q&U9-cOP1> z6w)=m;~zaTl)GFNmI|~2#{5hE9>_PAw%l!0z=3B$Q=f~@of%lPma@s>R3lUD;ZB3< zdWgSfZ8uhv?dxFD>?!Ar*t@803?JwT{{UhKBm`laRbx@z<}uOmqb86$2>rk6`+DV& ziQP5ISz^-Hx_ItYhK)y2a13m2c`Qe@`qhHeWb~EcF}-qE{aEW2D+Oh6%mSl%ZtCg^ z+fdd>cI1DlvHqlwZU!;sbJZQ;YJn9i=hKyGi3AmpppYZAJ2Y|&F(XuOVHh^&g(XP0 zJbUzx`hn3z>A#4Pr#_u3adIt1Jz|ExiXl42#H(r3>M_}um2fy>dHR1(Z^rRRfvd-d zR!eDBixur1U*Ga=`AGB|zE6%@k0$>Bz_-*DIh~5rN(@wc#}iJYDTj=+Fpv1=H3W*P zNHW8Jt9vkpw@ApR=u+c2!*1ZO=hdO&^9-sZ|m#u*`v# zPKy=+iy!OtKHj(R*}O0cpI={}%crg+c^MQ6H<|V7;WiFLW-wG0m16ZCBTsfJ8v1!_ z0vh~Wk9xWN8G;Oh>BsHqh-5WwLZBXk{@1SEI0@jKtv}UXo0em&DlIe7LEY7}=kh}*lQeQ<;&Ck1bnz`bvZ!xSn8oRm zR=4#0@_p?PX?^xSQ7Y(B7aG)?-cUkV0e|Rk$@a{w z#4%1Z>BzK$M;u5$>ic>)5kW{PRiRaoII$y18dK<{4eS(~@C~i~J%`?<>fCG5tF=H1 z)Btr_e>rMuDR$Y18%g`kA}J^evneggkU1P&k_Q6*-w{nZK9)Q>sM0&1fZ%$iczXuB zYHm6~T*eO*s*=S)v2g6}bgj^_=KPlW`}L8f(F|!$l*+3$nPpz?hkftz6BRz#d1)(x z*GWi1s!9WiLVyvJHMFPUgB~sIP1NXhp=z3N{h#W`NUiNiWGB|WS8ZR&octMj$mgn+ zR8%{}%Pz2EdyQ%Zxw8!?{fDykjoqrP+wBf?dpYz0;JUN;nDo*{`21%T4?s|X4A>lRZ12=E1(gE zZ6T##UsQve-F+YrXOV6;O9#_M9;Y6Df9B~O&7{OE?8K-&8EATMGL$u$(1-5jSgM+o zTt{`5CkjrhGJv{1m$~*IG#5d@>@8Bim#-h$(U7{5C>Z9-^9|m5MB_3tSYn*pF42ZV%SrllAwa>;q8u zQy=R800ugJV3viMMvQ&GKA_-t9z$%@Gt{|_nb$(GZ%}4BnSm!t>9-}Vet7npStUKP z&>vo(ND)C1HDAyDU+nbbxbsnN2WpigT!GNIyn{mm-hwPOpP~BS{pmE55er0(N*|0R8oV> zt&V?}$89cuuOQpGYQ83o1Xs1#omc+6`wnlm?3Z3LWgTnLw%IPLWdv21 zJy&P<9}!Aq#^)3x-MfZ zOu)4&YmOBE01x{eCMy`ovM42ltSh9IzE zr5u|R!PWhF_oB5G$6f@3N>`{1iped=SspnGUr{ljWl)C19uS^bo^5M;Y2#+0CY^1K zRDgJo_^zP(qj;7vsillc?40O@L&u>3OP6BTHdYq6P!GB^Y-j)#AGfbmk=w(p5E413>b4SI1>6?WhZ1kMBYV3gc2*q&#l*}^Sl8en54vCNM%G3u?{4>pQ(t{4w0yf52da>(oIbhs#Gxj zy?F5BL;kAug1v+g%Q09$eM4Q;$F4zO8Hz2=mr_U4{{WtBTaU#-u0`$i^y#x&hM*() z^(@aN4&J7xR}l%5?9vdy46Nmh^R!C7tH@1?fJo!%?dqLAB8(sM{{Wk-9VU#zqyvkTzz+BA|V)OdRT0IScU->G3T zsHk3w{RMkaR#8{SB*`@mR5ekaTTg}KkRrHZO0z^PY%Tu1y^yDfhBl{f`mxcB%*RPe zSF0+?$3>C;9h;_%EiUE6<8zd#vAGWgeKJcSjzaOHi04KZ7UfUWUglxgqc;!mb*ZT- zjbcBm=l-wudT*(YV-SEsR>fIbNY|Y%Yzy36AIS$uw;tC-g~IE4510CfOb`HSHOq9-LX8l%^TuRDKhac}ROcX~V$|$5$_KJSanThmBhtvGs z7uw8hGi`mvjtnKL*?W$UIZClVjL)~TvVRV&R3Ukenx-mg3I70_hW7TRAhT>bXQ$L! zO0p01j&z0l1uIYRbdy+FrFIyQ75wo}&!x`9+VmTTJ6DgU@u`z;W$NQEDe<4Q!pl)5 zGS33Ytt^qN6x6<^UuO_l+h0k1$Ur7C!k$zE0-QYm0NLpi%iuVHv!#Ykc=-?VbnVtO zg)6f0Q%c&aF_KiTrG|>kD?w7aS5OC4GfAq#_qi7OdnUfL7T@DYp_PafIr9GiSC>lc zZo}Q&BB$ww$Ed*mU0uIoZ(50V@58^5xQP^3)9u*20=wPc?)_k=% zaZoLFVvH~K_Ib&hm(gyX42wl`<`lvihS$17b+^o_CYBPQkSkhweEKNfEVm0et?sPr;UNZq(9_c% zUoM5-m)+9M)wSC@E#&?!(;LF7m3-{;n0Dsjs1!L!hL%=XX>;@|AhMMPeP5qz_Isly z%9d=zTv|v}dX=xA`8uTVH}5-}m@SNO+#<7Q%}1A}dOSNGeW$u`v*Yr$ikXO(o*H*Z zmE)Efq$s+Osbc;&1Tf%{W6Ab1f@p7}g5E*{kTb(RzwFndxtd5CSu0w9gZ*DVg8u;K zNAcdPWAsNhHqx>Jf_p$6_nM{*Wz*b4cSh+gKI?e=!rHaN2~jQ*Jp{IPe})l zU~h7)Ofl`Zv5iWEsXR%=a(~a!k1kxL+_Txn)Tq_yN7>aJVzO%na+#jbPc-n;NSvtg z%EY#vT8hFIfCj_gj6ZmI|5*_MxhdMH=EUYtl3#U4@l}`BSHh-u~EQ;m<-TQPTEM*kNCn zN$3n_tI|u2LQ6KK(uzgP4I{^?f%N`4_Vi43@TmS@>ioJ#I;y)-MLj-!IuP?EOf4eE zB*?}PsBR>tRsAR#rEgX3^yBO8*`xZHnOM-Sap~#PL{^%H2TprRrNwH-(keSb@JP<; zier)3NPzR8GqL)S$s8Z2yIZ!E(DAPiww-QCSz_&Tll-aBnby1aJ=R;V1-!5ouR*xB zM&`tBd@IK%jm1_rvNx4=Ms$d$2odwdnN?93N%b&pr~Cfs1yo%6Qv+mFQ&@-+@Zcu-|159+hfMG){h{lsFk+D!D2T*HSNc7&^-sa!y`%z~i z=q5AU{!jS4^qO$XI?}YSM6-2mdi+LCvLg|)O2lhZc`4WiPfeLlr2^I*pRczgjhrwI zgZ>_tM!{CHp3;9lh2Dbd>aEQlV>(>NJpy=fF*sFLsBrAzixF}zEI8!xZ)NwsC92TV zi2ndr`TB4ejY_w0=x@VRO+i&rh0G`0*1*pjqqdNE1>QgKU@!T%vg?QwNtq96KkEMg zW2cf=5)E8O_&?9lRQV>D8t7(9#YwblATh8fK?-bj5UKRy{Qm$?Yph6y24g~`f3wvm zc4<Kbs+m)FEG;BG-G{O^9Xs{XU1;l(5OE zKyntJKikn|b%yN~uSZ+1x|687?ut5`loV4`qKEy&fgC!3&8vng2_O;${+`+dqZJ~* z&!)_3=Svz9!~QR$VbfGyW4X6g98@t;tg9?Y9Sm`=?y%CSq$rT-u^;RI0FSlsK+=%F z07p#Eap8d-aY|=}dbdh%kJ~(KT#a)iEQX2~j%`K9E4U`g=h=!=a)nUt0Qfsu%>F=k|3vhXayl zlu0VeH3Ajd;41(v82T1Ec(wTadq|T=vKgyTcyzzR*0dn>klkBjTZuI_*e%}bR9Qbm zuo{Ac`2Nkg_pN7BvEq7dBXm+Un*RW+&!i1S9d05sc4JNvt*ESttX#S+?4Lq;wf(h^ zKAj0bD}&U%o48gSn2tfA7=)17mro?VkTph6H@&V$9G`VJA$2KJr;k?oo*f-7vF-fN zT2|B7;VVr#NjP%;U)cqj^s9w2`ndd9d)%?ZbrR}K9=#0mK+f@!81!@T{{S#Irs1f^ zMN^ui@YArUWR01A+OWvt<|)}5w8xdCG4t0NhKfr-|GDO(&zcx z{F|TTm(R`HQk9KSh3fsgg6rHkXEMV9!BZNXnL(*nRJq)o<(OLb0P;QEBS>Tx@s1pS zK9SrLBb%mUtPk~Ls(`PMyyiH^mN1|ItcuYw{K$1MP@s+n^}qGHjvjq5k&B^LVk^`J zrtz>wm4we2W{q!X)U|rl&OQUWHOe@=fk_vHspc=h0Y zI)hTms^Uq!f!e`<)W=9Kq%zqQYHJ(u_~U?EWB7Gc{{XActqmlM(;mOhy+t(0m73-U z#)!zCrq=2hmny}H5)#)ICd6N#dP9QCk=4k~t{q@)YRm#ND{Edaq$>k*&OIwKI^R*T zx#R2Z!n8P|^`ZehdTw=%VT?R;I~59K4juGJ`eX|r4#1{@O~^L?0AJlCLqw+ldeLz>q?*azx98Irfk(h z#~e@wEgfA#Exek5ShPsY$W4?tjZ|LxNBH}!vM^R$f_{8!*S$%{Le9&{b>16sZP)F5 zKI+fx{{R%tl*jDs#TFkr^M2Rb$pvM721=D9Ps%h$$W8q|-0?J`GOn+X>%giCvrryA zST@wme=Ho$3R2YX{{WReOHx)8icFTx*xiGMh&mB7ODzR{4!5k+i=tsXBr@=8j zeq4Vu*6ppJ2*N3VQ}h1-FHF1t0IB{j7Otx4FPi%kdt-(!c`Gp8gRysyU2Yg)Xq-y~ zSmmb9W3p8==;Vmws#7MC7oToLdZkzp!|my(vyBKoC3+-T&x_S`(gxVw&C*?lm5$;u zi{34_GJ-U_!ez&AJbvGj2AH6-hGI>H&*qH}jY2L~pw^ZBnEwD(dftt3)bSo&8Xd3K z9htPc({Dk)_O5P;@O5=mP-FrocMmQSt5!mSNOPDuGB~LHZ0eCb!bJd`AbT)KU%~~Z zt0?u)?dgcn#>ZJ9PY$uv$rO~}Lm(3_n;RmqPg7{oXVtA=om^bldtYSUEQEq620zWx zSt!&1`JR#axrC@?14s%`=xEwThQiP5V!#bwQEgZJ*pF)^A)|UOqv_VPz(|Zgcc(?N zt?6lZkf)K_8ZF0(uiLd~9f=DtmM9sVg&;>VQ_2SfX}=#|X|~ywZe+IARGCVBgy;Ex zE|T6v79@}{A~y~8Q_t-CdP?m))kXs|OHWYJQr6?@-yc+uIf2LS$qclzT(fYcfgeV{ z=i8PytzhQfL#ANGYJA6$>vs`JEKyurpn8ygRQ~{Hq0?r-m_6!_^$?{!2ICY{Yi$hJ z{MnNpW{Ncd*%fNgn}z^=eVRS6me!(hO1b@>zwGpy+2oG;`CUNNf`D-O^=*A8M!VgP=?@IdtaYp4tledw%Z-**q^CNYHvVz=jJ*F^8M6NUB20Lr%5FL06LH7)iP1# zB*J8tu9TW$^Zo54;^k>rOXUv(s)h0-qSxhv4{>xCvRO$T<*+JC3L0X+>iwM!TG=j* zUX&a~bNegNr|8bY#pF9$VY_PQrK0S5JdOuBT}$Ns%#~~gItqt^C9#pR!WoDoQyzVf z`BQq1=Xcy9zBM;UD1W3lbRXU}`Tmv}?b@0Nuc!z79UOh7xiULep>#o^Xi&KT`{hXG(#+3bKyms$r=FC{@Kx~wHa(LV!!N9 zxME9|5{61>siuF~#X1`Zl>h(^AJ3)c7P~FFWUZl|ywFzC>z0l>c;ci%ELUL<)v8VO z{#)z7x4Rz}W-MNm{{UC){{RPC@DUAA@#Fp+dUUqV`(s?w<>kwN>Wg*AuHzLY^ zHuk6zrGYgk9Wbp@M;ebVuu?gp$+%KH5mcnG5J2-Rt{o%_t*w&8U;3Y~w75%+1dz@A z>He?JtCU8Kj=t<*{{Vxfg>C-;i&U#KEHzZ7q83GH-P)!xC?qi}q9VM0pY|T_Cl&{Z z>8O{(el!}h{;!u?h=0O;_nY@$$DYdR3b}myi>J30F5|({N<@-A&)z0foKvZ7VhnzM znd}9>;78NxaURyN%9?R*U}wbzP9Faf_Na& zPP(9xU4p!d{6xSD1=8a8`d{Ae=CoL|BU9(oXwI?*j2#Eedi}n=AJysls;qUg4u-AJ z)J@_~+-YPb$kNu=Yv24oAJ^FvZqX-XL7 zPmFa~d1{2w`T4gPR;Iou5UF*n2BfC zBqPpN<)pTdw<-W-WE_v`552^z;Rso12d}TE=hdc8#BcOMYfnGV?dYO+xAE-GQW^?X zs(GpyMgrtNnmjGi`_NA&=k&GhH}Z)dKNeRNkqewnR)eWZTc^hu&(3LhbkzyH!P6&UA^ zUmT1V@~YfO;DQD0ZKPP(lk~r_WS%7%soP)mW1?cqq?5q^0IU2xXQZTt4NoGF+FU5N zApjwMwjqNbr|B*JzqA)jlC=TNDbo8?Au8XOQ*O%O#0QL`+}H;V_0}8~wvqiW#~#w8 zOoD}~zFFyDIzcCgOdGb8%R(lMc*q6ck!K_TPas=Tn_G?#y_1S{t!l=V=n2hb;i9H! zY7u^=L1g;KEEFkXp+c|p;fM71Su0%w4gRk_v`%^taaky8s;^J0U#m!nTsE6sNK(b5 z1&xm&e_wQTcZ?k)Rebu@YH8=t{{Z*O_%Ru_uL@`eHfDKA33)A|$D0Bzf3458P=$Wb z2@B=^Z}n%U-%7P>+t5>&-C13=ip*{rtdx{gJAQ*DnW&t~$SGsS)5%uNbl}FKAESK_ z1L}RM5j=D{NcsN&pQN`0IRs{d^Zx)){#|MVy7z8Jd~e#L`PB(t&wr z4hi~57T3~kYmf18(nTN~b!e2Ls;~g{j(Jl>qi9q#WXB+Dl_0i|6}Yiurr*=?{WaW+ z9BYs~`gN+=p*=HH$igywY!>7_QC&h=6aWGObeQHRf&d!1xwVbjplTqI^68yIAxYHarZsXH0`F~HX$$J~+QfbjwEC2xaJ{(cWmD(>02S65 zvB{Fn(o=*2DoS|Jf=Q3b$&rJCV_PkU)B8O8urao*WOeH57!~SH<ak-+EtJ=?+3JH3?aO{{Tb{3KFEyQoMgZ@^s{J zq;nT2NGqE9{{WY(q4-n3s4M$wO|wxlM}zM^wcJ|{yIabTeg5U!Zq#9edKA*<;zM=; zm6qe5Yo0$2C})Z?(hxFEDyJ3vy07WHvc}4FG2zvZ^vJ<{p!j+68{+QZnvSD!O|?IZ z?cnVk)i~YlwIwuuN_SOffv3pgDdakBtIBWgXJxnM4qlSpGBlMK^dNsKbsX*_?K|0- zO=@@y@jhd%QBHQVY{iz@S}E zV@z`_&@2rw5KA0|)4?M@l^t0D2Uevf{?YwZUOabjL{ZS{6h4{j(b_wc9Ii<`M?)Us z?#I3db(q@hW=Z2F zSn3S&OBFEM@Vfm0*@P_|+6|n<(Uq0GdQ?ktgrCig?d&0cZS?b$A#waa4@b~WBvl0WQ~W(X zQ5iA&ra7W@NUN#RxA$Lyl?WJezOPcZ1M6}4A4bf+9O2vhHeWCEboF?8)2GVs3>%I} z{KhCMp^jR4>Cw>)1GN^?G&3Ka7%kNOKAH-@B@k zSgBSudS`?MNo7=H;f`ca_4NXH{Jy~Jjo14>K9jRYEn<)3`HXbJo-%?$pb{j2d6}B# z#7YPR6(EZf&Az`=?M^6L13#bozI`z9G-Rozc>SG6udrH5#R(x{_LF*q-H5V)gi}P3bX(*JD#>xQx zhW`MMcA2W6ifX4#ni5IFdVj0etM2~*gzb0zH%xypp1<2UdEtj`_iisU)OqhKvQs5~ zUL#w!rgHZV^x5ouN`}J1QU|r0JBBy5rOu+dfF8b`GkU5Gkr_3quU|?079JRK5z85c zQkrN8(6*F@Z6Y$F+!jAi_CCwynVQ_QDf1Nb{hdFeI*$kYKTk^hx*zA65f&3p#$<&9 zkaZ|?$T6`h1+DnMvJ$|)4-Sk_X#r0fbu%A3)JD;~W;qkm%5Foc9)qjsPajY5_S2D) z$E)R-tq%{&_VrgiyV5wk&g#$hHYRFFtLrk=(AUw|RE4N_hJO=OQ!K0rP|7U4TzeT> zUqb{I=_CZP7@+$)K`drK;d7^t=l*(i3vc47u~g|#m8YY1^sF*R8n7(dnB9;7gfaSp zP00S9_bt)ZOlw6ypGy+id^9dQfd2rB>Lq_y?d;Z8Jhy-ep>OlJaN8;Z1Cb=!AEli%h z7>+sZ>0Ei+;$IyQpdHeF?^ z^d_AEySMo6UZ`iJgv29%B1;~tgl1#Rubp~d0)YY5mp|&` z(ZcxgQ;f~jVqwi{Ddd1U$)u=Z>XP=ojDzue5`R9#TVlg9uZT{cFZM@7cWokurvaLE zc$n_B+c0$I;HNOmj_!6W=ViY1lSHYDSRMG(aD_Yw!o z{a#%-)5K*ln^{0>ixaDWNq^M#Kap@a{=Y)rB$M#;YJW~Kr{U>U4m6nQ*nuFthRT;$ z3JAC>YnyPu^T)b?rnLsWHLTVF9&});0*H>aN90C=i$OIQFcVfDAS6r*Y+`F_rwG}2v&>1))# z9;DnEXeuhnqsA9fz#)!TyDqj2Krx0y`itM%s1Zz+D^%#ZdxlU7oKyWnt5n)Q4K@XK zK9X6I3WDhq6fJc+6!63`KiB*&eXeVWt=J%h2KD~{W27u)oN6Y3=RTcKpY!(JdjsP? zTJ4^T*b`Dy_BU$MPo3RUQb-nDCtpy{9W^yH$q^DoF6YWCB7F-}(@FR|wYcAwgb2s_ zzu4;5K*uquBxyhF(!FW*ho-zG4{7epXlz+0RBJ&@b$A;g?( z$D?1ewE6U-k|glTr_f~$eRqw~et@Yc>h7gRqrhwb0AHe-Q>}cfo|AKii%3mU5OjGY z($`kft*EI|Nn8CE*Io$r;ZPSVfgYJkth%FpVGoUz=!_CL z0R_Ii0(kb=nWG_)W7E&CDz(UO6W7b8-@0`uu95|k;zs}iHNrn_QT-*Z*H5q0{4ef? zSsuP2$CvqQ<(obOnplmdRgyO#dW$rnRFZTOct1h;KZEY7NfhBuuA-#W zjvuh~9-+~6F?0r@ymC2^h>;WzRg^D^W(1ZN{0sdDy-a|GtJ9?6*`$zGfDWTbt)*ku z@wf$S#lLZh`V~v?(f$uN7y5gulC`YBulm22PXX)0rYeE~V85zDV@69L0KAUss#GYE zN)QO={p1QT4tzl1uUoGcJUWLP29Rnlima|Aay7alhG0~GVDk1nUc=sn1Ts*LJyxKt zPAk@Y8%4J>xbHdA11c?a?D(vJ4emsDUB@#*r0nshFVTGe{iBO z01iPn{QG_ua+-jr+tYHg=o*ld`#NOI<0`0{pD7X30#rioPm{@JkPSs_RK}v@{XVAm zj*wh~!2|2>PR_cxSIgznw6=ysR%+@a*QR~r zd1gw0M!JZjWtLNJOm`@t{{Ud9^sWB@t@-y!KrF6CY5e-6PO8qONd(iUuDZ@oiO9o- zmN_eMTe_XGIV>)@5?bo|T8I7_RYn=3iY%QqO930X0HYMWkFvY1xo+4p9W#PWet+>@ z9PQVl!Q4)v?OTZR{JKEzydvSZCQBbK7b}6tQM$!fjLAbaEnO_6jYeu&#jUNw@sbHuD&_>@%^1A z7k1Pd)}1c!dH&SGnrf^LV{zl&2|SdQ7}lwYspVM0iD~L1O;E_I{k67}`M%dS~`@*VJwC$R-j2C#Os8iMqZ(;WAZ~n=^lI{iBh~BQ14wIn1USvb*k>JjBlf zwM9&GO1j%fH*QGwbzx`CB0mb&cZoeK$D}tGTO�+*_l`ULa(Dv(Sf&_(SrKeo!6V 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 F{{y?tYry~j literal 0 HcmV?d00001 diff --git a/test/assets/lion.png b/test/assets/lion.png new file mode 100644 index 0000000000000000000000000000000000000000..6bab65392592f20364c33aa4990087011fa4a302 GIT binary patch literal 19826 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 literal 0 HcmV?d00001 diff --git a/test/assets/scorpion-sprite.png b/test/assets/scorpion-sprite.png new file mode 100644 index 0000000000000000000000000000000000000000..dd19e647a535b153d6a085c5355e1a479b5f14fc GIT binary patch literal 26550 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 V#VgD7Z=e_*q9CIxT_b50`hT5z_1ORb literal 0 HcmV?d00001 diff --git a/test/assets/tiger.ts b/test/assets/tiger.ts new file mode 100644 index 000000000..754f7078e --- /dev/null +++ b/test/assets/tiger.ts @@ -0,0 +1,1313 @@ +export default [ + { + fill: '#ffffff', + stroke: '#000000', + strokeWidth: 0.172, + data: + 'M-122.304 84.285C-122.304 84.285 -122.203 86.179 -123.027 86.16C-123.851 86.141 -140.305 38.066 -160.833 40.309C-160.833 40.309 -143.05 32.956 -122.304 84.285z', + }, + { + fill: '#ffffff', + stroke: '#000000', + strokeWidth: 0.172, + data: + 'M-118.774 81.262C-118.774 81.262 -119.323 83.078 -120.092 82.779C-120.86 82.481 -119.977 31.675 -140.043 26.801C-140.043 26.801 -120.82 25.937 -118.774 81.262z', + }, + { + fill: '#ffffff', + stroke: '#000000', + strokeWidth: 0.172, + data: + 'M-91.284 123.59C-91.284 123.59 -89.648 124.55 -90.118 125.227C-90.589 125.904 -139.763 113.102 -149.218 131.459C-149.218 131.459 -145.539 112.572 -91.284 123.59z', + }, + { + fill: '#ffffff', + stroke: '#000000', + strokeWidth: 0.172, + data: + 'M-94.093 133.801C-94.093 133.801 -92.237 134.197 -92.471 134.988C-92.704 135.779 -143.407 139.121 -146.597 159.522C-146.597 159.522 -149.055 140.437 -94.093 133.801z', + }, + { + fill: '#ffffff', + stroke: '#000000', + strokeWidth: 0.172, + data: + 'M-98.304 128.276C-98.304 128.276 -96.526 128.939 -96.872 129.687C-97.218 130.435 -147.866 126.346 -153.998 146.064C-153.998 146.064 -153.646 126.825 -98.304 128.276z', + }, + { + fill: '#ffffff', + stroke: '#000000', + strokeWidth: 0.172, + data: + 'M-109.009 110.072C-109.009 110.072 -107.701 111.446 -108.34 111.967C-108.979 112.488 -152.722 86.634 -166.869 101.676C-166.869 101.676 -158.128 84.533 -109.009 110.072z', + }, + { + fill: '#ffffff', + stroke: '#000000', + strokeWidth: 0.172, + data: + 'M-116.554 114.263C-116.554 114.263 -115.098 115.48 -115.674 116.071C-116.25 116.661 -162.638 95.922 -174.992 112.469C-174.992 112.469 -168.247 94.447 -116.554 114.263z', + }, + { + fill: '#ffffff', + stroke: '#000000', + strokeWidth: 0.172, + data: + 'M-119.154 118.335C-119.154 118.335 -117.546 119.343 -118.036 120.006C-118.526 120.669 -167.308 106.446 -177.291 124.522C-177.291 124.522 -173.066 105.749 -119.154 118.335z', + }, + { + fill: '#ffffff', + stroke: '#000000', + strokeWidth: 0.172, + data: + 'M-108.42 118.949C-108.42 118.949 -107.298 120.48 -107.999 120.915C-108.7 121.35 -148.769 90.102 -164.727 103.207C-164.727 103.207 -153.862 87.326 -108.42 118.949z', + }, + { + fill: '#ffffff', + stroke: '#000000', + strokeWidth: 0.172, + data: + 'M-128.2 90C-128.2 90 -127.6 91.8 -128.4 92C-129.2 92.2 -157.8 50.2 -177.001 57.8C-177.001 57.8 -161.8 46 -128.2 90z', + }, + { + fill: '#ffffff', + stroke: '#000000', + strokeWidth: 0.172, + data: + 'M-127.505 96.979C-127.505 96.979 -126.53 98.608 -127.269 98.975C-128.007 99.343 -164.992 64.499 -182.101 76.061C-182.101 76.061 -169.804 61.261 -127.505 96.979z', + }, + { + fill: '#ffffff', + stroke: '#000000', + strokeWidth: 0.172, + data: + 'M-127.62 101.349C-127.62 101.349 -126.498 102.88 -127.199 103.315C-127.9 103.749 -167.969 72.502 -183.927 85.607C-183.927 85.607 -173.062 69.726 -127.62 101.349z', + }, + { + fill: '#ffffff', + stroke: '#000000', + data: + 'M-129.83 103.065C-129.327 109.113 -128.339 115.682 -126.6 118.801C-126.6 118.801 -130.2 131.201 -121.4 144.401C-121.4 144.401 -121.8 151.601 -120.2 154.801C-120.2 154.801 -116.2 163.201 -111.4 164.001C-107.516 164.648 -98.793 167.717 -88.932 169.121C-88.932 169.121 -71.8 183.201 -75 196.001C-75 196.001 -75.4 212.401 -79 214.001C-79 214.001 -67.4 202.801 -77 219.601L-81.4 238.401C-81.4 238.401 -55.8 216.801 -71.4 235.201L-81.4 261.201C-81.4 261.201 -61.8 242.801 -69 251.201L-72.2 260.001C-72.2 260.001 -29 232.801 -59.8 262.401C-59.8 262.401 -51.8 258.801 -47.4 261.601C-47.4 261.601 -40.6 260.401 -41.4 262.001C-41.4 262.001 -62.2 272.401 -65.8 290.801C-65.8 290.801 -57.4 280.801 -60.6 291.601L-60.2 303.201C-60.2 303.201 -56.2 281.601 -56.6 319.201C-56.6 319.201 -37.4 301.201 -49 322.001L-49 338.801C-49 338.801 -33.8 322.401 -40.2 335.201C-40.2 335.201 -30.2 326.401 -34.2 341.601C-34.2 341.601 -35 352.001 -30.6 340.801C-30.6 340.801 -14.6 310.201 -20.6 336.401C-20.6 336.401 -21.4 355.601 -16.6 340.801C-16.6 340.801 -16.2 351.201 -7 358.401C-7 358.401 -8.2 307.601 4.6 343.601L8.6 360.001C8.6 360.001 11.4 350.801 11 345.601C11 345.601 25.8 329.201 19 353.601C19 353.601 34.2 330.801 31 344.001C31 344.001 23.4 360.001 25 364.801C25 364.801 41.8 330.001 43 328.401C43 328.401 41 370.802 51.8 334.801C51.8 334.801 57.4 346.801 54.6 351.201C54.6 351.201 62.6 343.201 61.8 340.001C61.8 340.001 66.4 331.801 69.2 345.401C69.2 345.401 71 354.801 72.6 351.601C72.6 351.601 76.6 375.602 77.8 352.801C77.8 352.801 79.4 339.201 72.2 327.601C72.2 327.601 73 324.401 70.2 320.401C70.2 320.401 83.8 342.001 76.6 313.201C76.6 313.201 87.801 321.201 89.001 321.201C89.001 321.201 75.4 298.001 84.2 302.801C84.2 302.801 79 292.401 97.001 304.401C97.001 304.401 81 288.401 98.601 298.001C98.601 298.001 106.601 304.401 99.001 294.401C99.001 294.401 84.6 278.401 106.601 296.401C106.601 296.401 118.201 312.801 119.001 315.601C119.001 315.601 109.001 286.401 104.601 283.601C104.601 283.601 113.001 247.201 154.201 262.801C154.201 262.801 161.001 280.001 165.401 261.601C165.401 261.601 178.201 255.201 189.401 282.801C189.401 282.801 193.401 269.201 192.601 266.401C192.601 266.401 199.401 267.601 198.601 266.401C198.601 266.401 211.801 270.801 213.001 270.001C213.001 270.001 219.801 276.801 220.201 273.201C220.201 273.201 229.401 276.001 227.401 272.401C227.401 272.401 236.201 288.001 236.601 291.601L239.001 277.601L241.001 280.401C241.001 280.401 242.601 272.801 241.801 271.601C241.001 270.401 261.801 278.401 266.601 299.201L268.601 307.601C268.601 307.601 274.601 292.801 273.001 288.801C273.001 288.801 278.201 289.601 278.601 294.001C278.601 294.001 282.601 270.801 277.801 264.801C277.801 264.801 282.201 264.001 283.401 267.601L283.401 260.401C283.401 260.401 290.601 261.201 290.601 258.801C290.601 258.801 295.001 254.801 297.001 259.601C297.001 259.601 284.601 224.401 303.001 243.601C303.001 243.601 310.201 254.401 306.601 235.601C303.001 216.801 299.001 215.201 303.801 214.801C303.801 214.801 304.601 211.201 302.601 209.601C300.601 208.001 303.801 209.601 303.801 209.601C303.801 209.601 308.601 213.601 303.401 191.601C303.401 191.601 309.801 193.201 297.801 164.001C297.801 164.001 300.601 161.601 296.601 153.201C296.601 153.201 304.601 157.601 307.401 156.001C307.401 156.001 307.001 154.401 303.801 150.401C303.801 150.401 282.201 95.6 302.601 117.601C302.601 117.601 314.451 131.151 308.051 108.351C308.051 108.351 298.94 84.341 299.717 80.045L-129.83 103.065z', + }, + { + fill: '#cc7226', + stroke: '#000000', + data: + 'M299.717 80.245C300.345 80.426 302.551 81.55 303.801 83.2C303.801 83.2 310.601 94 305.401 75.6C305.401 75.6 296.201 46.8 305.001 58C305.001 58 311.001 65.2 307.801 51.6C303.936 35.173 301.401 28.8 301.401 28.8C301.401 28.8 313.001 33.6 286.201 -6L295.001 -2.4C295.001 -2.4 275.401 -42 253.801 -47.2L245.801 -53.2C245.801 -53.2 284.201 -91.2 271.401 -128C271.401 -128 264.601 -133.2 255.001 -124C255.001 -124 248.601 -119.2 242.601 -120.8C242.601 -120.8 211.801 -119.6 209.801 -119.6C207.801 -119.6 173.001 -156.8 107.401 -139.2C107.401 -139.2 102.201 -137.2 97.801 -138.4C97.801 -138.4 79.4 -154.4 30.6 -131.6C30.6 -131.6 20.6 -129.6 19 -129.6C17.4 -129.6 14.6 -129.6 6.6 -123.2C-1.4 -116.8 -1.8 -116 -3.8 -114.4C-3.8 -114.4 -20.2 -103.2 -25 -102.4C-25 -102.4 -36.6 -96 -41 -86L-44.6 -84.8C-44.6 -84.8 -46.2 -77.6 -46.6 -76.4C-46.6 -76.4 -51.4 -72.8 -52.2 -67.2C-52.2 -67.2 -61 -61.2 -60.6 -56.8C-60.6 -56.8 -62.2 -51.6 -63 -46.8C-63 -46.8 -70.2 -42 -69.4 -39.2C-69.4 -39.2 -77 -25.2 -75.8 -18.4C-75.8 -18.4 -82.2 -18.8 -85 -16.4C-85 -16.4 -85.8 -11.6 -87.4 -11.2C-87.4 -11.2 -90.2 -10 -87.8 -6C-87.8 -6 -89.4 -3.2 -89.8 -1.6C-89.8 -1.6 -89 1.2 -93.4 6.8C-93.4 6.8 -99.8 25.6 -97.8 30.8C-97.8 30.8 -97.4 35.6 -100.2 37.2C-100.2 37.2 -103.8 36.8 -95.4 48.8C-95.4 48.8 -94.6 50 -97.8 52.4C-97.8 52.4 -115 56 -117.4 72.4C-117.4 72.4 -131 87.2 -131 92.4C-131 94.705 -130.729 97.852 -130.03 102.465C-130.03 102.465 -130.6 110.801 -103 111.601C-75.4 112.401 299.717 80.245 299.717 80.245z', + }, + { + fill: '#cc7226', + data: + 'M-115.6 102.6C-140.6 63.2 -126.2 119.601 -126.2 119.601C-117.4 154.001 12.2 116.401 12.2 116.401C12.2 116.401 181.001 86 192.201 82C203.401 78 298.601 84.4 298.601 84.4L293.001 67.6C228.201 21.2 209.001 44.4 195.401 40.4C181.801 36.4 184.201 46 181.001 46.8C177.801 47.6 138.601 22.8 132.201 23.6C125.801 24.4 100.459 0.649 115.401 32.4C131.401 66.4 57 71.6 40.2 60.4C23.4 49.2 47.4 78.8 47.4 78.8C65.8 98.8 31.4 82 31.4 82C-3 69.2 -27 94.8 -30.2 95.6C-33.4 96.4 -38.2 99.6 -39 93.2C-39.8 86.8 -47.31 70.099 -79 96.4C-99 113.001 -112.8 91 -112.8 91L-115.6 102.6z', + }, + { + fill: '#e87f3a', + data: + 'M133.51 25.346C127.11 26.146 101.743 2.407 116.71 34.146C133.31 69.346 58.31 73.346 41.51 62.146C24.709 50.946 48.71 80.546 48.71 80.546C67.11 100.546 32.709 83.746 32.709 83.746C-1.691 70.946 -25.691 96.546 -28.891 97.346C-32.091 98.146 -36.891 101.346 -37.691 94.946C-38.491 88.546 -45.87 72.012 -77.691 98.146C-98.927 115.492 -112.418 94.037 -112.418 94.037L-115.618 104.146C-140.618 64.346 -125.546 122.655 -125.546 122.655C-116.745 157.056 13.509 118.146 13.509 118.146C13.509 118.146 182.31 87.746 193.51 83.746C204.71 79.746 299.038 86.073 299.038 86.073L293.51 68.764C228.71 22.364 210.31 46.146 196.71 42.146C183.11 38.146 185.51 47.746 182.31 48.546C179.11 49.346 139.91 24.546 133.51 25.346z', + }, + { + fill: '#ea8c4d', + data: + 'M134.819 27.091C128.419 27.891 103.685 3.862 118.019 35.891C134.219 72.092 59.619 75.092 42.819 63.892C26.019 52.692 50.019 82.292 50.019 82.292C68.419 102.292 34.019 85.492 34.019 85.492C-0.381 72.692 -24.382 98.292 -27.582 99.092C-30.782 99.892 -35.582 103.092 -36.382 96.692C-37.182 90.292 -44.43 73.925 -76.382 99.892C-98.855 117.983 -112.036 97.074 -112.036 97.074L-115.636 105.692C-139.436 66.692 -124.891 125.71 -124.891 125.71C-116.091 160.11 14.819 119.892 14.819 119.892C14.819 119.892 183.619 89.492 194.819 85.492C206.019 81.492 299.474 87.746 299.474 87.746L294.02 69.928C229.219 23.528 211.619 47.891 198.019 43.891C184.419 39.891 186.819 49.491 183.619 50.292C180.419 51.092 141.219 26.291 134.819 27.091z', + }, + { + fill: '#ec9961', + data: + 'M136.128 28.837C129.728 29.637 104.999 5.605 119.328 37.637C136.128 75.193 60.394 76.482 44.128 65.637C27.328 54.437 51.328 84.037 51.328 84.037C69.728 104.037 35.328 87.237 35.328 87.237C0.928 74.437 -23.072 100.037 -26.272 100.837C-29.472 101.637 -34.272 104.837 -35.072 98.437C-35.872 92.037 -42.989 75.839 -75.073 101.637C-98.782 120.474 -111.655 100.11 -111.655 100.11L-115.655 107.237C-137.455 70.437 -124.236 128.765 -124.236 128.765C-115.436 163.165 16.128 121.637 16.128 121.637C16.128 121.637 184.928 91.237 196.129 87.237C207.329 83.237 299.911 89.419 299.911 89.419L294.529 71.092C229.729 24.691 212.929 49.637 199.329 45.637C185.728 41.637 188.128 51.237 184.928 52.037C181.728 52.837 142.528 28.037 136.128 28.837z', + }, + { + fill: '#eea575', + data: + 'M137.438 30.583C131.037 31.383 106.814 7.129 120.637 39.383C137.438 78.583 62.237 78.583 45.437 67.383C28.637 56.183 52.637 85.783 52.637 85.783C71.037 105.783 36.637 88.983 36.637 88.983C2.237 76.183 -21.763 101.783 -24.963 102.583C-28.163 103.383 -32.963 106.583 -33.763 100.183C-34.563 93.783 -41.548 77.752 -73.763 103.383C-98.709 122.965 -111.273 103.146 -111.273 103.146L-115.673 108.783C-135.473 73.982 -123.582 131.819 -123.582 131.819C-114.782 166.22 17.437 123.383 17.437 123.383C17.437 123.383 186.238 92.983 197.438 88.983C208.638 84.983 300.347 91.092 300.347 91.092L295.038 72.255C230.238 25.855 214.238 51.383 200.638 47.383C187.038 43.383 189.438 52.983 186.238 53.783C183.038 54.583 143.838 29.783 137.438 30.583z', + }, + { + fill: '#f1b288', + data: + 'M138.747 32.328C132.347 33.128 106.383 9.677 121.947 41.128C141.147 79.928 63.546 80.328 46.746 69.128C29.946 57.928 53.946 87.528 53.946 87.528C72.346 107.528 37.946 90.728 37.946 90.728C3.546 77.928 -20.454 103.528 -23.654 104.328C-26.854 105.128 -31.654 108.328 -32.454 101.928C-33.254 95.528 -40.108 79.665 -72.454 105.128C-98.636 125.456 -110.891 106.183 -110.891 106.183L-115.691 110.328C-133.691 77.128 -122.927 134.874 -122.927 134.874C-114.127 169.274 18.746 125.128 18.746 125.128C18.746 125.128 187.547 94.728 198.747 90.728C209.947 86.728 300.783 92.764 300.783 92.764L295.547 73.419C230.747 27.019 215.547 53.128 201.947 49.128C188.347 45.128 190.747 54.728 187.547 55.528C184.347 56.328 145.147 31.528 138.747 32.328z', + }, + { + fill: '#f3bf9c', + data: + 'M140.056 34.073C133.655 34.873 107.313 11.613 123.255 42.873C143.656 82.874 64.855 82.074 48.055 70.874C31.255 59.674 55.255 89.274 55.255 89.274C73.655 109.274 39.255 92.474 39.255 92.474C4.855 79.674 -19.145 105.274 -22.345 106.074C-25.545 106.874 -30.345 110.074 -31.145 103.674C-31.945 97.274 -38.668 81.578 -71.145 106.874C-98.564 127.947 -110.509 109.219 -110.509 109.219L-115.709 111.874C-131.709 81.674 -122.273 137.929 -122.273 137.929C-113.473 172.329 20.055 126.874 20.055 126.874C20.055 126.874 188.856 96.474 200.056 92.474C211.256 88.474 301.22 94.437 301.22 94.437L296.056 74.583C231.256 28.183 216.856 54.874 203.256 50.874C189.656 46.873 192.056 56.474 188.856 57.274C185.656 58.074 146.456 33.273 140.056 34.073z', + }, + { + fill: '#f5ccb0', + data: + 'M141.365 35.819C134.965 36.619 107.523 13.944 124.565 44.619C146.565 84.219 66.164 83.819 49.364 72.619C32.564 61.419 56.564 91.019 56.564 91.019C74.964 111.019 40.564 94.219 40.564 94.219C6.164 81.419 -17.836 107.019 -21.036 107.819C-24.236 108.619 -29.036 111.819 -29.836 105.419C-30.636 99.019 -37.227 83.492 -69.836 108.619C-98.491 130.438 -110.127 112.256 -110.127 112.256L-115.727 113.419C-130.128 85.019 -121.618 140.983 -121.618 140.983C-112.818 175.384 21.364 128.619 21.364 128.619C21.364 128.619 190.165 98.219 201.365 94.219C212.565 90.219 301.656 96.11 301.656 96.11L296.565 75.746C231.765 29.346 218.165 56.619 204.565 52.619C190.965 48.619 193.365 58.219 190.165 59.019C186.965 59.819 147.765 35.019 141.365 35.819z', + }, + { + fill: '#f8d8c4', + data: + 'M142.674 37.565C136.274 38.365 108.832 15.689 125.874 46.365C147.874 85.965 67.474 85.565 50.674 74.365C33.874 63.165 57.874 92.765 57.874 92.765C76.274 112.765 41.874 95.965 41.874 95.965C7.473 83.165 -16.527 108.765 -19.727 109.565C-22.927 110.365 -27.727 113.565 -28.527 107.165C-29.327 100.765 -35.786 85.405 -68.527 110.365C-98.418 132.929 -109.745 115.293 -109.745 115.293L-115.745 114.965C-129.346 88.564 -120.963 144.038 -120.963 144.038C-112.163 178.438 22.673 130.365 22.673 130.365C22.673 130.365 191.474 99.965 202.674 95.965C213.874 91.965 302.093 97.783 302.093 97.783L297.075 76.91C232.274 30.51 219.474 58.365 205.874 54.365C192.274 50.365 194.674 59.965 191.474 60.765C188.274 61.565 149.074 36.765 142.674 37.565z', + }, + { + fill: '#fae5d7', + data: + 'M143.983 39.31C137.583 40.11 110.529 17.223 127.183 48.11C149.183 88.91 68.783 87.31 51.983 76.11C35.183 64.91 59.183 94.51 59.183 94.51C77.583 114.51 43.183 97.71 43.183 97.71C8.783 84.91 -15.217 110.51 -18.417 111.31C-21.618 112.11 -26.418 115.31 -27.218 108.91C-28.018 102.51 -34.346 87.318 -67.218 112.11C-98.345 135.42 -109.363 118.329 -109.363 118.329L-115.764 116.51C-128.764 92.51 -120.309 147.093 -120.309 147.093C-111.509 181.493 23.983 132.11 23.983 132.11C23.983 132.11 192.783 101.71 203.983 97.71C215.183 93.71 302.529 99.456 302.529 99.456L297.583 78.074C232.783 31.673 220.783 60.11 207.183 56.11C193.583 52.11 195.983 61.71 192.783 62.51C189.583 63.31 150.383 38.51 143.983 39.31z', + }, + { + fill: '#fcf2eb', + data: + 'M145.292 41.055C138.892 41.855 112.917 18.411 128.492 49.855C149.692 92.656 70.092 89.056 53.292 77.856C36.492 66.656 60.492 96.256 60.492 96.256C78.892 116.256 44.492 99.456 44.492 99.456C10.092 86.656 -13.908 112.256 -17.108 113.056C-20.308 113.856 -25.108 117.056 -25.908 110.656C-26.708 104.256 -32.905 89.232 -65.908 113.856C-98.273 137.911 -108.982 121.365 -108.982 121.365L-115.782 118.056C-128.582 94.856 -119.654 150.147 -119.654 150.147C-110.854 184.547 25.292 133.856 25.292 133.856C25.292 133.856 194.093 103.456 205.293 99.456C216.493 95.456 302.965 101.128 302.965 101.128L298.093 79.237C233.292 32.837 222.093 61.856 208.493 57.856C194.893 53.855 197.293 63.456 194.093 64.256C190.892 65.056 151.692 40.255 145.292 41.055z', + }, + { + fill: '#ffffff', + data: + 'M-115.8 119.601C-128.6 97.6 -119 153.201 -119 153.201C-110.2 187.601 26.6 135.601 26.6 135.601C26.6 135.601 195.401 105.2 206.601 101.2C217.801 97.2 303.401 102.8 303.401 102.8L298.601 80.4C233.801 34 223.401 63.6 209.801 59.6C196.201 55.6 198.601 65.2 195.401 66C192.201 66.8 153.001 42 146.601 42.8C140.201 43.6 114.981 19.793 129.801 51.6C152.028 99.307 69.041 89.227 54.6 79.6C37.8 68.4 61.8 98 61.8 98C80.2 118.001 45.8 101.2 45.8 101.2C11.4 88.4 -12.6 114.001 -15.8 114.801C-19 115.601 -23.8 118.801 -24.6 112.401C-25.4 106 -31.465 91.144 -64.6 115.601C-98.2 140.401 -108.6 124.401 -108.6 124.401L-115.8 119.601z', + }, + { + fill: '#000000', + data: + 'M-74.2 149.601C-74.2 149.601 -81.4 161.201 -60.6 174.401C-60.6 174.401 -59.2 175.801 -77.2 171.601C-77.2 171.601 -83.4 169.601 -85 159.201C-85 159.201 -89.8 154.801 -94.6 149.201C-99.4 143.601 -74.2 149.601 -74.2 149.601z', + }, + { + fill: '#cccccc', + data: + 'M65.8 102C65.8 102 83.498 128.821 82.9 133.601C81.6 144.001 81.4 153.601 84.6 157.601C87.801 161.601 96.601 194.801 96.601 194.801C96.601 194.801 96.201 196.001 108.601 158.001C108.601 158.001 120.201 142.001 100.201 123.601C100.201 123.601 65 94.8 65.8 102z', + }, + { + fill: '#000000', + data: + 'M-54.2 176.401C-54.2 176.401 -43 183.601 -57.4 214.801L-51 212.401C-51 212.401 -51.8 223.601 -55 226.001L-47.8 222.801C-47.8 222.801 -43 230.801 -47 235.601C-47 235.601 -30.2 243.601 -31 250.001C-31 250.001 -24.6 242.001 -28.6 235.601C-32.6 229.201 -39.8 233.201 -39 214.801L-47.8 218.001C-47.8 218.001 -42.2 209.201 -42.2 202.801L-50.2 205.201C-50.2 205.201 -34.731 178.623 -45.4 177.201C-51.4 176.401 -54.2 176.401 -54.2 176.401z', + }, + { + fill: '#cccccc', + data: + 'M-21.8 193.201C-21.8 193.201 -19 188.801 -21.8 189.601C-24.6 190.401 -55.8 205.201 -61.8 214.801C-61.8 214.801 -27.4 190.401 -21.8 193.201z', + }, + { + fill: '#cccccc', + data: + 'M-11.4 201.201C-11.4 201.201 -8.6 196.801 -11.4 197.601C-14.2 198.401 -45.4 213.201 -51.4 222.801C-51.4 222.801 -17 198.401 -11.4 201.201z', + }, + { + fill: '#cccccc', + data: + 'M1.8 186.001C1.8 186.001 4.6 181.601 1.8 182.401C-1 183.201 -32.2 198.001 -38.2 207.601C-38.2 207.601 -3.8 183.201 1.8 186.001z', + }, + { + fill: '#cccccc', + data: + 'M-21.4 229.601C-21.4 229.601 -21.4 223.601 -24.2 224.401C-27 225.201 -63 242.801 -69 252.401C-69 252.401 -27 226.801 -21.4 229.601z', + }, + { + fill: '#cccccc', + data: + 'M-20.2 218.801C-20.2 218.801 -19 214.001 -21.8 214.801C-23.8 214.801 -50.2 226.401 -56.2 236.001C-56.2 236.001 -26.6 214.401 -20.2 218.801z', + }, + { + fill: '#cccccc', + data: + 'M-34.6 266.401L-44.6 274.001C-44.6 274.001 -34.2 266.401 -30.6 267.601C-30.6 267.601 -37.4 278.801 -38.2 284.001C-38.2 284.001 -27.8 271.201 -22.2 271.601C-22.2 271.601 -14.6 272.001 -14.6 282.801C-14.6 282.801 -9 272.401 -5.8 272.801C-5.8 272.801 -4.6 279.201 -5.8 286.001C-5.8 286.001 -1.8 278.401 2.2 280.001C2.2 280.001 8.6 278.001 7.8 289.601C7.8 289.601 7.8 300.001 7 302.801C7 302.801 12.6 276.401 15 276.001C15 276.001 23 274.801 27.8 283.601C27.8 283.601 23.8 276.001 28.6 278.001C28.6 278.001 39.4 279.601 42.6 286.401C42.6 286.401 35.8 274.401 41.4 277.601C41.4 277.601 48.2 277.601 49.4 284.001C49.4 284.001 57.8 305.201 59.8 306.801C59.8 306.801 52.2 285.201 53.8 285.201C53.8 285.201 51.8 273.201 57 288.001C57 288.001 53.8 274.001 59.4 274.801C65 275.601 69.4 285.601 77.8 283.201C77.8 283.201 87.401 288.801 89.401 219.601L-34.6 266.401z', + }, + { + fill: '#000000', + data: + 'M-29.8 173.601C-29.8 173.601 -15 167.601 25 173.601C25 173.601 32.2 174.001 39 165.201C45.8 156.401 72.6 149.201 79 151.201L88.601 157.601L89.401 158.801C89.401 158.801 101.801 169.201 102.201 176.801C102.601 184.401 87.801 232.401 78.2 248.401C68.6 264.401 59 276.801 39.8 274.401C39.8 274.401 19 270.401 -6.6 274.401C-6.6 274.401 -35.8 272.801 -38.6 264.801C-41.4 256.801 -27.4 241.601 -27.4 241.601C-27.4 241.601 -23 233.201 -24.2 218.801C-25.4 204.401 -25 176.401 -29.8 173.601z', + }, + { + fill: '#e5668c', + data: + 'M-7.8 175.601C0.6 194.001 -29 259.201 -29 259.201C-31 260.801 -16.34 266.846 -6.2 264.401C4.746 261.763 45 266.001 45 266.001C68.6 250.401 81.4 206.001 81.4 206.001C81.4 206.001 91.801 182.001 74.2 178.801C56.6 175.601 -7.8 175.601 -7.8 175.601z', + }, + { + fill: '#b23259', + data: + 'M-9.831 206.497C-6.505 193.707 -4.921 181.906 -7.8 175.601C-7.8 175.601 54.6 182.001 65.8 161.201C70.041 153.326 84.801 184.001 84.4 193.601C84.4 193.601 21.4 208.001 6.6 196.801L-9.831 206.497z', + }, + { + fill: '#a5264c', + data: + 'M-5.4 222.801C-5.4 222.801 -3.4 230.001 -5.8 234.001C-5.8 234.001 -7.4 234.801 -8.6 235.201C-8.6 235.201 -7.4 238.801 -1.4 240.401C-1.4 240.401 0.6 244.801 3 245.201C5.4 245.601 10.2 251.201 14.2 250.001C18.2 248.801 29.4 244.801 29.4 244.801C29.4 244.801 35 241.601 43.8 245.201C43.8 245.201 46.175 244.399 46.6 240.401C47.1 235.701 50.2 232.001 52.2 230.001C54.2 228.001 63.8 215.201 62.6 214.801C61.4 214.401 -5.4 222.801 -5.4 222.801z', + }, + { + fill: '#ff727f', + stroke: '#000000', + data: + 'M-9.8 174.401C-9.8 174.401 -12.6 196.801 -9.4 205.201C-6.2 213.601 -7 215.601 -7.8 219.601C-8.6 223.601 -4.2 233.601 1.4 239.601L13.4 241.201C13.4 241.201 28.6 237.601 37.8 240.401C37.8 240.401 46.794 241.744 50.2 226.801C50.2 226.801 55 220.401 62.2 217.601C69.4 214.801 76.6 173.201 72.6 165.201C68.6 157.201 54.2 152.801 38.2 168.401C22.2 184.001 20.2 167.201 -9.8 174.401z', + }, + { + fill: '#ffffcc', + stroke: '#000000', + strokeWidth: 0.5, + data: + 'M-8.2 249.201C-8.2 249.201 -9 247.201 -13.4 246.801C-13.4 246.801 -35.8 243.201 -44.2 230.801C-44.2 230.801 -51 225.201 -46.6 236.801C-46.6 236.801 -36.2 257.201 -29.4 260.001C-29.4 260.001 -13 264.001 -8.2 249.201z', + }, + { + fill: '#cc3f4c', + data: + 'M71.742 185.229C72.401 177.323 74.354 168.709 72.6 165.201C66.154 152.307 49.181 157.695 38.2 168.401C22.2 184.001 20.2 167.201 -9.8 174.401C-9.8 174.401 -11.545 188.364 -10.705 198.376C-10.705 198.376 26.6 186.801 27.4 192.401C27.4 192.401 29 189.201 38.2 189.201C47.4 189.201 70.142 188.029 71.742 185.229z', + }, + { + stroke: '#a51926', + strokeWidth: 2, + data: + 'M28.6 175.201C28.6 175.201 33.4 180.001 29.8 189.601C29.8 189.601 15.4 205.601 17.4 219.601', + }, + { + fill: '#ffffcc', + stroke: '#000000', + strokeWidth: 0.5, + data: + 'M-19.4 260.001C-19.4 260.001 -23.8 247.201 -15 254.001C-15 254.001 -10.2 256.001 -11.4 257.601C-12.6 259.201 -18.2 263.201 -19.4 260.001z', + }, + { + fill: '#ffffcc', + stroke: '#000000', + strokeWidth: 0.5, + data: + 'M-14.36 261.201C-14.36 261.201 -17.88 250.961 -10.84 256.401C-10.84 256.401 -6.419 258.849 -7.96 259.281C-12.52 260.561 -7.96 263.121 -14.36 261.201z', + }, + { + fill: '#ffffcc', + stroke: '#000000', + strokeWidth: 0.5, + data: + 'M-9.56 261.201C-9.56 261.201 -13.08 250.961 -6.04 256.401C-6.04 256.401 -1.665 258.711 -3.16 259.281C-6.52 260.561 -3.16 263.121 -9.56 261.201z', + }, + { + fill: '#ffffcc', + stroke: '#000000', + strokeWidth: 0.5, + data: + 'M-2.96 261.401C-2.96 261.401 -6.48 251.161 0.56 256.601C0.56 256.601 4.943 258.933 3.441 259.481C0.48 260.561 3.441 263.321 -2.96 261.401z', + }, + { + fill: '#ffffcc', + stroke: '#000000', + strokeWidth: 0.5, + data: + 'M3.52 261.321C3.52 261.321 0 251.081 7.041 256.521C7.041 256.521 10.881 258.121 9.921 259.401C8.961 260.681 9.921 263.241 3.52 261.321z', + }, + { + fill: '#ffffcc', + stroke: '#000000', + strokeWidth: 0.5, + data: + 'M10.2 262.001C10.2 262.001 5.4 249.601 14.6 256.001C14.6 256.001 19.4 258.001 18.2 259.601C17 261.201 18.2 264.401 10.2 262.001z', + }, + { + stroke: '#a5264c', + strokeWidth: 2, + data: + 'M-18.2 244.801C-18.2 244.801 -5 242.001 1 245.201C1 245.201 7 246.401 8.2 246.001C9.4 245.601 12.6 245.201 12.6 245.201', + }, + { + stroke: '#a5264c', + strokeWidth: 2, + data: + 'M15.8 253.601C15.8 253.601 27.8 240.001 39.8 244.401C46.816 246.974 45.8 243.601 46.6 240.801C47.4 238.001 47.6 233.801 52.6 230.801', + }, + { + fill: '#ffffcc', + stroke: '#000000', + strokeWidth: 0.5, + data: + 'M33 237.601C33 237.601 29 226.801 26.2 239.601C23.4 252.401 20.2 256.001 18.6 258.801C18.6 258.801 18.6 264.001 27 263.601C27 263.601 37.8 263.201 38.2 260.401C38.6 257.601 37 246.001 33 237.601z', + }, + { + stroke: '#a5264c', + strokeWidth: 2, + data: 'M47 244.801C47 244.801 50.6 242.401 53 243.601', + }, + { + stroke: '#a5264c', + strokeWidth: 2, + data: 'M53.5 228.401C53.5 228.401 56.4 223.501 61.2 222.701', + }, + { + fill: '#b2b2b2', + data: + 'M-25.8 265.201C-25.8 265.201 -7.8 268.401 -3.4 266.801C-3.4 266.801 5.4 266.801 -3 268.801C-3 268.801 -15.8 268.801 -23.8 267.601C-23.8 267.601 -35.4 262.001 -25.8 265.201z', + }, + { + fill: '#ffffcc', + stroke: '#000000;', + strokeWidth: 0.5, + data: + 'M-11.8 172.001C-11.8 172.001 5.8 172.001 7.8 172.801C7.8 172.801 15 203.601 11.4 211.201C11.4 211.201 10.2 214.001 7.4 208.401C7.4 208.401 -11 175.601 -14.2 173.601C-17.4 171.601 -13 172.001 -11.8 172.001z', + }, + { + fill: '#ffffcc', + stroke: '#000000;', + strokeWidth: 0.5, + data: + 'M-88.9 169.301C-88.9 169.301 -80 171.001 -67.4 173.601C-67.4 173.601 -62.6 196.001 -59.4 200.801C-56.2 205.601 -59.8 205.601 -63.4 202.801C-67 200.001 -81.8 186.001 -83.8 181.601C-85.8 177.201 -88.9 169.301 -88.9 169.301z', + }, + { + fill: '#ffffcc', + stroke: '#000000;', + strokeWidth: 0.5, + data: + 'M-67.039 173.818C-67.039 173.818 -61.239 175.366 -60.23 177.581C-59.222 179.795 -61.432 183.092 -61.432 183.092C-61.432 183.092 -62.432 186.397 -63.634 184.235C-64.836 182.072 -67.708 174.412 -67.039 173.818z', + }, + { + fill: '#000000', + data: + 'M-67 173.601C-67 173.601 -63.4 178.801 -59.8 178.801C-56.2 178.801 -55.818 178.388 -53 179.001C-48.4 180.001 -48.8 178.001 -42.2 179.201C-39.56 179.681 -37 178.801 -34.2 180.001C-31.4 181.201 -28.2 180.401 -27 178.401C-25.8 176.401 -21 172.201 -21 172.201C-21 172.201 -33.8 174.001 -36.6 174.801C-36.6 174.801 -59 176.001 -67 173.601z', + }, + { + fill: '#ffffcc', + stroke: '#000000;', + strokeWidth: 0.5, + data: + 'M-22.4 173.801C-22.4 173.801 -28.85 177.301 -29.25 179.701C-29.65 182.101 -24 185.801 -24 185.801C-24 185.801 -21.25 190.401 -20.65 188.001C-20.05 185.601 -21.6 174.201 -22.4 173.801z', + }, + { + fill: '#ffffcc', + stroke: '#000000;', + strokeWidth: 0.5, + data: + 'M-59.885 179.265C-59.885 179.265 -52.878 190.453 -52.661 179.242C-52.661 179.242 -52.104 177.984 -53.864 177.962C-59.939 177.886 -58.418 173.784 -59.885 179.265z', + }, + { + fill: '#ffffcc', + stroke: '#000000;', + strokeWidth: 0.5, + data: + 'M-52.707 179.514C-52.707 179.514 -44.786 190.701 -45.422 179.421C-45.422 179.421 -45.415 179.089 -47.168 178.936C-51.915 178.522 -51.57 174.004 -52.707 179.514z', + }, + { + fill: '#ffffcc', + stroke: '#000000;', + strokeWidth: 0.5, + data: + 'M-45.494 179.522C-45.494 179.522 -37.534 190.15 -38.203 180.484C-38.203 180.484 -38.084 179.251 -39.738 178.95C-43.63 178.244 -43.841 174.995 -45.494 179.522z', + }, + { + fill: '#ffffcc', + stroke: '#000000;', + strokeWidth: 0.5, + data: + 'M-38.618 179.602C-38.618 179.602 -30.718 191.163 -30.37 181.382C-30.37 181.382 -28.726 180.004 -30.472 179.782C-36.29 179.042 -35.492 174.588 -38.618 179.602z', + }, + { + fill: '#e5e5b2', + data: + 'M-74.792 183.132L-82.45 181.601C-85.05 176.601 -87.15 170.451 -87.15 170.451C-87.15 170.451 -80.8 171.451 -68.3 174.251C-68.3 174.251 -67.424 177.569 -65.952 183.364L-74.792 183.132z', + }, + { + fill: '#e5e5b2', + data: + 'M-9.724 178.47C-11.39 175.964 -12.707 174.206 -13.357 173.8C-16.37 171.917 -12.227 172.294 -11.098 172.294C-11.098 172.294 5.473 172.294 7.356 173.047C7.356 173.047 7.88 175.289 8.564 178.68C8.564 178.68 -1.524 176.67 -9.724 178.47z', + }, + { + fill: '#cc7226', + data: + 'M43.88 40.321C71.601 44.281 97.121 8.641 98.881 -1.04C100.641 -10.72 90.521 -22.6 90.521 -22.6C91.841 -25.68 87.001 -39.76 81.721 -49C76.441 -58.24 60.54 -57.266 43 -58.24C27.16 -59.12 8.68 -35.8 7.36 -34.04C6.04 -32.28 12.2 6.001 13.52 11.721C14.84 17.441 12.2 43.841 12.2 43.841C46.44 34.741 16.16 36.361 43.88 40.321z', + }, + { + fill: '#ea8e51', + data: + 'M8.088 -33.392C6.792 -31.664 12.84 5.921 14.136 11.537C15.432 17.153 12.84 43.073 12.84 43.073C45.512 34.193 16.728 35.729 43.944 39.617C71.161 43.505 96.217 8.513 97.945 -0.992C99.673 -10.496 89.737 -22.16 89.737 -22.16C91.033 -25.184 86.281 -39.008 81.097 -48.08C75.913 -57.152 60.302 -56.195 43.08 -57.152C27.528 -58.016 9.384 -35.12 8.088 -33.392z', + }, + { + fill: '#efaa7c', + data: + 'M8.816 -32.744C7.544 -31.048 13.48 5.841 14.752 11.353C16.024 16.865 13.48 42.305 13.48 42.305C44.884 33.145 17.296 35.097 44.008 38.913C70.721 42.729 95.313 8.385 97.009 -0.944C98.705 -10.272 88.953 -21.72 88.953 -21.72C90.225 -24.688 85.561 -38.256 80.473 -47.16C75.385 -56.064 60.063 -55.125 43.16 -56.064C27.896 -56.912 10.088 -34.44 8.816 -32.744z', + }, + { + fill: '#f4c6a8', + data: + 'M9.544 -32.096C8.296 -30.432 14.12 5.761 15.368 11.169C16.616 16.577 14.12 41.537 14.12 41.537C43.556 32.497 17.864 34.465 44.072 38.209C70.281 41.953 94.409 8.257 96.073 -0.895C97.737 -10.048 88.169 -21.28 88.169 -21.28C89.417 -24.192 84.841 -37.504 79.849 -46.24C74.857 -54.976 59.824 -54.055 43.24 -54.976C28.264 -55.808 10.792 -33.76 9.544 -32.096z', + }, + { + fill: '#f9e2d3', + data: + 'M10.272 -31.448C9.048 -29.816 14.76 5.681 15.984 10.985C17.208 16.289 14.76 40.769 14.76 40.769C42.628 31.849 18.432 33.833 44.136 37.505C69.841 41.177 93.505 8.129 95.137 -0.848C96.769 -9.824 87.385 -20.84 87.385 -20.84C88.609 -23.696 84.121 -36.752 79.225 -45.32C74.329 -53.888 59.585 -52.985 43.32 -53.888C28.632 -54.704 11.496 -33.08 10.272 -31.448z', + }, + { + fill: '#ffffff', + data: + 'M44.2 36.8C69.4 40.4 92.601 8 94.201 -0.8C95.801 -9.6 86.601 -20.4 86.601 -20.4C87.801 -23.2 83.4 -36 78.6 -44.4C73.8 -52.8 59.346 -51.914 43.4 -52.8C29 -53.6 12.2 -32.4 11 -30.8C9.8 -29.2 15.4 5.6 16.6 10.8C17.8 16 15.4 40 15.4 40C40.9 31.4 19 33.2 44.2 36.8z', + }, + { + fill: '#cccccc', + data: + 'M90.601 2.8C90.601 2.8 62.8 10.4 51.2 8.8C51.2 8.8 35.4 2.2 26.6 24C26.6 24 23 31.2 21 33.2C19 35.2 90.601 2.8 90.601 2.8z', + }, + { + fill: '#000000', + data: + 'M94.401 0.6C94.401 0.6 65.4 12.8 55.4 12.4C55.4 12.4 39 7.8 30.6 22.4C30.6 22.4 22.2 31.6 19 33.2C19 33.2 18.6 34.8 25 30.8L35.4 36C35.4 36 50.2 45.6 59.8 29.6C59.8 29.6 63.8 18.4 63.8 16.4C63.8 14.4 85 8.8 86.601 8.4C88.201 8 94.801 3.8 94.401 0.6z', + }, + { + fill: '#99cc32', + data: + 'M47 36.514C40.128 36.514 31.755 32.649 31.755 26.4C31.755 20.152 40.128 13.887 47 13.887C53.874 13.887 59.446 18.952 59.446 25.2C59.446 31.449 53.874 36.514 47 36.514z', + }, + { + fill: '#659900', + data: + 'M43.377 19.83C38.531 20.552 33.442 22.055 33.514 21.839C35.054 17.22 41.415 13.887 47 13.887C51.296 13.887 55.084 15.865 57.32 18.875C57.32 18.875 52.004 18.545 43.377 19.83z', + }, + { + fill: '#ffffff', + data: 'M55.4 19.6C55.4 19.6 51 16.4 51 18.6C51 18.6 54.6 23 55.4 19.6z', + }, + { + fill: '#000000', + data: + 'M45.4 27.726C42.901 27.726 40.875 25.7 40.875 23.2C40.875 20.701 42.901 18.675 45.4 18.675C47.9 18.675 49.926 20.701 49.926 23.2C49.926 25.7 47.9 27.726 45.4 27.726z', + }, + { + fill: '#cc7226', + data: + 'M-58.6 14.4C-58.6 14.4 -61.8 -6.8 -59.4 -11.2C-59.4 -11.2 -48.6 -21.2 -49 -24.8C-49 -24.8 -49.4 -42.8 -50.6 -43.6C-51.8 -44.4 -59.4 -50.4 -65.4 -44C-65.4 -44 -75.8 -26 -75 -19.6L-75 -17.6C-75 -17.6 -82.6 -18 -84.2 -16C-84.2 -16 -85.4 -10.8 -86.6 -10.4C-86.6 -10.4 -89.4 -8 -87.4 -5.2C-87.4 -5.2 -89.4 -2.8 -89 1.2L-81.4 5.2C-81.4 5.2 -79.4 19.6 -68.6 24.8C-63.764 27.129 -60.6 20.4 -58.6 14.4z', + }, + { + fill: '#ffffff', + data: + 'M-59.6 12.56C-59.6 12.56 -62.48 -6.52 -60.32 -10.48C-60.32 -10.48 -50.6 -19.48 -50.96 -22.72C-50.96 -22.72 -51.32 -38.92 -52.4 -39.64C-53.48 -40.36 -60.32 -45.76 -65.72 -40C-65.72 -40 -75.08 -23.8 -74.36 -18.04L-74.36 -16.24C-74.36 -16.24 -81.2 -16.6 -82.64 -14.8C-82.64 -14.8 -83.72 -10.12 -84.8 -9.76C-84.8 -9.76 -87.32 -7.6 -85.52 -5.08C-85.52 -5.08 -87.32 -2.92 -86.96 0.68L-80.12 4.28C-80.12 4.28 -78.32 17.24 -68.6 21.92C-64.248 24.015 -61.4 17.96 -59.6 12.56z', + }, + { + fill: '#eb955c', + data: + 'M-51.05 -42.61C-52.14 -43.47 -59.63 -49.24 -65.48 -43C-65.48 -43 -75.62 -25.45 -74.84 -19.21L-74.84 -17.26C-74.84 -17.26 -82.25 -17.65 -83.81 -15.7C-83.81 -15.7 -84.98 -10.63 -86.15 -10.24C-86.15 -10.24 -88.88 -7.9 -86.93 -5.17C-86.93 -5.17 -88.88 -2.83 -88.49 1.07L-81.08 4.97C-81.08 4.97 -79.13 19.01 -68.6 24.08C-63.886 26.35 -60.8 19.79 -58.85 13.94C-58.85 13.94 -61.97 -6.73 -59.63 -11.02C-59.63 -11.02 -49.1 -20.77 -49.49 -24.28C-49.49 -24.28 -49.88 -41.83 -51.05 -42.61z', + }, + { + fill: '#f2b892', + data: + 'M-51.5 -41.62C-52.48 -42.54 -59.86 -48.08 -65.56 -42C-65.56 -42 -75.44 -24.9 -74.68 -18.82L-74.68 -16.92C-74.68 -16.92 -81.9 -17.3 -83.42 -15.4C-83.42 -15.4 -84.56 -10.46 -85.7 -10.08C-85.7 -10.08 -88.36 -7.8 -86.46 -5.14C-86.46 -5.14 -88.36 -2.86 -87.98 0.94L-80.76 4.74C-80.76 4.74 -78.86 18.42 -68.6 23.36C-64.006 25.572 -61 19.18 -59.1 13.48C-59.1 13.48 -62.14 -6.66 -59.86 -10.84C-59.86 -10.84 -49.6 -20.34 -49.98 -23.76C-49.98 -23.76 -50.36 -40.86 -51.5 -41.62z', + }, + { + fill: '#f8dcc8', + data: + 'M-51.95 -40.63C-52.82 -41.61 -60.09 -46.92 -65.64 -41C-65.64 -41 -75.26 -24.35 -74.52 -18.43L-74.52 -16.58C-74.52 -16.58 -81.55 -16.95 -83.03 -15.1C-83.03 -15.1 -84.14 -10.29 -85.25 -9.92C-85.25 -9.92 -87.84 -7.7 -85.99 -5.11C-85.99 -5.11 -87.84 -2.89 -87.47 0.81L-80.44 4.51C-80.44 4.51 -78.59 17.83 -68.6 22.64C-64.127 24.794 -61.2 18.57 -59.35 13.02C-59.35 13.02 -62.31 -6.59 -60.09 -10.66C-60.09 -10.66 -50.1 -19.91 -50.47 -23.24C-50.47 -23.24 -50.84 -39.89 -51.95 -40.63z', + }, + { + fill: '#ffffff', + data: + 'M-59.6 12.46C-59.6 12.46 -62.48 -6.52 -60.32 -10.48C-60.32 -10.48 -50.6 -19.48 -50.96 -22.72C-50.96 -22.72 -51.32 -38.92 -52.4 -39.64C-53.16 -40.68 -60.32 -45.76 -65.72 -40C-65.72 -40 -75.08 -23.8 -74.36 -18.04L-74.36 -16.24C-74.36 -16.24 -81.2 -16.6 -82.64 -14.8C-82.64 -14.8 -83.72 -10.12 -84.8 -9.76C-84.8 -9.76 -87.32 -7.6 -85.52 -5.08C-85.52 -5.08 -87.32 -2.92 -86.96 0.68L-80.12 4.28C-80.12 4.28 -78.32 17.24 -68.6 21.92C-64.248 24.015 -61.4 17.86 -59.6 12.46z', + }, + { + fill: '#cccccc', + data: + 'M-62.7 6.2C-62.7 6.2 -84.3 -4 -85.2 -4.8C-85.2 -4.8 -76.1 3.4 -75.3 3.4C-74.5 3.4 -62.7 6.2 -62.7 6.2z', + }, + { + fill: '#000000', + data: + 'M-79.8 0C-79.8 0 -61.4 3.6 -61.4 8C-61.4 10.912 -61.643 24.331 -67 22.8C-75.4 20.4 -71.8 6 -79.8 0z', + }, + { + fill: '#99cc32', + data: + 'M-71.4 3.8C-71.4 3.8 -62.422 5.274 -61.4 8C-60.8 9.6 -60.137 17.908 -65.6 19C-70.152 19.911 -72.382 9.69 -71.4 3.8z', + }, + { + fill: '#000000', + data: + 'M14.595 46.349C14.098 44.607 15.409 44.738 17.2 44.2C19.2 43.6 31.4 39.8 32.2 37.2C33 34.6 46.2 39 46.2 39C48 39.8 52.4 42.4 52.4 42.4C57.2 43.6 63.8 44 63.8 44C66.2 45 69.6 47.8 69.6 47.8C84.2 58 96.601 50.8 96.601 50.8C116.601 44.2 110.601 27 110.601 27C107.601 18 110.801 14.6 110.801 14.6C111.001 10.8 118.201 17.2 118.201 17.2C120.801 21.4 121.601 26.4 121.601 26.4C129.601 37.6 126.201 19.8 126.201 19.8C126.401 18.8 123.601 15.2 123.601 14C123.601 12.8 121.801 9.4 121.801 9.4C118.801 6 121.201 -1 121.201 -1C123.001 -14.8 120.801 -13 120.801 -13C119.601 -14.8 110.401 -4.8 110.401 -4.8C108.201 -1.4 102.201 0.2 102.201 0.2C99.401 2 96.001 0.6 96.001 0.6C93.401 0.2 87.801 7.2 87.801 7.2C90.601 7 93.001 11.4 95.401 11.6C97.801 11.8 99.601 9.2 101.201 8.6C102.801 8 105.601 13.8 105.601 13.8C106.001 16.4 100.401 21.2 100.401 21.2C100.001 25.8 98.401 24.2 98.401 24.2C95.401 23.6 94.201 27.4 93.201 32C92.201 36.6 88.001 37 88.001 37C86.401 44.4 85.2 41.4 85.2 41.4C85 35.8 79 41.6 79 41.6C77.8 43.6 73.2 41.4 73.2 41.4C66.4 39.4 68.8 37.4 68.8 37.4C70.6 35.2 81.8 37.4 81.8 37.4C84 35.8 76 31.8 76 31.8C75.4 30 76.4 25.6 76.4 25.6C77.6 22.4 84.4 16.8 84.4 16.8C93.801 15.6 91.001 14 91.001 14C84.801 8.8 79 16.4 79 16.4C76.8 22.6 59.4 37.6 59.4 37.6C54.6 41 57.2 34.2 53.2 37.6C49.2 41 28.6 32 28.6 32C17.038 30.807 14.306 46.549 10.777 43.429C10.777 43.429 16.195 51.949 14.595 46.349z', + }, + { + fill: '#000000', + data: + 'M209.401 -120C209.401 -120 183.801 -112 181.001 -93.2C181.001 -93.2 178.601 -70.4 199.001 -52.8C199.001 -52.8 199.401 -46.4 201.401 -43.2C201.401 -43.2 199.801 -38.4 218.601 -46L245.801 -54.4C245.801 -54.4 252.201 -56.8 257.401 -65.6C262.601 -74.4 277.801 -93.2 274.201 -118.4C274.201 -118.4 275.401 -129.6 269.401 -130C269.401 -130 261.001 -131.6 253.801 -124C253.801 -124 247.001 -120.8 244.601 -121.2L209.401 -120z', + }, + { + fill: '#000000', + data: + 'M264.022 -120.99C264.022 -120.99 266.122 -129.92 261.282 -125.08C261.282 -125.08 254.242 -119.36 246.761 -119.36C246.761 -119.36 232.241 -117.16 227.841 -103.96C227.841 -103.96 223.881 -77.12 231.801 -71.4C231.801 -71.4 236.641 -63.92 243.681 -70.52C250.722 -77.12 266.222 -107.35 264.022 -120.99z', + }, + { + fill: '#323232', + data: + 'M263.648 -120.632C263.648 -120.632 265.738 -129.376 260.986 -124.624C260.986 -124.624 254.074 -119.008 246.729 -119.008C246.729 -119.008 232.473 -116.848 228.153 -103.888C228.153 -103.888 224.265 -77.536 232.041 -71.92C232.041 -71.92 236.793 -64.576 243.705 -71.056C250.618 -77.536 265.808 -107.24 263.648 -120.632z', + }, + { + fill: '#666666', + data: + 'M263.274 -120.274C263.274 -120.274 265.354 -128.832 260.69 -124.168C260.69 -124.168 253.906 -118.656 246.697 -118.656C246.697 -118.656 232.705 -116.536 228.465 -103.816C228.465 -103.816 224.649 -77.952 232.281 -72.44C232.281 -72.44 236.945 -65.232 243.729 -71.592C250.514 -77.952 265.394 -107.13 263.274 -120.274z', + }, + { + fill: '#999999', + data: + 'M262.9 -119.916C262.9 -119.916 264.97 -128.288 260.394 -123.712C260.394 -123.712 253.738 -118.304 246.665 -118.304C246.665 -118.304 232.937 -116.224 228.777 -103.744C228.777 -103.744 225.033 -78.368 232.521 -72.96C232.521 -72.96 237.097 -65.888 243.753 -72.128C250.41 -78.368 264.98 -107.02 262.9 -119.916z', + }, + { + fill: '#cccccc', + data: + 'M262.526 -119.558C262.526 -119.558 264.586 -127.744 260.098 -123.256C260.098 -123.256 253.569 -117.952 246.633 -117.952C246.633 -117.952 233.169 -115.912 229.089 -103.672C229.089 -103.672 225.417 -78.784 232.761 -73.48C232.761 -73.48 237.249 -66.544 243.777 -72.664C250.305 -78.784 264.566 -106.91 262.526 -119.558z', + }, + { + fill: '#ffffff', + data: + 'M262.151 -119.2C262.151 -119.2 264.201 -127.2 259.801 -122.8C259.801 -122.8 253.401 -117.6 246.601 -117.6C246.601 -117.6 233.401 -115.6 229.401 -103.6C229.401 -103.6 225.801 -79.2 233.001 -74C233.001 -74 237.401 -67.2 243.801 -73.2C250.201 -79.2 264.151 -106.8 262.151 -119.2z', + }, + { + fill: '#992600', + data: + 'M50.6 84C50.6 84 30.2 64.8 22.2 64C22.2 64 -12.2 60 -27 78C-27 78 -9.4 57.6 18.2 63.2C18.2 63.2 -3.4 58.8 -15.8 62C-15.8 62 -32.6 62 -42.2 76L-45 80.8C-45 80.8 -41 66 -22.6 60C-22.6 60 0.2 55.2 11 60C11 60 -10.6 53.2 -20.6 55.2C-20.6 55.2 -51 52.8 -63.8 79.2C-63.8 79.2 -59.8 64.8 -45 57.6C-45 57.6 -31.4 48.8 -11 51.6C-11 51.6 3.4 54.8 8.6 57.2C13.8 59.6 12.6 56.8 4.2 52C4.2 52 -1.4 42 -15.4 42.4C-15.4 42.4 -58.2 46 -68.6 58C-68.6 58 -55 46.8 -44.6 44C-44.6 44 -22.2 36 -13.8 36.8C-13.8 36.8 11 37.8 18.6 33.8C18.6 33.8 7.4 38.8 10.6 42C13.8 45.2 20.6 52.8 20.6 54C20.6 55.2 44.8 77.3 48.4 81.7L50.6 84z', + }, + { + fill: '#cccccc', + data: + 'M189 278C189 278 173.5 241.5 161 232C161 232 187 248 190.5 266C190.5 266 190.5 276 189 278z', + }, + { + fill: '#cccccc', + data: + 'M236 285.5C236 285.5 209.5 230.5 191 206.5C191 206.5 234.5 244 239.5 270.5L240 276L237 273.5C237 273.5 236.5 282.5 236 285.5z', + }, + { + fill: '#cccccc', + data: + 'M292.5 237C292.5 237 230 177.5 228.5 175C228.5 175 289 241 292 248.5C292 248.5 290 239.5 292.5 237z', + }, + { + fill: '#cccccc', + data: + 'M104 280.5C104 280.5 123.5 228.5 142.5 251C142.5 251 157.5 261 157 264C157 264 153 257.5 135 258C135 258 116 255 104 280.5z', + }, + { + fill: '#cccccc', + data: + 'M294.5 153C294.5 153 249.5 124.5 242 123C230.193 120.639 291.5 152 296.5 162.5C296.5 162.5 298.5 160 294.5 153z', + }, + { + fill: '#000000', + data: + 'M143.801 259.601C143.801 259.601 164.201 257.601 171.001 250.801L175.401 254.401L193.001 216.001L196.601 221.201C196.601 221.201 211.001 206.401 210.201 198.401C209.401 190.401 223.001 204.401 223.001 204.401C223.001 204.401 222.201 192.801 229.401 199.601C229.401 199.601 227.001 184.001 235.401 192.001C235.401 192.001 224.864 161.844 247.401 187.601C253.001 194.001 248.601 187.201 248.601 187.201C248.601 187.201 222.601 139.201 244.201 153.601C244.201 153.601 246.201 130.801 245.001 126.401C243.801 122.001 241.801 99.6 237.001 94.4C232.201 89.2 237.401 87.6 243.001 92.8C243.001 92.8 231.801 68.8 245.001 80.8C245.001 80.8 241.401 65.6 237.001 62.8C237.001 62.8 231.401 45.6 246.601 56.4C246.601 56.4 242.201 44 239.001 40.8C239.001 40.8 227.401 13.2 234.601 18L239.001 21.6C239.001 21.6 232.201 7.6 238.601 12C245.001 16.4 245.001 16 245.001 16C245.001 16 223.801 -17.2 244.201 0.4C244.201 0.4 236.042 -13.518 232.601 -20.4C232.601 -20.4 213.801 -40.8 228.201 -34.4L233.001 -32.8C233.001 -32.8 224.201 -42.8 216.201 -44.4C208.201 -46 218.601 -52.4 225.001 -50.4C231.401 -48.4 247.001 -40.8 247.001 -40.8C247.001 -40.8 259.801 -22 263.801 -21.6C263.801 -21.6 243.801 -29.2 249.801 -21.2C249.801 -21.2 264.201 -7.2 257.001 -7.6C257.001 -7.6 251.001 -0.4 255.801 8.4C255.801 8.4 237.342 -9.991 252.201 15.6L259.001 32C259.001 32 234.601 7.2 245.801 29.2C245.801 29.2 263.001 52.8 265.001 53.2C267.001 53.6 271.401 62.4 271.401 62.4L267.001 60.4L272.201 69.2C272.201 69.2 261.001 57.2 267.001 70.4L272.601 84.8C272.601 84.8 252.201 62.8 265.801 92.4C265.801 92.4 249.401 87.2 258.201 104.4C258.201 104.4 256.601 120.401 257.001 125.601C257.401 130.801 258.601 159.201 254.201 167.201C249.801 175.201 260.201 194.401 262.201 198.401C264.201 202.401 267.801 213.201 259.001 204.001C250.201 194.801 254.601 200.401 256.601 209.201C258.601 218.001 264.601 233.601 263.801 239.201C263.801 239.201 262.601 240.401 259.401 236.801C259.401 236.801 244.601 214.001 246.201 228.401C246.201 228.401 245.001 236.401 241.801 245.201C241.801 245.201 238.601 256.001 238.601 247.201C238.601 247.201 235.401 230.401 232.601 238.001C229.801 245.601 226.201 251.601 223.401 254.001C220.601 256.401 215.401 233.601 214.201 244.001C214.201 244.001 202.201 231.601 197.401 248.001L185.801 264.401C185.801 264.401 185.401 252.001 184.201 258.001C184.201 258.001 154.201 264.001 143.801 259.601z', + }, + { + fill: '#000000', + data: + 'M109.401 -97.2C109.401 -97.2 97.801 -105.2 93.801 -104.8C89.801 -104.4 121.401 -113.6 162.601 -86C162.601 -86 167.401 -83.2 171.001 -83.6C171.001 -83.6 174.201 -81.2 171.401 -77.6C171.401 -77.6 162.601 -68 173.801 -56.8C173.801 -56.8 192.201 -50 186.601 -58.8C186.601 -58.8 197.401 -54.8 199.801 -50.8C202.201 -46.8 201.001 -50.8 201.001 -50.8C201.001 -50.8 194.601 -58 188.601 -63.2C188.601 -63.2 183.401 -65.2 180.601 -73.6C177.801 -82 175.401 -92 179.801 -95.2C179.801 -95.2 175.801 -90.8 176.601 -94.8C177.401 -98.8 181.001 -102.4 182.601 -102.8C184.201 -103.2 200.601 -119 207.401 -119.4C207.401 -119.4 198.201 -118 195.201 -119C192.201 -120 165.601 -131.4 159.601 -132.6C159.601 -132.6 142.801 -139.2 154.801 -137.2C154.801 -137.2 190.601 -133.4 208.801 -120.2C208.801 -120.2 201.601 -128.6 183.201 -135.6C183.201 -135.6 161.001 -148.2 125.801 -143.2C125.801 -143.2 108.001 -140 100.201 -138.2C100.201 -138.2 97.601 -138.8 97.001 -139.2C96.401 -139.6 84.6 -148.6 57 -141.6C57 -141.6 40 -137 31.4 -132.2C31.4 -132.2 16.2 -131 12.6 -127.8C12.6 -127.8 -6 -113.2 -8 -112.4C-10 -111.6 -21.4 -104 -22.2 -103.6C-22.2 -103.6 2.4 -110.2 4.8 -112.6C7.2 -115 24.6 -117.6 27 -116.2C29.4 -114.8 37.8 -115.4 28.2 -114.8C28.2 -114.8 103.801 -100 104.601 -98C105.401 -96 109.401 -97.2 109.401 -97.2z', + }, + { + fill: '#cc7226', + data: + 'M180.801 -106.4C180.801 -106.4 170.601 -113.8 168.601 -113.8C166.601 -113.8 154.201 -124 150.001 -123.6C145.801 -123.2 133.601 -133.2 106.201 -125C106.201 -125 105.601 -127 109.201 -127.8C109.201 -127.8 115.601 -130 116.001 -130.6C116.001 -130.6 136.201 -134.8 143.401 -131.2C143.401 -131.2 152.601 -128.6 158.801 -122.4C158.801 -122.4 170.001 -119.2 173.201 -120.2C173.201 -120.2 182.001 -118 182.401 -116.2C182.401 -116.2 188.201 -113.2 186.401 -110.6C186.401 -110.6 186.801 -109 180.801 -106.4z', + }, + { + fill: '#cc7226', + data: + 'M168.33 -108.509C169.137 -107.877 170.156 -107.779 170.761 -106.97C170.995 -106.656 170.706 -106.33 170.391 -106.233C169.348 -105.916 168.292 -106.486 167.15 -105.898C166.748 -105.691 166.106 -105.873 165.553 -106.022C163.921 -106.463 162.092 -106.488 160.401 -105.8C158.416 -106.929 156.056 -106.345 153.975 -107.346C153.917 -107.373 153.695 -107.027 153.621 -107.054C150.575 -108.199 146.832 -107.916 144.401 -110.2C141.973 -110.612 139.616 -111.074 137.188 -111.754C135.37 -112.263 133.961 -113.252 132.341 -114.084C130.964 -114.792 129.507 -115.314 127.973 -115.686C126.11 -116.138 124.279 -116.026 122.386 -116.546C122.293 -116.571 122.101 -116.227 122.019 -116.254C121.695 -116.362 121.405 -116.945 121.234 -116.892C119.553 -116.37 118.065 -117.342 116.401 -117C115.223 -118.224 113.495 -117.979 111.949 -118.421C108.985 -119.269 105.831 -117.999 102.801 -119C106.914 -120.842 111.601 -119.61 115.663 -121.679C117.991 -122.865 120.653 -121.763 123.223 -122.523C123.71 -122.667 124.401 -122.869 124.801 -122.2C124.935 -122.335 125.117 -122.574 125.175 -122.546C127.625 -121.389 129.94 -120.115 132.422 -119.049C132.763 -118.903 133.295 -119.135 133.547 -118.933C135.067 -117.717 137.01 -117.82 138.401 -116.6C140.099 -117.102 141.892 -116.722 143.621 -117.346C143.698 -117.373 143.932 -117.032 143.965 -117.054C145.095 -117.802 146.25 -117.531 147.142 -117.227C147.48 -117.112 148.143 -116.865 148.448 -116.791C149.574 -116.515 150.43 -116.035 151.609 -115.852C151.723 -115.834 151.908 -116.174 151.98 -116.146C153.103 -115.708 154.145 -115.764 154.801 -114.6C154.936 -114.735 155.101 -114.973 155.183 -114.946C156.21 -114.608 156.859 -113.853 157.96 -113.612C158.445 -113.506 159.057 -112.88 159.633 -112.704C162.025 -111.973 163.868 -110.444 166.062 -109.549C166.821 -109.239 167.697 -109.005 168.33 -108.509z', + }, + { + fill: '#cc7226', + data: + 'M91.696 -122.739C89.178 -124.464 86.81 -125.57 84.368 -127.356C84.187 -127.489 83.827 -127.319 83.625 -127.441C82.618 -128.05 81.73 -128.631 80.748 -129.327C80.209 -129.709 79.388 -129.698 78.88 -129.956C76.336 -131.248 73.707 -131.806 71.2 -133C71.882 -133.638 73.004 -133.394 73.6 -134.2C73.795 -133.92 74.033 -133.636 74.386 -133.827C76.064 -134.731 77.914 -134.884 79.59 -134.794C81.294 -134.702 83.014 -134.397 84.789 -134.125C85.096 -134.078 85.295 -133.555 85.618 -133.458C87.846 -132.795 90.235 -133.32 92.354 -132.482C93.945 -131.853 95.515 -131.03 96.754 -129.755C97.006 -129.495 96.681 -129.194 96.401 -129C96.789 -129.109 97.062 -128.903 97.173 -128.59C97.257 -128.351 97.257 -128.049 97.173 -127.81C97.061 -127.498 96.782 -127.397 96.408 -127.346C95.001 -127.156 96.773 -128.536 96.073 -128.088C94.8 -127.274 95.546 -125.868 94.801 -124.6C94.521 -124.794 94.291 -125.012 94.401 -125.4C94.635 -124.878 94.033 -124.588 93.865 -124.272C93.48 -123.547 92.581 -122.132 91.696 -122.739z', + }, + { + fill: '#cc7226', + data: + 'M59.198 -115.391C56.044 -116.185 52.994 -116.07 49.978 -117.346C49.911 -117.374 49.688 -117.027 49.624 -117.054C48.258 -117.648 47.34 -118.614 46.264 -119.66C45.351 -120.548 43.693 -120.161 42.419 -120.648C42.095 -120.772 41.892 -121.284 41.591 -121.323C40.372 -121.48 39.445 -122.429 38.4 -123C40.736 -123.795 43.147 -123.764 45.609 -124.148C45.722 -124.166 45.867 -123.845 46 -123.845C46.136 -123.845 46.266 -124.066 46.4 -124.2C46.595 -123.92 46.897 -123.594 47.154 -123.848C47.702 -124.388 48.258 -124.198 48.798 -124.158C48.942 -124.148 49.067 -123.845 49.2 -123.845C49.336 -123.845 49.467 -124.156 49.6 -124.156C49.736 -124.155 49.867 -123.845 50 -123.845C50.136 -123.845 50.266 -124.066 50.4 -124.2C51.092 -123.418 51.977 -123.972 52.799 -123.793C53.837 -123.566 54.104 -122.418 55.178 -122.12C59.893 -120.816 64.03 -118.671 68.393 -116.584C68.7 -116.437 68.91 -116.189 68.8 -115.8C69.067 -115.8 69.38 -115.888 69.57 -115.756C70.628 -115.024 71.669 -114.476 72.366 -113.378C72.582 -113.039 72.253 -112.632 72.02 -112.684C67.591 -113.679 63.585 -114.287 59.198 -115.391z', + }, + { + fill: '#cc7226', + data: + 'M45.338 -71.179C43.746 -72.398 43.162 -74.429 42.034 -76.221C41.82 -76.561 42.094 -76.875 42.411 -76.964C42.971 -77.123 43.514 -76.645 43.923 -76.443C45.668 -75.581 47.203 -74.339 49.2 -74.2C51.19 -71.966 55.45 -71.581 55.457 -68.2C55.458 -67.341 54.03 -68.259 53.6 -67.4C51.149 -68.403 48.76 -68.3 46.38 -69.767C45.763 -70.148 46.093 -70.601 45.338 -71.179z', + }, + { + fill: '#cc7226', + data: + 'M17.8 -123.756C17.935 -123.755 24.966 -123.522 24.949 -123.408C24.904 -123.099 17.174 -122.05 16.81 -122.22C16.646 -122.296 9.134 -119.866 9 -120C9.268 -120.135 17.534 -123.756 17.8 -123.756z', + }, + { + fill: '#000000', + data: + 'M33.2 -114C33.2 -114 18.4 -112.2 14 -111C9.6 -109.8 -9 -102.2 -12 -100.2C-12 -100.2 -25.4 -94.8 -42.4 -74.8C-42.4 -74.8 -34.8 -78.2 -32.6 -81C-32.6 -81 -19 -93.6 -19.2 -91C-19.2 -91 -7 -99.6 -7.6 -97.4C-7.6 -97.4 16.8 -108.6 14.8 -105.4C14.8 -105.4 36.4 -110 35.4 -108C35.4 -108 54.2 -103.6 51.4 -103.4C51.4 -103.4 45.6 -102.2 52 -98.6C52 -98.6 48.6 -94.2 43.2 -98.2C37.8 -102.2 40.8 -100 35.8 -99C35.8 -99 33.2 -98.2 28.6 -102.2C28.6 -102.2 23 -106.8 14.2 -103.2C14.2 -103.2 -16.4 -90.6 -18.4 -90C-18.4 -90 -22 -87.2 -24.4 -83.6C-24.4 -83.6 -30.2 -79.2 -33.2 -77.8C-33.2 -77.8 -46 -66.2 -47.2 -64.8C-47.2 -64.8 -50.6 -59.6 -51.4 -59.2C-51.4 -59.2 -45 -63 -43 -65C-43 -65 -29 -75 -23.6 -75.8C-23.6 -75.8 -19.2 -78.8 -18.4 -80.2C-18.4 -80.2 -4 -89.4 0.2 -89.4C0.2 -89.4 9.4 -84.2 11.8 -91.2C11.8 -91.2 17.6 -93 23.2 -91.8C23.2 -91.8 26.4 -94.4 25.6 -96.6C25.6 -96.6 27.2 -98.4 28.2 -94.6C28.2 -94.6 31.6 -91 36.4 -93C36.4 -93 40.4 -93.2 38.4 -90.8C38.4 -90.8 34 -87 22.2 -86.8C22.2 -86.8 9.8 -86.2 -6.6 -78.6C-6.6 -78.6 -36.4 -68.2 -45.6 -57.8C-45.6 -57.8 -52 -49 -57.4 -47.8C-57.4 -47.8 -63.2 -47 -69.2 -39.6C-69.2 -39.6 -59.4 -45.4 -50.4 -45.4C-50.4 -45.4 -46.4 -47.8 -50.2 -44.2C-50.2 -44.2 -53.8 -36.6 -52.2 -31.2C-52.2 -31.2 -52.8 -26 -53.6 -24.4C-53.6 -24.4 -61.4 -11.6 -61.4 -9.2C-61.4 -6.8 -60.2 3 -59.8 3.6C-59.4 4.2 -60.8 2 -57 4.4C-53.2 6.8 -50.4 8.4 -49.6 11.2C-48.8 14 -51.6 5.8 -51.8 4C-52 2.2 -56.2 -5 -55.4 -7.4C-55.4 -7.4 -54.4 -6.4 -53.6 -5C-53.6 -5 -54.2 -5.6 -53.6 -9.2C-53.6 -9.2 -52.8 -14.4 -51.4 -17.6C-50 -20.8 -48 -24.6 -47.6 -25.4C-47.2 -26.2 -47.2 -32 -45.8 -29.4L-42.4 -26.8C-42.4 -26.8 -45.2 -29.4 -43 -31.6C-43 -31.6 -44 -37.2 -42.2 -39.8C-42.2 -39.8 -35.2 -48.2 -33.6 -49.2C-32 -50.2 -33.4 -49.8 -33.4 -49.8C-33.4 -49.8 -27.4 -54 -33.2 -52.4C-33.2 -52.4 -37.2 -50.8 -40.2 -50.8C-40.2 -50.8 -47.8 -48.8 -43.8 -53C-39.8 -57.2 -29.8 -62.6 -26 -62.4L-25.2 -60.8L-14 -63.2L-15.2 -62.4C-15.2 -62.4 -15.4 -62.6 -11.2 -63C-7 -63.4 -1.2 -62 0.2 -63.8C1.6 -65.6 5 -66.6 4.6 -65.2C4.2 -63.8 4 -61.8 4 -61.8C4 -61.8 9 -67.6 8.4 -65.4C7.8 -63.2 -0.4 -58 -1.8 -51.8L8.6 -60L12.2 -63C12.2 -63 15.8 -60.8 16 -62.4C16.2 -64 20.8 -69.8 22 -69.6C23.2 -69.4 25.2 -72.2 25 -69.6C24.8 -67 32.4 -61.6 32.4 -61.6C32.4 -61.6 35.6 -63.4 37 -62C38.4 -60.6 42.6 -81.8 42.6 -81.8L67.6 -92.4L111.201 -95.8L94.201 -102.6L33.2 -114z', + }, + { + stroke: '#4c0000', + strokeWidth: 2, + data: 'M51.4 85C51.4 85 36.4 68.2 28 65.6C28 65.6 14.6 58.8 -10 66.6', + }, + { + stroke: '#4c0000', + strokeWidth: 2, + data: + 'M24.8 64.2C24.8 64.2 -0.4 56.2 -15.8 60.4C-15.8 60.4 -34.2 62.4 -42.6 76.2', + }, + { + stroke: '#4c0000', + strokeWidth: 2, + data: + 'M21.2 63C21.2 63 4.2 55.8 -10.6 53.6C-10.6 53.6 -27.2 51 -43.8 58.2C-43.8 58.2 -56 64.2 -61.4 74.4', + }, + { + stroke: '#4c0000', + strokeWidth: 2, + data: + 'M22.2 63.4C22.2 63.4 6.8 52.4 5.8 51C5.8 51 -1.2 40 -14.2 39.6C-14.2 39.6 -35.6 40.4 -52.8 48.4', + }, + { + fill: '#000000', + data: + 'M20.895 54.407C22.437 55.87 49.4 84.8 49.4 84.8C84.6 121.401 56.6 87.2 56.6 87.2C49 82.4 39.8 63.6 39.8 63.6C38.6 60.8 53.8 70.8 53.8 70.8C57.8 71.6 71.4 90.8 71.4 90.8C64.6 88.4 69.4 95.6 69.4 95.6C72.2 97.6 92.601 113.201 92.601 113.201C96.201 117.201 100.201 118.801 100.201 118.801C114.201 113.601 107.801 126.801 107.801 126.801C110.201 133.601 115.801 122.001 115.801 122.001C127.001 105.2 110.601 107.601 110.601 107.601C80.6 110.401 73.8 94.4 73.8 94.4C71.4 92 80.2 94.4 80.2 94.4C88.601 96.4 73 82 73 82C75.4 82 84.6 88.8 84.6 88.8C95.001 98 97.001 96 97.001 96C115.001 87.2 125.401 94.8 125.401 94.8C127.401 96.4 121.801 103.2 123.401 108.401C125.001 113.601 129.801 126.001 129.801 126.001C127.401 127.601 127.801 138.401 127.801 138.401C144.601 161.601 135.001 159.601 135.001 159.601C119.401 159.201 134.201 166.801 134.201 166.801C137.401 168.801 146.201 176.001 146.201 176.001C143.401 174.801 141.801 180.001 141.801 180.001C146.601 184.001 143.801 188.801 143.801 188.801C137.801 190.001 136.601 194.001 136.601 194.001C143.401 202.001 133.401 202.401 133.401 202.401C137.001 206.801 132.201 218.801 132.201 218.801C127.401 218.801 121.001 224.401 121.001 224.401C123.401 229.201 113.001 234.801 113.001 234.801C104.601 236.401 107.401 243.201 107.401 243.201C99.401 249.201 97.001 265.201 97.001 265.201C96.201 275.601 93.801 278.801 99.001 276.801C104.201 274.801 103.401 262.401 103.401 262.401C98.601 246.801 141.401 230.801 141.401 230.801C145.401 229.201 146.201 224.001 146.201 224.001C148.201 224.401 157.001 232.001 157.001 232.001C164.601 243.201 165.001 234.001 165.001 234.001C166.201 230.401 164.601 224.401 164.601 224.401C170.601 202.801 156.601 196.401 156.601 196.401C146.601 162.801 160.601 171.201 160.601 171.201C163.401 176.801 174.201 182.001 174.201 182.001L177.801 179.601C176.201 174.801 184.601 168.801 184.601 168.801C187.401 175.201 193.401 167.201 193.401 167.201C197.001 142.801 209.401 157.201 209.401 157.201C213.401 158.401 214.601 151.601 214.601 151.601C218.201 141.201 214.601 127.601 214.601 127.601C218.201 127.201 227.801 133.201 227.801 133.201C230.601 129.601 221.401 112.801 225.401 115.201C229.401 117.601 233.801 119.201 233.801 119.201C234.601 117.201 224.601 104.801 224.601 104.801C220.201 102 215.001 81.6 215.001 81.6C222.201 85.2 212.201 70 212.201 70C212.201 66.8 218.201 55.6 218.201 55.6C217.401 48.8 218.201 49.2 218.201 49.2C221.001 50.4 229.001 52 222.201 45.6C215.401 39.2 223.001 34.4 223.001 34.4C227.401 31.6 213.801 32 213.801 32C208.601 27.6 209.001 23.6 209.001 23.6C217.001 25.6 202.601 11.2 200.201 7.6C197.801 4 207.401 -1.2 207.401 -1.2C220.601 -4.8 209.001 -8 209.001 -8C189.401 -7.6 200.201 -18.4 200.201 -18.4C206.201 -18 204.601 -20.4 204.601 -20.4C199.401 -21.6 189.801 -28 189.801 -28C185.801 -31.6 189.401 -30.8 189.401 -30.8C206.201 -29.6 177.401 -40.8 177.401 -40.8C185.401 -40.8 167.401 -51.2 167.401 -51.2C165.401 -52.8 162.201 -60.4 162.201 -60.4C156.201 -65.6 151.401 -72.4 151.401 -72.4C151.001 -76.8 146.201 -81.6 146.201 -81.6C134.601 -95.2 129.001 -94.8 129.001 -94.8C114.201 -98.4 109.001 -97.6 109.001 -97.6L56.2 -93.2C29.8 -80.4 37.6 -59.4 37.6 -59.4C44 -51 53.2 -54.8 53.2 -54.8C57.8 -61 69.4 -58.8 69.4 -58.8C89.801 -55.6 87.201 -59.2 87.201 -59.2C84.801 -63.8 68.6 -70 68.4 -70.6C68.2 -71.2 59.4 -74.6 59.4 -74.6C56.4 -75.8 52 -85 52 -85C48.8 -88.4 64.6 -82.6 64.6 -82.6C63.4 -81.6 70.8 -77.6 70.8 -77.6C88.201 -78.6 98.801 -67.8 98.801 -67.8C109.601 -51.2 109.801 -59.4 109.801 -59.4C112.601 -68.8 100.801 -90 100.801 -90C101.201 -92 109.401 -85.4 109.401 -85.4C110.801 -87.4 111.601 -81.6 111.601 -81.6C111.801 -79.2 115.601 -71.2 115.601 -71.2C118.401 -58.2 122.001 -65.6 122.001 -65.6L126.601 -56.2C128.001 -53.6 122.001 -46 122.001 -46C121.801 -43.2 122.601 -43.4 117.001 -35.8C111.401 -28.2 114.801 -23.8 114.801 -23.8C113.401 -17.2 122.201 -17.6 122.201 -17.6C124.801 -15.4 128.201 -15.4 128.201 -15.4C130.001 -13.4 132.401 -14 132.401 -14C134.001 -17.8 140.201 -15.8 140.201 -15.8C141.601 -18.2 149.801 -18.6 149.801 -18.6C150.801 -21.2 151.201 -22.8 154.601 -23.4C158.001 -24 133.401 -67 133.401 -67C139.801 -67.8 131.601 -80.2 131.601 -80.2C129.401 -86.8 140.801 -72.2 143.001 -70.8C145.201 -69.4 146.201 -67.2 144.601 -67.4C143.001 -67.6 141.201 -65.4 142.601 -65.2C144.001 -65 157.001 -50 160.401 -39.8C163.801 -29.6 169.801 -25.6 176.001 -19.6C182.201 -13.6 181.401 10.6 181.401 10.6C181.001 19.4 187.001 30 187.001 30C189.001 33.8 184.801 52 184.801 52C182.801 54.2 184.201 55 184.201 55C185.201 56.2 192.001 69.4 192.001 69.4C190.201 69.2 193.801 72.8 193.801 72.8C199.001 78.8 192.601 75.8 192.601 75.8C186.601 74.2 193.601 84 193.601 84C194.801 85.8 185.801 81.2 185.801 81.2C176.601 80.6 188.201 87.8 188.201 87.8C196.801 95 185.401 90.6 185.401 90.6C180.801 88.8 184.001 95.6 184.001 95.6C187.201 97.2 204.401 104.2 204.401 104.2C204.801 108.001 201.801 113.001 201.801 113.001C202.201 117.001 200.001 120.401 200.001 120.401C198.801 128.601 198.201 129.401 198.201 129.401C194.001 129.601 186.601 143.401 186.601 143.401C184.801 146.001 174.601 158.001 174.601 158.001C172.601 165.001 154.601 157.801 154.601 157.801C148.001 161.201 150.001 157.801 150.001 157.801C149.601 155.601 154.401 149.601 154.401 149.601C161.401 147.001 158.801 136.201 158.801 136.201C162.801 134.801 151.601 132.001 151.801 130.801C152.001 129.601 157.801 128.201 157.801 128.201C165.801 126.201 161.401 123.801 161.401 123.801C160.801 119.801 163.801 114.201 163.801 114.201C175.401 113.401 163.801 97.2 163.801 97.2C153.001 89.6 152.001 83.8 152.001 83.8C164.601 75.6 156.401 63.2 156.601 59.6C156.801 56 158.001 34.4 158.001 34.4C156.001 28.2 153.001 14.6 153.001 14.6C155.201 9.4 162.601 -3.2 162.601 -3.2C165.401 -7.4 174.201 -12.2 172.001 -15.2C169.801 -18.2 162.001 -16.4 162.001 -16.4C154.201 -17.8 154.801 -12.6 154.801 -12.6C153.201 -11.6 152.401 -6.6 152.401 -6.6C151.68 1.333 142.801 7.6 142.801 7.6C131.601 13.8 140.801 17.8 140.801 17.8C146.801 24.4 137.001 24.6 137.001 24.6C126.001 22.8 134.201 33 134.201 33C145.001 45.8 142.001 48.6 142.001 48.6C131.801 49.6 144.401 58.8 144.401 58.8C144.401 58.8 143.601 56.8 143.801 58.6C144.001 60.4 147.001 64.6 147.801 66.6C148.601 68.6 144.601 68.8 144.601 68.8C145.201 78.4 129.801 74.2 129.801 74.2C129.801 74.2 129.801 74.2 128.201 74.4C126.601 74.6 115.401 73.8 109.601 71.6C103.801 69.4 97.001 69.4 97.001 69.4C97.001 69.4 93.001 71.2 85.4 71C77.8 70.8 69.8 73.6 69.8 73.6C65.4 73.2 74 68.8 74.2 69C74.4 69.2 80 63.6 72 64.2C50.203 65.835 39.4 55.6 39.4 55.6C37.4 54.2 34.8 51.4 34.8 51.4C24.8 49.4 36.2 63.8 36.2 63.8C37.4 65.2 36 66.2 36 66.2C35.2 64.6 27.4 59.2 27.4 59.2C24.589 58.227 23.226 56.893 20.895 54.407z', + }, + { + fill: '#4c0000', + data: + 'M-3 42.8C-3 42.8 8.6 48.4 11.2 51.2C13.8 54 27.8 65.4 27.8 65.4C27.8 65.4 22.4 63.4 19.8 61.6C17.2 59.8 6.4 51.6 6.4 51.6C6.4 51.6 2.6 45.6 -3 42.8z', + }, + { + fill: '#99cc32', + data: + 'M-61.009 11.603C-60.672 11.455 -61.196 8.743 -61.4 8.2C-62.422 5.474 -71.4 4 -71.4 4C-71.627 5.365 -71.682 6.961 -71.576 8.599C-71.576 8.599 -66.708 14.118 -61.009 11.603z', + }, + { + fill: '#659900', + data: + 'M-61.009 11.403C-61.458 11.561 -61.024 8.669 -61.2 8.2C-62.222 5.474 -71.4 3.9 -71.4 3.9C-71.627 5.265 -71.682 6.861 -71.576 8.499C-71.576 8.499 -67.308 13.618 -61.009 11.403z', + }, + { + fill: '#000000', + data: + 'M-65.4 11.546C-66.025 11.546 -66.531 10.406 -66.531 9C-66.531 7.595 -66.025 6.455 -65.4 6.455C-64.775 6.455 -64.268 7.595 -64.268 9C-64.268 10.406 -64.775 11.546 -65.4 11.546z', + }, + { fill: '#000000', data: 'M-65.4 9z' }, + { + fill: '#000000', + data: + 'M-111 109.601C-111 109.601 -116.6 119.601 -91.8 113.601C-91.8 113.601 -77.8 112.401 -75.4 110.001C-74.2 110.801 -65.834 113.734 -63 114.401C-56.2 116.001 -47.8 106 -47.8 106C-47.8 106 -43.2 95.5 -40.4 95.5C-37.6 95.5 -40.8 97.1 -40.8 97.1C-40.8 97.1 -47.4 107.201 -47 108.801C-47 108.801 -52.2 128.801 -68.2 129.601C-68.2 129.601 -84.35 130.551 -83 136.401C-83 136.401 -74.2 134.001 -71.8 136.401C-71.8 136.401 -61 136.001 -69 142.401L-75.8 154.001C-75.8 154.001 -75.66 157.919 -85.8 154.401C-95.6 151.001 -105.9 138.101 -105.9 138.101C-105.9 138.101 -121.85 123.551 -111 109.601z', + }, + { + fill: '#e59999', + data: + 'M-112.2 113.601C-112.2 113.601 -114.2 123.201 -77.4 112.801C-77.4 112.801 -73 112.801 -70.6 113.601C-68.2 114.401 -56.2 117.201 -54.2 116.001C-54.2 116.001 -61.4 129.601 -73 128.001C-73 128.001 -86.2 129.601 -85.8 134.401C-85.8 134.401 -81.8 141.601 -77 144.001C-77 144.001 -74.2 146.401 -74.6 149.601C-75 152.801 -77.8 154.401 -79.8 155.201C-81.8 156.001 -85 152.801 -86.6 152.801C-88.2 152.801 -96.6 146.401 -101 141.601C-105.4 136.801 -113.8 124.801 -113.4 122.001C-113 119.201 -112.2 113.601 -112.2 113.601z', + }, + { + fill: '#b26565', + data: + 'M-109 131.051C-106.4 135.001 -103.2 139.201 -101 141.601C-96.6 146.401 -88.2 152.801 -86.6 152.801C-85 152.801 -81.8 156.001 -79.8 155.201C-77.8 154.401 -75 152.801 -74.6 149.601C-74.2 146.401 -77 144.001 -77 144.001C-80.066 142.468 -82.806 138.976 -84.385 136.653C-84.385 136.653 -84.2 139.201 -89.4 138.401C-94.6 137.601 -99.8 134.801 -101.4 131.601C-103 128.401 -105.4 126.001 -103.8 129.601C-102.2 133.201 -99.8 136.801 -98.2 137.201C-96.6 137.601 -97 138.801 -99.4 138.401C-101.8 138.001 -104.6 137.601 -109 132.401z', + }, + { + fill: '#992600', + data: + 'M-111.6 110.001C-111.6 110.001 -109.8 96.4 -108.6 92.4C-108.6 92.4 -109.4 85.6 -107 81.4C-104.6 77.2 -102.6 71 -99.6 65.6C-96.6 60.2 -96.4 56.2 -92.4 54.6C-88.4 53 -82.4 44.4 -79.6 43.4C-76.8 42.4 -77 43.2 -77 43.2C-77 43.2 -70.2 28.4 -56.6 32.4C-56.6 32.4 -72.8 29.6 -57 20.2C-57 20.2 -61.8 21.3 -58.5 14.3C-56.299 9.632 -56.8 16.4 -67.8 28.2C-67.8 28.2 -72.8 36.8 -78 39.8C-83.2 42.8 -95.2 49.8 -96.4 53.6C-97.6 57.4 -100.8 63.2 -102.8 64.8C-104.8 66.4 -107.6 70.6 -108 74C-108 74 -109.2 78 -110.6 79.2C-112 80.4 -112.2 83.6 -112.2 85.6C-112.2 87.6 -114.2 90.4 -114 92.8C-114 92.8 -113.2 111.801 -113.6 113.801L-111.6 110.001z', + }, + { + fill: '#ffffff', + data: + 'M-120.2 114.601C-120.2 114.601 -122.2 113.201 -126.6 119.201C-126.6 119.201 -119.3 152.201 -119.3 153.601C-119.3 153.601 -118.2 151.501 -119.5 144.301C-120.8 137.101 -121.7 124.401 -121.7 124.401L-120.2 114.601z', + }, + { + fill: '#992600', + data: + 'M-98.6 54C-98.6 54 -116.2 57.2 -115.8 86.4L-116.6 111.201C-116.6 111.201 -117.8 85.6 -119 84C-120.2 82.4 -116.2 71.2 -119.4 77.2C-119.4 77.2 -133.4 91.2 -125.4 112.401C-125.4 112.401 -123.9 115.701 -126.9 111.101C-126.9 111.101 -131.5 98.5 -130.4 92.1C-130.4 92.1 -130.2 89.9 -128.3 87.1C-128.3 87.1 -119.7 75.4 -117 73.1C-117 73.1 -115.2 58.7 -99.8 53.5C-99.8 53.5 -94.1 51.2 -98.6 54z', + }, + { + fill: '#000000', + data: + 'M40.8 -12.2C41.46 -12.554 41.451 -13.524 42.031 -13.697C43.18 -14.041 43.344 -15.108 43.862 -15.892C44.735 -17.211 44.928 -18.744 45.51 -20.235C45.782 -20.935 45.809 -21.89 45.496 -22.55C44.322 -25.031 43.62 -27.48 42.178 -29.906C41.91 -30.356 41.648 -31.15 41.447 -31.748C40.984 -33.132 39.727 -34.123 38.867 -35.443C38.579 -35.884 39.104 -36.809 38.388 -36.893C37.491 -36.998 36.042 -37.578 35.809 -36.552C35.221 -33.965 36.232 -31.442 37.2 -29C36.418 -28.308 36.752 -27.387 36.904 -26.62C37.614 -23.014 36.416 -19.662 35.655 -16.188C35.632 -16.084 35.974 -15.886 35.946 -15.824C34.724 -13.138 33.272 -10.693 31.453 -8.312C30.695 -7.32 29.823 -6.404 29.326 -5.341C28.958 -4.554 28.55 -3.588 28.8 -2.6C25.365 0.18 23.115 4.025 20.504 7.871C20.042 8.551 20.333 9.76 20.884 10.029C21.697 10.427 22.653 9.403 23.123 8.557C23.512 7.859 23.865 7.209 24.356 6.566C24.489 6.391 24.31 5.972 24.445 5.851C27.078 3.504 28.747 0.568 31.2 -1.8C33.15 -2.129 34.687 -3.127 36.435 -4.14C36.743 -4.319 37.267 -4.07 37.557 -4.265C39.31 -5.442 39.308 -7.478 39.414 -9.388C39.464 -10.272 39.66 -11.589 40.8 -12.2z', + }, + { + fill: '#000000', + data: + 'M31.959 -16.666C32.083 -16.743 31.928 -17.166 32.037 -17.382C32.199 -17.706 32.602 -17.894 32.764 -18.218C32.873 -18.434 32.71 -18.814 32.846 -18.956C35.179 -21.403 35.436 -24.427 34.4 -27.4C35.424 -28.02 35.485 -29.282 35.06 -30.129C34.207 -31.829 34.014 -33.755 33.039 -35.298C32.237 -36.567 30.659 -37.811 29.288 -36.508C28.867 -36.108 28.546 -35.321 28.824 -34.609C28.888 -34.446 29.173 -34.3 29.146 -34.218C29.039 -33.894 28.493 -33.67 28.487 -33.398C28.457 -31.902 27.503 -30.391 28.133 -29.062C28.905 -27.433 29.724 -25.576 30.4 -23.8C29.166 -21.684 30.199 -19.235 28.446 -17.358C28.31 -17.212 28.319 -16.826 28.441 -16.624C28.733 -16.138 29.139 -15.732 29.625 -15.44C29.827 -15.319 30.175 -15.317 30.375 -15.441C30.953 -15.803 31.351 -16.29 31.959 -16.666z', + }, + { + fill: '#000000', + data: + 'M94.771 -26.977C96.16 -25.185 96.45 -22.39 94.401 -21C94.951 -17.691 98.302 -19.67 100.401 -20.2C100.292 -20.588 100.519 -20.932 100.802 -20.937C101.859 -20.952 102.539 -21.984 103.601 -21.8C104.035 -23.357 105.673 -24.059 106.317 -25.439C108.043 -29.134 107.452 -33.407 104.868 -36.653C104.666 -36.907 104.883 -37.424 104.759 -37.786C104.003 -39.997 101.935 -40.312 100.001 -41C98.824 -44.875 98.163 -48.906 96.401 -52.6C94.787 -52.85 94.089 -54.589 92.752 -55.309C91.419 -56.028 90.851 -54.449 90.892 -53.403C90.899 -53.198 91.351 -52.974 91.181 -52.609C91.105 -52.445 90.845 -52.334 90.845 -52.2C90.846 -52.065 91.067 -51.934 91.201 -51.8C90.283 -50.98 88.86 -50.503 88.565 -49.358C87.611 -45.648 90.184 -42.523 91.852 -39.322C92.443 -38.187 91.707 -36.916 90.947 -35.708C90.509 -35.013 90.617 -33.886 90.893 -33.03C91.645 -30.699 93.236 -28.96 94.771 -26.977z', + }, + { + fill: '#000000', + data: + 'M57.611 -8.591C56.124 -6.74 52.712 -4.171 55.629 -2.243C55.823 -2.114 56.193 -2.11 56.366 -2.244C58.387 -3.809 60.39 -4.712 62.826 -5.294C62.95 -5.323 63.224 -4.856 63.593 -5.017C65.206 -5.72 67.216 -5.662 68.4 -7C72.167 -6.776 75.732 -7.892 79.123 -9.2C80.284 -9.648 81.554 -10.207 82.755 -10.709C84.131 -11.285 85.335 -12.213 86.447 -13.354C86.58 -13.49 86.934 -13.4 87.201 -13.4C87.161 -14.263 88.123 -14.39 88.37 -15.012C88.462 -15.244 88.312 -15.64 88.445 -15.742C90.583 -17.372 91.503 -19.39 90.334 -21.767C90.049 -22.345 89.8 -22.963 89.234 -23.439C88.149 -24.35 87.047 -23.496 86 -23.8C85.841 -23.172 85.112 -23.344 84.726 -23.146C83.867 -22.707 82.534 -23.292 81.675 -22.854C80.313 -22.159 79.072 -21.99 77.65 -21.613C77.338 -21.531 76.56 -21.627 76.4 -21C76.266 -21.134 76.118 -21.368 76.012 -21.346C74.104 -20.95 72.844 -20.736 71.543 -19.044C71.44 -18.911 70.998 -19.09 70.839 -18.955C69.882 -18.147 69.477 -16.913 68.376 -16.241C68.175 -16.118 67.823 -16.286 67.629 -16.157C66.983 -15.726 66.616 -15.085 65.974 -14.638C65.645 -14.409 65.245 -14.734 65.277 -14.99C65.522 -16.937 66.175 -18.724 65.6 -20.6C67.677 -23.12 70.194 -25.069 72 -27.8C72.015 -29.966 72.707 -32.112 72.594 -34.189C72.584 -34.382 72.296 -35.115 72.17 -35.462C71.858 -36.316 72.764 -37.382 71.92 -38.106C70.516 -39.309 69.224 -38.433 68.4 -37C66.562 -36.61 64.496 -35.917 62.918 -37.151C61.911 -37.938 61.333 -38.844 60.534 -39.9C59.549 -41.202 59.884 -42.638 59.954 -44.202C59.96 -44.33 59.645 -44.466 59.645 -44.6C59.646 -44.735 59.866 -44.866 60 -45C59.294 -45.626 59.019 -46.684 58 -47C58.305 -48.092 57.629 -48.976 56.758 -49.278C54.763 -49.969 53.086 -48.057 51.194 -47.984C50.68 -47.965 50.213 -49.003 49.564 -49.328C49.132 -49.544 48.428 -49.577 48.066 -49.311C47.378 -48.807 46.789 -48.693 46.031 -48.488C44.414 -48.052 43.136 -46.958 41.656 -46.103C40.171 -45.246 39.216 -43.809 38.136 -42.489C37.195 -41.337 37.059 -38.923 38.479 -38.423C40.322 -37.773 41.626 -40.476 43.592 -40.15C43.904 -40.099 44.11 -39.788 44 -39.4C44.389 -39.291 44.607 -39.52 44.8 -39.8C45.658 -38.781 46.822 -38.444 47.76 -37.571C48.73 -36.667 50.476 -37.085 51.491 -36.088C53.02 -34.586 52.461 -31.905 54.4 -30.6C53.814 -29.287 53.207 -28.01 52.872 -26.583C52.59 -25.377 53.584 -24.18 54.795 -24.271C56.053 -24.365 56.315 -25.124 56.8 -26.2C57.067 -25.933 57.536 -25.636 57.495 -25.42C57.038 -23.033 56.011 -21.04 55.553 -18.609C55.494 -18.292 55.189 -18.09 54.8 -18.2C54.332 -14.051 50.28 -11.657 47.735 -8.492C47.332 -7.99 47.328 -6.741 47.737 -6.338C49.14 -4.951 51.1 -6.497 52.8 -7C53.013 -8.206 53.872 -9.148 55.204 -9.092C55.46 -9.082 55.695 -9.624 56.019 -9.754C56.367 -9.892 56.869 -9.668 57.155 -9.866C58.884 -11.061 60.292 -12.167 62.03 -13.356C62.222 -13.487 62.566 -13.328 62.782 -13.436C63.107 -13.598 63.294 -13.985 63.617 -14.17C63.965 -14.37 64.207 -14.08 64.4 -13.8C63.754 -13.451 63.75 -12.494 63.168 -12.292C62.393 -12.024 61.832 -11.511 61.158 -11.064C60.866 -10.871 60.207 -11.119 60.103 -10.94C59.505 -9.912 58.321 -9.474 57.611 -8.591z', + }, + { + fill: '#000000', + data: + 'M2.2 -58C2.2 -58 -7.038 -60.872 -18.2 -35.2C-18.2 -35.2 -20.6 -30 -23 -28C-25.4 -26 -36.6 -22.4 -38.6 -18.4L-49 -2.4C-49 -2.4 -34.2 -18.4 -31 -20.8C-31 -20.8 -23 -29.2 -26.2 -22.4C-26.2 -22.4 -40.2 -11.6 -39 -2.4C-39 -2.4 -44.6 12 -45.4 14C-45.4 14 -29.4 -18 -27 -19.2C-24.6 -20.4 -23.4 -20.4 -24.6 -16.8C-25.8 -13.2 -26.2 3.2 -29 5.2C-29 5.2 -21 -15.2 -21.8 -18.4C-21.8 -18.4 -18.6 -22 -16.2 -16.8L-17.4 -0.8L-13 11.2C-13 11.2 -15.4 0 -13.8 -15.6C-13.8 -15.6 -15.8 -26 -11.8 -20.4C-7.8 -14.8 1.8 -8.8 1.8 -4C1.8 -4 -3.4 -21.6 -12.6 -26.4L-16.6 -20.4L-17.8 -22.4C-17.8 -22.4 -21.4 -23.2 -17 -30C-12.6 -36.8 -13 -37.6 -13 -37.6C-13 -37.6 -6.6 -30.4 -5 -30.4C-5 -30.4 8.2 -38 9.4 -13.6C9.4 -13.6 16.2 -28 7 -34.8C7 -34.8 -7.8 -36.8 -6.6 -42L0.6 -54.4C4.2 -59.6 2.6 -56.8 2.6 -56.8z', + }, + { + fill: '#000000', + data: + 'M-17.8 -41.6C-17.8 -41.6 -30.6 -41.6 -33.8 -36.4L-41 -26.8C-41 -26.8 -23.8 -36.8 -19.8 -38C-15.8 -39.2 -17.8 -41.6 -17.8 -41.6z', + }, + { + fill: '#000000', + data: + 'M-57.8 -35.2C-57.8 -35.2 -59.8 -34 -60.2 -31.2C-60.6 -28.4 -63 -28 -62.2 -25.2C-61.4 -22.4 -59.4 -20 -59.4 -24C-59.4 -28 -57.8 -30 -57 -31.2C-56.2 -32.4 -54.6 -36.8 -57.8 -35.2z', + }, + { + fill: '#000000', + data: + 'M-66.6 26C-66.6 26 -75 22 -78.2 18.4C-81.4 14.8 -80.948 19.966 -85.8 19.6C-91.647 19.159 -90.6 3.2 -90.6 3.2L-94.6 10.8C-94.6 10.8 -95.8 25.2 -87.8 22.8C-83.893 21.628 -82.6 23.2 -84.2 24C-85.8 24.8 -78.6 25.2 -81.4 26.8C-84.2 28.4 -69.8 23.2 -72.2 33.6L-66.6 26z', + }, + { + fill: '#000000', + data: + 'M-79.2 40.4C-79.2 40.4 -94.6 44.8 -98.2 35.2C-98.2 35.2 -103 37.6 -100.8 40.6C-98.6 43.6 -97.4 44 -97.4 44C-97.4 44 -92 45.2 -92.6 46C-93.2 46.8 -95.6 50.2 -95.6 50.2C-95.6 50.2 -85.4 44.2 -79.2 40.4z', + }, + { + fill: '#ffffff', + data: + 'M149.201 118.601C148.774 120.735 147.103 121.536 145.201 122.201C143.284 121.243 140.686 118.137 138.801 120.201C138.327 119.721 137.548 119.661 137.204 118.999C136.739 118.101 137.011 117.055 136.669 116.257C136.124 114.985 135.415 113.619 135.601 112.201C137.407 111.489 138.002 109.583 137.528 107.82C137.459 107.563 137.03 107.366 137.23 107.017C137.416 106.694 137.734 106.467 138.001 106.2C137.866 106.335 137.721 106.568 137.61 106.548C137 106.442 137.124 105.805 137.254 105.418C137.839 103.672 139.853 103.408 141.201 104.6C141.457 104.035 141.966 104.229 142.401 104.2C142.351 103.621 142.759 103.094 142.957 102.674C143.475 101.576 145.104 102.682 145.901 102.07C146.977 101.245 148.04 100.546 149.118 101.149C150.927 102.162 152.636 103.374 153.835 105.115C154.41 105.949 154.65 107.23 154.592 108.188C154.554 108.835 153.173 108.483 152.83 109.412C152.185 111.16 154.016 111.679 154.772 113.017C154.97 113.366 154.706 113.67 154.391 113.768C153.98 113.896 153.196 113.707 153.334 114.16C154.306 117.353 151.55 118.031 149.201 118.601z', + }, + { + fill: '#ffffff', + data: + 'M139.6 138.201C139.593 136.463 137.992 134.707 139.201 133.001C139.336 133.135 139.467 133.356 139.601 133.356C139.736 133.356 139.867 133.135 140.001 133.001C141.496 135.217 145.148 136.145 145.006 138.991C144.984 139.438 143.897 140.356 144.801 141.001C142.988 142.349 142.933 144.719 142.001 146.601C140.763 146.315 139.551 145.952 138.401 145.401C138.753 143.915 138.636 142.231 139.456 140.911C139.89 140.213 139.603 139.134 139.6 138.201z', + }, + { + fill: '#cccccc', + data: + 'M-26.6 129.201C-26.6 129.201 -43.458 139.337 -29.4 124.001C-20.6 114.401 -10.6 108.801 -10.6 108.801C-10.6 108.801 -0.2 104.4 3.4 103.2C7 102 22.2 96.8 25.4 96.4C28.6 96 38.2 92 45 96C51.8 100 59.8 104.4 59.8 104.4C59.8 104.4 43.4 96 39.8 98.4C36.2 100.8 29 100.4 23 103.6C23 103.6 8.2 108.001 5 110.001C1.8 112.001 -8.6 123.601 -10.2 122.801C-11.8 122.001 -9.8 121.601 -8.6 118.801C-7.4 116.001 -9.4 114.401 -17.4 120.801C-25.4 127.201 -26.6 129.201 -26.6 129.201z', + }, + { + fill: '#000000', + data: + 'M-19.195 123.234C-19.195 123.234 -17.785 110.194 -9.307 111.859C-9.307 111.859 -1.081 107.689 1.641 105.721C1.641 105.721 9.78 104.019 11.09 103.402C29.569 94.702 44.288 99.221 44.835 98.101C45.381 96.982 65.006 104.099 68.615 108.185C69.006 108.628 58.384 102.588 48.686 100.697C40.413 99.083 18.811 100.944 7.905 106.48C4.932 107.989 -4.013 113.773 -6.544 113.662C-9.075 113.55 -19.195 123.234 -19.195 123.234z', + }, + { + fill: '#cccccc', + data: + 'M-23 148.801C-23 148.801 -38.2 146.401 -21.4 144.801C-21.4 144.801 -3.4 142.801 0.6 137.601C0.6 137.601 14.2 128.401 17 128.001C19.8 127.601 49.8 120.401 50.2 118.001C50.6 115.601 56.2 115.601 57.8 116.401C59.4 117.201 58.6 118.401 55.8 119.201C53 120.001 21.8 136.401 15.4 137.601C9 138.801 -2.6 146.401 -7.4 147.601C-12.2 148.801 -23 148.801 -23 148.801z', + }, + { + fill: '#000000', + data: + 'M-3.48 141.403C-3.48 141.403 -12.062 140.574 -3.461 139.755C-3.461 139.755 5.355 136.331 7.403 133.668C7.403 133.668 14.367 128.957 15.8 128.753C17.234 128.548 31.194 124.861 31.399 123.633C31.604 122.404 65.67 109.823 70.09 113.013C73.001 115.114 63.1 113.437 53.466 117.847C52.111 118.467 18.258 133.054 14.981 133.668C11.704 134.283 5.765 138.174 3.307 138.788C0.85 139.403 -3.48 141.403 -3.48 141.403z', + }, + { + fill: '#000000', + data: + 'M-11.4 143.601C-11.4 143.601 -6.2 143.201 -7.4 144.801C-8.6 146.401 -11 145.601 -11 145.601L-11.4 143.601z', + }, + { + fill: '#000000', + data: + 'M-18.6 145.201C-18.6 145.201 -13.4 144.801 -14.6 146.401C-15.8 148.001 -18.2 147.201 -18.2 147.201L-18.6 145.201z', + }, + { + fill: '#000000', + data: + 'M-29 146.801C-29 146.801 -23.8 146.401 -25 148.001C-26.2 149.601 -28.6 148.801 -28.6 148.801L-29 146.801z', + }, + { + fill: '#000000', + data: + 'M-36.6 147.601C-36.6 147.601 -31.4 147.201 -32.6 148.801C-33.8 150.401 -36.2 149.601 -36.2 149.601L-36.6 147.601z', + }, + { + fill: '#000000', + data: + 'M1.8 108.001C1.8 108.001 6.2 108.001 5 109.601C3.8 111.201 0.6 110.801 0.6 110.801L1.8 108.001z', + }, + { + fill: '#000000', + data: + 'M-8.2 113.601C-8.2 113.601 -1.694 111.46 -4.2 114.801C-5.4 116.401 -7.8 115.601 -7.8 115.601L-8.2 113.601z', + }, + { + fill: '#000000', + data: + 'M-19.4 118.401C-19.4 118.401 -14.2 118.001 -15.4 119.601C-16.6 121.201 -19 120.401 -19 120.401L-19.4 118.401z', + }, + { + fill: '#000000', + data: + 'M-27 124.401C-27 124.401 -21.8 124.001 -23 125.601C-24.2 127.201 -26.6 126.401 -26.6 126.401L-27 124.401z', + }, + { + fill: '#000000', + data: + 'M-33.8 129.201C-33.8 129.201 -28.6 128.801 -29.8 130.401C-31 132.001 -33.4 131.201 -33.4 131.201L-33.8 129.201z', + }, + { + fill: '#000000', + data: + 'M5.282 135.598C5.282 135.598 12.203 135.066 10.606 137.195C9.009 139.325 5.814 138.26 5.814 138.26L5.282 135.598z', + }, + { + fill: '#000000', + data: + 'M15.682 130.798C15.682 130.798 22.603 130.266 21.006 132.395C19.409 134.525 16.214 133.46 16.214 133.46L15.682 130.798z', + }, + { + fill: '#000000', + data: + 'M26.482 126.398C26.482 126.398 33.403 125.866 31.806 127.995C30.209 130.125 27.014 129.06 27.014 129.06L26.482 126.398z', + }, + { + fill: '#000000', + data: + 'M36.882 121.598C36.882 121.598 43.803 121.066 42.206 123.195C40.609 125.325 37.414 124.26 37.414 124.26L36.882 121.598z', + }, + { + fill: '#000000', + data: + 'M9.282 103.598C9.282 103.598 16.203 103.066 14.606 105.195C13.009 107.325 9.014 107.06 9.014 107.06L9.282 103.598z', + }, + { + fill: '#000000', + data: + 'M19.282 100.398C19.282 100.398 26.203 99.866 24.606 101.995C23.009 104.125 18.614 103.86 18.614 103.86L19.282 100.398z', + }, + { + fill: '#000000', + data: + 'M-3.4 140.401C-3.4 140.401 1.8 140.001 0.6 141.601C-0.6 143.201 -3 142.401 -3 142.401L-3.4 140.401z', + }, + { + fill: '#992600', + data: + 'M-76.6 41.2C-76.6 41.2 -81 50 -81.4 53.2C-81.4 53.2 -80.6 44.4 -79.4 42.4C-78.2 40.4 -76.6 41.2 -76.6 41.2z', + }, + { + fill: '#992600', + data: + 'M-95 55.2C-95 55.2 -98.2 69.6 -97.8 72.4C-97.8 72.4 -99 60.8 -98.6 59.6C-98.2 58.4 -95 55.2 -95 55.2z', + }, + { + fill: '#cccccc', + data: + 'M-74.2 -19.4L-74.4 -16.2L-76.6 -16C-76.6 -16 -62.4 -3.4 -61.8 4.2C-61.8 4.2 -61 -4 -74.2 -19.4z', + }, + { + fill: '#000000', + data: + 'M-70.216 -18.135C-70.647 -18.551 -70.428 -19.296 -70.836 -19.556C-71.645 -20.072 -69.538 -20.129 -69.766 -20.845C-70.149 -22.051 -69.962 -22.072 -70.084 -23.348C-70.141 -23.946 -69.553 -25.486 -69.168 -25.926C-67.722 -27.578 -69.046 -30.51 -67.406 -32.061C-67.102 -32.35 -66.726 -32.902 -66.441 -33.32C-65.782 -34.283 -64.598 -34.771 -63.648 -35.599C-63.33 -35.875 -63.531 -36.702 -62.962 -36.61C-62.248 -36.495 -61.007 -36.625 -61.052 -35.784C-61.165 -33.664 -62.494 -31.944 -63.774 -30.276C-63.323 -29.572 -63.781 -28.937 -64.065 -28.38C-65.4 -25.76 -65.211 -22.919 -65.385 -20.079C-65.39 -19.994 -65.697 -19.916 -65.689 -19.863C-65.336 -17.528 -64.752 -15.329 -63.873 -13.1C-63.507 -12.17 -63.036 -11.275 -62.886 -10.348C-62.775 -9.662 -62.672 -8.829 -63.08 -8.124C-61.045 -5.234 -62.354 -2.583 -61.185 0.948C-60.978 1.573 -59.286 3.487 -59.749 3.326C-62.262 2.455 -62.374 2.057 -62.551 1.304C-62.697 0.681 -63.027 -0.696 -63.264 -1.298C-63.328 -1.462 -63.499 -3.346 -63.577 -3.468C-65.09 -5.85 -63.732 -5.674 -65.102 -8.032C-66.53 -8.712 -67.496 -9.816 -68.619 -10.978C-68.817 -11.182 -67.674 -11.906 -67.855 -12.119C-68.947 -13.408 -70.1 -14.175 -69.764 -15.668C-69.609 -16.358 -69.472 -17.415 -70.216 -18.135z', + }, + { + fill: '#000000', + data: + 'M-73.8 -16.4C-73.8 -16.4 -73.4 -9.6 -71 -8C-68.6 -6.4 -69.8 -7.2 -73 -8.4C-76.2 -9.6 -75 -10.4 -75 -10.4C-75 -10.4 -77.8 -10 -75.4 -8C-73 -6 -69.4 -3.6 -71 -3.6C-72.6 -3.6 -80.2 -7.6 -80.2 -10.4C-80.2 -13.2 -81.2 -17.3 -81.2 -17.3C-81.2 -17.3 -80.1 -18.1 -75.3 -18C-75.3 -18 -73.9 -17.3 -73.8 -16.4z', + }, + { + fill: '#ffffff', + stroke: '#000000', + strokeWidth: 0.1, + data: + 'M-74.6 2.2C-74.6 2.2 -83.12 -0.591 -101.6 2.8C-101.6 2.8 -92.569 0.722 -73.8 3C-63.5 4.25 -74.6 2.2 -74.6 2.2z', + }, + { + fill: '#ffffff', + stroke: '#000000', + strokeWidth: 0.1, + data: + 'M-72.502 2.129C-72.502 2.129 -80.748 -1.389 -99.453 0.392C-99.453 0.392 -90.275 -0.897 -71.774 2.995C-61.62 5.131 -72.502 2.129 -72.502 2.129z', + }, + { + fill: '#ffffff', + stroke: '#000000', + strokeWidth: 0.1, + data: + 'M-70.714 2.222C-70.714 2.222 -78.676 -1.899 -97.461 -1.514C-97.461 -1.514 -88.213 -2.118 -70.052 3.14C-60.086 6.025 -70.714 2.222 -70.714 2.222z', + }, + { + fill: '#ffffff', + stroke: '#000000', + strokeWidth: 0.1, + data: + 'M-69.444 2.445C-69.444 2.445 -76.268 -1.862 -93.142 -2.96C-93.142 -2.96 -84.803 -2.79 -68.922 3.319C-60.206 6.672 -69.444 2.445 -69.444 2.445z', + }, + { + fill: '#ffffff', + stroke: '#000000', + strokeWidth: 0.1, + data: + 'M45.84 12.961C45.84 12.961 44.91 13.605 45.124 12.424C45.339 11.243 73.547 -1.927 77.161 -1.677C77.161 -1.677 46.913 11.529 45.84 12.961z', + }, + { + fill: '#ffffff', + stroke: '#000000', + strokeWidth: 0.1, + data: + 'M42.446 13.6C42.446 13.6 41.57 14.315 41.691 13.121C41.812 11.927 68.899 -3.418 72.521 -3.452C72.521 -3.452 43.404 12.089 42.446 13.6z', + }, + { + fill: '#ffffff', + stroke: '#000000', + strokeWidth: 0.1, + data: + 'M39.16 14.975C39.16 14.975 38.332 15.747 38.374 14.547C38.416 13.348 58.233 -2.149 68.045 -4.023C68.045 -4.023 50.015 4.104 39.16 14.975z', + }, + { + fill: '#ffffff', + stroke: '#000000', + strokeWidth: 0.1, + data: + 'M36.284 16.838C36.284 16.838 35.539 17.532 35.577 16.453C35.615 15.373 53.449 1.426 62.28 -0.26C62.28 -0.26 46.054 7.054 36.284 16.838z', + }, + { + fill: '#cccccc', + data: + 'M4.6 164.801C4.6 164.801 -10.6 162.401 6.2 160.801C6.2 160.801 24.2 158.801 28.2 153.601C28.2 153.601 41.8 144.401 44.6 144.001C47.4 143.601 63.8 140.001 64.2 137.601C64.6 135.201 70.6 132.801 72.2 133.601C73.8 134.401 73.8 143.601 71 144.401C68.2 145.201 49.4 152.401 43 153.601C36.6 154.801 25 162.401 20.2 163.601C15.4 164.801 4.6 164.801 4.6 164.801z', + }, + { + fill: '#000000', + data: + 'M77.6 127.401C77.6 127.401 74.6 129.001 73.4 131.601C73.4 131.601 67 142.201 52.8 145.401C52.8 145.401 29.8 154.401 22 156.401C22 156.401 8.6 161.401 1.2 160.601C1.2 160.601 -5.8 160.801 0.4 162.401C0.4 162.401 20.6 160.401 24 158.601C24 158.601 39.6 153.401 42.6 150.801C45.6 148.201 63.8 143.201 66 141.201C68.2 139.201 78 130.801 77.6 127.401z', + }, + { + fill: '#000000', + data: + 'M18.882 158.911C18.882 158.911 24.111 158.685 22.958 160.234C21.805 161.784 19.357 160.91 19.357 160.91L18.882 158.911z', + }, + { + fill: '#000000', + data: + 'M11.68 160.263C11.68 160.263 16.908 160.037 15.756 161.586C14.603 163.136 12.155 162.263 12.155 162.263L11.68 160.263z', + }, + { + fill: '#000000', + data: + 'M1.251 161.511C1.251 161.511 6.48 161.284 5.327 162.834C4.174 164.383 1.726 163.51 1.726 163.51L1.251 161.511z', + }, + { + fill: '#000000', + data: + 'M-6.383 162.055C-6.383 162.055 -1.154 161.829 -2.307 163.378C-3.46 164.928 -5.908 164.054 -5.908 164.054L-6.383 162.055z', + }, + { + fill: '#000000', + data: + 'M35.415 151.513C35.415 151.513 42.375 151.212 40.84 153.274C39.306 155.336 36.047 154.174 36.047 154.174L35.415 151.513z', + }, + { + fill: '#000000', + data: + 'M45.73 147.088C45.73 147.088 51.689 143.787 51.155 148.849C50.885 151.405 46.362 149.749 46.362 149.749L45.73 147.088z', + }, + { + fill: '#000000', + data: + 'M54.862 144.274C54.862 144.274 62.021 140.573 60.287 146.035C59.509 148.485 55.493 146.935 55.493 146.935L54.862 144.274z', + }, + { + fill: '#000000', + data: + 'M64.376 139.449C64.376 139.449 68.735 134.548 69.801 141.21C70.207 143.748 65.008 142.11 65.008 142.11L64.376 139.449z', + }, + { + fill: '#000000', + data: + 'M26.834 155.997C26.834 155.997 32.062 155.77 30.91 157.32C29.757 158.869 27.308 157.996 27.308 157.996L26.834 155.997z', + }, + { + fill: '#ffffff', + stroke: '#000000', + strokeWidth: 0.1, + data: + 'M62.434 34.603C62.434 34.603 61.708 35.268 61.707 34.197C61.707 33.127 79.191 19.863 88.034 18.479C88.034 18.479 71.935 25.208 62.434 34.603z', + }, + { + fill: '#000000', + data: + 'M65.4 98.4C65.4 98.4 87.401 120.801 96.601 124.401C96.601 124.401 105.801 135.601 101.801 161.601C101.801 161.601 98.601 169.201 95.401 148.401C95.401 148.401 98.601 123.201 87.401 139.201C87.401 139.201 79 129.301 85.4 129.601C85.4 129.601 88.601 131.601 89.001 130.001C89.401 128.401 81.4 114.801 64.2 100.4C47 86 65.4 98.4 65.4 98.4z', + }, + { + fill: '#ffffff', + stroke: '#000000', + strokeWidth: 0.1, + data: + 'M7 137.201C7 137.201 6.8 135.401 8.6 136.201C10.4 137.001 104.601 143.201 136.201 167.201C136.201 167.201 91.001 144.001 7 137.201z', + }, + { + fill: '#ffffff', + stroke: '#000000', + strokeWidth: 0.1, + data: + 'M17.4 132.801C17.4 132.801 17.2 131.001 19 131.801C20.8 132.601 157.401 131.601 181.001 164.001C181.001 164.001 159.001 138.801 17.4 132.801z', + }, + { + fill: '#ffffff', + stroke: '#000000', + strokeWidth: 0.1, + data: + 'M29 128.801C29 128.801 28.8 127.001 30.6 127.801C32.4 128.601 205.801 115.601 229.401 148.001C229.401 148.001 219.801 122.401 29 128.801z', + }, + { + fill: '#ffffff', + stroke: '#000000', + strokeWidth: 0.1, + data: + 'M39 124.001C39 124.001 38.8 122.201 40.6 123.001C42.4 123.801 164.601 85.2 188.201 117.601C188.201 117.601 174.801 93 39 124.001z', + }, + { + fill: '#ffffff', + stroke: '#000000', + strokeWidth: 0.1, + data: + 'M-19 146.801C-19 146.801 -19.2 145.001 -17.4 145.801C-15.6 146.601 2.2 148.801 4.2 187.601C4.2 187.601 -3 145.601 -19 146.801z', + }, + { + fill: '#ffffff', + stroke: '#000000', + strokeWidth: 0.1, + data: + 'M-27.8 148.401C-27.8 148.401 -28 146.601 -26.2 147.401C-24.4 148.201 -10.2 143.601 -13 182.401C-13 182.401 -11.8 147.201 -27.8 148.401z', + }, + { + fill: '#ffffff', + stroke: '#000000', + strokeWidth: 0.1, + data: + 'M-35.8 148.801C-35.8 148.801 -36 147.001 -34.2 147.801C-32.4 148.601 -17 149.201 -29.4 171.601C-29.4 171.601 -19.8 147.601 -35.8 148.801z', + }, + { + fill: '#ffffff', + stroke: '#000000', + strokeWidth: 0.1, + data: + 'M11.526 104.465C11.526 104.465 11.082 106.464 12.631 105.247C28.699 92.622 61.141 33.72 116.826 28.086C116.826 28.086 78.518 15.976 11.526 104.465z', + }, + { + fill: '#ffffff', + stroke: '#000000', + strokeWidth: 0.1, + data: + 'M22.726 102.665C22.726 102.665 21.363 101.472 23.231 100.847C25.099 100.222 137.541 27.72 176.826 35.686C176.826 35.686 149.719 28.176 22.726 102.665z', + }, + { + fill: '#ffffff', + stroke: '#000000', + strokeWidth: 0.1, + data: + 'M1.885 108.767C1.885 108.767 1.376 110.366 3.087 109.39C12.062 104.27 15.677 47.059 59.254 45.804C59.254 45.804 26.843 31.09 1.885 108.767z', + }, + { + fill: '#ffffff', + stroke: '#000000', + strokeWidth: 0.1, + data: + 'M-18.038 119.793C-18.038 119.793 -19.115 121.079 -17.162 120.825C-6.916 119.493 14.489 78.222 58.928 83.301C58.928 83.301 26.962 68.955 -18.038 119.793z', + }, + { + fill: '#ffffff', + stroke: '#000000', + strokeWidth: 0.1, + data: + 'M-6.8 113.667C-6.8 113.667 -7.611 115.136 -5.742 114.511C4.057 111.237 17.141 66.625 61.729 63.078C61.729 63.078 27.603 55.135 -6.8 113.667z', + }, + { + fill: '#ffffff', + stroke: '#000000', + strokeWidth: 0.1, + data: + 'M-25.078 124.912C-25.078 124.912 -25.951 125.954 -24.369 125.748C-16.07 124.669 1.268 91.24 37.264 95.354C37.264 95.354 11.371 83.734 -25.078 124.912z', + }, + { + fill: '#ffffff', + stroke: '#000000', + strokeWidth: 0.1, + data: + 'M-32.677 130.821C-32.677 130.821 -33.682 131.866 -32.091 131.748C-27.923 131.439 2.715 98.36 21.183 113.862C21.183 113.862 9.168 95.139 -32.677 130.821z', + }, + { + fill: '#ffffff', + stroke: '#000000', + strokeWidth: 0.1, + data: + 'M36.855 98.898C36.855 98.898 35.654 97.543 37.586 97.158C39.518 96.774 160.221 39.061 198.184 51.927C198.184 51.927 172.243 41.053 36.855 98.898z', + }, + { + fill: '#ffffff', + stroke: '#000000', + strokeWidth: 0.1, + data: + 'M3.4 163.201C3.4 163.201 3.2 161.401 5 162.201C6.8 163.001 22.2 163.601 9.8 186.001C9.8 186.001 19.4 162.001 3.4 163.201z', + }, + { + fill: '#ffffff', + stroke: '#000000', + strokeWidth: 0.1, + data: + 'M13.8 161.601C13.8 161.601 13.6 159.801 15.4 160.601C17.2 161.401 35 163.601 37 202.401C37 202.401 29.8 160.401 13.8 161.601z', + }, + { + fill: '#ffffff', + stroke: '#000000', + strokeWidth: 0.1, + data: + 'M20.6 160.001C20.6 160.001 20.4 158.201 22.2 159.001C24 159.801 48.6 163.201 72.2 195.601C72.2 195.601 36.6 158.801 20.6 160.001z', + }, + { + fill: '#ffffff', + stroke: '#000000', + strokeWidth: 0.1, + data: + 'M28.225 157.972C28.225 157.972 27.788 156.214 29.678 156.768C31.568 157.322 52.002 155.423 90.099 189.599C90.099 189.599 43.924 154.656 28.225 157.972z', + }, + { + fill: '#ffffff', + stroke: '#000000', + strokeWidth: 0.1, + data: + 'M38.625 153.572C38.625 153.572 38.188 151.814 40.078 152.368C41.968 152.922 76.802 157.423 128.499 192.399C128.499 192.399 54.324 150.256 38.625 153.572z', + }, + { + fill: '#ffffff', + stroke: '#000000', + strokeWidth: 0.1, + data: + 'M-1.8 142.001C-1.8 142.001 -2 140.201 -0.2 141.001C1.6 141.801 55 144.401 85.4 171.201C85.4 171.201 50.499 146.426 -1.8 142.001z', + }, + { + fill: '#ffffff', + stroke: '#000000', + strokeWidth: 0.1, + data: + 'M-11.8 146.001C-11.8 146.001 -12 144.201 -10.2 145.001C-8.4 145.801 16.2 149.201 39.8 181.601C39.8 181.601 4.2 144.801 -11.8 146.001z', + }, + { + fill: '#ffffff', + stroke: '#000000', + strokeWidth: 0.1, + data: + 'M49.503 148.962C49.503 148.962 48.938 147.241 50.864 147.655C52.79 148.068 87.86 150.004 141.981 181.098C141.981 181.098 64.317 146.704 49.503 148.962z', + }, + { + fill: '#ffffff', + stroke: '#000000', + strokeWidth: 0.1, + data: + 'M57.903 146.562C57.903 146.562 57.338 144.841 59.264 145.255C61.19 145.668 96.26 147.604 150.381 178.698C150.381 178.698 73.317 143.904 57.903 146.562z', + }, + { + fill: '#ffffff', + stroke: '#000000', + strokeWidth: 0.1, + data: + 'M67.503 141.562C67.503 141.562 66.938 139.841 68.864 140.255C70.79 140.668 113.86 145.004 203.582 179.298C203.582 179.298 82.917 138.904 67.503 141.562z', + }, + { + fill: '#000000', + data: + 'M-43.8 148.401C-43.8 148.401 -38.6 148.001 -39.8 149.601C-41 151.201 -43.4 150.401 -43.4 150.401L-43.8 148.401z', + }, + { + fill: '#000000', + data: + 'M-13 162.401C-13 162.401 -7.8 162.001 -9 163.601C-10.2 165.201 -12.6 164.401 -12.6 164.401L-13 162.401z', + }, + { + fill: '#000000', + data: + 'M-21.8 162.001C-21.8 162.001 -16.6 161.601 -17.8 163.201C-19 164.801 -21.4 164.001 -21.4 164.001L-21.8 162.001z', + }, + { + fill: '#000000', + data: + 'M-117.169 150.182C-117.169 150.182 -112.124 151.505 -113.782 152.624C-115.439 153.744 -117.446 152.202 -117.446 152.202L-117.169 150.182z', + }, + { + fill: '#000000', + data: + 'M-115.169 140.582C-115.169 140.582 -110.124 141.905 -111.782 143.024C-113.439 144.144 -115.446 142.602 -115.446 142.602L-115.169 140.582z', + }, + { + fill: '#000000', + data: + 'M-122.369 136.182C-122.369 136.182 -117.324 137.505 -118.982 138.624C-120.639 139.744 -122.646 138.202 -122.646 138.202L-122.369 136.182z', + }, + { + fill: '#cccccc', + data: + 'M-42.6 211.201C-42.6 211.201 -44.2 211.201 -48.2 213.201C-50.2 213.201 -61.4 216.801 -67 226.801C-67 226.801 -54.6 217.201 -42.6 211.201z', + }, + { + fill: '#cccccc', + data: + 'M45.116 303.847C45.257 304.105 45.312 304.525 45.604 304.542C46.262 304.582 47.495 304.883 47.37 304.247C46.522 299.941 45.648 295.004 41.515 293.197C40.876 292.918 39.434 293.331 39.36 294.215C39.233 295.739 39.116 297.088 39.425 298.554C39.725 299.975 41.883 299.985 42.8 298.601C43.736 300.273 44.168 302.116 45.116 303.847z', + }, + { + fill: '#cccccc', + data: + 'M34.038 308.581C34.786 309.994 34.659 311.853 36.074 312.416C36.814 312.71 38.664 311.735 38.246 310.661C37.444 308.6 37.056 306.361 35.667 304.55C35.467 304.288 35.707 303.755 35.547 303.427C34.953 302.207 33.808 301.472 32.4 301.801C31.285 304.004 32.433 306.133 33.955 307.842C34.091 307.994 33.925 308.37 34.038 308.581z', + }, + { + fill: '#cccccc', + data: + 'M-5.564 303.391C-5.672 303.014 -5.71 302.551 -5.545 302.23C-5.014 301.197 -4.221 300.075 -4.558 299.053C-4.906 297.997 -6.022 298.179 -6.672 298.748C-7.807 299.742 -7.856 301.568 -8.547 302.927C-8.743 303.313 -8.692 303.886 -9.133 304.277C-9.607 304.698 -10.047 306.222 -9.951 306.793C-9.898 307.106 -10.081 317.014 -9.859 316.751C-9.24 316.018 -6.19 306.284 -6.121 305.392C-6.064 304.661 -5.332 304.196 -5.564 303.391z', + }, + { + fill: '#cccccc', + data: + 'M-31.202 296.599C-28.568 294.1 -25.778 291.139 -26.22 287.427C-26.336 286.451 -28.111 286.978 -28.298 287.824C-29.1 291.449 -31.139 294.11 -33.707 296.502C-35.903 298.549 -37.765 304.893 -38 305.401C-34.303 300.145 -32.046 297.399 -31.202 296.599z', + }, + { + fill: '#cccccc', + data: + 'M-44.776 290.635C-44.253 290.265 -44.555 289.774 -44.338 289.442C-43.385 287.984 -42.084 286.738 -42.066 285C-42.063 284.723 -42.441 284.414 -42.776 284.638C-43.053 284.822 -43.395 284.952 -43.503 285.082C-45.533 287.531 -46.933 290.202 -48.376 293.014C-48.559 293.371 -49.703 297.862 -49.39 297.973C-49.151 298.058 -47.431 293.877 -47.221 293.763C-45.958 293.077 -45.946 291.462 -44.776 290.635z', + }, + { + fill: '#cccccc', + data: + 'M-28.043 310.179C-27.599 309.31 -26.023 308.108 -26.136 307.219C-26.254 306.291 -25.786 304.848 -26.698 305.536C-27.955 306.484 -31.404 307.833 -31.674 313.641C-31.7 314.212 -28.726 311.519 -28.043 310.179z', + }, + { + fill: '#cccccc', + data: + 'M-13.6 293.001C-13.2 292.333 -12.492 292.806 -12.033 292.543C-11.385 292.171 -10.774 291.613 -10.482 290.964C-9.512 288.815 -7.743 286.995 -7.6 284.601C-9.091 283.196 -9.77 285.236 -10.4 286.201C-11.723 284.554 -12.722 286.428 -14.022 286.947C-14.092 286.975 -14.305 286.628 -14.38 286.655C-15.557 287.095 -16.237 288.176 -17.235 288.957C-17.406 289.091 -17.811 288.911 -17.958 289.047C-18.61 289.65 -19.583 289.975 -19.863 290.657C-20.973 293.364 -24.113 295.459 -26 303.001C-25.619 303.91 -21.488 296.359 -21.001 295.661C-20.165 294.465 -20.047 297.322 -18.771 296.656C-18.72 296.629 -18.534 296.867 -18.4 297.001C-18.206 296.721 -17.988 296.492 -17.6 296.601C-17.6 296.201 -17.734 295.645 -17.533 295.486C-16.296 294.509 -16.38 293.441 -15.6 292.201C-15.142 292.99 -14.081 292.271 -13.6 293.001z', + }, + { + fill: '#cccccc', + data: + 'M46.2 347.401C46.2 347.401 53.6 327.001 49.2 315.801C49.2 315.801 60.6 337.401 56 348.601C56 348.601 55.6 338.201 51.6 333.201C51.6 333.201 47.6 346.001 46.2 347.401z', + }, + { + fill: '#cccccc', + data: + 'M31.4 344.801C31.4 344.801 36.8 336.001 28.8 317.601C28.8 317.601 28 338.001 21.2 349.001C21.2 349.001 35.4 328.801 31.4 344.801z', + }, + { + fill: '#cccccc', + data: + 'M21.4 342.801C21.4 342.801 21.2 322.801 21.6 319.801C21.6 319.801 17.8 336.401 7.6 346.001C7.6 346.001 22 334.001 21.4 342.801z', + }, + { + fill: '#cccccc', + data: + 'M11.8 310.801C11.8 310.801 17.8 324.401 7.8 342.801C7.8 342.801 14.2 330.601 9.4 323.601C9.4 323.601 12 320.201 11.8 310.801z', + }, + { + fill: '#cccccc', + data: + 'M-7.4 342.401C-7.4 342.401 -8.4 326.801 -6.6 324.601C-6.6 324.601 -6.4 318.201 -6.8 317.201C-6.8 317.201 -2.8 311.001 -2.6 318.401C-2.6 318.401 -1.2 326.201 1.6 330.801C1.6 330.801 5.2 336.201 5 342.601C5 342.601 -5 312.401 -7.4 342.401z', + }, + { + fill: '#cccccc', + data: + 'M-11 314.801C-11 314.801 -17.6 325.601 -19.4 344.601C-19.4 344.601 -20.8 338.401 -17 324.001C-17 324.001 -12.8 308.601 -11 314.801z', + }, + { + fill: '#cccccc', + data: + 'M-32.8 334.601C-32.8 334.601 -27.8 329.201 -26.4 324.201C-26.4 324.201 -22.8 308.401 -29.2 317.001C-29.2 317.001 -29 325.001 -37.2 332.401C-37.2 332.401 -32.4 330.001 -32.8 334.601z', + }, + { + fill: '#cccccc', + data: + 'M-38.6 329.601C-38.6 329.601 -35.2 312.201 -34.4 311.401C-34.4 311.401 -32.6 308.001 -35.4 311.201C-35.4 311.201 -44.2 330.401 -48.2 337.001C-48.2 337.001 -40.2 327.801 -38.6 329.601z', + }, + { + fill: '#cccccc', + data: + 'M-44.4 313.001C-44.4 313.001 -32.8 290.601 -54.6 316.401C-54.6 316.401 -43.6 306.601 -44.4 313.001z', + }, + { + fill: '#cccccc', + data: + 'M-59.8 298.401C-59.8 298.401 -55 279.601 -52.4 279.801C-52.4 279.801 -44.2 270.801 -50.8 281.401C-50.8 281.401 -56.8 291.001 -56.2 300.801C-56.2 300.801 -56.8 291.201 -59.8 298.401z', + }, + { + fill: '#cccccc', + data: + 'M270.5 287C270.5 287 258.5 277 256 273.5C256 273.5 269.5 292 269.5 299C269.5 299 272 291.5 270.5 287z', + }, + { + fill: '#cccccc', + data: + 'M276 265C276 265 255 250 251.5 242.5C251.5 242.5 278 272 278 276.5C278 276.5 278.5 267.5 276 265z', + }, + { + fill: '#cccccc', + data: + 'M293 111C293 111 281 103 279.5 105C279.5 105 290 111.5 292.5 120C292.5 120 291 111 293 111z', + }, + { + fill: '#cccccc', + data: 'M301.5 191.5L284 179.5C284 179.5 303 196.5 303.5 200.5L301.5 191.5z', + }, + { stroke: '#000000', data: 'M-89.25 169L-67.25 173.75' }, + { stroke: '#000000', data: 'M-39 331C-39 331 -39.5 327.5 -48.5 338' }, + { stroke: '#000000', data: 'M-33.5 336C-33.5 336 -31.5 329.5 -38 334' }, + { stroke: '#000000', data: 'M20.5 344.5C20.5 344.5 22 333.5 10.5 346.5' }, +]; diff --git a/test/assets/worldMap.ts b/test/assets/worldMap.ts new file mode 100644 index 000000000..2efc4ce89 --- /dev/null +++ b/test/assets/worldMap.ts @@ -0,0 +1,346 @@ +export default { + shapes: { + AE: + 'M604.196,161.643l0.514-0.129l0,0.772l2.188-0.386l2.189,0l1.672,0.129l1.803-1.802l2.058-1.802l1.674-1.673l0.518,0.900l0.385,2.189l-1.417,0l-0.258,1.802l0.517,0.386l-1.159,0.515l-0.129,1.029l-0.773,1.159l0,1.030l-0.514,0.644l-8.110-1.416l-1.031-2.704l0.127,0.643z', + AF: + 'M630.069,130.876l2.832,1.030l2.059-0.257l0.517-1.288l2.058-0.386l1.546-0.772l0.515-2.188l2.317-0.516l0.387-1.030l1.285,0.774l0.902,0.128l1.416,0l2.059,0.515l0.773,0.385l2.059-0.900l0.901,0.515l0.773-1.287l1.674,0.128l0.386-0.387l0.256-1.157l1.160-0.903l1.543,0.645l-0.384,0.772l0.901,0.129l-0.259,2.317l1.030,0.900l0.904-0.643l1.285-0.257l1.674-1.159l1.802,0.129l2.832,0l0.387,0.773l-1.545,0.385l-1.416,0.516l-3.090,0.256l-2.833,0.517l-1.545,1.287l0.645,1.029l0.257,1.416l-1.287,1.159l0.129,1.029l-0.773,0.902l-2.575,0l1.030,1.673l-1.673,0.772l-1.158,1.545l0.129,1.674l-1.031,0.772l-1.029-0.257l-2.061,0.386l-0.257,0.644l-2.058,0l-1.417,1.544l-0.129,2.317l-3.476,1.159l-1.931-0.257l-0.514,0.643l-1.674-0.386l-2.704,0.386l-4.504-1.415l2.445-2.447l-0.129-1.673l-2.060-0.515l-0.256-1.674l-0.902-2.188l1.158-1.416l-1.158-0.386l0.773-1.930l-1.029,3.477z', + AL: + 'M520.651,114.27l-0.257,0.900l0.385,1.160l1.029,0.643l0,0.644l-0.901,0.386l-0.128,0.901l-1.288,1.287l-0.386-0.128l-0.127-0.644l-1.417-0.900l-0.259-1.288l0.259-1.803l0.256-0.901l-0.384-0.386l-0.258-0.901l1.287-1.288l0.129,0.516l0.771-0.258l0.516,0.773l0.643,0.257l-0.130-1.030z', + AM: + 'M582.697,116.33l3.605,-0.515l0.642,0.772l1.032,0.386l-0.516,0.773l1.416,0.900l-0.772,0.902l1.159,0.643l1.158,0.516l0.129,1.801l-1.029,0.129l-1.032,-1.544l0,-0.515l-1.287,0.129l-0.771,-0.772l-0.516,0l-1.029,-0.773l-2.059,-0.643l0.256,-1.288l0.386,0.901z', + AO: + 'M497.994,242.615l-0.643-2.060l1.030-1.159l0.900-0.515l0.902,1.031l-0.902,0.516l-0.514,0.642l0,1.159l0.773-0.386zM496.836,273.64l-0.257-1.804l0.385-2.317l0.900-2.445l0.130-1.158l0.901-2.447l0.643-1.157l1.545-1.674l0.902-1.288l0.257-1.931l-0.129-1.544l-0.771-0.902l-0.775-1.673l-0.642-1.674l0.129-0.515l0.772-1.029l-0.772-2.704l-0.516-1.802l-1.414-1.674l0.257-0.515l1.157-0.387l0.774,0.131l0.900-0.389l7.982,0.131l0.643,1.930l0.771,1.674l0.645,0.773l1.031,1.415l1.801-0.128l0.900-0.387l1.418,0.387l0.514-0.772l0.644-1.545l1.673-0.128l0.128-0.388l1.417,0l-0.258,0.902l3.219,0l0.128,1.672l0.514,1.031l-0.385,1.673l0.129,1.674l0.900,1.030l-0.129,3.091l0.645-0.131l1.158,0l1.674-0.385l1.287,0.128l0.257,0.902l-0.257,1.286l0.387,1.287l-0.387,0.903l0.257,1.028l-5.536-0.127l-0.128,8.625l1.804,2.187l1.673,1.674l-4.892,1.158l-6.566-0.385l-1.801-1.287l-10.944,0.128l-0.384,0.128l-1.674-1.159l-1.672-0.128l-1.674,0.515l1.288-0.516z', + AR: + 'M319.448,295.781l1.288,1.544v2.188l-2.319,1.416l-1.801,1.158l-2.961,2.576l-3.605,3.732l-0.771,2.188l-0.645,2.702v2.705l-0.643,0.643l-0.129,1.674l-0.257,1.418l3.475,2.316l-0.387,1.802l1.675,1.287l-0.129,1.288l-2.574,3.475l-3.991,1.418l-5.406,0.512l-2.961-0.256l0.514,1.674l-0.514,1.931l0.514,1.415l-1.673,0.902l-2.703,0.385l-2.575-1.027l-1.029,0.77l0.386,2.705l1.801,0.771l1.417-0.9l0.901,1.416l-2.575,0.901l-2.188,1.673l-0.386,2.705l-0.643,1.414h-2.448l-2.188,1.416l-0.772,1.932l2.704,2.06l2.574,0.517l-0.901,2.444l-3.218,1.545l-1.803,3.09l-2.445,1.03l-1.031,1.287l0.774,2.832l1.802,1.543l-1.03-0.127l-2.574-0.387l-6.436-0.386l-1.16-1.545v-2.06l-1.801,0.129l-0.902-0.902l-0.258-2.831l2.06-1.288l0.901-1.674l-0.386-1.288l1.546-2.315l0.9-3.605l-0.257-1.545l1.158-0.516l-0.258-1.029l-1.287-0.514l0.901-1.158l-1.157-1.03l-0.645-3.089l1.03-0.516l-0.385-3.348l0.513-2.703l0.773-2.447l1.673-1.029l-0.9-2.574v-2.445l2.06-1.803v-2.189l1.415-2.701l0.129-2.447l-0.772-0.514l-1.287-4.637l1.672-2.83l-0.257-2.575l1.03-2.446l1.802-2.574l1.802-1.673l-0.772-1.03l0.515-0.9v-4.379l2.96-1.414l0.902-2.704l-0.386-0.772l2.316-2.447l3.477,0.645l1.544,2.061l1.03-2.188l3.089,0.127l0.515,0.516l4.892,4.377l2.188,0.387l3.348,2.059l2.703,1.03l0.386,1.157l-2.574,4.121l2.702,0.771l2.961,0.387l2.189-0.387l2.446-2.059l0.386-2.445L319.448,295.781zM282.761,371.99l3.475,1.674l3.733,0.643l-1.159,1.416l-2.574,0.131l-1.416-1.031h-1.546h-2.96l0.129-5.924l0.901,1.16l-1.417-1.931L282.761,371.99z', + AT: + 'M510.996,97.278l-0.257,1.158l-1.545,0l0.643,0.643l-0.900,1.674l-0.515,0.515l-2.446,0l-1.289,0.644l-2.315-0.258l-3.734-0.644l-0.644-0.900l-2.703,0.386l-0.258,0.514l-1.672-0.386l-1.416,0l-1.160-0.514l0.385-0.644l-0.128-0.515l0.903-0.128l1.285,0.772l0.387-0.772l2.446,0.128l1.931-0.515l1.287,0.128l0.773,0.515l0.258-0.386l-0.387-1.802l1.030-0.386l0.901-1.158l2.058,0.772l1.417-1.030l1.030-0.258l2.061,0.901l1.286-0.129l1.158,0.516l-0.127,0.256l-0.257-0.903z', + AU: + 'M863.067,336.975l1.674,0.129l0.129,3.218l-0.900,0.901l-0.258,2.188l-0.900-0.772l-1.934,1.931l-0.514-0.129l-1.672-0.129l-1.675-2.316l-0.385-1.803l-1.545-2.318l0.127-1.287l1.674,0.259l2.576,0.901l1.545-0.258l-2.058,0.515zM805.011,313.803l-2.832,1.288l-2.317,0.643l-0.513,1.416l-1.034,1.159l-2.185,0l-1.803,0.256l-2.318-0.513l-1.930,0.386l-1.930,0.127l-1.546,1.417l-0.772-0.128l-1.416,0.772l-1.287,0.772l-1.932-0.128l-1.800,0l-2.834-1.674l-1.416-0.514l0-1.545l1.289-0.387l0.515-0.515l-0.131-1.029l0.387-1.932l-0.256-1.545l-1.547-2.702l-0.386-1.546l0.129-1.545l-1.030-1.801l-0.127-0.773l-1.160-1.030l-0.387-2.058l-1.545-2.189l-0.384-1.160l1.287,1.160l-1.029-2.447l1.416,0.774l0.771,1.030l0-1.417l-1.416-2.061l-0.258-0.900l-0.644-0.773l0.386-1.545l0.516-0.644l0.387-1.415l-0.258-1.546l1.029-1.930l0.258,2.060l1.158-1.932l2.188-0.900l1.287-1.160l2.060-0.901l1.159-0.257l0.773,0.387l2.188-1.029l1.544-0.258l0.516-0.644l0.643-0.257l1.545,0.128l2.832-0.901l1.418-1.160l0.640-1.414l1.676-1.416l0.129-1.030l0-1.417l1.930-2.318l1.158,2.318l1.031-0.514l-0.902-1.287l0.902-1.287l1.156,0.516l0.260-2.061l1.545-1.289l0.643-1.028l1.289-0.516l0.127-0.773l1.158,0.386l0-0.643l1.158-0.387l1.416-0.385l1.930,1.157l1.547,1.675l1.671,0l1.676,0.258l-0.515-1.545l1.287-2.060l1.158-0.772l-0.385-0.643l1.158-1.545l1.672-1.031l1.289,0.385l2.317-0.514l-0.129-1.416l-1.932-0.900l1.418-0.388l1.801,0.775l1.416,1.029l2.316,0.772l0.774-0.387l1.674,0.902l1.544-0.773l1.030,0.258l0.644-0.516l1.158,1.289l-0.644,1.416l-1.029,1.157l-0.903,0l0.260,1.160l-0.773,1.286l-0.901,1.289l0.127,0.772l2.190,1.545l2.058,0.900l1.418,0.902l1.930,1.544l0.771,0l1.418,0.773l0.387,0.772l2.574,0.900l1.801-0.900l0.516-1.416l0.513-1.289l0.387-1.415l0.772-2.188l-0.385-1.286l0.127-0.775l-0.256-1.542l0.387-2.062l0.513-0.514l-0.386-0.901l0.644-1.417l0.516-1.414l0-0.772l1.029-1.032l0.772,1.288l0.130,1.674l0.641,0.385l0.131,1.029l1.029,1.417l0.258,1.544l-0.129,1.031l0.902,2.061l1.801-1.031l0.903,1.158l1.285,1.031l-0.256,1.158l0.515,2.317l0.387,1.416l0.641,0.257l0.773,2.319l-0.256,1.414l0.901,1.805l2.961,1.414l1.800,1.288l1.803,1.159l-0.258,0.642l1.545,1.674l1.030,2.961l1.031-0.642l1.158,1.286l0.643-0.516l0.386,2.961l1.932,1.544l1.287,1.030l2.061,2.189l0.771,2.189l0.129,1.545l-0.260,1.674l1.289,2.316l-0.129,2.317l-0.515,1.287l-0.645,2.447l0,1.545l-0.513,1.930l-1.159,2.446l-2.058,1.288l-0.903,2.060l-0.900,1.415l-0.902,2.317l-1.030,1.288l-0.642,2.060l-0.387,1.802l0.129,0.900l-1.545,0.902l-2.961,0.128l-2.445,1.031l-1.287,1.030l-1.674,1.157l-2.188-1.157l-1.675-0.515l0.517-1.287l-1.547,0.516l-2.316,1.929l-2.316-0.773l-1.547-0.385l-1.545-0.258l-2.572-0.772l-1.803-1.674l-0.516-2.060l-0.644-1.288l-1.287-1.157l-2.575-0.258l0.903-1.287l-0.645-2.060l-1.287,1.931l-2.445,0.387l1.416-1.416l0.386-1.545l1.030-1.288l-0.258-2.059l-2.188,2.316l-1.673,0.902l-1.032,2.189l-2.058-1.159l0.129-1.416l-1.674-1.932l-1.545-1.029l0.516-0.643l-3.348-1.675l-1.932,0l-2.574-1.286l-4.893,0.256l-3.474,0.902l-3.090,0.902l2.574,0.130z', + AZ: + 'M590.292,114.27l0.643,0l1.931,1.673l1.158,0.129l0.516-0.644l1.545-1.030l1.416,1.417l1.417,1.802l1.286,0.129l0.774,0.773l-2.190,0.257l-0.514,2.059l-0.386,0.901l-1.031,0.644l0,1.416l-0.643,0.129l-1.674-1.417l0.902-1.415l-0.773-0.773l-1.030,0.258l-3.089,1.930l-0.129-1.801l-1.158-0.516l-1.159-0.643l0.772-0.902l-1.416-0.900l0.516-0.773l-1.032-0.386l-0.642-0.772l0.129,0l0.644-0.387l1.930,0.772l1.545,0.130l0.258-0.258l-1.287-1.545l-0.771,0.257zM589.521,122.637l-1.804-0.386l-1.415-1.288l-0.387-1.028l0.516,0l0.771,0.772l1.287-0.129l0,0.515l-1.032-1.544z', + BA: + 'M516.403,106.159l1.030,0l-0.645,1.159l1.289,1.030l-0.389,1.287l-1.158,0.387l-0.900,0.515l-0.387,1.545l-2.445-1.030l-1.031-1.159l-0.901-0.514l-1.286-1.031l-0.643-0.901l-1.290-1.158l0.516-1.159l1.031,0.643l0.643-0.643l1.159,0l2.316,0.386l1.931,0l-1.160-0.643z', + BD: + 'M714.901,167.564l-0.13,1.932l-0.899-0.387l0.127,2.189l-0.771-1.417l-0.129-1.415l-0.514-1.287l-1.031-1.545l-2.575-0.129l0.259,1.159l-0.771,1.544l-1.158-0.644l-0.389,0.516l-0.772-0.258l-1.028-0.258l-0.516-2.188l-0.9-2.059l0.514-1.674L702.544,161l0.514-1.031l1.803-1.03l-2.061-1.415l1.031-1.803l2.061,1.159l1.285,0.128l0.26,1.931l2.574,0.386l2.574-0.128l1.545,0.515l-1.289,2.317l-1.158,0.129l-0.9,1.545l1.545,1.416l0.387-1.802h0.771L714.901,167.564z', + BE: + 'M474.179,88.652l1.932,0.258l2.574-0.643l1.673,1.158l1.416,0.644l-0.258,1.930l-0.644,0l-0.385,1.544l-2.318-1.286l-1.416,0.257l-1.801-1.287l-1.288-1.029l-1.287,0l-0.385-1.031l-2.187,0.515z', + BF: + 'M457.573,201.035l-1.802,-0.773l-1.287,0.129l-0.902,0.644l-1.286,-0.515l-0.387,-0.902l-1.287,-0.643l-0.128,-1.545l0.771,-1.159l-0.128,-0.900l2.189,-2.189l0.385,-1.802l0.773,-0.644l1.287,0.257l1.159,-0.514l0.257,-0.645l2.189,-1.285l0.514,-0.774l2.446,-1.158l1.545,-0.387l0.644,0.516l1.673,0l-0.129,1.287l0.258,1.287l1.545,1.673l0.128,1.417l3.091,0.515l0,1.930l-0.645,0.774l-1.287,0.257l-0.515,1.159l-1.030,0.256l-2.317,0l-1.288,-0.256l-0.770,0.514l-1.289,-0.258l-4.634,0.129l-0.129,1.545l-0.386,-2.060z', + BG: + 'M526.314,107.833l0.773,1.030l1.031-0.129l2.059,0.386l3.990,0.130l1.287-0.644l3.219-0.644l1.930,1.030l1.544,0.258l-1.416,1.158l-0.900,1.931l0.772,1.416l-2.317-0.257l-2.705,0.772l0,1.417l-2.445,0.256l-1.930-1.029l-2.187,0.773l-1.932-0.130l-0.258-1.674l-1.287-0.900l0.385-0.387l-0.256-0.386l0.515-0.772l1.030-0.901l-1.415-1.158l-0.259-0.902l-0.772,0.644z', + BI: + 'M544.208,239.14l-0.130-3.347l-0.643-1.159l1.673,0.258l0.773-1.545l1.415,0.128l0.131,1.030l0.642,0.643l0,0.903l-0.642,0.513l-1.030,1.416l-1.031,1.032l1.158-0.128z', + BJ: + 'M472.505,210.174l-2.188,0.258l-0.773-1.803l0.131-6.307l-0.516-0.515l-0.129-1.287l-0.900-0.902l-0.775-0.900l0.259-1.417l1.030-0.256l0.515-1.159l1.287-0.257l0.645-0.774l0.901-0.773l0.901-0.127l2.059,1.674l-0.129,0.771l0.643,1.673l-0.514,1.031l0.258,0.773l-1.288,1.672l-0.901,0.773l-0.386,1.674l0,1.802l0.130-4.376z', + BN: + 'M772.829,214.809l1.16-1.029l2.314-1.416l-0.127,1.287l-0.26,1.674h-1.285l-0.516,0.902L772.829,214.809z', + BO: + 'M295.89,286.383l-3.089-0.127l-1.030,2.187l-1.544-2.060l-3.477-0.644l-2.316,2.447l-1.932,0.386l-1.028-3.733l-1.417-2.960l0.773-2.576l-1.417-1.157l-0.387-1.933l-1.286-1.932l1.673-2.830l-1.158-2.318l0.643-0.901l-0.515-1.029l1.159-1.287l0-2.317l0.128-1.931l0.644-0.901l-2.445-4.248l2.060,0.127l1.415,0l0.515-0.771l2.446-1.160l1.416-1.029l3.476-0.386l-0.258,1.930l0.387,1.159l-0.258,1.802l2.960,2.317l2.962,0.515l1.030,1.030l1.801,0.515l1.159,0.772l1.673,0l1.545,0.773l0.128,1.544l0.516,0.773l0.128,1.158l-0.772,0l1.031,3.219l5.148,0.131l-0.386,1.542l0.258,1.030l1.416,0.771l0.643,1.676l-0.386,2.061l-0.772,1.158l0.257,1.544l-0.901,0.643l0-0.902l-2.575-1.285l-2.446-0.130l-4.634,0.772l-1.416,2.447l0,1.414l-1.030,3.219l0.515,0.515z', + BR: + 'M310.05,308.396l3.605-3.732l2.961-2.576l1.801-1.158l2.319-1.416v-2.188l-1.288-1.544l-1.416,0.516l0.515-1.546l0.386-1.545v-1.544l-0.9-0.516l-1.031,0.516l-1.028-0.129l-0.259-1.031l-0.256-2.443l-0.516-0.902l-1.802-0.643l-1.159,0.514l-2.831-0.514l0.128-3.736l-0.772-1.414l0.901-0.643l-0.257-1.545l0.771-1.158l0.386-2.061l-0.643-1.676l-1.416-0.771l-0.258-1.029l0.386-1.543l-5.148-0.131l-1.031-3.219h0.772l-0.128-1.158l-0.516-0.772l-0.128-1.544l-1.545-0.773h-1.673l-1.159-0.771l-1.801-0.516l-1.03-1.029l-2.962-0.516l-2.96-2.316l0.258-1.803l-0.387-1.158l0.258-1.931l-3.476,0.386l-1.416,1.029l-2.446,1.16l-0.515,0.771h-1.415l-2.06-0.127l-1.416,0.383l-1.287-0.256l0.256-4.119l-2.317,1.545h-2.317l-1.03-1.416l-1.801-0.129l0.644-1.158l-1.546-1.674l-1.158-2.445l0.772-0.516v-1.158l1.545-0.773l-0.257-1.416l0.772-0.9l0.129-1.289l3.089-1.801l2.188-0.516l0.386-0.514l2.446,0.129l1.159-7.338l0.129-1.159l-0.515-1.544l-1.159-1.03v-1.931l1.545-0.387l0.515,0.258l0.129-1.029l-1.545-0.258l-0.129-1.674h5.278l0.9-0.902l0.773,0.902l0.515,1.545l0.516-0.387l1.544,1.416l2.06-0.129l0.515-0.771l1.93-0.645l1.159-0.515l0.257-1.159l1.931-0.771l-0.128-0.514l-2.188-0.26l-0.387-1.672v-1.805l-1.158-0.643l0.514-0.257l2.06,0.257l2.059,0.773l0.774-0.643l1.93-0.516l3.09-0.902l0.9-1.029l-0.257-0.772l1.287-0.129l0.644,0.644l-0.257,1.158l0.9,0.387l0.644,1.287l-0.772,0.902l-0.515,2.316l0.773,1.287l0.128,1.287l1.674,1.287l1.288,0.129l0.386-0.516l0.771-0.128l1.288-0.515l0.901-0.645l1.416,0.26l0.643-0.131l1.546,0.131l0.258-0.518l-0.517-0.514l0.259-0.773l1.158,0.26l1.159-0.26l1.545,0.516l1.287,0.516l0.771-0.645l0.644,0.129l0.387,0.771l1.287-0.256l1.03-1.031l0.771-1.93l1.545-2.446l1.029-0.128l0.646,1.415l1.544,4.763l1.416,0.387v1.931l-1.932,2.188l0.773,0.772l4.763,0.388l0.128,2.701l2.06-1.674l3.348,0.902l4.505,1.674l1.288,1.545l-0.387,1.545l3.09-0.9l5.277,1.414h3.991l3.99,2.189l3.476,2.961l2.06,0.771l2.317,0.129l0.9,0.901l0.901,3.476l0.516,1.545l-1.159,4.504l-1.287,1.676l-3.861,3.863l-1.674,2.959l-2.06,2.316l-0.643,0.129l-0.773,1.932l0.257,5.02l-0.772,4.25l-0.256,1.672l-0.902,1.158l-0.515,3.605l-2.703,3.475l-0.388,2.833l-2.187,1.158l-0.645,1.546h-2.96l-4.249,1.027l-1.931,1.289l-2.96,0.772l-3.219,2.06l-2.188,2.703l-0.386,2.061l0.386,1.416l-0.515,2.703l-0.645,1.416l-1.803,1.416l-2.96,4.764l-2.446,2.189l-1.802,1.156l-1.287,2.574l-1.673,1.545l-0.771-1.545l1.157-1.286l-1.545-1.804l-2.188-1.414l-2.702-1.805l-1.03,0.129l-2.704-2.059L310.05,308.396z', + BT: + 'M712.198,152.117l1.158,0.901l-0.257,1.674l-2.188,0l-2.189,-0.129l-1.672,0.386l-2.447,-1.029l-0.129,-0.516l1.804,-1.931l1.414,-0.773l1.930,0.645l1.416,0.128l-1.160,-0.644z', + BW: + 'M534.296,276.857l0.516,0.516l0.900,1.544l3.089,2.962l1.158,0.256l0,1.030l0.772,1.674l2.061,0.385l1.673,1.290l-3.734,1.929l-2.445,2.059l-0.901,1.804l-0.773,1.030l-1.545,0.128l-0.386,1.287l-0.258,0.901l-1.801,0.645l-2.188,-0.129l-1.288,-0.773l-1.159,-0.387l-1.287,0.644l-0.642,1.286l-1.287,0.775l-1.290,1.287l-1.929,0.256l-0.645,-0.901l0.258,-1.673l-1.544,-2.575l-0.772,-0.386l0,-7.852l2.574,-0.130l0.129,-9.654l2.060,0l4.119,-1.030l1.029,1.158l1.674,-1.028l0.901,0l1.416,-0.645l0.515,0.259l-1.030,-2.058z', + BY: + 'M528.503,81.701l2.574,0l2.961,-0.901l0.643,-1.545l2.189,-0.901l-0.258,-1.159l1.674,-0.514l2.831,-1.031l2.833,0.644l0.387,0.772l1.416,-0.385l2.703,0.643l0.258,1.287l-0.645,0.644l1.672,1.802l1.160,0.515l-0.129,0.515l1.803,0.387l0.772,0.772l-1.030,0.643l-2.187,-0.128l-0.516,0.257l0.644,0.901l0.643,1.674l-2.318,0.129l-0.900,0.643l-0.128,1.416l-1.031,-0.258l-2.446,0.129l-0.772,-0.643l-1.030,0.386l-0.900,-0.386l-2.189,0l-2.959,-0.644l-2.706,-0.258l-2.187,0.129l-1.417,0.644l-1.286,0.129l-0.129,-1.159l-0.772,-1.287l1.672,-0.516l0,-1.029l-0.771,-1.029l0.129,1.288z', + BZ: + 'M225.09,179.022l0,-0.387l0.257,-0.129l0.515,0.258l1.030,-1.544l0.515,-0.130l0,0.387l0.515,0.128l-0.129,0.645l-0.386,1.159l0.258,0.513l-0.258,0.902l0.128,0.258l-0.256,1.416l-0.644,0.643l-0.387,0.129l-0.643,0.901l-0.772,0l0.257,-3.089l0,2.060z', + CA: + 'M212.989,24.93l-1.416,1.159l-3.862-0.257l-3.347-0.644l1.417-1.288l3.99-0.772l2.317,1.03l-0.901-0.772L212.989,24.93zM212.474,18.107l-1.287,0.13l-5.02-0.13l-0.772-0.772h5.535l1.802,0.515l0.258-0.257L212.474,18.107zM204.622,14.761l3.218,0.901l-0.772,1.03l-3.991,0.515l-2.188-0.644l-1.159-0.901l-0.257-1.159l3.604,0.129l-1.545-0.129L204.622,14.761zM227.793,26.604l-4.377-0.387l-7.208-0.9l-0.901-1.417l-0.258-1.287l-2.703-1.287l-5.664-0.257l-3.09-0.901l1.03-1.031l5.535,0.13l2.962,0.901h5.406l2.317,0.901l-0.643,1.029l3.089,0.515l1.673,0.643l3.605,0.13l3.99,0.257L236.804,23l5.535-0.129L246.716,23l2.832,1.029l0.644,1.159l-1.674,0.644l-3.991,0.644l-3.475-0.387l-7.724,0.387l5.535-0.128L227.793,26.604zM165.489,16.434l3.862,0.386l-0.902,0.901l-5.02,0.772l-3.991-0.9l2.188-0.901L165.489,16.434zM166.261,14.632l3.604,0.644l-3.347,0.515h-4.505l0.128-0.387l2.704-0.901L166.261,14.632zM205.137,40.636l2.703,1.158l-1.673,0.902l-3.605-1.031l-2.188,0.516l-3.09-0.387l1.803-1.673l1.931-1.159l2.059,0.643l-2.06-1.031L205.137,40.636zM315.458,88.781l-1.417,1.673l-1.802,2.317l1.802-0.9l1.802,0.643l-1.029,0.902l2.446,0.772l1.287-0.772l2.574,0.901l-0.772,1.93l1.932-0.386l0.257,1.417l0.9,1.673l-1.157,2.317l-1.288,0.129l-1.673-0.515l0.515-2.189l-0.771-0.386l-3.09,2.317h-1.545l1.801-1.287l-2.573-0.644l-2.832,0.13l-5.278-0.13l-0.386-0.772l1.674-0.901l-1.159-0.773l2.317-1.673l2.702-4.248l1.675-1.545l2.316-0.901l1.288,0.129l0.516-0.772L315.458,88.781zM239.25,51.578l2.96,0.901l3.09,0.901l0.258,1.287l1.93-0.257l1.931,0.9l-2.316,0.903l-4.249-0.774l-1.544-1.158l-2.575,1.416l-3.861,1.416l-0.902-1.544l-3.733,0.257l2.317-1.416l0.386-2.06l0.901-2.445l1.931,0.257l0.515,1.158l1.417-0.514l-1.544-0.772L239.25,51.578zM218.525,6.393l7.08-0.643l5.278-0.386l5.921-0.13l3.604-1.415l11.199-0.773l9.656,0.129l7.723-0.386l18.924,0.514l10.555,1.802L291.9,6.264l-6.437,0.515l-2.445,0.644h5.792L278.126,9.74l-10.169,2.704l-9.913,0.9l3.734,0.258l-1.931,0.515l2.317,1.287l-6.694,1.674l-1.287,1.159l-3.863,0.772l0.387,0.643l3.604,0.258v0.644l-6.049,1.158l-7.081-0.643l-7.981,0.386l-9.012-0.515l-0.385-1.288l5.02-0.643l-1.158-0.902l2.187-0.9l6.437,0.9l-7.981-2.316l2.188-1.03l4.763-0.644l0.773-0.901l-3.862-1.03l-1.159-1.416l7.338,0.129l6.437-0.644l-15.577-0.128l-4.762-1.031l-5.407-1.802l0.515,0.901L218.525,6.393zM253.024,32.01l2.574-1.03l5.922,1.417l3.734,1.287l0.385,1.158l5.02-0.643l2.833,1.674l6.437,1.158l2.317,1.03l2.574,2.575l-4.891,1.158l6.307,1.803l4.248,0.643l3.862,2.446l4.248,0.128l-0.773,1.932l-4.763,3.089l-3.347-1.158l-4.248-2.575l-3.476,0.386l-0.257,1.545l2.832,1.545l3.605,1.287l1.159,0.644l1.673,2.704l-0.902,1.93l-3.347-0.772l-6.821-2.061l3.862,2.318l2.702,1.545l0.516,1.03l-7.339-1.159l-5.793-1.545l-3.218-1.286l0.903-0.774l-3.991-1.415l-3.992-1.287l0.129,0.772l-7.853,0.386l-2.188-0.901l1.675-1.931l5.149-0.129l5.535-0.257l-0.901-1.031l0.901-1.287l3.475-2.702l-0.772-1.159l-1.03-0.901l-4.12-1.288l-5.406-0.901l1.674-0.772l-2.832-1.674l-2.317-0.129l-2.189-0.9l-1.416,0.772l-4.891,0.385l-9.784-0.643l-5.664-0.772l-4.377-0.386l-2.317-0.901l2.832-1.287h-3.862l-0.772-2.704l2.059-2.446l2.704-1.03l6.951-0.772l-1.931,1.802l2.188,1.674l2.447-2.189l6.823-1.159l4.633,2.832l-0.386,1.675l-5.278,0.774L253.024,32.01zM210.672,27.248l5.536,0.128l5.148,0.645l-3.989,2.445l-3.219,0.514l-2.833,1.932l-3.088-0.128l-1.675-2.318v-1.287l1.417-1.158L210.672,27.248zM206.552,9.869l1.931-0.901l2.704-0.128l-1.159-0.644l6.308-0.129l3.348,1.416l8.753,1.673l5.664,2.06l-3.733,0.772l-5.021,2.06l-4.763,0.258l-5.535-0.386l-2.961-1.031l0.129-1.03l2.059-0.772l-4.891,0.129l-2.961-0.902l-1.673-1.287L206.552,9.869zM194.71,31.109l-2.832-2.574l2.961-0.514l3.218,0.643l4.119-0.258l0.515,1.03l-1.544,0.901l3.604,1.803l-0.644,1.416l-3.862,1.415l-2.574-0.257l-1.803-1.03l-5.535-1.544l-1.673-1.16L194.71,31.109zM178.233,30.08l3.089,1.158l1.674,2.574l0.772,1.932l4.634,1.287l4.764,1.287l-0.258,1.159l-4.377,0.257l1.673,1.03l-0.9,1.03h-6.436l-1.804-0.644l-4.376-0.386l-5.278,1.545l-6.565,0.644l-3.604,0.128l-2.704-2.059l-6.05-0.386l-4.505-1.674l2.96-0.772l4.119-0.386l3.863,0.129l3.475-0.516l-5.149-0.644l-5.793,0.258l-3.862-0.129l-1.416-0.901l6.308-1.159l-4.249,0.129l-4.634-0.772l2.189-2.059l1.932-1.031l7.208-1.673l2.703,0.515l-1.287,1.287l5.922-0.772l3.861,1.287l2.961-1.287l2.446,0.901l2.189,2.574l1.416-1.157l-1.932-2.704l2.446-0.387L178.233,30.08zM174.757,22.613l2.446-0.385l2.832,0.128l0.385,1.287l-1.543,1.287l-9.141,0.387l-6.822,1.159l-4.12,0.128l-0.257-0.901l5.535-1.159l-12.228,0.257l-3.734-0.514l3.734-2.575l2.445-0.772l7.596,0.901l4.891,1.673l4.634,0.129l-3.862-2.574l2.446-1.03l1.803,0.643l0.9,1.287l-2.06-0.644L174.757,22.613zM134.336,21.969l4.506-2.059l5.535-1.803l4.12,0.13l3.732-0.387l-0.385,2.06l-2.06,0.901l-2.575,0.129l-5.02,1.158l-4.248,0.386l3.605,0.515L134.336,21.969zM137.812,26.476l3.862,0.514l6.823,0.129l2.703,0.772l2.832,1.158l-3.347,0.644l-6.694,1.674L140,33.427l-0.643,1.287l-5.664,1.287l-1.802-1.03l-5.922-1.544l0.129-0.902l2.188-2.317l2.06-1.159l-1.673-2.188L137.812,26.476zM107.69,81.443l2.574-0.256l-0.773,3.088l2.318,2.188h-1.03l-1.674-1.287l-0.9-1.287l-1.416-0.772l-0.516-1.158l0.13-0.902l-1.287-0.386L107.69,81.443zM199.73,20.682l1.288,0.901V23l-1.416,1.801l-3.218,0.387l-2.961-0.387l0.129-1.545l-4.507,0.13l-0.128-2.06l2.961,0.129l3.99-0.901l-3.862-0.128L199.73,20.682zM181.064,13.344l5.279,0.387l7.337,0.901l2.06,1.288l1.03,1.158l-4.377-0.258l-4.506-0.9l-5.922-0.129l2.576-0.773l-3.348-0.644l0.129,1.03L181.064,13.344zM127.385,92.386l1.288,1.287l2.702,1.158l1.16,1.416l-1.417,0.387l-4.376-1.159l-0.773-1.029l-2.446-0.903l-0.515-0.772l-2.703-0.514l-1.03-1.416l0.129-0.643l2.832,0.643l1.673,0.386l2.575,0.257l-0.901-0.902L127.385,92.386zM315.071,83.502l0.129,2.961l-1.932,1.031l-1.932,0.901l-4.376,1.03l-3.476,2.188l-4.505,0.386l-5.793-0.515h-3.99l-2.832,0.129l-2.318,1.93l-3.346,1.288l-3.863,3.476l-3.089,2.575l2.189-0.515l4.376-3.476l5.664-2.317l3.991-0.257l2.445,1.286l-2.573,1.932l0.772,2.832l0.901,2.06l3.476,1.287l4.504-0.387l2.704-2.96l0.258,1.931l1.673,1.029l-3.347,1.674l-5.921,1.674l-2.703,1.029l-2.961,1.931l-2.06-0.128l-0.128-2.317l4.633-2.189h-4.247l-2.961,0.387l-1.803-1.545v-3.605l-1.157-0.772l-1.804,0.386l-0.9-0.644l-2.06,1.932l-0.901,2.187l-0.902,1.159l-1.158,0.515h-0.901l-0.258,0.772h-4.891h-4.12l-1.287,0.516l-2.703,1.801l-0.387,0.258l-0.256,0.258l-0.387,0.386l-0.257,0.515h-0.643h-0.516h-0.901l-0.772-0.128h-0.902h-0.643l-0.772,0.128h-0.258l-0.515,0.257l-0.386,0.129l0.257,0.386v0.129l0.387,0.772v0.258v0.128l-0.258,0.13l-0.386,0.128l-0.772,0.258l-0.902,0.257l-0.643,0.257l-0.643,0.258l-0.644,0.129h-0.128h-0.387l-0.9,0.128l-0.645,0.129l-0.644,0.258l-0.643,0.385l-0.644,0.258l-0.644,0.257l-0.643,0.258h-0.644l-0.514-0.129l-0.387-0.257l-0.257-0.257v-0.13v-0.257l0.644-0.9l1.286-1.546v-0.128v-0.129l0.259-0.515l0.385-0.515l0.129-0.258l-0.258-0.771l-0.129-0.515v-0.386l-0.127-0.515l-0.13-0.515l-0.129-0.515l-0.128-0.386l-0.13-0.515v-0.257l-0.128-0.387l-0.515-0.386l-0.514-0.128l-0.644-0.258l-0.643-0.257l-0.516-0.257l0.386-0.515v-0.129h-0.128l-0.258-0.258h-0.128l-0.258,0.128l-0.386-0.128l-0.258-0.129h-0.128l-0.129-0.257h-0.129v-0.258v-0.128v-0.129v-0.129h-0.257l-0.258,0.258h-0.772l0.128-0.258h-0.257l-0.386-0.257l-0.128-0.387l-0.13-0.386l-0.514-0.257l-0.515-0.129l-0.515-0.258l-0.515-0.257l-0.515-0.128l-0.515-0.258l-0.515-0.258l-0.514-0.128l-0.258-0.128l-0.387-0.13l-0.643-0.257l-0.772-0.386l-0.772-0.258l-0.773-0.257l-0.386-0.257h-0.258l-0.386-0.258l-0.644-0.129l-0.643,0.129l-0.772,0.258l-0.387,0.128l-0.386,0.129l-0.258,0.129h-0.515h-0.385l-3.219-0.773l-2.188,0.387l-2.703-0.773l-2.704-0.515l-1.93-0.129l-0.772-0.514l-0.516-1.417h-0.901v1.03h-5.536h-9.139h-9.397h-32.182h-2.704H133.95l-5.149-2.574l-1.931-1.287l-4.891-1.03l-1.545-2.446l0.385-1.673l-3.474-1.031l-0.387-2.188l-3.348-2.061v-1.287l1.417-1.287v-1.802l-4.634-1.673l-2.703-3.09l-1.674-1.93l-2.446-1.159l-1.802-1.159l-1.545-1.417l-2.703,0.902l-2.575,1.545L92.5,66.51l-1.802-1.157l-2.704-0.774H85.42V49.133V39.22l5.019,0.644l4.249,1.286l2.832,0.258l2.317-1.158l3.347-0.901l3.99,0.385l3.992-1.157l4.376-0.644l1.931,1.029l1.931-0.644l0.643-1.158l1.803,0.257l4.634,2.447l3.604-1.931l0.387,2.059l3.218-0.387l1.029-0.772l3.219,0.129l4.12,1.159l6.307,0.901l3.733,0.515l2.704-0.129l3.604,1.288l-3.734,1.415l4.763,0.515l7.338-0.257l2.317-0.515l2.832,1.544l2.96-1.287l-2.832-1.158l1.803-0.901l3.218-0.129l2.189-0.258l2.188,0.644l2.703,1.417l2.961-0.258l4.763,1.287l4.248-0.386h3.862l-0.258-1.673l2.446-0.515l4.12,0.9v2.576l1.673-2.06h2.188l1.288-2.704l-2.962-1.673l-3.088-1.03l0.128-2.961l3.218-2.06l3.605,0.515l2.703,1.158l3.604,3.091l-2.317,1.287l5.02,0.514v2.832l3.605-2.189l3.218,1.804l-0.9,1.93l2.702,1.802l2.704-1.931l2.06-2.317l0.129-2.96l3.861,0.257l3.862,0.387l3.733,1.287l0.128,1.416l-2.059,1.416l1.931,1.416l-0.386,1.286l-5.277,1.932l-3.734,0.386l-2.704-0.772l-0.901,1.287l-2.574,2.317l-0.773,1.159l-3.089,1.802l-3.862,0.257l-2.188,1.031l-0.13,1.802l-3.089,0.386l-3.347,2.188l-2.961,2.961l-1.028,2.188l-0.13,3.09l3.991,0.386l1.159,2.576l1.287,2.059l3.733-0.515l5.02,1.159l2.704,1.029l1.93,1.288l3.347,0.643l2.832,1.158l4.507,0.129l2.959,0.258l-0.514,2.446l0.901,2.702l1.931,2.961l3.991,2.576l2.059-0.902l1.545-2.703l-1.416-4.247l-1.931-1.545l4.247-1.159l3.09-1.931l1.545-1.931l-0.257-1.803l-1.802-2.188l-3.348-2.06l3.219-2.832l-1.158-2.445l-0.902-4.249l1.931-0.514l4.506,0.643l2.832,0.257l2.188-0.644l2.575,0.902l3.347,1.545l0.772,1.029l4.763,0.259v2.187l0.901,3.476l2.446,0.386l1.931,1.545l3.862-1.416l2.574-2.961l1.802-1.287l2.06,2.446l3.605,3.347l2.96,3.218l-1.159,1.802l3.604,1.417l2.446,1.545l4.25,0.772l1.802,0.772l1.03,2.317l2.06,0.387l-1.158-1.028L315.071,83.502z', + CD: + 'M500.183,239.912l-0.902,-1.031l-0.900,0.515l-1.030,1.159l-2.189,-2.832l2.059,-1.544l-1.029,-1.804l0.901,-0.643l1.802,-0.257l0.256,-1.287l1.416,1.287l2.319,0.129l0.900,-1.288l0.258,-1.802l-0.258,-2.059l-1.286,-1.545l1.157,-3.219l-0.642,-0.515l-2.060,0.258l-0.643,-1.415l0.127,-1.160l3.475,0.129l2.062,0.644l2.187,0.643l0.259,-1.416l1.415,-2.446l1.545,-1.544l1.803,0.514l1.800,0.130l-0.257,1.673l-0.770,1.416l-0.517,1.673l-0.386,2.448l0.257,1.414l-0.514,1.030l0,0.901l-0.385,0.901l-1.805,1.287l-1.156,1.417l-1.160,2.575l0,2.190l-0.645,0.898l-1.543,1.288l-1.544,1.804l-1.032,-0.516l-0.128,-0.772l-1.544,0l-0.901,1.030l0.772,0.258z', + CF: + 'M506.361,206.957l2.318,-0.129l0.384,-0.773l0.517,0.129l0.642,0.515l3.349,-1.029l1.157,-1.031l1.416,-0.901l-0.256,-0.900l0.772,-0.259l2.574,0.130l2.574,-1.287l1.932,-2.962l1.417,-1.030l1.672,-0.514l0.258,1.157l1.545,1.674l0,1.159l-0.387,1.159l0.129,0.773l1.029,0.771l2.059,1.159l1.419,1.159l0,0.901l1.800,1.287l1.159,1.287l0.643,1.544l2.059,1.031l0.389,0.901l-0.903,0.257l-1.674,0l-2.058,-0.257l-0.901,0.129l-0.514,0.643l-0.775,0.129l-1.158,-0.514l-2.961,1.287l-1.287,-0.258l-0.258,0.258l-0.900,1.544l-1.930,-0.514l-2.060,-0.258l-1.674,-1.030l-2.190,-0.900l-1.415,0.900l-1.030,1.417l-0.258,1.802l-1.800,-0.130l-1.803,-0.514l-1.545,1.544l-1.415,2.446l-0.387,-0.772l-0.129,-1.287l-1.157,-0.773l-1.031,-1.416l-0.257,-1.029l-1.288,-1.416l0.258,-0.772l-0.258,-1.160l0.258,-2.060l0.643,-0.515l-1.287,2.702z', + CG: + 'M548.327,217.513l-0.258,3.217l1.159,0.258l-0.901,1.031l-1.031,0.643l-1.029,1.416l-0.514,1.287l-0.131,2.189l-0.643,1.028l0,2.061l-0.901,0.643l0,1.674l-0.386,0.128l-0.257,1.546l0.643,1.159l0.130,3.347l0.514,2.445l-0.257,1.415l0.514,1.546l1.545,1.546l1.545,3.346l-1.030,-0.258l-3.733,0.386l-0.643,0.387l-0.771,1.673l0.642,1.288l-0.514,3.088l-0.387,2.705l0.772,0.514l1.932,1.031l0.642,-0.516l0.258,2.961l-2.058,0l-1.159,-1.545l-0.903,-1.158l-2.058,-0.387l-0.644,-1.416l-1.674,0.901l-2.187,-0.385l-0.903,-1.158l-1.672,-0.258l-1.289,0l-0.128,-0.772l-0.901,-0.130l-1.287,-0.128l-1.674,0.385l-1.158,0l-0.645,0.131l0.129,-3.091l-0.900,-1.030l-0.129,-1.674l0.385,-1.673l-0.514,-1.031l-0.128,-1.672l-3.219,0l0.258,-0.902l-1.417,0l-0.128,0.388l-1.673,0.128l-0.644,1.545l-0.514,0.772l-1.418,-0.387l-0.900,0.387l-1.801,0.128l-1.031,-1.415l-0.645,-0.773l-0.771,-1.674l-0.643,-1.930l-7.982,-0.131l-0.900,0.389l-0.774,-0.131l-1.157,0.387l-0.387,-0.772l0.773,-0.386l0,-1.159l0.514,-0.642l0.902,-0.516l0.772,0.258l0.901,-1.030l1.544,0l0.128,0.772l1.032,0.516l1.544,-1.804l1.543,-1.288l0.645,-0.898l0,-2.190l1.160,-2.575l1.156,-1.417l1.805,-1.287l0.385,-0.901l0,-0.901l0.514,-1.030l-0.257,-1.414l0.386,-2.448l0.517,-1.673l0.770,-1.416l0.257,-1.673l0.258,-1.802l1.030,-1.417l1.415,-0.900l2.190,0.900l1.674,1.030l2.060,0.258l1.930,0.514l0.900,-1.544l0.258,-0.258l1.287,0.258l2.961,-1.287l1.158,0.514l0.775,-0.129l0.514,-0.643l0.901,-0.129l2.058,0.257l1.674,0l0.903,-0.257l1.672,2.188l1.158,0.387l0.773,-0.515l1.287,0.257l1.416,-0.643l0.644,1.159l-2.446,-1.802z', + CH: + 'M491.042,98.951l0.128,0.515l-0.385,0.644l1.160,0.514l1.416,0l-0.258,1.159l-1.158,0.386l-1.932,-0.257l-0.643,1.030l-1.288,0.129l-0.387,-0.516l-1.543,1.031l-1.287,0.128l-1.160,-0.643l-0.901,-1.159l-1.288,0.386l0,-1.158l1.932,-1.545l-0.130,-0.644l1.288,0.257l0.772,-0.515l2.317,0l0.515,-0.514l-2.832,-0.772z', + CI: + 'M457.573,213.521l-1.287,0l-1.802,-0.514l-1.802,0l-3.219,0.514l-1.802,0.773l-2.703,1.030l-0.516,-0.129l0.259,-2.188l0.257,-0.387l-0.129,-1.030l-1.159,-1.158l-0.772,-0.129l-0.901,-0.772l0.644,-1.158l-0.258,-1.287l0.129,-0.773l0.386,0l0.129,-1.159l-0.129,-0.644l0.258,-0.257l1.030,-0.387l-0.772,-2.187l-0.516,-1.031l0.129,-0.901l0.515,-0.257l0.387,-0.258l0.772,0.386l2.059,0l0.514,-0.772l0.516,0.129l0.772,-0.385l0.387,1.157l0.643,-0.257l1.030,-0.515l1.287,0.643l0.387,0.902l1.286,0.515l0.902,-0.644l1.287,-0.129l1.802,0.773l0.772,3.861l-1.158,2.190l-0.644,3.088l1.159,2.317l0.129,-1.030z', + CL: + 'M266.669,369.286l-3.347-1.544l-0.772-1.676l0.644-1.543l-1.288-1.803l-0.386-4.634l1.158-2.573l2.832-2.062l-3.99-0.772l2.445-2.445l1.03-4.506l2.962,1.031l1.416-5.666l-1.802-0.642l-0.902,3.345l-1.674-0.386l0.902-3.862l0.901-5.02l1.159-1.801l-0.773-2.576l-0.129-3.09l1.03-0.129l1.673-4.248l1.932-4.377l1.158-3.99l-0.643-3.99l0.772-2.316l-0.387-3.348l1.674-3.218l0.386-5.278l0.901-5.535l0.902-6.051l-0.259-4.378l-0.513-3.862l1.415-0.644l0.644-1.417l1.286,1.932l0.387,1.934l1.417,1.156l-0.773,2.576l1.417,2.96l1.028,3.733l1.932-0.387l0.386,0.772l-0.902,2.704l-2.96,1.415v4.378l-0.515,0.9l0.772,1.031l-1.802,1.672l-1.802,2.574l-1.03,2.446l0.257,2.575l-1.672,2.831l1.287,4.636l0.772,0.514l-0.129,2.447l-1.415,2.702v2.188l-2.06,1.803v2.445l0.9,2.574l-1.673,1.03l-0.773,2.446l-0.513,2.703l0.385,3.348l-1.03,0.516l0.645,3.09l1.157,1.029l-0.901,1.158l1.287,0.514l0.258,1.03l-1.158,0.515l0.257,1.545l-0.9,3.605l-1.546,2.316l0.386,1.287l-0.901,1.674l-2.06,1.289l0.258,2.83l0.902,0.902l1.801-0.129v2.061l1.16,1.545l6.436,0.385l2.574,0.387h-2.446l-1.288,0.643l-2.444,1.029l-0.387,2.447l-1.159,0.129l-3.09-0.902L266.669,369.286zM283.274,374.822h1.546l-0.902,1.156l-2.316,0.774h-1.288l-1.544-0.256l-1.932-0.774l-2.831-0.386l-3.476-1.545l-2.704-1.416l-3.732-3.09l2.188,0.646l3.862,1.801l3.476,0.901l1.416-1.159l0.901-1.932l2.445-1.029l1.931,0.258l0.129,0.127l-0.129,5.924H283.274z', + CM: + 'M500.439,220.859l-0.256,-0.129l-1.674,0.387l-1.673,-0.387l-1.288,0.129l-4.378,0l0.387,-2.188l-1.029,-1.802l-1.158,-0.387l-0.516,-1.287l-0.772,-0.386l0,-0.643l0.772,-1.932l1.289,-2.575l0.771,-0.128l1.544,-1.545l1.029,0l1.546,1.030l1.803,-0.901l0.257,-1.029l0.644,-1.159l0.387,-1.288l1.414,-1.159l0.645,-1.931l0.513,-0.514l0.387,-1.417l0.773,-1.673l2.188,-2.189l0.129,-0.901l0.387,-0.386l-1.160,-1.158l0.128,-0.773l0.774,-0.257l1.029,1.801l0.258,1.804l-0.128,1.802l1.415,2.446l-1.415,-0.129l-0.774,0.257l-1.287,-0.257l-0.514,1.287l1.545,1.546l1.158,0.385l0.387,1.160l0.900,1.930l-0.515,0.644l-1.287,2.702l-0.643,0.515l-0.258,2.060l0.258,1.160l-0.258,0.772l1.288,1.416l0.257,1.029l1.031,1.416l1.157,0.773l0.129,1.287l0.387,0.772l-0.259,1.416l-2.187,-0.643l-2.062,-0.644l3.475,0.129z', + CN: + 'M760.085,177.992l-2.188-0.902v-2.317l1.288-1.158l2.961-0.773h1.544l0.645,1.031l-1.289,1.287l-0.514,1.545L760.085,177.992zM712.198,152.117l-1.16-0.644l-1.416-0.128l-1.93-0.645l-1.414,0.773l-1.805,1.931l-0.258-2.059l-1.543,0.514l-3.221-0.257l-2.959-0.644l-2.189-1.158l-2.188-0.515l-0.9-1.288l-1.545-0.386l-2.703-1.802l-2.061-0.772l-1.158,0.643l-3.732-1.93l-2.704-1.674l-0.772-2.96l1.932,0.385l0.129-1.416l-1.029-1.416l0.256-2.189l-2.961-3.089l-4.375-1.159l-0.773-2.059l-2.059-1.287l-0.388-0.773l-0.515-1.416l0.129-1.158l-1.674-0.515l-0.772,0.256l-0.772-2.573l0.772-0.516l-0.386-0.643l2.574-1.288l1.93-0.514l2.834,0.257l1.029-1.673l3.476-0.258l0.901-1.158l4.248-1.416l0.387-0.644l-0.26-1.545l1.931-0.643l-2.444-4.635l5.278-1.159l1.415-0.514l1.932-4.892l5.408,0.901l1.416-1.288l0.127-2.704l2.316-0.128l2.061-1.801l1.029-0.258l0.645,1.802l2.317,1.545l3.862,0.901l1.803,2.188l-1.031,3.219l1.031,1.158l3.217,0.387l3.605,0.385l3.217,1.674l1.673,0.386l1.159,2.446l1.672,1.545h2.962l5.536,0.644l3.605-0.386l2.701,0.386l3.861,1.673h3.348l1.159,0.773l3.091-1.416l4.375-0.902l4.121-0.128l3.088-1.03l1.932-1.416l1.931-0.902l-0.515-0.9l-0.774-1.03l1.416-1.674l1.416,0.257l2.832,0.516l2.704-1.417l4.119-1.029l1.932-1.803l1.932-0.772l3.861-0.386l2.189,0.258l0.258-0.902l-2.447-1.931l-2.189-0.772l-2.059,0.901l-2.701-0.386l-1.42,0.386l-0.771-1.158l1.932-2.704l1.286-1.931l3.22,0.9l3.861-1.672v-1.159l2.447-2.832l1.414-0.901v-1.416l-1.545-0.644l2.316-1.417l3.35-0.513h3.475l4.119,0.772l2.316,1.03l1.674,2.703l1.031,1.158l0.9,1.674l1.029,2.574l4.635,0.902l3.219,1.93l1.158,2.447h3.99l2.447-1.03l4.375-0.774l-1.414,2.448l-1.031,1.029l-0.9,2.832l-1.803,2.704l-3.346-0.516l-2.318,0.901l0.771,2.317l-0.385,3.219l-1.416,0.129v1.288l-1.675-1.546l-1.028,1.546l-4.248,1.157l0.387,1.417l-2.319-0.13l-1.286-0.9l-1.803,1.93l-2.961,1.546l-2.189,1.673l-3.732,0.772l-2.059,1.288l-2.832,0.772l1.416-1.288l-0.513-1.028l2.058-1.803l-1.418-1.417l-2.314,0.902l-3.09,1.931l-1.674,1.673l-2.576,0.129l-1.414,1.287l1.414,1.802l2.189,0.387l0.129,1.287l2.061,0.773l3.088-1.931l2.447,1.029l1.672,0.129l0.388,1.416l-3.733,0.772l-1.287,1.416l-2.574,1.288l-1.418,1.931l2.834,1.417l1.158,2.702l1.545,2.446l1.93,2.06l-0.129,2.059l-1.674,0.773l0.645,1.416l1.545,0.773l-0.387,2.187l-0.643,2.189l-1.545,0.258l-1.933,2.832l-2.188,3.604l-2.443,3.219l-3.734,2.446l-3.732,2.317l-3.09,0.258l-1.674,1.157l-0.9-0.772l-1.545,1.287l-3.733,1.416l-2.831,0.386l-0.9,2.833l-1.547,0.129l-0.643-1.931l0.643-1.031l-3.605-0.9l-1.284,0.387l-2.704-0.645l-1.289-1.029l0.387-1.545l-2.445-0.515l-1.287-1.03l-2.316,1.416l-2.576,0.257h-2.187l-1.416,0.644l-1.416,0.386l0.386,3.089h-1.418l-0.256-0.643l-0.128-1.158l-1.931,0.773l-1.16-0.387l-2.059-1.03l0.771-2.317l-1.674-0.515l-0.645-2.446l-2.832,0.386l0.387-3.089l2.445-2.318l0.131-2.188v-2.06l-1.289-0.644l-0.9-1.545l-1.545,0.13l-2.832-0.386l0.9-1.159l-1.285-1.674l-1.934,1.158l-2.314-0.643l-3.092,1.674l-2.445,2.059L712.198,152.117z', + CO: + 'M262.164,227.425l-1.159,-0.644l-1.287,-0.901l-0.772,0.386l-2.318,-0.386l-0.643,-1.157l-0.515,0.127l-2.704,-1.544l-0.386,-0.902l1.031,-0.129l-0.130,-1.416l0.644,-1.029l1.417,-0.129l1.029,-1.674l1.030,-1.416l-0.901,-0.644l0.515,-1.545l-0.644,-2.445l0.515,-0.772l-0.386,-2.318l-1.030,-1.416l0.258,-1.287l0.900,0.257l0.515,-0.901l-0.643,-1.544l0.386,-0.387l1.416,0.129l1.931,-1.931l1.158,-0.258l0,-0.901l0.515,-2.317l1.545,-1.158l1.674,-0.128l0.257,-0.516l2.059,0.257l2.189,-1.415l1.029,-0.644l1.288,-1.288l0.901,0.258l0.773,0.644l-0.516,0.901l-1.802,0.514l-0.644,1.289l-1.029,0.771l-0.772,1.030l-0.387,1.931l-0.772,1.545l1.415,0.129l0.387,1.287l0.644,0.645l0.128,1.028l-0.257,1.030l0,0.516l0.772,0.257l0.644,0.901l3.475,-0.258l1.546,0.387l1.802,2.317l1.158,-0.258l1.931,0.129l1.545,-0.386l0.902,0.515l-0.517,1.416l-0.513,0.901l-0.259,1.931l0.516,1.802l0.773,0.772l0.127,0.644l-1.416,1.287l1.031,0.644l0.772,0.901l0.773,2.703l-0.516,0.387l-0.515,-1.545l-0.773,-0.902l-0.900,0.902l-5.278,0l0.129,1.674l1.545,0.258l-0.129,1.029l-0.515,-0.258l-1.545,0.387l0,1.931l1.159,1.030l0.515,1.544l-0.129,1.159l-1.159,7.338l-1.416,-1.417l-0.772,0l1.802,-2.704l-2.060,-1.287l-1.673,0.259l-1.030,-0.516l-1.416,0.644l-2.060,-0.257l-1.544,-2.832l-1.288,-0.644l-0.772,-1.287l-1.802,-1.288l0.772,-0.258z', + CR: + 'M241.695,204.768l-1.415,-0.515l-0.515,-0.644l0.257,-0.386l-0.128,-0.644l-0.644,-0.643l-1.159,-0.514l-0.901,-0.387l-0.128,-0.773l-0.773,-0.515l0.257,0.901l-0.643,0.644l-0.515,-0.772l-0.901,-0.258l-0.386,-0.644l0,-0.772l0.386,-0.901l-0.772,-0.257l0.644,-0.643l0.386,-0.259l1.801,0.644l0.644,-0.257l0.773,0.128l0.515,0.644l0.772,0.128l0.644,-0.514l0.644,1.416l1.029,1.030l1.287,1.157l-1.029,0.260l0,1.157l0.514,0.387l-0.385,0.257l0.128,0.515l-0.257,0.515l0.130,-0.515z', + CU: + 'M243.626,164.475l2.318,0.257l2.059,0l2.576,0.902l1.028,1.030l2.576,-0.387l0.900,0.644l2.318,1.673l1.673,1.287l0.901,-0.128l1.545,0.644l-0.129,0.772l1.931,0l2.060,1.159l-0.257,0.644l-1.803,0.385l-1.802,0.129l-1.931,-0.257l-3.861,0.257l1.801,-1.544l-1.029,-0.644l-1.802,-0.258l-0.902,-0.772l-0.643,-1.415l-1.546,0l-2.445,-0.645l-0.772,-0.644l-3.604,-0.385l-0.902,-0.515l1.030,-0.644l-2.704,-0.128l-1.930,1.416l-1.030,0l-0.386,0.643l-1.417,0.257l-1.158,-0.257l1.417,-0.772l0.643,-1.030l1.159,-0.515l1.415,-0.515l2.059,-0.257l-0.644,0.387z', + CY: + 'M556.694,132.549l0.129,0.259l-2.704,1.028l-1.417,-0.385l-0.514,-1.030l1.159,-0.129l0.258,0.129l0.127,0l0.130,0l0.257,0l0.257,-0.129l0.260,-0.128l0.127,0.128l0.258,0l0.128,0l0.128,0l0.130,0.129l0,0.258l0.129,-0.130l0.257,0.130l0.128,0l0.131,-0.130l0.128,0l0.128,0l0.129,-0.128l0.128,0l-0.129,-0.128z', + CZ: + 'M510.866,96.119l-1.158,-0.516l-1.286,0.129l-2.061,-0.901l-1.030,0.258l-1.417,1.030l-2.058,-0.772l-1.544,-1.159l-1.288,-0.645l-0.386,-1.157l-0.387,-0.773l1.932,-0.643l1.029,-0.644l1.932,-0.515l0.642,-0.516l0.645,0.259l1.287,-0.259l1.287,0.903l1.932,0.256l-0.129,0.645l1.414,0.644l0.517,-0.773l1.802,0.386l0.257,0.772l1.930,0.129l1.289,1.416l-0.774,0l-0.385,0.515l-0.644,0l-0.256,0.643l-0.517,0.129l0,0.257l-0.900,0.258l-1.288,0l0.387,-0.644z', + DE: + 'M491.945,78.87l0.127,1.028l2.703,0.644l-0.128,0.901l2.831,-0.514l1.417,-0.644l3.090,1.029l1.287,0.901l0.642,1.287l-0.770,0.773l1.029,0.901l0.644,1.417l-0.257,1.030l1.158,1.672l-1.287,0.259l-0.645,-0.259l-0.642,0.516l-1.932,0.515l-1.029,0.644l-1.932,0.643l0.387,0.773l0.386,1.157l1.288,0.645l1.544,1.159l-0.901,1.158l-1.030,0.386l0.387,1.802l-0.258,0.386l-0.773,-0.515l-1.287,-0.128l-1.931,0.515l-2.446,-0.128l-0.387,0.772l-1.285,-0.772l-0.903,0.128l-2.832,-0.772l-0.515,0.514l-2.317,0l0.257,-1.931l1.416,-1.802l-3.861,-0.514l-1.287,-0.773l0.129,-1.159l-0.516,-0.515l0.258,-1.930l-0.386,-2.833l1.544,0l0.773,-0.901l0.644,-2.574l-0.515,-0.902l0.515,-0.515l2.317,-0.129l0.385,0.516l1.933,-1.288l-0.645,-1.029l-0.129,-1.544l2.060,0.385l-1.675,0.385z', + DJ: + 'M581.28,192.797l0.645,0.771l-0.129,1.159l-1.545,0.644l1.158,0.772l-0.900,1.416l-0.645,-0.514l-0.642,0.256l-1.545,-0.128l0,-0.773l-0.257,-0.771l0.901,-1.288l1.030,-1.159l1.158,0.257l-0.771,0.642z', + DK: + 'M488.21,78.87l-1.159,-1.417l0,-2.832l0.387,-0.644l0.772,-0.901l2.447,-0.130l0.900,-0.772l2.188,-0.771l-0.128,1.415l-0.772,0.902l0.385,0.772l1.417,0.386l-0.644,1.029l-0.773,-0.257l-2.060,1.932l0.775,1.288l-1.675,0.385l2.060,0.385zM498.509,75.779l0.900,1.416l-1.545,2.188l-2.831,-1.544l-0.386,-1.158l-3.862,0.902z', + DO: + 'M272.075,173.873l0.259,-0.516l2.187,0l1.545,0.772l0.772,-0.128l0.387,1.030l1.545,-0.129l-0.129,0.901l1.288,0l1.286,1.030l-1.030,1.159l-1.287,-0.644l-1.287,0.129l-0.773,-0.129l-0.514,0.515l-1.030,0.129l-0.387,-0.644l-0.900,0.386l-1.159,1.803l-0.643,-0.387l-0.130,-0.772l0,-0.773l-0.643,-0.772l0.643,-0.515l0.259,-1.029l0.259,1.416z', + DZ: + 'M497.608,163.703l-9.269,5.150l-7.852,5.276l-3.734,1.288l-2.961,0.257l-0.128,-1.801l-1.159,-0.387l-1.672,-0.772l-0.645,-1.288l-9.139,-5.792l-9.140,-5.922l-10.040,-6.566l0,-0.514l0,-3.347l4.377,-1.931l2.703,-0.514l2.188,-0.644l1.030,-1.417l3.090,-1.029l0.128,-2.061l1.545,-0.128l1.287,-1.030l3.476,-0.515l0.515,-1.030l-0.772,-0.514l-0.902,-2.832l-0.128,-1.674l-1.030,-1.674l2.574,-1.545l2.962,-0.515l1.673,-1.029l2.574,-0.902l4.633,-0.385l4.377,-0.258l1.416,0.385l2.575,-1.028l2.833,0l1.029,0.643l1.930,-0.258l-0.642,1.416l0.514,2.575l-0.642,2.189l-1.674,1.545l0.257,2.059l2.187,1.545l0,0.643l1.674,1.159l1.159,4.763l0.903,2.446l0.126,1.158l-0.513,2.318l0.256,1.158l-0.387,1.546l0.259,1.673l-1.030,1.030l1.546,2.059l0.127,1.159l0.902,1.415l1.286,-0.385l2.060,1.158l-1.288,-1.674z', + EC: + 'M248.905,236.179l1.415,-2.060l-0.514,-1.159l-1.031,1.288l-1.672,-1.160l0.515,-0.772l-0.387,-2.445l0.901,-0.516l0.515,-1.673l1.030,-1.674l-0.258,-1.158l1.545,-0.514l1.802,-1.030l2.704,1.544l0.515,-0.127l0.643,1.157l2.318,0.386l0.772,-0.386l1.287,0.901l1.159,0.644l0.386,2.059l-0.772,1.674l-2.961,2.832l-3.219,1.030l-1.673,2.446l-0.514,1.802l-1.545,1.030l-1.159,-1.286l-1.030,-0.388l-1.159,0.257l0,-1.029l0.773,-0.643l0.386,1.030z', + EE: + 'M530.69,71.273l0.387-1.544l-1.029,0.257l-1.674-0.9l-0.256-1.545l3.344-0.773l3.478-0.386l2.833,0.515l2.831-0.129l0.386,0.515l-1.931,1.544l0.9,2.446l-1.158,0.901h-2.317l-2.316-1.028l-1.158-0.387L530.69,71.273z', + EG: + 'M559.269,147.483l-0.773,1.158l-0.514,1.931l-0.771,1.417l-0.645,0.514l-0.901-0.901l-1.159-1.158l-1.93-3.862l-0.258,0.258l1.158,2.831l1.546,2.703l2.059,4.119l1.03,1.545l0.902,1.545l2.316,2.961l-0.517,0.386l0.13,1.802l3.089,2.447l0.259,0.514h-10.299h-10.557h-10.812v-9.912v-9.526l-0.901-2.189l0.772-1.673l-0.388-1.159l0.903-1.287h3.604l2.574,0.644l2.705,0.773l1.287,0.514l2.059-0.901l1.029-0.772l2.447-0.258l1.93,0.386l0.643,1.287l0.646-0.9l2.187,0.644l2.061,0.128l1.415-0.644L559.269,147.483z', + EH: + 'M441.482,153.92l0,-1.417l0.387,0l0,0.129l0,0.514l0,4.120l-8.883,-0.129l0.129,6.823l-2.574,0.257l-0.644,1.417l0.515,3.862l-10.557,0l-0.643,0.901l0.129,-1.159l0.129,0l6.050,-0.129l0.257,-1.029l1.159,-1.159l0.901,-3.733l3.733,-2.961l1.287,-3.347l0.773,-0.257l0.900,-2.060l2.319,-0.257l0.900,0.257l1.288,0l0.901,-0.515l-1.544,0.128z', + ER: + 'M579.351,193.182l-0.901,-0.901l-1.160,-1.545l-1.158,-0.902l-0.773,-0.900l-2.317,-1.030l-1.801,-0.129l-0.644,-0.514l-1.674,0.643l-1.544,-1.287l-0.900,2.059l-3.091,-0.514l-0.258,-1.160l1.160,-3.861l0.258,-1.802l0.770,-0.901l2.061,-0.386l1.288,-1.546l1.543,3.090l0.773,2.446l1.545,1.288l3.604,2.574l1.545,1.545l1.415,1.544l0.903,0.902l1.285,0.902l-0.771,0.642l1.158,0.257z', + ES: + 'M440.838,114.141l0.129,-1.931l-1.029,-1.158l3.861,-1.932l3.219,0.515l3.604,0l2.960,0.387l2.189,-0.129l4.377,0.129l1.029,1.030l5.021,1.158l0.901,-0.514l3.089,1.158l3.090,-0.258l0.129,1.545l-2.574,1.802l-3.478,0.516l-0.127,0.900l-1.672,1.545l-1.031,2.189l1.031,1.544l-1.547,1.159l-0.642,1.803l-2.061,0.514l-1.802,2.060l-3.476,0l-2.574,0l-1.673,0.901l-1.031,1.030l-1.287,-0.129l-1.030,-1.030l-0.772,-1.545l-2.446,-0.385l-0.257,-0.902l1.030,-1.030l0.258,-0.644l-0.902,-0.900l0.772,-1.674l-1.030,-1.674l1.160,-0.256l0,-1.159l0.514,-0.387l0,-2.189l1.287,-0.643l-0.773,-1.416l-1.545,-0.128l-0.514,0.385l-1.545,0l-0.643,-1.287l-1.158,0.387l1.031,-0.643z', + ET: + 'M579.351,193.182l-1.030,1.159l-0.901,1.288l0.257,0.771l0,0.773l1.545,0.128l0.642,-0.256l0.645,0.514l-0.645,0.901l1.032,1.545l1.029,1.287l1.029,0.901l8.754,3.218l2.316,0l-7.722,8.110l-3.475,0.129l-2.318,1.932l-1.803,0l-1.029,0.644l-1.030,0.256l-1.931,-1.158l-2.445,1.287l-1.030,1.159l-1.031,-0.387l-0.900,0.258l-1.159,-0.385l-0.772,-0.130l-3.089,-2.574l-2.318,0l-0.129,-0.644l-0.772,-1.288l-1.159,-0.515l-1.158,-2.832l-1.286,-0.644l-0.388,-1.158l-1.416,-1.287l-1.673,-0.129l0.901,-1.545l1.416,-0.127l0.386,-0.774l0,-2.447l0.774,-2.831l1.286,-0.772l0.259,-1.030l1.158,-2.060l1.672,-1.415l1.158,-2.575l0.387,-2.317l3.091,0.514l0.900,-2.059l1.544,1.287l1.674,-0.643l0.644,0.514l1.801,0.129l2.317,1.030l0.773,0.900l1.158,0.902l1.160,1.545l-0.901,-0.901z', + FI: + 'M542.276,40.893l-0.384,1.932l4.119,1.801l-2.448,2.060l3.089,2.960l-1.801,2.318l2.445,2.060l-1.157,1.802l3.991,1.802l-1.030,1.416l-2.448,1.545l-5.792,3.347l-4.890,0.257l-4.764,1.030l-4.377,0.515l-1.545,-1.416l-2.574,-0.901l0.514,-2.704l-1.286,-2.445l1.286,-1.545l2.447,-1.673l6.180,-2.961l1.800,-0.515l-0.256,-1.159l-3.734,-1.286l-0.901,-1.031l-0.128,-4.120l-4.250,-1.801l-3.475,-1.417l1.545,-0.643l2.961,1.416l3.606,-0.129l2.832,0.644l2.572,-1.159l1.289,-2.060l4.247,-0.900l3.476,1.157l1.159,-1.803z', + FJ: + 'M946.097,274.154l0.773,-0.514l0.901,0.772l-0.516,1.416l-1.672,0.385l-1.418,-0.256l-0.256,-1.289l1.029,-0.900l-1.159,-0.386zM950.089,271.579l-1.160,0.773l-1.545,0.644l-0.385,-1.287l1.031,-1.030l0.899,-0.130l1.160,-0.256l-0.001,0l0.515,-0.129l-0.387,1.287l-0.128,0.128l-0.001,0z', + FK: + 'M302.584,365.296l-0.129,1.159l-1.03,1.416l2.188-1.031l1.158-1.286L302.584,365.296zM307.733,365.037l1.159,0.388l-0.902,1.415l-2.188,0.772l-0.257-0.9l1.288-1.416L307.733,365.037z', + FR: + 'M481.903,93.673l1.287,0.773l3.861,0.514l-1.416,1.802l-0.257,1.931l-0.772,0.515l-1.288,-0.257l0.130,0.644l-1.932,1.545l0,1.158l1.288,-0.386l0.901,1.159l-0.128,0.772l0.772,1.029l-0.901,0.774l0.642,2.058l1.418,0.386l-0.258,1.160l-2.446,1.544l-5.277,-0.772l-3.992,0.901l-0.257,1.673l-3.090,0.258l-3.089,-1.158l-0.901,0.514l-5.021,-1.158l-1.029,-1.030l1.416,-1.674l0.515,-5.277l-2.832,-2.833l-2.060,-1.415l-3.991,-1.031l-0.386,-1.931l3.604,-0.644l4.506,0.773l-0.901,-3.090l2.575,1.159l6.306,-2.060l0.775,-2.317l2.317,-0.515l0.385,1.031l1.287,0l1.288,1.029l1.801,1.287l1.416,-0.257l2.318,1.286l0.643,0.259l-0.773,0.129zM488.854,112.082l1.674,-1.030l0.514,2.317l-0.899,2.188l-1.289,-0.643l-0.644,-1.803l-0.644,1.029z', + GA: + 'M495.162,237.723l-2.833-2.703l-1.801-2.316l-1.544-2.704V229.1l0.642-0.902l0.644-1.932l0.516-2.06l0.903-0.128h3.987l-0.128-3.219l1.288-0.129l1.673,0.387l1.674-0.387l0.257,0.129l-0.127,1.16l0.643,1.414l2.06-0.258l0.643,0.516l-1.157,3.219l1.286,1.545l0.258,2.059l-0.258,1.803l-0.9,1.287l-2.318-0.129l-1.416-1.287l-0.256,1.287l-1.803,0.258l-0.9,0.643l1.028,1.805L495.162,237.723z', + GB: + 'M444.829,78.483l2.317-0.129l2.831,1.673l-1.415,1.803l-2.061-0.516h-1.673l0.515-1.416L444.829,78.483zM453.84,69.214l3.347-0.257l-2.961,2.96l2.832-0.386h2.832l-0.643,2.189l-2.446,2.446l2.832,0.256l2.575,3.348l1.801,0.515l1.674,3.089l0.773,1.03l3.347,0.515l-0.387,1.674L468,87.365l1.159,1.416l-2.446,1.417h-3.604l-4.634,0.772l-1.158-0.516l-1.804,1.159l-2.573-0.257l-1.803,1.03l-1.415-0.515l3.86-2.832l2.446-0.644l-4.247-0.386l-0.772-1.03l2.831-0.901l-1.416-1.416l0.516-1.803l3.99,0.258l0.387-1.545l-1.804-1.674l-3.346-0.515l-0.646-0.772l1.031-1.158l-0.9-0.772l-1.416,1.286l-0.259-2.573l-1.286-1.417l0.9-2.704l2.189-2.187L453.84,69.214z', + GE: + 'M577.161,115.042l0.387-1.159l-0.643-1.801l-1.546-1.03l-1.544-0.258l-0.9-0.772l0.256-0.387l2.318,0.516l3.989,0.386l3.604,1.287l0.517,0.515l1.672-0.387l2.445,0.516l0.772,1.158l1.803,0.644l-0.771,0.257l1.287,1.545l-0.258,0.258l-1.545-0.13l-1.93-0.772l-0.645,0.387l-3.733,0.515l-2.702-1.416L577.161,115.042z', + GF: + 'M319.834,211.463l0.902,0.256l2.058,0.645l2.833,2.316l0.386,1.159l-1.545,2.446l-0.771,1.93l-1.03,1.031l-1.287,0.256l-0.387-0.771l-0.644-0.129l-0.771,0.645l-1.287-0.516l0.772-1.158l0.257-1.159l0.386-1.157l-1.029-1.674l-0.259-1.803L319.834,211.463z', + GH: + 'M468.13,210.946l-4.249,1.674l-1.545,0.901l-2.446,0.773l-2.317,-0.773l0.129,-1.030l-1.159,-2.317l0.644,-3.088l1.158,-2.190l-0.772,-3.861l-0.386,-2.060l0.129,-1.545l4.634,-0.129l1.289,0.258l0.770,-0.514l1.288,0.256l-0.258,0.772l1.159,1.417l0,1.932l0.258,2.187l0.643,1.030l-0.514,2.318l0.128,1.416l0.773,1.673l-0.644,-0.900z', + GL: + 'M339.272,4.333l9.011,-1.544l9.525,0.128l3.348,-1.029l9.526,-0.258l21.497,0.386l16.864,2.060l-4.892,1.029l-10.298,0.129l-14.546,0.258l1.287,0.515l9.654,-0.257l8.110,0.901l5.149,-0.773l2.317,0.901l-2.961,1.545l6.824,-1.030l13.130,-1.030l7.981,0.515l1.545,1.159l-10.942,1.931l-1.546,0.644l-8.625,0.514l6.180,0.129l-3.089,1.931l-2.189,1.802l0.129,2.961l3.218,1.674l-4.249,0.128l-4.376,0.902l4.893,1.415l0.643,2.318l-2.832,0.257l3.476,2.317l-5.923,0.129l3.091,1.159l-0.902,0.900l-3.733,0.387l-3.862,0l3.476,1.931l0,1.158l-5.407,-1.158l-1.287,0.773l3.604,0.644l3.476,1.673l1.030,2.188l-4.763,0.515l-2.060,-1.031l-3.347,-1.544l0.901,1.803l-3.090,1.416l7.081,0.129l3.733,0.128l-7.208,2.316l-7.338,2.189l-7.852,0.902l-2.962,0l-2.831,1.030l-3.734,2.832l-5.793,1.931l-1.930,0.128l-3.604,0.644l-3.862,0.644l-2.317,1.673l0,1.802l-1.288,1.802l-4.505,2.189l1.158,2.060l-1.287,2.188l-1.287,2.703l-3.863,0.129l-3.989,-2.188l-5.278,0l-2.704,-1.545l-1.802,-2.574l-4.635,-3.347l-1.415,-1.803l-0.258,-2.316l-3.732,-2.576l0.900,-1.930l-1.802,-1.031l2.703,-3.088l3.991,-1.031l1.159,-1.158l0.515,-2.059l-3.476,-0.259l-6.179,-1.416l2.189,0l6.049,0l-4.634,-1.801l-2.446,-0.902l-4.892,-0.258l2.960,-2.445l-1.544,-1.030l-2.188,-1.931l-3.218,-2.832l-3.475,-1.030l0.128,-1.159l-7.338,-1.545l-5.664,-0.257l-7.208,0.129l-6.565,0.257l-3.090,-0.901l-4.763,-1.673l7.081,-0.901l5.405,-0.130l-11.457,-0.643l-6.050,-1.158l0.387,-1.030l10.169,-1.288l9.784,-1.287l1.030,-1.030l-7.210,-0.901l2.318,-1.029l9.397,-1.931l3.862,-0.258l-1.159,-1.287l6.437,-0.644l8.238,-0.387l8.368,-0.128l2.832,0.901l7.209,-1.545l6.436,1.030l3.347,1.159l6.050,0l-6.436,-1.545l-0.386,1.159z', + GM: + 'M419.855,191.51l0.387,-1.160l2.961,-0.129l0.515,-0.643l0.901,0l1.030,0.643l0.901,0l0.900,-0.387l0.516,0.773l-1.159,0.644l-1.158,-0.128l-1.159,-0.516l-1.030,0.644l-0.514,0l-0.644,0.386l2.447,0.127z', + GN: + 'M442.512,206.313l-0.772,-0.129l-0.515,1.158l-0.772,-0.128l-0.515,-0.515l0.128,-1.029l-1.158,-1.674l-0.644,0.257l-0.643,0.130l-0.644,0.127l0,-1.030l-0.387,-0.642l0,-0.773l-0.515,-1.159l-0.772,-1.029l-2.188,0l-0.644,0.514l-0.772,0.129l-0.386,0.515l-0.387,0.772l-1.415,1.159l-1.159,-1.544l-1.030,-1.031l-0.644,-0.386l-0.772,-0.515l-0.257,-1.159l-0.386,-0.643l-0.773,-0.515l1.159,-1.287l0.901,0.128l0.644,-0.515l0.643,0l0.386,-0.386l-0.257,-0.901l0.257,-0.257l0.129,-0.901l1.287,0l1.931,0.643l0.643,0l0.130,-0.258l1.544,0.129l0.387,-0.129l0.128,1.030l0.387,0l0.772,-0.387l0.386,0.130l0.772,0.643l1.159,0.258l0.772,-0.644l0.773,-0.387l0.643,-0.385l0.515,0.129l0.644,0.643l0.386,0.644l1.030,1.158l-0.516,0.645l-0.128,0.900l0.644,-0.257l0.257,0.386l-0.128,0.773l0.772,0.772l-0.515,0.257l-0.129,0.901l0.516,1.031l0.772,2.187l-1.030,0.387l-0.258,0.257l0.129,0.644l-0.129,1.159l0.386,0z', + GQ: + 'M490.785,224.206l-0.515,-0.387l0.900,-2.960l4.378,0l0.128,3.219l-3.988,0l0.903,-0.128z', + GR: + 'M536.099,131.906l-0.387,0.773l-3.861,0.257l0,-0.515l-3.219,-0.515l0.387,-1.159l1.543,0.902l2.060,-0.129l2.059,0.257l-0.127,0.387l-1.545,0.258zM521.808,116.973l1.804,-0.258l1.029,-0.643l1.417,0.128l0.515,-0.513l0.514,-0.130l1.932,0.130l2.187,-0.773l1.930,1.029l2.445,-0.256l0,-1.417l1.289,0.772l-0.771,1.673l-0.645,0.258l-1.674,0l-1.416,-0.258l-3.218,0.644l1.802,1.545l-1.287,0.387l-1.543,0l-1.418,-1.417l-0.514,0.645l0.643,1.672l1.289,1.159l-1.031,0.644l1.545,1.286l1.286,0.774l0.130,1.545l-2.575,-0.773l0.772,1.417l-1.672,0.256l1.028,2.317l-1.800,0.129l-2.189,-1.287l-1.030,-2.059l-0.516,-1.803l-1.030,-1.288l-1.287,-1.545l-0.258,-0.772l1.288,-1.287l0.128,-0.901l0.901,-0.386l0,0.644z', + GT: + 'M222.516,189.963l-1.417,-0.514l-1.673,0l-1.159,-0.515l-1.544,-1.159l0.128,-0.773l0.257,-0.643l-0.385,-0.514l1.416,-2.188l3.347,0l0.128,-0.903l-0.385,-0.128l-0.387,-0.644l-1.030,-0.643l-0.901,-0.901l1.158,0l0,-1.416l2.575,0l2.446,0l0,2.060l-0.257,3.089l0.772,0l0.901,0.515l0.258,-0.386l0.771,0.257l-1.158,1.030l-1.287,0.772l-0.257,0.516l0.257,0.514l-0.515,0.773l-0.644,0.129l0.129,0.258l-0.515,0.385l-0.901,0.644l0.128,-0.385z', + GW: + 'M424.49,197.173l-1.416,-1.030l-1.159,-0.257l-0.643,-0.773l0,-0.386l-0.772,-0.515l-0.258,-0.644l1.545,-0.386l0.901,0.129l0.644,-0.386l5.020,0.129l-0.129,0.901l-0.257,0.257l0.257,0.901l-0.386,0.386l-0.643,0l-0.644,0.515l-0.901,-0.128l1.159,-1.287z', + GY: + 'M304.257,204.383l1.804,1.028l1.672,1.803l0,1.415l1.030,0l1.417,1.289l1.157,1.028l-0.514,2.319l-1.545,0.772l0.129,0.643l-0.514,1.416l1.157,1.931l0.902,0l0.385,1.545l1.545,2.317l-0.643,0.130l-1.416,-0.259l-0.901,0.644l-1.288,0.515l-0.772,0.128l-0.386,0.515l-1.287,-0.128l-1.674,-1.288l-0.128,-1.287l-0.773,-1.287l0.515,-2.316l0.772,-0.902l-0.644,-1.288l-0.900,-0.386l0.257,-1.159l-0.644,-0.643l-1.287,0.129l-1.930,-2.061l0.772,-0.772l0,-1.287l1.673,-0.385l0.644,-0.516l-0.902,-1.029l0.130,-0.902l-2.187,1.672z', + HN: + 'M229.981,192.023l-0.385,-0.900l-0.902,-0.258l0.258,-1.031l-0.386,-0.256l-0.515,-0.258l-1.287,0.386l0,-0.386l-0.902,-0.386l-0.515,-0.643l-0.772,-0.129l0.515,-0.773l-0.257,-0.514l0.257,-0.516l1.287,-0.772l1.158,-1.030l0.258,0.129l0.644,-0.386l0.772,-0.129l0.257,0.258l0.386,-0.129l1.288,0.257l1.288,-0.128l0.772,-0.258l0.386,-0.258l0.773,0.129l0.643,0.129l0.772,0l0.515,-0.258l1.287,0.387l0.387,0l0.772,0.515l0.773,0.643l1.030,0.387l0.643,0.772l-0.901,-0.128l-0.386,0.386l-0.902,0.386l-0.643,0l-0.643,0.385l-0.516,-0.127l-0.514,-0.517l-0.258,0.130l-0.258,0.643l-0.257,0l-0.129,0.516l-0.900,0.771l-0.515,0.258l-0.258,0.386l-0.773,-0.515l-0.643,0.643l-0.515,0l-0.643,0.129l0,1.288l-0.387,0l-0.257,0.644l0.902,-0.128z', + HR: + 'M516.017,103.327l0.643,1.031l0.773,0.772l-1.030,1.029l-1.160,-0.643l-1.931,0l-2.316,-0.386l-1.159,0l-0.643,0.643l-1.031,-0.643l-0.516,1.159l1.290,1.158l0.643,0.901l1.286,1.031l0.901,0.514l1.031,1.159l2.445,1.030l-0.258,0.514l-2.572,-1.030l-1.547,-1.029l-2.444,-0.773l-2.318,-1.931l0.514,-0.257l-1.157,-1.159l-0.130,-0.901l-1.674,-0.386l-0.898,1.159l-0.774,-0.901l0.128,-0.902l0.129,-0.128l1.802,0.128l0.516,-0.386l0.901,0.386l1.030,0l0,-0.772l0.901,-0.257l0.255,-1.030l2.190,-0.773l0.902,0.386l1.930,1.159l2.316,0.515l-1.032,0.387zM502.372,101.654l2.315,0.258l1.289,-0.644l2.446,0l0.515,-0.515l0.385,0l0.515,0.901l-2.190,0.773l-0.255,1.030l-0.901,0.257l0,0.772l-1.030,0l-0.901,-0.386l-0.516,0.386l-1.802,-0.128l0.517,-0.258l-0.646,-1.029l-0.259,1.417z', + HT: + 'M268.085,173.357l1.673,0.129l2.317,0.387l0.259,1.416l-0.259,1.029l-0.643,0.515l0.643,0.772l0,0.773l-1.802,-0.515l-1.287,0.257l-1.673,-0.257l-1.159,0.515l-1.545,-0.773l0.258,-0.900l2.446,0.385l2.060,0.258l1.029,-0.643l-1.288,-1.159l0,-1.030l-1.673,-0.387l-0.644,0.772z', + HU: + 'M508.937,100.753l0.900,-1.674l-0.643,-0.643l1.545,0l0.257,-1.158l1.288,0.772l1.028,0.257l2.318,-0.257l0.129,-0.644l1.158,0l1.287,-0.515l0.258,0.258l1.287,-0.387l0.645,-0.643l0.900,-0.129l2.832,0.772l0.645,-0.257l1.415,0.773l0.256,0.643l-1.671,0.643l-1.290,1.803l-1.673,1.802l-2.059,0.515l-1.672,-0.129l-2.060,0.772l-1.032,0.387l-2.316,-0.515l-1.930,-1.159l-0.902,-0.386l-0.515,-0.901l0.385,0z', + ID: + 'M801.921,250.982l0.258,0.515v0.772l-1.674,2.061l-2.317,0.516l-0.386-0.258l0.258-0.902l1.158-1.674L801.921,250.982zM826.767,245.576l-0.258-2.059l0.516-0.902l0.516-1.03l0.643,0.901v1.285L826.767,245.576zM845.175,242.742v8.755l-2.447-2.188l-2.701-0.514l-0.645,0.771l-3.475,0.129l1.155-2.189l1.677-0.771l-0.645-2.963l-1.287-2.316l-5.279-2.188l-2.188-0.256l-3.992-2.447l-0.898,1.287l-1.031,0.258l-0.516-1.03v-1.157l-2.059-1.288l2.832-1.03h1.932l-0.26-0.644h-3.859l-1.16-1.674l-2.314-0.515l-1.16-1.287l3.605-0.644l1.414-0.901l4.248,1.16l0.516,1.027l0.771,4.248l2.705,1.676l2.316-2.833l3.091-1.674h2.315l2.318,0.901l2.059,1.029l2.832,0.516L845.175,242.742zM761.116,223.434l1.801,1.416l1.803-0.514l1.672,0.257l1.546-1.417l1.288-0.257l2.574,0.772l2.189-0.516l1.414-3.861l1.031-0.901l0.9-3.089h3.09l2.316,0.515l-1.545,2.446l2.059,2.574l-0.514,1.16l3.09,2.573l-3.217,0.257l-0.902,1.803l0.129,2.447l-2.575,1.93l-0.13,2.574l-1.029,4.119l-0.387-0.9l-3.088,1.158l-1.03-1.543l-1.931-0.258l-1.287-0.773l-3.219,0.9l-1.029-1.287l-1.801,0.129l-2.188-0.256l-0.388-3.606l-1.416-0.772l-1.287-2.316l-0.26-2.317l0.26-2.573l1.546-1.675L761.116,223.434zM813.765,234.505l2.961,0.772l0.902,2.059l-2.19-1.029l-2.317-0.256l-1.545,0.129h-1.801l0.643-1.546L813.765,234.505zM807.069,237.209l-1.93-0.516l-0.516-1.158l2.705-0.129l0.643,0.9L807.069,237.209zM809.903,221.117l0.129,1.416l1.674,0.258l0.256,1.158l-0.256,2.316l-1.289-0.258l-0.514,1.674l1.159,1.418l-0.774,0.256l-1.029-1.674l-0.771-3.476l0.514-2.06L809.903,221.117zM796.386,224.593l3.09-0.13l2.703-1.93l0.387,0.643l-2.061,2.704l-2.059,0.515l-2.574-0.644l-4.506,0.257l-2.316,0.387l-0.387,1.932l2.315,2.445l1.546-1.158l5.021-1.031l-0.258,1.289l-1.158-0.387l-1.16,1.545l-2.445,1.029l2.574,3.477l-0.514,0.902l2.445,3.217v1.674l-1.416,0.771l-1.029-0.901l1.287-2.187l-2.703,1.028l-0.645-0.772l0.385-1.031l-1.931-1.543l0.132-2.574l-1.805,0.773l0.257,3.088l0.13,3.861l-1.801,0.387l-1.16-0.773l0.773-2.443l-0.389-2.574l-1.156-0.131l-0.771-1.802l1.158-1.802l0.385-2.061l1.288-4.119l0.515-1.029l2.317-2.061l2.188,0.772L796.386,224.593zM789.306,254.588l-3.604-1.804l2.574-0.644l1.416,0.902l0.902,0.771l-0.131,0.773H789.306zM792.138,249.953l1.803-0.129l2.316-1.029l-0.385,1.544l-3.992,0.644l-3.604-0.258v-1.029l2.188-0.516L792.138,249.953zM783.771,249.566l1.673-0.258l0.645,1.158l-3.09,0.516l-1.803,0.387h-1.545l1.029-1.674h1.416l0.773-0.9L783.771,249.566zM757.511,244.287l0.386,0.902l5.149,0.258l0.514-1.031l5.021,1.288l1.029,1.674l3.99,0.515l3.35,1.674l-3.092,1.031l-2.962-1.16l-2.444,0.129l-2.832-0.258l-2.445-0.514l-3.219-0.902l-1.932-0.387l-1.158,0.387l-4.891-1.158l-0.387-1.158l-2.574-0.129l1.93-2.574l3.219,0.127l2.189,1.031l-1.157-0.256L757.511,244.287zM746.438,229.871l0.388,1.932l0.903,1.415l2.058,0.257l1.289,1.803l-0.645,3.347l-0.129,4.118h-2.961l-2.316-2.188l-3.477-2.188l-1.158-1.674l-2.059-2.188l-1.289-2.06l-2.062-3.733l-2.313-2.188l-0.775-2.317l-1.027-2.187l-2.447-1.674l-1.416-2.318l-2.06-1.416l-2.705-3.09l-0.256-1.287l1.675,0.129l4.247,0.515l2.317,2.575l2.058,1.803l1.548,1.157l2.571,2.962h2.706l2.188,1.801l1.674,2.318l2.06,1.158l-1.157,2.188l1.545,1.03h-1.027H746.438z', + IE: + 'M448.562,81.83l0.387,1.931l-2.061,2.445l-4.764,1.544l-3.732-0.385l2.188-2.832l-1.415-2.703l3.604-2.06l2.06-1.287l0.515,1.415l-0.515,1.416h1.673L448.562,81.83z', + IL: + 'M561.458,138.857l-0.516,0.902l-0.900,-0.387l-0.645,1.803l0.774,0.258l-0.774,0.385l-0.128,0.644l1.287,-0.257l0.130,1.029l-1.417,4.249l-1.674,-4.635l0.773,-0.901l-0.258,-0.129l0.772,-1.287l0.515,-1.931l0.385,-0.773l0.130,0l0.900,0l0.259,-0.515l0.643,0l0,1.160l0.256,-0.385z', + IN: + 'M674.866,131.391l2.961,3.089l-0.256,2.189l1.03,1.416l-0.13,1.416l-1.932-0.385l0.773,2.96l2.703,1.674l3.732,1.93l-1.672,1.16l-1.16,2.573l2.703,1.031l2.447,1.287l3.604,1.545l3.604,0.386l1.674,1.287l2.059,0.257l3.22,0.644h2.188l0.385-1.158l-0.385-1.674l0.258-1.159l1.543-0.514l0.259,2.059l0.129,0.516l2.446,1.029l1.673-0.386l2.188,0.129h2.188l0.257-1.674l-1.158-0.901l2.188-0.258l2.444-2.059l3.092-1.674l2.314,0.643l1.934-1.158l1.285,1.674l-0.899,1.159l2.832,0.386l0.258,1.029l-1.03,0.515l0.256,1.674l-1.93-0.515l-3.475,1.802l0.127,1.545l-1.545,2.317l-0.127,1.287l-1.159,2.189l-2.188-0.515v2.704l-0.642,0.9l0.255,1.159l-1.287,0.643l-1.416-4.247h-0.771l-0.387,1.802l-1.545-1.416l0.901-1.545l1.157-0.129l1.289-2.317l-1.545-0.515l-2.573,0.128l-2.574-0.386l-0.26-1.931l-1.285-0.128l-2.062-1.159l-1.03,1.803l2.06,1.415l-1.802,1.03L702.544,161l1.673,0.643l-0.515,1.674l0.901,2.059l0.515,2.188l-0.387,1.03l-1.931-0.128l-3.218,0.643l0.129,1.931l-1.416,1.674l-3.86,1.802l-3.092,3.218l-2.06,1.675l-2.574,1.673l-0.129,1.287l-1.287,0.644l-2.446,1.029l-1.287,0.129l-0.772,2.059l0.646,3.476l0.127,2.189l-1.159,2.574v4.635l-1.414,0.128l-1.289,2.06l0.903,0.901l-2.448,0.772l-0.9,1.802l-1.157,0.772l-2.576-2.574l-1.159-3.734l-1.027-2.703l-1.03-1.287l-1.416-2.575l-0.646-3.347l-0.513-1.674l-2.448-3.733l-1.029-5.278l-0.899-3.346v-3.347l-0.517-2.446l-3.86,1.544l-1.932-0.257l-3.476-3.347l1.287-0.901l-0.772-1.159l-3.218-2.188l1.801-1.802h5.922l-0.514-2.317l-1.545-1.417l-0.258-2.059l-1.802-1.159l2.961-2.832l3.218,0.129l2.704-2.833l1.802-2.702l2.575-2.704v-1.931l2.187-1.545l-2.059-1.287l-1.031-1.802l-0.899-2.447l1.286-1.157l4.121,0.643l2.961-0.386L674.866,131.391z', + IQ: + 'M585.658,126.628l0.128,0l1.803,3.476l1.802,0.772l0.130,1.545l-1.289,0.902l-0.643,2.187l1.802,2.575l3.347,1.416l1.415,2.060l-0.514,1.931l0.901,0l0,1.416l1.545,1.416l-1.674,-0.128l-1.803,-0.258l-1.930,2.703l-5.020,-0.258l-7.596,-5.406l-3.990,-1.931l-3.218,-0.773l-1.158,-3.218l6.051,-2.832l1.029,-3.218l-0.258,-1.931l1.417,-0.773l1.416,-1.673l1.158,-0.385l3.091,0.385l0.899,0.643l1.287,-0.385l0.128,0.258z', + IR: + 'M610.502,126.756l2.317,-0.513l1.932,-1.546l1.803,0.129l1.157,-0.515l1.932,0.257l2.961,1.288l2.188,0.387l3.088,2.317l2.060,0.128l0.129,2.188l-1.029,3.477l-0.773,1.930l1.158,0.386l-1.158,1.416l0.902,2.188l0.256,1.674l2.060,0.515l0.129,1.673l-2.445,2.447l1.414,1.415l1.031,1.674l2.574,1.159l0.128,2.446l1.288,0.386l0.259,1.287l-3.992,1.288l-1.030,3.218l-5.020,-0.902l-2.961,-0.515l-2.961,-0.386l-1.160,-3.346l-1.285,-0.515l-2.058,0.515l-2.706,1.287l-3.345,-0.901l-2.705,-2.060l-2.575,-0.773l-1.800,-2.446l-2.061,-3.604l-1.416,0.387l-1.674,-0.902l-1.029,1.030l-1.545,-1.416l0,-1.416l-0.901,0l0.514,-1.931l-1.415,-2.060l-3.347,-1.416l-1.802,-2.575l0.643,-2.187l1.289,-0.902l-0.130,-1.545l-1.802,-0.772l-1.803,-3.476l-0.128,0l-1.288,-1.931l0.516,-0.901l-0.773,-3.089l1.802,-0.772l0.387,1.028l1.415,1.288l1.804,0.386l1.029,-0.129l3.089,-1.930l1.030,-0.258l0.773,0.773l-0.902,1.415l1.674,1.417l0.643,-0.129l0.901,1.931l2.575,0.516l1.803,1.415l3.862,0.385l4.247,-0.643l-0.257,0.644z', + IS: + 'M426.163,47.974l-0.644,1.672l3.09,1.932l-3.604,2.059l-7.723,1.802l-2.318,0.515l-3.475-0.385l-7.596-0.902l2.703-1.158l-5.922-1.287l4.763-0.516l-0.128-0.9l-5.663-0.644l1.93-1.674l3.991-0.386l4.248,1.803l4.118-1.417l3.349,0.645l4.376-1.417L426.163,47.974z', + IT: + 'M493.361,100.624l1.672,0.386l0.258,-0.514l2.703,-0.386l0.644,0.900l3.734,0.644l-0.259,1.417l0.646,1.029l-2.063,-0.386l-2.315,1.030l0.257,1.287l-0.387,0.772l0.900,1.417l2.577,1.287l1.287,2.317l2.961,2.189l2.187,-0.130l0.645,0.644l-0.773,0.515l2.445,1.030l1.933,0.772l2.315,1.416l0.257,0.516l-0.513,0.900l-1.417,-1.157l-2.316,-0.516l-1.159,1.803l1.931,0.901l-0.387,1.416l-1.030,0.257l-1.544,2.317l-1.029,0.129l0,-0.772l0.514,-1.417l0.644,-0.643l-1.158,-1.545l-0.772,-1.417l-1.160,-0.256l-0.772,-1.159l-1.673,-0.515l-1.159,-1.030l-2.060,-0.257l-2.061,-1.159l-2.444,-1.802l-1.933,-1.545l-0.772,-2.703l-1.286,-0.258l-2.189,-0.901l-1.288,0.386l-1.545,1.287l-1.157,0.130l0.258,-1.160l-1.418,-0.386l-0.642,-2.058l0.901,-0.774l-0.772,-1.029l0.128,-0.772l1.160,0.643l1.287,-0.128l1.543,-1.031l0.387,0.516l1.288,-0.129l0.643,-1.030l1.932,0.257l1.158,-0.386l-0.258,1.159zM504.944,124.183l2.061,-0.258l-0.901,2.188l0.387,0.773l-0.644,1.415l-2.061,-1.030l-1.286,-0.256l-3.733,-1.416l0.384,-1.288l3.091,0.257l-2.702,0.385zM488.726,116.844l1.287,-0.901l1.675,1.931l-0.387,3.605l-1.288,-0.258l-1.029,0.902l-1.032,-0.644l-0.128,-3.219l-0.642,-1.545l-1.544,-0.129z', + JM: + 'M256.242,177.22l1.802,0.128l1.416,0.644l0.515,0.772l-1.931,0.129l-0.772,0.386l-1.544,-0.386l-1.545,-1.030l0.385,-0.643l1.030,-0.130l-0.644,-0.130z', + JO: + 'M560.942,139.759l0.516,-0.902l2.960,1.031l5.278,-2.833l1.158,3.218l-0.514,0.516l-5.407,1.287l2.703,2.703l-0.901,0.515l-0.515,0.902l-2.060,0.386l-0.643,0.901l-1.160,0.900l-2.960,-0.514l-0.128,-0.386l1.417,-4.249l-0.130,-1.029l0.386,-0.902l0,1.544z', + JP: + 'M847.491,121.479l-2.574,2.704l0.129,2.703l-1.031,2.188l0.387,1.287l-1.287,1.931l-3.477,1.288l-4.762,0.128l-3.861,3.09l-1.801-1.03l-0.129-1.932l-4.635,0.517l-3.22,1.287h-3.089l2.703,2.059l-1.803,4.506l-1.801,1.159l-1.287-1.031l0.643-2.445l-1.672-0.772l-1.031-1.804l2.445-0.9l1.416-1.674l2.705-1.415l2.06-1.803l5.276-0.773l2.961,0.516l2.832-4.764l1.803,1.288l3.861-2.704l1.545-1.029l1.674-3.347l-0.387-2.961l1.158-1.803l2.832-0.386l1.416,3.734v-2.188V121.479zM854.829,108.606l1.93-1.159l0.516,2.961l-3.99,0.772l-2.316,2.703l-4.25-1.931l-1.414,2.962l-3.09,0.128l-0.387-2.703l1.416-2.06l2.832-0.128l0.773-3.734l0.771-2.188l3.219,2.832l2.06,0.901l-1.93-0.644L854.829,108.606zM821.874,136.798l1.416-1.545l1.545,0.257l1.16-1.157l1.93,0.643l0.388,0.9l-1.546,1.674l-1.158-0.901l-1.287,0.643l-0.773,1.545l-1.801-0.772L821.874,136.798z', + KE: + 'M561.972,214.552l2.318,0l3.089,2.574l0.772,0.130l1.159,0.385l0.900,-0.258l1.031,0.387l1.030,-1.159l2.445,-1.287l1.931,1.158l1.030,-0.256l-2.188,2.960l-0.130,10.169l1.931,2.189l-1.931,1.030l-0.514,1.416l-1.030,0.258l-0.515,1.545l-0.902,1.158l-0.513,1.673l-1.031,1.157l-4.119,-2.445l-0.256,-2.059l-10.042,-5.793l0,-2.832l0,-0.772l1.931,-1.674l1.029,-1.931l-0.771,-1.930l-1.031,-2.704l-1.287,-1.930l1.416,-1.159l2.188,-2.447l1.159,0.515l0.772,1.288l-0.129,-0.644z', + KG: + 'M656.46,113.111l0.514,-1.159l1.801,-0.386l4.378,0.902l0.514,-1.545l1.545,-0.644l3.732,1.159l1.030,-0.258l4.505,0l3.993,0.386l1.287,0.902l1.674,0.386l-0.387,0.644l-4.248,1.416l-0.901,1.158l-3.476,0.258l-1.029,1.673l-2.834,-0.257l-1.930,0.514l-2.574,1.288l0.386,0.643l-0.773,0.516l-5.020,0.514l-3.347,-0.901l-2.961,0.129l0.257,-1.545l2.961,0.515l1.030,-0.900l2.060,0.257l3.346,-1.932l-3.090,-1.416l-1.929,0.772l-2.061,-1.030l2.317,-1.801l0.770,0.258z', + KH: + 'M743.995,198.331l-1.031,-1.415l-1.416,-2.834l-0.643,-3.217l1.801,-2.189l3.475,-0.514l2.447,0.387l2.316,1.029l1.160,-1.803l2.446,0.901l0.644,1.803l-0.386,3.218l-4.506,2.059l1.160,1.674l-2.834,0.258l-2.316,1.030l2.317,0.387z', + KP: + 'M817.112,112.726l0.385,0.514l-1.029-0.129l-1.158,0.902l-0.773,0.901l0.131,1.93l-1.418,0.644l-0.516,0.386l-1.027,0.772l-1.803,0.516l-1.158,0.773v1.158l-0.387,0.257l1.156,0.386l1.418,1.159l-0.385,0.772l-1.033,0.129l-1.93,0.129l-1.029,1.158h-1.284l-0.132,0.257l-1.286-0.514l-0.386,0.386l-0.773,0.257l-0.129-0.514l-0.645-0.258l-0.771-0.386l0.771-1.159l0.645-0.385l-0.256-0.387l0.641-1.545l-0.127-0.386l-1.545-0.258l-1.289-0.772l2.189-1.673l2.961-1.546l1.803-1.93l1.286,0.9l2.319,0.13l-0.387-1.417l4.248-1.157l1.028-1.546L817.112,112.726z', + KR: + 'M810.933,122.895l2.447,3.218l0.642,1.803l0,3.089l-1.029,1.416l-2.445,0.515l-2.190,1.159l-2.445,0.258l-0.258,-1.545l0.514,-1.932l-1.158,-2.833l1.931,-0.514l-1.802,-2.189l0.131,-0.257l1.285,0l1.029,-1.158l1.930,-0.129l1.033,-0.129l-0.385,0.772z', + KW: + 'M594.411,146.196l0.645,1.158l-0.257,0.643l0.9,2.06l-1.93,0.129l-0.645-1.288l-2.447-0.257l1.931-2.703L594.411,146.196z', + KZ: + 'M656.46,113.111l-1.547,0.515l-3.603,1.802l-1.160,1.931l-1.030,0.129l-0.771,-1.288l-3.347,-0.128l-0.644,-2.189l-1.287,0l0.258,-2.703l-3.219,-2.060l-4.633,0.259l-3.219,0.385l-2.574,-2.446l-2.189,-1.029l-4.120,-1.931l-0.515,-0.129l-6.951,1.544l0.130,9.914l-1.419,0.128l-1.930,-2.060l-1.800,-0.772l-3.090,0.515l-1.160,0.900l-0.127,-0.643l0.642,-1.159l-0.515,-0.900l-3.089,-0.902l-1.286,-2.446l-1.416,-0.644l-0.130,-0.901l2.702,0.258l0,-1.931l2.320,-0.514l2.316,0.385l0.515,-2.574l-0.387,-1.674l-2.704,0.129l-2.316,-0.644l-3.090,1.159l-2.574,0.643l-1.416,-0.514l0.387,-1.416l-1.803,-1.803l-1.931,0.129l-2.317,-1.802l1.545,-2.060l-0.772,-0.515l2.186,-2.960l2.705,1.544l0.387,-1.931l5.535,-2.962l4.248,-0.127l5.922,1.931l3.088,1.029l2.961,-1.158l4.250,0l3.474,1.416l0.773,-0.772l3.732,0l0.644,-1.159l-4.376,-1.931l2.702,-1.288l-0.515,-0.772l2.575,-0.644l-1.929,-1.931l1.158,-0.901l10.039,-0.901l1.418,-0.644l6.693,-1.028l2.446,-1.160l4.763,0.644l0.901,2.833l2.832,-0.645l3.474,0.901l-0.258,1.416l2.577,-0.128l6.822,-2.575l-1.029,0.901l3.474,2.060l5.922,6.822l1.545,-1.416l3.605,1.546l3.860,-0.644l1.545,0.514l1.289,1.545l1.930,0.515l1.158,1.159l3.478,-0.387l1.414,1.675l-2.060,1.801l-2.317,0.128l-0.127,2.704l-1.416,1.288l-5.408,-0.901l-1.931,4.892l-1.415,0.514l-5.279,1.159l2.445,4.635l-1.931,0.643l0.260,1.545l-1.674,-0.386l-1.287,-0.902l-3.993,-0.386l-4.505,0l-1.030,0.258l-3.732,-1.159l-1.545,0.644l-0.514,1.545l-4.378,-0.902l-1.801,0.386l0.514,-1.159z', + LA: + 'M748.628,188.549l0.902,-1.288l0.127,-2.189l-2.187,-2.446l-0.129,-2.574l-2.059,-2.189l-2.060,-0.258l-0.516,1.030l-1.545,0l-0.900,-0.385l-2.832,1.544l0,-2.446l0.642,-2.832l-1.800,-0.128l-0.129,-1.546l-1.161,-0.900l0.516,-0.902l2.318,-1.802l0.256,0.643l1.418,0l-0.386,-3.089l1.416,-0.386l1.544,2.188l1.159,2.446l3.347,0l1.028,2.317l-1.672,0.772l-0.774,0.902l3.219,1.674l2.188,3.217l1.673,2.318l2.061,1.931l0.645,1.803l-0.387,2.702l-2.446,-0.901l-1.160,1.803l2.316,1.029z', + LB: + 'M561.714,137.312l-0.643,0l-0.259,0.515l-0.900,0l0.900,-2.187l1.289,-1.932l0.128,0l1.159,0.128l0.515,1.031l-1.546,1.029l-0.514,1.416l0.129,0z', + LK: + 'M685.552,206.699l-0.387,2.832l-1.158,0.771l-2.317,0.643l-1.288-2.187l-0.514-3.862l1.285-4.377l1.805,1.545l1.285,1.802L685.552,206.699z', + LR: + 'M444.442,215.195l-0.643,0l-2.832,-1.287l-2.446,-2.060l-2.317,-1.416l-1.802,-1.673l0.644,-0.902l0.129,-0.771l1.287,-1.546l1.159,-1.157l0.643,-0.130l0.644,-0.257l1.158,1.674l-0.128,1.029l0.515,0.515l0.772,0.128l0.515,-1.158l0.772,0.129l-0.129,0.773l0.258,1.287l-0.644,1.158l0.901,0.772l0.772,0.129l1.159,1.158l0.129,1.030l-0.257,0.387l0.259,-2.188z', + LS: + 'M543.306,304.922l0.902,0.900l-0.773,1.287l-0.515,0.902l-1.417,0.385l-0.514,0.901l-1.030,0.258l-1.931,-2.059l1.416,-1.803l1.416,-1.029l1.287,-0.516l-1.159,-0.774z', + LT: + 'M526.442,80.67l-0.128,-0.772l0.259,-0.643l-1.289,-0.515l-2.702,-0.386l-0.644,-2.317l3.088,-0.773l4.506,0.130l2.705,-0.259l0.385,0.515l1.416,0.257l2.574,1.288l0.258,1.159l-2.189,0.901l-0.643,1.545l-2.961,0.901l-2.574,0l-0.644,-0.772l1.417,0.259z', + LU: + 'M481.516,91.999l0.516,0.515l-0.129,1.159l-0.773,0.129l-0.643,-0.259l0.385,-1.544l-0.644,0z', + LV: + 'M521.938,76.037l0.128,-2.060l1.288,-1.674l2.573,-0.900l2.060,2.059l2.190,-0.128l0.513,-2.061l2.319,-0.514l1.158,0.387l2.316,1.028l2.318,0l1.286,0.644l0.129,1.287l0.901,1.545l-2.831,1.031l-1.674,0.514l-2.574,-1.288l-1.416,-0.257l-0.385,-0.515l-2.705,0.259l-4.506,-0.130l3.088,-0.773z', + LY: + 'M505.204,165.376l-1.932,1.03l-1.416-1.544l-4.248-1.159l-1.288-1.674l-2.061-1.158l-1.286,0.385l-0.901-1.415l-0.127-1.159l-1.546-2.059l1.029-1.03l-0.259-1.673l0.387-1.546l-0.256-1.158l0.514-2.318l-0.127-1.158l-0.902-2.446l1.287-0.643l0.257-1.031l-0.257-1.158l1.803-1.029l0.9-0.902l1.287-0.772l0.13-2.06l3.217,0.901l1.03-0.257l2.319,0.514l3.603,1.159l1.159,2.446l2.446,0.515l3.86,1.158l2.833,1.288l1.286-0.644l1.288-1.287l-0.644-2.059l0.9-1.288l1.932-1.288l1.801-0.385l3.734,0.514l0.901,1.287h1.028l0.773,0.516l2.703,0.257l0.645,0.901l-0.902,1.287l0.387,1.159l-0.772,1.673l0.901,2.189v9.526v9.912v5.408h-2.832v1.415l-11.069-5.407l-10.814-5.149L505.204,165.376z', + MA: + 'M461.436,138.472l0.772,0.514l-0.515,1.030l-3.476,0.515l-1.287,1.030l-1.545,0.128l-0.128,2.061l-3.090,1.029l-1.030,1.417l-2.188,0.644l-2.703,0.514l-4.377,1.931l0,3.218l-0.387,0l0,1.417l-1.544,0.128l-0.901,0.515l-1.288,0l-0.900,-0.257l-2.319,0.257l-0.900,2.060l-0.773,0.257l-1.287,3.347l-3.733,2.961l-0.901,3.733l-1.159,1.159l-0.257,1.029l-6.050,0.129l-0.129,0l0.129,-1.158l1.030,-0.772l0.901,-1.416l-0.129,-0.902l0.901,-1.930l1.545,-1.674l0.901,-0.515l0.644,-1.546l0.128,-1.415l0.901,-1.673l1.802,-1.031l1.803,-2.703l1.287,-1.030l2.574,-0.386l2.060,-1.802l1.416,-0.644l2.189,-2.317l-0.644,-3.347l1.031,-2.317l0.384,-1.416l1.675,-1.803l2.703,-1.287l2.059,-1.029l1.802,-2.833l0.773,-1.673l2.059,0l1.545,1.158l2.575,-0.128l2.832,0.515l1.159,0.128l1.030,1.674l0.128,1.674l-0.902,-2.832z', + MD: + 'M536.998,97.02l0.644,-0.386l1.675,-0.259l2.059,0.903l1.029,0.128l1.287,0.644l-0.257,0.901l1.030,0.515l0.386,1.030l0.902,0.772l-0.131,0.386l0.517,0.258l-0.773,0.257l-1.545,-0.129l-0.258,-0.386l-0.513,0.258l0.129,0.386l-0.774,0.901l-0.385,0.901l-0.773,0.386l-0.387,-1.287l0.257,-1.159l-0.128,-1.158l-1.545,-1.674l-0.902,-1.029l-0.770,-0.901l0.774,0.258z', + MG: + 'M598.66,260.508l0.772,1.160l0.643,1.801l0.385,3.219l0.775,1.287l-0.257,1.287l-0.518,0.773l-0.898,-1.545l-0.516,0.772l0.516,2.060l-0.258,1.158l-0.774,0.516l-0.129,2.316l-1.028,3.219l-1.417,3.604l-1.545,5.149l-1.031,3.734l-1.285,3.089l-2.188,0.644l-2.318,1.157l-1.544,-0.641l-2.189,-1.031l-0.773,-1.417l-0.129,-2.317l-0.900,-2.188l-0.258,-1.931l0.387,-1.930l1.287,-0.515l0,-0.901l1.288,-1.932l0.257,-1.802l-0.645,-1.285l-0.514,-1.676l-0.128,-2.446l0.900,-1.544l0.387,-1.673l1.287,-0.130l1.544,-0.514l0.901,-0.516l1.289,0l1.544,-1.544l2.189,-1.674l0.771,-1.415l-0.387,-1.159l1.159,0.386l1.545,-1.931l0,-1.544l0.901,-1.288l-0.902,-1.158z', + MK: + 'M520.651,114.27l0.385,0l0.129,-0.515l1.545,-0.515l1.544,-0.257l1.288,0l1.287,0.900l0.258,1.674l-0.514,0.130l-0.515,0.513l-1.417,-0.128l-1.029,0.643l-1.804,0.258l-1.029,-0.643l-0.385,-1.160l-0.257,0.900z', + ML: + 'M432.471,187.646l0.902,-0.514l0.385,-1.674l0.902,0l1.930,0.772l1.416,-0.514l1.160,0.129l0.385,-0.644l10.814,0l0.514,-1.931l-0.385,-0.257l-1.288,-11.587l-1.416,-11.714l4.119,0l9.140,5.922l9.139,5.792l0.645,1.288l1.672,0.772l1.159,0.387l0.128,1.801l2.961,-0.257l0,6.179l-1.543,1.802l-0.130,1.674l-2.445,0.386l-3.735,0.258l-0.900,0.901l-1.802,0.129l-1.673,0l-0.644,-0.516l-1.545,0.387l-2.446,1.158l-0.514,0.774l-2.189,1.285l-0.257,0.645l-1.159,0.514l-1.287,-0.257l-0.773,0.644l-0.385,1.802l-2.189,2.189l0.128,0.900l-0.771,1.159l0.128,1.545l-1.030,0.515l-0.643,0.257l-0.387,-1.157l-0.772,0.385l-0.516,-0.129l-0.514,0.772l-2.059,0l-0.772,-0.386l-0.387,0.258l-0.772,-0.772l0.128,-0.773l-0.257,-0.386l-0.644,0.257l0.128,-0.900l0.516,-0.645l-1.030,-1.158l-0.386,-0.644l-0.644,-0.643l-0.515,-0.129l-0.643,0.385l-0.773,0.387l-0.772,0.644l-1.159,-0.258l-0.772,-0.643l-0.386,-0.130l-0.772,0.387l-0.387,0l-0.128,-1.030l0.128,-0.772l-0.257,-1.030l-1.030,-0.772l-0.515,-1.545l0.129,1.674z', + MM: + 'M733.437,172.585l-1.672,1.159l-1.803,0.129l-1.287,2.960l-1.158,0.515l1.287,2.317l1.802,1.931l1.030,1.802l-0.901,2.318l-1.029,0.514l0.643,1.416l1.802,2.060l0.387,1.545l-0.129,1.287l1.158,2.447l-1.545,2.445l-1.287,2.832l-0.257,-2.059l0.773,-2.060l-0.902,-1.544l0.257,-2.962l-1.158,-1.416l-0.773,-3.219l-0.516,-3.345l-1.158,-2.318l-1.803,1.415l-3.088,1.932l-1.416,-0.257l-1.673,-0.644l0.902,-3.347l-0.647,-2.575l-2.058,-3.090l0.386,-0.900l-1.671,-0.387l-1.934,-2.188l-0.127,-2.189l0.900,0.387l0.129,-1.932l1.287,-0.643l-0.255,-1.159l0.642,-0.900l0,-2.704l2.188,0.515l1.160,-2.189l0.127,-1.287l1.545,-2.317l-0.127,-1.545l3.474,-1.802l1.930,0.515l-0.256,-1.674l1.030,-0.515l-0.258,-1.029l1.545,-0.130l0.900,1.545l1.289,0.644l0,2.060l-0.131,2.188l-2.445,2.318l-0.387,3.089l2.832,-0.386l0.645,2.446l1.674,0.515l-0.772,2.317l2.059,1.030l1.160,0.387l1.930,-0.773l0.128,1.158l-2.318,1.802l-0.516,0.902l1.544,-0.643z', + MN: + 'M701.642,94.188l2.832-0.515l5.148-2.317l4.121-1.287l2.316,0.901h2.832l1.803,1.287l2.702,0.129l3.862,0.644l2.574-1.931l-1.029-1.545l2.703-2.832l3.09,1.158l2.445,0.257l3.09,0.773l0.516,1.931l3.861,1.158l2.574-0.515l3.348-0.257l2.705,0.257l2.701,1.287l1.674,1.417h2.445l3.348,0.386l2.574-0.644l3.477-0.387l3.99-1.93l1.545,0.258l1.414,0.9l3.219-0.128l-1.287,1.931l-1.932,2.704l0.771,1.158l1.42-0.386l2.701,0.386l2.059-0.901l2.189,0.772l2.448,1.931l-0.259,0.902l-2.189-0.258l-3.861,0.386l-1.932,0.772l-1.932,1.803l-4.119,1.029l-2.703,1.417l-2.832-0.516l-1.416-0.257l-1.416,1.674l0.773,1.03l0.516,0.9l-1.932,0.902l-1.932,1.416l-3.088,1.03l-4.121,0.128l-4.375,0.902l-3.09,1.416l-1.16-0.773h-3.347l-3.862-1.673l-2.701-0.386l-3.604,0.386l-5.536-0.644h-2.962l-1.673-1.545l-1.158-2.446l-1.673-0.386l-3.218-1.674l-3.605-0.385l-3.216-0.387l-1.032-1.158l1.032-3.219l-1.804-2.188l-3.862-0.901l-2.317-1.545L701.642,94.188z', + MR: + 'M432.471,187.646l-1.802,-1.930l-1.674,-1.931l-1.801,-0.772l-1.288,-0.773l-1.416,0l-1.287,0.643l-1.416,-0.257l-0.901,0.901l-0.258,-1.416l0.773,-1.416l0.386,-2.445l-0.386,-2.704l-0.258,-1.417l0.258,-1.287l-0.773,-1.287l-1.416,-1.158l0.643,-0.901l10.557,0l-0.515,-3.862l0.644,-1.417l2.574,-0.257l-0.129,-6.823l8.883,0.129l0,-4.120l10.040,6.566l-4.119,0l1.416,11.714l1.288,11.587l0.385,0.257l-0.514,1.931l-10.814,0l-0.385,0.644l-1.160,-0.129l-1.416,0.514l-1.930,-0.772l-0.902,0l-0.385,1.674l0.902,-0.514z', + MW: + 'M558.368,258.062l-0.773,1.932l0.773,3.605l0.901,-0.130l1.030,0.902l1.030,1.930l0.258,3.476l-1.160,0.645l-0.772,1.801l-1.802,-1.674l-0.258,-1.931l0.645,-1.158l-0.130,-1.159l-1.159,-0.644l-0.643,0.259l-1.545,-1.289l-1.416,-0.770l0.772,-2.447l0.773,-0.902l-0.516,-2.317l0.645,-2.189l0.386,-0.644l-0.644,-2.315l-1.287,-1.160l2.704,0.514l1.415,1.933l-0.773,-3.732z', + + MX: + 'M203.592,157.266l-1.030,2.446l-0.515,1.931l-0.257,3.605l-0.257,1.287l0.514,1.416l0.773,1.287l0.644,2.188l1.802,1.931l0.515,1.545l1.158,1.416l2.832,0.643l1.029,1.159l2.447,-0.772l2.060,-0.258l1.930,-0.513l1.803,-0.388l1.672,-1.158l0.644,-1.545l0.258,-2.317l0.386,-0.772l1.803,-0.644l2.961,-0.644l2.316,0l1.674,-0.129l0.644,0.516l-0.129,1.287l-1.417,1.674l-0.643,1.544l0.515,0.515l-0.386,1.158l-0.772,2.060l-0.644,-0.644l-0.515,0l-0.515,0.130l-1.030,1.544l-0.515,-0.258l-0.257,0.129l0,0.387l-2.446,0l-2.575,0l0,1.416l-1.158,0l0.901,0.901l1.030,0.643l0.387,0.644l0.385,0.128l-0.128,0.903l-3.347,0l-1.416,2.188l0.385,0.514l-0.257,0.643l-0.128,0.773l-2.961,-2.832l-1.416,-0.901l-2.189,-0.772l-1.544,0.257l-2.189,1.030l-1.287,0.258l-1.930,-0.773l-2.060,-0.515l-2.446,-1.158l-2.061,-0.387l-3.088,-1.287l-2.189,-1.286l-0.644,-0.645l-1.545,-0.258l-2.702,-0.772l-1.159,-1.287l-2.961,-1.545l-1.288,-1.673l-0.644,-1.287l0.902,-0.258l-0.258,-0.772l0.644,-0.772l0,-0.902l-0.901,-1.158l-0.257,-1.159l-0.902,-1.287l-2.445,-2.704l-2.703,-2.059l-1.288,-1.674l-2.317,-1.159l-0.515,-0.643l0.386,-1.674l-1.287,-0.643l-1.673,-1.287l-0.644,-1.802l-1.416,-0.258l-1.545,-1.416l-1.287,-1.288l-0.129,-0.901l-1.416,-2.060l-1.029,-2.059l0.128,-1.030l-1.931,-1.030l-0.901,0.129l-1.544,-0.773l-0.515,1.159l0.515,1.288l0.257,1.930l0.901,1.160l1.931,1.801l0.515,0.644l0.386,0.257l0.386,0.902l0.515,0l0.515,1.673l0.773,0.644l0.643,1.030l1.673,1.415l0.902,2.446l0.772,1.159l0.773,1.287l0.128,1.416l1.287,0.129l1.030,1.158l1.029,1.288l-0.128,0.386l-1.029,1.030l-0.516,0l-0.772,-1.673l-1.673,-1.546l-1.931,-1.286l-1.416,-0.644l0.129,-1.931l-0.515,-1.545l-1.288,-0.773l-1.802,-1.287l-0.386,0.386l-0.644,-0.643l-1.673,-0.643l-1.545,-1.675l0.129,-0.128l1.158,0.128l1.030,-1.029l0,-1.159l-2.059,-1.931l-1.545,-0.772l-1.031,-1.674l-0.900,-1.802l-1.287,-2.189l-1.159,-2.317l3.090,-0.256l3.475,-0.259l-0.258,0.515l3.992,1.288l6.178,1.931l5.407,0l2.060,0l0.129,-1.158l4.633,0l0.902,0.900l1.416,0.901l1.545,1.159l0.900,1.416l0.772,1.545l1.288,0.772l2.316,0.772l1.674,-2.058l2.189,-0.130l1.930,1.159l1.288,1.802l1.030,1.545l1.545,1.545l0.515,1.931l0.772,1.287l2.188,0.773l1.931,0.643l-1.030,0.129z', + + MY: + 'M740.39,210.174l0.642,0.258l1.545,1.673l1.160,1.803l0.129,1.803l-0.258,1.287l0.258,0.900l0.129,1.545l1.029,0.772l1.030,2.318l0,0.901l-1.932,0.257l-2.574,-2.059l-3.217,-2.060l-0.260,-1.416l-1.543,-1.802l-0.386,-2.188l-1.032,-1.546l0.387,-1.931l-0.643,-1.158l0.516,-0.385l2.188,1.157l0.129,1.287l1.802,-0.257l-0.901,1.159zM760.601,221.632l2.058,0.901l2.061,-0.514l0.513,-2.318l1.159,-0.515l3.218,-0.515l1.932,-2.189l1.287,-1.673l1.287,1.417l0.516,-0.902l1.285,0l0.260,-1.674l0.127,-1.287l2.060,-1.931l1.287,-2.059l1.159,0l1.287,1.286l0.129,1.159l1.802,0.772l2.319,0.773l-0.258,1.158l-1.803,0.129l0.514,1.288l-2.059,0.901l-2.316,-0.515l-3.090,0l-0.900,3.089l-1.032,0.901l-1.414,3.862l-2.189,0.515l-2.574,-0.772l-1.289,0.257l-1.545,1.417l-1.672,-0.257l-1.803,0.514l-1.801,-1.416l0.515,1.802z', + MZ: + 'M558.368,258.062l1.931,-0.256l3.347,0.771l0.644,-0.386l1.930,0l0.902,-0.900l1.672,0.128l2.961,-1.030l2.060,-1.674l0.516,1.287l-0.129,2.705l0.257,2.316l0.128,4.248l0.516,1.289l-0.772,1.930l-1.160,1.803l-1.673,1.673l-2.446,1.030l-3.090,1.416l-2.961,2.830l-1.029,0.517l-1.930,1.930l-1.160,0.643l-0.128,1.803l1.288,2.060l0.514,1.674l-0.129,1.415l0.644,-0.770l-0.129,2.573l-0.386,1.288l0.643,0.514l-0.387,1.030l-1.157,1.030l-2.187,0.901l-3.349,1.417l-1.159,1.030l0.259,1.158l0.643,0.127l-0.130,1.418l-2.058,0l-0.259,-1.158l-0.385,-1.289l-0.258,-0.901l0.514,-3.090l-0.771,-1.801l-1.287,-3.863l2.832,-3.089l0.773,-1.930l0.386,-0.258l0.257,-1.545l-0.385,-0.773l0.128,-2.061l0.513,-1.801l0,-3.475l-1.415,-0.774l-1.287,-0.254l-0.515,-0.645l-1.287,-0.645l-2.317,0.129l-0.129,-1.029l-0.258,-1.932l8.239,-2.189l1.545,1.289l0.643,-0.259l1.159,0.644l0.130,1.159l-0.645,1.158l0.258,1.931l1.802,1.674l0.772,-1.801l1.160,-0.645l-0.258,-3.476l-1.030,-1.930l-1.030,-0.902l-0.901,0.130l-0.773,-3.605l-0.773,1.932z', + NA: + 'M509.322,304.019l-2.059,-2.059l-1.030,-2.060l-0.644,-2.575l-0.645,-1.930l-0.900,-4.120l0,-3.216l-0.387,-1.545l-1.029,-1.032l-1.416,-2.317l-1.414,-3.218l-0.647,-1.674l-2.187,-2.575l-0.128,-2.058l1.288,-0.516l1.674,-0.515l1.672,0.128l1.674,1.159l0.384,-0.128l10.944,-0.128l1.801,1.287l6.566,0.385l4.892,-1.158l2.187,-0.644l1.803,0.258l1.030,0.513l0,0.259l-1.416,0.645l-0.901,0l-1.674,1.028l-1.029,-1.158l-4.119,1.030l-2.060,0l-0.129,9.654l-2.574,0.130l0,7.852l0,9.912l-2.446,1.416l-1.418,0.129l-1.673,-0.514l-1.288,-0.129l-0.383,-1.158l-1.033,-0.773l1.286,-1.415z', + NC: + 'M911.856,283.809l2.188,1.673l1.416,1.159l-1.029,0.643l-1.545,-0.643l-1.932,-1.287l-1.672,-1.416l-1.803,-1.932l-0.386,-0.901l1.158,0.129l1.545,0.901l1.158,0.902l-0.902,-0.772z', + NE: + 'M471.091,194.855l0,-1.930l-3.091,-0.515l-0.128,-1.417l-1.545,-1.673l-0.258,-1.287l0.129,-1.287l1.802,-0.129l0.900,-0.901l3.735,-0.258l2.445,-0.386l0.130,-1.674l1.543,-1.802l0,-6.179l3.734,-1.288l7.852,-5.276l9.269,-5.150l4.248,1.159l1.416,1.544l1.932,-1.030l0.643,4.249l1.029,0.643l0.129,0.901l1.030,0.901l-0.514,1.159l-1.030,5.406l-0.130,3.605l-3.475,2.446l-1.158,3.605l1.158,1.029l0,1.673l1.674,0.130l-0.258,1.158l-0.774,0.257l-0.128,0.773l-0.514,0.128l-1.803,-2.960l-0.644,-0.129l-2.058,1.545l-2.061,-0.772l-1.545,-0.258l-0.772,0.386l-1.545,0l-1.544,1.159l-1.416,0l-3.219,-1.417l-1.286,0.644l-1.416,0l-1.030,-1.030l-2.704,-1.029l-2.832,0.385l-0.772,0.516l-0.259,1.544l-0.770,1.159l-0.258,2.447l-2.059,-1.674l-0.901,0.127l0.901,-0.773z', + NG: + 'M488.082,214.166l-2.704,0.900l-1.029,-0.128l-1.031,0.644l-2.188,-0.129l-1.415,-1.674l-0.902,-1.931l-1.931,-1.802l-2.059,0.128l-2.318,0l0.130,-4.376l0,-1.802l0.386,-1.674l0.901,-0.773l1.288,-1.672l-0.258,-0.773l0.514,-1.031l-0.643,-1.673l0.129,-0.771l0.258,-2.447l0.770,-1.159l0.259,-1.544l0.772,-0.516l2.832,-0.385l2.704,1.029l1.030,1.030l1.416,0l1.286,-0.644l3.219,1.417l1.416,0l1.544,-1.159l1.545,0l0.772,-0.386l1.545,0.258l2.061,0.772l2.058,-1.545l0.644,0.129l1.803,2.960l0.514,-0.128l1.160,1.158l-0.387,0.386l-0.129,0.901l-2.188,2.189l-0.773,1.673l-0.387,1.417l-0.513,0.514l-0.645,1.931l-1.414,1.159l-0.387,1.288l-0.644,1.159l-0.257,1.029l-1.803,0.901l-1.546,-1.030l-1.029,0l-1.544,1.545l-0.771,0.128l-1.289,2.575l0.772,-1.932z', + NI: + 'M234.359,197.045l-0.902,-0.774l-1.287,-1.158l-0.643,-0.901l-1.159,-0.773l-1.288,-1.287l0.258,-0.386l0.514,0.386l0.129,-0.129l0.902,-0.128l0.257,-0.644l0.387,0l0,-1.288l0.643,-0.129l0.515,0l0.643,-0.643l0.773,0.515l0.258,-0.386l0.515,-0.258l0.900,-0.771l0.129,-0.516l0.257,0l0.258,-0.643l0.258,-0.130l0.514,0.517l0.516,0.127l0.643,-0.385l0.643,0l0.902,-0.386l0.386,-0.386l0.901,0.128l-0.129,0.258l-0.129,0.514l0.258,1.030l-0.643,0.901l-0.258,1.159l-0.129,1.158l0.129,0.644l0.128,1.287l-0.514,0.258l-0.129,1.159l0.129,0.644l-0.516,0.771l0.130,0.645l0.386,0.514l-0.644,0.514l-0.772,-0.128l-0.515,-0.644l-0.773,-0.128l-0.644,0.257l-1.801,-0.644l0.386,-0.259z', + NL: + 'M481.646,82.859h2.188l0.515,0.902l-0.644,2.574l-0.773,0.901h-1.544l0.387,2.833l-1.416-0.644l-1.674-1.158l-2.573,0.643l-1.933-0.258l1.417-0.772l2.317-3.991L481.646,82.859z', + NO: + 'M494.905,68.442l-1.802,-1.674l-5.279,3.090l-3.603,0.643l-3.734,-1.415l-0.902,-2.833l-0.900,-6.179l2.445,-1.802l7.080,-2.189l5.407,-2.832l4.892,-3.733l6.435,-5.278l4.508,-1.931l7.465,-3.476l5.922,-1.158l4.377,0.129l4.119,-2.188l4.893,0.128l4.889,-0.515l8.368,1.931l-3.474,0.773l2.961,1.672l-4.507,1.031l-2.189,0.257l1.159,-1.803l-3.476,-1.157l-4.247,0.900l-1.289,2.060l-2.572,1.159l-2.832,-0.644l-3.606,0.129l-2.961,-1.416l-1.545,0.643l-1.673,0.129l-0.513,1.803l-5.022,-0.387l-0.644,1.417l-2.702,0l-1.674,1.931l-2.703,2.960l-4.248,3.862l1.031,0.901l-0.903,1.030l-2.705,0l-1.800,2.446l0.127,3.605l1.803,1.415l-0.900,3.090l-2.318,1.931l1.158,-1.545z', + NP: + 'M702.673,151.859l-0.258,1.159l0.385,1.674l-0.385,1.158l-2.189,0l-3.219,-0.644l-2.059,-0.257l-1.674,-1.287l-3.603,-0.386l-3.604,-1.545l-2.447,-1.287l-2.703,-1.031l1.160,-2.573l1.672,-1.160l1.158,-0.643l2.061,0.772l2.703,1.802l1.545,0.386l0.900,1.288l2.188,0.515l2.189,1.158l2.959,0.644l-3.221,-0.257z', + NZ: + 'M941.72,334.914l-1.030,1.417l-1.287,1.931l-2.058,1.030l-0.514,-0.772l-1.160,-0.386l1.545,-2.189l-0.774,-1.416l-2.961,-1.159l0.131,-0.901l1.930,-1.030l0.387,-2.059l-0.129,-1.674l-1.029,-1.803l0,-0.514l-1.290,-1.158l-2.058,-2.317l-1.158,-1.932l1.027,-0.256l1.418,1.544l2.187,0.773l0.774,2.315l1.930,2.834l0,-1.803l1.289,0.773l0.384,1.931l2.190,0.901l1.803,0.257l1.545,-1.030l1.285,0.258l-0.645,2.446l-0.771,1.544l-2.059,0l-0.771,0.773l0.255,1.158l0.386,-0.514zM922.282,344.312l2.319,-1.416l1.671,-1.416l1.161,-1.931l1.029,-0.772l0.387,-1.416l1.929,-1.287l0.514,1.158l0.645,1.030l1.933,-1.030l0.770,1.160l0,1.157l-1.028,1.160l-1.802,2.059l-1.289,1.029l1.029,1.288l-2.188,0l-2.316,1.030l-0.645,1.803l-1.545,2.703l-2.060,1.286l-1.414,0.773l-2.445,-0.128l-1.805,-0.901l-2.830,-0.130l-0.516,-1.030l1.416,-1.930l3.477,-2.704l1.672,-0.515l-1.931,1.030z', + OM: + 'M617.197,159.841l1.157,1.802l1.545,1.030l1.932,0.387l1.674,0.385l1.158,1.545l0.772,0.902l0.902,0.385l0,0.644l-1.031,1.545l-0.387,0.772l-1.158,0.902l-1.029,1.802l-1.157,-0.130l-0.517,0.645l-0.514,1.416l0.385,1.673l-0.257,0.387l-1.286,0l-1.675,1.028l-0.257,1.289l-0.642,0.514l-1.675,0l-1.031,0.773l0,1.029l-1.287,0.773l-1.416,-0.257l-1.802,0.900l-1.286,0.129l-0.900,-1.930l-2.061,-4.378l7.981,-2.702l1.802,-5.408l-1.159,-1.931l0,-1.030l0.773,-1.159l0.129,-1.029l1.159,-0.515l-0.517,-0.386l0.258,-1.802l-1.417,0zM616.294,156.752l0.773,-0.902l0.387,0.257l-0.257,1.159l-0.385,0.386l0.518,0.900z', + PA: + 'M255.47,207.471l-0.902,-0.772l-0.643,-1.416l0.643,-0.773l-0.643,-0.127l-0.514,-0.903l-1.288,-0.771l-1.159,0.257l-0.644,0.900l-1.029,0.644l-0.644,0.129l-0.257,0.515l1.287,1.545l-0.643,0.258l-0.387,0.385l-1.287,0.129l-0.515,-1.544l-0.387,0.386l-0.772,-0.129l-0.643,-1.030l-1.030,-0.258l-0.773,-0.257l-1.158,0l0,0.644l-0.387,-0.515l0.130,-0.515l0.257,-0.515l-0.128,-0.515l0.385,-0.257l-0.514,-0.387l0,-1.157l1.029,-0.260l1.030,1.031l-0.129,0.516l1.159,0.129l0.129,-0.129l0.772,0.643l1.416,-0.258l1.031,-0.643l1.673,-0.515l0.900,-0.901l1.545,0.257l-0.129,0.257l1.545,0l1.159,0.516l0.900,0.773l1.031,0.771l-0.386,0.387l0.643,1.544l-0.515,0.901l-0.900,-0.257l0.258,-1.287z', + PE: + 'M277.74,274.281l-0.644,1.417l-1.415,0.644l-2.704-1.543l-0.258-1.031l-5.278-2.705l-4.891-2.959l-2.059-1.674l-1.159-2.188l0.515-0.773l-2.318-3.605l-2.703-4.891l-2.446-5.406l-1.158-1.289l-0.902-1.93l-2.058-1.802l-1.932-1.028l0.901-1.16l-1.287-2.576l0.772-1.93l2.189-1.672l0.386,1.029l-0.773,0.643v1.029l1.159-0.257l1.03,0.388l1.159,1.286l1.545-1.03l0.514-1.802l1.673-2.446l3.219-1.029l2.961-2.832l0.772-1.674l-0.386-2.06l0.772-0.258l1.802,1.288l0.772,1.287l1.288,0.644l1.544,2.832l2.06,0.257l1.416-0.644l1.03,0.517l1.673-0.26l2.06,1.287l-1.802,2.704h0.772l1.416,1.417l-2.446-0.129l-0.386,0.514l-2.188,0.516l-3.089,1.802l-0.129,1.288l-0.772,0.9l0.257,1.416l-1.545,0.773v1.158l-0.772,0.516l1.158,2.445l1.546,1.674l-0.644,1.158l1.801,0.129l1.03,1.416h2.317l2.317-1.545l-0.256,4.119l1.287,0.256l1.416-0.383l2.445,4.248l-0.644,0.9l-0.128,1.932v2.316l-1.159,1.287l0.515,1.029l-0.643,0.9l1.158,2.318L277.74,274.281z', + PG: + 'M845.175,242.742l-0.129-8.752l4.635,1.803l5.02,1.543l1.932,1.417l1.416,1.417l0.385,1.544l4.505,1.673l0.646,1.416l-2.445,0.258l0.643,1.803l2.316,1.802l1.803,2.832l1.545-0.128l-0.129,1.287l2.059,0.387l-0.771,0.514l2.831,1.158l-0.258,0.773l-1.803,0.129l-0.641-0.645l-2.32-0.258l-2.701-0.385l-2.061-1.803l-1.545-1.416l-1.414-2.446l-3.479-1.159l-2.314,0.771l-1.673,0.902l0.386,2.06l-2.189,0.901l-1.416-0.515l-2.832-0.129V242.742zM876.454,236.822l1.031,0.9l0.258,1.418l-0.771,0.641l-0.518-1.545l-0.643-1.027l-1.288-0.902l-1.545-1.158l-1.931-0.773l0.773-0.643l1.416,0.772l1.029,0.515l1.031,0.643L876.454,236.822zM872.851,242.742l-1.545,0.645l-1.286,0.645h-1.546l-2.188-0.772l-1.545-0.772l0.256-0.901l2.447,0.388l1.416-0.131l0.387-1.287l0.385-0.127l0.261,1.414l1.543-0.128l0.772-0.901l1.543-1.031l-0.256-1.545l1.543-0.127l0.517,0.515v1.416l-0.901,1.674l-1.416,0.259L872.851,242.742zM882.118,241.328l0.775,0.645l1.414,1.674l1.158,0.899l-0.258,0.771l-0.771,0.26l-1.159-1.03l-1.287-1.673l-0.516-2.061l0.387-0.258L882.118,241.328z', + PH: + 'M790.722,192.797l-1.416,-2.061l2.318,0l1.031,1.030l-0.775,2.316l1.158,1.285zM795.485,200.134l0.645,-0.773l0.256,-1.673l1.545,-0.129l-0.385,1.802l1.930,-2.703l-0.258,2.574l-0.903,0.902l-0.900,1.802l-0.900,0.773l-1.545,-1.932l-0.515,0.643zM805.655,204.253l0.258,1.802l0.256,1.545l-1.029,2.446l-0.901,-2.704l-1.289,1.287l0.903,2.060l-0.774,1.288l-3.217,-1.545l-0.771,-2.059l0.898,-1.287l-1.801,-1.159l-0.773,1.030l-1.285,-0.129l-2.061,1.545l-0.386,-0.773l1.031,-2.317l1.672,-0.773l1.545,-0.901l0.902,1.159l2.061,-0.772l0.384,-1.158l1.930,-0.129l-0.129,-2.061l2.192,1.288l0.255,1.416l-0.129,-0.901zM784.415,201.936l-3.477,2.447l1.288,-1.804l1.929,-1.673l1.676,-1.931l1.285,-2.575l0.518,2.190l-1.803,1.415l1.416,-1.931zM794.841,177.863l-0.514,1.159l0.901,1.931l-0.643,2.188l-1.545,0.901l-0.516,2.188l0.645,2.189l1.416,0.257l1.158,-0.257l3.348,1.415l-0.258,1.417l0.900,0.772l-0.257,1.159l-2.061,-1.287l-1.029,-1.416l-0.643,1.031l-1.803,-1.676l-2.445,0.387l-1.287,-0.515l0.127,-1.157l0.775,-0.645l-0.775,-0.643l-0.256,0.901l-1.416,-1.545l-0.387,-1.159l-0.127,-2.575l1.157,0.902l0.257,-4.248l0.901,-2.447l1.545,0l1.674,0.773l0.902,-0.643l-0.256,-0.643zM793.94,196.271l-0.386,-1.286l1.674,0.771l1.673,0l0,1.160l-1.287,1.157l-1.674,0.773l-0.128,-1.287l-0.128,1.288zM803.337,194.212l0.773,2.961l-2.060,-0.644l0,0.901l0.644,1.674l-1.287,0.514l-0.129,-1.802l-0.773,-0.128l-0.385,-1.674l1.545,0.257l0,-1.029l-1.676,-2.060l2.576,0l-0.772,-1.030z', + PK: + 'M667.659,126.886l2.059,1.287l0.773,2.059l4.375,1.159l-2.572,2.189l-2.961,0.386l-4.121,-0.643l-1.287,1.157l0.900,2.447l1.031,1.802l2.059,1.287l-2.187,1.545l0,1.931l-2.575,2.704l-1.802,2.702l-2.704,2.833l-3.218,-0.129l-2.961,2.832l1.802,1.159l0.258,2.059l1.545,1.417l0.514,2.317l-5.922,0l-1.801,1.802l-1.931,-0.772l-0.774,-1.932l-2.187,-2.059l-4.891,0.514l-4.377,0.130l-3.863,0.386l1.030,-3.218l3.992,-1.288l-0.259,-1.287l-1.288,-0.386l-0.128,-2.446l-2.574,-1.159l-1.031,-1.674l-1.414,-1.415l4.504,1.415l2.704,-0.386l1.674,0.386l0.514,-0.643l1.931,0.257l3.476,-1.159l0.129,-2.317l1.417,-1.544l2.058,0l0.257,-0.644l2.061,-0.386l1.029,0.257l1.031,-0.772l-0.129,-1.674l1.158,-1.545l1.673,-0.772l-1.030,-1.673l2.575,0l0.773,-0.902l-0.129,-1.029l1.287,-1.159l-0.257,-1.416l-0.645,-1.029l1.545,-1.287l2.833,-0.517l3.090,-0.256l1.416,-0.516l-1.545,0.385z', + PL: + 'M505.718,89.295l-1.158,-1.672l0.257,-1.030l-0.644,-1.417l-1.029,-0.901l0.770,-0.773l-0.642,-1.287l1.802,-0.901l4.248,-1.158l3.347,-0.901l2.703,0.387l0.258,0.643l2.574,0.129l3.348,0.256l4.890,0l1.417,0.259l0.644,0.772l0.129,1.288l0.771,1.029l0,1.029l-1.672,0.516l0.772,1.287l0.129,1.159l1.286,2.317l-0.257,0.773l-1.287,0.385l-2.447,2.189l0.646,1.287l-0.515,-0.257l-2.577,-1.030l-1.929,0.386l-1.289,-0.257l-1.672,0.644l-1.289,-1.030l-1.158,0.386l-0.127,-0.129l-1.289,-1.416l-1.930,-0.129l-0.257,-0.772l-1.802,-0.386l-0.517,0.773l-1.414,-0.644l0.129,-0.645l-1.932,-0.256l1.287,0.903z', + PR: + 'M286.622,177.09l1.416,0.258l0.516,0.515l-0.644,0.643h-2.06l-1.545,0.129l-0.258-1.158l0.387-0.387H286.622z', + PS: + 'M560.942,139.759l0,1.544l-0.386,0.902l-1.287,0.257l0.128,-0.644l0.774,-0.385l-0.774,-0.258l0.645,-1.803l-0.900,-0.387z', + PT: + 'M440.838,114.141l1.031,-0.643l1.158,-0.387l0.643,1.287l1.545,0l0.514,-0.385l1.545,0.128l0.773,1.416l-1.287,0.643l0,2.189l-0.514,0.387l0,1.159l-1.160,0.256l1.030,1.674l-0.772,1.674l0.902,0.900l-0.258,0.644l-1.030,1.030l0.257,0.902l-1.158,0.772l-1.416,-0.387l-1.416,0.258l0.386,-2.059l-0.129,-1.674l-1.288,-0.258l-0.643,-1.030l0.259,-1.802l1.028,-0.900l0.259,-1.159l0.514,-1.545l0,-1.159l-0.644,-1.030l0.129,0.901z', + PY: + 'M296.405,286.898l1.03-3.219v-1.414l1.416-2.447l4.634-0.772l2.446,0.13l2.575,1.285v0.902l0.772,1.414l-0.128,3.736l2.831,0.514l1.159-0.514l1.802,0.643l0.516,0.902l0.256,2.443l0.259,1.031l1.028,0.129l1.031-0.516l0.9,0.516v1.544l-0.386,1.545l-0.515,1.546l-0.386,2.445l-2.446,2.059l-2.189,0.387l-2.961-0.387l-2.702-0.771l2.574-4.121l-0.386-1.157l-2.703-1.03l-3.348-2.059l-2.188-0.387L296.405,286.898z', + QA: + 'M602.136,160.227l-0.257,-1.931l0.770,-1.416l0.772,-0.257l0.775,0.901l0,1.545l-0.517,1.544l-0.772,0.258l0.771,0.644z', + RO: + 'M526.442,97.921l1.159,-0.515l1.674,0.258l1.673,0l1.289,0.772l0.899,-0.515l1.931,-0.257l0.773,-0.644l1.158,0l0.774,0.258l0.770,0.901l0.902,1.029l1.545,1.674l0.128,1.158l-0.257,1.159l0.387,1.287l1.287,0.386l1.287,-0.386l1.158,0.515l0,0.645l-1.287,0.643l-0.772,-0.258l-0.773,3.219l-1.544,-0.258l-1.930,-1.030l-3.219,0.644l-1.287,0.644l-3.990,-0.130l-2.059,-0.386l-1.031,0.129l-0.773,-1.030l-0.513,-0.515l0.641,-0.386l-0.771,-0.386l-0.774,0.644l-1.543,-0.773l-0.257,-1.158l-1.674,-0.643l-0.258,-0.902l-1.416,-1.030l2.059,-0.515l1.673,-1.802l1.290,-1.803l-1.671,0.643z', + RS: + 'M519.749,102.684l1.416,1.030l0.258,0.902l1.674,0.643l0.257,1.158l1.543,0.773l0.774,-0.644l0.771,0.386l-0.641,0.386l0.513,0.515l-0.772,0.644l0.259,0.902l1.415,1.158l-1.030,0.901l-0.515,0.772l0.256,0.386l-0.385,0.387l-1.288,0l-1.544,0.257l-1.545,0.515l-0.129,0.515l-0.385,0l-0.130,-1.030l-0.643,-0.257l-0.516,-0.773l-0.771,0.258l-0.129,-0.516l-1.287,1.288l0.258,0.901l-0.516,-0.128l-0.773,-0.902l-1.159,-0.515l0.258,-0.514l0.387,-1.545l0.900,-0.515l1.158,-0.387l0.389,-1.287l-1.289,-1.030l0.645,-1.159l-1.030,0l1.030,-1.029l-0.773,-0.772l-0.643,-1.031l2.060,-0.772l-1.672,-0.129z', + RU: + 'M950.089,36.129l-0.258,0l-0.516,-1.801l0.774,-0.772l0.127,-0.129l6.308,1.158l6.435,-1.544zM586.045,9.869l5.276,-0.515l4.121,0l0.514,0.773l1.545,-0.644l2.574,-0.515l3.990,0.644l-1.028,0.386l-3.605,0.385l-2.447,0.130l-0.384,0.514l-3.221,0.386l-2.830,-0.643l1.545,-0.772l6.050,0.129zM950.089,51.964l-3.992,1.802l2.574,3.219l-0.641,2.188l-5.539,-0.773l-7.336,1.674l-6.177,2.703l-4.764,2.703l-3.990,-1.673l-7.725,1.803l-6.693,0.128l-4.377,4.506l3.088,0.772l0,4.634l-3.475,1.545l0.645,1.803l-4.506,1.544l-1.159,3.219l-4.250,1.158l-0.513,1.931l-4.119,3.089l-1.674,-6.178l-1.545,-5.922l1.545,-4.249l2.060,-1.157l0.127,-1.287l3.864,-0.773l5.148,-3.219l4.506,-2.832l5.019,-2.060l2.061,-3.732l-3.219,0.128l-1.672,2.317l-6.695,2.446l-2.187,-3.089l-7.081,0.901l-6.693,4.247l1.803,1.288l-6.309,1.416l-10.041,-1.416l-11.715,0l-6.564,1.159l-8.369,5.278l-9.781,5.665l3.861,0.643l0.771,2.317l3.092,0.385l1.672,-1.545l2.961,0.387l3.475,2.060l0.515,3.089l-1.543,2.189l-0.902,2.575l-1.031,5.535l-4.120,3.862l-0.900,1.802l-3.603,3.219l-3.735,3.089l-1.674,1.545l-3.603,1.674l-1.674,0l-1.674,-1.288l-3.601,1.931l-0.518,0.901l-0.385,-0.514l0,-1.288l1.416,-0.129l0.385,-3.219l-0.771,-2.317l2.318,-0.901l3.346,0.516l1.802,-2.704l0.901,-2.832l1.031,-1.029l1.414,-2.448l-4.375,0.774l-2.447,1.030l-3.990,0l-1.159,-2.447l-3.218,-1.930l-4.635,-0.902l-1.029,-2.574l-0.901,-1.674l-1.031,-1.158l-1.674,-2.703l-2.316,-1.030l-4.119,-0.772l-3.475,0l-3.350,0.513l-2.316,1.417l1.545,0.644l0,1.416l-1.414,0.901l-2.447,2.832l0,1.159l-3.862,1.672l-3.219,-0.900l-3.218,0.128l-1.414,-0.900l-1.545,-0.258l-3.991,1.930l-3.476,0.387l-2.574,0.644l-3.348,-0.386l-2.445,0l-1.674,-1.417l-2.701,-1.287l-2.705,-0.257l-3.348,0.257l-2.574,0.515l-3.862,-1.158l-0.515,-1.931l-3.090,-0.773l-2.445,-0.257l-3.090,-1.158l-2.703,2.832l1.029,1.545l-2.574,1.931l-3.862,-0.644l-2.703,-0.129l-1.802,-1.287l-2.832,0l-2.317,-0.901l-4.121,1.287l-5.148,2.317l-2.832,0.515l-1.030,0.258l-1.414,-1.675l-3.478,0.387l-1.158,-1.159l-1.930,-0.515l-1.289,-1.545l-1.545,-0.514l-3.860,0.644l-3.605,-1.546l-1.545,1.416l-5.922,-6.822l-3.474,-2.060l1.029,-0.901l-6.822,2.575l-2.577,0.128l0.258,-1.416l-3.474,-0.901l-2.832,0.645l-0.901,-2.833l-4.763,-0.644l-2.446,1.160l-6.693,1.028l-1.418,0.644l-10.039,0.901l-1.158,0.901l1.929,1.931l-2.575,0.644l0.515,0.772l-2.702,1.288l4.376,1.931l-0.644,1.159l-3.732,0l-0.773,0.772l-3.474,-1.416l-4.250,0l-2.961,1.158l-3.088,-1.029l-5.922,-1.931l-4.248,0.127l-5.535,2.962l-0.387,1.931l-2.705,-1.544l-2.186,2.960l0.772,0.515l-1.545,2.060l2.317,1.802l1.931,-0.129l1.803,1.803l-0.387,1.416l1.416,0.514l-1.287,1.546l-2.575,0.386l-2.704,2.831l2.445,2.576l-0.255,1.801l2.960,3.218l-1.545,1.030l-0.516,0.644l-1.158,-0.129l-1.931,-1.673l-0.643,0l-1.803,-0.644l-0.772,-1.158l-2.446,-0.516l-1.671,0.387l-0.517,-0.515l-3.604,-1.287l-3.990,-0.386l-2.318,-0.516l-0.256,0.387l-3.477,-2.318l-3.089,-1.029l-2.318,-1.545l1.931,-0.514l2.317,-2.189l-1.544,-1.030l3.991,-1.159l-0.129,-0.643l-2.446,0.515l0.128,-1.159l1.417,-0.772l2.575,-0.258l0.384,-0.901l-0.513,-1.417l1.029,-1.415l0,-0.772l-3.990,-0.902l-1.545,0l-1.674,-1.287l-2.059,0.386l-3.476,-0.901l0.129,-0.514l-1.030,-1.159l-2.058,-0.129l-0.258,-0.901l0.643,-0.515l-1.673,-1.544l-2.833,0.256l-0.772,-0.128l-0.773,0.644l-0.901,-0.129l-0.643,-1.674l-0.644,-0.901l0.516,-0.257l2.187,0.128l1.030,-0.643l-0.772,-0.772l-1.803,-0.387l0.129,-0.515l-1.160,-0.515l-1.672,-1.802l0.645,-0.644l-0.258,-1.287l-2.703,-0.643l-1.416,0.385l-0.387,-0.772l-2.833,-0.644l-0.901,-1.545l-0.129,-1.287l-1.286,-0.644l1.158,-0.901l-0.900,-2.446l1.930,-1.544l-0.386,-0.515l3.089,-1.545l-2.832,-1.287l5.792,-3.347l2.448,-1.545l1.030,-1.416l-3.991,-1.802l1.157,-1.802l-2.445,-2.060l1.801,-2.318l-3.089,-2.960l2.448,-2.060l-4.119,-1.801l0.384,-1.932l2.189,-0.257l4.507,-1.031l2.830,-0.900l4.378,1.545l7.466,0.643l10.169,3.089l2.059,1.288l0.129,1.802l-7.336,2.061l-12.102,-2.061l-1.929,0.386l4.504,3.219l0.772,2.060l2.961,1.544l3.218,-2.703l7.596,1.287l0,-2.960l7.465,-1.803l3.992,-0.901l-2.190,-1.674l-0.643,-3.218l7.466,0.772l-1.801,3.348l4.632,-0.129l7.210,-2.703l9.783,-2.318l2.060,1.417l9.397,-1.546l6.695,0.902l0.643,-3.219l7.853,0.772l10.684,2.832l1.673,-1.801l-3.991,-4.507l4.505,-2.702l2.190,-3.090l8.369,0.386l0.769,4.763l0.260,5.536l1.672,1.674l-0.516,1.802l-4.119,2.832l2.832,0.386l5.151,-2.961l1.029,-3.991l-2.832,-1.159l-1.029,-5.664l3.345,-3.346l2.190,1.802l0.644,2.060l1.672,-1.288l3.477,-0.901l5.535,-0.128l5.019,1.544l-2.445,-2.575l-0.256,-2.574l4.760,-0.514l6.437,0.128l5.793,-0.387l-2.189,-1.415l3.219,-1.674l3.090,-0.129l5.150,-1.288l0.385,-0.128l1.029,0l1.418,0l1.545,-0.128l1.416,-0.129l1.027,0l0.389,0l0.900,-0.773l7.080,-0.257l2.190,0.643l6.049,-1.415l4.890,0l0.774,-1.159l2.574,-1.159l6.309,-1.030l4.632,0.772l-3.603,0.644l6.051,0.515l0.771,1.288l2.447,-0.644l9.782,-0.257l5.023,1.673l-2.318,3.089l-7.082,1.546l1.031,1.544l6.180,-0.257l2.961,1.030l11.968,0l2.705,1.544l10.299,0.129l0.387,-1.673l16.603,1.673l0.518,4.892l4.246,1.030l8.111,-1.545l15.834,-0.515l1.930,-3.476l23.170,1.802l2.320,1.545l7.078,2.059l14.416,-0.385l6.438,3.733l10.170,-0.128l9.269,-0.259l6.178,2.447l0.774,-3.219l13.257,0.515l8.496,1.159l3.735,1.158l6.564,2.059l7.209,2.448l8.110,1.029l5.277,2.575l-6.178,1.416l-0.386,2.703l-4.506,0.129l-5.278,-2.317l-5.150,-0.644l-3.475,-1.674l-1.802,2.961l0.385,-0.129zM518.204,80.414l0.645,-1.288l3.733,-0.772l2.702,0.386l1.289,0.515l-0.259,0.643l0.128,0.772l-4.890,0l3.348,0.256zM861.522,24.158l5.666,0.515l-0.128,1.416l-7.725,-1.416l-2.187,0.515zM836.034,22.871l5.279,-0.387l10.426,0.772l1.803,2.189l-9.527,-0.128l-3.989,1.030l-5.021,-1.931l-1.029,1.545zM742.835,13.473l0.516,0.772l5.019,2.575l-14.287,0.387l3.604,-3.090l-5.148,0.644zM718.763,9.226l10.556,0.386l5.922,3.346l-7.853,1.674l-11.328,-1.030l-0.127,-2.446l-2.830,1.930zM609.345,28.277l6.435,-2.317l-0.643,-1.287l6.050,-1.417l8.882,-1.673l8.882,-0.514l4.634,-1.030l5.279,-0.387l1.801,1.159l-1.801,0.772l-9.526,1.417l-8.239,1.287l-8.367,2.445l-3.993,2.704l-4.246,2.574l0.644,2.189l5.149,2.317l-1.672,0.129l-8.756,-0.257l-0.771,-1.287l-4.891,-0.644l-0.386,-1.545l2.830,-0.515l-0.127,-1.417l5.277,-2.316l2.445,0.387zM850.194,82.344l0.901,2.575l0,2.575l1.158,2.832l2.705,4.763l-3.990,-0.901l-1.674,3.862l2.574,2.703l0,1.931l-2.058,-1.674l-1.803,2.189l-0.516,-2.317l0.258,-2.575l-0.258,-2.960l0.645,-2.061l0.127,-3.604l-1.545,-2.575l0.131,-3.733l2.574,-1.287l-1.160,-1.158l1.289,-0.387l-0.642,-1.802z', + RW: + 'M547.169,229.999l1.028,1.545l-0.128,1.544l-0.773,0.387l-1.415,-0.128l-0.773,1.545l-1.673,-0.258l0.257,-1.546l0.386,-0.128l0,-1.674l0.901,-0.643l0.643,0.256l-1.547,0.900z', + SA: + 'M580.509,182.883l-0.387,-1.157l-0.771,-0.773l-0.259,-1.031l-1.415,-1.029l-1.416,-2.188l-0.772,-2.189l-1.802,-1.931l-1.289,-0.386l-1.672,-2.574l-0.386,-1.932l0.128,-1.544l-1.545,-2.961l-1.287,-1.030l-1.416,-0.644l-0.902,-1.545l0.130,-0.514l-0.771,-1.417l-0.774,-0.643l-1.030,-2.060l-1.674,-2.059l-1.287,-1.931l-1.417,0l0.387,-1.417l0.131,-0.901l0.384,-1.158l2.960,0.514l1.160,-0.900l0.643,-0.901l2.060,-0.386l0.515,-0.902l0.901,-0.515l-2.703,-2.703l5.407,-1.287l0.514,-0.516l3.218,0.773l3.990,1.931l7.596,5.406l5.020,0.258l2.447,0.257l0.644,1.288l1.930,-0.129l1.030,2.317l1.288,0.644l0.513,0.902l1.803,1.158l0.127,1.159l-0.256,0.900l0.387,0.901l0.772,0.644l0.386,0.901l0.387,0.644l0.771,0.644l0.772,-0.258l0.517,1.030l0.127,0.643l1.031,2.704l8.110,1.416l0.514,-0.644l1.159,1.931l-1.802,5.408l-7.981,2.702l-7.853,1.030l-2.445,1.159l-1.931,2.832l-1.287,0.515l-0.644,-0.901l-1.031,0.128l-2.574,-0.257l-0.514,-0.257l-3.090,0l-0.773,0.257l-1.158,-0.644l-0.645,1.288l0.258,1.158l1.158,-0.772z', + SB: + 'M901.944,255.23l0.772,0.903l-1.930,0l-1.031,-1.674l1.674,0.643l-0.515,-0.128zM900.786,252.784l-0.387,0.516l-2.060,-2.318l-0.514,-1.544l0.901,0l1.029,2.059l-1.031,-1.287zM898.597,253.557l-1.159,0.129l-1.544,-0.386l-0.643,-0.386l0.256,-1.031l1.674,0.387l0.900,0.644l-0.516,-0.643zM895.251,248.537l0.643,0.901l0.128,0.515l-2.060,-1.158l-1.543,-0.902l-1.031,-0.901l0.384,-0.258l1.290,0.644l-2.189,-1.159zM888.556,245.834l1.031,0.900l-0.516,0.129l-1.160,-0.515l-1.158,-1.159l0.129,-0.386l-1.674,-1.031z', + SD: + 'M556.308,215.711l-1.416,1.028l-1.545,0l-2.189,0.644l-1.802,-0.644l-1.029,0.774l-2.446,-1.802l-0.644,-1.159l-1.416,0.643l-1.287,-0.257l-0.773,0.515l-1.158,-0.387l-1.672,-2.188l-0.389,-0.901l-2.059,-1.031l-0.643,-1.544l-1.159,-1.287l-1.800,-1.287l0,-0.901l-1.419,-1.159l-2.059,-1.159l-1.029,-0.771l-0.129,-0.773l0.387,-1.159l0,-1.159l-1.545,-1.674l-0.258,-1.157l0,-0.645l-0.902,-0.772l-0.126,-1.544l-0.517,-1.030l-0.902,0.129l0.259,-1.031l0.643,-1.030l-0.258,-1.159l0.901,-0.772l-0.643,-0.643l0.774,-1.673l1.158,-2.060l2.316,0.257l0.258,-10.427l0,-1.415l2.832,0l0,-5.408l10.813,0l10.556,0l10.299,0l1.158,2.704l-0.643,0.386l0.385,2.832l1.030,3.218l1.029,0.644l1.418,1.029l-1.288,1.546l-2.061,0.386l-0.770,0.901l-0.258,1.802l-1.160,3.861l0.258,1.160l-0.387,2.317l-1.158,2.575l-1.672,1.415l-1.158,2.060l-0.259,1.030l-1.286,0.772l-0.774,2.831l0,2.447l-0.386,0.774l-1.416,0.127l-0.901,1.545l1.673,0.129l1.416,1.287l0.388,1.158l1.286,0.644l1.158,2.832l-2.188,2.447l1.416,-1.159z', + SE: + 'M525.026,49.905l-2.703,1.930l0.516,1.674l-4.377,2.060l-5.150,2.317l-1.930,3.861l1.930,1.932l2.576,1.415l-2.576,3.090l-2.704,0.643l-1.028,4.507l-1.545,2.445l-3.348,-0.257l-1.415,2.188l-3.218,0.129l-0.773,-2.575l-2.317,-3.090l-2.059,-3.732l1.158,-1.545l2.318,-1.931l0.900,-3.090l-1.803,-1.415l-0.127,-3.605l1.800,-2.446l2.705,0l0.903,-1.030l-1.031,-0.901l4.248,-3.862l2.703,-2.960l1.674,-1.931l2.702,0l0.644,-1.417l5.022,0.387l0.513,-1.803l1.673,-0.129l3.475,1.417l4.250,1.801l0.128,4.120l0.901,1.031l4.635,-0.772z', + SJ: + 'M539.059,11.285l-3.991,1.416l-7.852,0.387l-7.854-0.515l-0.514-0.644l-3.863-0.128l-2.961-1.159l8.369-0.772l3.86,0.644l2.704-0.772l6.822,0.643L539.059,11.285zM505.976,12.314h-3.733l-1.546-0.901l-7.335,1.031l2.059,2.06l5.276,2.317l3.99,0.772l-2.314,0.9l5.791,1.675l3.219-0.129l1.287-2.189l2.316-0.515l1.545-2.06l6.693-1.031l-8.881-1.931l-3.347-1.03l-3.991,0.128L505.976,12.314zM531.851,17.207l-3.863-0.515l-1.158-1.03l-5.535,0.515l1.674,0.901l-1.932,0.643l4.765,0.645L531.851,17.207z', + SK: + 'M516.017,93.673l0.127,0.129l1.158,-0.386l1.289,1.030l1.672,-0.644l1.289,0.257l1.929,-0.386l2.577,1.030l-0.774,0.772l-0.513,1.030l-0.645,0.257l-2.832,-0.772l-0.900,0.129l-0.645,0.643l-1.287,0.387l-0.258,-0.258l-1.287,0.515l-1.158,0l-0.129,0.644l-2.318,0.257l-1.028,-0.257l-1.288,-0.772l-0.257,-0.903l0.127,-0.256l0.387,-0.644l1.288,0l0.900,-0.258l0,-0.257l0.517,-0.129l0.256,-0.643l0.644,0l0.385,-0.515l-0.774,0z', + SL: + 'M434.402,208.759l-0.772,-0.257l-1.931,-1.031l-1.287,-1.544l-0.515,-0.902l-0.386,-2.059l1.415,-1.159l0.387,-0.772l0.386,-0.515l0.772,-0.129l0.644,-0.514l2.188,0l0.772,1.029l0.515,1.159l0,0.773l0.387,0.642l0,1.030l0.644,-0.127l-1.159,1.157l-1.287,1.546l-0.129,0.771l0.644,-0.902z', + SN: + 'M420.242,190.35l-1.159,-2.059l-1.287,-1.030l1.159,-0.515l1.287,-1.803l0.644,-1.416l0.901,-0.901l1.416,0.257l1.287,-0.643l1.416,0l1.288,0.773l1.801,0.772l1.674,1.931l1.802,1.930l0.129,1.674l0.515,1.545l1.030,0.772l0.257,1.030l-0.128,0.772l-0.387,0.129l-1.544,-0.129l-0.130,0.258l-0.643,0l-1.931,-0.643l-1.287,0l-5.020,-0.129l-0.644,0.386l-0.901,-0.129l-1.545,0.386l-0.387,-2.058l2.447,0.127l0.644,-0.386l0.514,0l1.030,-0.644l1.159,0.516l1.158,0.128l1.159,-0.644l-0.516,-0.773l-0.900,0.387l-0.901,0l-1.030,-0.643l-0.901,0l-0.515,0.643l2.961,-0.129z', + SO: + 'M591.708,205.411l-8.754-3.218l-1.029-0.901l-1.028-1.287l-1.032-1.545l0.645-0.901l0.9-1.416l0.902,0.515l0.516,1.03l1.286,1.158h1.288l2.575-0.643l2.959-0.387l2.318-0.771l1.286-0.259l1.029-0.515h1.545l0.902-0.128l1.158-0.387l1.416-0.257l1.288-0.9h1.028l-0.13-0.772l0.26,1.544l-0.258,1.545v1.415l-0.644,0.901l-0.772,2.961l-1.288,2.961l-1.674,3.475l-2.316,3.991l-2.189,2.962l-3.218,3.732l-2.702,2.188l-3.991,2.576l-2.575,2.059l-2.959,3.348l-0.645,1.414l-0.516,0.646l-1.931-2.188l0.13-10.17l2.188-2.959l1.029-0.645h1.803l2.318-1.932l3.475-0.129l7.722-8.11H591.708z', + SR: + 'M311.337,210.946l3.219,0.517l0.257,-0.517l2.188,-0.257l2.833,0.774l-1.417,2.316l0.259,1.803l1.029,1.674l-0.386,1.157l-0.257,1.159l-0.772,1.158l-1.545,-0.515l-1.159,0.259l-1.158,-0.259l-0.259,0.773l0.516,0.514l-0.257,0.517l-1.546,-0.130l-1.545,-2.317l-0.385,-1.545l-0.902,0l-1.157,-1.931l0.514,-1.416l-0.129,-0.643l1.545,-0.772l-0.514,2.319z', + SV: + 'M228.694,190.865l-0.257,0.645l-1.545,0l-1.030,-0.259l-1.029,-0.515l-1.545,-0.129l-0.772,-0.644l0.128,-0.385l0.901,-0.644l0.515,-0.385l-0.129,-0.258l0.644,-0.129l0.772,0.129l0.515,0.643l0.902,0.386l0,0.386l1.287,-0.386l0.515,0.258l0.386,0.256l0.258,-1.031z', + SY: + 'M569.696,137.055l-5.278,2.833l-2.96-1.031l0.256-0.385v-1.16l0.644-1.416l1.546-1.029l-0.516-1.031l-1.159-0.128l-0.257-2.059l0.645-1.159l0.771-0.643l0.644-0.515l0.129-1.545l0.901,0.514l2.96-0.772l1.416,0.514h2.317l3.09-1.028l1.416,0.128l3.09-0.515l-1.416,1.673l-1.417,0.773l0.259,1.931l-1.029,3.218L569.696,137.055z', + SZ: + 'M551.674,299l-0.644,1.158l-1.545,0.386l-1.545,-1.544l-0.128,-0.902l0.771,-1.030l0.258,-0.771l0.773,-0.129l1.416,0.385l0.385,1.289l-0.259,-1.158z', + TD: + 'M504.302,192.281l0.258,-1.158l-1.674,-0.130l0,-1.673l-1.158,-1.029l1.158,-3.605l3.475,-2.446l0.130,-3.605l1.030,-5.406l0.514,-1.159l-1.030,-0.901l-0.129,-0.901l-1.029,-0.643l-0.643,-4.249l2.702,-1.416l10.815,5.149l11.069,5.407l-0.258,10.427l-2.316,-0.257l-1.158,2.060l-0.774,1.673l0.643,0.643l-0.901,0.772l0.258,1.159l-0.643,1.030l-0.259,1.031l0.902,-0.129l0.517,1.030l0.126,1.544l0.902,0.772l0,0.645l-1.672,0.514l-1.417,1.030l-1.932,2.962l-2.574,1.287l-2.574,-0.130l-0.772,0.259l0.256,0.900l-1.416,0.901l-1.157,1.031l-3.349,1.029l-0.642,-0.515l-0.517,-0.129l-0.384,0.773l-2.318,0.129l0.515,-0.644l-0.900,-1.930l-0.387,-1.160l-1.158,-0.385l-1.545,-1.546l0.514,-1.287l1.287,0.257l0.774,-0.257l1.415,0.129l-1.415,-2.446l0.128,-1.802l-0.258,-1.804l1.029,1.801z', + TG: + 'M470.317,210.432l-2.187,0.514l-0.644,-0.900l-0.773,-1.673l-0.128,-1.416l0.514,-2.318l-0.643,-1.030l-0.258,-2.187l0,-1.932l-1.159,-1.417l0.258,-0.772l2.317,0l-0.259,1.417l0.775,0.900l0.900,0.902l0.129,1.287l0.516,0.515l-0.131,6.307l-0.773,-1.803z', + TH: + 'M741.548,194.082l-2.445-1.157h-2.188l0.385-2.06h-2.446l-0.128,2.961l-1.545,3.99l-0.902,2.318l0.26,1.931l1.801,0.129l1.031,2.445l0.514,2.318l1.416,1.545l1.674,0.257l1.416,1.415l-0.9,1.159l-1.802,0.257l-0.13-1.287l-2.188-1.156l-0.516,0.385l-1.029-1.029l-0.515-1.288l-1.419-1.544l-1.285-1.287l-0.516,1.544l-0.514-1.417l0.385-1.672l0.773-2.576l1.287-2.832l1.545-2.445l-1.158-2.447l0.129-1.287l-0.387-1.545l-1.803-2.06l-0.643-1.416l1.029-0.514l0.9-2.318l-1.03-1.802l-1.802-1.931l-1.287-2.317l1.158-0.515l1.287-2.96l1.803-0.129l1.672-1.159l1.544-0.643l1.161,0.9l0.13,1.546l1.8,0.128l-0.643,2.832v2.446l2.832-1.544l0.9,0.385h1.545l0.516-1.03l2.061,0.258l2.059,2.189l0.129,2.574l2.188,2.446l-0.127,2.189l-0.902,1.288l-2.447-0.387l-3.475,0.514l-1.801,2.189L741.548,194.082z', + TJ: + 'M656.587,118.519l-1.030,0.900l-2.961,-0.515l-0.257,1.545l2.961,-0.129l3.347,0.901l5.020,-0.514l0.773,2.573l0.772,-0.256l1.674,0.515l-0.129,1.158l0.515,1.416l-2.832,0l-1.802,-0.129l-1.674,1.159l-1.285,0.257l-0.904,0.643l-1.030,-0.900l0.259,-2.317l-0.901,-0.129l0.384,-0.772l-1.543,-0.645l-1.160,0.903l-0.256,1.157l-0.386,0.387l-1.674,-0.128l-0.773,1.287l-0.901,-0.515l-2.059,0.900l-0.773,-0.385l1.547,-2.703l-0.645,-2.060l-1.931,-0.644l0.643,-1.159l2.318,0.129l1.285,-1.417l0.775,-1.801l3.603,-0.644l-0.514,1.287l0.386,0.772l-1.158,0.127z', + TL: + 'M801.921,250.982l0.258,-0.643l2.316,-0.644l1.934,-0.129l0.771,-0.257l1.029,0.257l-0.900,0.773l-2.834,1.158l-2.316,0.773l0,-0.773l0.258,0.515z', + TM: + 'M630.069,130.876l-0.129,-2.188l-2.060,-0.128l-3.088,-2.317l-2.188,-0.387l-2.961,-1.288l-1.932,-0.257l-1.157,0.515l-1.803,-0.129l-1.932,1.546l-2.317,0.513l-0.515,-1.930l0.387,-2.832l-2.059,-0.901l0.644,-1.802l-1.804,-0.129l0.645,-2.318l2.574,0.644l2.316,-0.773l-1.931,-1.673l-0.772,-1.544l-2.187,0.643l-0.260,2.059l-0.900,-1.802l1.160,-0.900l3.090,-0.515l1.800,0.772l1.930,2.060l1.419,-0.128l2.959,0l-0.384,-1.417l2.315,-0.901l2.188,-1.545l3.735,1.416l0.256,2.188l1.030,0.515l2.832,-0.128l1.029,0.515l1.289,2.702l3.089,1.931l1.674,1.288l2.832,1.288l3.604,1.158l0,1.674l-0.902,-0.128l-1.285,-0.774l-0.387,1.030l-2.317,0.516l-0.515,2.188l-1.546,0.772l-2.058,0.386l-0.517,1.288l-2.059,0.257l2.832,1.030z', + TN: + 'M490.785,145.294l-1.159,-4.763l-1.674,-1.159l0,-0.643l-2.187,-1.545l-0.257,-2.059l1.674,-1.545l0.642,-2.189l-0.514,-2.575l0.642,-1.416l2.962,-1.029l1.801,0.257l0,1.415l2.190,-1.028l0.257,0.514l-1.417,1.416l0,1.159l0.902,0.643l-0.258,2.446l-1.801,1.287l0.515,1.416l1.415,0.129l0.644,1.287l1.030,0.387l-0.129,2.060l-1.288,0.772l-0.900,0.902l-1.803,1.029l0.257,1.158l-0.257,1.031l1.287,-0.643z', + TR: + 'M535.712,120.707l2.961-2.575l4.247-0.129l1.03-2.06l5.149,0.387l3.218-1.803l3.219-0.772h4.376l4.765,1.932l3.86,1.028l3.089-0.515l2.317,0.259l3.218-1.417l2.834-0.128l2.702,1.416l0.386,0.901l-0.256,1.288l2.059,0.643l1.029,0.773l-1.802,0.772l0.772,3.089l-0.516,0.901l1.416,2.189l-1.287,0.385l-0.899-0.643l-3.091-0.385l-1.157,0.385l-3.091,0.515l-1.416-0.128l-3.09,1.028h-2.317l-1.416-0.514l-2.96,0.772l-0.901-0.514l-0.129,1.545l-0.644,0.515l-0.771,0.643l-1.029-1.287l1.029-0.902l-1.674,0.129l-2.188-0.514l-1.803,1.544l-4.118,0.257l-2.189-1.416l-2.961-0.128l-0.644,1.159l-1.802,0.256l-2.574-1.415h-2.961l-1.545-2.574l-2.059-1.545l1.286-2.06L535.712,120.707zM535.581,114.27l2.705-0.772l2.317,0.257l0.386,1.03l2.316,0.902l-0.514,0.643l-3.219,0.257l-1.031,0.772l-2.313,1.417l-0.774-1.159v-0.644l0.645-0.258l0.771-1.673L535.581,114.27z', + TW: + 'M793.296,161.256l-1.672,4.378l-1.16,2.188l-1.414-2.317l-0.26-1.932l1.545-2.702l2.189-2.06l1.288,0.772L793.296,161.256z', + TZ: + 'M551.03,229.742l5.407,-0.258l10.042,5.793l0.256,2.059l4.119,2.445l-1.289,3.092l0.131,1.543l1.802,1.031l0,0.645l-0.644,1.673l0.129,0.772l-0.129,1.287l0.901,1.673l1.160,2.704l0.900,0.514l-2.060,1.674l-2.961,1.030l-1.672,-0.128l-0.902,0.900l-1.930,0l-0.644,0.386l-3.347,-0.771l-1.931,0.256l-0.773,-3.732l-1.415,-1.933l-2.704,-0.514l-1.543,-0.901l-1.675,-0.384l-1.030,-0.387l-1.159,-0.773l-1.545,-3.346l-1.545,-1.546l-0.514,-1.546l0.257,-1.415l-0.514,-2.445l1.158,-0.128l1.031,-1.032l1.030,-1.416l0.642,-0.513l0,-0.903l-0.642,-0.643l-0.131,-1.030l0.773,-0.387l0.128,-1.544l-1.028,-1.545l0.900,-0.257l-2.961,0z', + UA: + 'M550.901,86.593l0.901,0.129l0.773,-0.644l0.772,0.128l2.833,-0.256l1.673,1.544l-0.643,0.515l0.258,0.901l2.058,0.129l1.030,1.159l-0.129,0.514l3.476,0.901l2.059,-0.386l1.674,1.287l1.545,0l3.990,0.902l0,0.772l-1.029,1.415l0.513,1.417l-0.384,0.901l-2.575,0.258l-1.417,0.772l-0.128,1.159l-2.060,0.128l-1.801,0.902l-2.576,0.128l-2.317,1.030l0.129,1.674l1.416,0.644l2.704,-0.130l-0.516,0.902l-2.959,0.515l-3.606,1.544l-1.544,-0.515l0.643,-1.286l-2.962,-0.774l0.389,-0.514l2.573,-0.901l-0.772,-0.645l-4.120,-0.643l-0.257,-1.029l-2.446,0.386l-1.030,1.416l-2.060,2.059l-1.158,-0.515l-1.287,0.386l-1.287,-0.386l0.773,-0.386l0.385,-0.901l0.774,-0.901l-0.129,-0.386l0.513,-0.258l0.258,0.386l1.545,0.129l0.773,-0.257l-0.517,-0.258l0.131,-0.386l-0.902,-0.772l-0.386,-1.030l-1.030,-0.515l0.257,-0.901l-1.287,-0.644l-1.029,-0.128l-2.059,-0.903l-1.675,0.259l-0.644,0.386l-1.158,0l-0.773,0.644l-1.931,0.257l-0.899,0.515l-1.289,-0.772l-1.673,0l-1.674,-0.258l-1.159,0.515l-0.256,-0.643l-1.415,-0.773l0.513,-1.030l0.774,-0.772l0.515,0.257l-0.646,-1.287l2.447,-2.189l1.287,-0.385l0.257,-0.773l-1.286,-2.317l1.286,-0.129l1.417,-0.644l2.187,-0.129l2.706,0.258l2.959,0.644l2.189,0l0.900,0.386l1.030,-0.386l0.772,0.643l2.446,-0.129l1.031,0.258l0.128,-1.416l0.900,-0.643l-2.318,0.129z', + UG: + 'M551.03,229.742h-2.961l-0.9,0.257l-1.547,0.899l-0.644-0.256v-2.061l0.644-1.027l0.131-2.189l0.514-1.287l1.029-1.416l1.031-0.643l0.9-1.031l-1.158-0.258l0.258-3.217l1.028-0.775l1.803,0.645l2.188-0.645h1.545l1.416-1.027l1.287,1.93l1.031,2.705l0.771,1.93l-1.028,1.931l-1.932,1.674v0.772v2.832L551.03,229.742z', + + US: + 'M284.434,106.546l-2.704,0.772l-2.575,0.644l-3.089,1.673l-1.287,1.417l-0.258,0.386l-0.127,1.545l0.9,1.415h1.159l-0.259-0.9l0.773,0.515l-0.257,0.772l-1.803,0.515l-1.286-0.13l-1.931,0.517l-1.159,0.128l-1.673,0.128l-2.06,0.644l3.733-0.387h0.128l0.773,0.515l-3.733,0.773h-1.802l0.129-0.257l0.128-0.644l-0.9,1.416h0.643l-0.515,2.06l-1.931,2.06l-0.257-0.773l-0.516-0.129l-0.643-0.643h-0.129h-0.128l0.514,1.416l0.773,1.416l0.129,0.257l-1.03,0.901l-1.545,2.188l-0.258-0.128l1.03-1.802l-1.416-1.287l-0.128-2.06l-0.387,0.901v2.446l-1.673-0.901l1.802,1.544l0.515,1.417l0.772,1.674l0.387,2.703l-1.803,1.93l-2.574,1.03l-2.318,1.417l-0.901,0.128l-1.158,1.931l-2.317,1.673l-2.832,1.288l-1.158,2.06l-0.516,1.415l0.387,2.061l1.03,2.187l1.159,2.061v1.029l1.157,2.703l0.129,2.447l-0.514,2.316l-1.159,0.516l-1.287-0.386l-0.386-1.159l-1.031-0.644l-1.545-2.317l-1.287-1.931l-0.257-1.287l0.515-1.674l-0.643-1.544l-1.803-1.545l-1.416-1.03l-3.089,1.158l-0.644-0.772l-2.574-1.287l-2.962,0.386l-2.445-0.258l-1.674,0.515h-1.544l-0.258,1.16l0.772,1.543l-3.605,0.13l-2.316-0.516l-1.545-0.514l-2.059-0.387l-2.318-0.128l-2.317,0.643l-2.446,1.931l-2.702,1.158l-1.417,1.289l-0.644,1.287v1.802l0.129,1.287l0.515,0.901l-1.03,0.129l-1.931-0.643l-2.188-0.773l-0.772-1.287l-0.515-1.931l-1.545-1.545l-1.03-1.545l-1.288-1.802l-1.93-1.159l-2.189,0.13l-1.674,2.058l-2.316-0.772l-1.288-0.772l-0.772-1.545l-0.9-1.416l-1.545-1.159l-1.416-0.901l-0.902-0.9h-4.633l-0.129,1.158h-2.06h-5.407l-6.178-1.931l-3.992-1.288l0.258-0.515l-3.475,0.259l-3.09,0.256l-0.258-1.029l-1.159-1.416l-2.831-1.545l-1.158-0.129l-1.16-0.9l-2.059-0.13l-0.772-0.515L140,132.292l-2.702-2.704l-2.189-3.732l0.128-0.644l-1.287-0.901l-2.059-2.317l-0.386-2.188l-1.417-1.417l0.644-2.189l-0.129-2.317l-0.901-1.544l0.901-2.96l0.129-2.962l0.514-4.119l-0.771-2.188l-0.387-2.575l3.734,0.515l1.158,2.06l0.644-0.773l-0.387-2.188l-1.287-2.189h15.962h2.704h32.182h18.536h5.536v-1.03h0.901l0.516,1.417l0.772,0.514l1.93,0.129l2.704,0.515l2.703,0.773l2.188-0.387l3.219,0.773h0.385h0.515l0.258-0.129l0.386-0.129l0.387-0.128l0.772-0.258l0.643-0.129l0.644,0.129l0.386,0.258h0.258l0.386,0.257l0.773,0.257l0.772,0.258l0.772,0.386l0.643,0.257l0.387,0.13l0.258,0.128l0.514,0.128l0.515,0.258l0.515,0.258l0.515,0.128l0.515,0.257l0.515,0.258l0.515,0.129l0.514,0.257l0.13,0.386l0.128,0.387l0.386,0.257h0.257h0.902h0.257v0.129v0.129v0.128v0.258h0.129l0.129,0.257h0.128l0.258,0.129l0.386,0.128l0.258-0.128h0.128l0.258,0.258h0.128v0.129l-0.386,0.515l0.516,0.257l0.643,0.257l0.644,0.258l0.514,0.128l0.515,0.386l0.128,0.387v0.257l0.13,0.515l0.128,0.386l0.129,0.515l0.13,0.515l0.127,0.515v0.386l0.129,0.515l0.258,0.772l-0.129,0.258l-0.385,0.515l-0.259,0.515v0.129v0.128l-0.514,0.516l-0.772,1.03l-0.387,0.385l-0.257,0.515v0.257v0.13l0.257,0.257l0.387,0.257l0.514,0.129h0.644l0.643-0.258l0.644-0.257l0.644-0.258l0.643-0.385l0.644-0.258l0.645-0.129l0.9-0.128h0.387h0.128l0.644-0.129l0.643-0.258l0.643-0.257l0.902-0.257l0.772-0.258l0.386-0.128l0.258-0.13v-0.128v-0.258l-0.387-0.772v-0.129l-0.257-0.386l0.386-0.129l0.515-0.257h0.258l0.772-0.128h0.643h0.902l0.772,0.128h0.901h0.516h0.643l0.257-0.515l0.387-0.386l0.256-0.258l0.387-0.258l2.703-1.801l1.287-0.516h4.12h4.891l0.258-0.772h0.901l1.158-0.515l0.902-1.159l0.901-2.187l2.06-1.932l0.9,0.644l1.804-0.386l1.157,0.772v3.605l1.803,1.545v-1.158V106.546zM16.808,64.322l2.059,0.257l0.258,1.031l-1.545,0.386l-1.802-0.516l-1.673-0.772l-2.703,0.386L16.808,64.322zM52.465,70.759l1.803,0.257l1.157,0.774l-2.317,1.286l-2.703,1.029l-1.416-0.643l-0.385-1.288l2.445-0.901l-1.416,0.514L52.465,70.759zM85.42,39.22v9.913v15.446h2.574l2.704,0.774L92.5,66.51l2.445,1.803l2.575-1.545l2.703-0.902l1.545,1.417l1.802,1.159l2.446,1.159l1.674,1.93l2.703,3.09l4.634,1.673v1.802l-1.417,1.287l-1.544-1.029l-2.316-0.901l-0.773-2.318l-3.476-2.189l-1.415-2.573l-2.576-0.129l-4.376-0.129l-3.09-0.773l-5.535-2.703l-2.702-0.514l-4.636-1.031l-3.733,0.259l-5.278-1.288l-3.217-1.159l-2.962,0.643l0.515,1.804l-1.544,0.257l-3.09,0.515l-2.318,0.9l-2.961,0.644l-0.385-1.673l1.159-2.575l2.831-0.9l-0.771-0.645l-3.347,1.545l-1.802,1.802l-3.991,1.931l2.059,1.288l-2.574,1.931l-2.961,1.03l-2.704,0.9l-0.643,1.159l-4.119,1.416l-0.901,1.288l-3.09,1.158l-1.931-0.258l-2.445,0.773l-2.832,0.901l-2.189,0.902l-4.634,0.772l-0.387-0.516l2.962-1.158l2.574-0.901l2.832-1.417l3.347-0.385l1.416-1.03l3.734-1.673l0.514-0.516l2.059-0.901l0.386-2.059l1.418-1.545l-3.091,0.773l-0.901-0.516l-1.415,1.031l-1.803-1.417l-0.644,1.03l-1.029-1.417l-2.704,1.16h-1.673l-0.257-1.674l0.514-0.901l-1.673-1.029l-3.604,0.513l-2.189-1.287l-1.931-0.643v-1.545l-2.059-1.03l1.029-1.673l2.188-1.416l1.03-1.416l2.189-0.129l1.802,0.386l2.189-1.287l1.93,0.257l2.059-0.901l-0.513-1.158l-1.546-0.515l2.059-1.03h-1.673l-2.832,0.515l-0.772,0.643l-2.188-0.514l-3.863,0.257l-3.861-0.643l-1.158-1.159l-3.476-1.545l3.862-1.03l6.05-1.416h2.188l-0.386,1.416l5.665-0.129l-2.189-1.673l-3.347-1.031l-1.931-1.286l-2.574-1.158l-3.605-0.901l1.417-1.417l4.762-0.129l3.475-1.158l0.644-1.288l2.703-1.287l2.704-0.386l5.021-1.159l2.574,0.128l4.119-1.415l3.99,0.643l2.06,1.159l1.159-0.515l4.505,0.128l-0.128,0.644l4.119,0.516l2.703-0.258l5.664,0.773l5.278,0.257l2.06,0.386l3.604-0.514l3.991,0.9l-2.961-0.387L85.42,39.22zM2.647,55.182l1.673,0.515l1.674-0.258l2.189,0.644l2.574,0.386l-0.128,0.258l-2.061,0.644l-2.059-0.644l-1.03-0.514l-2.446,0.128L2.39,56.213l-0.257,1.031L2.647,55.182zM45.256,175.546v-0.773l-0.385-1.029l0.643-0.643l-0.258-0.516l0.129-0.128v-0.129l1.803,0.773l0.256,0.385v0.258l0.258,0.129l0.129,0.128l0.385,0.387l-0.643,0.514l-0.772,0.129l-0.515,0.515l-0.258,0.387L45.256,175.546zM43.067,170.01l-0.385,0.258l-1.158-0.128l0.128-0.387L43.067,170.01zM44.999,170.912v0.257l-0.258,0.129l-0.9,0.128l-0.13-0.514h-0.386l-0.258-0.387l0.13-0.128l0.257-0.129l0.257,0.385l0.516-0.128L44.999,170.912zM39.335,169.496l-0.515-0.643l0.386-0.13l0.515-0.257l0.386,0.643h0.257l0.258,0.516h-0.515l-0.257-0.129h-0.129H39.335zM34.829,167.564l0.129-0.256l0.386-0.259l0.643,0.13l0.13,0.129l-0.13,0.514l-0.256,0.258l-0.516-0.129L34.829,167.564z', + + UY: + 'M310.05,308.396l1.674,-0.257l2.704,2.059l1.030,-0.130l2.702,1.805l2.189,1.414l1.545,1.804l-1.158,1.286l0.772,1.545l-1.159,1.674l-3.089,1.545l-2.060,-0.515l-1.416,0.257l-2.447,-1.157l-1.801,0.128l-1.674,-1.545l0.129,-1.674l0.643,-0.643l0,-2.705l0.644,-2.702l-0.772,2.189z', + UZ: + 'M644.487,126.371l0,-1.674l-3.604,-1.158l-2.832,-1.288l-1.674,-1.288l-3.089,-1.931l-1.289,-2.702l-1.029,-0.515l-2.832,0.128l-1.030,-0.515l-0.256,-2.188l-3.735,-1.416l-2.188,1.545l-2.315,0.901l0.384,1.417l-2.959,0l-0.130,-9.914l6.951,-1.544l0.515,0.129l0.644,0.386l1.159,0.515l2.317,1.030l2.189,1.029l2.574,2.446l3.219,-0.385l4.633,-0.259l3.219,2.060l-0.258,2.703l1.287,0l0.644,2.189l3.347,0.128l0.771,1.288l1.030,-0.129l1.160,-1.931l3.603,-1.802l1.547,-0.515l0.770,0.258l-2.317,1.801l2.061,1.030l1.929,-0.772l3.090,1.416l-3.346,1.932l-2.060,-0.257l-1.158,0.127l-0.386,-0.772l0.514,-1.287l-3.603,0.644l-0.775,1.801l-1.285,1.417l-2.318,-0.129l-0.643,1.159l1.931,0.644l0.645,2.060l-1.547,2.703l-2.059,-0.515l1.416,0z', + VE: + 'M273.105,195.242l-0.128,0.644l-1.545,0.257l0.902,1.287l-0.129,1.416l-1.160,1.545l1.030,2.188l1.159,-0.257l0.643,-1.931l-0.900,-0.901l-0.129,-2.060l3.346,-1.159l-0.385,-1.158l1.029,-0.901l0.902,1.931l1.930,0l1.804,1.545l0,0.902l2.444,0l2.962,-0.259l1.544,1.159l2.060,0.387l1.416,-0.902l0.128,-0.644l3.218,-0.128l3.348,-0.130l-2.317,0.773l0.900,1.288l2.189,0.257l2.060,1.288l0.386,2.187l1.417,-0.128l1.028,0.645l-2.187,1.672l-0.130,0.902l0.902,1.029l-0.644,0.516l-1.673,0.385l0,1.287l-0.772,0.772l1.930,2.061l0.257,0.772l-0.900,1.030l-3.090,0.902l-1.930,0.515l-0.774,0.643l-2.059,-0.773l-2.060,-0.257l-0.514,0.257l1.158,0.643l0,1.804l0.387,1.672l2.188,0.259l0.128,0.514l-1.931,0.772l-0.257,1.159l-1.159,0.515l-1.930,0.644l-0.515,0.772l-2.060,0.129l-1.544,-1.417l-0.773,-2.703l-0.772,-0.901l-1.031,-0.644l1.416,-1.287l-0.127,-0.644l-0.773,-0.772l-0.516,-1.802l0.259,-1.931l0.513,-0.901l0.517,-1.416l-0.902,-0.515l-1.545,0.386l-1.931,-0.129l-1.158,0.258l-1.802,-2.317l-1.546,-0.387l-3.475,0.258l-0.644,-0.901l-0.772,-0.257l0,-0.516l0.257,-1.030l-0.128,-1.028l-0.644,-0.645l-0.387,-1.287l-1.415,-0.129l0.772,-1.545l0.387,-1.931l0.772,-1.030l1.029,-0.771l0.644,-1.289l-1.802,0.514z', + VN: + 'M756.353,168.853l-3.606,2.316l-2.316,2.575l-0.514,1.930l2.059,2.832l2.445,3.476l2.445,1.675l1.674,2.188l1.287,5.020l-0.385,4.634l-2.318,1.802l-3.090,1.803l-2.187,2.188l-3.348,2.574l-0.902,-1.801l0.644,-1.803l-1.929,-1.544l2.316,-1.030l2.834,-0.258l-1.160,-1.674l4.506,-2.059l0.386,-3.218l-0.644,-1.803l0.387,-2.702l-0.645,-1.803l-2.061,-1.931l-1.673,-2.318l-2.188,-3.217l-3.219,-1.674l0.774,-0.902l1.672,-0.772l-1.028,-2.317l-3.347,0l-1.159,-2.446l-1.544,-2.188l1.416,-0.644l2.187,0l2.576,-0.257l2.317,-1.416l1.287,1.030l2.445,0.515l-0.387,1.545l1.289,1.029l-2.704,-0.645z', + VU: + 'M915.718,269.777l1.674,1.545l-0.901,0.387l-0.902,-1.160l-0.129,0.772zM914.56,269.133l-0.387,-0.643l0,-2.060l1.287,0.773l0.387,2.189l-0.774,-0.387l0.513,-0.128z', + YE: + 'M608.315,182.111l-1.931,0.772l-0.516,1.159l-0.127,0.901l-2.704,1.159l-4.249,1.287l-2.445,1.802l-1.158,0.258l-0.774,-0.258l-1.673,1.159l-1.673,0.515l-2.189,0.128l-0.772,0.130l-0.515,0.772l-0.772,0.128l-0.386,0.774l-1.287,-0.130l-0.903,0.387l-1.930,-0.129l-0.644,-1.545l0.129,-1.546l-0.516,-0.772l-0.513,-1.930l-0.772,-1.158l0.514,-0.129l-0.258,-1.159l0.388,-0.515l-0.130,-1.288l1.158,-0.772l-0.258,-1.158l0.645,-1.288l1.158,0.644l0.773,-0.257l3.090,0l0.514,0.257l2.574,0.257l1.031,-0.128l0.644,0.901l1.287,-0.515l1.931,-2.832l2.445,-1.159l7.853,-1.030l2.061,4.378l-0.900,-1.930z', + ZA: + 'M550.13,305.822l-0.516,0.387l-1.158,1.287l-0.773,1.416l-1.544,1.93l-2.96,2.832l-1.932,1.545l-2.061,1.287l-2.832,1.031l-1.287,0.128l-0.387,0.772l-1.672-0.386l-1.288,0.514l-2.961-0.514l-1.544,0.257l-1.158-0.128l-2.834,1.028l-2.316,0.517l-1.545,1.028h-1.285l-1.16-0.9l-0.9-0.128l-1.158-1.16l-0.131,0.388l-0.385-0.772v-1.546l-0.771-1.801l0.771-0.516v-2.061l-1.802-2.445l-1.288-2.316l-1.931-3.478l1.286-1.415l1.032,0.773l0.384,1.158l1.288,0.129l1.673,0.514l1.418-0.129l2.445-1.416v-9.912l0.772,0.387l1.544,2.574l-0.258,1.674l0.645,0.9l1.93-0.256l1.289-1.287l1.287-0.774l0.643-1.286l1.287-0.645l1.158,0.387l1.288,0.773l2.188,0.129l1.801-0.645l0.258-0.901l0.387-1.287l1.545-0.128l0.772-1.03l0.901-1.804l2.445-2.059l3.733-1.93h1.157l1.289,0.514l0.9-0.387l1.416,0.258l1.287,3.862l0.771,1.802l-0.514,3.09l0.258,0.9l-1.416-0.385l-0.773,0.129l-0.258,0.771l-0.771,1.029L547.94,299l1.545,1.544l1.545-0.386l0.644-1.158h2.059l-0.772,1.93l-0.258,2.318l-0.77,1.157L550.13,305.822zM543.306,304.922l-1.158-0.773l-1.287,0.516l-1.416,1.029l-1.416,1.803l1.931,2.059l1.03-0.258l0.514-0.9l1.417-0.386l0.515-0.901l0.773-1.287L543.306,304.922z', + ZM: + 'M553.476,251.883l1.287,1.160l0.644,2.315l-0.386,0.644l-0.645,2.189l0.516,2.317l-0.773,0.902l-0.772,2.447l1.416,0.770l-8.239,2.189l0.258,1.932l-2.060,0.385l-1.543,1.031l-0.259,1.028l-1.028,0.130l-2.319,2.188l-1.545,1.802l-0.902,0l-0.772,-0.257l-3.088,-0.256l-0.515,-0.259l0,-0.259l-1.030,-0.513l-1.803,-0.258l-2.187,0.644l-1.673,-1.674l-1.804,-2.187l0.128,-8.625l5.536,0.127l-0.257,-1.028l0.387,-0.903l-0.387,-1.287l0.257,-1.286l-0.257,-0.902l0.901,0.130l0.128,0.772l1.289,0l1.672,0.258l0.903,1.158l2.187,0.385l1.674,-0.901l0.644,1.416l2.058,0.387l0.903,1.158l1.159,1.545l2.058,0l-0.258,-2.961l-0.642,0.516l-1.932,-1.031l-0.772,-0.514l0.387,-2.705l0.514,-3.088l-0.642,-1.288l0.771,-1.673l0.643,-0.387l3.733,-0.386l1.030,0.258l1.159,0.773l1.030,0.387l1.675,0.384l-1.543,-0.901z', + ZW: + 'M549.228,286.898l-1.416,-0.257l-0.901,0.386l-1.289,-0.513l-1.157,0l-1.673,-1.290l-2.061,-0.385l-0.772,-1.674l0,-1.030l-1.158,-0.256l-3.089,-2.962l-0.900,-1.544l-0.516,-0.516l-1.030,-2.058l3.088,0.256l0.772,0.257l0.902,0l1.545,-1.802l2.319,-2.188l1.028,-0.130l0.259,-1.028l1.543,-1.031l2.060,-0.385l0.129,1.029l2.317,-0.129l1.287,0.645l0.515,0.645l1.287,0.254l1.415,0.774l0,3.475l-0.513,1.801l-0.128,2.061l0.385,0.773l-0.257,1.545l-0.386,0.258l-0.773,1.930l2.832,-3.089z', + }, +}; diff --git a/test/bunnies.html b/test/bunnies.html new file mode 100644 index 000000000..f40aba225 --- /dev/null +++ b/test/bunnies.html @@ -0,0 +1,200 @@ + + + + + + +
      + + + + diff --git a/test/ifame.html b/test/ifame.html new file mode 100644 index 000000000..386c0d7c5 --- /dev/null +++ b/test/ifame.html @@ -0,0 +1,14 @@ + + + + + KonvaJS Sandbox + + + + + + + + + \ No newline at end of file diff --git a/test/import-test.cjs b/test/import-test.cjs new file mode 100644 index 000000000..c060a3cde --- /dev/null +++ b/test/import-test.cjs @@ -0,0 +1,6 @@ +// try to import only core +const Konva = require('../'); + +// just do a simple action +const stage = new Konva.Stage(); +stage.toDataURL(); diff --git a/test/import-test.mjs b/test/import-test.mjs new file mode 100644 index 000000000..cebddda14 --- /dev/null +++ b/test/import-test.mjs @@ -0,0 +1,18 @@ +function equal(val1, val2, message) { + if (val1 !== val2) { + throw new Error('Not passed: ' + message); + } +} + +// try to import only core +import Konva from '../lib/Core.js'; +import { Rect } from '../lib/shapes/Rect.js'; +import '../lib/index-node.js'; + +equal(Rect !== undefined, true, 'Rect is defined'); + +equal(Konva.Rect, Rect, 'Rect is injected'); + +// // just do a simple action +const stage = new Konva.Stage(); +stage.toDataURL(); diff --git a/test/manual-tests.html b/test/manual-tests.html new file mode 100644 index 000000000..a4c09abfa --- /dev/null +++ b/test/manual-tests.html @@ -0,0 +1,70 @@ + + +
      + + + + + + + + + + +
      + + + + + diff --git a/test/manual/Blur-test.ts b/test/manual/Blur-test.ts new file mode 100644 index 000000000..9bafa8e3f --- /dev/null +++ b/test/manual/Blur-test.ts @@ -0,0 +1,279 @@ +import { assert } from 'chai'; + +import { addStage, Konva, loadImage } from '../unit/test-utils'; + +describe('Blur', function () { + // ====================================================== + it('basic blur', function (done) { + var stage = addStage(); + + loadImage('darth-vader.jpg', (imageObj) => { + var layer = new Konva.Layer(); + const darth = new Konva.Image({ + x: 10, + y: 10, + image: imageObj, + draggable: true, + }); + + layer.add(darth); + stage.add(layer); + + darth.cache(); + darth.filters([Konva.Filters.Blur]); + darth.blurRadius(10); + + assert.equal(darth.blurRadius(), 10); + assert.equal(darth._filterUpToDate, false); + + layer.draw(); + + assert.equal(darth._filterUpToDate, true); + + darth.blurRadius(20); + + assert.equal(darth.blurRadius(), 20); + assert.equal(darth._filterUpToDate, false); + + layer.draw(); + + assert.equal(darth._filterUpToDate, true); + + done(); + }); + }); + + it('blur group', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var group = new Konva.Group({ + x: 100, + y: 100, + draggable: true, + }); + var top = new Konva.Circle({ + x: 0, + y: -70, + radius: 30, + fill: 'blue', + stroke: 'black', + strokeWidth: 4, + }); + var right = new Konva.Circle({ + x: 70, + y: 0, + radius: 30, + fill: 'blue', + stroke: 'black', + strokeWidth: 4, + }); + var bottom = new Konva.Circle({ + x: 0, + y: 70, + radius: 30, + fill: 'blue', + stroke: 'black', + strokeWidth: 4, + }); + var left = new Konva.Circle({ + x: -70, + y: 0, + radius: 30, + fill: 'blue', + stroke: 'black', + strokeWidth: 4, + }); + + group.add(top).add(right).add(bottom).add(left); + layer.add(group); + stage.add(layer); + + group.cache(); + + group.offset(); + + group.filters([Konva.Filters.Blur]); + group.blurRadius(20); + + layer.draw(); + }); + + // ====================================================== + it('tween blur', function (done) { + var stage = addStage(); + + loadImage('darth-vader.jpg', (imageObj) => { + var layer = new Konva.Layer(); + const darth = new Konva.Image({ + x: 10, + y: 10, + image: imageObj, + draggable: true, + }); + + layer.add(darth); + stage.add(layer); + + darth.cache(); + darth.filters([Konva.Filters.Blur]); + darth.blurRadius(100); + layer.draw(); + + var tween = new Konva.Tween({ + node: darth, + duration: 2.0, + blurRadius: 0, + easing: Konva.Easings.EaseInOut, + }); + + darth.on('mouseover', function () { + tween.play(); + }); + + darth.on('mouseout', function () { + tween.reverse(); + }); + + done(); + }); + }); + + // ====================================================== + it('crop blur', function (done) { + var stage = addStage(); + + loadImage('darth-vader.jpg', (imageObj) => { + var layer = new Konva.Layer(); + const darth = new Konva.Image({ + x: 10, + y: 10, + image: imageObj, + crop: { x: 128, y: 48, width: 256, height: 128 }, + draggable: true, + }); + + layer.add(darth); + stage.add(layer); + + darth.cache(); + darth.filters([Konva.Filters.Blur]); + darth.blurRadius(10); + layer.draw(); + + done(); + }); + }); + + // ====================================================== + it('crop tween blur', function (done) { + var stage = addStage(); + + loadImage('darth-vader.jpg', (imageObj) => { + var layer = new Konva.Layer(); + const darth = new Konva.Image({ + x: 10, + y: 10, + image: imageObj, + crop: { x: 128, y: 48, width: 256, height: 128 }, + draggable: true, + }); + + layer.add(darth); + stage.add(layer); + + darth.cache(); + darth.filters([Konva.Filters.Blur]); + darth.blurRadius(100); + layer.draw(); + + var tween = new Konva.Tween({ + node: darth, + duration: 2.0, + blurRadius: 0, + easing: Konva.Easings.EaseInOut, + }); + + darth.on('mouseover', function () { + tween.play(); + }); + + darth.on('mouseout', function () { + tween.reverse(); + }); + + done(); + }); + }); + + // ====================================================== + it('transparency', function (done) { + var stage = addStage(); + + loadImage('darth-vader.jpg', (imageObj) => { + var layer = new Konva.Layer(); + const darth = new Konva.Image({ + x: 10, + y: 10, + image: imageObj, + draggable: true, + }); + + layer.add(darth); + stage.add(layer); + + darth.cache(); + darth.filters([Konva.Filters.Blur]); + darth.blurRadius(100); + layer.draw(); + + var tween = new Konva.Tween({ + node: darth, + duration: 1, + blurRadius: 0, + easing: Konva.Easings.EaseInOut, + }); + + darth.on('mouseover', function () { + tween.play(); + }); + + darth.on('mouseout', function () { + tween.reverse(); + }); + + done(); + }); + }); + + // ====================================================== + it('blur hit region', function (done) { + var stage = addStage(); + + loadImage('lion.png', (imageObj) => { + var layer = new Konva.Layer(); + const darth = new Konva.Image({ + x: 10, + y: 10, + image: imageObj, + draggable: true, + }); + + //console.log(darth.hasStroke()) + + layer.add(darth); + stage.add(layer); + + darth.cache(); + darth.filters([Konva.Filters.Blur]); + darth.blurRadius(20); + darth.drawHitFromCache(100); + layer.draw(); + + //console.log(darth._getCanvasCache().hit.getContext().getTrace()); + + //assert.equal(darth._getCanvasCache().hit.getContext().getTrace(true), 'save();translate();beginPath();rect();closePath();save();fillStyle;fill();restore();restore();clearRect();getImageData();putImageData();'); + + done(); + }); + }); +}); diff --git a/test/manual/Brighten-test.ts b/test/manual/Brighten-test.ts new file mode 100644 index 000000000..06112211d --- /dev/null +++ b/test/manual/Brighten-test.ts @@ -0,0 +1,140 @@ +import { assert } from 'chai'; + +import { addStage, Konva, loadImage } from '../unit/test-utils'; + +describe('Brighten', function () { + // ====================================================== + it('basic', function (done) { + var stage = addStage(); + + loadImage('darth-vader.jpg', (imageObj) => { + var layer = new Konva.Layer(); + const darth = new Konva.Image({ + x: 10, + y: 10, + image: imageObj, + draggable: true, + }); + + layer.add(darth); + stage.add(layer); + + darth.cache(); + darth.filters([Konva.Filters.Brighten]); + darth.brightness(0.3); + layer.draw(); + + assert.equal(darth.brightness(), 0.3); + + done(); + }); + }); + + // ====================================================== + it('tween', function (done) { + var stage = addStage(); + + loadImage('darth-vader.jpg', (imageObj) => { + var layer = new Konva.Layer(); + const darth = new Konva.Image({ + x: 10, + y: 10, + image: imageObj, + draggable: true, + }); + + layer.add(darth); + stage.add(layer); + + darth.cache(); + darth.filters([Konva.Filters.Brighten]); + darth.brightness(0.3); + layer.draw(); + + var tween = new Konva.Tween({ + node: darth, + duration: 2.0, + brightness: 0, + easing: Konva.Easings.EaseInOut, + }); + + darth.on('mouseover', function () { + tween.play(); + }); + + darth.on('mouseout', function () { + tween.reverse(); + }); + + done(); + }); + }); + + // ====================================================== + it('crop', function (done) { + var stage = addStage(); + + loadImage('darth-vader.jpg', (imageObj) => { + var layer = new Konva.Layer(); + const darth = new Konva.Image({ + x: 10, + y: 10, + image: imageObj, + crop: { x: 128, y: 48, width: 256, height: 128 }, + draggable: true, + }); + + layer.add(darth); + stage.add(layer); + + darth.cache(); + darth.filters([Konva.Filters.Brighten]); + darth.brightness(-0.3); + layer.draw(); + + assert.equal(darth.brightness(), -0.3); + + done(); + }); + }); + + // ====================================================== + it('tween transparency', function (done) { + var stage = addStage(); + + loadImage('lion.png', (imageObj) => { + var layer = new Konva.Layer(); + const darth = new Konva.Image({ + x: 10, + y: 10, + image: imageObj, + draggable: true, + }); + + layer.add(darth); + stage.add(layer); + + darth.cache(); + darth.filters([Konva.Filters.Brighten]); + darth.brightness(0.3); + layer.draw(); + + var tween = new Konva.Tween({ + node: darth, + duration: 2.0, + brightness: -0.3, + easing: Konva.Easings.EaseInOut, + }); + + darth.on('mouseover', function () { + tween.play(); + }); + + darth.on('mouseout', function () { + tween.reverse(); + }); + + done(); + }); + }); +}); diff --git a/test/manual/Contrast-test.ts b/test/manual/Contrast-test.ts new file mode 100644 index 000000000..0528af45c --- /dev/null +++ b/test/manual/Contrast-test.ts @@ -0,0 +1,100 @@ +import { assert } from 'chai'; + +import { addStage, Konva, loadImage } from '../unit/test-utils'; + +describe('Filter Contrast', function () { + // ====================================================== + it('basic', function (done) { + var stage = addStage(); + + loadImage('darth-vader.jpg', (imageObj) => { + var layer = new Konva.Layer(); + var darth = new Konva.Image({ + x: 10, + y: 10, + image: imageObj, + draggable: true, + }); + + layer.add(darth); + stage.add(layer); + + darth.cache(); + darth.filters([Konva.Filters.Contrast]); + darth.contrast(40); + layer.draw(); + + assert.equal(darth.contrast(), 40); + + done(); + }); + }); + + // ====================================================== + it('tween', function (done) { + var stage = addStage(); + + loadImage('darth-vader.jpg', (imageObj) => { + var layer = new Konva.Layer(); + var darth = new Konva.Image({ + x: 10, + y: 10, + image: imageObj, + draggable: true, + }); + + layer.add(darth); + stage.add(layer); + + darth.cache(); + darth.filters([Konva.Filters.Contrast]); + darth.contrast(40); + layer.draw(); + + var tween = new Konva.Tween({ + node: darth, + duration: 2.0, + contrast: 0, + easing: Konva.Easings.EaseInOut, + }); + + darth.on('mouseover', function () { + tween.play(); + }); + + darth.on('mouseout', function () { + tween.reverse(); + }); + + done(); + }); + }); + + // ====================================================== + it('crop', function (done) { + var stage = addStage(); + + loadImage('darth-vader.jpg', (imageObj) => { + var layer = new Konva.Layer(); + var darth = new Konva.Image({ + x: 10, + y: 10, + image: imageObj, + crop: { x: 128, y: 48, width: 256, height: 128 }, + draggable: true, + }); + + layer.add(darth); + stage.add(layer); + + darth.cache(); + darth.filters([Konva.Filters.Contrast]); + darth.contrast(-40); + layer.draw(); + + assert.equal(darth.contrast(), -40); + + done(); + }); + }); +}); diff --git a/test/manual/Emboss-test.ts b/test/manual/Emboss-test.ts new file mode 100644 index 000000000..841e44fed --- /dev/null +++ b/test/manual/Emboss-test.ts @@ -0,0 +1,91 @@ +import { assert } from 'chai'; + +import { addStage, Konva, loadImage } from '../unit/test-utils'; + +describe('Emboss', function () { + // ====================================================== + it('basic emboss', function (done) { + var stage = addStage(); + + loadImage('darth-vader.jpg', (imageObj) => { + var layer = new Konva.Layer(); + const darth = new Konva.Image({ + x: 10, + y: 10, + image: imageObj, + draggable: true, + }); + + layer.add(darth); + stage.add(layer); + darth.cache(); + darth.filters([Konva.Filters.Emboss]); + darth.embossStrength(0.5); + darth.embossWhiteLevel(0.8); + darth.embossDirection('top-right'); + + layer.draw(); + + var tween = new Konva.Tween({ + node: darth, + duration: 0.6, + embossStrength: 10, + easing: Konva.Easings.EaseInOut, + }); + + darth.on('mouseover', function () { + tween.play(); + }); + + darth.on('mouseout', function () { + tween.reverse(); + }); + + done(); + }); + //imageObj.src = 'assets/lion.png'; + }); + + // ====================================================== + it('blended emboss', function (done) { + var stage = addStage(); + + loadImage('darth-vader.jpg', (imageObj) => { + var layer = new Konva.Layer(); + const darth = new Konva.Image({ + x: 10, + y: 10, + image: imageObj, + draggable: true, + }); + + layer.add(darth); + stage.add(layer); + darth.cache(); + darth.filters([Konva.Filters.Emboss]); + darth.embossStrength(0.5); + darth.embossWhiteLevel(0.2); + darth.embossBlend(true); + + layer.draw(); + + var tween = new Konva.Tween({ + node: darth, + duration: 0.6, + embossStrength: 10, + easing: Konva.Easings.EaseInOut, + }); + + darth.on('mouseover', function () { + tween.play(); + }); + + darth.on('mouseout', function () { + tween.reverse(); + }); + + done(); + }); + //imageObj.src = 'assets/lion.png'; + }); +}); diff --git a/test/manual/Enhance-test.ts b/test/manual/Enhance-test.ts new file mode 100644 index 000000000..0f93f2b18 --- /dev/null +++ b/test/manual/Enhance-test.ts @@ -0,0 +1,77 @@ +import { assert } from 'chai'; + +import { addStage, Konva, loadImage } from '../unit/test-utils'; + +describe('Enhance', function () { + // ====================================================== + it('on image', function (done) { + var stage = addStage(); + + loadImage('scorpion-sprite.png', (imageObj) => { + var layer = new Konva.Layer(); + var filt = new Konva.Image({ + x: 10, + y: 10, + image: imageObj, + draggable: true, + }); + var orig = new Konva.Image({ + x: 200, + y: 10, + image: imageObj, + draggable: true, + }); + + layer.add(filt); + layer.add(orig); + stage.add(layer); + + filt.cache(); + filt.enhance(1.0); + filt.filters([Konva.Filters.Enhance]); + layer.draw(); + + done(); + }); + }); + + // ====================================================== + it('tween enhance', function (done) { + var stage = addStage(); + + loadImage('scorpion-sprite.png', (imageObj) => { + var layer = new Konva.Layer(); + const darth = new Konva.Image({ + x: 10, + y: 10, + image: imageObj, + draggable: true, + }); + + layer.add(darth); + stage.add(layer); + + darth.cache(); + darth.filters([Konva.Filters.Enhance]); + darth.enhance(-1); + layer.draw(); + + var tween = new Konva.Tween({ + node: darth, + duration: 2.0, + enhance: 1.0, + easing: Konva.Easings.EaseInOut, + }); + + darth.on('mouseover', function () { + tween.play(); + }); + + darth.on('mouseout', function () { + tween.reverse(); + }); + + done(); + }); + }); +}); diff --git a/test/manual/Filter-test.ts b/test/manual/Filter-test.ts new file mode 100644 index 000000000..a12af132c --- /dev/null +++ b/test/manual/Filter-test.ts @@ -0,0 +1,27 @@ +import { addStage, Konva, cloneAndCompareLayer } from '../unit/test-utils'; + +describe('Filter', function () { + it('pixelRaio check', function () { + Konva.pixelRatio = 2; + var stage = addStage(); + var layer = new Konva.Layer(); + + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + fill: 'red', + stroke: 'green', + radius: 15, + }); + + layer.add(circle); + stage.add(layer); + circle.cache(); + circle.filters([Konva.Filters.Blur]); + circle.blurRadius(0); + layer.draw(); + + cloneAndCompareLayer(layer, 150); + Konva.pixelRatio = 1; + }); +}); diff --git a/test/manual/Grayscale-test.ts b/test/manual/Grayscale-test.ts new file mode 100644 index 000000000..0b04baa09 --- /dev/null +++ b/test/manual/Grayscale-test.ts @@ -0,0 +1,78 @@ +import { assert } from 'chai'; + +import { addStage, Konva, loadImage } from '../unit/test-utils'; + +describe('Grayscale', function () { + // ====================================================== + it('basic', function (done) { + var stage = addStage(); + + loadImage('darth-vader.jpg', (imageObj) => { + var layer = new Konva.Layer(); + const darth = new Konva.Image({ + x: 10, + y: 10, + image: imageObj, + draggable: true, + }); + + layer.add(darth); + stage.add(layer); + + darth.cache(); + darth.filters([Konva.Filters.Grayscale]); + layer.draw(); + + done(); + }); + }); + + // ====================================================== + it('crop', function (done) { + var stage = addStage(); + + loadImage('darth-vader.jpg', (imageObj) => { + var layer = new Konva.Layer(); + const darth = new Konva.Image({ + x: 10, + y: 10, + image: imageObj, + crop: { x: 128, y: 48, width: 256, height: 128 }, + draggable: true, + }); + + layer.add(darth); + stage.add(layer); + + darth.cache(); + darth.filters([Konva.Filters.Grayscale]); + layer.draw(); + + done(); + }); + }); + + // ====================================================== + it('with transparency', function (done) { + var stage = addStage(); + + loadImage('lion.png', (imageObj) => { + var layer = new Konva.Layer(); + const darth = new Konva.Image({ + x: 10, + y: 10, + image: imageObj, + draggable: true, + }); + + layer.add(darth); + stage.add(layer); + + darth.cache(); + darth.filters([Konva.Filters.Grayscale]); + layer.draw(); + + done(); + }); + }); +}); diff --git a/test/manual/HSL-test.ts b/test/manual/HSL-test.ts new file mode 100644 index 000000000..ea4fc3406 --- /dev/null +++ b/test/manual/HSL-test.ts @@ -0,0 +1,125 @@ +import { assert } from 'chai'; + +import { addStage, Konva, loadImage } from '../unit/test-utils'; + +describe('HSL', function () { + // ====================================================== + it('hue shift tween transparancy', function (done) { + var stage = addStage(); + + loadImage('lion.png', (imageObj) => { + var layer = new Konva.Layer(); + const darth = new Konva.Image({ + x: 10, + y: 10, + image: imageObj, + draggable: true, + }); + + layer.add(darth); + stage.add(layer); + + darth.cache(); + darth.filters([Konva.Filters.HSL]); + darth.hue(360); + layer.draw(); + + var tween = new Konva.Tween({ + node: darth, + duration: 1.0, + hue: 0, + easing: Konva.Easings.EaseInOut, + }); + + darth.on('mouseover', function () { + tween.play(); + }); + + darth.on('mouseout', function () { + tween.reverse(); + }); + + done(); + }); + }); + + // ====================================================== + it('HSL luminance tween transparancy', function (done) { + var stage = addStage(); + + loadImage('darth-vader.jpg', (imageObj) => { + var layer = new Konva.Layer(); + const darth = new Konva.Image({ + x: 10, + y: 10, + image: imageObj, + draggable: true, + }); + + layer.add(darth); + stage.add(layer); + + darth.cache(); + darth.filters([Konva.Filters.HSL]); + darth.luminance(1.0); + layer.draw(); + + var tween = new Konva.Tween({ + node: darth, + duration: 1.0, + luminance: -1.0, + easing: Konva.Easings.EaseInOut, + }); + + darth.on('mouseover', function () { + tween.play(); + }); + + darth.on('mouseout', function () { + tween.reverse(); + }); + + done(); + }); + }); + + // ====================================================== + it('HSL saturation tween transparancy', function (done) { + var stage = addStage(); + + loadImage('lion.png', (imageObj) => { + var layer = new Konva.Layer(); + const darth = new Konva.Image({ + x: 10, + y: 10, + image: imageObj, + draggable: true, + }); + + layer.add(darth); + stage.add(layer); + + darth.cache(); + darth.filters([Konva.Filters.HSL]); + darth.saturation(1.0); + layer.draw(); + + var tween = new Konva.Tween({ + node: darth, + duration: 1.0, + saturation: -1.0, + easing: Konva.Easings.EaseInOut, + }); + + darth.on('mouseover', function () { + tween.play(); + }); + + darth.on('mouseout', function () { + tween.reverse(); + }); + + done(); + }); + }); +}); diff --git a/test/manual/HSV-test.ts b/test/manual/HSV-test.ts new file mode 100644 index 000000000..695266178 --- /dev/null +++ b/test/manual/HSV-test.ts @@ -0,0 +1,166 @@ +import { assert } from 'chai'; + +import { addStage, Konva, loadImage } from '../unit/test-utils'; + +describe('HSV', function () { + // ====================================================== + it('hue shift tween transparancy', function (done) { + var stage = addStage(); + + loadImage('darth-vader.jpg', (imageObj) => { + var layer = new Konva.Layer(); + const darth = new Konva.Image({ + x: 10, + y: 10, + image: imageObj, + draggable: true, + }); + + layer.add(darth); + stage.add(layer); + + darth.cache(); + darth.filters([Konva.Filters.HSV]); + darth.hue(360); + layer.draw(); + + var tween = new Konva.Tween({ + node: darth, + duration: 1.0, + hue: 0, + easing: Konva.Easings.EaseInOut, + }); + + darth.on('mouseover', function () { + tween.play(); + }); + + darth.on('mouseout', function () { + tween.reverse(); + }); + + done(); + }); + }); + + // ====================================================== + it('saturate image', function (done) { + var stage = addStage(); + + loadImage('darth-vader.jpg', (imageObj) => { + var layer = new Konva.Layer(); + const darth = new Konva.Image({ + x: 10, + y: 10, + image: imageObj, + draggable: true, + }); + + layer.add(darth); + stage.add(layer); + + darth.cache(); + darth.filters([Konva.Filters.HSV]); + + darth.saturation(1.0); + layer.draw(); + + var tween = new Konva.Tween({ + node: darth, + duration: 1.0, + saturation: -1.0, + easing: Konva.Easings.EaseInOut, + }); + + darth.on('mouseover', function () { + tween.play(); + }); + + darth.on('mouseout', function () { + tween.reverse(); + }); + + done(); + }); + }); + + // ====================================================== + it('saturation tween transparancy', function (done) { + var stage = addStage(); + + loadImage('darth-vader.jpg', (imageObj) => { + var layer = new Konva.Layer(); + const darth = new Konva.Image({ + x: 10, + y: 10, + image: imageObj, + draggable: true, + }); + + layer.add(darth); + stage.add(layer); + + darth.cache(); + darth.filters([Konva.Filters.HSV]); + darth.saturation(1.0); + layer.draw(); + + var tween = new Konva.Tween({ + node: darth, + duration: 1.0, + saturation: -1, + easing: Konva.Easings.EaseInOut, + }); + + darth.on('mouseover', function () { + tween.play(); + }); + + darth.on('mouseout', function () { + tween.reverse(); + }); + + done(); + }); + }); + + // ====================================================== + it('value tween transparancy', function (done) { + var stage = addStage(); + + loadImage('darth-vader.jpg', (imageObj) => { + var layer = new Konva.Layer(); + const darth = new Konva.Image({ + x: 10, + y: 10, + image: imageObj, + draggable: true, + }); + + layer.add(darth); + stage.add(layer); + + darth.cache(); + darth.filters([Konva.Filters.HSV]); + darth.value(1.0); + layer.draw(); + + var tween = new Konva.Tween({ + node: darth, + duration: 1.0, + value: -1.0, + easing: Konva.Easings.EaseInOut, + }); + + darth.on('mouseover', function () { + tween.play(); + }); + + darth.on('mouseout', function () { + tween.reverse(); + }); + + done(); + }); + }); +}); diff --git a/test/manual/Invert-test.ts b/test/manual/Invert-test.ts new file mode 100644 index 000000000..800cebf9d --- /dev/null +++ b/test/manual/Invert-test.ts @@ -0,0 +1,78 @@ +import { assert } from 'chai'; + +import { addStage, Konva, loadImage } from '../unit/test-utils'; + +describe('Invert', function () { + // ====================================================== + it('basic', function (done) { + var stage = addStage(); + + loadImage('darth-vader.jpg', (imageObj) => { + var layer = new Konva.Layer(); + const darth = new Konva.Image({ + x: 10, + y: 10, + image: imageObj, + draggable: true, + }); + + layer.add(darth); + stage.add(layer); + + darth.cache(); + darth.filters([Konva.Filters.Invert]); + layer.draw(); + + done(); + }); + }); + + // ====================================================== + it('crop', function (done) { + var stage = addStage(); + + loadImage('darth-vader.jpg', (imageObj) => { + var layer = new Konva.Layer(); + const darth = new Konva.Image({ + x: 10, + y: 10, + image: imageObj, + crop: { x: 128, y: 48, width: 256, height: 128 }, + draggable: true, + }); + + layer.add(darth); + stage.add(layer); + + darth.cache(); + darth.filters([Konva.Filters.Invert]); + layer.draw(); + + done(); + }); + }); + + // ====================================================== + it('transparancy', function (done) { + var stage = addStage(); + + loadImage('darth-vader.jpg', (imageObj) => { + var layer = new Konva.Layer(); + const darth = new Konva.Image({ + x: 10, + y: 10, + image: imageObj, + draggable: true, + }); + + layer.add(darth); + stage.add(layer); + + darth.cache(); + darth.filters([Konva.Filters.Invert]); + layer.draw(); + + done(); + }); + }); +}); diff --git a/test/manual/Kaleidoscope-test.ts b/test/manual/Kaleidoscope-test.ts new file mode 100644 index 000000000..ef400ac1f --- /dev/null +++ b/test/manual/Kaleidoscope-test.ts @@ -0,0 +1,127 @@ +import { assert } from 'chai'; + +import { addStage, Konva, loadImage } from '../unit/test-utils'; + +describe('Kaleidoscope', function () { + // ====================================================== + it('basic', function (done) { + var stage = addStage(); + + loadImage('darth-vader.jpg', (imageObj) => { + var layer = new Konva.Layer(); + const darth = new Konva.Image({ + x: 10, + y: 10, + image: imageObj, + draggable: true, + }); + + layer.add(darth); + stage.add(layer); + + darth.cache(); + darth.filters([Konva.Filters.Kaleidoscope]); + darth.kaleidoscopePower(2); + + assert.equal(darth.kaleidoscopePower(), 2); + assert.equal(darth._filterUpToDate, false); + + layer.draw(); + + assert.equal(darth._filterUpToDate, true); + + darth.kaleidoscopePower(3); + + assert.equal(darth.kaleidoscopePower(), 3); + assert.equal(darth._filterUpToDate, false); + + layer.draw(); + + assert.equal(darth._filterUpToDate, true); + + done(); + }); + }); + + // ====================================================== + it('tween angle', function (done) { + var stage = addStage(); + + loadImage('darth-vader.jpg', (imageObj) => { + var layer = new Konva.Layer(); + const darth = new Konva.Image({ + x: 10, + y: 10, + image: imageObj, + draggable: true, + }); + + layer.add(darth); + stage.add(layer); + + darth.cache(); + darth.filters([Konva.Filters.Kaleidoscope]); + darth.kaleidoscopePower(3); + darth.kaleidoscopeAngle(0); + layer.draw(); + + var tween = new Konva.Tween({ + node: darth, + duration: 10.0, + kaleidoscopeAngle: 720, + easing: Konva.Easings.EaseInOut, + }); + + darth.on('mouseover', function () { + tween.play(); + }); + + darth.on('mouseout', function () { + tween.reverse(); + }); + + done(); + }); + }); + + // ====================================================== + it('tween power', function (done) { + var stage = addStage(); + + loadImage('darth-vader.jpg', (imageObj) => { + var layer = new Konva.Layer(); + const darth = new Konva.Image({ + x: 10, + y: 10, + image: imageObj, + draggable: true, + }); + + layer.add(darth); + stage.add(layer); + + darth.cache(); + darth.filters([Konva.Filters.Kaleidoscope]); + darth.kaleidoscopePower(0); + darth.kaleidoscopeAngle(0); + layer.draw(); + + var tween = new Konva.Tween({ + node: darth, + duration: 2.0, + kaleidoscopePower: 8, + easing: Konva.Easings.EaseInOut, + }); + + darth.on('mouseover', function () { + tween.play(); + }); + + darth.on('mouseout', function () { + tween.reverse(); + }); + + done(); + }); + }); +}); diff --git a/test/manual/Manual-test.ts b/test/manual/Manual-test.ts new file mode 100644 index 000000000..1a506a04b --- /dev/null +++ b/test/manual/Manual-test.ts @@ -0,0 +1,416 @@ +import { assert } from 'chai'; + +import { addStage, Konva, loadImage } from '../unit/test-utils'; + +describe('Manual', function () { + // ====================================================== + it('oscillation animation', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var hexagon = new Konva.RegularPolygon({ + x: stage.width() / 2, + y: stage.height() / 2, + sides: 6, + radius: 70, + fill: 'red', + stroke: 'black', + strokeWidth: 4, + }); + + // var hexagon = new Konva.Rect({ + // x: stage.width()/2, + // y: stage.height()/2, + // width: 100, + // height: 50, + // fill: 'red', + // stroke: 'black', + // strokeWidth: 4 + // }); + + layer.add(hexagon); + stage.add(layer); + + var amplitude = 150; + var period = 2000; + // in ms + var centerX = stage.width() / 2; + + var anim = new Konva.Animation(function (frame) { + hexagon.x( + amplitude * Math.sin((new Date().getTime() * 2 * Math.PI) / period) + + centerX + ); + }, layer); + + anim.start(); + }); + + // ====================================================== + it('rotation animation', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var rect; + + for (var n = 0; n < 100; n++) { + rect = new Konva.Rect({ + x: Math.random() * 400, + y: Math.random() * 400, + width: 100, + height: 50, + fill: 'red', + stroke: 'black', + strokeWidth: 4, + }); + + layer.add(rect); + } + + stage.add(layer); + + var velocity = 360; // 1 rev per second + + var anim = new Konva.Animation(function (frame) { + layer + .find('Rect') + .forEach((rect) => rect.rotate((velocity * frame.timeDiff) / 1000)); + }, layer); + + anim.start(); + }); + + // ====================================================== + it('tween node', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var rect = new Konva.Rect({ + x: 100, + y: 100, + width: 100, + height: 50, + fill: 'green', + stroke: 'black', + strokeWidth: 2, + opacity: 0.2, + }); + + layer.add(rect); + stage.add(layer); + + var tween = new Konva.Tween({ + node: rect, + duration: 1, + x: 400, + y: 30, + rotation: 90, + opacity: 1, + strokeWidth: 6, + scaleX: 1.5, + }); + + tween.play(); + }); + + // ====================================================== + it('tween spline', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var spline = new Konva.Line({ + points: [73, 160, 340, 23, 500, 109, 300, 109], + stroke: 'blue', + strokeWidth: 10, + lineCap: 'round', + lineJoin: 'round', + draggable: true, + tension: 1, + }); + + layer.add(spline); + stage.add(layer); + + var tween = new Konva.Tween({ + node: spline, + duration: 1, + //x: 100, + + points: [200, 160, 200, 23, 500, 109, 100, 10], + easing: Konva.Easings.BackEaseOut, + yoyo: false, + }); + + // stage.getContent().addEventListener('mouseover', function() { + // tween.play(); + // }); + + // stage.getContent().addEventListener('mouseout', function() { + // tween.reverse(); + // }); + + tween.play(); + }); + + // ====================================================== + it('blur and tween spline', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var spline = new Konva.Line({ + points: [73, 160, 340, 23, 500, 109, 300, 109], + stroke: 'blue', + strokeWidth: 10, + lineCap: 'round', + lineJoin: 'round', + draggable: true, + tension: 1, + }); + + layer.add(spline); + stage.add(layer); + + spline.cache({ + width: stage.width(), + height: stage.height(), + }); + + spline.filters([Konva.Filters.Blur]).blurRadius(40); + layer.draw(); + + layer.on('beforeDraw', function () { + spline.cache({ + width: stage.width(), + height: stage.height(), + }); + }); + + var tween = new Konva.Tween({ + node: spline, + duration: 2, + //x: 100, + + points: [200, 160, 200, 23, 500, 109, 100, 10], + blurRadius: 0, + easing: Konva.Easings.BackEaseOut, + yoyo: false, + }); + + // stage.getContent().addEventListener('mouseover', function() { + // tween.play(); + // }); + + // stage.getContent().addEventListener('mouseout', function() { + // tween.reverse(); + // }); + + tween.play(); + }); + + it('Make sure that all texts are inside rectangles.', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var text = new Konva.Text({ + fontSize: 50, + y: 5, + x: 25, + fill: 'black', + text: 'text', + }); + var params = text.getSelfRect(); + var rect = new Konva.Rect({ + x: text.x() + params.x, + y: text.y() + params.y, + width: params.width, + height: params.height, + stroke: 'black', + }); + layer.add(rect, text); + + text = new Konva.Text({ + fontSize: 40, + y: 40, + x: 150, + fill: 'black', + text: 'Hello\nWorld! How Are you?', + align: 'center', + }); + params = text.getSelfRect(); + rect = new Konva.Rect({ + x: text.x() + params.x, + y: text.y() + params.y, + width: params.width, + height: params.height, + stroke: 'black', + }); + layer.add(rect, text); + + stage.add(layer); + }); + + it('change hit graph ratio', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 50, + stroke: 'black', + fill: 'red', + strokeWidth: 5, + draggable: true, + }); + + var text = new Konva.Text({ + text: 'click on circle to decrease hit grpah retion', + }); + + layer.add(circle, text); + stage.add(layer); + showHit(layer); + + circle.on('mouseenter', function () { + document.body.style.cursor = 'pointer'; + }); + + circle.on('mouseleave', function () { + document.body.style.cursor = 'default'; + }); + + circle.on('click', function () { + var ratio = layer.getHitCanvas().getPixelRatio() * 0.8; + console.log('new ratio', ratio); + layer.getHitCanvas().setPixelRatio(ratio); + layer.draw(); + }); + }); + + it('tween color', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + + var circle = new Konva.Circle({ + x: 100, + y: stage.height() / 2, + radius: 70, + fill: 'red', + stroke: 'blue', + strokeWidth: 4, + shadowOffx: 10, + shadowOffsetY: 10, + shadowColor: 'black', + }); + + var text = new Konva.Text({ + text: 'click on circle to start tween', + }); + + layer.add(circle, text); + stage.add(layer); + + circle.on('click', function () { + var tween = new Konva.Tween({ + node: circle, + duration: 1, + fill: Konva.Util.getRandomColor(), + stroke: Konva.Util.getRandomColor(), + shadowColor: Konva.Util.getRandomColor(), + }); + tween.play(); + }); + }); + + // ====================================================== + it('create image hit region with pixelRatio, look at hit, test hit with mouseover', function (done) { + var imageObj = new Image(); + + Konva.pixelRatio = 2; + var stage = addStage(); + var layer = new Konva.Layer(); + + imageObj.onload = function () { + var lion = new Konva.Image({ + x: 200, + y: 40, + image: imageObj, + draggable: true, + }); + + layer.add(lion); + + stage.add(layer); + + lion.cache(); + lion.drawHitFromCache(); + layer.draw(); + + lion.on('mouseenter', function () { + document.body.style.cursor = 'pointer'; + }); + + lion.on('mouseleave', function () { + document.body.style.cursor = 'default'; + }); + + Konva.pixelRatio = undefined; + done(); + }; + imageObj.src = 'assets/lion.png'; + + showHit(layer); + }); + + // ====================================================== + it('image hit region with alpha threshold, mouseover circle', function (done) { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var group = new Konva.Group(); + + var circle = new Konva.Circle({ + x: 50, + y: 50, + fill: 'red', + radius: 40, + }); + var rect = new Konva.Rect({ + width: 100, + height: 100, + fill: 'green', + opacity: 0.5, + }); + group.add(rect, circle); + + group.toImage({ + width: 100, + height: 100, + callback: function (img) { + var image = new Konva.Image({ + image: img, + }); + image.cache(); + image.drawHitFromCache(200); + layer.add(image); + layer.draw(); + var shape = layer.getIntersection({ + x: 5, + y: 5, + }); + + assert.equal(!!shape, false, 'shape should not be detected'); + + image.on('mouseenter', function () { + document.body.style.cursor = 'pointer'; + }); + + image.on('mouseleave', function () { + document.body.style.cursor = 'default'; + }); + done(); + }, + }); + + showHit(layer); + }); +}); diff --git a/test/manual/Mask-test.ts b/test/manual/Mask-test.ts new file mode 100644 index 000000000..870c4f31f --- /dev/null +++ b/test/manual/Mask-test.ts @@ -0,0 +1,38 @@ +import { addStage, Konva, loadImage } from '../unit/test-utils'; + +describe('Mask', function () { + // ====================================================== + it('basic', function (done) { + var stage = addStage(); + + loadImage('darth-vader.jpg', (imageObj) => { + var layer = new Konva.Layer({ + throttle: 999, + }); + var bamoon = new Konva.Image({ + x: 0, + y: 0, + image: imageObj, + draggable: true, + }), + filtered = new Konva.Image({ + x: 300, + y: 0, + image: imageObj, + draggable: true, + }); + + layer.add(bamoon); + layer.add(filtered); + stage.add(layer); + + filtered.cache(); + filtered.filters([Konva.Filters.Mask]); + filtered.threshold(10); + + layer.draw(); + + done(); + }); + }); +}); diff --git a/test/manual/Noise-test.ts b/test/manual/Noise-test.ts new file mode 100644 index 000000000..c910a640d --- /dev/null +++ b/test/manual/Noise-test.ts @@ -0,0 +1,45 @@ +import { assert } from 'chai'; + +import { addStage, Konva, loadImage } from '../unit/test-utils'; + +describe('Noise', function () { + // ====================================================== + it('noise tween', function (done) { + var stage = addStage(); + + loadImage('darth-vader.jpg', (imageObj) => { + var layer = new Konva.Layer(); + const darth = new Konva.Image({ + x: 10, + y: 10, + image: imageObj, + draggable: true, + }); + + layer.add(darth); + stage.add(layer); + + darth.cache(); + darth.filters([Konva.Filters.Noise]); + darth.noise(1); + layer.draw(); + + var tween = new Konva.Tween({ + node: darth, + duration: 5.0, + noise: 0, + easing: Konva.Easings.EaseInOut, + }); + + darth.on('mouseover', function () { + tween.play(); + }); + + darth.on('mouseout', function () { + tween.reverse(); + }); + + done(); + }); + }); +}); diff --git a/test/manual/Pixelate-test.ts b/test/manual/Pixelate-test.ts new file mode 100644 index 000000000..a8a9dc416 --- /dev/null +++ b/test/manual/Pixelate-test.ts @@ -0,0 +1,65 @@ +import { addStage, Konva, loadImage } from '../unit/test-utils'; +import { cloneAndCompareLayer } from '../unit/test-utils'; + +describe('Pixelate', function () { + // ====================================================== + it('tween pixelate', function (done) { + var stage = addStage(); + + loadImage('darth-vader.jpg', (imageObj) => { + var layer = new Konva.Layer(); + const lion = new Konva.Image({ + x: 10, + y: 10, + image: imageObj, + draggable: true, + }); + + layer.add(lion); + stage.add(layer); + + lion.cache(); + lion.filters([Konva.Filters.Pixelate]); + lion.pixelSize(16); + layer.draw(); + + var tween = new Konva.Tween({ + node: lion, + duration: 3.0, + pixelSize: 1, + easing: Konva.Easings.EaseInOut, + }); + + lion.on('mouseover', function () { + tween.play(); + }); + + lion.on('mouseout', function () { + tween.reverse(); + }); + + done(); + }); + }); + + it('make sure we have no extra transparent pixels', function (done) { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + Konva.Image.fromURL( + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGUAAABmCAYAAADS6F9hAAAAAXNSR0IArs4c6QAAAXJJREFUeF7t1cEJADAMw8B2/6Fd6BT3UCYQEiZ3205HGbhFoXp8mKJ4TYoCNilKUUQDIFM/pSigARCppRQFNAAitZSigAZApJZSFNAAiNRSigIaAJFaSlFAAyBSSykKaABEailFAQ2ASC2lKKABEKmlFAU0ACK1lKKABkCkllIU0ACI1FKKAhoAkVpKUUADIFJLKQpoAERqKUUBDYBILaUooAEQqaUUBTQAIrWUooAGQKSWUhTQAIjUUooCGgCRWkpRQAMgUkspCmgARGopRQENgEgtpSigARCppRQFNAAitZSigAZApJZSFNAAiNRSigIaAJFaSlFAAyBSSykKaABEailFAQ2ASC2lKKABEKmlFAU0ACK1lKKABkCkllIU0ACI1FKKAhoAkVpKUUADIFJLKQpoAERqKUUBDYBILaUooAEQqaUUBTQAIrWUooAGQKSWUhTQAIjUUooCGgCRWkpRQAMgUkspCmgARGopRQENgEgPgGOW3jCsp3sAAAAASUVORK5CYII=', + function (image) { + layer.add(image); + + image.cache(); + image.filters([Konva.Filters.Pixelate]); + image.pixelSize(4); + layer.draw(); + cloneAndCompareLayer(layer); + + done(); + } + ); + }); +}); diff --git a/test/manual/Posterize-test.ts b/test/manual/Posterize-test.ts new file mode 100644 index 000000000..e83f14d49 --- /dev/null +++ b/test/manual/Posterize-test.ts @@ -0,0 +1,45 @@ +import { assert } from 'chai'; + +import { addStage, Konva, loadImage } from '../unit/test-utils'; + +describe('Posterize', function () { + // ====================================================== + it('on image tween', function (done) { + var stage = addStage(); + + loadImage('darth-vader.jpg', (imageObj) => { + var layer = new Konva.Layer(); + const darth = new Konva.Image({ + x: 10, + y: 10, + image: imageObj, + draggable: true, + }); + + layer.add(darth); + stage.add(layer); + + darth.cache(); + darth.filters([Konva.Filters.Posterize]); + darth.levels(0.2); + layer.draw(); + + var tween = new Konva.Tween({ + node: darth, + duration: 1.0, + levels: 0, + easing: Konva.Easings.Linear, + }); + + darth.on('mouseover', function () { + tween.play(); + }); + + darth.on('mouseout', function () { + tween.reverse(); + }); + + done(); + }); + }); +}); diff --git a/test/manual/RGB-test.ts b/test/manual/RGB-test.ts new file mode 100644 index 000000000..2fef7e740 --- /dev/null +++ b/test/manual/RGB-test.ts @@ -0,0 +1,109 @@ +import { assert } from 'chai'; + +import { addStage, Konva, loadImage } from '../unit/test-utils'; + +describe('RGB', function () { + // ====================================================== + it('colorize basic', function (done) { + var stage = addStage(); + + loadImage('darth-vader.jpg', (imageObj) => { + var layer = new Konva.Layer(); + const darth = new Konva.Image({ + x: 10, + y: 10, + image: imageObj, + draggable: true, + }); + + layer.add(darth); + stage.add(layer); + + darth.cache(); + darth.filters([Konva.Filters.RGB]); + darth.red(255).green(0).blue(128); + layer.draw(); + + // Assert fails even though '[255,0,128] = [255,0,128]' + // assert.deepEqual(darth.getFilterColorizeColor(), [255,0,128]); + + done(); + }); + }); + + // ====================================================== + it('colorize crop', function (done) { + var stage = addStage(); + + loadImage('darth-vader.jpg', (imageObj) => { + var layer = new Konva.Layer(); + const darth = new Konva.Image({ + x: 10, + y: 10, + image: imageObj, + crop: { x: 128, y: 48, width: 256, height: 128 }, + draggable: true, + }); + + layer.add(darth); + stage.add(layer); + + darth.cache(); + darth.filters([Konva.Filters.RGB]); + darth.red(0).green(255).blue(0); + layer.draw(); + + // assert.deepEqual(darth.getFilterColorizeColor(), [0,255,0]); + + done(); + }); + }); + + // ====================================================== + it('colorize transparancy', function (done) { + loadImage('lion.png', (imageObj) => { + var stage = addStage(); + var layer = new Konva.Layer(); + + var colors = [ + [255, 0, 0], + [255, 128, 0], + [255, 255, 0], + [0, 255, 0], + [0, 255, 128], + [0, 255, 255], + [0, 0, 255], + [128, 0, 255], + [255, 0, 255], + [0, 0, 0], + [128, 128, 128], + [255, 255, 255], + ]; + var i, + l = colors.length; + var nAdded = 0; + for (i = 0; i < l; i += 1) { + const color = colors[i]; + const x = -64 + (i / l) * stage.width(); + var darth = new Konva.Image({ + x: x, + y: 32, + image: imageObj, + draggable: true, + }); + layer.add(darth); + + darth.cache(); + darth.filters([Konva.Filters.RGB]); + darth.red(color[0]).green(color[1]).blue(color[2]); + + nAdded += 1; + if (nAdded >= l) { + stage.add(layer); + layer.draw(); + done(); + } + } + }); + }); +}); diff --git a/test/manual/RGBA-test.ts b/test/manual/RGBA-test.ts new file mode 100644 index 000000000..37c671336 --- /dev/null +++ b/test/manual/RGBA-test.ts @@ -0,0 +1,72 @@ +import { assert } from 'chai'; + +import { addStage, Konva, loadImage } from '../unit/test-utils'; + +describe('RGBA', function () { + // ====================================================== + it.skip('colorize basic', function (done) { + var data = [ + { + color: '#2a6511', + filter: [242, 193, 168, 0.33], + result: [108, 131, 67, 255], + }, + { + color: '#e4d526', + filter: [175, 98, 37, 0.79], + result: [186, 122, 37, 255], + }, + ]; + + var stage = new Konva.Stage({ + container: 'konva-container', + width: data.length, + height: 1, + }); + + var layer = new Konva.Layer({ + id: 'layer', + }); + + for (var i = 0; i < data.length; i += 1) { + var d = data[i]; + + var rect = new Konva.Rect({ + x: i, + y: 0, + width: 1, + height: 1, + fill: d.color, + }); + + rect.cache(); + + rect.red(d.filter[0]); + rect.green(d.filter[1]); + rect.blue(d.filter[2]); + rect.alpha(d.filter[3]); + + rect.filters([Konva.Filters.RGBA]); + layer.add(rect); + } + + stage.add(layer); + layer.batchDraw(); + + var context = layer.getCanvas().getContext(); + + var imageDataToArray = function (x) { + var imageData = context.getImageData(x, 0, 1, 1).data; + + return [imageData['0'], imageData['1'], imageData['2'], imageData['3']]; + }; + + var a0 = imageDataToArray(0); + var a1 = imageDataToArray(1); + + assert.deepEqual(a0, data[0].result); + assert.deepEqual(a1, data[1].result); + + done(); + }); +}); diff --git a/test/manual/Sepia-test.ts b/test/manual/Sepia-test.ts new file mode 100644 index 000000000..e22a132a8 --- /dev/null +++ b/test/manual/Sepia-test.ts @@ -0,0 +1,78 @@ +import { assert } from 'chai'; + +import { addStage, Konva, loadImage } from '../unit/test-utils'; + +describe('Filter Sepia', function () { + // ====================================================== + it('basic', function (done) { + var stage = addStage(); + + loadImage('darth-vader.jpg', (imageObj) => { + var layer = new Konva.Layer(); + const darth = new Konva.Image({ + x: 10, + y: 10, + image: imageObj, + draggable: true, + }); + + layer.add(darth); + stage.add(layer); + + darth.cache(); + darth.filters([Konva.Filters.Sepia]); + layer.draw(); + + done(); + }); + }); + + // ====================================================== + it('crop', function (done) { + var stage = addStage(); + + loadImage('darth-vader.jpg', (imageObj) => { + var layer = new Konva.Layer(); + const darth = new Konva.Image({ + x: 10, + y: 10, + image: imageObj, + crop: { x: 128, y: 48, width: 256, height: 128 }, + draggable: true, + }); + + layer.add(darth); + stage.add(layer); + + darth.cache(); + darth.filters([Konva.Filters.Sepia]); + layer.draw(); + + done(); + }); + }); + + // ====================================================== + it('with transparency', function (done) { + var stage = addStage(); + + loadImage('darth-vader.jpg', (imageObj) => { + var layer = new Konva.Layer(); + const darth = new Konva.Image({ + x: 10, + y: 10, + image: imageObj, + draggable: true, + }); + + layer.add(darth); + stage.add(layer); + + darth.cache(); + darth.filters([Konva.Filters.Sepia]); + layer.draw(); + + done(); + }); + }); +}); diff --git a/test/manual/Solarize-test.ts b/test/manual/Solarize-test.ts new file mode 100644 index 000000000..a32faec00 --- /dev/null +++ b/test/manual/Solarize-test.ts @@ -0,0 +1,30 @@ +import { assert } from 'chai'; + +import { addStage, Konva, loadImage } from '../unit/test-utils'; + +describe('Solarize', function () { + // ====================================================== + it('solarize', function (done) { + var stage = addStage(); + + loadImage('darth-vader.jpg', (imageObj) => { + var layer = new Konva.Layer(); + const darth = new Konva.Image({ + x: 10, + y: 10, + image: imageObj, + draggable: true, + }); + + layer.add(darth); + stage.add(layer); + darth.cache(); + darth.filters([Konva.Filters.Solarize]); + + layer.draw(); + + done(); + }); + //imageObj.src = 'assets/lion.png'; + }); +}); diff --git a/test/manual/Threshold-test.ts b/test/manual/Threshold-test.ts new file mode 100644 index 000000000..df1d0701f --- /dev/null +++ b/test/manual/Threshold-test.ts @@ -0,0 +1,45 @@ +import { assert } from 'chai'; + +import { addStage, Konva, loadImage } from '../unit/test-utils'; + +describe('Threshold', function () { + // ====================================================== + it('image tween', function (done) { + var stage = addStage(); + + loadImage('darth-vader.jpg', (imageObj) => { + var layer = new Konva.Layer(); + const darth = new Konva.Image({ + x: 10, + y: 10, + image: imageObj, + draggable: true, + }); + + layer.add(darth); + stage.add(layer); + + darth.cache(); + darth.filters([Konva.Filters.Threshold]); + darth.threshold(1); + layer.draw(); + + var tween = new Konva.Tween({ + node: darth, + duration: 5.0, + threshold: 0, + easing: Konva.Easings.EaseInOut, + }); + + darth.on('mouseover', function () { + tween.play(); + }); + + darth.on('mouseout', function () { + tween.reverse(); + }); + + done(); + }); + }); +}); diff --git a/test/node-global-setup.mjs b/test/node-global-setup.mjs new file mode 100644 index 000000000..b54de7247 --- /dev/null +++ b/test/node-global-setup.mjs @@ -0,0 +1,11 @@ +export function mochaGlobalSetup() { + globalThis.Path2D ??= class Path2D { + constructor(path) { + this.path = path + } + + get [Symbol.toStringTag]() { + return `Path2D`; + } + } +} diff --git a/test/package.json b/test/package.json new file mode 100644 index 000000000..5bbefffba --- /dev/null +++ b/test/package.json @@ -0,0 +1,3 @@ +{ + "type": "commonjs" +} diff --git a/test/performance/bunnies_native.html b/test/performance/bunnies_native.html new file mode 100644 index 000000000..35f63484d --- /dev/null +++ b/test/performance/bunnies_native.html @@ -0,0 +1,155 @@ + + + + + + + + + + + diff --git a/test/performance/creating_elements.html b/test/performance/creating_elements.html new file mode 100644 index 000000000..847171bf4 --- /dev/null +++ b/test/performance/creating_elements.html @@ -0,0 +1,107 @@ + + + + + + +
      + + + + + + diff --git a/test/performance/jump-shape.html b/test/performance/jump-shape.html new file mode 100644 index 000000000..e9b25223d --- /dev/null +++ b/test/performance/jump-shape.html @@ -0,0 +1,166 @@ + + + + + + +
      + + + + + + diff --git a/test/runner.js b/test/runner.js new file mode 100644 index 000000000..c432fe1e6 --- /dev/null +++ b/test/runner.js @@ -0,0 +1,511 @@ +mocha.ui('tdd'); +mocha.setup('bdd'); +var assert = chai.assert, + konvaContainer = document.getElementById('konva-container'), + origAssertEqual = assert.equal, + origAssert = assert, + origNotEqual = assert.notEqual, + origDeepEqual = assert.deepEqual, + assertionCount = 0, + assertions = document.createElement('em'); + +window.requestAnimFrame = (function (callback) { + return ( + window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + window.oRequestAnimationFrame || + window.msRequestAnimationFrame || + function (callback) { + window.setTimeout(callback, 1000 / 30); + } + ); +})(); + +function init() { + // assert extenders so that we can count assertions + assert = function () { + origAssert.apply(this, arguments); + assertions.innerHTML = ++assertionCount; + }; + assert.equal = function () { + origAssertEqual.apply(this, arguments); + assertions.innerHTML = ++assertionCount; + }; + assert.notEqual = function () { + origNotEqual.apply(this, arguments); + assertions.innerHTML = ++assertionCount; + }; + + assert.deepEqual = function () { + origDeepEqual.apply(this, arguments); + assertions.innerHTML = ++assertionCount; + }; + + window.onload = function () { + var mochaStats = document.getElementById('mocha-stats'); + + if (mochaStats) { + var li = document.createElement('li'); + var anchor = document.createElement('a'); + + anchor.href = '#'; + anchor.innerHTML = 'assertions:'; + assertions.innerHTML = 0; + + li.appendChild(anchor); + li.appendChild(assertions); + mochaStats.appendChild(li); + } + }; + + //addStats(); +} + +Konva.enableTrace = true; +Konva.showWarnings = true; +//Konva.pixelRatio = 2; +window.isPhantomJS = /PhantomJS/.test(window.navigator.userAgent); + +function addStats() { + stats = new Stats(); + stats.setMode(0); + stats.domElement.style.position = 'fixed'; + stats.domElement.style.left = '0px'; + stats.domElement.style.top = '0px'; + document.getElementsByTagName('body')[0].appendChild(stats.domElement); + + function animate(lastTime) { + stats.begin(); + + requestAnimFrame(function () { + stats.end(); + animate(lastTime); + }); + } + + animate(); +} + +function addStage(attrs) { + var container = document.createElement('div'); + const props = Konva.Util._assign( + { + container: container, + width: 578, + height: 200, + }, + attrs + ); + + var stage = new Konva.Stage(props); + + konvaContainer.appendChild(container); + return stage; +} + +function createCanvas() { + var canvas = document.createElement('canvas'); + var ratio = Konva.pixelRatio || window.devicePixelRatio; + canvas.width = 578 * ratio; + canvas.height = 200 * ratio; + canvas.getContext('2d').scale(ratio, ratio); + canvas.ratio = ratio; + return canvas; +} + +function get(element, content) { + element = document.createElement(element); + if (element && content) { + element.innerHTML = content; + } + return element; +} +function compareCanvases(canvas1, canvas2, tol) { + // don't test in PhantomJS as it use old chrome engine + // it it has opacity + shadow bug + var equal = imagediff.equal(canvas1, canvas2, tol); + if (!equal) { + var div = get('div'), + b = get('div', '
      Expected:
      '), + c = get('div', '
      Diff:
      '), + diff = imagediff.diff(canvas1, canvas2), + diffCanvas = get('canvas'), + context; + + diffCanvas.height = diff.height; + diffCanvas.width = diff.width; + + div.style.overflow = 'hidden'; + b.style.float = 'left'; + c.style.float = 'left'; + + canvas2.style.position = ''; + canvas2.style.display = ''; + + context = diffCanvas.getContext('2d'); + context.putImageData(diff, 0, 0); + + b.appendChild(canvas2); + c.appendChild(diffCanvas); + + var base64 = diffCanvas.toDataURL(); + console.error('Diff image:'); + console.error(base64); + + div.appendChild(b); + div.appendChild(c); + konvaContainer.appendChild(div); + } + assert.equal( + equal, + true, + 'Result from Konva is different with canvas result' + ); +} + +function compareLayerAndCanvas(layer, canvas, tol) { + compareCanvases(layer.getCanvas()._canvas, canvas, tol); +} + +function compareLayers(layer1, layer2, tol) { + compareLayerAndCanvas(layer1, layer2.getCanvas()._canvas, tol); +} + +function cloneAndCompareLayer(layer, tol) { + var layer2 = layer.clone(); + layer.getStage().add(layer2); + layer2.hide(); + compareLayers(layer, layer2, tol); +} + +function cloneAndCompareLayerWithHit(layer, tol) { + var layer2 = layer.clone(); + layer.getStage().add(layer2); + layer2.hide(); + compareLayers(layer, layer2, tol); + compareCanvases( + layer.getHitCanvas()._canvas, + layer2.getHitCanvas()._canvas, + tol + ); +} + +function compareSceneAndHit(layer) { + compareLayerAndCanvas(layer, layer.getHitCanvas()._canvas, 254); +} + +function addContainer() { + var container = document.createElement('div'); + + konvaContainer.appendChild(container); + + return container; +} + +function showCanvas(canvas) { + canvas.style.position = 'relative'; + + konvaContainer.appendChild(canvas); +} +function showHit(layer) { + var canvas = layer.hitCanvas._canvas; + canvas.style.position = 'relative'; + + konvaContainer.appendChild(canvas); +} + +beforeEach(function () { + var title = document.createElement('h2'), + test = this.currentTest; + + title.innerHTML = test.parent.title + ' - ' + test.title; + title.className = 'konva-title'; + konvaContainer.appendChild(title); + + // resets + Konva.inDblClickWindow = false; + Konva.DD && (Konva.DD.isDragging = false); + Konva.DD && (Konva.DD.node = undefined); + + if ( + !( + this.currentTest.body.indexOf('assert') !== -1 || + this.currentTest.body.toLowerCase().indexOf('compare') !== -1 + ) + ) { + console.error(this.currentTest.title); + } +}); + +Konva.UA.mobile = false; + +afterEach(function () { + var isFailed = this.currentTest.state == 'failed'; + var isManual = this.currentTest.parent.title === 'Manual'; + + Konva.stages.forEach(function (stage) { + clearTimeout(stage.dblTimeout); + }); + + if (!isFailed && !isManual) { + Konva.stages.forEach(function (stage) { + stage.destroy(); + }); + if (Konva.DD._dragElements.size) { + throw 'Why drag elements are not cleaned?'; + } + } +}); + +Konva.Stage.prototype.simulateMouseDown = function (pos) { + var top = this.content.getBoundingClientRect().top; + + this._mousedown({ + clientX: pos.x, + clientY: pos.y + top, + button: pos.button || 0, + }); +}; + +Konva.Stage.prototype.simulateMouseMove = function (pos) { + var top = this.content.getBoundingClientRect().top; + + var evt = { + clientX: pos.x, + clientY: pos.y + top, + button: pos.button || 0, + }; + + this._mousemove(evt); + Konva.DD._drag(evt); +}; + +Konva.Stage.prototype.simulateMouseUp = function (pos) { + var top = this.content.getBoundingClientRect().top; + + var evt = { + clientX: pos.x, + clientY: pos.y + top, + button: pos.button || 0, + }; + + Konva.DD._endDragBefore(evt); + this._mouseup(evt); + Konva.DD._endDragAfter(evt); +}; + +Konva.Stage.prototype.simulateTouchStart = function (pos, changed) { + var top = this.content.getBoundingClientRect().top; + + var touches; + var changedTouches; + if (Array.isArray(pos)) { + touches = pos.map(function (touch) { + return { + identifier: touch.id, + clientX: touch.x, + clientY: touch.y + top, + }; + }); + changedTouches = (changed || pos).map(function (touch) { + return { + identifier: touch.id, + clientX: touch.x, + clientY: touch.y + top, + }; + }); + } else { + changedTouches = touches = [ + { + clientX: pos.x, + clientY: pos.y + top, + id: 0, + }, + ]; + } + var evt = { + touches: touches, + changedTouches: changedTouches, + }; + + this._touchstart(evt); +}; + +Konva.Stage.prototype.simulateTouchMove = function (pos, changed) { + var top = this.content.getBoundingClientRect().top; + + var touches; + var changedTouches; + if (Array.isArray(pos)) { + touches = pos.map(function (touch) { + return { + identifier: touch.id, + clientX: touch.x, + clientY: touch.y + top, + }; + }); + changedTouches = (changed || pos).map(function (touch) { + return { + identifier: touch.id, + clientX: touch.x, + clientY: touch.y + top, + }; + }); + } else { + changedTouches = touches = [ + { + clientX: pos.x, + clientY: pos.y + top, + id: 0, + }, + ]; + } + var evt = { + touches: touches, + changedTouches: changedTouches, + }; + + this._touchmove(evt); + Konva.DD._drag(evt); +}; + +Konva.Stage.prototype.simulateTouchEnd = function (pos, changed) { + var top = this.content.getBoundingClientRect().top; + + var touches; + var changedTouches; + if (Array.isArray(pos)) { + touches = pos.map(function (touch) { + return { + identifier: touch.id, + clientX: touch.x, + clientY: touch.y + top, + }; + }); + changedTouches = (changed || pos).map(function (touch) { + return { + identifier: touch.id, + clientX: touch.x, + clientY: touch.y + top, + }; + }); + } else { + changedTouches = touches = [ + { + clientX: pos.x, + clientY: pos.y + top, + id: 0, + }, + ]; + } + var evt = { + touches: touches, + changedTouches: changedTouches, + }; + + Konva.DD._endDragBefore(evt); + this._touchend(evt); + Konva.DD._endDragAfter(evt); +}; + +Konva.Stage.prototype.simulatePointerDown = function (pos) { + var top = this.content.getBoundingClientRect().top; + + this._mousedown({ + clientX: pos.x, + clientY: pos.y + top, + button: pos.button || 0, + pointerId: pos.pointerId || 1, + }); +}; + +Konva.Stage.prototype.simulatePointerMove = function (pos) { + var top = this.content.getBoundingClientRect().top; + + var evt = { + clientX: pos.x, + clientY: pos.y + top, + button: pos.button || 0, + pointerId: pos.pointerId || 1, + }; + + this._mousemove(evt); + Konva.DD._drag(evt); +}; + +Konva.Stage.prototype.simulatePointerUp = function (pos) { + var top = this.content.getBoundingClientRect().top; + + var evt = { + clientX: pos.x, + clientY: pos.y + top, + button: pos.button || 0, + pointerId: pos.pointerId || 1, + }; + + Konva.DD._endDragBefore(evt); + this._mouseup(evt); + Konva.DD._endDragAfter(evt); +}; + +init(); + +// polyfills +if (!Array.prototype.find) { + Object.defineProperty(Array.prototype, 'find', { + value: function (predicate) { + // 1. Let O be ? ToObject(this value). + if (this == null) { + throw TypeError('"this" is null or not defined'); + } + + var o = Object(this); + + // 2. Let len be ? ToLength(? Get(O, "length")). + var len = o.length >>> 0; + + // 3. If IsCallable(predicate) is false, throw a TypeError exception. + if (typeof predicate !== 'function') { + throw TypeError('predicate must be a function'); + } + + // 4. If thisArg was supplied, let T be thisArg; else let T be undefined. + var thisArg = arguments[1]; + + // 5. Let k be 0. + var k = 0; + + // 6. Repeat, while k < len + while (k < len) { + // a. Let Pk be ! ToString(k). + // b. Let kValue be ? Get(O, Pk). + // c. Let testResult be ToBoolean(? Call(predicate, T, « kValue, k, O »)). + // d. If testResult is true, return kValue. + var kValue = o[k]; + if (predicate.call(thisArg, kValue, k, o)) { + return kValue; + } + // e. Increase k by 1. + k++; + } + + // 7. Return undefined. + return undefined; + }, + configurable: true, + writable: true, + }); +} + +String.prototype.trimRight = + String.prototype.trimRight || + function polyfill() { + return this.replace(/[\s\xa0]+$/, ''); + }; + +String.prototype.trimLeft = + String.prototype.trimLeft || + function polyfill() { + return this.replace(/^\s+/, ''); + }; diff --git a/test/sandbox.html b/test/sandbox.html new file mode 100644 index 000000000..2b17ce672 --- /dev/null +++ b/test/sandbox.html @@ -0,0 +1,71 @@ + + + + + KonvaJS Sandbox + + + + + + + + + + + +
      + + + + diff --git a/test/text-paths.html b/test/text-paths.html new file mode 100644 index 000000000..f329445f9 --- /dev/null +++ b/test/text-paths.html @@ -0,0 +1,419 @@ + + + + + KonvaJS text paths + + + + + + + +
      + + + + diff --git a/test/tsconfig.json b/test/tsconfig.json new file mode 100644 index 000000000..ce747036f --- /dev/null +++ b/test/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "target": "ES2015", + "noEmitOnError": true, + "moduleResolution": "node", + "lib": ["ES2015", "dom"], + "module": "CommonJS" + }, + "include": ["../src/**/*.ts"] +} diff --git a/test/unit-tests.html b/test/unit-tests.html new file mode 100644 index 000000000..1262ea64f --- /dev/null +++ b/test/unit-tests.html @@ -0,0 +1,80 @@ + + +
      + + + + + + + + +
      + + + + + diff --git a/test/unit/Animation-test.ts b/test/unit/Animation-test.ts new file mode 100644 index 000000000..856632e20 --- /dev/null +++ b/test/unit/Animation-test.ts @@ -0,0 +1,130 @@ +import { assert } from 'chai'; + +import { addStage, Konva } from './test-utils'; + +describe('Animation', function () { + // ====================================================== + it('test start and stop', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var rect = new Konva.Rect({ + x: 200, + y: 100, + width: 100, + height: 50, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + }); + + layer.add(rect); + stage.add(layer); + + var amplitude = 150; + var period = 1000; + // in ms + var centerX = stage.width() / 2 - 100 / 2; + + var anim = new Konva.Animation(function (frame) { + rect.x( + amplitude * Math.sin((frame.time * 2 * Math.PI) / period) + centerX + ); + }, layer); + var a = Konva.Animation.animations; + var startLen = a.length; + + assert.equal(a.length, startLen, '1should be no animations running'); + + anim.start(); + assert.equal(a.length, startLen + 1, '2should be 1 animation running'); + + anim.stop(); + assert.equal(a.length, startLen, '3should be no animations running'); + + anim.start(); + assert.equal(a.length, startLen + 1, '4should be 1 animation running'); + + anim.start(); + assert.equal(a.length, startLen + 1, '5should be 1 animation runningg'); + + anim.stop(); + assert.equal(a.length, startLen, '6should be no animations running'); + + anim.stop(); + assert.equal(a.length, startLen, '7should be no animations running'); + }); + + // ====================================================== + it('layer batch draw', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var rect = new Konva.Rect({ + x: 200, + y: 100, + width: 100, + height: 50, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + }); + + layer.add(rect); + stage.add(layer); + + var draws = 0; + + layer.on('draw', function () { + //console.log('draw') + draws++; + }); + + layer.draw(); + layer.draw(); + layer.draw(); + + assert.equal(draws, 3, 'draw count should be 3'); + + layer.batchDraw(); + layer.batchDraw(); + layer.batchDraw(); + + assert.notEqual(draws, 6, 'should not be 6 draws'); + }); + + // ====================================================== + it('stage batch draw', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var rect = new Konva.Rect({ + x: 200, + y: 100, + width: 100, + height: 50, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + }); + + layer.add(rect); + stage.add(layer); + + var draws = 0; + + layer.on('draw', function () { + //console.log('draw') + draws++; + }); + + stage.draw(); + stage.draw(); + stage.draw(); + + assert.equal(draws, 3, 'draw count should be 3'); + + stage.batchDraw(); + stage.batchDraw(); + stage.batchDraw(); + + assert.notEqual(draws, 6, 'should not be 6 draws'); + }); +}); diff --git a/test/unit/Arc-test.ts b/test/unit/Arc-test.ts new file mode 100644 index 000000000..6fe18537f --- /dev/null +++ b/test/unit/Arc-test.ts @@ -0,0 +1,204 @@ +import { assert } from 'chai'; + +import { + addStage, + Konva, + createCanvas, + compareLayerAndCanvas, + assertAlmostDeepEqual, +} from './test-utils'; + +describe('Arc', function () { + // ====================================================== + it('add arc', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var arc = new Konva.Arc({ + x: 100, + y: 100, + innerRadius: 50, + outerRadius: 80, + angle: 90, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myArc', + draggable: true, + }); + + layer.add(arc); + stage.add(layer); + + assert.equal(arc.getClassName(), 'Arc'); + + var trace = layer.getContext().getTrace(); + //console.log(trace); + assert.equal( + trace, + 'clearRect(0,0,578,200);save();transform(1,0,0,1,100,100);beginPath();arc(0,0,80,0,1.571,false);arc(0,0,50,1.571,0,true);closePath();fillStyle=green;fill();lineWidth=4;strokeStyle=black;stroke();restore();' + ); + }); + + // ====================================================== + it('attrs sync', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var arc = new Konva.Arc({ + x: 100, + y: 100, + innerRadius: 50, + outerRadius: 80, + angle: 90, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myArc', + draggable: true, + }); + + layer.add(arc); + stage.add(layer); + assert.equal(arc.getWidth(), 160); + assert.equal(arc.getHeight(), 160); + + arc.setWidth(100); + assert.equal(arc.outerRadius(), 50); + assert.equal(arc.getHeight(), 100); + + arc.setHeight(120); + assert.equal(arc.outerRadius(), 60); + assert.equal(arc.getHeight(), 120); + }); + + it('getSelfRect', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var arc = new Konva.Arc({ + x: 100, + y: 100, + innerRadius: 50, + outerRadius: 80, + angle: 90, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myArc', + draggable: true, + }); + + layer.add(arc); + stage.add(layer); + + assertAlmostDeepEqual(arc.getSelfRect(), { + x: 0, + y: 0, + width: 80, + height: 80, + }); + }); + + it('getSelfRect on clockwise', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var arc = new Konva.Arc({ + x: 100, + y: 100, + innerRadius: 50, + outerRadius: 80, + angle: 90, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myArc', + draggable: true, + clockwise: true, + }); + + layer.add(arc); + stage.add(layer); + + assertAlmostDeepEqual(arc.getSelfRect(), { + x: -80, + y: -80, + width: 160, + height: 160, + }); + }); + + it('getSelfRect on quarter clockwise arc bounds to the visible part', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var arc = new Konva.Arc({ + x: 100, + y: 100, + innerRadius: 50, + outerRadius: 80, + angle: 270, + strokeWidth: 4, + clockwise: true, + }); + + layer.add(arc); + stage.add(layer); + + assertAlmostDeepEqual(arc.getSelfRect(), { + x: 0, + y: -80, + width: 80, + height: 80, + }); + }); + + it('getSelfRect on small angle arc should bounds to inner radius', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var arc = new Konva.Arc({ + x: 100, + y: 100, + innerRadius: 50, + outerRadius: 80, + angle: 60, + strokeWidth: 4, + }); + + layer.add(arc); + stage.add(layer); + + assertAlmostDeepEqual(arc.getSelfRect(), { + x: 25, + y: 0, + width: 55, + height: 69.282032302755, + }); + }); + + it('cache', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var arc = new Konva.Arc({ + x: 100, + y: 100, + innerRadius: 50, + outerRadius: 80, + angle: 90, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + }); + + layer.add(arc); + stage.add(layer); + + var canvas = createCanvas(); + var context = canvas.getContext('2d'); + context.beginPath(); + context.arc(100, 100, 80, 0, Math.PI / 2, false); + context.arc(100, 100, 50, Math.PI / 2, 0, true); + context.closePath(); + context.fillStyle = 'green'; + context.fill(); + context.lineWidth = 4; + context.stroke(); + compareLayerAndCanvas(layer, canvas, 10); + }); +}); diff --git a/test/unit/Arrow-test.ts b/test/unit/Arrow-test.ts new file mode 100644 index 000000000..ace482a61 --- /dev/null +++ b/test/unit/Arrow-test.ts @@ -0,0 +1,261 @@ +import { assert } from 'chai'; + +import { addStage, Konva, cloneAndCompareLayer } from './test-utils'; + +describe('Arrow', function () { + // ====================================================== + it('add arrow', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var arrow = new Konva.Arrow({ + points: [73, 160, 340, 23], + stroke: 'blue', + fill: 'blue', + strokeWidth: 1, + draggable: true, + tension: 0, + }); + + layer.add(arrow); + stage.add(layer); + + arrow.points([1, 2, 3, 4]); + assert.equal(arrow.points()[0], 1); + + arrow.points([5, 6, 7, 8]); + assert.equal(arrow.points()[0], 5); + arrow.points([73, 160, 340, 23, 50, 100, 80, 50]); + arrow.tension(0); + + arrow.pointerLength(15); + assert.equal(arrow.pointerLength(), 15); + + arrow.pointerWidth(15); + assert.equal(arrow.pointerWidth(), 15); + + assert.equal(arrow.getClassName(), 'Arrow'); + + layer.draw(); + }); + + it('do not draw dash for head', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var arrow = new Konva.Arrow({ + points: [50, 50, 100, 100], + stroke: 'red', + fill: 'blue', + strokeWidth: 5, + pointerWidth: 20, + pointerLength: 20, + dash: [5, 5], + }); + + layer.add(arrow); + stage.add(layer); + + var trace = layer.getContext().getTrace(); + + // console.log(trace); + + assert.equal( + trace, + 'clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);beginPath();moveTo(50,50);lineTo(100,100);setLineDash(5,5);lineDashOffset=0;lineWidth=5;strokeStyle=red;stroke();save();beginPath();translate(100,100);rotate(0.785);moveTo(0,0);lineTo(-20,10);lineTo(-20,-10);closePath();restore();setLineDash();fillStyle=blue;fill();lineWidth=5;strokeStyle=red;stroke();restore();' + ); + }); + + it('pointer on both directions', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var arrow = new Konva.Arrow({ + points: [50, 50, 100, 100], + stroke: 'red', + strokeWidth: 5, + pointerWidth: 20, + pointerLength: 20, + pointerAtBeginning: true, + pointerAtEnding: true, + opacity: 0.5, + }); + + layer.add(arrow); + stage.add(layer); + + var trace = layer.getContext().getTrace(); + + // console.log(trace); + + assert.equal( + trace, + 'clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);globalAlpha=0.5;beginPath();moveTo(50,50);lineTo(100,100);lineWidth=5;strokeStyle=red;stroke();save();beginPath();translate(100,100);rotate(0.785);moveTo(0,0);lineTo(-20,10);lineTo(-20,-10);closePath();restore();setLineDash();lineWidth=5;strokeStyle=red;stroke();save();beginPath();translate(50,50);rotate(3.927);moveTo(0,0);lineTo(-20,10);lineTo(-20,-10);closePath();restore();setLineDash();lineWidth=5;strokeStyle=red;stroke();restore();' + ); + }); + + it('dash checks', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + stage.add(layer); + + var width = stage.width(); + var height = stage.height(); + + // regular line + var arrow = new Konva.Arrow({ + x: stage.width() / 4, + y: stage.height() / 4, + points: [0, 0, width / 2, height / 2], + pointerLength: 20, + pointerWidth: 20, + fill: 'red', + stroke: 'red', + strokeWidth: 4, + dash: [10, 5], + }); + layer.add(arrow); + + // arrow with no end (like a simple line) + var arrowNoEnd = new Konva.Arrow({ + x: stage.width() / 4 + 50, + y: stage.height() / 4, + points: [0, 0, width / 2, height / 2], + pointerLength: 20, + pointerWidth: 20, + pointerAtEnding: false, + fill: 'blue', + stroke: 'blue', + strokeWidth: 4, + dash: [10, 5], + }); + layer.add(arrowNoEnd); + + var arrowStartButNoEnd = new Konva.Arrow({ + x: stage.width() / 4 + 100, + y: stage.height() / 4, + points: [0, 0, width / 2, height / 2], + pointerLength: 20, + pointerWidth: 20, + pointerAtEnding: false, + pointerAtBeginning: true, + fill: 'green', + stroke: 'green', + strokeWidth: 4, + dash: [10, 5], + }); + layer.add(arrowStartButNoEnd); + layer.draw(); + + var trace = layer.getContext().getTrace(); + + assert.equal( + trace, + 'clearRect(0,0,578,200);clearRect(0,0,578,200);save();transform(1,0,0,1,144.5,50);beginPath();moveTo(0,0);lineTo(289,100);setLineDash(10,5);lineDashOffset=0;lineWidth=4;strokeStyle=red;stroke();save();beginPath();translate(289,100);rotate(0.333);moveTo(0,0);lineTo(-20,10);lineTo(-20,-10);closePath();restore();setLineDash();fillStyle=red;fill();lineWidth=4;strokeStyle=red;stroke();restore();save();transform(1,0,0,1,194.5,50);beginPath();moveTo(0,0);lineTo(289,100);setLineDash(10,5);lineDashOffset=0;lineWidth=4;strokeStyle=blue;stroke();restore();save();transform(1,0,0,1,244.5,50);beginPath();moveTo(0,0);lineTo(289,100);setLineDash(10,5);lineDashOffset=0;lineWidth=4;strokeStyle=green;stroke();save();beginPath();translate(0,0);rotate(3.475);moveTo(0,0);lineTo(-20,10);lineTo(-20,-10);closePath();restore();setLineDash();fillStyle=green;fill();lineWidth=4;strokeStyle=green;stroke();restore();' + ); + }); + + it('direction with tension', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var arrow = new Konva.Arrow({ + points: [50, 50, 100, 50, 100, 100], + stroke: 'red', + fill: 'red', + tension: 1, + pointerAtBeginning: true, + }); + + layer.add(arrow); + stage.add(layer); + + var trace = layer.getContext().getTrace(false, true); + + assert.equal( + trace, + 'clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);beginPath();moveTo(50,50);quadraticCurveTo(75,25,100,50);quadraticCurveTo(125,75,100,100);lineWidth=2;strokeStyle=red;stroke();save();beginPath();translate(100,100);rotate(2);moveTo(0,0);lineTo(-10,5);lineTo(-10,-5);closePath();restore();setLineDash();fillStyle=red;fill();lineWidth=2;strokeStyle=red;stroke();save();beginPath();translate(50,50);rotate(2);moveTo(0,0);lineTo(-10,5);lineTo(-10,-5);closePath();restore();setLineDash();fillStyle=red;fill();lineWidth=2;strokeStyle=red;stroke();restore();' + ); + }); + + it('direction with tension 2', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var arrow = new Konva.Arrow({ + points: [ + 79.57486136783733, 63.27171903881701, 87.33826247689463, + 80.73937153419593, 124.99075785582254, 82.29205175600738, + 141.68207024029573, 107.52310536044362, 165.74861367837337, + 104.80591497227356, + ], + stroke: 'red', + fill: 'red', + tension: 1, + pointerWidth: 10, + pointerAtBeginning: true, + }); + + layer.add(arrow); + stage.add(layer); + + var trace = layer.getContext().getTrace(false, true); + + assert.equal( + trace, + 'clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);beginPath();moveTo(79,63);quadraticCurveTo(72,74,87,80);bezierCurveTo(117,93,94,67,124,82);bezierCurveTo(149,94,119,95,141,107);quadraticCurveTo(159,117,165,104);lineWidth=2;strokeStyle=red;stroke();save();beginPath();translate(165,104);rotate(5);moveTo(0,0);lineTo(-10,5);lineTo(-10,-5);closePath();restore();setLineDash();fillStyle=red;fill();lineWidth=2;strokeStyle=red;stroke();save();beginPath();translate(79,63);rotate(4);moveTo(0,0);lineTo(-10,5);lineTo(-10,-5);closePath();restore();setLineDash();fillStyle=red;fill();lineWidth=2;strokeStyle=red;stroke();restore();' + ); + }); + + it('test cache', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var arrow = new Konva.Arrow({ + points: [50, 50, 150, 50], + stroke: 'blue', + fill: 'blue', + // large stroke width will not work :( + strokeWidth: 1, + draggable: true, + tension: 0, + }); + layer.add(arrow); + + stage.add(layer); + arrow.cache(); + layer.draw(); + + cloneAndCompareLayer(layer, 255, 50); + }); + + it('getClientRect', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var arrow = new Konva.Arrow({ + points: [50, 50, 150, 50], + stroke: 'blue', + fill: 'blue', + // large stroke width will not work :( + strokeWidth: 1, + draggable: true, + tension: 0, + pointerLength: 10, + pointerWidth: 20, + }); + layer.add(arrow); + + + stage.add(layer); + + var rect = arrow.getClientRect({ skipStroke: true }); + layer.add(new Konva.Rect({...rect, stroke: 'red' })); + + assert.equal(rect.x, 50); + assert.equal(rect.y, 40); + assert.equal(rect.width, 100); + assert.equal(rect.height, 20); + }); +}); diff --git a/test/unit/AutoDraw-test.ts b/test/unit/AutoDraw-test.ts new file mode 100644 index 000000000..42e24a4ca --- /dev/null +++ b/test/unit/AutoDraw-test.ts @@ -0,0 +1,163 @@ +import { assert } from 'chai'; +import { addStage, isNode, Konva } from './test-utils'; + +describe('AutoDraw', function () { + // ====================================================== + it('schedule draw on shape add/change/remove', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + let callCount = 0; + layer.batchDraw = function () { + callCount += 1; + Konva.Layer.prototype.batchDraw.call(this); + return layer; + }; + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + }); + layer.add(circle); + assert.equal(callCount, 1); + circle.radius(50); + assert.equal(callCount, 2); + circle.destroy(); + assert.equal(callCount, 3); + }); + + // ====================================================== + it('schedule draw on order change', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + }); + layer.add(circle); + + var circle2 = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + }); + layer.add(circle2); + + let callCount = 0; + layer.batchDraw = function () { + callCount += 1; + Konva.Layer.prototype.batchDraw.call(this); + return layer; + }; + + circle.moveToTop(); + assert.equal(callCount, 1); + }); + + // ====================================================== + it('schedules draw when calling removeChildren/destroyChildren', () => { + var stage = addStage(); + var layer = new Konva.Layer(); + var group1 = new Konva.Group(); + var group2 = new Konva.Group(); + + stage.add(layer); + layer.add(group1); + group1.add(new Konva.Circle()); + layer.add(group2); + group2.add(new Konva.Circle()); + + let callCount = 0; + layer.batchDraw = function () { + callCount += 1; + Konva.Layer.prototype.batchDraw.call(this); + return layer; + }; + + group1.destroyChildren(); + assert.equal(callCount, 1); + group2.removeChildren(); + assert.equal(callCount, 2); + }); + + // ====================================================== + it('schedule draw on cache', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + }); + layer.add(circle); + + let callCount = 0; + layer.batchDraw = function () { + callCount += 1; + Konva.Layer.prototype.batchDraw.call(this); + return layer; + }; + + circle.cache(); + assert.equal(callCount, 1); + + circle.clearCache(); + assert.equal(callCount, 2); + }); + + // ====================================================== + it('redraw for images', function (done) { + // don't test on node, because of specific url access + if (isNode) { + return done(); + } + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + const { src } = document.getElementById( + 'darth-vader.jpg' + ) as HTMLImageElement; + + const img = new Image(); + img.src = src + '?'; // change url to reset cache + const image = new Konva.Image({ + image: img, + }); + layer.add(image); + + let callCount = 0; + layer.batchDraw = function () { + callCount += 1; + Konva.Layer.prototype.batchDraw.call(this); + return layer; + }; + + img.onload = () => { + assert.equal(callCount, 1); + done(); + }; + }); +}); diff --git a/test/unit/Blob-test.ts b/test/unit/Blob-test.ts new file mode 100644 index 000000000..aa4315afb --- /dev/null +++ b/test/unit/Blob-test.ts @@ -0,0 +1,121 @@ +import { assert } from 'chai'; +import { Line } from '../../src/shapes/Line'; + +import { addStage, Konva, cloneAndCompareLayer } from './test-utils'; + +describe('Blob', function () { + // ====================================================== + it('add blob', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var blob = new Konva.Line({ + points: [73, 140, 340, 23, 500, 109, 300, 170], + stroke: 'blue', + strokeWidth: 10, + draggable: true, + fill: '#aaf', + tension: 0.8, + closed: true, + }); + + layer.add(blob); + stage.add(layer); + + assert.equal(blob.tension(), 0.8); + + assert.equal(blob.getClassName(), 'Line'); + + //console.log(blob1.getPoints()) + + // test setter + blob.tension(1.5); + assert.equal(blob.tension(), 1.5); + + var trace = layer.getContext().getTrace(); + assert.equal( + trace, + 'clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);beginPath();moveTo(73,140);bezierCurveTo(90.922,74.135,129.542,38.279,340,23);bezierCurveTo(471.142,13.479,514.876,54.33,500,109);bezierCurveTo(482.876,171.93,463.05,158.163,300,170);bezierCurveTo(121.45,182.963,58.922,191.735,73,140);closePath();fillStyle=#aaf;fill();lineWidth=10;strokeStyle=blue;stroke();restore();' + ); + }); + + // ====================================================== + it('define tension first', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var blob = new Konva.Line({ + tension: 0.8, + points: [73, 140, 340, 23, 500, 109, 300, 170], + stroke: 'blue', + strokeWidth: 10, + draggable: true, + fill: '#aaf', + closed: true, + }); + + layer.add(blob); + stage.add(layer); + + assert.equal(stage.findOne('Line').points().length, 8); + }); + + // ====================================================== + it('check for konva event handlers', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var blob = new Konva.Line({ + points: [73, 140, 340, 23, 500, 109, 300, 170], + stroke: 'blue', + strokeWidth: 10, + draggable: true, + fill: '#aaf', + tension: 0.8, + closed: true, + }); + + layer.add(blob); + + stage.add(layer); + + assert.equal(blob.eventListeners.pointsChange[0].name, 'konva'); + assert.equal(blob.eventListeners.tensionChange[0].name, 'konva'); + + // removing handlers should not remove konva specific handlers + blob.off('pointsChange'); + blob.off('tensionChange'); + + assert.equal(blob.eventListeners.pointsChange[0].name, 'konva'); + assert.equal(blob.eventListeners.tensionChange[0].name, 'konva'); + + // you can force remove an event by adding the name + blob.off('pointsChange.konva'); + blob.off('tensionChange.konva'); + + assert.equal(blob.eventListeners.pointsChange, undefined); + assert.equal(blob.eventListeners.tensionChange, undefined); + }); + + it('cache', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var blob = new Konva.Line({ + x: 50, + y: 50, + points: [-25, 50, 250, -30, 150, 50, 250, 110], + stroke: 'black', + strokeWidth: 10, + draggable: true, + fill: '#aaf', + tension: 0.3, + closed: true, + }); + + blob.cache(); + layer.add(blob); + stage.add(layer); + + cloneAndCompareLayer(layer, 150); + }); +}); diff --git a/test/unit/Canvas-test.ts b/test/unit/Canvas-test.ts new file mode 100644 index 000000000..05fa37f92 --- /dev/null +++ b/test/unit/Canvas-test.ts @@ -0,0 +1,42 @@ +import { assert } from 'chai'; +import { addStage, Konva } from './test-utils'; + +describe('Canvas', function () { + // ====================================================== + it('pixel ratio', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + + var circle = new Konva.Circle({ + x: 100, + y: 70, + radius: 70, + fill: 'green', + stroke: 'blue', + strokeWidth: 4, + draggable: true, + }); + + layer.add(circle); + stage.add(layer); + + stage.width(578 / 2); + stage.height(100); + + stage.draw(); + assert.equal(layer.getCanvas().getPixelRatio(), Konva.pixelRatio); + + layer.getCanvas().setPixelRatio(1); + assert.equal(layer.getCanvas().getPixelRatio(), 1); + assert.equal(layer.getCanvas().width, 289); + assert.equal(layer.getCanvas().height, 100); + + layer.getCanvas().setPixelRatio(2); + assert.equal(layer.getCanvas().getPixelRatio(), 2); + assert.equal(layer.getCanvas().width, 578); + assert.equal(layer.getCanvas().height, 200); + + layer.draw(); + }); +}); diff --git a/test/unit/Circle-test.ts b/test/unit/Circle-test.ts new file mode 100644 index 000000000..11c1c2660 --- /dev/null +++ b/test/unit/Circle-test.ts @@ -0,0 +1,320 @@ +import { assert } from 'chai'; + +import { + addStage, + Konva, + createCanvas, + compareLayerAndCanvas, + loadImage, +} from './test-utils'; + +describe('Circle', function () { + // ====================================================== + + it('add circle to stage', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var group = new Konva.Group(); + var circle = new Konva.Circle({ + x: 100, + y: 100, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + draggable: true, + }); + + stage.add(layer); + layer.add(group); + group.add(circle); + layer.draw(); + + var attrs = circle.getAttrs(); + + assert.equal(attrs.x, 100); + assert.equal(attrs.y, 100); + assert.equal(attrs.radius, 70); + assert.equal(attrs.fill, 'green'); + assert.equal(attrs.stroke, 'black'); + assert.equal(attrs.strokeWidth, 4); + assert.equal(attrs.name, 'myCircle'); + assert.equal(attrs.draggable, true); + assert.equal(circle.getClassName(), 'Circle'); + + var trace = layer.getContext().getTrace(); + // console.log(trace); + // console.log( + // 'clearRect(0,0,578,200);clearRect(0,0,578,200);save();transform(1,0,0,1,100,100);beginPath();arc(0,0,70,0,6.283,false);closePath();fillStyle=green;fill();lineWidth=4;strokeStyle=black;stroke();restore();' + // ); + assert.equal( + trace, + 'clearRect(0,0,578,200);clearRect(0,0,578,200);save();transform(1,0,0,1,100,100);beginPath();arc(0,0,70,0,6.283,false);closePath();fillStyle=green;fill();lineWidth=4;strokeStyle=black;stroke();restore();' + ); + }); + + it('clone', function () { + var circle = new Konva.Circle(); + var clone = circle.clone(); + assert.equal(clone instanceof Konva.Circle, true); + assert.equal(clone.className, 'Circle'); + }); + + // ====================================================== + it('add circle with pattern fill', function (done) { + loadImage('darth-vader.jpg', (imageObj) => { + var stage = addStage(); + var layer = new Konva.Layer(); + var group = new Konva.Group(); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fillPatternImage: imageObj, + fillPatternOffset: { x: -5, y: -5 }, + fillPatternScale: { x: 0.7, y: 0.7 }, + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + draggable: true, + }); + + group.add(circle); + layer.add(group); + stage.add(layer); + + assert.equal(circle.fillPatternOffset().x, -5); + assert.equal(circle.fillPatternOffset().y, -5); + + circle.fillPatternOffset({ x: 1, y: 2 }); + assert.equal(circle.fillPatternOffset().x, 1); + assert.equal(circle.fillPatternOffset().y, 2); + + circle.fillPatternOffset({ + x: 3, + y: 4, + }); + assert.equal(circle.fillPatternOffset().x, 3); + assert.equal(circle.fillPatternOffset().y, 4); + + done(); + }); + }); + + // ====================================================== + it('add circle with radial gradient fill', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var group = new Konva.Group(); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fillRadialGradientStartPoint: { x: -20, y: -20 }, + fillRadialGradientStartRadius: 0, + fillRadialGradientEndPoint: { x: -60, y: -60 }, + fillRadialGradientEndRadius: 130, + fillRadialGradientColorStops: [0, 'red', 0.2, 'yellow', 1, 'blue'], + name: 'myCircle', + draggable: true, + scale: { + x: 0.5, + y: 0.5, + }, + }); + + group.add(circle); + layer.add(group); + stage.add(layer); + + assert.equal(circle.fillRadialGradientStartPoint().x, -20); + assert.equal(circle.fillRadialGradientStartPoint().y, -20); + assert.equal(circle.fillRadialGradientStartRadius(), 0); + assert.equal(circle.fillRadialGradientEndPoint().x, -60); + assert.equal(circle.fillRadialGradientEndPoint().y, -60); + assert.equal(circle.fillRadialGradientEndRadius(), 130); + assert.equal(circle.fillRadialGradientColorStops().length, 6); + }); + + // ====================================================== + it('add shape with linear gradient fill', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var group = new Konva.Group(); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fillLinearGradientStartPoint: { x: -35, y: -35 }, + fillLinearGradientEndPoint: { x: 35, y: 35 }, + fillLinearGradientColorStops: [0, 'red', 1, 'blue'], + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + draggable: true, + }); + + group.add(circle); + layer.add(group); + stage.add(layer); + + var canvas = createCanvas(); + var ctx = canvas.getContext('2d'); + + var start = { x: -35, y: -35 }; + var end = { x: 35, y: 35 }; + var colorStops = [0, 'red', 1, 'blue']; + var grd = ctx.createLinearGradient(start.x, start.y, end.x, end.y); + + // build color stops + for (var n = 0; n < colorStops.length; n += 2) { + grd.addColorStop(colorStops[n] as number, colorStops[n + 1] as string); + } + ctx.beginPath(); + ctx.translate(circle.x(), circle.y()); + ctx.arc(0, 0, 70, 0, Math.PI * 2, false); + ctx.closePath(); + + ctx.fillStyle = grd; + ctx.lineWidth = 4; + + ctx.fill(); + ctx.stroke(); + + compareLayerAndCanvas(layer, canvas, 200); + }); + + // ====================================================== + it('set opacity after instantiation', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var group = new Konva.Group(); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'red', + }); + + group.add(circle); + layer.add(group); + stage.add(layer); + + circle.opacity(0.5); + layer.draw(); + + circle.opacity(1); + layer.draw(); + + var trace = layer.getContext().getTrace(); + assert.equal( + trace, + 'clearRect(0,0,578,200);save();transform(1,0,0,1,289,100);beginPath();arc(0,0,70,0,6.283,false);closePath();fillStyle=red;fill();restore();clearRect(0,0,578,200);save();transform(1,0,0,1,289,100);globalAlpha=0.5;beginPath();arc(0,0,70,0,6.283,false);closePath();fillStyle=red;fill();restore();clearRect(0,0,578,200);save();transform(1,0,0,1,289,100);beginPath();arc(0,0,70,0,6.283,false);closePath();fillStyle=red;fill();restore();' + ); + }); + + // ====================================================== + it('attrs sync', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + }); + + layer.add(circle); + stage.add(layer); + + assert.equal(circle.getWidth(), 140); + assert.equal(circle.getHeight(), 140); + + circle.setWidth(100); + assert.equal(circle.radius(), 50); + assert.equal(circle.getHeight(), 100); + + circle.setHeight(120); + assert.equal(circle.radius(), 60); + assert.equal(circle.getHeight(), 120); + }); + + // ====================================================== + it('set fill after instantiation', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + }); + layer.add(circle); + + circle.fill('blue'); + + stage.add(layer); + + var trace = layer.getContext().getTrace(); + assert.equal( + trace, + 'clearRect(0,0,578,200);save();transform(1,0,0,1,289,100);beginPath();arc(0,0,70,0,6.283,false);closePath();fillStyle=blue;fill();lineWidth=4;strokeStyle=black;stroke();restore();' + ); + }); + + it('getSelfRect', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: 100, + y: 100, + radius: 50, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + draggable: true, + }); + + layer.add(circle); + stage.add(layer); + + assert.deepEqual(circle.getSelfRect(), { + x: -50, + y: -50, + width: 100, + height: 100, + }); + }); + + it('cache', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: 100, + y: 100, + radius: 50, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + }); + + layer.add(circle); + stage.add(layer); + + var canvas = createCanvas(); + var context = canvas.getContext('2d'); + context.beginPath(); + context.arc(100, 100, 50, 0, Math.PI * 2, false); + context.closePath(); + context.fillStyle = 'green'; + context.fill(); + context.lineWidth = 4; + context.stroke(); + compareLayerAndCanvas(layer, canvas, 100); + }); +}); diff --git a/test/unit/Container-test.ts b/test/unit/Container-test.ts new file mode 100644 index 000000000..045dfdd7a --- /dev/null +++ b/test/unit/Container-test.ts @@ -0,0 +1,2731 @@ +import { assert } from 'chai'; +import { Shape } from '../../src/Shape.js'; +import { addStage, Konva, compareLayers, isNode } from './test-utils'; + +describe('Container', function () { + // ====================================================== + it('clip', function () { + var stage = addStage(); + var layer = new Konva.Layer({ + clip: { x: 0, y: 0, width: stage.width() / 2, height: 100 }, + }); + var group = new Konva.Group(); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + draggable: true, + }); + + stage.add(layer); + layer.add(group); + group.add(circle); + layer.draw(); + + var trace = layer.getContext().getTrace(); + assert.equal( + trace, + 'clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);beginPath();rect(0,0,289,100);clip();transform(1,0,0,1,0,0);restore();clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);beginPath();rect(0,0,289,100);clip();transform(1,0,0,1,0,0);save();transform(1,0,0,1,289,100);beginPath();arc(0,0,70,0,6.283,false);closePath();fillStyle=green;fill();lineWidth=4;strokeStyle=black;stroke();restore();restore();' + ); + }); + + // ====================================================== + it('clip function', function () { + var stage = addStage(); + + // cliped by circle is the same as draw circle + var layer = new Konva.Layer(); + stage.add(layer); + var circle = new Konva.Circle({ + fill: 'black', + x: 50, + y: 50, + radius: 40, + }); + layer.add(circle); + + layer.draw(); + + var clipedLayer = new Konva.Layer({ + clipFunc: function (ctx) { + ctx.arc(50, 50, 40, 0, Math.PI * 2, false); + }, + }); + stage.add(clipedLayer); + var rect = new Konva.Rect({ + x: 10, + y: 10, + fill: 'black', + width: 200, + height: 200, + }); + clipedLayer.add(rect); + stage.draw(); + + compareLayers(layer, clipedLayer, 150); + }); + + // ====================================================== + it('clip function', function () { + if (isNode) { + // how to use Path2D in nodejs env? + return; + } + var stage = addStage(); + + // cliped by circle is the same as draw circle + var layer = new Konva.Layer(); + stage.add(layer); + var circle = new Konva.Circle({ + fill: 'black', + x: 50, + y: 50, + radius: 40, + }); + layer.add(circle); + + layer.draw(); + + var clipedLayer = new Konva.Layer({ + clipFunc: function (ctx) { + const path2D = new Path2D(); + path2D.arc(50, 50, 40, 0, Math.PI * 2, false); + ctx.fill(path2D); + }, + }); + stage.add(clipedLayer); + var rect = new Konva.Rect({ + x: 10, + y: 10, + fill: 'black', + width: 200, + height: 200, + }); + clipedLayer.add(rect); + stage.draw(); + + compareLayers(layer, clipedLayer, 150); + }); + + // ====================================================== + it('adder validation', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var group = new Konva.Group(); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + }); + + stage.add(layer); + layer.add(group); + group.add(circle); + layer.draw(); + + // disassemble the tree + circle.remove(); + group.remove(); + layer.remove(); + + // =================================== + var errorThrown = false; + try { + stage.add(stage as any); + } catch (err) { + errorThrown = true; + } + assert.equal( + errorThrown, + true, + 'error should have been thrown when adding stage to stage' + ); + stage.remove(); + + // =================================== + var errorThrown = false; + try { + stage.add(group as any); + } catch (err) { + errorThrown = true; + } + assert.equal( + errorThrown, + true, + 'error should have been thrown when adding group to stage' + ); + group.remove(); + + // =================================== + var errorThrown = false; + try { + stage.add(circle as any); + } catch (err) { + errorThrown = true; + } + assert.equal( + errorThrown, + true, + 'error should have been thrown when adding shape to stage' + ); + circle.remove(); + + // =================================== + var errorThrown = false; + try { + layer.add(stage as any); + } catch (err) { + errorThrown = true; + } + assert.equal( + errorThrown, + true, + 'error should have been thrown when adding stage to layer' + ); + stage.remove(); + + // =================================== + var errorThrown = false; + try { + layer.add(layer); + } catch (err) { + errorThrown = true; + } + assert.equal( + errorThrown, + true, + 'error should have been thrown when adding layer to layer' + ); + layer.remove(); + + // =================================== + var errorThrown = false; + try { + group.add(stage as any); + } catch (err) { + errorThrown = true; + } + assert.equal( + errorThrown, + true, + 'error should have been thrown when adding stage to group' + ); + stage.remove(); + + // =================================== + var errorThrown = false; + try { + group.add(layer); + } catch (err) { + errorThrown = true; + } + assert.equal( + errorThrown, + true, + 'error should have been thrown when adding layer to group' + ); + layer.remove(); + }); + + // ====================================================== + it('add layer then group then shape', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var group = new Konva.Group(); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + }); + + // these should all pass because they are valid + stage.add(layer); + layer.add(group); + group.add(circle); + layer.draw(); + + var trace = layer.getContext().getTrace(); + assert.equal( + trace, + 'clearRect(0,0,578,200);clearRect(0,0,578,200);save();transform(1,0,0,1,289,100);beginPath();arc(0,0,70,0,6.283,false);closePath();fillStyle=green;fill();lineWidth=4;strokeStyle=black;stroke();restore();' + ); + }); + + // ====================================================== + it('add shape then stage then layer', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var group = new Konva.Group(); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + }); + + group.add(circle); + stage.add(layer); + layer.add(group); + + layer.draw(); + + var trace = layer.getContext().getTrace(); + assert.equal( + trace, + 'clearRect(0,0,578,200);clearRect(0,0,578,200);save();transform(1,0,0,1,289,100);beginPath();arc(0,0,70,0,6.283,false);closePath();fillStyle=green;fill();lineWidth=4;strokeStyle=black;stroke();restore();' + ); + }); + + // ====================================================== + it('select shape by id and name', function () { + var stage = addStage(); + var layer = new Konva.Layer({ + id: 'myLayer', + }); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + id: 'myCircle', + }); + + var rect = new Konva.Rect({ + x: 300, + y: 100, + width: 100, + height: 50, + fill: 'purple', + stroke: 'black', + strokeWidth: 4, + name: 'myRect', + }); + + layer.add(circle); + layer.add(rect); + stage.add(layer); + + var node; + node = stage.find('#myCircle')[0]; + assert.equal(node.className, 'Circle', 'className should be Circle'); + node = layer.find('.myRect')[0]; + assert.equal(node.className, 'Rect', 'className should be rect'); + node = layer.find('#myLayer')[0]; + assert.equal(node, undefined, 'node should be undefined'); + node = stage.find('#myLayer')[0]; + assert.equal(node.nodeType, 'Layer', 'node type should be Layer'); + }); + + // ====================================================== + it('select shape by name with "-" char', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var rect = new Konva.Rect({ + x: 300, + y: 100, + width: 100, + height: 50, + fill: 'purple', + stroke: 'black', + strokeWidth: 4, + name: 'bounding-box', + }); + + layer.add(rect); + stage.add(layer); + + var node = stage.find('.bounding-box')[0]; + assert.equal(node, rect); + }); + + // ====================================================== + it('select should return elements in their order', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + // create circle before any nodes + var circle = new Konva.Circle({ + radius: 50, + fill: 'red', + name: 'node', + }); + + var group = new Konva.Group({ + name: 'node', + }); + layer.add(group); + + var rect = new Konva.Rect({ + x: 300, + y: 100, + width: 100, + height: 50, + fill: 'purple', + stroke: 'black', + strokeWidth: 4, + name: 'node', + }); + group.add(rect); + group.add(circle); + + // move circle + circle.moveToBottom(); + + assert.equal(layer.findOne('.node'), group, 'group here'); + + var nodes = layer.find('.node'); + assert.equal(nodes[0], group, 'group first'); + assert.equal(nodes[1], circle, 'then circle'); + assert.equal(nodes[2], rect, 'then rect'); + + assert.equal(layer.findOne('Shape'), circle, 'circle is first'); + }); + + // ====================================================== + it('select shape with findOne', function () { + var stage = addStage(); + var layer = new Konva.Layer({ + id: 'myLayer', + }); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + id: 'myCircle', + }); + + var rect = new Konva.Rect({ + x: 300, + y: 100, + width: 100, + height: 50, + fill: 'purple', + stroke: 'black', + strokeWidth: 4, + name: 'myRect', + }); + + layer.add(circle); + layer.add(rect); + stage.add(layer); + + var node; + node = stage.findOne('#myCircle'); + assert.equal(node, circle); + node = layer.findOne('.myRect'); + assert.equal(node, rect); + node = layer.findOne('#myLayer'); + assert.equal(node, undefined, 'node should be undefined'); + node = stage.findOne('#myLayer'); + assert.equal(node, layer, 'node type should be Layer'); + node = stage.findOne(function (node) { + return node.getType() === 'Shape'; + }); + assert.equal(node, circle, 'findOne should work with functions'); + }); + + // ====================================================== + it('select shapes with multiple selectors', function () { + var stage = addStage(); + var layer = new Konva.Layer({ + id: 'myLayer', + }); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + id: 'myCircle', + }); + + var rect = new Konva.Rect({ + x: 300, + y: 100, + width: 100, + height: 50, + fill: 'purple', + stroke: 'black', + strokeWidth: 4, + name: 'myRect', + }); + + layer.add(circle); + layer.add(rect); + stage.add(layer); + + assert.equal( + layer.find('#myCircle, .myRect').length, + 2, + 'should be 2 items in the array' + ); + assert.equal( + layer.find('#myCircle, .myRect')[0]._id, + circle._id, + 'circle id is wrong' + ); + assert.equal( + layer.find('#myCircle, .myRect')[1]._id, + rect._id, + 'rect id is wrong' + ); + + assert.equal( + layer.find('#myCircle, Circle, .myRect, Rect').length, + 2, + 'should be 2 items in the array' + ); + assert.equal( + layer.find('#myCircle, Circle, .myRect, Rect')[0]._id, + circle._id, + 'circle id is wrong' + ); + assert.equal( + layer.find('#myCircle, Circle, .myRect, Rect')[1]._id, + rect._id, + 'rect id is wrong' + ); + }); + + // ====================================================== + it('select shape by function', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var rect = new Konva.Rect({ + x: 300, + y: 100, + width: 100, + height: 50, + fill: 'purple', + stroke: 'black', + strokeWidth: 4, + name: 'myRect', + }); + + layer.add(rect); + stage.add(layer); + + var fn = function (node) { + return node.nodeType === 'Shape'; + }; + + var noOp = function (node) { + return false; + }; + + assert.equal(stage.find(fn)[0], rect); + assert.equal(stage.find(noOp).length, 0); + }); + + // ====================================================== + it('select shape with duplicate id', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect1 = new Konva.Rect({ + x: 300, + y: 100, + width: 100, + height: 50, + fill: 'purple', + stroke: 'black', + strokeWidth: 4, + id: 'myRect', + }); + layer.add(rect1); + + var rect2 = new Konva.Rect({ + x: 300, + y: 100, + width: 100, + height: 50, + fill: 'purple', + stroke: 'black', + strokeWidth: 4, + id: 'myRect', + }); + layer.add(rect2); + stage.draw(); + + assert.equal(stage.find('#myRect').length, 2); + assert.equal(stage.find('#myRect')[0], rect1); + assert.equal(stage.find('#myRect')[1], rect2); + }); + + // ====================================================== + it('set x on an array of nodes', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myShape', + }); + + var rect = new Konva.Rect({ + x: 300, + y: 100, + width: 100, + height: 50, + fill: 'purple', + stroke: 'black', + strokeWidth: 4, + name: 'myShape', + }); + + layer.add(circle); + layer.add(rect); + stage.add(layer); + + var shapes = layer.find('.myShape'); + + assert.equal(shapes.length, 2, 'shapes array should have 2 elements'); + + shapes.forEach(function (node) { + node.x(200); + }); + + layer.draw(); + + shapes.forEach(function (node) { + assert.equal(node.x(), 200, 'shape x should be 200'); + }); + }); + + // ====================================================== + it('set fill on array by Shape-selector', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myShape', + }); + + var rect = new Konva.Rect({ + x: 300, + y: 100, + width: 100, + height: 50, + fill: 'purple', + stroke: 'black', + strokeWidth: 4, + name: 'myShape', + }); + + layer.add(circle); + layer.add(rect); + stage.add(layer); + + var shapes = layer.find('Shape'); + + assert.equal(shapes.length, 2, 'shapes array should have 2 elements'); + + shapes.forEach(function (node) { + node.fill('gray'); + }); + + layer.draw(); + + shapes.forEach(function (node) { + assert.equal(node.fill(), 'gray', 'shape x should be 200'); + }); + }); + + // ====================================================== + it('add listener to an array of nodes', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myShape', + }); + + var rect = new Konva.Rect({ + x: 300, + y: 100, + width: 100, + height: 50, + fill: 'purple', + stroke: 'black', + strokeWidth: 4, + name: 'myShape', + }); + + layer.add(circle); + layer.add(rect); + stage.add(layer); + + var shapes = layer.find('.myShape'); + + assert.equal(shapes.length, 2, 'shapes array should have 2 elements'); + var a = 0; + shapes.forEach((shape) => + shape.on('mouseover', function () { + a++; + }) + ); + circle.fire('mouseover'); + assert.equal(a, 1, 'listener should have fired for circle'); + rect.fire('mouseover'); + assert.equal(a, 2, 'listener should have fired for rect'); + }); + + // ====================================================== + it('remove all children from layer', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var group = new Konva.Group(); + var circle1 = new Konva.Circle({ + x: 100, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + }); + + var circle2 = new Konva.Circle({ + x: 300, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + }); + + group.add(circle1); + group.add(circle2); + layer.add(group); + stage.add(layer); + + assert.equal(layer.children.length, 1, 'layer should have 1 children'); + assert.equal(group.children.length, 2, 'group should have 2 children'); + + layer.removeChildren(); + layer.draw(); + + assert.equal(layer.children.length, 0, 'layer should have 0 children'); + assert.equal( + group.children.length, + 2, + 'group still should have 2 children' + ); + }); + + // ====================================================== + it('add group', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var group = new Konva.Group(); + + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + }); + + group.add(circle); + layer.add(group); + stage.add(layer); + + var trace = layer.getContext().getTrace(); + assert.equal( + trace, + 'clearRect(0,0,578,200);save();transform(1,0,0,1,289,100);beginPath();arc(0,0,70,0,6.283,false);closePath();fillStyle=green;fill();lineWidth=4;strokeStyle=black;stroke();restore();' + ); + }); + + // ====================================================== + it('create two groups, move first group', function () { + var stage = addStage(); + var greenLayer = new Konva.Layer(); + var blueLayer = new Konva.Layer(); + var greenGroup = new Konva.Group(); + var blueGroup = new Konva.Group(); + + var greencircle = new Konva.Circle({ + x: stage.width() / 2 - 100, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + draggable: true, + }); + + var bluecircle = new Konva.Circle({ + x: stage.width() / 2 + 100, + y: stage.height() / 2, + radius: 70, + fill: 'blue', + stroke: 'black', + strokeWidth: 4, + }); + + greenGroup.add(greencircle); + blueGroup.add(bluecircle); + greenLayer.add(greenGroup); + blueLayer.add(blueGroup); + stage.add(greenLayer); + stage.add(blueLayer); + + blueLayer.removeChildren(); + var blueGroup2 = new Konva.Group(); + var bluecircle2 = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'blue', + stroke: 'black', + strokeWidth: 4, + }); + blueGroup2.add(bluecircle2); + blueLayer.add(blueGroup2); + blueLayer.draw(); + blueGroup2.position({ x: 100, y: 0 }); + blueLayer.draw(); + + var trace = blueLayer.getContext().getTrace(); + assert.equal( + trace, + 'clearRect(0,0,578,200);save();transform(1,0,0,1,389,100);beginPath();arc(0,0,70,0,6.283,false);closePath();fillStyle=blue;fill();lineWidth=4;strokeStyle=black;stroke();restore();clearRect(0,0,578,200);save();transform(1,0,0,1,289,100);beginPath();arc(0,0,70,0,6.283,false);closePath();fillStyle=blue;fill();lineWidth=4;strokeStyle=black;stroke();restore();clearRect(0,0,578,200);save();transform(1,0,0,1,389,100);beginPath();arc(0,0,70,0,6.283,false);closePath();fillStyle=blue;fill();lineWidth=4;strokeStyle=black;stroke();restore();' + ); + }); + + // ====================================================== + it('node type selector', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var fooLayer = new Konva.Layer(); + var group = new Konva.Group(); + + var blue = new Konva.Rect({ + x: 200, + y: 100, + width: 100, + height: 50, + fill: 'blue', + }); + + var red = new Konva.Rect({ + x: 250, + y: 100, + width: 100, + height: 50, + fill: 'red', + }); + + group.add(red); + layer.add(blue); + layer.add(group); + stage.add(layer); + stage.add(fooLayer); + + assert.equal(stage.find('Shape').length, 2, 'stage should have 2 shapes'); + assert.equal(layer.find('Shape').length, 2, 'layer should have 2 shapes'); + assert.equal(group.find('Shape').length, 1, 'layer should have 2 shapes'); + + assert.equal(stage.find('Layer').length, 2, 'stage should have 2 layers'); + assert.equal(stage.find('Group').length, 1, 'stage should have 1 group'); + + assert.equal(layer.find('Group').length, 1, 'layer should have 1 group'); + assert.equal(layer.find('Shape').length, 2, 'layer should have 2 shapes'); + assert.equal(layer.find('Layer').length, 0, 'layer should have 0 layers'); + + assert.equal( + layer.find('Group, Rect').length, + 3, + 'layer should have 3 [group or rects]' + ); + + assert.equal( + fooLayer.find('Group').length, + 0, + 'layer should have 0 groups' + ); + assert.equal( + fooLayer.find('Shape').length, + 0, + 'layer should have 0 shapes' + ); + + assert.equal(group.find('Shape').length, 1, 'group should have 1 shape'); + assert.equal(group.find('Layer').length, 0, 'group should have 0 layers'); + assert.equal(group.find('Group').length, 0, 'group should have 0 groups'); + }); + + // ====================================================== + it('node and shape type selector', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var layer2 = new Konva.Layer(); + var fooLayer = new Konva.Layer(); + var group = new Konva.Group(); + + var blue = new Konva.Rect({ + x: 100, + y: 50, + width: 100, + height: 50, + fill: 'blue', + }); + + var red = new Konva.Rect({ + x: 150, + y: 75, + width: 100, + height: 50, + fill: 'red', + }); + + var green = new Konva.Rect({ + x: 200, + y: 100, + width: 100, + height: 50, + fill: 'green', + }); + + var blueCircle = new Konva.Circle({ + x: 350, + y: 75, + radius: 40, + fill: 'blue', + }); + + var redCircle = new Konva.Circle({ + x: 400, + y: 125, + radius: 40, + fill: 'red', + }); + + var textpath = new Konva.TextPath({ + y: 35, + stroke: 'black', + strokeWidth: 1, + fill: 'orange', + fontSize: 18, + fontFamily: 'Arial', + text: "The quick brown fox jumped over the lazy dog's back", + data: 'M 10,10 300,150 550,150', + }); + + var path = new Konva.Path({ + x: 200, + y: -75, + data: 'M200,100h100v50z', + fill: '#ccc', + stroke: '#333', + strokeWidth: 2, + shadowColor: 'black', + shadowBlur: 2, + shadowOffset: { x: 10, y: 10 }, + shadowOpacity: 0.5, + }); + + var poly = new Konva.RegularPolygon({ + x: stage.width() / 2, + y: stage.height() / 2, + sides: 5, + radius: 50, + fill: 'green', + stroke: 'blue', + strokeWidth: 5, + name: 'foobar', + }); + + group.add(red); + group.add(redCircle); + layer.add(blue); + layer.add(green); + layer.add(blueCircle); + layer.add(group); + layer2.add(textpath); + layer2.add(path); + layer2.add(poly); + stage.add(layer); + stage.add(layer2); + stage.add(fooLayer); + + assert.equal(stage.find('Shape').length, 8, 'stage should have 5 shapes'); + assert.equal(stage.find('Layer').length, 3, 'stage should have 2 layers'); + assert.equal(stage.find('Group').length, 1, 'stage should have 1 group'); + assert.equal(stage.find('Rect').length, 3, 'stage should have 3 rects'); + assert.equal(stage.find('Circle').length, 2, 'stage should have 2 circles'); + assert.equal( + stage.find('RegularPolygon').length, + 1, + 'stage should have 1 regular polygon' + ); + assert.equal( + stage.find('TextPath').length, + 1, + 'stage should have 1 text path' + ); + assert.equal(stage.find('Path').length, 1, 'stage should have 1 path'); + + assert.equal(layer.find('Shape').length, 5, 'layer should have 5 shapes'); + assert.equal(layer.find('Layer').length, 0, 'layer should have 0 layers'); + assert.equal(layer.find('Group').length, 1, 'layer should have 1 group'); + assert.equal(layer.find('Rect').length, 3, 'layer should have 3 rects'); + assert.equal(layer.find('Circle').length, 2, 'layer should have 2 circles'); + assert.equal( + layer.find('RegularPolygon').length, + 0, + 'layer should have 0 regular polygon' + ); + assert.equal( + layer.find('TextPath').length, + 0, + 'layer should have 0 text path' + ); + assert.equal(layer.find('Path').length, 0, 'layer should have 0 path'); + + assert.equal(layer2.find('Shape').length, 3, 'layer2 should have 3 shapes'); + assert.equal(layer2.find('Layer').length, 0, 'layer2 should have 0 layers'); + assert.equal(layer2.find('Group').length, 0, 'layer2 should have 0 group'); + assert.equal( + layer2.find('RegularPolygon').length, + 1, + 'layer2 should have 1 regular polygon' + ); + assert.equal( + layer2.find('TextPath').length, + 1, + 'layer2 should have 1 text path' + ); + assert.equal(layer2.find('Path').length, 1, 'layer2 should have 1 path'); + + assert.equal( + fooLayer.find('Shape').length, + 0, + 'layer should have 0 shapes' + ); + assert.equal( + fooLayer.find('Group').length, + 0, + 'layer should have 0 groups' + ); + assert.equal(fooLayer.find('Rect').length, 0, 'layer should have 0 rects'); + assert.equal( + fooLayer.find('Circle').length, + 0, + 'layer should have 0 circles' + ); + + assert.equal(group.find('Shape').length, 2, 'group should have 2 shape'); + assert.equal(group.find('Layer').length, 0, 'group should have 0 layers'); + assert.equal(group.find('Group').length, 0, 'group should have 0 groups'); + assert.equal(group.find('Rect').length, 1, 'group should have 1 rects'); + assert.equal(group.find('Circle').length, 1, 'gropu should have 1 circles'); + }); + + // ====================================================== + it('test find() selector by adding shapes with multiple names', function () { + var stage = addStage(); + var layer = new Konva.Layer({ + name: 'layerName', + id: 'layerId', + }); + var group = new Konva.Group({ + name: 'groupName', + id: 'groupId', + }); + var rect = new Konva.Rect({ + x: 200, + y: 20, + width: 100, + height: 50, + fill: 'red', + name: 'red rectangle', + id: 'rectId', + }); + var circle = new Konva.Circle({ + x: 50, + y: 50, + radius: 20, + fill: 'red', + name: 'red circle', + id: 'circleId', + }); + + group.add(rect); + group.add(circle); + layer.add(group); + stage.add(layer); + + assert.equal( + stage.find('.rectangle')[0], + rect, + 'problem with shape name selector' + ); + assert.equal( + layer.find('.rectangle')[0], + rect, + 'problem with shape name selector' + ); + assert.equal( + group.find('.rectangle')[0], + rect, + 'problem with shape name selector' + ); + + assert.equal( + stage.find('.circle')[0], + circle, + 'problem with shape name selector' + ); + assert.equal( + layer.find('.circle')[0], + circle, + 'problem with shape name selector' + ); + assert.equal( + group.find('.circle')[0], + circle, + 'problem with shape name selector' + ); + + assert.equal( + stage.find('.red')[0], + rect, + 'problem with shape name selector' + ); + assert.equal( + stage.find('.red')[1], + circle, + 'problem with shape name selector' + ); + assert.equal( + layer.find('.red')[0], + rect, + 'problem with shape name selector' + ); + assert.equal( + layer.find('.red')[1], + circle, + 'problem with shape name selector' + ); + assert.equal( + group.find('.red')[0], + rect, + 'problem with shape name selector' + ); + assert.equal( + group.find('.red')[1], + circle, + 'problem with shape name selector' + ); + + assert.equal( + stage.find('.groupName')[0], + group, + 'problem with group name selector' + ); + assert.equal( + layer.find('.groupName')[0], + group, + 'problem with group name selector' + ); + + assert.equal( + stage.find('.layerName')[0], + layer, + 'problem with layer name selector' + ); + }); + + // ====================================================== + it('test find() selector by adding shape, then group, then layer', function () { + var stage = addStage(); + var layer = new Konva.Layer({ + name: 'layerName', + id: 'layerId', + }); + var group = new Konva.Group({ + name: 'groupName', + id: 'groupId', + }); + var rect = new Konva.Rect({ + x: 200, + y: 20, + width: 100, + height: 50, + fill: 'red', + stroke: 'black', + strokeWidth: 4, + name: 'rectName', + id: 'rectId', + }); + + group.add(rect); + layer.add(group); + stage.add(layer); + + assert.equal( + stage.find('.rectName')[0].attrs.id, + 'rectId', + 'problem with shape name selector' + ); + assert.equal( + stage.find('#rectId')[0].attrs.id, + 'rectId', + 'problem with shape id selector' + ); + assert.equal( + layer.find('.rectName')[0].attrs.id, + 'rectId', + 'problem with shape name selector' + ); + assert.equal( + layer.find('#rectId')[0].attrs.id, + 'rectId', + 'problem with shape id selector' + ); + assert.equal( + group.find('.rectName')[0].attrs.id, + 'rectId', + 'problem with shape name selector' + ); + assert.equal( + group.find('#rectId')[0].attrs.id, + 'rectId', + 'problem with shape id selector' + ); + + assert.equal( + stage.find('.groupName')[0].attrs.id, + 'groupId', + 'problem with group name selector' + ); + assert.equal( + stage.find('#groupId')[0].attrs.id, + 'groupId', + 'problem with group id selector' + ); + assert.equal( + layer.find('.groupName')[0].attrs.id, + 'groupId', + 'problem with group name selector' + ); + assert.equal( + layer.find('#groupId')[0].attrs.id, + 'groupId', + 'problem with group id selector' + ); + + assert.equal( + stage.find('.layerName')[0].attrs.id, + 'layerId', + 'problem with layer name selector' + ); + assert.equal( + stage.find('#layerId')[0].attrs.id, + 'layerId', + 'problem with layer id selector' + ); + }); + + // ====================================================== + it('test find() selector by adding group, then shape, then layer', function () { + var stage = addStage(); + var layer = new Konva.Layer({ + name: 'layerName', + id: 'layerId', + }); + var group = new Konva.Group({ + name: 'groupName', + id: 'groupId', + }); + var rect = new Konva.Rect({ + x: 200, + y: 20, + width: 100, + height: 50, + fill: 'red', + stroke: 'black', + strokeWidth: 4, + name: 'rectName', + id: 'rectId', + }); + + layer.add(group); + group.add(rect); + stage.add(layer); + + assert.equal( + stage.find('.rectName')[0].attrs.id, + 'rectId', + 'problem with shape name selector' + ); + assert.equal( + stage.find('#rectId')[0].attrs.id, + 'rectId', + 'problem with shape id selector' + ); + assert.equal( + layer.find('.rectName')[0].attrs.id, + 'rectId', + 'problem with shape name selector' + ); + assert.equal( + layer.find('#rectId')[0].attrs.id, + 'rectId', + 'problem with shape id selector' + ); + assert.equal( + group.find('.rectName')[0].attrs.id, + 'rectId', + 'problem with shape name selector' + ); + assert.equal( + group.find('#rectId')[0].attrs.id, + 'rectId', + 'problem with shape id selector' + ); + + assert.equal( + stage.find('.groupName')[0].attrs.id, + 'groupId', + 'problem with group name selector' + ); + assert.equal( + stage.find('#groupId')[0].attrs.id, + 'groupId', + 'problem with group id selector' + ); + assert.equal( + layer.find('.groupName')[0].attrs.id, + 'groupId', + 'problem with group name selector' + ); + assert.equal( + layer.find('#groupId')[0].attrs.id, + 'groupId', + 'problem with group id selector' + ); + + assert.equal( + stage.find('.layerName')[0].attrs.id, + 'layerId', + 'problem with layer name selector' + ); + assert.equal( + stage.find('#layerId')[0].attrs.id, + 'layerId', + 'problem with layer id selector' + ); + }); + + // ====================================================== + it('test find() selector by adding group, then layer, then shape', function () { + var stage = addStage(); + var layer = new Konva.Layer({ + name: 'layerName', + id: 'layerId', + }); + var group = new Konva.Group({ + name: 'groupName', + id: 'groupId', + }); + var rect = new Konva.Rect({ + x: 200, + y: 20, + width: 100, + height: 50, + fill: 'red', + stroke: 'black', + strokeWidth: 4, + name: 'rectName', + id: 'rectId', + }); + + layer.add(group); + stage.add(layer); + group.add(rect); + + assert.equal( + stage.find('.rectName')[0].attrs.id, + 'rectId', + 'problem with shape name selector' + ); + assert.equal( + stage.find('#rectId')[0].attrs.id, + 'rectId', + 'problem with shape id selector' + ); + assert.equal( + layer.find('.rectName')[0].attrs.id, + 'rectId', + 'problem with shape name selector' + ); + assert.equal( + layer.find('#rectId')[0].attrs.id, + 'rectId', + 'problem with shape id selector' + ); + assert.equal( + group.find('.rectName')[0].attrs.id, + 'rectId', + 'problem with shape name selector' + ); + assert.equal( + group.find('#rectId')[0].attrs.id, + 'rectId', + 'problem with shape id selector' + ); + + assert.equal( + stage.find('.groupName')[0].attrs.id, + 'groupId', + 'problem with group name selector' + ); + assert.equal( + stage.find('#groupId')[0].attrs.id, + 'groupId', + 'problem with group id selector' + ); + assert.equal( + layer.find('.groupName')[0].attrs.id, + 'groupId', + 'problem with group name selector' + ); + assert.equal( + layer.find('#groupId')[0].attrs.id, + 'groupId', + 'problem with group id selector' + ); + + assert.equal( + stage.find('.layerName')[0].attrs.id, + 'layerId', + 'problem with layer name selector' + ); + assert.equal( + stage.find('#layerId')[0].attrs.id, + 'layerId', + 'problem with layer id selector' + ); + }); + + // ====================================================== + it('test find() selector by adding layer, then group, then shape', function () { + var stage = addStage(); + var layer = new Konva.Layer({ + name: 'layerName', + id: 'layerId', + }); + var group = new Konva.Group({ + name: 'groupName', + id: 'groupId', + }); + var rect = new Konva.Rect({ + x: 200, + y: 20, + width: 100, + height: 50, + fill: 'red', + stroke: 'black', + strokeWidth: 4, + name: 'rectName', + id: 'rectId', + }); + + stage.add(layer); + layer.add(group); + group.add(rect); + + assert.equal( + stage.find('.rectName')[0].attrs.id, + 'rectId', + 'problem with shape name selector' + ); + assert.equal( + stage.find('#rectId')[0].attrs.id, + 'rectId', + 'problem with shape id selector' + ); + assert.equal( + layer.find('.rectName')[0].attrs.id, + 'rectId', + 'problem with shape name selector' + ); + assert.equal( + layer.find('#rectId')[0].attrs.id, + 'rectId', + 'problem with shape id selector' + ); + assert.equal( + group.find('.rectName')[0].attrs.id, + 'rectId', + 'problem with shape name selector' + ); + assert.equal( + group.find('#rectId')[0].attrs.id, + 'rectId', + 'problem with shape id selector' + ); + + assert.equal( + stage.find('.groupName')[0].attrs.id, + 'groupId', + 'problem with group name selector' + ); + assert.equal( + stage.find('#groupId')[0].attrs.id, + 'groupId', + 'problem with group id selector' + ); + assert.equal( + layer.find('.groupName')[0].attrs.id, + 'groupId', + 'problem with group name selector' + ); + assert.equal( + layer.find('#groupId')[0].attrs.id, + 'groupId', + 'problem with group id selector' + ); + + assert.equal( + stage.find('.layerName')[0].attrs.id, + 'layerId', + 'problem with layer name selector' + ); + assert.equal( + stage.find('#layerId')[0].attrs.id, + 'layerId', + 'problem with layer id selector' + ); + + layer.draw(); + }); + + it('warn on invalid selector', function () { + var stage = addStage(); + var layer = new Konva.Layer({ + name: 'layerName', + id: 'layerId', + }); + var group = new Konva.Group({ + name: 'groupName', + id: 'groupId', + }); + var rect = new Konva.Rect({ + x: 200, + y: 20, + width: 100, + height: 50, + fill: 'red', + stroke: 'black', + strokeWidth: 4, + name: 'rectName', + id: 'rectId', + }); + + stage.add(layer); + layer.add(group); + group.add(rect); + layer.draw(); + + var counter = 0; + var oldWarn = Konva.Util.warn; + Konva.Util.warn = function () { + counter += 1; + }; + + // forgot dot + group.find('rectName'); + assert.equal(counter > 0, true); + Konva.Util.warn = oldWarn; + }); + + // ====================================================== + it('add layer then shape', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + }); + + stage.add(layer); + layer.add(circle); + layer.draw(); + + assert(layer.getChildren().length === 1); + }); + + // ====================================================== + it('move blue layer on top of green layer with setZIndex', function () { + var stage = addStage(); + var blueLayer = new Konva.Layer(); + var greenLayer = new Konva.Layer(); + + var bluecircle = new Konva.Circle({ + x: 200, + y: stage.height() / 2, + radius: 70, + fill: 'blue', + stroke: 'black', + strokeWidth: 4, + }); + + var greencircle = new Konva.Circle({ + x: 280, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + }); + + blueLayer.add(bluecircle); + greenLayer.add(greencircle); + + stage.add(blueLayer); + stage.add(greenLayer); + + blueLayer.setZIndex(1); + + //console.log(greenLayer.getZIndex()); + + assert.equal( + greenLayer.getZIndex(), + 0, + 'green layer should have z index of 0' + ); + assert.equal( + blueLayer.getZIndex(), + 1, + 'blue layer should have z index of 1' + ); + }); + + // ====================================================== + it('move blue layer on top of green layer with moveToTop', function () { + var stage = addStage(); + var blueLayer = new Konva.Layer(); + var greenLayer = new Konva.Layer(); + + var bluecircle = new Konva.Circle({ + x: 200, + y: stage.height() / 2, + radius: 70, + fill: 'blue', + stroke: 'black', + strokeWidth: 4, + }); + + var greencircle = new Konva.Circle({ + x: 280, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + }); + + blueLayer.add(bluecircle); + greenLayer.add(greencircle); + + stage.add(blueLayer); + stage.add(greenLayer); + + blueLayer.moveToTop(); + + assert(blueLayer.zIndex() === 1); + assert(greenLayer.zIndex() === 0); + }); + + // ====================================================== + it('move green layer below blue layer with moveToBottom', function () { + var stage = addStage(); + var blueLayer = new Konva.Layer(); + var greenLayer = new Konva.Layer(); + + var bluecircle = new Konva.Circle({ + x: 200, + y: stage.height() / 2, + radius: 70, + fill: 'blue', + stroke: 'black', + strokeWidth: 4, + }); + + var greencircle = new Konva.Circle({ + x: 280, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + }); + + blueLayer.add(bluecircle); + greenLayer.add(greencircle); + + stage.add(blueLayer); + stage.add(greenLayer); + + greenLayer.moveToBottom(); + + assert(blueLayer.zIndex() === 1); + assert(greenLayer.zIndex() === 0); + }); + + // ====================================================== + it('move green layer below blue layer with moveDown', function () { + var stage = addStage(); + var blueLayer = new Konva.Layer(); + var greenLayer = new Konva.Layer(); + + var bluecircle = new Konva.Circle({ + x: 200, + y: stage.height() / 2, + radius: 70, + fill: 'blue', + stroke: 'black', + strokeWidth: 4, + }); + + var greencircle = new Konva.Circle({ + x: 280, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + }); + + blueLayer.add(bluecircle); + greenLayer.add(greencircle); + + stage.add(blueLayer); + stage.add(greenLayer); + greenLayer.moveDown(); + + assert(blueLayer.zIndex() === 1); + assert(greenLayer.zIndex() === 0); + }); + + // ====================================================== + it('move blue layer above green layer with moveUp', function () { + var stage = addStage(); + var blueLayer = new Konva.Layer(); + var greenLayer = new Konva.Layer(); + + var bluecircle = new Konva.Circle({ + x: 200, + y: stage.height() / 2, + radius: 70, + fill: 'blue', + stroke: 'black', + strokeWidth: 4, + }); + + var greencircle = new Konva.Circle({ + x: 280, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + }); + + blueLayer.add(bluecircle); + greenLayer.add(greencircle); + + stage.add(blueLayer); + stage.add(greenLayer); + blueLayer.moveUp(); + + assert(blueLayer.zIndex() === 1); + assert(greenLayer.zIndex() === 0); + }); + + // ====================================================== + it('move blue circle on top of green circle with moveToTop', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var bluecircle = new Konva.Circle({ + x: 200, + y: stage.height() / 2, + radius: 70, + fill: 'blue', + stroke: 'black', + strokeWidth: 4, + }); + + var greencircle = new Konva.Circle({ + x: 280, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + }); + + layer.add(bluecircle); + layer.add(greencircle); + stage.add(layer); + + assert.equal( + bluecircle.getZIndex(), + 0, + 'blue circle should have zindex 0 before relayering' + ); + assert.equal( + greencircle.getZIndex(), + 1, + 'green circle should have zindex 1 before relayering' + ); + + bluecircle.moveToTop(); + + assert.equal( + bluecircle.getZIndex(), + 1, + 'blue circle should have zindex 1 after relayering' + ); + assert.equal( + greencircle.getZIndex(), + 0, + 'green circle should have zindex 0 after relayering' + ); + + layer.draw(); + }); + + // ====================================================== + it('move green circle below blue circle with moveDown', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var bluecircle = new Konva.Circle({ + x: 200, + y: stage.height() / 2, + radius: 70, + fill: 'blue', + stroke: 'black', + strokeWidth: 4, + }); + + var greencircle = new Konva.Circle({ + x: 280, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + }); + + layer.add(bluecircle); + layer.add(greencircle); + stage.add(layer); + + assert.equal( + bluecircle.getZIndex(), + 0, + 'blue circle should have zindex 0 before relayering' + ); + assert.equal( + greencircle.getZIndex(), + 1, + 'green circle should have zindex 1 before relayering' + ); + + greencircle.moveDown(); + + assert.equal( + bluecircle.getZIndex(), + 1, + 'blue circle should have zindex 1 after relayering' + ); + assert.equal( + greencircle.getZIndex(), + 0, + 'green circle should have zindex 0 after relayering' + ); + + layer.draw(); + }); + + // ====================================================== + it('layer layer when only one layer', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var bluecircle = new Konva.Circle({ + x: 200, + y: stage.height() / 2, + radius: 70, + fill: 'blue', + stroke: 'black', + strokeWidth: 4, + }); + + layer.add(bluecircle); + stage.add(layer); + + assert.equal(layer.getZIndex(), 0, 'layer should have zindex of 0'); + + layer.moveDown(); + assert.equal(layer.getZIndex(), 0, 'layer should have zindex of 0'); + + layer.moveToBottom(); + assert.equal(layer.getZIndex(), 0, 'layer should have zindex of 0'); + + layer.moveUp(); + assert.equal(layer.getZIndex(), 0, 'layer should have zindex of 0'); + + layer.moveToTop(); + assert.equal(layer.getZIndex(), 0, 'layer should have zindex of 0'); + }); + + // ====================================================== + it('move blue group on top of green group with moveToTop', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var greenGroup = new Konva.Group(); + var blueGroup = new Konva.Group(); + + var bluecircle = new Konva.Circle({ + x: 200, + y: stage.height() / 2, + radius: 70, + fill: 'blue', + stroke: 'black', + strokeWidth: 4, + }); + + var greencircle = new Konva.Circle({ + x: 280, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + }); + + blueGroup.add(bluecircle); + greenGroup.add(greencircle); + + layer.add(blueGroup); + layer.add(greenGroup); + stage.add(layer); + + assert.equal( + blueGroup.getZIndex(), + 0, + 'blue group should have zindex 0 before relayering' + ); + assert.equal( + greenGroup.getZIndex(), + 1, + 'green group should have zindex 1 before relayering' + ); + + blueGroup.moveToTop(); + + assert.equal( + blueGroup.getZIndex(), + 1, + 'blue group should have zindex 1 after relayering' + ); + assert.equal( + greenGroup.getZIndex(), + 0, + 'green group should have zindex 0 after relayering' + ); + + layer.draw(); + }); + + // ====================================================== + it('move blue group on top of green group with moveUp', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var greenGroup = new Konva.Group(); + var blueGroup = new Konva.Group(); + + var bluecircle = new Konva.Circle({ + x: 200, + y: stage.height() / 2, + radius: 70, + fill: 'blue', + stroke: 'black', + strokeWidth: 4, + }); + + var greencircle = new Konva.Circle({ + x: 280, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + }); + + blueGroup.add(bluecircle); + greenGroup.add(greencircle); + + layer.add(blueGroup); + layer.add(greenGroup); + stage.add(layer); + + assert.equal( + blueGroup.getZIndex(), + 0, + 'blue group should have zindex 0 before relayering' + ); + assert.equal( + greenGroup.getZIndex(), + 1, + 'green group should have zindex 1 before relayering' + ); + + blueGroup.moveUp(); + + assert.equal( + blueGroup.getZIndex(), + 1, + 'blue group should have zindex 1 after relayering' + ); + assert.equal( + greenGroup.getZIndex(), + 0, + 'green group should have zindex 0 after relayering' + ); + + layer.draw(); + }); + + // ====================================================== + it('add and moveTo should work same way (depend on parent)', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var greenGroup = new Konva.Group(); + var blueGroup = new Konva.Group(); + + var bluecircle = new Konva.Circle({ + x: 200, + y: stage.height() / 2, + radius: 70, + fill: 'blue', + stroke: 'black', + strokeWidth: 4, + }); + + bluecircle.moveTo(blueGroup); + + layer.add(blueGroup); + layer.add(greenGroup); + stage.add(layer); + + assert.equal( + blueGroup.getChildren().length, + 1, + 'blue group should have only one children' + ); + blueGroup.add(bluecircle); + assert.equal( + blueGroup.getChildren().length, + 1, + 'blue group should have only one children after adding node twice' + ); + + greenGroup.add(bluecircle); + assert.equal( + blueGroup.getChildren().length, + 0, + 'blue group should not have children' + ); + assert.equal( + greenGroup.getChildren().length, + 1, + 'green group should have only one children' + ); + + layer.draw(); + }); + + // ====================================================== + it('getChildren may use filter function', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var group = new Konva.Group(); + + var circle1 = new Konva.Circle({ + x: 200, + y: stage.height() / 2, + radius: 70, + fill: 'blue', + stroke: 'black', + strokeWidth: 4, + }); + var circle2 = circle1.clone(); + group.add(circle1).add(circle2); + + var rect = new Konva.Rect({ + name: 'test', + }); + group.add(rect); + + var circles = group.getChildren(function (node) { + return node.getClassName() === 'Circle'; + }); + assert.equal(circles.length, 2, 'group has two circle children'); + assert.equal(circles.indexOf(circle1) > -1, true); + assert.equal(circles.indexOf(circle2) > -1, true); + + var testName = group.getChildren(function (node) { + return node.name() === 'test'; + }); + + assert.equal(testName.length, 1, 'group has one children with test name'); + + layer.add(group); + + layer.draw(); + }); + + it('add multiple nodes to container', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle1 = new Konva.Circle({ + x: 0, + y: 0, + radius: 10, + fill: 'red', + }); + var circle2 = new Konva.Circle({ + x: 0, + y: 0, + radius: 10, + fill: 'white', + }); + var circle3 = new Konva.Circle({ + x: 0, + y: 0, + radius: 10, + fill: 'blue', + }); + layer.add(circle1, circle2, circle3); + assert.equal( + layer.getChildren().length, + 3, + 'layer has exactly three children' + ); + }); + it('getClientRect - adding a zero bounds shape should result in zero bounds', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + var grp = new Konva.Group(); + var zeroRect = new Konva.Rect({ x: 0, y: 0, width: 0, height: 0 }); + grp.add(zeroRect); + var bounds = grp.getClientRect(); + assert.deepEqual(bounds, { + x: 0, + y: 0, + width: 0, + height: 0, + }); + }); + it('getClientRect - test empty case', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + var group = new Konva.Group({ + x: 10, + y: 10, + }); + group.add(new Konva.Group()); + assert.deepEqual(group.getClientRect(), { + x: 10, + y: 10, + width: 0, + height: 0, + }); + }); + + it('get client rect with deep nested hidden shape', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + var group = new Konva.Group({ + draggable: true, + x: 100, + y: 40, + }); + + var rect = new Konva.Rect({ + height: 100, + width: 100, + fill: 'red', + }); + group.add(rect); + layer.add(group); + + var subGroup = new Konva.Group(); + group.add(subGroup); + + subGroup.add( + new Konva.Rect({ + visible: false, + }) + ); + + stage.add(layer); + stage.draw(); + + var clientRect = group.getClientRect(); + + assert.deepEqual(clientRect, { + x: 100, + y: 40, + width: 100, + height: 100, + }); + }); + + it('getClientRect - test group with invisible child', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + var group = new Konva.Group({ + x: 10, + y: 10, + }); + layer.add(group); + layer.draw(); + group.add( + new Konva.Rect({ + x: 0, + y: 0, + width: 50, + height: 50, + }) + ); + group.add( + new Konva.Rect({ + x: 400, + y: 400, + width: 50, + height: 50, + visible: false, + }) + ); + assert.deepEqual(group.getClientRect(), { + x: 10, + y: 10, + width: 50, + height: 50, + }); + }); + + it('getClientRect - test group with invisible child inside invisible parent', function () { + var stage = addStage(); + var layer = new Konva.Layer({ + visible: false, + }); + stage.add(layer); + var group = new Konva.Group({ + x: 10, + y: 10, + }); + layer.add(group); + layer.draw(); + group.add( + new Konva.Rect({ + x: 0, + y: 0, + width: 50, + height: 50, + }) + ); + group.add( + new Konva.Rect({ + x: 400, + y: 400, + width: 50, + height: 50, + visible: false, + }) + ); + assert.deepEqual(group.getClientRect(), { + x: 10, + y: 10, + width: 50, + height: 50, + }); + }); + + it('getClientRect - test group with visible child inside invisible parent', function () { + var stage = addStage(); + var layer = new Konva.Layer({ + visible: false, + }); + stage.add(layer); + var group = new Konva.Group({ + x: 10, + y: 10, + }); + layer.add(group); + + var subGroup = new Konva.Group({ + visible: false, + }); + group.add(subGroup); + subGroup.add( + new Konva.Rect({ + x: 0, + y: 0, + width: 50, + height: 50, + visible: true, + }) + ); + subGroup.add( + new Konva.Rect({ + x: 400, + y: 400, + width: 50, + height: 50, + visible: true, + }) + ); + layer.draw(); + assert.deepEqual(group.getClientRect(), { + x: 10, + y: 10, + width: 0, + height: 0, + }); + }); + + it('get client rect with deep nested hidden shape 2', function () { + var layer = new Konva.Layer(); + var group = new Konva.Group({ + visible: false, + x: 100, + y: 40, + }); + + var rect = new Konva.Rect({ + height: 100, + width: 100, + fill: 'red', + }); + group.add(rect); + layer.add(group); + + var clientRect = layer.getClientRect(); + + assert.deepEqual(clientRect, { + x: 0, + y: 0, + width: 0, + height: 0, + }); + }); + + it('getClientRect - test layer', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var group = new Konva.Group(); + + var rect = new Konva.Rect({ + x: 50, + y: 100, + width: 200, + height: 75, + fill: 'red', + }); + + group.add(rect); + layer.add(group); + stage.add(layer); + + assert.deepEqual(layer.getClientRect(), { + x: 50, + y: 100, + width: 200, + height: 75, + }); + }); + + it('getClientRect - nested group with a hidden shapes', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + stage.add(layer); + + var group1 = new Konva.Group(); + layer.add(group1); + + var rect = new Konva.Rect({ + x: 50, + y: 100, + width: 200, + height: 75, + fill: 'red', + }); + group1.add(rect); + + var group2 = new Konva.Group(); + layer.add(group2); + + var rect2 = new Konva.Rect({ + x: 0, + y: 0, + width: 200, + height: 75, + fill: 'red', + visible: false, + }); + group1.add(rect2); + + assert.deepEqual(layer.getClientRect(), { + x: 50, + y: 100, + width: 200, + height: 75, + }); + }); + + it('clip-cache', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var group = new Konva.Group(); + var circle = new Konva.Circle({ + x: 50, + y: 50, + radius: 30, + fill: 'red', + }); + + group.add(circle); + layer.add(group.clip({ x: 25, y: 25, width: 50, height: 50 })); + stage.add(layer); + + layer.cache(); + stage.draw(); + + var data = layer.getContext().getImageData(24, 50, 1, 1).data; + var isTransparent = data[3] == 0; + assert.equal( + isTransparent, + true, + 'tested pixel (24,50) should be transparent: ' + + data[0] + + '_' + + data[1] + + '_' + + data[2] + + '_' + + data[3] + ); + + data = layer.getContext().getImageData(50, 24, 1, 1).data; + isTransparent = data[3] == 0; + assert.equal( + isTransparent, + true, + 'tested pixel (50,24) should be transparent: ' + + data[0] + + '_' + + data[1] + + '_' + + data[2] + + '_' + + data[3] + ); + + data = layer.getHitCanvas().getContext().getImageData(76, 50, 1, 1).data; + isTransparent = data[3] == 0; + + assert.equal( + isTransparent, + true, + 'tested pixel (76,50) should be transparent: ' + + data[0] + + '_' + + data[1] + + '_' + + data[2] + + '_' + + data[3] + ); + + data = layer.getHitCanvas().getContext().getImageData(50, 76, 1, 1).data; + isTransparent = data[3] == 0; + assert.equal( + isTransparent, + true, + 'tested pixel (50,76) should be transparent: ' + + data[0] + + '_' + + data[1] + + '_' + + data[2] + + '_' + + data[3] + ); + + var ratio = layer.getCanvas().getPixelRatio(); + data = layer.getContext().getImageData(26 * ratio, 50 * ratio, 1, 1).data; + var isRed = + data[0] == 255 && data[1] == 0 && data[2] == 0 && data[3] == 255; + assert.equal( + isRed, + true, + 'tested pixel (26,50) should be red: ' + + data[0] + + '_' + + data[1] + + '_' + + data[2] + + '_' + + data[3] + ); + + data = layer.getContext().getImageData(50 * ratio, 26 * ratio, 1, 1).data; + isRed = data[0] == 255 && data[1] == 0 && data[2] == 0 && data[3] == 255; + assert.equal( + isRed, + true, + 'tested pixel (50,26) should be red: ' + + data[0] + + '_' + + data[1] + + '_' + + data[2] + + '_' + + data[3] + ); + + data = layer.getContext().getImageData(74 * ratio, 50 * ratio, 1, 1).data; + isRed = data[0] == 255 && data[1] == 0 && data[2] == 0 && data[3] == 255; + assert.equal( + isRed, + true, + 'tested pixel (74,50) should be red: ' + + data[0] + + '_' + + data[1] + + '_' + + data[2] + + '_' + + data[3] + ); + + data = layer.getContext().getImageData(50 * ratio, 74 * ratio, 1, 1).data; + isRed = data[0] == 255 && data[1] == 0 && data[2] == 0 && data[3] == 255; + assert.equal( + isRed, + true, + 'tested pixel (50,74) should be red: ' + + data[0] + + '_' + + data[1] + + '_' + + data[2] + + '_' + + data[3] + ); + }); + + it('clip-cache-scale', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var group = new Konva.Group(); + var circle = new Konva.Circle({ + x: 50, + y: 50, + radius: 30, + fill: 'red', + }); + + group.add(circle); + layer.add(group.clip({ x: 25, y: 25, width: 50, height: 50 })); + stage.add(layer); + + layer.cache(); + stage.scale({ x: 2, y: 2 }); + stage.draw(); + + var data = layer.getHitCanvas().getContext().getImageData(48, 100, 1, 1) + .data; + var isTransparent = data[3] == 0; + assert.equal( + isTransparent, + true, + 'tested pixel (48,100) should be transparent: ' + + data[0] + + '_' + + data[1] + + '_' + + data[2] + + '_' + + data[3] + ); + + data = layer.getHitCanvas().getContext().getImageData(100, 48, 1, 1).data; + isTransparent = data[3] == 0; + assert.equal( + isTransparent, + true, + 'tested pixel (100,48) should be transparent: ' + + data[0] + + '_' + + data[1] + + '_' + + data[2] + + '_' + + data[3] + ); + + data = layer.getHitCanvas().getContext().getImageData(152, 100, 1, 1).data; + isTransparent = data[3] == 0; + assert.equal( + isTransparent, + true, + 'tested pixel (152,100) should be transparent: ' + + data[0] + + '_' + + data[1] + + '_' + + data[2] + + '_' + + data[3] + ); + + data = layer.getHitCanvas().getContext().getImageData(100, 152, 1, 1).data; + isTransparent = data[3] == 0; + assert.equal( + isTransparent, + true, + 'tested pixel (100,152) should be transparent: ' + + data[0] + + '_' + + data[1] + + '_' + + data[2] + + '_' + + data[3] + ); + + var ratio = layer.getCanvas().getPixelRatio(); + data = layer.getContext().getImageData(52 * ratio, 100 * ratio, 1, 1).data; + var isRed = + data[0] == 255 && data[1] == 0 && data[2] == 0 && data[3] == 255; + assert.equal( + isRed, + true, + 'tested pixel (52,100) should be red: ' + + data[0] + + '_' + + data[1] + + '_' + + data[2] + + '_' + + data[3] + ); + + data = layer.getContext().getImageData(100 * ratio, 52 * ratio, 1, 1).data; + isRed = data[0] == 255 && data[1] == 0 && data[2] == 0 && data[3] == 255; + assert.equal( + isRed, + true, + 'tested pixel (100,52) should be red: ' + + data[0] + + '_' + + data[1] + + '_' + + data[2] + + '_' + + data[3] + ); + + data = layer.getContext().getImageData(148 * ratio, 100 * ratio, 1, 1).data; + isRed = data[0] == 255 && data[1] == 0 && data[2] == 0 && data[3] == 255; + assert.equal( + isRed, + true, + 'tested pixel (148,100) should be red: ' + + data[0] + + '_' + + data[1] + + '_' + + data[2] + + '_' + + data[3] + ); + + data = layer.getContext().getImageData(100 * ratio, 148 * ratio, 1, 1).data; + isRed = data[0] == 255 && data[1] == 0 && data[2] == 0 && data[3] == 255; + assert.equal( + isRed, + true, + 'tested pixel (100,148) should be red: ' + + data[0] + + '_' + + data[1] + + '_' + + data[2] + + '_' + + data[3] + ); + }); +}); diff --git a/test/unit/Context-test.ts b/test/unit/Context-test.ts new file mode 100644 index 000000000..7cf6fc5d8 --- /dev/null +++ b/test/unit/Context-test.ts @@ -0,0 +1,118 @@ +import { assert } from 'chai'; +import { addStage, Konva } from './test-utils'; + +describe('Context', function () { + // ====================================================== + var contextMethods = [ + 'clearRect', + 'fillRect', + 'strokeRect', + 'fillText', + 'strokeText', + 'measureText', + 'createLinearGradient', + 'createRadialGradient', + 'createPattern', + 'beginPath', + 'closePath', + 'moveTo', + 'lineTo', + 'bezierCurveTo', + 'quadraticCurveTo', + 'arc', + 'arcTo', + 'rect', + 'roundRect', + 'ellipse', + 'fill', + 'stroke', + 'clip', + 'isPointInPath', + 'scale', + 'rotate', + 'translate', + 'transform', + 'setTransform', + 'drawImage', + 'createImageData', + 'getImageData', + 'putImageData', + 'save', + 'restore', + ]; + + var contextProperties = [ + 'fillStyle', + 'strokeStyle', + 'shadowColor', + 'shadowBlur', + 'shadowOffsetX', + 'shadowOffsetY', + 'lineCap', + 'lineDashOffset', + 'lineJoin', + 'lineWidth', + 'miterLimit', + 'font', + 'textAlign', + 'textBaseline', + 'globalAlpha', + 'globalCompositeOperation', + ] as const; + + it('context wrapper should work like native context', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + + var circle = new Konva.Circle({ + x: 100, + y: 70, + radius: 70, + fill: 'green', + stroke: 'blue', + strokeWidth: 4, + draggable: true, + }); + + layer.add(circle); + stage.add(layer); + + var context = layer.getContext(); + var nativeContext = context._context; + + contextMethods.forEach(function (method) { + assert.equal( + typeof nativeContext[method], + 'function', + 'native context has no method ' + method + ); + assert.equal( + typeof context[method], + 'function', + 'context wrapper has no method ' + method + ); + }); + + contextProperties.forEach(function (prop) { + assert.equal( + nativeContext[prop] !== undefined, + true, + 'native context has no property ' + prop + ); + assert.equal( + context[prop] !== undefined, + true, + 'context wrapper has no property ' + prop + ); + }); + + // test get + nativeContext.fillStyle = '#ff0000'; + assert.equal(context.fillStyle, '#ff0000'); + + // test set + context.globalAlpha = 0.5; + assert.equal(context.globalAlpha, 0.5); + }); +}); diff --git a/test/unit/DragAndDrop-test.ts b/test/unit/DragAndDrop-test.ts new file mode 100644 index 000000000..c734fcec9 --- /dev/null +++ b/test/unit/DragAndDrop-test.ts @@ -0,0 +1,1282 @@ +import { assert } from 'chai'; +import { + addStage, + Konva, + simulateMouseDown, + simulateMouseMove, + simulateMouseUp, + simulateTouchStart, + simulateTouchEnd, + simulateTouchMove, +} from './test-utils'; + +describe('DragAndDrop', function () { + // ====================================================== + it('test drag and drop properties and methods', function (done) { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + }); + + stage.add(layer); + layer.add(circle); + + setTimeout(function () { + layer.draw(); + + // test defaults + assert.equal(circle.draggable(), false); + + //change properties + circle.setDraggable(true); + + //circle.on('click', function(){}); + + layer.draw(); + + // test new properties + assert.equal(circle.draggable(), true); + + done(); + }, 50); + }); + + // ====================================================== + it('multiple drag and drop sets with setDraggable()', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: 380, + y: stage.height() / 2, + radius: 70, + strokeWidth: 4, + fill: 'red', + stroke: 'black', + }); + + circle.setDraggable(true); + assert.equal(circle.draggable(), true); + circle.setDraggable(true); + assert.equal(circle.draggable(), true); + circle.setDraggable(false); + assert.equal(!circle.draggable(), true); + + layer.add(circle); + stage.add(layer); + }); + + // ====================================================== + it('right click is not for dragging', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + draggable: true, + }); + + layer.add(circle); + stage.add(layer); + + simulateMouseDown(stage, { + x: 291, + y: 112, + }); + + simulateMouseMove(stage, { + x: 311, + y: 112, + }); + + assert(circle.isDragging(), 'dragging is ok'); + + simulateMouseUp(stage, { + x: 291, + y: 112, + }); + + assert(!circle.isDragging(), 'drag stopped'); + + simulateMouseDown(stage, { + x: 291, + y: 112, + button: 2, + }); + + simulateMouseMove(stage, { + x: 311, + y: 112, + button: 2, + }); + + assert(circle.isDragging() === false, 'no dragging with right click'); + + Konva.dragButtons = [0, 2]; + simulateMouseUp(stage, { + x: 291, + y: 112, + button: 2, + }); + + // simulate buttons change + simulateMouseDown(stage, { + x: 291, + y: 112, + button: 2, + }); + + simulateMouseMove(stage, { + x: 311, + y: 112, + button: 2, + }); + + assert(circle.isDragging() === true, 'now dragging with right click'); + + simulateMouseUp(stage, { + x: 291, + y: 112, + button: 2, + }); + Konva.dragButtons = [0]; + }); + + // ====================================================== + it('changing draggable on mousedown should take effect', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + }); + + layer.add(circle); + stage.add(layer); + + circle.on('mousedown', () => { + circle.draggable(true); + }); + + simulateMouseDown(stage, { + x: circle.x(), + y: circle.y(), + }); + + simulateMouseMove(stage, { + x: circle.x() + 10, + y: circle.y() + 10, + }); + + assert.equal(circle.isDragging(), true); + + simulateMouseUp(stage, { + x: circle.x() + 10, + y: circle.y() + 10, + }); + }); + + // ====================================================== + it('while dragging do not draw hit', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + stage.add(layer); + + var dragLayer = new Konva.Layer(); + stage.add(dragLayer); + + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + draggable: true, + }); + + dragLayer.add(circle); + dragLayer.draw(); + + var rect = new Konva.Rect({ + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + width: 50, + height: 50, + draggable: true, + }); + layer.add(rect); + layer.draw(); + + var shape = layer.getIntersection({ + x: 2, + y: 2, + }); + + assert.equal(shape, rect, 'rect is detected'); + + simulateMouseDown(stage, { + x: stage.width() / 2, + y: stage.height() / 2, + }); + + simulateMouseMove(stage, { + x: stage.width() / 2 + 5, + y: stage.height() / 2, + }); + + // redraw layer. hit must be not touched for not dragging layer + layer.draw(); + shape = layer.getIntersection({ + x: 2, + y: 2, + }); + assert.equal(shape, rect, 'rect is still detected'); + + assert(circle.isDragging(), 'dragging is ok'); + + dragLayer.draw(); + shape = dragLayer.getIntersection({ + x: stage.width() / 2, + y: stage.height() / 2, + }); + // as dragLayer under dragging we should not able to detect intersect + assert.equal(!!shape, false, 'circle is not detected'); + + simulateMouseUp(stage, { + x: 291, + y: 112, + }); + }); + + // ====================================================== + it('it is possible to change layer while dragging', function () { + var stage = addStage(); + + var startDragLayer = new Konva.Layer(); + stage.add(startDragLayer); + + var endDragLayer = new Konva.Layer(); + stage.add(endDragLayer); + + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + draggable: true, + }); + + startDragLayer.add(circle); + startDragLayer.draw(); + + var rect = new Konva.Rect({ + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + width: 50, + height: 50, + draggable: true, + }); + endDragLayer.add(rect); + endDragLayer.draw(); + + simulateMouseDown(stage, { + x: stage.width() / 2, + y: stage.height() / 2, + }); + + simulateMouseMove(stage, { + x: stage.width() / 2 + 5, + y: stage.height() / 2, + }); + + // change layer while dragging circle + circle.moveTo(endDragLayer); + // move rectange for test hit update + rect.moveTo(startDragLayer); + startDragLayer.draw(); + + var shape = startDragLayer.getIntersection({ + x: 2, + y: 2, + }); + assert.equal(shape, rect, 'rect is detected'); + + assert(circle.isDragging(), 'dragging is ok'); + + endDragLayer.draw(); + shape = endDragLayer.getIntersection({ + x: stage.width() / 2, + y: stage.height() / 2, + }); + // as endDragLayer under dragging we should not able to detect intersect + assert.equal(!!shape, false, 'circle is not detected'); + + simulateMouseUp(stage, { + x: 291, + y: 112, + }); + }); + + // ====================================================== + it('removing parent of draggable node should not throw error', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + var circle = new Konva.Circle({ + x: 380, + y: stage.height() / 2, + radius: 70, + strokeWidth: 4, + fill: 'red', + stroke: 'black', + draggable: true, + }); + + layer.add(circle); + simulateMouseMove(stage, { + x: stage.width() / 2 + 5, + y: stage.height() / 2, + }); + + circle.startDrag(); + try { + layer.destroy(); + assert.equal(true, true, 'no error, that is very good'); + } catch (e) { + assert.equal(true, false, 'error happened'); + } + }); + + it('update hit on stage drag end', function (done) { + var stage = addStage(); + + stage.draggable(true); + + var layer = new Konva.Layer(); + stage.add(layer); + + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + }); + + layer.add(circle); + layer.draw(); + + simulateMouseDown(stage, { + x: stage.width() / 2, + y: stage.height() / 2, + }); + + simulateMouseMove(stage, { + x: stage.width() / 2 - 50, + y: stage.height() / 2, + }); + + setTimeout(function () { + assert.equal(stage.isDragging(), true); + simulateMouseUp(stage, { + x: stage.width() / 2 - 50, + y: stage.height() / 2, + }); + setTimeout(function () { + var shape = layer.getIntersection({ + x: stage.width() / 2 + 5, + y: stage.height() / 2, + }); + + assert.equal(shape, circle); + done(); + }, 100); + }, 50); + }); + + it('removing shape while drag and drop should no throw error', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + draggable: true, + }); + + layer.add(circle); + stage.add(layer); + + simulateMouseDown(stage, { + x: 291, + y: 112, + }); + + circle.remove(); + + simulateMouseMove(stage, { + x: 311, + y: 112, + }); + + simulateMouseUp(stage, { + x: 291, + y: 112, + button: 2, + }); + + assert(Konva.isDragging() === false); + }); + + it('destroying shape while drag and drop should no throw error', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + draggable: true, + }); + + layer.add(circle); + stage.add(layer); + + simulateMouseDown(stage, { + x: 291, + y: 112, + }); + + circle.destroy(); + + simulateMouseMove(stage, { + x: 311, + y: 112, + }); + + simulateMouseUp(stage, { + x: 291, + y: 112, + }); + + assert(Konva.isDragging() === false); + }); + + it('drag start should trigger before movement', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var circle = new Konva.Circle({ + x: 70, + y: 70, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + draggable: true, + }); + + layer.add(circle); + stage.add(layer); + + circle.on('dragstart', function () { + assert.equal(circle.x(), 70); + assert.equal(circle.y(), 70); + }); + + simulateMouseDown(stage, { + x: 70, + y: 70, + }); + + simulateMouseMove(stage, { + x: 100, + y: 100, + }); + + simulateMouseUp(stage, { + x: 100, + y: 100, + }); + }); + + it('drag with touch', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var circle = new Konva.Circle({ + x: 70, + y: 70, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + draggable: true, + }); + + layer.add(circle); + stage.add(layer); + + circle.on('dragstart', function () { + assert.equal(circle.x(), 70); + assert.equal(circle.y(), 70); + }); + + simulateTouchStart(stage, { + x: 70, + y: 70, + }); + + simulateTouchMove(stage, { + x: 100, + y: 100, + }); + + simulateTouchEnd(stage, { + x: 100, + y: 100, + }); + assert.equal(circle.x(), 100); + assert.equal(circle.y(), 100); + }); + + it('drag with multi-touch (second finger on empty space)', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var circle = new Konva.Circle({ + x: 70, + y: 70, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + draggable: true, + }); + + layer.add(circle); + stage.add(layer); + + circle.on('dragstart', function () { + assert.equal(circle.x(), 70); + assert.equal(circle.y(), 70); + }); + + simulateTouchStart(stage, [ + { + x: 70, + y: 70, + id: 0, + }, + { + x: 270, + y: 270, + id: 1, + }, + ]); + + simulateTouchMove(stage, [ + { + x: 100, + y: 100, + id: 0, + }, + { + x: 270, + y: 270, + id: 1, + }, + ]); + + simulateTouchEnd(stage, [ + { + x: 100, + y: 100, + id: 0, + }, + { + x: 270, + y: 270, + id: 1, + }, + ]); + assert.equal(circle.x(), 100); + assert.equal(circle.y(), 100); + }); + + it('drag with multi-touch (two shapes)', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var circle1 = new Konva.Circle({ + x: 70, + y: 70, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + draggable: true, + }); + layer.add(circle1); + + var circle2 = new Konva.Circle({ + x: 270, + y: 70, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + draggable: true, + }); + layer.add(circle2); + layer.draw(); + + var dragstart1 = 0; + var dragmove1 = 0; + circle1.on('dragstart', function () { + dragstart1 += 1; + }); + circle1.on('dragmove', function () { + dragmove1 += 1; + }); + + var dragstart2 = 0; + var dragmove2 = 0; + circle2.on('dragstart', function () { + dragstart2 += 1; + }); + + circle2.on('dragmove', function () { + dragmove2 += 1; + }); + + simulateTouchStart(stage, [ + { + x: 70, + y: 70, + id: 0, + }, + { + x: 270, + y: 70, + id: 1, + }, + ]); + + // move one finger + simulateTouchMove( + stage, + [ + { + x: 100, + y: 100, + id: 0, + }, + { + x: 270, + y: 70, + id: 1, + }, + ], + [ + { + x: 100, + y: 100, + id: 0, + }, + ] + ); + + assert.equal(dragstart1, 1); + assert.equal(circle1.isDragging(), true); + assert.equal(dragmove1, 1); + assert.equal(dragmove2, 0); + assert.equal(circle1.x(), 100); + assert.equal(circle1.y(), 100); + + // move second finger + simulateTouchMove(stage, [ + { + x: 100, + y: 100, + id: 0, + }, + { + x: 290, + y: 70, + id: 1, + }, + ]); + + assert.equal(dragstart2, 1); + assert.equal(circle2.isDragging(), true); + assert.equal(dragmove2, 1); + assert.equal(circle2.x(), 290); + assert.equal(circle2.y(), 70); + + // remove first finger + simulateTouchEnd( + stage, + [ + { + x: 290, + y: 70, + id: 1, + }, + ], + [ + { + x: 100, + y: 100, + id: 0, + }, + ] + ); + assert.equal(circle1.isDragging(), false); + assert.equal(circle2.isDragging(), true); + assert.equal(Konva.DD.isDragging, true); + // remove first finger + simulateTouchEnd( + stage, + [], + [ + { + x: 290, + y: 70, + id: 1, + }, + ] + ); + assert.equal(circle2.isDragging(), false); + assert.equal(Konva.DD.isDragging, false); + }); + + it('drag with multi-touch (same shape)', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var circle1 = new Konva.Circle({ + x: 70, + y: 70, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + draggable: true, + }); + layer.add(circle1); + layer.draw(); + + var dragstart1 = 0; + var dragmove1 = 0; + circle1.on('dragstart', function () { + dragstart1 += 1; + }); + circle1.on('dragmove', function () { + dragmove1 += 1; + }); + + simulateTouchStart(stage, [ + { + x: 70, + y: 70, + id: 0, + }, + ]); + // move one finger + simulateTouchMove(stage, [ + { + x: 75, + y: 75, + id: 0, + }, + ]); + + simulateTouchStart( + stage, + [ + { + x: 75, + y: 75, + id: 0, + }, + { + x: 80, + y: 80, + id: 1, + }, + ], + [ + { + x: 80, + y: 80, + id: 1, + }, + ] + ); + + simulateTouchMove( + stage, + [ + { + x: 75, + y: 75, + id: 0, + }, + { + x: 85, + y: 85, + id: 1, + }, + ], + [ + { + x: 85, + y: 85, + id: 1, + }, + ] + ); + + assert.equal(dragstart1, 1); + assert.equal(circle1.isDragging(), true); + assert.equal(dragmove1, 1); + assert.equal(circle1.x(), 75); + assert.equal(circle1.y(), 75); + + // remove first finger + simulateTouchEnd( + stage, + [], + [ + { + x: 75, + y: 75, + id: 0, + }, + { + x: 85, + y: 85, + id: 1, + }, + ] + ); + }); + + it('can stop drag on dragstart without changing position later', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var circle = new Konva.Circle({ + x: 70, + y: 70, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + draggable: true, + }); + + layer.add(circle); + stage.add(layer); + + circle.on('dragstart', function () { + circle.stopDrag(); + }); + circle.on('dragmove', function () { + assert.equal(false, true, 'dragmove called!'); + }); + + simulateMouseDown(stage, { + x: 70, + y: 70, + }); + + simulateMouseMove(stage, { + x: 100, + y: 100, + }); + + simulateMouseUp(stage, { + x: 100, + y: 100, + }); + + assert.equal(circle.x(), 70); + assert.equal(circle.y(), 70); + }); + + it('can force drag at any time (when pointer already registered)', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var circle = new Konva.Circle({ + x: 70, + y: 70, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + draggable: true, + }); + + layer.add(circle); + stage.add(layer); + + simulateMouseMove(stage, { x: 70, y: 70 }); + circle.startDrag(); + assert.equal(circle.isDragging(), true); + simulateMouseMove(stage, { x: 80, y: 80 }); + simulateMouseUp(stage, { x: 80, y: 80 }); + assert.equal(circle.x(), 80); + assert.equal(circle.y(), 80); + }); + + it('can force drag at any time (when pointer not registered)', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var circle = new Konva.Circle({ + x: 70, + y: 70, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + draggable: true, + }); + + layer.add(circle); + stage.add(layer); + + circle.startDrag(); + assert.equal(circle.isDragging(), true); + // let us think that offset will be equal 0 if not registered pointers + simulateMouseMove(stage, { x: 80, y: 80 }); + simulateMouseUp(stage, { x: 80, y: 80 }); + + assert.equal(circle.x(), 80); + assert.equal(circle.y(), 80); + }); + + it('calling startDrag show still fire event when required', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var circle = new Konva.Circle({ + x: 70, + y: 70, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + draggable: true, + }); + + var dragstart = 0; + circle.on('dragstart', function () { + dragstart += 1; + }); + layer.add(circle); + stage.add(layer); + + // register pointer + simulateMouseMove(stage, { x: 70, y: 80 }); + circle.startDrag(); + assert.equal(dragstart, 1); + assert.equal(circle.isDragging(), true); + // moving by one pixel should move circle too + simulateMouseMove(stage, { x: 70, y: 81 }); + simulateMouseUp(stage, { x: 70, y: 81 }); + + assert.equal(circle.x(), 70); + assert.equal(circle.y(), 71); + }); + + it('make sure we have event object', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var circle = new Konva.Circle({ + x: 70, + y: 70, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + draggable: true, + }); + + circle.on('dragstart', function (e) { + assert.equal(e.evt === undefined, false); + }); + circle.on('dragmove', function (e) { + assert.equal(e.evt === undefined, false); + }); + circle.on('dragend', function (e) { + assert.equal(e.evt === undefined, false); + }); + layer.add(circle); + stage.add(layer); + + // register pointer + simulateMouseDown(stage, { x: 70, y: 80 }); + // moving by one pixel should move circle too + simulateMouseMove(stage, { x: 80, y: 80 }); + simulateMouseUp(stage, { x: 70, y: 80 }); + }); + + it('try nested dragging', function () { + var stage = addStage(); + var layer = new Konva.Layer({ + draggable: true, + }); + + var circle = new Konva.Circle({ + x: 70, + y: 70, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + draggable: true, + }); + + layer.add(circle); + stage.add(layer); + + layer.add(circle.clone({ x: 30, fill: 'red', draggable: false })); + + simulateMouseDown(stage, { x: 70, y: 70 }); + simulateMouseMove(stage, { x: 80, y: 80 }); + + assert.equal(circle.x(), 80); + assert.equal(circle.y(), 80); + assert.equal(layer.x(), 0); + assert.equal(layer.y(), 0); + // layer is not dragging, because drag is registered on circle + assert.equal(layer.isDragging(), false); + simulateMouseUp(stage, { x: 80, y: 80 }); + }); + + it('warn on bad dragBoundFunc', function () { + var stage = addStage(); + var layer = new Konva.Layer({ + draggable: true, + }); + + var circle = new Konva.Circle({ + x: 70, + y: 70, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + draggable: true, + dragBoundFunc: function () {} as any, + }); + + layer.add(circle); + stage.add(layer); + + var counter = 0; + var oldWarn = Konva.Util.warn; + Konva.Util.warn = function () { + counter += 1; + }; + simulateMouseDown(stage, { x: 70, y: 70 }); + simulateMouseMove(stage, { x: 80, y: 80 }); + simulateMouseUp(stage, { x: 80, y: 80 }); + assert.equal(counter > 0, true); + Konva.Util.warn = oldWarn; + }); + + it('deletage drag', function () { + var stage = addStage(); + stage.draggable(true); + var layer = new Konva.Layer(); + + var circle = new Konva.Circle({ + x: 70, + y: 70, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + }); + + layer.add(circle); + stage.add(layer); + + stage.on('dragstart', function (e) { + if (e.target === stage) { + stage.stopDrag(); + circle.startDrag(); + } + }); + + simulateMouseDown(stage, { x: 5, y: 5 }); + simulateMouseMove(stage, { x: 10, y: 10 }); + + assert.equal(circle.isDragging(), true); + assert.equal(stage.isDragging(), false); + + simulateMouseUp(stage, { x: 10, y: 10 }); + + assert.equal(circle.x(), 70); + assert.equal(circle.y(), 70); + }); + + it('disable drag on click', function () { + var stage = addStage(); + stage.draggable(true); + var layer = new Konva.Layer(); + stage.add(layer); + + var circle = new Konva.Circle({ + x: 70, + y: 70, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + draggable: true, + }); + layer.add(circle); + layer.draw(); + + circle.on('click', function () { + circle.draggable(false); + circle.draggable(true); + }); + + var dragstart = 0; + var dragend = 0; + + stage.on('dragstart', function (e) { + dragstart += 1; + }); + stage.on('dragend', function (e) { + dragend += 1; + }); + + simulateMouseDown(stage, { x: 70, y: 75 }); + simulateMouseUp(stage, { x: 70, y: 70 }); + + // drag events should not be called + assert.equal(dragstart, 0); + assert.equal(dragend, 0); + }); + + it('mouseleave should not occur after drag and drop', function (done) { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: 40, + y: 40, + radius: 20, + strokeWidth: 4, + fill: 'green', + stroke: 'black', + draggable: true, + }); + layer.add(circle); + stage.add(layer); + var mouseleave = false; + var dragend = false; + var mouseenter = true; + circle.on('mouseenter', function () { + mouseenter = true; + }); + circle.on('mouseleave', function () { + mouseleave = true; + }); + circle.on('dragend', function () { + dragend = true; + }); + simulateMouseMove(stage, { + x: 40, + y: 40, + }); + assert(mouseenter, 'mouseenter event should have been fired'); + simulateMouseDown(stage, { + x: 40, + y: 40, + }); + setTimeout(function () { + simulateMouseMove(stage, { + x: 100, + y: 100, + }); + setTimeout(() => { + simulateMouseUp(stage, { + x: 100, + y: 100, + }); + simulateMouseMove(stage, { + x: 102, + y: 102, + }); + assert(!mouseleave, 'mouseleave event should not have been fired'); + assert(dragend, 'dragend event should have been fired'); + done(); + }, 70); + }, 70); + }); +}); diff --git a/test/unit/DragAndDropEvents-test.ts b/test/unit/DragAndDropEvents-test.ts new file mode 100644 index 000000000..2eb0474d6 --- /dev/null +++ b/test/unit/DragAndDropEvents-test.ts @@ -0,0 +1,662 @@ +import { assert } from 'chai'; + +import { + addStage, + Konva, + simulateMouseDown, + simulateMouseMove, + simulateMouseUp, + simulatePointerDown, + simulatePointerMove, + simulatePointerUp, +} from './test-utils'; + +describe('DragAndDropEvents', function () { + // ====================================================== + it('test dragstart, dragmove, dragend', function (done) { + var stage = addStage(); + + var layer = new Konva.Layer(); + + var greenCircle = new Konva.Circle({ + x: 40, + y: 40, + radius: 20, + strokeWidth: 4, + fill: 'green', + stroke: 'black', + opacity: 0.5, + }); + + var circle = new Konva.Circle({ + x: 380, + y: stage.height() / 2, + radius: 70, + strokeWidth: 4, + fill: 'red', + stroke: 'black', + opacity: 0.5, + }); + + circle.setDraggable(true); + + layer.add(circle); + layer.add(greenCircle); + stage.add(layer); + + var dragStart = false; + var dragMove = false; + var dragEnd = false; + var events = []; + + circle.on('dragstart', function () { + dragStart = true; + }); + + circle.on('dragmove', function () { + dragMove = true; + }); + + circle.on('dragend', function () { + dragEnd = true; + events.push('dragend'); + }); + + circle.on('mouseup', function () { + events.push('mouseup'); + }); + + assert(!Konva.isDragging(), ' isDragging() should be false 1'); + assert(!Konva.isDragReady(), ' isDragReady()) should be false 2'); + + /* + * simulate drag and drop + */ + simulateMouseDown(stage, { + x: 380, + y: 98, + }); + assert(!dragStart, 'dragstart event should not have been triggered 3'); + //assert.equal(!dragMove, 'dragmove event should not have been triggered'); + assert(!dragEnd, 'dragend event should not have been triggered 4'); + + assert(!Konva.isDragging(), ' isDragging() should be false 5'); + assert(Konva.isDragReady(), ' isDragReady()) should be true 6'); + + setTimeout(function () { + simulateMouseMove(stage, { + x: 385, + y: 98, + }); + + assert(Konva.isDragging(), ' isDragging() should be true 7'); + assert(Konva.isDragReady(), ' isDragReady()) should be true 8'); + + assert(dragStart, 'dragstart event was not triggered 9'); + //assert.equal(dragMove, 'dragmove event was not triggered'); + assert(!dragEnd, 'dragend event should not have been triggered 10'); + + simulateMouseUp(stage, { + x: 385, + y: 98, + }); + + assert(dragStart, 'dragstart event was not triggered 11'); + assert(dragMove, 'dragmove event was not triggered 12'); + assert(dragEnd, 'dragend event was not triggered 13'); + + assert.equal( + events.toString(), + 'mouseup,dragend', + 'mouseup should occur before dragend 14' + ); + + assert(!Konva.isDragging(), ' isDragging() should be false 15'); + assert(!Konva.isDragReady(), ' isDragReady()) should be false 16'); + + //console.log(greenCircle.getPosition()); + //console.log(circle.getPosition()); + + assert.equal(greenCircle.x(), 40, 'green circle x should be 40'); + assert.equal(greenCircle.y(), 40, 'green circle y should be 40'); + assert.equal(circle.x(), 385, 'circle x should be 100'); + assert.equal(circle.y(), 100, 'circle y should be 100'); + + done(); + }, 20); + }); + + // ====================================================== + it('destroy shape while dragging', function (done) { + var stage = addStage(); + var layer = new Konva.Layer(); + + var greenCircle = new Konva.Circle({ + x: 40, + y: 40, + radius: 20, + strokeWidth: 4, + fill: 'green', + stroke: 'black', + opacity: 0.5, + }); + + var circle = new Konva.Circle({ + x: 380, + y: stage.height() / 2, + radius: 70, + strokeWidth: 4, + fill: 'red', + stroke: 'black', + opacity: 0.5, + }); + + circle.setDraggable(true); + + layer.add(circle); + layer.add(greenCircle); + stage.add(layer); + + var dragEnd = false; + + circle.on('dragend', function () { + dragEnd = true; + }); + + assert(!Konva.isDragging(), ' isDragging() should be false'); + assert(!Konva.isDragReady(), ' isDragReady()) should be false'); + + simulateMouseDown(stage, { + x: 380, + y: 98, + }); + + assert(!circle.isDragging(), 'circle should not be dragging'); + + setTimeout(function () { + simulateMouseMove(stage, { + x: 100, + y: 98, + }); + + assert(circle.isDragging(), 'circle should be dragging'); + assert(!dragEnd, 'dragEnd should not have fired yet'); + + // at this point, we are in drag and drop mode + + // removing or destroying the circle should trigger dragend + circle.destroy(); + layer.draw(); + + assert( + !circle.isDragging(), + 'destroying circle should stop drag and drop' + ); + assert(dragEnd, 'dragEnd should have fired'); + done(); + }, 20); + }); + + // ====================================================== + it('click should not occur after drag and drop', function (done) { + var stage = addStage(); + var layer = new Konva.Layer(); + + var circle = new Konva.Circle({ + x: 40, + y: 40, + radius: 20, + strokeWidth: 4, + fill: 'green', + stroke: 'black', + draggable: true, + }); + + layer.add(circle); + stage.add(layer); + + var clicked = false; + + circle.on('click', function () { + //console.log('click'); + clicked = true; + }); + + simulateMouseDown(stage, { + x: 40, + y: 40, + }); + + setTimeout(function () { + simulateMouseMove(stage, { + x: 100, + y: 100, + }); + + simulateMouseUp(stage, { + x: 100, + y: 100, + }); + + assert(!clicked, 'click event should not have been fired'); + + done(); + }, 20); + }); + + // TODO: how to solve it? + // hint: every shape has pointerId that indicates which pointer is dragging it + // but "pointer" event and mouse event has different pointerId + // so we need to find a way to match them + // should we save several pointers per shape? + // doesn't sound good + // switch to pointer only event handling? + it.skip('click should not occur after drag and drop', function (done) { + var stage = addStage(); + var layer = new Konva.Layer(); + + var circle = new Konva.Circle({ + x: 40, + y: 40, + radius: 20, + strokeWidth: 4, + fill: 'green', + stroke: 'black', + draggable: true, + }); + + layer.add(circle); + stage.add(layer); + + var clicked = false; + + stage.on('pointerclick', function () { + clicked = true; + }); + + simulatePointerDown(stage, { + x: 40, + y: 40, + }); + + setTimeout(function () { + simulatePointerMove(stage, { + x: 100, + y: 100, + }); + + simulatePointerUp(stage, { + x: 100, + y: 100, + }); + + assert(!clicked, 'click event should not have been fired'); + + done(); + }, 20); + }); + + // ====================================================== + it('drag and drop distance', function (done) { + var stage = addStage(); + var layer = new Konva.Layer(); + + var circle = new Konva.Circle({ + x: 40, + y: 40, + radius: 20, + strokeWidth: 4, + fill: 'green', + stroke: 'black', + draggable: true, + }); + + layer.add(circle); + stage.add(layer); + circle.dragDistance(4); + + simulateMouseDown(stage, { + x: 40, + y: 40, + }); + + setTimeout(function () { + simulateMouseMove(stage, { + x: 40, + y: 42, + }); + assert(!circle.isDragging(), 'still not dragging'); + simulateMouseMove(stage, { + x: 40, + y: 45, + }); + assert(circle.isDragging(), 'now circle is dragging'); + simulateMouseUp(stage, { + x: 41, + y: 45, + }); + + done(); + }, 20); + }); + + // ====================================================== + it('cancel drag and drop by setting draggable to false', function (done) { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: 380, + y: 100, + radius: 70, + strokeWidth: 4, + fill: 'red', + stroke: 'black', + draggable: true, + }); + + var dragStart = false; + var dragMove = false; + var dragEnd = false; + + circle.on('dragstart', function () { + dragStart = true; + }); + + circle.on('dragmove', function () { + dragMove = true; + }); + + circle.on('dragend', function () { + dragEnd = true; + }); + + circle.on('mousedown', function () { + circle.setDraggable(false); + }); + + layer.add(circle); + stage.add(layer); + + /* + * simulate drag and drop + */ + simulateMouseDown(stage, { + x: 380, + y: 100, + }); + + setTimeout(function () { + simulateMouseMove(stage, { + x: 100, + y: 100, + }); + + simulateMouseUp(stage, { + x: 100, + y: 100, + }); + + assert.equal(circle.getPosition().x, 380, 'circle x should be 380'); + assert.equal(circle.getPosition().y, 100, 'circle y should be 100'); + done(); + }, 20); + }); + + // ====================================================== + it('drag and drop layer', function (done) { + var stage = addStage(); + var layer = new Konva.Layer({ + sceneFunc: function () { + var context = this.getContext(); + context.beginPath(); + context.moveTo(200, 50); + context.lineTo(420, 80); + context.quadraticCurveTo(300, 100, 260, 170); + context.closePath(); + context.fillStyle = 'blue'; + context.fill(context); + }, + draggable: true, + }); + + var circle1 = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'red', + }); + + var circle2 = new Konva.Circle({ + x: 400, + y: stage.height() / 2, + radius: 70, + fill: 'green', + }); + + layer.add(circle1); + layer.add(circle2); + + stage.add(layer); + + /* + * simulate drag and drop + */ + simulateMouseDown(stage, { + x: 399, + y: 96, + }); + + setTimeout(function () { + simulateMouseMove(stage, { + x: 210, + y: 109, + }); + + simulateMouseUp(stage, { + x: 210, + y: 109, + }); + + //console.log(layer.getPosition()) + + assert.equal(layer.x(), -189, 'layer x should be -189'); + assert.equal(layer.y(), 13, 'layer y should be 13'); + + done(); + }, 20); + }); + + // ====================================================== + it('drag and drop stage', function (done) { + var stage = addStage({ draggable: true }); + + //stage.setDraggable(true); + + var layer = new Konva.Layer(); + + var circle = new Konva.Circle({ + x: 100, + y: 100, + radius: 70, + fill: 'red', + }); + + layer.add(circle); + stage.add(layer); + + assert.equal(stage.x(), 0); + assert.equal(stage.y(), 0); + + /* + * simulate drag and drop + */ + simulateMouseDown(stage, { + x: 0, + y: 100, + }); + + setTimeout(function () { + simulateMouseMove(stage, { + x: 300, + y: 110, + }); + + simulateMouseUp(stage, { + x: 300, + y: 110, + }); + + assert.equal(stage.x(), 300); + assert.equal(stage.y(), 10); + + done(); + }, 20); + }); + + it('click should not start drag&drop', function () { + var stage = addStage(); + var layer = new Konva.Layer({ + draggable: true, + }); + + var circle = new Konva.Circle({ + x: 70, + y: 70, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + draggable: true, + }); + + layer.add(circle); + stage.add(layer); + + var dragstart = 0; + circle.on('dragstart', function () { + dragstart += 1; + }); + + var dragmove = 0; + circle.on('dragmove', function () { + dragmove += 1; + }); + + var dragend = 0; + circle.on('dragend', function () { + dragend += 1; + }); + + var click = 0; + circle.on('click', function () { + click += 1; + }); + simulateMouseDown(stage, { x: 70, y: 70 }); + simulateMouseUp(stage, { x: 70, y: 70 }); + + assert.equal(click, 1, 'click triggered'); + assert.equal(dragstart, 0, 'dragstart not triggered'); + assert.equal(dragmove, 0, 'dragmove not triggered'); + assert.equal(dragend, 0, 'dragend not triggered'); + }); + + it('drag&drop should not fire click', function () { + var stage = addStage(); + var layer = new Konva.Layer({ + draggable: true, + }); + + var circle = new Konva.Circle({ + x: 70, + y: 70, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + draggable: true, + }); + + layer.add(circle); + stage.add(layer); + + var dragstart = 0; + circle.on('dragstart', function () { + dragstart += 1; + }); + + var dragmove = 0; + circle.on('dragmove', function () { + dragmove += 1; + }); + + var dragend = 0; + circle.on('dragend', function () { + dragend += 1; + }); + + var click = 0; + circle.on('click', function () { + click += 1; + }); + simulateMouseDown(stage, { x: 70, y: 70 }); + simulateMouseMove(stage, { x: 80, y: 80 }); + simulateMouseUp(stage, { x: 80, y: 80 }); + + assert.equal(click, 0, 'click triggered'); + assert.equal(dragstart, 1, 'dragstart not triggered'); + assert.equal(dragmove, 1, 'dragmove not triggered'); + assert.equal(dragend, 1, 'dragend not triggered'); + }); + + it('drag events should not trigger on a click even if we stop drag on dragstart', function () { + var stage = addStage(); + var layer = new Konva.Layer({ + draggable: true, + }); + + var circle = new Konva.Circle({ + x: 70, + y: 70, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + draggable: true, + }); + + layer.add(circle); + stage.add(layer); + + var dragstart = 0; + circle.on('dragstart', function () { + circle.stopDrag(); + dragstart += 1; + }); + + var dragmove = 0; + circle.on('dragmove', function () { + dragmove += 1; + }); + + var dragend = 0; + circle.on('dragend', function () { + dragend += 1; + }); + + var click = 0; + circle.on('click', function () { + click += 1; + }); + simulateMouseDown(stage, { x: 70, y: 70 }); + simulateMouseMove(stage, { x: 75, y: 75 }); + simulateMouseUp(stage, { x: 75, y: 75 }); + + assert.equal(click, 0, 'click triggered'); + assert.equal(dragstart, 1, 'dragstart triggered'); + assert.equal(dragmove, 0, 'dragmove not triggered'); + assert.equal(dragend, 1, 'dragend triggered'); + }); +}); diff --git a/test/unit/Ellipse-test.ts b/test/unit/Ellipse-test.ts new file mode 100644 index 000000000..2411dc50e --- /dev/null +++ b/test/unit/Ellipse-test.ts @@ -0,0 +1,116 @@ +import { assert } from 'chai'; + +import { + addStage, + Konva, + createCanvas, + compareLayerAndCanvas, +} from './test-utils'; + +describe('Ellipse', function () { + // ====================================================== + it('add ellipse', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var ellipse = new Konva.Ellipse({ + x: stage.width() / 2, + y: stage.height() / 2, + radiusX: 70, + radiusY: 35, + fill: 'green', + stroke: 'black', + strokeWidth: 8, + }); + layer.add(ellipse); + stage.add(layer); + assert.equal(ellipse.getClassName(), 'Ellipse'); + + var trace = layer.getContext().getTrace(); + assert.equal( + trace, + 'clearRect(0,0,578,200);save();transform(1,0,0,1,289,100);beginPath();save();scale(1,0.5);arc(0,0,70,0,6.283,false);restore();closePath();fillStyle=green;fill();lineWidth=8;strokeStyle=black;stroke();restore();' + ); + }); + + // ====================================================== + it('attrs sync', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var ellipse = new Konva.Ellipse({ + x: stage.width() / 2, + y: stage.height() / 2, + radiusX: 70, + radiusY: 35, + fill: 'green', + stroke: 'black', + strokeWidth: 8, + }); + layer.add(ellipse); + stage.add(layer); + + assert.equal(ellipse.getWidth(), 140); + assert.equal(ellipse.getHeight(), 70); + + ellipse.setWidth(100); + assert.equal(ellipse.radiusX(), 50); + assert.equal(ellipse.radiusY(), 35); + + ellipse.setHeight(120); + assert.equal(ellipse.radiusX(), 50); + assert.equal(ellipse.radiusY(), 60); + }); + + it('getSelfRect', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var ellipse = new Konva.Ellipse({ + x: stage.width() / 2, + y: stage.height() / 2, + radiusX: 70, + radiusY: 35, + fill: 'green', + stroke: 'black', + strokeWidth: 8, + }); + layer.add(ellipse); + stage.add(layer); + + assert.deepEqual(ellipse.getSelfRect(), { + x: -70, + y: -35, + width: 140, + height: 70, + }); + }); + + it('cache', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var ellipse = new Konva.Ellipse({ + x: stage.width() / 2, + y: stage.height() / 2, + radiusX: 70, + radiusY: 35, + fill: 'green', + stroke: 'black', + strokeWidth: 8, + }); + ellipse.cache(); + layer.add(ellipse); + stage.add(layer); + + var canvas = createCanvas(); + var context = canvas.getContext('2d'); + context.save(); + context.beginPath(); + context.scale(1, 0.5); + context.arc(stage.width() / 2, stage.height(), 70, 0, Math.PI * 2, false); + context.closePath(); + context.restore(); + context.fillStyle = 'green'; + context.fill(); + context.lineWidth = 8; + context.stroke(); + compareLayerAndCanvas(layer, canvas, 150); + }); +}); diff --git a/test/unit/Global-test.ts b/test/unit/Global-test.ts new file mode 100644 index 000000000..551542e40 --- /dev/null +++ b/test/unit/Global-test.ts @@ -0,0 +1,22 @@ +import { assert } from 'chai'; +import { Konva } from './test-utils'; + +describe('Global', function () { + // ====================================================== + it('test Konva version number', function () { + assert.equal(!!Konva.version, true); + }); + + // ====================================================== + it('getAngle()', function () { + // test that default angleDeg is true + assert.equal(Konva.angleDeg, true); + assert.equal(Konva.getAngle(180), Math.PI); + + Konva.angleDeg = false; + assert.equal(Konva.getAngle(1), 1); + + // set angleDeg back to true for future tests + Konva.angleDeg = true; + }); +}); diff --git a/test/unit/Group-test.ts b/test/unit/Group-test.ts new file mode 100644 index 000000000..c2ec769ff --- /dev/null +++ b/test/unit/Group-test.ts @@ -0,0 +1,118 @@ +import { addStage, cloneAndCompareLayer, Konva } from './test-utils'; +import { assert } from 'chai'; + +describe('Group', function () { + // ====================================================== + it('cache group with text', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + var group = new Konva.Group({ + draggable: true, + x: 50, + y: 40, + }); + var text = new Konva.Text({ + text: 'some text', + fontSize: 20, + fill: 'black', + y: 50, + }); + + var rect = new Konva.Rect({ + height: 100, + width: 100, + stroke: 'black', + strokeWidth: 10, + // cornerRadius: 1, + }); + group.add(text); + group.add(rect); + layer.add(group); + + stage.add(layer); + + group + .cache({ + x: -15, + y: -15, + width: 150, + height: 150, + }) + .offsetX(5) + .offsetY(5); + + layer.draw(); + + cloneAndCompareLayer(layer, 200); + }); + + it('clip group with a Path2D', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + + var path = new Konva.Group({ + width: 100, + height: 100, + clipFunc: () => [new Path2D('M0 0v50h50Z')], + }); + + layer.add(path); + stage.add(layer); + + const trace = layer.getContext().getTrace(); + + assert.equal( + trace, + 'clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);beginPath();clip([object Path2D]);transform(1,0,0,1,0,0);restore();' + ); + }); + + it('clip group with by zero size', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + + var group = new Konva.Group({ + width: 100, + height: 100, + clipWidth: 0, + clipHeight: 0, + }); + + layer.add(group); + stage.add(layer); + + const trace = layer.getContext().getTrace(); + + console.log(trace); + + assert.equal( + trace, + 'clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);beginPath();rect(0,0,0,0);clip();transform(1,0,0,1,0,0);restore();' + ); + }); + + it('clip group with a Path2D and clipRule', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + + var path = new Konva.Group({ + width: 100, + height: 100, + clipFunc: () => [new Path2D('M0 0v50h50Z'), 'evenodd'], + }); + + layer.add(path); + stage.add(layer); + + const trace = layer.getContext().getTrace(); + + assert.equal( + trace, + 'clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);beginPath();clip([object Path2D],evenodd);transform(1,0,0,1,0,0);restore();' + ); + }); +}); diff --git a/test/unit/Image-test.ts b/test/unit/Image-test.ts new file mode 100644 index 000000000..be9c95168 --- /dev/null +++ b/test/unit/Image-test.ts @@ -0,0 +1,451 @@ +import { assert } from 'chai'; + +import { + addStage, + Konva, + createCanvas, + compareLayerAndCanvas, + loadImage, + isNode, + isBrowser, +} from './test-utils'; + +describe('Image', function () { + // ====================================================== + it('add image', function (done) { + loadImage('darth-vader.jpg', (imageObj) => { + var stage = addStage(); + + var layer = new Konva.Layer(); + var darth = new Konva.Image({ + x: 200, + y: 60, + image: imageObj, + width: 100, + height: 100, + offset: { x: 50, y: 30 }, + crop: { x: 135, y: 7, width: 167, height: 134 }, + draggable: true, + }); + + layer.add(darth); + stage.add(layer); + + darth.width(200); + layer.draw(); + + darth.width(100); + layer.draw(); + + assert.equal(darth.x(), 200); + assert.equal(darth.y(), 60); + assert.equal(darth.getWidth(), 100); + assert.equal(darth.getHeight(), 100); + assert.equal(darth.offset().x, 50); + assert.equal(darth.offset().y, 30); + + var crop = darth.crop(); + + assert.equal(crop.x, 135); + assert.equal(crop.y, 7); + assert.equal(crop.width, 167); + assert.equal(crop.height, 134); + + darth.crop({ + x: 8, + y: 9, + width: 10, + height: 11, + }); + crop = darth.crop(); + assert.equal(crop.x, 8); + assert.equal(crop.y, 9); + assert.equal(crop.width, 10); + assert.equal(crop.height, 11); + + darth.cropX(12); + crop = darth.crop(); + assert.equal(crop.x, 12); + assert.equal(crop.y, 9); + assert.equal(crop.width, 10); + assert.equal(crop.height, 11); + + darth.cropY(13); + crop = darth.crop(); + assert.equal(crop.x, 12); + assert.equal(crop.y, 13); + assert.equal(crop.width, 10); + assert.equal(crop.height, 11); + + darth.cropWidth(14); + crop = darth.crop(); + assert.equal(crop.x, 12); + assert.equal(crop.y, 13); + assert.equal(crop.width, 14); + assert.equal(crop.height, 11); + + darth.cropHeight(15); + crop = darth.crop(); + assert.equal(crop.x, 12); + assert.equal(crop.y, 13); + assert.equal(crop.width, 14); + assert.equal(crop.height, 15); + + darth.setAttrs({ + x: 200, + y: 60, + image: imageObj, + width: 100, + height: 100, + offsetX: 50, + offsetY: 30, + crop: { x: 135, y: 7, width: 167, height: 134 }, + draggable: true, + }); + + assert.equal(darth.getClassName(), 'Image'); + + var trace = layer.getContext().getTrace(); + + if (isBrowser) { + assert.equal( + trace, + 'clearRect(0,0,578,200);save();transform(1,0,0,1,150,30);drawImage([object HTMLImageElement],135,7,167,134,0,0,100,100);restore();clearRect(0,0,578,200);save();transform(1,0,0,1,150,30);drawImage([object HTMLImageElement],135,7,167,134,0,0,200,100);restore();clearRect(0,0,578,200);save();transform(1,0,0,1,150,30);drawImage([object HTMLImageElement],135,7,167,134,0,0,100,100);restore();' + ); + } else { + assert.equal( + trace, + 'clearRect(0,0,578,200);save();transform(1,0,0,1,150,30);drawImage([object Object],135,7,167,134,0,0,100,100);restore();clearRect(0,0,578,200);save();transform(1,0,0,1,150,30);drawImage([object Object],135,7,167,134,0,0,200,100);restore();clearRect(0,0,578,200);save();transform(1,0,0,1,150,30);drawImage([object Object],135,7,167,134,0,0,100,100);restore();' + ); + } + + done(); + }); + }); + + // ====================================================== + it('try image will fill pattern', function (done) { + loadImage('darth-vader.jpg', (imageObj) => { + loadImage('lion.png', (lion) => { + var stage = addStage(); + + var layer = new Konva.Layer(); + var darth = new Konva.Image({ + x: 20, + y: 20, + image: lion, + draggable: true, + fillPatternImage: imageObj, + fillPatternRepeat: 'no-repeat', + fillPatternX: 50, + }); + + layer.add(darth); + stage.add(layer); + + assert.equal( + layer.getContext().getTrace(true), + 'clearRect();save();transform();beginPath();rect();closePath();fillStyle;fill();drawImage();restore();' + ); + + done(); + }); + }); + }); + + // ====================================================== + it('crop and scale image', function (done) { + loadImage('darth-vader.jpg', (imageObj) => { + var stage = addStage(); + var layer = new Konva.Layer(); + var darth = new Konva.Image({ + x: 200, + y: 75, + image: imageObj, + width: 107, + height: 75, + crop: { x: 186, y: 211, width: 106, height: 74 }, + draggable: true, + scale: { x: 0.5, y: 0.5 }, + cornerRadius: 15, + }); + + layer.add(darth); + stage.add(layer); + + assert.equal(darth.crop().x, 186); + assert.equal(darth.crop().y, 211); + assert.equal(darth.crop().width, 106); + assert.equal(darth.crop().height, 74); + + assert.equal(darth.cropX(), 186); + assert.equal(darth.cropY(), 211); + assert.equal(darth.cropWidth(), 106); + assert.equal(darth.cropHeight(), 74); + + darth.crop({ x: 1, y: 2, width: 3, height: 4 }); + + assert.equal(darth.crop().x, 1); + assert.equal(darth.crop().y, 2); + assert.equal(darth.crop().width, 3); + assert.equal(darth.crop().height, 4); + + assert.equal(darth.cropX(), 1); + assert.equal(darth.cropY(), 2); + assert.equal(darth.cropWidth(), 3); + assert.equal(darth.cropHeight(), 4); + + darth.cropX(5); + darth.cropY(6); + darth.cropWidth(7); + darth.cropHeight(8); + + assert.equal(darth.crop().x, 5); + assert.equal(darth.crop().y, 6); + assert.equal(darth.crop().width, 7); + assert.equal(darth.crop().height, 8); + + assert.equal(darth.cropX(), 5); + assert.equal(darth.cropY(), 6); + assert.equal(darth.cropWidth(), 7); + assert.equal(darth.cropHeight(), 8); + assert.equal(darth.cornerRadius(), 15); + + done(); + }); + }); + + // ====================================================== + it('image with opacity and shadow', function (done) { + loadImage('darth-vader.jpg', (imageObj) => { + var stage = addStage(); + + var layer = new Konva.Layer(); + var darth = new Konva.Image({ + x: 200, + y: 60, + image: imageObj, + width: 100, + height: 100, + offset: { x: 50, y: 30 }, + draggable: true, + opacity: 0.5, + shadowColor: 'black', + shadowBlur: 10, + shadowOpacity: 0.1, + shadowOffset: { x: 20, y: 20 }, + }); + + layer.add(darth); + stage.add(layer); + + var trace = layer.getContext().getTrace(); + + if (isBrowser) { + assert.equal( + trace, + 'clearRect(0,0,578,200);save();transform(1,0,0,1,150,30);globalAlpha=0.5;shadowColor=rgba(0,0,0,0.1);shadowBlur=10;shadowOffsetX=20;shadowOffsetY=20;drawImage([object HTMLImageElement],0,0,100,100);restore();' + ); + } else { + assert.equal( + trace, + 'clearRect(0,0,578,200);save();transform(1,0,0,1,150,30);globalAlpha=0.5;shadowColor=rgba(0,0,0,0.1);shadowBlur=10;shadowOffsetX=20;shadowOffsetY=20;drawImage([object Object],0,0,100,100);restore();' + ); + } + + done(); + }); + }); + + // ====================================================== + it('image with stroke, opacity and shadow', function (done) { + loadImage('darth-vader.jpg', (imageObj) => { + var stage = addStage(); + + var layer = new Konva.Layer(); + var darth = new Konva.Image({ + x: 200, + y: 60, + image: imageObj, + width: 100, + height: 100, + offset: { x: 50, y: 30 }, + draggable: true, + opacity: 0.5, + shadowColor: 'black', + shadowBlur: 10, + shadowOpacity: 0.5, + shadowOffset: { x: 20, y: 20 }, + stroke: 'red', + strokeWidth: 20, + }); + + layer.add(darth); + stage.add(layer); + + var trace = layer.getContext().getTrace(); + + if (isBrowser) { + assert.equal( + trace, + 'clearRect(0,0,578,200);save();shadowColor=rgba(0,0,0,0.5);shadowBlur=10;shadowOffsetX=20;shadowOffsetY=20;globalAlpha=0.5;drawImage([object HTMLCanvasElement],0,0,578,200);restore();' + ); + } else { + assert.equal( + trace, + 'clearRect(0,0,578,200);save();shadowColor=rgba(0,0,0,0.5);shadowBlur=10;shadowOffsetX=20;shadowOffsetY=20;globalAlpha=0.5;drawImage([object Object],0,0,578,200);restore();' + ); + } + + done(); + }); + }); + + // ====================================================== + it('image caching', function (done) { + loadImage('darth-vader.jpg', (imageObj) => { + var stage = addStage(); + var layer = new Konva.Layer(); + var darth = new Konva.Image({ + x: 200, + y: 60, + image: imageObj, + width: 100, + height: 100, + draggable: true, + }); + + darth.cache(); + layer.add(darth); + stage.add(layer); + + assert.deepEqual(darth.getSelfRect(), { + x: 0, + y: 0, + width: 100, + height: 100, + }); + + var canvas = createCanvas(); + var context = canvas.getContext('2d'); + context.drawImage(imageObj, 200, 60, 100, 100); + compareLayerAndCanvas(layer, canvas, 10); + done(); + }); + }); + + it('image loader', function (done) { + if (isNode) { + done(); + return; + } + loadImage('darth-vader.jpg', (img) => { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + var src = img.src; + Konva.Image.fromURL(src, function (image) { + layer.add(image); + layer.draw(); + assert.equal(image instanceof Konva.Image, true); + var nativeImg = image.image(); + assert.equal(nativeImg instanceof Image, true); + assert.equal(nativeImg.src.indexOf(src) !== -1, true); + assert.equal(nativeImg.complete, true); + done(); + }); + }); + }); + + it('check loading failure', function (done) { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + var src = 'non-existent.jpg'; + Konva.Image.fromURL(src, null, function (e) { + done(); + }); + }); + + it('check zero values', function (done) { + loadImage('darth-vader.jpg', (imageObj) => { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + const image = new Konva.Image({ image: imageObj }); + layer.add(image); + + image.width(0); + image.height(0); + layer.draw(); + + assert.equal(image.width(), 0); + assert.equal(image.height(), 0); + done(); + }); + }); + + it('corner radius', function (done) { + loadImage('darth-vader.jpg', (imageObj) => { + var stage = addStage(); + + var layer = new Konva.Layer(); + var darth = new Konva.Image({ + x: 20, + y: 20, + image: imageObj, + cornerRadius: 10, + draggable: true, + stroke: 'red', + strokeWidth: 100, + strokeEnabled: false, + }); + + layer.add(darth); + stage.add(layer); + + assert.equal( + layer.getContext().getTrace(true), + 'clearRect();save();transform();beginPath();moveTo();lineTo();arc();lineTo();arc();lineTo();arc();lineTo();arc();closePath();clip();drawImage();restore();' + ); + + done(); + }); + }); + + it('corner radius with shadow', function (done) { + // that will trigger buffer canvas + loadImage('darth-vader.jpg', (imageObj) => { + var stage = addStage(); + + var layer = new Konva.Layer(); + var darth = new Konva.Image({ + x: 20, + y: 20, + image: imageObj, + cornerRadius: 10, + draggable: true, + stroke: 'red', + strokeWidth: 100, + strokeEnabled: false, + shadowColor: 'black', + shadowBlur: 10, + shadowOffsetX: 10, + shadowOffsetY: 10, + scaleX: 0.5, + scaleY: 0.5, + }); + + layer.add(darth); + stage.add(layer); + + assert.equal( + layer.getContext().getTrace(true), + 'clearRect();save();shadowColor;shadowBlur;shadowOffsetX;shadowOffsetY;drawImage();restore();' + ); + + done(); + }); + }); +}); diff --git a/test/unit/Label-test.ts b/test/unit/Label-test.ts new file mode 100644 index 000000000..1f3e31620 --- /dev/null +++ b/test/unit/Label-test.ts @@ -0,0 +1,376 @@ +import { assert } from 'chai'; + +import { addStage, Konva, cloneAndCompareLayer, isBrowser } from './test-utils'; + +describe('Label', function () { + // ====================================================== + it('add label', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var label = new Konva.Label({ + x: 100, + y: 100, + draggable: true, + }); + + // add a tag to the label + label.add( + new Konva.Tag({ + fill: '#bbb', + shadowColor: 'black', + shadowBlur: 10, + shadowOffset: { x: 10, y: 10 }, + shadowOpacity: 0.2, + lineJoin: 'round', + pointerDirection: 'up', + pointerWidth: 20, + pointerHeight: 20, + cornerRadius: 5, + }) + ); + + // add text to the label + label.add( + new Konva.Text({ + text: '', + fontSize: 50, + //fontFamily: 'Calibri', + //fontStyle: 'normal', + lineHeight: 1.2, + //padding: 10, + fill: 'green', + }) + ); + + layer.add(label); + stage.add(layer); + + label.getText().fontSize(100); + + label.getText().fontSize(50); + + label.getText().text('Hello big world'); + + layer.draw(); + + assert.equal(label.getType(), 'Group'); + assert.equal(label.getClassName(), 'Label'); + + // use relaxed trace because text can be a slightly different size in different browsers, + // resulting in slightly different tag dimensions + var trace = layer.getContext().getTrace(true); + assert.equal( + trace, + 'clearRect();save();lineJoin;transform();shadowColor;shadowBlur;shadowOffsetX;shadowOffsetY;beginPath();moveTo();lineTo();lineTo();lineTo();lineTo();arc();lineTo();arc();lineTo();arc();lineTo();arc();closePath();fillStyle;fill();restore();save();transform();restore();clearRect();save();lineJoin;transform();shadowColor;shadowBlur;shadowOffsetX;shadowOffsetY;beginPath();moveTo();lineTo();lineTo();lineTo();lineTo();arc();lineTo();arc();lineTo();arc();lineTo();arc();closePath();fillStyle;fill();restore();save();transform();font;textBaseline;textAlign;translate();save();fillStyle;fillText();restore();restore();' + ); + }); + + // ====================================================== + it('create label from json', function () { + var stage = addStage(); + + var json = + '{"attrs":{"x":100,"y":100,"draggable":true},"className":"Label","children":[{"attrs":{"fill":"#bbb","shadowColor":"black","shadowBlur":10,"shadowOffsetX":10,"shadowOffsetY":10,"shadowOpacity":0.2,"lineJoin":"round","pointerDirection":"up","pointerWidth":20,"pointerHeight":20,"cornerRadius":5,"x":-151.5,"y":20,"width":303,"height":60},"className":"Tag"},{"attrs":{"text":"Hello big world","fontSize":50,"lineHeight":1.2,"fill":"green","width":"auto","height":"auto","x":-151.5,"y":20},"className":"Text"}]}'; + var layer = new Konva.Layer(); + + var label = Konva.Node.create(json); + + layer.add(label); + stage.add(layer); + + var trace = layer.getContext().getTrace(false, true); + + if (isBrowser) { + assert.equal( + trace, + 'clearRect(0,0,578,200);save();lineJoin=round;transform(1,0,0,1,-64,120);shadowColor=rgba(0,0,0,0.2);shadowBlur=10;shadowOffsetX=10;shadowOffsetY=10;beginPath();moveTo(5,0);lineTo(153,0);lineTo(163,-20);lineTo(173,0);lineTo(322,0);arc(322,5,5,4,0,false);lineTo(327,55);arc(322,55,5,0,1,false);lineTo(5,60);arc(5,55,5,1,3,false);lineTo(0,5);arc(5,5,5,3,4,false);closePath();fillStyle=#bbb;fill();restore();save();transform(1,0,0,1,-64,120);font=normal normal 50px Arial;textBaseline=middle;textAlign=left;translate(0,0);save();fillStyle=green;fillText(Hello big world,0,30);restore();restore();' + ); + } else { + assert.equal( + trace, + 'clearRect(0,0,578,200);save();lineJoin=round;transform(1,0,0,1,-64,120);shadowColor=rgba(0,0,0,0.2);shadowBlur=10;shadowOffsetX=10;shadowOffsetY=10;beginPath();moveTo(5,0);lineTo(153,0);lineTo(163,-20);lineTo(173,0);lineTo(322,0);arc(322,5,5,4,0,false);lineTo(327,55);arc(322,55,5,0,1,false);lineTo(5,60);arc(5,55,5,1,3,false);lineTo(0,5);arc(5,5,5,3,4,false);closePath();fillStyle=#bbb;fill();restore();save();transform(1,0,0,1,-64,120);font=normal normal 50px Arial;textBaseline=middle;textAlign=left;translate(0,0);save();fillStyle=green;fillText(Hello big world,0,30);restore();restore();' + ); + } + }); + + it('find label class', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var label = new Konva.Label({ + x: 100, + y: 100, + }); + + // add a tag to the label + label.add( + new Konva.Tag({ + fill: '#bbb', + }) + ); + + // add text to the label + label.add( + new Konva.Text({ + text: 'Test Label', + fill: 'green', + }) + ); + + layer.add(label); + stage.add(layer); + + assert.equal(stage.find('Label')[0], label); + }); + + // caching doesn't give exactly the same result. WHY? + it('cache label', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + // tooltip + var tooltip = new Konva.Label({ + x: 170, + y: 75, + opacity: 0.75, + }); + tooltip.add( + new Konva.Tag({ + fill: 'black', + pointerDirection: 'down', + pointerWidth: 10, + pointerHeight: 10, + lineJoin: 'round', + shadowColor: 'black', + shadowBlur: 10, + shadowOffsetX: 10, + shadowOpacity: 0.5, + }) + ); + tooltip.add( + new Konva.Text({ + text: 'Tooltip pointing down', + fontFamily: 'Calibri', + fontSize: 18, + padding: 5, + fill: 'white', + }) + ); + + var tooltipUp = new Konva.Label({ + x: 170, + y: 75, + opacity: 0.75, + }); + tooltipUp.add( + new Konva.Tag({ + fill: 'black', + pointerDirection: 'up', + pointerWidth: 10, + pointerHeight: 10, + lineJoin: 'round', + shadowColor: 'black', + shadowBlur: 10, + shadowOffsetX: 10, + shadowOpacity: 0.5, + }) + ); + tooltipUp.add( + new Konva.Text({ + text: 'Tooltip pointing up', + fontFamily: 'Calibri', + fontSize: 18, + padding: 5, + fill: 'white', + }) + ); + // label with left pointer + var labelLeft = new Konva.Label({ + x: 20, + y: 130, + opacity: 0.75, + }); + labelLeft.add( + new Konva.Tag({ + fill: 'green', + pointerDirection: 'left', + pointerWidth: 30, + pointerHeight: 28, + lineJoin: 'round', + }) + ); + labelLeft.add( + new Konva.Text({ + text: 'Label pointing left', + fontFamily: 'Calibri', + fontSize: 18, + padding: 5, + fill: 'white', + }) + ); + // label with left pointer + var labelRight = new Konva.Label({ + x: 160, + y: 170, + offsetX: 20, + opacity: 0.75, + }); + labelRight.add( + new Konva.Tag({ + fill: 'green', + pointerDirection: 'right', + pointerWidth: 20, + pointerHeight: 28, + lineJoin: 'round', + }) + ); + labelRight.add( + new Konva.Text({ + text: 'Label right', + fontFamily: 'Calibri', + fontSize: 18, + padding: 5, + fill: 'white', + }) + ); + // simple label + var simpleLabel = new Konva.Label({ + x: 180, + y: 150, + opacity: 0.75, + }); + simpleLabel.add( + new Konva.Tag({ + fill: 'yellow', + }) + ); + simpleLabel.add( + new Konva.Text({ + text: 'Simple label', + fontFamily: 'Calibri', + fontSize: 18, + padding: 5, + fill: 'black', + }) + ); + // add the labels to layer + layer.add(tooltip, tooltipUp, labelLeft, labelRight, simpleLabel); + layer.children.forEach((child) => child.cache()); + + stage.add(layer); + + cloneAndCompareLayer(layer, 250, 100); + }); + + it('tag should list text size changes', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var label = new Konva.Label(); + + var tag = new Konva.Tag({ + stroke: 'black', + }); + + label.add(tag); + + var text = new Konva.Text({ + text: 'hello hello hello hello hello hello hello hello', + }); + label.add(text); + + layer.add(label); + layer.draw(); + + text.width(200); + + layer.draw(); + assert.equal(tag.width(), text.width()); + + text.height(200); + assert.equal(tag.height(), text.height()); + }); + + it('tag cornerRadius', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var tag = new Konva.Tag({ + x: 50, + y: 50, + width: 100, + height: 100, + fill: 'black', + cornerRadius: [0, 10, 20, 30], + }); + layer.add(tag); + stage.add(layer); + layer.draw(); + + assert.equal(tag.cornerRadius()[0], 0); + assert.equal(tag.cornerRadius()[1], 10); + assert.equal(tag.cornerRadius()[2], 20); + assert.equal(tag.cornerRadius()[3], 30); + + var trace = layer.getContext().getTrace(); + assert.equal( + trace, + 'clearRect(0,0,578,200);save();transform(1,0,0,1,50,50);beginPath();moveTo(0,0);lineTo(90,0);arc(90,10,10,4.712,0,false);lineTo(100,80);arc(80,80,20,0,1.571,false);lineTo(30,100);arc(30,70,30,1.571,3.142,false);lineTo(0,0);arc(0,0,0,3.142,4.712,false);closePath();fillStyle=black;fill();restore();clearRect(0,0,578,200);save();transform(1,0,0,1,50,50);beginPath();moveTo(0,0);lineTo(90,0);arc(90,10,10,4.712,0,false);lineTo(100,80);arc(80,80,20,0,1.571,false);lineTo(30,100);arc(30,70,30,1.571,3.142,false);lineTo(0,0);arc(0,0,0,3.142,4.712,false);closePath();fillStyle=black;fill();restore();' + ); + }); + + it('react to pointer properties', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var label = new Konva.Label({ + x: 100, + y: 100, + draggable: true, + }); + + var counter = 0; + var oldSync = label._sync; + label._sync = () => { + oldSync.call(label); + counter += 1; + }; + + const tag = new Konva.Tag({ + fill: '#bbb', + shadowColor: 'black', + shadowBlur: 10, + shadowOffset: { x: 10, y: 10 }, + shadowOpacity: 0.2, + lineJoin: 'round', + pointerDirection: 'up', + pointerWidth: 20, + pointerHeight: 20, + cornerRadius: 5, + }); + // add a tag to the label + label.add(tag); + + // add text to the label + label.add( + new Konva.Text({ + text: 'hello', + fontSize: 50, + lineHeight: 1.2, + fill: 'green', + }) + ); + layer.add(label); + + assert.equal(counter, 4); + tag.pointerDirection('bottom'); + assert.equal(counter, 5); + tag.pointerWidth(30); + assert.equal(counter, 6); + tag.pointerHeight(40); + assert.equal(counter, 7); + }); +}); diff --git a/test/unit/Layer-test.ts b/test/unit/Layer-test.ts new file mode 100644 index 000000000..b0891d560 --- /dev/null +++ b/test/unit/Layer-test.ts @@ -0,0 +1,452 @@ +import { assert } from 'chai'; + +import { + addStage, + simulateMouseDown, + simulateMouseMove, + simulateMouseUp, + showHit, + Konva, + loadImage, + isNode, +} from './test-utils'; + +describe('Layer', function () { + // ====================================================== + it('width and height', function () { + Konva.showWarnings = false; + var stage = addStage(); + + var layer = new Konva.Layer(); + assert.equal( + layer.width(), + undefined, + 'while layer is not on stage width is undefined' + ); + assert.equal( + layer.height(), + undefined, + 'while layer is not on stage height is undefined' + ); + + layer.width(10); + assert.equal( + layer.width(), + undefined, + 'while layer is not on stage changing width doing nothing' + ); + layer.height(10); + assert.equal( + layer.height(), + undefined, + 'while layer is not on stage changing height doing nothing' + ); + stage.add(layer); + + assert.equal( + layer.width(), + stage.width(), + 'while layer is on stage width is stage`s width' + ); + assert.equal( + layer.height(), + stage.height(), + 'while layer is on stage height is stage`s height' + ); + + layer.width(10); + assert.equal( + layer.width(), + stage.width(), + 'while layer is on stage changing width doing nothing' + ); + layer.height(10); + assert.equal( + layer.height(), + stage.height(), + 'while layer is on stage changing height doing nothing' + ); + Konva.showWarnings = true; + }); + + // ====================================================== + it('test canvas inline styles', function () { + if (isNode) { + return; + } + var stage = addStage(); + + var layer = new Konva.Layer(); + + var circle = new Konva.Circle({ + x: 100, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + }); + + layer.add(circle); + stage.add(layer); + + var style = layer.getCanvas()._canvas.style; + + assert.equal( + style.position, + 'absolute', + 'canvas position style should be absolute' + ); + assert.equal( + style.border.indexOf('0px'), + 0, + 'canvas border style should be 0px' + ); + assert.equal(style.margin, '0px', 'canvas margin style should be 0px'); + assert.equal(style.padding, '0px', 'canvas padding style should be 0px'); + assert.equal( + style.backgroundColor, + 'transparent', + 'canvas backgroundColor style should be transparent' + ); + assert.equal(style.top, '0px', 'canvas top should be 0px'); + assert.equal(style.left, '0px', 'canvas left should be 0px'); + }); + + it('test clear()', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: 100, + y: 100, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + draggable: true, + }); + + circle.colorKey = '#000000'; + + circle.on('mouseover', function () { + console.log('mouseover'); + }); + + layer.add(circle); + stage.add(layer); + + layer.clear(); + + var trace = layer.getContext().getTrace(); + //console.log(trace); + assert.equal( + trace, + 'clearRect(0,0,578,200);save();transform(1,0,0,1,100,100);beginPath();arc(0,0,70,0,6.283,false);closePath();fillStyle=green;fill();lineWidth=4;strokeStyle=black;stroke();restore();clearRect(0,0,578,200);' + ); + + var hitTrace = layer.getHitCanvas().getContext().getTrace(); + //console.log(hitTrace); + assert.equal( + hitTrace, + 'clearRect(0,0,578,200);save();transform(1,0,0,1,100,100);beginPath();arc(0,0,70,0,6.283,false);closePath();save();fillStyle=#000000;fill();restore();lineWidth=4;strokeStyle=#000000;stroke();restore();clearRect(0,0,578,200);' + ); + + showHit(layer); + }); + + it('test clear() with bounds', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: 100, + y: 100, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + draggable: true, + }); + + circle.colorKey = '#000000'; + + circle.on('mouseover', function () { + console.log('mouseover'); + }); + + layer.add(circle); + stage.add(layer); + + layer.clear({ x: 100, y: 100, width: 100, height: 100 }); + + var trace = layer.getContext().getTrace(); + //console.log(trace); + assert.equal( + trace, + 'clearRect(0,0,578,200);save();transform(1,0,0,1,100,100);beginPath();arc(0,0,70,0,6.283,false);closePath();fillStyle=green;fill();lineWidth=4;strokeStyle=black;stroke();restore();clearRect(100,100,100,100);' + ); + + var hitTrace = layer.getHitCanvas().getContext().getTrace(); + //console.log(hitTrace); + assert.equal( + hitTrace, + 'clearRect(0,0,578,200);save();transform(1,0,0,1,100,100);beginPath();arc(0,0,70,0,6.283,false);closePath();save();fillStyle=#000000;fill();restore();lineWidth=4;strokeStyle=#000000;stroke();restore();clearRect(100,100,100,100);' + ); + + showHit(layer); + }); + + // ====================================================== + it('layer getIntersection()', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var redCircle = new Konva.Circle({ + x: 380, + y: stage.height() / 2, + radius: 70, + strokeWidth: 4, + fill: 'red', + stroke: 'black', + id: 'redCircle', + }); + + var greenCircle = new Konva.Circle({ + x: 300, + y: stage.height() / 2, + radius: 70, + strokeWidth: 4, + fill: 'green', + stroke: 'black', + id: 'greenCircle', + }); + + layer.add(redCircle); + layer.add(greenCircle); + stage.add(layer); + + assert.equal( + layer.getIntersection({ x: 300, y: 100 }).id(), + 'greenCircle', + 'shape should be greenCircle' + ); + assert.equal( + layer.getIntersection({ x: 380, y: 100 }).id(), + 'redCircle', + 'shape should be redCircle' + ); + assert.equal( + layer.getIntersection({ x: 100, y: 100 }), + null, + 'shape should be null' + ); + }); + + // ====================================================== + it('set layer visibility', function () { + var stage = addStage(); + var layer = new Konva.Layer({ + visible: false, + }); + + stage.add(layer); + + assert(layer.getNativeCanvasElement().style.display === 'none'); + }); + + // ====================================================== + it('set clearBeforeDraw to false, and test toDataURL for stage, layer, group, and shape', function () { + var stage = addStage(); + + var layer = new Konva.Layer({ + clearBeforeDraw: false, + throttle: 999, + }); + + var group = new Konva.Group(); + + var circle = new Konva.Circle({ + x: 100, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + }); + + group.add(circle); + layer.add(group); + stage.add(layer); + + for (var n = 0; n < 20; n++) { + circle.move({ x: 10, y: 0 }); + layer.draw(); + } + + var trace = layer.getContext().getTrace(); + //console.log(trace); + assert.equal( + trace, + 'save();transform(1,0,0,1,220,100);beginPath();arc(0,0,70,0,6.283,false);closePath();fillStyle=green;fill();lineWidth=4;strokeStyle=black;stroke();restore();save();transform(1,0,0,1,230,100);beginPath();arc(0,0,70,0,6.283,false);closePath();fillStyle=green;fill();lineWidth=4;strokeStyle=black;stroke();restore();save();transform(1,0,0,1,240,100);beginPath();arc(0,0,70,0,6.283,false);closePath();fillStyle=green;fill();lineWidth=4;strokeStyle=black;stroke();restore();save();transform(1,0,0,1,250,100);beginPath();arc(0,0,70,0,6.283,false);closePath();fillStyle=green;fill();lineWidth=4;strokeStyle=black;stroke();restore();save();transform(1,0,0,1,260,100);beginPath();arc(0,0,70,0,6.283,false);closePath();fillStyle=green;fill();lineWidth=4;strokeStyle=black;stroke();restore();save();transform(1,0,0,1,270,100);beginPath();arc(0,0,70,0,6.283,false);closePath();fillStyle=green;fill();lineWidth=4;strokeStyle=black;stroke();restore();save();transform(1,0,0,1,280,100);beginPath();arc(0,0,70,0,6.283,false);closePath();fillStyle=green;fill();lineWidth=4;strokeStyle=black;stroke();restore();save();transform(1,0,0,1,290,100);beginPath();arc(0,0,70,0,6.283,false);closePath();fillStyle=green;fill();lineWidth=4;strokeStyle=black;stroke();restore();save();transform(1,0,0,1,300,100);beginPath();arc(0,0,70,0,6.283,false);closePath();fillStyle=green;fill();lineWidth=4;strokeStyle=black;stroke();restore();' + ); + }); + + // ====================================================== + it('save layer as png', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var Circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'violet', + stroke: 'black', + strokeWidth: 4, + }); + + layer.add(Circle); + stage.add(layer); + + var dataUrl = layer.toDataURL(); + assert(dataUrl.length > 30); + }); + + // ====================================================== + it('save layer as low quality jpg', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'violet', + stroke: 'black', + strokeWidth: 4, + }); + + layer.add(circle); + stage.add(layer); + + var dataUrl = layer.toDataURL({ + mimeType: 'image/jpeg', + quality: 0.2, + }); + + assert( + dataUrl.length < + layer.toDataURL({ + mimeType: 'image/jpeg', + }).length + ); + }); + + // ====================================================== + it('hit graph enable disable', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'red', + stroke: 'black', + strokeWidth: 4, + }); + + layer.add(circle); + stage.add(layer); + + assert.equal(layer.listening(), true); + assert.equal(layer.shouldDrawHit(), true); + + layer.listening(false); + + assert.equal(layer.listening(), false); + assert.equal(layer.shouldDrawHit(), false); + + layer.listening(true); + + assert.equal(layer.listening(), true); + assert.equal(layer.shouldDrawHit(), true); + }); + + // ====================================================== + it('should not draw hit on stage drag', function () { + var stage = addStage(); + stage.draggable(true); + var layer = new Konva.Layer(); + + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'red', + stroke: 'black', + strokeWidth: 4, + }); + + layer.add(circle); + stage.add(layer); + + simulateMouseDown(stage, { + x: circle.x(), + y: circle.y(), + }); + + simulateMouseMove(stage, { + x: circle.x() + 10, + y: circle.y() + 10, + }); + assert.equal(stage.isDragging(), true, 'dragging of stage is ok'); + assert.equal(layer.shouldDrawHit(), false); + + simulateMouseUp(stage, { + x: 291, + y: 112, + }); + }); + + it('get/set layer size', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + assert.deepEqual(layer.size(), stage.size()); + assert.equal(layer.width(), stage.width()); + assert.equal(layer.height(), stage.height()); + }); + + it('get/set imageSmoothingEnabled', function (done) { + loadImage('darth-vader.jpg', (imageObj) => { + var stage = addStage(); + + var layer = new Konva.Layer({ + imageSmoothingEnabled: false, + }); + var darth = new Konva.Image({ + image: imageObj, + scaleX: 5, + scaleY: 5, + }); + + layer.add(darth); + stage.add(layer); + + assert.equal(layer.getContext()['imageSmoothingEnabled'], false); + + layer.imageSmoothingEnabled(true); + + assert.equal(layer.getContext()['imageSmoothingEnabled'], true); + + layer.imageSmoothingEnabled(false); + // change size + stage.width(stage.width() + 1); + assert.equal(layer.getContext()['imageSmoothingEnabled'], false); + + done(); + }); + }); +}); diff --git a/test/unit/Line-test.ts b/test/unit/Line-test.ts new file mode 100644 index 000000000..2efa2816b --- /dev/null +++ b/test/unit/Line-test.ts @@ -0,0 +1,716 @@ +import { assert } from 'chai'; + +import { + addStage, + Konva, + createCanvas, + compareLayerAndCanvas, + compareLayers, +} from './test-utils'; + +describe('Line', function () { + // ====================================================== + it('add line', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var line = new Konva.Line({ + stroke: 'blue', + strokeWidth: 20, + lineCap: 'round', + lineJoin: 'round', + draggable: true, + tension: 0, + }); + + layer.add(line); + stage.add(layer); + + line.points([1, 2, 3, 4]); + assert.equal(line.points()[0], 1); + + line.points([5, 6, 7, 8]); + assert.equal(line.points()[0], 5); + + line.points([73, 160, 340, 23, 340, 80]); + assert.equal(line.points()[0], 73); + + assert.equal(line.getClassName(), 'Line'); + + layer.draw(); + }); + + // ====================================================== + it('test default ponts array for two lines', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var line = new Konva.Line({ + stroke: 'blue', + strokeWidth: 20, + lineCap: 'round', + lineJoin: 'round', + draggable: true, + }); + + var redLine = new Konva.Line({ + x: 50, + stroke: 'red', + strokeWidth: 20, + lineCap: 'round', + lineJoin: 'round', + draggable: true, + }); + + line.points([0, 1, 2, 3]); + redLine.points([4, 5, 6, 7]); + + layer.add(line).add(redLine); + stage.add(layer); + + assert.equal(line.points()[0], 0); + assert.equal(redLine.points()[0], 4); + }); + + // ====================================================== + it('add dashed line', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + /* + var points = [{ + x: 73, + y: 160 + }, { + x: 340, + y: 23 + }, { + x: 500, + y: 109 + }, { + x: 500, + y: 180 + }]; + */ + + var line = new Konva.Line({ + points: [73, 160, 340, 23, 500, 109, 500, 180], + stroke: 'blue', + + strokeWidth: 10, + lineCap: 'round', + lineJoin: 'round', + draggable: true, + dash: [30, 10, 0, 10, 10, 20], + shadowColor: '#aaa', + shadowBlur: 10, + shadowOffset: { + x: 20, + y: 20, + }, + //opacity: 0.2 + }); + + layer.add(line); + stage.add(layer); + + assert.equal(line.dash().length, 6); + line.dash([10, 10]); + assert.equal(line.dash().length, 2); + + assert.equal(line.points().length, 8); + }); + + // ====================================================== + it('add line with shadow', function () { + const oldRatio = Konva.pixelRatio; + Konva.pixelRatio = 1; + var stage = addStage(); + var layer = new Konva.Layer(); + + var line = new Konva.Line({ + points: [73, 160, 340, 23], + stroke: 'blue', + strokeWidth: 20, + lineCap: 'round', + lineJoin: 'round', + shadowColor: 'black', + shadowBlur: 20, + shadowOffset: { + x: 10, + y: 10, + }, + shadowOpacity: 0.5, + draggable: true, + }); + + layer.add(line); + stage.add(layer); + + var canvas = createCanvas(); + var context = canvas.getContext('2d'); + + context.save(); + context.lineJoin = 'round'; + context.lineCap = 'round'; + context.lineWidth = 20; + context.strokeStyle = 'blue'; + + context.shadowColor = 'rgba(0,0,0,0.5)'; + context.shadowBlur = 20; + context.shadowOffsetX = 10; + context.shadowOffsetY = 10; + context.moveTo(73, 160); + context.lineTo(340, 23); + + context.stroke(); + // context.fill(); + context.restore(); + + Konva.pixelRatio = oldRatio; + + compareLayerAndCanvas(layer, canvas, 50); + + var trace = layer.getContext().getTrace(); + + assert.equal( + trace, + 'clearRect(0,0,578,200);save();lineJoin=round;transform(1,0,0,1,0,0);shadowColor=rgba(0,0,0,0.5);shadowBlur=20;shadowOffsetX=10;shadowOffsetY=10;beginPath();moveTo(73,160);lineTo(340,23);lineCap=round;lineWidth=20;strokeStyle=blue;stroke();restore();' + ); + }); + + it('line hit test with strokeScaleEnabled = false', function () { + var stage = addStage(); + var scale = 0.1; + var layer = new Konva.Layer(); + + var group = new Konva.Group({ + scale: { + x: scale, + y: scale, + }, + }); + + var line1 = new Konva.Line({ + points: [0, 0, 300, 0], + stroke: 'red', + strokeScaleEnabled: false, + strokeWidth: 10, + y: 0, + }); + group.add(line1); + + var line2 = new Konva.Line({ + points: [0, 0, 300, 0], + stroke: 'green', + strokeWidth: 40 / scale, + y: 60 / scale, + }); + group.add(line2); + + layer.add(group); + stage.add(layer); + + var shape = layer.getIntersection({ + x: 10, + y: 60, + }); + assert.equal(shape, line2, 'second line detected'); + + shape = layer.getIntersection({ + x: 10, + y: 4, + }); + assert.equal(shape, line1, 'first line detected'); + }); + + it('line get size', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var line = new Konva.Line({ + points: [73, 160, 340, 23, 500, 109, 500, 180], + stroke: 'blue', + + strokeWidth: 10, + }); + + layer.add(line); + stage.add(layer); + + assert.deepEqual(line.size(), { + width: 500 - 73, + height: 180 - 23, + }); + }); + + it('getSelfRect', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var blob = new Konva.Line({ + x: 50, + y: 50, + points: [-25, 50, 250, -30, 150, 50, 250, 110], + stroke: 'blue', + strokeWidth: 10, + draggable: true, + fill: '#aaf', + closed: true, + }); + + layer.add(blob); + stage.add(layer); + + assert.deepEqual(blob.getSelfRect(), { + x: -25, + y: -30, + width: 275, + height: 140, + }); + }); + + it('getClientRect', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var poly = new Konva.Line({ + x: 0, + y: 0, + points: [-100, 0, +100, 0, +100, 100, -100, 100], + closed: true, + fill: '#0f0', + }); + + stage.position({ + x: 150, + y: 50, + }); + + layer.add(poly); + stage.add(layer); + + var rect = layer.getClientRect({ relativeTo: stage as any }); + assert.deepEqual(rect, { + x: -100, + y: 0, + width: 200, + height: 100, + }); + }); + + it('getClientRect with tension', function () { + var stage = addStage(); + stage.draggable(true); + var layer = new Konva.Layer(); + + var line = new Konva.Line({ + x: 0, + y: 0, + points: [75, 75, 100, 200, 300, 140], + tension: 0.5, + stroke: '#0f0', + }); + layer.add(line); + + var client = line.getClientRect(); + var rect = new Konva.Rect(Konva.Util._assign({ stroke: 'red' }, client)); + layer.add(rect); + + stage.add(layer); + + assert.equal(Math.round(client.x), 56, 'check x'); + assert.equal(Math.round(client.y), 74, 'check y'); + assert.equal(Math.round(client.width), 245, 'check width'); + assert.equal(Math.round(client.height), 147, 'check height'); + }); + + it('getClientRect with tension 2', function () { + var stage = addStage(); + stage.draggable(true); + var layer = new Konva.Layer(); + + var line = new Konva.Line({ + x: 0, + y: 0, + points: [ + 494.39880507841673, + 795.3696788648244, + 494.49880507841675, + 795.4696788648245, + 494.39880507841673, + 796.8633308439133, + 489.9178491411501, + 798.3569828230022, + 480.95593726661684, + 802.8379387602688, + 467.513069454817, + 810.3061986557132, + 451.0828976848394, + 820.7617625093353, + 433.15907393577294, + 832.7109783420462, + 415.2352501867065, + 846.1538461538461, + 398.8050784167289, + 859.596713965646, + 383.8685586258402, + 871.545929798357, + 374.90664675130694, + 880.5078416728902, + 371.9193427931292, + 883.4951456310679, + 371.9193427931292, + 883.4951456310679, + 371.9193427931292, + 883.4951456310679, + 376.40029873039583, + 882.0014936519791, + 395.8177744585511, + 876.0268857356235, + 443.6146377893951, + 856.6094100074682, + 507.84167289021656, + 838.6855862584017, + 551.1575802837939, + 825.2427184466019, + 624.3465272591486, + 807.3188946975355, + 696.0418222554144, + 789.395070948469, + 758.7752053771471, + 777.445855115758, + 802.0911127707244, + 772.9648991784914, + 820.0149365197909, + 771.4712471994025, + 821.5085884988797, + 771.4712471994025, + 820.0149365197909, + 775.9522031366691, + 799.1038088125466, + 790.8887229275579, + 743.8386855862584, + 825.2427184466019, + 652.7259148618372, + 871.545929798357, + 542.1956684092606, + 926.8110530246452, + 455.563853622106, + 977.5952203136669, + 412.24794622852875, + 1010.455563853622, + 397.31142643764, + 1026.8857356235997, + 397.31142643764, + 1032.8603435399552, + 400.29873039581776, + 1038.8349514563106, + 415.2352501867065, + 1043.3159073935774, + 463.0321135175504, + 1043.3159073935774, + 563.1067961165048, + 1040.3286034353996, + 696.0418222554144, + 1032.8603435399552, + 787.1545929798357, + 1026.8857356235997, + 921.5832710978342, + 1017.9238237490664, + 1018.6706497386109, + 1013.4428678117998, + 1069.4548170276325, + 1013.4428678117998, + 1076.923076923077, + 1013.4428678117998, + 1075.4294249439881, + 1014.9365197908887, + 1051.530993278566, + 1026.8857356235997, + 979.8356982823002, + 1053.7714712471993, + 888.722927557879, + 1079.1635548917102, + 761.7625093353248, + 1116.504854368932, + 672.1433905899925, + 1150.858849887976, + 628.8274831964152, + 1171.7699775952203, + 615.3846153846154, + 1180.7318894697535, + 615.3846153846154, + 1182.2255414488425, + 618.3719193427931, + 1183.7191934279313, + 633.3084391336819, + 1182.2255414488425, + 687.0799103808812, + 1171.7699775952203, + 775.2053771471248, + 1150.858849887976, + 902.1657953696788, + 1116.504854368932, + 990.2912621359224, + 1091.1127707244211, + 1082.8976848394325, + 1062.7333831217327, + 1133.681852128454, + 1046.303211351755, + 1144.1374159820762, + 1041.8222554144884, + 1144.1374159820762, + 1041.8222554144884, + 1141.1501120238984, + 1041.8222554144884, + 1117.2516803584765, + 1043.3159073935774, + 1082.8976848394325, + 1046.303211351755, + 1008.2150858849888, + 1062.7333831217327, + 917.1023151605675, + 1092.6064227035101, + 861.8371919342793, + 1117.9985063480208, + 814.0403286034353, + 1152.352501867065, + 794.62285287528, + 1176.250933532487, + 790.1418969380135, + 1189.6938013442868, + 793.1292008961912, + 1198.65571321882, + 802.0911127707244, + 1206.1239731142643, + 831.9641523525019, + 1216.5795369678865, + 903.6594473487677, + 1225.5414488424196, + 1014.1896938013442, + 1228.5287528005974, + 1148.6183719193427, + 1228.5287528005974, + 1272.591486183719, + 1225.5414488424196, + 1314.4137415982075, + 1225.5414488424196, + 1326.3629574309186, + 1225.5414488424196, + 1326.3629574309186, + 1225.5414488424196, + 1314.4137415982075, + 1228.5287528005974, + 1272.591486183719, + 1237.4906646751306, + 1197.9088872292755, + 1247.9462285287527, + 1105.3024645257656, + 1270.3510082150858, + 1048.5436893203882, + 1286.7811799850635, + 1024.6452576549664, + 1295.7430918595967, + 1006.7214339058999, + 1306.1986557132188, + 1000.7468259895444, + 1313.6669156086632, + 1000.7468259895444, + 1315.160567587752, + 1003.7341299477222, + 1316.6542195668408, + 1015.6833457804331, + 1319.6415235250186, + 1050.0373412994772, + 1321.1351755041076, + 1103.8088125466766, + 1321.1351755041076, + 1169.529499626587, + 1316.6542195668408, + 1220.3136669156086, + 1310.6796116504854, + 1248.6930545182972, + 1307.6923076923076, + 1253.1740104555638, + 1307.6923076923076, + 1253.1740104555638, + 1307.6923076923076, + 1253.1740104555638, + 1307.6923076923076, + 1248.6930545182972, + 1309.1859596713964, + 1229.275578790142, + 1312.1732636295742, + 1199.4025392083645, + 1319.6415235250186, + 1172.5168035847648, + 1330.0970873786407, + 1154.5929798356983, + 1342.0463032113516, + 1144.1374159820762, + 1353.9955190440626, + 1139.6564600448096, + 1361.463778939507, + 1138.1628080657206, + 1364.4510828976847, + 1138.1628080657206, + 1365.9447348767737, + 1138.1628080657206, + 1365.9447348767737, + ], + tension: 0.5, + stroke: '#0f0', + }); + layer.add(line); + + var client = line.getClientRect(); + var rect = new Konva.Rect(Konva.Util._assign({ stroke: 'red' }, client)); + layer.add(rect); + + stage.add(layer); + + assert.equal(Math.round(client.x), 371, 'check x'); + assert.equal(Math.round(client.y), 770, 'check y'); + assert.equal(Math.round(client.width), 956, 'check width'); + assert.equal(Math.round(client.height), 597, 'check height'); + }); + + it('getClientRect with low number of points', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var line = new Konva.Line({ + x: 0, + y: 0, + points: [], + tension: 0.5, + stroke: '#0f0', + }); + layer.add(line); + layer.draw(); + + var client = line.getClientRect(); + + assert.equal(client.x, -1, 'check x'); + assert.equal(client.y, -1, 'check y'); + assert.equal(client.width, 2, 'check width'); + assert.equal(client.height, 2, 'check height'); + + line.points([10, 10]); + client = line.getClientRect(); + + assert.equal(client.x, 9, 'check x'); + assert.equal(client.y, 9, 'check y'); + assert.equal(client.width, 2, 'check width'); + assert.equal(client.height, 2, 'check height'); + }); + + it('line caching', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var blob = new Konva.Line({ + x: 50, + y: 50, + points: [-25, 50, 250, -30, 150, 50, 250, 110], + stroke: 'black', + strokeWidth: 10, + draggable: true, + closed: true, + }); + + layer.add(blob); + var layer2 = layer.clone(); + blob.cache({ + offset: 30, + }); + stage.add(layer); + stage.add(layer2); + layer2.hide(); + compareLayers(layer, layer2, 150); + }); + + it('updating points with old mutable array should trigger recalculations', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var points = [-25, 50, 250, -30, 150, 50]; + var blob = new Konva.Line({ + x: 50, + y: 50, + points: points, + stroke: 'blue', + strokeWidth: 10, + draggable: true, + closed: true, + tension: 1, + }); + + var tensionPoints = blob.getTensionPoints(); + points.push(250, 100); + blob.points(points); + + layer.add(blob); + stage.add(layer); + + assert.equal( + tensionPoints === blob.getTensionPoints(), + false, + 'calculated points should change' + ); + }); + + it('hit test for scaled line', function () { + var stage = addStage(); + var scale = 42; + stage.scaleX(scale); + stage.scaleY(scale); + var layer = new Konva.Layer(); + stage.add(layer); + + var points = [1, 1, 7, 2, 8, 7, 2, 6]; + var line = new Konva.Line({ + points: points.map(function (v) { + return (v * 20) / scale; + }), + closed: true, + fill: 'green', + draggable: true, + }); + layer.add(line); + layer.draw(); + + assert.equal(line.hasHitStroke(), false); + assert.equal(layer.getIntersection({ x: 1, y: 1 }), null); + + layer.toggleHitCanvas(); + }); + + it('getClientRect with scaling', function () { + var stage = addStage(); + var scale = 42; + stage.scaleX(scale); + stage.scaleY(scale); + var layer = new Konva.Layer(); + stage.add(layer); + + var points = [1, 1, 7, 2, 8, 7, 2, 6]; + var line = new Konva.Line({ + points: points.map(function (v) { + return (v * 20) / scale; + }), + closed: true, + fill: 'green', + draggable: true, + }); + layer.add(line); + layer.draw(); + + var client = line.getClientRect(); + + assert.equal(client.x, 20, 'check x'); + assert.equal(client.y, 20, 'check y'); + assert.equal(client.width, 140, 'check width'); + assert.equal(client.height, 120, 'check height'); + }); +}); diff --git a/test/unit/MouseEvents-test.ts b/test/unit/MouseEvents-test.ts new file mode 100644 index 000000000..ed08d1441 --- /dev/null +++ b/test/unit/MouseEvents-test.ts @@ -0,0 +1,2443 @@ +import { assert } from 'chai'; + +import { + addStage, + Konva, + simulateMouseDown, + simulateMouseMove, + simulateMouseUp, + simulateTouchStart, + simulateTouchEnd, + isNode, +} from './test-utils'; + +describe('MouseEvents', function () { + // ====================================================== + it('remove shape with onclick', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + draggable: true, + }); + + layer.add(circle); + stage.add(layer); + + function remove() { + circle.remove(); + layer.draw(); + } + + circle.on('click', function () { + setTimeout(remove, 0); + }); + + simulateMouseDown(stage, { + x: 291, + y: 112, + }); + + simulateMouseUp(stage, { + x: 291, + y: 112, + }); + Konva.DD._endDragAfter({ dragEndNode: circle }); + }); + + // ====================================================== + it('test listening true/false with clicks', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + }); + + var clickCount = 0; + + circle.on('click', function () { + clickCount++; + }); + + layer.add(circle); + stage.add(layer); + + // ----------------------------------- + simulateMouseDown(stage, { + x: 291, + y: 112, + }); + simulateMouseUp(stage, { + x: 291, + y: 112, + }); + assert.equal(clickCount, 1, 'should be 1 click'); + + // ----------------------------------- + circle.listening(false); + simulateMouseDown(stage, { + x: 291, + y: 112, + }); + simulateMouseUp(stage, { + x: 291, + y: 112, + }); + assert.equal( + clickCount, + 1, + 'should be 1 click even though another click occurred' + ); + + // ----------------------------------- + circle.listening(true); + simulateMouseDown(stage, { + x: 291, + y: 112, + }); + simulateMouseUp(stage, { + x: 291, + y: 112, + }); + assert.equal(clickCount, 2, 'should be 2 clicks'); + }); + + // ====================================================== + it('click mapping', function () { + var stage = addStage(); + var layer = new Konva.Layer({ + sceneFunc: function () { + var context = this.getContext(); + context.beginPath(); + context.moveTo(200, 50); + context.lineTo(420, 80); + context.quadraticCurveTo(300, 100, 260, 170); + context.closePath(); + context.fillStyle = 'blue'; + context.fill(context); + }, + }); + + var redCircle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'red', + }); + + var greenCircle = new Konva.Circle({ + x: 400, + y: stage.height() / 2, + radius: 70, + fill: 'green', + }); + + var redClicks = 0; + var greenClicks = 0; + + redCircle.on('click', function () { + //console.log('clicked redCircle'); + redClicks++; + }); + + greenCircle.on('click', function () { + //console.log('clicked greenCircle'); + greenClicks++; + }); + + layer.add(redCircle); + layer.add(greenCircle); + + stage.add(layer); + + // mousedown and mouseup on red circle + simulateMouseDown(stage, { + x: 284, + y: 113, + }); + + simulateMouseUp(stage, { + x: 284, + y: 113, + }); + + assert.equal(redClicks, 1, 'red circle should have 1 click'); + assert.equal(greenClicks, 0, 'green circle should have 0 clicks'); + + // mousedown and mouseup on green circle + simulateMouseDown(stage, { + x: 397, + y: 108, + }); + + simulateMouseUp(stage, { + x: 397, + y: 108, + }); + + assert.equal(redClicks, 1, 'red circle should have 1 click'); + assert.equal(greenClicks, 1, 'green circle should have 1 click'); + + // mousedown red circle and mouseup on green circle + simulateMouseDown(stage, { + x: 284, + y: 113, + }); + + simulateMouseUp(stage, { + x: 397, + y: 108, + }); + + assert.equal(redClicks, 1, 'red circle should still have 1 click'); + assert.equal(greenClicks, 1, 'green circle should still have 1 click'); + }); + + // ====================================================== + it('text events', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var text = new Konva.Text({ + x: 290, + y: 111, + fontFamily: 'Calibri', + fontSize: 30, + fill: 'red', + text: 'Testing 123', + draggable: true, + }); + + var click = false; + + text.on('click', function () { + //console.log('text click'); + click = true; + }); + + layer.add(text); + stage.add(layer); + + simulateMouseDown(stage, { + x: 300, + y: 120, + }); + + simulateMouseUp(stage, { + x: 300, + y: 120, + }); + + assert.equal( + click, + true, + 'click event should have been fired when mousing down and then up on text' + ); + }); + + // ====================================================== + it('modify fill stroke and stroke width on hover with circle', function (done) { + var stage = addStage(); + var layer = new Konva.Layer({ + throttle: 999, + }); + var circle = new Konva.Circle({ + x: 380, + y: stage.height() / 2, + radius: 70, + strokeWidth: 4, + fill: 'red', + stroke: 'black', + }); + + circle.on('mouseover', function () { + this.fill('yellow'); + this.stroke('purple'); + this.strokeWidth(20); + //console.log('mouseover') + layer.draw(); + }); + + circle.on('mouseout', function () { + this.fill('red'); + this.stroke('black'); + this.strokeWidth(4); + //console.log('mouseout') + layer.draw(); + }); + + layer.add(circle); + stage.add(layer); + + assert.equal(circle.fill(), 'red', 'circle fill should be red'); + assert.equal(circle.stroke(), 'black', 'circle stroke should be black'); + + setTimeout(function () { + simulateMouseMove(stage, { + x: 377, + y: 101, + }); + + assert.equal(circle.fill(), 'yellow', 'circle fill should be yellow'); + assert.equal(circle.stroke(), 'purple', 'circle stroke should be purple'); + + setTimeout(function () { + // move mouse back out of circle + simulateMouseMove(stage, { + x: 157, + y: 138, + }); + + assert.equal(circle.fill(), 'red', 'circle fill should be red'); + assert.equal(circle.stroke(), 'black', 'circle stroke should be black'); + done(); + }, 20); + }, 20); + }); + + it.skip('mouseleave and mouseenter', function () { + var stage = addStage(); + var layer = new Konva.Layer({ + throttle: 999, + }); + var circle = new Konva.Circle({ + x: 100, + y: 100, + radius: 70, + strokeWidth: 4, + fill: 'red', + stroke: 'black', + }); + layer.add(circle); + stage.add(layer); + + var mouseenter = 0; + circle.on('mouseenter', () => { + mouseenter += 1; + }); + + var mouseleave = 0; + circle.on('mouseleave', () => { + mouseleave += 1; + }); + + simulateMouseMove(stage, { + x: 10, + y: 10, + }); + simulateMouseMove(stage, { + x: 100, + y: 100, + }); + simulateMouseMove(stage, { + x: 100, + y: 100, + }); + assert.equal(mouseenter, 1); + assert.equal(mouseleave, 0); + simulateMouseMove(stage, { + x: 10, + y: 10, + }); + assert.equal(mouseenter, 1); + assert.equal(mouseleave, 1); + }); + + // ====================================================== + it('mousedown mouseup mouseover mouseout mousemove click dblclick', function (done) { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'red', + stroke: 'black', + strokeWidth: 4, + }); + + // desktop events + var mousedown = false; + var mouseup = false; + var click = false; + var dblclick = false; + var mouseover = false; + var mouseout = false; + var mousemove = false; + + circle.on('mousedown', function () { + mousedown = true; + //log('mousedown'); + }); + + circle.on('mouseup', function () { + mouseup = true; + //log('mouseup'); + }); + + circle.on('mouseover', function () { + mouseover = true; + //log('mouseover'); + }); + + circle.on('mouseout', function () { + mouseout = true; + //log('mouseout'); + }); + + circle.on('mousemove', function () { + mousemove = true; + //log('mousemove'); + }); + + circle.on('click', function () { + click = true; + //log('click'); + }); + + circle.on('dblclick', function () { + dblclick = true; + //log('dblclick'); + }); + + layer.add(circle); + stage.add(layer); + + setTimeout(function () { + // move mouse to center of circle to trigger mouseover + simulateMouseMove(stage, { + x: 290, + y: 100, + }); + + assert(mouseover, '1) mouseover should be true'); + assert(mousemove, '1) mousemove should be true'); + assert(!mousedown, '1) mousedown should be false'); + assert(!mouseup, '1) mouseup should be false'); + assert(!click, '1) click should be false'); + assert(!dblclick, '1) dblclick should be false'); + assert(!mouseout, '1) mouseout should be false'); + + setTimeout(function () { + // move mouse again inside circle to trigger mousemove + simulateMouseMove(stage, { + x: 290, + y: 100, + }); + + assert(mouseover, '2) mouseover should be true'); + assert(mousemove, '2) mousemove should be true'); + assert(!mousedown, '2) mousedown should be false'); + assert(!mouseup, '2) mouseup should be false'); + assert(!click, '2) click should be false'); + assert(!dblclick, '2) dblclick should be false'); + assert(!mouseout, '2) mouseout should be false'); + + // mousedown inside circle + simulateMouseDown(stage, { + x: 290, + y: 100, + }); + + assert(mouseover, '3) mouseover should be true'); + assert(mousemove, '3) mousemove should be true'); + assert(mousedown, '3) mousedown should be true'); + assert(!mouseup, '3) mouseup should be false'); + assert(!click, '3) click should be false'); + assert(!dblclick, '3) dblclick should be false'); + assert(!mouseout, '3) mouseout should be false'); + + // mouseup inside circle + simulateMouseUp(stage, { + x: 290, + y: 100, + }); + + assert(mouseover, '4) mouseover should be true'); + assert(mousemove, '4) mousemove should be true'); + assert(mousedown, '4) mousedown should be true'); + assert(mouseup, '4) mouseup should be true'); + assert(click, '4) click should be true'); + assert(!dblclick, '4) dblclick should be false'); + assert(!mouseout, '4) mouseout should be false'); + + // mousedown inside circle + simulateMouseDown(stage, { + x: 290, + y: 100, + }); + + assert(mouseover, '5) mouseover should be true'); + assert(mousemove, '5) mousemove should be true'); + assert(mousedown, '5) mousedown should be true'); + assert(mouseup, '5) mouseup should be true'); + assert(click, '5) click should be true'); + assert(!dblclick, '5) dblclick should be false'); + assert(!mouseout, '5) mouseout should be false'); + + // mouseup inside circle to trigger double click + simulateMouseUp(stage, { + x: 290, + y: 100, + }); + + assert(mouseover, '6) mouseover should be true'); + assert(mousemove, '6) mousemove should be true'); + assert(mousedown, '6) mousedown should be true'); + assert(mouseup, '6) mouseup should be true'); + assert(click, '6) click should be true'); + assert(dblclick, '6) dblclick should be true'); + assert(!mouseout, '6) mouseout should be false'); + + setTimeout(function () { + // move mouse outside of circle to trigger mouseout + simulateMouseMove(stage, { + x: 0, + y: 100, + }); + + assert(mouseover, '7) mouseover should be true'); + assert(mousemove, '7) mousemove should be true'); + assert(mousedown, '7) mousedown should be true'); + assert(mouseup, '7) mouseup should be true'); + assert(click, '7) click should be true'); + assert(dblclick, '7) dblclick should be true'); + assert(mouseout, '7) mouseout should be true'); + done(); + }, 20); + }, 20); + }, 20); + }); + + // ====================================================== + it('test group mousedown events', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var group = new Konva.Group(); + + var redCircle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 80, + strokeWidth: 4, + fill: 'red', + stroke: 'black', + name: 'red', + }); + + var greenCircle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 40, + strokeWidth: 4, + fill: 'green', + stroke: 'black', + name: 'green', + }); + + group.add(redCircle); + group.add(greenCircle); + + layer.add(group); + stage.add(layer); + + var groupMousedowns = 0; + var greenCircleMousedowns = 0; + + group.on('mousedown', function () { + groupMousedowns++; + }); + + greenCircle.on('mousedown', function () { + greenCircleMousedowns++; + }); + + simulateMouseDown(stage, { + x: 285, + y: 100, + }); + + assert.equal(groupMousedowns, 1, 'groupMousedowns should be 1'); + assert.equal(greenCircleMousedowns, 1, 'greenCircleMousedowns should be 1'); + + simulateMouseDown(stage, { + x: 332, + y: 139, + }); + + assert.equal(groupMousedowns, 2, 'groupMousedowns should be 2'); + assert.equal(greenCircleMousedowns, 1, 'greenCircleMousedowns should be 1'); + + simulateMouseDown(stage, { + x: 285, + y: 92, + }); + + assert.equal(groupMousedowns, 3, 'groupMousedowns should be 3'); + assert.equal(greenCircleMousedowns, 2, 'greenCircleMousedowns should be 2'); + + simulateMouseDown(stage, { + x: 221, + y: 146, + }); + + //assert.equal(groupMousedowns, 4, 'groupMousedowns should be 4'); + assert.equal(greenCircleMousedowns, 2, 'greenCircleMousedowns should be 2'); + }); + + // ====================================================== + it('test mousedown events with antialiasing', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var group = new Konva.Group(); + + var greenCircle = new Konva.Circle({ + x: 50, + y: 50, + radius: 50, + fill: 'green', + name: 'green', + }); + + var groupMousedowns = 0; + group.add(greenCircle); + layer.add(group); + + group.cache(); + group.scale({ + x: 5, + y: 5, + }); + group.on('mousedown', function () { + groupMousedowns++; + }); + + stage.add(layer); + layer.draw(); + + simulateMouseDown(stage, { + x: 135, + y: 30, + }); + + assert.equal(groupMousedowns, 1, 'groupMousedowns should be 1'); + }); + it('test mousemove events with antialiasing', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var group = new Konva.Group({ + name: 'group', + }); + var rect1 = new Konva.Rect({ + x: 0, + y: 0, + width: 100, + height: 100, + fill: 'red', + }); + + var rect2 = new Konva.Rect({ + x: 50, + y: 0, + width: 70, + height: 70, + rotation: 45, + fill: 'green', + }); + group.add(rect1).add(rect2); + layer.add(group); + group.scaleX(5); + group.scaleY(5); + var mouseenterCount = 0; + group.on('mouseenter', function () { + mouseenterCount++; + }); + + stage.add(layer); + + // move mouse slowly + for (var i = 99; i < 129; i++) { + simulateMouseMove(stage, { + x: i, + y: 135, + }); + } + assert.equal(mouseenterCount, 1, 'mouseenterCount should be 1'); + }); + + // ====================================================== + it('group mouseenter events', function (done) { + var stage = addStage(); + var layer = new Konva.Layer(); + var group = new Konva.Group({ + name: 'group', + }); + + var redMouseenters = 0; + var redMouseleaves = 0; + var greenMouseenters = 0; + var greenMouseleaves = 0; + var groupMouseenters = 0; + var groupMouseleaves = 0; + + var redCircle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 80, + strokeWidth: 4, + fill: 'red', + stroke: 'black', + name: 'red', + }); + + var greenCircle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 40, + strokeWidth: 4, + fill: 'green', + stroke: 'black', + name: 'green', + }); + + group.on('mouseenter', function () { + groupMouseenters++; + //console.log('group over') + }); + + group.on('mouseleave', function () { + groupMouseleaves++; + //console.log('group out') + }); + + redCircle.on('mouseenter', function () { + redMouseenters++; + //console.log('red over') + }); + + redCircle.on('mouseleave', function () { + redMouseleaves++; + //console.log('red out') + }); + + greenCircle.on('mouseenter', function () { + greenMouseenters++; + //console.log('green over') + }); + + greenCircle.on('mouseleave', function () { + greenMouseleaves++; + //console.log('green out') + }); + + group.add(redCircle); + group.add(greenCircle); + + layer.add(group); + stage.add(layer); + + setTimeout(function () { + // move mouse outside of circles + simulateMouseMove(stage, { + x: 177, + y: 146, + }); + + assert.equal(redMouseenters, 0, 'redMouseenters should be 0'); + assert.equal(redMouseleaves, 0, 'redMouseleaves should be 0'); + assert.equal(greenMouseenters, 0, 'greenMouseenters should be 0'); + assert.equal(greenMouseleaves, 0, 'greenMouseleaves should be 0'); + assert.equal(groupMouseenters, 0, 'groupMouseenters should be 0'); + assert.equal(groupMouseleaves, 0, 'groupMouseleaves should be 0'); + + setTimeout(function () { + // move mouse inside of red circle + simulateMouseMove(stage, { + x: 236, + y: 145, + }); + + //console.log('groupMouseenters=' + groupMouseenters); + + assert.equal(redMouseenters, 1, 'redMouseenters should be 1'); + assert.equal(redMouseleaves, 0, 'redMouseleaves should be 0'); + assert.equal(greenMouseenters, 0, 'greenMouseenters should be 0'); + assert.equal(greenMouseleaves, 0, 'greenMouseleaves should be 0'); + assert.equal(groupMouseenters, 1, 'groupMouseenters should be 1'); + assert.equal(groupMouseleaves, 0, 'groupMouseleaves should be 0'); + + setTimeout(function () { + // move mouse inside of green circle + simulateMouseMove(stage, { + x: 284, + y: 118, + }); + + assert.equal(redMouseenters, 1, 'redMouseenters should be 1'); + assert.equal(redMouseleaves, 1, 'redMouseleaves should be 1'); + assert.equal(greenMouseenters, 1, 'greenMouseenters should be 1'); + assert.equal(greenMouseleaves, 0, 'greenMouseleaves should be 0'); + assert.equal(groupMouseenters, 1, 'groupMouseenters should be 1'); + assert.equal(groupMouseleaves, 0, 'groupMouseleaves should be 0'); + + setTimeout(function () { + // move mouse back to red circle + + simulateMouseMove(stage, { + x: 345, + y: 105, + }); + + assert.equal(redMouseenters, 2, 'redMouseenters should be 2'); + assert.equal(redMouseleaves, 1, 'redMouseleaves should be 1'); + assert.equal(greenMouseenters, 1, 'greenMouseenters should be 1'); + assert.equal(greenMouseleaves, 1, 'greenMouseleaves should be 1'); + assert.equal(groupMouseenters, 1, 'groupMouseenters should be 1'); + assert.equal(groupMouseleaves, 0, 'groupMouseleaves should be 0'); + + setTimeout(function () { + // move mouse outside of circles + simulateMouseMove(stage, { + x: 177, + y: 146, + }); + + assert.equal(redMouseenters, 2, 'redMouseenters should be 2'); + assert.equal(redMouseleaves, 2, 'redMouseleaves should be 2'); + assert.equal(greenMouseenters, 1, 'greenMouseenters should be 1'); + assert.equal(greenMouseleaves, 1, 'greenMouseleaves should be 1'); + assert.equal(groupMouseenters, 1, 'groupMouseenters should be 1'); + assert.equal(groupMouseleaves, 1, 'groupMouseleaves should be 1'); + + //document.body.appendChild(layer.bufferCanvas.element) + + //layer.bufferCanvas.element.style.marginTop = '220px'; + + done(); + }, 20); + }, 20); + }, 20); + }, 20); + }, 20); + }); + + // ====================================================== + it('test mouseleave with multiple groups', function () { + var stage = addStage(); + var layer = new Konva.Layer({ + id: 'layer', + }); + + var rect1 = new Konva.Rect({ + x: 0, + y: 0, + width: 100, + height: 100, + fill: 'red', + id: 'redRect', + }); + + var rect2 = new Konva.Rect({ + x: 50, + y: 0, + width: 70, + height: 70, + rotation: 45, + fill: 'green', + id: 'greenRect', + }); + + var group = new Konva.Group({ + id: 'group1', + }); + var group2 = new Konva.Group({ + id: 'group2', + }); + group.add(rect1); + group2.add(rect2); + group.add(group2); + layer.add(group); + stage.add(layer); + layer.draw(); + + var groupMouseenter = 0; + var groupMouseleave = 0; + var groupMouseover = 0; + var groupMouseout = 0; + + var group2Mouseleave = 0; + var group2Mouseenter = 0; + var group2Mouseover = 0; + var group2Mouseout = 0; + + group.on('mouseenter', function () { + groupMouseenter += 1; + }); + group.on('mouseleave', function () { + groupMouseleave += 1; + }); + group.on('mouseover', function () { + groupMouseover += 1; + }); + group.on('mouseout', function () { + groupMouseout += 1; + }); + + group2.on('mouseenter', function () { + group2Mouseenter += 1; + }); + group2.on('mouseleave', function () { + group2Mouseleave += 1; + }); + group2.on('mouseover', function () { + group2Mouseover += 1; + }); + group2.on('mouseout', function () { + group2Mouseout += 1; + }); + + simulateMouseMove(stage, { + x: 10, + y: 10, + }); + assert.equal(groupMouseenter, 1, 'move1 : group mouseenter should trigger'); + assert.equal( + group2Mouseenter, + 0, + 'move1 : group2 mouseenter should not trigger' + ); + + assert.equal( + groupMouseleave, + 0, + 'move1 : group mouseleave should not trigger' + ); + assert.equal( + group2Mouseleave, + 0, + 'move1 : group2 mouseleave should not trigger' + ); + + assert.equal(groupMouseover, 1, 'move1 : group mouseover should trigger'); + assert.equal( + group2Mouseover, + 0, + 'move1 : group2 mouseover should not trigger' + ); + + assert.equal(groupMouseout, 0, 'move1 : group mouseout should not trigger'); + assert.equal( + group2Mouseout, + 0, + 'move1 : group2 mouseout should not trigger' + ); + + simulateMouseMove(stage, { + x: 50, + y: 50, + }); + assert.equal( + groupMouseenter, + 1, + 'move2 : group mouseenter should not trigger' + ); + assert.equal( + group2Mouseenter, + 1, + 'move2 : group2 mouseenter should trigger' + ); + + assert.equal( + groupMouseleave, + 0, + 'move2 : group mouseleave should not trigger' + ); + assert.equal( + group2Mouseleave, + 0, + 'move2 : group2 mouseleave should not trigger' + ); + + assert.equal(groupMouseover, 2, 'move2 : group mouseover should trigger'); + assert.equal(group2Mouseover, 1, 'move2 : group2 mouseover should trigger'); + + assert.equal(groupMouseout, 1, 'move2 : group mouseout should trigger'); + assert.equal( + group2Mouseout, + 0, + 'move2 : group2 mouseout should not trigger' + ); + + simulateMouseMove(stage, { + x: 10, + y: 10, + }); + assert.equal( + groupMouseenter, + 1, + 'move3 : group mouseenter should not trigger' + ); + assert.equal( + group2Mouseenter, + 1, + 'move3 : group2 mouseenter should not trigger' + ); + + assert.equal( + groupMouseleave, + 0, + 'move3 : group mouseleave should not trigger' + ); + assert.equal( + group2Mouseleave, + 1, + 'move3 : group2 mouseleave should trigger' + ); + + assert.equal(groupMouseover, 3, 'move3 : group mouseover should trigger'); + assert.equal(group2Mouseover, 1, 'move3 : group2 mouseover should trigger'); + + assert.equal(groupMouseout, 2, 'move3 : group mouseout should trigger'); + assert.equal(group2Mouseout, 1, 'move3 : group2 mouseout should trigger'); + + simulateMouseMove(stage, { + x: 50, + y: 50, + }); + + assert.equal(groupMouseenter, 1, 'move4 : mouseenter should not trigger'); + assert.equal( + group2Mouseenter, + 2, + 'move4 : group2 mouseenter should trigger' + ); + + assert.equal( + groupMouseleave, + 0, + 'move4 : group mouseleave should not trigger' + ); + assert.equal( + group2Mouseleave, + 1, + 'move4 : group2 mouseleave should not trigger' + ); + + assert.equal(groupMouseover, 4, 'move1 : group mouseover should trigger'); + assert.equal(group2Mouseover, 2, 'move1 : group2 mouseover should trigger'); + + assert.equal(groupMouseout, 3, 'move4 : group mouseout should trigger'); + assert.equal( + group2Mouseout, + 1, + 'move4 : group2 mouseout should not trigger' + ); + }); + + // ====================================================== + it('test mouseleave with multiple groups 2', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var group1 = new Konva.Group({ name: 'group1' }); + layer.add(group1); + + var bigRect = new Konva.Rect({ + x: 0, + y: 0, + width: 200, + height: 200, + fill: 'green', + }); + group1.add(bigRect); + + var group21 = new Konva.Group({ name: 'group21' }); + layer.add(group21); + + var group22 = new Konva.Group({ name: 'group22' }); + group21.add(group22); + + var smallShape = new Konva.Rect({ + x: 50, + y: 50, + width: 100, + height: 100, + fill: 'red', + }); + group22.add(smallShape); + stage.draw(); + + var group1Mouseenter = 0; + var group1Mouseleave = 0; + var group1Mouseover = 0; + var group1Mouseout = 0; + + var group21Mouseenter = 0; + var group21Mouseleave = 0; + var group21Mouseover = 0; + var group21Mouseout = 0; + + var group22Mouseenter = 0; + var group22Mouseleave = 0; + var group22Mouseover = 0; + var group22Mouseout = 0; + + group1.on('mouseenter', function () { + group1Mouseenter += 1; + }); + group1.on('mouseleave', function () { + group1Mouseleave += 1; + }); + group1.on('mouseover', function () { + group1Mouseover += 1; + }); + group1.on('mouseout', function () { + group1Mouseout += 1; + }); + + group21.on('mouseenter', function () { + group21Mouseenter += 1; + }); + group21.on('mouseleave', function () { + group21Mouseleave += 1; + }); + group21.on('mouseover', function () { + group21Mouseover += 1; + }); + group21.on('mouseout', function () { + group21Mouseout += 1; + }); + + group22.on('mouseenter', function () { + group22Mouseenter += 1; + }); + group22.on('mouseleave', function () { + group22Mouseleave += 1; + }); + group22.on('mouseover', function () { + group22Mouseover += 1; + }); + group22.on('mouseout', function () { + group22Mouseout += 1; + }); + + simulateMouseMove(stage, { + x: 10, + y: 10, + }); + + assert.equal( + group1Mouseenter, + 1, + 'move1 : group1 mouseenter should trigger' + ); + + simulateMouseMove(stage, { + x: 60, + y: 60, + }); + assert.equal( + group21Mouseenter, + 1, + 'move2 : group21 mouseenter should trigger' + ); + assert.equal( + group22Mouseenter, + 1, + 'move2 : group22 mouseenter should trigger' + ); + assert.equal( + group1Mouseleave, + 1, + 'move2 : group1 mouseleave should trigger' + ); + + simulateMouseMove(stage, { + x: 10, + y: 10, + }); + + assert.equal( + group21Mouseleave, + 1, + 'move3 : group21 mouseleave should trigger' + ); + assert.equal( + group22Mouseleave, + 1, + 'move3 : group22 mouseleave should trigger' + ); + assert.equal( + group1Mouseenter, + 2, + 'move3 : group1 mouseenter should trigger' + ); + }); + + // ====================================================== + it('test mouseleave and mouseenter on deep nesting', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + // top group + var group = new Konva.Group({ + x: 0, + y: 0, + width: stage.width(), + height: stage.height(), + name: 'top-group', + }); + layer.add(group); + + // circle inside top group + var circle = new Konva.Circle({ + x: 50, + y: 50, + radius: 50, + fill: 'green', + }); + group.add(circle); + + // two level nesting + var group2 = new Konva.Group({ + x: 0, + y: 0, + name: 'group-2', + }); + group.add(group2); + + var group3 = new Konva.Group({ + x: 0, + y: 0, + name: 'group-3', + }); + group2.add(group3); + + // circle inside deep group + var circle2 = new Konva.Circle({ + x: 50, + y: 50, + radius: 20, + fill: 'white', + }); + group3.add(circle2); + + layer.draw(); + + var mouseenter = 0; + var mouseleave = 0; + group.on('mouseenter', function () { + mouseenter += 1; + }); + group.on('mouseleave', function () { + mouseleave += 1; + }); + // move to big circle + simulateMouseMove(stage, { + x: 20, + y: 20, + }); + assert.equal(mouseenter, 1, 'first enter big circle'); + assert.equal(mouseleave, 0, 'no leave on first move'); + + // move to small inner circle + simulateMouseMove(stage, { + x: 50, + y: 50, + }); + assert.equal(mouseenter, 1, 'enter small circle'); + assert.equal(mouseleave, 0, 'no leave on second move'); + + // move to big circle + simulateMouseMove(stage, { + x: 20, + y: 20, + }); + assert.equal(mouseenter, 1, 'second enter big circle'); + assert.equal(mouseleave, 0, 'no leave on third move'); + + // move out of group + simulateMouseMove(stage, { + x: 0, + y: 0, + }); + assert.equal(mouseenter, 1, 'mouseenter = 1 at the end'); + assert.equal(mouseleave, 1, 'first mouseleave'); + }); + + // ====================================================== + it('test dblclick to a wrong target', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var leftRect = new Konva.Rect({ + x: 0, + y: 0, + width: 100, + height: 100, + fill: 'red', + }); + layer.add(leftRect); + + var rightRect = new Konva.Rect({ + x: 100, + y: 0, + width: 100, + height: 100, + fill: 'blue', + }); + layer.add(rightRect); + + stage.draw(); + + var leftRectSingleClick = 0; + var rightRectSingleClick = 0; + var rightRectDblClick = 0; + + leftRect.on('click', function () { + leftRectSingleClick++; + }); + rightRect.on('click', function () { + rightRectSingleClick++; + }); + rightRect.on('dblclick', function () { + rightRectDblClick++; + }); + + simulateMouseDown(stage, { + x: 50, + y: 50, + }); + simulateMouseUp(stage, { + x: 50, + y: 50, + }); + assert.equal(leftRectSingleClick, 1, 'leftRect trigger a click'); + + simulateMouseDown(stage, { + x: 150, + y: 50, + }); + simulateMouseUp(stage, { + x: 150, + y: 50, + }); + assert.equal(rightRectSingleClick, 1, 'rightRect trigger a click'); + assert.equal(rightRectDblClick, 0, 'rightRect dblClick should not trigger'); + }); + + // ====================================================== + it('test mouseleave + mouseenter with deep nesting', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var leftRect = new Konva.Rect({ + x: 0, + y: 0, + width: 100, + height: 100, + fill: 'green', + }); + layer.add(leftRect); + + var rightGrandParentGroup = new Konva.Group({ + name: 'rightGrandParentGroup', + }); + layer.add(rightGrandParentGroup); + + var rightParentGroup = new Konva.Group({ name: 'rightParentGroup' }); + rightGrandParentGroup.add(rightParentGroup); + + var rightRect = new Konva.Rect({ + x: 100, + y: 0, + width: 100, + height: 100, + fill: 'red', + }); + rightParentGroup.add(rightRect); + stage.draw(); + + var leftRectMouseenter = 0; + var leftRectMouseleave = 0; + var leftRectMouseover = 0; + var leftRectMouseout = 0; + + var rightGrandParentGroupMouseenter = 0; + var rightGrandParentGroupMouseleave = 0; + var rightGrandParentGroupMouseover = 0; + var rightGrandParentGroupMouseout = 0; + + var rightParentGroupMouseenter = 0; + var rightParentGroupMouseleave = 0; + var rightParentGroupMouseover = 0; + var rightParentGroupMouseout = 0; + + var rightRectMouseenter = 0; + var rightRectMouseleave = 0; + var rightRectMouseover = 0; + var rightRectMouseout = 0; + + leftRect.on('mouseenter', function () { + leftRectMouseenter += 1; + }); + leftRect.on('mouseleave', function () { + leftRectMouseleave += 1; + }); + leftRect.on('mouseover', function () { + leftRectMouseover += 1; + }); + leftRect.on('mouseout', function () { + leftRectMouseout += 1; + }); + + rightGrandParentGroup.on('mouseenter', function () { + rightGrandParentGroupMouseenter += 1; + }); + rightGrandParentGroup.on('mouseleave', function () { + rightGrandParentGroupMouseleave += 1; + }); + rightGrandParentGroup.on('mouseover', function () { + rightGrandParentGroupMouseover += 1; + }); + rightGrandParentGroup.on('mouseout', function () { + rightGrandParentGroupMouseout += 1; + }); + + rightParentGroup.on('mouseenter', function () { + rightParentGroupMouseenter += 1; + }); + rightParentGroup.on('mouseleave', function () { + rightParentGroupMouseleave += 1; + }); + rightParentGroup.on('mouseover', function () { + rightParentGroupMouseover += 1; + }); + rightParentGroup.on('mouseout', function () { + rightParentGroupMouseout += 1; + }); + + rightRect.on('mouseenter', function () { + rightRectMouseenter += 1; + }); + rightRect.on('mouseleave', function () { + rightRectMouseleave += 1; + }); + rightRect.on('mouseover', function () { + rightRectMouseover += 1; + }); + rightRect.on('mouseout', function () { + rightRectMouseout += 1; + }); + + simulateMouseMove(stage, { + x: 50, + y: 50, + }); + + assert.equal( + leftRectMouseenter, + 1, + 'move1 : leftRectMouseenter mouseenter should trigger' + ); + + simulateMouseMove(stage, { + x: 150, + y: 50, + }); + + assert.equal( + leftRectMouseleave, + 1, + 'move2 : leftRectMouseleave mouseleave should trigger' + ); + assert.equal( + rightRectMouseenter, + 1, + 'move2 : rightRect mouseenter should trigger' + ); + assert.equal( + rightParentGroupMouseenter, + 1, + 'move2 : rightParentGroup mouseenter should trigger' + ); + assert.equal( + rightGrandParentGroupMouseenter, + 1, + 'move2 : rightGrandParentGroup mouseenter should trigger' + ); + + simulateMouseMove(stage, { + x: 50, + y: 50, + }); + + assert.equal( + rightRectMouseleave, + 1, + 'move3 : rightRect mouseleave should trigger' + ); + assert.equal( + rightParentGroupMouseleave, + 1, + 'move3 : rightParentGroup mouseleave should trigger' + ); + assert.equal( + rightGrandParentGroupMouseleave, + 1, + 'move3 : rightGrandParentGroup mouseleave should trigger' + ); + + assert.equal( + leftRectMouseenter, + 2, + 'move3 : leftRectMouseenter mouseenter should trigger' + ); + }); + + // ====================================================== + it('test event bubbling', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: 380, + y: stage.height() / 2, + radius: 70, + strokeWidth: 4, + fill: 'red', + stroke: 'black', + id: 'myCircle', + }); + + var group1 = new Konva.Group(); + var group2 = new Konva.Group(); + + /* + * stage + * | + * layer + * | + * group2 + * | + * group1 + * | + * circle + */ + + group1.add(circle); + group2.add(group1); + layer.add(group2); + stage.add(layer); + + // events array + var e = []; + + circle.on('click', function () { + e.push('circle'); + }); + group1.on('click', function () { + e.push('group1'); + }); + group2.on('click', function () { + e.push('group2'); + }); + layer.on('click', function (evt) { + //console.log(evt) + assert.equal(evt.target.id(), 'myCircle'); + assert.equal(evt.type, 'click'); + e.push('layer'); + }); + stage.on('click', function (evt) { + e.push('stage'); + }); + // click on circle + simulateMouseDown(stage, { + x: 374, + y: 114, + }); + simulateMouseUp(stage, { + x: 374, + y: 114, + }); + Konva.DD._endDragAfter({ dragEndNode: circle }); + + assert.equal( + e.toString(), + 'circle,group1,group2,layer,stage', + 'problem with event bubbling' + ); + }); + + // ====================================================== + it('test custom circle hit function', function (done) { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: 380, + y: stage.height() / 2, + radius: 70, + strokeWidth: 4, + fill: 'red', + stroke: 'black', + hitFunc: function (context) { + var _context = context._context; + + _context.beginPath(); + _context.arc(0, 0, this.getRadius() + 100, 0, Math.PI * 2, true); + _context.closePath(); + context.fillStrokeShape(this); + }, + }); + + circle.setDraggable(true); + + layer.add(circle); + stage.add(layer); + + var mouseovers = 0; + var mouseouts = 0; + + circle.on('mouseover', function () { + mouseovers++; + }); + + circle.on('mouseout', function () { + mouseouts++; + }); + + setTimeout(function () { + // move mouse far outside circle + simulateMouseMove(stage, { + x: 113, + y: 112, + }); + + setTimeout(function () { + assert.equal(mouseovers, 0, '1) mouseovers should be 0'); + assert.equal(mouseouts, 0, '1) mouseouts should be 0'); + + simulateMouseMove(stage, { + x: 286, + y: 118, + }); + + assert.equal(mouseovers, 1, '2) mouseovers should be 1'); + assert.equal(mouseouts, 0, '2)mouseouts should be 0'); + + setTimeout(function () { + simulateMouseMove(stage, { + x: 113, + y: 112, + }); + + assert.equal(mouseovers, 1, '3) mouseovers should be 1'); + assert.equal(mouseouts, 1, '3) mouseouts should be 1'); + + // set drawBufferFunc with setter + + circle.hitFunc(function (context) { + var _context = context._context; + _context.beginPath(); + _context.arc(0, 0, this.getRadius() - 50, 0, Math.PI * 2, true); + _context.closePath(); + context.fillStrokeShape(this); + }); + + layer.getHitCanvas().getContext().clear(); + layer.drawHit(); + + setTimeout(function () { + // move mouse far outside circle + simulateMouseMove(stage, { + x: 113, + y: 112, + }); + + assert.equal(mouseovers, 1, '4) mouseovers should be 1'); + assert.equal(mouseouts, 1, '4) mouseouts should be 1'); + + setTimeout(function () { + simulateMouseMove(stage, { + x: 286, + y: 118, + }); + + assert.equal(mouseovers, 1, '5) mouseovers should be 1'); + assert.equal(mouseouts, 1, '5) mouseouts should be 1'); + + setTimeout(function () { + simulateMouseMove(stage, { + x: 321, + y: 112, + }); + + assert.equal(mouseovers, 1, '6) mouseovers should be 1'); + assert.equal(mouseouts, 1, '6) mouseouts should be 1'); + + setTimeout(function () { + // move to center of circle + simulateMouseMove(stage, { + x: 375, + y: 112, + }); + + assert.equal(mouseovers, 2, '7) mouseovers should be 2'); + assert.equal(mouseouts, 1, '7) mouseouts should be 1'); + + done(); + }, 20); + }, 20); + }, 20); + }, 20); + }, 20); + }, 20); + }, 20); + }); + + it('change ratio for hit graph', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 50, + stroke: 'black', + fill: 'red', + strokeWidth: 5, + draggable: true, + }); + + layer.add(circle); + stage.add(layer); + + layer.getHitCanvas().setPixelRatio(0.5); + + layer.draw(); + var shape = layer.getIntersection({ + x: stage.width() / 2 - 55, + y: stage.height() / 2 - 55, + }); + assert.equal(!!shape, false, 'no shape here'); + shape = layer.getIntersection({ + x: stage.width() / 2 + 55, + y: stage.height() / 2 + 55, + }); + assert.equal(!!shape, false, 'no shape here'); + shape = layer.getIntersection({ + x: stage.width() / 2, + y: stage.height() / 2, + }); + assert.equal(shape, circle); + }); + + it('double click after click should trigger event', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var bigRect = new Konva.Rect({ + x: 0, + y: 0, + width: 200, + height: 200, + fill: 'yellow', + }); + layer.add(bigRect); + + var smallShape = new Konva.Circle({ + x: 100, + y: 100, + width: 100, + fill: 'red', + }); + layer.add(smallShape); + layer.draw(); + + var bigClicks = 0; + var smallClicks = 0; + var smallDblClicks = 0; + + bigRect.on('click', function () { + bigClicks += 1; + }); + + smallShape.on('click', function () { + smallClicks += 1; + }); + + smallShape.on('dblclick', function () { + smallDblClicks += 1; + }); + + simulateMouseDown(stage, { + x: 10, + y: 10, + }); + simulateMouseUp(stage, { + x: 10, + y: 10, + }); + + assert.equal(bigClicks, 1, 'single click on big rect'); + assert.equal(smallClicks, 0, 'no click on small rect'); + assert.equal(smallDblClicks, 0, 'no dblclick on small rect'); + + simulateMouseDown(stage, { + x: 100, + y: 100, + }); + simulateMouseUp(stage, { + x: 100, + y: 100, + }); + + assert.equal(bigClicks, 1, 'single click on big rect'); + assert.equal(smallClicks, 1, 'single click on small rect'); + assert.equal(smallDblClicks, 0, 'no dblclick on small rect'); + + simulateMouseDown(stage, { + x: 100, + y: 100, + }); + simulateMouseUp(stage, { + x: 100, + y: 100, + }); + + assert.equal(bigClicks, 1, 'single click on big rect'); + assert.equal(smallClicks, 2, 'second click on small rect'); + assert.equal(smallDblClicks, 1, 'single dblclick on small rect'); + }); + + it('click on stage and second click on shape should not trigger double click (check after dblclick)', function (done) { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var bigRect = new Konva.Rect({ + x: 50, + y: 50, + width: 200, + height: 200, + fill: 'yellow', + }); + layer.add(bigRect); + + layer.draw(); + + var bigClicks = 0; + var bigDblClicks = 0; + + // make dblclick + simulateMouseDown(stage, { + x: 100, + y: 100, + }); + simulateMouseUp(stage, { + x: 100, + y: 100, + }); + simulateMouseDown(stage, { + x: 100, + y: 100, + }); + simulateMouseUp(stage, { + x: 100, + y: 100, + }); + + bigRect.on('click', function () { + bigClicks += 1; + }); + + bigRect.on('dblclick', function () { + bigDblClicks += 1; + }); + + simulateMouseDown(stage, { + x: 10, + y: 10, + }); + simulateMouseUp(stage, { + x: 10, + y: 10, + }); + + assert.equal(bigClicks, 0); + assert.equal(bigDblClicks, 0); + + simulateMouseDown(stage, { + x: 100, + y: 100, + }); + simulateMouseUp(stage, { + x: 100, + y: 100, + }); + + assert.equal(bigClicks, 1); + assert.equal(bigDblClicks, 0); + + done(); + }); + + it('click and dblclick with cancel bubble on container', function (done) { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var bigRect = new Konva.Rect({ + x: 50, + y: 50, + width: 200, + height: 200, + fill: 'yellow', + }); + layer.add(bigRect); + + layer.draw(); + + var clicks = 0; + var dblclicks = 0; + + layer.on('click', (e) => { + e.cancelBubble = true; + clicks += 1; + }); + + layer.on('dblclick', (e) => { + e.cancelBubble = true; + dblclicks += 1; + }); + + // make dblclick + simulateMouseDown(stage, { + x: 100, + y: 100, + }); + simulateMouseUp(stage, { + x: 100, + y: 100, + }); + simulateMouseDown(stage, { + x: 100, + y: 100, + }); + simulateMouseUp(stage, { + x: 100, + y: 100, + }); + + assert.equal(clicks, 2); + assert.equal(dblclicks, 1); + + done(); + }); + + it('double click after drag should trigger event', function (done) { + // skip this test for NodeJS because it fails sometimes + // TODO: WHY?!?!?! + if (!Konva.isBrowser) { + return done(); + } + + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var bigRect = new Konva.Rect({ + x: 0, + y: 0, + width: 200, + height: 200, + fill: 'yellow', + draggable: true, + }); + layer.add(bigRect); + + var smallShape = new Konva.Circle({ + x: 100, + y: 100, + width: 100, + fill: 'red', + }); + layer.add(smallShape); + layer.draw(); + + var bigClicks = 0; + var smallClicks = 0; + var smallDblClicks = 0; + + bigRect.on('click', function () { + bigClicks += 1; + }); + + smallShape.on('click', function () { + smallClicks += 1; + }); + + smallShape.on('dblclick', function () { + smallDblClicks += 1; + }); + + simulateMouseDown(stage, { + x: 10, + y: 10, + }); + simulateMouseMove(stage, { + x: 15, + y: 15, + }); + simulateMouseUp(stage, { + x: 15, + y: 15, + }); + + assert.equal(bigClicks, 0, 'single click on big rect (1)'); + assert.equal(smallClicks, 0, 'no click on small rect'); + assert.equal(smallDblClicks, 0, 'no dblclick on small rect'); + + setTimeout(function () { + simulateMouseDown(stage, { + x: 100, + y: 100, + }); + simulateMouseUp(stage, { + x: 100, + y: 100, + }); + + assert.equal(bigClicks, 0, 'single click on big rect (2)'); + assert.equal(smallClicks, 1, 'single click on small rect'); + assert.equal(smallDblClicks, 0, 'no dblclick on small rect'); + + setTimeout(function () { + simulateMouseDown(stage, { + x: 100, + y: 100, + }); + simulateMouseUp(stage, { + x: 100, + y: 100, + }); + + assert.equal(bigClicks, 0, 'single click on big rect (3)'); + assert.equal(smallClicks, 2, 'second click on small rect'); + assert.equal(smallDblClicks, 1, 'single dblclick on small rect'); + + done(); + }, 5); + }); + }); + + it('test mouseenter on empty stage', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var mouseenterCount = 0; + stage.on('mouseenter', function () { + mouseenterCount += 1; + }); + + var top = (stage.content && stage.content.getBoundingClientRect().top) || 0; + var evt = { + clientX: 10, + clientY: 10 + top, + button: 0, + type: 'mouseenter', + }; + + stage._pointerenter(evt); + + assert.equal(mouseenterCount, 1, 'mouseenterCount should be 1'); + }); + + it('test mouseleave on empty stage', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var mouseleave = 0; + stage.on('mouseleave', function () { + mouseleave += 1; + }); + + var top = (stage.content && stage.content.getBoundingClientRect().top) || 0; + var evt = { + clientX: 0, + clientY: 0 + top, + button: 0, + type: 'mouseleave', + }; + + stage._pointerleave(evt); + + assert.equal(mouseleave, 1, 'mouseleave should be 1'); + }); + + it('test mouseleave from the shape', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var circle = new Konva.Circle({ + fill: 'red', + radius: 100, + x: 200, + y: 0, + }); + layer.add(circle); + layer.draw(); + + var mouseleave = 0; + stage.on('mouseleave', function () { + mouseleave += 1; + }); + + var mouseout = 0; + stage.on('mouseout', function () { + mouseout += 1; + }); + + var circleMouseleave = 0; + circle.on('mouseleave', function () { + circleMouseleave += 1; + }); + + var circleMouseout = 0; + circle.on('mouseout', function () { + circleMouseout += 1; + }); + + var layerMouseleave = 0; + layer.on('mouseleave', function () { + layerMouseleave += 1; + }); + + var layerMouseout = 0; + layer.on('mouseout', function () { + layerMouseout += 1; + }); + + // move into a circle + simulateMouseMove(stage, { x: 200, y: 5 }); + + var top = (stage.content && stage.content.getBoundingClientRect().top) || 0; + var evt = { + clientX: 200, + clientY: -5 + top, + button: 0, + type: 'mouseleave', + }; + + stage._pointerleave(evt); + + assert.equal(circleMouseleave, 1, 'circleMouseleave should be 1'); + assert.equal(circleMouseout, 1, 'circleMouseout should be 1'); + assert.equal(layerMouseleave, 1, 'layerMouseleave should be 1'); + assert.equal(layerMouseout, 1, 'layerMouseout should be 1'); + assert.equal(mouseleave, 1, 'mouseleave should be 1'); + assert.equal(mouseout, 1, 'mouseout should be 1'); + }); + + it('should not trigger mouseenter on stage when we go to the shape from empty space', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + width: 50, + height: 50, + fill: 'red', + }); + layer.add(rect); + + layer.draw(); + + var mouseenter = 0; + stage.on('mouseenter', function () { + debugger; + mouseenter += 1; + }); + + simulateMouseMove(stage, { + x: 100, + y: 100, + }); + + simulateMouseMove(stage, { + x: 25, + y: 25, + }); + + assert.equal(mouseenter, 0, 'mouseenter should be 1'); + }); + + it('should not trigger mouseleave after shape destroy', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + width: 50, + height: 50, + fill: 'red', + }); + layer.add(rect); + + layer.draw(); + + var mouseout = 0; + var mouseleave = 0; + stage.on('mouseout', function () { + mouseout += 1; + }); + + rect.on('mouseout', function () { + mouseout += 1; + }); + + stage.on('mouseleave', function () { + mouseleave += 1; + }); + + rect.on('mouseleave', function () { + mouseleave += 1; + }); + + simulateMouseMove(stage, { + x: 10, + y: 10, + }); + + rect.destroy(); + layer.draw(); + simulateMouseMove(stage, { + x: 20, + y: 20, + }); + assert.equal(mouseout, 0); + assert.equal(mouseleave, 0); + }); + + it('should not trigger mouseenter on stage twice when we go to the shape directly', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + width: 50, + height: 50, + fill: 'red', + }); + layer.add(rect); + + layer.draw(); + + var mouseenter = 0; + stage.on('mouseenter', function () { + mouseenter += 1; + }); + + var top = (stage.content && stage.content.getBoundingClientRect().top) || 0; + var evt = { + clientX: 10, + clientY: 10 + top, + button: 0, + type: 'mouseenter', + }; + + stage._pointerenter(evt); + + simulateMouseMove(stage, { + x: 10, + y: 10, + }); + + stage._pointerleave({ + ...evt, + type: 'mouseleave', + }); + + assert.equal(mouseenter, 1, 'mouseenter should be 1'); + }); + + it('should trigger mouse events if we set Konva.hitOnDragEnabled = true', function () { + Konva.hitOnDragEnabled = true; + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + width: 50, + height: 50, + fill: 'red', + draggable: true, + }); + layer.add(rect); + + layer.draw(); + + var mousemove = 0; + rect.on('mousemove', function () { + mousemove += 1; + }); + + simulateMouseDown(stage, { + x: 10, + y: 10, + }); + + simulateMouseMove(stage, { + x: 20, + y: 20, + }); + simulateMouseMove(stage, { + x: 30, + y: 30, + }); + simulateMouseUp(stage, { + x: 30, + y: 30, + }); + + assert.equal(mousemove, 2, 'mousemove should be 2'); + Konva.hitOnDragEnabled = false; + }); + + it('test scaled with CSS stage', function () { + if (isNode) { + return; + } + var stage = addStage(); + + stage.container().style.transform = 'scale(0.5)'; + stage.container().style.transformOrigin = 'left top'; + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + width: 50, + height: 50, + fill: 'red', + draggable: true, + }); + layer.add(rect); + + layer.draw(); + + var clicks = 0; + rect.on('click', function () { + clicks += 1; + }); + + simulateMouseDown(stage, { + x: 40, + y: 40, + }); + + simulateMouseUp(stage, { + x: 40, + y: 40, + }); + + // should not register this click this click, because the stage is scaled + assert.equal(clicks, 0, 'clicks not triggered'); + assert.deepEqual(stage.getPointerPosition(), { x: 80, y: 80 }); + + // try touch too + simulateTouchStart(stage, { + x: 30, + y: 30, + }); + simulateTouchEnd(stage, { + x: 30, + y: 30, + }); + assert.deepEqual(stage.getPointerPosition(), { x: 60, y: 60 }); + }); + + it('mousedown on empty then mouseup on shape', function () { + if (isNode) { + return; + } + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + stage.on('mousedown mousemove mouseup click', function (e) { + console.log('state', e.type); + }); + + var rect = new Konva.Rect({ + width: 50, + height: 50, + fill: 'red', + draggable: true, + }); + layer.add(rect); + + layer.draw(); + + var clicks = 0; + rect.on('click', function () { + console.log('click'); + clicks += 1; + if (clicks === 2) { + debugger; + } + }); + + simulateMouseDown(stage, { + x: 40, + y: 40, + }); + + simulateMouseUp(stage, { + x: 40, + y: 40, + }); + + // trigger click + assert.equal(clicks, 1, 'clicks not triggered'); + + // mousedown outside + simulateMouseDown(stage, { + x: 60, + y: 6, + }); + // move into rect + simulateMouseMove(stage, { + x: 40, + y: 40, + }); + // mouseup inside rect + simulateMouseUp(stage, { + x: 40, + y: 40, + }); + // it shouldn't trigger the click event!!! + assert.equal(clicks, 1, 'clicks not triggered'); + }); +}); diff --git a/test/unit/Node-cache-test.ts b/test/unit/Node-cache-test.ts new file mode 100644 index 000000000..687a1c5ad --- /dev/null +++ b/test/unit/Node-cache-test.ts @@ -0,0 +1,1551 @@ +import { assert } from 'chai'; +import { + addStage, + Konva, + compareLayerAndCanvas, + cloneAndCompareLayer, + compareCanvases, + createCanvas, + loadImage, + getPixelRatio, +} from './test-utils'; + +describe('Caching', function () { + it('cache simple rectangle', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + + var rect = new Konva.Rect({ + x: 100, + y: 50, + width: 100, + height: 50, + fill: 'green', + draggable: true, + }); + rect.cache(); + + layer.add(rect); + stage.add(layer); + + var canvas = createCanvas(); + var context = canvas.getContext('2d'); + context.beginPath(); + context.rect(100, 50, 100, 50); + context.closePath(); + context.fillStyle = 'green'; + context.fill(); + + compareLayerAndCanvas(layer, canvas, 10); + cloneAndCompareLayer(layer); + }); + + it('cache simple rectangle with transform', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + + var rect = new Konva.Rect({ + x: 100, + y: 50, + width: 100, + height: 50, + rotation: 45, + scaleY: 2, + fill: 'green', + }); + rect.cache(); + + layer.add(rect); + stage.add(layer); + + var canvas = createCanvas(); + var context = canvas.getContext('2d'); + context.translate(100, 50); + context.rotate(Math.PI / 4); + context.beginPath(); + context.rect(0, 0, 100, 100); + context.closePath(); + context.fillStyle = 'green'; + context.fill(); + + compareLayerAndCanvas(layer, canvas, 200, 100); + cloneAndCompareLayer(layer, 150, 100); + }); + + it('cache rectangle with fill and stroke', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + + var rect = new Konva.Rect({ + x: 100, + y: 50, + width: 100, + height: 50, + fill: 'green', + stroke: 'black', + strokeWidth: 20, + }); + rect.cache(); + + layer.add(rect); + stage.add(layer); + + var canvas = createCanvas(); + var context = canvas.getContext('2d'); + context.beginPath(); + context.rect(100, 50, 100, 50); + context.closePath(); + context.fillStyle = 'green'; + context.fill(); + context.lineWidth = 20; + context.stroke(); + compareLayerAndCanvas(layer, canvas, 50); + cloneAndCompareLayer(layer, 50); + }); + + it('cache rectangle with fill and opacity', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + + var rect = new Konva.Rect({ + x: 100, + y: 50, + width: 100, + height: 50, + fill: 'green', + opacity: 0.5, + }); + rect.cache(); + rect.opacity(0.3); + + layer.add(rect); + stage.add(layer); + + var canvas = createCanvas(); + var context = canvas.getContext('2d'); + context.globalAlpha = 0.3; + context.beginPath(); + context.rect(100, 50, 100, 50); + context.closePath(); + context.fillStyle = 'green'; + context.fill(); + compareLayerAndCanvas(layer, canvas, 5); + }); + + it('cache rectangle with fill, stroke opacity', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + + var rect = new Konva.Rect({ + x: 100, + y: 50, + width: 100, + height: 50, + fill: 'green', + opacity: 0.5, + stroke: 'black', + strokeWidth: 10, + }); + rect.cache(); + rect.opacity(0.3); + + layer.add(rect); + stage.add(layer); + + cloneAndCompareLayer(layer, 100); + }); + + // skip, because opacity rendering of cached shape is different + // nothing we can do here + it('cache rectangle with fill, shadow and opacity', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + + var rect = new Konva.Rect({ + x: 10, + y: 10, + width: 100, + height: 50, + fill: 'green', + opacity: 0.5, + shadowBlur: 10, + shadowColor: 'black', + draggable: true, + }); + // rect.cache(); + // rect.opacity(0.3); + + layer.add(rect.clone({ y: 50, x: 50, shadowEnabled: false })); + layer.add(rect); + stage.add(layer); + + cloneAndCompareLayer(layer, 10); + }); + + it('cache rectangle with fill and simple shadow', function () { + Konva.pixelRatio = 1; + var stage = addStage(); + + var layer = new Konva.Layer(); + + var rect = new Konva.Rect({ + x: 100, + y: 50, + width: 100, + height: 50, + fill: 'green', + shadowColor: 'black', + shadowBlur: 10, + draggable: true, + }); + rect.cache(); + + layer.add(rect); + stage.add(layer); + + var canvas = createCanvas(); + var context = canvas.getContext('2d'); + context.beginPath(); + context.rect(100, 50, 100, 50); + context.closePath(); + context.fillStyle = 'green'; + context.shadowColor = 'black'; + context.shadowBlur = 10; + context.fill(); + + compareLayerAndCanvas(layer, canvas, 100); + Konva.pixelRatio = getPixelRatio(); + }); + + it('cache rectangle with fill and shadow with offset', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + + var rect = new Konva.Rect({ + x: 100, + y: 50, + width: 50, + height: 25, + fill: 'green', + shadowOffsetX: 10, + shadowOffsetY: 10, + shadowColor: 'black', + shadowBlur: 10, + }); + rect.cache(); + + layer.add(rect); + stage.add(layer); + + var canvas = createCanvas(); + var context = canvas.getContext('2d'); + + context.translate(100, 50); + context.beginPath(); + context.rect(0, 0, 50, 25); + context.closePath(); + context.fillStyle = 'green'; + context.shadowColor = 'black'; + context.shadowBlur = 10 * Konva.pixelRatio; + context.shadowOffsetX = 10 * Konva.pixelRatio; + context.shadowOffsetY = 10 * Konva.pixelRatio; + context.fill(); + compareLayerAndCanvas(layer, canvas, 50); + }); + + it('cache rectangle with fill and shadow with negative offset', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + + var rect = new Konva.Rect({ + x: 100, + y: 50, + width: 50, + height: 25, + fill: 'green', + shadowOffsetX: -10, + shadowOffsetY: -10, + shadowColor: 'black', + shadowBlur: 10, + }); + rect.cache(); + + layer.add(rect); + stage.add(layer); + + var canvas = createCanvas(); + var context = canvas.getContext('2d'); + + context.translate(100, 50); + context.beginPath(); + context.rect(0, 0, 50, 25); + context.closePath(); + context.fillStyle = 'green'; + context.shadowColor = 'black'; + context.shadowBlur = 10 * Konva.pixelRatio; + context.shadowOffsetX = -10 * Konva.pixelRatio; + context.shadowOffsetY = -10 * Konva.pixelRatio; + context.fill(); + compareLayerAndCanvas(layer, canvas, 50); + }); + + it('cache rectangle with fill and shadow with negative offset and scale', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + + var rect = new Konva.Rect({ + x: 100, + y: 50, + width: 50, + height: 25, + fill: 'green', + shadowOffsetX: -10, + shadowOffsetY: -10, + shadowColor: 'black', + shadowBlur: 10, + scaleX: 2, + scaleY: 2, + }); + rect.cache(); + + layer.add(rect); + stage.add(layer); + + cloneAndCompareLayer(layer, 200); + }); + + it('cache rectangle with fill and shadow and some transform', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + + var rect = new Konva.Rect({ + x: 100, + y: 50, + width: 50, + height: 25, + fill: 'green', + shadowOffsetX: -10, + shadowOffsetY: -10, + shadowColor: 'black', + shadowBlur: 10, + offsetX: 50, + offsetY: 25, + }); + rect.cache(); + + layer.add(rect); + stage.add(layer); + + var canvas = createCanvas(); + var context = canvas.getContext('2d'); + + context.translate(50, 25); + context.beginPath(); + context.rect(0, 0, 50, 25); + context.closePath(); + context.fillStyle = 'green'; + context.shadowColor = 'black'; + context.shadowBlur = 10 * Konva.pixelRatio; + context.shadowOffsetX = -10 * Konva.pixelRatio; + context.shadowOffsetY = -10 * Konva.pixelRatio; + context.fill(); + compareLayerAndCanvas(layer, canvas, 50); + }); + + // CACHING CONTAINERS + it('cache group with simple rectangle', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + + var group = new Konva.Group({ + x: 100, + y: 50, + }); + + var rect = new Konva.Rect({ + width: 100, + height: 50, + fill: 'green', + }); + group.add(rect); + group.cache(); + + layer.add(group); + stage.add(layer); + + var canvas = createCanvas(); + var context = canvas.getContext('2d'); + context.beginPath(); + context.rect(100, 50, 100, 50); + context.closePath(); + context.fillStyle = 'green'; + context.fill(); + compareLayerAndCanvas(layer, canvas, 10); + cloneAndCompareLayer(layer); + }); + + it('cache group with simple rectangle with transform', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + + var group = new Konva.Group({ + x: 50, + y: 25, + }); + + var rect = new Konva.Rect({ + x: 50, + y: 25, + width: 100, + height: 50, + fill: 'green', + rotation: 45, + }); + group.add(rect); + group.cache(); + + layer.add(group); + stage.add(layer); + cloneAndCompareLayer(layer, 200); + }); + + it('cache group with several shape with transform', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + + var group = new Konva.Group({ + x: 50, + y: 25, + }); + + var rect = new Konva.Rect({ + x: 50, + y: 25, + width: 100, + height: 50, + fill: 'green', + shadowOffsetX: 10, + shadowOffsetY: 10, + shadowBlur: 10, + }); + group.add(rect); + + var circle = new Konva.Circle({ + x: 250, + y: 50, + radius: 25, + fill: 'red', + // rotation on circle should not have any effects + stroke: 'black', + rotation: 45, + scaleX: 2, + scaleY: 2, + }); + group.add(circle); + + group.cache(); + + layer.add(group); + stage.add(layer); + + var canvas = createCanvas(); + var context = canvas.getContext('2d'); + + // draw rect + context.save(); + context.beginPath(); + context.rect(100, 50, 100, 50); + context.closePath(); + context.fillStyle = 'green'; + context.shadowColor = 'black'; + context.shadowBlur = 10 * Konva.pixelRatio; + context.shadowOffsetX = 10 * Konva.pixelRatio; + context.shadowOffsetY = 10 * Konva.pixelRatio; + context.fill(); + context.restore(); + + // circle + context.save(); + context.beginPath(); + context.arc(300, 75, 50, 0, Math.PI * 2); + context.closePath(); + context.fillStyle = 'red'; + context.lineWidth = 4; + context.fill(); + context.stroke(); + context.restore(); + + compareLayerAndCanvas(layer, canvas, 210, 20); + + // recache + group.cache(); + layer.draw(); + compareLayerAndCanvas(layer, canvas, 210, 20); + }); + + it('cache group with rectangle and text', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + + var button = new Konva.Group({ + width: 100, + height: 50, + draggable: true, + }); + + var face = new Konva.Rect({ + fill: 'red', + x: 0, + y: 0, + width: 100, + height: 50, + }); + + var text = new Konva.Text({ + text: 'Wrong button', + x: 15, + y: 20, + }); + + button.add(face); + button.add(text); + + button.cache(); + + layer.add(button); + stage.add(layer); + + cloneAndCompareLayer(layer, 100); + }); + + it('cache layer with several shape with transform', function () { + var stage = addStage(); + + var layer = new Konva.Layer({ + draggable: true, + }); + + var group = new Konva.Group({ + x: 50, + y: 25, + }); + + var rect = new Konva.Rect({ + x: 50, + y: 25, + width: 100, + height: 50, + fill: 'green', + shadowOffsetX: 10, + shadowOffsetY: 10, + shadowBlur: 10, + }); + group.add(rect); + + var circle = new Konva.Circle({ + x: 250, + y: 50, + radius: 25, + fill: 'red', + // rotation on circle should not have any effects + rotation: 45, + stroke: 'black', + scaleX: 2, + scaleY: 2, + }); + group.add(circle); + + group.cache(); + + layer.add(group); + stage.add(layer); + + var canvas = createCanvas(); + var context = canvas.getContext('2d'); + + // draw rect + context.save(); + context.beginPath(); + context.rect(100, 50, 100, 50); + context.closePath(); + context.fillStyle = 'green'; + context.shadowColor = 'black'; + context.shadowBlur = 10 * Konva.pixelRatio; + context.shadowOffsetX = 10 * Konva.pixelRatio; + context.shadowOffsetY = 10 * Konva.pixelRatio; + context.fill(); + context.restore(); + + // circle + context.save(); + context.beginPath(); + context.arc(300, 75, 50, 0, Math.PI * 2); + context.closePath(); + context.fillStyle = 'red'; + context.lineWidth = 4; + context.fill(); + context.stroke(); + context.restore(); + + compareLayerAndCanvas(layer, canvas, 150); + + // recache + group.cache(); + layer.draw(); + compareLayerAndCanvas(layer, canvas, 150); + }); + + it('cache shape that is larger than stage', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var group = new Konva.Group(); + var circle = new Konva.Circle({ + x: 74, + y: 74, + radius: 300, + fill: 'red', + stroke: 'black', + strokeWidth: 4, + scaleX: 1 / 2, + scaleY: 1 / 2, + }); + + group.add(circle); + layer.add(group); + stage.add(layer); + + assert.equal(circle._getCanvasCache(), undefined); + + var canvas = createCanvas(); + var context = canvas.getContext('2d'); + // circle + context.save(); + context.beginPath(); + context.arc(74, 74, 150, 0, Math.PI * 2); + context.closePath(); + context.fillStyle = 'red'; + context.lineWidth = 2; + context.fill(); + context.stroke(); + context.restore(); + + compareLayerAndCanvas(layer, canvas, 150); + }); + + it('cache shape that is larger than stage but need buffer canvas', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var group = new Konva.Group(); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 400, + fill: 'red', + stroke: 'black', + strokeWidth: 50, + opacity: 0.5, + scaleX: 1 / 5, + scaleY: 1 / 5, + }); + + group.add(circle); + layer.add(group); + stage.add(layer); + circle.cache(); + layer.draw(); + + cloneAndCompareLayer(layer, 200); + }); + + it('cache nested groups', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var groupOuter = new Konva.Group({ + x: 50, + y: 10, + }); + + var groupInner = new Konva.Group({ + x: 10, + y: 10, + draggable: true, + }); + var rect = new Konva.Rect({ + width: 50, + height: 50, + stroke: 'grey', + strokeWidth: 3, + fill: 'yellow', + }); + + var text = new Konva.Text({ + x: 18, + y: 15, + text: 'A', + fill: 'black', + fontSize: 24, + }); + + groupInner.add(rect); + groupInner.add(text); + + groupOuter.add(groupInner); + + layer.add(groupOuter); + stage.add(layer); + + groupInner.cache(); + + layer.draw(); + cloneAndCompareLayer(layer, 150); + + groupInner.clearCache(); + groupOuter.cache(); + layer.draw(); + cloneAndCompareLayer(layer, 150); + + groupOuter.clearCache(); + groupInner.clearCache(); + rect.cache(); + layer.draw(); + cloneAndCompareLayer(layer, 150); + }); + + it('test group with circle + buffer canvas usage', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var group = new Konva.Group({ + x: 100, + y: 100, + draggable: true, + }); + layer.add(group); + + var circle = new Konva.Circle({ + radius: 10, + // fill: 'white', + fillRadialGradientStartRadius: 0, + fillRadialGradientEndRadius: 10, + fillRadialGradientColorStops: [0, 'red', 0.5, 'yellow', 1, 'black'], + opacity: 0.4, + hitStrokeWidth: 0, + stroke: 'rgba(0,0,0,0)', + }); + group.add(circle); + group.cache(); + stage.draw(); + + cloneAndCompareLayer(layer, 200); + }); + + it('test group with opacity', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var group = new Konva.Group({ + x: 100, + y: 100, + draggable: true, + }); + layer.add(group); + + var circle = new Konva.Circle({ + radius: 10, + fillRadialGradientStartRadius: 0, + fillRadialGradientEndRadius: 10, + fillRadialGradientColorStops: [0, 'red', 0.5, 'yellow', 1, 'black'], + opacity: 0.4, + hitStrokeWidth: 0, + stroke: 'rgba(0,0,0,0)', + }); + group.add(circle); + group.cache(); + stage.draw(); + + cloneAndCompareLayer(layer, 210); + }); + + it('test group with opacity', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var group = new Konva.Group({ + x: 100, + y: 100, + draggable: true, + }); + layer.add(group); + + var circle = new Konva.Circle({ + radius: 10, + fillRadialGradientStartRadius: 0, + fillRadialGradientEndRadius: 10, + fillRadialGradientColorStops: [0, 'red', 0.5, 'yellow', 1, 'black'], + opacity: 0.4, + hitStrokeWidth: 0, + stroke: 'rgba(0,0,0,0)', + }); + group.add(circle); + group.cache(); + stage.draw(); + + cloneAndCompareLayer(layer, 100); + }); + + it('test rect with float dimensions', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var group = new Konva.Group({ + x: 10, + y: 10, + draggable: true, + }); + layer.add(group); + + var circle = new Konva.Circle({ + radius: 52.2, + fill: 'red', + }); + group.add(circle); + group.cache(); + + const canvas = group._cache.get('canvas').scene; + console.log(canvas.width / 2); + assert.equal(canvas.width, 106 * canvas.pixelRatio); + }); + + it('cache group with rectangle with fill and opacity', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + + var group = new Konva.Group({ + opacity: 0.5, + }); + + var rect = new Konva.Rect({ + x: 100, + y: 50, + width: 100, + height: 50, + fill: 'green', + }); + + group.add(rect); + layer.add(group); + stage.add(layer); + + group.cache(); + layer.draw(); + + var canvas = createCanvas(); + var context = canvas.getContext('2d'); + context.globalAlpha = 0.5; + context.beginPath(); + context.rect(100, 50, 100, 50); + context.closePath(); + context.fillStyle = 'green'; + context.fill(); + compareLayerAndCanvas(layer, canvas, 5); + }); + + it('even if parent is not visible cache should be created', function () { + var stage = addStage(); + + var layer = new Konva.Layer({ + visible: false, + }); + + var rect = new Konva.Rect({ + x: 100, + y: 50, + width: 100, + height: 100, + fill: 'green', + }); + + layer.add(rect); + stage.add(layer); + + rect.cache(); + layer.visible(true); + layer.draw(); + + var canvas = createCanvas(); + var context = canvas.getContext('2d'); + context.beginPath(); + context.rect(100, 50, 100, 100); + context.closePath(); + context.fillStyle = 'green'; + context.fill(); + compareLayerAndCanvas(layer, canvas, 5); + assert.equal(stage.getIntersection({ x: 150, y: 100 }), rect); + }); + + it('check cache for invisible shape', function () { + var stage = addStage(); + + var layer = new Konva.Layer({ + // visible: false, + }); + + var group = new Konva.Group(); + layer.add(group); + + group.add( + new Konva.Rect({ + x: 50, + y: 50, + width: 100, + height: 100, + fill: 'red', + }) + ); + var rect = new Konva.Rect({ + x: 0, + y: 0, + width: 100, + height: 100, + fill: 'green', + visible: false, + }); + group.add(rect); + + stage.add(layer); + + group.cache(); + layer.draw(); + cloneAndCompareLayer(layer); + }); + + it('even if parent is not listening cache and hit should be created', function () { + var stage = addStage(); + + var layer = new Konva.Layer({ + listening: false, + }); + + var rect = new Konva.Rect({ + x: 100, + y: 50, + width: 100, + height: 100, + fill: 'green', + }); + + layer.add(rect); + stage.add(layer); + + rect.cache(); + layer.listening(true); + layer.draw(); + + var canvas = createCanvas(); + var context = canvas.getContext('2d'); + context.beginPath(); + context.rect(100, 50, 100, 100); + context.closePath(); + context.fillStyle = 'green'; + context.fill(); + compareLayerAndCanvas(layer, canvas, 5); + assert.equal(stage.getIntersection({ x: 150, y: 100 }), rect); + }); + + // hard to fix + it.skip('even if parent is not visible cache should be created - test for group', function () { + var stage = addStage(); + + var layer = new Konva.Layer({ + visible: false, + }); + + var group = new Konva.Group({ + opacity: 0.5, + }); + + var rect = new Konva.Rect({ + x: 100, + y: 50, + width: 100, + height: 100, + fill: 'green', + }); + + group.add(rect); + layer.add(group); + stage.add(layer); + + group.cache(); + layer.visible(true); + layer.draw(); + + var canvas = createCanvas(); + var context = canvas.getContext('2d'); + context.globalAlpha = 0.5; + context.beginPath(); + context.rect(100, 50, 100, 100); + context.closePath(); + context.fillStyle = 'green'; + context.fill(); + compareLayerAndCanvas(layer, canvas, 5); + assert.equal(stage.getIntersection({ x: 150, y: 100 }), rect); + }); + + it('listening false on a shape should not create hit area', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + stage.add(layer); + + var bigCircle = new Konva.Circle({ + x: 100, + y: 100, + radius: 100, + fill: 'green', + }); + + layer.add(bigCircle); + + var smallCircle = new Konva.Circle({ + x: 100, + y: 100, + radius: 50, + fill: 'red', + listening: false, + }); + + layer.add(smallCircle); + smallCircle.cache(); + layer.draw(); + + var shape = stage.getIntersection({ x: 100, y: 100 }); + assert.equal(shape, bigCircle); + }); + + it('listening false on a shape inside group should not create hit area', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + stage.add(layer); + + var group = new Konva.Group(); + layer.add(group); + + var bigCircle = new Konva.Circle({ + x: 100, + y: 100, + radius: 100, + fill: 'green', + }); + + group.add(bigCircle); + + var smallCircle = new Konva.Circle({ + x: 100, + y: 100, + radius: 50, + fill: 'red', + listening: false, + }); + + group.add(smallCircle); + group.cache(); + + layer.draw(); + var shape = stage.getIntersection({ x: 100, y: 100 }); + assert.equal(shape, bigCircle); + }); + it('listening false on a group inside a group should not create hit area', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + stage.add(layer); + + var group = new Konva.Group(); + layer.add(group); + + var bigCircle = new Konva.Circle({ + x: 100, + y: 100, + radius: 100, + fill: 'green', + }); + group.add(bigCircle); + + var innerGroup = new Konva.Group({ + listening: false, + }); + group.add(innerGroup); + + var smallCircle = new Konva.Circle({ + x: 100, + y: 100, + radius: 50, + fill: 'red', + }); + + innerGroup.add(smallCircle); + group.cache(); + + layer.draw(); + var shape = stage.getIntersection({ x: 100, y: 100 }); + assert.equal(shape, bigCircle); + }); + + // for performance reasons + it('do no call client rect calculation, if we do not need it', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + stage.add(layer); + + var group = new Konva.Group(); + layer.add(group); + + var bigCircle = new Konva.Circle({ + x: 100, + y: 100, + radius: 100, + fill: 'green', + }); + group.add(bigCircle); + + layer.draw(); + + var called = false; + group.getClientRect = function () { + called = true; + } as any; + group.cache({ + x: 0, + y: 0, + width: 100, + height: 100, + }); + + assert.equal(called, false); + }); + + // for performance reasons + it('caching should skip clearing internal caching for perf boos', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + stage.add(layer); + + var bigCircle = new Konva.Circle({ + x: 100, + y: 100, + radius: 100, + fill: 'green', + }); + layer.add(bigCircle); + + layer.cache(); + + var callCount = 0; + bigCircle._clearSelfAndDescendantCache = function () { + callCount += 1; + }; + + layer.x(10); + assert.equal(callCount, 0); + layer.clearCache(); + // make sure all cleared for children + assert.equal(callCount, 1); + }); + + it('caching group with clip', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + stage.add(layer); + + var width = 100; + var height = 100; + var verts = [ + { x: width * 0.2, y: 0 }, + { x: width, y: 0 }, + { x: width * 0.8, y: height }, + { x: 0, y: height }, + ]; + + var clipFunc = (ctx) => { + for (let i = 0; i < verts.length; i++) { + const vertex = verts[i]; + if (i === 0) { + ctx.moveTo(vertex.x, vertex.y); + } else { + ctx.lineTo(vertex.x, vertex.y); + } + } + ctx.closePath(); + }; + + var group1 = new Konva.Group({ + clipFunc: clipFunc, + x: 50, + y: 50, + listening: false, + }); + layer.add(group1); + var rect1 = new Konva.Rect({ + fill: 'green', + width: 100, + height: 100, + }); + group1.add(rect1); + + layer.draw(); + group1.cache(); + + layer.draw(); + + // var group2 = group1.clone({ + // x: height - 20, + // y: 50, + // }); + // group2.findOne('Rect').fill('red'); + // layer.add(group2); + // group2.cache(); + + // var group3 = group1.clone({ + // x: width + 20, + // }); + // layer.add(group3); + // group3.findOne('Rect').x(150); + // group2.cache(); + + layer.draw(); + + cloneAndCompareLayer(layer, 10); + }); + + it('check caching with global composite operation', function () { + var stage = addStage(); + + const layer = new Konva.Layer(); + stage.add(layer); + + function getColor(pos) { + var ratio = layer.canvas.pixelRatio; + var p = layer.canvas.context.getImageData( + Math.round(pos.x * ratio), + Math.round(pos.y * ratio), + 1, + 1 + ).data; + return Konva.Util._rgbToHex(p[0], p[1], p[2]); + } + + const bg = new Konva.Rect({ + x: 0, + y: 0, + width: stage.width(), + height: stage.height(), + fill: 'lightgray', + }); + layer.add(bg); + + const group = new Konva.Group(); + layer.add(group); + + const rect = new Konva.Rect({ + x: 10, + y: 0, + width: 200, + height: 100, + fill: 'blue', + draggable: true, + }); + group.add(rect); + + const maskgroup = new Konva.Group({}); + group.add(maskgroup); + + const mask = new Konva.Rect({ + x: 50, + y: 0, + width: 100, + height: 100, + fill: 'black', + }); + maskgroup.add(mask); + + maskgroup.cache(); + var canvasBefore = maskgroup._cache.get('canvas').scene._canvas; + + maskgroup.globalCompositeOperation('destination-in'); + maskgroup.cache(); + var canvasAfter = maskgroup._cache.get('canvas').scene._canvas; + + compareCanvases(canvasBefore, canvasAfter); + + maskgroup.clearCache(); + + layer.draw(); + // no caches - mask group clipped all drawing + assert.equal(getColor({ x: 5, y: 20 }), '000000'); + assert.equal(getColor({ x: 55, y: 20 }), '0000ff'); + + // cache inner mask group - same result + maskgroup.cache(); + layer.draw(); + + assert.equal(getColor({ x: 5, y: 20 }), '000000'); + assert.equal(getColor({ x: 55, y: 20 }), '0000ff'); + + // cache group + // background will be visible now, because globalCompositeOperation + // will work inside cached parent only + group.cache(); + layer.draw(); + + assert.equal(getColor({ x: 5, y: 20 }), 'd3d3d3'); + assert.equal(getColor({ x: 55, y: 20 }), '0000ff'); + }); + + it('recache should update internal caching', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + stage.add(layer); + + var bigCircle = new Konva.Circle({ + x: 100, + y: 100, + radius: 100, + fill: 'red', + draggable: true, + }); + layer.add(bigCircle); + + bigCircle.cache(); + + layer.draw(); + + var d = layer.getContext().getImageData(100, 100, 1, 1).data; + assert.equal(d[0], 255, 'see red'); + + bigCircle.fill('blue'); + bigCircle.cache(); + + layer.draw(); + d = layer.getContext().getImageData(100, 100, 1, 1).data; + assert.equal(d[0], 0, 'no red'); + assert.equal(d[2], 255, 'see blue'); + }); + + it('recache with filters', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + stage.add(layer); + + var bigCircle = new Konva.Circle({ + x: 100, + y: 100, + radius: 100, + fill: 'red', + draggable: true, + }); + layer.add(bigCircle); + + bigCircle.filters([Konva.Filters.Blur]); + bigCircle.blurRadius(10); + bigCircle.cache(); + + layer.draw(); + bigCircle.cache(); + + layer.draw(); + + var d = layer.getContext().getImageData(100, 100, 1, 1).data; + assert.equal(d[0], 255, 'see red'); + }); + + it('check image smooth', function () { + var stage = addStage(); + + var layer = new Konva.Layer({ + imageSmoothingEnabled: false, + }); + stage.add(layer); + + var bigCircle = new Konva.Circle({ + x: 100, + y: 100, + radius: 10, + fill: 'red', + draggable: true, + scaleX: 10, + scaleY: 10, + }); + layer.add(bigCircle); + + bigCircle.cache({ + imageSmoothingEnabled: false, + }); + + layer.draw(); + assert.equal( + bigCircle._cache.get('canvas').scene.getContext()._context + .imageSmoothingEnabled, + false + ); + }); + + it('getAbsolutePosition for cached container', function () { + var stage = addStage(); + + var layer = new Konva.Layer({}); + stage.add(layer); + + var circle = new Konva.Circle({ + x: 100, + y: 100, + radius: 10, + fill: 'red', + draggable: true, + scaleX: 10, + scaleY: 10, + }); + layer.add(circle); + // initial calculations + circle.getAbsolutePosition(); + // + + layer.cache(); + layer.draw(); + layer.position({ + x: 10, + y: 10, + }); + assert.equal(circle.getAbsolutePosition().x, 110); + assert.equal(circle.getAbsolutePosition().y, 110); + }); + + it('cached node should not have filter canvas until we have a filter', function () { + var stage = addStage(); + + var layer = new Konva.Layer({}); + stage.add(layer); + + var circle = new Konva.Circle({ + x: 100, + y: 100, + radius: 10, + fill: 'red', + draggable: true, + scaleX: 10, + scaleY: 10, + }); + layer.add(circle); + circle.cache(); + + assert.equal(circle._cache.get('canvas').filter.width, 0); + circle.filters([Konva.Filters.Blur]); + layer.draw(); + assert.equal( + circle._cache.get('canvas').filter.width, + 20 * circle._cache.get('canvas').filter.pixelRatio + ); + circle.filters([]); + // TODO: should we clear cache canvas? + // assert.equal(circle._cache.get('canvas').filter.width, 0); + }); + + it('hit from cache + global composite', function (done) { + // blend mode should NOT effect hit detection. + var stage = addStage(); + + var layer = new Konva.Layer({}); + stage.add(layer); + + loadImage('lion.png', (img) => { + const lion = new Konva.Image({ image: img }); + lion.name('lion'); + lion.cache(); + lion.drawHitFromCache(); + layer.add(lion); + + const lion2 = new Konva.Image({ image: img }); + + lion2.position({ + x: 50, + y: 50, + }); + lion2.name('lion2'); + lion2.globalCompositeOperation('overlay'); + lion2.cache(); + lion2.drawHitFromCache(); + layer.add(lion2); + layer.draw(); + // layer.toggleHitCanvas(); + + var shape = layer.getIntersection({ x: 106, y: 78 }); + assert.equal(shape, lion2); + done(); + }); + }); + + it('hit from cache with custom pixelRatio', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + + var rect = new Konva.Rect({ + x: 100, + y: 50, + width: 100, + height: 100, + fill: 'green', + }); + + layer.add(rect); + stage.add(layer); + + rect.cache({ + hitCanvasPixelRatio: 0.2, + }); + layer.draw(); + + var hitCanvas = rect._cache.get('canvas').hit; + assert.equal(hitCanvas._canvas.width, rect.width() * 0.2); + assert.equal(hitCanvas._canvas.height, rect.height() * 0.2); + assert.equal(hitCanvas.pixelRatio, 0.2); + + var canvas = createCanvas(); + var context = canvas.getContext('2d'); + context.beginPath(); + context.rect(100, 50, 100, 100); + context.closePath(); + context.fillStyle = 'green'; + context.fill(); + + compareLayerAndCanvas(layer, canvas, 5); + + assert.equal(stage.getIntersection({ x: 150, y: 100 }), rect); + }); +}); diff --git a/test/unit/Node-test.ts b/test/unit/Node-test.ts new file mode 100644 index 000000000..599254b65 --- /dev/null +++ b/test/unit/Node-test.ts @@ -0,0 +1,3845 @@ +import { assert } from 'chai'; +import { Shape } from '../../src/Shape'; + +import { + addStage, + simulateMouseDown, + simulateMouseUp, + showHit, + compareLayerAndCanvas, + compareLayers, + addContainer, + loadImage, + Konva, + isBrowser, + simulateMouseMove, +} from './test-utils'; + +describe('Node', function () { + // ====================================================== + it('getType and getClassName', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var group = new Konva.Group(); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + }); + + stage.add(layer.add(group.add(circle))); + + //console.log(stage.getType()); + + assert.equal(stage.getType(), 'Stage'); + assert.equal(layer.getType(), 'Layer'); + assert.equal(group.getType(), 'Group'); + assert.equal(circle.getType(), 'Shape'); + + assert.equal(stage.getClassName(), 'Stage'); + assert.equal(layer.getClassName(), 'Layer'); + assert.equal(group.getClassName(), 'Group'); + assert.equal(circle.getClassName(), 'Circle'); + }); + // ====================================================== + it('get layer', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + }); + assert.equal(circle.getLayer(), null); + + stage.add(layer.add(circle)); + assert.equal(circle.getLayer(), layer); + }); + // ====================================================== + it('setAttr', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + }); + + stage.add(layer.add(circle)); + + circle.setAttr('fill', 'red'); + layer.draw(); + + assert.equal(circle.fill(), 'red'); + + circle.setAttr('position', { x: 5, y: 6 }); + + assert.equal(circle.x(), 5); + assert.equal(circle.y(), 6); + + circle.setAttr('foobar', 12); + + assert.equal(circle.getAttr('foobar'), 12); + }); + + // ====================================================== + it('unset attr', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + }); + + stage.add(layer.add(circle)); + + circle.setAttr('x', undefined); + assert.equal(circle.x(), 0); + + circle.y(null); + assert.equal(circle.y(), 0); + }); + + // ====================================================== + it('set shape and layer opacity to 0.5', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + }); + + circle.opacity(0.5); + layer.opacity(0.5); + layer.add(circle); + stage.add(layer); + + assert.equal(circle.getAbsoluteOpacity(), 0.25); + assert.equal(layer.getAbsoluteOpacity(), 0.5); + }); + // ====================================================== + it('transform cache', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + }); + + assert.equal(circle._cache.get('transform'), undefined); + + layer.add(circle); + stage.add(layer); + + // transform cache + assert.notEqual(circle._cache.get('transform'), undefined); + circle.x(100); + assert.equal(circle._cache.get('transform').dirty, true); + layer.draw(); + assert.equal(circle._cache.get('transform').dirty, false); + }); + + // ====================================================== + it('visible cache', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + }); + layer.add(circle); + stage.add(layer); + + // visible cache + assert.equal(circle._cache.get('visible'), true); + circle.hide(); + assert.equal(circle._cache.get('visible'), undefined); + stage.draw(); + assert.equal(circle._cache.get('visible'), false); + circle.show(); + assert.equal(circle._cache.get('visible'), undefined); + layer.draw(); + assert.equal(circle._cache.get('visible'), true); + }); + + // ====================================================== + it('shadow cache', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + }); + layer.add(circle); + stage.add(layer); + + // shadow cache + assert.equal(circle._cache.get('hasShadow'), false); + circle.shadowColor('red'); + circle.shadowOffsetX(10); + assert.equal(circle._cache.get('hasShadow'), undefined); + layer.draw(); + assert.equal(circle._cache.get('hasShadow'), true); + layer.draw(); + assert.equal(circle._cache.get('hasShadow'), true); + }); + + // ====================================================== + it('has shadow', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var rect = new Konva.Rect({ + x: 10, + y: stage.height() / 3, + width: 100, + height: 100, + fill: 'red', + stroke: 'black', + strokeWidth: 4, + draggable: true, + }); + layer.add(rect); + stage.add(layer); + rect.shadowEnabled(true); + rect.shadowColor('grey'); + assert.equal(rect.hasShadow(), true); + rect.shadowEnabled(false); + assert.equal(rect.hasShadow(), false); + }); + + // ====================================================== + it('opacity cache', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + }); + layer.add(circle); + stage.add(layer); + + // opacity cache + assert.equal(circle._cache.get('absoluteOpacity'), 1); + circle.opacity(0.5); + assert.equal(circle._cache.get('absoluteOpacity'), undefined); + layer.draw(); + assert.equal(circle._cache.get('absoluteOpacity'), 0.5); + }); + + // ====================================================== + it('listening cache', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + }); + layer.add(circle); + stage.add(layer); + + // listening cache + + // prime the cache + circle.isListening(); + + assert.equal(circle._cache.get('listening'), true); + circle.listening(false); + assert.equal(circle._cache.get('listening'), undefined); + circle.isListening(); + assert.equal(circle._cache.get('listening'), false); + }); + + // ====================================================== + it('stage cache', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + }); + layer.add(circle); + stage.add(layer); + + // stage cache + var st = circle.getStage(); + assert.equal(circle._cache.get('stage')._id, stage._id); + }); + + // ====================================================== + it('toDataURL + HDPI', function (done) { + // this.timeout(5000); + var oldRatio = Konva.pixelRatio; + Konva.pixelRatio = 2; + + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var circle = new Konva.Circle({ + fill: 'green', + x: stage.width() / 2, + y: stage.height() / 2, + radius: 50, + }); + layer.add(circle); + + var layer2 = new Konva.Layer(); + stage.add(layer2); + + stage.draw(); + stage.toDataURL({ + pixelRatio: 2, + callback: function (url) { + loadImage(url, (img) => { + var image = new Konva.Image({ + image: img, + scaleX: 0.5, + scaleY: 0.5, + }); + assert.equal( + image.width(), + stage.width() * 2, + 'image has double size' + ); + layer2.add(image); + layer2.draw(); + compareLayers(layer, layer2, 150); + Konva.pixelRatio = oldRatio; + done(); + }); + }, + }); + }); + + // ====================================================== + it('toDataURL with moved layer', function () { + var stage = addStage(); + var layer = new Konva.Layer({ + x: 50, + y: 50, + }); + stage.add(layer); + + var circle = new Konva.Circle({ + fill: 'green', + x: 50, + y: 50, + radius: 50, + }); + layer.add(circle); + + stage.draw(); + var stageExport = stage.toCanvas({ + pixelRatio: layer.getCanvas().getPixelRatio(), + }); + compareLayerAndCanvas(layer, stageExport); + }); + + // ====================================================== + it('toDataURL with moved layer and moved export', function () { + var stage = addStage(); + var layer = new Konva.Layer({}); + stage.add(layer); + + var circle = new Konva.Circle({ + fill: 'green', + x: 50, + y: 50, + radius: 50, + }); + layer.add(circle); + + stage.draw(); + var stageExport = stage.toCanvas({ + x: 50, + y: 50, + pixelRatio: layer.getCanvas().getPixelRatio(), + }); + layer.x(-50); + layer.y(-50); + layer.draw(); + compareLayerAndCanvas(layer, stageExport); + }); + + // ====================================================== + it('toDataURL of moved shape', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var circle = new Konva.Circle({ + fill: 'green', + radius: 50, + }); + layer.add(circle); + + var oldURL = circle.toDataURL(); + + circle.x(100); + circle.y(100); + + var newURL = circle.toDataURL(); + assert.equal(oldURL, newURL); + }); + + // ====================================================== + it('toDataURL of transformer shape', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var text = new Konva.Text({ + fill: 'green', + text: 'hello, test', + rotation: 45, + }); + layer.add(text); + + var oldURL = text.toDataURL(); + + text.x(100); + text.y(100); + + var newURL = text.toDataURL(); + assert.equal(oldURL, newURL); + }); + + // ====================================================== + it('export with smooth disabled', function (done) { + loadImage('lion.png', (imageObj) => { + var stage = addStage(); + var layer = new Konva.Layer({ + imageSmoothingEnabled: false, + }); + stage.add(layer); + + var image = new Konva.Image({ + x: -50, + y: -50, + image: imageObj, + scaleX: 5, + scaleY: 5, + }); + layer.add(image); + + const canvas = layer.toCanvas({ + imageSmoothingEnabled: false, + pixelRatio: layer.getCanvas().getPixelRatio(), + }); + layer.draw(); + compareLayerAndCanvas(layer, canvas); + + // just check clone without crashes + done(); + }); + }); + + // ====================================================== + it("listen and don't listen", function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var rect = new Konva.Rect({ + x: 50, + y: 50, + width: 200, + height: 50, + fill: 'blue', + }); + + var rect2 = new Konva.Rect({ + x: 200, + y: 100, + width: 200, + height: 50, + fill: 'red', + listening: false, + }); + + layer.add(rect).add(rect2); + stage.add(layer); + + assert.equal(rect.listening(), true); + + assert.equal(rect.isListening(), true); + rect.listening(false); + assert.equal(rect.listening(), false); + + assert.equal(rect2.listening(), false); + rect2.listening(true); + assert.equal(rect2.listening(), true); + }); + + // ====================================================== + it("listen and don't listen with one shape", function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var rect = new Konva.Rect({ + x: 50, + y: 50, + width: 200, + height: 50, + fill: 'blue', + }); + + layer.add(rect); + stage.add(layer); + + rect.listening(false); + layer.drawHit(); + + showHit(layer); + + assert.equal(layer.getIntersection({ x: 60, y: 60 }), null); + }); + + // ====================================================== + it('test offset attr change', function () { + /* + * the premise of this test to make sure that only + * root level attributes trigger an attr change event. + * for this test, we have two offset properties. one + * is in the root level, and the other is inside the shadow + * object + */ + var stage = addStage(); + var layer = new Konva.Layer(); + var rect = new Konva.Rect({ + x: 50, + y: 50, + width: 200, + height: 50, + fill: 'blue', + offset: { x: 10, y: 10 }, + shadowColor: 'black', + shadowOffset: { x: 20, y: 20 }, + }); + + layer.add(rect); + stage.add(layer); + + var offsetChange = false; + var shadowOffsetChange = false; + + rect.on('offsetChange', function (val) { + offsetChange = true; + }); + + rect.offset({ x: 1, y: 2 }); + + assert.equal(offsetChange, true); + }); + + // ====================================================== + it('simple clone', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var rect = new Konva.Rect({ + x: 0, + y: 0, + width: 100, + height: 100, + stroke: 'red', + }); + + var clone = rect.clone({ + stroke: 'green', + }); + + layer.add(clone); + stage.add(layer); + + assert.equal(rect.stroke(), 'red'); + assert.equal(clone.stroke(), 'green'); + }); + + // ====================================================== + it('clone - check reference', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var line = new Konva.Line({ + x: 0, + y: 0, + stroke: 'red', + points: [0, 0, 10, 10], + }); + + var clone = line.clone({ + stroke: 'green', + }); + + layer.add(clone); + stage.add(layer); + + assert.equal(line.points() === clone.points(), false); + assert.equal(JSON.stringify(clone.points()), '[0,0,10,10]'); + }); + + // ====================================================== + it('complex clone', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var rect = new Konva.Rect({ + x: 50, + y: 50, + width: 200, + height: 50, + fill: 'blue', + offsetX: 10, + offsetY: 10, + shadowColor: 'black', + shadowOffsetX: 20, + shadowOffsetY: 20, + draggable: true, + name: 'myRect', + id: 'myRect', + }); + + var clicks = []; + + rect.on('click', function () { + clicks.push(this.name()); + }); + var clone = rect.clone({ + x: 300, + fill: 'red', + name: 'rectClone', + }); + + assert.equal(clone.x(), 300); + assert.equal(clone.y(), 50); + assert.equal(clone.width(), 200); + assert.equal(clone.height(), 50); + assert.equal(clone.fill(), 'red'); + + assert.equal(rect.shadowColor(), 'black'); + assert.equal(clone.shadowColor(), 'black'); + + assert.equal(clone.id() === 'myRect', true, 'clone id'); + + clone.shadowColor('green'); + + /* + * Make sure that when we change a clone object attr that the rect object + * attr isn't updated by reference + */ + + assert.equal(rect.shadowColor(), 'black'); + assert.equal(clone.shadowColor(), 'green'); + + layer.add(rect); + layer.add(clone); + stage.add(layer); + + // make sure private ids are different + assert(rect._id !== clone._id, 'rect and clone ids should be different'); + + // test user event binding cloning + assert.equal(clicks.length, 0); + rect.fire('click'); + assert.equal(clicks.toString(), 'myRect'); + clone.fire('click'); + assert.equal(clicks.toString(), 'myRect,rectClone'); + }); + + // ====================================================== + it('clone a group', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var group = new Konva.Group({ + x: 50, + draggable: true, + name: 'myGroup', + }); + + var rect = new Konva.Rect({ + x: 0, + y: 50, + width: 200, + height: 50, + fill: 'red', + offsetX: 10, + offsetY: 10, + shadowColor: 'black', + shadowOffset: { x: 20, y: 20 }, + name: 'myRect', + myAttr: 'group rect', + }); + var text = new Konva.Text({ + x: 0, + y: 110, + text: 'Some awesome text!', + fontSize: 14, + fontFamily: 'Calibri', + fill: 'blue', + name: 'myText', + }); + group.add(rect); + group.add(text); + + var clicks = []; + var taps = []; + + group.on('click', function () { + clicks.push(this.name()); + }); + rect.on('tap', function () { + taps.push(this.attrs.myAttr); + }); + var clone = group.clone({ + x: 300, + name: 'groupClone', + }); + + showHit(layer); + + layer.add(group); + layer.add(clone); + stage.add(layer); + + assert.equal(clone.x(), 300); + assert.equal(clone.y(), 0); + assert.equal(clone.draggable(), true); + // test alias + assert.equal(clone.draggable(), true); + assert.equal(clone.name(), 'groupClone'); + + assert.equal(group.getChildren().length, 2); + assert.equal(clone.getChildren().length, 2); + assert.equal(group.find('.myText')[0].fill(), 'blue'); + assert.equal(clone.find('.myText')[0].fill(), 'blue'); + clone.find('.myText')[0].fill('black'); + assert.equal(group.find('.myRect')[0].attrs.myAttr, 'group rect'); + assert.equal(clone.find('.myRect')[0].attrs.myAttr, 'group rect'); + clone.find('.myRect')[0].setAttrs({ + myAttr: 'clone rect', + }); + + // Make sure that when we change a clone object attr that the rect object + // attr isn't updated by reference + + assert.equal(group.find('.myText')[0].fill(), 'blue'); + assert.equal(clone.find('.myText')[0].fill(), 'black'); + + assert.equal(group.find('.myRect')[0].attrs.myAttr, 'group rect'); + assert.equal(clone.find('.myRect')[0].attrs.myAttr, 'clone rect'); + + // make sure private ids are different + assert.notEqual(group._id, clone._id); + + // make sure childrens private ids are different + assert.notEqual(group.find('.myRect')[0]._id, clone.find('.myRect')[0]._id); + assert.notEqual(group.find('.myText')[0]._id, clone.find('.myText')[0]._id); + + // test user event binding cloning + assert.equal(clicks.length, 0); + group.fire('click'); + assert.equal(clicks.toString(), 'myGroup'); + clone.fire('click'); + assert.equal(clicks.toString(), 'myGroup,groupClone'); + + // test user event binding cloning on children + assert.equal(taps.length, 0); + group.find('.myRect')[0].fire('tap'); + assert.equal(taps.toString(), 'group rect'); + clone.find('.myRect')[0].fire('tap'); + assert.equal(taps.toString(), 'group rect,clone rect'); + + stage.draw(); + }); + + // ====================================================== + it('test on attr change', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var rect = new Konva.Rect({ + x: 50, + y: 50, + width: 200, + height: 50, + fill: 'blue', + shadowOffset: { x: 10, y: 10 }, + }); + + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 35, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + }); + + layer.add(circle); + layer.add(rect); + stage.add(layer); + + var widthChanged = 0; + var shadowChanged = 0; + var radiusChanged = 0; + + rect.on('widthChange', function (evt) { + widthChanged++; + assert.equal(evt['oldVal'], 200); + assert.equal(evt['newVal'], 210); + }); + + rect.on('shadowOffsetChange', function () { + shadowChanged++; + }); + + circle.on('radiusChange', function () { + radiusChanged++; + }); + + circle.radius(70); + + rect.setSize({ width: 210, height: 210 }); + rect.shadowOffset({ + x: 20, + y: 0, + }); + + assert.equal(widthChanged, 1); + assert.equal(shadowChanged, 1); + assert.equal(radiusChanged, 1); + }); + + // ====================================================== + it('test on attr change for same value', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var rect = new Konva.Rect({ + x: 50, + y: 50, + width: 200, + height: 50, + fill: 'blue', + shadowOffset: { x: 10, y: 10 }, + }); + + layer.add(rect); + stage.add(layer); + + var widthChanged = 0; + + rect.on('widthChange', function (evt) { + widthChanged++; + }); + + rect.width(210); + rect.width(210); + + assert.equal(widthChanged, 1, 'should trigger only once'); + }); + + // ====================================================== + it('set shape, layer and stage opacity to 0.5', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + }); + + circle.opacity(0.5); + layer.opacity(0.5); + stage.opacity(0.5); + layer.add(circle); + stage.add(layer); + + assert.equal(circle.getAbsoluteOpacity(), 0.125); + assert.equal(layer.getAbsoluteOpacity(), 0.25); + assert.equal(stage.getAbsoluteOpacity(), 0.5); + }); + + // ====================================================== + it('hide show layer', function () { + var stage = addStage(); + + var layer1 = new Konva.Layer(); + var layer2 = new Konva.Layer(); + + var circle1 = new Konva.Circle({ + x: 100, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + }); + var circle2 = new Konva.Circle({ + x: 150, + y: stage.height() / 2, + radius: 70, + fill: 'red', + stroke: 'black', + strokeWidth: 4, + }); + + circle1.on('mousemove', function () { + console.log('mousemove circle1'); + }); + + circle2.on('mousemove', function () { + console.log('mousemove circle2'); + }); + + layer1.add(circle1); + layer2.add(circle2); + stage.add(layer1).add(layer2); + + assert.equal(layer2.isVisible(), true); + layer2.hide(); + assert.equal(layer2.isVisible(), false); + assert.equal(layer2.canvas._canvas.style.display, 'none'); + + layer2.show(); + assert.equal(layer2.isVisible(), true); + assert.equal(layer2.canvas._canvas.style.display, 'block'); + }); + + // ====================================================== + it('rotation in degrees', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var rect = new Konva.Rect({ + x: 200, + y: 100, + width: 100, + height: 50, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + rotation: 10, + }); + + assert.equal(rect.rotation(), 10); + rect.rotation(20); + assert.equal(rect.rotation(), 20); + rect.rotate(20); + assert.equal(rect.rotation(), 40); + + layer.add(rect); + stage.add(layer); + }); + + // ====================================================== + it('skew', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var rect = new Konva.Rect({ + x: 200, + y: 100, + width: 100, + height: 50, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + skewX: 1, + }); + + layer.add(rect); + stage.add(layer); + + assert.equal(rect.skewX(), 1); + assert.equal(rect.skewY(), 0); + + /* + rect.transitionTo({ + duration: 4, + skewY: -2, + easing: 'ease-in-out' + + + }) + */ + }); + + // ====================================================== + it('init with position, scale, rotation, then change scale', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var rect = new Konva.Rect({ + x: 200, + y: 100, + width: 100, + height: 50, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + scale: { + x: 0.5, + y: 0.5, + }, + rotation: 20, + }); + + assert.equal(rect.getPosition().x, 200); + assert.equal(rect.getPosition().y, 100); + assert.equal(rect.scale().x, 0.5); + assert.equal(rect.scale().y, 0.5); + assert.equal(rect.rotation(), 20); + + rect.scale({ x: 2, y: 0.3 }); + assert.equal(rect.scale().x, 2); + assert.equal(rect.scale().y, 0.3); + + layer.add(rect); + stage.add(layer); + }); + + // ====================================================== + it('clone sprite', function (done) { + loadImage('scorpion-sprite.png', (imageObj) => { + var stage = addStage(); + var layer = new Konva.Layer(); + + var sprite = new Konva.Sprite({ + x: 200, + y: 50, + image: imageObj, + animation: 'standing', + animations: { + standing: [ + 0, 0, 49, 109, 52, 0, 49, 109, 105, 0, 49, 109, 158, 0, 49, 109, + 210, 0, 49, 109, 262, 0, 49, 109, + ], + kicking: [ + 0, 109, 45, 98, 45, 109, 45, 98, 95, 109, 63, 98, 156, 109, 70, 98, + 229, 109, 60, 98, 287, 109, 41, 98, + ], + }, + frameRate: 10, + draggable: true, + shadowColor: 'black', + shadowBlur: 3, + shadowOffset: { x: 3, y: 1 }, + shadowOpacity: 0.3, + }); + + var clone = sprite.clone(); + layer.add(clone); + stage.add(layer); + clone.start(); + + assert.equal(clone.image(), sprite.image()); + + // just check clone without crashes + done(); + }); + }); + + // ====================================================== + it('hide group', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + var group = new Konva.Group(); + + var circle1 = new Konva.Circle({ + x: 100, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + }); + var circle2 = new Konva.Circle({ + x: 150, + y: stage.height() / 2, + radius: 70, + fill: 'red', + stroke: 'black', + strokeWidth: 4, + }); + + circle1.on('mousemove', function () { + console.log('mousemove circle1'); + }); + + circle2.on('mousemove', function () { + console.log('mousemove circle2'); + }); + + group.add(circle2); + layer.add(circle1).add(group); + stage.add(layer); + + assert.equal(group.isVisible(), true); + assert.equal(circle2.isVisible(), true); + + group.hide(); + layer.draw(); + + assert.equal(!group.isVisible(), true); + assert.equal(!circle2.isVisible(), true); + }); + + // ====================================================== + it('add shape with custom attr pointing to self', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + offset: { + x: 0, + y: 0, + }, + scale: { + x: 2, + y: 2, + }, + }); + layer.add(circle); + stage.add(layer); + + /* + * add custom attr that points to self. The setAttrs method should + * not inifinitely recurse causing a stack overflow + */ + circle.setAttrs({ + self: circle, + }); + + /* + * serialize the stage. The json should succeed because objects that have + * methods, such as self, are not serialized, and will therefore avoid + * circular json errors. + */ + // console.log(stage.children); + // return; + var json = stage.toJSON(); + + // make sure children are ok after json + assert.equal(stage.children[0], layer); + }); + + // ====================================================== + it('set offset offset after instantiation', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var rect = new Konva.Rect({ + x: 200, + y: 100, + width: 100, + height: 50, + stroke: 'blue', + offset: { + x: 40, + y: 20, + }, + }); + + layer.add(rect); + stage.add(layer); + + assert.equal(rect.offsetX(), 40); + assert.equal(rect.offsetY(), 20); + + assert.equal(rect.offset().x, 40); + assert.equal(rect.offset().y, 20); + + rect.offset({ x: 80, y: 40 }); + + assert.equal(rect.offsetX(), 80); + assert.equal(rect.offsetY(), 40); + + assert.equal(rect.offset().x, 80); + assert.equal(rect.offset().y, 40); + }); + + // ====================================================== + it('test name methods', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + }); + + layer.add(circle); + stage.add(layer); + assert.equal(circle.name(), ''); + + circle.addName('foo'); + assert.equal(circle.name(), 'foo'); + circle.addName('myCircle'); + assert.equal(circle.name(), 'foo myCircle'); + + // add existing name + circle.addName('foo'); + assert.equal(circle.name(), 'foo myCircle'); + + // check hasName + assert.equal(circle.hasName('myCircle'), true); + assert.equal(circle.hasName('foo'), true); + assert.equal(circle.hasName('boo'), false); + // should return false for empty name + assert.equal(layer.hasName(''), false); + assert.equal(stage.findOne('.foo'), circle); + + // removing name + circle.removeName('foo'); + assert.equal(circle.name(), 'myCircle'); + assert.equal(layer.find('.foo').length, 0); + }); + + // ====================================================== + it('test setting shadow offset', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var rect = new Konva.Rect({ + x: 0, + y: 0, + width: 100, + height: 50, + fill: 'red', + shadowColor: 'blue', + shadowBlur: 12, + }); + + layer.add(rect); + stage.add(layer); + + rect.shadowOffset({ x: 1, y: 2 }); + assert.equal(rect.shadowOffset().x, 1); + assert.equal(rect.shadowOffset().y, 2); + // make sure we still have the other properties + assert.equal(rect.shadowColor(), 'blue'); + assert.equal(rect.shadowBlur(), 12); + + rect.shadowOffset({ + x: 3, + y: 4, + }); + assert.equal(rect.shadowOffset().x, 3); + assert.equal(rect.shadowOffset().y, 4); + + // test partial setting + rect.shadowOffsetX(5); + assert.equal(rect.shadowOffset().x, 5); + assert.equal(rect.shadowOffset().y, 4); + + // test partial setting + rect.shadowOffsetY(6); + assert.equal(rect.shadowOffset().x, 5); + assert.equal(rect.shadowOffset().y, 6); + }); + + // ====================================================== + it('test offset', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var rect = new Konva.Rect({ + x: 0, + y: 0, + width: 100, + height: 50, + fill: 'red', + }); + + layer.add(rect); + stage.add(layer); + + rect.offset({ x: 1, y: 2 }); + assert.equal(rect.offset().x, 1); + assert.equal(rect.offset().y, 2); + + rect.offset({ x: 3, y: 4 }); + assert.equal(rect.offset().x, 3); + assert.equal(rect.offset().y, 4); + + rect.offset({ + x: 5, + y: 6, + }); + assert.equal(rect.offset().x, 5); + assert.equal(rect.offset().y, 6); + + rect.offsetX(7); + assert.equal(rect.offset().x, 7); + assert.equal(rect.offset().y, 6); + + rect.offsetY(8); + assert.equal(rect.offset().x, 7); + assert.equal(rect.offset().y, 8); + }); + + // ====================================================== + it('test setPosition and move', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var rect = new Konva.Rect({ + x: 0, + y: 0, + width: 100, + height: 50, + fill: 'red', + }); + + layer.add(rect); + stage.add(layer); + + rect.setPosition({ x: 1, y: 2 }); + assert.equal(rect.getPosition().x, 1); + assert.equal(rect.getPosition().y, 2); + + rect.setPosition({ x: 3, y: 4 }); + assert.equal(rect.getPosition().x, 3); + assert.equal(rect.getPosition().y, 4); + + rect.setPosition({ + x: 5, + y: 6, + }); + assert.equal(rect.getPosition().x, 5); + assert.equal(rect.getPosition().y, 6); + + rect.x(7); + assert.equal(rect.getPosition().x, 7); + assert.equal(rect.getPosition().y, 6); + + rect.y(8); + assert.equal(rect.getPosition().x, 7); + assert.equal(rect.getPosition().y, 8); + + rect.move({ x: 10, y: 10 }); + assert.equal(rect.getPosition().x, 17); + assert.equal(rect.getPosition().y, 18); + }); + + // ====================================================== + it('test setScale', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var rect = new Konva.Rect({ + x: 200, + y: 20, + width: 100, + height: 50, + fill: 'red', + stroke: 'black', + strokeWidth: 4, + }); + + layer.add(rect); + stage.add(layer); + + rect.scale({ x: 2, y: 3 }); + assert.equal(rect.scale().x, 2); + assert.equal(rect.scale().y, 3); + + rect.scale({ x: 4, y: 4 }); + assert.equal(rect.scale().x, 4); + assert.equal(rect.scale().y, 4); + + rect.scale({ x: 5, y: 6 }); + assert.equal(rect.scale().x, 5); + assert.equal(rect.scale().y, 6); + + rect.scale({ x: 7, y: 8 }); + assert.equal(rect.scale().x, 7); + assert.equal(rect.scale().y, 8); + + rect.scale({ + x: 9, + y: 10, + }); + assert.equal(rect.scale().x, 9); + assert.equal(rect.scale().y, 10); + + rect.scaleX(11); + assert.equal(rect.scale().x, 11); + assert.equal(rect.scale().y, 10); + + rect.scaleY(12); + assert.equal(rect.scale().x, 11); + assert.equal(rect.scale().y, 12); + }); + + // ====================================================== + it('test config scale', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var rect1 = new Konva.Rect({ + x: 200, + y: 20, + width: 100, + height: 50, + fill: 'red', + scale: { + x: 2, + y: 3, + }, + }); + + var rect2 = new Konva.Rect({ + x: 200, + y: 20, + width: 100, + height: 50, + fill: 'red', + scale: { x: 2, y: 2 }, + }); + + var rect3 = new Konva.Rect({ + x: 200, + y: 20, + width: 100, + height: 50, + fill: 'red', + scale: { x: 2, y: 3 }, + }); + + var rect4 = new Konva.Rect({ + x: 200, + y: 20, + width: 100, + height: 50, + fill: 'red', + scaleX: 2, + }); + + var rect5 = new Konva.Rect({ + x: 200, + y: 20, + width: 100, + height: 50, + fill: 'red', + scaleY: 2, + }); + + layer.add(rect1).add(rect2).add(rect3).add(rect4).add(rect5); + stage.add(layer); + + assert.equal(rect1.scale().x, 2); + assert.equal(rect1.scale().y, 3); + + assert.equal(rect2.scale().x, 2); + assert.equal(rect2.scale().y, 2); + + assert.equal(rect3.scale().x, 2); + assert.equal(rect3.scale().y, 3); + + assert.equal(rect4.scale().x, 2); + assert.equal(rect4.scale().y, 1); + + //assert.equal(rect5.scale().x, 1); + assert.equal(rect5.scale().y, 2); + }); + + // ====================================================== + it('test config position', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var rect1 = new Konva.Rect({ + x: 1, + y: 2, + width: 100, + height: 50, + fill: 'red', + }); + + var rect2 = new Konva.Rect({ + x: 3, + width: 100, + height: 50, + fill: 'red', + }); + + var rect3 = new Konva.Rect({ + y: 4, + width: 100, + height: 50, + fill: 'red', + }); + + var rect4 = new Konva.Rect({ + width: 100, + height: 50, + fill: 'red', + }); + + layer.add(rect1).add(rect2).add(rect3).add(rect4); + stage.add(layer); + + assert.equal(rect1.getPosition().x, 1); + assert.equal(rect1.getPosition().y, 2); + + assert.equal(rect2.getPosition().x, 3); + assert.equal(rect2.getPosition().y, 0); + + assert.equal(rect3.getPosition().x, 0); + assert.equal(rect3.getPosition().y, 4); + + assert.equal(rect4.getPosition().x, 0); + assert.equal(rect4.getPosition().y, 0); + }); + + // ====================================================== + it('test getPosition and getAbsolutePosition for shape inside transformed stage', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var rect = new Konva.Rect({ + x: 200, + y: 20, + width: 100, + height: 50, + fill: 'red', + stroke: 'black', + strokeWidth: 4, + draggable: true, + }); + + layer.add(rect); + stage.add(layer); + + stage.rotate(180 / 3); + stage.scale({ x: 0.5, y: 0.5 }); + + stage.draw(); + + assert.equal(rect.getPosition().x, 200); + assert.equal(rect.getPosition().y, 20); + + assert.equal(Math.round(rect.getAbsolutePosition().x), 41); + assert.equal(Math.round(rect.getAbsolutePosition().y), 92); + }); + + // ====================================================== + it('test consecutive getAbsolutePositions()s when shape has offset', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var rect = new Konva.Rect({ + x: 200, + y: 20, + width: 100, + height: 50, + fill: 'red', + stroke: 'black', + strokeWidth: 4, + draggable: true, + offsetX: 30, + offsetY: 30, + //rotationDeg: 60 + //rotationDeg: Math.PI / 3 + }); + + layer.add(rect); + stage.add(layer); + + assert( + rect.getAbsolutePosition().x === 200 && + rect.getAbsolutePosition().y === 20, + 'absolute position should be 200, 20' + ); + assert( + rect.getAbsolutePosition().x === 200 && + rect.getAbsolutePosition().y === 20, + 'absolute position should be 200, 20' + ); + assert( + rect.getAbsolutePosition().x === 200 && + rect.getAbsolutePosition().y === 20, + 'absolute position should be 200, 20' + ); + assert( + rect.getAbsolutePosition().x === 200 && + rect.getAbsolutePosition().y === 20, + 'absolute position should be 200, 20' + ); + assert( + rect.getAbsolutePosition().x === 200 && + rect.getAbsolutePosition().y === 20, + 'absolute position should be 200, 20' + ); + }); + + // ====================================================== + it('test getPosition and getAbsolutePosition for transformed parent with offset offset', function () { + var side = 100; + var diagonal = Math.sqrt(side * side * 2); + + var stage = addStage(); + var layer = new Konva.Layer({ + name: 'layerName', + }); + var group = new Konva.Group({ + name: 'groupName', + id: 'groupId', + rotation: 45, + offset: { x: side / 2, y: side / 2 }, + x: diagonal / 2, + y: diagonal / 2, + }); + var rect = new Konva.Rect({ + x: 0, + y: 0, + width: side, + height: side, + fill: 'red', + name: 'rectName', + }); + var marker = new Konva.Rect({ + x: side, + y: 0, + width: 1, + height: 1, + fill: 'blue', + stroke: 'blue', + strokeWidth: 4, + name: 'markerName', + id: 'markerId', + }); + + group.add(rect); + group.add(marker); + layer.add(group); + stage.add(layer); + + assert.equal( + Math.round(marker.getAbsolutePosition().x), + Math.round(diagonal), + 'marker absolute position x should be about ' + + Math.round(diagonal) + + ' but is about ' + + Math.round(marker.getAbsolutePosition().x) + ); + assert.equal( + Math.round(marker.getAbsolutePosition().y), + Math.round(diagonal / 2), + 'marker absolute position y should be about ' + + Math.round(diagonal / 2) + + ' but is about ' + + Math.round(marker.getAbsolutePosition().y) + ); + }); + + // ====================================================== + it('test relative getAbsolutePosition for transformed parent ', function () { + var stage = addStage(); + var layer = new Konva.Layer({ + name: 'layerName', + x: 100, + y: 100, + }); + var group = new Konva.Group({ + name: 'groupName', + x: 100, + y: 100, + }); + var rect = new Konva.Rect({ + x: 50, + y: 60, + width: 50, + height: 50, + fill: 'red', + name: 'rectName', + }); + + group.add(rect); + layer.add(group); + stage.add(layer); + + assert.equal(rect.getAbsolutePosition(layer).x, 150); + assert.equal(rect.getAbsolutePosition(layer).y, 160); + }); + + // ====================================================== + it( + 'results of getAbsoluteTransform limited to position and offset transformations are the same' + + " when used with transformsEnabled = 'all' and transformsEnabled = 'position'", + function () { + var stage = addStage(); + var layer1 = new Konva.Layer({ + name: 'layerName', + x: 90, + y: 110, + offsetX: 50, + offsetY: 50, + transformsEnabled: 'all', + }); + var group1 = new Konva.Group({ + name: 'groupName', + x: 30, + y: 30, + offsetX: -60, + offsetY: -80, + transformsEnabled: 'all', + }); + var rect1 = new Konva.Rect({ + x: -50, + y: -60, + offsetX: 50, + offsetY: 50, + width: 50, + height: 50, + fill: 'red', + name: 'rectName', + id: 'rectId1', + transformsEnabled: 'all', + }); + + var layer2 = layer1.clone({ transformsEnabled: 'position' }); + var group2 = group1.clone({ transformsEnabled: 'position' }); + var rect2 = rect1.clone({ transformsEnabled: 'position' }); + + group1.add(rect1); + layer1.add(group1); + stage.add(layer1); + + group2.add(rect2); + layer2.add(group2); + stage.add(layer2); + + assert.equal(layer1.transformsEnabled(), 'all'); + assert.equal(group1.transformsEnabled(), 'all'); + assert.equal(rect1.transformsEnabled(), 'all'); + + assert.equal(layer2.transformsEnabled(), 'position'); + assert.equal(group2.transformsEnabled(), 'position'); + assert.equal(rect2.transformsEnabled(), 'position'); + + assert.deepEqual( + rect2.getAbsoluteTransform(), + rect1.getAbsoluteTransform() + ); + } + ); + + // ====================================================== + it('test dragDistance', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var rect1 = new Konva.Rect({ + x: 1, + y: 2, + width: 100, + height: 50, + fill: 'red', + }); + + var group = new Konva.Group({ + dragDistance: 2, + }); + + var rect2 = new Konva.Rect({ + x: 3, + width: 100, + height: 50, + fill: 'red', + }); + group.add(rect2); + + layer.add(rect1).add(group); + stage.add(layer); + + assert.equal(rect1.dragDistance(), Konva.dragDistance); + assert.equal(group.dragDistance(), 2); + assert.equal(rect2.dragDistance(), 2); + }); + + // ====================================================== + it('translate, rotate, scale shape', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Rect({ + x: 100, + y: 100, + rotation: 20, + width: 100, + height: 50, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + scale: { + x: 2, + y: 1, + }, + offset: { + x: 50, + y: 25, + }, + }); + + layer.add(circle); + stage.add(layer); + + var trace = layer.getContext().getTrace(); + assert.equal( + trace, + 'clearRect(0,0,578,200);save();transform(1.879,0.684,-0.342,0.94,14.581,42.306);beginPath();rect(0,0,100,50);closePath();fillStyle=green;fill();lineWidth=4;strokeStyle=black;stroke();restore();' + ); + }); + + // ====================================================== + it('test isListening', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var rect = new Konva.Rect({ + x: 100, + y: 100, + rotation: 20, + width: 100, + height: 50, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + }); + + layer.add(rect); + stage.add(layer); + + assert.equal(rect.isListening(), true); + + rect.listening(false); + assert.equal(rect.isListening(), false); + + rect.listening(true); + assert.equal(rect.isListening(), true); + + layer.listening(false); + + assert.equal(rect.isListening(), false); + + layer.listening(true); + assert.equal(rect.isListening(), true); + + stage.listening(false); + assert.equal(rect.isListening(), false); + }); + + // ====================================================== + it('test fire event', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + }); + + stage.add(layer); + layer.add(circle); + layer.draw(); + + var clicks = []; + + circle.on('click', function () { + clicks.push('circle'); + + /* + var evt = window.event; + var rightClick = evt.which ? evt.which == 3 : evt.button == 2; + console.log(rightClick); + */ + }); + var foo; + circle.on('customEvent', function (evt) { + foo = evt['foo']; + }); + + layer.on('click', function () { + clicks.push('layer'); + }); + // fire event with bubbling + circle.fire('click', undefined, true); + + //console.log(clicks); + + assert.equal(clicks.toString(), 'circle,layer'); + + // no bubble + circle.fire('click'); + + assert.equal(clicks.toString(), 'circle,layer,circle'); + + // test custom event + circle.fire('customEvent', { + foo: 'bar', + }); + + assert.equal(foo, 'bar'); + + // test fireing custom event that doesn't exist. script should not fail + circle.fire('kaboom'); + }); + + // ====================================================== + it('add remove event', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + }); + + /* + * test regular on and off + */ + assert.equal(circle.eventListeners['click'], undefined); + + circle.on('click', function () {}); + assert.equal(circle.eventListeners['click'].length, 1); + + circle.on('click', function () {}); + assert.equal(circle.eventListeners['click'].length, 2); + + circle.off('click'); + assert.equal(circle.eventListeners['click'], undefined); + + /* + * test name spacing + */ + circle.on('click.foo', function () {}); + assert.equal(circle.eventListeners['click'].length, 1); + + circle.on('click.foo', function () {}); + assert.equal(circle.eventListeners['click'].length, 2); + circle.on('click.bar', function () {}); + assert.equal(circle.eventListeners['click'].length, 3); + + circle.off('click.foo'); + assert.equal(circle.eventListeners['click'].length, 1); + + circle.off('click.bar'); + assert.equal(circle.eventListeners['click'], undefined); + + /* + * test remove all events in name space + */ + circle.on('click.foo', function () {}); + circle.on('click.foo', function () {}); + circle.on('touch.foo', function () {}); + circle.on('click.bar', function () {}); + circle.on('touch.bar', function () {}); + assert.equal(circle.eventListeners['click'].length, 3); + assert.equal(circle.eventListeners['touch'].length, 2); + circle.off('.foo'); + assert.equal(circle.eventListeners['click'].length, 1); + assert.equal(circle.eventListeners['touch'].length, 1); + + circle.off('.bar'); + assert.equal(circle.eventListeners['click'], undefined); + assert.equal(circle.eventListeners['touch'], undefined); + + // test remove all events + circle.on('click.konva', function () {}); + circle.on('click', function () {}); + circle.on('boo', function () {}); + assert.equal(circle.eventListeners['click'].length, 2); + assert.equal(circle.eventListeners['boo'].length, 1); + circle.off(); + assert.equal(circle.eventListeners['boo'], undefined); + // should not remove konva listeners + assert.equal(circle.eventListeners['click'].length, 1); + stage.add(layer); + layer.add(circle); + layer.draw(); + }); + + // ====================================================== + it('remove event with with callback', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + }); + stage.add(layer); + layer.add(circle); + layer.draw(); + + var event1 = 0; + var event2 = 0; + + var callback1 = function () { + event1 += 1; + }; + var callback2 = function () { + event2 += 1; + }; + + circle.on('event', callback1); + circle.on('event', callback2); + + circle.fire('event'); + + assert.equal(event1, 1, 'event1 triggered once'); + assert.equal(event2, 1, 'event2 triggered once'); + + circle.off('event', callback1); + circle.fire('event'); + + assert.equal(event1, 1, 'event1 still triggered once'); + assert.equal(event2, 2, 'event2 triggered twice'); + }); + + // ====================================================== + it('simulate event bubble', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + }); + + stage.add(layer); + layer.add(circle); + layer.draw(); + + var clicks = []; + + circle.on('click', function () { + clicks.push('circle'); + }); + + layer.on('click', function () { + clicks.push('layer'); + }); + + circle.fire('click', undefined, true); + + assert.equal(clicks[0], 'circle'); + assert.equal(clicks[1], 'layer'); + }); + + // ====================================================== + it('simulate cancel event bubble', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + }); + + stage.add(layer); + layer.add(circle); + layer.draw(); + + var clicks = []; + + circle.on('click', function (e) { + e.cancelBubble = true; + clicks.push('circle'); + }); + + layer.on('click', function () { + clicks.push('layer'); + }); + + circle.fire('click', {}, true); + + assert.equal(clicks[0], 'circle'); + assert.equal(clicks.length, 1); + }); + + // TODO: should we remove deligation? + it.skip('simple event delegation', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + }); + + stage.add(layer); + layer.add(circle); + layer.draw(); + + var fired = false; + // layer.on('click', 'Circle', function (e) { + // assert.equal(this, circle); + // assert.equal(e.currentTarget, circle); + // fired = true; + // }); + circle.fire('click', undefined, true); + assert.equal(fired, true); + }); + + it.skip('complex event delegation', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + }); + var group = new Konva.Group({ + name: 'group1 group2', + }); + group.add(circle); + layer.add(group); + + layer.draw(); + + var fired = false; + // layer.on('click', '.group1', function (e) { + // assert.equal(this, group); + // assert.equal(e.currentTarget, group); + // fired = true; + // }); + circle.fire('click', undefined, true); + assert.equal(fired, true); + }); + // ====================================================== + it('move shape, group, and layer, and then get absolute position', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var group = new Konva.Group(); + + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + }); + + group.add(circle); + layer.add(group); + stage.add(layer); + + circle.setPosition({ x: 100, y: 0 }); + group.setPosition({ x: 100, y: 0 }); + layer.setPosition({ x: 100, y: 0 }); + + // test relative positions + assert.equal(circle.getPosition().x, 100); + assert.equal(group.getPosition().x, 100); + assert.equal(layer.getPosition().x, 100); + + // test absolute positions + assert.equal(circle.getAbsolutePosition().x, 300); + assert.equal(group.getAbsolutePosition().x, 200); + assert.equal(layer.getAbsolutePosition().x, 100); + + layer.draw(); + }); + + // ====================================================== + it('scale layer, rotate group, position shape, and then get absolute position', function () { + var stage = addStage(); + var layer = new Konva.Layer({ + scale: { + x: 2, + y: 2, + }, + }); + var group = new Konva.Group({ + x: 100, + rotation: 90, + }); + + var rect = new Konva.Rect({ + x: 50, + y: 10, + width: 100, + height: 50, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + draggable: true, + }); + + group.add(rect); + layer.add(group); + stage.add(layer); + + // test absolute positions + assert.equal(rect.getAbsolutePosition().x, 180); + assert.equal(rect.getAbsolutePosition().y, 100); + + layer.draw(); + }); + + // ====================================================== + it('hide show circle', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + }); + + layer.add(circle); + stage.add(layer); + + assert.equal(circle.isVisible(), true); + + circle.hide(); + layer.draw(); + + assert.equal(circle.isVisible(), false); + + circle.show(); + layer.draw(); + + assert.equal(circle.isVisible(), true); + }); + + // ====================================================== + it('set shape opacity to 0.5', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 20, + draggable: true, + }); + + circle.opacity(0.5); + layer.add(circle); + stage.add(layer); + + var sceneTrace = layer.getContext().getTrace(); + //console.log(sceneTrace); + + var bufferTrace = stage.bufferCanvas.getContext().getTrace(); + + if (isBrowser) { + assert.equal( + sceneTrace, + 'clearRect(0,0,578,200);save();globalAlpha=0.5;drawImage([object HTMLCanvasElement],0,0,578,200);restore();' + ); + } else { + assert.equal( + sceneTrace, + 'clearRect(0,0,578,200);save();globalAlpha=0.5;drawImage([object Object],0,0,578,200);restore();' + ); + } + + assert.equal( + bufferTrace, + 'clearRect(0,0,578,200);save();transform(1,0,0,1,289,100);beginPath();arc(0,0,70,0,6.283,false);closePath();fillStyle=green;fill();lineWidth=20;strokeStyle=black;stroke();restore();' + ); + }); + + it('set shape opacity to 0.5 then back to 1', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + }); + + circle.opacity(0.5); + layer.add(circle); + stage.add(layer); + + assert.equal(circle.getAbsoluteOpacity(), 0.5); + + circle.opacity(1); + layer.draw(); + + assert.equal(circle.getAbsoluteOpacity(), 1); + }); + + // ====================================================== + it('get absolute z index', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var group1 = new Konva.Group(); + var group2 = new Konva.Group(); + var group3 = new Konva.Group(); + var group4 = new Konva.Group(); + + var shape1 = new Konva.Circle({ + x: 150, + y: stage.height() / 2, + radius: 40, + fill: 'green', + }); + + var shape2 = new Konva.Circle({ + x: 250, + y: stage.height() / 2, + radius: 40, + fill: 'green', + }); + + /* + * Stage(0) + * | + * Layer(1) + * | + * +-----+-----+ + * | | + * G1(2) G2(3) + * | | + * + +---+---+ + * | | | + * S1(4) G3(5) G4(6) + * | + * + + * | + * S2(7) + */ + + group1.add(shape1); + group2.add(group3); + group2.add(group4); + group3.add(shape2); + layer.add(group1); + layer.add(group2); + stage.add(layer); + + assert.equal(stage.getAbsoluteZIndex(), 0); + //console.log(layer.getAbsoluteZIndex()); + assert.equal(layer.getAbsoluteZIndex(), 1); + assert.equal(group1.getAbsoluteZIndex(), 2); + assert.equal(group2.getAbsoluteZIndex(), 3); + assert.equal(shape1.getAbsoluteZIndex(), 4); + assert.equal(group3.getAbsoluteZIndex(), 5); + assert.equal(group4.getAbsoluteZIndex(), 6); + assert.equal(shape2.getAbsoluteZIndex(), 7); + + const tempLayer = new Konva.Layer(); + assert.equal(tempLayer.getAbsoluteZIndex(), 0); + }); + + // ====================================================== + it('JPEG toDataURL() Not Hiding Lower Layers with Black', function (done) { + var stage = addStage(); + + var layer1 = new Konva.Layer(); + var layer2 = new Konva.Layer(); + + layer1.add( + new Konva.Rect({ + x: 10, + y: 10, + width: 25, + height: 15, + fill: 'red', + }) + ); + layer2.add( + new Konva.Rect({ + x: 50, + y: 50, + width: 15, + height: 25, + fill: 'green', + }) + ); + + stage.add(layer1); + stage.add(layer2); + + stage.toDataURL({ + height: 100, + width: 100, + mimeType: 'image/jpeg', + quality: 0.8, + callback: function (url) { + loadImage(url, (imageObj) => { + layer2.add( + new Konva.Image({ + x: 200, + y: 10, + image: imageObj, + }) + ); + layer2.draw(); + done(); + }); + }, + }); + }); + + // ====================================================== + it('serialize stage', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var group = new Konva.Group(); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + draggable: true, + }); + + stage.add(layer); + layer.add(group); + group.add(circle); + layer.draw(); + + var expectedJson = + '{"attrs":{"width":578,"height":200},"className":"Stage","children":[{"attrs":{},"className":"Layer","children":[{"attrs":{},"className":"Group","children":[{"attrs":{"x":289,"y":100,"radius":70,"fill":"green","stroke":"black","strokeWidth":4,"name":"myCircle","draggable":true},"className":"Circle"}]}]}]}'; + + assert.equal(stage.toJSON(), expectedJson); + }); + + // ====================================================== + it('serialize shape', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var group = new Konva.Group(); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + draggable: true, + dash: [5, 5], + }); + + stage.add(layer); + layer.add(group); + group.add(circle); + layer.draw(); + + var expectedJson = + '{"attrs":{"x":289,"y":100,"radius":70,"fill":"green","stroke":"black","strokeWidth":4,"name":"myCircle","draggable":true,"dash":[5,5]},"className":"Circle"}'; + + assert.equal(circle.toJSON(), expectedJson); + }); + + // ====================================================== + it('serialize shape with custom attributes', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var group = new Konva.Group(); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + draggable: true, + }); + + stage.add(layer); + layer.add(group); + group.add(circle); + layer.draw(); + + circle.setAttr('customAttr', 3); + circle.setAttr('customAttrObj', { + x: 1, + y: 5, + size: { + width: 10, + height: 20, + }, + }); + + var expectedJson = + '{"attrs":{"x":289,"y":100,"radius":70,"fill":"green","stroke":"black","strokeWidth":4,"name":"myCircle","draggable":true,"customAttr":3,"customAttrObj":{"x":1,"y":5,"size":{"width":10,"height":20}}},"className":"Circle"}'; + + assert.equal(circle.toJSON(), expectedJson); + }); + + // ====================================================== + it('load stage using json', function () { + var container = addContainer(); + var json = + '{"attrs":{"width":578,"height":200},"className":"Stage","children":[{"attrs":{},"className":"Layer","children":[{"attrs":{},"className":"Group","children":[{"attrs":{"x":289,"y":100,"radius":70,"fill":"green","stroke":"black","strokeWidth":4,"name":"myCircle","draggable":true},"className":"Circle"}]}]}]}'; + var stage = Konva.Node.create(json, container); + + assert.equal(stage.toJSON(), json); + }); + + // ====================================================== + it('create node using object', function () { + var node = new Konva.Circle({ + id: 'test', + radius: 10, + }); + var clone = Konva.Node.create(node.toObject()); + + assert.deepEqual(node.toObject(), clone.toObject()); + }); + + it('make sure we can create non existing node type', function () { + var json = + '{"attrs":{},"className":"Layer","children":[{"attrs":{},"className":"Group","children":[{"attrs":{"x":289,"y":100,"radius":70,"fill":"green","stroke":"black","strokeWidth":4,"name":"myCircle","draggable":true},"className":"WeirdShape"}]}]}'; + var layer = Konva.Node.create(json); + + assert.deepEqual(layer.find('Shape').length, 1); + }); + + // ====================================================== + it('serialize stage with custom shape', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var group = new Konva.Group(); + + var drawTriangle = function (context) { + context.beginPath(); + context.moveTo(200, 50); + context.lineTo(420, 80); + context.quadraticCurveTo(300, 100, 260, 170); + context.closePath(); + context.fillStrokeShape(this); + }; + var triangle = new Konva.Shape({ + sceneFunc: drawTriangle, + fill: '#00D2FF', + stroke: 'black', + strokeWidth: 4, + id: 'myTriangle', + }); + + group.add(triangle); + layer.add(group); + stage.add(layer); + + assert.equal(triangle.id(), 'myTriangle'); + + var expectedJson = + '{"attrs":{"width":578,"height":200},"className":"Stage","children":[{"attrs":{},"className":"Layer","children":[{"attrs":{},"className":"Group","children":[{"attrs":{"fill":"#00D2FF","stroke":"black","strokeWidth":4,"id":"myTriangle"},"className":"Shape"}]}]}]}'; + + assert.equal(stage.toJSON(), expectedJson); + + layer.draw(); + }); + + // ====================================================== + it('load stage with custom shape using json', function () { + var container = addContainer(); + + var drawTriangle = function (context) { + context.beginPath(); + context.moveTo(200, 50); + context.lineTo(420, 80); + context.quadraticCurveTo(300, 100, 260, 170); + context.closePath(); + context.fillStrokeShape(this); + }; + var json = + '{"attrs":{"width":578,"height":200},"className":"Stage","children":[{"attrs":{},"className":"Layer","children":[{"attrs":{},"className":"Group","children":[{"attrs":{"fill":"#00D2FF","stroke":"black","strokeWidth":4,"id":"myTriangle1","customAttrObj":{"x":1,"y":5,"size":{"width":10,"height":20}}},"className":"Shape"}]}]}]}'; + + var stage = Konva.Node.create(json, container); + + stage.find('#myTriangle1').forEach(function (node) { + node.sceneFunc(drawTriangle); + }); + + stage.draw(); + + assert.equal(stage.toJSON(), json); + }); + + // ====================================================== + it('serialize stage with an image', function (done) { + loadImage('darth-vader.jpg', (imageObj) => { + var stage = addStage(); + var layer = new Konva.Layer(); + const darth = new Konva.Image({ + x: 200, + y: 60, + image: imageObj, + offset: { + x: 50, + y: imageObj.height / 2, + }, + id: 'darth', + }); + + layer.add(darth); + stage.add(layer); + var json = + '{"attrs":{"width":578,"height":200},"className":"Stage","children":[{"attrs":{},"className":"Layer","children":[{"attrs":{"x":200,"y":60,"offsetX":50,"offsetY":150,"id":"darth"},"className":"Image"}]}]}'; + + assert.equal(stage.toJSON(), json); + + done(); + }); + }); + + // ====================================================== + it('load stage with an image', function (done) { + var container = addContainer(); + loadImage('darth-vader.jpg', (imageObj) => { + var json = + '{"attrs":{"width":578,"height":200},"className":"Stage","children":[{"attrs":{},"className":"Layer","children":[{"attrs":{"x":200,"y":60,"offsetX":50,"offsetY":150,"id":"darth1"},"className":"Image"}]}]}'; + var stage = Konva.Node.create(json, container); + + assert.equal(stage.toJSON(), json); + stage.find('#darth1').forEach(function (node) { + node.setImage(imageObj); + }); + stage.draw(); + + done(); + }); + }); + + // ====================================================== + it('remove shape', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + }); + + layer.add(circle); + stage.add(layer); + + assert.equal(layer.children.length, 1); + + circle.remove(); + + assert.equal(layer.children.length, 0); + + layer.draw(); + + assert.equal(circle.getParent(), undefined); + }); + + // ====================================================== + it('destroy shape', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + }); + + layer.add(circle); + stage.add(layer); + + assert.equal(layer.children.length, 1); + + circle.destroy(); + + assert.equal(layer.children.length, 0); + + layer.draw(); + + assert.equal(circle.getParent(), undefined); + }); + + // ====================================================== + it('destroy shape without adding its parent to stage', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + id: 'myCircle', + }); + + layer.add(circle); + + var node = stage.find('#myCircle')[0]; + + assert.equal(node, undefined); + + circle.destroy(); + }); + + // ====================================================== + it('destroy layer with shape', function () { + var stage = addStage(); + var layer = new Konva.Layer({ + name: 'myLayer', + }); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + }); + + layer.add(circle); + stage.add(layer); + + assert.equal(stage.children.length, 1); + assert(stage.find('.myLayer')[0] !== undefined); + assert(stage.find('.myCircle')[0] !== undefined); + + layer.destroy(); + + assert.equal(stage.children.length, 0); + assert.equal(stage.find('.myLayer')[0], undefined); + assert.equal(stage.find('.myCircle')[0], undefined); + + stage.draw(); + }); + + // ====================================================== + it('destroy stage with layer and shape', function () { + var stage = addStage(); + var layer = new Konva.Layer({ + name: 'myLayer', + }); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + }); + + layer.add(circle); + stage.add(layer); + + stage.destroy(); + + assert.equal(layer.getParent(), undefined); + assert.equal(circle.getParent(), undefined); + assert.equal(stage.children.length, 0); + assert.equal(layer.children.length, 0); + }); + + // ====================================================== + it('destroy group with shape', function () { + var stage = addStage(); + var layer = new Konva.Layer({ + name: 'myLayer', + }); + var group = new Konva.Group({ + name: 'myGroup', + }); + + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + }); + + group.add(circle); + layer.add(group); + stage.add(layer); + + assert.equal(layer.getChildren().length, 1); + assert(stage.find('.myGroup')[0] !== undefined); + assert(stage.find('.myCircle')[0] !== undefined); + + group.destroy(); + + assert.equal(layer.children.length, 0); + assert.equal(stage.find('.myGroup')[0], undefined); + assert.equal(stage.find('.myCircle')[0], undefined); + + stage.draw(); + }); + + // ====================================================== + it('destroy layer with no shapes', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + layer.destroy(); + + assert.equal(stage.children.length, 0); + }); + + // ====================================================== + it('destroy shape multiple times', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var shape1 = new Konva.Circle({ + x: 150, + y: 100, + radius: 50, + fill: 'green', + name: 'myCircle', + }); + + var shape2 = new Konva.Circle({ + x: 250, + y: 100, + radius: 50, + fill: 'green', + name: 'myCircle', + }); + + layer.add(shape1); + layer.add(shape2); + stage.add(layer); + + assert.equal(layer.getChildren().length, 2); + + shape1.destroy(); + shape1.destroy(); + + assert.equal(layer.getChildren().length, 1); + + layer.draw(); + }); + + // ====================================================== + it('remove layer multiple times', function () { + var stage = addStage(); + var layer1 = new Konva.Layer(); + var layer2 = new Konva.Layer(); + + var shape1 = new Konva.Circle({ + x: 150, + y: 100, + radius: 50, + fill: 'green', + name: 'myCircle', + }); + + var shape2 = new Konva.Circle({ + x: 250, + y: 100, + radius: 50, + fill: 'green', + name: 'myCircle', + }); + + layer1.add(shape1); + layer2.add(shape2); + stage.add(layer1); + stage.add(layer2); + + assert.equal(stage.getChildren().length, 2); + + layer1.remove(); + layer1.remove(); + + assert.equal(stage.getChildren().length, 1); + + stage.draw(); + }); + + // ====================================================== + it('destroy shape by id or name', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + id: 'myCircle2', + }); + + var rect = new Konva.Rect({ + x: 300, + y: 100, + width: 100, + height: 50, + fill: 'purple', + stroke: 'black', + strokeWidth: 4, + name: 'myRect2', + }); + + var circleColorKey = circle.colorKey; + var rectColorKey = rect.colorKey; + + layer.add(circle); + layer.add(rect); + stage.add(layer); + + assert.equal(Konva.shapes[circleColorKey]._id, circle._id); + assert.equal(Konva.shapes[rectColorKey]._id, rect._id); + + circle.destroy(); + + assert.equal(Konva.shapes[circleColorKey], undefined); + assert.equal(Konva.shapes[rectColorKey]._id, rect._id); + + rect.destroy(); + + assert.equal(Konva.shapes[circleColorKey], undefined); + assert.equal(Konva.shapes[rectColorKey], undefined); + }); + // ====================================================== + it('hide stage', function () { + var stage = addStage({ + visible: false, + }); + var layer = new Konva.Layer(); + var group = new Konva.Group(); + + var rect = new Konva.Rect({ + x: 200, + y: 100, + width: 100, + height: 50, + fill: 'red', + stroke: 'black', + strokeWidth: 4, + draggable: true, + rotation: 60, + scale: { + x: 2, + y: 1, + }, + visible: false, + }); + + group.add(rect); + layer.add(group); + stage.add(layer); + + if (isBrowser) { + assert.equal(stage.content.style.display, 'none'); + } + + stage.show(); + stage.draw(); + if (isBrowser) { + assert.equal(stage.content.style.display, ''); + } + + stage.hide(); + stage.draw(); + if (isBrowser) { + assert.equal(stage.content.style.display, 'none'); + } + }); + + // ====================================================== + it('listening, & shouldDrawHit', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + + var rect = new Konva.Rect({ + x: 100, + y: 50, + width: 100, + height: 50, + fill: 'green', + stroke: 'blue', + }); + + layer.add(rect); + stage.add(layer); + + assert.equal(rect.isListening(), true); + assert.equal(rect.shouldDrawHit(), true); + + rect.listening(false); + + assert.equal(rect.isListening(), false); + assert.equal(rect.shouldDrawHit(), false); + }); + + // ====================================================== + it('group, listening, & shouldDrawHit', function () { + var stage = addStage(); + + var layer = new Konva.Layer({ + listening: false, + }); + stage.add(layer); + + var group = new Konva.Group({ + listening: false, + }); + layer.add(group); + + var rect = new Konva.Rect({ + x: 100, + y: 50, + width: 100, + height: 50, + fill: 'green', + stroke: 'blue', + listening: true, + }); + group.add(rect); + layer.draw(); + + showHit(layer); + + assert.equal(rect.isListening(), false); + assert.equal(rect.shouldDrawHit(), false); + + assert.equal(group.isListening(), false); + assert.equal(group.shouldDrawHit(), false, 'hit graph for group'); + + assert.equal(layer.isListening(), false); + assert.equal(layer.shouldDrawHit(), false, 'hit graph for layer'); + + var layerClick = 0; + var groupClick = 0; + var rectClick = 0; + + rect.on('click', function () { + rectClick += 1; + }); + group.on('click', function () { + groupClick += 1; + }); + layer.on('click', function () { + layerClick += 1; + }); + showHit(layer); + + simulateMouseDown(stage, { + x: 150, + y: 75, + }); + simulateMouseUp(stage, { + x: 150, + y: 75, + }); + + assert.equal(rectClick, 0, 'click on rectangle'); + assert.equal(groupClick, 0, 'no click on group'); + assert.equal(layerClick, 0, 'no click on layer'); + }); + + // ====================================================== + it('isVisible', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var group = new Konva.Group(); + var circle = new Konva.Circle({ + x: 100, + y: 100, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + draggable: true, + }); + + group.add(circle); + layer.add(group); + stage.add(layer); + + assert.equal(stage.isVisible(), true); + assert.equal(layer.isVisible(), true); + assert.equal(circle.isVisible(), true); + + stage.visible(false); + + assert.equal(stage.isVisible(), false); + assert.equal(layer.isVisible(), false); + assert.equal(circle.isVisible(), false); + + stage.visible(true); + layer.visible(false); + + assert.equal(stage.isVisible(), true); + assert.equal(layer.isVisible(), false); + assert.equal(circle.isVisible(), false); + + layer.visible(true); + circle.visible(false); + + assert.equal(stage.isVisible(), true); + assert.equal(layer.isVisible(), true); + assert.equal(circle.isVisible(), false); + + circle.visible(true); + stage.visible(true); + + assert.equal(stage.isVisible(), true); + assert.equal(layer.isVisible(), true); + assert.equal(circle.isVisible(), true); + + stage.visible(true); + layer.visible(true); + + assert.equal(stage.isVisible(), true); + assert.equal(layer.isVisible(), true); + assert.equal(circle.isVisible(), true); + + layer.visible(true); + circle.visible(true); + + assert.equal(stage.isVisible(), true); + assert.equal(layer.isVisible(), true); + assert.equal(circle.isVisible(), true); + }); + + it('overloaders', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var group = new Konva.Group(); + var circle = new Konva.Circle({ + x: 100, + y: 100, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + draggable: true, + }); + + group.add(circle); + layer.add(group); + stage.add(layer); + + circle.x(1); + assert.equal(circle.x(), 1); + + circle.y(2); + assert.equal(circle.y(), 2); + + circle.opacity(0.5); + assert.equal(circle.opacity(), 0.5); + + circle.name('foo'); + assert.equal(circle.name(), 'foo'); + + circle.id('bar'); + assert.equal(circle.id(), 'bar'); + + circle.rotation(2); + assert.equal(circle.rotation(), 2); + + circle.scale({ x: 2, y: 2 }); + assert.equal(circle.scale().x, 2); + assert.equal(circle.scale().y, 2); + + circle.scaleX(5); + assert.equal(circle.scaleX(), 5); + + circle.scaleY(8); + assert.equal(circle.scaleY(), 8); + + circle.skew({ x: 2, y: 2 }); + assert.equal(circle.skew().x, 2); + assert.equal(circle.skew().y, 2); + + circle.skewX(5); + assert.equal(circle.skewX(), 5); + + circle.skewY(8); + assert.equal(circle.skewY(), 8); + + circle.offset({ x: 2, y: 2 }); + assert.equal(circle.offset().x, 2); + assert.equal(circle.offset().y, 2); + + circle.offsetX(5); + assert.equal(circle.offsetX(), 5); + + circle.offsetY(8); + assert.equal(circle.offsetY(), 8); + + circle.width(23); + assert.equal(circle.width(), 23); + + circle.height(11); + assert.equal(circle.height(), 11); + + circle.listening(false); + assert.equal(circle.listening(), false); + + circle.visible(false); + assert.equal(circle.visible(), false); + + // circle.transformsEnabled(false); + // assert.equal(circle.transformsEnabled(), false); + + circle.position({ x: 6, y: 8 }); + assert.equal(circle.position().x, 6); + assert.equal(circle.position().y, 8); + + // because the height was set to 11, the width + // is also 11 because the node is a circle + assert.equal(circle.size().width, 11); + assert.equal(circle.size().height, 11); + }); + + it('overloaders reset', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + radius: 70, + fill: 'green', + }); + + layer.add(circle); + stage.add(layer); + + circle.scale({ x: 2, y: 2 }); + + circle.scale(undefined); + + assert.equal(circle.scaleX(), 1); + assert.equal(circle.scaleY(), 1); + }); + + it('cache shape', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var group = new Konva.Group(); + var circle = new Konva.Circle({ + x: 74, + y: 74, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + draggable: true, + }); + + group.add(circle); + layer.add(group); + stage.add(layer); + + assert.equal(circle._getCanvasCache(), undefined); + + circle + .cache({ + x: -74, + y: -74, + width: 148, + height: 148, + }) + .offset({ + x: 74, + y: 74, + }); + + assert.notEqual(circle._getCanvasCache().scene, undefined); + assert.notEqual(circle._getCanvasCache().hit, undefined); + + layer.draw(); + + //document.body.appendChild(circle._getCanvasCache().scene._canvas); + // document.body.appendChild(circle._getCanvasCache().hit._canvas); + + showHit(layer); + + //assert.equal(layer.getContext().getTrace(), 'clearRect(0,0,578,200);save();transform(1,0,0,1,74,74);beginPath();arc(0,0,70,0,6.283,false);closePath();fillStyle=green;fill();lineWidth=4;strokeStyle=black;stroke();restore();clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);drawImage([object HTMLCanvasElement],0,0);restore();'); + + //assert.equal(circle._getCanvasCache().scene.getContext().getTrace(), 'save();translate(74,74);beginPath();arc(0,0,70,0,6.283,false);closePath();fillStyle=green;fill();lineWidth=4;strokeStyle=black;stroke();restore();'); + }); + + it('cache shape before adding to layer', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var group = new Konva.Group({ + x: 0, + y: 0, + }); + var rect = new Konva.Rect({ + x: 35, + y: 35, + width: 50, + height: 50, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + offsetX: 25, + offsetY: 25, + rotation: 45, + draggable: true, + }); + group.add(rect); + + assert.equal(rect._getCanvasCache(), undefined); + group.cache({ + x: 0, + y: 0, + width: 148, + height: 148, + }); + stage.add(layer); + + assert(group._getCanvasCache().scene); + assert(group._getCanvasCache().hit); + + layer.add(group); + layer.draw(); + showHit(layer); + var shape = stage.getIntersection({ + x: 5, + y: 5, + }); + assert(!shape, 'no shape (rotate applied)'); + shape = stage.getIntersection({ + x: 35, + y: 35, + }); + assert.equal(shape, rect, 'rect found'); + }); + + it('stage.toObject() when stage contains an image', function (done) { + loadImage('darth-vader.jpg', (imageObj) => { + var stage = addStage(); + + var layer = new Konva.Layer(); + const darth = new Konva.Image({ + x: 200, + y: 60, + image: imageObj, + width: 100, + height: 100, + offset: { x: 50, y: 30 }, + crop: { x: 135, y: 7, width: 167, height: 134 }, + draggable: true, + }); + + layer.add(darth); + stage.add(layer); + + assert.equal(stage.toObject().className, 'Stage'); + + done(); + }); + }); + + it('toObject with extended prototypes', function () { + var node = new Konva.Circle({ + id: 'foo', + radius: 10, + }); + Number.prototype['customFunc'] = function () {}; + assert.equal(node.toObject().attrs.radius, 10); + delete Number.prototype['customFunc']; + }); + + it('toObject with property in attrs and instanse', function () { + var node = new Konva.Circle({ + id: 'foo1', + radius: 10, + filled: true, + }); + node['filled'] = true; + assert.equal(node.toObject().attrs.filled, true); + }); + + it('test findAncestor', function () { + var stage = addStage(); + stage.setAttrs({ + name: 'stage', + id: 'stage', + }); + var layer = new Konva.Layer({ + id: 'layer', + name: 'layer', + }); + stage.add(layer); + + var group = new Konva.Group({ + id: 'group', + name: 'group', + }); + layer.add(group); + + var rect = new Konva.Rect({ + x: 35, + y: 35, + width: 50, + height: 50, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'rect', + }); + group.add(rect); + + stage.draw(); + + assert.equal(!!rect.findAncestor('.null'), false); + + assert.notEqual( + rect.findAncestor('.rect'), + rect, + 'do not find self in ancestors' + ); + + assert.equal(rect.findAncestor('.stage'), stage, 'find stage by name'); + assert.equal(rect.findAncestor('#stage'), stage, 'find stage by id'); + assert.equal(rect.findAncestor('Stage'), stage, 'find stage by node type'); + + assert.equal(rect.findAncestor('.layer'), layer); + assert.equal(rect.findAncestor('#layer'), layer); + assert.equal(rect.findAncestor('Layer'), layer); + + assert.equal(rect.findAncestor('.group'), group); + assert.equal(rect.findAncestor('#group'), group); + assert.equal(rect.findAncestor('Group'), group); + + assert.equal(rect.findAncestor(), null, 'return null if no selector'); + }); + + it('moveToTop() properly changes z-indices of the node and its siblings', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect1 = new Konva.Rect(); + var rect2 = new Konva.Rect(); + var rect3 = new Konva.Rect(); + var rect4 = new Konva.Rect(); + layer.add(rect1, rect2, rect3, rect4); + + assert.equal(rect1.getZIndex(), 0); + assert.equal(rect2.getZIndex(), 1); + assert.equal(rect3.getZIndex(), 2); + assert.equal(rect4.getZIndex(), 3); + + rect2.moveToTop(); + + assert.equal(rect1.getZIndex(), 0); + assert.equal(rect3.getZIndex(), 1); + assert.equal(rect4.getZIndex(), 2); + assert.equal(rect2.getZIndex(), 3); + + rect1.moveToTop(); + + assert.equal(rect3.getZIndex(), 0); + assert.equal(rect4.getZIndex(), 1); + assert.equal(rect2.getZIndex(), 2); + assert.equal(rect1.getZIndex(), 3); + }); + + it('moveToBottom() properly changes z-indices of the node and its siblings', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect1 = new Konva.Rect(); + var rect2 = new Konva.Rect(); + var rect3 = new Konva.Rect(); + var rect4 = new Konva.Rect(); + layer.add(rect1, rect2, rect3, rect4); + + assert.equal(rect1.getZIndex(), 0); + assert.equal(rect2.getZIndex(), 1); + assert.equal(rect3.getZIndex(), 2); + assert.equal(rect4.getZIndex(), 3); + + rect3.moveToBottom(); + + assert.equal(rect3.getZIndex(), 0); + assert.equal(rect1.getZIndex(), 1); + assert.equal(rect2.getZIndex(), 2); + assert.equal(rect4.getZIndex(), 3); + + rect4.moveToBottom(); + + assert.equal(rect4.getZIndex(), 0); + assert.equal(rect3.getZIndex(), 1); + assert.equal(rect1.getZIndex(), 2); + assert.equal(rect2.getZIndex(), 3); + }); + + it('moveUp() properly changes z-indices of the node and its siblings', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect1 = new Konva.Rect(); + var rect2 = new Konva.Rect(); + var rect3 = new Konva.Rect(); + var rect4 = new Konva.Rect(); + layer.add(rect1, rect2, rect3, rect4); + + assert.equal(rect1.getZIndex(), 0); + assert.equal(rect2.getZIndex(), 1); + assert.equal(rect3.getZIndex(), 2); + assert.equal(rect4.getZIndex(), 3); + + rect1.moveUp(); + + assert.equal(rect2.getZIndex(), 0); + assert.equal(rect1.getZIndex(), 1); + assert.equal(rect3.getZIndex(), 2); + assert.equal(rect4.getZIndex(), 3); + + rect3.moveUp(); + + assert.equal(rect2.getZIndex(), 0); + assert.equal(rect1.getZIndex(), 1); + assert.equal(rect4.getZIndex(), 2); + assert.equal(rect3.getZIndex(), 3); + }); + + it('moveDown() properly changes z-indices of the node and its siblings', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect1 = new Konva.Rect(); + var rect2 = new Konva.Rect(); + var rect3 = new Konva.Rect(); + var rect4 = new Konva.Rect(); + layer.add(rect1, rect2, rect3, rect4); + + assert.equal(rect1.getZIndex(), 0); + assert.equal(rect2.getZIndex(), 1); + assert.equal(rect3.getZIndex(), 2); + assert.equal(rect4.getZIndex(), 3); + + rect4.moveDown(); + + assert.equal(rect1.getZIndex(), 0); + assert.equal(rect2.getZIndex(), 1); + assert.equal(rect4.getZIndex(), 2); + assert.equal(rect3.getZIndex(), 3); + + rect2.moveDown(); + + assert.equal(rect2.getZIndex(), 0); + assert.equal(rect1.getZIndex(), 1); + assert.equal(rect4.getZIndex(), 2); + assert.equal(rect3.getZIndex(), 3); + }); + + it('setZIndex() properly changes z-indices of the node and its siblings', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect1 = new Konva.Rect(); + var rect2 = new Konva.Rect(); + var rect3 = new Konva.Rect(); + var rect4 = new Konva.Rect(); + layer.add(rect1, rect2, rect3, rect4); + + assert.equal(rect1.getZIndex(), 0); + assert.equal(rect2.getZIndex(), 1); + assert.equal(rect3.getZIndex(), 2); + assert.equal(rect4.getZIndex(), 3); + + rect1.setZIndex(2); + + assert.equal(rect2.getZIndex(), 0); + assert.equal(rect3.getZIndex(), 1); + assert.equal(rect1.getZIndex(), 2); + assert.equal(rect4.getZIndex(), 3); + + rect2.setZIndex(3); + + assert.equal(rect3.getZIndex(), 0); + assert.equal(rect1.getZIndex(), 1); + assert.equal(rect4.getZIndex(), 2); + assert.equal(rect2.getZIndex(), 3); + + rect2.setZIndex(1); + + assert.equal(rect3.getZIndex(), 0); + assert.equal(rect2.getZIndex(), 1); + assert.equal(rect1.getZIndex(), 2); + assert.equal(rect4.getZIndex(), 3); + + rect4.setZIndex(0); + + assert.equal(rect4.getZIndex(), 0); + assert.equal(rect3.getZIndex(), 1); + assert.equal(rect2.getZIndex(), 2); + assert.equal(rect1.getZIndex(), 3); + }); + + it('remove() removes the node and properly changes z-indices of its siblings', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect1 = new Konva.Rect(); + var rect2 = new Konva.Rect(); + var rect3 = new Konva.Rect(); + var rect4 = new Konva.Rect(); + layer.add(rect1, rect2, rect3, rect4); + + assert.equal(layer.getChildren().length, 4); + assert.equal(rect1.getZIndex(), 0); + assert.equal(rect2.getZIndex(), 1); + assert.equal(rect3.getZIndex(), 2); + assert.equal(rect4.getZIndex(), 3); + + rect4.remove(); + + assert.equal(layer.getChildren().length, 3); + assert.equal(rect1.getZIndex(), 0); + assert.equal(rect2.getZIndex(), 1); + assert.equal(rect3.getZIndex(), 2); + + rect2.remove(); + + assert.equal(layer.getChildren().length, 2); + assert.equal(rect1.getZIndex(), 0); + assert.equal(rect3.getZIndex(), 1); + + rect1.remove(); + + assert.equal(layer.getChildren().length, 1); + assert.equal(rect3.getZIndex(), 0); + }); + + it('show warning when we are trying to use non-objects for component setters', function () { + if (!Konva.isUnminified) { + return; + } + var stage = addStage(); + var callCount = 0; + var oldWarn = Konva.Util.warn; + Konva.Util.warn = function () { + callCount += 1; + }; + + stage.scale(1 as any); + assert.equal(callCount, 1); + Konva.Util.warn = oldWarn; + }); + + it('show warning for unexpected zIndexes', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + stage.add(layer); + + var shape = new Konva.Circle({ + radius: 50, + fill: 'red', + }); + layer.add(shape); + + var callCount = 0; + var oldWarn = Konva.Util.warn; + Konva.Util.warn = function () { + callCount += 1; + }; + + shape.zIndex(-1); + shape.zIndex(0); + shape.zIndex(10); + + assert.equal(callCount, 2); + Konva.Util.warn = oldWarn; + }); + + it('check transform caching', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + + var rect = new Konva.Rect({ + x: 50, + y: 50, + width: 150, + height: 50, + stroke: 'black', + strokeWidth: 4, + }); + + var text00 = new Konva.Text({ + text: 'Sample text', + x: 50, + y: 50, + fontSize: 20, + }); + + layer.add(rect); + layer.add(text00); + + stage.add(layer); + + stage.x(+40); + stage.y(+40); + + stage.draw(); + + layer.removeChildren(); + text00.getClientRect({}); // Commenting this line or putting it after line 41 puts text in rectangle + + layer.add(text00); + layer.add(rect); + + stage.draw(); + + assert.equal(text00.getClientRect().x, 90); + assert.equal(text00.getClientRect().y, 90); + }); + + // ====================================================== + it('isClientRectOnScreen() method', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 30, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + }); + + assert.equal( + circle.isClientRectOnScreen(), + false, + 'not visible when not on stage' + ); + + layer.add(circle); + stage.add(layer); + + assert.equal(circle.isClientRectOnScreen(), true); + + circle.x(-circle.radius() - circle.strokeWidth() / 2 - 1); // Move circle 1px outside of visible area + assert.equal(circle.isClientRectOnScreen(), false); + assert.equal(circle.isClientRectOnScreen({ x: 10, y: 0 }), true); + assert.equal(circle.isClientRectOnScreen({ x: 0, y: 10 }), false); + + stage.x(10); + assert.equal(circle.isClientRectOnScreen(), true); + }); + + // ====================================================== + it('getRelativePointerPosition() method', function () { + var stage = addStage(); + var layer = new Konva.Layer({ + scaleX: 2, + }); + stage.add(layer); + var circle = new Konva.Circle({ + x: 100, + y: 100, + radius: 30, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + }); + layer.add(circle); + + simulateMouseMove(stage, { + x: 100, + y: 100, + }); + + assert.equal(circle.getRelativePointerPosition().x, -50); + assert.equal(circle.getRelativePointerPosition().y, 0); + }); +}); diff --git a/test/unit/Path-test.ts b/test/unit/Path-test.ts new file mode 100644 index 000000000..4b3501f67 --- /dev/null +++ b/test/unit/Path-test.ts @@ -0,0 +1,1669 @@ +import { assert } from 'chai'; + +import worldMap from '../assets/worldMap'; +import tiger from '../assets/tiger'; + +import { + addStage, + Konva, + createCanvas, + compareLayerAndCanvas, + cloneAndCompareLayer, + isNode, + assertAlmostDeepEqual, + isBrowser, +} from './test-utils'; + +describe('Path', function () { + // ====================================================== + it('add simple path', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + + var path = new Konva.Path({ + data: 'M200,100h100v50z', + fill: '#ccc', + stroke: '#333', + strokeWidth: 2, + shadowColor: 'black', + shadowBlur: 2, + shadowOffset: { x: 10, y: 10 }, + shadowOpacity: 0.5, + draggable: true, + }); + + path.on('mouseover', function () { + this.fill('red'); + layer.draw(); + }); + + path.on('mouseout', function () { + this.fill('#ccc'); + layer.draw(); + }); + + layer.add(path); + + stage.add(layer); + + assert.equal(path.data(), 'M200,100h100v50z'); + assert.equal(path.dataArray.length, 4); + + path.data('M200'); + + assert.equal(path.data(), 'M200'); + assert.equal(path.dataArray.length, 1); + + path.data('M200,100h100v50z'); + + assert.equal(path.getClassName(), 'Path'); + }); + + // ====================================================== + it('add path with line cap and line join', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var path = new Konva.Path({ + data: 'M200,100h100v50', + stroke: '#333', + strokeWidth: 20, + draggable: true, + lineCap: 'round', + lineJoin: 'round', + }); + + layer.add(path); + + stage.add(layer); + + var trace = layer.getContext().getTrace(); + + assert.equal( + trace, + 'clearRect(0,0,578,200);save();lineJoin=round;transform(1,0,0,1,0,0);beginPath();moveTo(200,100);lineTo(300,100);lineTo(300,150);lineCap=round;lineWidth=20;strokeStyle=#333;stroke();restore();' + ); + }); + + //======================================================= + it('add path with double closed path and releative moveto', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var path = new Konva.Path({ + data: 'm 4.114181,28.970898 8.838835,50.205 22.980968,-48.4372 z m -4.59619304,13.7887 0,18.385 c 14.10943004,-12.211 24.57748204,-6.2149 35.00178204,0 l 2.304443,-8.6004 -13.264598,0 c 2.227131,-5.4642 7.171257,-11.834 -6.858423,-11.8792 -3.920218,12.899 -9.493066,14.6427 -17.18320404,2.0946 z', + stroke: '#000', + strokeWidth: 1, + lineCap: 'round', + lineJoin: 'round', + }); + + layer.add(path); + + stage.add(layer); + + var trace = layer.getContext().getTrace(); + + assert.equal( + trace, + 'clearRect(0,0,578,200);save();lineJoin=round;transform(1,0,0,1,0,0);beginPath();moveTo(4.114,28.971);lineTo(12.953,79.176);lineTo(35.934,30.739);closePath();moveTo(-0.482,42.76);lineTo(-0.482,61.145);bezierCurveTo(13.627,48.934,24.095,54.93,34.52,61.145);lineTo(36.824,52.544);lineTo(23.56,52.544);bezierCurveTo(25.787,47.08,30.731,40.71,16.701,40.665);bezierCurveTo(12.781,53.564,7.208,55.308,-0.482,42.76);closePath();lineCap=round;lineWidth=1;strokeStyle=#000;stroke();restore();' + ); + }); + + //======================================================= + it('complex path made of many different closed and open paths (Sopwith Camel)', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var path = new Konva.Path({ + data: 'm 15.749277,58.447629 8.495831,-0.05348 m 0.319898,-15.826548 -0.202438,17.295748 0.942206,0.941911 1.345933,-1.816987 0.20211,-11.642611 z m 77.458374,28.680768 c 0,5.308829 -4.303525,9.612686 -9.612485,9.612686 -5.30873,0 -9.612194,-4.303857 -9.612194,-9.612686 0,-5.308829 4.303464,-9.61226 9.612194,-9.61226 5.30896,0 9.612485,4.303431 9.612485,9.61226 z m -3.520874,0 c 0,3.364079 -2.72763,6.091348 -6.091611,6.091348 -3.364243,0 -6.091119,-2.727269 -6.091119,-6.091348 0,-3.363719 2.726876,-6.090791 6.091119,-6.090791 3.363981,0 6.091611,2.727072 6.091611,6.090791 z m -3.997576,0 c 0,1.156718 -0.937743,2.093937 -2.094035,2.093937 -1.156062,0 -2.093871,-0.937219 -2.093871,-2.093937 0,-1.156357 0.937809,-2.093773 2.093871,-2.093773 1.156292,0 2.094035,0.937416 2.094035,2.093773 z m 45.77821,4.283023 c -0.24607,1.90039 5.06492,3.680204 7.61403,5.520093 0.50662,0.514199 0.27889,0.975967 -0.0984,1.427532 l 3.9019,-1.141987 c -0.59258,-0.121397 -1.85951,0.01969 -1.71294,-0.380038 -0.85894,-1.950525 -3.68693,-2.761261 -5.61518,-4.092495 -1.06971,-1.03496 0.0997,-1.60766 0.76126,-2.284203 z M 43.206396,42.60133 55.578964,74.008743 58.71987,73.910313 47.203939,44.40726 c -1.109013,-0.737406 -1.174108,-2.1004 -3.997543,-1.808752 z m -18.654022,-0.570632 12.467721,31.692335 3.140643,0.09843 -12.467656,-31.692927 z m 2.285318,42.353106 -2.636648,-0.06431 0.163066,0.734584 3.709372,9.956142 2.357927,-1.168202 z m 19.411934,0.566268 -6.370726,9.901284 2.090163,1.615665 7.13671,-11.417403 0.303821,-0.4347 -2.942667,-0.02953 z m -12.091915,8.286013 c -5.729323,0 -10.367941,4.560169 -10.367941,10.184405 0,5.62429 4.638618,10.18489 10.367941,10.18489 5.729424,0 10.37654,-4.5606 10.37654,-10.18489 0,-5.624236 -4.647083,-10.184405 -10.37654,-10.184405 z m 0,2.473319 c 4.310029,0 7.811352,3.453552 7.811352,7.711086 0,4.25776 -3.50129,7.71167 -7.811352,7.71167 -4.310157,0 -7.803016,-3.45391 -7.803016,-7.71167 0,-4.257534 3.492859,-7.711086 7.803016,-7.711086 z m 3.528526,-21.795876 c -1.29032,-0.0066 -2.97525,0.03839 -3.402437,1.45155 l -0.01969,7.494437 c 0.586775,0.761915 1.42432,0.688978 2.236565,0.71411 l 26.529545,-0.14502 8.636784,0.761324 0,-7.518487 C 71.56989,75.908478 71.09444,75.467051 70.239377,75.338961 61.126027,73.734287 49.244756,73.929146 37.690371,73.911166 z M 20.959576,41.269176 c -0.0098,0.603377 0.575258,0.881409 0.575258,0.881409 L 58.95771,42.33629 c -4.893946,-0.985482 -16.592629,-2.859625 -32.835015,-2.783473 -1.570354,0.107617 -5.151439,1.109571 -5.163119,1.712718 z m 3.353022,14.276273 c -2.79955,0.01312 -5.595489,0.02953 -8.382964,0.05545 l 0,9.9e-5 0.0033,1.447677 -1.173484,0.01312 0.0066,1.244485 1.184048,0.05807 c -1.34298,0.220812 -2.956414,1.305807 -3.054779,3.476618 0.0098,3.269061 0.01312,6.538943 0.01312,9.808103 l -1.21197,0.0033 -0.01969,-2.361569 -4.6851755,0.0033 0,5.901969 4.6323185,0.0066 -0.02953,-1.7556 1.308596,-0.02297 0.0098,9.180447 c -0.0066,1.315781 2.739048,3.634336 4.542583,3.634336 l 4.811756,-2.995032 c 1.616583,-0.107617 1.758126,0.482078 1.884346,1.076924 l 35.667571,0.318914 6.909664,-0.81031 m 4.994738,-0.585889 85.216614,-9.991675 c 4.93952,-0.487623 14.9162,-22.255511 -3.75098,-25.556727 -5.12814,-0.887479 -15.53194,4.839613 -21.44018,9.104984 -2.31314,1.954593 -1.74166,4.084194 0.0263,5.982879 l -72.209399,-8.111923 -2.12281,-0.0012 c -0.966453,1.390128 -3.158262,3.260465 -4.554559,4.053123 M 49.36027,58.361483 c -1.699757,-1.038536 -2.965602,-2.804438 -4.533856,-2.875275 -3.903936,0.0011 -7.904399,0.0066 -11.882849,0.01312 m -3.081192,0.0066 c -1.043195,0.0033 -2.082715,0.0066 -3.116396,0.0098', + stroke: '#000', + strokeWidth: 1, + lineCap: 'round', + lineJoin: 'round', + }); + + layer.add(path); + + stage.add(layer); + + var trace = layer.getContext().getTrace(); + + assert.equal( + trace, + 'bezierCurveTo(140.037,77.432,145.348,79.212,147.897,81.051);bezierCurveTo(148.404,81.566,148.176,82.027,147.799,82.479);lineTo(151.701,81.337);bezierCurveTo(151.108,81.216,149.841,81.357,149.988,80.957);bezierCurveTo(149.129,79.006,146.301,78.196,144.373,76.864);bezierCurveTo(143.303,75.83,144.472,75.257,145.134,74.58);closePath();moveTo(43.206,42.601);lineTo(55.579,74.009);lineTo(58.72,73.91);lineTo(47.204,44.407);bezierCurveTo(46.095,43.67,46.03,42.307,43.206,42.599);closePath();moveTo(24.552,42.031);lineTo(37.02,73.723);lineTo(40.161,73.821);lineTo(27.693,42.129);closePath();moveTo(26.838,84.384);lineTo(24.201,84.319);lineTo(24.364,85.054);lineTo(28.073,95.01);lineTo(30.431,93.842);closePath();moveTo(46.25,84.95);lineTo(39.879,94.851);lineTo(41.969,96.467);lineTo(49.106,85.05);lineTo(49.41,84.615);lineTo(46.467,84.585);closePath();moveTo(34.158,93.236);bezierCurveTo(28.428,93.236,23.79,97.796,23.79,103.42);bezierCurveTo(23.79,109.045,28.428,113.605,34.158,113.605);bezierCurveTo(39.887,113.605,44.534,109.045,44.534,103.42);bezierCurveTo(44.534,97.796,39.887,93.236,34.158,93.236);closePath();moveTo(34.158,95.709);bezierCurveTo(38.468,95.709,41.969,99.163,41.969,103.42);bezierCurveTo(41.969,107.678,38.468,111.132,34.158,111.132);bezierCurveTo(29.848,111.132,26.355,107.678,26.355,103.42);bezierCurveTo(26.355,99.163,29.848,95.709,34.158,95.709);closePath();moveTo(37.686,73.914);bezierCurveTo(36.396,73.907,34.711,73.952,34.284,75.365);lineTo(34.264,82.86);bezierCurveTo(34.851,83.621,35.688,83.548,36.501,83.574);lineTo(63.03,83.429);lineTo(71.667,84.19);lineTo(71.667,76.671);bezierCurveTo(71.57,75.908,71.094,75.467,70.239,75.339);bezierCurveTo(61.126,73.734,49.245,73.929,37.69,73.911);closePath();moveTo(20.96,41.269);bezierCurveTo(20.95,41.873,21.535,42.151,21.535,42.151);lineTo(58.958,42.336);bezierCurveTo(54.064,41.351,42.365,39.477,26.123,39.553);bezierCurveTo(24.552,39.66,20.971,40.662,20.96,41.266);closePath();moveTo(24.313,55.545);bezierCurveTo(21.513,55.559,18.717,55.575,15.93,55.601);lineTo(15.93,55.601);lineTo(15.933,57.049);lineTo(14.759,57.062);lineTo(14.766,58.306);lineTo(15.95,58.364);bezierCurveTo(14.607,58.585,12.994,59.67,12.895,61.841);bezierCurveTo(12.905,65.11,12.908,68.38,12.908,71.649);lineTo(11.696,71.652);lineTo(11.677,69.291);lineTo(6.992,69.294);lineTo(6.992,75.196);lineTo(11.624,75.203);lineTo(11.594,73.447);lineTo(12.903,73.424);lineTo(12.913,82.605);bezierCurveTo(12.906,83.92,15.652,86.239,17.455,86.239);lineTo(22.267,83.244);bezierCurveTo(23.884,83.136,24.025,83.726,24.151,84.321);lineTo(59.819,84.64);lineTo(66.729,83.829);moveTo(71.723,83.243);lineTo(156.94,73.252);bezierCurveTo(161.88,72.764,171.856,50.996,153.189,47.695);bezierCurveTo(148.061,46.808,137.657,52.535,131.749,56.8);bezierCurveTo(129.436,58.755,130.007,60.884,131.775,62.783);lineTo(59.566,54.671);lineTo(57.443,54.67);bezierCurveTo(56.477,56.06,54.285,57.93,52.888,58.723);moveTo(49.36,58.361);bezierCurveTo(47.661,57.323,46.395,55.557,44.826,55.486);bezierCurveTo(40.922,55.487,36.922,55.493,32.944,55.499);moveTo(29.862,55.506);bezierCurveTo(28.819,55.509,27.78,55.513,26.746,55.516);lineCap=round;lineWidth=1;strokeStyle=#000;stroke();restore();' + ); + }); + + //======================================================= + it('complex path made of many different closed and open paths (Sopwith Camel) cached', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var path = new Konva.Path({ + data: 'm 15.749277,58.447629 8.495831,-0.05348 m 0.319898,-15.826548 -0.202438,17.295748 0.942206,0.941911 1.345933,-1.816987 0.20211,-11.642611 z m 77.458374,28.680768 c 0,5.308829 -4.303525,9.612686 -9.612485,9.612686 -5.30873,0 -9.612194,-4.303857 -9.612194,-9.612686 0,-5.308829 4.303464,-9.61226 9.612194,-9.61226 5.30896,0 9.612485,4.303431 9.612485,9.61226 z m -3.520874,0 c 0,3.364079 -2.72763,6.091348 -6.091611,6.091348 -3.364243,0 -6.091119,-2.727269 -6.091119,-6.091348 0,-3.363719 2.726876,-6.090791 6.091119,-6.090791 3.363981,0 6.091611,2.727072 6.091611,6.090791 z m -3.997576,0 c 0,1.156718 -0.937743,2.093937 -2.094035,2.093937 -1.156062,0 -2.093871,-0.937219 -2.093871,-2.093937 0,-1.156357 0.937809,-2.093773 2.093871,-2.093773 1.156292,0 2.094035,0.937416 2.094035,2.093773 z m 45.77821,4.283023 c -0.24607,1.90039 5.06492,3.680204 7.61403,5.520093 0.50662,0.514199 0.27889,0.975967 -0.0984,1.427532 l 3.9019,-1.141987 c -0.59258,-0.121397 -1.85951,0.01969 -1.71294,-0.380038 -0.85894,-1.950525 -3.68693,-2.761261 -5.61518,-4.092495 -1.06971,-1.03496 0.0997,-1.60766 0.76126,-2.284203 z M 43.206396,42.60133 55.578964,74.008743 58.71987,73.910313 47.203939,44.40726 c -1.109013,-0.737406 -1.174108,-2.1004 -3.997543,-1.808752 z m -18.654022,-0.570632 12.467721,31.692335 3.140643,0.09843 -12.467656,-31.692927 z m 2.285318,42.353106 -2.636648,-0.06431 0.163066,0.734584 3.709372,9.956142 2.357927,-1.168202 z m 19.411934,0.566268 -6.370726,9.901284 2.090163,1.615665 7.13671,-11.417403 0.303821,-0.4347 -2.942667,-0.02953 z m -12.091915,8.286013 c -5.729323,0 -10.367941,4.560169 -10.367941,10.184405 0,5.62429 4.638618,10.18489 10.367941,10.18489 5.729424,0 10.37654,-4.5606 10.37654,-10.18489 0,-5.624236 -4.647083,-10.184405 -10.37654,-10.184405 z m 0,2.473319 c 4.310029,0 7.811352,3.453552 7.811352,7.711086 0,4.25776 -3.50129,7.71167 -7.811352,7.71167 -4.310157,0 -7.803016,-3.45391 -7.803016,-7.71167 0,-4.257534 3.492859,-7.711086 7.803016,-7.711086 z m 3.528526,-21.795876 c -1.29032,-0.0066 -2.97525,0.03839 -3.402437,1.45155 l -0.01969,7.494437 c 0.586775,0.761915 1.42432,0.688978 2.236565,0.71411 l 26.529545,-0.14502 8.636784,0.761324 0,-7.518487 C 71.56989,75.908478 71.09444,75.467051 70.239377,75.338961 61.126027,73.734287 49.244756,73.929146 37.690371,73.911166 z M 20.959576,41.269176 c -0.0098,0.603377 0.575258,0.881409 0.575258,0.881409 L 58.95771,42.33629 c -4.893946,-0.985482 -16.592629,-2.859625 -32.835015,-2.783473 -1.570354,0.107617 -5.151439,1.109571 -5.163119,1.712718 z m 3.353022,14.276273 c -2.79955,0.01312 -5.595489,0.02953 -8.382964,0.05545 l 0,9.9e-5 0.0033,1.447677 -1.173484,0.01312 0.0066,1.244485 1.184048,0.05807 c -1.34298,0.220812 -2.956414,1.305807 -3.054779,3.476618 0.0098,3.269061 0.01312,6.538943 0.01312,9.808103 l -1.21197,0.0033 -0.01969,-2.361569 -4.6851755,0.0033 0,5.901969 4.6323185,0.0066 -0.02953,-1.7556 1.308596,-0.02297 0.0098,9.180447 c -0.0066,1.315781 2.739048,3.634336 4.542583,3.634336 l 4.811756,-2.995032 c 1.616583,-0.107617 1.758126,0.482078 1.884346,1.076924 l 35.667571,0.318914 6.909664,-0.81031 m 4.994738,-0.585889 85.216614,-9.991675 c 4.93952,-0.487623 14.9162,-22.255511 -3.75098,-25.556727 -5.12814,-0.887479 -15.53194,4.839613 -21.44018,9.104984 -2.31314,1.954593 -1.74166,4.084194 0.0263,5.982879 l -72.209399,-8.111923 -2.12281,-0.0012 c -0.966453,1.390128 -3.158262,3.260465 -4.554559,4.053123 M 49.36027,58.361483 c -1.699757,-1.038536 -2.965602,-2.804438 -4.533856,-2.875275 -3.903936,0.0011 -7.904399,0.0066 -11.882849,0.01312 m -3.081192,0.0066 c -1.043195,0.0033 -2.082715,0.0066 -3.116396,0.0098', + stroke: '#000', + strokeWidth: 1, + lineCap: 'round', + lineJoin: 'round', + draggable: true, + }); + + layer.add(path); + + stage.add(layer); + + path.cache(); + layer.draw(); + // layer.draw(); + cloneAndCompareLayer(layer, 230); + }); + + // ====================================================== + it('moveTo with implied lineTos and trailing comma', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var path = new Konva.Path({ + data: 'm200,100,100,0,0,50,-100,0z', + fill: '#fcc', + // stroke: '#333', + // strokeWidth: 2, + shadowColor: 'maroon', + shadowBlur: 2, + shadowOffset: { x: 10, y: 10 }, + shadowOpacity: 1, + draggable: true, + }); + + path.on('mouseover', function () { + this.fill('red'); + layer.draw(); + }); + + path.on('mouseout', function () { + this.fill('#ccc'); + layer.draw(); + }); + + layer.add(path); + + stage.add(layer); + + assert.equal(path.data(), 'm200,100,100,0,0,50,-100,0z'); + assert.equal(path.dataArray.length, 5); + + assert.equal(path.dataArray[1].command, 'L'); + + var canvas = createCanvas(); + var context = canvas.getContext('2d'); + // stroke + context.beginPath(); + context.moveTo(200, 100); + context.lineTo(300, 100); + context.lineTo(300, 150); + context.lineTo(200, 150); + context.closePath(); + context.fillStyle = '#fcc'; + context.shadowColor = 'maroon'; + context.shadowBlur = 2 * Konva.pixelRatio; + context.shadowOffsetX = 10 * Konva.pixelRatio; + context.shadowOffsetY = 10 * Konva.pixelRatio; + context.fill(); + // context.stroke(); + compareLayerAndCanvas(layer, canvas, 20); + }); + + // ====================================================== + it('add map path', function () { + var stage = addStage(); + var mapLayer = new Konva.Layer(); + + for (var key in worldMap.shapes) { + var c = worldMap.shapes[key]; + + var path = new Konva.Path({ + data: c, + fill: '#ccc', + stroke: '#999', + strokeWidth: 1, + }); + + if (key === 'US') { + assert.equal(path.dataArray[0].command, 'M'); + } + + path.on('mouseover', function () { + this.fill('red'); + mapLayer.drawScene(); + }); + + path.on('mouseout', function () { + this.fill('#ccc'); + mapLayer.drawScene(); + }); + + mapLayer.add(path); + } + + stage.add(mapLayer); + + //document.body.appendChild(mapLayer.bufferCanvas.element); + }); + + // ====================================================== + it('curved arrow path', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var c = + 'M12.582,9.551C3.251,16.237,0.921,29.021,7.08,38.564l-2.36,1.689l4.893,2.262l4.893,2.262l-0.568-5.36l-0.567-5.359l-2.365,1.694c-4.657-7.375-2.83-17.185,4.352-22.33c7.451-5.338,17.817-3.625,23.156,3.824c5.337,7.449,3.625,17.813-3.821,23.152l2.857,3.988c9.617-6.893,11.827-20.277,4.935-29.896C35.591,4.87,22.204,2.658,12.582,9.551z'; + + var path = new Konva.Path({ + data: c, + fill: '#ccc', + stroke: '#999', + strokeWidth: 1, + }); + + path.on('mouseover', function () { + this.fill('red'); + layer.draw(); + }); + + path.on('mouseout', function () { + this.fill('#ccc'); + layer.draw(); + }); + + layer.add(path); + stage.add(layer); + + var trace = layer.getContext().getTrace(); + + assert.equal( + trace, + 'clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);beginPath();moveTo(12.582,9.551);bezierCurveTo(3.251,16.237,0.921,29.021,7.08,38.564);lineTo(4.72,40.253);lineTo(9.613,42.515);lineTo(14.506,44.777);lineTo(13.938,39.417);lineTo(13.371,34.058);lineTo(11.006,35.752);bezierCurveTo(6.349,28.377,8.176,18.567,15.358,13.422);bezierCurveTo(22.809,8.084,33.175,9.797,38.514,17.246);bezierCurveTo(43.851,24.695,42.139,35.059,34.693,40.398);lineTo(37.55,44.386);bezierCurveTo(47.167,37.493,49.377,24.109,42.485,14.49);bezierCurveTo(35.591,4.87,22.204,2.658,12.582,9.551);closePath();fillStyle=#ccc;fill();lineWidth=1;strokeStyle=#999;stroke();restore();' + ); + }); + + // ====================================================== + it('Quadradic Curve test from SVG w3c spec', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var c = 'M200,300 Q400,50 600,300 T1000,300'; + + var path = new Konva.Path({ + data: c, + stroke: 'red', + strokeWidth: 5, + }); + + layer.add(path); + + layer.add( + new Konva.Circle({ + x: 200, + y: 300, + radius: 10, + fill: 'black', + }) + ); + + layer.add( + new Konva.Circle({ + x: 600, + y: 300, + radius: 10, + fill: 'black', + }) + ); + + layer.add( + new Konva.Circle({ + x: 1000, + y: 300, + radius: 10, + fill: 'black', + }) + ); + + layer.add( + new Konva.Circle({ + x: 400, + y: 50, + radius: 10, + fill: '#888', + }) + ); + + layer.add( + new Konva.Circle({ + x: 800, + y: 550, + radius: 10, + fill: '#888', + }) + ); + + layer.add( + new Konva.Path({ + data: 'M200,300 L400,50L600,300L800,550L1000,300', + stroke: '#888', + strokeWidth: 2, + }) + ); + + stage.add(layer); + + var trace = layer.getContext().getTrace(); + assert.equal( + trace, + 'clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);beginPath();moveTo(200,300);quadraticCurveTo(400,50,600,300);quadraticCurveTo(800,550,1000,300);lineWidth=5;strokeStyle=red;stroke();restore();save();transform(1,0,0,1,200,300);beginPath();arc(0,0,10,0,6.283,false);closePath();fillStyle=black;fill();restore();save();transform(1,0,0,1,600,300);beginPath();arc(0,0,10,0,6.283,false);closePath();fillStyle=black;fill();restore();save();transform(1,0,0,1,1000,300);beginPath();arc(0,0,10,0,6.283,false);closePath();fillStyle=black;fill();restore();save();transform(1,0,0,1,400,50);beginPath();arc(0,0,10,0,6.283,false);closePath();fillStyle=#888;fill();restore();save();transform(1,0,0,1,800,550);beginPath();arc(0,0,10,0,6.283,false);closePath();fillStyle=#888;fill();restore();save();transform(1,0,0,1,0,0);beginPath();moveTo(200,300);lineTo(400,50);lineTo(600,300);lineTo(800,550);lineTo(1000,300);lineWidth=2;strokeStyle=#888;stroke();restore();' + ); + }); + + // ====================================================== + it('Cubic Bezier Curve test from SVG w3c spec using data', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var c = 'M100,200 C100,100 250,100 250,200 S400,300 400,200'; + + var path = new Konva.Path({ + stroke: 'red', + strokeWidth: 5, + }); + + path.data(c); + + layer.add(path); + + layer.add( + new Konva.Circle({ + x: 100, + y: 200, + radius: 10, + stroke: '#888', + }) + ); + + layer.add( + new Konva.Circle({ + x: 250, + y: 200, + radius: 10, + stroke: '#888', + }) + ); + + layer.add( + new Konva.Circle({ + x: 400, + y: 200, + radius: 10, + stroke: '#888', + }) + ); + + layer.add( + new Konva.Circle({ + x: 100, + y: 100, + radius: 10, + fill: '#888', + }) + ); + + layer.add( + new Konva.Circle({ + x: 250, + y: 100, + radius: 10, + fill: '#888', + }) + ); + + layer.add( + new Konva.Circle({ + x: 400, + y: 300, + radius: 10, + fill: '#888', + }) + ); + + layer.add( + new Konva.Circle({ + x: 250, + y: 300, + radius: 10, + stroke: 'blue', + }) + ); + + stage.add(layer); + + var trace = layer.getContext().getTrace(); + assert.equal( + trace, + 'clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);beginPath();moveTo(100,200);bezierCurveTo(100,100,250,100,250,200);bezierCurveTo(250,300,400,300,400,200);lineWidth=5;strokeStyle=red;stroke();restore();save();transform(1,0,0,1,100,200);beginPath();arc(0,0,10,0,6.283,false);closePath();lineWidth=2;strokeStyle=#888;stroke();restore();save();transform(1,0,0,1,250,200);beginPath();arc(0,0,10,0,6.283,false);closePath();lineWidth=2;strokeStyle=#888;stroke();restore();save();transform(1,0,0,1,400,200);beginPath();arc(0,0,10,0,6.283,false);closePath();lineWidth=2;strokeStyle=#888;stroke();restore();save();transform(1,0,0,1,100,100);beginPath();arc(0,0,10,0,6.283,false);closePath();fillStyle=#888;fill();restore();save();transform(1,0,0,1,250,100);beginPath();arc(0,0,10,0,6.283,false);closePath();fillStyle=#888;fill();restore();save();transform(1,0,0,1,400,300);beginPath();arc(0,0,10,0,6.283,false);closePath();fillStyle=#888;fill();restore();save();transform(1,0,0,1,250,300);beginPath();arc(0,0,10,0,6.283,false);closePath();lineWidth=2;strokeStyle=blue;stroke();restore();' + ); + }); + + // ====================================================== + it('path arc', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var c = + 'M100,350 l 50,-25 a25,25 -30 0,1 50,-25 l 50,-25 a25,50 -30 0,1 50,-25 l 50,-25 a25,75 -30 0,1 50,-25 l 50,-25 a25,100 -30 0,1 50,-25 l 50,-25'; + + var path = new Konva.Path({ + data: c, + fill: 'none', + stroke: '#999', + strokeWidth: 1, + }); + + path.on('mouseover', function () { + this.fill('red'); + layer.draw(); + }); + + path.on('mouseout', function () { + this.fill('none'); + layer.draw(); + }); + + layer.add(path); + stage.add(layer); + + var trace = layer.getContext().getTrace(); + assert.equal( + trace, + 'clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);beginPath();moveTo(100,350);lineTo(150,325);translate(175,312.5);rotate(-0.524);scale(1,1);arc(0,0,27.951,-3.082,0.06,0);scale(1,1);rotate(0.524);translate(-175,-312.5);lineTo(250,275);translate(275,262.5);rotate(-0.524);scale(0.5,1);arc(0,0,55.826,-3.112,0.03,0);scale(2,1);rotate(0.524);translate(-275,-262.5);lineTo(350,225);translate(375,212.5);rotate(-0.524);scale(0.333,1);arc(0,0,83.719,-3.122,0.02,0);scale(3,1);rotate(0.524);translate(-375,-212.5);lineTo(450,175);translate(475,162.5);rotate(-0.524);scale(0.25,1);arc(0,0,111.615,-3.127,0.015,0);scale(4,1);rotate(0.524);translate(-475,-162.5);lineTo(550,125);fillStyle=none;fill();lineWidth=1;strokeStyle=#999;stroke();restore();' + ); + }); + + // ====================================================== + it('Tiger (RAWR!)', function () { + this.timeout(5000); + var stage = addStage(); + var layer = new Konva.Layer(); + var group = new Konva.Group(); + + for (var i = 0; i < tiger.length; i++) { + var path = new Konva.Path(tiger[i]); + group.add(path); + } + + group.setDraggable(true); + layer.add(group); + stage.add(layer); + + var trace = layer.getContext().getTrace(); + assert.equal( + trace, + 'moveTo(-44.4,313.001);bezierCurveTo(-44.4,313.001,-32.8,290.601,-54.6,316.401);bezierCurveTo(-54.6,316.401,-43.6,306.601,-44.4,313.001);closePath();fillStyle=#cccccc;fill();restore();save();transform(1,0,0,1,0,0);beginPath();moveTo(-59.8,298.401);bezierCurveTo(-59.8,298.401,-55,279.601,-52.4,279.801);bezierCurveTo(-52.4,279.801,-44.2,270.801,-50.8,281.401);bezierCurveTo(-50.8,281.401,-56.8,291.001,-56.2,300.801);bezierCurveTo(-56.2,300.801,-56.8,291.201,-59.8,298.401);closePath();fillStyle=#cccccc;fill();restore();save();transform(1,0,0,1,0,0);beginPath();moveTo(270.5,287);bezierCurveTo(270.5,287,258.5,277,256,273.5);bezierCurveTo(256,273.5,269.5,292,269.5,299);bezierCurveTo(269.5,299,272,291.5,270.5,287);closePath();fillStyle=#cccccc;fill();restore();save();transform(1,0,0,1,0,0);beginPath();moveTo(276,265);bezierCurveTo(276,265,255,250,251.5,242.5);bezierCurveTo(251.5,242.5,278,272,278,276.5);bezierCurveTo(278,276.5,278.5,267.5,276,265);closePath();fillStyle=#cccccc;fill();restore();save();transform(1,0,0,1,0,0);beginPath();moveTo(293,111);bezierCurveTo(293,111,281,103,279.5,105);bezierCurveTo(279.5,105,290,111.5,292.5,120);bezierCurveTo(292.5,120,291,111,293,111);closePath();fillStyle=#cccccc;fill();restore();save();transform(1,0,0,1,0,0);beginPath();moveTo(301.5,191.5);lineTo(284,179.5);bezierCurveTo(284,179.5,303,196.5,303.5,200.5);lineTo(301.5,191.5);closePath();fillStyle=#cccccc;fill();restore();save();transform(1,0,0,1,0,0);beginPath();moveTo(-89.25,169);lineTo(-67.25,173.75);lineWidth=2;strokeStyle=#000000;stroke();restore();save();transform(1,0,0,1,0,0);beginPath();moveTo(-39,331);bezierCurveTo(-39,331,-39.5,327.5,-48.5,338);lineWidth=2;strokeStyle=#000000;stroke();restore();save();transform(1,0,0,1,0,0);beginPath();moveTo(-33.5,336);bezierCurveTo(-33.5,336,-31.5,329.5,-38,334);lineWidth=2;strokeStyle=#000000;stroke();restore();save();transform(1,0,0,1,0,0);beginPath();moveTo(20.5,344.5);bezierCurveTo(20.5,344.5,22,333.5,10.5,346.5);lineWidth=2;strokeStyle=#000000;stroke();restore();' + ); + }); + + // ====================================================== + it('Tiger (RAWR!) cached', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var group = new Konva.Group(); + + for (var i = 0; i < tiger.length; i++) { + var path = new Konva.Path(tiger[i]); + group.add(path); + } + + group.setDraggable(true); + layer.add(group); + stage.add(layer); + group.cache(); + layer.draw(); + + cloneAndCompareLayer(layer, 200); + }); + + // ====================================================== + it('Able to determine point on line some distance from another point on line', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var c = 'M10,10 210,160'; + // i.e., from a 3-4-5 triangle + + var path = new Konva.Path({ + stroke: 'red', + strokeWidth: 3, + }); + + path.data(c); + layer.add(path); + + layer.add( + new Konva.Circle({ + x: 10, + y: 10, + radius: 10, + fill: 'black', + }) + ); + + var p1 = Konva.Path.getPointOnLine(125, 10, 10, 210, 160); + // should be 1/2 way, or (110,85) + assert.equal(Math.round(p1.x), 110); + assert.equal(Math.round(p1.y), 85); + + layer.add( + new Konva.Circle({ + x: p1.x, + y: p1.y, + radius: 10, + fill: 'blue', + }) + ); + + stage.add(layer); + }); + + // ====================================================== + it('Able to determine points on Cubic Bezier Curve', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var c = 'M100,200 C100,100 250,100 250,200 S400,300 400,200'; + + var path = new Konva.Path({ + stroke: 'red', + strokeWidth: 3, + }); + + path.data(c); + + layer.add(path); + c = 'M 100 200'; + + for (let t = 0.25; t <= 1; t += 0.25) { + var p1 = Konva.Path.getPointOnCubicBezier( + t, + 100, + 200, + 100, + 100, + 250, + 100, + 250, + 200 + ); + c += ' ' + p1.x.toString() + ' ' + p1.y.toString(); + } + + for (let t = 0.25; t <= 1; t += 0.25) { + var p1 = Konva.Path.getPointOnCubicBezier( + t, + 250, + 200, + 250, + 300, + 400, + 300, + 400, + 200 + ); + c += ' ' + p1.x.toString() + ' ' + p1.y.toString(); + } + + var testPath = new Konva.Path({ + stroke: 'black', + strokewidth: 2, + data: c, + }); + + layer.add(testPath); + stage.add(layer); + + assert.equal( + c, + 'M 100 200 123.4375 143.75 175 125 226.5625 143.75 250 200 273.4375 256.25 325 275 376.5625 256.25 400 200' + ); + }); + + // ====================================================== + it('Able to determine points on Quadratic Curve', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var c = 'M200,300 Q400,50 600,300 T1000,300'; + + var path = new Konva.Path({ + stroke: 'red', + strokeWidth: 3, + }); + + path.data(c); + + layer.add(path); + c = 'M 200 300'; + + for (let t = 0.333; t <= 1; t += 0.333) { + var p1 = Konva.Path.getPointOnQuadraticBezier( + t, + 200, + 300, + 400, + 50, + 600, + 300 + ); + c += ' ' + p1.x.toString() + ' ' + p1.y.toString(); + } + + for (let t = 0.333; t <= 1; t += 0.333) { + var p1 = Konva.Path.getPointOnQuadraticBezier( + t, + 600, + 300, + 800, + 550, + 1000, + 300 + ); + c += ' ' + p1.x.toString() + ' ' + p1.y.toString(); + } + + var testPath = new Konva.Path({ + stroke: 'black', + strokewidth: 2, + data: c, + }); + + layer.add(testPath); + stage.add(layer); + + assert.equal( + c, + 'M 200 300 333.20000000000005 188.9445 466.40000000000003 188.77800000000002 599.6 299.50050000000005 733.2 411.05550000000005 866.4 411.222 999.6 300.49949999999995' + ); + }); + + // ====================================================== + it('Able to determine points on Elliptical Arc with clockwise stroke', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var c = 'M 50,100 A 100 50 0 1 1 150 150'; + + var path = new Konva.Path({ + stroke: 'red', + strokeWidth: 3, + }); + + path.data(c); + + layer.add(path); + + var centerParamPoints = Konva.Path.convertEndpointToCenterParameterization( + 50, + 100, + 150, + 150, + 1, + 1, + 100, + 50, + 0 + ); + + var start = centerParamPoints[4]; + // 4 = theta + var dTheta = centerParamPoints[5]; + // 5 = dTheta + var end = centerParamPoints[4] + dTheta; + var inc = Math.PI / 6.0; + // 30 degree resolution + + var p1 = Konva.Path.getPointOnEllipticalArc( + centerParamPoints[0], + centerParamPoints[1], + centerParamPoints[2], + centerParamPoints[3], + start, + 0 + ); + c = 'M ' + p1.x.toFixed(2) + ' ' + p1.y.toFixed(2); + + if ( + dTheta < 0 // clockwise + ) { + for (let t = start - inc; t > end; t -= inc) { + p1 = Konva.Path.getPointOnEllipticalArc( + centerParamPoints[0], + centerParamPoints[1], + centerParamPoints[2], + centerParamPoints[3], + t, + 0 + ); + c += ' ' + p1.x.toFixed(2) + ' ' + p1.y.toFixed(2); + } + } else { + // counter-clockwise + for (let t = start + inc; t < end; t += inc) { + p1 = Konva.Path.getPointOnEllipticalArc( + centerParamPoints[0], + centerParamPoints[1], + centerParamPoints[2], + centerParamPoints[3], + t, + 0 + ); + c += ' ' + p1.x.toFixed(2) + ' ' + p1.y.toFixed(2); + } + } + p1 = Konva.Path.getPointOnEllipticalArc( + centerParamPoints[0], + centerParamPoints[1], + centerParamPoints[2], + centerParamPoints[3], + end, + 0 + ); + c += ' ' + p1.x.toFixed(2) + ' ' + p1.y.toFixed(2); + + var testpath = new Konva.Path({ + stroke: 'black', + strokeWidth: 2, + data: c, + }); + + layer.add(testpath); + stage.add(layer); + + assert.equal( + c, + 'M 50.00 100.00 63.40 75.00 100.00 56.70 150.00 50.00 200.00 56.70 236.60 75.00 250.00 100.00 236.60 125.00 200.00 143.30 150.00 150.00' + ); + }); + + // ====================================================== + it('Able to determine points on Elliptical Arc with counter-clockwise stroke', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var c = 'M 250,100 A 100 50 0 1 0 150 150'; + + var path = new Konva.Path({ + stroke: 'red', + strokeWidth: 3, + }); + + path.data(c); + + layer.add(path); + + var centerParamPoints = Konva.Path.convertEndpointToCenterParameterization( + 250, + 100, + 150, + 150, + 1, + 0, + 100, + 50, + 0 + ); + + var start = centerParamPoints[4]; + // 4 = theta + var dTheta = centerParamPoints[5]; + // 5 = dTheta + var end = centerParamPoints[4] + dTheta; + var inc = Math.PI / 6.0; + // 30 degree resolution + + var p1 = Konva.Path.getPointOnEllipticalArc( + centerParamPoints[0], + centerParamPoints[1], + centerParamPoints[2], + centerParamPoints[3], + start, + 0 + ); + c = 'M ' + p1.x.toString() + ' ' + p1.y.toString(); + + if ( + dTheta < 0 // clockwise + ) { + for (let t = start - inc; t > end; t -= inc) { + p1 = Konva.Path.getPointOnEllipticalArc( + centerParamPoints[0], + centerParamPoints[1], + centerParamPoints[2], + centerParamPoints[3], + t, + 0 + ); + c += ' ' + p1.x.toString() + ' ' + p1.y.toString(); + } + } else { + // counter-clockwise + for (let t = start + inc; t < end; t += inc) { + p1 = Konva.Path.getPointOnEllipticalArc( + centerParamPoints[0], + centerParamPoints[1], + centerParamPoints[2], + centerParamPoints[3], + t, + 0 + ); + c += ' ' + p1.x.toString() + ' ' + p1.y.toString(); + } + } + p1 = Konva.Path.getPointOnEllipticalArc( + centerParamPoints[0], + centerParamPoints[1], + centerParamPoints[2], + centerParamPoints[3], + end, + 0 + ); + c += ' ' + p1.x.toString() + ' ' + p1.y.toString(); + + var testpath = new Konva.Path({ + stroke: 'black', + strokeWidth: 2, + data: c, + }); + + layer.add(testpath); + stage.add(layer); + + assert.equal( + c, + 'M 250 100 236.60254037844388 75 200 56.69872981077807 150 50 100.00000000000003 56.69872981077806 63.39745962155615 74.99999999999999 50 99.99999999999997 63.397459621556095 124.99999999999997 99.99999999999996 143.30127018922192 149.99999999999997 150' + ); + }); + + // ====================================================== + it('Able to determine points on Elliptical Arc when rotated', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var c = 'M 250,100 A 100 50 30 1 0 150 150'; + + var path = new Konva.Path({ + stroke: 'red', + strokeWidth: 3, + }); + + path.data(c); + + layer.add(path); + + var centerParamPoints = Konva.Path.convertEndpointToCenterParameterization( + 250, + 100, + 150, + 150, + 1, + 0, + 100, + 50, + 30 + ); + + var start = centerParamPoints[4]; + // 4 = theta + var dTheta = centerParamPoints[5]; + // 5 = dTheta + var end = centerParamPoints[4] + dTheta; + var inc = Math.PI / 6.0; + // 30 degree resolution + var psi = centerParamPoints[6]; + // 6 = psi radians + + var p1 = Konva.Path.getPointOnEllipticalArc( + centerParamPoints[0], + centerParamPoints[1], + centerParamPoints[2], + centerParamPoints[3], + start, + psi + ); + c = 'M ' + p1.x.toFixed(2) + ' ' + p1.y.toFixed(2); + + if ( + dTheta < 0 // clockwise + ) { + for (let t = start - inc; t > end; t -= inc) { + p1 = Konva.Path.getPointOnEllipticalArc( + centerParamPoints[0], + centerParamPoints[1], + centerParamPoints[2], + centerParamPoints[3], + t, + psi + ); + c += ' ' + p1.x.toFixed(2) + ' ' + p1.y.toFixed(2); + } + } else { + // counter-clockwise + for (let t = start + inc; t < end; t += inc) { + p1 = Konva.Path.getPointOnEllipticalArc( + centerParamPoints[0], + centerParamPoints[1], + centerParamPoints[2], + centerParamPoints[3], + t, + psi + ); + c += ' ' + p1.x.toFixed(2) + ' ' + p1.y.toFixed(2); + } + } + p1 = Konva.Path.getPointOnEllipticalArc( + centerParamPoints[0], + centerParamPoints[1], + centerParamPoints[2], + centerParamPoints[3], + end, + psi + ); + c += ' ' + p1.x.toFixed(2) + ' ' + p1.y.toFixed(2); + + var testpath = new Konva.Path({ + stroke: 'black', + strokeWidth: 2, + data: c, + }); + + layer.add(testpath); + stage.add(layer); + + assert.equal( + c, + 'M 250.00 100.00 209.63 69.47 162.97 50.77 122.52 48.92 99.13 64.41 99.05 93.09 122.32 127.28 150.00 150.00' + ); + }); + + // ====================================================== + it('getPointOnLine for different directions', function () { + var origo = { + x: 0, + y: 0, + }; + + var p, point; + + //point up left + p = { + x: -10, + y: -10, + }; + point = Konva.Path.getPointOnLine(10, origo.x, origo.y, p.x, p.y); + assert(point.x < 0 && point.y < 0, 'The new point should be up left'); + + //point up right + p = { + x: 10, + y: -10, + }; + point = Konva.Path.getPointOnLine(10, origo.x, origo.y, p.x, p.y); + assert(point.x > 0 && point.y < 0, 'The new point should be up right'); + + //point down right + p = { + x: 10, + y: 10, + }; + point = Konva.Path.getPointOnLine(10, origo.x, origo.y, p.x, p.y); + assert(point.x > 0 && point.y > 0, 'The new point should be down right'); + + //point down left + p = { + x: -10, + y: 10, + }; + point = Konva.Path.getPointOnLine(10, origo.x, origo.y, p.x, p.y); + assert(point.x < 0 && point.y > 0, 'The new point should be down left'); + + //point left + p = { + x: -10, + y: 0, + }; + point = Konva.Path.getPointOnLine(10, origo.x, origo.y, p.x, p.y); + assert(point.x == -10 && point.y == 0, 'The new point should be left'); + + //point up + p = { + x: 0, + y: -10, + }; + point = Konva.Path.getPointOnLine(10, origo.x, origo.y, p.x, p.y); + assert( + Math.abs(point.x) < 0.0000001 && point.y == -10, + 'The new point should be up' + ); + + //point right + p = { + x: 10, + y: 0, + }; + point = Konva.Path.getPointOnLine(10, origo.x, origo.y, p.x, p.y); + assert(point.x == 10 && point.y == 0, 'The new point should be right'); + + //point down + p = { + x: 0, + y: 10, + }; + point = Konva.Path.getPointOnLine(10, origo.x, origo.y, p.x, p.y); + assert( + Math.abs(point.x) < 0.0000001 && point.y == 10, + 'The new point should be down' + ); + }); + + // ====================================================== + it('get path length', function () { + var path = new Konva.Path({ data: 'M 10,10 L 20,10 L 20,20' }); + assert.equal(path.getLength(), 20); + }); + + // ====================================================== + + it('get point at path', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + const data = + 'M 300,10 L 250,100 A 100 40 30 1 0 150 150 C 160,100, 290,100, 300,150'; + var path = new Konva.Path({ + stroke: 'red', + strokeWidth: 3, + data, + }); + layer.add(path); + if (isBrowser) { + const SVGPath = document.createElementNS( + 'http://www.w3.org/2000/svg', + 'path' + ) as SVGPathElement; + SVGPath.setAttribute('d', data); + for (var i = 0; i < path.getLength(); i += 1) { + var p = path.getPointAtLength(i); + var circle = new Konva.Circle({ + x: p.x, + y: p.y, + radius: 2, + fill: 'black', + stroke: 'black', + }); + layer.add(circle); + const position = SVGPath.getPointAtLength(i); + assert( + Math.abs(p.x / position.x) >= 0.8, + 'error should be smaller than 10%' + ); + assert( + Math.abs(p.y / position.y) >= 0.8, + 'error should be smaller than 10%' + ); + } + } else { + var points = []; + for (var i = 0; i < path.getLength(); i += 20) { + var p = path.getPointAtLength(i); + points.push(p); + var circle = new Konva.Circle({ + x: p.x, + y: p.y, + radius: 2, + fill: 'black', + stroke: 'black', + }); + layer.add(circle); + } + + assert.deepEqual(points, [ + { x: 300, y: 10 }, + { x: 290.28714137642737, y: 27.483145522430753 }, + { x: 280.57428275285474, y: 44.96629104486151 }, + { x: 270.86142412928206, y: 62.44943656729226 }, + { x: 261.1485655057094, y: 79.93258208972301 }, + { x: 251.4357068821368, y: 97.41572761215377 }, + { x: 230.89220826660141, y: 87.23996356219386 }, + { x: 207.0639321224534, y: 74.08466390481559 }, + { x: 182.87529785963875, y: 63.52674972743341 }, + { x: 159.56025996483157, y: 56.104820499018956 }, + { x: 138.30820744216845, y: 52.197497135977514 }, + { x: 120.20328854394192, y: 52.00410710518156 }, + { x: 106.16910423342256, y: 55.53451596967142 }, + { x: 96.92159177720502, y: 62.60862410865827 }, + { x: 92.93250205472883, y: 72.86555428606191 }, + { x: 94.40533374670959, y: 85.78206137467119 }, + { x: 101.26495209131289, y: 100.69922508568548 }, + { x: 113.1614217949117, y: 116.85606400569954 }, + { x: 129.4878585660311, y: 133.42835616090537 }, + { x: 149.41138859764925, y: 149.5706857234721 }, + { x: 159.43138712714935, y: 133.06025615594774 }, + { x: 175.3017710206886, y: 122.31378864213205 }, + { x: 194.92856277944335, y: 115.73314636675508 }, + { x: 214.84499816899648, y: 112.85265466076682 }, + { x: 234.86585690487928, y: 112.83275701234302 }, + { x: 254.65745479392615, y: 115.6401774356189 }, + { x: 273.58108654098885, y: 121.79846344304384 }, + { x: 289.93157588171135, y: 132.43782950384232 }, + { x: 299.87435436448743, y: 149.4028482225714 }, + ]); + } + + stage.add(layer); + }); + + it('get point at path with float attrs', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + const data = + 'M419.0000314094981 342.88624187900973 L419.00003140949804 577.0038889378335 L465.014001082264 577.0038889378336 Z'; + var path = new Konva.Path({ + stroke: 'red', + strokeWidth: 3, + data, + }); + layer.add(path); + if (isBrowser) { + const SVGPath = document.createElementNS( + 'http://www.w3.org/2000/svg', + 'path' + ) as SVGPathElement; + SVGPath.setAttribute('d', data); + for (var i = 0; i < path.getLength(); i += 1) { + var p = path.getPointAtLength(i); + var circle = new Konva.Circle({ + x: p.x, + y: p.y, + radius: 2, + fill: 'black', + stroke: 'black', + }); + layer.add(circle); + const position = SVGPath.getPointAtLength(i); + assert( + Math.abs(p.x - position.x) <= 1, + 'error for x should be smaller than 10% for i = ' + i + ); + assert( + Math.abs(p.y - position.y) <= 1, + 'error for y should be smaller than 10% for i = ' + i + ); + } + } + }); + + it('get point at path - bezier', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + const data = + 'M100,250 q150,-150 300,0 M 117.12814070351759 108.66938206658291 C 79.18719346733668 277.73956799623113 75.85761180904522 379.96743797110554 82.84673366834171 395.7761659861809 S 148.83130025125627 280.47708118718595 177.12060301507537 244.36661824748745 S 326.1725898241206 61.02036887562815 325.67336683417085 85.815110709799 S 174.998726758794 435.7304316896985 172.8354271356784 457.1970202575377 S 273.65633103015074 310.01551271984926 307.1042713567839 270.07767352386935 S 466.09929459798997 92.08432302135678 459.9422110552764 114.3829499057789 S 266.23512060301505 435.5226006595478 254.2537688442211 461.4821961369347 S 328.1430565326633 368.1639210113065 357.09798994974875 337.2120956344221 S 486.31961118090453 207.61623570979899 502.79396984924625 195.8012916143216 S 511.48859170854274 200.85065719221106 498.50879396984925 235.79626648869348 S 379.73086055276383 489.4401119660804 391.37939698492465 495.76360317211055 S 573.2022663316583 313.03941849874377 598.4962311557789 290.0751609610553 S 608.3285672110553 288.6610529208543 608.4949748743719 298.64551271984925 S 604.9168530150754 352.64801334799 599.9246231155779 375.778678548995 S 540.6820665829146 508.5077162374372 565.643216080402 497.19199513190955 S 690.3761155778894 408.77881799623117 814.1834170854271 278.6480252826633'; + var path = new Konva.Path({ + stroke: 'red', + strokeWidth: 3, + data, + }); + layer.add(path); + if (isBrowser) { + const SVGPath = document.createElementNS( + 'http://www.w3.org/2000/svg', + 'path' + ) as SVGPathElement; + SVGPath.setAttribute('d', data); + for (var i = 0; i < path.getLength(); i += 10) { + var p = path.getPointAtLength(i); + var circle = new Konva.Circle({ + x: p.x, + y: p.y, + radius: 2, + fill: 'black', + stroke: 'black', + }); + layer.add(circle); + const position = SVGPath.getPointAtLength(i); + assert( + Math.abs(p.x / position.x) >= 0.8, + 'error should be smaller than 10%' + ); + assert( + Math.abs(p.y / position.y) >= 0.8, + 'error should be smaller than 10%' + ); + } + } else { + var points = []; + for (var i = 0; i < path.getLength(); i += 500) { + var p = path.getPointAtLength(i); + points.push(p); + var circle = new Konva.Circle({ + x: p.x, + y: p.y, + radius: 2, + fill: 'black', + stroke: 'black', + }); + layer.add(circle); + } + + assert.deepEqual(points, [ + { x: 100, y: 250 }, + { x: 88.80979830887104, y: 261.9310198815103 }, + { x: 296.17215373535686, y: 105.30891997028526 }, + { x: 207.5911710830848, y: 414.96086124898176 }, + { x: 410.01622229664224, y: 202.72024124427364 }, + { x: 374.86125434742394, y: 318.78396882819396 }, + { x: 392.21257855027216, y: 483.8201732191269 }, + { x: 572.3287288437606, y: 447.38305323763467 }, + ]); + } + stage.add(layer); + }); + + // ====================================================== + it('Borneo Map (has scientific notation: -10e-4)', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var borneo = new Konva.Path({ + data: 'm 136.68513,236.08861 c -0.63689,-0.67792 -0.75417,-1.28099 -1.03556,-5.32352 -0.26489,-3.80589 -0.4465,-4.81397 -1.09951,-6.1026 -0.51169,-1.00981 -0.98721,-1.54361 -1.375,-1.54361 -0.8911,0 -3.48931,-1.22828 -3.80975,-1.80103 -0.16294,-0.29089 -0.87295,-0.56825 -1.68693,-0.65886 -1.13423,-0.12629 -1.91094,0.0661 -4.02248,0.99633 -4.0367,1.77835 -5.46464,1.87106 -6.79674,0.44127 -0.51948,-0.55765 -0.64763,-1.12674 -0.64763,-2.87683 l 0,-2.18167 -0.87832,0.20996 c -0.48312,0.11549 -1.12041,0.33383 -1.41635,0.4852 -1.52799,0.78172 -4.61534,-0.0398 -5.55846,-1.47906 -0.30603,-0.46718 -1.06518,-1.19501 -1.68667,-1.61739 -1.27136,-0.86387 -1.62607,-0.6501 -1.63439,0.98494 -0.007,1.00822 -0.76687,2.38672 -1.31885,2.38672 -0.17579,0 -1.27182,0.66553 -2.4356,1.47895 -4.016775,2.8076 -6.006455,3.29182 -7.693525,1.87231 -0.52348,-0.44054 -1.43004,-1.00203 -2.01445,-1.24775 -1.35902,-0.57143 -2.10139,-0.21496 -5.36296,2.57523 -2.00259,1.71315 -2.55857,2.02869 -3.57441,2.02869 -0.66172,0 -1.31931,-0.17966 -1.46135,-0.39925 -0.27734,-0.42865 -0.75823,-5.15099 -0.87007,-8.54399 -0.0708,-2.14922 -0.41754,-3.83281 -0.78935,-3.83281 -0.1176,0 -0.45993,0.28746 -0.76078,0.63881 -0.66657,0.77849 -3.4572,0.87321 -4.70537,0.15969 -1.29782,-0.7419 -2.38029,-0.55672 -5.01545,0.85797 -2.16783,1.16385 -2.75945,1.33971 -4.5666,1.35746 -1.66762,0.0163 -2.276,-0.12217 -3.09174,-0.70405 -0.61985,-0.44211 -1.09397,-0.5977 -1.21663,-0.39925 -0.32993,0.53385 -2.25686,0.37294 -2.80642,-0.23436 -0.27856,-0.30774 -0.65658,-0.95453 -0.8401,-1.43731 -0.42448,-1.11632 -0.91809,-1.10316 -3.01531,0.0804 -0.93379,0.52702 -2.13107,0.9582 -2.66054,0.9582 -1.46554,0 -1.97734,-0.82307 -2.19476,-3.52955 -0.10515,-1.30865 -0.4137,-2.90864 -0.68575,-3.55553 -0.37975,-0.90312 -0.41736,-1.39768 -0.16196,-2.13038 0.35544,-1.01957 -0.24711,-3.50377 -1.40121,-5.77657 -0.48023,-0.94578 -0.50724,-1.33822 -0.19445,-2.82926 0.40575,-1.93441 -0.0409,-3.36568 -1.16059,-3.72114 -0.3255,-0.10331 -0.93466,-0.55279 -1.35374,-0.99885 -1.12569,-1.19829 -1.03821,-2.92553 0.22088,-4.35957 0.85079,-0.96896 1.01308,-1.45348 1.2082,-3.60666 l 0.22545,-2.48734 -1.16949,-1.19763 c -0.64324,-0.65869 -1.26203,-1.64897 -1.37517,-2.20061 -0.13388,-0.6528 -0.56813,-1.23242 -1.24372,-1.66009 l -1.03807,-0.65709 0,1.0782 c 0,0.59301 -0.21786,1.38922 -0.48413,1.76937 -0.68007,0.97099 -4.56312,2.96438 -5.77445,2.96438 -1.55729,0 -1.88611,-0.67097 -1.88611,-3.84837 0,-3.52819 0.41663,-4.13666 2.83284,-4.13666 1.49279,0 1.57631,-0.0396 1.09598,-0.51996 -0.4316,-0.43155 -0.69566,-0.4587 -1.55343,-0.15971 -0.56839,0.19815 -1.3354,0.35443 -1.70442,0.34729 -0.86278,-0.0167 -2.61563,-1.51607 -3.02205,-2.58498 -0.3513,-0.92403 -0.12267,-3.38466 0.34119,-3.67132 0.16474,-0.1018 -0.39367,-0.50661 -1.24085,-0.89959 -2.032471,-0.94281 -2.321421,-1.35146 -2.487701,-3.51839 -0.0772,-1.00533 -0.30119,-2.31552 -0.4979,-2.91152 -0.48076,-1.45668 -0.16499,-2.30832 0.90163,-2.43139 0.843711,-0.0974 0.860511,-0.14171 0.748911,-1.97594 -0.0696,-1.14269 0.0236,-1.96143 0.23793,-2.09396 0.47223,-0.29188 -2.501621,-3.97433 -3.330171,-4.12358 -0.34456,-0.062 -0.75956,-0.23921 -0.92229,-0.39365 -0.3459,-0.32835 -0.78945,-2.83658 -0.98794,-5.58637 -0.0769,-1.06517 -0.35848,-2.55647 -0.62576,-3.31402 -0.71739,-2.03331 -0.61465,-2.55112 0.76687,-3.86532 l 1.25273,-1.19173 -0.46915,-1.36178 c -0.53343,-1.54826 -0.33638,-2.99085 0.48923,-3.5815 0.65547,-0.46898 1.32731,-2.61652 1.52388,-4.87126 0.13191,-1.51252 0.2658,-1.7153 2.531131,-3.83281 2.21127,-2.06705 2.41106,-2.36144 2.64687,-3.89989 0.31881,-2.07979 0.74608,-2.60075 2.34208,-2.85597 0.69615,-0.11132 1.66359,-0.53718 2.14988,-0.94636 1.89204,-1.59201 4.16695,-1.77416 4.16695,-0.33363 0,0.40454 -0.23171,1.4157 -0.51499,2.24703 -0.28322,0.83134 -0.45486,1.57164 -0.38139,1.64512 0.0735,0.0735 1.32057,0.92807 2.77127,1.89909 2.57827,1.72574 2.68847,1.7655 4.89522,1.7655 1.74495,0 2.50706,-0.15424 3.35669,-0.67937 0.91121,-0.56315 1.2344,-0.61779 1.88934,-0.3194 0.43449,0.19798 1.19684,0.35997 1.69411,0.35997 1.03354,0 1.51204,0.45563 1.67033,1.59058 0.10938,0.78459 0.54215,1.02641 2.56344,1.43244 0.47079,0.0946 1.07249,0.38843 1.33713,0.65302 0.29826,0.29829 0.55659,0.35879 0.67998,0.15922 0.3007,-0.48659 2.51019,-0.38548 3.21433,0.1471 0.90129,0.6817 0.99638,0.6211 1.2201,-0.77786 0.1114,-0.69691 0.4878,-1.53284 0.83642,-1.85761 0.34861,-0.32477 0.76943,-1.29968 0.93532,-2.16645 0.36198,-1.89196 1.67658,-4.95214 2.37708,-5.53353 0.45941,-0.38127 0.45882,-0.50661 -0.007,-1.40586 -0.92929,-1.79695 -1.07762,-2.78281 -0.59325,-3.94207 0.32267,-0.77223 0.71393,-1.13742 1.3562,-1.26589 l 0.90282,-0.18055 -0.12723,-3.168 -0.1273,-3.168021 1.13626,0 c 0.6249,0 1.22425,0.14254 1.33189,0.31676 0.11034,0.17851 0.92013,-0.22348 1.85538,-0.92103 2.57554,-1.920815 3.6054,-2.317745 6.74013,-2.597735 2.80648,-0.25066 4.59942,-0.61535 8.65387,-1.76019 1.05398,-0.29761 2.49129,-0.66582 3.19396,-0.81822 2.5583,-0.55486 5.16562,-1.18239 7.665675,-1.84504 2.13376,-0.56557 2.7297,-0.87493 3.61346,-1.87587 1.968,-2.22882 6.60136,-8.28119 7.54474,-9.85529 0.55323,-0.92329 1.87182,-2.29956 3.218,-3.35904 2.58733,-2.03622 6.23997,-6.36804 7.37413,-8.74536 0.64823,-1.35877 0.73216,-1.8923 0.56253,-3.57654 -0.2316,-2.3005 -0.44696,-2.16353 3.91929,-2.49301 3.85817,-0.29115 6.65679,-1.49266 9.77494,-4.19656 2.99721,-2.5991 5.77546,-4.25711 7.14234,-4.26265 1.34414,-0.005 2.18866,0.95864 1.93792,2.21228 l -0.19117,0.956 1.02783,-0.62674 c 0.66237,-0.40384 1.60221,-0.62716 2.64269,-0.62793 1.73168,-10e-4 3.01752,-0.70122 4.31246,-2.34742 0.89476,-1.13744 0.70339,-1.77317 -0.78398,-2.60556 -0.68465,-0.38314 -1.52661,-1.0834 -1.87097,-1.55613 -0.54929,-0.75408 -0.57635,-0.97959 -0.22059,-1.83856 0.52649,-1.27114 3.93115,-4.11017 4.92904,-4.11017 0.41859,0 1.13672,0.14279 1.59566,0.3173 1.3868,0.52725 2.80354,-0.28364 3.6531,-2.09077 0.39579,-0.84216 1.25891,-2.18904 1.91795,-2.99304 1.48075,-1.80638 2.89866,-4.72745 2.89866,-5.97158 0,-0.75538 0.58238,-1.50827 3.06391,-3.96067 2.7523,-2.72002 6.3045,-6.98689 7.09162,-8.51845 0.1634,-0.318 0.3954,-1.22055 0.51562,-2.00566 0.25722,-1.68064 1.72382,-4.16066 2.46108,-4.16147 0.9766,-10e-4 2.12459,1.22566 2.31255,2.47132 0.0998,0.66067 0.27255,1.72385 0.384,2.36261 0.1155,0.66184 0.0472,1.45181 -0.15868,1.83656 -0.24595,0.45955 -0.25349,0.67517 -0.0229,0.67517 0.51299,0 2.24002,-2.8963 2.24002,-3.75665 0,-0.8354 0.53999,-2.02246 1.08581,-2.38694 0.19334,-0.12906 0.94292,-0.23686 1.66584,-0.23955 1.77381,-0.007 2.99753,0.95517 2.99753,2.35583 0,0.57021 0.21732,1.76868 0.48299,2.66324 l 0.48306,1.6265 0.92969,-0.92972 c 1.22287,-1.22287 2.47045,-1.24866 2.92225,-0.0604 0.22007,0.57891 0.22505,1.10057 0.0151,1.56166 -0.27458,0.60266 -0.20454,0.71514 0.53993,0.86809 1.18369,0.24315 3.55993,2.06175 3.91536,2.99648 0.59574,1.567 0.35077,3.19938 -0.65144,4.34081 -0.94122,1.07196 -0.94371,1.08593 -0.60505,3.28498 0.18712,1.21464 0.38753,2.25901 0.44545,2.32083 0.2451,0.26166 3.313,-0.9897 3.8317,-1.56289 1.62004,-1.79007 4.61934,0.34098 4.61934,3.28202 0,0.59127 -0.10771,1.21358 -0.23953,1.38292 -0.13176,0.16934 0.1309,-0.10749 0.58362,-0.61518 l 0.82309,-0.92308 2.45525,0.57882 c 3.13892,0.74002 4.67982,1.61224 5.4805,3.10222 0.49583,0.92281 0.83285,1.18897 1.50604,1.18964 0.49596,0.001 1.31643,0.39216 1.91637,0.91477 0.57707,0.50266 1.55223,1.17153 2.16717,1.48639 0.61481,0.31487 1.27608,0.78847 1.46955,1.05246 0.39952,0.54529 2.27154,0.59949 2.79024,0.0808 0.66827,-0.66817 2.3398,-0.37712 3.37202,0.58712 0.87138,0.81397 0.99174,1.13441 0.98984,2.63507 -0.007,3.14067 -1.18875,4.18009 -7.03587,6.17196 -3.71253,1.26471 -4.57971,1.44538 -6.93747,1.44538 -2.24861,0 -2.8547,-0.11412 -3.66279,-0.68954 -0.94626,-0.67382 -0.99252,-0.67652 -2.02854,-0.11858 -0.5831,0.31401 -1.383,0.91461 -1.77767,1.33464 l -0.71741,0.76372 1.56061,1.58439 c 1.40266,1.42413 1.61342,1.53657 2.08298,1.11159 0.76662,-0.69377 2.74012,-0.60035 3.50647,0.16598 0.78732,0.78729 0.81648,1.55502 0.0799,2.09925 -0.83901,0.61987 -0.0838,1.18313 1.57667,1.17578 1.61709,-0.007 2.17621,0.35138 2.17621,1.3954 0,0.59148 -0.17166,0.7594 -0.7769,0.7594 -0.48332,0 -0.84989,0.22977 -0.96998,0.60798 -0.26508,0.83534 -2.11417,1.6503 -4.4471,1.96007 -1.90366,0.25276 -5.24254,1.10817 -7.59191,1.94503 -1.09649,0.39058 -1.18265,0.52074 -1.37769,2.08163 -0.25454,2.03716 -0.67941,2.42422 -2.5359,2.31005 -0.79407,-0.0488 -1.53022,-0.002 -1.6359,0.10335 -0.10561,0.10567 0.32091,0.60142 0.94784,1.10167 0.62693,0.50027 1.13993,1.14348 1.13993,1.4294 0,0.28592 0.21555,0.69878 0.47906,0.91747 1.02219,0.84833 0.30092,2.43799 -1.55295,3.4227 -0.52676,0.27977 -0.48306,0.33828 0.3819,0.51126 1.25557,0.25111 1.75716,1.19504 1.48651,2.79737 -0.15363,0.90893 -0.36794,1.2537 -0.77945,1.2537 -1.42926,0 -3.3719,-2.70726 -2.60535,-3.63084 0.50081,-0.60337 -1.57909,-0.86467 -4.87669,-0.61268 -2.37814,0.18174 -2.45709,0.21144 -1.43732,0.54105 0.67928,0.21956 1.25642,0.70374 1.55806,1.30695 0.41505,0.8301 0.62988,0.94551 1.607,0.86325 0.85566,-0.072 1.30196,0.0903 1.84916,0.67285 0.87917,0.9358 1.26172,2.8927 0.69828,3.57163 -0.45639,0.54984 -2.57856,0.65234 -3.08199,0.14886 -0.23101,-0.23099 -0.45619,-0.1844 -0.73549,0.15214 -0.34547,0.41624 -0.19184,0.54147 1.0828,0.88237 2.06555,0.55246 2.84678,1.34484 2.63181,2.66945 -0.12598,0.77608 -0.0111,1.1894 0.4446,1.60189 0.33781,0.30575 0.61514,0.85703 0.61626,1.22506 0,0.40883 0.37665,0.8823 0.9648,1.21704 0.60282,0.34303 1.20761,1.11895 1.61742,2.075045 0.37403,0.87256 1.58191,2.485991 2.81788,3.764031 2.72839,2.82133 3.02053,3.36933 2.75178,5.16167 -0.1765,1.17708 -0.43169,1.57351 -1.52084,2.36249 -0.71977,0.52142 -1.65712,1.46074 -2.08292,2.08735 -0.66074,0.97241 -0.72193,1.26543 -0.41747,2.00042 0.19615,0.47362 1.00666,1.25369 1.80099,1.7335 0.79426,0.47981 1.6716,1.26687 1.94966,1.74904 0.56868,0.98649 2.52869,2.54597 4.42534,3.52103 0.69619,0.35796 1.69715,1.10835 2.22417,1.66754 0.52702,0.55918 1.52124,1.30625 2.2095,1.66012 1.53401,0.78869 4.33814,2.85596 4.33814,3.19814 0,0.64314 2.36392,2.78408 3.29157,2.98114 3.11842,0.66236 2.71293,3.44603 -0.88801,6.09705 l -1.28558,0.94651 -5.32705,-0.0434 c -4.41945,-0.036 -5.46766,-0.13568 -6.15336,-0.58491 -1.12014,-0.734 -3.69123,-1.21344 -3.69123,-0.68833 0,0.88679 -1.22942,1.53613 -2.56839,1.35654 -1.12847,-0.15136 -1.45376,-0.0446 -2.40271,0.78858 -0.60361,0.52999 -1.09747,1.11694 -1.09747,1.30432 0,0.61061 -2.01766,4.84486 -2.64971,5.56065 -0.83547,0.94619 -1.93367,5.6836 -1.50374,6.48688 0.50015,0.93456 0.37973,2.29694 -0.31815,3.59909 -0.77894,1.45317 -0.79106,1.89641 -0.10398,3.81328 0.46,1.28334 0.67568,1.5151 1.48658,1.597 1.48403,0.14992 1.74197,0.90287 0.92798,2.70938 -0.38137,0.84625 -0.78522,2.35688 -0.89764,3.35694 -0.11931,1.06047 -0.42298,2.01508 -0.72888,2.29042 -0.68334,0.61527 -3.70237,1.79849 -4.6086,1.8063 -0.72042,0.007 -3.41815,2.85544 -5.35745,5.65834 -1.05175,1.52015 -2.85327,2.4565 -4.21281,2.18961 -0.75535,-0.14829 -0.87832,-0.0687 -0.87832,0.56857 0,0.91256 -0.75207,1.60008 -2.29008,2.09359 -1.4381,0.46144 -1.7214,0.80341 -1.96204,2.3682 -0.23809,1.54838 -0.68406,2.08325 -2.35507,2.82408 l -1.33701,0.5928 0.77815,0.77808 c 0.69428,0.6944 0.77808,1.05197 0.77808,3.32499 0,1.85231 -0.13241,2.67923 -0.48529,3.03212 -0.43398,0.43402 -0.35818,0.52049 0.71872,0.81954 0.66212,0.18388 1.51875,0.33512 1.9036,0.3361 0.38485,0.001 0.78136,0.13367 0.88094,0.29487 0.25866,0.41856 -0.38281,4.69924 -0.97325,6.49419 l -0.49911,1.51716 -1.65116,-0.001 -1.65116,-10e-4 0.0983,3.6244 0.0984,3.6244 -1.14753,1.00754 c -0.63119,0.55415 -1.34035,1.00754 -1.57601,1.00754 -0.28893,0 -0.47605,0.57495 -0.57491,1.76696 -0.11787,1.42104 -0.33794,1.96816 -1.1244,2.79476 -1.13233,1.19012 -2.96046,4.69205 -2.96046,5.671 0,1.11194 -0.56115,1.80916 -1.6279,2.02253 -0.55663,0.11131 -1.67566,0.67436 -2.48682,1.25124 -1.22006,0.86773 -6.20079,3.10238 -6.91473,3.10238 -0.11119,0 -1.23238,0.43908 -2.49148,0.97576 -1.25917,0.53667 -2.86172,1.21939 -3.56125,1.51716 -0.69952,0.29776 -3.03704,1.4397 -5.19451,2.53764 -2.15747,1.09794 -4.25494,1.99626 -4.66121,1.99626 -0.4062,0 -1.06176,-0.34404 -1.4569,-0.76453 z', + fill: 'blue', + }); + layer.add(borneo); + stage.add(layer); + + var trace = layer.getContext().getTrace(); + assert.equal( + trace, + 'bezierCurveTo(209.761,60.371,209.972,60.483,210.442,60.058);bezierCurveTo(211.208,59.365,213.182,59.458,213.948,60.224);bezierCurveTo(214.736,61.012,214.765,61.779,214.028,62.324);bezierCurveTo(213.189,62.943,213.944,63.507,215.605,63.499);bezierCurveTo(217.222,63.492,217.781,63.851,217.781,64.895);bezierCurveTo(217.781,65.486,217.609,65.654,217.004,65.654);bezierCurveTo(216.521,65.654,216.154,65.884,216.034,66.262);bezierCurveTo(215.769,67.097,213.92,67.912,211.587,68.222);bezierCurveTo(209.683,68.475,206.345,69.33,203.995,70.167);bezierCurveTo(202.899,70.558,202.813,70.688,202.617,72.249);bezierCurveTo(202.363,74.286,201.938,74.673,200.082,74.559);bezierCurveTo(199.287,74.51,198.551,74.557,198.446,74.662);bezierCurveTo(198.34,74.768,198.767,75.264,199.394,75.764);bezierCurveTo(200.02,76.264,200.533,76.907,200.533,77.193);bezierCurveTo(200.533,77.479,200.749,77.892,201.012,78.111);bezierCurveTo(202.035,78.959,201.313,80.549,199.46,81.533);bezierCurveTo(198.933,81.813,198.976,81.872,199.841,82.045);bezierCurveTo(201.097,82.296,201.599,83.24,201.328,84.842);bezierCurveTo(201.174,85.751,200.96,86.096,200.549,86.096);bezierCurveTo(199.119,86.096,197.177,83.389,197.943,82.465);bezierCurveTo(198.444,81.862,196.364,81.6,193.066,81.852);bezierCurveTo(190.688,82.034,190.609,82.064,191.629,82.393);bezierCurveTo(192.308,82.613,192.886,83.097,193.187,83.7);bezierCurveTo(193.602,84.53,193.817,84.646,194.794,84.564);bezierCurveTo(195.65,84.492,196.096,84.654,196.643,85.236);bezierCurveTo(197.523,86.172,197.905,88.129,197.342,88.808);bezierCurveTo(196.885,89.358,194.763,89.46,194.26,88.957);bezierCurveTo(194.029,88.726,193.803,88.772,193.524,89.109);bezierCurveTo(193.179,89.525,193.332,89.65,194.607,89.991);bezierCurveTo(196.673,90.544,197.454,91.336,197.239,92.661);bezierCurveTo(197.113,93.437,197.228,93.85,197.683,94.263);bezierCurveTo(198.021,94.568,198.299,95.12,198.3,95.488);bezierCurveTo(198.3,95.897,198.676,96.37,199.264,96.705);bezierCurveTo(199.867,97.048,200.472,97.824,200.882,98.78);bezierCurveTo(201.256,99.652,202.464,101.266,203.7,102.544);bezierCurveTo(206.428,105.365,206.72,105.913,206.452,107.706);bezierCurveTo(206.275,108.883,206.02,109.279,204.931,110.068);bezierCurveTo(204.211,110.589,203.274,111.529,202.848,112.155);bezierCurveTo(202.187,113.128,202.126,113.421,202.43,114.156);bezierCurveTo(202.626,114.629,203.437,115.409,204.231,115.889);bezierCurveTo(205.026,116.369,205.903,117.156,206.181,117.638);bezierCurveTo(206.75,118.625,208.71,120.184,210.606,121.159);bezierCurveTo(211.302,121.517,212.303,122.268,212.83,122.827);bezierCurveTo(213.357,123.386,214.352,124.133,215.04,124.487);bezierCurveTo(216.574,125.276,219.378,127.343,219.378,127.685);bezierCurveTo(219.378,128.328,221.742,130.469,222.67,130.666);bezierCurveTo(225.788,131.329,225.383,134.112,221.782,136.763);lineTo(220.496,137.71);lineTo(215.169,137.666);bezierCurveTo(210.75,137.63,209.701,137.531,209.016,137.082);bezierCurveTo(207.896,136.348,205.324,135.868,205.324,136.393);bezierCurveTo(205.324,137.28,204.095,137.929,202.756,137.75);bezierCurveTo(201.628,137.598,201.302,137.705,200.353,138.538);bezierCurveTo(199.75,139.068,199.256,139.655,199.256,139.843);bezierCurveTo(199.256,140.453,197.238,144.688,196.606,145.403);bezierCurveTo(195.771,146.35,194.672,151.087,195.102,151.89);bezierCurveTo(195.603,152.825,195.482,154.187,194.784,155.489);bezierCurveTo(194.005,156.942,193.993,157.386,194.68,159.303);bezierCurveTo(195.14,160.586,195.356,160.818,196.167,160.9);bezierCurveTo(197.651,161.049,197.909,161.802,197.095,163.609);bezierCurveTo(196.713,164.455,196.31,165.966,196.197,166.966);bezierCurveTo(196.078,168.026,195.774,168.981,195.468,169.256);bezierCurveTo(194.785,169.872,191.766,171.055,190.86,171.063);bezierCurveTo(190.139,171.07,187.442,173.918,185.502,176.721);bezierCurveTo(184.451,178.241,182.649,179.177,181.289,178.911);bezierCurveTo(180.534,178.762,180.411,178.842,180.411,179.479);bezierCurveTo(180.411,180.392,179.659,181.079,178.121,181.573);bezierCurveTo(176.683,182.034,176.4,182.376,176.159,183.941);bezierCurveTo(175.921,185.489,175.475,186.024,173.804,186.765);lineTo(172.467,187.358);lineTo(173.245,188.136);bezierCurveTo(173.939,188.83,174.023,189.188,174.023,191.461);bezierCurveTo(174.023,193.313,173.891,194.14,173.538,194.493);bezierCurveTo(173.104,194.927,173.18,195.013,174.257,195.313);bezierCurveTo(174.919,195.496,175.775,195.648,176.16,195.649);bezierCurveTo(176.545,195.65,176.942,195.782,177.041,195.944);bezierCurveTo(177.3,196.362,176.658,200.643,176.068,202.438);lineTo(175.569,203.955);lineTo(173.918,203.954);lineTo(172.266,203.953);lineTo(172.365,207.577);lineTo(172.463,211.202);lineTo(171.316,212.209);bezierCurveTo(170.684,212.763,169.975,213.217,169.74,213.217);bezierCurveTo(169.451,213.217,169.264,213.792,169.165,214.984);bezierCurveTo(169.047,216.405,168.827,216.952,168.04,217.778);bezierCurveTo(166.908,218.969,165.08,222.471,165.08,223.449);bezierCurveTo(165.08,224.561,164.519,225.259,163.452,225.472);bezierCurveTo(162.895,225.583,161.776,226.146,160.965,226.723);bezierCurveTo(159.745,227.591,154.764,229.826,154.05,229.826);bezierCurveTo(153.939,229.826,152.818,230.265,151.559,230.801);bezierCurveTo(150.3,231.338,148.697,232.021,147.998,232.319);bezierCurveTo(147.298,232.616,144.961,233.758,142.803,234.856);bezierCurveTo(140.646,235.954,138.548,236.852,138.142,236.852);bezierCurveTo(137.736,236.852,137.08,236.508,136.685,236.088);closePath();fillStyle=blue;fill();restore();' + ); + }); + + // ====================================================== + it('Stroke and fill when no closed', function () { + // https://github.com/konvajs/konva/issues/150 + + var stage = addStage(); + var layer = new Konva.Layer(); + + var path = new Konva.Path({ + data: 'M 50 0 C 50 150 170 170 200 170', + stroke: 'black', + fill: '#ff0000', + }); + + // override color key so that we can test the context trace + path.colorKey = 'black'; + + path.on('mouseover', function () { + this.stroke('#f00'); + layer.draw(); + }); + + path.on('mouseout', function () { + this.stroke('#000'); + layer.draw(); + }); + + layer.add(path); + stage.add(layer); + + var trace = layer.getContext().getTrace(); + + //console.log(trace); + + var hitTrace = layer.hitCanvas.getContext().getTrace(); + //console.log(hitTrace); + + assert.equal( + trace, + 'clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);beginPath();moveTo(50,0);bezierCurveTo(50,150,170,170,200,170);fillStyle=#ff0000;fill();lineWidth=2;strokeStyle=black;stroke();restore();' + ); + assert.equal( + hitTrace, + 'clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);beginPath();moveTo(50,0);bezierCurveTo(50,150,170,170,200,170);save();fillStyle=black;fill();restore();lineWidth=2;strokeStyle=black;stroke();restore();' + ); + }); + + // ====================================================== + // do we need to fill hit, when it is not closed? + it('Stroke when no closed', function () { + // https://github.com/konvajs/konva/issues/867 + + var stage = addStage(); + var layer = new Konva.Layer(); + + var path = new Konva.Path({ + data: 'M 0 0 L 100 100 L 100 0', + stroke: 'black', + }); + + // override color key so that we can test the context trace + path.colorKey = 'black'; + + path.on('mouseover', function () { + this.stroke('#f00'); + layer.draw(); + }); + + path.on('mouseout', function () { + this.stroke('#000'); + layer.draw(); + }); + + layer.add(path); + stage.add(layer); + + var trace = layer.getContext().getTrace(); + + var hitTrace = layer.hitCanvas.getContext().getTrace(); + + assert.equal( + trace, + 'clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);beginPath();moveTo(0,0);lineTo(100,100);lineTo(100,0);lineWidth=2;strokeStyle=black;stroke();restore();' + ); + assert.equal( + hitTrace, + 'clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);beginPath();moveTo(0,0);lineTo(100,100);lineTo(100,0);lineWidth=2;strokeStyle=black;stroke();restore();' + ); + }); + + // ====================================================== + it('draw path with no space in numbers', function () { + // https://github.com/konvajs/konva/issues/329 + + var stage = addStage(); + var layer = new Konva.Layer(); + + var path = new Konva.Path({ + data: 'M10.5.5l10 10', + stroke: 'black', + }); + layer.add(path); + + stage.add(layer); + + var trace = layer.getContext().getTrace(); + + assert.equal( + trace, + 'clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);beginPath();moveTo(10.5,0.5);lineTo(20.5,10.5);lineWidth=2;strokeStyle=black;stroke();restore();' + ); + }); + + it('getClientRect', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var path = new Konva.Path({ + data: 'M61.55,184.55 60.55,280.55 164.55,284.55 151.55,192.55 Z', + fill: 'black', + stroke: 'red', + }); + layer.add(path); + var rect = path.getClientRect(); + assertAlmostDeepEqual(rect, { + x: 59.55, + y: 183.55, + width: 106, + height: 102, + }); + }); + + it('getClientRect of complex path', function () { + // TODO: it is failing on Node + if (isNode) { + return; + } + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var path = new Konva.Path({ + data: 'M9.9,104.71l2.19-1.27a2,2,0,0,1,1.94,0l.5.29a.5.5,0,0,1,.21.67s0,0,0,0a.5.5,0,0,1-.19.19l-2.2,1.27a1.92,1.92,0,0,1-1.94,0l-.5-.29a.51.51,0,0,1-.21-.68l0,0A.52.52,0,0,1,9.9,104.71Zm4.85-1.9.5.29a1.92,1.92,0,0,0,1.94,0l2.19-1.27a.51.51,0,0,0,.21-.68l0,0a.44.44,0,0,0-.19-.19l-.5-.29a1.92,1.92,0,0,0-1.94,0l-2.2,1.27a.5.5,0,0,0-.21.67s0,0,0,0a.5.5,0,0,0,.19.19Zm4.86-2.8.5.29a1.92,1.92,0,0,0,1.94,0L24.25,99a.51.51,0,0,0,.21-.68l0,0a.44.44,0,0,0-.19-.19l-.5-.29a1.92,1.92,0,0,0-1.94,0l-2.19,1.27a.5.5,0,0,0-.21.67s0,0,0,0a.5.5,0,0,0,.19.19Zm4.85-2.8.5.29a2,2,0,0,0,2,0l2.21-1.27a.52.52,0,0,0,.21-.68s0,0,0,0a.57.57,0,0,0-.19-.19l-.5-.29a1.92,1.92,0,0,0-1.94,0L24.51,96.3a.5.5,0,0,0-.25.66s0,0,0,0a.49.49,0,0,0,.18.2Zm4.86-2.8.5.29a2,2,0,0,0,1.94,0L34,93.43a.51.51,0,0,0,.21-.68l0,0a.44.44,0,0,0-.19-.19l-.5-.29a1.92,1.92,0,0,0-1.94,0L29.32,93.5a.52.52,0,0,0-.18.72A.47.47,0,0,0,29.32,94.41Zm4.85-2.81.5.29a1.92,1.92,0,0,0,1.94,0l2.2-1.26A.52.52,0,0,0,39,90s0,0,0,0a.57.57,0,0,0-.19-.19l-.5-.29a1.92,1.92,0,0,0-1.94,0l-2.2,1.26a.5.5,0,0,0-.22.67l0,0A.5.5,0,0,0,34.17,91.6ZM39,88.8l.5.29a1.92,1.92,0,0,0,1.94,0l2.21-1.26a.53.53,0,0,0,.18-.73.77.77,0,0,0-.18-.18l-.5-.29a2,2,0,0,0-1.94,0L39,87.9a.5.5,0,0,0-.23.67l0,0a.52.52,0,0,0,.19.2ZM43.88,86l.5.29a1.92,1.92,0,0,0,1.94,0L48.52,85a.52.52,0,0,0,.21-.68l0,0a.57.57,0,0,0-.19-.19l-.5-.29a2,2,0,0,0-2,0L43.83,85.1a.49.49,0,0,0-.17.69h0a.44.44,0,0,0,.2.18Zm4.85-2.8.5.29a1.92,1.92,0,0,0,1.94,0l2.19-1.27a.52.52,0,0,0,.21-.68l0,0a.57.57,0,0,0-.19-.19l-.5-.29a1.92,1.92,0,0,0-1.94,0L48.73,82.3a.5.5,0,0,0-.2.68l0,0a.47.47,0,0,0,.18.19Zm4.86-2.8.5.29a1.92,1.92,0,0,0,1.94,0l2.2-1.27a.52.52,0,0,0,.21-.68s0,0,0,0a.57.57,0,0,0-.19-.19l-.5-.29a1.92,1.92,0,0,0-1.94,0L53.6,79.5a.5.5,0,0,0-.21.68l0,0a.58.58,0,0,0,.19.19Zm4.85-2.8.5.29a2,2,0,0,0,2,0l2.19-1.27a.52.52,0,0,0,.21-.68s0,0,0,0a.57.57,0,0,0-.19-.19l-.5-.29a1.92,1.92,0,0,0-1.94,0l-2.2,1.27a.49.49,0,0,0-.25.66.08.08,0,0,0,0,0,.49.49,0,0,0,.18.2Zm4.86-2.8.5.29a2,2,0,0,0,1.94,0l2.2-1.27a.52.52,0,0,0,.21-.68s0,0,0,0a.57.57,0,0,0-.19-.19l-.5-.29a1.92,1.92,0,0,0-1.94,0l-2.19,1.26a.53.53,0,0,0-.19.73h0a.52.52,0,0,0,.18.18ZM68.15,72l.5.29a1.92,1.92,0,0,0,1.94,0L72.79,71a.52.52,0,0,0,.21-.68l0,0a.57.57,0,0,0-.19-.19l-.5-.29a1.92,1.92,0,0,0-1.94,0l-2.2,1.26a.5.5,0,0,0-.22.67l0,0A.5.5,0,0,0,68.15,72ZM73,69.19l.5.29a1.92,1.92,0,0,0,1.94,0l2.19-1.26a.53.53,0,0,0,.18-.73.7.7,0,0,0-.15-.19l-.5-.29a2,2,0,0,0-1.94,0L73,68.3a.49.49,0,0,0-.22.67s0,0,0,0a.5.5,0,0,0,.19.19ZM12.94,107.37l2.2,1.27a2,2,0,0,0,1.94,0l2.2-1.27a.51.51,0,0,0,.21-.68l0,0a.44.44,0,0,0-.19-.19l-2.2-1.27a1.92,1.92,0,0,0-1.94,0l-2.2,1.27a.5.5,0,0,0-.21.67s0,0,0,0A.5.5,0,0,0,12.94,107.37Zm4.86-2.8L20,105.83a1.92,1.92,0,0,0,1.94,0l2.2-1.26a.51.51,0,0,0,.21-.68l0,0a.44.44,0,0,0-.19-.19l-2.2-1.27a1.92,1.92,0,0,0-1.94,0l-2.19,1.27a.5.5,0,0,0-.17.69s0,0,0,0a.54.54,0,0,0,.15.15Zm4.85-2.8,2.2,1.26a1.92,1.92,0,0,0,1.94,0L29,101.77a.54.54,0,0,0,.19-.73.66.66,0,0,0-.19-.18L26.79,99.6a1.92,1.92,0,0,0-1.94,0l-2.18,1.26a.52.52,0,0,0-.19.72.58.58,0,0,0,.19.19ZM27.51,99l2.19,1.27a1.92,1.92,0,0,0,1.94,0L33.84,99a.51.51,0,0,0,.21-.68l0,0a.44.44,0,0,0-.19-.19L31.67,96.8a1.92,1.92,0,0,0-1.94,0l-2.19,1.26a.5.5,0,0,0-.22.67l0,0a.5.5,0,0,0,.19.19Zm4.85-2.8,2.2,1.27a1.92,1.92,0,0,0,1.94,0l2.19-1.27a.51.51,0,0,0,.21-.68l0,0a.44.44,0,0,0-.19-.19L36.5,94a2,2,0,0,0-1.94,0l-2.2,1.27a.5.5,0,0,0-.21.67s0,0,0,0A.5.5,0,0,0,32.36,96.16Zm4.86-2.8,2.19,1.27a1.92,1.92,0,0,0,1.94,0l2.2-1.27a.51.51,0,0,0,.21-.68l0,0a.44.44,0,0,0-.19-.19l-2.2-1.27a2,2,0,0,0-1.94,0l-2.19,1.27a.5.5,0,0,0-.21.67l0,0A.5.5,0,0,0,37.22,93.36Zm4.85-2.8,2.2,1.27a2,2,0,0,0,1.94,0l2.19-1.27a.51.51,0,0,0,.21-.68l0,0a.44.44,0,0,0-.19-.19l-2.19-1.27a1.92,1.92,0,0,0-1.94,0l-2.2,1.27a.5.5,0,0,0-.2.68.48.48,0,0,0,.2.2Zm4.85-2.8L49.12,89a2,2,0,0,0,1.94,0l2.2-1.27a.51.51,0,0,0,.21-.68l0,0a.44.44,0,0,0-.19-.19l-2.2-1.27a1.92,1.92,0,0,0-1.94,0l-2.2,1.27a.5.5,0,0,0-.21.67s0,0,0,0A.5.5,0,0,0,46.92,87.76ZM51.78,85,54,86.22a1.94,1.94,0,0,0,2,0L58.16,85a.51.51,0,0,0,.21-.68l0,0a.44.44,0,0,0-.19-.19L56,82.8a1.94,1.94,0,0,0-2,0l-2.19,1.27a.5.5,0,0,0-.21.67s0,0,0,0a.5.5,0,0,0,.19.19Zm4.85-2.8,2.2,1.26a1.92,1.92,0,0,0,1.94,0L63,82.16a.51.51,0,0,0,.21-.68l0,0a.62.62,0,0,0-.18-.18L60.77,80a1.92,1.92,0,0,0-1.94,0l-2.2,1.26a.52.52,0,0,0-.18.72A.47.47,0,0,0,56.63,82.16Zm4.86-2.81,2.19,1.27a1.92,1.92,0,0,0,1.94,0l2.2-1.27a.51.51,0,0,0,.21-.68l0,0a.44.44,0,0,0-.19-.19l-2.2-1.26a1.92,1.92,0,0,0-1.94,0l-2.19,1.26a.5.5,0,0,0-.21.67s0,0,0,0A.5.5,0,0,0,61.49,79.35Zm4.85-2.8,2.2,1.27a1.92,1.92,0,0,0,1.94,0l2.19-1.27a.52.52,0,0,0,.21-.68l0,0a.57.57,0,0,0-.19-.19l-2.19-1.27a2,2,0,0,0-1.94,0l-2.2,1.27a.5.5,0,0,0-.2.68l0,0a.62.62,0,0,0,.18.18Zm4.91-2.83L73.45,75a2,2,0,0,0,1.94,0l7-4a.51.51,0,0,0,.21-.68l0,0a.44.44,0,0,0-.19-.19L80.2,68.8a2,2,0,0,0-1.94,0l-7,4a.49.49,0,0,0-.22.67s0,0,0,0a.5.5,0,0,0,.19.19ZM17.83,110.19l2.2,1.27a2,2,0,0,0,1.94,0l3.82-2.21a.51.51,0,0,0,.21-.68l0,0a.44.44,0,0,0-.19-.19l-2.19-1.27a1.92,1.92,0,0,0-1.94,0l-3.83,2.22a.48.48,0,0,0-.17.68v0a.5.5,0,0,0,.19.19Zm6.5-3.74,2.19,1.26a1.92,1.92,0,0,0,1.94,0l2.21-1.26a.52.52,0,0,0,.21-.68l0,0a.77.77,0,0,0-.18-.18l-2.22-1.24a1.92,1.92,0,0,0-1.94,0l-2.19,1.26a.52.52,0,0,0-.18.72.47.47,0,0,0,.18.19Zm4.85-2.8,2.2,1.26a1.92,1.92,0,0,0,1.94,0l2.19-1.26a.51.51,0,0,0,.21-.68l0,0a.62.62,0,0,0-.18-.18l-2.19-1.26a1.92,1.92,0,0,0-1.94,0l-2.21,1.26a.52.52,0,0,0-.19.72.58.58,0,0,0,.19.19ZM34,100.84l2.2,1.27a1.92,1.92,0,0,0,1.94,0l2.2-1.27a.51.51,0,0,0,.21-.68l0,0a.44.44,0,0,0-.19-.19l-2.2-1.27a2,2,0,0,0-1.94,0L34,99.94a.5.5,0,0,0-.23.67l0,0a.52.52,0,0,0,.19.2ZM38.89,98l2.18,1.26a1.94,1.94,0,0,0,2,0L45.26,98a.51.51,0,0,0,.21-.68l0,0a.44.44,0,0,0-.19-.19l-2.19-1.27a2,2,0,0,0-2,0l-2.19,1.27a.5.5,0,0,0-.21.67s0,0,0,0a.5.5,0,0,0,.19.19Zm4.85-2.8,2.2,1.27a2,2,0,0,0,1.94,0l2.2-1.27a.51.51,0,0,0,.21-.68l0,0a.44.44,0,0,0-.19-.19l-2.2-1.27a2,2,0,0,0-1.94,0l-2.2,1.27a.5.5,0,0,0-.21.67s0,0,0,0A.5.5,0,0,0,43.74,95.24Zm4.86-2.8,2.19,1.27a2,2,0,0,0,1.94,0l2.2-1.27a.51.51,0,0,0,.21-.68l0,0a.44.44,0,0,0-.19-.19l-2.2-1.27a1.92,1.92,0,0,0-1.94,0L48.6,91.54a.5.5,0,0,0-.21.67s0,0,0,0A.5.5,0,0,0,48.6,92.44Zm4.85-2.8,2.2,1.27a2,2,0,0,0,1.94,0l2.19-1.27A.51.51,0,0,0,60,89l0,0a.44.44,0,0,0-.19-.19l-2.19-1.27a1.92,1.92,0,0,0-1.94,0l-2.2,1.27a.5.5,0,0,0-.21.67l0,0A.5.5,0,0,0,53.45,89.64Zm4.86-2.8L60.5,88.1a1.92,1.92,0,0,0,1.94,0l2.2-1.26a.54.54,0,0,0,.17-.74.46.46,0,0,0-.17-.17l-2.2-1.26a1.92,1.92,0,0,0-1.94,0l-2.19,1.26a.52.52,0,0,0-.18.72.47.47,0,0,0,.18.19ZM63.16,84l2.2,1.26a1.92,1.92,0,0,0,1.94,0L69.49,84a.54.54,0,0,0,.17-.74.46.46,0,0,0-.17-.17L67.3,81.87a1.92,1.92,0,0,0-1.94,0l-2.2,1.26a.52.52,0,0,0-.18.72.47.47,0,0,0,.18.19ZM68,81.23l2.19,1.27a1.92,1.92,0,0,0,1.94,0l2.2-1.27a.52.52,0,0,0,.19-.71.44.44,0,0,0-.19-.19l-2.2-1.26a1.92,1.92,0,0,0-1.94,0L68,80.33a.5.5,0,0,0-.22.67l0,0a.41.41,0,0,0,.18.19Zm4.85-2.8,2.19,1.27a1.94,1.94,0,0,0,2,0l2.19-1.27a.51.51,0,0,0,.21-.68l0,0a.44.44,0,0,0-.19-.19l-2.19-1.27a2,2,0,0,0-2,0l-2.19,1.27a.5.5,0,0,0-.21.67s0,0,0,0A.5.5,0,0,0,72.87,78.43Zm4.91-2.83L80,76.87a2,2,0,0,0,1.94,0l5.36-3.1a.51.51,0,0,0,.21-.68l0,0a.44.44,0,0,0-.19-.19l-2.2-1.27a2,2,0,0,0-1.94,0l-5.36,3.1a.5.5,0,0,0-.21.67s0,0,0,0A.5.5,0,0,0,77.78,75.6ZM22.78,113,25,114.3a1.92,1.92,0,0,0,1.94,0l3.83-2.2a.54.54,0,0,0,.17-.74.46.46,0,0,0-.17-.17l-2.2-1.26a1.92,1.92,0,0,0-1.94,0l-3.83,2.2a.52.52,0,0,0-.19.72h0a.55.55,0,0,0,.18.19Zm6.5-3.75,2.2,1.27a1.92,1.92,0,0,0,1.94,0l2.17-1.26a.51.51,0,0,0,.21-.68l0,0a.44.44,0,0,0-.19-.19l-2.19-1.26a1.92,1.92,0,0,0-1.94,0l-2.2,1.26a.5.5,0,0,0-.22.67l0,0a.41.41,0,0,0,.18.19Zm4.85-2.8,2.2,1.27a1.92,1.92,0,0,0,1.94,0l2.2-1.27a.51.51,0,0,0,.2-.68h0a.57.57,0,0,0-.19-.19l-2.23-1.31a2,2,0,0,0-1.94,0l-2.2,1.27a.5.5,0,0,0-.29.65l0,0a.53.53,0,0,0,.26.27Zm4.86-2.8L41.17,105a2,2,0,0,0,1.95,0l2.19-1.27a.52.52,0,0,0,.21-.68l0,0a.57.57,0,0,0-.19-.19l-2.2-1.25a2,2,0,0,0-2,0L39,102.8a.5.5,0,0,0-.15.69s0,0,0,0a.44.44,0,0,0,.17.16Zm4.85-2.8,2.2,1.27a2,2,0,0,0,1.94,0l2.19-1.27a.52.52,0,0,0,.19-.71.44.44,0,0,0-.19-.19L48,98.72a1.92,1.92,0,0,0-1.94,0L43.84,100a.5.5,0,0,0-.22.67l0,0a.41.41,0,0,0,.18.19Zm4.86-2.8,2.19,1.27a2,2,0,0,0,1.94,0L55,98.09a.52.52,0,0,0,.19-.71.44.44,0,0,0-.19-.19l-2.2-1.27a1.92,1.92,0,0,0-1.94,0l-2.22,1.27a.49.49,0,0,0-.22.67s0,0,0,0a.44.44,0,0,0,.19.19Zm4.85-2.8,2.2,1.26a1.92,1.92,0,0,0,1.94,0l2.19-1.26a.52.52,0,0,0,.19-.71.58.58,0,0,0-.19-.19l-2.21-1.27a1.92,1.92,0,0,0-1.94,0l-2.2,1.27a.49.49,0,0,0-.22.67s0,0,0,0a.44.44,0,0,0,.19.19Zm4.86-2.8,2.19,1.26a1.92,1.92,0,0,0,1.94,0l2.2-1.26a.54.54,0,0,0,.17-.74.46.46,0,0,0-.17-.17l-2.2-1.26a1.92,1.92,0,0,0-1.94,0l-2.19,1.26a.53.53,0,0,0-.26.7.57.57,0,0,0,.25.26Zm4.85-2.81L65.46,91a1.92,1.92,0,0,0,1.94,0l2.19-1.27a.52.52,0,0,0,.19-.71.58.58,0,0,0-.19-.19L67.4,87.52a1.92,1.92,0,0,0-1.94,0l-2.2,1.26a.5.5,0,0,0-.22.67l0,0a.41.41,0,0,0,.18.19Zm4.86-2.8,2.19,1.27a1.92,1.92,0,0,0,1.94,0l2.2-1.27a.52.52,0,0,0,.19-.71.58.58,0,0,0-.19-.19l-2.2-1.27a2,2,0,0,0-1.94,0L68.12,86a.5.5,0,0,0-.22.67l0,0a.41.41,0,0,0,.18.19ZM73,84.08l2.2,1.27a2,2,0,0,0,2,0l2.19-1.27a.53.53,0,0,0,.19-.71.69.69,0,0,0-.19-.19l-2.19-1.27a2,2,0,0,0-2,0L73,83.18a.49.49,0,0,0-.22.67s0,0,0,0a.44.44,0,0,0,.19.19Zm4.85-2.8L80,82.54a2,2,0,0,0,1.94,0l2.2-1.27a.51.51,0,0,0,.21-.68l0,0a.44.44,0,0,0-.19-.19L82,79.1a1.92,1.92,0,0,0-1.94,0l-2.2,1.27a.5.5,0,0,0-.23.67l0,0a.58.58,0,0,0,.2.21Zm4.92-2.83,2.19,1.26a1.92,1.92,0,0,0,1.94,0l5.36-3.09a.52.52,0,0,0,.19-.71.58.58,0,0,0-.19-.19L90,74.45a1.92,1.92,0,0,0-1.94,0l-5.36,3.09a.53.53,0,0,0-.18.73.52.52,0,0,0,.18.18Zm-55,37.43,2.19,1.27a1.94,1.94,0,0,0,1.95,0l7.29-4.21a.55.55,0,0,0,.17-.74.46.46,0,0,0-.17-.17L37,110.77a1.92,1.92,0,0,0-1.94,0L27.73,115a.5.5,0,0,0-.23.67l0,0a.52.52,0,0,0,.19.2Zm10-5.75,2.2,1.27a1.92,1.92,0,0,0,1.94,0l2.19-1.27a.52.52,0,0,0,.19-.71.58.58,0,0,0-.19-.19L41.88,108a1.92,1.92,0,0,0-1.94,0l-2.2,1.26a.5.5,0,0,0-.26.66l0,0a.46.46,0,0,0,.18.21Zm4.86-2.8,2.19,1.27a1.92,1.92,0,0,0,1.94,0l2.2-1.27a.52.52,0,0,0,.19-.71.58.58,0,0,0-.19-.19l-2.2-1.27a2,2,0,0,0-1.94,0l-2.19,1.27a.5.5,0,0,0-.26.66l0,0a.46.46,0,0,0,.18.21Zm4.85-2.8,2.2,1.27a1.92,1.92,0,0,0,1.94,0l2.19-1.27a.52.52,0,0,0,.19-.71.58.58,0,0,0-.19-.19l-2.19-1.27a2,2,0,0,0-1.94,0l-2.2,1.27a.5.5,0,0,0-.3.64l0,0a.45.45,0,0,0,.19.22Zm4.86-2.8L54.5,103a2,2,0,0,0,1.94,0l2.2-1.27a.52.52,0,0,0,.19-.71.58.58,0,0,0-.19-.19l-2.2-1.27a1.92,1.92,0,0,0-1.94,0l-2.19,1.27a.5.5,0,0,0-.26.66l0,0a.46.46,0,0,0,.18.21Zm4.85-2.8,2.19,1.27a2,2,0,0,0,2,0l2.19-1.27a.52.52,0,0,0,.19-.71.58.58,0,0,0-.19-.19l-2.19-1.27a1.94,1.94,0,0,0-2,0L57.17,98a.49.49,0,0,0-.26.66s0,0,0,0a.41.41,0,0,0,.18.21ZM62,96.13l2.2,1.26a1.92,1.92,0,0,0,1.94,0l2.2-1.26a.52.52,0,0,0,.19-.71.58.58,0,0,0-.19-.19L66.15,94a1.92,1.92,0,0,0-1.94,0L62,95.23a.5.5,0,0,0-.26.66l0,0a.46.46,0,0,0,.18.21Zm4.86-2.8,2.19,1.26a1.92,1.92,0,0,0,1.94,0l2.2-1.26a.54.54,0,0,0,.17-.74.46.46,0,0,0-.17-.17L71,91.16a1.92,1.92,0,0,0-1.94,0l-2.19,1.26a.52.52,0,0,0-.23.71.55.55,0,0,0,.17.2Zm4.85-2.81,2.2,1.27a1.92,1.92,0,0,0,1.94,0L78,90.54a.51.51,0,0,0,.21-.68l0,0a.44.44,0,0,0-.19-.19l-2.19-1.26a1.92,1.92,0,0,0-1.94,0l-2.2,1.26a.51.51,0,0,0-.24.67l0,0a.49.49,0,0,0,.18.2Zm4.86-2.8L78.77,89a1.92,1.92,0,0,0,1.94,0l2.2-1.27A.52.52,0,0,0,83.1,87a.58.58,0,0,0-.19-.19l-2.2-1.27a2,2,0,0,0-1.94,0L76.56,86.8a.5.5,0,0,0-.26.66l0,0a.46.46,0,0,0,.18.21Zm4.85-2.8,2.2,1.27a1.92,1.92,0,0,0,1.94,0l2.19-1.27a.52.52,0,0,0,.19-.71.58.58,0,0,0-.19-.19l-2.19-1.27a2,2,0,0,0-1.94,0L81.43,84a.5.5,0,0,0-.26.66l0,0a.46.46,0,0,0,.18.21Zm4.84-2.79,2.2,1.26a1.92,1.92,0,0,0,1.94,0l6.81-3.92a.54.54,0,0,0,.17-.74.46.46,0,0,0-.17-.17L95,77.3a1.92,1.92,0,0,0-1.94,0l-6.81,3.93a.5.5,0,0,0-.26.66l0,0a.46.46,0,0,0,.18.21Zm-53.58,36.6,2.2,1.27a2,2,0,0,0,1.94,0l3.84-2.2a.53.53,0,0,0,.19-.71.69.69,0,0,0-.19-.19l-2.2-1.27a1.92,1.92,0,0,0-1.94,0l-3.83,2.21a.49.49,0,0,0-.26.66s0,0,0,0a.41.41,0,0,0,.18.21Zm6.5-3.74,2.19,1.27a2,2,0,0,0,1.94,0l2.2-1.27a.52.52,0,0,0,.19-.71.58.58,0,0,0-.19-.19l-2.21-1.29a1.92,1.92,0,0,0-1.94,0l-2.2,1.29a.49.49,0,0,0-.26.66s0,0,0,0a.41.41,0,0,0,.18.21Zm4.85-2.8,2.2,1.26a1.92,1.92,0,0,0,1.94,0l2.19-1.26a.52.52,0,0,0,.19-.71.58.58,0,0,0-.19-.19L48.17,110a1.92,1.92,0,0,0-1.94,0L44,111.3a.51.51,0,0,0-.24.67l0,0a.49.49,0,0,0,.18.2Zm4.86-2.8,2.19,1.26a1.92,1.92,0,0,0,1.94,0l2.2-1.26a.54.54,0,0,0,.17-.74.46.46,0,0,0-.17-.17L53,107.22a1.92,1.92,0,0,0-1.94,0l-2.19,1.26a.52.52,0,0,0-.23.71.55.55,0,0,0,.17.2Zm4.85-2.81,2.2,1.27a1.92,1.92,0,0,0,1.94,0L79.48,95.39a.52.52,0,0,0,.19-.71.58.58,0,0,0-.19-.19l-2.19-1.27a1.94,1.94,0,0,0-2,0L53.7,105.68a.5.5,0,0,0-.17.69l0,0a.44.44,0,0,0,.13.14ZM78,92.54l2.2,1.26a1.92,1.92,0,0,0,1.94,0l2.2-1.26a.51.51,0,0,0,.21-.68l0,0a.44.44,0,0,0-.19-.19l-2.2-1.27a1.92,1.92,0,0,0-1.94,0L78,91.64a.5.5,0,0,0-.21.67l0,0a.47.47,0,0,0,.18.19Zm4.86-2.8L85.06,91A1.92,1.92,0,0,0,87,91l2.2-1.26a.52.52,0,0,0,0-.91L87,87.57a1.92,1.92,0,0,0-1.94,0l-2.19,1.26a.53.53,0,0,0-.05,1Zm4.85-2.81,2.2,1.27a1.92,1.92,0,0,0,1.94,0l2.19-1.27a.51.51,0,0,0,.21-.68l0,0a.44.44,0,0,0-.19-.19l-2.19-1.26a1.92,1.92,0,0,0-1.94,0L87.72,86a.51.51,0,0,0-.34.62.54.54,0,0,0,.29.32Zm4.91-2.83,2.2,1.27a2,2,0,0,0,1.94,0l5.36-3.1a.51.51,0,0,0,.21-.68l0,0a.44.44,0,0,0-.19-.19L99.94,80.1a2,2,0,0,0-1.94,0l-5.37,3.1a.51.51,0,0,0-.34.62.54.54,0,0,0,.29.32ZM59,109.65l12.15,7a1.92,1.92,0,0,0,1.94,0l21.57-12.46a.52.52,0,0,0,.21-.68l0,0a.54.54,0,0,0-.19-.18l-12.15-7a2,2,0,0,0-1.94,0L59,108.75a.52.52,0,0,0-.19.72.49.49,0,0,0,.2.18Z', + fill: 'black', + stroke: 'red', + }); + layer.add(path); + var rect = path.getClientRect(); + + var back = new Konva.Rect({ + x: rect.x, + y: rect.y, + width: rect.width, + height: rect.height, + stroke: 'red', + }); + layer.add(back); + layer.draw(); + + assertAlmostDeepEqual(rect, { + x: 8.6440882161882, + y: 65.75902834, + width: 94.74182356762, + height: 55.4919433, + }); + }); + + it('getClientRect of another complex path', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var path = new Konva.Path({ + x: 50, + y: 50, + data: 'M0,29 C71,-71,142,128,213,29 L213,207 C142,307,71,108,0,207 L0,29 Z', + fill: 'black', + stroke: 'red', + scaleY: 0.3, + }); + layer.add(path); + var rect = path.getClientRect(); + + var back = new Konva.Rect({ + x: rect.x, + y: rect.y, + width: rect.width, + height: rect.height, + stroke: 'red', + }); + layer.add(back); + layer.draw(); + + assertAlmostDeepEqual(rect, { + x: 49, + y: 49.7086649, + width: 215, + height: 71.3826701999, + }); + }); + + it('getClientRect of one more path', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var path = new Konva.Path({ + x: 50, + y: 50, + data: 'M25.21,2.36C22.11,6.1,19,10.17,22.1,15.52a2.14,2.14,0,0,1,.22.69c.18,1.09-.52,1.31-1.31,1.11C19.88,17,19.29,16,18.55,15.21a12.71,12.71,0,0,0-7.82-4.28c-3.24-.42-7.9,1.26-9,3.68-2.24,5-2.64,10.23.66,14.94a26,26,0,0,0,11.57,9c6.17,2.56,12.6,4.45,18.67,7.28,1.33.62,1.67-.14,2.11-1.12,3.84-8.44,5.64-17.32,6-28.25.53-3.82-1.37-8.64-4.3-13.12C33.91-.58,28.2-1.24,25.21,2.36Z', + fill: 'black', + stroke: 'red', + }); + layer.add(path); + var rect = path.getClientRect(); + + var back = new Konva.Rect({ + x: rect.x, + y: rect.y, + width: rect.width, + height: rect.height, + stroke: 'red', + }); + layer.add(back); + layer.draw(); + + assertAlmostDeepEqual(rect, { + x: 48.981379, + y: 48.996825, + width: 42.84717526, + height: 48.057550000000006, + }); + }); + + it('getClientRect for arc', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var path = new Konva.Path({ + data: 'M -12274.95703125 17975.16015625 C -12271.4072265625 17975.16015625 -12268.017578125 17974.345703125 -12264.8837890625 17972.740234375 C -12261.892578125 17971.208984375 -12259.24609375 17968.97265625 -12257.2314453125 17966.2734375 L -12256.775390625 17965.662109375 L -12256.0654296875 17965.939453125 C -12253.494140625 17966.947265625 -12250.7783203125 17967.45703125 -12247.9921875 17967.45703125 C -12245.01171875 17967.45703125 -12242.1201171875 17966.873046875 -12239.396484375 17965.720703125 C -12236.765625 17964.607421875 -12234.4013671875 17963.013671875 -12232.3701171875 17960.982421875 C -12230.3388671875 17958.953125 -12228.7431640625 17956.587890625 -12227.62890625 17953.95703125 C -12226.4755859375 17951.232421875 -12225.890625 17948.337890625 -12225.890625 17945.35546875 C -12225.890625 17941.30859375 -12226.99609375 17937.349609375 -12229.0888671875 17933.90625 C -12231.12109375 17930.5625 -12234.01171875 17927.802734375 -12237.447265625 17925.927734375 L -12237.849609375 17925.708984375 L -12237.9462890625 17925.26171875 C -12238.99609375 17920.408203125 -12241.708984375 17915.994140625 -12245.5830078125 17912.83203125 C -12247.5146484375 17911.2578125 -12249.6748046875 17910.029296875 -12252.0068359375 17909.18359375 C -12254.41796875 17908.306640625 -12256.9541015625 17907.86328125 -12259.54296875 17907.86328125 C -12263.171875 17907.86328125 -12266.7568359375 17908.75390625 -12269.9111328125 17910.44140625 L -12270.556640625 17910.78515625 L -12271.0810546875 17910.275390625 C -12275.2353515625 17906.2265625 -12280.7138671875 17903.99609375 -12286.5078125 17903.99609375 C -12288.9462890625 17903.99609375 -12291.34375 17904.390625 -12293.630859375 17905.171875 C -12295.84375 17905.92578125 -12297.916015625 17907.0234375 -12299.791015625 17908.4375 C -12301.646484375 17909.8359375 -12303.2646484375 17911.509765625 -12304.599609375 17913.41015625 C -12305.9541015625 17915.3359375 -12306.984375 17917.44921875 -12307.6640625 17919.69140625 L -12307.8193359375 17920.203125 L -12308.3310546875 17920.359375 C -12310.5712890625 17921.0390625 -12312.6826171875 17922.068359375 -12314.6044921875 17923.421875 C -12316.501953125 17924.755859375 -12318.1708984375 17926.37109375 -12319.56640625 17928.224609375 C -12322.466796875 17932.078125 -12324 17936.671875 -12324 17941.51171875 C -12324 17944.498046875 -12323.416015625 17947.392578125 -12322.2646484375 17950.1171875 C -12321.15234375 17952.75 -12319.55859375 17955.11328125 -12317.529296875 17957.142578125 C -12315.5 17959.171875 -12313.1376953125 17960.765625 -12310.505859375 17961.876953125 C -12307.7822265625 17963.029296875 -12304.8876953125 17963.61328125 -12301.90234375 17963.61328125 C -12299.7900390625 17963.61328125 -12297.71875 17963.322265625 -12295.744140625 17962.74609375 L -12294.95703125 17962.517578125 L -12294.578125 17963.24609375 C -12292.7373046875 17966.78125 -12289.9716796875 17969.759765625 -12286.580078125 17971.861328125 C -12283.095703125 17974.01953125 -12279.076171875 17975.16015625 -12274.95703125 17975.16015625 M -12274.95703125 17976.16015625 C -12283.8671875 17976.16015625 -12291.609375 17971.11328125 -12295.46484375 17963.70703125 C -12297.50390625 17964.30078125 -12299.66796875 17964.61328125 -12301.90234375 17964.61328125 C -12314.6640625 17964.61328125 -12325 17954.27734375 -12325 17941.51171875 C -12325 17931.08203125 -12318.1015625 17922.27734375 -12308.62109375 17919.40234375 C -12305.74609375 17909.91015625 -12296.92578125 17902.99609375 -12286.5078125 17902.99609375 C -12280.234375 17902.99609375 -12274.54296875 17905.50390625 -12270.3828125 17909.55859375 C -12267.15234375 17907.83203125 -12263.46484375 17906.86328125 -12259.54296875 17906.86328125 C -12248.48046875 17906.86328125 -12239.21875 17914.65234375 -12236.96875 17925.05078125 C -12229.78125 17928.97265625 -12224.890625 17936.58984375 -12224.890625 17945.35546875 C -12224.890625 17958.11328125 -12235.25 17968.45703125 -12247.9921875 17968.45703125 C -12250.96875 17968.45703125 -12253.81640625 17967.89453125 -12256.4296875 17966.87109375 C -12260.640625 17972.51171875 -12267.37109375 17976.16015625 -12274.95703125 17976.16015625 Z', + fill: 'black', + stroke: 'blue', + strokeWidth: 10, + }); + layer.add(path); + var rect = path.getClientRect(); + + var scale = stage.height() / rect.height / 2; + + path.x(-rect.x * scale); + path.y(-rect.y * scale); + path.scaleX(scale); + path.scaleY(scale); + + rect = path.getClientRect(); + + var back = new Konva.Rect({ + x: rect.x, + y: rect.y, + width: rect.width, + height: rect.height, + stroke: 'red', + }); + layer.add(back); + layer.draw(); + + assertAlmostDeepEqual(rect, { + x: 0, + y: 0, + width: 132.4001878816343, + height: 100, + }); + }); + + it('getClientRect on scaled', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var path = new Konva.Path({ + x: -100, + y: -190, + data: 'M10 10 h10 v10 h-10 z', + fill: 'yellow', + stroke: 'blue', + strokeWidth: 0.1, + scaleX: 20, + scaleY: 20, + }); + layer.add(path); + var rect = path.getClientRect(); + + var back = new Konva.Rect({ + x: rect.x, + y: rect.y, + width: rect.width, + height: rect.height, + stroke: 'red', + }); + layer.add(back); + layer.draw(); + + assertAlmostDeepEqual(rect, { + height: 201.99999999999994, + width: 201.99999999999994, + x: 99, + y: 9, + }); + }); + + it('check arc parsing', function () { + var stage = addStage(); + var layer1 = new Konva.Layer(); + stage.add(layer1); + + const weirdPath = new Konva.Path({ + x: 40, + y: 40, + scale: { x: 5, y: 5 }, + data: + 'M16 5.095c0-2.255-1.88-4.083-4.2-4.083-1.682 0-3.13.964-3.8 2.352' + + 'a4.206 4.206 0 00-3.8-2.352' + // Merged arc command flags (00) + 'C1.88 1.012 0 2.84 0 5.095c0 .066.007.13.01.194H.004c.001.047.01.096.014.143l.013.142c.07.8.321 1.663.824 2.573C2.073 10.354 4.232 12.018 8 15c3.767-2.982 5.926-4.647 7.144-6.854.501-.905.752-1.766.823-2.562.007-.055.012-.11.016-.164.003-.043.012-.088.013-.13h-.006c.003-.066.01-.13.01-.195z', + fill: 'red', + }); + layer1.add(weirdPath); + layer1.draw(); + + const layer2 = new Konva.Layer(); + stage.add(layer2); + + const normalPath = new Konva.Path({ + x: 40, + y: 40, + scale: { x: 5, y: 5 }, + data: + 'M16 5.095c0-2.255-1.88-4.083-4.2-4.083-1.682 0-3.13.964-3.8 2.352' + + 'a4.206 4.206 0 0 0-3.8-2.352' + // Spaced arc command flags (0 0) + 'C1.88 1.012 0 2.84 0 5.095c0 .066.007.13.01.194H.004c.001.047.01.096.014.143l.013.142c.07.8.321 1.663.824 2.573C2.073 10.354 4.232 12.018 8 15c3.767-2.982 5.926-4.647 7.144-6.854.501-.905.752-1.766.823-2.562.007-.055.012-.11.016-.164.003-.043.012-.088.013-.13h-.006c.003-.066.01-.13.01-.195z', + fill: 'red', + }); + layer2.add(normalPath); + layer2.draw(); + + var trace1 = layer1.getContext().getTrace(); + var trace2 = layer2.getContext().getTrace(); + + assert.equal(trace1, trace2); + }); + + it('draw path with fillRule', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + + var path = new Konva.Path({ + data: 'M200,100h100v50z', + fill: '#ccc', + fillRule: 'evenodd', + }); + + layer.add(path); + stage.add(layer); + + const trace = layer.getContext().getTrace(); + + assert.equal( + trace, + 'clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);beginPath();moveTo(200,100);lineTo(300,100);lineTo(300,150);closePath();fillStyle=#ccc;fill(evenodd);restore();' + ); + }); +}); diff --git a/test/unit/PointerEvents-test.ts b/test/unit/PointerEvents-test.ts new file mode 100644 index 000000000..f2d36095b --- /dev/null +++ b/test/unit/PointerEvents-test.ts @@ -0,0 +1,229 @@ +import { assert } from 'chai'; + +import { + addStage, + Konva, + simulatePointerDown, + simulatePointerMove, + simulatePointerUp, +} from './test-utils'; + +describe.skip('PointerEvents', function () { + // ====================================================== + it('pointerdown pointerup pointermove', function (done) { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'red', + stroke: 'black', + strokeWidth: 4, + }); + + // mobile events + var pointerdown = false; + var pointerup = false; + var pointermove = false; + + /* + * mobile + */ + circle.on('pointerdown', function () { + pointerdown = true; + }); + + circle.on('pointerup', function () { + pointerup = true; + }); + + circle.on('pointermove', function () { + pointermove = true; + }); + + layer.add(circle); + stage.add(layer); + + // touchstart circle + simulatePointerDown(stage, { + x: 289, + y: 100, + }); + + assert(pointerdown, '1) pointerdown should be true'); + assert(!pointermove, '1) pointermove should be false'); + assert(!pointerup, '1) pointerup should be false'); + + // pointerup circle + simulatePointerUp(stage, { + x: 289, + y: 100, + }); + + assert(pointerdown, '2) pointerdown should be true'); + assert(!pointermove, '2) pointermove should be false'); + assert(pointerup, '2) pointerup should be true'); + + // pointerdown circle + simulatePointerDown(stage, { + x: 289, + y: 100, + }); + + assert(pointerdown, '3) pointerdown should be true'); + assert(!pointermove, '3) pointermove should be false'); + assert(pointerup, '3) pointerup should be true'); + + // pointerup circle to triger dbltap + simulatePointerUp(stage, { + x: 289, + y: 100, + }); + // end drag is tied to document mouseup and pointerup event + // which can't be simulated. call _endDrag manually + //Konva.DD._endDrag(); + + assert(pointerdown, '4) pointerdown should be true'); + assert(!pointermove, '4) pointermove should be false'); + assert(pointerup, '4) pointerup should be true'); + + setTimeout(function () { + // pointermove circle + simulatePointerMove(stage, { + x: 290, + y: 100, + }); + + assert(pointerdown, '5) pointerdown should be true'); + assert(pointermove, '5) pointermove should be true'); + assert(pointerup, '5) pointerup should be true'); + + done(); + }, 17); + }); + + // ====================================================== + it.skip('pointer capture', function (done) { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'red', + stroke: 'black', + strokeWidth: 4, + }); + + var circle2 = new Konva.Circle({ + x: stage.width() / 2, + y: 20, + radius: 20, + fill: 'red', + stroke: 'black', + strokeWidth: 4, + }); + + // mobile events + var downCount = 0; + var otherDownCount = 0; + + var pointerup = false; + var pointermove = false; + + circle2.on('pointerdown', function () { + otherDownCount++; + }); + + circle.on('pointerdown', function (event) { + downCount++; + this.setPointerCapture(event['pointerId']); + }); + + circle.on('pointerup', function (evt) { + assert( + this.hasPointerCapture(evt['pointerId']), + 'circle released capture' + ); + pointerup = true; + }); + + circle.on('pointermove', function (evt) { + assert(this.hasPointerCapture(evt['pointerId']), 'circle has capture'); + pointermove = true; + }); + + layer.add(circle); + layer.add(circle2); + stage.add(layer); + + // on circle 2 to confirm it works + simulatePointerDown(stage, { + x: 289, + y: 10, + pointerId: 0, + preventDefault: function () {}, + }); + + assert.equal(otherDownCount, 1, '6) otherDownCount should be 1'); + assert(downCount === 0, '6) downCount should be 0'); + assert(!pointermove, '6) pointermove should be false'); + assert(!pointerup, '6) pointerup should be false'); + + // on circle with capture + simulatePointerDown(stage, { + x: 289, + y: 100, + pointerId: 1, + preventDefault: function () {}, + }); + + assert.equal(otherDownCount, 1, '7) otherDownCount should be 1'); + assert(downCount === 1, '7) downCount should be 1'); + assert(!pointermove, '7) pointermove should be false'); + assert(!pointerup, '7) pointerup should be true'); + + // second pointerdown + simulatePointerDown(stage, { + x: 289, + y: 10, + pointerId: 2, + preventDefault: function () {}, + }); + + assert.equal(otherDownCount, 1, '8) otherDownCount should be 1'); + assert(downCount === 2, '8) pointerdown should be 2'); + assert(!pointermove, '8) pointermove should be false'); + assert(!pointerup, '8) pointerup should be true'); + + setTimeout(function () { + // pointermove over circle 2 + simulatePointerMove(stage, { + x: 290, + y: 10, + pointerId: 1, + }); + + assert(otherDownCount === 1, '9) otherDownCount should be 1'); + assert(pointermove, '9) pointermove should be true'); + + simulatePointerUp(stage, { + pointerId: 1, + preventDefault: function () {}, + }); + + simulatePointerDown(stage, { + x: 289, + y: 10, + pointerId: 1, + preventDefault: function () {}, + }); + + assert(otherDownCount === 2, '10) otherDownCount should be 1'); + assert(pointerup, '10) pointerup should be true'); + + done(); + }, 17); + }); +}); diff --git a/test/unit/Polygon-test.ts b/test/unit/Polygon-test.ts new file mode 100644 index 000000000..d1b6e5769 --- /dev/null +++ b/test/unit/Polygon-test.ts @@ -0,0 +1,25 @@ +import { assert } from 'chai'; + +import { addStage, Konva } from './test-utils'; + +describe('Polygon', function () { + it('add polygon', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + var points = [73, 192, 73, 160, 340, 23, 500, 109, 499, 139, 342, 93]; + + var poly = new Konva.Line({ + points: points, + fill: 'green', + stroke: 'blue', + strokeWidth: 5, + closed: true, + }); + + layer.add(poly); + stage.add(layer); + + assert.equal(poly.getClassName(), 'Line'); + }); +}); diff --git a/test/unit/Rect-test.ts b/test/unit/Rect-test.ts new file mode 100644 index 000000000..308a61888 --- /dev/null +++ b/test/unit/Rect-test.ts @@ -0,0 +1,237 @@ +import { assert } from 'chai'; + +import { + addStage, + Konva, + createCanvas, + compareLayerAndCanvas, +} from './test-utils'; + +describe('Rect', function () { + // ====================================================== + it('add rect to stage', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + + var rect = new Konva.Rect({ + x: 100, + y: 50, + width: 100, + height: 50, + fill: 'green', + stroke: 'blue', + }); + + layer.add(rect); + stage.add(layer); + + assert.equal(rect.x(), 100); + assert.equal(rect.y(), 50); + + var trace = layer.getContext().getTrace(); + //console.log(trace); + assert.equal( + trace, + 'clearRect(0,0,578,200);save();transform(1,0,0,1,100,50);beginPath();rect(0,0,100,50);closePath();fillStyle=green;fill();lineWidth=2;strokeStyle=blue;stroke();restore();' + ); + + var relaxedTrace = layer.getContext().getTrace(true); + //console.log(relaxedTrace); + assert.equal( + relaxedTrace, + 'clearRect();save();transform();beginPath();rect();closePath();fillStyle;fill();lineWidth;strokeStyle;stroke();restore();' + ); + }); + + // ====================================================== + it('add rect with shadow, corner radius, and opacity', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + + var rect = new Konva.Rect({ + x: 100, + y: 50, + width: 100, + height: 50, + fill: 'green', + stroke: 'blue', + shadowColor: 'red', + shadowBlur: 10, + shadowOffset: { x: 5, y: 5 }, + shadowOpacity: 0.5, + opacity: 0.4, + cornerRadius: 5, + }); + + layer.add(rect); + stage.add(layer); + + assert.equal(rect.shadowColor(), 'red'); + assert.equal(rect.shadowBlur(), 10); + assert.equal(rect.shadowOffsetX(), 5); + assert.equal(rect.shadowOffsetY(), 5); + assert.equal(rect.shadowOpacity(), 0.5); + assert.equal(rect.opacity(), 0.4); + assert.equal(rect.cornerRadius(), 5); + }); + + // ====================================================== + it('draw rect', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var rect = new Konva.Rect({ + x: 200, + y: 90, + width: 100, + height: 50, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + scale: { + x: 2, + y: 2, + }, + cornerRadius: 15, + draggable: true, + }); + + layer.add(rect); + stage.add(layer); + + assert.equal(rect.getClassName(), 'Rect'); + }); + + // ====================================================== + it('add fill stroke rect', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var rect = new Konva.Rect({ + x: 200, + y: 100, + width: 100, + height: 50, + fill: 'blue', + stroke: 'green', + strokeWidth: 4, + }); + + layer.add(rect); + stage.add(layer); + + var canvas = createCanvas(); + var context = canvas.getContext('2d'); + context.beginPath(); + context.rect(200, 100, 100, 50); + context.fillStyle = 'blue'; + context.fill(); + context.lineWidth = 4; + context.strokeStyle = 'green'; + context.stroke(); + + compareLayerAndCanvas(layer, canvas); + }); + + // ====================================================== + it('add stroke rect', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var rect = new Konva.Rect({ + x: 200, + y: 100, + width: 100, + height: 50, + stroke: 'green', + strokeWidth: 4, + }); + + layer.add(rect); + stage.add(layer); + + var canvas = createCanvas(); + var context = canvas.getContext('2d'); + context.beginPath(); + context.rect(200, 100, 100, 50); + context.lineWidth = 4; + context.strokeStyle = 'green'; + context.stroke(); + + compareLayerAndCanvas(layer, canvas); + }); + + // ====================================================== + it('use default stroke width (stroke width should be 2)', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var rect = new Konva.Rect({ + x: 200, + y: 100, + width: 100, + height: 50, + stroke: 'blue', + }); + + layer.add(rect); + stage.add(layer); + + var canvas = createCanvas(); + var context = canvas.getContext('2d'); + context.beginPath(); + context.rect(200, 100, 100, 50); + context.lineWidth = 2; + context.strokeStyle = 'blue'; + context.stroke(); + compareLayerAndCanvas(layer, canvas); + }); + + // ====================================================== + it('limit corner radius', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var rect = new Konva.Rect({ + x: 50, + y: 50, + width: 100, + height: 100, + fill: 'black', + cornerRadius: 100, + }); + + layer.add(rect); + stage.add(layer); + + // as corner radius is much bigger we should have circe in the result + var canvas = createCanvas(); + var context = canvas.getContext('2d'); + context.beginPath(); + context.arc(100, 100, 50, 0, Math.PI * 2); + context.fillStyle = 'black'; + context.fill(); + compareLayerAndCanvas(layer, canvas, 100); + }); + + // ====================================================== + it('array for corner radius', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var rect = new Konva.Rect({ + x: 50, + y: 50, + width: 100, + height: 100, + fill: 'black', + cornerRadius: [0, 10, 20, 30], + }); + + layer.add(rect); + stage.add(layer); + layer.draw(); + + var trace = layer.getContext().getTrace(); + assert.equal( + trace, + 'clearRect(0,0,578,200);save();transform(1,0,0,1,50,50);beginPath();moveTo(0,0);lineTo(90,0);arc(90,10,10,4.712,0,false);lineTo(100,80);arc(80,80,20,0,1.571,false);lineTo(30,100);arc(30,70,30,1.571,3.142,false);lineTo(0,0);arc(0,0,0,3.142,4.712,false);closePath();fillStyle=black;fill();restore();clearRect(0,0,578,200);save();transform(1,0,0,1,50,50);beginPath();moveTo(0,0);lineTo(90,0);arc(90,10,10,4.712,0,false);lineTo(100,80);arc(80,80,20,0,1.571,false);lineTo(30,100);arc(30,70,30,1.571,3.142,false);lineTo(0,0);arc(0,0,0,3.142,4.712,false);closePath();fillStyle=black;fill();restore();' + ); + }); +}); diff --git a/test/unit/RegularPolygon-test.ts b/test/unit/RegularPolygon-test.ts new file mode 100644 index 000000000..8e996f805 --- /dev/null +++ b/test/unit/RegularPolygon-test.ts @@ -0,0 +1,209 @@ +import { assert } from 'chai'; + +import { + addStage, + Konva, + cloneAndCompareLayer, + assertAlmostEqual, +} from './test-utils'; + +describe('RegularPolygon', function () { + // ====================================================== + it('add regular polygon triangle', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + + var poly = new Konva.RegularPolygon({ + x: 200, + y: 100, + sides: 3, + radius: 50, + fill: 'green', + stroke: 'blue', + strokeWidth: 5, + name: 'foobar', + center: { + x: 0, + y: -50, + }, + }); + + layer.add(poly); + stage.add(layer); + + assert.equal(poly.getClassName(), 'RegularPolygon'); + }); + + // ====================================================== + it('add regular polygon square', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var poly = new Konva.RegularPolygon({ + x: 200, + y: 100, + sides: 4, + radius: 50, + fill: 'green', + stroke: 'blue', + strokeWidth: 5, + name: 'foobar', + }); + + layer.add(poly); + stage.add(layer); + + var trace = layer.getContext().getTrace(); + + assert.equal( + trace, + 'clearRect(0,0,578,200);save();transform(1,0,0,1,200,100);beginPath();moveTo(0,-50);lineTo(50,0);lineTo(0,50);lineTo(-50,0);closePath();fillStyle=green;fill();lineWidth=5;strokeStyle=blue;stroke();restore();' + ); + }); + + // ====================================================== + it('add regular polygon pentagon', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var poly = new Konva.RegularPolygon({ + x: 200, + y: 100, + sides: 5, + radius: 50, + fill: 'green', + stroke: 'blue', + strokeWidth: 5, + name: 'foobar', + }); + + layer.add(poly); + stage.add(layer); + + var trace = layer.getContext().getTrace(); + + assert.equal( + trace, + 'clearRect(0,0,578,200);save();transform(1,0,0,1,200,100);beginPath();moveTo(0,-50);lineTo(47.553,-15.451);lineTo(29.389,40.451);lineTo(-29.389,40.451);lineTo(-47.553,-15.451);closePath();fillStyle=green;fill();lineWidth=5;strokeStyle=blue;stroke();restore();' + ); + }); + + // ====================================================== + it('add regular polygon octogon', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var poly = new Konva.RegularPolygon({ + x: 200, + y: 100, + sides: 8, + radius: 50, + fill: 'green', + stroke: 'blue', + strokeWidth: 5, + name: 'foobar', + }); + + layer.add(poly); + stage.add(layer); + + var trace = layer.getContext().getTrace(); + + assert.equal( + trace, + 'clearRect(0,0,578,200);save();transform(1,0,0,1,200,100);beginPath();moveTo(0,-50);lineTo(35.355,-35.355);lineTo(50,0);lineTo(35.355,35.355);lineTo(0,50);lineTo(-35.355,35.355);lineTo(-50,0);lineTo(-35.355,-35.355);closePath();fillStyle=green;fill();lineWidth=5;strokeStyle=blue;stroke();restore();' + ); + }); + + // ====================================================== + it('attr sync', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var poly = new Konva.RegularPolygon({ + x: 200, + y: 100, + sides: 5, + radius: 50, + fill: 'green', + stroke: 'blue', + strokeWidth: 5, + name: 'foobar', + }); + + layer.add(poly); + stage.add(layer); + + assert.equal(poly.getWidth(), 100); + assert.equal(poly.getHeight(), 100); + + poly.setWidth(120); + assert.equal(poly.radius(), 60); + assert.equal(poly.getHeight(), 120); + + poly.setHeight(140); + assert.equal(poly.radius(), 70); + assert.equal(poly.getHeight(), 140); + }); + + it('polygon cache', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var poly = new Konva.RegularPolygon({ + x: 200, + y: 100, + sides: 5, + radius: 50, + fill: 'green', + stroke: 'black', + strokeWidth: 5, + name: 'foobar', + }); + poly.cache(); + layer.add(poly); + stage.add(layer); + + assert.deepEqual(poly.getSelfRect(), { + x: -47.55282581475768, + y: -50, + height: 90.45084971874738, + width: 95.10565162951536, + }); + + cloneAndCompareLayer(layer, 254); + }); + + it('triangle - bounding box', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + stage.add(layer); + + var poly = new Konva.RegularPolygon({ + x: 200, + y: 100, + sides: 3, + radius: 50, + fill: 'green', + stroke: 'blue', + strokeWidth: 5, + name: 'foobar', + }); + + layer.add(poly); + + var tr = new Konva.Transformer({ + nodes: [poly], + }); + layer.add(tr); + + layer.draw(); + + var box = poly.getClientRect(); + + assertAlmostEqual(box.width, 91.60254037844388); + assertAlmostEqual(box.height, 80.00000000000003); + }); +}); diff --git a/test/unit/Ring-test.ts b/test/unit/Ring-test.ts new file mode 100644 index 000000000..7abe4f756 --- /dev/null +++ b/test/unit/Ring-test.ts @@ -0,0 +1,92 @@ +import { assert } from 'chai'; + +import { addStage, Konva, compareLayers } from './test-utils'; + +describe('Ring', function () { + // ====================================================== + it('add ring', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var ring = new Konva.Ring({ + x: stage.width() / 2, + y: stage.height() / 2, + innerRadius: 50, + outerRadius: 90, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + draggable: true, + }); + layer.add(ring); + stage.add(layer); + assert.equal(ring.getClassName(), 'Ring'); + + var trace = layer.getContext().getTrace(); + assert.equal( + trace, + 'clearRect(0,0,578,200);save();transform(1,0,0,1,289,100);beginPath();arc(0,0,50,0,6.283,false);moveTo(90,0);arc(0,0,90,6.283,0,true);closePath();fillStyle=green;fill();lineWidth=4;strokeStyle=black;stroke();restore();' + ); + }); + + // ====================================================== + it('ring attrs sync', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var ring = new Konva.Ring({ + name: 'ring', + x: 30, + y: 50, + innerRadius: 15, + outerRadius: 30, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + draggable: true, + }); + layer.add(ring); + stage.add(layer); + + assert(ring.width(), 60); + assert(ring.height(), 60); + + ring.height(100); + assert(ring.width(), 100); + assert(ring.outerRadius(), 50); + + ring.width(120); + assert(ring.height(), 120); + assert(ring.outerRadius(), 60); + }); + + it('ring cache', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var ring = new Konva.Ring({ + name: 'ring', + x: 30, + y: 50, + innerRadius: 15, + outerRadius: 30, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + draggable: true, + }); + + layer.add(ring); + stage.add(layer); + + assert.deepEqual(ring.getSelfRect(), { + x: -30, + y: -30, + width: 60, + height: 60, + }); + + var layer2 = layer.clone(); + stage.add(layer2); + layer2.hide(); + + compareLayers(layer, layer2); + }); +}); diff --git a/test/unit/Shape-test.ts b/test/unit/Shape-test.ts new file mode 100644 index 000000000..26758dda7 --- /dev/null +++ b/test/unit/Shape-test.ts @@ -0,0 +1,2338 @@ +import { assert } from 'chai'; + +import { + addStage, + simulateMouseDown, + simulateMouseMove, + simulateMouseUp, + createCanvas, + isBrowser, + isNode, + compareLayerAndCanvas, + compareLayers, + loadImage, + Konva, + compareCanvases, +} from './test-utils'; + +describe('Shape', function () { + // ====================================================== + it('test intersects()', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var rect = new Konva.Rect({ + x: 200, + y: 100, + width: 100, + height: 50, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + }); + + layer.add(rect); + stage.add(layer); + + assert.equal( + rect.intersects({ + x: 201, + y: 101, + }), + true, + '(201,101) should intersect the shape' + ); + + assert.equal( + rect.intersects({ + x: 197, + y: 97, + }), + false, + '(197, 97) should not intersect the shape' + ); + + assert.equal( + rect.intersects({ + x: 250, + y: 125, + }), + true, + '(250, 125) should intersect the shape' + ); + + assert.equal( + rect.intersects({ + x: 300, + y: 150, + }), + true, + '(300, 150) should intersect the shape' + ); + + assert.equal( + rect.intersects({ + x: 303, + y: 153, + }), + false, + '(303, 153) should not intersect the shape' + ); + }); + + // ====================================================== + it('test hasShadow() method', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var shape = new Konva.Shape({ + sceneFunc: function (context) { + context.beginPath(); + context.moveTo(0, 0); + context.lineTo(100, 0); + context.lineTo(100, 100); + context.closePath(); + context.fillStrokeShape(this); + }, + x: 10, + y: 10, + fill: 'green', + stroke: 'blue', + strokeWidth: 5, + shadowColor: 'black', + shadowOffsetX: 10, + shadowOpacity: 0, + }); + + layer.add(shape); + stage.add(layer); + + assert.equal( + shape.hasShadow(), + false, + 'shape should not have a shadow because opacity is 0' + ); + + shape.shadowOpacity(0.5); + + assert.equal( + shape.hasShadow(), + true, + 'shape should have a shadow because opacity is nonzero' + ); + + shape.shadowEnabled(false); + + assert.equal( + shape.hasShadow(), + false, + 'shape should not have a shadow because it is not enabled' + ); + }); + + // ====================================================== + it('custom shape with fill, stroke, and strokeWidth', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var shape = new Konva.Shape({ + sceneFunc: function (context) { + context.beginPath(); + context.moveTo(0, 0); + context.lineTo(100, 0); + context.lineTo(100, 100); + context.closePath(); + context.fillStrokeShape(this); + }, + x: 200, + y: 100, + fill: 'green', + stroke: 'blue', + strokeWidth: 5, + }); + + layer.add(shape); + stage.add(layer); + + var trace = layer.getContext().getTrace(); + + assert.equal( + trace, + 'clearRect(0,0,578,200);save();transform(1,0,0,1,200,100);beginPath();moveTo(0,0);lineTo(100,0);lineTo(100,100);closePath();fillStyle=green;fill();lineWidth=5;strokeStyle=blue;stroke();restore();' + ); + }); + + // ====================================================== + it('add star with translated, scaled, rotated fill', function (done) { + loadImage('darth-vader.jpg', (imageObj) => { + var stage = addStage(); + var layer = new Konva.Layer(); + + var star = new Konva.Star({ + x: 200, + y: 100, + numPoints: 5, + innerRadius: 40, + outerRadius: 70, + + fillPatternImage: imageObj, + fillPatternX: -20, + fillPatternY: -30, + fillPatternScale: { x: 0.5, y: 0.5 }, + fillPatternOffset: { x: 219, y: 150 }, + fillPatternRotation: 90, + fillPatternRepeat: 'no-repeat', + + stroke: 'blue', + strokeWidth: 5, + draggable: true, + }); + + layer.add(star); + stage.add(layer); + + /* + var anim = new Konva.Animation(function() { + star.attrs.fill.rotation += 0.02; + }, layer); + anim.start(); + */ + + assert.equal(star.fillPatternX(), -20, 'star fill x should be -20'); + assert.equal(star.fillPatternY(), -30, 'star fill y should be -30'); + assert.equal( + star.fillPatternScale().x, + 0.5, + 'star fill scale x should be 0.5' + ); + assert.equal( + star.fillPatternScale().y, + 0.5, + 'star fill scale y should be 0.5' + ); + assert.equal( + star.fillPatternOffset().x, + 219, + 'star fill offset x should be 219' + ); + assert.equal( + star.fillPatternOffset().y, + 150, + 'star fill offset y should be 150' + ); + assert.equal( + star.fillPatternRotation(), + 90, + 'star fill rotation should be 90' + ); + + star.fillPatternRotation(180); + + assert.equal( + star.fillPatternRotation(), + 180, + 'star fill rotation should be 180' + ); + + star.fillPatternScale({ x: 1, y: 1 }); + + assert.equal( + star.fillPatternScale().x, + 1, + 'star fill scale x should be 1' + ); + assert.equal( + star.fillPatternScale().y, + 1, + 'star fill scale y should be 1' + ); + + star.fillPatternOffset({ x: 100, y: 120 }); + + assert.equal( + star.fillPatternOffset().x, + 100, + 'star fill offset x should be 100' + ); + assert.equal( + star.fillPatternOffset().y, + 120, + 'star fill offset y should be 120' + ); + + done(); + }); + }); + + // ====================================================== + it('test size setters and getters', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 50, + fill: 'red', + }); + + var ellipse = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 50, + fill: 'yellow', + }); + + layer.add(ellipse); + layer.add(circle); + stage.add(layer); + + // circle tests + assert.equal(circle.width(), 100, 'circle width should be 100'); + assert.equal(circle.height(), 100, 'circle height should be 100'); + assert.equal(circle.getSize().width, 100, 'circle width should be 100'); + assert.equal(circle.getSize().height, 100, 'circle height should be 100'); + assert.equal(circle.radius(), 50, 'circle radius should be 50'); + + circle.setWidth(200); + + assert.equal(circle.width(), 200, 'circle width should be 200'); + assert.equal(circle.height(), 200, 'circle height should be 200'); + assert.equal(circle.getSize().width, 200, 'circle width should be 200'); + assert.equal(circle.getSize().height, 200, 'circle height should be 200'); + assert.equal(circle.radius(), 100, 'circle radius should be 100'); + }); + + // ====================================================== + it('set image fill to color then image then linear gradient then back to image', function (done) { + loadImage('darth-vader.jpg', (imageObj) => { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: 200, + y: 60, + radius: 50, + fill: 'blue', + }); + + layer.add(circle); + stage.add(layer); + + assert.equal(circle.fill(), 'blue', 'circle fill should be blue'); + + circle.fill(null); + circle.fillPatternImage(imageObj); + circle.fillPatternRepeat('no-repeat'); + circle.fillPatternOffset({ x: -200, y: -70 }); + + assert.notEqual( + circle.fillPatternImage(), + undefined, + 'circle fill image should be defined' + ); + assert.equal( + circle.fillPatternRepeat(), + 'no-repeat', + 'circle fill repeat should be no-repeat' + ); + assert.equal( + circle.fillPatternOffset().x, + -200, + 'circle fill offset x should be -200' + ); + assert.equal( + circle.fillPatternOffset().y, + -70, + 'circle fill offset y should be -70' + ); + + circle.fillPatternImage(null); + circle.fillLinearGradientStartPoint({ x: -35, y: -35 }); + circle.fillLinearGradientEndPoint({ x: 35, y: 35 }); + circle.fillLinearGradientColorStops([0, 'red', 1, 'blue']); + + circle.fillLinearGradientStartPoint({ x: 0, y: 0 }); + circle.fillPatternImage(imageObj); + circle.fillPatternRepeat('repeat'); + circle.fillPatternOffset({ x: 0, y: 0 }); + + layer.draw(); + + done(); + }); + }); + + it('stroke gradient', function () { + var stage = addStage(); + var layer = new Konva.Layer({ + scaleY: 1.5, + }); + + var shape = new Konva.Rect({ + x: 10, + y: 10, + width: 100, + height: 100, + fillLinearGradientColorStops: [0, 'yellow', 0.5, 'red', 1, 'white'], + fillLinearGradientStartPoint: { + x: 0, + y: 0, + }, + scaleX: 3, + fillLinearGradientEndPoint: { + x: 100, + y: 100, + }, + strokeLinearGradientColorStops: [0, 'red', 0.5, 'blue', 1, 'green'], + strokeLinearGradientStartPoint: { + x: 0, + y: 0, + }, + strokeLinearGradientEndPoint: { + x: 100, + y: 100, + }, + }); + layer.add(shape); + stage.add(layer); + + if (isNode) { + assert.equal( + layer.getContext().getTrace(true), + 'clearRect();save();transform();beginPath();rect();closePath();fillStyle;fill();lineWidth;createLinearGradient();strokeStyle;stroke();restore();' + ); + } else { + assert.equal( + layer.getContext().getTrace(), + 'clearRect(0,0,578,200);save();transform(3,0,0,1.5,10,15);beginPath();rect(0,0,100,100);closePath();fillStyle=[object CanvasGradient];fill();lineWidth=2;createLinearGradient(0,0,100,100);strokeStyle=[object CanvasGradient];stroke();restore();' + ); + } + }); + + // ====================================================== + it('test enablers and disablers', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + shadowColor: 'black', + shadowBlur: 10, + shadowOffset: { x: 10, y: 10 }, + dash: [10, 10], + scaleX: 3, + }); + layer.add(circle); + stage.add(layer); + + assert.equal(circle.strokeScaleEnabled(), true); + assert.equal(circle.fillEnabled(), true, 'fillEnabled should be true'); + assert.equal(circle.strokeEnabled(), true, 'strokeEnabled should be true'); + assert.equal(circle.shadowEnabled(), true, 'shadowEnabled should be true'); + assert.equal(circle.dashEnabled(), true, 'dashEnabled should be true'); + + circle.strokeScaleEnabled(false); + assert.equal(circle.strokeScaleEnabled(), false); + + layer.draw(); + // var trace = layer.getContext().getTrace(); + + circle.fillEnabled(false); + assert.equal(circle.fillEnabled(), false, 'fillEnabled should be false'); + + circle.strokeEnabled(false); + assert.equal( + circle.strokeEnabled(), + false, + 'strokeEnabled should be false' + ); + + circle.shadowEnabled(false); + assert.equal( + circle.shadowEnabled(), + false, + 'shadowEnabled should be false' + ); + + circle.dashEnabled(false); + assert.equal(circle.dashEnabled(), false, 'dashEnabled should be false'); + + // re-enable + + circle.dashEnabled(true); + assert.equal(circle.dashEnabled(), true, 'dashEnabled should be true'); + + circle.shadowEnabled(true); + assert.equal(circle.shadowEnabled(), true, 'shadowEnabled should be true'); + + circle.strokeEnabled(true); + assert.equal(circle.strokeEnabled(), true, 'strokeEnabled should be true'); + + circle.fillEnabled(true); + assert.equal(circle.fillEnabled(), true, 'fillEnabled should be true'); + }); + + // ====================================================== + it('fill with shadow and opacity', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + + var rect = new Konva.Rect({ + x: 100, + y: 50, + width: 100, + height: 50, + fill: 'green', + opacity: 0.5, + shadowColor: 'black', + shadowBlur: 10, + shadowOffset: { x: 10, y: 10 }, + shadowOpacity: 0.5, + }); + + layer.add(rect); + stage.add(layer); + + assert.equal(rect.x(), 100); + assert.equal(rect.y(), 50); + + var canvas = createCanvas(); + var context = canvas.getContext('2d'); + context.globalAlpha = 0.5; + // rect + context.beginPath(); + context.rect(100, 50, 100, 50); + context.closePath(); + + context.fillStyle = 'green'; + context.shadowColor = 'rgba(0,0,0,0.5)'; + context.shadowBlur = 10 * Konva.pixelRatio; + context.shadowOffsetX = 10 * Konva.pixelRatio; + context.shadowOffsetY = 10 * Konva.pixelRatio; + context.fill(); + + compareLayerAndCanvas(layer, canvas, 30); + + var trace = layer.getContext().getTrace(); + + assert.equal( + trace, + 'clearRect(0,0,578,200);save();transform(1,0,0,1,100,50);globalAlpha=0.5;shadowColor=rgba(0,0,0,0.5);shadowBlur=10;shadowOffsetX=10;shadowOffsetY=10;beginPath();rect(0,0,100,50);closePath();fillStyle=green;fill();restore();' + ); + }); + + // ====================================================== + it('draw fill after stroke', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + + var rect = new Konva.Rect({ + x: 100, + y: 50, + width: 100, + height: 50, + fill: 'green', + stroke: 'red', + strokeWidth: 10, + fillAfterStrokeEnabled: true, + }); + + layer.add(rect); + stage.add(layer); + + var trace = layer.getContext().getTrace(); + + assert.equal( + trace, + 'clearRect(0,0,578,200);save();transform(1,0,0,1,100,50);beginPath();rect(0,0,100,50);closePath();lineWidth=10;strokeStyle=red;stroke();fillStyle=green;fill();restore();' + ); + }); + + // ====================================================== + it('test strokeWidth = 0', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + + var rect = new Konva.Rect({ + x: 100, + y: 50, + width: 100, + height: 50, + fill: 'green', + strokeWidth: 0, + stroke: 'black', + }); + + layer.add(rect); + stage.add(layer); + + var canvas = createCanvas(); + var context = canvas.getContext('2d'); + context.beginPath(); + context.rect(100, 50, 100, 50); + context.closePath(); + + context.fillStyle = 'green'; + context.fill(); + var trace = layer.getContext().getTrace(); + + compareLayerAndCanvas(layer, canvas, 30); + + assert.equal( + trace, + 'clearRect(0,0,578,200);save();transform(1,0,0,1,100,50);beginPath();rect(0,0,100,50);closePath();fillStyle=green;fill();restore();' + ); + }); + + // ====================================================== + it('stroke with shadow and opacity', function () { + Konva.pixelRatio = 1; + var stage = addStage(); + + var layer = new Konva.Layer(); + + var rect = new Konva.Rect({ + x: 100, + y: 50, + width: 100, + height: 50, + stroke: 'red', + strokeWidth: 20, + opacity: 0.5, + shadowColor: 'black', + shadowBlur: 10, + shadowOffset: { x: 10, y: 10 }, + shadowOpacity: 0.5, + }); + + layer.add(rect); + stage.add(layer); + + assert.equal(rect.x(), 100); + assert.equal(rect.y(), 50); + + var canvas = createCanvas(); + var context = canvas.getContext('2d'); + context.globalAlpha = 0.5; + // rect + context.beginPath(); + context.rect(100, 50, 100, 50); + context.closePath(); + + context.strokeStyle = 'red'; + context.lineWidth = 20; + + context.shadowColor = 'rgba(0,0,0,0.5)'; + context.shadowBlur = 10 * Konva.pixelRatio; + context.shadowOffsetX = 10 * Konva.pixelRatio; + context.shadowOffsetY = 10 * Konva.pixelRatio; + context.stroke(); + + compareLayerAndCanvas(layer, canvas, 10); + + var trace = layer.getContext().getTrace(); + //console.log(trace); + assert.equal( + trace, + 'clearRect(0,0,578,200);save();transform(1,0,0,1,100,50);globalAlpha=0.5;shadowColor=rgba(0,0,0,0.5);shadowBlur=10;shadowOffsetX=10;shadowOffsetY=10;beginPath();rect(0,0,100,50);closePath();lineWidth=20;strokeStyle=red;stroke();restore();' + ); + }); + + // ====================================================== + it('fill and stroke with opacity', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + + var rect = new Konva.Rect({ + x: 100, + y: 50, + width: 100, + height: 50, + fill: 'green', + stroke: 'black', + strokeWidth: 10, + opacity: 0.5, + }); + + layer.add(rect); + + stage.add(layer); + + var canvas = createCanvas(); + var context = canvas.getContext('2d'); + context.globalAlpha = 0.5; + // stroke + context.beginPath(); + context.moveTo(100, 50); + context.lineTo(200, 50); + context.lineTo(200, 100); + context.lineTo(100, 100); + context.closePath(); + context.lineWidth = 10; + context.strokeStyle = 'black'; + context.stroke(); + + // rect + context.fillStyle = 'green'; + context.fillRect(105, 55, 90, 40); + + compareLayerAndCanvas(layer, canvas, 10); + }); + + // ====================================================== + it('fill and stroke with shadow', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + + var rect = new Konva.Rect({ + x: 100, + y: 50, + width: 100, + height: 50, + fill: 'green', + stroke: 'black', + strokeWidth: 10, + shadowColor: 'grey', + shadowBlur: 10, + shadowOffset: { + x: 20, + y: 20, + }, + }); + + layer.add(rect); + stage.add(layer); + + var canvas = createCanvas(); + var context = canvas.getContext('2d'); + context.beginPath(); + context.rect(100, 50, 100, 50); + context.closePath(); + context.fillStyle = 'green'; + + context.lineWidth = 10; + context.fill(); + context.stroke(); + + var c2 = createCanvas(); + var ctx2 = c2.getContext('2d'); + ctx2.shadowColor = 'grey'; + ctx2.shadowBlur = 10 * Konva.pixelRatio; + ctx2.shadowOffsetX = 20 * Konva.pixelRatio; + ctx2.shadowOffsetY = 20 * Konva.pixelRatio; + + ctx2.drawImage(canvas, 0, 0, canvas.width / 2, canvas.height / 2); + + // compareLayerAndCanvas(layer, c2, 50); + + if (isBrowser) { + var trace = layer.getContext().getTrace(); + assert.equal( + trace, + 'clearRect(0,0,578,200);save();shadowColor=rgba(128,128,128,1);shadowBlur=10;shadowOffsetX=20;shadowOffsetY=20;drawImage([object HTMLCanvasElement],0,0,578,200);restore();' + ); + } else { + var trace = layer.getContext().getTrace(true); + assert.equal( + trace, + 'clearRect();save();shadowColor;shadowBlur;shadowOffsetX;shadowOffsetY;drawImage();restore();' + ); + } + }); + + // ====================================================== + // hard to emulate the same drawing + it('fill and stroke with shadow and opacity', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var rect = new Konva.Rect({ + x: 100, + y: 50, + width: 100, + height: 50, + fill: 'green', + stroke: 'black', + strokeWidth: 10, + shadowColor: 'grey', + opacity: 0.5, + shadowBlur: 5, + shadowOffset: { + x: 20, + y: 20, + }, + }); + + layer.add(rect); + stage.add(layer); + + var canvas = createCanvas(); + var context = canvas.getContext('2d'); + context.globalAlpha = 0.3; + + // draw shadow + context.save(); + context.beginPath(); + context.rect(95, 45, 110, 60); + context.closePath(); + context.shadowColor = 'grey'; + context.shadowBlur = 5 * Konva.pixelRatio; + context.shadowOffsetX = 20 * Konva.pixelRatio; + context.shadowOffsetY = 20 * Konva.pixelRatio; + context.fillStyle = 'black'; + context.fill(); + context.restore(); + + // draw "stroke" + context.save(); + context.beginPath(); + context.moveTo(100, 50); + context.lineTo(200, 50); + context.lineTo(200, 100); + context.lineTo(100, 100); + context.closePath(); + context.lineWidth = 10; + context.strokeStyle = 'black'; + context.stroke(); + context.restore(); + + context.save(); + context.beginPath(); + context.fillStyle = 'green'; + context.rect(105, 55, 90, 40); + context.closePath(); + context.fill(); + context.restore(); + + compareLayerAndCanvas(layer, canvas, 260); + + if (isBrowser) { + var trace = layer.getContext().getTrace(); + assert.equal( + trace, + 'clearRect(0,0,578,200);save();shadowColor=rgba(128,128,128,1);shadowBlur=5;shadowOffsetX=20;shadowOffsetY=20;globalAlpha=0.5;drawImage([object HTMLCanvasElement],0,0,578,200);restore();' + ); + } else { + var trace = layer.getContext().getTrace(true); + assert.equal( + trace, + 'clearRect();save();shadowColor;shadowBlur;shadowOffsetX;shadowOffsetY;globalAlpha;drawImage();restore();' + ); + } + }); + + // ====================================================== + it('text with fill and stroke with shadow', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + + var text = new Konva.Text({ + x: 50, + y: 50, + text: 'Test TEXT', + fontSize: 50, + fill: 'green', + stroke: 'black', + strokeWidth: 2, + shadowColor: 'black', + shadowBlur: 2, + shadowOffset: { + x: 20, + y: 20, + }, + }); + + layer.add(text); + stage.add(layer); + + var canvas = createCanvas(); + var context = canvas.getContext('2d'); + + context.save(); + context.shadowColor = 'black'; + context.shadowBlur = 2 * Konva.pixelRatio; + context.shadowOffsetX = 20 * Konva.pixelRatio; + context.shadowOffsetY = 20 * Konva.pixelRatio; + context.font = 'normal 50px Arial'; + context.textBaseline = 'middle'; + + context.fillStyle = 'green'; + context.fillText('Test TEXT', 50, 75); + + context.lineWidth = 2; + context.strokeStyle = 'black'; + context.strokeText('Test TEXT', 50, 75); + + context.stroke(); + context.fill(); + context.restore(); + + // draw text again to remove shadow under stroke + context.font = 'normal 50px Arial'; + context.textBaseline = 'middle'; + context.fillText('Test TEXT', 50, 75); + context.fillStyle = 'green'; + context.fillText('Test TEXT', 50, 75); + + context.lineWidth = 2; + context.strokeStyle = 'black'; + context.strokeText('Test TEXT', 50, 75); + + compareLayerAndCanvas(layer, canvas, 254, 10); + }); + + // ====================================================== + it('shape intersect with shadow', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + + var rect = new Konva.Rect({ + fill: '#ff0000', + x: 50, + y: 50, + width: 200, + height: 200, + draggable: true, + shadowColor: '#000', // if all shadow properties removed, works fine + }); + layer.add(rect); + stage.add(layer); + + //error here + assert.equal(rect.intersects({ x: 52, y: 52 }), true); + assert.equal(rect.intersects({ x: 45, y: 45 }), false); + }); + + // ====================================================== + it('shape intersect while dragging', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + + var rect = new Konva.Rect({ + fill: '#ff0000', + x: 50, + y: 50, + width: 200, + height: 200, + draggable: true, + shadowColor: '#000', // if all shadow properties removed, works fine + }); + layer.add(rect); + stage.add(layer); + + simulateMouseDown(stage, { x: 55, y: 55 }); + simulateMouseMove(stage, { x: 65, y: 65 }); + + //error here + assert.equal(rect.intersects({ x: 65, y: 65 }), true); + simulateMouseUp(stage, { x: 65, y: 65 }); + }); + + // ====================================================== + it('overloaded getters and setters', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + + var rect = new Konva.Rect({ + x: 100, + y: 50, + width: 100, + height: 50, + fill: 'green', + stroke: 'red', + strokeWidth: 20, + draggable: true, + }); + + layer.add(rect); + stage.add(layer); + + rect.stroke('blue'); + assert.equal(rect.stroke(), 'blue'); + + rect.lineJoin('bevel'); + assert.equal(rect.lineJoin(), 'bevel'); + + rect.lineCap('square'); + assert.equal(rect.lineCap(), 'square'); + + rect.strokeWidth(8); + assert.equal(rect.strokeWidth(), 8); + + const f = () => {}; + rect.sceneFunc(f); + assert.equal(rect.sceneFunc(), f); + + rect.hitFunc(f); + assert.equal(rect.hitFunc(), f); + + rect.dash([1]); + assert.equal(rect.dash()[0], 1); + + // NOTE: skipping the rest because it would take hours to test all possible methods. + // This should hopefully be enough to test Factor overloaded methods + }); + + // ====================================================== + it('create image hit region', function (done) { + loadImage('lion.png', (imageObj) => { + var stage = addStage(); + var layer = new Konva.Layer(); + var lion = new Konva.Image({ + x: 0, + y: 0, + image: imageObj, + draggable: true, + shadowColor: 'black', + shadowBlur: 10, + shadowOffsetX: 20, + shadowOpacity: 0.2, + }); + + // override color key with black + // lion.colorKey = '#000000'; + // Konva.shapes['#000000'] = lion; + + layer.add(lion); + + stage.add(layer); + + assert.equal(layer.getIntersection({ x: 10, y: 10 }), lion); + + lion.cache(); + + lion.drawHitFromCache(); + + layer.draw(); + + assert.equal(layer.getIntersection({ x: 10, y: 10 }), null); + assert.equal(layer.getIntersection({ x: 50, y: 50 }), lion); + done(); + }); + }); + + it('test defaults', function () { + var shape = new Konva.Shape(); + + assert.equal(shape.strokeWidth(), 2); + assert.equal(shape.shadowOffsetX(), 0); + assert.equal(shape.shadowOffsetY(), 0); + assert.equal(shape.fillPatternX(), 0); + assert.equal(shape.fillPatternY(), 0); + assert.equal(shape.fillRadialGradientStartRadius(), 0); + assert.equal(shape.fillRadialGradientEndRadius(), 0); + assert.equal(shape.fillPatternRepeat(), 'repeat'); + assert.equal(shape.fillEnabled(), true); + assert.equal(shape.strokeEnabled(), true); + assert.equal(shape.shadowEnabled(), true); + assert.equal(shape.dashEnabled(), true); + assert.equal(shape.dashOffset(), 0); + assert.equal(shape.strokeScaleEnabled(), true); + assert.equal(shape.fillPriority(), 'color'); + assert.equal(shape.fillPatternOffsetX(), 0); + assert.equal(shape.fillPatternOffsetY(), 0); + assert.equal(shape.fillPatternScaleX(), 1); + assert.equal(shape.fillPatternScaleY(), 1); + assert.equal(shape.fillLinearGradientStartPointX(), 0); + assert.equal(shape.fillLinearGradientStartPointY(), 0); + assert.equal(shape.fillLinearGradientEndPointX(), 0); + assert.equal(shape.fillLinearGradientEndPointY(), 0); + assert.equal(shape.fillRadialGradientStartPointX(), 0); + assert.equal(shape.fillRadialGradientStartPointY(), 0); + assert.equal(shape.fillRadialGradientEndPointX(), 0); + assert.equal(shape.fillRadialGradientEndPointY(), 0); + assert.equal(shape.fillPatternRotation(), 0); + }); + + // ====================================================== + it('hit graph when shape cached before adding to Layer', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var rect = new Konva.Rect({ + x: 290, + y: 111, + width: 50, + height: 50, + fill: 'black', + }); + rect.cache(); + + var click = false; + + rect.on('click', function () { + click = true; + }); + + layer.add(rect); + stage.add(layer); + + simulateMouseDown(stage, { + x: 300, + y: 120, + }); + + simulateMouseUp(stage, { + x: 300, + y: 120, + }); + + assert.equal( + click, + true, + 'click event should have been fired when mousing down and then up on rect' + ); + }); + + it('class inherence', function () { + var rect = new Konva.Rect(); + assert.equal(rect instanceof Konva.Rect, true); + assert.equal(rect instanceof Konva.Shape, true); + assert.equal(rect instanceof Konva.Node, true); + }); + + it('disable stroke for hit', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + + var rect = new Konva.Rect({ + x: 100, + y: 50, + width: 100, + height: 50, + stroke: 'red', + strokeWidth: 20, + draggable: true, + }); + // default value + assert.equal(rect.hitStrokeWidth(), 'auto'); + + rect.hitStrokeWidth(0); + assert.equal(rect.hitStrokeWidth(), 0); + + layer.add(rect); + stage.add(layer); + + assert.equal(rect.y(), 50); + + var trace = layer.getHitCanvas().getContext().getTrace(true); + assert.equal( + trace, + 'clearRect();save();transform();beginPath();rect();closePath();save();fillStyle;fill();restore();restore();' + ); + }); + + it('hitStrokeWidth', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + + var rect = new Konva.Rect({ + x: 10, + y: 10, + width: 100, + height: 100, + stroke: 'red', + strokeWidth: 2, + }); + // default value + layer.add(rect); + stage.add(layer); + + // default value is auto + assert.equal(rect.hitStrokeWidth(), 'auto'); + + // try to hit test near edge + assert.equal(stage.getIntersection({ x: 5, y: 5 }), null); + + rect.hitStrokeWidth(20); + layer.draw(); + // no we should hit the rect + assert.equal(stage.getIntersection({ x: 5, y: 5 }), rect); + + rect.strokeHitEnabled(false); + + assert.equal(rect.hitStrokeWidth(), 0); + + rect.strokeHitEnabled(true); + + assert.equal(rect.hitStrokeWidth(), 'auto'); + + rect.hitStrokeWidth(0); + + assert.equal(rect.strokeHitEnabled(), false); + + // var trace = layer + // .getHitCanvas() + // .getContext() + // .getTrace(true); + // assert.equal( + // trace, + // 'clearRect();save();transform();beginPath();rect();closePath();save();fillStyle;fill();restore();restore();' + // ); + }); + + it('enable hitStrokeWidth even if we have no stroke on scene', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + + var rect = new Konva.Rect({ + x: 10, + y: 10, + width: 100, + height: 100, + }); + // default value + layer.add(rect); + stage.add(layer); + + // try to hit test near edge + assert.equal(stage.getIntersection({ x: 5, y: 5 }), null); + + rect.hitStrokeWidth(20); + layer.draw(); + // no we should hit the rect + assert.equal(stage.getIntersection({ x: 5, y: 5 }), rect); + }); + + it('cache shadow color rgba', function () { + var circle = new Konva.Circle({ + fill: 'green', + radius: 50, + }); + // no shadow on start + assert.equal(circle.hasShadow(), false); + assert.equal(circle.getShadowRGBA(), undefined); + + // set shadow + circle.shadowColor('black'); + assert.equal(circle.hasShadow(), true); + assert.equal(circle.getShadowRGBA(), 'rgba(0,0,0,1)'); + + // set another shadow property + circle.shadowOpacity(0.2); + assert.equal(circle.getShadowRGBA(), 'rgba(0,0,0,0.2)'); + + circle.shadowColor('rgba(10,10,10,0.5)'); + assert.equal(circle.getShadowRGBA(), 'rgba(10,10,10,0.1)'); + + // reset shadow + circle.shadowColor(null); + assert.equal(circle.getShadowRGBA(), undefined); + + // illegal color + circle.shadowColor('#0000f'); + assert.equal(circle.hasShadow(), true); + assert.equal(circle.getShadowRGBA(), undefined); + }); + + it('scale should also effect shadow offset', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + + var rect = new Konva.Rect({ + x: 100, + y: 100, + width: 100, + height: 100, + scaleX: 0.5, + scaleY: 0.5, + fill: 'green', + shadowColor: 'black', + shadowBlur: 0, + shadowOffset: { x: 10, y: 10 }, + }); + + layer.add(rect); + stage.add(layer); + + var canvas = createCanvas(); + var context = canvas.getContext('2d'); + // rect + context.beginPath(); + context.rect(100, 100, 50, 50); + context.closePath(); + + context.fillStyle = 'green'; + context.shadowColor = 'rgba(0,0,0,1)'; + context.shadowBlur = 0; + context.shadowOffsetX = 5 * Konva.pixelRatio; + context.shadowOffsetY = 5 * Konva.pixelRatio; + context.fill(); + + compareLayerAndCanvas(layer, canvas, 10); + + var trace = layer.getContext().getTrace(); + + assert.equal( + trace, + 'clearRect(0,0,578,200);save();transform(0.5,0,0,0.5,100,100);shadowColor=rgba(0,0,0,1);shadowBlur=0;shadowOffsetX=5;shadowOffsetY=5;beginPath();rect(0,0,100,100);closePath();fillStyle=green;fill();restore();' + ); + }); + + // TODO: restore it! + it.skip('scale should also effect shadow offset - negative scale', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + + var rect = new Konva.Rect({ + x: 100, + y: 100, + width: 100, + height: 100, + scaleX: -0.5, + scaleY: 0.5, + fill: 'green', + shadowColor: 'black', + shadowBlur: 10, + shadowOffset: { x: 10, y: 10 }, + }); + + layer.add(rect); + stage.add(layer); + + var canvas = createCanvas(); + var context = canvas.getContext('2d'); + // rect + context.beginPath(); + context.rect(50, 100, 50, 50); + context.closePath(); + + context.fillStyle = 'green'; + context.shadowColor = 'rgba(0,0,0,1)'; + context.shadowBlur = 10; + context.shadowOffsetX = -5 * Konva.pixelRatio; + context.shadowOffsetY = 5 * Konva.pixelRatio; + context.fill(); + + compareLayerAndCanvas(layer, canvas, 150); + + // var trace = layer.getContext().getTrace(); + + // assert.equal( + // trace, + // 'clearRect(0,0,578,200);save();transform(-0.5,0,0,0.5,100,100);save();shadowColor=rgba(0,0,0,1);shadowBlur=10;shadowOffsetX=-5;shadowOffsetY=5;beginPath();rect(0,0,100,100);closePath();fillStyle=green;fill();restore();restore();' + // ); + }); + + it('scale of parent container should also effect shadow offset', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + var group = new Konva.Group({ + x: 100, + y: 100, + scaleX: 0.5, + scaleY: 0.5, + }); + var rect = new Konva.Rect({ + width: 200, + height: 200, + scaleX: 0.5, + scaleY: 0.5, + fill: 'green', + shadowColor: 'black', + shadowBlur: 0, + shadowOffset: { x: 20, y: 20 }, + }); + + group.add(rect); + layer.add(group); + stage.add(layer); + + var canvas = createCanvas(); + var context = canvas.getContext('2d'); + // rect + context.beginPath(); + context.rect(100, 100, 50, 50); + context.closePath(); + + context.fillStyle = 'green'; + context.shadowColor = 'rgba(0,0,0,1)'; + context.shadowBlur = 0; + context.shadowOffsetX = 5 * Konva.pixelRatio; + context.shadowOffsetY = 5 * Konva.pixelRatio; + context.fill(); + + compareLayerAndCanvas(layer, canvas, 10); + + var trace = layer.getContext().getTrace(); + + assert.equal( + trace, + 'clearRect(0,0,578,200);save();transform(0.25,0,0,0.25,100,100);shadowColor=rgba(0,0,0,1);shadowBlur=0;shadowOffsetX=5;shadowOffsetY=5;beginPath();rect(0,0,200,200);closePath();fillStyle=green;fill();restore();' + ); + }); + + it('optional disable buffer canvas', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + + var rect = new Konva.Rect({ + x: 100, + y: 50, + width: 100, + height: 50, + fill: 'green', + stroke: 'black', + strokeWidth: 10, + opacity: 0.5, + perfectDrawEnabled: false, + }); + + layer.add(rect); + + stage.add(layer); + + var canvas = createCanvas(); + var context = canvas.getContext('2d'); + context.globalAlpha = 0.5; + // stroke + context.beginPath(); + context.rect(100, 50, 100, 50); + context.closePath(); + context.lineWidth = 10; + context.strokeStyle = 'black'; + context.fillStyle = 'green'; + context.fill(); + context.stroke(); + + compareLayerAndCanvas(layer, canvas, 10); + + var trace = layer.getContext().getTrace(); + + assert.equal( + trace, + 'clearRect(0,0,578,200);save();transform(1,0,0,1,100,50);globalAlpha=0.5;beginPath();rect(0,0,100,50);closePath();fillStyle=green;fill();lineWidth=10;strokeStyle=black;stroke();restore();' + ); + }); + + it('check lineJoin in buffer canvas', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + + var rect = new Konva.Rect({ + x: 100, + y: 50, + width: 100, + height: 50, + fill: 'green', + stroke: 'black', + strokeWidth: 10, + opacity: 0.5, + lineJoin: 'round', + }); + + layer.add(rect); + + stage.add(layer); + + var canvas = createCanvas(); + var context = canvas.getContext('2d'); + + // stroke + context.beginPath(); + context.rect(100, 50, 100, 50); + context.closePath(); + context.lineWidth = 10; + context.strokeStyle = 'black'; + context.fillStyle = 'green'; + context.lineJoin = 'round'; + context.fill(); + context.stroke(); + + var canvas2 = createCanvas(); + var context2 = canvas2.getContext('2d'); + context2.globalAlpha = 0.5; + context2.drawImage(canvas, 0, 0, canvas.width / 2, canvas.height / 2); + + compareLayerAndCanvas(layer, canvas2, 150); + + if (isBrowser) { + var trace = layer.getContext().getTrace(); + + assert.equal( + trace, + 'clearRect(0,0,578,200);save();globalAlpha=0.5;drawImage([object HTMLCanvasElement],0,0,578,200);restore();' + ); + } else { + var trace = layer.getContext().getTrace(true); + + assert.equal( + trace, + 'clearRect();save();globalAlpha;drawImage();restore();' + ); + } + }); + + it('export when buffer canvas is used should handle scaling correctly', async function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + stage.add(layer); + + var group = new Konva.Group(); + layer.add(group); + + var text = new Konva.Text({ + text: 'hello', + fontSize: 300, + fill: 'green', + shadowColor: 'black', + }); + group.add(text); + + const canvas1 = group.toCanvas({ + x: group.x(), + y: group.y(), + width: text.width(), + height: text.height(), + }); + text.stroke('transparent'); + const canvas2 = group.toCanvas({ + x: group.x(), + y: group.y(), + width: text.width(), + height: text.height(), + }); + + compareCanvases(canvas2, canvas1, 255, 10); + }); + + it('export when buffer canvas is used should handle scaling correctly another time', async function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + stage.add(layer); + + var group = new Konva.Group({ + x: 400, + }); + layer.add(group); + + var text = new Konva.Text({ + text: 'hello', + fontSize: 300, + fill: 'green', + shadowColor: 'black', + }); + group.add(text); + + const canvas1 = group.toCanvas({ + x: group.x(), + y: group.y(), + width: text.width(), + height: text.height(), + }); + text.stroke('transparent'); + const canvas2 = group.toCanvas({ + x: group.x(), + y: group.y(), + width: text.width(), + height: text.height(), + }); + + compareCanvases(canvas2, canvas1, 240, 110); + }); + + // ====================================================== + it('optional disable shadow for stroke', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + + var rect = new Konva.Rect({ + x: 100, + y: 50, + width: 100, + height: 50, + fill: 'green', + stroke: 'black', + strokeWidth: 10, + shadowColor: 'grey', + shadowBlur: 10, + shadowOffset: { + x: 20, + y: 20, + }, + shadowForStrokeEnabled: false, + }); + + layer.add(rect); + stage.add(layer); + + var canvas = createCanvas(); + var context = canvas.getContext('2d'); + context.beginPath(); + context.rect(100, 50, 100, 50); + context.closePath(); + context.fillStyle = 'green'; + context.shadowColor = 'grey'; + context.shadowBlur = 10 * Konva.pixelRatio; + context.shadowOffsetX = 20 * Konva.pixelRatio; + context.shadowOffsetY = 20 * Konva.pixelRatio; + context.lineWidth = 10; + context.fill(); + + context.shadowColor = 'rgba(0,0,0,0)'; + context.stroke(); + + compareLayerAndCanvas(layer, canvas, 10); + + var trace = layer.getContext().getTrace(); + assert.equal( + trace, + 'clearRect(0,0,578,200);save();transform(1,0,0,1,100,50);shadowColor=rgba(128,128,128,1);shadowBlur=10;shadowOffsetX=20;shadowOffsetY=20;beginPath();rect(0,0,100,50);closePath();fillStyle=green;fill();lineWidth=10;shadowColor=rgba(0,0,0,0);strokeStyle=black;stroke();restore();' + ); + }); + + it('clone custom shape', function () { + var className = 'myCustomName'; + class CustomShape extends Konva.Shape { + foo() {} + } + CustomShape.prototype.className = className; + // var CustomShape = function () { + // CustomShape.super.apply(this, arguments); + // this.className = className; + // }; + + // CustomShape.prototype.foo = function () {}; + + // Konva.Util.extend(CustomShape, Konva.Shape); + + var myShape = new CustomShape({ + fill: 'grey', + }); + + var clone = myShape.clone(); + assert.equal(clone instanceof CustomShape, true); + assert.equal(clone instanceof Konva.Shape, true); + assert.equal(clone.className, className); + assert.equal(clone.fill(), 'grey'); + assert.equal(clone.foo, CustomShape.prototype.foo); + }); + + it('getClientRect should skip disabled attributes', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var shape = new Konva.Rect({ + x: 200, + y: 100, + width: 100, + height: 100, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + strokeEnabled: false, + shadowOffsetX: 10, + shadowEnabled: false, + }); + + layer.add(shape); + stage.add(layer); + + var rect = shape.getClientRect(); + + assert.equal(rect.width, 100, 'should not effect width'); + assert.equal(rect.height, 100, 'should not effect width'); + }); + + it('getClientRect should not use cached values', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var shape = new Konva.Rect({ + x: 100, + y: 100, + width: 100, + height: 100, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + strokeEnabled: false, + shadowOffsetX: 10, + shadowEnabled: false, + }); + + layer.add(shape); + stage.add(layer); + + layer.cache(); + + layer.scaleX(2); + + const rect = shape.getClientRect(); + + assert.equal(rect.x, 200); + }); + + it('getClientRect for shape in transformed parent', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var group = new Konva.Group({ + x: 110, + y: 0, + rotation: 90, + }); + layer.add(group); + + var shape = new Konva.Rect({ + x: 0, + y: 0, + width: 100, + height: 100, + fill: 'green', + }); + group.add(shape); + + var relativeRect = shape.getClientRect({ relativeTo: group }); + + assert.equal(relativeRect.x, 0); + assert.equal(relativeRect.y, 0); + assert.equal(relativeRect.width, 100); + assert.equal(relativeRect.height, 100); + + var absRect = shape.getClientRect(); + + assert.equal(absRect.x, 10); + assert.equal(absRect.y, 0); + assert.equal(absRect.width, 100); + assert.equal(absRect.height, 100); + }); + + it('getClientRect with skew', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var shape = new Konva.Rect({ + x: 0, + y: 0, + width: 200, + height: 100, + skewX: 0.5, + scaleX: 2, + fill: 'green', + }); + layer.add(shape); + + var back = new Konva.Rect({ + stroke: 'red', + }); + back.setAttrs(shape.getClientRect()); + layer.add(back); + layer.draw(); + + var absRect = shape.getClientRect(); + + assert.equal(absRect.x, 0); + assert.equal(absRect.y, 0); + assert.equal(absRect.width, 450); + assert.equal(absRect.height, 100); + }); + + it('decompose transform', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var shape = new Konva.Rect({ + x: 0, + y: 0, + width: 200, + height: 100, + skewX: 0.5, + scaleX: 2, + scaleY: 2, + fill: 'green', + }); + layer.add(shape); + layer.draw(); + + assert.equal(shape.getTransform().decompose().scaleX, 2); + assert.equal(shape.getTransform().decompose().scaleY, 2); + assert.equal(shape.getTransform().decompose().skewX, 0.5); + + shape.skewX(2); + shape.scaleX(0.5); + + assert.equal(shape.getTransform().decompose().skewX, 2); + assert.equal(shape.getTransform().decompose().scaleX, 0.5); + assert.equal(shape.getTransform().decompose().scaleY, 2); + }); + + it('shadow should respect pixel ratio', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + layer.getCanvas().setPixelRatio(2); + var shape = new Konva.Rect({ + width: 100, + height: 100, + fill: 'black', + shadowColor: 'green', + shadowOffsetX: 20, + shadowOffsetY: 20, + shadowBlur: 0, + }); + + layer.add(shape); + stage.add(layer); + var data = layer.getContext().getImageData(15 * 2, (100 + 5) * 2, 1, 1); + assert.equal(data.data[3], 0, 'pixel should be empty, no shadow here'); + }); + + it('text shadow blur should take scale into account', function () { + var stage = addStage(); + var layer1 = new Konva.Layer(); + stage.add(layer1); + + var rect1 = new Konva.Rect({ + x: 10, + y: 10, + scaleX: 0.5, + scaleY: 0.5, + width: 80, + height: 80, + fill: 'black', + shadowColor: 'black', + shadowOffsetX: 0, + shadowOffsetY: 50, + shadowBlur: 10, + }); + layer1.add(rect1); + stage.add(layer1); + + var layer2 = new Konva.Layer(); + stage.add(layer2); + + var rect2 = new Konva.Rect({ + x: 10, + y: 10, + fill: 'black', + width: 40, + height: 40, + shadowColor: 'black', + shadowOffsetX: 0, + shadowOffsetY: 25, + shadowBlur: 5, + }); + layer2.add(rect2); + stage.add(layer2); + + compareLayers(layer1, layer2, 30); + }); + + // ====================================================== + it('sceneFunc and hitFunc should have shape as second argument', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var shape = new Konva.Shape({ + sceneFunc: function (context, shape) { + assert.equal(this, shape); + context.beginPath(); + context.moveTo(0, 0); + context.lineTo(100, 0); + context.lineTo(100, 100); + context.closePath(); + context.fillStrokeShape(shape); + }, + x: 200, + y: 100, + fill: 'green', + stroke: 'blue', + strokeWidth: 5, + }); + layer.add(shape); + + var rect = new Konva.Rect({ + hitFunc: function (ctx, shape) { + assert.equal(this, shape); + }, + }); + layer.add(rect); + stage.add(layer); + }); + + // ====================================================== + it('cache fill pattern', function (done) { + loadImage('darth-vader.jpg', (imageObj) => { + var stage = addStage(); + var layer = new Konva.Layer(); + + var star = new Konva.Star({ + x: 200, + y: 100, + numPoints: 5, + innerRadius: 40, + outerRadius: 70, + + fillPatternImage: imageObj, + fillPatternX: -20, + fillPatternY: -30, + fillPatternScale: { x: 0.5, y: 0.5 }, + fillPatternOffset: { x: 219, y: 150 }, + fillPatternRotation: 90, + fillPatternRepeat: 'no-repeat', + + stroke: 'blue', + strokeWidth: 5, + draggable: true, + }); + + layer.add(star); + stage.add(layer); + + var ctx = layer.getContext(); + var oldCreate = ctx.createPattern; + + var callCount = 0; + ctx.createPattern = function () { + callCount += 1; + return oldCreate.apply(this, arguments); + }; + + layer.draw(); + layer.draw(); + assert.equal(callCount, 0); + done(); + }); + }); + + it('recache fill pattern on changes', function (done) { + loadImage('darth-vader.jpg', (imageObj) => { + var stage = addStage(); + var layer = new Konva.Layer(); + + var star = new Konva.Star({ + x: 200, + y: 100, + numPoints: 5, + innerRadius: 40, + outerRadius: 70, + + fillPatternImage: imageObj, + fillPatternX: -20, + fillPatternY: -30, + fillPatternScale: { x: 0.5, y: 0.5 }, + fillPatternOffset: { x: 219, y: 150 }, + fillPatternRotation: 90, + fillPatternRepeat: 'no-repeat', + + stroke: 'blue', + strokeWidth: 5, + draggable: true, + }); + + layer.add(star); + stage.add(layer); + + var pattern1 = star._getFillPattern(); + + star.fillPatternImage(Konva.Util.createCanvasElement()); + + var pattern2 = star._getFillPattern(); + + assert.notEqual(pattern1, pattern2); + + star.fillPatternRepeat('repeat'); + + var pattern3 = star._getFillPattern(); + + assert.notEqual(pattern2, pattern3); + + star.fillPatternX(10); + + var pattern4 = star._getFillPattern(); + + assert.notEqual(pattern4, pattern3); + + star.fillPatternOffsetX(10); + + var pattern5 = star._getFillPattern(); + + assert.notEqual(pattern4, pattern5); + + done(); + }); + }); + + // ====================================================== + it('cache linear gradient', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var star = new Konva.Star({ + x: 200, + y: 100, + numPoints: 5, + innerRadius: 40, + outerRadius: 70, + + fillLinearGradientStartPoint: { x: -50, y: -50 }, + fillLinearGradientEndPoint: { x: 50, y: 50 }, + fillLinearGradientColorStops: [0, 'red', 1, 'yellow'], + + stroke: 'blue', + strokeWidth: 5, + draggable: true, + }); + + layer.add(star); + stage.add(layer); + + var ctx = layer.getContext(); + var oldCreate = ctx.createLinearGradient; + + var callCount = 0; + ctx.createLinearGradient = function () { + callCount += 1; + return oldCreate.apply(this, arguments); + }; + + layer.draw(); + layer.draw(); + assert.equal(callCount, 0); + }); + + it('recache linear gradient on changes', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var star = new Konva.Star({ + x: 200, + y: 100, + numPoints: 5, + innerRadius: 40, + outerRadius: 70, + + fillLinearGradientStartPoint: { x: -50, y: -50 }, + fillLinearGradientEndPoint: { x: 50, y: 50 }, + fillLinearGradientColorStops: [0, 'red', 1, 'yellow'], + + stroke: 'blue', + strokeWidth: 5, + draggable: true, + }); + + layer.add(star); + stage.add(layer); + + var gradient1 = star._getLinearGradient(); + + star.fillLinearGradientStartPointX(-10); + + var gradient2 = star._getLinearGradient(); + + assert.notEqual(gradient1, gradient2); + + star.fillLinearGradientStartPointY(-10); + + var gradient3 = star._getLinearGradient(); + + assert.notEqual(gradient2, gradient3); + + star.fillLinearGradientEndPointX(100); + + var gradient4 = star._getLinearGradient(); + + assert.notEqual(gradient3, gradient4); + + star.fillLinearGradientEndPointY(100); + + var gradient5 = star._getLinearGradient(); + + assert.notEqual(gradient4, gradient5); + + star.fillLinearGradientColorStops([0, 'red', 1, 'green']); + + var gradient6 = star._getLinearGradient(); + + assert.notEqual(gradient5, gradient6); + + layer.draw(); + }); + + // ====================================================== + it('cache radial gradient', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var star = new Konva.Star({ + x: 200, + y: 100, + numPoints: 5, + innerRadius: 40, + outerRadius: 70, + + fillRadialGradientStartPoint: { x: 0, y: 0 }, + fillRadialGradientStartRadius: 0, + fillRadialGradientEndPoint: { x: 0, y: 0 }, + fillRadialGradientEndRadius: 70, + fillRadialGradientColorStops: [0, 'red', 0.5, 'yellow', 1, 'blue'], + + stroke: 'blue', + strokeWidth: 5, + draggable: true, + }); + + layer.add(star); + stage.add(layer); + + var ctx = layer.getContext(); + var oldCreate = ctx.createRadialGradient; + + var callCount = 0; + ctx.createRadialGradient = function () { + callCount += 1; + return oldCreate.apply(this, arguments); + }; + + layer.draw(); + layer.draw(); + assert.equal(callCount, 0); + }); + + it('recache linear gradient on changes', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var star = new Konva.Star({ + x: 200, + y: 100, + numPoints: 5, + innerRadius: 40, + outerRadius: 70, + + fillRadialGradientStartPoint: { x: 0, y: 0 }, + fillRadialGradientStartRadius: 0, + fillRadialGradientEndPoint: { x: 0, y: 0 }, + fillRadialGradientEndRadius: 70, + fillRadialGradientColorStops: [0, 'red', 0.5, 'yellow', 1, 'blue'], + + stroke: 'blue', + strokeWidth: 5, + draggable: true, + }); + + layer.add(star); + stage.add(layer); + + var gradient1 = star._getRadialGradient(); + + star.fillRadialGradientStartPointX(-10); + + var gradient2 = star._getRadialGradient(); + + assert.notEqual(gradient1, gradient2); + + star.fillRadialGradientStartPointY(-10); + + var gradient3 = star._getRadialGradient(); + + assert.notEqual(gradient2, gradient3); + + star.fillRadialGradientEndPointX(100); + + var gradient4 = star._getRadialGradient(); + + assert.notEqual(gradient3, gradient4); + + star.fillRadialGradientEndPointY(100); + + var gradient5 = star._getRadialGradient(); + + assert.notEqual(gradient4, gradient5); + + star.fillRadialGradientColorStops([0, 'red', 1, 'green']); + + var gradient6 = star._getRadialGradient(); + + assert.notEqual(gradient5, gradient6); + + star.fillRadialGradientStartRadius(10); + + var gradient7 = star._getRadialGradient(); + + assert.notEqual(gradient6, gradient7); + + star.fillRadialGradientEndRadius(200); + + var gradient8 = star._getRadialGradient(); + + assert.notEqual(gradient7, gradient8); + + layer.draw(); + }); + + it('try to add destroyed shape', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var star = new Konva.Star({ + x: 200, + y: 100, + numPoints: 5, + innerRadius: 40, + outerRadius: 70, + + stroke: 'blue', + strokeWidth: 5, + draggable: true, + }); + + star.destroy(); + + var callCount = 0; + var oldWarn = Konva.Util.warn; + Konva.Util.warn = function () { + callCount += 1; + }; + + layer.add(star); + + layer.draw(); + + assert.equal(callCount, 1); + Konva.Util.warn = oldWarn; + }); + + it('hasFill getter', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var shape = new Konva.Shape({ + stroke: 'black', + strokeWidth: 4, + sceneFunc: function (context) { + context.beginPath(); + context.moveTo(20, 50); + context.quadraticCurveTo(550, 0, 500, 500); + context.fillStrokeShape(shape); + }, + fill: 'red', + fillEnabled: false, + }); + + layer.add(shape); + assert.equal(shape.hasFill(), false); + }); + + it('test hit of non filled shape', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var line = new Konva.Shape({ + sceneFunc: function (context) { + context.beginPath(); + context.moveTo(20, 50); + context.quadraticCurveTo(550, 0, 500, 500); + + context.fillStrokeShape(line); + }, + }); + + layer.add(line); + layer.draw(); + + // we still should register shape here + // like for a non filled rectangle (with just stroke), + // we need fill it for full events + var shape = layer.getIntersection({ x: 50, y: 70 }); + assert.equal(shape, line); + }); + + it('validation on stroke should accept gradients', function () { + var callCount = 0; + var oldWarn = Konva.Util.warn; + Konva.Util.warn = function () { + callCount += 1; + }; + + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var canvas = createCanvas(); + var ctx = canvas.getContext('2d'); + + var gradient = ctx.createLinearGradient(0, 75, 100, 75); + gradient.addColorStop(0.0, 'rgba(255,255,255,1)'); + gradient.addColorStop(1 / 6, 'rgba(255,255,255,0.8)'); + gradient.addColorStop(2 / 6, 'rgba(255,255,255,0.6)'); + gradient.addColorStop(3 / 6, 'rgba(255,255,255,0.4)'); + gradient.addColorStop(4 / 6, 'rgba(255,255,255,0.3)'); + gradient.addColorStop(5 / 6, 'rgba(255,255,255,0.2)'); + gradient.addColorStop(1.0, 'rgba(255,255,255, 0)'); + + var star = new Konva.Star({ + x: 200, + y: 100, + numPoints: 5, + innerRadius: 40, + outerRadius: 70, + + stroke: gradient, + strokeWidth: 5, + draggable: true, + }); + layer.add(star); + + layer.draw(); + + assert.equal(callCount, 0); + Konva.Util.warn = oldWarn; + }); + + it('fill rule on hit graph', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + stage.add(layer); + + var mask = new Konva.Shape({ + sceneFunc: function (ctx, shape) { + ctx.beginPath(); + ctx.rect(0, 0, 500, 500); + ctx.rect(100, 100, 100, 100); + ctx.closePath(); + ctx.fillShape(shape); + }, + draggable: true, + fill: 'red', + fillRule: 'evenodd', + }); + + layer.add(mask); + layer.draw(); + const trace = layer.getContext().getTrace(); + + assert.equal( + trace, + 'clearRect(0,0,578,200);clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);beginPath();rect(0,0,500,500);rect(100,100,100,100);closePath();fillStyle=red;fill(evenodd);restore();' + ); + + const hitShape = layer.getIntersection({ x: 150, y: 150 }); + assert.equal(hitShape, null); + }); +}); diff --git a/test/unit/Spline-test.ts b/test/unit/Spline-test.ts new file mode 100644 index 000000000..8ec014200 --- /dev/null +++ b/test/unit/Spline-test.ts @@ -0,0 +1,113 @@ +import { assert } from 'chai'; + +import { addStage, Konva } from './test-utils'; + +describe('Spline', function () { + // ====================================================== + it('add splines', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var line1 = new Konva.Line({ + points: [73, 160, 340, 23, 500, 109, 300, 109], + stroke: 'blue', + strokeWidth: 10, + lineCap: 'round', + lineJoin: 'round', + draggable: true, + tension: 1, + }); + + var line2 = new Konva.Line({ + points: [73, 160, 340, 23, 500, 109], + stroke: 'red', + strokeWidth: 10, + lineCap: 'round', + lineJoin: 'round', + draggable: true, + tension: 1, + }); + + var line3 = new Konva.Line({ + points: [73, 160, 340, 23], + stroke: 'green', + strokeWidth: 10, + lineCap: 'round', + lineJoin: 'round', + draggable: true, + tension: 1, + }); + + layer.add(line1); + layer.add(line2); + layer.add(line3); + stage.add(layer); + + assert.equal(line1.getClassName(), 'Line'); + + var trace = layer.getContext().getTrace(); + + //console.log(trace); + assert.equal( + trace, + 'clearRect(0,0,578,200);save();lineJoin=round;transform(1,0,0,1,0,0);beginPath();moveTo(73,160);quadraticCurveTo(74.006,54.77,340,23);bezierCurveTo(501.006,3.77,519.038,68.068,500,109);quadraticCurveTo(479.038,154.068,300,109);lineCap=round;lineWidth=10;strokeStyle=blue;stroke();restore();save();lineJoin=round;transform(1,0,0,1,0,0);beginPath();moveTo(73,160);quadraticCurveTo(74.006,54.77,340,23);quadraticCurveTo(501.006,3.77,500,109);lineCap=round;lineWidth=10;strokeStyle=red;stroke();restore();save();lineJoin=round;transform(1,0,0,1,0,0);beginPath();moveTo(73,160);lineTo(340,23);lineCap=round;lineWidth=10;strokeStyle=green;stroke();restore();' + ); + }); + + // ====================================================== + it('update spline points', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var spline = new Konva.Line({ + points: [73, 160, 340, 23, 500, 109, 300, 109], + stroke: 'blue', + strokeWidth: 10, + lineCap: 'round', + lineJoin: 'round', + draggable: true, + tension: 1, + }); + + layer.add(spline); + stage.add(layer); + + assert.equal(spline.getTensionPoints().length, 12); + + spline.points([73, 160, 340, 23, 500, 109]); + + assert.equal(spline.getTensionPoints().length, 6); + + layer.draw(); + }); + + // ====================================================== + it('add point to spline points', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var spline = new Konva.Line({ + points: [73, 160, 340, 23, 500, 109, 300, 109], + stroke: 'blue', + strokeWidth: 10, + lineCap: 'round', + lineJoin: 'round', + draggable: true, + tension: 1, + }); + + layer.add(spline); + stage.add(layer); + + assert.equal(spline.points().length, 8); + + var points = spline.points(); + points.push(300); + points.push(200); + spline.clearCache(); + + assert.equal(spline.points().length, 10); + + layer.draw(); + }); +}); diff --git a/test/unit/Sprite-test.ts b/test/unit/Sprite-test.ts new file mode 100644 index 000000000..9e8f3cfc1 --- /dev/null +++ b/test/unit/Sprite-test.ts @@ -0,0 +1,464 @@ +import { assert } from 'chai'; + +import { addStage, Konva, loadImage } from './test-utils'; + +describe('Sprite', function () { + // ====================================================== + it('add sprite', function (done) { + loadImage('scorpion-sprite.png', (imageObj) => { + var stage = addStage(); + var layer = new Konva.Layer(); + + var sprite = new Konva.Sprite({ + x: 200, + y: 50, + image: imageObj, + animation: 'standing', + animations: { + standing: [ + 0, + 0, + 49, + 109, + 52, + 0, + 49, + 109, + 105, + 0, + 49, + 109, + 158, + 0, + 49, + 109, + 210, + 0, + 49, + 109, + 262, + 0, + 49, + 109, + ], + kicking: [ + 0, + 109, + 45, + 98, + 45, + 109, + 45, + 98, + 95, + 109, + 63, + 98, + 156, + 109, + 70, + 98, + 229, + 109, + 60, + 98, + 287, + 109, + 41, + 98, + ], + }, + frameRate: 10, + draggable: true, + shadowColor: 'black', + shadowBlur: 3, + shadowOffset: { x: 3, y: 1 }, + shadowOpacity: 0.3, + }); + + layer.add(sprite); + stage.add(layer); + + assert.equal(sprite.getClassName(), 'Sprite'); + assert.equal(sprite.frameIndex(), 0); + + var trace = layer.hitCanvas.getContext().getTrace(); + + assert.equal(trace.indexOf(sprite.colorKey) >= 0, true); + + sprite.start(); + + // kick once + setTimeout(function () { + sprite.animation('kicking'); + sprite.on('indexChange', function (evt) { + if (evt['newVal'] === 0 && this.animation() === 'kicking') { + sprite.animation('standing'); + } + }); + }, 2000); + setTimeout(function () { + sprite.stop(); + }, 3000); + + done(); + }); + }); + + // ====================================================== + it('don`t update layer too many times', function (done) { + loadImage('scorpion-sprite.png', (imageObj) => { + var stage = addStage(); + var layer = new Konva.Layer(); + + var sprite = new Konva.Sprite({ + x: 200, + y: 50, + image: imageObj, + animation: 'standing', + animations: { + standing: [ + 0, + 0, + 49, + 109, + 52, + 0, + 49, + 109, + 105, + 0, + 49, + 109, + 158, + 0, + 49, + 109, + 210, + 0, + 49, + 109, + 262, + 0, + 49, + 109, + ], + }, + frameRate: 5, + draggable: true, + shadowColor: 'black', + shadowBlur: 3, + shadowOffset: { x: 3, y: 1 }, + shadowOpacity: 0.3, + }); + + layer.add(sprite); + stage.add(layer); + + var oldDraw = layer.draw; + var updateCount = 0; + layer.draw = function () { + updateCount++; + oldDraw.call(layer); + return layer; + }; + + sprite.start(); + setTimeout(function () { + sprite.stop(); + assert.equal(updateCount < 7, true); + done(); + }, 1000); + }); + }); + + // ====================================================== + it('don`t update layer too many times 2', function (done) { + loadImage('scorpion-sprite.png', (imageObj) => { + var stage = addStage(); + var layer = new Konva.Layer(); + + var sprite = new Konva.Sprite({ + x: 200, + y: 50, + image: imageObj, + animation: 'standing', + animations: { + standing: [ + 0, + 0, + 49, + 109, + 52, + 0, + 49, + 109, + 105, + 0, + 49, + 109, + 158, + 0, + 49, + 109, + 210, + 0, + 49, + 109, + 262, + 0, + 49, + 109, + ], + }, + frameRate: 5, + }); + + var sprite2 = new Konva.Sprite({ + x: 200, + y: 50, + image: imageObj, + animation: 'standing', + animations: { + standing: [ + 0, + 0, + 49, + 109, + 52, + 0, + 49, + 109, + 105, + 0, + 49, + 109, + 158, + 0, + 49, + 109, + 210, + 0, + 49, + 109, + 262, + 0, + 49, + 109, + ], + }, + frameRate: 20, + }); + + layer.add(sprite).add(sprite2); + stage.add(layer); + + var oldDraw = layer.draw; + var updateCount = 0; + layer.draw = function () { + updateCount++; + oldDraw.call(layer); + return layer; + }; + + sprite.start(); + sprite2.start(); + setTimeout(function () { + sprite.stop(); + sprite2.stop(); + assert.equal(updateCount > 15, true); + assert.equal(updateCount < 27, true); + done(); + }, 1000); + }); + }); + + it('check is sprite running', function (done) { + loadImage('scorpion-sprite.png', (imageObj) => { + var stage = addStage(); + var layer = new Konva.Layer(); + + var sprite = new Konva.Sprite({ + x: 200, + y: 50, + image: imageObj, + animation: 'standing', + animations: { + standing: [ + 0, + 0, + 49, + 109, + 52, + 0, + 49, + 109, + 105, + 0, + 49, + 109, + 158, + 0, + 49, + 109, + 210, + 0, + 49, + 109, + 262, + 0, + 49, + 109, + ], + }, + frameRate: 50, + draggable: true, + shadowColor: 'black', + shadowBlur: 3, + shadowOffset: { x: 3, y: 1 }, + shadowOpacity: 0.3, + }); + + layer.add(sprite); + stage.add(layer); + assert.equal(sprite.isRunning(), false); + sprite.start(); + assert.equal(sprite.isRunning(), true); + sprite.stop(); + done(); + }); + }); + + it('start do nothing if animation is already running', function (done) { + loadImage('scorpion-sprite.png', (imageObj) => { + var stage = addStage(); + var layer = new Konva.Layer(); + + var sprite = new Konva.Sprite({ + x: 200, + y: 50, + image: imageObj, + animation: 'standing', + animations: { + standing: [ + 0, + 0, + 49, + 109, + 52, + 0, + 49, + 109, + 105, + 0, + 49, + 109, + 158, + 0, + 49, + 109, + 210, + 0, + 49, + 109, + 262, + 0, + 49, + 109, + ], + }, + frameRate: 50, + draggable: true, + shadowColor: 'black', + shadowBlur: 3, + shadowOffset: { x: 3, y: 1 }, + shadowOpacity: 0.3, + }); + + layer.add(sprite); + stage.add(layer); + + var counter = 0; + sprite.on('frameIndexChange.konva', function (event) { + counter += 1; + }); + + sprite.start(); + sprite.start(); + sprite.stop(); + + setTimeout(function () { + assert.equal(counter, 0); + done(); + }, 200); + }); + }); + + // need fix. + it.skip('can change frame rate on fly', function (done) { + loadImage('scorpion-sprite.png', (imageObj) => { + var stage = addStage(); + var layer = new Konva.Layer(); + + var sprite = new Konva.Sprite({ + x: 200, + y: 50, + image: imageObj, + animation: 'standing', + animations: { + standing: [ + 0, + 0, + 49, + 109, + 52, + 0, + 49, + 109, + 105, + 0, + 49, + 109, + 158, + 0, + 49, + 109, + 210, + 0, + 49, + 109, + 262, + 0, + 49, + 109, + ], + }, + frameRate: 50, + draggable: true, + shadowColor: 'black', + shadowBlur: 3, + shadowOffset: { x: 3, y: 1 }, + shadowOpacity: 0.3, + }); + + layer.add(sprite); + stage.add(layer); + assert.equal(sprite.frameRate(), 50); + setTimeout(function () { + sprite.frameRate(100); + assert.equal(sprite.frameRate(), 100); + assert.equal(sprite.anim.isRunning(), false, '1'); + }, 23); + + setTimeout(function () { + sprite.start(); + sprite.frameRate(52); + assert.equal(sprite.anim.isRunning(), true); + // for this moment should tick more than 2 times + // make sure that sprite is not restating after set frame rate + assert.equal(sprite.frameIndex() > 2, true, '2'); + done(); + }, 68); + }); + }); +}); diff --git a/test/unit/Stage-test.ts b/test/unit/Stage-test.ts new file mode 100644 index 000000000..76f236781 --- /dev/null +++ b/test/unit/Stage-test.ts @@ -0,0 +1,1456 @@ +import { assert } from 'chai'; + +import { + addStage, + simulateMouseDown, + simulateMouseMove, + simulateMouseUp, + simulateTouchStart, + simulateTouchMove, + simulateTouchEnd, + compareCanvases, + createCanvas, + showHit, + getContainer, + isNode, + isBrowser, + Konva, +} from './test-utils'; + +describe('Stage', function () { + // ====================================================== + it('instantiate stage with id', function () { + if (isNode) { + return; + } + var container = Konva.document.createElement('div'); + container.id = 'container'; + getContainer().appendChild(container); + + var stage = new Konva.Stage({ + container: 'container', + width: 578, + height: 200, + }); + + assert.equal(stage.getContent().className, 'konvajs-content'); + assert.equal(stage.getContent().getAttribute('role'), 'presentation'); + }); + + // ====================================================== + it('test stage buffer canvas and hit buffer canvas', function () { + if (isNode) { + return; + } + var container = Konva.document.createElement('div'); + container.id = 'container'; + + getContainer().appendChild(container); + + // simulate pixelRatio = 2 + Konva.pixelRatio = 2; + + var stage = new Konva.Stage({ + container: 'container', + width: 578, + height: 200, + }); + + assert.equal(stage.bufferCanvas.getPixelRatio(), 2); + assert.equal(stage.bufferHitCanvas.getPixelRatio(), 1); + + // reset + Konva.pixelRatio = 1; + }); + + // ====================================================== + it('instantiate stage with dom element', function () { + if (isNode) { + return; + } + var container = Konva.document.createElement('div'); + + getContainer().appendChild(container); + + var stage = new Konva.Stage({ + container: container, + width: 578, + height: 200, + }); + + assert.equal(stage.container(), container); + }); + + // ====================================================== + it('stage instantiation should clear container', function () { + if (isNode) { + return; + } + var container = Konva.document.createElement('div'); + var dummy = Konva.document.createElement('p'); + + container.appendChild(dummy); + getContainer().appendChild(container); + + var stage = new Konva.Stage({ + container: container, + width: 578, + height: 200, + }); + + assert.equal( + container.getElementsByTagName('p').length, + 0, + 'container should have no p tags' + ); + }); + + // ====================================================== + it('test stage cloning', function () { + if (isNode) { + return; + } + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var stageClone = stage.clone(); + assert.notEqual( + stage.container(), + stageClone.container(), + 'clone should be in different container' + ); + + assert.equal( + stage.container().childNodes[0].childNodes.length, + 1, + 'container should not have changes' + ); + }); + + // ====================================================== + it('set stage size', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + }); + + assert.equal(stage.getSize().width, 578); + assert.equal(stage.getSize().height, 200); + stage.setSize({ width: 1, height: 2 }); + assert.equal(stage.getSize().width, 1); + assert.equal(stage.getSize().height, 2); + stage.setSize({ width: 3, height: 3 }); + assert.equal(stage.getSize().width, 3); + assert.equal(stage.getSize().height, 3); + stage.setSize({ + width: 4, + height: 5, + }); + assert.equal(stage.getSize().width, 4); + assert.equal(stage.getSize().height, 5); + stage.width(6); + assert.equal(stage.getSize().width, 6); + assert.equal(stage.getSize().height, 5); + stage.height(7); + assert.equal(stage.getSize().width, 6); + assert.equal(stage.getSize().height, 7); + stage.setSize({ width: 8, height: 9 }); + assert.equal(stage.getSize().width, 8); + assert.equal(stage.getSize().height, 9); + stage.setSize({ width: 10, height: 11 }); + assert.equal(stage.getSize().width, 10); + assert.equal(stage.getSize().height, 11); + + layer.add(circle); + stage.add(layer); + + stage.setSize({ width: 333, height: 155 }); + + assert.equal(stage.getSize().width, 333); + assert.equal(stage.getSize().height, 155); + if (isBrowser) { + assert.equal(stage.getContent().style.width, '333px'); + assert.equal(stage.getContent().style.height, '155px'); + } + + assert.equal( + layer.getCanvas()._canvas.width, + 333 * layer.getCanvas().getPixelRatio() + ); + assert.equal( + layer.getCanvas()._canvas.height, + 155 * layer.getCanvas().getPixelRatio() + ); + }); + + // ====================================================== + it('get stage DOM', function () { + if (isNode) { + return; + } + var stage = addStage(); + + assert.equal(stage.getContent().className, 'konvajs-content'); + }); + + it('try to move stage ', function () { + if (isNode) { + return; + } + var stage = addStage(); + var container = document.createElement('div'); + var wrap = stage.container().parentNode; + wrap.appendChild(container); + + stage.container(container); + + assert.equal(stage.container(), container); + + assert.equal(stage.content, container.children[0]); + }); + + it('clone stage ', function () { + if (isNode) { + return; + } + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var shape = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + strokeWidth: 4, + fill: 'red', + stroke: 'black', + }); + layer.add(shape); + layer.draw(); + + var container = document.createElement('div'); + var wrap = stage.container().parentNode; + wrap.appendChild(container); + + var clone = stage.clone(); + clone.container(container); + + assert.equal(clone.container(), container); + + assert.equal(clone.content, container.children[0]); + }); + + it('dangling stage ', function () { + if (isNode) { + return; + } + var stage = addStage(); + var container = stage.container(); + var parent = stage.content.parentElement; + + parent.removeChild(stage.content); + + stage.setContainer(container); + + assert.equal(stage.container(), container); + }); + + // ====================================================== + it('stage getIntersection()', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var redCircle = new Konva.Circle({ + x: 380, + y: stage.height() / 2, + radius: 70, + strokeWidth: 4, + fill: 'red', + stroke: 'black', + }); + + var greenCircle = new Konva.Circle({ + x: 300, + y: stage.height() / 2, + radius: 70, + strokeWidth: 4, + fill: 'green', + stroke: 'black', + }); + + layer.add(redCircle); + layer.add(greenCircle); + stage.add(layer); + + assert.equal( + stage.getIntersection({ x: 300, y: 100 }), + greenCircle, + 'shape should be greenCircle' + ); + assert.equal( + stage.getIntersection({ x: 380, y: 100 }), + redCircle, + 'shape should be redCircle' + ); + assert.equal( + stage.getIntersection({ x: 100, y: 100 }), + null, + 'shape should be null' + ); + }); + + // ====================================================== + it('stage getIntersection() edge detection', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var redCircle = new Konva.Circle({ + x: 380, + y: stage.height() / 2, + radius: 70, + strokeWidth: 4, + fill: 'red', + stroke: 'black', + }); + + var greenCircle = new Konva.Circle({ + x: 300, + y: stage.height() / 2, + radius: 70, + strokeWidth: 4, + fill: 'green', + stroke: 'black', + }); + + stage.on('contentMousemove', function () { + var pos = stage.getPointerPosition(); + var shape = stage.getIntersection(pos); + if (!shape) { + //console.log(pos); + } + }); + + layer.add(redCircle); + layer.add(greenCircle); + stage.add(layer); + + assert.equal( + stage.getIntersection({ x: 370, y: 93 }), + greenCircle, + 'shape should be greenCircle' + ); + assert.equal( + stage.getIntersection({ x: 371, y: 93 }), + greenCircle, + 'shape should be greenCircle' + ); + assert.equal( + stage.getIntersection({ x: 372, y: 93 }), + redCircle, + 'shape should be redCircle' + ); + }); + + // ====================================================== + it('test getAllIntersections', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var redCircle = new Konva.Circle({ + x: 380, + y: stage.height() / 2, + radius: 70, + strokeWidth: 4, + fill: 'red', + stroke: 'black', + id: 'redCircle', + }); + + var greenCircle = new Konva.Circle({ + x: 300, + y: stage.height() / 2, + radius: 70, + strokeWidth: 4, + fill: 'green', + stroke: 'black', + id: 'greenCircle', + }); + + layer.add(redCircle); + layer.add(greenCircle); + stage.add(layer); + + // test individual shapes + assert.equal( + stage.getAllIntersections({ x: 266, y: 114 }).length, + 1, + '17) getAllIntersections should return one shape' + ); + assert.equal( + stage.getAllIntersections({ x: 266, y: 114 })[0].getId(), + 'greenCircle', + '19) first intersection should be greenCircle' + ); + + assert.equal( + stage.getAllIntersections({ x: 414, y: 115 }).length, + 1, + '18) getAllIntersections should return one shape' + ); + assert.equal( + stage.getAllIntersections({ x: 414, y: 115 })[0].getId(), + 'redCircle', + '20) first intersection should be redCircle' + ); + + assert.equal( + stage.getAllIntersections({ x: 350, y: 118 }).length, + 2, + '1) getAllIntersections should return two shapes' + ); + assert.equal( + stage.getAllIntersections({ x: 350, y: 118 })[0].getId(), + 'redCircle', + '2) first intersection should be redCircle' + ); + assert.equal( + stage.getAllIntersections({ x: 350, y: 118 })[1].getId(), + 'greenCircle', + '3) second intersection should be greenCircle' + ); + + // hide green circle. make sure only red circle is in result set + greenCircle.hide(); + layer.draw(); + + assert.equal( + stage.getAllIntersections({ x: 350, y: 118 }).length, + 1, + '4) getAllIntersections should return one shape' + ); + assert.equal( + stage.getAllIntersections({ x: 350, y: 118 })[0].getId(), + 'redCircle', + '5) first intersection should be redCircle' + ); + + // show green circle again. make sure both circles are in result set + greenCircle.show(); + layer.draw(); + + assert.equal( + stage.getAllIntersections({ x: 350, y: 118 }).length, + 2, + '6) getAllIntersections should return two shapes' + ); + assert.equal( + stage.getAllIntersections({ x: 350, y: 118 })[0].getId(), + 'redCircle', + '7) first intersection should be redCircle' + ); + assert.equal( + stage.getAllIntersections({ x: 350, y: 118 })[1].getId(), + 'greenCircle', + '8) second intersection should be greenCircle' + ); + + // hide red circle. make sure only green circle is in result set + redCircle.hide(); + layer.draw(); + + assert.equal( + stage.getAllIntersections({ x: 350, y: 118 }).length, + 1, + '9) getAllIntersections should return one shape' + ); + assert.equal( + stage.getAllIntersections({ x: 350, y: 118 })[0].getId(), + 'greenCircle', + '10) first intersection should be greenCircle' + ); + + // show red circle again. make sure both circles are in result set + redCircle.show(); + layer.draw(); + + assert.equal( + stage.getAllIntersections({ x: 350, y: 118 }).length, + 2, + '11) getAllIntersections should return two shapes' + ); + assert.equal( + stage.getAllIntersections({ x: 350, y: 118 })[0].getId(), + 'redCircle', + '12) first intersection should be redCircle' + ); + assert.equal( + stage.getAllIntersections({ x: 350, y: 118 })[1].getId(), + 'greenCircle', + '13) second intersection should be greenCircle' + ); + + // test from layer + assert.equal( + layer.getAllIntersections({ x: 350, y: 118 }).length, + 2, + '14) getAllIntersections should return two shapes' + ); + assert.equal( + layer.getAllIntersections({ x: 350, y: 118 })[0].getId(), + 'redCircle', + '15) first intersection should be redCircle' + ); + assert.equal( + layer.getAllIntersections({ x: 350, y: 118 })[1].getId(), + 'greenCircle', + '16) second intersection should be greenCircle' + ); + + // now hide layer and but force visible for shape. + + layer.hide(); + redCircle.visible(true); + assert.equal(stage.getAllIntersections(redCircle.position()).length, 0); + }); + + // ====================================================== + it('test getAllIntersections for text', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var text = new Konva.Text({ + x: 0, + y: 0, + text: 'Hello world', + fontSize: 50, + name: 'intersectText', + }); + + layer.add(text); + stage.add(layer); + + // test individual shapes + assert.equal( + stage.getAllIntersections({ x: 10, y: 10 }).length, + 1, + '17) getAllIntersections should return one shape' + ); + }); + + // ====================================================== + it('Should not throw on clip for stage', function () { + // no asserts, because we check throw + var stage = addStage({ + clipFunc: function () {}, + }); + var layer = new Konva.Layer(); + + var text = new Konva.Text({ + x: 0, + y: 0, + text: 'Hello world', + fontSize: 50, + name: 'intersectText', + }); + + layer.add(text); + stage.add(layer); + }); + + // ====================================================== + it('scale stage after add layer', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + }); + + layer.add(circle); + stage.add(layer); + + stage.scale({ x: 0.5, y: 0.5 }); + + assert.equal(stage.scale().x, 0.5, 'stage scale x should be 0.5'); + assert.equal(stage.scale().y, 0.5, 'stage scale y should be 0.5'); + stage.draw(); + }); + + // ====================================================== + it('scale stage before add shape', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + }); + + stage.scale({ x: 0.5, y: 0.5 }); + + assert.equal(stage.scale().x, 0.5, 'stage scale x should be 0.5'); + assert.equal(stage.scale().y, 0.5, 'stage scale y should be 0.5'); + + layer.add(circle); + stage.add(layer); + }); + + // ====================================================== + // TODO: restore it, remove should deatach from DOM + it.skip('remove stage', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + }); + + layer.add(circle); + stage.add(layer); + + // remove should have no effect, and should cause no JS error + stage.remove(); + + assert.equal(stage.content.parentNode, undefined); + }); + + // ====================================================== + it('destroy stage', function () { + var stage = addStage({ + width: 578, + height: 200, + id: 'stageFalconId', + name: 'stageFalconName', + }); + + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + id: 'circleFalconId', + name: 'circleFalconName', + }); + + layer.add(circle); + stage.add(layer); + + stage.destroy(); + + assert.equal( + Konva.stages.indexOf(stage) === -1, + true, + 'stage should not be in stages array' + ); + }); + + // ====================================================== + it('scale stage with no shapes', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + + stage.add(layer); + stage.scaleX(0.5); + + stage.draw(); + + assert.equal(stage.scaleX(), 0.5); + }); + + // ====================================================== + it('test stage.getStage()', function () { + var stage = addStage(); + + assert.notEqual(stage.getStage(), undefined); + + //console.log(stage.getStage()); + }); + + it('add multiple layers to stage', function () { + var stage = addStage(); + var layer1 = new Konva.Layer(); + var layer2 = new Konva.Layer(); + var layer3 = new Konva.Layer(); + stage.add(layer1, layer2, layer3); + assert.equal(stage.getLayers().length, 3, 'stage has exactly three layers'); + }); + // ====================================================== + it('test drag and click', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var rect = new Konva.Rect({ + x: 50, + y: 50, + width: 50, + height: 50, + fill: 'red', + draggable: true, + }); + + layer.add(rect); + stage.add(layer); + + rect.on('dblclick', function () { + assert(false, 'double click fired'); + }); + + var y = 60; + + // simulate dragging + simulateMouseDown(stage, { + x: 60, + y: y, + }); + + simulateMouseMove(stage, { + x: 61, + y: y, + }); + + simulateMouseMove(stage, { + x: 62, + y: y, + }); + + simulateMouseMove(stage, { + x: 63, + y: y, + }); + + simulateMouseMove(stage, { + x: 64, + y: y, + }); + + simulateMouseUp(stage, { + x: 65, + y: y, + }); + + assert.equal(Konva.isDragging(), false); + assert.equal(Konva.DD.node, undefined); + // simulate click + simulateMouseDown(stage, { + x: 66, + y: y, + }); + + simulateMouseUp(stage, { + x: 66, + y: y, + }); + assert.equal(Konva.isDragging(), false); + assert.equal(Konva.DD.node, undefined); + }); + + // ====================================================== + it('do not trigger stage click after dragend', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var rect = new Konva.Rect({ + x: 0, + y: 0, + width: 50, + height: 50, + fill: 'red', + draggable: true, + }); + + layer.add(rect); + stage.add(layer); + + var clicks = 0; + + stage.on('click', function () { + clicks += 1; + }); + + // simulate dragging + simulateMouseDown(stage, { + x: 25, + y: 25, + }); + + simulateMouseMove(stage, { + x: 100, + y: 100, + }); + + // move rect out of mouse + rect.x(-30); + rect.y(-30); + + simulateMouseUp(stage, { + x: 100, + y: 100, + }); + + assert.equal(clicks, 0); + }); + + it('can listen click on empty areas', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var dblicks = 0; + var clicks = 0; + var mousedowns = 0; + var mouseups = 0; + var mousemoves = 0; + + stage.on('mousedown', function (e) { + mousedowns += 1; + assert.equal(e.target, stage); + assert.equal(e.currentTarget, stage); + }); + + stage.on('mousemove', function (e) { + mousemoves += 1; + assert.equal(e.target, stage); + assert.equal(e.currentTarget, stage); + }); + + stage.on('mouseup', function (e) { + mouseups += 1; + assert.equal(e.target, stage); + assert.equal(e.currentTarget, stage); + }); + + stage.on('click', function (e) { + clicks += 1; + assert.equal(e.target, stage); + assert.equal(e.currentTarget, stage); + }); + + stage.on('dblclick', function (e) { + dblicks += 1; + assert.equal(e.target, stage); + assert.equal(e.currentTarget, stage); + }); + + // simulate dragging + simulateMouseDown(stage, { + x: 60, + y: 10, + }); + + simulateMouseMove(stage, { + x: 60, + y: 10, + }); + + simulateMouseUp(stage, { + x: 65, + y: 10, + }); + + assert.equal(mousedowns, 1, 'first mousedown registered'); + assert.equal(mouseups, 1, 'first mouseup registered'); + assert.equal(clicks, 1, 'first click registered'); + assert.equal(mousemoves, 1, 'first mousemove registered'); + assert.equal(dblicks, 0, 'no dbclicks registered'); + + simulateMouseDown(stage, { + x: 60, + y: 10, + }); + + simulateMouseUp(stage, { + x: 65, + y: 10, + }); + + assert.equal(mousedowns, 2, 'second mousedown registered'); + assert.equal(mouseups, 2, 'seconds mouseup registered'); + assert.equal(clicks, 2, 'seconds click registered'); + assert.equal(dblicks, 1, 'first dbclick registered'); + }); + + it('can listen taps on empty areas', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var dbltaps = 0; + var taps = 0; + var touchstarts = 0; + var touchends = 0; + var touchmoves = 0; + + stage.on('touchstart', function (e) { + touchstarts += 1; + assert.equal(e.target, stage); + assert.equal(e.currentTarget, stage); + }); + + stage.on('touchend', function (e) { + touchends += 1; + assert.equal(e.target, stage); + assert.equal(e.currentTarget, stage); + }); + + stage.on('touchmove', function (e) { + touchmoves += 1; + assert.equal(e.target, stage); + assert.equal(e.currentTarget, stage); + }); + + stage.on('tap', function (e) { + taps += 1; + assert.equal(e.target, stage); + assert.equal(e.currentTarget, stage); + }); + + stage.on('dbltap', function (e) { + dbltaps += 1; + assert.equal(e.target, stage); + assert.equal(e.currentTarget, stage); + }); + + // simulate dragging + simulateTouchStart(stage, [{ x: 100, y: 100, id: 1 }]); + + simulateTouchMove(stage, [{ x: 100, y: 100, id: 1 }]); + + simulateTouchEnd(stage, [], [{ x: 100, y: 100, id: 1 }]); + + assert.equal(touchstarts, 1, 'first touchstart registered'); + assert.equal(touchends, 1, 'first touchends registered'); + assert.equal(taps, 1, 'first tap registered'); + assert.equal(touchmoves, 1, 'first touchmove registered'); + assert.equal(dbltaps, 0, 'no dbltap registered'); + + simulateTouchStart(stage, [{ x: 100, y: 100, id: 1 }]); + + simulateTouchEnd(stage, [], [{ x: 100, y: 100, id: 1 }]); + + assert.equal(touchstarts, 2, 'first touchstart registered'); + assert.equal(touchends, 2, 'first touchends registered'); + assert.equal(taps, 2, 'first tap registered'); + assert.equal(dbltaps, 1, 'dbltap registered'); + }); + + it('pass context and wheel events to shape', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + x: 50, + y: 50, + width: 100, + height: 100, + fill: 'red', + }); + layer.add(rect); + layer.draw(); + + var contextmenus = 0; + var wheels = 0; + + // test on empty + stage.on('contextmenu', function (e) { + contextmenus += 1; + assert.equal(e.target, stage); + assert.equal(e.currentTarget, stage); + }); + + stage.on('wheel', function (e) { + wheels += 1; + assert.equal(e.target, stage); + assert.equal(e.currentTarget, stage); + }); + + var top = stage.content ? stage.content.getBoundingClientRect().top : 0; + stage._contextmenu({ + clientX: 0, + clientY: top + 0, + }); + stage._wheel({ + clientX: 0, + clientY: top + 0, + }); + + assert.equal(contextmenus, 1, 'first contextment registered'); + assert.equal(wheels, 1, 'first wheel registered'); + + stage.off('contextmenu'); + stage.off('wheel'); + + // test on shape + stage.on('contextmenu', function (e) { + contextmenus += 1; + assert.equal(e.target, rect); + assert.equal(e.currentTarget, stage); + }); + + stage.on('wheel', function (e) { + wheels += 1; + assert.equal(e.target, rect); + assert.equal(e.currentTarget, stage); + }); + stage._contextmenu({ + clientX: 60, + clientY: top + 60, + }); + stage._wheel({ + clientX: 60, + clientY: top + 60, + }); + + assert.equal(contextmenus, 2, 'second contextment registered'); + assert.equal(wheels, 2, 'second wheel registered'); + }); + + it('make sure it does not trigger too many events', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + var rect = new Konva.Rect({ + width: stage.width(), + height: stage.height(), + }); + layer.add(rect); + layer.draw(); + + var dblicks = 0; + var clicks = 0; + var mousedowns = 0; + var mouseups = 0; + + stage.on('mousedown', function (e) { + mousedowns += 1; + assert.equal(e.target, rect); + assert.equal(e.currentTarget, stage); + }); + + stage.on('mouseup', function (e) { + mouseups += 1; + assert.equal(e.target, rect); + assert.equal(e.currentTarget, stage); + }); + + stage.on('click', function (e) { + clicks += 1; + assert.equal(e.target, rect); + assert.equal(e.currentTarget, stage); + }); + + stage.on('dblclick', function (e) { + dblicks += 1; + assert.equal(e.target, rect); + assert.equal(e.currentTarget, stage); + }); + + // simulate dragging + simulateMouseDown(stage, { + x: 60, + y: 10, + }); + + simulateMouseUp(stage, { + x: 65, + y: 10, + }); + + assert.equal(mousedowns, 1, 'first mousedown registered'); + assert.equal(mouseups, 1, 'first mouseup registered'); + assert.equal(clicks, 1, 'first click registered'); + assert.equal(dblicks, 0, 'no dbclicks registered'); + + simulateMouseDown(stage, { + x: 60, + y: 10, + }); + + simulateMouseUp(stage, { + x: 65, + y: 10, + }); + + assert.equal(mousedowns, 2, 'second mousedown registered'); + assert.equal(mouseups, 2, 'seconds mouseup registered'); + assert.equal(clicks, 2, 'seconds click registered'); + assert.equal(dblicks, 1, 'first dbclick registered'); + }); + + it('test mouseover event on stage', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + var rect1 = new Konva.Rect({ + x: 50, + y: 50, + width: 50, + height: 50, + fill: 'red', + }); + layer.add(rect1); + + var rect2 = new Konva.Rect({ + x: 100, + y: 100, + width: 50, + height: 50, + fill: 'red', + }); + layer.add(rect2); + layer.draw(); + + var mouseover = 0; + + stage.on('mouseover', function (e) { + mouseover += 1; + + if (mouseover === 1) { + assert.equal(e.target, stage); + assert.equal(e.currentTarget, stage); + } + if (mouseover === 2) { + assert.equal(e.target, rect1); + } + if (mouseover === 3) { + assert.equal(e.target, rect2); + } + }); + + stage._pointerover({ + clientX: 0, + clientY: 0, + type: 'mouseover', + }); + + assert.equal(mouseover, 1, 'initial over'); + simulateMouseMove(stage, { + x: 10, + y: 10, + }); + + assert.equal(mouseover, 1, 'moved inside stage - no new over events'); + + simulateMouseMove(stage, { + x: 60, + y: 60, + }); + + assert.equal(mouseover, 2, 'moved into inner shape, trigger new mouseover'); + + simulateMouseMove(stage, { + x: 110, + y: 110, + }); + + assert.equal( + mouseover, + 3, + 'moved into second shape, trigger new mouseover' + ); + + simulateMouseMove(stage, { + x: 10, + y: 10, + }); + + assert.equal( + mouseover, + 4, + 'moved to empty space shape, trigger new mouseover' + ); + }); + + it('toCanvas in sync way', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + fill: 'black', + radius: 50, + }); + layer.add(circle); + stage.add(layer); + + compareCanvases(stage.toCanvas(), layer.toCanvas(), 200); + }); + + it('listen to mouseleave event on container', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + fill: 'black', + radius: 50, + }); + layer.add(circle); + stage.add(layer); + + var count = 0; + stage.on('mouseleave', function () { + count += 1; + }); + stage.on('mouseout', function () { + count += 1; + }); + stage._pointerleave({ type: 'mouseleave' }); + assert.equal(count, 2); + }); + + it('toDataURL with hidden layer', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + fill: 'red', + radius: 50, + }); + layer.add(circle); + stage.add(layer); + + var stageDataUrl = stage.toDataURL(); + layer.visible(false); + assert.equal(stage.toDataURL() === stageDataUrl, false); + }); + + it('toDataURL works as toCanvas', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + fill: 'red', + radius: 50, + }); + layer.add(circle); + stage.add(layer); + + assert.equal(stage.toDataURL(), stage.toCanvas().toDataURL()); + }); + + it('toDataURL should no relate on stage size', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + fill: 'red', + radius: stage.height() * 0.6, + }); + layer.add(circle); + stage.add(layer); + + compareCanvases(stage.toCanvas(circle.getClientRect()), circle.toCanvas()); + }); + + it('toCanvas with large size', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var radius = stage.height() / 2 + 10; + var circle = new Konva.Circle({ + x: stage.height() / 2, + y: stage.height() / 2, + fill: 'black', + radius: radius, + }); + layer.add(circle); + stage.add(layer); + + var stageCanvas = stage.toCanvas({ + x: -10, + y: -10, + width: stage.height() + 20, + height: stage.height() + 20, + }); + + var canvas = createCanvas(); + canvas.width = radius * 2; + canvas.height = radius * 2; + var context = canvas.getContext('2d'); + context.beginPath(); + context.arc(radius, radius, radius, 0, 2 * Math.PI); + context.fillStyle = 'black'; + context.fill(); + compareCanvases(stageCanvas, canvas, 100); + }); + + it('toImage with large size', async function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var radius = stage.height() / 2 + 10; + var circle = new Konva.Circle({ + x: stage.height() / 2, + y: stage.height() / 2, + fill: 'black', + radius: radius, + }); + layer.add(circle); + stage.add(layer); + + if (isBrowser) { + try { + const img = await stage.toImage({ + x: -10, + y: -10, + width: stage.height() + 20, + height: stage.height() + 20, + callback: (img) => + assert.isTrue(img instanceof Image, 'not an image'), + }); + assert.isTrue(img instanceof Image, 'not an image'); + } catch (e) { + console.error(e); + assert.fail('error creating image'); + } + } + }); + + it('toBlob with large size', async function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var radius = stage.height() / 2 + 10; + var circle = new Konva.Circle({ + x: stage.height() / 2, + y: stage.height() / 2, + fill: 'black', + radius: radius, + }); + layer.add(circle); + stage.add(layer); + + if (isBrowser) { + try { + const blob = await stage.toBlob({ + x: -10, + y: -10, + width: stage.height() + 20, + height: stage.height() + 20, + callback: (blob) => + assert.isTrue( + blob instanceof Blob && blob.size > 0, + 'blob is empty' + ), + }); + assert.isTrue(blob instanceof Blob && blob.size > 0, 'blob is empty'); + } catch (e) { + console.error(e); + assert.fail('error creating blob'); + } + } + }); + + it('toBlob with mimeType option using', async function () { + const stage = addStage(); + const layer = new Konva.Layer(); + + stage.add(layer); + + if (isBrowser) { + try { + const blob = await stage.toBlob({ + mimeType: 'image/jpeg', + quality: 0.5, + }); + assert.isTrue(blob instanceof Blob && blob.type === 'image/jpeg', "can't change type of blob"); + } catch (e) { + console.error(e); + assert.fail('error creating blob'); + } + } + }); + + it('check hit graph with stage listening property', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + showHit(layer); + var circle = new Konva.Circle({ + fill: 'green', + radius: 50, + }); + layer.add(circle); + + var pos = { + x: stage.width() / 2, + y: stage.height() / 2, + }; + circle.position(pos); + stage.draw(); + + // try to detect circle via hit graph + assert.equal(stage.getIntersection(pos), circle, 'has circle'); + + // disable hit graph + stage.listening(false); + stage.draw(); + assert.equal(!!stage.getIntersection(pos), false, 'no circle'); + + // enable it again + stage.listening(true); + stage.draw(); + assert.equal(stage.getIntersection(pos), circle, 'circle again'); + }); + + it('toDataURL should use pixelRatio 1 by default', function (done) { + var stage = addStage(); + + var url = stage.toDataURL(); + var image = Konva.Util.createImageElement(); + image.onload = function () { + assert.equal(image.width, stage.width()); + assert.equal(image.height, stage.height()); + done(); + }; + image.src = url; + }); + + it('show a warning if the stage has too many layers', function () { + var stage = addStage(); + var oldWarn = Konva.Util.warn; + var called = false; + Konva.Util.warn = function () { + called = true; + }; + + // let say 5 is max number + stage.add(new Konva.Layer()); + stage.add(new Konva.Layer()); + stage.add(new Konva.Layer()); + stage.add(new Konva.Layer()); + stage.add(new Konva.Layer()); + stage.add(new Konva.Layer()); + stage.add(new Konva.Layer()); + + Konva.Util.warn = oldWarn; + assert.equal(called, true); + }); +}); diff --git a/test/unit/Star-test.ts b/test/unit/Star-test.ts new file mode 100644 index 000000000..52cc3fd9c --- /dev/null +++ b/test/unit/Star-test.ts @@ -0,0 +1,153 @@ +import { assert } from 'chai'; + +import { addStage, Konva, cloneAndCompareLayer } from './test-utils'; + +describe('Star', function () { + // ====================================================== + it('add five point star', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + + var star = new Konva.Star({ + x: 200, + y: 100, + numPoints: 5, + innerRadius: 40, + outerRadius: 70, + fill: 'green', + stroke: 'blue', + strokeWidth: 5, + name: 'foobar', + center: { + x: 0, + y: -70, + }, + scale: { + x: 0.5, + y: 0.5, + }, + }); + + layer.add(star); + stage.add(layer); + + assert.equal(star.getClassName(), 'Star'); + }); + + // ====================================================== + it('add star with line join and shadow', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var rect = new Konva.Rect({ + x: 250, + y: 75, + width: 100, + height: 100, + fill: 'red', + }); + + var star = new Konva.Star({ + x: 200, + y: 100, + numPoints: 5, + innerRadius: 40, + outerRadius: 70, + fill: 'green', + stroke: 'blue', + strokeWidth: 5, + lineJoin: 'round', + shadowColor: 'black', + shadowBlur: 10, + shadowOffset: { x: 20, y: 20 }, + shadowOpacity: 0.5, + draggable: true, + }); + + layer.add(rect); + layer.add(star); + + stage.add(layer); + + assert.equal(star.lineJoin(), 'round'); + star.lineJoin('bevel'); + assert.equal(star.lineJoin(), 'bevel'); + + star.lineJoin('round'); + }); + + // ====================================================== + it('attr sync', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var star = new Konva.Star({ + x: 200, + y: 100, + numPoints: 5, + innerRadius: 30, + outerRadius: 50, + fill: 'green', + stroke: 'blue', + strokeWidth: 5, + lineJoin: 'round', + shadowColor: 'black', + shadowBlur: 10, + shadowOffset: { x: 20, y: 20 }, + shadowOpacity: 0.5, + draggable: true, + }); + + layer.add(star); + + stage.add(layer); + + assert.equal(star.getWidth(), 100); + assert.equal(star.getHeight(), 100); + + star.setWidth(120); + assert.equal(star.outerRadius(), 60); + assert.equal(star.getHeight(), 120); + + star.setHeight(140); + assert.equal(star.outerRadius(), 70); + assert.equal(star.getHeight(), 140); + }); + + // ====================================================== + it('star cache', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var star = new Konva.Star({ + x: 200, + y: 100, + numPoints: 5, + innerRadius: 30, + outerRadius: 50, + fill: 'green', + stroke: 'black', + strokeWidth: 5, + lineJoin: 'round', + shadowColor: 'black', + shadowBlur: 10, + shadowOffset: { x: 20, y: 20 }, + shadowOpacity: 0.5, + draggable: true, + }); + + layer.add(star); + + stage.add(layer); + star.cache(); + + assert.deepEqual(star.getSelfRect(), { + x: -50, + y: -50, + height: 100, + width: 100, + }); + cloneAndCompareLayer(layer, 100); + }); +}); diff --git a/test/unit/Text-test.ts b/test/unit/Text-test.ts new file mode 100644 index 000000000..6f5ae0016 --- /dev/null +++ b/test/unit/Text-test.ts @@ -0,0 +1,1761 @@ +import { assert } from 'chai'; + +import { + addStage, + Konva, + createCanvas, + compareLayerAndCanvas, + compareLayers, + loadImage, + isBrowser, + isNode, + compareCanvases, +} from './test-utils'; + +describe('Text', function () { + // ====================================================== + it('text with empty config is allowed', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + stage.add(layer); + var text = new Konva.Text(); + + layer.add(text); + layer.draw(); + + var trace = + 'clearRect(0,0,578,200);clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);restore();'; + + assert.equal(layer.getContext().getTrace(), trace); + }); + + it('check text with FALSY values', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + stage.add(layer); + var text = new Konva.Text(); + + layer.add(text); + layer.draw(); + + text.text(0 as any); + assert.equal(text.text(), '0'); + + text.text(true as any); + assert.equal(text.text(), 'true'); + + text.text(false as any); + assert.equal(text.text(), 'false'); + + text.text(undefined); + assert.equal(text.text(), ''); + }); + + // ====================================================== + it('text with undefined text property should not throw an error', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + stage.add(layer); + var text = new Konva.Text({ text: undefined }); + + layer.add(text); + layer.draw(); + + assert.equal(text.getWidth(), 0); + }); + + it('add text with shadows', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var text = new Konva.Text({ + x: 40, + y: 40, + text: 'Hello World!', + fontSize: 50, + fontFamily: 'Arial', + fontStyle: 'normal', + fill: '#888', + stroke: '#333', + align: 'right', + shadowForStrokeEnabled: false, + lineHeight: 1.2, + width: 400, + height: 100, + padding: 10, + shadowColor: 'red', + shadowBlur: 1, + shadowOffset: { x: 10, y: 10 }, + shadowOpacity: 0.2, + }); + + var group = new Konva.Group({ + draggable: true, + }); + + group.add(text); + layer.add(group); + stage.add(layer); + + assert.equal( + layer.getContext().getTrace(false, true), + 'clearRect(0,0,578,200);save();transform(1,0,0,1,40,40);shadowColor=rgba(255,0,0,0.2);shadowBlur=1;shadowOffsetX=10;shadowOffsetY=10;font=normal normal 50px Arial;textBaseline=middle;textAlign=left;translate(10,10);save();fillStyle=#888;fillText(Hello World!,108,30);lineWidth=2;shadowColor=rgba(0,0,0,0);strokeStyle=#333;miterLimit=2;strokeText(Hello World!,108,30);restore();restore();' + ); + + assert.equal(text.getClassName(), 'Text', 'getClassName should be Text'); + }); + + it('text with fill and shadow', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var text = new Konva.Text({ + x: 10, + y: 10, + text: 'Hello World!', + fontSize: 50, + fill: 'black', + shadowColor: 'darkgrey', + shadowOffsetX: 0, + shadowOffsetY: 50, + shadowBlur: 0, + }); + + layer.add(text); + stage.add(layer); + + var canvas = createCanvas(); + var context = canvas.getContext('2d'); + context.textBaseline = 'middle'; + context.font = 'normal normal 50px Arial'; + context.fillStyle = 'darkgrey'; + context.fillText('Hello World!', 10, 10 + 50 + 25); + context.fillStyle = 'black'; + context.fillText('Hello World!', 10, 10 + 25); + + compareLayerAndCanvas(layer, canvas, 254, 200); + }); + + it('check emoji with letterSpacing', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var text = new Konva.Text({ + x: 10, + y: 10, + text: '😬', + fontSize: 50, + letterSpacing: 1, + }); + + layer.add(text); + stage.add(layer); + + var canvas = createCanvas(); + var context = canvas.getContext('2d'); + context.textBaseline = 'middle'; + context.font = 'normal normal 50px Arial'; + context.fillStyle = 'darkgrey'; + context.fillText('😬', 10, 10 + 25); + + compareLayerAndCanvas(layer, canvas, 254); + }); + + it('check hindi with letterSpacing', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var text = new Konva.Text({ + x: 10, + y: 10, + text: 'आपकी दौड़ के लिए परफेक्ट जूते!', + fontSize: 50, + letterSpacing: 10, + }); + + layer.add(text); + stage.add(layer); + + var canvas = createCanvas(); + var context = canvas.getContext('2d'); + context.textBaseline = 'middle'; + context.letterSpacing = '10px'; + context.font = 'normal normal 50px Arial'; + context.fillStyle = 'darkgrey'; + context.fillText('आपकी दौड़ के लिए परफेक्ट जूते!', 10, 10 + 25); + + if (isBrowser) { + compareLayerAndCanvas(layer, canvas, 254, 200); + } + }); + + it('text cache with fill and shadow', function () { + // TODO: on node-canvas it doesn't work + // text scaling is not correct + if (isNode) { + return; + } + var stage = addStage(); + var layer1 = new Konva.Layer(); + layer1.getCanvas().setPixelRatio(1); + stage.add(layer1); + + var text1 = new Konva.Text({ + x: 10, + y: 10, + text: 'some text', + fontSize: 50, + fill: 'black', + shadowColor: 'black', + shadowOffsetX: 0, + shadowOffsetY: 50, + opacity: 1, + shadowBlur: 10, + draggable: true, + }); + layer1.add(text1); + + var layer2 = new Konva.Layer(); + layer2.getCanvas().setPixelRatio(1); + + layer2.add(text1.clone().cache({ pixelRatio: 3 })); + stage.add(layer1, layer2); + + compareLayers(layer1, layer2, 250, 100); + }); + + it('text cache with fill and shadow and some scale', function () { + var stage = addStage(); + var layer1 = new Konva.Layer(); + stage.add(layer1); + + var text1 = new Konva.Text({ + x: 10, + y: 10, + text: 'some text', + fontSize: 50, + fill: 'black', + shadowColor: 'black', + shadowOffsetX: 0, + shadowOffsetY: 50, + opacity: 1, + shadowBlur: 10, + draggable: true, + }); + layer1.add(text1); + + var layer2 = new Konva.Layer({ + scaleX: 0.5, + scaleY: 0.5, + }); + stage.add(layer2); + + var group = new Konva.Group(); + layer2.add(group); + + var text2 = text1.clone(); + group.add(text2); + + text2.cache(); + group.scale({ x: 2, y: 2 }); + + stage.draw(); + + compareLayers(layer1, layer2, 200); + }); + + // ====================================================== + it('add text with letter spacing', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + stage.add(layer); + var text = new Konva.Text({ + text: 'hello', + }); + layer.add(text); + layer.draw(); + + var oldWidth = text.width(); + text.letterSpacing(10); + + assert.equal(text.width(), oldWidth + 40); + layer.draw(); + }); + // ====================================================== + it('text getters and setters', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var text = new Konva.Text({ + x: stage.width() / 2, + y: stage.height() / 2, + text: 'Hello World!', + fontSize: 50, + fontFamily: 'Calibri', + fontStyle: 'normal', + fontVariant: 'normal', + fill: '#888', + stroke: '#333', + align: 'right', + lineHeight: 1.2, + width: 400, + height: 100, + padding: 10, + shadowColor: 'black', + shadowBlur: 1, + shadowOffset: { x: 10, y: 10 }, + shadowOpacity: 0.2, + draggable: true, + }); + + // center text box + text.offsetX(text.getWidth() / 2); + + layer.add(text); + stage.add(layer); + + /* + * test getters and setters + */ + + assert.equal(text.x(), stage.width() / 2); + assert.equal(text.y(), stage.height() / 2); + assert.equal(text.text(), 'Hello World!'); + assert.equal(text.fontSize(), 50); + assert.equal(text.fontFamily(), 'Calibri'); + assert.equal(text.fontStyle(), 'normal'); + assert.equal(text.fontVariant(), 'normal'); + assert.equal(text.fill(), '#888'); + assert.equal(text.stroke(), '#333'); + assert.equal(text.align(), 'right'); + assert.equal(text.lineHeight(), 1.2); + assert.equal(text.getWidth(), 400); + assert.equal(text.height(), 100); + assert.equal(text.padding(), 10); + assert.equal(text.shadowColor(), 'black'); + assert.equal(text.draggable(), true); + assert.equal(text.getWidth(), 400); + assert.equal(text.height(), 100); + assert(text.getTextWidth() > 0, 'text width should be greater than 0'); + assert(text.fontSize() > 0, 'text height should be greater than 0'); + + text.x(1); + text.y(2); + text.text('bye world!'); + text.fontSize(10); + text.fontFamily('Arial'); + text.fontStyle('bold'); + text.fontVariant('small-caps'); + text.fill('green'); + text.stroke('yellow'); + text.align('left'); + text.width(300); + text.height(75); + text.padding(20); + text.shadowColor('green'); + text.setDraggable(false); + + assert.equal(text.x(), 1); + assert.equal(text.y(), 2); + assert.equal(text.text(), 'bye world!'); + assert.equal(text.fontSize(), 10); + assert.equal(text.fontFamily(), 'Arial'); + assert.equal(text.fontStyle(), 'bold'); + assert.equal(text.fontVariant(), 'small-caps'); + assert.equal(text.fill(), 'green'); + assert.equal(text.stroke(), 'yellow'); + assert.equal(text.align(), 'left'); + assert.equal(text.getWidth(), 300); + assert.equal(text.height(), 75); + assert.equal(text.padding(), 20); + assert.equal(text.shadowColor(), 'green'); + assert.equal(text.draggable(), false); + + // test set text to integer + text.text(5 as any); + + //document.body.appendChild(layer.bufferCanvas.element) + + //layer.setListening(false); + layer.drawHit(); + }); + + // ====================================================== + it('reset text auto width', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var text = new Konva.Text({ + text: 'Hello World!', + fontSize: 50, + draggable: true, + width: 10, + }); + + assert.equal(text.width(), 10); + text.setAttr('width', undefined); + assert.equal(text.width() > 100, true); + + layer.add(text); + stage.add(layer); + }); + + // ====================================================== + it('text multi line', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var rect = new Konva.Rect({ + x: 10, + y: 10, + width: 380, + height: 300, + fill: 'red', + }); + + var text = new Konva.Text({ + x: 10, + y: 10, + text: "HEADING\n\nAll the world's a stage, merely players. They have their exits and their entrances; And one man in his time plays many parts.", + //text: 'HEADING\n\nThis is a really cool paragraph. \n And this is a footer.', + fontSize: 14, + fontFamily: 'Calibri', + fontStyle: 'normal', + fill: '#555', + //width: 20, + width: 380, + //width: 200, + padding: 10, + lineHeight: 20, + align: 'center', + draggable: true, + wrap: 'WORD', + }); + + rect.height(text.height()); + // center text box + //text.setOffset(text.getBoxWidth() / 2, text.getBoxHeight() / 2); + + layer.add(rect).add(text); + stage.add(layer); + + assert.equal(text.lineHeight(), 20); + }); + + // ====================================================== + it('text single line with ellipsis', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var rect = new Konva.Rect({ + x: 10, + y: 10, + width: 380, + height: 300, + fill: 'red', + }); + + var text = new Konva.Text({ + x: 10, + y: 10, + text: "HEADING\n\nAll the world's a stage, merely players. They have their exits and their entrances; And one man in his time plays many parts.", + fontSize: 14, + fontFamily: 'Calibri', + fontStyle: 'normal', + fill: '#555', + width: 100, + padding: 0, + lineHeight: 20, + align: 'center', + wrap: 'none', + ellipsis: true, + }); + + layer.add(rect).add(text); + stage.add(layer); + + assert.equal(text.textArr.length, 3); + assert.equal(text.textArr[2].text.slice(-1), '…'); + }); + + // ====================================================== + it('text single line with ellipsis when there is no need in them', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var rect = new Konva.Rect({ + x: 10, + y: 10, + width: 380, + height: 300, + fill: 'red', + }); + + var text = new Konva.Text({ + width: 497, + height: 49, + text: 'Body text', + fill: 'black', + fontSize: 40, + shadowColor: 'black', + shadowOpacity: 1, + lineHeight: 1.2, + letterSpacing: 0, + ellipsis: true, + }); + + layer.add(rect).add(text); + stage.add(layer); + + assert.equal(text.textArr.length, 1); + assert.equal(text.textArr[0].text.slice(-1), 't'); + }); + + // ====================================================== + it('multiline with ellipsis', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var text = new Konva.Text({ + x: 10, + y: 10, + text: "HEADING\n\nAll the world's a stage, merely players. They have theirrrrrrr exits and theirrrrr entrances; And one man in his time plays many parts.", + fontSize: 14, + fontFamily: 'Arial', + fontStyle: 'normal', + width: 100, + padding: 0, + align: 'center', + height: 100, + ellipsis: true, + }); + + layer.add(text); + stage.add(layer); + + assert.equal(text.textArr.length, 7); + assert.equal(text.textArr[6].text.slice(-1), '…'); + + if (isBrowser) { + assert.equal( + layer.getContext().getTrace(false, true), + "clearRect(0,0,578,200);save();transform(1,0,0,1,10,10);font=normal normal 14px Arial;textBaseline=middle;textAlign=left;translate(0,0);save();fillStyle=black;fillText(HEADING,18,7);restore();save();fillStyle=black;fillText(,50,21);restore();save();fillStyle=black;fillText(All the world's,7,35);restore();save();fillStyle=black;fillText(a stage,,25,49);restore();save();fillStyle=black;fillText(merely,28,63);restore();save();fillStyle=black;fillText(players. They,7,77);restore();save();fillStyle=black;fillText(have…,27,91);restore();restore();" + ); + } + }); + + // ====================================================== + it('multiline with ellipsis and lineWidth less than maxWidth', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var text = new Konva.Text({ + x: 10, + y: 10, + text: "HEADING\nAll the\n world's a stage, merely players. They have theirrrrrrr exits and theirrrrr entrances; And one man in his time plays many parts.", + fontSize: 14, + fontFamily: 'Arial', + fontStyle: 'normal', + width: 100, + padding: 0, + align: 'center', + height: 30, + ellipsis: true, + }); + + layer.add(text); + stage.add(layer); + + assert.equal(text.textArr.length, 2); + assert.equal(text.textArr[1].text.slice(-1), '…'); + + if (isBrowser) { + assert.equal( + layer.getContext().getTrace(false, true), + 'clearRect(0,0,578,200);save();transform(1,0,0,1,10,10);font=normal normal 14px Arial;textBaseline=middle;textAlign=left;translate(0,0);save();fillStyle=black;fillText(HEADING,18,7);restore();save();fillStyle=black;fillText(All the…,23,21);restore();restore();' + ); + } + }); + + // ====================================================== + it('make sure we respect false for ellipsis', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var text = new Konva.Text({ + x: 10, + y: 10, + text: 'Hello foo bar', + wrap: 'word', + ellipsis: false, + width: 60, + height: 20, + }); + + layer.add(text); + stage.add(layer); + + assert.equal(text.textArr.length, 1); + assert.equal(text.textArr[0].text, 'Hello foo'); + }); + + // ====================================================== + it('wrap none check', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var text = new Konva.Text({ + x: 10, + y: 10, + text: 'Hello foo bar', + wrap: 'none', + ellipsis: false, + width: 60, + height: 20, + }); + + layer.add( + new Konva.Rect({ + ...text.getClientRect(), + fill: 'rgba(0, 0, 0, 0.4)', + }) + ); + + layer.add(text); + stage.add(layer); + + assert.equal(text.textArr.length, 1); + assert.equal(text.textArr[0].text, 'Hello foo b'); + + var trace = + 'clearRect(0,0,578,200);save();transform(1,0,0,1,10,10);beginPath();rect(0,0,60,20);closePath();fillStyle=rgba(0, 0, 0, 0.4);fill();restore();save();transform(1,0,0,1,10,10);font=normal normal 12px Arial;textBaseline=middle;textAlign=left;translate(0,0);save();fillStyle=black;fillText(Hello foo b,0,6);restore();restore();'; + + assert.equal(layer.getContext().getTrace(), trace); + }); + + // ====================================================== + it('text multi line with justify align', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var rect = new Konva.Rect({ + x: 10, + y: 10, + width: 380, + height: 300, + fill: 'yellow', + }); + + var text = new Konva.Text({ + x: 10, + y: 10, + text: "HEADING\n\n All the world's a stage, merely players. They have their exits and their entrances; And one man in his time plays many parts.", + fontSize: 14, + fontFamily: 'Arial', + fontStyle: 'normal', + fill: '#555', + width: 380, + align: 'justify', + letterSpacing: 5, + draggable: true, + }); + + rect.height(text.height()); + layer.add(rect).add(text); + + stage.add(layer); + + var trace = + 'fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();restore();save();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();restore();restore();'; + + assert.equal(layer.getContext().getTrace(true), trace); + }); + + it('text justify should not justify just one line', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var text = new Konva.Text({ + x: 10, + y: 10, + rotation: 0, + width: 500, + height: 58, + text: 'YOU ARE INVITED!', + fontSize: 30, + align: 'justify', + draggable: true, + }); + + layer.add(text); + + stage.add(layer); + + if (Konva.isBrowser) { + var trace = + 'clearRect(0,0,578,200);save();transform(1,0,0,1,10,10);font=normal normal 30px Arial;textBaseline=middle;textAlign=left;translate(0,0);save();fillStyle=black;fillText(Y,0,15);fillStyle=black;fillText(O,20,15);fillStyle=black;fillText(U,43,15);fillStyle=black;fillText( ,65,15);fillStyle=black;fillText(A,73,15);fillStyle=black;fillText(R,93,15);fillStyle=black;fillText(E,115,15);fillStyle=black;fillText( ,135,15);fillStyle=black;fillText(I,143,15);fillStyle=black;fillText(N,151,15);fillStyle=black;fillText(V,173,15);fillStyle=black;fillText(I,193,15);fillStyle=black;fillText(T,201,15);fillStyle=black;fillText(E,220,15);fillStyle=black;fillText(D,240,15);fillStyle=black;fillText(!,261,15);restore();restore();'; + + assert.equal(layer.getContext().getTrace(false, true), trace); + } + }); + + it('text multi line with justify align and several paragraphs', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var rect = new Konva.Rect({ + x: 10, + y: 10, + width: 380, + height: 300, + fill: 'yellow', + }); + + var text = new Konva.Text({ + x: 10, + y: 10, + text: "HEADING\n\n All the world's a stage, merely players. They have their exits and their entrances;\nAnd one man in his time plays many parts.", + fontSize: 14, + fontFamily: 'Arial', + fontStyle: 'normal', + fill: '#555', + width: 380, + align: 'justify', + letterSpacing: 5, + draggable: true, + }); + + rect.height(text.height()); + layer.add(rect).add(text); + + stage.add(layer); + + var trace = + 'fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();restore();save();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();restore();save();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();restore();restore();'; + + assert.equal(layer.getContext().getTrace(true), trace); + }); + + // ====================================================== + it('text multi line with justify align and decoration', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var rect = new Konva.Rect({ + x: 10, + y: 10, + width: 380, + height: 300, + fill: 'yellow', + }); + + var text = new Konva.Text({ + x: 10, + y: 10, + text: "HEADING\n\n All the world's a stage, merely players. They have their exits and their entrances; And one man in his time plays many parts.", + fontSize: 14, + fontFamily: 'Arial', + fontStyle: 'normal', + fill: '#555', + width: 380, + align: 'justify', + letterSpacing: 5, + textDecoration: 'underline line-through', + padding: 20, + draggable: true, + }); + + rect.height(text.height()); + + layer.add(rect).add(text); + + stage.add(layer); + + if (isNode) { + return; + } + + var trace = + 'fillText(;,106.482,77);fillStyle=#555;fillText( ,117.549,77);fillStyle=#555;fillText(A,126.438,77);fillStyle=#555;fillText(n,140.776,77);fillStyle=#555;fillText(d,153.563,77);fillStyle=#555;fillText( ,168.525,77);fillStyle=#555;fillText(o,177.415,77);fillStyle=#555;fillText(n,190.201,77);fillStyle=#555;fillText(e,202.987,77);fillStyle=#555;fillText( ,217.95,77);fillStyle=#555;fillText(m,226.84,77);fillStyle=#555;fillText(a,243.502,77);fillStyle=#555;fillText(n,256.288,77);fillStyle=#555;fillText( ,271.251,77);fillStyle=#555;fillText(i,280.141,77);fillStyle=#555;fillText(n,288.251,77);fillStyle=#555;fillText( ,303.214,77);fillStyle=#555;fillText(h,312.104,77);fillStyle=#555;fillText(i,324.89,77);fillStyle=#555;fillText(s,333,77);restore();save();save();beginPath();moveTo(0,98);lineTo(245,98);stroke();restore();save();beginPath();moveTo(0,91);lineTo(245,91);stroke();restore();fillStyle=#555;fillText(t,0,91);fillStyle=#555;fillText(i,8.89,91);fillStyle=#555;fillText(m,17,91);fillStyle=#555;fillText(e,33.662,91);fillStyle=#555;fillText( ,46.448,91);fillStyle=#555;fillText(p,55.338,91);fillStyle=#555;fillText(l,68.124,91);fillStyle=#555;fillText(a,76.234,91);fillStyle=#555;fillText(y,89.021,91);fillStyle=#555;fillText(s,101.021,91);fillStyle=#555;fillText( ,113.021,91);fillStyle=#555;fillText(m,121.91,91);fillStyle=#555;fillText(a,138.572,91);fillStyle=#555;fillText(n,151.358,91);fillStyle=#555;fillText(y,164.145,91);fillStyle=#555;fillText( ,176.145,91);fillStyle=#555;fillText(p,185.034,91);fillStyle=#555;fillText(a,197.82,91);fillStyle=#555;fillText(r,210.606,91);fillStyle=#555;fillText(t,220.269,91);fillStyle=#555;fillText(s,229.158,91);fillStyle=#555;fillText(.,241.158,91);restore();restore();'; + + assert.equal(layer.getContext().getTrace(), trace); + }); + + // ====================================================== + it('text multi line with shadows', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var text = new Konva.Text({ + x: 10, + y: 10, + //stroke: '#555', + //strokeWidth: 5, + text: "HEADING\n\nAll the world's a stage, and all the men and women merely players. They have their exits and their entrances; And one man in his time plays many parts.", + //text: 'HEADING\n\nThis is a really cool paragraph. \n And this is a footer.', + fontSize: 16, + fontFamily: 'Calibri', + fontStyle: 'normal', + fill: '#555', + //width: 20, + width: 380, + //width: 200, + padding: 20, + align: 'center', + shadowColor: 'red', + shadowBlur: 1, + shadowOffset: { x: 10, y: 10 }, + shadowOpacity: 0.5, + draggable: true, + }); + + layer.add(text); + stage.add(layer); + + if (isBrowser) { + assert.equal( + layer.getContext().getTrace(false, true), + "clearRect(0,0,578,200);save();transform(1,0,0,1,10,10);shadowColor=rgba(255,0,0,0.5);shadowBlur=1;shadowOffsetX=10;shadowOffsetY=10;font=normal normal 16px Calibri;textBaseline=middle;textAlign=left;translate(20,20);save();fillStyle=#555;fillText(HEADING,133,8);restore();save();fillStyle=#555;fillText(,170,24);restore();save();fillStyle=#555;fillText(All the world's a stage, and all the men and women,6,40);restore();save();fillStyle=#555;fillText(merely players. They have their exits and their,21,56);restore();save();fillStyle=#555;fillText(entrances; And one man in his time plays many,18,72);restore();save();fillStyle=#555;fillText(parts.,152,88);restore();restore();" + ); + } else { + // use relax, because in GitHub Actions calculations are too different + assert.equal( + layer.getContext().getTrace(true, true), + 'clearRect();save();transform();shadowColor;shadowBlur;shadowOffsetX;shadowOffsetY;font;textBaseline;textAlign;translate();save();fillStyle;fillText();restore();save();fillStyle;fillText();restore();save();fillStyle;fillText();restore();save();fillStyle;fillText();restore();save();fillStyle;fillText();restore();save();fillStyle;fillText();restore();restore();' + ); + } + }); + + // ====================================================== + it('text multi line with underline and spacing', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var text = new Konva.Text({ + x: 10, + y: 10, + text: 'hello\nworld', + fontSize: 80, + fill: 'red', + letterSpacing: 5, + textDecoration: 'underline', + draggable: true, + }); + + layer.add(text); + stage.add(layer); + + assert.equal( + layer.getContext().getTrace(true), + 'clearRect();save();transform();font;textBaseline;textAlign;translate();save();save();beginPath();moveTo();lineTo();stroke();restore();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();restore();save();save();beginPath();moveTo();lineTo();stroke();restore();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();fillStyle;fillText();restore();restore();' + ); + }); + + // ====================================================== + it('test text with crazy font families', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var text = new Konva.Text({ + text: 'hello', + fontFamily: 'Arial', + }); + layer.add(text); + layer.draw(); + + text.fontFamily('Font Awesome'); + layer.draw(); + text.fontFamily('Font Awesome, Arial'); + layer.draw(); + text.fontFamily('"Font Awesome", Arial'); + layer.draw(); + + var trace = + 'clearRect(0,0,578,200);clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);font=normal normal 12px Arial;textBaseline=middle;textAlign=left;translate(0,0);save();fillStyle=black;fillText(hello,0,6);restore();restore();clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);font=normal normal 12px "Font Awesome";textBaseline=middle;textAlign=left;translate(0,0);save();fillStyle=black;fillText(hello,0,6);restore();restore();clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);font=normal normal 12px "Font Awesome", Arial;textBaseline=middle;textAlign=left;translate(0,0);save();fillStyle=black;fillText(hello,0,6);restore();restore();clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);font=normal normal 12px "Font Awesome", Arial;textBaseline=middle;textAlign=left;translate(0,0);save();fillStyle=black;fillText(hello,0,6);restore();restore();'; + + assert.equal(layer.getContext().getTrace(), trace); + }); + + // ====================================================== + it('text with underline and large line height', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var text = new Konva.Text({ + fontFamily: 'Arial', + text: 'text', + fontSize: 80, + lineHeight: 2, + textDecoration: 'underline', + }); + + layer.add(text); + stage.add(layer); + + var canvas = createCanvas(); + var context = canvas.getContext('2d'); + context.translate(0, 80); + context.lineWidth = 2; + context.font = '80px Arial'; + context.textBaseline = 'middle'; + context.fillText('text', 0, 0); + context.beginPath(); + context.moveTo(0, 40); + context.lineTo(text.width(), 40); + context.lineWidth = 80 / 15; + context.stroke(); + compareLayerAndCanvas(layer, canvas, 50); + }); + + it('text multi line with strike', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var text = new Konva.Text({ + x: 10, + y: 10, + text: 'hello\nworld', + fontSize: 80, + fill: 'red', + textDecoration: 'line-through', + }); + + layer.add(text); + stage.add(layer); + + var trace = + 'clearRect();save();transform();font;textBaseline;textAlign;translate();save();save();beginPath();moveTo();lineTo();stroke();restore();fillStyle;fillText();restore();save();save();beginPath();moveTo();lineTo();stroke();restore();fillStyle;fillText();restore();restore();'; + assert.equal(layer.getContext().getTrace(true), trace); + }); + + it('text multi line with underline and strike', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var text = new Konva.Text({ + x: 10, + y: 10, + text: 'hello\nworld', + fontSize: 80, + fill: 'red', + textDecoration: 'underline line-through', + }); + + layer.add(text); + stage.add(layer); + + var trace = + 'clearRect();save();transform();font;textBaseline;textAlign;translate();save();save();beginPath();moveTo();lineTo();stroke();restore();save();beginPath();moveTo();lineTo();stroke();restore();fillStyle;fillText();restore();save();save();beginPath();moveTo();lineTo();stroke();restore();save();beginPath();moveTo();lineTo();stroke();restore();fillStyle;fillText();restore();restore();'; + + assert.equal(layer.getContext().getTrace(true), trace); + }); + + it('text multi line with underline and strike and gradient', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var text = new Konva.Text({ + x: 10, + y: 10, + text: 'hello\nworld', + fontSize: 80, + // fill: 'red', + fillPriority: 'linear-gradient', + fillLinearGradientStartPoint: { x: 0, y: 0 }, + fillLinearGradientEndPoint: { x: 100, y: 0 }, + fillLinearGradientColorStops: [0, 'red', 1, 'yellow'], + fillAfterStrokeEnabled: true, + textDecoration: 'underline line-through', + }); + + layer.add(text); + stage.add(layer); + + if (isNode) { + return; + } + var trace = + 'clearRect(0,0,578,200);save();transform(1,0,0,1,10,10);font=normal normal 80px Arial;textBaseline=middle;textAlign=left;translate(0,0);save();save();beginPath();moveTo(0,80);lineTo(169,80);stroke();restore();save();beginPath();moveTo(0,40);lineTo(169,40);stroke();restore();fillStyle=[object CanvasGradient];fillText(hello,0,40);restore();save();save();beginPath();moveTo(0,160);lineTo(191,160);stroke();restore();save();beginPath();moveTo(0,120);lineTo(191,120);stroke();restore();fillStyle=[object CanvasGradient];fillText(world,0,120);restore();restore();'; + + assert.equal(layer.getContext().getTrace(), trace); + }); + + it('text multi line with underline and strike and gradient vertical', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var text = new Konva.Text({ + x: 10, + y: 10, + text: 'hello\nworld', + fontSize: 80, + // fill: 'red', + fillPriority: 'linear-gradient', + fillLinearGradientStartPoint: { x: 0, y: 0 }, + fillLinearGradientEndPoint: { x: 0, y: 160 }, + fillLinearGradientColorStops: [0, 'red', 1, 'yellow'], + fillAfterStrokeEnabled: true, + textDecoration: 'underline line-through', + }); + + layer.add(text); + stage.add(layer); + + if (isNode) { + return; + } + + var trace = + 'clearRect(0,0,578,200);save();transform(1,0,0,1,10,10);font=normal normal 80px Arial;textBaseline=middle;textAlign=left;translate(0,0);save();save();beginPath();moveTo(0,80);lineTo(169,80);stroke();restore();save();beginPath();moveTo(0,40);lineTo(169,40);stroke();restore();fillStyle=[object CanvasGradient];fillText(hello,0,40);restore();save();save();beginPath();moveTo(0,160);lineTo(191,160);stroke();restore();save();beginPath();moveTo(0,120);lineTo(191,120);stroke();restore();fillStyle=[object CanvasGradient];fillText(world,0,120);restore();restore();'; + + assert.equal(layer.getContext().getTrace(), trace); + }); + + it('text with underline and shadow', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var text = new Konva.Text({ + text: 'Test', + fill: 'black', + fontSize: 40, + textDecoration: 'underline', + shadowEnabled: true, + shadowColor: 'red', + shadowOffsetX: 15, + shadowOffsetY: 15, + }); + + layer.add(text); + stage.add(layer); + + var trace = + 'clearRect();save();shadowColor;shadowBlur;shadowOffsetX;shadowOffsetY;drawImage();restore();'; + + assert.equal(layer.getContext().getTrace(true), trace); + + // now check result visually + // text with red shadow is the same as red text with back text on top + const group = new Konva.Group({}); + layer.add(group); + group.add(text.clone({ shadowEnabled: false, x: 15, y: 15, fill: 'red' })); + group.add(text.clone({ shadowEnabled: false })); + const groupCanvas = group.toCanvas(); + + compareCanvases(groupCanvas, text.toCanvas(), 200); + }); + + it('text with line-through and shadow', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var text = new Konva.Text({ + text: 'Test', + fill: 'black', + fontSize: 40, + textDecoration: 'line-through', + shadowEnabled: true, + shadowColor: 'red', + shadowOffsetX: 5, + shadowOffsetY: 5, + }); + + layer.add(text); + stage.add(layer); + + var trace = + 'clearRect();save();shadowColor;shadowBlur;shadowOffsetX;shadowOffsetY;drawImage();restore();'; + + assert.equal(layer.getContext().getTrace(true), trace); + + // now check result visually + // text with red shadow is the same as red text with back text on top + const group = new Konva.Group({}); + layer.add(group); + group.add(text.clone({ shadowEnabled: false, x: 5, y: 5, fill: 'red' })); + group.add(text.clone({ shadowEnabled: false })); + const groupCanvas = group.toCanvas(); + + compareCanvases(groupCanvas, text.toCanvas(), 200, 50); + }); + + // ====================================================== + it('change font size should update text data', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var text = new Konva.Text({ + x: 10, + y: 10, + text: 'Some awesome text', + fontSize: 16, + fontFamily: 'Calibri', + fontStyle: 'normal', + fill: '#555', + align: 'center', + padding: 5, + draggable: true, + }); + + var width = text.getWidth(); + var height = text.height(); + + layer.add(text); + stage.add(layer); + + text.fontSize(30); + layer.draw(); + + assert(text.getWidth() > width, 'width should have increased'); + assert(text.height() > height, 'height should have increased'); + }); + + it('text vertical align', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var rect = new Konva.Rect({ + x: 10, + y: 10, + width: 200, + height: 100, + stroke: 'black', + }); + layer.add(rect); + + var text = new Konva.Text({ + x: rect.x(), + y: rect.y(), + width: rect.width(), + height: rect.height(), + text: 'Some awesome text', + fontSize: 16, + fill: '#555', + align: 'center', + padding: 10, + draggable: true, + }); + + assert.equal(text.verticalAlign(), 'top'); + + text.verticalAlign('middle'); + + layer.add(text); + stage.add(layer); + + if (isBrowser) { + assert.equal( + layer.getContext().getTrace(false, true), + 'clearRect(0,0,578,200);save();transform(1,0,0,1,10,10);beginPath();rect(0,0,200,100);closePath();lineWidth=2;strokeStyle=black;stroke();restore();save();transform(1,0,0,1,10,10);font=normal normal 16px Arial;textBaseline=middle;textAlign=left;translate(10,42);save();fillStyle=#555;fillText(Some awesome text,17,8);restore();restore();' + ); + } else { + assert.equal( + layer.getContext().getTrace(false, true), + 'clearRect(0,0,578,200);save();transform(1,0,0,1,10,10);beginPath();rect(0,0,200,100);closePath();lineWidth=2;strokeStyle=black;stroke();restore();save();transform(1,0,0,1,10,10);font=normal normal 16px Arial;textBaseline=middle;textAlign=left;translate(10,42);save();fillStyle=#555;fillText(Some awesome text,17,8);restore();restore();' + ); + } + }); + + it('get text width', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + var text = new Konva.Text({ + text: 'hello asd fasdf asdf asd fasdf asdfasd fa sds helloo', + fill: 'black', + width: 100, + }); + + layer.add(text); + layer.draw(); + assert.equal(text.getTextWidth() > 0 && text.getTextWidth() < 100, true); + }); + + it('get text width of long text with spacing (check it visually!)', function () { + var stage = addStage(); + stage.draggable(true); + var layer = new Konva.Layer(); + stage.add(layer); + + var textProps = { + x: 10, + y: 10, + fontSize: 19, + text: 'very long text, very long text, very long text, very long text, very long text, very long text, very long text, very long text, very long text, very long text, very long text, very long text, very long text, very long text.', + draggable: true, + }; + + var text1 = new Konva.Text(textProps); + layer.add(text1); + var box1 = new Konva.Rect( + Object.assign(text1.getClientRect(), { stroke: 'black' }) + ); + layer.add(box1); + + // demo2 + var text2 = new Konva.Text( + Object.assign(textProps, { letterSpacing: 4, y: 50 }) + ); + layer.add(text2); + var box2 = new Konva.Rect( + Object.assign(text2.getClientRect(), { stroke: 'black' }) + ); + layer.add(box2); + + // demo3 + + var text3 = new Konva.Text( + Object.assign(textProps, { + text: 'gregrg44g4g4g4g4g4g4g4regrg44g4g4g4g4g4g4g4regrg44g4g4g4g4g4g4g4regrg44g4g4g4g4g4g4g4regrg44g4g4g4g4g4g4g4regrg44g4g4g4g4g4g4g4regrg4g4g4', + letterSpacing: 4, + fontSize: 20, + y: 100, + }) + ); + layer.add(text3); + var box3 = new Konva.Rect( + Object.assign(text3.getClientRect(), { stroke: 'black' }) + ); + layer.add(box3); + + // demo4 + var text4 = new Konva.Text( + Object.assign(textProps, { + text: 'gregrg44g4g4g4g4g4g4g4regrg44g4g4g4g4g4g4g4regrg44g4g4g4g4g4g4g4regrg44g4g4g4g4g4g4g4regrg44g4g4g4g4g4g4g4regrg44g4g4g4g4g4g4g4regrg4g4g4', + letterSpacing: 4, + fontSize: 19, + y: 150, + }) + ); + layer.add(text4); + var box4 = new Konva.Rect( + Object.assign(text4.getClientRect(), { stroke: 'black' }) + ); + layer.add(box4); + + layer.draw(); + + // on nodejs the length is very different + // so we need to adjust offset + const diff = isBrowser ? 4 : 50; + assert.equal(Math.abs(Math.round(text1.width()) - 1725) < diff, true); + assert.equal(Math.abs(Math.round(text2.width()) - 2613) < diff, true); + assert.equal(Math.abs(Math.round(text3.width()) - 2005) < diff, true); + assert.equal(Math.abs(Math.round(text4.width()) - 1932) < diff, true); + }); + + it('default text color should be black', function () { + var text = new Konva.Text(); + assert.equal(text.fill(), 'black'); + }); + + it('text with stoke and strokeScaleEnabled', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var text = new Konva.Text({ + fontSize: 50, + y: 50, + x: 50, + fill: 'black', + text: 'text', + stroke: 'red', + strokeScaleEnabled: false, + strokeWidth: 2, + scaleX: 2, + }); + layer.add(text); + stage.add(layer); + + var canvas = createCanvas(); + var context = canvas.getContext('2d'); + context.translate(50, 50); + context.lineWidth = 2; + context.font = '50px Arial'; + context.strokeStyle = 'red'; + context.scale(2, 1); + context.textBaseline = 'middle'; + context.fillText('text', 0, 25); + context.miterLimit = 2; + context.strokeText('text', 0, 25); + compareLayerAndCanvas(layer, canvas); + }); + + it('text getSelfRect', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var text = new Konva.Text({ + fontSize: 50, + y: 50, + x: 50, + fill: 'black', + text: 'text', + }); + + layer.add(text); + stage.add(layer); + + var rect = text.getSelfRect(); + + assert.deepEqual(rect, { + x: 0, + y: 0, + width: text.width(), + height: 50, + }); + }); + + it('linear gradient', function () { + // Konva.pixelRatio = 1; + var stage = addStage(); + var layer = new Konva.Layer(); + + var text = new Konva.Text({ + fontSize: 50, + fillLinearGradientStartPoint: { x: 0, y: 0 }, + fillLinearGradientEndPoint: { x: 300, y: 0 }, + fillLinearGradientColorStops: [0, 'black', 1, 'red'], + text: 'Text with gradient!!', + draggable: true, + }); + layer.add(text); + stage.add(layer); + + var canvas = createCanvas(); + var ctx = canvas.getContext('2d'); + + ctx.fillStyle = 'green'; + ctx.font = 'normal 50px Arial'; + ctx.textBaseline = 'middle'; + + var start = { x: 0, y: 0 }; + var end = { x: 300, y: 0 }; + var colorStops = [0, 'black', 1, 'red']; + var grd = ctx.createLinearGradient(start.x, start.y, end.x, end.y); + + // build color stops + for (var n = 0; n < colorStops.length; n += 2) { + grd.addColorStop(colorStops[n] as number, colorStops[n + 1] as string); + } + ctx.fillStyle = grd; + + ctx.fillText(text.text(), text.x(), text.y() + text.fontSize() / 2); + + compareLayerAndCanvas(layer, canvas, 200); + }); + + it('linear gradient multiline', function () { + const oldRatio = Konva.pixelRatio; + Konva.pixelRatio = 1; + var stage = addStage(); + var layer = new Konva.Layer(); + + var text = new Konva.Text({ + fontSize: 50, + fillLinearGradientStartPoint: { x: 0, y: 0 }, + fillLinearGradientEndPoint: { x: 0, y: 100 }, + fillLinearGradientColorStops: [0, 'yellow', 1, 'red'], + text: 'Text with gradient!!\nText with gradient!!', + draggable: true, + }); + layer.add(text); + stage.add(layer); + + var canvas = createCanvas(); + var ctx = canvas.getContext('2d'); + + ctx.fillStyle = 'green'; + ctx.font = 'normal 50px Arial'; + ctx.textBaseline = 'middle'; + + var start = { x: 0, y: 0 }; + var end = { x: 0, y: 100 }; + var colorStops = [0, 'yellow', 1, 'red']; + var grd = ctx.createLinearGradient(start.x, start.y, end.x, end.y); + + // build color stops + for (var n = 0; n < colorStops.length; n += 2) { + grd.addColorStop(colorStops[n] as number, colorStops[n + 1] as string); + } + ctx.fillStyle = grd; + + ctx.fillText( + 'Text with gradient!!', + text.x(), + text.y() + text.fontSize() / 2 + ); + ctx.fillText( + 'Text with gradient!!', + text.x(), + text.y() + text.fontSize() / 2 + text.fontSize() + ); + + compareLayerAndCanvas(layer, canvas, 200); + + var data = layer.getContext().getImageData(25, 41, 1, 1).data; + Konva.pixelRatio = oldRatio; + }); + + it('radial gradient', function () { + const oldRatio = Konva.pixelRatio; + Konva.pixelRatio = 1; + var stage = addStage(); + var layer = new Konva.Layer(); + + var text = new Konva.Text({ + fontSize: 50, + y: 0, + x: 0, + fillRadialGradientStartPoint: { x: 100, y: 0 }, + fillRadialGradientStartRadius: 0, + fillRadialGradientEndRadius: 100, + fillRadialGradientEndPoint: { x: 100, y: 0 }, + fillRadialGradientColorStops: [0, 'yellow', 1, 'red'], + text: 'Text with gradient!!', + draggable: true, + }); + layer.add(text); + stage.add(layer); + + var canvas = createCanvas(); + var ctx = canvas.getContext('2d'); + + ctx.fillStyle = 'green'; + ctx.font = 'normal 50px Arial'; + ctx.textBaseline = 'middle'; + + var start = { x: 100, y: 0 }; + var end = { x: 100, y: 0 }; + var colorStops = [0, 'yellow', 1, 'red']; + var grd = ctx.createRadialGradient(start.x, start.y, 0, end.x, end.y, 100); + + // build color stops + for (var n = 0; n < colorStops.length; n += 2) { + grd.addColorStop(colorStops[n] as number, colorStops[n + 1] as string); + } + ctx.fillStyle = grd; + + ctx.translate(0, 25); + + ctx.fillText(text.text(), 0, 0); + + Konva.pixelRatio = oldRatio; + + compareLayerAndCanvas(layer, canvas, 100, 30); + }); + + it('text should be centered in line height', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + layer.add( + new Konva.Rect({ + stroke: 'black', + width: 100, + height: 40 * 3, + }) + ); + + var text = new Konva.Text({ + fontSize: 40, + text: 'Some good text', + lineHeight: 3, + draggable: true, + }); + layer.add(text); + stage.add(layer); + + // this text should look like it is positioned in y = 40 + + var trace = + 'clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);beginPath();rect(0,0,100,120);closePath();lineWidth=2;strokeStyle=black;stroke();restore();save();transform(1,0,0,1,0,0);font=normal normal 40px Arial;textBaseline=middle;textAlign=left;translate(0,0);save();fillStyle=black;fillText(Some good text,0,60);restore();restore();'; + + assert.equal(layer.getContext().getTrace(), trace); + }); + + it('check wrapping', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var text = new Konva.Text({ + fontSize: 40, + text: 'Hello, this is some good text', + width: 185, + draggable: true, + }); + layer.add(text); + stage.add(layer); + + var lines = text.textArr; + + // first line should fit "Hello, this" + // I faced this issue in large app + // we should draw as much text in one line, as possible + // so Konva.Text + textarea editing works better + assert.equal(lines[0].text, 'Hello, this'); + }); + + it('check trip when go to new line', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var text = new Konva.Text({ + text: 'Hello, this is some good text', + fontSize: 30, + }); + layer.add(text); + stage.add(layer); + + text.width(245); + + var lines = text.textArr; + + // remove all trimming spaces + // it also looks better in many cases + // it will work as text in div + assert.equal(lines[0].text, 'Hello, this is some'); + assert.equal(lines[1].text, 'good text'); + + text.width(261); + var lines = text.textArr; + + assert.equal(lines[0].text, 'Hello, this is some'); + assert.equal(lines[1].text, 'good text'); + layer.draw(); + }); + + it('image gradient for text', function (done) { + const oldRatio = Konva.pixelRatio; + Konva.pixelRatio = 1; + loadImage('darth-vader.jpg', (imageObj) => { + var stage = addStage(); + var layer = new Konva.Layer(); + + var text = new Konva.Text({ + text: 'Hello, this is some good text', + fontSize: 30, + fillPatternImage: imageObj, + }); + layer.add(text); + stage.add(layer); + + var canvas = createCanvas(); + var ctx = canvas.getContext('2d'); + + ctx.fillStyle = 'green'; + ctx.font = 'normal normal 30px Arial'; + ctx.textBaseline = 'middle'; + + var grd = ctx.createPattern(imageObj, 'repeat'); + ctx.fillStyle = grd; + + ctx.fillText(text.text(), 0, 15); + + compareLayerAndCanvas(layer, canvas, 200); + Konva.pixelRatio = oldRatio; + done(); + }); + }); + + it('image gradient for text with offset', function (done) { + if (isNode) { + // skip in NodeJS because it has not transform API on gradients + return done(); + } + const oldRatio = Konva.pixelRatio; + Konva.pixelRatio = 1; + loadImage('darth-vader.jpg', (imageObj) => { + var stage = addStage(); + var layer = new Konva.Layer(); + + var text = new Konva.Text({ + text: 'Hello, this is some good text', + fontSize: 30, + fillPatternImage: imageObj, + fillPatternOffsetX: 50, + fillPatternRotation: 0, + }); + layer.add(text); + stage.add(layer); + + var canvas = createCanvas(); + var ctx = canvas.getContext('2d'); + + ctx.fillStyle = 'green'; + ctx.font = 'normal normal 30px Arial'; + ctx.textBaseline = 'middle'; + + var grd = ctx.createPattern(imageObj, 'repeat'); + grd.setTransform({ + a: 1, + b: 0, + c: 0, + d: 1, + e: -50, + f: 0, + }); + ctx.fillStyle = grd; + + ctx.fillText(text.text(), 0, 15); + + compareLayerAndCanvas(layer, canvas, 200); + Konva.pixelRatio = oldRatio; + done(); + }); + }); + + it('image gradient for text with scale', function (done) { + const oldRatio = Konva.pixelRatio; + Konva.pixelRatio = 1; + loadImage('darth-vader.jpg', (imageObj) => { + var stage = addStage(); + var layer = new Konva.Layer(); + + var text = new Konva.Text({ + text: 'Hello, this is some good text', + fontSize: 30, + fillPatternImage: imageObj, + fillPatternScaleX: 0.5, + fillPatternScaleY: 0.5, + }); + layer.add(text); + stage.add(layer); + + var canvas = createCanvas(); + var ctx = canvas.getContext('2d'); + + ctx.fillStyle = 'green'; + ctx.font = 'normal normal 30px Arial'; + ctx.textBaseline = 'middle'; + + var grd = ctx.createPattern(imageObj, 'repeat'); + const matrix = + typeof DOMMatrix === 'undefined' + ? { + a: 0.5, // Horizontal scaling. A value of 1 results in no scaling. + b: 0, // Vertical skewing. + c: 0, // Horizontal skewing. + d: 0.5, + e: 0, // Horizontal translation (moving). + f: 0, // Vertical translation (moving). + } + : new DOMMatrix([0.5, 0, 0, 0.5, 0, 0]); + + grd.setTransform(matrix); + + ctx.fillStyle = grd; + + ctx.fillText(text.text(), 0, 15); + + compareLayerAndCanvas(layer, canvas, 200); + Konva.pixelRatio = oldRatio; + done(); + }); + }); + + it('stripe bad stroke', () => { + var stage = addStage(); + var layer = new Konva.Layer(); + + stage.add(layer); + var text = new Konva.Text({ + text: 'HELLO WORLD', + fontFamily: 'Arial', + fontSize: 80, + stroke: 'red', + strokeWidth: 20, + fillAfterStrokeEnabled: true, + draggable: true, + }); + + layer.add(text); + layer.draw(); + + var trace = + 'clearRect(0,0,578,200);clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);font=normal normal 80px Arial;textBaseline=middle;textAlign=left;translate(0,0);save();lineWidth=20;strokeStyle=red;miterLimit=2;strokeText(HELLO WORLD,0,40);fillStyle=black;fillText(HELLO WORLD,0,40);restore();restore();'; + + assert.equal(layer.getContext().getTrace(), trace); + }); + + it('sets ltr text direction', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + stage.add(layer); + var text = new Konva.Text({ + text: 'ltr text', + direction: 'ltr', + }); + + layer.add(text); + layer.draw(); + + var trace = + 'clearRect(0,0,578,200);clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);font=normal normal 12px Arial;textBaseline=middle;textAlign=left;translate(0,0);save();fillStyle=black;fillText(ltr text,0,6);restore();restore();'; + + assert.equal(layer.getContext().getTrace(), trace); + }); + + it('sets rtl text direction', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + stage.add(layer); + var text = new Konva.Text({ + text: 'rtl text', + direction: 'rtl', + }); + + layer.add(text); + layer.draw(); + + var trace = + 'clearRect(0,0,578,200);clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);direction=rtl;font=normal normal 12px Arial;textBaseline=middle;textAlign=left;translate(0,0);save();fillStyle=black;fillText(rtl text,0,6);restore();restore();'; + + assert.equal(layer.getContext().getTrace(), trace); + }); + + it('sets rtl text direction with letterSpacing', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + stage.add(layer); + var text = new Konva.Text({ + text: 'rtl text', + direction: 'rtl', + letterSpacing: 2, + }); + + layer.add(text); + layer.draw(); + + var trace = + 'clearRect(0,0,578,200);clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);direction=rtl;font=normal normal 12px Arial;textBaseline=middle;textAlign=left;translate(0,0);save();letterSpacing=2px;fillStyle=black;fillText(rtl text,0,6);restore();restore();'; + + assert.equal(layer.getContext().getTrace(), trace); + }); + + it('try fixed render', () => { + Konva._fixTextRendering = true; + var stage = addStage(); + var layer = new Konva.Layer(); + + stage.add(layer); + var text = new Konva.Text({ text: 'hello', fontSize: 100 }); + + layer.add(text); + layer.draw(); + Konva._fixTextRendering = false; + + const trace = + 'clearRect(0,0,578,200);clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);font=normal normal 100px Arial;textBaseline=alphabetic;textAlign=left;translate(0,0);save();fillStyle=black;fillText(hello,0,85);restore();restore();'; + + assert.equal(layer.getContext().getTrace(), trace); + }); +}); diff --git a/test/unit/TextPath-test.ts b/test/unit/TextPath-test.ts new file mode 100644 index 000000000..e03a40950 --- /dev/null +++ b/test/unit/TextPath-test.ts @@ -0,0 +1,906 @@ +import { assert } from 'chai'; + +import { addStage, Konva, cloneAndCompareLayer, isBrowser } from './test-utils'; + +describe('TextPath', function () { + // ====================================================== + it('Render Text Along Line', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var c = 'M 10,10 300,150'; + + var path = new Konva.Path({ + stroke: 'red', + strokeWidth: 1, + data: c, + }); + + layer.add(path); + + var textpath = new Konva.TextPath({ + fill: 'orange', + fontSize: 24, + fontFamily: 'Arial', + text: "The quick brown fox jumped over the lazy dog's back", + data: c, + }); + textpath.on('mouseover', function () { + this.fill('blue'); + layer.drawScene(); + }); + textpath.on('mouseout', function () { + this.fill('orange'); + layer.drawScene(); + }); + + layer.add(textpath); + stage.add(layer); + + assert.equal( + textpath.getClassName(), + 'TextPath', + 'getClassName should be TextPath' + ); + + var trace = layer.getContext().getTrace(true); + //console.log(trace); + assert.equal( + trace, + 'restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();restore();restore();' + ); + }); + + it('Draw more characters then there are path', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var c = 'M 10,10 60,10'; + + var path = new Konva.Path({ + stroke: 'red', + strokeWidth: 1, + data: c, + }); + + layer.add(path); + + var textpath = new Konva.TextPath({ + fill: 'orange', + fontSize: 24, + fontFamily: 'Arial', + text: "The quick brown fox jumped over the lazy dog's back", + data: c, + }); + layer.add(textpath); + + layer.draw(); + + var trace = layer.getContext().getTrace(true); + assert.equal( + trace, + 'clearRect();clearRect();save();transform();beginPath();moveTo();lineTo();lineWidth;strokeStyle;stroke();restore();save();transform();font;textBaseline;textAlign;save();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();restore();restore();' + ); + }); + + // ====================================================== + it('Find Next Segment when Arc is in Path', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var c = 'M10,10 C0,0 10,150 100,100 S300,150 40,130'; + var path = new Konva.Path({ + x: 0, + y: 50, + stroke: 'green', + strokeWidth: 1, + data: c, + }); + + layer.add(path); + + var textpath = new Konva.TextPath({ + x: 0, + y: 50, + fill: '#333', + fontSize: 50, + fontFamily: 'Arial', + text: "All mhe world's a smage, and all mhe men and women merely players.", + data: c, + }); + + layer.add(textpath); + stage.add(layer); + + var trace = layer.getContext().getTrace(); + assert.equal(trace.indexOf('NaN') === -1, true, 'No NaNs'); + }); + + // ====================================================== + it('Check getter and setter', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var c = 'M 50 50 l 250 0'; + var path = new Konva.TextPath({ + text: 'some text', + stroke: 'red', + strokeWidth: 1, + }); + + layer.add(path); + stage.add(layer); + + assert.equal(path.data(), undefined); + path.data(c); + assert.equal(path.data(), c); + + layer.draw(); + }); + + // ====================================================== + it('Render Text Along Vertical Line', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + // Top Down + var c = 'M 50,10 50,150'; + + var path = new Konva.Path({ + stroke: 'red', + strokeWidth: 1, + data: c, + }); + + layer.add(path); + + var textpath = new Konva.TextPath({ + stroke: 'black', + strokeWidth: 1, + fill: 'orange', + fontSize: 18, + fontFamily: 'Arial', + text: "The quick brown fox jumped over the lazy dog's back", + data: c, + }); + + layer.add(textpath); + + // Bottom up + c = 'M 150,150 150,10'; + + path = new Konva.Path({ + stroke: 'red', + strokeWidth: 1, + data: c, + }); + + layer.add(path); + + textpath = new Konva.TextPath({ + stroke: 'black', + strokeWidth: 1, + fill: 'orange', + fontSize: 18, + fontFamily: 'Arial', + text: "The quick brown fox jumped over the lazy dog's back", + data: c, + }); + + layer.add(textpath); + stage.add(layer); + + assert.equal( + layer.getContext().getTrace(true), + 'rotate();fillStyle;fillText();lineWidth;strokeStyle;strokeText();restore();save();translate();rotate();fillStyle;fillText();lineWidth;strokeStyle;strokeText();restore();save();translate();rotate();fillStyle;fillText();lineWidth;strokeStyle;strokeText();restore();save();translate();rotate();fillStyle;fillText();lineWidth;strokeStyle;strokeText();restore();save();translate();rotate();fillStyle;fillText();lineWidth;strokeStyle;strokeText();restore();save();translate();rotate();fillStyle;fillText();lineWidth;strokeStyle;strokeText();restore();save();translate();rotate();fillStyle;fillText();lineWidth;strokeStyle;strokeText();restore();save();translate();rotate();fillStyle;fillText();lineWidth;strokeStyle;strokeText();restore();save();translate();rotate();fillStyle;fillText();lineWidth;strokeStyle;strokeText();restore();save();translate();rotate();fillStyle;fillText();lineWidth;strokeStyle;strokeText();restore();save();translate();rotate();fillStyle;fillText();lineWidth;strokeStyle;strokeText();restore();restore();restore();' + ); + }); + + // ====================================================== + it('Render Text Along two connected Bezier', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var c = 'M10,10 C0,0 10,150 100,100 S300,150 400,50'; + var path = new Konva.Path({ + stroke: 'red', + strokeWidth: 1, + data: c, + }); + + layer.add(path); + + var textpath = new Konva.TextPath({ + stroke: 'black', + strokeWidth: 1, + fill: 'orange', + fontSize: 8, + fontFamily: 'Arial', + text: "All the world's a stage, and all the men and women merely players. They have their exits and their entrances; And one man in his time plays many parts.", + data: c, + }); + + layer.add(textpath); + stage.add(layer); + + assert.equal( + layer.getContext().getTrace(true), + 'rotate();fillStyle;fillText();lineWidth;strokeStyle;strokeText();restore();save();translate();rotate();fillStyle;fillText();lineWidth;strokeStyle;strokeText();restore();save();translate();rotate();fillStyle;fillText();lineWidth;strokeStyle;strokeText();restore();save();translate();rotate();fillStyle;fillText();lineWidth;strokeStyle;strokeText();restore();save();translate();rotate();fillStyle;fillText();lineWidth;strokeStyle;strokeText();restore();save();translate();rotate();fillStyle;fillText();lineWidth;strokeStyle;strokeText();restore();save();translate();rotate();fillStyle;fillText();lineWidth;strokeStyle;strokeText();restore();save();translate();rotate();fillStyle;fillText();lineWidth;strokeStyle;strokeText();restore();save();translate();rotate();fillStyle;fillText();lineWidth;strokeStyle;strokeText();restore();save();translate();rotate();fillStyle;fillText();lineWidth;strokeStyle;strokeText();restore();save();translate();rotate();fillStyle;fillText();lineWidth;strokeStyle;strokeText();restore();restore();restore();' + ); + }); + + // ====================================================== + it('Render Text Along Elliptical Arc', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var c = 'M 250,100 A 100 50 30 1 0 150 150'; + var path = new Konva.Path({ + stroke: 'red', + strokeWidth: 1, + data: c, + }); + + layer.add(path); + + var textpath = new Konva.TextPath({ + fill: 'black', + fontSize: 10, + text: "All the world's a stage, and all the men and women merely players. They have their exits and their entrances; And one man in his time plays many parts.", + data: c, + }); + + layer.add(textpath); + stage.add(layer); + + assert.equal( + layer.getContext().getTrace(true), + 'restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();restore();restore();' + ); + }); + + // ====================================================== + it('Render Text Along complex path', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var c = + 'M 955.92249,-42.126952 L 955.92249,-42.126952 L 955.92249,-42.126952 L 961.93262,212.9279 C 961.72797,213.3372 961.22315,215.2234 960.80572,215.5574 C 957.45077,218.2413 956.9054,218.3026 953.66869,216.6843 C 952.62164,216.1607 951.67338,214.3658 949.91236,214.8061 C 947.3405,215.4491 948.09281,215.8744 946.53166,217.4355 C 945.22315,218.744 943.52659,219.8744 943.52659,222.3188 C 943.52659,225.6087 944.62943,224.7909 946.15603,226.8264 C 947.55965,228.6979 948.18154,229.6696 948.78546,232.0852 C 949.37174,234.4304 951.2918,235.2197 952.16616,236.9685 C 953.11809,238.8723 956.44837,240.9001 955.17122,242.6029 C 955.17122,242.8772 955.27602,243.9657 955.17122,244.1055 C 954.37248,245.1705 952.25782,247.1195 951.79052,248.9887 C 951.25154,251.1447 951.97226,252.3937 951.41489,254.6232 C 950.9178,256.6116 949.53672,257.6472 949.53672,259.8821 C 949.53672,261.2894 949.87203,263.5578 950.66362,265.1409 C 951.32462,266.4629 953.24159,268.3158 953.66869,270.0242 C 954.03114,271.474 954.12634,273.8281 953.66869,275.6587 C 953.20033,277.5321 952.16616,278.7427 952.16616,280.9175 C 952.16616,281.7694 952.66216,286.9313 952.16616,287.3033 C 950.55129,287.3033 950.38215,287.5144 949.16109,288.4302 C 947.74898,289.4893 945.57047,291.4095 944.65349,292.9378 C 943.57061,294.7426 942.86906,296.6011 942.3997,298.9479 C 941.97063,301.0933 941.32659,303.0261 940.1459,304.2068 C 938.60102,305.7517 939.019,307.4128 939.019,309.8413 C 939.019,311.6467 939.44296,314.3005 938.26773,315.4758 C 937.15545,316.5881 934.88703,318.5361 934.88703,320.7346 C 934.88703,322.7058 934.79432,324.8714 935.26267,326.7448 C 935.72373,328.589 935.6383,330.6902 935.6383,332.7549 C 935.6383,334.5937 936.08895,337.1125 935.26267,338.765 C 933.38787,342.5146 935.26267,342.5858 935.26267,345.5264 C 935.61053,346.9179 935.6383,348.2383 935.6383,350.034 C 935.6383,351.5752 934.96036,354.5783 932.63323,353.4147 C 932.09123,353.1437 928.92886,348.8032 927.75,351.1609 C 926.64231,353.3763 926.87972,354.3829 928.12564,356.0442 C 929.10471,357.3496 930.01787,360.3569 928.12564,361.303 C 926.67006,362.0308 924.24963,362.5828 924.74494,365.0593 C 925.21304,367.3998 926.19847,367.8684 926.6231,369.567 C 926.7781,370.1869 927.80544,374.5783 926.24747,375.2014 C 924.2456,376.0022 920.63857,376.64 919.86171,378.5821 C 918.7844,381.2754 918.89909,381.8572 921.36424,383.0897 C 922.93947,383.8774 923.65296,384.6272 925.12057,386.0948 C 925.4026,386.3768 928.41848,391.3951 926.99874,392.1049 C 926.6231,392.2301 926.22599,392.3035 925.87184,392.4806 C 924.02717,393.4029 922.07311,394.7556 920.61297,395.4856 C 918.19436,396.6949 919.66034,398.0609 920.23734,400.3689 C 920.66358,402.0738 920.9143,404.1809 919.48607,405.2521 C 918.27148,406.163 916.40598,407.9567 914.60284,407.5059 C 912.7458,407.0416 911.06841,406.8699 909.71961,407.8815 C 908.08698,409.106 906.39997,410.6424 905.96328,412.3891 C 905.46424,414.3853 903.5041,416.8116 901.83132,417.648 C 900.14443,418.4914 897.73682,419.2163 895.82119,420.6531 C 894.39644,421.7216 891.99114,423.3808 890.93796,424.785 C 889.59804,426.5716 888.40557,428.0687 886.80599,429.6682 C 885.18365,431.2906 883.35936,432.8052 882.29839,434.9271 C 881.56876,436.3864 879.95545,436.9836 879.29333,438.3078 C 878.57656,439.7413 877.73542,441.3406 876.28826,442.0641 C 874.75553,442.8305 873.35007,443.456 871.40503,443.9423 C 867.75936,444.8537 869.30342,446.1864 868.7756,448.8255 C 868.7756,449.4008 868.88599,450.1518 868.7756,450.7037 C 868.4147,452.5082 867.97176,454.46 866.14617,454.46 C 863.87643,454.46 863.13519,452.5202 860.51167,452.9575 C 858.30041,453.326 855.7288,453.4708 853.75028,454.46 C 851.66578,455.5023 850.88183,456.6114 849.24268,457.8407 C 848.34172,458.5165 844.59521,461.2214 842.85692,461.2214 C 841.06194,461.2214 838.75283,461.625 837.59805,460.4702 C 836.02546,458.8976 834.59299,457.0331 834.59299,454.8357 C 834.59299,452.5753 834.44046,450.9268 833.09045,449.5768 C 831.22582,447.7122 830.88608,448.6344 829.33412,450.7037 C 827.57516,453.049 826.50225,455.876 824.07526,457.0895 C 820.97109,458.6416 819.33963,458.3772 818.44076,461.9727 C 817.87317,464.2431 816.93823,466.7246 816.93823,469.1097 C 816.93823,470.8675 817.70296,474.7173 816.93823,476.2468 C 816.14706,477.8291 812.41394,478.9791 810.9281,476.998 C 808.83845,474.2118 810.41749,473.2417 805.66924,473.2417 C 801.6473,473.2417 799.28347,473.0146 799.28347,477.3737 C 799.28347,479.1155 799.58784,484.5107 796.65404,484.5107 C 796.27841,484.5107 795.90277,484.5107 795.52714,484.5107 C 793.06311,484.5107 790.57051,484.2819 789.51701,486.3889 C 789.24153,486.9398 789.17021,490.492 788.39011,491.2721 C 785.76325,493.8989 789.66759,493.7526 790.26828,496.1553 C 790.57092,497.3659 791.29959,501.1341 790.26828,502.1654 C 788.37505,504.0587 788.1443,505.2726 787.63885,507.7999 C 787.12622,510.3631 787.28641,510.4294 784.25815,510.4294 C 779.52049,510.4294 778.62062,512.1783 781.25308,515.6882 C 782.04773,516.7478 784.15693,519.0183 785.76068,519.8202 C 787.2339,520.5568 788.2453,521.5264 787.63885,523.9522 C 787.29534,525.3262 785.38505,526.8783 785.38505,528.8354 C 785.38505,532.3304 785.96541,532.0452 787.63885,533.7186 C 789.35939,535.4392 791.26358,536.4988 790.64391,538.9775 C 790.07532,541.2518 787.846,540.5966 785.38505,540.1044 C 784.8577,539.9989 777.87238,538.1167 777.87238,538.2262 C 777.87238,538.3043 777.87238,541.4667 777.87238,543.1095 C 777.87238,545.7389 776.11001,547.6978 773.74042,549.1196 C 769.72179,551.5308 769.56137,548.92 765.85212,547.9927 C 764.43987,547.6396 762.84706,547.0925 762.84706,544.9876 C 762.84706,542.5025 764.72522,540.5566 764.72522,538.9775 C 764.72522,537.481 764.49962,535.4457 763.97396,533.343 C 763.53464,531.5857 763.96677,529.2128 760.96889,529.9623 C 759.74476,530.2683 755.76059,530.9158 755.3344,529.211 C 754.79258,527.0438 753.83472,525.0819 752.32933,523.5765 C 751.7239,522.9711 748.78535,518.481 747.07047,520.1958 C 745.42956,521.8367 745.1923,522.8794 745.1923,525.4547 C 745.1923,529.5231 743.80555,527.5927 741.43597,529.9623 C 739.21241,532.1859 738.84328,532.0691 738.05527,535.2212 C 737.62578,536.9391 737.33255,538.9489 736.17711,540.1044 C 735.37222,540.9093 731.5352,542.6268 730.91824,543.8607 C 729.89113,545.9149 730.31425,546.7847 731.29388,548.744 C 731.93347,550.0231 732.94949,551.8879 732.42078,554.0028 C 731.86797,556.214 729.92914,558.5699 727.16191,559.2617 C 726.16187,559.7617 724.82639,560.5029 723.78121,560.7642 C 721.91594,561.2305 719.64925,561.351 719.64925,564.1449 C 719.64925,566.832 719.04019,568.7236 721.15178,569.7794 C 722.21289,570.31 724.72561,571.2926 725.28375,572.4088 C 726.18968,574.2207 726.03501,576.214 726.03501,578.419 C 726.03501,580.9518 724.90811,582.9761 724.90811,585.1804 C 724.90811,587.587 724.17206,589.3326 725.28375,590.8149 C 726.38582,592.2843 727.68532,592.9085 728.28881,595.3225 C 728.47077,596.0503 729.29883,599.6882 728.66444,600.957 C 728.20299,601.8799 726.62388,604.7133 724.90811,604.7133 C 722.23081,604.7133 719.55156,603.2108 717.77108,603.2108 C 712.9722,603.2108 711.01958,602.0443 709.88279,606.5915 C 709.52114,608.038 708.85871,610.3121 708.38026,612.2259 C 707.78279,614.6158 706.87772,616.6877 706.87772,619.363 C 706.87772,621.8398 706.7087,624.1711 706.12646,626.5 C 705.78303,627.8737 704.58011,630.6495 702.74576,631.3832 C 700.14612,632.4231 699.90837,632.6269 696.73563,633.2614 C 695.19072,633.5704 692.38471,634.0127 690.34987,634.0127 C 687.92024,634.0127 684.24023,633.3112 682.08594,634.3883 C 680.51621,635.1732 677.63742,637.5327 677.20271,639.2715 C 676.32889,642.7668 669.65019,641.1298 666.68498,639.6472 C 665.51347,639.0614 662.57658,637.112 662.17738,635.5152 C 661.57521,633.1065 663.16351,629.2235 662.17738,627.2513 C 661.26634,625.4292 659.87344,623.4448 658.42105,621.9924 C 657.38134,620.9527 655.38855,620.0777 654.28908,618.6117 C 653.089,617.0116 651.62053,616.0553 650.15712,614.1041 C 648.34003,611.6813 647.12666,612.2259 643.77136,612.2259 C 639.94754,612.2259 634.27092,612.8011 630.99983,610.3478 C 628.83169,608.7217 627.09631,607.7996 625.74097,605.0889 C 624.63961,602.8862 624.51407,601.3082 623.8628,598.7032 C 623.8628,597.1031 624.2465,594.9791 623.8628,593.4443 C 623.39918,591.5898 621.23337,589.3243 621.23337,587.4342 C 621.23337,587.1837 621.29411,586.9258 621.23337,586.6829 C 620.53685,583.8968 622.36027,582.4393 622.36027,580.6728 C 622.36027,578.1478 621.87342,577.1809 620.10647,575.4139 C 619.11396,574.4214 614.71345,572.543 612.96944,574.287 C 611.60526,575.6512 609.17921,577.309 606.95931,578.419 C 604.01326,579.892 598.66588,576.9755 597.19285,579.9215 C 596.40756,581.4921 595.76926,583.6587 595.31468,585.9316 C 594.88705,588.0698 594.09657,589.556 591.55835,590.0636 C 590.26591,590.3221 585.80562,591.0513 585.17259,592.3174 C 584.45323,593.7561 582.33804,595.3917 581.79189,597.5763 C 581.21425,599.8868 580.53762,600.7708 578.78683,602.0839 C 576.60544,603.7199 574.24457,604.0233 571.27416,602.8351 C 569.56134,602.15 566.96195,601.3583 564.51277,601.7082 C 562.15094,602.0456 560.7219,604.7047 559.2539,604.3377 C 556.608,603.6762 556.41629,603.5592 554.74631,601.3326 C 553.7801,600.0443 552.83677,595.5353 551.36561,594.9468 C 549.22437,594.0903 546.63624,594.001 543.85294,593.4443 C 541.39906,592.9535 538.87331,593.0687 536.34028,593.0687 C 532.49916,593.0687 532.93906,592.2969 530.70579,590.0636 C 529.57858,588.9364 527.94151,588.118 525.82255,587.0585 C 523.85495,586.0747 523.02163,585.6928 520.56369,586.3073 C 518.15725,586.9089 517.4765,588.4877 515.68046,588.9367 C 514.53264,589.2237 511.38458,588.643 510.04596,589.3123 C 508.49749,590.0866 507.19267,590.5834 506.66527,592.693 C 506.20828,594.521 505.99947,595.9598 504.7871,597.5763 C 503.10137,599.8239 501.43481,599.4686 499.1526,598.3275 C 496.74377,597.1231 496.63249,597.7484 493.89374,597.2006 C 491.45635,596.7131 490.45647,596.313 488.63487,594.9468 C 486.20245,593.1225 485.84728,591.7342 484.87854,589.3123 C 484.34805,587.9861 483.82138,584.0535 482.24911,584.0535 C 479.1858,584.0535 478.32694,584.2633 476.23898,582.1753 C 475.01433,580.9507 474.104,579.7043 472.85828,578.0433 C 471.87387,576.7308 471.15841,575.0383 468.72632,575.0383 C 465.62648,575.0383 465.0931,574.4101 463.09182,572.4088 C 461.80618,571.1232 459.77548,570.155 457.45733,570.155 C 454.22738,570.155 453.13567,570.2034 450.69593,572.0332 C 449.01793,573.2917 445.74427,574.287 443.5589,574.287 C 441.14907,574.287 438.88122,574.5776 436.7975,573.5357 C 435.27776,572.7759 434.01441,571.5961 432.28991,570.9063 C 429.9965,569.989 427.79078,568.6525 425.15288,568.6525 C 423.40022,568.6525 419.8328,569.7488 418.39148,569.0281 C 418.14106,568.9029 417.89064,568.7777 417.64021,568.6525 C 415.49479,567.5798 416.55622,567.2358 415.38641,564.8962 C 414.77237,563.6681 414.63515,562.1788 414.63515,560.0129 C 414.63515,558.3145 415.04465,556.0165 414.63515,554.3784 C 414.06491,552.0975 414.24886,549.8602 412.38135,547.9927 C 411.40995,547.0213 409.24156,545.0938 408.62502,543.8607 C 408.07318,542.757 407.08617,540.8193 405.99559,539.7288 C 404.23882,537.972 404.86869,537.4962 404.86869,535.2212 C 404.86869,532.3223 402.92378,530.8222 402.23926,528.0841 C 402.03511,527.2676 400.20775,523.9522 399.23419,523.9522 C 397.40724,523.9522 395.17436,524.3278 393.59969,524.3278 C 392.1471,524.3278 388.62445,524.895 387.9652,523.5765 C 387.16017,521.9665 386.46266,520.8647 386.46266,518.3177 C 386.46266,517.2392 387.06995,513.4929 386.46266,512.6832 C 385.44124,511.3213 383.94518,508.9268 382.3307,508.9268 C 380.0442,508.9268 378.68472,509.6505 377.07184,510.0537 C 374.43842,510.7121 375.12089,510.9506 374.06677,513.0588 C 372.99551,515.2013 371.43568,515.6866 369.55917,513.8101 C 367.11608,511.367 367.54854,511.9833 366.17847,513.8101 C 364.4331,516.1372 362.02692,517.942 359.04145,517.942 C 356.27733,517.942 354.79253,517.3325 353.78258,515.3126 C 352.71976,513.187 352.20547,512.3075 349.65062,512.3075 C 347.43943,512.3075 345.67638,511.8115 345.14302,509.6781 C 344.69437,507.8835 343.8574,505.0515 342.51359,504.0436 C 341.49931,503.2829 339.32282,500.99 337.25472,502.5411 C 336.12724,503.3867 330.59067,511.5766 329.49596,511.5766 L 339.92116,9.4291543 L 531.3294,9.5579943 C 531.53498,9.8775343 531.74056,10.197084 531.94614,10.516624 C 532.70213,11.691684 530.89998,12.894794 530.62953,14.247024 C 530.42067,15.291354 532.94855,14.371684 533.70163,15.124764 C 533.96143,15.384574 533.06188,17.795104 533.26276,18.196854 C 533.6241,18.919554 537.09651,16.118584 537.43203,15.783074 C 538.52925,14.685844 541.26067,15.533334 542.2596,15.783074 C 544.36225,16.308734 544.53484,13.969904 545.77057,16.441374 C 546.72008,18.340404 548.8757,18.577754 550.81758,18.855164 C 551.5334,18.957424 552.36959,15.108804 552.7925,14.685894 C 553.70371,13.774684 554.04733,13.026284 554.76742,14.466454 C 555.55609,16.043794 556.96728,16.885754 558.27838,18.196854 C 559.14892,19.067394 560.36843,19.874104 561.35048,20.610644 C 562.42985,21.420174 563.12715,21.998014 564.20314,22.805004 C 565.9662,24.127294 567.78898,25.511804 570.12789,26.096534 C 572.7652,26.755854 576.55367,27.553934 578.90531,28.729754 C 580.9132,29.733704 583.43718,29.459644 585.48837,30.485234 C 586.49144,30.986774 588.94826,31.133324 590.09651,31.362974 C 591.42028,32.024864 591.77294,34.338314 592.07143,35.532254 C 592.3559,36.670124 593.11993,38.320014 593.82691,39.262654 C 594.69143,40.415344 596.17315,41.423224 597.11844,41.895874 C 598.26675,42.470034 600.11464,43.649294 601.28771,44.529104 C 602.4452,45.397214 603.546,45.151114 603.04319,47.162324 C 602.73764,48.384554 601.38101,48.605074 600.62941,49.356674 C 599.50817,50.477904 599.93932,51.519254 600.84884,52.428774 C 601.81016,53.390084 603.26382,53.305314 604.14037,52.428774 C 604.62824,51.940894 608.18038,52.428774 608.96795,52.428774 C 611.1468,52.428774 610.66216,51.127474 612.47891,50.673284 C 612.63759,50.633624 612.77149,50.526994 612.91778,50.453854 C 614.68717,49.569154 616.9206,51.445064 617.9648,49.356674 C 618.52936,48.227544 619.56541,48.220674 619.93972,46.723454 C 620.25133,45.477014 620.37729,44.531694 621.03689,43.212484 C 621.76915,41.747964 621.9135,40.434484 622.79237,39.262654 C 623.77356,37.954414 624.27391,36.972204 625.64503,36.629424 C 627.98413,36.044654 628.95445,36.884634 629.81431,38.604344 C 630.5868,40.149334 629.04661,41.566394 628.05882,42.554184 C 627.03053,43.582464 626.94563,46.049134 627.83939,46.942884 C 628.71859,47.822094 631.7203,46.960114 632.66697,46.723454 C 635.14429,46.104124 638.40825,46.723454 641.00551,46.723454 C 642.99376,46.723454 643.25279,47.744904 644.29704,49.137244 C 645.27121,50.436134 645.05681,51.584644 643.63873,52.648204 C 642.199,53.728004 640.62809,54.372964 639.25003,55.061994 C 637.13418,56.119914 635.43133,55.127564 633.54471,54.184254 C 631.95211,53.387954 630.44161,53.389994 628.71713,53.964814 C 626.84122,54.590124 627.42091,55.720304 625.20616,55.720304 C 623.21044,55.720304 622.67528,55.410144 621.25633,54.842564 C 619.91862,54.307474 619.00883,54.278974 617.9648,55.061994 C 617.10854,55.704184 616.39298,55.720304 614.8927,55.720304 C 613.05499,55.720304 612.78965,55.409564 611.82061,56.378604 C 611.11873,57.080484 611.94664,57.914654 609.40682,57.914654 C 607.90864,57.914654 607.56008,59.135134 606.55416,59.889574 C 605.2063,60.900474 602.08634,60.328444 600.40997,60.328444 C 598.82692,60.328444 597.23216,60.282954 596.02126,60.767314 C 592.93299,62.002624 597.05347,63.219724 597.77675,63.400534 C 599.71594,63.885334 600.39327,64.211484 600.84884,66.033764 C 601.33813,67.990904 602.14535,68.474354 603.48206,66.692064 C 604.91144,64.786234 602.91352,64.497714 606.77359,64.497714 C 607.59464,64.497714 608.63043,67.232284 608.96795,67.569814 C 610.45793,69.059794 611.16665,70.095494 613.13722,71.080774 C 614.46498,71.744654 616.30615,67.595574 616.64819,66.911504 C 617.28296,65.641964 617.99069,64.704204 619.28141,64.058844 C 621.30547,63.046814 622.75619,64.278284 624.76729,64.278284 C 626.50942,64.278284 627.61995,65.003454 627.61995,62.742234 C 627.61995,61.212584 627.63406,61.199134 628.93656,60.547884 C 628.93656,59.039954 631.8995,61.398604 633.10584,62.303364 C 634.22905,63.145774 635.25806,64.560214 636.6168,65.375454 C 638.02819,66.222284 639.45789,65.179164 639.90833,64.278284 C 640.50672,63.081494 642.69629,63.368184 643.63873,63.839414 C 644.9694,64.504744 646.71554,64.500074 648.02744,65.156024 C 649.65658,65.970594 651.25018,66.091894 652.63558,67.130944 C 654.5709,68.582434 655.72441,69.284754 658.12146,69.764164 C 660.76933,70.293734 662.17378,70.473704 664.26565,71.519644 C 666.22906,72.501344 668.08427,73.121854 669.75154,74.372304 C 670.99777,75.306984 673.61008,75.688914 675.23742,75.688914 C 678.09495,75.688914 679.5978,74.715624 682.03992,73.494564 C 683.61178,72.708634 685.09563,72.194334 686.20919,71.080774 C 687.25214,70.037824 688.09533,68.975204 689.28128,67.789244 C 690.81968,66.250844 691.90496,66.472634 694.10886,66.472634 C 695.98476,66.472634 697.61589,67.130944 699.37531,67.130944 C 700.88236,67.130944 702.30921,68.008684 703.98345,68.008684 C 705.78815,68.008684 706.82154,67.443974 708.15272,66.911504 C 709.49084,66.376254 710.32631,65.391024 711.22482,64.717154 C 712.93357,63.435584 713.93405,62.155634 715.83296,61.206184 C 717.44839,60.398474 719.60451,59.255264 721.09941,58.134094 C 722.32027,57.218444 724.55866,55.842944 725.92699,55.500864 C 727.42616,55.126074 729.09302,54.102794 730.53513,53.525944 C 732.4374,52.765044 734.47148,52.545224 736.02101,51.770464 C 736.81463,51.373654 738.38579,51.112164 739.31254,51.112164 C 739.58229,50.977294 739.8977,50.965874 740.19028,50.892724 C 741.93619,50.456234 744.97275,50.145724 746.55391,51.331594 C 747.77567,52.247914 749.08929,52.550364 750.06487,53.525944 C 751.05366,54.514734 751.10636,54.963084 752.6981,55.281434 C 753.97746,55.537304 755.20688,54.403694 756.64793,54.403694 C 757.60799,54.403694 759.65763,56.143574 760.59777,56.378604 C 762.10547,56.755534 763.41059,56.817474 764.98648,56.817474 C 766.46659,56.817474 768.85254,54.943624 770.47236,54.403694 C 772.25575,53.809224 773.23113,53.525944 775.29994,53.525944 C 777.348,53.525944 779.39606,53.525944 781.44413,53.525944 C 783.12504,53.525944 784.01926,53.375894 785.17453,53.087074 C 786.13177,52.847764 786.81429,52.867644 787.80775,52.867644 C 789.68721,52.397784 790.54366,51.799654 792.41589,51.331594 C 793.72507,51.004304 794.52824,48.862394 795.04912,47.820634 C 795.74654,46.425784 796.31421,45.768114 797.24347,44.529104 C 798.0814,43.411864 799.90954,42.318324 801.19331,41.676444 C 802.47959,41.033304 803.007,40.301614 804.04597,39.262654 C 804.9791,38.329524 805.42163,37.448114 806.24032,36.629424 C 807.32555,35.544194 808.33509,33.723304 809.09298,32.460154 C 809.72369,31.408974 811.13754,30.635024 812.16508,29.607494 C 812.75994,29.012634 816.59236,28.500674 817.43152,28.290884 C 818.9728,27.905564 820.03772,26.864014 820.94249,25.657664 C 821.81326,24.496634 822.20664,23.673144 822.47854,22.585564 C 822.70979,21.660554 823.16846,20.484194 823.35628,19.732904 C 823.39176,19.590984 823.35628,19.440324 823.35628,19.294034 C 824.72829,14.181234 833.5556,11.720324 838.16552,9.4153643 C 840.3455,8.3253643 841.62867,5.2222343 843.25846,3.0491743 C 844.34873,1.5954943 847.99376,1.4409443 850.04906,0.92711429 C 853.15105,0.15161429 855.95039,-0.84630571 858.11289,-2.4681757 C 860.2827,-4.0955457 863.83523,-5.3512957 866.17672,-6.2878857 C 868.93603,-7.3916157 871.61677,-9.3068957 873.81614,-10.956426 C 875.97519,-12.575706 878.16034,-13.552932 880.60673,-14.776132 C 882.92916,-15.937342 883.77331,-17.477632 886.5485,-18.171422 C 890.51751,-19.163682 894.57232,-17.476362 898.43204,-19.020252 C 901.2465,-20.146032 904.60721,-21.731172 907.3447,-22.415552 C 909.30842,-22.906482 911.47245,-25.328252 913.28647,-26.235262 C 916.00359,-27.593822 917.08159,-29.412202 919.65265,-30.054972 C 921.32298,-30.472552 924.26602,-31.730552 926.44325,-32.601442 C 928.89479,-33.582062 931.86421,-33.402072 933.65826,-34.299092 C 936.16619,-35.553052 937.08458,-36.322802 939.17561,-36.845562 C 941.67817,-37.471202 944.13749,-38.007702 946.81503,-38.543212 C 948.94134,-38.968472 950.98649,-40.592612 952.33239,-41.938512 C 953.1616,-42.767712 955.07166,-42.233042 955.92249,-42.126952 z '; + + var textpath = new Konva.TextPath({ + y: 50, + fill: 'black', + fontSize: 24, + text: Array(4).join( + "All the world's a stage, and all the men and women merely players. They have their exits and their entrances; And one man in his time plays many parts." + ), + data: c, + }); + + layer.add(textpath); + stage.add(layer); + + assert.equal( + layer.getContext().getTrace(true), + 'restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();restore();restore();' + ); + }); + + // ====================================================== + it('Render Text Along complex path cached', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var c = 'M10,10 C0,0 10,150 100,100 S300,150 400,50'; + + var textpath = new Konva.TextPath({ + stroke: 'black', + strokeWidth: 1, + fill: 'orange', + fontSize: 10, + fontFamily: 'Arial', + text: "All the world's a stage, and all the men and women merely players. They have their exits and their entrances; And one man in his time plays many parts.", + data: c, + draggable: true, + }); + + textpath.cache(); + + layer.add(textpath); + stage.add(layer); + + cloneAndCompareLayer(layer, 200, 10); + }); + + it('Text path with letter spacing', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var c = 'M10,10 C0,0 10,150 100,100 S300,150 400,50'; + + var textpath = new Konva.TextPath({ + stroke: 'black', + strokeWidth: 1, + fill: 'orange', + fontSize: 10, + fontFamily: 'Arial', + letterSpacing: 5, + text: "All the world's a stage, and all the men and women merely players.", + data: c, + }); + + textpath.cache(); + + layer.add(textpath); + stage.add(layer); + cloneAndCompareLayer(layer, 200, 30); + }); + + it('Text path with align', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var c = 'M10,10 300, 10'; + + var textpath = new Konva.TextPath({ + fill: 'black', + fontSize: 10, + fontFamily: 'Arial', + letterSpacing: 5, + text: "All the world's a stage.", + align: 'center', + data: c, + }); + + layer.add(textpath); + stage.add(layer); + + var trace = + 'restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();restore();restore();'; + + assert.equal(layer.getContext().getTrace(true), trace); + }); + + it('Text path with emoji', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var c = 'M10,10 300, 10'; + + var textpath = new Konva.TextPath({ + fill: 'black', + fontSize: 10, + fontFamily: 'Arial', + letterSpacing: 5, + text: '😬', + align: 'center', + data: c, + }); + + layer.add(textpath); + stage.add(layer); + + assert.equal( + layer.getContext().getTrace(true), + 'clearRect();save();transform();font;textBaseline;textAlign;save();save();translate();rotate();fillStyle;fillText();restore();restore();restore();' + ); + }); + + it('Text path with center align - arc', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var textpath = new Konva.TextPath({ + fill: '#333', + fontSize: 20, + text: 'Hello World', + align: 'right', + data: 'M 50 200 a 100 100 0 0 1 200 0', + }); + layer.add(textpath); + + var path = new Konva.Path({ + stroke: '#000', + data: 'M 50 200 a 100 100 0 0 1 200 0', + }); + layer.add(path); + stage.add(layer); + + assert.equal( + layer.getContext().getTrace(true), + 'clearRect();save();transform();font;textBaseline;textAlign;save();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();restore();restore();save();transform();beginPath();moveTo();translate();rotate();scale();arc();scale();rotate();translate();lineWidth;strokeStyle;stroke();restore();' + ); + }); + + it('Text path with align right', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var c = 'M10,10 300, 10'; + + var path = new Konva.Path({ + stroke: 'red', + data: c, + }); + + layer.add(path); + + var textpath = new Konva.TextPath({ + fill: 'black', + fontSize: 10, + fontFamily: 'Arial', + text: "All the world's a stage.", + align: 'right', + data: c, + }); + + layer.add(textpath); + stage.add(layer); + + assert.equal( + layer.getContext().getTrace(true), + 'restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();restore();restore();' + ); + }); + + it('Text path with justify align', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var c = 'M10,10 C0,0 10,150 100,100 S300,150 400,50'; + + var textpath = new Konva.TextPath({ + stroke: 'black', + strokeWidth: 1, + fill: 'orange', + fontSize: 10, + fontFamily: 'Arial', + letterSpacing: 5, + text: 'All the worlds a stage.', + align: 'justify', + data: c, + }); + + layer.add(textpath); + stage.add(layer); + + var trace = + 'rotate();fillStyle;fillText();lineWidth;strokeStyle;strokeText();restore();save();translate();rotate();fillStyle;fillText();lineWidth;strokeStyle;strokeText();restore();save();translate();rotate();fillStyle;fillText();lineWidth;strokeStyle;strokeText();restore();save();translate();rotate();fillStyle;fillText();lineWidth;strokeStyle;strokeText();restore();save();translate();rotate();fillStyle;fillText();lineWidth;strokeStyle;strokeText();restore();save();translate();rotate();fillStyle;fillText();lineWidth;strokeStyle;strokeText();restore();save();translate();rotate();fillStyle;fillText();lineWidth;strokeStyle;strokeText();restore();save();translate();rotate();fillStyle;fillText();lineWidth;strokeStyle;strokeText();restore();save();translate();rotate();fillStyle;fillText();lineWidth;strokeStyle;strokeText();restore();save();translate();rotate();fillStyle;fillText();lineWidth;strokeStyle;strokeText();restore();save();translate();rotate();fillStyle;fillText();lineWidth;strokeStyle;strokeText();restore();restore();restore();'; + + assert.equal(layer.getContext().getTrace(true), trace); + }); + + it('Text path with underline', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var c = 'M10,10 C0,0 10,150 100,100 S300,150 400,50'; + + var textpath = new Konva.TextPath({ + fill: 'orange', + fontSize: 10, + fontFamily: 'Arial', + letterSpacing: 5, + text: 'All the worlds a stage.', + textDecoration: 'underline', + data: c, + draggable: true, + }); + + layer.add(textpath); + stage.add(layer); + + var trace = + 'rotate();fillStyle;fillText();lineTo();restore();save();translate();rotate();fillStyle;fillText();lineTo();restore();save();translate();rotate();fillStyle;fillText();lineTo();restore();save();translate();rotate();fillStyle;fillText();lineTo();restore();save();translate();rotate();fillStyle;fillText();lineTo();restore();save();translate();rotate();fillStyle;fillText();lineTo();restore();save();translate();rotate();fillStyle;fillText();lineTo();restore();save();translate();rotate();fillStyle;fillText();lineTo();restore();save();translate();rotate();fillStyle;fillText();lineTo();restore();save();translate();rotate();fillStyle;fillText();lineTo();restore();save();translate();rotate();fillStyle;fillText();lineTo();restore();save();translate();rotate();fillStyle;fillText();lineTo();restore();save();translate();rotate();fillStyle;fillText();lineTo();restore();save();translate();rotate();fillStyle;fillText();lineTo();restore();stroke();restore();restore();'; + + assert.equal(layer.getContext().getTrace(true), trace); + }); + + it('Text with baseline', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var c = 'M 10,10 300,10'; + + var path = new Konva.Path({ + stroke: 'red', + strokeWidth: 1, + data: c, + }); + + layer.add(path); + + var textpath = new Konva.TextPath({ + fill: 'orange', + fontSize: 24, + fontFamily: 'Arial', + text: "The quick brown fox jumped over the lazy dog's back", + data: c, + textBaseline: 'top', + }); + textpath.on('mouseover', function () { + this.fill('blue'); + layer.drawScene(); + }); + textpath.on('mouseout', function () { + this.fill('orange'); + layer.drawScene(); + }); + + layer.add(textpath); + stage.add(layer); + + assert.equal( + layer.getContext().getTrace(true), + 'restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();restore();restore();' + ); + }); + + it('Text with kerning', function () { + var stage = addStage(); + + // simulate lack of kerning support + stage.content && (stage.container().style.fontKerning = 'none'); + + var layer = new Konva.Layer(); + var pairs = { + A: { + V: -0.07421875, + }, + V: { + A: -0.07421875, + }, + }; + + const kernedText = new Konva.TextPath({ + x: 0, + y: 30, + fill: 'black', + text: 'AV', + fontSize: 60, + data: 'M0,0 L200,0', + kerningFunc: function (leftChar, rightChar) { + return pairs.hasOwnProperty(leftChar) + ? pairs[leftChar][rightChar] || 0 + : 0; + }, + }); + + const unkernedText = new Konva.TextPath({ + x: 0, + y: 90, + fill: 'black', + text: 'AV', + fontSize: 60, + data: 'M0,0 L200,0', + }); + + layer.add(kernedText); + layer.add(unkernedText); + stage.add(layer); + + assert( + kernedText.getTextWidth() < unkernedText.getTextWidth(), + 'kerned text lenght must be less then unkerned text length' + ); + }); + + it('Text with invalid kerning getter should not fail (fallback to unkerned)', function () { + var stage = addStage(); + + // simulate lack of kerning support + stage.content && (stage.container().style.fontKerning = 'none'); + + var layer = new Konva.Layer(); + + const kernedText = new Konva.TextPath({ + x: 0, + y: 30, + fill: 'black', + text: 'AV', + fontSize: 60, + data: 'M0,0 L200,0', + kerningFunc: function (leftChar, rightChar) { + // getter that fails + throw new Error('something went wrong'); + }, + }); + + const unkernedText = new Konva.TextPath({ + x: 0, + y: 90, + fill: 'black', + text: 'AV', + fontSize: 60, + data: 'M0,0 L200,0', + }); + + layer.add(kernedText); + layer.add(unkernedText); + stage.add(layer); + + assert.equal( + kernedText.getTextWidth(), + unkernedText.getTextWidth(), + 'should gracefully fallback to unkerned text' + ); + }); + + it('can set kerning after initialization', function () { + var stage = addStage(); + + // simulate lack of kerning support + stage.content && (stage.container().style.fontKerning = 'none'); + + var layer = new Konva.Layer(); + stage.add(layer); + + const kernedText = new Konva.TextPath({ + x: 0, + y: 30, + fill: 'black', + text: 'AV', + fontSize: 60, + data: 'M0,0 L200,0', + }); + layer.add(kernedText); + layer.draw(); + + var called = false; + kernedText.kerningFunc(function () { + called = true; + return 1; + }); + + layer.draw(); + assert.equal(called, true); + }); + + it.skip('linear gradient for path', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + stage.add(layer); + + const text = new Konva.TextPath({ + x: 0, + y: 30, + fontSize: 20, + data: 'M0,0 L200,0', + fillLinearGradientStartPoint: { x: 0, y: 0 }, + fillLinearGradientEndPoint: { x: 200, y: 0 }, + fillLinearGradientColorStops: [0, 'yellow', 1, 'red'], + text: 'Text with gradient!!', + }); + layer.add(text); + layer.draw(); + }); + + it('visual check for text path', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + stage.add(layer); + + layer.add( + new Konva.TextPath({ + fill: '#333', + fontSize: 20, + x: 80, + y: 300, + fontFamily: 'Calibri', + text: 'Hello World', + align: 'center', + textBaseline: 'bottom', + data: 'M -80.34441853748636 -247.27469423673992 A 260 260 0 0 1 80.34441853748628 -247.27469423673995', + }) + ); + + layer.add( + new Konva.TextPath({ + fill: '#333', + fontSize: 20, + x: 80, + y: 350, + fontFamily: 'Calibri', + text: 'Hello World', + align: 'center', + // textBaseline: 'bottom', + data: 'M -80.34441853748636 -247.27469423673992 A 260 260 0 0 1 80.34441853748628 -247.27469423673995', + }) + ); + + layer.add( + new Konva.Text({ + text: 'Hello world', + }) + ); + + layer.add( + new Konva.TextPath({ + fill: '#333', + text: 'Hello world', + y: 20, + data: 'M 0 0 L100 0', + }) + ); + layer.draw(); + + assert.equal( + layer.getContext().getTrace(true), + 'save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();restore();restore();save();transform();font;textBaseline;textAlign;translate();save();fillStyle;fillText();restore();restore();save();transform();font;textBaseline;textAlign;save();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();restore();restore();' + ); + }); + + it('client rect calculations', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + stage.add(layer); + + var textpath = new Konva.TextPath({ + x: 100, + y: 150, + fill: '#333', + fontSize: 16, + fontFamily: 'Arial', + align: 'right', + text: 'test_path', + data: 'M 0,10 L 300 10', + }); + layer.add(textpath); + layer.draw(); + + var rect = textpath.getClientRect(); + + assert.equal(rect.height, 16, 'check height'); + + textpath.text(''); + rect = textpath.getClientRect(); + assert.equal(rect.height, 0, 'check height'); + }); + + it('dashes on line', function () { + var stage = addStage(); + stage.draggable(true); + + var layer = new Konva.Layer(); + stage.add(layer); + + var textpath = new Konva.TextPath({ + fill: '#333', + fontSize: 16, + scaleX: 0.8, + scaleY: 0.8, + text: '_______________', + data: 'M 0,10 L 300 10', + y: 5, + }); + layer.add(textpath); + var path = new Konva.Path({ + stroke: 'red', + scaleX: 0.8, + scaleY: 0.8, + data: 'M 0,10 L 300 10', + }); + layer.add(path); + layer.draw(); + + var rect = textpath.getClientRect(); + + // just different results in different envs + assert.equal(Math.round(rect.height), 13, 'check height'); + }); + + it('check bad calculations', function () { + var stage = addStage(); + stage.draggable(true); + + var layer = new Konva.Layer(); + stage.add(layer); + + var textpath = new Konva.TextPath({ + fill: '#333', + fontSize: 16, + scaleX: 0.8, + scaleY: 0.8, + text: '___________________________________________________________________________________________________________________________________________________________________________________________________________________', + data: 'M 109.98618090452261 138.6656132223618 C 135.94577638190955 48.80547503140701 149.91187876884422 79.79800957914573 151.40954773869348 117.23973382537689 S 123.00811620603017 419.616741991206 122.84170854271358 460.0538041771357 S 134.33883542713568 469.8304329459799 149.98115577889448 464.33898005653265 S 245.4620163316583 411.5856081972362 257.1105527638191 412.91686950376885 S 239.31850251256282 474.434854428392 249.96859296482413 475.76611573492465 S 338.21036306532665 425.67526648869347 348.5276381909548 424.3440051821608 S 337.3640408291457 461.1772344535176 338.5288944723618 464.33898005653265 S 346.8778454773869 466.79295744346734 358.52638190954775 451.4834524183417', + }); + layer.add(textpath); + + layer.draw(); + + var trace = layer.getContext().getTrace(true); + assert.equal( + trace, + 'restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();restore();restore();' + ); + + var path = new Konva.Path({ + stroke: 'red', + scaleX: 0.8, + scaleY: 0.8, + data: 'M 109.98618090452261 138.6656132223618 C 135.94577638190955 48.80547503140701 149.91187876884422 79.79800957914573 151.40954773869348 117.23973382537689 S 123.00811620603017 419.616741991206 122.84170854271358 460.0538041771357 S 134.33883542713568 469.8304329459799 149.98115577889448 464.33898005653265 S 245.4620163316583 411.5856081972362 257.1105527638191 412.91686950376885 S 239.31850251256282 474.434854428392 249.96859296482413 475.76611573492465 S 338.21036306532665 425.67526648869347 348.5276381909548 424.3440051821608 S 337.3640408291457 461.1772344535176 338.5288944723618 464.33898005653265 S 346.8778454773869 466.79295744346734 358.52638190954775 451.4834524183417', + }); + layer.add(path); + layer.draw(); + + var rect = textpath.getClientRect(); + + // just different results in different envs + assert.equal(Math.round(rect.height), 330, 'check height'); + + textpath.text(''); + rect = textpath.getClientRect(); + assert.equal(rect.height, 0, 'check height'); + }); + + it('check bad calculations 2', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + stage.add(layer); + + var textpath = new Konva.TextPath({ + x: 0, + y: 0, + fill: '#333', + fontSize: 10, + // strokeWidth: 100, + stroke: 'black', + scaleX: 0.4, + scaleY: 0.4, + text: '....................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................', + data: 'M 117.12814070351759 108.66938206658291 C 79.18719346733668 277.73956799623113 75.85761180904522 379.96743797110554 82.84673366834171 395.7761659861809 S 148.83130025125627 280.47708118718595 177.12060301507537 244.36661824748745 S 326.1725898241206 61.02036887562815 325.67336683417085 85.815110709799 S 174.998726758794 435.7304316896985 172.8354271356784 457.1970202575377 S 273.65633103015074 310.01551271984926 307.1042713567839 270.07767352386935 S 466.09929459798997 92.08432302135678 459.9422110552764 114.3829499057789 S 266.23512060301505 435.5226006595478 254.2537688442211 461.4821961369347 S 328.1430565326633 368.1639210113065 357.09798994974875 337.2120956344221 S 486.31961118090453 207.61623570979899 502.79396984924625 195.8012916143216 S 511.48859170854274 200.85065719221106 498.50879396984925 235.79626648869348 S 379.73086055276383 489.4401119660804 391.37939698492465 495.76360317211055 S 573.2022663316583 313.03941849874377 598.4962311557789 290.0751609610553 S 608.3285672110553 288.6610529208543 608.4949748743719 298.64551271984925 S 604.9168530150754 352.64801334799 599.9246231155779 375.778678548995 S 540.6820665829146 508.5077162374372 565.643216080402 497.19199513190955 S 690.3761155778894 408.77881799623117 814.1834170854271 278.6480252826633', + }); + var path = new Konva.Path({ + x: 0, + y: 0, + stroke: 'red', + scaleX: 0.4, + scaleY: 0.4, + data: 'M 117.12814070351759 108.66938206658291 C 79.18719346733668 277.73956799623113 75.85761180904522 379.96743797110554 82.84673366834171 395.7761659861809 S 148.83130025125627 280.47708118718595 177.12060301507537 244.36661824748745 S 326.1725898241206 61.02036887562815 325.67336683417085 85.815110709799 S 174.998726758794 435.7304316896985 172.8354271356784 457.1970202575377 S 273.65633103015074 310.01551271984926 307.1042713567839 270.07767352386935 S 466.09929459798997 92.08432302135678 459.9422110552764 114.3829499057789 S 266.23512060301505 435.5226006595478 254.2537688442211 461.4821961369347 S 328.1430565326633 368.1639210113065 357.09798994974875 337.2120956344221 S 486.31961118090453 207.61623570979899 502.79396984924625 195.8012916143216 S 511.48859170854274 200.85065719221106 498.50879396984925 235.79626648869348 S 379.73086055276383 489.4401119660804 391.37939698492465 495.76360317211055 S 573.2022663316583 313.03941849874377 598.4962311557789 290.0751609610553 S 608.3285672110553 288.6610529208543 608.4949748743719 298.64551271984925 S 604.9168530150754 352.64801334799 599.9246231155779 375.778678548995 S 540.6820665829146 508.5077162374372 565.643216080402 497.19199513190955 S 690.3761155778894 408.77881799623117 814.1834170854271 278.6480252826633', + }); + layer.add(path); + // emulate different size function: + // I found the app with custom font + // we calculations were not correct + // so I just coppied text size from that app + textpath._getTextSize = () => { + return { height: 10, width: 5.9399871826171875 }; + }; + layer.add(textpath); + + layer.draw(); + + var rect = textpath.getClientRect(); + assert.equal(Math.round(rect.width), 299); + assert.equal(Math.round(rect.height), 171); + }); + + it.skip('check vertical text path', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + stage.add(layer); + + var textpath = new Konva.TextPath({ + x: -280, + y: -190, + fill: 'black', + fontSize: 10, + fontFamily: 'Arial', + align: 'right', + text: '&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&', + data: 'M 283 383 L 283 187', + }); + layer.add(textpath); + layer.draw(); + + var rect = textpath.getClientRect(); + + assert.equal(rect.height, 200, 'check height'); + }); +}); diff --git a/test/unit/TouchEvents-test.ts b/test/unit/TouchEvents-test.ts new file mode 100644 index 000000000..5d8fa7701 --- /dev/null +++ b/test/unit/TouchEvents-test.ts @@ -0,0 +1,849 @@ +import { assert } from 'chai'; + +import { + addStage, + Konva, + simulateTouchStart, + simulateTouchEnd, + simulateTouchMove, +} from './test-utils'; + +describe('TouchEvents', function () { + // ====================================================== + it('touchstart touchend touchmove tap dbltap', function (done) { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + fill: 'red', + stroke: 'black', + strokeWidth: 4, + }); + + // mobile events + var touchstart = false; + var touchend = false; + var tap = false; + var touchmove = false; + var dbltap = false; + + /* + * mobile + */ + circle.on('touchstart', function () { + touchstart = true; + //log('touchstart'); + //alert('touchstart') + }); + + circle.on('touchend', function () { + touchend = true; + //alert('touchend') + //log('touchend'); + }); + + circle.on('touchmove', function () { + touchmove = true; + //log('touchmove'); + }); + + circle.on('tap', function (evt) { + tap = true; + //log('tap'); + }); + + circle.on('dbltap', function () { + dbltap = true; + //log('dbltap'); + }); + + layer.add(circle); + stage.add(layer); + + // reset inDoubleClickWindow + Konva._touchInDblClickWindow = false; + + // touchstart circle + simulateTouchStart(stage, [{ x: 289, y: 100, id: 0 }]); + + assert(touchstart, '8) touchstart should be true'); + assert(!touchmove, '8) touchmove should be false'); + assert(!touchend, '8) touchend should be false'); + assert(!tap, '8) tap should be false'); + assert(!dbltap, '8) dbltap should be false'); + + // touchend circle + simulateTouchEnd(stage, [], [{ x: 289, y: 100, id: 0 }]); + // end drag is tied to document mouseup and touchend event + // which can't be simulated. call _endDrag manually + //Konva.DD._endDrag(); + + assert(touchstart, '9) touchstart should be true'); + assert(!touchmove, '9) touchmove should be false'); + assert(touchend, '9) touchend should be true'); + assert(tap, '9) tap should be true'); + assert(!dbltap, '9) dbltap should be false'); + + // touchstart circle + simulateTouchStart(stage, [{ x: 289, y: 100, id: 0 }]); + + assert(touchstart, '10) touchstart should be true'); + assert(!touchmove, '10) touchmove should be false'); + assert(touchend, '10) touchend should be true'); + assert(tap, '10) tap should be true'); + assert(!dbltap, '10) dbltap should be false'); + + // touchend circle to triger dbltap + simulateTouchEnd(stage, [], [{ x: 289, y: 100, id: 0 }]); + // end drag is tied to document mouseup and touchend event + // which can't be simulated. call _endDrag manually + //Konva.DD._endDrag(); + + assert(touchstart, '11) touchstart should be true'); + assert(!touchmove, '11) touchmove should be false'); + assert(touchend, '11) touchend should be true'); + assert(tap, '11) tap should be true'); + assert(dbltap, '11) dbltap should be true'); + + setTimeout(function () { + // touchmove circle + simulateTouchMove(stage, [], [{ x: 289, y: 100, id: 0 }]); + + assert(touchstart, '12) touchstart should be true'); + assert(touchmove, '12) touchmove should be true'); + assert(touchend, '12) touchend should be true'); + assert(tap, '12) tap should be true'); + assert(dbltap, '12) dbltap should be true'); + + done(); + }, 17); + }); + + it('tap on stage and second tap on shape should not trigger double tap (check after dbltap)', function (done) { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var bigRect = new Konva.Rect({ + x: 50, + y: 50, + width: 200, + height: 200, + fill: 'yellow', + }); + layer.add(bigRect); + + layer.draw(); + + var bigClicks = 0; + var bigDblClicks = 0; + + // make dblclick + simulateTouchStart(stage, { + x: 100, + y: 100, + }); + simulateTouchEnd(stage, { + x: 100, + y: 100, + }); + simulateTouchStart(stage, { + x: 100, + y: 100, + }); + simulateTouchEnd(stage, { + x: 100, + y: 100, + }); + + bigRect.on('tap', function () { + bigClicks += 1; + }); + + bigRect.on('dbltap', function () { + bigDblClicks += 1; + }); + + simulateTouchStart(stage, { + x: 10, + y: 10, + }); + simulateTouchEnd(stage, { + x: 10, + y: 10, + }); + + assert.equal(bigClicks, 0); + assert.equal(bigDblClicks, 0); + + simulateTouchStart(stage, { + x: 100, + y: 100, + }); + simulateTouchEnd(stage, { + x: 100, + y: 100, + }); + + assert.equal(bigClicks, 1); + assert.equal(bigDblClicks, 0); + + done(); + }); + + // test for https://github.com/konvajs/konva/issues/156 + it('touchstart out of shape, then touch end inside shape', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: 100, + y: 100, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + }); + + layer.add(circle); + stage.add(layer); + + var circleTouchend = 0; + + circle.on('touchend', function () { + circleTouchend++; + }); + + simulateTouchStart(stage, [{ x: 1, y: 1, id: 0 }]); + simulateTouchEnd(stage, [], [{ x: 100, y: 100, id: 0 }]); + + assert.equal(circleTouchend, 1); + }); + + it('tap on one shape, then fast tap on another shape should no trigger double tap', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var circle1 = new Konva.Circle({ + x: 100, + y: 100, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + }); + + layer.add(circle1); + + var circle2 = new Konva.Circle({ + x: 200, + y: 100, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + }); + + layer.add(circle2); + + layer.draw(); + + var circle1Tap = 0; + var circle2Tap = 0; + var circle2DoubleTap = 0; + + circle1.on('tap', function () { + circle1Tap++; + }); + circle2.on('tap', function () { + circle2Tap++; + }); + circle2.on('dbltap', function () { + circle2DoubleTap++; + }); + + simulateTouchStart(stage, { x: 100, y: 100 }); + simulateTouchEnd(stage, { x: 100, y: 100 }); + + assert.equal(circle1Tap, 1, 'should trigger tap on first circle'); + assert.equal(circle2Tap, 0, 'should NOT trigger tap on second circle'); + assert.equal( + circle2DoubleTap, + 0, + 'should NOT trigger dbltap on second circle' + ); + + simulateTouchStart(stage, { x: 200, y: 100 }); + simulateTouchEnd(stage, { x: 200, y: 100 }); + + assert.equal(circle1Tap, 1, 'should trigger tap on first circle'); + assert.equal(circle2Tap, 1, 'should trigger tap on second circle'); + assert.equal( + circle2DoubleTap, + 0, + 'should NOT trigger dbltap on second circle' + ); + }); + + it('multitouch - register all touches', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var circle1 = new Konva.Circle({ + x: 100, + y: 100, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle1', + draggable: true, + }); + layer.add(circle1); + + var circle2 = new Konva.Circle({ + x: 100, + y: 200, + radius: 80, + fill: 'red', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle2', + draggable: true, + }); + + layer.add(circle2); + layer.draw(); + + var touchStart = 0; + var touchMove = 0; + var touchEnd = 0; + var touchEnd2 = 0; + + circle1.on('touchstart', function () { + touchStart++; + }); + circle1.on('touchmove', function () { + touchMove++; + }); + circle1.on('touchend', function () { + touchEnd++; + }); + + circle2.on('touchend', function () { + touchEnd2++; + }); + + var stageTouchStart = 0; + var stageTouchMove = 0; + var stageTouchEnd = 0; + var stageTap = 0; + var stageEventStack: string[] = []; + stage.on('touchstart', function () { + stageTouchStart++; + stageEventStack.push('touchstart'); + }); + stage.on('touchmove', function () { + stageTouchMove++; + }); + stage.on('touchend', function () { + stageTouchEnd++; + stageEventStack.push('touchend'); + }); + stage.on('tap', function () { + stageTap++; + stageEventStack.push('tap'); + }); + + // start with one touch + simulateTouchStart( + stage, + [{ x: 100, y: 100, id: 0 }], + [{ x: 100, y: 100, id: 0 }] + ); + + assert.equal(stageTouchStart, 1, 'trigger first touch start on stage'); + assert.equal(touchStart, 1, 'trigger first touch start on circle'); + + // make second touch + simulateTouchStart( + stage, + [ + { x: 100, y: 100, id: 0 }, + { x: 210, y: 100, id: 1 }, + ], + [{ x: 210, y: 100, id: 1 }] + ); + + assert.equal( + stageTouchStart, + 2, + 'should trigger the second touch on stage' + ); + assert.equal( + touchStart, + 1, + 'should not trigger the second touch start (it is outside)' + ); + + // now try to make two touches at the same time + simulateTouchStart( + stage, + [ + { x: 100, y: 100, id: 0 }, + { x: 210, y: 100, id: 1 }, + ], + [ + { x: 100, y: 100, id: 0 }, + { x: 210, y: 100, id: 1 }, + ] + ); + + assert.equal(stageTouchStart, 3, 'should trigger one more touch'); + assert.equal( + touchStart, + 2, + 'should trigger the second touch start on the circle' + ); + + // check variables + assert.deepEqual(stage.getPointerPosition(), { x: 100, y: 100 }); + assert.deepEqual(stage.getPointersPositions(), [ + { x: 100, y: 100, id: 0 }, + { x: 210, y: 100, id: 1 }, + ]); + + // move one finger + simulateTouchMove( + stage, + [ + { x: 100, y: 100, id: 0 }, + { x: 220, y: 100, id: 1 }, + ], + [{ x: 220, y: 100, id: 1 }] + ); + assert.equal(touchMove, 0, 'should not trigger touch move on circle'); + assert.equal(stageTouchMove, 1, 'should trigger touch move on stage'); + + // move two fingers + simulateTouchMove( + stage, + [ + { x: 100, y: 100, id: 0 }, + { x: 220, y: 100, id: 1 }, + ], + [ + { x: 100, y: 100, id: 0 }, + { x: 220, y: 100, id: 1 }, + ] + ); + assert.equal(touchMove, 1, 'should trigger touch move on circle'); + assert.equal( + stageTouchMove, + 2, + 'should trigger two more touchmoves on stage' + ); + + simulateTouchEnd( + stage, + [], + [ + { x: 100, y: 100, id: 0 }, + { x: 220, y: 100, id: 1 }, + ] + ); + assert.equal(touchEnd, 1); + assert.equal(stageTouchEnd, 1); + assert.equal(stageTap, 1, 'one tap should be fired'); + + assert.equal( + stageEventStack.join(' '), + 'touchstart touchstart touchstart touchend tap', + 'should fire tap after touchend' + ); + + // try two touch ends on both shapes + simulateTouchEnd( + stage, + [], + [ + { x: 100, y: 100, id: 0 }, + { x: 100, y: 170, id: 1 }, + ] + ); + + assert.equal(touchEnd, 2); + assert.equal(touchEnd2, 1); + assert.equal(stageTouchEnd, 3); + assert.equal(stageTap, 1, 'still one tap should be fired'); + // Don't need to check event stack here, the pointers moved so no tap is fired + }); + + it.skip('letting go of two fingers quickly should not fire dbltap', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var touchend = 0; + var dbltap = 0; + + stage.on('dbltap', function (e) { + dbltap += 1; + }); + + stage.on('touchend', function (e) { + touchend += 1; + }); + + simulateTouchStart( + stage, + [{ x: 100, y: 100, id: 0 }], + [{ x: 100, y: 100, id: 0 }] + ); + + simulateTouchStart( + stage, + [{ x: 110, y: 110, id: 1 }], + [{ x: 110, y: 110, id: 1 }] + ); + + assert.equal( + touchend, + 0, + '1) no touchend triggered after holding down two fingers' + ); + assert.equal( + dbltap, + 0, + '1) no dbltap triggered after holding down two fingers' + ); + + simulateTouchEnd( + stage, + [{ x: 110, y: 110, id: 1 }], + [{ x: 100, y: 100, id: 0 }] + ); + simulateTouchEnd(stage, [], [{ x: 110, y: 110, id: 1 }]); + + assert.equal( + touchend, + 2, + '2) touchend triggered twice after letting go two fingers' + ); + assert.equal( + dbltap, + 0, + '2) no dbltap triggered after letting go two fingers' + ); + }); + + it('can capture touch events', function () { + Konva.capturePointerEventsEnabled = true; + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var circle1 = new Konva.Circle({ + x: 100, + y: 100, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle1', + }); + layer.add(circle1); + + layer.draw(); + + var touchStart = 0; + var touchMove = 0; + var touchEnd = 0; + + circle1.on('touchstart', function (e) { + touchStart++; + }); + circle1.on('touchmove', function () { + touchMove++; + }); + circle1.on('touchend', function () { + touchEnd++; + }); + + simulateTouchStart( + stage, + [{ x: 100, y: 100, id: 0 }], + [{ x: 100, y: 100, id: 0 }] + ); + + // go out of circle + simulateTouchMove( + stage, + [{ x: 180, y: 100, id: 0 }], + [{ x: 180, y: 100, id: 0 }] + ); + assert.equal(touchMove, 1, 'first touchmove'); + + // add another finger + simulateTouchStart( + stage, + [ + { x: 180, y: 100, id: 0 }, + { x: 100, y: 100, id: 1 }, + ], + [{ x: 100, y: 100, id: 1 }] + ); + + // move all out + simulateTouchMove( + stage, + [ + { x: 185, y: 100, id: 0 }, + { x: 190, y: 100, id: 1 }, + ], + [ + { x: 185, y: 100, id: 0 }, + { x: 190, y: 100, id: 1 }, + ] + ); + // should trigger just one more touchmove + assert.equal(touchMove, 2, 'second touchmove'); + + // remove fingers + simulateTouchEnd( + stage, + [], + [ + { x: 185, y: 100, id: 0 }, + { x: 190, y: 100, id: 1 }, + ] + ); + + assert.equal(touchEnd, 1, 'first touchend'); + + // should release captures on touchend + assert.equal(circle1.hasPointerCapture(0), false); + assert.equal(circle1.hasPointerCapture(1), false); + + Konva.capturePointerEventsEnabled = false; + }); + + it('tap and double tap should trigger just once on stage', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var circle1 = new Konva.Circle({ + x: 100, + y: 100, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle1', + }); + layer.add(circle1); + layer.draw(); + + var tap = 0; + var dbltap = 0; + + stage.on('tap', function (e) { + assert.equal(e.target, circle1); + tap += 1; + }); + + stage.on('dbltap', function (e) { + assert.equal(e.target, circle1); + dbltap += 1; + }); + + simulateTouchStart( + stage, + [{ x: 100, y: 100, id: 0 }], + [{ x: 100, y: 100, id: 0 }] + ); + + simulateTouchEnd(stage, [], [{ x: 100, y: 100, id: 0 }]); + + assert.equal(tap, 1, 'tap triggered'); + assert.equal(dbltap, 0, 'no dbltap triggered'); + + simulateTouchStart( + stage, + [{ x: 100, y: 100, id: 0 }], + [{ x: 100, y: 100, id: 0 }] + ); + + simulateTouchEnd(stage, [], [{ x: 100, y: 100, id: 0 }]); + assert.equal(tap, 2, 'tap triggered'); + assert.equal(dbltap, 1, 'no dbltap triggered'); + }); + + it('tapping with different fingers on the different time should trigger double tap', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var circle1 = new Konva.Circle({ + x: 100, + y: 100, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle1', + }); + layer.add(circle1); + layer.draw(); + + var tap = 0; + var dbltap = 0; + + stage.on('tap', function (e) { + assert.equal(e.target, circle1); + tap += 1; + }); + + stage.on('dbltap', function (e) { + assert.equal(e.target, circle1); + dbltap += 1; + }); + + simulateTouchStart( + stage, + [{ x: 100, y: 100, id: 0 }], + [{ x: 100, y: 100, id: 0 }] + ); + + simulateTouchEnd(stage, [], [{ x: 100, y: 100, id: 0 }]); + + assert.equal(tap, 1, 'tap triggered'); + assert.equal(dbltap, 0, 'no dbltap triggered'); + + simulateTouchStart( + stage, + [{ x: 100, y: 100, id: 1 }], + [{ x: 100, y: 100, id: 1 }] + ); + + simulateTouchEnd(stage, [], [{ x: 100, y: 100, id: 1 }]); + assert.equal(tap, 2, 'tap triggered'); + assert.equal(dbltap, 1, 'dbltap triggered'); + }); + + it('drag and second tap should not trigger dbltap', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var circle1 = new Konva.Circle({ + x: 100, + y: 100, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle1', + draggable: true, + }); + layer.add(circle1); + layer.draw(); + + var tap = 0; + var dbltap = 0; + var dragmove = 0; + + stage.on('tap', function (e) { + assert.equal(e.target, circle1); + tap += 1; + }); + + stage.on('dbltap', function (e) { + dbltap += 1; + }); + + stage.on('dragmove', function (e) { + dragmove += 1; + }); + + simulateTouchStart( + stage, + [{ x: 100, y: 100, id: 0 }], + [{ x: 100, y: 100, id: 0 }] + ); + + simulateTouchMove( + stage, + [{ x: 150, y: 150, id: 0 }], + [{ x: 150, y: 150, id: 0 }] + ); + + simulateTouchEnd(stage, [], [{ x: 150, y: 150, id: 0 }]); + + assert.equal(tap, 0, 'no tap triggered'); + assert.equal(dbltap, 0, 'no dbltap triggered'); + assert.equal(dragmove, 1, 'dragmove triggered'); + + simulateTouchStart( + stage, + [{ x: 150, y: 150, id: 0 }], + [{ x: 150, y: 150, id: 0 }] + ); + + simulateTouchEnd(stage, [], [{ x: 150, y: 150, id: 0 }]); + + assert.equal(tap, 1, 'tap triggered'); + assert.equal(dbltap, 0, 'no dbltap triggered'); + }); + + it('tap should give pointer position', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var circle1 = new Konva.Circle({ + x: 100, + y: 100, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle1', + draggable: true, + }); + layer.add(circle1); + layer.draw(); + + var tap = 0; + var click = 0; + + stage.on('tap', function (e) { + assert.equal(e.target, circle1); + assert.equal(stage.getPointerPosition().x, 100); + assert.equal(stage.getPointerPosition().y, 100); + tap += 1; + }); + + stage.on('click', function (e) { + click += 1; + }); + + simulateTouchStart( + stage, + [{ x: 100, y: 100, id: 0 }], + [{ x: 100, y: 100, id: 0 }] + ); + + simulateTouchEnd(stage, [], [{ x: 100, y: 100, id: 0 }]); + + assert.equal(tap, 1, 'tap triggered'); + assert.equal(click, 0, 'no click triggered'); + }); +}); diff --git a/test/unit/Transformer-test.ts b/test/unit/Transformer-test.ts new file mode 100644 index 000000000..af6782df1 --- /dev/null +++ b/test/unit/Transformer-test.ts @@ -0,0 +1,5091 @@ +import { assert } from 'chai'; +import { Transformer } from '../../src/shapes/Transformer'; + +import { + addStage, + isNode, + Konva, + simulateMouseDown as sd, + simulateMouseMove as sm, + simulateMouseUp as su, + assertAlmostEqual, +} from './test-utils'; + +function simulateMouseDown(tr, pos) { + sd(tr.getStage(), pos); +} + +function simulateMouseMove(tr, pos) { + const stage = tr.getStage(); + var top = (stage.content && stage.content.getBoundingClientRect().top) || 0; + tr._handleMouseMove({ + ...pos, + clientX: pos.x, + clientY: pos.y + top, + }); + sm(stage, pos); +} + +function simulateMouseUp(tr: Transformer, pos = { x: 0, y: 0 }) { + const stage = tr.getStage(); + var top = (stage.content && stage.content.getBoundingClientRect().top) || 0; + tr._handleMouseUp({ + clientX: pos.x, + clientY: pos.y + top, + }); + su(tr.getStage(), pos || { x: 1, y: 1 }); +} + +describe('Transformer', function () { + // ====================================================== + it('init transformer on simple rectangle', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + x: 100, + y: 60, + draggable: true, + width: 100, + height: 100, + fill: 'yellow', + }); + layer.add(rect); + + var tr = new Konva.Transformer({ + nodes: [rect], + }); + layer.add(tr); + + layer.draw(); + assert.equal(tr.getClassName(), 'Transformer'); + + assert.equal(tr.x(), rect.x()); + assert.equal(tr.y(), rect.y()); + assert.equal(tr.width(), rect.width()); + assert.equal(tr.height(), rect.height()); + + // manual check of correct position of node + var handler = tr.findOne('.bottom-right'); + var pos = handler.getAbsolutePosition(); + assert.equal(pos.x, rect.x() + rect.width()); + assert.equal(pos.y, rect.y() + rect.height()); + }); + + it('can attach transformer into several nodes', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect1 = new Konva.Rect({ + x: 10, + y: 10, + draggable: true, + width: 100, + height: 50, + fill: 'yellow', + }); + layer.add(rect1); + + var rect2 = new Konva.Rect({ + x: 110, + y: 60, + draggable: true, + width: 100, + height: 50, + fill: 'red', + }); + + layer.add(rect2); + + var tr = new Konva.Transformer({ + nodes: [rect1, rect2], + }); + layer.add(tr); + + layer.draw(); + assert.equal(tr.x(), rect1.x()); + assert.equal(tr.y(), rect1.y()); + assert.equal(tr.width(), rect1.width() + rect2.width()); + assert.equal(tr.height(), rect1.height() + rect2.height()); + assert.equal(tr.rotation(), 0); + }); + + it('try set/get node', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + x: 100, + y: 60, + draggable: true, + width: 100, + height: 100, + fill: 'yellow', + }); + layer.add(rect); + + var circle = new Konva.Circle({ + x: 10, + y: 60, + radius: 100, + fill: 'red', + }); + layer.add(circle); + + var tr = new Konva.Transformer({ + nodes: [rect], + }); + layer.add(tr); + + layer.draw(); + assert.equal(tr.nodes()[0], rect); + + tr.nodes([circle]); + assert.equal(tr.nodes()[0], circle); + layer.draw(); + }); + + it('try to fit simple rectangle', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + x: 100, + y: 60, + draggable: true, + width: 100, + height: 100, + fill: 'yellow', + }); + layer.add(rect); + + var tr = new Konva.Transformer(); + layer.add(tr); + tr.nodes([rect]); + + layer.draw(); + + tr._fitNodesInto({ + x: 120, + y: 60, + width: 50, + height: 50, + rotation: Konva.getAngle(45), + }); + + assert.equal(tr.x(), rect.x()); + assert.equal(Math.round(tr.y()), rect.y()); + assert.equal(tr.width(), 50); + assert.equal(tr.height(), 50); + assert.equal(tr.rotation(), rect.rotation()); + }); + + it('try to fit simple rotated rectangle', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + x: 50, + y: 50, + draggable: true, + width: 100, + height: 150, + fill: 'yellow', + rotation: 45, + }); + layer.add(rect); + + var tr = new Konva.Transformer(); + layer.add(tr); + tr.nodes([rect]); + + layer.draw(); + + tr._fitNodesInto({ + x: 50, + y: 50, + width: 100, + height: 150, + rotation: Konva.getAngle(45), + }); + + assertAlmostEqual(rect.x(), 50); + assertAlmostEqual(rect.y(), 50); + assertAlmostEqual(tr.width(), 100); + assertAlmostEqual(tr.height(), 150); + assertAlmostEqual(tr.rotation(), rect.rotation()); + }); + + it('transformer should follow rotation on single node', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + x: 100, + y: 60, + draggable: true, + width: 100, + height: 100, + fill: 'yellow', + }); + layer.add(rect); + + var tr = new Konva.Transformer({ + nodes: [rect], + }); + layer.add(tr); + + layer.draw(); + + rect.rotation(45); + layer.draw(); + + assert.equal(tr.rotation(), 45); + }); + + it('try to fit simple rotated rectangle in group', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var group = new Konva.Group({ + rotation: 45, + x: 50, + y: 50, + }); + layer.add(group); + + var rect = new Konva.Rect({ + draggable: true, + width: 100, + height: 150, + fill: 'yellow', + }); + group.add(rect); + + var tr = new Konva.Transformer(); + layer.add(tr); + tr.nodes([rect]); + + layer.draw(); + + tr._fitNodesInto({ + x: 50, + y: 50, + width: 100, + height: 150, + rotation: 0, + }); + + assertAlmostEqual(rect.x(), 0); + assertAlmostEqual(rect.y(), 0); + assertAlmostEqual(tr.width(), 100); + assertAlmostEqual(tr.height(), 150); + assertAlmostEqual(rect.rotation(), -45); + }); + + it('transformer should follow rotation on single node inside group', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var group = new Konva.Group({ + rotation: 45, + }); + layer.add(group); + + var rect = new Konva.Rect({ + x: 100, + y: 60, + draggable: true, + width: 100, + height: 100, + fill: 'yellow', + }); + group.add(rect); + + var tr = new Konva.Transformer({ + nodes: [rect], + }); + group.add(tr); + + layer.draw(); + + rect.rotation(45); + layer.draw(); + + assertAlmostEqual(tr.rotation(), 90); + }); + + it('try to fit simple rotated rectangle - 2', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + x: 50, + y: 50, + draggable: true, + width: 100, + height: 200, + fill: 'yellow', + rotation: 45, + }); + layer.add(rect); + + var tr = new Konva.Transformer(); + layer.add(tr); + tr.nodes([rect]); + + layer.draw(); + + tr._fitNodesInto({ + x: 40, + y: 40, + width: 100, + height: 100, + rotation: 0, + }); + + assertAlmostEqual(rect.x(), 40); + assertAlmostEqual(rect.y(), 40); + assertAlmostEqual(rect.width(), 100); + assertAlmostEqual(rect.height(), 200); + assertAlmostEqual(rect.scaleY(), 0.5); + assertAlmostEqual(rect.rotation(), 0); + }); + + it('rotate around center', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + x: 50, + y: 50, + draggable: true, + width: 100, + height: 200, + fill: 'yellow', + rotation: 45, + }); + layer.add(rect); + + var tr = new Konva.Transformer(); + layer.add(tr); + tr.nodes([rect]); + + layer.draw(); + + tr._fitNodesInto({ + x: 40, + y: 40, + width: 100, + height: 100, + rotation: 0, + }); + + assertAlmostEqual(rect.x(), 40); + assertAlmostEqual(rect.y(), 40); + assertAlmostEqual(rect.width(), 100); + assertAlmostEqual(rect.height(), 200); + assertAlmostEqual(rect.scaleY(), 0.5); + assertAlmostEqual(rect.rotation(), 0); + }); + + it('change transform of parent', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + x: 50, + y: 50, + draggable: true, + width: 100, + height: 100, + fill: 'yellow', + }); + layer.add(rect); + + var tr = new Konva.Transformer({ + nodes: [rect], + }); + layer.add(tr); + + layer.draw(); + + assert.equal(tr.x(), 50, 'first x'); + + stage.scaleX(2); + stage.scaleY(2); + + // check attrs + assert.equal(tr.x(), 100, 'second x'); + assert.equal(tr.width(), 200); + // check visual + var pos = tr.findOne('.top-right').getAbsolutePosition(); + assert.equal(pos.x, 300); + + stage.draw(); + }); + + it('rotated inside scaled (in one direction) parent', function () { + var stage = addStage(); + stage.scaleX(2); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + x: 50, + y: 50, + draggable: true, + width: 100, + height: 100, + fill: 'yellow', + }); + layer.add(rect); + + var tr = new Konva.Transformer({ + nodes: [rect], + }); + layer.add(tr); + + layer.draw(); + tr._fitNodesInto({ + x: 100, + y: 0, + width: 50, + height: 50, + rotation: Konva.getAngle(45), + }); + + assert.equal(rect.x(), 50); + assert.equal(rect.skewX(), 0.75); + assert.equal(rect.skewY(), 0); + }); + + it('try to fit rectangle with skew', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + x: 100, + y: 60, + draggable: true, + width: 100, + height: 100, + fill: 'yellow', + skewX: 0.5, + scaleX: 2, + }); + layer.add(rect); + + var tr = new Konva.Transformer(); + layer.add(tr); + tr.nodes([rect]); + + layer.draw(); + + tr._fitNodesInto({ + x: 120, + y: 60, + width: 50, + height: 50, + rotation: Konva.getAngle(45), + }); + + assert.equal(tr.x(), rect.x()); + assert.equal(Math.round(tr.y()), rect.y()); + assert.equal(tr.width(), 50); + assert.equal(tr.height(), 50); + assert.equal(tr.rotation(), rect.rotation()); + assertAlmostEqual(rect.skewX(), 0.2); + }); + + it('try to resize in draggable stage', function () { + var stage = addStage(); + stage.draggable(true); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + x: 50, + y: 50, + draggable: true, + width: 100, + height: 100, + fill: 'yellow', + }); + layer.add(rect); + + var tr = new Konva.Transformer({ + nodes: [rect], + }); + layer.add(tr); + layer.draw(); + + var dragstart = 0; + var dragmove = 0; + var dragend = 0; + stage.on('dragstart', function () { + dragstart += 1; + }); + stage.on('dragmove', function () { + dragmove += 1; + }); + stage.on('dragend', function () { + dragend += 1; + }); + + simulateMouseDown(tr, { + x: 50, + y: 50, + }); + simulateMouseMove(tr, { + x: 60, + y: 60, + }); + assert.equal(stage.isDragging(), false); + assert.equal(dragstart, 0); + simulateMouseUp(tr, { x: 60, y: 60 }); + assert.equal(dragmove, 0); + assert.equal(dragend, 0); + + simulateMouseDown(tr, { + x: 40, + y: 40, + }); + sm(stage, { + x: 45, + y: 45, + }); + sm(stage, { + x: 50, + y: 50, + }); + assert.equal(stage.isDragging(), true); + assert.equal(stage.x(), 10); + assert.equal(stage.y(), 10); + su(stage, { + x: 45, + y: 45, + }); + }); + + it('try to fit simple rectangle into negative scale', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + x: 0, + y: 0, + draggable: true, + width: 100, + height: 100, + fill: 'yellow', + }); + layer.add(rect); + + var tr = new Konva.Transformer(); + layer.add(tr); + tr.nodes([rect]); + + layer.draw(); + + var box = { + x: 100, + y: 0, + width: -100, + height: 100, + rotation: 0, + }; + + tr._fitNodesInto(box); + + assertAlmostEqual(rect.x(), 100); + assertAlmostEqual(rect.y(), 0); + assertAlmostEqual(rect.width(), 100); + assertAlmostEqual(rect.scaleX(), 1); + assertAlmostEqual(rect.height(), 100); + assertAlmostEqual(rect.scaleY(), -1); + assertAlmostEqual(rect.rotation(), -180); + + layer.draw(); + }); + it('try to fit rectangle with ignoreStroke = false', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + x: 20, + y: 20, + width: 100, + height: 100, + fill: 'green', + stroke: 'rgba(0,0,0,0.5)', + strokeWidth: 40, + name: 'myCircle', + draggable: true, + strokeScaleEnabled: false, + }); + layer.add(rect); + + var tr = new Konva.Transformer({ + ignoreStroke: true, + }); + layer.add(tr); + tr.nodes([rect]); + + layer.draw(); + + tr._fitNodesInto({ + x: 20, + y: 20, + width: 200, + height: 200, + rotation: 0, + }); + + assertAlmostEqual(rect.x(), 20); + assertAlmostEqual(rect.y(), 20); + assertAlmostEqual(rect.width(), 100); + assertAlmostEqual(rect.height(), 100); + assertAlmostEqual(rect.scaleX(), 2); + }); + + it('listen shape changes', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + draggable: true, + fill: 'yellow', + }); + layer.add(rect); + + var tr = new Konva.Transformer(); + layer.add(tr); + tr.nodes([rect]); + + layer.draw(); + assert.equal(tr.getClassName(), 'Transformer'); + + rect.setAttrs({ + x: 50, + y: 50, + width: 100, + height: 100, + }); + layer.draw(); + assert.equal(tr.x(), rect.x()); + assert.equal(tr.y(), rect.y()); + assert.equal(tr.width(), rect.width()); + assert.equal(tr.height(), rect.height()); + assert.equal(tr.findOne('.back').width(), rect.width()); + }); + + it('add transformer for transformed rect', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + x: 150, + y: 60, + draggable: true, + width: 100, + height: 100, + fill: 'yellow', + rotation: 90, + scaleY: 1.5, + }); + layer.add(rect); + + var tr = new Konva.Transformer(); + layer.add(tr); + tr.nodes([rect]); + + layer.draw(); + assert.equal(tr.getClassName(), 'Transformer'); + + layer.draw(); + + assert.equal(tr.x(), rect.x()); + assert.equal(tr.y(), rect.y()); + assert.equal(tr.width(), rect.width() * rect.scaleX()); + assert.equal(tr.height(), rect.height() * rect.scaleY()); + assert.equal(tr.rotation(), rect.rotation()); + }); + + it('try to fit a transformed rect', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + x: 150, + y: 60, + draggable: true, + width: 150, + height: 100, + fill: 'yellow', + rotation: 90, + scaleY: 1.5, + }); + layer.add(rect); + + var tr = new Konva.Transformer(); + layer.add(tr); + tr.nodes([rect]); + + layer.draw(); + + tr._fitNodesInto({ + x: 100, + y: 70, + width: 100, + height: 100, + rotation: 0, + }); + + assertAlmostEqual(rect.x(), 100); + assertAlmostEqual(rect.y(), 70); + assertAlmostEqual(rect.width() * rect.scaleX(), 100); + assertAlmostEqual(rect.height() * rect.scaleY(), 100); + assertAlmostEqual(rect.rotation(), rect.rotation()); + }); + + it('add transformer for transformed rect with offset', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + x: 100, + y: 100, + draggable: true, + width: 100, + height: 100, + scaleX: 2, + scaleY: 2, + fill: 'yellow', + offsetX: 50, + offsetY: 50, + }); + layer.add(rect); + + var tr = new Konva.Transformer(); + layer.add(tr); + tr.nodes([rect]); + + layer.draw(); + assert.equal(tr.getClassName(), 'Transformer'); + + assert.equal(tr.x(), 0); + assert.equal(tr.y(), 0); + assert.equal(tr.width(), rect.width() * rect.scaleX()); + assert.equal(tr.height(), rect.height() * rect.scaleY()); + assert.equal(tr.rotation(), rect.rotation()); + }); + + it('fit rect with offset', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + x: 50, + y: 50, + draggable: true, + width: 100, + height: 100, + fill: 'yellow', + offsetX: 50, + offsetY: 50, + }); + layer.add(rect); + + var tr = new Konva.Transformer(); + layer.add(tr); + tr.nodes([rect]); + + tr._fitNodesInto({ + x: 0, + y: 0, + width: 200, + height: 100, + rotation: 0, + }); + layer.draw(); + + assertAlmostEqual(rect.x(), 100); + assertAlmostEqual(rect.y(), 50); + assertAlmostEqual(rect.width() * rect.scaleX(), 200); + assertAlmostEqual(rect.height() * rect.scaleY(), 100); + assertAlmostEqual(rect.rotation(), rect.rotation()); + + assertAlmostEqual(tr.x(), 0); + assertAlmostEqual(tr.y(), 0); + assertAlmostEqual(tr.width(), 200); + assertAlmostEqual(tr.height(), 100); + assertAlmostEqual(rect.rotation(), rect.rotation()); + }); + + it('add transformer for circle', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var circle = new Konva.Circle({ + x: 40, + y: 40, + draggable: true, + radius: 40, + fill: 'yellow', + }); + layer.add(circle); + + var tr = new Konva.Transformer(); + layer.add(tr); + tr.nodes([circle]); + + layer.draw(); + assert.equal(tr.getClassName(), 'Transformer'); + + assert.equal(tr.x(), 0); + assert.equal(tr.y(), 0); + assert.equal(tr.width(), circle.width() * circle.scaleX()); + assert.equal(tr.height(), circle.height() * circle.scaleY()); + assert.equal(tr.rotation(), circle.rotation()); + }); + + it('fit a circle', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var circle = new Konva.Circle({ + x: 40, + y: 40, + draggable: true, + radius: 40, + fill: 'yellow', + }); + layer.add(circle); + + var tr = new Konva.Transformer(); + layer.add(tr); + tr.nodes([circle]); + + tr._fitNodesInto({ + x: 40, + y: 40, + width: 160, + height: 80, + rotation: 0, + }); + layer.draw(); + + assert.equal(circle.x(), 120); + assert.equal(circle.y(), 80); + assert.equal(circle.width() * circle.scaleX(), 160); + assert.equal(circle.height() * circle.scaleY(), 80); + + assert.equal(tr.x(), 40); + assert.equal(tr.y(), 40); + assert.equal(tr.width(), 160); + assert.equal(tr.height(), 80); + }); + + it('fit a rotated circle', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var circle = new Konva.Circle({ + x: 40, + y: 40, + draggable: true, + radius: 40, + fill: 'yellow', + }); + layer.add(circle); + + var tr = new Konva.Transformer(); + layer.add(tr); + tr.nodes([circle]); + + tr._fitNodesInto({ + x: 80, + y: 0, + width: 80, + height: 80, + rotation: Konva.getAngle(90), + }); + layer.draw(); + + assertAlmostEqual(circle.x(), 40); + assertAlmostEqual(circle.y(), 40); + assertAlmostEqual(circle.width() * circle.scaleX(), 80); + assertAlmostEqual(circle.height() * circle.scaleY(), 80); + assertAlmostEqual(circle.rotation(), 90); + + assertAlmostEqual(tr.x(), 80); + assertAlmostEqual(tr.y(), 0); + assertAlmostEqual(tr.width(), 80); + assertAlmostEqual(tr.height(), 80); + }); + + it('add transformer for transformed circle', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var circle = new Konva.Circle({ + x: 100, + y: 100, + draggable: true, + radius: 40, + fill: 'yellow', + scaleX: 1.5, + }); + layer.add(circle); + + var tr = new Konva.Transformer(); + layer.add(tr); + tr.nodes([circle]); + + layer.draw(); + assert.equal(tr.getClassName(), 'Transformer'); + + assert.equal(tr.x(), 40); + assert.equal(tr.y(), 60); + assert.equal(tr.width(), 120); + assert.equal(tr.height(), 80); + assert.equal(tr.rotation(), 0); + }); + + it('add transformer for rotated circle', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var circle = new Konva.Circle({ + x: 100, + y: 100, + draggable: true, + radius: 40, + fill: 'yellow', + scaleX: 1.5, + rotation: 90, + }); + layer.add(circle); + + var tr = new Konva.Transformer(); + layer.add(tr); + tr.nodes([circle]); + + layer.draw(); + + assert.equal(tr.x(), 140); + assert.equal(tr.y(), 40); + assert.equal(tr.width(), 120); + assert.equal(tr.height(), 80); + assert.equal(tr.rotation(), circle.rotation()); + }); + + it('add transformer to group', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var group = new Konva.Group({ + x: 50, + y: 50, + draggable: true, + }); + layer.add(group); + + var shape1 = new Konva.Rect({ + radius: 100, + fill: 'red', + x: 0, + y: 0, + width: 100, + height: 100, + }); + + group.add(shape1); + + var shape2 = new Konva.Rect({ + radius: 100, + fill: 'yellow', + x: 50, + y: 50, + width: 100, + height: 100, + }); + group.add(shape2); + + var tr = new Konva.Transformer(); + layer.add(tr); + tr.nodes([group]); + + layer.draw(); + + assert.equal(tr.x(), group.x()); + assert.equal(tr.y(), group.y()); + assert.equal(tr.width(), 150); + assert.equal(tr.height(), 150); + assert.equal(tr.rotation(), 0); + }); + + it('rotated fit group', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var group = new Konva.Group({ + x: 100, + y: 100, + draggable: true, + }); + layer.add(group); + + var shape1 = new Konva.Rect({ + fill: 'red', + x: -50, + y: -50, + width: 50, + height: 50, + }); + + group.add(shape1); + + var shape2 = new Konva.Rect({ + fill: 'yellow', + x: 0, + y: 0, + width: 50, + height: 50, + }); + group.add(shape2); + + var tr = new Konva.Transformer(); + layer.add(tr); + tr.nodes([group]); + + tr._fitNodesInto({ + x: 100, + y: 0, + width: 100, + height: 100, + rotation: Konva.getAngle(90), + }); + layer.draw(); + + var rect = group.getClientRect(); + + assertAlmostEqual(group.x(), 50); + assertAlmostEqual(group.y(), 50); + assertAlmostEqual(rect.width, 100); + assertAlmostEqual(rect.height, 100); + assertAlmostEqual(group.rotation(), 90); + + assertAlmostEqual(tr.x(), 100); + assertAlmostEqual(tr.y(), 0); + assertAlmostEqual(tr.width(), 100); + assertAlmostEqual(tr.height(), 100); + }); + + it('add transformer to another group', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var group = new Konva.Group({ + x: 100, + y: 100, + draggable: true, + }); + layer.add(group); + + var shape1 = new Konva.Rect({ + fill: 'red', + x: -50, + y: -50, + width: 50, + height: 50, + }); + + group.add(shape1); + + var shape2 = new Konva.Rect({ + fill: 'yellow', + x: 0, + y: 0, + width: 50, + height: 50, + }); + group.add(shape2); + + var tr = new Konva.Transformer(); + layer.add(tr); + tr.nodes([group]); + + layer.draw(); + + assert.equal(tr.x(), 50); + assert.equal(tr.y(), 50); + assert.equal(tr.width(), 100); + assert.equal(tr.height(), 100); + assert.equal(tr.rotation(), 0); + }); + + it('fit group', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var group = new Konva.Group({ + x: 100, + y: 100, + draggable: true, + }); + layer.add(group); + + var shape1 = new Konva.Rect({ + fill: 'red', + x: -50, + y: -50, + width: 50, + height: 50, + }); + + group.add(shape1); + + var shape2 = new Konva.Rect({ + fill: 'yellow', + x: 0, + y: 0, + width: 50, + height: 50, + }); + group.add(shape2); + + var tr = new Konva.Transformer(); + layer.add(tr); + tr.nodes([group]); + + tr._fitNodesInto({ + x: 0, + y: 0, + width: 200, + height: 100, + rotation: 0, + }); + layer.draw(); + + var rect = group.getClientRect(); + + assertAlmostEqual(group.x(), 100); + assertAlmostEqual(group.y(), 50); + assertAlmostEqual(rect.width, 200); + assertAlmostEqual(rect.height, 100); + + assertAlmostEqual(tr.x(), 0); + assertAlmostEqual(tr.y(), 0); + assertAlmostEqual(tr.width(), 200); + assertAlmostEqual(tr.height(), 100); + }); + + it('toJSON should not save attached node and children', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + x: 100, + y: 60, + draggable: true, + width: 100, + height: 100, + fill: 'yellow', + }); + layer.add(rect); + + var tr = new Konva.Transformer(); + layer.add(tr); + tr.nodes([rect]); + + layer.draw(); + + var json = tr.toJSON(); + var object = JSON.parse(json); + + assert.equal(object.attrs.node, undefined); + assert.equal(object.children, undefined); + }); + + it('make sure we can work without inner node', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var tr = new Konva.Transformer(); + layer.add(tr); + layer.draw(); + + // just check not throw + assert.equal(1, 1); + }); + + it('reset attrs on node set', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + x: 100, + y: 60, + draggable: true, + width: 100, + height: 100, + fill: 'yellow', + }); + layer.add(rect); + + var tr = new Konva.Transformer(); + layer.add(tr); + layer.draw(); + + assert.equal(tr.getWidth(), 0); + + tr.nodes([rect]); + assert.equal(tr.getWidth(), 100); + }); + + it('can destroy without attached node', function () { + var tr = new Konva.Transformer(); + tr.destroy(); + // just check not throw + assert.equal(1, 1); + }); + + it('can destroy with attached node while resize', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + x: 100, + y: 60, + draggable: true, + width: 100, + height: 100, + fill: 'yellow', + }); + layer.add(rect); + + var tr = new Konva.Transformer({ + nodes: [rect], + }); + layer.add(tr); + + layer.draw(); + + simulateMouseDown(tr, { + x: 100, + y: 60, + }); + + assert.equal(tr.isTransforming(), true); + + tr.destroy(); + + assert.equal(tr.isTransforming(), false); + + assert.equal(tr.getNode(), undefined); + + su(stage, { + x: 100, + y: 60, + }); + }); + + it('can add padding', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + x: 30, + y: 30, + draggable: true, + width: 100, + height: 100, + fill: 'yellow', + }); + layer.add(rect); + + var tr = new Konva.Transformer({ + nodes: [rect], + padding: 20, + }); + layer.add(tr); + layer.draw(); + + simulateMouseDown(tr, { + x: 10, + y: 80, + }); + + simulateMouseMove(tr, { + x: 60, + y: 80, + }); + + simulateMouseUp(tr, { + x: 200, + y: 150, + }); + + assertAlmostEqual(rect.x(), 80); + assertAlmostEqual(rect.y(), 30); + assertAlmostEqual(rect.width(), 100); + assertAlmostEqual(rect.scaleX(), 0.5); + assertAlmostEqual(rect.height(), 100); + assertAlmostEqual(rect.scaleY(), 1); + }); + + it('keep ratio should allow negative scaling', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + x: 150, + y: 10, + draggable: true, + width: 50, + height: 50, + fill: 'yellow', + }); + layer.add(rect); + + var tr = new Konva.Transformer({ + nodes: [rect], + }); + layer.add(tr); + layer.draw(); + + var anchor = tr.findOne('.top-right'); + var pos = anchor.getAbsolutePosition(); + + simulateMouseDown(tr, { + x: pos.x, + y: pos.y, + }); + simulateMouseMove(tr, { + x: pos.x - 100, + y: pos.y + 100, + }); + simulateMouseUp(tr, { + x: pos.x - 100, + y: pos.y + 100, + }); + + assertAlmostEqual(rect.scaleX(), 1); + assertAlmostEqual(rect.scaleY(), 1); + assertAlmostEqual(rect.rotation(), -180); + }); + + it('slightly move for cache check (top-left anchor)', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + x: 20, + y: 20, + draggable: true, + width: 100, + height: 100, + fill: 'yellow', + }); + layer.add(rect); + + var tr = new Konva.Transformer({ + nodes: [rect], + }); + layer.add(tr); + + layer.draw(); + + var anchor = tr.findOne('.top-left'); + assertAlmostEqual(anchor.getAbsolutePosition().x, 20); + + simulateMouseDown(tr, { + x: 20, + y: 20, + }); + + simulateMouseMove(tr, { + x: 20, + y: 20, + }); + + simulateMouseUp(tr); + layer.draw(); + + assertAlmostEqual(rect.x(), 20); + assertAlmostEqual(rect.y(), 20); + }); + + it('rotation snaps', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + x: 50, + y: 50, + width: 100, + height: 100, + fill: 'yellow', + }); + layer.add(rect); + + var tr = new Konva.Transformer({ + nodes: [rect], + rotationSnaps: [0, 90, 180, 270], + rotationSnapTolerance: 45, + }); + layer.add(tr); + + layer.draw(); + + simulateMouseDown(tr, { + x: 100, + y: 0, + }); + + // move to almost 45 deg + simulateMouseMove(tr, { + x: 199, + y: 0, + }); + assert.equal(rect.rotation(), 0); + + // move to more than 45 deg + simulateMouseMove(tr, { + x: 200, + y: 2, + }); + assert.equal(rect.rotation(), 90); + + simulateMouseMove(tr, { + x: 200, + y: 199, + }); + assert.equal(rect.rotation(), 90); + + simulateMouseMove(tr, { + x: 199, + y: 200, + }); + assert.equal(rect.rotation(), 180); + + simulateMouseMove(tr, { + x: 1, + y: 200, + }); + assert.equal(rect.rotation(), 180); + + simulateMouseMove(tr, { + x: 0, + y: 199, + }); + assertAlmostEqual(rect.rotation(), -90); + + simulateMouseMove(tr, { + x: 0, + y: 50, + }); + assertAlmostEqual(rect.rotation(), -90); + simulateMouseMove(tr, { + x: 0, + y: 45, + }); + assertAlmostEqual(rect.rotation(), -90); + + simulateMouseMove(tr, { + x: 0, + y: 1, + }); + assertAlmostEqual(rect.rotation(), -90); + + simulateMouseMove(tr, { + x: 1, + y: 0, + }); + assert.equal(rect.rotation(), 0); + + simulateMouseUp(tr); + }); + + it('switch scaling with padding - x', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + x: 10, + y: 10, + draggable: true, + width: 100, + height: 100, + fill: 'yellow', + }); + layer.add(rect); + + var tr = new Konva.Transformer({ + nodes: [rect], + padding: 10, + }); + layer.add(tr); + + layer.draw(); + + simulateMouseDown(tr, { + x: 0, + y: 60, + }); + + simulateMouseMove(tr, { + x: 125, + y: 60, + }); + + assertAlmostEqual(rect.x(), 115); + assertAlmostEqual(rect.y(), 10); + assertAlmostEqual(rect.width(), 100); + assertAlmostEqual(rect.scaleX(), 0.05); + assertAlmostEqual(rect.scaleY(), -1); + assertAlmostEqual(rect.height(), 100); + assertAlmostEqual(rect.rotation(), -180); + + simulateMouseMove(tr, { + x: 125, + y: 60, + }); + + assertAlmostEqual(rect.x(), 115); + assertAlmostEqual(rect.y(), 10); + assertAlmostEqual(rect.width(), 100); + assertAlmostEqual(rect.scaleX(), 0.05); + assertAlmostEqual(rect.height(), 100); + assertAlmostEqual(rect.scaleY(), -1); + + // switch again + simulateMouseMove(tr, { + x: 90, + y: 60, + }); + + assertAlmostEqual(rect.x(), 100); + assertAlmostEqual(rect.y(), 10); + assertAlmostEqual(rect.width(), 100); + assertAlmostEqual(rect.scaleY(), 1); + assertAlmostEqual(rect.scaleX(), 0.1); + assertAlmostEqual(rect.height(), 100); + + simulateMouseUp(tr); + }); + + it('switch scaling with padding - y', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + x: 10, + y: 10, + draggable: true, + width: 100, + height: 100, + fill: 'yellow', + }); + layer.add(rect); + + var tr = new Konva.Transformer({ + nodes: [rect], + padding: 10, + }); + layer.add(tr); + + layer.draw(); + + simulateMouseDown(tr, { + x: 60, + y: 0, + }); + + simulateMouseMove(tr, { + x: 60, + y: 125, + }); + + assertAlmostEqual(rect.x(), 10); + assertAlmostEqual(rect.y(), 115); + assertAlmostEqual(rect.width(), 100); + assertAlmostEqual(rect.scaleY(), -0.05); + assertAlmostEqual(rect.height(), 100); + assertAlmostEqual(rect.rotation(), 0); + + simulateMouseMove(tr, { + x: 60, + y: 125, + }); + + assertAlmostEqual(rect.x(), 10); + assertAlmostEqual(rect.y(), 115); + assertAlmostEqual(rect.width(), 100); + assertAlmostEqual(rect.scaleY(), -0.05); + assertAlmostEqual(rect.height(), 100); + assertAlmostEqual(rect.rotation(), 0); + + // switch again + simulateMouseMove(tr, { + x: 60, + y: 90, + }); + + assertAlmostEqual(rect.x(), 10); + assertAlmostEqual(rect.y(), 100); + assertAlmostEqual(rect.width(), 100); + assertAlmostEqual(rect.scaleX(), 1); + assertAlmostEqual(rect.scaleY(), 0.1); + assertAlmostEqual(rect.height(), 100); + assertAlmostEqual(rect.rotation(), 0); + + simulateMouseUp(tr); + }); + + it('switch horizontal scaling with (top-left anchor)', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + x: 0, + y: 0, + draggable: true, + width: 100, + height: 100, + fill: 'yellow', + }); + layer.add(rect); + + var tr = new Konva.Transformer({ + nodes: [rect], + }); + layer.add(tr); + + layer.draw(); + + simulateMouseDown(tr, { + x: 0, + y: 0, + }); + + simulateMouseMove(tr, { + x: 150, + y: 50, + }); + layer.draw(); + + assertAlmostEqual(rect.x(), 150); + assertAlmostEqual(rect.y(), 50); + assertAlmostEqual(rect.width(), 100); + assertAlmostEqual(rect.scaleX(), 0.5); + assertAlmostEqual(rect.scaleY(), -0.5); + assertAlmostEqual(rect.height(), 100); + assertAlmostEqual(rect.rotation(), -180); + + simulateMouseMove(tr, { + x: 98, + y: 2.859375, + }); + simulateMouseMove(tr, { + x: 98, + y: 2.859375, + }); + simulateMouseMove(tr, { + x: 98, + y: 2.859375, + }); + simulateMouseMove(tr, { + x: 100, + y: 2.859375, + }); + layer.draw(); + simulateMouseMove(tr, { + x: 101, + y: 2.859375, + }); + layer.draw(); + simulateMouseMove(tr, { + x: 101, + y: 2.859375, + }); + layer.draw(); + simulateMouseMove(tr, { + x: 101, + y: 2.859375, + }); + layer.draw(); + simulateMouseMove(tr, { + x: 102, + y: 2.859375, + }); + layer.draw(); + // switch again + simulateMouseMove(tr, { + x: 0, + y: 0, + }); + + assertAlmostEqual(rect.x(), 0); + assertAlmostEqual(rect.y(), 0); + assertAlmostEqual(rect.width(), 100); + assertAlmostEqual(rect.scaleY(), 1); + assertAlmostEqual(rect.scaleX(), 1); + assertAlmostEqual(rect.height(), 100); + + simulateMouseUp(tr); + }); + + it('switch vertical scaling with (top-left anchor)', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + x: 0, + y: 0, + draggable: true, + width: 100, + height: 100, + fill: 'yellow', + }); + layer.add(rect); + + var tr = new Konva.Transformer({ + nodes: [rect], + }); + layer.add(tr); + + layer.draw(); + + simulateMouseDown(tr, { + x: 0, + y: 0, + }); + + simulateMouseMove(tr, { + x: 0, + y: 200, + }); + layer.draw(); + + assertAlmostEqual(rect.x(), 0); + assertAlmostEqual(rect.y(), 200); + assertAlmostEqual(rect.width(), 100); + assertAlmostEqual(rect.scaleX(), 1); + assertAlmostEqual(rect.height(), 100); + assertAlmostEqual(rect.rotation(), 0); + + simulateMouseMove(tr, { + x: 0, + y: 0, + }); + layer.draw(); + simulateMouseUp(tr); + + assertAlmostEqual(rect.x(), 0); + assertAlmostEqual(rect.y(), 0); + assertAlmostEqual(rect.width(), 100); + assertAlmostEqual(rect.scaleX(), 1); + assertAlmostEqual(rect.height(), 100); + assertAlmostEqual(rect.rotation(), 0); + assertAlmostEqual(rect.scaleY(), 1); + }); + + it('switch scaling with padding for rotated - x', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + x: 110, + y: 10, + draggable: true, + width: 100, + height: 100, + fill: 'yellow', + rotation: 90, + }); + layer.add(rect); + + var tr = new Konva.Transformer({ + nodes: [rect], + padding: 10, + }); + layer.add(tr); + + layer.draw(); + + simulateMouseDown(tr, { + x: 60, + y: 0, + }); + + simulateMouseMove(tr, { + x: 60, + y: 125, + }); + + assertAlmostEqual(rect.x(), 110); + assertAlmostEqual(rect.y(), 115); + assertAlmostEqual(rect.width(), 100); + assertAlmostEqual(rect.scaleX(), 0.05); + assertAlmostEqual(rect.scaleY(), -1); + assertAlmostEqual(rect.height(), 100); + assertAlmostEqual(rect.rotation(), -90); + + simulateMouseMove(tr, { + x: 60, + y: 125, + }); + + assertAlmostEqual(rect.x(), 110); + assertAlmostEqual(rect.y(), 115); + assertAlmostEqual(rect.width(), 100); + assertAlmostEqual(rect.scaleX(), 0.05); + assertAlmostEqual(rect.height(), 100); + assertAlmostEqual(rect.scaleY(), -1); + + layer.draw(); + + // switch again + simulateMouseMove(tr, { + x: 60, + y: 90, + }); + + assertAlmostEqual(rect.x(), 110); + assertAlmostEqual(rect.y(), 100); + assertAlmostEqual(rect.width(), 100); + assertAlmostEqual(rect.scaleX(), 0.1); + assertAlmostEqual(rect.scaleY(), 1); + + assertAlmostEqual(rect.height(), 100); + + simulateMouseUp(tr); + }); + + it('switch scaling with padding for rotated - y', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + x: 110, + y: 10, + draggable: true, + width: 100, + height: 100, + fill: 'yellow', + rotation: 90, + }); + layer.add(rect); + + var tr = new Konva.Transformer({ + nodes: [rect], + padding: 10, + }); + layer.add(tr); + + layer.draw(); + + simulateMouseDown(tr, { + x: 0, + y: 60, + }); + + simulateMouseMove(tr, { + x: 125, + y: 60, + }); + + assertAlmostEqual(rect.x(), 110); + assertAlmostEqual(rect.y(), 10); + assertAlmostEqual(rect.width(), 100); + assertAlmostEqual(rect.scaleX(), 1); + assertAlmostEqual(rect.scaleY(), -0.05); + assertAlmostEqual(rect.height(), 100); + assertAlmostEqual(rect.rotation(), 90); + + simulateMouseMove(tr, { + x: 125, + y: 60, + }); + + assertAlmostEqual(rect.x(), 110); + assertAlmostEqual(rect.y(), 10); + assertAlmostEqual(rect.width(), 100); + assertAlmostEqual(rect.scaleX(), 1); + assertAlmostEqual(rect.scaleY(), -0.05); + assertAlmostEqual(rect.height(), 100); + assertAlmostEqual(rect.rotation(), 90); + + // switch again + simulateMouseMove(tr, { + x: 90, + y: 60, + }); + + assertAlmostEqual(rect.x(), 110); + assertAlmostEqual(rect.y() - 120 < 0.001, true); + assertAlmostEqual(rect.width(), 100); + assertAlmostEqual(rect.scaleX(), 1); + assertAlmostEqual(rect.scaleY(), 0.1); + assertAlmostEqual(rect.height(), 100); + + simulateMouseUp(tr); + }); + + it('transformer should automatically track attr changes of a node', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + x: 100, + y: 60, + draggable: true, + width: 100, + height: 100, + fill: 'yellow', + }); + layer.add(rect); + + var tr = new Konva.Transformer({ + nodes: [rect], + }); + layer.add(tr); + + layer.draw(); + + assert.equal(tr.x(), 100); + assert.equal(tr.y(), 60); + assert.equal(tr.width(), 100); + assert.equal(rect.height(), 100); + assert.equal(rect.rotation(), 0); + + rect.x(0); + assert.equal(tr.x(), 0); + + rect.y(0); + assert.equal(tr.y(), 0); + + rect.width(50); + assert.equal(tr.width(), 50); + + rect.height(50); + assert.equal(tr.height(), 50); + + rect.scaleX(2); + assert.equal(tr.width(), 100); + + rect.scaleY(2); + assert.equal(tr.height(), 100); + + // manual check position + var back = tr.findOne('.back'); + assert.equal(back.getAbsolutePosition().x, 0); + + layer.batchDraw(); + }); + + it('on detach should remove all listeners', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + x: 100, + y: 60, + draggable: true, + width: 100, + height: 100, + fill: 'yellow', + }); + layer.add(rect); + + var tr = new Konva.Transformer({ + nodes: [rect], + }); + layer.add(tr); + + layer.draw(); + + tr.detach(); + + rect.width(200); + assert.equal(tr.width(), 0); + layer.draw(); + + var called = false; + // clear cache is called on each update + // make sure we don't call it + tr._clearCache = function () { + called = true; + }; + rect.width(50); + assert.equal(called, false, 'don not call clear cache'); + }); + + it('check transformer with drag&drop', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + x: 0, + y: 0, + width: 100, + height: 100, + fill: 'green', + draggable: true, + }); + + layer.add(rect); + + var tr = new Konva.Transformer({ + nodes: [rect], + }); + layer.add(tr); + + layer.draw(); + + simulateMouseDown(tr, { + x: 20, + y: 20, + }); + + sm(stage, { + x: 30, + y: 30, + }); + + su(stage, { + x: 30, + y: 30, + }); + + assert.equal(rect.x(), 10); + assert.equal(rect.y(), 10); + + assert.equal(tr.x(), 10); + assert.equal(tr.y(), 10); + }); + + it('check transformer with drag&drop and scaled shape', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + x: 0, + y: 0, + width: 100, + height: 100, + fill: 'green', + draggable: true, + scaleX: 2, + }); + + layer.add(rect); + + var tr = new Konva.Transformer({ + nodes: [rect], + }); + layer.add(tr); + + layer.draw(); + + simulateMouseDown(tr, { + x: 20, + y: 20, + }); + + sm(stage, { + x: 30, + y: 30, + }); + + layer.draw(); + + assert.equal(rect.x(), 10); + assert.equal(rect.y(), 10); + + assert.equal(tr.x(), 10); + assert.equal(tr.y(), 10); + + assert.equal(tr.width(), 200); + + su(stage, { + x: 30, + y: 30, + }); + }); + + it('try rotate scaled rect', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + x: 50, + y: 150, + draggable: true, + width: 100, + height: 100, + fill: 'yellow', + scaleY: -1, + }); + layer.add(rect); + + var tr = new Konva.Transformer({ + nodes: [rect], + }); + layer.add(tr); + layer.draw(); + + var rotater = tr.findOne('.rotater'); + var pos = rotater.getAbsolutePosition(); + + simulateMouseDown(tr, { + x: pos.x, + y: pos.y, + }); + simulateMouseMove(tr, { + x: pos.x + 100, + y: pos.y + 100, + }); + simulateMouseUp(tr, { + x: pos.x + 100, + y: pos.y + 100, + }); + + assert.equal(rect.rotation(), 90); + }); + + it('check correct cursor on scaled shape', function () { + if (isNode) { + return; + } + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + x: 50, + y: 100, + draggable: true, + width: 100, + height: 100, + fill: 'yellow', + scaleY: -1, + }); + layer.add(rect); + + var tr = new Konva.Transformer({ + nodes: [rect], + }); + layer.add(tr); + layer.draw(); + + sm(stage, { + x: 50, + y: 1, + }); + assert.equal(stage.content.style.cursor, 'nwse-resize'); + }); + + it('check correct cursor off on Transformer destroy', function () { + if (isNode) { + return; + } + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + x: 0, + y: 0, + draggable: true, + width: 100, + height: 100, + fill: 'yellow', + }); + layer.add(rect); + + var tr = new Konva.Transformer({ + nodes: [rect], + }); + layer.add(tr); + layer.draw(); + + sm(stage, { + x: 100, + y: 100, + }); + simulateMouseDown(tr, { + x: 100, + y: 100, + }); + + assert.equal(stage.content.style.cursor, 'nwse-resize'); + + var target = stage.getIntersection({ + x: 100, + y: 100, + }); + simulateMouseMove(tr, { + x: 120, + y: 100, + }); + + // here is duplicate, because transformer is listening window events + simulateMouseUp(tr, { + x: 120, + y: 100, + }); + su(stage, { + x: 120, + y: 100, + }); + + tr.destroy(); + sm(stage, { + x: 140, + y: 100, + }); + assert.equal(stage.content.style.cursor, ''); + }); + + it('check correct cursor on scaled parent', function () { + if (isNode) { + return; + } + var stage = addStage(); + var layer = new Konva.Layer({ + y: 100, + scaleY: -1, + }); + stage.add(layer); + + var rect = new Konva.Rect({ + x: 50, + y: 0, + draggable: true, + width: 100, + height: 100, + fill: 'yellow', + }); + layer.add(rect); + + var tr = new Konva.Transformer({ + nodes: [rect], + }); + layer.add(tr); + layer.draw(); + + sm(stage, { + x: 50, + y: 1, + }); + assert.equal(stage.content.style.cursor, 'nwse-resize'); + }); + + it('check default cursor transformer', function () { + if (isNode) { + return; + } + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + x: 50, + y: 50, + draggable: true, + width: 100, + height: 100, + fill: 'yellow', + }); + layer.add(rect); + + var tr = new Konva.Transformer({ + nodes: [rect], + }); + layer.add(tr); + layer.draw(); + + sm(stage, { + x: 100, + y: 0, + }); + assert.equal(stage.content.style.cursor, 'crosshair'); + }); + + it('using custom cursor on configured transformer should show custom cursor instead of crosshair', function () { + if (isNode) { + return; + } + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + x: 50, + y: 50, + draggable: true, + width: 100, + height: 100, + fill: 'yellow', + }); + layer.add(rect); + + var tr = new Konva.Transformer({ + nodes: [rect], + rotateAnchorCursor: 'grab', + }); + layer.add(tr); + layer.draw(); + + sm(stage, { + x: 100, + y: 0, + }); + assert.equal(stage.content.style.cursor, 'grab'); + }); + + it('changing parent transform should recalculate transformer attrs', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + x: 0, + y: 0, + draggable: true, + width: 100, + height: 100, + fill: 'yellow', + }); + layer.add(rect); + + var tr = new Konva.Transformer({ + nodes: [rect], + }); + layer.add(tr); + layer.draw(); + + layer.scaleX(2); + + layer.draw(); + + assert.equal(tr.width(), 200); + }); + + it('check fit and correct cursor on rotated parent', function () { + if (isNode) { + return; + } + var stage = addStage(); + var layer = new Konva.Layer({ + x: 100, + y: -50, + rotation: 90, + }); + stage.add(layer); + + var rect = new Konva.Rect({ + x: 50, + y: 0, + draggable: true, + width: 100, + height: 100, + fill: 'yellow', + }); + layer.add(rect); + + var tr = new Konva.Transformer({ + nodes: [rect], + }); + layer.add(tr); + layer.draw(); + + var box = { + x: 100, + y: 0, + width: 100, + height: 100, + rotation: Konva.getAngle(90), + }; + tr._fitNodesInto(box); + + assert.equal(Math.round(tr.x()), Math.round(box.x)); + assert.equal(Math.round(tr.y()), Math.round(box.y)); + assert.equal(Math.round(tr.width()), Math.round(box.width)); + assert.equal(Math.round(tr.height()), Math.round(box.height)); + + sm(stage, { + x: 50, + y: 1, + }); + assert.equal(stage.content.style.cursor, 'ns-resize'); + }); + + it('check drag with transformer', function () { + var stage = addStage(); + stage.draggable(true); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + x: 0, + y: 0, + draggable: true, + width: 100, + height: 100, + fill: 'yellow', + }); + layer.add(rect); + + var tr = new Konva.Transformer({ + nodes: [rect], + }); + layer.add(tr); + layer.draw(); + + simulateMouseDown(tr, { + x: 50, + y: 50, + }); + + sm(stage, { + x: 55, + y: 50, + }); + sm(stage, { + x: 60, + y: 50, + }); + + su(stage, { + x: 60, + y: 50, + }); + + assert.equal(rect.x(), 10); + assert.equal(rect.y(), 0); + }); + + it('stopTransform method', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + x: 50, + y: 50, + draggable: true, + width: 100, + height: 100, + fill: 'yellow', + }); + layer.add(rect); + + var tr = new Konva.Transformer({ + nodes: [rect], + }); + layer.add(tr); + layer.draw(); + + simulateMouseDown(tr, { + x: 50, + y: 50, + }); + + simulateMouseMove(tr, { + x: 60, + y: 60, + }); + + assert.equal(tr.isTransforming(), true); + assert.equal(rect.x(), 60); + + var transformend = 0; + rect.on('transformend', function () { + transformend += 1; + }); + + tr.stopTransform(); + + assert.equal(transformend, 1); + + assert.equal(tr.isTransforming(), false); + assert.equal(rect.x(), 60); + + // here is duplicate, because transformer is listening window events + su(stage, { + x: 100, + y: 100, + }); + }); + + it('transform events check', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + x: 50, + y: 50, + draggable: true, + width: 100, + height: 100, + fill: 'yellow', + }); + layer.add(rect); + + var tr = new Konva.Transformer({ + nodes: [rect], + }); + layer.add(tr); + layer.draw(); + + var callCount = 0; + rect.on('transformstart', function (e) { + callCount += 1; + assert.equal(e.target, rect); + assert.equal(tr.getActiveAnchor(), 'top-left'); + assert.equal(typeof e.evt.clientX === 'number', true); + }); + + rect.on('transform', function (e) { + callCount += 1; + assert.equal(e.target, rect); + assert.equal(tr.getActiveAnchor(), 'top-left'); + }); + + rect.on('transformend', function (e) { + callCount += 1; + assert.equal(e.target, rect); + assert.equal(tr.getActiveAnchor(), 'top-left'); + }); + + tr.on('transformstart', function (e) { + callCount += 1; + assert.equal(e.target, rect); + }); + + tr.on('transform', function (e) { + callCount += 1; + assert.equal(e.target, rect); + }); + + tr.on('transformend', function (e) { + callCount += 1; + assert.equal(e.target, rect); + }); + + simulateMouseDown(tr, { + x: 50, + y: 50, + }); + + simulateMouseMove(tr, { + x: 60, + y: 60, + }); + + simulateMouseUp(tr, { + x: 60, + y: 60, + }); + + assert.equal(callCount, 6); + assert.equal(tr.getActiveAnchor(), null); + }); + + it('on force update should clear transform', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var group = new Konva.Group({ + x: 50, + y: 50, + }); + layer.add(group); + + var tr = new Konva.Transformer(); + layer.add(tr); + tr.nodes([group]); + + layer.draw(); + + assert.equal(tr._cache.get('transform').m[4], 50); + + var rect = new Konva.Rect({ + x: 50, + y: 50, + width: 100, + height: 100, + fill: 'yellow', + }); + group.add(rect); + + tr.forceUpdate(); + layer.draw(); + + assert.equal(tr._cache.get('transform').m[4], 100); + + // tr._fitNodesInto({ + // x: 100, + // y: 70, + // width: 100, + // height: 100 + // }); + + // assert.equal(rect.x(), 100); + // assert.equal(rect.y(), 70); + // assert.equal(rect.width() * rect.scaleX(), 100); + // assert.equal(rect.height() * rect.scaleY(), 100); + // assert.equal(rect.rotation(), rect.rotation()); + }); + + it('test cache reset on attach', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + x: 20, + y: 20, + draggable: true, + width: 150, + height: 100, + fill: 'yellow', + }); + layer.add(rect); + + var tr = new Konva.Transformer(); + layer.add(tr); + + // make draw to set all caches + layer.draw(); + // then attach + tr.nodes([rect]); + + layer.draw(); + + var shape = layer.getIntersection({ + x: 20, + y: 20, + }); + assert.equal(shape.name(), 'top-left _anchor'); + }); + + it('check rotator size on scaled transformer', function () { + var stage = addStage(); + var layer = new Konva.Layer({ + scaleX: 10, + scaleY: 10, + }); + stage.add(layer); + + var rect = new Konva.Rect({ + x: 5, + y: 16, + draggable: true, + width: 10, + height: 10, + fill: 'yellow', + }); + layer.add(rect); + + var tr = new Konva.Transformer({ + nodes: [rect], + }); + layer.add(tr); + layer.draw(); + + var rotater = tr.findOne('.rotater'); + var pos = rotater.getAbsolutePosition(); + + // pos.x === (x * scaleX - (height)) + assert.equal(pos.x, 100); + + // pos.y === (y * scaleY - (height * scaleY / 2)) + assert.equal(pos.y, 110); + }); + + var tests = [ + { + name: 'top-left', + startPos: { + x: 0, + y: 0, + }, + endPos: { + x: 25, + y: 25, + }, + expectedWidth: 50, + expectedHeight: 50, + }, + { + name: 'top-center', + startPos: { + x: 50, + y: 0, + }, + endPos: { + x: 50, + y: 25, + }, + expectedWidth: 100, + expectedHeight: 50, + }, + { + name: 'top-right', + startPos: { + x: 100, + y: 0, + }, + endPos: { + x: 75, + y: 25, + }, + expectedWidth: 50, + expectedHeight: 50, + }, + { + name: 'middle-left', + startPos: { + x: 0, + y: 50, + }, + endPos: { + x: 25, + y: 50, + }, + expectedWidth: 50, + expectedHeight: 100, + }, + { + name: 'middle-right', + startPos: { + x: 100, + y: 50, + }, + endPos: { + x: 75, + y: 50, + }, + expectedWidth: 50, + expectedHeight: 100, + }, + { + name: 'bottom-left', + startPos: { + x: 0, + y: 100, + }, + endPos: { + x: 25, + y: 75, + }, + expectedWidth: 50, + expectedHeight: 50, + }, + { + name: 'bottom-center', + startPos: { + x: 50, + y: 100, + }, + endPos: { + x: 50, + y: 75, + }, + expectedWidth: 100, + expectedHeight: 50, + }, + { + name: 'bottom-right', + startPos: { + x: 100, + y: 100, + }, + endPos: { + x: 75, + y: 75, + }, + expectedWidth: 50, + expectedHeight: 50, + }, + // { + // name: 'top-left-reverse', + // startPos: { + // x: 0, + // y: 0 + // }, + // endPos: { + // x: 100, + // y: 100 + // }, + // expectedWidth: 100, + // expectedHeight: 100 + // } + ]; + + it('if alt is pressed should transform around center', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + draggable: true, + fill: 'yellow', + }); + layer.add(rect); + + var tr = new Konva.Transformer({ + nodes: [rect], + }); + layer.add(tr); + + tests.forEach(function (test) { + rect.setAttrs({ + x: 0, + y: 0, + width: 100, + height: 100, + scaleX: 1, + scaleY: 1, + }); + tr.update(); + + layer.draw(); + + simulateMouseDown(tr, test.startPos); + + var target = stage.getIntersection(test.startPos); + simulateMouseMove(tr, { + target: target, + x: test.endPos.x, + y: test.endPos.y, + altKey: true, + }); + + // here is duplicate, because transformer is listening window events + simulateMouseUp(tr, { + x: test.endPos.x, + y: test.endPos.y, + }); + su(stage, { + x: test.endPos.x, + y: test.endPos.y, + }); + layer.draw(); + + assertAlmostEqual(rect.width() * rect.scaleX(), test.expectedWidth); + assertAlmostEqual(rect.height() * rect.scaleY(), test.expectedHeight); + }); + }); + + it('centered scaling - no keep ratio', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + draggable: true, + fill: 'yellow', + }); + layer.add(rect); + + var tr = new Konva.Transformer({ + nodes: [rect], + centeredScaling: true, + keepRatio: false, + }); + layer.add(tr); + + tests.forEach(function (test) { + rect.setAttrs({ + x: 0, + y: 0, + width: 100, + height: 100, + scaleX: 1, + scaleY: 1, + }); + tr.update(); + + layer.draw(); + + simulateMouseDown(tr, test.startPos); + + var target = stage.getIntersection(test.startPos); + simulateMouseMove(tr, { + target: target, + x: test.endPos.x, + y: test.endPos.y, + }); + + // here is duplicate, because transformer is listening window events + simulateMouseUp(tr, { + x: test.endPos.x, + y: test.endPos.y, + }); + su(stage, { + x: test.endPos.x, + y: test.endPos.y, + }); + layer.draw(); + + assertAlmostEqual(rect.width() * rect.scaleX(), test.expectedWidth); + assertAlmostEqual(rect.height() * rect.scaleY(), test.expectedHeight); + }); + }); + + it('centered scaling', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + draggable: true, + fill: 'yellow', + }); + layer.add(rect); + + var tr = new Konva.Transformer({ + nodes: [rect], + centeredScaling: true, + }); + layer.add(tr); + + tests.forEach(function (test) { + rect.setAttrs({ + x: 0, + y: 0, + width: 100, + height: 100, + scaleX: 1, + scaleY: 1, + }); + tr.update(); + + layer.draw(); + + simulateMouseDown(tr, test.startPos); + + var target = stage.getIntersection(test.startPos); + simulateMouseMove(tr, { + target: target, + x: test.endPos.x, + y: test.endPos.y, + }); + + // here is duplicate, because transformer is listening window events + simulateMouseUp(tr, { + x: test.endPos.x, + y: test.endPos.y, + }); + su(stage, { + x: test.endPos.x, + y: test.endPos.y, + }); + layer.draw(); + + assertAlmostEqual(rect.width() * rect.scaleX(), test.expectedWidth); + assertAlmostEqual(rect.height() * rect.scaleY(), test.expectedHeight); + }); + }); + + it('centered scaling on flip + keep ratio', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + draggable: true, + fill: 'yellow', + }); + layer.add(rect); + + var tr = new Konva.Transformer({ + nodes: [rect], + centeredScaling: true, + keepRatio: true, + }); + layer.add(tr); + + // try to move mouse from edge corners into different directions + var tl = { x: 0, y: 0 }; + var trr = { x: 200, y: 0 }; + var bl = { x: 0, y: 100 }; + var br = { x: 200, y: 100 }; + + var tests = [ + [tl, trr], + [tl, bl], + [tl, br], + [trr, tl], + [trr, bl], + [trr, br], + + [bl, tl], + [bl, trr], + [bl, br], + + [br, tl], + [br, trr], + [br, bl], + ]; + tests.forEach((test) => { + var start = test[0]; + var end = test[1]; + rect.setAttrs({ + x: 0, + y: 0, + width: 200, + height: 100, + scaleX: 1, + scaleY: 1, + rotation: 0, + }); + layer.draw(); + + // move from start to end + simulateMouseDown(tr, start); + simulateMouseMove(tr, end); + var box = rect.getClientRect(); + assertAlmostEqual(box.x, 0); + assertAlmostEqual(box.y, 0); + assertAlmostEqual(box.width, 200); + assertAlmostEqual(box.height, 100); + + // make extra move on end + simulateMouseMove(tr, end); + var box = rect.getClientRect(); + assertAlmostEqual(box.x, 0); + assertAlmostEqual(box.y, 0); + assertAlmostEqual(box.width, 200); + assertAlmostEqual(box.height, 100); + + // move back + simulateMouseMove(tr, start); + simulateMouseUp(tr); + assertAlmostEqual(box.x, 0); + assertAlmostEqual(box.y, 0); + assertAlmostEqual(box.width, 200); + assertAlmostEqual(box.height, 100); + }); + }); + + it('transform scaled (in one direction) node', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + draggable: true, + x: 150, + y: 50, + width: 100, + height: 100, + scaleX: -1, + fillLinearGradientStartPoint: { x: 0, y: 0 }, + fillLinearGradientEndPoint: { x: 100, y: 100 }, + fillLinearGradientColorStops: [0, 'red', 0.8, 'yellow'], + }); + layer.add(rect); + + var tr = new Konva.Transformer({ + nodes: [rect], + }); + layer.add(tr); + + layer.draw(); + + simulateMouseDown(tr, { + x: 150, + y: 150, + }); + + var target = stage.getIntersection({ + x: 150, + y: 150, + }); + simulateMouseMove(tr, { + x: 100, + y: 100, + }); + + // here is duplicate, because transformer is listening window events + simulateMouseUp(tr, { + x: 100, + y: 100, + }); + su(stage, { + x: 100, + y: 100, + }); + layer.draw(); + + assert.equal(rect.width() * rect.scaleX() - 50 < 1, true, ' width check'); + assert.equal(rect.height() * rect.scaleY() + 50 < 1, true, ' height check'); + }); + + it('transformer should ignore shadow', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + x: 50, + y: 50, + draggable: true, + width: 100, + height: 100, + fill: 'yellow', + shadowBlur: 10, + }); + layer.add(rect); + + var tr = new Konva.Transformer({ + nodes: [rect], + }); + layer.add(tr); + layer.draw(); + + assert.equal(tr.x(), 50); + assert.equal(tr.y(), 50); + + assert.equal(tr.width(), 100); + assert.equal(tr.height(), 100); + + tr._fitNodesInto({ + x: 50, + y: 50, + width: 100, + height: 100, + rotation: 0, + }); + + assert.equal(rect.x(), 50); + assert.equal(rect.y(), 50); + + assert.equal(rect.width(), 100); + assert.equal(rect.height(), 100); + }); + + it.skip('transformer should skip scale on stroke if strokeScaleEnabled = false', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + x: 50, + y: 50, + draggable: true, + width: 10, + height: 10, + scaleX: 10, + scaleY: 10, + fill: 'yellow', + strokeWidth: 10, + stroke: 'red', + strokeScaleEnabled: false, + }); + layer.add(rect); + + var tr = new Konva.Transformer({ + nodes: [rect], + ignoreStroke: true, + }); + layer.add(tr); + layer.draw(); + + assert.equal(tr.x(), 50); + assert.equal(tr.y(), 50); + + assert.equal(tr.width(), 100); + assert.equal(tr.height(), 100); + + tr._fitNodesInto({ + x: 50, + y: 50, + width: 100, + height: 100, + rotation: 0, + }); + + assert.equal(rect.x(), 50); + assert.equal(rect.y(), 50); + + assert.equal(rect.width(), 100); + assert.equal(rect.height(), 100); + }); + + it.skip('check calculations when the size = 0', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + x: 50, + y: 50, + draggable: true, + // can we fit from empty width? + width: 0, + height: 100, + fill: 'yellow', + }); + layer.add(rect); + + var tr = new Konva.Transformer({ + nodes: [rect], + }); + layer.add(tr); + layer.draw(); + + tr._fitNodesInto({ + x: 50, + y: 50, + width: 100, + height: 100, + rotation: 0, + }); + layer.draw(); + assert.equal(rect.scaleX(), 1); + }); + + it('attrs change - arc', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var shape = new Konva.Arc({ + x: stage.width() / 2, + y: stage.height() / 2, + innerRadius: 40, + outerRadius: 70, + angle: 60, + fill: 'yellow', + stroke: 'black', + strokeWidth: 4, + }); + layer.add(shape); + + var tr = new Konva.Transformer({ + nodes: [shape], + }); + layer.add(tr); + + layer.draw(); + + shape.outerRadius(100); + + layer.draw(); + var rect = Konva.Util._assign({}, tr._getNodeRect()); + delete rect.rotation; + assert.deepEqual(shape.getClientRect(), rect); + layer.draw(); + }); + + it('attrs change - line', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var shape = new Konva.Arrow({ + x: stage.width() / 4, + y: stage.height() / 4, + points: [0, 0, stage.width() / 2, stage.height() / 2], + pointerLength: 20, + pointerWidth: 20, + fill: 'black', + stroke: 'black', + strokeWidth: 4, + draggable: true, + }); + layer.add(shape); + + var tr = new Konva.Transformer({ + nodes: [shape], + }); + layer.add(tr); + + layer.draw(); + + shape.points([10, 10, 100, 10]); + var rect = Konva.Util._assign({}, tr._getNodeRect()); + delete rect.rotation; + layer.draw(); + assert.deepEqual(shape.getClientRect(), rect); + + shape.strokeWidth(10); + var rect = Konva.Util._assign({}, tr._getNodeRect()); + delete rect.rotation; + layer.draw(); + assert.deepEqual(shape.getClientRect(), rect); + }); + + it('attrs change - circle', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var shape = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 40, + fill: 'yellow', + stroke: 'black', + strokeWidth: 4, + }); + layer.add(shape); + + var tr = new Konva.Transformer({ + nodes: [shape], + }); + layer.add(tr); + + shape.radius(100); + layer.draw(); + var rect = Konva.Util._assign({}, tr._getNodeRect()); + delete rect.rotation; + assert.deepEqual(shape.getClientRect(), rect); + }); + + it('attrs change - ellipse', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var shape = new Konva.Ellipse({ + x: stage.width() / 2, + y: stage.height() / 2, + radiusX: 100, + radiusY: 50, + fill: 'yellow', + stroke: 'black', + strokeWidth: 4, + }); + layer.add(shape); + + var tr = new Konva.Transformer({ + nodes: [shape], + }); + layer.add(tr); + + shape.radiusX(120); + + layer.draw(); + var rect = Konva.Util._assign({}, tr._getNodeRect()); + delete rect.rotation; + assert.deepEqual(shape.getClientRect(), rect); + + shape.radiusY(100); + var rect = Konva.Util._assign({}, tr._getNodeRect()); + delete rect.rotation; + assert.deepEqual(shape.getClientRect(), rect); + + layer.draw(); + }); + + it('attrs change - rect', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var shape = new Konva.Rect({ + x: stage.width() / 2, + y: stage.height() / 2, + width: 100, + height: 100, + fill: 'yellow', + stroke: 'black', + strokeWidth: 4, + }); + layer.add(shape); + + var tr = new Konva.Transformer({ + nodes: [shape], + }); + layer.add(tr); + + shape.width(120); + + layer.draw(); + var rect = Konva.Util._assign({}, tr._getNodeRect()); + delete rect.rotation; + assert.deepEqual(shape.getClientRect(), rect); + + shape.height(110); + var rect = Konva.Util._assign({}, tr._getNodeRect()); + delete rect.rotation; + assert.deepEqual(shape.getClientRect(), rect); + + layer.draw(); + }); + + it('attrs change - path', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var shape = new Konva.Path({ + x: 50, + y: 40, + data: 'M12.582,9.551C3.251,16.237,0.921,29.021,7.08,38.564l-2.36,1.689l4.893,2.262l4.893,2.262l-0.568-5.36l-0.567-5.359l-2.365,1.694c-4.657-7.375-2.83-17.185,4.352-22.33c7.451-5.338,17.817-3.625,23.156,3.824c5.337,7.449,3.625,17.813-3.821,23.152l2.857,3.988c9.617-6.893,11.827-20.277,4.935-29.896C35.591,4.87,22.204,2.658,12.582,9.551z', + fill: 'green', + }); + layer.add(shape); + + var tr = new Konva.Transformer({ + nodes: [shape], + }); + layer.add(tr); + + shape.data('M200,100h100v50z'); + layer.draw(); + + var rect = Konva.Util._assign({}, tr._getNodeRect()); + delete rect.rotation; + assert.deepEqual(shape.getClientRect(), rect); + }); + + it('attrs change - regular polygon', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var shape = new Konva.RegularPolygon({ + x: 100, + y: 150, + sides: 6, + radius: 70, + fill: 'red', + stroke: 'black', + strokeWidth: 4, + }); + layer.add(shape); + + var tr = new Konva.Transformer({ + nodes: [shape], + }); + layer.add(tr); + + shape.radius(100); + layer.draw(); + + var rect = Konva.Util._assign({}, tr._getNodeRect()); + delete rect.rotation; + assert.deepEqual(shape.getClientRect(), rect); + }); + + it('attrs change - ring', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var shape = new Konva.Ring({ + x: stage.width() / 2, + y: stage.height() / 2, + innerRadius: 40, + outerRadius: 70, + fill: 'yellow', + stroke: 'black', + strokeWidth: 4, + }); + layer.add(shape); + + var tr = new Konva.Transformer({ + nodes: [shape], + }); + layer.add(tr); + + shape.outerRadius(100); + + layer.draw(); + var rect = Konva.Util._assign({}, tr._getNodeRect()); + delete rect.rotation; + assert.deepEqual(shape.getClientRect(), rect); + + shape.innerRadius(200); + var rect = Konva.Util._assign({}, tr._getNodeRect()); + delete rect.rotation; + assert.deepEqual(shape.getClientRect(), rect); + + layer.draw(); + }); + + it('attrs change - star', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var shape = new Konva.Star({ + x: stage.width() / 2, + y: stage.height() / 2, + numPoints: 6, + innerRadius: 40, + outerRadius: 70, + fill: 'yellow', + stroke: 'black', + strokeWidth: 4, + }); + layer.add(shape); + + var tr = new Konva.Transformer({ + nodes: [shape], + }); + layer.add(tr); + + shape.outerRadius(100); + + layer.draw(); + var rect = Konva.Util._assign({}, tr._getNodeRect()); + delete rect.rotation; + assert.deepEqual(shape.getClientRect(), rect); + + shape.innerRadius(200); + var rect = Konva.Util._assign({}, tr._getNodeRect()); + delete rect.rotation; + assert.deepEqual(shape.getClientRect(), rect); + + layer.draw(); + }); + + it('attrs change - wedge', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var shape = new Konva.Wedge({ + x: stage.width() / 2, + y: stage.height() / 2, + radius: 70, + angle: 60, + fill: 'red', + stroke: 'black', + strokeWidth: 4, + }); + layer.add(shape); + + var tr = new Konva.Transformer({ + nodes: [shape], + }); + layer.add(tr); + + shape.radius(100); + layer.draw(); + var rect = Konva.Util._assign({}, tr._getNodeRect()); + delete rect.rotation; + assert.deepEqual(shape.getClientRect(), rect); + }); + + it('attrs change - text', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var shape = new Konva.Text({ + x: stage.width() / 2, + y: 15, + text: 'Simple Text', + fontSize: 60, + fontFamily: 'Arial', + fill: 'green', + }); + layer.add(shape); + + var tr = new Konva.Transformer({ + nodes: [shape], + }); + layer.add(tr); + + shape.text('Simple'); + layer.draw(); + var rect = Konva.Util._assign({}, tr._getNodeRect()); + delete rect.rotation; + assert.deepEqual(shape.getClientRect(), rect, 'change text'); + + shape.fontSize(30); + layer.draw(); + var rect = Konva.Util._assign({}, tr._getNodeRect()); + delete rect.rotation; + assert.deepEqual(shape.getClientRect(), rect, 'change font size'); + + shape.padding(10); + layer.draw(); + var rect = Konva.Util._assign({}, tr._getNodeRect()); + delete rect.rotation; + assert.deepEqual(shape.getClientRect(), rect, 'change padding'); + + shape.lineHeight(2); + layer.draw(); + var rect = Konva.Util._assign({}, tr._getNodeRect()); + delete rect.rotation; + assert.deepEqual(shape.getClientRect(), rect, 'change line height'); + + shape.width(30); + layer.draw(); + var rect = Konva.Util._assign({}, tr._getNodeRect()); + delete rect.rotation; + assert.deepEqual(shape.getClientRect(), rect), 'change width'; + + shape.height(30); + layer.draw(); + var rect = Konva.Util._assign({}, tr._getNodeRect()); + delete rect.rotation; + assert.deepEqual(shape.getClientRect(), rect, 'change height'); + }); + + it('attrs change - text path', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var shape = new Konva.TextPath({ + x: 0, + y: 50, + fill: '#333', + fontSize: 16, + fontFamily: 'Arial', + text: "All the world's a stage, and all the men and women merely players.", + data: 'M10,10 C0,0 10,150 100,100 S300,150 400,50', + }); + layer.add(shape); + + var tr = new Konva.Transformer({ + nodes: [shape], + }); + layer.add(tr); + + shape.text('Simple'); + layer.draw(); + var rect = Konva.Util._assign({}, tr._getNodeRect()); + delete rect.rotation; + assert.deepEqual(shape.getClientRect(), rect, 'change text'); + + shape.fontSize(30); + layer.draw(); + var rect = Konva.Util._assign({}, tr._getNodeRect()); + delete rect.rotation; + assert.deepEqual(shape.getClientRect(), rect, 'change font size'); + + shape.data('M10,10 C0,0 10,150 100,100 S300,150 400,50'); + layer.draw(); + var rect = Konva.Util._assign({}, tr._getNodeRect()); + delete rect.rotation; + assert.deepEqual(shape.getClientRect(), rect), 'change data'; + }); + + it('make sure transformer events are not cloned', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect1 = new Konva.Rect({ + x: stage.width() / 5, + y: stage.height() / 5, + width: 50, + height: 50, + fill: 'green', + draggable: true, + }); + + layer.add(rect1); + + var tr1 = new Konva.Transformer({ + nodes: [rect1], + }); + layer.add(tr1); + + var rect2 = rect1.clone({ + fill: 'red', + x: stage.width() / 3, + y: stage.height() / 3, + }); + layer.add(rect2); + + tr1.destroy(); + + var tr2 = new Konva.Transformer({ + nodes: [rect2], + }); + layer.add(tr2); + + // should not throw error + rect2.width(100); + + assertAlmostEqual(tr2.width(), 100); + + stage.draw(); + }); + + it('try to move anchor on scaled with css stage', function () { + if (isNode) { + return; + } + var stage = addStage(); + stage.container().style.transform = 'scale(0.5)'; + stage.container().style.transformOrigin = 'top left'; + + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + x: 0, + y: 0, + draggable: true, + width: 100, + height: 100, + fill: 'yellow', + }); + layer.add(rect); + + var tr = new Konva.Transformer({ + nodes: [rect], + keepRatio: false, + }); + layer.add(tr); + layer.draw(); + + sm(stage, { + x: 50, + y: 50, + }); + simulateMouseDown(tr, { + x: 50, + y: 50, + }); + + var target = stage.getIntersection({ + x: 50, + y: 50, + }); + simulateMouseMove(tr, { + x: 100, + y: 50, + }); + + // here is duplicate, because transformer is listening window events + simulateMouseUp(tr, { + x: 100, + y: 50, + }); + su(stage, { + x: 100, + y: 50, + }); + + assertAlmostEqual(rect.width() * rect.scaleX(), 200); + }); + + it('rotate several nodes', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect1 = new Konva.Rect({ + x: 50, + y: 50, + draggable: true, + width: 50, + height: 50, + fill: 'yellow', + }); + layer.add(rect1); + + var rect2 = new Konva.Rect({ + x: 100, + y: 100, + draggable: true, + width: 50, + height: 50, + fill: 'red', + }); + + layer.add(rect2); + + var tr = new Konva.Transformer({ + nodes: [rect1, rect2], + }); + layer.add(tr); + layer.draw(); + + tr._fitNodesInto({ + x: 100, + y: 0, + width: 100, + height: 100, + rotation: Konva.getAngle(90), + }); + + layer.draw(); + + assertAlmostEqual(rect1.x(), 100); + assertAlmostEqual(rect1.y(), 0); + assertAlmostEqual(rect1.width() + rect2.width(), 100); + assertAlmostEqual(rect1.height() + rect2.width(), 100); + assertAlmostEqual(rect1.rotation(), 90); + + assertAlmostEqual(rect2.x(), 50); + assertAlmostEqual(rect2.y(), 50); + assertAlmostEqual(rect2.width() + rect2.width(), 100); + assertAlmostEqual(rect2.height() + rect2.width(), 100); + assertAlmostEqual(tr.rotation(), 90); + + tr._fitNodesInto({ + x: 100, + y: 100, + width: 100, + height: 100, + rotation: Konva.getAngle(180), + }); + + assertAlmostEqual(tr.x(), rect1.x()); + assertAlmostEqual(tr.y(), rect1.y()); + assertAlmostEqual(tr.width(), rect1.width() + rect2.width()); + assertAlmostEqual(tr.height(), rect1.height() + rect2.width()); + assertAlmostEqual(tr.rotation(), 180); + }); + + it('events on several nodes', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect1 = new Konva.Rect({ + x: 100, + y: 60, + draggable: true, + width: 100, + height: 100, + fill: 'yellow', + }); + layer.add(rect1); + var rect2 = new Konva.Rect({ + x: 100, + y: 60, + draggable: true, + width: 100, + height: 100, + fill: 'yellow', + }); + layer.add(rect2); + + var transformstart = 0; + var transform = 0; + var transformend = 0; + + rect1.on('transformstart', function () { + transformstart += 1; + }); + rect1.on('transform', function () { + transform += 1; + }); + rect1.on('transformend', function () { + transformend += 1; + }); + + rect2.on('transformstart', function () { + transformstart += 1; + }); + rect2.on('transform', function () { + transform += 1; + }); + rect2.on('transformend', function () { + transformend += 1; + }); + + var tr = new Konva.Transformer({ + nodes: [rect1, rect2], + }); + layer.add(tr); + + layer.draw(); + + simulateMouseDown(tr, { + x: 100, + y: 60, + }); + + simulateMouseMove(tr, { + x: 105, + y: 60, + }); + + simulateMouseUp(tr, { + x: 105, + y: 60, + }); + + assert.equal(transformstart, 2); + assert.equal(transform, 2); + assert.equal(transformend, 2); + }); + + it('transform several rotated nodes', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect1 = new Konva.Rect({ + x: 50, + y: 50, + draggable: true, + width: 50, + height: 50, + fill: 'blue', + rotation: 45, + }); + layer.add(rect1); + + var rect2 = new Konva.Rect({ + x: 100, + y: 100, + draggable: true, + width: 50, + height: 50, + fill: 'red', + rotation: 120, + }); + + layer.add(rect2); + + var tr = new Konva.Transformer({ + nodes: [rect1, rect2], + }); + layer.add(tr); + layer.draw(); + + tr._fitNodesInto({ + x: 100, + y: 0, + width: 100, + height: 100, + rotation: Konva.getAngle(90), + }); + + layer.draw(); + + assertAlmostEqual(rect1.x(), 100); + assertAlmostEqual(rect1.y(), 41.421356237309496); + assertAlmostEqual(rect1.width() + rect2.width(), 100); + assertAlmostEqual(rect1.height() + rect2.width(), 100); + assertAlmostEqual(rect1.rotation(), 132.45339125826706); + + assertAlmostEqual(rect2.x(), 46.41016151377549); + assertAlmostEqual(rect2.y(), 100); + assertAlmostEqual(rect2.width() + rect2.width(), 100); + assertAlmostEqual(rect2.height() + rect2.width(), 100); + assertAlmostEqual(tr.rotation(), 90); + + tr._fitNodesInto({ + x: 100, + y: 100, + width: 100, + height: 100, + rotation: Konva.getAngle(180), + }); + + assertAlmostEqual(tr.x(), 100); + assertAlmostEqual(tr.y(), 100); + }); + + it('drag several nodes', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect1 = new Konva.Rect({ + x: 50, + y: 50, + draggable: true, + width: 50, + height: 50, + fill: 'yellow', + }); + layer.add(rect1); + + var rect2 = new Konva.Rect({ + x: 100, + y: 100, + draggable: true, + width: 50, + height: 50, + fill: 'red', + }); + + layer.add(rect2); + + var dragstart = 0; + var dragmove = 0; + var dragend = 0; + + rect2.on('dragstart', () => { + dragstart += 1; + }); + rect2.on('dragmove', () => { + dragmove += 1; + }); + rect2.on('dragend', () => { + dragend += 1; + }); + + var tr = new Konva.Transformer({ + nodes: [rect1, rect2], + }); + + // make sure drag also triggers on the transformer. + tr.on('dragstart', (e) => { + assert.equal(!!e.evt, true); + dragstart += 1; + }); + tr.on('dragmove', () => { + dragmove += 1; + }); + tr.on('dragend', () => { + dragend += 1; + }); + + // also drag should bubble to stage + // two times for two rects + stage.on('dragstart', (e) => { + assert.equal(!!e.evt, true); + dragstart += 1; + }); + + layer.add(tr); + layer.draw(); + + simulateMouseDown(tr, { + x: 75, + y: 75, + }); + sm(stage, { + x: 80, + y: 80, + }); + + sm(stage, { + x: 85, + y: 85, + }); + + su(stage, { + x: 80, + y: 80, + }); + + // proxy drag to other nodes + assert.equal(rect2.x(), 110); + assert.equal(rect2.y(), 110); + assert.equal(dragstart, 4, 'dragstart'); + assert.equal(dragmove, 3, 'dragmove'); + assert.equal(dragend, 2, 'dragend'); + }); + + it('reattach from several and drag one', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect1 = new Konva.Rect({ + x: 50, + y: 50, + draggable: true, + width: 50, + height: 50, + fill: 'yellow', + }); + layer.add(rect1); + + var rect2 = new Konva.Rect({ + x: 100, + y: 100, + draggable: true, + width: 50, + height: 50, + fill: 'red', + }); + + layer.add(rect2); + + var tr = new Konva.Transformer({ + nodes: [rect1, rect2], + }); + layer.add(tr); + layer.draw(); + + tr.nodes([rect1]); + + // now drag just the first + simulateMouseDown(tr, { + x: 125, + y: 125, + }); + sm(stage, { + x: 130, + y: 130, + }); + + su(stage, { + x: 130, + y: 130, + }); + + // no changes on the second + assert.equal(rect1.x(), 50); + assert.equal(rect1.y(), 50); + }); + + it('transformer should not hide shapes', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect1 = new Konva.Rect({ + x: 50, + y: 50, + draggable: true, + width: 50, + height: 50, + fill: 'yellow', + }); + layer.add(rect1); + + var click = 0; + + rect1.on('click', () => { + click += 1; + }); + + var tr = new Konva.Transformer({ + nodes: [rect1], + }); + layer.add(tr); + layer.draw(); + + simulateMouseDown(tr, { + x: 75, + y: 75, + }); + sm(stage, { + x: 75, + y: 75, + }); + + su(stage, { + x: 75, + y: 75, + }); + + // proxy drag to other nodes + assert.equal(click, 1); + }); + + it('drag several nodes by transformer back', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect1 = new Konva.Rect({ + x: 50, + y: 50, + draggable: true, + width: 50, + height: 50, + fill: 'yellow', + }); + layer.add(rect1); + + var rect2 = new Konva.Rect({ + x: 100, + y: 100, + draggable: true, + width: 50, + height: 50, + fill: 'red', + }); + + layer.add(rect2); + + var dragstart = 0; + var dragmove = 0; + var dragend = 0; + + rect1.on('dragstart', () => { + dragstart += 1; + }); + rect1.on('dragmove', () => { + dragmove += 1; + }); + rect1.on('dragend', () => { + dragend += 1; + }); + rect2.on('dragstart', () => { + dragstart += 1; + }); + rect2.on('dragmove', () => { + dragmove += 1; + }); + rect2.on('dragend', () => { + dragend += 1; + }); + + var tr = new Konva.Transformer({ + nodes: [rect1, rect2], + shouldOverdrawWholeArea: true, + }); + + tr.on('dragstart', () => { + dragstart += 1; + }); + tr.on('dragmove', () => { + dragmove += 1; + }); + tr.on('dragend', () => { + dragend += 1; + }); + layer.add(tr); + layer.draw(); + + simulateMouseDown(tr, { + x: 110, + y: 90, + }); + + // move mouse twice + // because first move will jus trigger start dragging + sm(stage, { + x: 120, + y: 90, + }); + sm(stage, { + x: 120, + y: 90, + }); + + su(stage, { + x: 120, + y: 90, + }); + + // proxy drag to other nodes + assert.equal(rect1.x(), 60); + assert.equal(rect1.y(), 50); + assert.equal(rect2.x(), 110); + assert.equal(rect2.y(), 100); + assert.equal(dragstart, 3, 'dragstart'); + assert.equal(dragmove, 3, 'dragmove'); + assert.equal(dragend, 3, 'dragend'); + }); + + it('reattach to several nodes', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect1 = new Konva.Rect({ + x: 50, + y: 50, + draggable: true, + width: 50, + height: 50, + fill: 'yellow', + }); + layer.add(rect1); + + var rect2 = new Konva.Rect({ + x: 100, + y: 100, + draggable: true, + width: 50, + height: 50, + fill: 'red', + }); + + layer.add(rect2); + + var tr = new Konva.Transformer({ + nodes: [rect1, rect2], + }); + layer.add(tr); + layer.draw(); + + tr._fitNodesInto({ + x: 100, + y: 0, + width: 100, + height: 100, + rotation: Konva.getAngle(90), + }); + + assertAlmostEqual(tr.x(), rect1.x()); + assertAlmostEqual(tr.y(), rect1.y()); + assertAlmostEqual(tr.width(), rect1.width() + rect2.width()); + assertAlmostEqual(tr.height(), rect1.height() + rect2.width()); + assertAlmostEqual(tr.rotation(), 90); + layer.draw(); + + tr.nodes([rect1, rect2]); + + assertAlmostEqual(tr.x(), 0); + assertAlmostEqual(tr.y(), 0); + assertAlmostEqual(tr.width(), rect1.width() + rect2.width()); + assertAlmostEqual(tr.height(), rect1.height() + rect2.width()); + assertAlmostEqual(tr.rotation(), 0); + }); + + it('rotate several nodes inside different parents', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect1 = new Konva.Rect({ + x: 0, + y: 0, + draggable: true, + width: 50, + height: 50, + fill: 'yellow', + }); + layer.add(rect1); + + var group = new Konva.Group({ + x: 50, + scaleX: 2, + }); + + layer.add(group); + + var rect2 = new Konva.Rect({ + x: 0, + y: 50, + draggable: true, + width: 25, + height: 50, + fill: 'red', + }); + group.add(rect2); + + var tr = new Konva.Transformer({ + nodes: [rect1, rect2], + }); + layer.add(tr); + layer.draw(); + + assert.equal(tr.x(), 0); + assert.equal(tr.y(), 0); + assert.equal(tr.width(), 100); + assert.equal(tr.height(), 100); + assert.equal(tr.rotation(), 0); + + // fit into the same area + const box = { + x: 0, + y: 0, + width: 100, + height: 100, + rotation: 0, + }; + + tr._fitNodesInto(box); + + var newBox = tr._getNodeRect(); + + assertAlmostEqual(box.x, newBox.x); + assertAlmostEqual(box.y, newBox.y); + assertAlmostEqual(box.width, newBox.width); + assertAlmostEqual(box.height, newBox.height); + assertAlmostEqual(box.rotation, newBox.rotation); + + assertAlmostEqual(rect1.x(), 0); + assertAlmostEqual(rect1.y(), 0); + assertAlmostEqual(rect1.width(), 50); + assertAlmostEqual(rect1.height(), 50); + assertAlmostEqual(rect1.rotation(), 0); + + assertAlmostEqual(rect2.x(), 0); + assertAlmostEqual(rect2.y(), 50); + assertAlmostEqual(rect2.width(), 25); + assertAlmostEqual(rect2.height(), 50); + assertAlmostEqual(rect2.rotation(), 0); + }); + + it('can attach transformer into several nodes and fit into negative scale', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect1 = new Konva.Rect({ + x: 0, + y: 0, + draggable: true, + width: 50, + height: 50, + fill: 'yellow', + }); + layer.add(rect1); + + var rect2 = new Konva.Rect({ + x: 50, + y: 50, + draggable: true, + width: 50, + height: 50, + fill: 'red', + }); + + layer.add(rect2); + + var tr = new Konva.Transformer({ + nodes: [rect1, rect2], + }); + layer.add(tr); + + tr._fitNodesInto({ + x: 100, + y: 0, + width: 0, + height: 100, + rotation: 0, + }); + + tr._fitNodesInto({ + x: 100, + y: 0, + width: -100, + height: 100, + rotation: 0, + }); + + layer.draw(); + assertAlmostEqual(Math.round(tr.x()), 0); + assertAlmostEqual(Math.round(tr.y()), 0); + assertAlmostEqual(tr.width(), rect1.width() + rect2.width()); + assertAlmostEqual(tr.height(), rect1.height() + rect2.height()); + assertAlmostEqual(tr.rotation(), 0); + }); + + it('boundBoxFox should work in absolute coordinates', function () { + var stage = addStage(); + var layer = new Konva.Layer({ + x: 10, + y: 10, + scaleX: 2, + scaleY: 2, + }); + stage.add(layer); + + var rect1 = new Konva.Rect({ + x: 0, + y: 0, + draggable: true, + width: 50, + height: 50, + fill: 'yellow', + }); + layer.add(rect1); + + var rect2 = new Konva.Rect({ + x: 50, + y: 50, + draggable: true, + width: 50, + height: 50, + fill: 'red', + }); + + layer.add(rect2); + + var callCount = 0; + var tr = new Konva.Transformer({ + nodes: [rect1, rect2], + boundBoxFunc: function (oldBox, newBox) { + callCount += 1; + assert.deepEqual(oldBox, { + x: 10, + y: 10, + width: 200, + height: 200, + rotation: 0, + }); + assert.deepEqual(newBox, { + x: 10, + y: 10, + width: 300, + height: 200, + rotation: 0, + }); + return newBox; + }, + }); + layer.add(tr); + + tr._fitNodesInto({ + x: 10, + y: 10, + width: 300, + height: 200, + rotation: 0, + }); + assert.equal(callCount, 1); + }); + + // TODO: move to manual tests + it.skip('performance check - drag several nodes', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + for (var i = 0; i < 500; i++) { + var shape = new Konva.Circle({ + x: 100, + y: 100, + radius: 50, + fill: 'red', + draggable: true, + }); + layer.add(shape); + } + var shapes = layer.find('Circle'); + var tr = new Konva.Transformer({ + nodes: shapes, + }); + layer.add(tr); + layer.draw(); + + throw 1; + }); + + // we don't support height = 0 + it.skip('try to transform zero size shape', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var shape = new Konva.Line({ + x: stage.width() / 4, + y: stage.height() / 4, + points: [0, 0, 200, 0], + fill: 'black', + stroke: 'black', + strokeWidth: 4, + draggable: true, + }); + layer.add(shape); + + var tr = new Konva.Transformer({ + nodes: [shape], + enabledAnchors: ['middle-left', 'middle-right'], + ignoreStroke: true, + }); + layer.add(tr); + + layer.draw(); + + simulateMouseDown(tr, { + x: stage.width() / 2, + y: stage.height() / 2, + }); + simulateMouseDown(tr, { + x: stage.width() / 2 + 100, + y: stage.height() / 2, + }); + simulateMouseUp(tr); + assert.equal(shape.scaleX(), 0.5); + }); + + it('check transform cache', function () { + var stage = addStage({ scaleX: 0.5, scaleY: 0.5 }); + var layer = new Konva.Layer(); + stage.add(layer); + + var textNode = new Konva.Text({ + text: 'Some text here', + x: 300, + y: 100, + fontSize: 20, + draggable: true, + width: 200, + }); + + var tr = new Konva.Transformer({ + nodes: [textNode], + enabledAnchors: [ + 'top-left', + 'top-right', + 'bottom-left', + 'bottom-right', + 'middle-left', + 'middle-right', + ], + boundBoxFunc: function (oldBox, newBox) { + if (newBox.width < 5 || newBox.height < 5 || newBox.width > 1000) { + return oldBox; + } + return newBox; + }, + }); + + layer.add(tr); + layer.add(textNode); + + assert.equal(textNode.getClientRect().width, 100); + }); + + // ====================================================== + it('init transformer on simple rectangle', function () { + var stage = addStage(); + stage.rotation(45); + + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + x: 100, + y: 60, + draggable: true, + width: 100, + height: 100, + fill: 'yellow', + rotation: 45, + }); + layer.add(rect); + + var tr = new Konva.Transformer({ + useSingleNodeRotation: false, + nodes: [rect], + }); + layer.add(tr); + + layer.draw(); + assert.equal(tr.getClassName(), 'Transformer'); + + assert.equal(tr.rotation(), 0); + }); + + it('use several transformers on a single node', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + x: 100, + y: 60, + draggable: true, + width: 100, + height: 100, + fill: 'yellow', + }); + layer.add(rect); + + var tr1 = new Konva.Transformer({ + nodes: [rect], + }); + layer.add(tr1); + + var tr2 = new Konva.Transformer({ + nodes: [rect], + }); + layer.add(tr2); + + // detach tr1 + tr1.nodes([]); + + // update rect + rect.setAttrs({ x: 0, y: 0, width: 50, height: 50 }); + + // it should update second transformer + assert.equal(tr2.x(), rect.x()); + assert.equal(tr2.y(), rect.y()); + assert.equal(tr2.width(), rect.width()); + assert.equal(tr2.height(), rect.height()); + }); + it('detached transformer should not affect client rect', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + x: 100, + y: 60, + draggable: true, + width: 100, + height: 100, + fill: 'yellow', + }); + layer.add(rect); + + var tr = new Konva.Transformer({ + nodes: [], + }); + layer.add(tr); + + const layerClientRect = layer.getClientRect(); + const rectClientRect = rect.getClientRect(); + + // the client rect should not be affected by the transformer + assert.deepEqual(layerClientRect, rectClientRect); + }); + it('attached transformer should affect client rect', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + x: 100, + y: 60, + draggable: true, + width: 100, + height: 100, + fill: 'yellow', + }); + layer.add(rect); + + var tr = new Konva.Transformer({ + nodes: [rect], + }); + layer.add(tr); + + const layerClientRect = layer.getClientRect(); + const rectClientRect = rect.getClientRect(); + const trClientRect = tr.getClientRect(); + + // the client rect should be affecte by the transformer + assert.notDeepEqual(layerClientRect, rectClientRect); + assert.deepEqual(layerClientRect, trClientRect); + }); + + it('cloning of transformer should double create child elements', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + x: 100, + y: 60, + draggable: true, + width: 100, + height: 100, + fill: 'yellow', + }); + layer.add(rect); + + var tr = new Konva.Transformer({ + nodes: [rect], + }); + layer.add(tr); + + const clone = tr.clone(); + assert.equal(clone.getChildren().length, tr.getChildren().length); + assert.equal(clone.nodes().length, 0); + }); + + it('should filter parent of the transformer', function () { + const stage = addStage(); + + const layer = new Konva.Layer(); + stage.add(layer); + + const tr = new Konva.Transformer(); + layer.add(tr); + + tr.nodes([layer]); + assert.equal(tr.nodes().length, 0); + }); + + it('anchorStyleFunc', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + x: 100, + y: 60, + draggable: true, + width: 100, + height: 100, + fill: 'yellow', + }); + layer.add(rect); + + var tr = new Konva.Transformer({ + nodes: [rect], + }); + layer.add(tr); + // manual check of correct position of node + var handler = tr.findOne('.bottom-right'); + assert.equal(handler.fill(), 'white'); + + tr.anchorStyleFunc((anchor) => { + if (anchor.hasName('bottom-right')) { + anchor.fill('red'); + } + }); + assert.equal(handler.fill(), 'red'); + tr.anchorStyleFunc(null); + assert.equal(handler.fill(), 'white'); + }); + + it('flip rectangle', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + draggable: true, + x: 150, + y: 50, + width: 100, + height: 100, + fillLinearGradientStartPoint: { x: -50, y: -50 }, + fillLinearGradientEndPoint: { x: 50, y: 50 }, + fillLinearGradientColorStops: [0, 'red', 1, 'yellow'], + }); + layer.add(rect); + + var tr = new Konva.Transformer({ + nodes: [rect], + flipEnabled: false, + }); + layer.add(tr); + + layer.draw(); + + simulateMouseDown(tr, { + x: 150, + y: 50, + }); + simulateMouseMove(tr, { + x: 250, + y: 50, + }); + simulateMouseMove(tr, { + x: 350, + y: 50, + }); + + simulateMouseUp(tr, { + x: 350, + y: 50, + }); + + layer.draw(); + + assertAlmostEqual(rect.x(), 250); + assertAlmostEqual(rect.y(), 50); + assertAlmostEqual(rect.scaleX(), 1); + assertAlmostEqual(rect.scaleY(), 1); + }); + + it('should be able to prevent rotation in transform event', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + x: 55, + y: 55, + draggable: true, + width: 100, + height: 100, + fill: 'yellow', + }); + layer.add(rect); + + var tr = new Konva.Transformer({ + nodes: [rect], + }); + layer.add(tr); + layer.draw(); + + tr.on('transform', function (e) { + tr.rotation(0); + }); + + simulateMouseDown(tr, { + x: 100, + y: 2, + }); + simulateMouseMove(tr, { + x: 110, + y: 2, + }); + assert.equal(tr.rotation(), 0); + simulateMouseUp(tr, { x: 110, y: 2 }); + }); + + it('skip render on hit graph while transforming', function (done) { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + x: 55, + y: 55, + draggable: true, + width: 100, + height: 100, + fill: 'yellow', + }); + layer.add(rect); + + var tr = new Konva.Transformer({ + nodes: [rect], + }); + layer.add(tr); + layer.draw(); + + simulateMouseDown(tr, { + x: 100, + y: 2, + }); + simulateMouseMove(tr, { + x: 110, + y: 2, + }); + let shape = layer.getIntersection({ + x: 100, + y: 100, + }); + assert.equal(shape, null); + simulateMouseUp(tr, { x: 110, y: 2 }); + layer.draw(); + shape = layer.getIntersection({ + x: 100, + y: 100, + }); + assert.equal(shape, rect); + // reset position + rect.setAttrs({ + x: 50, + y: 50, + draggable: true, + width: 100, + height: 100, + }); + + tr.nodes([rect]); + layer.draw(); + // now check if graph is visible back when we moved a bit + simulateMouseDown(tr, { + x: 100, + y: 2, + }); + simulateMouseMove(tr, { + x: 110, + y: 2, + }); + setTimeout(() => { + shape = layer.getIntersection({ + x: 100, + y: 100, + }); + assert.equal(shape, null); + simulateMouseUp(tr, { x: 110, y: 2 }); + setTimeout(() => { + shape = layer.getIntersection({ + x: 100, + y: 100, + }); + assert.equal(shape, rect); + done(); + }, 100); + }, 100); + }); + + it('enable hit graph back on transformer destroy', function (done) { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + x: 55, + y: 55, + draggable: true, + width: 100, + height: 100, + fill: 'yellow', + }); + layer.add(rect); + + var tr = new Konva.Transformer({ + nodes: [rect], + }); + layer.add(tr); + layer.draw(); + + // now check if graph is visible back when we moved a bit + simulateMouseDown(tr, { + x: 100, + y: 2, + }); + simulateMouseMove(tr, { + x: 110, + y: 2, + }); + setTimeout(() => { + tr.destroy(); + setTimeout(() => { + var shape = layer.getIntersection({ + x: 100, + y: 100, + }); + assert.equal(shape, rect); + done(); + }, 100); + }, 100); + }); +}); diff --git a/test/unit/Tween-test.ts b/test/unit/Tween-test.ts new file mode 100644 index 000000000..838f1e131 --- /dev/null +++ b/test/unit/Tween-test.ts @@ -0,0 +1,410 @@ +import { assert } from 'chai'; + +import { addStage, Konva } from './test-utils'; + +describe('Tween', function () { + // ====================================================== + it('tween node', function (done) { + var stage = addStage(); + + var layer = new Konva.Layer(); + + var circle = new Konva.Circle({ + x: 100, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'blue', + strokeWidth: 4, + }); + + layer.add(circle); + stage.add(layer); + + var finishCount = 0; + var onFinish = function () { + assert(++finishCount <= 1, 'finishCount should not exceed 1'); + done(); + }; + + var tweens = 0; + var attrs = 0; + + for (var key in Konva.Tween.tweens) { + tweens++; + } + for (var key in Konva.Tween.attrs) { + attrs++; + } + + assert.equal(tweens, 0); + assert.equal(attrs, 0); + + var tween = new Konva.Tween({ + node: circle, + duration: 0.2, + x: 200, + y: 100, + onFinish: onFinish, + }).play(); + + var tweens = 0; + var attrs = 0; + for (var key in Konva.Tween.tweens) { + tweens++; + } + for (var key in Konva.Tween.attrs[circle._id][tween._id]) { + attrs++; + } + + assert.equal(tweens, 1); + assert.equal(attrs, 2); + + assert.notEqual(Konva.Tween.attrs[circle._id][tween._id].x, undefined); + assert.notEqual(Konva.Tween.attrs[circle._id][tween._id].y, undefined); + }); + + // ====================================================== + it('destroy tween while tweening', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + + var circle = new Konva.Circle({ + x: 100, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'blue', + strokeWidth: 4, + }); + + layer.add(circle); + stage.add(layer); + + var tween = new Konva.Tween({ + node: circle, + duration: 0.2, + x: 200, + y: 100, + }).play(); + + // start/diff object = attrs.nodeId.tweenId.attr + // tweenId = tweens.nodeId.attr + + assert.notEqual(tween._id, undefined); + assert.equal(Konva.Tween.tweens[circle._id].x, tween._id); + assert.notEqual(Konva.Tween.attrs[circle._id][tween._id], undefined); + + tween.destroy(); + + assert.equal(Konva.Tween.tweens[circle._id].x, undefined); + assert.equal(Konva.Tween.attrs[circle._id][tween._id], undefined); + }); + + // ====================================================== + it('zero duration', function (done) { + var stage = addStage(); + + var layer = new Konva.Layer(); + + var circle = new Konva.Circle({ + x: 100, + y: stage.height() / 2, + radius: 70, + fill: 'green', + stroke: 'blue', + strokeWidth: 4, + }); + + layer.add(circle); + stage.add(layer); + + var tween = new Konva.Tween({ + node: circle, + duration: 0, + x: 200, + y: 100, + }); + tween.play(); + + setTimeout(function () { + assert.equal(circle.x(), 200); + assert.equal(circle.y(), 100); + done(); + }, 60); + }); + + it('color tweening', function (done) { + var stage = addStage(); + + var layer = new Konva.Layer(); + + var circle = new Konva.Circle({ + x: 100, + y: stage.height() / 2, + radius: 70, + fill: 'red', + stroke: 'blue', + strokeWidth: 4, + }); + + layer.add(circle); + stage.add(layer); + + var duration = 0.1; + var c = Konva.Util.colorToRGBA('rgba(0,255,0,0.5)'); + var endFill = 'rgba(' + c.r + ',' + c.g + ',' + c.b + ',' + c.a + ')'; + var midFill = 'rgba(128,128,0,0.75)'; + + var tween = new Konva.Tween({ + node: circle, + duration: duration, + fill: endFill, + onFinish: function () { + assert.equal(endFill, circle.fill()); + done(); + }, + }); + + tween.seek(duration * 0.5); + assert.equal(midFill, circle.fill()); + + tween.seek(0); + tween.play(); + }); + + it('gradient tweening', function (done) { + var stage = addStage(); + + var layer = new Konva.Layer(); + + var circle = new Konva.Circle({ + x: 100, + y: stage.height() / 2, + radius: 70, + fillLinearGradientStartPoint: { x: -50, y: -50 }, + fillLinearGradientEndPoint: { x: 50, y: 50 }, + fillLinearGradientColorStops: [0, 'red', 0.5, 'blue'], + }); + + layer.add(circle); + stage.add(layer); + + var duration = 0.1; + var endFill = [0.5, 'red', 1, 'black']; + + var tween = new Konva.Tween({ + node: circle, + duration: duration, + fillLinearGradientColorStops: endFill, + onFinish: function () { + assert.deepEqual( + [0.5, 'rgba(255,0,0,1)', 1, 'rgba(0,0,0,1)'], + circle.fillLinearGradientColorStops() + ); + done(); + }, + }); + + tween.seek(duration * 0.5); + assert.deepEqual( + [0.25, 'rgba(255,0,0,1)', 0.75, 'rgba(0,0,128,1)'], + circle.fillLinearGradientColorStops() + ); + + tween.seek(0); + tween.play(); + }); + + it('to method', function (done) { + var stage = addStage(); + + var layer = new Konva.Layer(); + + var circle = new Konva.Circle({ + radius: 70, + fill: 'red', + stroke: 'blue', + strokeWidth: 4, + }); + + layer.add(circle); + stage.add(layer); + + circle.to({ + x: stage.width() / 2, + y: stage.height() / 2, + duration: 0.1, + onFinish: function () { + assert.equal(circle.x(), stage.width() / 2); + assert.equal(Object.keys(Konva.Tween.attrs[circle._id]).length, 0); + done(); + }, + }); + }); + + it('to method simple usage', function (done) { + var stage = addStage(); + + stage.to({ + x: 10, + duration: 0.001, + onFinish: () => { + assert(stage.x() === 10); + done(); + }, + }); + }); + + it('tween to call update callback', function (done) { + var stage = addStage(); + var updateCount = 0; + + stage.to({ + x: 10, + duration: 0.1, + onUpdate: function () { + updateCount++; + }, + onFinish: function () { + assert(updateCount > 2); + done(); + }, + }); + }); + + it('prepare array closed', function () { + var start = [0, 0, 10, 0, 10, 10]; + var end = [0, 0, 10, 0, 10, 10, 0, 10]; + var newStart = Konva.Util._prepareArrayForTween(start, end, true); + assert.deepEqual(newStart, [0, 0, 10, 0, 10, 10, 5, 5]); + }); + + it('prepare array - opened', function () { + var start = [0, 0, 10, 0, 10, 10, 0, 10]; + var end = [0, 0, 10, 0, 7, 9]; + end = Konva.Util._prepareArrayForTween(start, end, false); + assert.deepEqual(end, [0, 0, 10, 0, 7, 9, 7, 9]); + }); + + it('tween array with bigger size', function (done) { + var stage = addStage(); + + var layer = new Konva.Layer(); + stage.add(layer); + + var line = new Konva.Line({ + stroke: 'black', + points: [100, 100, 200, 100, 200, 200], + closed: true, + }); + layer.add(line); + + line.to({ + points: [100, 100, 200, 100, 200, 200, 100, 200], + duration: 0.1, + onFinish: function () { + assert.deepEqual(line.points(), [ + 100, + 100, + 200, + 100, + 200, + 200, + 100, + 200, + ]); + done(); + }, + }); + }); + + it('tween array to lower size', function (done) { + var stage = addStage(); + + var layer = new Konva.Layer(); + stage.add(layer); + + var line = new Konva.Line({ + stroke: 'black', + points: [100, 100, 200, 100, 200, 200, 100, 200], + closed: true, + }); + layer.add(line); + + line.to({ + points: [100, 100, 200, 100, 200, 200], + duration: 0.1, + onFinish: function () { + assert.deepEqual(line.points(), [100, 100, 200, 100, 200, 200]); + done(); + }, + }); + }); + + it('tween array to lower size and go back', function (done) { + var stage = addStage(); + + var layer = new Konva.Layer(); + stage.add(layer); + + var line = new Konva.Line({ + stroke: 'black', + points: [100, 100, 200, 100, 200, 200, 100, 200], + closed: true, + }); + layer.add(line); + + var tween = new Konva.Tween({ + node: line, + points: [100, 100, 200, 100, 200, 200], + duration: 0.01, + onFinish: function () { + tween.reverse(); + }, + onReset: function () { + assert.deepEqual(line.points(), [ + 100, + 100, + 200, + 100, + 200, + 200, + 100, + 200, + ]); + done(); + }, + }); + tween.play(); + }); + + it('tween array to bigger size and go back', function (done) { + var stage = addStage(); + + var layer = new Konva.Layer(); + stage.add(layer); + + var line = new Konva.Line({ + stroke: 'black', + points: [100, 100, 200, 100, 200, 200], + closed: true, + }); + layer.add(line); + + var tween = new Konva.Tween({ + node: line, + points: [100, 100, 200, 100, 200, 200, 100, 200], + duration: 0.01, + onFinish: function () { + tween.reverse(); + }, + onReset: function () { + assert.deepEqual(line.points(), [100, 100, 200, 100, 200, 200]); + done(); + }, + }); + tween.play(); + }); +}); diff --git a/test/unit/Util-test.ts b/test/unit/Util-test.ts new file mode 100644 index 000000000..be49bebc8 --- /dev/null +++ b/test/unit/Util-test.ts @@ -0,0 +1,119 @@ +import { assert } from 'chai'; +import { Konva } from './test-utils'; + +describe('Util', function () { + it('test _prepareToStringify', function () { + var o: any = { + a: 1, + b: 'string1', + }; + o.c = { + d: 'string2', + e: o, + f: global.document ? global.document.createElement('p') : { nodeType: 1 }, + }; + o.g = o; + + assert.deepEqual(Konva.Util._prepareToStringify(o), { + a: 1, + b: 'string1', + c: { + d: 'string2', + }, + }); + }); + + it('colorToRGBA() - from HSL to RGBA conversion', function () { + assert.deepEqual(Konva.Util.colorToRGBA('hsl(0, 0%, 0%)'), { + r: 0, + g: 0, + b: 0, + a: 1, + }); + + assert.deepEqual(Konva.Util.colorToRGBA('hsl(96, 48%, 59%)'), { + r: 140, + g: 201, + b: 100, + a: 1, + }); + + assert.deepEqual(Konva.Util.colorToRGBA('hsl(200, 100%, 70%)'), { + r: 102, + g: 204, + b: 255, + a: 1, + }); + }); + + it('colorToRGBA() - from color string with percentage to RGBA conversion!', function () { + assert.deepEqual(Konva.Util.colorToRGBA('rgba(50, 100, 150, 0.5)'), { + r: 50, + g: 100, + b: 150, + a: 0.5, + }); + + assert.deepEqual(Konva.Util.colorToRGBA('rgba(50, 100, 150, 50%)'), { + r: 50, + g: 100, + b: 150, + a: 0.5, + }); + + assert.deepEqual(Konva.Util.colorToRGBA('rgba(25%, 50%, 100%, 0.5)'), { + r: 63.75, + g: 127.5, + b: 255, + a: 0.5, + }); + + assert.deepEqual(Konva.Util.colorToRGBA('rgba(0%, 50%, 100%, 100%)'), { + r: 0, + g: 127.5, + b: 255, + a: 1, + }); + }); + + it('colorToRGBA() - from hex color string with percentage to RGBA conversion!', function () { + assert.deepEqual(Konva.Util.colorToRGBA('#F00'), { + r: 255, + g: 0, + b: 0, + a: 1, + }); + + assert.deepEqual(Konva.Util.colorToRGBA('#F00F'), { + r: 255, + g: 0, + b: 0, + a: 1, + }); + + assert.deepEqual(Konva.Util.colorToRGBA('#F00C'), { + r: 255, + g: 0, + b: 0, + a: 0.8, + }); + + assert.deepEqual(Konva.Util.colorToRGBA('#FF0000FF'), { + r: 255, + g: 0, + b: 0, + a: 1, + }); + + assert.deepEqual(Konva.Util.colorToRGBA('#FF0000CC'), { + r: 255, + g: 0, + b: 0, + a: 0.8, + }); + }); + + it('make sure Transform is exported', () => { + assert.equal(!!Konva.Transform, true); + }); +}); diff --git a/test/unit/Wedge-test.ts b/test/unit/Wedge-test.ts new file mode 100644 index 000000000..0b0c231d4 --- /dev/null +++ b/test/unit/Wedge-test.ts @@ -0,0 +1,86 @@ +import { assert } from 'chai'; + +import { addStage, Konva } from './test-utils'; + +describe('Wedge', function () { + // ====================================================== + it('add wedge', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var wedge = new Konva.Wedge({ + x: 100, + y: 100, + radius: 70, + angle: 180 * 0.4, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle', + draggable: true, + }); + + layer.add(wedge); + stage.add(layer); + + assert.equal(wedge.getClassName(), 'Wedge'); + + var trace = layer.getContext().getTrace(); + //console.log(trace); + assert.equal( + trace, + 'clearRect(0,0,578,200);save();transform(1,0,0,1,100,100);beginPath();arc(0,0,70,0,1.257,false);lineTo(0,0);closePath();fillStyle=green;fill();lineWidth=4;strokeStyle=black;stroke();restore();' + ); + }); + + it('attrs sync', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var wedge = new Konva.Wedge({ + x: stage.width() / 2, + y: stage.height() / 2, + angle: 180 * 0.4, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + }); + + layer.add(wedge); + stage.add(layer); + + assert.equal(wedge.getWidth(), 140); + assert.equal(wedge.getHeight(), 140); + + wedge.setWidth(100); + assert.equal(wedge.radius(), 50); + assert.equal(wedge.getHeight(), 100); + + wedge.setHeight(120); + assert.equal(wedge.radius(), 60); + assert.equal(wedge.getHeight(), 120); + }); + + it('getSelfRect', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var wedge = new Konva.Wedge({ + x: stage.width() / 2, + y: stage.height() / 2, + angle: 180 * 0.4, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + }); + + layer.add(wedge); + stage.add(layer); + + assert.deepEqual(wedge.getSelfRect(), { + x: -70, + y: -70, + width: 140, + height: 140, + }); + }); +}); diff --git a/test/unit/imagediff.ts b/test/unit/imagediff.ts new file mode 100644 index 000000000..bb1e563f5 --- /dev/null +++ b/test/unit/imagediff.ts @@ -0,0 +1,332 @@ +import { createCanvas, Canvas } from 'canvas'; + +var TYPE_ARRAY = /\[object Array\]/i, + TYPE_CANVAS = /\[object (Canvas|HTMLCanvasElement)\]/i, + TYPE_NODE_CANVAS = /\[object (Canvas|HTMLCanvasElement)\]/i, + TYPE_CONTEXT = /\[object CanvasRenderingContext2D\]/i, + TYPE_IMAGE = /\[object (Image|HTMLImageElement)\]/i, + TYPE_IMAGE_DATA = /\[object ImageData\]/i, + UNDEFINED = 'undefined', + canvas = getCanvas(), + context = canvas.getContext('2d'); + +// Creation +function getCanvas(width?, height?) { + return createCanvas(width, height); +} +function getImageData(width, height) { + canvas.width = width; + canvas.height = height; + context.clearRect(0, 0, width, height); + return context.createImageData(width, height); +} + +// Type Checking +function isImage(object) { + return isType(object, TYPE_IMAGE); +} +function isCanvas(object) { + return isType(object, TYPE_CANVAS) || object instanceof Canvas; +} +function isContext(object) { + return isType(object, TYPE_CONTEXT); +} +function isImageData(object) { + return !!( + object && + isType(object, TYPE_IMAGE_DATA) && + typeof object.width !== UNDEFINED && + typeof object.height !== UNDEFINED && + typeof object.data !== UNDEFINED + ); +} +function isImageType(object) { + return ( + isImage(object) || + isCanvas(object) || + isContext(object) || + isImageData(object) + ); +} +function isType(object, type) { + return ( + typeof object === 'object' && + !!Object.prototype.toString.apply(object).match(type) + ); +} + +// Type Conversion +function copyImageData(imageData) { + var height = imageData.height, + width = imageData.width, + data = imageData.data, + newImageData, + newData, + i; + + canvas.width = width; + canvas.height = height; + newImageData = context.getImageData(0, 0, width, height); + newData = newImageData.data; + + for (i = imageData.data.length; i--; ) { + newData[i] = data[i]; + } + + return newImageData; +} +function toImageData(object) { + if (isImage(object)) { + return toImageDataFromImage(object); + } + if (isCanvas(object)) { + return toImageDataFromCanvas(object); + } + if (isContext(object)) { + return toImageDataFromContext(object); + } + if (isImageData(object)) { + return object; + } +} +function toImageDataFromImage(image) { + var height = image.height, + width = image.width; + canvas.width = width; + canvas.height = height; + context.clearRect(0, 0, width, height); + context.drawImage(image, 0, 0); + return context.getImageData(0, 0, width, height); +} +function toImageDataFromCanvas(canvas) { + var height = canvas.height, + width = canvas.width, + context = canvas.getContext('2d'); + if (!width || !height) { + console.trace(width, height); + } + + return context.getImageData(0, 0, width, height); +} +function toImageDataFromContext(context) { + var canvas = context.canvas, + height = canvas.height, + width = canvas.width; + return context.getImageData(0, 0, width, height); +} +function toCanvas(object) { + var data = toImageData(object), + canvas = getCanvas(data.width, data.height), + context = canvas.getContext('2d'); + + context.putImageData(data, 0, 0); + return canvas; +} + +// ImageData Equality Operators +function equalWidth(a, b) { + return a.width === b.width; +} +function equalHeight(a, b) { + return a.height === b.height; +} +function equalDimensions(a, b) { + return equalHeight(a, b) && equalWidth(a, b); +} + +export function equal(a, b, tolerance, secondTol) { + var aData = a.data, + bData = b.data, + length = aData.length, + i; + + tolerance = tolerance || 0; + + var count = 0; + if (!equalDimensions(a, b)) return false; + for (i = length; i--; ) { + const d = Math.abs(aData[i] - bData[i]); + if (aData[i] !== bData[i] && d > tolerance) { + if (!secondTol) { + console.log('Diff', d); + return false; + } else { + count += 1; + } + if (count > secondTol) { + console.log('Diff', d, count); + return false; + } + } + } + + return true; +} + +// Diff +function diff(a, b, options) { + return (equalDimensions(a, b) ? diffEqual : diffUnequal)(a, b, options); +} +function diffEqual(a, b, options) { + var height = a.height, + width = a.width, + c = getImageData(width, height), // c = a - b + aData = a.data, + bData = b.data, + cData = c.data, + length = cData.length, + row, + column, + i, + j, + k, + v; + + for (i = 0; i < length; i += 4) { + cData[i] = Math.abs(aData[i] - bData[i]); + cData[i + 1] = Math.abs(aData[i + 1] - bData[i + 1]); + cData[i + 2] = Math.abs(aData[i + 2] - bData[i + 2]); + cData[i + 3] = Math.abs(255 - Math.abs(aData[i + 3] - bData[i + 3])); + } + + return c; +} +function diffUnequal(a, b, options) { + var height = Math.max(a.height, b.height), + width = Math.max(a.width, b.width), + c = getImageData(width, height), // c = a - b + aData = a.data, + bData = b.data, + cData = c.data, + align = options && options.align, + rowOffset, + columnOffset, + row, + column, + i, + j, + k, + v; + + for (i = cData.length - 1; i > 0; i = i - 4) { + cData[i] = 255; + } + + // Add First Image + offsets(a); + for (row = a.height; row--; ) { + for (column = a.width; column--; ) { + i = 4 * ((row + rowOffset) * width + (column + columnOffset)); + j = 4 * (row * a.width + column); + cData[i + 0] = aData[j + 0]; // r + cData[i + 1] = aData[j + 1]; // g + cData[i + 2] = aData[j + 2]; // b + // cData[i+3] = aData[j+3]; // a + } + } + + // Subtract Second Image + offsets(b); + for (row = b.height; row--; ) { + for (column = b.width; column--; ) { + i = 4 * ((row + rowOffset) * width + (column + columnOffset)); + j = 4 * (row * b.width + column); + cData[i + 0] = Math.abs(cData[i + 0] - bData[j + 0]); // r + cData[i + 1] = Math.abs(cData[i + 1] - bData[j + 1]); // g + cData[i + 2] = Math.abs(cData[i + 2] - bData[j + 2]); // b + } + } + + // Helpers + function offsets(imageData) { + if (align === 'top') { + rowOffset = 0; + columnOffset = 0; + } else { + rowOffset = Math.floor((height - imageData.height) / 2); + columnOffset = Math.floor((width - imageData.width) / 2); + } + } + + return c; +} + +// Validation +function checkType(...args) { + var i; + for (i = 0; i < args.length; i++) { + if (!isImageType(args[i])) { + throw { + name: 'ImageTypeError', + message: 'Submitted object was not an image.', + }; + } + } +} + +// function formatImageDiffEqualHtmlReport(actual, expected) { +// var div = get('div', 'Expected to be equal.'), +// a = get('div', '
      Actual:
      '), +// b = get('div', '
      Expected:
      '), +// c = get('div', '
      Diff:
      '), +// diff = imagediff.diff(actual, expected), +// canvas = getCanvas(), +// context; + +// canvas.height = diff.height; +// canvas.width = diff.width; + +// div.style.overflow = 'hidden'; +// a.style.float = 'left'; +// b.style.float = 'left'; +// c.style.float = 'left'; + +// context = canvas.getContext('2d'); +// context.putImageData(diff, 0, 0); + +// a.appendChild(toCanvas(actual)); +// b.appendChild(toCanvas(expected)); +// c.appendChild(canvas); + +// div.appendChild(a); +// div.appendChild(b); +// div.appendChild(c); + +// return div.innerHTML; +// } + +// function formatImageDiffEqualTextReport(actual, expected) { +// return 'Expected to be equal.'; +// } + +export const imagediff = { + createCanvas: getCanvas, + createImageData: getImageData, + + isImage: isImage, + isCanvas: isCanvas, + isContext: isContext, + isImageData: isImageData, + isImageType: isImageType, + + toImageData: function (object) { + checkType(object); + if (isImageData(object)) { + return copyImageData(object); + } + return toImageData(object); + }, + + equal: function (a, b, tolerance, secondTol) { + checkType(a, b); + a = toImageData(a); + b = toImageData(b); + return equal(a, b, tolerance, secondTol); + }, + diff: function (a, b, options?) { + checkType(a, b); + a = toImageData(a); + b = toImageData(b); + return diff(a, b, options); + }, +}; diff --git a/test/unit/test-utils.ts b/test/unit/test-utils.ts new file mode 100644 index 000000000..3738df2e3 --- /dev/null +++ b/test/unit/test-utils.ts @@ -0,0 +1,392 @@ +import { assert } from 'chai'; +import KonvaModule from '../../src/index'; +import '../../src/index-node'; + +export const Konva = KonvaModule; + +import * as canvas from 'canvas'; + +Konva.enableTrace = true; +Konva.showWarnings = true; + +import { imagediff } from './imagediff'; +import { Layer } from '../../src/Layer'; +import { Stage } from '../../src/Stage'; + +// reset some data +beforeEach(function () { + Konva._mouseInDblClickWindow = false; + Konva._touchInDblClickWindow = false; + Konva._pointerInDblClickWindow = false; +}); + +// clear after test +afterEach(function () { + var isFailed = this.currentTest.state == 'failed'; + var isManual = this.currentTest.parent.title === 'Manual'; + + Konva.stages.forEach(function (stage) { + clearTimeout(stage._mouseDblTimeout); + clearTimeout(stage._touchDblTimeout); + clearTimeout(stage._pointerDblTimeout); + }); + + if (!isFailed && !isManual) { + Konva.stages.forEach(function (stage) { + stage.destroy(); + }); + if (Konva.DD._dragElements.size) { + throw 'Why drag elements are not cleaned?'; + } + } +}); + +export const isNode = typeof global.document === 'undefined'; +export const isBrowser = !isNode; + +export function getContainer() { + return document.getElementById('konva-container'); +} + +export function addContainer() { + if (isNode) { + return; + } + var container = document.createElement('div'); + + getContainer().appendChild(container); + return container; +} + +export function addStage(attrs?) { + var container = + (!isNode && global.document.createElement('div')) || undefined; + + var stage = new Konva.Stage({ + container: container, + width: 578, + height: 200, + ...attrs, + }); + + if (!isNode) { + getContainer().appendChild(container); + } + + return stage; +} + +export function loadImage(url, callback) { + const isBase64 = url.indexOf('base64') >= 0; + if (isNode && !isBase64) { + url = './test/assets/' + url; + } else if (!isBase64) { + url = (document.getElementById(url) as HTMLImageElement).src; + } + + return canvas + .loadImage(url) + .then(callback) + .catch((e) => { + console.error(e); + }); +} + +export function getPixelRatio() { + return (typeof window !== 'undefined' && window.devicePixelRatio) || 1; +} + +function get(element, content?) { + element = document.createElement(element); + if (element && content) { + element.innerHTML = content; + } + return element; +} + +export function compareCanvases(canvas1, canvas2, tol?, secondTol?) { + // don't test in PhantomJS as it use old chrome engine + // it it has opacity + shadow bug + var equal = imagediff.equal(canvas1, canvas2, tol, secondTol); + if (!equal) { + const diff = imagediff.diff(canvas1, canvas2); + const diffCanvas = createCanvas(); + + const context = diffCanvas.getContext('2d'); + context.putImageData(diff, 0, 0); + + var base64 = diffCanvas.toDataURL(); + console.error('Diff image:'); + console.error(base64); + + if (isBrowser) { + var div = get('div'), + b = get('div', '
      Expected:
      '), + c = get('div', '
      Diff:
      '); + div.style.overflow = 'hidden'; + b.style.float = 'left'; + c.style.float = 'left'; + canvas2.style.position = ''; + canvas2.style.display = ''; + b.appendChild(canvas2); + c.appendChild(diffCanvas); + div.appendChild(b); + div.appendChild(c); + getContainer().appendChild(div); + } + } + assert.equal( + equal, + true, + 'Result from Konva is different with canvas result' + ); +} + +export function compareLayerAndCanvas(layer: Layer, canvas, tol?, secondTol?) { + compareCanvases(layer.getNativeCanvasElement(), canvas, tol, secondTol); +} + +export function cloneAndCompareLayer(layer: Layer, tol?, secondTol?) { + var layer2 = layer.clone(); + layer.getStage().add(layer2); + layer2.hide(); + compareLayers(layer, layer2, tol, secondTol); +} + +export function compareLayers(layer1: Layer, layer2: Layer, tol?, secondTol?) { + compareLayerAndCanvas( + layer1, + layer2.getNativeCanvasElement(), + tol, + secondTol + ); +} + +export function createCanvas() { + var node = canvas.createCanvas(300, 300); + node.width = 578 * Konva.pixelRatio; + node.height = 200 * Konva.pixelRatio; + node.getContext('2d').scale(Konva.pixelRatio, Konva.pixelRatio); + return node; +} + +export function showHit(layer) { + if (isNode) { + return; + } + var canvas = layer.hitCanvas._canvas; + canvas.style.position = 'relative'; + + getContainer().appendChild(canvas); +} + +export function simulateMouseDown(stage, pos) { + simulatePointerDown(stage, pos); + var top = isNode ? 0 : stage.content.getBoundingClientRect().top; + + stage._pointerdown({ + clientX: pos.x, + clientY: pos.y + top, + button: pos.button || 0, + type: 'mousedown', + }); +} + +export function simulateMouseMove(stage, pos) { + simulatePointerMove(stage, pos); + var top = isNode ? 0 : stage.content.getBoundingClientRect().top; + var evt = { + clientX: pos.x, + clientY: pos.y + top, + button: pos.button || 0, + type: 'mousemove', + }; + + Konva.DD._drag(evt); + stage._pointermove(evt); +} + +export function simulateMouseUp(stage, pos) { + simulatePointerUp(stage, pos); + var top = isNode ? 0 : stage.content.getBoundingClientRect().top; + var evt = { + clientX: pos.x, + clientY: pos.y + top, + button: pos.button || 0, + type: 'mouseup', + }; + + Konva.DD._endDragBefore(evt); + stage._pointerup(evt); + Konva.DD._endDragAfter(evt); +} + +export function simulateTouchStart(stage, pos, changed?) { + var top = isNode ? 0 : stage.content.getBoundingClientRect().top; + var touches; + var changedTouches; + if (Array.isArray(pos)) { + touches = pos.map(function (touch) { + return { + identifier: touch.id, + clientX: touch.x, + clientY: touch.y + top, + }; + }); + changedTouches = (changed || pos).map(function (touch) { + return { + identifier: touch.id, + clientX: touch.x, + clientY: touch.y + top, + }; + }); + } else { + changedTouches = touches = [ + { + clientX: pos.x, + clientY: pos.y + top, + id: 0, + }, + ]; + } + var evt = { + touches: touches, + changedTouches: changedTouches, + type: 'touchstart', + }; + + stage._pointerdown(evt); +} + +export function simulateTouchMove(stage, pos, changed?) { + var top = isNode ? 0 : stage.content.getBoundingClientRect().top; + var touches; + var changedTouches; + if (Array.isArray(pos)) { + touches = pos.map(function (touch) { + return { + identifier: touch.id, + clientX: touch.x, + clientY: touch.y + top, + }; + }); + changedTouches = (changed || pos).map(function (touch) { + return { + identifier: touch.id, + clientX: touch.x, + clientY: touch.y + top, + }; + }); + } else { + changedTouches = touches = [ + { + clientX: pos.x, + clientY: pos.y + top, + id: 0, + }, + ]; + } + var evt = { + touches: touches, + changedTouches: changedTouches, + type: 'touchmove', + }; + + stage._pointermove(evt); + Konva.DD._drag(evt); +} + +export function simulateTouchEnd(stage, pos, changed?) { + var top = isNode ? 0 : stage.content.getBoundingClientRect().top; + var touches; + var changedTouches; + if (Array.isArray(pos)) { + touches = pos.map(function (touch) { + return { + identifier: touch.id, + clientX: touch.x, + clientY: touch.y + top, + }; + }); + changedTouches = (changed || pos).map(function (touch) { + return { + identifier: touch.id, + clientX: touch.x, + clientY: touch.y + top, + }; + }); + } else { + changedTouches = touches = [ + { + clientX: pos.x, + clientY: pos.y + top, + id: 0, + }, + ]; + } + var evt = { + touches: touches, + changedTouches: changedTouches, + type: 'touchend', + }; + + Konva.DD._endDragBefore(evt); + stage._pointerup(evt); + Konva.DD._endDragAfter(evt); +} + +export function simulatePointerDown(stage: Stage, pos) { + var top = isNode ? 0 : stage.content.getBoundingClientRect().top; + stage._pointerdown({ + clientX: pos.x, + clientY: pos.y + top, + button: pos.button || 0, + pointerId: pos.pointerId || 1, + type: 'pointerdown', + } as any); +} + +export function simulatePointerMove(stage: Stage, pos) { + var top = isNode ? 0 : stage.content.getBoundingClientRect().top; + var evt = { + clientX: pos.x, + clientY: pos.y + top, + button: pos.button || 0, + pointerId: pos.pointerId || 1, + type: 'pointermove', + }; + + stage._pointermove(evt as any); + // Konva.DD._drag(evt); +} + +export function simulatePointerUp(stage: Stage, pos) { + var top = isNode ? 0 : stage.content.getBoundingClientRect().top; + var evt = { + clientX: pos.x, + clientY: pos.y + top, + button: pos.button || 0, + pointerId: pos.pointerId || 1, + type: 'pointerup', + }; + + // Konva.DD._endDragBefore(evt); + stage._pointerup(evt as any); + // Konva.DD._endDragAfter(evt); +} + +function isClose(a, b) { + return Math.abs(a - b) < 0.000001; +} + +export const assertAlmostEqual = function (val1, val2) { + if (!isClose(val1, val2)) { + throw new Error('Expected ' + val1 + ' to be almost equal to ' + val2); + } +}; + +export const assertAlmostDeepEqual = function (obj1, obj2) { + for (var key1 in obj1) { + assertAlmostEqual(obj1[key1], obj2[key1]); + } +}; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 000000000..0312b5cc1 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "outDir": "lib", + "module": "CommonJS", + "target": "ES2018", + // "sourceMap": true, + "noEmitOnError": true, + "lib": ["ES2019", "dom"], + "moduleResolution": "node", + "declaration": true, + "removeComments": false, + + "strict": true, + "noImplicitAny": false, + "noImplicitThis": false, + "useUnknownInCatchVariables": false, + "skipLibCheck": true, + // probably we would never enable this one + // because it's too strict, konva generates many functions on the runtime + "strictPropertyInitialization": false, + + }, + "include": ["./src/**/*.ts"], +} diff --git a/tsconfig.testing.json b/tsconfig.testing.json new file mode 100644 index 000000000..0177d39c7 --- /dev/null +++ b/tsconfig.testing.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "outDir": "lib", + "target": "ES2015", + "noEmitOnError": true, + "moduleResolution": "node", + "lib": ["ES2015", "dom"] + }, + "include": ["./src/**/*.ts"] +} From a24b30b110193e3065ce1f9c1045413aec9424bf Mon Sep 17 00:00:00 2001 From: Adam Greenan Date: Wed, 11 Dec 2024 13:35:24 +0000 Subject: [PATCH 3/7] Add calculation for letter spacing to better align with css letter-spacing properties --- src/shapes/Text.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/shapes/Text.ts b/src/shapes/Text.ts index dbd3b9485..7097ca088 100644 --- a/src/shapes/Text.ts +++ b/src/shapes/Text.ts @@ -263,7 +263,7 @@ export class Text extends Shape { var lineTranslateY = 0; var obj = textArr[n], text = obj.text, - width = obj.width, + width = obj.width + letterSpacing, lastLine = obj.lastInParagraph, spacesNumber, oneWord, @@ -310,7 +310,7 @@ export class Text extends Shape { spacesNumber = text.split(' ').length - 1; oneWord = spacesNumber === 0; lineWidth = - align === JUSTIFY && !lastLine ? totalWidth - padding * 2 : width; + align === JUSTIFY && !lastLine ? totalWidth - padding * 2 : width; context.lineTo( lineTranslateX + Math.round(lineWidth), translateY + lineTranslateY + yOffset @@ -383,7 +383,8 @@ export class Text extends Shape { return isAuto ? this.getTextWidth() + this.padding() * 2 : this.attrs.width; } getHeight() { - const isAuto = this.attrs.height === AUTO || this.attrs.height === undefined; + const isAuto = + this.attrs.height === AUTO || this.attrs.height === undefined; return isAuto ? this.fontSize() * this.textArr.length * this.lineHeight() + this.padding() * 2 @@ -501,7 +502,9 @@ export class Text extends Shape { this.textArr = []; getDummyContext().font = this._getContextFont(); - const additionalWidth = shouldAddEllipsis ? this._getTextWidth(ELLIPSIS) : 0; + const additionalWidth = shouldAddEllipsis + ? this._getTextWidth(ELLIPSIS) + : 0; for (let i = 0, max = lines.length; i < max; ++i) { let line = lines[i]; From 9e39485e00de6c93d8cd98d33905e3e7b8d69bd6 Mon Sep 17 00:00:00 2001 From: Adam Greenan Date: Wed, 11 Dec 2024 13:37:28 +0000 Subject: [PATCH 4/7] Remove personal prettier changes --- src/shapes/Text.ts | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/shapes/Text.ts b/src/shapes/Text.ts index 7097ca088..662fa5f22 100644 --- a/src/shapes/Text.ts +++ b/src/shapes/Text.ts @@ -309,8 +309,7 @@ export class Text extends Shape { context.moveTo(lineTranslateX, translateY + lineTranslateY + yOffset); spacesNumber = text.split(' ').length - 1; oneWord = spacesNumber === 0; - lineWidth = - align === JUSTIFY && !lastLine ? totalWidth - padding * 2 : width; + lineWidth = align === JUSTIFY && !lastLine ? totalWidth - padding * 2 : width; context.lineTo( lineTranslateX + Math.round(lineWidth), translateY + lineTranslateY + yOffset @@ -383,8 +382,7 @@ export class Text extends Shape { return isAuto ? this.getTextWidth() + this.padding() * 2 : this.attrs.width; } getHeight() { - const isAuto = - this.attrs.height === AUTO || this.attrs.height === undefined; + const isAuto = this.attrs.height === AUTO || this.attrs.height === undefined; return isAuto ? this.fontSize() * this.textArr.length * this.lineHeight() + this.padding() * 2 @@ -502,9 +500,7 @@ export class Text extends Shape { this.textArr = []; getDummyContext().font = this._getContextFont(); - const additionalWidth = shouldAddEllipsis - ? this._getTextWidth(ELLIPSIS) - : 0; + const additionalWidth = shouldAddEllipsis ? this._getTextWidth(ELLIPSIS) : 0; for (let i = 0, max = lines.length; i < max; ++i) { let line = lines[i]; From 3e092392cca1200b6b5e837e473a4124ede32718 Mon Sep 17 00:00:00 2001 From: Adam Greenan Date: Wed, 11 Dec 2024 13:39:27 +0000 Subject: [PATCH 5/7] remove more personal prettier changes --- src/shapes/Text.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/shapes/Text.ts b/src/shapes/Text.ts index 662fa5f22..ba8c23538 100644 --- a/src/shapes/Text.ts +++ b/src/shapes/Text.ts @@ -309,7 +309,8 @@ export class Text extends Shape { context.moveTo(lineTranslateX, translateY + lineTranslateY + yOffset); spacesNumber = text.split(' ').length - 1; oneWord = spacesNumber === 0; - lineWidth = align === JUSTIFY && !lastLine ? totalWidth - padding * 2 : width; + lineWidth = + align === JUSTIFY && !lastLine ? totalWidth - padding * 2 : width; context.lineTo( lineTranslateX + Math.round(lineWidth), translateY + lineTranslateY + yOffset From 505348099d3008af47175447fca61e594757de59 Mon Sep 17 00:00:00 2001 From: Adam Greenan Date: Wed, 11 Dec 2024 13:37:28 +0000 Subject: [PATCH 6/7] Remove personal prettier changes remove more personal prettier changes remove personal prettier changes --- src/shapes/Text.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/shapes/Text.ts b/src/shapes/Text.ts index 7097ca088..da1755ee2 100644 --- a/src/shapes/Text.ts +++ b/src/shapes/Text.ts @@ -310,7 +310,7 @@ export class Text extends Shape { spacesNumber = text.split(' ').length - 1; oneWord = spacesNumber === 0; lineWidth = - align === JUSTIFY && !lastLine ? totalWidth - padding * 2 : width; + align === JUSTIFY && !lastLine ? totalWidth - padding * 2 : width; context.lineTo( lineTranslateX + Math.round(lineWidth), translateY + lineTranslateY + yOffset @@ -383,8 +383,7 @@ export class Text extends Shape { return isAuto ? this.getTextWidth() + this.padding() * 2 : this.attrs.width; } getHeight() { - const isAuto = - this.attrs.height === AUTO || this.attrs.height === undefined; + const isAuto = this.attrs.height === AUTO || this.attrs.height === undefined; return isAuto ? this.fontSize() * this.textArr.length * this.lineHeight() + this.padding() * 2 @@ -502,9 +501,7 @@ export class Text extends Shape { this.textArr = []; getDummyContext().font = this._getContextFont(); - const additionalWidth = shouldAddEllipsis - ? this._getTextWidth(ELLIPSIS) - : 0; + const additionalWidth = shouldAddEllipsis ? this._getTextWidth(ELLIPSIS) : 0; for (let i = 0, max = lines.length; i < max; ++i) { let line = lines[i]; From 7e174b075c6976c988fa8e63e60584181fb324cb Mon Sep 17 00:00:00 2001 From: Anton Lavrevov Date: Mon, 23 Dec 2024 09:25:58 -0500 Subject: [PATCH 7/7] adjust tests --- src/shapes/Text.ts | 9 +++---- test/sandbox.html | 58 +++++++++++++++++++++++++++--------------- test/unit/Text-test.ts | 10 ++++---- 3 files changed, 46 insertions(+), 31 deletions(-) diff --git a/src/shapes/Text.ts b/src/shapes/Text.ts index 4b861d795..284590aec 100644 --- a/src/shapes/Text.ts +++ b/src/shapes/Text.ts @@ -264,7 +264,7 @@ export class Text extends Shape { var lineTranslateY = 0; var obj = textArr[n], text = obj.text, - width = obj.width + letterSpacing, + width = obj.width, lastLine = obj.lastInParagraph, spacesNumber, oneWord, @@ -477,10 +477,9 @@ export class Text extends Shape { _getTextWidth(text: string) { const letterSpacing = this.letterSpacing(); const length = text.length; - return ( - getDummyContext().measureText(text).width + - (length ? letterSpacing * (length - 1) : 0) - ); + // letterSpacing * length is the total letter spacing for the text + // previously we used letterSpacing * (length - 1) but it doesn't match DOM behavior + return getDummyContext().measureText(text).width + letterSpacing * length; } _setTextData() { let lines = this.text().split('\n'), diff --git a/test/sandbox.html b/test/sandbox.html index 2b17ce672..82bf29e92 100644 --- a/test/sandbox.html +++ b/test/sandbox.html @@ -13,6 +13,27 @@ width: 100vw; height: 100vh; } + body { + padding: 0; + margin: 0; + } + + .test { + position: absolute; + color: red; + font-size: 20px; + font-family: Arial; + border: 0; + background-color: transparent; + outline: none; + resize: none; + overflow: hidden; + line-height: 1; + padding: 0px; + letter-spacing: 20px; + width: 500px; + text-align: center; + } diff --git a/test/unit/Text-test.ts b/test/unit/Text-test.ts index 25897e980..206fe0f22 100644 --- a/test/unit/Text-test.ts +++ b/test/unit/Text-test.ts @@ -282,7 +282,7 @@ describe('Text', function () { var oldWidth = text.width(); text.letterSpacing(10); - assert.equal(text.width(), oldWidth + 40); + assert.equal(text.width(), oldWidth + 50); layer.draw(); }); // ====================================================== @@ -780,7 +780,7 @@ describe('Text', function () { } var trace = - 'fillText(;,106.482,77);fillStyle=#555;fillText( ,117.549,77);fillStyle=#555;fillText(A,126.438,77);fillStyle=#555;fillText(n,140.776,77);fillStyle=#555;fillText(d,153.563,77);fillStyle=#555;fillText( ,168.525,77);fillStyle=#555;fillText(o,177.415,77);fillStyle=#555;fillText(n,190.201,77);fillStyle=#555;fillText(e,202.987,77);fillStyle=#555;fillText( ,217.95,77);fillStyle=#555;fillText(m,226.84,77);fillStyle=#555;fillText(a,243.502,77);fillStyle=#555;fillText(n,256.288,77);fillStyle=#555;fillText( ,271.251,77);fillStyle=#555;fillText(i,280.141,77);fillStyle=#555;fillText(n,288.251,77);fillStyle=#555;fillText( ,303.214,77);fillStyle=#555;fillText(h,312.104,77);fillStyle=#555;fillText(i,324.89,77);fillStyle=#555;fillText(s,333,77);restore();save();save();beginPath();moveTo(0,98);lineTo(245,98);stroke();restore();save();beginPath();moveTo(0,91);lineTo(245,91);stroke();restore();fillStyle=#555;fillText(t,0,91);fillStyle=#555;fillText(i,8.89,91);fillStyle=#555;fillText(m,17,91);fillStyle=#555;fillText(e,33.662,91);fillStyle=#555;fillText( ,46.448,91);fillStyle=#555;fillText(p,55.338,91);fillStyle=#555;fillText(l,68.124,91);fillStyle=#555;fillText(a,76.234,91);fillStyle=#555;fillText(y,89.021,91);fillStyle=#555;fillText(s,101.021,91);fillStyle=#555;fillText( ,113.021,91);fillStyle=#555;fillText(m,121.91,91);fillStyle=#555;fillText(a,138.572,91);fillStyle=#555;fillText(n,151.358,91);fillStyle=#555;fillText(y,164.145,91);fillStyle=#555;fillText( ,176.145,91);fillStyle=#555;fillText(p,185.034,91);fillStyle=#555;fillText(a,197.82,91);fillStyle=#555;fillText(r,210.606,91);fillStyle=#555;fillText(t,220.269,91);fillStyle=#555;fillText(s,229.158,91);fillStyle=#555;fillText(.,241.158,91);restore();restore();'; + 'fillText(;,106.482,77);fillStyle=#555;fillText( ,116.549,77);fillStyle=#555;fillText(A,125.438,77);fillStyle=#555;fillText(n,139.776,77);fillStyle=#555;fillText(d,152.563,77);fillStyle=#555;fillText( ,166.525,77);fillStyle=#555;fillText(o,175.415,77);fillStyle=#555;fillText(n,188.201,77);fillStyle=#555;fillText(e,200.987,77);fillStyle=#555;fillText( ,214.95,77);fillStyle=#555;fillText(m,223.84,77);fillStyle=#555;fillText(a,240.502,77);fillStyle=#555;fillText(n,253.288,77);fillStyle=#555;fillText( ,267.251,77);fillStyle=#555;fillText(i,276.141,77);fillStyle=#555;fillText(n,284.251,77);fillStyle=#555;fillText( ,298.214,77);fillStyle=#555;fillText(h,307.104,77);fillStyle=#555;fillText(i,319.89,77);fillStyle=#555;fillText(s,328,77);restore();save();save();beginPath();moveTo(0,98);lineTo(250,98);stroke();restore();save();beginPath();moveTo(0,91);lineTo(250,91);stroke();restore();fillStyle=#555;fillText(t,0,91);fillStyle=#555;fillText(i,8.89,91);fillStyle=#555;fillText(m,17,91);fillStyle=#555;fillText(e,33.662,91);fillStyle=#555;fillText( ,46.448,91);fillStyle=#555;fillText(p,55.338,91);fillStyle=#555;fillText(l,68.124,91);fillStyle=#555;fillText(a,76.234,91);fillStyle=#555;fillText(y,89.021,91);fillStyle=#555;fillText(s,101.021,91);fillStyle=#555;fillText( ,113.021,91);fillStyle=#555;fillText(m,121.91,91);fillStyle=#555;fillText(a,138.572,91);fillStyle=#555;fillText(n,151.358,91);fillStyle=#555;fillText(y,164.145,91);fillStyle=#555;fillText( ,176.145,91);fillStyle=#555;fillText(p,185.034,91);fillStyle=#555;fillText(a,197.82,91);fillStyle=#555;fillText(r,210.606,91);fillStyle=#555;fillText(t,220.269,91);fillStyle=#555;fillText(s,229.158,91);fillStyle=#555;fillText(.,241.158,91);restore();restore();'; assert.equal(layer.getContext().getTrace(), trace); }); @@ -1244,9 +1244,9 @@ describe('Text', function () { // so we need to adjust offset const diff = isBrowser ? 4 : 50; assert.equal(Math.abs(Math.round(text1.width()) - 1725) < diff, true); - assert.equal(Math.abs(Math.round(text2.width()) - 2613) < diff, true); - assert.equal(Math.abs(Math.round(text3.width()) - 2005) < diff, true); - assert.equal(Math.abs(Math.round(text4.width()) - 1932) < diff, true); + assert.equal(Math.abs(Math.round(text2.width()) - 2616) < diff, true); + assert.equal(Math.abs(Math.round(text3.width()) - 2009) < diff, true); + assert.equal(Math.abs(Math.round(text4.width()) - 1936) < diff, true); }); it('default text color should be black', function () {