MinsoftK

add test repo

Showing 354 changed files with 29301 additions and 0 deletions
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
4 */ 4 */
5 import React from 'react'; 5 import React from 'react';
6 import TuiImageEditor from 'tui-image-editor'; 6 import TuiImageEditor from 'tui-image-editor';
7 +import 'tui-image-editor/dist/tui-image-editor.css'
8 +import ImageEditor from '@toast-ui/react-image-editor'
7 9
8 export default class ImageEditor extends React.Component { 10 export default class ImageEditor extends React.Component {
9 rootEl = React.createRef(); 11 rootEl = React.createRef();
......
1 +{
2 + "presets": ["es2015"],
3 + "plugins": [
4 + ["transform-es2015-destructuring", { "loose": true }],
5 + ["transform-es2015-for-of", { "loose": true }],
6 + ["transform-es2015-spread", { "loose": true }]
7 + ]
8 +}
1 +[*]
2 +charset = utf-8
3 +indent_style = space
4 +indent_size = 4
5 +end_of_line = lf
...\ No newline at end of file ...\ No newline at end of file
1 +# Ignore polyfill
2 +src/js/polyfill.js
1 +module.exports = {
2 + extends: ['tui/es6', 'plugin:prettier/recommended'],
3 + plugins: ['prettier'],
4 + env: {
5 + browser: true,
6 + amd: true,
7 + node: true,
8 + jasmine: true,
9 + jquery: true,
10 + es6: true,
11 + },
12 + globals: {
13 + fabric: true,
14 + tui: true,
15 + loadFixtures: true,
16 + },
17 + parserOptions: {
18 + sourceType: 'module',
19 + },
20 + rules: {
21 + 'prefer-destructuring': [
22 + 'error',
23 + {
24 + VariableDeclarator: { array: true, object: true },
25 + AssignmentExpression: { array: false, object: false },
26 + },
27 + ],
28 + 'prettier/prettier': 'error',
29 + },
30 +};
1 +# Logs
2 +logs
3 +*.log
4 +
5 +# Runtime data
6 +pids
7 +*.pid
8 +*.seed
9 +
10 +# Directory for instrumented libs generated by jscoverage/JSCover
11 +lib-cov
12 +
13 +# Coverage directory used by tools like istanbul
14 +coverage
15 +
16 +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
17 +.grunt
18 +
19 +# Compiled binary addons (http://nodejs.org/api/addons.html)
20 +build/Release
21 +
22 +# Dependency directory
23 +# Deployed apps should consider commenting this line out:
24 +# see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git
25 +node_modules
26 +
27 +# Bower Components
28 +bower_components
29 +lib
30 +
31 +# IDEA
32 +.idea
33 +*.iml
34 +
35 +# Window
36 +Thumbs.db
37 +Desktop.ini
38 +
39 +# MAC
40 +.DS_Store
41 +
42 +# SVN
43 +.svn
44 +
45 +# eclipse
46 +.project
47 +.metadata
48 +
49 +# etc
50 +temp
51 +doc
52 +demo
53 +report
54 +*.vim
55 +test.html
56 +
57 +# Compiled files
58 +dist
1 +{
2 + "singleQuote": true,
3 + "printWidth": 100,
4 + "tabWidth": 2,
5 + "useTabs": false,
6 + "semi": true,
7 + "quoteProps": "as-needed",
8 + "trailingComma": "es5",
9 + "arrowParens": "always",
10 + "endOfLine": "lf",
11 + "bracketSpacing": true,
12 + "proseWrap": "preserve"
13 +}
1 +# Contributor Covenant Code of Conduct
2 +
3 +## Our Pledge
4 +
5 +In the interest of fostering an open and welcoming environment, we as
6 +contributors and maintainers pledge to making participation in our project and
7 +our community a harassment-free experience for everyone, regardless of age, body
8 +size, disability, ethnicity, gender identity and expression, level of experience,
9 +education, socio-economic status, nationality, personal appearance, race,
10 +religion, or sexual identity and orientation.
11 +
12 +## Our Standards
13 +
14 +Examples of behavior that contributes to creating a positive environment
15 +include:
16 +
17 +- Using welcoming and inclusive language
18 +- Being respectful of differing viewpoints and experiences
19 +- Gracefully accepting constructive criticism
20 +- Focusing on what is best for the community
21 +- Showing empathy towards other community members
22 +
23 +Examples of unacceptable behavior by participants include:
24 +
25 +- The use of sexualized language or imagery and unwelcome sexual attention or
26 + advances
27 +- Trolling, insulting/derogatory comments, and personal or political attacks
28 +- Public or private harassment
29 +- Publishing others' private information, such as a physical or electronic
30 + address, without explicit permission
31 +- Other conduct which could reasonably be considered inappropriate in a
32 + professional setting
33 +
34 +## Our Responsibilities
35 +
36 +Project maintainers are responsible for clarifying the standards of acceptable
37 +behavior and are expected to take appropriate and fair corrective action in
38 +response to any instances of unacceptable behavior.
39 +
40 +Project maintainers have the right and responsibility to remove, edit, or
41 +reject comments, commits, code, wiki edits, issues, and other contributions
42 +that are not aligned to this Code of Conduct, or to ban temporarily or
43 +permanently any contributor for other behaviors that they deem inappropriate,
44 +threatening, offensive, or harmful.
45 +
46 +## Scope
47 +
48 +This Code of Conduct applies both within project spaces and in public spaces
49 +when an individual is representing the project or its community. Examples of
50 +representing a project or community include using an official project e-mail
51 +address, posting via an official social media account, or acting as an appointed
52 +representative at an online or offline event. Representation of a project may be
53 +further defined and clarified by project maintainers.
54 +
55 +## Enforcement
56 +
57 +Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 +reported by contacting the project team at dl_javascript@nhn.com. All
59 +complaints will be reviewed and investigated and will result in a response that
60 +is deemed necessary and appropriate to the circumstances. The project team is
61 +obligated to maintain confidentiality with regard to the reporter of an incident.
62 +Further details of specific enforcement policies may be posted separately.
63 +
64 +Project maintainers who do not follow or enforce the Code of Conduct in good
65 +faith may face temporary or permanent repercussions as determined by other
66 +members of the project's leadership.
67 +
68 +## Attribution
69 +
70 +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 +
73 +[homepage]: https://www.contributor-covenant.org
1 +# Contributing to TOAST UI
2 +
3 +First off, thanks for taking the time to contribute! 🎉 😘 ✨
4 +
5 +The following is a set of guidelines for contributing to TOAST UI. These are mostly guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request.
6 +
7 +## Reporting Bugs
8 +
9 +Bugs are tracked as GitHub issues. Search the list and try reproduce on [demo][demo] before you create an issue. When you create an issue, please provide the following information by filling in the template.
10 +
11 +Explain the problem and include additional details to help maintainers reproduce the problem:
12 +
13 +- **Use a clear and descriptive title** for the issue to identify the problem.
14 +- **Describe the exact steps which reproduce the problem** in as many details as possible. Don't just say what you did, but explain how you did it. For example, if you moved the cursor to the end of a line, explain if you used a mouse or a keyboard.
15 +- **Provide specific examples to demonstrate the steps.** Include links to files or GitHub projects, or copy/pasteable snippets, which you use in those examples. If you're providing snippets on the issue, use Markdown code blocks.
16 +- **Describe the behavior you observed after following the steps** and point out what exactly is the problem with that behavior.
17 +- **Explain which behavior you expected to see instead and why.**
18 +- **Include screenshots and animated GIFs** which show you following the described steps and clearly demonstrate the problem.
19 +
20 +## Suggesting Enhancements
21 +
22 +In case you want to suggest for TOAST UI ImageEditor, please follow this guideline to help maintainers and the community understand your suggestion.
23 +Before creating suggestions, please check [issue list](../../labels/enhancement) if there's already a request.
24 +
25 +Create an issue and provide the following information:
26 +
27 +- **Use a clear and descriptive title** for the issue to identify the suggestion.
28 +- **Provide a step-by-step description of the suggested enhancement** in as many details as possible.
29 +- **Provide specific examples to demonstrate the steps.** Include copy/pasteable snippets which you use in those examples, as Markdown code blocks.
30 +- **Include screenshots and animated GIFs** which helps demonstrate the steps or point out the part of TOAST UI ImageEditor which the suggestion is related to.
31 +- **Explain why this enhancement would be useful** to most TOAST UI users.
32 +- **List some other image editors or applications where this enhancement exists.**
33 +
34 +## First Code Contribution
35 +
36 +Unsure where to begin contributing to TOAST UI? You can start by looking through these `document`, `good first issue` and `help wanted` issues:
37 +
38 +- **document issues**: issues which should be reviewed or improved.
39 +- **good first issues**: issues which should only require a few lines of code, and a test or two.
40 +- **help wanted issues**: issues which should be a bit more involved than beginner issues.
41 +
42 +## Pull Requests
43 +
44 +### Development WorkFlow
45 +
46 +- Set up your development environment
47 +- Make change from a right branch
48 +- Be sure the code passes `npm run lint`, `npm run test`
49 +- Make a pull request
50 +
51 +### Development environment
52 +
53 +- Prepare your machine node and it's packages installed.
54 +- Checkout our repository
55 +- Install dependencies by `npm install && bower install`
56 +- Start webpack-dev-server by `npm run serve`
57 +
58 +### Make changes
59 +
60 +#### Checkout a branch
61 +
62 +- **develop**: PR base branch. merge features, updates for next minor or major release
63 +- **master**: bug fix or document update for next patch release. develop branch will rebase every time master branch update. so keep code change to a minimum.
64 +- **production**: lastest release branch with distribution files. never make a PR on this
65 +- **gh-pages**: API docs, examples and demo
66 +
67 +#### Check Code Style
68 +
69 +Run `npm run eslint` and make sure all the tests pass.
70 +
71 +#### Test
72 +
73 +Run `npm run test` and verify all the tests pass.
74 +If you are adding new commands or features, they must include tests.
75 +If you are changing functionality, update the tests if you need to.
76 +
77 +#### Commit
78 +
79 +Follow our [commit message conventions](./docs/COMMIT_MESSAGE_CONVENTION.md).
80 +
81 +### Yes! Pull request
82 +
83 +Make your pull request, then describe your changes.
84 +
85 +#### Title
86 +
87 +Follow other PR title format on below.
88 +
89 +```
90 + <Type>: Short Description (fix #111)
91 + <Type>: Short Description (fix #123, #111, #122)
92 + <Type>: Short Description (ref #111)
93 +```
94 +
95 +- capitalize first letter of Type
96 +- use present tense: 'change' not 'changed' or 'changes'
97 +
98 +#### Description
99 +
100 +If it has related to issues, add links to the issues(like `#123`) in the description.
101 +Fill in the [Pull Request Template](./docs/PULL_REQUEST_TEMPLATE.md) by check your case.
102 +
103 +## Code of Conduct
104 +
105 +This project and everyone participating in it is governed by the [Code of Conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. Please report unacceptable behavior to dl_javascript@nhn.com.
106 +
107 +> This Guide is base on [atom contributing guide](https://github.com/atom/atom/blob/master/CONTRIBUTING.md), [CocoaPods](http://guides.cocoapods.org/contributing/contribute-to-cocoapods.html) and [ESLint](http://eslint.org/docs/developer-guide/contributing/pull-requests)
1 +<!--
2 +Thank you for your contribution.
3 +
4 +When it comes to write an issue, please, use the template below.
5 +To use the template is mandatory for submit new issue and we won't reply the issue that without the template.
6 +
7 +And you can write template's contents in Korean also.
8 +-->
9 +
10 +<!-- TEMPLATE -->
11 +
12 +## Version
13 +
14 +<!-- Write the version of the grid you are currently using. -->
15 +
16 +## Development Environment
17 +
18 +<!-- Write the browser type, OS and so on -->
19 +
20 +## Current Behavior
21 +
22 +<!-- Write a description of the current operation. You can add example code, 'CodePen' or 'jsfiddle' links. -->
23 +
24 +```js
25 +// Write example code
26 +```
27 +
28 +## Expected Behavior
29 +
30 +<!-- Write a description of the future action. -->
1 +
2 +The MIT License
3 +
4 +Copyright (c) 2019 NHN Corp.
5 +
6 +Permission is hereby granted, free of charge, to any person obtaining a copy
7 +of this software and associated documentation files (the "Software"), to deal
8 +in the Software without restriction, including without limitation the rights
9 +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 +copies of the Software, and to permit persons to whom the Software is
11 +furnished to do so, subject to the following conditions:
12 +
13 +The above copyright notice and this permission notice shall be included in
14 +all copies or substantial portions of the Software.
15 +
16 +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 +THE SOFTWARE.
1 +# ![Toast UI ImageEditor](https://user-images.githubusercontent.com/35218826/40895380-0b9f4cd6-67ea-11e8-982f-18121daa3a04.png)
2 +
3 +> Full featured image editor using HTML5 Canvas. It's easy to use and provides powerful filters.
4 +
5 +[![github version](https://img.shields.io/github/release/nhn/tui.image-editor.svg)](https://github.com/nhn/tui.image-editor/releases/latest) [![npm version](https://img.shields.io/npm/v/tui-image-editor.svg)](https://www.npmjs.com/package/tui-image-editor) [![bower version](https://img.shields.io/bower/v/tui.image-editor.svg)](https://github.com/nhn/tui.image-editor/releases/latest) [![license](https://img.shields.io/github/license/nhn/tui.image-editor.svg)](https://github.com/nhn/tui.image-editor/blob/master/LICENSE) [![PRs welcome](https://img.shields.io/badge/PRs-welcome-ff69b4.svg)](https://github.com/nhn/tui.image-editor/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22)[![code with hearth by NHN](https://img.shields.io/badge/%3C%2F%3E%20with%20%E2%99%A5%20by-NHN%20Entertainment-ff1414.svg)](https://github.com/nhn)
6 +
7 +## Wrappers
8 +
9 +- [toast-ui.vue-image-editor](https://github.com/nhn/toast-ui.vue-image-editor): Vue wrapper component is powered by [NHN](https://github.com/nhn).
10 +- [toast-ui.react-image-editor](https://github.com/nhn/toast-ui.react-image-editor): React wrapper component is powered by [NHN](https://github.com/nhn).
11 +
12 +![6 -20-2018 17-45-54](https://user-images.githubusercontent.com/35218826/41647896-7b218ae0-74b2-11e8-90db-d7805cc23e8c.gif)
13 +
14 +## 🚩 Table of Contents
15 +
16 +- [Collect statistics on the use of open source](#Collect-statistics-on-the-use-of-open-source)
17 +- [Browser Support](#-browser-support)
18 +- [Has full features that stick to the basic.](#-has-full-features-that-stick-to-the-basic)
19 + - [Photo manipulation](#photo-manipulation)
20 + - [Integration function](#integration-function)
21 + - [Powerful filter function](#powerful-filter-function)
22 + - [Select only the desired function](#select-only-the-desired-function)
23 +- [Easy to apply the size and design you want](#-easy-to-apply-the-size-and-design-you-want)
24 + - [Can be used everywhere](#can-be-used-everywhere)
25 + - [Nice default & Fully customizable Themes](#nice-default--fully-customizable-themes)
26 +- [Features](#-features)
27 +- [Install](#-install)
28 + - [Via Package Manager](#via-package-manager)
29 + - [Via Contents Delivery Network (CDN)](#via-contents-delivery-network-cdn)
30 + - [Download Source Files](#download-source-files)
31 +- [Usage](#-usage)
32 + - [HTML](#html)
33 + - [JavaScript](#javascript)
34 + - [Menu svg icon setting](#menu-svg-icon-setting)
35 + - [TypeScript](#typescript)
36 +- [Development](#-development)
37 + - [Setup](#setup)
38 + - [Run webpack-dev-server](#run-webpack-dev-server)
39 +- [Documents](#-documents)
40 +- [Contributing](#-contributing)
41 +- [Dependency](#-dependency)
42 +- [TOAST UI Family](#-toast-ui-family)
43 +- [Used By](#-used-by)
44 +- [License](#-license)
45 +
46 +## Collect statistics on the use of open source
47 +
48 +TOAST UI ImageEditor applies Google Analytics (GA) to collect statistics on the use of open source, in order to identify how widely TOAST UI ImageEditor is used throughout the world. It also serves as important index to determine the future course of projects. location.hostname (e.g. > “ui.toast.com") is to be collected and the sole purpose is nothing but to measure statistics on the usage. To disable GA, use the following `usageStatistics` option when creating the instance.
49 +
50 +```js
51 +var options = {
52 + //...
53 + usageStatistics: false,
54 +};
55 +
56 +var imageEditor = new tui.ImageEditor('#tui-image-editor-container', options);
57 +```
58 +
59 +Or, include [`tui-code-snippet`](https://github.com/nhn/tui.code-snippet)(**v1.4.0** or **later**) and then immediately write the options as follows:
60 +
61 +```js
62 +tui.usageStatistics = false;
63 +```
64 +
65 +## 🌏 Browser Support
66 +
67 +| <img src="https://user-images.githubusercontent.com/1215767/34348387-a2e64588-ea4d-11e7-8267-a43365103afe.png" alt="Chrome" width="16px" height="16px" /> Chrome | <img src="https://user-images.githubusercontent.com/1215767/34348590-250b3ca2-ea4f-11e7-9efb-da953359321f.png" alt="IE" width="16px" height="16px" /> Internet Explorer | <img src="https://user-images.githubusercontent.com/1215767/34348380-93e77ae8-ea4d-11e7-8696-9a989ddbbbf5.png" alt="Edge" width="16px" height="16px" /> Edge | <img src="https://user-images.githubusercontent.com/1215767/34348394-a981f892-ea4d-11e7-9156-d128d58386b9.png" alt="Safari" width="16px" height="16px" /> Safari | <img src="https://user-images.githubusercontent.com/1215767/34348383-9e7ed492-ea4d-11e7-910c-03b39d52f496.png" alt="Firefox" width="16px" height="16px" /> Firefox |
68 +| :--------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------: |
69 +| Yes | 10+ | Yes | Yes | Yes |
70 +
71 +## 💪 Has full features that stick to the basic.
72 +
73 +### Photo manipulation
74 +
75 +- Crop, Flip, Rotation, Drawing, Shape, Icon, Text, Mask Filter, Image Filter
76 +
77 +### Integration function
78 +
79 +- Download, Image Load, Undo, Redo, Reset, Delete Object(Shape, Line, Mask Image...)
80 +
81 +<table>
82 + <tbody>
83 + <tr>
84 + <th width="20%">Crop</th>
85 + <th width="20%">Flip</th>
86 + <th width="20%">Rotation</th>
87 + <th width="20%">Drawing</th>
88 + <th width="20%">Shape</th>
89 + </tr>
90 + <tr>
91 + <td><img src="https://user-images.githubusercontent.com/35218826/40904241-0c28ec68-6815-11e8-8296-89a1716b22d8.png" alt="2018-06-04 4 33 16" style="max-width:100%;"></td>
92 + <td><img src="https://user-images.githubusercontent.com/35218826/40904521-f7c6e184-6815-11e8-8ba3-c94664da69a2.png" alt="2018-06-04 4 40 06" style="max-width:100%;"></td>
93 + <td><img src="https://user-images.githubusercontent.com/35218826/40904664-656aa748-6816-11e8-9943-6607c209deac.png" alt="2018-06-04 4 43 02" style="max-width:100%;"></td>
94 + <td><img src="https://user-images.githubusercontent.com/35218826/40904850-0f26ebde-6817-11e8-97d0-d3a7e4bc02da.png" alt="2018-06-04 4 47 40" style="max-width:100%;"></td>
95 + <td><img src="https://user-images.githubusercontent.com/35218826/40905037-a026296a-6817-11e8-9d28-9e1ca7bc58c4.png" alt="2018-06-04 4 51 45" style="max-width:100%;"></td>
96 + </tr>
97 + <tr>
98 + <th>Icon</th>
99 + <th>Text</th>
100 + <th>Mask</th>
101 + <th>Filter</th>
102 + <th></th>
103 + </tr>
104 + <tr>
105 + <td><img src="https://user-images.githubusercontent.com/35218826/40931205-2d255db6-6865-11e8-98af-ad34c5a01da1.png" alt="2018-06-05 2 06 29" style="max-width:100%;"></td>
106 + <td><img src="https://user-images.githubusercontent.com/35218826/40931484-46253948-6866-11e8-8a04-fa042920e457.png" alt="2018-06-05 2 14 36" style="max-width:100%;"></td>
107 + <td><img src="https://user-images.githubusercontent.com/35218826/40931743-21eeb346-6867-11e8-8e31-a59f7a43482b.png" alt="2018-06-05 2 20 46" style="max-width:100%;"></td>
108 + <td><img src="https://user-images.githubusercontent.com/35218826/40932016-093ed1f4-6868-11e8-8224-a048c3ee8a09.png" alt="2018-06-05 2 27 10" style="max-width:100%;"></td>
109 + <td></td>
110 + </tr>
111 + </tbody>
112 +</table>
113 +
114 +### Powerful filter function
115 +
116 +- Grayscale, Invert, Sepia, Blur Sharpen, Emboss, RemoveWhite, Brightness, Noise, Pixelate, ColorFilter, Tint, Multiply, Blend
117 +
118 +| Grayscale | Noise | Emboss | Pixelate |
119 +| ------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
120 +| ![grayscale](https://user-images.githubusercontent.com/35218826/41753470-930fb7b0-7608-11e8-9966-1c890e73d131.png) | ![noise](https://user-images.githubusercontent.com/35218826/41753458-9013bc82-7608-11e8-91d9-74dcc3ffce31.png) | ![emboss](https://user-images.githubusercontent.com/35218826/41753460-906c018a-7608-11e8-8861-c135c0117cea.png) | ![pixelate](https://user-images.githubusercontent.com/35218826/41753461-90a614a6-7608-11e8-97a7-0d3b7bb4aec4.png) |
121 +
122 +| Sepia | Sepia2 | Blend-righten | Blend-diff | Invert |
123 +| -------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
124 +| ![sepia](https://user-images.githubusercontent.com/35218826/41753464-91acc41c-7608-11e8-8652-572f935ea704.png) | ![sepia2](https://user-images.githubusercontent.com/35218826/41753640-91e57248-7609-11e8-8960-145e0de57e39.png) | ![blend-righten](https://user-images.githubusercontent.com/35218826/41753462-9114bc3a-7608-11e8-9ab4-16ce20a34321.png) | ![blend-diff](https://user-images.githubusercontent.com/35218826/41753465-91e4baf2-7608-11e8-9b8f-79e1b956d387.png) | ![invert](https://user-images.githubusercontent.com/35218826/41753466-9260b224-7608-11e8-848a-73231a02ae3a.png) |
125 +
126 +| Multifly | Tint | Brightness | Remove-white | Sharpen |
127 +| ----------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
128 +| ![multifly](https://user-images.githubusercontent.com/35218826/41753467-92baae28-7608-11e8-80d2-187a310213f5.png) | ![tint](https://user-images.githubusercontent.com/35218826/41753468-92e6391c-7608-11e8-8977-651366ebe693.png) | ![brightness](https://user-images.githubusercontent.com/35218826/41753457-8fb3d3c6-7608-11e8-9e1d-10c6e4aeba68.png) | ![remove-white](https://user-images.githubusercontent.com/35218826/41753463-917feeb0-7608-11e8-862d-eb3af84e120a.png) | ![sharpen](https://user-images.githubusercontent.com/35218826/41753639-91b8470a-7609-11e8-8d13-48ac3232365b.png) |
129 +
130 +### Select only the desired function
131 +
132 +```javascripot
133 +var imageEditor = new tui.ImageEditor('#tui-image-editor-container', {
134 + includeUI: {
135 + menu: ['shape', 'crop']
136 + ...
137 + },
138 + ...
139 +```
140 +
141 +## 🙆 Easy to apply the size and design you want
142 +
143 +### Can be used everywhere.
144 +
145 +- Widely supported in browsers including IE10.
146 +- Option to support various display sizes.
147 + (allows you to use the editor features on your web pages at least over **550 \* 450 sizes**)
148 +
149 + ![2018-06-04 5 35 25](https://user-images.githubusercontent.com/35218826/40907369-9221f482-681e-11e8-801c-78d6f2e246a8.png)
150 +
151 +### Nice default & Fully customizable Themes
152 +
153 +- Has a white and black theme, and you can modify the theme file to customize it.
154 +- Has an API so that you can create your own instead of the built-in.
155 +
156 +| black - top | black - bottom | white - left | white - right |
157 +| --------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
158 +| ![2018-06-05 1 41 13](https://user-images.githubusercontent.com/35218826/40930753-8b72502e-6863-11e8-9cff-1719aee9aef0.png) | ![2018-06-05 1 40 24](https://user-images.githubusercontent.com/35218826/40930755-8bcee136-6863-11e8-8e28-0a6722d38c59.png) | ![2018-06-05 1 41 48](https://user-images.githubusercontent.com/35218826/40930756-8bfe0b50-6863-11e8-8682-bab11a0a2289.png) | ![2018-06-05 1 42 27](https://user-images.githubusercontent.com/35218826/40930754-8ba1dba0-6863-11e8-9439-cc059241b675.png) |
159 +
160 +## 🎨 Features
161 +
162 +- Load image to canvas
163 +- Undo/Redo (With shortcut)
164 +- Crop
165 +- Flip
166 +- Rotation
167 +- Free drawing
168 +- Line drawing
169 +- Shape
170 +- Icon
171 +- Text
172 +- Mask Filter
173 +- Image Filter
174 +
175 +## 💾 Install
176 +
177 +The TOAST UI products can be installed by using the package manager or downloading the source directly.
178 +However, we highly recommend using the package manager.
179 +
180 +### Via Package Manager
181 +
182 +You can find TOAST UI producs via [npm](https://www.npmjs.com/) and [bower](https://bower.io/) package managers.
183 +Install by using the commands provided by each package manager.
184 +When using npm, be sure [Node.js](https://nodejs.org) is installed in the environment.
185 +
186 +#### npm
187 +
188 +#### 1. ImageEditor installation
189 +
190 +```sh
191 +$ npm install --save tui-image-editor # Latest version
192 +$ npm install --save tui-image-editor@<version> # Specific version
193 +```
194 +
195 +##### 2. If the installation of the `fabric.js` dependency module does not go smoothly
196 +
197 +To solve the problem, you need to refer to [Some Steps](https://github.com/fabricjs/fabric.js#install-with-npm) to solve the problem.
198 +
199 +#### bower
200 +
201 +```sh
202 +$ bower install tui-image-editor # Latest version
203 +$ bower install tui-image-editor#<tag> # Specific version
204 +```
205 +
206 +### Via Contents Delivery Network (CDN)
207 +
208 +TOAST UI products are available over the CDN powered by [TOAST Cloud](https://www.toast.com).
209 +
210 +You can use the CDN as below.
211 +
212 +```html
213 +<link
214 + rel="stylesheet"
215 + href="https://uicdn.toast.com/tui-image-editor/latest/tui-image-editor.css"
216 +/>
217 +<script src="https://uicdn.toast.com/tui-image-editor/latest/tui-image-editor.js"></script>
218 +```
219 +
220 +If you want to use a specific version, use the tag name instead of `latest` in the URL.
221 +
222 +The CDN directory has the following structure.
223 +
224 +```
225 +tui-image-editor/
226 +├─ latest/
227 +│ ├─ tui-image-editor.js
228 +│ ├─ tui-image-editor.min.js
229 +│ └─ tui-image-editor.css
230 +├─ v3.1.0/
231 +│ ├─ ...
232 +```
233 +
234 +### Download Source Files
235 +
236 +- [Download bundle files from `dist` folder](https://github.com/nhn/tui.image-editor/tree/production/dist)
237 +- [Download all sources for each version](https://github.com/nhn/tui.image-editor/releases)
238 +
239 +## 🔨 Usage
240 +
241 +### HTML
242 +
243 +Add the container element where TOAST UI ImageEditor will be created.
244 +
245 +```html
246 +<body>
247 + ...
248 + <div id="tui-image-editor"></div>
249 + ...
250 +</body>
251 +```
252 +
253 +### javascript
254 +
255 +Add dependencies & initialize ImageEditor class with given element to make an image editor.
256 +
257 +```javascript
258 +var ImageEditor = require('tui-image-editor');
259 +var FileSaver = require('file-saver'); //to download edited image to local. Use after npm install file-saver
260 +var blackTheme = require('./js/theme/black-theme.js');
261 +var locale_ru_RU = {
262 + // override default English locale to your custom
263 + Crop: 'Обзрезать',
264 + 'Delete-all': 'Удалить всё',
265 + // etc...
266 +};
267 +var instance = new ImageEditor(document.querySelector('#tui-image-editor'), {
268 + includeUI: {
269 + loadImage: {
270 + path: 'img/sampleImage.jpg',
271 + name: 'SampleImage',
272 + },
273 + locale: locale_ru_RU,
274 + theme: blackTheme, // or whiteTheme
275 + initMenu: 'filter',
276 + menuBarPosition: 'bottom',
277 + },
278 + cssMaxWidth: 700,
279 + cssMaxHeight: 500,
280 + selectionStyle: {
281 + cornerSize: 20,
282 + rotatingPointOffset: 70,
283 + },
284 +});
285 +```
286 +
287 +Or ~ UI
288 +
289 +```javascript
290 +var ImageEditor = require('tui-image-editor');
291 +var instance = new ImageEditor(document.querySelector('#tui-image-editor'), {
292 + cssMaxWidth: 700,
293 + cssMaxHeight: 500,
294 + selectionStyle: {
295 + cornerSize: 20,
296 + rotatingPointOffset: 70,
297 + },
298 +});
299 +```
300 +
301 +### Menu svg icon setting
302 +
303 +#### There are two ways to set icons.
304 +
305 +1. **Use default svg built** into imageEditor without setting svg file path (Features added since version v3.9.0).
306 +2. There is a way to use the **actual physical svg file** and **set the file location manually**.
307 +
308 +Can find more details in [this document](https://github.com/nhn/tui.image-editor/blob/master/docs/Basic-Tutorial.md#4-menu-submenu-svg-icon-setting).
309 +
310 +### TypeScript
311 +
312 +If you using TypeScript, You must `import module = require('module')` on importing.
313 +[`export =` and `import = require()`](https://www.typescriptlang.org/docs/handbook/modules.html#export--and-import--require)
314 +
315 +```typescript
316 +import ImageEditor = require('tui-image-editor');
317 +var FileSaver = require('file-saver'); //to download edited image to local. Use after npm install file-saver
318 +
319 +const instance = new ImageEditor(document.querySelector('#tui-image-editor'), {
320 + cssMaxWidth: 700,
321 + cssMaxHeight: 500,
322 + selectionStyle: {
323 + cornerSize: 20,
324 + rotatingPointOffset: 70,
325 + },
326 +});
327 +```
328 +
329 +See [details](https://nhn.github.io/tui.image-editor/latest) for additional informations.
330 +
331 +## 🔧 Development
332 +
333 +The TOAST UI products are open-source.
334 +After fixing issues, create a pull request(PR).
335 +Run npm scripts and develop with the following process.
336 +
337 +### Setup
338 +
339 +Fork `master` branch into your personal repository.
340 +Clone to local computer.
341 +Install node modules.
342 +Before starting development, check for any errors.
343 +
344 +```sh
345 +$ git clone https://github.com/{username}/tui.image-editor.git
346 +$ cd tui.image-editor
347 +$ npm install
348 +$ npm run test
349 +```
350 +
351 +### Run webpack-dev-server
352 +
353 +```sh
354 +$ npm run serve
355 +```
356 +
357 +## 📙 Documents
358 +
359 +- **Tutorial** : [https://github.com/nhn/tui.image-editor/tree/master/docs](https://github.com/nhn/tui.image-editor/tree/master/docs)
360 +- **Example** : [http://nhn.github.io/tui.image-editor/latest/tutorial-example01-includeUi](http://nhn.github.io/tui.image-editor/latest/tutorial-example01-includeUi)
361 +- **API** : [http://nhn.github.io/tui.image-editor/latest](http://nhn.github.io/tui.image-editor/latest/index)
362 +
363 +## 💬 Contributing
364 +
365 +- [Code of Conduct](https://github.com/nhn/tui.image-editor/blob/master/CODE_OF_CONDUCT.md)
366 +- [Contributing guideline](https://github.com/nhn/tui.image-editor/blob/master/CONTRIBUTING.md)
367 +- [Issue guideline](https://github.com/nhn/tui.image-editor/blob/master/ISSUE_TEMPLATE.md)
368 +- [Commit convention](https://github.com/nhn/tui.image-editor/blob/production/docs/COMMIT_MESSAGE_CONVENTION.md)
369 +
370 +## 🔩 Dependency
371 +
372 +- [fabric.js](https://github.com/fabricjs/fabric.js/releases) = 4.2.0
373 +- [tui.code-snippet](https://github.com/nhn/tui.code-snippet/releases/tag/v1.5.0) >= 1.5.0
374 +- [tui.color-picker](https://github.com/nhn/tui.color-picker/releases/tag/v2.2.6) >= 2.2.6
375 +
376 +## 🍞 TOAST UI Family
377 +
378 +- [TOAST UI Editor](https://github.com/nhn/tui.editor)
379 +- [TOAST UI Grid](https://github.com/nhn/tui.grid)
380 +- [TOAST UI Chart](https://github.com/nhn/tui.chart)
381 +- [TOAST UI Calendar](https://github.com/nhn/tui.calendar)
382 +- [TOAST UI Components](https://github.com/nhn)
383 +
384 +## 🚀 Used By
385 +
386 +- [TOAST Dooray! - Collaboration Service (Project, Messenger, Mail, Calendar, Drive, Wiki, Contacts)](https://dooray.com/home/)
387 +- [Catalyst](https://catalystapp.co/)
388 +
389 +## 📜 License
390 +
391 +[MIT LICENSE](https://github.com/nhn/tui.image-editor/blob/master/LICENSE)
1 +{
2 + "name": "tui-image-editor",
3 + "authors": ["NHN FE Dev Lab <dl_javascript@nhn.com>"],
4 + "license": "MIT",
5 + "main": ["dist/tui-image-editor.js"],
6 + "ignore": [
7 + "**/.*",
8 + "node_modules",
9 + "bower_components",
10 + "test",
11 + "tests",
12 + "src",
13 + "server",
14 + "data.js",
15 + "Gruntfile.js",
16 + "gulpfile.js",
17 + "karma.*.js",
18 + "conf.json",
19 + "package.json",
20 + ".gitignore",
21 + "samples",
22 + "index.js",
23 + "jsdoc.conf.json",
24 + "webpack.*.js"
25 + ],
26 + "dependencies": {
27 + "fabric": "4.2.0",
28 + "tui-code-snippet": "^1.5.0",
29 + "tui-color-picker": "^2.2.0"
30 + },
31 + "devDependencies": {
32 + "tui-component-colorpicker": "~1.0.1",
33 + "filesaver": "*"
34 + },
35 + "resolutions": {
36 + "tui-code-snippet": "^1.5.0",
37 + "tui-color-picker": "^2.2.0"
38 + }
39 +}
1 +const fs = require('fs');
2 +const path = require('path');
3 +const config = require(path.resolve(process.cwd(), 'tuidoc.config.json'));
4 +const examples = config.examples || {};
5 +const { filePath, globalErrorLogVariable } = examples;
6 +
7 +/**
8 + * Get Examples Url
9 + */
10 +function getTestUrls() {
11 + if (!filePath) {
12 + throw Error('not exist examples path at tuidoc.config.json');
13 + }
14 +
15 + const urlPrefix = 'http://nhn.github.io/tui.image-editor/latest';
16 +
17 + const testUrls = fs.readdirSync(filePath).reduce((urls, fileName) => {
18 + if (/html$/.test(fileName)) {
19 + urls.push(`${urlPrefix}/${filePath}/${fileName}`);
20 + }
21 +
22 + return urls;
23 + }, []);
24 +
25 + fs.writeFileSync('url.txt', testUrls.join(', '));
26 +}
27 +
28 +function getGlobalVariable() {
29 + if (!globalErrorLogVariable) {
30 + throw Error('not exist examples path at tuidoc.config.json');
31 + }
32 +
33 + fs.writeFileSync('errorVariable.txt', globalErrorLogVariable);
34 +}
35 +
36 +getTestUrls();
37 +getGlobalVariable();
1 +.border {
2 + border: 1px solid black;
3 +}
4 +.body-container {
5 + width: 1000px;
6 +}
7 +.tui-image-editor-controls {
8 + min-height: 250px;
9 +}
10 +.menu {
11 + padding: 0;
12 + margin-bottom: 5px;
13 + text-align: center;
14 + color: #544b61;
15 + font-weight: 400;
16 + list-style-type: none;
17 + user-select: none;
18 + -moz-user-select: none;
19 + -ms-user-select: none;
20 + -webkit-user-select: none;
21 +}
22 +.logo {
23 + margin: 0 auto;
24 + width: 300px;
25 + vertical-align: middle;
26 +}
27 +.header .name {
28 + padding: 10px;
29 + line-height: 50px;
30 + font-size: 30px;
31 + font-weight: 100;
32 + vertical-align: middle;
33 +}
34 +.header .menu {
35 + display: inline-block;
36 +}
37 +.menu-item {
38 + padding: 10px;
39 + display: inline-block;
40 + cursor: pointer;
41 + vertical-align: middle;
42 +}
43 +.menu-item a {
44 + text-decoration: none;
45 +}
46 +.menu-item.no-pointer {
47 + cursor: default;
48 +}
49 +.menu-item.active,
50 +.menu-item:hover {
51 + background-color: #f3f3f3;
52 +}
53 +.menu-item.disabled {
54 + cursor: default;
55 + color: #bfbebe;
56 +}
57 +.align-left-top {
58 + text-align: left;
59 + vertical-align: top;
60 +}
61 +.range-narrow {
62 + width: 80px;
63 +}
64 +.sub-menu-container {
65 + font-size: 14px;
66 + margin-bottom: 1em;
67 + display: none;
68 +}
69 +.tui-image-editor {
70 + height: 500px;
71 +}
72 +.tui-image-editor-canvas-container {
73 + margin: 0 auto;
74 + top: 50%;
75 + transform: translateY(-50%);
76 + -ms-transform: translateY(-50%);
77 + -moz-transform: translateY(-50%);
78 + -webkit-transform: translateY(-50%);
79 + border: 1px dashed black;
80 + overflow: hidden;
81 +}
82 +.tui-colorpicker-container {
83 + margin: 5px auto 0;
84 +}
85 +.tui-colorpicker-palette-toggle-slider {
86 + display: none;
87 +}
88 +.input-wrapper {
89 + position: relative;
90 +}
91 +.input-wrapper input {
92 + cursor: pointer;
93 + position: absolute;
94 + font-size: 999px;
95 + left: 0;
96 + top: 0;
97 + opacity: 0;
98 + width: 100%;
99 + height: 100%;
100 + overflow: hidden;
101 +}
102 +.btn-text-style {
103 + padding: 5px;
104 + margin: 3px 1px;
105 + border: 1px dashed #bfbebe;
106 + outline: 0;
107 + background-color: #eee;
108 + cursor: pointer;
109 +}
110 +.icon-text {
111 + font-size: 20px;
112 +}
113 +.select-line-type {
114 + outline: 0;
115 + vertical-align: middle;
116 +}
117 +#tui-color-picker {
118 + display: inline-block;
119 + vertical-align: middle;
120 +}
121 +#tui-text-palette {
122 + display: none;
123 + position: absolute;
124 + padding: 10px;
125 + border: 1px solid #bfbebe;
126 + background-color: #fff;
127 + z-index: 9999;
128 +}
1 +html,
2 +body {
3 + margin: 0;
4 + padding: 0;
5 + height: 100%;
6 + overflow: hidden;
7 + background-color: #383838;
8 + font-family: Sans-Serif;
9 +}
10 +ul,
11 +li {
12 + list-style: none;
13 + margin: 0;
14 + padding: 0;
15 +}
16 +input[type='button'],
17 +button {
18 + -webkit-appearance: none;
19 + -moz-appearance: none;
20 + background-color: #fff;
21 +}
22 +input[type='file'] {
23 + position: absolute;
24 + margin: 0;
25 + padding: 0;
26 + top: 0;
27 + left: 0;
28 + width: 100%;
29 + height: 100%;
30 + cursor: pointer;
31 + opacity: 0;
32 + filter: alpha(opacity=0);
33 +}
34 +.header {
35 + position: fixed;
36 + left: 0;
37 + top: 0;
38 + width: 100%;
39 + background-color: #fff;
40 + text-align: center;
41 + z-index: 9999;
42 +}
43 +.header .logo {
44 + margin: 10px 5px;
45 + width: 180px;
46 + vertical-align: middle;
47 +}
48 +.header .name {
49 + font-size: 16px;
50 + font-weight: bold;
51 +}
52 +.header .menu {
53 + padding: 10px;
54 + background-color: #000;
55 +}
56 +.header .menu input {
57 + opacity: 0;
58 +}
59 +.header .menu img {
60 + width: 20px;
61 + height: 20px;
62 + vertical-align: middle;
63 +}
64 +.header .button {
65 + position: relative;
66 + display: inline-block;
67 + margin: 0 5px;
68 + padding: 0;
69 + border-radius: 5px 5px;
70 + width: 30px;
71 + height: 30px;
72 + border: 0;
73 + background-color: #fff;
74 + vertical-align: middle;
75 +}
76 +.header .button.disabled img {
77 + opacity: 0.5;
78 +}
79 +.tui-image-editor {
80 + height: 100%;
81 +}
82 +.tui-image-editor-canvas-container {
83 + margin: 0 auto;
84 + top: 50%;
85 + transform: translateY(-50%);
86 + -ms-transform: translateY(-50%);
87 + -moz-transform: translateY(-50%);
88 + -webkit-transform: translateY(-50%);
89 + overflow: hidden;
90 +}
91 +.tui-image-editor-controls {
92 + position: fixed;
93 + width: 100%;
94 + left: 0;
95 + bottom: 0;
96 + background-color: #fff;
97 +}
98 +.tui-image-editor-controls .scrollable {
99 + display: inline-block;
100 + overflow-x: auto;
101 + width: 100%;
102 + height: 100%;
103 + white-space: nowrap;
104 + font-size: 0;
105 + background-color: #000;
106 + vertical-align: middle;
107 +}
108 +.tui-image-editor-controls .no-scrollable {
109 + overflow-x: hidden;
110 +}
111 +.tui-image-editor-controls .menu-item {
112 + display: inline-block;
113 + height: 80px;
114 + border-right: 1px solid #383838;
115 + background-color: #ddd;
116 + vertical-align: middle;
117 +}
118 +.tui-image-editor-controls .menu-button {
119 + width: 80px;
120 + height: 80px;
121 + border: none;
122 + vertical-align: middle;
123 + background-color: #000;
124 + color: #fff;
125 + font-size: 12px;
126 + font-weight: bold;
127 + outline: 0;
128 +}
129 +.tui-image-editor-controls .submenu-button {
130 + width: 80px;
131 + height: 80px;
132 + border: none;
133 + background-color: #ddd;
134 + vertical-align: middle;
135 +}
136 +.tui-image-editor-controls .hiddenmenu-button {
137 + margin: 0 10px;
138 + padding: 5px;
139 + border: none;
140 + color: #fff;
141 + background-color: rgba(255, 255, 255, 0);
142 +}
143 +.tui-image-editor-controls .submenu {
144 + display: none;
145 + position: absolute;
146 + top: 0;
147 + left: 0;
148 + width: 100%;
149 + font-size: 0;
150 +}
151 +.tui-image-editor-controls .submenu.show {
152 + display: block;
153 +}
154 +.tui-image-editor-controls .submenu .menu-item:last-child {
155 + margin-right: 50px;
156 +}
157 +.tui-image-editor-controls .hiddenmenu {
158 + position: absolute;
159 + display: none;
160 + padding: 40px;
161 + width: 100%;
162 + left: 0;
163 + bottom: 80px;
164 + background-color: rgba(0, 0, 0, 0.7);
165 + text-align: center;
166 + -webkit-box-sizing: border-box;
167 + -moz-box-sizing: border-box;
168 + box-sizing: border-box;
169 + z-index: 9999;
170 +}
171 +.tui-image-editor-controls .hiddenmenu.show {
172 + display: block;
173 +}
174 +.tui-image-editor-controls .hiddenmenu .top {
175 + font-size: 12px;
176 + color: #fff;
177 + margin-bottom: 20px;
178 +}
179 +.tui-image-editor-controls .btn-prev {
180 + display: inline-block;
181 + width: 30px;
182 + height: 80px;
183 + background-color: #000;
184 + color: #fff;
185 + border: none;
186 + vertical-align: middle;
187 +}
188 +.tui-image-editor-controls .tui-colorpicker-container {
189 + display: inline-block;
190 +}
191 +.tui-image-editor-controls .msg {
192 + position: absolute;
193 + margin-left: 50%;
194 + padding: 5px 10px;
195 + left: -86px;
196 + top: -50px;
197 + border-radius: 5px 5px;
198 + background-color: rgba(255, 255, 255, 0.5);
199 + font-size: 12px;
200 +}
201 +.tui-image-editor-controls .msg.hide {
202 + display: none;
203 +}
1 +body {
2 + margin: 0;
3 + padding: 0;
4 +}
5 +
6 +.code-description {
7 + padding: 22px 52px;
8 + background-color: rgba(81, 92, 230, 0.1);
9 + line-height: 1.4em;
10 +}
11 +
12 +.code-description,
13 +.code-description a {
14 + font-family: Arial;
15 + font-size: 14px;
16 + color: #515ce6;
17 +}
18 +
19 +.code-html {
20 + padding: 20px 52px;
21 +}
1 +<!DOCTYPE html>
2 +<html>
3 + <head>
4 + <meta charset="UTF-8" />
5 + <title>0. Design</title>
6 + <link
7 + type="text/css"
8 + href="https://uicdn.toast.com/tui-color-picker/v2.2.6/tui-color-picker.css"
9 + rel="stylesheet"
10 + />
11 + <link type="text/css" href="../dist/tui-image-editor.css" rel="stylesheet" />
12 + <style>
13 + @import url(http://fonts.googleapis.com/css?family=Noto+Sans);
14 + html,
15 + body {
16 + height: 100%;
17 + margin: 0;
18 + }
19 + </style>
20 + </head>
21 + <body>
22 + <div id="tui-image-editor-container"></div>
23 + <script
24 + type="text/javascript"
25 + src="https://api-storage.cloud.toast.com/v1/AUTH_e18353c4ea5746c097143946d0644e61/toast-ui-cdn/tui-image-editor/v3.11.0/example/fabric-v4.2.0.js"
26 + ></script>
27 + <script
28 + type="text/javascript"
29 + src="https://uicdn.toast.com/tui.code-snippet/v1.5.0/tui-code-snippet.min.js"
30 + ></script>
31 + <script
32 + type="text/javascript"
33 + src="https://uicdn.toast.com/tui-color-picker/v2.2.6/tui-color-picker.js"
34 + ></script>
35 + <script
36 + type="text/javascript"
37 + src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/1.3.3/FileSaver.min.js"
38 + ></script>
39 + <script type="text/javascript" src="../dist/tui-image-editor.js"></script>
40 + <script type="text/javascript" src="./js/theme/white-theme.js"></script>
41 + <script type="text/javascript" src="./js/theme/black-theme.js"></script>
42 + <script>
43 + // Image editor
44 + var imageEditor = new tui.ImageEditor('#tui-image-editor-container', {
45 + includeUI: {
46 + loadImage: {
47 + path: 'img/sampleImage2.png',
48 + name: 'SampleImage',
49 + },
50 + theme: blackTheme, // or whiteTheme
51 + initMenu: 'filter',
52 + menuBarPosition: 'bottom',
53 + },
54 + cssMaxWidth: 700,
55 + cssMaxHeight: 500,
56 + usageStatistics: false,
57 + });
58 + window.onresize = function () {
59 + imageEditor.ui.resizeEditor();
60 + };
61 + </script>
62 + </body>
63 +</html>
1 +<!DOCTYPE html>
2 +<html>
3 + <head>
4 + <meta charset="UTF-8" />
5 + <title>1. Basic</title>
6 + <link
7 + type="text/css"
8 + href="https://uicdn.toast.com/tui-color-picker/v2.2.6/tui-color-picker.css"
9 + rel="stylesheet"
10 + />
11 + <link type="text/css" href="css/service-basic.css" rel="stylesheet" />
12 + </head>
13 + <body>
14 + <div class="body-container">
15 + <div class="tui-image-editor-controls">
16 + <div class="header">
17 + <img class="logo" src="img/TOAST UI Component.png" />
18 + <span class="name"> Image Editor</span>
19 + <ul class="menu">
20 + <li class="menu-item border input-wrapper">
21 + Load
22 + <input type="file" accept="image/*" id="input-image-file" />
23 + </li>
24 + <li class="menu-item border" id="btn-download">Download</li>
25 + </ul>
26 + </div>
27 + <ul class="menu">
28 + <li class="menu-item disabled" id="btn-undo">Undo</li>
29 + <li class="menu-item disabled" id="btn-redo">Redo</li>
30 + <li class="menu-item" id="btn-clear-objects">ClearObjects</li>
31 + <li class="menu-item" id="btn-remove-active-object">RemoveActiveObject</li>
32 + <li class="menu-item" id="btn-crop">Crop</li>
33 + <li class="menu-item" id="btn-flip">Flip</li>
34 + <li class="menu-item" id="btn-rotation">Rotation</li>
35 + <li class="menu-item" id="btn-draw-line">DrawLine</li>
36 + <li class="menu-item" id="btn-draw-shape">Shape</li>
37 + <li class="menu-item" id="btn-add-icon">Icon</li>
38 + <li class="menu-item" id="btn-text">Text</li>
39 + <li class="menu-item" id="btn-mask-filter">Mask</li>
40 + <li class="menu-item" id="btn-image-filter">Filter</li>
41 + </ul>
42 + <div class="sub-menu-container" id="crop-sub-menu">
43 + <ul class="menu">
44 + <li class="menu-item" id="btn-apply-crop">Apply</li>
45 + <li class="menu-item" id="btn-cancel-crop">Cancel</li>
46 + </ul>
47 + </div>
48 + <div class="sub-menu-container" id="flip-sub-menu">
49 + <ul class="menu">
50 + <li class="menu-item" id="btn-flip-x">FlipX</li>
51 + <li class="menu-item" id="btn-flip-y">FlipY</li>
52 + <li class="menu-item" id="btn-reset-flip">Reset</li>
53 + <li class="menu-item close">Close</li>
54 + </ul>
55 + </div>
56 + <div class="sub-menu-container" id="rotation-sub-menu">
57 + <ul class="menu">
58 + <li class="menu-item" id="btn-rotate-clockwise">Clockwise(30)</li>
59 + <li class="menu-item" id="btn-rotate-counter-clockwise">Counter-Clockwise(-30)</li>
60 + <li class="menu-item no-pointer">
61 + <label>
62 + Range input
63 + <input id="input-rotation-range" type="range" min="-360" value="0" max="360" />
64 + </label>
65 + </li>
66 + <li class="menu-item close">Close</li>
67 + </ul>
68 + </div>
69 + <div class="sub-menu-container menu" id="draw-line-sub-menu">
70 + <ul class="menu">
71 + <li class="menu-item">
72 + <label>
73 + <input type="radio" name="select-line-type" value="freeDrawing" checked="checked" />
74 + Free drawing
75 + </label>
76 + <label>
77 + <input type="radio" name="select-line-type" value="lineDrawing" />
78 + Straight line
79 + </label>
80 + </li>
81 + <li class="menu-item">
82 + <div id="tui-brush-color-picker">Brush color</div>
83 + </li>
84 + <li class="menu-item">
85 + <label class="menu-item no-pointer">
86 + Brush width
87 + <input id="input-brush-width-range" type="range" min="5" max="30" value="12" />
88 + </label>
89 + </li>
90 + <li class="menu-item close">Close</li>
91 + </ul>
92 + </div>
93 + <div class="sub-menu-container" id="draw-shape-sub-menu">
94 + <ul class="menu">
95 + <li class="menu-item">
96 + <label>
97 + <input type="radio" name="select-shape-type" value="rect" checked="checked" />
98 + rect
99 + </label>
100 + <label>
101 + <input type="radio" name="select-shape-type" value="circle" />
102 + circle
103 + </label>
104 + <label>
105 + <input type="radio" name="select-shape-type" value="triangle" />
106 + triangle
107 + </label>
108 + </li>
109 + <li class="menu-item">
110 + <select name="select-color-type">
111 + <option value="fill">Fill</option>
112 + <option value="stroke">Stroke</option>
113 + </select>
114 + <label>
115 + <input
116 + type="radio"
117 + name="input-check-fill"
118 + id="input-check-transparent"
119 + value="transparent"
120 + />
121 + transparent
122 + </label>
123 + <label>
124 + <input
125 + type="radio"
126 + name="input-check-fill"
127 + id="input-check-filter"
128 + value="filter"
129 + />
130 + filter
131 + </label>
132 + <div id="tui-shape-color-picker"></div>
133 + </li>
134 + <li class="menu-item">
135 + <label class="menu-item no-pointer">
136 + Stroke width
137 + <input id="input-stroke-width-range" type="range" min="0" max="300" value="12" />
138 + </label>
139 + </li>
140 + <li class="menu-item close">Close</li>
141 + </ul>
142 + </div>
143 + <div class="sub-menu-container" id="icon-sub-menu">
144 + <ul class="menu">
145 + <li class="menu-item">
146 + <div id="tui-icon-color-picker">Icon color</div>
147 + </li>
148 + <li class="menu-item border" id="btn-register-icon">Register custom icon</li>
149 + <li class="menu-item icon-text" data-icon-type="arrow"></li>
150 + <li class="menu-item icon-text" data-icon-type="cancel"></li>
151 + <li class="menu-item close">Close</li>
152 + </ul>
153 + </div>
154 + <div class="sub-menu-container" id="text-sub-menu">
155 + <ul class="menu">
156 + <li class="menu-item">
157 + <div>
158 + <button class="btn-text-style" data-style-type="b">Bold</button>
159 + <button class="btn-text-style" data-style-type="i">Italic</button>
160 + <button class="btn-text-style" data-style-type="u">Underline</button>
161 + </div>
162 + <div>
163 + <button class="btn-text-style" data-style-type="l">Left</button>
164 + <button class="btn-text-style" data-style-type="c">Center</button>
165 + <button class="btn-text-style" data-style-type="r">Right</button>
166 + </div>
167 + </li>
168 + <li class="menu-item">
169 + <label class="no-pointer">
170 + <input id="input-font-size-range" type="range" min="10" max="100" value="10" />
171 + </label>
172 + </li>
173 + <li class="menu-item">
174 + <div id="tui-text-color-picker">Text color</div>
175 + </li>
176 + <li class="menu-item close">Close</li>
177 + </ul>
178 + </div>
179 + <div class="sub-menu-container" id="filter-sub-menu">
180 + <ul class="menu">
181 + <li class="menu-item border input-wrapper">
182 + Load Mask Image
183 + <input type="file" accept="image/*" id="input-mask-image-file" />
184 + </li>
185 + <li class="menu-item" id="btn-apply-mask">Apply mask filter</li>
186 + <li class="menu-item close">Close</li>
187 + </ul>
188 + </div>
189 + <div class="sub-menu-container" id="image-filter-sub-menu">
190 + <ul class="menu">
191 + <li class="menu-item align-left-top">
192 + <table>
193 + <tbody>
194 + <tr>
195 + <td>
196 + <label><input type="checkbox" id="input-check-grayscale" />Grayscale</label>
197 + </td>
198 + <td>
199 + <label><input type="checkbox" id="input-check-invert" />Invert</label>
200 + </td>
201 + <td>
202 + <label><input type="checkbox" id="input-check-sepia" />Sepia</label>
203 + </td>
204 + </tr>
205 + <tr>
206 + <td>
207 + <label><input type="checkbox" id="input-check-sepia2" />Sepia2</label>
208 + </td>
209 + <td>
210 + <label><input type="checkbox" id="input-check-blur" />Blur</label>
211 + </td>
212 + <td>
213 + <label><input type="checkbox" id="input-check-sharpen" />Sharpen</label>
214 + </td>
215 + </tr>
216 + <tr>
217 + <td>
218 + <label><input type="checkbox" id="input-check-emboss" />Emboss</label>
219 + </td>
220 + </tr>
221 + </tbody>
222 + </table>
223 + </li>
224 + <li class="menu-item align-left-top">
225 + <p>
226 + <label>
227 + <input type="checkbox" id="input-check-remove-white" />
228 + RemoveWhite
229 + </label>
230 + <br />
231 + <label>
232 + Threshold
233 + <input
234 + class="range-narrow"
235 + id="input-range-remove-white-threshold"
236 + type="range"
237 + min="0"
238 + value="60"
239 + max="255"
240 + />
241 + </label>
242 + <br />
243 + <label>
244 + Distance
245 + <input
246 + class="range-narrow"
247 + id="input-range-remove-white-distance"
248 + type="range"
249 + min="0"
250 + value="10"
251 + max="255"
252 + />
253 + </label>
254 + </p>
255 + </li>
256 + <li class="menu-item align-left-top">
257 + <p>
258 + <label><input type="checkbox" id="input-check-brightness" />Brightness</label><br />
259 + <label>
260 + Value
261 + <input
262 + class="range-narrow"
263 + id="input-range-brightness-value"
264 + type="range"
265 + min="-255"
266 + value="100"
267 + max="255"
268 + />
269 + </label>
270 + </p>
271 + </li>
272 + <li class="menu-item align-left-top">
273 + <p>
274 + <label><input type="checkbox" id="input-check-noise" />Noise</label><br />
275 + <label>
276 + Value
277 + <input
278 + class="range-narrow"
279 + id="input-range-noise-value"
280 + type="range"
281 + min="0"
282 + value="100"
283 + max="1000"
284 + />
285 + </label>
286 + </p>
287 + </li>
288 + <li class="menu-item align-left-top">
289 + <p>
290 + <label>
291 + <input type="checkbox" id="input-check-color-filter" />
292 + ColorFilter
293 + </label>
294 + <br />
295 + <label>
296 + Threshold
297 + <input
298 + class="range-narrow"
299 + id="input-range-color-filter-value"
300 + type="range"
301 + min="0"
302 + value="45"
303 + max="255"
304 + />
305 + </label>
306 + </p>
307 + </li>
308 + <li class="menu-item align-left-top">
309 + <p>
310 + <label><input type="checkbox" id="input-check-pixelate" />Pixelate</label><br />
311 + <label>
312 + Value
313 + <input
314 + class="range-narrow"
315 + id="input-range-pixelate-value"
316 + type="range"
317 + min="2"
318 + value="4"
319 + max="20"
320 + />
321 + </label>
322 + </p>
323 + </li>
324 + <li class="menu-item align-left-top">
325 + <p>
326 + <label><input type="checkbox" id="input-check-tint" />Tint</label><br />
327 + </p>
328 + <div id="tui-tint-color-picker"></div>
329 + <label>
330 + Opacity
331 + <input
332 + class="range-narrow"
333 + id="input-range-tint-opacity-value"
334 + type="range"
335 + min="0"
336 + value="1"
337 + max="1"
338 + step="0.1"
339 + />
340 + </label>
341 + </li>
342 + <li class="menu-item align-left-top">
343 + <p>
344 + <label><input type="checkbox" id="input-check-multiply" />Multiply</label>
345 + </p>
346 + <div id="tui-multiply-color-picker"></div>
347 + </li>
348 + <li class="menu-item align-left-top">
349 + <p>
350 + <label><input type="checkbox" id="input-check-blend" />Blend</label>
351 + </p>
352 + <div id="tui-blend-color-picker"></div>
353 + <select name="select-blend-type">
354 + <option value="add" selected>Add</option>
355 + <option value="diff">Diff</option>
356 + <option value="diff">Subtract</option>
357 + <option value="multiply">Multiply</option>
358 + <option value="screen">Screen</option>
359 + <option value="lighten">Lighten</option>
360 + <option value="darken">Darken</option>
361 + </select>
362 + </li>
363 + <li class="menu-item close">Close</li>
364 + </ul>
365 + </div>
366 + </div>
367 + <div class="tui-image-editor"></div>
368 + </div>
369 + <script
370 + type="text/javascript"
371 + src="https://api-storage.cloud.toast.com/v1/AUTH_e18353c4ea5746c097143946d0644e61/toast-ui-cdn/tui-image-editor/v3.11.0/example/fabric-v4.2.0.js"
372 + ></script>
373 + <script
374 + type="text/javascript"
375 + src="https://uicdn.toast.com/tui.code-snippet/v1.5.0/tui-code-snippet.min.js"
376 + ></script>
377 + <script
378 + type="text/javascript"
379 + src="https://uicdn.toast.com/tui-color-picker/v2.2.6/tui-color-picker.min.js"
380 + ></script>
381 + <script
382 + type="text/javascript"
383 + src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.8.3/jquery.min.js"
384 + ></script>
385 + <script
386 + type="text/javascript"
387 + src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/1.3.3/FileSaver.min.js"
388 + ></script>
389 + <script type="text/javascript" src="../dist/tui-image-editor.js"></script>
390 + <script src="js/service-basic.js"></script>
391 + </body>
392 +</html>
1 +<!DOCTYPE html>
2 +<html>
3 + <head>
4 + <meta charset="UTF-8" />
5 + <meta name="viewport" content="width=device-width, user-scalable=no" />
6 + <title>2. Mobile</title>
7 + <link
8 + type="text/css"
9 + href="https://uicdn.toast.com/tui-color-picker/v2.2.6/tui-color-picker.css"
10 + rel="stylesheet"
11 + />
12 + <link type="text/css" href="css/service-mobile.css" rel="stylesheet" />
13 + </head>
14 + <body>
15 + <!-- Image editor controls - top area -->
16 + <div class="header">
17 + <div>
18 + <img class="logo" src="img/TOAST UI Component.png" /> <span class="name">Image Editor</span>
19 + </div>
20 + <div class="menu">
21 + <span class="button">
22 + <img src="img/openImage.png" style="margin-top: 5px" />
23 + <input type="file" accept="image/*" id="input-image-file" />
24 + </span>
25 + <button class="button disabled" id="btn-undo"><img src="img/undo.png" /></button>
26 + <button class="button disabled" id="btn-redo"><img src="img/redo.png" /></button>
27 + <button class="button" id="btn-remove-active-object"><img src="img/remove.png" /></button>
28 + <button class="button" id="btn-download"><img src="img/download.png" /></button>
29 + </div>
30 + </div>
31 + <!-- Image editor area -->
32 + <div class="tui-image-editor"></div>
33 + <!-- Image editor controls - bottom area -->
34 + <div class="tui-image-editor-controls">
35 + <ul class="scrollable">
36 + <li class="menu-item">
37 + <button class="menu-button" id="btn-crop">Crop</button>
38 + <div class="submenu">
39 + <button class="btn-prev">&lt;</button>
40 + <ul class="scrollable">
41 + <li class="menu-item">
42 + <button class="submenu-button" id="btn-apply-crop">Apply</button>
43 + </li>
44 + </ul>
45 + </div>
46 + </li>
47 + <li class="menu-item">
48 + <button class="menu-button">Orientation</button>
49 + <div class="submenu">
50 + <button class="btn-prev">&lt;</button>
51 + <ul class="scrollable">
52 + <li class="menu-item">
53 + <button class="submenu-button" id="btn-rotate-clockwise">Rotate +90</button>
54 + </li>
55 + <li class="menu-item">
56 + <button class="submenu-button" id="btn-rotate-counter-clockwise">Rotate -90</button>
57 + </li>
58 + <li class="menu-item">
59 + <button class="submenu-button" id="btn-flip-x">FilpX</button>
60 + </li>
61 + <li class="menu-item">
62 + <button class="submenu-button" id="btn-flip-y">FilpY</button>
63 + </li>
64 + </ul>
65 + </div>
66 + </li>
67 + <li class="menu-item">
68 + <button class="menu-button" id="btn-draw-line">Drawing</button>
69 + <div class="submenu">
70 + <button class="btn-prev">&lt;</button>
71 + <ul class="scrollable">
72 + <li class="menu-item">
73 + <button class="submenu-button" id="btn-free-drawing">Free<br />Drawing</button>
74 + </li>
75 + <li class="menu-item">
76 + <button class="submenu-button" id="btn-line-drawing">Line<br />Drawing</button>
77 + </li>
78 + <li class="menu-item">
79 + <button class="submenu-button" id="btn-change-size">Brush<br />Size</button>
80 + <div class="hiddenmenu">
81 + <input id="input-brush-range" type="range" min="10" max="100" value="50" />
82 + </div>
83 + </li>
84 + <li class="menu-item">
85 + <button class="submenu-button" id="btn-change-text-color">Brush<br />Color</button>
86 + <div class="hiddenmenu">
87 + <div id="tui-brush-color-picker"></div>
88 + </div>
89 + </li>
90 + </ul>
91 + </div>
92 + </li>
93 + <li class="menu-item">
94 + <button class="menu-button" id="btn-draw-shape">Shape</button>
95 + <div class="submenu">
96 + <button class="btn-prev">&lt;</button>
97 + <ul class="scrollable">
98 + <li class="menu-item">
99 + <button class="submenu-button" id="btn-add-rect">Rectagle</button>
100 + </li>
101 + <li class="menu-item">
102 + <button class="submenu-button" id="btn-add-square">Square</button>
103 + </li>
104 + <li class="menu-item">
105 + <button class="submenu-button" id="btn-add-ellipse">Ellipse</button>
106 + </li>
107 + <li class="menu-item">
108 + <button class="submenu-button" id="btn-add-circle">Circle</button>
109 + </li>
110 + <li class="menu-item">
111 + <button class="submenu-button" id="btn-add-triangle">Triangle</button>
112 + </li>
113 + <li class="menu-item">
114 + <button class="submenu-button" id="btn-stroke-size">Stroke<br />Size</button>
115 + <div class="hiddenmenu">
116 + <input id="input-stroke-range" type="range" min="1" max="100" value="10" />
117 + </div>
118 + </li>
119 + <li class="menu-item">
120 + <button class="submenu-button" id="btn-change-shape-color">Color</button>
121 + <div class="hiddenmenu">
122 + <div class="top">
123 + <label for="fill-color"
124 + ><input
125 + type="radio"
126 + id="fill-color"
127 + name="select-color-type"
128 + value="fill"
129 + checked="checked"
130 + />
131 + Fill</label
132 + >
133 + <label for="stroke-color"
134 + ><input
135 + type="radio"
136 + id="stroke-color"
137 + name="select-color-type"
138 + value="stroke"
139 + />
140 + Stroke</label
141 + >
142 + <label for="input-check-transparent"
143 + ><input type="checkbox" id="input-check-transparent" />Transparent</label
144 + >
145 + </div>
146 + <div id="tui-shape-color-picker"></div>
147 + </div>
148 + </li>
149 + </ul>
150 + </div>
151 + </li>
152 + <li class="menu-item">
153 + <button class="menu-button">Icon</button>
154 + <div class="submenu">
155 + <button class="btn-prev">&lt;</button>
156 + <ul class="scrollable">
157 + <li class="menu-item">
158 + <button class="submenu-button" id="btn-add-arrow-icon">Arrow<br />Icon</button>
159 + </li>
160 + <li class="menu-item">
161 + <button class="submenu-button" id="btn-add-cancel-icon">Cancel<br />Icon</button>
162 + </li>
163 + <li class="menu-item">
164 + <button class="submenu-button" id="btn-change-icon-color">Color</button>
165 + <div class="hiddenmenu">
166 + <div id="tui-icon-color-picker"></div>
167 + </div>
168 + </li>
169 + </ul>
170 + </div>
171 + </li>
172 + <li class="menu-item">
173 + <button class="menu-button" id="btn-add-text">Text</button>
174 + <div class="submenu">
175 + <button class="btn-prev">&lt;</button>
176 + <ul class="scrollable">
177 + <li class="menu-item">
178 + <button class="submenu-button" id="btn-change-size">Size</button>
179 + <div class="hiddenmenu">
180 + <input id="input-text-size-range" type="range" min="10" max="240" value="120" />
181 + </div>
182 + </li>
183 + <li class="menu-item">
184 + <button class="submenu-button" id="btn-change-style">Style</button>
185 + <div class="hiddenmenu">
186 + <button class="hiddenmenu-button btn-change-text-style" data-style-type="bold">
187 + <b>Bold</b>
188 + </button>
189 + <button class="hiddenmenu-button btn-change-text-style" data-style-type="italic">
190 + <i>Italic</i>
191 + </button>
192 + <button
193 + class="hiddenmenu-button btn-change-text-style"
194 + data-style-type="underline"
195 + >
196 + <u>Underline</u>
197 + </button>
198 + </div>
199 + </li>
200 + <li class="menu-item">
201 + <button class="submenu-button" id="btn-change-align">Align</button>
202 + <div class="hiddenmenu">
203 + <button class="hiddenmenu-button btn-change-text-style" data-style-type="left">
204 + Left
205 + </button>
206 + <button class="hiddenmenu-button btn-change-text-style" data-style-type="center">
207 + Center
208 + </button>
209 + <button class="hiddenmenu-button btn-change-text-style" data-style-type="right">
210 + Right
211 + </button>
212 + </div>
213 + </li>
214 + <li class="menu-item">
215 + <button class="submenu-button" id="btn-change-text-color">Color</button>
216 + <div class="hiddenmenu">
217 + <div id="tui-text-color-picker"></div>
218 + </div>
219 + </li>
220 + </ul>
221 + </div>
222 + </li>
223 + </ul>
224 + <p class="msg">Menu Scrolling <b>Left ⇔ Right</b></p>
225 + </div>
226 + <script
227 + type="text/javascript"
228 + src="https://api-storage.cloud.toast.com/v1/AUTH_e18353c4ea5746c097143946d0644e61/toast-ui-cdn/tui-image-editor/v3.11.0/example/fabric-v4.2.0.js"
229 + ></script>
230 + <script
231 + type="text/javascript"
232 + src="https://uicdn.toast.com/tui.code-snippet/v1.5.0/tui-code-snippet.min.js"
233 + ></script>
234 + <script
235 + type="text/javascript"
236 + src="https://uicdn.toast.com/tui-color-picker/v2.2.6/tui-color-picker.min.js"
237 + ></script>
238 + <script
239 + type="text/javascript"
240 + src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.8.3/jquery.min.js"
241 + ></script>
242 + <script
243 + type="text/javascript"
244 + src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/1.3.3/FileSaver.min.js"
245 + ></script>
246 + <script type="text/javascript" src="../dist/tui-image-editor.js"></script>
247 + <script src="js/service-mobile.js"></script>
248 + </body>
249 +</html>
1 +{
2 + "example01-includeUi": {
3 + "title": "1. Include ui"
4 + },
5 + "example02-useApiDirect": {
6 + "title": "2. Use api direct (basic)"
7 + },
8 + "example03-mobile": {
9 + "title": "3. Mobile"
10 + }
11 +}
1 +/**
2 + * basic.js
3 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
4 + * @fileoverview
5 + */
6 +/* eslint-disable vars-on-top,no-var,strict,prefer-template,prefer-arrow-callback,prefer-destructuring,object-shorthand,require-jsdoc,complexity,prefer-const,no-unused-vars */
7 +var PIXELATE_FILTER_DEFAULT_VALUE = 20;
8 +var supportingFileAPI = !!(window.File && window.FileList && window.FileReader);
9 +var rImageType = /data:(image\/.+);base64,/;
10 +var shapeOptions = {};
11 +var shapeType;
12 +var activeObjectId;
13 +
14 +// Buttons
15 +var $btns = $('.menu-item');
16 +var $btnsActivatable = $btns.filter('.activatable');
17 +var $inputImage = $('#input-image-file');
18 +var $btnDownload = $('#btn-download');
19 +
20 +var $btnUndo = $('#btn-undo');
21 +var $btnRedo = $('#btn-redo');
22 +var $btnClearObjects = $('#btn-clear-objects');
23 +var $btnRemoveActiveObject = $('#btn-remove-active-object');
24 +var $btnCrop = $('#btn-crop');
25 +var $btnFlip = $('#btn-flip');
26 +var $btnRotation = $('#btn-rotation');
27 +var $btnDrawLine = $('#btn-draw-line');
28 +var $btnDrawShape = $('#btn-draw-shape');
29 +var $btnApplyCrop = $('#btn-apply-crop');
30 +var $btnCancelCrop = $('#btn-cancel-crop');
31 +var $btnFlipX = $('#btn-flip-x');
32 +var $btnFlipY = $('#btn-flip-y');
33 +var $btnResetFlip = $('#btn-reset-flip');
34 +var $btnRotateClockwise = $('#btn-rotate-clockwise');
35 +var $btnRotateCounterClockWise = $('#btn-rotate-counter-clockwise');
36 +var $btnText = $('#btn-text');
37 +var $btnTextStyle = $('.btn-text-style');
38 +var $btnAddIcon = $('#btn-add-icon');
39 +var $btnRegisterIcon = $('#btn-register-icon');
40 +var $btnMaskFilter = $('#btn-mask-filter');
41 +var $btnImageFilter = $('#btn-image-filter');
42 +var $btnLoadMaskImage = $('#input-mask-image-file');
43 +var $btnApplyMask = $('#btn-apply-mask');
44 +var $btnClose = $('.close');
45 +
46 +// Input etc.
47 +var $inputRotationRange = $('#input-rotation-range');
48 +var $inputBrushWidthRange = $('#input-brush-width-range');
49 +var $inputFontSizeRange = $('#input-font-size-range');
50 +var $inputStrokeWidthRange = $('#input-stroke-width-range');
51 +var $inputCheckTransparent = $('#input-check-transparent');
52 +var $inputCheckFilter = $('#input-check-filter');
53 +var $inputCheckGrayscale = $('#input-check-grayscale');
54 +var $inputCheckInvert = $('#input-check-invert');
55 +var $inputCheckSepia = $('#input-check-sepia');
56 +var $inputCheckSepia2 = $('#input-check-sepia2');
57 +var $inputCheckBlur = $('#input-check-blur');
58 +var $inputCheckSharpen = $('#input-check-sharpen');
59 +var $inputCheckEmboss = $('#input-check-emboss');
60 +var $inputCheckRemoveWhite = $('#input-check-remove-white');
61 +var $inputRangeRemoveWhiteThreshold = $('#input-range-remove-white-threshold');
62 +var $inputRangeRemoveWhiteDistance = $('#input-range-remove-white-distance');
63 +var $inputCheckBrightness = $('#input-check-brightness');
64 +var $inputRangeBrightnessValue = $('#input-range-brightness-value');
65 +var $inputCheckNoise = $('#input-check-noise');
66 +var $inputRangeNoiseValue = $('#input-range-noise-value');
67 +var $inputCheckPixelate = $('#input-check-pixelate');
68 +var $inputRangePixelateValue = $('#input-range-pixelate-value');
69 +var $inputCheckTint = $('#input-check-tint');
70 +var $inputRangeTintOpacityValue = $('#input-range-tint-opacity-value');
71 +var $inputCheckMultiply = $('#input-check-multiply');
72 +var $inputCheckBlend = $('#input-check-blend');
73 +var $inputCheckColorFilter = $('#input-check-color-filter');
74 +var $inputRangeColorFilterValue = $('#input-range-color-filter-value');
75 +
76 +// Sub menus
77 +var $displayingSubMenu = $();
78 +var $cropSubMenu = $('#crop-sub-menu');
79 +var $flipSubMenu = $('#flip-sub-menu');
80 +var $rotationSubMenu = $('#rotation-sub-menu');
81 +var $freeDrawingSubMenu = $('#free-drawing-sub-menu');
82 +var $drawLineSubMenu = $('#draw-line-sub-menu');
83 +var $drawShapeSubMenu = $('#draw-shape-sub-menu');
84 +var $textSubMenu = $('#text-sub-menu');
85 +var $iconSubMenu = $('#icon-sub-menu');
86 +var $filterSubMenu = $('#filter-sub-menu');
87 +var $imageFilterSubMenu = $('#image-filter-sub-menu');
88 +
89 +// Select line type
90 +var $selectLine = $('[name="select-line-type"]');
91 +
92 +// Select shape type
93 +var $selectShapeType = $('[name="select-shape-type"]');
94 +
95 +// Select color of shape type
96 +var $selectColorType = $('[name="select-color-type"]');
97 +
98 +// Select blend type
99 +var $selectBlendType = $('[name="select-blend-type"]');
100 +
101 +// Image editor
102 +var imageEditor = new tui.ImageEditor('.tui-image-editor', {
103 + cssMaxWidth: 700,
104 + cssMaxHeight: 500,
105 + selectionStyle: {
106 + cornerSize: 20,
107 + rotatingPointOffset: 70,
108 + },
109 +});
110 +
111 +// Color picker for free drawing
112 +var brushColorpicker = tui.colorPicker.create({
113 + container: $('#tui-brush-color-picker')[0],
114 + color: '#000000',
115 +});
116 +
117 +// Color picker for text palette
118 +var textColorpicker = tui.colorPicker.create({
119 + container: $('#tui-text-color-picker')[0],
120 + color: '#000000',
121 +});
122 +
123 +// Color picker for shape
124 +var shapeColorpicker = tui.colorPicker.create({
125 + container: $('#tui-shape-color-picker')[0],
126 + color: '#000000',
127 +});
128 +
129 +// Color picker for icon
130 +var iconColorpicker = tui.colorPicker.create({
131 + container: $('#tui-icon-color-picker')[0],
132 + color: '#000000',
133 +});
134 +
135 +var tintColorpicker = tui.colorPicker.create({
136 + container: $('#tui-tint-color-picker')[0],
137 + color: '#000000',
138 +});
139 +
140 +var multiplyColorpicker = tui.colorPicker.create({
141 + container: $('#tui-multiply-color-picker')[0],
142 + color: '#000000',
143 +});
144 +
145 +var blendColorpicker = tui.colorPicker.create({
146 + container: $('#tui-blend-color-picker')[0],
147 + color: '#00FF00',
148 +});
149 +
150 +// Common global functions
151 +// HEX to RGBA
152 +function hexToRGBa(hex, alpha) {
153 + var r = parseInt(hex.slice(1, 3), 16);
154 + var g = parseInt(hex.slice(3, 5), 16);
155 + var b = parseInt(hex.slice(5, 7), 16);
156 + var a = alpha || 1;
157 +
158 + return 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')';
159 +}
160 +
161 +function base64ToBlob(data) {
162 + var mimeString = '';
163 + var raw, uInt8Array, i, rawLength;
164 +
165 + raw = data.replace(rImageType, function (header, imageType) {
166 + mimeString = imageType;
167 +
168 + return '';
169 + });
170 +
171 + raw = atob(raw);
172 + rawLength = raw.length;
173 + uInt8Array = new Uint8Array(rawLength); // eslint-disable-line
174 +
175 + for (i = 0; i < rawLength; i += 1) {
176 + uInt8Array[i] = raw.charCodeAt(i);
177 + }
178 +
179 + return new Blob([uInt8Array], { type: mimeString });
180 +}
181 +
182 +function resizeEditor() {
183 + var $editor = $('.tui-image-editor');
184 + var $container = $('.tui-image-editor-canvas-container');
185 + var height = parseFloat($container.css('max-height'));
186 +
187 + $editor.height(height);
188 +}
189 +
190 +function getBrushSettings() {
191 + var brushWidth = parseInt($inputBrushWidthRange.val(), 10);
192 + var brushColor = brushColorpicker.getColor();
193 +
194 + return {
195 + width: brushWidth,
196 + color: hexToRGBa(brushColor, 0.5),
197 + };
198 +}
199 +
200 +function activateShapeMode() {
201 + if (imageEditor.getDrawingMode() !== 'SHAPE') {
202 + imageEditor.stopDrawingMode();
203 + imageEditor.startDrawingMode('SHAPE');
204 + }
205 +}
206 +
207 +function activateIconMode() {
208 + imageEditor.stopDrawingMode();
209 +}
210 +
211 +function activateTextMode() {
212 + if (imageEditor.getDrawingMode() !== 'TEXT') {
213 + imageEditor.stopDrawingMode();
214 + imageEditor.startDrawingMode('TEXT');
215 + }
216 +}
217 +
218 +function setTextToolbar(obj) {
219 + var fontSize = obj.fontSize;
220 + var fontColor = obj.fill;
221 +
222 + $inputFontSizeRange.val(fontSize);
223 + textColorpicker.setColor(fontColor);
224 +}
225 +
226 +function setIconToolbar(obj) {
227 + var iconColor = obj.fill;
228 +
229 + iconColorpicker.setColor(iconColor);
230 +}
231 +
232 +function setShapeToolbar(obj) {
233 + var fillColor, isTransparent, isFilter;
234 + var colorType = $selectColorType.val();
235 + var changeValue = colorType === 'stroke' ? obj.stroke : obj.fill.type;
236 + isTransparent = changeValue === 'transparent';
237 + isFilter = changeValue === 'filter';
238 +
239 + if (colorType === 'stroke') {
240 + if (!isTransparent && !isFilter) {
241 + shapeColorpicker.setColor(changeValue);
242 + }
243 + } else if (colorType === 'fill') {
244 + if (!isTransparent && !isFilter) {
245 + fillColor = obj.fill.color;
246 + shapeColorpicker.setColor(fillColor);
247 + }
248 + }
249 +
250 + $inputCheckTransparent.prop('checked', isTransparent);
251 + $inputCheckFilter.prop('checked', isFilter);
252 + $inputStrokeWidthRange.val(obj.strokeWidth);
253 +}
254 +
255 +function showSubMenu(type) {
256 + var $submenu;
257 +
258 + switch (type) {
259 + case 'shape':
260 + $submenu = $drawShapeSubMenu;
261 + break;
262 + case 'icon':
263 + $submenu = $iconSubMenu;
264 + break;
265 + case 'text':
266 + $submenu = $textSubMenu;
267 + break;
268 + default:
269 + $submenu = 0;
270 + }
271 +
272 + $displayingSubMenu.hide();
273 + $displayingSubMenu = $submenu.show();
274 +}
275 +
276 +function applyOrRemoveFilter(applying, type, options) {
277 + if (applying) {
278 + imageEditor.applyFilter(type, options).then(function (result) {
279 + console.log(result);
280 + });
281 + } else {
282 + imageEditor.removeFilter(type);
283 + }
284 +}
285 +
286 +// Attach image editor custom events
287 +imageEditor.on({
288 + objectAdded: function (objectProps) {
289 + console.info(objectProps);
290 + },
291 + undoStackChanged: function (length) {
292 + if (length) {
293 + $btnUndo.removeClass('disabled');
294 + } else {
295 + $btnUndo.addClass('disabled');
296 + }
297 + resizeEditor();
298 + },
299 + redoStackChanged: function (length) {
300 + if (length) {
301 + $btnRedo.removeClass('disabled');
302 + } else {
303 + $btnRedo.addClass('disabled');
304 + }
305 + resizeEditor();
306 + },
307 + objectScaled: function (obj) {
308 + if (obj.type === 'text') {
309 + $inputFontSizeRange.val(obj.fontSize);
310 + }
311 + },
312 + addText: function (pos) {
313 + imageEditor
314 + .addText('Double Click', {
315 + position: pos.originPosition,
316 + })
317 + .then(function (objectProps) {
318 + console.log(objectProps);
319 + });
320 + },
321 + objectActivated: function (obj) {
322 + activeObjectId = obj.id;
323 + if (obj.type === 'rect' || obj.type === 'circle' || obj.type === 'triangle') {
324 + showSubMenu('shape');
325 + setShapeToolbar(obj);
326 + activateShapeMode();
327 + } else if (obj.type === 'icon') {
328 + showSubMenu('icon');
329 + setIconToolbar(obj);
330 + activateIconMode();
331 + } else if (obj.type === 'text') {
332 + showSubMenu('text');
333 + setTextToolbar(obj);
334 + activateTextMode();
335 + }
336 + },
337 + mousedown: function (event, originPointer) {
338 + if ($imageFilterSubMenu.is(':visible') && imageEditor.hasFilter('colorFilter')) {
339 + imageEditor.applyFilter('colorFilter', {
340 + x: parseInt(originPointer.x, 10),
341 + y: parseInt(originPointer.y, 10),
342 + });
343 + }
344 + },
345 +});
346 +
347 +// Attach button click event listeners
348 +$btns.on('click', function () {
349 + $btnsActivatable.removeClass('active');
350 +});
351 +
352 +$btnsActivatable.on('click', function () {
353 + $(this).addClass('active');
354 +});
355 +
356 +$btnUndo.on('click', function () {
357 + $displayingSubMenu.hide();
358 +
359 + if (!$(this).hasClass('disabled')) {
360 + imageEditor.undo();
361 + }
362 +});
363 +
364 +$btnRedo.on('click', function () {
365 + $displayingSubMenu.hide();
366 +
367 + if (!$(this).hasClass('disabled')) {
368 + imageEditor.redo();
369 + }
370 +});
371 +
372 +$btnClearObjects.on('click', function () {
373 + $displayingSubMenu.hide();
374 + imageEditor.clearObjects();
375 +});
376 +
377 +$btnRemoveActiveObject.on('click', function () {
378 + $displayingSubMenu.hide();
379 + imageEditor.removeObject(activeObjectId);
380 +});
381 +
382 +$btnCrop.on('click', function () {
383 + imageEditor.startDrawingMode('CROPPER');
384 + $displayingSubMenu.hide();
385 + $displayingSubMenu = $cropSubMenu.show();
386 +});
387 +
388 +$btnFlip.on('click', function () {
389 + imageEditor.stopDrawingMode();
390 + $displayingSubMenu.hide();
391 + $displayingSubMenu = $flipSubMenu.show();
392 +});
393 +
394 +$btnRotation.on('click', function () {
395 + imageEditor.stopDrawingMode();
396 + $displayingSubMenu.hide();
397 + $displayingSubMenu = $rotationSubMenu.show();
398 +});
399 +
400 +$btnClose.on('click', function () {
401 + imageEditor.stopDrawingMode();
402 + $displayingSubMenu.hide();
403 +});
404 +
405 +$btnApplyCrop.on('click', function () {
406 + imageEditor.crop(imageEditor.getCropzoneRect()).then(function () {
407 + imageEditor.stopDrawingMode();
408 + resizeEditor();
409 + });
410 +});
411 +
412 +$btnCancelCrop.on('click', function () {
413 + imageEditor.stopDrawingMode();
414 +});
415 +
416 +$btnFlipX.on('click', function () {
417 + imageEditor.flipX().then(function (status) {
418 + console.log('flipX: ', status.flipX);
419 + console.log('flipY: ', status.flipY);
420 + console.log('angle: ', status.angle);
421 + });
422 +});
423 +
424 +$btnFlipY.on('click', function () {
425 + imageEditor.flipY().then(function (status) {
426 + console.log('flipX: ', status.flipX);
427 + console.log('flipY: ', status.flipY);
428 + console.log('angle: ', status.angle);
429 + });
430 +});
431 +
432 +$btnResetFlip.on('click', function () {
433 + imageEditor.resetFlip().then(function (status) {
434 + console.log('flipX: ', status.flipX);
435 + console.log('flipY: ', status.flipY);
436 + console.log('angle: ', status.angle);
437 + });
438 +});
439 +
440 +$btnRotateClockwise.on('click', function () {
441 + imageEditor.rotate(30);
442 +});
443 +
444 +$btnRotateCounterClockWise.on('click', function () {
445 + imageEditor.rotate(-30);
446 +});
447 +
448 +$inputRotationRange.on('mousedown', function () {
449 + var changeAngle = function () {
450 + imageEditor.setAngle(parseInt($inputRotationRange.val(), 10))['catch'](function () {});
451 + };
452 + $(document).on('mousemove', changeAngle);
453 + $(document).on('mouseup', function stopChangingAngle() {
454 + $(document).off('mousemove', changeAngle);
455 + $(document).off('mouseup', stopChangingAngle);
456 + });
457 +});
458 +
459 +$inputRotationRange.on('change', function () {
460 + imageEditor.setAngle(parseInt($inputRotationRange.val(), 10))['catch'](function () {});
461 +});
462 +
463 +$inputBrushWidthRange.on('change', function () {
464 + imageEditor.setBrush({ width: parseInt(this.value, 10) });
465 +});
466 +
467 +$inputImage.on('change', function (event) {
468 + var file;
469 +
470 + if (!supportingFileAPI) {
471 + alert('This browser does not support file-api');
472 + }
473 +
474 + file = event.target.files[0];
475 + imageEditor.loadImageFromFile(file).then(function (result) {
476 + console.log(result);
477 + imageEditor.clearUndoStack();
478 + });
479 +});
480 +
481 +$btnDownload.on('click', function () {
482 + var imageName = imageEditor.getImageName();
483 + var dataURL = imageEditor.toDataURL();
484 + var blob, type, w;
485 +
486 + if (supportingFileAPI) {
487 + blob = base64ToBlob(dataURL);
488 + type = blob.type.split('/')[1];
489 + if (imageName.split('.').pop() !== type) {
490 + imageName += '.' + type;
491 + }
492 +
493 + // Library: FileSaver - saveAs
494 + saveAs(blob, imageName); // eslint-disable-line
495 + } else {
496 + alert('This browser needs a file-server');
497 + w = window.open();
498 + w.document.body.innerHTML = '<img src="' + dataURL + '">';
499 + }
500 +});
501 +
502 +// control draw line mode
503 +$btnDrawLine.on('click', function () {
504 + imageEditor.stopDrawingMode();
505 + $displayingSubMenu.hide();
506 + $displayingSubMenu = $drawLineSubMenu.show();
507 + $selectLine.eq(0).change();
508 +});
509 +
510 +$selectLine.on('change', function () {
511 + var mode = $(this).val();
512 + var settings = getBrushSettings();
513 +
514 + imageEditor.stopDrawingMode();
515 + if (mode === 'freeDrawing') {
516 + imageEditor.startDrawingMode('FREE_DRAWING', settings);
517 + } else {
518 + imageEditor.startDrawingMode('LINE_DRAWING', settings);
519 + }
520 +});
521 +
522 +brushColorpicker.on('selectColor', function (event) {
523 + imageEditor.setBrush({
524 + color: hexToRGBa(event.color, 0.5),
525 + });
526 +});
527 +
528 +// control draw shape mode
529 +$btnDrawShape.on('click', function () {
530 + showSubMenu('shape');
531 +
532 + // step 1. get options to draw shape from toolbar
533 + shapeType = $('[name="select-shape-type"]:checked').val();
534 +
535 + shapeOptions.stroke = '#000000';
536 + shapeOptions.fill = '#ffffff';
537 +
538 + shapeOptions.strokeWidth = Number($inputStrokeWidthRange.val());
539 +
540 + // step 2. set options to draw shape
541 + imageEditor.setDrawingShape(shapeType, shapeOptions);
542 +
543 + // step 3. start drawing shape mode
544 + activateShapeMode();
545 +});
546 +
547 +$selectShapeType.on('change', function () {
548 + shapeType = $(this).val();
549 +
550 + imageEditor.setDrawingShape(shapeType);
551 +});
552 +$selectColorType.on('change', function () {
553 + var colorType = $(this).val();
554 + if (colorType === 'stroke') {
555 + $inputCheckFilter.prop('disabled', true);
556 + $inputCheckFilter.prop('checked', false);
557 + } else {
558 + $inputCheckTransparent.prop('disabled', false);
559 + $inputCheckFilter.prop('disabled', false);
560 + }
561 +});
562 +
563 +$inputCheckTransparent.on('change', onChangeShapeFill);
564 +$inputCheckFilter.on('change', onChangeShapeFill);
565 +shapeColorpicker.on('selectColor', function (event) {
566 + $inputCheckTransparent.prop('checked', false);
567 + $inputCheckFilter.prop('checked', false);
568 + onChangeShapeFill(event);
569 +});
570 +
571 +function onChangeShapeFill(event) {
572 + var colorType = $selectColorType.val();
573 + var isTransparent = $inputCheckTransparent.prop('checked');
574 + var isFilter = $inputCheckFilter.prop('checked');
575 + var shapeOption;
576 +
577 + if (event.color) {
578 + shapeOption = event.color;
579 + } else if (isTransparent) {
580 + shapeOption = 'transparent';
581 + } else if (isFilter) {
582 + shapeOption = {
583 + type: 'filter',
584 + filter: [{ pixelate: PIXELATE_FILTER_DEFAULT_VALUE }],
585 + };
586 + }
587 +
588 + if (colorType === 'stroke') {
589 + imageEditor.changeShape(activeObjectId, {
590 + stroke: shapeOption,
591 + });
592 + } else if (colorType === 'fill') {
593 + imageEditor.changeShape(activeObjectId, {
594 + fill: shapeOption,
595 + });
596 + }
597 +
598 + imageEditor.setDrawingShape(shapeType, shapeOptions);
599 +}
600 +
601 +$inputStrokeWidthRange.on('change', function () {
602 + var strokeWidth = Number($(this).val());
603 +
604 + imageEditor.changeShape(activeObjectId, {
605 + strokeWidth: strokeWidth,
606 + });
607 +
608 + imageEditor.setDrawingShape(shapeType, shapeOptions);
609 +});
610 +
611 +// control text mode
612 +$btnText.on('click', function () {
613 + showSubMenu('text');
614 + activateTextMode();
615 +});
616 +
617 +$inputFontSizeRange.on('change', function () {
618 + imageEditor.changeTextStyle(activeObjectId, {
619 + fontSize: parseInt(this.value, 10),
620 + });
621 +});
622 +
623 +$btnTextStyle.on('click', function (e) {
624 + // eslint-disable-line
625 + var styleType = $(this).attr('data-style-type');
626 + var styleObj;
627 +
628 + e.stopPropagation();
629 +
630 + switch (styleType) {
631 + case 'b':
632 + styleObj = { fontWeight: 'bold' };
633 + break;
634 + case 'i':
635 + styleObj = { fontStyle: 'italic' };
636 + break;
637 + case 'u':
638 + styleObj = { underline: true };
639 + break;
640 + case 'l':
641 + styleObj = { textAlign: 'left' };
642 + break;
643 + case 'c':
644 + styleObj = { textAlign: 'center' };
645 + break;
646 + case 'r':
647 + styleObj = { textAlign: 'right' };
648 + break;
649 + default:
650 + styleObj = {};
651 + }
652 +
653 + imageEditor.changeTextStyle(activeObjectId, styleObj);
654 +});
655 +
656 +textColorpicker.on('selectColor', function (event) {
657 + imageEditor.changeTextStyle(activeObjectId, {
658 + fill: event.color,
659 + });
660 +});
661 +
662 +// control icon
663 +$btnAddIcon.on('click', function () {
664 + showSubMenu('icon');
665 + activateIconMode();
666 +});
667 +
668 +function onClickIconSubMenu(event) {
669 + var element = event.target || event.srcElement;
670 + var iconType = $(element).attr('data-icon-type');
671 +
672 + imageEditor.once('mousedown', function (e, originPointer) {
673 + imageEditor
674 + .addIcon(iconType, {
675 + left: originPointer.x,
676 + top: originPointer.y,
677 + })
678 + .then(function (objectProps) {
679 + // console.log(objectProps);
680 + });
681 + });
682 +}
683 +
684 +$btnRegisterIcon.on('click', function () {
685 + $iconSubMenu
686 + .find('.menu-item')
687 + .eq(3)
688 + .after('<li id="customArrow" class="menu-item icon-text" data-icon-type="customArrow">↑</li>');
689 +
690 + imageEditor.registerIcons({
691 + customArrow: 'M 60 0 L 120 60 H 90 L 75 45 V 180 H 45 V 45 L 30 60 H 0 Z',
692 + });
693 +
694 + $btnRegisterIcon.off('click');
695 +
696 + $iconSubMenu.on('click', '#customArrow', onClickIconSubMenu);
697 +});
698 +
699 +$iconSubMenu.on('click', '.icon-text', onClickIconSubMenu);
700 +
701 +iconColorpicker.on('selectColor', function (event) {
702 + imageEditor.changeIconColor(activeObjectId, event.color);
703 +});
704 +
705 +// control mask filter
706 +$btnMaskFilter.on('click', function () {
707 + imageEditor.stopDrawingMode();
708 + $displayingSubMenu.hide();
709 +
710 + $displayingSubMenu = $filterSubMenu.show();
711 +});
712 +
713 +$btnImageFilter.on('click', function () {
714 + var filters = {
715 + grayscale: $inputCheckGrayscale,
716 + invert: $inputCheckInvert,
717 + sepia: $inputCheckSepia,
718 + sepia2: $inputCheckSepia2,
719 + blur: $inputCheckBlur,
720 + shapren: $inputCheckSharpen,
721 + emboss: $inputCheckEmboss,
722 + removeWhite: $inputCheckRemoveWhite,
723 + brightness: $inputCheckBrightness,
724 + noise: $inputCheckNoise,
725 + pixelate: $inputCheckPixelate,
726 + tint: $inputCheckTint,
727 + multiply: $inputCheckMultiply,
728 + blend: $inputCheckBlend,
729 + colorFilter: $inputCheckColorFilter,
730 + };
731 +
732 + tui.util.forEach(filters, function ($value, key) {
733 + $value.prop('checked', imageEditor.hasFilter(key));
734 + });
735 + $displayingSubMenu.hide();
736 +
737 + $displayingSubMenu = $imageFilterSubMenu.show();
738 +});
739 +
740 +$btnLoadMaskImage.on('change', function () {
741 + var file;
742 + var imgUrl;
743 +
744 + if (!supportingFileAPI) {
745 + alert('This browser does not support file-api');
746 + }
747 +
748 + file = event.target.files[0];
749 +
750 + if (file) {
751 + imgUrl = URL.createObjectURL(file);
752 +
753 + imageEditor.loadImageFromURL(imageEditor.toDataURL(), 'FilterImage').then(function () {
754 + imageEditor.addImageObject(imgUrl).then(function (objectProps) {
755 + URL.revokeObjectURL(file);
756 + console.log(objectProps);
757 + });
758 + });
759 + }
760 +});
761 +
762 +$btnApplyMask.on('click', function () {
763 + imageEditor
764 + .applyFilter('mask', {
765 + maskObjId: activeObjectId,
766 + })
767 + .then(function (result) {
768 + console.log(result);
769 + });
770 +});
771 +
772 +$inputCheckGrayscale.on('change', function () {
773 + applyOrRemoveFilter(this.checked, 'Grayscale', null);
774 +});
775 +
776 +$inputCheckInvert.on('change', function () {
777 + applyOrRemoveFilter(this.checked, 'Invert', null);
778 +});
779 +
780 +$inputCheckSepia.on('change', function () {
781 + applyOrRemoveFilter(this.checked, 'Sepia', null);
782 +});
783 +
784 +$inputCheckSepia2.on('change', function () {
785 + applyOrRemoveFilter(this.checked, 'Sepia2', null);
786 +});
787 +
788 +$inputCheckBlur.on('change', function () {
789 + applyOrRemoveFilter(this.checked, 'Blur', null);
790 +});
791 +
792 +$inputCheckSharpen.on('change', function () {
793 + applyOrRemoveFilter(this.checked, 'Sharpen', null);
794 +});
795 +
796 +$inputCheckEmboss.on('change', function () {
797 + applyOrRemoveFilter(this.checked, 'Emboss', null);
798 +});
799 +
800 +$inputCheckRemoveWhite.on('change', function () {
801 + applyOrRemoveFilter(this.checked, 'removeWhite', {
802 + threshold: parseInt($inputRangeRemoveWhiteThreshold.val(), 10),
803 + distance: parseInt($inputRangeRemoveWhiteDistance.val(), 10),
804 + });
805 +});
806 +
807 +$inputRangeRemoveWhiteThreshold.on('change', function () {
808 + applyOrRemoveFilter($inputCheckRemoveWhite.is(':checked'), 'removeWhite', {
809 + threshold: parseInt(this.value, 10),
810 + });
811 +});
812 +
813 +$inputRangeRemoveWhiteDistance.on('change', function () {
814 + applyOrRemoveFilter($inputCheckRemoveWhite.is(':checked'), 'removeWhite', {
815 + distance: parseInt(this.value, 10),
816 + });
817 +});
818 +
819 +$inputCheckBrightness.on('change', function () {
820 + applyOrRemoveFilter(this.checked, 'brightness', {
821 + brightness: parseInt($inputRangeBrightnessValue.val(), 10),
822 + });
823 +});
824 +
825 +$inputRangeBrightnessValue.on('change', function () {
826 + applyOrRemoveFilter($inputCheckBrightness.is(':checked'), 'brightness', {
827 + brightness: parseInt(this.value, 10),
828 + });
829 +});
830 +
831 +$inputCheckNoise.on('change', function () {
832 + applyOrRemoveFilter(this.checked, 'noise', {
833 + noise: parseInt($inputRangeNoiseValue.val(), 10),
834 + });
835 +});
836 +
837 +$inputRangeNoiseValue.on('change', function () {
838 + applyOrRemoveFilter($inputCheckNoise.is(':checked'), 'noise', {
839 + noise: parseInt(this.value, 10),
840 + });
841 +});
842 +
843 +$inputCheckPixelate.on('change', function () {
844 + applyOrRemoveFilter(this.checked, 'pixelate', {
845 + blocksize: parseInt($inputRangePixelateValue.val(), 10),
846 + });
847 +});
848 +
849 +$inputRangePixelateValue.on('change', function () {
850 + applyOrRemoveFilter($inputCheckPixelate.is(':checked'), 'pixelate', {
851 + blocksize: parseInt(this.value, 10),
852 + });
853 +});
854 +
855 +$inputCheckTint.on('change', function () {
856 + applyOrRemoveFilter(this.checked, 'tint', {
857 + color: tintColorpicker.getColor(),
858 + opacity: parseFloat($inputRangeTintOpacityValue.val()),
859 + });
860 +});
861 +
862 +tintColorpicker.on('selectColor', function (e) {
863 + applyOrRemoveFilter($inputCheckTint.is(':checked'), 'tint', {
864 + color: e.color,
865 + });
866 +});
867 +
868 +$inputRangeTintOpacityValue.on('change', function () {
869 + applyOrRemoveFilter($inputCheckTint.is(':checked'), 'tint', {
870 + opacity: parseFloat($inputRangeTintOpacityValue.val()),
871 + });
872 +});
873 +
874 +$inputCheckMultiply.on('change', function () {
875 + applyOrRemoveFilter(this.checked, 'multiply', {
876 + color: multiplyColorpicker.getColor(),
877 + });
878 +});
879 +
880 +multiplyColorpicker.on('selectColor', function (e) {
881 + applyOrRemoveFilter($inputCheckMultiply.is(':checked'), 'multiply', {
882 + color: e.color,
883 + });
884 +});
885 +
886 +$inputCheckBlend.on('change', function () {
887 + applyOrRemoveFilter(this.checked, 'blend', {
888 + color: blendColorpicker.getColor(),
889 + mode: $selectBlendType.val(),
890 + });
891 +});
892 +
893 +blendColorpicker.on('selectColor', function (e) {
894 + applyOrRemoveFilter($inputCheckBlend.is(':checked'), 'blend', {
895 + color: e.color,
896 + });
897 +});
898 +
899 +$selectBlendType.on('change', function () {
900 + applyOrRemoveFilter($inputCheckBlend.is(':checked'), 'blend', {
901 + mode: this.value,
902 + });
903 +});
904 +
905 +$inputCheckColorFilter.on('change', function () {
906 + applyOrRemoveFilter(this.checked, 'colorFilter', {
907 + color: '#FFFFFF',
908 + threshold: $inputRangeColorFilterValue.val(),
909 + });
910 +});
911 +
912 +$inputRangeColorFilterValue.on('change', function () {
913 + applyOrRemoveFilter($inputCheckColorFilter.is(':checked'), 'colorFilter', {
914 + threshold: this.value,
915 + });
916 +});
917 +
918 +// Etc..
919 +
920 +// Load sample image
921 +imageEditor.loadImageFromURL('img/sampleImage.jpg', 'SampleImage').then(function (sizeValue) {
922 + console.log(sizeValue);
923 + imageEditor.clearUndoStack();
924 +});
925 +
926 +// IE9 Unselectable
927 +$('.menu').on('selectstart', function () {
928 + return false;
929 +});
1 +/**
2 + * mobile.js
3 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
4 + * @fileoverview
5 + */
6 +/* eslint-disable vars-on-top,no-var,strict,prefer-template,prefer-arrow-callback,prefer-destructuring,object-shorthand,require-jsdoc,complexity */
7 +'use strict';
8 +
9 +var MAX_RESOLUTION = 3264 * 2448; // 8MP (Mega Pixel)
10 +
11 +var supportingFileAPI = !!(window.File && window.FileList && window.FileReader);
12 +var rImageType = /data:(image\/.+);base64,/;
13 +var shapeOpt = {
14 + fill: '#fff',
15 + stroke: '#000',
16 + strokeWidth: 10,
17 +};
18 +var activeObjectId;
19 +
20 +// Selector of image editor controls
21 +var submenuClass = '.submenu';
22 +var hiddenmenuClass = '.hiddenmenu';
23 +
24 +var $controls = $('.tui-image-editor-controls');
25 +var $menuButtons = $controls.find('.menu-button');
26 +var $submenuButtons = $controls.find('.submenu-button');
27 +var $btnShowMenu = $controls.find('.btn-prev');
28 +var $msg = $controls.find('.msg');
29 +
30 +var $subMenus = $controls.find(submenuClass);
31 +var $hiddenMenus = $controls.find(hiddenmenuClass);
32 +
33 +// Image editor controls - top menu buttons
34 +var $inputImage = $('#input-image-file');
35 +var $btnDownload = $('#btn-download');
36 +var $btnUndo = $('#btn-undo');
37 +var $btnRedo = $('#btn-redo');
38 +var $btnRemoveActiveObject = $('#btn-remove-active-object');
39 +
40 +// Image editor controls - bottom menu buttons
41 +var $btnCrop = $('#btn-crop');
42 +var $btnAddText = $('#btn-add-text');
43 +
44 +// Image editor controls - bottom submenu buttons
45 +var $btnApplyCrop = $('#btn-apply-crop');
46 +var $btnFlipX = $('#btn-flip-x');
47 +var $btnFlipY = $('#btn-flip-y');
48 +var $btnRotateClockwise = $('#btn-rotate-clockwise');
49 +var $btnRotateCounterClockWise = $('#btn-rotate-counter-clockwise');
50 +var $btnAddArrowIcon = $('#btn-add-arrow-icon');
51 +var $btnAddCancelIcon = $('#btn-add-cancel-icon');
52 +var $btnAddCustomIcon = $('#btn-add-custom-icon');
53 +var $btnFreeDrawing = $('#btn-free-drawing');
54 +var $btnLineDrawing = $('#btn-line-drawing');
55 +var $btnAddRect = $('#btn-add-rect');
56 +var $btnAddSquare = $('#btn-add-square');
57 +var $btnAddEllipse = $('#btn-add-ellipse');
58 +var $btnAddCircle = $('#btn-add-circle');
59 +var $btnAddTriangle = $('#btn-add-triangle');
60 +var $btnChangeTextStyle = $('.btn-change-text-style');
61 +
62 +// Image editor controls - etc.
63 +var $inputTextSizeRange = $('#input-text-size-range');
64 +var $inputBrushWidthRange = $('#input-brush-range');
65 +var $inputStrokeWidthRange = $('#input-stroke-range');
66 +var $inputCheckTransparent = $('#input-check-transparent');
67 +
68 +// Colorpicker
69 +var iconColorpicker = tui.colorPicker.create({
70 + container: $('#tui-icon-color-picker')[0],
71 + color: '#000000',
72 +});
73 +
74 +var textColorpicker = tui.colorPicker.create({
75 + container: $('#tui-text-color-picker')[0],
76 + color: '#000000',
77 +});
78 +
79 +var brushColorpicker = tui.colorPicker.create({
80 + container: $('#tui-brush-color-picker')[0],
81 + color: '#000000',
82 +});
83 +
84 +var shapeColorpicker = tui.colorPicker.create({
85 + container: $('#tui-shape-color-picker')[0],
86 + color: '#000000',
87 +});
88 +
89 +// Create image editor
90 +var imageEditor = new tui.ImageEditor('.tui-image-editor', {
91 + cssMaxWidth: document.documentElement.clientWidth,
92 + cssMaxHeight: document.documentElement.clientHeight,
93 + selectionStyle: {
94 + cornerSize: 50,
95 + rotatingPointOffset: 100,
96 + },
97 +});
98 +
99 +var $displayingSubMenu, $displayingHiddenMenu;
100 +
101 +function hexToRGBa(hex, alpha) {
102 + var r = parseInt(hex.slice(1, 3), 16);
103 + var g = parseInt(hex.slice(3, 5), 16);
104 + var b = parseInt(hex.slice(5, 7), 16);
105 + var a = alpha || 1;
106 +
107 + return 'rgba(' + r + ', ' + g + ', ' + b + ', ' + a + ')';
108 +}
109 +
110 +function base64ToBlob(data) {
111 + var mimeString = '';
112 + var raw, uInt8Array, i, rawLength;
113 +
114 + raw = data.replace(rImageType, function (header, imageType) {
115 + mimeString = imageType;
116 +
117 + return '';
118 + });
119 +
120 + raw = atob(raw);
121 + rawLength = raw.length;
122 + uInt8Array = new Uint8Array(rawLength); // eslint-disable-line
123 +
124 + for (i = 0; i < rawLength; i += 1) {
125 + uInt8Array[i] = raw.charCodeAt(i);
126 + }
127 +
128 + return new Blob([uInt8Array], { type: mimeString });
129 +}
130 +
131 +function getBrushSettings() {
132 + var brushWidth = $inputBrushWidthRange.val();
133 + var brushColor = brushColorpicker.getColor();
134 +
135 + return {
136 + width: brushWidth,
137 + color: hexToRGBa(brushColor, 0.5),
138 + };
139 +}
140 +
141 +function activateShapeMode() {
142 + imageEditor.stopDrawingMode();
143 +}
144 +
145 +function activateIconMode() {
146 + imageEditor.stopDrawingMode();
147 +}
148 +
149 +function activateTextMode() {
150 + if (imageEditor.getDrawingMode() !== 'TEXT') {
151 + imageEditor.stopDrawingMode();
152 + imageEditor.startDrawingMode('TEXT');
153 + }
154 +}
155 +
156 +function setTextToolbar(obj) {
157 + var fontSize = obj.fontSize;
158 + var fontColor = obj.fill;
159 +
160 + $inputTextSizeRange.val(fontSize);
161 + textColorpicker.setColor(fontColor);
162 +}
163 +
164 +function setIconToolbar(obj) {
165 + var iconColor = obj.fill;
166 +
167 + iconColorpicker.setColor(iconColor);
168 +}
169 +
170 +function setShapeToolbar(obj) {
171 + var strokeColor, fillColor, isTransparent;
172 + var colorType = $('[name="select-color-type"]:checked').val();
173 +
174 + if (colorType === 'stroke') {
175 + strokeColor = obj.stroke;
176 + isTransparent = strokeColor === 'transparent';
177 +
178 + if (!isTransparent) {
179 + shapeColorpicker.setColor(strokeColor);
180 + }
181 + } else if (colorType === 'fill') {
182 + fillColor = obj.fill;
183 + isTransparent = fillColor === 'transparent';
184 +
185 + if (!isTransparent) {
186 + shapeColorpicker.setColor(fillColor);
187 + }
188 + }
189 +
190 + $inputCheckTransparent.prop('checked', isTransparent);
191 + $inputStrokeWidthRange.val(obj.strokeWith);
192 +}
193 +
194 +function showSubMenu(type) {
195 + var index;
196 +
197 + switch (type) {
198 + case 'shape':
199 + index = 3;
200 + break;
201 + case 'icon':
202 + index = 4;
203 + break;
204 + case 'text':
205 + index = 5;
206 + break;
207 + default:
208 + index = 0;
209 + }
210 +
211 + $displayingSubMenu.hide();
212 + $displayingHiddenMenu.hide();
213 +
214 + $displayingSubMenu = $menuButtons.eq(index).parent().find(submenuClass).show();
215 +}
216 +
217 +// Bind custom event of image editor
218 +imageEditor.on({
219 + undoStackChanged: function (length) {
220 + if (length) {
221 + $btnUndo.removeClass('disabled');
222 + } else {
223 + $btnUndo.addClass('disabled');
224 + }
225 + },
226 + redoStackChanged: function (length) {
227 + if (length) {
228 + $btnRedo.removeClass('disabled');
229 + } else {
230 + $btnRedo.addClass('disabled');
231 + }
232 + },
233 + objectScaled: function (obj) {
234 + if (obj.type === 'text') {
235 + $inputTextSizeRange.val(obj.fontSize);
236 + }
237 + },
238 + objectActivated: function (obj) {
239 + activeObjectId = obj.id;
240 + if (obj.type === 'rect' || obj.type === 'circle' || obj.type === 'triangle') {
241 + showSubMenu('shape');
242 + setShapeToolbar(obj);
243 + activateShapeMode();
244 + } else if (obj.type === 'icon') {
245 + showSubMenu('icon');
246 + setIconToolbar(obj);
247 + activateIconMode();
248 + } else if (obj.type === 'text') {
249 + showSubMenu('text');
250 + setTextToolbar(obj);
251 + activateTextMode();
252 + }
253 + },
254 +});
255 +
256 +// Image editor controls action
257 +$menuButtons.on('click', function () {
258 + $displayingSubMenu = $(this).parent().find(submenuClass).show();
259 + $displayingHiddenMenu = $(this).parent().find(hiddenmenuClass);
260 +});
261 +
262 +$submenuButtons.on('click', function () {
263 + $displayingHiddenMenu.hide();
264 + $displayingHiddenMenu = $(this).parent().find(hiddenmenuClass).show();
265 +});
266 +
267 +$btnShowMenu.on('click', function () {
268 + $displayingSubMenu.hide();
269 + $displayingHiddenMenu.hide();
270 + $msg.show();
271 +
272 + imageEditor.stopDrawingMode();
273 +});
274 +
275 +// Image load action
276 +$inputImage.on('change', function (event) {
277 + var file;
278 + var img;
279 + var resolution;
280 +
281 + if (!supportingFileAPI) {
282 + alert('This browser does not support file-api');
283 + }
284 +
285 + file = event.target.files[0];
286 +
287 + if (file) {
288 + img = new Image();
289 +
290 + img.onload = function () {
291 + resolution = this.width * this.height;
292 +
293 + if (resolution <= MAX_RESOLUTION) {
294 + imageEditor.loadImageFromFile(file).then(function () {
295 + imageEditor.clearUndoStack();
296 + });
297 + } else {
298 + alert("Loaded image's resolution is too large!\nRecommended resolution is 3264 * 2448!");
299 + }
300 +
301 + URL.revokeObjectURL(file);
302 + };
303 +
304 + img.src = URL.createObjectURL(file);
305 + }
306 +});
307 +
308 +// Undo action
309 +$btnUndo.on('click', function () {
310 + if (!$(this).hasClass('disabled')) {
311 + imageEditor.undo();
312 + }
313 +});
314 +
315 +// Redo action
316 +$btnRedo.on('click', function () {
317 + if (!$(this).hasClass('disabled')) {
318 + imageEditor.redo();
319 + }
320 +});
321 +
322 +// Remove active object action
323 +$btnRemoveActiveObject.on('click', function () {
324 + imageEditor.removeObject(activeObjectId);
325 +});
326 +
327 +// Download action
328 +$btnDownload.on('click', function () {
329 + var imageName = imageEditor.getImageName();
330 + var dataURL = imageEditor.toDataURL();
331 + var blob, type, w;
332 +
333 + if (supportingFileAPI) {
334 + blob = base64ToBlob(dataURL);
335 + type = blob.type.split('/')[1];
336 + if (imageName.split('.').pop() !== type) {
337 + imageName += '.' + type;
338 + }
339 +
340 + // Library: FileSaver - saveAs
341 + saveAs(blob, imageName); // eslint-disable-line
342 + } else {
343 + alert('This browser needs a file-server');
344 + w = window.open();
345 + w.document.body.innerHTML = '<img src=' + dataURL + '>';
346 + }
347 +});
348 +
349 +// Crop menu action
350 +$btnCrop.on('click', function () {
351 + imageEditor.startDrawingMode('CROPPER');
352 +});
353 +
354 +$btnApplyCrop.on('click', function () {
355 + imageEditor.crop(imageEditor.getCropzoneRect()).then(function () {
356 + imageEditor.stopDrawingMode();
357 + $subMenus.removeClass('show');
358 + $hiddenMenus.removeClass('show');
359 + });
360 +});
361 +
362 +// Orientation menu action
363 +$btnRotateClockwise.on('click', function () {
364 + imageEditor.rotate(90);
365 +});
366 +
367 +$btnRotateCounterClockWise.on('click', function () {
368 + imageEditor.rotate(-90);
369 +});
370 +
371 +$btnFlipX.on('click', function () {
372 + imageEditor.flipX();
373 +});
374 +
375 +$btnFlipY.on('click', function () {
376 + imageEditor.flipY();
377 +});
378 +
379 +// Icon menu action
380 +$btnAddArrowIcon.on('click', function () {
381 + imageEditor.addIcon('arrow');
382 +});
383 +
384 +$btnAddCancelIcon.on('click', function () {
385 + imageEditor.addIcon('cancel');
386 +});
387 +
388 +$btnAddCustomIcon.on('click', function () {
389 + imageEditor.addIcon('customArrow');
390 +});
391 +
392 +iconColorpicker.on('selectColor', function (event) {
393 + imageEditor.changeIconColor(activeObjectId, event.color);
394 +});
395 +
396 +// Text menu action
397 +$btnAddText.on('click', function () {
398 + var initText = 'DoubleClick';
399 +
400 + imageEditor.startDrawingMode('TEXT');
401 + imageEditor.addText(initText, {
402 + styles: {
403 + fontSize: parseInt($inputTextSizeRange.val(), 10),
404 + },
405 + });
406 +});
407 +
408 +$btnChangeTextStyle.on('click', function () {
409 + var styleType = $(this).attr('data-style-type');
410 + var styleObj = {};
411 + var styleObjKey;
412 +
413 + switch (styleType) {
414 + case 'bold':
415 + styleObjKey = 'fontWeight';
416 + break;
417 + case 'italic':
418 + styleObjKey = 'fontStyle';
419 + break;
420 + case 'underline':
421 + styleObjKey = 'underline';
422 + break;
423 + case 'left':
424 + styleObjKey = 'textAlign';
425 + break;
426 + case 'center':
427 + styleObjKey = 'textAlign';
428 + break;
429 + case 'right':
430 + styleObjKey = 'textAlign';
431 + break;
432 + default:
433 + styleObjKey = '';
434 + }
435 +
436 + styleObj[styleObjKey] = styleType;
437 +
438 + imageEditor.changeTextStyle(activeObjectId, styleObj);
439 +});
440 +
441 +$inputTextSizeRange.on('change', function () {
442 + imageEditor.changeTextStyle(activeObjectId, {
443 + fontSize: parseInt($(this).val(), 10),
444 + });
445 +});
446 +
447 +textColorpicker.on('selectColor', function (event) {
448 + imageEditor.changeTextStyle(activeObjectId, {
449 + fill: event.color,
450 + });
451 +});
452 +
453 +// Draw line menu action
454 +$btnFreeDrawing.on('click', function () {
455 + var settings = getBrushSettings();
456 +
457 + imageEditor.stopDrawingMode();
458 + imageEditor.startDrawingMode('FREE_DRAWING', settings);
459 +});
460 +
461 +$btnLineDrawing.on('click', function () {
462 + var settings = getBrushSettings();
463 +
464 + imageEditor.stopDrawingMode();
465 + imageEditor.startDrawingMode('LINE_DRAWING', settings);
466 +});
467 +
468 +$inputBrushWidthRange.on('change', function () {
469 + imageEditor.setBrush({
470 + width: parseInt($(this).val(), 10),
471 + });
472 +});
473 +
474 +brushColorpicker.on('selectColor', function (event) {
475 + imageEditor.setBrush({
476 + color: hexToRGBa(event.color, 0.5),
477 + });
478 +});
479 +
480 +// Add shape menu action
481 +$btnAddRect.on('click', function () {
482 + imageEditor.addShape(
483 + 'rect',
484 + tui.util.extend(
485 + {
486 + width: 500,
487 + height: 300,
488 + },
489 + shapeOpt
490 + )
491 + );
492 +});
493 +
494 +$btnAddSquare.on('click', function () {
495 + imageEditor.addShape(
496 + 'rect',
497 + tui.util.extend(
498 + {
499 + width: 400,
500 + height: 400,
501 + isRegular: true,
502 + },
503 + shapeOpt
504 + )
505 + );
506 +});
507 +
508 +$btnAddEllipse.on('click', function () {
509 + imageEditor.addShape(
510 + 'circle',
511 + tui.util.extend(
512 + {
513 + rx: 300,
514 + ry: 200,
515 + },
516 + shapeOpt
517 + )
518 + );
519 +});
520 +
521 +$btnAddCircle.on('click', function () {
522 + imageEditor.addShape(
523 + 'circle',
524 + tui.util.extend(
525 + {
526 + rx: 200,
527 + ry: 200,
528 + isRegular: true,
529 + },
530 + shapeOpt
531 + )
532 + );
533 +});
534 +
535 +$btnAddTriangle.on('click', function () {
536 + imageEditor.addShape(
537 + 'triangle',
538 + tui.util.extend(
539 + {
540 + width: 500,
541 + height: 400,
542 + isRegular: true,
543 + },
544 + shapeOpt
545 + )
546 + );
547 +});
548 +
549 +$inputStrokeWidthRange.on('change', function () {
550 + imageEditor.changeShape(activeObjectId, {
551 + strokeWidth: parseInt($(this).val(), 10),
552 + });
553 +});
554 +
555 +$inputCheckTransparent.on('change', function () {
556 + var colorType = $('[name="select-color-type"]:checked').val();
557 + var isTransparent = $(this).prop('checked');
558 + var color;
559 +
560 + if (!isTransparent) {
561 + color = shapeColorpicker.getColor();
562 + } else {
563 + color = 'transparent';
564 + }
565 +
566 + if (colorType === 'stroke') {
567 + imageEditor.changeShape(activeObjectId, {
568 + stroke: color,
569 + });
570 + } else if (colorType === 'fill') {
571 + imageEditor.changeShape(activeObjectId, {
572 + fill: color,
573 + });
574 + }
575 +});
576 +
577 +shapeColorpicker.on('selectColor', function (event) {
578 + var colorType = $('[name="select-color-type"]:checked').val();
579 + var isTransparent = $inputCheckTransparent.prop('checked');
580 + var color = event.color;
581 +
582 + if (isTransparent) {
583 + return;
584 + }
585 +
586 + if (colorType === 'stroke') {
587 + imageEditor.changeShape(activeObjectId, {
588 + stroke: color,
589 + });
590 + } else if (colorType === 'fill') {
591 + imageEditor.changeShape(activeObjectId, {
592 + fill: color,
593 + });
594 + }
595 +});
596 +
597 +// Load sample image
598 +imageEditor.loadImageFromURL('img/sampleImage.jpg', 'SampleImage').then(function () {
599 + imageEditor.clearUndoStack();
600 +});
1 +var blackTheme = {
2 + 'common.bi.image': 'https://uicdn.toast.com/toastui/img/tui-image-editor-bi.png',
3 + 'common.bisize.width': '251px',
4 + 'common.bisize.height': '21px',
5 + 'common.backgroundImage': 'none',
6 + 'common.backgroundColor': '#1e1e1e',
7 + 'common.border': '0px',
8 +
9 + // header
10 + 'header.backgroundImage': 'none',
11 + 'header.backgroundColor': 'transparent',
12 + 'header.border': '0px',
13 +
14 + // load button
15 + 'loadButton.backgroundColor': '#fff',
16 + 'loadButton.border': '1px solid #ddd',
17 + 'loadButton.color': '#222',
18 + 'loadButton.fontFamily': "'Noto Sans', sans-serif",
19 + 'loadButton.fontSize': '12px',
20 +
21 + // download button
22 + 'downloadButton.backgroundColor': '#fdba3b',
23 + 'downloadButton.border': '1px solid #fdba3b',
24 + 'downloadButton.color': '#fff',
25 + 'downloadButton.fontFamily': "'Noto Sans', sans-serif",
26 + 'downloadButton.fontSize': '12px',
27 +
28 + // main icons
29 + 'menu.normalIcon.color': '#8a8a8a',
30 + 'menu.activeIcon.color': '#555555',
31 + 'menu.disabledIcon.color': '#434343',
32 + 'menu.hoverIcon.color': '#e9e9e9',
33 + 'menu.iconSize.width': '24px',
34 + 'menu.iconSize.height': '24px',
35 +
36 + // submenu icons
37 + 'submenu.normalIcon.color': '#8a8a8a',
38 + 'submenu.activeIcon.color': '#e9e9e9',
39 + 'submenu.iconSize.width': '32px',
40 + 'submenu.iconSize.height': '32px',
41 +
42 + // submenu primary color
43 + 'submenu.backgroundColor': '#1e1e1e',
44 + 'submenu.partition.color': '#3c3c3c',
45 +
46 + // submenu labels
47 + 'submenu.normalLabel.color': '#8a8a8a',
48 + 'submenu.normalLabel.fontWeight': 'lighter',
49 + 'submenu.activeLabel.color': '#fff',
50 + 'submenu.activeLabel.fontWeight': 'lighter',
51 +
52 + // checkbox style
53 + 'checkbox.border': '0px',
54 + 'checkbox.backgroundColor': '#fff',
55 +
56 + // range style
57 + 'range.pointer.color': '#fff',
58 + 'range.bar.color': '#666',
59 + 'range.subbar.color': '#d1d1d1',
60 +
61 + 'range.disabledPointer.color': '#414141',
62 + 'range.disabledBar.color': '#282828',
63 + 'range.disabledSubbar.color': '#414141',
64 +
65 + 'range.value.color': '#fff',
66 + 'range.value.fontWeight': 'lighter',
67 + 'range.value.fontSize': '11px',
68 + 'range.value.border': '1px solid #353535',
69 + 'range.value.backgroundColor': '#151515',
70 + 'range.title.color': '#fff',
71 + 'range.title.fontWeight': 'lighter',
72 +
73 + // colorpicker style
74 + 'colorpicker.button.border': '1px solid #1e1e1e',
75 + 'colorpicker.title.color': '#fff',
76 +};
1 +var whiteTheme = {
2 + 'common.bi.image': 'https://uicdn.toast.com/toastui/img/tui-image-editor-bi.png',
3 + 'common.bisize.width': '251px',
4 + 'common.bisize.height': '21px',
5 + 'common.backgroundImage': './img/bg.png',
6 + 'common.backgroundColor': '#fff',
7 + 'common.border': '1px solid #c1c1c1',
8 +
9 + // header
10 + 'header.backgroundImage': 'none',
11 + 'header.backgroundColor': 'transparent',
12 + 'header.border': '0px',
13 +
14 + // load button
15 + 'loadButton.backgroundColor': '#fff',
16 + 'loadButton.border': '1px solid #ddd',
17 + 'loadButton.color': '#222',
18 + 'loadButton.fontFamily': "'Noto Sans', sans-serif",
19 + 'loadButton.fontSize': '12px',
20 +
21 + // download button
22 + 'downloadButton.backgroundColor': '#fdba3b',
23 + 'downloadButton.border': '1px solid #fdba3b',
24 + 'downloadButton.color': '#fff',
25 + 'downloadButton.fontFamily': "'Noto Sans', sans-serif",
26 + 'downloadButton.fontSize': '12px',
27 +
28 + // main icons
29 + 'menu.normalIcon.color': '#8a8a8a',
30 + 'menu.activeIcon.color': '#555555',
31 + 'menu.disabledIcon.color': '#434343',
32 + 'menu.hoverIcon.color': '#e9e9e9',
33 + 'menu.iconSize.width': '24px',
34 + 'menu.iconSize.height': '24px',
35 +
36 + // submenu icons
37 + 'submenu.normalIcon.color': '#8a8a8a',
38 + 'submenu.activeIcon.color': '#555555',
39 + 'submenu.iconSize.width': '32px',
40 + 'submenu.iconSize.height': '32px',
41 +
42 + // submenu primary color
43 + 'submenu.backgroundColor': 'transparent',
44 + 'submenu.partition.color': '#e5e5e5',
45 +
46 + // submenu labels
47 + 'submenu.normalLabel.color': '#858585',
48 + 'submenu.normalLabel.fontWeight': 'normal',
49 + 'submenu.activeLabel.color': '#000',
50 + 'submenu.activeLabel.fontWeight': 'normal',
51 +
52 + // checkbox style
53 + 'checkbox.border': '1px solid #ccc',
54 + 'checkbox.backgroundColor': '#fff',
55 +
56 + // rango style
57 + 'range.pointer.color': '#333',
58 + 'range.bar.color': '#ccc',
59 + 'range.subbar.color': '#606060',
60 +
61 + 'range.disabledPointer.color': '#d3d3d3',
62 + 'range.disabledBar.color': 'rgba(85,85,85,0.06)',
63 + 'range.disabledSubbar.color': 'rgba(51,51,51,0.2)',
64 +
65 + 'range.value.color': '#000',
66 + 'range.value.fontWeight': 'normal',
67 + 'range.value.fontSize': '11px',
68 + 'range.value.border': '0',
69 + 'range.value.backgroundColor': '#f5f5f5',
70 + 'range.title.color': '#000',
71 + 'range.title.fontWeight': 'lighter',
72 +
73 + // colorpicker style
74 + 'colorpicker.button.border': '0px',
75 + 'colorpicker.title.color': '#000',
76 +};
1 +// Type definitions for TOAST UI Image Editor v3.9.0
2 +// TypeScript Version: 3.2.2
3 +
4 +declare namespace tuiImageEditor {
5 + type AngleType = number;
6 +
7 + interface IThemeConfig {
8 + 'common.bi.image'?: string;
9 + 'common.bisize.width'?: string;
10 + 'common.bisize.height'?: string;
11 + 'common.backgroundImage'?: string;
12 + 'common.backgroundColor'?: string;
13 + 'common.border'?: string;
14 + 'header.backgroundImage'?: string;
15 + 'header.backgroundColor'?: string;
16 + 'header.border'?: string;
17 + 'loadButton.backgroundColor'?: string;
18 + 'loadButton.border'?: string;
19 + 'loadButton.color'?: string;
20 + 'loadButton.fontFamily'?: string;
21 + 'loadButton.fontSize'?: string;
22 + 'downloadButton.backgroundColor'?: string;
23 + 'downloadButton.border'?: string;
24 + 'downloadButton.color'?: string;
25 + 'downloadButton.fontFamily'?: string;
26 + 'downloadButton.fontSize'?: string;
27 + 'menu.normalIcon.path'?: string;
28 + 'menu.normalIcon.name'?: string;
29 + 'menu.activeIcon.path'?: string;
30 + 'menu.activeIcon.name'?: string;
31 + 'menu.iconSize.width'?: string;
32 + 'menu.iconSize.height'?: string;
33 + 'submenu.backgroundColor'?: string;
34 + 'submenu.partition.color'?: string;
35 + 'submenu.normalIcon.path'?: string;
36 + 'submenu.normalIcon.name'?: string;
37 + 'submenu.activeIcon.path'?: string;
38 + 'submenu.activeIcon.name'?: string;
39 + 'submenu.iconSize.width'?: string;
40 + 'submenu.iconSize.height'?: string;
41 + 'submenu.normalLabel.color'?: string;
42 + 'submenu.normalLabel.fontWeight'?: string;
43 + 'submenu.activeLabel.color'?: string;
44 + 'submenu.activeLabel.fontWeight'?: string;
45 + 'checkbox.border'?: string;
46 + 'checkbox.backgroundColor'?: string;
47 + 'range.pointer.color'?: string;
48 + 'range.bar.color'?: string;
49 + 'range.subbar.color'?: string;
50 + 'range.value.color'?: string;
51 + 'range.value.fontWeight'?: string;
52 + 'range.value.fontSize'?: string;
53 + 'range.value.border'?: string;
54 + 'range.value.backgroundColor'?: string;
55 + 'range.title.color'?: string;
56 + 'range.title.fontWeight'?: string;
57 + 'colorpicker.button.border'?: string;
58 + 'colorpicker.title.color'?: string;
59 + }
60 +
61 + interface IIconInfo {
62 + [propName: string]: string;
63 + }
64 +
65 + interface IIconOptions {
66 + fill?: string;
67 + left?: number;
68 + top?: number;
69 + }
70 +
71 + interface IShapeOptions {
72 + fill?: string;
73 + stroke?: string;
74 + strokeWidth?: number;
75 + width?: number;
76 + height?: number;
77 + rx?: number;
78 + ry?: number;
79 + left?: number;
80 + top?: number;
81 + isRegular?: boolean;
82 + }
83 +
84 + interface IGenerateTextOptions {
85 + styles?: ITextStyleConfig;
86 + position?: {
87 + x: number;
88 + y: number;
89 + };
90 + }
91 +
92 + interface ITextStyleConfig {
93 + fill?: string;
94 + fontFamily?: string;
95 + fontSize?: number;
96 + fontStyle?: string;
97 + fontWeight?: string;
98 + textAlign?: string;
99 + textDecoration?: string;
100 + }
101 +
102 + interface IRectConfig {
103 + left: number;
104 + top: number;
105 + width: number;
106 + height: number;
107 + }
108 +
109 + interface ICanvasSize {
110 + width: number;
111 + height: number;
112 + }
113 +
114 + interface IBrushOptions {
115 + width: number;
116 + color: string;
117 + }
118 +
119 + interface IPositionConfig {
120 + x: number;
121 + y: number;
122 + originX: string;
123 + originY: string;
124 + }
125 +
126 + interface IToDataURLOptions {
127 + format?: string;
128 + quality?: number;
129 + multiplier?: number;
130 + left?: number;
131 + top?: number;
132 + width?: number;
133 + height?: number;
134 + }
135 +
136 + interface IGraphicObjectProps {
137 + id?: number;
138 + type?: string;
139 + text?: string;
140 + left?: string | number;
141 + top?: string | number;
142 + width?: string | number;
143 + height?: string | number;
144 + fill?: string;
145 + stroke?: string;
146 + strokeWidth?: string | number;
147 + fontFamily?: string;
148 + fontSize?: number;
149 + fontStyle?: string;
150 + fontWeight?: string;
151 + textAlign?: string;
152 + textDecoration?: string;
153 + opacity?: number;
154 + [propName: string]: number | string | boolean | undefined;
155 + }
156 +
157 + interface IIncludeUIOptions {
158 + loadImage?: {
159 + path: string;
160 + name: string;
161 + };
162 + theme?: IThemeConfig;
163 + menu?: string[];
164 + initMenu?: string;
165 + uiSize?: {
166 + width: string;
167 + height: string;
168 + };
169 + menuBarPosition?: string;
170 + usageStatistics?: boolean;
171 + }
172 +
173 + interface ISelectionStyleConfig {
174 + cornerStyle?: string;
175 + cornerSize?: number;
176 + cornerColor?: string;
177 + cornerStrokeColor?: string;
178 + transparentCorners?: boolean;
179 + lineWidth?: number;
180 + borderColor?: string;
181 + rotatingPointOffset?: number;
182 + }
183 +
184 + interface IObjectProps {
185 + // icon, shape
186 + fill: string;
187 + height: number;
188 + id: number;
189 + left: number;
190 + opacity: number;
191 + stroke: string | null;
192 + strokeWidth: number | null;
193 + top: number;
194 + type: string;
195 + width: number;
196 + }
197 +
198 + interface ITextObjectProps extends IObjectProps {
199 + fontFamily: string;
200 + fontSize: string;
201 + fontStyle: string;
202 + text: string;
203 + textAlign: string;
204 + textDecoration: string;
205 + }
206 +
207 + interface IFilterResolveObject {
208 + type: string;
209 + action: string;
210 + }
211 +
212 + interface ICropResolveObject {
213 + oldWidth: number;
214 + oldHeight: number;
215 + newWidth: number;
216 + newHeight: number;
217 + }
218 +
219 + interface IFlipXYResolveObject {
220 + flipX: boolean;
221 + flipY: boolean;
222 + angle: AngleType;
223 + }
224 +
225 + interface IOptions {
226 + includeUI?: IIncludeUIOptions;
227 + cssMaxWidth?: number;
228 + cssMaxHeight?: number;
229 + usageStatistics?: boolean;
230 + selectionStyle?: ISelectionStyleConfig;
231 + }
232 +
233 + interface IUIDimension {
234 + height?: string;
235 + width?: string;
236 + }
237 +
238 + interface IImageDimension {
239 + oldHeight?: number;
240 + oldWidth?: number;
241 + newHeight?: number;
242 + newWidth?: number;
243 + }
244 +
245 + interface IEditorSize {
246 + uiSize?: IUIDimension;
247 + imageSize?: IImageDimension;
248 + }
249 +
250 + interface UI {
251 + resizeEditor(dimension: IEditorSize): Promise<void>;
252 + }
253 +
254 + class ImageEditor {
255 + constructor(wrapper: string | Element, options: IOptions);
256 + public ui: UI;
257 +
258 + public addIcon(type: string, options?: IIconOptions): Promise<IObjectProps>;
259 + public addImageObject(imgUrl: string): Promise<void>;
260 + public addShape(type: string, options?: IShapeOptions): Promise<IObjectProps>;
261 + public addText(text: string, options?: IGenerateTextOptions): Promise<ITextObjectProps>;
262 + public applyFilter(
263 + type: string,
264 + options?: {
265 + maskObjId: number;
266 + },
267 + isSilent?: boolean
268 + ): Promise<IFilterResolveObject>;
269 + public changeCursor(cursorType: string): void;
270 + public changeIconColor(id: number, color: string): Promise<void>;
271 + public changeSelectableAll(selectable: boolean): void;
272 + public changeShape(id: number, options?: IShapeOptions, isSilent?: boolean): Promise<void>;
273 + public changeText(id: number, text?: string): Promise<void>;
274 + public changeTextStyle(
275 + id: number,
276 + styleObj: ITextStyleConfig,
277 + isSilent?: boolean
278 + ): Promise<void>;
279 + public clearObjects(): Promise<void>;
280 + public clearRedoStack(): void;
281 + public clearUndoStack(): void;
282 + public crop(rect: IRectConfig): Promise<ICropResolveObject>;
283 + public deactivateAll(): void;
284 + public destroy(): void;
285 + public discardSelection(): void;
286 + public flipX(): Promise<IFlipXYResolveObject>;
287 + public flipY(): Promise<IFlipXYResolveObject>;
288 + public getCanvasSize(): ICanvasSize;
289 + public getCropzoneRect(): IRectConfig;
290 + public getDrawingMode(): string;
291 + public getImageName(): string;
292 + public getObjectPosition(id: number, originX: string, originY: string): ICanvasSize;
293 + public getObjectProperties(
294 + id: number,
295 + keys: string | string[] | IGraphicObjectProps
296 + ): IGraphicObjectProps;
297 + public hasFilter(type: string): boolean;
298 + public isEmptyRedoStack(): boolean;
299 + public isEmptyUndoStack(): boolean;
300 + public loadImageFromFile(imgFile: File, imageName?: string): Promise<ICropResolveObject>;
301 + public loadImageFromURL(url: string, imageName?: string): Promise<ICropResolveObject>;
302 + public redo(): Promise<any>;
303 + public registerIcons(infos: IIconInfo): void;
304 + public removeActiveObject(): void;
305 + public removeFilter(type?: string): Promise<IFilterResolveObject>;
306 + public removeObject(id: number): Promise<void>;
307 + public resetFlip(): Promise<IFlipXYResolveObject>;
308 + public resizeCanvasDimension(dimension: ICanvasSize): Promise<void>;
309 + public rotate(angle: AngleType, isSilent?: boolean): Promise<AngleType>;
310 + public setAngle(angle: AngleType, isSilent?: boolean): Promise<AngleType>;
311 + public setBrush(option: IBrushOptions): void;
312 + public setCropzoneRect(mode?: number): void;
313 + public setDrawingShape(type: string, options?: IShapeOptions): void;
314 + public setObjectPosition(id: number, posInfo?: IPositionConfig): Promise<void>;
315 + public setObjectProperties(id: number, keyValue?: IGraphicObjectProps): Promise<void>;
316 + public setObjectPropertiesQuietly(id: number, keyValue?: IGraphicObjectProps): Promise<void>;
317 + public startDrawingMode(mode: string, option?: { width?: number; color?: string }): boolean;
318 + public stopDrawingMode(): void;
319 + public toDataURL(options?: IToDataURLOptions): string;
320 + public undo(): Promise<any>;
321 + public on(eventName: string, handler: (...args: any[]) => void): void;
322 + }
323 +}
324 +
325 +declare module 'tui-image-editor' {
326 + export = tuiImageEditor.ImageEditor;
327 +}
1 +{
2 + "source": {
3 + "include": ["src", "README.md"],
4 + "exclude": [],
5 + "includePattern": ".+\\.js(doc)?$",
6 + "excludePattern": "(^|\\/|\\\\)_"
7 + },
8 + "plugins": ["plugins/markdown"],
9 + "templates": {
10 + "name": "ImageEditor",
11 + "logo": {
12 + "url": "https://user-images.githubusercontent.com/35218826/40895380-0b9f4cd6-67ea-11e8-982f-18121daa3a04.png",
13 + "width": "150px",
14 + "height": "13px",
15 + "link": "https://github.com/nhn/tui.jsdoc-template"
16 + }
17 + },
18 + "opts": {
19 + "private": false,
20 + "recurse": true,
21 + "destination": "doc",
22 + "tutorials": "examples",
23 + "template": "./node_modules/tui-jsdoc-template",
24 + "package": "package.json"
25 + }
26 +}
1 +/* eslint-disable consts-on-top, no-process-env, require-jsdoc */
2 +/* eslint-disable no-process-env, require-jsdoc */
3 +const webdriverConfig = {
4 + hostname: 'fe.nhnent.com',
5 + port: 4444,
6 + remoteHost: true,
7 +};
8 +
9 +function setConfig(defaultConfig, server) {
10 + if (server === 'ne') {
11 + defaultConfig.customLaunchers = {
12 + IE9: {
13 + base: 'WebDriver',
14 + config: webdriverConfig,
15 + browserName: 'internet explorer',
16 + version: '9',
17 + },
18 + IE10: {
19 + base: 'WebDriver',
20 + config: webdriverConfig,
21 + browserName: 'internet explorer',
22 + version: '10',
23 + },
24 + IE11: {
25 + base: 'WebDriver',
26 + config: webdriverConfig,
27 + browserName: 'internet explorer',
28 + version: '11',
29 + },
30 + Edge: {
31 + base: 'WebDriver',
32 + config: webdriverConfig,
33 + browserName: 'MicrosoftEdge',
34 + },
35 + 'Chrome-WebDriver': {
36 + base: 'WebDriver',
37 + config: webdriverConfig,
38 + browserName: 'chrome',
39 + },
40 + 'Firefox-WebDriver': {
41 + base: 'WebDriver',
42 + config: webdriverConfig,
43 + browserName: 'firefox',
44 + },
45 + 'Safari-WebDriver': {
46 + base: 'WebDriver',
47 + config: webdriverConfig,
48 + browserName: 'safari',
49 + },
50 + };
51 + defaultConfig.browsers = [
52 + 'IE9',
53 + 'IE10',
54 + // 'IE11',
55 + // 'Edge',
56 + 'Chrome-WebDriver',
57 + 'Firefox-WebDriver',
58 + // 'Safari-WebDriver'
59 + ];
60 + defaultConfig.reporters.push('coverage');
61 + defaultConfig.reporters.push('junit');
62 + defaultConfig.coverageReporter = {
63 + dir: 'report/coverage/',
64 + reporters: [
65 + {
66 + type: 'html',
67 + subdir(browser) {
68 + return `report-html/${browser}`;
69 + },
70 + },
71 + {
72 + type: 'cobertura',
73 + subdir(browser) {
74 + return `report-cobertura/${browser}`;
75 + },
76 + file: 'cobertura.txt',
77 + },
78 + ],
79 + };
80 + defaultConfig.junitReporter = {
81 + outputDir: 'report/junit',
82 + suite: '',
83 + };
84 + } else {
85 + defaultConfig.browsers = ['ChromeHeadless'];
86 + }
87 +}
88 +
89 +module.exports = function (config) {
90 + const defaultConfig = {
91 + basePath: './',
92 + frameworks: ['jasmine', 'jquery-3.2.1', 'es5-shim'],
93 + files: [
94 + // reason for not using karma-jasmine-jquery framework is that including older jasmine-karma file
95 + // included jasmine-karma version is 2.0.5 and this version don't support ie8
96 + 'node_modules/jasmine-jquery/lib/jasmine-jquery.js',
97 + 'node_modules/fabric/dist/fabric.js',
98 + 'test/index.js',
99 + {
100 + pattern: 'test/fixtures/*.jpg',
101 + watched: false,
102 + included: false,
103 + served: true,
104 + },
105 + {
106 + pattern: 'test/fixtures/*.png',
107 + watched: false,
108 + included: false,
109 + served: true,
110 + },
111 + {
112 + pattern: 'test/fixtures/*.svg',
113 + watched: false,
114 + included: false,
115 + served: true,
116 + },
117 + ],
118 + preprocessors: {
119 + 'test/index.js': ['webpack', 'sourcemap'],
120 + },
121 + reporters: ['dots'],
122 + webpack: {
123 + mode: 'development',
124 + devtool: 'inline-source-map',
125 + externals: {
126 + fabric: 'fabric',
127 + },
128 + module: {
129 + rules: [
130 + {
131 + test: /\.js$/,
132 + include: /src/,
133 + exclude: /node_modules/,
134 + loader: 'eslint-loader',
135 + enforce: 'pre',
136 + },
137 + {
138 + test: /\.js$/,
139 + exclude: /(test|node_modules)/,
140 + loader: 'istanbul-instrumenter-loader',
141 + query: {
142 + esModules: true,
143 + },
144 + },
145 + {
146 + test: /\.js$/,
147 + exclude: /node_modules/,
148 + loader: 'babel-loader?cacheDirectory',
149 + options: {
150 + babelrc: true,
151 + },
152 + },
153 + {
154 + test: /\.styl$/,
155 + use: ['css-loader', 'stylus-loader'],
156 + },
157 + {
158 + test: /\.svg$/,
159 + loader: 'svg-inline-loader',
160 + },
161 + ],
162 + },
163 + },
164 + port: 9876,
165 + colors: true,
166 + logLevel: config.LOG_INFO,
167 + autoWatch: true,
168 + singleRun: true,
169 + };
170 +
171 + /* eslint-disable */
172 + setConfig(defaultConfig, process.env.KARMA_SERVER);
173 + config.set(defaultConfig);
174 +};
1 +const fs = require('fs');
2 +const mkdirp = require('mkdirp');
3 +const svgstore = require('svgstore');
4 +const svgDir = './src/svg';
5 +
6 +function getFileList(dir) {
7 + const targetDir = `${svgDir}/${dir}`;
8 + const sprites = svgstore();
9 + fs.readdir(targetDir, (err, files) => {
10 + if (!files) return;
11 + files.forEach((file) => {
12 + if (file.match(/^\./)) return;
13 + const id = `${dir}-${file.replace(/\.svg$/, '')}`;
14 + const svg = fs.readFileSync(`${targetDir}/${file}`);
15 + sprites.add(id, svg);
16 + });
17 + fs.writeFileSync(`./dist/svg/${dir}.svg`, sprites);
18 + });
19 +}
20 +
21 +mkdirp('./dist/svg', (mkdirpErr) => {
22 + if (mkdirpErr) {
23 + console.error(mkdirpErr);
24 + } else {
25 + fs.readdir(svgDir, (err, dirs) => {
26 + dirs.forEach((dir) => {
27 + getFileList(dir);
28 + });
29 + });
30 + }
31 +});
This diff could not be displayed because it is too large.
1 +{
2 + "name": "tui-image-editor",
3 + "author": "NHN FE Development Lab <dl_javascript@nhn.com>",
4 + "version": "3.11.0",
5 + "license": "MIT",
6 + "repository": {
7 + "type": "git",
8 + "url": "https://github.com/nhn/tui.image-editor.git"
9 + },
10 + "main": "dist/tui-image-editor.js",
11 + "files": [
12 + "src",
13 + "dist",
14 + "index.d.ts"
15 + ],
16 + "description": "TOAST UI Component: ImageEditor",
17 + "keywords": [
18 + "nhn",
19 + "nhnent",
20 + "tui",
21 + "component",
22 + "image",
23 + "editor",
24 + "canvas",
25 + "fabric"
26 + ],
27 + "devDependencies": {
28 + "babel-core": "^6.26.3",
29 + "babel-eslint": "^10.0.3",
30 + "babel-loader": "^7.1.2",
31 + "babel-preset-es2015": "^6.24.1",
32 + "css-loader": "^3.4.1",
33 + "dtslint": "^0.4.2",
34 + "es5-shim": "^4.5.9",
35 + "eslint": "^4.5.0",
36 + "eslint-config-prettier": "^6.15.0",
37 + "eslint-config-tui": "^1.0.1",
38 + "eslint-loader": "^2.0.0",
39 + "eslint-plugin-prettier": "^3.1.4",
40 + "file-saver": "^1.3.3",
41 + "istanbul-instrumenter-loader": "^1.0.0",
42 + "jasmine-core": "^2.4.1",
43 + "jasmine-jquery": "^2.1.1",
44 + "jquery": "^3.4.0",
45 + "jsdoc": "^3.5.4",
46 + "karma": "^4.4.1",
47 + "karma-chrome-launcher": "^2.2.0",
48 + "karma-coverage": "^2.0.1",
49 + "karma-edge-launcher": "^0.4.2",
50 + "karma-es5-shim": "0.0.4",
51 + "karma-firefox-launcher": "^1.1.0",
52 + "karma-ie-launcher": "^1.0.0",
53 + "karma-jasmine": "^1.1.1",
54 + "karma-jasmine-jquery-2": "^0.1.1",
55 + "karma-jquery": "^0.2.4",
56 + "karma-junit-reporter": "^1.2.0",
57 + "karma-sourcemap-loader": "^0.3.7",
58 + "karma-webdriver-launcher": "git+https://github.com/nhn/karma-webdriver-launcher.git#v1.2.0",
59 + "karma-webpack": "^4.0.2",
60 + "mini-css-extract-plugin": "^0.9.0",
61 + "mkdirp": "^0.5.1",
62 + "optimize-css-assets-webpack-plugin": "^5.0.3",
63 + "prettier": "^2.2.1",
64 + "safe-umd-webpack-plugin": "^4.0.0",
65 + "selenium-webdriver": "^4.0.0-alpha.7",
66 + "simulant": "^0.2.2",
67 + "stylus": "^0.54.5",
68 + "stylus-loader": "^3.0.2",
69 + "svg-inline-loader": "^0.8.2",
70 + "svgstore": "^2.0.3",
71 + "tslint": "^5.12.0",
72 + "tui-jsdoc-template": "^1.0.4",
73 + "typescript": "^3.2.2",
74 + "uglifyjs-webpack-plugin": "^2.2.0",
75 + "webpack": "^4.41.5",
76 + "webpack-cli": "^3.3.10",
77 + "webpack-dev-server": "^3.10.1"
78 + },
79 + "scripts": {
80 + "test": "karma start --no-single-run",
81 + "test:ne": "KARMA_SERVER=ne karma start",
82 + "test:types": "tsc --project test/types",
83 + "bundle": "webpack && webpack -p && npm run bundle:svg && node tsBannerGenerator.js",
84 + "bundle:svg": "node makesvg.js",
85 + "serve": "webpack-dev-server",
86 + "doc:dev": "tuidoc --dev",
87 + "doc": "tuidoc",
88 + "tslint": "tslint index.d.ts"
89 + },
90 + "dependencies": {
91 + "core-js-pure": "^3.6.4",
92 + "fabric": "4.2.0",
93 + "tui-code-snippet": "^1.5.0",
94 + "tui-color-picker": "^2.2.6"
95 + }
96 +}
1 +/* ICON BUTTON */
2 +.tie-icon-add-button
3 + &.icon-bubble .{prefix}-button[data-icontype="icon-bubble"] svg > use.active,
4 + &.icon-heart .{prefix}-button[data-icontype="icon-heart"] svg > use.active,
5 + &.icon-location .{prefix}-button[data-icontype="icon-location"] svg > use.active,
6 + &.icon-polygon .{prefix}-button[data-icontype="icon-polygon"] svg > use.active,
7 + &.icon-star .{prefix}-button[data-icontype="icon-star"] svg > use.active,
8 + &.icon-star-2 .{prefix}-button[data-icontype="icon-star-2"] svg > use.active,
9 + &.icon-arrow-3 .{prefix}-button[data-icontype="icon-arrow-3"] svg > use.active,
10 + &.icon-arrow-2 .{prefix}-button[data-icontype="icon-arrow-2"] svg > use.active,
11 + &.icon-arrow .{prefix}-button[data-icontype="icon-arrow"] svg > use.active,
12 + &.icon-bubble .{prefix}-button[data-icontype="icon-bubble"] svg > use.active
13 + display: block;
14 +
15 +/* DRAW BUTTON */
16 +.tie-draw-line-select-button
17 + &.line .{prefix}-button.line svg > use.normal,
18 + &.free .{prefix}-button.free svg > use.normal
19 + display: none;
20 +
21 + &.line .{prefix}-button.line svg > use.active,
22 + &.free .{prefix}-button.free svg > use.active
23 + display: block;
24 +
25 +/* FLIP BUTTON */
26 +.tie-flip-button
27 + &.resetFlip .{prefix}-button.resetFlip,
28 + &.flipX .{prefix}-button.flipX,
29 + &.flipY .{prefix}-button.flipY
30 + svg > use.normal
31 + display: none;
32 + svg > use.active
33 + display: block;
34 +
35 +/* MASK BUTTON */
36 +.tie-mask-apply.apply.active .{prefix}-button.apply
37 + label
38 + color: #fff;
39 + svg > use.active
40 + display: block;
41 +
42 +/* CROP BUTTON */
43 +.tie-crop-button,
44 +.tie-crop-preset-button
45 + .{prefix}-button.apply
46 + margin-right: 24px;
47 + .{prefix}-button.preset.active svg > use.active
48 + display: block;
49 + .{prefix}-button.apply.active svg > use.active
50 + display: block;
51 +
52 +
53 +/* SHAPE BUTTON */
54 +.tie-shape-button
55 + &.rect .{prefix}-button.rect,
56 + &.circle .{prefix}-button.circle,
57 + &.triangle .{prefix}-button.triangle
58 + svg > use.normal
59 + display: none;
60 + svg > use.active
61 + display: block;
62 +
63 +/* TEXT BUTTON */
64 +.tie-text-effect-button
65 + .{prefix}-button.active svg > use.active
66 + display: block;
67 +.tie-text-align-button
68 + &.left .{prefix}-button.left svg > use.active,
69 + &.center .{prefix}-button.center svg > use.active,
70 + &.right .{prefix}-button.right svg > use.active
71 + display: block;
72 +.tie-mask-image-file,
73 +.tie-icon-image-file
74 + opacity: 0;
75 + position: absolute;
76 + width: 100%;
77 + height: 100%;
78 + border: 1px solid green;
79 + cursor: inherit;
80 + left: 0;
81 + top: 0;
1 +/* VIRTUAL CHECKBOX */
2 +.{prefix}-container
3 + .filter-color-item
4 + display: inline-block;
5 + .tui-image-editor-checkbox
6 + display: block;
7 + .{prefix}-checkbox-wrap
8 + display: inline-block !important;
9 + text-align: left;
10 + .{prefix}-checkbox-wrap.fixed-width
11 + width: 187px;
12 + white-space: normal;
13 + .{prefix}-checkbox
14 + display: inline-block;
15 + margin: 1px 0 1px 0;
16 + input
17 + width: 14px;
18 + height: 14px;
19 + opacity: 0;
20 + > label > span
21 + color: #fff;
22 + height: 14px;
23 + position: relative;
24 + input + label:before,
25 + > label > span:before
26 + content: '';
27 + position: absolute;
28 + width: 14px;
29 + height: 14px;
30 + background-color: #fff;
31 + top: 6px;
32 + left: -19px;
33 + display: inline-block;
34 + margin: 0;
35 + text-align: center;
36 + font-size: 11px;
37 + border: 0;
38 + border-radius: 2px;
39 + padding-top: 1px;
40 + box-sizing: border-box;
41 + input[type='checkbox']:checked + span:before
42 + background-size: cover;
43 + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAMBJREFUKBWVkjEOwjAMRe2WgZW7IIHEDdhghhuwcQ42rlJugAQS54Cxa5cq1QM5TUpByZfS2j9+dlJVt/tX5ZxbS4ZU9VLkQvSHKTIGRaVJYFmKrBbTCJxE2UgCdDzMZDkHrOV6b95V0US6UmgKodujEZbJg0B0ZgEModO5lrY1TMQf1TpyJGBEjD+E2NPN7ukIUDiF/BfEXgRiGEw8NgkffYGYwCi808fpn/6OvfUfsDr/Vc1IfRf8sKnFVqeiVQfDu0tf/nWH9gAAAABJRU5ErkJggg==');
44 +
45 + .{prefix}-selectlist-wrap
46 + position: relative;
47 + select
48 + width: 100%;
49 + height: 28px;
50 + margin-top: 4px;
51 + border: 0;
52 + outline: 0;
53 + border-radius: 0;
54 + border: 1px solid #cbdbdb;
55 + background-color: #fff;
56 + -webkit-appearance: none;
57 + -moz-appearance: none;
58 + appearance: none;
59 + padding: 0 7px 0 10px;
60 + .{prefix}-selectlist
61 + display: none;
62 + position: relative;
63 + top: -1px;
64 + border: 1px solid #ccc;
65 + background-color: #fff;
66 + border-top: 0px;
67 + padding: 4px 0;
68 + li
69 + display: block;
70 + text-align: left;
71 + padding: 7px 10px;
72 + font-family: 'Noto Sans', sans-serif;
73 + li:hover
74 + background-color: rgba(81, 92, 230, 0.05);
75 + .{prefix}-selectlist-wrap:before
76 + content: '';
77 + position: absolute;
78 + display: inline-block;
79 + width: 14px;
80 + height: 14px;
81 + right: 5px;
82 + top: 10px;
83 + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAHlJREFUKBVjYBgFOEOAEVkmPDxc89+/f6eAYjzI4kD2FyYmJrOVK1deh4kzwRggGiQBVJCELAZig8SQNYHEmEEEMrh69eo1HR0dfqCYJUickZGxf9WqVf3IakBsFBthklpaWmVA9mEQhrJhUoTp0NBQCRAmrHL4qgAAuu4cWZOZIGsAAAAASUVORK5CYII=');
84 + background-size: cover;
85 + .{prefix}-selectlist-wrap select::-ms-expand
86 + display:none;
1 +/* COLOR PICKER */
2 +.{prefix}-container
3 + div.tui-colorpicker-clearfix
4 + width: 159px;
5 + height: 28px;
6 + border: 1px solid #d5d5d5;
7 + border-radius: 2px;
8 + background-color: #f5f5f5;
9 + margin-top: 6px;
10 + padding: 4px 7px 4px 7px;
11 + .tui-colorpicker-palette-hex
12 + width: 114px;
13 + background-color: #f5f5f5;
14 + border: 0;
15 + font-size: 11px;
16 + margin-top: 2px;
17 + font-family: 'Noto Sans', sans-serif;
18 + .tui-colorpicker-palette-hex[value='#ffffff'] + .tui-colorpicker-palette-preview,
19 + .tui-colorpicker-palette-hex[value=''] + .tui-colorpicker-palette-preview
20 + border: 1px solid #ccc;
21 + .tui-colorpicker-palette-hex[value=''] + .tui-colorpicker-palette-preview
22 + background-size: cover;
23 + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAdBJREFUWAnFl0FuwjAQRZ0ukiugHqFSOQNdseuKW3ALzkA4BateICvUGyCxrtRFd4WuunH/TzykaYJrnLEYaTJJsP2+x8GZZCbQrLU5mj7Bn+EP8HvnCObd+R7xBV5lWfaNON4AnsA38E94qLEt+0yiFaBzAV/Bv+Cxxr4co7hKCDpw1q9wLeNYYdlAwyn8TYt8Hme3+8D5ozcTaMCZ68PXa2tnM2sbEcOZAJhrrpl2DAcTOGNjZPSfCdzkw6JrfbiMv+osBe4y9WOedhm4jZfhbENWuxS44H9Wz/xw4WzqLOAqh1+zycgAwzEMzr5k5gaHOa9ULBwuuDkFlHI1Kl4PJ66kgIpnoywOTmRFAYcbwYk9UMApWkD8zAV5ihcwHk4Rx7gl0IFTQL0EFc+CTQ9OZHWH3YhlVJiVpTHbrTGLhTHLZVgff6s9lyBsI9KduSS83oj+34rTwJutmBmCnMsvozRwZqB5GTkBw6/jdPDu69iJ6BYk6eCcfbcgcQIK/MByaaiMqm8rHcjol2TnpWDhyAKSGdA3FrxtJUToX0ODqatetfGE+8tyEUOV8GY5dGRwLP/MBS4RHQr4bT7NRAQjlcOTfZxmv2G+c4hI8nn+Ax5PG/zhI393AAAAAElFTkSuQmCC');
24 + .tui-colorpicker-palette-preview
25 + border-radius: 100%;
26 + float: left;
27 + width: 17px;
28 + height: 17px;
29 + border: 0;
30 + .color-picker-control
31 + position: absolute;
32 + display: none;
33 + z-index: 99;
34 + width: 192px;
35 + background-color: #fff;
36 + box-shadow: 0 3px 22px 6px rgba(0, 0, 0, .15);
37 + padding: 16px;
38 + border-radius: 2px;
39 + .tui-colorpicker-palette-toggle-slider
40 + display: none;
41 + .tui-colorpicker-palette-button
42 + border: 0;
43 + border-radius: 100%;
44 + margin: 2px;
45 + background-size: cover;
46 + font-size: 1px;
47 + &[title='#ffffff']
48 + border: 1px solid #ccc;
49 + &[title='']
50 + border: 1px solid #ccc;
51 + .triangle
52 + width: 0;
53 + height: 0;
54 + border-right: 7px solid transparent;
55 + border-top: 8px solid #fff;
56 + border-left: 7px solid transparent;
57 + position: absolute;
58 + bottom: -8px;
59 + left: 84px;
60 + .tui-colorpicker-container,
61 + .tui-colorpicker-palette-container ul,
62 + .tui-colorpicker-palette-container
63 + width: 100%;
64 + height: auto;
65 +
66 +
67 + .filter-color-item
68 + .color-picker-control label
69 + font-color: #333;
70 + font-weight: normal;
71 + margin-right: 7pxleft
72 + .tui-image-editor-checkbox
73 + margin-top: 0;
74 + input + label:before,
75 + > label:before
76 + left: -16px;
77 + .color-picker
78 + width: 100%;
79 + height: auto;
80 + .color-picker-value
81 + width: 32px;
82 + height: 32px;
83 + border: 0px;
84 + border-radius: 100%;
85 + margin: auto;
86 + margin-bottom: 1px;
87 + &.transparent
88 + border: 1px solid #cbcbcb;
89 + background-size: cover;
90 + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAdBJREFUWAnFl0FuwjAQRZ0ukiugHqFSOQNdseuKW3ALzkA4BateICvUGyCxrtRFd4WuunH/TzykaYJrnLEYaTJJsP2+x8GZZCbQrLU5mj7Bn+EP8HvnCObd+R7xBV5lWfaNON4AnsA38E94qLEt+0yiFaBzAV/Bv+Cxxr4co7hKCDpw1q9wLeNYYdlAwyn8TYt8Hme3+8D5ozcTaMCZ68PXa2tnM2sbEcOZAJhrrpl2DAcTOGNjZPSfCdzkw6JrfbiMv+osBe4y9WOedhm4jZfhbENWuxS44H9Wz/xw4WzqLOAqh1+zycgAwzEMzr5k5gaHOa9ULBwuuDkFlHI1Kl4PJ66kgIpnoywOTmRFAYcbwYk9UMApWkD8zAV5ihcwHk4Rx7gl0IFTQL0EFc+CTQ9OZHWH3YhlVJiVpTHbrTGLhTHLZVgff6s9lyBsI9KduSS83oj+34rTwJutmBmCnMsvozRwZqB5GTkBw6/jdPDu69iJ6BYk6eCcfbcgcQIK/MByaaiMqm8rHcjol2TnpWDhyAKSGdA3FrxtJUToX0ODqatetfGE+8tyEUOV8GY5dGRwLP/MBS4RHQr4bT7NRAQjlcOTfZxmv2G+c4hI8nn+Ax5PG/zhI393AAAAAElFTkSuQmCC');
91 +
92 + .color-picker-value + label
93 + color: #fff;
94 +
95 + .{prefix}-submenu svg > use
96 + display: none;
97 + .{prefix}-submenu svg > use.normal
98 + display: block;
1 +/* GRID VISUAL OF FLIP AND ROTATE MENU */
2 +.{prefix}-container
3 + .{prefix}-grid-visual
4 + display: none;
5 + position: absolute;
6 + width: 100%;
7 + height: 100%;
8 + border: 1px solid rgba(255,255,255,0.7);
9 + .{prefix}-main.{prefix}-menu-flip,
10 + .{prefix}-main.{prefix}-menu-rotate
11 + .tui-image-editor
12 + transition: none;
13 + .{prefix}-main.{prefix}-menu-flip .{prefix}-grid-visual,
14 + .{prefix}-main.{prefix}-menu-rotate .{prefix}-grid-visual
15 + display: block;
16 + .{prefix}-grid-visual
17 + table
18 + width: 100%;
19 + height: 100%;
20 + border-collapse: collapse;
21 + td
22 + border: 1px solid rgba(255,255,255,0.3);
23 + td.dot:before
24 + content: '';
25 + position: absolute;
26 + box-sizing: border-box;
27 + width: 10px;
28 + height: 10px;
29 + border: 0;
30 + box-shadow: 0 0 1px 0 rgba(0,0,0,0.3);
31 + border-radius: 100%;
32 + background-color: #fff;
33 + td.dot.left-top:before
34 + top: -5px;
35 + left: -5px;
36 + td.dot.right-top:before
37 + top: -5px;
38 + right: -5px;
39 + td.dot.left-bottom:before
40 + bottom: -5px;
41 + left: -5px;
42 + td.dot.right-bottom:before
43 + bottom: -5px;
44 + right: -5px;
1 +/* ICON */
2 +.{prefix}-container
3 + .tie-icon-add-button .{prefix}-button
4 + min-width: 42px;
5 + .svg_ic-menu
6 + .svg_ic-helpmenu
7 + width: 24px;
8 + height: 24px;
9 + .svg_ic-submenu
10 + width: 32px;
11 + height: 32px;
12 + .svg_img-bi
13 + width: 257px;
14 + height: 26px;
15 +
16 + .{prefix}-controls
17 + svg > use
18 + display: none;
19 + .enabled svg:hover > use.hover
20 + .normal svg:hover > use.hover
21 + display: block;
22 + .active svg:hover > use.hover
23 + display: none;
24 + svg > use.normal
25 + display: block;
26 + .active svg > use.active
27 + display: block;
28 + .enabled svg > use.enabled
29 + display: block;
30 + .active svg > use.normal,
31 + .enabled svg > use.normal
32 + display: none;
33 + .help svg > use.disabled,
34 + .help.enabled svg > use.normal
35 + display: block;
36 + .help.enabled svg > use.disabled
37 + display: none;
38 +
39 + .{prefix}-controls:hover
40 + z-index: 3;
1 +prefix = 'tui-image-editor'
2 +
3 +@import 'main.styl'
4 +@import 'gridtable.styl'
5 +@import 'submenu.styl'
6 +@import 'checkbox.styl'
7 +@import 'range.styl'
8 +@import 'position.styl'
9 +@import 'icon.styl'
10 +@import 'colorpicker.styl'
11 +@import 'buttons.styl'
12 +.{prefix}-container.top
13 + &.{prefix}-top-optimization
14 + .{prefix}-controls ul
15 + text-align: right;
16 + .{prefix}-controls-logo
17 + display: none;
1 +body > textarea
2 + position: fixed !important;
3 +
4 ++prefix-classes(prefix)
5 + .-container
6 + margin: 0;
7 + padding: 0;
8 + box-sizing: border-box;
9 + min-height: 300px;
10 + height: 100%;
11 + position: relative;
12 + background-color: #282828;
13 + overflow: hidden;
14 + letter-spacing: 0.3px;
15 +
16 + div, ul, label, input, li
17 + box-sizing: border-box;
18 + margin: 0;
19 + padding: 0;
20 + -ms-user-select: none;
21 + -moz-user-select: -moz-none;
22 + -khtml-user-select: none;
23 + -webkit-user-select: none;
24 + user-select: none;
25 +
26 + .-header
27 + /* BUTTON AND LOGO */
28 + min-width: 533px;
29 + position: absolute;
30 + background-color: #151515;
31 + top: 0;
32 + width: 100%;
33 + .-header-buttons,
34 + .-controls-buttons
35 + float: right;
36 + margin: 8px;
37 +
38 + .-header-logo,
39 + .-controls-logo
40 + float: left;
41 + width: 30%;
42 + padding: 17px;
43 +
44 + .-controls-logo,
45 + .-controls-buttons
46 + width: 270px;
47 + height: 100%;
48 + display: none;
49 +
50 + .-header-buttons button,
51 + .-header-buttons div,
52 + .-controls-buttons button,
53 + .-controls-buttons div
54 + display: inline-block;
55 + position: relative;
56 + width: 120px;
57 + height: 40px;
58 + padding: 0;
59 + line-height: 40px;
60 + outline: none;
61 + border-radius: 20px;
62 + border: 1px solid #ddd;
63 + font-family: 'Noto Sans', sans-serif;
64 + font-size: 12px;
65 + font-weight: bold;
66 + cursor: pointer;
67 + vertical-align: middle;
68 + letter-spacing: 0.3px;
69 + text-align: center;
70 +
71 + .-download-btn
72 + background-color: #fdba3b;
73 + border-color: #fdba3b;
74 + color: #fff;
75 + .-load-btn
76 + position: absolute;
77 + left: 0;
78 + right: 0;
79 + display: inline-block;
80 + top: 0;
81 + bottom: 0;
82 + width: 100%;
83 + cursor: pointer;
84 + opacity: 0;
85 + .-main-container
86 + position: absolute;
87 + width: 100%;
88 + top: 0;
89 + bottom: 64px;
90 + .-main
91 + position: absolute;
92 + text-align: center;
93 + top: 64px;
94 + bottom: 0;
95 + right: 0;
96 + left: 0;
97 + .-wrap
98 + position: absolute;
99 + bottom: 0;
100 + width: 100%;
101 + overflow: auto;
102 + .-size-wrap
103 + display: table;
104 + width: 100%;
105 + height: 100%
106 + .-align-wrap
107 + display: table-cell;
108 + vertical-align: middle;
109 + .
110 + position: relative;
111 + display: inline-block;
112 +
113 +
114 +/* BIG MENU */
115 +.{prefix}-container
116 + .{prefix}-menu
117 + width: auto;
118 + list-style: none;
119 + padding: 0;
120 + margin: 0 auto;
121 + display: table-cell;
122 + text-align: center;
123 + vertical-align: middle;
124 + white-space: nowrap;
125 + > .{prefix}-item
126 + position: relative;
127 + display: inline-block;
128 + border-radius: 2px;
129 + padding: 7px 8px 3px 8px;
130 + cursor: pointer;
131 + margin: 0 4px;
132 + > .{prefix}-item[tooltip-content]:hover
133 + &:before
134 + content: '';
135 + position: absolute;
136 + display: inline-block;
137 + margin: 0 auto 0;
138 + width: 0;
139 + height: 0;
140 + border-right: 7px solid transparent;
141 + border-top: 7px solid #2f2f2f;
142 + border-left: 7px solid transparent;
143 + left: 13px;
144 + top: -2px;
145 + &:after
146 + content: attr(tooltip-content);
147 + position: absolute;
148 + display: inline-block;
149 + background-color: #2f2f2f;
150 + color: #fff;
151 + padding: 5px 8px;
152 + font-size: 11px;
153 + font-weight: lighter;
154 + border-radius: 3px;
155 + max-height: 23px;
156 + top: -25px;
157 + left: 0;
158 + min-width: 24px;
159 + > .{prefix}-item.active
160 + background-color: #fff;
161 + transition: all .3s ease;
162 + .{prefix}-wrap
163 + position: absolute;
1 +/* POSITION LEFT */
2 +.{prefix}-container
3 + &.left
4 + .{prefix}-menu
5 + > .{prefix}-item[tooltip-content]
6 + &:before
7 + left: 28px;
8 + top: 11px;
9 + border-right: 7px solid #2f2f2f;
10 + border-top: 7px solid transparent;
11 + border-bottom: 7px solid transparent;
12 + &:after
13 + top: 7px;
14 + left: 42px;
15 + white-space: nowrap;
16 + .{prefix}-submenu
17 + left: 0;
18 + height: 100%;
19 + width: 248px;
20 + .{prefix}-main-container
21 + left: 64px;
22 + width: calc(100% - 64px);
23 + height: 100%;
24 + .{prefix}-controls
25 + width: 64px;
26 + height: 100%;
27 + display: table;
28 +
29 +/* POSITION LEFT & RIGHT */
30 +.{prefix}-container
31 + &.left, &.right
32 + .{prefix}-menu
33 + white-space: inherit;
34 + .{prefix}-submenu
35 + white-space: normal;
36 + > div
37 + vertical-align: middle;
38 + .{prefix}-controls li
39 + display: inline-block;
40 + margin: 4px auto;
41 + .{prefix}-icpartition
42 + position: relative;
43 + top: -7px;
44 + width: 24px;
45 + height: 1px;
46 + .{prefix}-submenu
47 + .{prefix}-partition
48 + display: block;
49 + width: 75%;
50 + margin: auto;
51 + > div
52 + border-left: 0;
53 + height:10px;
54 + border-bottom: 1px solid #3c3c3c;
55 + width: 100%;
56 + margin: 0;
57 + .{prefix}-submenu-align
58 + margin-right: 0;
59 + .{prefix}-submenu-item
60 + li
61 + margin-top: 15px;
62 + .tui-colorpicker-clearfix li
63 + margin-top: 0;
64 +
65 + .{prefix}-checkbox-wrap.fixed-width
66 + width: 182px;
67 + white-space: normal;
68 + .{prefix}-range-wrap.{prefix}-newline label.range
69 + display: block;
70 + text-align: left;
71 + width: 75%;
72 + margin: auto;
73 + .{prefix}-range
74 + width: 136px;
75 +
76 +
77 +/* POSITION RIGIHT */
78 +.{prefix}-container
79 + &.right
80 + .{prefix}-menu
81 + > .{prefix}-item[tooltip-content]
82 + &:before
83 + left: -3px;
84 + top: 11px;
85 + border-left: 7px solid #2f2f2f;
86 + border-top: 7px solid transparent;
87 + border-bottom: 7px solid transparent;
88 + &:after
89 + top: 7px;
90 + left: unset;
91 + right: 43px;
92 + white-space: nowrap;
93 + .{prefix}-submenu
94 + right: 0;
95 + height: 100%;
96 + width: 248px;
97 + .{prefix}-main-container
98 + right: 64px;
99 + width: calc(100% - 64px);
100 + height: 100%;
101 + .{prefix}-controls
102 + right: 0;
103 + width: 64px;
104 + height: 100%;
105 + display: table;
106 +
107 +
108 +/* POSITION TOP & BOTTOM */
109 +.{prefix}-container
110 + &.top, &.bottom
111 + .{prefix}-submenu
112 + .{prefix}-partition.only-left-right
113 + display: none;
114 +
115 +
116 +/* POSITION BOTTOM */
117 +.{prefix}-container
118 + &.bottom .tui-image-editor-submenu > div
119 + padding-bottom: 24px;
120 +
121 +/* POSITION TOP */
122 +.{prefix}-container
123 + &.top
124 + .color-picker-control .triangle
125 + top: -8px;
126 + border-right: 7px solid transparent;
127 + border-top: 0px;
128 + border-left: 7px solid transparent;
129 + border-bottom: 8px solid #fff;
130 + .{prefix}-size-wrap
131 + height: 100%;
132 + .{prefix}-main-container
133 + bottom: 0;
134 + .{prefix}-menu
135 + > .{prefix}-item[tooltip-content]
136 + &:before
137 + left: 13px;
138 + border-top: 0;
139 + border-bottom: 7px solid #2f2f2f;
140 + top: 33px;
141 + &:after
142 + top: 38px;
143 + .{prefix}-submenu
144 + top: 0;
145 + bottom: auto;
146 + > div
147 + padding-top: 24px;
148 + vertical-align: top;
149 + .{prefix}-controls-logo
150 + display: table-cell;
151 + .{prefix}-controls-buttons
152 + display: table-cell;
153 + .{prefix}-main
154 + top: 64px;
155 + height: calc(100% - 64px);
156 + .{prefix}-controls
157 + top: 0;
158 + bottom: inherit;
159 +
1 +/* VIRTUAL RANGE */
2 +.{prefix}-container
3 +
4 + .{prefix}-virtual-range-bar
5 + .{prefix}-virtual-range-subbar
6 + .{prefix}-virtual-range-pointer
7 + .{prefix}-disabled
8 + backbround-color: red;
9 +
10 + .{prefix}-range
11 + position: relative;
12 + top: 5px;
13 + width: 166px;
14 + height: 17px;
15 + display: inline-block;
16 + .{prefix}-virtual-range-bar
17 + top: 7px;
18 + position: absolute;
19 + width: 100%;
20 + height: 2px;
21 + background-color: #666666;
22 + .{prefix}-virtual-range-subbar
23 + position: absolute;
24 + height: 100%;
25 + left: 0;
26 + right: 0;
27 + background-color: #d1d1d1;
28 + .{prefix}-virtual-range-pointer
29 + position: absolute;
30 + cursor: pointer;
31 + top: -5px;
32 + left: 0;
33 + width: 12px;
34 + height: 12px;
35 + background-color: #fff;
36 + border-radius: 100%;
37 + .{prefix}-range-wrap
38 + display: inline-block;
39 + margin-left: 4px;
40 + &.short .tui-image-editor-range
41 + width: 100px;
42 + .color-picker-control
43 + .{prefix}-range
44 + width: 108px;
45 + margin-left: 10px;
46 + .{prefix}-virtual-range-pointer
47 + background-color: #333;
48 + .{prefix}-virtual-range-bar
49 + background-color: #ccc;
50 + .{prefix}-virtual-range-subbar
51 + background-color: #606060;
52 + .{prefix}-range-wrap.{prefix}-newline.short
53 + margin-top: -2px;
54 + margin-left: 19px;
55 + label
56 + color: #8e8e8e;
57 + font-weight: normal;
58 + .{prefix}-range-wrap label
59 + vertical-align: baseline;
60 + font-size: 11px;
61 + margin-right: 7px;
62 + color: #fff;
63 + .{prefix}-range-value
64 + cursor: default;
65 + width: 40px;
66 + height: 24px;
67 + outline: none;
68 + border-radius: 2px;
69 + box-shadow: none;
70 + border: 1px solid #d5d5d5;
71 + text-align: center;
72 + background-color: #1c1c1c;
73 + color: #fff;
74 + font-weight: lighter;
75 + vertical-align: baseline;
76 + font-family: 'Noto Sans', sans-serif;
77 + margin-top: 21px;
78 + margin-left: 4px;
79 + .{prefix}-controls
80 + position: absolute;
81 + background-color: #151515;
82 + width: 100%;
83 + height: 64px;
84 + display: table;
85 + bottom: 0;
86 + z-index: 2;
87 + .{prefix}-icpartition
88 + display: inline-block;
89 + background-color: #282828;
90 + width: 1px;
91 + height: 24px;
...\ No newline at end of file ...\ No newline at end of file
1 +/* SUBMENU */
2 +.{prefix}-container
3 + .{prefix}-submenu
4 + display: none;
5 + position: absolute;
6 + bottom: 0;
7 + width:100%;
8 + height: 150px;
9 + white-space: nowrap;
10 + z-index: 2;
11 + .{prefix}-button:hover svg > use.active
12 + display: block;
13 + .{prefix}-submenu-item
14 + li
15 + display: inline-block;
16 + vertical-align: top;
17 + .{prefix}-newline
18 + display: block;
19 + margin-top: 0;
20 + .{prefix}-button
21 + position: relative;
22 + cursor: pointer;
23 + display: inline-block;
24 + font-weight: normal;
25 + font-size: 11px;
26 + margin: 0 9px 0 9px;
27 + .{prefix}-button.preset
28 + margin: 0 9px 20px 5px;
29 + label > span
30 + display: inline-block;
31 + cursor: pointer;
32 + padding-top: 5px;
33 + font-family: "Noto Sans", sans-serif;
34 + font-size: 11px;
35 + .{prefix}-button.apply label,
36 + .{prefix}-button.cancel label
37 + vertical-align: 7px;
38 + > div
39 + display: none;
40 + vertical-align: bottom;
41 + .{prefix}-submenu-style
42 + opacity: 0.95;
43 + z-index: -1;
44 + position: absolute;
45 + top: 0;
46 + bottom: 0;
47 + left: 0;
48 + right: 0;
49 + display: block;
50 +
51 + .{prefix}-partition > div
52 + width: 1px;
53 + height: 52px;
54 + border-left: 1px solid #3c3c3c;
55 + margin: 0 8px 0 8px;
56 + .{prefix}-main.{prefix}-menu-filter .{prefix}-partition > div
57 + height: 108px;
58 + margin: 0 29px 0 0px;
59 + .{prefix}-submenu-align
60 + text-align: left;
61 + margin-right: 30px;
62 + label > span
63 + width: 55px;
64 + white-space: nowrap;
65 + .{prefix}-submenu-align:first-child
66 + margin-right: 0;
67 + label > span
68 + width: 70px;
69 + .{prefix}-main.{prefix}-menu-crop .{prefix}-submenu > div.{prefix}-menu-crop,
70 + .{prefix}-main.{prefix}-menu-flip .{prefix}-submenu > div.{prefix}-menu-flip,
71 + .{prefix}-main.{prefix}-menu-rotate .{prefix}-submenu > div.{prefix}-menu-rotate,
72 + .{prefix}-main.{prefix}-menu-shape .{prefix}-submenu > div.{prefix}-menu-shape,
73 + .{prefix}-main.{prefix}-menu-text .{prefix}-submenu > div.{prefix}-menu-text,
74 + .{prefix}-main.{prefix}-menu-mask .{prefix}-submenu > div.{prefix}-menu-mask,
75 + .{prefix}-main.{prefix}-menu-icon .{prefix}-submenu > div.{prefix}-menu-icon,
76 + .{prefix}-main.{prefix}-menu-draw .{prefix}-submenu > div.{prefix}-menu-draw,
77 + .{prefix}-main.{prefix}-menu-filter .{prefix}-submenu > div.{prefix}-menu-filter
78 + display: table-cell;
79 + .{prefix}-main.{prefix}-menu-crop,
80 + .{prefix}-main.{prefix}-menu-flip,
81 + .{prefix}-main.{prefix}-menu-rotate,
82 + .{prefix}-main.{prefix}-menu-shape,
83 + .{prefix}-main.{prefix}-menu-text,
84 + .{prefix}-main.{prefix}-menu-mask,
85 + .{prefix}-main.{prefix}-menu-icon,
86 + .{prefix}-main.{prefix}-menu-draw,
87 + .{prefix}-main.{prefix}-menu-filter
88 + .{prefix}-submenu
89 + display: table;
90 +
1 +import './js/polyfill';
2 +import ImageEditor from './js/imageEditor';
3 +import './css/index.styl';
4 +
5 +// commands
6 +import './js/command/addIcon';
7 +import './js/command/addImageObject';
8 +import './js/command/addObject';
9 +import './js/command/addShape';
10 +import './js/command/addText';
11 +import './js/command/applyFilter';
12 +import './js/command/changeIconColor';
13 +import './js/command/changeShape';
14 +import './js/command/changeText';
15 +import './js/command/changeTextStyle';
16 +import './js/command/clearObjects';
17 +import './js/command/flip';
18 +import './js/command/loadImage';
19 +import './js/command/removeFilter';
20 +import './js/command/removeObject';
21 +import './js/command/resizeCanvasDimension';
22 +import './js/command/rotate';
23 +import './js/command/setObjectProperties';
24 +import './js/command/setObjectPosition';
25 +import './js/command/changeSelection';
26 +
27 +module.exports = ImageEditor;
1 +import { extend } from 'tui-code-snippet';
2 +import { isSupportFileApi, base64ToBlob, toInteger } from './util';
3 +import Imagetracer from './helper/imagetracer';
4 +
5 +export default {
6 + /**
7 + * Get ui actions
8 + * @returns {Object} actions for ui
9 + * @private
10 + */
11 + getActions() {
12 + return {
13 + main: this._mainAction(),
14 + shape: this._shapeAction(),
15 + crop: this._cropAction(),
16 + flip: this._flipAction(),
17 + rotate: this._rotateAction(),
18 + text: this._textAction(),
19 + mask: this._maskAction(),
20 + draw: this._drawAction(),
21 + icon: this._iconAction(),
22 + filter: this._filterAction(),
23 + };
24 + },
25 +
26 + /**
27 + * Main Action
28 + * @returns {Object} actions for ui main
29 + * @private
30 + */
31 + _mainAction() {
32 + const exitCropOnAction = () => {
33 + if (this.ui.submenu === 'crop') {
34 + this.stopDrawingMode();
35 + this.ui.changeMenu('crop');
36 + }
37 + };
38 + const setAngleRangeBarOnAction = (angle) => {
39 + if (this.ui.submenu === 'rotate') {
40 + this.ui.rotate.setRangeBarAngle('setAngle', angle);
41 + }
42 + };
43 + const setFilterStateRangeBarOnAction = (filterOptions) => {
44 + if (this.ui.submenu === 'filter') {
45 + this.ui.filter.setFilterState(filterOptions);
46 + }
47 + };
48 + const onEndUndoRedo = (result) => {
49 + setAngleRangeBarOnAction(result);
50 + setFilterStateRangeBarOnAction(result);
51 +
52 + return result;
53 + };
54 +
55 + return extend(
56 + {
57 + initLoadImage: (imagePath, imageName) =>
58 + this.loadImageFromURL(imagePath, imageName).then((sizeValue) => {
59 + exitCropOnAction();
60 + this.ui.initializeImgUrl = imagePath;
61 + this.ui.resizeEditor({ imageSize: sizeValue });
62 + this.clearUndoStack();
63 + }),
64 + undo: () => {
65 + if (!this.isEmptyUndoStack()) {
66 + exitCropOnAction();
67 + this.deactivateAll();
68 + this.undo().then(onEndUndoRedo);
69 + }
70 + },
71 + redo: () => {
72 + if (!this.isEmptyRedoStack()) {
73 + exitCropOnAction();
74 + this.deactivateAll();
75 + this.redo().then(onEndUndoRedo);
76 + }
77 + },
78 + reset: () => {
79 + exitCropOnAction();
80 + this.loadImageFromURL(this.ui.initializeImgUrl, 'resetImage').then((sizeValue) => {
81 + exitCropOnAction();
82 + this.ui.resizeEditor({ imageSize: sizeValue });
83 + this.clearUndoStack();
84 + });
85 + },
86 + delete: () => {
87 + this.ui.changeHelpButtonEnabled('delete', false);
88 + exitCropOnAction();
89 + this.removeActiveObject();
90 + this.activeObjectId = null;
91 + },
92 + deleteAll: () => {
93 + exitCropOnAction();
94 + this.clearObjects();
95 + this.ui.changeHelpButtonEnabled('delete', false);
96 + this.ui.changeHelpButtonEnabled('deleteAll', false);
97 + },
98 + load: (file) => {
99 + if (!isSupportFileApi()) {
100 + alert('This browser does not support file-api');
101 + }
102 +
103 + this.ui.initializeImgUrl = URL.createObjectURL(file);
104 + this.loadImageFromFile(file)
105 + .then((sizeValue) => {
106 + exitCropOnAction();
107 + this.clearUndoStack();
108 + this.ui.activeMenuEvent();
109 + this.ui.resizeEditor({ imageSize: sizeValue });
110 + })
111 + ['catch']((message) => Promise.reject(message));
112 + },
113 + download: () => {
114 + const dataURL = this.toDataURL();
115 + let imageName = this.getImageName();
116 + let blob, type, w;
117 +
118 + if (isSupportFileApi() && window.saveAs) {
119 + blob = base64ToBlob(dataURL);
120 + type = blob.type.split('/')[1];
121 + if (imageName.split('.').pop() !== type) {
122 + imageName += `.${type}`;
123 + }
124 + saveAs(blob, imageName); // eslint-disable-line
125 + } else {
126 + w = window.open();
127 + w.document.body.innerHTML = `<img src='${dataURL}'>`;
128 + }
129 + },
130 + },
131 + this._commonAction()
132 + );
133 + },
134 +
135 + /**
136 + * Icon Action
137 + * @returns {Object} actions for ui icon
138 + * @private
139 + */
140 + _iconAction() {
141 + return extend(
142 + {
143 + changeColor: (color) => {
144 + if (this.activeObjectId) {
145 + this.changeIconColor(this.activeObjectId, color);
146 + }
147 + },
148 + addIcon: (iconType, iconColor) => {
149 + this.startDrawingMode('ICON');
150 + this.setDrawingIcon(iconType, iconColor);
151 + },
152 + cancelAddIcon: () => {
153 + this.ui.icon.clearIconType();
154 + this.changeSelectableAll(true);
155 + this.changeCursor('default');
156 + this.stopDrawingMode();
157 + },
158 + registDefalutIcons: (type, path) => {
159 + const iconObj = {};
160 + iconObj[type] = path;
161 + this.registerIcons(iconObj);
162 + },
163 + registCustomIcon: (imgUrl, file) => {
164 + const imagetracer = new Imagetracer();
165 + imagetracer.imageToSVG(
166 + imgUrl,
167 + (svgstr) => {
168 + const [, svgPath] = svgstr.match(/path[^>]*d="([^"]*)"/);
169 + const iconObj = {};
170 + iconObj[file.name] = svgPath;
171 + this.registerIcons(iconObj);
172 + this.addIcon(file.name, {
173 + left: 100,
174 + top: 100,
175 + });
176 + },
177 + Imagetracer.tracerDefaultOption()
178 + );
179 + },
180 + },
181 + this._commonAction()
182 + );
183 + },
184 +
185 + /**
186 + * Draw Action
187 + * @returns {Object} actions for ui draw
188 + * @private
189 + */
190 + _drawAction() {
191 + return extend(
192 + {
193 + setDrawMode: (type, settings) => {
194 + this.stopDrawingMode();
195 + if (type === 'free') {
196 + this.startDrawingMode('FREE_DRAWING', settings);
197 + } else {
198 + this.startDrawingMode('LINE_DRAWING', settings);
199 + }
200 + },
201 + setColor: (color) => {
202 + this.setBrush({
203 + color,
204 + });
205 + },
206 + },
207 + this._commonAction()
208 + );
209 + },
210 +
211 + /**
212 + * Mask Action
213 + * @returns {Object} actions for ui mask
214 + * @private
215 + */
216 + _maskAction() {
217 + return extend(
218 + {
219 + loadImageFromURL: (imgUrl, file) =>
220 + this.loadImageFromURL(this.toDataURL(), 'FilterImage').then(() => {
221 + this.addImageObject(imgUrl).then(() => {
222 + URL.revokeObjectURL(file);
223 + });
224 + }),
225 + applyFilter: () => {
226 + this.applyFilter('mask', {
227 + maskObjId: this.activeObjectId,
228 + });
229 + },
230 + },
231 + this._commonAction()
232 + );
233 + },
234 +
235 + /**
236 + * Text Action
237 + * @returns {Object} actions for ui text
238 + * @private
239 + */
240 + _textAction() {
241 + return extend(
242 + {
243 + changeTextStyle: (styleObj, isSilent) => {
244 + if (this.activeObjectId) {
245 + this.changeTextStyle(this.activeObjectId, styleObj, isSilent);
246 + }
247 + },
248 + },
249 + this._commonAction()
250 + );
251 + },
252 +
253 + /**
254 + * Rotate Action
255 + * @returns {Object} actions for ui rotate
256 + * @private
257 + */
258 + _rotateAction() {
259 + return extend(
260 + {
261 + rotate: (angle, isSilent) => {
262 + this.rotate(angle, isSilent);
263 + this.ui.resizeEditor();
264 + this.ui.rotate.setRangeBarAngle('rotate', angle);
265 + },
266 + setAngle: (angle, isSilent) => {
267 + this.setAngle(angle, isSilent);
268 + this.ui.resizeEditor();
269 + this.ui.rotate.setRangeBarAngle('setAngle', angle);
270 + },
271 + },
272 + this._commonAction()
273 + );
274 + },
275 +
276 + /**
277 + * Shape Action
278 + * @returns {Object} actions for ui shape
279 + * @private
280 + */
281 + _shapeAction() {
282 + return extend(
283 + {
284 + changeShape: (changeShapeObject, isSilent) => {
285 + if (this.activeObjectId) {
286 + this.changeShape(this.activeObjectId, changeShapeObject, isSilent);
287 + }
288 + },
289 + setDrawingShape: (shapeType) => {
290 + this.setDrawingShape(shapeType);
291 + },
292 + },
293 + this._commonAction()
294 + );
295 + },
296 +
297 + /**
298 + * Crop Action
299 + * @returns {Object} actions for ui crop
300 + * @private
301 + */
302 + _cropAction() {
303 + return extend(
304 + {
305 + crop: () => {
306 + const cropRect = this.getCropzoneRect();
307 + if (cropRect) {
308 + this.crop(cropRect)
309 + .then(() => {
310 + this.stopDrawingMode();
311 + this.ui.resizeEditor();
312 + this.ui.changeMenu('crop');
313 + })
314 + ['catch']((message) => Promise.reject(message));
315 + }
316 + },
317 + cancel: () => {
318 + this.stopDrawingMode();
319 + this.ui.changeMenu('crop');
320 + },
321 + /* eslint-disable */
322 + preset: (presetType) => {
323 + switch (presetType) {
324 + case 'preset-square':
325 + this.setCropzoneRect(1 / 1);
326 + break;
327 + case 'preset-3-2':
328 + this.setCropzoneRect(3 / 2);
329 + break;
330 + case 'preset-4-3':
331 + this.setCropzoneRect(4 / 3);
332 + break;
333 + case 'preset-5-4':
334 + this.setCropzoneRect(5 / 4);
335 + break;
336 + case 'preset-7-5':
337 + this.setCropzoneRect(7 / 5);
338 + break;
339 + case 'preset-16-9':
340 + this.setCropzoneRect(16 / 9);
341 + break;
342 + default:
343 + this.setCropzoneRect();
344 + this.ui.crop.changeApplyButtonStatus(false);
345 + break;
346 + }
347 + },
348 + },
349 + this._commonAction()
350 + );
351 + },
352 +
353 + /**
354 + * Flip Action
355 + * @returns {Object} actions for ui flip
356 + * @private
357 + */
358 + _flipAction() {
359 + return extend(
360 + {
361 + flip: (flipType) => this[flipType](),
362 + },
363 + this._commonAction()
364 + );
365 + },
366 +
367 + /**
368 + * Filter Action
369 + * @returns {Object} actions for ui filter
370 + * @private
371 + */
372 + _filterAction() {
373 + return extend(
374 + {
375 + applyFilter: (applying, type, options, isSilent) => {
376 + if (applying) {
377 + this.applyFilter(type, options, isSilent);
378 + } else if (this.hasFilter(type)) {
379 + this.removeFilter(type);
380 + }
381 + },
382 + },
383 + this._commonAction()
384 + );
385 + },
386 +
387 + /**
388 + * Image Editor Event Observer
389 + */
390 + setReAction() {
391 + this.on({
392 + undoStackChanged: (length) => {
393 + if (length) {
394 + this.ui.changeHelpButtonEnabled('undo', true);
395 + this.ui.changeHelpButtonEnabled('reset', true);
396 + } else {
397 + this.ui.changeHelpButtonEnabled('undo', false);
398 + this.ui.changeHelpButtonEnabled('reset', false);
399 + }
400 + this.ui.resizeEditor();
401 + },
402 + redoStackChanged: (length) => {
403 + if (length) {
404 + this.ui.changeHelpButtonEnabled('redo', true);
405 + } else {
406 + this.ui.changeHelpButtonEnabled('redo', false);
407 + }
408 + this.ui.resizeEditor();
409 + },
410 + /* eslint-disable complexity */
411 + objectActivated: (obj) => {
412 + this.activeObjectId = obj.id;
413 +
414 + this.ui.changeHelpButtonEnabled('delete', true);
415 + this.ui.changeHelpButtonEnabled('deleteAll', true);
416 +
417 + if (obj.type === 'cropzone') {
418 + this.ui.crop.changeApplyButtonStatus(true);
419 + } else if (['rect', 'circle', 'triangle'].indexOf(obj.type) > -1) {
420 + this.stopDrawingMode();
421 + if (this.ui.submenu !== 'shape') {
422 + this.ui.changeMenu('shape', false, false);
423 + }
424 + this.ui.shape.setShapeStatus({
425 + strokeColor: obj.stroke,
426 + strokeWidth: obj.strokeWidth,
427 + fillColor: obj.fill,
428 + });
429 +
430 + this.ui.shape.setMaxStrokeValue(Math.min(obj.width, obj.height));
431 + } else if (obj.type === 'path' || obj.type === 'line') {
432 + if (this.ui.submenu !== 'draw') {
433 + this.ui.changeMenu('draw', false, false);
434 + this.ui.draw.changeStandbyMode();
435 + }
436 + } else if (['i-text', 'text'].indexOf(obj.type) > -1) {
437 + if (this.ui.submenu !== 'text') {
438 + this.ui.changeMenu('text', false, false);
439 + }
440 +
441 + this.ui.text.setTextStyleStateOnAction(obj);
442 + } else if (obj.type === 'icon') {
443 + this.stopDrawingMode();
444 + if (this.ui.submenu !== 'icon') {
445 + this.ui.changeMenu('icon', false, false);
446 + }
447 + this.ui.icon.setIconPickerColor(obj.fill);
448 + }
449 + },
450 + /* eslint-enable complexity */
451 + addText: (pos) => {
452 + const { textColor: fill, fontSize, fontStyle, fontWeight, underline } = this.ui.text;
453 + const fontFamily = 'Noto Sans';
454 +
455 + this.addText('Double Click', {
456 + position: pos.originPosition,
457 + styles: { fill, fontSize, fontFamily, fontStyle, fontWeight, underline },
458 + }).then(() => {
459 + this.changeCursor('default');
460 + });
461 + },
462 + addObjectAfter: (obj) => {
463 + if (obj.type === 'icon') {
464 + this.ui.icon.changeStandbyMode();
465 + } else if (['rect', 'circle', 'triangle'].indexOf(obj.type) > -1) {
466 + this.ui.shape.setMaxStrokeValue(Math.min(obj.width, obj.height));
467 + this.ui.shape.changeStandbyMode();
468 + }
469 + },
470 + objectScaled: (obj) => {
471 + if (['i-text', 'text'].indexOf(obj.type) > -1) {
472 + this.ui.text.fontSize = toInteger(obj.fontSize);
473 + } else if (['rect', 'circle', 'triangle'].indexOf(obj.type) >= 0) {
474 + const { width, height } = obj;
475 + const strokeValue = this.ui.shape.getStrokeValue();
476 +
477 + if (width < strokeValue) {
478 + this.ui.shape.setStrokeValue(width);
479 + }
480 + if (height < strokeValue) {
481 + this.ui.shape.setStrokeValue(height);
482 + }
483 + }
484 + },
485 + selectionCleared: () => {
486 + this.activeObjectId = null;
487 + if (this.ui.submenu === 'text') {
488 + this.changeCursor('text');
489 + } else if (this.ui.submenu !== 'draw' && this.ui.submenu !== 'crop') {
490 + this.stopDrawingMode();
491 + }
492 + },
493 + });
494 + },
495 +
496 + /**
497 + * Common Action
498 + * @returns {Object} common actions for ui
499 + * @private
500 + */
501 + _commonAction() {
502 + return {
503 + modeChange: (menu) => {
504 + switch (menu) {
505 + case 'text':
506 + this._changeActivateMode('TEXT');
507 + break;
508 + case 'crop':
509 + this.startDrawingMode('CROPPER');
510 + break;
511 + case 'shape':
512 + this._changeActivateMode('SHAPE');
513 + this.setDrawingShape(this.ui.shape.type, this.ui.shape.options);
514 + break;
515 + default:
516 + break;
517 + }
518 + },
519 + deactivateAll: this.deactivateAll.bind(this),
520 + changeSelectableAll: this.changeSelectableAll.bind(this),
521 + discardSelection: this.discardSelection.bind(this),
522 + stopDrawingMode: this.stopDrawingMode.bind(this),
523 + };
524 + },
525 +
526 + /**
527 + * Mixin
528 + * @param {ImageEditor} ImageEditor instance
529 + */
530 + mixin(ImageEditor) {
531 + extend(ImageEditor.prototype, this);
532 + },
533 +};
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Add an icon
4 + */
5 +import commandFactory from '../factory/command';
6 +import { Promise } from '../util';
7 +import { componentNames, commandNames } from '../consts';
8 +
9 +const { ICON } = componentNames;
10 +
11 +const command = {
12 + name: commandNames.ADD_ICON,
13 +
14 + /**
15 + * Add an icon
16 + * @param {Graphics} graphics - Graphics instance
17 + * @param {string} type - Icon type ('arrow', 'cancel', custom icon name)
18 + * @param {Object} options - Icon options
19 + * @param {string} [options.fill] - Icon foreground color
20 + * @param {string} [options.left] - Icon x position
21 + * @param {string} [options.top] - Icon y position
22 + * @returns {Promise}
23 + */
24 + execute(graphics, type, options) {
25 + const iconComp = graphics.getComponent(ICON);
26 +
27 + return iconComp.add(type, options).then((objectProps) => {
28 + this.undoData.object = graphics.getObject(objectProps.id);
29 +
30 + return objectProps;
31 + });
32 + },
33 + /**
34 + * @param {Graphics} graphics - Graphics instance
35 + * @returns {Promise}
36 + */
37 + undo(graphics) {
38 + graphics.remove(this.undoData.object);
39 +
40 + return Promise.resolve();
41 + },
42 +};
43 +
44 +commandFactory.register(command);
45 +
46 +export default command;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Add an image object
4 + */
5 +import commandFactory from '../factory/command';
6 +import { Promise } from '../util';
7 +import { commandNames } from '../consts';
8 +
9 +const command = {
10 + name: commandNames.ADD_IMAGE_OBJECT,
11 +
12 + /**
13 + * Add an image object
14 + * @param {Graphics} graphics - Graphics instance
15 + * @param {string} imgUrl - Image url to make object
16 + * @returns {Promise}
17 + */
18 + execute(graphics, imgUrl) {
19 + return graphics.addImageObject(imgUrl).then((objectProps) => {
20 + this.undoData.object = graphics.getObject(objectProps.id);
21 +
22 + return objectProps;
23 + });
24 + },
25 + /**
26 + * @param {Graphics} graphics - Graphics instance
27 + * @returns {Promise}
28 + */
29 + undo(graphics) {
30 + graphics.remove(this.undoData.object);
31 +
32 + return Promise.resolve();
33 + },
34 +};
35 +
36 +commandFactory.register(command);
37 +
38 +export default command;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Add an object
4 + */
5 +import commandFactory from '../factory/command';
6 +import { Promise } from '../util';
7 +import { commandNames, rejectMessages } from '../consts';
8 +
9 +const command = {
10 + name: commandNames.ADD_OBJECT,
11 +
12 + /**
13 + * Add an object
14 + * @param {Graphics} graphics - Graphics instance
15 + * @param {Object} object - Fabric object
16 + * @returns {Promise}
17 + */
18 + execute(graphics, object) {
19 + return new Promise((resolve, reject) => {
20 + if (!graphics.contains(object)) {
21 + graphics.add(object);
22 + resolve(object);
23 + } else {
24 + reject(rejectMessages.addedObject);
25 + }
26 + });
27 + },
28 + /**
29 + * @param {Graphics} graphics - Graphics instance
30 + * @param {Object} object - Fabric object
31 + * @returns {Promise}
32 + */
33 + undo(graphics, object) {
34 + return new Promise((resolve, reject) => {
35 + if (graphics.contains(object)) {
36 + graphics.remove(object);
37 + resolve(object);
38 + } else {
39 + reject(rejectMessages.noObject);
40 + }
41 + });
42 + },
43 +};
44 +
45 +commandFactory.register(command);
46 +
47 +export default command;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Add a shape
4 + */
5 +import commandFactory from '../factory/command';
6 +import { Promise } from '../util';
7 +import { componentNames, commandNames } from '../consts';
8 +
9 +const { SHAPE } = componentNames;
10 +
11 +const command = {
12 + name: commandNames.ADD_SHAPE,
13 +
14 + /**
15 + * Add a shape
16 + * @param {Graphics} graphics - Graphics instance
17 + * @param {string} type - Shape type (ex: 'rect', 'circle', 'triangle')
18 + * @param {Object} options - Shape options
19 + * @param {string} [options.fill] - Shape foreground color (ex: '#fff', 'transparent')
20 + * @param {string} [options.stroke] - Shape outline color
21 + * @param {number} [options.strokeWidth] - Shape outline width
22 + * @param {number} [options.width] - Width value (When type option is 'rect', this options can use)
23 + * @param {number} [options.height] - Height value (When type option is 'rect', this options can use)
24 + * @param {number} [options.rx] - Radius x value (When type option is 'circle', this options can use)
25 + * @param {number} [options.ry] - Radius y value (When type option is 'circle', this options can use)
26 + * @param {number} [options.left] - Shape x position
27 + * @param {number} [options.top] - Shape y position
28 + * @param {number} [options.isRegular] - Whether resizing shape has 1:1 ratio or not
29 + * @returns {Promise}
30 + */
31 + execute(graphics, type, options) {
32 + const shapeComp = graphics.getComponent(SHAPE);
33 +
34 + return shapeComp.add(type, options).then((objectProps) => {
35 + this.undoData.object = graphics.getObject(objectProps.id);
36 +
37 + return objectProps;
38 + });
39 + },
40 + /**
41 + * @param {Graphics} graphics - Graphics instance
42 + * @returns {Promise}
43 + */
44 + undo(graphics) {
45 + graphics.remove(this.undoData.object);
46 +
47 + return Promise.resolve();
48 + },
49 +};
50 +
51 +commandFactory.register(command);
52 +
53 +export default command;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Add a text object
4 + */
5 +import commandFactory from '../factory/command';
6 +import { Promise } from '../util';
7 +import { componentNames, commandNames, rejectMessages } from '../consts';
8 +const { TEXT } = componentNames;
9 +
10 +const command = {
11 + name: commandNames.ADD_TEXT,
12 +
13 + /**
14 + * Add a text object
15 + * @param {Graphics} graphics - Graphics instance
16 + * @param {string} text - Initial input text
17 + * @param {Object} [options] Options for text styles
18 + * @param {Object} [options.styles] Initial styles
19 + * @param {string} [options.styles.fill] Color
20 + * @param {string} [options.styles.fontFamily] Font type for text
21 + * @param {number} [options.styles.fontSize] Size
22 + * @param {string} [options.styles.fontStyle] Type of inclination (normal / italic)
23 + * @param {string} [options.styles.fontWeight] Type of thicker or thinner looking (normal / bold)
24 + * @param {string} [options.styles.textAlign] Type of text align (left / center / right)
25 + * @param {string} [options.styles.textDecoration] Type of line (underline / line-through / overline)
26 + * @param {{x: number, y: number}} [options.position] - Initial position
27 + * @returns {Promise}
28 + */
29 + execute(graphics, text, options) {
30 + const textComp = graphics.getComponent(TEXT);
31 +
32 + if (this.undoData.object) {
33 + const undoObject = this.undoData.object;
34 +
35 + return new Promise((resolve, reject) => {
36 + if (!graphics.contains(undoObject)) {
37 + graphics.add(undoObject);
38 + resolve(undoObject);
39 + } else {
40 + reject(rejectMessages.redo);
41 + }
42 + });
43 + }
44 +
45 + return textComp.add(text, options).then((objectProps) => {
46 + const { id } = objectProps;
47 + const textObject = graphics.getObject(id);
48 +
49 + this.undoData.object = textObject;
50 +
51 + return objectProps;
52 + });
53 + },
54 + /**
55 + * @param {Graphics} graphics - Graphics instance
56 + * @returns {Promise}
57 + */
58 + undo(graphics) {
59 + graphics.remove(this.undoData.object);
60 +
61 + return Promise.resolve();
62 + },
63 +};
64 +
65 +commandFactory.register(command);
66 +
67 +export default command;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Apply a filter into an image
4 + */
5 +import snippet from 'tui-code-snippet';
6 +import commandFactory from '../factory/command';
7 +import { componentNames, rejectMessages, commandNames } from '../consts';
8 +
9 +const { FILTER } = componentNames;
10 +
11 +/**
12 + * Chched data for undo
13 + * @type {Object}
14 + */
15 +let chchedUndoDataForSilent = null;
16 +
17 +/**
18 + * Make undoData
19 + * @param {string} type - Filter type
20 + * @param {Object} prevfilterOption - prev Filter options
21 + * @param {Object} options - Filter options
22 + * @returns {object} - undo data
23 + */
24 +function makeUndoData(type, prevfilterOption, options) {
25 + const undoData = {};
26 +
27 + if (type === 'mask') {
28 + undoData.object = options.mask;
29 + }
30 +
31 + undoData.options = prevfilterOption;
32 +
33 + return undoData;
34 +}
35 +
36 +const command = {
37 + name: commandNames.APPLY_FILTER,
38 +
39 + /**
40 + * Apply a filter into an image
41 + * @param {Graphics} graphics - Graphics instance
42 + * @param {string} type - Filter type
43 + * @param {Object} options - Filter options
44 + * @param {number} options.maskObjId - masking image object id
45 + * @param {boolean} isSilent - is silent execution or not
46 + * @returns {Promise}
47 + */
48 + execute(graphics, type, options, isSilent) {
49 + const filterComp = graphics.getComponent(FILTER);
50 +
51 + if (type === 'mask') {
52 + const maskObj = graphics.getObject(options.maskObjId);
53 +
54 + if (!(maskObj && maskObj.isType('image'))) {
55 + return Promise.reject(rejectMessages.invalidParameters);
56 + }
57 +
58 + snippet.extend(options, { mask: maskObj });
59 + graphics.remove(options.mask);
60 + }
61 + if (!this.isRedo) {
62 + const prevfilterOption = filterComp.getOptions(type);
63 + const undoData = makeUndoData(type, prevfilterOption, options);
64 +
65 + chchedUndoDataForSilent = this.setUndoData(undoData, chchedUndoDataForSilent, isSilent);
66 + }
67 +
68 + return filterComp.add(type, options);
69 + },
70 + /**
71 + * @param {Graphics} graphics - Graphics instance
72 + * @param {string} type - Filter type
73 + * @returns {Promise}
74 + */
75 + undo(graphics, type) {
76 + const filterComp = graphics.getComponent(FILTER);
77 +
78 + if (type === 'mask') {
79 + const mask = this.undoData.object;
80 + graphics.add(mask);
81 + graphics.setActiveObject(mask);
82 +
83 + return filterComp.remove(type);
84 + }
85 +
86 + // options changed case
87 + if (this.undoData.options) {
88 + return filterComp.add(type, this.undoData.options);
89 + }
90 +
91 + // filter added case
92 + return filterComp.remove(type);
93 + },
94 +};
95 +
96 +commandFactory.register(command);
97 +
98 +export default command;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Change icon color
4 + */
5 +import commandFactory from '../factory/command';
6 +import { Promise } from '../util';
7 +import { componentNames, rejectMessages, commandNames } from '../consts';
8 +
9 +const { ICON } = componentNames;
10 +
11 +const command = {
12 + name: commandNames.CHANGE_ICON_COLOR,
13 +
14 + /**
15 + * Change icon color
16 + * @param {Graphics} graphics - Graphics instance
17 + * @param {number} id - object id
18 + * @param {string} color - Color for icon
19 + * @returns {Promise}
20 + */
21 + execute(graphics, id, color) {
22 + return new Promise((resolve, reject) => {
23 + const iconComp = graphics.getComponent(ICON);
24 + const targetObj = graphics.getObject(id);
25 +
26 + if (!targetObj) {
27 + reject(rejectMessages.noObject);
28 + }
29 +
30 + this.undoData.object = targetObj;
31 + this.undoData.color = iconComp.getColor(targetObj);
32 + iconComp.setColor(color, targetObj);
33 + resolve();
34 + });
35 + },
36 + /**
37 + * @param {Graphics} graphics - Graphics instance
38 + * @returns {Promise}
39 + */
40 + undo(graphics) {
41 + const iconComp = graphics.getComponent(ICON);
42 + const { object: icon, color } = this.undoData;
43 +
44 + iconComp.setColor(color, icon);
45 +
46 + return Promise.resolve();
47 + },
48 +};
49 +
50 +commandFactory.register(command);
51 +
52 +export default command;
1 +/**
2 + * @author NHN. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview change selection
4 + */
5 +import commandFactory from '../factory/command';
6 +import { Promise } from '../util';
7 +import { commandNames } from '../consts';
8 +import { getCachedUndoDataForDimension } from '../helper/selectionModifyHelper';
9 +
10 +const command = {
11 + name: commandNames.CHANGE_SELECTION,
12 +
13 + execute(graphics, props) {
14 + if (this.isRedo) {
15 + props.forEach((prop) => {
16 + graphics.setObjectProperties(prop.id, prop);
17 + });
18 + } else {
19 + this.undoData = getCachedUndoDataForDimension();
20 + }
21 +
22 + return Promise.resolve();
23 + },
24 + undo(graphics) {
25 + this.undoData.forEach((datum) => {
26 + graphics.setObjectProperties(datum.id, datum);
27 + });
28 +
29 + return Promise.resolve();
30 + },
31 +};
32 +
33 +commandFactory.register(command);
34 +
35 +export default command;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview change a shape
4 + */
5 +import snippet from 'tui-code-snippet';
6 +import { Promise } from '../util';
7 +import commandFactory from '../factory/command';
8 +import { componentNames, rejectMessages, commandNames } from '../consts';
9 +
10 +const { SHAPE } = componentNames;
11 +
12 +/**
13 + * Chched data for undo
14 + * @type {Object}
15 + */
16 +let chchedUndoDataForSilent = null;
17 +
18 +/**
19 + * Make undoData
20 + * @param {object} options - shape options
21 + * @param {Component} targetObj - shape component
22 + * @returns {object} - undo data
23 + */
24 +function makeUndoData(options, targetObj) {
25 + const undoData = {
26 + object: targetObj,
27 + options: {},
28 + };
29 +
30 + snippet.forEachOwnProperties(options, (value, key) => {
31 + undoData.options[key] = targetObj[key];
32 + });
33 +
34 + return undoData;
35 +}
36 +
37 +const command = {
38 + name: commandNames.CHANGE_SHAPE,
39 +
40 + /**
41 + * Change a shape
42 + * @param {Graphics} graphics - Graphics instance
43 + * @param {number} id - object id
44 + * @param {Object} options - Shape options
45 + * @param {string} [options.fill] - Shape foreground color (ex: '#fff', 'transparent')
46 + * @param {string} [options.stroke] - Shape outline color
47 + * @param {number} [options.strokeWidth] - Shape outline width
48 + * @param {number} [options.width] - Width value (When type option is 'rect', this options can use)
49 + * @param {number} [options.height] - Height value (When type option is 'rect', this options can use)
50 + * @param {number} [options.rx] - Radius x value (When type option is 'circle', this options can use)
51 + * @param {number} [options.ry] - Radius y value (When type option is 'circle', this options can use)
52 + * @param {number} [options.left] - Shape x position
53 + * @param {number} [options.top] - Shape y position
54 + * @param {number} [options.isRegular] - Whether resizing shape has 1:1 ratio or not
55 + * @param {boolean} isSilent - is silent execution or not
56 + * @returns {Promise}
57 + */
58 + execute(graphics, id, options, isSilent) {
59 + const shapeComp = graphics.getComponent(SHAPE);
60 + const targetObj = graphics.getObject(id);
61 +
62 + if (!targetObj) {
63 + return Promise.reject(rejectMessages.noObject);
64 + }
65 +
66 + if (!this.isRedo) {
67 + const undoData = makeUndoData(options, targetObj);
68 +
69 + chchedUndoDataForSilent = this.setUndoData(undoData, chchedUndoDataForSilent, isSilent);
70 + }
71 +
72 + return shapeComp.change(targetObj, options);
73 + },
74 + /**
75 + * @param {Graphics} graphics - Graphics instance
76 + * @returns {Promise}
77 + */
78 + undo(graphics) {
79 + const shapeComp = graphics.getComponent(SHAPE);
80 + const { object: shape, options } = this.undoData;
81 +
82 + return shapeComp.change(shape, options);
83 + },
84 +};
85 +
86 +commandFactory.register(command);
87 +
88 +export default command;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Change a text
4 + */
5 +import commandFactory from '../factory/command';
6 +import { Promise } from '../util';
7 +import { componentNames, rejectMessages, commandNames } from '../consts';
8 +
9 +const { TEXT } = componentNames;
10 +
11 +const command = {
12 + name: commandNames.CHANGE_TEXT,
13 +
14 + /**
15 + * Change a text
16 + * @param {Graphics} graphics - Graphics instance
17 + * @param {number} id - object id
18 + * @param {string} text - Changing text
19 + * @returns {Promise}
20 + */
21 + execute(graphics, id, text) {
22 + const textComp = graphics.getComponent(TEXT);
23 + const targetObj = graphics.getObject(id);
24 +
25 + if (!targetObj) {
26 + return Promise.reject(rejectMessages.noObject);
27 + }
28 +
29 + this.undoData.object = targetObj;
30 + this.undoData.text = textComp.getText(targetObj);
31 +
32 + return textComp.change(targetObj, text);
33 + },
34 + /**
35 + * @param {Graphics} graphics - Graphics instance
36 + * @returns {Promise}
37 + */
38 + undo(graphics) {
39 + const textComp = graphics.getComponent(TEXT);
40 + const { object: textObj, text } = this.undoData;
41 +
42 + return textComp.change(textObj, text);
43 + },
44 +};
45 +
46 +commandFactory.register(command);
47 +
48 +export default command;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Change text styles
4 + */
5 +import snippet from 'tui-code-snippet';
6 +import commandFactory from '../factory/command';
7 +import { Promise } from '../util';
8 +import { componentNames, rejectMessages, commandNames } from '../consts';
9 +
10 +const { TEXT } = componentNames;
11 +
12 +/**
13 + * Chched data for undo
14 + * @type {Object}
15 + */
16 +let chchedUndoDataForSilent = null;
17 +
18 +/**
19 + * Make undoData
20 + * @param {object} styles - text styles
21 + * @param {Component} targetObj - text component
22 + * @returns {object} - undo data
23 + */
24 +function makeUndoData(styles, targetObj) {
25 + const undoData = {
26 + object: targetObj,
27 + styles: {},
28 + };
29 + snippet.forEachOwnProperties(styles, (value, key) => {
30 + const undoValue = targetObj[key];
31 + undoData.styles[key] = undoValue;
32 + });
33 +
34 + return undoData;
35 +}
36 +
37 +const command = {
38 + name: commandNames.CHANGE_TEXT_STYLE,
39 +
40 + /**
41 + * Change text styles
42 + * @param {Graphics} graphics - Graphics instance
43 + * @param {number} id - object id
44 + * @param {Object} styles - text styles
45 + * @param {string} [styles.fill] Color
46 + * @param {string} [styles.fontFamily] Font type for text
47 + * @param {number} [styles.fontSize] Size
48 + * @param {string} [styles.fontStyle] Type of inclination (normal / italic)
49 + * @param {string} [styles.fontWeight] Type of thicker or thinner looking (normal / bold)
50 + * @param {string} [styles.textAlign] Type of text align (left / center / right)
51 + * @param {string} [styles.textDecoration] Type of line (underline / line-through / overline)
52 + * @param {boolean} isSilent - is silent execution or not
53 + * @returns {Promise}
54 + */
55 + execute(graphics, id, styles, isSilent) {
56 + const textComp = graphics.getComponent(TEXT);
57 + const targetObj = graphics.getObject(id);
58 +
59 + if (!targetObj) {
60 + return Promise.reject(rejectMessages.noObject);
61 + }
62 + if (!this.isRedo) {
63 + const undoData = makeUndoData(styles, targetObj);
64 +
65 + chchedUndoDataForSilent = this.setUndoData(undoData, chchedUndoDataForSilent, isSilent);
66 + }
67 +
68 + return textComp.setStyle(targetObj, styles);
69 + },
70 + /**
71 + * @param {Graphics} graphics - Graphics instance
72 + * @returns {Promise}
73 + */
74 + undo(graphics) {
75 + const textComp = graphics.getComponent(TEXT);
76 + const { object: textObj, styles } = this.undoData;
77 +
78 + return textComp.setStyle(textObj, styles);
79 + },
80 +};
81 +
82 +commandFactory.register(command);
83 +
84 +export default command;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Clear all objects
4 + */
5 +import commandFactory from '../factory/command';
6 +import { Promise } from '../util';
7 +import { commandNames } from '../consts';
8 +
9 +const command = {
10 + name: commandNames.CLEAR_OBJECTS,
11 +
12 + /**
13 + * Clear all objects without background (main) image
14 + * @param {Graphics} graphics - Graphics instance
15 + * @returns {Promise}
16 + */
17 + execute(graphics) {
18 + return new Promise((resolve) => {
19 + this.undoData.objects = graphics.removeAll();
20 + resolve();
21 + });
22 + },
23 + /**
24 + * @param {Graphics} graphics - Graphics instance
25 + * @returns {Promise}
26 + * @ignore
27 + */
28 + undo(graphics) {
29 + graphics.add(this.undoData.objects);
30 +
31 + return Promise.resolve();
32 + },
33 +};
34 +
35 +commandFactory.register(command);
36 +
37 +export default command;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Flip an image
4 + */
5 +import commandFactory from '../factory/command';
6 +import { componentNames, commandNames } from '../consts';
7 +
8 +const { FLIP } = componentNames;
9 +
10 +const command = {
11 + name: commandNames.FLIP_IMAGE,
12 +
13 + /**
14 + * flip an image
15 + * @param {Graphics} graphics - Graphics instance
16 + * @param {string} type - 'flipX' or 'flipY' or 'reset'
17 + * @returns {Promise}
18 + */
19 + execute(graphics, type) {
20 + const flipComp = graphics.getComponent(FLIP);
21 +
22 + this.undoData.setting = flipComp.getCurrentSetting();
23 +
24 + return flipComp[type]();
25 + },
26 + /**
27 + * @param {Graphics} graphics - Graphics instance
28 + * @returns {Promise}
29 + */
30 + undo(graphics) {
31 + const flipComp = graphics.getComponent(FLIP);
32 +
33 + return flipComp.set(this.undoData.setting);
34 + },
35 +};
36 +
37 +commandFactory.register(command);
38 +
39 +export default command;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Load a background (main) image
4 + */
5 +import commandFactory from '../factory/command';
6 +import { componentNames, commandNames } from '../consts';
7 +
8 +const { IMAGE_LOADER } = componentNames;
9 +
10 +const command = {
11 + name: commandNames.LOAD_IMAGE,
12 +
13 + /**
14 + * Load a background (main) image
15 + * @param {Graphics} graphics - Graphics instance
16 + * @param {string} imageName - Image name
17 + * @param {string} imgUrl - Image Url
18 + * @returns {Promise}
19 + */
20 + execute(graphics, imageName, imgUrl) {
21 + const loader = graphics.getComponent(IMAGE_LOADER);
22 + const prevImage = loader.getCanvasImage();
23 + const prevImageWidth = prevImage ? prevImage.width : 0;
24 + const prevImageHeight = prevImage ? prevImage.height : 0;
25 + const objects = graphics.removeAll(true).filter((objectItem) => objectItem.type !== 'cropzone');
26 +
27 + objects.forEach((objectItem) => {
28 + objectItem.evented = true;
29 + });
30 +
31 + this.undoData = {
32 + name: loader.getImageName(),
33 + image: prevImage,
34 + objects,
35 + };
36 +
37 + return loader.load(imageName, imgUrl).then((newImage) => ({
38 + oldWidth: prevImageWidth,
39 + oldHeight: prevImageHeight,
40 + newWidth: newImage.width,
41 + newHeight: newImage.height,
42 + }));
43 + },
44 +
45 + /**
46 + * @param {Graphics} graphics - Graphics instance
47 + * @returns {Promise}
48 + */
49 + undo(graphics) {
50 + const loader = graphics.getComponent(IMAGE_LOADER);
51 + const { objects, name, image } = this.undoData;
52 +
53 + graphics.removeAll(true);
54 + graphics.add(objects);
55 +
56 + return loader.load(name, image);
57 + },
58 +};
59 +
60 +commandFactory.register(command);
61 +
62 +export default command;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Remove a filter from an image
4 + */
5 +import commandFactory from '../factory/command';
6 +import { componentNames, commandNames } from '../consts';
7 +
8 +const { FILTER } = componentNames;
9 +
10 +const command = {
11 + name: commandNames.REMOVE_FILTER,
12 +
13 + /**
14 + * Remove a filter from an image
15 + * @param {Graphics} graphics - Graphics instance
16 + * @param {string} type - Filter type
17 + * @returns {Promise}
18 + */
19 + execute(graphics, type) {
20 + const filterComp = graphics.getComponent(FILTER);
21 +
22 + this.undoData.options = filterComp.getOptions(type);
23 +
24 + return filterComp.remove(type);
25 + },
26 + /**
27 + * @param {Graphics} graphics - Graphics instance
28 + * @param {string} type - Filter type
29 + * @returns {Promise}
30 + */
31 + undo(graphics, type) {
32 + const filterComp = graphics.getComponent(FILTER);
33 + const { options } = this.undoData;
34 +
35 + return filterComp.add(type, options);
36 + },
37 +};
38 +
39 +commandFactory.register(command);
40 +
41 +export default command;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Remove an object
4 + */
5 +import commandFactory from '../factory/command';
6 +import { Promise } from '../util';
7 +import { commandNames, rejectMessages } from '../consts';
8 +
9 +const command = {
10 + name: commandNames.REMOVE_OBJECT,
11 +
12 + /**
13 + * Remove an object
14 + * @param {Graphics} graphics - Graphics instance
15 + * @param {number} id - object id
16 + * @returns {Promise}
17 + */
18 + execute(graphics, id) {
19 + return new Promise((resolve, reject) => {
20 + this.undoData.objects = graphics.removeObjectById(id);
21 + if (this.undoData.objects.length) {
22 + resolve();
23 + } else {
24 + reject(rejectMessages.noObject);
25 + }
26 + });
27 + },
28 + /**
29 + * @param {Graphics} graphics - Graphics instance
30 + * @returns {Promise}
31 + */
32 + undo(graphics) {
33 + graphics.add(this.undoData.objects);
34 +
35 + return Promise.resolve();
36 + },
37 +};
38 +
39 +commandFactory.register(command);
40 +
41 +export default command;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Resize a canvas
4 + */
5 +import commandFactory from '../factory/command';
6 +import { Promise } from '../util';
7 +import { commandNames } from '../consts';
8 +
9 +const command = {
10 + name: commandNames.RESIZE_CANVAS_DIMENSION,
11 +
12 + /**
13 + * resize the canvas with given dimension
14 + * @param {Graphics} graphics - Graphics instance
15 + * @param {{width: number, height: number}} dimension - Max width & height
16 + * @returns {Promise}
17 + */
18 + execute(graphics, dimension) {
19 + return new Promise((resolve) => {
20 + this.undoData.size = {
21 + width: graphics.cssMaxWidth,
22 + height: graphics.cssMaxHeight,
23 + };
24 +
25 + graphics.setCssMaxDimension(dimension);
26 + graphics.adjustCanvasDimension();
27 + resolve();
28 + });
29 + },
30 + /**
31 + * @param {Graphics} graphics - Graphics instance
32 + * @returns {Promise}
33 + */
34 + undo(graphics) {
35 + graphics.setCssMaxDimension(this.undoData.size);
36 + graphics.adjustCanvasDimension();
37 +
38 + return Promise.resolve();
39 + },
40 +};
41 +
42 +commandFactory.register(command);
43 +
44 +export default command;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Rotate an image
4 + */
5 +import commandFactory from '../factory/command';
6 +import { componentNames, commandNames } from '../consts';
7 +
8 +const { ROTATION } = componentNames;
9 +
10 +/**
11 + * Chched data for undo
12 + * @type {Object}
13 + */
14 +let chchedUndoDataForSilent = null;
15 +
16 +/**
17 + * Make undo data
18 + * @param {Component} rotationComp - rotation component
19 + * @returns {object} - undodata
20 + */
21 +function makeUndoData(rotationComp) {
22 + return {
23 + angle: rotationComp.getCurrentAngle(),
24 + };
25 +}
26 +
27 +const command = {
28 + name: commandNames.ROTATE_IMAGE,
29 +
30 + /**
31 + * Rotate an image
32 + * @param {Graphics} graphics - Graphics instance
33 + * @param {string} type - 'rotate' or 'setAngle'
34 + * @param {number} angle - angle value (degree)
35 + * @param {boolean} isSilent - is silent execution or not
36 + * @returns {Promise}
37 + */
38 + execute(graphics, type, angle, isSilent) {
39 + const rotationComp = graphics.getComponent(ROTATION);
40 +
41 + if (!this.isRedo) {
42 + const undoData = makeUndoData(rotationComp);
43 +
44 + chchedUndoDataForSilent = this.setUndoData(undoData, chchedUndoDataForSilent, isSilent);
45 + }
46 +
47 + return rotationComp[type](angle);
48 + },
49 + /**
50 + * @param {Graphics} graphics - Graphics instance
51 + * @returns {Promise}
52 + */
53 + undo(graphics) {
54 + const rotationComp = graphics.getComponent(ROTATION);
55 + const [, type, angle] = this.args;
56 +
57 + if (type === 'setAngle') {
58 + return rotationComp[type](this.undoData.angle);
59 + }
60 +
61 + return rotationComp.rotate(-angle);
62 + },
63 +};
64 +
65 +commandFactory.register(command);
66 +
67 +export default command;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Set object properties
4 + */
5 +import commandFactory from '../factory/command';
6 +import { Promise } from '../util';
7 +import { commandNames, rejectMessages } from '../consts';
8 +
9 +const command = {
10 + name: commandNames.SET_OBJECT_POSITION,
11 +
12 + /**
13 + * Set object properties
14 + * @param {Graphics} graphics - Graphics instance
15 + * @param {number} id - object id
16 + * @param {Object} posInfo - position object
17 + * @param {number} posInfo.x - x position
18 + * @param {number} posInfo.y - y position
19 + * @param {string} posInfo.originX - can be 'left', 'center', 'right'
20 + * @param {string} posInfo.originY - can be 'top', 'center', 'bottom'
21 + * @returns {Promise}
22 + */
23 + execute(graphics, id, posInfo) {
24 + const targetObj = graphics.getObject(id);
25 +
26 + if (!targetObj) {
27 + return Promise.reject(rejectMessages.noObject);
28 + }
29 +
30 + this.undoData.objectId = id;
31 + this.undoData.props = graphics.getObjectProperties(id, ['left', 'top']);
32 +
33 + graphics.setObjectPosition(id, posInfo);
34 + graphics.renderAll();
35 +
36 + return Promise.resolve();
37 + },
38 + /**
39 + * @param {Graphics} graphics - Graphics instance
40 + * @returns {Promise}
41 + */
42 + undo(graphics) {
43 + const { objectId, props } = this.undoData;
44 +
45 + graphics.setObjectProperties(objectId, props);
46 + graphics.renderAll();
47 +
48 + return Promise.resolve();
49 + },
50 +};
51 +
52 +commandFactory.register(command);
53 +
54 +export default command;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Set object properties
4 + */
5 +import snippet from 'tui-code-snippet';
6 +import commandFactory from '../factory/command';
7 +import { Promise } from '../util';
8 +import { commandNames, rejectMessages } from '../consts';
9 +
10 +const command = {
11 + name: commandNames.SET_OBJECT_PROPERTIES,
12 +
13 + /**
14 + * Set object properties
15 + * @param {Graphics} graphics - Graphics instance
16 + * @param {number} id - object id
17 + * @param {Object} props - properties
18 + * @param {string} [props.fill] Color
19 + * @param {string} [props.fontFamily] Font type for text
20 + * @param {number} [props.fontSize] Size
21 + * @param {string} [props.fontStyle] Type of inclination (normal / italic)
22 + * @param {string} [props.fontWeight] Type of thicker or thinner looking (normal / bold)
23 + * @param {string} [props.textAlign] Type of text align (left / center / right)
24 + * @param {string} [props.textDecoration] Type of line (underline / line-through / overline)
25 + * @returns {Promise}
26 + */
27 + execute(graphics, id, props) {
28 + const targetObj = graphics.getObject(id);
29 +
30 + if (!targetObj) {
31 + return Promise.reject(rejectMessages.noObject);
32 + }
33 +
34 + this.undoData.props = {};
35 + snippet.forEachOwnProperties(props, (value, key) => {
36 + this.undoData.props[key] = targetObj[key];
37 + });
38 +
39 + graphics.setObjectProperties(id, props);
40 +
41 + return Promise.resolve();
42 + },
43 + /**
44 + * @param {Graphics} graphics - Graphics instance
45 + * @param {number} id - object id
46 + * @returns {Promise}
47 + */
48 + undo(graphics, id) {
49 + const { props } = this.undoData;
50 +
51 + graphics.setObjectProperties(id, props);
52 +
53 + return Promise.resolve();
54 + },
55 +};
56 +
57 +commandFactory.register(command);
58 +
59 +export default command;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Image crop module (start cropping, end cropping)
4 + */
5 +import snippet from 'tui-code-snippet';
6 +import fabric from 'fabric';
7 +import Component from '../interface/component';
8 +import Cropzone from '../extension/cropzone';
9 +import { keyCodes, componentNames, CROPZONE_DEFAULT_OPTIONS } from '../consts';
10 +import { clamp, fixFloatingPoint } from '../util';
11 +
12 +const MOUSE_MOVE_THRESHOLD = 10;
13 +const DEFAULT_OPTION = {
14 + presetRatio: null,
15 + top: -10,
16 + left: -10,
17 + height: 1,
18 + width: 1,
19 +};
20 +
21 +/**
22 + * Cropper components
23 + * @param {Graphics} graphics - Graphics instance
24 + * @extends {Component}
25 + * @class Cropper
26 + * @ignore
27 + */
28 +class Cropper extends Component {
29 + constructor(graphics) {
30 + super(componentNames.CROPPER, graphics);
31 +
32 + /**
33 + * Cropzone
34 + * @type {Cropzone}
35 + * @private
36 + */
37 + this._cropzone = null;
38 +
39 + /**
40 + * StartX of Cropzone
41 + * @type {number}
42 + * @private
43 + */
44 + this._startX = null;
45 +
46 + /**
47 + * StartY of Cropzone
48 + * @type {number}
49 + * @private
50 + */
51 + this._startY = null;
52 +
53 + /**
54 + * State whether shortcut key is pressed or not
55 + * @type {boolean}
56 + * @private
57 + */
58 + this._withShiftKey = false;
59 +
60 + /**
61 + * Listeners
62 + * @type {object.<string, function>}
63 + * @private
64 + */
65 + this._listeners = {
66 + keydown: this._onKeyDown.bind(this),
67 + keyup: this._onKeyUp.bind(this),
68 + mousedown: this._onFabricMouseDown.bind(this),
69 + mousemove: this._onFabricMouseMove.bind(this),
70 + mouseup: this._onFabricMouseUp.bind(this),
71 + };
72 + }
73 +
74 + /**
75 + * Start cropping
76 + */
77 + start() {
78 + if (this._cropzone) {
79 + return;
80 + }
81 + const canvas = this.getCanvas();
82 +
83 + canvas.forEachObject((obj) => {
84 + // {@link http://fabricjs.com/docs/fabric.Object.html#evented}
85 + obj.evented = false;
86 + });
87 +
88 + this._cropzone = new Cropzone(
89 + canvas,
90 + snippet.extend(
91 + {
92 + left: 0,
93 + top: 0,
94 + width: 0.5,
95 + height: 0.5,
96 + strokeWidth: 0, // {@link https://github.com/kangax/fabric.js/issues/2860}
97 + cornerSize: 10,
98 + cornerColor: 'black',
99 + fill: 'transparent',
100 + },
101 + CROPZONE_DEFAULT_OPTIONS,
102 + this.graphics.cropSelectionStyle
103 + )
104 + );
105 +
106 + canvas.discardActiveObject();
107 + canvas.add(this._cropzone);
108 + canvas.on('mouse:down', this._listeners.mousedown);
109 + canvas.selection = false;
110 + canvas.defaultCursor = 'crosshair';
111 +
112 + fabric.util.addListener(document, 'keydown', this._listeners.keydown);
113 + fabric.util.addListener(document, 'keyup', this._listeners.keyup);
114 + }
115 +
116 + /**
117 + * End cropping
118 + */
119 + end() {
120 + const canvas = this.getCanvas();
121 + const cropzone = this._cropzone;
122 +
123 + if (!cropzone) {
124 + return;
125 + }
126 + canvas.remove(cropzone);
127 + canvas.selection = true;
128 + canvas.defaultCursor = 'default';
129 + canvas.off('mouse:down', this._listeners.mousedown);
130 + canvas.forEachObject((obj) => {
131 + obj.evented = true;
132 + });
133 +
134 + this._cropzone = null;
135 +
136 + fabric.util.removeListener(document, 'keydown', this._listeners.keydown);
137 + fabric.util.removeListener(document, 'keyup', this._listeners.keyup);
138 + }
139 +
140 + /**
141 + * Change cropzone visible
142 + * @param {boolean} visible - cropzone visible state
143 + */
144 + changeVisibility(visible) {
145 + if (this._cropzone) {
146 + this._cropzone.set({ visible });
147 + }
148 + }
149 +
150 + /**
151 + * onMousedown handler in fabric canvas
152 + * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event
153 + * @private
154 + */
155 + _onFabricMouseDown(fEvent) {
156 + const canvas = this.getCanvas();
157 +
158 + if (fEvent.target) {
159 + return;
160 + }
161 +
162 + canvas.selection = false;
163 + const coord = canvas.getPointer(fEvent.e);
164 +
165 + this._startX = coord.x;
166 + this._startY = coord.y;
167 +
168 + canvas.on({
169 + 'mouse:move': this._listeners.mousemove,
170 + 'mouse:up': this._listeners.mouseup,
171 + });
172 + }
173 +
174 + /**
175 + * onMousemove handler in fabric canvas
176 + * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event
177 + * @private
178 + */
179 + _onFabricMouseMove(fEvent) {
180 + const canvas = this.getCanvas();
181 + const pointer = canvas.getPointer(fEvent.e);
182 + const { x, y } = pointer;
183 + const cropzone = this._cropzone;
184 +
185 + if (Math.abs(x - this._startX) + Math.abs(y - this._startY) > MOUSE_MOVE_THRESHOLD) {
186 + canvas.remove(cropzone);
187 + cropzone.set(this._calcRectDimensionFromPoint(x, y));
188 +
189 + canvas.add(cropzone);
190 + canvas.setActiveObject(cropzone);
191 + }
192 + }
193 +
194 + /**
195 + * Get rect dimension setting from Canvas-Mouse-Position(x, y)
196 + * @param {number} x - Canvas-Mouse-Position x
197 + * @param {number} y - Canvas-Mouse-Position Y
198 + * @returns {{left: number, top: number, width: number, height: number}}
199 + * @private
200 + */
201 + _calcRectDimensionFromPoint(x, y) {
202 + const canvas = this.getCanvas();
203 + const canvasWidth = canvas.getWidth();
204 + const canvasHeight = canvas.getHeight();
205 + const startX = this._startX;
206 + const startY = this._startY;
207 + let left = clamp(x, 0, startX);
208 + let top = clamp(y, 0, startY);
209 + let width = clamp(x, startX, canvasWidth) - left; // (startX <= x(mouse) <= canvasWidth) - left
210 + let height = clamp(y, startY, canvasHeight) - top; // (startY <= y(mouse) <= canvasHeight) - top
211 +
212 + if (this._withShiftKey) {
213 + // make fixed ratio cropzone
214 + if (width > height) {
215 + height = width;
216 + } else if (height > width) {
217 + width = height;
218 + }
219 +
220 + if (startX >= x) {
221 + left = startX - width;
222 + }
223 +
224 + if (startY >= y) {
225 + top = startY - height;
226 + }
227 + }
228 +
229 + return {
230 + left,
231 + top,
232 + width,
233 + height,
234 + };
235 + }
236 +
237 + /**
238 + * onMouseup handler in fabric canvas
239 + * @private
240 + */
241 + _onFabricMouseUp() {
242 + const cropzone = this._cropzone;
243 + const listeners = this._listeners;
244 + const canvas = this.getCanvas();
245 +
246 + canvas.setActiveObject(cropzone);
247 + canvas.off({
248 + 'mouse:move': listeners.mousemove,
249 + 'mouse:up': listeners.mouseup,
250 + });
251 + }
252 +
253 + /**
254 + * Get cropped image data
255 + * @param {Object} cropRect cropzone rect
256 + * @param {Number} cropRect.left left position
257 + * @param {Number} cropRect.top top position
258 + * @param {Number} cropRect.width width
259 + * @param {Number} cropRect.height height
260 + * @returns {?{imageName: string, url: string}} cropped Image data
261 + */
262 + getCroppedImageData(cropRect) {
263 + const canvas = this.getCanvas();
264 + const containsCropzone = canvas.contains(this._cropzone);
265 + if (!cropRect) {
266 + return null;
267 + }
268 +
269 + if (containsCropzone) {
270 + canvas.remove(this._cropzone);
271 + }
272 +
273 + const imageData = {
274 + imageName: this.getImageName(),
275 + url: canvas.toDataURL(cropRect),
276 + };
277 +
278 + if (containsCropzone) {
279 + canvas.add(this._cropzone);
280 + }
281 +
282 + return imageData;
283 + }
284 +
285 + /**
286 + * Get cropped rect
287 + * @returns {Object} rect
288 + */
289 + getCropzoneRect() {
290 + const cropzone = this._cropzone;
291 +
292 + if (!cropzone.isValid()) {
293 + return null;
294 + }
295 +
296 + return {
297 + left: cropzone.left,
298 + top: cropzone.top,
299 + width: cropzone.width,
300 + height: cropzone.height,
301 + };
302 + }
303 +
304 + /**
305 + * Set a cropzone square
306 + * @param {number} [presetRatio] - preset ratio
307 + */
308 + setCropzoneRect(presetRatio) {
309 + const canvas = this.getCanvas();
310 + const cropzone = this._cropzone;
311 +
312 + canvas.discardActiveObject();
313 + canvas.selection = false;
314 + canvas.remove(cropzone);
315 +
316 + cropzone.set(presetRatio ? this._getPresetPropertiesForCropSize(presetRatio) : DEFAULT_OPTION);
317 +
318 + canvas.add(cropzone);
319 + canvas.selection = true;
320 +
321 + if (presetRatio) {
322 + canvas.setActiveObject(cropzone);
323 + }
324 + }
325 +
326 + /**
327 + * get a cropzone square info
328 + * @param {number} presetRatio - preset ratio
329 + * @returns {{presetRatio: number, left: number, top: number, width: number, height: number}}
330 + * @private
331 + */
332 + _getPresetPropertiesForCropSize(presetRatio) {
333 + const canvas = this.getCanvas();
334 + const originalWidth = canvas.getWidth();
335 + const originalHeight = canvas.getHeight();
336 +
337 + const standardSize = originalWidth >= originalHeight ? originalWidth : originalHeight;
338 + const getScale = (value, orignalValue) => (value > orignalValue ? orignalValue / value : 1);
339 +
340 + let width = standardSize * presetRatio;
341 + let height = standardSize;
342 +
343 + const scaleWidth = getScale(width, originalWidth);
344 + [width, height] = snippet.map([width, height], (sizeValue) => sizeValue * scaleWidth);
345 +
346 + const scaleHeight = getScale(height, originalHeight);
347 + [width, height] = snippet.map([width, height], (sizeValue) =>
348 + fixFloatingPoint(sizeValue * scaleHeight)
349 + );
350 +
351 + return {
352 + presetRatio,
353 + top: (originalHeight - height) / 2,
354 + left: (originalWidth - width) / 2,
355 + width,
356 + height,
357 + };
358 + }
359 +
360 + /**
361 + * Keydown event handler
362 + * @param {KeyboardEvent} e - Event object
363 + * @private
364 + */
365 + _onKeyDown(e) {
366 + if (e.keyCode === keyCodes.SHIFT) {
367 + this._withShiftKey = true;
368 + }
369 + }
370 +
371 + /**
372 + * Keyup event handler
373 + * @param {KeyboardEvent} e - Event object
374 + * @private
375 + */
376 + _onKeyUp(e) {
377 + if (e.keyCode === keyCodes.SHIFT) {
378 + this._withShiftKey = false;
379 + }
380 + }
381 +}
382 +
383 +export default Cropper;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Add filter module
4 + */
5 +import { isUndefined, extend, forEach, filter } from 'tui-code-snippet';
6 +import { Promise } from '../util';
7 +import fabric from 'fabric';
8 +import Component from '../interface/component';
9 +import Mask from '../extension/mask';
10 +import { rejectMessages, componentNames } from '../consts';
11 +import Sharpen from '../extension/sharpen';
12 +import Emboss from '../extension/emboss';
13 +import ColorFilter from '../extension/colorFilter';
14 +
15 +const { filters } = fabric.Image;
16 +filters.Mask = Mask;
17 +filters.Sharpen = Sharpen;
18 +filters.Emboss = Emboss;
19 +filters.ColorFilter = ColorFilter;
20 +
21 +/**
22 + * Filter
23 + * @class Filter
24 + * @param {Graphics} graphics - Graphics instance
25 + * @extends {Component}
26 + * @ignore
27 + */
28 +class Filter extends Component {
29 + constructor(graphics) {
30 + super(componentNames.FILTER, graphics);
31 + }
32 +
33 + /**
34 + * Add filter to source image (a specific filter is added on fabric.js)
35 + * @param {string} type - Filter type
36 + * @param {Object} [options] - Options of filter
37 + * @returns {Promise}
38 + */
39 + add(type, options) {
40 + return new Promise((resolve, reject) => {
41 + const sourceImg = this._getSourceImage();
42 + const canvas = this.getCanvas();
43 + let imgFilter = this._getFilter(sourceImg, type);
44 + if (!imgFilter) {
45 + imgFilter = this._createFilter(sourceImg, type, options);
46 + }
47 +
48 + if (!imgFilter) {
49 + reject(rejectMessages.invalidParameters);
50 + }
51 +
52 + this._changeFilterValues(imgFilter, options);
53 +
54 + this._apply(sourceImg, () => {
55 + canvas.renderAll();
56 + resolve({
57 + type,
58 + action: 'add',
59 + options,
60 + });
61 + });
62 + });
63 + }
64 +
65 + /**
66 + * Remove filter to source image
67 + * @param {string} type - Filter type
68 + * @returns {Promise}
69 + */
70 + remove(type) {
71 + return new Promise((resolve, reject) => {
72 + const sourceImg = this._getSourceImage();
73 + const canvas = this.getCanvas();
74 + const options = this.getOptions(type);
75 +
76 + if (!sourceImg.filters.length) {
77 + reject(rejectMessages.unsupportedOperation);
78 + }
79 +
80 + this._removeFilter(sourceImg, type);
81 +
82 + this._apply(sourceImg, () => {
83 + canvas.renderAll();
84 + resolve({
85 + type,
86 + action: 'remove',
87 + options,
88 + });
89 + });
90 + });
91 + }
92 +
93 + /**
94 + * Whether this has the filter or not
95 + * @param {string} type - Filter type
96 + * @returns {boolean} true if it has the filter
97 + */
98 + hasFilter(type) {
99 + return !!this._getFilter(this._getSourceImage(), type);
100 + }
101 +
102 + /**
103 + * Get a filter options
104 + * @param {string} type - Filter type
105 + * @returns {Object} filter options or null if there is no that filter
106 + */
107 + getOptions(type) {
108 + const sourceImg = this._getSourceImage();
109 + const imgFilter = this._getFilter(sourceImg, type);
110 + if (!imgFilter) {
111 + return null;
112 + }
113 +
114 + return extend({}, imgFilter.options);
115 + }
116 +
117 + /**
118 + * Change filter values
119 + * @param {Object} imgFilter object of filter
120 + * @param {Object} options object
121 + * @private
122 + */
123 + _changeFilterValues(imgFilter, options) {
124 + forEach(options, (value, key) => {
125 + if (!isUndefined(imgFilter[key])) {
126 + imgFilter[key] = value;
127 + }
128 + });
129 + forEach(imgFilter.options, (value, key) => {
130 + if (!isUndefined(options[key])) {
131 + imgFilter.options[key] = options[key];
132 + }
133 + });
134 + }
135 +
136 + /**
137 + * Apply filter
138 + * @param {fabric.Image} sourceImg - Source image to apply filter
139 + * @param {function} callback - Executed function after applying filter
140 + * @private
141 + */
142 + _apply(sourceImg, callback) {
143 + sourceImg.filters.push();
144 + const result = sourceImg.applyFilters();
145 + if (result) {
146 + callback();
147 + }
148 + }
149 +
150 + /**
151 + * Get source image on canvas
152 + * @returns {fabric.Image} Current source image on canvas
153 + * @private
154 + */
155 + _getSourceImage() {
156 + return this.getCanvasImage();
157 + }
158 +
159 + /**
160 + * Create filter instance
161 + * @param {fabric.Image} sourceImg - Source image to apply filter
162 + * @param {string} type - Filter type
163 + * @param {Object} [options] - Options of filter
164 + * @returns {Object} Fabric object of filter
165 + * @private
166 + */
167 + _createFilter(sourceImg, type, options) {
168 + let filterObj;
169 + // capitalize first letter for matching with fabric image filter name
170 + const fabricType = this._getFabricFilterType(type);
171 + const ImageFilter = fabric.Image.filters[fabricType];
172 + if (ImageFilter) {
173 + filterObj = new ImageFilter(options);
174 + filterObj.options = options;
175 + sourceImg.filters.push(filterObj);
176 + }
177 +
178 + return filterObj;
179 + }
180 +
181 + /**
182 + * Get applied filter instance
183 + * @param {fabric.Image} sourceImg - Source image to apply filter
184 + * @param {string} type - Filter type
185 + * @returns {Object} Fabric object of filter
186 + * @private
187 + */
188 + _getFilter(sourceImg, type) {
189 + let imgFilter = null;
190 +
191 + if (sourceImg) {
192 + const fabricType = this._getFabricFilterType(type);
193 + const { length } = sourceImg.filters;
194 + let item, i;
195 +
196 + for (i = 0; i < length; i += 1) {
197 + item = sourceImg.filters[i];
198 + if (item.type === fabricType) {
199 + imgFilter = item;
200 + break;
201 + }
202 + }
203 + }
204 +
205 + return imgFilter;
206 + }
207 +
208 + /**
209 + * Remove applied filter instance
210 + * @param {fabric.Image} sourceImg - Source image to apply filter
211 + * @param {string} type - Filter type
212 + * @private
213 + */
214 + _removeFilter(sourceImg, type) {
215 + const fabricType = this._getFabricFilterType(type);
216 + sourceImg.filters = filter(sourceImg.filters, (value) => value.type !== fabricType);
217 + }
218 +
219 + /**
220 + * Change filter class name to fabric's, especially capitalizing first letter
221 + * @param {string} type - Filter type
222 + * @example
223 + * 'grayscale' -> 'Grayscale'
224 + * @returns {string} Fabric filter class name
225 + */
226 + _getFabricFilterType(type) {
227 + return type.charAt(0).toUpperCase() + type.slice(1);
228 + }
229 +}
230 +
231 +export default Filter;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Image flip module
4 + */
5 +import snippet from 'tui-code-snippet';
6 +import { Promise } from '../util';
7 +import Component from '../interface/component';
8 +import { componentNames, rejectMessages } from '../consts';
9 +
10 +/**
11 + * Flip
12 + * @class Flip
13 + * @param {Graphics} graphics - Graphics instance
14 + * @extends {Component}
15 + * @ignore
16 + */
17 +class Flip extends Component {
18 + constructor(graphics) {
19 + super(componentNames.FLIP, graphics);
20 + }
21 +
22 + /**
23 + * Get current flip settings
24 + * @returns {{flipX: Boolean, flipY: Boolean}}
25 + */
26 + getCurrentSetting() {
27 + const canvasImage = this.getCanvasImage();
28 +
29 + return {
30 + flipX: canvasImage.flipX,
31 + flipY: canvasImage.flipY,
32 + };
33 + }
34 +
35 + /**
36 + * Set flipX, flipY
37 + * @param {{flipX: Boolean, flipY: Boolean}} newSetting - Flip setting
38 + * @returns {Promise}
39 + */
40 + set(newSetting) {
41 + const setting = this.getCurrentSetting();
42 + const isChangingFlipX = setting.flipX !== newSetting.flipX;
43 + const isChangingFlipY = setting.flipY !== newSetting.flipY;
44 +
45 + if (!isChangingFlipX && !isChangingFlipY) {
46 + return Promise.reject(rejectMessages.flip);
47 + }
48 +
49 + snippet.extend(setting, newSetting);
50 + this.setImageProperties(setting, true);
51 + this._invertAngle(isChangingFlipX, isChangingFlipY);
52 + this._flipObjects(isChangingFlipX, isChangingFlipY);
53 +
54 + return Promise.resolve({
55 + flipX: setting.flipX,
56 + flipY: setting.flipY,
57 + angle: this.getCanvasImage().angle,
58 + });
59 + }
60 +
61 + /**
62 + * Invert image angle for flip
63 + * @param {boolean} isChangingFlipX - Change flipX
64 + * @param {boolean} isChangingFlipY - Change flipY
65 + */
66 + _invertAngle(isChangingFlipX, isChangingFlipY) {
67 + const canvasImage = this.getCanvasImage();
68 + let { angle } = canvasImage;
69 +
70 + if (isChangingFlipX) {
71 + angle *= -1;
72 + }
73 + if (isChangingFlipY) {
74 + angle *= -1;
75 + }
76 + canvasImage.rotate(parseFloat(angle)).setCoords(); // parseFloat for -0 to 0
77 + }
78 +
79 + /**
80 + * Flip objects
81 + * @param {boolean} isChangingFlipX - Change flipX
82 + * @param {boolean} isChangingFlipY - Change flipY
83 + * @private
84 + */
85 + _flipObjects(isChangingFlipX, isChangingFlipY) {
86 + const canvas = this.getCanvas();
87 +
88 + if (isChangingFlipX) {
89 + canvas.forEachObject((obj) => {
90 + obj
91 + .set({
92 + angle: parseFloat(obj.angle * -1), // parseFloat for -0 to 0
93 + flipX: !obj.flipX,
94 + left: canvas.width - obj.left,
95 + })
96 + .setCoords();
97 + });
98 + }
99 + if (isChangingFlipY) {
100 + canvas.forEachObject((obj) => {
101 + obj
102 + .set({
103 + angle: parseFloat(obj.angle * -1), // parseFloat for -0 to 0
104 + flipY: !obj.flipY,
105 + top: canvas.height - obj.top,
106 + })
107 + .setCoords();
108 + });
109 + }
110 + canvas.renderAll();
111 + }
112 +
113 + /**
114 + * Reset flip settings
115 + * @returns {Promise}
116 + */
117 + reset() {
118 + return this.set({
119 + flipX: false,
120 + flipY: false,
121 + });
122 + }
123 +
124 + /**
125 + * Flip x
126 + * @returns {Promise}
127 + */
128 + flipX() {
129 + const current = this.getCurrentSetting();
130 +
131 + return this.set({
132 + flipX: !current.flipX,
133 + flipY: current.flipY,
134 + });
135 + }
136 +
137 + /**
138 + * Flip y
139 + * @returns {Promise}
140 + */
141 + flipY() {
142 + const current = this.getCurrentSetting();
143 +
144 + return this.set({
145 + flipX: current.flipX,
146 + flipY: !current.flipY,
147 + });
148 + }
149 +}
150 +
151 +export default Flip;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Free drawing module, Set brush
4 + */
5 +import fabric from 'fabric';
6 +import Component from '../interface/component';
7 +import { componentNames } from '../consts';
8 +
9 +/**
10 + * FreeDrawing
11 + * @class FreeDrawing
12 + * @param {Graphics} graphics - Graphics instance
13 + * @extends {Component}
14 + * @ignore
15 + */
16 +class FreeDrawing extends Component {
17 + constructor(graphics) {
18 + super(componentNames.FREE_DRAWING, graphics);
19 +
20 + /**
21 + * Brush width
22 + * @type {number}
23 + */
24 + this.width = 12;
25 +
26 + /**
27 + * fabric.Color instance for brush color
28 + * @type {fabric.Color}
29 + */
30 + this.oColor = new fabric.Color('rgba(0, 0, 0, 0.5)');
31 + }
32 +
33 + /**
34 + * Start free drawing mode
35 + * @param {{width: ?number, color: ?string}} [setting] - Brush width & color
36 + */
37 + start(setting) {
38 + const canvas = this.getCanvas();
39 +
40 + canvas.isDrawingMode = true;
41 + this.setBrush(setting);
42 + }
43 +
44 + /**
45 + * Set brush
46 + * @param {{width: ?number, color: ?string}} [setting] - Brush width & color
47 + */
48 + setBrush(setting) {
49 + const brush = this.getCanvas().freeDrawingBrush;
50 +
51 + setting = setting || {};
52 + this.width = setting.width || this.width;
53 + if (setting.color) {
54 + this.oColor = new fabric.Color(setting.color);
55 + }
56 + brush.width = this.width;
57 + brush.color = this.oColor.toRgba();
58 + }
59 +
60 + /**
61 + * End free drawing mode
62 + */
63 + end() {
64 + const canvas = this.getCanvas();
65 +
66 + canvas.isDrawingMode = false;
67 + }
68 +}
69 +
70 +export default FreeDrawing;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Add icon module
4 + */
5 +import fabric from 'fabric';
6 +import snippet from 'tui-code-snippet';
7 +import { Promise } from '../util';
8 +import Component from '../interface/component';
9 +import { eventNames as events, rejectMessages, componentNames, fObjectOptions } from '../consts';
10 +
11 +const pathMap = {
12 + arrow: 'M 0 90 H 105 V 120 L 160 60 L 105 0 V 30 H 0 Z',
13 + cancel:
14 + 'M 0 30 L 30 60 L 0 90 L 30 120 L 60 90 L 90 120 L 120 90 ' +
15 + 'L 90 60 L 120 30 L 90 0 L 60 30 L 30 0 Z',
16 +};
17 +
18 +/**
19 + * Icon
20 + * @class Icon
21 + * @param {Graphics} graphics - Graphics instance
22 + * @extends {Component}
23 + * @ignore
24 + */
25 +class Icon extends Component {
26 + constructor(graphics) {
27 + super(componentNames.ICON, graphics);
28 +
29 + /**
30 + * Default icon color
31 + * @type {string}
32 + */
33 + this._oColor = '#000000';
34 +
35 + /**
36 + * Path value of each icon type
37 + * @type {Object}
38 + */
39 + this._pathMap = pathMap;
40 +
41 + /**
42 + * Type of the drawing icon
43 + * @type {string}
44 + * @private
45 + */
46 + this._type = null;
47 +
48 + /**
49 + * Color of the drawing icon
50 + * @type {string}
51 + * @private
52 + */
53 + this._iconColor = null;
54 +
55 + /**
56 + * Event handler list
57 + * @type {Object}
58 + * @private
59 + */
60 + this._handlers = {
61 + mousedown: this._onFabricMouseDown.bind(this),
62 + mousemove: this._onFabricMouseMove.bind(this),
63 + mouseup: this._onFabricMouseUp.bind(this),
64 + };
65 + }
66 +
67 + /**
68 + * Set states of the current drawing shape
69 + * @ignore
70 + * @param {string} type - Icon type ('arrow', 'cancel', custom icon name)
71 + * @param {string} iconColor - Icon foreground color
72 + */
73 + setStates(type, iconColor) {
74 + this._type = type;
75 + this._iconColor = iconColor;
76 + }
77 +
78 + /**
79 + * Start to draw the icon on canvas
80 + * @ignore
81 + */
82 + start() {
83 + const canvas = this.getCanvas();
84 + canvas.selection = false;
85 + canvas.on('mouse:down', this._handlers.mousedown);
86 + }
87 +
88 + /**
89 + * End to draw the icon on canvas
90 + * @ignore
91 + */
92 + end() {
93 + const canvas = this.getCanvas();
94 +
95 + canvas.selection = true;
96 + canvas.off({
97 + 'mouse:down': this._handlers.mousedown,
98 + });
99 + }
100 +
101 + /**
102 + * Add icon
103 + * @param {string} type - Icon type
104 + * @param {Object} options - Icon options
105 + * @param {string} [options.fill] - Icon foreground color
106 + * @param {string} [options.left] - Icon x position
107 + * @param {string} [options.top] - Icon y position
108 + * @returns {Promise}
109 + */
110 + add(type, options) {
111 + return new Promise((resolve, reject) => {
112 + const canvas = this.getCanvas();
113 + const path = this._pathMap[type];
114 + const selectionStyle = fObjectOptions.SELECTION_STYLE;
115 + const icon = path ? this._createIcon(path) : null;
116 + this._icon = icon;
117 +
118 + if (!icon) {
119 + reject(rejectMessages.invalidParameters);
120 + }
121 +
122 + icon.set(
123 + snippet.extend(
124 + {
125 + type: 'icon',
126 + fill: this._oColor,
127 + },
128 + selectionStyle,
129 + options,
130 + this.graphics.controlStyle
131 + )
132 + );
133 +
134 + canvas.add(icon).setActiveObject(icon);
135 +
136 + resolve(this.graphics.createObjectProperties(icon));
137 + });
138 + }
139 +
140 + /**
141 + * Register icon paths
142 + * @param {{key: string, value: string}} pathInfos - Path infos
143 + */
144 + registerPaths(pathInfos) {
145 + snippet.forEach(
146 + pathInfos,
147 + (path, type) => {
148 + this._pathMap[type] = path;
149 + },
150 + this
151 + );
152 + }
153 +
154 + /**
155 + * Set icon object color
156 + * @param {string} color - Color to set
157 + * @param {fabric.Path}[obj] - Current activated path object
158 + */
159 + setColor(color, obj) {
160 + this._oColor = color;
161 +
162 + if (obj && obj.get('type') === 'icon') {
163 + obj.set({ fill: this._oColor });
164 + this.getCanvas().renderAll();
165 + }
166 + }
167 +
168 + /**
169 + * Get icon color
170 + * @param {fabric.Path}[obj] - Current activated path object
171 + * @returns {string} color
172 + */
173 + getColor(obj) {
174 + return obj.fill;
175 + }
176 +
177 + /**
178 + * Create icon object
179 + * @param {string} path - Path value to create icon
180 + * @returns {fabric.Path} Path object
181 + */
182 + _createIcon(path) {
183 + return new fabric.Path(path);
184 + }
185 +
186 + /**
187 + * MouseDown event handler on canvas
188 + * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event object
189 + * @private
190 + */
191 + _onFabricMouseDown(fEvent) {
192 + const canvas = this.getCanvas();
193 +
194 + this._startPoint = canvas.getPointer(fEvent.e);
195 + const { x: left, y: top } = this._startPoint;
196 +
197 + this.add(this._type, {
198 + left,
199 + top,
200 + fill: this._iconColor,
201 + }).then(() => {
202 + this.fire(events.ADD_OBJECT, this.graphics.createObjectProperties(this._icon));
203 + canvas.on('mouse:move', this._handlers.mousemove);
204 + canvas.on('mouse:up', this._handlers.mouseup);
205 + });
206 + }
207 +
208 + /**
209 + * MouseMove event handler on canvas
210 + * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event object
211 + * @private
212 + */
213 + _onFabricMouseMove(fEvent) {
214 + const canvas = this.getCanvas();
215 +
216 + if (!this._icon) {
217 + return;
218 + }
219 + const moveOriginPointer = canvas.getPointer(fEvent.e);
220 +
221 + const scaleX = (moveOriginPointer.x - this._startPoint.x) / this._icon.width;
222 + const scaleY = (moveOriginPointer.y - this._startPoint.y) / this._icon.height;
223 +
224 + this._icon.set({
225 + scaleX: Math.abs(scaleX * 2),
226 + scaleY: Math.abs(scaleY * 2),
227 + });
228 +
229 + this._icon.setCoords();
230 + canvas.renderAll();
231 + }
232 +
233 + /**
234 + * MouseUp event handler on canvas
235 + * @private
236 + */
237 + _onFabricMouseUp() {
238 + const canvas = this.getCanvas();
239 +
240 + this.fire(events.OBJECT_ADDED, this.graphics.createObjectProperties(this._icon));
241 +
242 + this._icon = null;
243 +
244 + canvas.off('mouse:down', this._handlers.mousedown);
245 + canvas.off('mouse:move', this._handlers.mousemove);
246 + canvas.off('mouse:up', this._handlers.mouseup);
247 + }
248 +}
249 +
250 +export default Icon;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Image loader
4 + */
5 +import Component from '../interface/component';
6 +import { componentNames, rejectMessages } from '../consts';
7 +import { Promise } from '../util';
8 +
9 +const imageOption = {
10 + padding: 0,
11 + crossOrigin: 'Anonymous',
12 +};
13 +
14 +/**
15 + * ImageLoader components
16 + * @extends {Component}
17 + * @class ImageLoader
18 + * @param {Graphics} graphics - Graphics instance
19 + * @ignore
20 + */
21 +class ImageLoader extends Component {
22 + constructor(graphics) {
23 + super(componentNames.IMAGE_LOADER, graphics);
24 + }
25 +
26 + /**
27 + * Load image from url
28 + * @param {?string} imageName - File name
29 + * @param {?(fabric.Image|string)} img - fabric.Image instance or URL of an image
30 + * @returns {Promise}
31 + */
32 + load(imageName, img) {
33 + let promise;
34 +
35 + if (!imageName && !img) {
36 + // Back to the initial state, not error.
37 + const canvas = this.getCanvas();
38 +
39 + canvas.backgroundImage = null;
40 + canvas.renderAll();
41 +
42 + promise = new Promise((resolve) => {
43 + this.setCanvasImage('', null);
44 + resolve();
45 + });
46 + } else {
47 + promise = this._setBackgroundImage(img).then((oImage) => {
48 + this.setCanvasImage(imageName, oImage);
49 + this.adjustCanvasDimension();
50 +
51 + return oImage;
52 + });
53 + }
54 +
55 + return promise;
56 + }
57 +
58 + /**
59 + * Set background image
60 + * @param {?(fabric.Image|String)} img fabric.Image instance or URL of an image to set background to
61 + * @returns {Promise}
62 + * @private
63 + */
64 + _setBackgroundImage(img) {
65 + if (!img) {
66 + return Promise.reject(rejectMessages.loadImage);
67 + }
68 +
69 + return new Promise((resolve, reject) => {
70 + const canvas = this.getCanvas();
71 +
72 + canvas.setBackgroundImage(
73 + img,
74 + () => {
75 + const oImage = canvas.backgroundImage;
76 +
77 + if (oImage && oImage.getElement()) {
78 + resolve(oImage);
79 + } else {
80 + reject(rejectMessages.loadingImageFailed);
81 + }
82 + },
83 + imageOption
84 + );
85 + });
86 + }
87 +}
88 +
89 +export default ImageLoader;
1 +/**
2 + * @author NHN. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Free drawing module, Set brush
4 + */
5 +import fabric from 'fabric';
6 +import snippet from 'tui-code-snippet';
7 +import Component from '../interface/component';
8 +import ArrowLine from '../extension/arrowLine';
9 +import { eventNames, componentNames, fObjectOptions } from '../consts';
10 +
11 +/**
12 + * Line
13 + * @class Line
14 + * @param {Graphics} graphics - Graphics instance
15 + * @extends {Component}
16 + * @ignore
17 + */
18 +class Line extends Component {
19 + constructor(graphics) {
20 + super(componentNames.LINE, graphics);
21 +
22 + /**
23 + * Brush width
24 + * @type {number}
25 + * @private
26 + */
27 + this._width = 12;
28 +
29 + /**
30 + * fabric.Color instance for brush color
31 + * @type {fabric.Color}
32 + * @private
33 + */
34 + this._oColor = new fabric.Color('rgba(0, 0, 0, 0.5)');
35 +
36 + /**
37 + * Listeners
38 + * @type {object.<string, function>}
39 + * @private
40 + */
41 + this._listeners = {
42 + mousedown: this._onFabricMouseDown.bind(this),
43 + mousemove: this._onFabricMouseMove.bind(this),
44 + mouseup: this._onFabricMouseUp.bind(this),
45 + };
46 + }
47 +
48 + /**
49 + * Start drawing line mode
50 + * @param {{width: ?number, color: ?string}} [setting] - Brush width & color
51 + */
52 + setHeadOption(setting) {
53 + const {
54 + arrowType = {
55 + head: null,
56 + tail: null,
57 + },
58 + } = setting;
59 +
60 + this._arrowType = arrowType;
61 + }
62 +
63 + /**
64 + * Start drawing line mode
65 + * @param {{width: ?number, color: ?string}} [setting] - Brush width & color
66 + */
67 + start(setting = {}) {
68 + const canvas = this.getCanvas();
69 +
70 + canvas.defaultCursor = 'crosshair';
71 + canvas.selection = false;
72 +
73 + this.setHeadOption(setting);
74 + this.setBrush(setting);
75 +
76 + canvas.forEachObject((obj) => {
77 + obj.set({
78 + evented: false,
79 + });
80 + });
81 +
82 + canvas.on({
83 + 'mouse:down': this._listeners.mousedown,
84 + });
85 + }
86 +
87 + /**
88 + * Set brush
89 + * @param {{width: ?number, color: ?string}} [setting] - Brush width & color
90 + */
91 + setBrush(setting) {
92 + const brush = this.getCanvas().freeDrawingBrush;
93 +
94 + setting = setting || {};
95 + this._width = setting.width || this._width;
96 +
97 + if (setting.color) {
98 + this._oColor = new fabric.Color(setting.color);
99 + }
100 + brush.width = this._width;
101 + brush.color = this._oColor.toRgba();
102 + }
103 +
104 + /**
105 + * End drawing line mode
106 + */
107 + end() {
108 + const canvas = this.getCanvas();
109 +
110 + canvas.defaultCursor = 'default';
111 + canvas.selection = true;
112 +
113 + canvas.forEachObject((obj) => {
114 + obj.set({
115 + evented: true,
116 + });
117 + });
118 +
119 + canvas.off('mouse:down', this._listeners.mousedown);
120 + }
121 +
122 + /**
123 + * Mousedown event handler in fabric canvas
124 + * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event object
125 + * @private
126 + */
127 + _onFabricMouseDown(fEvent) {
128 + const canvas = this.getCanvas();
129 + const { x, y } = canvas.getPointer(fEvent.e);
130 + const points = [x, y, x, y];
131 +
132 + this._line = new ArrowLine(points, {
133 + stroke: this._oColor.toRgba(),
134 + strokeWidth: this._width,
135 + arrowType: this._arrowType,
136 + evented: false,
137 + });
138 +
139 + this._line.set(fObjectOptions.SELECTION_STYLE);
140 +
141 + canvas.add(this._line);
142 +
143 + canvas.on({
144 + 'mouse:move': this._listeners.mousemove,
145 + 'mouse:up': this._listeners.mouseup,
146 + });
147 +
148 + this.fire(eventNames.ADD_OBJECT, this._createLineEventObjectProperties());
149 + }
150 +
151 + /**
152 + * Mousemove event handler in fabric canvas
153 + * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event object
154 + * @private
155 + */
156 + _onFabricMouseMove(fEvent) {
157 + const canvas = this.getCanvas();
158 + const pointer = canvas.getPointer(fEvent.e);
159 +
160 + this._line.set({
161 + x2: pointer.x,
162 + y2: pointer.y,
163 + });
164 +
165 + this._line.setCoords();
166 +
167 + canvas.renderAll();
168 + }
169 +
170 + /**
171 + * Mouseup event handler in fabric canvas
172 + * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event object
173 + * @private
174 + */
175 + _onFabricMouseUp() {
176 + const canvas = this.getCanvas();
177 +
178 + this.fire(eventNames.OBJECT_ADDED, this._createLineEventObjectProperties());
179 +
180 + this._line = null;
181 +
182 + canvas.off({
183 + 'mouse:move': this._listeners.mousemove,
184 + 'mouse:up': this._listeners.mouseup,
185 + });
186 + }
187 +
188 + /**
189 + * create line event object properties
190 + * @returns {Object} properties line object
191 + * @private
192 + */
193 + _createLineEventObjectProperties() {
194 + const params = this.graphics.createObjectProperties(this._line);
195 + const { x1, x2, y1, y2 } = this._line;
196 +
197 + return snippet.extend({}, params, {
198 + startPosition: {
199 + x: x1,
200 + y: y1,
201 + },
202 + endPosition: {
203 + x: x2,
204 + y: y2,
205 + },
206 + });
207 + }
208 +}
209 +
210 +export default Line;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Image rotation module
4 + */
5 +import fabric from 'fabric';
6 +import { Promise } from '../util';
7 +import Component from '../interface/component';
8 +import { componentNames } from '../consts';
9 +
10 +/**
11 + * Image Rotation component
12 + * @class Rotation
13 + * @extends {Component}
14 + * @param {Graphics} graphics - Graphics instance
15 + * @ignore
16 + */
17 +class Rotation extends Component {
18 + constructor(graphics) {
19 + super(componentNames.ROTATION, graphics);
20 + }
21 +
22 + /**
23 + * Get current angle
24 + * @returns {Number}
25 + */
26 + getCurrentAngle() {
27 + return this.getCanvasImage().angle;
28 + }
29 +
30 + /**
31 + * Set angle of the image
32 + *
33 + * Do not call "this.setImageProperties" for setting angle directly.
34 + * Before setting angle, The originX,Y of image should be set to center.
35 + * See "http://fabricjs.com/docs/fabric.Object.html#setAngle"
36 + *
37 + * @param {number} angle - Angle value
38 + * @returns {Promise}
39 + */
40 + setAngle(angle) {
41 + const oldAngle = this.getCurrentAngle() % 360; // The angle is lower than 2*PI(===360 degrees)
42 +
43 + angle %= 360;
44 +
45 + const canvasImage = this.getCanvasImage();
46 + const oldImageCenter = canvasImage.getCenterPoint();
47 + canvasImage.set({ angle }).setCoords();
48 + this.adjustCanvasDimension();
49 + const newImageCenter = canvasImage.getCenterPoint();
50 + this._rotateForEachObject(oldImageCenter, newImageCenter, angle - oldAngle);
51 +
52 + return Promise.resolve(angle);
53 + }
54 +
55 + /**
56 + * Rotate for each object
57 + * @param {fabric.Point} oldImageCenter - Image center point before rotation
58 + * @param {fabric.Point} newImageCenter - Image center point after rotation
59 + * @param {number} angleDiff - Image angle difference after rotation
60 + * @private
61 + */
62 + _rotateForEachObject(oldImageCenter, newImageCenter, angleDiff) {
63 + const canvas = this.getCanvas();
64 + const centerDiff = {
65 + x: oldImageCenter.x - newImageCenter.x,
66 + y: oldImageCenter.y - newImageCenter.y,
67 + };
68 +
69 + canvas.forEachObject((obj) => {
70 + const objCenter = obj.getCenterPoint();
71 + const radian = fabric.util.degreesToRadians(angleDiff);
72 + const newObjCenter = fabric.util.rotatePoint(objCenter, oldImageCenter, radian);
73 +
74 + obj.set({
75 + left: newObjCenter.x - centerDiff.x,
76 + top: newObjCenter.y - centerDiff.y,
77 + angle: (obj.angle + angleDiff) % 360,
78 + });
79 + obj.setCoords();
80 + });
81 + canvas.renderAll();
82 + }
83 +
84 + /**
85 + * Rotate the image
86 + * @param {number} additionalAngle - Additional angle
87 + * @returns {Promise}
88 + */
89 + rotate(additionalAngle) {
90 + const current = this.getCurrentAngle();
91 +
92 + return this.setAngle(current + additionalAngle);
93 + }
94 +}
95 +
96 +export default Rotation;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Shape component
4 + */
5 +import fabric from 'fabric';
6 +import Component from '../interface/component';
7 +import {
8 + rejectMessages,
9 + eventNames,
10 + keyCodes as KEY_CODES,
11 + componentNames,
12 + fObjectOptions,
13 + SHAPE_DEFAULT_OPTIONS,
14 + SHAPE_FILL_TYPE,
15 +} from '../consts';
16 +import resizeHelper from '../helper/shapeResizeHelper';
17 +import {
18 + getFillImageFromShape,
19 + rePositionFilterTypeFillImage,
20 + reMakePatternImageSource,
21 + makeFillPatternForFilter,
22 + makeFilterOptionFromFabricImage,
23 + resetFillPatternCanvas,
24 +} from '../helper/shapeFilterFillHelper';
25 +import {
26 + Promise,
27 + changeOrigin,
28 + getCustomProperty,
29 + getFillTypeFromOption,
30 + getFillTypeFromObject,
31 + isShape,
32 +} from '../util';
33 +import { extend } from 'tui-code-snippet';
34 +
35 +const SHAPE_INIT_OPTIONS = extend(
36 + {
37 + strokeWidth: 1,
38 + stroke: '#000000',
39 + fill: '#ffffff',
40 + width: 1,
41 + height: 1,
42 + rx: 0,
43 + ry: 0,
44 + },
45 + SHAPE_DEFAULT_OPTIONS
46 +);
47 +
48 +const DEFAULT_TYPE = 'rect';
49 +const DEFAULT_WIDTH = 20;
50 +const DEFAULT_HEIGHT = 20;
51 +
52 +/**
53 + * Make fill option
54 + * @param {Object} options - Options to create the shape
55 + * @param {Object.Image} canvasImage - canvas background image
56 + * @param {Function} createStaticCanvas - static canvas creater
57 + * @returns {Object} - shape option
58 + * @private
59 + */
60 +function makeFabricFillOption(options, canvasImage, createStaticCanvas) {
61 + const fillOption = options.fill;
62 + const fillType = getFillTypeFromOption(options.fill);
63 + let fill = fillOption;
64 +
65 + if (fillOption.color) {
66 + fill = fillOption.color;
67 + }
68 +
69 + let extOption = null;
70 + if (fillType === 'filter') {
71 + const newStaticCanvas = createStaticCanvas();
72 + extOption = makeFillPatternForFilter(canvasImage, fillOption.filter, newStaticCanvas);
73 + } else {
74 + extOption = { fill };
75 + }
76 +
77 + return extend({}, options, extOption);
78 +}
79 +
80 +/**
81 + * Shape
82 + * @class Shape
83 + * @param {Graphics} graphics - Graphics instance
84 + * @extends {Component}
85 + * @ignore
86 + */
87 +export default class Shape extends Component {
88 + constructor(graphics) {
89 + super(componentNames.SHAPE, graphics);
90 +
91 + /**
92 + * Object of The drawing shape
93 + * @type {fabric.Object}
94 + * @private
95 + */
96 + this._shapeObj = null;
97 +
98 + /**
99 + * Type of the drawing shape
100 + * @type {string}
101 + * @private
102 + */
103 + this._type = DEFAULT_TYPE;
104 +
105 + /**
106 + * Options to draw the shape
107 + * @type {Object}
108 + * @private
109 + */
110 + this._options = extend({}, SHAPE_INIT_OPTIONS);
111 +
112 + /**
113 + * Whether the shape object is selected or not
114 + * @type {boolean}
115 + * @private
116 + */
117 + this._isSelected = false;
118 +
119 + /**
120 + * Pointer for drawing shape (x, y)
121 + * @type {Object}
122 + * @private
123 + */
124 + this._startPoint = {};
125 +
126 + /**
127 + * Using shortcut on drawing shape
128 + * @type {boolean}
129 + * @private
130 + */
131 + this._withShiftKey = false;
132 +
133 + /**
134 + * Event handler list
135 + * @type {Object}
136 + * @private
137 + */
138 + this._handlers = {
139 + mousedown: this._onFabricMouseDown.bind(this),
140 + mousemove: this._onFabricMouseMove.bind(this),
141 + mouseup: this._onFabricMouseUp.bind(this),
142 + keydown: this._onKeyDown.bind(this),
143 + keyup: this._onKeyUp.bind(this),
144 + };
145 + }
146 +
147 + /**
148 + * Start to draw the shape on canvas
149 + * @ignore
150 + */
151 + start() {
152 + const canvas = this.getCanvas();
153 +
154 + this._isSelected = false;
155 +
156 + canvas.defaultCursor = 'crosshair';
157 + canvas.selection = false;
158 + canvas.uniformScaling = true;
159 + canvas.on({
160 + 'mouse:down': this._handlers.mousedown,
161 + });
162 +
163 + fabric.util.addListener(document, 'keydown', this._handlers.keydown);
164 + fabric.util.addListener(document, 'keyup', this._handlers.keyup);
165 + }
166 +
167 + /**
168 + * End to draw the shape on canvas
169 + * @ignore
170 + */
171 + end() {
172 + const canvas = this.getCanvas();
173 +
174 + this._isSelected = false;
175 +
176 + canvas.defaultCursor = 'default';
177 +
178 + canvas.selection = true;
179 + canvas.uniformScaling = false;
180 + canvas.off({
181 + 'mouse:down': this._handlers.mousedown,
182 + });
183 +
184 + fabric.util.removeListener(document, 'keydown', this._handlers.keydown);
185 + fabric.util.removeListener(document, 'keyup', this._handlers.keyup);
186 + }
187 +
188 + /**
189 + * Set states of the current drawing shape
190 + * @ignore
191 + * @param {string} type - Shape type (ex: 'rect', 'circle')
192 + * @param {Object} [options] - Shape options
193 + * @param {(ShapeFillOption | string)} [options.fill] - {@link ShapeFillOption} or
194 + * Shape foreground color (ex: '#fff', 'transparent')
195 + * @param {string} [options.stoke] - Shape outline color
196 + * @param {number} [options.strokeWidth] - Shape outline width
197 + * @param {number} [options.width] - Width value (When type option is 'rect', this options can use)
198 + * @param {number} [options.height] - Height value (When type option is 'rect', this options can use)
199 + * @param {number} [options.rx] - Radius x value (When type option is 'circle', this options can use)
200 + * @param {number} [options.ry] - Radius y value (When type option is 'circle', this options can use)
201 + */
202 + setStates(type, options) {
203 + this._type = type;
204 +
205 + if (options) {
206 + this._options = extend(this._options, options);
207 + }
208 + }
209 +
210 + /**
211 + * Add the shape
212 + * @ignore
213 + * @param {string} type - Shape type (ex: 'rect', 'circle')
214 + * @param {Object} options - Shape options
215 + * @param {(ShapeFillOption | string)} [options.fill] - ShapeFillOption or Shape foreground color (ex: '#fff', 'transparent') or ShapeFillOption object
216 + * @param {string} [options.stroke] - Shape outline color
217 + * @param {number} [options.strokeWidth] - Shape outline width
218 + * @param {number} [options.width] - Width value (When type option is 'rect', this options can use)
219 + * @param {number} [options.height] - Height value (When type option is 'rect', this options can use)
220 + * @param {number} [options.rx] - Radius x value (When type option is 'circle', this options can use)
221 + * @param {number} [options.ry] - Radius y value (When type option is 'circle', this options can use)
222 + * @param {number} [options.isRegular] - Whether scaling shape has 1:1 ratio or not
223 + * @returns {Promise}
224 + */
225 + add(type, options) {
226 + return new Promise((resolve) => {
227 + const canvas = this.getCanvas();
228 + const extendOption = this._extendOptions(options);
229 +
230 + const shapeObj = this._createInstance(type, extendOption);
231 + const objectProperties = this.graphics.createObjectProperties(shapeObj);
232 +
233 + this._bindEventOnShape(shapeObj);
234 +
235 + canvas.add(shapeObj).setActiveObject(shapeObj);
236 +
237 + this._resetPositionFillFilter(shapeObj);
238 +
239 + resolve(objectProperties);
240 + });
241 + }
242 +
243 + /**
244 + * Change the shape
245 + * @ignore
246 + * @param {fabric.Object} shapeObj - Selected shape object on canvas
247 + * @param {Object} options - Shape options
248 + * @param {(ShapeFillOption | string)} [options.fill] - {@link ShapeFillOption} or
249 + * Shape foreground color (ex: '#fff', 'transparent')
250 + * @param {string} [options.stroke] - Shape outline color
251 + * @param {number} [options.strokeWidth] - Shape outline width
252 + * @param {number} [options.width] - Width value (When type option is 'rect', this options can use)
253 + * @param {number} [options.height] - Height value (When type option is 'rect', this options can use)
254 + * @param {number} [options.rx] - Radius x value (When type option is 'circle', this options can use)
255 + * @param {number} [options.ry] - Radius y value (When type option is 'circle', this options can use)
256 + * @param {number} [options.isRegular] - Whether scaling shape has 1:1 ratio or not
257 + * @returns {Promise}
258 + */
259 + change(shapeObj, options) {
260 + return new Promise((resolve, reject) => {
261 + if (!isShape(shapeObj)) {
262 + reject(rejectMessages.unsupportedType);
263 + }
264 + const hasFillOption = getFillTypeFromOption(options.fill) === 'filter';
265 + const { canvasImage, createStaticCanvas } = this.graphics;
266 +
267 + shapeObj.set(
268 + hasFillOption ? makeFabricFillOption(options, canvasImage, createStaticCanvas) : options
269 + );
270 +
271 + if (hasFillOption) {
272 + this._resetPositionFillFilter(shapeObj);
273 + }
274 +
275 + this.getCanvas().renderAll();
276 + resolve();
277 + });
278 + }
279 +
280 + /**
281 + * make fill property for user event
282 + * @param {fabric.Object} shapeObj - fabric object
283 + * @returns {Object}
284 + */
285 + makeFillPropertyForUserEvent(shapeObj) {
286 + const fillType = getFillTypeFromObject(shapeObj);
287 + const fillProp = {};
288 +
289 + if (fillType === SHAPE_FILL_TYPE.FILTER) {
290 + const fillImage = getFillImageFromShape(shapeObj);
291 + const filterOption = makeFilterOptionFromFabricImage(fillImage);
292 +
293 + fillProp.type = fillType;
294 + fillProp.filter = filterOption;
295 + } else {
296 + fillProp.type = SHAPE_FILL_TYPE.COLOR;
297 + fillProp.color = shapeObj.fill || 'transparent';
298 + }
299 +
300 + return fillProp;
301 + }
302 +
303 + /**
304 + * Copy object handling.
305 + * @param {fabric.Object} shapeObj - Shape object
306 + * @param {fabric.Object} originalShapeObj - Shape object
307 + */
308 + processForCopiedObject(shapeObj, originalShapeObj) {
309 + this._bindEventOnShape(shapeObj);
310 +
311 + if (getFillTypeFromObject(shapeObj) === 'filter') {
312 + const fillImage = getFillImageFromShape(originalShapeObj);
313 + const filterOption = makeFilterOptionFromFabricImage(fillImage);
314 + const newStaticCanvas = this.graphics.createStaticCanvas();
315 +
316 + shapeObj.set(
317 + makeFillPatternForFilter(this.graphics.canvasImage, filterOption, newStaticCanvas)
318 + );
319 + this._resetPositionFillFilter(shapeObj);
320 + }
321 + }
322 +
323 + /**
324 + * Create the instance of shape
325 + * @param {string} type - Shape type
326 + * @param {Object} options - Options to creat the shape
327 + * @returns {fabric.Object} Shape instance
328 + * @private
329 + */
330 + _createInstance(type, options) {
331 + let instance;
332 +
333 + switch (type) {
334 + case 'rect':
335 + instance = new fabric.Rect(options);
336 + break;
337 + case 'circle':
338 + instance = new fabric.Ellipse(
339 + extend(
340 + {
341 + type: 'circle',
342 + },
343 + options
344 + )
345 + );
346 + break;
347 + case 'triangle':
348 + instance = new fabric.Triangle(options);
349 + break;
350 + default:
351 + instance = {};
352 + }
353 +
354 + return instance;
355 + }
356 +
357 + /**
358 + * Get the options to create the shape
359 + * @param {Object} options - Options to creat the shape
360 + * @returns {Object} Shape options
361 + * @private
362 + */
363 + _extendOptions(options) {
364 + const selectionStyles = fObjectOptions.SELECTION_STYLE;
365 + const { canvasImage, createStaticCanvas } = this.graphics;
366 +
367 + options = extend({}, SHAPE_INIT_OPTIONS, this._options, selectionStyles, options);
368 +
369 + return makeFabricFillOption(options, canvasImage, createStaticCanvas);
370 + }
371 +
372 + /**
373 + * Bind fabric events on the creating shape object
374 + * @param {fabric.Object} shapeObj - Shape object
375 + * @private
376 + */
377 + _bindEventOnShape(shapeObj) {
378 + const self = this;
379 + const canvas = this.getCanvas();
380 +
381 + shapeObj.on({
382 + added() {
383 + self._shapeObj = this;
384 + resizeHelper.setOrigins(self._shapeObj);
385 + },
386 + selected() {
387 + self._isSelected = true;
388 + self._shapeObj = this;
389 + canvas.uniformScaling = true;
390 + canvas.defaultCursor = 'default';
391 + resizeHelper.setOrigins(self._shapeObj);
392 + },
393 + deselected() {
394 + self._isSelected = false;
395 + self._shapeObj = null;
396 + canvas.defaultCursor = 'crosshair';
397 + canvas.uniformScaling = false;
398 + },
399 + modified() {
400 + const currentObj = self._shapeObj;
401 +
402 + resizeHelper.adjustOriginToCenter(currentObj);
403 + resizeHelper.setOrigins(currentObj);
404 + },
405 + modifiedInGroup(activeSelection) {
406 + self._fillFilterRePositionInGroupSelection(shapeObj, activeSelection);
407 + },
408 + moving() {
409 + self._resetPositionFillFilter(this);
410 + },
411 + rotating() {
412 + self._resetPositionFillFilter(this);
413 + },
414 + scaling(fEvent) {
415 + const pointer = canvas.getPointer(fEvent.e);
416 + const currentObj = self._shapeObj;
417 +
418 + canvas.setCursor('crosshair');
419 + resizeHelper.resize(currentObj, pointer, true);
420 +
421 + self._resetPositionFillFilter(this);
422 + },
423 + });
424 + }
425 +
426 + /**
427 + * MouseDown event handler on canvas
428 + * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event object
429 + * @private
430 + */
431 + _onFabricMouseDown(fEvent) {
432 + if (!fEvent.target) {
433 + this._isSelected = false;
434 + this._shapeObj = false;
435 + }
436 +
437 + if (!this._isSelected && !this._shapeObj) {
438 + const canvas = this.getCanvas();
439 + this._startPoint = canvas.getPointer(fEvent.e);
440 +
441 + canvas.on({
442 + 'mouse:move': this._handlers.mousemove,
443 + 'mouse:up': this._handlers.mouseup,
444 + });
445 + }
446 + }
447 +
448 + /**
449 + * MouseDown event handler on canvas
450 + * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event object
451 + * @private
452 + */
453 + _onFabricMouseMove(fEvent) {
454 + const canvas = this.getCanvas();
455 + const pointer = canvas.getPointer(fEvent.e);
456 + const startPointX = this._startPoint.x;
457 + const startPointY = this._startPoint.y;
458 + const width = startPointX - pointer.x;
459 + const height = startPointY - pointer.y;
460 + const shape = this._shapeObj;
461 +
462 + if (!shape) {
463 + this.add(this._type, {
464 + left: startPointX,
465 + top: startPointY,
466 + width,
467 + height,
468 + }).then((objectProps) => {
469 + this.fire(eventNames.ADD_OBJECT, objectProps);
470 + });
471 + } else {
472 + this._shapeObj.set({
473 + isRegular: this._withShiftKey,
474 + });
475 +
476 + resizeHelper.resize(shape, pointer);
477 + canvas.renderAll();
478 +
479 + this._resetPositionFillFilter(shape);
480 + }
481 + }
482 +
483 + /**
484 + * MouseUp event handler on canvas
485 + * @private
486 + */
487 + _onFabricMouseUp() {
488 + const canvas = this.getCanvas();
489 + const startPointX = this._startPoint.x;
490 + const startPointY = this._startPoint.y;
491 + const shape = this._shapeObj;
492 +
493 + if (!shape) {
494 + this.add(this._type, {
495 + left: startPointX,
496 + top: startPointY,
497 + width: DEFAULT_WIDTH,
498 + height: DEFAULT_HEIGHT,
499 + }).then((objectProps) => {
500 + this.fire(eventNames.ADD_OBJECT, objectProps);
501 + });
502 + } else if (shape) {
503 + resizeHelper.adjustOriginToCenter(shape);
504 + this.fire(eventNames.OBJECT_ADDED, this.graphics.createObjectProperties(shape));
505 + }
506 +
507 + canvas.off({
508 + 'mouse:move': this._handlers.mousemove,
509 + 'mouse:up': this._handlers.mouseup,
510 + });
511 + }
512 +
513 + /**
514 + * Keydown event handler on document
515 + * @param {KeyboardEvent} e - Event object
516 + * @private
517 + */
518 + _onKeyDown(e) {
519 + if (e.keyCode === KEY_CODES.SHIFT) {
520 + this._withShiftKey = true;
521 +
522 + if (this._shapeObj) {
523 + this._shapeObj.isRegular = true;
524 + }
525 + }
526 + }
527 +
528 + /**
529 + * Keyup event handler on document
530 + * @param {KeyboardEvent} e - Event object
531 + * @private
532 + */
533 + _onKeyUp(e) {
534 + if (e.keyCode === KEY_CODES.SHIFT) {
535 + this._withShiftKey = false;
536 +
537 + if (this._shapeObj) {
538 + this._shapeObj.isRegular = false;
539 + }
540 + }
541 + }
542 +
543 + /**
544 + * Reset shape position and internal proportions in the filter type fill area.
545 + * @param {fabric.Object} shapeObj - Shape object
546 + * @private
547 + */
548 + _resetPositionFillFilter(shapeObj) {
549 + if (getFillTypeFromObject(shapeObj) !== 'filter') {
550 + return;
551 + }
552 +
553 + const { patternSourceCanvas } = getCustomProperty(shapeObj, 'patternSourceCanvas');
554 +
555 + const fillImage = getFillImageFromShape(shapeObj);
556 + const { originalAngle } = getCustomProperty(fillImage, 'originalAngle');
557 +
558 + if (this.graphics.canvasImage.angle !== originalAngle) {
559 + reMakePatternImageSource(shapeObj, this.graphics.canvasImage);
560 + }
561 + const { originX, originY } = shapeObj;
562 +
563 + resizeHelper.adjustOriginToCenter(shapeObj);
564 +
565 + shapeObj.width *= shapeObj.scaleX;
566 + shapeObj.height *= shapeObj.scaleY;
567 + shapeObj.rx *= shapeObj.scaleX;
568 + shapeObj.ry *= shapeObj.scaleY;
569 + shapeObj.scaleX = 1;
570 + shapeObj.scaleY = 1;
571 +
572 + rePositionFilterTypeFillImage(shapeObj);
573 +
574 + changeOrigin(shapeObj, {
575 + originX,
576 + originY,
577 + });
578 +
579 + resetFillPatternCanvas(patternSourceCanvas);
580 + }
581 +
582 + /**
583 + * Reset filter area position within group selection.
584 + * @param {fabric.Object} shapeObj - Shape object
585 + * @param {fabric.ActiveSelection} activeSelection - Shape object
586 + * @private
587 + */
588 + _fillFilterRePositionInGroupSelection(shapeObj, activeSelection) {
589 + if (activeSelection.scaleX !== 1 || activeSelection.scaleY !== 1) {
590 + // This is necessary because the group's scale transition state affects the relative size of the fill area.
591 + // The only way to reset the object transformation scale state to neutral.
592 + // {@link https://github.com/fabricjs/fabric.js/issues/5372}
593 + activeSelection.addWithUpdate();
594 + }
595 +
596 + const { angle, left, top } = shapeObj;
597 +
598 + activeSelection.realizeTransform(shapeObj);
599 + this._resetPositionFillFilter(shapeObj);
600 +
601 + shapeObj.set({
602 + angle,
603 + left,
604 + top,
605 + });
606 + }
607 +}
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Text module
4 + */
5 +import fabric from 'fabric';
6 +import snippet from 'tui-code-snippet';
7 +import Component from '../interface/component';
8 +import { eventNames as events, componentNames, fObjectOptions } from '../consts';
9 +import { Promise } from '../util';
10 +
11 +const defaultStyles = {
12 + fill: '#000000',
13 + left: 0,
14 + top: 0,
15 +};
16 +const resetStyles = {
17 + fill: '#000000',
18 + fontStyle: 'normal',
19 + fontWeight: 'normal',
20 + textAlign: 'left',
21 + underline: false,
22 +};
23 +
24 +const DBCLICK_TIME = 500;
25 +
26 +/**
27 + * Text
28 + * @class Text
29 + * @param {Graphics} graphics - Graphics instance
30 + * @extends {Component}
31 + * @ignore
32 + */
33 +class Text extends Component {
34 + constructor(graphics) {
35 + super(componentNames.TEXT, graphics);
36 +
37 + /**
38 + * Default text style
39 + * @type {Object}
40 + */
41 + this._defaultStyles = defaultStyles;
42 +
43 + /**
44 + * Selected state
45 + * @type {boolean}
46 + */
47 + this._isSelected = false;
48 +
49 + /**
50 + * Selected text object
51 + * @type {Object}
52 + */
53 + this._selectedObj = {};
54 +
55 + /**
56 + * Editing text object
57 + * @type {Object}
58 + */
59 + this._editingObj = {};
60 +
61 + /**
62 + * Listeners for fabric event
63 + * @type {Object}
64 + */
65 + this._listeners = {
66 + mousedown: this._onFabricMouseDown.bind(this),
67 + select: this._onFabricSelect.bind(this),
68 + selectClear: this._onFabricSelectClear.bind(this),
69 + scaling: this._onFabricScaling.bind(this),
70 + };
71 +
72 + /**
73 + * Textarea element for editing
74 + * @type {HTMLElement}
75 + */
76 + this._textarea = null;
77 +
78 + /**
79 + * Ratio of current canvas
80 + * @type {number}
81 + */
82 + this._ratio = 1;
83 +
84 + /**
85 + * Last click time
86 + * @type {Date}
87 + */
88 + this._lastClickTime = new Date().getTime();
89 +
90 + /**
91 + * Text object infos before editing
92 + * @type {Object}
93 + */
94 + this._editingObjInfos = {};
95 +
96 + /**
97 + * Previous state of editing
98 + * @type {boolean}
99 + */
100 + this.isPrevEditing = false;
101 + }
102 +
103 + /**
104 + * Start input text mode
105 + */
106 + start() {
107 + const canvas = this.getCanvas();
108 +
109 + canvas.selection = false;
110 + canvas.defaultCursor = 'text';
111 + canvas.on({
112 + 'mouse:down': this._listeners.mousedown,
113 + 'selection:created': this._listeners.select,
114 + 'selection:updated': this._listeners.select,
115 + 'before:selection:cleared': this._listeners.selectClear,
116 + 'object:scaling': this._listeners.scaling,
117 + 'text:editing': this._listeners.modify,
118 + });
119 +
120 + canvas.forEachObject((obj) => {
121 + if (obj.type === 'i-text') {
122 + this.adjustOriginPosition(obj, 'start');
123 + }
124 + });
125 +
126 + this.setCanvasRatio();
127 + }
128 +
129 + /**
130 + * End input text mode
131 + */
132 + end() {
133 + const canvas = this.getCanvas();
134 +
135 + canvas.selection = true;
136 + canvas.defaultCursor = 'default';
137 +
138 + canvas.forEachObject((obj) => {
139 + if (obj.type === 'i-text') {
140 + if (obj.text === '') {
141 + canvas.remove(obj);
142 + } else {
143 + this.adjustOriginPosition(obj, 'end');
144 + }
145 + }
146 + });
147 +
148 + canvas.off({
149 + 'mouse:down': this._listeners.mousedown,
150 + 'object:selected': this._listeners.select,
151 + 'before:selection:cleared': this._listeners.selectClear,
152 + 'object:scaling': this._listeners.scaling,
153 + 'text:editing': this._listeners.modify,
154 + });
155 + }
156 +
157 + /**
158 + * Adjust the origin position
159 + * @param {fabric.Object} text - text object
160 + * @param {string} editStatus - 'start' or 'end'
161 + */
162 + adjustOriginPosition(text, editStatus) {
163 + let [originX, originY] = ['center', 'center'];
164 + if (editStatus === 'start') {
165 + [originX, originY] = ['left', 'top'];
166 + }
167 +
168 + const { x: left, y: top } = text.getPointByOrigin(originX, originY);
169 + text.set({
170 + left,
171 + top,
172 + originX,
173 + originY,
174 + });
175 + text.setCoords();
176 + }
177 +
178 + /**
179 + * Add new text on canvas image
180 + * @param {string} text - Initial input text
181 + * @param {Object} options - Options for generating text
182 + * @param {Object} [options.styles] Initial styles
183 + * @param {string} [options.styles.fill] Color
184 + * @param {string} [options.styles.fontFamily] Font type for text
185 + * @param {number} [options.styles.fontSize] Size
186 + * @param {string} [options.styles.fontStyle] Type of inclination (normal / italic)
187 + * @param {string} [options.styles.fontWeight] Type of thicker or thinner looking (normal / bold)
188 + * @param {string} [options.styles.textAlign] Type of text align (left / center / right)
189 + * @param {string} [options.styles.textDecoration] Type of line (underline / line-through / overline)
190 + * @param {{x: number, y: number}} [options.position] - Initial position
191 + * @returns {Promise}
192 + */
193 + add(text, options) {
194 + return new Promise((resolve) => {
195 + const canvas = this.getCanvas();
196 + let newText = null;
197 + let selectionStyle = fObjectOptions.SELECTION_STYLE;
198 + let styles = this._defaultStyles;
199 +
200 + this._setInitPos(options.position);
201 +
202 + if (options.styles) {
203 + styles = snippet.extend(styles, options.styles);
204 + }
205 +
206 + if (!snippet.isExisty(options.autofocus)) {
207 + options.autofocus = true;
208 + }
209 +
210 + newText = new fabric.IText(text, styles);
211 + selectionStyle = snippet.extend({}, selectionStyle, {
212 + originX: 'left',
213 + originY: 'top',
214 + });
215 +
216 + newText.set(selectionStyle);
217 + newText.on({
218 + mouseup: this._onFabricMouseUp.bind(this),
219 + });
220 +
221 + canvas.add(newText);
222 +
223 + if (options.autofocus) {
224 + newText.enterEditing();
225 + newText.selectAll();
226 + }
227 +
228 + if (!canvas.getActiveObject()) {
229 + canvas.setActiveObject(newText);
230 + }
231 +
232 + this.isPrevEditing = true;
233 + resolve(this.graphics.createObjectProperties(newText));
234 + });
235 + }
236 +
237 + /**
238 + * Change text of activate object on canvas image
239 + * @param {Object} activeObj - Current selected text object
240 + * @param {string} text - Changed text
241 + * @returns {Promise}
242 + */
243 + change(activeObj, text) {
244 + return new Promise((resolve) => {
245 + activeObj.set('text', text);
246 +
247 + this.getCanvas().renderAll();
248 + resolve();
249 + });
250 + }
251 +
252 + /**
253 + * Set style
254 + * @param {Object} activeObj - Current selected text object
255 + * @param {Object} styleObj - Initial styles
256 + * @param {string} [styleObj.fill] Color
257 + * @param {string} [styleObj.fontFamily] Font type for text
258 + * @param {number} [styleObj.fontSize] Size
259 + * @param {string} [styleObj.fontStyle] Type of inclination (normal / italic)
260 + * @param {string} [styleObj.fontWeight] Type of thicker or thinner looking (normal / bold)
261 + * @param {string} [styleObj.textAlign] Type of text align (left / center / right)
262 + * @param {string} [styleObj.textDecoration] Type of line (underline / line-through / overline)
263 + * @returns {Promise}
264 + */
265 + setStyle(activeObj, styleObj) {
266 + return new Promise((resolve) => {
267 + snippet.forEach(
268 + styleObj,
269 + (val, key) => {
270 + if (activeObj[key] === val && key !== 'fontSize') {
271 + styleObj[key] = resetStyles[key] || '';
272 + }
273 + },
274 + this
275 + );
276 +
277 + if ('textDecoration' in styleObj) {
278 + snippet.extend(styleObj, this._getTextDecorationAdaptObject(styleObj.textDecoration));
279 + }
280 +
281 + activeObj.set(styleObj);
282 +
283 + this.getCanvas().renderAll();
284 + resolve();
285 + });
286 + }
287 +
288 + /**
289 + * Get the text
290 + * @param {Object} activeObj - Current selected text object
291 + * @returns {String} text
292 + */
293 + getText(activeObj) {
294 + return activeObj.text;
295 + }
296 +
297 + /**
298 + * Set infos of the current selected object
299 + * @param {fabric.Text} obj - Current selected text object
300 + * @param {boolean} state - State of selecting
301 + */
302 + setSelectedInfo(obj, state) {
303 + this._selectedObj = obj;
304 + this._isSelected = state;
305 + }
306 +
307 + /**
308 + * Whether object is selected or not
309 + * @returns {boolean} State of selecting
310 + */
311 + isSelected() {
312 + return this._isSelected;
313 + }
314 +
315 + /**
316 + * Get current selected text object
317 + * @returns {fabric.Text} Current selected text object
318 + */
319 + getSelectedObj() {
320 + return this._selectedObj;
321 + }
322 +
323 + /**
324 + * Set ratio value of canvas
325 + */
326 + setCanvasRatio() {
327 + const canvasElement = this.getCanvasElement();
328 + const cssWidth = parseInt(canvasElement.style.maxWidth, 10);
329 + const originWidth = canvasElement.width;
330 + const ratio = originWidth / cssWidth;
331 +
332 + this._ratio = ratio;
333 + }
334 +
335 + /**
336 + * Get ratio value of canvas
337 + * @returns {number} Ratio value
338 + */
339 + getCanvasRatio() {
340 + return this._ratio;
341 + }
342 +
343 + /**
344 + * Get text decoration adapt object
345 + * @param {string} textDecoration - text decoration option string
346 + * @returns {object} adapt object for override
347 + */
348 + _getTextDecorationAdaptObject(textDecoration) {
349 + return {
350 + underline: textDecoration === 'underline',
351 + linethrough: textDecoration === 'line-through',
352 + overline: textDecoration === 'overline',
353 + };
354 + }
355 +
356 + /**
357 + * Set initial position on canvas image
358 + * @param {{x: number, y: number}} [position] - Selected position
359 + * @private
360 + */
361 + _setInitPos(position) {
362 + position = position || this.getCanvasImage().getCenterPoint();
363 +
364 + this._defaultStyles.left = position.x;
365 + this._defaultStyles.top = position.y;
366 + }
367 +
368 + /**
369 + * Input event handler
370 + * @private
371 + */
372 + _onInput() {
373 + const ratio = this.getCanvasRatio();
374 + const obj = this._editingObj;
375 + const textareaStyle = this._textarea.style;
376 +
377 + textareaStyle.width = `${Math.ceil(obj.width / ratio)}px`;
378 + textareaStyle.height = `${Math.ceil(obj.height / ratio)}px`;
379 + }
380 +
381 + /**
382 + * Keydown event handler
383 + * @private
384 + */
385 + _onKeyDown() {
386 + const ratio = this.getCanvasRatio();
387 + const obj = this._editingObj;
388 + const textareaStyle = this._textarea.style;
389 +
390 + setTimeout(() => {
391 + obj.text(this._textarea.value);
392 +
393 + textareaStyle.width = `${Math.ceil(obj.width / ratio)}px`;
394 + textareaStyle.height = `${Math.ceil(obj.height / ratio)}px`;
395 + }, 0);
396 + }
397 +
398 + /**
399 + * Blur event handler
400 + * @private
401 + */
402 + _onBlur() {
403 + const ratio = this.getCanvasRatio();
404 + const editingObj = this._editingObj;
405 + const editingObjInfos = this._editingObjInfos;
406 + const textContent = this._textarea.value;
407 + let transWidth = editingObj.width / ratio - editingObjInfos.width / ratio;
408 + let transHeight = editingObj.height / ratio - editingObjInfos.height / ratio;
409 +
410 + if (ratio === 1) {
411 + transWidth /= 2;
412 + transHeight /= 2;
413 + }
414 +
415 + this._textarea.style.display = 'none';
416 +
417 + editingObj.set({
418 + left: editingObjInfos.left + transWidth,
419 + top: editingObjInfos.top + transHeight,
420 + });
421 +
422 + if (textContent.length) {
423 + this.getCanvas().add(editingObj);
424 +
425 + const params = {
426 + id: snippet.stamp(editingObj),
427 + type: editingObj.type,
428 + text: textContent,
429 + };
430 +
431 + this.fire(events.TEXT_CHANGED, params);
432 + }
433 + }
434 +
435 + /**
436 + * Scroll event handler
437 + * @private
438 + */
439 + _onScroll() {
440 + this._textarea.scrollLeft = 0;
441 + this._textarea.scrollTop = 0;
442 + }
443 +
444 + /**
445 + * Fabric scaling event handler
446 + * @param {fabric.Event} fEvent - Current scaling event on selected object
447 + * @private
448 + */
449 + _onFabricScaling(fEvent) {
450 + const obj = fEvent.target;
451 + const scalingSize = obj.fontSize * obj.scaleY;
452 +
453 + obj.fontSize = scalingSize;
454 + obj.scaleX = 1;
455 + obj.scaleY = 1;
456 + }
457 +
458 + /**
459 + * onSelectClear handler in fabric canvas
460 + * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event
461 + * @private
462 + */
463 + _onFabricSelectClear(fEvent) {
464 + const obj = this.getSelectedObj();
465 +
466 + this.isPrevEditing = true;
467 +
468 + this.setSelectedInfo(fEvent.target, false);
469 +
470 + if (obj) {
471 + // obj is empty object at initial time, will be set fabric object
472 + if (obj.text === '') {
473 + this.getCanvas().remove(obj);
474 + }
475 + }
476 + }
477 +
478 + /**
479 + * onSelect handler in fabric canvas
480 + * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event
481 + * @private
482 + */
483 + _onFabricSelect(fEvent) {
484 + this.isPrevEditing = true;
485 +
486 + this.setSelectedInfo(fEvent.target, true);
487 + }
488 +
489 + /**
490 + * Fabric 'mousedown' event handler
491 + * @param {fabric.Event} fEvent - Current mousedown event on selected object
492 + * @private
493 + */
494 + _onFabricMouseDown(fEvent) {
495 + const obj = fEvent.target;
496 +
497 + if (obj && !obj.isType('text')) {
498 + return;
499 + }
500 +
501 + if (this.isPrevEditing) {
502 + this.isPrevEditing = false;
503 +
504 + return;
505 + }
506 +
507 + this._fireAddText(fEvent);
508 + }
509 +
510 + /**
511 + * Fire 'addText' event if object is not selected.
512 + * @param {fabric.Event} fEvent - Current mousedown event on selected object
513 + * @private
514 + */
515 + _fireAddText(fEvent) {
516 + const obj = fEvent.target;
517 + const e = fEvent.e || {};
518 + const originPointer = this.getCanvas().getPointer(e);
519 +
520 + if (!obj) {
521 + this.fire(events.ADD_TEXT, {
522 + originPosition: {
523 + x: originPointer.x,
524 + y: originPointer.y,
525 + },
526 + clientPosition: {
527 + x: e.clientX || 0,
528 + y: e.clientY || 0,
529 + },
530 + });
531 + }
532 + }
533 +
534 + /**
535 + * Fabric mouseup event handler
536 + * @param {fabric.Event} fEvent - Current mousedown event on selected object
537 + * @private
538 + */
539 + _onFabricMouseUp(fEvent) {
540 + const { target } = fEvent;
541 + const newClickTime = new Date().getTime();
542 +
543 + if (this._isDoubleClick(newClickTime) && !target.isEditing) {
544 + target.enterEditing();
545 + }
546 +
547 + if (target.isEditing) {
548 + this.fire(events.TEXT_EDITING); // fire editing text event
549 + }
550 +
551 + this._lastClickTime = newClickTime;
552 + }
553 +
554 + /**
555 + * Get state of firing double click event
556 + * @param {Date} newClickTime - Current clicked time
557 + * @returns {boolean} Whether double clicked or not
558 + * @private
559 + */
560 + _isDoubleClick(newClickTime) {
561 + return newClickTime - this._lastClickTime < DBCLICK_TIME;
562 + }
563 +}
564 +
565 +export default Text;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Constants
4 + */
5 +import { keyMirror } from './util';
6 +
7 +/**
8 + * Editor help features
9 + * @type {Array.<string>}
10 + */
11 +export const HELP_MENUS = ['undo', 'redo', 'reset', 'delete', 'deleteAll'];
12 +
13 +/**
14 + * Filter name value map
15 + * @type {Object.<string, string>}
16 + */
17 +export const FILTER_NAME_VALUE_MAP = {
18 + blur: 'blur',
19 + blocksize: 'pixelate',
20 +};
21 +
22 +/**
23 + * Fill type for shape
24 + * @type {Object.<string, string>}
25 + */
26 +export const SHAPE_FILL_TYPE = {
27 + FILTER: 'filter',
28 + COLOR: 'color',
29 +};
30 +
31 +/**
32 + * Shape type list
33 + * @type {Array.<string>}
34 + */
35 +export const SHAPE_TYPE = ['rect', 'circle', 'triangle'];
36 +
37 +/**
38 + * Component names
39 + * @type {Object.<string, string>}
40 + */
41 +export const componentNames = keyMirror(
42 + 'IMAGE_LOADER',
43 + 'CROPPER',
44 + 'FLIP',
45 + 'ROTATION',
46 + 'FREE_DRAWING',
47 + 'LINE',
48 + 'TEXT',
49 + 'ICON',
50 + 'FILTER',
51 + 'SHAPE'
52 +);
53 +
54 +/**
55 + * Shape default option
56 + * @type {Object}
57 + */
58 +export const SHAPE_DEFAULT_OPTIONS = {
59 + lockSkewingX: true,
60 + lockSkewingY: true,
61 + bringForward: true,
62 + isRegular: false,
63 +};
64 +
65 +/**
66 + * Cropzone default option
67 + * @type {Object}
68 + */
69 +export const CROPZONE_DEFAULT_OPTIONS = {
70 + hasRotatingPoint: false,
71 + hasBorders: false,
72 + lockScalingFlip: true,
73 + lockRotation: true,
74 + lockSkewingX: true,
75 + lockSkewingY: true,
76 +};
77 +
78 +/**
79 + * Command names
80 + * @type {Object.<string, string>}
81 + */
82 +export const commandNames = {
83 + CLEAR_OBJECTS: 'clearObjects',
84 + LOAD_IMAGE: 'loadImage',
85 + FLIP_IMAGE: 'flip',
86 + ROTATE_IMAGE: 'rotate',
87 + ADD_OBJECT: 'addObject',
88 + REMOVE_OBJECT: 'removeObject',
89 + APPLY_FILTER: 'applyFilter',
90 + REMOVE_FILTER: 'removeFilter',
91 + ADD_ICON: 'addIcon',
92 + CHANGE_ICON_COLOR: 'changeIconColor',
93 + ADD_SHAPE: 'addShape',
94 + CHANGE_SHAPE: 'changeShape',
95 + ADD_TEXT: 'addText',
96 + CHANGE_TEXT: 'changeText',
97 + CHANGE_TEXT_STYLE: 'changeTextStyle',
98 + ADD_IMAGE_OBJECT: 'addImageObject',
99 + RESIZE_CANVAS_DIMENSION: 'resizeCanvasDimension',
100 + SET_OBJECT_PROPERTIES: 'setObjectProperties',
101 + SET_OBJECT_POSITION: 'setObjectPosition',
102 + CHANGE_SELECTION: 'changeSelection',
103 +};
104 +
105 +/**
106 + * Event names
107 + * @type {Object.<string, string>}
108 + */
109 +export const eventNames = {
110 + OBJECT_ACTIVATED: 'objectActivated',
111 + OBJECT_MOVED: 'objectMoved',
112 + OBJECT_SCALED: 'objectScaled',
113 + OBJECT_CREATED: 'objectCreated',
114 + OBJECT_ROTATED: 'objectRotated',
115 + OBJECT_ADDED: 'objectAdded',
116 + OBJECT_MODIFIED: 'objectModified',
117 + TEXT_EDITING: 'textEditing',
118 + TEXT_CHANGED: 'textChanged',
119 + ICON_CREATE_RESIZE: 'iconCreateResize',
120 + ICON_CREATE_END: 'iconCreateEnd',
121 + ADD_TEXT: 'addText',
122 + ADD_OBJECT: 'addObject',
123 + ADD_OBJECT_AFTER: 'addObjectAfter',
124 + MOUSE_DOWN: 'mousedown',
125 + MOUSE_UP: 'mouseup',
126 + MOUSE_MOVE: 'mousemove',
127 + // UNDO/REDO Events
128 + REDO_STACK_CHANGED: 'redoStackChanged',
129 + UNDO_STACK_CHANGED: 'undoStackChanged',
130 + SELECTION_CLEARED: 'selectionCleared',
131 + SELECTION_CREATED: 'selectionCreated',
132 +};
133 +
134 +/**
135 + * Editor states
136 + * @type {Object.<string, string>}
137 + */
138 +export const drawingModes = keyMirror(
139 + 'NORMAL',
140 + 'CROPPER',
141 + 'FREE_DRAWING',
142 + 'LINE_DRAWING',
143 + 'TEXT',
144 + 'SHAPE',
145 + 'ICON'
146 +);
147 +
148 +/**
149 + * Shortcut key values
150 + * @type {Object.<string, number>}
151 + */
152 +export const keyCodes = {
153 + Z: 90,
154 + Y: 89,
155 + C: 67,
156 + V: 86,
157 + SHIFT: 16,
158 + BACKSPACE: 8,
159 + DEL: 46,
160 + ARROW_DOWN: 40,
161 + ARROW_UP: 38,
162 +};
163 +
164 +/**
165 + * Fabric object options
166 + * @type {Object.<string, Object>}
167 + */
168 +export const fObjectOptions = {
169 + SELECTION_STYLE: {
170 + borderColor: 'red',
171 + cornerColor: 'green',
172 + cornerSize: 10,
173 + originX: 'center',
174 + originY: 'center',
175 + transparentCorners: false,
176 + },
177 +};
178 +
179 +/**
180 + * Promise reject messages
181 + * @type {Object.<string, string>}
182 + */
183 +export const rejectMessages = {
184 + addedObject: 'The object is already added.',
185 + flip: 'The flipX and flipY setting values are not changed.',
186 + invalidDrawingMode: 'This operation is not supported in the drawing mode.',
187 + invalidParameters: 'Invalid parameters.',
188 + isLock: 'The executing command state is locked.',
189 + loadImage: 'The background image is empty.',
190 + loadingImageFailed: 'Invalid image loaded.',
191 + noActiveObject: 'There is no active object.',
192 + noObject: 'The object is not in canvas.',
193 + redo: 'The promise of redo command is reject.',
194 + rotation: 'The current angle is same the old angle.',
195 + undo: 'The promise of undo command is reject.',
196 + unsupportedOperation: 'Unsupported operation.',
197 + unsupportedType: 'Unsupported object type.',
198 +};
199 +
200 +/**
201 + * Default icon menu svg path
202 + * @type {Object.<string, string>}
203 + */
204 +export const defaultIconPath = {
205 + 'icon-arrow': 'M40 12V0l24 24-24 24V36H0V12h40z',
206 + 'icon-arrow-2': 'M49,32 H3 V22 h46 l-18,-18 h12 l23,23 L43,50 h-12 l18,-18 z ',
207 + 'icon-arrow-3':
208 + 'M43.349998,27 L17.354,53 H1.949999 l25.996,-26 L1.949999,1 h15.404 L43.349998,27 z ',
209 + 'icon-star':
210 + 'M35,54.557999 l-19.912001,10.468 l3.804,-22.172001 l-16.108,-15.7 l22.26,-3.236 L35,3.746 l9.956,20.172001 l22.26,3.236 l-16.108,15.7 l3.804,22.172001 z ',
211 + 'icon-star-2':
212 + 'M17,31.212 l-7.194,4.08 l-4.728,-6.83 l-8.234,0.524 l-1.328,-8.226 l-7.644,-3.14 l2.338,-7.992 l-5.54,-6.18 l5.54,-6.176 l-2.338,-7.994 l7.644,-3.138 l1.328,-8.226 l8.234,0.522 l4.728,-6.83 L17,-24.312 l7.194,-4.08 l4.728,6.83 l8.234,-0.522 l1.328,8.226 l7.644,3.14 l-2.338,7.992 l5.54,6.178 l-5.54,6.178 l2.338,7.992 l-7.644,3.14 l-1.328,8.226 l-8.234,-0.524 l-4.728,6.83 z ',
213 + 'icon-polygon': 'M3,31 L19,3 h32 l16,28 l-16,28 H19 z ',
214 + 'icon-location':
215 + 'M24 62C8 45.503 0 32.837 0 24 0 10.745 10.745 0 24 0s24 10.745 24 24c0 8.837-8 21.503-24 38zm0-28c5.523 0 10-4.477 10-10s-4.477-10-10-10-10 4.477-10 10 4.477 10 10 10z',
216 + 'icon-heart':
217 + 'M49.994999,91.349998 l-6.96,-6.333 C18.324001,62.606995 2.01,47.829002 2.01,29.690998 C2.01,14.912998 13.619999,3.299999 28.401001,3.299999 c8.349,0 16.362,5.859 21.594,12 c5.229,-6.141 13.242001,-12 21.591,-12 c14.778,0 26.390999,11.61 26.390999,26.390999 c0,18.138 -16.314001,32.916 -41.025002,55.374001 l-6.96,6.285 z ',
218 + 'icon-bubble':
219 + 'M44 48L34 58V48H12C5.373 48 0 42.627 0 36V12C0 5.373 5.373 0 12 0h40c6.627 0 12 5.373 12 12v24c0 6.627-5.373 12-12 12h-8z',
220 +};
221 +
222 +export const defaultRotateRangeValus = {
223 + realTimeEvent: true,
224 + min: -360,
225 + max: 360,
226 + value: 0,
227 +};
228 +
229 +export const defaultDrawRangeValus = {
230 + min: 5,
231 + max: 30,
232 + value: 12,
233 +};
234 +
235 +export const defaultShapeStrokeValus = {
236 + realTimeEvent: true,
237 + min: 2,
238 + max: 300,
239 + value: 3,
240 +};
241 +
242 +export const defaultTextRangeValus = {
243 + realTimeEvent: true,
244 + min: 10,
245 + max: 100,
246 + value: 50,
247 +};
248 +
249 +export const defaultFilterRangeValus = {
250 + tintOpacityRange: {
251 + realTimeEvent: true,
252 + min: 0,
253 + max: 1,
254 + value: 0.7,
255 + useDecimal: true,
256 + },
257 + removewhiteDistanceRange: {
258 + realTimeEvent: true,
259 + min: 0,
260 + max: 1,
261 + value: 0.2,
262 + useDecimal: true,
263 + },
264 + brightnessRange: {
265 + realTimeEvent: true,
266 + min: -1,
267 + max: 1,
268 + value: 0,
269 + useDecimal: true,
270 + },
271 + noiseRange: {
272 + realTimeEvent: true,
273 + min: 0,
274 + max: 1000,
275 + value: 100,
276 + },
277 + pixelateRange: {
278 + realTimeEvent: true,
279 + min: 2,
280 + max: 20,
281 + value: 4,
282 + },
283 + colorfilterThresholeRange: {
284 + realTimeEvent: true,
285 + min: 0,
286 + max: 1,
287 + value: 0.2,
288 + useDecimal: true,
289 + },
290 + blurFilterRange: {
291 + value: 0.1,
292 + },
293 +};
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview CropperDrawingMode class
4 + */
5 +import DrawingMode from '../interface/drawingMode';
6 +import { drawingModes, componentNames as components } from '../consts';
7 +
8 +/**
9 + * CropperDrawingMode class
10 + * @class
11 + * @ignore
12 + */
13 +class CropperDrawingMode extends DrawingMode {
14 + constructor() {
15 + super(drawingModes.CROPPER);
16 + }
17 +
18 + /**
19 + * start this drawing mode
20 + * @param {Graphics} graphics - Graphics instance
21 + * @override
22 + */
23 + start(graphics) {
24 + const cropper = graphics.getComponent(components.CROPPER);
25 + cropper.start();
26 + }
27 +
28 + /**
29 + * stop this drawing mode
30 + * @param {Graphics} graphics - Graphics instance
31 + * @override
32 + */
33 + end(graphics) {
34 + const cropper = graphics.getComponent(components.CROPPER);
35 + cropper.end();
36 + }
37 +}
38 +
39 +export default CropperDrawingMode;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview FreeDrawingMode class
4 + */
5 +import DrawingMode from '../interface/drawingMode';
6 +import { drawingModes, componentNames as components } from '../consts';
7 +
8 +/**
9 + * FreeDrawingMode class
10 + * @class
11 + * @ignore
12 + */
13 +class FreeDrawingMode extends DrawingMode {
14 + constructor() {
15 + super(drawingModes.FREE_DRAWING);
16 + }
17 +
18 + /**
19 + * start this drawing mode
20 + * @param {Graphics} graphics - Graphics instance
21 + * @param {{width: ?number, color: ?string}} [options] - Brush width & color
22 + * @override
23 + */
24 + start(graphics, options) {
25 + const freeDrawing = graphics.getComponent(components.FREE_DRAWING);
26 + freeDrawing.start(options);
27 + }
28 +
29 + /**
30 + * stop this drawing mode
31 + * @param {Graphics} graphics - Graphics instance
32 + * @override
33 + */
34 + end(graphics) {
35 + const freeDrawing = graphics.getComponent(components.FREE_DRAWING);
36 + freeDrawing.end();
37 + }
38 +}
39 +
40 +export default FreeDrawingMode;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview IconDrawingMode class
4 + */
5 +import DrawingMode from '../interface/drawingMode';
6 +import { drawingModes, componentNames as components } from '../consts';
7 +
8 +/**
9 + * IconDrawingMode class
10 + * @class
11 + * @ignore
12 + */
13 +class IconDrawingMode extends DrawingMode {
14 + constructor() {
15 + super(drawingModes.ICON);
16 + }
17 +
18 + /**
19 + * start this drawing mode
20 + * @param {Graphics} graphics - Graphics instance
21 + * @override
22 + */
23 + start(graphics) {
24 + const icon = graphics.getComponent(components.ICON);
25 + icon.start();
26 + }
27 +
28 + /**
29 + * stop this drawing mode
30 + * @param {Graphics} graphics - Graphics instance
31 + * @override
32 + */
33 + end(graphics) {
34 + const icon = graphics.getComponent(components.ICON);
35 + icon.end();
36 + }
37 +}
38 +
39 +export default IconDrawingMode;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview LineDrawingMode class
4 + */
5 +import DrawingMode from '../interface/drawingMode';
6 +import { drawingModes, componentNames as components } from '../consts';
7 +
8 +/**
9 + * LineDrawingMode class
10 + * @class
11 + * @ignore
12 + */
13 +class LineDrawingMode extends DrawingMode {
14 + constructor() {
15 + super(drawingModes.LINE_DRAWING);
16 + }
17 +
18 + /**
19 + * start this drawing mode
20 + * @param {Graphics} graphics - Graphics instance
21 + * @param {{width: ?number, color: ?string}} [options] - Brush width & color
22 + * @override
23 + */
24 + start(graphics, options) {
25 + const lineDrawing = graphics.getComponent(components.LINE);
26 + lineDrawing.start(options);
27 + }
28 +
29 + /**
30 + * stop this drawing mode
31 + * @param {Graphics} graphics - Graphics instance
32 + * @override
33 + */
34 + end(graphics) {
35 + const lineDrawing = graphics.getComponent(components.LINE);
36 + lineDrawing.end();
37 + }
38 +}
39 +
40 +export default LineDrawingMode;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview ShapeDrawingMode class
4 + */
5 +import DrawingMode from '../interface/drawingMode';
6 +import { drawingModes, componentNames as components } from '../consts';
7 +
8 +/**
9 + * ShapeDrawingMode class
10 + * @class
11 + * @ignore
12 + */
13 +class ShapeDrawingMode extends DrawingMode {
14 + constructor() {
15 + super(drawingModes.SHAPE);
16 + }
17 +
18 + /**
19 + * start this drawing mode
20 + * @param {Graphics} graphics - Graphics instance
21 + * @override
22 + */
23 + start(graphics) {
24 + const shape = graphics.getComponent(components.SHAPE);
25 + shape.start();
26 + }
27 +
28 + /**
29 + * stop this drawing mode
30 + * @param {Graphics} graphics - Graphics instance
31 + * @override
32 + */
33 + end(graphics) {
34 + const shape = graphics.getComponent(components.SHAPE);
35 + shape.end();
36 + }
37 +}
38 +
39 +export default ShapeDrawingMode;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview TextDrawingMode class
4 + */
5 +import DrawingMode from '../interface/drawingMode';
6 +import { drawingModes, componentNames as components } from '../consts';
7 +
8 +/**
9 + * TextDrawingMode class
10 + * @class
11 + * @ignore
12 + */
13 +class TextDrawingMode extends DrawingMode {
14 + constructor() {
15 + super(drawingModes.TEXT);
16 + }
17 +
18 + /**
19 + * start this drawing mode
20 + * @param {Graphics} graphics - Graphics instance
21 + * @override
22 + */
23 + start(graphics) {
24 + const text = graphics.getComponent(components.TEXT);
25 + text.start();
26 + }
27 +
28 + /**
29 + * stop this drawing mode
30 + * @param {Graphics} graphics - Graphics instance
31 + * @override
32 + */
33 + end(graphics) {
34 + const text = graphics.getComponent(components.TEXT);
35 + text.end();
36 + }
37 +}
38 +
39 +export default TextDrawingMode;
1 +/**
2 + * @author NHN. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Blur extending fabric.Image.filters.Convolute
4 + */
5 +import fabric from 'fabric';
6 +
7 +const ARROW_ANGLE = 30;
8 +const CHEVRON_SIZE_RATIO = 2.7;
9 +const TRIANGLE_SIZE_RATIO = 1.7;
10 +const RADIAN_CONVERSION_VALUE = 180;
11 +
12 +const ArrowLine = fabric.util.createClass(
13 + fabric.Line,
14 + /** @lends Convolute.prototype */ {
15 + /**
16 + * Line type
17 + * @param {String} type
18 + * @default
19 + */
20 + type: 'line',
21 +
22 + /**
23 + * Constructor
24 + * @param {Array} [points] Array of points
25 + * @param {Object} [options] Options object
26 + * @override
27 + */
28 + initialize(points, options = {}) {
29 + this.callSuper('initialize', points, options);
30 +
31 + this.arrowType = options.arrowType;
32 + },
33 +
34 + /**
35 + * Render ArrowLine
36 + * @private
37 + * @override
38 + */
39 + _render(ctx) {
40 + const { x1: fromX, y1: fromY, x2: toX, y2: toY } = this.calcLinePoints();
41 + const linePosition = {
42 + fromX,
43 + fromY,
44 + toX,
45 + toY,
46 + };
47 + this.ctx = ctx;
48 + ctx.lineWidth = this.strokeWidth;
49 +
50 + this._renderBasicLinePath(linePosition);
51 + this._drawDecoratorPath(linePosition);
52 +
53 + this._renderStroke(ctx);
54 + },
55 +
56 + /**
57 + * Render Basic line path
58 + * @param {Object} linePosition - line position
59 + * @param {number} option.fromX - line start position x
60 + * @param {number} option.fromY - line start position y
61 + * @param {number} option.toX - line end position x
62 + * @param {number} option.toY - line end position y
63 + * @private
64 + */
65 + _renderBasicLinePath({ fromX, fromY, toX, toY }) {
66 + this.ctx.beginPath();
67 + this.ctx.moveTo(fromX, fromY);
68 + this.ctx.lineTo(toX, toY);
69 + },
70 +
71 + /**
72 + * Render Arrow Head
73 + * @param {Object} linePosition - line position
74 + * @param {number} option.fromX - line start position x
75 + * @param {number} option.fromY - line start position y
76 + * @param {number} option.toX - line end position x
77 + * @param {number} option.toY - line end position y
78 + * @private
79 + */
80 + _drawDecoratorPath(linePosition) {
81 + this._drawDecoratorPathType('head', linePosition);
82 + this._drawDecoratorPathType('tail', linePosition);
83 + },
84 +
85 + /**
86 + * Render Arrow Head
87 + * @param {string} type - 'head' or 'tail'
88 + * @param {Object} linePosition - line position
89 + * @param {number} option.fromX - line start position x
90 + * @param {number} option.fromY - line start position y
91 + * @param {number} option.toX - line end position x
92 + * @param {number} option.toY - line end position y
93 + * @private
94 + */
95 + _drawDecoratorPathType(type, linePosition) {
96 + switch (this.arrowType[type]) {
97 + case 'triangle':
98 + this._drawTrianglePath(type, linePosition);
99 + break;
100 + case 'chevron':
101 + this._drawChevronPath(type, linePosition);
102 + break;
103 + default:
104 + break;
105 + }
106 + },
107 +
108 + /**
109 + * Render Triangle Head
110 + * @param {string} type - 'head' or 'tail'
111 + * @param {Object} linePosition - line position
112 + * @param {number} option.fromX - line start position x
113 + * @param {number} option.fromY - line start position y
114 + * @param {number} option.toX - line end position x
115 + * @param {number} option.toY - line end position y
116 + * @private
117 + */
118 + _drawTrianglePath(type, linePosition) {
119 + const decorateSize = this.ctx.lineWidth * TRIANGLE_SIZE_RATIO;
120 +
121 + this._drawChevronPath(type, linePosition, decorateSize);
122 + this.ctx.closePath();
123 + },
124 +
125 + /**
126 + * Render Chevron Head
127 + * @param {string} type - 'head' or 'tail'
128 + * @param {Object} linePosition - line position
129 + * @param {number} option.fromX - line start position x
130 + * @param {number} option.fromY - line start position y
131 + * @param {number} option.toX - line end position x
132 + * @param {number} option.toY - line end position y
133 + * @param {number} decorateSize - decorate size
134 + * @private
135 + */
136 + _drawChevronPath(type, { fromX, fromY, toX, toY }, decorateSize) {
137 + const { ctx } = this;
138 + if (!decorateSize) {
139 + decorateSize = this.ctx.lineWidth * CHEVRON_SIZE_RATIO;
140 + }
141 +
142 + const [standardX, standardY] = type === 'head' ? [fromX, fromY] : [toX, toY];
143 + const [compareX, compareY] = type === 'head' ? [toX, toY] : [fromX, fromY];
144 +
145 + const angle =
146 + (Math.atan2(compareY - standardY, compareX - standardX) * RADIAN_CONVERSION_VALUE) /
147 + Math.PI;
148 + const rotatedPosition = (changeAngle) =>
149 + this.getRotatePosition(decorateSize, changeAngle, {
150 + x: standardX,
151 + y: standardY,
152 + });
153 +
154 + ctx.moveTo(...rotatedPosition(angle + ARROW_ANGLE));
155 + ctx.lineTo(standardX, standardY);
156 + ctx.lineTo(...rotatedPosition(angle - ARROW_ANGLE));
157 + },
158 +
159 + /**
160 + * return position from change angle.
161 + * @param {number} distance - change distance
162 + * @param {number} angle - change angle
163 + * @param {Object} referencePosition - reference position
164 + * @returns {Array}
165 + * @private
166 + */
167 + getRotatePosition(distance, angle, referencePosition) {
168 + const radian = (angle * Math.PI) / RADIAN_CONVERSION_VALUE;
169 + const { x, y } = referencePosition;
170 +
171 + return [distance * Math.cos(radian) + x, distance * Math.sin(radian) + y];
172 + },
173 + }
174 +);
175 +
176 +export default ArrowLine;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Blur extending fabric.Image.filters.Convolute
4 + */
5 +import fabric from 'fabric';
6 +
7 +/**
8 + * Blur object
9 + * @class Blur
10 + * @extends {fabric.Image.filters.Convolute}
11 + * @ignore
12 + */
13 +const Blur = fabric.util.createClass(
14 + fabric.Image.filters.Convolute,
15 + /** @lends Convolute.prototype */ {
16 + /**
17 + * Filter type
18 + * @param {String} type
19 + * @default
20 + */
21 + type: 'Blur',
22 +
23 + /**
24 + * constructor
25 + * @override
26 + */
27 + initialize() {
28 + this.matrix = [1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9];
29 + },
30 + }
31 +);
32 +
33 +export default Blur;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview ColorFilter extending fabric.Image.filters.BaseFilter
4 + */
5 +import fabric from 'fabric';
6 +
7 +/**
8 + * ColorFilter object
9 + * @class ColorFilter
10 + * @extends {fabric.Image.filters.BaseFilter}
11 + * @ignore
12 + */
13 +const ColorFilter = fabric.util.createClass(
14 + fabric.Image.filters.BaseFilter,
15 + /** @lends BaseFilter.prototype */ {
16 + /**
17 + * Filter type
18 + * @param {String} type
19 + * @default
20 + */
21 + type: 'ColorFilter',
22 +
23 + /**
24 + * Constructor
25 + * @member fabric.Image.filters.ColorFilter.prototype
26 + * @param {Object} [options] Options object
27 + * @param {Number} [options.color='#FFFFFF'] Value of color (0...255)
28 + * @param {Number} [options.threshold=45] Value of threshold (0...255)
29 + * @override
30 + */
31 + initialize(options) {
32 + if (!options) {
33 + options = {};
34 + }
35 + this.color = options.color || '#FFFFFF';
36 + this.threshold = options.threshold || 45;
37 + this.x = options.x || null;
38 + this.y = options.y || null;
39 + },
40 +
41 + /**
42 + * Applies filter to canvas element
43 + * @param {Object} canvas Canvas object passed by fabric
44 + */
45 + // eslint-disable-next-line complexity
46 + applyTo(canvas) {
47 + const { canvasEl } = canvas;
48 + const context = canvasEl.getContext('2d');
49 + const imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height);
50 + const { data } = imageData;
51 + const { threshold } = this;
52 + let filterColor = fabric.Color.sourceFromHex(this.color);
53 + let i, len;
54 +
55 + if (this.x && this.y) {
56 + filterColor = this._getColor(imageData, this.x, this.y);
57 + }
58 +
59 + for (i = 0, len = data.length; i < len; i += 4) {
60 + if (
61 + this._isOutsideThreshold(data[i], filterColor[0], threshold) ||
62 + this._isOutsideThreshold(data[i + 1], filterColor[1], threshold) ||
63 + this._isOutsideThreshold(data[i + 2], filterColor[2], threshold)
64 + ) {
65 + continue;
66 + }
67 + data[i] = data[i + 1] = data[i + 2] = data[i + 3] = 0;
68 + }
69 + context.putImageData(imageData, 0, 0);
70 + },
71 +
72 + /**
73 + * Check color if it is within threshold
74 + * @param {Number} color1 source color
75 + * @param {Number} color2 filtering color
76 + * @param {Number} threshold threshold
77 + * @returns {boolean} true if within threshold or false
78 + */
79 + _isOutsideThreshold(color1, color2, threshold) {
80 + const diff = color1 - color2;
81 +
82 + return Math.abs(diff) > threshold;
83 + },
84 +
85 + /**
86 + * Get color at (x, y)
87 + * @param {Object} imageData of canvas
88 + * @param {Number} x left position
89 + * @param {Number} y top position
90 + * @returns {Array} color array
91 + */
92 + _getColor(imageData, x, y) {
93 + const color = [0, 0, 0, 0];
94 + const { data, width } = imageData;
95 + const bytes = 4;
96 + const position = (width * y + x) * bytes;
97 +
98 + color[0] = data[position];
99 + color[1] = data[position + 1];
100 + color[2] = data[position + 2];
101 + color[3] = data[position + 3];
102 +
103 + return color;
104 + },
105 + }
106 +);
107 +
108 +export default ColorFilter;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Cropzone extending fabric.Rect
4 + */
5 +import snippet from 'tui-code-snippet';
6 +import fabric from 'fabric';
7 +import { clamp } from '../util';
8 +import { eventNames as events } from '../consts';
9 +
10 +const CORNER_TYPE_TOP_LEFT = 'tl';
11 +const CORNER_TYPE_TOP_RIGHT = 'tr';
12 +const CORNER_TYPE_MIDDLE_TOP = 'mt';
13 +const CORNER_TYPE_MIDDLE_LEFT = 'ml';
14 +const CORNER_TYPE_MIDDLE_RIGHT = 'mr';
15 +const CORNER_TYPE_MIDDLE_BOTTOM = 'mb';
16 +const CORNER_TYPE_BOTTOM_LEFT = 'bl';
17 +const CORNER_TYPE_BOTTOM_RIGHT = 'br';
18 +const CORNER_TYPE_LIST = [
19 + CORNER_TYPE_TOP_LEFT,
20 + CORNER_TYPE_TOP_RIGHT,
21 + CORNER_TYPE_MIDDLE_TOP,
22 + CORNER_TYPE_MIDDLE_LEFT,
23 + CORNER_TYPE_MIDDLE_RIGHT,
24 + CORNER_TYPE_MIDDLE_BOTTOM,
25 + CORNER_TYPE_BOTTOM_LEFT,
26 + CORNER_TYPE_BOTTOM_RIGHT,
27 +];
28 +const NOOP_FUNCTION = () => {};
29 +
30 +/**
31 + * Align with cropzone ratio
32 + * @param {string} selectedCorner - selected corner type
33 + * @returns {{width: number, height: number}}
34 + * @private
35 + */
36 +function cornerTypeValid(selectedCorner) {
37 + return CORNER_TYPE_LIST.indexOf(selectedCorner) >= 0;
38 +}
39 +
40 +/**
41 + * return scale basis type
42 + * @param {number} diffX - X distance of the cursor and corner.
43 + * @param {number} diffY - Y distance of the cursor and corner.
44 + * @returns {string}
45 + * @private
46 + */
47 +function getScaleBasis(diffX, diffY) {
48 + return diffX > diffY ? 'width' : 'height';
49 +}
50 +
51 +/**
52 + * Cropzone object
53 + * Issue: IE7, 8(with excanvas)
54 + * - Cropzone is a black zone without transparency.
55 + * @class Cropzone
56 + * @extends {fabric.Rect}
57 + * @ignore
58 + */
59 +const Cropzone = fabric.util.createClass(
60 + fabric.Rect,
61 + /** @lends Cropzone.prototype */ {
62 + /**
63 + * Constructor
64 + * @param {Object} canvas canvas
65 + * @param {Object} options Options object
66 + * @param {Object} extendsOptions object for extends "options"
67 + * @override
68 + */
69 + initialize(canvas, options, extendsOptions) {
70 + options = snippet.extend(options, extendsOptions);
71 + options.type = 'cropzone';
72 +
73 + this.callSuper('initialize', options);
74 + this._addEventHandler();
75 +
76 + this.canvas = canvas;
77 + this.options = options;
78 + },
79 + canvasEventDelegation(eventName) {
80 + let delegationState = 'unregisted';
81 + const isRegisted = this.canvasEventTrigger[eventName] !== NOOP_FUNCTION;
82 + if (isRegisted) {
83 + delegationState = 'registed';
84 + } else if ([events.OBJECT_MOVED, events.OBJECT_SCALED].indexOf(eventName) < 0) {
85 + delegationState = 'none';
86 + }
87 +
88 + return delegationState;
89 + },
90 + canvasEventRegister(eventName, eventTrigger) {
91 + this.canvasEventTrigger[eventName] = eventTrigger;
92 + },
93 + _addEventHandler() {
94 + this.canvasEventTrigger = {
95 + [events.OBJECT_MOVED]: NOOP_FUNCTION,
96 + [events.OBJECT_SCALED]: NOOP_FUNCTION,
97 + };
98 + this.on({
99 + moving: this._onMoving.bind(this),
100 + scaling: this._onScaling.bind(this),
101 + });
102 + },
103 + _renderCropzone(ctx) {
104 + const cropzoneDashLineWidth = 7;
105 + const cropzoneDashLineOffset = 7;
106 +
107 + // Calc original scale
108 + const originalFlipX = this.flipX ? -1 : 1;
109 + const originalFlipY = this.flipY ? -1 : 1;
110 + const originalScaleX = originalFlipX / this.scaleX;
111 + const originalScaleY = originalFlipY / this.scaleY;
112 +
113 + // Set original scale
114 + ctx.scale(originalScaleX, originalScaleY);
115 +
116 + // Render outer rect
117 + this._fillOuterRect(ctx, 'rgba(0, 0, 0, 0.5)');
118 +
119 + if (this.options.lineWidth) {
120 + this._fillInnerRect(ctx);
121 + this._strokeBorder(ctx, 'rgb(255, 255, 255)', {
122 + lineWidth: this.options.lineWidth,
123 + });
124 + } else {
125 + // Black dash line
126 + this._strokeBorder(ctx, 'rgb(0, 0, 0)', {
127 + lineDashWidth: cropzoneDashLineWidth,
128 + });
129 +
130 + // White dash line
131 + this._strokeBorder(ctx, 'rgb(255, 255, 255)', {
132 + lineDashWidth: cropzoneDashLineWidth,
133 + lineDashOffset: cropzoneDashLineOffset,
134 + });
135 + }
136 +
137 + // Reset scale
138 + ctx.scale(1 / originalScaleX, 1 / originalScaleY);
139 + },
140 +
141 + /**
142 + * Render Crop-zone
143 + * @private
144 + * @override
145 + */
146 + _render(ctx) {
147 + this.callSuper('_render', ctx);
148 +
149 + this._renderCropzone(ctx);
150 + },
151 +
152 + /**
153 + * Cropzone-coordinates with outer rectangle
154 + *
155 + * x0 x1 x2 x3
156 + * y0 +--------------------------+
157 + * |///////|//////////|///////| // <--- "Outer-rectangle"
158 + * |///////|//////////|///////|
159 + * y1 +-------+----------+-------+
160 + * |///////| Cropzone |///////| Cropzone is the "Inner-rectangle"
161 + * |///////| (0, 0) |///////| Center point (0, 0)
162 + * y2 +-------+----------+-------+
163 + * |///////|//////////|///////|
164 + * |///////|//////////|///////|
165 + * y3 +--------------------------+
166 + *
167 + * @typedef {{x: Array<number>, y: Array<number>}} cropzoneCoordinates
168 + * @ignore
169 + */
170 +
171 + /**
172 + * Fill outer rectangle
173 + * @param {CanvasRenderingContext2D} ctx - Context
174 + * @param {string|CanvasGradient|CanvasPattern} fillStyle - Fill-style
175 + * @private
176 + */
177 + _fillOuterRect(ctx, fillStyle) {
178 + const { x, y } = this._getCoordinates();
179 +
180 + ctx.save();
181 + ctx.fillStyle = fillStyle;
182 + ctx.beginPath();
183 +
184 + // Outer rectangle
185 + // Numbers are +/-1 so that overlay edges don't get blurry.
186 + ctx.moveTo(x[0] - 1, y[0] - 1);
187 + ctx.lineTo(x[3] + 1, y[0] - 1);
188 + ctx.lineTo(x[3] + 1, y[3] + 1);
189 + ctx.lineTo(x[0] - 1, y[3] + 1);
190 + ctx.lineTo(x[0] - 1, y[0] - 1);
191 + ctx.closePath();
192 +
193 + // Inner rectangle
194 + ctx.moveTo(x[1], y[1]);
195 + ctx.lineTo(x[1], y[2]);
196 + ctx.lineTo(x[2], y[2]);
197 + ctx.lineTo(x[2], y[1]);
198 + ctx.lineTo(x[1], y[1]);
199 + ctx.closePath();
200 +
201 + ctx.fill();
202 + ctx.restore();
203 + },
204 +
205 + /**
206 + * Draw Inner grid line
207 + * @param {CanvasRenderingContext2D} ctx - Context
208 + * @private
209 + */
210 + _fillInnerRect(ctx) {
211 + const { x: outerX, y: outerY } = this._getCoordinates();
212 + const x = this._caculateInnerPosition(outerX, (outerX[2] - outerX[1]) / 3);
213 + const y = this._caculateInnerPosition(outerY, (outerY[2] - outerY[1]) / 3);
214 +
215 + ctx.save();
216 + ctx.strokeStyle = 'rgba(255, 255, 255, 0.7)';
217 + ctx.lineWidth = this.options.lineWidth;
218 + ctx.beginPath();
219 +
220 + ctx.moveTo(x[0], y[1]);
221 + ctx.lineTo(x[3], y[1]);
222 +
223 + ctx.moveTo(x[0], y[2]);
224 + ctx.lineTo(x[3], y[2]);
225 +
226 + ctx.moveTo(x[1], y[0]);
227 + ctx.lineTo(x[1], y[3]);
228 +
229 + ctx.moveTo(x[2], y[0]);
230 + ctx.lineTo(x[2], y[3]);
231 + ctx.stroke();
232 + ctx.closePath();
233 +
234 + ctx.restore();
235 + },
236 +
237 + /**
238 + * Calculate Inner Position
239 + * @param {Array} outer - outer position
240 + * @param {number} size - interval for calculate
241 + * @returns {Array} - inner position
242 + * @private
243 + */
244 + _caculateInnerPosition(outer, size) {
245 + const position = [];
246 + position[0] = outer[1];
247 + position[1] = outer[1] + size;
248 + position[2] = outer[1] + size * 2;
249 + position[3] = outer[2];
250 +
251 + return position;
252 + },
253 +
254 + /**
255 + * Get coordinates
256 + * @returns {cropzoneCoordinates} - {@link cropzoneCoordinates}
257 + * @private
258 + */
259 + _getCoordinates() {
260 + const { canvas, width, height, left, top } = this;
261 + const halfWidth = width / 2;
262 + const halfHeight = height / 2;
263 + const canvasHeight = canvas.getHeight(); // fabric object
264 + const canvasWidth = canvas.getWidth(); // fabric object
265 +
266 + return {
267 + x: snippet.map(
268 + [
269 + -(halfWidth + left), // x0
270 + -halfWidth, // x1
271 + halfWidth, // x2
272 + halfWidth + (canvasWidth - left - width), // x3
273 + ],
274 + Math.ceil
275 + ),
276 + y: snippet.map(
277 + [
278 + -(halfHeight + top), // y0
279 + -halfHeight, // y1
280 + halfHeight, // y2
281 + halfHeight + (canvasHeight - top - height), // y3
282 + ],
283 + Math.ceil
284 + ),
285 + };
286 + },
287 +
288 + /**
289 + * Stroke border
290 + * @param {CanvasRenderingContext2D} ctx - Context
291 + * @param {string|CanvasGradient|CanvasPattern} strokeStyle - Stroke-style
292 + * @param {number} lineDashWidth - Dash width
293 + * @param {number} [lineDashOffset] - Dash offset
294 + * @param {number} [lineWidth] - line width
295 + * @private
296 + */
297 + _strokeBorder(ctx, strokeStyle, { lineDashWidth, lineDashOffset, lineWidth }) {
298 + const halfWidth = this.width / 2;
299 + const halfHeight = this.height / 2;
300 +
301 + ctx.save();
302 + ctx.strokeStyle = strokeStyle;
303 +
304 + if (ctx.setLineDash) {
305 + ctx.setLineDash([lineDashWidth, lineDashWidth]);
306 + }
307 + if (lineDashOffset) {
308 + ctx.lineDashOffset = lineDashOffset;
309 + }
310 + if (lineWidth) {
311 + ctx.lineWidth = lineWidth;
312 + }
313 +
314 + ctx.beginPath();
315 + ctx.moveTo(-halfWidth, -halfHeight);
316 + ctx.lineTo(halfWidth, -halfHeight);
317 + ctx.lineTo(halfWidth, halfHeight);
318 + ctx.lineTo(-halfWidth, halfHeight);
319 + ctx.lineTo(-halfWidth, -halfHeight);
320 + ctx.stroke();
321 +
322 + ctx.restore();
323 + },
324 +
325 + /**
326 + * onMoving event listener
327 + * @private
328 + */
329 + _onMoving() {
330 + const { height, width, left, top } = this;
331 + const maxLeft = this.canvas.getWidth() - width;
332 + const maxTop = this.canvas.getHeight() - height;
333 +
334 + this.left = clamp(left, 0, maxLeft);
335 + this.top = clamp(top, 0, maxTop);
336 +
337 + this.canvasEventTrigger[events.OBJECT_MOVED](this);
338 + },
339 +
340 + /**
341 + * onScaling event listener
342 + * @param {{e: MouseEvent}} fEvent - Fabric event
343 + * @private
344 + */
345 + _onScaling(fEvent) {
346 + const selectedCorner = fEvent.transform.corner;
347 + const pointer = this.canvas.getPointer(fEvent.e);
348 + const settings = this._calcScalingSizeFromPointer(pointer, selectedCorner);
349 +
350 + // On scaling cropzone,
351 + // change real width and height and fix scaleFactor to 1
352 + this.scale(1).set(settings);
353 +
354 + this.canvasEventTrigger[events.OBJECT_SCALED](this);
355 + },
356 +
357 + /**
358 + * Calc scaled size from mouse pointer with selected corner
359 + * @param {{x: number, y: number}} pointer - Mouse position
360 + * @param {string} selectedCorner - selected corner type
361 + * @returns {Object} Having left or(and) top or(and) width or(and) height.
362 + * @private
363 + */
364 + _calcScalingSizeFromPointer(pointer, selectedCorner) {
365 + const isCornerTypeValid = cornerTypeValid(selectedCorner);
366 +
367 + return isCornerTypeValid && this._resizeCropZone(pointer, selectedCorner);
368 + },
369 +
370 + /**
371 + * Align with cropzone ratio
372 + * @param {number} width - cropzone width
373 + * @param {number} height - cropzone height
374 + * @param {number} maxWidth - limit max width
375 + * @param {number} maxHeight - limit max height
376 + * @param {number} scaleTo - cropzone ratio
377 + * @returns {{width: number, height: number}}
378 + * @private
379 + */
380 + adjustRatioCropzoneSize({ width, height, leftMaker, topMaker, maxWidth, maxHeight, scaleTo }) {
381 + width = maxWidth ? clamp(width, 1, maxWidth) : width;
382 + height = maxHeight ? clamp(height, 1, maxHeight) : height;
383 +
384 + if (!this.presetRatio) {
385 + return {
386 + width,
387 + height,
388 + left: leftMaker(width),
389 + top: topMaker(height),
390 + };
391 + }
392 +
393 + if (scaleTo === 'width') {
394 + height = width / this.presetRatio;
395 + } else {
396 + width = height * this.presetRatio;
397 + }
398 +
399 + const maxScaleFactor = Math.min(maxWidth / width, maxHeight / height);
400 + if (maxScaleFactor <= 1) {
401 + [width, height] = [width, height].map((v) => v * maxScaleFactor);
402 + }
403 +
404 + return {
405 + width,
406 + height,
407 + left: leftMaker(width),
408 + top: topMaker(height),
409 + };
410 + },
411 +
412 + /**
413 + * Get dimension last state cropzone
414 + * @returns {{rectTop: number, rectLeft: number, rectWidth: number, rectHeight: number}}
415 + * @private
416 + */
417 + _getCropzoneRectInfo() {
418 + const { width: canvasWidth, height: canvasHeight } = this.canvas;
419 + const {
420 + top: rectTop,
421 + left: rectLeft,
422 + width: rectWidth,
423 + height: rectHeight,
424 + } = this.getBoundingRect(false, true);
425 +
426 + return {
427 + rectTop,
428 + rectLeft,
429 + rectWidth,
430 + rectHeight,
431 + rectRight: rectLeft + rectWidth,
432 + rectBottom: rectTop + rectHeight,
433 + canvasWidth,
434 + canvasHeight,
435 + };
436 + },
437 +
438 + /**
439 + * Calc scaling dimension
440 + * @param {Object} position - Mouse position
441 + * @param {string} corner - corner type
442 + * @returns {{left: number, top: number, width: number, height: number}}
443 + * @private
444 + */
445 + _resizeCropZone({ x, y }, corner) {
446 + const {
447 + rectWidth,
448 + rectHeight,
449 + rectTop,
450 + rectLeft,
451 + rectBottom,
452 + rectRight,
453 + canvasWidth,
454 + canvasHeight,
455 + } = this._getCropzoneRectInfo();
456 +
457 + const resizeInfoMap = {
458 + tl: {
459 + width: rectRight - x,
460 + height: rectBottom - y,
461 + leftMaker: (newWidth) => rectRight - newWidth,
462 + topMaker: (newHeight) => rectBottom - newHeight,
463 + maxWidth: rectRight,
464 + maxHeight: rectBottom,
465 + scaleTo: getScaleBasis(rectLeft - x, rectTop - y),
466 + },
467 + tr: {
468 + width: x - rectLeft,
469 + height: rectBottom - y,
470 + leftMaker: () => rectLeft,
471 + topMaker: (newHeight) => rectBottom - newHeight,
472 + maxWidth: canvasWidth - rectLeft,
473 + maxHeight: rectBottom,
474 + scaleTo: getScaleBasis(x - rectRight, rectTop - y),
475 + },
476 + mt: {
477 + width: rectWidth,
478 + height: rectBottom - y,
479 + leftMaker: () => rectLeft,
480 + topMaker: (newHeight) => rectBottom - newHeight,
481 + maxWidth: canvasWidth - rectLeft,
482 + maxHeight: rectBottom,
483 + scaleTo: 'height',
484 + },
485 + ml: {
486 + width: rectRight - x,
487 + height: rectHeight,
488 + leftMaker: (newWidth) => rectRight - newWidth,
489 + topMaker: () => rectTop,
490 + maxWidth: rectRight,
491 + maxHeight: canvasHeight - rectTop,
492 + scaleTo: 'width',
493 + },
494 + mr: {
495 + width: x - rectLeft,
496 + height: rectHeight,
497 + leftMaker: () => rectLeft,
498 + topMaker: () => rectTop,
499 + maxWidth: canvasWidth - rectLeft,
500 + maxHeight: canvasHeight - rectTop,
501 + scaleTo: 'width',
502 + },
503 + mb: {
504 + width: rectWidth,
505 + height: y - rectTop,
506 + leftMaker: () => rectLeft,
507 + topMaker: () => rectTop,
508 + maxWidth: canvasWidth - rectLeft,
509 + maxHeight: canvasHeight - rectTop,
510 + scaleTo: 'height',
511 + },
512 + bl: {
513 + width: rectRight - x,
514 + height: y - rectTop,
515 + leftMaker: (newWidth) => rectRight - newWidth,
516 + topMaker: () => rectTop,
517 + maxWidth: rectRight,
518 + maxHeight: canvasHeight - rectTop,
519 + scaleTo: getScaleBasis(rectLeft - x, y - rectBottom),
520 + },
521 + br: {
522 + width: x - rectLeft,
523 + height: y - rectTop,
524 + leftMaker: () => rectLeft,
525 + topMaker: () => rectTop,
526 + maxWidth: canvasWidth - rectLeft,
527 + maxHeight: canvasHeight - rectTop,
528 + scaleTo: getScaleBasis(x - rectRight, y - rectBottom),
529 + },
530 + };
531 +
532 + return this.adjustRatioCropzoneSize(resizeInfoMap[corner]);
533 + },
534 +
535 + /**
536 + * Return the whether this cropzone is valid
537 + * @returns {boolean}
538 + */
539 + isValid() {
540 + return this.left >= 0 && this.top >= 0 && this.width > 0 && this.height > 0;
541 + },
542 + }
543 +);
544 +
545 +export default Cropzone;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Emboss extending fabric.Image.filters.Convolute
4 + */
5 +import fabric from 'fabric';
6 +
7 +/**
8 + * Emboss object
9 + * @class Emboss
10 + * @extends {fabric.Image.filters.Convolute}
11 + * @ignore
12 + */
13 +const Emboss = fabric.util.createClass(
14 + fabric.Image.filters.Convolute,
15 + /** @lends Convolute.prototype */ {
16 + /**
17 + * Filter type
18 + * @param {String} type
19 + * @default
20 + */
21 + type: 'Emboss',
22 +
23 + /**
24 + * constructor
25 + * @override
26 + */
27 + initialize() {
28 + const matrix = [1, 1, 1, 1, 0.7, -1, -1, -1, -1];
29 + this.matrix = matrix;
30 + },
31 + }
32 +);
33 +
34 +export default Emboss;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Mask extending fabric.Image.filters.Mask
4 + */
5 +import fabric from 'fabric';
6 +
7 +/**
8 + * Mask object
9 + * @class Mask
10 + * @extends {fabric.Image.filters.BlendImage}
11 + * @ignore
12 + */
13 +const Mask = fabric.util.createClass(
14 + fabric.Image.filters.BlendImage,
15 + /** @lends Mask.prototype */ {
16 + /**
17 + * Apply filter to canvas element
18 + * @param {Object} pipelineState - Canvas element to apply filter
19 + * @override
20 + */
21 + applyTo(pipelineState) {
22 + if (!this.mask) {
23 + return;
24 + }
25 +
26 + const canvas = pipelineState.canvasEl;
27 + const { width, height } = canvas;
28 + const maskCanvasEl = this._createCanvasOfMask(width, height);
29 + const ctx = canvas.getContext('2d');
30 + const maskCtx = maskCanvasEl.getContext('2d');
31 + const imageData = ctx.getImageData(0, 0, width, height);
32 +
33 + this._drawMask(maskCtx, canvas, ctx);
34 + this._mapData(maskCtx, imageData, width, height);
35 +
36 + pipelineState.imageData = imageData;
37 + },
38 +
39 + /**
40 + * Create canvas of mask image
41 + * @param {number} width - Width of main canvas
42 + * @param {number} height - Height of main canvas
43 + * @returns {HTMLElement} Canvas element
44 + * @private
45 + */
46 + _createCanvasOfMask(width, height) {
47 + const maskCanvasEl = fabric.util.createCanvasElement();
48 +
49 + maskCanvasEl.width = width;
50 + maskCanvasEl.height = height;
51 +
52 + return maskCanvasEl;
53 + },
54 +
55 + /**
56 + * Draw mask image on canvas element
57 + * @param {Object} maskCtx - Context of mask canvas
58 + * @private
59 + */
60 + _drawMask(maskCtx) {
61 + const { mask } = this;
62 + const maskImg = mask.getElement();
63 + const { angle, left, scaleX, scaleY, top } = mask;
64 +
65 + maskCtx.save();
66 + maskCtx.translate(left, top);
67 + maskCtx.rotate((angle * Math.PI) / 180);
68 + maskCtx.scale(scaleX, scaleY);
69 + maskCtx.drawImage(maskImg, -maskImg.width / 2, -maskImg.height / 2);
70 + maskCtx.restore();
71 + },
72 +
73 + /**
74 + * Map mask image data to source image data
75 + * @param {Object} maskCtx - Context of mask canvas
76 + * @param {Object} imageData - Data of source image
77 + * @param {number} width - Width of main canvas
78 + * @param {number} height - Height of main canvas
79 + * @private
80 + */
81 + _mapData(maskCtx, imageData, width, height) {
82 + const { data, height: imgHeight, width: imgWidth } = imageData;
83 + const sourceData = data;
84 + const len = imgWidth * imgHeight * 4;
85 + const maskData = maskCtx.getImageData(0, 0, width, height).data;
86 +
87 + for (let i = 0; i < len; i += 4) {
88 + sourceData[i + 3] = maskData[i]; // adjust value of alpha data
89 + }
90 + },
91 + }
92 +);
93 +
94 +export default Mask;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Sharpen extending fabric.Image.filters.Convolute
4 + */
5 +import fabric from 'fabric';
6 +
7 +/**
8 + * Sharpen object
9 + * @class Sharpen
10 + * @extends {fabric.Image.filters.Convolute}
11 + * @ignore
12 + */
13 +const Sharpen = fabric.util.createClass(
14 + fabric.Image.filters.Convolute,
15 + /** @lends Convolute.prototype */ {
16 + /**
17 + * Filter type
18 + * @param {String} type
19 + * @default
20 + */
21 + type: 'Sharpen',
22 +
23 + /**
24 + * constructor
25 + * @override
26 + */
27 + initialize() {
28 + const matrix = [0, -1, 0, -1, 5, -1, 0, -1, 0];
29 + this.matrix = matrix;
30 + },
31 + }
32 +);
33 +
34 +export default Sharpen;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Command factory
4 + */
5 +import Command from '../interface/command';
6 +
7 +const commands = {};
8 +
9 +/**
10 + * Create a command
11 + * @param {string} name - Command name
12 + * @param {...*} args - Arguments for creating command
13 + * @returns {Command}
14 + * @ignore
15 + */
16 +function create(name, ...args) {
17 + const actions = commands[name];
18 + if (actions) {
19 + return new Command(actions, args);
20 + }
21 +
22 + return null;
23 +}
24 +
25 +/**
26 + * Register a command with name as a key
27 + * @param {Object} command - {name:{string}, execute: {function}, undo: {function}}
28 + * @param {string} command.name - command name
29 + * @param {function} command.execute - executable function
30 + * @param {function} command.undo - undo function
31 + * @ignore
32 + */
33 +function register(command) {
34 + commands[command.name] = command;
35 +}
36 +
37 +export default {
38 + create,
39 + register,
40 +};
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Error-message factory
4 + */
5 +import snippet from 'tui-code-snippet';
6 +import { keyMirror } from '../util';
7 +
8 +const types = keyMirror('UN_IMPLEMENTATION', 'NO_COMPONENT_NAME');
9 +const messages = {
10 + UN_IMPLEMENTATION: 'Should implement a method: ',
11 + NO_COMPONENT_NAME: 'Should set a component name',
12 +};
13 +const map = {
14 + UN_IMPLEMENTATION(methodName) {
15 + return messages.UN_IMPLEMENTATION + methodName;
16 + },
17 + NO_COMPONENT_NAME() {
18 + return messages.NO_COMPONENT_NAME;
19 + },
20 +};
21 +
22 +export default {
23 + types: snippet.extend({}, types),
24 +
25 + create(type, ...args) {
26 + type = type.toLowerCase();
27 + const func = map[type];
28 +
29 + return func(...args);
30 + },
31 +};
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Graphics module
4 + */
5 +import snippet from 'tui-code-snippet';
6 +import fabric from 'fabric';
7 +import ImageLoader from './component/imageLoader';
8 +import Cropper from './component/cropper';
9 +import Flip from './component/flip';
10 +import Rotation from './component/rotation';
11 +import FreeDrawing from './component/freeDrawing';
12 +import Line from './component/line';
13 +import Text from './component/text';
14 +import Icon from './component/icon';
15 +import Filter from './component/filter';
16 +import Shape from './component/shape';
17 +import CropperDrawingMode from './drawingMode/cropper';
18 +import FreeDrawingMode from './drawingMode/freeDrawing';
19 +import LineDrawingMode from './drawingMode/lineDrawing';
20 +import ShapeDrawingMode from './drawingMode/shape';
21 +import TextDrawingMode from './drawingMode/text';
22 +import IconDrawingMode from './drawingMode/icon';
23 +import { getProperties, includes, isShape, Promise } from './util';
24 +import {
25 + componentNames as components,
26 + eventNames as events,
27 + drawingModes,
28 + fObjectOptions,
29 +} from './consts';
30 +import {
31 + makeSelectionUndoData,
32 + makeSelectionUndoDatum,
33 + setCachedUndoDataForDimension,
34 +} from './helper/selectionModifyHelper';
35 +
36 +const {
37 + extend,
38 + stamp,
39 + isArray,
40 + isString,
41 + forEachArray,
42 + forEachOwnProperties,
43 + CustomEvents,
44 +} = snippet;
45 +const DEFAULT_CSS_MAX_WIDTH = 1000;
46 +const DEFAULT_CSS_MAX_HEIGHT = 800;
47 +const EXTRA_PX_FOR_PASTE = 10;
48 +
49 +const cssOnly = {
50 + cssOnly: true,
51 +};
52 +const backstoreOnly = {
53 + backstoreOnly: true,
54 +};
55 +
56 +/**
57 + * Graphics class
58 + * @class
59 + * @param {string|HTMLElement} wrapper - Wrapper's element or selector
60 + * @param {Object} [option] - Canvas max width & height of css
61 + * @param {number} option.cssMaxWidth - Canvas css-max-width
62 + * @param {number} option.cssMaxHeight - Canvas css-max-height
63 + * @ignore
64 + */
65 +class Graphics {
66 + constructor(element, { cssMaxWidth, cssMaxHeight } = {}) {
67 + /**
68 + * Fabric image instance
69 + * @type {fabric.Image}
70 + */
71 + this.canvasImage = null;
72 +
73 + /**
74 + * Max width of canvas elements
75 + * @type {number}
76 + */
77 + this.cssMaxWidth = cssMaxWidth || DEFAULT_CSS_MAX_WIDTH;
78 +
79 + /**
80 + * Max height of canvas elements
81 + * @type {number}
82 + */
83 + this.cssMaxHeight = cssMaxHeight || DEFAULT_CSS_MAX_HEIGHT;
84 +
85 + /**
86 + * cropper Selection Style
87 + * @type {Object}
88 + */
89 + this.cropSelectionStyle = {};
90 +
91 + /**
92 + * target fabric object for copy paste feature
93 + * @type {fabric.Object}
94 + * @private
95 + */
96 + this.targetObjectForCopyPaste = null;
97 +
98 + /**
99 + * Image name
100 + * @type {string}
101 + */
102 + this.imageName = '';
103 +
104 + /**
105 + * Object Map
106 + * @type {Object}
107 + * @private
108 + */
109 + this._objects = {};
110 +
111 + /**
112 + * Fabric-Canvas instance
113 + * @type {fabric.Canvas}
114 + * @private
115 + */
116 + this._canvas = null;
117 +
118 + /**
119 + * Drawing mode
120 + * @type {string}
121 + * @private
122 + */
123 + this._drawingMode = drawingModes.NORMAL;
124 +
125 + /**
126 + * DrawingMode map
127 + * @type {Object.<string, DrawingMode>}
128 + * @private
129 + */
130 + this._drawingModeMap = {};
131 +
132 + /**
133 + * Component map
134 + * @type {Object.<string, Component>}
135 + * @private
136 + */
137 + this._componentMap = {};
138 +
139 + /**
140 + * fabric event handlers
141 + * @type {Object.<string, function>}
142 + * @private
143 + */
144 + this._handler = {
145 + onMouseDown: this._onMouseDown.bind(this),
146 + onObjectAdded: this._onObjectAdded.bind(this),
147 + onObjectRemoved: this._onObjectRemoved.bind(this),
148 + onObjectMoved: this._onObjectMoved.bind(this),
149 + onObjectScaled: this._onObjectScaled.bind(this),
150 + onObjectModified: this._onObjectModified.bind(this),
151 + onObjectRotated: this._onObjectRotated.bind(this),
152 + onObjectSelected: this._onObjectSelected.bind(this),
153 + onPathCreated: this._onPathCreated.bind(this),
154 + onSelectionCleared: this._onSelectionCleared.bind(this),
155 + onSelectionCreated: this._onSelectionCreated.bind(this),
156 + };
157 +
158 + this._setObjectCachingToFalse();
159 + this._setCanvasElement(element);
160 + this._createDrawingModeInstances();
161 + this._createComponents();
162 + this._attachCanvasEvents();
163 + }
164 +
165 + /**
166 + * Destroy canvas element
167 + */
168 + destroy() {
169 + const { wrapperEl } = this._canvas;
170 +
171 + this._canvas.clear();
172 +
173 + wrapperEl.parentNode.removeChild(wrapperEl);
174 + }
175 +
176 + /**
177 + * Deactivates all objects on canvas
178 + * @returns {Graphics} this
179 + */
180 + deactivateAll() {
181 + this._canvas.discardActiveObject();
182 +
183 + return this;
184 + }
185 +
186 + /**
187 + * Renders all objects on canvas
188 + * @returns {Graphics} this
189 + */
190 + renderAll() {
191 + this._canvas.renderAll();
192 +
193 + return this;
194 + }
195 +
196 + /**
197 + * Adds objects on canvas
198 + * @param {Object|Array} objects - objects
199 + */
200 + add(objects) {
201 + let theArgs = [];
202 + if (isArray(objects)) {
203 + theArgs = objects;
204 + } else {
205 + theArgs.push(objects);
206 + }
207 +
208 + this._canvas.add(...theArgs);
209 + }
210 +
211 + /**
212 + * Removes the object or group
213 + * @param {Object} target - graphics object or group
214 + * @returns {boolean} true if contains or false
215 + */
216 + contains(target) {
217 + return this._canvas.contains(target);
218 + }
219 +
220 + /**
221 + * Gets all objects or group
222 + * @returns {Array} all objects, shallow copy
223 + */
224 + getObjects() {
225 + return this._canvas.getObjects().slice();
226 + }
227 +
228 + /**
229 + * Get an object by id
230 + * @param {number} id - object id
231 + * @returns {fabric.Object} object corresponding id
232 + */
233 + getObject(id) {
234 + return this._objects[id];
235 + }
236 +
237 + /**
238 + * Removes the object or group
239 + * @param {Object} target - graphics object or group
240 + */
241 + remove(target) {
242 + this._canvas.remove(target);
243 + }
244 +
245 + /**
246 + * Removes all object or group
247 + * @param {boolean} includesBackground - remove the background image or not
248 + * @returns {Array} all objects array which is removed
249 + */
250 + removeAll(includesBackground) {
251 + const canvas = this._canvas;
252 + const objects = canvas.getObjects().slice();
253 + canvas.remove(...this._canvas.getObjects());
254 +
255 + if (includesBackground) {
256 + canvas.clear();
257 + }
258 +
259 + return objects;
260 + }
261 +
262 + /**
263 + * Removes an object or group by id
264 + * @param {number} id - object id
265 + * @returns {Array} removed objects
266 + */
267 + removeObjectById(id) {
268 + const objects = [];
269 + const canvas = this._canvas;
270 + const target = this.getObject(id);
271 + const isValidGroup = target && target.isType('group') && !target.isEmpty();
272 +
273 + if (isValidGroup) {
274 + canvas.discardActiveObject(); // restore states for each objects
275 + target.forEachObject((obj) => {
276 + objects.push(obj);
277 + canvas.remove(obj);
278 + });
279 + } else if (canvas.contains(target)) {
280 + objects.push(target);
281 + canvas.remove(target);
282 + }
283 +
284 + return objects;
285 + }
286 +
287 + /**
288 + * Get an id by object instance
289 + * @param {fabric.Object} object object
290 + * @returns {number} object id if it exists or null
291 + */
292 + getObjectId(object) {
293 + let key = null;
294 + for (key in this._objects) {
295 + if (this._objects.hasOwnProperty(key)) {
296 + if (object === this._objects[key]) {
297 + return key;
298 + }
299 + }
300 + }
301 +
302 + return null;
303 + }
304 +
305 + /**
306 + * Gets an active object or group
307 + * @returns {Object} active object or group instance
308 + */
309 + getActiveObject() {
310 + return this._canvas._activeObject;
311 + }
312 +
313 + /**
314 + * Returns the object ID to delete the object.
315 + * @returns {number} object id for remove
316 + */
317 + getActiveObjectIdForRemove() {
318 + const activeObject = this.getActiveObject();
319 + const { type, left, top } = activeObject;
320 + const isSelection = type === 'activeSelection';
321 +
322 + if (isSelection) {
323 + const group = new fabric.Group([...activeObject.getObjects()], {
324 + left,
325 + top,
326 + });
327 +
328 + return this._addFabricObject(group);
329 + }
330 +
331 + return this.getObjectId(activeObject);
332 + }
333 +
334 + /**
335 + * Verify that you are ready to erase the object.
336 + * @returns {boolean} ready for object remove
337 + */
338 + isReadyRemoveObject() {
339 + const activeObject = this.getActiveObject();
340 +
341 + return activeObject && !activeObject.isEditing;
342 + }
343 +
344 + /**
345 + * Gets an active group object
346 + * @returns {Object} active group object instance
347 + */
348 + getActiveObjects() {
349 + const activeObject = this._canvas._activeObject;
350 +
351 + return activeObject && activeObject.type === 'activeSelection' ? activeObject : null;
352 + }
353 +
354 + /**
355 + * Get Active object Selection from object ids
356 + * @param {Array.<Object>} objects - fabric objects
357 + * @returns {Object} target - target object group
358 + */
359 + getActiveSelectionFromObjects(objects) {
360 + const canvas = this.getCanvas();
361 +
362 + return new fabric.ActiveSelection(objects, { canvas });
363 + }
364 +
365 + /**
366 + * Activates an object or group
367 + * @param {Object} target - target object or group
368 + */
369 + setActiveObject(target) {
370 + this._canvas.setActiveObject(target);
371 + }
372 +
373 + /**
374 + * Set Crop selection style
375 + * @param {Object} style - Selection styles
376 + */
377 + setCropSelectionStyle(style) {
378 + this.cropSelectionStyle = style;
379 + }
380 +
381 + /**
382 + * Get component
383 + * @param {string} name - Component name
384 + * @returns {Component}
385 + */
386 + getComponent(name) {
387 + return this._componentMap[name];
388 + }
389 +
390 + /**
391 + * Get current drawing mode
392 + * @returns {string}
393 + */
394 + getDrawingMode() {
395 + return this._drawingMode;
396 + }
397 +
398 + /**
399 + * Start a drawing mode. If the current mode is not 'NORMAL', 'stopDrawingMode()' will be called first.
400 + * @param {String} mode Can be one of <I>'CROPPER', 'FREE_DRAWING', 'LINE', 'TEXT', 'SHAPE'</I>
401 + * @param {Object} [option] parameters of drawing mode, it's available with 'FREE_DRAWING', 'LINE_DRAWING'
402 + * @param {Number} [option.width] brush width
403 + * @param {String} [option.color] brush color
404 + * @returns {boolean} true if success or false
405 + */
406 + startDrawingMode(mode, option) {
407 + if (this._isSameDrawingMode(mode)) {
408 + return true;
409 + }
410 +
411 + // If the current mode is not 'NORMAL', 'stopDrawingMode()' will be called first.
412 + this.stopDrawingMode();
413 +
414 + const drawingModeInstance = this._getDrawingModeInstance(mode);
415 + if (drawingModeInstance && drawingModeInstance.start) {
416 + drawingModeInstance.start(this, option);
417 +
418 + this._drawingMode = mode;
419 + }
420 +
421 + return !!drawingModeInstance;
422 + }
423 +
424 + /**
425 + * Stop the current drawing mode and back to the 'NORMAL' mode
426 + */
427 + stopDrawingMode() {
428 + if (this._isSameDrawingMode(drawingModes.NORMAL)) {
429 + return;
430 + }
431 +
432 + const drawingModeInstance = this._getDrawingModeInstance(this.getDrawingMode());
433 + if (drawingModeInstance && drawingModeInstance.end) {
434 + drawingModeInstance.end(this);
435 + }
436 + this._drawingMode = drawingModes.NORMAL;
437 + }
438 +
439 + /**
440 + * To data url from canvas
441 + * @param {Object} options - options for toDataURL
442 + * @param {String} [options.format=png] The format of the output image. Either "jpeg" or "png"
443 + * @param {Number} [options.quality=1] Quality level (0..1). Only used for jpeg.
444 + * @param {Number} [options.multiplier=1] Multiplier to scale by
445 + * @param {Number} [options.left] Cropping left offset. Introduced in fabric v1.2.14
446 + * @param {Number} [options.top] Cropping top offset. Introduced in fabric v1.2.14
447 + * @param {Number} [options.width] Cropping width. Introduced in fabric v1.2.14
448 + * @param {Number} [options.height] Cropping height. Introduced in fabric v1.2.14
449 + * @returns {string} A DOMString containing the requested data URI.
450 + */
451 + toDataURL(options) {
452 + const cropper = this.getComponent(components.CROPPER);
453 + cropper.changeVisibility(false);
454 +
455 + const dataUrl = this._canvas && this._canvas.toDataURL(options);
456 + cropper.changeVisibility(true);
457 +
458 + return dataUrl;
459 + }
460 +
461 + /**
462 + * Save image(background) of canvas
463 + * @param {string} name - Name of image
464 + * @param {?fabric.Image} canvasImage - Fabric image instance
465 + */
466 + setCanvasImage(name, canvasImage) {
467 + if (canvasImage) {
468 + stamp(canvasImage);
469 + }
470 + this.imageName = name;
471 + this.canvasImage = canvasImage;
472 + }
473 +
474 + /**
475 + * Set css max dimension
476 + * @param {{width: number, height: number}} maxDimension - Max width & Max height
477 + */
478 + setCssMaxDimension(maxDimension) {
479 + this.cssMaxWidth = maxDimension.width || this.cssMaxWidth;
480 + this.cssMaxHeight = maxDimension.height || this.cssMaxHeight;
481 + }
482 +
483 + /**
484 + * Adjust canvas dimension with scaling image
485 + */
486 + adjustCanvasDimension() {
487 + const canvasImage = this.canvasImage.scale(1);
488 + const { width, height } = canvasImage.getBoundingRect();
489 + const maxDimension = this._calcMaxDimension(width, height);
490 +
491 + this.setCanvasCssDimension({
492 + width: '100%',
493 + height: '100%', // Set height '' for IE9
494 + 'max-width': `${maxDimension.width}px`,
495 + 'max-height': `${maxDimension.height}px`,
496 + });
497 +
498 + this.setCanvasBackstoreDimension({
499 + width,
500 + height,
501 + });
502 + this._canvas.centerObject(canvasImage);
503 + }
504 +
505 + /**
506 + * Set canvas dimension - css only
507 + * {@link http://fabricjs.com/docs/fabric.Canvas.html#setDimensions}
508 + * @param {Object} dimension - Canvas css dimension
509 + */
510 + setCanvasCssDimension(dimension) {
511 + this._canvas.setDimensions(dimension, cssOnly);
512 + }
513 +
514 + /**
515 + * Set canvas dimension - backstore only
516 + * {@link http://fabricjs.com/docs/fabric.Canvas.html#setDimensions}
517 + * @param {Object} dimension - Canvas backstore dimension
518 + */
519 + setCanvasBackstoreDimension(dimension) {
520 + this._canvas.setDimensions(dimension, backstoreOnly);
521 + }
522 +
523 + /**
524 + * Set image properties
525 + * {@link http://fabricjs.com/docs/fabric.Image.html#set}
526 + * @param {Object} setting - Image properties
527 + * @param {boolean} [withRendering] - If true, The changed image will be reflected in the canvas
528 + */
529 + setImageProperties(setting, withRendering) {
530 + const { canvasImage } = this;
531 +
532 + if (!canvasImage) {
533 + return;
534 + }
535 +
536 + canvasImage.set(setting).setCoords();
537 + if (withRendering) {
538 + this._canvas.renderAll();
539 + }
540 + }
541 +
542 + /**
543 + * Returns canvas element of fabric.Canvas[[lower-canvas]]
544 + * @returns {HTMLCanvasElement}
545 + */
546 + getCanvasElement() {
547 + return this._canvas.getElement();
548 + }
549 +
550 + /**
551 + * Get fabric.Canvas instance
552 + * @returns {fabric.Canvas}
553 + * @private
554 + */
555 + getCanvas() {
556 + return this._canvas;
557 + }
558 +
559 + /**
560 + * Get canvasImage (fabric.Image instance)
561 + * @returns {fabric.Image}
562 + */
563 + getCanvasImage() {
564 + return this.canvasImage;
565 + }
566 +
567 + /**
568 + * Get image name
569 + * @returns {string}
570 + */
571 + getImageName() {
572 + return this.imageName;
573 + }
574 +
575 + /**
576 + * Add image object on canvas
577 + * @param {string} imgUrl - Image url to make object
578 + * @returns {Promise}
579 + */
580 + addImageObject(imgUrl) {
581 + const callback = this._callbackAfterLoadingImageObject.bind(this);
582 +
583 + return new Promise((resolve) => {
584 + fabric.Image.fromURL(
585 + imgUrl,
586 + (image) => {
587 + callback(image);
588 + resolve(this.createObjectProperties(image));
589 + },
590 + {
591 + crossOrigin: 'Anonymous',
592 + }
593 + );
594 + });
595 + }
596 +
597 + /**
598 + * Get center position of canvas
599 + * @returns {Object} {left, top}
600 + */
601 + getCenter() {
602 + return this._canvas.getCenter();
603 + }
604 +
605 + /**
606 + * Get cropped rect
607 + * @returns {Object} rect
608 + */
609 + getCropzoneRect() {
610 + return this.getComponent(components.CROPPER).getCropzoneRect();
611 + }
612 +
613 + /**
614 + * Get cropped rect
615 + * @param {number} [mode] cropzone rect mode
616 + */
617 + setCropzoneRect(mode) {
618 + this.getComponent(components.CROPPER).setCropzoneRect(mode);
619 + }
620 +
621 + /**
622 + * Get cropped image data
623 + * @param {Object} cropRect cropzone rect
624 + * @param {Number} cropRect.left left position
625 + * @param {Number} cropRect.top top position
626 + * @param {Number} cropRect.width width
627 + * @param {Number} cropRect.height height
628 + * @returns {?{imageName: string, url: string}} cropped Image data
629 + */
630 + getCroppedImageData(cropRect) {
631 + return this.getComponent(components.CROPPER).getCroppedImageData(cropRect);
632 + }
633 +
634 + /**
635 + * Set brush option
636 + * @param {Object} option brush option
637 + * @param {Number} option.width width
638 + * @param {String} option.color color like 'FFFFFF', 'rgba(0, 0, 0, 0.5)'
639 + */
640 + setBrush(option) {
641 + const drawingMode = this._drawingMode;
642 + let compName = components.FREE_DRAWING;
643 +
644 + if (drawingMode === drawingModes.LINE_DRAWING) {
645 + compName = components.LINE;
646 + }
647 +
648 + this.getComponent(compName).setBrush(option);
649 + }
650 +
651 + /**
652 + * Set states of current drawing shape
653 + * @param {string} type - Shape type (ex: 'rect', 'circle', 'triangle')
654 + * @param {Object} [options] - Shape options
655 + * @param {(ShapeFillOption | string)} [options.fill] - {@link ShapeFillOption} or
656 + * Shape foreground color (ex: '#fff', 'transparent')
657 + * @param {string} [options.stoke] - Shape outline color
658 + * @param {number} [options.strokeWidth] - Shape outline width
659 + * @param {number} [options.width] - Width value (When type option is 'rect', this options can use)
660 + * @param {number} [options.height] - Height value (When type option is 'rect', this options can use)
661 + * @param {number} [options.rx] - Radius x value (When type option is 'circle', this options can use)
662 + * @param {number} [options.ry] - Radius y value (When type option is 'circle', this options can use)
663 + * @param {number} [options.isRegular] - Whether resizing shape has 1:1 ratio or not
664 + */
665 + setDrawingShape(type, options) {
666 + this.getComponent(components.SHAPE).setStates(type, options);
667 + }
668 +
669 + /**
670 + * Set style of current drawing icon
671 + * @param {string} type - icon type (ex: 'icon-arrow', 'icon-star')
672 + * @param {Object} [iconColor] - Icon color
673 + */
674 + setIconStyle(type, iconColor) {
675 + this.getComponent(components.ICON).setStates(type, iconColor);
676 + }
677 +
678 + /**
679 + * Register icon paths
680 + * @param {Object} pathInfos - Path infos
681 + * @param {string} pathInfos.key - key
682 + * @param {string} pathInfos.value - value
683 + */
684 + registerPaths(pathInfos) {
685 + this.getComponent(components.ICON).registerPaths(pathInfos);
686 + }
687 +
688 + /**
689 + * Change cursor style
690 + * @param {string} cursorType - cursor type
691 + */
692 + changeCursor(cursorType) {
693 + const canvas = this.getCanvas();
694 + canvas.defaultCursor = cursorType;
695 + canvas.renderAll();
696 + }
697 +
698 + /**
699 + * Whether it has the filter or not
700 + * @param {string} type - Filter type
701 + * @returns {boolean} true if it has the filter
702 + */
703 + hasFilter(type) {
704 + return this.getComponent(components.FILTER).hasFilter(type);
705 + }
706 +
707 + /**
708 + * Set selection style of fabric object by init option
709 + * @param {Object} styles - Selection styles
710 + */
711 + setSelectionStyle(styles) {
712 + extend(fObjectOptions.SELECTION_STYLE, styles);
713 + }
714 +
715 + /**
716 + * Set object properties
717 + * @param {number} id - object id
718 + * @param {Object} props - props
719 + * @param {string} [props.fill] Color
720 + * @param {string} [props.fontFamily] Font type for text
721 + * @param {number} [props.fontSize] Size
722 + * @param {string} [props.fontStyle] Type of inclination (normal / italic)
723 + * @param {string} [props.fontWeight] Type of thicker or thinner looking (normal / bold)
724 + * @param {string} [props.textAlign] Type of text align (left / center / right)
725 + * @param {string} [props.textDecoration] Type of line (underline / line-through / overline)
726 + * @returns {Object} applied properties
727 + */
728 + setObjectProperties(id, props) {
729 + const object = this.getObject(id);
730 + const clone = extend({}, props);
731 +
732 + object.set(clone);
733 +
734 + object.setCoords();
735 +
736 + this.getCanvas().renderAll();
737 +
738 + return clone;
739 + }
740 +
741 + /**
742 + * Get object properties corresponding key
743 + * @param {number} id - object id
744 + * @param {Array<string>|ObjectProps|string} keys - property's key
745 + * @returns {Object} properties
746 + */
747 + getObjectProperties(id, keys) {
748 + const object = this.getObject(id);
749 + const props = {};
750 +
751 + if (isString(keys)) {
752 + props[keys] = object[keys];
753 + } else if (isArray(keys)) {
754 + forEachArray(keys, (value) => {
755 + props[value] = object[value];
756 + });
757 + } else {
758 + forEachOwnProperties(keys, (value, key) => {
759 + props[key] = object[key];
760 + });
761 + }
762 +
763 + return props;
764 + }
765 +
766 + /**
767 + * Get object position by originX, originY
768 + * @param {number} id - object id
769 + * @param {string} originX - can be 'left', 'center', 'right'
770 + * @param {string} originY - can be 'top', 'center', 'bottom'
771 + * @returns {Object} {{x:number, y: number}} position by origin if id is valid, or null
772 + */
773 + getObjectPosition(id, originX, originY) {
774 + const targetObj = this.getObject(id);
775 + if (!targetObj) {
776 + return null;
777 + }
778 +
779 + return targetObj.getPointByOrigin(originX, originY);
780 + }
781 +
782 + /**
783 + * Set object position by originX, originY
784 + * @param {number} id - object id
785 + * @param {Object} posInfo - position object
786 + * @param {number} posInfo.x - x position
787 + * @param {number} posInfo.y - y position
788 + * @param {string} posInfo.originX - can be 'left', 'center', 'right'
789 + * @param {string} posInfo.originY - can be 'top', 'center', 'bottom'
790 + * @returns {boolean} true if target id is valid or false
791 + */
792 + setObjectPosition(id, posInfo) {
793 + const targetObj = this.getObject(id);
794 + const { x, y, originX, originY } = posInfo;
795 + if (!targetObj) {
796 + return false;
797 + }
798 +
799 + const targetOrigin = targetObj.getPointByOrigin(originX, originY);
800 + const centerOrigin = targetObj.getPointByOrigin('center', 'center');
801 + const diffX = centerOrigin.x - targetOrigin.x;
802 + const diffY = centerOrigin.y - targetOrigin.y;
803 +
804 + targetObj.set({
805 + left: x + diffX,
806 + top: y + diffY,
807 + });
808 +
809 + targetObj.setCoords();
810 +
811 + return true;
812 + }
813 +
814 + /**
815 + * Get the canvas size
816 + * @returns {Object} {{width: number, height: number}} image size
817 + */
818 + getCanvasSize() {
819 + const image = this.getCanvasImage();
820 +
821 + return {
822 + width: image ? image.width : 0,
823 + height: image ? image.height : 0,
824 + };
825 + }
826 +
827 + /**
828 + * Create fabric static canvas
829 + * @returns {Object} {{width: number, height: number}} image size
830 + */
831 + createStaticCanvas() {
832 + const staticCanvas = new fabric.StaticCanvas();
833 +
834 + staticCanvas.set({
835 + enableRetinaScaling: false,
836 + });
837 +
838 + return staticCanvas;
839 + }
840 +
841 + /**
842 + * Get a DrawingMode instance
843 + * @param {string} modeName - DrawingMode Class Name
844 + * @returns {DrawingMode} DrawingMode instance
845 + * @private
846 + */
847 + _getDrawingModeInstance(modeName) {
848 + return this._drawingModeMap[modeName];
849 + }
850 +
851 + /**
852 + * Set object caching to false. This brought many bugs when draw Shape & cropzone
853 + * @see http://fabricjs.com/fabric-object-caching
854 + * @private
855 + */
856 + _setObjectCachingToFalse() {
857 + fabric.Object.prototype.objectCaching = false;
858 + }
859 +
860 + /**
861 + * Set canvas element to fabric.Canvas
862 + * @param {Element|string} element - Wrapper or canvas element or selector
863 + * @private
864 + */
865 + _setCanvasElement(element) {
866 + let selectedElement;
867 + let canvasElement;
868 +
869 + if (element.nodeType) {
870 + selectedElement = element;
871 + } else {
872 + selectedElement = document.querySelector(element);
873 + }
874 +
875 + if (selectedElement.nodeName.toUpperCase() !== 'CANVAS') {
876 + canvasElement = document.createElement('canvas');
877 + selectedElement.appendChild(canvasElement);
878 + }
879 +
880 + this._canvas = new fabric.Canvas(canvasElement, {
881 + containerClass: 'tui-image-editor-canvas-container',
882 + enableRetinaScaling: false,
883 + });
884 + }
885 +
886 + /**
887 + * Creates DrawingMode instances
888 + * @private
889 + */
890 + _createDrawingModeInstances() {
891 + this._register(this._drawingModeMap, new CropperDrawingMode());
892 + this._register(this._drawingModeMap, new FreeDrawingMode());
893 + this._register(this._drawingModeMap, new LineDrawingMode());
894 + this._register(this._drawingModeMap, new ShapeDrawingMode());
895 + this._register(this._drawingModeMap, new TextDrawingMode());
896 + this._register(this._drawingModeMap, new IconDrawingMode());
897 + }
898 +
899 + /**
900 + * Create components
901 + * @private
902 + */
903 + _createComponents() {
904 + this._register(this._componentMap, new ImageLoader(this));
905 + this._register(this._componentMap, new Cropper(this));
906 + this._register(this._componentMap, new Flip(this));
907 + this._register(this._componentMap, new Rotation(this));
908 + this._register(this._componentMap, new FreeDrawing(this));
909 + this._register(this._componentMap, new Line(this));
910 + this._register(this._componentMap, new Text(this));
911 + this._register(this._componentMap, new Icon(this));
912 + this._register(this._componentMap, new Filter(this));
913 + this._register(this._componentMap, new Shape(this));
914 + }
915 +
916 + /**
917 + * Register component
918 + * @param {Object} map - map object
919 + * @param {Object} module - module which has getName method
920 + * @private
921 + */
922 + _register(map, module) {
923 + map[module.getName()] = module;
924 + }
925 +
926 + /**
927 + * Get the current drawing mode is same with given mode
928 + * @param {string} mode drawing mode
929 + * @returns {boolean} true if same or false
930 + */
931 + _isSameDrawingMode(mode) {
932 + return this.getDrawingMode() === mode;
933 + }
934 +
935 + /**
936 + * Calculate max dimension of canvas
937 + * The css-max dimension is dynamically decided with maintaining image ratio
938 + * The css-max dimension is lower than canvas dimension (attribute of canvas, not css)
939 + * @param {number} width - Canvas width
940 + * @param {number} height - Canvas height
941 + * @returns {{width: number, height: number}} - Max width & Max height
942 + * @private
943 + */
944 + _calcMaxDimension(width, height) {
945 + const wScaleFactor = this.cssMaxWidth / width;
946 + const hScaleFactor = this.cssMaxHeight / height;
947 + let cssMaxWidth = Math.min(width, this.cssMaxWidth);
948 + let cssMaxHeight = Math.min(height, this.cssMaxHeight);
949 +
950 + if (wScaleFactor < 1 && wScaleFactor < hScaleFactor) {
951 + cssMaxWidth = width * wScaleFactor;
952 + cssMaxHeight = height * wScaleFactor;
953 + } else if (hScaleFactor < 1 && hScaleFactor < wScaleFactor) {
954 + cssMaxWidth = width * hScaleFactor;
955 + cssMaxHeight = height * hScaleFactor;
956 + }
957 +
958 + return {
959 + width: Math.floor(cssMaxWidth),
960 + height: Math.floor(cssMaxHeight),
961 + };
962 + }
963 +
964 + /**
965 + * Callback function after loading image
966 + * @param {fabric.Image} obj - Fabric image object
967 + * @private
968 + */
969 + _callbackAfterLoadingImageObject(obj) {
970 + const centerPos = this.getCanvasImage().getCenterPoint();
971 +
972 + obj.set(fObjectOptions.SELECTION_STYLE);
973 + obj.set({
974 + left: centerPos.x,
975 + top: centerPos.y,
976 + crossOrigin: 'Anonymous',
977 + });
978 +
979 + this.getCanvas().add(obj).setActiveObject(obj);
980 + }
981 +
982 + /**
983 + * Attach canvas's events
984 + */
985 + _attachCanvasEvents() {
986 + const canvas = this._canvas;
987 + const handler = this._handler;
988 + canvas.on({
989 + 'mouse:down': handler.onMouseDown,
990 + 'object:added': handler.onObjectAdded,
991 + 'object:removed': handler.onObjectRemoved,
992 + 'object:moving': handler.onObjectMoved,
993 + 'object:scaling': handler.onObjectScaled,
994 + 'object:modified': handler.onObjectModified,
995 + 'object:rotating': handler.onObjectRotated,
996 + 'path:created': handler.onPathCreated,
997 + 'selection:cleared': handler.onSelectionCleared,
998 + 'selection:created': handler.onSelectionCreated,
999 + 'selection:updated': handler.onObjectSelected,
1000 + });
1001 + }
1002 +
1003 + /**
1004 + * "mouse:down" canvas event handler
1005 + * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event
1006 + * @private
1007 + */
1008 + _onMouseDown(fEvent) {
1009 + const { e: event, target } = fEvent;
1010 + const originPointer = this._canvas.getPointer(event);
1011 +
1012 + if (target) {
1013 + const { type } = target;
1014 + const undoData = makeSelectionUndoData(target, (item) =>
1015 + makeSelectionUndoDatum(this.getObjectId(item), item, type === 'activeSelection')
1016 + );
1017 +
1018 + setCachedUndoDataForDimension(undoData);
1019 + }
1020 +
1021 + this.fire(events.MOUSE_DOWN, event, originPointer);
1022 + }
1023 +
1024 + /**
1025 + * "object:added" canvas event handler
1026 + * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event
1027 + * @private
1028 + */
1029 + _onObjectAdded(fEvent) {
1030 + const obj = fEvent.target;
1031 + if (obj.isType('cropzone')) {
1032 + return;
1033 + }
1034 +
1035 + this._addFabricObject(obj);
1036 + }
1037 +
1038 + /**
1039 + * "object:removed" canvas event handler
1040 + * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event
1041 + * @private
1042 + */
1043 + _onObjectRemoved(fEvent) {
1044 + const obj = fEvent.target;
1045 +
1046 + this._removeFabricObject(stamp(obj));
1047 + }
1048 +
1049 + /**
1050 + * "object:moving" canvas event handler
1051 + * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event
1052 + * @private
1053 + */
1054 + _onObjectMoved(fEvent) {
1055 + this._lazyFire(
1056 + events.OBJECT_MOVED,
1057 + (object) => this.createObjectProperties(object),
1058 + fEvent.target
1059 + );
1060 + }
1061 +
1062 + /**
1063 + * "object:scaling" canvas event handler
1064 + * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event
1065 + * @private
1066 + */
1067 + _onObjectScaled(fEvent) {
1068 + this._lazyFire(
1069 + events.OBJECT_SCALED,
1070 + (object) => this.createObjectProperties(object),
1071 + fEvent.target
1072 + );
1073 + }
1074 +
1075 + /**
1076 + * "object:modified" canvas event handler
1077 + * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event
1078 + * @private
1079 + */
1080 + _onObjectModified(fEvent) {
1081 + const { target } = fEvent;
1082 + if (target.type === 'activeSelection') {
1083 + const items = target.getObjects();
1084 +
1085 + items.forEach((item) => item.fire('modifiedInGroup', target));
1086 + }
1087 +
1088 + this.fire(events.OBJECT_MODIFIED, target, this.getObjectId(target));
1089 + }
1090 +
1091 + /**
1092 + * "object:rotating" canvas event handler
1093 + * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event
1094 + * @private
1095 + */
1096 + _onObjectRotated(fEvent) {
1097 + this._lazyFire(
1098 + events.OBJECT_ROTATED,
1099 + (object) => this.createObjectProperties(object),
1100 + fEvent.target
1101 + );
1102 + }
1103 +
1104 + /**
1105 + * Lazy event emitter
1106 + * @param {string} eventName - event name
1107 + * @param {Function} paramsMaker - make param function
1108 + * @param {Object} [target] - Object of the event owner.
1109 + * @private
1110 + */
1111 + _lazyFire(eventName, paramsMaker, target) {
1112 + const existEventDelegation = target && target.canvasEventDelegation;
1113 + const delegationState = existEventDelegation ? target.canvasEventDelegation(eventName) : 'none';
1114 +
1115 + if (delegationState === 'unregisted') {
1116 + target.canvasEventRegister(eventName, (object) => {
1117 + this.fire(eventName, paramsMaker(object));
1118 + });
1119 + }
1120 +
1121 + if (delegationState === 'none') {
1122 + this.fire(eventName, paramsMaker(target));
1123 + }
1124 + }
1125 +
1126 + /**
1127 + * "object:selected" canvas event handler
1128 + * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event
1129 + * @private
1130 + */
1131 + _onObjectSelected(fEvent) {
1132 + const { target } = fEvent;
1133 + const params = this.createObjectProperties(target);
1134 +
1135 + this.fire(events.OBJECT_ACTIVATED, params);
1136 + }
1137 +
1138 + /**
1139 + * "path:created" canvas event handler
1140 + * @param {{path: fabric.Path}} obj - Path object
1141 + * @private
1142 + */
1143 + _onPathCreated(obj) {
1144 + const { x: left, y: top } = obj.path.getCenterPoint();
1145 + obj.path.set(
1146 + extend(
1147 + {
1148 + left,
1149 + top,
1150 + },
1151 + fObjectOptions.SELECTION_STYLE
1152 + )
1153 + );
1154 +
1155 + const params = this.createObjectProperties(obj.path);
1156 +
1157 + this.fire(events.ADD_OBJECT, params);
1158 + }
1159 +
1160 + /**
1161 + * "selction:cleared" canvas event handler
1162 + * @private
1163 + */
1164 + _onSelectionCleared() {
1165 + this.fire(events.SELECTION_CLEARED);
1166 + }
1167 +
1168 + /**
1169 + * "selction:created" canvas event handler
1170 + * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event
1171 + * @private
1172 + */
1173 + _onSelectionCreated(fEvent) {
1174 + const { target } = fEvent;
1175 + const params = this.createObjectProperties(target);
1176 +
1177 + this.fire(events.OBJECT_ACTIVATED, params);
1178 + this.fire(events.SELECTION_CREATED, fEvent.target);
1179 + }
1180 +
1181 + /**
1182 + * Canvas discard selection all
1183 + */
1184 + discardSelection() {
1185 + this._canvas.discardActiveObject();
1186 + this._canvas.renderAll();
1187 + }
1188 +
1189 + /**
1190 + * Canvas Selectable status change
1191 + * @param {boolean} selectable - expect status
1192 + */
1193 + changeSelectableAll(selectable) {
1194 + this._canvas.forEachObject((obj) => {
1195 + obj.selectable = selectable;
1196 + obj.hoverCursor = selectable ? 'move' : 'crosshair';
1197 + });
1198 + }
1199 +
1200 + /**
1201 + * Return object's properties
1202 + * @param {fabric.Object} obj - fabric object
1203 + * @returns {Object} properties object
1204 + */
1205 + createObjectProperties(obj) {
1206 + const predefinedKeys = [
1207 + 'left',
1208 + 'top',
1209 + 'width',
1210 + 'height',
1211 + 'fill',
1212 + 'stroke',
1213 + 'strokeWidth',
1214 + 'opacity',
1215 + 'angle',
1216 + ];
1217 + const props = {
1218 + id: stamp(obj),
1219 + type: obj.type,
1220 + };
1221 +
1222 + extend(props, getProperties(obj, predefinedKeys));
1223 +
1224 + if (includes(['i-text', 'text'], obj.type)) {
1225 + extend(props, this._createTextProperties(obj, props));
1226 + } else if (includes(['rect', 'triangle', 'circle'], obj.type)) {
1227 + const shapeComp = this.getComponent(components.SHAPE);
1228 + extend(props, {
1229 + fill: shapeComp.makeFillPropertyForUserEvent(obj),
1230 + });
1231 + }
1232 +
1233 + return props;
1234 + }
1235 +
1236 + /**
1237 + * Get text object's properties
1238 + * @param {fabric.Object} obj - fabric text object
1239 + * @param {Object} props - properties
1240 + * @returns {Object} properties object
1241 + */
1242 + _createTextProperties(obj) {
1243 + const predefinedKeys = [
1244 + 'text',
1245 + 'fontFamily',
1246 + 'fontSize',
1247 + 'fontStyle',
1248 + 'textAlign',
1249 + 'textDecoration',
1250 + 'fontWeight',
1251 + ];
1252 + const props = {};
1253 + extend(props, getProperties(obj, predefinedKeys));
1254 +
1255 + return props;
1256 + }
1257 +
1258 + /**
1259 + * Add object array by id
1260 + * @param {fabric.Object} obj - fabric object
1261 + * @returns {number} object id
1262 + */
1263 + _addFabricObject(obj) {
1264 + const id = stamp(obj);
1265 + this._objects[id] = obj;
1266 +
1267 + return id;
1268 + }
1269 +
1270 + /**
1271 + * Remove an object in array yb id
1272 + * @param {number} id - object id
1273 + */
1274 + _removeFabricObject(id) {
1275 + delete this._objects[id];
1276 + }
1277 +
1278 + /**
1279 + * Reset targetObjectForCopyPaste value from activeObject
1280 + */
1281 + resetTargetObjectForCopyPaste() {
1282 + const activeObject = this.getActiveObject();
1283 +
1284 + if (activeObject) {
1285 + this.targetObjectForCopyPaste = activeObject;
1286 + }
1287 + }
1288 +
1289 + /**
1290 + * Paste fabric object
1291 + * @returns {Promise}
1292 + */
1293 + pasteObject() {
1294 + if (!this.targetObjectForCopyPaste) {
1295 + return Promise.resolve([]);
1296 + }
1297 +
1298 + const targetObject = this.targetObjectForCopyPaste;
1299 + const isGroupSelect = targetObject.type === 'activeSelection';
1300 + const targetObjects = isGroupSelect ? targetObject.getObjects() : [targetObject];
1301 + let newTargetObject = null;
1302 +
1303 + this.discardSelection();
1304 +
1305 + return this._cloneObject(targetObjects).then((addedObjects) => {
1306 + if (addedObjects.length > 1) {
1307 + newTargetObject = this.getActiveSelectionFromObjects(addedObjects);
1308 + } else {
1309 + [newTargetObject] = addedObjects;
1310 + }
1311 + this.targetObjectForCopyPaste = newTargetObject;
1312 + this.setActiveObject(newTargetObject);
1313 + });
1314 + }
1315 +
1316 + /**
1317 + * Clone object
1318 + * @param {fabric.Object} targetObjects - fabric object
1319 + * @returns {Promise}
1320 + * @private
1321 + */
1322 + _cloneObject(targetObjects) {
1323 + const addedObjects = snippet.map(targetObjects, (targetObject) =>
1324 + this._cloneObjectItem(targetObject)
1325 + );
1326 +
1327 + return Promise.all(addedObjects);
1328 + }
1329 +
1330 + /**
1331 + * Clone object one item
1332 + * @param {fabric.Object} targetObject - fabric object
1333 + * @returns {Promise}
1334 + * @private
1335 + */
1336 + _cloneObjectItem(targetObject) {
1337 + return this._copyFabricObjectForPaste(targetObject).then((clonedObject) => {
1338 + const objectProperties = this.createObjectProperties(clonedObject);
1339 + this.add(clonedObject);
1340 +
1341 + this.fire(events.ADD_OBJECT, objectProperties);
1342 +
1343 + return clonedObject;
1344 + });
1345 + }
1346 +
1347 + /**
1348 + * Copy fabric object with Changed position for copy and paste
1349 + * @param {fabric.Object} targetObject - fabric object
1350 + * @returns {Promise}
1351 + * @private
1352 + */
1353 + _copyFabricObjectForPaste(targetObject) {
1354 + const addExtraPx = (value, isReverse) =>
1355 + isReverse ? value - EXTRA_PX_FOR_PASTE : value + EXTRA_PX_FOR_PASTE;
1356 +
1357 + return this._copyFabricObject(targetObject).then((clonedObject) => {
1358 + const { left, top, width, height } = clonedObject;
1359 + const { width: canvasWidth, height: canvasHeight } = this.getCanvasSize();
1360 + const rightEdge = left + width / 2;
1361 + const bottomEdge = top + height / 2;
1362 +
1363 + clonedObject.set(
1364 + snippet.extend(
1365 + {
1366 + left: addExtraPx(left, rightEdge + EXTRA_PX_FOR_PASTE > canvasWidth),
1367 + top: addExtraPx(top, bottomEdge + EXTRA_PX_FOR_PASTE > canvasHeight),
1368 + },
1369 + fObjectOptions.SELECTION_STYLE
1370 + )
1371 + );
1372 +
1373 + return clonedObject;
1374 + });
1375 + }
1376 +
1377 + /**
1378 + * Copy fabric object
1379 + * @param {fabric.Object} targetObject - fabric object
1380 + * @returns {Promise}
1381 + * @private
1382 + */
1383 + _copyFabricObject(targetObject) {
1384 + return new Promise((resolve) => {
1385 + targetObject.clone((cloned) => {
1386 + const shapeComp = this.getComponent(components.SHAPE);
1387 + if (isShape(cloned)) {
1388 + shapeComp.processForCopiedObject(cloned, targetObject);
1389 + }
1390 +
1391 + resolve(cloned);
1392 + });
1393 + });
1394 + }
1395 +}
1396 +
1397 +CustomEvents.mixin(Graphics);
1398 +
1399 +export default Graphics;
1 +/*
2 + imagetracer.js version 1.2.4
3 + Simple raster image tracer and vectorizer written in JavaScript.
4 + andras@jankovics.net
5 +*/
6 +
7 +/*
8 + The Unlicense / PUBLIC DOMAIN
9 + This is free and unencumbered software released into the public domain.
10 + Anyone is free to copy, modify, publish, use, compile, sell, or
11 + distribute this software, either in source code form or as a compiled
12 + binary, for any purpose, commercial or non-commercial, and by any
13 + means.
14 + In jurisdictions that recognize copyright laws, the author or authors
15 + of this software dedicate any and all copyright interest in the
16 + software to the public domain. We make this dedication for the benefit
17 + of the public at large and to the detriment of our heirs and
18 + successors. We intend this dedication to be an overt act of
19 + relinquishment in perpetuity of all present and future rights to this
20 + software under copyright law.
21 + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22 + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23 + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
24 + IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
25 + OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
26 + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
27 + OTHER DEALINGS IN THE SOFTWARE.
28 + For more information, please refer to http://unlicense.org/
29 +*/
30 +export default class ImageTracer {
31 + static tracerDefaultOption() {
32 + return {
33 + pathomit: 100,
34 + ltres: 0.1,
35 + qtres: 1,
36 +
37 + scale: 1,
38 + strokewidth: 5,
39 + viewbox: false,
40 + linefilter: true,
41 + desc: false,
42 + rightangleenhance: false,
43 + pal: [
44 + {
45 + r: 0,
46 + g: 0,
47 + b: 0,
48 + a: 255,
49 + },
50 + {
51 + r: 255,
52 + g: 255,
53 + b: 255,
54 + a: 255,
55 + },
56 + ],
57 + };
58 + }
59 + /* eslint-disable */
60 + constructor() {
61 + this.versionnumber = '1.2.4';
62 + this.optionpresets = {
63 + default: {
64 + corsenabled: false,
65 + ltres: 1,
66 + qtres: 1,
67 + pathomit: 8,
68 + rightangleenhance: true,
69 + colorsampling: 2,
70 + numberofcolors: 16,
71 + mincolorratio: 0,
72 + colorquantcycles: 3,
73 + layering: 0,
74 + strokewidth: 1,
75 + linefilter: false,
76 + scale: 1,
77 + roundcoords: 1,
78 + viewbox: false,
79 + desc: false,
80 + lcpr: 0,
81 + qcpr: 0,
82 + blurradius: 0,
83 + blurdelta: 20,
84 + },
85 + posterized1: {
86 + colorsampling: 0,
87 + numberofcolors: 2,
88 + },
89 + posterized2: {
90 + numberofcolors: 4,
91 + blurradius: 5,
92 + },
93 + curvy: {
94 + ltres: 0.01,
95 + linefilter: true,
96 + rightangleenhance: false,
97 + },
98 + sharp: { qtres: 0.01, linefilter: false },
99 + detailed: { pathomit: 0, roundcoords: 2, ltres: 0.5, qtres: 0.5, numberofcolors: 64 },
100 + smoothed: { blurradius: 5, blurdelta: 64 },
101 + grayscale: { colorsampling: 0, colorquantcycles: 1, numberofcolors: 7 },
102 + fixedpalette: { colorsampling: 0, colorquantcycles: 1, numberofcolors: 27 },
103 + randomsampling1: { colorsampling: 1, numberofcolors: 8 },
104 + randomsampling2: { colorsampling: 1, numberofcolors: 64 },
105 + artistic1: {
106 + colorsampling: 0,
107 + colorquantcycles: 1,
108 + pathomit: 0,
109 + blurradius: 5,
110 + blurdelta: 64,
111 + ltres: 0.01,
112 + linefilter: true,
113 + numberofcolors: 16,
114 + strokewidth: 2,
115 + },
116 + artistic2: {
117 + qtres: 0.01,
118 + colorsampling: 0,
119 + colorquantcycles: 1,
120 + numberofcolors: 4,
121 + strokewidth: 0,
122 + },
123 + artistic3: { qtres: 10, ltres: 10, numberofcolors: 8 },
124 + artistic4: {
125 + qtres: 10,
126 + ltres: 10,
127 + numberofcolors: 64,
128 + blurradius: 5,
129 + blurdelta: 256,
130 + strokewidth: 2,
131 + },
132 + posterized3: {
133 + ltres: 1,
134 + qtres: 1,
135 + pathomit: 20,
136 + rightangleenhance: true,
137 + colorsampling: 0,
138 + numberofcolors: 3,
139 + mincolorratio: 0,
140 + colorquantcycles: 3,
141 + blurradius: 3,
142 + blurdelta: 20,
143 + strokewidth: 0,
144 + linefilter: false,
145 + roundcoords: 1,
146 + pal: [
147 + { r: 0, g: 0, b: 100, a: 255 },
148 + { r: 255, g: 255, b: 255, a: 255 },
149 + ],
150 + },
151 + };
152 +
153 + this.pathscan_combined_lookup = [
154 + [
155 + [-1, -1, -1, -1],
156 + [-1, -1, -1, -1],
157 + [-1, -1, -1, -1],
158 + [-1, -1, -1, -1],
159 + ],
160 + [
161 + [0, 1, 0, -1],
162 + [-1, -1, -1, -1],
163 + [-1, -1, -1, -1],
164 + [0, 2, -1, 0],
165 + ],
166 + [
167 + [-1, -1, -1, -1],
168 + [-1, -1, -1, -1],
169 + [0, 1, 0, -1],
170 + [0, 0, 1, 0],
171 + ],
172 + [
173 + [0, 0, 1, 0],
174 + [-1, -1, -1, -1],
175 + [0, 2, -1, 0],
176 + [-1, -1, -1, -1],
177 + ],
178 + [
179 + [-1, -1, -1, -1],
180 + [0, 0, 1, 0],
181 + [0, 3, 0, 1],
182 + [-1, -1, -1, -1],
183 + ],
184 + [
185 + [13, 3, 0, 1],
186 + [13, 2, -1, 0],
187 + [7, 1, 0, -1],
188 + [7, 0, 1, 0],
189 + ],
190 + [
191 + [-1, -1, -1, -1],
192 + [0, 1, 0, -1],
193 + [-1, -1, -1, -1],
194 + [0, 3, 0, 1],
195 + ],
196 + [
197 + [0, 3, 0, 1],
198 + [0, 2, -1, 0],
199 + [-1, -1, -1, -1],
200 + [-1, -1, -1, -1],
201 + ],
202 + [
203 + [0, 3, 0, 1],
204 + [0, 2, -1, 0],
205 + [-1, -1, -1, -1],
206 + [-1, -1, -1, -1],
207 + ],
208 + [
209 + [-1, -1, -1, -1],
210 + [0, 1, 0, -1],
211 + [-1, -1, -1, -1],
212 + [0, 3, 0, 1],
213 + ],
214 + [
215 + [11, 1, 0, -1],
216 + [14, 0, 1, 0],
217 + [14, 3, 0, 1],
218 + [11, 2, -1, 0],
219 + ],
220 + [
221 + [-1, -1, -1, -1],
222 + [0, 0, 1, 0],
223 + [0, 3, 0, 1],
224 + [-1, -1, -1, -1],
225 + ],
226 + [
227 + [0, 0, 1, 0],
228 + [-1, -1, -1, -1],
229 + [0, 2, -1, 0],
230 + [-1, -1, -1, -1],
231 + ],
232 + [
233 + [-1, -1, -1, -1],
234 + [-1, -1, -1, -1],
235 + [0, 1, 0, -1],
236 + [0, 0, 1, 0],
237 + ],
238 + [
239 + [0, 1, 0, -1],
240 + [-1, -1, -1, -1],
241 + [-1, -1, -1, -1],
242 + [0, 2, -1, 0],
243 + ],
244 + [
245 + [-1, -1, -1, -1],
246 + [-1, -1, -1, -1],
247 + [-1, -1, -1, -1],
248 + [-1, -1, -1, -1],
249 + ],
250 + ];
251 +
252 + this.gks = [
253 + [0.27901, 0.44198, 0.27901],
254 + [0.135336, 0.228569, 0.272192, 0.228569, 0.135336],
255 + [0.086776, 0.136394, 0.178908, 0.195843, 0.178908, 0.136394, 0.086776],
256 + [0.063327, 0.093095, 0.122589, 0.144599, 0.152781, 0.144599, 0.122589, 0.093095, 0.063327],
257 + [
258 + 0.049692,
259 + 0.069304,
260 + 0.089767,
261 + 0.107988,
262 + 0.120651,
263 + 0.125194,
264 + 0.120651,
265 + 0.107988,
266 + 0.089767,
267 + 0.069304,
268 + 0.049692,
269 + ],
270 + ];
271 +
272 + this.specpalette = [
273 + { r: 0, g: 0, b: 0, a: 255 },
274 + { r: 128, g: 128, b: 128, a: 255 },
275 + { r: 0, g: 0, b: 128, a: 255 },
276 + { r: 64, g: 64, b: 128, a: 255 },
277 + { r: 192, g: 192, b: 192, a: 255 },
278 + { r: 255, g: 255, b: 255, a: 255 },
279 + { r: 128, g: 128, b: 192, a: 255 },
280 + { r: 0, g: 0, b: 192, a: 255 },
281 + { r: 128, g: 0, b: 0, a: 255 },
282 + { r: 128, g: 64, b: 64, a: 255 },
283 + { r: 128, g: 0, b: 128, a: 255 },
284 + { r: 168, g: 168, b: 168, a: 255 },
285 + { r: 192, g: 128, b: 128, a: 255 },
286 + { r: 192, g: 0, b: 0, a: 255 },
287 + { r: 255, g: 255, b: 255, a: 255 },
288 + { r: 0, g: 128, b: 0, a: 255 },
289 + ];
290 + }
291 +
292 + imageToSVG(url, callback, options) {
293 + options = this.checkoptions(options);
294 + this.loadImage(
295 + url,
296 + (canvas) => {
297 + callback(this.imagedataToSVG(this.getImgdata(canvas), options));
298 + },
299 + options
300 + );
301 + }
302 +
303 + imagedataToSVG(imgd, options) {
304 + options = this.checkoptions(options);
305 + const td = this.imagedataToTracedata(imgd, options);
306 +
307 + return this.getsvgstring(td, options);
308 + }
309 +
310 + imageToTracedata(url, callback, options) {
311 + options = this.checkoptions(options);
312 + this.loadImage(
313 + url,
314 + (canvas) => {
315 + callback(this.imagedataToTracedata(this.getImgdata(canvas), options));
316 + },
317 + options
318 + );
319 + }
320 +
321 + imagedataToTracedata(imgd, options) {
322 + options = this.checkoptions(options);
323 + const ii = this.colorquantization(imgd, options);
324 + let tracedata;
325 + if (options.layering === 0) {
326 + tracedata = {
327 + layers: [],
328 + palette: ii.palette,
329 + width: ii.array[0].length - 2,
330 + height: ii.array.length - 2,
331 + };
332 +
333 + for (let colornum = 0; colornum < ii.palette.length; colornum += 1) {
334 + const tracedlayer = this.batchtracepaths(
335 + this.internodes(
336 + this.pathscan(this.layeringstep(ii, colornum), options.pathomit),
337 + options
338 + ),
339 + options.ltres,
340 + options.qtres
341 + );
342 + tracedata.layers.push(tracedlayer);
343 + }
344 + } else {
345 + const ls = this.layering(ii);
346 + if (options.layercontainerid) {
347 + this.drawLayers(ls, this.specpalette, options.scale, options.layercontainerid);
348 + }
349 + const bps = this.batchpathscan(ls, options.pathomit);
350 + const bis = this.batchinternodes(bps, options);
351 + tracedata = {
352 + layers: this.batchtracelayers(bis, options.ltres, options.qtres),
353 + palette: ii.palette,
354 + width: imgd.width,
355 + height: imgd.height,
356 + };
357 + }
358 +
359 + return tracedata;
360 + }
361 +
362 + checkoptions(options) {
363 + options = options || {};
364 + if (typeof options === 'string') {
365 + options = options.toLowerCase();
366 + if (this.optionpresets[options]) {
367 + options = this.optionpresets[options];
368 + } else {
369 + options = {};
370 + }
371 + }
372 + const ok = Object.keys(this.optionpresets['default']);
373 + for (let k = 0; k < ok.length; k += 1) {
374 + if (!options.hasOwnProperty(ok[k])) {
375 + options[ok[k]] = this.optionpresets['default'][ok[k]];
376 + }
377 + }
378 +
379 + return options;
380 + }
381 +
382 + colorquantization(imgd, options) {
383 + const arr = [];
384 + let idx = 0;
385 + let cd;
386 + let cdl;
387 + let ci;
388 + const paletteacc = [];
389 + const pixelnum = imgd.width * imgd.height;
390 + let i;
391 + let j;
392 + let k;
393 + let cnt;
394 + let palette;
395 +
396 + for (j = 0; j < imgd.height + 2; j += 1) {
397 + arr[j] = [];
398 + for (i = 0; i < imgd.width + 2; i += 1) {
399 + arr[j][i] = -1;
400 + }
401 + }
402 + if (options.pal) {
403 + palette = options.pal;
404 + } else if (options.colorsampling === 0) {
405 + palette = this.generatepalette(options.numberofcolors);
406 + } else if (options.colorsampling === 1) {
407 + palette = this.samplepalette(options.numberofcolors, imgd);
408 + } else {
409 + palette = this.samplepalette2(options.numberofcolors, imgd);
410 + }
411 + if (options.blurradius > 0) {
412 + imgd = this.blur(imgd, options.blurradius, options.blurdelta);
413 + }
414 + for (cnt = 0; cnt < options.colorquantcycles; cnt += 1) {
415 + if (cnt > 0) {
416 + for (k = 0; k < palette.length; k += 1) {
417 + if (paletteacc[k].n > 0) {
418 + palette[k] = {
419 + r: Math.floor(paletteacc[k].r / paletteacc[k].n),
420 + g: Math.floor(paletteacc[k].g / paletteacc[k].n),
421 + b: Math.floor(paletteacc[k].b / paletteacc[k].n),
422 + a: Math.floor(paletteacc[k].a / paletteacc[k].n),
423 + };
424 + }
425 +
426 + if (
427 + paletteacc[k].n / pixelnum < options.mincolorratio &&
428 + cnt < options.colorquantcycles - 1
429 + ) {
430 + palette[k] = {
431 + r: Math.floor(Math.random() * 255),
432 + g: Math.floor(Math.random() * 255),
433 + b: Math.floor(Math.random() * 255),
434 + a: Math.floor(Math.random() * 255),
435 + };
436 + }
437 + }
438 + }
439 +
440 + for (i = 0; i < palette.length; i += 1) {
441 + paletteacc[i] = { r: 0, g: 0, b: 0, a: 0, n: 0 };
442 + }
443 +
444 + for (j = 0; j < imgd.height; j += 1) {
445 + for (i = 0; i < imgd.width; i += 1) {
446 + idx = (j * imgd.width + i) * 4;
447 +
448 + ci = 0;
449 + cdl = 1024;
450 + for (k = 0; k < palette.length; k += 1) {
451 + cd =
452 + Math.abs(palette[k].r - imgd.data[idx]) +
453 + Math.abs(palette[k].g - imgd.data[idx + 1]) +
454 + Math.abs(palette[k].b - imgd.data[idx + 2]) +
455 + Math.abs(palette[k].a - imgd.data[idx + 3]);
456 +
457 + if (cd < cdl) {
458 + cdl = cd;
459 + ci = k;
460 + }
461 + }
462 +
463 + paletteacc[ci].r += imgd.data[idx];
464 + paletteacc[ci].g += imgd.data[idx + 1];
465 + paletteacc[ci].b += imgd.data[idx + 2];
466 + paletteacc[ci].a += imgd.data[idx + 3];
467 + paletteacc[ci].n += 1;
468 +
469 + arr[j + 1][i + 1] = ci;
470 + }
471 + }
472 + }
473 +
474 + return { array: arr, palette };
475 + }
476 +
477 + samplepalette(numberofcolors, imgd) {
478 + let idx;
479 + const palette = [];
480 + for (let i = 0; i < numberofcolors; i += 1) {
481 + idx = Math.floor((Math.random() * imgd.data.length) / 4) * 4;
482 + palette.push({
483 + r: imgd.data[idx],
484 + g: imgd.data[idx + 1],
485 + b: imgd.data[idx + 2],
486 + a: imgd.data[idx + 3],
487 + });
488 + }
489 +
490 + return palette;
491 + }
492 +
493 + samplepalette2(numberofcolors, imgd) {
494 + let idx;
495 + const palette = [];
496 + const ni = Math.ceil(Math.sqrt(numberofcolors));
497 + const nj = Math.ceil(numberofcolors / ni);
498 + const vx = imgd.width / (ni + 1);
499 + const vy = imgd.height / (nj + 1);
500 + for (let j = 0; j < nj; j += 1) {
501 + for (let i = 0; i < ni; i += 1) {
502 + if (palette.length === numberofcolors) {
503 + break;
504 + } else {
505 + idx = Math.floor((j + 1) * vy * imgd.width + (i + 1) * vx) * 4;
506 + palette.push({
507 + r: imgd.data[idx],
508 + g: imgd.data[idx + 1],
509 + b: imgd.data[idx + 2],
510 + a: imgd.data[idx + 3],
511 + });
512 + }
513 + }
514 + }
515 +
516 + return palette;
517 + }
518 +
519 + generatepalette(numberofcolors) {
520 + const palette = [];
521 + let rcnt;
522 + let gcnt;
523 + let bcnt;
524 + if (numberofcolors < 8) {
525 + const graystep = Math.floor(255 / (numberofcolors - 1));
526 + for (let i = 0; i < numberofcolors; i += 1) {
527 + palette.push({ r: i * graystep, g: i * graystep, b: i * graystep, a: 255 });
528 + }
529 + } else {
530 + const colorqnum = Math.floor(Math.pow(numberofcolors, 1 / 3));
531 + const colorstep = Math.floor(255 / (colorqnum - 1));
532 + const rndnum = numberofcolors - colorqnum * colorqnum * colorqnum;
533 + for (rcnt = 0; rcnt < colorqnum; rcnt += 1) {
534 + for (gcnt = 0; gcnt < colorqnum; gcnt += 1) {
535 + for (bcnt = 0; bcnt < colorqnum; bcnt += 1) {
536 + palette.push({ r: rcnt * colorstep, g: gcnt * colorstep, b: bcnt * colorstep, a: 255 });
537 + }
538 + }
539 + }
540 + for (rcnt = 0; rcnt < rndnum; rcnt += 1) {
541 + palette.push({
542 + r: Math.floor(Math.random() * 255),
543 + g: Math.floor(Math.random() * 255),
544 + b: Math.floor(Math.random() * 255),
545 + a: Math.floor(Math.random() * 255),
546 + });
547 + }
548 + }
549 +
550 + return palette;
551 + }
552 +
553 + layering(ii) {
554 + const layers = [];
555 + let val = 0;
556 + const ah = ii.array.length;
557 + const aw = ii.array[0].length;
558 + let n1;
559 + let n2;
560 + let n3;
561 + let n4;
562 + let n5;
563 + let n6;
564 + let n7;
565 + let n8;
566 + let i;
567 + let j;
568 + let k;
569 + for (k = 0; k < ii.palette.length; k += 1) {
570 + layers[k] = [];
571 + for (j = 0; j < ah; j += 1) {
572 + layers[k][j] = [];
573 + for (i = 0; i < aw; i += 1) {
574 + layers[k][j][i] = 0;
575 + }
576 + }
577 + }
578 + for (j = 1; j < ah - 1; j += 1) {
579 + for (i = 1; i < aw - 1; i += 1) {
580 + val = ii.array[j][i];
581 +
582 + n1 = ii.array[j - 1][i - 1] === val ? 1 : 0;
583 + n2 = ii.array[j - 1][i] === val ? 1 : 0;
584 + n3 = ii.array[j - 1][i + 1] === val ? 1 : 0;
585 + n4 = ii.array[j][i - 1] === val ? 1 : 0;
586 + n5 = ii.array[j][i + 1] === val ? 1 : 0;
587 + n6 = ii.array[j + 1][i - 1] === val ? 1 : 0;
588 + n7 = ii.array[j + 1][i] === val ? 1 : 0;
589 + n8 = ii.array[j + 1][i + 1] === val ? 1 : 0;
590 +
591 + layers[val][j + 1][i + 1] = 1 + n5 * 2 + n8 * 4 + n7 * 8;
592 + if (!n4) {
593 + layers[val][j + 1][i] = 0 + 2 + n7 * 4 + n6 * 8;
594 + }
595 + if (!n2) {
596 + layers[val][j][i + 1] = 0 + n3 * 2 + n5 * 4 + 8;
597 + }
598 + if (!n1) {
599 + layers[val][j][i] = 0 + n2 * 2 + 4 + n4 * 8;
600 + }
601 + }
602 + }
603 +
604 + return layers;
605 + }
606 +
607 + layeringstep(ii, cnum) {
608 + const layer = [];
609 + const ah = ii.array.length;
610 + const aw = ii.array[0].length;
611 + let i;
612 + let j;
613 + for (j = 0; j < ah; j += 1) {
614 + layer[j] = [];
615 + for (i = 0; i < aw; i += 1) {
616 + layer[j][i] = 0;
617 + }
618 + }
619 + for (j = 1; j < ah; j += 1) {
620 + for (i = 1; i < aw; i += 1) {
621 + layer[j][i] =
622 + (ii.array[j - 1][i - 1] === cnum ? 1 : 0) +
623 + (ii.array[j - 1][i] === cnum ? 2 : 0) +
624 + (ii.array[j][i - 1] === cnum ? 8 : 0) +
625 + (ii.array[j][i] === cnum ? 4 : 0);
626 + }
627 + }
628 +
629 + return layer;
630 + }
631 +
632 + pathscan(arr, pathomit) {
633 + const paths = [];
634 + let pacnt = 0;
635 + let pcnt = 0;
636 + let px = 0;
637 + let py = 0;
638 + const w = arr[0].length;
639 + const h = arr.length;
640 + let dir = 0;
641 + let pathfinished = true;
642 + let holepath = false;
643 + let lookuprow;
644 + for (let j = 0; j < h; j += 1) {
645 + for (let i = 0; i < w; i += 1) {
646 + if (arr[j][i] === 4 || arr[j][i] === 11) {
647 + px = i;
648 + py = j;
649 + paths[pacnt] = {};
650 + paths[pacnt].points = [];
651 + paths[pacnt].boundingbox = [px, py, px, py];
652 + paths[pacnt].holechildren = [];
653 + pathfinished = false;
654 + pcnt = 0;
655 + holepath = arr[j][i] === 11;
656 + dir = 1;
657 +
658 + while (!pathfinished) {
659 + paths[pacnt].points[pcnt] = {};
660 + paths[pacnt].points[pcnt].x = px - 1;
661 + paths[pacnt].points[pcnt].y = py - 1;
662 + paths[pacnt].points[pcnt].t = arr[py][px];
663 +
664 + if (px - 1 < paths[pacnt].boundingbox[0]) {
665 + paths[pacnt].boundingbox[0] = px - 1;
666 + }
667 + if (px - 1 > paths[pacnt].boundingbox[2]) {
668 + paths[pacnt].boundingbox[2] = px - 1;
669 + }
670 + if (py - 1 < paths[pacnt].boundingbox[1]) {
671 + paths[pacnt].boundingbox[1] = py - 1;
672 + }
673 + if (py - 1 > paths[pacnt].boundingbox[3]) {
674 + paths[pacnt].boundingbox[3] = py - 1;
675 + }
676 +
677 + lookuprow = this.pathscan_combined_lookup[arr[py][px]][dir];
678 + arr[py][px] = lookuprow[0];
679 + dir = lookuprow[1];
680 + px += lookuprow[2];
681 + py += lookuprow[3];
682 +
683 + if (px - 1 === paths[pacnt].points[0].x && py - 1 === paths[pacnt].points[0].y) {
684 + pathfinished = true;
685 +
686 + if (paths[pacnt].points.length < pathomit) {
687 + paths.pop();
688 + } else {
689 + paths[pacnt].isholepath = !!holepath;
690 +
691 + if (holepath) {
692 + let parentidx = 0,
693 + parentbbox = [-1, -1, w + 1, h + 1];
694 + for (let parentcnt = 0; parentcnt < pacnt; parentcnt++) {
695 + if (
696 + !paths[parentcnt].isholepath &&
697 + this.boundingboxincludes(
698 + paths[parentcnt].boundingbox,
699 + paths[pacnt].boundingbox
700 + ) &&
701 + this.boundingboxincludes(parentbbox, paths[parentcnt].boundingbox)
702 + ) {
703 + parentidx = parentcnt;
704 + parentbbox = paths[parentcnt].boundingbox;
705 + }
706 + }
707 + paths[parentidx].holechildren.push(pacnt);
708 + }
709 + pacnt += 1;
710 + }
711 + }
712 + pcnt += 1;
713 + }
714 + }
715 + }
716 + }
717 +
718 + return paths;
719 + }
720 +
721 + boundingboxincludes(parentbbox, childbbox) {
722 + return (
723 + parentbbox[0] < childbbox[0] &&
724 + parentbbox[1] < childbbox[1] &&
725 + parentbbox[2] > childbbox[2] &&
726 + parentbbox[3] > childbbox[3]
727 + );
728 + }
729 +
730 + batchpathscan(layers, pathomit) {
731 + const bpaths = [];
732 + for (const k in layers) {
733 + if (!layers.hasOwnProperty(k)) {
734 + continue;
735 + }
736 + bpaths[k] = this.pathscan(layers[k], pathomit);
737 + }
738 +
739 + return bpaths;
740 + }
741 +
742 + internodes(paths, options) {
743 + const ins = [];
744 + let palen = 0;
745 + let nextidx = 0;
746 + let nextidx2 = 0;
747 + let previdx = 0;
748 + let previdx2 = 0;
749 + let pacnt;
750 + let pcnt;
751 + for (pacnt = 0; pacnt < paths.length; pacnt += 1) {
752 + ins[pacnt] = {};
753 + ins[pacnt].points = [];
754 + ins[pacnt].boundingbox = paths[pacnt].boundingbox;
755 + ins[pacnt].holechildren = paths[pacnt].holechildren;
756 + ins[pacnt].isholepath = paths[pacnt].isholepath;
757 + palen = paths[pacnt].points.length;
758 +
759 + for (pcnt = 0; pcnt < palen; pcnt += 1) {
760 + nextidx = (pcnt + 1) % palen;
761 + nextidx2 = (pcnt + 2) % palen;
762 + previdx = (pcnt - 1 + palen) % palen;
763 + previdx2 = (pcnt - 2 + palen) % palen;
764 +
765 + if (
766 + options.rightangleenhance &&
767 + this.testrightangle(paths[pacnt], previdx2, previdx, pcnt, nextidx, nextidx2)
768 + ) {
769 + if (ins[pacnt].points.length > 0) {
770 + ins[pacnt].points[ins[pacnt].points.length - 1].linesegment = this.getdirection(
771 + ins[pacnt].points[ins[pacnt].points.length - 1].x,
772 + ins[pacnt].points[ins[pacnt].points.length - 1].y,
773 + paths[pacnt].points[pcnt].x,
774 + paths[pacnt].points[pcnt].y
775 + );
776 + }
777 +
778 + ins[pacnt].points.push({
779 + x: paths[pacnt].points[pcnt].x,
780 + y: paths[pacnt].points[pcnt].y,
781 + linesegment: this.getdirection(
782 + paths[pacnt].points[pcnt].x,
783 + paths[pacnt].points[pcnt].y,
784 + (paths[pacnt].points[pcnt].x + paths[pacnt].points[nextidx].x) / 2,
785 + (paths[pacnt].points[pcnt].y + paths[pacnt].points[nextidx].y) / 2
786 + ),
787 + });
788 + }
789 +
790 + ins[pacnt].points.push({
791 + x: (paths[pacnt].points[pcnt].x + paths[pacnt].points[nextidx].x) / 2,
792 + y: (paths[pacnt].points[pcnt].y + paths[pacnt].points[nextidx].y) / 2,
793 + linesegment: this.getdirection(
794 + (paths[pacnt].points[pcnt].x + paths[pacnt].points[nextidx].x) / 2,
795 + (paths[pacnt].points[pcnt].y + paths[pacnt].points[nextidx].y) / 2,
796 + (paths[pacnt].points[nextidx].x + paths[pacnt].points[nextidx2].x) / 2,
797 + (paths[pacnt].points[nextidx].y + paths[pacnt].points[nextidx2].y) / 2
798 + ),
799 + });
800 + }
801 + }
802 +
803 + return ins;
804 + }
805 +
806 + testrightangle(path, idx1, idx2, idx3, idx4, idx5) {
807 + return (
808 + (path.points[idx3].x === path.points[idx1].x &&
809 + path.points[idx3].x === path.points[idx2].x &&
810 + path.points[idx3].y === path.points[idx4].y &&
811 + path.points[idx3].y === path.points[idx5].y) ||
812 + (path.points[idx3].y === path.points[idx1].y &&
813 + path.points[idx3].y === path.points[idx2].y &&
814 + path.points[idx3].x === path.points[idx4].x &&
815 + path.points[idx3].x === path.points[idx5].x)
816 + );
817 + }
818 +
819 + getdirection(x1, y1, x2, y2) {
820 + let val = 8;
821 + if (x1 < x2) {
822 + if (y1 < y2) {
823 + val = 1;
824 + } else if (y1 > y2) {
825 + val = 7;
826 + } else {
827 + val = 0;
828 + }
829 + } else if (x1 > x2) {
830 + if (y1 < y2) {
831 + val = 3;
832 + } else if (y1 > y2) {
833 + val = 5;
834 + } else {
835 + val = 4;
836 + }
837 + } else if (y1 < y2) {
838 + val = 2;
839 + } else if (y1 > y2) {
840 + val = 6;
841 + } else {
842 + val = 8;
843 + }
844 +
845 + return val;
846 + }
847 +
848 + batchinternodes(bpaths, options) {
849 + const binternodes = [];
850 + for (const k in bpaths) {
851 + if (!bpaths.hasOwnProperty(k)) {
852 + continue;
853 + }
854 + binternodes[k] = this.internodes(bpaths[k], options);
855 + }
856 +
857 + return binternodes;
858 + }
859 +
860 + tracepath(path, ltres, qtres) {
861 + let pcnt = 0;
862 + let segtype1;
863 + let segtype2;
864 + let seqend;
865 + const smp = {};
866 + smp.segments = [];
867 + smp.boundingbox = path.boundingbox;
868 + smp.holechildren = path.holechildren;
869 + smp.isholepath = path.isholepath;
870 +
871 + while (pcnt < path.points.length) {
872 + segtype1 = path.points[pcnt].linesegment;
873 + segtype2 = -1;
874 + seqend = pcnt + 1;
875 + while (
876 + (path.points[seqend].linesegment === segtype1 ||
877 + path.points[seqend].linesegment === segtype2 ||
878 + segtype2 === -1) &&
879 + seqend < path.points.length - 1
880 + ) {
881 + if (path.points[seqend].linesegment !== segtype1 && segtype2 === -1) {
882 + segtype2 = path.points[seqend].linesegment;
883 + }
884 + seqend += 1;
885 + }
886 + if (seqend === path.points.length - 1) {
887 + seqend = 0;
888 + }
889 +
890 + smp.segments = smp.segments.concat(this.fitseq(path, ltres, qtres, pcnt, seqend));
891 +
892 + if (seqend > 0) {
893 + pcnt = seqend;
894 + } else {
895 + pcnt = path.points.length;
896 + }
897 + }
898 +
899 + return smp;
900 + }
901 +
902 + fitseq(path, ltres, qtres, seqstart, seqend) {
903 + if (seqend > path.points.length || seqend < 0) {
904 + return [];
905 + }
906 + let errorpoint = seqstart,
907 + errorval = 0,
908 + curvepass = true,
909 + px,
910 + py,
911 + dist2;
912 + let tl = seqend - seqstart;
913 + if (tl < 0) {
914 + tl += path.points.length;
915 + }
916 + let vx = (path.points[seqend].x - path.points[seqstart].x) / tl,
917 + vy = (path.points[seqend].y - path.points[seqstart].y) / tl;
918 + let pcnt = (seqstart + 1) % path.points.length,
919 + pl;
920 + while (pcnt != seqend) {
921 + pl = pcnt - seqstart;
922 + if (pl < 0) {
923 + pl += path.points.length;
924 + }
925 + px = path.points[seqstart].x + vx * pl;
926 + py = path.points[seqstart].y + vy * pl;
927 + dist2 =
928 + (path.points[pcnt].x - px) * (path.points[pcnt].x - px) +
929 + (path.points[pcnt].y - py) * (path.points[pcnt].y - py);
930 + if (dist2 > ltres) {
931 + curvepass = false;
932 + }
933 + if (dist2 > errorval) {
934 + errorpoint = pcnt;
935 + errorval = dist2;
936 + }
937 + pcnt = (pcnt + 1) % path.points.length;
938 + }
939 + if (curvepass) {
940 + return [
941 + {
942 + type: 'L',
943 + x1: path.points[seqstart].x,
944 + y1: path.points[seqstart].y,
945 + x2: path.points[seqend].x,
946 + y2: path.points[seqend].y,
947 + },
948 + ];
949 + }
950 + const fitpoint = errorpoint;
951 + curvepass = true;
952 + errorval = 0;
953 + let t = (fitpoint - seqstart) / tl,
954 + t1 = (1 - t) * (1 - t),
955 + t2 = 2 * (1 - t) * t,
956 + t3 = t * t;
957 + let cpx =
958 + (t1 * path.points[seqstart].x + t3 * path.points[seqend].x - path.points[fitpoint].x) / -t2,
959 + cpy =
960 + (t1 * path.points[seqstart].y + t3 * path.points[seqend].y - path.points[fitpoint].y) / -t2;
961 + pcnt = seqstart + 1;
962 + while (pcnt != seqend) {
963 + t = (pcnt - seqstart) / tl;
964 + t1 = (1 - t) * (1 - t);
965 + t2 = 2 * (1 - t) * t;
966 + t3 = t * t;
967 + px = t1 * path.points[seqstart].x + t2 * cpx + t3 * path.points[seqend].x;
968 + py = t1 * path.points[seqstart].y + t2 * cpy + t3 * path.points[seqend].y;
969 + dist2 =
970 + (path.points[pcnt].x - px) * (path.points[pcnt].x - px) +
971 + (path.points[pcnt].y - py) * (path.points[pcnt].y - py);
972 + if (dist2 > qtres) {
973 + curvepass = false;
974 + }
975 + if (dist2 > errorval) {
976 + errorpoint = pcnt;
977 + errorval = dist2;
978 + }
979 + pcnt = (pcnt + 1) % path.points.length;
980 + }
981 + if (curvepass) {
982 + return [
983 + {
984 + type: 'Q',
985 + x1: path.points[seqstart].x,
986 + y1: path.points[seqstart].y,
987 + x2: cpx,
988 + y2: cpy,
989 + x3: path.points[seqend].x,
990 + y3: path.points[seqend].y,
991 + },
992 + ];
993 + }
994 + const splitpoint = fitpoint;
995 +
996 + return this.fitseq(path, ltres, qtres, seqstart, splitpoint).concat(
997 + this.fitseq(path, ltres, qtres, splitpoint, seqend)
998 + );
999 + }
1000 +
1001 + batchtracepaths(internodepaths, ltres, qtres) {
1002 + const btracedpaths = [];
1003 + for (const k in internodepaths) {
1004 + if (!internodepaths.hasOwnProperty(k)) {
1005 + continue;
1006 + }
1007 + btracedpaths.push(this.tracepath(internodepaths[k], ltres, qtres));
1008 + }
1009 +
1010 + return btracedpaths;
1011 + }
1012 +
1013 + batchtracelayers(binternodes, ltres, qtres) {
1014 + const btbis = [];
1015 + for (const k in binternodes) {
1016 + if (!binternodes.hasOwnProperty(k)) {
1017 + continue;
1018 + }
1019 + btbis[k] = this.batchtracepaths(binternodes[k], ltres, qtres);
1020 + }
1021 +
1022 + return btbis;
1023 + }
1024 +
1025 + roundtodec(val, places) {
1026 + return Number(val.toFixed(places));
1027 + }
1028 +
1029 + svgpathstring(tracedata, lnum, pathnum, options) {
1030 + let layer = tracedata.layers[lnum],
1031 + smp = layer[pathnum],
1032 + str = '',
1033 + pcnt;
1034 + if (options.linefilter && smp.segments.length < 3) {
1035 + return str;
1036 + }
1037 + str = `<path ${options.desc ? `desc="l ${lnum} p ${pathnum}" ` : ''}${this.tosvgcolorstr(
1038 + tracedata.palette[lnum],
1039 + options
1040 + )}d="`;
1041 + if (options.roundcoords === -1) {
1042 + str += `M ${smp.segments[0].x1 * options.scale} ${smp.segments[0].y1 * options.scale} `;
1043 + for (pcnt = 0; pcnt < smp.segments.length; pcnt++) {
1044 + str += `${smp.segments[pcnt].type} ${smp.segments[pcnt].x2 * options.scale} ${
1045 + smp.segments[pcnt].y2 * options.scale
1046 + } `;
1047 + if (smp.segments[pcnt].hasOwnProperty('x3')) {
1048 + str += `${smp.segments[pcnt].x3 * options.scale} ${
1049 + smp.segments[pcnt].y3 * options.scale
1050 + } `;
1051 + }
1052 + }
1053 + str += 'Z ';
1054 + } else {
1055 + str += `M ${this.roundtodec(
1056 + smp.segments[0].x1 * options.scale,
1057 + options.roundcoords
1058 + )} ${this.roundtodec(smp.segments[0].y1 * options.scale, options.roundcoords)} `;
1059 + for (pcnt = 0; pcnt < smp.segments.length; pcnt++) {
1060 + str += `${smp.segments[pcnt].type} ${this.roundtodec(
1061 + smp.segments[pcnt].x2 * options.scale,
1062 + options.roundcoords
1063 + )} ${this.roundtodec(smp.segments[pcnt].y2 * options.scale, options.roundcoords)} `;
1064 + if (smp.segments[pcnt].hasOwnProperty('x3')) {
1065 + str += `${this.roundtodec(
1066 + smp.segments[pcnt].x3 * options.scale,
1067 + options.roundcoords
1068 + )} ${this.roundtodec(smp.segments[pcnt].y3 * options.scale, options.roundcoords)} `;
1069 + }
1070 + }
1071 + str += 'Z ';
1072 + }
1073 + for (var hcnt = 0; hcnt < smp.holechildren.length; hcnt++) {
1074 + var hsmp = layer[smp.holechildren[hcnt]];
1075 +
1076 + if (options.roundcoords === -1) {
1077 + if (hsmp.segments[hsmp.segments.length - 1].hasOwnProperty('x3')) {
1078 + str += `M ${hsmp.segments[hsmp.segments.length - 1].x3 * options.scale} ${
1079 + hsmp.segments[hsmp.segments.length - 1].y3 * options.scale
1080 + } `;
1081 + } else {
1082 + str += `M ${hsmp.segments[hsmp.segments.length - 1].x2 * options.scale} ${
1083 + hsmp.segments[hsmp.segments.length - 1].y2 * options.scale
1084 + } `;
1085 + }
1086 + for (pcnt = hsmp.segments.length - 1; pcnt >= 0; pcnt--) {
1087 + str += `${hsmp.segments[pcnt].type} `;
1088 + if (hsmp.segments[pcnt].hasOwnProperty('x3')) {
1089 + str += `${hsmp.segments[pcnt].x2 * options.scale} ${
1090 + hsmp.segments[pcnt].y2 * options.scale
1091 + } `;
1092 + }
1093 + str += `${hsmp.segments[pcnt].x1 * options.scale} ${
1094 + hsmp.segments[pcnt].y1 * options.scale
1095 + } `;
1096 + }
1097 + } else {
1098 + if (hsmp.segments[hsmp.segments.length - 1].hasOwnProperty('x3')) {
1099 + str += `M ${this.roundtodec(
1100 + hsmp.segments[hsmp.segments.length - 1].x3 * options.scale
1101 + )} ${this.roundtodec(hsmp.segments[hsmp.segments.length - 1].y3 * options.scale)} `;
1102 + } else {
1103 + str += `M ${this.roundtodec(
1104 + hsmp.segments[hsmp.segments.length - 1].x2 * options.scale
1105 + )} ${this.roundtodec(hsmp.segments[hsmp.segments.length - 1].y2 * options.scale)} `;
1106 + }
1107 + for (pcnt = hsmp.segments.length - 1; pcnt >= 0; pcnt--) {
1108 + str += `${hsmp.segments[pcnt].type} `;
1109 + if (hsmp.segments[pcnt].hasOwnProperty('x3')) {
1110 + str += `${this.roundtodec(hsmp.segments[pcnt].x2 * options.scale)} ${this.roundtodec(
1111 + hsmp.segments[pcnt].y2 * options.scale
1112 + )} `;
1113 + }
1114 + str += `${this.roundtodec(hsmp.segments[pcnt].x1 * options.scale)} ${this.roundtodec(
1115 + hsmp.segments[pcnt].y1 * options.scale
1116 + )} `;
1117 + }
1118 + }
1119 + str += 'Z ';
1120 + }
1121 + str += '" />';
1122 + if (options.lcpr || options.qcpr) {
1123 + for (pcnt = 0; pcnt < smp.segments.length; pcnt++) {
1124 + if (smp.segments[pcnt].hasOwnProperty('x3') && options.qcpr) {
1125 + str += `<circle cx="${smp.segments[pcnt].x2 * options.scale}" cy="${
1126 + smp.segments[pcnt].y2 * options.scale
1127 + }" r="${options.qcpr}" fill="cyan" stroke-width="${
1128 + options.qcpr * 0.2
1129 + }" stroke="black" />`;
1130 + str += `<circle cx="${smp.segments[pcnt].x3 * options.scale}" cy="${
1131 + smp.segments[pcnt].y3 * options.scale
1132 + }" r="${options.qcpr}" fill="white" stroke-width="${
1133 + options.qcpr * 0.2
1134 + }" stroke="black" />`;
1135 + str += `<line x1="${smp.segments[pcnt].x1 * options.scale}" y1="${
1136 + smp.segments[pcnt].y1 * options.scale
1137 + }" x2="${smp.segments[pcnt].x2 * options.scale}" y2="${
1138 + smp.segments[pcnt].y2 * options.scale
1139 + }" stroke-width="${options.qcpr * 0.2}" stroke="cyan" />`;
1140 + str += `<line x1="${smp.segments[pcnt].x2 * options.scale}" y1="${
1141 + smp.segments[pcnt].y2 * options.scale
1142 + }" x2="${smp.segments[pcnt].x3 * options.scale}" y2="${
1143 + smp.segments[pcnt].y3 * options.scale
1144 + }" stroke-width="${options.qcpr * 0.2}" stroke="cyan" />`;
1145 + }
1146 + if (!smp.segments[pcnt].hasOwnProperty('x3') && options.lcpr) {
1147 + str += `<circle cx="${smp.segments[pcnt].x2 * options.scale}" cy="${
1148 + smp.segments[pcnt].y2 * options.scale
1149 + }" r="${options.lcpr}" fill="white" stroke-width="${
1150 + options.lcpr * 0.2
1151 + }" stroke="black" />`;
1152 + }
1153 + }
1154 +
1155 + for (var hcnt = 0; hcnt < smp.holechildren.length; hcnt++) {
1156 + var hsmp = layer[smp.holechildren[hcnt]];
1157 + for (pcnt = 0; pcnt < hsmp.segments.length; pcnt++) {
1158 + if (hsmp.segments[pcnt].hasOwnProperty('x3') && options.qcpr) {
1159 + str += `<circle cx="${hsmp.segments[pcnt].x2 * options.scale}" cy="${
1160 + hsmp.segments[pcnt].y2 * options.scale
1161 + }" r="${options.qcpr}" fill="cyan" stroke-width="${
1162 + options.qcpr * 0.2
1163 + }" stroke="black" />`;
1164 + str += `<circle cx="${hsmp.segments[pcnt].x3 * options.scale}" cy="${
1165 + hsmp.segments[pcnt].y3 * options.scale
1166 + }" r="${options.qcpr}" fill="white" stroke-width="${
1167 + options.qcpr * 0.2
1168 + }" stroke="black" />`;
1169 + str += `<line x1="${hsmp.segments[pcnt].x1 * options.scale}" y1="${
1170 + hsmp.segments[pcnt].y1 * options.scale
1171 + }" x2="${hsmp.segments[pcnt].x2 * options.scale}" y2="${
1172 + hsmp.segments[pcnt].y2 * options.scale
1173 + }" stroke-width="${options.qcpr * 0.2}" stroke="cyan" />`;
1174 + str += `<line x1="${hsmp.segments[pcnt].x2 * options.scale}" y1="${
1175 + hsmp.segments[pcnt].y2 * options.scale
1176 + }" x2="${hsmp.segments[pcnt].x3 * options.scale}" y2="${
1177 + hsmp.segments[pcnt].y3 * options.scale
1178 + }" stroke-width="${options.qcpr * 0.2}" stroke="cyan" />`;
1179 + }
1180 + if (!hsmp.segments[pcnt].hasOwnProperty('x3') && options.lcpr) {
1181 + str += `<circle cx="${hsmp.segments[pcnt].x2 * options.scale}" cy="${
1182 + hsmp.segments[pcnt].y2 * options.scale
1183 + }" r="${options.lcpr}" fill="white" stroke-width="${
1184 + options.lcpr * 0.2
1185 + }" stroke="black" />`;
1186 + }
1187 + }
1188 + }
1189 + }
1190 +
1191 + return str;
1192 + }
1193 +
1194 + getsvgstring(tracedata, options) {
1195 + options = this.checkoptions(options);
1196 + const w = tracedata.width * options.scale;
1197 + const h = tracedata.height * options.scale;
1198 +
1199 + let svgstr = `<svg ${
1200 + options.viewbox ? `viewBox="0 0 ${w} ${h}" ` : `width="${w}" height="${h}" `
1201 + }version="1.1" xmlns="http://www.w3.org/2000/svg" desc="Created with imagetracer.js version ${
1202 + this.versionnumber
1203 + }" >`;
1204 + for (let lcnt = 0; lcnt < tracedata.layers.length; lcnt += 1) {
1205 + for (let pcnt = 0; pcnt < tracedata.layers[lcnt].length; pcnt += 1) {
1206 + if (!tracedata.layers[lcnt][pcnt].isholepath) {
1207 + svgstr += this.svgpathstring(tracedata, lcnt, pcnt, options);
1208 + }
1209 + }
1210 + }
1211 + svgstr += '</svg>';
1212 +
1213 + return svgstr;
1214 + }
1215 +
1216 + compareNumbers(a, b) {
1217 + return a - b;
1218 + }
1219 +
1220 + torgbastr(c) {
1221 + return `rgba(${c.r},${c.g},${c.b},${c.a})`;
1222 + }
1223 +
1224 + tosvgcolorstr(c, options) {
1225 + return `fill="rgb(${c.r},${c.g},${c.b})" stroke="rgb(${c.r},${c.g},${c.b})" stroke-width="${
1226 + options.strokewidth
1227 + }" opacity="${c.a / 255.0}" `;
1228 + }
1229 +
1230 + appendSVGString(svgstr, parentid) {
1231 + let div;
1232 + if (parentid) {
1233 + div = document.getElementById(parentid);
1234 + if (!div) {
1235 + div = document.createElement('div');
1236 + div.id = parentid;
1237 + document.body.appendChild(div);
1238 + }
1239 + } else {
1240 + div = document.createElement('div');
1241 + document.body.appendChild(div);
1242 + }
1243 + div.innerHTML += svgstr;
1244 + }
1245 +
1246 + blur(imgd, radius, delta) {
1247 + let i, j, k, d, idx, racc, gacc, bacc, aacc, wacc;
1248 + const imgd2 = { width: imgd.width, height: imgd.height, data: [] };
1249 + radius = Math.floor(radius);
1250 + if (radius < 1) {
1251 + return imgd;
1252 + }
1253 + if (radius > 5) {
1254 + radius = 5;
1255 + }
1256 + delta = Math.abs(delta);
1257 + if (delta > 1024) {
1258 + delta = 1024;
1259 + }
1260 + const thisgk = this.gks[radius - 1];
1261 + for (j = 0; j < imgd.height; j++) {
1262 + for (i = 0; i < imgd.width; i++) {
1263 + racc = 0;
1264 + gacc = 0;
1265 + bacc = 0;
1266 + aacc = 0;
1267 + wacc = 0;
1268 +
1269 + for (k = -radius; k < radius + 1; k++) {
1270 + if (i + k > 0 && i + k < imgd.width) {
1271 + idx = (j * imgd.width + i + k) * 4;
1272 + racc += imgd.data[idx] * thisgk[k + radius];
1273 + gacc += imgd.data[idx + 1] * thisgk[k + radius];
1274 + bacc += imgd.data[idx + 2] * thisgk[k + radius];
1275 + aacc += imgd.data[idx + 3] * thisgk[k + radius];
1276 + wacc += thisgk[k + radius];
1277 + }
1278 + }
1279 +
1280 + idx = (j * imgd.width + i) * 4;
1281 + imgd2.data[idx] = Math.floor(racc / wacc);
1282 + imgd2.data[idx + 1] = Math.floor(gacc / wacc);
1283 + imgd2.data[idx + 2] = Math.floor(bacc / wacc);
1284 + imgd2.data[idx + 3] = Math.floor(aacc / wacc);
1285 + }
1286 + }
1287 + const himgd = new Uint8ClampedArray(imgd2.data);
1288 + for (j = 0; j < imgd.height; j++) {
1289 + for (i = 0; i < imgd.width; i++) {
1290 + racc = 0;
1291 + gacc = 0;
1292 + bacc = 0;
1293 + aacc = 0;
1294 + wacc = 0;
1295 +
1296 + for (k = -radius; k < radius + 1; k++) {
1297 + if (j + k > 0 && j + k < imgd.height) {
1298 + idx = ((j + k) * imgd.width + i) * 4;
1299 + racc += himgd[idx] * thisgk[k + radius];
1300 + gacc += himgd[idx + 1] * thisgk[k + radius];
1301 + bacc += himgd[idx + 2] * thisgk[k + radius];
1302 + aacc += himgd[idx + 3] * thisgk[k + radius];
1303 + wacc += thisgk[k + radius];
1304 + }
1305 + }
1306 +
1307 + idx = (j * imgd.width + i) * 4;
1308 + imgd2.data[idx] = Math.floor(racc / wacc);
1309 + imgd2.data[idx + 1] = Math.floor(gacc / wacc);
1310 + imgd2.data[idx + 2] = Math.floor(bacc / wacc);
1311 + imgd2.data[idx + 3] = Math.floor(aacc / wacc);
1312 + }
1313 + }
1314 + for (j = 0; j < imgd.height; j++) {
1315 + for (i = 0; i < imgd.width; i++) {
1316 + idx = (j * imgd.width + i) * 4;
1317 +
1318 + d =
1319 + Math.abs(imgd2.data[idx] - imgd.data[idx]) +
1320 + Math.abs(imgd2.data[idx + 1] - imgd.data[idx + 1]) +
1321 + Math.abs(imgd2.data[idx + 2] - imgd.data[idx + 2]) +
1322 + Math.abs(imgd2.data[idx + 3] - imgd.data[idx + 3]);
1323 +
1324 + if (d > delta) {
1325 + imgd2.data[idx] = imgd.data[idx];
1326 + imgd2.data[idx + 1] = imgd.data[idx + 1];
1327 + imgd2.data[idx + 2] = imgd.data[idx + 2];
1328 + imgd2.data[idx + 3] = imgd.data[idx + 3];
1329 + }
1330 + }
1331 + }
1332 +
1333 + return imgd2;
1334 + }
1335 +
1336 + loadImage(url, callback, options) {
1337 + const img = new Image();
1338 + if (options && options.corsenabled) {
1339 + img.crossOrigin = 'Anonymous';
1340 + }
1341 + img.src = url;
1342 + img.onload = function () {
1343 + const canvas = document.createElement('canvas');
1344 + canvas.width = img.width;
1345 + canvas.height = img.height;
1346 + const context = canvas.getContext('2d');
1347 + context.drawImage(img, 0, 0);
1348 + callback(canvas);
1349 + };
1350 + }
1351 +
1352 + getImgdata(canvas) {
1353 + const context = canvas.getContext('2d');
1354 +
1355 + return context.getImageData(0, 0, canvas.width, canvas.height);
1356 + }
1357 +
1358 + drawLayers(layers, palette, scale, parentid) {
1359 + scale = scale || 1;
1360 + let w, h, i, j, k;
1361 + let div;
1362 + if (parentid) {
1363 + div = document.getElementById(parentid);
1364 + if (!div) {
1365 + div = document.createElement('div');
1366 + div.id = parentid;
1367 + document.body.appendChild(div);
1368 + }
1369 + } else {
1370 + div = document.createElement('div');
1371 + document.body.appendChild(div);
1372 + }
1373 + for (k in layers) {
1374 + if (!layers.hasOwnProperty(k)) {
1375 + continue;
1376 + }
1377 +
1378 + w = layers[k][0].length;
1379 + h = layers[k].length;
1380 +
1381 + const canvas = document.createElement('canvas');
1382 + canvas.width = w * scale;
1383 + canvas.height = h * scale;
1384 + const context = canvas.getContext('2d');
1385 +
1386 + for (j = 0; j < h; j += 1) {
1387 + for (i = 0; i < w; i += 1) {
1388 + context.fillStyle = this.torgbastr(palette[layers[k][j][i] % palette.length]);
1389 + context.fillRect(i * scale, j * scale, scale, scale);
1390 + }
1391 + }
1392 +
1393 + div.appendChild(canvas);
1394 + }
1395 + }
1396 +}
1 +/**
2 + * @author NHN. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Selection modification helper
4 + */
5 +
6 +import { extend } from 'tui-code-snippet/src/js/object';
7 +
8 +/**
9 + * Cached selection's info
10 + * @type {Array}
11 + * @private
12 + */
13 +let cachedUndoDataForChangeDimension = null;
14 +
15 +/**
16 + * Set cached undo data
17 + * @param {Array} undoData - selection object
18 + * @private
19 + */
20 +export function setCachedUndoDataForDimension(undoData) {
21 + cachedUndoDataForChangeDimension = undoData;
22 +}
23 +
24 +/**
25 + * Get cached undo data
26 + * @returns {Object} cached undo data
27 + * @private
28 + */
29 +export function getCachedUndoDataForDimension() {
30 + return cachedUndoDataForChangeDimension;
31 +}
32 +
33 +/**
34 + * Make undo data
35 + * @param {fabric.Object} obj - selection object
36 + * @param {Function} undoDatumMaker - make undo datum
37 + * @returns {Array} undoData
38 + * @private
39 + */
40 +export function makeSelectionUndoData(obj, undoDatumMaker) {
41 + let undoData;
42 +
43 + if (obj.type === 'activeSelection') {
44 + undoData = obj.getObjects().map((item) => {
45 + const { angle, left, top, scaleX, scaleY, width, height } = item;
46 +
47 + obj.realizeTransform(item);
48 + const result = undoDatumMaker(item);
49 +
50 + item.set({
51 + angle,
52 + left,
53 + top,
54 + width,
55 + height,
56 + scaleX,
57 + scaleY,
58 + });
59 +
60 + return result;
61 + });
62 + } else {
63 + undoData = [undoDatumMaker(obj)];
64 + }
65 +
66 + return undoData;
67 +}
68 +
69 +/**
70 + * Make undo datum
71 + * @param {number} id - object id
72 + * @param {fabric.Object} obj - selection object
73 + * @param {boolean} isSelection - whether or not object is selection
74 + * @returns {Object} undo datum
75 + * @private
76 + */
77 +export function makeSelectionUndoDatum(id, obj, isSelection) {
78 + return isSelection
79 + ? {
80 + id,
81 + width: obj.width,
82 + height: obj.height,
83 + top: obj.top,
84 + left: obj.left,
85 + angle: obj.angle,
86 + scaleX: obj.scaleX,
87 + scaleY: obj.scaleY,
88 + }
89 + : extend({ id }, obj);
90 +}
1 +/**
2 + * @author NHN. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Shape resize helper
4 + */
5 +import { forEach, map, extend } from 'tui-code-snippet';
6 +import { capitalizeString, flipObject, setCustomProperty, getCustomProperty } from '../util';
7 +import resizeHelper from '../helper/shapeResizeHelper';
8 +
9 +const FILTER_OPTION_MAP = {
10 + pixelate: 'blocksize',
11 + blur: 'blur',
12 +};
13 +const POSITION_DIMENSION_MAP = {
14 + x: 'width',
15 + y: 'height',
16 +};
17 +
18 +const FILTER_NAME_VALUE_MAP = flipObject(FILTER_OPTION_MAP);
19 +
20 +/**
21 + * Cached canvas image element for fill image
22 + * @type {boolean}
23 + * @private
24 + */
25 +let cachedCanvasImageElement = null;
26 +
27 +/**
28 + * Get background image of fill
29 + * @param {fabric.Object} shapeObj - Shape object
30 + * @returns {fabric.Image}
31 + * @private
32 + */
33 +export function getFillImageFromShape(shapeObj) {
34 + const { patternSourceCanvas } = getCustomProperty(shapeObj, 'patternSourceCanvas');
35 + const [fillImage] = patternSourceCanvas.getObjects();
36 +
37 + return fillImage;
38 +}
39 +
40 +/**
41 + * Reset the image position in the filter type fill area.
42 + * @param {fabric.Object} shapeObj - Shape object
43 + * @private
44 + */
45 +export function rePositionFilterTypeFillImage(shapeObj) {
46 + const { angle, flipX, flipY } = shapeObj;
47 + const fillImage = getFillImageFromShape(shapeObj);
48 + const rotatedShapeCornerDimension = getRotatedDimension(shapeObj);
49 + const { right, bottom } = rotatedShapeCornerDimension;
50 + let { width, height } = rotatedShapeCornerDimension;
51 + const diffLeft = (width - shapeObj.width) / 2;
52 + const diffTop = (height - shapeObj.height) / 2;
53 + const cropX = shapeObj.left - shapeObj.width / 2 - diffLeft;
54 + const cropY = shapeObj.top - shapeObj.height / 2 - diffTop;
55 + let left = width / 2 - diffLeft;
56 + let top = height / 2 - diffTop;
57 + const fillImageMaxSize = Math.max(width, height) + Math.max(diffLeft, diffTop);
58 +
59 + [left, top, width, height] = calculateFillImageDimensionOutsideCanvas({
60 + shapeObj,
61 + left,
62 + top,
63 + width,
64 + height,
65 + cropX,
66 + cropY,
67 + flipX,
68 + flipY,
69 + right,
70 + bottom,
71 + });
72 +
73 + fillImage.set({
74 + angle: flipX === flipY ? -angle : angle,
75 + left,
76 + top,
77 + width,
78 + height,
79 + cropX,
80 + cropY,
81 + flipX,
82 + flipY,
83 + });
84 +
85 + setCustomProperty(fillImage, { fillImageMaxSize });
86 +}
87 +
88 +/**
89 + * Make filter option from fabric image
90 + * @param {fabric.Image} imageObject - fabric image object
91 + * @returns {object}
92 + */
93 +export function makeFilterOptionFromFabricImage(imageObject) {
94 + return map(imageObject.filters, (filter) => {
95 + const [key] = Object.keys(filter);
96 +
97 + return {
98 + [FILTER_NAME_VALUE_MAP[key]]: filter[key],
99 + };
100 + });
101 +}
102 +
103 +/**
104 + * Calculate fill image position and size for out of Canvas
105 + * @param {Object} options - options for position dimension calculate
106 + * @param {fabric.Object} shapeObj - shape object
107 + * @param {number} left - original left position
108 + * @param {number} top - original top position
109 + * @param {number} width - image width
110 + * @param {number} height - image height
111 + * @param {number} cropX - image cropX
112 + * @param {number} cropY - image cropY
113 + * @param {boolean} flipX - shape flipX
114 + * @param {boolean} flipY - shape flipY
115 + * @returns {Object}
116 + */
117 +function calculateFillImageDimensionOutsideCanvas({
118 + shapeObj,
119 + left,
120 + top,
121 + width,
122 + height,
123 + cropX,
124 + cropY,
125 + flipX,
126 + flipY,
127 + right,
128 + bottom,
129 +}) {
130 + const overflowAreaPositionFixer = (type, outDistance, imageLeft, imageTop) =>
131 + calculateDistanceOverflowPart({
132 + type,
133 + outDistance,
134 + shapeObj,
135 + flipX,
136 + flipY,
137 + left: imageLeft,
138 + top: imageTop,
139 + });
140 + const [originalWidth, originalHeight] = [width, height];
141 +
142 + [left, top, width, height] = calculateDimensionLeftTopEdge(overflowAreaPositionFixer, {
143 + left,
144 + top,
145 + width,
146 + height,
147 + cropX,
148 + cropY,
149 + });
150 +
151 + [left, top, width, height] = calculateDimensionRightBottomEdge(overflowAreaPositionFixer, {
152 + left,
153 + top,
154 + insideCanvasRealImageWidth: width,
155 + insideCanvasRealImageHeight: height,
156 + right,
157 + bottom,
158 + cropX,
159 + cropY,
160 + originalWidth,
161 + originalHeight,
162 + });
163 +
164 + return [left, top, width, height];
165 +}
166 +
167 +/**
168 + * Calculate fill image position and size for for right bottom edge
169 + * @param {Function} overflowAreaPositionFixer - position fixer
170 + * @param {Object} options - options for position dimension calculate
171 + * @param {fabric.Object} shapeObj - shape object
172 + * @param {number} left - original left position
173 + * @param {number} top - original top position
174 + * @param {number} width - image width
175 + * @param {number} height - image height
176 + * @param {number} right - image right
177 + * @param {number} bottom - image bottom
178 + * @param {number} cropX - image cropX
179 + * @param {number} cropY - image cropY
180 + * @param {boolean} originalWidth - image original width
181 + * @param {boolean} originalHeight - image original height
182 + * @returns {Object}
183 + */
184 +function calculateDimensionRightBottomEdge(
185 + overflowAreaPositionFixer,
186 + {
187 + left,
188 + top,
189 + insideCanvasRealImageWidth,
190 + insideCanvasRealImageHeight,
191 + right,
192 + bottom,
193 + cropX,
194 + cropY,
195 + originalWidth,
196 + originalHeight,
197 + }
198 +) {
199 + let [width, height] = [insideCanvasRealImageWidth, insideCanvasRealImageHeight];
200 + const { width: canvasWidth, height: canvasHeight } = cachedCanvasImageElement;
201 +
202 + if (right > canvasWidth && cropX > 0) {
203 + width = originalWidth - Math.abs(right - canvasWidth);
204 + }
205 + if (bottom > canvasHeight && cropY > 0) {
206 + height = originalHeight - Math.abs(bottom - canvasHeight);
207 + }
208 +
209 + const diff = {
210 + x: (insideCanvasRealImageWidth - width) / 2,
211 + y: (insideCanvasRealImageHeight - height) / 2,
212 + };
213 +
214 + forEach(['x', 'y'], (type) => {
215 + const cropDistance2 = diff[type];
216 + if (cropDistance2 > 0) {
217 + [left, top] = overflowAreaPositionFixer(type, cropDistance2, left, top);
218 + }
219 + });
220 +
221 + return [left, top, width, height];
222 +}
223 +
224 +/**
225 + * Calculate fill image position and size for for left top
226 + * @param {Function} overflowAreaPositionFixer - position fixer
227 + * @param {Object} options - options for position dimension calculate
228 + * @param {fabric.Object} shapeObj - shape object
229 + * @param {number} left - original left position
230 + * @param {number} top - original top position
231 + * @param {number} width - image width
232 + * @param {number} height - image height
233 + * @param {number} cropX - image cropX
234 + * @param {number} cropY - image cropY
235 + * @returns {Object}
236 + */
237 +function calculateDimensionLeftTopEdge(
238 + overflowAreaPositionFixer,
239 + { left, top, width, height, cropX, cropY }
240 +) {
241 + const dimension = {
242 + width,
243 + height,
244 + };
245 +
246 + forEach(['x', 'y'], (type) => {
247 + const cropDistance = type === 'x' ? cropX : cropY;
248 + const compareSize = dimension[POSITION_DIMENSION_MAP[type]];
249 + const standardSize = cachedCanvasImageElement[POSITION_DIMENSION_MAP[type]];
250 +
251 + if (compareSize > standardSize) {
252 + const outDistance = (compareSize - standardSize) / 2;
253 +
254 + dimension[POSITION_DIMENSION_MAP[type]] = standardSize;
255 + [left, top] = overflowAreaPositionFixer(type, outDistance, left, top);
256 + }
257 + if (cropDistance < 0) {
258 + [left, top] = overflowAreaPositionFixer(type, cropDistance, left, top);
259 + }
260 + });
261 +
262 + return [left, top, dimension.width, dimension.height];
263 +}
264 +
265 +/**
266 + * Make fill property of dynamic pattern type
267 + * @param {fabric.Image} canvasImage - canvas background image
268 + * @param {Array} filterOption - filter option
269 + * @param {fabric.StaticCanvas} patternSourceCanvas - fabric static canvas
270 + * @returns {Object}
271 + */
272 +export function makeFillPatternForFilter(canvasImage, filterOption, patternSourceCanvas) {
273 + const copiedCanvasElement = getCachedCanvasImageElement(canvasImage);
274 + const fillImage = makeFillImage(copiedCanvasElement, canvasImage.angle, filterOption);
275 + patternSourceCanvas.add(fillImage);
276 +
277 + const fabricProperty = {
278 + fill: new fabric.Pattern({
279 + source: patternSourceCanvas.getElement(),
280 + repeat: 'no-repeat',
281 + }),
282 + };
283 +
284 + setCustomProperty(fabricProperty, { patternSourceCanvas });
285 +
286 + return fabricProperty;
287 +}
288 +
289 +/**
290 + * Reset fill pattern canvas
291 + * @param {fabric.StaticCanvas} patternSourceCanvas - fabric static canvas
292 + */
293 +export function resetFillPatternCanvas(patternSourceCanvas) {
294 + const [innerImage] = patternSourceCanvas.getObjects();
295 + let { fillImageMaxSize } = getCustomProperty(innerImage, 'fillImageMaxSize');
296 + fillImageMaxSize = Math.max(1, fillImageMaxSize);
297 +
298 + patternSourceCanvas.setDimensions({
299 + width: fillImageMaxSize,
300 + height: fillImageMaxSize,
301 + });
302 + patternSourceCanvas.renderAll();
303 +}
304 +
305 +/**
306 + * Remake filter pattern image source
307 + * @param {fabric.Object} shapeObj - Shape object
308 + * @param {fabric.Image} canvasImage - canvas background image
309 + */
310 +export function reMakePatternImageSource(shapeObj, canvasImage) {
311 + const { patternSourceCanvas } = getCustomProperty(shapeObj, 'patternSourceCanvas');
312 + const [fillImage] = patternSourceCanvas.getObjects();
313 + const filterOption = makeFilterOptionFromFabricImage(fillImage);
314 +
315 + patternSourceCanvas.remove(fillImage);
316 +
317 + const copiedCanvasElement = getCachedCanvasImageElement(canvasImage, true);
318 + const newFillImage = makeFillImage(copiedCanvasElement, canvasImage.angle, filterOption);
319 +
320 + patternSourceCanvas.add(newFillImage);
321 +}
322 +
323 +/**
324 + * Calculate a point line outside the canvas.
325 + * @param {fabric.Image} canvasImage - canvas background image
326 + * @param {boolean} reset - default is false
327 + * @returns {HTMLImageElement}
328 + */
329 +export function getCachedCanvasImageElement(canvasImage, reset = false) {
330 + if (!cachedCanvasImageElement || reset) {
331 + cachedCanvasImageElement = canvasImage.toCanvasElement();
332 + }
333 +
334 + return cachedCanvasImageElement;
335 +}
336 +
337 +/**
338 + * Calculate fill image position for out of Canvas
339 + * @param {string} type - 'x' or 'y'
340 + * @param {fabric.Object} shapeObj - shape object
341 + * @param {number} outDistance - distance away
342 + * @param {number} left - original left position
343 + * @param {number} top - original top position
344 + * @returns {Array}
345 + */
346 +function calculateDistanceOverflowPart({ type, shapeObj, outDistance, left, top, flipX, flipY }) {
347 + const shapePointNavigation = getShapeEdgePoint(shapeObj);
348 + const shapeNeighborPointNavigation = [
349 + [1, 2],
350 + [0, 3],
351 + [0, 3],
352 + [1, 2],
353 + ];
354 + const linePointsOutsideCanvas = calculateLinePointsOutsideCanvas(
355 + type,
356 + shapePointNavigation,
357 + shapeNeighborPointNavigation
358 + );
359 + const reatAngles = calculateLineAngleOfOutsideCanvas(
360 + type,
361 + shapePointNavigation,
362 + linePointsOutsideCanvas
363 + );
364 + const { startPointIndex } = linePointsOutsideCanvas;
365 + const diffPosition = getReversePositionForFlip({
366 + outDistance,
367 + startPointIndex,
368 + flipX,
369 + flipY,
370 + reatAngles,
371 + });
372 +
373 + return [left + diffPosition.left, top + diffPosition.top];
374 +}
375 +
376 +/**
377 + * Calculate fill image position for out of Canvas
378 + * @param {number} outDistance - distance away
379 + * @param {boolean} flipX - flip x statux
380 + * @param {boolean} flipY - flip y statux
381 + * @param {Array} reatAngles - Line angle of the rectangle vertex.
382 + * @returns {Object} diffPosition
383 + */
384 +function getReversePositionForFlip({ outDistance, startPointIndex, flipX, flipY, reatAngles }) {
385 + const rotationChangePoint1 = outDistance * Math.cos((reatAngles[0] * Math.PI) / 180);
386 + const rotationChangePoint2 = outDistance * Math.cos((reatAngles[1] * Math.PI) / 180);
387 + const isForward = startPointIndex === 2 || startPointIndex === 3;
388 + const diffPosition = {
389 + top: isForward ? rotationChangePoint1 : rotationChangePoint2,
390 + left: isForward ? rotationChangePoint2 : rotationChangePoint1,
391 + };
392 +
393 + if (isReverseLeftPositionForFlip(startPointIndex, flipX, flipY)) {
394 + diffPosition.left = diffPosition.left * -1;
395 + }
396 + if (isReverseTopPositionForFlip(startPointIndex, flipX, flipY)) {
397 + diffPosition.top = diffPosition.top * -1;
398 + }
399 +
400 + return diffPosition;
401 +}
402 +
403 +/**
404 + * Calculate a point line outside the canvas.
405 + * @param {string} type - 'x' or 'y'
406 + * @param {Array} shapePointNavigation - shape edge positions
407 + * @param {Object} shapePointNavigation.lefttop - left top position
408 + * @param {Object} shapePointNavigation.righttop - right top position
409 + * @param {Object} shapePointNavigation.leftbottom - lefttop position
410 + * @param {Object} shapePointNavigation.rightbottom - rightbottom position
411 + * @param {Array} shapeNeighborPointNavigation - Array to find adjacent edges.
412 + * @returns {Object}
413 + */
414 +function calculateLinePointsOutsideCanvas(
415 + type,
416 + shapePointNavigation,
417 + shapeNeighborPointNavigation
418 +) {
419 + let minimumPoint = 0;
420 + let minimumPointIndex = 0;
421 + forEach(shapePointNavigation, (point, index) => {
422 + if (point[type] < minimumPoint) {
423 + minimumPoint = point[type];
424 + minimumPointIndex = index;
425 + }
426 + });
427 +
428 + const [endPointIndex1, endPointIndex2] = shapeNeighborPointNavigation[minimumPointIndex];
429 +
430 + return {
431 + startPointIndex: minimumPointIndex,
432 + endPointIndex1,
433 + endPointIndex2,
434 + };
435 +}
436 +
437 +/**
438 + * Calculate a point line outside the canvas.
439 + * @param {string} type - 'x' or 'y'
440 + * @param {Array} shapePointNavigation - shape edge positions
441 + * @param {object} shapePointNavigation.lefttop - left top position
442 + * @param {object} shapePointNavigation.righttop - right top position
443 + * @param {object} shapePointNavigation.leftbottom - lefttop position
444 + * @param {object} shapePointNavigation.rightbottom - rightbottom position
445 + * @param {Object} linePointsOfOneVertexIndex - Line point of one vertex
446 + * @param {Object} linePointsOfOneVertexIndex.startPoint - start point index
447 + * @param {Object} linePointsOfOneVertexIndex.endPointIndex1 - end point index
448 + * @param {Object} linePointsOfOneVertexIndex.endPointIndex2 - end point index
449 + * @returns {Object}
450 + */
451 +function calculateLineAngleOfOutsideCanvas(type, shapePointNavigation, linePointsOfOneVertexIndex) {
452 + const { startPointIndex, endPointIndex1, endPointIndex2 } = linePointsOfOneVertexIndex;
453 + const horizontalVerticalAngle = type === 'x' ? 180 : 270;
454 +
455 + return map([endPointIndex1, endPointIndex2], (pointIndex) => {
456 + const startPoint = shapePointNavigation[startPointIndex];
457 + const endPoint = shapePointNavigation[pointIndex];
458 + const diffY = startPoint.y - endPoint.y;
459 + const diffX = startPoint.x - endPoint.x;
460 +
461 + return (Math.atan2(diffY, diffX) * 180) / Math.PI - horizontalVerticalAngle;
462 + });
463 +}
464 +
465 +/* eslint-disable complexity */
466 +/**
467 + * Calculate a point line outside the canvas for horizontal.
468 + * @param {number} startPointIndex - start point index
469 + * @param {boolean} flipX - flip x statux
470 + * @param {boolean} flipY - flip y statux
471 + * @returns {boolean} flipY - flip y statux
472 + */
473 +function isReverseLeftPositionForFlip(startPointIndex, flipX, flipY) {
474 + return (
475 + (((!flipX && flipY) || (!flipX && !flipY)) && startPointIndex === 0) ||
476 + (((flipX && flipY) || (flipX && !flipY)) && startPointIndex === 1) ||
477 + (((!flipX && !flipY) || (!flipX && flipY)) && startPointIndex === 2) ||
478 + (((flipX && !flipY) || (flipX && flipY)) && startPointIndex === 3)
479 + );
480 +}
481 +/* eslint-enable complexity */
482 +
483 +/* eslint-disable complexity */
484 +/**
485 + * Calculate a point line outside the canvas for vertical.
486 + * @param {number} startPointIndex - start point index
487 + * @param {boolean} flipX - flip x statux
488 + * @param {boolean} flipY - flip y statux
489 + * @returns {boolean} flipY - flip y statux
490 + */
491 +function isReverseTopPositionForFlip(startPointIndex, flipX, flipY) {
492 + return (
493 + (((flipX && !flipY) || (!flipX && !flipY)) && startPointIndex === 0) ||
494 + (((!flipX && !flipY) || (flipX && !flipY)) && startPointIndex === 1) ||
495 + (((flipX && flipY) || (!flipX && flipY)) && startPointIndex === 2) ||
496 + (((!flipX && flipY) || (flipX && flipY)) && startPointIndex === 3)
497 + );
498 +}
499 +/* eslint-enable complexity */
500 +
501 +/**
502 + * Shape edge points
503 + * @param {fabric.Object} shapeObj - Selected shape object on canvas
504 + * @returns {Array} shapeEdgePoint - shape edge positions
505 + */
506 +function getShapeEdgePoint(shapeObj) {
507 + return [
508 + shapeObj.getPointByOrigin('left', 'top'),
509 + shapeObj.getPointByOrigin('right', 'top'),
510 + shapeObj.getPointByOrigin('left', 'bottom'),
511 + shapeObj.getPointByOrigin('right', 'bottom'),
512 + ];
513 +}
514 +
515 +/**
516 + * Rotated shape dimension
517 + * @param {fabric.Object} shapeObj - Shape object
518 + * @returns {Object} Rotated shape dimension
519 + */
520 +function getRotatedDimension(shapeObj) {
521 + const [
522 + { x: ax, y: ay },
523 + { x: bx, y: by },
524 + { x: cx, y: cy },
525 + { x: dx, y: dy },
526 + ] = getShapeEdgePoint(shapeObj);
527 +
528 + const left = Math.min(ax, bx, cx, dx);
529 + const top = Math.min(ay, by, cy, dy);
530 + const right = Math.max(ax, bx, cx, dx);
531 + const bottom = Math.max(ay, by, cy, dy);
532 +
533 + return {
534 + left,
535 + top,
536 + right,
537 + bottom,
538 + width: right - left,
539 + height: bottom - top,
540 + };
541 +}
542 +
543 +/**
544 + * Make fill image
545 + * @param {HTMLImageElement} copiedCanvasElement - html image element
546 + * @param {number} currentCanvasImageAngle - current canvas angle
547 + * @param {Array} filterOption - filter option
548 + * @returns {fabric.Image}
549 + * @private
550 + */
551 +function makeFillImage(copiedCanvasElement, currentCanvasImageAngle, filterOption) {
552 + const fillImage = new fabric.Image(copiedCanvasElement);
553 +
554 + forEach(extend({}, ...filterOption), (value, key) => {
555 + const fabricFiterClassName = capitalizeString(key);
556 + const filter = new fabric.Image.filters[fabricFiterClassName]({
557 + [FILTER_OPTION_MAP[key]]: value,
558 + });
559 + fillImage.filters.push(filter);
560 + });
561 + fillImage.applyFilters();
562 +
563 + setCustomProperty(fillImage, {
564 + originalAngle: currentCanvasImageAngle,
565 + fillImageMaxSize: Math.max(fillImage.width, fillImage.height),
566 + });
567 + resizeHelper.adjustOriginToCenter(fillImage);
568 +
569 + return fillImage;
570 +}
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Shape resize helper
4 + */
5 +const DIVISOR = {
6 + rect: 1,
7 + circle: 2,
8 + triangle: 1,
9 +};
10 +const DIMENSION_KEYS = {
11 + rect: {
12 + w: 'width',
13 + h: 'height',
14 + },
15 + circle: {
16 + w: 'rx',
17 + h: 'ry',
18 + },
19 + triangle: {
20 + w: 'width',
21 + h: 'height',
22 + },
23 +};
24 +
25 +/**
26 + * Set the start point value to the shape object
27 + * @param {fabric.Object} shape - Shape object
28 + * @ignore
29 + */
30 +function setStartPoint(shape) {
31 + const { originX, originY } = shape;
32 + const originKey = originX.substring(0, 1) + originY.substring(0, 1);
33 +
34 + shape.startPoint = shape.origins[originKey];
35 +}
36 +
37 +/**
38 + * Get the positions of ratated origin by the pointer value
39 + * @param {{x: number, y: number}} origin - Origin value
40 + * @param {{x: number, y: number}} pointer - Pointer value
41 + * @param {number} angle - Rotating angle
42 + * @returns {Object} Postions of origin
43 + * @ignore
44 + */
45 +function getPositionsOfRotatedOrigin(origin, pointer, angle) {
46 + const sx = origin.x;
47 + const sy = origin.y;
48 + const px = pointer.x;
49 + const py = pointer.y;
50 + const r = (angle * Math.PI) / 180;
51 + const rx = (px - sx) * Math.cos(r) - (py - sy) * Math.sin(r) + sx;
52 + const ry = (px - sx) * Math.sin(r) + (py - sy) * Math.cos(r) + sy;
53 +
54 + return {
55 + originX: sx > rx ? 'right' : 'left',
56 + originY: sy > ry ? 'bottom' : 'top',
57 + };
58 +}
59 +
60 +/**
61 + * Whether the shape has the center origin or not
62 + * @param {fabric.Object} shape - Shape object
63 + * @returns {boolean} State
64 + * @ignore
65 + */
66 +function hasCenterOrigin(shape) {
67 + return shape.originX === 'center' && shape.originY === 'center';
68 +}
69 +
70 +/**
71 + * Adjust the origin of shape by the start point
72 + * @param {{x: number, y: number}} pointer - Pointer value
73 + * @param {fabric.Object} shape - Shape object
74 + * @ignore
75 + */
76 +function adjustOriginByStartPoint(pointer, shape) {
77 + const centerPoint = shape.getPointByOrigin('center', 'center');
78 + const angle = -shape.angle;
79 + const originPositions = getPositionsOfRotatedOrigin(centerPoint, pointer, angle);
80 + const { originX, originY } = originPositions;
81 + const origin = shape.getPointByOrigin(originX, originY);
82 + const left = shape.left - (centerPoint.x - origin.x);
83 + const top = shape.top - (centerPoint.y - origin.y);
84 +
85 + shape.set({
86 + originX,
87 + originY,
88 + left,
89 + top,
90 + });
91 +
92 + shape.setCoords();
93 +}
94 +
95 +/**
96 + * Adjust the origin of shape by the moving pointer value
97 + * @param {{x: number, y: number}} pointer - Pointer value
98 + * @param {fabric.Object} shape - Shape object
99 + * @ignore
100 + */
101 +function adjustOriginByMovingPointer(pointer, shape) {
102 + const origin = shape.startPoint;
103 + const angle = -shape.angle;
104 + const originPositions = getPositionsOfRotatedOrigin(origin, pointer, angle);
105 + const { originX, originY } = originPositions;
106 +
107 + shape.setPositionByOrigin(origin, originX, originY);
108 + shape.setCoords();
109 +}
110 +
111 +/**
112 + * Adjust the dimension of shape on firing scaling event
113 + * @param {fabric.Object} shape - Shape object
114 + * @ignore
115 + */
116 +function adjustDimensionOnScaling(shape) {
117 + const { type, scaleX, scaleY } = shape;
118 + const dimensionKeys = DIMENSION_KEYS[type];
119 + let width = shape[dimensionKeys.w] * scaleX;
120 + let height = shape[dimensionKeys.h] * scaleY;
121 +
122 + if (shape.isRegular) {
123 + const maxScale = Math.max(scaleX, scaleY);
124 +
125 + width = shape[dimensionKeys.w] * maxScale;
126 + height = shape[dimensionKeys.h] * maxScale;
127 + }
128 +
129 + const options = {
130 + hasControls: false,
131 + hasBorders: false,
132 + scaleX: 1,
133 + scaleY: 1,
134 + };
135 +
136 + options[dimensionKeys.w] = width;
137 + options[dimensionKeys.h] = height;
138 +
139 + shape.set(options);
140 +}
141 +
142 +/**
143 + * Adjust the dimension of shape on firing mouse move event
144 + * @param {{x: number, y: number}} pointer - Pointer value
145 + * @param {fabric.Object} shape - Shape object
146 + * @ignore
147 + */
148 +function adjustDimensionOnMouseMove(pointer, shape) {
149 + const { type, strokeWidth, startPoint: origin } = shape;
150 + const divisor = DIVISOR[type];
151 + const dimensionKeys = DIMENSION_KEYS[type];
152 + const isTriangle = !!(shape.type === 'triangle');
153 + const options = {};
154 + let width = Math.abs(origin.x - pointer.x) / divisor;
155 + let height = Math.abs(origin.y - pointer.y) / divisor;
156 +
157 + if (width > strokeWidth) {
158 + width -= strokeWidth / divisor;
159 + }
160 +
161 + if (height > strokeWidth) {
162 + height -= strokeWidth / divisor;
163 + }
164 +
165 + if (shape.isRegular) {
166 + width = height = Math.max(width, height);
167 +
168 + if (isTriangle) {
169 + height = (Math.sqrt(3) / 2) * width;
170 + }
171 + }
172 +
173 + options[dimensionKeys.w] = width;
174 + options[dimensionKeys.h] = height;
175 +
176 + shape.set(options);
177 +}
178 +
179 +module.exports = {
180 + /**
181 + * Set each origin value to shape
182 + * @param {fabric.Object} shape - Shape object
183 + */
184 + setOrigins(shape) {
185 + const leftTopPoint = shape.getPointByOrigin('left', 'top');
186 + const rightTopPoint = shape.getPointByOrigin('right', 'top');
187 + const rightBottomPoint = shape.getPointByOrigin('right', 'bottom');
188 + const leftBottomPoint = shape.getPointByOrigin('left', 'bottom');
189 +
190 + shape.origins = {
191 + lt: leftTopPoint,
192 + rt: rightTopPoint,
193 + rb: rightBottomPoint,
194 + lb: leftBottomPoint,
195 + };
196 + },
197 +
198 + /**
199 + * Resize the shape
200 + * @param {fabric.Object} shape - Shape object
201 + * @param {{x: number, y: number}} pointer - Mouse pointer values on canvas
202 + * @param {boolean} isScaling - Whether the resizing action is scaling or not
203 + */
204 + resize(shape, pointer, isScaling) {
205 + if (hasCenterOrigin(shape)) {
206 + adjustOriginByStartPoint(pointer, shape);
207 + setStartPoint(shape);
208 + }
209 +
210 + if (isScaling) {
211 + adjustDimensionOnScaling(shape, pointer);
212 + } else {
213 + adjustDimensionOnMouseMove(pointer, shape);
214 + }
215 +
216 + adjustOriginByMovingPointer(pointer, shape);
217 + },
218 +
219 + /**
220 + * Adjust the origin position of shape to center
221 + * @param {fabric.Object} shape - Shape object
222 + */
223 + adjustOriginToCenter(shape) {
224 + const centerPoint = shape.getPointByOrigin('center', 'center');
225 + const { originX, originY } = shape;
226 + const origin = shape.getPointByOrigin(originX, originY);
227 + const left = shape.left + (centerPoint.x - origin.x);
228 + const top = shape.top + (centerPoint.y - origin.y);
229 +
230 + shape.set({
231 + hasControls: true,
232 + hasBorders: true,
233 + originX: 'center',
234 + originY: 'center',
235 + left,
236 + top,
237 + });
238 +
239 + shape.setCoords(); // For left, top properties
240 + },
241 +};
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Image-editor application class
4 + */
5 +import snippet from 'tui-code-snippet';
6 +import Invoker from './invoker';
7 +import UI from './ui';
8 +import action from './action';
9 +import commandFactory from './factory/command';
10 +import Graphics from './graphics';
11 +import { sendHostName, Promise } from './util';
12 +import { eventNames as events, commandNames as commands, keyCodes, rejectMessages } from './consts';
13 +import { makeSelectionUndoData, makeSelectionUndoDatum } from './helper/selectionModifyHelper';
14 +
15 +const { isUndefined, forEach, CustomEvents } = snippet;
16 +
17 +const {
18 + MOUSE_DOWN,
19 + OBJECT_MOVED,
20 + OBJECT_SCALED,
21 + OBJECT_ACTIVATED,
22 + OBJECT_ROTATED,
23 + OBJECT_ADDED,
24 + OBJECT_MODIFIED,
25 + ADD_TEXT,
26 + ADD_OBJECT,
27 + TEXT_EDITING,
28 + TEXT_CHANGED,
29 + ICON_CREATE_RESIZE,
30 + ICON_CREATE_END,
31 + SELECTION_CLEARED,
32 + SELECTION_CREATED,
33 + ADD_OBJECT_AFTER,
34 +} = events;
35 +
36 +/**
37 + * Image filter result
38 + * @typedef {object} FilterResult
39 + * @property {string} type - filter type like 'mask', 'Grayscale' and so on
40 + * @property {string} action - action type like 'add', 'remove'
41 + */
42 +
43 +/**
44 + * Flip status
45 + * @typedef {object} FlipStatus
46 + * @property {boolean} flipX - x axis
47 + * @property {boolean} flipY - y axis
48 + * @property {Number} angle - angle
49 + */
50 +/**
51 + * Rotation status
52 + * @typedef {Number} RotateStatus
53 + * @property {Number} angle - angle
54 + */
55 +
56 +/**
57 + * Old and new Size
58 + * @typedef {object} SizeChange
59 + * @property {Number} oldWidth - old width
60 + * @property {Number} oldHeight - old height
61 + * @property {Number} newWidth - new width
62 + * @property {Number} newHeight - new height
63 + */
64 +
65 +/**
66 + * @typedef {string} ErrorMsg - {string} error message
67 + */
68 +
69 +/**
70 + * @typedef {object} ObjectProps - graphics object properties
71 + * @property {number} id - object id
72 + * @property {string} type - object type
73 + * @property {string} text - text content
74 + * @property {(string | number)} left - Left
75 + * @property {(string | number)} top - Top
76 + * @property {(string | number)} width - Width
77 + * @property {(string | number)} height - Height
78 + * @property {string} fill - Color
79 + * @property {string} stroke - Stroke
80 + * @property {(string | number)} strokeWidth - StrokeWidth
81 + * @property {string} fontFamily - Font type for text
82 + * @property {number} fontSize - Font Size
83 + * @property {string} fontStyle - Type of inclination (normal / italic)
84 + * @property {string} fontWeight - Type of thicker or thinner looking (normal / bold)
85 + * @property {string} textAlign - Type of text align (left / center / right)
86 + * @property {string} textDecoration - Type of line (underline / line-through / overline)
87 + */
88 +
89 +/**
90 + * Shape filter option
91 + * @typedef {object.<string, number>} ShapeFilterOption
92 + */
93 +
94 +/**
95 + * Shape filter option
96 + * @typedef {object} ShapeFillOption - fill option of shape
97 + * @property {string} type - fill type ('color' or 'filter')
98 + * @property {Array.<ShapeFillFilterOption>} [filter] - {@link ShapeFilterOption} List.
99 + * only applies to filter types
100 + * (ex: \[\{pixelate: 20\}, \{blur: 0.3\}\])
101 + * @property {string} [color] - Shape foreground color (ex: '#fff', 'transparent')
102 + */
103 +
104 +/**
105 + * Image editor
106 + * @class
107 + * @param {string|HTMLElement} wrapper - Wrapper's element or selector
108 + * @param {Object} [options] - Canvas max width & height of css
109 + * @param {number} [options.includeUI] - Use the provided UI
110 + * @param {Object} [options.includeUI.loadImage] - Basic editing image
111 + * @param {string} options.includeUI.loadImage.path - image path
112 + * @param {string} options.includeUI.loadImage.name - image name
113 + * @param {Object} [options.includeUI.theme] - Theme object
114 + * @param {Array} [options.includeUI.menu] - It can be selected when only specific menu is used, Default values are \['crop', 'flip', 'rotate', 'draw', 'shape', 'icon', 'text', 'mask', 'filter'\].
115 + * @param {string} [options.includeUI.initMenu] - The first menu to be selected and started.
116 + * @param {Object} [options.includeUI.uiSize] - ui size of editor
117 + * @param {string} options.includeUI.uiSize.width - width of ui
118 + * @param {string} options.includeUI.uiSize.height - height of ui
119 + * @param {string} [options.includeUI.menuBarPosition=bottom] - Menu bar position('top', 'bottom', 'left', 'right')
120 + * @param {number} options.cssMaxWidth - Canvas css-max-width
121 + * @param {number} options.cssMaxHeight - Canvas css-max-height
122 + * @param {Object} [options.selectionStyle] - selection style
123 + * @param {string} [options.selectionStyle.cornerStyle] - selection corner style
124 + * @param {number} [options.selectionStyle.cornerSize] - selection corner size
125 + * @param {string} [options.selectionStyle.cornerColor] - selection corner color
126 + * @param {string} [options.selectionStyle.cornerStrokeColor] = selection corner stroke color
127 + * @param {boolean} [options.selectionStyle.transparentCorners] - selection corner transparent
128 + * @param {number} [options.selectionStyle.lineWidth] - selection line width
129 + * @param {string} [options.selectionStyle.borderColor] - selection border color
130 + * @param {number} [options.selectionStyle.rotatingPointOffset] - selection rotating point length
131 + * @param {Boolean} [options.usageStatistics=true] - Let us know the hostname. If you don't want to send the hostname, please set to false.
132 + * @example
133 + * var ImageEditor = require('tui-image-editor');
134 + * var blackTheme = require('./js/theme/black-theme.js');
135 + * var instance = new ImageEditor(document.querySelector('#tui-image-editor'), {
136 + * includeUI: {
137 + * loadImage: {
138 + * path: 'img/sampleImage.jpg',
139 + * name: 'SampleImage'
140 + * },
141 + * theme: blackTheme, // or whiteTheme
142 + * menu: ['shape', 'filter'],
143 + * initMenu: 'filter',
144 + * uiSize: {
145 + * width: '1000px',
146 + * height: '700px'
147 + * },
148 + * menuBarPosition: 'bottom'
149 + * },
150 + * cssMaxWidth: 700,
151 + * cssMaxHeight: 500,
152 + * selectionStyle: {
153 + * cornerSize: 20,
154 + * rotatingPointOffset: 70
155 + * }
156 + * });
157 + */
158 +class ImageEditor {
159 + constructor(wrapper, options) {
160 + options = snippet.extend(
161 + {
162 + includeUI: false,
163 + usageStatistics: true,
164 + },
165 + options
166 + );
167 +
168 + this.mode = null;
169 +
170 + this.activeObjectId = null;
171 +
172 + /**
173 + * UI instance
174 + * @type {Ui}
175 + */
176 + if (options.includeUI) {
177 + const UIOption = options.includeUI;
178 + UIOption.usageStatistics = options.usageStatistics;
179 +
180 + this.ui = new UI(wrapper, UIOption, this.getActions());
181 + options = this.ui.setUiDefaultSelectionStyle(options);
182 + }
183 +
184 + /**
185 + * Invoker
186 + * @type {Invoker}
187 + * @private
188 + */
189 + this._invoker = new Invoker();
190 +
191 + /**
192 + * Graphics instance
193 + * @type {Graphics}
194 + * @private
195 + */
196 + this._graphics = new Graphics(this.ui ? this.ui.getEditorArea() : wrapper, {
197 + cssMaxWidth: options.cssMaxWidth,
198 + cssMaxHeight: options.cssMaxHeight,
199 + });
200 +
201 + /**
202 + * Event handler list
203 + * @type {Object}
204 + * @private
205 + */
206 + this._handlers = {
207 + keydown: this._onKeyDown.bind(this),
208 + mousedown: this._onMouseDown.bind(this),
209 + objectActivated: this._onObjectActivated.bind(this),
210 + objectMoved: this._onObjectMoved.bind(this),
211 + objectScaled: this._onObjectScaled.bind(this),
212 + objectRotated: this._onObjectRotated.bind(this),
213 + objectAdded: this._onObjectAdded.bind(this),
214 + objectModified: this._onObjectModified.bind(this),
215 + createdPath: this._onCreatedPath,
216 + addText: this._onAddText.bind(this),
217 + addObject: this._onAddObject.bind(this),
218 + textEditing: this._onTextEditing.bind(this),
219 + textChanged: this._onTextChanged.bind(this),
220 + iconCreateResize: this._onIconCreateResize.bind(this),
221 + iconCreateEnd: this._onIconCreateEnd.bind(this),
222 + selectionCleared: this._selectionCleared.bind(this),
223 + selectionCreated: this._selectionCreated.bind(this),
224 + };
225 +
226 + this._attachInvokerEvents();
227 + this._attachGraphicsEvents();
228 + this._attachDomEvents();
229 + this._setSelectionStyle(options.selectionStyle, {
230 + applyCropSelectionStyle: options.applyCropSelectionStyle,
231 + applyGroupSelectionStyle: options.applyGroupSelectionStyle,
232 + });
233 +
234 + if (options.usageStatistics) {
235 + sendHostName();
236 + }
237 +
238 + if (this.ui) {
239 + this.ui.initCanvas();
240 + this.setReAction();
241 + }
242 + fabric.enableGLFiltering = false;
243 + }
244 +
245 + /**
246 + * Set selection style by init option
247 + * @param {Object} selectionStyle - Selection styles
248 + * @param {Object} applyTargets - Selection apply targets
249 + * @param {boolean} applyCropSelectionStyle - whether apply with crop selection style or not
250 + * @param {boolean} applyGroupSelectionStyle - whether apply with group selection style or not
251 + * @private
252 + */
253 + _setSelectionStyle(selectionStyle, { applyCropSelectionStyle, applyGroupSelectionStyle }) {
254 + if (selectionStyle) {
255 + this._graphics.setSelectionStyle(selectionStyle);
256 + }
257 +
258 + if (applyCropSelectionStyle) {
259 + this._graphics.setCropSelectionStyle(selectionStyle);
260 + }
261 +
262 + if (applyGroupSelectionStyle) {
263 + this.on('selectionCreated', (eventTarget) => {
264 + if (eventTarget.type === 'activeSelection') {
265 + eventTarget.set(selectionStyle);
266 + }
267 + });
268 + }
269 + }
270 +
271 + /**
272 + * Attach invoker events
273 + * @private
274 + */
275 + _attachInvokerEvents() {
276 + const { UNDO_STACK_CHANGED, REDO_STACK_CHANGED } = events;
277 +
278 + /**
279 + * Undo stack changed event
280 + * @event ImageEditor#undoStackChanged
281 + * @param {Number} length - undo stack length
282 + * @example
283 + * imageEditor.on('undoStackChanged', function(length) {
284 + * console.log(length);
285 + * });
286 + */
287 + this._invoker.on(UNDO_STACK_CHANGED, this.fire.bind(this, UNDO_STACK_CHANGED));
288 + /**
289 + * Redo stack changed event
290 + * @event ImageEditor#redoStackChanged
291 + * @param {Number} length - redo stack length
292 + * @example
293 + * imageEditor.on('redoStackChanged', function(length) {
294 + * console.log(length);
295 + * });
296 + */
297 + this._invoker.on(REDO_STACK_CHANGED, this.fire.bind(this, REDO_STACK_CHANGED));
298 + }
299 +
300 + /**
301 + * Attach canvas events
302 + * @private
303 + */
304 + _attachGraphicsEvents() {
305 + this._graphics.on({
306 + [MOUSE_DOWN]: this._handlers.mousedown,
307 + [OBJECT_MOVED]: this._handlers.objectMoved,
308 + [OBJECT_SCALED]: this._handlers.objectScaled,
309 + [OBJECT_ROTATED]: this._handlers.objectRotated,
310 + [OBJECT_ACTIVATED]: this._handlers.objectActivated,
311 + [OBJECT_ADDED]: this._handlers.objectAdded,
312 + [OBJECT_MODIFIED]: this._handlers.objectModified,
313 + [ADD_TEXT]: this._handlers.addText,
314 + [ADD_OBJECT]: this._handlers.addObject,
315 + [TEXT_EDITING]: this._handlers.textEditing,
316 + [TEXT_CHANGED]: this._handlers.textChanged,
317 + [ICON_CREATE_RESIZE]: this._handlers.iconCreateResize,
318 + [ICON_CREATE_END]: this._handlers.iconCreateEnd,
319 + [SELECTION_CLEARED]: this._handlers.selectionCleared,
320 + [SELECTION_CREATED]: this._handlers.selectionCreated,
321 + });
322 + }
323 +
324 + /**
325 + * Attach dom events
326 + * @private
327 + */
328 + _attachDomEvents() {
329 + // ImageEditor supports IE 9 higher
330 + document.addEventListener('keydown', this._handlers.keydown);
331 + }
332 +
333 + /**
334 + * Detach dom events
335 + * @private
336 + */
337 + _detachDomEvents() {
338 + // ImageEditor supports IE 9 higher
339 + document.removeEventListener('keydown', this._handlers.keydown);
340 + }
341 +
342 + /**
343 + * Keydown event handler
344 + * @param {KeyboardEvent} e - Event object
345 + * @private
346 + */
347 + /* eslint-disable complexity */
348 + _onKeyDown(e) {
349 + const { ctrlKey, keyCode, metaKey } = e;
350 + const isModifierKey = ctrlKey || metaKey;
351 +
352 + if (isModifierKey) {
353 + if (keyCode === keyCodes.C) {
354 + this._graphics.resetTargetObjectForCopyPaste();
355 + } else if (keyCode === keyCodes.V) {
356 + this._graphics.pasteObject();
357 + this.clearRedoStack();
358 + } else if (keyCode === keyCodes.Z) {
359 + // There is no error message on shortcut when it's empty
360 + this.undo()['catch'](() => {});
361 + } else if (keyCode === keyCodes.Y) {
362 + // There is no error message on shortcut when it's empty
363 + this.redo()['catch'](() => {});
364 + }
365 + }
366 +
367 + const isDeleteKey = keyCode === keyCodes.BACKSPACE || keyCode === keyCodes.DEL;
368 + const isRemoveReady = this._graphics.isReadyRemoveObject();
369 +
370 + if (isRemoveReady && isDeleteKey) {
371 + e.preventDefault();
372 + this.removeActiveObject();
373 + }
374 + }
375 +
376 + /**
377 + * Remove Active Object
378 + */
379 + removeActiveObject() {
380 + const activeObjectId = this._graphics.getActiveObjectIdForRemove();
381 +
382 + this.removeObject(activeObjectId);
383 + }
384 +
385 + /**
386 + * mouse down event handler
387 + * @param {Event} event - mouse down event
388 + * @param {Object} originPointer - origin pointer
389 + * @param {Number} originPointer.x x position
390 + * @param {Number} originPointer.y y position
391 + * @private
392 + */
393 + _onMouseDown(event, originPointer) {
394 + /**
395 + * The mouse down event with position x, y on canvas
396 + * @event ImageEditor#mousedown
397 + * @param {Object} event - browser mouse event object
398 + * @param {Object} originPointer origin pointer
399 + * @param {Number} originPointer.x x position
400 + * @param {Number} originPointer.y y position
401 + * @example
402 + * imageEditor.on('mousedown', function(event, originPointer) {
403 + * console.log(event);
404 + * console.log(originPointer);
405 + * if (imageEditor.hasFilter('colorFilter')) {
406 + * imageEditor.applyFilter('colorFilter', {
407 + * x: parseInt(originPointer.x, 10),
408 + * y: parseInt(originPointer.y, 10)
409 + * });
410 + * }
411 + * });
412 + */
413 +
414 + this.fire(events.MOUSE_DOWN, event, originPointer);
415 + }
416 +
417 + /**
418 + * Add a 'addObject' command
419 + * @param {Object} obj - Fabric object
420 + * @private
421 + */
422 + _pushAddObjectCommand(obj) {
423 + const command = commandFactory.create(commands.ADD_OBJECT, this._graphics, obj);
424 + this._invoker.pushUndoStack(command);
425 + }
426 +
427 + /**
428 + * Add a 'changeSelection' command
429 + * @param {fabric.Object} obj - selection object
430 + * @private
431 + */
432 + _pushModifyObjectCommand(obj) {
433 + const { type } = obj;
434 + const props = makeSelectionUndoData(obj, (item) =>
435 + makeSelectionUndoDatum(this._graphics.getObjectId(item), item, type === 'activeSelection')
436 + );
437 + const command = commandFactory.create(commands.CHANGE_SELECTION, this._graphics, props);
438 + command.execute(this._graphics, props);
439 +
440 + this._invoker.pushUndoStack(command);
441 + }
442 +
443 + /**
444 + * 'objectActivated' event handler
445 + * @param {ObjectProps} props - object properties
446 + * @private
447 + */
448 + _onObjectActivated(props) {
449 + /**
450 + * The event when object is selected(aka activated).
451 + * @event ImageEditor#objectActivated
452 + * @param {ObjectProps} objectProps - object properties
453 + * @example
454 + * imageEditor.on('objectActivated', function(props) {
455 + * console.log(props);
456 + * console.log(props.type);
457 + * console.log(props.id);
458 + * });
459 + */
460 + this.fire(events.OBJECT_ACTIVATED, props);
461 + }
462 +
463 + /**
464 + * 'objectMoved' event handler
465 + * @param {ObjectProps} props - object properties
466 + * @private
467 + */
468 + _onObjectMoved(props) {
469 + /**
470 + * The event when object is moved
471 + * @event ImageEditor#objectMoved
472 + * @param {ObjectProps} props - object properties
473 + * @example
474 + * imageEditor.on('objectMoved', function(props) {
475 + * console.log(props);
476 + * console.log(props.type);
477 + * });
478 + */
479 + this.fire(events.OBJECT_MOVED, props);
480 + }
481 +
482 + /**
483 + * 'objectScaled' event handler
484 + * @param {ObjectProps} props - object properties
485 + * @private
486 + */
487 + _onObjectScaled(props) {
488 + /**
489 + * The event when scale factor is changed
490 + * @event ImageEditor#objectScaled
491 + * @param {ObjectProps} props - object properties
492 + * @example
493 + * imageEditor.on('objectScaled', function(props) {
494 + * console.log(props);
495 + * console.log(props.type);
496 + * });
497 + */
498 + this.fire(events.OBJECT_SCALED, props);
499 + }
500 +
501 + /**
502 + * 'objectRotated' event handler
503 + * @param {ObjectProps} props - object properties
504 + * @private
505 + */
506 + _onObjectRotated(props) {
507 + /**
508 + * The event when object angle is changed
509 + * @event ImageEditor#objectRotated
510 + * @param {ObjectProps} props - object properties
511 + * @example
512 + * imageEditor.on('objectRotated', function(props) {
513 + * console.log(props);
514 + * console.log(props.type);
515 + * });
516 + */
517 + this.fire(events.OBJECT_ROTATED, props);
518 + }
519 +
520 + /**
521 + * Get current drawing mode
522 + * @returns {string}
523 + * @example
524 + * // Image editor drawing mode
525 + * //
526 + * // NORMAL: 'NORMAL'
527 + * // CROPPER: 'CROPPER'
528 + * // FREE_DRAWING: 'FREE_DRAWING'
529 + * // LINE_DRAWING: 'LINE_DRAWING'
530 + * // TEXT: 'TEXT'
531 + * //
532 + * if (imageEditor.getDrawingMode() === 'FREE_DRAWING') {
533 + * imageEditor.stopDrawingMode();
534 + * }
535 + */
536 + getDrawingMode() {
537 + return this._graphics.getDrawingMode();
538 + }
539 +
540 + /**
541 + * Clear all objects
542 + * @returns {Promise}
543 + * @example
544 + * imageEditor.clearObjects();
545 + */
546 + clearObjects() {
547 + return this.execute(commands.CLEAR_OBJECTS);
548 + }
549 +
550 + /**
551 + * Deactivate all objects
552 + * @example
553 + * imageEditor.deactivateAll();
554 + */
555 + deactivateAll() {
556 + this._graphics.deactivateAll();
557 + this._graphics.renderAll();
558 + }
559 +
560 + /**
561 + * discard selction
562 + * @example
563 + * imageEditor.discardSelection();
564 + */
565 + discardSelection() {
566 + this._graphics.discardSelection();
567 + }
568 +
569 + /**
570 + * selectable status change
571 + * @param {boolean} selectable - selctable status
572 + * @example
573 + * imageEditor.changeSelectableAll(false); // or true
574 + */
575 + changeSelectableAll(selectable) {
576 + this._graphics.changeSelectableAll(selectable);
577 + }
578 +
579 + /**
580 + * Invoke command
581 + * @param {String} commandName - Command name
582 + * @param {...*} args - Arguments for creating command
583 + * @returns {Promise}
584 + * @private
585 + */
586 + execute(commandName, ...args) {
587 + // Inject an Graphics instance as first parameter
588 + const theArgs = [this._graphics].concat(args);
589 +
590 + return this._invoker.execute(commandName, ...theArgs);
591 + }
592 +
593 + /**
594 + * Invoke command
595 + * @param {String} commandName - Command name
596 + * @param {...*} args - Arguments for creating command
597 + * @returns {Promise}
598 + * @private
599 + */
600 + executeSilent(commandName, ...args) {
601 + // Inject an Graphics instance as first parameter
602 + const theArgs = [this._graphics].concat(args);
603 +
604 + return this._invoker.executeSilent(commandName, ...theArgs);
605 + }
606 +
607 + /**
608 + * Undo
609 + * @returns {Promise}
610 + * @example
611 + * imageEditor.undo();
612 + */
613 + undo() {
614 + return this._invoker.undo();
615 + }
616 +
617 + /**
618 + * Redo
619 + * @returns {Promise}
620 + * @example
621 + * imageEditor.redo();
622 + */
623 + redo() {
624 + return this._invoker.redo();
625 + }
626 +
627 + /**
628 + * Load image from file
629 + * @param {File} imgFile - Image file
630 + * @param {string} [imageName] - imageName
631 + * @returns {Promise<SizeChange, ErrorMsg>}
632 + * @example
633 + * imageEditor.loadImageFromFile(file).then(result => {
634 + * console.log('old : ' + result.oldWidth + ', ' + result.oldHeight);
635 + * console.log('new : ' + result.newWidth + ', ' + result.newHeight);
636 + * });
637 + */
638 + loadImageFromFile(imgFile, imageName) {
639 + if (!imgFile) {
640 + return Promise.reject(rejectMessages.invalidParameters);
641 + }
642 +
643 + const imgUrl = URL.createObjectURL(imgFile);
644 + imageName = imageName || imgFile.name;
645 +
646 + return this.loadImageFromURL(imgUrl, imageName).then((value) => {
647 + URL.revokeObjectURL(imgFile);
648 +
649 + return value;
650 + });
651 + }
652 +
653 + /**
654 + * Load image from url
655 + * @param {string} url - File url
656 + * @param {string} imageName - imageName
657 + * @returns {Promise<SizeChange, ErrorMsg>}
658 + * @example
659 + * imageEditor.loadImageFromURL('http://url/testImage.png', 'lena').then(result => {
660 + * console.log('old : ' + result.oldWidth + ', ' + result.oldHeight);
661 + * console.log('new : ' + result.newWidth + ', ' + result.newHeight);
662 + * });
663 + */
664 + loadImageFromURL(url, imageName) {
665 + if (!imageName || !url) {
666 + return Promise.reject(rejectMessages.invalidParameters);
667 + }
668 +
669 + return this.execute(commands.LOAD_IMAGE, imageName, url);
670 + }
671 +
672 + /**
673 + * Add image object on canvas
674 + * @param {string} imgUrl - Image url to make object
675 + * @returns {Promise<ObjectProps, ErrorMsg>}
676 + * @example
677 + * imageEditor.addImageObject('path/fileName.jpg').then(objectProps => {
678 + * console.log(ojectProps.id);
679 + * });
680 + */
681 + addImageObject(imgUrl) {
682 + if (!imgUrl) {
683 + return Promise.reject(rejectMessages.invalidParameters);
684 + }
685 +
686 + return this.execute(commands.ADD_IMAGE_OBJECT, imgUrl);
687 + }
688 +
689 + /**
690 + * Start a drawing mode. If the current mode is not 'NORMAL', 'stopDrawingMode()' will be called first.
691 + * @param {String} mode Can be one of <I>'CROPPER', 'FREE_DRAWING', 'LINE_DRAWING', 'TEXT', 'SHAPE'</I>
692 + * @param {Object} [option] parameters of drawing mode, it's available with 'FREE_DRAWING', 'LINE_DRAWING'
693 + * @param {Number} [option.width] brush width
694 + * @param {String} [option.color] brush color
695 + * @param {Object} [option.arrowType] arrow decorate
696 + * @param {string} [option.arrowType.tail] arrow decorate for tail. 'chevron' or 'triangle'
697 + * @param {string} [option.arrowType.head] arrow decorate for head. 'chevron' or 'triangle'
698 + * @returns {boolean} true if success or false
699 + * @example
700 + * imageEditor.startDrawingMode('FREE_DRAWING', {
701 + * width: 10,
702 + * color: 'rgba(255,0,0,0.5)'
703 + * });
704 + * imageEditor.startDrawingMode('LINE_DRAWING', {
705 + * width: 10,
706 + * color: 'rgba(255,0,0,0.5)',
707 + * arrowType: {
708 + * tail: 'chevron' // triangle
709 + * }
710 + * });
711 + *
712 + */
713 + startDrawingMode(mode, option) {
714 + return this._graphics.startDrawingMode(mode, option);
715 + }
716 +
717 + /**
718 + * Stop the current drawing mode and back to the 'NORMAL' mode
719 + * @example
720 + * imageEditor.stopDrawingMode();
721 + */
722 + stopDrawingMode() {
723 + this._graphics.stopDrawingMode();
724 + }
725 +
726 + /**
727 + * Crop this image with rect
728 + * @param {Object} rect crop rect
729 + * @param {Number} rect.left left position
730 + * @param {Number} rect.top top position
731 + * @param {Number} rect.width width
732 + * @param {Number} rect.height height
733 + * @returns {Promise}
734 + * @example
735 + * imageEditor.crop(imageEditor.getCropzoneRect());
736 + */
737 + crop(rect) {
738 + const data = this._graphics.getCroppedImageData(rect);
739 + if (!data) {
740 + return Promise.reject(rejectMessages.invalidParameters);
741 + }
742 +
743 + return this.loadImageFromURL(data.url, data.imageName);
744 + }
745 +
746 + /**
747 + * Get the cropping rect
748 + * @returns {Object} {{left: number, top: number, width: number, height: number}} rect
749 + */
750 + getCropzoneRect() {
751 + return this._graphics.getCropzoneRect();
752 + }
753 +
754 + /**
755 + * Set the cropping rect
756 + * @param {number} [mode] crop rect mode [1, 1.5, 1.3333333333333333, 1.25, 1.7777777777777777]
757 + */
758 + setCropzoneRect(mode) {
759 + this._graphics.setCropzoneRect(mode);
760 + }
761 +
762 + /**
763 + * Flip
764 + * @returns {Promise}
765 + * @param {string} type - 'flipX' or 'flipY' or 'reset'
766 + * @returns {Promise<FlipStatus, ErrorMsg>}
767 + * @private
768 + */
769 + _flip(type) {
770 + return this.execute(commands.FLIP_IMAGE, type);
771 + }
772 +
773 + /**
774 + * Flip x
775 + * @returns {Promise<FlipStatus, ErrorMsg>}
776 + * @example
777 + * imageEditor.flipX().then((status => {
778 + * console.log('flipX: ', status.flipX);
779 + * console.log('flipY: ', status.flipY);
780 + * console.log('angle: ', status.angle);
781 + * }).catch(message => {
782 + * console.log('error: ', message);
783 + * });
784 + */
785 + flipX() {
786 + return this._flip('flipX');
787 + }
788 +
789 + /**
790 + * Flip y
791 + * @returns {Promise<FlipStatus, ErrorMsg>}
792 + * @example
793 + * imageEditor.flipY().then(status => {
794 + * console.log('flipX: ', status.flipX);
795 + * console.log('flipY: ', status.flipY);
796 + * console.log('angle: ', status.angle);
797 + * }).catch(message => {
798 + * console.log('error: ', message);
799 + * });
800 + */
801 + flipY() {
802 + return this._flip('flipY');
803 + }
804 +
805 + /**
806 + * Reset flip
807 + * @returns {Promise<FlipStatus, ErrorMsg>}
808 + * @example
809 + * imageEditor.resetFlip().then(status => {
810 + * console.log('flipX: ', status.flipX);
811 + * console.log('flipY: ', status.flipY);
812 + * console.log('angle: ', status.angle);
813 + * }).catch(message => {
814 + * console.log('error: ', message);
815 + * });;
816 + */
817 + resetFlip() {
818 + return this._flip('reset');
819 + }
820 +
821 + /**
822 + * @param {string} type - 'rotate' or 'setAngle'
823 + * @param {number} angle - angle value (degree)
824 + * @param {boolean} isSilent - is silent execution or not
825 + * @returns {Promise<RotateStatus, ErrorMsg>}
826 + * @private
827 + */
828 + _rotate(type, angle, isSilent) {
829 + let result = null;
830 + if (isSilent) {
831 + result = this.executeSilent(commands.ROTATE_IMAGE, type, angle);
832 + } else {
833 + result = this.execute(commands.ROTATE_IMAGE, type, angle);
834 + }
835 +
836 + return result;
837 + }
838 +
839 + /**
840 + * Rotate image
841 + * @returns {Promise}
842 + * @param {number} angle - Additional angle to rotate image
843 + * @param {boolean} isSilent - is silent execution or not
844 + * @returns {Promise<RotateStatus, ErrorMsg>}
845 + * @example
846 + * imageEditor.rotate(10); // angle = 10
847 + * imageEditor.rotate(10); // angle = 20
848 + * imageEidtor.rotate(5); // angle = 5
849 + * imageEidtor.rotate(-95); // angle = -90
850 + * imageEditor.rotate(10).then(status => {
851 + * console.log('angle: ', status.angle);
852 + * })).catch(message => {
853 + * console.log('error: ', message);
854 + * });
855 + */
856 + rotate(angle, isSilent) {
857 + return this._rotate('rotate', angle, isSilent);
858 + }
859 +
860 + /**
861 + * Set angle
862 + * @param {number} angle - Angle of image
863 + * @param {boolean} isSilent - is silent execution or not
864 + * @returns {Promise<RotateStatus, ErrorMsg>}
865 + * @example
866 + * imageEditor.setAngle(10); // angle = 10
867 + * imageEditor.rotate(10); // angle = 20
868 + * imageEidtor.setAngle(5); // angle = 5
869 + * imageEidtor.rotate(50); // angle = 55
870 + * imageEidtor.setAngle(-40); // angle = -40
871 + * imageEditor.setAngle(10).then(status => {
872 + * console.log('angle: ', status.angle);
873 + * })).catch(message => {
874 + * console.log('error: ', message);
875 + * });
876 + */
877 + setAngle(angle, isSilent) {
878 + return this._rotate('setAngle', angle, isSilent);
879 + }
880 +
881 + /**
882 + * Set drawing brush
883 + * @param {Object} option brush option
884 + * @param {Number} option.width width
885 + * @param {String} option.color color like 'FFFFFF', 'rgba(0, 0, 0, 0.5)'
886 + * @example
887 + * imageEditor.startDrawingMode('FREE_DRAWING');
888 + * imageEditor.setBrush({
889 + * width: 12,
890 + * color: 'rgba(0, 0, 0, 0.5)'
891 + * });
892 + * imageEditor.setBrush({
893 + * width: 8,
894 + * color: 'FFFFFF'
895 + * });
896 + */
897 + setBrush(option) {
898 + this._graphics.setBrush(option);
899 + }
900 +
901 + /**
902 + * Set states of current drawing shape
903 + * @param {string} type - Shape type (ex: 'rect', 'circle', 'triangle')
904 + * @param {Object} [options] - Shape options
905 + * @param {(ShapeFillOption | string)} [options.fill] - {@link ShapeFillOption} or
906 + * Shape foreground color (ex: '#fff', 'transparent')
907 + * @param {string} [options.stoke] - Shape outline color
908 + * @param {number} [options.strokeWidth] - Shape outline width
909 + * @param {number} [options.width] - Width value (When type option is 'rect', this options can use)
910 + * @param {number} [options.height] - Height value (When type option is 'rect', this options can use)
911 + * @param {number} [options.rx] - Radius x value (When type option is 'circle', this options can use)
912 + * @param {number} [options.ry] - Radius y value (When type option is 'circle', this options can use)
913 + * @param {number} [options.isRegular] - Whether resizing shape has 1:1 ratio or not
914 + * @example
915 + * imageEditor.setDrawingShape('rect', {
916 + * fill: 'red',
917 + * width: 100,
918 + * height: 200
919 + * });
920 + * @example
921 + * imageEditor.setDrawingShape('rect', {
922 + * fill: {
923 + * type: 'filter',
924 + * filter: [{blur: 0.3}, {pixelate: 20}]
925 + * },
926 + * width: 100,
927 + * height: 200
928 + * });
929 + * @example
930 + * imageEditor.setDrawingShape('circle', {
931 + * fill: 'transparent',
932 + * stroke: 'blue',
933 + * strokeWidth: 3,
934 + * rx: 10,
935 + * ry: 100
936 + * });
937 + * @example
938 + * imageEditor.setDrawingShape('triangle', { // When resizing, the shape keep the 1:1 ratio
939 + * width: 1,
940 + * height: 1,
941 + * isRegular: true
942 + * });
943 + * @example
944 + * imageEditor.setDrawingShape('circle', { // When resizing, the shape keep the 1:1 ratio
945 + * rx: 10,
946 + * ry: 10,
947 + * isRegular: true
948 + * });
949 + */
950 + setDrawingShape(type, options) {
951 + this._graphics.setDrawingShape(type, options);
952 + }
953 +
954 + setDrawingIcon(type, iconColor) {
955 + this._graphics.setIconStyle(type, iconColor);
956 + }
957 +
958 + /**
959 + * Add shape
960 + * @param {string} type - Shape type (ex: 'rect', 'circle', 'triangle')
961 + * @param {Object} options - Shape options
962 + * @param {(ShapeFillOption | string)} [options.fill] - {@link ShapeFillOption} or
963 + * Shape foreground color (ex: '#fff', 'transparent')
964 + * @param {string} [options.stroke] - Shape outline color
965 + * @param {number} [options.strokeWidth] - Shape outline width
966 + * @param {number} [options.width] - Width value (When type option is 'rect', this options can use)
967 + * @param {number} [options.height] - Height value (When type option is 'rect', this options can use)
968 + * @param {number} [options.rx] - Radius x value (When type option is 'circle', this options can use)
969 + * @param {number} [options.ry] - Radius y value (When type option is 'circle', this options can use)
970 + * @param {number} [options.left] - Shape x position
971 + * @param {number} [options.top] - Shape y position
972 + * @param {boolean} [options.isRegular] - Whether resizing shape has 1:1 ratio or not
973 + * @returns {Promise<ObjectProps, ErrorMsg>}
974 + * @example
975 + * imageEditor.addShape('rect', {
976 + * fill: 'red',
977 + * stroke: 'blue',
978 + * strokeWidth: 3,
979 + * width: 100,
980 + * height: 200,
981 + * left: 10,
982 + * top: 10,
983 + * isRegular: true
984 + * });
985 + * @example
986 + * imageEditor.addShape('circle', {
987 + * fill: 'red',
988 + * stroke: 'blue',
989 + * strokeWidth: 3,
990 + * rx: 10,
991 + * ry: 100,
992 + * isRegular: false
993 + * }).then(objectProps => {
994 + * console.log(objectProps.id);
995 + * });
996 + * @example
997 + * imageEditor.addShape('rect', {
998 + * fill: {
999 + * type: 'filter',
1000 + * filter: [{blur: 0.3}, {pixelate: 20}]
1001 + * },
1002 + * stroke: 'blue',
1003 + * strokeWidth: 3,
1004 + * rx: 10,
1005 + * ry: 100,
1006 + * isRegular: false
1007 + * }).then(objectProps => {
1008 + * console.log(objectProps.id);
1009 + * });
1010 + */
1011 + addShape(type, options) {
1012 + options = options || {};
1013 +
1014 + this._setPositions(options);
1015 +
1016 + return this.execute(commands.ADD_SHAPE, type, options);
1017 + }
1018 +
1019 + /**
1020 + * Change shape
1021 + * @param {number} id - object id
1022 + * @param {Object} options - Shape options
1023 + * @param {(ShapeFillOption | string)} [options.fill] - {@link ShapeFillOption} or
1024 + * Shape foreground color (ex: '#fff', 'transparent')
1025 + * @param {string} [options.stroke] - Shape outline color
1026 + * @param {number} [options.strokeWidth] - Shape outline width
1027 + * @param {number} [options.width] - Width value (When type option is 'rect', this options can use)
1028 + * @param {number} [options.height] - Height value (When type option is 'rect', this options can use)
1029 + * @param {number} [options.rx] - Radius x value (When type option is 'circle', this options can use)
1030 + * @param {number} [options.ry] - Radius y value (When type option is 'circle', this options can use)
1031 + * @param {boolean} [options.isRegular] - Whether resizing shape has 1:1 ratio or not
1032 + * @param {boolean} isSilent - is silent execution or not
1033 + * @returns {Promise}
1034 + * @example
1035 + * // call after selecting shape object on canvas
1036 + * imageEditor.changeShape(id, { // change rectagle or triangle
1037 + * fill: 'red',
1038 + * stroke: 'blue',
1039 + * strokeWidth: 3,
1040 + * width: 100,
1041 + * height: 200
1042 + * });
1043 + * @example
1044 + * // call after selecting shape object on canvas
1045 + * imageEditor.changeShape(id, { // change circle
1046 + * fill: 'red',
1047 + * stroke: 'blue',
1048 + * strokeWidth: 3,
1049 + * rx: 10,
1050 + * ry: 100
1051 + * });
1052 + */
1053 + changeShape(id, options, isSilent) {
1054 + const executeMethodName = isSilent ? 'executeSilent' : 'execute';
1055 +
1056 + return this[executeMethodName](commands.CHANGE_SHAPE, id, options);
1057 + }
1058 +
1059 + /**
1060 + * Add text on image
1061 + * @param {string} text - Initial input text
1062 + * @param {Object} [options] Options for generating text
1063 + * @param {Object} [options.styles] Initial styles
1064 + * @param {string} [options.styles.fill] Color
1065 + * @param {string} [options.styles.fontFamily] Font type for text
1066 + * @param {number} [options.styles.fontSize] Size
1067 + * @param {string} [options.styles.fontStyle] Type of inclination (normal / italic)
1068 + * @param {string} [options.styles.fontWeight] Type of thicker or thinner looking (normal / bold)
1069 + * @param {string} [options.styles.textAlign] Type of text align (left / center / right)
1070 + * @param {string} [options.styles.textDecoration] Type of line (underline / line-through / overline)
1071 + * @param {{x: number, y: number}} [options.position] - Initial position
1072 + * @param {boolean} [options.autofocus] - text autofocus, default is true
1073 + * @returns {Promise}
1074 + * @example
1075 + * imageEditor.addText('init text');
1076 + * @example
1077 + * imageEditor.addText('init text', {
1078 + * styles: {
1079 + * fill: '#000',
1080 + * fontSize: 20,
1081 + * fontWeight: 'bold'
1082 + * },
1083 + * position: {
1084 + * x: 10,
1085 + * y: 10
1086 + * }
1087 + * }).then(objectProps => {
1088 + * console.log(objectProps.id);
1089 + * });
1090 + */
1091 + addText(text, options) {
1092 + text = text || '';
1093 + options = options || {};
1094 +
1095 + return this.execute(commands.ADD_TEXT, text, options);
1096 + }
1097 +
1098 + /**
1099 + * Change contents of selected text object on image
1100 + * @param {number} id - object id
1101 + * @param {string} text - Changing text
1102 + * @returns {Promise<ObjectProps, ErrorMsg>}
1103 + * @example
1104 + * imageEditor.changeText(id, 'change text');
1105 + */
1106 + changeText(id, text) {
1107 + text = text || '';
1108 +
1109 + return this.execute(commands.CHANGE_TEXT, id, text);
1110 + }
1111 +
1112 + /**
1113 + * Set style
1114 + * @param {number} id - object id
1115 + * @param {Object} styleObj - text styles
1116 + * @param {string} [styleObj.fill] Color
1117 + * @param {string} [styleObj.fontFamily] Font type for text
1118 + * @param {number} [styleObj.fontSize] Size
1119 + * @param {string} [styleObj.fontStyle] Type of inclination (normal / italic)
1120 + * @param {string} [styleObj.fontWeight] Type of thicker or thinner looking (normal / bold)
1121 + * @param {string} [styleObj.textAlign] Type of text align (left / center / right)
1122 + * @param {string} [styleObj.textDecoration] Type of line (underline / line-through / overline)
1123 + * @param {boolean} isSilent - is silent execution or not
1124 + * @returns {Promise}
1125 + * @example
1126 + * imageEditor.changeTextStyle(id, {
1127 + * fontStyle: 'italic'
1128 + * });
1129 + */
1130 + changeTextStyle(id, styleObj, isSilent) {
1131 + const executeMethodName = isSilent ? 'executeSilent' : 'execute';
1132 +
1133 + return this[executeMethodName](commands.CHANGE_TEXT_STYLE, id, styleObj);
1134 + }
1135 +
1136 + /**
1137 + * change text mode
1138 + * @param {string} type - change type
1139 + * @private
1140 + */
1141 + _changeActivateMode(type) {
1142 + if (type !== 'ICON' && this.getDrawingMode() !== type) {
1143 + this.startDrawingMode(type);
1144 + }
1145 + }
1146 +
1147 + /**
1148 + * 'textChanged' event handler
1149 + * @param {Object} objectProps changed object properties
1150 + * @private
1151 + */
1152 + _onTextChanged(objectProps) {
1153 + this.changeText(objectProps.id, objectProps.text);
1154 + }
1155 +
1156 + /**
1157 + * 'iconCreateResize' event handler
1158 + * @param {Object} originPointer origin pointer
1159 + * @param {Number} originPointer.x x position
1160 + * @param {Number} originPointer.y y position
1161 + * @private
1162 + */
1163 + _onIconCreateResize(originPointer) {
1164 + this.fire(events.ICON_CREATE_RESIZE, originPointer);
1165 + }
1166 +
1167 + /**
1168 + * 'iconCreateEnd' event handler
1169 + * @param {Object} originPointer origin pointer
1170 + * @param {Number} originPointer.x x position
1171 + * @param {Number} originPointer.y y position
1172 + * @private
1173 + */
1174 + _onIconCreateEnd(originPointer) {
1175 + this.fire(events.ICON_CREATE_END, originPointer);
1176 + }
1177 +
1178 + /**
1179 + * 'textEditing' event handler
1180 + * @private
1181 + */
1182 + _onTextEditing() {
1183 + /**
1184 + * The event which starts to edit text object
1185 + * @event ImageEditor#textEditing
1186 + * @example
1187 + * imageEditor.on('textEditing', function() {
1188 + * console.log('text editing');
1189 + * });
1190 + */
1191 + this.fire(events.TEXT_EDITING);
1192 + }
1193 +
1194 + /**
1195 + * Mousedown event handler in case of 'TEXT' drawing mode
1196 + * @param {fabric.Event} event - Current mousedown event object
1197 + * @private
1198 + */
1199 + _onAddText(event) {
1200 + /**
1201 + * The event when 'TEXT' drawing mode is enabled and click non-object area.
1202 + * @event ImageEditor#addText
1203 + * @param {Object} pos
1204 + * @param {Object} pos.originPosition - Current position on origin canvas
1205 + * @param {Number} pos.originPosition.x - x
1206 + * @param {Number} pos.originPosition.y - y
1207 + * @param {Object} pos.clientPosition - Current position on client area
1208 + * @param {Number} pos.clientPosition.x - x
1209 + * @param {Number} pos.clientPosition.y - y
1210 + * @example
1211 + * imageEditor.on('addText', function(pos) {
1212 + * console.log('text position on canvas: ' + pos.originPosition);
1213 + * console.log('text position on brwoser: ' + pos.clientPosition);
1214 + * });
1215 + */
1216 + this.fire(events.ADD_TEXT, {
1217 + originPosition: event.originPosition,
1218 + clientPosition: event.clientPosition,
1219 + });
1220 + }
1221 +
1222 + /**
1223 + * 'addObject' event handler
1224 + * @param {Object} objectProps added object properties
1225 + * @private
1226 + */
1227 + _onAddObject(objectProps) {
1228 + const obj = this._graphics.getObject(objectProps.id);
1229 + this._pushAddObjectCommand(obj);
1230 + }
1231 +
1232 + /**
1233 + * 'objectAdded' event handler
1234 + * @param {Object} objectProps added object properties
1235 + * @private
1236 + */
1237 + _onObjectAdded(objectProps) {
1238 + /**
1239 + * The event when object added
1240 + * @event ImageEditor#objectAdded
1241 + * @param {ObjectProps} props - object properties
1242 + * @example
1243 + * imageEditor.on('objectAdded', function(props) {
1244 + * console.log(props);
1245 + * });
1246 + */
1247 + this.fire(OBJECT_ADDED, objectProps);
1248 +
1249 + /**
1250 + * The event when object added (deprecated)
1251 + * @event ImageEditor#addObjectAfter
1252 + * @param {ObjectProps} props - object properties
1253 + * @deprecated
1254 + */
1255 + this.fire(ADD_OBJECT_AFTER, objectProps);
1256 + }
1257 +
1258 + /**
1259 + * 'objectModified' event handler
1260 + * @param {fabric.Object} obj - selection object
1261 + * @private
1262 + */
1263 + _onObjectModified(obj) {
1264 + this._pushModifyObjectCommand(obj);
1265 + }
1266 +
1267 + /**
1268 + * 'selectionCleared' event handler
1269 + * @private
1270 + */
1271 + _selectionCleared() {
1272 + this.fire(SELECTION_CLEARED);
1273 + }
1274 +
1275 + /**
1276 + * 'selectionCreated' event handler
1277 + * @param {Object} eventTarget - Fabric object
1278 + * @private
1279 + */
1280 + _selectionCreated(eventTarget) {
1281 + this.fire(SELECTION_CREATED, eventTarget);
1282 + }
1283 +
1284 + /**
1285 + * Register custom icons
1286 + * @param {{iconType: string, pathValue: string}} infos - Infos to register icons
1287 + * @example
1288 + * imageEditor.registerIcons({
1289 + * customIcon: 'M 0 0 L 20 20 L 10 10 Z',
1290 + * customArrow: 'M 60 0 L 120 60 H 90 L 75 45 V 180 H 45 V 45 L 30 60 H 0 Z'
1291 + * });
1292 + */
1293 + registerIcons(infos) {
1294 + this._graphics.registerPaths(infos);
1295 + }
1296 +
1297 + /**
1298 + * Change canvas cursor type
1299 + * @param {string} cursorType - cursor type
1300 + * @example
1301 + * imageEditor.changeCursor('crosshair');
1302 + */
1303 + changeCursor(cursorType) {
1304 + this._graphics.changeCursor(cursorType);
1305 + }
1306 +
1307 + /**
1308 + * Add icon on canvas
1309 + * @param {string} type - Icon type ('arrow', 'cancel', custom icon name)
1310 + * @param {Object} options - Icon options
1311 + * @param {string} [options.fill] - Icon foreground color
1312 + * @param {number} [options.left] - Icon x position
1313 + * @param {number} [options.top] - Icon y position
1314 + * @returns {Promise<ObjectProps, ErrorMsg>}
1315 + * @example
1316 + * imageEditor.addIcon('arrow'); // The position is center on canvas
1317 + * @example
1318 + * imageEditor.addIcon('arrow', {
1319 + * left: 100,
1320 + * top: 100
1321 + * }).then(objectProps => {
1322 + * console.log(objectProps.id);
1323 + * });
1324 + */
1325 + addIcon(type, options) {
1326 + options = options || {};
1327 +
1328 + this._setPositions(options);
1329 +
1330 + return this.execute(commands.ADD_ICON, type, options);
1331 + }
1332 +
1333 + /**
1334 + * Change icon color
1335 + * @param {number} id - object id
1336 + * @param {string} color - Color for icon
1337 + * @returns {Promise}
1338 + * @example
1339 + * imageEditor.changeIconColor(id, '#000000');
1340 + */
1341 + changeIconColor(id, color) {
1342 + return this.execute(commands.CHANGE_ICON_COLOR, id, color);
1343 + }
1344 +
1345 + /**
1346 + * Remove an object or group by id
1347 + * @param {number} id - object id
1348 + * @returns {Promise}
1349 + * @example
1350 + * imageEditor.removeObject(id);
1351 + */
1352 + removeObject(id) {
1353 + return this.execute(commands.REMOVE_OBJECT, id);
1354 + }
1355 +
1356 + /**
1357 + * Whether it has the filter or not
1358 + * @param {string} type - Filter type
1359 + * @returns {boolean} true if it has the filter
1360 + */
1361 + hasFilter(type) {
1362 + return this._graphics.hasFilter(type);
1363 + }
1364 +
1365 + /**
1366 + * Remove filter on canvas image
1367 + * @param {string} type - Filter type
1368 + * @returns {Promise<FilterResult, ErrorMsg>}
1369 + * @example
1370 + * imageEditor.removeFilter('Grayscale').then(obj => {
1371 + * console.log('filterType: ', obj.type);
1372 + * console.log('actType: ', obj.action);
1373 + * }).catch(message => {
1374 + * console.log('error: ', message);
1375 + * });
1376 + */
1377 + removeFilter(type) {
1378 + return this.execute(commands.REMOVE_FILTER, type);
1379 + }
1380 +
1381 + /**
1382 + * Apply filter on canvas image
1383 + * @param {string} type - Filter type
1384 + * @param {Object} options - Options to apply filter
1385 + * @param {number} options.maskObjId - masking image object id
1386 + * @param {boolean} isSilent - is silent execution or not
1387 + * @returns {Promise<FilterResult, ErrorMsg>}
1388 + * @example
1389 + * imageEditor.applyFilter('Grayscale');
1390 + * @example
1391 + * imageEditor.applyFilter('mask', {maskObjId: id}).then(obj => {
1392 + * console.log('filterType: ', obj.type);
1393 + * console.log('actType: ', obj.action);
1394 + * }).catch(message => {
1395 + * console.log('error: ', message);
1396 + * });;
1397 + */
1398 + applyFilter(type, options, isSilent) {
1399 + const executeMethodName = isSilent ? 'executeSilent' : 'execute';
1400 +
1401 + return this[executeMethodName](commands.APPLY_FILTER, type, options);
1402 + }
1403 +
1404 + /**
1405 + * Get data url
1406 + * @param {Object} options - options for toDataURL
1407 + * @param {String} [options.format=png] The format of the output image. Either "jpeg" or "png"
1408 + * @param {Number} [options.quality=1] Quality level (0..1). Only used for jpeg.
1409 + * @param {Number} [options.multiplier=1] Multiplier to scale by
1410 + * @param {Number} [options.left] Cropping left offset. Introduced in fabric v1.2.14
1411 + * @param {Number} [options.top] Cropping top offset. Introduced in fabric v1.2.14
1412 + * @param {Number} [options.width] Cropping width. Introduced in fabric v1.2.14
1413 + * @param {Number} [options.height] Cropping height. Introduced in fabric v1.2.14
1414 + * @returns {string} A DOMString containing the requested data URI
1415 + * @example
1416 + * imgEl.src = imageEditor.toDataURL();
1417 + *
1418 + * imageEditor.loadImageFromURL(imageEditor.toDataURL(), 'FilterImage').then(() => {
1419 + * imageEditor.addImageObject(imgUrl);
1420 + * });
1421 + */
1422 + toDataURL(options) {
1423 + return this._graphics.toDataURL(options);
1424 + }
1425 +
1426 + /**
1427 + * Get image name
1428 + * @returns {string} image name
1429 + * @example
1430 + * console.log(imageEditor.getImageName());
1431 + */
1432 + getImageName() {
1433 + return this._graphics.getImageName();
1434 + }
1435 +
1436 + /**
1437 + * Clear undoStack
1438 + * @example
1439 + * imageEditor.clearUndoStack();
1440 + */
1441 + clearUndoStack() {
1442 + this._invoker.clearUndoStack();
1443 + }
1444 +
1445 + /**
1446 + * Clear redoStack
1447 + * @example
1448 + * imageEditor.clearRedoStack();
1449 + */
1450 + clearRedoStack() {
1451 + this._invoker.clearRedoStack();
1452 + }
1453 +
1454 + /**
1455 + * Whehter the undo stack is empty or not
1456 + * @returns {boolean}
1457 + * imageEditor.isEmptyUndoStack();
1458 + */
1459 + isEmptyUndoStack() {
1460 + return this._invoker.isEmptyUndoStack();
1461 + }
1462 +
1463 + /**
1464 + * Whehter the redo stack is empty or not
1465 + * @returns {boolean}
1466 + * imageEditor.isEmptyRedoStack();
1467 + */
1468 + isEmptyRedoStack() {
1469 + return this._invoker.isEmptyRedoStack();
1470 + }
1471 +
1472 + /**
1473 + * Resize canvas dimension
1474 + * @param {{width: number, height: number}} dimension - Max width & height
1475 + * @returns {Promise}
1476 + */
1477 + resizeCanvasDimension(dimension) {
1478 + if (!dimension) {
1479 + return Promise.reject(rejectMessages.invalidParameters);
1480 + }
1481 +
1482 + return this.execute(commands.RESIZE_CANVAS_DIMENSION, dimension);
1483 + }
1484 +
1485 + /**
1486 + * Destroy
1487 + */
1488 + destroy() {
1489 + this.stopDrawingMode();
1490 + this._detachDomEvents();
1491 + this._graphics.destroy();
1492 + this._graphics = null;
1493 +
1494 + if (this.ui) {
1495 + this.ui.destroy();
1496 + }
1497 +
1498 + forEach(
1499 + this,
1500 + (value, key) => {
1501 + this[key] = null;
1502 + },
1503 + this
1504 + );
1505 + }
1506 +
1507 + /**
1508 + * Set position
1509 + * @param {Object} options - Position options (left or top)
1510 + * @private
1511 + */
1512 + _setPositions(options) {
1513 + const centerPosition = this._graphics.getCenter();
1514 +
1515 + if (isUndefined(options.left)) {
1516 + options.left = centerPosition.left;
1517 + }
1518 +
1519 + if (isUndefined(options.top)) {
1520 + options.top = centerPosition.top;
1521 + }
1522 + }
1523 +
1524 + /**
1525 + * Set properties of active object
1526 + * @param {number} id - object id
1527 + * @param {Object} keyValue - key & value
1528 + * @returns {Promise}
1529 + * @example
1530 + * imageEditor.setObjectProperties(id, {
1531 + * left:100,
1532 + * top:100,
1533 + * width: 200,
1534 + * height: 200,
1535 + * opacity: 0.5
1536 + * });
1537 + */
1538 + setObjectProperties(id, keyValue) {
1539 + return this.execute(commands.SET_OBJECT_PROPERTIES, id, keyValue);
1540 + }
1541 +
1542 + /**
1543 + * Set properties of active object, Do not leave an invoke history.
1544 + * @param {number} id - object id
1545 + * @param {Object} keyValue - key & value
1546 + * @example
1547 + * imageEditor.setObjectPropertiesQuietly(id, {
1548 + * left:100,
1549 + * top:100,
1550 + * width: 200,
1551 + * height: 200,
1552 + * opacity: 0.5
1553 + * });
1554 + */
1555 + setObjectPropertiesQuietly(id, keyValue) {
1556 + this._graphics.setObjectProperties(id, keyValue);
1557 + }
1558 +
1559 + /**
1560 + * Get properties of active object corresponding key
1561 + * @param {number} id - object id
1562 + * @param {Array<string>|ObjectProps|string} keys - property's key
1563 + * @returns {ObjectProps} properties if id is valid or null
1564 + * @example
1565 + * var props = imageEditor.getObjectProperties(id, 'left');
1566 + * console.log(props);
1567 + * @example
1568 + * var props = imageEditor.getObjectProperties(id, ['left', 'top', 'width', 'height']);
1569 + * console.log(props);
1570 + * @example
1571 + * var props = imageEditor.getObjectProperties(id, {
1572 + * left: null,
1573 + * top: null,
1574 + * width: null,
1575 + * height: null,
1576 + * opacity: null
1577 + * });
1578 + * console.log(props);
1579 + */
1580 + getObjectProperties(id, keys) {
1581 + const object = this._graphics.getObject(id);
1582 + if (!object) {
1583 + return null;
1584 + }
1585 +
1586 + return this._graphics.getObjectProperties(id, keys);
1587 + }
1588 +
1589 + /**
1590 + * Get the canvas size
1591 + * @returns {Object} {{width: number, height: number}} canvas size
1592 + * @example
1593 + * var canvasSize = imageEditor.getCanvasSize();
1594 + * console.log(canvasSize.width);
1595 + * console.height(canvasSize.height);
1596 + */
1597 + getCanvasSize() {
1598 + return this._graphics.getCanvasSize();
1599 + }
1600 +
1601 + /**
1602 + * Get object position by originX, originY
1603 + * @param {number} id - object id
1604 + * @param {string} originX - can be 'left', 'center', 'right'
1605 + * @param {string} originY - can be 'top', 'center', 'bottom'
1606 + * @returns {Object} {{x:number, y: number}} position by origin if id is valid, or null
1607 + * @example
1608 + * var position = imageEditor.getObjectPosition(id, 'left', 'top');
1609 + * console.log(position);
1610 + */
1611 + getObjectPosition(id, originX, originY) {
1612 + return this._graphics.getObjectPosition(id, originX, originY);
1613 + }
1614 +
1615 + /**
1616 + * Set object position by originX, originY
1617 + * @param {number} id - object id
1618 + * @param {Object} posInfo - position object
1619 + * @param {number} posInfo.x - x position
1620 + * @param {number} posInfo.y - y position
1621 + * @param {string} posInfo.originX - can be 'left', 'center', 'right'
1622 + * @param {string} posInfo.originY - can be 'top', 'center', 'bottom'
1623 + * @returns {Promise}
1624 + * @example
1625 + * // align the object to 'left', 'top'
1626 + * imageEditor.setObjectPosition(id, {
1627 + * x: 0,
1628 + * y: 0,
1629 + * originX: 'left',
1630 + * originY: 'top'
1631 + * });
1632 + * @example
1633 + * // align the object to 'right', 'top'
1634 + * var canvasSize = imageEditor.getCanvasSize();
1635 + * imageEditor.setObjectPosition(id, {
1636 + * x: canvasSize.width,
1637 + * y: 0,
1638 + * originX: 'right',
1639 + * originY: 'top'
1640 + * });
1641 + * @example
1642 + * // align the object to 'left', 'bottom'
1643 + * var canvasSize = imageEditor.getCanvasSize();
1644 + * imageEditor.setObjectPosition(id, {
1645 + * x: 0,
1646 + * y: canvasSize.height,
1647 + * originX: 'left',
1648 + * originY: 'bottom'
1649 + * });
1650 + * @example
1651 + * // align the object to 'right', 'bottom'
1652 + * var canvasSize = imageEditor.getCanvasSize();
1653 + * imageEditor.setObjectPosition(id, {
1654 + * x: canvasSize.width,
1655 + * y: canvasSize.height,
1656 + * originX: 'right',
1657 + * originY: 'bottom'
1658 + * });
1659 + */
1660 + setObjectPosition(id, posInfo) {
1661 + return this.execute(commands.SET_OBJECT_POSITION, id, posInfo);
1662 + }
1663 +}
1664 +
1665 +action.mixin(ImageEditor);
1666 +CustomEvents.mixin(ImageEditor);
1667 +
1668 +export default ImageEditor;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Command interface
4 + */
5 +import snippet from 'tui-code-snippet';
6 +import errorMessage from '../factory/errorMessage';
7 +
8 +const createMessage = errorMessage.create;
9 +const errorTypes = errorMessage.types;
10 +
11 +/**
12 + * Command class
13 + * @class
14 + * @param {{name:function, execute: function, undo: function,
15 + * executeCallback: function, undoCallback: function}} actions - Command actions
16 + * @param {Array} args - passing arguments on execute, undo
17 + * @ignore
18 + */
19 +class Command {
20 + constructor(actions, args) {
21 + /**
22 + * command name
23 + * @type {string}
24 + */
25 + this.name = actions.name;
26 +
27 + /**
28 + * arguments
29 + * @type {Array}
30 + */
31 + this.args = args;
32 +
33 + /**
34 + * Execute function
35 + * @type {function}
36 + */
37 + this.execute = actions.execute;
38 +
39 + /**
40 + * Undo function
41 + * @type {function}
42 + */
43 + this.undo = actions.undo;
44 +
45 + /**
46 + * executeCallback
47 + * @type {function}
48 + */
49 + this.executeCallback = actions.executeCallback || null;
50 +
51 + /**
52 + * undoCallback
53 + * @type {function}
54 + */
55 + this.undoCallback = actions.undoCallback || null;
56 +
57 + /**
58 + * data for undo
59 + * @type {Object}
60 + */
61 + this.undoData = {};
62 + }
63 +
64 + /**
65 + * Execute action
66 + * @param {Object.<string, Component>} compMap - Components injection
67 + * @abstract
68 + */
69 + execute() {
70 + throw new Error(createMessage(errorTypes.UN_IMPLEMENTATION, 'execute'));
71 + }
72 +
73 + /**
74 + * Undo action
75 + * @param {Object.<string, Component>} compMap - Components injection
76 + * @abstract
77 + */
78 + undo() {
79 + throw new Error(createMessage(errorTypes.UN_IMPLEMENTATION, 'undo'));
80 + }
81 +
82 + /**
83 + * command for redo if undoData exists
84 + * @returns {boolean} isRedo
85 + */
86 + get isRedo() {
87 + return Object.keys(this.undoData).length;
88 + }
89 +
90 + /**
91 + * Set undoData action
92 + * @param {Object} undoData - maked undo data
93 + * @param {Object} cachedUndoDataForSilent - cached undo data
94 + * @param {boolean} isSilent - is silent execution or not
95 + * @returns {Object} cachedUndoDataForSilent
96 + */
97 + setUndoData(undoData, cachedUndoDataForSilent, isSilent) {
98 + if (cachedUndoDataForSilent) {
99 + undoData = cachedUndoDataForSilent;
100 + }
101 +
102 + if (!isSilent) {
103 + snippet.extend(this.undoData, undoData);
104 + cachedUndoDataForSilent = null;
105 + } else if (!cachedUndoDataForSilent) {
106 + cachedUndoDataForSilent = undoData;
107 + }
108 +
109 + return cachedUndoDataForSilent;
110 + }
111 +
112 + /**
113 + * Attach execute callabck
114 + * @param {function} callback - Callback after execution
115 + * @returns {Command} this
116 + */
117 + setExecuteCallback(callback) {
118 + this.executeCallback = callback;
119 +
120 + return this;
121 + }
122 +
123 + /**
124 + * Attach undo callback
125 + * @param {function} callback - Callback after undo
126 + * @returns {Command} this
127 + */
128 + setUndoCallback(callback) {
129 + this.undoCallback = callback;
130 +
131 + return this;
132 + }
133 +}
134 +
135 +export default Command;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Component interface
4 + */
5 +
6 +/**
7 + * Component interface
8 + * @class
9 + * @param {string} name - component name
10 + * @param {Graphics} graphics - Graphics instance
11 + * @ignore
12 + */
13 +class Component {
14 + constructor(name, graphics) {
15 + /**
16 + * Component name
17 + * @type {string}
18 + */
19 + this.name = name;
20 +
21 + /**
22 + * Graphics instance
23 + * @type {Graphics}
24 + */
25 + this.graphics = graphics;
26 + }
27 +
28 + /**
29 + * Fire Graphics event
30 + * @returns {Object} return value
31 + */
32 + fire(...args) {
33 + const context = this.graphics;
34 +
35 + return this.graphics.fire.apply(context, args);
36 + }
37 +
38 + /**
39 + * Save image(background) of canvas
40 + * @param {string} name - Name of image
41 + * @param {fabric.Image} oImage - Fabric image instance
42 + */
43 + setCanvasImage(name, oImage) {
44 + this.graphics.setCanvasImage(name, oImage);
45 + }
46 +
47 + /**
48 + * Returns canvas element of fabric.Canvas[[lower-canvas]]
49 + * @returns {HTMLCanvasElement}
50 + */
51 + getCanvasElement() {
52 + return this.graphics.getCanvasElement();
53 + }
54 +
55 + /**
56 + * Get fabric.Canvas instance
57 + * @returns {fabric.Canvas}
58 + */
59 + getCanvas() {
60 + return this.graphics.getCanvas();
61 + }
62 +
63 + /**
64 + * Get canvasImage (fabric.Image instance)
65 + * @returns {fabric.Image}
66 + */
67 + getCanvasImage() {
68 + return this.graphics.getCanvasImage();
69 + }
70 +
71 + /**
72 + * Get image name
73 + * @returns {string}
74 + */
75 + getImageName() {
76 + return this.graphics.getImageName();
77 + }
78 +
79 + /**
80 + * Get image editor
81 + * @returns {ImageEditor}
82 + */
83 + getEditor() {
84 + return this.graphics.getEditor();
85 + }
86 +
87 + /**
88 + * Return component name
89 + * @returns {string}
90 + */
91 + getName() {
92 + return this.name;
93 + }
94 +
95 + /**
96 + * Set image properties
97 + * @param {Object} setting - Image properties
98 + * @param {boolean} [withRendering] - If true, The changed image will be reflected in the canvas
99 + */
100 + setImageProperties(setting, withRendering) {
101 + this.graphics.setImageProperties(setting, withRendering);
102 + }
103 +
104 + /**
105 + * Set canvas dimension - css only
106 + * @param {Object} dimension - Canvas css dimension
107 + */
108 + setCanvasCssDimension(dimension) {
109 + this.graphics.setCanvasCssDimension(dimension);
110 + }
111 +
112 + /**
113 + * Set canvas dimension - css only
114 + * @param {Object} dimension - Canvas backstore dimension
115 + */
116 + setCanvasBackstoreDimension(dimension) {
117 + this.graphics.setCanvasBackstoreDimension(dimension);
118 + }
119 +
120 + /**
121 + * Adjust canvas dimension with scaling image
122 + */
123 + adjustCanvasDimension() {
124 + this.graphics.adjustCanvasDimension();
125 + }
126 +}
127 +
128 +export default Component;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview DrawingMode interface
4 + */
5 +import errorMessage from '../factory/errorMessage';
6 +
7 +const createMessage = errorMessage.create;
8 +const errorTypes = errorMessage.types;
9 +
10 +/**
11 + * DrawingMode interface
12 + * @class
13 + * @param {string} name - drawing mode name
14 + * @ignore
15 + */
16 +class DrawingMode {
17 + constructor(name) {
18 + /**
19 + * the name of drawing mode
20 + * @type {string}
21 + */
22 + this.name = name;
23 + }
24 +
25 + /**
26 + * Get this drawing mode name;
27 + * @returns {string} drawing mode name
28 + */
29 + getName() {
30 + return this.name;
31 + }
32 +
33 + /**
34 + * start this drawing mode
35 + * @param {Object} options - drawing mode options
36 + * @abstract
37 + */
38 + start() {
39 + throw new Error(createMessage(errorTypes.UN_IMPLEMENTATION, 'start'));
40 + }
41 +
42 + /**
43 + * stop this drawing mode
44 + * @abstract
45 + */
46 + stop() {
47 + throw new Error(createMessage(errorTypes.UN_IMPLEMENTATION, 'stop'));
48 + }
49 +}
50 +
51 +export default DrawingMode;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Invoker - invoke commands
4 + */
5 +import snippet from 'tui-code-snippet';
6 +import { Promise } from './util';
7 +import commandFactory from './factory/command';
8 +import { eventNames, rejectMessages } from './consts';
9 +
10 +const { isFunction, isString, CustomEvents } = snippet;
11 +
12 +/**
13 + * Invoker
14 + * @class
15 + * @ignore
16 + */
17 +class Invoker {
18 + constructor() {
19 + /**
20 + * Undo stack
21 + * @type {Array.<Command>}
22 + * @private
23 + */
24 + this._undoStack = [];
25 +
26 + /**
27 + * Redo stack
28 + * @type {Array.<Command>}
29 + * @private
30 + */
31 + this._redoStack = [];
32 +
33 + /**
34 + * Lock-flag for executing command
35 + * @type {boolean}
36 + * @private
37 + */
38 + this._isLocked = false;
39 +
40 + this._isSilent = false;
41 + }
42 +
43 + /**
44 + * Invoke command execution
45 + * @param {Command} command - Command
46 + * @returns {Promise}
47 + * @private
48 + */
49 + _invokeExecution(command) {
50 + this.lock();
51 +
52 + let { args } = command;
53 + if (!args) {
54 + args = [];
55 + }
56 +
57 + return command
58 + .execute(...args)
59 + .then((value) => {
60 + if (!this._isSilent) {
61 + this.pushUndoStack(command);
62 + }
63 + this.unlock();
64 + if (isFunction(command.executeCallback)) {
65 + command.executeCallback(value);
66 + }
67 +
68 + return value;
69 + })
70 + ['catch']((message) => {
71 + this.unlock();
72 +
73 + return Promise.reject(message);
74 + });
75 + }
76 +
77 + /**
78 + * Invoke command undo
79 + * @param {Command} command - Command
80 + * @returns {Promise}
81 + * @private
82 + */
83 + _invokeUndo(command) {
84 + this.lock();
85 +
86 + let { args } = command;
87 + if (!args) {
88 + args = [];
89 + }
90 +
91 + return command
92 + .undo(...args)
93 + .then((value) => {
94 + this.pushRedoStack(command);
95 + this.unlock();
96 + if (isFunction(command.undoCallback)) {
97 + command.undoCallback(value);
98 + }
99 +
100 + return value;
101 + })
102 + ['catch']((message) => {
103 + this.unlock();
104 +
105 + return Promise.reject(message);
106 + });
107 + }
108 +
109 + /**
110 + * fire REDO_STACK_CHANGED event
111 + * @private
112 + */
113 + _fireRedoStackChanged() {
114 + this.fire(eventNames.REDO_STACK_CHANGED, this._redoStack.length);
115 + }
116 +
117 + /**
118 + * fire UNDO_STACK_CHANGED event
119 + * @private
120 + */
121 + _fireUndoStackChanged() {
122 + this.fire(eventNames.UNDO_STACK_CHANGED, this._undoStack.length);
123 + }
124 +
125 + /**
126 + * Lock this invoker
127 + */
128 + lock() {
129 + this._isLocked = true;
130 + }
131 +
132 + /**
133 + * Unlock this invoker
134 + */
135 + unlock() {
136 + this._isLocked = false;
137 + }
138 +
139 + executeSilent(...args) {
140 + this._isSilent = true;
141 +
142 + return this.execute(...args, this._isSilent).then(() => {
143 + this._isSilent = false;
144 + });
145 + }
146 +
147 + /**
148 + * Invoke command
149 + * Store the command to the undoStack
150 + * Clear the redoStack
151 + * @param {String} commandName - Command name
152 + * @param {...*} args - Arguments for creating command
153 + * @returns {Promise}
154 + */
155 + execute(...args) {
156 + if (this._isLocked) {
157 + return Promise.reject(rejectMessages.isLock);
158 + }
159 +
160 + let [command] = args;
161 + if (isString(command)) {
162 + command = commandFactory.create(...args);
163 + }
164 +
165 + return this._invokeExecution(command).then((value) => {
166 + this.clearRedoStack();
167 +
168 + return value;
169 + });
170 + }
171 +
172 + /**
173 + * Undo command
174 + * @returns {Promise}
175 + */
176 + undo() {
177 + let command = this._undoStack.pop();
178 + let promise;
179 + let message = '';
180 +
181 + if (command && this._isLocked) {
182 + this.pushUndoStack(command, true);
183 + command = null;
184 + }
185 + if (command) {
186 + if (this.isEmptyUndoStack()) {
187 + this._fireUndoStackChanged();
188 + }
189 + promise = this._invokeUndo(command);
190 + } else {
191 + message = rejectMessages.undo;
192 + if (this._isLocked) {
193 + message = `${message} Because ${rejectMessages.isLock}`;
194 + }
195 + promise = Promise.reject(message);
196 + }
197 +
198 + return promise;
199 + }
200 +
201 + /**
202 + * Redo command
203 + * @returns {Promise}
204 + */
205 + redo() {
206 + let command = this._redoStack.pop();
207 + let promise;
208 + let message = '';
209 +
210 + if (command && this._isLocked) {
211 + this.pushRedoStack(command, true);
212 + command = null;
213 + }
214 + if (command) {
215 + if (this.isEmptyRedoStack()) {
216 + this._fireRedoStackChanged();
217 + }
218 + promise = this._invokeExecution(command);
219 + } else {
220 + message = rejectMessages.redo;
221 + if (this._isLocked) {
222 + message = `${message} Because ${rejectMessages.isLock}`;
223 + }
224 + promise = Promise.reject(message);
225 + }
226 +
227 + return promise;
228 + }
229 +
230 + /**
231 + * Push undo stack
232 + * @param {Command} command - command
233 + * @param {boolean} [isSilent] - Fire event or not
234 + */
235 + pushUndoStack(command, isSilent) {
236 + this._undoStack.push(command);
237 + if (!isSilent) {
238 + this._fireUndoStackChanged();
239 + }
240 + }
241 +
242 + /**
243 + * Push redo stack
244 + * @param {Command} command - command
245 + * @param {boolean} [isSilent] - Fire event or not
246 + */
247 + pushRedoStack(command, isSilent) {
248 + this._redoStack.push(command);
249 + if (!isSilent) {
250 + this._fireRedoStackChanged();
251 + }
252 + }
253 +
254 + /**
255 + * Return whether the redoStack is empty
256 + * @returns {boolean}
257 + */
258 + isEmptyRedoStack() {
259 + return this._redoStack.length === 0;
260 + }
261 +
262 + /**
263 + * Return whether the undoStack is empty
264 + * @returns {boolean}
265 + */
266 + isEmptyUndoStack() {
267 + return this._undoStack.length === 0;
268 + }
269 +
270 + /**
271 + * Clear undoStack
272 + */
273 + clearUndoStack() {
274 + if (!this.isEmptyUndoStack()) {
275 + this._undoStack = [];
276 + this._fireUndoStackChanged();
277 + }
278 + }
279 +
280 + /**
281 + * Clear redoStack
282 + */
283 + clearRedoStack() {
284 + if (!this.isEmptyRedoStack()) {
285 + this._redoStack = [];
286 + this._fireRedoStackChanged();
287 + }
288 + }
289 +}
290 +
291 +CustomEvents.mixin(Invoker);
292 +
293 +export default Invoker;
1 +// https://developer.mozilla.org/en-US/docs/Web/API/Element/closest
2 +// Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/if (!Element.prototype.matches)
3 +Element.prototype.matches =
4 + Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector;
5 +
6 +if (!Element.prototype.closest)
7 + Element.prototype.closest = function (s) {
8 + var el = this;
9 + if (!document.documentElement.contains(el)) return null;
10 + do {
11 + if (el.matches(s)) return el;
12 + el = el.parentElement || el.parentNode;
13 + } while (el !== null && el.nodeType === 1);
14 + return null;
15 + };
16 +
17 +/*
18 + * classList.js: Cross-browser full element.classList implementation.
19 + * 1.1.20170427
20 + *
21 + * By Eli Grey, http://eligrey.com
22 + * License: Dedicated to the public domain.
23 + * See https://github.com/eligrey/classList.js/blob/master/LICENSE.md
24 + */
25 +
26 +/*global self, document, DOMException */
27 +
28 +/*! @source http://purl.eligrey.com/github/classList.js/blob/master/classList.js */
29 +
30 +if ('document' in window.self) {
31 + // Full polyfill for browsers with no classList support
32 + // Including IE < Edge missing SVGElement.classList
33 + if (
34 + !('classList' in document.createElement('_')) ||
35 + (document.createElementNS &&
36 + !('classList' in document.createElementNS('http://www.w3.org/2000/svg', 'g')))
37 + ) {
38 + (function (view) {
39 + 'use strict';
40 +
41 + if (!('Element' in view)) return;
42 +
43 + var classListProp = 'classList',
44 + protoProp = 'prototype',
45 + elemCtrProto = view.Element[protoProp],
46 + objCtr = Object,
47 + strTrim =
48 + String[protoProp].trim ||
49 + function () {
50 + return this.replace(/^\s+|\s+$/g, '');
51 + },
52 + arrIndexOf =
53 + Array[protoProp].indexOf ||
54 + function (item) {
55 + var i = 0,
56 + len = this.length;
57 + for (; i < len; i++) {
58 + if (i in this && this[i] === item) {
59 + return i;
60 + }
61 + }
62 + return -1;
63 + },
64 + // Vendors: please allow content code to instantiate DOMExceptions
65 + DOMEx = function (type, message) {
66 + this.name = type;
67 + this.code = DOMException[type];
68 + this.message = message;
69 + },
70 + checkTokenAndGetIndex = function (classList, token) {
71 + if (token === '') {
72 + throw new DOMEx('SYNTAX_ERR', 'An invalid or illegal string was specified');
73 + }
74 + if (/\s/.test(token)) {
75 + throw new DOMEx('INVALID_CHARACTER_ERR', 'String contains an invalid character');
76 + }
77 + return arrIndexOf.call(classList, token);
78 + },
79 + ClassList = function (elem) {
80 + var trimmedClasses = strTrim.call(elem.getAttribute('class') || ''),
81 + classes = trimmedClasses ? trimmedClasses.split(/\s+/) : [],
82 + i = 0,
83 + len = classes.length;
84 + for (; i < len; i++) {
85 + this.push(classes[i]);
86 + }
87 + this._updateClassName = function () {
88 + elem.setAttribute('class', this.toString());
89 + };
90 + },
91 + classListProto = (ClassList[protoProp] = []),
92 + classListGetter = function () {
93 + return new ClassList(this);
94 + };
95 + // Most DOMException implementations don't allow calling DOMException's toString()
96 + // on non-DOMExceptions. Error's toString() is sufficient here.
97 + DOMEx[protoProp] = Error[protoProp];
98 + classListProto.item = function (i) {
99 + return this[i] || null;
100 + };
101 + classListProto.contains = function (token) {
102 + token += '';
103 + return checkTokenAndGetIndex(this, token) !== -1;
104 + };
105 + classListProto.add = function () {
106 + var tokens = arguments,
107 + i = 0,
108 + l = tokens.length,
109 + token,
110 + updated = false;
111 + do {
112 + token = tokens[i] + '';
113 + if (checkTokenAndGetIndex(this, token) === -1) {
114 + this.push(token);
115 + updated = true;
116 + }
117 + } while (++i < l);
118 +
119 + if (updated) {
120 + this._updateClassName();
121 + }
122 + };
123 + classListProto.remove = function () {
124 + var tokens = arguments,
125 + i = 0,
126 + l = tokens.length,
127 + token,
128 + updated = false,
129 + index;
130 + do {
131 + token = tokens[i] + '';
132 + index = checkTokenAndGetIndex(this, token);
133 + while (index !== -1) {
134 + this.splice(index, 1);
135 + updated = true;
136 + index = checkTokenAndGetIndex(this, token);
137 + }
138 + } while (++i < l);
139 +
140 + if (updated) {
141 + this._updateClassName();
142 + }
143 + };
144 + classListProto.toggle = function (token, force) {
145 + token += '';
146 +
147 + var result = this.contains(token),
148 + method = result ? force !== true && 'remove' : force !== false && 'add';
149 + if (method) {
150 + this[method](token);
151 + }
152 +
153 + if (force === true || force === false) {
154 + return force;
155 + } else {
156 + return !result;
157 + }
158 + };
159 + classListProto.toString = function () {
160 + return this.join(' ');
161 + };
162 +
163 + if (objCtr.defineProperty) {
164 + var classListPropDesc = {
165 + get: classListGetter,
166 + enumerable: true,
167 + configurable: true,
168 + };
169 + try {
170 + objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc);
171 + } catch (ex) {
172 + // IE 8 doesn't support enumerable:true
173 + // adding undefined to fight this issue https://github.com/eligrey/classList.js/issues/36
174 + // modernie IE8-MSW7 machine has IE8 8.0.6001.18702 and is affected
175 + if (ex.number === undefined || ex.number === -0x7ff5ec54) {
176 + classListPropDesc.enumerable = false;
177 + objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc);
178 + }
179 + }
180 + } else if (objCtr[protoProp].__defineGetter__) {
181 + elemCtrProto.__defineGetter__(classListProp, classListGetter);
182 + }
183 + })(window.self);
184 + }
185 +
186 + // There is full or partial native classList support, so just check if we need
187 + // to normalize the add/remove and toggle APIs.
188 +
189 + (function () {
190 + 'use strict';
191 +
192 + var testElement = document.createElement('_');
193 +
194 + testElement.classList.add('c1', 'c2');
195 +
196 + // Polyfill for IE 10/11 and Firefox <26, where classList.add and
197 + // classList.remove exist but support only one argument at a time.
198 + if (!testElement.classList.contains('c2')) {
199 + var createMethod = function (method) {
200 + var original = DOMTokenList.prototype[method];
201 +
202 + DOMTokenList.prototype[method] = function (token) {
203 + var i,
204 + len = arguments.length;
205 +
206 + for (i = 0; i < len; i++) {
207 + token = arguments[i];
208 + original.call(this, token);
209 + }
210 + };
211 + };
212 + createMethod('add');
213 + createMethod('remove');
214 + }
215 +
216 + testElement.classList.toggle('c3', false);
217 +
218 + // Polyfill for IE 10 and Firefox <24, where classList.toggle does not
219 + // support the second argument.
220 + if (testElement.classList.contains('c3')) {
221 + var _toggle = DOMTokenList.prototype.toggle;
222 +
223 + DOMTokenList.prototype.toggle = function (token, force) {
224 + if (1 in arguments && !this.contains(token) === !force) {
225 + return force;
226 + } else {
227 + return _toggle.call(this, token);
228 + }
229 + };
230 + }
231 +
232 + testElement = null;
233 + })();
234 +}
235 +
236 +/*!
237 + * @copyright Copyright (c) 2017 IcoMoon.io
238 + * @license Licensed under MIT license
239 + * See https://github.com/Keyamoon/svgxuse
240 + * @version 1.2.6
241 + */
242 +/*jslint browser: true */
243 +/*global XDomainRequest, MutationObserver, window */
244 +(function () {
245 + 'use strict';
246 + if (typeof window !== 'undefined' && window.addEventListener) {
247 + var cache = Object.create(null); // holds xhr objects to prevent multiple requests
248 + var checkUseElems;
249 + var tid; // timeout id
250 + var debouncedCheck = function () {
251 + clearTimeout(tid);
252 + tid = setTimeout(checkUseElems, 100);
253 + };
254 + var unobserveChanges = function () {
255 + return;
256 + };
257 + var observeChanges = function () {
258 + var observer;
259 + window.addEventListener('resize', debouncedCheck, false);
260 + window.addEventListener('orientationchange', debouncedCheck, false);
261 + if (window.MutationObserver) {
262 + observer = new MutationObserver(debouncedCheck);
263 + observer.observe(document.documentElement, {
264 + childList: true,
265 + subtree: true,
266 + attributes: true,
267 + });
268 + unobserveChanges = function () {
269 + try {
270 + observer.disconnect();
271 + window.removeEventListener('resize', debouncedCheck, false);
272 + window.removeEventListener('orientationchange', debouncedCheck, false);
273 + } catch (ignore) {}
274 + };
275 + } else {
276 + document.documentElement.addEventListener('DOMSubtreeModified', debouncedCheck, false);
277 + unobserveChanges = function () {
278 + document.documentElement.removeEventListener('DOMSubtreeModified', debouncedCheck, false);
279 + window.removeEventListener('resize', debouncedCheck, false);
280 + window.removeEventListener('orientationchange', debouncedCheck, false);
281 + };
282 + }
283 + };
284 + var createRequest = function (url) {
285 + // In IE 9, cross origin requests can only be sent using XDomainRequest.
286 + // XDomainRequest would fail if CORS headers are not set.
287 + // Therefore, XDomainRequest should only be used with cross origin requests.
288 + function getOrigin(loc) {
289 + var a;
290 + if (loc.protocol !== undefined) {
291 + a = loc;
292 + } else {
293 + a = document.createElement('a');
294 + a.href = loc;
295 + }
296 + return a.protocol.replace(/:/g, '') + a.host;
297 + }
298 + var Request;
299 + var origin;
300 + var origin2;
301 + if (window.XMLHttpRequest) {
302 + Request = new XMLHttpRequest();
303 + origin = getOrigin(location);
304 + origin2 = getOrigin(url);
305 + if (Request.withCredentials === undefined && origin2 !== '' && origin2 !== origin) {
306 + Request = XDomainRequest || undefined;
307 + } else {
308 + Request = XMLHttpRequest;
309 + }
310 + }
311 + return Request;
312 + };
313 + var xlinkNS = 'http://www.w3.org/1999/xlink';
314 + checkUseElems = function () {
315 + var base;
316 + var bcr;
317 + var fallback = ''; // optional fallback URL in case no base path to SVG file was given and no symbol definition was found.
318 + var hash;
319 + var href;
320 + var i;
321 + var inProgressCount = 0;
322 + var isHidden;
323 + var Request;
324 + var url;
325 + var uses;
326 + var xhr;
327 + function observeIfDone() {
328 + // If done with making changes, start watching for chagnes in DOM again
329 + inProgressCount -= 1;
330 + if (inProgressCount === 0) {
331 + // if all xhrs were resolved
332 + unobserveChanges(); // make sure to remove old handlers
333 + observeChanges(); // watch for changes to DOM
334 + }
335 + }
336 + function attrUpdateFunc(spec) {
337 + return function () {
338 + if (cache[spec.base] !== true) {
339 + spec.useEl.setAttributeNS(xlinkNS, 'xlink:href', '#' + spec.hash);
340 + if (spec.useEl.hasAttribute('href')) {
341 + spec.useEl.setAttribute('href', '#' + spec.hash);
342 + }
343 + }
344 + };
345 + }
346 + function onloadFunc(xhr) {
347 + return function () {
348 + var body = document.body;
349 + var x = document.createElement('x');
350 + var svg;
351 + xhr.onload = null;
352 + x.innerHTML = xhr.responseText;
353 + svg = x.getElementsByTagName('svg')[0];
354 + if (svg) {
355 + svg.setAttribute('aria-hidden', 'true');
356 + svg.style.position = 'absolute';
357 + svg.style.width = 0;
358 + svg.style.height = 0;
359 + svg.style.overflow = 'hidden';
360 + body.insertBefore(svg, body.firstChild);
361 + }
362 + observeIfDone();
363 + };
364 + }
365 + function onErrorTimeout(xhr) {
366 + return function () {
367 + xhr.onerror = null;
368 + xhr.ontimeout = null;
369 + observeIfDone();
370 + };
371 + }
372 + unobserveChanges(); // stop watching for changes to DOM
373 + // find all use elements
374 + uses = document.getElementsByTagName('use');
375 + for (i = 0; i < uses.length; i += 1) {
376 + try {
377 + bcr = uses[i].getBoundingClientRect();
378 + } catch (ignore) {
379 + // failed to get bounding rectangle of the use element
380 + bcr = false;
381 + }
382 + href =
383 + uses[i].getAttribute('href') ||
384 + uses[i].getAttributeNS(xlinkNS, 'href') ||
385 + uses[i].getAttribute('xlink:href');
386 + if (href && href.split) {
387 + url = href.split('#');
388 + } else {
389 + url = ['', ''];
390 + }
391 + base = url[0];
392 + hash = url[1];
393 + isHidden = bcr && bcr.left === 0 && bcr.right === 0 && bcr.top === 0 && bcr.bottom === 0;
394 + if (bcr && bcr.width === 0 && bcr.height === 0 && !isHidden) {
395 + // the use element is empty
396 + // if there is a reference to an external SVG, try to fetch it
397 + // use the optional fallback URL if there is no reference to an external SVG
398 + if (fallback && !base.length && hash && !document.getElementById(hash)) {
399 + base = fallback;
400 + }
401 + if (uses[i].hasAttribute('href')) {
402 + uses[i].setAttributeNS(xlinkNS, 'xlink:href', href);
403 + }
404 + if (base.length) {
405 + // schedule updating xlink:href
406 + xhr = cache[base];
407 + if (xhr !== true) {
408 + // true signifies that prepending the SVG was not required
409 + setTimeout(
410 + attrUpdateFunc({
411 + useEl: uses[i],
412 + base: base,
413 + hash: hash,
414 + }),
415 + 0
416 + );
417 + }
418 + if (xhr === undefined) {
419 + Request = createRequest(base);
420 + if (Request !== undefined) {
421 + xhr = new Request();
422 + cache[base] = xhr;
423 + xhr.onload = onloadFunc(xhr);
424 + xhr.onerror = onErrorTimeout(xhr);
425 + xhr.ontimeout = onErrorTimeout(xhr);
426 + xhr.open('GET', base);
427 + xhr.send();
428 + inProgressCount += 1;
429 + }
430 + }
431 + }
432 + } else {
433 + if (!isHidden) {
434 + if (cache[base] === undefined) {
435 + // remember this URL if the use element was not empty and no request was sent
436 + cache[base] = true;
437 + } else if (cache[base].onload) {
438 + // if it turns out that prepending the SVG is not necessary,
439 + // abort the in-progress xhr.
440 + cache[base].abort();
441 + delete cache[base].onload;
442 + cache[base] = true;
443 + }
444 + } else if (base.length && cache[base]) {
445 + setTimeout(
446 + attrUpdateFunc({
447 + useEl: uses[i],
448 + base: base,
449 + hash: hash,
450 + }),
451 + 0
452 + );
453 + }
454 + }
455 + }
456 + uses = '';
457 + inProgressCount += 1;
458 + observeIfDone();
459 + };
460 + var winLoad;
461 + winLoad = function () {
462 + window.removeEventListener('load', winLoad, false); // to prevent memory leaks
463 + tid = setTimeout(checkUseElems, 0);
464 + };
465 + if (document.readyState !== 'complete') {
466 + // The load event fires when all resources have finished loading, which allows detecting whether SVG use elements are empty.
467 + window.addEventListener('load', winLoad, false);
468 + } else {
469 + // No need to add a listener if the document is already loaded, initialize immediately.
470 + winLoad();
471 + }
472 + }
473 +})();
1 +import snippet from 'tui-code-snippet';
2 +import { HELP_MENUS } from './consts';
3 +import { getSelector, assignmentForDestroy, cls } from './util';
4 +import mainContainer from './ui/template/mainContainer';
5 +import controls from './ui/template/controls';
6 +
7 +import Theme from './ui/theme/theme';
8 +import Shape from './ui/shape';
9 +import Crop from './ui/crop';
10 +import Flip from './ui/flip';
11 +import Rotate from './ui/rotate';
12 +import Text from './ui/text';
13 +import Mask from './ui/mask';
14 +import Icon from './ui/icon';
15 +import Draw from './ui/draw';
16 +import Filter from './ui/filter';
17 +import Locale from './ui/locale/locale';
18 +
19 +const SUB_UI_COMPONENT = {
20 + Shape,
21 + Crop,
22 + Flip,
23 + Rotate,
24 + Text,
25 + Mask,
26 + Icon,
27 + Draw,
28 + Filter,
29 +};
30 +
31 +const BI_EXPRESSION_MINSIZE_WHEN_TOP_POSITION = '1300';
32 +
33 +/**
34 + * Ui class
35 + * @class
36 + * @param {string|HTMLElement} element - Wrapper's element or selector
37 + * @param {Object} [options] - Ui setting options
38 + * @param {number} options.loadImage - Init default load image
39 + * @param {number} options.initMenu - Init start menu
40 + * @param {Boolean} [options.menuBarPosition=bottom] - Let
41 + * @param {Boolean} [options.applyCropSelectionStyle=false] - Let
42 + * @param {Boolean} [options.usageStatistics=false] - Use statistics or not
43 + * @param {Object} [options.uiSize] - ui size of editor
44 + * @param {string} options.uiSize.width - width of ui
45 + * @param {string} options.uiSize.height - height of ui
46 + * @param {Object} actions - ui action instance
47 + */
48 +class Ui {
49 + constructor(element, options, actions) {
50 + this.options = this._initializeOption(options);
51 + this._actions = actions;
52 + this.submenu = false;
53 + this.imageSize = {};
54 + this.uiSize = {};
55 + this._locale = new Locale(this.options.locale);
56 + this.theme = new Theme(this.options.theme);
57 + this.eventHandler = {};
58 + this._submenuChangeTransection = false;
59 + this._selectedElement = null;
60 + this._mainElement = null;
61 + this._editorElementWrap = null;
62 + this._editorElement = null;
63 + this._menuElement = null;
64 + this._subMenuElement = null;
65 + this._makeUiElement(element);
66 + this._setUiSize();
67 + this._initMenuEvent = false;
68 +
69 + this._makeSubMenu();
70 + }
71 +
72 + /**
73 + * Destroys the instance.
74 + */
75 + destroy() {
76 + this._removeUiEvent();
77 + this._destroyAllMenu();
78 + this._selectedElement.innerHTML = '';
79 +
80 + assignmentForDestroy(this);
81 + }
82 +
83 + /**
84 + * Set Default Selection for includeUI
85 + * @param {Object} option - imageEditor options
86 + * @returns {Object} - extends selectionStyle option
87 + * @ignore
88 + */
89 + setUiDefaultSelectionStyle(option) {
90 + return snippet.extend(
91 + {
92 + applyCropSelectionStyle: true,
93 + applyGroupSelectionStyle: true,
94 + selectionStyle: {
95 + cornerStyle: 'circle',
96 + cornerSize: 16,
97 + cornerColor: '#fff',
98 + cornerStrokeColor: '#fff',
99 + transparentCorners: false,
100 + lineWidth: 2,
101 + borderColor: '#fff',
102 + },
103 + },
104 + option
105 + );
106 + }
107 +
108 + /**
109 + * Change editor size
110 + * @param {Object} resizeInfo - ui & image size info
111 + * @param {Object} [resizeInfo.uiSize] - image size dimension
112 + * @param {string} resizeInfo.uiSize.width - ui width
113 + * @param {string} resizeInfo.uiSize.height - ui height
114 + * @param {Object} [resizeInfo.imageSize] - image size dimension
115 + * @param {Number} resizeInfo.imageSize.oldWidth - old width
116 + * @param {Number} resizeInfo.imageSize.oldHeight - old height
117 + * @param {Number} resizeInfo.imageSize.newWidth - new width
118 + * @param {Number} resizeInfo.imageSize.newHeight - new height
119 + * @example
120 + * // Change the image size and ui size, and change the affected ui state together.
121 + * imageEditor.ui.resizeEditor({
122 + * imageSize: {oldWidth: 100, oldHeight: 100, newWidth: 700, newHeight: 700},
123 + * uiSize: {width: 1000, height: 1000}
124 + * });
125 + * @example
126 + * // Apply the ui state while preserving the previous attribute (for example, if responsive Ui)
127 + * imageEditor.ui.resizeEditor();
128 + */
129 + resizeEditor({ uiSize, imageSize = this.imageSize } = {}) {
130 + if (imageSize !== this.imageSize) {
131 + this.imageSize = imageSize;
132 + }
133 + if (uiSize) {
134 + this._setUiSize(uiSize);
135 + }
136 +
137 + const { width, height } = this._getCanvasMaxDimension();
138 + const editorElementStyle = this._editorElement.style;
139 + const { menuBarPosition } = this.options;
140 +
141 + editorElementStyle.height = `${height}px`;
142 + editorElementStyle.width = `${width}px`;
143 +
144 + this._setEditorPosition(menuBarPosition);
145 +
146 + this._editorElementWrap.style.bottom = `0px`;
147 + this._editorElementWrap.style.top = `0px`;
148 + this._editorElementWrap.style.left = `0px`;
149 + this._editorElementWrap.style.width = `100%`;
150 +
151 + const selectElementClassList = this._selectedElement.classList;
152 +
153 + if (
154 + menuBarPosition === 'top' &&
155 + this._selectedElement.offsetWidth < BI_EXPRESSION_MINSIZE_WHEN_TOP_POSITION
156 + ) {
157 + selectElementClassList.add('tui-image-editor-top-optimization');
158 + } else {
159 + selectElementClassList.remove('tui-image-editor-top-optimization');
160 + }
161 + }
162 +
163 + /**
164 + * Change help button status
165 + * @param {string} buttonType - target button type
166 + * @param {Boolean} enableStatus - enabled status
167 + * @ignore
168 + */
169 + changeHelpButtonEnabled(buttonType, enableStatus) {
170 + const buttonClassList = this._buttonElements[buttonType].classList;
171 +
172 + buttonClassList[enableStatus ? 'add' : 'remove']('enabled');
173 + }
174 +
175 + /**
176 + * Change delete button status
177 + * @param {Object} [options] - Ui setting options
178 + * @param {object} [options.loadImage] - Init default load image
179 + * @param {string} [options.initMenu] - Init start menu
180 + * @param {string} [options.menuBarPosition=bottom] - Let
181 + * @param {boolean} [options.applyCropSelectionStyle=false] - Let
182 + * @param {boolean} [options.usageStatistics=false] - Send statistics ping or not
183 + * @returns {Object} initialize option
184 + * @private
185 + */
186 + _initializeOption(options) {
187 + return snippet.extend(
188 + {
189 + loadImage: {
190 + path: '',
191 + name: '',
192 + },
193 + locale: {},
194 + menuIconPath: '',
195 + menu: ['crop', 'flip', 'rotate', 'draw', 'shape', 'icon', 'text', 'mask', 'filter'],
196 + initMenu: '',
197 + uiSize: {
198 + width: '100%',
199 + height: '100%',
200 + },
201 + menuBarPosition: 'bottom',
202 + },
203 + options
204 + );
205 + }
206 +
207 + /**
208 + * Set ui container size
209 + * @param {Object} uiSize - ui dimension
210 + * @param {string} uiSize.width - css width property
211 + * @param {string} uiSize.height - css height property
212 + * @private
213 + */
214 + _setUiSize(uiSize = this.options.uiSize) {
215 + const elementDimension = this._selectedElement.style;
216 + elementDimension.width = uiSize.width;
217 + elementDimension.height = uiSize.height;
218 + }
219 +
220 + /**
221 + * Make submenu dom element
222 + * @private
223 + */
224 + _makeSubMenu() {
225 + snippet.forEach(this.options.menu, (menuName) => {
226 + const SubComponentClass =
227 + SUB_UI_COMPONENT[menuName.replace(/^[a-z]/, ($0) => $0.toUpperCase())];
228 +
229 + // make menu element
230 + this._makeMenuElement(menuName);
231 +
232 + // menu btn element
233 + this._buttonElements[menuName] = this._menuElement.querySelector(`.tie-btn-${menuName}`);
234 +
235 + // submenu ui instance
236 + this[menuName] = new SubComponentClass(this._subMenuElement, {
237 + locale: this._locale,
238 + makeSvgIcon: this.theme.makeMenSvgIconSet.bind(this.theme),
239 + menuBarPosition: this.options.menuBarPosition,
240 + usageStatistics: this.options.usageStatistics,
241 + });
242 + });
243 + }
244 +
245 + /**
246 + * Make primary ui dom element
247 + * @param {string|HTMLElement} element - Wrapper's element or selector
248 + * @private
249 + */
250 + _makeUiElement(element) {
251 + let selectedElement;
252 +
253 + window.snippet = snippet;
254 +
255 + if (element.nodeType) {
256 + selectedElement = element;
257 + } else {
258 + selectedElement = document.querySelector(element);
259 + }
260 + const selector = getSelector(selectedElement);
261 +
262 + selectedElement.classList.add('tui-image-editor-container');
263 + selectedElement.innerHTML =
264 + controls({
265 + locale: this._locale,
266 + biImage: this.theme.getStyle('common.bi'),
267 + loadButtonStyle: this.theme.getStyle('loadButton'),
268 + downloadButtonStyle: this.theme.getStyle('downloadButton'),
269 + }) +
270 + mainContainer({
271 + locale: this._locale,
272 + biImage: this.theme.getStyle('common.bi'),
273 + commonStyle: this.theme.getStyle('common'),
274 + headerStyle: this.theme.getStyle('header'),
275 + loadButtonStyle: this.theme.getStyle('loadButton'),
276 + downloadButtonStyle: this.theme.getStyle('downloadButton'),
277 + submenuStyle: this.theme.getStyle('submenu'),
278 + });
279 +
280 + this._selectedElement = selectedElement;
281 + this._selectedElement.classList.add(this.options.menuBarPosition);
282 +
283 + this._mainElement = selector('.tui-image-editor-main');
284 + this._editorElementWrap = selector('.tui-image-editor-wrap');
285 + this._editorElement = selector('.tui-image-editor');
286 + this._menuElement = selector('.tui-image-editor-menu');
287 + this._subMenuElement = selector('.tui-image-editor-submenu');
288 + this._buttonElements = {
289 + download: this._selectedElement.querySelectorAll('.tui-image-editor-download-btn'),
290 + load: this._selectedElement.querySelectorAll('.tui-image-editor-load-btn'),
291 + };
292 +
293 + this._addHelpMenus();
294 + }
295 +
296 + /**
297 + * make array for help menu output, including partitions.
298 + * @returns {Array}
299 + * @private
300 + */
301 + _makeHelpMenuWithPartition() {
302 + const helpMenuWithPartition = [...HELP_MENUS, ''];
303 + helpMenuWithPartition.splice(3, 0, '');
304 +
305 + return helpMenuWithPartition;
306 + }
307 +
308 + /**
309 + * Add help menu
310 + * @private
311 + */
312 + _addHelpMenus() {
313 + const helpMenuWithPartition = this._makeHelpMenuWithPartition();
314 +
315 + snippet.forEach(helpMenuWithPartition, (menuName) => {
316 + if (!menuName) {
317 + this._makeMenuPartitionElement();
318 + } else {
319 + this._makeMenuElement(menuName, ['normal', 'disabled', 'hover'], 'help');
320 +
321 + if (menuName) {
322 + this._buttonElements[menuName] = this._menuElement.querySelector(`.tie-btn-${menuName}`);
323 + }
324 + }
325 + });
326 + }
327 +
328 + /**
329 + * Make menu partition element
330 + * @private
331 + */
332 + _makeMenuPartitionElement() {
333 + const partitionElement = document.createElement('li');
334 + const partitionInnerElement = document.createElement('div');
335 + partitionElement.className = cls('item');
336 + partitionInnerElement.className = cls('icpartition');
337 + partitionElement.appendChild(partitionInnerElement);
338 +
339 + this._menuElement.appendChild(partitionElement);
340 + }
341 +
342 + /**
343 + * Make menu button element
344 + * @param {string} menuName - menu name
345 + * @param {Array} useIconTypes - Possible values are \['normal', 'active', 'hover', 'disabled'\]
346 + * @param {string} menuType - 'normal' or 'help'
347 + * @private
348 + */
349 + _makeMenuElement(menuName, useIconTypes = ['normal', 'active', 'hover'], menuType = 'normal') {
350 + const btnElement = document.createElement('li');
351 + const menuItemHtml = this.theme.makeMenSvgIconSet(useIconTypes, menuName);
352 +
353 + this._addTooltipAttribute(btnElement, menuName);
354 + btnElement.className = `tie-btn-${menuName} ${cls('item')} ${menuType}`;
355 + btnElement.innerHTML = menuItemHtml;
356 +
357 + this._menuElement.appendChild(btnElement);
358 + }
359 +
360 + /**
361 + * Add help action event
362 + * @private
363 + */
364 + _addHelpActionEvent() {
365 + snippet.forEach(HELP_MENUS, (helpName) => {
366 + this.eventHandler[helpName] = () => this._actions.main[helpName]();
367 + this._buttonElements[helpName].addEventListener('click', this.eventHandler[helpName]);
368 + });
369 + }
370 +
371 + /**
372 + * Remove help action event
373 + * @private
374 + */
375 + _removeHelpActionEvent() {
376 + snippet.forEach(HELP_MENUS, (helpName) => {
377 + this._buttonElements[helpName].removeEventListener('click', this.eventHandler[helpName]);
378 + });
379 + }
380 +
381 + /**
382 + * Add attribute for menu tooltip
383 + * @param {HTMLElement} element - menu element
384 + * @param {string} tooltipName - tooltipName
385 + * @private
386 + */
387 + _addTooltipAttribute(element, tooltipName) {
388 + element.setAttribute(
389 + 'tooltip-content',
390 + this._locale.localize(tooltipName.replace(/^[a-z]/g, ($0) => $0.toUpperCase()))
391 + );
392 + }
393 +
394 + /**
395 + * Add download event
396 + * @private
397 + */
398 + _addDownloadEvent() {
399 + this.eventHandler.download = () => this._actions.main.download();
400 + snippet.forEach(this._buttonElements.download, (element) => {
401 + element.addEventListener('click', this.eventHandler.download);
402 + });
403 + }
404 +
405 + _removeDownloadEvent() {
406 + snippet.forEach(this._buttonElements.download, (element) => {
407 + element.removeEventListener('click', this.eventHandler.download);
408 + });
409 + }
410 +
411 + /**
412 + * Add load event
413 + * @private
414 + */
415 + _addLoadEvent() {
416 + this.eventHandler.loadImage = (event) => this._actions.main.load(event.target.files[0]);
417 +
418 + snippet.forEach(this._buttonElements.load, (element) => {
419 + element.addEventListener('change', this.eventHandler.loadImage);
420 + });
421 + }
422 +
423 + /**
424 + * Remmove load event
425 + * @private
426 + */
427 + _removeLoadEvent() {
428 + snippet.forEach(this._buttonElements.load, (element) => {
429 + element.removeEventListener('change', this.eventHandler.loadImage);
430 + });
431 + }
432 +
433 + /**
434 + * Add menu event
435 + * @param {string} menuName - menu name
436 + * @private
437 + */
438 + _addMainMenuEvent(menuName) {
439 + this.eventHandler[menuName] = () => this.changeMenu(menuName);
440 + this._buttonElements[menuName].addEventListener('click', this.eventHandler[menuName]);
441 + }
442 +
443 + /**
444 + * Add menu event
445 + * @param {string} menuName - menu name
446 + * @private
447 + */
448 + _addSubMenuEvent(menuName) {
449 + this[menuName].addEvent(this._actions[menuName]);
450 + }
451 +
452 + /**
453 + * Add menu event
454 + * @private
455 + */
456 + _addMenuEvent() {
457 + snippet.forEach(this.options.menu, (menuName) => {
458 + this._addMainMenuEvent(menuName);
459 + this._addSubMenuEvent(menuName);
460 + });
461 + }
462 +
463 + /**
464 + * Remove menu event
465 + * @private
466 + */
467 + _removeMainMenuEvent() {
468 + snippet.forEach(this.options.menu, (menuName) => {
469 + this._buttonElements[menuName].removeEventListener('click', this.eventHandler[menuName]);
470 + });
471 + }
472 +
473 + /**
474 + * Get editor area element
475 + * @returns {HTMLElement} editor area html element
476 + * @ignore
477 + */
478 + getEditorArea() {
479 + return this._editorElement;
480 + }
481 +
482 + /**
483 + * Add event for menu items
484 + * @ignore
485 + */
486 + activeMenuEvent() {
487 + if (this._initMenuEvent) {
488 + return;
489 + }
490 +
491 + this._addHelpActionEvent();
492 + this._addDownloadEvent();
493 + this._addMenuEvent();
494 + this._initMenu();
495 + this._initMenuEvent = true;
496 + }
497 +
498 + /**
499 + * Remove ui event
500 + * @private
501 + */
502 + _removeUiEvent() {
503 + this._removeHelpActionEvent();
504 + this._removeDownloadEvent();
505 + this._removeLoadEvent();
506 + this._removeMainMenuEvent();
507 + }
508 +
509 + /**
510 + * Destroy all menu instance
511 + * @private
512 + */
513 + _destroyAllMenu() {
514 + snippet.forEach(this.options.menu, (menuName) => {
515 + this[menuName].destroy();
516 + });
517 + }
518 +
519 + /**
520 + * Init canvas
521 + * @ignore
522 + */
523 + initCanvas() {
524 + const loadImageInfo = this._getLoadImage();
525 + if (loadImageInfo.path) {
526 + this._actions.main.initLoadImage(loadImageInfo.path, loadImageInfo.name).then(() => {
527 + this.activeMenuEvent();
528 + });
529 + }
530 +
531 + this._addLoadEvent();
532 +
533 + const gridVisual = document.createElement('div');
534 +
535 + gridVisual.className = cls('grid-visual');
536 + const grid = `<table>
537 + <tr><td class="dot left-top"></td><td></td><td class="dot right-top"></td></tr>
538 + <tr><td></td><td></td><td></td></tr>
539 + <tr><td class="dot left-bottom"></td><td></td><td class="dot right-bottom"></td></tr>
540 + </table>`;
541 + gridVisual.innerHTML = grid;
542 + this._editorContainerElement = this._editorElement.querySelector(
543 + '.tui-image-editor-canvas-container'
544 + );
545 + this._editorContainerElement.appendChild(gridVisual);
546 + }
547 +
548 + /**
549 + * get editor area element
550 + * @returns {Object} load image option
551 + * @private
552 + */
553 + _getLoadImage() {
554 + return this.options.loadImage;
555 + }
556 +
557 + /**
558 + * change menu
559 + * @param {string} menuName - menu name
560 + * @param {boolean} toggle - whether toogle or not
561 + * @param {boolean} discardSelection - discard selection
562 + * @ignore
563 + */
564 + changeMenu(menuName, toggle = true, discardSelection = true) {
565 + if (!this._submenuChangeTransection) {
566 + this._submenuChangeTransection = true;
567 + this._changeMenu(menuName, toggle, discardSelection);
568 + this._submenuChangeTransection = false;
569 + }
570 + }
571 +
572 + /**
573 + * change menu
574 + * @param {string} menuName - menu name
575 + * @param {boolean} toggle - whether toogle or not
576 + * @param {boolean} discardSelection - discard selection
577 + * @private
578 + */
579 + _changeMenu(menuName, toggle, discardSelection) {
580 + if (this.submenu) {
581 + this._buttonElements[this.submenu].classList.remove('active');
582 + this._mainElement.classList.remove(`tui-image-editor-menu-${this.submenu}`);
583 + if (discardSelection) {
584 + this._actions.main.discardSelection();
585 + }
586 + this._actions.main.changeSelectableAll(true);
587 + this[this.submenu].changeStandbyMode();
588 + }
589 +
590 + if (this.submenu === menuName && toggle) {
591 + this.submenu = null;
592 + } else {
593 + this._buttonElements[menuName].classList.add('active');
594 + this._mainElement.classList.add(`tui-image-editor-menu-${menuName}`);
595 + this.submenu = menuName;
596 + this[this.submenu].changeStartMode();
597 + }
598 +
599 + this.resizeEditor();
600 + }
601 +
602 + /**
603 + * Init menu
604 + * @private
605 + */
606 + _initMenu() {
607 + if (this.options.initMenu) {
608 + const evt = document.createEvent('MouseEvents');
609 + evt.initEvent('click', true, false);
610 + this._buttonElements[this.options.initMenu].dispatchEvent(evt);
611 + }
612 +
613 + if (this.icon) {
614 + this.icon.registDefaultIcon();
615 + }
616 + }
617 +
618 + /**
619 + * Get canvas max Dimension
620 + * @returns {Object} - width & height of editor
621 + * @private
622 + */
623 + _getCanvasMaxDimension() {
624 + const { maxWidth, maxHeight } = this._editorContainerElement.style;
625 + const width = parseFloat(maxWidth);
626 + const height = parseFloat(maxHeight);
627 +
628 + return {
629 + width,
630 + height,
631 + };
632 + }
633 +
634 + /**
635 + * Set editor position
636 + * @param {string} menuBarPosition - top or right or bottom or left
637 + * @private
638 + */
639 + // eslint-disable-next-line complexity
640 + _setEditorPosition(menuBarPosition) {
641 + const { width, height } = this._getCanvasMaxDimension();
642 + const editorElementStyle = this._editorElement.style;
643 + let top = 0;
644 + let left = 0;
645 +
646 + if (this.submenu) {
647 + if (menuBarPosition === 'bottom') {
648 + if (height > this._editorElementWrap.scrollHeight - 150) {
649 + top = (height - this._editorElementWrap.scrollHeight) / 2;
650 + } else {
651 + top = (150 / 2) * -1;
652 + }
653 + } else if (menuBarPosition === 'top') {
654 + if (height > this._editorElementWrap.offsetHeight - 150) {
655 + top = 150 / 2 - (height - (this._editorElementWrap.offsetHeight - 150)) / 2;
656 + } else {
657 + top = 150 / 2;
658 + }
659 + } else if (menuBarPosition === 'left') {
660 + if (width > this._editorElementWrap.offsetWidth - 248) {
661 + left = 248 / 2 - (width - (this._editorElementWrap.offsetWidth - 248)) / 2;
662 + } else {
663 + left = 248 / 2;
664 + }
665 + } else if (menuBarPosition === 'right') {
666 + if (width > this._editorElementWrap.scrollWidth - 248) {
667 + left = (width - this._editorElementWrap.scrollWidth) / 2;
668 + } else {
669 + left = (248 / 2) * -1;
670 + }
671 + }
672 + }
673 + editorElementStyle.top = `${top}px`;
674 + editorElementStyle.left = `${left}px`;
675 + }
676 +}
677 +
678 +export default Ui;
1 +import snippet from 'tui-code-snippet';
2 +import Submenu from './submenuBase';
3 +import { assignmentForDestroy } from '../util';
4 +import templateHtml from './template/submenu/crop';
5 +
6 +/**
7 + * Crop ui class
8 + * @class
9 + * @ignore
10 + */
11 +class Crop extends Submenu {
12 + constructor(subMenuElement, { locale, makeSvgIcon, menuBarPosition, usageStatistics }) {
13 + super(subMenuElement, {
14 + locale,
15 + name: 'crop',
16 + makeSvgIcon,
17 + menuBarPosition,
18 + templateHtml,
19 + usageStatistics,
20 + });
21 +
22 + this.status = 'active';
23 +
24 + this._els = {
25 + apply: this.selector('.tie-crop-button .apply'),
26 + cancel: this.selector('.tie-crop-button .cancel'),
27 + preset: this.selector('.tie-crop-preset-button'),
28 + };
29 +
30 + this.defaultPresetButton = this._els.preset.querySelector('.preset-none');
31 + }
32 +
33 + /**
34 + * Destroys the instance.
35 + */
36 + destroy() {
37 + this._removeEvent();
38 +
39 + assignmentForDestroy(this);
40 + }
41 +
42 + /**
43 + * Add event for crop
44 + * @param {Object} actions - actions for crop
45 + * @param {Function} actions.crop - crop action
46 + * @param {Function} actions.cancel - cancel action
47 + * @param {Function} actions.preset - draw rectzone at a predefined ratio
48 + */
49 + addEvent(actions) {
50 + const apply = this._applyEventHandler.bind(this);
51 + const cancel = this._cancelEventHandler.bind(this);
52 + const cropzonePreset = this._cropzonePresetEventHandler.bind(this);
53 +
54 + this.eventHandler = {
55 + apply,
56 + cancel,
57 + cropzonePreset,
58 + };
59 +
60 + this.actions = actions;
61 + this._els.apply.addEventListener('click', apply);
62 + this._els.cancel.addEventListener('click', cancel);
63 + this._els.preset.addEventListener('click', cropzonePreset);
64 + }
65 +
66 + /**
67 + * Remove event
68 + * @private
69 + */
70 + _removeEvent() {
71 + this._els.apply.removeEventListener('click', this.eventHandler.apply);
72 + this._els.cancel.removeEventListener('click', this.eventHandler.cancel);
73 + this._els.preset.removeEventListener('click', this.eventHandler.cropzonePreset);
74 + }
75 +
76 + _applyEventHandler() {
77 + this.actions.crop();
78 + this._els.apply.classList.remove('active');
79 + }
80 +
81 + _cancelEventHandler() {
82 + this.actions.cancel();
83 + this._els.apply.classList.remove('active');
84 + }
85 +
86 + _cropzonePresetEventHandler(event) {
87 + const button = event.target.closest('.tui-image-editor-button.preset');
88 + if (button) {
89 + const [presetType] = button.className.match(/preset-[^\s]+/);
90 +
91 + this._setPresetButtonActive(button);
92 + this.actions.preset(presetType);
93 + }
94 + }
95 +
96 + /**
97 + * Executed when the menu starts.
98 + */
99 + changeStartMode() {
100 + this.actions.modeChange('crop');
101 + }
102 +
103 + /**
104 + * Returns the menu to its default state.
105 + */
106 + changeStandbyMode() {
107 + this.actions.stopDrawingMode();
108 + this._setPresetButtonActive();
109 + }
110 +
111 + /**
112 + * Change apply button status
113 + * @param {Boolean} enableStatus - apply button status
114 + */
115 + changeApplyButtonStatus(enableStatus) {
116 + if (enableStatus) {
117 + this._els.apply.classList.add('active');
118 + } else {
119 + this._els.apply.classList.remove('active');
120 + }
121 + }
122 +
123 + /**
124 + * Set preset button to active status
125 + * @param {HTMLElement} button - event target element
126 + * @private
127 + */
128 + _setPresetButtonActive(button = this.defaultPresetButton) {
129 + snippet.forEach([].slice.call(this._els.preset.querySelectorAll('.preset')), (presetButton) => {
130 + presetButton.classList.remove('active');
131 + });
132 +
133 + if (button) {
134 + button.classList.add('active');
135 + }
136 + }
137 +}
138 +
139 +export default Crop;
1 +import { assignmentForDestroy, getRgb } from '../util';
2 +import Colorpicker from './tools/colorpicker';
3 +import Range from './tools/range';
4 +import Submenu from './submenuBase';
5 +import templateHtml from './template/submenu/draw';
6 +import { defaultDrawRangeValus } from '../consts';
7 +const DRAW_OPACITY = 0.7;
8 +
9 +/**
10 + * Draw ui class
11 + * @class
12 + * @ignore
13 + */
14 +class Draw extends Submenu {
15 + constructor(subMenuElement, { locale, makeSvgIcon, menuBarPosition, usageStatistics }) {
16 + super(subMenuElement, {
17 + locale,
18 + name: 'draw',
19 + makeSvgIcon,
20 + menuBarPosition,
21 + templateHtml,
22 + usageStatistics,
23 + });
24 +
25 + this._els = {
26 + lineSelectButton: this.selector('.tie-draw-line-select-button'),
27 + drawColorPicker: new Colorpicker(
28 + this.selector('.tie-draw-color'),
29 + '#00a9ff',
30 + this.toggleDirection,
31 + this.usageStatistics
32 + ),
33 + drawRange: new Range(
34 + {
35 + slider: this.selector('.tie-draw-range'),
36 + input: this.selector('.tie-draw-range-value'),
37 + },
38 + defaultDrawRangeValus
39 + ),
40 + };
41 +
42 + this.type = null;
43 + this.color = this._els.drawColorPicker.color;
44 + this.width = this._els.drawRange.value;
45 + }
46 +
47 + /**
48 + * Destroys the instance.
49 + */
50 + destroy() {
51 + this._removeEvent();
52 + this._els.drawColorPicker.destroy();
53 + this._els.drawRange.destroy();
54 +
55 + assignmentForDestroy(this);
56 + }
57 +
58 + /**
59 + * Add event for draw
60 + * @param {Object} actions - actions for crop
61 + * @param {Function} actions.setDrawMode - set draw mode
62 + */
63 + addEvent(actions) {
64 + this.eventHandler.changeDrawType = this._changeDrawType.bind(this);
65 +
66 + this.actions = actions;
67 + this._els.lineSelectButton.addEventListener('click', this.eventHandler.changeDrawType);
68 + this._els.drawColorPicker.on('change', this._changeDrawColor.bind(this));
69 + this._els.drawRange.on('change', this._changeDrawRange.bind(this));
70 + }
71 +
72 + /**
73 + * Remove event
74 + * @private
75 + */
76 + _removeEvent() {
77 + this._els.lineSelectButton.removeEventListener('click', this.eventHandler.changeDrawType);
78 + this._els.drawColorPicker.off();
79 + this._els.drawRange.off();
80 + }
81 +
82 + /**
83 + * set draw mode - action runner
84 + */
85 + setDrawMode() {
86 + this.actions.setDrawMode(this.type, {
87 + width: this.width,
88 + color: getRgb(this.color, DRAW_OPACITY),
89 + });
90 + }
91 +
92 + /**
93 + * Returns the menu to its default state.
94 + */
95 + changeStandbyMode() {
96 + this.type = null;
97 + this.actions.stopDrawingMode();
98 + this.actions.changeSelectableAll(true);
99 + this._els.lineSelectButton.classList.remove('free');
100 + this._els.lineSelectButton.classList.remove('line');
101 + }
102 +
103 + /**
104 + * Executed when the menu starts.
105 + */
106 + changeStartMode() {
107 + this.type = 'free';
108 + this._els.lineSelectButton.classList.add('free');
109 + this.setDrawMode();
110 + }
111 +
112 + /**
113 + * Change draw type event
114 + * @param {object} event - line select event
115 + * @private
116 + */
117 + _changeDrawType(event) {
118 + const button = event.target.closest('.tui-image-editor-button');
119 + if (button) {
120 + const lineType = this.getButtonType(button, ['free', 'line']);
121 + this.actions.discardSelection();
122 +
123 + if (this.type === lineType) {
124 + this.changeStandbyMode();
125 +
126 + return;
127 + }
128 +
129 + this.changeStandbyMode();
130 + this.type = lineType;
131 + this._els.lineSelectButton.classList.add(lineType);
132 + this.setDrawMode();
133 + }
134 + }
135 +
136 + /**
137 + * Change drawing color
138 + * @param {string} color - select drawing color
139 + * @private
140 + */
141 + _changeDrawColor(color) {
142 + this.color = color || 'transparent';
143 + if (!this.type) {
144 + this.changeStartMode();
145 + } else {
146 + this.setDrawMode();
147 + }
148 + }
149 +
150 + /**
151 + * Change drawing Range
152 + * @param {number} value - select drawing range
153 + * @private
154 + */
155 + _changeDrawRange(value) {
156 + this.width = value;
157 + if (!this.type) {
158 + this.changeStartMode();
159 + } else {
160 + this.setDrawMode();
161 + }
162 + }
163 +}
164 +
165 +export default Draw;
1 +import snippet from 'tui-code-snippet';
2 +import Colorpicker from './tools/colorpicker';
3 +import Range from './tools/range';
4 +import Submenu from './submenuBase';
5 +import templateHtml from './template/submenu/filter';
6 +import { toInteger, toCamelCase, assignmentForDestroy } from '../util';
7 +import { defaultFilterRangeValus as FILTER_RANGE } from '../consts';
8 +
9 +const PICKER_CONTROL_HEIGHT = '130px';
10 +const BLEND_OPTIONS = ['add', 'diff', 'subtract', 'multiply', 'screen', 'lighten', 'darken'];
11 +const FILTER_OPTIONS = [
12 + 'grayscale',
13 + 'invert',
14 + 'sepia',
15 + 'vintage',
16 + 'blur',
17 + 'sharpen',
18 + 'emboss',
19 + 'remove-white',
20 + 'brightness',
21 + 'noise',
22 + 'pixelate',
23 + 'color-filter',
24 + 'tint',
25 + 'multiply',
26 + 'blend',
27 +];
28 +
29 +const filterNameMap = {
30 + grayscale: 'grayscale',
31 + invert: 'invert',
32 + sepia: 'sepia',
33 + blur: 'blur',
34 + sharpen: 'sharpen',
35 + emboss: 'emboss',
36 + removeWhite: 'removeColor',
37 + brightness: 'brightness',
38 + contrast: 'contrast',
39 + saturation: 'saturation',
40 + vintage: 'vintage',
41 + polaroid: 'polaroid',
42 + noise: 'noise',
43 + pixelate: 'pixelate',
44 + colorFilter: 'removeColor',
45 + tint: 'blendColor',
46 + multiply: 'blendColor',
47 + blend: 'blendColor',
48 + hue: 'hue',
49 + gamma: 'gamma',
50 +};
51 +
52 +const RANGE_INSTANCE_NAMES = [
53 + 'removewhiteDistanceRange',
54 + 'colorfilterThresholeRange',
55 + 'pixelateRange',
56 + 'noiseRange',
57 + 'brightnessRange',
58 + 'tintOpacity',
59 +];
60 +const COLORPICKER_INSTANCE_NAMES = ['filterBlendColor', 'filterMultiplyColor', 'filterTintColor'];
61 +
62 +/**
63 + * Filter ui class
64 + * @class
65 + * @ignore
66 + */
67 +class Filter extends Submenu {
68 + constructor(subMenuElement, { locale, menuBarPosition, usageStatistics }) {
69 + super(subMenuElement, {
70 + locale,
71 + name: 'filter',
72 + menuBarPosition,
73 + templateHtml,
74 + usageStatistics,
75 + });
76 +
77 + this.selectBoxShow = false;
78 +
79 + this.checkedMap = {};
80 + this._makeControlElement();
81 + }
82 +
83 + /**
84 + * Destroys the instance.
85 + */
86 + destroy() {
87 + this._removeEvent();
88 + this._destroyToolInstance();
89 +
90 + assignmentForDestroy(this);
91 + }
92 +
93 + /**
94 + * Remove event for filter
95 + */
96 + _removeEvent() {
97 + snippet.forEach(FILTER_OPTIONS, (filter) => {
98 + const filterCheckElement = this.selector(`.tie-${filter}`);
99 + const filterNameCamelCase = toCamelCase(filter);
100 +
101 + filterCheckElement.removeEventListener('change', this.eventHandler[filterNameCamelCase]);
102 + });
103 +
104 + snippet.forEach([...RANGE_INSTANCE_NAMES, ...COLORPICKER_INSTANCE_NAMES], (instanceName) => {
105 + this._els[instanceName].off();
106 + });
107 +
108 + this._els.blendType.removeEventListener('change', this.eventHandler.changeBlendFilter);
109 + this._els.blendType.removeEventListener('click', this.eventHandler.changeBlendFilter);
110 + }
111 +
112 + _destroyToolInstance() {
113 + snippet.forEach([...RANGE_INSTANCE_NAMES, ...COLORPICKER_INSTANCE_NAMES], (instanceName) => {
114 + this._els[instanceName].destroy();
115 + });
116 + }
117 +
118 + /**
119 + * Add event for filter
120 + * @param {Object} actions - actions for crop
121 + * @param {Function} actions.applyFilter - apply filter option
122 + */
123 + addEvent({ applyFilter }) {
124 + const changeFilterState = (filterName) =>
125 + this._changeFilterState.bind(this, applyFilter, filterName);
126 + const changeFilterStateForRange = (filterName) => (value, isLast) =>
127 + this._changeFilterState(applyFilter, filterName, isLast);
128 +
129 + this.eventHandler = {
130 + changeBlendFilter: changeFilterState('blend'),
131 + blandTypeClick: (event) => event.stopPropagation(),
132 + };
133 +
134 + snippet.forEach(FILTER_OPTIONS, (filter) => {
135 + const filterCheckElement = this.selector(`.tie-${filter}`);
136 + const filterNameCamelCase = toCamelCase(filter);
137 + this.checkedMap[filterNameCamelCase] = filterCheckElement;
138 + this.eventHandler[filterNameCamelCase] = changeFilterState(filterNameCamelCase);
139 +
140 + filterCheckElement.addEventListener('change', this.eventHandler[filterNameCamelCase]);
141 + });
142 +
143 + this._els.removewhiteDistanceRange.on('change', changeFilterStateForRange('removeWhite'));
144 + this._els.colorfilterThresholeRange.on('change', changeFilterStateForRange('colorFilter'));
145 + this._els.pixelateRange.on('change', changeFilterStateForRange('pixelate'));
146 + this._els.noiseRange.on('change', changeFilterStateForRange('noise'));
147 + this._els.brightnessRange.on('change', changeFilterStateForRange('brightness'));
148 +
149 + this._els.filterBlendColor.on('change', this.eventHandler.changeBlendFilter);
150 + this._els.filterMultiplyColor.on('change', changeFilterState('multiply'));
151 + this._els.filterTintColor.on('change', changeFilterState('tint'));
152 + this._els.tintOpacity.on('change', changeFilterStateForRange('tint'));
153 + this._els.filterMultiplyColor.on('changeShow', this.colorPickerChangeShow.bind(this));
154 + this._els.filterTintColor.on('changeShow', this.colorPickerChangeShow.bind(this));
155 + this._els.filterBlendColor.on('changeShow', this.colorPickerChangeShow.bind(this));
156 +
157 + this._els.blendType.addEventListener('change', this.eventHandler.changeBlendFilter);
158 + this._els.blendType.addEventListener('click', this.eventHandler.blandTypeClick);
159 + }
160 +
161 + /**
162 + * Set filter for undo changed
163 + * @param {Object} chagedFilterInfos - changed command infos
164 + * @param {string} type - filter type
165 + * @param {string} action - add or remove
166 + * @param {Object} options - filter options
167 + */
168 + setFilterState(chagedFilterInfos) {
169 + const { type, options, action } = chagedFilterInfos;
170 + const filterName = this._getFilterNameFromOptions(type, options);
171 + const isRemove = action === 'remove';
172 +
173 + if (!isRemove) {
174 + this._setFilterState(filterName, options);
175 + }
176 +
177 + this.checkedMap[filterName].checked = !isRemove;
178 + }
179 +
180 + /**
181 + * Set filter for undo changed
182 + * @param {string} filterName - filter name
183 + * @param {Object} options - filter options
184 + * @private
185 + */
186 + // eslint-disable-next-line complexity
187 + _setFilterState(filterName, options) {
188 + if (filterName === 'colorFilter') {
189 + this._els.colorfilterThresholeRange.value = options.distance;
190 + } else if (filterName === 'removeWhite') {
191 + this._els.removewhiteDistanceRange.value = options.distance;
192 + } else if (filterName === 'pixelate') {
193 + this._els.pixelateRange.value = options.blocksize;
194 + } else if (filterName === 'brightness') {
195 + this._els.brightnessRange.value = options.brightness;
196 + } else if (filterName === 'noise') {
197 + this._els.noiseRange.value = options.noise;
198 + } else if (filterName === 'tint') {
199 + this._els.tintOpacity.value = options.alpha;
200 + this._els.filterTintColor.color = options.color;
201 + } else if (filterName === 'blend') {
202 + this._els.filterBlendColor.color = options.color;
203 + } else if (filterName === 'multiply') {
204 + this._els.filterMultiplyColor.color = options.color;
205 + }
206 + }
207 +
208 + /**
209 + * Get filter name
210 + * @param {string} type - filter type
211 + * @param {Object} options - filter options
212 + * @returns {string} filter name
213 + * @private
214 + */
215 + _getFilterNameFromOptions(type, options) {
216 + let filterName = type;
217 +
218 + if (type === 'removeColor') {
219 + filterName = snippet.isExisty(options.useAlpha) ? 'removeWhite' : 'colorFilter';
220 + } else if (type === 'blendColor') {
221 + filterName = {
222 + add: 'blend',
223 + multiply: 'multiply',
224 + tint: 'tint',
225 + }[options.mode];
226 + }
227 +
228 + return filterName;
229 + }
230 +
231 + /**
232 + * Add event for filter
233 + * @param {Function} applyFilter - actions for firter
234 + * @param {string} filterName - filter name
235 + * @param {boolean} [isLast] - Is last change
236 + */
237 + _changeFilterState(applyFilter, filterName, isLast = true) {
238 + const apply = this.checkedMap[filterName].checked;
239 + const type = filterNameMap[filterName];
240 +
241 + const checkboxGroup = this.checkedMap[filterName].closest('.tui-image-editor-checkbox-group');
242 + if (checkboxGroup) {
243 + if (apply) {
244 + checkboxGroup.classList.remove('tui-image-editor-disabled');
245 + } else {
246 + checkboxGroup.classList.add('tui-image-editor-disabled');
247 + }
248 + }
249 + applyFilter(apply, type, this._getFilterOption(filterName), !isLast);
250 + }
251 +
252 + /**
253 + * Get filter option
254 + * @param {String} type - filter type
255 + * @returns {Object} filter option object
256 + * @private
257 + */
258 + // eslint-disable-next-line complexity
259 + _getFilterOption(type) {
260 + const option = {};
261 + switch (type) {
262 + case 'removeWhite':
263 + option.color = '#FFFFFF';
264 + option.useAlpha = false;
265 + option.distance = parseFloat(this._els.removewhiteDistanceRange.value);
266 + break;
267 + case 'colorFilter':
268 + option.color = '#FFFFFF';
269 + option.distance = parseFloat(this._els.colorfilterThresholeRange.value);
270 + break;
271 + case 'pixelate':
272 + option.blocksize = toInteger(this._els.pixelateRange.value);
273 + break;
274 + case 'noise':
275 + option.noise = toInteger(this._els.noiseRange.value);
276 + break;
277 + case 'brightness':
278 + option.brightness = parseFloat(this._els.brightnessRange.value);
279 + break;
280 + case 'blend':
281 + option.mode = 'add';
282 + option.color = this._els.filterBlendColor.color;
283 + option.mode = this._els.blendType.value;
284 + break;
285 + case 'multiply':
286 + option.mode = 'multiply';
287 + option.color = this._els.filterMultiplyColor.color;
288 + break;
289 + case 'tint':
290 + option.mode = 'tint';
291 + option.color = this._els.filterTintColor.color;
292 + option.alpha = this._els.tintOpacity.value;
293 + break;
294 + case 'blur':
295 + option.blur = this._els.blurRange.value;
296 + break;
297 + default:
298 + break;
299 + }
300 +
301 + return option;
302 + }
303 +
304 + /**
305 + * Make submenu range and colorpicker control
306 + * @private
307 + */
308 + _makeControlElement() {
309 + this._els = {
310 + removewhiteDistanceRange: new Range(
311 + { slider: this.selector('.tie-removewhite-distance-range') },
312 + FILTER_RANGE.removewhiteDistanceRange
313 + ),
314 + brightnessRange: new Range(
315 + { slider: this.selector('.tie-brightness-range') },
316 + FILTER_RANGE.brightnessRange
317 + ),
318 + noiseRange: new Range({ slider: this.selector('.tie-noise-range') }, FILTER_RANGE.noiseRange),
319 + pixelateRange: new Range(
320 + { slider: this.selector('.tie-pixelate-range') },
321 + FILTER_RANGE.pixelateRange
322 + ),
323 + colorfilterThresholeRange: new Range(
324 + { slider: this.selector('.tie-colorfilter-threshole-range') },
325 + FILTER_RANGE.colorfilterThresholeRange
326 + ),
327 + filterTintColor: new Colorpicker(
328 + this.selector('.tie-filter-tint-color'),
329 + '#03bd9e',
330 + this.toggleDirection,
331 + this.usageStatistics
332 + ),
333 + filterMultiplyColor: new Colorpicker(
334 + this.selector('.tie-filter-multiply-color'),
335 + '#515ce6',
336 + this.toggleDirection,
337 + this.usageStatistics
338 + ),
339 + filterBlendColor: new Colorpicker(
340 + this.selector('.tie-filter-blend-color'),
341 + '#ffbb3b',
342 + this.toggleDirection,
343 + this.usageStatistics
344 + ),
345 + blurRange: FILTER_RANGE.blurFilterRange,
346 + };
347 +
348 + this._els.tintOpacity = this._pickerWithRange(this._els.filterTintColor.pickerControl);
349 + this._els.blendType = this._pickerWithSelectbox(this._els.filterBlendColor.pickerControl);
350 +
351 + this.colorPickerControls.push(this._els.filterTintColor);
352 + this.colorPickerControls.push(this._els.filterMultiplyColor);
353 + this.colorPickerControls.push(this._els.filterBlendColor);
354 + }
355 +
356 + /**
357 + * Make submenu control for picker & range mixin
358 + * @param {HTMLElement} pickerControl - pickerControl dom element
359 + * @returns {Range}
360 + * @private
361 + */
362 + _pickerWithRange(pickerControl) {
363 + const rangeWrap = document.createElement('div');
364 + const rangelabel = document.createElement('label');
365 + const slider = document.createElement('div');
366 +
367 + slider.id = 'tie-filter-tint-opacity';
368 + rangelabel.innerHTML = 'Opacity';
369 + rangeWrap.appendChild(rangelabel);
370 + rangeWrap.appendChild(slider);
371 + pickerControl.appendChild(rangeWrap);
372 + pickerControl.style.height = PICKER_CONTROL_HEIGHT;
373 +
374 + return new Range({ slider }, FILTER_RANGE.tintOpacityRange);
375 + }
376 +
377 + /**
378 + * Make submenu control for picker & selectbox
379 + * @param {HTMLElement} pickerControl - pickerControl dom element
380 + * @returns {HTMLElement}
381 + * @private
382 + */
383 + _pickerWithSelectbox(pickerControl) {
384 + const selectlistWrap = document.createElement('div');
385 + const selectlist = document.createElement('select');
386 + const optionlist = document.createElement('ul');
387 +
388 + selectlistWrap.className = 'tui-image-editor-selectlist-wrap';
389 + optionlist.className = 'tui-image-editor-selectlist';
390 +
391 + selectlistWrap.appendChild(selectlist);
392 + selectlistWrap.appendChild(optionlist);
393 +
394 + this._makeSelectOptionList(selectlist);
395 +
396 + pickerControl.appendChild(selectlistWrap);
397 + pickerControl.style.height = PICKER_CONTROL_HEIGHT;
398 +
399 + this._drawSelectOptionList(selectlist, optionlist);
400 + this._pickerWithSelectboxForAddEvent(selectlist, optionlist);
401 +
402 + return selectlist;
403 + }
404 +
405 + /**
406 + * Make selectbox option list custom style
407 + * @param {HTMLElement} selectlist - selectbox element
408 + * @param {HTMLElement} optionlist - custom option list item element
409 + * @private
410 + */
411 + _drawSelectOptionList(selectlist, optionlist) {
412 + const options = selectlist.querySelectorAll('option');
413 + snippet.forEach(options, (option) => {
414 + const optionElement = document.createElement('li');
415 + optionElement.innerHTML = option.innerHTML;
416 + optionElement.setAttribute('data-item', option.value);
417 + optionlist.appendChild(optionElement);
418 + });
419 + }
420 +
421 + /**
422 + * custome selectbox custom event
423 + * @param {HTMLElement} selectlist - selectbox element
424 + * @param {HTMLElement} optionlist - custom option list item element
425 + * @private
426 + */
427 + _pickerWithSelectboxForAddEvent(selectlist, optionlist) {
428 + optionlist.addEventListener('click', (event) => {
429 + const optionValue = event.target.getAttribute('data-item');
430 + const fireEvent = document.createEvent('HTMLEvents');
431 +
432 + selectlist.querySelector(`[value="${optionValue}"]`).selected = true;
433 + fireEvent.initEvent('change', true, true);
434 +
435 + selectlist.dispatchEvent(fireEvent);
436 +
437 + this.selectBoxShow = false;
438 + optionlist.style.display = 'none';
439 + });
440 +
441 + selectlist.addEventListener('mousedown', (event) => {
442 + event.preventDefault();
443 + this.selectBoxShow = !this.selectBoxShow;
444 + optionlist.style.display = this.selectBoxShow ? 'block' : 'none';
445 + optionlist.setAttribute('data-selectitem', selectlist.value);
446 + optionlist.querySelector(`[data-item='${selectlist.value}']`).classList.add('active');
447 + });
448 + }
449 +
450 + /**
451 + * Make option list for select control
452 + * @param {HTMLElement} selectlist - blend option select list element
453 + * @private
454 + */
455 + _makeSelectOptionList(selectlist) {
456 + snippet.forEach(BLEND_OPTIONS, (option) => {
457 + const selectOption = document.createElement('option');
458 + selectOption.setAttribute('value', option);
459 + selectOption.innerHTML = option.replace(/^[a-z]/, ($0) => $0.toUpperCase());
460 + selectlist.appendChild(selectOption);
461 + });
462 + }
463 +}
464 +
465 +export default Filter;
1 +import snippet from 'tui-code-snippet';
2 +import { assignmentForDestroy } from '../util';
3 +import Submenu from './submenuBase';
4 +import templateHtml from './template/submenu/flip';
5 +
6 +/**
7 + * Flip ui class
8 + * @class
9 + * @ignore
10 + */
11 +class Flip extends Submenu {
12 + constructor(subMenuElement, { locale, makeSvgIcon, menuBarPosition, usageStatistics }) {
13 + super(subMenuElement, {
14 + locale,
15 + name: 'flip',
16 + makeSvgIcon,
17 + menuBarPosition,
18 + templateHtml,
19 + usageStatistics,
20 + });
21 + this.flipStatus = false;
22 +
23 + this._els = {
24 + flipButton: this.selector('.tie-flip-button'),
25 + };
26 + }
27 +
28 + /**
29 + * Destroys the instance.
30 + */
31 + destroy() {
32 + this._removeEvent();
33 +
34 + assignmentForDestroy(this);
35 + }
36 +
37 + /**
38 + * Add event for flip
39 + * @param {Object} actions - actions for flip
40 + * @param {Function} actions.flip - flip action
41 + */
42 + addEvent(actions) {
43 + this.eventHandler.changeFlip = this._changeFlip.bind(this);
44 + this._actions = actions;
45 + this._els.flipButton.addEventListener('click', this.eventHandler.changeFlip);
46 + }
47 +
48 + /**
49 + * Remove event
50 + * @private
51 + */
52 + _removeEvent() {
53 + this._els.flipButton.removeEventListener('click', this.eventHandler.changeFlip);
54 + }
55 +
56 + /**
57 + * change Flip status
58 + * @param {object} event - change event
59 + * @private
60 + */
61 + _changeFlip(event) {
62 + const button = event.target.closest('.tui-image-editor-button');
63 + if (button) {
64 + const flipType = this.getButtonType(button, ['flipX', 'flipY', 'resetFlip']);
65 + if (!this.flipStatus && flipType === 'resetFlip') {
66 + return;
67 + }
68 +
69 + this._actions.flip(flipType).then((flipStatus) => {
70 + const flipClassList = this._els.flipButton.classList;
71 + this.flipStatus = false;
72 +
73 + flipClassList.remove('resetFlip');
74 + snippet.forEach(['flipX', 'flipY'], (type) => {
75 + flipClassList.remove(type);
76 + if (flipStatus[type]) {
77 + flipClassList.add(type);
78 + flipClassList.add('resetFlip');
79 + this.flipStatus = true;
80 + }
81 + });
82 + });
83 + }
84 + }
85 +}
86 +
87 +export default Flip;
1 +import snippet from 'tui-code-snippet';
2 +import Colorpicker from './tools/colorpicker';
3 +import Submenu from './submenuBase';
4 +import templateHtml from './template/submenu/icon';
5 +import { isSupportFileApi, assignmentForDestroy } from '../util';
6 +import { defaultIconPath } from '../consts';
7 +
8 +/**
9 + * Icon ui class
10 + * @class
11 + * @ignore
12 + */
13 +class Icon extends Submenu {
14 + constructor(subMenuElement, { locale, makeSvgIcon, menuBarPosition, usageStatistics }) {
15 + super(subMenuElement, {
16 + locale,
17 + name: 'icon',
18 + makeSvgIcon,
19 + menuBarPosition,
20 + templateHtml,
21 + usageStatistics,
22 + });
23 +
24 + this.iconType = null;
25 + this._iconMap = {};
26 +
27 + this._els = {
28 + registrIconButton: this.selector('.tie-icon-image-file'),
29 + addIconButton: this.selector('.tie-icon-add-button'),
30 + iconColorpicker: new Colorpicker(
31 + this.selector('.tie-icon-color'),
32 + '#ffbb3b',
33 + this.toggleDirection,
34 + this.usageStatistics
35 + ),
36 + };
37 + }
38 +
39 + /**
40 + * Destroys the instance.
41 + */
42 + destroy() {
43 + this._removeEvent();
44 + this._els.iconColorpicker.destroy();
45 +
46 + assignmentForDestroy(this);
47 + }
48 +
49 + /**
50 + * Add event for icon
51 + * @param {Object} actions - actions for icon
52 + * @param {Function} actions.registCustomIcon - register icon
53 + * @param {Function} actions.addIcon - add icon
54 + * @param {Function} actions.changeColor - change icon color
55 + */
56 + addEvent(actions) {
57 + const registerIcon = this._registerIconHandler.bind(this);
58 + const addIcon = this._addIconHandler.bind(this);
59 +
60 + this.eventHandler = {
61 + registerIcon,
62 + addIcon,
63 + };
64 +
65 + this.actions = actions;
66 + this._els.iconColorpicker.on('change', this._changeColorHandler.bind(this));
67 + this._els.registrIconButton.addEventListener('change', registerIcon);
68 + this._els.addIconButton.addEventListener('click', addIcon);
69 + }
70 +
71 + /**
72 + * Remove event
73 + * @private
74 + */
75 + _removeEvent() {
76 + this._els.iconColorpicker.off();
77 + this._els.registrIconButton.removeEventListener('change', this.eventHandler.registerIcon);
78 + this._els.addIconButton.removeEventListener('click', this.eventHandler.addIcon);
79 + }
80 +
81 + /**
82 + * Clear icon type
83 + */
84 + clearIconType() {
85 + this._els.addIconButton.classList.remove(this.iconType);
86 + this.iconType = null;
87 + }
88 +
89 + /**
90 + * Register default icon
91 + */
92 + registDefaultIcon() {
93 + snippet.forEach(defaultIconPath, (path, type) => {
94 + this.actions.registDefalutIcons(type, path);
95 + });
96 + }
97 +
98 + /**
99 + * Set icon picker color
100 + * @param {string} iconColor - rgb color string
101 + */
102 + setIconPickerColor(iconColor) {
103 + this._els.iconColorpicker.color = iconColor;
104 + }
105 +
106 + /**
107 + * Returns the menu to its default state.
108 + */
109 + changeStandbyMode() {
110 + this.clearIconType();
111 + this.actions.cancelAddIcon();
112 + }
113 +
114 + /**
115 + * Change icon color
116 + * @param {string} color - color for change
117 + * @private
118 + */
119 + _changeColorHandler(color) {
120 + color = color || 'transparent';
121 + this.actions.changeColor(color);
122 + }
123 +
124 + /**
125 + * Change icon color
126 + * @param {object} event - add button event object
127 + * @private
128 + */
129 + _addIconHandler(event) {
130 + const button = event.target.closest('.tui-image-editor-button');
131 +
132 + if (button) {
133 + const iconType = button.getAttribute('data-icontype');
134 + const iconColor = this._els.iconColorpicker.color;
135 + this.actions.discardSelection();
136 + this.actions.changeSelectableAll(false);
137 + this._els.addIconButton.classList.remove(this.iconType);
138 + this._els.addIconButton.classList.add(iconType);
139 +
140 + if (this.iconType === iconType) {
141 + this.changeStandbyMode();
142 + } else {
143 + this.actions.addIcon(iconType, iconColor);
144 + this.iconType = iconType;
145 + }
146 + }
147 + }
148 +
149 + /**
150 + * register icon
151 + * @param {object} event - file change event object
152 + * @private
153 + */
154 + _registerIconHandler(event) {
155 + let imgUrl;
156 +
157 + if (!isSupportFileApi) {
158 + alert('This browser does not support file-api');
159 + }
160 +
161 + const [file] = event.target.files;
162 +
163 + if (file) {
164 + imgUrl = URL.createObjectURL(file);
165 + this.actions.registCustomIcon(imgUrl, file);
166 + }
167 + }
168 +}
169 +
170 +export default Icon;
1 +/**
2 + * Translate messages
3 + */
4 +class Locale {
5 + constructor(locale) {
6 + this._locale = locale;
7 + }
8 +
9 + /**
10 + * localize message
11 + * @param {string} message - message who will be localized
12 + * @returns {string}
13 + */
14 + localize(message) {
15 + return this._locale[message] || message;
16 + }
17 +}
18 +
19 +export default Locale;
1 +import Submenu from './submenuBase';
2 +import { assignmentForDestroy, isSupportFileApi } from '../util';
3 +import templateHtml from './template/submenu/mask';
4 +
5 +/**
6 + * Mask ui class
7 + * @class
8 + * @ignore
9 + */
10 +class Mask extends Submenu {
11 + constructor(subMenuElement, { locale, makeSvgIcon, menuBarPosition, usageStatistics }) {
12 + super(subMenuElement, {
13 + locale,
14 + name: 'mask',
15 + makeSvgIcon,
16 + menuBarPosition,
17 + templateHtml,
18 + usageStatistics,
19 + });
20 +
21 + this._els = {
22 + applyButton: this.selector('.tie-mask-apply'),
23 + maskImageButton: this.selector('.tie-mask-image-file'),
24 + };
25 + }
26 +
27 + /**
28 + * Destroys the instance.
29 + */
30 + destroy() {
31 + this._removeEvent();
32 +
33 + assignmentForDestroy(this);
34 + }
35 +
36 + /**
37 + * Add event for mask
38 + * @param {Object} actions - actions for crop
39 + * @param {Function} actions.loadImageFromURL - load image action
40 + * @param {Function} actions.applyFilter - apply filter action
41 + */
42 + addEvent(actions) {
43 + const loadMaskFile = this._loadMaskFile.bind(this);
44 + const applyMask = this._applyMask.bind(this);
45 +
46 + this.eventHandler = {
47 + loadMaskFile,
48 + applyMask,
49 + };
50 +
51 + this.actions = actions;
52 + this._els.maskImageButton.addEventListener('change', loadMaskFile);
53 + this._els.applyButton.addEventListener('click', applyMask);
54 + }
55 +
56 + /**
57 + * Remove event
58 + * @private
59 + */
60 + _removeEvent() {
61 + this._els.maskImageButton.removeEventListener('change', this.eventHandler.loadMaskFile);
62 + this._els.applyButton.removeEventListener('click', this.eventHandler.applyMask);
63 + }
64 +
65 + /**
66 + * Apply mask
67 + * @private
68 + */
69 + _applyMask() {
70 + this.actions.applyFilter();
71 + this._els.applyButton.classList.remove('active');
72 + }
73 +
74 + /**
75 + * Load mask file
76 + * @param {object} event - File change event object
77 + * @private
78 + */
79 + _loadMaskFile(event) {
80 + let imgUrl;
81 +
82 + if (!isSupportFileApi()) {
83 + alert('This browser does not support file-api');
84 + }
85 +
86 + const [file] = event.target.files;
87 +
88 + if (file) {
89 + imgUrl = URL.createObjectURL(file);
90 + this.actions.loadImageFromURL(imgUrl, file);
91 + this._els.applyButton.classList.add('active');
92 + }
93 + }
94 +}
95 +
96 +export default Mask;
1 +import Range from './tools/range';
2 +import Submenu from './submenuBase';
3 +import templateHtml from './template/submenu/rotate';
4 +import { toInteger, assignmentForDestroy } from '../util';
5 +import { defaultRotateRangeValus } from '../consts';
6 +
7 +const CLOCKWISE = 30;
8 +const COUNTERCLOCKWISE = -30;
9 +
10 +/**
11 + * Rotate ui class
12 + * @class
13 + * @ignore
14 + */
15 +class Rotate extends Submenu {
16 + constructor(subMenuElement, { locale, makeSvgIcon, menuBarPosition, usageStatistics }) {
17 + super(subMenuElement, {
18 + locale,
19 + name: 'rotate',
20 + makeSvgIcon,
21 + menuBarPosition,
22 + templateHtml,
23 + usageStatistics,
24 + });
25 + this._value = 0;
26 +
27 + this._els = {
28 + rotateButton: this.selector('.tie-retate-button'),
29 + rotateRange: new Range(
30 + {
31 + slider: this.selector('.tie-rotate-range'),
32 + input: this.selector('.tie-ratate-range-value'),
33 + },
34 + defaultRotateRangeValus
35 + ),
36 + };
37 + }
38 +
39 + /**
40 + * Destroys the instance.
41 + */
42 + destroy() {
43 + this._removeEvent();
44 + this._els.rotateRange.destroy();
45 +
46 + assignmentForDestroy(this);
47 + }
48 +
49 + setRangeBarAngle(type, angle) {
50 + let resultAngle = angle;
51 +
52 + if (type === 'rotate') {
53 + resultAngle = parseInt(this._els.rotateRange.value, 10) + angle;
54 + }
55 +
56 + this._setRangeBarRatio(resultAngle);
57 + }
58 +
59 + _setRangeBarRatio(angle) {
60 + this._els.rotateRange.value = angle;
61 + }
62 +
63 + /**
64 + * Add event for rotate
65 + * @param {Object} actions - actions for crop
66 + * @param {Function} actions.rotate - rotate action
67 + * @param {Function} actions.setAngle - set angle action
68 + */
69 + addEvent(actions) {
70 + this.eventHandler.rotationAngleChanged = this._changeRotateForButton.bind(this);
71 +
72 + // {rotate, setAngle}
73 + this.actions = actions;
74 + this._els.rotateButton.addEventListener('click', this.eventHandler.rotationAngleChanged);
75 + this._els.rotateRange.on('change', this._changeRotateForRange.bind(this));
76 + }
77 +
78 + /**
79 + * Remove event
80 + * @private
81 + */
82 + _removeEvent() {
83 + this._els.rotateButton.removeEventListener('click', this.eventHandler.rotationAngleChanged);
84 + this._els.rotateRange.off();
85 + }
86 +
87 + /**
88 + * Change rotate for range
89 + * @param {number} value - angle value
90 + * @param {boolean} isLast - Is last change
91 + * @private
92 + */
93 + _changeRotateForRange(value, isLast) {
94 + const angle = toInteger(value);
95 + this.actions.setAngle(angle, !isLast);
96 + this._value = angle;
97 + }
98 +
99 + /**
100 + * Change rotate for button
101 + * @param {object} event - add button event object
102 + * @private
103 + */
104 + _changeRotateForButton(event) {
105 + const button = event.target.closest('.tui-image-editor-button');
106 + const angle = this._els.rotateRange.value;
107 +
108 + if (button) {
109 + const rotateType = this.getButtonType(button, ['counterclockwise', 'clockwise']);
110 + const rotateAngle = {
111 + clockwise: CLOCKWISE,
112 + counterclockwise: COUNTERCLOCKWISE,
113 + }[rotateType];
114 + const newAngle = parseInt(angle, 10) + rotateAngle;
115 + const isRotatable = newAngle >= -360 && newAngle <= 360;
116 + if (isRotatable) {
117 + this.actions.rotate(rotateAngle);
118 + }
119 + }
120 + }
121 +}
122 +
123 +export default Rotate;
1 +import Colorpicker from './tools/colorpicker';
2 +import Range from './tools/range';
3 +import Submenu from './submenuBase';
4 +import templateHtml from './template/submenu/shape';
5 +import { toInteger, assignmentForDestroy } from '../util';
6 +import { defaultShapeStrokeValus } from '../consts';
7 +
8 +const SHAPE_DEFAULT_OPTION = {
9 + stroke: '#ffbb3b',
10 + fill: '',
11 + strokeWidth: 3,
12 +};
13 +
14 +/**
15 + * Shape ui class
16 + * @class
17 + * @ignore
18 + */
19 +class Shape extends Submenu {
20 + constructor(subMenuElement, { locale, makeSvgIcon, menuBarPosition, usageStatistics }) {
21 + super(subMenuElement, {
22 + locale,
23 + name: 'shape',
24 + makeSvgIcon,
25 + menuBarPosition,
26 + templateHtml,
27 + usageStatistics,
28 + });
29 + this.type = null;
30 + this.options = SHAPE_DEFAULT_OPTION;
31 +
32 + this._els = {
33 + shapeSelectButton: this.selector('.tie-shape-button'),
34 + shapeColorButton: this.selector('.tie-shape-color-button'),
35 + strokeRange: new Range(
36 + {
37 + slider: this.selector('.tie-stroke-range'),
38 + input: this.selector('.tie-stroke-range-value'),
39 + },
40 + defaultShapeStrokeValus
41 + ),
42 + fillColorpicker: new Colorpicker(
43 + this.selector('.tie-color-fill'),
44 + '',
45 + this.toggleDirection,
46 + this.usageStatistics
47 + ),
48 + strokeColorpicker: new Colorpicker(
49 + this.selector('.tie-color-stroke'),
50 + '#ffbb3b',
51 + this.toggleDirection,
52 + this.usageStatistics
53 + ),
54 + };
55 +
56 + this.colorPickerControls.push(this._els.fillColorpicker);
57 + this.colorPickerControls.push(this._els.strokeColorpicker);
58 + }
59 +
60 + /**
61 + * Destroys the instance.
62 + */
63 + destroy() {
64 + this._removeEvent();
65 + this._els.strokeRange.destroy();
66 + this._els.fillColorpicker.destroy();
67 + this._els.strokeColorpicker.destroy();
68 +
69 + assignmentForDestroy(this);
70 + }
71 +
72 + /**
73 + * Add event for shape
74 + * @param {Object} actions - actions for shape
75 + * @param {Function} actions.changeShape - change shape mode
76 + * @param {Function} actions.setDrawingShape - set dreawing shape
77 + */
78 + addEvent(actions) {
79 + this.eventHandler.shapeTypeSelected = this._changeShapeHandler.bind(this);
80 + this.actions = actions;
81 +
82 + this._els.shapeSelectButton.addEventListener('click', this.eventHandler.shapeTypeSelected);
83 + this._els.strokeRange.on('change', this._changeStrokeRangeHandler.bind(this));
84 + this._els.fillColorpicker.on('change', this._changeFillColorHandler.bind(this));
85 + this._els.strokeColorpicker.on('change', this._changeStrokeColorHandler.bind(this));
86 + this._els.fillColorpicker.on('changeShow', this.colorPickerChangeShow.bind(this));
87 + this._els.strokeColorpicker.on('changeShow', this.colorPickerChangeShow.bind(this));
88 + }
89 +
90 + /**
91 + * Remove event
92 + * @private
93 + */
94 + _removeEvent() {
95 + this._els.shapeSelectButton.removeEventListener('click', this.eventHandler.shapeTypeSelected);
96 + this._els.strokeRange.off();
97 + this._els.fillColorpicker.off();
98 + this._els.strokeColorpicker.off();
99 + }
100 +
101 + /**
102 + * Set Shape status
103 + * @param {Object} options - options of shape status
104 + * @param {string} strokeWidth - stroke width
105 + * @param {string} strokeColor - stroke color
106 + * @param {string} fillColor - fill color
107 + */
108 + setShapeStatus({ strokeWidth, strokeColor, fillColor }) {
109 + this._els.strokeRange.value = strokeWidth;
110 + this._els.strokeColorpicker.color = strokeColor;
111 + this._els.fillColorpicker.color = fillColor;
112 + this.options.stroke = strokeColor;
113 + this.options.fill = fillColor;
114 + this.options.strokeWidth = strokeWidth;
115 +
116 + this.actions.setDrawingShape(this.type, { strokeWidth });
117 + }
118 +
119 + /**
120 + * Executed when the menu starts.
121 + */
122 + changeStartMode() {
123 + this.actions.stopDrawingMode();
124 + }
125 +
126 + /**
127 + * Returns the menu to its default state.
128 + */
129 + changeStandbyMode() {
130 + this.type = null;
131 + this.actions.changeSelectableAll(true);
132 + this._els.shapeSelectButton.classList.remove('circle');
133 + this._els.shapeSelectButton.classList.remove('triangle');
134 + this._els.shapeSelectButton.classList.remove('rect');
135 + }
136 +
137 + /**
138 + * set range stroke max value
139 + * @param {number} maxValue - expect max value for change
140 + */
141 + setMaxStrokeValue(maxValue) {
142 + let strokeMaxValue = maxValue;
143 + if (strokeMaxValue <= 0) {
144 + strokeMaxValue = defaultShapeStrokeValus.max;
145 + }
146 + this._els.strokeRange.max = strokeMaxValue;
147 + }
148 +
149 + /**
150 + * Set stroke value
151 + * @param {number} value - expect value for strokeRange change
152 + */
153 + setStrokeValue(value) {
154 + this._els.strokeRange.value = value;
155 + this._els.strokeRange.trigger('change');
156 + }
157 +
158 + /**
159 + * Get stroke value
160 + * @returns {number} - stroke range value
161 + */
162 + getStrokeValue() {
163 + return this._els.strokeRange.value;
164 + }
165 +
166 + /**
167 + * Change icon color
168 + * @param {object} event - add button event object
169 + * @private
170 + */
171 + _changeShapeHandler(event) {
172 + const button = event.target.closest('.tui-image-editor-button');
173 + if (button) {
174 + this.actions.stopDrawingMode();
175 + this.actions.discardSelection();
176 + const shapeType = this.getButtonType(button, ['circle', 'triangle', 'rect']);
177 +
178 + if (this.type === shapeType) {
179 + this.changeStandbyMode();
180 +
181 + return;
182 + }
183 + this.changeStandbyMode();
184 + this.type = shapeType;
185 + event.currentTarget.classList.add(shapeType);
186 + this.actions.changeSelectableAll(false);
187 + this.actions.modeChange('shape');
188 + }
189 + }
190 +
191 + /**
192 + * Change stroke range
193 + * @param {number} value - stroke range value
194 + * @param {boolean} isLast - Is last change
195 + * @private
196 + */
197 + _changeStrokeRangeHandler(value, isLast) {
198 + this.options.strokeWidth = toInteger(value);
199 + this.actions.changeShape(
200 + {
201 + strokeWidth: value,
202 + },
203 + !isLast
204 + );
205 +
206 + this.actions.setDrawingShape(this.type, this.options);
207 + }
208 +
209 + /**
210 + * Change shape color
211 + * @param {string} color - fill color
212 + * @private
213 + */
214 + _changeFillColorHandler(color) {
215 + color = color || 'transparent';
216 + this.options.fill = color;
217 + this.actions.changeShape({
218 + fill: color,
219 + });
220 + }
221 +
222 + /**
223 + * Change shape stroke color
224 + * @param {string} color - fill color
225 + * @private
226 + */
227 + _changeStrokeColorHandler(color) {
228 + color = color || 'transparent';
229 + this.options.stroke = color;
230 + this.actions.changeShape({
231 + stroke: color,
232 + });
233 + }
234 +}
235 +
236 +export default Shape;
1 +/**
2 + * Submenu Base Class
3 + * @class
4 + * @ignore
5 + */
6 +class Submenu {
7 + /**
8 + * @param {HTMLElement} subMenuElement - submenu dom element
9 + * @param {Locale} locale - translate text
10 + * @param {string} name - name of sub menu
11 + * @param {Object} iconStyle - style of icon
12 + * @param {string} menuBarPosition - position of menu
13 + * @param {*} templateHtml - template for SubMenuElement
14 + * @param {boolean} [usageStatistics=false] - template for SubMenuElement
15 + */
16 + constructor(
17 + subMenuElement,
18 + { locale, name, makeSvgIcon, menuBarPosition, templateHtml, usageStatistics }
19 + ) {
20 + this.subMenuElement = subMenuElement;
21 + this.menuBarPosition = menuBarPosition;
22 + this.toggleDirection = menuBarPosition === 'top' ? 'down' : 'up';
23 + this.colorPickerControls = [];
24 + this.usageStatistics = usageStatistics;
25 + this.eventHandler = {};
26 + this._makeSubMenuElement({
27 + locale,
28 + name,
29 + makeSvgIcon,
30 + templateHtml,
31 + });
32 + }
33 +
34 + /**
35 + * editor dom ui query selector
36 + * @param {string} selectName - query selector string name
37 + * @returns {HTMLElement}
38 + */
39 + selector(selectName) {
40 + return this.subMenuElement.querySelector(selectName);
41 + }
42 +
43 + /**
44 + * change show state change for colorpicker instance
45 + * @param {Colorpicker} occurredControl - target Colorpicker Instance
46 + */
47 + colorPickerChangeShow(occurredControl) {
48 + this.colorPickerControls.forEach((pickerControl) => {
49 + if (occurredControl !== pickerControl) {
50 + pickerControl.hide();
51 + }
52 + });
53 + }
54 +
55 + /**
56 + * Get butten type
57 + * @param {HTMLElement} button - event target element
58 + * @param {array} buttonNames - Array of button names
59 + * @returns {string} - button type
60 + */
61 + getButtonType(button, buttonNames) {
62 + return button.className.match(RegExp(`(${buttonNames.join('|')})`))[0];
63 + }
64 +
65 + /**
66 + * Get butten type
67 + * @param {HTMLElement} target - event target element
68 + * @param {string} removeClass - remove class name
69 + * @param {string} addClass - add class name
70 + */
71 + changeClass(target, removeClass, addClass) {
72 + target.classList.remove(removeClass);
73 + target.classList.add(addClass);
74 + }
75 +
76 + /**
77 + * Interface method whose implementation is optional.
78 + * Returns the menu to its default state.
79 + */
80 + changeStandbyMode() {}
81 +
82 + /**
83 + * Interface method whose implementation is optional.
84 + * Executed when the menu starts.
85 + */
86 + changeStartMode() {}
87 +
88 + /**
89 + * Make submenu dom element
90 + * @param {Locale} locale - translate text
91 + * @param {string} name - submenu name
92 + * @param {Object} iconStyle - icon style
93 + * @param {*} templateHtml - template for SubMenuElement
94 + * @private
95 + */
96 + _makeSubMenuElement({ locale, name, iconStyle, makeSvgIcon, templateHtml }) {
97 + const iconSubMenu = document.createElement('div');
98 + iconSubMenu.className = `tui-image-editor-menu-${name}`;
99 + iconSubMenu.innerHTML = templateHtml({
100 + locale,
101 + iconStyle,
102 + makeSvgIcon,
103 + });
104 +
105 + this.subMenuElement.appendChild(iconSubMenu);
106 + }
107 +}
108 +
109 +export default Submenu;
1 +export default ({ locale, biImage, loadButtonStyle, downloadButtonStyle }) => `
2 + <div class="tui-image-editor-controls">
3 + <div class="tui-image-editor-controls-logo">
4 + <img src="${biImage}" />
5 + </div>
6 + <ul class="tui-image-editor-menu"></ul>
7 +
8 + <div class="tui-image-editor-controls-buttons">
9 + <div style="${loadButtonStyle}">
10 + ${locale.localize('Load')}
11 + <input type="file" class="tui-image-editor-load-btn" />
12 + </div>
13 + <button class="tui-image-editor-download-btn" style="${downloadButtonStyle}">
14 + ${locale.localize('Download')}
15 + </button>
16 + </div>
17 + </div>
18 +`;
1 +export default ({
2 + locale,
3 + biImage,
4 + commonStyle,
5 + headerStyle,
6 + loadButtonStyle,
7 + downloadButtonStyle,
8 + submenuStyle,
9 +}) => `
10 + <div class="tui-image-editor-main-container" style="${commonStyle}">
11 + <div class="tui-image-editor-header" style="${headerStyle}">
12 + <div class="tui-image-editor-header-logo">
13 + <img src="${biImage}" />
14 + </div>
15 + <div class="tui-image-editor-header-buttons">
16 + <div style="${loadButtonStyle}">
17 + ${locale.localize('Load')}
18 + <input type="file" class="tui-image-editor-load-btn" />
19 + </div>
20 + <button class="tui-image-editor-download-btn" style="${downloadButtonStyle}">
21 + ${locale.localize('Download')}
22 + </button>
23 + </div>
24 + </div>
25 + <div class="tui-image-editor-main">
26 + <div class="tui-image-editor-submenu">
27 + <div class="tui-image-editor-submenu-style" style="${submenuStyle}"></div>
28 + </div>
29 + <div class="tui-image-editor-wrap">
30 + <div class="tui-image-editor-size-wrap">
31 + <div class="tui-image-editor-align-wrap">
32 + <div class="tui-image-editor"></div>
33 + </div>
34 + </div>
35 + </div>
36 + </div>
37 + </div>
38 +`;
1 +export default ({
2 + subMenuLabelActive,
3 + subMenuLabelNormal,
4 + subMenuRangeTitle,
5 + submenuPartitionVertical,
6 + submenuPartitionHorizontal,
7 + submenuCheckbox,
8 + submenuRangePointer,
9 + submenuRangeValue,
10 + submenuColorpickerTitle,
11 + submenuColorpickerButton,
12 + submenuRangeBar,
13 + submenuRangeSubbar,
14 + submenuDisabledRangePointer,
15 + submenuDisabledRangeBar,
16 + submenuDisabledRangeSubbar,
17 + submenuIconSize,
18 + menuIconSize,
19 + biSize,
20 + menuIconStyle,
21 + submenuIconStyle,
22 +}) => `
23 + .tie-icon-add-button.icon-bubble .tui-image-editor-button[data-icontype="icon-bubble"] label,
24 + .tie-icon-add-button.icon-heart .tui-image-editor-button[data-icontype="icon-heart"] label,
25 + .tie-icon-add-button.icon-location .tui-image-editor-button[data-icontype="icon-location"] label,
26 + .tie-icon-add-button.icon-polygon .tui-image-editor-button[data-icontype="icon-polygon"] label,
27 + .tie-icon-add-button.icon-star .tui-image-editor-button[data-icontype="icon-star"] label,
28 + .tie-icon-add-button.icon-star-2 .tui-image-editor-button[data-icontype="icon-star-2"] label,
29 + .tie-icon-add-button.icon-arrow-3 .tui-image-editor-button[data-icontype="icon-arrow-3"] label,
30 + .tie-icon-add-button.icon-arrow-2 .tui-image-editor-button[data-icontype="icon-arrow-2"] label,
31 + .tie-icon-add-button.icon-arrow .tui-image-editor-button[data-icontype="icon-arrow"] label,
32 + .tie-icon-add-button.icon-bubble .tui-image-editor-button[data-icontype="icon-bubble"] label,
33 + .tie-draw-line-select-button.line .tui-image-editor-button.line label,
34 + .tie-draw-line-select-button.free .tui-image-editor-button.free label,
35 + .tie-flip-button.flipX .tui-image-editor-button.flipX label,
36 + .tie-flip-button.flipY .tui-image-editor-button.flipY label,
37 + .tie-flip-button.resetFlip .tui-image-editor-button.resetFlip label,
38 + .tie-crop-button .tui-image-editor-button.apply.active label,
39 + .tie-crop-preset-button .tui-image-editor-button.preset.active label,
40 + .tie-shape-button.rect .tui-image-editor-button.rect label,
41 + .tie-shape-button.circle .tui-image-editor-button.circle label,
42 + .tie-shape-button.triangle .tui-image-editor-button.triangle label,
43 + .tie-text-effect-button .tui-image-editor-button.active label,
44 + .tie-text-align-button.left .tui-image-editor-button.left label,
45 + .tie-text-align-button.center .tui-image-editor-button.center label,
46 + .tie-text-align-button.right .tui-image-editor-button.right label,
47 + .tie-mask-apply.apply.active .tui-image-editor-button.apply label,
48 + .tui-image-editor-container .tui-image-editor-submenu .tui-image-editor-button:hover > label,
49 + .tui-image-editor-container .tui-image-editor-checkbox label > span {
50 + ${subMenuLabelActive}
51 + }
52 + .tui-image-editor-container .tui-image-editor-submenu .tui-image-editor-button > label,
53 + .tui-image-editor-container .tui-image-editor-range-wrap.tui-image-editor-newline.short label,
54 + .tui-image-editor-container .tui-image-editor-range-wrap.tui-image-editor-newline.short label > span {
55 + ${subMenuLabelNormal}
56 + }
57 + .tui-image-editor-container .tui-image-editor-range-wrap label > span {
58 + ${subMenuRangeTitle}
59 + }
60 + .tui-image-editor-container .tui-image-editor-partition > div {
61 + ${submenuPartitionVertical}
62 + }
63 + .tui-image-editor-container.left .tui-image-editor-submenu .tui-image-editor-partition > div,
64 + .tui-image-editor-container.right .tui-image-editor-submenu .tui-image-editor-partition > div {
65 + ${submenuPartitionHorizontal}
66 + }
67 + .tui-image-editor-container .tui-image-editor-checkbox label > span:before {
68 + ${submenuCheckbox}
69 + }
70 + .tui-image-editor-container .tui-image-editor-checkbox label > input:checked + span:before {
71 + border: 0;
72 + }
73 + .tui-image-editor-container .tui-image-editor-virtual-range-pointer {
74 + ${submenuRangePointer}
75 + }
76 + .tui-image-editor-container .tui-image-editor-virtual-range-bar {
77 + ${submenuRangeBar}
78 + }
79 + .tui-image-editor-container .tui-image-editor-virtual-range-subbar {
80 + ${submenuRangeSubbar}
81 + }
82 + .tui-image-editor-container .tui-image-editor-disabled .tui-image-editor-virtual-range-pointer {
83 + ${submenuDisabledRangePointer}
84 + }
85 + .tui-image-editor-container .tui-image-editor-disabled .tui-image-editor-virtual-range-subbar {
86 + ${submenuDisabledRangeSubbar}
87 + }
88 + .tui-image-editor-container .tui-image-editor-disabled .tui-image-editor-virtual-range-bar {
89 + ${submenuDisabledRangeBar}
90 + }
91 + .tui-image-editor-container .tui-image-editor-range-value {
92 + ${submenuRangeValue}
93 + }
94 + .tui-image-editor-container .tui-image-editor-submenu .tui-image-editor-button .color-picker-value + label {
95 + ${submenuColorpickerTitle}
96 + }
97 + .tui-image-editor-container .tui-image-editor-submenu .tui-image-editor-button .color-picker-value {
98 + ${submenuColorpickerButton}
99 + }
100 + .tui-image-editor-container .svg_ic-menu {
101 + ${menuIconSize}
102 + }
103 + .tui-image-editor-container .svg_ic-submenu {
104 + ${submenuIconSize}
105 + }
106 + .tui-image-editor-container .tui-image-editor-controls-logo > img,
107 + .tui-image-editor-container .tui-image-editor-header-logo > img {
108 + ${biSize}
109 + }
110 + .tui-image-editor-menu use.normal.use-default {
111 + fill-rule: evenodd;
112 + fill: ${menuIconStyle.normal.color};
113 + stroke: ${menuIconStyle.normal.color};
114 + }
115 + .tui-image-editor-menu use.active.use-default {
116 + fill-rule: evenodd;
117 + fill: ${menuIconStyle.active.color};
118 + stroke: ${menuIconStyle.active.color};
119 + }
120 + .tui-image-editor-menu use.hover.use-default {
121 + fill-rule: evenodd;
122 + fill: ${menuIconStyle.hover.color};
123 + stroke: ${menuIconStyle.hover.color};
124 + }
125 + .tui-image-editor-menu use.disabled.use-default {
126 + fill-rule: evenodd;
127 + fill: ${menuIconStyle.disabled.color};
128 + stroke: ${menuIconStyle.disabled.color};
129 + }
130 + .tui-image-editor-submenu use.normal.use-default {
131 + fill-rule: evenodd;
132 + fill: ${submenuIconStyle.normal.color};
133 + stroke: ${submenuIconStyle.normal.color};
134 + }
135 + .tui-image-editor-submenu use.active.use-default {
136 + fill-rule: evenodd;
137 + fill: ${submenuIconStyle.active.color};
138 + stroke: ${submenuIconStyle.active.color};
139 + }
140 +`;
1 +/**
2 + * @param {Object} submenuInfo - submenu info for make template
3 + * @param {Locale} locale - Translate text
4 + * @param {Function} makeSvgIcon - svg icon generator
5 + * @returns {string}
6 + */
7 +export default ({ locale, makeSvgIcon }) => `
8 + <ul class="tui-image-editor-submenu-item">
9 + <li class="tie-crop-preset-button">
10 + <div class="tui-image-editor-button preset preset-none active">
11 + <div>
12 + ${makeSvgIcon(['normal', 'active'], 'shape-rectangle', true)}
13 + </div>
14 + <label> ${locale.localize('Custom')} </label>
15 + </div>
16 + <div class="tui-image-editor-button preset preset-square">
17 + <div>
18 + ${makeSvgIcon(['normal', 'active'], 'crop', true)}
19 + </div>
20 + <label> ${locale.localize('Square')} </label>
21 + </div>
22 + <div class="tui-image-editor-button preset preset-3-2">
23 + <div>
24 + ${makeSvgIcon(['normal', 'active'], 'crop', true)}
25 + </div>
26 + <label> ${locale.localize('3:2')} </label>
27 + </div>
28 + <div class="tui-image-editor-button preset preset-4-3">
29 + <div>
30 + ${makeSvgIcon(['normal', 'active'], 'crop', true)}
31 + </div>
32 + <label> ${locale.localize('4:3')} </label>
33 + </div>
34 + <div class="tui-image-editor-button preset preset-5-4">
35 + <div>
36 + ${makeSvgIcon(['normal', 'active'], 'crop', true)}
37 + </div>
38 + <label> ${locale.localize('5:4')} </label>
39 + </div>
40 + <div class="tui-image-editor-button preset preset-7-5">
41 + <div>
42 + ${makeSvgIcon(['normal', 'active'], 'crop', true)}
43 + </div>
44 + <label> ${locale.localize('7:5')} </label>
45 + </div>
46 + <div class="tui-image-editor-button preset preset-16-9">
47 + <div>
48 + ${makeSvgIcon(['normal', 'active'], 'crop', true)}
49 + </div>
50 + <label> ${locale.localize('16:9')} </label>
51 + </div>
52 + </li>
53 + <li class="tui-image-editor-partition tui-image-editor-newline">
54 + </li>
55 + <li class="tui-image-editor-partition only-left-right">
56 + <div></div>
57 + </li>
58 + <li class="tie-crop-button action">
59 + <div class="tui-image-editor-button apply">
60 + ${makeSvgIcon(['normal', 'active'], 'apply')}
61 + <label>
62 + ${locale.localize('Apply')}
63 + </label>
64 + </div>
65 + <div class="tui-image-editor-button cancel">
66 + ${makeSvgIcon(['normal', 'active'], 'cancel')}
67 + <label>
68 + ${locale.localize('Cancel')}
69 + </label>
70 + </div>
71 + </li>
72 + </ul>
73 +`;
1 +/**
2 + * @param {Object} submenuInfo - submenu info for make template
3 + * @param {Locale} locale - Translate text
4 + * @param {Function} makeSvgIcon - svg icon generator
5 + * @returns {string}
6 + */
7 +export default ({ locale, makeSvgIcon }) => `
8 + <ul class="tui-image-editor-submenu-item">
9 + <li class="tie-draw-line-select-button">
10 + <div class="tui-image-editor-button free">
11 + <div>
12 + ${makeSvgIcon(['normal', 'active'], 'draw-free', true)}
13 + </div>
14 + <label>
15 + ${locale.localize('Free')}
16 + </label>
17 + </div>
18 + <div class="tui-image-editor-button line">
19 + <div>
20 + ${makeSvgIcon(['normal', 'active'], 'draw-line', true)}
21 + </div>
22 + <label>
23 + ${locale.localize('Straight')}
24 + </label>
25 + </div>
26 + </li>
27 + <li class="tui-image-editor-partition">
28 + <div></div>
29 + </li>
30 + <li>
31 + <div class="tie-draw-color" title="${locale.localize('Color')}"></div>
32 + </li>
33 + <li class="tui-image-editor-partition only-left-right">
34 + <div></div>
35 + </li>
36 + <li class="tui-image-editor-newline tui-image-editor-range-wrap">
37 + <label class="range">${locale.localize('Range')}</label>
38 + <div class="tie-draw-range"></div>
39 + <input class="tie-draw-range-value tui-image-editor-range-value" value="0" />
40 + </li>
41 + </ul>
42 +`;
1 +/**
2 + * @param {Locale} locale - Translate text
3 + * @returns {string}
4 + */
5 +export default ({ locale }) => `
6 + <ul class="tui-image-editor-submenu-item">
7 + <li class="tui-image-editor-submenu-align">
8 + <div class="tui-image-editor-checkbox-wrap fixed-width">
9 + <div class="tui-image-editor-checkbox">
10 + <label>
11 + <input type="checkbox" class="tie-grayscale">
12 + <span>${locale.localize('Grayscale')}</span>
13 + </label>
14 + </div>
15 + <div class="tui-image-editor-checkbox">
16 + <label>
17 + <input type="checkbox" class="tie-invert">
18 + <span>${locale.localize('Invert')}</span>
19 + </label>
20 + </div>
21 + <div class="tui-image-editor-checkbox">
22 + <label>
23 + <input type="checkbox" class="tie-sepia">
24 + <span>${locale.localize('Sepia')}</span>
25 + </label>
26 + </div>
27 + <div class="tui-image-editor-checkbox">
28 + <label>
29 + <input type="checkbox" class="tie-vintage">
30 + <span>${locale.localize('Sepia2')}</span>
31 + </label>
32 + </div>
33 + <div class="tui-image-editor-checkbox">
34 + <label>
35 + <input type="checkbox" class="tie-blur">
36 + <span>${locale.localize('Blur')}</span>
37 + </label>
38 + </div>
39 + <div class="tui-image-editor-checkbox">
40 + <label>
41 + <input type="checkbox" class="tie-sharpen">
42 + <span>${locale.localize('Sharpen')}</span>
43 + </label>
44 + </div>
45 + <div class="tui-image-editor-checkbox">
46 + <label>
47 + <input type="checkbox" class="tie-emboss">
48 + <span>${locale.localize('Emboss')}</span>
49 + </label>
50 + </div>
51 + </div>
52 + </li>
53 + <li class="tui-image-editor-partition">
54 + <div></div>
55 + </li>
56 + <li class="tui-image-editor-submenu-align">
57 + <div class="tui-image-editor-checkbox-group tui-image-editor-disabled" style="margin-bottom: 7px;">
58 + <div class="tui-image-editor-checkbox-wrap">
59 + <div class="tui-image-editor-checkbox">
60 + <label>
61 + <input type="checkbox" class="tie-remove-white">
62 + <span>${locale.localize('Remove White')}</span>
63 + </label>
64 + </div>
65 + </div>
66 + <div class="tui-image-editor-newline tui-image-editor-range-wrap short">
67 + <label>${locale.localize('Distance')}</label>
68 + <div class="tie-removewhite-distance-range"></div>
69 + </div>
70 + </div>
71 + <div class="tui-image-editor-checkbox-group tui-image-editor-disabled">
72 + <div class="tui-image-editor-checkbox">
73 + <label>
74 + <input type="checkbox" class="tie-brightness">
75 + <span>${locale.localize('Brightness')}</span>
76 + </label>
77 + </div>
78 + <div class="tui-image-editor-range-wrap short">
79 + <div class="tie-brightness-range"></div>
80 + </div>
81 + </div>
82 + <div class="tui-image-editor-checkbox-group tui-image-editor-disabled">
83 + <div class="tui-image-editor-checkbox">
84 + <label>
85 + <input type="checkbox" class="tie-noise">
86 + <span>${locale.localize('Noise')}</span>
87 + </label>
88 + </div>
89 + <div class="tui-image-editor-range-wrap short">
90 + <div class="tie-noise-range"></div>
91 + </div>
92 + </div>
93 + </li>
94 + <li class="tui-image-editor-partition only-left-right">
95 + <div></div>
96 + </li>
97 + <li class="tui-image-editor-submenu-align">
98 + <div class="tui-image-editor-checkbox-group tui-image-editor-disabled">
99 + <div class="tui-image-editor-checkbox">
100 + <label>
101 + <input type="checkbox" class="tie-pixelate">
102 + <span>${locale.localize('Pixelate')}</span>
103 + </label>
104 + </div>
105 + <div class="tui-image-editor-range-wrap short">
106 + <div class="tie-pixelate-range"></div>
107 + </div>
108 + </div>
109 + <div class="tui-image-editor-checkbox-group tui-image-editor-disabled">
110 + <div class="tui-image-editor-newline tui-image-editor-checkbox-wrap">
111 + <div class="tui-image-editor-checkbox">
112 + <label>
113 + <input type="checkbox" class="tie-color-filter">
114 + <span>${locale.localize('Color Filter')}</span>
115 + </label>
116 + </div>
117 + </div>
118 + <div class="tui-image-editor-newline tui-image-editor-range-wrap short">
119 + <label>${locale.localize('Threshold')}</label>
120 + <div class="tie-colorfilter-threshole-range"></div>
121 + </div>
122 + </div>
123 + </li>
124 + <li class="tui-image-editor-partition">
125 + <div></div>
126 + </li>
127 + <li>
128 + <div class="filter-color-item">
129 + <div class="tie-filter-tint-color" title="${locale.localize('Tint')}"></div>
130 + <div class="tui-image-editor-checkbox">
131 + <label>
132 + <input type="checkbox" class="tie-tint">
133 + <span></span>
134 + </label>
135 + </div>
136 + </div>
137 + <div class="filter-color-item">
138 + <div class="tie-filter-multiply-color" title="${locale.localize('Multiply')}"></div>
139 + <div class="tui-image-editor-checkbox">
140 + <label>
141 + <input type="checkbox" class="tie-multiply">
142 + <span></span>
143 + </label>
144 + </div>
145 + </div>
146 + <div class="filter-color-item">
147 + <div class="tie-filter-blend-color" title="${locale.localize('Blend')}"></div>
148 + <div class="tui-image-editor-checkbox">
149 + <label>
150 + <input type="checkbox" class="tie-blend">
151 + <span></span>
152 + </label>
153 + </div>
154 + </div>
155 + </li>
156 + </ul>
157 +`;
1 +/**
2 + * @param {Object} submenuInfo - submenu info for make template
3 + * @param {Locale} locale - Translate text
4 + * @param {Function} makeSvgIcon - svg icon generator
5 + * @returns {string}
6 + */
7 +export default ({ locale, makeSvgIcon }) => `
8 + <ul class="tie-flip-button tui-image-editor-submenu-item">
9 + <li>
10 + <div class="tui-image-editor-button flipX">
11 + <div>
12 + ${makeSvgIcon(['normal', 'active'], 'flip-x', true)}
13 + </div>
14 + <label>
15 + ${locale.localize('Flip X')}
16 + </label>
17 + </div>
18 + <div class="tui-image-editor-button flipY">
19 + <div>
20 + ${makeSvgIcon(['normal', 'active'], 'flip-y', true)}
21 + </div>
22 + <label>
23 + ${locale.localize('Flip Y')}
24 + </label>
25 + </div>
26 + </li>
27 + <li class="tui-image-editor-partition">
28 + <div></div>
29 + </li>
30 + <li>
31 + <div class="tui-image-editor-button resetFlip">
32 + <div>
33 + ${makeSvgIcon(['normal', 'active'], 'flip-reset', true)}
34 + </div>
35 + <label>
36 + ${locale.localize('Reset')}
37 + </label>
38 + </div>
39 + </li>
40 + </ul>
41 +`;
1 +/**
2 + * @param {Object} submenuInfo - submenu info for make template
3 + * @param {Locale} locale - Translate text
4 + * @param {Function} makeSvgIcon - svg icon generator
5 + * @returns {string}
6 + */
7 +export default ({ locale, makeSvgIcon }) => `
8 + <ul class="tui-image-editor-submenu-item">
9 + <li class="tie-icon-add-button">
10 + <div class="tui-image-editor-button" data-icontype="icon-arrow">
11 + <div>
12 + ${makeSvgIcon(['normal', 'active'], 'icon-arrow', true)}
13 + </div>
14 + <label>
15 + ${locale.localize('Arrow')}
16 + </label>
17 + </div>
18 + <div class="tui-image-editor-button" data-icontype="icon-arrow-2">
19 + <div>
20 + ${makeSvgIcon(['normal', 'active'], 'icon-arrow-2', true)}
21 + </div>
22 + <label>
23 + ${locale.localize('Arrow-2')}
24 + </label>
25 + </div>
26 + <div class="tui-image-editor-button" data-icontype="icon-arrow-3">
27 + <div>
28 + ${makeSvgIcon(['normal', 'active'], 'icon-arrow-3', true)}
29 + </div>
30 + <label>
31 + ${locale.localize('Arrow-3')}
32 + </label>
33 + </div>
34 + <div class="tui-image-editor-button" data-icontype="icon-star">
35 + <div>
36 + ${makeSvgIcon(['normal', 'active'], 'icon-star', true)}
37 + </div>
38 + <label>
39 + ${locale.localize('Star-1')}
40 + </label>
41 + </div>
42 + <div class="tui-image-editor-button" data-icontype="icon-star-2">
43 + <div>
44 + ${makeSvgIcon(['normal', 'active'], 'icon-star-2', true)}
45 + </div>
46 + <label>
47 + ${locale.localize('Star-2')}
48 + </label>
49 + </div>
50 +
51 + <div class="tui-image-editor-button" data-icontype="icon-polygon">
52 + <div>
53 + ${makeSvgIcon(['normal', 'active'], 'icon-polygon', true)}
54 + </div>
55 + <label>
56 + ${locale.localize('Polygon')}
57 + </label>
58 + </div>
59 +
60 + <div class="tui-image-editor-button" data-icontype="icon-location">
61 + <div>
62 + ${makeSvgIcon(['normal', 'active'], 'icon-location', true)}
63 + </div>
64 + <label>
65 + ${locale.localize('Location')}
66 + </label>
67 + </div>
68 +
69 + <div class="tui-image-editor-button" data-icontype="icon-heart">
70 + <div>
71 + ${makeSvgIcon(['normal', 'active'], 'icon-heart', true)}
72 + </div>
73 + <label>
74 + ${locale.localize('Heart')}
75 + </label>
76 + </div>
77 +
78 + <div class="tui-image-editor-button" data-icontype="icon-bubble">
79 + <div>
80 + ${makeSvgIcon(['normal', 'active'], 'icon-bubble', true)}
81 + </div>
82 + <label>
83 + ${locale.localize('Bubble')}
84 + </label>
85 + </div>
86 + </li>
87 + <li class="tui-image-editor-partition">
88 + <div></div>
89 + </li>
90 + <li class="tie-icon-add-button">
91 + <div class="tui-image-editor-button" style="margin:0">
92 + <div>
93 + <input type="file" accept="image/*" class="tie-icon-image-file">
94 + ${makeSvgIcon(['normal', 'active'], 'icon-load', true)}
95 + </div>
96 + <label>
97 + ${locale.localize('Custom icon')}
98 + </label>
99 + </div>
100 + </li>
101 + <li class="tui-image-editor-partition">
102 + <div></div>
103 + </li>
104 + <li>
105 + <div class="tie-icon-color" title="${locale.localize('Color')}"></div>
106 + </li>
107 + </ul>
108 +`;
1 +/**
2 + * @param {Object} submenuInfo - submenu info for make template
3 + * @param {Locale} locale - Translate text
4 + * @param {Function} makeSvgIcon - svg icon generator
5 + * @returns {string}
6 + */
7 +export default ({ locale, makeSvgIcon }) => `
8 + <ul class="tui-image-editor-submenu-item">
9 + <li>
10 + <div class="tui-image-editor-button">
11 + <div>
12 + <input type="file" accept="image/*" class="tie-mask-image-file">
13 + ${makeSvgIcon(['normal', 'active'], 'mask-load', true)}
14 + </div>
15 + <label> ${locale.localize('Load Mask Image')} </label>
16 + </div>
17 + </li>
18 + <li class="tui-image-editor-partition only-left-right">
19 + <div></div>
20 + </li>
21 + <li class="tie-mask-apply tui-image-editor-newline apply" style="margin-top: 22px;margin-bottom: 5px">
22 + <div class="tui-image-editor-button apply">
23 + ${makeSvgIcon(['normal', 'active'], 'apply')}
24 + <label>
25 + ${locale.localize('Apply')}
26 + </label>
27 + </div>
28 + </li>
29 + </ul>
30 +`;
1 +/**
2 + * @param {Object} submenuInfo - submenu info for make template
3 + * @param {Locale} locale - Translate text
4 + * @param {Function} makeSvgIcon - svg icon generator
5 + * @returns {string}
6 + */
7 +export default ({ locale, makeSvgIcon }) => `
8 + <ul class="tui-image-editor-submenu-item">
9 + <li class="tie-retate-button">
10 + <div class="tui-image-editor-button clockwise">
11 + <div>
12 + ${makeSvgIcon(['normal', 'active'], 'rotate-clockwise', true)}
13 + </div>
14 + <label> 30 </label>
15 + </div>
16 + <div class="tui-image-editor-button counterclockwise">
17 + <div>
18 + ${makeSvgIcon(['normal', 'active'], 'rotate-counterclockwise', true)}
19 + </div>
20 + <label> -30 </label>
21 + </div>
22 + </li>
23 + <li class="tui-image-editor-partition only-left-right">
24 + <div></div>
25 + </li>
26 + <li class="tui-image-editor-newline tui-image-editor-range-wrap">
27 + <label class="range">${locale.localize('Range')}</label>
28 + <div class="tie-rotate-range"></div>
29 + <input class="tie-ratate-range-value tui-image-editor-range-value" value="0" />
30 + </li>
31 + </ul>
32 +`;
1 +/**
2 + * @param {Object} submenuInfo - submenu info for make template
3 + * @param {Locale} locale - Translate text
4 + * @param {Function} makeSvgIcon - svg icon generator
5 + * @returns {string}
6 + */
7 +export default ({ locale, makeSvgIcon }) => `
8 + <ul class="tui-image-editor-submenu-item">
9 + <li class="tie-shape-button">
10 + <div class="tui-image-editor-button rect">
11 + <div>
12 + ${makeSvgIcon(['normal', 'active'], 'shape-rectangle', true)}
13 + </div>
14 + <label> ${locale.localize('Rectangle')} </label>
15 + </div>
16 + <div class="tui-image-editor-button circle">
17 + <div>
18 + ${makeSvgIcon(['normal', 'active'], 'shape-circle', true)}
19 + </div>
20 + <label> ${locale.localize('Circle')} </label>
21 + </div>
22 + <div class="tui-image-editor-button triangle">
23 + <div>
24 + ${makeSvgIcon(['normal', 'active'], 'shape-triangle', true)}
25 + </div>
26 + <label> ${locale.localize('Triangle')} </label>
27 + </div>
28 + </li>
29 + <li class="tui-image-editor-partition">
30 + <div></div>
31 + </li>
32 + <li class="tie-shape-color-button">
33 + <div class="tie-color-fill" title="${locale.localize('Fill')}"></div>
34 + <div class="tie-color-stroke" title="${locale.localize('Stroke')}"></div>
35 + </li>
36 + <li class="tui-image-editor-partition only-left-right">
37 + <div></div>
38 + </li>
39 + <li class="tui-image-editor-newline tui-image-editor-range-wrap">
40 + <label class="range">${locale.localize('Stroke')}</label>
41 + <div class="tie-stroke-range"></div>
42 + <input class="tie-stroke-range-value tui-image-editor-range-value" value="0" />
43 + </li>
44 + </ul>
45 +`;
1 +/**
2 + * @param {Object} submenuInfo - submenu info for make template
3 + * @param {Locale} locale - Translate text
4 + * @param {Function} makeSvgIcon - svg icon generator
5 + * @returns {string}
6 + */
7 +export default ({ locale, makeSvgIcon }) => `
8 + <ul class="tui-image-editor-submenu-item">
9 + <li class="tie-text-effect-button">
10 + <div class="tui-image-editor-button bold">
11 + <div>
12 + ${makeSvgIcon(['normal', 'active'], 'text-bold', true)}
13 + </div>
14 + <label> ${locale.localize('Bold')} </label>
15 + </div>
16 + <div class="tui-image-editor-button italic">
17 + <div>
18 + ${makeSvgIcon(['normal', 'active'], 'text-italic', true)}
19 + </div>
20 + <label> ${locale.localize('Italic')} </label>
21 + </div>
22 + <div class="tui-image-editor-button underline">
23 + <div>
24 + ${makeSvgIcon(['normal', 'active'], 'text-underline', true)}
25 + </div>
26 + <label> ${locale.localize('Underline')} </label>
27 + </div>
28 + </li>
29 + <li class="tui-image-editor-partition">
30 + <div></div>
31 + </li>
32 + <li class="tie-text-align-button">
33 + <div class="tui-image-editor-button left">
34 + <div>
35 + ${makeSvgIcon(['normal', 'active'], 'text-align-left', true)}
36 + </div>
37 + <label> ${locale.localize('Left')} </label>
38 + </div>
39 + <div class="tui-image-editor-button center">
40 + <div>
41 + ${makeSvgIcon(['normal', 'active'], 'text-align-center', true)}
42 + </div>
43 + <label> ${locale.localize('Center')} </label>
44 + </div>
45 + <div class="tui-image-editor-button right">
46 + <div>
47 + ${makeSvgIcon(['normal', 'active'], 'text-align-right', true)}
48 + </div>
49 + <label> ${locale.localize('Right')} </label>
50 + </div>
51 + </li>
52 + <li class="tui-image-editor-partition">
53 + <div></div>
54 + </li>
55 + <li>
56 + <div class="tie-text-color" title="${locale.localize('Color')}"></div>
57 + </li>
58 + <li class="tui-image-editor-partition only-left-right">
59 + <div></div>
60 + </li>
61 + <li class="tui-image-editor-newline tui-image-editor-range-wrap">
62 + <label class="range">${locale.localize('Text size')}</label>
63 + <div class="tie-text-range"></div>
64 + <input class="tie-text-range-value tui-image-editor-range-value" value="0" />
65 + </li>
66 + </ul>
67 +`;
1 +import { assignmentForDestroy } from '../util';
2 +import Range from './tools/range';
3 +import Colorpicker from './tools/colorpicker';
4 +import Submenu from './submenuBase';
5 +import templateHtml from './template/submenu/text';
6 +import { defaultTextRangeValus } from '../consts';
7 +
8 +/**
9 + * Crop ui class
10 + * @class
11 + * @ignore
12 + */
13 +export default class Text extends Submenu {
14 + constructor(subMenuElement, { locale, makeSvgIcon, menuBarPosition, usageStatistics }) {
15 + super(subMenuElement, {
16 + locale,
17 + name: 'text',
18 + makeSvgIcon,
19 + menuBarPosition,
20 + templateHtml,
21 + usageStatistics,
22 + });
23 + this.effect = {
24 + bold: false,
25 + italic: false,
26 + underline: false,
27 + };
28 + this.align = 'left';
29 + this._els = {
30 + textEffectButton: this.selector('.tie-text-effect-button'),
31 + textAlignButton: this.selector('.tie-text-align-button'),
32 + textColorpicker: new Colorpicker(
33 + this.selector('.tie-text-color'),
34 + '#ffbb3b',
35 + this.toggleDirection,
36 + this.usageStatistics
37 + ),
38 + textRange: new Range(
39 + {
40 + slider: this.selector('.tie-text-range'),
41 + input: this.selector('.tie-text-range-value'),
42 + },
43 + defaultTextRangeValus
44 + ),
45 + };
46 + }
47 +
48 + /**
49 + * Destroys the instance.
50 + */
51 + destroy() {
52 + this._removeEvent();
53 + this._els.textColorpicker.destroy();
54 + this._els.textRange.destroy();
55 +
56 + assignmentForDestroy(this);
57 + }
58 +
59 + /**
60 + * Add event for text
61 + * @param {Object} actions - actions for text
62 + * @param {Function} actions.changeTextStyle - change text style
63 + */
64 + addEvent(actions) {
65 + const setTextEffect = this._setTextEffectHandler.bind(this);
66 + const setTextAlign = this._setTextAlignHandler.bind(this);
67 +
68 + this.eventHandler = {
69 + setTextEffect,
70 + setTextAlign,
71 + };
72 +
73 + this.actions = actions;
74 + this._els.textEffectButton.addEventListener('click', setTextEffect);
75 + this._els.textAlignButton.addEventListener('click', setTextAlign);
76 + this._els.textRange.on('change', this._changeTextRnageHandler.bind(this));
77 + this._els.textColorpicker.on('change', this._changeColorHandler.bind(this));
78 + }
79 +
80 + /**
81 + * Remove event
82 + * @private
83 + */
84 + _removeEvent() {
85 + const { setTextEffect, setTextAlign } = this.eventHandler;
86 +
87 + this._els.textEffectButton.removeEventListener('click', setTextEffect);
88 + this._els.textAlignButton.removeEventListener('click', setTextAlign);
89 + this._els.textRange.off();
90 + this._els.textColorpicker.off();
91 + }
92 +
93 + /**
94 + * Returns the menu to its default state.
95 + */
96 + changeStandbyMode() {
97 + this.actions.stopDrawingMode();
98 + }
99 +
100 + /**
101 + * Executed when the menu starts.
102 + */
103 + changeStartMode() {
104 + this.actions.modeChange('text');
105 + }
106 +
107 + set textColor(color) {
108 + this._els.textColorpicker.color = color;
109 + }
110 +
111 + /**
112 + * Get text color
113 + * @returns {string} - text color
114 + */
115 + get textColor() {
116 + return this._els.textColorpicker.color;
117 + }
118 +
119 + /**
120 + * Get text size
121 + * @returns {string} - text size
122 + */
123 + get fontSize() {
124 + return this._els.textRange.value;
125 + }
126 +
127 + /**
128 + * Set text size
129 + * @param {Number} value - text size
130 + */
131 + set fontSize(value) {
132 + this._els.textRange.value = value;
133 + }
134 +
135 + /**
136 + * get font style
137 + * @returns {string} - font style
138 + */
139 + get fontStyle() {
140 + return this.effect.italic ? 'italic' : 'normal';
141 + }
142 +
143 + /**
144 + * get font weight
145 + * @returns {string} - font weight
146 + */
147 + get fontWeight() {
148 + return this.effect.bold ? 'bold' : 'normal';
149 + }
150 +
151 + /**
152 + * get text underline text underline
153 + * @returns {boolean} - true or false
154 + */
155 + get underline() {
156 + return this.effect.underline;
157 + }
158 +
159 + setTextStyleStateOnAction(textStyle = {}) {
160 + const { fill, fontSize, fontStyle, fontWeight, textDecoration, textAlign } = textStyle;
161 +
162 + this.textColor = fill;
163 + this.fontSize = fontSize;
164 + this.setEffactState('italic', fontStyle);
165 + this.setEffactState('bold', fontWeight);
166 + this.setEffactState('underline', textDecoration);
167 + this.setAlignState(textAlign);
168 + }
169 +
170 + setEffactState(effactName, value) {
171 + const effactValue = value === 'italic' || value === 'bold' || value === 'underline';
172 + const button = this._els.textEffectButton.querySelector(
173 + `.tui-image-editor-button.${effactName}`
174 + );
175 +
176 + this.effect[effactName] = effactValue;
177 +
178 + button.classList[effactValue ? 'add' : 'remove']('active');
179 + }
180 +
181 + setAlignState(value) {
182 + const button = this._els.textAlignButton;
183 + button.classList.remove(this.align);
184 + button.classList.add(value);
185 + this.align = value;
186 + }
187 +
188 + /**
189 + * text effect set handler
190 + * @param {object} event - add button event object
191 + * @private
192 + */
193 + _setTextEffectHandler(event) {
194 + const button = event.target.closest('.tui-image-editor-button');
195 + const [styleType] = button.className.match(/(bold|italic|underline)/);
196 + const styleObj = {
197 + bold: { fontWeight: 'bold' },
198 + italic: { fontStyle: 'italic' },
199 + underline: { textDecoration: 'underline' },
200 + }[styleType];
201 +
202 + this.effect[styleType] = !this.effect[styleType];
203 + button.classList.toggle('active');
204 + this.actions.changeTextStyle(styleObj);
205 + }
206 +
207 + /**
208 + * text effect set handler
209 + * @param {object} event - add button event object
210 + * @private
211 + */
212 + _setTextAlignHandler(event) {
213 + const button = event.target.closest('.tui-image-editor-button');
214 + if (button) {
215 + const styleType = this.getButtonType(button, ['left', 'center', 'right']);
216 +
217 + event.currentTarget.classList.remove(this.align);
218 + if (this.align !== styleType) {
219 + event.currentTarget.classList.add(styleType);
220 + }
221 + this.actions.changeTextStyle({ textAlign: styleType });
222 +
223 + this.align = styleType;
224 + }
225 + }
226 +
227 + /**
228 + * text align set handler
229 + * @param {number} value - range value
230 + * @param {boolean} isLast - Is last change
231 + * @private
232 + */
233 + _changeTextRnageHandler(value, isLast) {
234 + this.actions.changeTextStyle(
235 + {
236 + fontSize: value,
237 + },
238 + !isLast
239 + );
240 + }
241 +
242 + /**
243 + * change color handler
244 + * @param {string} color - change color string
245 + * @private
246 + */
247 + _changeColorHandler(color) {
248 + color = color || 'transparent';
249 + this.actions.changeTextStyle({
250 + fill: color,
251 + });
252 + }
253 +}
1 +/**
2 + * @fileoverview The standard theme
3 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
4 + */
5 +
6 +/**
7 + * Full configuration for theme.<br>
8 + * @typedef {object} themeConfig
9 + * @property {string} common.bi.image - Brand icon image
10 + * @property {string} common.bisize.width - Icon image width
11 + * @property {string} common.bisize.height - Icon Image Height
12 + * @property {string} common.backgroundImage - Background image
13 + * @property {string} common.backgroundColor - Background color
14 + * @property {string} common.border - Full area border style
15 + * @property {string} header.backgroundImage - header area background
16 + * @property {string} header.backgroundColor - header area background color
17 + * @property {string} header.border - header area border style
18 + * @property {string} loadButton.backgroundColor - load button background color
19 + * @property {string} loadButton.border - load button border style
20 + * @property {string} loadButton.color - load button foreground color
21 + * @property {string} loadButton.fontFamily - load button font type
22 + * @property {string} loadButton.fontSize - load button font size
23 + * @property {string} downloadButton.backgroundColor - download button background color
24 + * @property {string} downloadButton.border - download button border style
25 + * @property {string} downloadButton.color - download button foreground color
26 + * @property {string} downloadButton.fontFamily - download button font type
27 + * @property {string} downloadButton.fontSize - download button font size
28 + * @property {string} menu.normalIcon.color - Menu normal color for default icon
29 + * @property {string} menu.normalIcon.path - Menu normal icon svg bundle file path
30 + * @property {string} menu.normalIcon.name - Menu normal icon svg bundle name
31 + * @property {string} menu.activeIcon.color - Menu active color for default icon
32 + * @property {string} menu.activeIcon.path - Menu active icon svg bundle file path
33 + * @property {string} menu.activeIcon.name - Menu active icon svg bundle name
34 + * @property {string} menu.disabled.color - Menu disabled color for default icon
35 + * @property {string} menu.disabled.path - Menu disabled icon svg bundle file path
36 + * @property {string} menu.disabled.name - Menu disabled icon svg bundle name
37 + * @property {string} menu.hover.color - Menu default icon hover color
38 + * @property {string} menu.hover.path - Menu hover icon svg bundle file path
39 + * @property {string} menu.hover.name - Menu hover icon svg bundle name
40 + * @property {string} menu.iconSize.width - Menu icon Size Width
41 + * @property {string} menu.iconSize.height - Menu Icon Size Height
42 + * @property {string} submenu.backgroundColor - Sub-menu area background color
43 + * @property {string} submenu.partition.color - Submenu partition line color
44 + * @property {string} submenu.normalIcon.color - Submenu normal color for default icon
45 + * @property {string} submenu.normalIcon.path - Submenu default icon svg bundle file path
46 + * @property {string} submenu.normalIcon.name - Submenu default icon svg bundle name
47 + * @property {string} submenu.activeIcon.color - Submenu active color for default icon
48 + * @property {string} submenu.activeIcon.path - Submenu active icon svg bundle file path
49 + * @property {string} submenu.activeIcon.name - Submenu active icon svg bundle name
50 + * @property {string} submenu.iconSize.width - Submenu icon Size Width
51 + * @property {string} submenu.iconSize.height - Submenu Icon Size Height
52 + * @property {string} submenu.normalLabel.color - Submenu default label color
53 + * @property {string} submenu.normalLabel.fontWeight - Sub Menu Default Label Font Thickness
54 + * @property {string} submenu.activeLabel.color - Submenu active label color
55 + * @property {string} submenu.activeLabel.fontWeight - Submenu active label Font thickness
56 + * @property {string} checkbox.border - Checkbox border style
57 + * @property {string} checkbox.backgroundColor - Checkbox background color
58 + * @property {string} range.pointer.color - range control pointer color
59 + * @property {string} range.bar.color - range control bar color
60 + * @property {string} range.subbar.color - range control subbar color
61 + * @property {string} range.value.color - range number box font color
62 + * @property {string} range.value.fontWeight - range number box font thickness
63 + * @property {string} range.value.fontSize - range number box font size
64 + * @property {string} range.value.border - range number box border style
65 + * @property {string} range.value.backgroundColor - range number box background color
66 + * @property {string} range.title.color - range title font color
67 + * @property {string} range.title.fontWeight - range title font weight
68 + * @property {string} colorpicker.button.border - colorpicker button border style
69 + * @property {string} colorpicker.title.color - colorpicker button title font color
70 + * @example
71 + // default keys and styles
72 + var customTheme = {
73 + 'common.bi.image': 'https://uicdn.toast.com/toastui/img/tui-image-editor-bi.png',
74 + 'common.bisize.width': '251px',
75 + 'common.bisize.height': '21px',
76 + 'common.backgroundImage': 'none',
77 + 'common.backgroundColor': '#1e1e1e',
78 + 'common.border': '0px',
79 +
80 + // header
81 + 'header.backgroundImage': 'none',
82 + 'header.backgroundColor': 'transparent',
83 + 'header.border': '0px',
84 +
85 + // load button
86 + 'loadButton.backgroundColor': '#fff',
87 + 'loadButton.border': '1px solid #ddd',
88 + 'loadButton.color': '#222',
89 + 'loadButton.fontFamily': 'NotoSans, sans-serif',
90 + 'loadButton.fontSize': '12px',
91 +
92 + // download button
93 + 'downloadButton.backgroundColor': '#fdba3b',
94 + 'downloadButton.border': '1px solid #fdba3b',
95 + 'downloadButton.color': '#fff',
96 + 'downloadButton.fontFamily': 'NotoSans, sans-serif',
97 + 'downloadButton.fontSize': '12px',
98 +
99 + // icons default
100 + 'menu.normalIcon.color': '#8a8a8a',
101 + 'menu.activeIcon.color': '#555555',
102 + 'menu.disabledIcon.color': '#434343',
103 + 'menu.hoverIcon.color': '#e9e9e9',
104 + 'submenu.normalIcon.color': '#8a8a8a',
105 + 'submenu.activeIcon.color': '#e9e9e9',
106 +
107 + 'menu.iconSize.width': '24px',
108 + 'menu.iconSize.height': '24px',
109 + 'submenu.iconSize.width': '32px',
110 + 'submenu.iconSize.height': '32px',
111 +
112 + // submenu primary color
113 + 'submenu.backgroundColor': '#1e1e1e',
114 + 'submenu.partition.color': '#858585',
115 +
116 + // submenu labels
117 + 'submenu.normalLabel.color': '#858585',
118 + 'submenu.normalLabel.fontWeight': 'lighter',
119 + 'submenu.activeLabel.color': '#fff',
120 + 'submenu.activeLabel.fontWeight': 'lighter',
121 +
122 + // checkbox style
123 + 'checkbox.border': '1px solid #ccc',
124 + 'checkbox.backgroundColor': '#fff',
125 +
126 + // rango style
127 + 'range.pointer.color': '#fff',
128 + 'range.bar.color': '#666',
129 + 'range.subbar.color': '#d1d1d1',
130 +
131 + 'range.disabledPointer.color': '#414141',
132 + 'range.disabledBar.color': '#282828',
133 + 'range.disabledSubbar.color': '#414141',
134 +
135 + 'range.value.color': '#fff',
136 + 'range.value.fontWeight': 'lighter',
137 + 'range.value.fontSize': '11px',
138 + 'range.value.border': '1px solid #353535',
139 + 'range.value.backgroundColor': '#151515',
140 + 'range.title.color': '#fff',
141 + 'range.title.fontWeight': 'lighter',
142 +
143 + // colorpicker style
144 + 'colorpicker.button.border': '1px solid #1e1e1e',
145 + 'colorpicker.title.color': '#fff'
146 +};
147 + */
148 +export default {
149 + 'common.bi.image': 'https://uicdn.toast.com/toastui/img/tui-image-editor-bi.png',
150 + 'common.bisize.width': '251px',
151 + 'common.bisize.height': '21px',
152 + 'common.backgroundImage': 'none',
153 + 'common.backgroundColor': '#1e1e1e',
154 + 'common.border': '0px',
155 +
156 + // header
157 + 'header.backgroundImage': 'none',
158 + 'header.backgroundColor': 'transparent',
159 + 'header.border': '0px',
160 +
161 + // load button
162 + 'loadButton.backgroundColor': '#fff',
163 + 'loadButton.border': '1px solid #ddd',
164 + 'loadButton.color': '#222',
165 + 'loadButton.fontFamily': "'Noto Sans', sans-serif",
166 + 'loadButton.fontSize': '12px',
167 +
168 + // download button
169 + 'downloadButton.backgroundColor': '#fdba3b',
170 + 'downloadButton.border': '1px solid #fdba3b',
171 + 'downloadButton.color': '#fff',
172 + 'downloadButton.fontFamily': "'Noto Sans', sans-serif",
173 + 'downloadButton.fontSize': '12px',
174 +
175 + // main icons
176 + 'menu.normalIcon.color': '#8a8a8a',
177 + 'menu.activeIcon.color': '#555555',
178 + 'menu.disabledIcon.color': '#434343',
179 + 'menu.hoverIcon.color': '#e9e9e9',
180 +
181 + // submenu icons
182 + 'submenu.normalIcon.color': '#8a8a8a',
183 + 'submenu.activeIcon.color': '#e9e9e9',
184 +
185 + 'menu.iconSize.width': '24px',
186 + 'menu.iconSize.height': '24px',
187 +
188 + 'submenu.iconSize.width': '32px',
189 + 'submenu.iconSize.height': '32px',
190 +
191 + // submenu primary color
192 + 'submenu.backgroundColor': '#1e1e1e',
193 + 'submenu.partition.color': '#3c3c3c',
194 +
195 + // submenu labels
196 + 'submenu.normalLabel.color': '#8a8a8a',
197 + 'submenu.normalLabel.fontWeight': 'lighter',
198 + 'submenu.activeLabel.color': '#fff',
199 + 'submenu.activeLabel.fontWeight': 'lighter',
200 +
201 + // checkbox style
202 + 'checkbox.border': '0px',
203 + 'checkbox.backgroundColor': '#fff',
204 +
205 + // range style
206 + 'range.pointer.color': '#fff',
207 + 'range.bar.color': '#666',
208 + 'range.subbar.color': '#d1d1d1',
209 +
210 + 'range.disabledPointer.color': '#414141',
211 + 'range.disabledBar.color': '#282828',
212 + 'range.disabledSubbar.color': '#414141',
213 +
214 + 'range.value.color': '#fff',
215 + 'range.value.fontWeight': 'lighter',
216 + 'range.value.fontSize': '11px',
217 + 'range.value.border': '1px solid #353535',
218 + 'range.value.backgroundColor': '#151515',
219 + 'range.title.color': '#fff',
220 + 'range.title.fontWeight': 'lighter',
221 +
222 + // colorpicker style
223 + 'colorpicker.button.border': '1px solid #1e1e1e',
224 + 'colorpicker.title.color': '#fff',
225 +};
1 +import { extend, forEach, map } from 'tui-code-snippet';
2 +import { styleLoad } from '../../util';
3 +import style from '../template/style';
4 +import standardTheme from './standard';
5 +import icon from '../../../svg/default.svg';
6 +
7 +/**
8 + * Theme manager
9 + * @class
10 + * @param {Object} customTheme - custom theme
11 + * @ignore
12 + */
13 +class Theme {
14 + constructor(customTheme) {
15 + this.styles = this._changeToObject(extend({}, standardTheme, customTheme));
16 + styleLoad(this._styleMaker());
17 +
18 + this._loadDefaultSvgIcon();
19 + }
20 +
21 + /**
22 + * Get a Style cssText or StyleObject
23 + * @param {string} type - style type
24 + * @returns {string|object} - cssText or StyleObject
25 + */
26 + // eslint-disable-next-line complexity
27 + getStyle(type) {
28 + let result = null;
29 + const firstProperty = type.replace(/\..+$/, '');
30 + const option = this.styles[type];
31 + switch (type) {
32 + case 'common.bi':
33 + result = this.styles[type].image;
34 + break;
35 + case 'menu.icon':
36 + result = {
37 + active: this.styles[`${firstProperty}.activeIcon`],
38 + normal: this.styles[`${firstProperty}.normalIcon`],
39 + hover: this.styles[`${firstProperty}.hoverIcon`],
40 + disabled: this.styles[`${firstProperty}.disabledIcon`],
41 + };
42 + break;
43 + case 'submenu.icon':
44 + result = {
45 + active: this.styles[`${firstProperty}.activeIcon`],
46 + normal: this.styles[`${firstProperty}.normalIcon`],
47 + };
48 + break;
49 + case 'submenu.label':
50 + result = {
51 + active: this._makeCssText(this.styles[`${firstProperty}.activeLabel`]),
52 + normal: this._makeCssText(this.styles[`${firstProperty}.normalLabel`]),
53 + };
54 + break;
55 + case 'submenu.partition':
56 + result = {
57 + vertical: this._makeCssText(
58 + extend({}, option, { borderLeft: `1px solid ${option.color}` })
59 + ),
60 + horizontal: this._makeCssText(
61 + extend({}, option, { borderBottom: `1px solid ${option.color}` })
62 + ),
63 + };
64 + break;
65 +
66 + case 'range.disabledPointer':
67 + case 'range.disabledBar':
68 + case 'range.disabledSubbar':
69 + case 'range.pointer':
70 + case 'range.bar':
71 + case 'range.subbar':
72 + option.backgroundColor = option.color;
73 + result = this._makeCssText(option);
74 + break;
75 + default:
76 + result = this._makeCssText(option);
77 + break;
78 + }
79 +
80 + return result;
81 + }
82 +
83 + /**
84 + * Make css resource
85 + * @returns {string} - serialized css text
86 + * @private
87 + */
88 + _styleMaker() {
89 + const submenuLabelStyle = this.getStyle('submenu.label');
90 + const submenuPartitionStyle = this.getStyle('submenu.partition');
91 +
92 + return style({
93 + subMenuLabelActive: submenuLabelStyle.active,
94 + subMenuLabelNormal: submenuLabelStyle.normal,
95 + submenuPartitionVertical: submenuPartitionStyle.vertical,
96 + submenuPartitionHorizontal: submenuPartitionStyle.horizontal,
97 + biSize: this.getStyle('common.bisize'),
98 + subMenuRangeTitle: this.getStyle('range.title'),
99 + submenuRangePointer: this.getStyle('range.pointer'),
100 + submenuRangeBar: this.getStyle('range.bar'),
101 + submenuRangeSubbar: this.getStyle('range.subbar'),
102 +
103 + submenuDisabledRangePointer: this.getStyle('range.disabledPointer'),
104 + submenuDisabledRangeBar: this.getStyle('range.disabledBar'),
105 + submenuDisabledRangeSubbar: this.getStyle('range.disabledSubbar'),
106 +
107 + submenuRangeValue: this.getStyle('range.value'),
108 + submenuColorpickerTitle: this.getStyle('colorpicker.title'),
109 + submenuColorpickerButton: this.getStyle('colorpicker.button'),
110 + submenuCheckbox: this.getStyle('checkbox'),
111 + menuIconSize: this.getStyle('menu.iconSize'),
112 + submenuIconSize: this.getStyle('submenu.iconSize'),
113 + menuIconStyle: this.getStyle('menu.icon'),
114 + submenuIconStyle: this.getStyle('submenu.icon'),
115 + });
116 + }
117 +
118 + /**
119 + * Change to low dimensional object.
120 + * @param {object} styleOptions - style object of user interface
121 + * @returns {object} low level object for style apply
122 + * @private
123 + */
124 + _changeToObject(styleOptions) {
125 + const styleObject = {};
126 + forEach(styleOptions, (value, key) => {
127 + const keyExplode = key.match(/^(.+)\.([a-z]+)$/i);
128 + const [, property, subProperty] = keyExplode;
129 +
130 + if (!styleObject[property]) {
131 + styleObject[property] = {};
132 + }
133 + styleObject[property][subProperty] = value;
134 + });
135 +
136 + return styleObject;
137 + }
138 +
139 + /**
140 + * Style object to Csstext serialize
141 + * @param {object} styleObject - style object
142 + * @returns {string} - css text string
143 + * @private
144 + */
145 + _makeCssText(styleObject) {
146 + const converterStack = [];
147 +
148 + forEach(styleObject, (value, key) => {
149 + if (['backgroundImage'].indexOf(key) > -1 && value !== 'none') {
150 + value = `url(${value})`;
151 + }
152 +
153 + converterStack.push(`${this._toUnderScore(key)}: ${value}`);
154 + });
155 +
156 + return converterStack.join(';');
157 + }
158 +
159 + /**
160 + * Camel key string to Underscore string
161 + * @param {string} targetString - change target
162 + * @returns {string}
163 + * @private
164 + */
165 + _toUnderScore(targetString) {
166 + return targetString.replace(/([A-Z])/g, ($0, $1) => `-${$1.toLowerCase()}`);
167 + }
168 +
169 + /**
170 + * Load defulat svg icon
171 + * @private
172 + */
173 + _loadDefaultSvgIcon() {
174 + if (!document.getElementById('tui-image-editor-svg-default-icons')) {
175 + const parser = new DOMParser();
176 + const dom = parser.parseFromString(icon, 'text/xml');
177 +
178 + document.body.appendChild(dom.documentElement);
179 + }
180 + }
181 +
182 + /**
183 + * Make className for svg icon
184 + * @param {string} iconType - normal' or 'active' or 'hover' or 'disabled
185 + * @param {boolean} isSubmenu - submenu icon or not.
186 + * @returns {string}
187 + * @private
188 + */
189 + _makeIconClassName(iconType, isSubmenu) {
190 + const iconStyleInfo = isSubmenu ? this.getStyle('submenu.icon') : this.getStyle('menu.icon');
191 + const { path, name } = iconStyleInfo[iconType];
192 +
193 + return path && name ? iconType : `${iconType} use-default`;
194 + }
195 +
196 + /**
197 + * Make svg use link path name
198 + * @param {string} iconType - normal' or 'active' or 'hover' or 'disabled
199 + * @param {boolean} isSubmenu - submenu icon or not.
200 + * @returns {string}
201 + * @private
202 + */
203 + _makeSvgIconPrefix(iconType, isSubmenu) {
204 + const iconStyleInfo = isSubmenu ? this.getStyle('submenu.icon') : this.getStyle('menu.icon');
205 + const { path, name } = iconStyleInfo[iconType];
206 +
207 + return path && name ? `${path}#${name}-` : '#';
208 + }
209 +
210 + /**
211 + * Make svg use link path name
212 + * @param {Array.<string>} useIconTypes - normal' or 'active' or 'hover' or 'disabled
213 + * @param {string} menuName - menu name
214 + * @param {boolean} isSubmenu - submenu icon or not.
215 + * @returns {string}
216 + * @private
217 + */
218 + _makeSvgItem(useIconTypes, menuName, isSubmenu) {
219 + return map(useIconTypes, (iconType) => {
220 + const svgIconPrefix = this._makeSvgIconPrefix(iconType, isSubmenu);
221 + const iconName = this._toUnderScore(menuName);
222 + const svgIconClassName = this._makeIconClassName(iconType, isSubmenu);
223 +
224 + return `<use xlink:href="${svgIconPrefix}ic-${iconName}" class="${svgIconClassName}"/>`;
225 + }).join('');
226 + }
227 +
228 + /**
229 + * Make svg icon set
230 + * @param {Array.<string>} useIconTypes - normal' or 'active' or 'hover' or 'disabled
231 + * @param {string} menuName - menu name
232 + * @param {boolean} isSubmenu - submenu icon or not.
233 + * @returns {string}
234 + */
235 + makeMenSvgIconSet(useIconTypes, menuName, isSubmenu = false) {
236 + return `<svg class="svg_ic-${isSubmenu ? 'submenu' : 'menu'}">${this._makeSvgItem(
237 + useIconTypes,
238 + menuName,
239 + isSubmenu
240 + )}</svg>`;
241 + }
242 +}
243 +
244 +export default Theme;
1 +import snippet from 'tui-code-snippet';
2 +import tuiColorPicker from 'tui-color-picker';
3 +const PICKER_COLOR = [
4 + '#000000',
5 + '#2a2a2a',
6 + '#545454',
7 + '#7e7e7e',
8 + '#a8a8a8',
9 + '#d2d2d2',
10 + '#ffffff',
11 + '',
12 + '#ff4040',
13 + '#ff6518',
14 + '#ffbb3b',
15 + '#03bd9e',
16 + '#00a9ff',
17 + '#515ce6',
18 + '#9e5fff',
19 + '#ff5583',
20 +];
21 +
22 +/**
23 + * Colorpicker control class
24 + * @class
25 + * @ignore
26 + */
27 +class Colorpicker {
28 + constructor(
29 + colorpickerElement,
30 + defaultColor = '#7e7e7e',
31 + toggleDirection = 'up',
32 + usageStatistics
33 + ) {
34 + this.colorpickerElement = colorpickerElement;
35 + this.usageStatistics = usageStatistics;
36 +
37 + this._show = false;
38 +
39 + this._colorpickerElement = colorpickerElement;
40 + this._toggleDirection = toggleDirection;
41 + this._makePickerButtonElement(defaultColor);
42 + this._makePickerLayerElement(colorpickerElement, colorpickerElement.getAttribute('title'));
43 + this._color = defaultColor;
44 + this.picker = tuiColorPicker.create({
45 + container: this.pickerElement,
46 + preset: PICKER_COLOR,
47 + color: defaultColor,
48 + usageStatistics: this.usageStatistics,
49 + });
50 +
51 + this._addEvent();
52 + }
53 +
54 + /**
55 + * Destroys the instance.
56 + */
57 + destroy() {
58 + this._removeEvent();
59 + this.picker.destroy();
60 + this.colorpickerElement.innerHTML = '';
61 + snippet.forEach(this, (value, key) => {
62 + this[key] = null;
63 + });
64 + }
65 +
66 + /**
67 + * Get color
68 + * @returns {Number} color value
69 + */
70 + get color() {
71 + return this._color;
72 + }
73 +
74 + /**
75 + * Set color
76 + * @param {string} color color value
77 + */
78 + set color(color) {
79 + this._color = color;
80 + this._changeColorElement(color);
81 + }
82 +
83 + /**
84 + * Change color element
85 + * @param {string} color color value
86 + * #private
87 + */
88 + _changeColorElement(color) {
89 + if (color) {
90 + this.colorElement.classList.remove('transparent');
91 + this.colorElement.style.backgroundColor = color;
92 + } else {
93 + this.colorElement.style.backgroundColor = '#fff';
94 + this.colorElement.classList.add('transparent');
95 + }
96 + }
97 +
98 + /**
99 + * Make picker button element
100 + * @param {string} defaultColor color value
101 + * @private
102 + */
103 + _makePickerButtonElement(defaultColor) {
104 + this.colorpickerElement.classList.add('tui-image-editor-button');
105 +
106 + this.colorElement = document.createElement('div');
107 + this.colorElement.className = 'color-picker-value';
108 + if (defaultColor) {
109 + this.colorElement.style.backgroundColor = defaultColor;
110 + } else {
111 + this.colorElement.classList.add('transparent');
112 + }
113 + }
114 +
115 + /**
116 + * Make picker layer element
117 + * @param {HTMLElement} colorpickerElement color picker element
118 + * @param {string} title picker title
119 + * @private
120 + */
121 + _makePickerLayerElement(colorpickerElement, title) {
122 + const label = document.createElement('label');
123 + const triangle = document.createElement('div');
124 +
125 + this.pickerControl = document.createElement('div');
126 + this.pickerControl.className = 'color-picker-control';
127 +
128 + this.pickerElement = document.createElement('div');
129 + this.pickerElement.className = 'color-picker';
130 +
131 + label.innerHTML = title;
132 + triangle.className = 'triangle';
133 +
134 + this.pickerControl.appendChild(this.pickerElement);
135 + this.pickerControl.appendChild(triangle);
136 +
137 + colorpickerElement.appendChild(this.pickerControl);
138 + colorpickerElement.appendChild(this.colorElement);
139 + colorpickerElement.appendChild(label);
140 + }
141 +
142 + /**
143 + * Add event
144 + * @private
145 + */
146 + _addEvent() {
147 + this.picker.on('selectColor', (value) => {
148 + this._changeColorElement(value.color);
149 + this._color = value.color;
150 + this.fire('change', value.color);
151 + });
152 +
153 + this.eventHandler = {
154 + pickerToggle: this._pickerToggleEventHandler.bind(this),
155 + pickerHide: () => this.hide(),
156 + };
157 +
158 + this.colorpickerElement.addEventListener('click', this.eventHandler.pickerToggle);
159 + document.body.addEventListener('click', this.eventHandler.pickerHide);
160 + }
161 +
162 + /**
163 + * Remove event
164 + * @private
165 + */
166 + _removeEvent() {
167 + this.colorpickerElement.removeEventListener('click', this.eventHandler.pickerToggle);
168 + document.body.removeEventListener('click', this.eventHandler.pickerHide);
169 + this.picker.off();
170 + }
171 +
172 + /**
173 + * Picker toggle event handler
174 + * @param {object} event - change event
175 + * @private
176 + */
177 + _pickerToggleEventHandler(event) {
178 + const { target } = event;
179 + const isInPickerControl = target && this._isElementInColorPickerControl(target);
180 +
181 + if (!isInPickerControl || (isInPickerControl && this._isPaletteButton(target))) {
182 + this._show = !this._show;
183 + this.pickerControl.style.display = this._show ? 'block' : 'none';
184 + this._setPickerControlPosition();
185 + this.fire('changeShow', this);
186 + }
187 + event.stopPropagation();
188 + }
189 +
190 + /**
191 + * Check hex input or not
192 + * @param {Element} target - Event target element
193 + * @returns {boolean}
194 + * @private
195 + */
196 + _isPaletteButton(target) {
197 + return target.className === 'tui-colorpicker-palette-button';
198 + }
199 +
200 + /**
201 + * Check given element is in pickerControl element
202 + * @param {Element} element - element to check
203 + * @returns {boolean}
204 + * @private
205 + */
206 + _isElementInColorPickerControl(element) {
207 + let parentNode = element;
208 +
209 + while (parentNode !== document.body) {
210 + if (!parentNode) {
211 + break;
212 + }
213 +
214 + if (parentNode === this.pickerControl) {
215 + return true;
216 + }
217 +
218 + parentNode = parentNode.parentNode;
219 + }
220 +
221 + return false;
222 + }
223 +
224 + hide() {
225 + this._show = false;
226 + this.pickerControl.style.display = 'none';
227 + }
228 +
229 + /**
230 + * Set picker control position
231 + * @private
232 + */
233 + _setPickerControlPosition() {
234 + const controlStyle = this.pickerControl.style;
235 + const halfPickerWidth = this._colorpickerElement.clientWidth / 2 + 2;
236 + const left = this.pickerControl.offsetWidth / 2 - halfPickerWidth;
237 + let top = (this.pickerControl.offsetHeight + 10) * -1;
238 +
239 + if (this._toggleDirection === 'down') {
240 + top = 30;
241 + }
242 +
243 + controlStyle.top = `${top}px`;
244 + controlStyle.left = `-${left}px`;
245 + }
246 +}
247 +
248 +snippet.CustomEvents.mixin(Colorpicker);
249 +export default Colorpicker;
1 +import snippet from 'tui-code-snippet';
2 +import { toInteger, clamp } from '../../util';
3 +import { keyCodes } from '../../consts';
4 +
5 +const INPUT_FILTER_REGEXP = /(-?)([0-9]*)[^0-9]*([0-9]*)/g;
6 +
7 +/**
8 + * Range control class
9 + * @class
10 + * @ignore
11 + */
12 +class Range {
13 + /**
14 + * @constructor
15 + * @extends {View}
16 + * @param {Object} rangeElements - Html resources for creating sliders
17 + * @param {HTMLElement} rangeElements.slider - b
18 + * @param {HTMLElement} [rangeElements.input] - c
19 + * @param {Object} options - Slider make options
20 + * @param {number} options.min - min value
21 + * @param {number} options.max - max value
22 + * @param {number} options.value - default value
23 + * @param {number} [options.useDecimal] - Decimal point processing.
24 + * @param {number} [options.realTimeEvent] - Reflect live events.
25 + */
26 + constructor(rangeElements, options = {}) {
27 + this._value = options.value || 0;
28 +
29 + this.rangeElement = rangeElements.slider;
30 + this.rangeInputElement = rangeElements.input;
31 +
32 + this._drawRangeElement();
33 +
34 + this.rangeWidth = this._getRangeWidth();
35 + this._min = options.min || 0;
36 + this._max = options.max || 100;
37 + this._useDecimal = options.useDecimal;
38 + this._absMax = this._min * -1 + this._max;
39 + this.realTimeEvent = options.realTimeEvent || false;
40 +
41 + this.eventHandler = {
42 + startChangingSlide: this._startChangingSlide.bind(this),
43 + stopChangingSlide: this._stopChangingSlide.bind(this),
44 + changeSlide: this._changeSlide.bind(this),
45 + changeSlideFinally: this._changeSlideFinally.bind(this),
46 + changeInput: this._changeValueWithInput.bind(this, false),
47 + changeInputFinally: this._changeValueWithInput.bind(this, true),
48 + changeInputWithArrow: this._changeValueWithInputKeyEvent.bind(this),
49 + };
50 +
51 + this._addClickEvent();
52 + this._addDragEvent();
53 + this._addInputEvent();
54 + this.value = options.value;
55 + this.trigger('change');
56 + }
57 +
58 + /**
59 + * Destroys the instance.
60 + */
61 + destroy() {
62 + this._removeClickEvent();
63 + this._removeDragEvent();
64 + this._removeInputEvent();
65 + this.rangeElement.innerHTML = '';
66 + snippet.forEach(this, (value, key) => {
67 + this[key] = null;
68 + });
69 + }
70 +
71 + /**
72 + * Set range max value and re position cursor
73 + * @param {number} maxValue - max value
74 + */
75 + set max(maxValue) {
76 + this._max = maxValue;
77 + this._absMax = this._min * -1 + this._max;
78 + this.value = this._value;
79 + }
80 +
81 + get max() {
82 + return this._max;
83 + }
84 +
85 + /**
86 + * Get range value
87 + * @returns {Number} range value
88 + */
89 + get value() {
90 + return this._value;
91 + }
92 +
93 + /**
94 + * Set range value
95 + * @param {Number} value range value
96 + * @param {Boolean} fire whether fire custom event or not
97 + */
98 + set value(value) {
99 + value = this._useDecimal ? value : toInteger(value);
100 +
101 + const absValue = value - this._min;
102 + let leftPosition = (absValue * this.rangeWidth) / this._absMax;
103 +
104 + if (this.rangeWidth < leftPosition) {
105 + leftPosition = this.rangeWidth;
106 + }
107 +
108 + this.pointer.style.left = `${leftPosition}px`;
109 + this.subbar.style.right = `${this.rangeWidth - leftPosition}px`;
110 +
111 + this._value = value;
112 + if (this.rangeInputElement) {
113 + this.rangeInputElement.value = value;
114 + }
115 + }
116 +
117 + /**
118 + * event tirigger
119 + * @param {string} type - type
120 + */
121 + trigger(type) {
122 + this.fire(type, this._value);
123 + }
124 +
125 + /**
126 + * Calculate slider width
127 + * @returns {number} - slider width
128 + */
129 + _getRangeWidth() {
130 + const getElementWidth = (element) => toInteger(window.getComputedStyle(element, null).width);
131 +
132 + return getElementWidth(this.rangeElement) - getElementWidth(this.pointer);
133 + }
134 +
135 + /**
136 + * Make range element
137 + * @private
138 + */
139 + _drawRangeElement() {
140 + this.rangeElement.classList.add('tui-image-editor-range');
141 +
142 + this.bar = document.createElement('div');
143 + this.bar.className = 'tui-image-editor-virtual-range-bar';
144 +
145 + this.subbar = document.createElement('div');
146 + this.subbar.className = 'tui-image-editor-virtual-range-subbar';
147 +
148 + this.pointer = document.createElement('div');
149 + this.pointer.className = 'tui-image-editor-virtual-range-pointer';
150 +
151 + this.bar.appendChild(this.subbar);
152 + this.bar.appendChild(this.pointer);
153 + this.rangeElement.appendChild(this.bar);
154 + }
155 +
156 + /**
157 + * Add range input editing event
158 + * @private
159 + */
160 + _addInputEvent() {
161 + if (this.rangeInputElement) {
162 + this.rangeInputElement.addEventListener('keydown', this.eventHandler.changeInputWithArrow);
163 + this.rangeInputElement.addEventListener('keyup', this.eventHandler.changeInput);
164 + this.rangeInputElement.addEventListener('blur', this.eventHandler.changeInputFinally);
165 + }
166 + }
167 +
168 + /**
169 + * Remove range input editing event
170 + * @private
171 + */
172 + _removeInputEvent() {
173 + if (this.rangeInputElement) {
174 + this.rangeInputElement.removeEventListener('keydown', this.eventHandler.changeInputWithArrow);
175 + this.rangeInputElement.removeEventListener('keyup', this.eventHandler.changeInput);
176 + this.rangeInputElement.removeEventListener('blur', this.eventHandler.changeInputFinally);
177 + }
178 + }
179 +
180 + /**
181 + * change angle event
182 + * @param {object} event - key event
183 + * @private
184 + */
185 + _changeValueWithInputKeyEvent(event) {
186 + const { keyCode, target } = event;
187 +
188 + if ([keyCodes.ARROW_UP, keyCodes.ARROW_DOWN].indexOf(keyCode) < 0) {
189 + return;
190 + }
191 +
192 + let value = Number(target.value);
193 +
194 + value = this._valueUpDownForKeyEvent(value, keyCode);
195 +
196 + const unChanged = value < this._min || value > this._max;
197 +
198 + if (!unChanged) {
199 + const clampValue = clamp(value, this._min, this.max);
200 + this.value = clampValue;
201 + this.fire('change', clampValue, false);
202 + }
203 + }
204 +
205 + /**
206 + * value up down for input
207 + * @param {number} value - original value number
208 + * @param {number} keyCode - input event key code
209 + * @returns {number} value - changed value
210 + * @private
211 + */
212 + _valueUpDownForKeyEvent(value, keyCode) {
213 + const step = this._useDecimal ? 0.1 : 1;
214 +
215 + if (keyCode === keyCodes.ARROW_UP) {
216 + value += step;
217 + } else if (keyCode === keyCodes.ARROW_DOWN) {
218 + value -= step;
219 + }
220 +
221 + return value;
222 + }
223 +
224 + /**
225 + * change angle event
226 + * @param {boolean} isLast - Is last change
227 + * @param {object} event - key event
228 + * @private
229 + */
230 + _changeValueWithInput(isLast, event) {
231 + const { keyCode, target } = event;
232 +
233 + if ([keyCodes.ARROW_UP, keyCodes.ARROW_DOWN].indexOf(keyCode) >= 0) {
234 + return;
235 + }
236 +
237 + const stringValue = this._filterForInputText(target.value);
238 + const waitForChange = !stringValue || isNaN(stringValue);
239 + target.value = stringValue;
240 +
241 + if (!waitForChange) {
242 + let value = this._useDecimal ? Number(stringValue) : toInteger(stringValue);
243 + value = clamp(value, this._min, this.max);
244 +
245 + this.value = value;
246 + this.fire('change', value, isLast);
247 + }
248 + }
249 +
250 + /**
251 + * Add Range click event
252 + * @private
253 + */
254 + _addClickEvent() {
255 + this.rangeElement.addEventListener('click', this.eventHandler.changeSlideFinally);
256 + }
257 +
258 + /**
259 + * Remove Range click event
260 + * @private
261 + */
262 + _removeClickEvent() {
263 + this.rangeElement.removeEventListener('click', this.eventHandler.changeSlideFinally);
264 + }
265 +
266 + /**
267 + * Add Range drag event
268 + * @private
269 + */
270 + _addDragEvent() {
271 + this.pointer.addEventListener('mousedown', this.eventHandler.startChangingSlide);
272 + }
273 +
274 + /**
275 + * Remove Range drag event
276 + * @private
277 + */
278 + _removeDragEvent() {
279 + this.pointer.removeEventListener('mousedown', this.eventHandler.startChangingSlide);
280 + }
281 +
282 + /**
283 + * change angle event
284 + * @param {object} event - change event
285 + * @private
286 + */
287 + _changeSlide(event) {
288 + const changePosition = event.screenX;
289 + const diffPosition = changePosition - this.firstPosition;
290 + let touchPx = this.firstLeft + diffPosition;
291 + touchPx = touchPx > this.rangeWidth ? this.rangeWidth : touchPx;
292 + touchPx = touchPx < 0 ? 0 : touchPx;
293 +
294 + this.pointer.style.left = `${touchPx}px`;
295 + this.subbar.style.right = `${this.rangeWidth - touchPx}px`;
296 +
297 + const ratio = touchPx / this.rangeWidth;
298 + const resultValue = this._absMax * ratio + this._min;
299 + const value = this._useDecimal ? resultValue : toInteger(resultValue);
300 + const isValueChanged = this.value !== value;
301 +
302 + if (isValueChanged) {
303 + this.value = value;
304 + if (this.realTimeEvent) {
305 + this.fire('change', this._value, false);
306 + }
307 + }
308 + }
309 +
310 + _changeSlideFinally(event) {
311 + event.stopPropagation();
312 + if (event.target.className !== 'tui-image-editor-range') {
313 + return;
314 + }
315 + const touchPx = event.offsetX;
316 + const ratio = touchPx / this.rangeWidth;
317 + const value = this._absMax * ratio + this._min;
318 + this.pointer.style.left = `${ratio * this.rangeWidth}px`;
319 + this.subbar.style.right = `${(1 - ratio) * this.rangeWidth}px`;
320 + this.value = value;
321 +
322 + this.fire('change', value, true);
323 + }
324 +
325 + _startChangingSlide(event) {
326 + this.firstPosition = event.screenX;
327 + this.firstLeft = toInteger(this.pointer.style.left) || 0;
328 +
329 + document.addEventListener('mousemove', this.eventHandler.changeSlide);
330 + document.addEventListener('mouseup', this.eventHandler.stopChangingSlide);
331 + }
332 +
333 + /**
334 + * stop change angle event
335 + * @private
336 + */
337 + _stopChangingSlide() {
338 + this.fire('change', this._value, true);
339 +
340 + document.removeEventListener('mousemove', this.eventHandler.changeSlide);
341 + document.removeEventListener('mouseup', this.eventHandler.stopChangingSlide);
342 + }
343 +
344 + /**
345 + * Unnecessary string filtering.
346 + * @param {string} inputValue - origin string of input
347 + * @returns {string} filtered string
348 + * @private
349 + */
350 + _filterForInputText(inputValue) {
351 + return inputValue.replace(INPUT_FILTER_REGEXP, '$1$2$3');
352 + }
353 +}
354 +
355 +snippet.CustomEvents.mixin(Range);
356 +export default Range;
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Util
4 + */
5 +import { forEach, sendHostname, extend, isString, pick, inArray } from 'tui-code-snippet';
6 +import Promise from 'core-js-pure/features/promise';
7 +import { SHAPE_FILL_TYPE, SHAPE_TYPE } from './consts';
8 +const FLOATING_POINT_DIGIT = 2;
9 +const CSS_PREFIX = 'tui-image-editor-';
10 +const { min, max } = Math;
11 +let hostnameSent = false;
12 +
13 +/**
14 + * Export Promise Class (for simplified module path)
15 + * @returns {Promise} promise class
16 + */
17 +export { Promise };
18 +
19 +/**
20 + * Clamp value
21 + * @param {number} value - Value
22 + * @param {number} minValue - Minimum value
23 + * @param {number} maxValue - Maximum value
24 + * @returns {number} clamped value
25 + */
26 +export function clamp(value, minValue, maxValue) {
27 + let temp;
28 + if (minValue > maxValue) {
29 + temp = minValue;
30 + minValue = maxValue;
31 + maxValue = temp;
32 + }
33 +
34 + return max(minValue, min(value, maxValue));
35 +}
36 +
37 +/**
38 + * Make key-value object from arguments
39 + * @returns {object.<string, string>}
40 + */
41 +export function keyMirror(...args) {
42 + const obj = {};
43 +
44 + forEach(args, (key) => {
45 + obj[key] = key;
46 + });
47 +
48 + return obj;
49 +}
50 +
51 +/**
52 + * Make CSSText
53 + * @param {Object} styleObj - Style info object
54 + * @returns {string} Connected string of style
55 + */
56 +export function makeStyleText(styleObj) {
57 + let styleStr = '';
58 +
59 + forEach(styleObj, (value, prop) => {
60 + styleStr += `${prop}: ${value};`;
61 + });
62 +
63 + return styleStr;
64 +}
65 +
66 +/**
67 + * Get object's properties
68 + * @param {Object} obj - object
69 + * @param {Array} keys - keys
70 + * @returns {Object} properties object
71 + */
72 +export function getProperties(obj, keys) {
73 + const props = {};
74 + const { length } = keys;
75 + let i = 0;
76 + let key;
77 +
78 + for (i = 0; i < length; i += 1) {
79 + key = keys[i];
80 + props[key] = obj[key];
81 + }
82 +
83 + return props;
84 +}
85 +
86 +/**
87 + * ParseInt simpliment
88 + * @param {number} value - Value
89 + * @returns {number}
90 + */
91 +export function toInteger(value) {
92 + return parseInt(value, 10);
93 +}
94 +
95 +/**
96 + * String to camelcase string
97 + * @param {string} targetString - change target
98 + * @returns {string}
99 + * @private
100 + */
101 +export function toCamelCase(targetString) {
102 + return targetString.replace(/-([a-z])/g, ($0, $1) => $1.toUpperCase());
103 +}
104 +
105 +/**
106 + * Check browser file api support
107 + * @returns {boolean}
108 + * @private
109 + */
110 +export function isSupportFileApi() {
111 + return !!(window.File && window.FileList && window.FileReader);
112 +}
113 +
114 +/**
115 + * hex to rgb
116 + * @param {string} color - hex color
117 + * @param {string} alpha - color alpha value
118 + * @returns {string} rgb expression
119 + */
120 +export function getRgb(color, alpha) {
121 + if (color.length === 4) {
122 + color = `${color}${color.slice(1, 4)}`;
123 + }
124 + const r = parseInt(color.slice(1, 3), 16);
125 + const g = parseInt(color.slice(3, 5), 16);
126 + const b = parseInt(color.slice(5, 7), 16);
127 + const a = alpha || 1;
128 +
129 + return `rgba(${r}, ${g}, ${b}, ${a})`;
130 +}
131 +
132 +/**
133 + * send hostname
134 + */
135 +export function sendHostName() {
136 + if (hostnameSent) {
137 + return;
138 + }
139 + hostnameSent = true;
140 +
141 + sendHostname('image-editor', 'UA-129999381-1');
142 +}
143 +
144 +/**
145 + * Apply css resource
146 + * @param {string} styleBuffer - serialized css text
147 + * @param {string} tagId - style tag id
148 + */
149 +export function styleLoad(styleBuffer, tagId) {
150 + const [head] = document.getElementsByTagName('head');
151 + const linkElement = document.createElement('link');
152 + const styleData = encodeURIComponent(styleBuffer);
153 + if (tagId) {
154 + linkElement.id = tagId;
155 + // linkElement.id = 'tui-image-editor-theme-style';
156 + }
157 + linkElement.setAttribute('rel', 'stylesheet');
158 + linkElement.setAttribute('type', 'text/css');
159 + linkElement.setAttribute('href', `data:text/css;charset=UTF-8,${styleData}`);
160 + head.appendChild(linkElement);
161 +}
162 +
163 +/**
164 + * Get selector
165 + * @param {HTMLElement} targetElement - target element
166 + * @returns {Function} selector
167 + */
168 +export function getSelector(targetElement) {
169 + return (str) => targetElement.querySelector(str);
170 +}
171 +
172 +/**
173 + * Change base64 to blob
174 + * @param {String} data - base64 string data
175 + * @returns {Blob} Blob Data
176 + */
177 +export function base64ToBlob(data) {
178 + const rImageType = /data:(image\/.+);base64,/;
179 + let mimeString = '';
180 + let raw, uInt8Array, i;
181 +
182 + raw = data.replace(rImageType, (header, imageType) => {
183 + mimeString = imageType;
184 +
185 + return '';
186 + });
187 +
188 + raw = atob(raw);
189 + const rawLength = raw.length;
190 + uInt8Array = new Uint8Array(rawLength); // eslint-disable-line
191 +
192 + for (i = 0; i < rawLength; i += 1) {
193 + uInt8Array[i] = raw.charCodeAt(i);
194 + }
195 +
196 + return new Blob([uInt8Array], { type: mimeString });
197 +}
198 +
199 +/**
200 + * Fix floating point diff.
201 + * @param {number} value - original value
202 + * @returns {number} fixed value
203 + */
204 +export function fixFloatingPoint(value) {
205 + return Number(value.toFixed(FLOATING_POINT_DIGIT));
206 +}
207 +
208 +/**
209 + * Assignment for destroying objects.
210 + * @param {Object} targetObject - object to be removed.
211 + */
212 +export function assignmentForDestroy(targetObject) {
213 + forEach(targetObject, (value, key) => {
214 + targetObject[key] = null;
215 + });
216 +}
217 +
218 +/**
219 + * Make class name for ui
220 + * @param {String} str - main string of className
221 + * @param {String} prefix - prefix string of className
222 + * @returns {String} class name
223 + */
224 +export function cls(str = '', prefix = '') {
225 + if (str.charAt(0) === '.') {
226 + return `.${CSS_PREFIX}${prefix}${str.slice(1)}`;
227 + }
228 +
229 + return `${CSS_PREFIX}${prefix}${str}`;
230 +}
231 +
232 +/**
233 + * Change object origin
234 + * @param {fabric.Object} fObject - fabric object
235 + * @param {Object} origin - origin of fabric object
236 + * @param {string} originX - horizontal basis.
237 + * @param {string} originY - vertical basis.
238 + */
239 +export function changeOrigin(fObject, origin) {
240 + const { originX, originY } = origin;
241 + const { x: left, y: top } = fObject.getPointByOrigin(originX, originY);
242 +
243 + fObject.set({
244 + left,
245 + top,
246 + originX,
247 + originY,
248 + });
249 +
250 + fObject.setCoords();
251 +}
252 +
253 +/**
254 + * Object key value flip
255 + * @param {Object} targetObject - The data object of the key value.
256 + * @returns {Object}
257 + */
258 +export function flipObject(targetObject) {
259 + const result = {};
260 +
261 + Object.keys(targetObject).forEach((key) => {
262 + result[targetObject[key]] = key;
263 + });
264 +
265 + return result;
266 +}
267 +
268 +/**
269 + * Set custom properties
270 + * @param {Object} targetObject - target object
271 + * @param {Object} props - custom props object
272 + */
273 +export function setCustomProperty(targetObject, props) {
274 + targetObject.customProps = targetObject.customProps || {};
275 + extend(targetObject.customProps, props);
276 +}
277 +
278 +/**
279 + * Get custom property
280 + * @param {fabric.Object} fObject - fabric object
281 + * @param {Array|string} propNames - prop name array
282 + * @returns {object | number | string}
283 + */
284 +export function getCustomProperty(fObject, propNames) {
285 + const resultObject = {};
286 + if (isString(propNames)) {
287 + propNames = [propNames];
288 + }
289 + forEach(propNames, (propName) => {
290 + resultObject[propName] = fObject.customProps[propName];
291 + });
292 +
293 + return resultObject;
294 +}
295 +
296 +/**
297 + * Capitalize string
298 + * @param {string} targetString - target string
299 + * @returns {string}
300 + */
301 +export function capitalizeString(targetString) {
302 + return targetString.charAt(0).toUpperCase() + targetString.slice(1);
303 +}
304 +
305 +/**
306 + * Array includes check
307 + * @param {Array} targetArray - target array
308 + * @param {string|number} compareValue - compare value
309 + * @returns {boolean}
310 + */
311 +export function includes(targetArray, compareValue) {
312 + return targetArray.indexOf(compareValue) >= 0;
313 +}
314 +
315 +/**
316 + * Get fill type
317 + * @param {Object | string} fillOption - shape fill option
318 + * @returns {string} 'color' or 'filter'
319 + */
320 +export function getFillTypeFromOption(fillOption = {}) {
321 + return pick(fillOption, 'type') || SHAPE_FILL_TYPE.COLOR;
322 +}
323 +
324 +/**
325 + * Get fill type of shape type object
326 + * @param {fabric.Object} shapeObj - fabric object
327 + * @returns {string} 'transparent' or 'color' or 'filter'
328 + */
329 +export function getFillTypeFromObject(shapeObj) {
330 + const { fill = {} } = shapeObj;
331 + if (fill.source) {
332 + return SHAPE_FILL_TYPE.FILTER;
333 + }
334 +
335 + return SHAPE_FILL_TYPE.COLOR;
336 +}
337 +
338 +/**
339 + * Check if the object is a shape object.
340 + * @param {fabric.Object} obj - fabric object
341 + * @returns {boolean}
342 + */
343 +export function isShape(obj) {
344 + return inArray(obj.get('type'), SHAPE_TYPE) >= 0;
345 +}
1 +<?xml version="1.0" encoding="UTF-8"?>
2 +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
3 +<svg display="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
4 +<defs id="tui-image-editor-svg-default-icons">
5 +<symbol id="ic-apply" viewBox="0 0 24 24">
6 + <path d="M0 0h24v24H0z" stroke="none" fill="none"/>
7 + <path fill="none" stroke="inherit" d="M4 12.011l5 5L20.011 6"/>
8 +</symbol>
9 +<symbol id="ic-cancel" viewBox="0 0 24 24">
10 + <path d="M0 0h24v24H0z" fill="none" stroke="none"/>
11 + <path fill="none" stroke="inherit" d="M6 6l12 12M18 6L6 18"/>
12 +</symbol>
13 +<symbol id="ic-crop" viewBox="0 0 24 24">
14 + <path d="M0 0h24v24H0z" stroke="none" fill="none" />
15 + <path stroke="none" fill="inherit" d="M4 0h1v20a1 1 0 0 1-1-1V0zM20 17h-1V5h1v12zm0 2v5h-1v-5h1z"/>
16 + <path stroke="none" fill="inherit" d="M5 19h19v1H5zM4.762 4v1H0V4h4.762zM7 4h12a1 1 0 0 1 1 1H7V4z"/>
17 +</symbol>
18 +<symbol id="ic-delete-all" viewBox="0 0 24 24">
19 + <path stroke="none" fill="inherit" d="M5 23H3a1 1 0 0 1-1-1V6h1v16h2v1zm16-10h-1V6h1v7zM9 13H8v-3h1v3zm3 0h-1v-3h1v3zm3 0h-1v-3h1v3zM14.794 3.794L13 2h-3L8.206 3.794A.963.963 0 0 1 8 2.5l.703-1.055A1 1 0 0 1 9.535 1h3.93a1 1 0 0 1 .832.445L15 2.5a.965.965 0 0 1-.206 1.294zM14.197 4H8.803h5.394z"/>
20 + <path stroke="none" fill="inherit" d="M0 3h23v1H0zM11.286 21H8.714L8 23H7l1-2.8V20h.071L9.5 16h1l1.429 4H12v.2l1 2.8h-1l-.714-2zm-.357-1L10 17.4 9.071 20h1.858zM20 22h3v1h-4v-7h1v6zm-5 0h3v1h-4v-7h1v6z"/>
21 +</symbol>
22 +<symbol id="ic-delete" viewBox="0 0 24 24">
23 + <path stroke="none" fill="inherit" d="M3 6v16h17V6h1v16a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V6h1zM14.794 3.794L13 2h-3L8.206 3.794A.963.963 0 0 1 8 2.5l.703-1.055A1 1 0 0 1 9.535 1h3.93a1 1 0 0 1 .832.445L15 2.5a.965.965 0 0 1-.206 1.294zM14.197 4H8.803h5.394z"/>
24 + <path stroke="none" fill="inherit" d="M0 3h23v1H0zM8 10h1v6H8v-6zm3 0h1v6h-1v-6zm3 0h1v6h-1v-6z"/>
25 +</symbol>
26 +<symbol id="ic-draw-free" viewBox="0 0 32 32">
27 + <path fill="none" stroke="inherit" d="M2.5 20.929C2.594 10.976 4.323 6 7.686 6c5.872 0 2.524 19 7.697 19s1.89-14.929 6.414-14.929 1.357 10.858 5.13 10.858c1.802 0 2.657-2.262 2.566-6.786"/>
28 +</symbol>
29 +<symbol id="ic-draw-line" viewBox="0 0 32 32">
30 + <path fill="none" stroke="inherit" d="M2 15.5h28"/>
31 +</symbol>
32 +<symbol id="ic-draw" viewBox="0 0 24 24">
33 + <path fill="none" stroke="inherit" d="M2.5 21.5H5c.245 0 .48-.058.691-.168l.124-.065.14.01c.429.028.85-.127 1.16-.437L22.55 5.405a.5.5 0 0 0 0-.707l-3.246-3.245a.5.5 0 0 0-.707 0L3.162 16.888a1.495 1.495 0 0 0-.437 1.155l.01.14-.065.123c-.111.212-.17.448-.17.694v2.5z"/>
34 + <path stroke="none" fill="inherit" d="M16.414 3.707l3.89 3.89-.708.706-3.889-3.889z"/>
35 +</symbol>
36 +<symbol id="ic-filter" viewBox="0 0 24 24">
37 + <path d="M0 0h24v24H0z" fill="none" stroke="none" />
38 + <path stroke="none" fill="inherit" d="M12 7v1H2V7h10zm6 0h4v1h-4V7zM12 16v1h10v-1H12zm-6 0H2v1h4v-1z"/>
39 + <path stroke="none" fill="inherit" d="M8.5 20a3.5 3.5 0 1 1 0-7 3.5 3.5 0 0 1 0 7zm0-1a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5zM15.5 11a3.5 3.5 0 1 1 0-7 3.5 3.5 0 0 1 0 7zm0-1a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5z"/>
40 +</symbol>
41 +<symbol id="ic-flip-reset" viewBox="0 0 31 32">
42 + <path fill="none" stroke="none" d="M31 0H0v32h31z"/>
43 + <path stroke="none" fill="inherit" d="M28 16a8 8 0 0 1-8 8H3v-1h1v-7H3a8 8 0 0 1 8-8h17v1h-1v7h1zM11 9a7 7 0 0 0-7 7v7h16a7 7 0 0 0 7-7V9H11z"/>
44 + <path fill="none" stroke="inherit" stroke-linecap="square" d="M24 5l3.5 3.5L24 12M7 20l-3.5 3.5L7 27"/>
45 +</symbol>
46 +<symbol id="ic-flip-x" viewBox="0 0 32 32">
47 + <path fill="none" stroke="none" d="M32 32H0V0h32z"/>
48 + <path stroke="none" fill="inherit" d="M17 32h-1V0h1zM27.167 11l.5 3h-1.03l-.546-3h1.076zm-.5-3h-1.122L25 5h-5V4h5.153a1 1 0 0 1 .986.836L26.667 8zm1.5 9l.5 3h-.94l-.545-3h.985zm1 6l.639 3.836A1 1 0 0 1 28.819 28H26v-1h3l-.726-4h.894zM23 28h-3v-1h3v1zM13 4v1H7L3 27h10v1H3.18a1 1 0 0 1-.986-1.164l3.666-22A1 1 0 0 1 6.847 4H13z"/>
49 +</symbol>
50 +<symbol id="ic-flip-y" viewBox="0 0 32 32">
51 + <path fill="none" stroke="none" d="M0 0v32h32V0z"/>
52 + <path stroke="none" fill="inherit" d="M0 16v1h32v-1zM11 27.167l3 .5v-1.03l-3-.546v1.076zm-3-.5v-1.122L5 25v-5H4v5.153a1 1 0 0 0 .836.986L8 26.667zm9 1.5l3 .5v-.94l-3-.545v.985zm6 1l3.836.639A1 1 0 0 0 28 28.82V26h-1v3l-4-.727v.894zM28 23v-3h-1v3h1zM4 13h1V7l22-4v10h1V3.18a1 1 0 0 0-1.164-.986l-22 3.667A1 1 0 0 0 4 6.847V13z"/>
53 +</symbol>
54 +<symbol id="ic-flip" viewBox="0 0 24 24">
55 + <path d="M0 0h24v24H0z" fill="none" stroke="none" />
56 + <path fill="inherit" stroke="none" d="M11 0h1v24h-1zM19 21v-1h2v-2h1v2a1 1 0 0 1-1 1h-2zm-2 0h-3v-1h3v1zm5-5h-1v-3h1v3zm0-5h-1V8h1v3zm0-5h-1V4h-2V3h2a1 1 0 0 1 1 1v2zm-5-3v1h-3V3h3zM9 3v1H2v16h7v1H2a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h7z"/>
57 +</symbol>
58 +<symbol id="ic-icon-arrow-2" viewBox="0 0 32 32">
59 + <path fill="none" stroke="inherit" stroke-linecap="round" stroke-linejoin="round" d="M21.793 18.5H2.5v-5h18.935l-7.6-8h5.872l10.5 10.5-10.5 10.5h-5.914l8-8z"/>
60 +</symbol>
61 +<symbol id="ic-icon-arrow-3" viewBox="0 0 32 32">
62 + <path fill="none" stroke="inherit" stroke-linecap="round" stroke-linejoin="round" d="M25.288 16.42L14.208 27.5H6.792l11.291-11.291L6.826 4.5h7.381l11.661 11.661-.58.258z"/>
63 +</symbol>
64 +<symbol id="ic-icon-arrow" viewBox="0 0 32 32">
65 + <path fill="none" stroke="inherit" d="M2.5 11.5v9h18v5.293L30.293 16 20.5 6.207V11.5h-18z"/>
66 +</symbol>
67 +<symbol id="ic-icon-bubble" viewBox="0 0 32 32">
68 + <path fill="none" stroke="inherit" stroke-linecap="round" stroke-linejoin="round" d="M22.207 24.5L16.5 30.207V24.5H8A6.5 6.5 0 0 1 1.5 18V9A6.5 6.5 0 0 1 8 2.5h16A6.5 6.5 0 0 1 30.5 9v9a6.5 6.5 0 0 1-6.5 6.5h-1.793z"/>
69 +</symbol>
70 +<symbol id="ic-icon-heart" viewBox="0 0 32 32">
71 + <path fill-rule="nonzero" fill="none" stroke="inherit" d="M15.996 30.675l1.981-1.79c7.898-7.177 10.365-9.718 12.135-13.012.922-1.716 1.377-3.37 1.377-5.076 0-4.65-3.647-8.297-8.297-8.297-2.33 0-4.86 1.527-6.817 3.824l-.38.447-.381-.447C13.658 4.027 11.126 2.5 8.797 2.5 4.147 2.5.5 6.147.5 10.797c0 1.714.46 3.375 1.389 5.098 1.775 3.288 4.26 5.843 12.123 12.974l1.984 1.806z"/>
72 +</symbol>
73 +<symbol id="ic-icon-load" viewBox="0 0 32 32">
74 + <path fill="none" stroke="inherit" stroke-linecap="round" stroke-linejoin="round" d="M17.314 18.867l1.951-2.53 4 5.184h-17l6.5-8.84 4.549 6.186z"/>
75 + <path stroke="none" fill="inherit" d="M18.01 4a11.798 11.798 0 0 0 0 1H3v24h24V14.986a8.738 8.738 0 0 0 1 0V29a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h15.01z"/>
76 + <path stroke="none" fill="inherit" d="M25 3h1v9h-1z"/>
77 + <path fill="none" stroke="inherit" d="M22 6l3.5-3.5L29 6"/>
78 +</symbol>
79 +<symbol id="ic-icon-location" viewBox="0 0 32 32">
80 + <path fill="none" stroke="inherit" d="M16 31.28C23.675 23.302 27.5 17.181 27.5 13c0-6.351-5.149-11.5-11.5-11.5S4.5 6.649 4.5 13c0 4.181 3.825 10.302 11.5 18.28z"/>
81 + <circle fill="none" stroke="inherit" cx="16" cy="13" r="4.5"/>
82 +</symbol>
83 +<symbol id="ic-icon-polygon" viewBox="0 0 32 32">
84 + <path fill="none" stroke="inherit" d="M.576 16L8.29 29.5h15.42L31.424 16 23.71 2.5H8.29L.576 16z"/>
85 +</symbol>
86 +<symbol id="ic-icon-star-2" viewBox="0 0 32 32">
87 + <path fill="none" stroke="inherit" d="M19.446 31.592l2.265-3.272 3.946.25.636-3.94 3.665-1.505-1.12-3.832 2.655-2.962-2.656-2.962 1.12-3.832-3.664-1.505-.636-3.941-3.946.25-2.265-3.271L16 3.024 12.554 1.07 10.289 4.34l-3.946-.25-.636 3.941-3.665 1.505 1.12 3.832L.508 16.33l2.656 2.962-1.12 3.832 3.664 1.504.636 3.942 3.946-.25 2.265 3.27L16 29.638l3.446 1.955z"/>
88 +</symbol>
89 +<symbol id="ic-icon-star" viewBox="0 0 32 32">
90 + <path fill="none" stroke="inherit" d="M25.292 29.878l-1.775-10.346 7.517-7.327-10.388-1.51L16 1.282l-4.646 9.413-10.388 1.51 7.517 7.327-1.775 10.346L16 24.993l9.292 4.885z"/>
91 +</symbol>
92 +<symbol id="ic-icon" viewBox="0 0 24 24">
93 + <path fill="none" stroke="inherit" stroke-linecap="round" stroke-linejoin="round" d="M11.923 19.136L5.424 22l.715-7.065-4.731-5.296 6.94-1.503L11.923 2l3.574 6.136 6.94 1.503-4.731 5.296L18.42 22z"/>
94 +</symbol>
95 +<symbol id="ic-mask-load" viewBox="0 0 32 32">
96 + <path stroke="none" fill="none" d="M0 0h32v32H0z"/>
97 + <path stroke="none" fill="inherit" d="M18.01 4a11.798 11.798 0 0 0 0 1H3v24h24V14.986a8.738 8.738 0 0 0 1 0V29a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h15.01zM15 23a6 6 0 1 1 0-12 6 6 0 0 1 0 12zm0-1a5 5 0 1 0 0-10 5 5 0 0 0 0 10z"/>
98 + <path stroke="none" fill="inherit" d="M25 3h1v9h-1z"/>
99 + <path fill="none" stroke="inherit" d="M22 6l3.5-3.5L29 6"/>
100 +</symbol>
101 +<symbol id="ic-mask" viewBox="0 0 24 24">
102 + <circle cx="12" cy="12" r="4.5" stroke="inherit" fill="none"/>
103 + <path stroke="none" fill="inherit" d="M2 1h20a1 1 0 0 1 1 1v20a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1zm0 1v20h20V2H2z"/>
104 +</symbol>
105 +<symbol id="ic-redo" viewBox="0 0 24 24">
106 + <path d="M0 0h24v24H0z" opacity=".5" fill="none" stroke="none" />
107 + <path stroke="none" fill="inherit" d="M21 6H9a6 6 0 1 0 0 12h12v1H9A7 7 0 0 1 9 5h12v1z"/>
108 + <path fill="none" stroke="inherit" stroke-linecap="square" d="M19 3l2.5 2.5L19 8"/>
109 +</symbol>
110 +<symbol id="ic-reset" viewBox="0 0 24 24">
111 + <path d="M0 0h24v24H0z" opacity=".5" stroke="none" fill="none"/>
112 + <path stroke="none" fill="inherit" d="M2 13v-1a7 7 0 0 1 7-7h13v1h-1v5h1v1a7 7 0 0 1-7 7H2v-1h1v-5H2zm7-7a6 6 0 0 0-6 6v6h12a6 6 0 0 0 6-6V6H9z"/>
113 + <path fill="none" stroke="inherit" stroke-linecap="square" d="M19 3l2.5 2.5L19 8M5 16l-2.5 2.5L5 21"/>
114 +</symbol>
115 +<symbol id="ic-rotate-clockwise" viewBox="0 0 32 32">
116 + <path stroke="none" fill="inherit" d="M29 17h-.924c0 6.627-5.373 12-12 12-6.628 0-12-5.373-12-12C4.076 10.398 9.407 5.041 16 5V4C8.82 4 3 9.82 3 17s5.82 13 13 13 13-5.82 13-13z"/>
117 + <path fill="none" stroke="inherit" stroke-linecap="square" d="M16 1.5l4 3-4 3"/>
118 + <path stroke="none" fill="inherit" fill-rule="nonzero" d="M16 4h4v1h-4z"/>
119 +</symbol>
120 +<symbol id="ic-rotate-counterclockwise" viewBox="0 0 32 32">
121 + <path stroke="none" d="M3 17h.924c0 6.627 5.373 12 12 12 6.628 0 12-5.373 12-12 0-6.602-5.331-11.96-11.924-12V4c7.18 0 13 5.82 13 13s-5.82 13-13 13S3 24.18 3 17z"/>
122 + <path stroke="none" fill="inherit" fill-rule="nonzero" d="M12 4h4v1h-4z"/>
123 + <path fill="none" stroke="inherit" stroke-linecap="square" d="M16 1.5l-4 3 4 3"/>
124 +</symbol>
125 +<symbol id="ic-rotate" viewBox="0 0 24 24">
126 + <path d="M0 0h24v24H0z" fill="none" stroke="none" />
127 + <path fill="inherit" stroke="none" d="M8.349 22.254a10.002 10.002 0 0 1-2.778-1.719l.65-.76a9.002 9.002 0 0 0 2.495 1.548l-.367.931zm2.873.704l.078-.997a9 9 0 1 0-.557-17.852l-.14-.99A10.076 10.076 0 0 1 12.145 3c5.523 0 10 4.477 10 10s-4.477 10-10 10c-.312 0-.62-.014-.924-.042zm-7.556-4.655a9.942 9.942 0 0 1-1.253-2.996l.973-.234a8.948 8.948 0 0 0 1.124 2.693l-.844.537zm-1.502-5.91A9.949 9.949 0 0 1 2.88 9.23l.925.382a8.954 8.954 0 0 0-.644 2.844l-.998-.062zm2.21-5.686c.687-.848 1.51-1.58 2.436-2.166l.523.852a9.048 9.048 0 0 0-2.188 1.95l-.771-.636z"/>
128 + <path stroke="inherit" fill="none" stroke-linecap="square" d="M13 1l-2.5 2.5L13 6"/>
129 +</symbol>
130 +<symbol id="ic-shape-circle" viewBox="0 0 32 32">
131 + <circle cx="16" cy="16" r="14.5" fill="none" stroke="inherit"/>
132 +</symbol>
133 +<symbol id="ic-shape-rectangle" viewBox="0 0 32 32">
134 + <rect width="27" height="27" x="2.5" y="2.5" fill="none" stroke="inherit" rx="1"/>
135 +</symbol>
136 +<symbol id="ic-shape-triangle" viewBox="0 0 32 32">
137 + <path fill="none" stroke-linecap="round" stroke-linejoin="round" d="M16 2.5l15.5 27H.5z"/>
138 +</symbol>
139 +<symbol id="ic-shape" viewBox="0 0 24 24">
140 + <path stroke="none" fill="inherit" d="M14.706 8H21a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H9a1 1 0 0 1-1-1v-4h1v4h12V9h-5.706l-.588-1z"/>
141 + <path fill="none" stroke="inherit" stroke-linecap="round" stroke-linejoin="round" d="M8.5 1.5l7.5 13H1z"/>
142 +</symbol>
143 +<symbol id="ic-text-align-center" viewBox="0 0 32 32">
144 + <path stroke="none" fill="none" d="M0 0h32v32H0z"/>
145 + <path stroke="none" fill="inherit" d="M2 5h28v1H2zM8 12h16v1H8zM2 19h28v1H2zM8 26h16v1H8z"/>
146 +</symbol>
147 +<symbol id="ic-text-align-left" viewBox="0 0 32 32">
148 + <path stroke="none" fill="none" d="M0 0h32v32H0z"/>
149 + <path stroke="none" fill="inherit" d="M2 5h28v1H2zM2 12h16v1H2zM2 19h28v1H2zM2 26h16v1H2z"/>
150 +</symbol>
151 +<symbol id="ic-text-align-right" viewBox="0 0 32 32">
152 + <path stroke="none" fill="none" d="M0 0h32v32H0z"/>
153 + <path stroke="none" fill="inherit" d="M2 5h28v1H2zM14 12h16v1H14zM2 19h28v1H2zM14 26h16v1H14z"/>
154 +</symbol>
155 +<symbol id="ic-text-bold" viewBox="0 0 32 32">
156 + <path fill="none" stroke="none" d="M0 0h32v32H0z"/>
157 + <path stroke="none" fill="inherit" d="M7 2h2v2H7zM7 28h2v2H7z"/>
158 + <path fill="none" stroke="inherit" stroke-width="2" d="M9 3v12h9a6 6 0 1 0 0-12H9zM9 15v14h10a7 7 0 0 0 0-14H9z"/>
159 +</symbol>
160 +<symbol id="ic-text-italic" viewBox="0 0 32 32">
161 + <path fill="none" stroke="none" d="M0 0h32v32H0z"/>
162 + <path stroke="none" fill="inherit" d="M15 2h5v1h-5zM11 29h5v1h-5zM17 3h1l-4 26h-1z"/>
163 +</symbol>
164 +<symbol id="ic-text-underline" viewBox="0 0 32 32">
165 + <path stroke="none" fill="none" d="M0 0h32v32H0z"/>
166 + <path stroke="none" fill="inherit" d="M8 2v14a8 8 0 1 0 16 0V2h1v14a9 9 0 0 1-18 0V2h1zM3 29h26v1H3z"/>
167 + <path stroke="none" fill="inherit" d="M5 2h5v1H5zM22 2h5v1h-5z"/>
168 +</symbol>
169 +<symbol id="ic-text" viewBox="0 0 24 24">
170 + <path stroke="none" fill="inherit" d="M4 3h15a1 1 0 0 1 1 1H3a1 1 0 0 1 1-1zM3 4h1v1H3zM19 4h1v1h-1z"/>
171 + <path stroke="none" fill="inherit" d="M11 3h1v18h-1z"/>
172 + <path stroke="none" fill="inherit" d="M10 20h3v1h-3z"/>
173 +</symbol>
174 +<symbol id="ic-undo" viewBox="0 0 24 24">
175 + <path d="M24 0H0v24h24z" opacity=".5" fill="none" stroke="none" />
176 + <path stroke="none" fill="inherit" d="M3 6h12a6 6 0 1 1 0 12H3v1h12a7 7 0 0 0 0-14H3v1z"/>
177 + <path fill="none" stroke="inherit" stroke-linecap="square" d="M5 3L2.5 5.5 5 8"/>
178 +</symbol>
179 +</defs>
180 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M0 0h24v24H0z"/>
4 + <path stroke="#434343" d="M4 12.011l5 5L20.011 6"/>
5 + </g>
6 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M0 0h24v24H0z"/>
4 + <path stroke="#434343" d="M6 6l12 12M18 6L6 18"/>
5 + </g>
6 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32" viewBox="0 0 32 32">
2 + <defs>
3 + <circle id="a" cx="16" cy="16" r="16"/>
4 + </defs>
5 + <g fill="none" fill-rule="evenodd">
6 + <g>
7 + <use fill="#FFF" xlink:href="#a"/>
8 + <circle cx="16" cy="16" r="15.5" stroke="#D5D5D5"/>
9 + </g>
10 + <path stroke="#FF4040" stroke-width="1.5" d="M27 5L5 27"/>
11 + </g>
12 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M0 0h24v24H0z"/>
4 + <path fill="#434343" d="M4 0h1v20a1 1 0 0 1-1-1V0zM20 17h-1V5h1v12zm0 2v5h-1v-5h1z"/>
5 + <path fill="#434343" d="M5 19h19v1H5zM4.762 4v1H0V4h4.762zM7 4h12a1 1 0 0 1 1 1H7V4z"/>
6 + </g>
7 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2 + <g fill="#434343" fill-rule="evenodd">
3 + <path d="M5 23H3a1 1 0 0 1-1-1V6h1v16h2v1zm16-10h-1V6h1v7zM9 13H8v-3h1v3zm3 0h-1v-3h1v3zm3 0h-1v-3h1v3zM14.794 3.794L13 2h-3L8.206 3.794A.963.963 0 0 1 8 2.5l.703-1.055A1 1 0 0 1 9.535 1h3.93a1 1 0 0 1 .832.445L15 2.5a.965.965 0 0 1-.206 1.294zM14.197 4H8.803h5.394z"/>
4 + <path d="M0 3h23v1H0zM11.286 21H8.714L8 23H7l1-2.8V20h.071L9.5 16h1l1.429 4H12v.2l1 2.8h-1l-.714-2zm-.357-1L10 17.4 9.071 20h1.858zM20 22h3v1h-4v-7h1v6zm-5 0h3v1h-4v-7h1v6z"/>
5 + </g>
6 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2 + <g fill="#434343" fill-rule="evenodd">
3 + <path d="M3 6v16h17V6h1v16a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V6h1zM14.794 3.794L13 2h-3L8.206 3.794A.963.963 0 0 1 8 2.5l.703-1.055A1 1 0 0 1 9.535 1h3.93a1 1 0 0 1 .832.445L15 2.5a.965.965 0 0 1-.206 1.294zM14.197 4H8.803h5.394z"/>
4 + <path d="M0 3h23v1H0zM8 10h1v6H8v-6zm3 0h1v6h-1v-6zm3 0h1v6h-1v-6z"/>
5 + </g>
6 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path stroke="#434343" d="M2.5 20.929C2.594 10.976 4.323 6 7.686 6c5.872 0 2.524 19 7.697 19s1.89-14.929 6.414-14.929 1.357 10.858 5.13 10.858c1.802 0 2.657-2.262 2.566-6.786"/>
4 + </g>
5 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path stroke="#434343" d="M2 15.5h28"/>
4 + </g>
5 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2 + <g fill="none">
3 + <path stroke="#434343" d="M2.5 21.5H5c.245 0 .48-.058.691-.168l.124-.065.14.01c.429.028.85-.127 1.16-.437L22.55 5.405a.5.5 0 0 0 0-.707l-3.246-3.245a.5.5 0 0 0-.707 0L3.162 16.888a1.495 1.495 0 0 0-.437 1.155l.01.14-.065.123c-.111.212-.17.448-.17.694v2.5z"/>
4 + <path fill="#434343" d="M16.414 3.707l3.89 3.89-.708.706-3.889-3.889z"/>
5 + </g>
6 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M0 0h24v24H0z"/>
4 + <path fill="#434343" d="M12 7v1H2V7h10zm6 0h4v1h-4V7zM12 16v1h10v-1H12zm-6 0H2v1h4v-1z"/>
5 + <path fill="#434343" d="M8.5 20a3.5 3.5 0 1 1 0-7 3.5 3.5 0 0 1 0 7zm0-1a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5zM15.5 11a3.5 3.5 0 1 1 0-7 3.5 3.5 0 0 1 0 7zm0-1a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5z"/>
6 + </g>
7 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="31" height="32" viewBox="0 0 31 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M31 0H0v32h31z"/>
4 + <path fill="#434343" d="M28 16a8 8 0 0 1-8 8H3v-1h1v-7H3a8 8 0 0 1 8-8h17v1h-1v7h1zM11 9a7 7 0 0 0-7 7v7h16a7 7 0 0 0 7-7V9H11z"/>
5 + <path stroke="#434343" stroke-linecap="square" d="M24 5l3.5 3.5L24 12M7 20l-3.5 3.5L7 27"/>
6 + </g>
7 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M32 32H0V0h32z"/>
4 + <path fill="#434343" d="M17 32h-1V0h1zM27.167 11l.5 3h-1.03l-.546-3h1.076zm-.5-3h-1.122L25 5h-5V4h5.153a1 1 0 0 1 .986.836L26.667 8zm1.5 9l.5 3h-.94l-.545-3h.985zm1 6l.639 3.836A1 1 0 0 1 28.819 28H26v-1h3l-.726-4h.894zM23 28h-3v-1h3v1zM13 4v1H7L3 27h10v1H3.18a1 1 0 0 1-.986-1.164l3.666-22A1 1 0 0 1 6.847 4H13z"/>
5 + </g>
6 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M0 0v32h32V0z"/>
4 + <path fill="#434343" d="M0 16v1h32v-1zM11 27.167l3 .5v-1.03l-3-.546v1.076zm-3-.5v-1.122L5 25v-5H4v5.153a1 1 0 0 0 .836.986L8 26.667zm9 1.5l3 .5v-.94l-3-.545v.985zm6 1l3.836.639A1 1 0 0 0 28 28.82V26h-1v3l-4-.727v.894zM28 23v-3h-1v3h1zM4 13h1V7l22-4v10h1V3.18a1 1 0 0 0-1.164-.986l-22 3.667A1 1 0 0 0 4 6.847V13z"/>
5 + </g>
6 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M0 0h24v24H0z"/>
4 + <path fill="#434343" d="M11 0h1v24h-1zM19 21v-1h2v-2h1v2a1 1 0 0 1-1 1h-2zm-2 0h-3v-1h3v1zm5-5h-1v-3h1v3zm0-5h-1V8h1v3zm0-5h-1V4h-2V3h2a1 1 0 0 1 1 1v2zm-5-3v1h-3V3h3zM9 3v1H2v16h7v1H2a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h7z"/>
5 + </g>
6 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path stroke="#434343" stroke-linecap="round" stroke-linejoin="round" d="M21.793 18.5H2.5v-5h18.935l-7.6-8h5.872l10.5 10.5-10.5 10.5h-5.914l8-8z"/>
4 + </g>
5 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path stroke="#434343" stroke-linecap="round" stroke-linejoin="round" d="M25.288 16.42L14.208 27.5H6.792l11.291-11.291L6.826 4.5h7.381l11.661 11.661-.58.258z"/>
4 + </g>
5 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path stroke="#434343" d="M2.5 11.5v9h18v5.293L30.293 16 20.5 6.207V11.5h-18z"/>
4 + </g>
5 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path stroke="#434343" stroke-linecap="round" stroke-linejoin="round" d="M22.207 24.5L16.5 30.207V24.5H8A6.5 6.5 0 0 1 1.5 18V9A6.5 6.5 0 0 1 8 2.5h16A6.5 6.5 0 0 1 30.5 9v9a6.5 6.5 0 0 1-6.5 6.5h-1.793z"/>
4 + </g>
5 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path fill-rule="nonzero" stroke="#434343" d="M15.996 30.675l1.981-1.79c7.898-7.177 10.365-9.718 12.135-13.012.922-1.716 1.377-3.37 1.377-5.076 0-4.65-3.647-8.297-8.297-8.297-2.33 0-4.86 1.527-6.817 3.824l-.38.447-.381-.447C13.658 4.027 11.126 2.5 8.797 2.5 4.147 2.5.5 6.147.5 10.797c0 1.714.46 3.375 1.389 5.098 1.775 3.288 4.26 5.843 12.123 12.974l1.984 1.806z"/>
4 + </g>
5 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path stroke="#434343" stroke-linecap="round" stroke-linejoin="round" d="M17.314 18.867l1.951-2.53 4 5.184h-17l6.5-8.84 4.549 6.186z"/>
4 + <path fill="#434343" d="M18.01 4a11.798 11.798 0 0 0 0 1H3v24h24V14.986a8.738 8.738 0 0 0 1 0V29a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h15.01z"/>
5 + <path fill="#434343" d="M25 3h1v9h-1z"/>
6 + <path stroke="#434343" d="M22 6l3.5-3.5L29 6"/>
7 + </g>
8 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <g stroke="#434343">
4 + <path d="M16 31.28C23.675 23.302 27.5 17.181 27.5 13c0-6.351-5.149-11.5-11.5-11.5S4.5 6.649 4.5 13c0 4.181 3.825 10.302 11.5 18.28z"/>
5 + <circle cx="16" cy="13" r="4.5"/>
6 + </g>
7 + </g>
8 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path stroke="#434343" d="M.576 16L8.29 29.5h15.42L31.424 16 23.71 2.5H8.29L.576 16z"/>
4 + </g>
5 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path stroke="#434343" d="M19.446 31.592l2.265-3.272 3.946.25.636-3.94 3.665-1.505-1.12-3.832 2.655-2.962-2.656-2.962 1.12-3.832-3.664-1.505-.636-3.941-3.946.25-2.265-3.271L16 3.024 12.554 1.07 10.289 4.34l-3.946-.25-.636 3.941-3.665 1.505 1.12 3.832L.508 16.33l2.656 2.962-1.12 3.832 3.664 1.504.636 3.942 3.946-.25 2.265 3.27L16 29.638l3.446 1.955z"/>
4 + </g>
5 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path stroke="#434343" d="M25.292 29.878l-1.775-10.346 7.517-7.327-10.388-1.51L16 1.282l-4.646 9.413-10.388 1.51 7.517 7.327-1.775 10.346L16 24.993l9.292 4.885z"/>
4 + </g>
5 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2 + <g fill="none">
3 + <path stroke="#434343" stroke-linecap="round" stroke-linejoin="round" d="M11.923 19.136L5.424 22l.715-7.065-4.731-5.296 6.94-1.503L11.923 2l3.574 6.136 6.94 1.503-4.731 5.296L18.42 22z"/>
4 + </g>
5 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M0 0h32v32H0z"/>
4 + <path fill="#434343" d="M18.01 4a11.798 11.798 0 0 0 0 1H3v24h24V14.986a8.738 8.738 0 0 0 1 0V29a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h15.01zM15 23a6 6 0 1 1 0-12 6 6 0 0 1 0 12zm0-1a5 5 0 1 0 0-10 5 5 0 0 0 0 10z"/>
5 + <path fill="#434343" d="M25 3h1v9h-1z"/>
6 + <path stroke="#434343" d="M22 6l3.5-3.5L29 6"/>
7 + </g>
8 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2 + <g fill="none">
3 + <circle cx="12" cy="12" r="4.5" stroke="#434343"/>
4 + <path fill="#434343" d="M2 1h20a1 1 0 0 1 1 1v20a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1zm0 1v20h20V2H2z"/>
5 + </g>
6 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M0 0h24v24H0z" opacity=".5"/>
4 + <path fill="#434343" d="M21 6H9a6 6 0 1 0 0 12h12v1H9A7 7 0 0 1 9 5h12v1z"/>
5 + <path stroke="#434343" stroke-linecap="square" d="M19 3l2.5 2.5L19 8"/>
6 + </g>
7 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M0 0h24v24H0z" opacity=".5"/>
4 + <path fill="#434343" d="M2 13v-1a7 7 0 0 1 7-7h13v1h-1v5h1v1a7 7 0 0 1-7 7H2v-1h1v-5H2zm7-7a6 6 0 0 0-6 6v6h12a6 6 0 0 0 6-6V6H9z"/>
5 + <path stroke="#434343" stroke-linecap="square" d="M19 3l2.5 2.5L19 8M5 16l-2.5 2.5L5 21"/>
6 + </g>
7 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path fill="#434343" d="M29 17h-.924c0 6.627-5.373 12-12 12-6.628 0-12-5.373-12-12C4.076 10.398 9.407 5.041 16 5V4C8.82 4 3 9.82 3 17s5.82 13 13 13 13-5.82 13-13z"/>
4 + <path stroke="#434343" stroke-linecap="square" d="M16 1.5l4 3-4 3"/>
5 + <path fill="#434343" fill-rule="nonzero" d="M16 4h4v1h-4z"/>
6 + </g>
7 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path fill="#434343" d="M3 17h.924c0 6.627 5.373 12 12 12 6.628 0 12-5.373 12-12 0-6.602-5.331-11.96-11.924-12V4c7.18 0 13 5.82 13 13s-5.82 13-13 13S3 24.18 3 17z"/>
4 + <path fill="#434343" fill-rule="nonzero" d="M12 4h4v1h-4z"/>
5 + <path stroke="#434343" stroke-linecap="square" d="M16 1.5l-4 3 4 3"/>
6 + </g>
7 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M0 0h24v24H0z"/>
4 + <path fill="#434343" d="M8.349 22.254a10.002 10.002 0 0 1-2.778-1.719l.65-.76a9.002 9.002 0 0 0 2.495 1.548l-.367.931zm2.873.704l.078-.997a9 9 0 1 0-.557-17.852l-.14-.99A10.076 10.076 0 0 1 12.145 3c5.523 0 10 4.477 10 10s-4.477 10-10 10c-.312 0-.62-.014-.924-.042zm-7.556-4.655a9.942 9.942 0 0 1-1.253-2.996l.973-.234a8.948 8.948 0 0 0 1.124 2.693l-.844.537zm-1.502-5.91A9.949 9.949 0 0 1 2.88 9.23l.925.382a8.954 8.954 0 0 0-.644 2.844l-.998-.062zm2.21-5.686c.687-.848 1.51-1.58 2.436-2.166l.523.852a9.048 9.048 0 0 0-2.188 1.95l-.771-.636z"/>
5 + <path stroke="#434343" stroke-linecap="square" d="M13 1l-2.5 2.5L13 6"/>
6 + </g>
7 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <circle cx="16" cy="16" r="14.5" stroke="#434343"/>
4 + </g>
5 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <rect width="27" height="27" x="2.5" y="2.5" stroke="#434343" rx="1"/>
4 + </g>
5 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path stroke="#434343" stroke-linecap="round" stroke-linejoin="round" d="M16 2.5l15.5 27H.5z"/>
4 + </g>
5 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2 + <g fill="none" fill-rule="evenodd">
3 + <path fill="#434343" d="M14.706 8H21a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H9a1 1 0 0 1-1-1v-4h1v4h12V9h-5.706l-.588-1z"/>
4 + <path stroke="#434343" stroke-linecap="round" stroke-linejoin="round" d="M8.5 1.5l7.5 13H1z"/>
5 + </g>
6 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M0 0h32v32H0z"/>
4 + <path fill="#434343" d="M2 5h28v1H2zM8 12h16v1H8zM2 19h28v1H2zM8 26h16v1H8z"/>
5 + </g>
6 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M0 0h32v32H0z"/>
4 + <path fill="#434343" d="M2 5h28v1H2zM2 12h16v1H2zM2 19h28v1H2zM2 26h16v1H2z"/>
5 + </g>
6 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M0 0h32v32H0z"/>
4 + <path fill="#434343" d="M2 5h28v1H2zM14 12h16v1H14zM2 19h28v1H2zM14 26h16v1H14z"/>
5 + </g>
6 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M0 0h32v32H0z"/>
4 + <path fill="#434343" d="M7 2h2v2H7zM7 28h2v2H7z"/>
5 + <path stroke="#434343" stroke-width="2" d="M9 3v12h9a6 6 0 1 0 0-12H9zM9 15v14h10a7 7 0 0 0 0-14H9z"/>
6 + </g>
7 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M0 0h32v32H0z"/>
4 + <path fill="#434343" d="M15 2h5v1h-5zM11 29h5v1h-5zM17 3h1l-4 26h-1z"/>
5 + </g>
6 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M0 0h32v32H0z"/>
4 + <path fill="#434343" d="M8 2v14a8 8 0 1 0 16 0V2h1v14a9 9 0 0 1-18 0V2h1zM3 29h26v1H3z"/>
5 + <path fill="#434343" d="M5 2h5v1H5zM22 2h5v1h-5z"/>
6 + </g>
7 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2 + <g fill="#434343" fill-rule="evenodd">
3 + <path d="M4 3h15a1 1 0 0 1 1 1H3a1 1 0 0 1 1-1zM3 4h1v1H3zM19 4h1v1h-1z"/>
4 + <path d="M11 3h1v18h-1z"/>
5 + <path d="M10 20h3v1h-3z"/>
6 + </g>
7 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M24 0H0v24h24z" opacity=".5"/>
4 + <path fill="#434343" d="M3 6h12a6 6 0 1 1 0 12H3v1h12a7 7 0 0 0 0-14H3v1z"/>
5 + <path stroke="#434343" stroke-linecap="square" d="M5 3L2.5 5.5 5 8"/>
6 + </g>
7 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="257" height="26" viewBox="0 0 257 26">
2 + <g fill="#FDBA3B">
3 + <path d="M26 5a8.001 8.001 0 0 0 0 16 8.001 8.001 0 0 0 0-16M51.893 19.812L43.676 5.396A.78.78 0 0 0 43 5a.78.78 0 0 0-.677.396l-8.218 14.418a.787.787 0 0 0 0 .792c.14.244.396.394.676.394h16.436c.28 0 .539-.15.678-.396a.796.796 0 0 0-.002-.792M15.767 5.231A.79.79 0 0 0 15.21 5H.791A.791.791 0 0 0 0 5.79v6.42a.793.793 0 0 0 .791.79h3.21v7.21c.001.21.082.408.234.56.147.148.347.23.558.23h6.416a.788.788 0 0 0 .792-.79V13h3.006c.413 0 .611-.082.762-.232.15-.149.23-.35.231-.559V5.791a.787.787 0 0 0-.233-.56M85.767 5.231A.79.79 0 0 0 85.21 5H70.791a.791.791 0 0 0-.791.79v6.42a.793.793 0 0 0 .791.79h3.21v7.21c.001.21.082.408.234.56.147.148.347.23.558.23h6.416a.788.788 0 0 0 .792-.79V13h3.006c.413 0 .611-.082.762-.232.15-.149.23-.35.231-.559V5.791a.787.787 0 0 0-.233-.56M65.942 9.948l2.17-3.76a.78.78 0 0 0 0-.792.791.791 0 0 0-.684-.396h-8.54A5.889 5.889 0 0 0 53 10.86a5.887 5.887 0 0 0 3.07 5.17l-2.184 3.782A.792.792 0 0 0 54.571 21h8.54a5.89 5.89 0 0 0 2.831-11.052M105.7 21h2.3V5h-2.3zM91 5h2.4v10.286c0 1.893 1.612 3.429 3.6 3.429s3.6-1.536 3.6-3.429V5h2.4v10.286c0 3.156-2.686 5.714-6 5.714-3.313 0-6-2.558-6-5.714V5zM252.148 21.128h-2.377V9.659h2.27v1.64c.69-1.299 1.792-1.938 3.304-1.938.497 0 .95.065 1.382.192l-.215 2.277a3.734 3.734 0 0 0-1.275-.213c-1.814 0-3.089 1.234-3.089 3.638v5.873zm-7.095-5.744a3.734 3.734 0 0 0-1.101-2.703c-.714-.766-1.6-1.149-2.658-1.149-1.058 0-1.944.383-2.679 1.149a3.803 3.803 0 0 0-1.08 2.703c0 1.063.368 1.978 1.08 2.722.735.746 1.62 1.128 2.68 1.128 1.058 0 1.943-.382 2.657-1.128.734-.744 1.101-1.659 1.101-2.722zm-9.916 0c0-1.682.583-3.086 1.729-4.256 1.166-1.17 2.635-1.767 4.428-1.767 1.793 0 3.262.597 4.407 1.767 1.167 1.17 1.75 2.574 1.75 4.256 0 1.7-.583 3.127-1.75 4.297-1.145 1.17-2.614 1.745-4.407 1.745-1.793 0-3.262-.575-4.428-1.745-1.146-1.17-1.729-2.596-1.729-4.297zm-1.5 3.233l.821 1.83c-.864.638-1.944.958-3.22.958-2.526 0-3.822-1.554-3.822-4.383V11.66h-2.01v-2h2.031V5.595h2.355v4.063h4.018v2h-4.018v5.405c0 1.469.605 2.191 1.793 2.191.626 0 1.318-.212 2.052-.638zm-12.43 2.51h2.375V9.66h-2.376v11.469zm1.23-12.977c-.929 0-1.642-.682-1.642-1.596 0-.873.713-1.554 1.643-1.554.885 0 1.576.681 1.576 1.554 0 .914-.69 1.596-1.576 1.596zm-6.49 7.234c0-1.086-.346-1.98-1.037-2.724-.692-.745-1.599-1.128-2.7-1.128-1.102 0-2.01.383-2.7 1.128-.692.744-1.037 1.638-1.037 2.724 0 1.084.345 2.02 1.036 2.766.691.744 1.6 1.105 2.7 1.105 1.102 0 2.01-.361 2.7-1.105.692-.746 1.038-1.682 1.038-2.766zm-.173-4.129V5h2.397v16.128h-2.354v-1.596c-1.015 1.255-2.333 1.873-3.91 1.873-1.663 0-3.068-.575-4.169-1.724-1.102-1.17-1.663-2.596-1.663-4.297 0-1.682.561-3.107 1.663-4.256 1.101-1.17 2.485-1.745 4.148-1.745 1.534 0 2.83.617 3.888 1.872zm-11.48 9.873h-10.218V5.405h10.195v2.318h-7.711V12h7.15v2.32h-7.15v4.489h7.733v2.319zm-23.891-9.724c-1.793 0-3.132 1.192-3.478 2.979h6.783c-.194-1.808-1.555-2.979-3.305-2.979zm5.703 3.766c0 .32-.021.703-.086 1.128h-9.095c.346 1.787 1.62 3 3.867 3 1.318 0 2.916-.49 3.953-1.234l.994 1.724c-1.189.872-3.067 1.595-5.033 1.595-4.364 0-6.243-3-6.243-6.021 0-1.724.54-3.15 1.642-4.277 1.101-1.127 2.548-1.702 4.298-1.702 1.664 0 3.046.511 4.105 1.553 1.058 1.043 1.598 2.447 1.598 4.234zm-19.949 3.894c1.08 0 1.966-.362 2.68-1.085.712-.724 1.058-1.617 1.058-2.703 0-1.084-.346-2-1.059-2.701-.713-.702-1.599-1.064-2.679-1.064-1.058 0-1.944.362-2.656 1.085-.714.702-1.059 1.596-1.059 2.68 0 1.086.345 2 1.059 2.724.712.702 1.598 1.064 2.656 1.064zm3.673-7.936V9.66h2.29v10.299c0 1.85-.584 3.32-1.728 4.404-1.146 1.085-2.68 1.638-4.58 1.638-1.945 0-3.672-.553-5.206-1.638l1.037-1.808c1.296.915 2.679 1.36 4.126 1.36 2.484 0 3.996-1.51 3.996-3.637v-.83c-1.015 1.127-2.311 1.702-3.91 1.702-1.684 0-3.089-.554-4.19-1.68-1.102-1.128-1.642-2.532-1.642-4.214 0-1.68.561-3.085 1.706-4.191 1.145-1.128 2.571-1.681 4.234-1.681 1.534 0 2.83.575 3.867 1.745zm-18.07 8.127c1.102 0 1.988-.382 2.7-1.128.714-.744 1.06-1.659 1.06-2.743 0-1.065-.346-1.98-1.06-2.724-.712-.745-1.598-1.128-2.7-1.128-1.101 0-2.008.383-2.7 1.128-.691.744-1.036 1.66-1.036 2.745 0 1.084.345 2 1.037 2.745.691.744 1.598 1.105 2.7 1.105zm3.652-8V9.66h2.29v11.469h-2.29v-1.575c-1.059 1.234-2.399 1.852-3.976 1.852-1.663 0-3.067-.575-4.168-1.745-1.102-1.17-1.642-2.617-1.642-4.34 0-1.724.54-3.128 1.642-4.256 1.1-1.128 2.505-1.681 4.168-1.681 1.577 0 2.917.617 3.976 1.872zM138.79 9.34c1.404 0 2.527.448 3.37 1.34.863.873 1.295 2.086 1.295 3.596v6.852h-2.376V14.66c0-2.021-1.036-3.128-2.657-3.128-1.727 0-2.915 1.255-2.915 3.192v6.404h-2.377v-6.426c0-1.978-1.037-3.17-2.679-3.17-1.728 0-2.937 1.277-2.937 3.234v6.362h-2.377V9.659h2.333v1.66c.692-1.212 1.988-1.979 3.522-1.979 1.533.021 2.958.767 3.586 2.107.798-1.277 2.419-2.107 4.212-2.107zm-19.517 11.788h2.484V5.405h-2.484v15.723z"/>
4 + </g>
5 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M0 0h24v24H0z"/>
4 + <path stroke="#555555" d="M4 12.011l5 5L20.011 6"/>
5 + </g>
6 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M0 0h24v24H0z"/>
4 + <path stroke="#555555" d="M6 6l12 12M18 6L6 18"/>
5 + </g>
6 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M0 0h24v24H0z"/>
4 + <path fill="#555555" d="M4 0h1v20a1 1 0 0 1-1-1V0zM20 17h-1V5h1v12zm0 2v5h-1v-5h1z"/>
5 + <path fill="#555555" d="M5 19h19v1H5zM4.762 4v1H0V4h4.762zM7 4h12a1 1 0 0 1 1 1H7V4z"/>
6 + </g>
7 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2 + <g fill="#555555" fill-rule="evenodd">
3 + <path d="M5 23H3a1 1 0 0 1-1-1V6h1v16h2v1zm16-10h-1V6h1v7zM9 13H8v-3h1v3zm3 0h-1v-3h1v3zm3 0h-1v-3h1v3zM14.794 3.794L13 2h-3L8.206 3.794A.963.963 0 0 1 8 2.5l.703-1.055A1 1 0 0 1 9.535 1h3.93a1 1 0 0 1 .832.445L15 2.5a.965.965 0 0 1-.206 1.294zM14.197 4H8.803h5.394z"/>
4 + <path d="M0 3h23v1H0zM11.286 21H8.714L8 23H7l1-2.8V20h.071L9.5 16h1l1.429 4H12v.2l1 2.8h-1l-.714-2zm-.357-1L10 17.4 9.071 20h1.858zM20 22h3v1h-4v-7h1v6zm-5 0h3v1h-4v-7h1v6z"/>
5 + </g>
6 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2 + <g fill="#555555" fill-rule="evenodd">
3 + <path d="M3 6v16h17V6h1v16a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V6h1zM14.794 3.794L13 2h-3L8.206 3.794A.963.963 0 0 1 8 2.5l.703-1.055A1 1 0 0 1 9.535 1h3.93a1 1 0 0 1 .832.445L15 2.5a.965.965 0 0 1-.206 1.294zM14.197 4H8.803h5.394z"/>
4 + <path d="M0 3h23v1H0zM8 10h1v6H8v-6zm3 0h1v6h-1v-6zm3 0h1v6h-1v-6z"/>
5 + </g>
6 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path stroke="#555555" d="M2.5 20.929C2.594 10.976 4.323 6 7.686 6c5.872 0 2.524 19 7.697 19s1.89-14.929 6.414-14.929 1.357 10.858 5.13 10.858c1.802 0 2.657-2.262 2.566-6.786"/>
4 + </g>
5 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path stroke="#555555" d="M2 15.5h28"/>
4 + </g>
5 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2 + <g fill="none">
3 + <path stroke="#555555" d="M2.5 21.5H5c.245 0 .48-.058.691-.168l.124-.065.14.01c.429.028.85-.127 1.16-.437L22.55 5.405a.5.5 0 0 0 0-.707l-3.246-3.245a.5.5 0 0 0-.707 0L3.162 16.888a1.495 1.495 0 0 0-.437 1.155l.01.14-.065.123c-.111.212-.17.448-.17.694v2.5z"/>
4 + <path fill="#555555" d="M16.414 3.707l3.89 3.89-.708.706-3.889-3.889z"/>
5 + </g>
6 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M0 0h24v24H0z"/>
4 + <path fill="#555555" d="M12 7v1H2V7h10zm6 0h4v1h-4V7zM12 16v1h10v-1H12zm-6 0H2v1h4v-1z"/>
5 + <path fill="#555555" d="M8.5 20a3.5 3.5 0 1 1 0-7 3.5 3.5 0 0 1 0 7zm0-1a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5zM15.5 11a3.5 3.5 0 1 1 0-7 3.5 3.5 0 0 1 0 7zm0-1a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5z"/>
6 + </g>
7 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="31" height="32" viewBox="0 0 31 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M31 0H0v32h31z"/>
4 + <path fill="#555555" d="M28 16a8 8 0 0 1-8 8H3v-1h1v-7H3a8 8 0 0 1 8-8h17v1h-1v7h1zM11 9a7 7 0 0 0-7 7v7h16a7 7 0 0 0 7-7V9H11z"/>
5 + <path stroke="#555555" stroke-linecap="square" d="M24 5l3.5 3.5L24 12M7 20l-3.5 3.5L7 27"/>
6 + </g>
7 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M32 32H0V0h32z"/>
4 + <path fill="#555555" d="M17 32h-1V0h1zM27.167 11l.5 3h-1.03l-.546-3h1.076zm-.5-3h-1.122L25 5h-5V4h5.153a1 1 0 0 1 .986.836L26.667 8zm1.5 9l.5 3h-.94l-.545-3h.985zm1 6l.639 3.836A1 1 0 0 1 28.819 28H26v-1h3l-.726-4h.894zM23 28h-3v-1h3v1zM13 4v1H7L3 27h10v1H3.18a1 1 0 0 1-.986-1.164l3.666-22A1 1 0 0 1 6.847 4H13z"/>
5 + </g>
6 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M0 0v32h32V0z"/>
4 + <path fill="#555555" d="M0 16v1h32v-1zM11 27.167l3 .5v-1.03l-3-.546v1.076zm-3-.5v-1.122L5 25v-5H4v5.153a1 1 0 0 0 .836.986L8 26.667zm9 1.5l3 .5v-.94l-3-.545v.985zm6 1l3.836.639A1 1 0 0 0 28 28.82V26h-1v3l-4-.727v.894zM28 23v-3h-1v3h1zM4 13h1V7l22-4v10h1V3.18a1 1 0 0 0-1.164-.986l-22 3.667A1 1 0 0 0 4 6.847V13z"/>
5 + </g>
6 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M0 0h24v24H0z"/>
4 + <path fill="#555555" d="M11 0h1v24h-1zM19 21v-1h2v-2h1v2a1 1 0 0 1-1 1h-2zm-2 0h-3v-1h3v1zm5-5h-1v-3h1v3zm0-5h-1V8h1v3zm0-5h-1V4h-2V3h2a1 1 0 0 1 1 1v2zm-5-3v1h-3V3h3zM9 3v1H2v16h7v1H2a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h7z"/>
5 + </g>
6 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path stroke="#555555" stroke-linecap="round" stroke-linejoin="round" d="M21.793 18.5H2.5v-5h18.935l-7.6-8h5.872l10.5 10.5-10.5 10.5h-5.914l8-8z"/>
4 + </g>
5 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path stroke="#555555" stroke-linecap="round" stroke-linejoin="round" d="M25.288 16.42L14.208 27.5H6.792l11.291-11.291L6.826 4.5h7.381l11.661 11.661-.58.258z"/>
4 + </g>
5 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path stroke="#555555" d="M2.5 11.5v9h18v5.293L30.293 16 20.5 6.207V11.5h-18z"/>
4 + </g>
5 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path stroke="#555555" stroke-linecap="round" stroke-linejoin="round" d="M22.207 24.5L16.5 30.207V24.5H8A6.5 6.5 0 0 1 1.5 18V9A6.5 6.5 0 0 1 8 2.5h16A6.5 6.5 0 0 1 30.5 9v9a6.5 6.5 0 0 1-6.5 6.5h-1.793z"/>
4 + </g>
5 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path fill-rule="nonzero" stroke="#555555" d="M15.996 30.675l1.981-1.79c7.898-7.177 10.365-9.718 12.135-13.012.922-1.716 1.377-3.37 1.377-5.076 0-4.65-3.647-8.297-8.297-8.297-2.33 0-4.86 1.527-6.817 3.824l-.38.447-.381-.447C13.658 4.027 11.126 2.5 8.797 2.5 4.147 2.5.5 6.147.5 10.797c0 1.714.46 3.375 1.389 5.098 1.775 3.288 4.26 5.843 12.123 12.974l1.984 1.806z"/>
4 + </g>
5 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path stroke="#555555" stroke-linecap="round" stroke-linejoin="round" d="M17.314 18.867l1.951-2.53 4 5.184h-17l6.5-8.84 4.549 6.186z"/>
4 + <path fill="#555555" d="M18.01 4a11.798 11.798 0 0 0 0 1H3v24h24V14.986a8.738 8.738 0 0 0 1 0V29a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h15.01z"/>
5 + <path fill="#555555" d="M25 3h1v9h-1z"/>
6 + <path stroke="#555555" d="M22 6l3.5-3.5L29 6"/>
7 + </g>
8 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <g stroke="#555555">
4 + <path d="M16 31.28C23.675 23.302 27.5 17.181 27.5 13c0-6.351-5.149-11.5-11.5-11.5S4.5 6.649 4.5 13c0 4.181 3.825 10.302 11.5 18.28z"/>
5 + <circle cx="16" cy="13" r="4.5"/>
6 + </g>
7 + </g>
8 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path stroke="#555555" d="M.576 16L8.29 29.5h15.42L31.424 16 23.71 2.5H8.29L.576 16z"/>
4 + </g>
5 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path stroke="#555555" d="M19.446 31.592l2.265-3.272 3.946.25.636-3.94 3.665-1.505-1.12-3.832 2.655-2.962-2.656-2.962 1.12-3.832-3.664-1.505-.636-3.941-3.946.25-2.265-3.271L16 3.024 12.554 1.07 10.289 4.34l-3.946-.25-.636 3.941-3.665 1.505 1.12 3.832L.508 16.33l2.656 2.962-1.12 3.832 3.664 1.504.636 3.942 3.946-.25 2.265 3.27L16 29.638l3.446 1.955z"/>
4 + </g>
5 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path stroke="#555555" d="M25.292 29.878l-1.775-10.346 7.517-7.327-10.388-1.51L16 1.282l-4.646 9.413-10.388 1.51 7.517 7.327-1.775 10.346L16 24.993l9.292 4.885z"/>
4 + </g>
5 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2 + <g fill="none">
3 + <path stroke="#555555" stroke-linecap="round" stroke-linejoin="round" d="M11.923 19.136L5.424 22l.715-7.065-4.731-5.296 6.94-1.503L11.923 2l3.574 6.136 6.94 1.503-4.731 5.296L18.42 22z"/>
4 + </g>
5 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M0 0h32v32H0z"/>
4 + <path fill="#555555" d="M18.01 4a11.798 11.798 0 0 0 0 1H3v24h24V14.986a8.738 8.738 0 0 0 1 0V29a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h15.01zM15 23a6 6 0 1 1 0-12 6 6 0 0 1 0 12zm0-1a5 5 0 1 0 0-10 5 5 0 0 0 0 10z"/>
5 + <path fill="#555555" d="M25 3h1v9h-1z"/>
6 + <path stroke="#555555" d="M22 6l3.5-3.5L29 6"/>
7 + </g>
8 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2 + <g fill="none">
3 + <circle cx="12" cy="12" r="4.5" stroke="#555555"/>
4 + <path fill="#555555" d="M2 1h20a1 1 0 0 1 1 1v20a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1zm0 1v20h20V2H2z"/>
5 + </g>
6 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M0 0h24v24H0z" opacity=".5"/>
4 + <path fill="#555555" d="M21 6H9a6 6 0 1 0 0 12h12v1H9A7 7 0 0 1 9 5h12v1z"/>
5 + <path stroke="#555555" stroke-linecap="square" d="M19 3l2.5 2.5L19 8"/>
6 + </g>
7 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M0 0h24v24H0z" opacity=".5"/>
4 + <path fill="#555555" d="M2 13v-1a7 7 0 0 1 7-7h13v1h-1v5h1v1a7 7 0 0 1-7 7H2v-1h1v-5H2zm7-7a6 6 0 0 0-6 6v6h12a6 6 0 0 0 6-6V6H9z"/>
5 + <path stroke="#555555" stroke-linecap="square" d="M19 3l2.5 2.5L19 8M5 16l-2.5 2.5L5 21"/>
6 + </g>
7 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path fill="#555555" d="M29 17h-.924c0 6.627-5.373 12-12 12-6.628 0-12-5.373-12-12C4.076 10.398 9.407 5.041 16 5V4C8.82 4 3 9.82 3 17s5.82 13 13 13 13-5.82 13-13z"/>
4 + <path stroke="#555555" stroke-linecap="square" d="M16 1.5l4 3-4 3"/>
5 + <path fill="#555555" fill-rule="nonzero" d="M16 4h4v1h-4z"/>
6 + </g>
7 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path fill="#555555" d="M3 17h.924c0 6.627 5.373 12 12 12 6.628 0 12-5.373 12-12 0-6.602-5.331-11.96-11.924-12V4c7.18 0 13 5.82 13 13s-5.82 13-13 13S3 24.18 3 17z"/>
4 + <path fill="#555555" fill-rule="nonzero" d="M12 4h4v1h-4z"/>
5 + <path stroke="#555555" stroke-linecap="square" d="M16 1.5l-4 3 4 3"/>
6 + </g>
7 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M0 0h24v24H0z"/>
4 + <path fill="#555555" d="M8.349 22.254a10.002 10.002 0 0 1-2.778-1.719l.65-.76a9.002 9.002 0 0 0 2.495 1.548l-.367.931zm2.873.704l.078-.997a9 9 0 1 0-.557-17.852l-.14-.99A10.076 10.076 0 0 1 12.145 3c5.523 0 10 4.477 10 10s-4.477 10-10 10c-.312 0-.62-.014-.924-.042zm-7.556-4.655a9.942 9.942 0 0 1-1.253-2.996l.973-.234a8.948 8.948 0 0 0 1.124 2.693l-.844.537zm-1.502-5.91A9.949 9.949 0 0 1 2.88 9.23l.925.382a8.954 8.954 0 0 0-.644 2.844l-.998-.062zm2.21-5.686c.687-.848 1.51-1.58 2.436-2.166l.523.852a9.048 9.048 0 0 0-2.188 1.95l-.771-.636z"/>
5 + <path stroke="#555555" stroke-linecap="square" d="M13 1l-2.5 2.5L13 6"/>
6 + </g>
7 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <circle cx="16" cy="16" r="14.5" stroke="#555555"/>
4 + </g>
5 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <rect width="27" height="27" x="2.5" y="2.5" stroke="#555555" rx="1"/>
4 + </g>
5 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path stroke="#555555" stroke-linecap="round" stroke-linejoin="round" d="M16 2.5l15.5 27H.5z"/>
4 + </g>
5 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2 + <g fill="none" fill-rule="evenodd">
3 + <path fill="#555555" d="M14.706 8H21a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H9a1 1 0 0 1-1-1v-4h1v4h12V9h-5.706l-.588-1z"/>
4 + <path stroke="#555555" stroke-linecap="round" stroke-linejoin="round" d="M8.5 1.5l7.5 13H1z"/>
5 + </g>
6 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M0 0h32v32H0z"/>
4 + <path fill="#555555" d="M2 5h28v1H2zM8 12h16v1H8zM2 19h28v1H2zM8 26h16v1H8z"/>
5 + </g>
6 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M0 0h32v32H0z"/>
4 + <path fill="#555555" d="M2 5h28v1H2zM2 12h16v1H2zM2 19h28v1H2zM2 26h16v1H2z"/>
5 + </g>
6 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M0 0h32v32H0z"/>
4 + <path fill="#555555" d="M2 5h28v1H2zM14 12h16v1H14zM2 19h28v1H2zM14 26h16v1H14z"/>
5 + </g>
6 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M0 0h32v32H0z"/>
4 + <path fill="#555555" d="M7 2h2v2H7zM7 28h2v2H7z"/>
5 + <path stroke="#555555" stroke-width="2" d="M9 3v12h9a6 6 0 1 0 0-12H9zM9 15v14h10a7 7 0 0 0 0-14H9z"/>
6 + </g>
7 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M0 0h32v32H0z"/>
4 + <path fill="#555555" d="M15 2h5v1h-5zM11 29h5v1h-5zM17 3h1l-4 26h-1z"/>
5 + </g>
6 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M0 0h32v32H0z"/>
4 + <path fill="#555555" d="M8 2v14a8 8 0 1 0 16 0V2h1v14a9 9 0 0 1-18 0V2h1zM3 29h26v1H3z"/>
5 + <path fill="#555555" d="M5 2h5v1H5zM22 2h5v1h-5z"/>
6 + </g>
7 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2 + <g fill="#555555" fill-rule="evenodd">
3 + <path d="M4 3h15a1 1 0 0 1 1 1H3a1 1 0 0 1 1-1zM3 4h1v1H3zM19 4h1v1h-1z"/>
4 + <path d="M11 3h1v18h-1z"/>
5 + <path d="M10 20h3v1h-3z"/>
6 + </g>
7 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M24 0H0v24h24z" opacity=".5"/>
4 + <path fill="#555555" d="M3 6h12a6 6 0 1 1 0 12H3v1h12a7 7 0 0 0 0-14H3v1z"/>
5 + <path stroke="#555555" stroke-linecap="square" d="M5 3L2.5 5.5 5 8"/>
6 + </g>
7 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="257" height="26" viewBox="0 0 257 26">
2 + <g fill="#FDBA3B">
3 + <path d="M26 5a8.001 8.001 0 0 0 0 16 8.001 8.001 0 0 0 0-16M51.893 19.812L43.676 5.396A.78.78 0 0 0 43 5a.78.78 0 0 0-.677.396l-8.218 14.418a.787.787 0 0 0 0 .792c.14.244.396.394.676.394h16.436c.28 0 .539-.15.678-.396a.796.796 0 0 0-.002-.792M15.767 5.231A.79.79 0 0 0 15.21 5H.791A.791.791 0 0 0 0 5.79v6.42a.793.793 0 0 0 .791.79h3.21v7.21c.001.21.082.408.234.56.147.148.347.23.558.23h6.416a.788.788 0 0 0 .792-.79V13h3.006c.413 0 .611-.082.762-.232.15-.149.23-.35.231-.559V5.791a.787.787 0 0 0-.233-.56M85.767 5.231A.79.79 0 0 0 85.21 5H70.791a.791.791 0 0 0-.791.79v6.42a.793.793 0 0 0 .791.79h3.21v7.21c.001.21.082.408.234.56.147.148.347.23.558.23h6.416a.788.788 0 0 0 .792-.79V13h3.006c.413 0 .611-.082.762-.232.15-.149.23-.35.231-.559V5.791a.787.787 0 0 0-.233-.56M65.942 9.948l2.17-3.76a.78.78 0 0 0 0-.792.791.791 0 0 0-.684-.396h-8.54A5.889 5.889 0 0 0 53 10.86a5.887 5.887 0 0 0 3.07 5.17l-2.184 3.782A.792.792 0 0 0 54.571 21h8.54a5.89 5.89 0 0 0 2.831-11.052M105.7 21h2.3V5h-2.3zM91 5h2.4v10.286c0 1.893 1.612 3.429 3.6 3.429s3.6-1.536 3.6-3.429V5h2.4v10.286c0 3.156-2.686 5.714-6 5.714-3.313 0-6-2.558-6-5.714V5zM252.148 21.128h-2.377V9.659h2.27v1.64c.69-1.299 1.792-1.938 3.304-1.938.497 0 .95.065 1.382.192l-.215 2.277a3.734 3.734 0 0 0-1.275-.213c-1.814 0-3.089 1.234-3.089 3.638v5.873zm-7.095-5.744a3.734 3.734 0 0 0-1.101-2.703c-.714-.766-1.6-1.149-2.658-1.149-1.058 0-1.944.383-2.679 1.149a3.803 3.803 0 0 0-1.08 2.703c0 1.063.368 1.978 1.08 2.722.735.746 1.62 1.128 2.68 1.128 1.058 0 1.943-.382 2.657-1.128.734-.744 1.101-1.659 1.101-2.722zm-9.916 0c0-1.682.583-3.086 1.729-4.256 1.166-1.17 2.635-1.767 4.428-1.767 1.793 0 3.262.597 4.407 1.767 1.167 1.17 1.75 2.574 1.75 4.256 0 1.7-.583 3.127-1.75 4.297-1.145 1.17-2.614 1.745-4.407 1.745-1.793 0-3.262-.575-4.428-1.745-1.146-1.17-1.729-2.596-1.729-4.297zm-1.5 3.233l.821 1.83c-.864.638-1.944.958-3.22.958-2.526 0-3.822-1.554-3.822-4.383V11.66h-2.01v-2h2.031V5.595h2.355v4.063h4.018v2h-4.018v5.405c0 1.469.605 2.191 1.793 2.191.626 0 1.318-.212 2.052-.638zm-12.43 2.51h2.375V9.66h-2.376v11.469zm1.23-12.977c-.929 0-1.642-.682-1.642-1.596 0-.873.713-1.554 1.643-1.554.885 0 1.576.681 1.576 1.554 0 .914-.69 1.596-1.576 1.596zm-6.49 7.234c0-1.086-.346-1.98-1.037-2.724-.692-.745-1.599-1.128-2.7-1.128-1.102 0-2.01.383-2.7 1.128-.692.744-1.037 1.638-1.037 2.724 0 1.084.345 2.02 1.036 2.766.691.744 1.6 1.105 2.7 1.105 1.102 0 2.01-.361 2.7-1.105.692-.746 1.038-1.682 1.038-2.766zm-.173-4.129V5h2.397v16.128h-2.354v-1.596c-1.015 1.255-2.333 1.873-3.91 1.873-1.663 0-3.068-.575-4.169-1.724-1.102-1.17-1.663-2.596-1.663-4.297 0-1.682.561-3.107 1.663-4.256 1.101-1.17 2.485-1.745 4.148-1.745 1.534 0 2.83.617 3.888 1.872zm-11.48 9.873h-10.218V5.405h10.195v2.318h-7.711V12h7.15v2.32h-7.15v4.489h7.733v2.319zm-23.891-9.724c-1.793 0-3.132 1.192-3.478 2.979h6.783c-.194-1.808-1.555-2.979-3.305-2.979zm5.703 3.766c0 .32-.021.703-.086 1.128h-9.095c.346 1.787 1.62 3 3.867 3 1.318 0 2.916-.49 3.953-1.234l.994 1.724c-1.189.872-3.067 1.595-5.033 1.595-4.364 0-6.243-3-6.243-6.021 0-1.724.54-3.15 1.642-4.277 1.101-1.127 2.548-1.702 4.298-1.702 1.664 0 3.046.511 4.105 1.553 1.058 1.043 1.598 2.447 1.598 4.234zm-19.949 3.894c1.08 0 1.966-.362 2.68-1.085.712-.724 1.058-1.617 1.058-2.703 0-1.084-.346-2-1.059-2.701-.713-.702-1.599-1.064-2.679-1.064-1.058 0-1.944.362-2.656 1.085-.714.702-1.059 1.596-1.059 2.68 0 1.086.345 2 1.059 2.724.712.702 1.598 1.064 2.656 1.064zm3.673-7.936V9.66h2.29v10.299c0 1.85-.584 3.32-1.728 4.404-1.146 1.085-2.68 1.638-4.58 1.638-1.945 0-3.672-.553-5.206-1.638l1.037-1.808c1.296.915 2.679 1.36 4.126 1.36 2.484 0 3.996-1.51 3.996-3.637v-.83c-1.015 1.127-2.311 1.702-3.91 1.702-1.684 0-3.089-.554-4.19-1.68-1.102-1.128-1.642-2.532-1.642-4.214 0-1.68.561-3.085 1.706-4.191 1.145-1.128 2.571-1.681 4.234-1.681 1.534 0 2.83.575 3.867 1.745zm-18.07 8.127c1.102 0 1.988-.382 2.7-1.128.714-.744 1.06-1.659 1.06-2.743 0-1.065-.346-1.98-1.06-2.724-.712-.745-1.598-1.128-2.7-1.128-1.101 0-2.008.383-2.7 1.128-.691.744-1.036 1.66-1.036 2.745 0 1.084.345 2 1.037 2.745.691.744 1.598 1.105 2.7 1.105zm3.652-8V9.66h2.29v11.469h-2.29v-1.575c-1.059 1.234-2.399 1.852-3.976 1.852-1.663 0-3.067-.575-4.168-1.745-1.102-1.17-1.642-2.617-1.642-4.34 0-1.724.54-3.128 1.642-4.256 1.1-1.128 2.505-1.681 4.168-1.681 1.577 0 2.917.617 3.976 1.872zM138.79 9.34c1.404 0 2.527.448 3.37 1.34.863.873 1.295 2.086 1.295 3.596v6.852h-2.376V14.66c0-2.021-1.036-3.128-2.657-3.128-1.727 0-2.915 1.255-2.915 3.192v6.404h-2.377v-6.426c0-1.978-1.037-3.17-2.679-3.17-1.728 0-2.937 1.277-2.937 3.234v6.362h-2.377V9.659h2.333v1.66c.692-1.212 1.988-1.979 3.522-1.979 1.533.021 2.958.767 3.586 2.107.798-1.277 2.419-2.107 4.212-2.107zm-19.517 11.788h2.484V5.405h-2.484v15.723z"/>
4 + </g>
5 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M0 0h24v24H0z"/>
4 + <path stroke="#e9e9e9" d="M4 12.011l5 5L20.011 6"/>
5 + </g>
6 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M0 0h24v24H0z"/>
4 + <path stroke="#e9e9e9" d="M6 6l12 12M18 6L6 18"/>
5 + </g>
6 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M0 0h24v24H0z"/>
4 + <path fill="#e9e9e9" d="M4 0h1v20a1 1 0 0 1-1-1V0zM20 17h-1V5h1v12zm0 2v5h-1v-5h1z"/>
5 + <path fill="#e9e9e9" d="M5 19h19v1H5zM4.762 4v1H0V4h4.762zM7 4h12a1 1 0 0 1 1 1H7V4z"/>
6 + </g>
7 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2 + <g fill="#e9e9e9" fill-rule="evenodd">
3 + <path d="M5 23H3a1 1 0 0 1-1-1V6h1v16h2v1zm16-10h-1V6h1v7zM9 13H8v-3h1v3zm3 0h-1v-3h1v3zm3 0h-1v-3h1v3zM14.794 3.794L13 2h-3L8.206 3.794A.963.963 0 0 1 8 2.5l.703-1.055A1 1 0 0 1 9.535 1h3.93a1 1 0 0 1 .832.445L15 2.5a.965.965 0 0 1-.206 1.294zM14.197 4H8.803h5.394z"/>
4 + <path d="M0 3h23v1H0zM11.286 21H8.714L8 23H7l1-2.8V20h.071L9.5 16h1l1.429 4H12v.2l1 2.8h-1l-.714-2zm-.357-1L10 17.4 9.071 20h1.858zM20 22h3v1h-4v-7h1v6zm-5 0h3v1h-4v-7h1v6z"/>
5 + </g>
6 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2 + <g fill="#e9e9e9" fill-rule="evenodd">
3 + <path d="M3 6v16h17V6h1v16a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V6h1zM14.794 3.794L13 2h-3L8.206 3.794A.963.963 0 0 1 8 2.5l.703-1.055A1 1 0 0 1 9.535 1h3.93a1 1 0 0 1 .832.445L15 2.5a.965.965 0 0 1-.206 1.294zM14.197 4H8.803h5.394z"/>
4 + <path d="M0 3h23v1H0zM8 10h1v6H8v-6zm3 0h1v6h-1v-6zm3 0h1v6h-1v-6z"/>
5 + </g>
6 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path stroke="#e9e9e9" d="M2.5 20.929C2.594 10.976 4.323 6 7.686 6c5.872 0 2.524 19 7.697 19s1.89-14.929 6.414-14.929 1.357 10.858 5.13 10.858c1.802 0 2.657-2.262 2.566-6.786"/>
4 + </g>
5 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path stroke="#e9e9e9" d="M2 15.5h28"/>
4 + </g>
5 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2 + <g fill="none">
3 + <path stroke="#e9e9e9" d="M2.5 21.5H5c.245 0 .48-.058.691-.168l.124-.065.14.01c.429.028.85-.127 1.16-.437L22.55 5.405a.5.5 0 0 0 0-.707l-3.246-3.245a.5.5 0 0 0-.707 0L3.162 16.888a1.495 1.495 0 0 0-.437 1.155l.01.14-.065.123c-.111.212-.17.448-.17.694v2.5z"/>
4 + <path fill="#e9e9e9" d="M16.414 3.707l3.89 3.89-.708.706-3.889-3.889z"/>
5 + </g>
6 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M0 0h24v24H0z"/>
4 + <path fill="#e9e9e9" d="M12 7v1H2V7h10zm6 0h4v1h-4V7zM12 16v1h10v-1H12zm-6 0H2v1h4v-1z"/>
5 + <path fill="#e9e9e9" d="M8.5 20a3.5 3.5 0 1 1 0-7 3.5 3.5 0 0 1 0 7zm0-1a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5zM15.5 11a3.5 3.5 0 1 1 0-7 3.5 3.5 0 0 1 0 7zm0-1a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5z"/>
6 + </g>
7 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="31" height="32" viewBox="0 0 31 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M31 0H0v32h31z"/>
4 + <path fill="#e9e9e9" d="M28 16a8 8 0 0 1-8 8H3v-1h1v-7H3a8 8 0 0 1 8-8h17v1h-1v7h1zM11 9a7 7 0 0 0-7 7v7h16a7 7 0 0 0 7-7V9H11z"/>
5 + <path stroke="#e9e9e9" stroke-linecap="square" d="M24 5l3.5 3.5L24 12M7 20l-3.5 3.5L7 27"/>
6 + </g>
7 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M32 32H0V0h32z"/>
4 + <path fill="#e9e9e9" d="M17 32h-1V0h1zM27.167 11l.5 3h-1.03l-.546-3h1.076zm-.5-3h-1.122L25 5h-5V4h5.153a1 1 0 0 1 .986.836L26.667 8zm1.5 9l.5 3h-.94l-.545-3h.985zm1 6l.639 3.836A1 1 0 0 1 28.819 28H26v-1h3l-.726-4h.894zM23 28h-3v-1h3v1zM13 4v1H7L3 27h10v1H3.18a1 1 0 0 1-.986-1.164l3.666-22A1 1 0 0 1 6.847 4H13z"/>
5 + </g>
6 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M0 0v32h32V0z"/>
4 + <path fill="#e9e9e9" d="M0 16v1h32v-1zM11 27.167l3 .5v-1.03l-3-.546v1.076zm-3-.5v-1.122L5 25v-5H4v5.153a1 1 0 0 0 .836.986L8 26.667zm9 1.5l3 .5v-.94l-3-.545v.985zm6 1l3.836.639A1 1 0 0 0 28 28.82V26h-1v3l-4-.727v.894zM28 23v-3h-1v3h1zM4 13h1V7l22-4v10h1V3.18a1 1 0 0 0-1.164-.986l-22 3.667A1 1 0 0 0 4 6.847V13z"/>
5 + </g>
6 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M0 0h24v24H0z"/>
4 + <path fill="#e9e9e9" d="M11 0h1v24h-1zM19 21v-1h2v-2h1v2a1 1 0 0 1-1 1h-2zm-2 0h-3v-1h3v1zm5-5h-1v-3h1v3zm0-5h-1V8h1v3zm0-5h-1V4h-2V3h2a1 1 0 0 1 1 1v2zm-5-3v1h-3V3h3zM9 3v1H2v16h7v1H2a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h7z"/>
5 + </g>
6 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path stroke="#e9e9e9" stroke-linecap="round" stroke-linejoin="round" d="M21.793 18.5H2.5v-5h18.935l-7.6-8h5.872l10.5 10.5-10.5 10.5h-5.914l8-8z"/>
4 + </g>
5 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path stroke="#e9e9e9" stroke-linecap="round" stroke-linejoin="round" d="M25.288 16.42L14.208 27.5H6.792l11.291-11.291L6.826 4.5h7.381l11.661 11.661-.58.258z"/>
4 + </g>
5 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path stroke="#e9e9e9" d="M2.5 11.5v9h18v5.293L30.293 16 20.5 6.207V11.5h-18z"/>
4 + </g>
5 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path stroke="#e9e9e9" stroke-linecap="round" stroke-linejoin="round" d="M22.207 24.5L16.5 30.207V24.5H8A6.5 6.5 0 0 1 1.5 18V9A6.5 6.5 0 0 1 8 2.5h16A6.5 6.5 0 0 1 30.5 9v9a6.5 6.5 0 0 1-6.5 6.5h-1.793z"/>
4 + </g>
5 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path fill-rule="nonzero" stroke="#e9e9e9" d="M15.996 30.675l1.981-1.79c7.898-7.177 10.365-9.718 12.135-13.012.922-1.716 1.377-3.37 1.377-5.076 0-4.65-3.647-8.297-8.297-8.297-2.33 0-4.86 1.527-6.817 3.824l-.38.447-.381-.447C13.658 4.027 11.126 2.5 8.797 2.5 4.147 2.5.5 6.147.5 10.797c0 1.714.46 3.375 1.389 5.098 1.775 3.288 4.26 5.843 12.123 12.974l1.984 1.806z"/>
4 + </g>
5 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path stroke="#e9e9e9" stroke-linecap="round" stroke-linejoin="round" d="M17.314 18.867l1.951-2.53 4 5.184h-17l6.5-8.84 4.549 6.186z"/>
4 + <path fill="#e9e9e9" d="M18.01 4a11.798 11.798 0 0 0 0 1H3v24h24V14.986a8.738 8.738 0 0 0 1 0V29a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h15.01z"/>
5 + <path fill="#e9e9e9" d="M25 3h1v9h-1z"/>
6 + <path stroke="#e9e9e9" d="M22 6l3.5-3.5L29 6"/>
7 + </g>
8 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <g stroke="#e9e9e9">
4 + <path d="M16 31.28C23.675 23.302 27.5 17.181 27.5 13c0-6.351-5.149-11.5-11.5-11.5S4.5 6.649 4.5 13c0 4.181 3.825 10.302 11.5 18.28z"/>
5 + <circle cx="16" cy="13" r="4.5"/>
6 + </g>
7 + </g>
8 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path stroke="#e9e9e9" d="M.576 16L8.29 29.5h15.42L31.424 16 23.71 2.5H8.29L.576 16z"/>
4 + </g>
5 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path stroke="#e9e9e9" d="M19.446 31.592l2.265-3.272 3.946.25.636-3.94 3.665-1.505-1.12-3.832 2.655-2.962-2.656-2.962 1.12-3.832-3.664-1.505-.636-3.941-3.946.25-2.265-3.271L16 3.024 12.554 1.07 10.289 4.34l-3.946-.25-.636 3.941-3.665 1.505 1.12 3.832L.508 16.33l2.656 2.962-1.12 3.832 3.664 1.504.636 3.942 3.946-.25 2.265 3.27L16 29.638l3.446 1.955z"/>
4 + </g>
5 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path stroke="#e9e9e9" d="M25.292 29.878l-1.775-10.346 7.517-7.327-10.388-1.51L16 1.282l-4.646 9.413-10.388 1.51 7.517 7.327-1.775 10.346L16 24.993l9.292 4.885z"/>
4 + </g>
5 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2 + <g fill="none">
3 + <path stroke="#e9e9e9" stroke-linecap="round" stroke-linejoin="round" d="M11.923 19.136L5.424 22l.715-7.065-4.731-5.296 6.94-1.503L11.923 2l3.574 6.136 6.94 1.503-4.731 5.296L18.42 22z"/>
4 + </g>
5 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M0 0h32v32H0z"/>
4 + <path fill="#e9e9e9" d="M18.01 4a11.798 11.798 0 0 0 0 1H3v24h24V14.986a8.738 8.738 0 0 0 1 0V29a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h15.01zM15 23a6 6 0 1 1 0-12 6 6 0 0 1 0 12zm0-1a5 5 0 1 0 0-10 5 5 0 0 0 0 10z"/>
5 + <path fill="#e9e9e9" d="M25 3h1v9h-1z"/>
6 + <path stroke="#e9e9e9" d="M22 6l3.5-3.5L29 6"/>
7 + </g>
8 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2 + <g fill="none">
3 + <circle cx="12" cy="12" r="4.5" stroke="#e9e9e9"/>
4 + <path fill="#e9e9e9" d="M2 1h20a1 1 0 0 1 1 1v20a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1zm0 1v20h20V2H2z"/>
5 + </g>
6 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M0 0h24v24H0z" opacity=".5"/>
4 + <path fill="#e9e9e9" d="M21 6H9a6 6 0 1 0 0 12h12v1H9A7 7 0 0 1 9 5h12v1z"/>
5 + <path stroke="#e9e9e9" stroke-linecap="square" d="M19 3l2.5 2.5L19 8"/>
6 + </g>
7 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M0 0h24v24H0z" opacity=".5"/>
4 + <path fill="#e9e9e9" d="M2 13v-1a7 7 0 0 1 7-7h13v1h-1v5h1v1a7 7 0 0 1-7 7H2v-1h1v-5H2zm7-7a6 6 0 0 0-6 6v6h12a6 6 0 0 0 6-6V6H9z"/>
5 + <path stroke="#e9e9e9" stroke-linecap="square" d="M19 3l2.5 2.5L19 8M5 16l-2.5 2.5L5 21"/>
6 + </g>
7 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path fill="#e9e9e9" d="M29 17h-.924c0 6.627-5.373 12-12 12-6.628 0-12-5.373-12-12C4.076 10.398 9.407 5.041 16 5V4C8.82 4 3 9.82 3 17s5.82 13 13 13 13-5.82 13-13z"/>
4 + <path stroke="#e9e9e9" stroke-linecap="square" d="M16 1.5l4 3-4 3"/>
5 + <path fill="#e9e9e9" fill-rule="nonzero" d="M16 4h4v1h-4z"/>
6 + </g>
7 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path fill="#e9e9e9" d="M3 17h.924c0 6.627 5.373 12 12 12 6.628 0 12-5.373 12-12 0-6.602-5.331-11.96-11.924-12V4c7.18 0 13 5.82 13 13s-5.82 13-13 13S3 24.18 3 17z"/>
4 + <path fill="#e9e9e9" fill-rule="nonzero" d="M12 4h4v1h-4z"/>
5 + <path stroke="#e9e9e9" stroke-linecap="square" d="M16 1.5l-4 3 4 3"/>
6 + </g>
7 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M0 0h24v24H0z"/>
4 + <path fill="#e9e9e9" d="M8.349 22.254a10.002 10.002 0 0 1-2.778-1.719l.65-.76a9.002 9.002 0 0 0 2.495 1.548l-.367.931zm2.873.704l.078-.997a9 9 0 1 0-.557-17.852l-.14-.99A10.076 10.076 0 0 1 12.145 3c5.523 0 10 4.477 10 10s-4.477 10-10 10c-.312 0-.62-.014-.924-.042zm-7.556-4.655a9.942 9.942 0 0 1-1.253-2.996l.973-.234a8.948 8.948 0 0 0 1.124 2.693l-.844.537zm-1.502-5.91A9.949 9.949 0 0 1 2.88 9.23l.925.382a8.954 8.954 0 0 0-.644 2.844l-.998-.062zm2.21-5.686c.687-.848 1.51-1.58 2.436-2.166l.523.852a9.048 9.048 0 0 0-2.188 1.95l-.771-.636z"/>
5 + <path stroke="#e9e9e9" stroke-linecap="square" d="M13 1l-2.5 2.5L13 6"/>
6 + </g>
7 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <circle cx="16" cy="16" r="14.5" stroke="#e9e9e9"/>
4 + </g>
5 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <rect width="27" height="27" x="2.5" y="2.5" stroke="#e9e9e9" rx="1"/>
4 + </g>
5 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path stroke="#e9e9e9" stroke-linecap="round" stroke-linejoin="round" d="M16 2.5l15.5 27H.5z"/>
4 + </g>
5 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2 + <g fill="none" fill-rule="evenodd">
3 + <path fill="#e9e9e9" d="M14.706 8H21a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H9a1 1 0 0 1-1-1v-4h1v4h12V9h-5.706l-.588-1z"/>
4 + <path stroke="#e9e9e9" stroke-linecap="round" stroke-linejoin="round" d="M8.5 1.5l7.5 13H1z"/>
5 + </g>
6 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M0 0h32v32H0z"/>
4 + <path fill="#e9e9e9" d="M2 5h28v1H2zM8 12h16v1H8zM2 19h28v1H2zM8 26h16v1H8z"/>
5 + </g>
6 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M0 0h32v32H0z"/>
4 + <path fill="#e9e9e9" d="M2 5h28v1H2zM2 12h16v1H2zM2 19h28v1H2zM2 26h16v1H2z"/>
5 + </g>
6 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M0 0h32v32H0z"/>
4 + <path fill="#e9e9e9" d="M2 5h28v1H2zM14 12h16v1H14zM2 19h28v1H2zM14 26h16v1H14z"/>
5 + </g>
6 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M0 0h32v32H0z"/>
4 + <path fill="#e9e9e9" d="M7 2h2v2H7zM7 28h2v2H7z"/>
5 + <path stroke="#e9e9e9" stroke-width="2" d="M9 3v12h9a6 6 0 1 0 0-12H9zM9 15v14h10a7 7 0 0 0 0-14H9z"/>
6 + </g>
7 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M0 0h32v32H0z"/>
4 + <path fill="#e9e9e9" d="M15 2h5v1h-5zM11 29h5v1h-5zM17 3h1l-4 26h-1z"/>
5 + </g>
6 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M0 0h32v32H0z"/>
4 + <path fill="#e9e9e9" d="M8 2v14a8 8 0 1 0 16 0V2h1v14a9 9 0 0 1-18 0V2h1zM3 29h26v1H3z"/>
5 + <path fill="#e9e9e9" d="M5 2h5v1H5zM22 2h5v1h-5z"/>
6 + </g>
7 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2 + <g fill="#e9e9e9" fill-rule="evenodd">
3 + <path d="M4 3h15a1 1 0 0 1 1 1H3a1 1 0 0 1 1-1zM3 4h1v1H3zM19 4h1v1h-1z"/>
4 + <path d="M11 3h1v18h-1z"/>
5 + <path d="M10 20h3v1h-3z"/>
6 + </g>
7 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M24 0H0v24h24z" opacity=".5"/>
4 + <path fill="#e9e9e9" d="M3 6h12a6 6 0 1 1 0 12H3v1h12a7 7 0 0 0 0-14H3v1z"/>
5 + <path stroke="#e9e9e9" stroke-linecap="square" d="M5 3L2.5 5.5 5 8"/>
6 + </g>
7 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="257" height="26" viewBox="0 0 257 26">
2 + <g fill="#FDBA3B">
3 + <path d="M26 5a8.001 8.001 0 0 0 0 16 8.001 8.001 0 0 0 0-16M51.893 19.812L43.676 5.396A.78.78 0 0 0 43 5a.78.78 0 0 0-.677.396l-8.218 14.418a.787.787 0 0 0 0 .792c.14.244.396.394.676.394h16.436c.28 0 .539-.15.678-.396a.796.796 0 0 0-.002-.792M15.767 5.231A.79.79 0 0 0 15.21 5H.791A.791.791 0 0 0 0 5.79v6.42a.793.793 0 0 0 .791.79h3.21v7.21c.001.21.082.408.234.56.147.148.347.23.558.23h6.416a.788.788 0 0 0 .792-.79V13h3.006c.413 0 .611-.082.762-.232.15-.149.23-.35.231-.559V5.791a.787.787 0 0 0-.233-.56M85.767 5.231A.79.79 0 0 0 85.21 5H70.791a.791.791 0 0 0-.791.79v6.42a.793.793 0 0 0 .791.79h3.21v7.21c.001.21.082.408.234.56.147.148.347.23.558.23h6.416a.788.788 0 0 0 .792-.79V13h3.006c.413 0 .611-.082.762-.232.15-.149.23-.35.231-.559V5.791a.787.787 0 0 0-.233-.56M65.942 9.948l2.17-3.76a.78.78 0 0 0 0-.792.791.791 0 0 0-.684-.396h-8.54A5.889 5.889 0 0 0 53 10.86a5.887 5.887 0 0 0 3.07 5.17l-2.184 3.782A.792.792 0 0 0 54.571 21h8.54a5.89 5.89 0 0 0 2.831-11.052M105.7 21h2.3V5h-2.3zM91 5h2.4v10.286c0 1.893 1.612 3.429 3.6 3.429s3.6-1.536 3.6-3.429V5h2.4v10.286c0 3.156-2.686 5.714-6 5.714-3.313 0-6-2.558-6-5.714V5zM252.148 21.128h-2.377V9.659h2.27v1.64c.69-1.299 1.792-1.938 3.304-1.938.497 0 .95.065 1.382.192l-.215 2.277a3.734 3.734 0 0 0-1.275-.213c-1.814 0-3.089 1.234-3.089 3.638v5.873zm-7.095-5.744a3.734 3.734 0 0 0-1.101-2.703c-.714-.766-1.6-1.149-2.658-1.149-1.058 0-1.944.383-2.679 1.149a3.803 3.803 0 0 0-1.08 2.703c0 1.063.368 1.978 1.08 2.722.735.746 1.62 1.128 2.68 1.128 1.058 0 1.943-.382 2.657-1.128.734-.744 1.101-1.659 1.101-2.722zm-9.916 0c0-1.682.583-3.086 1.729-4.256 1.166-1.17 2.635-1.767 4.428-1.767 1.793 0 3.262.597 4.407 1.767 1.167 1.17 1.75 2.574 1.75 4.256 0 1.7-.583 3.127-1.75 4.297-1.145 1.17-2.614 1.745-4.407 1.745-1.793 0-3.262-.575-4.428-1.745-1.146-1.17-1.729-2.596-1.729-4.297zm-1.5 3.233l.821 1.83c-.864.638-1.944.958-3.22.958-2.526 0-3.822-1.554-3.822-4.383V11.66h-2.01v-2h2.031V5.595h2.355v4.063h4.018v2h-4.018v5.405c0 1.469.605 2.191 1.793 2.191.626 0 1.318-.212 2.052-.638zm-12.43 2.51h2.375V9.66h-2.376v11.469zm1.23-12.977c-.929 0-1.642-.682-1.642-1.596 0-.873.713-1.554 1.643-1.554.885 0 1.576.681 1.576 1.554 0 .914-.69 1.596-1.576 1.596zm-6.49 7.234c0-1.086-.346-1.98-1.037-2.724-.692-.745-1.599-1.128-2.7-1.128-1.102 0-2.01.383-2.7 1.128-.692.744-1.037 1.638-1.037 2.724 0 1.084.345 2.02 1.036 2.766.691.744 1.6 1.105 2.7 1.105 1.102 0 2.01-.361 2.7-1.105.692-.746 1.038-1.682 1.038-2.766zm-.173-4.129V5h2.397v16.128h-2.354v-1.596c-1.015 1.255-2.333 1.873-3.91 1.873-1.663 0-3.068-.575-4.169-1.724-1.102-1.17-1.663-2.596-1.663-4.297 0-1.682.561-3.107 1.663-4.256 1.101-1.17 2.485-1.745 4.148-1.745 1.534 0 2.83.617 3.888 1.872zm-11.48 9.873h-10.218V5.405h10.195v2.318h-7.711V12h7.15v2.32h-7.15v4.489h7.733v2.319zm-23.891-9.724c-1.793 0-3.132 1.192-3.478 2.979h6.783c-.194-1.808-1.555-2.979-3.305-2.979zm5.703 3.766c0 .32-.021.703-.086 1.128h-9.095c.346 1.787 1.62 3 3.867 3 1.318 0 2.916-.49 3.953-1.234l.994 1.724c-1.189.872-3.067 1.595-5.033 1.595-4.364 0-6.243-3-6.243-6.021 0-1.724.54-3.15 1.642-4.277 1.101-1.127 2.548-1.702 4.298-1.702 1.664 0 3.046.511 4.105 1.553 1.058 1.043 1.598 2.447 1.598 4.234zm-19.949 3.894c1.08 0 1.966-.362 2.68-1.085.712-.724 1.058-1.617 1.058-2.703 0-1.084-.346-2-1.059-2.701-.713-.702-1.599-1.064-2.679-1.064-1.058 0-1.944.362-2.656 1.085-.714.702-1.059 1.596-1.059 2.68 0 1.086.345 2 1.059 2.724.712.702 1.598 1.064 2.656 1.064zm3.673-7.936V9.66h2.29v10.299c0 1.85-.584 3.32-1.728 4.404-1.146 1.085-2.68 1.638-4.58 1.638-1.945 0-3.672-.553-5.206-1.638l1.037-1.808c1.296.915 2.679 1.36 4.126 1.36 2.484 0 3.996-1.51 3.996-3.637v-.83c-1.015 1.127-2.311 1.702-3.91 1.702-1.684 0-3.089-.554-4.19-1.68-1.102-1.128-1.642-2.532-1.642-4.214 0-1.68.561-3.085 1.706-4.191 1.145-1.128 2.571-1.681 4.234-1.681 1.534 0 2.83.575 3.867 1.745zm-18.07 8.127c1.102 0 1.988-.382 2.7-1.128.714-.744 1.06-1.659 1.06-2.743 0-1.065-.346-1.98-1.06-2.724-.712-.745-1.598-1.128-2.7-1.128-1.101 0-2.008.383-2.7 1.128-.691.744-1.036 1.66-1.036 2.745 0 1.084.345 2 1.037 2.745.691.744 1.598 1.105 2.7 1.105zm3.652-8V9.66h2.29v11.469h-2.29v-1.575c-1.059 1.234-2.399 1.852-3.976 1.852-1.663 0-3.067-.575-4.168-1.745-1.102-1.17-1.642-2.617-1.642-4.34 0-1.724.54-3.128 1.642-4.256 1.1-1.128 2.505-1.681 4.168-1.681 1.577 0 2.917.617 3.976 1.872zM138.79 9.34c1.404 0 2.527.448 3.37 1.34.863.873 1.295 2.086 1.295 3.596v6.852h-2.376V14.66c0-2.021-1.036-3.128-2.657-3.128-1.727 0-2.915 1.255-2.915 3.192v6.404h-2.377v-6.426c0-1.978-1.037-3.17-2.679-3.17-1.728 0-2.937 1.277-2.937 3.234v6.362h-2.377V9.659h2.333v1.66c.692-1.212 1.988-1.979 3.522-1.979 1.533.021 2.958.767 3.586 2.107.798-1.277 2.419-2.107 4.212-2.107zm-19.517 11.788h2.484V5.405h-2.484v15.723z"/>
4 + </g>
5 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M0 0h24v24H0z"/>
4 + <path stroke="#8a8a8a" d="M4 12.011l5 5L20.011 6"/>
5 + </g>
6 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M0 0h24v24H0z"/>
4 + <path stroke="#8a8a8a" d="M6 6l12 12M18 6L6 18"/>
5 + </g>
6 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M0 0h24v24H0z"/>
4 + <path fill="#8a8a8a" d="M4 0h1v20a1 1 0 0 1-1-1V0zM20 17h-1V5h1v12zm0 2v5h-1v-5h1z"/>
5 + <path fill="#8a8a8a" d="M5 19h19v1H5zM4.762 4v1H0V4h4.762zM7 4h12a1 1 0 0 1 1 1H7V4z"/>
6 + </g>
7 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2 + <g fill="#8a8a8a" fill-rule="evenodd">
3 + <path d="M5 23H3a1 1 0 0 1-1-1V6h1v16h2v1zm16-10h-1V6h1v7zM9 13H8v-3h1v3zm3 0h-1v-3h1v3zm3 0h-1v-3h1v3zM14.794 3.794L13 2h-3L8.206 3.794A.963.963 0 0 1 8 2.5l.703-1.055A1 1 0 0 1 9.535 1h3.93a1 1 0 0 1 .832.445L15 2.5a.965.965 0 0 1-.206 1.294zM14.197 4H8.803h5.394z"/>
4 + <path d="M0 3h23v1H0zM11.286 21H8.714L8 23H7l1-2.8V20h.071L9.5 16h1l1.429 4H12v.2l1 2.8h-1l-.714-2zm-.357-1L10 17.4 9.071 20h1.858zM20 22h3v1h-4v-7h1v6zm-5 0h3v1h-4v-7h1v6z"/>
5 + </g>
6 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2 + <g fill="#8a8a8a" fill-rule="evenodd">
3 + <path d="M3 6v16h17V6h1v16a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V6h1zM14.794 3.794L13 2h-3L8.206 3.794A.963.963 0 0 1 8 2.5l.703-1.055A1 1 0 0 1 9.535 1h3.93a1 1 0 0 1 .832.445L15 2.5a.965.965 0 0 1-.206 1.294zM14.197 4H8.803h5.394z"/>
4 + <path d="M0 3h23v1H0zM8 10h1v6H8v-6zm3 0h1v6h-1v-6zm3 0h1v6h-1v-6z"/>
5 + </g>
6 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path stroke="#8a8a8a" d="M2.5 20.929C2.594 10.976 4.323 6 7.686 6c5.872 0 2.524 19 7.697 19s1.89-14.929 6.414-14.929 1.357 10.858 5.13 10.858c1.802 0 2.657-2.262 2.566-6.786"/>
4 + </g>
5 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path stroke="#8a8a8a" d="M2 15.5h28"/>
4 + </g>
5 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2 + <g fill="none">
3 + <path stroke="#8a8a8a" d="M2.5 21.5H5c.245 0 .48-.058.691-.168l.124-.065.14.01c.429.028.85-.127 1.16-.437L22.55 5.405a.5.5 0 0 0 0-.707l-3.246-3.245a.5.5 0 0 0-.707 0L3.162 16.888a1.495 1.495 0 0 0-.437 1.155l.01.14-.065.123c-.111.212-.17.448-.17.694v2.5z"/>
4 + <path fill="#8a8a8a" d="M16.414 3.707l3.89 3.89-.708.706-3.889-3.889z"/>
5 + </g>
6 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M0 0h24v24H0z"/>
4 + <path fill="#8a8a8a" d="M12 7v1H2V7h10zm6 0h4v1h-4V7zM12 16v1h10v-1H12zm-6 0H2v1h4v-1z"/>
5 + <path fill="#8a8a8a" d="M8.5 20a3.5 3.5 0 1 1 0-7 3.5 3.5 0 0 1 0 7zm0-1a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5zM15.5 11a3.5 3.5 0 1 1 0-7 3.5 3.5 0 0 1 0 7zm0-1a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5z"/>
6 + </g>
7 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="31" height="32" viewBox="0 0 31 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M31 0H0v32h31z"/>
4 + <path fill="#8a8a8a" d="M28 16a8 8 0 0 1-8 8H3v-1h1v-7H3a8 8 0 0 1 8-8h17v1h-1v7h1zM11 9a7 7 0 0 0-7 7v7h16a7 7 0 0 0 7-7V9H11z"/>
5 + <path stroke="#8a8a8a" stroke-linecap="square" d="M24 5l3.5 3.5L24 12M7 20l-3.5 3.5L7 27"/>
6 + </g>
7 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M32 32H0V0h32z"/>
4 + <path fill="#8a8a8a" d="M17 32h-1V0h1zM27.167 11l.5 3h-1.03l-.546-3h1.076zm-.5-3h-1.122L25 5h-5V4h5.153a1 1 0 0 1 .986.836L26.667 8zm1.5 9l.5 3h-.94l-.545-3h.985zm1 6l.639 3.836A1 1 0 0 1 28.819 28H26v-1h3l-.726-4h.894zM23 28h-3v-1h3v1zM13 4v1H7L3 27h10v1H3.18a1 1 0 0 1-.986-1.164l3.666-22A1 1 0 0 1 6.847 4H13z"/>
5 + </g>
6 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M0 0v32h32V0z"/>
4 + <path fill="#8a8a8a" d="M0 16v1h32v-1zM11 27.167l3 .5v-1.03l-3-.546v1.076zm-3-.5v-1.122L5 25v-5H4v5.153a1 1 0 0 0 .836.986L8 26.667zm9 1.5l3 .5v-.94l-3-.545v.985zm6 1l3.836.639A1 1 0 0 0 28 28.82V26h-1v3l-4-.727v.894zM28 23v-3h-1v3h1zM4 13h1V7l22-4v10h1V3.18a1 1 0 0 0-1.164-.986l-22 3.667A1 1 0 0 0 4 6.847V13z"/>
5 + </g>
6 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M0 0h24v24H0z"/>
4 + <path fill="#8a8a8a" d="M11 0h1v24h-1zM19 21v-1h2v-2h1v2a1 1 0 0 1-1 1h-2zm-2 0h-3v-1h3v1zm5-5h-1v-3h1v3zm0-5h-1V8h1v3zm0-5h-1V4h-2V3h2a1 1 0 0 1 1 1v2zm-5-3v1h-3V3h3zM9 3v1H2v16h7v1H2a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h7z"/>
5 + </g>
6 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path stroke="#8a8a8a" stroke-linecap="round" stroke-linejoin="round" d="M21.793 18.5H2.5v-5h18.935l-7.6-8h5.872l10.5 10.5-10.5 10.5h-5.914l8-8z"/>
4 + </g>
5 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path stroke="#8a8a8a" stroke-linecap="round" stroke-linejoin="round" d="M25.288 16.42L14.208 27.5H6.792l11.291-11.291L6.826 4.5h7.381l11.661 11.661-.58.258z"/>
4 + </g>
5 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path stroke="#8a8a8a" d="M2.5 11.5v9h18v5.293L30.293 16 20.5 6.207V11.5h-18z"/>
4 + </g>
5 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path stroke="#8a8a8a" stroke-linecap="round" stroke-linejoin="round" d="M22.207 24.5L16.5 30.207V24.5H8A6.5 6.5 0 0 1 1.5 18V9A6.5 6.5 0 0 1 8 2.5h16A6.5 6.5 0 0 1 30.5 9v9a6.5 6.5 0 0 1-6.5 6.5h-1.793z"/>
4 + </g>
5 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path fill-rule="nonzero" stroke="#8a8a8a" d="M15.996 30.675l1.981-1.79c7.898-7.177 10.365-9.718 12.135-13.012.922-1.716 1.377-3.37 1.377-5.076 0-4.65-3.647-8.297-8.297-8.297-2.33 0-4.86 1.527-6.817 3.824l-.38.447-.381-.447C13.658 4.027 11.126 2.5 8.797 2.5 4.147 2.5.5 6.147.5 10.797c0 1.714.46 3.375 1.389 5.098 1.775 3.288 4.26 5.843 12.123 12.974l1.984 1.806z"/>
4 + </g>
5 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path stroke="#8a8a8a" stroke-linecap="round" stroke-linejoin="round" d="M17.314 18.867l1.951-2.53 4 5.184h-17l6.5-8.84 4.549 6.186z"/>
4 + <path fill="#8a8a8a" d="M18.01 4a11.798 11.798 0 0 0 0 1H3v24h24V14.986a8.738 8.738 0 0 0 1 0V29a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h15.01z"/>
5 + <path fill="#8a8a8a" d="M25 3h1v9h-1z"/>
6 + <path stroke="#8a8a8a" d="M22 6l3.5-3.5L29 6"/>
7 + </g>
8 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <g stroke="#8a8a8a">
4 + <path d="M16 31.28C23.675 23.302 27.5 17.181 27.5 13c0-6.351-5.149-11.5-11.5-11.5S4.5 6.649 4.5 13c0 4.181 3.825 10.302 11.5 18.28z"/>
5 + <circle cx="16" cy="13" r="4.5"/>
6 + </g>
7 + </g>
8 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path stroke="#8a8a8a" d="M.576 16L8.29 29.5h15.42L31.424 16 23.71 2.5H8.29L.576 16z"/>
4 + </g>
5 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path stroke="#8a8a8a" d="M19.446 31.592l2.265-3.272 3.946.25.636-3.94 3.665-1.505-1.12-3.832 2.655-2.962-2.656-2.962 1.12-3.832-3.664-1.505-.636-3.941-3.946.25-2.265-3.271L16 3.024 12.554 1.07 10.289 4.34l-3.946-.25-.636 3.941-3.665 1.505 1.12 3.832L.508 16.33l2.656 2.962-1.12 3.832 3.664 1.504.636 3.942 3.946-.25 2.265 3.27L16 29.638l3.446 1.955z"/>
4 + </g>
5 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path stroke="#8a8a8a" d="M25.292 29.878l-1.775-10.346 7.517-7.327-10.388-1.51L16 1.282l-4.646 9.413-10.388 1.51 7.517 7.327-1.775 10.346L16 24.993l9.292 4.885z"/>
4 + </g>
5 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2 + <g fill="none">
3 + <path stroke="#8a8a8a" stroke-linecap="round" stroke-linejoin="round" d="M11.923 19.136L5.424 22l.715-7.065-4.731-5.296 6.94-1.503L11.923 2l3.574 6.136 6.94 1.503-4.731 5.296L18.42 22z"/>
4 + </g>
5 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M0 0h32v32H0z"/>
4 + <path fill="#8a8a8a" d="M18.01 4a11.798 11.798 0 0 0 0 1H3v24h24V14.986a8.738 8.738 0 0 0 1 0V29a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h15.01zM15 23a6 6 0 1 1 0-12 6 6 0 0 1 0 12zm0-1a5 5 0 1 0 0-10 5 5 0 0 0 0 10z"/>
5 + <path fill="#8a8a8a" d="M25 3h1v9h-1z"/>
6 + <path stroke="#8a8a8a" d="M22 6l3.5-3.5L29 6"/>
7 + </g>
8 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2 + <g fill="none">
3 + <circle cx="12" cy="12" r="4.5" stroke="#8a8a8a"/>
4 + <path fill="#8a8a8a" d="M2 1h20a1 1 0 0 1 1 1v20a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1zm0 1v20h20V2H2z"/>
5 + </g>
6 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M0 0h24v24H0z" opacity=".5"/>
4 + <path fill="#8a8a8a" d="M21 6H9a6 6 0 1 0 0 12h12v1H9A7 7 0 0 1 9 5h12v1z"/>
5 + <path stroke="#8a8a8a" stroke-linecap="square" d="M19 3l2.5 2.5L19 8"/>
6 + </g>
7 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M0 0h24v24H0z" opacity=".5"/>
4 + <path fill="#8a8a8a" d="M2 13v-1a7 7 0 0 1 7-7h13v1h-1v5h1v1a7 7 0 0 1-7 7H2v-1h1v-5H2zm7-7a6 6 0 0 0-6 6v6h12a6 6 0 0 0 6-6V6H9z"/>
5 + <path stroke="#8a8a8a" stroke-linecap="square" d="M19 3l2.5 2.5L19 8M5 16l-2.5 2.5L5 21"/>
6 + </g>
7 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path fill="#8a8a8a" d="M29 17h-.924c0 6.627-5.373 12-12 12-6.628 0-12-5.373-12-12C4.076 10.398 9.407 5.041 16 5V4C8.82 4 3 9.82 3 17s5.82 13 13 13 13-5.82 13-13z"/>
4 + <path stroke="#8a8a8a" stroke-linecap="square" d="M16 1.5l4 3-4 3"/>
5 + <path fill="#8a8a8a" fill-rule="nonzero" d="M16 4h4v1h-4z"/>
6 + </g>
7 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path fill="#8a8a8a" d="M3 17h.924c0 6.627 5.373 12 12 12 6.628 0 12-5.373 12-12 0-6.602-5.331-11.96-11.924-12V4c7.18 0 13 5.82 13 13s-5.82 13-13 13S3 24.18 3 17z"/>
4 + <path fill="#8a8a8a" fill-rule="nonzero" d="M12 4h4v1h-4z"/>
5 + <path stroke="#8a8a8a" stroke-linecap="square" d="M16 1.5l-4 3 4 3"/>
6 + </g>
7 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M0 0h24v24H0z"/>
4 + <path fill="#8a8a8a" d="M8.349 22.254a10.002 10.002 0 0 1-2.778-1.719l.65-.76a9.002 9.002 0 0 0 2.495 1.548l-.367.931zm2.873.704l.078-.997a9 9 0 1 0-.557-17.852l-.14-.99A10.076 10.076 0 0 1 12.145 3c5.523 0 10 4.477 10 10s-4.477 10-10 10c-.312 0-.62-.014-.924-.042zm-7.556-4.655a9.942 9.942 0 0 1-1.253-2.996l.973-.234a8.948 8.948 0 0 0 1.124 2.693l-.844.537zm-1.502-5.91A9.949 9.949 0 0 1 2.88 9.23l.925.382a8.954 8.954 0 0 0-.644 2.844l-.998-.062zm2.21-5.686c.687-.848 1.51-1.58 2.436-2.166l.523.852a9.048 9.048 0 0 0-2.188 1.95l-.771-.636z"/>
5 + <path stroke="#8a8a8a" stroke-linecap="square" d="M13 1l-2.5 2.5L13 6"/>
6 + </g>
7 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <circle cx="16" cy="16" r="14.5" stroke="#8a8a8a"/>
4 + </g>
5 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <rect width="27" height="27" x="2.5" y="2.5" stroke="#8a8a8a" rx="1"/>
4 + </g>
5 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path stroke="#8a8a8a" stroke-linecap="round" stroke-linejoin="round" d="M16 2.5l15.5 27H.5z"/>
4 + </g>
5 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2 + <g fill="none" fill-rule="evenodd">
3 + <path fill="#8a8a8a" d="M14.706 8H21a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H9a1 1 0 0 1-1-1v-4h1v4h12V9h-5.706l-.588-1z"/>
4 + <path stroke="#8a8a8a" stroke-linecap="round" stroke-linejoin="round" d="M8.5 1.5l7.5 13H1z"/>
5 + </g>
6 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M0 0h32v32H0z"/>
4 + <path fill="#8a8a8a" d="M2 5h28v1H2zM8 12h16v1H8zM2 19h28v1H2zM8 26h16v1H8z"/>
5 + </g>
6 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M0 0h32v32H0z"/>
4 + <path fill="#8a8a8a" d="M2 5h28v1H2zM2 12h16v1H2zM2 19h28v1H2zM2 26h16v1H2z"/>
5 + </g>
6 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M0 0h32v32H0z"/>
4 + <path fill="#8a8a8a" d="M2 5h28v1H2zM14 12h16v1H14zM2 19h28v1H2zM14 26h16v1H14z"/>
5 + </g>
6 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M0 0h32v32H0z"/>
4 + <path fill="#8a8a8a" d="M7 2h2v2H7zM7 28h2v2H7z"/>
5 + <path stroke="#8a8a8a" stroke-width="2" d="M9 3v12h9a6 6 0 1 0 0-12H9zM9 15v14h10a7 7 0 0 0 0-14H9z"/>
6 + </g>
7 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M0 0h32v32H0z"/>
4 + <path fill="#8a8a8a" d="M15 2h5v1h-5zM11 29h5v1h-5zM17 3h1l-4 26h-1z"/>
5 + </g>
6 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M0 0h32v32H0z"/>
4 + <path fill="#8a8a8a" d="M8 2v14a8 8 0 1 0 16 0V2h1v14a9 9 0 0 1-18 0V2h1zM3 29h26v1H3z"/>
5 + <path fill="#8a8a8a" d="M5 2h5v1H5zM22 2h5v1h-5z"/>
6 + </g>
7 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2 + <g fill="#8a8a8a" fill-rule="evenodd">
3 + <path d="M4 3h15a1 1 0 0 1 1 1H3a1 1 0 0 1 1-1zM3 4h1v1H3zM19 4h1v1h-1z"/>
4 + <path d="M11 3h1v18h-1z"/>
5 + <path d="M10 20h3v1h-3z"/>
6 + </g>
7 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M24 0H0v24h24z" opacity=".5"/>
4 + <path fill="#8a8a8a" d="M3 6h12a6 6 0 1 1 0 12H3v1h12a7 7 0 0 0 0-14H3v1z"/>
5 + <path stroke="#8a8a8a" stroke-linecap="square" d="M5 3L2.5 5.5 5 8"/>
6 + </g>
7 +</svg>
1 +<svg xmlns="http://www.w3.org/2000/svg" width="257" height="26" viewBox="0 0 257 26">
2 + <g fill="#FDBA3B">
3 + <path d="M26 5a8.001 8.001 0 0 0 0 16 8.001 8.001 0 0 0 0-16M51.893 19.812L43.676 5.396A.78.78 0 0 0 43 5a.78.78 0 0 0-.677.396l-8.218 14.418a.787.787 0 0 0 0 .792c.14.244.396.394.676.394h16.436c.28 0 .539-.15.678-.396a.796.796 0 0 0-.002-.792M15.767 5.231A.79.79 0 0 0 15.21 5H.791A.791.791 0 0 0 0 5.79v6.42a.793.793 0 0 0 .791.79h3.21v7.21c.001.21.082.408.234.56.147.148.347.23.558.23h6.416a.788.788 0 0 0 .792-.79V13h3.006c.413 0 .611-.082.762-.232.15-.149.23-.35.231-.559V5.791a.787.787 0 0 0-.233-.56M85.767 5.231A.79.79 0 0 0 85.21 5H70.791a.791.791 0 0 0-.791.79v6.42a.793.793 0 0 0 .791.79h3.21v7.21c.001.21.082.408.234.56.147.148.347.23.558.23h6.416a.788.788 0 0 0 .792-.79V13h3.006c.413 0 .611-.082.762-.232.15-.149.23-.35.231-.559V5.791a.787.787 0 0 0-.233-.56M65.942 9.948l2.17-3.76a.78.78 0 0 0 0-.792.791.791 0 0 0-.684-.396h-8.54A5.889 5.889 0 0 0 53 10.86a5.887 5.887 0 0 0 3.07 5.17l-2.184 3.782A.792.792 0 0 0 54.571 21h8.54a5.89 5.89 0 0 0 2.831-11.052M105.7 21h2.3V5h-2.3zM91 5h2.4v10.286c0 1.893 1.612 3.429 3.6 3.429s3.6-1.536 3.6-3.429V5h2.4v10.286c0 3.156-2.686 5.714-6 5.714-3.313 0-6-2.558-6-5.714V5zM252.148 21.128h-2.377V9.659h2.27v1.64c.69-1.299 1.792-1.938 3.304-1.938.497 0 .95.065 1.382.192l-.215 2.277a3.734 3.734 0 0 0-1.275-.213c-1.814 0-3.089 1.234-3.089 3.638v5.873zm-7.095-5.744a3.734 3.734 0 0 0-1.101-2.703c-.714-.766-1.6-1.149-2.658-1.149-1.058 0-1.944.383-2.679 1.149a3.803 3.803 0 0 0-1.08 2.703c0 1.063.368 1.978 1.08 2.722.735.746 1.62 1.128 2.68 1.128 1.058 0 1.943-.382 2.657-1.128.734-.744 1.101-1.659 1.101-2.722zm-9.916 0c0-1.682.583-3.086 1.729-4.256 1.166-1.17 2.635-1.767 4.428-1.767 1.793 0 3.262.597 4.407 1.767 1.167 1.17 1.75 2.574 1.75 4.256 0 1.7-.583 3.127-1.75 4.297-1.145 1.17-2.614 1.745-4.407 1.745-1.793 0-3.262-.575-4.428-1.745-1.146-1.17-1.729-2.596-1.729-4.297zm-1.5 3.233l.821 1.83c-.864.638-1.944.958-3.22.958-2.526 0-3.822-1.554-3.822-4.383V11.66h-2.01v-2h2.031V5.595h2.355v4.063h4.018v2h-4.018v5.405c0 1.469.605 2.191 1.793 2.191.626 0 1.318-.212 2.052-.638zm-12.43 2.51h2.375V9.66h-2.376v11.469zm1.23-12.977c-.929 0-1.642-.682-1.642-1.596 0-.873.713-1.554 1.643-1.554.885 0 1.576.681 1.576 1.554 0 .914-.69 1.596-1.576 1.596zm-6.49 7.234c0-1.086-.346-1.98-1.037-2.724-.692-.745-1.599-1.128-2.7-1.128-1.102 0-2.01.383-2.7 1.128-.692.744-1.037 1.638-1.037 2.724 0 1.084.345 2.02 1.036 2.766.691.744 1.6 1.105 2.7 1.105 1.102 0 2.01-.361 2.7-1.105.692-.746 1.038-1.682 1.038-2.766zm-.173-4.129V5h2.397v16.128h-2.354v-1.596c-1.015 1.255-2.333 1.873-3.91 1.873-1.663 0-3.068-.575-4.169-1.724-1.102-1.17-1.663-2.596-1.663-4.297 0-1.682.561-3.107 1.663-4.256 1.101-1.17 2.485-1.745 4.148-1.745 1.534 0 2.83.617 3.888 1.872zm-11.48 9.873h-10.218V5.405h10.195v2.318h-7.711V12h7.15v2.32h-7.15v4.489h7.733v2.319zm-23.891-9.724c-1.793 0-3.132 1.192-3.478 2.979h6.783c-.194-1.808-1.555-2.979-3.305-2.979zm5.703 3.766c0 .32-.021.703-.086 1.128h-9.095c.346 1.787 1.62 3 3.867 3 1.318 0 2.916-.49 3.953-1.234l.994 1.724c-1.189.872-3.067 1.595-5.033 1.595-4.364 0-6.243-3-6.243-6.021 0-1.724.54-3.15 1.642-4.277 1.101-1.127 2.548-1.702 4.298-1.702 1.664 0 3.046.511 4.105 1.553 1.058 1.043 1.598 2.447 1.598 4.234zm-19.949 3.894c1.08 0 1.966-.362 2.68-1.085.712-.724 1.058-1.617 1.058-2.703 0-1.084-.346-2-1.059-2.701-.713-.702-1.599-1.064-2.679-1.064-1.058 0-1.944.362-2.656 1.085-.714.702-1.059 1.596-1.059 2.68 0 1.086.345 2 1.059 2.724.712.702 1.598 1.064 2.656 1.064zm3.673-7.936V9.66h2.29v10.299c0 1.85-.584 3.32-1.728 4.404-1.146 1.085-2.68 1.638-4.58 1.638-1.945 0-3.672-.553-5.206-1.638l1.037-1.808c1.296.915 2.679 1.36 4.126 1.36 2.484 0 3.996-1.51 3.996-3.637v-.83c-1.015 1.127-2.311 1.702-3.91 1.702-1.684 0-3.089-.554-4.19-1.68-1.102-1.128-1.642-2.532-1.642-4.214 0-1.68.561-3.085 1.706-4.191 1.145-1.128 2.571-1.681 4.234-1.681 1.534 0 2.83.575 3.867 1.745zm-18.07 8.127c1.102 0 1.988-.382 2.7-1.128.714-.744 1.06-1.659 1.06-2.743 0-1.065-.346-1.98-1.06-2.724-.712-.745-1.598-1.128-2.7-1.128-1.101 0-2.008.383-2.7 1.128-.691.744-1.036 1.66-1.036 2.745 0 1.084.345 2 1.037 2.745.691.744 1.598 1.105 2.7 1.105zm3.652-8V9.66h2.29v11.469h-2.29v-1.575c-1.059 1.234-2.399 1.852-3.976 1.852-1.663 0-3.067-.575-4.168-1.745-1.102-1.17-1.642-2.617-1.642-4.34 0-1.724.54-3.128 1.642-4.256 1.1-1.128 2.505-1.681 4.168-1.681 1.577 0 2.917.617 3.976 1.872zM138.79 9.34c1.404 0 2.527.448 3.37 1.34.863.873 1.295 2.086 1.295 3.596v6.852h-2.376V14.66c0-2.021-1.036-3.128-2.657-3.128-1.727 0-2.915 1.255-2.915 3.192v6.404h-2.377v-6.426c0-1.978-1.037-3.17-2.679-3.17-1.728 0-2.937 1.277-2.937 3.234v6.362h-2.377V9.659h2.333v1.66c.692-1.212 1.988-1.979 3.522-1.979 1.533.021 2.958.767 3.586 2.107.798-1.277 2.419-2.107 4.212-2.107zm-19.517 11.788h2.484V5.405h-2.484v15.723z"/>
4 + </g>
5 +</svg>
1 +module.exports = {
2 + rules: {
3 + 'max-nested-callbacks': 0,
4 + },
5 +};
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Test cases of "src/js/action.js"
4 + */
5 +import snippet from 'tui-code-snippet';
6 +import { Promise } from '../src/js/util';
7 +import ImageEditor from '../src/js/imageEditor';
8 +import action from '../src/js/action';
9 +import { eventNames } from '../src/js/consts';
10 +
11 +describe('Ui', () => {
12 + let actions;
13 + let imageEditorMock;
14 +
15 + beforeEach(() => {
16 + action.mixin(ImageEditor);
17 + imageEditorMock = new ImageEditor(document.createElement('div'), {
18 + includeUI: {
19 + loadImage: false,
20 + initMenu: 'flip',
21 + menuBarPosition: 'bottom',
22 + applyCropSelectionStyle: true,
23 + },
24 + });
25 + actions = imageEditorMock.getActions();
26 +
27 + spyOn(snippet, 'imagePing');
28 + });
29 +
30 + afterEach(() => {
31 + imageEditorMock.destroy();
32 + });
33 +
34 + describe('mainAction', () => {
35 + let mainAction;
36 + beforeEach(() => {
37 + mainAction = actions.main;
38 + });
39 +
40 + it('LoadImageFromURL() API should be executed When the initLoadImage action occurs', (done) => {
41 + const promise = new Promise((resolve) => {
42 + resolve(300);
43 + });
44 + spyOn(imageEditorMock, 'loadImageFromURL').and.returnValue(promise);
45 + spyOn(imageEditorMock, 'clearUndoStack');
46 + spyOn(imageEditorMock.ui, 'resizeEditor');
47 +
48 + mainAction.initLoadImage('path', 'imageName').then(() => {
49 + expect(imageEditorMock.clearUndoStack).toHaveBeenCalled();
50 + expect(imageEditorMock.ui.resizeEditor).toHaveBeenCalled();
51 + expect(imageEditorMock.loadImageFromURL).toHaveBeenCalled();
52 + done();
53 + });
54 + });
55 +
56 + it('Undo() API should be executed When the undo action occurs', () => {
57 + spyOn(imageEditorMock, 'isEmptyUndoStack').and.returnValue(false);
58 + spyOn(imageEditorMock, 'undo').and.returnValue({ then: () => {} });
59 +
60 + mainAction.undo();
61 +
62 + expect(imageEditorMock.undo).toHaveBeenCalled();
63 + });
64 +
65 + it('Redo() API should be executed When the redo action occurs', () => {
66 + spyOn(imageEditorMock, 'isEmptyRedoStack').and.returnValue(false);
67 + spyOn(imageEditorMock, 'redo').and.returnValue({ then: () => {} });
68 +
69 + mainAction.redo();
70 +
71 + expect(imageEditorMock.redo).toHaveBeenCalled();
72 + });
73 +
74 + it('removeObject() API should be executed When the delete action occurs', () => {
75 + imageEditorMock.activeObjectId = 10;
76 + spyOn(imageEditorMock, 'removeActiveObject');
77 +
78 + mainAction['delete']();
79 +
80 + expect(imageEditorMock.removeActiveObject).toHaveBeenCalled();
81 + expect(imageEditorMock.activeObjectId).toBe(null);
82 + });
83 +
84 + it('clearObjects() API should be run and the enabled state should be changed When the deleteAll action occurs', () => {
85 + spyOn(imageEditorMock, 'clearObjects');
86 + spyOn(imageEditorMock.ui, 'changeHelpButtonEnabled');
87 +
88 + mainAction.deleteAll();
89 +
90 + const changeHelpButtonCalls = imageEditorMock.ui.changeHelpButtonEnabled.calls;
91 +
92 + expect(imageEditorMock.clearObjects).toHaveBeenCalled();
93 + expect(changeHelpButtonCalls.argsFor(0)[0]).toBe('delete');
94 + expect(changeHelpButtonCalls.argsFor(1)[0]).toBe('deleteAll');
95 + });
96 +
97 + it('loadImageFromFile() API should be executed When the load action occurs', (done) => {
98 + const promise = new Promise((resolve) => {
99 + resolve();
100 + });
101 +
102 + spyOn(imageEditorMock, 'loadImageFromFile').and.returnValue(promise);
103 + spyOn(imageEditorMock, 'clearUndoStack');
104 + spyOn(imageEditorMock.ui, 'resizeEditor');
105 +
106 + window.URL = {
107 + createObjectURL: jasmine.createSpy('URL'),
108 + };
109 +
110 + mainAction.load();
111 +
112 + promise.then(() => {
113 + expect(imageEditorMock.loadImageFromFile).toHaveBeenCalled();
114 + expect(imageEditorMock.clearUndoStack).toHaveBeenCalled();
115 + expect(imageEditorMock.ui.resizeEditor).toHaveBeenCalled();
116 + done();
117 + });
118 + });
119 + });
120 +
121 + describe('shapeAction', () => {
122 + let shapeAction;
123 +
124 + beforeEach(() => {
125 + shapeAction = actions.shape;
126 + });
127 +
128 + it('changeShape() API should be executed When the changeShape action occurs', () => {
129 + imageEditorMock.activeObjectId = 10;
130 + spyOn(imageEditorMock, 'changeShape');
131 +
132 + shapeAction.changeShape({
133 + strokeWidth: '#000000',
134 + });
135 + expect(imageEditorMock.changeShape).toHaveBeenCalled();
136 + });
137 +
138 + it('setDrawingShape() API should be executed When the setDrawingShape action occurs', () => {
139 + spyOn(imageEditorMock, 'setDrawingShape');
140 +
141 + shapeAction.setDrawingShape();
142 + expect(imageEditorMock.setDrawingShape).toHaveBeenCalled();
143 + });
144 + });
145 +
146 + describe('cropAction', () => {
147 + let cropAction;
148 + beforeEach(() => {
149 + cropAction = actions.crop;
150 + });
151 + it('getCropzoneRect(), stopDrawingMode(), ui.resizeEditor(), ui.changeMenu() API should be executed When the crop action occurs', (done) => {
152 + const promise = new Promise((resolve) => {
153 + resolve();
154 + });
155 + spyOn(imageEditorMock, 'crop').and.returnValue(promise);
156 + spyOn(imageEditorMock, 'getCropzoneRect').and.returnValue(true);
157 + spyOn(imageEditorMock, 'stopDrawingMode');
158 + spyOn(imageEditorMock.ui, 'resizeEditor');
159 + spyOn(imageEditorMock.ui, 'changeMenu');
160 +
161 + cropAction.crop();
162 +
163 + expect(imageEditorMock.getCropzoneRect).toHaveBeenCalled();
164 + expect(imageEditorMock.crop).toHaveBeenCalled();
165 + promise.then(() => {
166 + expect(imageEditorMock.stopDrawingMode).toHaveBeenCalled();
167 + expect(imageEditorMock.ui.resizeEditor).toHaveBeenCalled();
168 + expect(imageEditorMock.ui.changeMenu).toHaveBeenCalled();
169 + done();
170 + });
171 + });
172 +
173 + it('stopDrawingMode() API should be executed When the cancel action occurs', () => {
174 + spyOn(imageEditorMock, 'stopDrawingMode');
175 + spyOn(imageEditorMock.ui, 'changeMenu');
176 +
177 + cropAction.cancel();
178 + expect(imageEditorMock.stopDrawingMode).toHaveBeenCalled();
179 + expect(imageEditorMock.ui.changeMenu).toHaveBeenCalled();
180 + });
181 + });
182 +
183 + describe('flipAction', () => {
184 + let flipAction;
185 + beforeEach(() => {
186 + flipAction = actions.flip;
187 + });
188 + it('{flipType}() API should be executed When the flip(fliptype) action occurs', () => {
189 + spyOn(imageEditorMock, 'flipX');
190 + spyOn(imageEditorMock, 'flipY');
191 +
192 + flipAction.flip('flipX');
193 + expect(imageEditorMock.flipX).toHaveBeenCalled();
194 +
195 + flipAction.flip('flipY');
196 + expect(imageEditorMock.flipY).toHaveBeenCalled();
197 + });
198 + });
199 +
200 + describe('rotateAction', () => {
201 + let rotateAction;
202 + beforeEach(() => {
203 + rotateAction = actions.rotate;
204 + });
205 +
206 + it('rotate() API should be executed When the rotate action occurs', () => {
207 + spyOn(imageEditorMock, 'rotate');
208 + spyOn(imageEditorMock.ui, 'resizeEditor');
209 +
210 + rotateAction.rotate(30);
211 + expect(imageEditorMock.rotate).toHaveBeenCalled();
212 + expect(imageEditorMock.ui.resizeEditor).toHaveBeenCalled();
213 + });
214 +
215 + it('setAngle() API should be executed When the setAngle action occurs', () => {
216 + spyOn(imageEditorMock, 'setAngle');
217 + spyOn(imageEditorMock.ui, 'resizeEditor');
218 +
219 + rotateAction.setAngle(30);
220 + expect(imageEditorMock.setAngle).toHaveBeenCalled();
221 + expect(imageEditorMock.ui.resizeEditor).toHaveBeenCalled();
222 + });
223 + });
224 +
225 + describe('textAction', () => {
226 + let textAction;
227 + beforeEach(() => {
228 + textAction = actions.text;
229 + });
230 +
231 + it('changeTextStyle() API should be executed When the changeTextStyle action occurs', () => {
232 + imageEditorMock.activeObjectId = 10;
233 + spyOn(imageEditorMock, 'changeTextStyle');
234 +
235 + textAction.changeTextStyle({ fontSize: 10 });
236 + expect(imageEditorMock.changeTextStyle.calls.mostRecent().args[0]).toBe(10);
237 + expect(imageEditorMock.changeTextStyle.calls.mostRecent().args[1]).toEqual({ fontSize: 10 });
238 + });
239 + });
240 +
241 + describe('maskAction', () => {
242 + let maskAction;
243 + beforeEach(() => {
244 + maskAction = actions.mask;
245 + });
246 +
247 + it('applyFilter() API should be executed When the applyFilter action occurs', () => {
248 + imageEditorMock.activeObjectId = 10;
249 + spyOn(imageEditorMock, 'applyFilter');
250 +
251 + maskAction.applyFilter();
252 + expect(imageEditorMock.applyFilter.calls.mostRecent().args[1]).toEqual({ maskObjId: 10 });
253 + });
254 + });
255 +
256 + describe('drawAction', () => {
257 + let drawAction, expected;
258 + beforeEach(() => {
259 + drawAction = actions.draw;
260 + });
261 +
262 + it('startDrawingMode("FREE_DRAWING") API should be executed When the setDrawMode("free") action occurs', () => {
263 + spyOn(imageEditorMock, 'startDrawingMode');
264 + drawAction.setDrawMode('free');
265 +
266 + expected = imageEditorMock.startDrawingMode.calls.mostRecent().args[0];
267 + expect(expected).toBe('FREE_DRAWING');
268 + });
269 +
270 + it('setBrush() API should be executed When the setColor() action occurs', () => {
271 + spyOn(imageEditorMock, 'setBrush');
272 + drawAction.setColor('#000000');
273 +
274 + expected = imageEditorMock.setBrush.calls.mostRecent().args[0].color;
275 + expect(expected).toBe('#000000');
276 + });
277 + });
278 +
279 + describe('iconAction', () => {
280 + let iconAction;
281 +
282 + beforeEach(() => {
283 + iconAction = actions.icon;
284 + });
285 +
286 + it('when the add icon occurs, the drawing mode should be run.', () => {
287 + spyOn(imageEditorMock, 'startDrawingMode');
288 + spyOn(imageEditorMock, 'setDrawingIcon');
289 +
290 + iconAction.addIcon('iconTypeA', '#fff');
291 +
292 + expect(imageEditorMock.startDrawingMode).toHaveBeenCalled();
293 + expect(imageEditorMock.setDrawingIcon).toHaveBeenCalled();
294 + });
295 + });
296 +
297 + describe('filterAction', () => {
298 + let filterAction;
299 + beforeEach(() => {
300 + filterAction = actions.filter;
301 + });
302 +
303 + it('removeFilter() API should be executed When the type of applyFilter is false', () => {
304 + spyOn(imageEditorMock, 'removeFilter');
305 + spyOn(imageEditorMock, 'hasFilter').and.returnValue(true);
306 + filterAction.applyFilter(false, {});
307 +
308 + expect(imageEditorMock.removeFilter).toHaveBeenCalled();
309 + });
310 +
311 + it('applyFilter() API should be executed When the type of applyFilter is true', () => {
312 + spyOn(imageEditorMock, 'applyFilter');
313 + filterAction.applyFilter(true, {});
314 +
315 + expect(imageEditorMock.applyFilter).toHaveBeenCalled();
316 + });
317 + });
318 +
319 + describe('commonAction', () => {
320 + it('Each action returned to the getActions method must contain commonAction.', () => {
321 + const submenus = [
322 + 'shape',
323 + 'crop',
324 + 'flip',
325 + 'rotate',
326 + 'text',
327 + 'mask',
328 + 'draw',
329 + 'icon',
330 + 'filter',
331 + ];
332 + snippet.forEach(submenus, (submenu) => {
333 + expect(actions[submenu].modeChange).toBeDefined();
334 + expect(actions[submenu].deactivateAll).toBeDefined();
335 + expect(actions[submenu].changeSelectableAll).toBeDefined();
336 + expect(actions[submenu].discardSelection).toBeDefined();
337 + expect(actions[submenu].stopDrawingMode).toBeDefined();
338 + });
339 + });
340 +
341 + describe('modeChange()', () => {
342 + let commonAction;
343 + beforeEach(() => {
344 + commonAction = actions.main;
345 + });
346 +
347 + it('_changeActivateMode("TEXT") API should be executed When the modeChange("text") action occurs', () => {
348 + spyOn(imageEditorMock, '_changeActivateMode');
349 +
350 + commonAction.modeChange('text');
351 + expect(imageEditorMock._changeActivateMode).toHaveBeenCalled();
352 + });
353 +
354 + it('startDrawingMode() API should be executed When the modeChange("crop") action occurs', () => {
355 + spyOn(imageEditorMock, 'startDrawingMode');
356 +
357 + commonAction.modeChange('crop');
358 + expect(imageEditorMock.startDrawingMode).toHaveBeenCalled();
359 + });
360 +
361 + it('stopDrawingMode(), setDrawingShape(), _changeActivateMode() API should be executed When the modeChange("shape") action occurs', () => {
362 + spyOn(imageEditorMock, 'setDrawingShape');
363 + spyOn(imageEditorMock, '_changeActivateMode');
364 +
365 + commonAction.modeChange('shape');
366 + expect(imageEditorMock.setDrawingShape).toHaveBeenCalled();
367 + expect(imageEditorMock._changeActivateMode).toHaveBeenCalled();
368 + });
369 + });
370 + });
371 +
372 + describe('reAction', () => {
373 + beforeEach(() => {
374 + imageEditorMock.setReAction();
375 + spyOn(imageEditorMock.ui, 'changeHelpButtonEnabled');
376 + });
377 +
378 + describe('undoStackChanged', () => {
379 + it('If the undo stack has a length greater than zero, the state of changeUndoButtonStatus, changeResetButtonStatus should be true.', () => {
380 + imageEditorMock.fire('undoStackChanged', 1);
381 +
382 + expect(imageEditorMock.ui.changeHelpButtonEnabled.calls.argsFor(0)).toEqual(['undo', true]);
383 + expect(imageEditorMock.ui.changeHelpButtonEnabled.calls.argsFor(1)).toEqual([
384 + 'reset',
385 + true,
386 + ]);
387 + });
388 +
389 + it('If the undo stack has a length of 0, the state of changeUndoButtonStatus, changeResetButtonStatus should be false.', () => {
390 + imageEditorMock.fire('undoStackChanged', 0);
391 +
392 + expect(imageEditorMock.ui.changeHelpButtonEnabled.calls.argsFor(0)).toEqual([
393 + 'undo',
394 + false,
395 + ]);
396 + expect(imageEditorMock.ui.changeHelpButtonEnabled.calls.argsFor(1)).toEqual([
397 + 'reset',
398 + false,
399 + ]);
400 + });
401 + });
402 +
403 + describe('redoStackChanged', () => {
404 + it('If the redo stack is greater than zero length, the state of changeRedoButtonStatus should be true.', () => {
405 + imageEditorMock.fire('redoStackChanged', 1);
406 + expect(imageEditorMock.ui.changeHelpButtonEnabled.calls.argsFor(0)).toEqual(['redo', true]);
407 + });
408 +
409 + it('If the redo stack has a length of zero, the state of changeRedoButtonStatus should be false.', () => {
410 + imageEditorMock.fire('redoStackChanged', 0);
411 + expect(imageEditorMock.ui.changeHelpButtonEnabled.calls.argsFor(0)).toEqual([
412 + 'redo',
413 + false,
414 + ]);
415 + });
416 + });
417 +
418 + describe('objectActivated', () => {
419 + it('When objectActivated occurs, the state of the delete button should be enabled.', () => {
420 + imageEditorMock.fire('objectActivated', { id: 1 });
421 + expect(imageEditorMock.ui.changeHelpButtonEnabled.calls.argsFor(0)).toEqual([
422 + 'delete',
423 + true,
424 + ]);
425 + expect(imageEditorMock.ui.changeHelpButtonEnabled.calls.argsFor(1)).toEqual([
426 + 'deleteAll',
427 + true,
428 + ]);
429 + });
430 +
431 + it("When objectActivated's target is cropzone, changeApplyButtonStatus should be enabled.", () => {
432 + spyOn(imageEditorMock.ui.crop, 'changeApplyButtonStatus');
433 + imageEditorMock.fire('objectActivated', {
434 + id: 1,
435 + type: 'cropzone',
436 + });
437 + expect(imageEditorMock.ui.crop.changeApplyButtonStatus.calls.mostRecent().args[0]).toBe(
438 + true
439 + );
440 + });
441 +
442 + it('If the target of objectActivated is shape and the existing menu is not shpe, the menu should be changed to shape.', () => {
443 + imageEditorMock.ui.submenu = 'crop';
444 + spyOn(imageEditorMock.ui, 'changeMenu');
445 + spyOn(imageEditorMock.ui.shape, 'setShapeStatus');
446 + spyOn(imageEditorMock.ui.shape, 'setMaxStrokeValue');
447 + imageEditorMock.fire('objectActivated', {
448 + id: 1,
449 + type: 'circle',
450 + });
451 +
452 + expect(imageEditorMock.ui.changeMenu.calls.mostRecent().args[0]).toBe('shape');
453 + expect(imageEditorMock.ui.shape.setMaxStrokeValue).toHaveBeenCalled();
454 + });
455 +
456 + it('If the target of objectActivated is text and the existing menu is not text, the menu should be changed to text.', () => {
457 + imageEditorMock.ui.submenu = 'crop';
458 + spyOn(imageEditorMock.ui, 'changeMenu');
459 + imageEditorMock.fire('objectActivated', {
460 + id: 1,
461 + type: 'i-text',
462 + });
463 +
464 + expect(imageEditorMock.ui.changeMenu.calls.mostRecent().args[0]).toBe('text');
465 + });
466 +
467 + it('If the target of objectActivated is icon and the existing menu is not icon, the menu should be changed to icon.', () => {
468 + imageEditorMock.ui.submenu = 'crop';
469 + spyOn(imageEditorMock.ui, 'changeMenu');
470 + spyOn(imageEditorMock.ui.icon, 'setIconPickerColor');
471 + imageEditorMock.fire('objectActivated', {
472 + id: 1,
473 + type: 'icon',
474 + });
475 +
476 + expect(imageEditorMock.ui.changeMenu.calls.mostRecent().args[0]).toBe('icon');
477 + expect(imageEditorMock.ui.icon.setIconPickerColor).toHaveBeenCalled();
478 + });
479 + });
480 +
481 + describe('addObjectAfter', () => {
482 + it("When addObjectAfter occurs, the shape's maxStrokeValue should be changed to match the size of the added object.", () => {
483 + spyOn(imageEditorMock.ui.shape, 'setMaxStrokeValue');
484 + spyOn(imageEditorMock.ui.shape, 'changeStandbyMode');
485 + imageEditorMock.fire('addObjectAfter', {
486 + type: 'circle',
487 + width: 100,
488 + height: 200,
489 + });
490 +
491 + expect(imageEditorMock.ui.shape.setMaxStrokeValue.calls.mostRecent().args[0]).toBe(100);
492 + expect(imageEditorMock.ui.shape.changeStandbyMode).toHaveBeenCalled();
493 + });
494 + });
495 +
496 + describe('objectScaled', () => {
497 + it('If objectScaled occurs on an object of type text, fontSize must be changed.', () => {
498 + imageEditorMock.ui.text.fontSize = 0;
499 + imageEditorMock.fire('objectScaled', {
500 + type: 'i-text',
501 + fontSize: 20,
502 + });
503 +
504 + expect(imageEditorMock.ui.text.fontSize).toBe(20);
505 + });
506 +
507 + it('If objectScaled is for a shape type object and strokeValue is greater than the size of the object, the value should change.', () => {
508 + spyOn(imageEditorMock.ui.shape, 'getStrokeValue').and.returnValue(20);
509 + spyOn(imageEditorMock.ui.shape, 'setStrokeValue');
510 + imageEditorMock.fire('objectScaled', {
511 + type: 'rect',
512 + width: 10,
513 + height: 10,
514 + });
515 + expect(imageEditorMock.ui.shape.setStrokeValue.calls.mostRecent().args[0]).toBe(10);
516 + });
517 + });
518 +
519 + describe('selectionCleared', () => {
520 + it('If selectionCleared occurs in the text menu state, the menu should be closed.', () => {
521 + imageEditorMock.ui.submenu = 'text';
522 + spyOn(imageEditorMock, 'changeCursor');
523 +
524 + imageEditorMock.fire('selectionCleared');
525 + expect(imageEditorMock.changeCursor.calls.mostRecent().args[0]).toBe('text');
526 + });
527 + });
528 + });
529 +});
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Test cases of "src/js/extension/allowLine.js"
4 + */
5 +import ArrowLine from '../src/js/extension/arrowLine';
6 +
7 +describe('AllowLine', () => {
8 + let ctx, arrowLine, linePath;
9 +
10 + beforeEach(() => {
11 + ctx = {
12 + lineWidth: 1,
13 + beginPath: jasmine.createSpy('beginPath'),
14 + moveTo: jasmine.createSpy('moveTo'),
15 + lineTo: jasmine.createSpy('lineTo'),
16 + closePath: jasmine.createSpy('closePath'),
17 + };
18 + arrowLine = new ArrowLine();
19 + arrowLine.ctx = ctx;
20 + linePath = {
21 + fromX: 1,
22 + fromY: 1,
23 + toX: 10,
24 + toY: 10,
25 + };
26 + });
27 +
28 + it('When attaching the "chevron" type to the endpoint, you need to draw the "v" calculated according to the angle around the "tail" of the line.', () => {
29 + arrowLine.arrowType = {
30 + tail: 'chevron',
31 + };
32 + arrowLine._drawDecoratorPath(linePath);
33 +
34 + const firstPoint = ctx.moveTo.calls.argsFor(0).map((value) => Math.round(value));
35 + const secondPoint = ctx.lineTo.calls.argsFor(0).map((value) => Math.round(value));
36 + const lastPoint = ctx.lineTo.calls.argsFor(1).map((value) => Math.round(value));
37 +
38 + expect(firstPoint).toEqual([9, 7]);
39 + expect(secondPoint).toEqual([10, 10]);
40 + expect(lastPoint).toEqual([7, 9]);
41 + });
42 +
43 + it('When attaching the "chevron" type to the startpoint, you need to draw the "v" calculated according to the angle around the "head" of the line.', () => {
44 + arrowLine.arrowType = {
45 + head: 'chevron',
46 + };
47 + arrowLine._drawDecoratorPath(linePath);
48 +
49 + const firstPoint = ctx.moveTo.calls.argsFor(0).map((value) => Math.round(value));
50 + const secondPoint = ctx.lineTo.calls.argsFor(0).map((value) => Math.round(value));
51 + const lastPoint = ctx.lineTo.calls.argsFor(1).map((value) => Math.round(value));
52 +
53 + expect(firstPoint).toEqual([2, 4]);
54 + expect(secondPoint).toEqual([1, 1]);
55 + expect(lastPoint).toEqual([4, 2]);
56 + });
57 +
58 + it('"triangle" should be a triangular shape that closes the path with closePath after drawing.', () => {
59 + arrowLine.arrowType = {
60 + head: 'triangle',
61 + };
62 + arrowLine._drawDecoratorPath(linePath);
63 +
64 + const firstPoint = ctx.moveTo.calls.argsFor(0).map((value) => Math.round(value));
65 + const secondPoint = ctx.lineTo.calls.argsFor(0).map((value) => Math.round(value));
66 + const thirdPoint = ctx.lineTo.calls.argsFor(1).map((value) => Math.round(value));
67 +
68 + expect(firstPoint).toEqual([1, 3]);
69 + expect(secondPoint).toEqual([1, 1]);
70 + expect(thirdPoint).toEqual([3, 1]);
71 + expect(ctx.closePath.calls.count()).toBe(1);
72 + });
73 +});
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Tests command with command-factory
4 + */
5 +import snippet from 'tui-code-snippet';
6 +import { Promise } from '../src/js/util';
7 +import fabric from 'fabric';
8 +import Invoker from '../src/js/invoker';
9 +import commandFactory from '../src/js/factory/command';
10 +import Graphics from '../src/js/graphics';
11 +import { commandNames as commands } from '../src/js/consts';
12 +import { getCachedUndoDataForDimension } from '../src/js/helper/selectionModifyHelper';
13 +
14 +describe('commandFactory', () => {
15 + let invoker, mockImage, canvas, graphics;
16 +
17 + beforeEach(() => {
18 + graphics = new Graphics(document.createElement('canvas'));
19 + invoker = new Invoker();
20 + mockImage = new fabric.Image();
21 +
22 + graphics.setCanvasImage('', mockImage);
23 + canvas = graphics.getCanvas();
24 + });
25 +
26 + describe('functions', () => {
27 + it('can register custom command', (done) => {
28 + const testCommand = {
29 + name: 'testCommand',
30 + execute() {},
31 + undo() {},
32 + };
33 +
34 + spyOn(testCommand, 'execute').and.returnValue(Promise.resolve('testCommand'));
35 + spyOn(testCommand, 'undo').and.returnValue(Promise.resolve());
36 +
37 + commandFactory.register(testCommand);
38 +
39 + const command = commandFactory.create('testCommand');
40 + expect(command).not.toBe(null);
41 +
42 + invoker
43 + .execute('testCommand', graphics)
44 + .then((commandName) => {
45 + expect(commandName).toBe('testCommand');
46 + expect(testCommand.execute).toHaveBeenCalledWith(graphics);
47 + done();
48 + })
49 + ['catch']((message) => {
50 + fail(message);
51 + done();
52 + });
53 + });
54 +
55 + it('can pass parameters on execute', (done) => {
56 + commandFactory.register({
57 + name: 'testCommand',
58 + execute(compMap, obj1, obj2, obj3) {
59 + expect(obj1).toBe(1);
60 + expect(obj2).toBe(2);
61 + expect(obj3).toBe(3);
62 +
63 + return Promise.resolve();
64 + },
65 + });
66 +
67 + invoker
68 + .execute('testCommand', graphics, 1, 2, 3)
69 + .then(() => {
70 + done();
71 + })
72 + ['catch']((message) => {
73 + fail(message);
74 + done();
75 + });
76 + });
77 +
78 + it('can pass parameters on undo', (done) => {
79 + commandFactory.register({
80 + name: 'testCommand',
81 + execute() {
82 + return Promise.resolve();
83 + },
84 + undo(compMap, obj1, obj2, obj3) {
85 + expect(obj1).toBe(1);
86 + expect(obj2).toBe(2);
87 + expect(obj3).toBe(3);
88 +
89 + return Promise.resolve();
90 + },
91 + });
92 +
93 + invoker
94 + .execute('testCommand', graphics, 1, 2, 3)
95 + .then(() => invoker.undo())
96 + .then(() => done())
97 + ['catch']((message) => {
98 + fail(message);
99 + done();
100 + });
101 + });
102 + });
103 +
104 + describe('addObjectCommand', () => {
105 + let obj;
106 +
107 + it('should stamp object', (done) => {
108 + obj = new fabric.Rect();
109 + invoker.execute(commands.ADD_OBJECT, graphics, obj).then(() => {
110 + expect(snippet.hasStamp(obj)).toBe(true);
111 + done();
112 + });
113 + });
114 +
115 + it('should add object to canvas', (done) => {
116 + obj = new fabric.Rect();
117 + invoker.execute(commands.ADD_OBJECT, graphics, obj).then(() => {
118 + expect(canvas.contains(obj)).toBe(true);
119 + done();
120 + });
121 + });
122 +
123 + it('"undo()" should remove object from canvas', (done) => {
124 + obj = new fabric.Rect();
125 + invoker
126 + .execute(commands.ADD_OBJECT, graphics, obj)
127 + .then(() => invoker.undo())
128 + .then(() => {
129 + expect(canvas.contains(obj)).toBe(false);
130 + done();
131 + });
132 + });
133 + });
134 + describe('changeSelectionCommand', () => {
135 + let obj;
136 +
137 + beforeEach(() => {
138 + spyOn(canvas, 'getPointer');
139 + obj = new fabric.Rect({
140 + width: 10,
141 + height: 10,
142 + top: 10,
143 + left: 10,
144 + scaleX: 1,
145 + scaleY: 1,
146 + angle: 0,
147 + });
148 + graphics._addFabricObject(obj);
149 + graphics._onMouseDown({ target: obj });
150 +
151 + const props = [
152 + {
153 + id: graphics.getObjectId(obj),
154 + width: 30,
155 + height: 30,
156 + top: 30,
157 + left: 30,
158 + scaleX: 0.5,
159 + scaleY: 0.5,
160 + angle: 10,
161 + },
162 + ];
163 + const makeCommand = commandFactory.create(commands.CHANGE_SELECTION, graphics, props);
164 + makeCommand.execute(graphics, props);
165 + invoker.pushUndoStack(makeCommand);
166 + });
167 +
168 + it('should work undo command correctly', (done) => {
169 + invoker.undo().then(() => {
170 + expect(obj.width).toBe(10);
171 + expect(obj.height).toBe(10);
172 + expect(obj.left).toBe(10);
173 + expect(obj.top).toBe(10);
174 + expect(obj.scaleX).toBe(1);
175 + expect(obj.scaleY).toBe(1);
176 + expect(obj.angle).toBe(0);
177 + done();
178 + });
179 + });
180 +
181 + it('should work redo command correctly', (done) => {
182 + invoker.undo().then(() => {
183 + invoker.redo().then(() => {
184 + expect(obj.width).toBe(30);
185 + expect(obj.height).toBe(30);
186 + expect(obj.left).toBe(30);
187 + expect(obj.top).toBe(30);
188 + expect(obj.scaleX).toBe(0.5);
189 + expect(obj.scaleY).toBe(0.5);
190 + expect(obj.angle).toBe(10);
191 + done();
192 + });
193 + });
194 + });
195 + });
196 +
197 + describe('loadImageCommand', () => {
198 + const imageURL = 'base/test/fixtures/sampleImage.jpg';
199 +
200 + beforeEach(() => {
201 + graphics.setCanvasImage('', null);
202 + });
203 +
204 + it('should clear canvas', () => {
205 + spyOn(canvas, 'clear');
206 + invoker.execute(commands.LOAD_IMAGE, graphics, 'image', imageURL);
207 +
208 + expect(canvas.clear).toHaveBeenCalled();
209 + });
210 +
211 + it('should load new image', (done) => {
212 + invoker.execute(commands.LOAD_IMAGE, graphics, 'image', imageURL).then((sizeChange) => {
213 + expect(graphics.getImageName()).toEqual('image');
214 + expect(graphics.getCanvasImage().getSrc()).toContain(imageURL);
215 + expect(sizeChange.oldWidth).toEqual(jasmine.any(Number));
216 + expect(sizeChange.oldHeight).toEqual(jasmine.any(Number));
217 + expect(sizeChange.newWidth).toEqual(jasmine.any(Number));
218 + expect(sizeChange.newHeight).toEqual(jasmine.any(Number));
219 + done();
220 + });
221 + });
222 +
223 + it('After running the LOAD_IMAGE command, existing objects should not include cropzone.', (done) => {
224 + const objCropzone = new fabric.Object({ type: 'cropzone' });
225 +
226 + invoker.execute(commands.ADD_OBJECT, graphics, objCropzone).then(() => {
227 + invoker.execute(commands.LOAD_IMAGE, graphics, 'image', imageURL).then(() => {
228 + const lastUndoIndex = invoker._undoStack.length - 1;
229 + const savedObjects = invoker._undoStack[lastUndoIndex].undoData.objects;
230 +
231 + expect(savedObjects.length).toBe(0);
232 + done();
233 + });
234 + });
235 + });
236 +
237 + it('`evented` attribute of the saved object must be true after LOAD_IMAGE.', (done) => {
238 + const objCircle = new fabric.Object({
239 + type: 'circle',
240 + evented: false,
241 + });
242 +
243 + invoker.execute(commands.ADD_OBJECT, graphics, objCircle).then(() => {
244 + invoker.execute(commands.LOAD_IMAGE, graphics, 'image', imageURL).then(() => {
245 + const lastUndoIndex = invoker._undoStack.length - 1;
246 + const [savedObject] = invoker._undoStack[lastUndoIndex].undoData.objects;
247 +
248 + expect(savedObject.evented).toBe(true);
249 + done();
250 + });
251 + });
252 + });
253 +
254 + it('"undo()" should clear image if not exists prev image', (done) => {
255 + invoker
256 + .execute(commands.LOAD_IMAGE, graphics, 'image', imageURL)
257 + .then(() => invoker.undo())
258 + .then(() => {
259 + expect(graphics.getCanvasImage()).toBe(null);
260 + expect(graphics.getImageName()).toBe('');
261 + done();
262 + });
263 + });
264 +
265 + it('"undo()" should restore to prev image', (done) => {
266 + const newImageURL = 'base/test/fixtures/TOAST%20UI%20Component.png';
267 +
268 + invoker
269 + .execute(commands.LOAD_IMAGE, graphics, 'image', imageURL)
270 + .then(() => invoker.execute(commands.LOAD_IMAGE, graphics, 'newImage', newImageURL))
271 + .then(() => {
272 + expect(graphics.getImageName()).toBe('newImage');
273 + expect(graphics.getCanvasImage().getSrc()).toContain(newImageURL);
274 +
275 + return invoker.undo();
276 + })
277 + .then(() => {
278 + expect(graphics.getImageName()).toEqual('image');
279 + expect(graphics.getCanvasImage().getSrc()).toContain(imageURL);
280 + done();
281 + });
282 + });
283 + });
284 +
285 + describe('flipImageCommand', () => {
286 + it('flipX', () => {
287 + const originFlipX = mockImage.flipX;
288 +
289 + invoker.execute(commands.FLIP_IMAGE, graphics, 'flipX');
290 +
291 + expect(mockImage.flipX).toBe(!originFlipX);
292 + });
293 +
294 + it('flipY', () => {
295 + const originFlipY = mockImage.flipY;
296 +
297 + invoker.execute(commands.FLIP_IMAGE, graphics, 'flipY');
298 +
299 + expect(mockImage.flipY).toBe(!originFlipY);
300 + });
301 +
302 + it('resetFlip', () => {
303 + mockImage.flipX = true;
304 + mockImage.flipY = true;
305 +
306 + invoker.execute(commands.FLIP_IMAGE, graphics, 'reset');
307 +
308 + expect(mockImage.flipX).toBe(false);
309 + expect(mockImage.flipY).toBe(false);
310 + });
311 +
312 + it('"undo()" should restore flipX', (done) => {
313 + const originFlipX = mockImage.flipX;
314 +
315 + invoker
316 + .execute(commands.FLIP_IMAGE, graphics, 'flipX')
317 + .then(() => invoker.undo())
318 + .then(() => {
319 + expect(mockImage.flipX).toBe(originFlipX);
320 + done();
321 + });
322 + });
323 +
324 + it('"undo()" should restore filpY', (done) => {
325 + const originFlipY = mockImage.flipY;
326 +
327 + invoker
328 + .execute(commands.FLIP_IMAGE, graphics, 'flipY')
329 + .then(() => invoker.undo())
330 + .then(() => {
331 + expect(mockImage.flipY).toBe(originFlipY);
332 + done();
333 + });
334 + });
335 + });
336 +
337 + describe('textCommand', () => {
338 + let textObjectId;
339 + const defaultFontSize = 50;
340 + const defaultUnderline = false;
341 + beforeEach((done) => {
342 + invoker
343 + .execute(commands.ADD_TEXT, graphics, 'text', {
344 + styles: {
345 + fontSize: defaultFontSize,
346 + underline: false,
347 + },
348 + })
349 + .then((textObject) => {
350 + textObjectId = textObject.id;
351 + done();
352 + });
353 + });
354 + it('"changeTextStyle" should set text style', (done) => {
355 + invoker
356 + .execute(commands.CHANGE_TEXT_STYLE, graphics, textObjectId, {
357 + fontSize: 30,
358 + underline: true,
359 + })
360 + .then(() => {
361 + const textObject = graphics.getObject(textObjectId);
362 + expect(textObject.fontSize).toBe(30);
363 + expect(textObject.underline).toBe(true);
364 + done();
365 + });
366 + });
367 + it('"undo()" should restore fontSize', (done) => {
368 + invoker
369 + .execute(commands.CHANGE_TEXT_STYLE, graphics, textObjectId, {
370 + fontSize: 30,
371 + underline: true,
372 + })
373 + .then(() => invoker.undo())
374 + .then(() => {
375 + const textObject = graphics.getObject(textObjectId);
376 + expect(textObject.fontSize).toBe(defaultFontSize);
377 + expect(textObject.underline).toBe(defaultUnderline);
378 + done();
379 + });
380 + });
381 + });
382 +
383 + describe('rotationImageCommand', () => {
384 + it('"rotate()" should add angle', () => {
385 + const originAngle = mockImage.angle;
386 +
387 + invoker.execute(commands.ROTATE_IMAGE, graphics, 'rotate', 10);
388 +
389 + expect(mockImage.angle).toBe(originAngle + 10);
390 + });
391 +
392 + it('"setAngle()" should set angle', () => {
393 + mockImage.angle = 100;
394 + invoker.execute(commands.ROTATE_IMAGE, graphics, 'setAngle', 30);
395 +
396 + expect(mockImage.angle).toBe(30);
397 + });
398 +
399 + it('"undo()" should restore angle', (done) => {
400 + const originalAngle = mockImage.angle;
401 +
402 + invoker
403 + .execute(commands.ROTATE_IMAGE, graphics, 'setAngle', 100)
404 + .then(() => invoker.undo())
405 + .then(() => {
406 + expect(mockImage.angle).toBe(originalAngle);
407 + done();
408 + });
409 + });
410 + });
411 +
412 + describe('shapeCommand', () => {
413 + let shapeObjectId;
414 + const defaultStrokeWidth = 12;
415 + beforeEach((done) => {
416 + invoker
417 + .execute(commands.ADD_SHAPE, graphics, 'rect', {
418 + strokeWidth: defaultStrokeWidth,
419 + })
420 + .then((shapeObject) => {
421 + shapeObjectId = shapeObject.id;
422 + done();
423 + });
424 + });
425 + it('"changeShape" should set strokeWidth', (done) => {
426 + invoker
427 + .execute(commands.CHANGE_SHAPE, graphics, shapeObjectId, {
428 + strokeWidth: 50,
429 + })
430 + .then(() => {
431 + const shapeObject = graphics.getObject(shapeObjectId);
432 + expect(shapeObject.strokeWidth).toBe(50);
433 + done();
434 + });
435 + });
436 + it('"redo()" should restore strokeWidth', (done) => {
437 + invoker
438 + .execute(commands.CHANGE_SHAPE, graphics, shapeObjectId, {
439 + strokeWidth: 50,
440 + })
441 + .then(() => invoker.undo())
442 + .then(() => {
443 + const shapeObject = graphics.getObject(shapeObjectId);
444 + expect(shapeObject.strokeWidth).toBe(defaultStrokeWidth);
445 + done();
446 + });
447 + });
448 + });
449 +
450 + describe('clearCommand', () => {
451 + let canvasContext;
452 +
453 + beforeEach(() => {
454 + canvasContext = canvas;
455 + });
456 +
457 + it('should clear all objects', () => {
458 + const objects = [new fabric.Rect(), new fabric.Rect(), new fabric.Rect()];
459 + canvas.add.apply(canvasContext, objects);
460 +
461 + expect(canvas.contains(objects[0])).toBe(true);
462 + expect(canvas.contains(objects[1])).toBe(true);
463 + expect(canvas.contains(objects[2])).toBe(true);
464 +
465 + invoker.execute(commands.CLEAR_OBJECTS, graphics);
466 +
467 + expect(canvas.contains(objects[0])).toBe(false);
468 + expect(canvas.contains(objects[1])).toBe(false);
469 + expect(canvas.contains(objects[2])).toBe(false);
470 + });
471 +
472 + it('"undo()" restore all objects', (done) => {
473 + const objects = [new fabric.Rect(), new fabric.Rect(), new fabric.Rect()];
474 + canvas.add.apply(canvasContext, objects);
475 + invoker
476 + .execute(commands.CLEAR_OBJECTS, graphics)
477 + .then(() => invoker.undo())
478 + .then(() => {
479 + expect(canvas.contains(objects[0])).toBe(true);
480 + expect(canvas.contains(objects[1])).toBe(true);
481 + expect(canvas.contains(objects[2])).toBe(true);
482 + done();
483 + });
484 + });
485 + });
486 +
487 + describe('removeCommand', () => {
488 + let object, object2, group;
489 +
490 + beforeEach(() => {
491 + object = new fabric.Rect({
492 + left: 10,
493 + top: 10,
494 + });
495 + object2 = new fabric.Rect({
496 + left: 5,
497 + top: 20,
498 + });
499 + group = new fabric.Group();
500 +
501 + graphics.add(object);
502 + graphics.add(object2);
503 + graphics.add(group);
504 + group.add(object, object2);
505 + });
506 +
507 + it('should remove an object', () => {
508 + graphics.setActiveObject(object);
509 + invoker.execute(commands.REMOVE_OBJECT, graphics, snippet.stamp(object));
510 +
511 + expect(canvas.contains(object)).toBe(false);
512 + });
513 +
514 + it('should remove objects group', () => {
515 + canvas.setActiveObject(group);
516 + invoker.execute(commands.REMOVE_OBJECT, graphics, snippet.stamp(group));
517 +
518 + expect(canvas.contains(object)).toBe(false);
519 + expect(canvas.contains(object2)).toBe(false);
520 + });
521 +
522 + it('"undo()" should restore the removed object', (done) => {
523 + canvas.setActiveObject(object);
524 +
525 + invoker
526 + .execute(commands.REMOVE_OBJECT, graphics, snippet.stamp(object))
527 + .then(() => invoker.undo())
528 + .then(() => {
529 + expect(canvas.contains(object)).toBe(true);
530 + done();
531 + });
532 + });
533 +
534 + it('"undo()" should restore the removed objects (group)', (done) => {
535 + canvas.setActiveObject(group);
536 + invoker
537 + .execute(commands.REMOVE_OBJECT, graphics, snippet.stamp(group))
538 + .then(() => invoker.undo())
539 + .then(() => {
540 + expect(canvas.contains(object)).toBe(true);
541 + expect(canvas.contains(object2)).toBe(true);
542 + done();
543 + });
544 + });
545 +
546 + it('"undo ()" should restore the position of the removed object (group). ', (done) => {
547 + const activeSelection = graphics.getActiveSelectionFromObjects(canvas.getObjects());
548 + graphics.setActiveObject(activeSelection);
549 +
550 + invoker
551 + .execute(commands.REMOVE_OBJECT, graphics, graphics.getActiveObjectIdForRemove())
552 + .then(() => invoker.undo())
553 + .then(() => {
554 + expect(object.left).toBe(10);
555 + expect(object.top).toBe(10);
556 + expect(object2.left).toBe(5);
557 + expect(object2.top).toBe(20);
558 + done();
559 + });
560 + });
561 + });
562 +});
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Test cases of "src/js/component/cropper.js"
4 + */
5 +import snippet from 'tui-code-snippet';
6 +import fabric from 'fabric';
7 +import $ from 'jquery';
8 +import Cropper from '../src/js/component/cropper';
9 +import Graphics from '../src/js/graphics';
10 +import { eventNames, CROPZONE_DEFAULT_OPTIONS } from '../src/js/consts';
11 +
12 +describe('Cropper', () => {
13 + let cropper, graphics, canvas;
14 +
15 + beforeEach(() => {
16 + graphics = new Graphics($('<canvas>')[0]);
17 + canvas = graphics.getCanvas();
18 + cropper = new Cropper(graphics);
19 + });
20 +
21 + describe('start()', () => {
22 + it('should create a cropzone', () => {
23 + cropper.start();
24 +
25 + expect(cropper._cropzone).toBeDefined();
26 + });
27 +
28 + it('should be applied predefined default options When creating a cropzone', () => {
29 + cropper.start();
30 + const cropzone = cropper._cropzone;
31 +
32 + snippet.forEach(CROPZONE_DEFAULT_OPTIONS, (optionValue, optionName) => {
33 + expect(cropzone[optionName]).toBe(optionValue);
34 + });
35 + });
36 +
37 + it('should add a cropzone to canvas', () => {
38 + spyOn(canvas, 'add');
39 + cropper.start();
40 +
41 + expect(canvas.add).toHaveBeenCalledWith(cropper._cropzone);
42 + });
43 +
44 + it('should no action if a croppzone has been defined', () => {
45 + cropper._cropzone = {};
46 + spyOn(canvas, 'add');
47 + cropper.start();
48 +
49 + expect(canvas.add).not.toHaveBeenCalled();
50 + });
51 +
52 + it('should set "evented" of all objects to false', () => {
53 + const objects = [
54 + new fabric.Rect({ evented: true }),
55 + new fabric.Rect({ evented: true }),
56 + new fabric.Rect({ evented: true }),
57 + ];
58 + canvas.add(objects[0], objects[1], objects[2]);
59 +
60 + cropper.start();
61 + expect(objects[0].evented).toBe(false);
62 + expect(objects[1].evented).toBe(false);
63 + expect(objects[2].evented).toBe(false);
64 + });
65 + });
66 +
67 + describe('"onFabricMouseDown()"', () => {
68 + let fEvent;
69 + beforeEach(() => {
70 + fEvent = {
71 + e: {},
72 + };
73 + spyOn(canvas, 'getPointer').and.returnValue({
74 + x: 10,
75 + y: 20,
76 + });
77 + });
78 +
79 + it('should set "selection" to false', () => {
80 + cropper._onFabricMouseDown(fEvent);
81 + expect(canvas.selection).toBe(false);
82 + });
83 +
84 + it('should set "startX, startY"', () => {
85 + // canvas.getPointer will return object{x: 10, y: 20}
86 + cropper._onFabricMouseDown(fEvent);
87 + expect(cropper._startX).toEqual(10);
88 + expect(cropper._startY).toEqual(20);
89 + });
90 + });
91 +
92 + describe('"onFabricMouseMove()', () => {
93 + beforeEach(() => {
94 + spyOn(canvas, 'getPointer').and.returnValue({
95 + x: 10,
96 + y: 20,
97 + });
98 + spyOn(canvas, 'getWidth').and.returnValue(100);
99 + spyOn(canvas, 'getHeight').and.returnValue(200);
100 + });
101 +
102 + it(
103 + 'should re-render(remove->set->add) cropzone ' +
104 + 'if the mouse moving is over the threshold(=10)',
105 + () => {
106 + cropper._startX = 0;
107 + cropper._startY = 0;
108 +
109 + cropper.start();
110 + spyOn(canvas, 'remove');
111 + spyOn(cropper._cropzone, 'set');
112 + spyOn(canvas, 'add');
113 + cropper._onFabricMouseMove({ e: {} });
114 +
115 + expect(canvas.remove).toHaveBeenCalled();
116 + expect(cropper._cropzone.set).toHaveBeenCalled();
117 + expect(canvas.add).toHaveBeenCalled();
118 + }
119 + );
120 +
121 + it('should not re-render cropzone ' + 'if the mouse moving is under the threshold', () => {
122 + cropper._startX = 14;
123 + cropper._startY = 18;
124 +
125 + cropper.start();
126 + spyOn(canvas, 'remove');
127 + spyOn(cropper._cropzone, 'set');
128 + spyOn(canvas, 'add');
129 + cropper._onFabricMouseMove({ e: {} });
130 +
131 + expect(canvas.remove).not.toHaveBeenCalled();
132 + expect(cropper._cropzone.set).not.toHaveBeenCalled();
133 + expect(canvas.add).not.toHaveBeenCalled();
134 + });
135 + });
136 +
137 + describe('_calcRectDimensionFromPoint()', () => {
138 + beforeEach(() => {
139 + cropper._startX = 10;
140 + cropper._startY = 20;
141 + snippet.extend(canvas, {
142 + getWidth() {
143 + return 100;
144 + },
145 + getHeight() {
146 + return 200;
147 + },
148 + });
149 + });
150 +
151 + it('should return cropzone-left&top (min: 0, max: startX,Y)', () => {
152 + const x = 20,
153 + y = -1,
154 + expected = {
155 + left: 10,
156 + top: 0,
157 + width: jasmine.any(Number),
158 + height: jasmine.any(Number),
159 + },
160 + actual = cropper._calcRectDimensionFromPoint(x, y);
161 +
162 + expect(actual).toEqual(expected);
163 + });
164 +
165 + it('should calculate and return cropzone-width&height', () => {
166 + let x, y, expected, actual;
167 +
168 + x = 30;
169 + y = 40;
170 + expected = {
171 + left: 10,
172 + top: 20,
173 + width: 20,
174 + height: 20,
175 + };
176 + actual = cropper._calcRectDimensionFromPoint(x, y);
177 + expect(actual).toEqual(expected);
178 +
179 + x = 300;
180 + y = 400;
181 + expected = {
182 + left: 10,
183 + top: 20,
184 + width: 90,
185 + height: 180,
186 + };
187 + actual = cropper._calcRectDimensionFromPoint(x, y);
188 + expect(actual).toEqual(expected);
189 + });
190 +
191 + it('should create cropzone that has fixed ratio during shift key is pressed.', () => {
192 + const x = 100;
193 + const y = 200;
194 + const expected = {
195 + left: 10,
196 + top: 20,
197 + width: 180,
198 + height: 180,
199 + };
200 +
201 + cropper._withShiftKey = true;
202 +
203 + const actual = cropper._calcRectDimensionFromPoint(x, y);
204 +
205 + expect(actual).toEqual(expected);
206 + });
207 +
208 + it('should create cropzone that inverted current mouse position during shift key is pressed.', () => {
209 + const x = -10;
210 + const y = -20;
211 + const expected = {
212 + left: -10,
213 + top: 0,
214 + width: 20,
215 + height: 20,
216 + };
217 +
218 + cropper._withShiftKey = true;
219 +
220 + const actual = cropper._calcRectDimensionFromPoint(x, y);
221 +
222 + expect(actual).toEqual(expected);
223 + });
224 + });
225 +
226 + it('"onFabricMouseUp()" should activate cropzone', () => {
227 + canvas.setActiveObject = jasmine.createSpy();
228 + cropper.start();
229 + cropper._onFabricMouseUp();
230 +
231 + expect(canvas.setActiveObject).toHaveBeenCalledWith(cropper._cropzone);
232 + });
233 +
234 + describe('"crop()"', () => {
235 + it('should return cropzone rect', () => {
236 + cropper.start();
237 + spyOn(cropper._cropzone, 'isValid').and.returnValue(true);
238 +
239 + expect(cropper.getCropzoneRect()).toBeTruthy();
240 + cropper.end();
241 + });
242 +
243 + it('should return cropzone data if the cropzone is valid', () => {
244 + cropper.start();
245 + spyOn(cropper._cropzone, 'isValid').and.returnValue(true);
246 +
247 + expect(cropper.getCroppedImageData(cropper.getCropzoneRect())).toEqual({
248 + imageName: jasmine.any(String),
249 + url: jasmine.any(String),
250 + });
251 + cropper.end();
252 + });
253 + });
254 +
255 + describe('"presets - setCropzoneRect()"', () => {
256 + beforeEach(() => {
257 + cropper.start();
258 + });
259 +
260 + afterEach(() => {
261 + cropper.end();
262 + });
263 +
264 + it('should return cropzone rect as a square', () => {
265 + spyOn(cropper._cropzone, 'isValid').and.returnValue(true);
266 + cropper.setCropzoneRect(1 / 1);
267 +
268 + expect(cropper.getCropzoneRect().width).toBe(cropper.getCropzoneRect().height);
269 + });
270 +
271 + it('should return cropzone rect as a 3:2 aspect box', () => {
272 + spyOn(cropper._cropzone, 'isValid').and.returnValue(true);
273 + cropper.setCropzoneRect(3 / 2);
274 +
275 + expect((cropper.getCropzoneRect().width / cropper.getCropzoneRect().height).toFixed(1)).toBe(
276 + (3 / 2).toFixed(1)
277 + );
278 + });
279 +
280 + it('should return cropzone rect as a 4:3 aspect box', () => {
281 + spyOn(cropper._cropzone, 'isValid').and.returnValue(true);
282 + cropper.setCropzoneRect(4 / 3);
283 +
284 + expect((cropper.getCropzoneRect().width / cropper.getCropzoneRect().height).toFixed(1)).toBe(
285 + (4 / 3).toFixed(1)
286 + );
287 + });
288 +
289 + it('should return cropzone rect as a 5:4 aspect box', () => {
290 + spyOn(cropper._cropzone, 'isValid').and.returnValue(true);
291 + cropper.setCropzoneRect(5 / 4);
292 +
293 + expect((cropper.getCropzoneRect().width / cropper.getCropzoneRect().height).toFixed(1)).toBe(
294 + (5 / 4).toFixed(1)
295 + );
296 + });
297 +
298 + it('should return cropzone rect as a 7:5 aspect box', () => {
299 + spyOn(cropper._cropzone, 'isValid').and.returnValue(true);
300 + cropper.setCropzoneRect(7 / 5);
301 +
302 + expect((cropper.getCropzoneRect().width / cropper.getCropzoneRect().height).toFixed(1)).toBe(
303 + (7 / 5).toFixed(1)
304 + );
305 + });
306 +
307 + it('should return cropzone rect as a 16:9 aspect box', () => {
308 + spyOn(cropper._cropzone, 'isValid').and.returnValue(true);
309 + cropper.setCropzoneRect(16 / 9);
310 +
311 + expect((cropper.getCropzoneRect().width / cropper.getCropzoneRect().height).toFixed(1)).toBe(
312 + (16 / 9).toFixed(1)
313 + );
314 + });
315 +
316 + it('Even in situations with floating point problems, should calculate the exact width you expect.', () => {
317 + spyOn(canvas, 'getWidth').and.returnValue(408);
318 + spyOn(canvas, 'getHeight').and.returnValue(312);
319 + spyOn(cropper._cropzone, 'set').and.callThrough();
320 +
321 + cropper.setCropzoneRect(16 / 9);
322 +
323 + expect(cropper._cropzone.set.calls.first().args[0].width).toBe(408);
324 + });
325 +
326 + it('should remove cropzone of cropper when falsy is passed', () => {
327 + cropper.setCropzoneRect();
328 + expect(cropper.getCropzoneRect()).toBeFalsy();
329 +
330 + cropper.setCropzoneRect(0);
331 + expect(cropper.getCropzoneRect()).toBeFalsy();
332 +
333 + cropper.setCropzoneRect(null);
334 + expect(cropper.getCropzoneRect()).toBeFalsy();
335 + });
336 + });
337 +
338 + describe('"end()"', () => {
339 + it('should set cropzone of cropper to null', () => {
340 + cropper.start();
341 + cropper.end();
342 +
343 + expect(cropper._cropzone).toBe(null);
344 + });
345 +
346 + it('should set "evented" of all obejcts to true', () => {
347 + const objects = [
348 + new fabric.Rect({ evented: false }),
349 + new fabric.Rect({ evented: false }),
350 + new fabric.Rect({ evented: false }),
351 + ];
352 + canvas.add(objects[0], objects[1], objects[2]);
353 +
354 + cropper.start();
355 + cropper.end();
356 + expect(objects[0].evented).toBe(true);
357 + expect(objects[1].evented).toBe(true);
358 + expect(objects[2].evented).toBe(true);
359 + });
360 + });
361 + describe('canvas event delegator', () => {
362 + it('The event of an object with an eventDelegator must fire the graphics.fire registered with the trigger.', () => {
363 + cropper.start();
364 + spyOn(graphics, 'fire');
365 + const events = eventNames;
366 + const fEvent = {
367 + target: cropper._cropzone,
368 + };
369 +
370 + const cropzone = cropper._cropzone;
371 +
372 + canvas.fire('object:scaling', fEvent);
373 +
374 + expect(graphics.fire.calls.count()).toBe(0);
375 + cropzone.canvasEventTrigger[events.OBJECT_SCALED](cropzone);
376 + expect(graphics.fire.calls.count()).toBe(1);
377 + });
378 + });
379 +});
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Test cases of "src/js/extension/cropzone.js"
4 + */
5 +import snippet from 'tui-code-snippet';
6 +import fabric from 'fabric';
7 +import Cropzone from '../src/js/extension/cropzone';
8 +
9 +describe('Cropzone', () => {
10 + const options = {
11 + left: 10,
12 + top: 10,
13 + width: 100,
14 + height: 100,
15 + cornerSize: 10,
16 + strokeWidth: 0,
17 + cornerColor: 'black',
18 + fill: 'transparent',
19 + hasRotatingPoint: false,
20 + hasBorders: false,
21 + lockScalingFlip: true,
22 + lockRotation: true,
23 + };
24 + const canvas = new fabric.Canvas();
25 + canvas.height = 400;
26 + canvas.width = 300;
27 +
28 + it('"_getCoordinates()" should return outer&inner rect coordinates(array)', () => {
29 + const cropzone = new Cropzone(canvas, options, {});
30 + const coords = cropzone._getCoordinates();
31 +
32 + expect(coords).toEqual({
33 + x: [-60, -50, 50, 240],
34 + y: [-60, -50, 50, 340],
35 + });
36 + });
37 +
38 + it('"_onMoving()" should set left and top between 0 and canvas size', () => {
39 + const cropzone = new Cropzone(canvas, options, {});
40 + const mockFabricCanvas = {
41 + getWidth() {
42 + return 300;
43 + },
44 + getHeight() {
45 + return 400;
46 + },
47 + };
48 +
49 + cropzone.canvas = mockFabricCanvas;
50 + cropzone.left = -1;
51 + cropzone.top = -1;
52 + cropzone._onMoving();
53 +
54 + expect(cropzone.top).toEqual(0);
55 + expect(cropzone.left).toEqual(0);
56 +
57 + cropzone.left = 1000;
58 + cropzone.top = 1000;
59 + cropzone._onMoving();
60 +
61 + expect(cropzone.left).toEqual(200);
62 + expect(cropzone.top).toEqual(300);
63 + });
64 +
65 + it('"isValid()" should return whether the cropzone has real area or not', () => {
66 + const cropzone = new Cropzone(canvas, options, {});
67 + cropzone.left = -1;
68 + expect(cropzone.isValid()).toBe(false);
69 +
70 + cropzone.left = 1;
71 + expect(cropzone.isValid()).toBe(true);
72 +
73 + cropzone.height = -1;
74 + expect(cropzone.isValid()).toBe(false);
75 +
76 + cropzone.height = 1;
77 + expect(cropzone.isValid()).toBe(true);
78 + });
79 +
80 + it('"_resizeTL" should give the expected value at run', () => {
81 + const cropzone = new Cropzone(canvas, options, {});
82 +
83 + expect(
84 + cropzone._resizeCropZone(
85 + {
86 + x: 30,
87 + y: 40,
88 + },
89 + 'tl'
90 + )
91 + ).toEqual({
92 + left: 30,
93 + top: 40,
94 + width: 80,
95 + height: 70,
96 + });
97 + });
98 +
99 + it('"_resizeTR" should give the expected value at run', () => {
100 + const cropzone = new Cropzone(canvas, options, {});
101 +
102 + expect(
103 + cropzone._resizeCropZone(
104 + {
105 + x: 80,
106 + y: 50,
107 + },
108 + 'tr'
109 + )
110 + ).toEqual({
111 + left: 10,
112 + top: 50,
113 + width: 70,
114 + height: 60,
115 + });
116 + });
117 +
118 + it('"_resizeBL" should give the expected value at run', () => {
119 + const cropzone = new Cropzone(canvas, options, {});
120 +
121 + expect(
122 + cropzone._resizeCropZone(
123 + {
124 + x: 30,
125 + y: 40,
126 + },
127 + 'bl'
128 + )
129 + ).toEqual({
130 + left: 30,
131 + top: 10,
132 + width: 80,
133 + height: 30,
134 + });
135 + });
136 +
137 + it('"_resizeBR" should give the expected value at run', () => {
138 + const cropzone = new Cropzone(canvas, options, {});
139 +
140 + expect(
141 + cropzone._resizeCropZone(
142 + {
143 + x: 30,
144 + y: 40,
145 + },
146 + 'br'
147 + )
148 + ).toEqual({
149 + left: 10,
150 + top: 10,
151 + width: 20,
152 + height: 30,
153 + });
154 + });
155 +
156 + it('should yield the result of maintaining the ratio at running the resize function at a fixed rate', () => {
157 + const presetRatio = 5 / 4;
158 + const cropzone = new Cropzone(
159 + canvas,
160 + snippet.extend({}, options, {
161 + width: 50,
162 + height: 40,
163 + presetRatio,
164 + }),
165 + {}
166 + );
167 +
168 + snippet.forEach(['tl', 'tr', 'mt', 'ml', 'mr', 'mb', 'bl', 'br'], (cornerType) => {
169 + const { width, height } = cropzone._resizeCropZone(
170 + {
171 + x: 20,
172 + y: 20,
173 + },
174 + cornerType
175 + );
176 +
177 + expect(width / height).toEqual(presetRatio);
178 + });
179 + });
180 +});
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Test cases of "src/js/imageEditor.js"
4 + */
5 +import ImageEditor from '../src/js/imageEditor';
6 +
7 +describe('DrawingMode', () => {
8 + let imageEditor;
9 + const imageURL = 'base/test/fixtures/sampleImage.jpg';
10 +
11 + beforeEach((done) => {
12 + imageEditor = new ImageEditor(document.createElement('div'), {
13 + cssMaxWidth: 700,
14 + cssMaxHeight: 500,
15 + });
16 + imageEditor.loadImageFromURL(imageURL, 'sampleImage').then(() => {
17 + done();
18 + });
19 + });
20 +
21 + afterEach(() => {
22 + imageEditor.destroy();
23 + });
24 +
25 + it('enter a drawing mode with startDrawingMode, CROPPER', () => {
26 + imageEditor.startDrawingMode('CROPPER');
27 +
28 + expect(imageEditor.getDrawingMode()).toBe('CROPPER');
29 + });
30 +
31 + it('stop a drawing mode with stopDrawingMode, ie, to normal', () => {
32 + imageEditor.stopDrawingMode();
33 +
34 + expect(imageEditor.getDrawingMode()).toBe('NORMAL');
35 + });
36 +
37 + it('enter all drawing mode with startDrawingMode in consecutive order', () => {
38 + const drawingModes = ['CROPPER', 'FREE_DRAWING', 'LINE_DRAWING', 'TEXT', 'SHAPE'];
39 + const { length } = drawingModes;
40 + let i;
41 +
42 + for (i = 0; i < length; i += 1) {
43 + imageEditor.startDrawingMode(drawingModes[i]);
44 +
45 + expect(imageEditor.getDrawingMode()).toBe(drawingModes[i]);
46 + }
47 +
48 + expect(imageEditor.startDrawingMode('CROPPER')).toBe(true);
49 + expect(imageEditor.startDrawingMode('CROPPER')).toBe(true); // call again, should return true
50 + expect(imageEditor.startDrawingMode('NOT_A_DRAWING_MODE')).toBe(false);
51 + });
52 +});
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Test cases of "src/js/component/filter.js"
4 + */
5 +import ImageEditor from '../src/js/imageEditor';
6 +
7 +describe('Filter', () => {
8 + let imageEditor;
9 + const imageURL = 'base/test/fixtures/sampleImage.jpg';
10 +
11 + beforeEach((done) => {
12 + imageEditor = new ImageEditor(document.createElement('div'), {
13 + cssMaxWidth: 700,
14 + cssMaxHeight: 500,
15 + });
16 + imageEditor.loadImageFromURL(imageURL, 'sampleImage').then(() => {
17 + imageEditor.clearUndoStack();
18 + done();
19 + });
20 + });
21 +
22 + afterEach(() => {
23 + imageEditor.destroy();
24 + });
25 +
26 + it('applyFilter() can add undo stack', (done) => {
27 + imageEditor
28 + .applyFilter('colorFilter')
29 + .then(() => {
30 + expect(imageEditor.isEmptyUndoStack()).toBe(false);
31 + done();
32 + })
33 + ['catch'](() => {
34 + fail();
35 + done();
36 + });
37 + });
38 +
39 + it('applyFilter() can not add undo stack at isSilent', (done) => {
40 + const isSilent = true;
41 +
42 + imageEditor
43 + .applyFilter('colorFilter', {}, isSilent)
44 + .then(() => {
45 + expect(imageEditor.isEmptyUndoStack()).toBe(true);
46 + done();
47 + })
48 + ['catch'](() => {
49 + fail();
50 + done();
51 + });
52 + });
53 +
54 + it('hasFilter', () => {
55 + imageEditor.applyFilter('colorFilter');
56 +
57 + expect(imageEditor.hasFilter('invert')).toBe(false);
58 + expect(imageEditor.hasFilter('colorFilter')).toBe(true);
59 + });
60 +
61 + it('removeFilter() can remove added filter', (done) => {
62 + imageEditor
63 + .applyFilter('colorFilter')
64 + .then(() => imageEditor.removeFilter('colorFilter'))
65 + .then(() => {
66 + expect(imageEditor.hasFilter('colorFilter')).toBe(false);
67 + expect(imageEditor.isEmptyUndoStack()).toBe(false);
68 + done();
69 + })
70 + ['catch'](() => {
71 + fail();
72 + done();
73 + });
74 + });
75 +});
1 +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs>
2 + <circle id="a" cx="16" cy="16" r="16"/>
3 + </defs><symbol id="icon-a-ic-apply" viewBox="0 0 24 24">
4 + <g fill="none" fill-rule="evenodd">
5 + <path d="M0 0h24v24H0z"/>
6 + <path stroke="#434343" d="M4 12.011l5 5L20.011 6"/>
7 + </g>
8 +</symbol><symbol id="icon-a-ic-cancel" viewBox="0 0 24 24">
9 + <g fill="none" fill-rule="evenodd">
10 + <path d="M0 0h24v24H0z"/>
11 + <path stroke="#434343" d="M6 6l12 12M18 6L6 18"/>
12 + </g>
13 +</symbol><symbol id="icon-a-ic-color-transparent-w" viewBox="0 0 32 32">
14 +
15 + <g fill="none" fill-rule="evenodd">
16 + <g>
17 + <use fill="#FFF" xlink:href="#a"/>
18 + <circle cx="16" cy="16" r="15.5" stroke="#D5D5D5"/>
19 + </g>
20 + <path stroke="#FF4040" stroke-width="1.5" d="M27 5L5 27"/>
21 + </g>
22 +</symbol><symbol id="icon-a-ic-crop" viewBox="0 0 24 24">
23 + <g fill="none" fill-rule="evenodd">
24 + <path d="M0 0h24v24H0z"/>
25 + <path fill="#434343" d="M4 0h1v20a1 1 0 0 1-1-1V0zM20 17h-1V5h1v12zm0 2v5h-1v-5h1z"/>
26 + <path fill="#434343" d="M5 19h19v1H5zM4.762 4v1H0V4h4.762zM7 4h12a1 1 0 0 1 1 1H7V4z"/>
27 + </g>
28 +</symbol><symbol id="icon-a-ic-delete-all" viewBox="0 0 24 24">
29 + <g fill="#434343" fill-rule="evenodd">
30 + <path d="M5 23H3a1 1 0 0 1-1-1V6h1v16h2v1zm16-10h-1V6h1v7zM9 13H8v-3h1v3zm3 0h-1v-3h1v3zm3 0h-1v-3h1v3zM14.794 3.794L13 2h-3L8.206 3.794A.963.963 0 0 1 8 2.5l.703-1.055A1 1 0 0 1 9.535 1h3.93a1 1 0 0 1 .832.445L15 2.5a.965.965 0 0 1-.206 1.294zM14.197 4H8.803h5.394z"/>
31 + <path d="M0 3h23v1H0zM11.286 21H8.714L8 23H7l1-2.8V20h.071L9.5 16h1l1.429 4H12v.2l1 2.8h-1l-.714-2zm-.357-1L10 17.4 9.071 20h1.858zM20 22h3v1h-4v-7h1v6zm-5 0h3v1h-4v-7h1v6z"/>
32 + </g>
33 +</symbol><symbol id="icon-a-ic-delete" viewBox="0 0 24 24">
34 + <g fill="#434343" fill-rule="evenodd">
35 + <path d="M3 6v16h17V6h1v16a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V6h1zM14.794 3.794L13 2h-3L8.206 3.794A.963.963 0 0 1 8 2.5l.703-1.055A1 1 0 0 1 9.535 1h3.93a1 1 0 0 1 .832.445L15 2.5a.965.965 0 0 1-.206 1.294zM14.197 4H8.803h5.394z"/>
36 + <path d="M0 3h23v1H0zM8 10h1v6H8v-6zm3 0h1v6h-1v-6zm3 0h1v6h-1v-6z"/>
37 + </g>
38 +</symbol><symbol id="icon-a-ic-draw-free" viewBox="0 0 32 32">
39 + <g fill="none" fill-rule="evenodd">
40 + <path stroke="#434343" d="M2.5 20.929C2.594 10.976 4.323 6 7.686 6c5.872 0 2.524 19 7.697 19s1.89-14.929 6.414-14.929 1.357 10.858 5.13 10.858c1.802 0 2.657-2.262 2.566-6.786"/>
41 + </g>
42 +</symbol><symbol id="icon-a-ic-draw-line" viewBox="0 0 32 32">
43 + <g fill="none" fill-rule="evenodd">
44 + <path stroke="#434343" d="M2 15.5h28"/>
45 + </g>
46 +</symbol><symbol id="icon-a-ic-draw" viewBox="0 0 24 24">
47 + <g fill="none">
48 + <path stroke="#434343" d="M2.5 21.5H5c.245 0 .48-.058.691-.168l.124-.065.14.01c.429.028.85-.127 1.16-.437L22.55 5.405a.5.5 0 0 0 0-.707l-3.246-3.245a.5.5 0 0 0-.707 0L3.162 16.888a1.495 1.495 0 0 0-.437 1.155l.01.14-.065.123c-.111.212-.17.448-.17.694v2.5z"/>
49 + <path fill="#434343" d="M16.414 3.707l3.89 3.89-.708.706-3.889-3.889z"/>
50 + </g>
51 +</symbol><symbol id="icon-a-ic-filter" viewBox="0 0 24 24">
52 + <g fill="none" fill-rule="evenodd">
53 + <path d="M0 0h24v24H0z"/>
54 + <path fill="#434343" d="M12 7v1H2V7h10zm6 0h4v1h-4V7zM12 16v1h10v-1H12zm-6 0H2v1h4v-1z"/>
55 + <path fill="#434343" d="M8.5 20a3.5 3.5 0 1 1 0-7 3.5 3.5 0 0 1 0 7zm0-1a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5zM15.5 11a3.5 3.5 0 1 1 0-7 3.5 3.5 0 0 1 0 7zm0-1a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5z"/>
56 + </g>
57 +</symbol><symbol id="icon-a-ic-flip-reset" viewBox="0 0 31 32">
58 + <g fill="none" fill-rule="evenodd">
59 + <path d="M31 0H0v32h31z"/>
60 + <path fill="#434343" d="M28 16a8 8 0 0 1-8 8H3v-1h1v-7H3a8 8 0 0 1 8-8h17v1h-1v7h1zM11 9a7 7 0 0 0-7 7v7h16a7 7 0 0 0 7-7V9H11z"/>
61 + <path stroke="#434343" stroke-linecap="square" d="M24 5l3.5 3.5L24 12M7 20l-3.5 3.5L7 27"/>
62 + </g>
63 +</symbol><symbol id="icon-a-ic-flip-x" viewBox="0 0 32 32">
64 + <g fill="none" fill-rule="evenodd">
65 + <path d="M32 32H0V0h32z"/>
66 + <path fill="#434343" d="M17 32h-1V0h1zM27.167 11l.5 3h-1.03l-.546-3h1.076zm-.5-3h-1.122L25 5h-5V4h5.153a1 1 0 0 1 .986.836L26.667 8zm1.5 9l.5 3h-.94l-.545-3h.985zm1 6l.639 3.836A1 1 0 0 1 28.819 28H26v-1h3l-.726-4h.894zM23 28h-3v-1h3v1zM13 4v1H7L3 27h10v1H3.18a1 1 0 0 1-.986-1.164l3.666-22A1 1 0 0 1 6.847 4H13z"/>
67 + </g>
68 +</symbol><symbol id="icon-a-ic-flip-y" viewBox="0 0 32 32">
69 + <g fill="none" fill-rule="evenodd">
70 + <path d="M0 0v32h32V0z"/>
71 + <path fill="#434343" d="M0 16v1h32v-1zM11 27.167l3 .5v-1.03l-3-.546v1.076zm-3-.5v-1.122L5 25v-5H4v5.153a1 1 0 0 0 .836.986L8 26.667zm9 1.5l3 .5v-.94l-3-.545v.985zm6 1l3.836.639A1 1 0 0 0 28 28.82V26h-1v3l-4-.727v.894zM28 23v-3h-1v3h1zM4 13h1V7l22-4v10h1V3.18a1 1 0 0 0-1.164-.986l-22 3.667A1 1 0 0 0 4 6.847V13z"/>
72 + </g>
73 +</symbol><symbol id="icon-a-ic-flip" viewBox="0 0 24 24">
74 + <g fill="none" fill-rule="evenodd">
75 + <path d="M0 0h24v24H0z"/>
76 + <path fill="#434343" d="M11 0h1v24h-1zM19 21v-1h2v-2h1v2a1 1 0 0 1-1 1h-2zm-2 0h-3v-1h3v1zm5-5h-1v-3h1v3zm0-5h-1V8h1v3zm0-5h-1V4h-2V3h2a1 1 0 0 1 1 1v2zm-5-3v1h-3V3h3zM9 3v1H2v16h7v1H2a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h7z"/>
77 + </g>
78 +</symbol><symbol id="icon-a-ic-icon-arrow-2" viewBox="0 0 32 32">
79 + <g fill="none" fill-rule="evenodd">
80 + <path stroke="#434343" stroke-linecap="round" stroke-linejoin="round" d="M21.793 18.5H2.5v-5h18.935l-7.6-8h5.872l10.5 10.5-10.5 10.5h-5.914l8-8z"/>
81 + </g>
82 +</symbol><symbol id="icon-a-ic-icon-arrow-3" viewBox="0 0 32 32">
83 + <g fill="none" fill-rule="evenodd">
84 + <path stroke="#434343" stroke-linecap="round" stroke-linejoin="round" d="M25.288 16.42L14.208 27.5H6.792l11.291-11.291L6.826 4.5h7.381l11.661 11.661-.58.258z"/>
85 + </g>
86 +</symbol><symbol id="icon-a-ic-icon-arrow" viewBox="0 0 32 32">
87 + <g fill="none" fill-rule="evenodd">
88 + <path stroke="#434343" d="M2.5 11.5v9h18v5.293L30.293 16 20.5 6.207V11.5h-18z"/>
89 + </g>
90 +</symbol><symbol id="icon-a-ic-icon-bubble" viewBox="0 0 32 32">
91 + <g fill="none" fill-rule="evenodd">
92 + <path stroke="#434343" stroke-linecap="round" stroke-linejoin="round" d="M22.207 24.5L16.5 30.207V24.5H8A6.5 6.5 0 0 1 1.5 18V9A6.5 6.5 0 0 1 8 2.5h16A6.5 6.5 0 0 1 30.5 9v9a6.5 6.5 0 0 1-6.5 6.5h-1.793z"/>
93 + </g>
94 +</symbol><symbol id="icon-a-ic-icon-heart" viewBox="0 0 32 32">
95 + <g fill="none" fill-rule="evenodd">
96 + <path fill-rule="nonzero" stroke="#434343" d="M15.996 30.675l1.981-1.79c7.898-7.177 10.365-9.718 12.135-13.012.922-1.716 1.377-3.37 1.377-5.076 0-4.65-3.647-8.297-8.297-8.297-2.33 0-4.86 1.527-6.817 3.824l-.38.447-.381-.447C13.658 4.027 11.126 2.5 8.797 2.5 4.147 2.5.5 6.147.5 10.797c0 1.714.46 3.375 1.389 5.098 1.775 3.288 4.26 5.843 12.123 12.974l1.984 1.806z"/>
97 + </g>
98 +</symbol><symbol id="icon-a-ic-icon-load" viewBox="0 0 32 32">
99 + <g fill="none" fill-rule="evenodd">
100 + <path stroke="#434343" stroke-linecap="round" stroke-linejoin="round" d="M17.314 18.867l1.951-2.53 4 5.184h-17l6.5-8.84 4.549 6.186z"/>
101 + <path fill="#434343" d="M18.01 4a11.798 11.798 0 0 0 0 1H3v24h24V14.986a8.738 8.738 0 0 0 1 0V29a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h15.01z"/>
102 + <path fill="#434343" d="M25 3h1v9h-1z"/>
103 + <path stroke="#434343" d="M22 6l3.5-3.5L29 6"/>
104 + </g>
105 +</symbol><symbol id="icon-a-ic-icon-location" viewBox="0 0 32 32">
106 + <g fill="none" fill-rule="evenodd">
107 + <g stroke="#434343">
108 + <path d="M16 31.28C23.675 23.302 27.5 17.181 27.5 13c0-6.351-5.149-11.5-11.5-11.5S4.5 6.649 4.5 13c0 4.181 3.825 10.302 11.5 18.28z"/>
109 + <circle cx="16" cy="13" r="4.5"/>
110 + </g>
111 + </g>
112 +</symbol><symbol id="icon-a-ic-icon-polygon" viewBox="0 0 32 32">
113 + <g fill="none" fill-rule="evenodd">
114 + <path stroke="#434343" d="M.576 16L8.29 29.5h15.42L31.424 16 23.71 2.5H8.29L.576 16z"/>
115 + </g>
116 +</symbol><symbol id="icon-a-ic-icon-star-2" viewBox="0 0 32 32">
117 + <g fill="none" fill-rule="evenodd">
118 + <path stroke="#434343" d="M19.446 31.592l2.265-3.272 3.946.25.636-3.94 3.665-1.505-1.12-3.832 2.655-2.962-2.656-2.962 1.12-3.832-3.664-1.505-.636-3.941-3.946.25-2.265-3.271L16 3.024 12.554 1.07 10.289 4.34l-3.946-.25-.636 3.941-3.665 1.505 1.12 3.832L.508 16.33l2.656 2.962-1.12 3.832 3.664 1.504.636 3.942 3.946-.25 2.265 3.27L16 29.638l3.446 1.955z"/>
119 + </g>
120 +</symbol><symbol id="icon-a-ic-icon-star" viewBox="0 0 32 32">
121 + <g fill="none" fill-rule="evenodd">
122 + <path stroke="#434343" d="M25.292 29.878l-1.775-10.346 7.517-7.327-10.388-1.51L16 1.282l-4.646 9.413-10.388 1.51 7.517 7.327-1.775 10.346L16 24.993l9.292 4.885z"/>
123 + </g>
124 +</symbol><symbol id="icon-a-ic-icon" viewBox="0 0 24 24">
125 + <g fill="none">
126 + <path stroke="#434343" stroke-linecap="round" stroke-linejoin="round" d="M11.923 19.136L5.424 22l.715-7.065-4.731-5.296 6.94-1.503L11.923 2l3.574 6.136 6.94 1.503-4.731 5.296L18.42 22z"/>
127 + </g>
128 +</symbol><symbol id="icon-a-ic-mask-load" viewBox="0 0 32 32">
129 + <g fill="none" fill-rule="evenodd">
130 + <path d="M0 0h32v32H0z"/>
131 + <path fill="#434343" d="M18.01 4a11.798 11.798 0 0 0 0 1H3v24h24V14.986a8.738 8.738 0 0 0 1 0V29a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h15.01zM15 23a6 6 0 1 1 0-12 6 6 0 0 1 0 12zm0-1a5 5 0 1 0 0-10 5 5 0 0 0 0 10z"/>
132 + <path fill="#434343" d="M25 3h1v9h-1z"/>
133 + <path stroke="#434343" d="M22 6l3.5-3.5L29 6"/>
134 + </g>
135 +</symbol><symbol id="icon-a-ic-mask" viewBox="0 0 24 24">
136 + <g fill="none">
137 + <circle cx="12" cy="12" r="4.5" stroke="#434343"/>
138 + <path fill="#434343" d="M2 1h20a1 1 0 0 1 1 1v20a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1zm0 1v20h20V2H2z"/>
139 + </g>
140 +</symbol><symbol id="icon-a-ic-redo" viewBox="0 0 24 24">
141 + <g fill="none" fill-rule="evenodd">
142 + <path d="M0 0h24v24H0z" opacity=".5"/>
143 + <path fill="#434343" d="M21 6H9a6 6 0 1 0 0 12h12v1H9A7 7 0 0 1 9 5h12v1z"/>
144 + <path stroke="#434343" stroke-linecap="square" d="M19 3l2.5 2.5L19 8"/>
145 + </g>
146 +</symbol><symbol id="icon-a-ic-reset" viewBox="0 0 24 24">
147 + <g fill="none" fill-rule="evenodd">
148 + <path d="M0 0h24v24H0z" opacity=".5"/>
149 + <path fill="#434343" d="M2 13v-1a7 7 0 0 1 7-7h13v1h-1v5h1v1a7 7 0 0 1-7 7H2v-1h1v-5H2zm7-7a6 6 0 0 0-6 6v6h12a6 6 0 0 0 6-6V6H9z"/>
150 + <path stroke="#434343" stroke-linecap="square" d="M19 3l2.5 2.5L19 8M5 16l-2.5 2.5L5 21"/>
151 + </g>
152 +</symbol><symbol id="icon-a-ic-rotate-clockwise" viewBox="0 0 32 32">
153 + <g fill="none" fill-rule="evenodd">
154 + <path fill="#434343" d="M29 17h-.924c0 6.627-5.373 12-12 12-6.628 0-12-5.373-12-12C4.076 10.398 9.407 5.041 16 5V4C8.82 4 3 9.82 3 17s5.82 13 13 13 13-5.82 13-13z"/>
155 + <path stroke="#434343" stroke-linecap="square" d="M16 1.5l4 3-4 3"/>
156 + <path fill="#434343" fill-rule="nonzero" d="M16 4h4v1h-4z"/>
157 + </g>
158 +</symbol><symbol id="icon-a-ic-rotate-counterclockwise" viewBox="0 0 32 32">
159 + <g fill="none" fill-rule="evenodd">
160 + <path fill="#434343" d="M3 17h.924c0 6.627 5.373 12 12 12 6.628 0 12-5.373 12-12 0-6.602-5.331-11.96-11.924-12V4c7.18 0 13 5.82 13 13s-5.82 13-13 13S3 24.18 3 17z"/>
161 + <path fill="#434343" fill-rule="nonzero" d="M12 4h4v1h-4z"/>
162 + <path stroke="#434343" stroke-linecap="square" d="M16 1.5l-4 3 4 3"/>
163 + </g>
164 +</symbol><symbol id="icon-a-ic-rotate" viewBox="0 0 24 24">
165 + <g fill="none" fill-rule="evenodd">
166 + <path d="M0 0h24v24H0z"/>
167 + <path fill="#434343" d="M8.349 22.254a10.002 10.002 0 0 1-2.778-1.719l.65-.76a9.002 9.002 0 0 0 2.495 1.548l-.367.931zm2.873.704l.078-.997a9 9 0 1 0-.557-17.852l-.14-.99A10.076 10.076 0 0 1 12.145 3c5.523 0 10 4.477 10 10s-4.477 10-10 10c-.312 0-.62-.014-.924-.042zm-7.556-4.655a9.942 9.942 0 0 1-1.253-2.996l.973-.234a8.948 8.948 0 0 0 1.124 2.693l-.844.537zm-1.502-5.91A9.949 9.949 0 0 1 2.88 9.23l.925.382a8.954 8.954 0 0 0-.644 2.844l-.998-.062zm2.21-5.686c.687-.848 1.51-1.58 2.436-2.166l.523.852a9.048 9.048 0 0 0-2.188 1.95l-.771-.636z"/>
168 + <path stroke="#434343" stroke-linecap="square" d="M13 1l-2.5 2.5L13 6"/>
169 + </g>
170 +</symbol><symbol id="icon-a-ic-shape-circle" viewBox="0 0 32 32">
171 + <g fill="none" fill-rule="evenodd">
172 + <circle cx="16" cy="16" r="14.5" stroke="#434343"/>
173 + </g>
174 +</symbol><symbol id="icon-a-ic-shape-rectangle" viewBox="0 0 32 32">
175 + <g fill="none" fill-rule="evenodd">
176 + <rect width="27" height="27" x="2.5" y="2.5" stroke="#434343" rx="1"/>
177 + </g>
178 +</symbol><symbol id="icon-a-ic-shape-triangle" viewBox="0 0 32 32">
179 + <g fill="none" fill-rule="evenodd">
180 + <path stroke="#434343" stroke-linecap="round" stroke-linejoin="round" d="M16 2.5l15.5 27H.5z"/>
181 + </g>
182 +</symbol><symbol id="icon-a-ic-shape" viewBox="0 0 24 24">
183 + <g fill="none" fill-rule="evenodd">
184 + <path fill="#434343" d="M14.706 8H21a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H9a1 1 0 0 1-1-1v-4h1v4h12V9h-5.706l-.588-1z"/>
185 + <path stroke="#434343" stroke-linecap="round" stroke-linejoin="round" d="M8.5 1.5l7.5 13H1z"/>
186 + </g>
187 +</symbol><symbol id="icon-a-ic-text-align-center" viewBox="0 0 32 32">
188 + <g fill="none" fill-rule="evenodd">
189 + <path d="M0 0h32v32H0z"/>
190 + <path fill="#434343" d="M2 5h28v1H2zM8 12h16v1H8zM2 19h28v1H2zM8 26h16v1H8z"/>
191 + </g>
192 +</symbol><symbol id="icon-a-ic-text-align-left" viewBox="0 0 32 32">
193 + <g fill="none" fill-rule="evenodd">
194 + <path d="M0 0h32v32H0z"/>
195 + <path fill="#434343" d="M2 5h28v1H2zM2 12h16v1H2zM2 19h28v1H2zM2 26h16v1H2z"/>
196 + </g>
197 +</symbol><symbol id="icon-a-ic-text-align-right" viewBox="0 0 32 32">
198 + <g fill="none" fill-rule="evenodd">
199 + <path d="M0 0h32v32H0z"/>
200 + <path fill="#434343" d="M2 5h28v1H2zM14 12h16v1H14zM2 19h28v1H2zM14 26h16v1H14z"/>
201 + </g>
202 +</symbol><symbol id="icon-a-ic-text-bold" viewBox="0 0 32 32">
203 + <g fill="none" fill-rule="evenodd">
204 + <path d="M0 0h32v32H0z"/>
205 + <path fill="#434343" d="M7 2h2v2H7zM7 28h2v2H7z"/>
206 + <path stroke="#434343" stroke-width="2" d="M9 3v12h9a6 6 0 1 0 0-12H9zM9 15v14h10a7 7 0 0 0 0-14H9z"/>
207 + </g>
208 +</symbol><symbol id="icon-a-ic-text-italic" viewBox="0 0 32 32">
209 + <g fill="none" fill-rule="evenodd">
210 + <path d="M0 0h32v32H0z"/>
211 + <path fill="#434343" d="M15 2h5v1h-5zM11 29h5v1h-5zM17 3h1l-4 26h-1z"/>
212 + </g>
213 +</symbol><symbol id="icon-a-ic-text-underline" viewBox="0 0 32 32">
214 + <g fill="none" fill-rule="evenodd">
215 + <path d="M0 0h32v32H0z"/>
216 + <path fill="#434343" d="M8 2v14a8 8 0 1 0 16 0V2h1v14a9 9 0 0 1-18 0V2h1zM3 29h26v1H3z"/>
217 + <path fill="#434343" d="M5 2h5v1H5zM22 2h5v1h-5z"/>
218 + </g>
219 +</symbol><symbol id="icon-a-ic-text" viewBox="0 0 24 24">
220 + <g fill="#434343" fill-rule="evenodd">
221 + <path d="M4 3h15a1 1 0 0 1 1 1H3a1 1 0 0 1 1-1zM3 4h1v1H3zM19 4h1v1h-1z"/>
222 + <path d="M11 3h1v18h-1z"/>
223 + <path d="M10 20h3v1h-3z"/>
224 + </g>
225 +</symbol><symbol id="icon-a-ic-undo" viewBox="0 0 24 24">
226 + <g fill="none" fill-rule="evenodd">
227 + <path d="M24 0H0v24h24z" opacity=".5"/>
228 + <path fill="#434343" d="M3 6h12a6 6 0 1 1 0 12H3v1h12a7 7 0 0 0 0-14H3v1z"/>
229 + <path stroke="#434343" stroke-linecap="square" d="M5 3L2.5 5.5 5 8"/>
230 + </g>
231 +</symbol><symbol id="icon-a-img-bi" viewBox="0 0 257 26">
232 + <g fill="#FDBA3B">
233 + <path d="M26 5a8.001 8.001 0 0 0 0 16 8.001 8.001 0 0 0 0-16M51.893 19.812L43.676 5.396A.78.78 0 0 0 43 5a.78.78 0 0 0-.677.396l-8.218 14.418a.787.787 0 0 0 0 .792c.14.244.396.394.676.394h16.436c.28 0 .539-.15.678-.396a.796.796 0 0 0-.002-.792M15.767 5.231A.79.79 0 0 0 15.21 5H.791A.791.791 0 0 0 0 5.79v6.42a.793.793 0 0 0 .791.79h3.21v7.21c.001.21.082.408.234.56.147.148.347.23.558.23h6.416a.788.788 0 0 0 .792-.79V13h3.006c.413 0 .611-.082.762-.232.15-.149.23-.35.231-.559V5.791a.787.787 0 0 0-.233-.56M85.767 5.231A.79.79 0 0 0 85.21 5H70.791a.791.791 0 0 0-.791.79v6.42a.793.793 0 0 0 .791.79h3.21v7.21c.001.21.082.408.234.56.147.148.347.23.558.23h6.416a.788.788 0 0 0 .792-.79V13h3.006c.413 0 .611-.082.762-.232.15-.149.23-.35.231-.559V5.791a.787.787 0 0 0-.233-.56M65.942 9.948l2.17-3.76a.78.78 0 0 0 0-.792.791.791 0 0 0-.684-.396h-8.54A5.889 5.889 0 0 0 53 10.86a5.887 5.887 0 0 0 3.07 5.17l-2.184 3.782A.792.792 0 0 0 54.571 21h8.54a5.89 5.89 0 0 0 2.831-11.052M105.7 21h2.3V5h-2.3zM91 5h2.4v10.286c0 1.893 1.612 3.429 3.6 3.429s3.6-1.536 3.6-3.429V5h2.4v10.286c0 3.156-2.686 5.714-6 5.714-3.313 0-6-2.558-6-5.714V5zM252.148 21.128h-2.377V9.659h2.27v1.64c.69-1.299 1.792-1.938 3.304-1.938.497 0 .95.065 1.382.192l-.215 2.277a3.734 3.734 0 0 0-1.275-.213c-1.814 0-3.089 1.234-3.089 3.638v5.873zm-7.095-5.744a3.734 3.734 0 0 0-1.101-2.703c-.714-.766-1.6-1.149-2.658-1.149-1.058 0-1.944.383-2.679 1.149a3.803 3.803 0 0 0-1.08 2.703c0 1.063.368 1.978 1.08 2.722.735.746 1.62 1.128 2.68 1.128 1.058 0 1.943-.382 2.657-1.128.734-.744 1.101-1.659 1.101-2.722zm-9.916 0c0-1.682.583-3.086 1.729-4.256 1.166-1.17 2.635-1.767 4.428-1.767 1.793 0 3.262.597 4.407 1.767 1.167 1.17 1.75 2.574 1.75 4.256 0 1.7-.583 3.127-1.75 4.297-1.145 1.17-2.614 1.745-4.407 1.745-1.793 0-3.262-.575-4.428-1.745-1.146-1.17-1.729-2.596-1.729-4.297zm-1.5 3.233l.821 1.83c-.864.638-1.944.958-3.22.958-2.526 0-3.822-1.554-3.822-4.383V11.66h-2.01v-2h2.031V5.595h2.355v4.063h4.018v2h-4.018v5.405c0 1.469.605 2.191 1.793 2.191.626 0 1.318-.212 2.052-.638zm-12.43 2.51h2.375V9.66h-2.376v11.469zm1.23-12.977c-.929 0-1.642-.682-1.642-1.596 0-.873.713-1.554 1.643-1.554.885 0 1.576.681 1.576 1.554 0 .914-.69 1.596-1.576 1.596zm-6.49 7.234c0-1.086-.346-1.98-1.037-2.724-.692-.745-1.599-1.128-2.7-1.128-1.102 0-2.01.383-2.7 1.128-.692.744-1.037 1.638-1.037 2.724 0 1.084.345 2.02 1.036 2.766.691.744 1.6 1.105 2.7 1.105 1.102 0 2.01-.361 2.7-1.105.692-.746 1.038-1.682 1.038-2.766zm-.173-4.129V5h2.397v16.128h-2.354v-1.596c-1.015 1.255-2.333 1.873-3.91 1.873-1.663 0-3.068-.575-4.169-1.724-1.102-1.17-1.663-2.596-1.663-4.297 0-1.682.561-3.107 1.663-4.256 1.101-1.17 2.485-1.745 4.148-1.745 1.534 0 2.83.617 3.888 1.872zm-11.48 9.873h-10.218V5.405h10.195v2.318h-7.711V12h7.15v2.32h-7.15v4.489h7.733v2.319zm-23.891-9.724c-1.793 0-3.132 1.192-3.478 2.979h6.783c-.194-1.808-1.555-2.979-3.305-2.979zm5.703 3.766c0 .32-.021.703-.086 1.128h-9.095c.346 1.787 1.62 3 3.867 3 1.318 0 2.916-.49 3.953-1.234l.994 1.724c-1.189.872-3.067 1.595-5.033 1.595-4.364 0-6.243-3-6.243-6.021 0-1.724.54-3.15 1.642-4.277 1.101-1.127 2.548-1.702 4.298-1.702 1.664 0 3.046.511 4.105 1.553 1.058 1.043 1.598 2.447 1.598 4.234zm-19.949 3.894c1.08 0 1.966-.362 2.68-1.085.712-.724 1.058-1.617 1.058-2.703 0-1.084-.346-2-1.059-2.701-.713-.702-1.599-1.064-2.679-1.064-1.058 0-1.944.362-2.656 1.085-.714.702-1.059 1.596-1.059 2.68 0 1.086.345 2 1.059 2.724.712.702 1.598 1.064 2.656 1.064zm3.673-7.936V9.66h2.29v10.299c0 1.85-.584 3.32-1.728 4.404-1.146 1.085-2.68 1.638-4.58 1.638-1.945 0-3.672-.553-5.206-1.638l1.037-1.808c1.296.915 2.679 1.36 4.126 1.36 2.484 0 3.996-1.51 3.996-3.637v-.83c-1.015 1.127-2.311 1.702-3.91 1.702-1.684 0-3.089-.554-4.19-1.68-1.102-1.128-1.642-2.532-1.642-4.214 0-1.68.561-3.085 1.706-4.191 1.145-1.128 2.571-1.681 4.234-1.681 1.534 0 2.83.575 3.867 1.745zm-18.07 8.127c1.102 0 1.988-.382 2.7-1.128.714-.744 1.06-1.659 1.06-2.743 0-1.065-.346-1.98-1.06-2.724-.712-.745-1.598-1.128-2.7-1.128-1.101 0-2.008.383-2.7 1.128-.691.744-1.036 1.66-1.036 2.745 0 1.084.345 2 1.037 2.745.691.744 1.598 1.105 2.7 1.105zm3.652-8V9.66h2.29v11.469h-2.29v-1.575c-1.059 1.234-2.399 1.852-3.976 1.852-1.663 0-3.067-.575-4.168-1.745-1.102-1.17-1.642-2.617-1.642-4.34 0-1.724.54-3.128 1.642-4.256 1.1-1.128 2.505-1.681 4.168-1.681 1.577 0 2.917.617 3.976 1.872zM138.79 9.34c1.404 0 2.527.448 3.37 1.34.863.873 1.295 2.086 1.295 3.596v6.852h-2.376V14.66c0-2.021-1.036-3.128-2.657-3.128-1.727 0-2.915 1.255-2.915 3.192v6.404h-2.377v-6.426c0-1.978-1.037-3.17-2.679-3.17-1.728 0-2.937 1.277-2.937 3.234v6.362h-2.377V9.659h2.333v1.66c.692-1.212 1.988-1.979 3.522-1.979 1.533.021 2.958.767 3.586 2.107.798-1.277 2.419-2.107 4.212-2.107zm-19.517 11.788h2.484V5.405h-2.484v15.723z"/>
234 + </g>
235 +</symbol></svg>
...\ No newline at end of file ...\ No newline at end of file
1 +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs/><symbol id="icon-b-ic-apply" viewBox="0 0 24 24">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M0 0h24v24H0z"/>
4 + <path stroke="#555555" d="M4 12.011l5 5L20.011 6"/>
5 + </g>
6 +</symbol><symbol id="icon-b-ic-cancel" viewBox="0 0 24 24">
7 + <g fill="none" fill-rule="evenodd">
8 + <path d="M0 0h24v24H0z"/>
9 + <path stroke="#555555" d="M6 6l12 12M18 6L6 18"/>
10 + </g>
11 +</symbol><symbol id="icon-b-ic-crop" viewBox="0 0 24 24">
12 + <g fill="none" fill-rule="evenodd">
13 + <path d="M0 0h24v24H0z"/>
14 + <path fill="#555555" d="M4 0h1v20a1 1 0 0 1-1-1V0zM20 17h-1V5h1v12zm0 2v5h-1v-5h1z"/>
15 + <path fill="#555555" d="M5 19h19v1H5zM4.762 4v1H0V4h4.762zM7 4h12a1 1 0 0 1 1 1H7V4z"/>
16 + </g>
17 +</symbol><symbol id="icon-b-ic-delete-all" viewBox="0 0 24 24">
18 + <g fill="#555555" fill-rule="evenodd">
19 + <path d="M5 23H3a1 1 0 0 1-1-1V6h1v16h2v1zm16-10h-1V6h1v7zM9 13H8v-3h1v3zm3 0h-1v-3h1v3zm3 0h-1v-3h1v3zM14.794 3.794L13 2h-3L8.206 3.794A.963.963 0 0 1 8 2.5l.703-1.055A1 1 0 0 1 9.535 1h3.93a1 1 0 0 1 .832.445L15 2.5a.965.965 0 0 1-.206 1.294zM14.197 4H8.803h5.394z"/>
20 + <path d="M0 3h23v1H0zM11.286 21H8.714L8 23H7l1-2.8V20h.071L9.5 16h1l1.429 4H12v.2l1 2.8h-1l-.714-2zm-.357-1L10 17.4 9.071 20h1.858zM20 22h3v1h-4v-7h1v6zm-5 0h3v1h-4v-7h1v6z"/>
21 + </g>
22 +</symbol><symbol id="icon-b-ic-delete" viewBox="0 0 24 24">
23 + <g fill="#555555" fill-rule="evenodd">
24 + <path d="M3 6v16h17V6h1v16a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V6h1zM14.794 3.794L13 2h-3L8.206 3.794A.963.963 0 0 1 8 2.5l.703-1.055A1 1 0 0 1 9.535 1h3.93a1 1 0 0 1 .832.445L15 2.5a.965.965 0 0 1-.206 1.294zM14.197 4H8.803h5.394z"/>
25 + <path d="M0 3h23v1H0zM8 10h1v6H8v-6zm3 0h1v6h-1v-6zm3 0h1v6h-1v-6z"/>
26 + </g>
27 +</symbol><symbol id="icon-b-ic-draw-free" viewBox="0 0 32 32">
28 + <g fill="none" fill-rule="evenodd">
29 + <path stroke="#555555" d="M2.5 20.929C2.594 10.976 4.323 6 7.686 6c5.872 0 2.524 19 7.697 19s1.89-14.929 6.414-14.929 1.357 10.858 5.13 10.858c1.802 0 2.657-2.262 2.566-6.786"/>
30 + </g>
31 +</symbol><symbol id="icon-b-ic-draw-line" viewBox="0 0 32 32">
32 + <g fill="none" fill-rule="evenodd">
33 + <path stroke="#555555" d="M2 15.5h28"/>
34 + </g>
35 +</symbol><symbol id="icon-b-ic-draw" viewBox="0 0 24 24">
36 + <g fill="none">
37 + <path stroke="#555555" d="M2.5 21.5H5c.245 0 .48-.058.691-.168l.124-.065.14.01c.429.028.85-.127 1.16-.437L22.55 5.405a.5.5 0 0 0 0-.707l-3.246-3.245a.5.5 0 0 0-.707 0L3.162 16.888a1.495 1.495 0 0 0-.437 1.155l.01.14-.065.123c-.111.212-.17.448-.17.694v2.5z"/>
38 + <path fill="#555555" d="M16.414 3.707l3.89 3.89-.708.706-3.889-3.889z"/>
39 + </g>
40 +</symbol><symbol id="icon-b-ic-filter" viewBox="0 0 24 24">
41 + <g fill="none" fill-rule="evenodd">
42 + <path d="M0 0h24v24H0z"/>
43 + <path fill="#555555" d="M12 7v1H2V7h10zm6 0h4v1h-4V7zM12 16v1h10v-1H12zm-6 0H2v1h4v-1z"/>
44 + <path fill="#555555" d="M8.5 20a3.5 3.5 0 1 1 0-7 3.5 3.5 0 0 1 0 7zm0-1a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5zM15.5 11a3.5 3.5 0 1 1 0-7 3.5 3.5 0 0 1 0 7zm0-1a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5z"/>
45 + </g>
46 +</symbol><symbol id="icon-b-ic-flip-reset" viewBox="0 0 31 32">
47 + <g fill="none" fill-rule="evenodd">
48 + <path d="M31 0H0v32h31z"/>
49 + <path fill="#555555" d="M28 16a8 8 0 0 1-8 8H3v-1h1v-7H3a8 8 0 0 1 8-8h17v1h-1v7h1zM11 9a7 7 0 0 0-7 7v7h16a7 7 0 0 0 7-7V9H11z"/>
50 + <path stroke="#555555" stroke-linecap="square" d="M24 5l3.5 3.5L24 12M7 20l-3.5 3.5L7 27"/>
51 + </g>
52 +</symbol><symbol id="icon-b-ic-flip-x" viewBox="0 0 32 32">
53 + <g fill="none" fill-rule="evenodd">
54 + <path d="M32 32H0V0h32z"/>
55 + <path fill="#555555" d="M17 32h-1V0h1zM27.167 11l.5 3h-1.03l-.546-3h1.076zm-.5-3h-1.122L25 5h-5V4h5.153a1 1 0 0 1 .986.836L26.667 8zm1.5 9l.5 3h-.94l-.545-3h.985zm1 6l.639 3.836A1 1 0 0 1 28.819 28H26v-1h3l-.726-4h.894zM23 28h-3v-1h3v1zM13 4v1H7L3 27h10v1H3.18a1 1 0 0 1-.986-1.164l3.666-22A1 1 0 0 1 6.847 4H13z"/>
56 + </g>
57 +</symbol><symbol id="icon-b-ic-flip-y" viewBox="0 0 32 32">
58 + <g fill="none" fill-rule="evenodd">
59 + <path d="M0 0v32h32V0z"/>
60 + <path fill="#555555" d="M0 16v1h32v-1zM11 27.167l3 .5v-1.03l-3-.546v1.076zm-3-.5v-1.122L5 25v-5H4v5.153a1 1 0 0 0 .836.986L8 26.667zm9 1.5l3 .5v-.94l-3-.545v.985zm6 1l3.836.639A1 1 0 0 0 28 28.82V26h-1v3l-4-.727v.894zM28 23v-3h-1v3h1zM4 13h1V7l22-4v10h1V3.18a1 1 0 0 0-1.164-.986l-22 3.667A1 1 0 0 0 4 6.847V13z"/>
61 + </g>
62 +</symbol><symbol id="icon-b-ic-flip" viewBox="0 0 24 24">
63 + <g fill="none" fill-rule="evenodd">
64 + <path d="M0 0h24v24H0z"/>
65 + <path fill="#555555" d="M11 0h1v24h-1zM19 21v-1h2v-2h1v2a1 1 0 0 1-1 1h-2zm-2 0h-3v-1h3v1zm5-5h-1v-3h1v3zm0-5h-1V8h1v3zm0-5h-1V4h-2V3h2a1 1 0 0 1 1 1v2zm-5-3v1h-3V3h3zM9 3v1H2v16h7v1H2a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h7z"/>
66 + </g>
67 +</symbol><symbol id="icon-b-ic-icon-arrow-2" viewBox="0 0 32 32">
68 + <g fill="none" fill-rule="evenodd">
69 + <path stroke="#555555" stroke-linecap="round" stroke-linejoin="round" d="M21.793 18.5H2.5v-5h18.935l-7.6-8h5.872l10.5 10.5-10.5 10.5h-5.914l8-8z"/>
70 + </g>
71 +</symbol><symbol id="icon-b-ic-icon-arrow-3" viewBox="0 0 32 32">
72 + <g fill="none" fill-rule="evenodd">
73 + <path stroke="#555555" stroke-linecap="round" stroke-linejoin="round" d="M25.288 16.42L14.208 27.5H6.792l11.291-11.291L6.826 4.5h7.381l11.661 11.661-.58.258z"/>
74 + </g>
75 +</symbol><symbol id="icon-b-ic-icon-arrow" viewBox="0 0 32 32">
76 + <g fill="none" fill-rule="evenodd">
77 + <path stroke="#555555" d="M2.5 11.5v9h18v5.293L30.293 16 20.5 6.207V11.5h-18z"/>
78 + </g>
79 +</symbol><symbol id="icon-b-ic-icon-bubble" viewBox="0 0 32 32">
80 + <g fill="none" fill-rule="evenodd">
81 + <path stroke="#555555" stroke-linecap="round" stroke-linejoin="round" d="M22.207 24.5L16.5 30.207V24.5H8A6.5 6.5 0 0 1 1.5 18V9A6.5 6.5 0 0 1 8 2.5h16A6.5 6.5 0 0 1 30.5 9v9a6.5 6.5 0 0 1-6.5 6.5h-1.793z"/>
82 + </g>
83 +</symbol><symbol id="icon-b-ic-icon-heart" viewBox="0 0 32 32">
84 + <g fill="none" fill-rule="evenodd">
85 + <path fill-rule="nonzero" stroke="#555555" d="M15.996 30.675l1.981-1.79c7.898-7.177 10.365-9.718 12.135-13.012.922-1.716 1.377-3.37 1.377-5.076 0-4.65-3.647-8.297-8.297-8.297-2.33 0-4.86 1.527-6.817 3.824l-.38.447-.381-.447C13.658 4.027 11.126 2.5 8.797 2.5 4.147 2.5.5 6.147.5 10.797c0 1.714.46 3.375 1.389 5.098 1.775 3.288 4.26 5.843 12.123 12.974l1.984 1.806z"/>
86 + </g>
87 +</symbol><symbol id="icon-b-ic-icon-load" viewBox="0 0 32 32">
88 + <g fill="none" fill-rule="evenodd">
89 + <path stroke="#555555" stroke-linecap="round" stroke-linejoin="round" d="M17.314 18.867l1.951-2.53 4 5.184h-17l6.5-8.84 4.549 6.186z"/>
90 + <path fill="#555555" d="M18.01 4a11.798 11.798 0 0 0 0 1H3v24h24V14.986a8.738 8.738 0 0 0 1 0V29a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h15.01z"/>
91 + <path fill="#555555" d="M25 3h1v9h-1z"/>
92 + <path stroke="#555555" d="M22 6l3.5-3.5L29 6"/>
93 + </g>
94 +</symbol><symbol id="icon-b-ic-icon-location" viewBox="0 0 32 32">
95 + <g fill="none" fill-rule="evenodd">
96 + <g stroke="#555555">
97 + <path d="M16 31.28C23.675 23.302 27.5 17.181 27.5 13c0-6.351-5.149-11.5-11.5-11.5S4.5 6.649 4.5 13c0 4.181 3.825 10.302 11.5 18.28z"/>
98 + <circle cx="16" cy="13" r="4.5"/>
99 + </g>
100 + </g>
101 +</symbol><symbol id="icon-b-ic-icon-polygon" viewBox="0 0 32 32">
102 + <g fill="none" fill-rule="evenodd">
103 + <path stroke="#555555" d="M.576 16L8.29 29.5h15.42L31.424 16 23.71 2.5H8.29L.576 16z"/>
104 + </g>
105 +</symbol><symbol id="icon-b-ic-icon-star-2" viewBox="0 0 32 32">
106 + <g fill="none" fill-rule="evenodd">
107 + <path stroke="#555555" d="M19.446 31.592l2.265-3.272 3.946.25.636-3.94 3.665-1.505-1.12-3.832 2.655-2.962-2.656-2.962 1.12-3.832-3.664-1.505-.636-3.941-3.946.25-2.265-3.271L16 3.024 12.554 1.07 10.289 4.34l-3.946-.25-.636 3.941-3.665 1.505 1.12 3.832L.508 16.33l2.656 2.962-1.12 3.832 3.664 1.504.636 3.942 3.946-.25 2.265 3.27L16 29.638l3.446 1.955z"/>
108 + </g>
109 +</symbol><symbol id="icon-b-ic-icon-star" viewBox="0 0 32 32">
110 + <g fill="none" fill-rule="evenodd">
111 + <path stroke="#555555" d="M25.292 29.878l-1.775-10.346 7.517-7.327-10.388-1.51L16 1.282l-4.646 9.413-10.388 1.51 7.517 7.327-1.775 10.346L16 24.993l9.292 4.885z"/>
112 + </g>
113 +</symbol><symbol id="icon-b-ic-icon" viewBox="0 0 24 24">
114 + <g fill="none">
115 + <path stroke="#555555" stroke-linecap="round" stroke-linejoin="round" d="M11.923 19.136L5.424 22l.715-7.065-4.731-5.296 6.94-1.503L11.923 2l3.574 6.136 6.94 1.503-4.731 5.296L18.42 22z"/>
116 + </g>
117 +</symbol><symbol id="icon-b-ic-mask-load" viewBox="0 0 32 32">
118 + <g fill="none" fill-rule="evenodd">
119 + <path d="M0 0h32v32H0z"/>
120 + <path fill="#555555" d="M18.01 4a11.798 11.798 0 0 0 0 1H3v24h24V14.986a8.738 8.738 0 0 0 1 0V29a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h15.01zM15 23a6 6 0 1 1 0-12 6 6 0 0 1 0 12zm0-1a5 5 0 1 0 0-10 5 5 0 0 0 0 10z"/>
121 + <path fill="#555555" d="M25 3h1v9h-1z"/>
122 + <path stroke="#555555" d="M22 6l3.5-3.5L29 6"/>
123 + </g>
124 +</symbol><symbol id="icon-b-ic-mask" viewBox="0 0 24 24">
125 + <g fill="none">
126 + <circle cx="12" cy="12" r="4.5" stroke="#555555"/>
127 + <path fill="#555555" d="M2 1h20a1 1 0 0 1 1 1v20a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1zm0 1v20h20V2H2z"/>
128 + </g>
129 +</symbol><symbol id="icon-b-ic-redo" viewBox="0 0 24 24">
130 + <g fill="none" fill-rule="evenodd">
131 + <path d="M0 0h24v24H0z" opacity=".5"/>
132 + <path fill="#555555" d="M21 6H9a6 6 0 1 0 0 12h12v1H9A7 7 0 0 1 9 5h12v1z"/>
133 + <path stroke="#555555" stroke-linecap="square" d="M19 3l2.5 2.5L19 8"/>
134 + </g>
135 +</symbol><symbol id="icon-b-ic-reset" viewBox="0 0 24 24">
136 + <g fill="none" fill-rule="evenodd">
137 + <path d="M0 0h24v24H0z" opacity=".5"/>
138 + <path fill="#555555" d="M2 13v-1a7 7 0 0 1 7-7h13v1h-1v5h1v1a7 7 0 0 1-7 7H2v-1h1v-5H2zm7-7a6 6 0 0 0-6 6v6h12a6 6 0 0 0 6-6V6H9z"/>
139 + <path stroke="#555555" stroke-linecap="square" d="M19 3l2.5 2.5L19 8M5 16l-2.5 2.5L5 21"/>
140 + </g>
141 +</symbol><symbol id="icon-b-ic-rotate-clockwise" viewBox="0 0 32 32">
142 + <g fill="none" fill-rule="evenodd">
143 + <path fill="#555555" d="M29 17h-.924c0 6.627-5.373 12-12 12-6.628 0-12-5.373-12-12C4.076 10.398 9.407 5.041 16 5V4C8.82 4 3 9.82 3 17s5.82 13 13 13 13-5.82 13-13z"/>
144 + <path stroke="#555555" stroke-linecap="square" d="M16 1.5l4 3-4 3"/>
145 + <path fill="#555555" fill-rule="nonzero" d="M16 4h4v1h-4z"/>
146 + </g>
147 +</symbol><symbol id="icon-b-ic-rotate-counterclockwise" viewBox="0 0 32 32">
148 + <g fill="none" fill-rule="evenodd">
149 + <path fill="#555555" d="M3 17h.924c0 6.627 5.373 12 12 12 6.628 0 12-5.373 12-12 0-6.602-5.331-11.96-11.924-12V4c7.18 0 13 5.82 13 13s-5.82 13-13 13S3 24.18 3 17z"/>
150 + <path fill="#555555" fill-rule="nonzero" d="M12 4h4v1h-4z"/>
151 + <path stroke="#555555" stroke-linecap="square" d="M16 1.5l-4 3 4 3"/>
152 + </g>
153 +</symbol><symbol id="icon-b-ic-rotate" viewBox="0 0 24 24">
154 + <g fill="none" fill-rule="evenodd">
155 + <path d="M0 0h24v24H0z"/>
156 + <path fill="#555555" d="M8.349 22.254a10.002 10.002 0 0 1-2.778-1.719l.65-.76a9.002 9.002 0 0 0 2.495 1.548l-.367.931zm2.873.704l.078-.997a9 9 0 1 0-.557-17.852l-.14-.99A10.076 10.076 0 0 1 12.145 3c5.523 0 10 4.477 10 10s-4.477 10-10 10c-.312 0-.62-.014-.924-.042zm-7.556-4.655a9.942 9.942 0 0 1-1.253-2.996l.973-.234a8.948 8.948 0 0 0 1.124 2.693l-.844.537zm-1.502-5.91A9.949 9.949 0 0 1 2.88 9.23l.925.382a8.954 8.954 0 0 0-.644 2.844l-.998-.062zm2.21-5.686c.687-.848 1.51-1.58 2.436-2.166l.523.852a9.048 9.048 0 0 0-2.188 1.95l-.771-.636z"/>
157 + <path stroke="#555555" stroke-linecap="square" d="M13 1l-2.5 2.5L13 6"/>
158 + </g>
159 +</symbol><symbol id="icon-b-ic-shape-circle" viewBox="0 0 32 32">
160 + <g fill="none" fill-rule="evenodd">
161 + <circle cx="16" cy="16" r="14.5" stroke="#555555"/>
162 + </g>
163 +</symbol><symbol id="icon-b-ic-shape-rectangle" viewBox="0 0 32 32">
164 + <g fill="none" fill-rule="evenodd">
165 + <rect width="27" height="27" x="2.5" y="2.5" stroke="#555555" rx="1"/>
166 + </g>
167 +</symbol><symbol id="icon-b-ic-shape-triangle" viewBox="0 0 32 32">
168 + <g fill="none" fill-rule="evenodd">
169 + <path stroke="#555555" stroke-linecap="round" stroke-linejoin="round" d="M16 2.5l15.5 27H.5z"/>
170 + </g>
171 +</symbol><symbol id="icon-b-ic-shape" viewBox="0 0 24 24">
172 + <g fill="none" fill-rule="evenodd">
173 + <path fill="#555555" d="M14.706 8H21a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H9a1 1 0 0 1-1-1v-4h1v4h12V9h-5.706l-.588-1z"/>
174 + <path stroke="#555555" stroke-linecap="round" stroke-linejoin="round" d="M8.5 1.5l7.5 13H1z"/>
175 + </g>
176 +</symbol><symbol id="icon-b-ic-text-align-center" viewBox="0 0 32 32">
177 + <g fill="none" fill-rule="evenodd">
178 + <path d="M0 0h32v32H0z"/>
179 + <path fill="#555555" d="M2 5h28v1H2zM8 12h16v1H8zM2 19h28v1H2zM8 26h16v1H8z"/>
180 + </g>
181 +</symbol><symbol id="icon-b-ic-text-align-left" viewBox="0 0 32 32">
182 + <g fill="none" fill-rule="evenodd">
183 + <path d="M0 0h32v32H0z"/>
184 + <path fill="#555555" d="M2 5h28v1H2zM2 12h16v1H2zM2 19h28v1H2zM2 26h16v1H2z"/>
185 + </g>
186 +</symbol><symbol id="icon-b-ic-text-align-right" viewBox="0 0 32 32">
187 + <g fill="none" fill-rule="evenodd">
188 + <path d="M0 0h32v32H0z"/>
189 + <path fill="#555555" d="M2 5h28v1H2zM14 12h16v1H14zM2 19h28v1H2zM14 26h16v1H14z"/>
190 + </g>
191 +</symbol><symbol id="icon-b-ic-text-bold" viewBox="0 0 32 32">
192 + <g fill="none" fill-rule="evenodd">
193 + <path d="M0 0h32v32H0z"/>
194 + <path fill="#555555" d="M7 2h2v2H7zM7 28h2v2H7z"/>
195 + <path stroke="#555555" stroke-width="2" d="M9 3v12h9a6 6 0 1 0 0-12H9zM9 15v14h10a7 7 0 0 0 0-14H9z"/>
196 + </g>
197 +</symbol><symbol id="icon-b-ic-text-italic" viewBox="0 0 32 32">
198 + <g fill="none" fill-rule="evenodd">
199 + <path d="M0 0h32v32H0z"/>
200 + <path fill="#555555" d="M15 2h5v1h-5zM11 29h5v1h-5zM17 3h1l-4 26h-1z"/>
201 + </g>
202 +</symbol><symbol id="icon-b-ic-text-underline" viewBox="0 0 32 32">
203 + <g fill="none" fill-rule="evenodd">
204 + <path d="M0 0h32v32H0z"/>
205 + <path fill="#555555" d="M8 2v14a8 8 0 1 0 16 0V2h1v14a9 9 0 0 1-18 0V2h1zM3 29h26v1H3z"/>
206 + <path fill="#555555" d="M5 2h5v1H5zM22 2h5v1h-5z"/>
207 + </g>
208 +</symbol><symbol id="icon-b-ic-text" viewBox="0 0 24 24">
209 + <g fill="#555555" fill-rule="evenodd">
210 + <path d="M4 3h15a1 1 0 0 1 1 1H3a1 1 0 0 1 1-1zM3 4h1v1H3zM19 4h1v1h-1z"/>
211 + <path d="M11 3h1v18h-1z"/>
212 + <path d="M10 20h3v1h-3z"/>
213 + </g>
214 +</symbol><symbol id="icon-b-ic-undo" viewBox="0 0 24 24">
215 + <g fill="none" fill-rule="evenodd">
216 + <path d="M24 0H0v24h24z" opacity=".5"/>
217 + <path fill="#555555" d="M3 6h12a6 6 0 1 1 0 12H3v1h12a7 7 0 0 0 0-14H3v1z"/>
218 + <path stroke="#555555" stroke-linecap="square" d="M5 3L2.5 5.5 5 8"/>
219 + </g>
220 +</symbol><symbol id="icon-b-img-bi" viewBox="0 0 257 26">
221 + <g fill="#FDBA3B">
222 + <path d="M26 5a8.001 8.001 0 0 0 0 16 8.001 8.001 0 0 0 0-16M51.893 19.812L43.676 5.396A.78.78 0 0 0 43 5a.78.78 0 0 0-.677.396l-8.218 14.418a.787.787 0 0 0 0 .792c.14.244.396.394.676.394h16.436c.28 0 .539-.15.678-.396a.796.796 0 0 0-.002-.792M15.767 5.231A.79.79 0 0 0 15.21 5H.791A.791.791 0 0 0 0 5.79v6.42a.793.793 0 0 0 .791.79h3.21v7.21c.001.21.082.408.234.56.147.148.347.23.558.23h6.416a.788.788 0 0 0 .792-.79V13h3.006c.413 0 .611-.082.762-.232.15-.149.23-.35.231-.559V5.791a.787.787 0 0 0-.233-.56M85.767 5.231A.79.79 0 0 0 85.21 5H70.791a.791.791 0 0 0-.791.79v6.42a.793.793 0 0 0 .791.79h3.21v7.21c.001.21.082.408.234.56.147.148.347.23.558.23h6.416a.788.788 0 0 0 .792-.79V13h3.006c.413 0 .611-.082.762-.232.15-.149.23-.35.231-.559V5.791a.787.787 0 0 0-.233-.56M65.942 9.948l2.17-3.76a.78.78 0 0 0 0-.792.791.791 0 0 0-.684-.396h-8.54A5.889 5.889 0 0 0 53 10.86a5.887 5.887 0 0 0 3.07 5.17l-2.184 3.782A.792.792 0 0 0 54.571 21h8.54a5.89 5.89 0 0 0 2.831-11.052M105.7 21h2.3V5h-2.3zM91 5h2.4v10.286c0 1.893 1.612 3.429 3.6 3.429s3.6-1.536 3.6-3.429V5h2.4v10.286c0 3.156-2.686 5.714-6 5.714-3.313 0-6-2.558-6-5.714V5zM252.148 21.128h-2.377V9.659h2.27v1.64c.69-1.299 1.792-1.938 3.304-1.938.497 0 .95.065 1.382.192l-.215 2.277a3.734 3.734 0 0 0-1.275-.213c-1.814 0-3.089 1.234-3.089 3.638v5.873zm-7.095-5.744a3.734 3.734 0 0 0-1.101-2.703c-.714-.766-1.6-1.149-2.658-1.149-1.058 0-1.944.383-2.679 1.149a3.803 3.803 0 0 0-1.08 2.703c0 1.063.368 1.978 1.08 2.722.735.746 1.62 1.128 2.68 1.128 1.058 0 1.943-.382 2.657-1.128.734-.744 1.101-1.659 1.101-2.722zm-9.916 0c0-1.682.583-3.086 1.729-4.256 1.166-1.17 2.635-1.767 4.428-1.767 1.793 0 3.262.597 4.407 1.767 1.167 1.17 1.75 2.574 1.75 4.256 0 1.7-.583 3.127-1.75 4.297-1.145 1.17-2.614 1.745-4.407 1.745-1.793 0-3.262-.575-4.428-1.745-1.146-1.17-1.729-2.596-1.729-4.297zm-1.5 3.233l.821 1.83c-.864.638-1.944.958-3.22.958-2.526 0-3.822-1.554-3.822-4.383V11.66h-2.01v-2h2.031V5.595h2.355v4.063h4.018v2h-4.018v5.405c0 1.469.605 2.191 1.793 2.191.626 0 1.318-.212 2.052-.638zm-12.43 2.51h2.375V9.66h-2.376v11.469zm1.23-12.977c-.929 0-1.642-.682-1.642-1.596 0-.873.713-1.554 1.643-1.554.885 0 1.576.681 1.576 1.554 0 .914-.69 1.596-1.576 1.596zm-6.49 7.234c0-1.086-.346-1.98-1.037-2.724-.692-.745-1.599-1.128-2.7-1.128-1.102 0-2.01.383-2.7 1.128-.692.744-1.037 1.638-1.037 2.724 0 1.084.345 2.02 1.036 2.766.691.744 1.6 1.105 2.7 1.105 1.102 0 2.01-.361 2.7-1.105.692-.746 1.038-1.682 1.038-2.766zm-.173-4.129V5h2.397v16.128h-2.354v-1.596c-1.015 1.255-2.333 1.873-3.91 1.873-1.663 0-3.068-.575-4.169-1.724-1.102-1.17-1.663-2.596-1.663-4.297 0-1.682.561-3.107 1.663-4.256 1.101-1.17 2.485-1.745 4.148-1.745 1.534 0 2.83.617 3.888 1.872zm-11.48 9.873h-10.218V5.405h10.195v2.318h-7.711V12h7.15v2.32h-7.15v4.489h7.733v2.319zm-23.891-9.724c-1.793 0-3.132 1.192-3.478 2.979h6.783c-.194-1.808-1.555-2.979-3.305-2.979zm5.703 3.766c0 .32-.021.703-.086 1.128h-9.095c.346 1.787 1.62 3 3.867 3 1.318 0 2.916-.49 3.953-1.234l.994 1.724c-1.189.872-3.067 1.595-5.033 1.595-4.364 0-6.243-3-6.243-6.021 0-1.724.54-3.15 1.642-4.277 1.101-1.127 2.548-1.702 4.298-1.702 1.664 0 3.046.511 4.105 1.553 1.058 1.043 1.598 2.447 1.598 4.234zm-19.949 3.894c1.08 0 1.966-.362 2.68-1.085.712-.724 1.058-1.617 1.058-2.703 0-1.084-.346-2-1.059-2.701-.713-.702-1.599-1.064-2.679-1.064-1.058 0-1.944.362-2.656 1.085-.714.702-1.059 1.596-1.059 2.68 0 1.086.345 2 1.059 2.724.712.702 1.598 1.064 2.656 1.064zm3.673-7.936V9.66h2.29v10.299c0 1.85-.584 3.32-1.728 4.404-1.146 1.085-2.68 1.638-4.58 1.638-1.945 0-3.672-.553-5.206-1.638l1.037-1.808c1.296.915 2.679 1.36 4.126 1.36 2.484 0 3.996-1.51 3.996-3.637v-.83c-1.015 1.127-2.311 1.702-3.91 1.702-1.684 0-3.089-.554-4.19-1.68-1.102-1.128-1.642-2.532-1.642-4.214 0-1.68.561-3.085 1.706-4.191 1.145-1.128 2.571-1.681 4.234-1.681 1.534 0 2.83.575 3.867 1.745zm-18.07 8.127c1.102 0 1.988-.382 2.7-1.128.714-.744 1.06-1.659 1.06-2.743 0-1.065-.346-1.98-1.06-2.724-.712-.745-1.598-1.128-2.7-1.128-1.101 0-2.008.383-2.7 1.128-.691.744-1.036 1.66-1.036 2.745 0 1.084.345 2 1.037 2.745.691.744 1.598 1.105 2.7 1.105zm3.652-8V9.66h2.29v11.469h-2.29v-1.575c-1.059 1.234-2.399 1.852-3.976 1.852-1.663 0-3.067-.575-4.168-1.745-1.102-1.17-1.642-2.617-1.642-4.34 0-1.724.54-3.128 1.642-4.256 1.1-1.128 2.505-1.681 4.168-1.681 1.577 0 2.917.617 3.976 1.872zM138.79 9.34c1.404 0 2.527.448 3.37 1.34.863.873 1.295 2.086 1.295 3.596v6.852h-2.376V14.66c0-2.021-1.036-3.128-2.657-3.128-1.727 0-2.915 1.255-2.915 3.192v6.404h-2.377v-6.426c0-1.978-1.037-3.17-2.679-3.17-1.728 0-2.937 1.277-2.937 3.234v6.362h-2.377V9.659h2.333v1.66c.692-1.212 1.988-1.979 3.522-1.979 1.533.021 2.958.767 3.586 2.107.798-1.277 2.419-2.107 4.212-2.107zm-19.517 11.788h2.484V5.405h-2.484v15.723z"/>
223 + </g>
224 +</symbol></svg>
1 +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs/><symbol id="icon-c-ic-apply" viewBox="0 0 24 24">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M0 0h24v24H0z"/>
4 + <path stroke="#e9e9e9" d="M4 12.011l5 5L20.011 6"/>
5 + </g>
6 +</symbol><symbol id="icon-c-ic-cancel" viewBox="0 0 24 24">
7 + <g fill="none" fill-rule="evenodd">
8 + <path d="M0 0h24v24H0z"/>
9 + <path stroke="#e9e9e9" d="M6 6l12 12M18 6L6 18"/>
10 + </g>
11 +</symbol><symbol id="icon-c-ic-crop" viewBox="0 0 24 24">
12 + <g fill="none" fill-rule="evenodd">
13 + <path d="M0 0h24v24H0z"/>
14 + <path fill="#e9e9e9" d="M4 0h1v20a1 1 0 0 1-1-1V0zM20 17h-1V5h1v12zm0 2v5h-1v-5h1z"/>
15 + <path fill="#e9e9e9" d="M5 19h19v1H5zM4.762 4v1H0V4h4.762zM7 4h12a1 1 0 0 1 1 1H7V4z"/>
16 + </g>
17 +</symbol><symbol id="icon-c-ic-delete-all" viewBox="0 0 24 24">
18 + <g fill="#e9e9e9" fill-rule="evenodd">
19 + <path d="M5 23H3a1 1 0 0 1-1-1V6h1v16h2v1zm16-10h-1V6h1v7zM9 13H8v-3h1v3zm3 0h-1v-3h1v3zm3 0h-1v-3h1v3zM14.794 3.794L13 2h-3L8.206 3.794A.963.963 0 0 1 8 2.5l.703-1.055A1 1 0 0 1 9.535 1h3.93a1 1 0 0 1 .832.445L15 2.5a.965.965 0 0 1-.206 1.294zM14.197 4H8.803h5.394z"/>
20 + <path d="M0 3h23v1H0zM11.286 21H8.714L8 23H7l1-2.8V20h.071L9.5 16h1l1.429 4H12v.2l1 2.8h-1l-.714-2zm-.357-1L10 17.4 9.071 20h1.858zM20 22h3v1h-4v-7h1v6zm-5 0h3v1h-4v-7h1v6z"/>
21 + </g>
22 +</symbol><symbol id="icon-c-ic-delete" viewBox="0 0 24 24">
23 + <g fill="#e9e9e9" fill-rule="evenodd">
24 + <path d="M3 6v16h17V6h1v16a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V6h1zM14.794 3.794L13 2h-3L8.206 3.794A.963.963 0 0 1 8 2.5l.703-1.055A1 1 0 0 1 9.535 1h3.93a1 1 0 0 1 .832.445L15 2.5a.965.965 0 0 1-.206 1.294zM14.197 4H8.803h5.394z"/>
25 + <path d="M0 3h23v1H0zM8 10h1v6H8v-6zm3 0h1v6h-1v-6zm3 0h1v6h-1v-6z"/>
26 + </g>
27 +</symbol><symbol id="icon-c-ic-draw-free" viewBox="0 0 32 32">
28 + <g fill="none" fill-rule="evenodd">
29 + <path stroke="#e9e9e9" d="M2.5 20.929C2.594 10.976 4.323 6 7.686 6c5.872 0 2.524 19 7.697 19s1.89-14.929 6.414-14.929 1.357 10.858 5.13 10.858c1.802 0 2.657-2.262 2.566-6.786"/>
30 + </g>
31 +</symbol><symbol id="icon-c-ic-draw-line" viewBox="0 0 32 32">
32 + <g fill="none" fill-rule="evenodd">
33 + <path stroke="#e9e9e9" d="M2 15.5h28"/>
34 + </g>
35 +</symbol><symbol id="icon-c-ic-draw" viewBox="0 0 24 24">
36 + <g fill="none">
37 + <path stroke="#e9e9e9" d="M2.5 21.5H5c.245 0 .48-.058.691-.168l.124-.065.14.01c.429.028.85-.127 1.16-.437L22.55 5.405a.5.5 0 0 0 0-.707l-3.246-3.245a.5.5 0 0 0-.707 0L3.162 16.888a1.495 1.495 0 0 0-.437 1.155l.01.14-.065.123c-.111.212-.17.448-.17.694v2.5z"/>
38 + <path fill="#e9e9e9" d="M16.414 3.707l3.89 3.89-.708.706-3.889-3.889z"/>
39 + </g>
40 +</symbol><symbol id="icon-c-ic-filter" viewBox="0 0 24 24">
41 + <g fill="none" fill-rule="evenodd">
42 + <path d="M0 0h24v24H0z"/>
43 + <path fill="#e9e9e9" d="M12 7v1H2V7h10zm6 0h4v1h-4V7zM12 16v1h10v-1H12zm-6 0H2v1h4v-1z"/>
44 + <path fill="#e9e9e9" d="M8.5 20a3.5 3.5 0 1 1 0-7 3.5 3.5 0 0 1 0 7zm0-1a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5zM15.5 11a3.5 3.5 0 1 1 0-7 3.5 3.5 0 0 1 0 7zm0-1a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5z"/>
45 + </g>
46 +</symbol><symbol id="icon-c-ic-flip-reset" viewBox="0 0 31 32">
47 + <g fill="none" fill-rule="evenodd">
48 + <path d="M31 0H0v32h31z"/>
49 + <path fill="#e9e9e9" d="M28 16a8 8 0 0 1-8 8H3v-1h1v-7H3a8 8 0 0 1 8-8h17v1h-1v7h1zM11 9a7 7 0 0 0-7 7v7h16a7 7 0 0 0 7-7V9H11z"/>
50 + <path stroke="#e9e9e9" stroke-linecap="square" d="M24 5l3.5 3.5L24 12M7 20l-3.5 3.5L7 27"/>
51 + </g>
52 +</symbol><symbol id="icon-c-ic-flip-x" viewBox="0 0 32 32">
53 + <g fill="none" fill-rule="evenodd">
54 + <path d="M32 32H0V0h32z"/>
55 + <path fill="#e9e9e9" d="M17 32h-1V0h1zM27.167 11l.5 3h-1.03l-.546-3h1.076zm-.5-3h-1.122L25 5h-5V4h5.153a1 1 0 0 1 .986.836L26.667 8zm1.5 9l.5 3h-.94l-.545-3h.985zm1 6l.639 3.836A1 1 0 0 1 28.819 28H26v-1h3l-.726-4h.894zM23 28h-3v-1h3v1zM13 4v1H7L3 27h10v1H3.18a1 1 0 0 1-.986-1.164l3.666-22A1 1 0 0 1 6.847 4H13z"/>
56 + </g>
57 +</symbol><symbol id="icon-c-ic-flip-y" viewBox="0 0 32 32">
58 + <g fill="none" fill-rule="evenodd">
59 + <path d="M0 0v32h32V0z"/>
60 + <path fill="#e9e9e9" d="M0 16v1h32v-1zM11 27.167l3 .5v-1.03l-3-.546v1.076zm-3-.5v-1.122L5 25v-5H4v5.153a1 1 0 0 0 .836.986L8 26.667zm9 1.5l3 .5v-.94l-3-.545v.985zm6 1l3.836.639A1 1 0 0 0 28 28.82V26h-1v3l-4-.727v.894zM28 23v-3h-1v3h1zM4 13h1V7l22-4v10h1V3.18a1 1 0 0 0-1.164-.986l-22 3.667A1 1 0 0 0 4 6.847V13z"/>
61 + </g>
62 +</symbol><symbol id="icon-c-ic-flip" viewBox="0 0 24 24">
63 + <g fill="none" fill-rule="evenodd">
64 + <path d="M0 0h24v24H0z"/>
65 + <path fill="#e9e9e9" d="M11 0h1v24h-1zM19 21v-1h2v-2h1v2a1 1 0 0 1-1 1h-2zm-2 0h-3v-1h3v1zm5-5h-1v-3h1v3zm0-5h-1V8h1v3zm0-5h-1V4h-2V3h2a1 1 0 0 1 1 1v2zm-5-3v1h-3V3h3zM9 3v1H2v16h7v1H2a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h7z"/>
66 + </g>
67 +</symbol><symbol id="icon-c-ic-icon-arrow-2" viewBox="0 0 32 32">
68 + <g fill="none" fill-rule="evenodd">
69 + <path stroke="#e9e9e9" stroke-linecap="round" stroke-linejoin="round" d="M21.793 18.5H2.5v-5h18.935l-7.6-8h5.872l10.5 10.5-10.5 10.5h-5.914l8-8z"/>
70 + </g>
71 +</symbol><symbol id="icon-c-ic-icon-arrow-3" viewBox="0 0 32 32">
72 + <g fill="none" fill-rule="evenodd">
73 + <path stroke="#e9e9e9" stroke-linecap="round" stroke-linejoin="round" d="M25.288 16.42L14.208 27.5H6.792l11.291-11.291L6.826 4.5h7.381l11.661 11.661-.58.258z"/>
74 + </g>
75 +</symbol><symbol id="icon-c-ic-icon-arrow" viewBox="0 0 32 32">
76 + <g fill="none" fill-rule="evenodd">
77 + <path stroke="#e9e9e9" d="M2.5 11.5v9h18v5.293L30.293 16 20.5 6.207V11.5h-18z"/>
78 + </g>
79 +</symbol><symbol id="icon-c-ic-icon-bubble" viewBox="0 0 32 32">
80 + <g fill="none" fill-rule="evenodd">
81 + <path stroke="#e9e9e9" stroke-linecap="round" stroke-linejoin="round" d="M22.207 24.5L16.5 30.207V24.5H8A6.5 6.5 0 0 1 1.5 18V9A6.5 6.5 0 0 1 8 2.5h16A6.5 6.5 0 0 1 30.5 9v9a6.5 6.5 0 0 1-6.5 6.5h-1.793z"/>
82 + </g>
83 +</symbol><symbol id="icon-c-ic-icon-heart" viewBox="0 0 32 32">
84 + <g fill="none" fill-rule="evenodd">
85 + <path fill-rule="nonzero" stroke="#e9e9e9" d="M15.996 30.675l1.981-1.79c7.898-7.177 10.365-9.718 12.135-13.012.922-1.716 1.377-3.37 1.377-5.076 0-4.65-3.647-8.297-8.297-8.297-2.33 0-4.86 1.527-6.817 3.824l-.38.447-.381-.447C13.658 4.027 11.126 2.5 8.797 2.5 4.147 2.5.5 6.147.5 10.797c0 1.714.46 3.375 1.389 5.098 1.775 3.288 4.26 5.843 12.123 12.974l1.984 1.806z"/>
86 + </g>
87 +</symbol><symbol id="icon-c-ic-icon-load" viewBox="0 0 32 32">
88 + <g fill="none" fill-rule="evenodd">
89 + <path stroke="#e9e9e9" stroke-linecap="round" stroke-linejoin="round" d="M17.314 18.867l1.951-2.53 4 5.184h-17l6.5-8.84 4.549 6.186z"/>
90 + <path fill="#e9e9e9" d="M18.01 4a11.798 11.798 0 0 0 0 1H3v24h24V14.986a8.738 8.738 0 0 0 1 0V29a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h15.01z"/>
91 + <path fill="#e9e9e9" d="M25 3h1v9h-1z"/>
92 + <path stroke="#e9e9e9" d="M22 6l3.5-3.5L29 6"/>
93 + </g>
94 +</symbol><symbol id="icon-c-ic-icon-location" viewBox="0 0 32 32">
95 + <g fill="none" fill-rule="evenodd">
96 + <g stroke="#e9e9e9">
97 + <path d="M16 31.28C23.675 23.302 27.5 17.181 27.5 13c0-6.351-5.149-11.5-11.5-11.5S4.5 6.649 4.5 13c0 4.181 3.825 10.302 11.5 18.28z"/>
98 + <circle cx="16" cy="13" r="4.5"/>
99 + </g>
100 + </g>
101 +</symbol><symbol id="icon-c-ic-icon-polygon" viewBox="0 0 32 32">
102 + <g fill="none" fill-rule="evenodd">
103 + <path stroke="#e9e9e9" d="M.576 16L8.29 29.5h15.42L31.424 16 23.71 2.5H8.29L.576 16z"/>
104 + </g>
105 +</symbol><symbol id="icon-c-ic-icon-star-2" viewBox="0 0 32 32">
106 + <g fill="none" fill-rule="evenodd">
107 + <path stroke="#e9e9e9" d="M19.446 31.592l2.265-3.272 3.946.25.636-3.94 3.665-1.505-1.12-3.832 2.655-2.962-2.656-2.962 1.12-3.832-3.664-1.505-.636-3.941-3.946.25-2.265-3.271L16 3.024 12.554 1.07 10.289 4.34l-3.946-.25-.636 3.941-3.665 1.505 1.12 3.832L.508 16.33l2.656 2.962-1.12 3.832 3.664 1.504.636 3.942 3.946-.25 2.265 3.27L16 29.638l3.446 1.955z"/>
108 + </g>
109 +</symbol><symbol id="icon-c-ic-icon-star" viewBox="0 0 32 32">
110 + <g fill="none" fill-rule="evenodd">
111 + <path stroke="#e9e9e9" d="M25.292 29.878l-1.775-10.346 7.517-7.327-10.388-1.51L16 1.282l-4.646 9.413-10.388 1.51 7.517 7.327-1.775 10.346L16 24.993l9.292 4.885z"/>
112 + </g>
113 +</symbol><symbol id="icon-c-ic-icon" viewBox="0 0 24 24">
114 + <g fill="none">
115 + <path stroke="#e9e9e9" stroke-linecap="round" stroke-linejoin="round" d="M11.923 19.136L5.424 22l.715-7.065-4.731-5.296 6.94-1.503L11.923 2l3.574 6.136 6.94 1.503-4.731 5.296L18.42 22z"/>
116 + </g>
117 +</symbol><symbol id="icon-c-ic-mask-load" viewBox="0 0 32 32">
118 + <g fill="none" fill-rule="evenodd">
119 + <path d="M0 0h32v32H0z"/>
120 + <path fill="#e9e9e9" d="M18.01 4a11.798 11.798 0 0 0 0 1H3v24h24V14.986a8.738 8.738 0 0 0 1 0V29a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h15.01zM15 23a6 6 0 1 1 0-12 6 6 0 0 1 0 12zm0-1a5 5 0 1 0 0-10 5 5 0 0 0 0 10z"/>
121 + <path fill="#e9e9e9" d="M25 3h1v9h-1z"/>
122 + <path stroke="#e9e9e9" d="M22 6l3.5-3.5L29 6"/>
123 + </g>
124 +</symbol><symbol id="icon-c-ic-mask" viewBox="0 0 24 24">
125 + <g fill="none">
126 + <circle cx="12" cy="12" r="4.5" stroke="#e9e9e9"/>
127 + <path fill="#e9e9e9" d="M2 1h20a1 1 0 0 1 1 1v20a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1zm0 1v20h20V2H2z"/>
128 + </g>
129 +</symbol><symbol id="icon-c-ic-redo" viewBox="0 0 24 24">
130 + <g fill="none" fill-rule="evenodd">
131 + <path d="M0 0h24v24H0z" opacity=".5"/>
132 + <path fill="#e9e9e9" d="M21 6H9a6 6 0 1 0 0 12h12v1H9A7 7 0 0 1 9 5h12v1z"/>
133 + <path stroke="#e9e9e9" stroke-linecap="square" d="M19 3l2.5 2.5L19 8"/>
134 + </g>
135 +</symbol><symbol id="icon-c-ic-reset" viewBox="0 0 24 24">
136 + <g fill="none" fill-rule="evenodd">
137 + <path d="M0 0h24v24H0z" opacity=".5"/>
138 + <path fill="#e9e9e9" d="M2 13v-1a7 7 0 0 1 7-7h13v1h-1v5h1v1a7 7 0 0 1-7 7H2v-1h1v-5H2zm7-7a6 6 0 0 0-6 6v6h12a6 6 0 0 0 6-6V6H9z"/>
139 + <path stroke="#e9e9e9" stroke-linecap="square" d="M19 3l2.5 2.5L19 8M5 16l-2.5 2.5L5 21"/>
140 + </g>
141 +</symbol><symbol id="icon-c-ic-rotate-clockwise" viewBox="0 0 32 32">
142 + <g fill="none" fill-rule="evenodd">
143 + <path fill="#e9e9e9" d="M29 17h-.924c0 6.627-5.373 12-12 12-6.628 0-12-5.373-12-12C4.076 10.398 9.407 5.041 16 5V4C8.82 4 3 9.82 3 17s5.82 13 13 13 13-5.82 13-13z"/>
144 + <path stroke="#e9e9e9" stroke-linecap="square" d="M16 1.5l4 3-4 3"/>
145 + <path fill="#e9e9e9" fill-rule="nonzero" d="M16 4h4v1h-4z"/>
146 + </g>
147 +</symbol><symbol id="icon-c-ic-rotate-counterclockwise" viewBox="0 0 32 32">
148 + <g fill="none" fill-rule="evenodd">
149 + <path fill="#e9e9e9" d="M3 17h.924c0 6.627 5.373 12 12 12 6.628 0 12-5.373 12-12 0-6.602-5.331-11.96-11.924-12V4c7.18 0 13 5.82 13 13s-5.82 13-13 13S3 24.18 3 17z"/>
150 + <path fill="#e9e9e9" fill-rule="nonzero" d="M12 4h4v1h-4z"/>
151 + <path stroke="#e9e9e9" stroke-linecap="square" d="M16 1.5l-4 3 4 3"/>
152 + </g>
153 +</symbol><symbol id="icon-c-ic-rotate" viewBox="0 0 24 24">
154 + <g fill="none" fill-rule="evenodd">
155 + <path d="M0 0h24v24H0z"/>
156 + <path fill="#e9e9e9" d="M8.349 22.254a10.002 10.002 0 0 1-2.778-1.719l.65-.76a9.002 9.002 0 0 0 2.495 1.548l-.367.931zm2.873.704l.078-.997a9 9 0 1 0-.557-17.852l-.14-.99A10.076 10.076 0 0 1 12.145 3c5.523 0 10 4.477 10 10s-4.477 10-10 10c-.312 0-.62-.014-.924-.042zm-7.556-4.655a9.942 9.942 0 0 1-1.253-2.996l.973-.234a8.948 8.948 0 0 0 1.124 2.693l-.844.537zm-1.502-5.91A9.949 9.949 0 0 1 2.88 9.23l.925.382a8.954 8.954 0 0 0-.644 2.844l-.998-.062zm2.21-5.686c.687-.848 1.51-1.58 2.436-2.166l.523.852a9.048 9.048 0 0 0-2.188 1.95l-.771-.636z"/>
157 + <path stroke="#e9e9e9" stroke-linecap="square" d="M13 1l-2.5 2.5L13 6"/>
158 + </g>
159 +</symbol><symbol id="icon-c-ic-shape-circle" viewBox="0 0 32 32">
160 + <g fill="none" fill-rule="evenodd">
161 + <circle cx="16" cy="16" r="14.5" stroke="#e9e9e9"/>
162 + </g>
163 +</symbol><symbol id="icon-c-ic-shape-rectangle" viewBox="0 0 32 32">
164 + <g fill="none" fill-rule="evenodd">
165 + <rect width="27" height="27" x="2.5" y="2.5" stroke="#e9e9e9" rx="1"/>
166 + </g>
167 +</symbol><symbol id="icon-c-ic-shape-triangle" viewBox="0 0 32 32">
168 + <g fill="none" fill-rule="evenodd">
169 + <path stroke="#e9e9e9" stroke-linecap="round" stroke-linejoin="round" d="M16 2.5l15.5 27H.5z"/>
170 + </g>
171 +</symbol><symbol id="icon-c-ic-shape" viewBox="0 0 24 24">
172 + <g fill="none" fill-rule="evenodd">
173 + <path fill="#e9e9e9" d="M14.706 8H21a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H9a1 1 0 0 1-1-1v-4h1v4h12V9h-5.706l-.588-1z"/>
174 + <path stroke="#e9e9e9" stroke-linecap="round" stroke-linejoin="round" d="M8.5 1.5l7.5 13H1z"/>
175 + </g>
176 +</symbol><symbol id="icon-c-ic-text-align-center" viewBox="0 0 32 32">
177 + <g fill="none" fill-rule="evenodd">
178 + <path d="M0 0h32v32H0z"/>
179 + <path fill="#e9e9e9" d="M2 5h28v1H2zM8 12h16v1H8zM2 19h28v1H2zM8 26h16v1H8z"/>
180 + </g>
181 +</symbol><symbol id="icon-c-ic-text-align-left" viewBox="0 0 32 32">
182 + <g fill="none" fill-rule="evenodd">
183 + <path d="M0 0h32v32H0z"/>
184 + <path fill="#e9e9e9" d="M2 5h28v1H2zM2 12h16v1H2zM2 19h28v1H2zM2 26h16v1H2z"/>
185 + </g>
186 +</symbol><symbol id="icon-c-ic-text-align-right" viewBox="0 0 32 32">
187 + <g fill="none" fill-rule="evenodd">
188 + <path d="M0 0h32v32H0z"/>
189 + <path fill="#e9e9e9" d="M2 5h28v1H2zM14 12h16v1H14zM2 19h28v1H2zM14 26h16v1H14z"/>
190 + </g>
191 +</symbol><symbol id="icon-c-ic-text-bold" viewBox="0 0 32 32">
192 + <g fill="none" fill-rule="evenodd">
193 + <path d="M0 0h32v32H0z"/>
194 + <path fill="#e9e9e9" d="M7 2h2v2H7zM7 28h2v2H7z"/>
195 + <path stroke="#e9e9e9" stroke-width="2" d="M9 3v12h9a6 6 0 1 0 0-12H9zM9 15v14h10a7 7 0 0 0 0-14H9z"/>
196 + </g>
197 +</symbol><symbol id="icon-c-ic-text-italic" viewBox="0 0 32 32">
198 + <g fill="none" fill-rule="evenodd">
199 + <path d="M0 0h32v32H0z"/>
200 + <path fill="#e9e9e9" d="M15 2h5v1h-5zM11 29h5v1h-5zM17 3h1l-4 26h-1z"/>
201 + </g>
202 +</symbol><symbol id="icon-c-ic-text-underline" viewBox="0 0 32 32">
203 + <g fill="none" fill-rule="evenodd">
204 + <path d="M0 0h32v32H0z"/>
205 + <path fill="#e9e9e9" d="M8 2v14a8 8 0 1 0 16 0V2h1v14a9 9 0 0 1-18 0V2h1zM3 29h26v1H3z"/>
206 + <path fill="#e9e9e9" d="M5 2h5v1H5zM22 2h5v1h-5z"/>
207 + </g>
208 +</symbol><symbol id="icon-c-ic-text" viewBox="0 0 24 24">
209 + <g fill="#e9e9e9" fill-rule="evenodd">
210 + <path d="M4 3h15a1 1 0 0 1 1 1H3a1 1 0 0 1 1-1zM3 4h1v1H3zM19 4h1v1h-1z"/>
211 + <path d="M11 3h1v18h-1z"/>
212 + <path d="M10 20h3v1h-3z"/>
213 + </g>
214 +</symbol><symbol id="icon-c-ic-undo" viewBox="0 0 24 24">
215 + <g fill="none" fill-rule="evenodd">
216 + <path d="M24 0H0v24h24z" opacity=".5"/>
217 + <path fill="#e9e9e9" d="M3 6h12a6 6 0 1 1 0 12H3v1h12a7 7 0 0 0 0-14H3v1z"/>
218 + <path stroke="#e9e9e9" stroke-linecap="square" d="M5 3L2.5 5.5 5 8"/>
219 + </g>
220 +</symbol><symbol id="icon-c-img-bi" viewBox="0 0 257 26">
221 + <g fill="#FDBA3B">
222 + <path d="M26 5a8.001 8.001 0 0 0 0 16 8.001 8.001 0 0 0 0-16M51.893 19.812L43.676 5.396A.78.78 0 0 0 43 5a.78.78 0 0 0-.677.396l-8.218 14.418a.787.787 0 0 0 0 .792c.14.244.396.394.676.394h16.436c.28 0 .539-.15.678-.396a.796.796 0 0 0-.002-.792M15.767 5.231A.79.79 0 0 0 15.21 5H.791A.791.791 0 0 0 0 5.79v6.42a.793.793 0 0 0 .791.79h3.21v7.21c.001.21.082.408.234.56.147.148.347.23.558.23h6.416a.788.788 0 0 0 .792-.79V13h3.006c.413 0 .611-.082.762-.232.15-.149.23-.35.231-.559V5.791a.787.787 0 0 0-.233-.56M85.767 5.231A.79.79 0 0 0 85.21 5H70.791a.791.791 0 0 0-.791.79v6.42a.793.793 0 0 0 .791.79h3.21v7.21c.001.21.082.408.234.56.147.148.347.23.558.23h6.416a.788.788 0 0 0 .792-.79V13h3.006c.413 0 .611-.082.762-.232.15-.149.23-.35.231-.559V5.791a.787.787 0 0 0-.233-.56M65.942 9.948l2.17-3.76a.78.78 0 0 0 0-.792.791.791 0 0 0-.684-.396h-8.54A5.889 5.889 0 0 0 53 10.86a5.887 5.887 0 0 0 3.07 5.17l-2.184 3.782A.792.792 0 0 0 54.571 21h8.54a5.89 5.89 0 0 0 2.831-11.052M105.7 21h2.3V5h-2.3zM91 5h2.4v10.286c0 1.893 1.612 3.429 3.6 3.429s3.6-1.536 3.6-3.429V5h2.4v10.286c0 3.156-2.686 5.714-6 5.714-3.313 0-6-2.558-6-5.714V5zM252.148 21.128h-2.377V9.659h2.27v1.64c.69-1.299 1.792-1.938 3.304-1.938.497 0 .95.065 1.382.192l-.215 2.277a3.734 3.734 0 0 0-1.275-.213c-1.814 0-3.089 1.234-3.089 3.638v5.873zm-7.095-5.744a3.734 3.734 0 0 0-1.101-2.703c-.714-.766-1.6-1.149-2.658-1.149-1.058 0-1.944.383-2.679 1.149a3.803 3.803 0 0 0-1.08 2.703c0 1.063.368 1.978 1.08 2.722.735.746 1.62 1.128 2.68 1.128 1.058 0 1.943-.382 2.657-1.128.734-.744 1.101-1.659 1.101-2.722zm-9.916 0c0-1.682.583-3.086 1.729-4.256 1.166-1.17 2.635-1.767 4.428-1.767 1.793 0 3.262.597 4.407 1.767 1.167 1.17 1.75 2.574 1.75 4.256 0 1.7-.583 3.127-1.75 4.297-1.145 1.17-2.614 1.745-4.407 1.745-1.793 0-3.262-.575-4.428-1.745-1.146-1.17-1.729-2.596-1.729-4.297zm-1.5 3.233l.821 1.83c-.864.638-1.944.958-3.22.958-2.526 0-3.822-1.554-3.822-4.383V11.66h-2.01v-2h2.031V5.595h2.355v4.063h4.018v2h-4.018v5.405c0 1.469.605 2.191 1.793 2.191.626 0 1.318-.212 2.052-.638zm-12.43 2.51h2.375V9.66h-2.376v11.469zm1.23-12.977c-.929 0-1.642-.682-1.642-1.596 0-.873.713-1.554 1.643-1.554.885 0 1.576.681 1.576 1.554 0 .914-.69 1.596-1.576 1.596zm-6.49 7.234c0-1.086-.346-1.98-1.037-2.724-.692-.745-1.599-1.128-2.7-1.128-1.102 0-2.01.383-2.7 1.128-.692.744-1.037 1.638-1.037 2.724 0 1.084.345 2.02 1.036 2.766.691.744 1.6 1.105 2.7 1.105 1.102 0 2.01-.361 2.7-1.105.692-.746 1.038-1.682 1.038-2.766zm-.173-4.129V5h2.397v16.128h-2.354v-1.596c-1.015 1.255-2.333 1.873-3.91 1.873-1.663 0-3.068-.575-4.169-1.724-1.102-1.17-1.663-2.596-1.663-4.297 0-1.682.561-3.107 1.663-4.256 1.101-1.17 2.485-1.745 4.148-1.745 1.534 0 2.83.617 3.888 1.872zm-11.48 9.873h-10.218V5.405h10.195v2.318h-7.711V12h7.15v2.32h-7.15v4.489h7.733v2.319zm-23.891-9.724c-1.793 0-3.132 1.192-3.478 2.979h6.783c-.194-1.808-1.555-2.979-3.305-2.979zm5.703 3.766c0 .32-.021.703-.086 1.128h-9.095c.346 1.787 1.62 3 3.867 3 1.318 0 2.916-.49 3.953-1.234l.994 1.724c-1.189.872-3.067 1.595-5.033 1.595-4.364 0-6.243-3-6.243-6.021 0-1.724.54-3.15 1.642-4.277 1.101-1.127 2.548-1.702 4.298-1.702 1.664 0 3.046.511 4.105 1.553 1.058 1.043 1.598 2.447 1.598 4.234zm-19.949 3.894c1.08 0 1.966-.362 2.68-1.085.712-.724 1.058-1.617 1.058-2.703 0-1.084-.346-2-1.059-2.701-.713-.702-1.599-1.064-2.679-1.064-1.058 0-1.944.362-2.656 1.085-.714.702-1.059 1.596-1.059 2.68 0 1.086.345 2 1.059 2.724.712.702 1.598 1.064 2.656 1.064zm3.673-7.936V9.66h2.29v10.299c0 1.85-.584 3.32-1.728 4.404-1.146 1.085-2.68 1.638-4.58 1.638-1.945 0-3.672-.553-5.206-1.638l1.037-1.808c1.296.915 2.679 1.36 4.126 1.36 2.484 0 3.996-1.51 3.996-3.637v-.83c-1.015 1.127-2.311 1.702-3.91 1.702-1.684 0-3.089-.554-4.19-1.68-1.102-1.128-1.642-2.532-1.642-4.214 0-1.68.561-3.085 1.706-4.191 1.145-1.128 2.571-1.681 4.234-1.681 1.534 0 2.83.575 3.867 1.745zm-18.07 8.127c1.102 0 1.988-.382 2.7-1.128.714-.744 1.06-1.659 1.06-2.743 0-1.065-.346-1.98-1.06-2.724-.712-.745-1.598-1.128-2.7-1.128-1.101 0-2.008.383-2.7 1.128-.691.744-1.036 1.66-1.036 2.745 0 1.084.345 2 1.037 2.745.691.744 1.598 1.105 2.7 1.105zm3.652-8V9.66h2.29v11.469h-2.29v-1.575c-1.059 1.234-2.399 1.852-3.976 1.852-1.663 0-3.067-.575-4.168-1.745-1.102-1.17-1.642-2.617-1.642-4.34 0-1.724.54-3.128 1.642-4.256 1.1-1.128 2.505-1.681 4.168-1.681 1.577 0 2.917.617 3.976 1.872zM138.79 9.34c1.404 0 2.527.448 3.37 1.34.863.873 1.295 2.086 1.295 3.596v6.852h-2.376V14.66c0-2.021-1.036-3.128-2.657-3.128-1.727 0-2.915 1.255-2.915 3.192v6.404h-2.377v-6.426c0-1.978-1.037-3.17-2.679-3.17-1.728 0-2.937 1.277-2.937 3.234v6.362h-2.377V9.659h2.333v1.66c.692-1.212 1.988-1.979 3.522-1.979 1.533.021 2.958.767 3.586 2.107.798-1.277 2.419-2.107 4.212-2.107zm-19.517 11.788h2.484V5.405h-2.484v15.723z"/>
223 + </g>
224 +</symbol></svg>
...\ No newline at end of file ...\ No newline at end of file
1 +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs/><symbol id="icon-d-ic-apply" viewBox="0 0 24 24">
2 + <g fill="none" fill-rule="evenodd">
3 + <path d="M0 0h24v24H0z"/>
4 + <path stroke="#8a8a8a" d="M4 12.011l5 5L20.011 6"/>
5 + </g>
6 +</symbol><symbol id="icon-d-ic-cancel" viewBox="0 0 24 24">
7 + <g fill="none" fill-rule="evenodd">
8 + <path d="M0 0h24v24H0z"/>
9 + <path stroke="#8a8a8a" d="M6 6l12 12M18 6L6 18"/>
10 + </g>
11 +</symbol><symbol id="icon-d-ic-crop" viewBox="0 0 24 24">
12 + <g fill="none" fill-rule="evenodd">
13 + <path d="M0 0h24v24H0z"/>
14 + <path fill="#8a8a8a" d="M4 0h1v20a1 1 0 0 1-1-1V0zM20 17h-1V5h1v12zm0 2v5h-1v-5h1z"/>
15 + <path fill="#8a8a8a" d="M5 19h19v1H5zM4.762 4v1H0V4h4.762zM7 4h12a1 1 0 0 1 1 1H7V4z"/>
16 + </g>
17 +</symbol><symbol id="icon-d-ic-delete-all" viewBox="0 0 24 24">
18 + <g fill="#8a8a8a" fill-rule="evenodd">
19 + <path d="M5 23H3a1 1 0 0 1-1-1V6h1v16h2v1zm16-10h-1V6h1v7zM9 13H8v-3h1v3zm3 0h-1v-3h1v3zm3 0h-1v-3h1v3zM14.794 3.794L13 2h-3L8.206 3.794A.963.963 0 0 1 8 2.5l.703-1.055A1 1 0 0 1 9.535 1h3.93a1 1 0 0 1 .832.445L15 2.5a.965.965 0 0 1-.206 1.294zM14.197 4H8.803h5.394z"/>
20 + <path d="M0 3h23v1H0zM11.286 21H8.714L8 23H7l1-2.8V20h.071L9.5 16h1l1.429 4H12v.2l1 2.8h-1l-.714-2zm-.357-1L10 17.4 9.071 20h1.858zM20 22h3v1h-4v-7h1v6zm-5 0h3v1h-4v-7h1v6z"/>
21 + </g>
22 +</symbol><symbol id="icon-d-ic-delete" viewBox="0 0 24 24">
23 + <g fill="#8a8a8a" fill-rule="evenodd">
24 + <path d="M3 6v16h17V6h1v16a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V6h1zM14.794 3.794L13 2h-3L8.206 3.794A.963.963 0 0 1 8 2.5l.703-1.055A1 1 0 0 1 9.535 1h3.93a1 1 0 0 1 .832.445L15 2.5a.965.965 0 0 1-.206 1.294zM14.197 4H8.803h5.394z"/>
25 + <path d="M0 3h23v1H0zM8 10h1v6H8v-6zm3 0h1v6h-1v-6zm3 0h1v6h-1v-6z"/>
26 + </g>
27 +</symbol><symbol id="icon-d-ic-draw-free" viewBox="0 0 32 32">
28 + <g fill="none" fill-rule="evenodd">
29 + <path stroke="#8a8a8a" d="M2.5 20.929C2.594 10.976 4.323 6 7.686 6c5.872 0 2.524 19 7.697 19s1.89-14.929 6.414-14.929 1.357 10.858 5.13 10.858c1.802 0 2.657-2.262 2.566-6.786"/>
30 + </g>
31 +</symbol><symbol id="icon-d-ic-draw-line" viewBox="0 0 32 32">
32 + <g fill="none" fill-rule="evenodd">
33 + <path stroke="#8a8a8a" d="M2 15.5h28"/>
34 + </g>
35 +</symbol><symbol id="icon-d-ic-draw" viewBox="0 0 24 24">
36 + <g fill="none">
37 + <path stroke="#8a8a8a" d="M2.5 21.5H5c.245 0 .48-.058.691-.168l.124-.065.14.01c.429.028.85-.127 1.16-.437L22.55 5.405a.5.5 0 0 0 0-.707l-3.246-3.245a.5.5 0 0 0-.707 0L3.162 16.888a1.495 1.495 0 0 0-.437 1.155l.01.14-.065.123c-.111.212-.17.448-.17.694v2.5z"/>
38 + <path fill="#8a8a8a" d="M16.414 3.707l3.89 3.89-.708.706-3.889-3.889z"/>
39 + </g>
40 +</symbol><symbol id="icon-d-ic-filter" viewBox="0 0 24 24">
41 + <g fill="none" fill-rule="evenodd">
42 + <path d="M0 0h24v24H0z"/>
43 + <path fill="#8a8a8a" d="M12 7v1H2V7h10zm6 0h4v1h-4V7zM12 16v1h10v-1H12zm-6 0H2v1h4v-1z"/>
44 + <path fill="#8a8a8a" d="M8.5 20a3.5 3.5 0 1 1 0-7 3.5 3.5 0 0 1 0 7zm0-1a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5zM15.5 11a3.5 3.5 0 1 1 0-7 3.5 3.5 0 0 1 0 7zm0-1a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5z"/>
45 + </g>
46 +</symbol><symbol id="icon-d-ic-flip-reset" viewBox="0 0 31 32">
47 + <g fill="none" fill-rule="evenodd">
48 + <path d="M31 0H0v32h31z"/>
49 + <path fill="#8a8a8a" d="M28 16a8 8 0 0 1-8 8H3v-1h1v-7H3a8 8 0 0 1 8-8h17v1h-1v7h1zM11 9a7 7 0 0 0-7 7v7h16a7 7 0 0 0 7-7V9H11z"/>
50 + <path stroke="#8a8a8a" stroke-linecap="square" d="M24 5l3.5 3.5L24 12M7 20l-3.5 3.5L7 27"/>
51 + </g>
52 +</symbol><symbol id="icon-d-ic-flip-x" viewBox="0 0 32 32">
53 + <g fill="none" fill-rule="evenodd">
54 + <path d="M32 32H0V0h32z"/>
55 + <path fill="#8a8a8a" d="M17 32h-1V0h1zM27.167 11l.5 3h-1.03l-.546-3h1.076zm-.5-3h-1.122L25 5h-5V4h5.153a1 1 0 0 1 .986.836L26.667 8zm1.5 9l.5 3h-.94l-.545-3h.985zm1 6l.639 3.836A1 1 0 0 1 28.819 28H26v-1h3l-.726-4h.894zM23 28h-3v-1h3v1zM13 4v1H7L3 27h10v1H3.18a1 1 0 0 1-.986-1.164l3.666-22A1 1 0 0 1 6.847 4H13z"/>
56 + </g>
57 +</symbol><symbol id="icon-d-ic-flip-y" viewBox="0 0 32 32">
58 + <g fill="none" fill-rule="evenodd">
59 + <path d="M0 0v32h32V0z"/>
60 + <path fill="#8a8a8a" d="M0 16v1h32v-1zM11 27.167l3 .5v-1.03l-3-.546v1.076zm-3-.5v-1.122L5 25v-5H4v5.153a1 1 0 0 0 .836.986L8 26.667zm9 1.5l3 .5v-.94l-3-.545v.985zm6 1l3.836.639A1 1 0 0 0 28 28.82V26h-1v3l-4-.727v.894zM28 23v-3h-1v3h1zM4 13h1V7l22-4v10h1V3.18a1 1 0 0 0-1.164-.986l-22 3.667A1 1 0 0 0 4 6.847V13z"/>
61 + </g>
62 +</symbol><symbol id="icon-d-ic-flip" viewBox="0 0 24 24">
63 + <g fill="none" fill-rule="evenodd">
64 + <path d="M0 0h24v24H0z"/>
65 + <path fill="#8a8a8a" d="M11 0h1v24h-1zM19 21v-1h2v-2h1v2a1 1 0 0 1-1 1h-2zm-2 0h-3v-1h3v1zm5-5h-1v-3h1v3zm0-5h-1V8h1v3zm0-5h-1V4h-2V3h2a1 1 0 0 1 1 1v2zm-5-3v1h-3V3h3zM9 3v1H2v16h7v1H2a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h7z"/>
66 + </g>
67 +</symbol><symbol id="icon-d-ic-icon-arrow-2" viewBox="0 0 32 32">
68 + <g fill="none" fill-rule="evenodd">
69 + <path stroke="#8a8a8a" stroke-linecap="round" stroke-linejoin="round" d="M21.793 18.5H2.5v-5h18.935l-7.6-8h5.872l10.5 10.5-10.5 10.5h-5.914l8-8z"/>
70 + </g>
71 +</symbol><symbol id="icon-d-ic-icon-arrow-3" viewBox="0 0 32 32">
72 + <g fill="none" fill-rule="evenodd">
73 + <path stroke="#8a8a8a" stroke-linecap="round" stroke-linejoin="round" d="M25.288 16.42L14.208 27.5H6.792l11.291-11.291L6.826 4.5h7.381l11.661 11.661-.58.258z"/>
74 + </g>
75 +</symbol><symbol id="icon-d-ic-icon-arrow" viewBox="0 0 32 32">
76 + <g fill="none" fill-rule="evenodd">
77 + <path stroke="#8a8a8a" d="M2.5 11.5v9h18v5.293L30.293 16 20.5 6.207V11.5h-18z"/>
78 + </g>
79 +</symbol><symbol id="icon-d-ic-icon-bubble" viewBox="0 0 32 32">
80 + <g fill="none" fill-rule="evenodd">
81 + <path stroke="#8a8a8a" stroke-linecap="round" stroke-linejoin="round" d="M22.207 24.5L16.5 30.207V24.5H8A6.5 6.5 0 0 1 1.5 18V9A6.5 6.5 0 0 1 8 2.5h16A6.5 6.5 0 0 1 30.5 9v9a6.5 6.5 0 0 1-6.5 6.5h-1.793z"/>
82 + </g>
83 +</symbol><symbol id="icon-d-ic-icon-heart" viewBox="0 0 32 32">
84 + <g fill="none" fill-rule="evenodd">
85 + <path fill-rule="nonzero" stroke="#8a8a8a" d="M15.996 30.675l1.981-1.79c7.898-7.177 10.365-9.718 12.135-13.012.922-1.716 1.377-3.37 1.377-5.076 0-4.65-3.647-8.297-8.297-8.297-2.33 0-4.86 1.527-6.817 3.824l-.38.447-.381-.447C13.658 4.027 11.126 2.5 8.797 2.5 4.147 2.5.5 6.147.5 10.797c0 1.714.46 3.375 1.389 5.098 1.775 3.288 4.26 5.843 12.123 12.974l1.984 1.806z"/>
86 + </g>
87 +</symbol><symbol id="icon-d-ic-icon-load" viewBox="0 0 32 32">
88 + <g fill="none" fill-rule="evenodd">
89 + <path stroke="#8a8a8a" stroke-linecap="round" stroke-linejoin="round" d="M17.314 18.867l1.951-2.53 4 5.184h-17l6.5-8.84 4.549 6.186z"/>
90 + <path fill="#8a8a8a" d="M18.01 4a11.798 11.798 0 0 0 0 1H3v24h24V14.986a8.738 8.738 0 0 0 1 0V29a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h15.01z"/>
91 + <path fill="#8a8a8a" d="M25 3h1v9h-1z"/>
92 + <path stroke="#8a8a8a" d="M22 6l3.5-3.5L29 6"/>
93 + </g>
94 +</symbol><symbol id="icon-d-ic-icon-location" viewBox="0 0 32 32">
95 + <g fill="none" fill-rule="evenodd">
96 + <g stroke="#8a8a8a">
97 + <path d="M16 31.28C23.675 23.302 27.5 17.181 27.5 13c0-6.351-5.149-11.5-11.5-11.5S4.5 6.649 4.5 13c0 4.181 3.825 10.302 11.5 18.28z"/>
98 + <circle cx="16" cy="13" r="4.5"/>
99 + </g>
100 + </g>
101 +</symbol><symbol id="icon-d-ic-icon-polygon" viewBox="0 0 32 32">
102 + <g fill="none" fill-rule="evenodd">
103 + <path stroke="#8a8a8a" d="M.576 16L8.29 29.5h15.42L31.424 16 23.71 2.5H8.29L.576 16z"/>
104 + </g>
105 +</symbol><symbol id="icon-d-ic-icon-star-2" viewBox="0 0 32 32">
106 + <g fill="none" fill-rule="evenodd">
107 + <path stroke="#8a8a8a" d="M19.446 31.592l2.265-3.272 3.946.25.636-3.94 3.665-1.505-1.12-3.832 2.655-2.962-2.656-2.962 1.12-3.832-3.664-1.505-.636-3.941-3.946.25-2.265-3.271L16 3.024 12.554 1.07 10.289 4.34l-3.946-.25-.636 3.941-3.665 1.505 1.12 3.832L.508 16.33l2.656 2.962-1.12 3.832 3.664 1.504.636 3.942 3.946-.25 2.265 3.27L16 29.638l3.446 1.955z"/>
108 + </g>
109 +</symbol><symbol id="icon-d-ic-icon-star" viewBox="0 0 32 32">
110 + <g fill="none" fill-rule="evenodd">
111 + <path stroke="#8a8a8a" d="M25.292 29.878l-1.775-10.346 7.517-7.327-10.388-1.51L16 1.282l-4.646 9.413-10.388 1.51 7.517 7.327-1.775 10.346L16 24.993l9.292 4.885z"/>
112 + </g>
113 +</symbol><symbol id="icon-d-ic-icon" viewBox="0 0 24 24">
114 + <g fill="none">
115 + <path stroke="#8a8a8a" stroke-linecap="round" stroke-linejoin="round" d="M11.923 19.136L5.424 22l.715-7.065-4.731-5.296 6.94-1.503L11.923 2l3.574 6.136 6.94 1.503-4.731 5.296L18.42 22z"/>
116 + </g>
117 +</symbol><symbol id="icon-d-ic-mask-load" viewBox="0 0 32 32">
118 + <g fill="none" fill-rule="evenodd">
119 + <path d="M0 0h32v32H0z"/>
120 + <path fill="#8a8a8a" d="M18.01 4a11.798 11.798 0 0 0 0 1H3v24h24V14.986a8.738 8.738 0 0 0 1 0V29a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h15.01zM15 23a6 6 0 1 1 0-12 6 6 0 0 1 0 12zm0-1a5 5 0 1 0 0-10 5 5 0 0 0 0 10z"/>
121 + <path fill="#8a8a8a" d="M25 3h1v9h-1z"/>
122 + <path stroke="#8a8a8a" d="M22 6l3.5-3.5L29 6"/>
123 + </g>
124 +</symbol><symbol id="icon-d-ic-mask" viewBox="0 0 24 24">
125 + <g fill="none">
126 + <circle cx="12" cy="12" r="4.5" stroke="#8a8a8a"/>
127 + <path fill="#8a8a8a" d="M2 1h20a1 1 0 0 1 1 1v20a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1zm0 1v20h20V2H2z"/>
128 + </g>
129 +</symbol><symbol id="icon-d-ic-redo" viewBox="0 0 24 24">
130 + <g fill="none" fill-rule="evenodd">
131 + <path d="M0 0h24v24H0z" opacity=".5"/>
132 + <path fill="#8a8a8a" d="M21 6H9a6 6 0 1 0 0 12h12v1H9A7 7 0 0 1 9 5h12v1z"/>
133 + <path stroke="#8a8a8a" stroke-linecap="square" d="M19 3l2.5 2.5L19 8"/>
134 + </g>
135 +</symbol><symbol id="icon-d-ic-reset" viewBox="0 0 24 24">
136 + <g fill="none" fill-rule="evenodd">
137 + <path d="M0 0h24v24H0z" opacity=".5"/>
138 + <path fill="#8a8a8a" d="M2 13v-1a7 7 0 0 1 7-7h13v1h-1v5h1v1a7 7 0 0 1-7 7H2v-1h1v-5H2zm7-7a6 6 0 0 0-6 6v6h12a6 6 0 0 0 6-6V6H9z"/>
139 + <path stroke="#8a8a8a" stroke-linecap="square" d="M19 3l2.5 2.5L19 8M5 16l-2.5 2.5L5 21"/>
140 + </g>
141 +</symbol><symbol id="icon-d-ic-rotate-clockwise" viewBox="0 0 32 32">
142 + <g fill="none" fill-rule="evenodd">
143 + <path fill="#8a8a8a" d="M29 17h-.924c0 6.627-5.373 12-12 12-6.628 0-12-5.373-12-12C4.076 10.398 9.407 5.041 16 5V4C8.82 4 3 9.82 3 17s5.82 13 13 13 13-5.82 13-13z"/>
144 + <path stroke="#8a8a8a" stroke-linecap="square" d="M16 1.5l4 3-4 3"/>
145 + <path fill="#8a8a8a" fill-rule="nonzero" d="M16 4h4v1h-4z"/>
146 + </g>
147 +</symbol><symbol id="icon-d-ic-rotate-counterclockwise" viewBox="0 0 32 32">
148 + <g fill="none" fill-rule="evenodd">
149 + <path fill="#8a8a8a" d="M3 17h.924c0 6.627 5.373 12 12 12 6.628 0 12-5.373 12-12 0-6.602-5.331-11.96-11.924-12V4c7.18 0 13 5.82 13 13s-5.82 13-13 13S3 24.18 3 17z"/>
150 + <path fill="#8a8a8a" fill-rule="nonzero" d="M12 4h4v1h-4z"/>
151 + <path stroke="#8a8a8a" stroke-linecap="square" d="M16 1.5l-4 3 4 3"/>
152 + </g>
153 +</symbol><symbol id="icon-d-ic-rotate" viewBox="0 0 24 24">
154 + <g fill="none" fill-rule="evenodd">
155 + <path d="M0 0h24v24H0z"/>
156 + <path fill="#8a8a8a" d="M8.349 22.254a10.002 10.002 0 0 1-2.778-1.719l.65-.76a9.002 9.002 0 0 0 2.495 1.548l-.367.931zm2.873.704l.078-.997a9 9 0 1 0-.557-17.852l-.14-.99A10.076 10.076 0 0 1 12.145 3c5.523 0 10 4.477 10 10s-4.477 10-10 10c-.312 0-.62-.014-.924-.042zm-7.556-4.655a9.942 9.942 0 0 1-1.253-2.996l.973-.234a8.948 8.948 0 0 0 1.124 2.693l-.844.537zm-1.502-5.91A9.949 9.949 0 0 1 2.88 9.23l.925.382a8.954 8.954 0 0 0-.644 2.844l-.998-.062zm2.21-5.686c.687-.848 1.51-1.58 2.436-2.166l.523.852a9.048 9.048 0 0 0-2.188 1.95l-.771-.636z"/>
157 + <path stroke="#8a8a8a" stroke-linecap="square" d="M13 1l-2.5 2.5L13 6"/>
158 + </g>
159 +</symbol><symbol id="icon-d-ic-shape-circle" viewBox="0 0 32 32">
160 + <g fill="none" fill-rule="evenodd">
161 + <circle cx="16" cy="16" r="14.5" stroke="#8a8a8a"/>
162 + </g>
163 +</symbol><symbol id="icon-d-ic-shape-rectangle" viewBox="0 0 32 32">
164 + <g fill="none" fill-rule="evenodd">
165 + <rect width="27" height="27" x="2.5" y="2.5" stroke="#8a8a8a" rx="1"/>
166 + </g>
167 +</symbol><symbol id="icon-d-ic-shape-triangle" viewBox="0 0 32 32">
168 + <g fill="none" fill-rule="evenodd">
169 + <path stroke="#8a8a8a" stroke-linecap="round" stroke-linejoin="round" d="M16 2.5l15.5 27H.5z"/>
170 + </g>
171 +</symbol><symbol id="icon-d-ic-shape" viewBox="0 0 24 24">
172 + <g fill="none" fill-rule="evenodd">
173 + <path fill="#8a8a8a" d="M14.706 8H21a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H9a1 1 0 0 1-1-1v-4h1v4h12V9h-5.706l-.588-1z"/>
174 + <path stroke="#8a8a8a" stroke-linecap="round" stroke-linejoin="round" d="M8.5 1.5l7.5 13H1z"/>
175 + </g>
176 +</symbol><symbol id="icon-d-ic-text-align-center" viewBox="0 0 32 32">
177 + <g fill="none" fill-rule="evenodd">
178 + <path d="M0 0h32v32H0z"/>
179 + <path fill="#8a8a8a" d="M2 5h28v1H2zM8 12h16v1H8zM2 19h28v1H2zM8 26h16v1H8z"/>
180 + </g>
181 +</symbol><symbol id="icon-d-ic-text-align-left" viewBox="0 0 32 32">
182 + <g fill="none" fill-rule="evenodd">
183 + <path d="M0 0h32v32H0z"/>
184 + <path fill="#8a8a8a" d="M2 5h28v1H2zM2 12h16v1H2zM2 19h28v1H2zM2 26h16v1H2z"/>
185 + </g>
186 +</symbol><symbol id="icon-d-ic-text-align-right" viewBox="0 0 32 32">
187 + <g fill="none" fill-rule="evenodd">
188 + <path d="M0 0h32v32H0z"/>
189 + <path fill="#8a8a8a" d="M2 5h28v1H2zM14 12h16v1H14zM2 19h28v1H2zM14 26h16v1H14z"/>
190 + </g>
191 +</symbol><symbol id="icon-d-ic-text-bold" viewBox="0 0 32 32">
192 + <g fill="none" fill-rule="evenodd">
193 + <path d="M0 0h32v32H0z"/>
194 + <path fill="#8a8a8a" d="M7 2h2v2H7zM7 28h2v2H7z"/>
195 + <path stroke="#8a8a8a" stroke-width="2" d="M9 3v12h9a6 6 0 1 0 0-12H9zM9 15v14h10a7 7 0 0 0 0-14H9z"/>
196 + </g>
197 +</symbol><symbol id="icon-d-ic-text-italic" viewBox="0 0 32 32">
198 + <g fill="none" fill-rule="evenodd">
199 + <path d="M0 0h32v32H0z"/>
200 + <path fill="#8a8a8a" d="M15 2h5v1h-5zM11 29h5v1h-5zM17 3h1l-4 26h-1z"/>
201 + </g>
202 +</symbol><symbol id="icon-d-ic-text-underline" viewBox="0 0 32 32">
203 + <g fill="none" fill-rule="evenodd">
204 + <path d="M0 0h32v32H0z"/>
205 + <path fill="#8a8a8a" d="M8 2v14a8 8 0 1 0 16 0V2h1v14a9 9 0 0 1-18 0V2h1zM3 29h26v1H3z"/>
206 + <path fill="#8a8a8a" d="M5 2h5v1H5zM22 2h5v1h-5z"/>
207 + </g>
208 +</symbol><symbol id="icon-d-ic-text" viewBox="0 0 24 24">
209 + <g fill="#8a8a8a" fill-rule="evenodd">
210 + <path d="M4 3h15a1 1 0 0 1 1 1H3a1 1 0 0 1 1-1zM3 4h1v1H3zM19 4h1v1h-1z"/>
211 + <path d="M11 3h1v18h-1z"/>
212 + <path d="M10 20h3v1h-3z"/>
213 + </g>
214 +</symbol><symbol id="icon-d-ic-undo" viewBox="0 0 24 24">
215 + <g fill="none" fill-rule="evenodd">
216 + <path d="M24 0H0v24h24z" opacity=".5"/>
217 + <path fill="#8a8a8a" d="M3 6h12a6 6 0 1 1 0 12H3v1h12a7 7 0 0 0 0-14H3v1z"/>
218 + <path stroke="#8a8a8a" stroke-linecap="square" d="M5 3L2.5 5.5 5 8"/>
219 + </g>
220 +</symbol><symbol id="icon-d-img-bi" viewBox="0 0 257 26">
221 + <g fill="#FDBA3B">
222 + <path d="M26 5a8.001 8.001 0 0 0 0 16 8.001 8.001 0 0 0 0-16M51.893 19.812L43.676 5.396A.78.78 0 0 0 43 5a.78.78 0 0 0-.677.396l-8.218 14.418a.787.787 0 0 0 0 .792c.14.244.396.394.676.394h16.436c.28 0 .539-.15.678-.396a.796.796 0 0 0-.002-.792M15.767 5.231A.79.79 0 0 0 15.21 5H.791A.791.791 0 0 0 0 5.79v6.42a.793.793 0 0 0 .791.79h3.21v7.21c.001.21.082.408.234.56.147.148.347.23.558.23h6.416a.788.788 0 0 0 .792-.79V13h3.006c.413 0 .611-.082.762-.232.15-.149.23-.35.231-.559V5.791a.787.787 0 0 0-.233-.56M85.767 5.231A.79.79 0 0 0 85.21 5H70.791a.791.791 0 0 0-.791.79v6.42a.793.793 0 0 0 .791.79h3.21v7.21c.001.21.082.408.234.56.147.148.347.23.558.23h6.416a.788.788 0 0 0 .792-.79V13h3.006c.413 0 .611-.082.762-.232.15-.149.23-.35.231-.559V5.791a.787.787 0 0 0-.233-.56M65.942 9.948l2.17-3.76a.78.78 0 0 0 0-.792.791.791 0 0 0-.684-.396h-8.54A5.889 5.889 0 0 0 53 10.86a5.887 5.887 0 0 0 3.07 5.17l-2.184 3.782A.792.792 0 0 0 54.571 21h8.54a5.89 5.89 0 0 0 2.831-11.052M105.7 21h2.3V5h-2.3zM91 5h2.4v10.286c0 1.893 1.612 3.429 3.6 3.429s3.6-1.536 3.6-3.429V5h2.4v10.286c0 3.156-2.686 5.714-6 5.714-3.313 0-6-2.558-6-5.714V5zM252.148 21.128h-2.377V9.659h2.27v1.64c.69-1.299 1.792-1.938 3.304-1.938.497 0 .95.065 1.382.192l-.215 2.277a3.734 3.734 0 0 0-1.275-.213c-1.814 0-3.089 1.234-3.089 3.638v5.873zm-7.095-5.744a3.734 3.734 0 0 0-1.101-2.703c-.714-.766-1.6-1.149-2.658-1.149-1.058 0-1.944.383-2.679 1.149a3.803 3.803 0 0 0-1.08 2.703c0 1.063.368 1.978 1.08 2.722.735.746 1.62 1.128 2.68 1.128 1.058 0 1.943-.382 2.657-1.128.734-.744 1.101-1.659 1.101-2.722zm-9.916 0c0-1.682.583-3.086 1.729-4.256 1.166-1.17 2.635-1.767 4.428-1.767 1.793 0 3.262.597 4.407 1.767 1.167 1.17 1.75 2.574 1.75 4.256 0 1.7-.583 3.127-1.75 4.297-1.145 1.17-2.614 1.745-4.407 1.745-1.793 0-3.262-.575-4.428-1.745-1.146-1.17-1.729-2.596-1.729-4.297zm-1.5 3.233l.821 1.83c-.864.638-1.944.958-3.22.958-2.526 0-3.822-1.554-3.822-4.383V11.66h-2.01v-2h2.031V5.595h2.355v4.063h4.018v2h-4.018v5.405c0 1.469.605 2.191 1.793 2.191.626 0 1.318-.212 2.052-.638zm-12.43 2.51h2.375V9.66h-2.376v11.469zm1.23-12.977c-.929 0-1.642-.682-1.642-1.596 0-.873.713-1.554 1.643-1.554.885 0 1.576.681 1.576 1.554 0 .914-.69 1.596-1.576 1.596zm-6.49 7.234c0-1.086-.346-1.98-1.037-2.724-.692-.745-1.599-1.128-2.7-1.128-1.102 0-2.01.383-2.7 1.128-.692.744-1.037 1.638-1.037 2.724 0 1.084.345 2.02 1.036 2.766.691.744 1.6 1.105 2.7 1.105 1.102 0 2.01-.361 2.7-1.105.692-.746 1.038-1.682 1.038-2.766zm-.173-4.129V5h2.397v16.128h-2.354v-1.596c-1.015 1.255-2.333 1.873-3.91 1.873-1.663 0-3.068-.575-4.169-1.724-1.102-1.17-1.663-2.596-1.663-4.297 0-1.682.561-3.107 1.663-4.256 1.101-1.17 2.485-1.745 4.148-1.745 1.534 0 2.83.617 3.888 1.872zm-11.48 9.873h-10.218V5.405h10.195v2.318h-7.711V12h7.15v2.32h-7.15v4.489h7.733v2.319zm-23.891-9.724c-1.793 0-3.132 1.192-3.478 2.979h6.783c-.194-1.808-1.555-2.979-3.305-2.979zm5.703 3.766c0 .32-.021.703-.086 1.128h-9.095c.346 1.787 1.62 3 3.867 3 1.318 0 2.916-.49 3.953-1.234l.994 1.724c-1.189.872-3.067 1.595-5.033 1.595-4.364 0-6.243-3-6.243-6.021 0-1.724.54-3.15 1.642-4.277 1.101-1.127 2.548-1.702 4.298-1.702 1.664 0 3.046.511 4.105 1.553 1.058 1.043 1.598 2.447 1.598 4.234zm-19.949 3.894c1.08 0 1.966-.362 2.68-1.085.712-.724 1.058-1.617 1.058-2.703 0-1.084-.346-2-1.059-2.701-.713-.702-1.599-1.064-2.679-1.064-1.058 0-1.944.362-2.656 1.085-.714.702-1.059 1.596-1.059 2.68 0 1.086.345 2 1.059 2.724.712.702 1.598 1.064 2.656 1.064zm3.673-7.936V9.66h2.29v10.299c0 1.85-.584 3.32-1.728 4.404-1.146 1.085-2.68 1.638-4.58 1.638-1.945 0-3.672-.553-5.206-1.638l1.037-1.808c1.296.915 2.679 1.36 4.126 1.36 2.484 0 3.996-1.51 3.996-3.637v-.83c-1.015 1.127-2.311 1.702-3.91 1.702-1.684 0-3.089-.554-4.19-1.68-1.102-1.128-1.642-2.532-1.642-4.214 0-1.68.561-3.085 1.706-4.191 1.145-1.128 2.571-1.681 4.234-1.681 1.534 0 2.83.575 3.867 1.745zm-18.07 8.127c1.102 0 1.988-.382 2.7-1.128.714-.744 1.06-1.659 1.06-2.743 0-1.065-.346-1.98-1.06-2.724-.712-.745-1.598-1.128-2.7-1.128-1.101 0-2.008.383-2.7 1.128-.691.744-1.036 1.66-1.036 2.745 0 1.084.345 2 1.037 2.745.691.744 1.598 1.105 2.7 1.105zm3.652-8V9.66h2.29v11.469h-2.29v-1.575c-1.059 1.234-2.399 1.852-3.976 1.852-1.663 0-3.067-.575-4.168-1.745-1.102-1.17-1.642-2.617-1.642-4.34 0-1.724.54-3.128 1.642-4.256 1.1-1.128 2.505-1.681 4.168-1.681 1.577 0 2.917.617 3.976 1.872zM138.79 9.34c1.404 0 2.527.448 3.37 1.34.863.873 1.295 2.086 1.295 3.596v6.852h-2.376V14.66c0-2.021-1.036-3.128-2.657-3.128-1.727 0-2.915 1.255-2.915 3.192v6.404h-2.377v-6.426c0-1.978-1.037-3.17-2.679-3.17-1.728 0-2.937 1.277-2.937 3.234v6.362h-2.377V9.659h2.333v1.66c.692-1.212 1.988-1.979 3.522-1.979 1.533.021 2.958.767 3.586 2.107.798-1.277 2.419-2.107 4.212-2.107zm-19.517 11.788h2.484V5.405h-2.484v15.723z"/>
223 + </g>
224 +</symbol></svg>
...\ No newline at end of file ...\ No newline at end of file
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Test cases of "src/js/component/flip.js"
4 + */
5 +import fabric from 'fabric';
6 +import $ from 'jquery';
7 +import Graphics from '../src/js/graphics';
8 +import Flip from '../src/js/component/flip';
9 +
10 +describe('Flip', () => {
11 + let graphics, flipModule, mockImage;
12 +
13 + beforeAll(() => {
14 + graphics = new Graphics($('<canvas>')[0]);
15 + flipModule = new Flip(graphics);
16 + });
17 +
18 + beforeEach(() => {
19 + mockImage = new fabric.Image();
20 + graphics.setCanvasImage('mockImage', mockImage);
21 + });
22 +
23 + it('"getCurrentSetting()" should return current flip-setting', () => {
24 + let setting = flipModule.getCurrentSetting();
25 +
26 + expect(setting).toEqual({
27 + flipX: false,
28 + flipY: false,
29 + });
30 +
31 + mockImage.set({ flipX: true });
32 + setting = flipModule.getCurrentSetting();
33 + expect(setting).toEqual({
34 + flipX: true,
35 + flipY: false,
36 + });
37 + });
38 +
39 + it('"set()" should set flip-setting', () => {
40 + flipModule.set({
41 + flipX: false,
42 + flipY: true,
43 + });
44 +
45 + expect(flipModule.getCurrentSetting()).toEqual({
46 + flipX: false,
47 + flipY: true,
48 + });
49 + });
50 +
51 + it('"reset()" should reset flip-setting to false', () => {
52 + mockImage.set({
53 + flipX: true,
54 + flipY: true,
55 + });
56 + flipModule.reset();
57 +
58 + expect(flipModule.getCurrentSetting()).toEqual({
59 + flipX: false,
60 + flipY: false,
61 + });
62 + });
63 +
64 + it('"flipX()" should toggle flipX', () => {
65 + flipModule.flipX();
66 +
67 + expect(flipModule.getCurrentSetting()).toEqual({
68 + flipX: true,
69 + flipY: false,
70 + });
71 +
72 + flipModule.flipX();
73 +
74 + expect(flipModule.getCurrentSetting()).toEqual({
75 + flipX: false,
76 + flipY: false,
77 + });
78 + });
79 +
80 + it('"flipY()" should toggle flipY', () => {
81 + flipModule.flipY();
82 +
83 + expect(flipModule.getCurrentSetting()).toEqual({
84 + flipX: false,
85 + flipY: true,
86 + });
87 +
88 + flipModule.flipY();
89 +
90 + expect(flipModule.getCurrentSetting()).toEqual({
91 + flipX: false,
92 + flipY: false,
93 + });
94 + });
95 +
96 + describe('Promise is returned with settings and angle,', () => {
97 + beforeEach(() => {
98 + mockImage.angle = 10;
99 + });
100 +
101 + it('flipX() is called.', (done) => {
102 + flipModule.flipX().then((obj) => {
103 + expect(obj).toEqual({
104 + flipX: true,
105 + flipY: false,
106 + angle: -10,
107 + });
108 + done();
109 + });
110 + });
111 +
112 + it('flipY() is called.', (done) => {
113 + flipModule.flipY().then((obj) => {
114 + expect(obj).toEqual({
115 + flipX: false,
116 + flipY: true,
117 + angle: -10,
118 + });
119 + done();
120 + });
121 + });
122 +
123 + it('flipY() is called.', (done) => {
124 + flipModule.flipY().then((obj) => {
125 + expect(obj).toEqual({
126 + flipX: false,
127 + flipY: true,
128 + angle: -10,
129 + });
130 + done();
131 + });
132 + });
133 +
134 + it('set() is called.', (done) => {
135 + flipModule
136 + .set({
137 + flipX: true,
138 + flipY: false,
139 + })
140 + .then((obj) => {
141 + expect(obj).toEqual({
142 + flipX: true,
143 + flipY: false,
144 + angle: -10,
145 + });
146 + done();
147 + });
148 + });
149 + });
150 +});
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Tests command with command-factory
4 + */
5 +import snippet from 'tui-code-snippet';
6 +import fabric from 'fabric';
7 +import Graphics from '../src/js/graphics';
8 +import { drawingModes, componentNames as components } from '../src/js/consts';
9 +
10 +describe('Graphics', () => {
11 + const cssMaxWidth = 900;
12 + const cssMaxHeight = 700;
13 + let graphics, canvas;
14 +
15 + beforeEach(() => {
16 + graphics = new Graphics(document.createElement('canvas'), {
17 + cssMaxWidth,
18 + cssMaxHeight,
19 + });
20 + canvas = graphics.getCanvas();
21 + });
22 +
23 + it('has several properties', () => {
24 + expect(canvas).not.toBe(null);
25 + expect(canvas).toEqual(jasmine.any(fabric.Canvas));
26 + expect(graphics.cssMaxWidth).toBe(900);
27 + expect(graphics.cssMaxHeight).toBe(700);
28 + expect(graphics.canvasImage).toBe(null);
29 + expect(graphics.imageName).toBe('');
30 + expect(graphics._drawingMode).toBe(drawingModes.NORMAL);
31 + expect(graphics._componentMap).not.toBe(null);
32 + });
33 +
34 + it('After the path has been drawn, "origin" should change to "left top-> center center" and "position" should change to the center coordinates of path.', () => {
35 + const pathObj = new fabric.Path('M 0 0 L 100 0 L 100 100 L 0 100 z');
36 + const expectPosition = pathObj.getCenterPoint();
37 + const expectX = expectPosition.x;
38 + const expectY = expectPosition.y;
39 +
40 + graphics._onPathCreated({ path: pathObj });
41 +
42 + expect(pathObj.originX).toBe('center');
43 + expect(pathObj.originY).toBe('center');
44 + expect(pathObj.left).toBe(expectX);
45 + expect(pathObj.top).toBe(expectY);
46 + });
47 +
48 + it('can attach canvas events', () => {
49 + const onMousedown = jasmine.createSpy('onMousedown');
50 + const onObjectAdded = jasmine.createSpy('onObjectAdded');
51 + const onObjectSelected = jasmine.createSpy('onObjectSelected');
52 +
53 + graphics.on({
54 + mousedown: onMousedown,
55 + 'object:added': onObjectAdded,
56 + });
57 + graphics.once('object:selected', onObjectSelected);
58 +
59 + graphics.fire('mousedown');
60 + graphics.fire('mousedown');
61 + graphics.fire('object:added');
62 + graphics.fire('object:added');
63 + graphics.fire('object:selected');
64 + graphics.fire('object:selected');
65 +
66 + expect(onMousedown.calls.count()).toBe(2);
67 + expect(onObjectAdded.calls.count()).toBe(2);
68 + expect(onObjectSelected.calls.count()).toBe(1);
69 + });
70 +
71 + it('deactivates all objects', () => {
72 + const triangle = new fabric.Triangle({
73 + width: 20,
74 + height: 30,
75 + });
76 +
77 + canvas.add(triangle).setActiveObject(triangle);
78 + expect(canvas.getActiveObject()).not.toBe(null);
79 + graphics.deactivateAll();
80 + expect(canvas.getActiveObject()).toBe(null);
81 + });
82 +
83 + it('renders objects', (done) => {
84 + let beforeRender = false;
85 + const triangle = new fabric.Triangle({
86 + width: 20,
87 + height: 30,
88 + });
89 +
90 + canvas.add(triangle);
91 + canvas.on('before:render', () => {
92 + beforeRender = true;
93 + });
94 + canvas.on('after:render', () => {
95 + expect(beforeRender).toBe(true);
96 + done();
97 + });
98 + graphics.renderAll();
99 + });
100 +
101 + it('removes a object or group by id', () => {
102 + const triangle = new fabric.Triangle({
103 + width: 20,
104 + height: 30,
105 + });
106 +
107 + graphics.add(triangle);
108 + const objectId = snippet.stamp(triangle);
109 + graphics.removeObjectById(objectId);
110 + expect(graphics.getObjects().length).toBe(0);
111 + });
112 +
113 + it('switches drawing modes', () => {
114 + let modeName;
115 + for (modeName in drawingModes) {
116 + if (drawingModes.hasOwnProperty(modeName)) {
117 + graphics.startDrawingMode(modeName);
118 + expect(graphics.getDrawingMode()).toBe(modeName);
119 + graphics.stopDrawingMode();
120 + expect(graphics.getDrawingMode()).toBe(drawingModes.NORMAL);
121 + }
122 + }
123 + });
124 +
125 + it('can get the cropped image data', () => {
126 + graphics.startDrawingMode(drawingModes.CROPPER);
127 + spyOn(graphics.getComponent(components.CROPPER)._cropzone, 'isValid').and.returnValue(true);
128 +
129 + expect(graphics.getCropzoneRect()).toBeTruthy();
130 + expect(graphics.getCroppedImageData(graphics.getCropzoneRect())).toEqual({
131 + imageName: jasmine.any(String),
132 + url: jasmine.any(String),
133 + });
134 +
135 + graphics.stopDrawingMode();
136 + });
137 +
138 + it('Cropzone must be hidden initially and then redisplayed after completion at toDataURL is executed with a cropzone present', () => {
139 + const cropper = graphics.getComponent(components.CROPPER);
140 + spyOn(cropper, 'changeVisibility');
141 +
142 + graphics.startDrawingMode(drawingModes.CROPPER);
143 + graphics.toDataURL();
144 +
145 + expect(cropper.changeVisibility.calls.allArgs()).toEqual([[false], [true]]);
146 + });
147 +
148 + it('can set brush setting into LINE_DRAWING, FREE_DRAWING', () => {
149 + graphics.startDrawingMode(drawingModes.LINE_DRAWING);
150 + graphics.setBrush({
151 + width: 12,
152 + color: 'FFFF00',
153 + });
154 + const brush = canvas.freeDrawingBrush;
155 + expect(brush.width).toBe(12);
156 + expect(brush.color).toBe('rgba(255,255,0,1)');
157 + graphics.stopDrawingMode();
158 + });
159 +
160 + it('can change a drawing shape', () => {
161 + const shapeComp = graphics.getComponent(components.SHAPE);
162 + graphics.setDrawingShape('circle', {
163 + fill: 'transparent',
164 + stroke: 'blue',
165 + strokeWidth: 3,
166 + rx: 10,
167 + ry: 100,
168 + });
169 + expect(shapeComp._type).toBe('circle');
170 + expect(shapeComp._options).toEqual({
171 + strokeWidth: 3,
172 + stroke: 'blue',
173 + fill: 'transparent',
174 + width: 1,
175 + height: 1,
176 + rx: 10,
177 + ry: 100,
178 + lockSkewingX: true,
179 + lockSkewingY: true,
180 + bringForward: true,
181 + isRegular: false,
182 + });
183 + });
184 +
185 + it('can register custom icon', () => {
186 + const iconComp = graphics.getComponent(components.ICON);
187 + graphics.registerPaths({
188 + customIcon: 'M 0 0 L 20 20 L 10 10 Z',
189 + });
190 +
191 + expect(iconComp._pathMap).toEqual(
192 + jasmine.objectContaining({
193 + customIcon: 'M 0 0 L 20 20 L 10 10 Z',
194 + })
195 + );
196 + });
197 +
198 + it('has the filter', () => {
199 + expect(graphics.hasFilter('Grayscale')).toBe(false);
200 + });
201 +
202 + describe('pasteObject()', () => {
203 + let targetObject1, targetObject2;
204 +
205 + beforeEach(() => {
206 + targetObject1 = new fabric.Object({});
207 + targetObject2 = new fabric.Object({});
208 +
209 + canvas.add(targetObject1);
210 + canvas.add(targetObject2);
211 + });
212 +
213 + it('Group objects must be duplicated as many as the number of objects in the group.', (done) => {
214 + const groupObject = graphics.getActiveSelectionFromObjects(canvas.getObjects());
215 + graphics.setActiveObject(groupObject);
216 + graphics.resetTargetObjectForCopyPaste();
217 +
218 + graphics.pasteObject().then(() => {
219 + expect(canvas.getObjects().length).toBe(4);
220 + done();
221 + });
222 + });
223 +
224 + it('Only one object should be duplicated.', (done) => {
225 + graphics.setActiveObject(targetObject1);
226 + graphics.resetTargetObjectForCopyPaste();
227 +
228 + graphics.pasteObject().then(() => {
229 + expect(canvas.getObjects().length).toBe(3);
230 + done();
231 + });
232 + });
233 + });
234 +});
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Test cases of "src/js/component/icon.js"
4 + */
5 +import fabric from 'fabric';
6 +import $ from 'jquery';
7 +import Graphics from '../src/js/graphics';
8 +import Icon from '../src/js/component/icon';
9 +
10 +describe('Icon', () => {
11 + let canvas, graphics, mockImage, icon;
12 +
13 + beforeAll(() => {
14 + graphics = new Graphics($('<canvas>')[0]);
15 + canvas = graphics.getCanvas();
16 + icon = new Icon(graphics);
17 + });
18 +
19 + beforeEach(() => {
20 + mockImage = new fabric.Image();
21 + graphics.setCanvasImage('mockImage', mockImage);
22 + });
23 +
24 + afterEach(() => {
25 + canvas.forEachObject((obj) => {
26 + canvas.remove(obj);
27 + });
28 + });
29 +
30 + describe('_onFabricMouseMove()', () => {
31 + let iconObj, fEvent;
32 +
33 + beforeEach((done) => {
34 + fEvent = { e: {} };
35 + icon._startPoint = {
36 + x: 300,
37 + y: 300,
38 + };
39 + icon
40 + .add('arrow', {
41 + left: icon._startPoint.x,
42 + top: icon._startPoint.y,
43 + color: '#000',
44 + })
45 + .then(() => {
46 + [iconObj] = canvas.getObjects();
47 + iconObj.set({
48 + width: 10,
49 + height: 10,
50 + });
51 + done();
52 + });
53 + });
54 +
55 + it('When dragging to the right-down from the starting point, the icon scale value should increase.', () => {
56 + spyOn(canvas, 'getPointer').and.returnValue({
57 + x: 500,
58 + y: 500,
59 + });
60 +
61 + icon._onFabricMouseMove(fEvent);
62 +
63 + expect(iconObj.scaleX).toBe(40);
64 + expect(iconObj.scaleY).toBe(40);
65 + });
66 +
67 + it('When dragging to the left-up from the starting point, the icon scale value should increase.', () => {
68 + spyOn(canvas, 'getPointer').and.returnValue({
69 + x: 100,
70 + y: 100,
71 + });
72 +
73 + icon._onFabricMouseMove(fEvent);
74 +
75 + expect(iconObj.scaleX).toBe(40);
76 + expect(iconObj.scaleY).toBe(40);
77 + });
78 + });
79 +
80 + it('add() should insert the activated icon object on canvas.', () => {
81 + icon.add('arrow');
82 +
83 + const activeObj = canvas.getActiveObject();
84 +
85 + expect(activeObj).not.toEqual(null);
86 + });
87 +
88 + it('add() should insert the icon object on center of canvas image.', () => {
89 + const centerPos = icon.getCanvasImage().getCenterPoint();
90 +
91 + icon.add('arrow');
92 +
93 + const activeObj = canvas.getActiveObject();
94 + const halfStrokeWidth = activeObj.strokeWidth / 2;
95 +
96 + expect(activeObj.left + halfStrokeWidth).toEqual(centerPos.x);
97 + expect(activeObj.top + halfStrokeWidth).toEqual(centerPos.y);
98 + });
99 +
100 + it('add() should create the arrow icon when parameter value is "arrow".', () => {
101 + const path = icon._pathMap.arrow;
102 +
103 + spyOn(icon, '_createIcon').and.returnValue(new fabric.Object({}));
104 +
105 + icon.add('arrow');
106 +
107 + expect(icon._createIcon).toHaveBeenCalledWith(path);
108 + });
109 +
110 + it('add() should create the cancel icon when parameter value is "cancel".', () => {
111 + const path = icon._pathMap.cancel;
112 +
113 + spyOn(icon, '_createIcon').and.returnValue(new fabric.Object({}));
114 +
115 + icon.add('cancel');
116 +
117 + expect(icon._createIcon).toHaveBeenCalledWith(path);
118 + });
119 +
120 + it('setColor() should change color of next inserted icon.', () => {
121 + let activeObj;
122 + const color = '#ffffff';
123 +
124 + icon.add('arrow');
125 + activeObj = canvas.getActiveObject();
126 + expect(activeObj.fill).not.toEqual(color);
127 +
128 + icon.setColor(color);
129 +
130 + icon.add('cancel');
131 + activeObj = canvas.getActiveObject();
132 + expect(activeObj.fill).toEqual(color);
133 + });
134 +});
1 +/**
2 + * @fileoverview Test env
3 + * @author NHN Ent. FE Development Lab <dl_javascript@nhn.com>
4 + */
5 +
6 +import snippet from 'tui-code-snippet';
7 +import ImageEditor from '../src/js/imageEditor';
8 +import fabric from 'fabric';
9 +import { eventNames, keyCodes } from '../src/js/consts';
10 +
11 +const { OBJECT_ROTATED } = eventNames;
12 +
13 +describe('ImageEditor', () => {
14 + // hostnameSent module scope variable can not be reset.
15 + // maintain cases with xit as it always fail, if you want to test these cases, change xit to fit one by one
16 + describe('constructor', () => {
17 + let imageEditor, el;
18 +
19 + beforeEach(() => {
20 + el = document.createElement('div');
21 + spyOn(snippet, 'sendHostname');
22 +
23 + imageEditor = new ImageEditor(el, {
24 + usageStatistics: false,
25 + });
26 + });
27 +
28 + afterEach(() => {
29 + imageEditor.destroy();
30 + });
31 +
32 + xit('should send hostname by default', () => {
33 + imageEditor = new ImageEditor(el);
34 +
35 + expect(snippet.sendHostname).toHaveBeenCalled();
36 + });
37 +
38 + xit('should not send hostname on usageStatistics option false', () => {
39 + imageEditor = new ImageEditor(el, {
40 + usageStatistics: false,
41 + });
42 +
43 + expect(snippet.sendHostname).not.toHaveBeenCalled();
44 + });
45 +
46 + it('`preventDefault` of BACKSPACE key events should not be executed when object is selected state.', () => {
47 + const spyCallback = jasmine.createSpy();
48 +
49 + spyOn(imageEditor._graphics, 'getActiveObject').and.returnValue(null);
50 +
51 + imageEditor._onKeyDown({
52 + keyCode: keyCodes.BACKSPACE,
53 + preventDefault: spyCallback,
54 + });
55 +
56 + expect(spyCallback).not.toHaveBeenCalled();
57 + });
58 +
59 + it('"objectRotated" event should be fire at object is rotate.', () => {
60 + const canvas = imageEditor._graphics.getCanvas();
61 + const obj = new fabric.Object({});
62 + const mock = { target: obj };
63 + canvas.add(obj);
64 +
65 + spyOn(imageEditor, 'fire').and.callThrough();
66 +
67 + canvas.fire('object:rotating', mock);
68 +
69 + expect(imageEditor.fire.calls.mostRecent().args[0]).toBe(OBJECT_ROTATED);
70 + });
71 + });
72 +});
1 +/**
2 + * @fileoverview Test env
3 + * @author NHN Ent. FE Development Lab <dl_javascript@nhn.com>
4 + */
5 +import '../src';
6 +import fabric from 'fabric';
7 +
8 +fabric.Object.prototype.objectCaching = false;
9 +
10 +const testsContext = require.context('.', true, /spec\.js$/);
11 +testsContext.keys().forEach(testsContext);
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Test cases of "src/js/invoker.js"
4 + */
5 +import { Promise } from '../src/js/util';
6 +import Invoker from '../src/js/invoker';
7 +import Command from '../src/js/interface/command';
8 +
9 +describe('Invoker', () => {
10 + let invoker, cmd;
11 +
12 + beforeEach(() => {
13 + invoker = new Invoker();
14 +
15 + cmd = new Command({
16 + execute: jasmine.createSpy().and.returnValue(Promise.resolve()),
17 + undo: jasmine.createSpy().and.returnValue(Promise.resolve()),
18 + });
19 + });
20 +
21 + it('"redo()" should call "command.execute" again', (done) => {
22 + invoker
23 + .execute(cmd)
24 + .then(() => invoker.undo())
25 + .then(() => {
26 + cmd.execute.calls.reset();
27 +
28 + return invoker.redo();
29 + })
30 + .then(() => {
31 + expect(cmd.execute).toHaveBeenCalled();
32 + done();
33 + });
34 + });
35 +
36 + it('should call the "command.executeCallback" after invoke', (done) => {
37 + const spyCallback = jasmine.createSpy();
38 +
39 + cmd.setExecuteCallback(spyCallback);
40 + invoker.execute(cmd).then(() => {
41 + expect(spyCallback).toHaveBeenCalled();
42 + done();
43 + });
44 + });
45 +
46 + it('should call the "command.undoCallback" after undo', (done) => {
47 + const spyCallback = jasmine.createSpy();
48 +
49 + cmd.setUndoCallback(spyCallback);
50 + invoker
51 + .execute(cmd)
52 + .then(() => invoker.undo())
53 + .then(() => {
54 + expect(spyCallback).toHaveBeenCalled();
55 + done();
56 + });
57 + });
58 +
59 + describe('invoker.customEvents', () => {
60 + let spyEvents;
61 +
62 + beforeEach(() => {
63 + spyEvents = {
64 + undoStackChanged: jasmine.createSpy(),
65 + redoStackChanged: jasmine.createSpy(),
66 + };
67 + });
68 +
69 + it(
70 + '"invoke()" should fire a event - ' + ' "pushUndoStack" (when redoStack is empty before)"',
71 + (done) => {
72 + invoker.on(spyEvents);
73 + invoker.execute(cmd).then(() => {
74 + expect(spyEvents.undoStackChanged).toHaveBeenCalledWith(1);
75 + expect(spyEvents.redoStackChanged).not.toHaveBeenCalled();
76 + done();
77 + });
78 + }
79 + );
80 +
81 + it(
82 + '"invoke()" should fire events - ' +
83 + ' "pushUndoStack", "clearRedoStack" (when redoStack is not empty before)',
84 + (done) => {
85 + invoker.pushRedoStack({});
86 +
87 + invoker.on(spyEvents);
88 + invoker.execute(cmd).then(() => {
89 + expect(spyEvents.undoStackChanged).toHaveBeenCalledWith(1);
90 + expect(spyEvents.redoStackChanged).toHaveBeenCalledWith(0);
91 + done();
92 + });
93 + }
94 + );
95 +
96 + it(
97 + '"undo()" should fire a event - ' + ' "pushRedoStack" (when undoStack is not empty after)',
98 + (done) => {
99 + invoker
100 + .execute(cmd)
101 + .then(() => invoker.execute(cmd))
102 + .then(() => {
103 + invoker.on(spyEvents);
104 +
105 + return invoker.undo();
106 + })
107 + .then(() => {
108 + expect(spyEvents.undoStackChanged).not.toHaveBeenCalled();
109 + expect(spyEvents.redoStackChanged).toHaveBeenCalledWith(1);
110 + done();
111 + });
112 + }
113 + );
114 +
115 + it(
116 + '"undo()" should fire events - ' +
117 + ' "pushRedoStack", "emptyUndoStack" (when undoStack is empty after)',
118 + (done) => {
119 + invoker
120 + .execute(cmd)
121 + .then(() => {
122 + invoker.on(spyEvents);
123 +
124 + return invoker.undo();
125 + })
126 + .then(() => {
127 + expect(spyEvents.redoStackChanged).toHaveBeenCalledWith(1);
128 + expect(spyEvents.undoStackChanged).toHaveBeenCalledWith(0);
129 + done();
130 + });
131 + }
132 + );
133 +
134 + it(
135 + '"redo()" should fire a event - ' + ' "pushUndoStack" (when redoStack is not empty after)',
136 + (done) => {
137 + invoker
138 + .execute(cmd)
139 + .then(() => invoker.execute(cmd))
140 + .then(() => invoker.undo())
141 + .then(() => invoker.undo())
142 + .then(() => {
143 + invoker.on(spyEvents);
144 +
145 + return invoker.redo();
146 + })
147 + .then(() => {
148 + expect(spyEvents.undoStackChanged).toHaveBeenCalledWith(1);
149 + expect(spyEvents.redoStackChanged).not.toHaveBeenCalled();
150 + done();
151 + });
152 + }
153 + );
154 +
155 + it(
156 + '"redo()" should fire events - ' +
157 + ' "pushUndoStack", "emptyRedoStack" (when undoStack is empty after)',
158 + (done) => {
159 + invoker
160 + .execute(cmd)
161 + .then(() => invoker.undo())
162 + .then(() => {
163 + invoker.on(spyEvents);
164 +
165 + return invoker.redo(cmd);
166 + })
167 + .then(() => {
168 + expect(spyEvents.undoStackChanged).toHaveBeenCalledWith(1);
169 + expect(spyEvents.redoStackChanged).toHaveBeenCalledWith(0);
170 + done();
171 + });
172 + }
173 + );
174 + });
175 +});
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Test cases of "src/js/component/line.js"
4 + */
5 +import fabric from 'fabric';
6 +import $ from 'jquery';
7 +import Graphics from '../src/js/graphics';
8 +import Line from '../src/js/component/line';
9 +import { eventNames } from '../src/js/consts';
10 +
11 +describe('Line', () => {
12 + let canvas, graphics, mockImage, line, fEvent;
13 +
14 + beforeAll(() => {
15 + graphics = new Graphics($('<canvas>')[0]);
16 + canvas = graphics.getCanvas();
17 + line = new Line(graphics);
18 + });
19 +
20 + beforeEach(() => {
21 + mockImage = new fabric.Image();
22 + graphics.setCanvasImage('mockImage', mockImage);
23 +
24 + fEvent = {
25 + e: {},
26 + };
27 + });
28 +
29 + afterEach(() => {
30 + canvas.forEachObject((obj) => {
31 + canvas.remove(obj);
32 + });
33 + });
34 +
35 + it('_onFabricMouseDown() should insert the line.', () => {
36 + line._onFabricMouseDown(fEvent);
37 +
38 + expect(canvas.getObjects().length).toEqual(1);
39 + });
40 +
41 + it('_onFabricMouseMove() should draw line located by mouse pointer.', () => {
42 + line._line = new fabric.Line([10, 20, 10, 20]);
43 +
44 + canvas.add(line._line);
45 +
46 + spyOn(canvas, 'getPointer').and.returnValue({
47 + x: 30,
48 + y: 60,
49 + });
50 +
51 + expect(canvas.getObjects()[0].get('x2')).toEqual(10);
52 + expect(canvas.getObjects()[0].get('y2')).toEqual(20);
53 +
54 + line._onFabricMouseMove(fEvent);
55 +
56 + expect(canvas.getObjects()[0].get('x2')).toEqual(30);
57 + expect(canvas.getObjects()[0].get('y2')).toEqual(60);
58 + });
59 +
60 + it('end() should restore all drawing objects activated.', () => {
61 + const path = new fabric.Path();
62 +
63 + canvas.add(path);
64 +
65 + line.start();
66 +
67 + expect(canvas.getObjects()[0].get('evented')).toEqual(false);
68 +
69 + line.end();
70 +
71 + expect(canvas.getObjects()[0].get('evented')).toEqual(true);
72 + });
73 +
74 + it('"objectAdded" event should fire after the line is drawn.', () => {
75 + spyOn(line, 'fire').and.callThrough();
76 + line._onFabricMouseUp(fEvent);
77 +
78 + expect(line.fire.calls.mostRecent().args[0]).toBe(eventNames.OBJECT_ADDED);
79 + });
80 +});
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Test cases of "src/js/component/filter.js"
4 + */
5 +import ImageEditor from '../src/js/imageEditor';
6 +
7 +describe('Promise API', () => {
8 + let imageEditor, canvas, activeObjectId;
9 + const imageURL = 'base/test/fixtures/sampleImage.jpg';
10 +
11 + beforeAll(() => {
12 + imageEditor = new ImageEditor(document.createElement('div'), {
13 + cssMaxWidth: 700,
14 + cssMaxHeight: 500,
15 + });
16 + canvas = imageEditor._graphics.getCanvas();
17 +
18 + imageEditor.on('objectActivated', (objectProps) => {
19 + activeObjectId = objectProps.id;
20 + });
21 + });
22 +
23 + afterAll(() => {
24 + imageEditor.destroy();
25 + });
26 +
27 + beforeEach((done) => {
28 + imageEditor
29 + .loadImageFromURL(imageURL, 'sampleImage')
30 + .then(() => done())
31 + ['catch'](() => done());
32 + });
33 +
34 + it('addIcon() supports Promise', (done) => {
35 + imageEditor
36 + .addIcon('arrow', {
37 + left: 10,
38 + top: 10,
39 + })
40 + .then(() => {
41 + expect(canvas.getObjects().length).toBe(1);
42 + done();
43 + })
44 + ['catch']((message) => {
45 + fail(message);
46 + done();
47 + });
48 + });
49 +
50 + it('clearObjects() supports Promise', (done) => {
51 + imageEditor
52 + .addIcon('arrow', {
53 + left: 10,
54 + top: 10,
55 + })
56 + .then(() => imageEditor.clearObjects())
57 + .then(() => {
58 + expect(canvas.getObjects().length).toBe(0);
59 + done();
60 + })
61 + ['catch']((message) => {
62 + fail(message);
63 + done();
64 + });
65 + });
66 +
67 + it('changeIconColor() supports Promise', (done) => {
68 + imageEditor
69 + .addIcon('arrow', {
70 + left: 10,
71 + top: 10,
72 + })
73 + .then(() => imageEditor.changeIconColor(activeObjectId, '#FFFF00'))
74 + .then(() => {
75 + expect(canvas.getObjects()[0].fill).toBe('#FFFF00');
76 + done();
77 + })
78 + ['catch']((message) => {
79 + fail(message);
80 + done();
81 + });
82 + });
83 +
84 + it('addShape() supports Promise', (done) => {
85 + imageEditor
86 + .addShape('rect', {
87 + width: 100,
88 + height: 100,
89 + fill: '#FFFF00',
90 + })
91 + .then(() => {
92 + const [shape] = canvas.getObjects();
93 + expect(shape.type).toBe('rect');
94 + expect(shape.width).toBe(100);
95 + expect(shape.height).toBe(100);
96 + expect(shape.fill).toBe('#FFFF00');
97 + done();
98 + })
99 + ['catch']((message) => {
100 + fail(message);
101 + done();
102 + });
103 + });
104 +
105 + it('changeShape() supports Promise', (done) => {
106 + imageEditor
107 + .addShape('rect', {
108 + width: 100,
109 + height: 100,
110 + fill: '#FFFF00',
111 + })
112 + .then(() =>
113 + imageEditor.changeShape(activeObjectId, {
114 + type: 'triangle',
115 + width: 200,
116 + fill: '#FF0000',
117 + })
118 + )
119 + .then(() => {
120 + const [shape] = canvas.getObjects();
121 + expect(shape.type).toBe('triangle');
122 + expect(shape.width).toBe(200);
123 + expect(shape.fill).toBe('#FF0000');
124 + done();
125 + })
126 + ['catch']((message) => {
127 + fail(message);
128 + done();
129 + });
130 + });
131 +
132 + it('can catch on failure', (done) => {
133 + imageEditor
134 + .addShape('rect', {
135 + width: 100,
136 + height: 100,
137 + fill: '#FFFF00',
138 + })
139 + .then(() => {
140 + imageEditor.deactivateAll();
141 +
142 + return imageEditor.changeShape(null, {
143 + type: 'triangle',
144 + widht: 200,
145 + fill: '#FF0000',
146 + });
147 + })
148 + .then(() => {
149 + fail();
150 + done();
151 + })
152 + ['catch']((message) => {
153 + expect(message).toBe('The object is not in canvas.');
154 + done();
155 + });
156 + });
157 +
158 + it('addImageObject() supports Promise', (done) => {
159 + const maskImageURL = 'base/test/fixtures/mask.png';
160 + imageEditor
161 + .addImageObject(maskImageURL)
162 + .then((objectProps) => {
163 + expect(canvas.getObjects().length).toBe(1);
164 + expect(objectProps.id).toBe(activeObjectId);
165 + done();
166 + })
167 + ['catch']((message) => {
168 + fail(message);
169 + done();
170 + });
171 + });
172 +
173 + it('resizeCanvasDimension() supports Promise', (done) => {
174 + imageEditor
175 + .resizeCanvasDimension({
176 + width: 900,
177 + height: 700,
178 + })
179 + .then(() =>
180 + // There is no way to get canvas dimension
181 + done()
182 + )
183 + ['catch']((message) => {
184 + fail(message);
185 + done();
186 + });
187 + });
188 +
189 + it('undo() supports Promise', (done) => {
190 + imageEditor
191 + .addShape('rect', {
192 + width: 100,
193 + height: 100,
194 + fill: '#FFFF00',
195 + })
196 + .then(() => imageEditor.undo())
197 + .then(() => {
198 + expect(canvas.getObjects().length).toBe(0);
199 + done();
200 + })
201 + ['catch']((message) => {
202 + fail(message);
203 + done();
204 + });
205 + });
206 +
207 + it('flipX() supports Promise', (done) => {
208 + imageEditor
209 + .flipX()
210 + .then((obj) => {
211 + expect(obj).toEqual({
212 + flipX: true,
213 + flipY: false,
214 + angle: 0,
215 + });
216 + done();
217 + })
218 + ['catch']((message) => {
219 + fail(message);
220 + done();
221 + });
222 + });
223 +
224 + it('flipY() supports Promise', (done) => {
225 + imageEditor
226 + .flipY()
227 + .then((obj) => {
228 + expect(obj).toEqual({
229 + flipX: false,
230 + flipY: true,
231 + angle: 0,
232 + });
233 + done();
234 + })
235 + ['catch']((message) => {
236 + fail(message);
237 + done();
238 + });
239 + });
240 +
241 + it('resetFlip() supports Promise', (done) => {
242 + imageEditor
243 + .resetFlip()
244 + .then((obj) => {
245 + expect(obj).toEqual({
246 + flipX: false,
247 + flipY: false,
248 + angle: 0,
249 + });
250 + fail();
251 + done();
252 + })
253 + ['catch']((message) => {
254 + expect(message).toBe('The flipX and flipY setting values are not changed.');
255 + done();
256 + });
257 + });
258 +
259 + it('rotate() supports Promise', (done) => {
260 + imageEditor
261 + .rotate(10)
262 + .then((angle) => {
263 + expect(angle).toBe(10);
264 + done();
265 + })
266 + ['catch']((message) => {
267 + fail(message);
268 + done();
269 + });
270 + });
271 +
272 + it('setAngle() supports Promise', (done) => {
273 + imageEditor
274 + .setAngle(10)
275 + .then((angle) => {
276 + expect(angle).toBe(10);
277 + done();
278 + })
279 + ['catch']((message) => {
280 + fail(message);
281 + done();
282 + });
283 + });
284 +
285 + it('removeObject() supports Promise', (done) => {
286 + imageEditor
287 + .addShape('rect', {
288 + width: 100,
289 + height: 100,
290 + })
291 + .then((objectProps) => imageEditor.removeObject(objectProps.id))
292 + .then(() => {
293 + expect(canvas.getObjects().length).toBe(0);
294 + done();
295 + })
296 + ['catch']((message) => {
297 + fail(message);
298 + done();
299 + });
300 + });
301 +
302 + describe('Watermark', () => {
303 + const maskImageURL = 'base/test/fixtures/mask.png';
304 + const properties = {
305 + fill: 'rgba(255, 255, 0, 0.5)',
306 + left: 150,
307 + top: 30,
308 + };
309 +
310 + beforeEach((done) => {
311 + imageEditor.addImageObject(maskImageURL).then(() => {
312 + done();
313 + });
314 + });
315 +
316 + it("setObjectProperties() should change object's properties", (done) => {
317 + imageEditor
318 + .setObjectProperties(activeObjectId, properties)
319 + .then(() => {
320 + done();
321 + })
322 + ['catch']((message) => {
323 + fail(message);
324 + done();
325 + });
326 + });
327 +
328 + it("getObjectProperties() should return object's properties", (done) => {
329 + imageEditor
330 + .setObjectProperties(activeObjectId, properties)
331 + .then(() => {
332 + const propKeys = {
333 + fill: null,
334 + left: null,
335 + top: null,
336 + };
337 + const result = imageEditor.getObjectProperties(activeObjectId, propKeys);
338 +
339 + expect(result).not.toBe(null);
340 + expect(result).toEqual(
341 + jasmine.objectContaining({
342 + fill: 'rgba(255, 255, 0, 0.5)',
343 + left: 150,
344 + top: 30,
345 + })
346 + );
347 + done();
348 + })
349 + ['catch']((message) => {
350 + fail(message);
351 + done();
352 + });
353 + });
354 +
355 + it('getObjectProperties(objectKeys) should return false if there is no object', (done) => {
356 + imageEditor
357 + .setObjectProperties(activeObjectId, properties)
358 + .then(() => {
359 + const propKeys = {
360 + fill: null,
361 + width: null,
362 + left: null,
363 + top: null,
364 + height: null,
365 + };
366 +
367 + imageEditor.deactivateAll();
368 +
369 + const result = imageEditor.getObjectProperties(null, propKeys);
370 +
371 + expect(result).toBe(null);
372 + done();
373 + })
374 + ['catch']((message) => {
375 + fail(message);
376 + done();
377 + });
378 + });
379 +
380 + it("getObjectProperties(arrayKeys) should return object's properties", (done) => {
381 + imageEditor
382 + .setObjectProperties(activeObjectId, properties)
383 + .then(() => {
384 + const arrayKeys = ['fill', 'width', 'left', 'top', 'height'];
385 + const result = imageEditor.getObjectProperties(activeObjectId, arrayKeys);
386 +
387 + expect(result).not.toBe(null);
388 + expect(result).toEqual(jasmine.objectContaining(properties));
389 + done();
390 + })
391 + ['catch']((message) => {
392 + fail(message);
393 + done();
394 + });
395 + });
396 +
397 + it("getObjectProperties(stringKey) should return object's property", (done) => {
398 + imageEditor
399 + .setObjectProperties(activeObjectId, properties)
400 + .then(() => {
401 + const result = imageEditor.getObjectProperties(activeObjectId, 'fill');
402 +
403 + expect(result).not.toBe(null);
404 + expect(result).toEqual(
405 + jasmine.objectContaining({
406 + fill: 'rgba(255, 255, 0, 0.5)',
407 + })
408 + );
409 + done();
410 + })
411 + ['catch']((message) => {
412 + fail(message);
413 + done();
414 + });
415 + });
416 +
417 + it("getCanvasSize() should return canvas's width, height.", () => {
418 + expect(imageEditor.getCanvasSize()).toEqual(
419 + jasmine.objectContaining({
420 + width: 1600,
421 + height: 1066,
422 + })
423 + );
424 + });
425 +
426 + it('getObjectPosition() should return global point by origin.', () => {
427 + // ImageEditor's object has origin('center', 'center').
428 + const { left, top, width, height } = imageEditor.getObjectProperties(activeObjectId, [
429 + 'left',
430 + 'top',
431 + 'width',
432 + 'height',
433 + ]);
434 + const ltPoint = imageEditor.getObjectPosition(activeObjectId, 'left', 'top');
435 + const ccPoint = imageEditor.getObjectPosition(activeObjectId, 'center', 'center');
436 + const rbPoint = imageEditor.getObjectPosition(activeObjectId, 'right', 'bottom');
437 +
438 + expect(ltPoint.x).toBe(left - width / 2);
439 + expect(ltPoint.y).toBe(top - height / 2);
440 + expect(ccPoint.x).toBe(left);
441 + expect(ccPoint.y).toBe(top);
442 + expect(rbPoint.x).toBe(left + width / 2);
443 + expect(rbPoint.y).toBe(top + height / 2);
444 + });
445 +
446 + it('setObjectPosition() can set object position by origin', (done) => {
447 + imageEditor
448 + .setObjectProperties(activeObjectId, {
449 + width: 200,
450 + height: 100,
451 + })
452 + .then(() =>
453 + imageEditor.setObjectPosition(activeObjectId, {
454 + x: 0,
455 + y: 0,
456 + originX: 'left',
457 + originY: 'top',
458 + })
459 + )
460 + .then(() => {
461 + const result = imageEditor.getObjectProperties(activeObjectId, ['left', 'top']);
462 +
463 + expect(result.left).toBe(100);
464 + expect(result.top).toBe(50);
465 +
466 + done();
467 + })
468 + ['catch']((message) => {
469 + fail(message);
470 + done();
471 + });
472 + });
473 + });
474 +});
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Test cases of "src/js/component/rotation.js"
4 + */
5 +import fabric from 'fabric';
6 +import $ from 'jquery';
7 +import Graphics from '../src/js/graphics';
8 +import Rotation from '../src/js/component/rotation';
9 +
10 +describe('Rotation', () => {
11 + let graphics, rotationModule, mockImage, canvas;
12 +
13 + beforeAll(() => {
14 + graphics = new Graphics($('<canvas>')[0]);
15 + canvas = graphics.getCanvas();
16 + rotationModule = new Rotation(graphics);
17 + });
18 +
19 + beforeEach(() => {
20 + mockImage = new fabric.Image();
21 + graphics.setCanvasImage('mockImage', mockImage);
22 + });
23 +
24 + it('"getCurrentAngle()" should return current angle value', () => {
25 + mockImage.angle = 30;
26 +
27 + expect(rotationModule.getCurrentAngle()).toEqual(30);
28 + });
29 +
30 + it('"setAngle()" should set angle value', () => {
31 + rotationModule.setAngle(40);
32 +
33 + expect(rotationModule.getCurrentAngle()).toEqual(40);
34 + });
35 +
36 + it('"rotate()" should add angle value', () => {
37 + let current = rotationModule.getCurrentAngle();
38 +
39 + rotationModule.rotate(10);
40 + expect(rotationModule.getCurrentAngle()).toBe(current + 10);
41 +
42 + current = rotationModule.getCurrentAngle();
43 + rotationModule.rotate(20);
44 + expect(rotationModule.getCurrentAngle()).toBe(current + 20);
45 + });
46 +
47 + it('"rotate()" should add angle value modular 360(===2*PI)', (done) => {
48 + rotationModule
49 + .setAngle(10)
50 + .then(() => rotationModule.rotate(380))
51 + .then(() => {
52 + expect(rotationModule.getCurrentAngle()).toBe(30);
53 + done();
54 + });
55 + });
56 +
57 + // @todo Move this tc to main.spec.js
58 + it('"adjustCanvasDimension()" should set canvas dimension from image-rect', () => {
59 + spyOn(mockImage, 'getBoundingRect').and.returnValue({
60 + width: 100,
61 + height: 110,
62 + });
63 +
64 + rotationModule.adjustCanvasDimension();
65 + expect(canvas.getWidth()).toEqual(100);
66 + expect(canvas.getHeight()).toEqual(110);
67 + });
68 +});
1 +import {
2 + setCachedUndoDataForDimension,
3 + getCachedUndoDataForDimension,
4 + makeSelectionUndoData,
5 + makeSelectionUndoDatum,
6 +} from '../src/js/helper/selectionModifyHelper';
7 +import Graphics from '../src/js/graphics';
8 +import fabric from 'fabric';
9 +
10 +describe('selectionModifyHelper', () => {
11 + let graphics, obj1, obj2;
12 + const rectOption = {
13 + width: 10,
14 + height: 10,
15 + top: 10,
16 + left: 10,
17 + scaleX: 1,
18 + scaleY: 1,
19 + angle: 0,
20 + };
21 +
22 + beforeEach(() => {
23 + graphics = new Graphics(document.createElement('canvas'));
24 + obj1 = new fabric.Rect(rectOption);
25 + obj2 = new fabric.Rect(rectOption);
26 + });
27 +
28 + it('should set/get cached undo data', () => {
29 + const undoData = [{ id: 1 }];
30 +
31 + setCachedUndoDataForDimension(undoData);
32 +
33 + expect(getCachedUndoDataForDimension()).toEqual(undoData);
34 + });
35 +
36 + describe('makeSelectionUndoData', () => {
37 + it('should make object undo data', () => {
38 + const result = makeSelectionUndoData(obj1, (obj) => obj);
39 +
40 + expect(result).toEqual([obj1]);
41 + });
42 +
43 + it('should make selection undo data', () => {
44 + const selection = graphics.getActiveSelectionFromObjects([obj1, obj2]);
45 +
46 + const result = makeSelectionUndoData(selection, (obj) => obj);
47 +
48 + expect(result).toEqual([obj1, obj2]);
49 + });
50 + });
51 +
52 + describe('makeSelectionUndoDatum', () => {
53 + it('should return undo datum', () => {
54 + const result = makeSelectionUndoDatum(1, obj1, true);
55 +
56 + expect(result).toEqual({
57 + id: 1,
58 + width: obj1.width,
59 + height: obj1.height,
60 + top: obj1.top,
61 + left: obj1.left,
62 + angle: obj1.angle,
63 + scaleX: obj1.scaleX,
64 + scaleY: obj1.scaleY,
65 + });
66 + });
67 + });
68 +});
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Test cases of "src/js/component/line.js"
4 + */
5 +import fabric from 'fabric';
6 +import $ from 'jquery';
7 +import Graphics from '../src/js/graphics';
8 +import Shape from '../src/js/component/shape';
9 +import { resize } from '../src/js/helper/shapeResizeHelper';
10 +import {
11 + getFillImageFromShape,
12 + getCachedCanvasImageElement,
13 +} from '../src/js/helper/shapeFilterFillHelper';
14 +
15 +describe('Shape', () => {
16 + let canvas, graphics, mockImage, fEvent, shape, shapeObj;
17 +
18 + beforeAll(() => {
19 + graphics = new Graphics($('<canvas>')[0]);
20 + canvas = graphics.getCanvas();
21 +
22 + shape = new Shape(graphics);
23 + });
24 +
25 + beforeEach(() => {
26 + mockImage = new fabric.Image();
27 + graphics.setCanvasImage('mockImage', mockImage);
28 +
29 + fEvent = {
30 + e: {},
31 + };
32 + });
33 +
34 + afterEach(() => {
35 + canvas.forEachObject((obj) => {
36 + canvas.remove(obj);
37 + });
38 + });
39 +
40 + it('The origin direction and position value initially adjusted at resize must be calculated correctly.', () => {
41 + const pointer = canvas.getPointer(fEvent.e);
42 + const settings = {
43 + strokeWidth: 0,
44 + type: 'rect',
45 + left: 150,
46 + top: 200,
47 + width: 40,
48 + height: 40,
49 + originX: 'center',
50 + originY: 'center',
51 + };
52 +
53 + shape.add('rect', settings);
54 + [shapeObj] = canvas.getObjects();
55 +
56 + spyOn(shapeObj, 'set').and.callThrough();
57 +
58 + resize(shapeObj, pointer);
59 +
60 + const [{ left: resultLeft, top: resultTop }] = shapeObj.set.calls.first().args;
61 +
62 + expect(resultLeft).toBe(settings.left - settings.width / 2);
63 + expect(resultTop).toBe(settings.top - settings.height / 2);
64 + });
65 +
66 + it('The rectagle object is created on canvas.', () => {
67 + shape.add('rect');
68 +
69 + [shapeObj] = canvas.getObjects();
70 +
71 + expect(shapeObj.type).toBe('rect');
72 + });
73 +
74 + it('The circle object(ellipse) is created on canvas.', () => {
75 + shape.add('circle');
76 +
77 + [shapeObj] = canvas.getObjects();
78 +
79 + expect(shapeObj.type).toBe('circle');
80 + });
81 +
82 + it('The triangle object is created on canvas.', () => {
83 + shape.add('triangle');
84 +
85 + [shapeObj] = canvas.getObjects();
86 +
87 + expect(shapeObj.type).toBe('triangle');
88 + });
89 +
90 + it('When add() is called with no options, the default options set the rectangle object.', () => {
91 + shape.add('rect');
92 +
93 + [shapeObj] = canvas.getObjects();
94 +
95 + expect(shapeObj.width).toBe(1); // strokeWidth: 1, width: 1
96 + expect(shapeObj.height).toBe(1); // strokeWidth: 1, height: 1
97 + });
98 +
99 + it('When add() is called with no options, the default options set the circle object.', () => {
100 + shape.add('circle');
101 +
102 + [shapeObj] = canvas.getObjects();
103 +
104 + expect(shapeObj.width).toBe(0);
105 + expect(shapeObj.height).toBe(0);
106 + });
107 +
108 + it('When add() is called with no options, the default options set the triangle object.', () => {
109 + shape.add('triangle');
110 +
111 + [shapeObj] = canvas.getObjects();
112 +
113 + expect(shapeObj.width).toBe(1); // strokeWidth: 1, width: 1
114 + expect(shapeObj.height).toBe(1); // strokeWidth: 1, height: 1
115 + });
116 +
117 + it('When add() is called with the options, this options set the rectagle object.', () => {
118 + const settings = {
119 + fill: 'blue',
120 + stroke: 'red',
121 + strokeWidth: 10,
122 + type: 'rect',
123 + width: 100,
124 + height: 100,
125 + };
126 +
127 + shape.add('rect', settings);
128 + [shapeObj] = canvas.getObjects();
129 +
130 + expect(shapeObj.fill).toBe('blue');
131 + expect(shapeObj.stroke).toBe('red');
132 + expect(shapeObj.strokeWidth).toBe(10);
133 + expect(shapeObj.width).toBe(100); // width + storkeWidth
134 + expect(shapeObj.height).toBe(100); // height + storkeWidth
135 + });
136 +
137 + it('When add() is called with the options, this options set the circle object.', () => {
138 + const settings = {
139 + fill: 'blue',
140 + stroke: 'red',
141 + strokeWidth: 3,
142 + type: 'circle',
143 + rx: 100,
144 + ry: 50,
145 + };
146 +
147 + shape.add('circle', settings);
148 + [shapeObj] = canvas.getObjects();
149 +
150 + expect(shapeObj.fill).toBe('blue');
151 + expect(shapeObj.stroke).toBe('red');
152 + expect(shapeObj.strokeWidth).toBe(3);
153 + expect(shapeObj.width).toBe(200); // rx * 2 + stokeWidth
154 + expect(shapeObj.height).toBe(100); // ry * 2 + stokeWidth
155 + });
156 +
157 + it('When add() is called with the options, this options set the triangle object.', () => {
158 + const settings = {
159 + fill: 'blue',
160 + stroke: 'red',
161 + strokeWidth: 0,
162 + type: 'triangle',
163 + width: 100,
164 + height: 100,
165 + };
166 +
167 + shape.add('triangle', settings);
168 + [shapeObj] = canvas.getObjects();
169 +
170 + expect(shapeObj.fill).toBe('blue');
171 + expect(shapeObj.stroke).toBe('red');
172 + expect(shapeObj.strokeWidth).toBe(0);
173 + expect(shapeObj.width).toBe(100);
174 + expect(shapeObj.height).toBe(100);
175 + });
176 +
177 + it('When change() is called, the style of the rectagle object is changed.', () => {
178 + shape.add('rect');
179 +
180 + [shapeObj] = canvas.getObjects();
181 +
182 + shape.change(shapeObj, {
183 + fill: 'blue',
184 + stroke: 'red',
185 + width: 10,
186 + height: 20,
187 + });
188 +
189 + expect(shapeObj.fill).toBe('blue');
190 + expect(shapeObj.stroke).toBe('red');
191 + expect(shapeObj.width).toBe(10);
192 + expect(shapeObj.height).toBe(20);
193 + });
194 +
195 + it('When change() is called, the style of the circle object is changed.', () => {
196 + shape.add('circle');
197 +
198 + [shapeObj] = canvas.getObjects();
199 +
200 + shape.change(shapeObj, {
201 + fill: 'blue',
202 + stroke: 'red',
203 + rx: 10,
204 + ry: 20,
205 + });
206 +
207 + expect(shapeObj.fill).toBe('blue');
208 + expect(shapeObj.stroke).toBe('red');
209 + expect(shapeObj.width).toBe(20);
210 + expect(shapeObj.height).toBe(40);
211 + });
212 +
213 + it('When change() is called, the style of the triangle object is changed.', () => {
214 + shape.add('triangle');
215 +
216 + [shapeObj] = canvas.getObjects();
217 +
218 + shape.change(shapeObj, {
219 + width: 10,
220 + height: 20,
221 + });
222 +
223 + expect(shapeObj.fill).toBe('#ffffff');
224 + expect(shapeObj.stroke).toBe('#000000');
225 + expect(shapeObj.width).toBe(10);
226 + expect(shapeObj.height).toBe(20);
227 + });
228 +
229 + describe('Fill - filter type', () => {
230 + beforeEach((done) => {
231 + const imageURL = 'base/test/fixtures/sampleImage.jpg';
232 +
233 + getCachedCanvasImageElement(canvas, true);
234 +
235 + fabric.Image.fromURL(imageURL, (sampleImage) => {
236 + graphics.setCanvasImage('', sampleImage);
237 + shape.add('rect', {
238 + strokeWidth: 0,
239 + left: 20,
240 + top: 30,
241 + width: 100,
242 + height: 80,
243 + fill: {
244 + type: 'filter',
245 + filter: [{ pixelate: 20 }],
246 + },
247 + });
248 + [shapeObj] = canvas.getObjects();
249 +
250 + done();
251 + });
252 + });
253 +
254 + it('"_resetPositionFillFilter" should be executed when a movement, rotation, and scaling event of a filter type fill is applied.', () => {
255 + spyOn(canvas, 'getPointer').and.returnValue({
256 + x: 10,
257 + y: 10,
258 + });
259 + spyOn(shape, '_resetPositionFillFilter');
260 + shapeObj.fire('moving');
261 + shapeObj.fire('rotating');
262 + shapeObj.fire('scaling');
263 +
264 + expect(shape._resetPositionFillFilter.calls.count()).toBe(3);
265 + });
266 +
267 + it('cropX and cropY values of the image filled with the shape background must be changed to match the canvas background exactly.', () => {
268 + shape._resetPositionFillFilter(shapeObj);
269 + const { cropX, cropY } = getFillImageFromShape(shapeObj);
270 +
271 + expect(cropX).toBe(-30);
272 + expect(cropY).toBe(-10);
273 + });
274 +
275 + it('The fill image should be the same size as the shape.', () => {
276 + shape._resetPositionFillFilter(shapeObj);
277 + const { width, height } = getFillImageFromShape(shapeObj);
278 +
279 + expect(width).toBe(100);
280 + expect(height).toBe(80);
281 + });
282 +
283 + it('The rotated object fill image must be the same size as the rectangle that draws the rotated object border.', () => {
284 + shapeObj.set({
285 + angle: 40,
286 + });
287 + shape._resetPositionFillFilter(shapeObj);
288 + const { width, height } = getFillImageFromShape(shapeObj);
289 +
290 + expect(Math.round(width)).toBe(128);
291 + expect(Math.round(height)).toBe(126);
292 + });
293 +
294 + it('If repositioning is performed while the angle is changed, the angle value of the fill image must have the shape reverse rotation value.', () => {
295 + shapeObj.set({
296 + angle: 40,
297 + });
298 + shape._resetPositionFillFilter(shapeObj);
299 + const { angle } = getFillImageFromShape(shapeObj);
300 +
301 + expect(angle).toBe(-40);
302 + });
303 +
304 + it('For shapes that go outside the bottom right area of the canvas, the size and position of the image position should give the expected result.', (done) => {
305 + shape
306 + .add('rect', {
307 + strokeWidth: 0,
308 + left: 250,
309 + top: 100,
310 + width: 200,
311 + height: 200,
312 + fill: {
313 + type: 'filter',
314 + filter: [{ pixelate: 20 }],
315 + },
316 + })
317 + .then((props) => {
318 + shapeObj = graphics.getObject(props.id);
319 + const fillImage = getFillImageFromShape(shapeObj);
320 + const { top, height, left, width } = fillImage;
321 + expect(top).toBe(75);
322 + expect(left).toBe(75);
323 + expect(height).toBe(150);
324 + expect(width).toBe(150);
325 +
326 + done();
327 + });
328 + });
329 +
330 + it('For shapes that go outside the top left area of the canvas, the size and position of the image position should give the expected result.', (done) => {
331 + shape
332 + .add('rect', {
333 + strokeWidth: 0,
334 + left: 50,
335 + top: 30,
336 + width: 200,
337 + height: 70,
338 + fill: {
339 + type: 'filter',
340 + filter: [{ pixelate: 20 }],
341 + },
342 + })
343 + .then((props) => {
344 + shapeObj = graphics.getObject(props.id);
345 + const fillImage = getFillImageFromShape(shapeObj);
346 + const { top, height, left, width } = fillImage;
347 + expect(Math.round(top)).toBe(40);
348 + expect(left).toBe(150);
349 + expect(height).toBe(70);
350 + expect(width).toBe(200);
351 +
352 + done();
353 + });
354 + });
355 +
356 + it('Background image of the shape to which the filter fill is applied must have the filter applied.', () => {
357 + const fillImage = getFillImageFromShape(shapeObj);
358 +
359 + expect(fillImage.filters.length).toBeGreaterThan(0);
360 + });
361 + });
362 +
363 + describe('_onFabricMouseMove()', () => {
364 + beforeEach(() => {
365 + shape.add('rect', {
366 + left: 100,
367 + top: 100,
368 + });
369 +
370 + [shapeObj] = canvas.getObjects();
371 + shape._shapeObj = shapeObj;
372 + });
373 +
374 + it(
375 + 'When the mouse direction is in 1th quadrant,' +
376 + 'the origin values of shape set to "left" and "top".',
377 + () => {
378 + spyOn(canvas, 'getPointer').and.returnValue({
379 + x: 200,
380 + y: 120,
381 + });
382 +
383 + shape._onFabricMouseMove(fEvent);
384 +
385 + expect(shapeObj.originX).toBe('left');
386 + expect(shapeObj.originY).toBe('top');
387 + }
388 + );
389 +
390 + it(
391 + 'When the mouse direction is in 2th quadrant,' +
392 + 'the origin values of shape set to "right" and "top".',
393 + () => {
394 + spyOn(canvas, 'getPointer').and.returnValue({
395 + x: 80,
396 + y: 100,
397 + });
398 +
399 + shape._onFabricMouseMove(fEvent);
400 +
401 + expect(shapeObj.originX).toBe('right');
402 + expect(shapeObj.originY).toBe('top');
403 + }
404 + );
405 +
406 + it(
407 + 'When the mouse direction is in 3th quadrant,' +
408 + 'the origin values of shape set to "right" and "bottom".',
409 + () => {
410 + spyOn(canvas, 'getPointer').and.returnValue({
411 + x: 80,
412 + y: 80,
413 + });
414 +
415 + shape._onFabricMouseMove(fEvent);
416 +
417 + expect(shapeObj.originX).toBe('right');
418 + expect(shapeObj.originY).toBe('bottom');
419 + }
420 + );
421 +
422 + it(
423 + 'When the mouse direction is in 4th quadrant,' +
424 + 'the origin values of shape set to "left" and "bottom".',
425 + () => {
426 + spyOn(canvas, 'getPointer').and.returnValue({
427 + x: 200,
428 + y: 80,
429 + });
430 +
431 + shape._onFabricMouseMove(fEvent);
432 +
433 + expect(shapeObj.originX).toBe('left');
434 + expect(shapeObj.originY).toBe('bottom');
435 + }
436 + );
437 + });
438 +
439 + describe('_onFabricMouseUp()', () => {
440 + let startPoint, expectedPoint;
441 +
442 + beforeEach(() => {
443 + shape.add('circle', {
444 + left: 100,
445 + top: 100,
446 + });
447 +
448 + [shapeObj] = canvas.getObjects();
449 + shape._shapeObj = shapeObj;
450 + });
451 +
452 + it('When the drawing shape is in 1th quadrant, "left" and "top" are the same as start point.', () => {
453 + spyOn(canvas, 'getPointer').and.returnValue({
454 + x: 200,
455 + y: 120,
456 + });
457 +
458 + startPoint = shapeObj.getPointByOrigin('left', 'top');
459 +
460 + shape._onFabricMouseMove(fEvent);
461 + shape._onFabricMouseUp();
462 +
463 + expectedPoint = shapeObj.getPointByOrigin('left', 'top');
464 +
465 + expect(expectedPoint.x).toBe(startPoint.x);
466 + expect(expectedPoint.y).toBe(startPoint.y);
467 + });
468 +
469 + it('When the drawing shape is in 2th quadrant, "right" and "top" are the same as start point.', () => {
470 + spyOn(canvas, 'getPointer').and.returnValue({
471 + x: 80,
472 + y: 120,
473 + });
474 +
475 + startPoint = shapeObj.getPointByOrigin('right', 'top');
476 +
477 + shape._onFabricMouseMove(fEvent);
478 + shape._onFabricMouseUp();
479 +
480 + expectedPoint = shapeObj.getPointByOrigin('right', 'top');
481 +
482 + expect(expectedPoint.x).toBe(startPoint.x);
483 + expect(expectedPoint.y).toBe(startPoint.y);
484 + });
485 +
486 + it('When the drawing shape is in 3th quadrant, "right" and "bottom" are the same as start point.', () => {
487 + spyOn(canvas, 'getPointer').and.returnValue({
488 + x: 80,
489 + y: 80,
490 + });
491 +
492 + startPoint = shapeObj.getPointByOrigin('right', 'bottom');
493 +
494 + shape._onFabricMouseMove(fEvent);
495 + shape._onFabricMouseUp();
496 +
497 + expectedPoint = shapeObj.getPointByOrigin('right', 'bottom');
498 +
499 + expect(expectedPoint.x).toBe(startPoint.x);
500 + expect(expectedPoint.y).toBe(startPoint.y);
501 + });
502 +
503 + it('When the drawing shape is in 4th quadrant, "left" and "bottom" are the same as start point.', () => {
504 + spyOn(canvas, 'getPointer').and.returnValue({
505 + x: 120,
506 + y: 80,
507 + });
508 +
509 + startPoint = shapeObj.getPointByOrigin('left', 'bottom');
510 +
511 + shape._onFabricMouseMove(fEvent);
512 + shape._onFabricMouseUp();
513 +
514 + expectedPoint = shapeObj.getPointByOrigin('left', 'bottom');
515 +
516 + expect(expectedPoint.x).toBe(startPoint.x);
517 + expect(expectedPoint.y).toBe(startPoint.y);
518 + });
519 + });
520 +
521 + it(
522 + 'When drawing the shape with mouse and the "isRegular" option set to true, ' +
523 + 'the created rectangle shape has the same "width" and "height" values.',
524 + () => {
525 + shape.add('rect', {
526 + left: 0,
527 + top: 0,
528 + });
529 +
530 + shape._withShiftKey = true;
531 + [shapeObj] = canvas.getObjects();
532 + shape._shapeObj = shapeObj;
533 +
534 + spyOn(canvas, 'getPointer').and.returnValue({
535 + x: 200,
536 + y: 100,
537 + });
538 +
539 + shape._onFabricMouseMove(fEvent);
540 + shape._onFabricMouseUp();
541 +
542 + expect(shapeObj.width).toBe(200); // has 1 storkeWidth
543 + expect(shapeObj.height).toBe(200); // has 1 storkeWidth
544 + }
545 + );
546 +
547 + it(
548 + 'When drawing the shape with mouse and the "isRegular" option set to true, ' +
549 + 'the created rectangle shape has the same "width" and "height" values.',
550 + () => {
551 + shape.add('rect', {
552 + left: 0,
553 + top: 0,
554 + });
555 +
556 + shape._withShiftKey = true;
557 + [shapeObj] = canvas.getObjects();
558 + shape._shapeObj = shapeObj;
559 +
560 + spyOn(canvas, 'getPointer').and.returnValue({
561 + x: 100,
562 + y: 200,
563 + });
564 +
565 + shape._onFabricMouseMove(fEvent);
566 + shape._onFabricMouseUp();
567 +
568 + expect(shapeObj.width).toBe(200); // has 1 storkeWidth
569 + expect(shapeObj.height).toBe(200); // has 1 storkeWidth
570 + }
571 + );
572 +});
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Test cases of "src/js/component/text.js"
4 + */
5 +import fabric from 'fabric';
6 +import $ from 'jquery';
7 +import Graphics from '../src/js/graphics';
8 +import Text from '../src/js/component/text';
9 +
10 +describe('Text', () => {
11 + let canvas, graphics, mockImage, text;
12 +
13 + beforeAll(() => {
14 + graphics = new Graphics($('<canvas>')[0]);
15 + canvas = graphics.getCanvas();
16 + text = new Text(graphics);
17 + });
18 +
19 + beforeEach(() => {
20 + mockImage = new fabric.Image();
21 + graphics.setCanvasImage('mockImage', mockImage);
22 + });
23 +
24 + afterEach(() => {
25 + canvas.forEachObject((obj) => {
26 + canvas.remove(obj);
27 + });
28 + });
29 +
30 + describe('add()', () => {
31 + let activeObj;
32 +
33 + beforeEach(() => {
34 + text.add('', {});
35 +
36 + activeObj = canvas.getActiveObject();
37 + });
38 +
39 + it('should make the blank text object when text parameter is empty string.', () => {
40 + const newText = activeObj.text;
41 +
42 + expect(newText).toEqual('');
43 + });
44 +
45 + it('should make the text object set default option when parameter has not "styles" property.', () => {
46 + const newTextStyle = activeObj.fontWeight;
47 +
48 + expect(newTextStyle).toEqual('normal');
49 + });
50 +
51 + it('should create the text object on center of canvas when parameter has not "position" property.', () => {
52 + const mockImagePos = mockImage.getCenterPoint();
53 +
54 + expect(activeObj.left).toEqual(mockImagePos.x);
55 + expect(activeObj.top).toEqual(mockImagePos.y);
56 + });
57 +
58 + it('Default option for autofocus should be true when adding text.', (done) => {
59 + text.add('default', {}).then((info) => {
60 + const newText = graphics.getObject(info.id);
61 +
62 + expect(newText.selectionStart).toBe(0);
63 + expect(newText.selectionEnd).toBe(7);
64 + expect(newText.isEditing).toBe(true);
65 +
66 + done();
67 + });
68 + });
69 + });
70 +
71 + it('Rotated text elements must also maintain consistent left and top positions after entering and exiting drawing mode.', () => {
72 + const left = 10;
73 + const top = 20;
74 + const newText = new fabric.IText('testString', {
75 + left,
76 + top,
77 + width: 30,
78 + height: 50,
79 + angle: 40,
80 + originX: 'center',
81 + originY: 'center',
82 + });
83 + text.useItext = true;
84 + canvas.add(newText);
85 +
86 + text.start();
87 + text.end();
88 +
89 + expect(newText.left).toEqual(left);
90 + expect(newText.top).toEqual(top);
91 + });
92 +
93 + it('change() should change contents in the text object as input.', () => {
94 + text.add('text123', {});
95 +
96 + const activeObj = canvas.getActiveObject();
97 +
98 + text.change(activeObj, 'abc');
99 +
100 + expect(activeObj.text).toEqual('abc');
101 +
102 + text.change(activeObj, 'def');
103 +
104 + expect(activeObj.text).toEqual('def');
105 + });
106 +
107 + describe('setStyle()', () => {
108 + beforeEach(() => {
109 + text.add('new text', {
110 + styles: {
111 + fontWeight: 'bold',
112 + },
113 + });
114 + });
115 +
116 + it('should unlock style when a selected style already apply on the activated text object.', () => {
117 + const activeObj = canvas.getActiveObject();
118 +
119 + text.setStyle(activeObj, {
120 + fontWeight: 'bold',
121 + });
122 +
123 + expect(activeObj.fontWeight).not.toEqual('bold');
124 + });
125 +
126 + it('should apply style when the activated text object has not a selected style.', () => {
127 + const activeObj = canvas.getActiveObject();
128 +
129 + text.setStyle(activeObj, {
130 + fontStyle: 'italic',
131 + });
132 +
133 + expect(activeObj.fontStyle).toEqual('italic');
134 + });
135 + });
136 +
137 + it('_onFabricScaling() should change size of selected text object.', () => {
138 + const obj = new fabric.Text('test');
139 + const mock = {
140 + target: obj,
141 + };
142 + const scale = 10;
143 + const originSize = obj.fontSize;
144 +
145 + text.start({});
146 +
147 + canvas.add(obj);
148 + obj.scaleY = scale;
149 +
150 + canvas.fire('object:scaling', mock);
151 +
152 + expect(obj.fontSize).toEqual(originSize * scale);
153 + });
154 +});
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Test cases of "src/js/component/cropper.js"
4 + */
5 +import snippet from 'tui-code-snippet';
6 +import Theme from '../src/js/ui/theme/theme';
7 +import defaultTheme from '../src/js/ui/theme/standard';
8 +
9 +describe('Theme', () => {
10 + let theme;
11 + beforeEach(() => {
12 + theme = new Theme(defaultTheme);
13 + });
14 + describe('getStyle()', () => {
15 + it('When the user sets the icon file location, the path and name information must be included.', () => {
16 + const addUserIconPath = 'base/test/fixtures/icon-d.svg';
17 + const addUserIconName = 'icon-d';
18 + const themeForIconPathSet = new Theme(
19 + snippet.extend({}, defaultTheme, {
20 + 'menu.normalIcon.path': addUserIconPath,
21 + 'menu.normalIcon.name': addUserIconName,
22 + })
23 + );
24 + const {
25 + normal: { path, name },
26 + } = themeForIconPathSet.getStyle('menu.icon');
27 +
28 + expect(path).toEqual('base/test/fixtures/icon-d.svg');
29 + expect(name).toEqual('icon-d');
30 + });
31 +
32 + it('should return default icon color information.', () => {
33 + const { normal, active, disabled, hover } = theme.getStyle('menu.icon');
34 +
35 + expect(normal.color).toEqual('#8a8a8a');
36 + expect(active.color).toEqual('#555555');
37 + expect(disabled.color).toEqual('#434343');
38 + expect(hover.color).toEqual('#e9e9e9');
39 + });
40 +
41 + it('In normal types, cssText should be returned.', () => {
42 + theme.styles.normal = {
43 + backgroundColor: '#fdba3b',
44 + border: '1px solid #fdba3b',
45 + color: '#fff',
46 + fontFamily: 'NotoSans, sans-serif',
47 + fontSize: '12px',
48 + };
49 +
50 + const expected =
51 + 'background-color: #fdba3b;border: 1px solid #fdba3b;color: #fff;font-family: NotoSans, sans-serif;font-size: 12px';
52 + expect(theme.getStyle('normal')).toBe(expected);
53 + });
54 +
55 + it('If all members are objects, you must leave the structure intact and return cssText.', () => {
56 + theme.styles['submenu.normalLabel'] = {
57 + color: '#858585',
58 + fontWeight: 'normal',
59 + };
60 + theme.styles['submenu.activeLabel'] = {
61 + color: '#000',
62 + fontWeight: 'normal',
63 + };
64 +
65 + const expected = {
66 + normal: 'color: #858585;font-weight: normal',
67 + active: 'color: #000;font-weight: normal',
68 + };
69 + expect(theme.getStyle('submenu.label')).toEqual(expected);
70 + });
71 + });
72 +
73 + describe('_makeCssText()', () => {
74 + it('Should return the cssText of the expected value for the object.', () => {
75 + const styleObject = {
76 + backgroundColor: '#fff',
77 + backgroundImage: './img/bg.png',
78 + border: '1px solid #ddd',
79 + color: '#222',
80 + fontFamily: 'NotoSans, sans-serif',
81 + fontSize: '12px',
82 + };
83 + const expected =
84 + 'background-color: #fff;background-image: url(./img/bg.png);border: 1px solid #ddd;color: #222;font-family: NotoSans, sans-serif;font-size: 12px';
85 + expect(theme._makeCssText(styleObject)).toBe(expected);
86 + });
87 + });
88 +
89 + describe('_makeSvgItem()', () => {
90 + it('When using the default icon, a svg set with the path prefix and no use-default class should be created.', () => {
91 + const useTagString = theme._makeSvgItem(['normal'], 'crop');
92 +
93 + expect(useTagString).toBe('<use xlink:href="#ic-crop" class="normal use-default"/>');
94 + });
95 +
96 + it('Setting the icon file should create a svg path with the prefix.', () => {
97 + const themeForIconPathSet = new Theme(
98 + snippet.extend({}, defaultTheme, {
99 + 'menu.normalIcon.path': 'base/test/fixtures/icon-d.svg',
100 + 'menu.normalIcon.name': 'icon-d',
101 + })
102 + );
103 + const useTagString = themeForIconPathSet._makeSvgItem(['normal'], 'crop');
104 +
105 + expect(useTagString).toBe(
106 + '<use xlink:href="base/test/fixtures/icon-d.svg#icon-d-ic-crop" class="normal"/>'
107 + );
108 + });
109 + });
110 +});
1 +{
2 + "compilerOptions": {
3 + "noEmit": true,
4 + "noImplicitAny": false
5 + },
6 + "include": ["../../index.d.ts", "./type-tests.ts"]
7 +}
1 +import ImageEditor = require('tui-image-editor');
2 +
3 +const blackTheme = {
4 + 'common.bi.image': 'https://uicdn.toast.com/toastui/img/tui-image-editor-bi.png',
5 + 'common.bisize.width': '251px',
6 + 'common.bisize.height': '21px',
7 + 'common.backgroundImage': 'none',
8 + 'common.backgroundColor': '#1e1e1e',
9 + 'common.border': '0px',
10 +
11 + // header
12 + 'header.backgroundImage': 'none',
13 + 'header.backgroundColor': 'transparent',
14 + 'header.border': '0px',
15 +
16 + // load button
17 + 'loadButton.backgroundColor': '#fff',
18 + 'loadButton.border': '1px solid #ddd',
19 + 'loadButton.color': '#222',
20 + 'loadButton.fontFamily': 'NotoSans, sans-serif',
21 + 'loadButton.fontSize': '12px',
22 +
23 + // download button
24 + 'downloadButton.backgroundColor': '#fdba3b',
25 + 'downloadButton.border': '1px solid #fdba3b',
26 + 'downloadButton.color': '#fff',
27 + 'downloadButton.fontFamily': 'NotoSans, sans-serif',
28 + 'downloadButton.fontSize': '12px',
29 +
30 + // main icons
31 + 'menu.normalIcon.path': '../dist/svg/icon-b.svg',
32 + 'menu.normalIcon.name': 'icon-b',
33 + 'menu.activeIcon.path': '../dist/svg/icon-a.svg',
34 + 'menu.activeIcon.name': 'icon-a',
35 + 'menu.iconSize.width': '24px',
36 + 'menu.iconSize.height': '24px',
37 +
38 + // submenu primary color
39 + 'submenu.backgroundColor': '#1e1e1e',
40 + 'submenu.partition.color': '#858585',
41 +
42 + // submenu icons
43 + 'submenu.normalIcon.path': '../dist/svg/icon-a.svg',
44 + 'submenu.normalIcon.name': 'icon-a',
45 + 'submenu.activeIcon.path': '../dist/svg/icon-c.svg',
46 + 'submenu.activeIcon.name': 'icon-c',
47 + 'submenu.iconSize.width': '32px',
48 + 'submenu.iconSize.height': '32px',
49 +
50 + // submenu labels
51 + 'submenu.normalLabel.color': '#858585',
52 + 'submenu.normalLabel.fontWeight': 'lighter',
53 + 'submenu.activeLabel.color': '#fff',
54 + 'submenu.activeLabel.fontWeight': 'lighter',
55 +
56 + // checkbox style
57 + 'checkbox.border': '1px solid #ccc',
58 + 'checkbox.backgroundColor': '#fff',
59 +
60 + // rango style
61 + 'range.pointer.color': '#fff',
62 + 'range.bar.color': '#666',
63 + 'range.subbar.color': '#d1d1d1',
64 + 'range.value.color': '#fff',
65 + 'range.value.fontWeight': 'lighter',
66 + 'range.value.fontSize': '11px',
67 + 'range.value.border': '1px solid #353535',
68 + 'range.value.backgroundColor': '#151515',
69 + 'range.title.color': '#fff',
70 + 'range.title.fontWeight': 'lighter',
71 +
72 + // colorpicker style
73 + 'colorpicker.button.border': '1px solid #1e1e1e',
74 + 'colorpicker.title.color': '#fff',
75 +};
76 +
77 +const imageEditor = new ImageEditor('#container', {
78 + includeUI: {
79 + loadImage: {
80 + path: 'img/sampleImage.jpg',
81 + name: 'SampleImage',
82 + },
83 + theme: blackTheme,
84 + menu: ['shape', 'filter'],
85 + initMenu: 'filter',
86 + uiSize: {
87 + width: '1000px',
88 + height: '700px',
89 + },
90 + menuBarPosition: 'bottom',
91 + },
92 + cssMaxWidth: 700,
93 + cssMaxHeight: 500,
94 + selectionStyle: {
95 + cornerSize: 20,
96 + rotatingPointOffset: 70,
97 + },
98 +});
99 +
100 +imageEditor.addIcon('arrow');
101 +imageEditor
102 + .addIcon('cancel', {
103 + left: 100,
104 + top: 100,
105 + })
106 + .then((objectProps) => {
107 + console.log(objectProps.id);
108 + });
109 +
110 +imageEditor.addImageObject('path/fileName.jpg').then((objectProps) => {
111 + console.log(objectProps);
112 +});
113 +
114 +imageEditor.addShape('rect', {
115 + fill: 'red',
116 + stroke: 'blue',
117 + strokeWidth: 3,
118 + width: 100,
119 + height: 200,
120 + left: 10,
121 + top: 10,
122 + isRegular: true,
123 +});
124 +
125 +imageEditor
126 + .addShape('circle', {
127 + fill: 'red',
128 + stroke: 'blue',
129 + strokeWidth: 3,
130 + rx: 10,
131 + ry: 100,
132 + isRegular: false,
133 + })
134 + .then((objectProps) => {
135 + console.log(objectProps.id);
136 + });
137 +
138 +imageEditor
139 + .addText('initText', {
140 + styles: {
141 + fill: '#000',
142 + fontSize: 20,
143 + fontWeight: 'bold',
144 + },
145 + position: {
146 + x: 10,
147 + y: 10,
148 + },
149 + })
150 + .then((objectProps) => {
151 + console.log(objectProps.id);
152 + });
153 +
154 +imageEditor.applyFilter('Grayscale');
155 +imageEditor
156 + .applyFilter('mask', {
157 + maskObjId: 0,
158 + })
159 + .then((obj) => {
160 + console.log(`filterType: ${obj.type}`);
161 + console.log(`actType: ${obj.action}`);
162 + });
163 +
164 +imageEditor.changeCursor('crosshair');
165 +imageEditor.changeIconColor(0, '#000000');
166 +imageEditor.changeSelectableAll(false);
167 +imageEditor.changeShape(0, {
168 + fill: 'red',
169 + stroke: 'blue',
170 + strokeWidth: 3,
171 + rx: 10,
172 + ry: 100,
173 +});
174 +
175 +imageEditor.changeText(0, 'change text');
176 +imageEditor.changeTextStyle(0, {
177 + fontStyle: 'italic',
178 +});
179 +
180 +imageEditor.clearObjects();
181 +imageEditor.clearRedoStack();
182 +imageEditor.clearUndoStack();
183 +
184 +imageEditor.crop(imageEditor.getCropzoneRect());
185 +imageEditor.deactivateAll();
186 +imageEditor.destroy();
187 +imageEditor.discardSelection();
188 +imageEditor
189 + .flipX()
190 + .then((status) => {
191 + console.log(`flipX: ${status.flipX}`);
192 + console.log(`flipY: ${status.flipY}`);
193 + console.log(`angle: ${status.angle}`);
194 + })
195 + .catch((message) => {
196 + console.log(`error: ${message}`);
197 + });
198 +imageEditor.flipY();
199 +imageEditor.getCanvasSize();
200 +imageEditor.getCropzoneRect();
201 +imageEditor.getDrawingMode();
202 +imageEditor.getImageName();
203 +imageEditor.getObjectPosition(0, 'left', 'top');
204 +imageEditor.getObjectProperties(0, 'left');
205 +imageEditor.getObjectProperties(0, ['left', 'top', 'width', 'height']);
206 +imageEditor.getObjectProperties(0, {
207 + left: null,
208 + top: null,
209 + height: null,
210 + opacity: null,
211 +});
212 +
213 +imageEditor.hasFilter('filterType');
214 +imageEditor.isEmptyRedoStack();
215 +imageEditor.isEmptyUndoStack();
216 +let fileObj: any;
217 +imageEditor.loadImageFromFile(fileObj, 'SampleImage').then((result) => {
218 + console.log(`old: ${result.oldWidth}, ${result.oldHeight}`);
219 + console.log(`new: ${result.newWidth}, ${result.newHeight}`);
220 +});
221 +imageEditor.loadImageFromURL('http://url/testImage.png', 'lena').then((result) => {
222 + console.log(`old: ${result.oldWidth}, ${result.oldHeight}`);
223 + console.log(`new: ${result.newWidth}, ${result.newHeight}`);
224 +});
225 +imageEditor.redo();
226 +imageEditor.registerIcons({
227 + customIcon: 'M 0 0 L 20 20 L 10 10 Z',
228 + customArrow: 'M 60 0 L 120 60 H 90 L 75 45 V 180 H 45 V 45 L 30 60 H 0 Z',
229 +});
230 +imageEditor.removeActiveObject();
231 +imageEditor
232 + .removeFilter('Grayscale')
233 + .then((obj) => {
234 + console.log(`filterType: ${obj.type}`);
235 + console.log(`actType: ${obj.action}`);
236 + })
237 + .catch((message) => {
238 + console.log(`error : ${message}`);
239 + });
240 +imageEditor.removeObject(0);
241 +imageEditor.resetFlip().then((status) => {
242 + console.log(`filpX : ${status.flipX}`);
243 + console.log(`flipY : ${status.flipY}`);
244 + console.log(`angle : ${status.angle}`);
245 +});
246 +
247 +imageEditor.resizeCanvasDimension({
248 + width: 300,
249 + height: 300,
250 +});
251 +imageEditor.rotate(10);
252 +imageEditor.setAngle(45);
253 +imageEditor.setBrush({
254 + width: 12,
255 + color: 'rgba(0, 0, 0, 0.5)',
256 +});
257 +imageEditor.setBrush({
258 + width: 20,
259 + color: '#FFFFFF',
260 +});
261 +imageEditor.setCropzoneRect(1 / 1);
262 +imageEditor.setDrawingShape('rect', {
263 + fill: 'red',
264 + width: 100,
265 + height: 200,
266 +});
267 +imageEditor.setDrawingShape('circle', {
268 + rx: 10,
269 + ry: 10,
270 + isRegular: true,
271 +});
272 +imageEditor.setObjectPosition(0, {
273 + x: 0,
274 + y: 0,
275 + originX: 'left',
276 + originY: 'top',
277 +});
278 +imageEditor
279 + .setObjectProperties(0, {
280 + left: 100,
281 + top: 100,
282 + width: 200,
283 + height: 200,
284 + opacity: 0.5,
285 + })
286 + .then((arg) => {
287 + console.log(arg);
288 + });
289 +imageEditor
290 + .setObjectPropertiesQuietly(0, {
291 + left: 100,
292 + top: 100,
293 + width: 200,
294 + height: 200,
295 + opacity: 0.5,
296 + })
297 + .then((arg) => {
298 + console.log(arg);
299 + });
300 +imageEditor.startDrawingMode('FREE_DRWARING', {
301 + width: 10,
302 + color: 'rgba(255, 0, 0, 0.5)',
303 +});
304 +imageEditor.stopDrawingMode();
305 +imageEditor.toDataURL();
306 +imageEditor.undo();
307 +
308 +imageEditor.on('addText', (pos) => {
309 + imageEditor.addText('Double Click', {
310 + position: pos.originPosition,
311 + });
312 +
313 + console.log(`text position on canvas : ${pos.originPosition}`);
314 + console.log(`text position on browser : ${pos.clientPosition}`);
315 +});
316 +imageEditor.ui.resizeEditor({ uiSize: { width: '600px', height: '1200px' } });
317 +imageEditor.ui.resizeEditor({ imageSize: { newWidth: 300, newHeight: 140 } });
1 +/**
2 + * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
3 + * @fileoverview Test cases of "src/js/ui.js"
4 + */
5 +import snippet from 'tui-code-snippet';
6 +import { Promise } from '../src/js/util';
7 +import UI from '../src/js/ui';
8 +import { HELP_MENUS } from '../src/js/consts';
9 +
10 +describe('UI', () => {
11 + let ui;
12 + let uiOptions;
13 + beforeEach(() => {
14 + uiOptions = {
15 + loadImage: {
16 + path: 'mockImagePath',
17 + name: '',
18 + },
19 + menu: ['crop', 'flip', 'rotate', 'draw', 'shape', 'icon', 'text', 'mask', 'filter'],
20 + initMenu: 'shape',
21 + menuBarPosition: 'bottom',
22 + };
23 + ui = new UI(document.createElement('div'), uiOptions, {});
24 + });
25 +
26 + describe('Destroy()', () => {
27 + it('"_destroyAllMenu()" The "destroy" function of all menu instances must be executed.', () => {
28 + snippet.forEach(uiOptions.menu, (menuName) => {
29 + spyOn(ui[menuName], 'destroy');
30 + });
31 +
32 + ui._destroyAllMenu();
33 +
34 + snippet.forEach(uiOptions.menu, (menuName) => {
35 + expect(ui[menuName].destroy).toHaveBeenCalled();
36 + });
37 + });
38 +
39 + it('"_removeUiEvent()" must execute "removeEventListener" of all menus.', () => {
40 + const allUiButtonElementName = [...uiOptions.menu, ...HELP_MENUS];
41 + snippet.forEach(allUiButtonElementName, (elementName) => {
42 + spyOn(ui._buttonElements[elementName], 'removeEventListener');
43 + });
44 +
45 + ui._removeUiEvent();
46 +
47 + snippet.forEach(allUiButtonElementName, (elementName) => {
48 + expect(ui._buttonElements[elementName].removeEventListener).toHaveBeenCalled();
49 + });
50 + });
51 + });
52 +
53 + describe('_changeMenu()', () => {
54 + beforeEach(() => {
55 + ui.submenu = 'shape';
56 + spyOn(ui, 'resizeEditor');
57 + spyOn(ui.shape, 'changeStandbyMode');
58 + spyOn(ui.filter, 'changeStartMode');
59 + ui._actions.main = {
60 + changeSelectableAll: jasmine.createSpy('changeSelectableAll'),
61 + };
62 + ui._changeMenu('filter', false, false);
63 + });
64 + it('When the menu changes, the changeStartMode () of the menu instance to be changed must be executed.', () => {
65 + expect(ui.shape.changeStandbyMode).toHaveBeenCalled();
66 + });
67 +
68 + it('When the menu changes, the changeStandbyMode () of the existing menu instance must be executed.', () => {
69 + expect(ui.filter.changeStartMode).toHaveBeenCalled();
70 + });
71 + });
72 +
73 + describe('_makeSubMenu()', () => {
74 + it('MakeMenuElement should be executed for the number of menus specified in the option.', () => {
75 + spyOn(ui, '_makeMenuElement');
76 +
77 + ui._makeSubMenu();
78 + expect(ui._makeMenuElement.calls.count()).toBe(uiOptions.menu.length);
79 + });
80 +
81 + it('Instance of the menu specified in the option must be created.', () => {
82 + spyOn(ui, '_makeMenuElement');
83 + const getConstructorName = (constructor) =>
84 + constructor.toString().match(/^function\s(.+?)\(/)[1];
85 +
86 + ui._makeSubMenu();
87 + snippet.forEach(uiOptions.menu, (menuName) => {
88 + const constructorNameOfInstance = getConstructorName(ui[menuName].constructor);
89 + const expected = menuName.replace(/^[a-z]/, ($0) => $0.toUpperCase());
90 + expect(constructorNameOfInstance).toBe(expected);
91 + });
92 + });
93 + });
94 +
95 + describe('initCanvas()', () => {
96 + let promise;
97 +
98 + beforeEach(() => {
99 + promise = new Promise((resolve) => {
100 + resolve();
101 + });
102 + ui._editorElement = {
103 + querySelector: jasmine
104 + .createSpy('querySelector')
105 + .and.returnValue(document.createElement('div')),
106 + };
107 + ui._actions.main = {
108 + initLoadImage: jasmine.createSpy('initLoadImage').and.returnValue(promise),
109 + };
110 + });
111 +
112 + it('When initCanvas is executed, some internal methods must be run as required.', (done) => {
113 + spyOn(ui, 'activeMenuEvent');
114 + spyOn(ui, '_addLoadEvent');
115 +
116 + ui.initCanvas();
117 + promise.then(() => {
118 + expect(ui.activeMenuEvent).toHaveBeenCalled();
119 + expect(ui._addLoadEvent).toHaveBeenCalled();
120 + done();
121 + });
122 + });
123 +
124 + it('`initLoadImage()` should not be run when has not image path.', () => {
125 + spyOn(ui, '_getLoadImage').and.returnValue({ path: '' });
126 +
127 + ui.initCanvas();
128 +
129 + expect(ui._actions.main.initLoadImage).not.toHaveBeenCalled();
130 + });
131 +
132 + it('`_AddLoadEvent()` should be executed even if there is no image path.', () => {
133 + spyOn(ui, '_getLoadImage').and.returnValue({ path: '' });
134 + spyOn(ui, '_addLoadEvent');
135 +
136 + ui.initCanvas();
137 +
138 + expect(ui._addLoadEvent).toHaveBeenCalled();
139 + });
140 + });
141 +
142 + describe('_setEditorPosition()', () => {
143 + beforeEach(() => {
144 + ui._editorElement = document.createElement('div');
145 + spyOn(ui, '_getCanvasMaxDimension').and.returnValue({
146 + width: 300,
147 + height: 300,
148 + });
149 + });
150 +
151 + it('Position is bottom, it should be reflected in the bottom of the editor position.', () => {
152 + ui.submenu = true;
153 + ui._setEditorPosition('bottom');
154 +
155 + expect(ui._editorElement.style.top).toBe('150px');
156 + expect(ui._editorElement.style.left).toBe('0px');
157 + });
158 +
159 + it('Position is top, it should be reflected in the top of the editor position.', () => {
160 + ui.submenu = true;
161 + ui._setEditorPosition('top');
162 +
163 + expect(ui._editorElement.style.top).toBe('-150px');
164 + expect(ui._editorElement.style.left).toBe('0px');
165 + });
166 + it('Position is left, it should be reflected in the left, right of the editor position.', () => {
167 + ui.submenu = true;
168 + ui._setEditorPosition('left');
169 +
170 + expect(ui._editorElement.style.top).toBe('0px');
171 + expect(ui._editorElement.style.left).toBe('-150px');
172 + });
173 + it('Position is right, it should be reflected in the right of the editor position.', () => {
174 + ui.submenu = true;
175 + ui._setEditorPosition('right');
176 +
177 + expect(ui._editorElement.style.top).toBe('0px');
178 + expect(ui._editorElement.style.left).toBe('150px');
179 + });
180 + });
181 +});
1 +import Range from '../src/js/ui/tools/range';
2 +import { defaultRotateRangeValus } from '../src/js/consts';
3 +
4 +describe('Range', () => {
5 + let range, input, slider;
6 + beforeEach(() => {
7 + input = document.createElement('input');
8 + slider = document.createElement('div');
9 + range = new Range(
10 + {
11 + slider,
12 + input,
13 + },
14 + defaultRotateRangeValus
15 + );
16 + });
17 +
18 + it('The value must be incremented by 1, when keyCode 38 is found in the event handler with changeInputWithArrow.', () => {
19 + const ev = {
20 + target: input,
21 + keyCode: 38,
22 + };
23 + input.value = '3';
24 + range.eventHandler.changeInputWithArrow(ev);
25 +
26 + expect(range.value).toBe(4);
27 + });
28 + it('The value must be decremented by 1, when keyCode 40 is found in the event handler with changeInputWithArrow.', () => {
29 + const ev = {
30 + target: input,
31 + keyCode: 40,
32 + };
33 + input.value = '3';
34 + range.eventHandler.changeInputWithArrow(ev);
35 +
36 + expect(range.value).toBe(2);
37 + });
38 +
39 + it('The `changeInput` event handler should filter out any invalid input values.', () => {
40 + const ev = {
41 + target: input,
42 + keyCode: 83,
43 + };
44 + input.value = '-3!!6s0s';
45 +
46 + range.eventHandler.changeInput(ev);
47 + expect(range.value).toBe(-360);
48 + });
49 +});
1 +/*eslint-disable*/
2 +var fs = require('fs');
3 +var path = require('path');
4 +var pkg = require('./package.json');
5 +
6 +var tsVersion = /[0-9.]+/.exec(pkg.devDependencies.typescript)[0];
7 +var declareFilePath = path.join(__dirname, 'index.d.ts');
8 +var declareRows = [];
9 +var TS_BANNER = [
10 + '// Type definitions for TOAST UI Image Editor v' + pkg.version,
11 + '// TypeScript Version: ' + tsVersion,
12 +].join('\n');
13 +
14 +fs.readFile(declareFilePath, 'utf8', function (error, data) {
15 + if (error) {
16 + throw error;
17 + }
18 +
19 + declareRows = data.toString().split('\n');
20 + declareRows.splice(0, 2, TS_BANNER);
21 +
22 + fs.writeFile(declareFilePath, declareRows.join('\n'), 'utf8', function (error, data) {
23 + if (error) {
24 + throw error;
25 + }
26 +
27 + console.log('Completed Write Banner for Typescript!');
28 + });
29 +});
1 +{
2 + "extends": "tslint:recommended",
3 + "rules": {
4 + "quotemark": [true, "single"],
5 + "trailing-comma": false,
6 + "max-classes-per-file": false,
7 + "no-namespace": false
8 + }
9 +}
1 +{
2 + "header": {
3 + "logo": {
4 + "src": "https://uicdn.toast.com/toastui/img/tui-image-editor-bi-white.png"
5 + },
6 + "title": {
7 + "text": "repo",
8 + "linkUrl": "https://github.com/nhn/tui.image-editor"
9 + }
10 + },
11 + "footer": [
12 + {
13 + "title": "NHN",
14 + "linkUrl": "https://github.com/nhn"
15 + },
16 + {
17 + "title": "FE Development Lab",
18 + "linkUrl": "https://github.com/nhn/fe.javascript"
19 + }
20 + ],
21 + "main": {
22 + "filePath": "README.md"
23 + },
24 + "api": {
25 + "filePath": "src/js/**",
26 + "fileLink": true
27 + },
28 + "examples": {
29 + "filePath": "examples",
30 + "titles": {
31 + "example01-includeUi": "1. Include ui",
32 + "example02-useApiDirect": "2. Use api direct (basic)",
33 + "example03-mobile": "3. Mobile"
34 + },
35 + "globalErrorLogVariable": "errorLogs"
36 + },
37 + "pathPrefix": "tui.image-editor"
38 +}
1 +/**
2 + * webpack.config.js created on 2016. 12. 01.
3 + * @author NHN Ent. FE Development Lab <dl_javascript@nhn.com>
4 + */
5 +const pkg = require('./package.json');
6 +const path = require('path');
7 +const webpack = require('webpack');
8 +const SafeUmdPlugin = require('safe-umd-webpack-plugin');
9 +const MiniCssExtractPlugin = require('mini-css-extract-plugin');
10 +const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
11 +const OptimizaeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
12 +
13 +const isProduction = process.argv.indexOf('-p') > -1;
14 +
15 +const FILENAME = pkg.name + (isProduction ? '.min' : '');
16 +const BANNER = [
17 + `${FILENAME}.js`,
18 + `@version ${pkg.version}`,
19 + `@author ${pkg.author}`,
20 + `@license ${pkg.license}`,
21 +].join('\n');
22 +
23 +module.exports = {
24 + mode: isProduction ? 'production' : 'development',
25 + entry: './src/index.js',
26 + output: {
27 + library: ['tui', 'ImageEditor'],
28 + libraryTarget: 'umd',
29 + path: path.resolve(__dirname, 'dist'),
30 + publicPath: '/dist',
31 + filename: `${FILENAME}.js`,
32 + },
33 + externals: [
34 + {
35 + 'tui-code-snippet': {
36 + commonjs: 'tui-code-snippet',
37 + commonjs2: 'tui-code-snippet',
38 + amd: 'tui-code-snippet',
39 + root: ['tui', 'util'],
40 + },
41 + 'tui-color-picker': {
42 + commonjs: 'tui-color-picker',
43 + commonjs2: 'tui-color-picker',
44 + amd: 'tui-color-picker',
45 + root: ['tui', 'colorPicker'],
46 + },
47 + fabric: {
48 + commonjs: ['fabric', 'fabric'],
49 + commonjs2: ['fabric', 'fabric'],
50 + amd: 'fabric',
51 + root: 'fabric',
52 + },
53 + },
54 + ],
55 + module: {
56 + rules: [
57 + {
58 + test: /\.js$/,
59 + exclude: /node_modules/,
60 + loader: 'eslint-loader',
61 + enforce: 'pre',
62 + options: {
63 + failOnWarning: false,
64 + failOnError: false,
65 + },
66 + },
67 + {
68 + test: /\.js$/,
69 + exclude: /node_modules/,
70 + loader: 'babel-loader?cacheDirectory',
71 + options: {
72 + babelrc: true,
73 + },
74 + },
75 + {
76 + test: /\.styl$/,
77 + use: [
78 + MiniCssExtractPlugin.loader,
79 + {
80 + loader: 'css-loader',
81 + options: {
82 + sourceMap: true,
83 + },
84 + },
85 + {
86 + loader: 'stylus-loader',
87 + options: {
88 + sourceMap: true,
89 + },
90 + },
91 + ],
92 + },
93 + {
94 + test: /\.svg$/,
95 + loader: 'svg-inline-loader',
96 + },
97 + ],
98 + },
99 + plugins: [
100 + new webpack.BannerPlugin(BANNER),
101 + new MiniCssExtractPlugin({
102 + filename: `${FILENAME}.css`,
103 + }),
104 + new SafeUmdPlugin(),
105 + ],
106 + optimization: {
107 + minimizer: [
108 + new UglifyJsPlugin({
109 + cache: true,
110 + parallel: true,
111 + sourceMap: true,
112 + }),
113 + new OptimizaeCSSAssetsPlugin({
114 + cssProcessorOptions: {
115 + map: {
116 + inline: false,
117 + },
118 + },
119 + }),
120 + ],
121 + },
122 + devServer: {
123 + historyApiFallback: false,
124 + progress: true,
125 + inline: true,
126 + host: '0.0.0.0',
127 + disableHostCheck: true,
128 + },
129 +};